pi-vault-mind 0.14.2 → 0.16.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 (164) hide show
  1. package/CHANGELOG.md +293 -0
  2. package/README.md +5 -5
  3. package/agents/broadcaster.agent.md +2 -2
  4. package/agents/heavy-lifter.agent.md +1 -1
  5. package/agents/main.agent.md +29 -0
  6. package/agents/manager.agent.md +1 -1
  7. package/agents/miner.agent.md +1 -1
  8. package/dist/packages/obsidian/src/client.js +331 -0
  9. package/dist/packages/obsidian/src/types.js +1 -0
  10. package/dist/src/activity.d.ts +49 -0
  11. package/dist/src/activity.js +175 -0
  12. package/dist/src/agent-bus/Agent.d.ts +9 -1
  13. package/dist/src/agent-bus/Agent.js +18 -2
  14. package/dist/src/agent-bus/index.d.ts +5 -5
  15. package/dist/src/agent-bus/index.js +1 -1
  16. package/dist/src/agent-queue.js +12 -3
  17. package/dist/src/agents/BroadcasterAgent.d.ts +1 -1
  18. package/dist/src/agents/BroadcasterAgent.js +2 -2
  19. package/dist/src/agents/HeavyLifterAgent.d.ts +1 -1
  20. package/dist/src/agents/HeavyLifterAgent.js +2 -2
  21. package/dist/src/agents/ManagerAgent.js +1 -1
  22. package/dist/src/agents/MinerAgent.d.ts +1 -1
  23. package/dist/src/agents/MinerAgent.js +2 -2
  24. package/dist/src/agents/index.d.ts +2 -2
  25. package/dist/src/agents/index.js +2 -2
  26. package/dist/src/auth.d.ts +17 -15
  27. package/dist/src/auth.js +26 -37
  28. package/dist/src/autosync.js +2 -2
  29. package/dist/src/commands.d.ts +2 -19
  30. package/dist/src/commands.js +301 -231
  31. package/dist/src/config-keys.d.ts +16 -0
  32. package/dist/src/config-keys.js +45 -0
  33. package/dist/src/discover-schema.js +4 -4
  34. package/dist/src/edits.d.ts +21 -0
  35. package/dist/src/edits.js +75 -0
  36. package/dist/src/embedding-probe.d.ts +34 -0
  37. package/dist/src/embedding-probe.js +142 -0
  38. package/dist/src/embedding-providers.d.ts +133 -0
  39. package/dist/src/embedding-providers.js +173 -0
  40. package/dist/src/embedding-secrets.d.ts +38 -0
  41. package/dist/src/embedding-secrets.js +291 -0
  42. package/dist/src/engine.d.ts +6 -0
  43. package/dist/src/engine.js +78 -6
  44. package/dist/src/events.js +7 -10
  45. package/dist/src/extension-packages.d.ts +9 -0
  46. package/dist/src/extension-packages.js +14 -0
  47. package/dist/src/git.d.ts +19 -0
  48. package/dist/src/git.js +78 -0
  49. package/dist/src/graph.d.ts +2 -1
  50. package/dist/src/graph.js +47 -13
  51. package/dist/src/identities-config.d.ts +6 -0
  52. package/dist/src/identities-config.js +39 -0
  53. package/dist/src/identity-injector.js +1 -1
  54. package/dist/src/index.js +9 -26
  55. package/dist/src/lance.d.ts +9 -0
  56. package/dist/src/lance.js +143 -203
  57. package/dist/src/modal-client.d.ts +16 -0
  58. package/dist/src/modal-client.js +16 -0
  59. package/dist/src/modal-config.d.ts +37 -14
  60. package/dist/src/modal-config.js +59 -32
  61. package/dist/src/model-router.d.ts +27 -0
  62. package/dist/src/model-router.js +67 -0
  63. package/dist/src/models.d.ts +42 -0
  64. package/dist/src/models.js +78 -0
  65. package/dist/src/personalize.js +17 -3
  66. package/dist/src/scaffold.d.ts +48 -0
  67. package/dist/src/scaffold.js +246 -0
  68. package/dist/src/server.d.ts +47 -4
  69. package/dist/src/server.js +988 -221
  70. package/dist/src/session-search.d.ts +63 -0
  71. package/dist/src/session-search.js +379 -0
  72. package/dist/src/settings-ui.d.ts +2 -2
  73. package/dist/src/settings-ui.js +76 -135
  74. package/dist/src/sync.d.ts +3 -3
  75. package/dist/src/sync.js +19 -15
  76. package/dist/src/tool-catalog.d.ts +18 -0
  77. package/dist/src/tool-catalog.js +232 -0
  78. package/dist/src/tools/marksman.js +1 -1
  79. package/dist/src/tools.js +111 -65
  80. package/dist/src/types.d.ts +127 -71
  81. package/dist/src/utils.d.ts +57 -6
  82. package/dist/src/utils.js +114 -28
  83. package/dist/src/vault-tools.d.ts +34 -0
  84. package/dist/src/vault-tools.js +99 -0
  85. package/dist/src/vault-writer.js +4 -1
  86. package/dist/src/vm-handlers.js +140 -43
  87. package/dist/src/watcher.js +9 -9
  88. package/dist/test/activity-tool.test.js +54 -0
  89. package/dist/test/agent-bus/Agent.test.js +174 -0
  90. package/dist/test/agent-bus/AgentLoader.test.js +180 -0
  91. package/dist/test/agent-bus/AgentRegistry.test.js +86 -0
  92. package/dist/test/agent-bus/LLMProvider.test.js +162 -0
  93. package/dist/test/agent-bus/TaskQueue.test.js +256 -0
  94. package/dist/test/agent-dispatch-context.test.js +73 -0
  95. package/dist/test/agent-queue.test.js +1 -1
  96. package/dist/test/agents/Agents.test.js +282 -0
  97. package/dist/test/auth.test.js +57 -56
  98. package/dist/test/bridge-identity.test.js +34 -3
  99. package/dist/test/classify-search-mode.test.js +51 -0
  100. package/dist/test/commands.test.js +410 -0
  101. package/dist/test/config-merge.test.js +98 -0
  102. package/dist/test/context-capture.test.js +56 -0
  103. package/dist/test/discover-schema.test.js +8 -5
  104. package/dist/test/dispatch.test.js +45 -91
  105. package/dist/test/edits.test.js +144 -0
  106. package/dist/test/embedding-probe.test.js +424 -0
  107. package/dist/test/embedding-providers.test.js +157 -0
  108. package/dist/test/embedding-secrets.test.js +210 -0
  109. package/dist/test/engine-identity.test.js +1 -1
  110. package/dist/test/events.test.js +675 -0
  111. package/dist/test/git-bridge.test.js +160 -0
  112. package/dist/test/graph.test.js +464 -0
  113. package/dist/test/helpers/server.js +81 -0
  114. package/dist/test/identity-injector.test.js +197 -0
  115. package/dist/test/index.test.js +5 -7
  116. package/dist/test/lance-modal.test.js +67 -2
  117. package/dist/test/lance.test.js +429 -0
  118. package/dist/test/modal-client.test.js +1 -1
  119. package/dist/test/modal-config.test.js +92 -79
  120. package/dist/test/model-readiness.test.js +122 -0
  121. package/dist/test/model-router.test.js +99 -0
  122. package/dist/test/obsidian-client.test.js +42 -0
  123. package/dist/test/personalize.test.js +1 -1
  124. package/dist/test/rest-models.test.js +153 -0
  125. package/dist/test/rest-queue.test.js +135 -87
  126. package/dist/test/rest-setup.test.js +625 -115
  127. package/dist/test/rest-vm.test.js +355 -94
  128. package/dist/test/server-cors.test.js +48 -0
  129. package/dist/test/server-ws.test.js +61 -34
  130. package/dist/test/session-crud.test.js +323 -0
  131. package/dist/test/session-search.test.js +139 -0
  132. package/dist/test/settings-ui.test.js +113 -4
  133. package/dist/test/sync.test.js +12 -12
  134. package/dist/test/tombstone.test.js +8 -10
  135. package/dist/test/tool-catalog.test.js +86 -0
  136. package/dist/test/utils.test.js +588 -0
  137. package/dist/test/vault-tools.test.js +103 -12
  138. package/dist/test/vault-writer.test.js +371 -0
  139. package/dist/test/vm-handlers.test.js +620 -0
  140. package/extension-packages.json +9 -0
  141. package/package.json +107 -90
  142. package/pi-vault-mind.config.example.json +24 -2
  143. package/scripts/check-deps.sh +35 -32
  144. package/scripts/cli-e2e.mjs +215 -0
  145. package/scripts/configuration-e2e.mjs +783 -0
  146. package/scripts/e2e-commands.mjs +391 -0
  147. package/scripts/e2e-ws.mjs +3 -2
  148. package/scripts/fetch-modal-token.sh +4 -3
  149. package/scripts/generate-config-keys-json.mjs +45 -0
  150. package/scripts/generate-extension-packages-json.mjs +55 -0
  151. package/scripts/pi-session-query +0 -0
  152. package/scripts/publish-obsidian-plugin.sh +0 -0
  153. package/scripts/reset-test-vault.sh +2 -2
  154. package/scripts/setup-test-config.sh +0 -0
  155. package/scripts/setup-vault-pi.sh +48 -9
  156. package/scripts/test-slash-commands.mjs +2 -2
  157. package/scripts/vault-pi.sh +12 -3
  158. package/skills/github-release-notes-file/SKILL.md +42 -0
  159. package/skills/obsidian-guided-test-session/SKILL.md +110 -0
  160. package/skills/obsidian-interactive-test/SKILL.md +88 -0
  161. package/skills/obsidian-plugin-publish/SKILL.md +58 -0
  162. package/skills/pi-vault-mind-release/SKILL.md +141 -0
  163. package/skills/pre-test-release/SKILL.md +116 -0
  164. package/dist/test/auto-indexer.test.js +0 -2
