@swarmvaultai/cli 0.3.0 → 0.5.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 +9 -3
  2. package/dist/index.js +126 -28
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -22,9 +22,10 @@ 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
28
29
  swarmvault source list
29
30
  swarmvault source reload --all
30
31
  sed -n '1,120p' swarmvault.schema.md
@@ -50,7 +51,7 @@ swarmvault graph push neo4j --dry-run
50
51
 
51
52
  ## Commands
52
53
 
53
- ### `swarmvault init [--obsidian]`
54
+ ### `swarmvault init [--obsidian] [--profile personal-research]`
54
55
 
55
56
  Create a workspace with:
56
57
 
@@ -67,19 +68,23 @@ Create a workspace with:
67
68
 
68
69
  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
70
 
70
- ### `swarmvault source add|list|reload|delete`
71
+ ### `swarmvault source add|list|reload|review|guide|delete`
71
72
 
72
73
  Manage recurring source roots through a registry-backed workflow.
73
74
 
74
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
75
76
  - 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
76
78
  - `source list` shows every managed source with its kind, status, and current brief path
77
79
  - `source reload [id]` re-syncs one source, or use `--all` to refresh everything in the registry and compile once
80
+ - `source review <id>` stages a lighter source-scoped review artifact
81
+ - `source guide <id>` stages the stronger guided-ingest bundle for that source scope
78
82
  - `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
83
 
80
84
  Useful flags:
81
85
 
82
86
  - `--all`
87
+ - `--guide`
83
88
  - `--no-compile`
84
89
  - `--no-brief`
85
90
  - `--max-pages <n>`
@@ -97,6 +102,7 @@ Ingest a local file path, directory path, or URL into immutable source storage a
97
102
  - use `source add` instead when the same local directory, public GitHub repo root, or docs hub should stay registered and reloadable
98
103
  - URL ingest still localizes remote image references by default
99
104
  - 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
100
106
  - 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
107
 
102
108
  Useful flags:
package/dist/index.js CHANGED
@@ -17,6 +17,8 @@ import {
17
17
  exportGraphHtml,
18
18
  getGitHookStatus,
19
19
  getWatchStatus,
20
+ guideManagedSource,
21
+ guideSourceScope,
20
22
  importInbox,
21
23
  ingestDirectory,
22
24
  ingestInputDetailed,
@@ -28,6 +30,7 @@ import {
28
30
  listCandidates,
29
31
  listGodNodes,
30
32
  listManagedSourceRecords,
33
+ listManifests,
31
34
  listSchedules,
32
35
  loadVaultConfig,
33
36
  pathGraphVault,
@@ -38,6 +41,8 @@ import {
38
41
  readApproval,
39
42
  rejectApproval,
40
43
  reloadManagedSources,
44
+ reviewManagedSource,
45
+ reviewSourceScope,
41
46
  runSchedule,
42
47
  runWatchCycle,
43
48
  serveSchedules,
@@ -221,9 +226,9 @@ program.name("swarmvault").description("SwarmVault is a local-first knowledge co
221
226
  function readCliVersion() {
222
227
  try {
223
228
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
224
- return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.3.0";
229
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.5.0";
225
230
  } catch {
226
- return "0.3.0";
231
+ return "0.5.0";
227
232
  }
228
233
  }
229
234
  function parsePositiveInt(value, fallback) {
@@ -231,6 +236,21 @@ function parsePositiveInt(value, fallback) {
231
236
  const parsed = Number.parseInt(value, 10);
232
237
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
233
238
  }
239
+ function sourceScopeFromManifests(input, manifests) {
240
+ if (!manifests.length) {
241
+ return null;
242
+ }
243
+ const primary = manifests[0];
244
+ return {
245
+ id: primary?.sourceGroupId ?? primary?.sourceId ?? slugForCli(input),
246
+ title: primary?.sourceGroupTitle ?? primary?.title ?? input,
247
+ sourceIds: manifests.map((manifest) => manifest.sourceId),
248
+ kind: primary?.sourceKind
249
+ };
250
+ }
251
+ function slugForCli(value) {
252
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "source";
253
+ }
234
254
  function isJson() {
235
255
  return program.opts().json === true;
236
256
  }
@@ -273,15 +293,20 @@ program.hook("postAction", async (_thisCommand, actionCommand) => {
273
293
  emitNotice(notice);
274
294
  }
275
295
  });
276
- 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) => {
277
- await initVault(process2.cwd(), { obsidian: options.obsidian ?? false });
296
+ 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) => {
297
+ await initVault(process2.cwd(), { obsidian: options.obsidian ?? false, profile: options.profile });
278
298
  if (isJson()) {
279
- emitJson({ status: "initialized", rootDir: process2.cwd(), obsidian: options.obsidian ?? false });
299
+ emitJson({
300
+ status: "initialized",
301
+ rootDir: process2.cwd(),
302
+ obsidian: options.obsidian ?? false,
303
+ profile: options.profile ?? "default"
304
+ });
280
305
  } else {
281
306
  log("Initialized SwarmVault workspace.");
282
307
  }
283
308
  });
284
- 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("--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(
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(
285
310
  async (input, options) => {
286
311
  const maxAssetSize = typeof options.maxAssetSize === "string" && options.maxAssetSize.trim() ? parsePositiveInt(options.maxAssetSize, 0) || void 0 : void 0;
287
312
  const maxFiles = typeof options.maxFiles === "string" && options.maxFiles.trim() ? parsePositiveInt(options.maxFiles, 0) || void 0 : void 0;
@@ -305,18 +330,59 @@ program.command("ingest").description("Ingest a local file path, directory path,
305
330
  (fs) => fs.stat(input).then((stat) => stat.isDirectory() ? ingestDirectory(process2.cwd(), input, commonOptions) : null).catch(() => null)
306
331
  ) : null;
307
332
  if (directoryResult) {
333
+ const scope2 = options.review || options.guide ? await (async () => {
334
+ const pathModule = await import("path");
335
+ const absoluteInput = pathModule.resolve(process2.cwd(), input);
336
+ const sourceIds = (await listManifests(process2.cwd())).filter((manifest) => {
337
+ if (!manifest.originalPath) {
338
+ return false;
339
+ }
340
+ const relative = pathModule.relative(absoluteInput, pathModule.resolve(manifest.originalPath));
341
+ return relative === "" || !relative.startsWith("..") && !pathModule.isAbsolute(relative);
342
+ }).map((manifest) => manifest.sourceId);
343
+ return sourceIds.length ? {
344
+ id: `directory-${absoluteInput.split(pathModule.sep).pop() ?? "source"}`,
345
+ title: absoluteInput.split(pathModule.sep).pop() ?? absoluteInput,
346
+ sourceIds,
347
+ kind: "directory"
348
+ } : void 0;
349
+ })() : void 0;
350
+ const shouldStage = Boolean(scope2 && (directoryResult.imported.length || directoryResult.updated.length));
351
+ const review3 = shouldStage && options.review && !options.guide ? await (async () => {
352
+ await compileVault(process2.cwd(), {});
353
+ return await reviewSourceScope(process2.cwd(), scope2);
354
+ })() : void 0;
355
+ const guide2 = shouldStage && options.guide ? await (async () => {
356
+ await compileVault(process2.cwd(), {});
357
+ return await guideSourceScope(process2.cwd(), scope2);
358
+ })() : void 0;
308
359
  if (isJson()) {
309
- emitJson(directoryResult);
360
+ emitJson(guide2 ? { ingest: directoryResult, guide: guide2 } : review3 ? { ingest: directoryResult, review: review3 } : directoryResult);
310
361
  } else {
311
362
  log(
312
363
  `Imported ${directoryResult.imported.length} file(s), updated ${directoryResult.updated.length}, skipped ${directoryResult.skipped.length}.`
313
364
  );
365
+ if (review3) {
366
+ log(`Staged source review at ${review3.reviewPath}.`);
367
+ }
368
+ if (guide2) {
369
+ log(`Staged source guide at ${guide2.guidePath}.`);
370
+ }
314
371
  }
315
372
  return;
316
373
  }
317
374
  const ingest = await ingestInputDetailed(process2.cwd(), input, commonOptions);
375
+ const scope = sourceScopeFromManifests(input, [...ingest.created, ...ingest.updated, ...ingest.unchanged]);
376
+ const review2 = options.review && !options.guide && scope && (ingest.created.length || ingest.updated.length || ingest.unchanged.length) ? await (async () => {
377
+ await compileVault(process2.cwd(), {});
378
+ return await reviewSourceScope(process2.cwd(), scope);
379
+ })() : void 0;
380
+ const guide = options.guide && scope && (ingest.created.length || ingest.updated.length || ingest.unchanged.length) ? await (async () => {
381
+ await compileVault(process2.cwd(), {});
382
+ return await guideSourceScope(process2.cwd(), scope);
383
+ })() : void 0;
318
384
  if (isJson()) {
319
- emitJson(ingest);
385
+ emitJson(guide ? { ingest, guide } : review2 ? { ingest, review: review2 } : ingest);
320
386
  } else {
321
387
  const primary = [...ingest.created, ...ingest.updated, ...ingest.unchanged][0];
322
388
  if (ingest.created.length + ingest.updated.length + ingest.removed.length <= 1 && primary) {
@@ -326,6 +392,12 @@ program.command("ingest").description("Ingest a local file path, directory path,
326
392
  `Created ${ingest.created.length}, updated ${ingest.updated.length}, unchanged ${ingest.unchanged.length}, removed ${ingest.removed.length}.`
327
393
  );
328
394
  }
395
+ if (review2) {
396
+ log(`Staged source review at ${review2.reviewPath}.`);
397
+ }
398
+ if (guide) {
399
+ log(`Staged source guide at ${guide.guidePath}.`);
400
+ }
329
401
  }
330
402
  }
331
403
  );
@@ -340,22 +412,26 @@ program.command("add").description("Capture supported URLs into normalized markd
340
412
  log(`${result.captureType}${result.fallback ? " (fallback)" : ""}: ${result.manifest.sourceId}`);
341
413
  }
342
414
  });
343
- var source = program.command("source").description("Manage recurring directory, public repo, and docs sources.");
344
- source.command("add").description("Register and sync a managed source from a directory, public GitHub repo root URL, or docs hub URL.").argument("<input>", "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("--max-pages <n>", "Maximum number of pages to crawl for docs sources").option("--max-depth <n>", "Maximum crawl depth for docs sources").action(async (input, options) => {
345
- const result = await addManagedSource(process2.cwd(), input, {
346
- compile: options.compile,
347
- brief: options.brief,
348
- maxPages: options.maxPages ? parsePositiveInt(options.maxPages, 0) || void 0 : void 0,
349
- maxDepth: options.maxDepth ? parsePositiveInt(options.maxDepth, 0) || void 0 : void 0
350
- });
351
- if (isJson()) {
352
- emitJson(result);
353
- } else {
354
- log(
355
- `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}` : ""}`
356
- );
415
+ 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(
417
+ async (input, options) => {
418
+ const result = await addManagedSource(process2.cwd(), input, {
419
+ compile: options.compile,
420
+ brief: options.brief,
421
+ review: options.review,
422
+ guide: options.guide,
423
+ maxPages: options.maxPages ? parsePositiveInt(options.maxPages, 0) || void 0 : void 0,
424
+ maxDepth: options.maxDepth ? parsePositiveInt(options.maxDepth, 0) || void 0 : void 0
425
+ });
426
+ if (isJson()) {
427
+ emitJson(result);
428
+ } else {
429
+ 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}` : ""}`
431
+ );
432
+ }
357
433
  }
