ultracode-ai 1.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 (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -0
  3. package/dist/commands/authoring-agents.d.ts +19 -0
  4. package/dist/commands/authoring-agents.d.ts.map +1 -0
  5. package/dist/commands/authoring-agents.js +123 -0
  6. package/dist/commands/authoring-agents.js.map +1 -0
  7. package/dist/commands/budget.d.ts +16 -0
  8. package/dist/commands/budget.d.ts.map +1 -0
  9. package/dist/commands/budget.js +105 -0
  10. package/dist/commands/budget.js.map +1 -0
  11. package/dist/commands/config.d.ts +15 -0
  12. package/dist/commands/config.d.ts.map +1 -0
  13. package/dist/commands/config.js +147 -0
  14. package/dist/commands/config.js.map +1 -0
  15. package/dist/commands/doctor.d.ts +65 -0
  16. package/dist/commands/doctor.d.ts.map +1 -0
  17. package/dist/commands/doctor.js +188 -0
  18. package/dist/commands/doctor.js.map +1 -0
  19. package/dist/commands/ext-format.d.ts +22 -0
  20. package/dist/commands/ext-format.d.ts.map +1 -0
  21. package/dist/commands/ext-format.js +115 -0
  22. package/dist/commands/ext-format.js.map +1 -0
  23. package/dist/commands/ext-helpers.d.ts +25 -0
  24. package/dist/commands/ext-helpers.d.ts.map +1 -0
  25. package/dist/commands/ext-helpers.js +36 -0
  26. package/dist/commands/ext-helpers.js.map +1 -0
  27. package/dist/commands/learn.d.ts +29 -0
  28. package/dist/commands/learn.d.ts.map +1 -0
  29. package/dist/commands/learn.js +190 -0
  30. package/dist/commands/learn.js.map +1 -0
  31. package/dist/commands/list.d.ts +11 -0
  32. package/dist/commands/list.d.ts.map +1 -0
  33. package/dist/commands/list.js +58 -0
  34. package/dist/commands/list.js.map +1 -0
  35. package/dist/commands/run.d.ts +13 -0
  36. package/dist/commands/run.d.ts.map +1 -0
  37. package/dist/commands/run.js +46 -0
  38. package/dist/commands/run.js.map +1 -0
  39. package/dist/commands/setup.d.ts +40 -0
  40. package/dist/commands/setup.d.ts.map +1 -0
  41. package/dist/commands/setup.js +146 -0
  42. package/dist/commands/setup.js.map +1 -0
  43. package/dist/commands/skills.d.ts +16 -0
  44. package/dist/commands/skills.d.ts.map +1 -0
  45. package/dist/commands/skills.js +148 -0
  46. package/dist/commands/skills.js.map +1 -0
  47. package/dist/commands/versioning.d.ts +13 -0
  48. package/dist/commands/versioning.d.ts.map +1 -0
  49. package/dist/commands/versioning.js +63 -0
  50. package/dist/commands/versioning.js.map +1 -0
  51. package/dist/context.d.ts +60 -0
  52. package/dist/context.d.ts.map +1 -0
  53. package/dist/context.js +69 -0
  54. package/dist/context.js.map +1 -0
  55. package/dist/core-loader.d.ts +22 -0
  56. package/dist/core-loader.d.ts.map +1 -0
  57. package/dist/core-loader.js +22 -0
  58. package/dist/core-loader.js.map +1 -0
  59. package/dist/format.d.ts +15 -0
  60. package/dist/format.d.ts.map +1 -0
  61. package/dist/format.js +80 -0
  62. package/dist/format.js.map +1 -0
  63. package/dist/index.d.ts +22 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +59 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/program.d.ts +19 -0
  68. package/dist/program.d.ts.map +1 -0
  69. package/dist/program.js +120 -0
  70. package/dist/program.js.map +1 -0
  71. package/dist/themes/dark.d.ts +4 -0
  72. package/dist/themes/dark.d.ts.map +1 -0
  73. package/dist/themes/dark.js +16 -0
  74. package/dist/themes/dark.js.map +1 -0
  75. package/dist/themes/dracula.d.ts +4 -0
  76. package/dist/themes/dracula.d.ts.map +1 -0
  77. package/dist/themes/dracula.js +16 -0
  78. package/dist/themes/dracula.js.map +1 -0
  79. package/dist/themes/index.d.ts +32 -0
  80. package/dist/themes/index.d.ts.map +1 -0
  81. package/dist/themes/index.js +45 -0
  82. package/dist/themes/index.js.map +1 -0
  83. package/dist/themes/light.d.ts +4 -0
  84. package/dist/themes/light.d.ts.map +1 -0
  85. package/dist/themes/light.js +16 -0
  86. package/dist/themes/light.js.map +1 -0
  87. package/dist/themes/theme.d.ts +34 -0
  88. package/dist/themes/theme.d.ts.map +1 -0
  89. package/dist/themes/theme.js +2 -0
  90. package/dist/themes/theme.js.map +1 -0
  91. package/dist/ui/App.d.ts +24 -0
  92. package/dist/ui/App.d.ts.map +1 -0
  93. package/dist/ui/App.js +217 -0
  94. package/dist/ui/App.js.map +1 -0
  95. package/dist/ui/ChatInput.d.ts +21 -0
  96. package/dist/ui/ChatInput.d.ts.map +1 -0
  97. package/dist/ui/ChatInput.js +88 -0
  98. package/dist/ui/ChatInput.js.map +1 -0
  99. package/dist/ui/ChatOutput.d.ts +22 -0
  100. package/dist/ui/ChatOutput.d.ts.map +1 -0
  101. package/dist/ui/ChatOutput.js +27 -0
  102. package/dist/ui/ChatOutput.js.map +1 -0
  103. package/dist/ui/StatusBar.d.ts +22 -0
  104. package/dist/ui/StatusBar.d.ts.map +1 -0
  105. package/dist/ui/StatusBar.js +17 -0
  106. package/dist/ui/StatusBar.js.map +1 -0
  107. package/dist/ui/ToolOutput.d.ts +20 -0
  108. package/dist/ui/ToolOutput.d.ts.map +1 -0
  109. package/dist/ui/ToolOutput.js +14 -0
  110. package/dist/ui/ToolOutput.js.map +1 -0
  111. package/dist/ui/ctrl-c.d.ts +21 -0
  112. package/dist/ui/ctrl-c.d.ts.map +1 -0
  113. package/dist/ui/ctrl-c.js +27 -0
  114. package/dist/ui/ctrl-c.js.map +1 -0
  115. package/dist/ui/start.d.ts +13 -0
  116. package/dist/ui/start.d.ts.map +1 -0
  117. package/dist/ui/start.js +25 -0
  118. package/dist/ui/start.js.map +1 -0
  119. package/dist/utils/cli-version.d.ts +15 -0
  120. package/dist/utils/cli-version.d.ts.map +1 -0
  121. package/dist/utils/cli-version.js +33 -0
  122. package/dist/utils/cli-version.js.map +1 -0
  123. package/dist/utils/resolve-home.d.ts +19 -0
  124. package/dist/utils/resolve-home.d.ts.map +1 -0
  125. package/dist/utils/resolve-home.js +28 -0
  126. package/dist/utils/resolve-home.js.map +1 -0
  127. package/dist/utils/text.d.ts +50 -0
  128. package/dist/utils/text.d.ts.map +1 -0
  129. package/dist/utils/text.js +133 -0
  130. package/dist/utils/text.js.map +1 -0
  131. package/package.json +45 -0
@@ -0,0 +1,188 @@
1
+ /**
2
+ * `ultracode doctor [--json]`
3
+ *
4
+ * Fully offline environment check: Node version, git availability, config
5
+ * directory writability, config validity, API-key presence (masked — the key
6
+ * value is never printed) and terminal capabilities. Critical failures set
7
+ * exit code 1.
8
+ */
9
+ import path from "node:path";
10
+ import process from "node:process";
11
+ import { promises as fs } from "node:fs";
12
+ import { randomUUID } from "node:crypto";
13
+ import { USER_CONFIG_DIR, serializeError } from "ultracode-shared";
14
+ import { safeDispose } from "../context.js";
15
+ import { resolveHome } from "../utils/resolve-home.js";
16
+ import { maskSecret } from "../utils/text.js";
17
+ /** Environment variables checked for API-key presence. */
18
+ export const API_KEY_VARS = [
19
+ "MISTRAL_API_KEY",
20
+ "ANTHROPIC_API_KEY",
21
+ "OPENAI_API_KEY",
22
+ "ULTRACODE_API_KEY",
23
+ ];
24
+ /** Minimum supported Node.js major version. */
25
+ export const MIN_NODE_MAJOR = 22;
26
+ /**
27
+ * Minimum supported Node.js minor version *within* {@link MIN_NODE_MAJOR}.
28
+ * A newer major satisfies the requirement regardless of its minor; only the
29
+ * boundary major is gated on this minor. Mirrors the `engines.node`
30
+ * (`>=22.13.0`) declared by every workspace package.
31
+ */
32
+ export const MIN_NODE_MINOR = 13;
33
+ /** Default writability probe: mkdir -p, then create + delete a marker file. */
34
+ export async function defaultCheckWritable(dir) {
35
+ const marker = path.join(dir, `.ultracode-doctor-${randomUUID()}`);
36
+ try {
37
+ await fs.mkdir(dir, { recursive: true });
38
+ await fs.writeFile(marker, "doctor", "utf8");
39
+ await fs.unlink(marker);
40
+ return true;
41
+ }
42
+ catch (err) {
43
+ return serializeError(err).message;
44
+ }
45
+ }
46
+ /** Build the production {@link DoctorDeps} from a {@link CliContext}. */
47
+ export function makeDefaultDoctorDeps(ctx) {
48
+ return {
49
+ nodeVersion: ctx.nodeVersion,
50
+ env: ctx.env,
51
+ isTTY: ctx.stdout.isTTY === true,
52
+ gitVersion: () => ctx.gitVersion(),
53
+ configDir: resolveHome(USER_CONFIG_DIR, ctx.homedir),
54
+ checkWritable: defaultCheckWritable,
55
+ loadConfig: async () => {
56
+ const facade = await ctx.createFacade({ cwd: ctx.cwd });
57
+ await safeDispose(facade);
58
+ },
59
+ };
60
+ }
61
+ /** Run all doctor checks. Never throws — failures become `ok: false` checks. */
62
+ export async function collectDoctorReport(deps) {
63
+ const checks = [];
64
+ // 1. Node.js version (critical)
65
+ const [majorRaw, minorRaw] = deps.nodeVersion.split(".");
66
+ const major = Number.parseInt(majorRaw ?? "", 10);
67
+ const minor = Number.parseInt(minorRaw ?? "", 10);
68
+ const required = `${MIN_NODE_MAJOR}.${MIN_NODE_MINOR}`;
69
+ // A newer major always qualifies; the boundary major additionally requires
70
+ // minor >= MIN_NODE_MINOR (an unparseable minor on the boundary major fails).
71
+ const nodeOk = Number.isFinite(major) &&
72
+ (major > MIN_NODE_MAJOR ||
73
+ (major === MIN_NODE_MAJOR && Number.isFinite(minor) && minor >= MIN_NODE_MINOR));
74
+ checks.push({
75
+ name: "node",
76
+ ok: nodeOk,
77
+ critical: true,
78
+ detail: nodeOk
79
+ ? `Node.js v${deps.nodeVersion} (>= ${required} required)`
80
+ : `Node.js v${deps.nodeVersion} is not supported — version ${required} or newer is required`,
81
+ });
82
+ // 2. git availability (non-critical)
83
+ let git = null;
84
+ try {
85
+ git = await deps.gitVersion();
86
+ }
87
+ catch {
88
+ git = null;
89
+ }
90
+ checks.push({
91
+ name: "git",
92
+ ok: git !== null,
93
+ critical: false,
94
+ detail: git !== null ? git : "git not found in PATH — versioning features are unavailable",
95
+ });
96
+ // 3. config directory writable (critical)
97
+ let writable;
98
+ try {
99
+ writable = await deps.checkWritable(deps.configDir);
100
+ }
101
+ catch (err) {
102
+ writable = serializeError(err).message;
103
+ }
104
+ checks.push({
105
+ name: "config-dir",
106
+ ok: writable === true,
107
+ critical: true,
108
+ detail: writable === true
109
+ ? `config directory writable: ${deps.configDir}`
110
+ : `config directory not writable (${deps.configDir}): ${writable}`,
111
+ });
112
+ // 4. configuration loads + validates (critical)
113
+ try {
114
+ await deps.loadConfig();
115
+ checks.push({
116
+ name: "config",
117
+ ok: true,
118
+ critical: true,
119
+ detail: "configuration loaded and validated",
120
+ });
121
+ }
122
+ catch (err) {
123
+ const s = serializeError(err);
124
+ checks.push({
125
+ name: "config",
126
+ ok: false,
127
+ critical: true,
128
+ detail: `configuration invalid: ${s.message}`,
129
+ });
130
+ }
131
+ // 5. API keys (presence only, masked; non-critical)
132
+ const present = API_KEY_VARS.filter((k) => {
133
+ const v = deps.env[k];
134
+ return typeof v === "string" && v.trim() !== "";
135
+ });
136
+ checks.push({
137
+ name: "api-keys",
138
+ ok: present.length > 0,
139
+ critical: false,
140
+ detail: present.length > 0
141
+ ? present.map((k) => `${k}=${maskSecret(deps.env[k] ?? "")}`).join(", ")
142
+ : "no API keys found in the environment — run `ultracode --setup`",
143
+ });
144
+ // 6. terminal capabilities (informational, non-critical)
145
+ const colorHints = [];
146
+ if (deps.env["NO_COLOR"] !== undefined)
147
+ colorHints.push("colors disabled (NO_COLOR)");
148
+ if (deps.env["TERM"] === "dumb")
149
+ colorHints.push("TERM=dumb");
150
+ checks.push({
151
+ name: "terminal",
152
+ ok: deps.isTTY,
153
+ critical: false,
154
+ detail: deps.isTTY
155
+ ? `interactive TTY available${colorHints.length > 0 ? ` — ${colorHints.join(", ")}` : ""}`
156
+ : "stdout is not a TTY — interactive mode unavailable (use `run -p`)",
157
+ });
158
+ const ok = checks.every((c) => c.ok || !c.critical);
159
+ return { ok, checks };
160
+ }
161
+ /** Render a {@link DoctorReport} as a ✓/✗ checklist. */
162
+ export function renderDoctorReport(report) {
163
+ const lines = ["UltraCode Doctor"];
164
+ for (const c of report.checks) {
165
+ lines.push(`${c.ok ? "✓" : "✗"} ${c.name}: ${c.detail}`);
166
+ }
167
+ lines.push(report.ok ? "✓ all critical checks passed" : "✗ critical problems found");
168
+ return `${lines.join("\n")}\n`;
169
+ }
170
+ /** Register the `doctor` command on `program`. */
171
+ export function registerDoctorCommand(program, ctx) {
172
+ program
173
+ .command("doctor")
174
+ .description("check the local environment (offline) and report problems")
175
+ .option("--json", "output as JSON")
176
+ .action(async (opts) => {
177
+ const report = await collectDoctorReport(makeDefaultDoctorDeps(ctx));
178
+ if (opts.json) {
179
+ ctx.stdout.write(`${JSON.stringify(report)}\n`);
180
+ }
181
+ else {
182
+ ctx.stdout.write(renderDoctorReport(report));
183
+ }
184
+ if (!report.ok)
185
+ process.exitCode = 1;
186
+ });
187
+ }
188
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Pure, ANSI-free, total formatters for the budget/authoring/learning result
3
+ * types. Mirrors `../format.ts` for the versioning subsystem. Never throws.
4
+ */
5
+ import type { BudgetStatusView, CostEstimate, LearnRunReport, LearnSource, LedgerEntry, SkillFrontmatter, AuthoringTestResult as TestResult } from "ultracode-core";
6
+ /** Format a {@link BudgetStatusView} as an indented plain-text report. */
7
+ export declare function formatBudgetStatus(s: BudgetStatusView): string;
8
+ /** Format a {@link CostEstimate} (used standalone by `budget estimate`). */
9
+ export declare function formatCostEstimate(e: CostEstimate): string;
10
+ /** Format a budget ledger ({@link LedgerEntry}[]) as a chronological list. */
11
+ export declare function formatLedger(entries: ReadonlyArray<LedgerEntry>): string;
12
+ /** Format a list of skills ({@link SkillFrontmatter}[]). */
13
+ export declare function formatSkillList(skills: ReadonlyArray<SkillFrontmatter>): string;
14
+ /** Format a {@link TestResult} with a per-check ✓/✗ breakdown. */
15
+ export declare function formatTestResult(t: TestResult): string;
16
+ /** Format a list of learn sources ({@link LearnSource}[]). */
17
+ export declare function formatSourceList(sources: ReadonlyArray<LearnSource>): string;
18
+ /** Format a {@link LearnRunReport} after an executed learn run. */
19
+ export declare function formatLearnReport(r: LearnRunReport): string;
20
+ /** Truncate a free-form block (rationale/diff) for display. */
21
+ export declare function formatBlock(label: string, text: string): string;
22
+ //# sourceMappingURL=ext-format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ext-format.d.ts","sourceRoot":"","sources":["../../src/commands/ext-format.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,mBAAmB,IAAI,UAAU,EAClC,MAAM,gBAAgB,CAAC;AAexB,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAc9D;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAiB1D;AAED,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,GAAG,MAAM,CAWxE;AAED,4DAA4D;AAC5D,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,CAAC,gBAAgB,CAAC,GAAG,MAAM,CAO/E;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAMtD;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,GAAG,MAAM,CAQ5E;AAED,mEAAmE;AACnE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,cAAc,GAAG,MAAM,CAS3D;AAED,+DAA+D;AAC/D,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM/D"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Pure, ANSI-free, total formatters for the budget/authoring/learning result
3
+ * types. Mirrors `../format.ts` for the versioning subsystem. Never throws.
4
+ */
5
+ import { truncate } from "ultracode-shared";
6
+ /** Cap applied to free-form blocks. */
7
+ const MAX_BLOCK_CHARS = 10_000;
8
+ /** Format a USD amount with two decimals (total; clamps non-finite to 0). */
9
+ function usd(n) {
10
+ return `$${(Number.isFinite(n) ? n : 0).toFixed(2)}`;
11
+ }
12
+ /** Format a EUR amount with two decimals. */
13
+ function eur(n) {
14
+ return `${(Number.isFinite(n) ? n : 0).toFixed(2)} EUR`;
15
+ }
16
+ /** Format a {@link BudgetStatusView} as an indented plain-text report. */
17
+ export function formatBudgetStatus(s) {
18
+ const lines = ["Budget Status"];
19
+ if (s.periodStart !== undefined)
20
+ lines.push(` Period start: ${s.periodStart}`);
21
+ lines.push(` Spent: ${usd(s.spentUsd)}`);
22
+ lines.push(` Cap: ${s.capUsd === null ? "(none)" : usd(s.capUsd)}`);
23
+ lines.push(` Remaining: ${usd(s.remainingUsd)}`);
24
+ if (s.entries !== undefined)
25
+ lines.push(` Ledger entries: ${s.entries}`);
26
+ if (s.byCategory && Object.keys(s.byCategory).length > 0) {
27
+ lines.push(" By category:");
28
+ for (const [cat, amount] of Object.entries(s.byCategory)) {
29
+ lines.push(` - ${cat}: ${usd(amount)}`);
30
+ }
31
+ }
32
+ return `${lines.join("\n")}\n`;
33
+ }
34
+ /** Format a {@link CostEstimate} (used standalone by `budget estimate`). */
35
+ export function formatCostEstimate(e) {
36
+ const lines = ["Cost Estimate"];
37
+ if (e.pages !== undefined)
38
+ lines.push(` Pages: ${e.pages}`);
39
+ lines.push(` Tokens: ${e.tokensIn} in / ${e.tokensOut} out`);
40
+ lines.push(` USD range: ${usd(e.usdLow)} – ${usd(e.usdHigh)}`);
41
+ lines.push(` EUR: ${eur(e.eur)} (fx ${e.fxRate})`);
42
+ if (e.byModel.length > 0) {
43
+ lines.push(" By model:");
44
+ for (const m of e.byModel) {
45
+ lines.push(` - ${m.model}: ${m.tokensIn} in / ${m.tokensOut} out → ${usd(m.usd)}`);
46
+ }
47
+ }
48
+ const assumptions = e.assumptions.join("; ").trim();
49
+ if (assumptions !== "") {
50
+ lines.push(` Assumptions: ${truncate(assumptions, 400)}`);
51
+ }
52
+ return `${lines.join("\n")}\n`;
53
+ }
54
+ /** Format a budget ledger ({@link LedgerEntry}[]) as a chronological list. */
55
+ export function formatLedger(entries) {
56
+ if (entries.length === 0)
57
+ return "(no ledger entries)\n";
58
+ const lines = [`Budget History — ${entries.length} entr${entries.length === 1 ? "y" : "ies"}`];
59
+ for (const e of entries) {
60
+ const model = e.model !== undefined ? ` ${e.model}` : "";
61
+ const note = e.note !== undefined && e.note !== "" ? ` — ${truncate(e.note, 120)}` : "";
62
+ lines.push(` ${e.ts} [${e.category}]${model} est ${usd(e.estimatedUsd)} / act ${usd(e.actualUsd)}${note}`);
63
+ }
64
+ return `${lines.join("\n")}\n`;
65
+ }
66
+ /** Format a list of skills ({@link SkillFrontmatter}[]). */
67
+ export function formatSkillList(skills) {
68
+ if (skills.length === 0)
69
+ return "(no skills)\n";
70
+ const lines = [`Skills — ${skills.length}`];
71
+ for (const s of skills) {
72
+ lines.push(` ${s.name} v${s.version} [${s.status}] (${s.catalog}) — ${truncate(s.description, 80)}`);
73
+ }
74
+ return `${lines.join("\n")}\n`;
75
+ }
76
+ /** Format a {@link TestResult} with a per-check ✓/✗ breakdown. */
77
+ export function formatTestResult(t) {
78
+ const lines = [`Test Result — ${t.passed ? "✓ PASSED" : "✗ FAILED"}`];
79
+ for (const c of t.checks) {
80
+ lines.push(` ${c.passed ? "✓" : "✗"} ${c.name} — ${truncate(c.detail, 200)}`);
81
+ }
82
+ return `${lines.join("\n")}\n`;
83
+ }
84
+ /** Format a list of learn sources ({@link LearnSource}[]). */
85
+ export function formatSourceList(sources) {
86
+ if (sources.length === 0)
87
+ return "(no learning sources)\n";
88
+ const lines = [`Learning Sources — ${sources.length}`];
89
+ for (const s of sources) {
90
+ const last = s.lastProcessed !== undefined ? ` last ${s.lastProcessed}` : " never processed";
91
+ lines.push(` ${s.id} [${s.catalog}/${s.schedule}] ${s.url}${last}`);
92
+ }
93
+ return `${lines.join("\n")}\n`;
94
+ }
95
+ /** Format a {@link LearnRunReport} after an executed learn run. */
96
+ export function formatLearnReport(r) {
97
+ const lines = [
98
+ `Learn Run — ${r.sourceId}`,
99
+ ` Pages: ${r.pagesChecked} checked, ${r.notModified} not-modified, ${r.unchanged} unchanged, ${r.processed} processed, ${r.errors} error(s)`,
100
+ ` Skills: ${r.skillsCreated} created, ${r.skillsUpdated} updated`,
101
+ ` Cost: est ${usd(r.estimatedUsd)} / act ${usd(r.actualUsd)}`,
102
+ ` Window: ${r.startedAt} → ${r.finishedAt}`,
103
+ ];
104
+ return `${lines.join("\n")}\n`;
105
+ }
106
+ /** Truncate a free-form block (rationale/diff) for display. */
107
+ export function formatBlock(label, text) {
108
+ if (text.trim() === "")
109
+ return "";
110
+ return `${label}:\n${truncate(text, MAX_BLOCK_CHARS)
111
+ .split(/\r?\n/)
112
+ .map((l) => ` ${l}`)
113
+ .join("\n")}\n`;
114
+ }
115
+ //# sourceMappingURL=ext-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ext-format.js","sourceRoot":"","sources":["../../src/commands/ext-format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Shared helpers for the budget/authoring/learning command groups.
3
+ *
4
+ * The `ultracode-core` `UltraCode` facade now exposes the additive
5
+ * `budget` / `authoring` / `learning` sub-APIs directly (Modules A/B/C of
6
+ * v1.2.0), so these helpers simply create/dispose a facade and narrow to the
7
+ * relevant sub-API — no casts, no ambient shapes.
8
+ */
9
+ import type { BudgetFacade, AuthoringFacade, LearningFacade } from "ultracode-core";
10
+ import type { CliContext, UltraCodeFacade } from "../context.js";
11
+ /** The facade as consumed by the budget/authoring/learning command groups. */
12
+ export type ExtendedFacade = UltraCodeFacade;
13
+ /**
14
+ * Run `fn` against a freshly created facade, always disposing it afterwards
15
+ * (even on throw). Mirrors the `withFacade` pattern of the other command
16
+ * groups.
17
+ */
18
+ export declare function withExtFacade<T>(ctx: CliContext, fn: (facade: ExtendedFacade) => Promise<T>): Promise<T>;
19
+ /** Narrow accessor for the budget sub-API. */
20
+ export declare function budgetOf(facade: ExtendedFacade): BudgetFacade;
21
+ /** Narrow accessor for the authoring sub-API. */
22
+ export declare function authoringOf(facade: ExtendedFacade): AuthoringFacade;
23
+ /** Narrow accessor for the learning sub-API. */
24
+ export declare function learningOf(facade: ExtendedFacade): LearningFacade;
25
+ //# sourceMappingURL=ext-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ext-helpers.d.ts","sourceRoot":"","sources":["../../src/commands/ext-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACpF,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGjE,8EAA8E;AAC9E,MAAM,MAAM,cAAc,GAAG,eAAe,CAAC;AAE7C;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,GAAG,EAAE,UAAU,EACf,EAAE,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,GACzC,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,8CAA8C;AAC9C,wBAAgB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,YAAY,CAE7D;AAED,iDAAiD;AACjD,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,eAAe,CAEnE;AAED,gDAAgD;AAChD,wBAAgB,UAAU,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAEjE"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Shared helpers for the budget/authoring/learning command groups.
3
+ *
4
+ * The `ultracode-core` `UltraCode` facade now exposes the additive
5
+ * `budget` / `authoring` / `learning` sub-APIs directly (Modules A/B/C of
6
+ * v1.2.0), so these helpers simply create/dispose a facade and narrow to the
7
+ * relevant sub-API — no casts, no ambient shapes.
8
+ */
9
+ import { safeDispose } from "../context.js";
10
+ /**
11
+ * Run `fn` against a freshly created facade, always disposing it afterwards
12
+ * (even on throw). Mirrors the `withFacade` pattern of the other command
13
+ * groups.
14
+ */
15
+ export async function withExtFacade(ctx, fn) {
16
+ const facade = await ctx.createFacade({ cwd: ctx.cwd });
17
+ try {
18
+ return await fn(facade);
19
+ }
20
+ finally {
21
+ await safeDispose(facade);
22
+ }
23
+ }
24
+ /** Narrow accessor for the budget sub-API. */
25
+ export function budgetOf(facade) {
26
+ return facade.budget;
27
+ }
28
+ /** Narrow accessor for the authoring sub-API. */
29
+ export function authoringOf(facade) {
30
+ return facade.authoring;
31
+ }
32
+ /** Narrow accessor for the learning sub-API. */
33
+ export function learningOf(facade) {
34
+ return facade.learning;
35
+ }
36
+ //# sourceMappingURL=ext-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ext-helpers.js","sourceRoot":"","sources":["../../src/commands/ext-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAK5C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAe,EACf,EAA0C;IAE1C,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC;IAC1B,CAAC;YAAS,CAAC;QACT,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,QAAQ,CAAC,MAAsB;IAC7C,OAAO,MAAM,CAAC,MAAM,CAAC;AACvB,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,WAAW,CAAC,MAAsB;IAChD,OAAO,MAAM,CAAC,SAAS,CAAC;AAC1B,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,UAAU,CAAC,MAAsB;IAC/C,OAAO,MAAM,CAAC,QAAQ,CAAC;AACzB,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `ultracode learn sources list|add <url>|remove <id>` and
3
+ * `ultracode learn run [sourceId]` / `ultracode learn schedule`.
4
+ *
5
+ * Documentation-learning commands backed by the facade's `learning` sub-API
6
+ * (Module C). `learn run` ALWAYS prints the budget cost report first
7
+ * (`budget.formatReport(estimate, decision)`), then requires explicit approval:
8
+ * `--yes`, or an interactive YES on a TTY. Without a TTY and without `--yes`
9
+ * the run is aborted before any network/LLM work happens.
10
+ *
11
+ * Every subcommand supports `--json` where it makes sense.
12
+ */
13
+ import type { Command } from "commander";
14
+ import type { CliContext } from "../context.js";
15
+ /**
16
+ * Confirmation surface for `learn run` — injectable so tests stay offline and
17
+ * deterministic. The default reads a single line from `ctx.stdin`.
18
+ */
19
+ export interface ConfirmIO {
20
+ /** True when an interactive prompt is possible (a TTY on both ends). */
21
+ interactive: boolean;
22
+ /** Ask a yes/no question; resolves true only for an affirmative answer. */
23
+ confirm(question: string): Promise<boolean>;
24
+ }
25
+ /** Build the default {@link ConfirmIO} from the context's stdin/stdout. */
26
+ export declare function createConfirmIO(ctx: CliContext): ConfirmIO;
27
+ /** Register the `learn` command group on `program`. */
28
+ export declare function registerLearnCommands(program: Command, ctx: CliContext, io?: ConfirmIO): void;
29
+ //# sourceMappingURL=learn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learn.d.ts","sourceRoot":"","sources":["../../src/commands/learn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAqBhD;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,wEAAwE;IACxE,WAAW,EAAE,OAAO,CAAC;IACrB,2EAA2E;IAC3E,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7C;AAID,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,GAAG,EAAE,UAAU,GAAG,SAAS,CAiB1D;AAqBD,uDAAuD;AACvD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,UAAU,EACf,EAAE,GAAE,SAAgC,GACnC,IAAI,CA+HN"}
@@ -0,0 +1,190 @@
1
+ /**
2
+ * `ultracode learn sources list|add <url>|remove <id>` and
3
+ * `ultracode learn run [sourceId]` / `ultracode learn schedule`.
4
+ *
5
+ * Documentation-learning commands backed by the facade's `learning` sub-API
6
+ * (Module C). `learn run` ALWAYS prints the budget cost report first
7
+ * (`budget.formatReport(estimate, decision)`), then requires explicit approval:
8
+ * `--yes`, or an interactive YES on a TTY. Without a TTY and without `--yes`
9
+ * the run is aborted before any network/LLM work happens.
10
+ *
11
+ * Every subcommand supports `--json` where it makes sense.
12
+ */
13
+ import process from "node:process";
14
+ import { ValidationError } from "ultracode-shared";
15
+ import { budgetOf, learningOf, withExtFacade } from "./ext-helpers.js";
16
+ import { formatLearnReport, formatSourceList } from "./ext-format.js";
17
+ const AFFIRMATIVE = new Set(["y", "yes", "j", "ja"]);
18
+ /** Build the default {@link ConfirmIO} from the context's stdin/stdout. */
19
+ export function createConfirmIO(ctx) {
20
+ const interactive = ctx.stdin.isTTY === true && ctx.stdout.isTTY === true;
21
+ return {
22
+ interactive,
23
+ confirm: (question) => new Promise((resolve) => {
24
+ ctx.stdout.write(question);
25
+ const onData = (chunk) => {
26
+ ctx.stdin.removeListener("data", onData);
27
+ ctx.stdin.pause?.();
28
+ const answer = (typeof chunk === "string" ? chunk : chunk.toString("utf8")).trim().toLowerCase();
29
+ resolve(AFFIRMATIVE.has(answer));
30
+ };
31
+ ctx.stdin.resume?.();
32
+ ctx.stdin.on("data", onData);
33
+ }),
34
+ };
35
+ }
36
+ /** Estimate → decision wrapper; falls back to a permissive decision if the
37
+ * facade does not expose `check` (so the report still renders). */
38
+ async function decideFor(facade, estimate) {
39
+ const budget = budgetOf(facade);
40
+ if (typeof budget.check === "function") {
41
+ return budget.check(estimate, "learning");
42
+ }
43
+ return {
44
+ allowed: true,
45
+ requiresApproval: true,
46
+ remainingUsd: 0,
47
+ capUsd: null,
48
+ reason: "budget.check unavailable; manual approval required",
49
+ };
50
+ }
51
+ /** Register the `learn` command group on `program`. */
52
+ export function registerLearnCommands(program, ctx, io = createConfirmIO(ctx)) {
53
+ const learn = program.command("learn").description("learn from documentation sources");
54
+ /* ---------------------------- sources ----------------------------- */
55
+ const sources = learn.command("sources").description("manage learning sources");
56
+ sources
57
+ .command("list")
58
+ .description("list registered documentation sources")
59
+ .option("--json", "output as JSON")
60
+ .action(async (opts) => withExtFacade(ctx, async (facade) => {
61
+ const list = await learningOf(facade).sources.list();
62
+ ctx.stdout.write(opts.json ? `${JSON.stringify(list)}\n` : formatSourceList(list));
63
+ }));
64
+ sources
65
+ .command("add")
66
+ .description("register a new documentation source")
67
+ .argument("<url>", "documentation root URL or sitemap")
68
+ .option("--catalog <catalog>", "catalog to file generated skills under")
69
+ .option("--schedule <schedule>", 'refresh schedule ("on-demand" | "annual" | cron)')
70
+ .option("--scope <scope>", "URL path scope filter")
71
+ .option("--locale <locale>", "default locale (e.g. de-de)")
72
+ .option("--json", "output as JSON")
73
+ .action(async (url, opts) => withExtFacade(ctx, async (facade) => {
74
+ if (url.trim() === "")
75
+ throw new ValidationError("Source URL must not be empty.");
76
+ const source = await learningOf(facade).sources.add({
77
+ url,
78
+ ...(opts.catalog !== undefined ? { catalog: opts.catalog } : {}),
79
+ ...(opts.schedule !== undefined ? { schedule: opts.schedule } : {}),
80
+ ...(opts.scope !== undefined ? { scope: opts.scope } : {}),
81
+ ...(opts.locale !== undefined ? { localeDefault: opts.locale } : {}),
82
+ });
83
+ ctx.stdout.write(opts.json ? `${JSON.stringify(source)}\n` : `✓ added source ${source.id} (${source.url})\n`);
84
+ }));
85
+ sources
86
+ .command("remove")
87
+ .description("remove a registered documentation source")
88
+ .argument("<id>", "source id")
89
+ .option("--json", "output as JSON")
90
+ .action(async (id, opts) => withExtFacade(ctx, async (facade) => {
91
+ const removed = await learningOf(facade).sources.remove(id);
92
+ if (opts.json) {
93
+ ctx.stdout.write(`${JSON.stringify({ id, removed })}\n`);
94
+ }
95
+ else {
96
+ ctx.stdout.write(removed ? `✓ removed source ${id}\n` : `source ${id} not found\n`);
97
+ }
98
+ if (!removed)
99
+ process.exitCode = 1;
100
+ }));
101
+ /* ------------------------------ run ------------------------------- */
102
+ learn
103
+ .command("run")
104
+ .description("plan and (after approval) run learning for a source — shows the cost report first")
105
+ .argument("[sourceId]", "source id; required unless exactly one source exists")
106
+ .option("--yes", "approve the run without an interactive prompt")
107
+ .option("--json", "output as JSON")
108
+ .action(async (sourceId, opts) => withExtFacade(ctx, async (facade) => {
109
+ const learning = learningOf(facade);
110
+ const id = await resolveSourceId(facade, sourceId);
111
+ const plan = await learning.plan(id);
112
+ const decision = await decideFor(facade, plan.estimate);
113
+ // ALWAYS show the cost report before doing anything else.
114
+ ctx.stdout.write(budgetOf(facade).formatReport(plan.estimate, decision));
115
+ if (!decision.allowed) {
116
+ ctx.stdout.write(`✗ run blocked by budget: ${decision.reason}\n`);
117
+ process.exitCode = 1;
118
+ return;
119
+ }
120
+ const approved = await approveRun(ctx, io, opts.yes === true);
121
+ if (!approved) {
122
+ // Non-TTY without --yes, or an explicit "no": abort WITHOUT network/LLM.
123
+ process.exitCode = 1;
124
+ return;
125
+ }
126
+ const report = await learning.run(id, {
127
+ approved: true,
128
+ onProgress: (p) => {
129
+ if (opts.json !== true && p.total > 0) {
130
+ ctx.stdout.write(` …${p.done}/${p.total}${p.url ? ` ${p.url}` : ""}\n`);
131
+ }
132
+ },
133
+ });
134
+ ctx.stdout.write(opts.json ? `${JSON.stringify(report)}\n` : formatLearnReport(report));
135
+ }));
136
+ /* ---------------------------- schedule ---------------------------- */
137
+ learn
138
+ .command("schedule")
139
+ .description("show each source's refresh schedule")
140
+ .option("--json", "output as JSON")
141
+ .action(async (opts) => withExtFacade(ctx, async (facade) => {
142
+ const list = await learningOf(facade).sources.list();
143
+ const rows = list.map((s) => ({
144
+ id: s.id,
145
+ schedule: s.schedule,
146
+ lastProcessed: s.lastProcessed ?? null,
147
+ }));
148
+ if (opts.json) {
149
+ ctx.stdout.write(`${JSON.stringify(rows)}\n`);
150
+ return;
151
+ }
152
+ if (rows.length === 0) {
153
+ ctx.stdout.write("(no learning sources)\n");
154
+ return;
155
+ }
156
+ ctx.stdout.write("Learning Schedule\n");
157
+ for (const r of rows) {
158
+ ctx.stdout.write(` ${r.id}: ${r.schedule} (last ${r.lastProcessed ?? "never"})\n`);
159
+ }
160
+ }));
161
+ }
162
+ /** Resolve the source id, defaulting to the only source when omitted. */
163
+ async function resolveSourceId(facade, sourceId) {
164
+ if (sourceId !== undefined && sourceId.trim() !== "")
165
+ return sourceId;
166
+ const list = await learningOf(facade).sources.list();
167
+ if (list.length === 1 && list[0] !== undefined)
168
+ return list[0].id;
169
+ if (list.length === 0)
170
+ throw new ValidationError("No learning sources registered. Add one with `learn sources add`.");
171
+ throw new ValidationError(`Multiple sources registered; specify one: ${list.map((s) => s.id).join(", ")}`);
172
+ }
173
+ /**
174
+ * Decide whether the run is approved: `--yes` short-circuits to true; otherwise
175
+ * an interactive YES is required. Without a TTY and without `--yes`, a hint is
176
+ * printed and the run is refused.
177
+ */
178
+ async function approveRun(ctx, io, yes) {
179
+ if (yes)
180
+ return true;
181
+ if (!io.interactive) {
182
+ ctx.stdout.write("Refusing to start without approval: pass --yes (no interactive terminal available).\n");
183
+ return false;
184
+ }
185
+ const ok = await io.confirm("Proceed with the learn run? [y/N] ");
186
+ if (!ok)
187
+ ctx.stdout.write("Aborted.\n");
188
+ return ok;
189
+ }
190
+ //# sourceMappingURL=learn.js.map