@doist/twist-cli 2.36.3 → 2.36.5
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/CHANGELOG.md +12 -0
- package/dist/commands/auth/helpers.d.ts +8 -1
- package/dist/commands/auth/helpers.d.ts.map +1 -1
- package/dist/commands/auth/helpers.js +12 -7
- package/dist/commands/auth/helpers.js.map +1 -1
- package/dist/commands/auth/index.d.ts.map +1 -1
- package/dist/commands/auth/index.js +12 -12
- package/dist/commands/auth/index.js.map +1 -1
- package/dist/commands/auth/login.d.ts +3 -3
- package/dist/commands/auth/login.d.ts.map +1 -1
- package/dist/commands/auth/login.js +24 -62
- package/dist/commands/auth/login.js.map +1 -1
- package/dist/commands/auth/logout.d.ts +10 -1
- package/dist/commands/auth/logout.d.ts.map +1 -1
- package/dist/commands/auth/logout.js +18 -6
- package/dist/commands/auth/logout.js.map +1 -1
- package/dist/commands/auth/status.d.ts +15 -3
- package/dist/commands/auth/status.d.ts.map +1 -1
- package/dist/commands/auth/status.js +87 -22
- package/dist/commands/auth/status.js.map +1 -1
- package/dist/commands/changelog.d.ts +0 -5
- package/dist/commands/changelog.d.ts.map +1 -1
- package/dist/commands/changelog.js +9 -103
- package/dist/commands/changelog.js.map +1 -1
- package/dist/commands/channel/list.js +5 -5
- package/dist/commands/channel/list.js.map +1 -1
- package/dist/commands/channel/threads.d.ts.map +1 -1
- package/dist/commands/channel/threads.js +5 -3
- package/dist/commands/channel/threads.js.map +1 -1
- package/dist/commands/config/view.d.ts.map +1 -1
- package/dist/commands/config/view.js +3 -3
- package/dist/commands/config/view.js.map +1 -1
- package/dist/commands/conversation/helpers.d.ts.map +1 -1
- package/dist/commands/conversation/helpers.js +4 -3
- package/dist/commands/conversation/helpers.js.map +1 -1
- package/dist/commands/conversation/unread.d.ts.map +1 -1
- package/dist/commands/conversation/unread.js +5 -4
- package/dist/commands/conversation/unread.js.map +1 -1
- package/dist/commands/conversation/view.d.ts.map +1 -1
- package/dist/commands/conversation/view.js +8 -6
- package/dist/commands/conversation/view.js.map +1 -1
- package/dist/commands/inbox.d.ts.map +1 -1
- package/dist/commands/inbox.js +9 -7
- package/dist/commands/inbox.js.map +1 -1
- package/dist/commands/thread/view.d.ts.map +1 -1
- package/dist/commands/thread/view.js +14 -12
- package/dist/commands/thread/view.js.map +1 -1
- package/dist/commands/update/index.d.ts.map +1 -1
- package/dist/commands/update/index.js +43 -14
- package/dist/commands/update/index.js.map +1 -1
- package/dist/index.js +27 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +23 -5
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +61 -3
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/auth-pages.d.ts +3 -0
- package/dist/lib/auth-pages.d.ts.map +1 -0
- package/dist/lib/{oauth-server.js → auth-pages.js} +3 -120
- package/dist/lib/auth-pages.js.map +1 -0
- package/dist/lib/auth-provider.d.ts +28 -0
- package/dist/lib/auth-provider.d.ts.map +1 -0
- package/dist/lib/auth-provider.js +241 -0
- package/dist/lib/auth-provider.js.map +1 -0
- package/dist/lib/auth.d.ts +16 -10
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +18 -0
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/config.d.ts +23 -2
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +95 -47
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/errors.d.ts +15 -6
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +14 -9
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/global-args.d.ts +35 -21
- package/dist/lib/global-args.d.ts.map +1 -1
- package/dist/lib/global-args.js +84 -64
- package/dist/lib/global-args.js.map +1 -1
- package/dist/lib/markdown.d.ts +1 -0
- package/dist/lib/markdown.d.ts.map +1 -1
- package/dist/lib/markdown.js +9 -14
- package/dist/lib/markdown.js.map +1 -1
- package/dist/lib/options.d.ts +2 -3
- package/dist/lib/options.d.ts.map +1 -1
- package/dist/lib/output.d.ts +5 -3
- package/dist/lib/output.d.ts.map +1 -1
- package/dist/lib/output.js +15 -17
- package/dist/lib/output.js.map +1 -1
- package/dist/lib/skills/content.d.ts +1 -1
- package/dist/lib/skills/content.d.ts.map +1 -1
- package/dist/lib/skills/content.js +16 -6
- package/dist/lib/skills/content.js.map +1 -1
- package/dist/lib/spinner.d.ts +2 -23
- package/dist/lib/spinner.d.ts.map +1 -1
- package/dist/lib/spinner.js +6 -107
- package/dist/lib/spinner.js.map +1 -1
- package/dist/lib/update.d.ts +9 -13
- package/dist/lib/update.d.ts.map +1 -1
- package/dist/lib/update.js +17 -43
- package/dist/lib/update.js.map +1 -1
- package/package.json +6 -6
- package/dist/commands/update/action.d.ts +0 -12
- package/dist/commands/update/action.d.ts.map +0 -1
- package/dist/commands/update/action.js +0 -104
- package/dist/commands/update/action.js.map +0 -1
- package/dist/commands/update/switch.d.ts +0 -5
- package/dist/commands/update/switch.d.ts.map +0 -1
- package/dist/commands/update/switch.js +0 -28
- package/dist/commands/update/switch.js.map +0 -1
- package/dist/lib/oauth-server.d.ts +0 -13
- package/dist/lib/oauth-server.d.ts.map +0 -1
- package/dist/lib/oauth-server.js.map +0 -1
- package/dist/lib/oauth.d.ts +0 -31
- package/dist/lib/oauth.d.ts.map +0 -1
- package/dist/lib/oauth.js +0 -150
- package/dist/lib/oauth.js.map +0 -1
- package/dist/lib/pkce.d.ts +0 -16
- package/dist/lib/pkce.d.ts.map +0 -1
- package/dist/lib/pkce.js +0 -35
- package/dist/lib/pkce.js.map +0 -1
- package/dist/test-helpers/empty-output.d.ts +0 -18
- package/dist/test-helpers/empty-output.d.ts.map +0 -1
- package/dist/test-helpers/empty-output.js +0 -43
- package/dist/test-helpers/empty-output.js.map +0 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { deriveChallenge, generateVerifier, } from '@doist/cli-core/auth';
|
|
2
|
+
import { createWrappedTwistClient } from './api.js';
|
|
3
|
+
import { clearApiToken, NoTokenError, probeApiToken, saveApiToken, } from './auth.js';
|
|
4
|
+
import { CliError } from './errors.js';
|
|
5
|
+
import { SecureStoreUnavailableError } from './secure-store.js';
|
|
6
|
+
export const AUTHORIZATION_URL = 'https://twist.com/oauth/authorize';
|
|
7
|
+
export const TOKEN_URL = 'https://twist.com/oauth/access_token';
|
|
8
|
+
export const REGISTRATION_URL = 'https://twist.com/oauth/register';
|
|
9
|
+
const LOGO_URI = 'https://raw.githubusercontent.com/Doist/twist-cli/d65c447ff453eb36af585044c2f5f2f602bcdb34/icons/twist-cli.png';
|
|
10
|
+
export const READ_WRITE_SCOPES = [
|
|
11
|
+
'user:read',
|
|
12
|
+
'user:write',
|
|
13
|
+
'workspaces:read',
|
|
14
|
+
'channels:read',
|
|
15
|
+
'threads:read',
|
|
16
|
+
'threads:write',
|
|
17
|
+
'comments:read',
|
|
18
|
+
'comments:write',
|
|
19
|
+
'messages:read',
|
|
20
|
+
'messages:write',
|
|
21
|
+
'reactions:read',
|
|
22
|
+
'reactions:write',
|
|
23
|
+
'groups:read',
|
|
24
|
+
'groups:write',
|
|
25
|
+
'groups:remove',
|
|
26
|
+
'search:read',
|
|
27
|
+
'notifications:read',
|
|
28
|
+
];
|
|
29
|
+
export const READ_ONLY_SCOPES = [
|
|
30
|
+
'user:read',
|
|
31
|
+
'workspaces:read',
|
|
32
|
+
'channels:read',
|
|
33
|
+
'threads:read',
|
|
34
|
+
'comments:read',
|
|
35
|
+
'messages:read',
|
|
36
|
+
'reactions:read',
|
|
37
|
+
'groups:read',
|
|
38
|
+
'search:read',
|
|
39
|
+
'notifications:read',
|
|
40
|
+
];
|
|
41
|
+
const AUTH_HINTS = ['Try again: tw auth login', 'Or set TWIST_API_TOKEN environment variable'];
|
|
42
|
+
function asHandshake(value) {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
function authFailed(message, cause) {
|
|
46
|
+
const detail = cause instanceof Error && cause.message ? `: ${cause.message}` : '';
|
|
47
|
+
return new CliError('AUTH_FAILED', `${message}${detail}`, AUTH_HINTS);
|
|
48
|
+
}
|
|
49
|
+
async function registerDynamicClient(redirectUri) {
|
|
50
|
+
let response;
|
|
51
|
+
try {
|
|
52
|
+
response = await fetch(REGISTRATION_URL, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
55
|
+
body: JSON.stringify({
|
|
56
|
+
client_name: 'Twist CLI',
|
|
57
|
+
client_uri: 'https://github.com/doist/twist-cli',
|
|
58
|
+
redirect_uris: [redirectUri],
|
|
59
|
+
grant_types: ['authorization_code'],
|
|
60
|
+
response_types: ['code'],
|
|
61
|
+
token_endpoint_auth_method: 'client_secret_basic',
|
|
62
|
+
application_type: 'native',
|
|
63
|
+
logo_uri: LOGO_URI,
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error instanceof CliError)
|
|
69
|
+
throw error;
|
|
70
|
+
throw authFailed('Failed to register OAuth client', error);
|
|
71
|
+
}
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
const errorText = await response.text().catch(() => '');
|
|
74
|
+
throw new CliError('AUTH_FAILED', `Client registration failed: ${response.status} ${response.statusText} - ${errorText}`, AUTH_HINTS);
|
|
75
|
+
}
|
|
76
|
+
let result;
|
|
77
|
+
try {
|
|
78
|
+
result = (await response.json());
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
throw authFailed('Invalid client registration response', error);
|
|
82
|
+
}
|
|
83
|
+
if (!result.client_id || !result.client_secret) {
|
|
84
|
+
throw new CliError('AUTH_FAILED', 'Invalid client registration response: missing client_id or client_secret', AUTH_HINTS);
|
|
85
|
+
}
|
|
86
|
+
return { clientId: result.client_id, clientSecret: result.client_secret };
|
|
87
|
+
}
|
|
88
|
+
export function createTwistAuthProvider() {
|
|
89
|
+
return {
|
|
90
|
+
async prepare({ redirectUri }) {
|
|
91
|
+
const { clientId, clientSecret } = await registerDynamicClient(redirectUri);
|
|
92
|
+
const handshake = { clientId, clientSecret };
|
|
93
|
+
return { handshake };
|
|
94
|
+
},
|
|
95
|
+
async authorize({ redirectUri, state, scopes, readOnly, handshake }) {
|
|
96
|
+
const hs = asHandshake(handshake);
|
|
97
|
+
const codeVerifier = generateVerifier();
|
|
98
|
+
const codeChallenge = deriveChallenge(codeVerifier);
|
|
99
|
+
const authMode = readOnly ? 'read-only' : 'read-write';
|
|
100
|
+
const authScope = scopes.join(' ');
|
|
101
|
+
const params = new URLSearchParams({
|
|
102
|
+
client_id: hs.clientId,
|
|
103
|
+
response_type: 'code',
|
|
104
|
+
redirect_uri: redirectUri,
|
|
105
|
+
scope: authScope,
|
|
106
|
+
state,
|
|
107
|
+
code_challenge: codeChallenge,
|
|
108
|
+
code_challenge_method: 'S256',
|
|
109
|
+
});
|
|
110
|
+
const nextHandshake = {
|
|
111
|
+
...hs,
|
|
112
|
+
codeVerifier,
|
|
113
|
+
authMode,
|
|
114
|
+
authScope,
|
|
115
|
+
};
|
|
116
|
+
return {
|
|
117
|
+
authorizeUrl: `${AUTHORIZATION_URL}?${params.toString()}`,
|
|
118
|
+
handshake: nextHandshake,
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
async exchangeCode({ code, redirectUri, handshake }) {
|
|
122
|
+
const hs = asHandshake(handshake);
|
|
123
|
+
if (!hs.codeVerifier) {
|
|
124
|
+
throw new CliError('AUTH_FAILED', 'Missing PKCE code verifier from authorize step', AUTH_HINTS);
|
|
125
|
+
}
|
|
126
|
+
const body = new URLSearchParams({
|
|
127
|
+
grant_type: 'authorization_code',
|
|
128
|
+
code,
|
|
129
|
+
redirect_uri: redirectUri,
|
|
130
|
+
code_verifier: hs.codeVerifier,
|
|
131
|
+
});
|
|
132
|
+
const credentials = btoa(`${hs.clientId}:${hs.clientSecret}`);
|
|
133
|
+
let response;
|
|
134
|
+
try {
|
|
135
|
+
response = await fetch(TOKEN_URL, {
|
|
136
|
+
method: 'POST',
|
|
137
|
+
headers: {
|
|
138
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
139
|
+
Accept: 'application/json',
|
|
140
|
+
Authorization: `Basic ${credentials}`,
|
|
141
|
+
},
|
|
142
|
+
body: body.toString(),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (error instanceof CliError)
|
|
147
|
+
throw error;
|
|
148
|
+
throw authFailed('Failed to exchange code for token', error);
|
|
149
|
+
}
|
|
150
|
+
if (!response.ok) {
|
|
151
|
+
const errorText = await response.text().catch(() => '');
|
|
152
|
+
throw new CliError('AUTH_FAILED', `Token exchange failed: ${response.status} ${response.statusText} - ${errorText}`, AUTH_HINTS);
|
|
153
|
+
}
|
|
154
|
+
let data;
|
|
155
|
+
try {
|
|
156
|
+
data = (await response.json());
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
throw authFailed('Invalid token exchange response', error);
|
|
160
|
+
}
|
|
161
|
+
if (data.error) {
|
|
162
|
+
throw new CliError('AUTH_FAILED', `OAuth error: ${data.error} - ${data.error_description ?? 'Unknown error'}`, AUTH_HINTS);
|
|
163
|
+
}
|
|
164
|
+
if (!data.access_token) {
|
|
165
|
+
throw new CliError('AUTH_FAILED', 'No access token received from OAuth server', AUTH_HINTS);
|
|
166
|
+
}
|
|
167
|
+
return { accessToken: data.access_token };
|
|
168
|
+
},
|
|
169
|
+
async validateToken({ token, handshake }) {
|
|
170
|
+
const hs = asHandshake(handshake);
|
|
171
|
+
const client = createWrappedTwistClient(token);
|
|
172
|
+
const user = await client.users.getSessionUser();
|
|
173
|
+
return {
|
|
174
|
+
id: String(user.id),
|
|
175
|
+
label: user.name,
|
|
176
|
+
authMode: hs.authMode ?? 'unknown',
|
|
177
|
+
authScope: hs.authScope ?? '',
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
export function createTwistTokenStore() {
|
|
183
|
+
let lastStorageResult;
|
|
184
|
+
let lastClearResult;
|
|
185
|
+
return {
|
|
186
|
+
async active() {
|
|
187
|
+
// Return a snapshot whenever a token resolves — even if the
|
|
188
|
+
// persisted identity is missing (env var, manual `tw auth token`,
|
|
189
|
+
// pre-upgrade config). Callers downstream (e.g. status's
|
|
190
|
+
// `fetchLive`) will re-derive the canonical account from the live
|
|
191
|
+
// API, so the placeholder fields below are intentionally empty.
|
|
192
|
+
// Returning a snapshot here also avoids a second credential read
|
|
193
|
+
// in those code paths — the token discovered during `probeApiToken`
|
|
194
|
+
// is reused rather than re-resolved through `getApiToken`.
|
|
195
|
+
//
|
|
196
|
+
// `SecureStoreUnavailableError` is downgraded to `null` (no
|
|
197
|
+
// snapshot) so headless / keyring-less environments fall back to
|
|
198
|
+
// the standard `NoTokenError` envelope instead of leaking the raw
|
|
199
|
+
// secure-store error — matching the legacy `getApiToken`
|
|
200
|
+
// behaviour the prior `showStatus()` relied on.
|
|
201
|
+
try {
|
|
202
|
+
const { token, metadata } = await probeApiToken();
|
|
203
|
+
return {
|
|
204
|
+
token,
|
|
205
|
+
account: {
|
|
206
|
+
id: metadata.authUserId !== undefined ? String(metadata.authUserId) : '',
|
|
207
|
+
label: metadata.authUserName ?? '',
|
|
208
|
+
authMode: metadata.authMode,
|
|
209
|
+
authScope: metadata.authScope ?? '',
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
if (error instanceof NoTokenError)
|
|
215
|
+
return null;
|
|
216
|
+
if (error instanceof SecureStoreUnavailableError)
|
|
217
|
+
return null;
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
async set(account, token) {
|
|
222
|
+
const userId = Number(account.id);
|
|
223
|
+
lastStorageResult = await saveApiToken(token, {
|
|
224
|
+
authMode: account.authMode,
|
|
225
|
+
authScope: account.authScope,
|
|
226
|
+
authUserId: Number.isFinite(userId) ? userId : undefined,
|
|
227
|
+
authUserName: account.label,
|
|
228
|
+
});
|
|
229
|
+
},
|
|
230
|
+
async clear() {
|
|
231
|
+
lastClearResult = await clearApiToken();
|
|
232
|
+
},
|
|
233
|
+
getLastStorageResult() {
|
|
234
|
+
return lastStorageResult;
|
|
235
|
+
},
|
|
236
|
+
getLastClearResult() {
|
|
237
|
+
return lastClearResult;
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=auth-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-provider.js","sourceRoot":"","sources":["../../src/lib/auth-provider.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/auth.d.ts
CHANGED
|
@@ -5,28 +5,34 @@ export declare class NoTokenError extends CliError {
|
|
|
5
5
|
}
|
|
6
6
|
export declare const TOKEN_ENV_VAR = "TWIST_API_TOKEN";
|
|
7
7
|
export type TokenStorageLocation = 'secure-store' | 'config-file';
|
|
8
|
-
export
|
|
8
|
+
export type TokenStorageResult = {
|
|
9
9
|
storage: TokenStorageLocation;
|
|
10
10
|
warning?: string;
|
|
11
|
-
}
|
|
12
|
-
export
|
|
11
|
+
};
|
|
12
|
+
export type SaveApiTokenOptions = {
|
|
13
13
|
authMode?: AuthMode;
|
|
14
14
|
authScope?: string;
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
authUserId?: number;
|
|
16
|
+
authUserName?: string;
|
|
17
|
+
};
|
|
18
|
+
export type AuthMetadata = {
|
|
17
19
|
authMode: AuthMode;
|
|
18
20
|
authScope?: string;
|
|
21
|
+
authUserId?: number;
|
|
22
|
+
authUserName?: string;
|
|
19
23
|
source: 'env' | 'config';
|
|
20
|
-
}
|
|
21
|
-
export
|
|
24
|
+
};
|
|
25
|
+
export type AuthProbeMetadata = {
|
|
22
26
|
authMode: AuthMode;
|
|
23
27
|
authScope?: string;
|
|
28
|
+
authUserId?: number;
|
|
29
|
+
authUserName?: string;
|
|
24
30
|
source: 'env' | 'config-file' | 'secure-store';
|
|
25
|
-
}
|
|
26
|
-
export
|
|
31
|
+
};
|
|
32
|
+
export type AuthProbeResult = {
|
|
27
33
|
token: string;
|
|
28
34
|
metadata: AuthProbeMetadata;
|
|
29
|
-
}
|
|
35
|
+
};
|
|
30
36
|
export declare function getApiToken(): Promise<string>;
|
|
31
37
|
export declare function probeApiToken(): Promise<AuthProbeResult>;
|
|
32
38
|
export declare function getAuthMetadata(): Promise<AuthMetadata>;
|
package/dist/lib/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,QAAQ,EAAoD,MAAM,aAAa,CAAA;AAC7F,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAOtC,qBAAa,YAAa,SAAQ,QAAQ;;CAUzC;AAED,eAAO,MAAM,aAAa,oBAAoB,CAAA;AAC9C,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG,aAAa,CAAA;AAEjE,MAAM,
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,QAAQ,EAAoD,MAAM,aAAa,CAAA;AAC7F,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAOtC,qBAAa,YAAa,SAAQ,QAAQ;;CAUzC;AAED,eAAO,MAAM,aAAa,oBAAoB,CAAA;AAC9C,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG,aAAa,CAAA;AAEjE,MAAM,MAAM,kBAAkB,GAAG;IAC7B,OAAO,EAAE,oBAAoB,CAAA;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAC9B,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACvB,QAAQ,EAAE,QAAQ,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,KAAK,GAAG,QAAQ,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC5B,QAAQ,EAAE,QAAQ,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,KAAK,GAAG,aAAa,GAAG,cAAc,CAAA;CACjD,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,iBAAiB,CAAA;CAC9B,CAAA;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CA6DnD;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,eAAe,CAAC,CAmD9D;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC,CAc7D;AAED,wBAAsB,YAAY,CAC9B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,mBAAwB,GAClC,OAAO,CAAC,kBAAkB,CAAC,CA6C7B;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,kBAAkB,CAAC,CA2BjE"}
|
package/dist/lib/auth.js
CHANGED
|
@@ -77,6 +77,8 @@ export async function probeApiToken() {
|
|
|
77
77
|
metadata: {
|
|
78
78
|
authMode: config.authMode ?? 'unknown',
|
|
79
79
|
authScope: config.authScope,
|
|
80
|
+
authUserId: config.authUserId,
|
|
81
|
+
authUserName: config.authUserName,
|
|
80
82
|
source: 'config-file',
|
|
81
83
|
},
|
|
82
84
|
};
|
|
@@ -93,6 +95,8 @@ export async function probeApiToken() {
|
|
|
93
95
|
metadata: {
|
|
94
96
|
authMode: config.authMode ?? 'unknown',
|
|
95
97
|
authScope: config.authScope,
|
|
98
|
+
authUserId: config.authUserId,
|
|
99
|
+
authUserName: config.authUserName,
|
|
96
100
|
source: 'secure-store',
|
|
97
101
|
},
|
|
98
102
|
};
|
|
@@ -115,6 +119,8 @@ export async function getAuthMetadata() {
|
|
|
115
119
|
return {
|
|
116
120
|
authMode: config.authMode ?? 'unknown',
|
|
117
121
|
authScope: config.authScope,
|
|
122
|
+
authUserId: config.authUserId,
|
|
123
|
+
authUserName: config.authUserName,
|
|
118
124
|
source: 'config',
|
|
119
125
|
};
|
|
120
126
|
}
|
|
@@ -153,6 +159,8 @@ export async function saveApiToken(token, options = {}) {
|
|
|
153
159
|
delete config.pendingSecureStoreClear;
|
|
154
160
|
config.authMode = options.authMode ?? 'unknown';
|
|
155
161
|
config.authScope = options.authScope;
|
|
162
|
+
config.authUserId = options.authUserId;
|
|
163
|
+
config.authUserName = options.authUserName;
|
|
156
164
|
await writeConfig(config);
|
|
157
165
|
return {
|
|
158
166
|
storage: 'config-file',
|
|
@@ -167,6 +175,8 @@ export async function clearApiToken() {
|
|
|
167
175
|
// the removal atomically alongside other state changes.
|
|
168
176
|
delete config.authMode;
|
|
169
177
|
delete config.authScope;
|
|
178
|
+
delete config.authUserId;
|
|
179
|
+
delete config.authUserName;
|
|
170
180
|
try {
|
|
171
181
|
await secureStore.deleteSecret();
|
|
172
182
|
const warning = await cleanupAuthFallbackState(config, 'Secure-store token was removed,');
|
|
@@ -187,8 +197,16 @@ async function saveAuthMetadata(options) {
|
|
|
187
197
|
const config = await getConfig();
|
|
188
198
|
config.authMode = options.authMode ?? 'unknown';
|
|
189
199
|
config.authScope = options.authScope;
|
|
200
|
+
config.authUserId = options.authUserId;
|
|
201
|
+
config.authUserName = options.authUserName;
|
|
190
202
|
await setConfig(config);
|
|
191
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Auth-local cousin of `setConfig` that deletes the file when the resulting
|
|
206
|
+
* config has no own keys. The public `setConfig` always serializes (even
|
|
207
|
+
* `{}`) — this wrapper exists for the auth flows that strip the legacy
|
|
208
|
+
* plaintext token and want to leave nothing behind.
|
|
209
|
+
*/
|
|
192
210
|
async function writeConfig(config) {
|
|
193
211
|
if (Object.keys(config).length === 0) {
|
|
194
212
|
try {
|
package/dist/lib/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAA8B,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC7F,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EACH,iBAAiB,EACjB,wBAAwB,EACxB,2BAA2B,GAC9B,MAAM,mBAAmB,CAAA;AAE1B,MAAM,OAAO,YAAa,SAAQ,QAAQ;IACtC;QACI,KAAK,CACD,UAAU,EACV,2BAA2B,aAAa,yDAAyD,EACjG,CAAC,2CAA2C,CAAC,EAC7C,MAAM,CACT,CAAA;QACD,IAAI,CAAC,IAAI,GAAG,cAAc,CAAA;IAC9B,CAAC;CACJ;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the canonical config path lazily. Computing on each call (instead of
|
|
3
|
+
* caching at module load) keeps the path responsive to vitest's `vi.doMock`
|
|
4
|
+
* for `node:os` — which only reliably reaches cli-core's compiled `homedir()`
|
|
5
|
+
* call after the mock has been set up by the test, not at import time.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getConfigPath(): string;
|
|
2
8
|
export type AuthMode = 'read-only' | 'read-write' | 'unknown';
|
|
3
9
|
export type UpdateChannel = 'stable' | 'pre-release';
|
|
10
|
+
export declare const UPDATE_CHANNELS: ReadonlySet<UpdateChannel>;
|
|
4
11
|
export interface UserSettings {
|
|
5
12
|
unarchiveNewThreads?: boolean;
|
|
6
13
|
}
|
|
@@ -10,9 +17,17 @@ export interface Config {
|
|
|
10
17
|
currentWorkspace?: number;
|
|
11
18
|
authMode?: AuthMode;
|
|
12
19
|
authScope?: string;
|
|
20
|
+
authUserId?: number;
|
|
21
|
+
authUserName?: string;
|
|
13
22
|
updateChannel?: UpdateChannel;
|
|
14
23
|
userSettings?: UserSettings;
|
|
15
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Thin wrapper around cli-core's lenient `readConfig`. Returns `{}` when the
|
|
27
|
+
* file is missing, unreadable, or invalid — runtime code paths treat "no
|
|
28
|
+
* config" and "empty config" the same. Use `readConfigStrict` for inspection
|
|
29
|
+
* commands that need to distinguish failure modes.
|
|
30
|
+
*/
|
|
16
31
|
export declare function getConfig(): Promise<Config>;
|
|
17
32
|
export type StrictReadResult = {
|
|
18
33
|
state: 'missing';
|
|
@@ -26,8 +41,14 @@ export type StrictReadResult = {
|
|
|
26
41
|
* swallows errors for runtime code paths; this one surfaces them.
|
|
27
42
|
*/
|
|
28
43
|
export declare function readConfigStrict(): Promise<StrictReadResult>;
|
|
44
|
+
/** Thin wrapper around cli-core's `writeConfig`. Dual-writes the channel field. */
|
|
29
45
|
export declare function setConfig(config: Config): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Atomic partial-write wrapper around cli-core's `updateConfig`. Preserves
|
|
48
|
+
* cli-core's read-merge-write atomicity so two concurrent `tw` processes
|
|
49
|
+
* can't lose each other's updates. Channel field is translated to disk
|
|
50
|
+
* shape (dual-written) before the merge.
|
|
51
|
+
*/
|
|
30
52
|
export declare function updateConfig(updates: Partial<Config>): Promise<void>;
|
|
31
53
|
export declare function validateConfigForDoctor(config: Record<string, unknown>): string[];
|
|
32
|
-
export declare function getConfigPath(): string;
|
|
33
54
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAWA;;;;;GAKG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,YAAY,GAAG,SAAS,CAAA;AAC7D,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,aAAa,CAAA;AAqBpD,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,aAAa,CAAsC,CAAA;AAE7F,MAAM,WAAW,YAAY;IACzB,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAChC;AAED,MAAM,WAAW,MAAM;IAEnB,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAEzB,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAIlB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,YAAY,CAAC,EAAE,YAAY,CAAA;CAC9B;AAuCD;;;;;GAKG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAGjD;AAED,MAAM,MAAM,gBAAgB,GAAG;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1F;;;;GAIG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAkClE;AAED,mFAAmF;AACnF,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7D;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1E;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CA+EjF"}
|
package/dist/lib/config.js
CHANGED
|
@@ -1,28 +1,79 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { dirname, join } from 'node:path';
|
|
1
|
+
import { getConfigPath as getConfigPathCore, readConfig as readConfigCore, readConfigStrict as readConfigStrictCore, updateConfig as updateConfigCore, writeConfig as writeConfigCore, } from '@doist/cli-core';
|
|
4
2
|
import { CliError } from './errors.js';
|
|
5
|
-
|
|
3
|
+
const APP_NAME = 'twist-cli';
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the canonical config path lazily. Computing on each call (instead of
|
|
6
|
+
* caching at module load) keeps the path responsive to vitest's `vi.doMock`
|
|
7
|
+
* for `node:os` — which only reliably reaches cli-core's compiled `homedir()`
|
|
8
|
+
* call after the mock has been set up by the test, not at import time.
|
|
9
|
+
*/
|
|
10
|
+
export function getConfigPath() {
|
|
11
|
+
return getConfigPathCore(APP_NAME);
|
|
12
|
+
}
|
|
6
13
|
const KNOWN_CONFIG_KEYS = new Set([
|
|
7
14
|
'token',
|
|
8
15
|
'pendingSecureStoreClear',
|
|
9
16
|
'currentWorkspace',
|
|
10
17
|
'authMode',
|
|
11
18
|
'authScope',
|
|
19
|
+
'authUserId',
|
|
20
|
+
'authUserName',
|
|
12
21
|
'updateChannel',
|
|
22
|
+
// Snake_case alias persisted on disk so cli-core's update command can read
|
|
23
|
+
// it directly. The in-memory `Config` type only exposes `updateChannel` —
|
|
24
|
+
// see `fromDiskShape` / `toDiskShape` for the persistence-seam translation.
|
|
25
|
+
'update_channel',
|
|
13
26
|
'userSettings',
|
|
14
27
|
]);
|
|
15
28
|
const KNOWN_USER_SETTINGS_KEYS = new Set(['unarchiveNewThreads']);
|
|
16
29
|
const AUTH_MODES = new Set(['read-only', 'read-write', 'unknown']);
|
|
17
|
-
const UPDATE_CHANNELS = new Set(['stable', 'pre-release']);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
export const UPDATE_CHANNELS = new Set(['stable', 'pre-release']);
|
|
31
|
+
/**
|
|
32
|
+
* Read-seam translation: normalise the persisted shape to the in-memory
|
|
33
|
+
* `Config` shape. cli-core's update command writes the channel under
|
|
34
|
+
* `update_channel`; older twist builds wrote it under `updateChannel`.
|
|
35
|
+
* We accept both and expose only `updateChannel` to twist callers.
|
|
36
|
+
*
|
|
37
|
+
* `update_channel` wins if both are present (cli-core just wrote, so the
|
|
38
|
+
* snake_case value is freshest). Non-object inputs (a manually-edited
|
|
39
|
+
* config containing `null` or a primitive) are returned untouched so the
|
|
40
|
+
* downstream `Record<string, unknown>` cast doesn't blow up on `in`.
|
|
41
|
+
*/
|
|
42
|
+
function fromDiskShape(raw) {
|
|
43
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
24
44
|
return {};
|
|
25
45
|
}
|
|
46
|
+
const record = raw;
|
|
47
|
+
const hasCanonical = 'update_channel' in record;
|
|
48
|
+
const hasLegacy = 'updateChannel' in record;
|
|
49
|
+
if (!hasCanonical && !hasLegacy)
|
|
50
|
+
return record;
|
|
51
|
+
const { update_channel, updateChannel, ...rest } = record;
|
|
52
|
+
const channel = hasCanonical ? update_channel : updateChannel;
|
|
53
|
+
return channel === undefined ? rest : { ...rest, updateChannel: channel };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Write-seam translation: dual-write `updateChannel` and `update_channel`
|
|
57
|
+
* to disk when a channel is set, so older twist builds keep reading the
|
|
58
|
+
* camelCase key while cli-core's update command reads the snake_case key.
|
|
59
|
+
* Once all deployed twist versions read `update_channel`, drop the
|
|
60
|
+
* camelCase write (likely a release or two after this lands).
|
|
61
|
+
*/
|
|
62
|
+
function toDiskShape(config) {
|
|
63
|
+
const { updateChannel, ...rest } = config;
|
|
64
|
+
if (updateChannel === undefined)
|
|
65
|
+
return rest;
|
|
66
|
+
return { ...rest, updateChannel, update_channel: updateChannel };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Thin wrapper around cli-core's lenient `readConfig`. Returns `{}` when the
|
|
70
|
+
* file is missing, unreadable, or invalid — runtime code paths treat "no
|
|
71
|
+
* config" and "empty config" the same. Use `readConfigStrict` for inspection
|
|
72
|
+
* commands that need to distinguish failure modes.
|
|
73
|
+
*/
|
|
74
|
+
export async function getConfig() {
|
|
75
|
+
const raw = await readConfigCore(getConfigPath());
|
|
76
|
+
return fromDiskShape(raw);
|
|
26
77
|
}
|
|
27
78
|
/**
|
|
28
79
|
* Read and parse the config file strictly — for inspection commands that need
|
|
@@ -30,45 +81,40 @@ export async function getConfig() {
|
|
|
30
81
|
* swallows errors for runtime code paths; this one surfaces them.
|
|
31
82
|
*/
|
|
32
83
|
export async function readConfigStrict() {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
catch (error) {
|
|
38
|
-
if (isMissingFileError(error))
|
|
84
|
+
const path = getConfigPath();
|
|
85
|
+
const result = await readConfigStrictCore(path);
|
|
86
|
+
switch (result.state) {
|
|
87
|
+
case 'missing':
|
|
39
88
|
return { state: 'missing' };
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
89
|
+
case 'present':
|
|
90
|
+
return {
|
|
91
|
+
state: 'present',
|
|
92
|
+
config: fromDiskShape(result.config),
|
|
93
|
+
};
|
|
94
|
+
case 'read-failed':
|
|
95
|
+
throw new CliError('CONFIG_READ_FAILED', `Could not read config file ${path}: ${result.error.message}`, ['Check file permissions, or run `tw doctor` to diagnose']);
|
|
96
|
+
case 'invalid-json':
|
|
97
|
+
throw new CliError('CONFIG_INVALID_JSON', `Config file at ${path} is not valid JSON: ${result.error.message}`, [
|
|
98
|
+
'Fix the JSON by hand, or delete the file and re-authenticate with `tw auth login`',
|
|
99
|
+
]);
|
|
100
|
+
case 'invalid-shape':
|
|
101
|
+
throw new CliError('CONFIG_INVALID_SHAPE', `Config file at ${path} must contain a JSON object (got ${result.actual})`, [
|
|
102
|
+
'Fix the JSON by hand, or delete the file and re-authenticate with `tw auth login`',
|
|
103
|
+
]);
|
|
54
104
|
}
|
|
55
|
-
return { state: 'present', config: parsed };
|
|
56
|
-
}
|
|
57
|
-
function isMissingFileError(error) {
|
|
58
|
-
return error instanceof Error && 'code' in error && error.code === 'ENOENT';
|
|
59
105
|
}
|
|
106
|
+
/** Thin wrapper around cli-core's `writeConfig`. Dual-writes the channel field. */
|
|
60
107
|
export async function setConfig(config) {
|
|
61
|
-
|
|
62
|
-
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
63
|
-
await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2), {
|
|
64
|
-
encoding: 'utf-8',
|
|
65
|
-
mode: 0o600,
|
|
66
|
-
});
|
|
67
|
-
await chmod(CONFIG_PATH, 0o600);
|
|
108
|
+
await writeConfigCore(getConfigPath(), toDiskShape(config));
|
|
68
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Atomic partial-write wrapper around cli-core's `updateConfig`. Preserves
|
|
112
|
+
* cli-core's read-merge-write atomicity so two concurrent `tw` processes
|
|
113
|
+
* can't lose each other's updates. Channel field is translated to disk
|
|
114
|
+
* shape (dual-written) before the merge.
|
|
115
|
+
*/
|
|
69
116
|
export async function updateConfig(updates) {
|
|
70
|
-
|
|
71
|
-
await setConfig({ ...config, ...updates });
|
|
117
|
+
await updateConfigCore(getConfigPath(), toDiskShape(updates));
|
|
72
118
|
}
|
|
73
119
|
export function validateConfigForDoctor(config) {
|
|
74
120
|
const issues = [];
|
|
@@ -100,6 +146,11 @@ export function validateConfigForDoctor(config) {
|
|
|
100
146
|
!UPDATE_CHANNELS.has(config.updateChannel))) {
|
|
101
147
|
issues.push('updateChannel must be one of: stable, pre-release');
|
|
102
148
|
}
|
|
149
|
+
if (config.update_channel !== undefined &&
|
|
150
|
+
(typeof config.update_channel !== 'string' ||
|
|
151
|
+
!UPDATE_CHANNELS.has(config.update_channel))) {
|
|
152
|
+
issues.push('update_channel must be one of: stable, pre-release');
|
|
153
|
+
}
|
|
103
154
|
if (config.userSettings !== undefined) {
|
|
104
155
|
const userSettings = config.userSettings;
|
|
105
156
|
if (userSettings === null ||
|
|
@@ -122,7 +173,4 @@ export function validateConfigForDoctor(config) {
|
|
|
122
173
|
}
|
|
123
174
|
return issues;
|
|
124
175
|
}
|
|
125
|
-
export function getConfigPath() {
|
|
126
|
-
return CONFIG_PATH;
|
|
127
|
-
}
|
|
128
176
|
//# sourceMappingURL=config.js.map
|