@swarmvaultai/cli 0.4.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 +79 -27
  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,
@@ -224,9 +226,9 @@ program.name("swarmvault").description("SwarmVault is a local-first knowledge co
224
226
  function readCliVersion() {
225
227
  try {
226
228
  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";
229
+ return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.5.0";
228
230
  } catch {
229
- return "0.4.0";
231
+ return "0.5.0";
230
232
  }
231
233
  }
232
234
  function parsePositiveInt(value, fallback) {
@@ -234,6 +236,21 @@ function parsePositiveInt(value, fallback) {
234
236
  const parsed = Number.parseInt(value, 10);
235
237
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
236
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
+ }
237
254
  function isJson() {
238
255
  return program.opts().json === true;
239
256
  }
@@ -276,15 +293,20 @@ program.hook("postAction", async (_thisCommand, actionCommand) => {
276
293
  emitNotice(notice);
277
294
  }
278
295
  });
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 });
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 });
281
298
  if (isJson()) {
282
- 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
+ });
283
305
  } else {
284
306
  log("Initialized SwarmVault workspace.");
285
307
  }
286
308
  });
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(
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(
288
310
  async (input, options) => {
289
311
  const maxAssetSize = typeof options.maxAssetSize === "string" && options.maxAssetSize.trim() ? parsePositiveInt(options.maxAssetSize, 0) || void 0 : void 0;
290
312
  const maxFiles = typeof options.maxFiles === "string" && options.maxFiles.trim() ? parsePositiveInt(options.maxFiles, 0) || void 0 : void 0;
@@ -308,8 +330,7 @@ program.command("ingest").description("Ingest a local file path, directory path,
308
330
  (fs) => fs.stat(input).then((stat) => stat.isDirectory() ? ingestDirectory(process2.cwd(), input, commonOptions) : null).catch(() => null)
309
331
  ) : null;
310
332
  if (directoryResult) {
311
- const review3 = options.review && (directoryResult.imported.length || directoryResult.updated.length) ? await (async () => {
312
- await compileVault(process2.cwd(), {});
333
+ const scope2 = options.review || options.guide ? await (async () => {
313
334
  const pathModule = await import("path");
314
335
  const absoluteInput = pathModule.resolve(process2.cwd(), input);
315
336
  const sourceIds = (await listManifests(process2.cwd())).filter((manifest) => {
@@ -319,14 +340,24 @@ program.command("ingest").description("Ingest a local file path, directory path,
319
340
  const relative = pathModule.relative(absoluteInput, pathModule.resolve(manifest.originalPath));
320
341
  return relative === "" || !relative.startsWith("..") && !pathModule.isAbsolute(relative);
321
342
  }).map((manifest) => manifest.sourceId);
322
- return sourceIds.length ? await reviewSourceScope(process2.cwd(), {
343
+ return sourceIds.length ? {
323
344
  id: `directory-${absoluteInput.split(pathModule.sep).pop() ?? "source"}`,
324
345
  title: absoluteInput.split(pathModule.sep).pop() ?? absoluteInput,
325
- sourceIds
326
- }) : void 0;
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);
327
358
  })() : void 0;
328
359
  if (isJson()) {
329
- emitJson(review3 ? { ingest: directoryResult, review: review3 } : directoryResult);
360
+ emitJson(guide2 ? { ingest: directoryResult, guide: guide2 } : review3 ? { ingest: directoryResult, review: review3 } : directoryResult);
330
361
  } else {
331
362
  log(
332
363
  `Imported ${directoryResult.imported.length} file(s), updated ${directoryResult.updated.length}, skipped ${directoryResult.skipped.length}.`
@@ -334,21 +365,24 @@ program.command("ingest").description("Ingest a local file path, directory path,
334
365
  if (review3) {
335
366
  log(`Staged source review at ${review3.reviewPath}.`);
336
367
  }
368
+ if (guide2) {
369
+ log(`Staged source guide at ${guide2.guidePath}.`);
370
+ }
337
371
  }
338
372
  return;
339
373
  }
340
374
  const ingest = await ingestInputDetailed(process2.cwd(), input, commonOptions);
341
- const review2 = options.review && (ingest.created.length || ingest.updated.length || ingest.unchanged.length) ? await (async () => {
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 () => {
342
381
  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
- });
382
+ return await guideSourceScope(process2.cwd(), scope);
349
383
  })() : void 0;
350
384
  if (isJson()) {
351
- emitJson(review2 ? { ingest, review: review2 } : ingest);
385
+ emitJson(guide ? { ingest, guide } : review2 ? { ingest, review: review2 } : ingest);
352
386
  } else {
353
387
  const primary = [...ingest.created, ...ingest.updated, ...ingest.unchanged][0];
354
388
  if (ingest.created.length + ingest.updated.length + ingest.removed.length <= 1 && primary) {
@@ -361,6 +395,9 @@ program.command("ingest").description("Ingest a local file path, directory path,
361
395
  if (review2) {
362
396
  log(`Staged source review at ${review2.reviewPath}.`);
363
397
  }
398
+ if (guide) {
399
+ log(`Staged source guide at ${guide.guidePath}.`);
400
+ }
364
401
  }
365
402
  }
366
403
  );
@@ -376,12 +413,13 @@ program.command("add").description("Capture supported URLs into normalized markd
376
413
  }
377
414
  });
378
415
  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(
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(
380
417
  async (input, options) => {
381
418
  const result = await addManagedSource(process2.cwd(), input, {
382
419
  compile: options.compile,
383
420
  brief: options.brief,
384
421
  review: options.review,
422
+ guide: options.guide,
385
423
  maxPages: options.maxPages ? parsePositiveInt(options.maxPages, 0) || void 0 : void 0,
386
424
  maxDepth: options.maxDepth ? parsePositiveInt(options.maxDepth, 0) || void 0 : void 0
387
425
  });
@@ -389,7 +427,7 @@ source.command("add").description("Register and sync a managed source from a loc
389
427
  emitJson(result);
390
428
  } else {
391
429
  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}` : ""}`
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}` : ""}`
393
431
  );
394
432
  }
395
433
  }
@@ -406,13 +444,15 @@ source.command("list").description("List managed sources registered in this vaul
406
444
  }
407
445
  }
408
446
  });
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(
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(
410
448
  async (id, options) => {
411
449
  const result = await reloadManagedSources(process2.cwd(), {
412
450
  id,
413
451
  all: options.all ?? false,
414
452
  compile: options.compile,
415
453
  brief: options.brief,
454
+ review: options.review,
455
+ guide: options.guide,
416
456
  maxPages: options.maxPages ? parsePositiveInt(options.maxPages, 0) || void 0 : void 0,
417
457
  maxDepth: options.maxDepth ? parsePositiveInt(options.maxDepth, 0) || void 0 : void 0
418
458
  });
@@ -420,7 +460,7 @@ source.command("reload").description("Re-sync one managed source or all managed
420
460
  emitJson(result);
421
461
  } else {
422
462
  log(
423
- `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}.` : ""}`
424
464
  );
425
465
  }
426
466
  }
@@ -441,6 +481,14 @@ source.command("review").description("Stage a source review artifact for a manag
441
481
  log(`Staged source review at ${result.reviewPath}.`);
442
482
  }
443
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
+ });
444
492
  var inbox = program.command("inbox").description("Inbox and capture workflows.");
445
493
  inbox.command("import").description("Import supported files from the configured inbox directory.").argument("[dir]", "Optional inbox directory override").action(async (dir) => {
446
494
  const result = await importInbox(process2.cwd(), dir);
@@ -651,7 +699,7 @@ review.command("list").description("List staged approval bundles and their resol
651
699
  }
652
700
  for (const approval of approvals) {
653
701
  log(
654
- `${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}`
655
703
  );
656
704
  }
657
705
  });
@@ -661,9 +709,13 @@ review.command("show").description("Show the entries inside a staged approval bu
661
709
  emitJson(approval);
662
710
  return;
663
711
  }
664
- 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
+ );
665
715
  for (const entry of approval.entries) {
666
- 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
+ );
667
719
  if (entry.changeSummary) log(` Summary: ${entry.changeSummary}`);
668
720
  if (entry.diff) {
669
721
  log("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/cli",
3
- "version": "0.4.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.4.0",
41
+ "@swarmvaultai/engine": "0.5.0",
42
42
  "commander": "^14.0.1"
43
43
  },
44
44
  "devDependencies": {