358
- });
434
+ );
359
435
  source.command("list").description("List managed sources registered in this vault.").action(async () => {
360
436
  const sources = await listManagedSourceRecords(process2.cwd());
361
437
  if (isJson()) {
@@ -368,13 +444,15 @@ source.command("list").description("List managed sources registered in this vaul
368
444
  }
369
445
  }
370
446
  });
371
- 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(
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(
372
448
  async (id, options) => {
373
449
  const result = await reloadManagedSources(process2.cwd(), {
374
450
  id,
375
451
  all: options.all ?? false,
376
452
  compile: options.compile,
377
453
  brief: options.brief,
454
+ review: options.review,
455
+ guide: options.guide,
378
456
  maxPages: options.maxPages ? parsePositiveInt(options.maxPages, 0) || void 0 : void 0,
379
457
  maxDepth: options.maxDepth ? parsePositiveInt(options.maxDepth, 0) || void 0 : void 0
380
458
  });
@@ -382,7 +460,7 @@ source.command("reload").description("Re-sync one managed source or all managed
382
460
  emitJson(result);
383
461
  } else {
384
462
  log(
385
- `Reloaded ${result.sources.length} source(s).${result.compile ? ` Compiled ${result.compile.sourceCount} source(s).` : ""}${result.briefPaths.length ? ` Briefs: ${result.briefPaths.length}.` : ""}`
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}.` : ""}`
386
464
  );
387
465
  }
388
466
  }
@@ -395,6 +473,22 @@ source.command("delete").description("Unregister a managed source and remove its
395
473
  log(`Deleted managed source ${result.removed.id}. Canonical vault content was left in place.`);
396
474
  }
397
475
  });
