@dmrlzz/davecode 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -2
  3. package/bin/davecode.js +2 -0
  4. package/dist/ChatApp-FO5SABXB.js +893 -0
  5. package/dist/ChatApp-FO5SABXB.js.map +1 -0
  6. package/dist/RunnerView-DLVR7C22.js +18 -0
  7. package/dist/RunnerView-DLVR7C22.js.map +1 -0
  8. package/dist/accounts-MTMGYKEP.js +724 -0
  9. package/dist/accounts-MTMGYKEP.js.map +1 -0
  10. package/dist/budget-RBHCEDPP.js +115 -0
  11. package/dist/budget-RBHCEDPP.js.map +1 -0
  12. package/dist/chat-ZDXX3N6X.js +95 -0
  13. package/dist/chat-ZDXX3N6X.js.map +1 -0
  14. package/dist/chunk-23KTE7IK.js +114 -0
  15. package/dist/chunk-23KTE7IK.js.map +1 -0
  16. package/dist/chunk-7XVXEO7U.js +23 -0
  17. package/dist/chunk-7XVXEO7U.js.map +1 -0
  18. package/dist/chunk-BOLFK3BH.js +20 -0
  19. package/dist/chunk-BOLFK3BH.js.map +1 -0
  20. package/dist/chunk-C7FDRA3U.js +9087 -0
  21. package/dist/chunk-C7FDRA3U.js.map +1 -0
  22. package/dist/chunk-DYQ56FBT.js +47 -0
  23. package/dist/chunk-DYQ56FBT.js.map +1 -0
  24. package/dist/chunk-FRFSJMN4.js +79 -0
  25. package/dist/chunk-FRFSJMN4.js.map +1 -0
  26. package/dist/chunk-IC3TDONV.js +12 -0
  27. package/dist/chunk-IC3TDONV.js.map +1 -0
  28. package/dist/chunk-IVZHKUCD.js +180 -0
  29. package/dist/chunk-IVZHKUCD.js.map +1 -0
  30. package/dist/chunk-MSLLH5XP.js +213 -0
  31. package/dist/chunk-MSLLH5XP.js.map +1 -0
  32. package/dist/chunk-OXRMGR72.js +20 -0
  33. package/dist/chunk-OXRMGR72.js.map +1 -0
  34. package/dist/chunk-OYEYNDOO.js +200 -0
  35. package/dist/chunk-OYEYNDOO.js.map +1 -0
  36. package/dist/chunk-PIIJ3RSI.js +1610 -0
  37. package/dist/chunk-PIIJ3RSI.js.map +1 -0
  38. package/dist/chunk-SF5ZEAP4.js +105 -0
  39. package/dist/chunk-SF5ZEAP4.js.map +1 -0
  40. package/dist/chunk-VVCXGQMY.js +107 -0
  41. package/dist/chunk-VVCXGQMY.js.map +1 -0
  42. package/dist/config-FRMY5OHG.js +158 -0
  43. package/dist/config-FRMY5OHG.js.map +1 -0
  44. package/dist/dashboard/assets/Accounts-BR3ZfVk4.js +1 -0
  45. package/dist/dashboard/assets/Badge-B-qzNRrZ.js +55 -0
  46. package/dist/dashboard/assets/Callout-B9g_wPDi.js +1 -0
  47. package/dist/dashboard/assets/Field-BfabPEcY.js +1 -0
  48. package/dist/dashboard/assets/Goals-BsV-6m94.js +1 -0
  49. package/dist/dashboard/assets/Library-CEA9P1m-.js +1 -0
  50. package/dist/dashboard/assets/Markdown-ClRsddnB.js +69 -0
  51. package/dist/dashboard/assets/Panel-B9tSgX6l.js +1 -0
  52. package/dist/dashboard/assets/RoutesPage-Dh-BJ3ir.js +4 -0
  53. package/dist/dashboard/assets/Runner-C_e9l5l2.js +1 -0
  54. package/dist/dashboard/assets/Settings-C6Yc1Gjg.js +1 -0
  55. package/dist/dashboard/assets/Switch-CUpOJjUT.js +1 -0
  56. package/dist/dashboard/assets/Tasks-CTku0rR3.js +2 -0
  57. package/dist/dashboard/assets/Traffic-CE6wZSMr.js +1 -0
  58. package/dist/dashboard/assets/Workspace-CxFxvkA6.js +1 -0
  59. package/dist/dashboard/assets/arrow-up-DL3CuFOO.js +1 -0
  60. package/dist/dashboard/assets/forms-D6UIYgC5.js +1 -0
  61. package/dist/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  62. package/dist/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  63. package/dist/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  64. package/dist/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  65. package/dist/dashboard/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  66. package/dist/dashboard/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  67. package/dist/dashboard/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  68. package/dist/dashboard/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  69. package/dist/dashboard/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  70. package/dist/dashboard/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  71. package/dist/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  72. package/dist/dashboard/assets/index-1iH716Xy.css +2 -0
  73. package/dist/dashboard/assets/index-BMWr9EKo.js +104 -0
  74. package/dist/dashboard/assets/task-errors-CE4oEr8H.js +1 -0
  75. package/dist/dashboard/assets/trash-CWqeZ7eg.js +1 -0
  76. package/dist/dashboard/favicon.svg +7 -0
  77. package/dist/dashboard/github.svg +1 -0
  78. package/dist/dashboard/index.html +31 -0
  79. package/dist/dist-ZPXEVBEC.js +357 -0
  80. package/dist/dist-ZPXEVBEC.js.map +1 -0
  81. package/dist/doctor-7RUD4D3A.js +279 -0
  82. package/dist/doctor-7RUD4D3A.js.map +1 -0
  83. package/dist/goal-JFRNPBSD.js +302 -0
  84. package/dist/goal-JFRNPBSD.js.map +1 -0
  85. package/dist/index.js +381 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/init-NFYNO3A3.js +194 -0
  88. package/dist/init-NFYNO3A3.js.map +1 -0
  89. package/dist/prompts-L37J72YW.js +215 -0
  90. package/dist/prompts-L37J72YW.js.map +1 -0
  91. package/dist/run-ARUPXFFB.js +376 -0
  92. package/dist/run-ARUPXFFB.js.map +1 -0
  93. package/dist/runner-state-QFU3LJVJ.js +17 -0
  94. package/dist/runner-state-QFU3LJVJ.js.map +1 -0
  95. package/dist/start-CFPOVIAR.js +285 -0
  96. package/dist/start-CFPOVIAR.js.map +1 -0
  97. package/dist/status-E4EACVE2.js +25 -0
  98. package/dist/status-E4EACVE2.js.map +1 -0
  99. package/dist/tasks-ZIK6BVEX.js +184 -0
  100. package/dist/tasks-ZIK6BVEX.js.map +1 -0
  101. package/package.json +68 -4
