@keel_flow/cli 0.2.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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/dist/build.d.ts +57 -0
  4. package/dist/build.d.ts.map +1 -0
  5. package/dist/build.js +350 -0
  6. package/dist/build.js.map +1 -0
  7. package/dist/claude-auth.d.ts +16 -0
  8. package/dist/claude-auth.d.ts.map +1 -0
  9. package/dist/claude-auth.js +75 -0
  10. package/dist/claude-auth.js.map +1 -0
  11. package/dist/doctor.d.ts +18 -0
  12. package/dist/doctor.d.ts.map +1 -0
  13. package/dist/doctor.js +547 -0
  14. package/dist/doctor.js.map +1 -0
  15. package/dist/git.d.ts +4 -0
  16. package/dist/git.d.ts.map +1 -0
  17. package/dist/git.js +138 -0
  18. package/dist/git.js.map +1 -0
  19. package/dist/goals.d.ts +34 -0
  20. package/dist/goals.d.ts.map +1 -0
  21. package/dist/goals.js +218 -0
  22. package/dist/goals.js.map +1 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +1015 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/kb-reindex.d.ts +31 -0
  28. package/dist/kb-reindex.d.ts.map +1 -0
  29. package/dist/kb-reindex.js +71 -0
  30. package/dist/kb-reindex.js.map +1 -0
  31. package/dist/keys.d.ts +26 -0
  32. package/dist/keys.d.ts.map +1 -0
  33. package/dist/keys.js +209 -0
  34. package/dist/keys.js.map +1 -0
  35. package/dist/learn.d.ts +37 -0
  36. package/dist/learn.d.ts.map +1 -0
  37. package/dist/learn.js +274 -0
  38. package/dist/learn.js.map +1 -0
  39. package/dist/lifecycle.d.ts +27 -0
  40. package/dist/lifecycle.d.ts.map +1 -0
  41. package/dist/lifecycle.js +193 -0
  42. package/dist/lifecycle.js.map +1 -0
  43. package/dist/load-ts.d.ts +2 -0
  44. package/dist/load-ts.d.ts.map +1 -0
  45. package/dist/load-ts.js +20 -0
  46. package/dist/load-ts.js.map +1 -0
  47. package/dist/map-repo.d.ts +15 -0
  48. package/dist/map-repo.d.ts.map +1 -0
  49. package/dist/map-repo.js +36 -0
  50. package/dist/map-repo.js.map +1 -0
  51. package/dist/memory.d.ts +23 -0
  52. package/dist/memory.d.ts.map +1 -0
  53. package/dist/memory.js +84 -0
  54. package/dist/memory.js.map +1 -0
  55. package/dist/orchestrate.d.ts +61 -0
  56. package/dist/orchestrate.d.ts.map +1 -0
  57. package/dist/orchestrate.js +556 -0
  58. package/dist/orchestrate.js.map +1 -0
  59. package/dist/reflect.d.ts +12 -0
  60. package/dist/reflect.d.ts.map +1 -0
  61. package/dist/reflect.js +67 -0
  62. package/dist/reflect.js.map +1 -0
  63. package/dist/report.d.ts +3 -0
  64. package/dist/report.d.ts.map +1 -0
  65. package/dist/report.js +56 -0
  66. package/dist/report.js.map +1 -0
  67. package/dist/rules.d.ts +16 -0
  68. package/dist/rules.d.ts.map +1 -0
  69. package/dist/rules.js +65 -0
  70. package/dist/rules.js.map +1 -0
  71. package/dist/telemetry-helper.d.ts +12 -0
  72. package/dist/telemetry-helper.d.ts.map +1 -0
  73. package/dist/telemetry-helper.js +40 -0
  74. package/dist/telemetry-helper.js.map +1 -0
  75. package/dist/up.d.ts +97 -0
  76. package/dist/up.d.ts.map +1 -0
  77. package/dist/up.js +656 -0
  78. package/dist/up.js.map +1 -0
  79. package/package.json +58 -0
