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 +11 -0
- package/README.md +21 -26
- package/dist/src/extension-packages.d.ts +4 -4
- package/dist/src/extension-packages.js +4 -4
- package/dist/test/pvm-setup.test.js +33 -0
- package/dist/test/vault-pi-script.test.js +69 -3
- package/package.json +1 -1
- package/scripts/pvm-setup.mjs +90 -19
- package/scripts/vault-pi.sh +25 -3
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
|
-
###
|
|
126
|
+
### Curated vault-local skills
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
`pvm setup` installs the supported Obsidian and NotebookLM skills into the
|
|
129
|
+
vault-local Pi agent directory:
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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\
|
|
26
|
-
"$PWD" "$PI_CODING_AGENT_DIR" "
|
|
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
|
-
|
|
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.
|
|
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",
|
package/scripts/pvm-setup.mjs
CHANGED
|
@@ -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("
|
|
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
|
-
|
|
88
|
+
encoding: "utf-8",
|
|
84
89
|
});
|
|
85
90
|
if (result.error) fail(`could not start uv: ${result.error.message}`);
|
|
86
|
-
if (result.status !== 0)
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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(
|
|
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
|
|
package/scripts/vault-pi.sh
CHANGED
|
@@ -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}"
|