@dmrlzz/davecode 0.0.0-stage → 0.4.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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -2
  3. package/bin/davecode.js +2 -0
  4. package/dist/ChatApp-BO7QMBWD.js +1045 -0
  5. package/dist/ChatApp-BO7QMBWD.js.map +1 -0
  6. package/dist/RunnerView-A7JSPUW5.js +18 -0
  7. package/dist/RunnerView-A7JSPUW5.js.map +1 -0
  8. package/dist/account-UGNLJPQ4.js +23 -0
  9. package/dist/account-UGNLJPQ4.js.map +1 -0
  10. package/dist/accounts-NM7IWP53.js +37 -0
  11. package/dist/accounts-NM7IWP53.js.map +1 -0
  12. package/dist/budget-INAW6FXD.js +115 -0
  13. package/dist/budget-INAW6FXD.js.map +1 -0
  14. package/dist/chat-U4QJQ7M6.js +359 -0
  15. package/dist/chat-U4QJQ7M6.js.map +1 -0
  16. package/dist/chunk-3Q2KCXLN.js +9608 -0
  17. package/dist/chunk-3Q2KCXLN.js.map +1 -0
  18. package/dist/chunk-5LKOJBUL.js +20 -0
  19. package/dist/chunk-5LKOJBUL.js.map +1 -0
  20. package/dist/chunk-7XVXEO7U.js +23 -0
  21. package/dist/chunk-7XVXEO7U.js.map +1 -0
  22. package/dist/chunk-CWBVNS3L.js +731 -0
  23. package/dist/chunk-CWBVNS3L.js.map +1 -0
  24. package/dist/chunk-DZ2FDABF.js +1614 -0
  25. package/dist/chunk-DZ2FDABF.js.map +1 -0
  26. package/dist/chunk-FRFSJMN4.js +79 -0
  27. package/dist/chunk-FRFSJMN4.js.map +1 -0
  28. package/dist/chunk-GE22VBCY.js +200 -0
  29. package/dist/chunk-GE22VBCY.js.map +1 -0
  30. package/dist/chunk-GHDOUII5.js +144 -0
  31. package/dist/chunk-GHDOUII5.js.map +1 -0
  32. package/dist/chunk-GZJ6JSO7.js +213 -0
  33. package/dist/chunk-GZJ6JSO7.js.map +1 -0
  34. package/dist/chunk-IC3TDONV.js +12 -0
  35. package/dist/chunk-IC3TDONV.js.map +1 -0
  36. package/dist/chunk-MC7RIPL7.js +116 -0
  37. package/dist/chunk-MC7RIPL7.js.map +1 -0
  38. package/dist/chunk-MOK5D3XJ.js +114 -0
  39. package/dist/chunk-MOK5D3XJ.js.map +1 -0
  40. package/dist/chunk-PGPXE6LF.js +20 -0
  41. package/dist/chunk-PGPXE6LF.js.map +1 -0
  42. package/dist/chunk-QA3O6RA2.js +81 -0
  43. package/dist/chunk-QA3O6RA2.js.map +1 -0
  44. package/dist/chunk-ROIUTNQV.js +20 -0
  45. package/dist/chunk-ROIUTNQV.js.map +1 -0
  46. package/dist/chunk-SF5ZEAP4.js +105 -0
  47. package/dist/chunk-SF5ZEAP4.js.map +1 -0
  48. package/dist/chunk-Z6NQDJEN.js +180 -0
  49. package/dist/chunk-Z6NQDJEN.js.map +1 -0
  50. package/dist/config-YEAPBMBX.js +158 -0
  51. package/dist/config-YEAPBMBX.js.map +1 -0
  52. package/dist/dashboard/assets/Accounts-CeVyX4a3.js +1 -0
  53. package/dist/dashboard/assets/Badge-CGY6DgGx.js +55 -0
  54. package/dist/dashboard/assets/Callout-DauCk_cW.js +1 -0
  55. package/dist/dashboard/assets/Field-OXMnT9Tb.js +1 -0
  56. package/dist/dashboard/assets/Goals-DH-KDh8M.js +1 -0
  57. package/dist/dashboard/assets/Library-C15szLjZ.js +1 -0
  58. package/dist/dashboard/assets/Markdown-Xhao3sgL.js +69 -0
  59. package/dist/dashboard/assets/Panel-Du40pM7n.js +1 -0
  60. package/dist/dashboard/assets/RoutesPage-Cj1OelGO.js +4 -0
  61. package/dist/dashboard/assets/Runner-BK1-ZF7h.js +1 -0
  62. package/dist/dashboard/assets/Settings-C1nzQQhe.js +1 -0
  63. package/dist/dashboard/assets/Switch-bwtIJ00H.js +1 -0
  64. package/dist/dashboard/assets/Tasks-x4s8IiPG.js +2 -0
  65. package/dist/dashboard/assets/Traffic-CV7TaTid.js +1 -0
  66. package/dist/dashboard/assets/Workspace-DAL-MBc0.js +1 -0
  67. package/dist/dashboard/assets/arrow-up-goM9r-I9.js +1 -0
  68. package/dist/dashboard/assets/forms-DtpfI65-.js +1 -0
  69. package/dist/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  70. package/dist/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  71. package/dist/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  72. package/dist/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  73. package/dist/dashboard/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  74. package/dist/dashboard/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  75. package/dist/dashboard/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  76. package/dist/dashboard/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  77. package/dist/dashboard/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  78. package/dist/dashboard/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  79. package/dist/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  80. package/dist/dashboard/assets/index-BCi-Vuvh.js +104 -0
  81. package/dist/dashboard/assets/index-CcJFhc98.css +2 -0
  82. package/dist/dashboard/assets/task-errors-B2pgjUGN.js +1 -0
  83. package/dist/dashboard/assets/trash-BVojETtF.js +1 -0
  84. package/dist/dashboard/favicon.svg +1 -0
  85. package/dist/dashboard/github.svg +1 -0
  86. package/dist/dashboard/index.html +31 -0
  87. package/dist/dist-RRQGUBL3.js +379 -0
  88. package/dist/dist-RRQGUBL3.js.map +1 -0
  89. package/dist/doctor-MQYK44S2.js +279 -0
  90. package/dist/doctor-MQYK44S2.js.map +1 -0
  91. package/dist/goal-6NUX4AIT.js +302 -0
  92. package/dist/goal-6NUX4AIT.js.map +1 -0
  93. package/dist/index.js +399 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/init-AXYIWAB3.js +194 -0
  96. package/dist/init-AXYIWAB3.js.map +1 -0
  97. package/dist/prompts-TZ7UC42M.js +219 -0
  98. package/dist/prompts-TZ7UC42M.js.map +1 -0
  99. package/dist/run-QRTLHFB3.js +376 -0
  100. package/dist/run-QRTLHFB3.js.map +1 -0
  101. package/dist/runner-state-QFU3LJVJ.js +17 -0
  102. package/dist/runner-state-QFU3LJVJ.js.map +1 -0
  103. package/dist/runtime.js +18 -0
  104. package/dist/runtime.js.map +1 -0
  105. package/dist/start-O5I5TSO2.js +285 -0
  106. package/dist/start-O5I5TSO2.js.map +1 -0
  107. package/dist/status-LEA3RVUX.js +25 -0
  108. package/dist/status-LEA3RVUX.js.map +1 -0
  109. package/dist/tasks-XOTVICVZ.js +184 -0
  110. package/dist/tasks-XOTVICVZ.js.map +1 -0
  111. package/package.json +73 -3
