pi-vault-mind 0.16.31 → 0.16.32

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 CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.32 — 2026-08-28
4
+
5
+ ### Changed
6
+
7
+ - **Concise setup progress.** `pvm setup` now reports one bounded progress line per extension and curated skill while preserving captured installer diagnostics on failure.
8
+
9
+ ### Fixed
10
+
11
+ - **Vault-isolated main sessions.** `pvm run` disables implicit skill and prompt-template discovery, then explicitly loads only curated and package skills rooted in the vault-local agent directory. Specialist skill availability can no longer select a specialist identity for the interactive main session.
12
+ - **Escaping package paths.** Setup removes local package paths that resolve outside the vault-local agent directory while preserving npm packages and in-runtime custom packages.
13
+
3
14
  ## 0.16.31 — 2026-08-28
4
15
 
5
16
  ### Added
package/README.md CHANGED
@@ -123,37 +123,32 @@ obsidian plugin:install id=obsidian42-brat enable
123
123
  # In Obsidian: Settings → BRAT → Beta Plugin List → Add
124
124
  ```
125
125
 
126
- ### Recommended: kepano/obsidian-skills (pi skills)
126
+ ### Curated vault-local skills
127
127
 
128
- Install via the official [`skills` CLI](https://github.com/vercel-labs/skills)
129
- (pi is a first-class target agent — auto-detected):
128
+ `pvm setup` installs the supported Obsidian and NotebookLM skills into the
129
+ vault-local Pi agent directory:
130
130
 
131
131
  ```bash
132
- # Non-interactive: install all 5 globally for pi
133
- DISABLE_TELEMETRY=1 yes y | npx -y skills add https://github.com/kepano/obsidian-skills \
134
- --skill obsidian-markdown --skill obsidian-bases \
135
- --skill json-canvas --skill obsidian-cli --skill defuddle \
136
- -g -a pi --copy -y
137
- ```
138
-
139
- This puts them at `~/.pi/agent/skills/<name>/SKILL.md` where pi
140
- auto-discovers them. The CLI handles agent detection, symlink/copy,
141
- and security risk confirmations.
142
-
143
- **Verify they're discoverable:**
144
-
145
- ```bash
146
- for s in obsidian-markdown obsidian-bases json-canvas obsidian-cli defuddle; do
147
- [ -f ~/.pi/agent/skills/$s/SKILL.md ] && echo "✅ $s" || echo "❌ $s"
148
- done
132
+ pvm setup /path/to/your/vault
149
133
  ```
150
- - `obsidian-markdown` — agent learns Obsidian-flavored markdown syntax
151
- - `obsidian-bases` — agent learns to generate valid `.base` files (database views)
152
- - `json-canvas` — agent learns Canvas JSON format (used for our graph sync)
153
- - `obsidian-cli` — agent learns to use the Obsidian CLI
154
- - `defuddle` — cleaner markdown extraction from web pages
155
134
 
156
- These skills let the **Miner, Broadcaster, and Heavy-Lifter agents** produce valid Obsidian content.
135
+ The skills live under
136
+ `<vault>/.vault-mind/.pi/agent/skills/<name>/SKILL.md`. `pvm run` disables
137
+ Pi's implicit global/project skill discovery and explicitly loads only these
138
+ curated skills plus skills provided by packages installed in the same
139
+ vault-local agent directory. Do not install these skills globally for a Vault
140
+ Mind session; global copies defeat vault isolation and can create skill-name
141
+ conflicts.
142
+
143
+ - `obsidian-markdown` — Obsidian-flavored Markdown syntax
144
+ - `obsidian-bases` — valid `.base` database views
145
+ - `json-canvas` — Canvas JSON used by graph sync
146
+ - `obsidian-cli` — Obsidian CLI operation
147
+ - `nlm-skill` — NotebookLM CLI and MCP workflows
148
+
149
+ These skills let the **Miner, Broadcaster, and Heavy-Lifter agents** produce
150
+ valid Obsidian content without changing the identity of the interactive main
151
+ agent.
157
152
 
158
153
  ### Built-in Obsidian features used
159
154
 
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Canonical list of pi extensions required/optional for vault-mind.
2
+ * Canonical extension list for the legacy `/vm setup` and Obsidian setup
3
+ * surfaces. The standalone `pvm` CLI has a broader package-managed runtime
4
+ * contract in `runtime/pvm-extensions.json`.
3
5
  *
4
- * This is the SINGLE SOURCE OF TRUTH. Shell scripts and the Obsidian plugin
5
- * consume generated JSON (see scripts/generate-extension-packages-json.mjs).
6
- * When adding/removing a package, edit ONLY this file and regenerate.
6
+ * Generate this surface with scripts/generate-extension-packages-json.mjs.
7
7
  */
8
8
  export declare const PI_REQUIRED_EXTENSIONS: readonly ["npm:pi-vault-mind", "npm:pi-context", "npm:@kylebrodeur/pi-model-discovery", "npm:@kylebrodeur/pi-model-router"];
9
9
  export declare const PI_OPTIONAL_EXTENSIONS: readonly ["npm:pi-1password"];
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Canonical list of pi extensions required/optional for vault-mind.
2
+ * Canonical extension list for the legacy `/vm setup` and Obsidian setup
3
+ * surfaces. The standalone `pvm` CLI has a broader package-managed runtime
4
+ * contract in `runtime/pvm-extensions.json`.
3
5
  *
4
- * This is the SINGLE SOURCE OF TRUTH. Shell scripts and the Obsidian plugin
5
- * consume generated JSON (see scripts/generate-extension-packages-json.mjs).
6
- * When adding/removing a package, edit ONLY this file and regenerate.
6
+ * Generate this surface with scripts/generate-extension-packages-json.mjs.
7
7
  */
8
8
  export const PI_REQUIRED_EXTENSIONS = [
9
9
  "npm:pi-vault-mind",
@@ -47,6 +47,8 @@ describe("pvm setup", () => {
47
47
  fs.writeFileSync(path.join(binDir, "pi"), `#!/usr/bin/env node
