@swarmvaultai/cli 0.5.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 +10 -6
  2. package/dist/index.js +116 -18
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -26,6 +26,7 @@ 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
28
  swarmvault source add ./exports/customer-call.srt --guide
29
+ swarmvault source session file-customer-call-srt-12345678
29
30
  swarmvault source list
30
31
  swarmvault source reload --all
31
32
  sed -n '1,120p' swarmvault.schema.md
@@ -68,29 +69,31 @@ Create a workspace with:
68
69
 
69
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.
70
71
 
71
- ### `swarmvault source add|list|reload|review|guide|delete`
72
+ ### `swarmvault source add|list|reload|review|guide|session|delete`
72
73
 
73
74
  Manage recurring source roots through a registry-backed workflow.
74
75
 
75
- - `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
76
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`
77
- - add `--guide` when you want a source brief, source review, source guide, and approval bundle for one-source-at-a-time integration
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
78
79
  - `source list` shows every managed source with its kind, status, and current brief path
79
80
  - `source reload [id]` re-syncs one source, or use `--all` to refresh everything in the registry and compile once
80
81
  - `source review <id>` stages a lighter source-scoped review artifact
81
- - `source guide <id>` stages the stronger guided-ingest bundle for that source scope
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
82
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
83
85
 
84
86
  Useful flags:
85
87
 
86
88
  - `--all`
87
89
  - `--guide`
90
+ - `--answers-file <path>`
88
91
  - `--no-compile`
89
92
  - `--no-brief`
90
93
  - `--max-pages <n>`
91
94
  - `--max-depth <n>`
92
95
 
93
- 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.
94
97
 
95
98
  ### `swarmvault ingest <path-or-url>`
96
99
 
@@ -102,12 +105,13 @@ Ingest a local file path, directory path, or URL into immutable source storage a
102
105
  - use `source add` instead when the same local directory, public GitHub repo root, or docs hub should stay registered and reloadable
103
106
  - URL ingest still localizes remote image references by default
104
107
  - local file ingest supports markdown, text, reStructuredText, HTML, PDF, DOCX, images, and code
105
- - add `--guide` when you want a source brief, source review, source guide, and approval bundle after ingest
108
+ - add `--guide` when you want a resumable source session, source brief, source review, source guide, and approval-bundled `wiki/insights/` updates after ingest
106
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
107
110
 
108
111
  Useful flags:
109
112
 
110
113
  - `--repo-root <path>`
114
+ - `--answers-file <path>`
111
115
  - `--include <glob...>`
112
116
  - `--exclude <glob...>`
