pi-vault-mind 0.8.5 → 0.8.7

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/README.md CHANGED
@@ -84,8 +84,8 @@ obsidian plugin:install id=obsidian-git enable
84
84
  obsidian plugin:install id=obsidian-breadcrumbs enable
85
85
  obsidian plugin:install id=graph-analysis enable
86
86
  obsidian plugin:install id=actions-uri enable
87
- obsidian plugin:install id=shellcommands enable
88
- ```
87
+ obsidian plugin:install id=obsidian42-brat enable
88
+ obsidian plugin:install id=obsidian-pi-vault-mind enable
89
89
 
90
90
  Full walkthrough: see [`docs/integrations/OBSIDIAN_SETUP.md`](docs/integrations/OBSIDIAN_SETUP.md).
91
91
 
@@ -97,7 +97,7 @@ Full walkthrough: see [`docs/integrations/OBSIDIAN_SETUP.md`](docs/integrations/
97
97
  | **Breadcrumbs** | `obsidian-breadcrumbs` | Parses typed edges (`agent:related-to`, `agent:derived-from`) in YAML frontmatter. The recommended way to display the agent-extracted knowledge graph in Obsidian's UI. | Highly recommended |
98
98
  | **Graph Analysis** | `graph-analysis` | Co-citation discovery, Jaccard similarity on the native Obsidian graph. Surfaces "always cited together but not yet linked" notes — a key agent discovery signal. | Highly recommended |
99
99
  | **Actions URI** | `actions-uri` | `x-callback-url` endpoints so the Manager agent can trigger Obsidian UI commands (open notes, run commands) from pi. | Recommended |
100
- | **obsidian-shellcommands** | `shellcommands` | Bridge from Obsidian events (e.g., file save) to pi agent workflows. Configure a `curl` to `http://127.0.0.1:11435/vault-mind/scan` on file save. | Recommended |
100
+ | **Vault Mind plugin** | `obsidian-pi-vault-mind` | Native setup/status/chat UI and HTTP bridge integration. | Recommended |
101
101
 
102
102
  ### Recommended: `notesmd-cli` (headless alternative)
103
103
 