48
48
  const fs = require("node:fs");
49
49
  fs.appendFileSync(process.env.PVM_TEST_PI_LOG, JSON.stringify(process.argv.slice(2)) + "\\n");
50
+ process.stdout.write("NOISY INSTALL STDOUT\\n");
51
+ process.stderr.write("NOISY INSTALL STDERR\\n");
50
52
  `, { mode: 0o755 });
51
53
  fs.writeFileSync(path.join(binDir, "uv"), `#!/usr/bin/env node
52
54
  const fs = require("node:fs");
@@ -121,6 +123,37 @@ fs.appendFileSync(process.env.PVM_TEST_UV_LOG, JSON.stringify(process.argv.slice
121
123
  ]) {
122
124
  assert.ok(fs.existsSync(path.join(nlmSkillDir, "references", ref)), `nlm-skill reference missing: ${ref}`);
123
125
  }
126
+ assert.doesNotMatch(result.stdout + result.stderr, /NOISY INSTALL/);
127
+ assert.match(result.stdout, /\[6\/6\] npm:pi-subagents@0\.58\.0 \.\.\. done/);
128
+ assert.match(result.stdout, /Curated skills \(5\)/);
129
+ });
130
+ it("removes package paths that escape the vault-local agent directory", () => {
131
+ const agentDir = path.join(vaultPath, ".vault-mind", ".pi", "agent");
132
+ const settingsPath = path.join(agentDir, "settings.json");
133
+ const insidePackage = path.join(agentDir, "packages", "custom-extension");
134
+ fs.mkdirSync(insidePackage, { recursive: true });
135
+ fs.writeFileSync(settingsPath, `${JSON.stringify({
136
+ packages: ["npm:pi-vault-mind", "../../../../content-writing-system", insidePackage],
137
+ theme: "dark",
138
+ }, null, 2)}\n`);
139
+ const result = runSetup(["--resources-only", vaultPath]);
140
+ assert.equal(result.status, 0, result.stderr || result.stdout);
141
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
142
+ assert.deepEqual(settings.packages, ["npm:pi-vault-mind", insidePackage]);
143
+ assert.equal(settings.theme, "dark");
144
+ assert.match(result.stdout, /Removed 1 external package path/);
145
+ });
146
+ it("prints captured installer diagnostics when an extension install fails", () => {
147
+ fs.writeFileSync(path.join(binDir, "pi"), `#!/usr/bin/env node
148
+ const args = process.argv.slice(2);
149
+ if (args[0] === "install" && args[1] === "npm:pi-context") {
150
+ process.stderr.write("PI_CONTEXT_INSTALL_FAILED\n");
151
+ process.exit(23);
152
+ }
153
+ `, { mode: 0o755 });
154
+ const result = runSetup(["--resources-only", vaultPath]);
155
+ assert.notEqual(result.status, 0);
156
+ assert.match(result.stderr + result.stdout, /PI_CONTEXT_INSTALL_FAILED/);
124
157
  });
