@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
package/src/commands/router.ts
CHANGED
|
@@ -2,64 +2,45 @@
|
|
|
2
2
|
// Slash-command router for /anthropic commands
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
|
|
5
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
-
import { basename, resolve } from "node:path";
|
|
7
5
|
import type { AccountManager } from "../accounts.js";
|
|
8
|
-
import { resolveBetaShortcut } from "../betas.js";
|
|
9
6
|
import type { AnthropicAuthConfig } from "../config.js";
|
|
10
|
-
import { loadConfigFresh, saveConfig } from "../config.js";
|
|
11
|
-
import { isTruthyEnv } from "../env.js";
|
|
12
7
|
import { loadAccounts } from "../storage.js";
|
|
13
8
|
import type { ManagedAccount } from "../token-refresh.js";
|
|
9
|
+
import { handleBetasCommand } from "./handlers/betas.js";
|
|
10
|
+
import { handleConfigCommand, handleSetCommand } from "./handlers/config.js";
|
|
11
|
+
import { handleFilesCommand } from "./handlers/files.js";
|
|
14
12
|
import { completeSlashOAuth, startSlashOAuth, type OAuthFlowDeps, type PendingOAuthEntry } from "./oauth-flow.js";
|
|
15
13
|
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Maximum number of file-to-account pinning entries retained in memory.
|
|
20
|
-
* Bounded to prevent unbounded growth across long sessions that touch many
|
|
21
|
-
* Files API uploads. Eviction is FIFO: when the cap is hit, the oldest entry
|
|
22
|
-
* (Maps preserve insertion order) is dropped before inserting the new one.
|
|
23
|
-
*/
|
|
24
|
-
export const FILE_ACCOUNT_MAP_MAX_SIZE = 1000;
|
|
14
|
+
// Re-export files utilities so existing imports from "./router.js" continue to work
|
|
15
|
+
export { capFileAccountMap, FILE_ACCOUNT_MAP_MAX_SIZE } from "./handlers/files.js";
|
|
25
16
|
|
|
26
|
-
|
|
27
|
-
* Insert a fileId→accountIndex binding with FIFO eviction when the cap is reached.
|
|
28
|
-
* See {@link FILE_ACCOUNT_MAP_MAX_SIZE} for the rationale.
|
|
29
|
-
*/
|
|
30
|
-
export function capFileAccountMap(fileAccountMap: Map<string, number>, fileId: string, accountIndex: number): void {
|
|
31
|
-
if (fileAccountMap.size >= FILE_ACCOUNT_MAP_MAX_SIZE) {
|
|
32
|
-
const oldestKey = fileAccountMap.keys().next().value;
|
|
33
|
-
if (oldestKey !== undefined) fileAccountMap.delete(oldestKey);
|
|
34
|
-
}
|
|
35
|
-
fileAccountMap.set(fileId, accountIndex);
|
|
36
|
-
}
|
|
17
|
+
export const ANTHROPIC_COMMAND_HANDLED = "__ANTHROPIC_COMMAND_HANDLED__";
|
|
37
18
|
|
|
38
19
|
export interface CliResult {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
20
|
+
code: number;
|
|
21
|
+
stdout: string;
|
|
22
|
+
stderr: string;
|
|
42
23
|
}
|
|
43
24
|
|
|
44
25
|
export interface CommandDeps {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
26
|
+
sendCommandMessage: (sessionID: string, message: string) => Promise<void>;
|
|
27
|
+
accountManager: AccountManager | null;
|
|
28
|
+
runCliCommand: (args: string[]) => Promise<CliResult>;
|
|
29
|
+
config: AnthropicAuthConfig;
|
|
30
|
+
fileAccountMap: Map<string, number>;
|
|
31
|
+
initialAccountPinned: boolean;
|
|
32
|
+
pendingSlashOAuth: Map<string, PendingOAuthEntry>;
|
|
33
|
+
reloadAccountManagerFromDisk: () => Promise<void>;
|
|
34
|
+
persistOpenCodeAuth: (refresh: string, access: string | undefined, expires: number | undefined) => Promise<void>;
|
|
35
|
+
refreshAccountTokenSingleFlight: (account: ManagedAccount) => Promise<string>;
|
|
55
36
|
}
|
|
56
37
|
|
|
57
38
|
/**
|
|
58
39
|
* Remove ANSI color/control codes from output text.
|
|
59
40
|
*/
|
|
60
41
|
export function stripAnsi(value: string): string {
|
|
61
|
-
|
|
62
|
-
|
|
42
|
+
// eslint-disable-next-line no-control-regex -- ANSI escape sequences start with \x1b which is a control char
|
|
43
|
+
return value.replace(/\x1b\[[0-9;]*m/g, "");
|
|
63
44
|
}
|
|
64
45
|
|
|
65
46
|
/**
|
|
@@ -70,641 +51,168 @@ export function stripAnsi(value: string): string {
|
|
|
70
51
|
* a 'c d' -> ["a", "c d"]
|
|
71
52
|
*/
|
|
72
53
|
export function parseCommandArgs(raw: string): string[] {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
54
|
+
if (!raw || !raw.trim()) return [];
|
|
55
|
+
const parts: string[] = [];
|
|
56
|
+
const re = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|(\S+)/g;
|
|
57
|
+
let match;
|
|
58
|
+
while ((match = re.exec(raw)) !== null) {
|
|
59
|
+
const token = match[1] ?? match[2] ?? match[3] ?? "";
|
|
60
|
+
parts.push(token.replace(/\\(["'\\])/g, "$1"));
|
|
61
|
+
}
|
|
62
|
+
return parts;
|
|
82
63
|
}
|
|
83
64
|
|
|
84
65
|
/**
|
|
85
66
|
* Handle /anthropic slash commands.
|
|
86
67
|
*/
|
|
87
68
|
export async function handleAnthropicSlashCommand(
|
|
88
|
-
|
|
89
|
-
|
|
69
|
+
input: { command: string; arguments?: string; sessionID: string },
|
|
70
|
+
deps: CommandDeps,
|
|
90
71
|
): Promise<void> {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const args = parseCommandArgs(input.arguments || "");
|
|
112
|
-
const primary = (args[0] || "list").toLowerCase();
|
|
113
|
-
|
|
114
|
-
// Friendly alias: /anthropic usage -> list
|
|
115
|
-
if (primary === "usage") {
|
|
116
|
-
const result = await runCliCommand(["list"]);
|
|
117
|
-
const heading = result.code === 0 ? "▣ Anthropic" : "▣ Anthropic (error)";
|
|
118
|
-
const body = result.stdout || result.stderr || "No output.";
|
|
119
|
-
await sendCommandMessage(input.sessionID, [heading, "", body].join("\n"));
|
|
120
|
-
await reloadAccountManagerFromDisk();
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Two-step login flow
|
|
125
|
-
if (primary === "login") {
|
|
126
|
-
if ((args[1] || "").toLowerCase() === "complete") {
|
|
127
|
-
const code = args.slice(2).join(" ").trim();
|
|
128
|
-
if (!code) {
|
|
129
|
-
await sendCommandMessage(
|
|
130
|
-
input.sessionID,
|
|
131
|
-
"▣ Anthropic OAuth\n\nMissing code. Use: /anthropic login complete <code#state>",
|
|
132
|
-
);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
const result = await completeSlashOAuth(input.sessionID, code, oauthFlowDeps);
|
|
136
|
-
const heading = result.ok ? "▣ Anthropic OAuth" : "▣ Anthropic OAuth (error)";
|
|
137
|
-
await sendCommandMessage(input.sessionID, `${heading}\n\n${result.message}`);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
await startSlashOAuth(input.sessionID, "login", undefined, oauthFlowDeps);
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Two-step reauth flow
|
|
145
|
-
if (primary === "reauth") {
|
|
146
|
-
if ((args[1] || "").toLowerCase() === "complete") {
|
|
147
|
-
const code = args.slice(2).join(" ").trim();
|
|
148
|
-
if (!code) {
|
|
149
|
-
await sendCommandMessage(
|
|
150
|
-
input.sessionID,
|
|
151
|
-
"▣ Anthropic OAuth\n\nMissing code. Use: /anthropic reauth complete <code#state>",
|
|
152
|
-
);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
const result = await completeSlashOAuth(input.sessionID, code, oauthFlowDeps);
|
|
156
|
-
const heading = result.ok ? "▣ Anthropic OAuth" : "▣ Anthropic OAuth (error)";
|
|
157
|
-
await sendCommandMessage(input.sessionID, `${heading}\n\n${result.message}`);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
const n = parseInt(args[1], 10);
|
|
161
|
-
if (Number.isNaN(n) || n < 1) {
|
|
162
|
-
await sendCommandMessage(
|
|
163
|
-
input.sessionID,
|
|
164
|
-
"▣ Anthropic OAuth\n\nProvide an account number. Example: /anthropic reauth 1",
|
|
165
|
-
);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
const stored = await loadAccounts();
|
|
169
|
-
if (!stored || stored.accounts.length === 0) {
|
|
170
|
-
await sendCommandMessage(input.sessionID, "▣ Anthropic OAuth (error)\n\nNo accounts configured.");
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const idx = n - 1;
|
|
174
|
-
if (idx >= stored.accounts.length) {
|
|
175
|
-
await sendCommandMessage(
|
|
176
|
-
input.sessionID,
|
|
177
|
-
`▣ Anthropic OAuth (error)\n\nAccount ${n} does not exist. You have ${stored.accounts.length} account(s).`,
|
|
178
|
-
);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
await startSlashOAuth(input.sessionID, "reauth", idx, oauthFlowDeps);
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// /anthropic config
|
|
186
|
-
if (primary === "config") {
|
|
187
|
-
const fresh = loadConfigFresh();
|
|
188
|
-
const lines = [
|
|
189
|
-
"▣ Anthropic Config",
|
|
190
|
-
"",
|
|
191
|
-
`strategy: ${fresh.account_selection_strategy}`,
|
|
192
|
-
`emulation: ${fresh.signature_emulation.enabled ? "on" : "off"}`,
|
|
193
|
-
`compaction: ${fresh.signature_emulation.prompt_compaction}`,
|
|
194
|
-
`1m-context: ${fresh.override_model_limits.enabled ? "on" : "off"}`,
|
|
195
|
-
`idle-refresh: ${fresh.idle_refresh.enabled ? "on" : "off"}`,
|
|
196
|
-
`debug: ${fresh.debug ? "on" : "off"}`,
|
|
197
|
-
`quiet: ${fresh.toasts.quiet ? "on" : "off"}`,
|
|
198
|
-
`custom_betas: ${fresh.custom_betas.length ? fresh.custom_betas.join(", ") : "(none)"}`,
|
|
199
|
-
];
|
|
200
|
-
await sendCommandMessage(input.sessionID, lines.join("\n"));
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// /anthropic set <key> <value>
|
|
205
|
-
if (primary === "set") {
|
|
206
|
-
const key = (args[1] || "").toLowerCase();
|
|
207
|
-
const value = (args[2] || "").toLowerCase();
|
|
208
|
-
const setters: Record<string, () => void> = {
|
|
209
|
-
emulation: () =>
|
|
210
|
-
saveConfig({
|
|
211
|
-
signature_emulation: {
|
|
212
|
-
enabled: value === "on" || value === "1" || value === "true",
|
|
213
|
-
},
|
|
214
|
-
}),
|
|
215
|
-
compaction: () =>
|
|
216
|
-
saveConfig({
|
|
217
|
-
signature_emulation: {
|
|
218
|
-
prompt_compaction: value === "off" ? "off" : "minimal",
|
|
219
|
-
},
|
|
220
|
-
}),
|
|
221
|
-
"1m-context": () =>
|
|
222
|
-
saveConfig({
|
|
223
|
-
override_model_limits: {
|
|
224
|
-
enabled: value === "on" || value === "1" || value === "true",
|
|
225
|
-
},
|
|
226
|
-
}),
|
|
227
|
-
"idle-refresh": () =>
|
|
228
|
-
saveConfig({
|
|
229
|
-
idle_refresh: {
|
|
230
|
-
enabled: value === "on" || value === "1" || value === "true",
|
|
231
|
-
},
|
|
232
|
-
}),
|
|
233
|
-
debug: () =>
|
|
234
|
-
saveConfig({
|
|
235
|
-
debug: value === "on" || value === "1" || value === "true",
|
|
236
|
-
}),
|
|
237
|
-
quiet: () =>
|
|
238
|
-
saveConfig({
|
|
239
|
-
toasts: {
|
|
240
|
-
quiet: value === "on" || value === "1" || value === "true",
|
|
241
|
-
},
|
|
242
|
-
}),
|
|
243
|
-
strategy: () => {
|
|
244
|
-
const valid = ["sticky", "round-robin", "hybrid"];
|
|
245
|
-
if (valid.includes(value))
|
|
246
|
-
saveConfig({ account_selection_strategy: value as "sticky" | "round-robin" | "hybrid" });
|
|
247
|
-
else throw new Error(`Invalid strategy. Valid: ${valid.join(", ")}`);
|
|
248
|
-
},
|
|
72
|
+
const {
|
|
73
|
+
sendCommandMessage,
|
|
74
|
+
accountManager,
|
|
75
|
+
runCliCommand,
|
|
76
|
+
config,
|
|
77
|
+
fileAccountMap,
|
|
78
|
+
initialAccountPinned,
|
|
79
|
+
pendingSlashOAuth,
|
|
80
|
+
reloadAccountManagerFromDisk,
|
|
81
|
+
persistOpenCodeAuth,
|
|
82
|
+
refreshAccountTokenSingleFlight,
|
|
83
|
+
} = deps;
|
|
84
|
+
|
|
85
|
+
const oauthFlowDeps: OAuthFlowDeps = {
|
|
86
|
+
pendingSlashOAuth,
|
|
87
|
+
sendCommandMessage,
|
|
88
|
+
reloadAccountManagerFromDisk,
|
|
89
|
+
persistOpenCodeAuth,
|
|
249
90
|
};
|
|
250
91
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
await sendCommandMessage(
|
|
254
|
-
input.sessionID,
|
|
255
|
-
`▣ Anthropic Set\n\nUsage: /anthropic set <key> <value>\nKeys: ${keys}\nValues: on/off (or specific values for strategy/compaction)`,
|
|
256
|
-
);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
if (!value) {
|
|
260
|
-
await sendCommandMessage(input.sessionID, `▣ Anthropic Set\n\nMissing value for "${key}".`);
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
setters[key]();
|
|
264
|
-
Object.assign(config, loadConfigFresh());
|
|
265
|
-
await sendCommandMessage(input.sessionID, `▣ Anthropic Set\n\n${key} = ${value}`);
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// /anthropic betas [add|remove <beta>]
|
|
270
|
-
if (primary === "betas") {
|
|
271
|
-
const action = (args[1] || "").toLowerCase();
|
|
272
|
-
|
|
273
|
-
if (!action || action === "list") {
|
|
274
|
-
const fresh = loadConfigFresh();
|
|
275
|
-
const strategy = fresh.account_selection_strategy || config.account_selection_strategy;
|
|
276
|
-
const lines = [
|
|
277
|
-
"▣ Anthropic Betas",
|
|
278
|
-
"",
|
|
279
|
-
"Preset betas (auto-computed per model/provider):",
|
|
280
|
-
" oauth-2025-04-20, claude-code-20250219,",
|
|
281
|
-
" advanced-tool-use-2025-11-20, fast-mode-2026-02-01,",
|
|
282
|
-
" interleaved-thinking-2025-05-14 (non-Opus 4.6) OR effort-2025-11-24 (Opus 4.6),",
|
|
283
|
-
" files-api-2025-04-14 (only /v1/files and requests with file_id),",
|
|
284
|
-
" token-counting-2024-11-01 (only /v1/messages/count_tokens),",
|
|
285
|
-
` prompt-caching-scope-2026-01-05 (non-interactive${strategy === "round-robin" ? ", skipped in round-robin" : ""})`,
|
|
286
|
-
"",
|
|
287
|
-
`Experimental betas: ${isTruthyEnv(process.env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS) ? "disabled (CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1)" : "enabled"}`,
|
|
288
|
-
`Strategy: ${strategy}${initialAccountPinned ? " (pinned via OPENCODE_ANTHROPIC_INITIAL_ACCOUNT)" : ""}`,
|
|
289
|
-
`Custom betas: ${fresh.custom_betas.length ? fresh.custom_betas.join(", ") : "(none)"}`,
|
|
290
|
-
"",
|
|
291
|
-
"Toggleable presets:",
|
|
292
|
-
" /anthropic betas add structured-outputs-2025-12-15",
|
|
293
|
-
" /anthropic betas add context-management-2025-06-27",
|
|
294
|
-
" /anthropic betas add task-budgets-2026-03-13",
|
|
295
|
-
" /anthropic betas add web-search-2025-03-05",
|
|
296
|
-
" /anthropic betas add compact-2026-01-12",
|
|
297
|
-
" /anthropic betas add mcp-servers-2025-12-04",
|
|
298
|
-
" /anthropic betas add redact-thinking-2026-02-12",
|
|
299
|
-
" /anthropic betas add 1m (shortcut for context-1m-2025-08-07)",
|
|
300
|
-
"",
|
|
301
|
-
"Remove: /anthropic betas remove <beta>",
|
|
302
|
-
];
|
|
303
|
-
await sendCommandMessage(input.sessionID, lines.join("\n"));
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
92
|
+
const args = parseCommandArgs(input.arguments || "");
|
|
93
|
+
const primary = (args[0] || "list").toLowerCase();
|
|
306
94
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const fresh = loadConfigFresh();
|
|
315
|
-
const current = fresh.custom_betas || [];
|
|
316
|
-
if (current.includes(beta)) {
|
|
317
|
-
await sendCommandMessage(input.sessionID, `▣ Anthropic Betas\n\n"${beta}" already added.`);
|
|
95
|
+
// Friendly alias: /anthropic usage -> list
|
|
96
|
+
if (primary === "usage") {
|
|
97
|
+
const result = await runCliCommand(["list"]);
|
|
98
|
+
const heading = result.code === 0 ? "▣ Anthropic" : "▣ Anthropic (error)";
|
|
99
|
+
const body = result.stdout || result.stderr || "No output.";
|
|
100
|
+
await sendCommandMessage(input.sessionID, [heading, "", body].join("\n"));
|
|
101
|
+
await reloadAccountManagerFromDisk();
|
|
318
102
|
return;
|
|
319
|
-
}
|
|
320
|
-
saveConfig({ custom_betas: [...current, beta] });
|
|
321
|
-
Object.assign(config, loadConfigFresh());
|
|
322
|
-
const fromShortcut = beta !== betaInput;
|
|
323
|
-
await sendCommandMessage(
|
|
324
|
-
input.sessionID,
|
|
325
|
-
`▣ Anthropic Betas\n\nAdded: ${beta}${fromShortcut ? ` (from shortcut: ${betaInput})` : ""}`,
|
|
326
|
-
);
|
|
327
|
-
return;
|
|
328
103
|
}
|
|
329
104
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
105
|
+
// Two-step login flow
|
|
106
|
+
if (primary === "login") {
|
|
107
|
+
if ((args[1] || "").toLowerCase() === "complete") {
|
|
108
|
+
const code = args.slice(2).join(" ").trim();
|
|
109
|
+
if (!code) {
|
|
110
|
+
await sendCommandMessage(
|
|
111
|
+
input.sessionID,
|
|
112
|
+
"▣ Anthropic OAuth\n\nMissing code. Use: /anthropic login complete <code#state>",
|
|
113
|
+
);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const result = await completeSlashOAuth(input.sessionID, code, oauthFlowDeps);
|
|
117
|
+
const heading = result.ok ? "▣ Anthropic OAuth" : "▣ Anthropic OAuth (error)";
|
|
118
|
+
await sendCommandMessage(input.sessionID, `${heading}\n\n${result.message}`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
await startSlashOAuth(input.sessionID, "login", undefined, oauthFlowDeps);
|
|
341
122
|
return;
|
|
342
|
-
}
|
|
343
|
-
saveConfig({ custom_betas: current.filter((b) => b !== beta) });
|
|
344
|
-
Object.assign(config, loadConfigFresh());
|
|
345
|
-
await sendCommandMessage(input.sessionID, `▣ Anthropic Betas\n\nRemoved: ${beta}`);
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
await sendCommandMessage(input.sessionID, "▣ Anthropic Betas\n\nUsage: /anthropic betas [add|remove <beta>]");
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// /anthropic files [list|upload|get|delete|download]
|
|
354
|
-
if (primary === "files") {
|
|
355
|
-
let targetAccountId: string | null = null;
|
|
356
|
-
const filteredArgs: string[] = [];
|
|
357
|
-
for (let i = 0; i < args.length; i++) {
|
|
358
|
-
if (args[i] === "--account" && i + 1 < args.length) {
|
|
359
|
-
targetAccountId = args[i + 1];
|
|
360
|
-
i++;
|
|
361
|
-
} else {
|
|
362
|
-
filteredArgs.push(args[i]);
|
|
363
|
-
}
|
|
364
123
|
}
|
|
365
|
-
const action = (filteredArgs[1] || "").toLowerCase();
|
|
366
124
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const idx = parseInt(identifier, 10);
|
|
383
|
-
if (!isNaN(idx) && idx >= 1) {
|
|
384
|
-
const byIdx = accounts.find((a) => a.index === idx - 1);
|
|
385
|
-
if (byIdx) return { account: byIdx, label: byIdx.email || `Account ${byIdx.index + 1}` };
|
|
125
|
+
// Two-step reauth flow
|
|
126
|
+
if (primary === "reauth") {
|
|
127
|
+
if ((args[1] || "").toLowerCase() === "complete") {
|
|
128
|
+
const code = args.slice(2).join(" ").trim();
|
|
129
|
+
if (!code) {
|
|
130
|
+
await sendCommandMessage(
|
|
131
|
+
input.sessionID,
|
|
132
|
+
"▣ Anthropic OAuth\n\nMissing code. Use: /anthropic reauth complete <code#state>",
|
|
133
|
+
);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const result = await completeSlashOAuth(input.sessionID, code, oauthFlowDeps);
|
|
137
|
+
const heading = result.ok ? "▣ Anthropic OAuth" : "▣ Anthropic OAuth (error)";
|
|
138
|
+
await sendCommandMessage(input.sessionID, `${heading}\n\n${result.message}`);
|
|
139
|
+
return;
|
|
386
140
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const current = accountManager!.getCurrentAccount();
|
|
390
|
-
if (!current) return null;
|
|
391
|
-
return { account: current, label: current.email || `Account ${current.index + 1}` };
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
async function getFilesAuth(acct: ManagedAccount) {
|
|
395
|
-
let tok = acct.access;
|
|
396
|
-
if (!tok || !acct.expires || acct.expires < Date.now()) {
|
|
397
|
-
tok = await refreshAccountTokenSingleFlight(acct);
|
|
398
|
-
}
|
|
399
|
-
return {
|
|
400
|
-
authorization: `Bearer ${tok}`,
|
|
401
|
-
"anthropic-beta": "oauth-2025-04-20,files-api-2025-04-14",
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const apiBase = "https://api.anthropic.com";
|
|
406
|
-
|
|
407
|
-
try {
|
|
408
|
-
if (!action || action === "list") {
|
|
409
|
-
if (targetAccountId) {
|
|
410
|
-
const resolved = resolveTargetAccount(targetAccountId);
|
|
411
|
-
if (!resolved) {
|
|
141
|
+
const n = parseInt(args[1], 10);
|
|
142
|
+
if (Number.isNaN(n) || n < 1) {
|
|
412
143
|
await sendCommandMessage(
|
|
413
|
-
|
|
414
|
-
|
|
144
|
+
input.sessionID,
|
|
145
|
+
"▣ Anthropic OAuth\n\nProvide an account number. Example: /anthropic reauth 1",
|
|
415
146
|
);
|
|
416
147
|
return;
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
148
|
+
}
|
|
149
|
+
const stored = await loadAccounts();
|
|
150
|
+
if (!stored || stored.accounts.length === 0) {
|
|
151
|
+
await sendCommandMessage(input.sessionID, "▣ Anthropic OAuth (error)\n\nNo accounts configured.");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const idx = n - 1;
|
|
155
|
+
if (idx >= stored.accounts.length) {
|
|
423
156
|
await sendCommandMessage(
|
|
424
|
-
|
|
425
|
-
|
|
157
|
+
input.sessionID,
|
|
158
|
+
`▣ Anthropic OAuth (error)\n\nAccount ${n} does not exist. You have ${stored.accounts.length} account(s).`,
|
|
426
159
|
);
|
|
427
160
|
return;
|
|
428
|
-
}
|
|
429
|
-
const data = (await res.json()) as {
|
|
430
|
-
data?: Array<{ id: string; filename: string; size: number; purpose: string }>;
|
|
431
|
-
};
|
|
432
|
-
const files = data.data || [];
|
|
433
|
-
for (const f of files) capFileAccountMap(fileAccountMap, f.id, account.index);
|
|
434
|
-
if (files.length === 0) {
|
|
435
|
-
await sendCommandMessage(input.sessionID, `▣ Anthropic Files [${label}]\n\nNo files uploaded.`);
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
const lines = [`▣ Anthropic Files [${label}]`, "", `${files.length} file(s):`, ""];
|
|
439
|
-
for (const f of files) {
|
|
440
|
-
const sizeKB = (f.size / 1024).toFixed(1);
|
|
441
|
-
lines.push(` ${f.id} ${f.filename} (${sizeKB} KB, ${f.purpose})`);
|
|
442
|
-
}
|
|
443
|
-
await sendCommandMessage(input.sessionID, lines.join("\n"));
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
const accounts = accountManager.getEnabledAccounts();
|
|
448
|
-
const allLines = ["▣ Anthropic Files (all accounts)", ""];
|
|
449
|
-
let totalFiles = 0;
|
|
450
|
-
for (const acct of accounts) {
|
|
451
|
-
const label = acct.email || `Account ${acct.index + 1}`;
|
|
452
|
-
try {
|
|
453
|
-
const headers = await getFilesAuth(acct);
|
|
454
|
-
const res = await fetch(`${apiBase}/v1/files`, { headers });
|
|
455
|
-
if (!res.ok) {
|
|
456
|
-
allLines.push(`[${label}] Error: HTTP ${res.status}`);
|
|
457
|
-
allLines.push("");
|
|
458
|
-
continue;
|
|
459
|
-
}
|
|
460
|
-
const data = (await res.json()) as {
|
|
461
|
-
data?: Array<{ id: string; filename: string; size: number; purpose: string }>;
|
|
462
|
-
};
|
|
463
|
-
const files = data.data || [];
|
|
464
|
-
for (const f of files) capFileAccountMap(fileAccountMap, f.id, acct.index);
|
|
465
|
-
totalFiles += files.length;
|
|
466
|
-
if (files.length === 0) {
|
|
467
|
-
allLines.push(`[${label}] No files`);
|
|
468
|
-
} else {
|
|
469
|
-
allLines.push(`[${label}] ${files.length} file(s):`);
|
|
470
|
-
for (const f of files) {
|
|
471
|
-
const sizeKB = (f.size / 1024).toFixed(1);
|
|
472
|
-
allLines.push(` ${f.id} ${f.filename} (${sizeKB} KB, ${f.purpose})`);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
allLines.push("");
|
|
476
|
-
} catch (err) {
|
|
477
|
-
allLines.push(`[${label}] Error: ${(err as Error).message}`);
|
|
478
|
-
allLines.push("");
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
if (totalFiles === 0 && accounts.length > 0) {
|
|
482
|
-
allLines.push(`Total: No files across ${accounts.length} account(s).`);
|
|
483
|
-
} else {
|
|
484
|
-
allLines.push(`Total: ${totalFiles} file(s) across ${accounts.length} account(s).`);
|
|
485
161
|
}
|
|
486
|
-
|
|
487
|
-
allLines.push("", "Tip: Use --account <email> to target a specific account.");
|
|
488
|
-
}
|
|
489
|
-
await sendCommandMessage(input.sessionID, allLines.join("\n"));
|
|
162
|
+
await startSlashOAuth(input.sessionID, "reauth", idx, oauthFlowDeps);
|
|
490
163
|
return;
|
|
491
|
-
|
|
164
|
+
}
|
|
492
165
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
await sendCommandMessage(input.sessionID, `▣ Anthropic Files (error)\n\n${errMsg}`);
|
|
166
|
+
// Delegate to focused handlers
|
|
167
|
+
if (primary === "config") {
|
|
168
|
+
await handleConfigCommand(input.sessionID, { sendCommandMessage, config });
|
|
497
169
|
return;
|
|
498
|
-
|
|
499
|
-
const { account, label } = resolved;
|
|
500
|
-
const authHeaders = await getFilesAuth(account);
|
|
170
|
+
}
|
|
501
171
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
if (!filePath) {
|
|
505
|
-
await sendCommandMessage(
|
|
506
|
-
input.sessionID,
|
|
507
|
-
"▣ Anthropic Files\n\nUsage: /anthropic files upload <path> [--account <email>]",
|
|
508
|
-
);
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
const resolvedPath = resolve(filePath);
|
|
512
|
-
if (!existsSync(resolvedPath)) {
|
|
513
|
-
await sendCommandMessage(input.sessionID, `▣ Anthropic Files (error)\n\nFile not found: ${resolvedPath}`);
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
const content = readFileSync(resolvedPath);
|
|
517
|
-
const filename = basename(resolvedPath);
|
|
518
|
-
const blob = new Blob([content]);
|
|
519
|
-
const form = new FormData();
|
|
520
|
-
form.append("file", blob, filename);
|
|
521
|
-
form.append("purpose", "assistants");
|
|
522
|
-
const res = await fetch(`${apiBase}/v1/files`, {
|
|
523
|
-
method: "POST",
|
|
524
|
-
headers: {
|
|
525
|
-
authorization: authHeaders.authorization,
|
|
526
|
-
"anthropic-beta": "oauth-2025-04-20,files-api-2025-04-14",
|
|
527
|
-
},
|
|
528
|
-
body: form,
|
|
529
|
-
});
|
|
530
|
-
if (!res.ok) {
|
|
531
|
-
const errBody = await res.text();
|
|
532
|
-
await sendCommandMessage(
|
|
533
|
-
input.sessionID,
|
|
534
|
-
`▣ Anthropic Files (error) [${label}]\n\nUpload failed (HTTP ${res.status}): ${errBody}`,
|
|
535
|
-
);
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
const file = (await res.json()) as { id: string; filename: string; size?: number };
|
|
539
|
-
const sizeKB = ((file.size || 0) / 1024).toFixed(1);
|
|
540
|
-
capFileAccountMap(fileAccountMap, file.id, account.index);
|
|
541
|
-
await sendCommandMessage(
|
|
542
|
-
input.sessionID,
|
|
543
|
-
`▣ Anthropic Files [${label}]\n\nUploaded: ${file.id}\n Filename: ${file.filename}\n Size: ${sizeKB} KB`,
|
|
544
|
-
);
|
|
172
|
+
if (primary === "set") {
|
|
173
|
+
await handleSetCommand(input.sessionID, args, { sendCommandMessage, config });
|
|
545
174
|
return;
|
|
546
|
-
|
|
175
|
+
}
|
|
547
176
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
if (!fileId) {
|
|
551
|
-
await sendCommandMessage(
|
|
552
|
-
input.sessionID,
|
|
553
|
-
"▣ Anthropic Files\n\nUsage: /anthropic files get <file_id> [--account <email>]",
|
|
554
|
-
);
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
const res = await fetch(`${apiBase}/v1/files/${encodeURIComponent(fileId)}`, { headers: authHeaders });
|
|
558
|
-
if (!res.ok) {
|
|
559
|
-
const errBody = await res.text();
|
|
560
|
-
await sendCommandMessage(
|
|
561
|
-
input.sessionID,
|
|
562
|
-
`▣ Anthropic Files (error) [${label}]\n\nHTTP ${res.status}: ${errBody}`,
|
|
563
|
-
);
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
const file = (await res.json()) as {
|
|
567
|
-
id: string;
|
|
568
|
-
filename: string;
|
|
569
|
-
purpose: string;
|
|
570
|
-
size?: number;
|
|
571
|
-
mime_type?: string;
|
|
572
|
-
created_at?: string;
|
|
573
|
-
};
|
|
574
|
-
capFileAccountMap(fileAccountMap, file.id, account.index);
|
|
575
|
-
const lines = [
|
|
576
|
-
`▣ Anthropic Files [${label}]`,
|
|
577
|
-
"",
|
|
578
|
-
` ID: ${file.id}`,
|
|
579
|
-
` Filename: ${file.filename}`,
|
|
580
|
-
` Purpose: ${file.purpose}`,
|
|
581
|
-
` Size: ${((file.size || 0) / 1024).toFixed(1)} KB`,
|
|
582
|
-
` Type: ${file.mime_type || "unknown"}`,
|
|
583
|
-
` Created: ${file.created_at || "unknown"}`,
|
|
584
|
-
];
|
|
585
|
-
await sendCommandMessage(input.sessionID, lines.join("\n"));
|
|
177
|
+
if (primary === "betas") {
|
|
178
|
+
await handleBetasCommand(input.sessionID, args, { sendCommandMessage, config, initialAccountPinned });
|
|
586
179
|
return;
|
|
587
|
-
|
|
180
|
+
}
|
|
588
181
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
);
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
const res = await fetch(`${apiBase}/v1/files/${encodeURIComponent(fileId)}`, {
|
|
599
|
-
method: "DELETE",
|
|
600
|
-
headers: authHeaders,
|
|
182
|
+
if (primary === "files") {
|
|
183
|
+
await handleFilesCommand(input.sessionID, args, {
|
|
184
|
+
sendCommandMessage,
|
|
185
|
+
accountManager,
|
|
186
|
+
fileAccountMap,
|
|
187
|
+
refreshAccountTokenSingleFlight,
|
|
601
188
|
});
|
|
602
|
-
if (!res.ok) {
|
|
603
|
-
const errBody = await res.text();
|
|
604
|
-
await sendCommandMessage(
|
|
605
|
-
input.sessionID,
|
|
606
|
-
`▣ Anthropic Files (error) [${label}]\n\nHTTP ${res.status}: ${errBody}`,
|
|
607
|
-
);
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
fileAccountMap.delete(fileId);
|
|
611
|
-
await sendCommandMessage(input.sessionID, `▣ Anthropic Files [${label}]\n\nDeleted: ${fileId}`);
|
|
612
189
|
return;
|
|
613
|
-
|
|
190
|
+
}
|
|
614
191
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
if (!fileId) {
|
|
618
|
-
await sendCommandMessage(
|
|
619
|
-
input.sessionID,
|
|
620
|
-
"▣ Anthropic Files\n\nUsage: /anthropic files download <file_id> [output_path] [--account <email>]",
|
|
621
|
-
);
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
const outputPath = filteredArgs.slice(3).join(" ").trim();
|
|
625
|
-
const metaRes = await fetch(`${apiBase}/v1/files/${encodeURIComponent(fileId)}`, { headers: authHeaders });
|
|
626
|
-
if (!metaRes.ok) {
|
|
627
|
-
const errBody = await metaRes.text();
|
|
628
|
-
await sendCommandMessage(
|
|
629
|
-
input.sessionID,
|
|
630
|
-
`▣ Anthropic Files (error) [${label}]\n\nHTTP ${metaRes.status}: ${errBody}`,
|
|
631
|
-
);
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
const meta = (await metaRes.json()) as { filename: string };
|
|
635
|
-
const savePath = outputPath ? resolve(outputPath) : resolve(meta.filename);
|
|
636
|
-
const res = await fetch(`${apiBase}/v1/files/${encodeURIComponent(fileId)}/content`, { headers: authHeaders });
|
|
637
|
-
if (!res.ok) {
|
|
638
|
-
const errBody = await res.text();
|
|
639
|
-
await sendCommandMessage(
|
|
640
|
-
input.sessionID,
|
|
641
|
-
`▣ Anthropic Files (error) [${label}]\n\nDownload failed (HTTP ${res.status}): ${errBody}`,
|
|
642
|
-
);
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
const buffer = Buffer.from(await res.arrayBuffer());
|
|
646
|
-
writeFileSync(savePath, buffer);
|
|
647
|
-
const sizeKB = (buffer.length / 1024).toFixed(1);
|
|
192
|
+
// Interactive CLI command is not compatible with slash flow.
|
|
193
|
+
if (primary === "manage" || primary === "mg") {
|
|
648
194
|
await sendCommandMessage(
|
|
649
|
-
|
|
650
|
-
|
|
195
|
+
input.sessionID,
|
|
196
|
+
"▣ Anthropic\n\n`manage` is interactive-only. Use granular slash commands (switch/enable/disable/remove/reset) or run `opencode-anthropic-auth manage` in a terminal.",
|
|
651
197
|
);
|
|
652
198
|
return;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
const helpLines = [
|
|
656
|
-
"▣ Anthropic Files",
|
|
657
|
-
"",
|
|
658
|
-
"Usage: /anthropic files <action> [--account <email|index>]",
|
|
659
|
-
"",
|
|
660
|
-
"Actions:",
|
|
661
|
-
" list List uploaded files (all accounts if no --account)",
|
|
662
|
-
" upload <path> Upload a file (max 350MB)",
|
|
663
|
-
" get <file_id> Get file metadata",
|
|
664
|
-
" delete <file_id> Delete a file",
|
|
665
|
-
" download <file_id> [path] Download file content",
|
|
666
|
-
"",
|
|
667
|
-
"Options:",
|
|
668
|
-
" --account <email|index> Target a specific account (1-based index)",
|
|
669
|
-
"",
|
|
670
|
-
"Supported formats: PDF, DOCX, TXT, CSV, Excel, Markdown, images",
|
|
671
|
-
"Files can be referenced by file_id in Messages API requests.",
|
|
672
|
-
"",
|
|
673
|
-
"When using round-robin, file_ids are automatically pinned to the",
|
|
674
|
-
"account that owns them for Messages API requests.",
|
|
675
|
-
];
|
|
676
|
-
await sendCommandMessage(input.sessionID, helpLines.join("\n"));
|
|
677
|
-
return;
|
|
678
|
-
} catch (err) {
|
|
679
|
-
await sendCommandMessage(input.sessionID, `▣ Anthropic Files (error)\n\n${(err as Error).message}`);
|
|
680
|
-
return;
|
|
681
199
|
}
|
|
682
|
-
}
|
|
683
200
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
input.sessionID,
|
|
688
|
-
"▣ Anthropic\n\n`manage` is interactive-only. Use granular slash commands (switch/enable/disable/remove/reset) or run `opencode-anthropic-auth manage` in a terminal.",
|
|
689
|
-
);
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
201
|
+
// Route remaining commands through the CLI command surface.
|
|
202
|
+
const cliArgs = [...args];
|
|
203
|
+
if (cliArgs.length === 0) cliArgs.push("list");
|
|
692
204
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
!cliArgs.includes("--force")
|
|
701
|
-
) {
|
|
702
|
-
cliArgs.push("--force");
|
|
703
|
-
}
|
|
205
|
+
// Avoid readline prompts in slash mode.
|
|
206
|
+
if (
|
|
207
|
+
(primary === "remove" || primary === "rm" || primary === "logout" || primary === "lo") &&
|
|
208
|
+
!cliArgs.includes("--force")
|
|
209
|
+
) {
|
|
210
|
+
cliArgs.push("--force");
|
|
211
|
+
}
|
|
704
212
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
213
|
+
const result = await runCliCommand(cliArgs);
|
|
214
|
+
const heading = result.code === 0 ? "▣ Anthropic" : "▣ Anthropic (error)";
|
|
215
|
+
const body = result.stdout || result.stderr || "No output.";
|
|
216
|
+
await sendCommandMessage(input.sessionID, [heading, "", body].join("\n"));
|
|
217
|
+
await reloadAccountManagerFromDisk();
|
|
710
218
|
}
|