@@ -52,10 +52,11 @@ const VM_USAGE = [
52
52
  " /vm watcher cancel <id> Cancel a pending agent job",
53
53
  " /vm server status Show HTTP server status + port",
54
54
  " /vm personalize Analyze vault and suggest personality tuning for system.md + AGENTS.md",
55
- " /vm setup Interactive global config wizard",
55
+ " /vm setup Onboarding flow: config + personalize (auto-uses cwd when it's a vault)",
56
56
  " /vm setup --vault <path> CLI: set vault path",
57
57
  " /vm setup --remoteUrl <url> CLI: set remote embedding URL",
58
58
  " /vm setup --localUrl <url> CLI: set local embedding URL",
59
+ " /vm setup --workspace <slug> CLI: derive Modal URL + set workspace",
59
60
  " /vm token Show API token source and masked value",
60
61
  " /vm token show Show API token source and masked value",
61
62
  " /vm token rotate Generate + persist a new API token (old token invalidated)",
@@ -237,9 +238,6 @@ const handleInit = async (_args, ctx, pi) => {
237
238
  ],
238
239
  vaultMind: {
239
240
  dataDir: ".lancedb",
240
- embedding: {
241
- model: "embeddinggemma",
242
- },
243
241
  ftsEnabled: true,
244
242
  graph: { enabled: true, canvasSync: false },
245
243
  },
@@ -289,22 +287,21 @@ const handleInit = async (_args, ctx, pi) => {
289
287
  msg.push("", '💡 Tip: Tell the agent "Remember: [fact]" to auto-append to the main collection!');
290
288
  if (ctx.hasUI) {
291
289
  ctx.ui.notify(msg.join("\n"), "info");
292
- if (hasPiContextTools(pi)) {
293
- const enable = await ctx.ui.confirm("Enable pi-context integration?", "pi-context tools were detected. Would you like to enable the integration now to capture session context?");
294
- if (enable) {
295
- const existing = JSON.parse(fs.readFileSync(cfgDest, "utf-8"));
296
- existing.extensionCompatibility = existing.extensionCompatibility || {};
297
- existing.extensionCompatibility["pi-context"] = {
298
- tagPatterns: [],
299
- enhanceInjectors: false,
300
- autoEnableAcm: true,
301
- indexContextEvents: true,
302
- ...existing.extensionCompatibility["pi-context"],
303
- enabled: true,
304
- };
305
- fs.writeFileSync(cfgDest, `${JSON.stringify(existing, null, 2)}\n`, "utf-8");
306
- ctx.ui.notify("✅ pi-context integration ENABLED in config", "info");
307
- }
290
+ }
291
+ if (hasPiContextTools(pi)) {
292
+ const existing = JSON.parse(fs.readFileSync(cfgDest, "utf-8"));
293
+ existing.extensionCompatibility = existing.extensionCompatibility || {};
294
+ existing.extensionCompatibility["pi-context"] = {
295
+ tagPatterns: [],
296
+ enhanceInjectors: false,
297
+ autoEnableAcm: true,
298
+ indexContextEvents: true,
299
+ ...existing.extensionCompatibility["pi-context"],
300
+ enabled: true,
301
+ };
302
+ fs.writeFileSync(cfgDest, `${JSON.stringify(existing, null, 2)}\n`, "utf-8");
303
+ if (ctx.hasUI) {
304
+ ctx.ui.notify("✅ pi-context automation enabled (auto-ACM + event indexing)", "info");
308
305
  }
309
306
  }
310
307
  };
@@ -1360,7 +1357,7 @@ const handleWatcher = async (args, ctx, pi) => {
1360
1357
  }
1361
1358
  };
1362
1359
  // ── Setup ────────────────────────────────────────────────────────────────────
1363
- const handleSetup = async (args, ctx) => {
1360
+ const handleSetup = async (args, ctx, pi) => {
1364
1361
  // Parse optional CLI-style args: --vault <path> --remoteUrl <url> --localUrl <url> --model <name> --workspace <name>
1365
1362
  const cliArgs = {};
1366
1363
  const parts = args.trim().split(/\s+--/);
@@ -1382,7 +1379,13 @@ const handleSetup = async (args, ctx) => {
1382
1379
  }
1383
1380
  const hasCliArgs = cliArgs.vault || cliArgs.remoteUrl || cliArgs.localUrl || cliArgs.model || cliArgs.workspace;
1384
1381
  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");
1382
+ if (ctx.hasUI) {
1383
+ ctx.ui.notify("Vault configured. Running personalization as part of onboarding...", "info");
1384
+ await runPersonalize(ctx, pi);
1385
+ }
1386
+ else {
1387
+ ctx.ui.notify("Vault is configured. Run /vm personalize in a TUI session to tune agent personality.", "info");
1388
+ }
1386
1389
  };
1387
1390
  // ── /vm intake ─────────────────────────────────────────────────────────────
1388
1391
  const handleIntake = async (args, ctx) => {
@@ -1671,7 +1674,7 @@ export const registerCommands = (pi) => {
1671
1674
  case "personalize":
1672
1675
  return runPersonalize(ctx, pi);
1673
1676
  case "setup":
1674
- return handleSetup(rest, ctx);
1677
+ return handleSetup(rest, ctx, pi);
1675
1678
  case "token":
1676
1679
  return handleToken(rest, ctx);
1677
1680
  default:
@@ -51,12 +51,8 @@ export const handleBeforeAgentStart = async (pi, event, ctx) => {
51
51
  }
52
52
  // 2. pi-context integration: auto-ACM and tag-based triggers
53
53
  const piContextCfg = getPiContextConfig(cfg);
54
- if (!piContextCfg.enabled) {
55
- // pi-context explicitly disabled — nothing to do
56
- }
57
- else if (!hasPiContextTools(pi)) {
58
- // pi-context enabled in config but tools not available — log once per turn_start
59
- console.warn("[pi-vault-mind] pi-context enabled but tools not found. Install pi-context extension.");
54
+ if (!piContextCfg.enabled || !hasPiContextTools(pi)) {
55
+ // pi-context is disabled or unavailable skip integration silently.
60
56
  }
61
57
  else {
62
58
  if (piContextCfg.autoEnableAcm) {
@@ -11,8 +11,8 @@
11
11
  * Binds 127.0.0.1 only — no network exposure. Port defaults to 11435,
12
12
  * configurable via vaultMind.httpPort in pi-vault-mind.config.json.
13
13
  *
14
- * Used by the obsidian-shellcommands plugin to notify pi of file saves,
15
- * replacing fs.watch reliance with explicit push notifications.
14
+ * Used by the Vault Mind Obsidian plugin and local automation scripts to notify
15
+ * pi of file saves via explicit push notifications.
16
16
  *
17
17
  * New Stage-1 routes (auth-gated by `PVM_API_TOKEN`):
18
18
  * GET /vm/status, POST /vm/init, POST /vm/setup, POST /server/start,
@@ -11,8 +11,8 @@
11
11
  * Binds 127.0.0.1 only — no network exposure. Port defaults to 11435,
12
12
  * configurable via vaultMind.httpPort in pi-vault-mind.config.json.
13
13
  *
14
- * Used by the obsidian-shellcommands plugin to notify pi of file saves,
15
- * replacing fs.watch reliance with explicit push notifications.
14
+ * Used by the Vault Mind Obsidian plugin and local automation scripts to notify
15
+ * pi of file saves via explicit push notifications.
16
16
  *
17
17
  * New Stage-1 routes (auth-gated by `PVM_API_TOKEN`):
18
18
  * GET /vm/status, POST /vm/init, POST /vm/setup, POST /server/start,
@@ -1,4 +1,5 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ export declare const detectVaultFromCwd: (cwd: string) => string | null;
2
3
  export declare const createCollectionWizard: (ctx: ExtensionContext) => Promise<void>;
3
4
  export declare const createInjectorWizard: (ctx: ExtensionContext) => Promise<void>;
4
5
  export declare const setupWizard: (ctx: ExtensionContext, cliArgs?: {
@@ -2,6 +2,15 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { MODAL_TOKEN_ENV, createModalClient, modalUrl, resolveModalToken } from "./modal-config.js";
4
4
  import { collectionNames, findConfig, getGlobalConfigPath, loadConfig, shrinkHome, } from "./utils.js";
5
+ export const detectVaultFromCwd = (cwd) => {
6
+ const obsidianDir = path.join(cwd, ".obsidian");
7
+ try {
8
+ return fs.statSync(obsidianDir).isDirectory() ? cwd : null;
9
+ }
10
+ catch {
11
+ return null;
12
+ }
13
+ };
5
14
  export const createCollectionWizard = async (ctx) => {
6
15
  const { project: cfgPath } = findConfig(ctx.cwd);
7
16
  if (!cfgPath) {
@@ -84,6 +93,7 @@ export const createInjectorWizard = async (ctx) => {
84
93
  };
85
94
  export const setupWizard = async (ctx, cliArgs) => {
86
95
  const existingGlobal = fs.existsSync(getGlobalConfigPath());
96
+ const detectedVaultPath = detectVaultFromCwd(ctx.cwd);
87
97
  // ── Non-interactive / CLI mode ──────────────────────────────────────────
88
98
  if (cliArgs && (cliArgs.vault || cliArgs.remoteUrl || cliArgs.localUrl || cliArgs.workspace)) {
89
99
  const config = existingGlobal
@@ -92,8 +102,9 @@ export const setupWizard = async (ctx, cliArgs) => {
92
102
  config.vaultMind = config.vaultMind || {};
93
103
  config.vaultMind.embedding = config.vaultMind.embedding || {};
94
104
  config.vaultMind.vaults = config.vaultMind.vaults || {};
95
- if (cliArgs.vault) {
96
- config.vaultMind.vaults.default = { path: shrinkHome(cliArgs.vault), autoSync: true };
105
+ const effectiveVaultPath = cliArgs.vault || detectedVaultPath || undefined;
106
+ if (effectiveVaultPath) {
107
+ config.vaultMind.vaults.default = { path: shrinkHome(effectiveVaultPath), autoSync: true };
97
108
  }
98
109
  if (cliArgs.remoteUrl) {
99
110
  config.vaultMind.embedding.remoteUrl = cliArgs.remoteUrl;
@@ -110,16 +121,19 @@ export const setupWizard = async (ctx, cliArgs) => {
110
121
  if (cliArgs.workspace) {
111
122
  config.vaultMind.embedding.modal = config.vaultMind.embedding.modal || {};
112
123
  config.vaultMind.embedding.modal.workspace = cliArgs.workspace;
124
+ if (!cliArgs.remoteUrl) {
125
+ config.vaultMind.embedding.remoteUrl = modalUrl(cliArgs.workspace);
126
+ }
113
127
  }
114
128
  const dir = path.dirname(getGlobalConfigPath());
115
129
  if (!fs.existsSync(dir))
116
130
  fs.mkdirSync(dir, { recursive: true });
117
131
  fs.writeFileSync(getGlobalConfigPath(), `${JSON.stringify(config, null, 2)}\n`, "utf-8");
118
132
  const lines = ["✅ Global config written:", ` ${getGlobalConfigPath()}`, ""];
119
- if (cliArgs.vault)
120
- lines.push(` Vault: ${cliArgs.vault}`);
121
- if (cliArgs.remoteUrl)
122
- lines.push(` Remote URL: ${cliArgs.remoteUrl}`);
133
+ if (effectiveVaultPath)
134
+ lines.push(` Vault: ${effectiveVaultPath}`);
135
+ if (config.vaultMind.embedding.remoteUrl)
136
+ lines.push(` Remote URL: ${config.vaultMind.embedding.remoteUrl}`);
123
137
  if (cliArgs.localUrl)
124
138
  lines.push(` Local URL: ${cliArgs.localUrl}`);
125
139
  if (cliArgs.model)
@@ -152,7 +166,12 @@ export const setupWizard = async (ctx, cliArgs) => {
152
166
  }
153
167
  }
154
168
  // ── Step 1: Vault path ─────────────────────────────────────────────────
155
- const vaultPath = await ctx.ui.input("Obsidian vault path (absolute):", `/home/${process.env.USER || "you"}/Obsidian/`);
169
+ const autoVaultPath = cliArgs?.vault?.trim() || detectedVaultPath || "";
170
+ if (autoVaultPath) {
171
+ ctx.ui.notify(`Using current vault path: ${autoVaultPath}`, "info");
172
+ }
173
+ const vaultPath = autoVaultPath ||
174
+ (await ctx.ui.input("Obsidian vault path (absolute):", `/home/${process.env.USER || "you"}/Obsidian/`));
156
175
  if (!vaultPath) {
157
176
  ctx.ui.notify("Setup cancelled — vault path is required.", "warning");
158
177
  return;
@@ -168,28 +187,69 @@ export const setupWizard = async (ctx, cliArgs) => {
168
187
  }
169
188
  }
170
189
  // ── Step 2: Embedding config ──────────────────────────────────────────
171
- const remoteUrl = await ctx.ui.input("Remote Embedding URL (e.g. https://.../v1, optional):", "");
190
+ let remoteUrl = await ctx.ui.input("Remote Embedding URL (e.g. https://.../v1, optional):", "");
172
191
  const localUrl = await ctx.ui.input("Local Embedding URL (e.g. http://127.0.0.1:11434/v1, optional):", "");
173
192
  let useTransformers = false;
193
+ let guidedModalWorkspace;
174
194
  if (!remoteUrl && !localUrl) {
175
- const useTF = await ctx.ui.confirm("Use Transformers", "No embedding URLs configured. Use local transformers (all-MiniLM-L6-v2, 384 dims, offline)?");
176
- if (!useTF) {
195
+ const route = await ctx.ui.select("No embedding URL configured", [
196
+ "Use local transformers (offline)",
197
+ "Guide me through Modal setup",
198
+ "Cancel setup",
199
+ ]);
200
+ if (!route || route === "Cancel setup") {
177
201
  ctx.ui.notify("Setup cancelled — at least one URL or transformers is required.", "warning");
178
202
  return;
179
203
  }
180
- useTransformers = true;
204
+ if (route === "Use local transformers (offline)") {
205
+ useTransformers = true;
206
+ }
207
+ else {
208
+ const workspaceSlug = await ctx.ui.input("Modal workspace slug:", "");
209
+ if (!workspaceSlug) {
210
+ ctx.ui.notify("Setup cancelled — workspace is required for Modal guidance.", "warning");
211
+ return;
212
+ }
213
+ guidedModalWorkspace = workspaceSlug.trim();
214
+ remoteUrl = modalUrl(guidedModalWorkspace);
215
+ ctx.ui.notify([
216
+ "Modal endpoint discovery:",
217
+ `- Derived remote URL: ${remoteUrl}`,
218
+ "- Set token now with one of:",
219
+ " /vm remote token",
220
+ " ./scripts/fetch-modal-token.sh --write",
221
+ "- Reference docs:",
222
+ " docs/getting-started/NEW_VAULT_WALKTHROUGH.md",
223
+ " docs/integrations/MODAL_EMBEDDING.md",
224
+ ].join("\n"), "info");
225
+ }
181
226
  }
182
- const model = await ctx.ui.input("Model name:", "embeddinggemma");
183
- if (!model) {
184
- ctx.ui.notify("Setup cancelled — model name is required.", "warning");
227
+ const model = (await ctx.ui.input("Embedding model key (optional):", ""))?.trim();
228
+ if (model === undefined) {
229
+ ctx.ui.notify("Setup cancelled.", "warning");
185
230
  return;
186
231
  }
187
232
  const dimStr = await ctx.ui.input("Dimensions (optional):", "");
188
233
  const dim = dimStr ? Number.parseInt(dimStr, 10) : undefined;
189
- let workspace;
190
- if (remoteUrl?.includes("modal.run")) {
234
+ let workspace = guidedModalWorkspace;
235
+ if (!workspace && remoteUrl?.includes("modal.run")) {
191
236
  workspace = await ctx.ui.input("Modal workspace (optional):", "");
192
237
  }
238
+ // ── Step 3: Deterministic runtime settings ──────────────────────────────
239
+ const enableContextAutomation = await ctx.ui.confirm("Context automation", "Enable pi-context integration with auto-/acm trigger and event indexing?");
240
+ const enableAutoStart = await ctx.ui.confirm("Vault auto-start", "Auto-start watcher/server for this vault when pi session starts?");
241
+ const apply = await ctx.ui.confirm("Apply setup", [
242
+ `Vault: ${vaultPath}`,
243
+ `Remote URL: ${remoteUrl || "none"}`,
244
+ `Local URL: ${localUrl || "none"}`,
245
+ `Model: ${model || "(auto/server default)"}`,
246
+ `Context automation: ${enableContextAutomation ? "enabled" : "disabled"}`,
247
+ `Vault auto-start: ${enableAutoStart ? "enabled" : "disabled"}`,
248
+ ].join("\n"));
249
+ if (!apply) {
250
+ ctx.ui.notify("Setup cancelled — no changes written.", "warning");
251
+ return;
252
+ }
193
253
  // ── Write config ────────────────────────────────────────────────────────
194
254
  const config = existingGlobal
195
255
  ? JSON.parse(fs.readFileSync(getGlobalConfigPath(), "utf-8"))
@@ -201,7 +261,8 @@ export const setupWizard = async (ctx, cliArgs) => {
201
261
  config.vaultMind.embedding.remoteUrl = remoteUrl;
202
262
  if (localUrl)
203
263
  config.vaultMind.embedding.localUrl = localUrl;
204
- config.vaultMind.embedding.model = model;
264
+ if (model)
265
+ config.vaultMind.embedding.model = model;
205
266
  if (useTransformers)
206
267
  config.vaultMind.embedding.useTransformers = true;
207
268
  if (dim)
@@ -210,9 +271,22 @@ export const setupWizard = async (ctx, cliArgs) => {
210
271
  config.vaultMind.embedding.modal = config.vaultMind.embedding.modal || {};
211
272
  config.vaultMind.embedding.modal.workspace = workspace;
212
273
  }
213
- config.vaultMind.vaults.default = { path: shrinkHome(vaultPath), autoSync: true };
274
+ config.vaultMind.vaults.default = {
275
+ path: shrinkHome(vaultPath),
276
+ autoSync: true,
277
+ autoStart: enableAutoStart,
278
+ };
214
279
  config.vaultMind.graph = config.vaultMind.graph || { enabled: true, canvasSync: true };
215
280
  config.vaultMind.ftsEnabled = config.vaultMind.ftsEnabled !== false;
281
+ config.extensionCompatibility = config.extensionCompatibility || {};
282
+ config.extensionCompatibility["pi-context"] = {
283
+ tagPatterns: [],
284
+ enhanceInjectors: false,
285
+ autoEnableAcm: true,
286
+ indexContextEvents: true,
287
+ ...(config.extensionCompatibility["pi-context"] || {}),
288
+ enabled: enableContextAutomation,
289
+ };
216
290
  const dir = path.dirname(getGlobalConfigPath());
217
291
  if (!fs.existsSync(dir))
218
292
  fs.mkdirSync(dir, { recursive: true });
@@ -224,10 +298,19 @@ export const setupWizard = async (ctx, cliArgs) => {
224
298
  ` Vault: ${vaultPath}`,
225
299
  ` Remote: ${remoteUrl || "none"}`,
226
300
  ` Local: ${localUrl || "none"}`,
227
- ` Model: ${model}`,
301
+ ` Model: ${model || "(auto/server default)"}`,
302
+ ` Context automation: ${enableContextAutomation ? "enabled" : "disabled"}`,
303
+ ` Vault auto-start: ${enableAutoStart ? "enabled" : "disabled"}`,
228
304
  "",
229
305
  "Next: /vm watcher start (or restart pi for auto-start)",
230
306
  ].join("\n"), "info");
307
+ if (remoteUrl?.includes("modal.run")) {
308
+ ctx.ui.notify([
309
+ "Modal token reminder:",
310
+ "- /vm remote token",
311
+ "- ./scripts/fetch-modal-token.sh --write",
312
+ ].join("\n"), "info");
313
+ }
231
314
  };
232
315
  /**
233
316
  * Interactive Modal embedding configuration + "Test connection" action.
@@ -347,30 +430,68 @@ export const openSettingsDashboard = async (ctx) => {
347
430
  let running = true;
348
431
  while (running) {
349
432
  const choice = await ctx.ui.select("pi-vault-mind Settings", [
350
- "Manage Collections",
351
- "Manage Injectors",
352
- "Modal Embedding",
353
- "Extension Integrations",
433
+ "Collections (storage + search targets)",
434
+ "Injectors (regex capture rules)",
435
+ "Embedding + remote provider",
436
+ "Context automation (pi-context)",
437
+ "Config (view active values)",
438
+ "Obsidian plugin settings (what to configure)",
354
439
  "Exit",
355
440
  ]);
356
441
  if (!choice || choice === "Exit") {
357
442
  running = false;
358
443
  break;
359
444
  }
360
- if (choice === "Manage Collections") {
445
+ if (choice === "Collections (storage + search targets)") {
361
446
  await manageCollections(ctx, cfgPath);
362
447
  }
363
- else if (choice === "Manage Injectors") {
448
+ else if (choice === "Injectors (regex capture rules)") {
364
449
  await manageInjectors(ctx, cfgPath);
365
450
  }
366
- else if (choice === "Modal Embedding") {
451
+ else if (choice === "Embedding + remote provider") {
367
452
  await configureModalWizard(ctx);
368
453
  }
369
- else if (choice === "Extension Integrations") {
454
+ else if (choice === "Context automation (pi-context)") {
370
455
  await manageExtensions(ctx, cfgPath);
371
456
  }
457
+ else if (choice === "Config (view active values)") {
458
+ await showConfigSettings(ctx, cfgPath);
459
+ }
460
+ else if (choice === "Obsidian plugin settings (what to configure)") {
461
+ showObsidianSettingsHelp(ctx);
462
+ }
372
463
  }
373
464
  };
465
+ const showConfigSettings = async (ctx, cfgPath) => {
466
+ const merged = loadConfig(ctx.cwd);
467
+ const paths = findConfig(ctx.cwd);
468
+ const lines = [
469
+ "Config sources:",
470
+ `- Global: ${paths.global || "(none)"}`,
471
+ `- Project: ${paths.project || "(none)"}`,
472
+ "",
473
+ "Merged runtime config:",
474
+ JSON.stringify(merged.vaultMind, null, 2),
475
+ ];
476
+ ctx.ui.notify(lines.join("\n"), "info");
477
+ const projectRaw = fs.readFileSync(cfgPath, "utf-8");
478
+ ctx.ui.notify(["Project config JSON:", projectRaw].join("\n"), "info");
479
+ };
480
+ const showObsidianSettingsHelp = (ctx) => {
481
+ ctx.ui.notify([
482
+ "Obsidian plugin settings (Vault Mind):",
483
+ "- Host / Port",
484
+ "- Pi binary path",
485
+ "- Check extension on startup",
486
+ "- Include editor context",
487
+ "- Include file picker",
488
+ "- Include slash commands",
489
+ "- Folder layout: Inbox / Library / Presentations / Journal",
490
+ "- API token (Import from dotenv)",
491
+ "",
492
+ "Location: Obsidian → Settings → Community plugins → Vault Mind",
493
+ ].join("\n"), "info");
494
+ };
374
495
  const manageCollections = async (ctx, cfgPath) => {
375
496
  const existing = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
376
497
  const names = Object.keys(existing.collections || {});
@@ -422,22 +543,27 @@ const manageInjectors = async (ctx, cfgPath) => {
422
543
  const manageExtensions = async (ctx, cfgPath) => {
423
544
  const existing = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
424
545
  existing.extensionCompatibility = existing.extensionCompatibility || {};
425
- const piCtx = existing.extensionCompatibility["pi-context"] || {};
426
- const choice = await ctx.ui.select("pi-context Integration", [
427
- `Toggle Enabled (currently ${piCtx.enabled ? "ON" : "OFF"})`,
428
- `Toggle Auto-ACM (currently ${piCtx.autoEnableAcm !== false ? "ON" : "OFF"})`,
429
- `Toggle Event Indexing (currently ${piCtx.indexContextEvents !== false ? "ON" : "OFF"})`,
546
+ const piCtx = {
547
+ enabled: true,
548
+ autoEnableAcm: true,
549
+ indexContextEvents: true,
550
+ ...(existing.extensionCompatibility["pi-context"] || {}),
551
+ };
552
+ const choice = await ctx.ui.select("Context automation (pi-context)", [
553
+ `Toggle integration (currently ${piCtx.enabled ? "ON" : "OFF"})`,
554
+ `Toggle auto-/acm trigger (currently ${piCtx.autoEnableAcm !== false ? "ON" : "OFF"})`,
555
+ `Toggle context event indexing (currently ${piCtx.indexContextEvents !== false ? "ON" : "OFF"})`,
430
556
  "Back",
431
557
  ]);
432
558
  if (!choice || choice === "Back")
433
559
  return;
434
- if (choice.startsWith("Toggle Enabled")) {
560
+ if (choice.startsWith("Toggle integration")) {
435
561
  piCtx.enabled = !piCtx.enabled;
436
562
  }
437
- else if (choice.startsWith("Toggle Auto-ACM")) {
563
+ else if (choice.startsWith("Toggle auto-/acm trigger")) {
438
564
  piCtx.autoEnableAcm = piCtx.autoEnableAcm === false;
439
565
  }
440
- else if (choice.startsWith("Toggle Event Indexing")) {
566
+ else if (choice.startsWith("Toggle context event indexing")) {
441
567
  piCtx.indexContextEvents = piCtx.indexContextEvents === false;
442
568
  }
443
569
  existing.extensionCompatibility["pi-context"] = piCtx;
package/dist/src/types.js CHANGED
@@ -32,7 +32,6 @@ export const DEFAULT_CONFIG = {
32
32
  },
33
33
  embedding: {
34
34
  useTransformers: true,
35
- model: "embeddinggemma",
36
35
  localUrl: "http://127.0.0.1:11434",
37
36
  },
38
37
  graph: {
@@ -45,7 +44,7 @@ export const DEFAULT_CONFIG = {
45
44
  },
46
45
  extensionCompatibility: {
47
46
  "pi-context": {
48
- enabled: false,
47
+ enabled: true,
49
48
  tagPatterns: [],
50
49
  enhanceInjectors: false,
51
50
  autoEnableAcm: true,
package/dist/src/utils.js CHANGED
@@ -7,7 +7,7 @@ export const CONFIG_FILES = ["pi-vault-mind.config.json", ".pi/vault-mind.config
7
7
  export const getGlobalConfigDir = () => path.join(process.env.HOME || process.env.USERPROFILE || "", ".pi", "agent");
8
8
  /** Lazy — recomputes HOME each call so tests can override it. */
9
9
  export const getGlobalConfigPath = () => path.join(getGlobalConfigDir(), "vault-mind.config.json");
10
- export const EXT_ROOT = path.join(import.meta.dirname, "..");
10
+ export const EXT_ROOT = path.join(import.meta.dirname, "..", "..");
11
11
  /** Expand a leading `~` to the user's home directory. */
12
12
  export const expandHome = (p) => {
13
13
  if (p.startsWith("~/") || p === "~") {
@@ -123,7 +123,7 @@ export const hasPiContextTools = (pi) => {
123
123
  };
124
124
  export const isPiContextEnabled = (cfg) => cfg.extensionCompatibility?.["pi-context"]?.enabled === true;
125
125
  export const getPiContextConfig = (cfg) => ({
126
- enabled: false,
126
+ enabled: true,
127
127
  tagPatterns: [],
128
128
  enhanceInjectors: false,
129
129
  autoEnableAcm: true,
@@ -99,7 +99,7 @@ describe("pi-vault-mind", () => {
99
99
  assert.equal(types.DEFAULT_CONFIG.version, 2);
100
100
  assert.ok(types.DEFAULT_CONFIG.collections, "collections should exist");
101
101
  assert.ok(types.DEFAULT_CONFIG.collections.main, "should have main collection");
102
- assert.equal(types.DEFAULT_CONFIG.vaultMind.embedding.model, "embeddinggemma");
102
+ assert.equal(types.DEFAULT_CONFIG.vaultMind.embedding.model, undefined);
103
103
  }
104
104
  finally {
105
105
  process.chdir(cwd);
@@ -0,0 +1,71 @@
1
+ import { strict as assert } from "node:assert";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { afterEach, describe, it } from "node:test";
6
+ import { detectVaultFromCwd, setupWizard } from "../src/settings-ui.js";
7
+ import { modalUrl } from "../src/modal-config.js";
8
+ import { getGlobalConfigPath } from "../src/utils.js";
9
+ const savedHome = process.env.HOME;
10
+ const mkTmpDir = (prefix) => fs.mkdtempSync(path.join(os.tmpdir(), prefix));
11
+ const makeCtx = (cwd) => ({
12
+ cwd,
13
+ hasUI: false,
14
+ ui: {
15
+ notify: () => undefined,
16
+ },
17
+ });
18
+ const expandMaybeHome = (p, home) => p.startsWith("~/") ? path.join(home, p.slice(2)) : p;
19
+ afterEach(() => {
20
+ if (savedHome === undefined) {
21
+ Reflect.deleteProperty(process.env, "HOME");
22
+ }
23
+ else {
24
+ process.env.HOME = savedHome;
25
+ }
26
+ });
27
+ describe("settings-ui vault detection", () => {
28
+ it("detectVaultFromCwd returns cwd when .obsidian exists", () => {
29
+ const vault = mkTmpDir("pvm-vault-detect-");
30
+ fs.mkdirSync(path.join(vault, ".obsidian"), { recursive: true });
31
+ assert.equal(detectVaultFromCwd(vault), vault);
32
+ fs.rmSync(vault, { recursive: true, force: true });
33
+ });
34
+ it("detectVaultFromCwd returns null when .obsidian missing", () => {
35
+ const dir = mkTmpDir("pvm-nonvault-detect-");
36
+ assert.equal(detectVaultFromCwd(dir), null);
37
+ fs.rmSync(dir, { recursive: true, force: true });
38
+ });
39
+ });
40
+ describe("setupWizard CLI defaults", () => {
41
+ it("auto-uses cwd as vault when cwd is an Obsidian vault", async () => {
42
+ const home = mkTmpDir("pvm-home-");
43
+ const vault = path.join(home, "Vault");
44
+ fs.mkdirSync(path.join(vault, ".obsidian"), { recursive: true });
45
+ process.env.HOME = home;
46
+ await setupWizard(makeCtx(vault), {
47
+ remoteUrl: "https://example.com/v1",
48
+ model: "embeddinggemma",
49
+ });
50
+ const cfg = JSON.parse(fs.readFileSync(getGlobalConfigPath(), "utf-8"));
51
+ const stored = cfg.vaultMind?.vaults?.default?.path;
52
+ assert.ok(stored, "default vault path should be written");
53
+ assert.equal(expandMaybeHome(stored, home), vault);
54
+ assert.equal(cfg.vaultMind?.embedding?.remoteUrl, "https://example.com/v1");
55
+ fs.rmSync(home, { recursive: true, force: true });
56
+ });
57
+ it("derives remoteUrl from workspace in CLI mode", async () => {
58
+ const home = mkTmpDir("pvm-home-");
59
+ const vault = path.join(home, "Vault");
60
+ fs.mkdirSync(path.join(vault, ".obsidian"), { recursive: true });
61
+ process.env.HOME = home;
62
+ await setupWizard(makeCtx(vault), {
63
+ workspace: "acme-workspace",
64
+ model: "embeddinggemma",
65
+ });
66
+ const cfg = JSON.parse(fs.readFileSync(getGlobalConfigPath(), "utf-8"));
67
+ assert.equal(cfg.vaultMind?.embedding?.modal?.workspace, "acme-workspace");
68
+ assert.equal(cfg.vaultMind?.embedding?.remoteUrl, modalUrl("acme-workspace"));
69
+ fs.rmSync(home, { recursive: true, force: true });
70
+ });
71
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-vault-mind",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "Passive Obsidian vault extension for pi. Watches @agent markers, dispatches forked subagents (Miner, Broadcaster, Heavy-Lifter), stores in LanceDB with vector + FTS + graph. Multi-agent 'Drop & Forget' workflow for the pi agent ecosystem.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env bash
2
+ # setup-vault-pi.sh — Bootstrap a vault-local pi agent config.
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/.
6
+ #
7
+ # Usage:
8
+ # cd /path/to/vault
9
+ # bash scripts/setup-vault-pi.sh
10
+ #
11
+ # After setup, start pi with:
12
+ # PI_CODING_AGENT_DIR="$(pwd)/.pi/agent" pi
13
+
14
+ set -euo pipefail
15
+
16
+ VAULT_ROOT="${1:-$(pwd)}"
17
+ PI_DIR="${VAULT_ROOT}/.pi/agent"
18
+
19
+ echo "==> Setting up vault-local pi config at ${PI_DIR}"
20
+
21
+ mkdir -p "${PI_DIR}"
22
+
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
+
27
+ echo ""
28
+ echo "==> Done. Start pi for this vault with:"
29
+ echo " PI_CODING_AGENT_DIR=\"${PI_DIR}\" pi"
30
+ echo ""
31
+ echo " Or from Obsidian Shell Commands:"
32
+ echo " PI_CODING_AGENT_DIR=${PI_DIR} pi"
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env bash
2
+ # vault-pi.sh — Start pi for a vault, ensuring only one instance runs.
3
+ #
4
+ # Usage:
5
+ # bash scripts/vault-pi.sh /path/to/vault
6
+ #
7
+ # Obsidian Shell Commands integration:
8
+ # Command: bash /path/to/pi-vault-mind/scripts/vault-pi.sh {{vault_path}}
9
+ #
10
+ # The script:
11
+ # 1. Checks for an existing PID file at <vault>/.pi/agent/daemon.pid
12
+ # 2. If the PID is alive → exits silently (already running)
13
+ # 3. If stale/dead → cleans up and starts fresh
14
+ # 4. Opens a new terminal window with pi using the vault-local config dir
15
+
16
+ set -euo pipefail
17
+
18
+ VAULT="${1:?Usage: vault-pi.sh <vault-path>}"
19
+ PI_DIR="${VAULT}/.pi/agent"
20
+ PID_FILE="${PI_DIR}/daemon.pid"
21
+
22
+ # Ensure the vault-local agent dir exists
23
+ mkdir -p "${PI_DIR}"
24
+
25
+ # Check for existing process
26
+ if [ -f "${PID_FILE}" ]; then
27
+ PID=$(cat "${PID_FILE}")
28
+ if kill -0 "${PID}" 2>/dev/null; then
29
+ echo "[vault-pi] pi already running for ${VAULT} (PID ${PID})"
30
+ exit 0
31
+ fi
32
+ echo "[vault-pi] Stale PID file (${PID} is dead), cleaning up"
33
+ rm -f "${PID_FILE}"
34
+ fi
35
+
36
+ # Start pi in a new terminal window.
37
+ # macOS: open -a Terminal or open -a iTerm
38
+ # The trap writes the PID and cleans up on exit.
39
+ PI_CMD="PI_CODING_AGENT_DIR='${PI_DIR}' pi; rm -f '${PID_FILE}'"
40
+
41
+ if [ -d "/Applications/iTerm.app" ]; then
42
+ open -a iTerm --args --detach bash -c "echo \$\$ > '${PID_FILE}'; cd '${VAULT}'; ${PI_CMD}"
43
+ elif [ -d "/Applications/Utilities/Terminal.app" ]; then
44
+ osascript -e "tell application \"Terminal\" to do script \"echo \$\$ > '${PID_FILE}'; cd '${VAULT}'; ${PI_CMD}\""
45
+ else
46
+ echo "[vault-pi] No supported terminal found (iTerm or Terminal.app)"
47
+ exit 1
48
+ fi
49
+
50
+ echo "[vault-pi] Started pi for ${VAULT}"
@@ -83,15 +83,15 @@ Uses `all-MiniLM-L6-v2` (384 dimensions). Downloads ONNX model on first use.
83
83
  ## Obsidian Bridge
84
84
 
85
85
  pi-vault-mind runs an HTTP server on `http://127.0.0.1:11435` (configurable
86
- via `vaultMind.httpPort`). The `obsidian-shellcommands` plugin can POST file-save
87
- events to `/vault-mind/scan` for explicit, reliable file-watching instead of
88
- relying solely on `fs.watch`.
86
+ via `vaultMind.httpPort`). The Vault Mind plugin (or any local automation)
87
+ can POST file-save events to `/vault-mind/scan` for explicit, reliable
88
+ file-watching instead of relying solely on `fs.watch`.
89
89
 
90
90
  ```bash
91
- # Shell command config in Obsidian:
91
+ # Manual local-bridge trigger:
92
92
  curl -s -X POST http://127.0.0.1:11435/vault-mind/scan \
93
93
  -H "Content-Type: application/json" \
94
- -d '{"file":"{{file_path:absolute}}"}'
94
+ -d '{"file":"/absolute/path/to/note.md"}'
95
95
  ```
96
96
 
97
97
  Endpoints:
@@ -73,7 +73,7 @@ obsidian plugin:install id=obsidian-git enable
73
73
  obsidian plugin:install id=obsidian-breadcrumbs enable
74
74
  obsidian plugin:install id=graph-analysis enable
75
75
  obsidian plugin:install id=actions-uri enable
76
- obsidian plugin:install id=shellcommands enable
76
+ obsidian plugin:install id=obsidian-pi-vault-mind enable
77
77
  obsidian plugin:install id=obsidian42-brat enable # for beta plugins
78
78
 
79
79
  # Verify
@@ -103,4 +103,4 @@ Community plugins to install:
103
103
  - `Breadcrumbs` — typed-edge graph
104
104
  - `Graph Analysis` — co-citation discovery
105
105
  - `Actions URI` — cross-app automation
106
- - `obsidian-shellcommands` — bridge to pi
106
+ - `Vault Mind plugin` — native setup/status/chat + local bridge trigger