476
+ source.command("review").description("Stage a source review artifact for a managed source id or raw source id.").argument("<id>", "Managed source id or raw source id").action(async (id) => {
477
+ const result = await reviewManagedSource(process2.cwd(), id);
478
+ if (isJson()) {
479
+ emitJson(result);
480
+ } else {
481
+ log(`Staged source review at ${result.reviewPath}.`);
482
+ }
483
+ });
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);
486
+ if (isJson()) {
487
+ emitJson(result);
488
+ } else {
489
+ log(`Staged source guide at ${result.guidePath}.`);
490
+ }
491
+ });
398
492
  var inbox = program.command("inbox").description("Inbox and capture workflows.");
399
493
  inbox.command("import").description("Import supported files from the configured inbox directory.").argument("[dir]", "Optional inbox directory override").action(async (dir) => {
400
494
  const result = await importInbox(process2.cwd(), dir);
@@ -605,7 +699,7 @@ review.command("list").description("List staged approval bundles and their resol
605
699
  }
606
700
  for (const approval of approvals) {
607
701
  log(
608
- `${approval.approvalId} pending=${approval.pendingCount} accepted=${approval.acceptedCount} rejected=${approval.rejectedCount} created=${approval.createdAt}`
702
+ `${approval.approvalId}${approval.bundleType ? ` [${approval.bundleType}]` : ""}${approval.title ? ` ${approval.title}` : ""} pending=${approval.pendingCount} accepted=${approval.acceptedCount} rejected=${approval.rejectedCount} created=${approval.createdAt}`
609
703
  );
610
704
  }
611
705
  });
