@workjournal/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -0
- package/dist/auth.d.ts +28 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +164 -0
- package/dist/auth.js.map +1 -0
- package/dist/credentials.d.ts +29 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +125 -0
- package/dist/credentials.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# @workjournal/cli
|
|
2
|
+
|
|
3
|
+
Command-line tool for authenticating with [Workjournal](https://workjournal.pro).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Run on demand without installing:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @workjournal/cli login
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or install globally:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g @workjournal/cli
|
|
17
|
+
workjournal login
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
| Command | Description |
|
|
23
|
+
|---------|-------------|
|
|
24
|
+
| `workjournal login` | Interactive paste-back login (terminal only) |
|
|
25
|
+
| `workjournal login start` | Print authorize URL and store PKCE state |
|
|
26
|
+
| `workjournal login finish <CODE>` | Exchange the pasted code for credentials |
|
|
27
|
+
| `workjournal logout` | Remove stored credentials |
|
|
28
|
+
| `workjournal whoami` | Check if authenticated |
|
|
29
|
+
| `workjournal status` | Show token expiry details |
|
|
30
|
+
|
|
31
|
+
## How login works
|
|
32
|
+
|
|
33
|
+
The CLI uses OAuth out-of-band redirect (`urn:ietf:wg:oauth:2.0:oob`) with PKCE. There is no port binding, no auto-opened browser, and no callback server. The flow works equally well in local terminals, SSH sessions, dev containers, and CI.
|
|
34
|
+
|
|
35
|
+
### Interactive (terminal)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
$ workjournal login
|
|
39
|
+
Open this URL in any browser:
|
|
40
|
+
|
|
41
|
+
https://app.workjournal.pro/authorize?...
|
|
42
|
+
|
|
43
|
+
Code: ABC123DE
|
|
44
|
+
Authenticated successfully!
|
|
45
|
+
Credentials saved to ~/.workjournal/credentials.json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The CLI prints an authorize URL, you open it in any browser, log in, click Approve, copy the displayed 8-character code, paste it into the terminal. Done.
|
|
49
|
+
|
|
50
|
+
### Two-phase (assistant-driven)
|
|
51
|
+
|
|
52
|
+
When invoked by an AI assistant via a one-shot Bash call (e.g. the [Workjournal journal skill](https://github.com/workjournal-pro/skill)), the same flow runs as two independent commands with PKCE state passed via a temp file on disk:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
workjournal login start # prints URL, stores PKCE state in ~/.workjournal/cli-login-state.json
|
|
56
|
+
# user opens URL, approves, copies the displayed code
|
|
57
|
+
workjournal login finish ABC123DE # reads state, exchanges code, writes credentials
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This is what makes the same flow drivable from a skill — the assistant doesn't need an interactive stdin to a still-running process.
|
|
61
|
+
|
|
62
|
+
## Credential storage
|
|
63
|
+
|
|
64
|
+
Credentials are written to `~/.workjournal/credentials.json` with mode `0600`, in a directory created with mode `0700`.
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
~/.workjournal/
|
|
68
|
+
├── credentials.json # { access_token, refresh_token, expires_at }
|
|
69
|
+
└── cli-login-state.json # ephemeral PKCE state, deleted after `login finish`
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The CLI never stores passwords. The MCP server (`@workjournal/mcp-server`) reads `credentials.json` automatically — there's no separate setup.
|
|
73
|
+
|
|
74
|
+
## Headless environments
|
|
75
|
+
|
|
76
|
+
The CLI's normal flow already works headless — you do not need a browser on the same machine as the CLI. You only need a browser somewhere (your laptop, your phone) to load the authorize URL.
|
|
77
|
+
|
|
78
|
+
If you need to skip interactive auth entirely (for example in CI), set `WORKJOURNAL_API_KEY` to a valid Supabase JWT and the MCP server will use that instead of `credentials.json`.
|
|
79
|
+
|
|
80
|
+
## Security
|
|
81
|
+
|
|
82
|
+
- **PKCE binds the URL to the redemption.** Even if someone intercepts the displayed code, they can't redeem it without the verifier sitting on the local disk.
|
|
83
|
+
- **Codes are single-use** and expire 5 minutes after `login start`.
|
|
84
|
+
- **No tokens in URLs** — the access token never appears in any redirect.
|
|
85
|
+
- **Constant-time comparison** of the PKCE verifier on the API side prevents timing attacks.
|
|
86
|
+
- **No long-lived listener** on the user's machine — the CLI binds no sockets.
|
|
87
|
+
|
|
88
|
+
## Links
|
|
89
|
+
|
|
90
|
+
- [Workjournal](https://workjournal.pro)
|
|
91
|
+
- [Get Started](https://workjournal.pro/docs/get-started)
|
|
92
|
+
- [GitHub](https://github.com/workjournal-pro/workjournal)
|
|
93
|
+
- [CLI auth workflow reference](https://github.com/workjournal-pro/workjournal/blob/main/docs/cli-tool-auth-workflow.md)
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 1: generate PKCE, write the in-progress state file, print the authorize URL.
|
|
3
|
+
*
|
|
4
|
+
* The state file holds the verifier between this command and `loginFinish`. The
|
|
5
|
+
* assistant (or the user) is expected to open the URL, approve the request, and
|
|
6
|
+
* paste the displayed code back into a `workjournal login finish <CODE>` call.
|
|
7
|
+
*/
|
|
8
|
+
export declare function loginStart(): void;
|
|
9
|
+
/**
|
|
10
|
+
* Phase 2: read the verifier from the state file, exchange the pasted code for
|
|
11
|
+
* Supabase session tokens, persist them.
|
|
12
|
+
*
|
|
13
|
+
* On API failure the state file is left in place so the user can retry `finish`
|
|
14
|
+
* with a corrected code (subject to the server-side 5-minute TTL on the code).
|
|
15
|
+
* The state file is deleted only on a fully successful exchange.
|
|
16
|
+
*/
|
|
17
|
+
export declare function loginFinish(rawCode: string): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Convenience wrapper for direct terminal use: runs `start`, prompts for the
|
|
20
|
+
* code via readline, then runs `finish` in the same process. Same on-disk
|
|
21
|
+
* shape as the two-command flow — the state file is written and deleted as
|
|
22
|
+
* a side effect.
|
|
23
|
+
*
|
|
24
|
+
* Refuses to run when stdin is not a TTY (e.g. when invoked by an assistant
|
|
25
|
+
* via Bash) and points the caller at the two-phase commands instead.
|
|
26
|
+
*/
|
|
27
|
+
export declare function loginInteractive(): Promise<void>;
|
|
28
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAgDA;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAcjC;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBhE;AAED;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAyCtD"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { createInterface } from 'node:readline/promises';
|
|
3
|
+
import { clearLoginState, isLoginStateExpired, readLoginState, writeCredentials, writeLoginState, } from './credentials.js';
|
|
4
|
+
const APP_URL = process.env['WORKJOURNAL_APP_URL'] ?? 'https://app.workjournal.pro';
|
|
5
|
+
const API_URL = process.env['WORKJOURNAL_API_URL'] ?? 'https://api.workjournal.pro';
|
|
6
|
+
const OOB_REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob';
|
|
7
|
+
function generatePkce() {
|
|
8
|
+
const verifier = randomBytes(32).toString('base64url');
|
|
9
|
+
const challenge = createHash('sha256').update(verifier).digest('base64url');
|
|
10
|
+
return { verifier, challenge };
|
|
11
|
+
}
|
|
12
|
+
function generateState() {
|
|
13
|
+
return randomBytes(16).toString('hex');
|
|
14
|
+
}
|
|
15
|
+
function buildAuthorizeUrl(challenge) {
|
|
16
|
+
const state = generateState();
|
|
17
|
+
return (`${APP_URL}/authorize?client_id=workjournal-cli` +
|
|
18
|
+
`&redirect_uri=${encodeURIComponent(OOB_REDIRECT_URI)}` +
|
|
19
|
+
`&state=${state}` +
|
|
20
|
+
`&code_challenge=${challenge}` +
|
|
21
|
+
`&code_challenge_method=S256`);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Phase 1: generate PKCE, write the in-progress state file, print the authorize URL.
|
|
25
|
+
*
|
|
26
|
+
* The state file holds the verifier between this command and `loginFinish`. The
|
|
27
|
+
* assistant (or the user) is expected to open the URL, approve the request, and
|
|
28
|
+
* paste the displayed code back into a `workjournal login finish <CODE>` call.
|
|
29
|
+
*/
|
|
30
|
+
export function loginStart() {
|
|
31
|
+
const { verifier, challenge } = generatePkce();
|
|
32
|
+
const state = {
|
|
33
|
+
verifier,
|
|
34
|
+
challenge,
|
|
35
|
+
created_at: new Date().toISOString(),
|
|
36
|
+
};
|
|
37
|
+
writeLoginState(state);
|
|
38
|
+
const url = buildAuthorizeUrl(challenge);
|
|
39
|
+
process.stdout.write('Open this URL in any browser:\n\n');
|
|
40
|
+
process.stdout.write(` ${url}\n\n`);
|
|
41
|
+
process.stdout.write('After approving, run:\n\n');
|
|
42
|
+
process.stdout.write(' workjournal login finish <CODE>\n');
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Phase 2: read the verifier from the state file, exchange the pasted code for
|
|
46
|
+
* Supabase session tokens, persist them.
|
|
47
|
+
*
|
|
48
|
+
* On API failure the state file is left in place so the user can retry `finish`
|
|
49
|
+
* with a corrected code (subject to the server-side 5-minute TTL on the code).
|
|
50
|
+
* The state file is deleted only on a fully successful exchange.
|
|
51
|
+
*/
|
|
52
|
+
export async function loginFinish(rawCode) {
|
|
53
|
+
const code = rawCode.trim().toUpperCase();
|
|
54
|
+
if (!code) {
|
|
55
|
+
process.stderr.write('Error: missing code argument.\nUsage: workjournal login finish <CODE>\n');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
const state = readLoginState();
|
|
59
|
+
if (!state) {
|
|
60
|
+
process.stderr.write('Error: no login in progress. Run `workjournal login start` first.\n');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
if (isLoginStateExpired(state)) {
|
|
64
|
+
clearLoginState();
|
|
65
|
+
process.stderr.write('Error: login state expired. Run `workjournal login start` again.\n');
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
await exchangeAndPersist(code, state.verifier);
|
|
69
|
+
clearLoginState();
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Convenience wrapper for direct terminal use: runs `start`, prompts for the
|
|
73
|
+
* code via readline, then runs `finish` in the same process. Same on-disk
|
|
74
|
+
* shape as the two-command flow — the state file is written and deleted as
|
|
75
|
+
* a side effect.
|
|
76
|
+
*
|
|
77
|
+
* Refuses to run when stdin is not a TTY (e.g. when invoked by an assistant
|
|
78
|
+
* via Bash) and points the caller at the two-phase commands instead.
|
|
79
|
+
*/
|
|
80
|
+
export async function loginInteractive() {
|
|
81
|
+
if (!process.stdin.isTTY) {
|
|
82
|
+
process.stderr.write('Error: `workjournal login` with no subcommand requires an interactive terminal.\n' +
|
|
83
|
+
'For non-interactive use, run:\n' +
|
|
84
|
+
' workjournal login start\n' +
|
|
85
|
+
' workjournal login finish <CODE>\n');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
const { verifier, challenge } = generatePkce();
|
|
89
|
+
const state = {
|
|
90
|
+
verifier,
|
|
91
|
+
challenge,
|
|
92
|
+
created_at: new Date().toISOString(),
|
|
93
|
+
};
|
|
94
|
+
writeLoginState(state);
|
|
95
|
+
const url = buildAuthorizeUrl(challenge);
|
|
96
|
+
process.stdout.write('Open this URL in any browser:\n\n');
|
|
97
|
+
process.stdout.write(` ${url}\n\n`);
|
|
98
|
+
process.stdout.write('After approving, paste the code shown on the page below.\n\n');
|
|
99
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
100
|
+
let code;
|
|
101
|
+
try {
|
|
102
|
+
const answer = await rl.question('Code: ');
|
|
103
|
+
code = answer.trim().toUpperCase();
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
rl.close();
|
|
107
|
+
}
|
|
108
|
+
if (!code) {
|
|
109
|
+
clearLoginState();
|
|
110
|
+
process.stderr.write('No code entered. Aborting.\n');
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
await exchangeAndPersist(code, verifier);
|
|
114
|
+
clearLoginState();
|
|
115
|
+
}
|
|
116
|
+
async function exchangeAndPersist(code, verifier) {
|
|
117
|
+
const controller = new AbortController();
|
|
118
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
119
|
+
let res;
|
|
120
|
+
try {
|
|
121
|
+
res = await fetch(`${API_URL}/v1/auth/cli/exchange`, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers: { 'Content-Type': 'application/json' },
|
|
124
|
+
body: JSON.stringify({ code, code_verifier: verifier }),
|
|
125
|
+
signal: controller.signal,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
clearTimeout(timeout);
|
|
130
|
+
const message = error instanceof Error && error.name === 'AbortError'
|
|
131
|
+
? 'Request timed out after 15s'
|
|
132
|
+
: error instanceof Error
|
|
133
|
+
? error.message
|
|
134
|
+
: String(error);
|
|
135
|
+
process.stderr.write(`Failed to exchange code: ${message}\n`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
clearTimeout(timeout);
|
|
139
|
+
if (!res.ok) {
|
|
140
|
+
const text = await res.text();
|
|
141
|
+
let message = text;
|
|
142
|
+
try {
|
|
143
|
+
const parsed = JSON.parse(text);
|
|
144
|
+
if (parsed.error?.message)
|
|
145
|
+
message = parsed.error.message;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// keep raw text
|
|
149
|
+
}
|
|
150
|
+
process.stderr.write(`Failed to exchange code (${res.status}): ${message}\n`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
const data = (await res.json());
|
|
154
|
+
const expiresAt = new Date(Date.now() + data.expires_in * 1000).toISOString();
|
|
155
|
+
const creds = {
|
|
156
|
+
access_token: data.access_token,
|
|
157
|
+
expires_at: expiresAt,
|
|
158
|
+
...(data.refresh_token ? { refresh_token: data.refresh_token } : {}),
|
|
159
|
+
};
|
|
160
|
+
writeCredentials(creds);
|
|
161
|
+
process.stdout.write('\nAuthenticated successfully!\n');
|
|
162
|
+
process.stdout.write('Credentials saved to ~/.workjournal/credentials.json\n');
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface StoredCredentials {
|
|
2
|
+
access_token: string;
|
|
3
|
+
refresh_token?: string;
|
|
4
|
+
expires_at: string;
|
|
5
|
+
}
|
|
6
|
+
export interface LoginState {
|
|
7
|
+
verifier: string;
|
|
8
|
+
challenge: string;
|
|
9
|
+
created_at: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function readCredentials(): StoredCredentials | null;
|
|
12
|
+
export declare function writeCredentials(creds: StoredCredentials): void;
|
|
13
|
+
export declare function clearCredentials(): void;
|
|
14
|
+
export declare function writeLoginState(state: LoginState): void;
|
|
15
|
+
/**
|
|
16
|
+
* Read the in-progress login state file. Returns null if missing or unreadable.
|
|
17
|
+
* Does NOT enforce TTL — callers should check `created_at` against `LOGIN_STATE_TTL_MS`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function readLoginState(): LoginState | null;
|
|
20
|
+
export declare function clearLoginState(): void;
|
|
21
|
+
export declare function isLoginStateExpired(state: LoginState): boolean;
|
|
22
|
+
export declare function isExpired(creds: StoredCredentials): boolean;
|
|
23
|
+
export declare function refreshAccessToken(refreshToken: string): Promise<StoredCredentials>;
|
|
24
|
+
/**
|
|
25
|
+
* Get a valid access token, refreshing if expired.
|
|
26
|
+
* Returns null if no credentials are stored.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getAccessToken(): Promise<string | null>;
|
|
29
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,iBAAiB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACnB;AASD,MAAM,WAAW,UAAU;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACnB;AAQD,wBAAgB,eAAe,IAAI,iBAAiB,GAAG,IAAI,CAU1D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAI/D;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAIvD;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,UAAU,GAAG,IAAI,CA0BlD;AAED,wBAAgB,eAAe,IAAI,IAAI,CAItC;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAI9D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAE3D;AAID,wBAAsB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA0BzF;AAED;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiB7D"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const CONFIG_DIR = join(homedir(), '.workjournal');
|
|
5
|
+
const CREDENTIALS_PATH = join(CONFIG_DIR, 'credentials.json');
|
|
6
|
+
const CREDENTIALS_TMP_PATH = `${CREDENTIALS_PATH}.tmp`;
|
|
7
|
+
const LOGIN_STATE_PATH = join(CONFIG_DIR, 'cli-login-state.json');
|
|
8
|
+
const LOGIN_STATE_TMP_PATH = `${LOGIN_STATE_PATH}.tmp`;
|
|
9
|
+
const LOGIN_STATE_TTL_MS = 10 * 60 * 1000;
|
|
10
|
+
function ensureConfigDir() {
|
|
11
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
12
|
+
mkdirSync(CONFIG_DIR, { mode: 0o700, recursive: true });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function readCredentials() {
|
|
16
|
+
if (!existsSync(CREDENTIALS_PATH)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const raw = readFileSync(CREDENTIALS_PATH, 'utf-8');
|
|
21
|
+
return JSON.parse(raw);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function writeCredentials(creds) {
|
|
28
|
+
ensureConfigDir();
|
|
29
|
+
writeFileSync(CREDENTIALS_TMP_PATH, `${JSON.stringify(creds, null, '\t')}\n`, { mode: 0o600 });
|
|
30
|
+
renameSync(CREDENTIALS_TMP_PATH, CREDENTIALS_PATH);
|
|
31
|
+
}
|
|
32
|
+
export function clearCredentials() {
|
|
33
|
+
if (existsSync(CREDENTIALS_PATH)) {
|
|
34
|
+
unlinkSync(CREDENTIALS_PATH);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function writeLoginState(state) {
|
|
38
|
+
ensureConfigDir();
|
|
39
|
+
writeFileSync(LOGIN_STATE_TMP_PATH, `${JSON.stringify(state, null, '\t')}\n`, { mode: 0o600 });
|
|
40
|
+
renameSync(LOGIN_STATE_TMP_PATH, LOGIN_STATE_PATH);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read the in-progress login state file. Returns null if missing or unreadable.
|
|
44
|
+
* Does NOT enforce TTL — callers should check `created_at` against `LOGIN_STATE_TTL_MS`.
|
|
45
|
+
*/
|
|
46
|
+
export function readLoginState() {
|
|
47
|
+
if (!existsSync(LOGIN_STATE_PATH)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const raw = readFileSync(LOGIN_STATE_PATH, 'utf-8');
|
|
52
|
+
const parsed = JSON.parse(raw);
|
|
53
|
+
if (!parsed ||
|
|
54
|
+
typeof parsed !== 'object' ||
|
|
55
|
+
typeof parsed.verifier !== 'string' ||
|
|
56
|
+
parsed.verifier.length === 0 ||
|
|
57
|
+
typeof parsed.challenge !== 'string' ||
|
|
58
|
+
parsed.challenge.length === 0 ||
|
|
59
|
+
typeof parsed.created_at !== 'string') {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
verifier: parsed.verifier,
|
|
64
|
+
challenge: parsed.challenge,
|
|
65
|
+
created_at: parsed.created_at,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function clearLoginState() {
|
|
73
|
+
if (existsSync(LOGIN_STATE_PATH)) {
|
|
74
|
+
unlinkSync(LOGIN_STATE_PATH);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export function isLoginStateExpired(state) {
|
|
78
|
+
const created = Date.parse(state.created_at);
|
|
79
|
+
if (!Number.isFinite(created))
|
|
80
|
+
return true;
|
|
81
|
+
return Date.now() - created > LOGIN_STATE_TTL_MS;
|
|
82
|
+
}
|
|
83
|
+
export function isExpired(creds) {
|
|
84
|
+
return new Date(creds.expires_at) < new Date();
|
|
85
|
+
}
|
|
86
|
+
const API_URL = process.env['WORKJOURNAL_API_URL'] ?? 'https://api.workjournal.pro';
|
|
87
|
+
export async function refreshAccessToken(refreshToken) {
|
|
88
|
+
const res = await fetch(`${API_URL}/v1/auth/refresh`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: { 'Content-Type': 'application/json' },
|
|
91
|
+
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
const text = await res.text();
|
|
95
|
+
throw new Error(`Token refresh failed (${res.status}): ${text}`);
|
|
96
|
+
}
|
|
97
|
+
const data = (await res.json());
|
|
98
|
+
const expiresAt = new Date(Date.now() + data.expires_in * 1000).toISOString();
|
|
99
|
+
const creds = {
|
|
100
|
+
access_token: data.access_token,
|
|
101
|
+
refresh_token: data.refresh_token,
|
|
102
|
+
expires_at: expiresAt,
|
|
103
|
+
};
|
|
104
|
+
writeCredentials(creds);
|
|
105
|
+
return creds;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get a valid access token, refreshing if expired.
|
|
109
|
+
* Returns null if no credentials are stored.
|
|
110
|
+
*/
|
|
111
|
+
export async function getAccessToken() {
|
|
112
|
+
const creds = readCredentials();
|
|
113
|
+
if (!creds) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if (isExpired(creds)) {
|
|
117
|
+
if (!creds.refresh_token) {
|
|
118
|
+
throw new Error('Access token expired and no refresh token available. Please run `workjournal login` again.');
|
|
119
|
+
}
|
|
120
|
+
const refreshed = await refreshAccessToken(creds.refresh_token);
|
|
121
|
+
return refreshed.access_token;
|
|
122
|
+
}
|
|
123
|
+
return creds.access_token;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { loginFinish, loginInteractive, loginStart } from './auth.js';
|
|
3
|
+
import { clearCredentials, clearLoginState, readCredentials } from './credentials.js';
|
|
4
|
+
const command = process.argv[2];
|
|
5
|
+
switch (command) {
|
|
6
|
+
case 'login': {
|
|
7
|
+
const sub = process.argv[3];
|
|
8
|
+
if (sub === 'start') {
|
|
9
|
+
loginStart();
|
|
10
|
+
}
|
|
11
|
+
else if (sub === 'finish') {
|
|
12
|
+
const code = process.argv[4];
|
|
13
|
+
if (!code) {
|
|
14
|
+
process.stderr.write('Usage: workjournal login finish <CODE>\n');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
await loginFinish(code);
|
|
18
|
+
}
|
|
19
|
+
else if (sub === undefined) {
|
|
20
|
+
await loginInteractive();
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
process.stderr.write(`Unknown login subcommand: ${sub}\n`);
|
|
24
|
+
process.stderr.write('Usage: workjournal login [start | finish <CODE>]\n');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
case 'logout':
|
|
30
|
+
clearCredentials();
|
|
31
|
+
clearLoginState();
|
|
32
|
+
process.stdout.write('Logged out. Credentials removed.\n');
|
|
33
|
+
break;
|
|
34
|
+
case 'whoami': {
|
|
35
|
+
const creds = readCredentials();
|
|
36
|
+
if (!creds) {
|
|
37
|
+
process.stderr.write('Not logged in. Run `workjournal login` to authenticate.\n');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
process.stdout.write('Authenticated. Credentials stored at ~/.workjournal/credentials.json\n');
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case 'status': {
|
|
44
|
+
const creds = readCredentials();
|
|
45
|
+
if (!creds) {
|
|
46
|
+
process.stdout.write('Status: not authenticated\n');
|
|
47
|
+
process.stdout.write('Run `workjournal login` to authenticate.\n');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const expiresAt = new Date(creds.expires_at);
|
|
51
|
+
const isExpired = expiresAt < new Date();
|
|
52
|
+
process.stdout.write('Status: authenticated\n');
|
|
53
|
+
process.stdout.write(`Token expires: ${expiresAt.toISOString()}${isExpired ? ' (expired, will auto-refresh)' : ''}\n`);
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
default:
|
|
58
|
+
process.stdout.write(`workjournal — CLI for Workjournal (https://workjournal.pro)
|
|
59
|
+
|
|
60
|
+
Usage:
|
|
61
|
+
workjournal login Interactive login (terminal only)
|
|
62
|
+
workjournal login start Print authorize URL and store PKCE state
|
|
63
|
+
workjournal login finish <CODE> Exchange the pasted code for credentials
|
|
64
|
+
workjournal logout Remove stored credentials
|
|
65
|
+
workjournal whoami Show current auth status
|
|
66
|
+
workjournal status Show detailed auth status\n`);
|
|
67
|
+
if (command && command !== 'help' && command !== '--help') {
|
|
68
|
+
process.stderr.write(`Unknown command: ${command}\n`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@workjournal/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Command-line tool for authenticating with Workjournal — the journaling service for AI agents and developers.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"workjournal": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/workjournal-pro/workjournal.git",
|
|
26
|
+
"directory": "packages/cli"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/workjournal-pro/workjournal/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://workjournal.pro",
|
|
32
|
+
"keywords": [
|
|
33
|
+
"workjournal",
|
|
34
|
+
"cli",
|
|
35
|
+
"oauth",
|
|
36
|
+
"authentication",
|
|
37
|
+
"ai-agents",
|
|
38
|
+
"journaling"
|
|
39
|
+
],
|
|
40
|
+
"dependencies": {},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "22.15.29",
|
|
43
|
+
"typescript": "5.8.3",
|
|
44
|
+
"vitest": "4.1.2"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsc --project tsconfig.json",
|
|
48
|
+
"test": "vitest run"
|
|
49
|
+
}
|
|
50
|
+
}
|