pi-vault-mind 0.8.0 → 0.8.3

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 (119) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +44 -44
  3. package/agents/broadcaster.agent.md +30 -0
  4. package/agents/heavy-lifter.agent.md +30 -0
  5. package/agents/manager.agent.md +29 -0
  6. package/agents/miner.agent.md +29 -0
  7. package/dist/src/agent-bus/Agent.d.ts +49 -0
  8. package/dist/src/agent-bus/Agent.js +97 -0
  9. package/dist/src/agent-bus/AgentLoader.d.ts +33 -0
  10. package/dist/src/agent-bus/AgentLoader.js +102 -0
  11. package/dist/src/agent-bus/AgentRegistry.d.ts +17 -0
  12. package/dist/src/agent-bus/AgentRegistry.js +20 -0
  13. package/dist/src/agent-bus/LLMProvider.d.ts +36 -0
  14. package/dist/src/agent-bus/LLMProvider.js +125 -0
  15. package/dist/src/agent-bus/MessageBus.d.ts +47 -0
  16. package/dist/src/agent-bus/MessageBus.js +81 -0
  17. package/dist/src/agent-bus/TaskQueue.d.ts +27 -0
  18. package/dist/src/agent-bus/TaskQueue.js +57 -0
  19. package/dist/src/agent-bus/index.d.ts +6 -0
  20. package/dist/src/agent-bus/index.js +6 -0
  21. package/dist/src/agents/BroadcasterAgent.d.ts +19 -0
  22. package/dist/src/agents/BroadcasterAgent.js +70 -0
  23. package/dist/src/agents/HeavyLifterAgent.d.ts +26 -0
  24. package/dist/src/agents/HeavyLifterAgent.js +73 -0
  25. package/dist/src/agents/ManagerAgent.d.ts +20 -0
  26. package/dist/src/agents/ManagerAgent.js +71 -0
  27. package/dist/src/agents/MinerAgent.d.ts +19 -0
  28. package/dist/src/agents/MinerAgent.js +70 -0
  29. package/dist/src/agents/index.d.ts +4 -0
  30. package/dist/src/agents/index.js +4 -0
  31. package/dist/src/auth.d.ts +49 -2
  32. package/dist/src/auth.js +149 -7
  33. package/dist/src/autosync.d.ts +1 -1
  34. package/dist/src/autosync.js +3 -3
  35. package/dist/src/bridge.d.ts +45 -0
  36. package/dist/src/bridge.js +189 -0
  37. package/dist/src/commands.d.ts +1 -1
  38. package/dist/src/commands.js +419 -295
  39. package/dist/src/context-capture.d.ts +22 -0
  40. package/dist/src/context-capture.js +41 -0
  41. package/dist/src/discover-schema.d.ts +1 -1
  42. package/dist/src/discover-schema.js +4 -4
  43. package/dist/src/embed-queue.d.ts +1 -1
  44. package/dist/src/embed-queue.js +1 -1
  45. package/dist/src/engine.d.ts +65 -0
  46. package/dist/src/engine.js +244 -0
  47. package/dist/src/events.js +1 -1
  48. package/dist/src/graph.d.ts +3 -3
  49. package/dist/src/identity-injector.d.ts +15 -0
  50. package/dist/src/identity-injector.js +102 -0
  51. package/dist/src/index.js +88 -49
  52. package/dist/src/intake.js +4 -4
  53. package/dist/src/lance.d.ts +11 -37
  54. package/dist/src/lance.js +183 -240
  55. package/dist/src/modal-client.d.ts +14 -4
  56. package/dist/src/modal-client.js +50 -34
  57. package/dist/src/modal-config.d.ts +26 -10
  58. package/dist/src/modal-config.js +36 -5
  59. package/dist/src/personalize.d.ts +47 -0
  60. package/dist/src/personalize.js +352 -0
  61. package/dist/src/server.d.ts +30 -4
  62. package/dist/src/server.js +146 -64
  63. package/dist/src/settings-ui.d.ts +3 -1
  64. package/dist/src/settings-ui.js +84 -77
  65. package/dist/src/status.d.ts +25 -0
  66. package/dist/src/status.js +49 -0
  67. package/dist/src/sync.d.ts +1 -1
  68. package/dist/src/sync.js +19 -19
  69. package/dist/src/tombstone.js +2 -2
  70. package/dist/src/tools.js +112 -130
  71. package/dist/src/types.d.ts +86 -18
  72. package/dist/src/types.js +7 -4
  73. package/dist/src/utils.d.ts +5 -1
  74. package/dist/src/utils.js +24 -8
  75. package/dist/src/vault-tools.d.ts +29 -0
  76. package/dist/src/vault-tools.js +314 -0
  77. package/dist/src/vault-writer.d.ts +4 -5
  78. package/dist/src/vault-writer.js +13 -13
  79. package/dist/src/vm-handlers.d.ts +15 -0
  80. package/dist/src/vm-handlers.js +469 -0
  81. package/dist/src/watcher.d.ts +18 -4
  82. package/dist/src/watcher.js +109 -98
  83. package/dist/test/auth.test.js +57 -7
  84. package/dist/test/bridge-identity.test.js +388 -0
  85. package/dist/test/discover-schema.test.js +2 -2
  86. package/dist/test/engine-identity.test.js +119 -0
  87. package/dist/test/index.test.js +5 -6
  88. package/dist/test/lance-modal.test.js +7 -7
  89. package/dist/test/modal-config.test.js +51 -29
  90. package/dist/test/personalize.test.js +212 -0
  91. package/dist/test/rest-queue.test.js +1 -1
  92. package/dist/test/rest-setup.test.js +21 -21
  93. package/dist/test/{rest-wiki.test.js → rest-vm.test.js} +27 -23
  94. package/dist/test/server-ws.test.js +4 -4
  95. package/dist/test/sync.test.js +2 -2
  96. package/dist/test/tombstone.test.js +1 -1
  97. package/dist/test/vault-tools.test.js +224 -0
  98. package/package.json +2 -1
  99. package/scripts/check-deps.sh +207 -0
  100. package/scripts/e2e-ws.mjs +4 -4
  101. package/scripts/modal-e2e-smoke.mjs +1 -1
  102. package/scripts/setup-test-config.sh +1 -5
  103. package/scripts/test-slash-commands.mjs +197 -0
  104. package/skills/.agents/broadcaster.agent.md +31 -0
  105. package/skills/.agents/heavy-lifter.agent.md +31 -0
  106. package/skills/.agents/manager.agent.md +30 -0
  107. package/skills/.agents/miner.agent.md +30 -0
  108. package/skills/vault-mind/SKILL.md +50 -50
  109. package/skills/vault-mind/references/tool-reference.md +25 -25
  110. package/skills/vault-mind-broadcaster/SKILL.md +1 -1
  111. package/skills/vault-mind-heavy-lifter/SKILL.md +3 -3
  112. package/skills/vault-mind-manager/SKILL.md +7 -7
  113. package/skills/vault-mind-miner/SKILL.md +11 -11
  114. package/skills/vault-mind-setup/SKILL.md +65 -265
  115. package/skills/vault-mind-setup/references/obsidian-vault-structure.md +6 -6
  116. package/skills/vault-mind-setup/references/pi-extension-wiring.md +24 -33
  117. package/skills/vault-mind-setup/references/troubleshooting-tree.md +25 -26
  118. package/dist/src/auto-index.d.ts +0 -47
  119. package/dist/src/auto-index.js +0 -122
@@ -8,10 +8,12 @@ import { DEFAULT_CONFIG } from "./types.js";
8
8
  import { CONFIG_FILES, EXT_ROOT, collectionNames, ensureDir, findConfig, getPiContextConfig, hasPiContextTools, loadConfig, } from "./utils.js";
9
9
  import { updateActiveCollectionWidget } from "./widget.js";
10
10
  import { cancelJobHelper, listJobs, retryJob, } from "./agent-queue.js";
11
+ import { revokeToken, rotateToken } from "./auth.js";
11
12
  import { handleDiscoverSchema } from "./discover-schema.js";
12
13
  import { runAskIntake } from "./intake.js";
13
- import { connect, pullOllamaModel, testOllamaConnection } from "./lance.js";
14
+ import { connect, searchHybrid } from "./lance.js";
14
15
  import { MODAL_TOKEN_ENV, createModalClient, isModalConfigured, resolveBaseUrl, resolveModalToken, resolveWorkspace, } from "./modal-config.js";
16
+ import { runPersonalize } from "./personalize.js";
15
17
  import { createServerState } from "./server.js";
16
18
  import { createCollectionWizard, createInjectorWizard, openSettingsDashboard, setupWizard, } from "./settings-ui.js";
17
19
  import { reindexRemote, syncAll, syncCollection } from "./sync.js";
@@ -19,48 +21,51 @@ import { tombstoneEntryById } from "./tombstone.js";
19
21
  import { expandHome } from "./utils.js";
20
22
  import { createWatcherState, getWatcherStatus, startWatcher, stopWatcher } from "./watcher.js";
21
23
  // ── Shared helpers ───────────────────────────────────────────────────────────