@@ -0,0 +1,724 @@
1
+ import {
2
+ PromptCancelledError
3
+ } from "./chunk-IC3TDONV.js";
4
+ import {
5
+ openAccounts,
6
+ resolveAccount
7
+ } from "./chunk-IVZHKUCD.js";
8
+ import {
9
+ table,
10
+ truncate
11
+ } from "./chunk-SF5ZEAP4.js";
12
+ import {
13
+ printJson
14
+ } from "./chunk-DYQ56FBT.js";
15
+ import "./chunk-VVCXGQMY.js";
16
+ import {
17
+ detectProjectRoot,
18
+ spawnInteractive,
19
+ which
20
+ } from "./chunk-PIIJ3RSI.js";
21
+ import {
22
+ CliError,
23
+ EXIT
24
+ } from "./chunk-7XVXEO7U.js";
25
+ import {
26
+ CLI_LOGINS,
27
+ EXPERIMENTAL_PROVIDER_KINDS,
28
+ SUBSCRIPTION_PROVIDER_KINDS,
29
+ SandboxManager,
30
+ cliBinaryFor,
31
+ globalPaths,
32
+ loginEnv
33
+ } from "./chunk-C7FDRA3U.js";
34
+
35
+ // src/commands/accounts.ts
36
+ import { createInterface } from "readline";
37
+
38
+ // src/lib/account-flow.ts
39
+ var PROVIDERS = [
40
+ { kind: "anthropic", name: "Anthropic API", auth: "api-key", defaultLabel: "Anthropic" },
41
+ { kind: "openai", name: "OpenAI API", auth: "api-key", defaultLabel: "OpenAI" },
42
+ { kind: "gemini", name: "Google Gemini API", auth: "api-key", defaultLabel: "Gemini" },
43
+ {
44
+ kind: "openai-compatible",
45
+ name: "OpenRouter or another OpenAI-compatible provider",
46
+ auth: "optional-key",
47
+ defaultLabel: "OpenRouter"
48
+ },
49
+ {
50
+ kind: "claude-cli",
51
+ name: "Claude Code CLI (your Claude login)",
52
+ auth: "cli-login",
53
+ defaultLabel: "Claude Code",
54
+ binary: "claude"
55
+ },
56
+ {
57
+ kind: "codex-cli",
58
+ name: "Codex CLI (your ChatGPT/OpenAI login)",
59
+ auth: "cli-login",
60
+ defaultLabel: "Codex",
61
+ binary: "codex"
62
+ },
63
+ {
64
+ kind: "gemini-cli",
65
+ name: "Gemini CLI (your Google login)",
66
+ auth: "cli-login",
67
+ defaultLabel: "Gemini CLI",
68
+ binary: "gemini"
69
+ },
70
+ {
71
+ kind: "gemini-web",
72
+ name: "Gemini web session (experimental)",
73
+ auth: "browser",
74
+ defaultLabel: "Gemini web"
75
+ }
76
+ ];
77
+ function providerInfo(kind) {
78
+ return PROVIDERS.find((p) => p.kind === kind);
79
+ }
80
+ var DEFAULT_COMPATIBLE_URL = "https://openrouter.ai/api/v1";
81
+ var GEMINI_WEB_TOS = "gemini-web automates the consumer Gemini web app through a browser profile. This likely violates Google\u2019s Terms of Service and your account can be rate-limited or suspended.";
82
+ var ROTATION_TOS = "Rotating several subscription logins of the same provider to get around per-account limits may violate the provider\u2019s consumer terms; accounts can be rate-limited or suspended.";
83
+ function accountPolicy(provider, config, existing) {
84
+ const warnings = [];
85
+ if (EXPERIMENTAL_PROVIDER_KINDS.includes(provider)) {
86
+ if (!config.experimental.geminiWeb) {
87
+ return {
88
+ warnings,
89
+ blocked: {
90
+ message: `${provider} is experimental and disabled. ${GEMINI_WEB_TOS}`,
91
+ hint: 'If you accept the risk, set "experimental": { "geminiWeb": true } in ~/.davecode/config.json (or DAVECODE_EXPERIMENTAL_GEMINI_WEB=1).'
92
+ }
93
+ };
94
+ }
95
+ warnings.push(GEMINI_WEB_TOS);
96
+ }
97
+ const sameProvider = existing.filter((a) => a.provider === provider).length;
98
+ if (SUBSCRIPTION_PROVIDER_KINDS.includes(provider) && sameProvider > 0) {
99
+ if (!config.experimental.multiAccountRotation) {
100
+ return {
101
+ warnings,
102
+ blocked: {
103
+ message: `You already have a ${provider} account. ${ROTATION_TOS}`,
104
+ hint: 'If you accept the risk, set "experimental": { "multiAccountRotation": true } in ~/.davecode/config.json (or DAVECODE_EXPERIMENTAL_MULTI_ACCOUNT_ROTATION=1).'
105
+ }
106
+ };
107
+ }
108
+ warnings.push(ROTATION_TOS);
109
+ }
110
+ return { warnings };
111
+ }
112
+ var LIMIT_KEYS = [
113
+ "tpm",
114
+ "rpm",
115
+ "tokens5h",
116
+ "requests5h",
117
+ "tokensDaily",
118
+ "requestsDaily"
119
+ ];
120
+ function parseLimits(pairs = []) {
121
+ const limits = {};
122
+ for (const pair of pairs) {
123
+ const [rawKey, rawValue] = pair.split("=", 2);
124
+ const key = LIMIT_KEYS.find((k) => k.toLowerCase() === rawKey?.trim().toLowerCase());
125
+ const value = Number(rawValue);
126
+ if (!key) {
127
+ throw new CliError(`Unknown limit ${JSON.stringify(rawKey)}`, {
128
+ hint: `Use one of: ${LIMIT_KEYS.join(", ")} (e.g. --limit tokens5h=500000)`
129
+ });
130
+ }
131
+ if (!Number.isInteger(value) || value <= 0) {
132
+ throw new CliError(
133
+ `Limit ${key} must be a positive integer, got ${JSON.stringify(rawValue)}`
134
+ );
135
+ }
136
+ limits[key] = value;
137
+ }
138
+ return limits;
139
+ }
140
+ function validateUrl(value) {
141
+ try {
142
+ const url = new URL(value);
143
+ return url.protocol === "http:" || url.protocol === "https:" ? void 0 : "must be an http(s) URL";
144
+ } catch {
145
+ return "must be a valid URL, e.g. http://localhost:11434/v1";
146
+ }
147
+ }
148
+ function requireProvider(value) {
149
+ if (!value) {
150
+ throw new CliError("Missing --provider", {
151
+ hint: `One of: ${PROVIDERS.map((p) => p.kind).join(", ")}`
152
+ });
153
+ }
154
+ const info = providerInfo(value);
155
+ if (!info) {
156
+ throw new CliError(`Unknown provider ${JSON.stringify(value)}`, {
157
+ hint: `One of: ${PROVIDERS.map((p) => p.kind).join(", ")}`
158
+ });
159
+ }
160
+ return info;
161
+ }
162
+ async function secretFromFlags(flags, sources) {
163
+ const chosen = [flags.secret !== void 0, !!flags.secretEnv, !!flags.secretStdin].filter(
164
+ Boolean
165
+ );
166
+ if (chosen.length > 1) {
167
+ throw new CliError("Use only one of --secret, --secret-env and --secret-stdin");
168
+ }
169
+ if (flags.secretStdin) {
170
+ const value2 = (await sources.readStdin()).trim();
171
+ if (!value2) throw new CliError("No secret received on stdin");
172
+ return value2;
173
+ }
174
+ if (flags.secretEnv) {
175
+ const value2 = sources.env[flags.secretEnv]?.trim();
176
+ if (!value2) throw new CliError(`Environment variable ${flags.secretEnv} is empty or unset`);
177
+ return value2;
178
+ }
179
+ const value = flags.secret?.trim();
180
+ return value ? value : void 0;
181
+ }
182
+ function providerConfig(info, values) {
183
+ const config = {};
184
+ if (values.baseUrl) config.baseUrl = values.baseUrl.replace(/\/+$/, "");
185
+ if (values.models && values.models.length > 0) config.models = [...new Set(values.models)];
186
+ if (values.defaultModel) config.defaultModel = values.defaultModel;
187
+ if (values.binaryPath && info.auth === "cli-login") config.binaryPath = values.binaryPath;
188
+ return config;
189
+ }
190
+ async function accountFromFlags(flags, sources) {
191
+ const info = requireProvider(flags.provider);
192
+ const secret = await secretFromFlags(flags, sources);
193
+ if (info.auth === "api-key" && !secret) {
194
+ throw new CliError(`${info.name} needs an API key`, {
195
+ hint: "Pass it with --secret-env <VAR> or pipe it with --secret-stdin."
196
+ });
197
+ }
198
+ if (secret && (info.auth === "cli-login" || info.auth === "browser")) {
199
+ throw new CliError(`${info.kind} accounts do not take a secret`, {
200
+ hint: info.auth === "cli-login" ? "Log in with `davecode accounts login <id>` after adding the account." : "The browser profile holds the session."
201
+ });
202
+ }
203
+ if (info.kind === "openai-compatible") {
204
+ if (!flags.baseUrl) {
205
+ throw new CliError("openai-compatible accounts need --base-url", {
206
+ hint: `e.g. --base-url ${DEFAULT_COMPATIBLE_URL} for OpenRouter`
207
+ });
208
+ }
209
+ }
210
+ if (flags.baseUrl) {
211
+ const problem = validateUrl(flags.baseUrl);
212
+ if (problem) throw new CliError(`--base-url ${problem}`);
213
+ }
214
+ const create = {
215
+ provider: info.kind,
216
+ label: flags.label?.trim() || info.defaultLabel,
217
+ config: providerConfig(info, {
218
+ ...flags.baseUrl ? { baseUrl: flags.baseUrl } : {},
219
+ ...flags.model ? { models: flags.model } : {},
220
+ ...flags.defaultModel ? { defaultModel: flags.defaultModel } : {},
221
+ ...flags.binaryPath ? { binaryPath: flags.binaryPath } : {}
222
+ })
223
+ };
224
+ if (flags.priority !== void 0) create.priority = flags.priority;
225
+ if (flags.weight !== void 0) create.weight = flags.weight;
226
+ const limits = parseLimits(flags.limit);
227
+ if (Object.keys(limits).length > 0) create.limits = limits;
228
+ if (flags.disabled) create.enabled = false;
229
+ if (secret) create.secret = secret;
230
+ return create;
231
+ }
232
+ function uniqueLabel(base, existing) {
233
+ const taken = new Set(existing.map((a) => a.label.toLowerCase()));
234
+ if (!taken.has(base.toLowerCase())) return base;
235
+ for (let n = 2; ; n++) {
236
+ const candidate = `${base} ${n}`;
237
+ if (!taken.has(candidate.toLowerCase())) return candidate;
238
+ }
239
+ }
240
+ async function accountInteractively(flags, deps) {
241
+ const { prompter, say } = deps;
242
+ const warn = deps.warn ?? ((t) => t);
243
+ const kind = flags.provider ? requireProvider(flags.provider).kind : await prompter.select(
244
+ "Provider",
245
+ PROVIDERS.map((p) => ({ value: p.kind, label: p.name, hint: authHint(p.auth) })),
246
+ "anthropic"
247
+ );
248
+ const info = requireProvider(kind);
249
+ const policy = accountPolicy(info.kind, deps.config, deps.existing);
250
+ if (policy.blocked) throw new CliError(policy.blocked.message, { hint: policy.blocked.hint });
251
+ for (const warning of policy.warnings) {
252
+ say(warn(`! ${warning}`));
253
+ if (!await prompter.confirm("I understand the risk. Continue?", false)) return void 0;
254
+ }
255
+ const label = flags.label?.trim() || await prompter.text("Label", {
256
+ initial: uniqueLabel(info.defaultLabel, deps.existing),
257
+ validate: (v) => v.trim() ? void 0 : "a label is required"
258
+ });
259
+ const priority = flags.priority ?? Number(
260
+ await prompter.text("Priority (lower is tried first)", {
261
+ initial: "100",
262
+ validate: (v) => /^\d+$/.test(v.trim()) ? void 0 : "enter a non-negative integer"
263
+ })
264
+ );
265
+ let baseUrl = flags.baseUrl;
266
+ let models = flags.model;
267
+ if (info.kind === "openai-compatible") {
268
+ baseUrl ??= await prompter.text("Base URL", {
269
+ initial: DEFAULT_COMPATIBLE_URL,
270
+ validate: validateUrl
271
+ });
272
+ if (!models) {
273
+ const answer = await prompter.text("Models (comma-separated, empty to discover)", {
274
+ initial: ""
275
+ });
276
+ models = answer.split(",").map((m) => m.trim()).filter(Boolean);
277
+ }
278
+ }
279
+ let secret;
280
+ if (info.auth === "api-key" || info.auth === "optional-key") {
281
+ const optional = info.auth === "optional-key";
282
+ const answer = await prompter.secret(
283
+ optional ? "API key (optional, input hidden)" : `${info.name} key (input hidden)`,
284
+ { optional }
285
+ );
286
+ secret = answer.trim() || void 0;
287
+ if (!secret && !optional) throw new CliError(`${info.name} needs an API key`);
288
+ }
289
+ const create = {
290
+ provider: info.kind,
291
+ label: label.trim(),
292
+ priority,
293
+ config: providerConfig(info, {
294
+ ...baseUrl ? { baseUrl } : {},
295
+ ...models ? { models } : {},
296
+ ...flags.defaultModel ? { defaultModel: flags.defaultModel } : {},
297
+ ...flags.binaryPath ? { binaryPath: flags.binaryPath } : {}
298
+ })
299
+ };
300
+ if (flags.weight !== void 0) create.weight = flags.weight;
301
+ const limits = parseLimits(flags.limit);
302
+ if (Object.keys(limits).length > 0) create.limits = limits;
303
+ if (flags.disabled) create.enabled = false;
304
+ if (secret) create.secret = secret;
305
+ say("");
306
+ for (const line of describeCreate(create)) say(` ${line}`);
307
+ say("");
308
+ if (!await prompter.confirm("Add this account?", true)) return void 0;
309
+ return create;
310
+ }
311
+ function authHint(auth) {
312
+ switch (auth) {
313
+ case "api-key":
314
+ return "API key";
315
+ case "optional-key":
316
+ return "base URL, optional key";
317
+ case "cli-login":
318
+ return "local CLI login, isolated per account";
319
+ default:
320
+ return "experimental, ToS risk";
321
+ }
322
+ }
323
+ function describeCreate(create) {
324
+ const lines = [
325
+ `provider ${create.provider}`,
326
+ `label ${create.label}`,
327
+ `priority ${create.priority ?? 100}`
328
+ ];
329
+ const config = create.config ?? {};
330
+ if (typeof config.baseUrl === "string") lines.push(`base URL ${config.baseUrl}`);
331
+ if (Array.isArray(config.models) && config.models.length > 0) {
332
+ lines.push(`models ${config.models.join(", ")}`);
333
+ }
334
+ if (create.limits && Object.keys(create.limits).length > 0) {
335
+ lines.push(
336
+ `limits ${Object.entries(create.limits).map(([k, v]) => `${k}=${v}`).join(" ")}`
337
+ );
338
+ }
339
+ if (create.secret !== void 0) lines.push("secret set (stored encrypted)");
340
+ return lines;
341
+ }
342
+
343
+ // src/commands/accounts.ts
344
+ async function backendFor(ctx, flags, deps) {
345
+ if (deps.backend) return deps.backend;
346
+ const projectRoot = await detectProjectRoot(ctx.cwd, ctx.home);
347
+ return openAccounts({
348
+ home: ctx.home,
349
+ env: ctx.env,
350
+ ...projectRoot ? { projectRoot } : {},
351
+ ...flags.local ? { local: true } : {}
352
+ });
353
+ }
354
+ async function withBackend(ctx, flags, deps, fn) {
355
+ const backend = await backendFor(ctx, flags, deps);
356
+ try {
357
+ return await fn(backend);
358
+ } finally {
359
+ if (!deps.backend) backend.close();
360
+ }
361
+ }
362
+ async function prompterFor(ctx, deps) {
363
+ if (deps.prompter) return deps.prompter;
364
+ const { createInkPrompter } = await import("./prompts-L37J72YW.js");
365
+ return createInkPrompter({ stdout: ctx.io.stdout, stdin: ctx.io.stdin, theme: ctx.theme });
366
+ }
367
+ function readAllStdin() {
368
+ return new Promise((resolve, reject) => {
369
+ let data = "";
370
+ process.stdin.setEncoding("utf8");
371
+ process.stdin.on("data", (chunk) => {
372
+ data += chunk;
373
+ });
374
+ process.stdin.on("end", () => resolve(data));
375
+ process.stdin.on("error", reject);
376
+ });
377
+ }
378
+ function statusText(theme, account) {
379
+ switch (account.status) {
380
+ case "active":
381
+ return theme.ok("active");
382
+ case "cooldown":
383
+ return theme.warn("cooldown");
384
+ case "error":
385
+ return theme.error("error");
386
+ default:
387
+ return theme.dim("disabled");
388
+ }
389
+ }
390
+ function detail(account) {
391
+ const config = account.config;
392
+ const parts = [];
393
+ if (typeof config.baseUrl === "string") parts.push(config.baseUrl);
394
+ if (Array.isArray(config.models) && config.models.length > 0) {
395
+ parts.push(config.models.slice(0, 3).join(", ") + (config.models.length > 3 ? ", \u2026" : ""));
396
+ }
397
+ if (typeof config.binaryPath === "string") parts.push(config.binaryPath);
398
+ return parts.join(" \xB7 ");
399
+ }
400
+ function renderAccountList(theme, accounts, columns) {
401
+ const cols = [
402
+ { header: "ID" },
403
+ { header: "LABEL" },
404
+ { header: "PROVIDER" },
405
+ { header: "STATUS" },
406
+ { header: "PRI", align: "right", drop: 2 },
407
+ { header: "WEIGHT", align: "right", drop: 3 },
408
+ { header: "DETAILS", drop: 4 }
409
+ ];
410
+ const rows = accounts.map((a) => [
411
+ theme.accent(a.id),
412
+ truncate(a.label, 28),
413
+ a.provider,
414
+ statusText(theme, a),
415
+ String(a.priority),
416
+ String(a.weight),
417
+ theme.dim(truncate(detail(a), 40))
418
+ ]);
419
+ return table(theme, cols, rows, columns);
420
+ }
421
+ async function accountsList(ctx, flags = {}, deps = {}) {
422
+ return withBackend(ctx, flags, deps, async (backend) => {
423
+ const accounts = await backend.list();
424
+ if (ctx.json) {
425
+ printJson(ctx, { source: backend.kind, accounts });
426
+ return EXIT.ok;
427
+ }
428
+ if (accounts.length === 0) {
429
+ ctx.out(ctx.theme.dim("No accounts yet."));
430
+ ctx.out(`Add one with ${ctx.theme.accent("davecode accounts add")}.`);
431
+ return EXIT.ok;
432
+ }
433
+ for (const line of renderAccountList(ctx.theme, accounts, ctx.columns)) ctx.out(line);
434
+ ctx.out();
435
+ ctx.out(
436
+ ctx.theme.dim(backend.kind === "gateway" ? "source: running gateway" : "source: local state")
437
+ );
438
+ return EXIT.ok;
439
+ });
440
+ }
441
+ async function accountsAdd(ctx, flags, deps = {}) {
442
+ return withBackend(ctx, flags, deps, async (backend) => {
443
+ const existing = await backend.list();
444
+ const interactive = ctx.interactive && !flags.yes && !flags.secretStdin;
445
+ const { theme, errTheme } = ctx;
446
+ let create;
447
+ if (interactive) {
448
+ try {
449
+ create = await accountInteractively(flags, {
450
+ prompter: await prompterFor(ctx, deps),
451
+ config: backend.config,
452
+ existing,
453
+ say: (line) => ctx.out(line),
454
+ warn: theme.warn
455
+ });
456
+ } catch (err) {
457
+ if (err instanceof PromptCancelledError) {
458
+ ctx.err(errTheme.dim("Cancelled."));
459
+ return EXIT.failure;
460
+ }
461
+ throw err;
462
+ }
463
+ if (!create) {
464
+ ctx.err(errTheme.dim("Nothing added."));
465
+ return EXIT.failure;
466
+ }
467
+ } else {
468
+ create = await accountFromFlags(flags, {
469
+ env: ctx.env,
470
+ readStdin: deps.readStdin ?? readAllStdin
471
+ });
472
+ const policy = accountPolicy(create.provider, backend.config, existing);
473
+ if (policy.blocked) {
474
+ throw new CliError(policy.blocked.message, { hint: policy.blocked.hint });
475
+ }
476
+ for (const warning of policy.warnings) {
477
+ ctx.err(errTheme.warn(`${errTheme.glyph.warn} ${warning}`));
478
+ }
479
+ if (flags.secret !== void 0) {
480
+ ctx.err(
481
+ errTheme.dim(
482
+ "Note: --secret is visible in shell history and process lists; prefer --secret-env or --secret-stdin."
483
+ )
484
+ );
485
+ }
486
+ }
487
+ const account = await backend.create(create);
488
+ if (ctx.json) {
489
+ printJson(ctx, { account });
490
+ return EXIT.ok;
491
+ }
492
+ ctx.out(
493
+ `${theme.ok(theme.glyph.ok)} Added ${theme.bold(account.label)} ${theme.dim(`(${account.id}, ${account.provider})`)}`
494
+ );
495
+ if (!interactive) {
496
+ for (const line of describeCreate(create)) ctx.out(theme.dim(` ${line}`));
497
+ }
498
+ const info = providerInfo(account.provider);
499
+ if (info?.auth === "cli-login") {
500
+ const loginNow = interactive && flags.login !== false && await (await prompterFor(ctx, deps)).confirm(`Log in to ${info.binary} for this account now?`, true);
501
+ if (loginNow) return accountsLogin(ctx, account.id, [], flags, { ...deps, backend });
502
+ ctx.out(
503
+ theme.dim(`Next: log in with ${theme.accent(`davecode accounts login ${account.id}`)}`)
504
+ );
505
+ }
506
+ return EXIT.ok;
507
+ });
508
+ }
509
+ async function accountsRemove(ctx, ref, flags, deps = {}) {
510
+ return withBackend(ctx, flags, deps, async (backend) => {
511
+ const account = await resolveAccount(backend, ref);
512
+ if (!flags.yes) {
513
+ if (!ctx.interactive) {
514
+ throw new CliError(`Refusing to remove ${account.id} without confirmation`, {
515
+ hint: "Pass --yes to remove it non-interactively."
516
+ });
517
+ }
518
+ const ok = await (await prompterFor(ctx, deps)).confirm(`Remove ${account.label} (${account.id}) and its sandbox?`, false).catch((err) => {
519
+ if (err instanceof PromptCancelledError) return false;
520
+ throw err;
521
+ });
522
+ if (!ok) {
523
+ ctx.err(ctx.errTheme.dim("Nothing removed."));
524
+ return EXIT.failure;
525
+ }
526
+ }
527
+ await backend.remove(account.id);
528
+ if (ctx.json) printJson(ctx, { removed: account.id });
529
+ else ctx.out(`${ctx.theme.ok(ctx.theme.glyph.ok)} Removed ${account.label} (${account.id})`);
530
+ return EXIT.ok;
531
+ });
532
+ }
533
+ async function accountsSetEnabled(ctx, ref, enabled, flags = {}, deps = {}) {
534
+ return withBackend(ctx, flags, deps, async (backend) => {
535
+ const account = await resolveAccount(backend, ref);
536
+ const updated = await backend.update(account.id, { enabled });
537
+ if (ctx.json) printJson(ctx, { account: updated });
538
+ else {
539
+ ctx.out(
540
+ `${ctx.theme.ok(ctx.theme.glyph.ok)} ${updated.label} is now ${enabled ? "enabled" : "disabled"} ${ctx.theme.dim(`(${updated.status})`)}`
541
+ );
542
+ }
543
+ return EXIT.ok;
544
+ });
545
+ }
546
+ function describeSubscription(subscription) {
547
+ if (!subscription) return "no plan recorded";
548
+ const parts = [];
549
+ if (subscription.plan) parts.push(subscription.plan);
550
+ if (subscription.price) {
551
+ const { amount, currency, period } = subscription.price;
552
+ parts.push(`${amount} ${currency}/${period}`);
553
+ }
554
+ if (subscription.paidAt) parts.push(`paid ${subscription.paidAt}`);
555
+ if (subscription.renewsAt) parts.push(`renews ${subscription.renewsAt}`);
556
+ if (subscription.notes) parts.push(subscription.notes);
557
+ return parts.length > 0 ? parts.join(" \xB7 ") : "no plan recorded";
558
+ }
559
+ function subscriptionFromFlags(existing, flags) {
560
+ const next = { ...existing };
561
+ if (flags.plan !== void 0) next.plan = flags.plan;
562
+ if (flags.paid !== void 0) next.paidAt = flags.paid;
563
+ if (flags.renews !== void 0) next.renewsAt = flags.renews;
564
+ if (flags.notes !== void 0) next.notes = flags.notes;
565
+ if (flags.price !== void 0 || flags.currency !== void 0 || flags.period !== void 0) {
566
+ const amount = flags.price !== void 0 ? Number(flags.price) : existing?.price?.amount;
567
+ if (amount === void 0 || !Number.isFinite(amount)) {
568
+ throw new CliError("--price needs a number, e.g. --price 20", {
569
+ hint: "Set the amount with --price, and optionally --currency USD --period month."
570
+ });
571
+ }
572
+ const period = flags.period ?? existing?.price?.period ?? "month";
573
+ if (period !== "month" && period !== "year") {
574
+ throw new CliError('--period must be "month" or "year"');
575
+ }
576
+ next.price = { amount, currency: flags.currency ?? existing?.price?.currency ?? "USD", period };
577
+ }
578
+ return next;
579
+ }
580
+ async function accountsPlan(ctx, ref, flags = {}, deps = {}) {
581
+ return withBackend(ctx, flags, deps, async (backend) => {
582
+ const account = await resolveAccount(backend, ref);
583
+ const { theme } = ctx;
584
+ const changes = ["plan", "paid", "renews", "price", "currency", "period", "notes"];
585
+ const editing = flags.clear || changes.some((key) => flags[key] !== void 0);
586
+ if (!editing) {
587
+ if (ctx.json)
588
+ printJson(ctx, { account: account.id, subscription: account.subscription ?? null });
589
+ else ctx.out(`${theme.bold(account.label)}: ${describeSubscription(account.subscription)}`);
590
+ return EXIT.ok;
591
+ }
592
+ const subscription = flags.clear ? null : subscriptionFromFlags(account.subscription, flags);
593
+ const updated = await backend.update(account.id, { subscription });
594
+ if (ctx.json) printJson(ctx, { account: updated });
595
+ else {
596
+ ctx.out(
597
+ `${theme.ok(theme.glyph.ok)} ${updated.label}: ${describeSubscription(updated.subscription)}`
598
+ );
599
+ }
600
+ return EXIT.ok;
601
+ });
602
+ }
603
+ function loginArgs(provider) {
604
+ return CLI_LOGINS[provider]?.loginArgs ?? [];
605
+ }
606
+ function waitForEnter() {
607
+ return new Promise((resolve) => {
608
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
609
+ rl.question("Press Enter to close this window.", () => {
610
+ rl.close();
611
+ resolve();
612
+ });
613
+ rl.once("close", resolve);
614
+ });
615
+ }
616
+ function describeConnection(theme, connection) {
617
+ const label = {
618
+ connected: theme.ok(`${theme.glyph.ok} connected`),
619
+ not_connected: theme.warn(`${theme.glyph.warn} not connected`),
620
+ unknown: theme.dim("? unknown")
621
+ };
622
+ return `${label[connection.status]}${connection.detail ? theme.dim(` \u2014 ${connection.detail}`) : ""}`;
623
+ }
624
+ async function accountsLogin(ctx, ref, extraArgs = [], flags = {}, deps = {}) {
625
+ if (!flags.hold) return runLogin(ctx, ref, extraArgs, flags, deps);
626
+ try {
627
+ return await runLogin(ctx, ref, extraArgs, flags, deps);
628
+ } catch (err) {
629
+ ctx.err(ctx.errTheme.warn(err instanceof Error ? err.message : String(err)));
630
+ return EXIT.failure;
631
+ } finally {
632
+ await (deps.waitForEnter ?? waitForEnter)();
633
+ }
634
+ }
635
+ function runLogin(ctx, ref, extraArgs, flags, deps) {
636
+ return withBackend(ctx, flags, deps, async (backend) => {
637
+ const account = await resolveAccount(backend, ref);
638
+ const spec = CLI_LOGINS[account.provider];
639
+ if (!spec) {
640
+ const info = providerInfo(account.provider);
641
+ throw new CliError(`${account.provider} accounts do not use a CLI login`, {
642
+ hint: info?.auth === "api-key" || info?.auth === "optional-key" ? `Rotate the key with: davecode accounts add \u2026 or PATCH /api/accounts/${account.id}` : void 0
643
+ });
644
+ }
645
+ const binary = cliBinaryFor(account) ?? spec.binary;
646
+ const resolved = which(binary, ctx.env);
647
+ if (!resolved) {
648
+ throw new CliError(`Cannot find \`${binary}\` on PATH`, { hint: spec.installHint });
649
+ }
650
+ const sandboxes = new SandboxManager(globalPaths(ctx.home).sandboxes);
651
+ const dir = sandboxes.prepare(account);
652
+ const isolation = loginEnv(account, sandboxes);
653
+ const env = { ...ctx.env, ...isolation };
654
+ const [variable] = Object.keys(isolation);
655
+ const { theme } = ctx;
656
+ ctx.out(
657
+ `${theme.accent(theme.glyph.arrow)} Logging in ${theme.bold(account.label)} ${theme.dim(`(${account.id})`)}`
658
+ );
659
+ ctx.out(theme.dim(` ${variable}=${dir}`));
660
+ ctx.out(theme.dim(` ${spec.instructions}`));
661
+ ctx.out(theme.dim(" DaveCode never sees your password; the vendor CLI keeps the login."));
662
+ ctx.out();
663
+ const args = extraArgs.length > 0 ? extraArgs : spec.loginArgs;
664
+ const exit = await spawnInteractive(resolved, args, {
665
+ env,
666
+ cwd: dir,
667
+ ...deps.spawn ? { spawn: deps.spawn } : {}
668
+ });
669
+ ctx.out();
670
+ if (exit !== 0) {
671
+ ctx.err(ctx.errTheme.warn(`${binary} exited with code ${exit}`));
672
+ return exit;
673
+ }
674
+ if (account.status === "error" && account.enabled) {
675
+ await backend.update(account.id, { enabled: true });
676
+ }
677
+ const connection = await backend.connection(account.id).catch(() => void 0);
678
+ if (connection) ctx.out(` ${describeConnection(theme, connection)}`);
679
+ ctx.out(
680
+ `${theme.ok(theme.glyph.ok)} ${account.label} is ready. Its login lives only in ${dir}`
681
+ );
682
+ return EXIT.ok;
683
+ });
684
+ }
685
+ async function accountsConnect(ctx, ref, flags = {}, deps = {}) {
686
+ const { account, connection } = await withBackend(ctx, flags, deps, async (backend) => {
687
+ const account2 = await resolveAccount(backend, ref);
688
+ return { account: account2, connection: await backend.connection(account2.id) };
689
+ });
690
+ if (ctx.json) {
691
+ printJson(ctx, { connection });
692
+ return connection.status === "connected" || flags.check ? EXIT.ok : EXIT.failure;
693
+ }
694
+ const { theme } = ctx;
695
+ ctx.out(`${theme.bold(account.label)} ${theme.dim(`(${account.id}, ${account.provider})`)}`);
696
+ ctx.out(` ${describeConnection(theme, connection)}`);
697
+ if (flags.check || connection.status === "connected") return EXIT.ok;
698
+ if (connection.method === "api-key") {
699
+ ctx.out(
700
+ theme.dim(
701
+ ` Store a key in the dashboard (Accounts \u2192 ${account.label}) or re-add the account with --secret-env.`
702
+ )
703
+ );
704
+ return EXIT.failure;
705
+ }
706
+ if (connection.method !== "cli-login") return EXIT.failure;
707
+ ctx.out();
708
+ return accountsLogin(ctx, account.id, [], flags, deps);
709
+ }
710
+ export {
711
+ accountsAdd,
712
+ accountsConnect,
713
+ accountsList,
714
+ accountsLogin,
715
+ accountsPlan,
716
+ accountsRemove,
717
+ accountsSetEnabled,
718
+ describeConnection,
719
+ describeSubscription,
720
+ loginArgs,
721
+ renderAccountList,
722
+ subscriptionFromFlags
723
+ };
724
+ //# sourceMappingURL=accounts-MTMGYKEP.js.map