113
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,
@@ -41,6 +42,7 @@ import {
41
42
  readApproval,
42
43
  rejectApproval,
43
44
  reloadManagedSources,
45
+ resumeSourceSession,
44
46
  reviewManagedSource,
45
47
  reviewSourceScope,
46
48
  runSchedule,
@@ -226,9 +228,9 @@ program.name("swarmvault").description("SwarmVault is a local-first knowledge co
226
228
  function readCliVersion() {
227
229
  try {
228
230
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
229
- return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.5.0";
231
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.6.0";
230
232
  } catch {
231
- return "0.5.0";
233
+ return "0.6.0";
232
234
  }
233
235
  }
234
236
  function parsePositiveInt(value, fallback) {
@@ -271,6 +273,57 @@ function emitNotice(message) {
271
273
  process2.stderr.write(`[swarmvault] ${message}
272
274
  `);
273
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
+ }
274
327
  function getCommandPath(command) {
275
328
  const names = [];
276
329
  let current = command;
@@ -306,8 +359,9 @@ program.command("init").description("Initialize a SwarmVault workspace in the cu
306
359
  log("Initialized SwarmVault workspace.");
307
360
  }
308
361
  });
309
- 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("--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(
310
363
  async (input, options) => {
364
+ const guideAnswers = readGuideAnswersFile(options.answersFile);
311
365
  const maxAssetSize = typeof options.maxAssetSize === "string" && options.maxAssetSize.trim() ? parsePositiveInt(options.maxAssetSize, 0) || void 0 : void 0;
312
366
  const maxFiles = typeof options.maxFiles === "string" && options.maxFiles.trim() ? parsePositiveInt(options.maxFiles, 0) || void 0 : void 0;
313
367
  const extractClasses = [
@@ -354,10 +408,13 @@ program.command("ingest").description("Ingest a local file path, directory path,
354
408
  })() : void 0;
355
409
  const guide2 = shouldStage && options.guide ? await (async () => {
356
410
  await compileVault(process2.cwd(), {});
357
- return await guideSourceScope(process2.cwd(), scope2);
411
+ return await guideSourceScope(process2.cwd(), scope2, { answers: guideAnswers });
358
412
  })() : void 0;
413
+ const completedGuide2 = guide2 && !options.answersFile ? await completeGuideInteractively(guide2, scope2?.id ?? input) : guide2;
359
414
  if (isJson()) {
360
- emitJson(guide2 ? { ingest: directoryResult, guide: guide2 } : review3 ? { ingest: directoryResult, review: review3 } : directoryResult);
415
+ emitJson(
416
+ completedGuide2 ? { ingest: directoryResult, guide: completedGuide2 } : review3 ? { ingest: directoryResult, review: review3 } : directoryResult
417
+ );
361
418
  } else {
362
419
  log(
363
420
  `Imported ${directoryResult.imported.length} file(s), updated ${directoryResult.updated.length}, skipped ${directoryResult.skipped.length}.`
@@ -365,8 +422,12 @@ program.command("ingest").description("Ingest a local file path, directory path,
365
422
  if (review3) {
366
423
  log(`Staged source review at ${review3.reviewPath}.`);
367
424
  }
368
- if (guide2) {
369
- log(`Staged source guide at ${guide2.guidePath}.`);
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}.`);
370
431
  }
371
432
  }
372
433
  return;
@@ -379,10 +440,11 @@ program.command("ingest").description("Ingest a local file path, directory path,
379
440
  })() : void 0;
380
441
  const guide = options.guide && scope && (ingest.created.length || ingest.updated.length || ingest.unchanged.length) ? await (async () => {
381
442
  await compileVault(process2.cwd(), {});
382
- return await guideSourceScope(process2.cwd(), scope);
443
+ return await guideSourceScope(process2.cwd(), scope, { answers: guideAnswers });
383
444
  })() : void 0;
445
+ const completedGuide = guide && !options.answersFile ? await completeGuideInteractively(guide, scope?.id ?? input) : guide;
384
446
  if (isJson()) {
385
- emitJson(guide ? { ingest, guide } : review2 ? { ingest, review: review2 } : ingest);
447
+ emitJson(completedGuide ? { ingest, guide: completedGuide } : review2 ? { ingest, review: review2 } : ingest);
386
448
  } else {
387
449
  const primary = [...ingest.created, ...ingest.updated, ...ingest.unchanged][0];
388
450
  if (ingest.created.length + ingest.updated.length + ingest.removed.length <= 1 && primary) {
@@ -395,8 +457,12 @@ program.command("ingest").description("Ingest a local file path, directory path,
395
457
  if (review2) {
396
458
  log(`Staged source review at ${review2.reviewPath}.`);
397
459
  }
398
- if (guide) {
399
- log(`Staged source guide at ${guide.guidePath}.`);
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}.`);
400
466
  }
401
467
  }
402
468
  }
@@ -413,21 +479,26 @@ program.command("add").description("Capture supported URLs into normalized markd
413
479
  }
414
480
  });
415
481
  var source = program.command("source").description("Manage recurring local files, directories, public repos, and docs sources.");
416
- 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("--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(
417
483
  async (input, options) => {
484
+ const guideAnswers = readGuideAnswersFile(options.answersFile);
418
485
  const result = await addManagedSource(process2.cwd(), input, {
419
486
  compile: options.compile,
420
487
  brief: options.brief,
421
488
  review: options.review,
422
489
  guide: options.guide,
490
+ guideAnswers,
423
491
  maxPages: options.maxPages ? parsePositiveInt(options.maxPages, 0) || void 0 : void 0,
424
492
  maxDepth: options.maxDepth ? parsePositiveInt(options.maxDepth, 0) || void 0 : void 0
425
493
  });
494
+ if (result.guide && !options.answersFile) {
495
+ result.guide = await completeGuideInteractively(result.guide, result.source.id);
496
+ }
426
497
  if (isJson()) {
427
498
  emitJson(result);
428
499
  } else {
429
500
  log(
430
- `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 ? ` Guide: ${result.guide.guidePath}` : ""}`
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}` : ""}`
431
502
  );
432
503
  }
433
504
  }
@@ -444,8 +515,9 @@ source.command("list").description("List managed sources registered in this vaul
444
515
  }
445
516
  }
446
517
  });
447
- 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("--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(
448
519
  async (id, options) => {
520
+ const guideAnswers = readGuideAnswersFile(options.answersFile);
449
521
  const result = await reloadManagedSources(process2.cwd(), {
450
522
  id,
451
523
  all: options.all ?? false,
@@ -453,14 +525,18 @@ source.command("reload").description("Re-sync one managed source or all managed
453
525
  brief: options.brief,
454
526
  review: options.review,
455
527
  guide: options.guide,
528
+ guideAnswers,
456
529
  maxPages: options.maxPages ? parsePositiveInt(options.maxPages, 0) || void 0 : void 0,
457
530
  maxDepth: options.maxDepth ? parsePositiveInt(options.maxDepth, 0) || void 0 : void 0
458
531
  });
532
+ if (!options.answersFile && result.guides.length === 1) {
533
+ result.guides = [await completeGuideInteractively(result.guides[0], result.sources[0]?.id ?? id ?? "source")];
534
+ }
459
535
  if (isJson()) {
460
536
  emitJson(result);
461
537
  } else {
462
538
  log(
463
- `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: ${result.guides.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}.` : ""}`
464
540
  );
465
541
  }
466
542
  }
@@ -481,12 +557,34 @@ source.command("review").description("Stage a source review artifact for a manag
481
557
  log(`Staged source review at ${result.reviewPath}.`);
482
558
  }
483
559
  });
484
- source.command("guide").description("Stage a guided source integration bundle for a managed source id or raw source id.").argument("<id>", "Managed source id or raw source id").action(async (id) => {
485
- const result = await guideManagedSource(process2.cwd(), id);
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
+ }
486
582
  if (isJson()) {
487
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.`);
488
586
  } else {
489
- log(`Staged source guide at ${result.guidePath}.`);
587
+ log(`Staged guided session at ${result.guidePath}.`);
490
588
  }
491
589
  });
492
590
  var inbox = program.command("inbox").description("Inbox and capture workflows.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "0.5.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.5.0",
41
+ "@swarmvaultai/engine": "0.6.0",
42
42
  "commander": "^14.0.1"
43
43
  },
44
44
  "devDependencies": {