@@ -0,0 +1,114 @@
1
+ import {
2
+ truncate,
3
+ visibleWidth
4
+ } from "./chunk-SF5ZEAP4.js";
5
+ import {
6
+ blockedTasks,
7
+ nextTask,
8
+ summarize,
9
+ topologicalOrder
10
+ } from "./chunk-3Q2KCXLN.js";
11
+
12
+ // src/lib/task-tree.ts
13
+ function statusGlyph(theme, task, blocked) {
14
+ const g = theme.glyph;
15
+ switch (task.status) {
16
+ case "SUCCESS":
17
+ return theme.ok(g.success);
18
+ case "IN_PROGRESS":
19
+ return theme.accent(g.running);
20
+ case "FAILED":
21
+ return theme.error(g.failed);
22
+ default:
23
+ return blocked ? theme.dim(g.blocked) : g.pending;
24
+ }
25
+ }
26
+ function summaryLine(theme, graph) {
27
+ const s = summarize(graph);
28
+ const g = theme.glyph;
29
+ const parts = [
30
+ `${theme.ok(g.success)} ${s.counts.SUCCESS} done`,
31
+ `${theme.accent(g.running)} ${s.counts.IN_PROGRESS} running`,
32
+ `${g.pending} ${s.counts.PENDING} pending`,
33
+ `${theme.error(g.failed)} ${s.counts.FAILED} failed`
34
+ ];
35
+ return `${s.counts.SUCCESS}/${s.total} done (${s.progress}%) ${theme.dim("\xB7")} ${parts.join(" ")}`;
36
+ }
37
+ function formatTaskTree(theme, graph, options = {}) {
38
+ const width = options.width ?? 100;
39
+ if (graph.tasks.length === 0) return [theme.dim("No tasks yet.")];
40
+ const order = topologicalOrder(graph);
41
+ const rank = new Map(order.map((t, i) => [t.id, i]));
42
+ const fileIndex = new Map(graph.tasks.map((t, i) => [t.id, i]));
43
+ const blocked = new Map(blockedTasks(graph).map((b) => [b.task.id, b]));
44
+ const next = nextTask(graph);
45
+ const primaryParent = (task) => [...task.dependsOn].sort((a, b) => (rank.get(b) ?? 0) - (rank.get(a) ?? 0))[0];
46
+ const children = /* @__PURE__ */ new Map();
47
+ const roots = [];
48
+ for (const task of graph.tasks) {
49
+ const parent = primaryParent(task);
50
+ if (parent === void 0) roots.push(task);
51
+ else children.set(parent, [...children.get(parent) ?? [], task]);
52
+ }
53
+ const byFileOrder = (a, b) => (fileIndex.get(a.id) ?? 0) - (fileIndex.get(b.id) ?? 0);
54
+ const idWidth = Math.min(24, Math.max(...graph.tasks.map((t) => t.id.length)));
55
+ const lines = [];
56
+ const annotate = (task, parent) => {
57
+ const notes = [];
58
+ const others = task.dependsOn.filter((d) => d !== parent);
59
+ if (others.length > 0) notes.push(theme.dim(`+ needs ${others.join(", ")}`));
60
+ const block = blocked.get(task.id);
61
+ if (block?.reason === "failed-dependency") {
62
+ notes.push(theme.error(`blocked: ${block.blockedBy.join(", ")} failed`));
63
+ } else if (block) {
64
+ notes.push(theme.dim(`waiting on ${block.blockedBy.join(", ")}`));
65
+ }
66
+ if (task.status === "IN_PROGRESS" && task.attempts) {
67
+ notes.push(theme.accent(`attempt ${task.attempts}`));
68
+ }
69
+ if (task.status === "FAILED") {
70
+ notes.push(theme.error(`failed${task.attempts ? ` after ${task.attempts} attempt(s)` : ""}`));
71
+ }
72
+ if (task.priority !== void 0 && task.status === "PENDING") {
73
+ notes.push(theme.dim(`p${task.priority}`));
74
+ }
75
+ if (next?.id === task.id) notes.push(theme.accent(`${theme.glyph.arrow} next`));
76
+ return notes;
77
+ };
78
+ const visit = (task, prefix, connector, parent) => {
79
+ const head = `${prefix}${connector}${statusGlyph(theme, task, blocked.has(task.id))} ${theme.bold(
80
+ task.id.padEnd(idWidth)
81
+ )} `;
82
+ const notes = annotate(task, parent);
83
+ const tail = notes.length > 0 ? ` ${notes.join(theme.dim(" \xB7 "))}` : "";
84
+ const room = width - visibleWidth(head) - visibleWidth(tail);
85
+ const title = truncate(task.title, Math.max(8, room), theme.glyph.ellipsis);
86
+ lines.push(`${head}${title}${tail}`);
87
+ const kids = [...children.get(task.id) ?? []].sort(byFileOrder);
88
+ const childPrefix = prefix + (connector === theme.glyph.tee ? theme.glyph.pipe : connector ? " " : "");
89
+ kids.forEach((kid, i) => {
90
+ visit(kid, childPrefix, i === kids.length - 1 ? theme.glyph.elbow : theme.glyph.tee, task.id);
91
+ });
92
+ };
93
+ for (const root of roots.sort(byFileOrder)) visit(root, "", "");
94
+ return lines;
95
+ }
96
+ function formatTaskDetail(theme, task) {
97
+ const lines = [`${theme.bold(theme.accent(task.id))} ${task.title}`];
98
+ if (task.description) lines.push("", ...task.description.split(/\r?\n/).map((l) => ` ${l}`));
99
+ if (task.dependsOn.length > 0)
100
+ lines.push("", `${theme.dim("depends on")} ${task.dependsOn.join(", ")}`);
101
+ if (task.priority !== void 0) lines.push(`${theme.dim("priority")} ${task.priority}`);
102
+ if (task.acceptance && task.acceptance.length > 0) {
103
+ lines.push("", theme.dim("acceptance"));
104
+ for (const item of task.acceptance) lines.push(` ${theme.glyph.bullet} ${item}`);
105
+ }
106
+ return lines;
107
+ }
108
+
109
+ export {
110
+ summaryLine,
111
+ formatTaskTree,
112
+ formatTaskDetail
113
+ };
114
+ //# sourceMappingURL=chunk-MOK5D3XJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/task-tree.ts"],"sourcesContent":["import {\n blockedTasks,\n nextTask,\n summarize,\n type TaskGraph,\n type TaskNode,\n topologicalOrder,\n} from '@davecode/core';\nimport { truncate, visibleWidth } from '../ui/format';\nimport type { Theme } from '../ui/theme';\n\n/**\n * Text rendering of the task DAG. Each task appears once, under its *primary* parent (the\n * dependency that comes last in topological order); other dependencies are listed inline as\n * `+ needs …`. Blocked tasks carry their reason.\n */\n\nexport function statusGlyph(theme: Theme, task: TaskNode, blocked: boolean): string {\n const g = theme.glyph;\n switch (task.status) {\n case 'SUCCESS':\n return theme.ok(g.success);\n case 'IN_PROGRESS':\n return theme.accent(g.running);\n case 'FAILED':\n return theme.error(g.failed);\n default:\n return blocked ? theme.dim(g.blocked) : g.pending;\n }\n}\n\nexport function summaryLine(theme: Theme, graph: TaskGraph): string {\n const s = summarize(graph);\n const g = theme.glyph;\n const parts = [\n `${theme.ok(g.success)} ${s.counts.SUCCESS} done`,\n `${theme.accent(g.running)} ${s.counts.IN_PROGRESS} running`,\n `${g.pending} ${s.counts.PENDING} pending`,\n `${theme.error(g.failed)} ${s.counts.FAILED} failed`,\n ];\n return `${s.counts.SUCCESS}/${s.total} done (${s.progress}%) ${theme.dim('·')} ${parts.join(' ')}`;\n}\n\nexport interface TreeOptions {\n /** Maximum line width (titles are truncated to fit). */\n width?: number;\n}\n\nexport function formatTaskTree(\n theme: Theme,\n graph: TaskGraph,\n options: TreeOptions = {},\n): string[] {\n const width = options.width ?? 100;\n if (graph.tasks.length === 0) return [theme.dim('No tasks yet.')];\n\n const order = topologicalOrder(graph);\n const rank = new Map(order.map((t, i) => [t.id, i]));\n const fileIndex = new Map(graph.tasks.map((t, i) => [t.id, i]));\n const blocked = new Map(blockedTasks(graph).map((b) => [b.task.id, b]));\n const next = nextTask(graph);\n\n const primaryParent = (task: TaskNode): string | undefined =>\n [...task.dependsOn].sort((a, b) => (rank.get(b) ?? 0) - (rank.get(a) ?? 0))[0];\n\n const children = new Map<string, TaskNode[]>();\n const roots: TaskNode[] = [];\n for (const task of graph.tasks) {\n const parent = primaryParent(task);\n if (parent === undefined) roots.push(task);\n else children.set(parent, [...(children.get(parent) ?? []), task]);\n }\n const byFileOrder = (a: TaskNode, b: TaskNode) =>\n (fileIndex.get(a.id) ?? 0) - (fileIndex.get(b.id) ?? 0);\n\n const idWidth = Math.min(24, Math.max(...graph.tasks.map((t) => t.id.length)));\n const lines: string[] = [];\n\n const annotate = (task: TaskNode, parent: string | undefined): string[] => {\n const notes: string[] = [];\n const others = task.dependsOn.filter((d) => d !== parent);\n if (others.length > 0) notes.push(theme.dim(`+ needs ${others.join(', ')}`));\n const block = blocked.get(task.id);\n if (block?.reason === 'failed-dependency') {\n notes.push(theme.error(`blocked: ${block.blockedBy.join(', ')} failed`));\n } else if (block) {\n notes.push(theme.dim(`waiting on ${block.blockedBy.join(', ')}`));\n }\n if (task.status === 'IN_PROGRESS' && task.attempts) {\n notes.push(theme.accent(`attempt ${task.attempts}`));\n }\n if (task.status === 'FAILED') {\n notes.push(theme.error(`failed${task.attempts ? ` after ${task.attempts} attempt(s)` : ''}`));\n }\n if (task.priority !== undefined && task.status === 'PENDING') {\n notes.push(theme.dim(`p${task.priority}`));\n }\n if (next?.id === task.id) notes.push(theme.accent(`${theme.glyph.arrow} next`));\n return notes;\n };\n\n const visit = (task: TaskNode, prefix: string, connector: string, parent?: string) => {\n const head = `${prefix}${connector}${statusGlyph(theme, task, blocked.has(task.id))} ${theme.bold(\n task.id.padEnd(idWidth),\n )} `;\n const notes = annotate(task, parent);\n const tail = notes.length > 0 ? ` ${notes.join(theme.dim(' · '))}` : '';\n const room = width - visibleWidth(head) - visibleWidth(tail);\n const title = truncate(task.title, Math.max(8, room), theme.glyph.ellipsis);\n lines.push(`${head}${title}${tail}`);\n\n const kids = [...(children.get(task.id) ?? [])].sort(byFileOrder);\n const childPrefix =\n prefix + (connector === theme.glyph.tee ? theme.glyph.pipe : connector ? ' ' : '');\n kids.forEach((kid, i) => {\n visit(kid, childPrefix, i === kids.length - 1 ? theme.glyph.elbow : theme.glyph.tee, task.id);\n });\n };\n\n for (const root of roots.sort(byFileOrder)) visit(root, '', '');\n return lines;\n}\n\n/** Detail view for `davecode tasks next`. */\nexport function formatTaskDetail(theme: Theme, task: TaskNode): string[] {\n const lines = [`${theme.bold(theme.accent(task.id))} ${task.title}`];\n if (task.description) lines.push('', ...task.description.split(/\\r?\\n/).map((l) => ` ${l}`));\n if (task.dependsOn.length > 0)\n lines.push('', `${theme.dim('depends on')} ${task.dependsOn.join(', ')}`);\n if (task.priority !== undefined) lines.push(`${theme.dim('priority')} ${task.priority}`);\n if (task.acceptance && task.acceptance.length > 0) {\n lines.push('', theme.dim('acceptance'));\n for (const item of task.acceptance) lines.push(` ${theme.glyph.bullet} ${item}`);\n }\n return lines;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,20 @@
1
+ import {
2
+ PLAIN_THEME
3
+ } from "./chunk-MC7RIPL7.js";
4
+
5
+ // src/tui/theme-context.tsx
6
+ import { createContext, useContext } from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var ThemeContext = createContext(PLAIN_THEME);
9
+ function ThemeProvider({ theme, children }) {
10
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: theme, children });
11
+ }
12
+ function useTheme() {
13
+ return useContext(ThemeContext);
14
+ }
15
+
16
+ export {
17
+ ThemeProvider,
18
+ useTheme
19
+ };
20
+ //# sourceMappingURL=chunk-PGPXE6LF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tui/theme-context.tsx"],"sourcesContent":["import { createContext, type ReactNode, useContext } from 'react';\nimport { PLAIN_THEME, type Theme } from '../ui/theme';\n\nconst ThemeContext = createContext<Theme>(PLAIN_THEME);\n\nexport function ThemeProvider({ theme, children }: { theme: Theme; children?: ReactNode }) {\n return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;\n}\n\nexport function useTheme(): Theme {\n return useContext(ThemeContext);\n}\n"],"mappings":";;;;;AAAA,SAAS,eAA+B,kBAAkB;AAMjD;AAHT,IAAM,eAAe,cAAqB,WAAW;AAE9C,SAAS,cAAc,EAAE,OAAO,SAAS,GAA2C;AACzF,SAAO,oBAAC,aAAa,UAAb,EAAsB,OAAO,OAAQ,UAAS;AACxD;AAEO,SAAS,WAAkB;AAChC,SAAO,WAAW,YAAY;AAChC;","names":[]}
@@ -0,0 +1,81 @@
1
+ import {
2
+ THEME_MODES,
3
+ themeFor
4
+ } from "./chunk-MC7RIPL7.js";
5
+
6
+ // src/context.ts
7
+ import { homedir } from "os";
8
+ import { join as join2, resolve } from "path";
9
+
10
+ // src/lib/preferences.ts
11
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
12
+ import { dirname, join } from "path";
13
+ import { z } from "zod";
14
+ var schema = z.object({
15
+ theme: z.enum(THEME_MODES).optional(),
16
+ onboarded: z.boolean().optional()
17
+ });
18
+ function preferencesPath(home) {
19
+ return join(home, "preferences.json");
20
+ }
21
+ function readPreferences(home) {
22
+ try {
23
+ const parsed = schema.safeParse(JSON.parse(readFileSync(preferencesPath(home), "utf8")));
24
+ return parsed.success ? parsed.data : {};
25
+ } catch {
26
+ return {};
27
+ }
28
+ }
29
+ function writePreferences(home, update) {
30
+ const next = { ...readPreferences(home), ...update };
31
+ const path = preferencesPath(home);
32
+ mkdirSync(dirname(path), { recursive: true });
33
+ writeFileSync(path, `${JSON.stringify(next, null, 2)}
34
+ `);
35
+ return next;
36
+ }
37
+
38
+ // src/context.ts
39
+ function resolveHome(env, flag, cwd = process.cwd()) {
40
+ if (flag) return resolve(cwd, flag);
41
+ return env.DAVECODE_HOME ? resolve(cwd, env.DAVECODE_HOME) : join2(homedir(), ".davecode");
42
+ }
43
+ function createContext(options, io, base) {
44
+ const home = resolveHome(base.env, options.home, base.cwd);
45
+ const env = { ...base.env, DAVECODE_HOME: home };
46
+ const json = options.json === true;
47
+ const mode = readPreferences(home).theme;
48
+ const theme = themeFor(env, io.stdout, json ? false : options.color, process.platform, mode);
49
+ const errTheme = themeFor(env, io.stderr, options.color, process.platform, mode);
50
+ return {
51
+ env,
52
+ home,
53
+ cwd: base.cwd,
54
+ json,
55
+ theme,
56
+ errTheme,
57
+ io,
58
+ interactive: io.stdin.isTTY === true && io.stdout.isTTY === true && !json,
59
+ columns: io.stdout.columns && io.stdout.columns > 0 ? io.stdout.columns : 80,
60
+ out: (line = "") => {
61
+ io.stdout.write(`${line}
62
+ `);
63
+ },
64
+ err: (line = "") => {
65
+ io.stderr.write(`${line}
66
+ `);
67
+ }
68
+ };
69
+ }
70
+ function printJson(ctx, value) {
71
+ ctx.io.stdout.write(`${JSON.stringify(value, null, 2)}
72
+ `);
73
+ }
74
+
75
+ export {
76
+ readPreferences,
77
+ writePreferences,
78
+ createContext,
79
+ printJson
80
+ };
81
+ //# sourceMappingURL=chunk-QA3O6RA2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context.ts","../src/lib/preferences.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { readPreferences } from './lib/preferences';\nimport { type Theme, themeFor } from './ui/theme';\n\n/** Minimal writable surface the CLI prints to (process streams in production, buffers in tests). */\nexport interface OutStream {\n write(chunk: string): unknown;\n isTTY?: boolean;\n columns?: number;\n}\n\nexport interface InStream {\n isTTY?: boolean;\n}\n\nexport interface CliIO {\n stdout: OutStream;\n stderr: OutStream;\n stdin: InStream;\n}\n\n/** Options shared by every command (`--home`, `--json`, `--no-color`). */\nexport interface GlobalOptions {\n home?: string;\n json?: boolean;\n color?: boolean;\n}\n\n/** Everything a command needs; built once per invocation from the global flags. */\nexport interface CliContext {\n /** Effective environment (`DAVECODE_HOME` reflects `--home`). */\n env: NodeJS.ProcessEnv;\n /** Resolved DaveCode home. */\n home: string;\n cwd: string;\n json: boolean;\n theme: Theme;\n /** Theme for stderr (it can be a TTY while stdout is piped). */\n errTheme: Theme;\n io: CliIO;\n /** Both stdin and stdout are terminals and `--json` is off. */\n interactive: boolean;\n /** Terminal width (80 when unknown). */\n columns: number;\n /** Print a line to stdout. */\n out(line?: string): void;\n /** Print a line to stderr. */\n err(line?: string): void;\n}\n\nexport function resolveHome(env: NodeJS.ProcessEnv, flag?: string, cwd = process.cwd()): string {\n if (flag) return resolve(cwd, flag);\n return env.DAVECODE_HOME ? resolve(cwd, env.DAVECODE_HOME) : join(homedir(), '.davecode');\n}\n\nexport function createContext(\n options: GlobalOptions,\n io: CliIO,\n base: { env: NodeJS.ProcessEnv; cwd: string },\n): CliContext {\n const home = resolveHome(base.env, options.home, base.cwd);\n const env = { ...base.env, DAVECODE_HOME: home };\n const json = options.json === true;\n const mode = readPreferences(home).theme;\n const theme = themeFor(env, io.stdout, json ? false : options.color, process.platform, mode);\n const errTheme = themeFor(env, io.stderr, options.color, process.platform, mode);\n return {\n env,\n home,\n cwd: base.cwd,\n json,\n theme,\n errTheme,\n io,\n interactive: io.stdin.isTTY === true && io.stdout.isTTY === true && !json,\n columns: io.stdout.columns && io.stdout.columns > 0 ? io.stdout.columns : 80,\n out: (line = '') => {\n io.stdout.write(`${line}\\n`);\n },\n err: (line = '') => {\n io.stderr.write(`${line}\\n`);\n },\n };\n}\n\n/** Print a value as pretty JSON on stdout (the `--json` contract). */\nexport function printJson(ctx: CliContext, value: unknown): void {\n ctx.io.stdout.write(`${JSON.stringify(value, null, 2)}\\n`);\n}\n","import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { z } from 'zod';\nimport { THEME_MODES, type ThemeMode } from '../ui/theme';\n\n/** Per-user terminal preferences chosen during first-run setup. */\nexport interface Preferences {\n theme?: ThemeMode;\n /** First-run setup finished (it is not shown again). */\n onboarded?: boolean;\n}\n\nconst schema = z.object({\n theme: z.enum(THEME_MODES as [ThemeMode, ...ThemeMode[]]).optional(),\n onboarded: z.boolean().optional(),\n});\n\nexport function preferencesPath(home: string): string {\n return join(home, 'preferences.json');\n}\n\n/** Missing or unreadable preferences behave like a first run. */\nexport function readPreferences(home: string): Preferences {\n try {\n const parsed = schema.safeParse(JSON.parse(readFileSync(preferencesPath(home), 'utf8')));\n return parsed.success ? parsed.data : {};\n } catch {\n return {};\n }\n}\n\nexport function writePreferences(home: string, update: Preferences): Preferences {\n const next = { ...readPreferences(home), ...update };\n const path = preferencesPath(home);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify(next, null, 2)}\\n`);\n return next;\n}\n"],"mappings":";;;;;;AAAA,SAAS,eAAe;AACxB,SAAS,QAAAA,OAAM,eAAe;;;ACD9B,SAAS,WAAW,cAAc,qBAAqB;AACvD,SAAS,SAAS,YAAY;AAC9B,SAAS,SAAS;AAUlB,IAAM,SAAS,EAAE,OAAO;AAAA,EACtB,OAAO,EAAE,KAAK,WAA0C,EAAE,SAAS;AAAA,EACnE,WAAW,EAAE,QAAQ,EAAE,SAAS;AAClC,CAAC;AAEM,SAAS,gBAAgB,MAAsB;AACpD,SAAO,KAAK,MAAM,kBAAkB;AACtC;AAGO,SAAS,gBAAgB,MAA2B;AACzD,MAAI;AACF,UAAM,SAAS,OAAO,UAAU,KAAK,MAAM,aAAa,gBAAgB,IAAI,GAAG,MAAM,CAAC,CAAC;AACvF,WAAO,OAAO,UAAU,OAAO,OAAO,CAAC;AAAA,EACzC,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEO,SAAS,iBAAiB,MAAc,QAAkC;AAC/E,QAAM,OAAO,EAAE,GAAG,gBAAgB,IAAI,GAAG,GAAG,OAAO;AACnD,QAAM,OAAO,gBAAgB,IAAI;AACjC,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,gBAAc,MAAM,GAAG,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,CAAI;AACxD,SAAO;AACT;;;ADcO,SAAS,YAAY,KAAwB,MAAe,MAAM,QAAQ,IAAI,GAAW;AAC9F,MAAI,KAAM,QAAO,QAAQ,KAAK,IAAI;AAClC,SAAO,IAAI,gBAAgB,QAAQ,KAAK,IAAI,aAAa,IAAIC,MAAK,QAAQ,GAAG,WAAW;AAC1F;AAEO,SAAS,cACd,SACA,IACA,MACY;AACZ,QAAM,OAAO,YAAY,KAAK,KAAK,QAAQ,MAAM,KAAK,GAAG;AACzD,QAAM,MAAM,EAAE,GAAG,KAAK,KAAK,eAAe,KAAK;AAC/C,QAAM,OAAO,QAAQ,SAAS;AAC9B,QAAM,OAAO,gBAAgB,IAAI,EAAE;AACnC,QAAM,QAAQ,SAAS,KAAK,GAAG,QAAQ,OAAO,QAAQ,QAAQ,OAAO,QAAQ,UAAU,IAAI;AAC3F,QAAM,WAAW,SAAS,KAAK,GAAG,QAAQ,QAAQ,OAAO,QAAQ,UAAU,IAAI;AAC/E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,KAAK,KAAK;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa,GAAG,MAAM,UAAU,QAAQ,GAAG,OAAO,UAAU,QAAQ,CAAC;AAAA,IACrE,SAAS,GAAG,OAAO,WAAW,GAAG,OAAO,UAAU,IAAI,GAAG,OAAO,UAAU;AAAA,IAC1E,KAAK,CAAC,OAAO,OAAO;AAClB,SAAG,OAAO,MAAM,GAAG,IAAI;AAAA,CAAI;AAAA,IAC7B;AAAA,IACA,KAAK,CAAC,OAAO,OAAO;AAClB,SAAG,OAAO,MAAM,GAAG,IAAI;AAAA,CAAI;AAAA,IAC7B;AAAA,EACF;AACF;AAGO,SAAS,UAAU,KAAiB,OAAsB;AAC/D,MAAI,GAAG,OAAO,MAAM,GAAG,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAAA,CAAI;AAC3D;","names":["join","join"]}
@@ -0,0 +1,20 @@
1
+ import {
2
+ brandLogo
3
+ } from "./chunk-3Q2KCXLN.js";
4
+
5
+ // src/ui/banner.ts
6
+ function brandBanner(theme, text, columns = 80, minColumns = 56) {
7
+ if (columns < minColumns) return text;
8
+ const art = brandLogo(theme.unicode);
9
+ const width = Math.max(...art.map((line) => line.length)) + 4;
10
+ const start = Math.max(0, Math.floor((art.length - text.length) / 2));
11
+ return art.map((line, i) => {
12
+ const beside = text[i - start];
13
+ return beside ? `${theme.accent(line.padEnd(width))}${beside}` : theme.accent(line);
14
+ });
15
+ }
16
+
17
+ export {
18
+ brandBanner
19
+ };
20
+ //# sourceMappingURL=chunk-ROIUTNQV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui/banner.ts"],"sourcesContent":["import { brandLogo } from '@davecode/core';\nimport type { Theme } from './theme';\n\n/**\n * The bird with a few lines of text beside it, vertically centred. Terminals narrower than\n * `minColumns` get the text alone.\n */\nexport function brandBanner(theme: Theme, text: string[], columns = 80, minColumns = 56): string[] {\n if (columns < minColumns) return text;\n const art = brandLogo(theme.unicode);\n const width = Math.max(...art.map((line) => line.length)) + 4;\n const start = Math.max(0, Math.floor((art.length - text.length) / 2));\n return art.map((line, i) => {\n const beside = text[i - start];\n return beside ? `${theme.accent(line.padEnd(width))}${beside}` : theme.accent(line);\n });\n}\n"],"mappings":";;;;;AAOO,SAAS,YAAY,OAAc,MAAgB,UAAU,IAAI,aAAa,IAAc;AACjG,MAAI,UAAU,WAAY,QAAO;AACjC,QAAM,MAAM,UAAU,MAAM,OAAO;AACnC,QAAM,QAAQ,KAAK,IAAI,GAAG,IAAI,IAAI,CAAC,SAAS,KAAK,MAAM,CAAC,IAAI;AAC5D,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,OAAO,IAAI,SAAS,KAAK,UAAU,CAAC,CAAC;AACpE,SAAO,IAAI,IAAI,CAAC,MAAM,MAAM;AAC1B,UAAM,SAAS,KAAK,IAAI,KAAK;AAC7B,WAAO,SAAS,GAAG,MAAM,OAAO,KAAK,OAAO,KAAK,CAAC,CAAC,GAAG,MAAM,KAAK,MAAM,OAAO,IAAI;AAAA,EACpF,CAAC;AACH;","names":[]}
@@ -0,0 +1,105 @@
1
+ // src/ui/format.ts
2
+ var ANSI = /\u001b\[[0-9;]*m/g;
3
+ function stripAnsi(text) {
4
+ return text.replace(ANSI, "");
5
+ }
6
+ function visibleWidth(text) {
7
+ return [...stripAnsi(text)].length;
8
+ }
9
+ function padEnd(text, width) {
10
+ const gap = width - visibleWidth(text);
11
+ return gap > 0 ? text + " ".repeat(gap) : text;
12
+ }
13
+ function padStart(text, width) {
14
+ const gap = width - visibleWidth(text);
15
+ return gap > 0 ? " ".repeat(gap) + text : text;
16
+ }
17
+ function truncate(text, width, ellipsis = "\u2026") {
18
+ const chars = [...text];
19
+ if (chars.length <= width) return text;
20
+ if (width <= ellipsis.length) return chars.slice(0, Math.max(0, width)).join("");
21
+ return chars.slice(0, width - ellipsis.length).join("") + ellipsis;
22
+ }
23
+ function percent(ratio) {
24
+ if (!Number.isFinite(ratio) || ratio <= 0) return "0%";
25
+ return `${Math.round(ratio * 100)}%`;
26
+ }
27
+ function formatTokens(n) {
28
+ if (!Number.isFinite(n)) return "0";
29
+ const abs = Math.abs(n);
30
+ if (abs < 1e3) return String(Math.round(n));
31
+ if (abs < 1e6) return `${trimZero((n / 1e3).toFixed(1))}k`;
32
+ return `${trimZero((n / 1e6).toFixed(1))}M`;
33
+ }
34
+ function trimZero(text) {
35
+ return text.endsWith(".0") ? text.slice(0, -2) : text;
36
+ }
37
+ function formatDuration(totalSeconds) {
38
+ const s = Math.max(0, Math.floor(totalSeconds));
39
+ if (s < 60) return `${s}s`;
40
+ const m = Math.floor(s / 60);
41
+ if (m < 60) return `${m}m ${s % 60}s`;
42
+ const h = Math.floor(m / 60);
43
+ if (h < 24) return `${h}h ${m % 60}m`;
44
+ const d = Math.floor(h / 24);
45
+ return `${d}d ${h % 24}h`;
46
+ }
47
+ function levelStyle(theme, ratio) {
48
+ if (ratio >= 0.9) return theme.error;
49
+ if (ratio >= 0.7) return theme.warn;
50
+ return theme.ok;
51
+ }
52
+ function bar(theme, ratio, width = 10, limited = true) {
53
+ const w = Math.max(1, width);
54
+ if (!limited) return theme.dim(theme.glyph.barEmpty.repeat(w));
55
+ const clamped = Math.min(1, Math.max(0, Number.isFinite(ratio) ? ratio : 0));
56
+ let filled = Math.round(clamped * w);
57
+ if (clamped > 0 && filled === 0) filled = 1;
58
+ const style = levelStyle(theme, ratio);
59
+ return style(theme.glyph.barFull.repeat(filled)) + theme.dim(theme.glyph.barEmpty.repeat(w - filled));
60
+ }
61
+ function table(theme, columns, rows, maxWidth = Number.POSITIVE_INFINITY) {
62
+ let visible = columns.map((_, i) => i);
63
+ const widthOf = (indexes) => {
64
+ const widths = indexes.map(
65
+ (i) => Math.max(visibleWidth(columns[i].header), ...rows.map((r) => visibleWidth(r[i] ?? "")))
66
+ );
67
+ return { widths, total: widths.reduce((a, b) => a + b, 0) + 2 * (indexes.length - 1) };
68
+ };
69
+ let layout = widthOf(visible);
70
+ while (layout.total > maxWidth) {
71
+ const droppable = visible.filter((i) => (columns[i].drop ?? 0) > 0).sort((a, b) => (columns[b].drop ?? 0) - (columns[a].drop ?? 0));
72
+ const victim = droppable[0];
73
+ if (victim === void 0) break;
74
+ visible = visible.filter((i) => i !== victim);
75
+ layout = widthOf(visible);
76
+ }
77
+ const render = (cells, style) => visible.map((col, k) => {
78
+ const width = layout.widths[k];
79
+ const cell = cells[col] ?? "";
80
+ const text = style ? style(cell) : cell;
81
+ const last = k === visible.length - 1;
82
+ if (columns[col].align === "right") return padStart(text, width);
83
+ return last ? text : padEnd(text, width);
84
+ }).join(" ").trimEnd();
85
+ return [
86
+ render(
87
+ columns.map((c) => c.header),
88
+ theme.dim
89
+ ),
90
+ ...rows.map((r) => render(r))
91
+ ];
92
+ }
93
+
94
+ export {
95
+ visibleWidth,
96
+ padEnd,
97
+ truncate,
98
+ percent,
99
+ formatTokens,
100
+ formatDuration,
101
+ levelStyle,
102
+ bar,
103
+ table
104
+ };
105
+ //# sourceMappingURL=chunk-SF5ZEAP4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui/format.ts"],"sourcesContent":["import type { Theme } from './theme';\n\n// biome-ignore lint/suspicious/noControlCharactersInRegex: matching ANSI escape sequences\nconst ANSI = /\\u001b\\[[0-9;]*m/g;\n\nexport function stripAnsi(text: string): string {\n return text.replace(ANSI, '');\n}\n\n/** Visible width (ANSI-stripped). Good enough for the ASCII/BMP text the CLI prints. */\nexport function visibleWidth(text: string): number {\n return [...stripAnsi(text)].length;\n}\n\nexport function padEnd(text: string, width: number): string {\n const gap = width - visibleWidth(text);\n return gap > 0 ? text + ' '.repeat(gap) : text;\n}\n\nexport function padStart(text: string, width: number): string {\n const gap = width - visibleWidth(text);\n return gap > 0 ? ' '.repeat(gap) + text : text;\n}\n\n/** Cut plain text to `width` columns, ending with an ellipsis when shortened. */\nexport function truncate(text: string, width: number, ellipsis = '…'): string {\n const chars = [...text];\n if (chars.length <= width) return text;\n if (width <= ellipsis.length) return chars.slice(0, Math.max(0, width)).join('');\n return chars.slice(0, width - ellipsis.length).join('') + ellipsis;\n}\n\n/** 0.234 → \"23%\". Values above 1 are shown as-is (an over-quota account reads \"104%\"). */\nexport function percent(ratio: number): string {\n if (!Number.isFinite(ratio) || ratio <= 0) return '0%';\n return `${Math.round(ratio * 100)}%`;\n}\n\n/** 950 → \"950\", 12_300 → \"12.3k\", 4_500_000 → \"4.5M\". */\nexport function formatTokens(n: number): string {\n if (!Number.isFinite(n)) return '0';\n const abs = Math.abs(n);\n if (abs < 1000) return String(Math.round(n));\n if (abs < 1_000_000) return `${trimZero((n / 1000).toFixed(1))}k`;\n return `${trimZero((n / 1_000_000).toFixed(1))}M`;\n}\n\nfunction trimZero(text: string): string {\n return text.endsWith('.0') ? text.slice(0, -2) : text;\n}\n\n/** 42 → \"42s\", 3_725 → \"1h 2m\", 90_061 → \"1d 1h\". */\nexport function formatDuration(totalSeconds: number): string {\n const s = Math.max(0, Math.floor(totalSeconds));\n if (s < 60) return `${s}s`;\n const m = Math.floor(s / 60);\n if (m < 60) return `${m}m ${s % 60}s`;\n const h = Math.floor(m / 60);\n if (h < 24) return `${h}h ${m % 60}m`;\n const d = Math.floor(h / 24);\n return `${d}d ${h % 24}h`;\n}\n\n/** Colour for a utilization ratio: calm below 70%, warning to 90%, error above. */\nexport function levelStyle(theme: Theme, ratio: number): (text: string) => string {\n if (ratio >= 0.9) return theme.error;\n if (ratio >= 0.7) return theme.warn;\n return theme.ok;\n}\n\n/**\n * A fixed-width quota bar, e.g. `████░░░░`. `ratio` is clamped to 0..1 for drawing.\n * Unlimited windows (`limited: false`) draw a dim empty track.\n */\nexport function bar(theme: Theme, ratio: number, width = 10, limited = true): string {\n const w = Math.max(1, width);\n if (!limited) return theme.dim(theme.glyph.barEmpty.repeat(w));\n const clamped = Math.min(1, Math.max(0, Number.isFinite(ratio) ? ratio : 0));\n let filled = Math.round(clamped * w);\n if (clamped > 0 && filled === 0) filled = 1;\n const style = levelStyle(theme, ratio);\n return (\n style(theme.glyph.barFull.repeat(filled)) + theme.dim(theme.glyph.barEmpty.repeat(w - filled))\n );\n}\n\nexport interface Column {\n header: string;\n align?: 'left' | 'right';\n /** Columns with a higher number are dropped first when the terminal is narrow. */\n drop?: number;\n}\n\n/**\n * Render rows as aligned columns separated by two spaces. Columns are dropped in `drop`\n * order (highest first) until the table fits in `maxWidth`.\n */\nexport function table(\n theme: Theme,\n columns: Column[],\n rows: string[][],\n maxWidth = Number.POSITIVE_INFINITY,\n): string[] {\n let visible = columns.map((_, i) => i);\n const widthOf = (indexes: number[]) => {\n const widths = indexes.map((i) =>\n Math.max(visibleWidth(columns[i]!.header), ...rows.map((r) => visibleWidth(r[i] ?? ''))),\n );\n return { widths, total: widths.reduce((a, b) => a + b, 0) + 2 * (indexes.length - 1) };\n };\n let layout = widthOf(visible);\n while (layout.total > maxWidth) {\n const droppable = visible\n .filter((i) => (columns[i]!.drop ?? 0) > 0)\n .sort((a, b) => (columns[b]!.drop ?? 0) - (columns[a]!.drop ?? 0));\n const victim = droppable[0];\n if (victim === undefined) break;\n visible = visible.filter((i) => i !== victim);\n layout = widthOf(visible);\n }\n const render = (cells: string[], style?: (s: string) => string) =>\n visible\n .map((col, k) => {\n const width = layout.widths[k]!;\n const cell = cells[col] ?? '';\n const text = style ? style(cell) : cell;\n const last = k === visible.length - 1;\n if (columns[col]!.align === 'right') return padStart(text, width);\n return last ? text : padEnd(text, width);\n })\n .join(' ')\n .trimEnd();\n return [\n render(\n columns.map((c) => c.header),\n theme.dim,\n ),\n ...rows.map((r) => render(r)),\n ];\n}\n\n/** Section heading: accent title with an optional dim suffix. */\nexport function heading(theme: Theme, title: string, suffix?: string): string {\n return suffix\n ? `${theme.bold(theme.accent(title))} ${theme.dim(suffix)}`\n : theme.bold(theme.accent(title));\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,180 @@
1
+ import {
2
+ accountCreateSchema,
3
+ accountPatchSchema,
4
+ findGateway
5
+ } from "./chunk-DZ2FDABF.js";
6
+ import {
7
+ CliError
8
+ } from "./chunk-7XVXEO7U.js";
9
+ import {
10
+ createEngine
11
+ } from "./chunk-3Q2KCXLN.js";
12
+
13
+ // src/lib/accounts-backend.ts
14
+ import { z } from "zod";
15
+ function invalid(error) {
16
+ return new CliError(`Invalid account: ${z.prettifyError(error)}`);
17
+ }
18
+ var LocalAccounts = class {
19
+ constructor(engine) {
20
+ this.engine = engine;
21
+ }
22
+ engine;
23
+ kind = "local";
24
+ get config() {
25
+ return this.engine.config;
26
+ }
27
+ async list() {
28
+ return this.engine.accounts.list();
29
+ }
30
+ async get(id) {
31
+ return this.engine.accounts.get(id);
32
+ }
33
+ async create(input) {
34
+ const parsed = accountCreateSchema.safeParse(input);
35
+ if (!parsed.success) throw invalid(parsed.error);
36
+ const { secret, ...fields } = parsed.data;
37
+ const { accounts, keyring, audit, db } = this.engine;
38
+ return db.transaction(() => {
39
+ const created = accounts.create(fields);
40
+ if (secret !== void 0) keyring.set(created.id, secret);
41
+ audit.record({
42
+ actor: "cli",
43
+ action: "account.create",
44
+ target: created.id,
45
+ details: { provider: created.provider, label: created.label, hasSecret: !!secret }
46
+ });
47
+ return created;
48
+ })();
49
+ }
50
+ async update(id, patch) {
51
+ const parsed = accountPatchSchema.safeParse(patch);
52
+ if (!parsed.success) throw invalid(parsed.error);
53
+ const existing = this.engine.accounts.get(id);
54
+ if (!existing) throw notFound(id);
55
+ const { secret, ...fields } = parsed.data;
56
+ const update = { ...fields };
57
+ const enabled = fields.enabled ?? existing.enabled;
58
+ if (!enabled) {
59
+ update.status = "disabled";
60
+ } else if (fields.enabled === true || secret !== void 0) {
61
+ update.status = "active";
62
+ update.cooldownUntil = null;
63
+ update.lastError = null;
64
+ }
65
+ const { accounts, keyring, audit, db } = this.engine;
66
+ return db.transaction(() => {
67
+ const updated = accounts.update(id, update);
68
+ if (secret !== void 0) keyring.set(id, secret);
69
+ audit.record({
70
+ actor: "cli",
71
+ action: "account.update",
72
+ target: id,
73
+ details: { fields: Object.keys(fields), secretChanged: secret !== void 0 }
74
+ });
75
+ return updated;
76
+ })();
77
+ }
78
+ async remove(id) {
79
+ const { accounts, sandboxes, chromium, audit } = this.engine;
80
+ if (!accounts.delete(id)) throw notFound(id);
81
+ for (const cleanup of [() => sandboxes.remove(id), () => chromium.remove(id)]) {
82
+ try {
83
+ cleanup();
84
+ } catch {
85
+ }
86
+ }
87
+ audit.record({ actor: "cli", action: "account.delete", target: id });
88
+ }
89
+ /** Quota windows are rebuilt from state.db; provider-reported limits live in the gateway only. */
90
+ async budget() {
91
+ return this.engine.budget.snapshot();
92
+ }
93
+ async connection(id) {
94
+ const account = this.engine.accounts.get(id);
95
+ if (!account) throw notFound(id);
96
+ return this.engine.connections.check(account);
97
+ }
98
+ close() {
99
+ this.engine.close();
100
+ }
101
+ };
102
+ var GatewayAccounts = class {
103
+ constructor(client, config) {
104
+ this.client = client;
105
+ this.config = config;
106
+ }
107
+ client;
108
+ config;
109
+ kind = "gateway";
110
+ async list() {
111
+ return (await this.client.get("/api/accounts")).accounts;
112
+ }
113
+ async get(id) {
114
+ return (await this.list()).find((a) => a.id === id);
115
+ }
116
+ async create(input) {
117
+ return (await this.client.post("/api/accounts", input)).account;
118
+ }
119
+ async update(id, patch) {
120
+ return (await this.client.patch(
121
+ `/api/accounts/${encodeURIComponent(id)}`,
122
+ patch
123
+ )).account;
124
+ }
125
+ async remove(id) {
126
+ await this.client.delete(`/api/accounts/${encodeURIComponent(id)}`);
127
+ }
128
+ async budget() {
129
+ return (await this.client.get("/api/budget")).budget;
130
+ }
131
+ async connection(id) {
132
+ return (await this.client.get(
133
+ `/api/accounts/${encodeURIComponent(id)}/connection`
134
+ )).connection;
135
+ }
136
+ close() {
137
+ }
138
+ };
139
+ function notFound(id) {
140
+ return new CliError(`No account with id ${JSON.stringify(id)}`, {
141
+ hint: "List accounts with `davecode accounts list`."
142
+ });
143
+ }
144
+ async function openAccounts(options) {
145
+ const engine = createEngine({
146
+ home: options.home,
147
+ env: options.env,
148
+ ...options.projectRoot ? { projectRoot: options.projectRoot } : {}
149
+ });
150
+ if (!options.local) {
151
+ const gateway = await findGateway(engine.config).catch(() => void 0);
152
+ if (gateway) {
153
+ const config = engine.config;
154
+ engine.close();
155
+ return new GatewayAccounts(gateway.client, config);
156
+ }
157
+ }
158
+ return new LocalAccounts(engine);
159
+ }
160
+ async function resolveAccount(backend, ref) {
161
+ const accounts = await backend.list();
162
+ const exact = accounts.find((a) => a.id === ref);
163
+ if (exact) return exact;
164
+ const matches = accounts.filter(
165
+ (a) => a.label.toLowerCase() === ref.toLowerCase() || a.id.startsWith(ref)
166
+ );
167
+ if (matches.length === 1) return matches[0];
168
+ if (matches.length > 1) {
169
+ throw new CliError(`${JSON.stringify(ref)} matches ${matches.length} accounts`, {
170
+ hint: `Use the full id: ${matches.map((a) => a.id).join(", ")}`
171
+ });
172
+ }
173
+ throw notFound(ref);
174
+ }
175
+
176
+ export {
177
+ openAccounts,
178
+ resolveAccount
179
+ };
180
+ //# sourceMappingURL=chunk-Z6NQDJEN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/accounts-backend.ts"],"sourcesContent":["import {\n type Account,\n type AccountConnection,\n type AccountUpdateInput,\n type BudgetSnapshot,\n createEngine,\n type DaveConfig,\n type Engine,\n type ProviderKind,\n type QuotaLimits,\n type SubscriptionInfo,\n} from '@davecode/core';\nimport { accountCreateSchema, accountPatchSchema } from '@davecode/server';\nimport { z } from 'zod';\nimport { findGateway, type GatewayClient } from '../client';\nimport { CliError } from '../errors';\n\n/** Body of `POST /api/accounts` (secret is write-only). */\nexport interface AccountCreate {\n provider: ProviderKind;\n label: string;\n enabled?: boolean;\n priority?: number;\n weight?: number;\n limits?: QuotaLimits;\n config?: Record<string, unknown>;\n subscription?: SubscriptionInfo;\n secret?: string;\n}\n\nexport type AccountPatch = Partial<Omit<AccountCreate, 'provider' | 'subscription'>> & {\n /** Replaces the subscription details; `null` clears them. */\n subscription?: SubscriptionInfo | null;\n};\n\n/**\n * Account operations against a running gateway (so the dashboard sees the change live) or, when\n * none is running, directly against `state.db` with the same validation and audit trail.\n */\nexport interface AccountsBackend {\n readonly kind: 'gateway' | 'local';\n readonly config: DaveConfig;\n list(): Promise<Account[]>;\n get(id: string): Promise<Account | undefined>;\n create(input: AccountCreate): Promise<Account>;\n update(id: string, patch: AccountPatch): Promise<Account>;\n remove(id: string): Promise<void>;\n /** Subscription details and remaining capacity per account. */\n budget(): Promise<BudgetSnapshot>;\n /** Whether the account's API key or vendor CLI login is in place. */\n connection(id: string): Promise<AccountConnection>;\n close(): void;\n}\n\nfunction invalid(error: z.ZodError): CliError {\n return new CliError(`Invalid account: ${z.prettifyError(error)}`);\n}\n\nexport class LocalAccounts implements AccountsBackend {\n readonly kind = 'local' as const;\n\n constructor(readonly engine: Engine) {}\n\n get config(): DaveConfig {\n return this.engine.config;\n }\n\n async list(): Promise<Account[]> {\n return this.engine.accounts.list();\n }\n\n async get(id: string): Promise<Account | undefined> {\n return this.engine.accounts.get(id);\n }\n\n async create(input: AccountCreate): Promise<Account> {\n const parsed = accountCreateSchema.safeParse(input);\n if (!parsed.success) throw invalid(parsed.error);\n const { secret, ...fields } = parsed.data;\n const { accounts, keyring, audit, db } = this.engine;\n return db.transaction(() => {\n const created = accounts.create(fields);\n if (secret !== undefined) keyring.set(created.id, secret);\n audit.record({\n actor: 'cli',\n action: 'account.create',\n target: created.id,\n details: { provider: created.provider, label: created.label, hasSecret: !!secret },\n });\n return created;\n })();\n }\n\n async update(id: string, patch: AccountPatch): Promise<Account> {\n const parsed = accountPatchSchema.safeParse(patch);\n if (!parsed.success) throw invalid(parsed.error);\n const existing = this.engine.accounts.get(id);\n if (!existing) throw notFound(id);\n const { secret, ...fields } = parsed.data;\n const update: AccountUpdateInput = { ...fields };\n const enabled = fields.enabled ?? existing.enabled;\n if (!enabled) {\n update.status = 'disabled';\n } else if (fields.enabled === true || secret !== undefined) {\n update.status = 'active';\n update.cooldownUntil = null;\n update.lastError = null;\n }\n const { accounts, keyring, audit, db } = this.engine;\n return db.transaction(() => {\n const updated = accounts.update(id, update)!;\n if (secret !== undefined) keyring.set(id, secret);\n audit.record({\n actor: 'cli',\n action: 'account.update',\n target: id,\n details: { fields: Object.keys(fields), secretChanged: secret !== undefined },\n });\n return updated;\n })();\n }\n\n async remove(id: string): Promise<void> {\n const { accounts, sandboxes, chromium, audit } = this.engine;\n if (!accounts.delete(id)) throw notFound(id);\n for (const cleanup of [() => sandboxes.remove(id), () => chromium.remove(id)]) {\n try {\n cleanup();\n } catch {\n // A leftover directory is harmless; the account row is gone.\n }\n }\n audit.record({ actor: 'cli', action: 'account.delete', target: id });\n }\n\n /** Quota windows are rebuilt from state.db; provider-reported limits live in the gateway only. */\n async budget(): Promise<BudgetSnapshot> {\n return this.engine.budget.snapshot();\n }\n\n async connection(id: string): Promise<AccountConnection> {\n const account = this.engine.accounts.get(id);\n if (!account) throw notFound(id);\n return this.engine.connections.check(account);\n }\n\n close(): void {\n this.engine.close();\n }\n}\n\nexport class GatewayAccounts implements AccountsBackend {\n readonly kind = 'gateway' as const;\n\n constructor(\n readonly client: GatewayClient,\n readonly config: DaveConfig,\n ) {}\n\n async list(): Promise<Account[]> {\n return (await this.client.get<{ accounts: Account[] }>('/api/accounts')).accounts;\n }\n\n async get(id: string): Promise<Account | undefined> {\n return (await this.list()).find((a) => a.id === id);\n }\n\n async create(input: AccountCreate): Promise<Account> {\n return (await this.client.post<{ account: Account }>('/api/accounts', input)).account;\n }\n\n async update(id: string, patch: AccountPatch): Promise<Account> {\n return (\n await this.client.patch<{ account: Account }>(\n `/api/accounts/${encodeURIComponent(id)}`,\n patch,\n )\n ).account;\n }\n\n async remove(id: string): Promise<void> {\n await this.client.delete(`/api/accounts/${encodeURIComponent(id)}`);\n }\n\n async budget(): Promise<BudgetSnapshot> {\n return (await this.client.get<{ budget: BudgetSnapshot }>('/api/budget')).budget;\n }\n\n async connection(id: string): Promise<AccountConnection> {\n return (\n await this.client.get<{ connection: AccountConnection }>(\n `/api/accounts/${encodeURIComponent(id)}/connection`,\n )\n ).connection;\n }\n\n close(): void {}\n}\n\nfunction notFound(id: string): CliError {\n return new CliError(`No account with id ${JSON.stringify(id)}`, {\n hint: 'List accounts with `davecode accounts list`.',\n });\n}\n\n/** Prefer the running gateway; fall back to the local database. */\nexport async function openAccounts(options: {\n home: string;\n env: NodeJS.ProcessEnv;\n projectRoot?: string;\n /** Skip the gateway probe (tests, or `--local`). */\n local?: boolean;\n}): Promise<AccountsBackend> {\n const engine = createEngine({\n home: options.home,\n env: options.env,\n ...(options.projectRoot ? { projectRoot: options.projectRoot } : {}),\n });\n if (!options.local) {\n const gateway = await findGateway(engine.config).catch(() => undefined);\n if (gateway) {\n const config = engine.config;\n engine.close();\n return new GatewayAccounts(gateway.client, config);\n }\n }\n return new LocalAccounts(engine);\n}\n\n/** Resolve an account by exact id, or by unique label/id prefix for convenience. */\nexport async function resolveAccount(backend: AccountsBackend, ref: string): Promise<Account> {\n const accounts = await backend.list();\n const exact = accounts.find((a) => a.id === ref);\n if (exact) return exact;\n const matches = accounts.filter(\n (a) => a.label.toLowerCase() === ref.toLowerCase() || a.id.startsWith(ref),\n );\n if (matches.length === 1) return matches[0]!;\n if (matches.length > 1) {\n throw new CliError(`${JSON.stringify(ref)} matches ${matches.length} accounts`, {\n hint: `Use the full id: ${matches.map((a) => a.id).join(', ')}`,\n });\n }\n throw notFound(ref);\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAS,SAAS;AAyClB,SAAS,QAAQ,OAA6B;AAC5C,SAAO,IAAI,SAAS,oBAAoB,EAAE,cAAc,KAAK,CAAC,EAAE;AAClE;AAEO,IAAM,gBAAN,MAA+C;AAAA,EAGpD,YAAqB,QAAgB;AAAhB;AAAA,EAAiB;AAAA,EAAjB;AAAA,EAFZ,OAAO;AAAA,EAIhB,IAAI,SAAqB;AACvB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA,EAEA,MAAM,OAA2B;AAC/B,WAAO,KAAK,OAAO,SAAS,KAAK;AAAA,EACnC;AAAA,EAEA,MAAM,IAAI,IAA0C;AAClD,WAAO,KAAK,OAAO,SAAS,IAAI,EAAE;AAAA,EACpC;AAAA,EAEA,MAAM,OAAO,OAAwC;AACnD,UAAM,SAAS,oBAAoB,UAAU,KAAK;AAClD,QAAI,CAAC,OAAO,QAAS,OAAM,QAAQ,OAAO,KAAK;AAC/C,UAAM,EAAE,QAAQ,GAAG,OAAO,IAAI,OAAO;AACrC,UAAM,EAAE,UAAU,SAAS,OAAO,GAAG,IAAI,KAAK;AAC9C,WAAO,GAAG,YAAY,MAAM;AAC1B,YAAM,UAAU,SAAS,OAAO,MAAM;AACtC,UAAI,WAAW,OAAW,SAAQ,IAAI,QAAQ,IAAI,MAAM;AACxD,YAAM,OAAO;AAAA,QACX,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,QAAQ,QAAQ;AAAA,QAChB,SAAS,EAAE,UAAU,QAAQ,UAAU,OAAO,QAAQ,OAAO,WAAW,CAAC,CAAC,OAAO;AAAA,MACnF,CAAC;AACD,aAAO;AAAA,IACT,CAAC,EAAE;AAAA,EACL;AAAA,EAEA,MAAM,OAAO,IAAY,OAAuC;AAC9D,UAAM,SAAS,mBAAmB,UAAU,KAAK;AACjD,QAAI,CAAC,OAAO,QAAS,OAAM,QAAQ,OAAO,KAAK;AAC/C,UAAM,WAAW,KAAK,OAAO,SAAS,IAAI,EAAE;AAC5C,QAAI,CAAC,SAAU,OAAM,SAAS,EAAE;AAChC,UAAM,EAAE,QAAQ,GAAG,OAAO,IAAI,OAAO;AACrC,UAAM,SAA6B,EAAE,GAAG,OAAO;AAC/C,UAAM,UAAU,OAAO,WAAW,SAAS;AAC3C,QAAI,CAAC,SAAS;AACZ,aAAO,SAAS;AAAA,IAClB,WAAW,OAAO,YAAY,QAAQ,WAAW,QAAW;AAC1D,aAAO,SAAS;AAChB,aAAO,gBAAgB;AACvB,aAAO,YAAY;AAAA,IACrB;AACA,UAAM,EAAE,UAAU,SAAS,OAAO,GAAG,IAAI,KAAK;AAC9C,WAAO,GAAG,YAAY,MAAM;AAC1B,YAAM,UAAU,SAAS,OAAO,IAAI,MAAM;AAC1C,UAAI,WAAW,OAAW,SAAQ,IAAI,IAAI,MAAM;AAChD,YAAM,OAAO;AAAA,QACX,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,SAAS,EAAE,QAAQ,OAAO,KAAK,MAAM,GAAG,eAAe,WAAW,OAAU;AAAA,MAC9E,CAAC;AACD,aAAO;AAAA,IACT,CAAC,EAAE;AAAA,EACL;AAAA,EAEA,MAAM,OAAO,IAA2B;AACtC,UAAM,EAAE,UAAU,WAAW,UAAU,MAAM,IAAI,KAAK;AACtD,QAAI,CAAC,SAAS,OAAO,EAAE,EAAG,OAAM,SAAS,EAAE;AAC3C,eAAW,WAAW,CAAC,MAAM,UAAU,OAAO,EAAE,GAAG,MAAM,SAAS,OAAO,EAAE,CAAC,GAAG;AAC7E,UAAI;AACF,gBAAQ;AAAA,MACV,QAAQ;AAAA,MAER;AAAA,IACF;AACA,UAAM,OAAO,EAAE,OAAO,OAAO,QAAQ,kBAAkB,QAAQ,GAAG,CAAC;AAAA,EACrE;AAAA;AAAA,EAGA,MAAM,SAAkC;AACtC,WAAO,KAAK,OAAO,OAAO,SAAS;AAAA,EACrC;AAAA,EAEA,MAAM,WAAW,IAAwC;AACvD,UAAM,UAAU,KAAK,OAAO,SAAS,IAAI,EAAE;AAC3C,QAAI,CAAC,QAAS,OAAM,SAAS,EAAE;AAC/B,WAAO,KAAK,OAAO,YAAY,MAAM,OAAO;AAAA,EAC9C;AAAA,EAEA,QAAc;AACZ,SAAK,OAAO,MAAM;AAAA,EACpB;AACF;AAEO,IAAM,kBAAN,MAAiD;AAAA,EAGtD,YACW,QACA,QACT;AAFS;AACA;AAAA,EACR;AAAA,EAFQ;AAAA,EACA;AAAA,EAJF,OAAO;AAAA,EAOhB,MAAM,OAA2B;AAC/B,YAAQ,MAAM,KAAK,OAAO,IAA6B,eAAe,GAAG;AAAA,EAC3E;AAAA,EAEA,MAAM,IAAI,IAA0C;AAClD,YAAQ,MAAM,KAAK,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAAA,EACpD;AAAA,EAEA,MAAM,OAAO,OAAwC;AACnD,YAAQ,MAAM,KAAK,OAAO,KAA2B,iBAAiB,KAAK,GAAG;AAAA,EAChF;AAAA,EAEA,MAAM,OAAO,IAAY,OAAuC;AAC9D,YACE,MAAM,KAAK,OAAO;AAAA,MAChB,iBAAiB,mBAAmB,EAAE,CAAC;AAAA,MACvC;AAAA,IACF,GACA;AAAA,EACJ;AAAA,EAEA,MAAM,OAAO,IAA2B;AACtC,UAAM,KAAK,OAAO,OAAO,iBAAiB,mBAAmB,EAAE,CAAC,EAAE;AAAA,EACpE;AAAA,EAEA,MAAM,SAAkC;AACtC,YAAQ,MAAM,KAAK,OAAO,IAAgC,aAAa,GAAG;AAAA,EAC5E;AAAA,EAEA,MAAM,WAAW,IAAwC;AACvD,YACE,MAAM,KAAK,OAAO;AAAA,MAChB,iBAAiB,mBAAmB,EAAE,CAAC;AAAA,IACzC,GACA;AAAA,EACJ;AAAA,EAEA,QAAc;AAAA,EAAC;AACjB;AAEA,SAAS,SAAS,IAAsB;AACtC,SAAO,IAAI,SAAS,sBAAsB,KAAK,UAAU,EAAE,CAAC,IAAI;AAAA,IAC9D,MAAM;AAAA,EACR,CAAC;AACH;AAGA,eAAsB,aAAa,SAMN;AAC3B,QAAM,SAAS,aAAa;AAAA,IAC1B,MAAM,QAAQ;AAAA,IACd,KAAK,QAAQ;AAAA,IACb,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,CAAC;AAAA,EACpE,CAAC;AACD,MAAI,CAAC,QAAQ,OAAO;AAClB,UAAM,UAAU,MAAM,YAAY,OAAO,MAAM,EAAE,MAAM,MAAM,MAAS;AACtE,QAAI,SAAS;AACX,YAAM,SAAS,OAAO;AACtB,aAAO,MAAM;AACb,aAAO,IAAI,gBAAgB,QAAQ,QAAQ,MAAM;AAAA,IACnD;AAAA,EACF;AACA,SAAO,IAAI,cAAc,MAAM;AACjC;AAGA,eAAsB,eAAe,SAA0B,KAA+B;AAC5F,QAAM,WAAW,MAAM,QAAQ,KAAK;AACpC,QAAM,QAAQ,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,GAAG;AAC/C,MAAI,MAAO,QAAO;AAClB,QAAM,UAAU,SAAS;AAAA,IACvB,CAAC,MAAM,EAAE,MAAM,YAAY,MAAM,IAAI,YAAY,KAAK,EAAE,GAAG,WAAW,GAAG;AAAA,EAC3E;AACA,MAAI,QAAQ,WAAW,EAAG,QAAO,QAAQ,CAAC;AAC1C,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,IAAI,SAAS,GAAG,KAAK,UAAU,GAAG,CAAC,YAAY,QAAQ,MAAM,aAAa;AAAA,MAC9E,MAAM,oBAAoB,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,IAC/D,CAAC;AAAA,EACH;AACA,QAAM,SAAS,GAAG;AACpB;","names":[]}