@vacbo/opencode-anthropic-fix 0.1.7 → 0.1.9
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 +88 -88
- package/dist/opencode-anthropic-auth-cli.mjs +804 -507
- package/dist/opencode-anthropic-auth-plugin.js +4751 -4109
- package/package.json +67 -59
- package/src/__tests__/billing-edge-cases.test.ts +59 -59
- package/src/__tests__/bun-proxy.parallel.test.ts +388 -382
- package/src/__tests__/cc-comparison.test.ts +87 -87
- package/src/__tests__/cc-credentials.test.ts +254 -250
- package/src/__tests__/cch-drift-checker.test.ts +51 -51
- package/src/__tests__/cch-native-style.test.ts +56 -56
- package/src/__tests__/debug-gating.test.ts +42 -42
- package/src/__tests__/decomposition-smoke.test.ts +68 -68
- package/src/__tests__/fingerprint-regression.test.ts +575 -566
- package/src/__tests__/helpers/conversation-history.smoke.test.ts +271 -271
- package/src/__tests__/helpers/conversation-history.ts +119 -119
- package/src/__tests__/helpers/deferred.smoke.test.ts +103 -103
- package/src/__tests__/helpers/deferred.ts +69 -69
- package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +155 -155
- package/src/__tests__/helpers/in-memory-storage.ts +88 -88
- package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +68 -68
- package/src/__tests__/helpers/mock-bun-proxy.ts +189 -189
- package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +273 -273
- package/src/__tests__/helpers/plugin-fetch-harness.ts +288 -288
- package/src/__tests__/helpers/sse.smoke.test.ts +236 -236
- package/src/__tests__/helpers/sse.ts +209 -209
- package/src/__tests__/index.parallel.test.ts +605 -595
- package/src/__tests__/sanitization-regex.test.ts +112 -112
- package/src/__tests__/state-bounds.test.ts +90 -90
- package/src/account-identity.test.ts +197 -192
- package/src/account-identity.ts +69 -67
- package/src/account-state.test.ts +86 -86
- package/src/account-state.ts +25 -25
- package/src/accounts/matching.test.ts +335 -0
- package/src/accounts/matching.ts +167 -0
- package/src/accounts/persistence.test.ts +345 -0
- package/src/accounts/persistence.ts +432 -0
- package/src/accounts/repair.test.ts +276 -0
- package/src/accounts/repair.ts +407 -0
- package/src/accounts.dedup.test.ts +621 -621
- package/src/accounts.test.ts +933 -929
- package/src/accounts.ts +633 -989
- package/src/backoff.test.ts +345 -345
- package/src/backoff.ts +219 -219
- package/src/betas.ts +124 -124
- package/src/bun-fetch.test.ts +345 -342
- package/src/bun-fetch.ts +424 -424
- package/src/bun-proxy.test.ts +25 -25
- package/src/bun-proxy.ts +209 -209
- package/src/cc-credentials.ts +111 -111
- package/src/circuit-breaker.test.ts +184 -184
- package/src/circuit-breaker.ts +169 -169
- package/src/cli/commands/auth.ts +963 -0
- package/src/cli/commands/config.ts +547 -0
- package/src/cli/formatting.test.ts +406 -0
- package/src/cli/formatting.ts +219 -0
- package/src/cli.ts +255 -2022
- package/src/commands/handlers/betas.ts +100 -0
- package/src/commands/handlers/config.ts +99 -0
- package/src/commands/handlers/files.ts +375 -0
- package/src/commands/oauth-flow.ts +181 -166
- package/src/commands/prompts.ts +61 -61
- package/src/commands/router.test.ts +421 -0
- package/src/commands/router.ts +143 -635
- package/src/config.test.ts +482 -482
- package/src/config.ts +412 -404
- package/src/constants.ts +48 -48
- package/src/drift/cch-constants.ts +95 -95
- package/src/env.ts +111 -105
- package/src/headers/billing.ts +33 -33
- package/src/headers/builder.ts +130 -130
- package/src/headers/cch.ts +75 -75
- package/src/headers/stainless.ts +25 -25
- package/src/headers/user-agent.ts +23 -23
- package/src/index.ts +436 -828
- package/src/models.ts +27 -27
- package/src/oauth.test.ts +102 -102
- package/src/oauth.ts +178 -178
- package/src/parent-pid-watcher.test.ts +148 -148
- package/src/parent-pid-watcher.ts +69 -69
- package/src/plugin-helpers.ts +82 -82
- package/src/refresh-helpers.ts +145 -139
- package/src/refresh-lock.test.ts +94 -94
- package/src/refresh-lock.ts +93 -93
- package/src/request/body.history.test.ts +579 -571
- package/src/request/body.ts +255 -255
- package/src/request/metadata.ts +65 -65
- package/src/request/retry.test.ts +156 -156
- package/src/request/retry.ts +67 -67
- package/src/request/url.ts +21 -21
- package/src/request-orchestration-helpers.ts +648 -0
- package/src/response/index.ts +5 -5
- package/src/response/mcp.ts +58 -58
- package/src/response/streaming.test.ts +313 -311
- package/src/response/streaming.ts +412 -410
- package/src/rotation.test.ts +304 -301
- package/src/rotation.ts +205 -205
- package/src/storage.test.ts +547 -547
- package/src/storage.ts +315 -291
- package/src/system-prompt/builder.ts +38 -38
- package/src/system-prompt/index.ts +5 -5
- package/src/system-prompt/normalize.ts +60 -60
- package/src/system-prompt/sanitize.ts +30 -30
- package/src/thinking.ts +21 -20
- package/src/token-refresh.test.ts +265 -265
- package/src/token-refresh.ts +219 -214
- package/src/types.ts +30 -30
- package/dist/bun-proxy.mjs +0 -291
|
@@ -2,36 +2,37 @@
|
|
|
2
2
|
// Slash-command OAuth flows (login / reauth)
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
|
|
5
|
+
import { resolveIdentityFromOAuthExchange } from "../account-identity.js";
|
|
5
6
|
import { authorize, exchange } from "../oauth.js";
|
|
6
7
|
import { createDefaultStats, loadAccounts, saveAccounts } from "../storage.js";
|
|
7
8
|
|
|
8
9
|
export const PENDING_OAUTH_TTL_MS = 10 * 60 * 1000;
|
|
9
10
|
|
|
10
11
|
export interface PendingOAuthEntry {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
mode: "login" | "reauth";
|
|
13
|
+
verifier: string;
|
|
14
|
+
state?: string;
|
|
15
|
+
targetIndex?: number;
|
|
16
|
+
createdAt: number;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export interface OAuthFlowDeps {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
pendingSlashOAuth: Map<string, PendingOAuthEntry>;
|
|
21
|
+
sendCommandMessage: (sessionID: string, message: string) => Promise<void>;
|
|
22
|
+
reloadAccountManagerFromDisk: () => Promise<void>;
|
|
23
|
+
persistOpenCodeAuth: (refresh: string, access: string | undefined, expires: number | undefined) => Promise<void>;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Remove expired pending OAuth entries from the map.
|
|
27
28
|
*/
|
|
28
29
|
export function pruneExpiredPendingOAuth(pendingSlashOAuth: Map<string, PendingOAuthEntry>): void {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
for (const [key, entry] of pendingSlashOAuth) {
|
|
32
|
+
if (now - entry.createdAt > PENDING_OAUTH_TTL_MS) {
|
|
33
|
+
pendingSlashOAuth.delete(key);
|
|
34
|
+
}
|
|
33
35
|
}
|
|
34
|
-
}
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
/**
|
|
@@ -39,178 +40,192 @@ export function pruneExpiredPendingOAuth(pendingSlashOAuth: Map<string, PendingO
|
|
|
39
40
|
* Sends the authorization URL to the user via sendCommandMessage.
|
|
40
41
|
*/
|
|
41
42
|
export async function startSlashOAuth(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
sessionID: string,
|
|
44
|
+
mode: "login" | "reauth",
|
|
45
|
+
targetIndex: number | undefined,
|
|
46
|
+
deps: OAuthFlowDeps,
|
|
46
47
|
): Promise<void> {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
48
|
+
const { pendingSlashOAuth, sendCommandMessage } = deps;
|
|
49
|
+
pruneExpiredPendingOAuth(pendingSlashOAuth);
|
|
50
|
+
const { url, verifier, state } = await authorize("max");
|
|
51
|
+
pendingSlashOAuth.set(sessionID, {
|
|
52
|
+
mode,
|
|
53
|
+
verifier,
|
|
54
|
+
state,
|
|
55
|
+
targetIndex,
|
|
56
|
+
createdAt: Date.now(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const action = mode === "login" ? "login" : `reauth ${(targetIndex ?? 0) + 1}`;
|
|
60
|
+
const followup =
|
|
61
|
+
mode === "login" ? "/anthropic login complete <code#state>" : "/anthropic reauth complete <code#state>";
|
|
62
|
+
|
|
63
|
+
await sendCommandMessage(
|
|
64
|
+
sessionID,
|
|
65
|
+
[
|
|
66
|
+
"▣ Anthropic OAuth",
|
|
67
|
+
"",
|
|
68
|
+
`Started ${action} flow.`,
|
|
69
|
+
"Open this URL in your browser:",
|
|
70
|
+
url,
|
|
71
|
+
"",
|
|
72
|
+
`Then run: ${followup}`,
|
|
73
|
+
"(Paste the full authorization code, including #state)",
|
|
74
|
+
].join("\n"),
|
|
75
|
+
);
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
/**
|
|
78
79
|
* Complete a pending slash-command OAuth flow.
|
|
79
80
|
*/
|
|
80
81
|
export async function completeSlashOAuth(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
sessionID: string,
|
|
83
|
+
code: string,
|
|
84
|
+
deps: OAuthFlowDeps,
|
|
84
85
|
): Promise<{ ok: boolean; message: string }> {
|
|
85
|
-
|
|
86
|
+
const { pendingSlashOAuth, reloadAccountManagerFromDisk, persistOpenCodeAuth } = deps;
|
|
87
|
+
|
|
88
|
+
const pending = pendingSlashOAuth.get(sessionID);
|
|
89
|
+
if (!pending) {
|
|
90
|
+
pruneExpiredPendingOAuth(pendingSlashOAuth);
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
message: "No pending OAuth flow. Start with /anthropic login or /anthropic reauth <N>.",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
86
96
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
}
|
|
97
|
+
if (Date.now() - pending.createdAt > PENDING_OAUTH_TTL_MS) {
|
|
98
|
+
pendingSlashOAuth.delete(sessionID);
|
|
99
|
+
return {
|
|
100
|
+
ok: false,
|
|
101
|
+
message: "Pending OAuth flow expired. Start again with /anthropic login or /anthropic reauth <N>.",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
95
104
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
// Validate state parameter to prevent CSRF attacks
|
|
106
|
+
const splits = code.split("#");
|
|
107
|
+
if (pending.state && splits[1] && splits[1] !== pending.state) {
|
|
108
|
+
pendingSlashOAuth.delete(sessionID);
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
message: "OAuth state mismatch — possible CSRF attack. Please try again.",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
103
114
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
const credentials = await exchange(code, pending.verifier);
|
|
116
|
+
if (credentials.type === "failed") {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
message: credentials.details
|
|
120
|
+
? `Token exchange failed (${credentials.details}).`
|
|
121
|
+
: "Token exchange failed. The code may be invalid or expired.",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
113
124
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
message: credentials.details
|
|
119
|
-
? `Token exchange failed (${credentials.details}).`
|
|
120
|
-
: "Token exchange failed. The code may be invalid or expired.",
|
|
125
|
+
const stored = (await loadAccounts()) ?? {
|
|
126
|
+
version: 1,
|
|
127
|
+
accounts: [],
|
|
128
|
+
activeIndex: 0,
|
|
121
129
|
};
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
130
|
+
|
|
131
|
+
if (pending.mode === "login") {
|
|
132
|
+
const existingIdx = stored.accounts.findIndex((acc) => acc.refreshToken === credentials.refresh);
|
|
133
|
+
if (existingIdx >= 0) {
|
|
134
|
+
const acc = stored.accounts[existingIdx];
|
|
135
|
+
const accIsCC = acc.source === "cc-keychain" || acc.source === "cc-file";
|
|
136
|
+
acc.access = credentials.access;
|
|
137
|
+
acc.expires = credentials.expires;
|
|
138
|
+
acc.token_updated_at = Date.now();
|
|
139
|
+
acc.enabled = true;
|
|
140
|
+
acc.consecutiveFailures = 0;
|
|
141
|
+
acc.lastFailureTime = null;
|
|
142
|
+
acc.rateLimitResetTimes = {};
|
|
143
|
+
if (!accIsCC) {
|
|
144
|
+
if (credentials.email) acc.email = credentials.email;
|
|
145
|
+
acc.identity = resolveIdentityFromOAuthExchange(credentials);
|
|
146
|
+
acc.source = acc.source ?? "oauth";
|
|
147
|
+
}
|
|
148
|
+
await saveAccounts(stored);
|
|
149
|
+
await persistOpenCodeAuth(acc.refreshToken, acc.access, acc.expires);
|
|
150
|
+
await reloadAccountManagerFromDisk();
|
|
151
|
+
pendingSlashOAuth.delete(sessionID);
|
|
152
|
+
const name = acc.email || `Account ${existingIdx + 1}`;
|
|
153
|
+
return {
|
|
154
|
+
ok: true,
|
|
155
|
+
message: `Updated existing account #${existingIdx + 1} (${name}).`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (stored.accounts.length >= 10) {
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
162
|
+
message: "Maximum of 10 accounts reached. Remove one first.",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const now = Date.now();
|
|
167
|
+
stored.accounts.push({
|
|
168
|
+
id: `${now}:${credentials.refresh.slice(0, 12)}`,
|
|
169
|
+
email: credentials.email,
|
|
170
|
+
identity: resolveIdentityFromOAuthExchange(credentials),
|
|
171
|
+
refreshToken: credentials.refresh,
|
|
172
|
+
access: credentials.access,
|
|
173
|
+
expires: credentials.expires,
|
|
174
|
+
token_updated_at: now,
|
|
175
|
+
addedAt: now,
|
|
176
|
+
lastUsed: 0,
|
|
177
|
+
enabled: true,
|
|
178
|
+
rateLimitResetTimes: {},
|
|
179
|
+
consecutiveFailures: 0,
|
|
180
|
+
lastFailureTime: null,
|
|
181
|
+
stats: createDefaultStats(now),
|
|
182
|
+
source: "oauth",
|
|
183
|
+
});
|
|
184
|
+
await saveAccounts(stored);
|
|
185
|
+
const newAccount = stored.accounts[stored.accounts.length - 1];
|
|
186
|
+
await persistOpenCodeAuth(newAccount.refreshToken, newAccount.access, newAccount.expires);
|
|
187
|
+
await reloadAccountManagerFromDisk();
|
|
188
|
+
pendingSlashOAuth.delete(sessionID);
|
|
189
|
+
const label = credentials.email || `Account ${stored.accounts.length}`;
|
|
190
|
+
return {
|
|
191
|
+
ok: true,
|
|
192
|
+
message: `Added account #${stored.accounts.length} (${label}).`,
|
|
193
|
+
};
|
|
150
194
|
}
|
|
151
195
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
196
|
+
// reauth flow
|
|
197
|
+
const idx = pending.targetIndex ?? -1;
|
|
198
|
+
if (idx < 0 || idx >= stored.accounts.length) {
|
|
199
|
+
pendingSlashOAuth.delete(sessionID);
|
|
200
|
+
return {
|
|
201
|
+
ok: false,
|
|
202
|
+
message: "Target account no longer exists. Start reauth again.",
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const existing = stored.accounts[idx];
|
|
207
|
+
const existingIsCC = existing.source === "cc-keychain" || existing.source === "cc-file";
|
|
208
|
+
existing.refreshToken = credentials.refresh;
|
|
209
|
+
existing.access = credentials.access;
|
|
210
|
+
existing.expires = credentials.expires;
|
|
211
|
+
existing.token_updated_at = Date.now();
|
|
212
|
+
existing.enabled = true;
|
|
213
|
+
existing.consecutiveFailures = 0;
|
|
214
|
+
existing.lastFailureTime = null;
|
|
215
|
+
existing.rateLimitResetTimes = {};
|
|
216
|
+
if (!existingIsCC) {
|
|
217
|
+
if (credentials.email) existing.email = credentials.email;
|
|
218
|
+
existing.identity = resolveIdentityFromOAuthExchange(credentials);
|
|
219
|
+
existing.source = existing.source ?? "oauth";
|
|
157
220
|
}
|
|
158
221
|
|
|
159
|
-
const now = Date.now();
|
|
160
|
-
stored.accounts.push({
|
|
161
|
-
id: `${now}:${credentials.refresh.slice(0, 12)}`,
|
|
162
|
-
email: credentials.email,
|
|
163
|
-
refreshToken: credentials.refresh,
|
|
164
|
-
access: credentials.access,
|
|
165
|
-
expires: credentials.expires,
|
|
166
|
-
token_updated_at: now,
|
|
167
|
-
addedAt: now,
|
|
168
|
-
lastUsed: 0,
|
|
169
|
-
enabled: true,
|
|
170
|
-
rateLimitResetTimes: {},
|
|
171
|
-
consecutiveFailures: 0,
|
|
172
|
-
lastFailureTime: null,
|
|
173
|
-
stats: createDefaultStats(now),
|
|
174
|
-
});
|
|
175
222
|
await saveAccounts(stored);
|
|
176
|
-
|
|
177
|
-
await persistOpenCodeAuth(newAccount.refreshToken, newAccount.access, newAccount.expires);
|
|
223
|
+
await persistOpenCodeAuth(existing.refreshToken, existing.access, existing.expires);
|
|
178
224
|
await reloadAccountManagerFromDisk();
|
|
179
225
|
pendingSlashOAuth.delete(sessionID);
|
|
180
|
-
const
|
|
226
|
+
const name = existing.email || `Account ${idx + 1}`;
|
|
181
227
|
return {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// reauth flow
|
|
188
|
-
const idx = pending.targetIndex ?? -1;
|
|
189
|
-
if (idx < 0 || idx >= stored.accounts.length) {
|
|
190
|
-
pendingSlashOAuth.delete(sessionID);
|
|
191
|
-
return {
|
|
192
|
-
ok: false,
|
|
193
|
-
message: "Target account no longer exists. Start reauth again.",
|
|
228
|
+
ok: true,
|
|
229
|
+
message: `Re-authenticated account #${idx + 1} (${name}).`,
|
|
194
230
|
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const existing = stored.accounts[idx];
|
|
198
|
-
existing.refreshToken = credentials.refresh;
|
|
199
|
-
existing.access = credentials.access;
|
|
200
|
-
existing.expires = credentials.expires;
|
|
201
|
-
if (credentials.email) existing.email = credentials.email;
|
|
202
|
-
existing.enabled = true;
|
|
203
|
-
existing.consecutiveFailures = 0;
|
|
204
|
-
existing.lastFailureTime = null;
|
|
205
|
-
existing.rateLimitResetTimes = {};
|
|
206
|
-
|
|
207
|
-
await saveAccounts(stored);
|
|
208
|
-
await persistOpenCodeAuth(existing.refreshToken, existing.access, existing.expires);
|
|
209
|
-
await reloadAccountManagerFromDisk();
|
|
210
|
-
pendingSlashOAuth.delete(sessionID);
|
|
211
|
-
const name = existing.email || `Account ${idx + 1}`;
|
|
212
|
-
return {
|
|
213
|
-
ok: true,
|
|
214
|
-
message: `Re-authenticated account #${idx + 1} (${name}).`,
|
|
215
|
-
};
|
|
216
231
|
}
|
package/src/commands/prompts.ts
CHANGED
|
@@ -10,82 +10,82 @@ import type { AccountManager } from "../accounts.js";
|
|
|
10
10
|
* Show the main account menu and return the user's choice.
|
|
11
11
|
*/
|
|
12
12
|
export async function promptAccountMenu(
|
|
13
|
-
|
|
13
|
+
accountManager: AccountManager,
|
|
14
14
|
): Promise<"add" | "fresh" | "manage" | "cancel"> {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const accounts = accountManager.getAccountsSnapshot();
|
|
16
|
+
const currentIndex = accountManager.getCurrentIndex();
|
|
17
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
try {
|
|
20
|
+
console.log(`\n${accounts.length} account(s) configured:`);
|
|
21
|
+
for (const acc of accounts) {
|
|
22
|
+
const name = acc.email || `Account ${acc.index + 1}`;
|
|
23
|
+
const active = acc.index === currentIndex ? " (active)" : "";
|
|
24
|
+
const disabled = !acc.enabled ? " [disabled]" : "";
|
|
25
|
+
console.log(` ${acc.index + 1}. ${name}${active}${disabled}`);
|
|
26
|
+
}
|
|
27
|
+
console.log("");
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
while (true) {
|
|
30
|
+
const answer = await rl.question("(a)dd new, (f)resh start, (m)anage, (c)ancel? [a/f/m/c]: ");
|
|
31
|
+
const normalized = answer.trim().toLowerCase();
|
|
32
|
+
if (normalized === "a" || normalized === "add") return "add";
|
|
33
|
+
if (normalized === "f" || normalized === "fresh") return "fresh";
|
|
34
|
+
if (normalized === "m" || normalized === "manage") return "manage";
|
|
35
|
+
if (normalized === "c" || normalized === "cancel") return "cancel";
|
|
36
|
+
console.log("Please enter 'a', 'f', 'm', or 'c'.");
|
|
37
|
+
}
|
|
38
|
+
} finally {
|
|
39
|
+
rl.close();
|
|
37
40
|
}
|
|
38
|
-
} finally {
|
|
39
|
-
rl.close();
|
|
40
|
-
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* Show the manage-accounts sub-menu (toggle/delete accounts).
|
|
45
45
|
*/
|
|
46
46
|
export async function promptManageAccounts(accountManager: AccountManager): Promise<void> {
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
const accounts = accountManager.getAccountsSnapshot();
|
|
48
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
try {
|
|
51
|
+
console.log("\nManage accounts:");
|
|
52
|
+
for (const acc of accounts) {
|
|
53
|
+
const name = acc.email || `Account ${acc.index + 1}`;
|
|
54
|
+
const status = acc.enabled ? "enabled" : "disabled";
|
|
55
|
+
console.log(` ${acc.index + 1}. ${name} [${status}]`);
|
|
56
|
+
}
|
|
57
|
+
console.log("");
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
while (true) {
|
|
60
|
+
const answer = await rl.question("Enter account number to toggle, (d)N to delete (e.g. d1), or (b)ack: ");
|
|
61
|
+
const normalized = answer.trim().toLowerCase();
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
if (normalized === "b" || normalized === "back") return;
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
65
|
+
// Delete: d1, d2, etc.
|
|
66
|
+
const deleteMatch = normalized.match(/^d(\d+)$/);
|
|
67
|
+
if (deleteMatch) {
|
|
68
|
+
const idx = parseInt(deleteMatch[1], 10) - 1;
|
|
69
|
+
if (idx >= 0 && idx < accounts.length) {
|
|
70
|
+
accountManager.removeAccount(idx);
|
|
71
|
+
console.log(`Removed account ${idx + 1}.`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
console.log("Invalid account number.");
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
// Toggle: just the number
|
|
79
|
+
const num = parseInt(normalized, 10);
|
|
80
|
+
if (!isNaN(num) && num >= 1 && num <= accounts.length) {
|
|
81
|
+
const newState = accountManager.toggleAccount(num - 1);
|
|
82
|
+
console.log(`Account ${num} is now ${newState ? "enabled" : "disabled"}.`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
console.log("Invalid input.");
|
|
87
|
+
}
|
|
88
|
+
} finally {
|
|
89
|
+
rl.close();
|
|
87
90
|
}
|
|
88
|
-
} finally {
|
|
89
|
-
rl.close();
|
|
90
|
-
}
|
|
91
91
|
}
|