Skip to content

Commit fd708f0

Browse files
committed
feat(auth): allow custom token file path via WHOOP_TOKEN_FILE
1 parent 9155835 commit fd708f0

3 files changed

Lines changed: 36 additions & 13 deletions

File tree

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
WHOOP_CLIENT_ID=
33
WHOOP_CLIENT_SECRET=
44
WHOOP_REDIRECT_URI=
5+
# Optional: custom path for OAuth token storage (default: ~/.whoop-cli/tokens.json)
6+
WHOOP_TOKEN_FILE=

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ Before using, you need to configure WHOOP API credentials:
3636
export WHOOP_CLIENT_ID=your_client_id
3737
export WHOOP_CLIENT_SECRET=your_client_secret
3838
export WHOOP_REDIRECT_URI=https://your-redirect-uri.com/callback
39+
# Optional: override token storage path for multi-account setups
40+
export WHOOP_TOKEN_FILE=~/.whoop-cli/tokens-personal.json
3941
```
4042

4143
Or create a `.env` file in your working directory.
@@ -45,7 +47,9 @@ Or create a `.env` file in your working directory.
4547
whoopskill auth login
4648
```
4749

48-
Tokens are stored in `~/.whoop-cli/tokens.json` and auto-refresh when expired.
50+
Tokens are stored in `~/.whoop-cli/tokens.json` by default and auto-refresh when expired.
51+
52+
Set `WHOOP_TOKEN_FILE` to override token location (useful for multiple users/accounts on one machine).
4953

5054
## Usage
5155

@@ -96,7 +100,7 @@ Important:
96100
- For automation, you must call `whoopskill auth refresh` periodically.
97101

98102
Recommended pattern:
99-
- Run `whoopskill auth login` once interactively (creates `~/.whoop-cli/tokens.json`).
103+
- Run `whoopskill auth login` once interactively (creates the default `~/.whoop-cli/tokens.json`, or your `WHOOP_TOKEN_FILE` path if set).
100104
- Run a small periodic monitor that calls `whoopskill auth refresh` and performs a lightweight fetch.
101105

102106
An example monitor script + systemd timer/cron examples are included here:

src/auth/tokens.ts

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,38 @@
11
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from 'node:fs';
22
import { homedir } from 'node:os';
3-
import { join } from 'node:path';
3+
import { dirname, join, resolve } from 'node:path';
44
import type { TokenData, OAuthTokenResponse } from '../types/whoop.js';
55
import { WhoopError, ExitCode } from '../utils/errors.js';
66

7-
const CONFIG_DIR = join(homedir(), '.whoop-cli');
8-
const TOKEN_FILE = join(CONFIG_DIR, 'tokens.json');
7+
const DEFAULT_TOKEN_FILE = join(homedir(), '.whoop-cli', 'tokens.json');
8+
9+
function getTokenFilePath(): string {
10+
const envPath = process.env.WHOOP_TOKEN_FILE?.trim();
11+
if (!envPath) {
12+
return DEFAULT_TOKEN_FILE;
13+
}
14+
15+
// Resolve relative paths against current working directory
16+
return resolve(envPath);
17+
}
18+
19+
function getConfigDir(): string {
20+
return dirname(getTokenFilePath());
21+
}
922

1023
// Refresh tokens 15 minutes before expiry to avoid race conditions
1124
const REFRESH_BUFFER_SECONDS = 900;
1225

1326
function ensureConfigDir(): void {
14-
if (!existsSync(CONFIG_DIR)) {
15-
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
27+
const configDir = getConfigDir();
28+
if (!existsSync(configDir)) {
29+
mkdirSync(configDir, { recursive: true, mode: 0o700 });
1630
}
1731
}
1832

1933
export function saveTokens(response: OAuthTokenResponse): void {
2034
ensureConfigDir();
35+
const tokenFile = getTokenFilePath();
2136

2237
const data: TokenData = {
2338
access_token: response.access_token,
@@ -27,26 +42,28 @@ export function saveTokens(response: OAuthTokenResponse): void {
2742
scope: response.scope,
2843
};
2944

30-
writeFileSync(TOKEN_FILE, JSON.stringify(data, null, 2));
31-
chmodSync(TOKEN_FILE, 0o600);
45+
writeFileSync(tokenFile, JSON.stringify(data, null, 2));
46+
chmodSync(tokenFile, 0o600);
3247
}
3348

3449
export function loadTokens(): TokenData | null {
35-
if (!existsSync(TOKEN_FILE)) {
50+
const tokenFile = getTokenFilePath();
51+
if (!existsSync(tokenFile)) {
3652
return null;
3753
}
3854

3955
try {
40-
const content = readFileSync(TOKEN_FILE, 'utf-8');
56+
const content = readFileSync(tokenFile, 'utf-8');
4157
return JSON.parse(content) as TokenData;
4258
} catch {
4359
return null;
4460
}
4561
}
4662

4763
export function clearTokens(): void {
48-
if (existsSync(TOKEN_FILE)) {
49-
writeFileSync(TOKEN_FILE, '');
64+
const tokenFile = getTokenFilePath();
65+
if (existsSync(tokenFile)) {
66+
writeFileSync(tokenFile, '');
5067
}
5168
}
5269

0 commit comments

Comments
 (0)