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.
- package/CHANGELOG.md +20 -0
- package/README.md +44 -44
- package/agents/broadcaster.agent.md +30 -0
- package/agents/heavy-lifter.agent.md +30 -0
- package/agents/manager.agent.md +29 -0
- package/agents/miner.agent.md +29 -0
- package/dist/src/agent-bus/Agent.d.ts +49 -0
- package/dist/src/agent-bus/Agent.js +97 -0
- package/dist/src/agent-bus/AgentLoader.d.ts +33 -0
- package/dist/src/agent-bus/AgentLoader.js +102 -0
- package/dist/src/agent-bus/AgentRegistry.d.ts +17 -0
- package/dist/src/agent-bus/AgentRegistry.js +20 -0
- package/dist/src/agent-bus/LLMProvider.d.ts +36 -0
- package/dist/src/agent-bus/LLMProvider.js +125 -0
- package/dist/src/agent-bus/MessageBus.d.ts +47 -0
- package/dist/src/agent-bus/MessageBus.js +81 -0
- package/dist/src/agent-bus/TaskQueue.d.ts +27 -0
- package/dist/src/agent-bus/TaskQueue.js +57 -0
- package/dist/src/agent-bus/index.d.ts +6 -0
- package/dist/src/agent-bus/index.js +6 -0
- package/dist/src/agents/BroadcasterAgent.d.ts +19 -0
- package/dist/src/agents/BroadcasterAgent.js +70 -0
- package/dist/src/agents/HeavyLifterAgent.d.ts +26 -0
- package/dist/src/agents/HeavyLifterAgent.js +73 -0
- package/dist/src/agents/ManagerAgent.d.ts +20 -0
- package/dist/src/agents/ManagerAgent.js +71 -0
- package/dist/src/agents/MinerAgent.d.ts +19 -0
- package/dist/src/agents/MinerAgent.js +70 -0
- package/dist/src/agents/index.d.ts +4 -0
- package/dist/src/agents/index.js +4 -0
- package/dist/src/auth.d.ts +49 -2
- package/dist/src/auth.js +149 -7
- package/dist/src/autosync.d.ts +1 -1
- package/dist/src/autosync.js +3 -3
- package/dist/src/bridge.d.ts +45 -0
- package/dist/src/bridge.js +189 -0
- package/dist/src/commands.d.ts +1 -1
- package/dist/src/commands.js +419 -295
- package/dist/src/context-capture.d.ts +22 -0
- package/dist/src/context-capture.js +41 -0
- package/dist/src/discover-schema.d.ts +1 -1
- package/dist/src/discover-schema.js +4 -4
- package/dist/src/embed-queue.d.ts +1 -1
- package/dist/src/embed-queue.js +1 -1
- package/dist/src/engine.d.ts +65 -0
- package/dist/src/engine.js +244 -0
- package/dist/src/events.js +1 -1
- package/dist/src/graph.d.ts +3 -3
- package/dist/src/identity-injector.d.ts +15 -0
- package/dist/src/identity-injector.js +102 -0
- package/dist/src/index.js +88 -49
- package/dist/src/intake.js +4 -4
- package/dist/src/lance.d.ts +11 -37
- package/dist/src/lance.js +183 -240
- package/dist/src/modal-client.d.ts +14 -4
- package/dist/src/modal-client.js +50 -34
- package/dist/src/modal-config.d.ts +26 -10
- package/dist/src/modal-config.js +36 -5
- package/dist/src/personalize.d.ts +47 -0
- package/dist/src/personalize.js +352 -0
- package/dist/src/server.d.ts +30 -4
- package/dist/src/server.js +146 -64
- package/dist/src/settings-ui.d.ts +3 -1
- package/dist/src/settings-ui.js +84 -77
- package/dist/src/status.d.ts +25 -0
- package/dist/src/status.js +49 -0
- package/dist/src/sync.d.ts +1 -1
- package/dist/src/sync.js +19 -19
- package/dist/src/tombstone.js +2 -2
- package/dist/src/tools.js +112 -130
- package/dist/src/types.d.ts +86 -18
- package/dist/src/types.js +7 -4
- package/dist/src/utils.d.ts +5 -1
- package/dist/src/utils.js +24 -8
- package/dist/src/vault-tools.d.ts +29 -0
- package/dist/src/vault-tools.js +314 -0
- package/dist/src/vault-writer.d.ts +4 -5
- package/dist/src/vault-writer.js +13 -13
- package/dist/src/vm-handlers.d.ts +15 -0
- package/dist/src/vm-handlers.js +469 -0
- package/dist/src/watcher.d.ts +18 -4
- package/dist/src/watcher.js +109 -98
- package/dist/test/auth.test.js +57 -7
- package/dist/test/bridge-identity.test.js +388 -0
- package/dist/test/discover-schema.test.js +2 -2
- package/dist/test/engine-identity.test.js +119 -0
- package/dist/test/index.test.js +5 -6
- package/dist/test/lance-modal.test.js +7 -7
- package/dist/test/modal-config.test.js +51 -29
- package/dist/test/personalize.test.js +212 -0
- package/dist/test/rest-queue.test.js +1 -1
- package/dist/test/rest-setup.test.js +21 -21
- package/dist/test/{rest-wiki.test.js → rest-vm.test.js} +27 -23
- package/dist/test/server-ws.test.js +4 -4
- package/dist/test/sync.test.js +2 -2
- package/dist/test/tombstone.test.js +1 -1
- package/dist/test/vault-tools.test.js +224 -0
- package/package.json +2 -1
- package/scripts/check-deps.sh +207 -0
- package/scripts/e2e-ws.mjs +4 -4
- package/scripts/modal-e2e-smoke.mjs +1 -1
- package/scripts/setup-test-config.sh +1 -5
- package/scripts/test-slash-commands.mjs +197 -0
- package/skills/.agents/broadcaster.agent.md +31 -0
- package/skills/.agents/heavy-lifter.agent.md +31 -0
- package/skills/.agents/manager.agent.md +30 -0
- package/skills/.agents/miner.agent.md +30 -0
- package/skills/vault-mind/SKILL.md +50 -50
- package/skills/vault-mind/references/tool-reference.md +25 -25
- package/skills/vault-mind-broadcaster/SKILL.md +1 -1
- package/skills/vault-mind-heavy-lifter/SKILL.md +3 -3
- package/skills/vault-mind-manager/SKILL.md +7 -7
- package/skills/vault-mind-miner/SKILL.md +11 -11
- package/skills/vault-mind-setup/SKILL.md +65 -265
- package/skills/vault-mind-setup/references/obsidian-vault-structure.md +6 -6
- package/skills/vault-mind-setup/references/pi-extension-wiring.md +24 -33
- package/skills/vault-mind-setup/references/troubleshooting-tree.md +25 -26
- package/dist/src/auto-index.d.ts +0 -47
- package/dist/src/auto-index.js +0 -122
package/dist/src/commands.js
CHANGED
|
@@ -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,
|
|
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
|
|
24
|
+
const VM_USAGE = [
|
|
23
25
|
"**pi-vault-mind Commands**",
|
|
24
26
|
"",
|
|
25
|
-
" /
|
|
26
|
-
" /
|
|
27
|
-
" /
|
|
28
|
-
" /
|
|
29
|
-
" /
|
|
30
|
-
" /
|
|
31
|
-
" /
|
|
32
|
-
" /
|
|
33
|
-
" /
|
|
34
|
-
" /
|
|
35
|
-
" /
|
|
36
|
-
" /
|
|
37
|
-
" /
|
|
38
|
-
" /
|
|
39
|
-
" /
|
|
40
|
-
" /
|
|
41
|
-
" /
|
|
42
|
-
" /
|
|
43
|
-
" /
|
|
44
|
-
" /
|
|
45
|
-
" /
|
|
46
|
-
" /
|
|
47
|
-
" /
|
|
48
|
-
" /
|
|
49
|
-
" /
|
|
50
|
-
" /
|
|
51
|
-
" /
|
|
52
|
-
" /
|
|
53
|
-
" /
|
|
54
|
-
" /
|
|
55
|
-
" /
|
|
56
|
-
" /
|
|
57
|
-
" /
|
|
58
|
-
" /
|
|
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 /
|
|
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
|
-
// ── /
|
|
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
|
-
|
|
238
|
+
vaultMind: {
|
|
234
239
|
dataDir: ".lancedb",
|
|
235
240
|
embedding: {
|
|
236
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
"
|
|
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
|
-
// ── /
|
|
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.
|
|
312
|
-
lines.push(`✅ LanceDB: Connected to ${cfg.
|
|
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.
|
|
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 /
|
|
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
|
-
// ── /
|
|
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
|
-
// ── /
|
|
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: /
|
|
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
|
-
// ── /
|
|
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.
|
|
427
|
-
ctx.ui.notify("--remote requires
|
|
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.
|
|
431
|
-
ctx.ui.notify(`
|
|
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.
|
|
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.
|
|
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.
|
|
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 /
|
|
645
|
+
: "Use /vm reindex --reembed to regenerate embeddings after model switch.");
|
|
538
646
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
539
647
|
};
|
|
540
|
-
// ── /
|
|
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 /
|
|
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
|
-
`
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
`
|
|
563
|
-
`
|
|
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 (
|
|
567
|
-
const modal =
|
|
568
|
-
|
|
569
|
-
|
|
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
|
|
686
|
+
ctx.ui.notify(`Unknown: ${subcommand}. Try: status`, "error");
|
|
672
687
|
return;
|
|
673
688
|
}
|
|
674
689
|
}
|
|
675
690
|
};
|
|
676
|
-
// ── /
|
|
691
|
+
// ── /vm remote (Modal provider) ─────────────────────────────────────────────
|
|
677
692
|
const MODAL_CONFIG_USAGE = [
|
|
678
|
-
"**/
|
|
693
|
+
"**/vm remote config**",
|
|
679
694
|
"",
|
|
680
|
-
" /
|
|
681
|
-
" /
|
|
682
|
-
" /
|
|
683
|
-
" /
|
|
684
|
-
" /
|
|
685
|
-
" /
|
|
686
|
-
" /
|
|
687
|
-
" /
|
|
688
|
-
" /
|
|
689
|
-
" /
|
|
690
|
-
" /
|
|
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
|
|
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.
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
const emb =
|
|
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 /
|
|
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.
|
|
738
|
+
const modal = cfg.vaultMind.embedding.modal ?? {};
|
|
724
739
|
if (!key) {
|
|
725
|
-
const tokenSrc = resolveModalToken(cfg.
|
|
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.
|
|
745
|
+
const derivedUrl = resolveBaseUrl(cfg.vaultMind);
|
|
731
746
|
const lines = [
|
|
732
747
|
"**Modal Config:**",
|
|
733
748
|
"",
|
|
734
|
-
` workspace: ${resolveWorkspace(cfg.
|
|
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.
|
|
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("/
|
|
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("/
|
|
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("/
|
|
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("/
|
|
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
|
|
833
|
-
const emb =
|
|
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("/
|
|
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.
|
|
872
|
-
const tokenSrc = resolveModalToken(cfg.
|
|
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.
|
|
892
|
+
const derivedUrl = resolveBaseUrl(cfg.vaultMind);
|
|
878
893
|
const lines = [
|
|
879
|
-
"**
|
|
894
|
+
"**Remote Status**",
|
|
880
895
|
"",
|
|
881
|
-
`Configured: ${isModalConfigured(cfg.
|
|
882
|
-
` workspace: ${resolveWorkspace(cfg.
|
|
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.
|
|
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.
|
|
915
|
-
ctx.ui.notify(`
|
|
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("/
|
|
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.
|
|
959
|
+
const client = createModalClient(cfg.vaultMind);
|
|
945
960
|
if (!client) {
|
|
946
|
-
ctx.ui.notify("
|
|
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 /
|
|
983
|
+
ctx.ui.notify("No project config found. Run /vm init first.", "error");
|
|
969
984
|
return;
|
|
970
985
|
}
|
|
971
|
-
const workspace = resolveWorkspace(cfg.
|
|
972
|
-
if (!workspace && !cfg.
|
|
973
|
-
ctx.ui.notify("Modal workspace or baseUrl not configured. Run:\n /
|
|
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.
|
|
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.
|
|
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
|
|
1006
|
-
const emb =
|
|
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
|
-
|
|
1024
|
-
obj.
|
|
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.
|
|
1043
|
+
` baseUrl: ${resolveBaseUrl(cfg.vaultMind)}`,
|
|
1029
1044
|
` model: ${defaultModel}`,
|
|
1030
1045
|
` dim: ${models.default_dim ?? "native"}`,
|
|
1031
1046
|
"",
|
|
1032
|
-
"Run /
|
|
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("/
|
|
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 /
|
|
1095
|
+
ctx.ui.notify("Unknown /vm remote subcommand. Try: status, config, sync, jobs, migrate", "error");
|
|
1081
1096
|
}
|
|
1082
1097
|
};
|
|
1083
|
-
// ── /
|
|
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 /
|
|
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
|
-
// ── /
|
|
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
|
-
// ── /
|
|
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 /
|
|
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.
|
|
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.
|
|
1219
|
-
ctx.ui.notify("No vaults configured. Add vaults to
|
|
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.
|
|
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 /
|
|
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: /
|
|
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-
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
"",
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
"
|
|
1301
|
-
|
|
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: /
|
|
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> --
|
|
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("
|
|
1344
|
-
cliArgs.
|
|
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.
|
|
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
|
-
// ── /
|
|
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: /
|
|
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
|
-
// ── /
|
|
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 /
|
|
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
|
-
// ──
|
|
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("
|
|
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
|
-
"
|
|
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: `
|
|
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", "--
|
|
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(
|
|
1678
|
+
ctx.ui.notify(VM_USAGE, "info");
|
|
1555
1679
|
}
|
|
1556
1680
|
},
|
|
1557
1681
|
});
|
|
1558
|
-
// Legacy aliases — /
|
|
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.",
|