package/dist/index.js ADDED
@@ -0,0 +1,1015 @@
1
+ #!/usr/bin/env node
2
+ import { createReadStream, statSync, readdirSync, writeFileSync, readFileSync } from "fs";
3
+ import { createHash } from "crypto";
4
+ import { resolve, join, extname } from "path";
5
+ import { pathToFileURL } from "url";
6
+ import { Command } from "commander";
7
+ import pc from "picocolors";
8
+ import { createArchitecture, verify } from "@keel_flow/core";
9
+ import { createSpecChecker, createProvider } from "@keel_flow/runtime";
10
+ import { ArchitectureMapSchema } from "@keel_flow/schema";
11
+ import { buildDiffFromGit, resolveDiffRef } from "./git.js";
12
+ import { importTsConfig } from "./load-ts.js";
13
+ import { printVerifyReport } from "./report.js";
14
+ import { runDoctor, printDoctorReport, collectEnvPreflight } from "./doctor.js";
15
+ import { envKeysProvider, printKeysList, runKeySet, runKeyTest, runKeyRemove, runSetupWalkthrough, } from "./keys.js";
16
+ import { createCliEmitter } from "./telemetry-helper.js";
17
+ import { runOrchestrate } from "./orchestrate.js";
18
+ import { runMapRepo } from "./map-repo.js";
19
+ import { runKbReindex, runKbStats, printKbStats } from "./kb-reindex.js";
20
+ import { runBuildAbort, runBuildApprove, runBuildExecute, runBuildReject, runBuildResume, runBuildStart, runBuildStatus, } from "./build.js";
21
+ import { runReflect, runReflectList } from "./reflect.js";
22
+ import { runMemoryAdd, runMemoryList, runMemoryRemove, runMemoryShow, } from "./memory.js";
23
+ import { runLearn, runLearnApply, runLearnList } from "./learn.js";
24
+ import { runUp, runDown, runStatus, runOpen, runRestart } from "./up.js";
25
+ import { CLAUDE_INSTALL_HINT, probeClaudeAuth, triggerClaudeLogin } from "./claude-auth.js";
26
+ import { runGoalAbandon, runGoalApprove, runGoalCreate, runGoalEvaluate, runGoalList, runGoalPause, runGoalReject, runGoalResume, runGoalStatus, } from "./goals.js";
27
+ import { runRulesList, runRulesAdd } from "./rules.js";
28
+ const program = new Command();
29
+ program.name("keel").description("Keel framework CLI").version("0.2.0");
30
+ program
31
+ .command("verify")
32
+ .description("Run the Keel verify gate against a diff")
33
+ .option("--diff <ref>", "Git ref range to diff (e.g. origin/main..HEAD)")
34
+ .option("--cwd <path>", "Working directory", process.cwd())
35
+ .option("--spec <summary>", "Spec summary for LLM-driven spec compliance check")
36
+ .option("--json <path>", "Write VerifyResult as JSON to this file path")
37
+ .action(async (opts) => {
38
+ const cwd = resolve(opts.cwd);
39
+ const envWarnings = collectEnvPreflight();
40
+ if (envWarnings.length > 0) {
41
+ process.stderr.write(pc.yellow("⚠ Environment preflight\n"));
42
+ for (const w of envWarnings) {
43
+ process.stderr.write(pc.yellow(` · ${w}\n`));
44
+ }
45
+ process.stderr.write("\n");
46
+ }
47
+ const { emitter } = createCliEmitter({ command: "verify", cwd });
48
+ const ref = resolveDiffRef(opts.diff, cwd);
49
+ const specSummaryEarly = opts.spec ?? process.env["KEEL_SPEC_SUMMARY"];
50
+ emitter?.emit({
51
+ sessionId: "",
52
+ parentEventId: null,
53
+ workspaceId: null,
54
+ triggeredBy: { kind: "user" },
55
+ kind: "session.start",
56
+ payload: { command: "verify", ref, hasSpec: !!specSummaryEarly },
57
+ });
58
+ let diff;
59
+ try {
60
+ diff = buildDiffFromGit(ref, cwd);
61
+ }
62
+ catch (err) {
63
+ process.stderr.write(pc.red(`Failed to build diff from git: ${String(err)}\n`));
64
+ emitter?.emit({
65
+ sessionId: "",
66
+ parentEventId: null,
67
+ workspaceId: null,
68
+ triggeredBy: { kind: "user" },
69
+ kind: "session.end",
70
+ payload: { exitCode: 2, error: "git-diff-failed" },
71
+ });
72
+ if (emitter)
73
+ await emitter.close();
74
+ process.exit(2);
75
+ }
76
+ const archPath = join(cwd, "architecture/data.ts");
77
+ const archUrl = pathToFileURL(archPath).href;
78
+ const principlesUrl = pathToFileURL(join(cwd, "principles/index.ts")).href;
79
+ let architecture;
80
+ let architectureSource = "";
81
+ let principles;
82
+ try {
83
+ const archMod = await importTsConfig(archUrl);
84
+ architecture = createArchitecture((archMod.default ?? archMod));
85
+ architectureSource = readFileSync(archPath, "utf-8");
86
+ }
87
+ catch (err) {
88
+ process.stderr.write(pc.red(`Failed to load architecture/data.ts: ${String(err)}\n`));
89
+ emitter?.emit({
90
+ sessionId: "",
91
+ parentEventId: null,
92
+ workspaceId: null,
93
+ triggeredBy: { kind: "user" },
94
+ kind: "session.end",
95
+ payload: { exitCode: 2, error: "arch-load-failed" },
96
+ });
97
+ if (emitter)
98
+ await emitter.close();
99
+ process.exit(2);
100
+ }
101
+ try {
102
+ const principlesMod = await importTsConfig(principlesUrl);
103
+ const registry = (principlesMod.keelPrinciples ?? principlesMod.default);
104
+ principles = registry;
105
+ }
106
+ catch (err) {
107
+ process.stderr.write(pc.red(`Failed to load principles/index.ts: ${String(err)}\n`));
108
+ emitter?.emit({
109
+ sessionId: "",
110
+ parentEventId: null,
111
+ workspaceId: null,
112
+ triggeredBy: { kind: "user" },
113
+ kind: "session.end",
114
+ payload: { exitCode: 2, error: "principles-load-failed" },
115
+ });
116
+ if (emitter)
117
+ await emitter.close();
118
+ process.exit(2);
119
+ }
120
+ const specSummary = opts.spec ?? process.env["KEEL_SPEC_SUMMARY"];
121
+ let specChecker;
122
+ if (specSummary) {
123
+ const providerKind = (process.env["KEEL_PROVIDER_KIND"] ?? "anthropic");
124
+ const apiKey = providerKind === "anthropic"
125
+ ? process.env["ANTHROPIC_API_KEY"]
126
+ : providerKind === "openai-compatible"
127
+ ? process.env["OPENAI_API_KEY"]
128
+ : undefined;
129
+ // No key → leave specChecker undefined so spec compliance lands as PENDING
130
+ // (non-blocking) rather than hard-failing the gate. This matches the local
131
+ // no-key path and keeps CI green without a token; set ANTHROPIC_API_KEY (or
132
+ // OPENAI_API_KEY for openai-compatible) to run real spec checks.
133
+ // claude-bridge needs no key: it rides Claude subscription auth.
134
+ if (!apiKey && providerKind !== "claude-bridge") {
135
+ process.stderr.write(pc.yellow(providerKind === "anthropic"
136
+ ? "Spec check requested but ANTHROPIC_API_KEY is not set — marking spec compliance PENDING (non-blocking)\n"
137
+ : "Spec check requested but OPENAI_API_KEY is not set — marking spec compliance PENDING (non-blocking)\n"));
138
+ }
139
+ else {
140
+ const baseURL = process.env["OPENAI_BASE_URL"];
141
+ const defaultModel = process.env["KEEL_PROVIDER_MODEL"];
142
+ const provider = createProvider({
143
+ kind: providerKind,
144
+ apiKey: apiKey ?? "",
145
+ ...(baseURL ? { baseURL } : {}),
146
+ ...(defaultModel ? { defaultModel } : {}),
147
+ });
148
+ specChecker = createSpecChecker({ provider });
149
+ }
150
+ }
151
+ const result = await verify({
152
+ diff,
153
+ architecture: architecture.map,
154
+ architectureSource,
155
+ principles,
156
+ specChecker,
157
+ specSummary,
158
+ // Forward the resolved working directory so checkTestsAndLint shells out
159
+ // in the right repo and every principle.check resolves diff paths against
160
+ // --cwd rather than process.cwd() (orchestrate/scheduler run verify with a
161
+ // cwd that is not the shell's).
162
+ options: { cwd },
163
+ });
164
+ printVerifyReport(result);
165
+ if (opts.json) {
166
+ const jsonPath = resolve(opts.json);
167
+ writeFileSync(jsonPath, JSON.stringify({ ...result, exitCode: result.exitCode }, null, 2), "utf-8");
168
+ }
169
+ if (emitter) {
170
+ const allViolations = [
171
+ ...result.checks.spec.violations,
172
+ ...result.checks.architecture.violations,
173
+ ...result.checks.principles.violations,
174
+ ...result.checks["tests-lint"].violations,
175
+ ];
176
+ const criticalCount = allViolations.filter((v) => v.severity === "critical").length;
177
+ const warningCount = allViolations.filter((v) => v.severity === "warning").length;
178
+ emitter.emit({
179
+ sessionId: "",
180
+ parentEventId: null,
181
+ workspaceId: null,
182
+ triggeredBy: { kind: "user" },
183
+ kind: "verify.run",
184
+ payload: {
185
+ exitCode: result.exitCode,
186
+ checks: {
187
+ spec: result.checks.spec.passed,
188
+ architecture: result.checks.architecture.passed,
189
+ principles: result.checks.principles.passed,
190
+ "tests-lint": result.checks["tests-lint"].passed,
191
+ },
192
+ criticalCount,
193
+ warningCount,
194
+ },
195
+ });
196
+ for (const checkName of ["spec", "architecture", "principles", "tests-lint"]) {
197
+ const check = result.checks[checkName];
198
+ emitter.emit({
199
+ sessionId: "",
200
+ parentEventId: null,
201
+ workspaceId: null,
202
+ triggeredBy: { kind: "user" },
203
+ kind: "verify.check",
204
+ payload: {
205
+ name: checkName,
206
+ passed: check.passed,
207
+ violations: check.violations.length,
208
+ },
209
+ });
210
+ }
211
+ emitter.emit({
212
+ sessionId: "",
213
+ parentEventId: null,
214
+ workspaceId: null,
215
+ triggeredBy: { kind: "user" },
216
+ kind: "session.end",
217
+ payload: { exitCode: result.exitCode },
218
+ });
219
+ await emitter.close();
220
+ }
221
+ process.exit(result.exitCode);
222
+ });
223
+ program
224
+ .command("map")
225
+ .description("Print or validate the architecture map (or the auto-derived repo-map with --repo)")
226
+ .option("--check", "Validate architecture/data.ts against schema, exit non-zero if invalid")
227
+ .option("--cwd <path>", "Working directory", process.cwd())
228
+ .option("--repo", "Emit the auto-derived repo-map (tree-sitter-style symbol summary)")
229
+ .option("--no-cache", "Skip the .keel/repo-map.cache.json cache")
230
+ .option("--output <path>", "Write output to a file instead of stdout")
231
+ .option("--json", "Emit structured JSON (only with --repo)")
232
+ .option("--max-tokens <n>", "Token budget for the repo-map summary", (v) => parseInt(v, 10))
233
+ .action(async (opts) => {
234
+ const cwd = resolve(opts.cwd);
235
+ if (opts.repo) {
236
+ try {
237
+ await runMapRepo({
238
+ cwd,
239
+ noCache: opts.cache === false,
240
+ ...(opts.output ? { output: opts.output } : {}),
241
+ ...(opts.json ? { json: true } : {}),
242
+ ...(opts.maxTokens !== undefined ? { maxTokens: opts.maxTokens } : {}),
243
+ });
244
+ return;
245
+ }
246
+ catch (err) {
247
+ process.stderr.write(pc.red(`map --repo failed: ${err instanceof Error ? err.message : String(err)}\n`));
248
+ process.exit(2);
249
+ }
250
+ }
251
+ const archUrl = pathToFileURL(join(cwd, "architecture/data.ts")).href;
252
+ let raw;
253
+ try {
254
+ const mod = await importTsConfig(archUrl);
255
+ raw = mod.default ?? mod;
256
+ }
257
+ catch (err) {
258
+ process.stderr.write(pc.red(`Failed to load architecture/data.ts: ${String(err)}\n`));
259
+ process.exit(2);
260
+ }
261
+ if (opts.check) {
262
+ const result = ArchitectureMapSchema.safeParse(raw);
263
+ if (!result.success) {
264
+ process.stderr.write(pc.red("architecture/data.ts is invalid:\n"));
265
+ for (const issue of result.error.issues) {
266
+ process.stderr.write(pc.red(` ${issue.path.join(".")} — ${issue.message}\n`));
267
+ }
268
+ process.exit(1);
269
+ }
270
+ process.stdout.write(pc.green("architecture/data.ts is valid\n"));
271
+ return;
272
+ }
273
+ const parsed = ArchitectureMapSchema.parse(raw);
274
+ process.stdout.write(pc.bold(`Architecture Map (schema v${parsed.schemaVersion})\n`));
275
+ process.stdout.write(pc.dim("─".repeat(50) + "\n"));
276
+ for (const layer of parsed.layers) {
277
+ const nodes = parsed.nodes.filter((n) => n.layer === layer.id);
278
+ process.stdout.write(pc.bold(`\n[${layer.label}]\n`));
279
+ for (const node of nodes) {
280
+ const conns = parsed.connections.filter((c) => c.from === node.id || c.to === node.id).length;
281
+ process.stdout.write(` ${pc.cyan(node.id)} ${pc.dim(node.description)}\n`);
282
+ process.stdout.write(` connections: ${conns}\n`);
283
+ }
284
+ }
285
+ process.stdout.write(pc.dim("\n" + "─".repeat(50) + "\n"));
286
+ process.stdout.write(`Total nodes: ${parsed.nodes.length}\n`);
287
+ process.stdout.write(`Total connections: ${parsed.connections.length}\n`);
288
+ process.stdout.write(`Contexts: ${parsed.contexts.map((c) => c.label).join(", ")}\n`);
289
+ });
290
+ const principlesCmd = program.command("principles").description("Manage principles");
291
+ principlesCmd
292
+ .command("list")
293
+ .description("List principles from principles/index.ts")
294
+ .option("--severity <level>", "Filter by severity (critical|warning|info)")
295
+ .option("--cwd <path>", "Working directory", process.cwd())
296
+ .action(async (opts) => {
297
+ const cwd = resolve(opts.cwd);
298
+ const principlesUrl = pathToFileURL(join(cwd, "principles/index.ts")).href;
299
+ let registry;
300
+ try {
301
+ const mod = await importTsConfig(principlesUrl);
302
+ registry = (mod.keelPrinciples ?? mod.default);
303
+ }
304
+ catch (err) {
305
+ process.stderr.write(pc.red(`Failed to load principles/index.ts: ${String(err)}\n`));
306
+ process.exit(2);
307
+ }
308
+ let principles = registry.all();
309
+ if (opts.severity) {
310
+ principles = principles.filter((p) => p.severity === opts.severity);
311
+ }
312
+ const byPack = new Map();
313
+ const ungrouped = [];
314
+ for (const p of principles) {
315
+ const packId = "packId" in p && typeof p.packId === "string" ? p.packId : undefined;
316
+ if (packId) {
317
+ if (!byPack.has(packId))
318
+ byPack.set(packId, []);
319
+ byPack.get(packId).push(p);
320
+ }
321
+ else {
322
+ ungrouped.push(p);
323
+ }
324
+ }
325
+ const printPrinciple = (p) => {
326
+ const sev = p.severity === "critical"
327
+ ? pc.red(p.severity)
328
+ : p.severity === "warning"
329
+ ? pc.yellow(p.severity)
330
+ : pc.cyan(p.severity);
331
+ process.stdout.write(` ${pc.bold(p.id)} [${sev}]\n`);
332
+ process.stdout.write(` ${p.description}\n`);
333
+ if (p.check)
334
+ process.stdout.write(pc.dim(" (has check function)\n"));
335
+ };
336
+ for (const [packId, ps] of byPack) {
337
+ process.stdout.write(pc.bold(`\nPack: ${packId}\n`));
338
+ for (const p of ps)
339
+ printPrinciple(p);
340
+ }
341
+ if (ungrouped.length > 0) {
342
+ if (byPack.size > 0)
343
+ process.stdout.write(pc.bold("\nUngrouped\n"));
344
+ for (const p of ungrouped)
345
+ printPrinciple(p);
346
+ }
347
+ process.stdout.write(`\n${principles.length} principle(s) listed\n`);
348
+ });
349
+ const rulesCmd = program
350
+ .command("rules")
351
+ .description("Manage project command rules (rules/commands.ts)");
352
+ rulesCmd
353
+ .command("list", { isDefault: true })
354
+ .description("List command rules declared in rules/commands.ts")
355
+ .option("--cwd <path>", "Working directory", process.cwd())
356
+ .action(async (opts) => {
357
+ try {
358
+ await runRulesList({ cwd: resolve(opts.cwd) });
359
+ }
360
+ catch (err) {
361
+ process.stderr.write(pc.red(`keel rules list failed: ${String(err)}\n`));
362
+ process.exit(2);
363
+ }
364
+ });
365
+ rulesCmd
366
+ .command("add <pattern>")
367
+ .description('Add a command rule, e.g. "pip,pip3" (comma/pipe = alternatives, space = token positions)')
368
+ .requiredOption("--justification <text>", "Why the rule exists / what to do instead")
369
+ .option("--decision <decision>", "forbidden | discouraged", "forbidden")
370
+ .option("--cwd <path>", "Working directory", process.cwd())
371
+ .action(async (pattern, opts) => {
372
+ if (opts.decision !== "forbidden" && opts.decision !== "discouraged") {
373
+ process.stderr.write(pc.red(`--decision must be "forbidden" or "discouraged", got "${opts.decision}"\n`));
374
+ process.exit(2);
375
+ }
376
+ try {
377
+ await runRulesAdd({
378
+ cwd: resolve(opts.cwd),
379
+ pattern,
380
+ decision: opts.decision,
381
+ justification: opts.justification,
382
+ });
383
+ }
384
+ catch (err) {
385
+ process.stderr.write(pc.red(`keel rules add failed: ${String(err)}\n`));
386
+ process.exit(2);
387
+ }
388
+ });
389
+ const kbCmd = program.command("kb").description("Knowledge base operations");
390
+ kbCmd
391
+ .command("upload <path>")
392
+ .description("Upload a file or directory to the workspace KB")
393
+ .option("--workspace <id>", "Workspace ID")
394
+ .action(async (filePath, _opts) => {
395
+ const abs = resolve(filePath);
396
+ let entries;
397
+ try {
398
+ const stat = statSync(abs);
399
+ if (stat.isDirectory()) {
400
+ entries = readdirSync(abs)
401
+ .map((f) => join(abs, f))
402
+ .filter((f) => {
403
+ const ext = extname(f).toLowerCase();
404
+ return ext === ".md" || ext === ".txt" || ext === ".pdf";
405
+ });
406
+ }
407
+ else {
408
+ entries = [abs];
409
+ }
410
+ }
411
+ catch {
412
+ process.stderr.write(pc.red(`Cannot access path: ${abs}\n`));
413
+ process.exit(1);
414
+ }
415
+ for (const entry of entries) {
416
+ const ext = extname(entry).toLowerCase();
417
+ if (ext === ".pdf") {
418
+ // PDF text extraction is not wired into the CLI upload path yet. Skip
419
+ // this entry and continue the batch rather than aborting partway and
420
+ // leaving earlier files uploaded but later ones silently dropped.
421
+ process.stderr.write(pc.yellow(`Skipping ${entry}: PDF upload is not supported via the CLI yet.\n`));
422
+ continue;
423
+ }
424
+ const hash = await fileHash(entry);
425
+ const size = statSync(entry).size;
426
+ process.stdout.write(`${entry} size=${size} sha256=${hash}\n`);
427
+ const workspaceUrl = process.env["KEEL_WORKSPACE_URL"];
428
+ if (!workspaceUrl) {
429
+ process.stdout.write(pc.dim("KB upload will POST to workspace API once configured. Add KEEL_WORKSPACE_URL env var to enable.\n"));
430
+ }
431
+ else {
432
+ const { readFileSync } = await import("fs");
433
+ const content = readFileSync(entry, "utf-8");
434
+ const res = await fetch(`${workspaceUrl}/api/kb/ingest`, {
435
+ method: "POST",
436
+ headers: { "Content-Type": "application/json" },
437
+ body: JSON.stringify({ source: entry, content }),
438
+ });
439
+ if (!res.ok) {
440
+ process.stderr.write(pc.red(`Upload failed: ${res.status} ${res.statusText}\n`));
441
+ process.exit(1);
442
+ }
443
+ process.stdout.write(pc.green(`Uploaded: ${entry}\n`));
444
+ }
445
+ }
446
+ });
447
+ kbCmd
448
+ .command("reindex")
449
+ .description("Re-embed all chunks with the active embedding model")
450
+ .option("--workspace <id>", "Workspace ID (defaults to KEEL_WORKSPACE_ID)")
451
+ .option("--model <name>", "Override the embedding model (voyage-context-3 | text-embedding-3-small | all-MiniLM-L6-v2)")
452
+ .action(async (opts) => {
453
+ try {
454
+ const result = await runKbReindex({
455
+ ...(opts.workspace ? { workspaceId: opts.workspace } : {}),
456
+ ...(opts.model ? { model: opts.model } : {}),
457
+ });
458
+ process.stdout.write(pc.green(`Reindex complete: ${result.reindexed} chunks re-embedded with ${result.model}` +
459
+ (result.skipped > 0 ? ` (${result.skipped} skipped)` : "") +
460
+ "\n"));
461
+ }
462
+ catch (err) {
463
+ process.stderr.write(pc.red(`kb reindex failed: ${err instanceof Error ? err.message : String(err)}\n`));
464
+ process.exit(2);
465
+ }
466
+ });
467
+ kbCmd
468
+ .command("stats")
469
+ .description("Show KB stats: docs, chunks, active embedder, rerank state")
470
+ .option("--workspace <id>", "Workspace ID (defaults to KEEL_WORKSPACE_ID)")
471
+ .action(async (opts) => {
472
+ try {
473
+ const stats = await runKbStats({
474
+ ...(opts.workspace ? { workspaceId: opts.workspace } : {}),
475
+ });
476
+ printKbStats(stats);
477
+ }
478
+ catch (err) {
479
+ process.stderr.write(pc.red(`kb stats failed: ${err instanceof Error ? err.message : String(err)}\n`));
480
+ process.exit(2);
481
+ }
482
+ });
483
+ program
484
+ .command("doctor")
485
+ .description("Diagnose environment + Keel project setup")
486
+ .option("--cwd <path>", "Working directory", process.cwd())
487
+ .action(async (opts) => {
488
+ const report = await runDoctor({ cwd: resolve(opts.cwd) });
489
+ printDoctorReport(report);
490
+ process.exit(report.exitCode);
491
+ });
492
+ program
493
+ .command("setup")
494
+ .description("Interactive walkthrough to configure required API keys and secrets")
495
+ .action(async () => {
496
+ await runSetupWalkthrough(envKeysProvider());
497
+ });
498
+ const keysCmd = program.command("keys").description("Manage API keys and secrets");
499
+ keysCmd
500
+ .command("list")
501
+ .description("List all known keys with status")
502
+ .action(async () => {
503
+ await printKeysList(envKeysProvider());
504
+ });
505
+ keysCmd
506
+ .command("set <name> [value]")
507
+ .description("Set a key (prompts for value if not given)")
508
+ .action(async (name, value) => {
509
+ await runKeySet(envKeysProvider(), name, value);
510
+ });
511
+ keysCmd
512
+ .command("test <name>")
513
+ .description("Validate a key against its provider's auth endpoint")
514
+ .action(async (name) => {
515
+ await runKeyTest(envKeysProvider(), name);
516
+ });
517
+ keysCmd
518
+ .command("rm <name>")
519
+ .description("Remove a key")
520
+ .action(async (name) => {
521
+ await runKeyRemove(envKeysProvider(), name);
522
+ });
523
+ program
524
+ .command("orchestrate <task>")
525
+ .description("Run the Keel orchestrator agent against a task")
526
+ .option("--cwd <path>", "Working directory", process.cwd())
527
+ .option("--provider <kind>", "Provider kind (anthropic | openai-compatible)")
528
+ .option("--model <id>", "Model id (provider-specific)")
529
+ .option("--max-iterations <n>", "Max orchestrator iterations", (v) => parseInt(v, 10))
530
+ .option("--no-questions", "Skip clarifying questions")
531
+ .option("--dry-run", "Plan only; skip real writes")
532
+ .option("--json <path>", "Write the final result JSON to this path")
533
+ .action(async (task, opts) => {
534
+ try {
535
+ const result = await runOrchestrate({
536
+ task,
537
+ cwd: opts.cwd,
538
+ ...(opts.provider ? { provider: opts.provider } : {}),
539
+ ...(opts.model ? { model: opts.model } : {}),
540
+ ...(opts.maxIterations !== undefined ? { maxIterations: opts.maxIterations } : {}),
541
+ noQuestions: opts.questions === false,
542
+ ...(opts.dryRun ? { dryRun: true } : {}),
543
+ ...(opts.json ? { json: opts.json } : {}),
544
+ });
545
+ process.stdout.write(`\n${result.finalMessage}\n`);
546
+ process.stdout.write(pc.dim(`\nsession=${result.sessionId} iterations=${result.iterations} toolCalls=${result.toolCalls} verifyExitCode=${String(result.verifyExitCode)}\n`));
547
+ process.exit(result.verifyExitCode ?? 0);
548
+ }
549
+ catch (err) {
550
+ process.stderr.write(pc.red(`orchestrate failed: ${err instanceof Error ? err.message : String(err)}\n`));
551
+ process.exit(2);
552
+ }
553
+ });
554
+ const buildCmd = program.command("build").description("Structured planning loop: draft → distill → approve → codify → execute");
555
+ buildCmd
556
+ .command("start <topic>", { isDefault: true })
557
+ .description("Start a new build (drafting REPL)")
558
+ .option("--cwd <path>", "Working directory", process.cwd())
559
+ .option("--provider <kind>", "Provider kind (anthropic | openai-compatible)")
560
+ .option("--model <id>", "Model id")
561
+ .action(async (topic, opts) => {
562
+ try {
563
+ await runBuildStart(topic, {
564
+ cwd: opts.cwd,
565
+ ...(opts.provider ? { provider: opts.provider } : {}),
566
+ ...(opts.model ? { model: opts.model } : {}),
567
+ });
568
+ }
569
+ catch (err) {
570
+ process.stderr.write(pc.red(`build start failed: ${err instanceof Error ? err.message : String(err)}\n`));
571
+ process.exit(2);
572
+ }
573
+ });
574
+ buildCmd
575
+ .command("status [slug]")
576
+ .description("Show one build (if slug given) or all builds")
577
+ .option("--cwd <path>", "Working directory", process.cwd())
578
+ .action(async (slug, opts) => {
579
+ try {
580
+ await runBuildStatus(slug, { cwd: opts.cwd });
581
+ }
582
+ catch (err) {
583
+ process.stderr.write(pc.red(`build status failed: ${err instanceof Error ? err.message : String(err)}\n`));
584
+ process.exit(2);
585
+ }
586
+ });
587
+ buildCmd
588
+ .command("approve <slug>")
589
+ .description("Approve the current summary and codify the spec")
590
+ .option("--cwd <path>", "Working directory", process.cwd())
591
+ .option("--provider <kind>", "Provider kind")
592
+ .option("--model <id>", "Model id")
593
+ .action(async (slug, opts) => {
594
+ try {
595
+ await runBuildApprove(slug, {
596
+ cwd: opts.cwd,
597
+ ...(opts.provider ? { provider: opts.provider } : {}),
598
+ ...(opts.model ? { model: opts.model } : {}),
599
+ });
600
+ }
601
+ catch (err) {
602
+ process.stderr.write(pc.red(`build approve failed: ${err instanceof Error ? err.message : String(err)}\n`));
603
+ process.exit(2);
604
+ }
605
+ });
606
+ buildCmd
607
+ .command("reject <slug> <reason>")
608
+ .description("Reject the current summary and return to drafting")
609
+ .option("--cwd <path>", "Working directory", process.cwd())
610
+ .action(async (slug, reason, opts) => {
611
+ try {
612
+ await runBuildReject(slug, reason, { cwd: opts.cwd });
613
+ }
614
+ catch (err) {
615
+ process.stderr.write(pc.red(`build reject failed: ${err instanceof Error ? err.message : String(err)}\n`));
616
+ process.exit(2);
617
+ }
618
+ });
619
+ buildCmd
620
+ .command("resume <slug>")
621
+ .description("Re-enter the REPL for a paused build")
622
+ .option("--cwd <path>", "Working directory", process.cwd())
623
+ .option("--provider <kind>", "Provider kind")
624
+ .option("--model <id>", "Model id")
625
+ .action(async (slug, opts) => {
626
+ try {
627
+ await runBuildResume(slug, {
628
+ cwd: opts.cwd,
629
+ ...(opts.provider ? { provider: opts.provider } : {}),
630
+ ...(opts.model ? { model: opts.model } : {}),
631
+ });
632
+ }
633
+ catch (err) {
634
+ process.stderr.write(pc.red(`build resume failed: ${err instanceof Error ? err.message : String(err)}\n`));
635
+ process.exit(2);
636
+ }
637
+ });
638
+ buildCmd
639
+ .command("abort <slug>")
640
+ .description("Mark a build as abandoned (artifacts preserved)")
641
+ .option("--cwd <path>", "Working directory", process.cwd())
642
+ .action(async (slug, opts) => {
643
+ try {
644
+ await runBuildAbort(slug, { cwd: opts.cwd });
645
+ }
646
+ catch (err) {
647
+ process.stderr.write(pc.red(`build abort failed: ${err instanceof Error ? err.message : String(err)}\n`));
648
+ process.exit(2);
649
+ }
650
+ });
651
+ buildCmd
652
+ .command("execute <slug>")
653
+ .description("Execute an approved spec via the orchestrator")
654
+ .option("--cwd <path>", "Working directory", process.cwd())
655
+ .option("--provider <kind>", "Provider kind")
656
+ .option("--model <id>", "Model id")
657
+ .option("--dry-run", "Plan only; skip real writes")
658
+ .action(async (slug, opts) => {
659
+ try {
660
+ await runBuildExecute(slug, {
661
+ cwd: opts.cwd,
662
+ ...(opts.provider ? { provider: opts.provider } : {}),
663
+ ...(opts.model ? { model: opts.model } : {}),
664
+ ...(opts.dryRun ? { dryRun: true } : {}),
665
+ });
666
+ }
667
+ catch (err) {
668
+ process.stderr.write(pc.red(`build execute failed: ${err instanceof Error ? err.message : String(err)}\n`));
669
+ process.exit(2);
670
+ }
671
+ });
672
+ const reflectCmd = program.command("reflect").description("End-of-session retrospective");
673
+ reflectCmd
674
+ .command("run", { isDefault: true })
675
+ .description("Generate a reflection for a session (defaults to most recent)")
676
+ .option("--cwd <path>", "Working directory", process.cwd())
677
+ .option("--session <id>", "Session id (defaults to most recent)")
678
+ .option("--auto", "Indicate the call came from an event trigger")
679
+ .option("--provider <kind>", "Provider kind")
680
+ .option("--model <id>", "Model id")
681
+ .action(async (opts) => {
682
+ try {
683
+ await runReflect({
684
+ cwd: opts.cwd,
685
+ ...(opts.session ? { session: opts.session } : {}),
686
+ ...(opts.auto ? { auto: true } : {}),
687
+ ...(opts.provider ? { provider: opts.provider } : {}),
688
+ ...(opts.model ? { model: opts.model } : {}),
689
+ });
690
+ }
691
+ catch (err) {
692
+ process.stderr.write(pc.red(`reflect failed: ${err instanceof Error ? err.message : String(err)}\n`));
693
+ process.exit(2);
694
+ }
695
+ });
696
+ reflectCmd
697
+ .command("ls")
698
+ .description("List recent reflections")
699
+ .option("--cwd <path>", "Working directory", process.cwd())
700
+ .action(async (opts) => {
701
+ await runReflectList({ cwd: opts.cwd });
702
+ });
703
+ const memoryCmd = program.command("memory").description("Read and write memory files");
704
+ memoryCmd
705
+ .command("list")
706
+ .description("List memory entries from project (default) or global")
707
+ .option("--cwd <path>", "Working directory", process.cwd())
708
+ .option("--global", "Read from ~/.keel/memory.md instead of project scope")
709
+ .action(async (opts) => {
710
+ await runMemoryList({ cwd: opts.cwd, ...(opts.global ? { global: true } : {}) });
711
+ });
712
+ memoryCmd
713
+ .command("add <fact>")
714
+ .description("Append a fact to memory (project scope by default)")
715
+ .option("--cwd <path>", "Working directory", process.cwd())
716
+ .option("--global", "Append to ~/.keel/memory.md instead of project scope")
717
+ .action(async (fact, opts) => {
718
+ await runMemoryAdd({
719
+ cwd: opts.cwd,
720
+ fact,
721
+ ...(opts.global ? { global: true } : {}),
722
+ });
723
+ });
724
+ memoryCmd
725
+ .command("show <pattern>")
726
+ .description("Search memory entries by regex or literal substring")
727
+ .option("--cwd <path>", "Working directory", process.cwd())
728
+ .option("--global", "Search ~/.keel/memory.md instead of project scope")
729
+ .action(async (pattern, opts) => {
730
+ await runMemoryShow({
731
+ cwd: opts.cwd,
732
+ pattern,
733
+ ...(opts.global ? { global: true } : {}),
734
+ });
735
+ });
736
+ memoryCmd
737
+ .command("rm <entryId>")
738
+ .description("Delete a memory entry by id (or id prefix)")
739
+ .option("--cwd <path>", "Working directory", process.cwd())
740
+ .action(async (entryId, opts) => {
741
+ await runMemoryRemove({ cwd: opts.cwd, id: entryId });
742
+ });
743
+ const learnCmd = program.command("learn").description("Review proposed framework updates");
744
+ learnCmd
745
+ .command("review", { isDefault: true })
746
+ .description("Step through pending proposals interactively")
747
+ .option("--cwd <path>", "Working directory", process.cwd())
748
+ .option("--provider <kind>", "Provider kind")
749
+ .option("--model <id>", "Model id")
750
+ .action(async (opts) => {
751
+ try {
752
+ await runLearn({
753
+ cwd: opts.cwd,
754
+ ...(opts.provider ? { provider: opts.provider } : {}),
755
+ ...(opts.model ? { model: opts.model } : {}),
756
+ });
757
+ }
758
+ catch (err) {
759
+ process.stderr.write(pc.red(`learn failed: ${err instanceof Error ? err.message : String(err)}\n`));
760
+ process.exit(2);
761
+ }
762
+ });
763
+ learnCmd
764
+ .command("ls")
765
+ .description("List proposals by status (default: pending)")
766
+ .option("--cwd <path>", "Working directory", process.cwd())
767
+ .option("--status <s>", "pending | accepted | rejected | deferred")
768
+ .action(async (opts) => {
769
+ const statusList = ["pending", "accepted", "rejected", "deferred"];
770
+ const status = opts.status && statusList.includes(opts.status)
771
+ ? opts.status
772
+ : "pending";
773
+ await runLearnList({ cwd: opts.cwd, status });
774
+ });
775
+ learnCmd
776
+ .command("apply <proposalId>")
777
+ .description("Apply a proposal non-interactively (for CI)")
778
+ .option("--cwd <path>", "Working directory", process.cwd())
779
+ .option("--provider <kind>", "Provider kind")
780
+ .option("--model <id>", "Model id")
781
+ .action(async (proposalId, opts) => {
782
+ try {
783
+ await runLearnApply({
784
+ cwd: opts.cwd,
785
+ proposalId,
786
+ ...(opts.provider ? { provider: opts.provider } : {}),
787
+ ...(opts.model ? { model: opts.model } : {}),
788
+ });
789
+ }
790
+ catch (err) {
791
+ process.stderr.write(pc.red(`learn apply failed: ${err instanceof Error ? err.message : String(err)}\n`));
792
+ process.exit(2);
793
+ }
794
+ });
795
+ const goalCmd = program.command("goal").description("Manage goals (Phase 3g)");
796
+ goalCmd
797
+ .command("list")
798
+ .description("List goals (optionally filtered by status)")
799
+ .option("--status <s>", "active | paused | achieved | abandoned")
800
+ .action(async (opts) => {
801
+ try {
802
+ await runGoalList({ ...(opts.status ? { status: opts.status } : {}) });
803
+ }
804
+ catch (err) {
805
+ process.stderr.write(pc.red(`goal list failed: ${err instanceof Error ? err.message : String(err)}\n`));
806
+ process.exit(2);
807
+ }
808
+ });
809
+ goalCmd
810
+ .command("create [title]")
811
+ .description("Create a goal interactively (built-in or custom)")
812
+ .action(async (title) => {
813
+ try {
814
+ await runGoalCreate({ ...(title ? { title } : {}) });
815
+ }
816
+ catch (err) {
817
+ process.stderr.write(pc.red(`goal create failed: ${err instanceof Error ? err.message : String(err)}\n`));
818
+ process.exit(2);
819
+ }
820
+ });
821
+ goalCmd
822
+ .command("status <id>")
823
+ .description("Show single-goal status + recent actions")
824
+ .action(async (id) => {
825
+ try {
826
+ await runGoalStatus({ id });
827
+ }
828
+ catch (err) {
829
+ process.stderr.write(pc.red(`goal status failed: ${err instanceof Error ? err.message : String(err)}\n`));
830
+ process.exit(2);
831
+ }
832
+ });
833
+ goalCmd
834
+ .command("evaluate <id>")
835
+ .description("Force an immediate evaluation (bypasses trigger schedule)")
836
+ .action(async (id) => {
837
+ try {
838
+ await runGoalEvaluate({ id });
839
+ }
840
+ catch (err) {
841
+ process.stderr.write(pc.red(`goal evaluate failed: ${err instanceof Error ? err.message : String(err)}\n`));
842
+ process.exit(2);
843
+ }
844
+ });
845
+ goalCmd
846
+ .command("approve <actionId>")
847
+ .description("Approve a pending action")
848
+ .option("--note <note>", "Optional approval note")
849
+ .action(async (actionId, opts) => {
850
+ try {
851
+ await runGoalApprove({ id: actionId, ...(opts.note ? { note: opts.note } : {}) });
852
+ }
853
+ catch (err) {
854
+ process.stderr.write(pc.red(`goal approve failed: ${err instanceof Error ? err.message : String(err)}\n`));
855
+ process.exit(2);
856
+ }
857
+ });
858
+ goalCmd
859
+ .command("reject <actionId> <reason>")
860
+ .description("Reject a pending action with a reason")
861
+ .action(async (actionId, reason) => {
862
+ try {
863
+ await runGoalReject({ id: actionId, reason });
864
+ }
865
+ catch (err) {
866
+ process.stderr.write(pc.red(`goal reject failed: ${err instanceof Error ? err.message : String(err)}\n`));
867
+ process.exit(2);
868
+ }
869
+ });
870
+ goalCmd
871
+ .command("pause <id>")
872
+ .description("Pause a goal")
873
+ .action(async (id) => {
874
+ try {
875
+ await runGoalPause({ id });
876
+ }
877
+ catch (err) {
878
+ process.stderr.write(pc.red(`goal pause failed: ${err instanceof Error ? err.message : String(err)}\n`));
879
+ process.exit(2);
880
+ }
881
+ });
882
+ goalCmd
883
+ .command("resume <id>")
884
+ .description("Resume a paused goal")
885
+ .action(async (id) => {
886
+ try {
887
+ await runGoalResume({ id });
888
+ }
889
+ catch (err) {
890
+ process.stderr.write(pc.red(`goal resume failed: ${err instanceof Error ? err.message : String(err)}\n`));
891
+ process.exit(2);
892
+ }
893
+ });
894
+ goalCmd
895
+ .command("abandon <id>")
896
+ .description("Abandon a goal")
897
+ .action(async (id) => {
898
+ try {
899
+ await runGoalAbandon({ id });
900
+ }
901
+ catch (err) {
902
+ process.stderr.write(pc.red(`goal abandon failed: ${err instanceof Error ? err.message : String(err)}\n`));
903
+ process.exit(2);
904
+ }
905
+ });
906
+ program
907
+ .command("up")
908
+ .description("Boot the workspace API + Next.js UI + database and open the browser")
909
+ .option("--no-open", "Do not open the browser after boot (for SSH/CI/detached runs)")
910
+ .option("--no-login", "Skip the Claude sign-in preflight (for CI; use CLAUDE_CODE_OAUTH_TOKEN)")
911
+ .action(async (opts) => {
912
+ // Match the house error style used by every other command: a runUp throw
913
+ // (port conflict, health timeout, docker/migration failure) becomes a single
914
+ // red line + clean exit, never the raw V8 stack trace + pnpm ELIFECYCLE that
915
+ // re-running `keel up` while already running used to produce.
916
+ try {
917
+ await runUp({ skipBrowser: opts.open === false, skipLogin: opts.login === false });
918
+ }
919
+ catch (err) {
920
+ process.stderr.write(pc.red(`\nkeel up failed: ${err instanceof Error ? err.message : String(err)}\n`));
921
+ process.exit(1);
922
+ }
923
+ });
924
+ program
925
+ .command("login")
926
+ .description("Sign in to your Claude subscription (keyless claude-bridge auth)")
927
+ .option("--token", "Mint a long-lived CLAUDE_CODE_OAUTH_TOKEN instead (headless/CI)")
928
+ .action(async (opts) => {
929
+ const existing = probeClaudeAuth();
930
+ if (existing.state === "authed" && !opts.token) {
931
+ process.stdout.write(pc.green(`✓ already signed in${existing.subscriptionType ? ` (${existing.subscriptionType})` : ""}\n`) +
932
+ (existing.email ? pc.dim(` ${existing.email}\n`) : ""));
933
+ return;
934
+ }
935
+ if (existing.state === "cli-missing") {
936
+ process.stderr.write(pc.yellow(`${CLAUDE_INSTALL_HINT}\n`));
937
+ process.exit(1);
938
+ }
939
+ const ok = triggerClaudeLogin(opts.token ? "token" : "browser");
940
+ if (!ok) {
941
+ process.stderr.write(pc.red("sign-in did not complete.\n"));
942
+ process.exit(1);
943
+ }
944
+ if (!opts.token) {
945
+ const after = probeClaudeAuth();
946
+ process.stdout.write(after.state === "authed"
947
+ ? pc.green(`✓ signed in${after.subscriptionType ? ` (${after.subscriptionType})` : ""}\n`)
948
+ : pc.green("✓ sign-in complete\n"));
949
+ }
950
+ });
951
+ program
952
+ .command("down")
953
+ .description("Stop the running workspace (API + UI + managed database) from any terminal")
954
+ .action(async () => {
955
+ try {
956
+ await runDown();
957
+ }
958
+ catch (err) {
959
+ process.stderr.write(pc.red(`\nkeel down failed: ${err instanceof Error ? err.message : String(err)}\n`));
960
+ process.exit(1);
961
+ }
962
+ });
963
+ program
964
+ .command("status")
965
+ .description("Show whether the workspace is running, and where")
966
+ .action(async () => {
967
+ try {
968
+ const allUp = await runStatus();
969
+ // Scriptable: non-zero when the workspace is not fully up.
970
+ if (!allUp)
971
+ process.exit(1);
972
+ }
973
+ catch (err) {
974
+ process.stderr.write(pc.red(`\nkeel status failed: ${err instanceof Error ? err.message : String(err)}\n`));
975
+ process.exit(1);
976
+ }
977
+ });
978
+ program
979
+ .command("open")
980
+ .description("Open the running workspace in your browser")
981
+ .action(async () => {
982
+ try {
983
+ const opened = await runOpen();
984
+ if (!opened)
985
+ process.exit(1);
986
+ }
987
+ catch (err) {
988
+ process.stderr.write(pc.red(`\nkeel open failed: ${err instanceof Error ? err.message : String(err)}\n`));
989
+ process.exit(1);
990
+ }
991
+ });
992
+ program
993
+ .command("restart")
994
+ .description("Stop the workspace if running, then boot it fresh")
995
+ .option("--no-open", "Do not open the browser after boot (for SSH/CI/detached runs)")
996
+ .action(async (opts) => {
997
+ try {
998
+ await runRestart({ skipBrowser: opts.open === false });
999
+ }
1000
+ catch (err) {
1001
+ process.stderr.write(pc.red(`\nkeel restart failed: ${err instanceof Error ? err.message : String(err)}\n`));
1002
+ process.exit(1);
1003
+ }
1004
+ });
1005
+ function fileHash(filePath) {
1006
+ return new Promise((resolve, reject) => {
1007
+ const hash = createHash("sha256");
1008
+ const stream = createReadStream(filePath);
1009
+ stream.on("data", (d) => hash.update(d));
1010
+ stream.on("end", () => resolve(hash.digest("hex")));
1011
+ stream.on("error", reject);
1012
+ });
1013
+ }
1014
+ program.parse(process.argv);
1015
+ //# sourceMappingURL=index.js.map