125
158
  it("ignores npm exec's transient Pi binary when a stable Pi install is available", () => {
126
159
  const transientBin = path.join(testDir, "_npx", "cache-key", "node_modules", ".bin");
@@ -19,11 +19,21 @@ describe("vault-pi launcher", () => {
19
19
  outputPath = path.join(testDir, "pi-output.txt");
20
20
  fs.mkdirSync(vaultPath, { recursive: true });
21
21
  fs.mkdirSync(binDir, { recursive: true });
22
+ const skillFiles = [
23
+ path.join(vaultPath, ".vault-mind", ".pi", "agent", "npm", "node_modules", "@scope", "package", "skills", "scoped-skill", "SKILL.md"),
24
+ path.join(vaultPath, ".vault-mind", ".pi", "agent", "npm", "node_modules", "pi-context", "skills", "context-management", "SKILL.md"),
25
+ path.join(vaultPath, ".vault-mind", ".pi", "agent", "skills", "obsidian-cli", "SKILL.md"),
26
+ ];
27
+ for (const skillFile of skillFiles) {
28
+ fs.mkdirSync(path.dirname(skillFile), { recursive: true });
29
+ fs.writeFileSync(skillFile, "---\nname: test\n---\n");
30
+ }
22
31
  fs.writeFileSync(path.join(binDir, "pi"), `#!/usr/bin/env bash
23
32
  set -euo pipefail
24
33
  pid_file="$PWD/.vault-mind/.pi/agent/daemon.pid"
25
- printf 'cwd=%s\nagent=%s\nargs=%s\npid=%s\n' \
26
- "$PWD" "$PI_CODING_AGENT_DIR" "$*" "$(cat "$pid_file")" > "$PVM_TEST_OUTPUT"
34
+ printf 'cwd=%s\nagent=%s\npid=%s\n' \
35
+ "$PWD" "$PI_CODING_AGENT_DIR" "$(cat "$pid_file")" > "$PVM_TEST_OUTPUT"
36
+ printf 'arg=%s\n' "$@" >> "$PVM_TEST_OUTPUT"
27
37
  `, { mode: 0o755 });
28
38
  for (const command of ["open", "osascript"]) {
29
39
  fs.writeFileSync(path.join(binDir, command), "#!/usr/bin/env bash\nexit 97\n", {
@@ -48,10 +58,42 @@ printf 'cwd=%s\nagent=%s\nargs=%s\npid=%s\n' \
48
58
  const output = fs.readFileSync(outputPath, "utf-8");
49
59
  assert.match(output, new RegExp(`^cwd=${vaultPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m"));
50
60
  assert.match(output, new RegExp(`^agent=${path.join(vaultPath, ".vault-mind", ".pi", "agent").replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m"));
51
- assert.match(output, /^args=$/m);
61
+ const args = [...output.matchAll(/^arg=(.*)$/gm)].map((match) => match[1]);
62
+ const agentDir = path.join(vaultPath, ".vault-mind", ".pi", "agent");
63
+ assert.deepEqual(args, [
64
+ "--no-skills",
65
+ "--no-prompt-templates",
66
+ "--skill",
67
+ path.join(agentDir, "npm", "node_modules", "@scope", "package", "skills", "scoped-skill", "SKILL.md"),
68
+ "--skill",
69
+ path.join(agentDir, "npm", "node_modules", "pi-context", "skills", "context-management", "SKILL.md"),
70
+ "--skill",
71
+ path.join(agentDir, "skills", "obsidian-cli", "SKILL.md"),
72
+ ]);
52
73
  assert.match(output, /^pid=\d+$/m);
53
74
  assert.equal(fs.existsSync(path.join(vaultPath, ".vault-mind", ".pi", "agent", "daemon.pid")), false);
54
75
  });
76
+ it("preserves the vault-local 1Password environment flag", () => {
77
+ const agentDir = path.join(vaultPath, ".vault-mind", ".pi", "agent");
78
+ fs.mkdirSync(path.join(agentDir, "npm", "node_modules", "pi-1password"), {
79
+ recursive: true,
80
+ });
81
+ fs.mkdirSync(path.join(vaultPath, ".vault-mind"), { recursive: true });
82
+ fs.writeFileSync(path.join(vaultPath, ".vault-mind", ".env.1pass"), "TOKEN=op://vault/item\n");
83
+ const result = spawnSync("bash", [launcher, "--same-terminal", vaultPath], {
84
+ cwd: testDir,
85
+ encoding: "utf-8",
86
+ env: {
87
+ ...process.env,
88
+ PATH: `${binDir}:${process.env.PATH ?? ""}`,
89
+ PVM_TEST_OUTPUT: outputPath,
90
+ },
91
+ });
92
+ assert.equal(result.status, 0, result.stderr || result.stdout);
93
+ const output = fs.readFileSync(outputPath, "utf-8");
94
+ const args = [...output.matchAll(/^arg=(.*)$/gm)].map((match) => match[1]);
95
+ assert.deepEqual(args.slice(-2), ["--op-env", ".vault-mind/.env.1pass"]);
96
+ });
55
97
  it("ignores npm exec's transient Pi binary when a stable Pi install is available", () => {
56
98
  const transientBin = path.join(testDir, "_npx", "cache-key", "node_modules", ".bin");
57
99
  fs.mkdirSync(transientBin, { recursive: true });
@@ -102,6 +144,30 @@ printf 'cwd=%s\nagent=%s\nargs=%s\npid=%s\n' \
102
144
  assert.equal(result.status, 97);
103
145
  assert.equal(fs.existsSync(outputPath), false);
104
146
  });
147
+ it("passes isolated skill arguments through new-window command construction", () => {
148
+ for (const command of ["open", "osascript"]) {
149
+ fs.writeFileSync(path.join(binDir, command), `#!/usr/bin/env node
150
+ const fs = require("node:fs");
151
+ fs.writeFileSync(process.env.PVM_TEST_OUTPUT, JSON.stringify(process.argv.slice(2)));
152
+ `, { mode: 0o755 });
153
+ }
154
+ const result = spawnSync("bash", [launcher, "--new-window", vaultPath], {
155
+ cwd: testDir,
156
+ encoding: "utf-8",
157
+ env: {
158
+ ...process.env,
159
+ PATH: `${binDir}:${process.env.PATH ?? ""}`,
160
+ PVM_TEST_OUTPUT: outputPath,
161
+ },
162
+ });
163
+ assert.equal(result.status, 0, result.stderr || result.stdout);
164
+ const commandText = JSON.parse(fs.readFileSync(outputPath, "utf-8")).join("\n");
165
+ assert.match(commandText, /--no-skills/);
166
+ assert.match(commandText, /--no-prompt-templates/);
167
+ assert.match(commandText, /--skill/);
168
+ assert.match(commandText, /obsidian-cli/);
169
+ assert.match(commandText, /SKILL\.md/);
170
+ });
105
171
  it("rejects an unknown launch flag with usage help", () => {
106
172
  const result = spawnSync("bash", [launcher, "--new-widnow"], {
107
173
  cwd: testDir,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-vault-mind",
3
- "version": "0.16.31",
3
+ "version": "0.16.32",
4
4
  "packageManager": "pnpm@10.34.5",
5
5
  "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.",
6
6
  "main": "dist/index.js",
@@ -63,12 +63,18 @@ function commandAvailable(command) {
63
63
  return !result.error && result.status === 0;
64
64
  }
65
65
 
66
+ function printFailureOutput(result) {
67
+ if (result.stdout) process.stderr.write(result.stdout);
68
+ if (result.stderr) process.stderr.write(result.stderr);
69
+ }
70
+
66
71
  function installNotebookLM() {
72
+ console.log("==> NotebookLM CLI");
67
73
  const versionCheck = spawnSync("nlm", ["--version"], { encoding: "utf-8" });
68
74
  if (!versionCheck.error && versionCheck.status === 0) {
69
75
  const installed = (versionCheck.stdout + versionCheck.stderr).trim();
70
76
  if (installed.includes("0.9.13")) {
71
- console.log("==> notebooklm-mcp-cli==0.9.13 already installed, skipping");
77
+ console.log(" notebooklm-mcp-cli 0.9.13 ... already installed");
72
78
  return;
73
79
  }
74
80
  }
@@ -78,12 +84,15 @@ function installNotebookLM() {
78
84
  "Install uv: https://docs.astral.sh/uv/getting-started/installation/"
79
85
  );
80
86
  }
81
- console.log("==> Installing notebooklm-mcp-cli==0.9.13 via uv");
82
87
  const result = spawnSync("uv", ["tool", "install", "--force", "notebooklm-mcp-cli==0.9.13"], {
83
- stdio: "inherit",
88
+ encoding: "utf-8",
84
89
  });
85
90
  if (result.error) fail(`could not start uv: ${result.error.message}`);
86
- if (result.status !== 0) process.exit(result.status ?? 1);
91
+ if (result.status !== 0) {
92
+ printFailureOutput(result);
93
+ fail("notebooklm-mcp-cli installation failed", result.status ?? 1);
94
+ }
95
+ console.log(" notebooklm-mcp-cli 0.9.13 ... installed");
87
96
  }
88
97
 
89
98
  let piExecutable;
@@ -116,14 +125,17 @@ function resolvePiExecutable() {
116
125
  return piExecutable;
117
126
  }
118
127
 
119
- function installExtension(extension, agentDir) {
120
- console.log(` ${extension}`);
128
+ function installExtension(extension, agentDir, index, total) {
121
129
  const result = spawnSync(resolvePiExecutable(), ["install", extension], {
122
- stdio: "inherit",
130
+ encoding: "utf-8",
123
131
  env: { ...process.env, PI_CODING_AGENT_DIR: agentDir },
124
132
  });
125
133
  if (result.error) fail(`could not start pi: ${result.error.message}`);
126
- if (result.status !== 0) process.exit(result.status ?? 1);
134
+ if (result.status !== 0) {
135
+ printFailureOutput(result);
136
+ fail(`extension installation failed: ${extension}`, result.status ?? 1);
137
+ }
138
+ console.log(` [${index}/${total}] ${extension} ... done`);
127
139
  }
128
140
 
129
141
  function removeStaleExtension(extension, agentDir) {
@@ -148,14 +160,17 @@ function removeStaleExtensions(agentDir) {
148
160
 
149
161
  function installExtensions(agentDir) {
150
162
  const manifest = readExtensionManifest();
151
- console.log("==> Installing required Pi extensions");
152
- for (const extension of manifest.required) installExtension(extension, agentDir);
153
-
154
- if (process.env.PVM_SETUP_SKIP_OPTIONAL === "1") return;
155
- for (const extension of manifest.optional) {
156
- if (extension === "npm:pi-1password" && !commandAvailable("op")) continue;
157
- console.log("==> Installing optional Pi extension");
158
- installExtension(extension, agentDir);
163
+ const extensions = [...manifest.required];
164
+ if (process.env.PVM_SETUP_SKIP_OPTIONAL !== "1") {
165
+ for (const extension of manifest.optional) {
166
+ if (extension === "npm:pi-1password" && !commandAvailable("op")) continue;
167
+ extensions.push(extension);
168
+ }
169
+ }
170
+
171
+ console.log(`==> Pi extensions (${extensions.length})`);
172
+ for (const [index, extension] of extensions.entries()) {
173
+ installExtension(extension, agentDir, index + 1, extensions.length);
159
174
  }
160
175
  }
161
176
 
@@ -163,8 +178,8 @@ function installRuntimeSkills(agentDir) {
163
178
  const sourceRoot = path.join(packageRoot, "runtime", "skills");
164
179
  const destinationRoot = path.join(agentDir, "skills");
165
180
  fs.mkdirSync(destinationRoot, { recursive: true });
166
- console.log("==> Installing curated runtime skills");
167
- for (const skill of runtimeSkills) {
181
+ console.log(`==> Curated skills (${runtimeSkills.length})`);
182
+ for (const [index, skill] of runtimeSkills.entries()) {
168
183
  const source = path.join(sourceRoot, skill);
169
184
  const destination = path.join(destinationRoot, skill);
170
185
  if (!fs.existsSync(path.join(source, "SKILL.md"))) {
@@ -172,8 +187,63 @@ function installRuntimeSkills(agentDir) {
172
187
  }
173
188
  fs.rmSync(destination, { recursive: true, force: true });
174
189
  fs.cpSync(source, destination, { recursive: true });
175
- console.log(` ${skill}`);
190
+ console.log(` [${index + 1}/${runtimeSkills.length}] ${skill} ... done`);
191
+ }
192
+ }
193
+ function isLocalPackagePath(packageSpec) {
194
+ return (
195
+ path.isAbsolute(packageSpec) ||
196
+ packageSpec.startsWith(".") ||
197
+ packageSpec === "~" ||
198
+ packageSpec.startsWith(`~${path.sep}`)
199
+ );
200
+ }
201
+
202
+ function resolvedPackagePath(packageSpec, agentDir) {
203
+ const expanded =
204
+ packageSpec === "~" || packageSpec.startsWith(`~${path.sep}`)
205
+ ? path.join(process.env.HOME ?? "", packageSpec.slice(2))
206
+ : packageSpec;
207
+ const resolved = path.resolve(agentDir, expanded);
208
+ try {
209
+ return fs.realpathSync(resolved);
210
+ } catch {
211
+ return resolved;
212
+ }
213
+ }
214
+
215
+ function isWithinDirectory(candidate, directory) {
216
+ const relative = path.relative(directory, candidate);
217
+ return relative === "" || (!relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
218
+ }
219
+
220
+ function sanitizeAgentSettings(agentDir) {
221
+ const settingsPath = path.join(agentDir, "settings.json");
222
+ if (!fs.existsSync(settingsPath)) return 0;
223
+
224
+ let settings;
225
+ try {
226
+ settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
227
+ } catch (error) {
228
+ fail(`cannot read ${settingsPath}: ${error instanceof Error ? error.message : String(error)}`);
229
+ }
230
+ if (!Array.isArray(settings.packages)) return 0;
231
+
232
+ const canonicalAgentDir = fs.realpathSync(agentDir);
233
+ let removed = 0;
234
+ settings.packages = settings.packages.filter((packageSpec) => {
235
+ if (typeof packageSpec !== "string" || !isLocalPackagePath(packageSpec)) return true;
236
+ const keep = isWithinDirectory(resolvedPackagePath(packageSpec, agentDir), canonicalAgentDir);
237
+ if (!keep) removed += 1;
238
+ return keep;
239
+ });
240
+ if (removed > 0) {
241
+ fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf-8");
242
+ console.log(
243
+ `==> Removed ${removed} external package path${removed === 1 ? "" : "s"} from vault-local settings`
244
+ );
176
245
  }
246
+ return removed;
177
247
  }
178
248
 
179
249
  function scaffoldMainAgentSystem(agentDir, vaultRoot) {
@@ -197,6 +267,7 @@ fs.mkdirSync(agentDir, { recursive: true });
197
267
  console.log(`==> Setting up vault-local Pi runtime at ${agentDir}`);
198
268
  removeStaleExtensions(agentDir);
199
269
  installExtensions(agentDir);
270
+ sanitizeAgentSettings(agentDir);
200
271
  installNotebookLM();
201
272
  installRuntimeSkills(agentDir);
202
273
 
@@ -111,6 +111,28 @@ USE_OP_ENV="false"
111
111
  if [ -f "${VAULT}/.vault-mind/.env.1pass" ] && [ -d "${PI_DIR}/npm/node_modules/pi-1password" ]; then
112
112
  USE_OP_ENV="true"
113
113
  fi
114
+ # Pi discovers user and project skills even when PI_CODING_AGENT_DIR is vault-local.
115
+ # Disable implicit discovery, then load only curated and installed package skills
116
+ # rooted inside this vault's agent directory.
117
+ PI_ARGS=(--no-skills --no-prompt-templates)
118
+ while IFS= read -r skill_file; do
119
+ PI_ARGS+=(--skill "${skill_file}")
120
+ done < <(
121
+ for skills_root in \
122
+ "${PI_DIR}/skills" \
123
+ "${PI_DIR}/npm/node_modules"/*/skills \
124
+ "${PI_DIR}/npm/node_modules"/@*/*/skills; do
125
+ [ -d "${skills_root}" ] || continue
126
+ find "${skills_root}" -mindepth 2 -maxdepth 2 -type f -name SKILL.md
127
+ done | LC_ALL=C sort -u
128
+ )
129
+
130
+ PI_ARGS_Q=""
131
+ for pi_arg in "${PI_ARGS[@]}"; do
132
+ printf -v pi_arg_q '%q' "${pi_arg}"
133
+ PI_ARGS_Q="${PI_ARGS_Q} ${pi_arg_q}"
134
+ done
135
+
114
136
 
115
137
  # Auto mode keeps interactive callers, including Obsidian Terminal, in place.
116
138
  # Non-interactive runners retain the legacy new-window behavior.
@@ -128,9 +150,9 @@ if [ "${MODE}" = "same-terminal" ]; then
128
150
  cd "${VAULT}"
129
151
  echo "[vault-pi] Starting pi for ${VAULT} in the current terminal"
130
152
  if [ "${USE_OP_ENV}" = "true" ]; then
131
- PI_CODING_AGENT_DIR="${PI_DIR}" "${PI_EXECUTABLE}" --op-env .vault-mind/.env.1pass
153
+ PI_CODING_AGENT_DIR="${PI_DIR}" "${PI_EXECUTABLE}" "${PI_ARGS[@]}" --op-env .vault-mind/.env.1pass
132
154
  else
133
- PI_CODING_AGENT_DIR="${PI_DIR}" "${PI_EXECUTABLE}"
155
+ PI_CODING_AGENT_DIR="${PI_DIR}" "${PI_EXECUTABLE}" "${PI_ARGS[@]}"
134
156
  fi
135
157
  exit $?
136
158
  fi
@@ -140,7 +162,7 @@ OP_ENV_ARG=""
140
162
  if [ "${USE_OP_ENV}" = "true" ]; then
141
163
  OP_ENV_ARG="--op-env .vault-mind/.env.1pass"
142
164
  fi
143
- PI_CMD="PI_CODING_AGENT_DIR='${PI_DIR}' ${PI_EXECUTABLE_Q} ${OP_ENV_ARG}; rm -f '${PID_FILE}'"
165
+ PI_CMD="PI_CODING_AGENT_DIR='${PI_DIR}' ${PI_EXECUTABLE_Q}${PI_ARGS_Q} ${OP_ENV_ARG}; rm -f '${PID_FILE}'"
144
166
 
145
167
  if [ -d "/Applications/iTerm.app" ]; then
146
168
  open -a iTerm --args --detach bash -c "echo \$\$ > '${PID_FILE}'; cd '${VAULT}'; ${PI_CMD}"