@@ -0,0 +1,391 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * E2E smoke for /vm slash commands + BC-2 rewind/fork RPC — real pi CLI.
4
+ *
5
+ * Creates a temp vault, scaffolds config, installs the extension via `pi install`,
6
+ * spawns `pi --mode rpc`, drives commands via JSON-line stdin, asserts on
7
+ * responses and file side-effects, then tears down. No mocks.
8
+ *
9
+ * Prerequisites: pi binary on PATH, pnpm build completed.
10
+ *
11
+ * Usage:
12
+ * pnpm build
13
+ * pnpm e2e:commands
14
+ */
15
+
16
+ import { spawn } from "node:child_process";
17
+ import { randomUUID } from "node:crypto";
18
+ import * as fs from "node:fs";
19
+ import * as os from "node:os";
20
+ import * as path from "node:path";
21
+
22
+ // ── Helpers ──────────────────────────────────────────────────────────────────
23
+
24
+ let failures = 0;
25
+ const pass = (msg) => console.log(` \x1b[32m✓\x1b[0m ${msg}`);
26
+ const fail = (msg) => {
27
+ failures++;
28
+ console.log(` \x1b[31m✗\x1b[0m ${msg}`);
29
+ };
30
+ const step = (msg) => console.log(`\n\x1b[1m${msg}\x1b[0m`);
31
+
32
+ function tmpDir(prefix) {
33
+ const dir = path.join(os.tmpdir(), `${prefix}-${randomUUID()}`);
34
+ fs.mkdirSync(dir, { recursive: true });
35
+ return dir;
36
+ }
37
+
38
+ // ── Vault setup ──────────────────────────────────────────────────────────────
39
+
40
+ async function hasOllamaEmbeddingModel() {
41
+ try {
42
+ const res = await fetch("http://localhost:11434/api/embeddings", {
43
+ method: "POST",
44
+ headers: { "Content-Type": "application/json" },
45
+ body: JSON.stringify({ model: "all-minilm", prompt: "health check" }),
46
+ signal: AbortSignal.timeout(5000),
47
+ });
48
+ if (!res.ok) return false;
49
+ const data = await res.json();
50
+ return Array.isArray(data.embedding) && data.embedding.length === 384;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ async function setupVault(vaultPath, seedSearchIndex) {
57
+ const cfgDir = path.join(vaultPath, ".vault-mind");
58
+ fs.mkdirSync(cfgDir, { recursive: true });
59
+ const collectionsDir = path.join(vaultPath, "collections");
60
+ fs.mkdirSync(collectionsDir, { recursive: true });
61
+
62
+ const entries = [
63
+ { id: "1", domain: "test", fact: "First test entry", tag: "alpha" },
64
+ { id: "2", domain: "test", fact: "Second test entry", tag: "beta" },
65
+ { id: "3", domain: "prod", fact: "Production entry", tag: "alpha" },
66
+ ];
67
+ const jsonl = `${entries.map((e) => JSON.stringify(e)).join("\n")}\n`;
68
+
69
+ const config = {
70
+ version: 2,
71
+ collections: {
72
+ main: {
73
+ path: "collections/main.jsonl",
74
+ schema: ["id", "domain", "source", "fact", "tag"],
75
+ dedupField: "fact",
76
+ },
77
+ },
78
+ injectors: [],
79
+ vaultMind: {
80
+ dataDir: ".lancedb",
81
+ embedding: {
82
+ localUrl: "http://localhost:11434",
83
+ model: "all-minilm",
84
+ dim: 384,
85
+ useTransformers: false,
86
+ },
87
+ ftsEnabled: true,
88
+ graph: { enabled: false },
89
+ },
90
+ };
91
+ fs.writeFileSync(
92
+ path.join(cfgDir, "vault-mind.config.json"),
93
+ JSON.stringify(config, null, 2),
94
+ "utf-8"
95
+ );
96
+ fs.writeFileSync(path.join(collectionsDir, "main.jsonl"), jsonl, "utf-8");
97
+
98
+ if (!seedSearchIndex) return;
99
+
100
+ // Build LanceDB index from the seeded JSONL entries so /vm search can find them
101
+ const repoRoot = path.resolve(import.meta.dirname, "..");
102
+ const { upsertEntry } = await import(path.join(repoRoot, "dist/src/lance.js"));
103
+ const { loadConfig } = await import(path.join(repoRoot, "dist/src/utils.js"));
104
+ const cfg = loadConfig(vaultPath);
105
+ const dataDir = path.resolve(vaultPath, cfg.vaultMind.dataDir);
106
+ for (const entry of entries) {
107
+ await upsertEntry(dataDir, "main", entry, cfg.vaultMind);
108
+ }
109
+ }
110
+
111
+ // ── Pi RPC ───────────────────────────────────────────────────────────────────
112
+
113
+ async function runPiRpc(vaultPath, commands) {
114
+ return new Promise((resolve, reject) => {
115
+ const piAgentDir = path.join(vaultPath, ".pi", "agent");
116
+ const env = {
117
+ ...process.env,
118
+ PI_CODING_AGENT_DIR: piAgentDir,
119
+ HOME: vaultPath,
120
+ USERPROFILE: vaultPath,
121
+ };
122
+
123
+ const child = spawn("pi", ["--mode", "rpc"], {
124
+ cwd: vaultPath,
125
+ env,
126
+ stdio: ["pipe", "pipe", "pipe"],
127
+ });
128
+
129
+ const responses = [];
130
+ let stderr = "";
131
+ let commandIndex = 0;
132
+ let resolved = false;
133
+ const TIMEOUT_MS = 120_000;
134
+
135
+ let buffer = "";
136
+ child.stdout.on("data", (chunk) => {
137
+ buffer += chunk.toString();
138
+ const lines = buffer.split("\n");
139
+ buffer = lines.pop() ?? "";
140
+ for (const line of lines) {
141
+ const trimmed = line.endsWith("\r") ? line.slice(0, -1) : line;
142
+ if (!trimmed) continue;
143
+ try {
144
+ const event = JSON.parse(trimmed);
145
+ responses.push(event);
146
+ if (event.type === "response" && commandIndex < commands.length) {
147
+ const cmd = commands[commandIndex++];
148
+ child.stdin.write(`${JSON.stringify(cmd)}\n`);
149
+ }
150
+ } catch {
151
+ responses.push({ _text: trimmed });
152
+ }
153
+ }
154
+ });
155
+
156
+ child.stderr.on("data", (data) => {
157
+ stderr += data.toString();
158
+ });
159
+
160
+ // Send first command after extension loads
161
+ const readyTimer = setTimeout(() => {
162
+ if (commandIndex < commands.length) {
163
+ const cmd = commands[commandIndex++];
164
+ child.stdin.write(`${JSON.stringify(cmd)}\n`);
165
+ }
166
+ }, 3000);
167
+
168
+ // Kill after all commands sent + grace period
169
+ const doneTimer = setTimeout(() => {
170
+ if (!resolved) {
171
+ resolved = true;
172
+ child.kill();
173
+ resolve({ responses, stderr, timedOut: false });
174
+ }
175
+ }, TIMEOUT_MS);
176
+
177
+ child.on("close", () => {
178
+ clearTimeout(readyTimer);
179
+ clearTimeout(doneTimer);
180
+ if (!resolved) {
181
+ resolved = true;
182
+ resolve({ responses, stderr, timedOut: false });
183
+ }
184
+ });
185
+ child.on("error", (err) => {
186
+ clearTimeout(readyTimer);
187
+ clearTimeout(doneTimer);
188
+ if (!resolved) {
189
+ resolved = true;
190
+ reject(err);
191
+ }
192
+ });
193
+ });
194
+ }
195
+
196
+ // ── Main ─────────────────────────────────────────────────────────────────────
197
+
198
+ async function main() {
199
+ console.log("pi-vault-mind /vm commands + BC-2 RPC E2E\n");
200
+ let piVersion = "";
201
+
202
+ try {
203
+ ({ stdout: piVersion } = await new Promise((resolve, reject) => {
204
+ const child = spawn("pi", ["--version"], { stdio: "pipe" });
205
+ let out = "";
206
+ child.stdout.on("data", (d) => (out += d));
207
+ child.on("close", (code) =>
208
+ code === 0 ? resolve({ stdout: out }) : reject(new Error(`pi --version exited ${code}`))
209
+ );
210
+ child.on("error", reject);
211
+ }));
212
+ console.log(` pi version: ${piVersion.trim()}`);
213
+ } catch {
214
+ console.error(" pi binary not found on PATH");
215
+ process.exit(1);
216
+ }
217
+
218
+ const vaultPath = tmpDir("pvm-e2e-commands");
219
+ const origHome = process.env.HOME;
220
+ const searchE2EAvailable = await hasOllamaEmbeddingModel();
221
+ if (!searchE2EAvailable) {
222
+ pass("Ollama all-minilm unavailable — skipping search e2e");
223
+ }
224
+
225
+ try {
226
+ await setupVault(vaultPath, searchE2EAvailable);
227
+
228
+ step("Installing pi-vault-mind via pi install");
229
+ const repoRoot = path.resolve(import.meta.dirname, "..");
230
+ const piAgentDir = path.join(vaultPath, ".pi", "agent");
231
+ await new Promise((resolve, reject) => {
232
+ const child = spawn("pi", ["install", repoRoot], {
233
+ cwd: vaultPath,
234
+ env: {
235
+ ...process.env,
236
+ PI_CODING_AGENT_DIR: piAgentDir,
237
+ HOME: vaultPath,
238
+ USERPROFILE: vaultPath,
239
+ },
240
+ stdio: "pipe",
241
+ });
242
+ let out = "";
243
+ child.stdout.on("data", (d) => (out += d));
244
+ child.stderr.on("data", (d) => (out += d));
245
+ child.on("close", (code) =>
246
+ code === 0
247
+ ? resolve()
248
+ : reject(new Error(`pi install exited ${code}: ${out.slice(0, 200)}`))
249
+ );
250
+ child.on("error", reject);
251
+ });
252
+ pass("pi install succeeded");
253
+
254
+ step("Running commands via pi --mode rpc");
255
+ const commands = [
256
+ { type: "prompt", message: "/vm doctor", id: "e2e-1" },
257
+ ...(searchE2EAvailable
258
+ ? [
259
+ { type: "prompt", message: "/vm reindex", id: "e2e-reindex" },
260
+ { type: "prompt", message: "/vm search test", id: "e2e-search" },
261
+ { type: "prompt", message: "/vm search test --mode=fts", id: "e2e-search-fts" },
262
+ ]
263
+ : []),
264
+ { type: "get_fork_messages", id: "e2e-fork" },
265
+ { type: "get_tree", id: "e2e-tree" },
266
+ { type: "get_entries", id: "e2e-entries" },
267
+ { type: "clone", id: "e2e-clone" },
268
+ ];
269
+ const { responses, stderr, timedOut } = await runPiRpc(vaultPath, commands);
270
+
271
+ if (timedOut) fail("pi RPC timed out (60s)");
272
+
273
+ step("Verifying responses");
274
+ const allText = responses.map((r) => JSON.stringify(r)).join(" ");
275
+
276
+ if (allText.includes("vault-mind") || allText.includes(".vault-mind"))
277
+ pass("/vm doctor references vault-mind paths");
278
+ else {
279
+ fail("/vm doctor references vault-mind paths");
280
+ console.log(` responses: ${allText.slice(0, 200)}`);
281
+ }
282
+
283
+ // BC-2: rewind/fork RPC commands
284
+ const byId = (id) => responses.find((r) => r.id === id);
285
+ const byCmd = (cmd) => responses.find((r) => r.command === cmd);
286
+ const supportsTreeRpc = !piVersion.trim().startsWith("0.79.");
287
+ const treeResp = byId("e2e-tree") || byCmd("get_tree");
288
+ if (!supportsTreeRpc) pass("get_tree skipped (pi < 0.80.0 in e2e runtime)");
289
+ else if (treeResp && treeResp.success !== false) pass("get_tree returns successfully");
290
+ else {
291
+ fail("get_tree failed or missing");
292
+ console.log(` treeResp: ${JSON.stringify(treeResp).slice(0, 200)}`);
293
+ }
294
+
295
+ const entriesResp = byId("e2e-entries") || byCmd("get_entries");
296
+ if (!supportsTreeRpc) pass("get_entries skipped (pi < 0.80.0 in e2e runtime)");
297
+ else if (entriesResp && entriesResp.success !== false) pass("get_entries returns successfully");
298
+ else {
299
+ fail("get_entries failed or missing");
300
+ console.log(` entriesResp: ${JSON.stringify(entriesResp).slice(0, 200)}`);
301
+ }
302
+
303
+ const cloneResp = byId("e2e-clone") || byCmd("clone");
304
+ if (!supportsTreeRpc) pass("clone skipped (pi < 0.80.0 in e2e runtime)");
305
+ else if (cloneResp && cloneResp.success !== false) pass("clone returns successfully");
306
+ else {
307
+ fail("clone failed or missing");
308
+ console.log(` cloneResp: ${JSON.stringify(cloneResp).slice(0, 200)}`);
309
+ }
310
+
311
+ if (searchE2EAvailable) {
312
+ // /vm reindex — builds LanceDB index from JSONL with real embeddings
313
+ const reindexResp = byId("e2e-reindex");
314
+ if (reindexResp && reindexResp.success !== false) pass("/vm reindex runs successfully");
315
+ else {
316
+ fail("/vm reindex failed or missing");
317
+ console.log(` reindexResp: ${JSON.stringify(reindexResp).slice(0, 200)}`);
318
+ }
319
+
320
+ // /vm search — verify both slash commands ran and the search notifications
321
+ // include seeded facts after reindex. The RPC stream can interleave UI
322
+ // notifications with prompt responses, so assert result content globally.
323
+ const searchResp = byId("e2e-search");
324
+ if (searchResp && searchResp.success !== false)
325
+ pass("/vm search command returns successfully");
326
+ else {
327
+ fail("/vm search command failed or missing");
328
+ console.log(` searchResp: ${JSON.stringify(searchResp).slice(0, 200)}`);
329
+ }
330
+ const searchFtsResp = byId("e2e-search-fts");
331
+ if (searchFtsResp && searchFtsResp.success !== false)
332
+ pass("/vm search --mode=fts command returns successfully");
333
+ else {
334
+ fail("/vm search --mode=fts command failed or missing");
335
+ console.log(` searchFtsResp: ${JSON.stringify(searchFtsResp).slice(0, 200)}`);
336
+ }
337
+ const repoRootForSearch = path.resolve(import.meta.dirname, "..");
338
+ const { searchHybrid, searchFts } = await import(
339
+ path.join(repoRootForSearch, "dist/src/lance.js")
340
+ );
341
+ const { loadConfig } = await import(path.join(repoRootForSearch, "dist/src/utils.js"));
342
+ const cfg = loadConfig(vaultPath);
343
+ const dataDir = path.resolve(vaultPath, cfg.vaultMind.dataDir);
344
+ const hybridResults = JSON.stringify(
345
+ await searchHybrid(dataDir, "main", "test", 5, cfg.vaultMind)
346
+ );
347
+ if (hybridResults.includes("First test entry") || hybridResults.includes("Second test entry"))
348
+ pass("real hybrid search returns seeded fact data");
349
+ else {
350
+ fail("real hybrid search did not return seeded fact data");
351
+ console.log(` hybridResults: ${hybridResults.slice(0, 300)}`);
352
+ }
353
+ const ftsResults = JSON.stringify(await searchFts(dataDir, "main", "test", 5, cfg.vaultMind));
354
+ if (ftsResults.includes("First test entry") || ftsResults.includes("Second test entry"))
355
+ pass("real FTS search returns seeded fact data");
356
+ else {
357
+ fail("real FTS search did not return seeded fact data");
358
+ console.log(` ftsResults: ${ftsResults.slice(0, 300)}`);
359
+ }
360
+ } else {
361
+ pass("search e2e assertions skipped — Ollama all-minilm unavailable");
362
+ }
363
+
364
+ step("Verifying file side-effects");
365
+ const cfgPath = path.join(vaultPath, ".vault-mind", "vault-mind.config.json");
366
+ const collPath = path.join(vaultPath, "collections", "main.jsonl");
367
+ if (fs.existsSync(cfgPath)) pass("vault-mind.config.json still exists");
368
+ else fail("vault-mind.config.json still exists");
369
+ if (fs.existsSync(collPath)) pass("main.jsonl collection still exists");
370
+ else fail("main.jsonl collection still exists");
371
+
372
+ console.log(
373
+ `\n${failures === 0 ? "\x1b[32mAll passed\x1b[0m" : `\x1b[31m${failures} failed\x1b[0m`}`
374
+ );
375
+ if (stderr && !stderr.includes("ExperimentalWarning"))
376
+ console.log(`\nstderr:\n${stderr.slice(0, 500)}`);
377
+ } finally {
378
+ process.env.HOME = origHome;
379
+ try {
380
+ fs.rmSync(vaultPath, { recursive: true, force: true });
381
+ } catch {
382
+ /* ignore */
383
+ }
384
+ }
385
+ process.exit(failures > 0 ? 1 : 0);
386
+ }
387
+
388
+ main().catch((err) => {
389
+ console.error(err);
390
+ process.exit(1);
391
+ });
@@ -54,8 +54,9 @@ function defaultVaultPath() {
54
54
  }
55
55
 
56
56
  function ensureProjectConfig(vaultPath) {
57
- const cfgPath = path.join(vaultPath, "pi-vault-mind.config.json");
57
+ const cfgPath = path.join(vaultPath, ".vault-mind", "vault-mind.config.json");
58
58
  if (!fs.existsSync(cfgPath)) {
59
+ fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
59
60
  fs.writeFileSync(
60
61
  cfgPath,
61
62
  JSON.stringify(
@@ -69,7 +70,7 @@ function ensureProjectConfig(vaultPath) {
69
70
  },
70
71
  },
71
72
  injectors: [],
72
- wiki: {
73
+ vaultMind: {
73
74
  dataDir: ".lancedb",
74
75
  embedding: {
75
76
  provider: "modal",
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env bash
2
2
  # Fetch the Modal API token from 1Password and export or persist it.
3
3
  # Usage:
4
- # ./scripts/fetch-modal-token.sh --export # prints an export line
5
- # ./scripts/fetch-modal-token.sh --write # writes to ~/.pi/agent/vault-mind.env (default)
4
+ # ./scripts/fetch-modal-token.sh --export # prints an export line
5
+ # ./scripts/fetch-modal-token.sh --write [vault-path] # writes to <vault>/.vault-mind/vault-mind.env
6
6
  #
7
7
  # The 1Password item must be titled "pi-vault-mind-auth" with the token in the
8
8
  # password field.
@@ -11,7 +11,8 @@ set -euo pipefail
11
11
 
12
12
  OP_ITEM="pi-vault-mind-auth"
13
13
  OP_FIELD="password"
14
- ENV_DIR="${HOME}/.pi/agent"
14
+ VAULT="${2:-$(pwd)}"
15
+ ENV_DIR="${VAULT}/.vault-mind"
15
16
  ENV_FILE="${ENV_DIR}/vault-mind.env"
16
17
 
17
18
  mode="write"
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generates config-keys.json from the canonical TS source.
4
+ *
5
+ * Reads src/config-keys.ts and writes JSON to:
6
+ * 1. config-keys.json (repo root — for reference)
7
+ * 2. packages/obsidian/src/config-keys.json (for plugin import)
8
+ *
9
+ * Run before building the plugin.
10
+ * Usage: node scripts/generate-config-keys-json.mjs
11
+ */
12
+
13
+ import { readFileSync, writeFileSync } from "node:fs";
14
+ import { dirname, resolve } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+ const repoRoot = resolve(__dirname, "..");
19
+
20
+ const src = readFileSync(resolve(repoRoot, "src/config-keys.ts"), "utf-8");
21
+
22
+ function extractArray(name) {
23
+ const re = new RegExp(`export const ${name} = \\[([\\s\\S]*?)\\] as const;`, "m");
24
+ const arrMatch = src.match(re);
25
+ if (!arrMatch) throw new Error(`Could not find ${name} in src/config-keys.ts`);
26
+ const items = [];
27
+ for (const m of arrMatch[1].matchAll(/"([^"]+)"/g)) {
28
+ items.push(m[1]);
29
+ }
30
+ return items;
31
+ }
32
+
33
+ const embeddingFlatKeys = extractArray("EMBEDDING_FLAT_KEYS");
34
+ const vaultMindConfigKeys = extractArray("VAULT_MIND_CONFIG_KEYS");
35
+
36
+ const json = `${JSON.stringify({ embeddingFlatKeys, vaultMindConfigKeys }, null, "\t")}\n`;
37
+
38
+ const rootOut = resolve(repoRoot, "config-keys.json");
39
+ const pluginOut = resolve(repoRoot, "packages/obsidian/src/config-keys.json");
40
+
41
+ writeFileSync(rootOut, json);
42
+ console.log(`Wrote ${rootOut}`);
43
+
44
+ writeFileSync(pluginOut, json);
45
+ console.log(`Wrote ${pluginOut}`);
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generates extension-packages.json from the canonical TS source.
4
+ *
5
+ * Reads src/extension-packages.ts and writes JSON to:
6
+ * 1. extension-packages.json (repo root — for shell scripts)
7
+ * 2. packages/obsidian/src/extension-packages.json (for plugin import)
8
+ * 3. packages/obsidian-ui/src/extension-packages.json (for sandbox import)
9
+ * Run before building the plugin or running shell scripts.
10
+ * Usage: node scripts/generate-extension-packages-json.mjs
11
+ */
12
+
13
+ import { writeFileSync } from "node:fs";
14
+ import { dirname, resolve } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+ const repoRoot = resolve(__dirname, "..");
19
+
20
+ // Dynamic import of the TS source — Node 20+ with --experimental-strip-types
21
+ // or tsx handles this. We use a simple regex-based extraction as a fallback
22
+ // that works without any loader flags.
23
+ import { readFileSync } from "node:fs";
24
+
25
+ const src = readFileSync(resolve(repoRoot, "src/extension-packages.ts"), "utf-8");
26
+
27
+ function extractArray(name) {
28
+ const re = new RegExp(`export const ${name} = \\[([\\s\\S]*?)\\] as const;`, "m");
29
+ const arrMatch = src.match(re);
30
+ if (!arrMatch) throw new Error(`Could not find ${name} in src/extension-packages.ts`);
31
+ // Parse each quoted string from the array body
32
+ const items = [];
33
+ for (const m of arrMatch[1].matchAll(/"([^"]+)"/g)) {
34
+ items.push(m[1]);
35
+ }
36
+ return items;
37
+ }
38
+
39
+ const required = extractArray("PI_REQUIRED_EXTENSIONS");
40
+ const optional = extractArray("PI_OPTIONAL_EXTENSIONS");
41
+
42
+ const json = `${JSON.stringify({ required, optional }, null, "\t")}\n`;
43
+
44
+ const rootOut = resolve(repoRoot, "extension-packages.json");
45
+ const pluginOut = resolve(repoRoot, "packages/obsidian/src/extension-packages.json");
46
+ const sandboxOut = resolve(repoRoot, "packages/obsidian-ui/src/extension-packages.json");
47
+
48
+ writeFileSync(rootOut, json);
49
+ console.log(`Wrote ${rootOut}`);
50
+
51
+ writeFileSync(pluginOut, json);
52
+ console.log(`Wrote ${pluginOut}`);
53
+
54
+ writeFileSync(sandboxOut, json);
55
+ console.log(`Wrote ${sandboxOut}`);
File without changes
File without changes
@@ -45,8 +45,8 @@ fi
45
45
 
46
46
  echo ""
47
47
  echo "✓ Vault reset complete. State removed:"
48
- echo " - .pi/ (extensions, settings, agent config)"
49
- echo " - pi-vault-mind.config.json"
48
+ echo " - .vault-mind/ (config, token, framework agent dir, queue)"
49
+ echo " - .pi/ (legacy agent dir, if present)"
50
50
  echo " - .lancedb/ (vector index)"
51
51
  echo " - collections/ (JSONL data)"
52
52
  echo ""
File without changes
@@ -1,32 +1,71 @@
1
1
  #!/usr/bin/env bash
2
2
  # setup-vault-pi.sh — Bootstrap a vault-local pi agent config.
3
3
  #
4
- # Run from the vault root. Creates .pi/agent/ with pi-vault-mind and pi-context
5
- # installed locally, isolated from your global ~/.pi/agent/.
4
+ # Run from the vault root. Creates .vault-mind/.pi/agent/ with pi-vault-mind,
5
+ # pi-context, pi-model-discovery, and pi-model-router installed locally,
6
+ # isolated from your global ~/.pi/agent/. If the 1Password CLI (op) is
7
+ # detected, also installs pi-1password so .vault-mind/.env.1pass (op://
8
+ # references) resolves at pi startup.
6
9
  #
7
10
  # Usage:
8
11
  # cd /path/to/vault
9
12
  # bash scripts/setup-vault-pi.sh
10
13
  #
11
14
  # After setup, start pi with:
12
- # PI_CODING_AGENT_DIR="$(pwd)/.pi/agent" pi
15
+ # PI_CODING_AGENT_DIR="$(pwd)/.vault-mind/.pi/agent" pi
13
16
 
14
17
  set -euo pipefail
15
18
 
16
19
  VAULT_ROOT="${1:-$(pwd)}"
17
- PI_DIR="${VAULT_ROOT}/.pi/agent"
20
+ PI_DIR="${VAULT_ROOT}/.vault-mind/.pi/agent"
18
21
 
19
22
  echo "==> Setting up vault-local pi config at ${PI_DIR}"
20
23
 
21
24
  mkdir -p "${PI_DIR}"
22
25
 
23
- # Install pi-vault-mind and pi-context into the vault-local agent dir
24
- PI_CODING_AGENT_DIR="${PI_DIR}" pi install npm:pi-vault-mind
25
- PI_CODING_AGENT_DIR="${PI_DIR}" pi install npm:pi-context
26
+ # Locate the canonical package list (generated from src/extension-packages.ts).
27
+ # The JSON ships next to the scripts/ directory in the npm package.
28
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
29
+ PKGS_JSON="${SCRIPT_DIR}/../extension-packages.json"
30
+
31
+ if ! command -v jq >/dev/null 2>&1; then
32
+ echo "ERROR: jq is required to parse extension-packages.json. Install it: brew install jq"
33
+ exit 1
34
+ fi
35
+
36
+ # Install required pi extensions from the canonical list.
37
+ echo "==> Installing required pi extensions..."
38
+ for pkg in $(jq -r '.required[]' "${PKGS_JSON}"); do
39
+ echo " ${pkg}"
40
+ PI_CODING_AGENT_DIR="${PI_DIR}" pi install "${pkg}"
41
+ done
42
+
43
+ # --op-env is a flag registered by pi-1password; only install optional
44
+ # extensions when their prerequisites are met.
45
+ OP_ENV_HINT=""
46
+ for pkg in $(jq -r '.optional[]' "${PKGS_JSON}"); do
47
+ case "${pkg}" in
48
+ npm:pi-1password)
49
+ if command -v op >/dev/null 2>&1; then
50
+ echo "==> 1Password CLI detected — installing ${pkg}"
51
+ PI_CODING_AGENT_DIR="${PI_DIR}" pi install "${pkg}"
52
+ OP_ENV_HINT=" --op-env .vault-mind/.env.1pass"
53
+ fi
54
+ ;;
55
+ *)
56
+ echo "==> Installing optional extension: ${pkg}"
57
+ PI_CODING_AGENT_DIR="${PI_DIR}" pi install "${pkg}"
58
+ ;;
59
+ esac
60
+ done
26
61
 
27
62
  echo ""
28
63
  echo "==> Done. Start pi for this vault with:"
29
- echo " PI_CODING_AGENT_DIR=\"${PI_DIR}\" pi"
64
+ echo " PI_CODING_AGENT_DIR=\"${PI_DIR}\" pi${OP_ENV_HINT}"
30
65
  echo ""
31
66
  echo " Or from Obsidian Shell Commands:"
32
- echo " PI_CODING_AGENT_DIR=${PI_DIR} pi"
67
+ echo " PI_CODING_AGENT_DIR=${PI_DIR} pi${OP_ENV_HINT}"
68
+ echo ""
69
+ echo " Then inside the pi session, run /vm setup — it scaffolds"
70
+ echo " .vault-mind/vault-mind.config.json AND .vault-mind/.pi/model-router.json"
71
+ echo " (tuned primary/fallback model defaults) in one step."
@@ -19,10 +19,10 @@
19
19
  import * as fs from "node:fs";
20
20
  import * as path from "node:path";
21
21
  import {
22
- DefaultResourceLoader,
23
- SessionManager,
24
22
  createAgentSession,
23
+ DefaultResourceLoader,
25
24
  getAgentDir,
25
+ SessionManager,
26
26
  } from "@earendil-works/pi-coding-agent";
27
27
 
28
28
  const VAULT_PATH = "/Users/kylebrodeur/workspace/recycvape/ReturnVape";