@@ -615,9 +709,13 @@ review.command("show").description("Show the entries inside a staged approval bu
615
709
  emitJson(approval);
616
710
  return;
617
711
  }
618
- log(`${approval.approvalId} pending=${approval.pendingCount} accepted=${approval.acceptedCount} rejected=${approval.rejectedCount}`);
712
+ log(
713
+ `${approval.approvalId}${approval.bundleType ? ` [${approval.bundleType}]` : ""}${approval.title ? ` ${approval.title}` : ""} pending=${approval.pendingCount} accepted=${approval.acceptedCount} rejected=${approval.rejectedCount}`
714
+ );
619
715
  for (const entry of approval.entries) {
620
- log(`- ${entry.status} ${entry.changeType} ${entry.pageId} ${entry.nextPath ?? entry.previousPath ?? ""}`.trim());
716
+ log(
717
+ `- ${entry.status} ${entry.changeType}${entry.label ? ` [${entry.label}]` : ""} ${entry.pageId} ${entry.nextPath ?? entry.previousPath ?? ""}`.trim()
718
+ );
621
719
  if (entry.changeSummary) log(` Summary: ${entry.changeSummary}`);
622
720
  if (entry.diff) {
623
721
  log("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "0.3.0",
3
+ "version": "0.5.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.3.0",
41
+ "@swarmvaultai/engine": "0.5.0",
42
42
  "commander": "^14.0.1"
43
43
  },
44
44
  "devDependencies": {