@swarmvaultai/cli 0.4.0 → 0.6.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 (3) hide show
  1. package/README.md +15 -5
  2. package/dist/index.js +177 -27
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -22,9 +22,11 @@ Installed commands:
22
22
  ```bash
23
23
  mkdir my-vault
24
24
  cd my-vault
25
- swarmvault init --obsidian
25
+ swarmvault init --obsidian --profile personal-research
26
26
  swarmvault source add https://github.com/karpathy/micrograd
27
27
  swarmvault source add https://example.com/docs/getting-started
28
+ swarmvault source add ./exports/customer-call.srt --guide
29
+ swarmvault source session file-customer-call-srt-12345678
28
30
  swarmvault source list
29
31
  swarmvault source reload --all
30
32
  sed -n '1,120p' swarmvault.schema.md
@@ -50,7 +52,7 @@ swarmvault graph push neo4j --dry-run
50
52
 
51
53
  ## Commands
52
54
 
53
- ### `swarmvault init [--obsidian]`
55
+ ### `swarmvault init [--obsidian] [--profile personal-research]`
54
56
 
55
57
  Create a workspace with:
56
58
 
@@ -67,25 +69,31 @@ Create a workspace with:
67
69
 
68
70
  The schema file is the vault-specific instruction layer. Edit it to define naming rules, categories, grounding expectations, and exclusions before a serious compile.
69
71
 
70
- ### `swarmvault source add|list|reload|delete`
72
+ ### `swarmvault source add|list|reload|review|guide|session|delete`
71
73
 
72
74
  Manage recurring source roots through a registry-backed workflow.
73
75
 
74
- - `source add <input>` supports local directories, public GitHub repo root URLs such as `https://github.com/karpathy/micrograd`, and docs/wiki/help/reference/tutorial hubs
76
+ - `source add <input>` supports local files, local directories, public GitHub repo root URLs such as `https://github.com/karpathy/micrograd`, and docs/wiki/help/reference/tutorial hubs
75
77
  - by default `source add` registers the source, syncs it into the vault, runs one compile, and writes a source brief to `wiki/outputs/source-briefs/<source-id>.md`
78
+ - add `--guide` when you want a resumable source session, source brief, source review, source guide, and approval-bundled `wiki/insights/` updates for one-source-at-a-time integration
76
79
  - `source list` shows every managed source with its kind, status, and current brief path
77
80
  - `source reload [id]` re-syncs one source, or use `--all` to refresh everything in the registry and compile once
81
+ - `source review <id>` stages a lighter source-scoped review artifact
82
+ - `source guide <id>` remains a compatibility alias for the guided session flow
83
+ - `source session <id>` resumes the latest guided session for a managed source id, raw source id, source scope id, or session id
78
84
  - `source delete <id>` unregisters the source and removes transient sync state under `state/sources/<id>/`, but leaves canonical `raw/`, `wiki/`, and saved output artifacts intact
79
85
 
80
86
  Useful flags:
81
87
 
82
88
  - `--all`
89
+ - `--guide`
90
+ - `--answers-file <path>`
83
91
  - `--no-compile`
84
92
  - `--no-brief`
85
93
  - `--max-pages <n>`
86
94
  - `--max-depth <n>`
87
95
 
88
- Managed sources write registry state to `state/sources.json`. Local directory entries remain compatible with `watch --repo`; remote GitHub and docs-crawl sources are manual `source reload` sources in this release.
96
+ Managed sources write registry state to `state/sources.json`. Guided sessions write durable anchors to `wiki/outputs/source-sessions/` and session state to `state/source-sessions/`. In an interactive TTY, `--guide` can ask the session questions immediately; otherwise use `source session <id>` or `--answers-file <path>` to resume and stage the approval bundle later. Local directory entries remain compatible with `watch --repo`; remote GitHub and docs-crawl sources are manual `source reload` sources in this release.
89
97
 