22
- const WIKI_USAGE = [
24
+ const VM_USAGE = [
23
25
  "**pi-vault-mind Commands**",
24
26
  "",
25
- " /wiki init Scaffold config + collections",
26
- " /wiki validate Check LanceDB, config, and collection health",
27
- " /wiki approve [collection] Batch-review pending entries",
28
- " /wiki tombstone <collection> <entry-id> Redact an entry in place",
29
- " /wiki settings Open interactive settings dashboard",
30
- " /wiki audit Audit config for missing defaults",
31
- " /wiki reindex Rebuild FTS + vector indexes (replaces old /qmd-index)",
32
- " /wiki collection select Select active collection",
33
- " /wiki collection create Create a new collection (wizard)",
34
- " /wiki discover-schema Infer schema from a JSONL file",
35
- " /wiki injector create Create a new injector (wizard)",
36
- " /wiki context enable|disable Enable/disable pi-context integration",
37
- " /wiki context status Show pi-context integration status",
38
- " /wiki embedding status Show embedding config + Ollama models",
39
- " /wiki embedding use Switch provider (ollama | transformers | modal)",
40
- " /wiki embedding model Set Ollama embedding model",
41
- " /wiki embedding models List available Ollama models",
42
- " /wiki embedding pull Pull a model from Ollama",
43
- " /wiki modal status Show Modal config + health + remote collections",
44
- " /wiki modal config Set Modal baseUrl/model/dim/sync/fallback",
45
- " /wiki modal sync Pull server vectors into local LanceDB [--full]",
46
- " /wiki modal jobs <id> Poll a Modal bulk job",
47
- " /wiki modal migrate <model> Change canonical model + re-embed (remote)",
48
- " /wiki watcher start Start passive file watcher",
49
- " /wiki watcher stop Stop passive file watcher",
50
- " /wiki watcher status Show watcher status",
51
- " /wiki watcher queue [status] Show agent dispatch queue (pending|running|done|failed|cancelled|all)",
52
- " /wiki watcher retry <id> Retry a failed or cancelled agent job",
53
- " /wiki watcher cancel <id> Cancel a pending agent job",
54
- " /wiki server status Show HTTP server status + port",
55
- " /wiki setup Interactive global config wizard",
56
- " /wiki setup --vault <path> CLI: set vault path",
57
- " /wiki setup --provider <p> CLI: set embedding provider",
58
- " /wiki help Show this help",
27
+ " /vm init Scaffold config + collections",
28
+ " /vm validate Check LanceDB, config, and collection health",
29
+ " /vm validate --audit Audit config for missing defaults",
30
+ " /vm approve [collection] Batch-review pending entries",
31
+ " /vm query <collection> <query> Hybrid semantic + FTS search",
32
+ " /vm tombstone <collection> <entry-id> Redact an entry in place",
33
+ " /vm settings Open interactive settings dashboard",
34
+ " /vm collection select Select active collection",
35
+ " /vm collection list List configured collections",
36
+ " /vm collection create Create a new collection (wizard)",
37
+ " /vm collection reindex Rebuild FTS + vector indexes (replaces old /qmd-index)",
38
+ " /vm discover-schema Infer schema from a JSONL file",
39
+ " /vm injector create Create a new injector (wizard)",
40
+ " /vm context enable|disable Enable/disable pi-context integration",
41
+ " /vm embedding status Show embedding config",
42
+ " /vm remote status Show remote provider config + health + collections",
43
+ " /vm remote config Set remote provider baseUrl/model/dim/sync/fallback",
44
+ " /vm remote auto One-command remote setup (workspace → URL, model discovery)",
45
+ " /vm remote sync Pull server vectors into local LanceDB [--full]",
46
+ " /vm remote jobs <id> Poll a remote bulk embed job",
47
+ " /vm watcher start Start passive file watcher",
48
+ " /vm watcher stop Stop passive file watcher",
49
+ " /vm watcher status Show watcher status",
50
+ " /vm watcher queue [status] Show agent dispatch queue (pending|running|done|failed|cancelled|all)",
51
+ " /vm watcher retry <id> Retry a failed or cancelled agent job",
52
+ " /vm watcher cancel <id> Cancel a pending agent job",
53
+ " /vm server status Show HTTP server status + port",
54
+ " /vm personalize Analyze vault and suggest personality tuning for system.md + AGENTS.md",
55
+ " /vm setup Interactive global config wizard",
56
+ " /vm setup --vault <path> CLI: set vault path",
57
+ " /vm setup --remoteUrl <url> CLI: set remote embedding URL",
58
+ " /vm setup --localUrl <url> CLI: set local embedding URL",
59
+ " /vm token Show API token source and masked value",
60
+ " /vm token show Show API token source and masked value",
61
+ " /vm token rotate Generate + persist a new API token (old token invalidated)",
62
+ " /vm token revoke \u003cidentity\u003e Remove PVM_TOKEN_\u003cIDENTITY\u003e from ~/.pi/agent/vault-mind.env",
63
+ " /vm help Show this help",
59
64
  ].join("\n");
60
65
  export const auditConfig = async (ctx) => {
61
66
  const { project: cfgPath } = findConfig(ctx.cwd);
62
67
  if (!cfgPath) {
63
- ctx.ui.notify("No project config file found to audit. Run /wiki init first.", "info");
68
+ ctx.ui.notify("No project config file found to audit. Run /vm init first.", "info");
64
69
  return;
65
70
  }
66
71
  const projectCfg = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
@@ -156,7 +161,7 @@ export const selectActiveCollection = async (ctx) => {
156
161
  ctx.ui.notify(`Active collection set to: ${result}`, "info");
157
162
  }
158
163
  };
159
- // ── /wiki init ──────────────────────────────────────────────────────────────
164
+ // ── /vm init ──────────────────────────────────────────────────────────────
160
165
  /**
161
166
  * Vault-root `.gitignore` entries that keep pi-vault-mind compatible with
162
167
  * `obsidian-git` (and, transitively, Obsidian Sync setups that also back up via
@@ -230,12 +235,10 @@ const handleInit = async (_args, ctx, pi) => {
230
235
  artifactPath: "collections/artifact.md",
231
236
  },
232
237
  ],
233
- wiki: {
238
+ vaultMind: {
234
239
  dataDir: ".lancedb",
235
240
  embedding: {
236
- provider: "transformers",
237
- ollamaModel: "embeddinggemma",
238
- ollamaHost: "http://127.0.0.1:11434",
241
+ model: "embeddinggemma",
239
242
  },
240
243
  ftsEnabled: true,
241
244
  graph: { enabled: true, canvasSync: false },
@@ -247,10 +250,12 @@ const handleInit = async (_args, ctx, pi) => {
247
250
  else {
248
251
  skipped.push(cfgDest);
249
252
  }
250
- for (const [name, def] of Object.entries(cfg.collections)) {
253
+ // Reload the config we just wrote so we scaffold the collections it defines.
254
+ const effectiveCfg = fs.existsSync(cfgDest) ? loadConfig(ctx.cwd) : cfg;
255
+ for (const [name, def] of Object.entries(effectiveCfg.collections)) {
251
256
  ensureFile(def.path, name === "pending" ? "PENDING_LEDGER.jsonl" : "UCL_LEDGER.jsonl");
252
257
  }
253
- for (const ij of cfg.injectors) {
258
+ for (const ij of effectiveCfg.injectors) {
254
259
  if (ij.artifactPath)
255
260
  ensureFile(ij.artifactPath, "ARTIFACT.md");
256
261
  }
@@ -270,7 +275,7 @@ const handleInit = async (_args, ctx, pi) => {
270
275
  skipped.push(gitignoreDest);
271
276
  }
272
277
  const msg = [
273
- "Wiki scaffolding complete.",
278
+ "Vault-mind scaffolding complete.",
274
279
  "",
275
280
  "Created:",
276
281
  ...created.map((c) => ` • ${path.relative(ctx.cwd, c)}`),
@@ -303,20 +308,28 @@ const handleInit = async (_args, ctx, pi) => {
303
308
  }
304
309
  }
305
310
  };
306
- // ── /wiki validate ───────────────────────────────────────────────────────────
311
+ // ── /vm validate ───────────────────────────────────────────────────────────
307
312
  const handleValidate = async (_args, ctx, pi) => {
313
+ const flags = new Set(_args
314
+ .trim()
315
+ .split(/\s+/)
316
+ .filter(Boolean)
317
+ .map((t) => t.toLowerCase()));
318
+ if (flags.has("--audit")) {
319
+ return auditConfig(ctx);
320
+ }
308
321
  const cfg = loadConfig(ctx.cwd);
309
322
  const lines = [];
310
323
  try {
311
- await connect(cfg.wiki.dataDir);
312
- lines.push(`✅ LanceDB: Connected to ${cfg.wiki.dataDir}`);
324
+ await connect(cfg.vaultMind.dataDir);
325
+ lines.push(`✅ LanceDB: Connected to ${cfg.vaultMind.dataDir}`);
313
326
  }
314
327
  catch (err) {
315
328
  lines.push(`❌ LanceDB: Connection failed. ${err.message}`);
316
329
  }
317
330
  // Check LanceDB tables
318
331
  try {
319
- const db = await connect(cfg.wiki.dataDir);
332
+ const db = await connect(cfg.vaultMind.dataDir);
320
333
  const tableNames = await db.tableNames();
321
334
  lines.push(`📊 LanceDB tables: ${tableNames.length > 0 ? tableNames.join(", ") : "(none — created on first append)"}`);
322
335
  }
@@ -341,13 +354,13 @@ const handleValidate = async (_args, ctx, pi) => {
341
354
  lines.push(` - Config enabled: ${contextCfg.enabled ? "✅ Yes" : "❌ No"}`);
342
355
  lines.push(` - Will capture events: ${willCapture ? "✅ Yes — on next turn" : "❌ No"}`);
343
356
  if (hasContext && !contextCfg.enabled)
344
- lines.push(" → Run /wiki context enable to activate");
357
+ lines.push(" → Run /vm context enable to activate");
345
358
  if (!hasContext && contextCfg.enabled)
346
359
  lines.push(" ⚠️ Config says enabled but tools missing. Install with: pi install npm:pi-context");
347
360
  if (ctx.hasUI)
348
361
  ctx.ui.notify(lines.join("\n"), "info");
349
362
  };
350
- // ── /wiki approve ────────────────────────────────────────────────────────────
363
+ // ── /vm approve ────────────────────────────────────────────────────────────
351
364
  const handleApprove = async (args, ctx) => {
352
365
  const cfg = loadConfig(ctx.cwd);
353
366
  const pending = cfg.collections.pending;
@@ -394,11 +407,106 @@ const handleApprove = async (args, ctx) => {
394
407
  fs.writeFileSync(pending.path, rejected.join("\n") + (rejected.length > 0 ? "\n" : ""));
395
408
  ctx.ui.notify(`Approved ${approved} → "${targetName}". Rejected ${rejected.length}.`, "info");
396
409
  };
397
- // ── /wiki tombstone ─────────────────────────────────────────────────────────
410
+ // ── /vm query ─────────────────────────────────────────────────────────────
411
+ const handleQuery = async (args, ctx) => {
412
+ const parts = args.trim().split(/\s+/).filter(Boolean);
413
+ if (parts.length === 0) {
414
+ ctx.ui.notify("Usage: /vm query [collection] <query>", "error");
415
+ return;
416
+ }
417
+ const cfg = loadConfig(ctx.cwd);
418
+ let collection = getActiveCollection(ctx.cwd) || "main";
419
+ let query = args.trim();
420
+ if (parts.length > 1 && cfg.collections[parts[0]]) {
421
+ collection = parts[0];
422
+ query = parts.slice(1).join(" ");
423
+ }
424
+ const def = cfg.collections[collection];
425
+ if (!def) {
426
+ ctx.ui.notify(`Unknown collection "${collection}". Run /vm init first.`, "error");
427
+ return;
428
+ }
429
+ try {
430
+ const results = await searchHybrid(cfg.vaultMind.dataDir, collection, query, 20, cfg.vaultMind);
431
+ if (results.length === 0) {
432
+ ctx.ui.notify(`No results for "${query}" in "${collection}".`, "info");
433
+ return;
434
+ }
435
+ // Rich TUI table when UI is available; plain text fallback for RPC
436
+ if (ctx.hasUI) {
437
+ await renderQueryResults(ctx, collection, query, results);
438
+ }
439
+ else {
440
+ const lines = [
441
+ `**Search results (${results.length}) for "${query}" in "${collection}":**`,
442
+ "",
443
+ ];
444
+ for (const r of results) {
445
+ const row = r;
446
+ const fact = String(row.fact ?? row.text ?? "(no text)");
447
+ lines.push(`- ${fact.slice(0, 120)}${fact.length > 120 ? "..." : ""}`);
448
+ }
449
+ ctx.ui.notify(lines.join("\n"), "info");
450
+ }
451
+ }
452
+ catch (err) {
453
+ ctx.ui.notify(`Search failed: ${err.message}`, "error");
454
+ }
455
+ };
456
+ /** Render search results as a scrollable TUI table with column headers. */
457
+ async function renderQueryResults(ctx, collection, query, results) {
458
+ // Infer columns from the first result
459
+ const first = results[0];
460
+ const keys = first ? Object.keys(first).filter((k) => !k.startsWith("_")) : [];
461
+ const displayKeys = keys.length > 0 ? keys : ["(raw)"];
462
+ // Build rows: each row is an array of truncated cell values
463
+ const colWidths = displayKeys.map((k) => Math.min(k.length + 2, 20));
464
+ const rows = [];
465
+ for (const r of results) {
466
+ const row = r;
467
+ const cells = displayKeys.map((k, i) => {
468
+ const raw = String(row[k] ?? "");
469
+ const max = colWidths[i];
470
+ return raw.length > max ? `${raw.slice(0, max - 1)}…` : raw.padEnd(max);
471
+ });
472
+ rows.push(cells);
473
+ }
474
+ // Header
475
+ const header = displayKeys.map((k, i) => k.padEnd(colWidths[i])).join(" ");
476
+ const divider = "─".repeat(header.length);
477
+ const lines = [
478
+ `${results.length} results for "${query}" in ${collection}`,
479
+ divider,
480
+ header,
481
+ divider,
482
+ ...rows.map((r) => r.join(" ")),
483
+ ];
484
+ await ctx.ui.custom((tui, theme, _keybindings, done) => {
485
+ const container = new Container();
486
+ let y = 0;
487
+ container.addChild(new Text(theme.fg("accent", lines[0]), 1, ++y));
488
+ container.addChild(new Text(theme.fg("dim", lines[1]), 1, ++y));
489
+ container.addChild(new Text(theme.fg("accent", lines[2]), 1, ++y));
490
+ container.addChild(new Text(theme.fg("dim", lines[3]), 1, ++y));
491
+ for (let i = 4; i < lines.length; i++) {
492
+ container.addChild(new Text(lines[i], 1, ++y));
493
+ }
494
+ container.addChild(new Text(theme.fg("dim", "esc to close"), 1, ++y));
495
+ return {
496
+ render: (w) => container.render(w),
497
+ handleInput: (data) => {
498
+ if (data === "\x1b")
499
+ done(null);
500
+ },
501
+ invalidate: () => container.invalidate(),
502
+ };
503
+ }, { overlay: true });
504
+ }
505
+ // ── /vm tombstone ─────────────────────────────────────────────────────────
398
506
  const handleTombstone = async (args, ctx) => {
399
507
  const parts = args.trim().split(/\s+/).filter(Boolean);
400
508
  if (parts.length < 2) {
401
- ctx.ui.notify("Usage: /wiki tombstone <collection> <entry-id>", "error");
509
+ ctx.ui.notify("Usage: /vm tombstone <collection> <entry-id>", "error");
402
510
  return;
403
511
  }
404
512
  const collection = parts[0];
@@ -412,7 +520,7 @@ const handleTombstone = async (args, ctx) => {
412
520
  const result = tombstoneEntryById(def.path, entryId);
413
521
  ctx.ui.notify(result.message, result.success ? "info" : "error");
414
522
  };
415
- // ── /wiki reindex ────────────────────────────────────────────────────────────
523
+ // ── /vm reindex ────────────────────────────────────────────────────────────
416
524
  const handleReindex = async (args, ctx, pi) => {
417
525
  const cfg = loadConfig(ctx.cwd);
418
526
  const tokens = args.trim().split(/\s+/).filter(Boolean);
@@ -423,12 +531,12 @@ const handleReindex = async (args, ctx, pi) => {
423
531
  const collectionFilter = reindexAll || rebuildEmbeddings ? null : (tokens.find((t) => !t.startsWith("--")) ?? null);
424
532
  // Remote bulk re-index: read JSONL → submit Modal bulk job → poll → sync down.
425
533
  if (remote) {
426
- if (cfg.wiki.embedding.provider !== "modal") {
427
- ctx.ui.notify("--remote requires the modal provider. Run /wiki embedding use modal.", "error");
534
+ if (!cfg.vaultMind.embedding.remoteUrl) {
535
+ ctx.ui.notify("--remote requires remoteUrl. Set it up via /vm setup.", "error");
428
536
  return;
429
537
  }
430
- if (!isModalConfigured(cfg.wiki)) {
431
- ctx.ui.notify(`Modal not configured. Set baseUrl (/wiki modal config baseUrl) and ${MODAL_TOKEN_ENV}.`, "error");
538
+ if (!isModalConfigured(cfg.vaultMind)) {
539
+ ctx.ui.notify(`Remote provider not configured. Set baseUrl (/vm remote config baseUrl) and ${MODAL_TOKEN_ENV}.`, "error");
432
540
  return;
433
541
  }
434
542
  const names = reindexAll
@@ -466,7 +574,7 @@ const handleReindex = async (args, ctx, pi) => {
466
574
  continue;
467
575
  const tableName = `collection_${name}`;
468
576
  try {
469
- const db = await connect(cfg.wiki.dataDir);
577
+ const db = await connect(cfg.vaultMind.dataDir);
470
578
  const existing = await db.tableNames();
471
579
  if (!existing.includes(tableName)) {
472
580
  lines.push(` ⚠️ ${tableName}: table not found (created on first append)`);
@@ -486,7 +594,7 @@ const handleReindex = async (args, ctx, pi) => {
486
594
  else {
487
595
  // Rebuild FTS index
488
596
  try {
489
- if (cfg.wiki.ftsEnabled !== false) {
597
+ if (cfg.vaultMind.ftsEnabled !== false) {
490
598
  await table.createIndex("fact", { config: lancedb.Index.fts(), replace: true });
491
599
  lines.push(` ✅ ${tableName}: FTS index rebuilt`);
492
600
  }
@@ -515,7 +623,7 @@ const handleReindex = async (args, ctx, pi) => {
515
623
  if (reindexAll) {
516
624
  for (const graphTable of ["entities", "relations"]) {
517
625
  try {
518
- const db = await connect(cfg.wiki.dataDir);
626
+ const db = await connect(cfg.vaultMind.dataDir);
519
627
  const existing = await db.tableNames();
520
628
  if (existing.includes(graphTable)) {
521
629
  const table = await db.openTable(graphTable);
@@ -534,10 +642,10 @@ const handleReindex = async (args, ctx, pi) => {
534
642
  lines.unshift("**Reindex Report:**", "");
535
643
  lines.push("", rebuildEmbeddings
536
644
  ? "Entries will be re-embedded with current embedding model on next append."
537
- : "Use /wiki reindex --reembed to regenerate embeddings after model switch.");
645
+ : "Use /vm reindex --reembed to regenerate embeddings after model switch.");
538
646
  ctx.ui.notify(lines.join("\n"), "info");
539
647
  };
540
- // ── /wiki embedding ──────────────────────────────────────────────────────────
648
+ // ── /vm embedding ──────────────────────────────────────────────────────────
541
649
  const handleEmbedding = async (args, ctx, pi) => {
542
650
  const cfg = loadConfig(ctx.cwd);
543
651
  const parts = args.trim().split(/\s+/g);
@@ -545,151 +653,58 @@ const handleEmbedding = async (args, ctx, pi) => {
545
653
  const value = parts.slice(1).join(" ");
546
654
  const { project: cfgPath } = findConfig(ctx.cwd);
547
655
  if (!cfgPath) {
548
- ctx.ui.notify("No config found. Run /wiki init first.", "error");
656
+ ctx.ui.notify("No config found. Run /vm init first.", "error");
549
657
  return;
550
658
  }
551
659
  switch (subcommand) {
552
660
  case "status": {
661
+ const emb = cfg.vaultMind.embedding;
553
662
  const lines = [
554
663
  "**Embedding Configuration**",
555
664
  "",
556
- `Provider: ${cfg.wiki.embedding.provider}`,
557
- cfg.wiki.embedding.provider === "ollama"
558
- ? `Model: ${cfg.wiki.embedding.ollamaModel || "embeddinggemma"}`
559
- : cfg.wiki.embedding.provider === "modal"
560
- ? `Model: ${cfg.wiki.embedding.modal?.model || "(default embeddinggemma)"}`
561
- : "Model: all-MiniLM-L6-v2 (384 dims)",
562
- `FTS: ${cfg.wiki.ftsEnabled !== false ? "enabled" : "disabled"}`,
563
- `Graph: ${cfg.wiki.graph?.enabled !== false ? "enabled" : "disabled"}`,
564
- `Data Dir: ${cfg.wiki.dataDir}`,
665
+ `Remote URL: ${emb.remoteUrl || "none"}`,
666
+ `Local URL: ${emb.localUrl || "none"}`,
667
+ `Model: ${emb.model || "(default)"}`,
668
+ `API Key: ${emb.apiKey ? "configured" : "none"}`,
669
+ `Dim: ${emb.dim || "(native)"}`,
670
+ `FTS: ${cfg.vaultMind.ftsEnabled !== false ? "enabled" : "disabled"}`,
671
+ `Graph: ${cfg.vaultMind.graph?.enabled !== false ? "enabled" : "disabled"}`,
672
+ `Data Dir: ${cfg.vaultMind.dataDir}`,
565
673
  ];
566
- if (cfg.wiki.embedding.provider === "modal") {
567
- const modal = cfg.wiki.embedding.modal;
568
- const tokenSrc = process.env[MODAL_TOKEN_ENV]
569
- ? `env ${MODAL_TOKEN_ENV} ✅`
570
- : modal?.apiToken
571
- ? "config (set env PVM_API_TOKEN to override)"
572
- : "❌ none (set PVM_API_TOKEN env)";
573
- lines.push("", "**Modal:**", ` Base URL: ${modal?.baseUrl || "❌ not set"}`, ` Model: ${modal?.model || "(default embeddinggemma)"}`, ` Dim: ${modal?.dim ?? "(native)"}`, ` Token: ${tokenSrc}`, ` Fallback: ${modal?.fallback?.enabled === false ? "disabled" : modal?.fallback?.provider || "(none — degrade to FTS)"}`, ` Sync: auto=${modal?.sync?.autoSync ? "on" : "off"}, interval=${modal?.sync?.autoSyncIntervalMs ?? 300000}ms`);
574
- const co = cfg.wiki.embedding.coalesce;
674
+ if (emb.modal) {
675
+ const modal = emb.modal;
676
+ lines.push("", "**Remote Provider (Modal):**", ` Workspace: ${modal.workspace || "(default)"}`, ` Fallback: ${modal.fallback?.enabled === false ? "disabled" : modal.fallback?.provider || "(none — degrade to FTS)"}`, ` Sync: auto=${modal.sync?.autoSync ? "on" : "off"}, interval=${modal.sync?.autoSyncIntervalMs ?? 300000}ms`);
677
+ const co = emb.coalesce;
575
678
  if (co) {
576
679
  lines.push(` Coalesce: debounce=${co.debounceMs ?? 1000}ms, batch=${co.maxBatchSize ?? 64}, concurrency=${co.maxConcurrentFlushes ?? 2}, dedupe=${co.dedupe ?? true}, searchBypass=${co.searchBypass ?? true}`);
577
680
  }
578
681
  }
579
- else if (cfg.wiki.embedding.provider === "ollama" || !cfg.wiki.embedding.provider) {
580
- const conn = await testOllamaConnection(pi);
581
- lines.push("", "**Ollama Status:**", ` Reachable: ${conn.reachable ? "✅ Yes" : "❌ No"}`);
582
- if (conn.error)
583
- lines.push(` Error: ${conn.error}`);
584
- if (conn.models.length > 0) {
585
- lines.push(` Available models (${conn.models.length}):`);
586
- for (const m of conn.models.slice(0, 10)) {
587
- const isCurrent = m.name === cfg.wiki.embedding.ollamaModel;
588
- lines.push(` ${isCurrent ? "★" : " "} ${m.name} (${m.size})`);
589
- }
590
- if (conn.models.length > 10)
591
- lines.push(` ... and ${conn.models.length - 10} more`);
592
- }
593
- else {
594
- lines.push(" No models found. Pull one with: /wiki embedding pull embeddinggemma");
595
- }
596
- }
597
- else {
598
- lines.push("", "**Transformers:** Using Xenova/all-MiniLM-L6-v2 (384 dims)", " Downloads on first use and caches locally.");
599
- }
600
682
  ctx.ui.notify(lines.join("\n"), "info");
601
683
  return;
602
684
  }
603
- case "use": {
604
- if (!value || !["ollama", "transformers", "modal"].includes(value)) {
605
- ctx.ui.notify("/wiki embedding use <ollama|transformers|modal>", "error");
606
- return;
607
- }
608
- if (value === "ollama") {
609
- const conn = await testOllamaConnection(pi);
610
- if (!conn.reachable) {
611
- const confirm = await ctx.ui.confirm("Ollama Not Reachable", "Cannot connect to Ollama. Switch anyway?\n\nVerify with:\n ollama list\n ollama pull embeddinggemma");
612
- if (!confirm)
613
- return;
614
- }
615
- }
616
- if (value === "modal") {
617
- const modal = cfg.wiki.embedding.modal;
618
- if (!modal?.baseUrl) {
619
- ctx.ui.notify("Modal needs a base URL. Set it with: /wiki modal config baseUrl <url>\n(Token via PVM_API_TOKEN env, preferred.)", "warning");
620
- }
621
- }
622
- const existing = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
623
- existing.wiki = existing.wiki || {};
624
- existing.wiki.embedding = existing.wiki.embedding || {};
625
- existing.wiki.embedding.provider = value;
626
- fs.writeFileSync(cfgPath, `${JSON.stringify(existing, null, 2)}\n`, "utf-8");
627
- ctx.ui.notify(`✅ Embedding provider set to: ${value}. Restart session for changes to take effect.`, "info");
628
- return;
629
- }
630
- case "model": {
631
- if (!value) {
632
- ctx.ui.notify(`Current: ${cfg.wiki.embedding.ollamaModel || "embeddinggemma"}. Usage: /wiki embedding model <name>`, "info");
633
- return;
634
- }
635
- const existing = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
636
- existing.wiki = existing.wiki || {};
637
- existing.wiki.embedding = existing.wiki.embedding || {};
638
- existing.wiki.embedding.ollamaModel = value;
639
- fs.writeFileSync(cfgPath, `${JSON.stringify(existing, null, 2)}\n`, "utf-8");
640
- ctx.ui.notify(`✅ Ollama embedding model set to: ${value}`, "info");
641
- return;
642
- }
643
- case "models": {
644
- const conn = await testOllamaConnection(pi);
645
- if (!conn.reachable) {
646
- ctx.ui.notify("❌ Ollama not reachable.", "error");
647
- return;
648
- }
649
- if (conn.models.length === 0) {
650
- ctx.ui.notify("No models found. Pull one with: /wiki embedding pull embeddinggemma", "info");
651
- return;
652
- }
653
- const lines = ["**Available Ollama Models:**", ""];
654
- for (const m of conn.models) {
655
- lines.push(` ${m.name === cfg.wiki.embedding.ollamaModel ? "★" : " "} ${m.name} (${m.size})`);
656
- }
657
- ctx.ui.notify(lines.join("\n"), "info");
658
- return;
659
- }
660
- case "pull": {
661
- if (!value) {
662
- ctx.ui.notify("/wiki embedding pull <model>. Try: embeddinggemma", "error");
663
- return;
664
- }
665
- ctx.ui.notify(`Pulling "${value}" from Ollama... (this may take a while)`, "info");
666
- const result = await pullOllamaModel(value, pi);
667
- ctx.ui.notify(result.message, result.success ? "info" : "error");
668
- return;
669
- }
670
685
  default: {
671
- ctx.ui.notify(`Unknown: ${subcommand}. Try: status, use, model, models, pull`, "error");
686
+ ctx.ui.notify(`Unknown: ${subcommand}. Try: status`, "error");
672
687
  return;
673
688
  }
674
689
  }
675
690
  };
676
- // ── /wiki modal ──────────────────────────────────────────────────────────────
691
+ // ── /vm remote (Modal provider) ─────────────────────────────────────────────
677
692
  const MODAL_CONFIG_USAGE = [
678
- "**/wiki modal config**",
693
+ "**/vm remote config**",
679
694
  "",
680
- " /wiki modal config baseUrl <url> Set the Modal ASGI base URL",
681
- " /wiki modal config workspace <name> Derive the Modal URL from workspace slug",
682
- " /wiki modal config model <name> Set the canonical embedder (default embeddinggemma)",
683
- " /wiki modal config dim <n> Set output dimension (omit for native)",
684
- " /wiki modal config fallback ollama|none Set offline fallback provider",
685
- " /wiki modal config sync auto on|off Toggle auto-sync",
686
- " /wiki modal config sync interval <ms> Auto-sync interval",
687
- " /wiki modal config pageSize <n> Sync page size",
688
- " /wiki modal config coalesce debounce <ms> Coalescer debounce window",
689
- " /wiki modal config coalesce batch <n> Coalescer max batch size",
690
- " /wiki modal config token Show token guidance (use PVM_API_TOKEN env)",
695
+ " /vm remote config baseUrl <url> Set the remote ASGI base URL",
696
+ " /vm remote config workspace <name> Derive the Modal URL from workspace slug",
697
+ " /vm remote config model <name> Set the canonical embedder (default embeddinggemma)",
698
+ " /vm remote config dim <n> Set output dimension (omit for native)",
699
+ " /vm remote config fallback ollama|none Set offline fallback provider",
700
+ " /vm remote config sync auto on|off Toggle auto-sync",
701
+ " /vm remote config sync interval <ms> Auto-sync interval",
702
+ " /vm remote config pageSize <n> Sync page size",
703
+ " /vm remote config coalesce debounce <ms> Coalescer debounce window",
704
+ " /vm remote config coalesce batch <n> Coalescer max batch size",
705
+ " /vm remote config token Show token guidance (use PVM_API_TOKEN env)",
691
706
  "",
692
- " (no args) Show current Modal config",
707
+ " (no args) Show current remote config",
693
708
  ].join("\n");
694
709
  /** Read the raw project config object (mutable). */
695
710
  const readProjectConfig = (cfgPath) => {
@@ -704,41 +719,41 @@ const writeProjectConfig = (cfgPath, obj) => {
704
719
  fs.writeFileSync(cfgPath, `${JSON.stringify(obj, null, 2)}\n`, "utf-8");
705
720
  };
706
721
  const modalSection = (obj) => {
707
- obj.wiki = obj.wiki || {};
708
- const wiki = obj.wiki;
709
- wiki.embedding = wiki.embedding || {};
710
- const emb = wiki.embedding;
722
+ obj.vaultMind = obj.vaultMind || {};
723
+ const vaultMind = obj.vaultMind;
724
+ vaultMind.embedding = vaultMind.embedding || {};
725
+ const emb = vaultMind.embedding;
711
726
  emb.modal = emb.modal || {};
712
727
  return emb.modal;
713
728
  };
714
729
  const handleModalConfig = async (args, ctx) => {
715
730
  const { project: cfgPath } = findConfig(ctx.cwd);
716
731
  if (!cfgPath) {
717
- ctx.ui.notify("No config found. Run /wiki init first.", "error");
732
+ ctx.ui.notify("No config found. Run /vm init first.", "error");
718
733
  return;
719
734
  }
720
735
  const cfg = loadConfig(ctx.cwd);
721
736
  const parts = args.trim().split(/\s+/).filter(Boolean);
722
737
  const key = parts[0]?.toLowerCase();
723
- const modal = cfg.wiki.embedding.modal ?? {};
738
+ const modal = cfg.vaultMind.embedding.modal ?? {};
724
739
  if (!key) {
725
- const tokenSrc = resolveModalToken(cfg.wiki)
740
+ const tokenSrc = resolveModalToken(cfg.vaultMind)
726
741
  ? "env or ~/.pi/agent/vault-mind.env ✅"
727
742
  : modal.apiToken
728
743
  ? "config (set PVM_API_TOKEN env/dotenv to override)"
729
744
  : "❌ none (set PVM_API_TOKEN env or ~/.pi/agent/vault-mind.env)";
730
- const derivedUrl = resolveBaseUrl(cfg.wiki);
745
+ const derivedUrl = resolveBaseUrl(cfg.vaultMind);
731
746
  const lines = [
732
747
  "**Modal Config:**",
733
748
  "",
734
- ` workspace: ${resolveWorkspace(cfg.wiki) || "(not set)"}`,
749
+ ` workspace: ${resolveWorkspace(cfg.vaultMind) || "(not set)"}`,
735
750
  ` baseUrl: ${modal.baseUrl || (derivedUrl ? `${derivedUrl} (derived)` : "❌ not set")}`,
736
751
  ` model: ${modal.model || "(default embeddinggemma)"}`,
737
752
  ` dim: ${modal.dim ?? "(native)"}`,
738
753
  ` token: ${tokenSrc}`,
739
754
  ` fallback: ${modal.fallback?.enabled === false ? "disabled" : modal.fallback?.provider || "(none)"}`,
740
755
  ` sync: ${JSON.stringify(modal.sync ?? {})}`,
741
- ` coalesce: ${JSON.stringify(cfg.wiki.embedding.coalesce ?? {})}`,
756
+ ` coalesce: ${JSON.stringify(cfg.vaultMind.embedding.coalesce ?? {})}`,
742
757
  ];
743
758
  ctx.ui.notify(lines.join("\n"), "info");
744
759
  return;
@@ -759,7 +774,7 @@ const handleModalConfig = async (args, ctx) => {
759
774
  case "url": {
760
775
  const url = parts[1];
761
776
  if (!url) {
762
- ctx.ui.notify("/wiki modal config baseUrl <url>", "error");
777
+ ctx.ui.notify("/vm remote config baseUrl <url>", "error");
763
778
  return;
764
779
  }
765
780
  m.baseUrl = url.replace(/\/$/, "");
@@ -769,7 +784,7 @@ const handleModalConfig = async (args, ctx) => {
769
784
  }
770
785
  case "model": {
771
786
  if (!parts[1]) {
772
- ctx.ui.notify("/wiki modal config model <name>", "error");
787
+ ctx.ui.notify("/vm remote config model <name>", "error");
773
788
  return;
774
789
  }
775
790
  m.model = parts[1];
@@ -793,7 +808,7 @@ const handleModalConfig = async (args, ctx) => {
793
808
  m.fallback = { enabled: true, provider: v };
794
809
  }
795
810
  else {
796
- ctx.ui.notify("/wiki modal config fallback <ollama|transformers|none>", "error");
811
+ ctx.ui.notify("/vm remote config fallback <ollama|transformers|none>", "error");
797
812
  return;
798
813
  }
799
814
  writeProjectConfig(cfgPath, obj);
@@ -819,7 +834,7 @@ const handleModalConfig = async (args, ctx) => {
819
834
  sync.collections = parts.slice(2);
820
835
  }
821
836
  else {
822
- ctx.ui.notify("/wiki modal config sync <auto on|off|interval <ms>|pageSize <n>|collections ...>", "error");
837
+ ctx.ui.notify("/vm remote config sync <auto on|off|interval <ms>|pageSize <n>|collections ...>", "error");
823
838
  return;
824
839
  }
825
840
  m.sync = sync;
@@ -829,8 +844,8 @@ const handleModalConfig = async (args, ctx) => {
829
844
  }
830
845
  case "coalesce": {
831
846
  const sub = parts[1]?.toLowerCase();
832
- const wiki = obj.wiki || {};
833
- const emb = wiki.embedding || {};
847
+ const vaultMind = obj.vaultMind || {};
848
+ const emb = vaultMind.embedding || {};
834
849
  const co = emb.coalesce || {};
835
850
  if (sub === "debounce") {
836
851
  if (!setNum(co, "debounceMs", parts[2] ?? ""))
@@ -845,7 +860,7 @@ const handleModalConfig = async (args, ctx) => {
845
860
  return;
846
861
  }
847
862
  else {
848
- ctx.ui.notify("/wiki modal config coalesce <debounce|batch|concurrency> <n>", "error");
863
+ ctx.ui.notify("/vm remote config coalesce <debounce|batch|concurrency> <n>", "error");
849
864
  return;
850
865
  }
851
866
  emb.coalesce = co;
@@ -868,24 +883,24 @@ const handleModal = async (args, ctx, pi) => {
868
883
  const rest = parts.slice(1).join(" ");
869
884
  switch (sub) {
870
885
  case "status": {
871
- const modal = cfg.wiki.embedding.modal;
872
- const tokenSrc = resolveModalToken(cfg.wiki)
886
+ const modal = cfg.vaultMind.embedding.modal;
887
+ const tokenSrc = resolveModalToken(cfg.vaultMind)
873
888
  ? "env or ~/.pi/agent/vault-mind.env ✅"
874
889
  : modal?.apiToken
875
890
  ? "config"
876
891
  : "❌ none";
877
- const derivedUrl = resolveBaseUrl(cfg.wiki);
892
+ const derivedUrl = resolveBaseUrl(cfg.vaultMind);
878
893
  const lines = [
879
- "**Modal Status**",
894
+ "**Remote Status**",
880
895
  "",
881
- `Configured: ${isModalConfigured(cfg.wiki) ? "✅" : "❌"}`,
882
- ` workspace: ${resolveWorkspace(cfg.wiki) || "(not set)"}`,
896
+ `Configured: ${isModalConfigured(cfg.vaultMind) ? "✅" : "❌"}`,
897
+ ` workspace: ${resolveWorkspace(cfg.vaultMind) || "(not set)"}`,
883
898
  ` baseUrl: ${modal?.baseUrl || (derivedUrl ? `${derivedUrl} (derived)` : "(not set)")}`,
884
899
  ` model: ${modal?.model || "(default embeddinggemma)"}`,
885
900
  ` dim: ${modal?.dim ?? "(native)"}`,
886
901
  ` token: ${tokenSrc}`,
887
902
  ];
888
- const client = createModalClient(cfg.wiki);
903
+ const client = createModalClient(cfg.vaultMind);
889
904
  if (client) {
890
905
  try {
891
906
  const health = await client.health();
@@ -911,8 +926,8 @@ const handleModal = async (args, ctx, pi) => {
911
926
  await handleModalConfig(rest, ctx);
912
927
  return;
913
928
  case "sync": {
914
- if (!isModalConfigured(cfg.wiki)) {
915
- ctx.ui.notify(`Modal not configured. Set baseUrl + ${MODAL_TOKEN_ENV} first.`, "error");
929
+ if (!isModalConfigured(cfg.vaultMind)) {
930
+ ctx.ui.notify(`Remote provider not configured. Set baseUrl + ${MODAL_TOKEN_ENV} first.`, "error");
916
931
  return;
917
932
  }
918
933
  const syncTokens = rest.split(/\s+/).filter(Boolean);
@@ -938,12 +953,12 @@ const handleModal = async (args, ctx, pi) => {
938
953
  case "jobs": {
939
954
  const jobId = parts[1];
940
955
  if (!jobId) {
941
- ctx.ui.notify("/wiki modal jobs <job_id> — poll a Modal bulk job.\n(Server-side job listing is pending upstream.)", "info");
956
+ ctx.ui.notify("/vm remote jobs <job_id> — poll a Modal bulk job.\n(Server-side job listing is pending upstream.)", "info");
942
957
  return;
943
958
  }
944
- const client = createModalClient(cfg.wiki);
959
+ const client = createModalClient(cfg.vaultMind);
945
960
  if (!client) {
946
- ctx.ui.notify("Modal not configured.", "error");
961
+ ctx.ui.notify("Remote provider not configured.", "error");
947
962
  return;
948
963
  }
949
964
  try {
@@ -965,20 +980,20 @@ const handleModal = async (args, ctx, pi) => {
965
980
  case "auto": {
966
981
  const cfgPath = findConfig(ctx.cwd).project;
967
982
  if (!cfgPath) {
968
- ctx.ui.notify("No project config found. Run /wiki init first.", "error");
983
+ ctx.ui.notify("No project config found. Run /vm init first.", "error");
969
984
  return;
970
985
  }
971
- const workspace = resolveWorkspace(cfg.wiki);
972
- if (!workspace && !cfg.wiki.embedding.modal?.baseUrl) {
973
- ctx.ui.notify("Modal workspace or baseUrl not configured. Run:\n /wiki modal config workspace <name>", "error");
986
+ const workspace = resolveWorkspace(cfg.vaultMind);
987
+ if (!workspace && !cfg.vaultMind.embedding.modal?.baseUrl) {
988
+ ctx.ui.notify("Modal workspace or baseUrl not configured. Run:\n /vm remote config workspace <name>", "error");
974
989
  }
975
- const token = resolveModalToken(cfg.wiki);
990
+ const token = resolveModalToken(cfg.vaultMind);
976
991
  if (!token) {
977
992
  ctx.ui.notify("Modal token not found. Set PVM_API_TOKEN env or write it to ~/.pi/agent/vault-mind.env", "error");
978
993
  return;
979
994
  }
980
995
  ctx.ui.notify("🔄 Auto-configuring Modal...", "info");
981
- const client = createModalClient(cfg.wiki);
996
+ const client = createModalClient(cfg.vaultMind);
982
997
  if (!client) {
983
998
  ctx.ui.notify("Could not create Modal client (missing workspace/baseUrl or token).", "error");
984
999
  return;
@@ -1002,8 +1017,8 @@ const handleModal = async (args, ctx, pi) => {
1002
1017
  }
1003
1018
  const defaultModel = models.default || health.default_model || "embeddinggemma";
1004
1019
  const obj = readProjectConfig(cfgPath);
1005
- const wiki = obj.wiki || {};
1006
- const emb = wiki.embedding || {};
1020
+ const vaultMind = obj.vaultMind || {};
1021
+ const emb = vaultMind.embedding || {};
1007
1022
  emb.provider = "modal";
1008
1023
  const modalSec = emb.modal || {};
1009
1024
  if (workspace)
@@ -1020,16 +1035,16 @@ const handleModal = async (args, ctx, pi) => {
1020
1035
  autoSyncIntervalMs: 300000,
1021
1036
  };
1022
1037
  emb.modal = modalSec;
1023
- wiki.embedding = emb;
1024
- obj.wiki = wiki;
1038
+ vaultMind.embedding = emb;
1039
+ obj.vaultMind = vaultMind;
1025
1040
  writeProjectConfig(cfgPath, obj);
1026
1041
  const lines = [
1027
1042
  "✅ Modal auto-configured:",
1028
- ` baseUrl: ${resolveBaseUrl(cfg.wiki)}`,
1043
+ ` baseUrl: ${resolveBaseUrl(cfg.vaultMind)}`,
1029
1044
  ` model: ${defaultModel}`,
1030
1045
  ` dim: ${models.default_dim ?? "native"}`,
1031
1046
  "",
1032
- "Run /wiki modal status to verify.",
1047
+ "Run /vm remote status to verify.",
1033
1048
  ];
1034
1049
  ctx.ui.notify(lines.join("\n"), "info");
1035
1050
  return;
@@ -1037,7 +1052,7 @@ const handleModal = async (args, ctx, pi) => {
1037
1052
  case "migrate": {
1038
1053
  const newModel = parts[1];
1039
1054
  if (!newModel) {
1040
- ctx.ui.notify("/wiki modal migrate <newModel> [dim] — change the canonical model + re-embed (remote).\nOld namespace is left intact until verified.", "info");
1055
+ ctx.ui.notify("/vm remote migrate <newModel> [dim] — change the canonical model + re-embed (remote).\nOld namespace is left intact until verified.", "info");
1041
1056
  return;
1042
1057
  }
1043
1058
  const newDim = parts[2] ? Number.parseInt(parts[2], 10) : undefined;
@@ -1077,17 +1092,17 @@ const handleModal = async (args, ctx, pi) => {
1077
1092
  return;
1078
1093
  }
1079
1094
  default:
1080
- ctx.ui.notify("Unknown /wiki modal subcommand. Try: status, config, sync, jobs, migrate", "error");
1095
+ ctx.ui.notify("Unknown /vm remote subcommand. Try: status, config, sync, jobs, migrate", "error");
1081
1096
  }
1082
1097
  };
1083
- // ── /wiki context ────────────────────────────────────────────────────────────
1098
+ // ── /vm context ────────────────────────────────────────────────────────────
1084
1099
  const handleContext = async (args, ctx, pi) => {
1085
1100
  const parts = args.trim().split(/\s+/g);
1086
1101
  const subcommand = parts[0]?.toLowerCase() || "status";
1087
1102
  const value = parts[1]?.toLowerCase();
1088
1103
  const { project: cfgPath } = findConfig(ctx.cwd);
1089
1104
  if (!cfgPath) {
1090
- ctx.ui.notify("No config found. Run /wiki init first.", "error");
1105
+ ctx.ui.notify("No config found. Run /vm init first.", "error");
1091
1106
  return;
1092
1107
  }
1093
1108
  switch (subcommand) {
@@ -1155,22 +1170,40 @@ const _toggleContext = (cfgPath, enabled) => {
1155
1170
  };
1156
1171
  fs.writeFileSync(cfgPath, `${JSON.stringify(existing, null, 2)}\n`, "utf-8");
1157
1172
  };
1158
- // ── /wiki collection ─────────────────────────────────────────────────────────────
1159
- const handleCollection = async (args, ctx) => {
1173
+ // ── /vm collection ─────────────────────────────────────────────────────────────
1174
+ const handleCollection = async (args, ctx, pi) => {
1160
1175
  const parts = args.trim().split(/\s+/g);
1161
1176
  const subcommand = parts[0]?.toLowerCase() || "select";
1162
1177
  switch (subcommand) {
1163
1178
  case "select":
1164
1179
  await selectActiveCollection(ctx);
1165
1180
  return;
1181
+ case "list": {
1182
+ const cfg = loadConfig(ctx.cwd);
1183
+ const names = Object.keys(cfg.collections);
1184
+ if (names.length === 0) {
1185
+ ctx.ui.notify("No collections configured.", "info");
1186
+ return;
1187
+ }
1188
+ const lines = [
1189
+ "**Configured collections:**",
1190
+ "",
1191
+ ...names.map((n) => ` • ${n} → ${cfg.collections[n].path}`),
1192
+ ];
1193
+ ctx.ui.notify(lines.join("\n"), "info");
1194
+ return;
1195
+ }
1166
1196
  case "create":
1167
1197
  await createCollectionWizard(ctx);
1168
1198
  return;
1199
+ case "reindex":
1200
+ await handleReindex(parts.slice(1).join(" "), ctx, pi);
1201
+ return;
1169
1202
  default:
1170
- ctx.ui.notify(`Unknown: ${subcommand}. Try: select, create`, "error");
1203
+ ctx.ui.notify(`Unknown: ${subcommand}. Try: select, list, create, reindex`, "error");
1171
1204
  }
1172
1205
  };
1173
- // ── /wiki injector ───────────────────────────────────────────────────────────
1206
+ // ── /vm injector ───────────────────────────────────────────────────────────
1174
1207
  const handleInjector = async (args, ctx) => {
1175
1208
  const parts = args.trim().split(/\s+/g);
1176
1209
  const subcommand = parts[0]?.toLowerCase() || "create";
@@ -1184,7 +1217,7 @@ const handleInjector = async (args, ctx) => {
1184
1217
  };
1185
1218
  // ── Watcher ──────────────────────────────────────────────────────────────────
1186
1219
  export const watcherState = createWatcherState();
1187
- /** Module-level server state, referenced by the /wiki server command.
1220
+ /** Module-level server state, referenced by the /vm server command.
1188
1221
  * Port is set when the server starts in index.ts. */
1189
1222
  export const serverState = createServerState();
1190
1223
  const handleServer = (ctx) => {
@@ -1209,21 +1242,21 @@ const handleWatcher = async (args, ctx, pi) => {
1209
1242
  const subcommand = parts[0]?.toLowerCase() || "status";
1210
1243
  const defaultVaultPath = () => {
1211
1244
  const cfg = loadConfig(ctx.cwd);
1212
- const p = cfg.wiki.vaults?.default?.path;
1245
+ const p = cfg.vaultMind.vaults?.default?.path;
1213
1246
  return p ? expandHome(p) : undefined;
1214
1247
  };
1215
1248
  switch (subcommand) {
1216
1249
  case "start": {
1217
1250
  const cfg = loadConfig(ctx.cwd);
1218
- if (!cfg.wiki.vaults || Object.keys(cfg.wiki.vaults).length === 0) {
1219
- ctx.ui.notify("No vaults configured. Add vaults to wiki.vaults in pi-vault-mind.config.json first.", "error");
1251
+ if (!cfg.vaultMind.vaults || Object.keys(cfg.vaultMind.vaults).length === 0) {
1252
+ ctx.ui.notify("No vaults configured. Add vaults to vaultMind.vaults in pi-vault-mind.config.json first.", "error");
1220
1253
  return;
1221
1254
  }
1222
1255
  if (watcherState.running) {
1223
1256
  ctx.ui.notify("Watcher is already running.", "info");
1224
1257
  return;
1225
1258
  }
1226
- startWatcher(pi, cfg.wiki.vaults, watcherState);
1259
+ startWatcher(pi, cfg.vaultMind.vaults, watcherState);
1227
1260
  ctx.ui.notify(getWatcherStatus(watcherState), "info");
1228
1261
  return;
1229
1262
  }
@@ -1239,7 +1272,7 @@ const handleWatcher = async (args, ctx, pi) => {
1239
1272
  case "queue": {
1240
1273
  const vaultPath = defaultVaultPath();
1241
1274
  if (!vaultPath) {
1242
- ctx.ui.notify("No default vault configured. Set one with /wiki setup --vault <path>.", "error");
1275
+ ctx.ui.notify("No default vault configured. Set one with /vm setup --vault <path>.", "error");
1243
1276
  return;
1244
1277
  }
1245
1278
  const statusArg = parts[1]?.toLowerCase();
@@ -1270,7 +1303,7 @@ const handleWatcher = async (args, ctx, pi) => {
1270
1303
  case "retry": {
1271
1304
  const id = parts[1];
1272
1305
  if (!id) {
1273
- ctx.ui.notify("Usage: /wiki watcher retry <job-id>", "error");
1306
+ ctx.ui.notify("Usage: /vm watcher retry <job-id>", "error");
1274
1307
  return;
1275
1308
  }
1276
1309
  const vaultPath = defaultVaultPath();
@@ -1284,29 +1317,27 @@ const handleWatcher = async (args, ctx, pi) => {
1284
1317
  return;
1285
1318
  }
1286
1319
  const job = result.job;
1287
- // Re-create the dispatch by re-sending the task as a user message
1288
- const taskDescription = [
1289
- `Retry of dispatch ${id} for @agent-${job.role} in ${job.filePath}.`,
1290
- "",
1291
- "Please dispatch a subagent to handle it:",
1292
- "",
1293
- "```",
1294
- "subagent({",
1295
- ` agent: "${job.agentName}",`,
1296
- ` task: ${JSON.stringify(job.instruction)},`,
1297
- ` context: "fork",`,
1298
- " async: true,",
1299
- "})",
1300
- "```",
1301
- ].join("\n");
1302
- pi.sendUserMessage(taskDescription, { deliverAs: "followUp" });
1303
- ctx.ui.notify(`✅ Retried job ${id} → ${job.agentName}`, "info");
1320
+ // Re-dispatch via the internal agent bus
1321
+ const bus = watcherState.bus;
1322
+ if (bus) {
1323
+ void bus.publish("JOB_AVAILABLE", "watcher-retry", {
1324
+ dispatchId: job.id,
1325
+ role: job.role,
1326
+ filePath: job.filePath,
1327
+ instruction: job.instruction,
1328
+ vaultPath,
1329
+ });
1330
+ ctx.ui.notify(`✅ Retried job ${id} → ${job.agentName} (via agent bus)`, "info");
1331
+ }
1332
+ else {
1333
+ ctx.ui.notify(`⚠️ Agent bus not available — cannot retry job ${id}`, "warning");
1334
+ }
1304
1335
  return;
1305
1336
  }
1306
1337
  case "cancel": {
1307
1338
  const id = parts[1];
1308
1339
  if (!id) {
1309
- ctx.ui.notify("Usage: /wiki watcher cancel <job-id>", "error");
1340
+ ctx.ui.notify("Usage: /vm watcher cancel <job-id>", "error");
1310
1341
  return;
1311
1342
  }
1312
1343
  const vaultPath = defaultVaultPath();
@@ -1330,7 +1361,7 @@ const handleWatcher = async (args, ctx, pi) => {
1330
1361
  };
1331
1362
  // ── Setup ────────────────────────────────────────────────────────────────────
1332
1363
  const handleSetup = async (args, ctx) => {
1333
- // Parse optional CLI-style args: --vault <path> --provider <name> --model <name> --workspace <name>
1364
+ // Parse optional CLI-style args: --vault <path> --remoteUrl <url> --localUrl <url> --model <name> --workspace <name>
1334
1365
  const cliArgs = {};
1335
1366
  const parts = args.trim().split(/\s+--/);
1336
1367
  for (const part of parts) {
@@ -1340,21 +1371,24 @@ const handleSetup = async (args, ctx) => {
1340
1371
  else if (trimmed === "vault") {
1341
1372
  /* flag without value, handled by interactive */
1342
1373
  }
1343
- else if (trimmed.startsWith("provider "))
1344
- cliArgs.provider = trimmed.slice(9).trim();
1374
+ else if (trimmed.startsWith("remoteUrl "))
1375
+ cliArgs.remoteUrl = trimmed.slice(10).trim();
1376
+ else if (trimmed.startsWith("localUrl "))
1377
+ cliArgs.localUrl = trimmed.slice(9).trim();
1345
1378
  else if (trimmed.startsWith("model "))
1346
1379
  cliArgs.model = trimmed.slice(6).trim();
1347
1380
  else if (trimmed.startsWith("workspace "))
1348
1381
  cliArgs.workspace = trimmed.slice(10).trim();
1349
1382
  }
1350
- const hasCliArgs = cliArgs.vault || cliArgs.provider || cliArgs.model || cliArgs.workspace;
1383
+ const hasCliArgs = cliArgs.vault || cliArgs.remoteUrl || cliArgs.localUrl || cliArgs.model || cliArgs.workspace;
1351
1384
  await setupWizard(ctx, hasCliArgs ? cliArgs : undefined);
1385
+ ctx.ui.notify("Vault is configured. Run /vm personalize to tune the agent personality for this vault.", "info");
1352
1386
  };
1353
- // ── /wiki intake ─────────────────────────────────────────────────────────────
1387
+ // ── /vm intake ─────────────────────────────────────────────────────────────
1354
1388
  const handleIntake = async (args, ctx) => {
1355
1389
  const filePath = args.trim();
1356
1390
  if (!filePath) {
1357
- ctx.ui.notify("Usage: /wiki intake <path-to-json-spec>", "error");
1391
+ ctx.ui.notify("Usage: /vm intake <path-to-json-spec>", "error");
1358
1392
  return;
1359
1393
  }
1360
1394
  if (!fs.existsSync(filePath)) {
@@ -1381,7 +1415,7 @@ const handleIntake = async (args, ctx) => {
1381
1415
  ctx.ui.notify("Intake complete. Answers stored.", "info");
1382
1416
  }
1383
1417
  };
1384
- // ── /wiki modal token ────────────────────────────────────────────────────────
1418
+ // ── /vm remote token ────────────────────────────────────────────────────────
1385
1419
  /** Dotenv path where the Modal token is persisted. */
1386
1420
  const MODAL_TOKEN_ENV_PATH = path.join(process.env.HOME || process.env.USERPROFILE || "", ".pi", "agent", "vault-mind.env");
1387
1421
  const writeTokenEnv = (token) => {
@@ -1421,7 +1455,7 @@ const handleModalToken = async (ctx, cliToken) => {
1421
1455
  return;
1422
1456
  }
1423
1457
  if (!ctx.hasUI) {
1424
- ctx.ui.notify("No 1Password token found and no TUI available. Use:\n /wiki modal token <token>", "error");
1458
+ ctx.ui.notify("No 1Password token found and no TUI available. Use:\n /vm remote token <token>", "error");
1425
1459
  return;
1426
1460
  }
1427
1461
  const token = await ctx.ui.input("Modal API token:", "paste bearer token here (will be written to ~/.pi/agent/vault-mind.env)");
@@ -1432,9 +1466,90 @@ const handleModalToken = async (ctx, cliToken) => {
1432
1466
  writeTokenEnv(token);
1433
1467
  ctx.ui.notify(`✅ Wrote token to ${MODAL_TOKEN_ENV_PATH}`, "info");
1434
1468
  };
1435
- // ── Main /wiki command ───────────────────────────────────────────────────────
1469
+ // ── /vm token ──────────────────────────────────────────────────────────────
1470
+ const maskToken = (token) => {
1471
+ if (token.length <= 8)
1472
+ return "****";
1473
+ return `${token.slice(0, 4)}…${token.slice(-4)}`;
1474
+ };
1475
+ const handleToken = (args, ctx) => {
1476
+ const parts = args.trim().split(/\s+/g).filter(Boolean);
1477
+ const sub = parts[0]?.toLowerCase() || "show";
1478
+ switch (sub) {
1479
+ case "show": {
1480
+ let source;
1481
+ let token = null;
1482
+ if (process.env.PVM_API_TOKEN) {
1483
+ source = "env (PVM_API_TOKEN)";
1484
+ token = process.env.PVM_API_TOKEN;
1485
+ }
1486
+ else {
1487
+ source = "none";
1488
+ try {
1489
+ const text = fs.readFileSync(MODAL_TOKEN_ENV_PATH, "utf-8");
1490
+ for (const line of text.split(/\r?\n/)) {
1491
+ const m = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*("([^"]*)"|'([^']*)'|[^\s#]+)/);
1492
+ if (!m || m[1] !== "PVM_API_TOKEN")
1493
+ continue;
1494
+ const val = m[3] ?? m[4] ?? m[2];
1495
+ if (val) {
1496
+ token = val;
1497
+ source = "~/.pi/agent/vault-mind.env";
1498
+ break;
1499
+ }
1500
+ }
1501
+ }
1502
+ catch {
1503
+ // file missing or unreadable
1504
+ }
1505
+ if (!token) {
1506
+ const cfg = loadConfig(ctx.cwd);
1507
+ const cfgToken = cfg?.vaultMind?.embedding?.modal?.apiToken;
1508
+ if (cfgToken) {
1509
+ token = cfgToken;
1510
+ source = "config (vaultMind.embedding.modal.apiToken)";
1511
+ }
1512
+ }
1513
+ }
1514
+ const masked = token ? maskToken(token) : "(not set)";
1515
+ ctx.ui.notify(["**API Token**", `Source: ${source}`, `Value: ${masked}`].join("\n"), token ? "info" : "warning");
1516
+ return;
1517
+ }
1518
+ case "rotate": {
1519
+ const newToken = rotateToken();
1520
+ const masked = maskToken(newToken);
1521
+ const envNote = process.env.PVM_API_TOKEN
1522
+ ? "\n\u26a0\ufe0f PVM_API_TOKEN env var is still set and overrides the file until you unset it and restart."
1523
+ : "";
1524
+ ctx.ui.notify([
1525
+ `\u2705 New token: ${masked}`,
1526
+ "Written to ~/.pi/agent/vault-mind.env.",
1527
+ `\u26a0\ufe0f All clients (Obsidian plugin, scripts) must update to the new token — the old token is now invalid.${envNote}`,
1528
+ ].join("\n"), "warning");
1529
+ return;
1530
+ }
1531
+ case "revoke": {
1532
+ const identity = parts[1];
1533
+ if (!identity) {
1534
+ ctx.ui.notify("Usage: /vm token revoke <identity>", "error");
1535
+ return;
1536
+ }
1537
+ const ok = revokeToken(identity);
1538
+ if (ok) {
1539
+ ctx.ui.notify(`Revoked token for identity: ${identity}`, "info");
1540
+ }
1541
+ else {
1542
+ ctx.ui.notify(`No token found for identity: ${identity}`, "warning");
1543
+ }
1544
+ return;
1545
+ }
1546
+ default:
1547
+ ctx.ui.notify(`Unknown: ${sub}. Try: show, rotate, revoke`, "error");
1548
+ }
1549
+ };
1550
+ // ── Main /vm command ───────────────────────────────────────────────────────
1436
1551
  export const registerCommands = (pi) => {
1437
- pi.registerCommand("wiki", {
1552
+ pi.registerCommand("vm", {
1438
1553
  description: "pi-vault-mind: manage collections, embedding, reindexing, and config.",
1439
1554
  getArgumentCompletions: (_prefix) => {
1440
1555
  const words = _prefix.trim().split(/\s+/);
@@ -1442,31 +1557,32 @@ export const registerCommands = (pi) => {
1442
1557
  "init",
1443
1558
  "validate",
1444
1559
  "approve",
1560
+ "query",
1445
1561
  "tombstone",
1446
1562
  "settings",
1447
- "audit",
1448
1563
  "help",
1449
- "reindex",
1450
1564
  "collection",
1451
1565
  "discover-schema",
1452
1566
  "injector",
1453
1567
  "context",
1454
1568
  "embedding",
1455
- "modal",
1569
+ "remote",
1456
1570
  "watcher",
1571
+ "personalize",
1457
1572
  "setup",
1573
+ "token",
1458
1574
  ];
1459
1575
  if (words.length === 1 || (words.length === 2 && words[1] === "")) {
1460
1576
  return top
1461
1577
  .filter((c) => c.startsWith(words[0] || ""))
1462
- .map((c) => ({ label: c, value: c, description: `wiki ${c}` }));
1578
+ .map((c) => ({ label: c, value: c, description: `vm ${c}` }));
1463
1579
  }
1464
1580
  // Subcommand completions
1465
1581
  const subcommand = words[0];
1466
1582
  if (words.length >= 2) {
1467
1583
  const prefix = words[1] || "";
1468
1584
  if (subcommand === "embedding") {
1469
- return ["status", "use", "model", "models", "pull"]
1585
+ return ["status", "use", "model", "models", "pull", "cloud"]
1470
1586
  .filter((c) => c.startsWith(prefix))
1471
1587
  .map((c) => ({ label: c, value: c, description: `embedding ${c}` }));
1472
1588
  }
@@ -1480,8 +1596,14 @@ export const registerCommands = (pi) => {
1480
1596
  .filter((c) => c.startsWith(prefix))
1481
1597
  .map((c) => ({ label: c, value: c, description: `discover-schema ${c}` }));
1482
1598
  }
1599
+ if (subcommand === "query") {
1600
+ const names = collectionNames(loadConfig(process.cwd()));
1601
+ return names
1602
+ .filter((c) => c.startsWith(prefix))
1603
+ .map((c) => ({ label: c, value: c, description: `query ${c}` }));
1604
+ }
1483
1605
  if (subcommand === "collection") {
1484
- return ["select", "create"]
1606
+ return ["select", "list", "create", "reindex"]
1485
1607
  .filter((c) => c.startsWith(prefix))
1486
1608
  .map((c) => ({ label: c, value: c, description: `collection ${c}` }));
1487
1609
  }
@@ -1490,26 +1612,26 @@ export const registerCommands = (pi) => {
1490
1612
  .filter((c) => c.startsWith(prefix))
1491
1613
  .map((c) => ({ label: c, value: c, description: `injector ${c}` }));
1492
1614
  }
1493
- if (subcommand === "reindex") {
1494
- return ["--all", "--reembed", "--remote"]
1495
- .filter((c) => c.startsWith(prefix))
1496
- .map((c) => ({ label: c, value: c, description: `reindex ${c}` }));
1497
- }
1498
- if (subcommand === "modal") {
1499
- return ["status", "config", "auto", "token", "sync", "jobs", "migrate"]
1500
- .filter((c) => c.startsWith(prefix))
1501
- .map((c) => ({ label: c, value: c, description: `modal ${c}` }));
1502
- }
1503
1615
  if (subcommand === "watcher") {
1504
1616
  return ["start", "stop", "status", "queue", "retry", "cancel"]
1505
1617
  .filter((c) => c.startsWith(prefix))
1506
1618
  .map((c) => ({ label: c, value: c, description: `watcher ${c}` }));
1507
1619
  }
1508
1620
  if (subcommand === "setup") {
1509
- return ["--vault", "--provider", "--model", "--workspace"]
1621
+ return ["--vault", "--remoteUrl", "--localUrl", "--model", "--workspace"]
1510
1622
  .filter((c) => c.startsWith(prefix))
1511
1623
  .map((c) => ({ label: c, value: c, description: `setup ${c}` }));
1512
1624
  }
1625
+ if (subcommand === "token") {
1626
+ return ["show", "rotate", "revoke"]
1627
+ .filter((c) => c.startsWith(prefix))
1628
+ .map((c) => ({ label: c, value: c, description: `token ${c}` }));
1629
+ }
1630
+ if (subcommand === "remote") {
1631
+ return ["status", "config", "auto", "sync", "jobs", "token"]
1632
+ .filter((c) => c.startsWith(prefix))
1633
+ .map((c) => ({ label: c, value: c, description: `remote ${c}` }));
1634
+ }
1513
1635
  }
1514
1636
  return [];
1515
1637
  },
@@ -1524,38 +1646,40 @@ export const registerCommands = (pi) => {
1524
1646
  return handleValidate(rest, ctx, pi);
1525
1647
  case "approve":
1526
1648
  return handleApprove(rest, ctx);
1649
+ case "query":
1650
+ return handleQuery(rest, ctx);
1527
1651
  case "tombstone":
1528
1652
  return handleTombstone(rest, ctx);
1529
1653
  case "settings":
1530
1654
  return openSettingsDashboard(ctx);
1531
- case "audit":
1532
- return auditConfig(ctx);
1533
- case "reindex":
1534
- return handleReindex(rest, ctx, pi);
1535
1655
  case "discover-schema":
1536
1656
  return handleDiscoverSchema(rest, ctx);
1537
1657
  case "collection":
1538
- return handleCollection(rest, ctx);
1658
+ return handleCollection(rest, ctx, pi);
1539
1659
  case "injector":
1540
1660
  return handleInjector(rest, ctx);
1541
1661
  case "context":
1542
1662
  return handleContext(rest, ctx, pi);
1543
1663
  case "embedding":
1544
1664
  return handleEmbedding(rest, ctx, pi);
1545
- case "modal":
1546
- return handleModal(rest, ctx, pi);
1547
1665
  case "server":
1548
1666
  return handleServer(ctx);
1549
1667
  case "watcher":
1550
1668
  return handleWatcher(rest, ctx, pi);
1669
+ case "remote":
1670
+ return handleModal(rest, ctx, pi);
1671
+ case "personalize":
1672
+ return runPersonalize(ctx, pi);
1551
1673
  case "setup":
1552
1674
  return handleSetup(rest, ctx);
1675
+ case "token":
1676
+ return handleToken(rest, ctx);
1553
1677
  default:
1554
- ctx.ui.notify(WIKI_USAGE, "info");
1678
+ ctx.ui.notify(VM_USAGE, "info");
1555
1679
  }
1556
1680
  },
1557
1681
  });
1558
- // Legacy aliases — /wiki-* commands delegate to /wiki subcommand handlers
1682
+ // Legacy aliases — /vm-* commands delegate to /vm subcommand handlers
1559
1683
  /* ── /intake ───────────────────────────────────────────────────────────── */
1560
1684
  pi.registerCommand("intake", {
1561
1685
  description: "Run an intake questionnaire from a JSON spec file. Optionally write the result to --output.",