90
98
  ### `swarmvault ingest <path-or-url>`
91
99
 
@@ -97,11 +105,13 @@ Ingest a local file path, directory path, or URL into immutable source storage a
97
105
  - use `source add` instead when the same local directory, public GitHub repo root, or docs hub should stay registered and reloadable
98
106
  - URL ingest still localizes remote image references by default
99
107
  - local file ingest supports markdown, text, reStructuredText, HTML, PDF, DOCX, images, and code
108
+ - add `--guide` when you want a resumable source session, source brief, source review, source guide, and approval-bundled `wiki/insights/` updates after ingest
100
109
  - code-aware directory ingest currently covers JavaScript, JSX, TypeScript, TSX, Python, Go, Rust, Java, Kotlin, Scala, Lua, Zig, C#, C, C++, PHP, Ruby, and PowerShell
101
110
 
102
111
  Useful flags:
103
112
 
104
113
  - `--repo-root <path>`
114
+ - `--answers-file <path>`
105
115
  - `--include <glob...>`
106
116
  - `--exclude <glob...>`
107
117
  - `--max-files <n>`
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  // src/index.ts
4
4
  import { readFileSync } from "fs";
5
5
  import process2 from "process";
6
+ import { createInterface } from "readline/promises";
6
7
  import {
7
8
  acceptApproval,
8
9
  addInput,
@@ -17,6 +18,8 @@ import {
17
18
  exportGraphHtml,
18
19
  getGitHookStatus,
19
20
  getWatchStatus,
21
+ guideManagedSource,
22
+ guideSourceScope,
20
23
  importInbox,
21
24
  ingestDirectory,
22
25
  ingestInputDetailed,
@@ -39,6 +42,7 @@ import {
39
42
  readApproval,
40
43
  rejectApproval,
41
44
  reloadManagedSources,
45
+ resumeSourceSession,
42
46
  reviewManagedSource,
43
47
  reviewSourceScope,
44
48
  runSchedule,
@@ -224,9 +228,9 @@ program.name("swarmvault").description("SwarmVault is a local-first knowledge co
224
228
  function readCliVersion() {
225
229
  try {
226
230
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
227
- return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.4.0";
231
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.6.0";
228
232
  } catch {
229
- return "0.4.0";
233
+ return "0.6.0";
230
234
  }
231
235
  }
232
236
  function parsePositiveInt(value, fallback) {
@@ -234,6 +238,21 @@ function parsePositiveInt(value, fallback) {
234
238
  const parsed = Number.parseInt(value, 10);
235
239
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
236
240
  }
241
+ function sourceScopeFromManifests(input, manifests) {
242
+ if (!manifests.length) {
243
+ return null;
244
+ }
245
+ const primary = manifests[0];
246
+ return {
247
+ id: primary?.sourceGroupId ?? primary?.sourceId ?? slugForCli(input),
248
+ title: primary?.sourceGroupTitle ?? primary?.title ?? input,
249
+ sourceIds: manifests.map((manifest) => manifest.sourceId),
250
+ kind: primary?.sourceKind
251
+ };
252
+ }
253
+ function slugForCli(value) {
254
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "source";
255
+ }
237
256
  function isJson() {
238
257
  return program.opts().json === true;
239
258
  }
@@ -254,6 +273,57 @@ function emitNotice(message) {
254
273
  process2.stderr.write(`[swarmvault] ${message}
255
274
  `);
256
275
  }
276
+ function canPromptGuide() {
277
+ return Boolean(process2.stdin.isTTY && process2.stdout.isTTY && !isJson());
278
+ }
279
+ function readGuideAnswersFile(filePath) {
280
+ if (!filePath) {
281
+ return void 0;
282
+ }
283
+ const raw = JSON.parse(readFileSync(filePath, "utf8"));
284
+ if (Array.isArray(raw)) {
285
+ return raw.filter((value) => typeof value === "string");
286
+ }
287
+ if (raw && typeof raw === "object") {
288
+ return Object.fromEntries(
289
+ Object.entries(raw).filter((entry) => typeof entry[0] === "string" && typeof entry[1] === "string")
290
+ );
291
+ }
292
+ throw new Error("Guide answers files must contain either a JSON object keyed by question id or a JSON array of answers.");
293
+ }
294
+ async function promptGuideAnswers(questions) {
295
+ const rl = createInterface({
296
+ input: process2.stdin,
297
+ output: process2.stdout
298
+ });
299
+ try {
300
+ const answers = {};
301
+ for (const question of questions) {
302
+ const promptLines = [question.prompt];
303
+ if (question.answer) {
304
+ promptLines.push(`Current: ${question.answer}`);
305
+ promptLines.push("Press Enter to keep the current answer.");
306
+ }
307
+ const answer = (await rl.question(`${promptLines.join("\n")}
308
+ > `)).trim();
309
+ if (answer) {
310
+ answers[question.id] = answer;
311
+ } else if (question.answer) {
312
+ answers[question.id] = question.answer;
313
+ }
314
+ }
315
+ return answers;
316
+ } finally {
317
+ rl.close();
318
+ }
319
+ }
320
+ async function completeGuideInteractively(guide, fallbackTarget) {
321
+ if (!guide.awaitingInput || !canPromptGuide()) {
322
+ return guide;
323
+ }
324
+ const answers = await promptGuideAnswers(guide.questions);
325
+ return await resumeSourceSession(process2.cwd(), guide.sessionId || fallbackTarget, { answers });
326
+ }
257
327
  function getCommandPath(command) {
258
328
  const names = [];
259
329
  let current = command;
@@ -276,16 +346,22 @@ program.hook("postAction", async (_thisCommand, actionCommand) => {
276
346
  emitNotice(notice);
277
347
  }
278
348
  });
279
- program.command("init").description("Initialize a SwarmVault workspace in the current directory.").option("--obsidian", "Generate a minimal .obsidian workspace alongside the vault", false).action(async (options) => {
280
- await initVault(process2.cwd(), { obsidian: options.obsidian ?? false });
349
+ program.command("init").description("Initialize a SwarmVault workspace in the current directory.").option("--obsidian", "Generate a minimal .obsidian workspace alongside the vault", false).addOption(new Option("--profile <profile>", "Starter workspace profile").choices(["default", "personal-research"])).action(async (options) => {
350
+ await initVault(process2.cwd(), { obsidian: options.obsidian ?? false, profile: options.profile });
281
351
  if (isJson()) {
282
- emitJson({ status: "initialized", rootDir: process2.cwd(), obsidian: options.obsidian ?? false });
352
+ emitJson({
353
+ status: "initialized",
354
+ rootDir: process2.cwd(),
355
+ obsidian: options.obsidian ?? false,
356
+ profile: options.profile ?? "default"
357
+ });
283
358
  } else {
284
359
  log("Initialized SwarmVault workspace.");
285
360
  }
286
361
  });
287
- program.command("ingest").description("Ingest a local file path, directory path, or URL into the raw SwarmVault workspace.").argument("<input>", "Local file path, directory path, or URL").option("--review", "Stage a source review artifact after ingest and compile", false).option("--include-assets", "Download remote image assets when ingesting URLs", true).option("--no-include-assets", "Skip downloading remote image assets when ingesting URLs").option("--max-asset-size <bytes>", "Maximum number of bytes to fetch for a single remote image asset").option("--repo-root <path>", "Override the detected repo root when ingesting a directory").option("--include <glob...>", "Only ingest files matching one or more glob patterns").option("--exclude <glob...>", "Skip files matching one or more glob patterns").option("--max-files <n>", "Maximum number of files to ingest from a directory").option("--include-third-party", "Also ingest repo files classified as third-party", false).option("--include-resources", "Also ingest repo files classified as resources", false).option("--include-generated", "Also ingest repo files classified as generated output", false).option("--no-gitignore", "Ignore .gitignore rules when ingesting a directory").action(
362
+ program.command("ingest").description("Ingest a local file path, directory path, or URL into the raw SwarmVault workspace.").argument("<input>", "Local file path, directory path, or URL").option("--review", "Stage a source review artifact after ingest and compile", false).option("--guide", "Stage a guided source integration bundle after ingest and compile", false).option("--answers-file <path>", "JSON file with guided-session answers keyed by question id or listed in prompt order").option("--include-assets", "Download remote image assets when ingesting URLs", true).option("--no-include-assets", "Skip downloading remote image assets when ingesting URLs").option("--max-asset-size <bytes>", "Maximum number of bytes to fetch for a single remote image asset").option("--repo-root <path>", "Override the detected repo root when ingesting a directory").option("--include <glob...>", "Only ingest files matching one or more glob patterns").option("--exclude <glob...>", "Skip files matching one or more glob patterns").option("--max-files <n>", "Maximum number of files to ingest from a directory").option("--include-third-party", "Also ingest repo files classified as third-party", false).option("--include-resources", "Also ingest repo files classified as resources", false).option("--include-generated", "Also ingest repo files classified as generated output", false).option("--no-gitignore", "Ignore .gitignore rules when ingesting a directory").action(
288
363
  async (input, options) => {
364
+ const guideAnswers = readGuideAnswersFile(options.answersFile);
289
365
  const maxAssetSize = typeof options.maxAssetSize === "string" && options.maxAssetSize.trim() ? parsePositiveInt(options.maxAssetSize, 0) || void 0 : void 0;
290
366
  const maxFiles = typeof options.maxFiles === "string" && options.maxFiles.trim() ? parsePositiveInt(options.maxFiles, 0) || void 0 : void 0;
291
367
  const extractClasses = [
@@ -308,8 +384,7 @@ program.command("ingest").description("Ingest a local file path, directory path,
308
384
  (fs) => fs.stat(input).then((stat) => stat.isDirectory() ? ingestDirectory(process2.cwd(), input, commonOptions) : null).catch(() => null)
309
385
  ) : null;
310
386
  if (directoryResult) {
311
- const review3 = options.review && (directoryResult.imported.length || directoryResult.updated.length) ? await (async () => {
312
- await compileVault(process2.cwd(), {});
387
+ const scope2 = options.review || options.guide ? await (async () => {
313
388
  const pathModule = await import("path");
314
389
  const absoluteInput = pathModule.resolve(process2.cwd(), input);
315
390
  const sourceIds = (await listManifests(process2.cwd())).filter((manifest) => {
@@ -319,14 +394,27 @@ program.command("ingest").description("Ingest a local file path, directory path,
319
394
  const relative = pathModule.relative(absoluteInput, pathModule.resolve(manifest.originalPath));
320
395
  return relative === "" || !relative.startsWith("..") && !pathModule.isAbsolute(relative);
321
396
  }).map((manifest) => manifest.sourceId);
322
- return sourceIds.length ? await reviewSourceScope(process2.cwd(), {
397
+ return sourceIds.length ? {
323
398
  id: `directory-${absoluteInput.split(pathModule.sep).pop() ?? "source"}`,
324
399
  title: absoluteInput.split(pathModule.sep).pop() ?? absoluteInput,
325
- sourceIds
326
- }) : void 0;
400
+ sourceIds,
401
+ kind: "directory"
402
+ } : void 0;
327
403
  })() : void 0;
404
+ const shouldStage = Boolean(scope2 && (directoryResult.imported.length || directoryResult.updated.length));
405
+ const review3 = shouldStage && options.review && !options.guide ? await (async () => {
406
+ await compileVault(process2.cwd(), {});
407
+ return await reviewSourceScope(process2.cwd(), scope2);
408
+ })() : void 0;
409
+ const guide2 = shouldStage && options.guide ? await (async () => {
410
+ await compileVault(process2.cwd(), {});
411
+ return await guideSourceScope(process2.cwd(), scope2, { answers: guideAnswers });
412
+ })() : void 0;
413
+ const completedGuide2 = guide2 && !options.answersFile ? await completeGuideInteractively(guide2, scope2?.id ?? input) : guide2;
328
414
  if (isJson()) {
329
- emitJson(review3 ? { ingest: directoryResult, review: review3 } : directoryResult);
415
+ emitJson(
416
+ completedGuide2 ? { ingest: directoryResult, guide: completedGuide2 } : review3 ? { ingest: directoryResult, review: review3 } : directoryResult
417
+ );
330
418
  } else {
331
419
  log(
332
420
  `Imported ${directoryResult.imported.length} file(s), updated ${directoryResult.updated.length}, skipped ${directoryResult.skipped.length}.`
@@ -334,21 +422,29 @@ program.command("ingest").description("Ingest a local file path, directory path,
334
422
  if (review3) {
335
423
  log(`Staged source review at ${review3.reviewPath}.`);
336
424
  }
425
+ if (completedGuide2?.awaitingInput) {
426
+ log(
427
+ `Created guided session at ${completedGuide2.sessionPath}. Resume with \`swarmvault source session ${completedGuide2.sessionId}\`.`
428
+ );
429
+ } else if (completedGuide2?.guidePath) {
430
+ log(`Staged guided session at ${completedGuide2.guidePath}.`);
431
+ }
337
432
  }
338
433
  return;
339
434
  }
340
435
  const ingest = await ingestInputDetailed(process2.cwd(), input, commonOptions);
341
- const review2 = options.review && (ingest.created.length || ingest.updated.length || ingest.unchanged.length) ? await (async () => {
436
+ const scope = sourceScopeFromManifests(input, [...ingest.created, ...ingest.updated, ...ingest.unchanged]);
437
+ const review2 = options.review && !options.guide && scope && (ingest.created.length || ingest.updated.length || ingest.unchanged.length) ? await (async () => {
342
438
  await compileVault(process2.cwd(), {});
343
- const scopeSourceIds = [...ingest.created, ...ingest.updated, ...ingest.unchanged].map((manifest) => manifest.sourceId);
344
- return await reviewSourceScope(process2.cwd(), {
345
- id: ingest.created[0]?.sourceGroupId ?? ingest.updated[0]?.sourceGroupId ?? ingest.unchanged[0]?.sourceGroupId ?? scopeSourceIds[0],
346
- title: [...ingest.created, ...ingest.updated, ...ingest.unchanged][0]?.sourceGroupTitle ?? [...ingest.created, ...ingest.updated, ...ingest.unchanged][0]?.title ?? input,
347
- sourceIds: scopeSourceIds
348
- });
439
+ return await reviewSourceScope(process2.cwd(), scope);
349
440
  })() : void 0;
441
+ const guide = options.guide && scope && (ingest.created.length || ingest.updated.length || ingest.unchanged.length) ? await (async () => {
442
+ await compileVault(process2.cwd(), {});
443
+ return await guideSourceScope(process2.cwd(), scope, { answers: guideAnswers });
444
+ })() : void 0;
445
+ const completedGuide = guide && !options.answersFile ? await completeGuideInteractively(guide, scope?.id ?? input) : guide;
350
446
  if (isJson()) {
351
- emitJson(review2 ? { ingest, review: review2 } : ingest);
447
+ emitJson(completedGuide ? { ingest, guide: completedGuide } : review2 ? { ingest, review: review2 } : ingest);
352
448
  } else {
353
449
  const primary = [...ingest.created, ...ingest.updated, ...ingest.unchanged][0];
354
450
  if (ingest.created.length + ingest.updated.length + ingest.removed.length <= 1 && primary) {
@@ -361,6 +457,13 @@ program.command("ingest").description("Ingest a local file path, directory path,
361
457
  if (review2) {
362
458
  log(`Staged source review at ${review2.reviewPath}.`);
363
459
  }
460
+ if (completedGuide?.awaitingInput) {
461
+ log(
462
+ `Created guided session at ${completedGuide.sessionPath}. Resume with \`swarmvault source session ${completedGuide.sessionId}\`.`
463
+ );
464
+ } else if (completedGuide?.guidePath) {
465
+ log(`Staged guided session at ${completedGuide.guidePath}.`);
466
+ }
364
467
  }
365
468
  }
366
469
  );
@@ -376,20 +479,26 @@ program.command("add").description("Capture supported URLs into normalized markd
376
479
  }
377
480
  });
378
481
  var source = program.command("source").description("Manage recurring local files, directories, public repos, and docs sources.");
379
- source.command("add").description("Register and sync a managed source from a local file, directory, public GitHub repo root URL, or docs hub URL.").argument("<input>", "Local file path, directory path, public GitHub repo root URL, or docs hub URL").option("--no-compile", "Register and sync without compiling the vault").option("--no-brief", "Skip source brief generation after sync").option("--review", "Stage a source review artifact after sync and compile", false).option("--max-pages <n>", "Maximum number of pages to crawl for docs sources").option("--max-depth <n>", "Maximum crawl depth for docs sources").action(
482
+ source.command("add").description("Register and sync a managed source from a local file, directory, public GitHub repo root URL, or docs hub URL.").argument("<input>", "Local file path, directory path, public GitHub repo root URL, or docs hub URL").option("--no-compile", "Register and sync without compiling the vault").option("--no-brief", "Skip source brief generation after sync").option("--review", "Stage a source review artifact after sync and compile", false).option("--guide", "Stage a guided source integration bundle after sync and compile", false).option("--answers-file <path>", "JSON file with guided-session answers keyed by question id or listed in prompt order").option("--max-pages <n>", "Maximum number of pages to crawl for docs sources").option("--max-depth <n>", "Maximum crawl depth for docs sources").action(
380
483
  async (input, options) => {
484
+ const guideAnswers = readGuideAnswersFile(options.answersFile);
381
485
  const result = await addManagedSource(process2.cwd(), input, {
382
486
  compile: options.compile,
383
487
  brief: options.brief,
384
488
  review: options.review,
489
+ guide: options.guide,
490
+ guideAnswers,
385
491
  maxPages: options.maxPages ? parsePositiveInt(options.maxPages, 0) || void 0 : void 0,
386
492
  maxDepth: options.maxDepth ? parsePositiveInt(options.maxDepth, 0) || void 0 : void 0
387
493
  });
494
+ if (result.guide && !options.answersFile) {
495
+ result.guide = await completeGuideInteractively(result.guide, result.source.id);
496
+ }
388
497
  if (isJson()) {
389
498
  emitJson(result);
390
499
  } else {
391
500
  log(
392
- `Registered ${result.source.kind} source ${result.source.id}. Status: ${result.source.status}.${result.compile ? ` Compiled ${result.compile.sourceCount} source(s).` : ""}${result.briefGenerated ? ` Brief: ${result.source.briefPath}` : ""}${result.review ? ` Review: ${result.review.reviewPath}` : ""}`
501
+ `Registered ${result.source.kind} source ${result.source.id}. Status: ${result.source.status}.${result.compile ? ` Compiled ${result.compile.sourceCount} source(s).` : ""}${result.briefGenerated ? ` Brief: ${result.source.briefPath}` : ""}${result.review ? ` Review: ${result.review.reviewPath}` : ""}${result.guide?.awaitingInput ? ` Session: ${result.guide.sessionPath}. Resume with \`swarmvault source session ${result.guide.sessionId}\`.` : result.guide?.guidePath ? ` Guide: ${result.guide.guidePath}` : ""}`
393
502
  );
394
503
  }
395
504
  }
@@ -406,21 +515,28 @@ source.command("list").description("List managed sources registered in this vaul
406
515
  }
407
516
  }
408
517
  });
409
- source.command("reload").description("Re-sync one managed source or all managed sources, then optionally compile and refresh briefs.").argument("[id]", "Managed source id").option("--all", "Reload all managed sources", false).option("--no-compile", "Re-sync without compiling the vault").option("--no-brief", "Skip source brief generation after sync").option("--max-pages <n>", "Maximum number of pages to crawl for docs sources").option("--max-depth <n>", "Maximum crawl depth for docs sources").action(
518
+ source.command("reload").description("Re-sync one managed source or all managed sources, then optionally compile and refresh briefs.").argument("[id]", "Managed source id").option("--all", "Reload all managed sources", false).option("--no-compile", "Re-sync without compiling the vault").option("--no-brief", "Skip source brief generation after sync").option("--review", "Stage a source review artifact after sync and compile", false).option("--guide", "Stage a guided source integration bundle after sync and compile", false).option("--answers-file <path>", "JSON file with guided-session answers keyed by question id or listed in prompt order").option("--max-pages <n>", "Maximum number of pages to crawl for docs sources").option("--max-depth <n>", "Maximum crawl depth for docs sources").action(
410
519
  async (id, options) => {
520
+ const guideAnswers = readGuideAnswersFile(options.answersFile);
411
521
  const result = await reloadManagedSources(process2.cwd(), {
412
522
  id,
413
523
  all: options.all ?? false,
414
524
  compile: options.compile,
415
525
  brief: options.brief,
526
+ review: options.review,
527
+ guide: options.guide,
528
+ guideAnswers,
416
529
  maxPages: options.maxPages ? parsePositiveInt(options.maxPages, 0) || void 0 : void 0,
417
530
  maxDepth: options.maxDepth ? parsePositiveInt(options.maxDepth, 0) || void 0 : void 0
418
531
  });
532
+ if (!options.answersFile && result.guides.length === 1) {
533
+ result.guides = [await completeGuideInteractively(result.guides[0], result.sources[0]?.id ?? id ?? "source")];
534
+ }
419
535
  if (isJson()) {
420
536
  emitJson(result);
421
537
  } else {
422
538
  log(
423
- `Reloaded ${result.sources.length} source(s).${result.compile ? ` Compiled ${result.compile.sourceCount} source(s).` : ""}${result.briefPaths.length ? ` Briefs: ${result.briefPaths.length}.` : ""}`
539
+ `Reloaded ${result.sources.length} source(s).${result.compile ? ` Compiled ${result.compile.sourceCount} source(s).` : ""}${result.briefPaths.length ? ` Briefs: ${result.briefPaths.length}.` : ""}${result.reviews.length ? ` Reviews: ${result.reviews.length}.` : ""}${result.guides.length ? ` Guides/Sessions: ${result.guides.length}.` : ""}`
424
540
  );
425
541
  }
426
542
  }
@@ -441,6 +557,36 @@ source.command("review").description("Stage a source review artifact for a manag
441
557
  log(`Staged source review at ${result.reviewPath}.`);
442
558
  }
443
559
  });
560
+ source.command("guide").description("Create or resume a guided source session for a managed source id or raw source id.").argument("<id>", "Managed source id or raw source id").option("--answers-file <path>", "JSON file with guided-session answers keyed by question id or listed in prompt order").action(async (id, options) => {
561
+ const guideAnswers = readGuideAnswersFile(options.answersFile);
562
+ let result = await guideManagedSource(process2.cwd(), id, { answers: guideAnswers });
563
+ if (!options.answersFile) {
564
+ result = await completeGuideInteractively(result, id);
565
+ }
566
+ if (isJson()) {
567
+ emitJson(result);
568
+ } else {
569
+ if (result.awaitingInput) {
570
+ log(`Created guided session at ${result.sessionPath}. Resume with \`swarmvault source session ${result.sessionId}\`.`);
571
+ } else {
572
+ log(`Staged guided session at ${result.guidePath}.`);
573
+ }
574
+ }
575
+ });
576
+ source.command("session").description("Resume the latest guided source session for a managed source id, raw source id, source scope id, or session id.").argument("<id>", "Managed source id, raw source id, source scope id, or guided session id").option("--answers-file <path>", "JSON file with guided-session answers keyed by question id or listed in prompt order").action(async (id, options) => {
577
+ const guideAnswers = readGuideAnswersFile(options.answersFile);
578
+ let result = await resumeSourceSession(process2.cwd(), id, { answers: guideAnswers });
579
+ if (!options.answersFile) {
580
+ result = await completeGuideInteractively(result, id);
581
+ }
582
+ if (isJson()) {
583
+ emitJson(result);
584
+ } else if (result.awaitingInput) {
585
+ log(`Updated guided session at ${result.sessionPath}. Resume with \`swarmvault source session ${result.sessionId}\` when ready.`);
586
+ } else {
587
+ log(`Staged guided session at ${result.guidePath}.`);
588
+ }
589
+ });
444
590
  var inbox = program.command("inbox").description("Inbox and capture workflows.");
445
591
  inbox.command("import").description("Import supported files from the configured inbox directory.").argument("[dir]", "Optional inbox directory override").action(async (dir) => {
446
592
  const result = await importInbox(process2.cwd(), dir);
@@ -651,7 +797,7 @@ review.command("list").description("List staged approval bundles and their resol
651
797
  }
652
798
  for (const approval of approvals) {
653
799
  log(
654
- `${approval.approvalId} pending=${approval.pendingCount} accepted=${approval.acceptedCount} rejected=${approval.rejectedCount} created=${approval.createdAt}`
800
+ `${approval.approvalId}${approval.bundleType ? ` [${approval.bundleType}]` : ""}${approval.title ? ` ${approval.title}` : ""} pending=${approval.pendingCount} accepted=${approval.acceptedCount} rejected=${approval.rejectedCount} created=${approval.createdAt}`
655
801
  );
656
802
  }
657
803
  });
@@ -661,9 +807,13 @@ review.command("show").description("Show the entries inside a staged approval bu
661
807
  emitJson(approval);
662
808
  return;
663
809
  }
664
- log(`${approval.approvalId} pending=${approval.pendingCount} accepted=${approval.acceptedCount} rejected=${approval.rejectedCount}`);
810
+ log(
811
+ `${approval.approvalId}${approval.bundleType ? ` [${approval.bundleType}]` : ""}${approval.title ? ` ${approval.title}` : ""} pending=${approval.pendingCount} accepted=${approval.acceptedCount} rejected=${approval.rejectedCount}`
812
+ );
665
813
  for (const entry of approval.entries) {
666
- log(`- ${entry.status} ${entry.changeType} ${entry.pageId} ${entry.nextPath ?? entry.previousPath ?? ""}`.trim());
814
+ log(
815
+ `- ${entry.status} ${entry.changeType}${entry.label ? ` [${entry.label}]` : ""} ${entry.pageId} ${entry.nextPath ?? entry.previousPath ?? ""}`.trim()
816
+ );
667
817
  if (entry.changeSummary) log(` Summary: ${entry.changeSummary}`);
668
818
  if (entry.diff) {
669
819
  log("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Global CLI for SwarmVault.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -38,7 +38,7 @@
38
38
  "node": ">=24.0.0"
39
39
  },
40
40
  "dependencies": {
41
- "@swarmvaultai/engine": "0.4.0",
41
+ "@swarmvaultai/engine": "0.6.0",
42
42
  "commander": "^14.0.1"
43
43
  },
44
44
  "devDependencies": {