pi-vault-mind 0.16.29 → 0.16.30

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,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.30 — 2026-08-27
4
+
5
+ ### Added
6
+
7
+ - **First-class `pvm` executable.** The npm package now exposes `pvm run [vault-path]`; it uses the current directory when the vault path is omitted and supports both global installation and `npx pi-vault-mind run`.
8
+ - **Explicit launch modes.** `pvm run` stays in the current terminal by default, with `--same-terminal`, `--new-window`, and `--auto` overrides.
9
+
10
+ ### Fixed
11
+
12
+ - **Portable vault launcher.** `vault-pi.sh` now handles interactive terminals directly, normalizes relative vault paths before deriving runtime state, removes inline-session PID files on exit, and rejects unknown flags instead of treating them as vault paths.
13
+
14
+ ### Verification
15
+
16
+ - Root and Obsidian builds passed; full test suite: 910/910 passing.
17
+ - `npm pack` includes `scripts/pvm.mjs` as an executable, and `npm exec` resolved the packed tarball's `pvm` command successfully.
18
+
3
19
  ## 0.16.29 — 2026-08-27
4
20
 
5
21
  ### Fixed
package/README.md CHANGED
@@ -194,10 +194,25 @@ The Broadcaster agent can generate podcasts, study guides, and slide decks from
194
194
 
195
195
  ```bash
196
196
  pi install npm:pi-vault-mind # latest
197
- pi install npm:pi-vault-mind@0.7.0 # pinned
197
+ pi install npm:pi-vault-mind@0.16.30 # pinned release
198
198
  pi -e npm:pi-vault-mind # try without installing
199
199
  ```
200
200
 
201
+ `pi install` loads the extension but does not expose its npm executable on your
202
+ shell `PATH`. Install the first-class launcher once when you want the bare
203
+ `pvm` command:
204
+
205
+ ```bash
206
+ npm install --global pi-vault-mind
207
+ pvm run /path/to/your/vault
208
+ ```
209
+
210
+ Or run it without a global installation:
211
+
212
+ ```bash
213
+ npx --yes pi-vault-mind run /path/to/your/vault
214
+ ```
215
+
201
216
  Or from git:
202
217
 
203
218
  ```bash
@@ -269,6 +284,174 @@ Edit `<vault>/.vault-mind/vault-mind.config.json` to match your domain:
269
284
  }
270
285
  ```
271
286
 
287
+ ## Using Vault Mind agent identities
288
+
289
+ Vault Mind keeps the interactive Pi session on the unrestricted `main` persona
290
+ unless you explicitly select a specialist boundary. Bundled specialist skills
291
+ are available resources; merely loading them does not change the active
292
+ persona.
293
+
294
+ ### Start the vault-local main agent
295
+
296
+ From the vault directory, start Pi in the current terminal:
297
+
298
+ ```bash
299
+ pvm run
300
+ ```
301
+
302
+ You can launch a different vault from anywhere or use the package without a
303
+ global install:
304
+
305
+ ```bash
306
+ pvm run /path/to/your/vault
307
+ npx --yes pi-vault-mind run /path/to/your/vault
308
+ ```
309
+
310
+ `pvm run` defaults to the current terminal. This is the recommended Obsidian
311
+ Terminal profile command. Use a vault-path placeholder supplied by the plugin,
312
+ or provide the vault's absolute path. Override the launch mode when needed:
313
+
314
+ ```bash
315
+ pvm run --same-terminal /path/to/your/vault
316
+ pvm run --new-window /path/to/your/vault
317
+ pvm run --auto /path/to/your/vault
318
+ ```
319
+
320
+ `--new-window` opens a dedicated Terminal/iTerm window. `--auto` uses the
321
+ current terminal when attached to a TTY and falls back to a new window
322
+ otherwise. The packaged shell launcher remains available for direct use as
323
+ `scripts/vault-pi.sh`.
324
+
325
+ In every mode, the launcher runs Pi inside the target vault with its
326
+ configuration isolated under `<vault>/.vault-mind/.pi/agent/`. It also prevents
327
+ two launcher-managed Pi processes from running for the same vault and removes
328
+ the PID file when an inline session exits.
329
+
330
+ Check the active persona after startup:
331
+
332
+ ```text
333
+ /vm identity status
334
+ ```
335
+
336
+ A normal main-agent session reports:
337
+
338
+ ```text
339
+ Persona: main
340
+ Effective boundary: none (main agent)
341
+ ```
342
+
343
+ `Injection` may report `enabled` or `disabled`. An enabled injector still
344
+ applies no boundary while the persona is `main`.
345
+
346
+ ### Use specialist agents without changing the main session
347
+
348
+ Normally, leave the interactive session on `main` and dispatch specialist work
349
+ through the passive watcher:
350
+
351
+ ```text
352
+ @agent-Miner
353
+ Research and extract the durable facts from this note.
354
+ ```
355
+
356
+ The watcher runs that work as an isolated specialist job. It does not turn the
357
+ interactive terminal into the Miner persona. The same distinction applies to
358
+ `@agent-Broadcaster`, `@agent-Manager`, and `@agent-Heavy-Lifter`.
359
+
360
+ ### Explicitly change the interactive persona
361
+
362
+ Use an identity boundary only when the current interactive session itself
363
+ should follow a specialist capability contract:
364
+
365
+ ```text
366
+ /vm identity set miner
367
+ /vm identity set broadcaster
368
+ /vm identity set manager
369
+ /vm identity set heavy-lifter
370
+ ```
371
+
372
+ | Persona | Intended interactive boundary |
373
+ |---|---|
374
+ | `main` | Unrestricted primary agent with the full configured toolset |
375
+ | `manager` | Orchestrates collections, publishing, and subagents; does not run Bash |
376
+ | `miner` | Extracts and stores knowledge; does not run Bash or publish |
377
+ | `broadcaster` | Produces presentation artifacts; does not run Bash or write durable knowledge |
378
+ | `heavy-lifter` | Performs file and Bash work in isolation; does not write durable knowledge or publish |
379
+
380
+ `/vm identity set <role>` persists the selected persona, enables boundary
381
+ injection, and applies on the next agent turn.
382
+
383
+ Use the remaining controls to inspect or suspend that selection:
384
+
385
+ ```text
386
+ /vm identity status
387
+ /vm identity off
388
+ /vm identity on
389
+ ```
390
+
391
+ - `off` disables boundary injection without discarding the selected persona.
392
+ - `on` re-enables the configured persona boundary on the next agent turn.
393
+ - `status` shows the configured persona, injection switch, effective boundary,
394
+ and any environment overrides.
395
+
396
+ Restore the safe main-agent default with:
397
+
398
+ ```text
399
+ /vm identity set main
400
+ /vm identity off
401
+ /vm identity status
402
+ ```
403
+
404
+ The final status should show `Effective boundary: none (main agent)`.
405
+
406
+ ### Configuration and environment overrides
407
+
408
+ Identity controls are stored in the vault-local configuration:
409
+
410
+ ```json
411
+ {
412
+ "vaultMind": {
413
+ "identities": {
414
+ "activeRole": "main",
415
+ "injectBoundaries": false
416
+ }
417
+ }
418
+ }
419
+ ```
420
+
421
+ The canonical path is
422
+ `<vault>/.vault-mind/vault-mind.config.json` under
423
+ `vaultMind.identities`. Identity commands preserve any existing
424
+ `vaultMind.identities.roles` allowlists.
425
+
426
+ Launch automation and explicit specialist forks can override the saved values
427
+ for one process:
428
+
429
+ ```bash
430
+ VAULT_MIND_AGENT_ROLE=miner VAULT_MIND_IDENTITY_INJECTION=on pi
431
+ VAULT_MIND_IDENTITY_INJECTION=off pi
432
+ ```
433
+
434
+ `VAULT_MIND_AGENT_ROLE` takes precedence over `activeRole`.
435
+ `VAULT_MIND_IDENTITY_INJECTION=off` suppresses every boundary, while `on`
436
+ enables the explicitly selected role. Missing, `main`, `none`, and unknown
437
+ roles all resolve safely to the unrestricted main agent.
438
+
439
+ ### Troubleshoot an unexpected specialist boundary
440
+
441
+ 1. Run `/vm identity status` and check both the configured persona and any
442
+ reported environment overrides.
443
+ 2. If an environment override is present, exit Pi, unset
444
+ `VAULT_MIND_AGENT_ROLE` and `VAULT_MIND_IDENTITY_INJECTION`, then relaunch.
445
+ 3. Run `/vm identity set main`, followed by `/vm identity off`.
446
+ 4. Send the next agent turn and re-run `/vm identity status`; boundary changes
447
+ take effect on the next turn.
448
+ 5. Confirm Pi was started for the intended vault and uses
449
+ `<vault>/.vault-mind/.pi/agent/` as `PI_CODING_AGENT_DIR`.
450
+
451
+ Do not edit `SYSTEM.md` or an agent skill to change the interactive identity.
452
+ Those files provide operating instructions and resources; persona selection
453
+ belongs in `vaultMind.identities` or an explicit environment override.
454
+
272
455
  ## Example domains
273
456
 
274
457
  ### Research project
@@ -0,0 +1,88 @@
1
+ import { strict as assert } from "node:assert";
2
+ import { spawnSync } from "node:child_process";
3
+ import * as fs from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import * as path from "node:path";
6
+ import { afterEach, beforeEach, describe, it } from "node:test";
7
+ import { fileURLToPath } from "node:url";
8
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
9
+ const pvmCli = path.join(repoRoot, "scripts", "pvm.mjs");
10
+ const vaultLauncher = path.join(repoRoot, "scripts", "vault-pi.sh");
11
+ describe("pvm CLI", () => {
12
+ let testDir;
13
+ let vaultPath;
14
+ let binDir;
15
+ let outputPath;
16
+ beforeEach(() => {
17
+ testDir = path.join(tmpdir(), `pvm-cli-${process.pid}-${Date.now()}`);
18
+ vaultPath = path.join(testDir, "Test Vault");
19
+ binDir = path.join(testDir, "bin");
20
+ outputPath = path.join(testDir, "bash-args.txt");
21
+ fs.mkdirSync(vaultPath, { recursive: true });
22
+ fs.mkdirSync(binDir, { recursive: true });
23
+ fs.writeFileSync(path.join(binDir, "bash"), `#!/bin/bash
24
+ set -euo pipefail
25
+ printf '%s\n' "$@" > "$PVM_TEST_OUTPUT"
26
+ exit "\${PVM_TEST_BASH_EXIT:-0}"
27
+ `, { mode: 0o755 });
28
+ });
29
+ afterEach(() => {
30
+ fs.rmSync(testDir, { recursive: true, force: true });
31
+ });
32
+ function runCli(args, cwd = vaultPath, env = {}) {
33
+ return spawnSync(process.execPath, [pvmCli, ...args], {
34
+ cwd,
35
+ encoding: "utf-8",
36
+ env: {
37
+ ...process.env,
38
+ PATH: `${binDir}:${process.env.PATH ?? ""}`,
39
+ PVM_TEST_OUTPUT: outputPath,
40
+ ...env,
41
+ },
42
+ });
43
+ }
44
+ function capturedBashArgs() {
45
+ return fs.readFileSync(outputPath, "utf-8").trimEnd().split("\n");
46
+ }
47
+ it("runs the current vault in the same terminal by default", () => {
48
+ const result = runCli(["run"]);
49
+ assert.equal(result.status, 0, result.stderr || result.stdout);
50
+ assert.deepEqual(capturedBashArgs(), [
51
+ vaultLauncher,
52
+ "--same-terminal",
53
+ fs.realpathSync(vaultPath),
54
+ ]);
55
+ });
56
+ it("passes an explicit vault and launch mode to the packaged launcher", () => {
57
+ const result = runCli(["run", "--new-window", vaultPath], testDir);
58
+ assert.equal(result.status, 0, result.stderr || result.stdout);
59
+ assert.deepEqual(capturedBashArgs(), [vaultLauncher, "--new-window", vaultPath]);
60
+ });
61
+ it("preserves the launcher's automatic TTY mode when requested", () => {
62
+ const result = runCli(["run", vaultPath, "--auto"], testDir);
63
+ assert.equal(result.status, 0, result.stderr || result.stdout);
64
+ assert.deepEqual(capturedBashArgs(), [vaultLauncher, vaultPath]);
65
+ });
66
+ it("propagates the packaged launcher's exit status", () => {
67
+ const result = runCli(["run"], vaultPath, { PVM_TEST_BASH_EXIT: "23" });
68
+ assert.equal(result.status, 23);
69
+ });
70
+ it("reports when the shell launcher cannot be started", () => {
71
+ const emptyBinDir = path.join(testDir, "empty-bin");
72
+ fs.mkdirSync(emptyBinDir);
73
+ const result = runCli(["run"], vaultPath, { PATH: emptyBinDir });
74
+ assert.equal(result.status, 1);
75
+ assert.match(result.stderr, /pvm: failed to start launcher:/);
76
+ });
77
+ it("rejects unknown commands with usage help", () => {
78
+ const result = runCli(["launch"]);
79
+ assert.equal(result.status, 2);
80
+ assert.match(result.stderr, /Usage: pvm run/);
81
+ assert.equal(fs.existsSync(outputPath), false);
82
+ });
83
+ it("publishes pvm as an executable package bin", () => {
84
+ const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, "package.json"), "utf-8"));
85
+ assert.equal(packageJson.bin?.pvm, "scripts/pvm.mjs");
86
+ assert.notEqual(fs.statSync(pvmCli).mode & 0o111, 0);
87
+ });
88
+ });
@@ -0,0 +1,101 @@
1
+ import { strict as assert } from "node:assert";
2
+ import { spawnSync } from "node:child_process";
3
+ import * as fs from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import * as path from "node:path";
6
+ import { afterEach, beforeEach, describe, it } from "node:test";
7
+ import { fileURLToPath } from "node:url";
8
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
9
+ const launcher = path.join(repoRoot, "scripts", "vault-pi.sh");
10
+ describe("vault-pi launcher", () => {
11
+ let testDir;
12
+ let vaultPath;
13
+ let binDir;
14
+ let outputPath;
15
+ beforeEach(() => {
16
+ testDir = path.join(tmpdir(), `pvm-vault-pi-${process.pid}-${Date.now()}`);
17
+ vaultPath = path.join(testDir, "Test Vault");
18
+ binDir = path.join(testDir, "bin");
19
+ outputPath = path.join(testDir, "pi-output.txt");
20
+ fs.mkdirSync(vaultPath, { recursive: true });
21
+ fs.mkdirSync(binDir, { recursive: true });
22
+ fs.writeFileSync(path.join(binDir, "pi"), `#!/usr/bin/env bash
23
+ set -euo pipefail
24
+ 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"
27
+ `, { mode: 0o755 });
28
+ for (const command of ["open", "osascript"]) {
29
+ fs.writeFileSync(path.join(binDir, command), "#!/usr/bin/env bash\nexit 97\n", {
30
+ mode: 0o755,
31
+ });
32
+ }
33
+ });
34
+ afterEach(() => {
35
+ fs.rmSync(testDir, { recursive: true, force: true });
36
+ });
37
+ it("runs Pi in the current terminal when same-terminal mode is requested", () => {
38
+ const result = spawnSync("bash", [launcher, "--same-terminal", vaultPath], {
39
+ cwd: testDir,
40
+ encoding: "utf-8",
41
+ env: {
42
+ ...process.env,
43
+ PATH: `${binDir}:${process.env.PATH ?? ""}`,
44
+ PVM_TEST_OUTPUT: outputPath,
45
+ },
46
+ });
47
+ assert.equal(result.status, 0, result.stderr || result.stdout);
48
+ const output = fs.readFileSync(outputPath, "utf-8");
49
+ assert.match(output, new RegExp(`^cwd=${vaultPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m"));
50
+ assert.match(output, new RegExp(`^agent=${path.join(vaultPath, ".vault-mind", ".pi", "agent").replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m"));
51
+ assert.match(output, /^args=$/m);
52
+ assert.match(output, /^pid=\d+$/m);
53
+ assert.equal(fs.existsSync(path.join(vaultPath, ".vault-mind", ".pi", "agent", "daemon.pid")), false);
54
+ });
55
+ it("normalizes a relative vault path before changing directories", () => {
56
+ const relativeVaultPath = path.relative(testDir, vaultPath);
57
+ const result = spawnSync("bash", [launcher, "--same-terminal", relativeVaultPath], {
58
+ cwd: testDir,
59
+ encoding: "utf-8",
60
+ env: {
61
+ ...process.env,
62
+ PATH: `${binDir}:${process.env.PATH ?? ""}`,
63
+ PVM_TEST_OUTPUT: outputPath,
64
+ },
65
+ });
66
+ assert.equal(result.status, 0, result.stderr || result.stdout);
67
+ const output = fs.readFileSync(outputPath, "utf-8");
68
+ const cwd = output.match(/^cwd=(.*)$/m)?.[1];
69
+ const agentDir = output.match(/^agent=(.*)$/m)?.[1];
70
+ assert.ok(cwd, "fake Pi did not report its working directory");
71
+ assert.equal(agentDir, path.join(cwd, ".vault-mind", ".pi", "agent"));
72
+ assert.equal(fs.existsSync(path.join(vaultPath, ".vault-mind", ".pi", "agent", "daemon.pid")), false);
73
+ });
74
+ it("falls back to a new window for non-interactive automatic launches", () => {
75
+ const result = spawnSync("bash", [launcher, vaultPath], {
76
+ cwd: testDir,
77
+ encoding: "utf-8",
78
+ env: {
79
+ ...process.env,
80
+ PATH: `${binDir}:${process.env.PATH ?? ""}`,
81
+ PVM_TEST_OUTPUT: outputPath,
82
+ },
83
+ });
84
+ assert.equal(result.status, 97);
85
+ assert.equal(fs.existsSync(outputPath), false);
86
+ });
87
+ it("rejects an unknown launch flag with usage help", () => {
88
+ const result = spawnSync("bash", [launcher, "--new-widnow"], {
89
+ cwd: testDir,
90
+ encoding: "utf-8",
91
+ env: {
92
+ ...process.env,
93
+ PATH: `${binDir}:${process.env.PATH ?? ""}`,
94
+ PVM_TEST_OUTPUT: outputPath,
95
+ },
96
+ });
97
+ assert.equal(result.status, 2);
98
+ assert.match(result.stderr, /Usage: vault-pi\.sh/);
99
+ assert.equal(fs.existsSync(path.join(testDir, "--new-widnow")), false);
100
+ });
101
+ });
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "pi-vault-mind",
3
- "version": "0.16.29",
3
+ "version": "0.16.30",
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",
7
7
  "type": "module",
8
+ "bin": {
9
+ "pvm": "scripts/pvm.mjs"
10
+ },
8
11
  "files": [
9
12
  "dist",
10
13
  "agents",
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const USAGE = `Usage: pvm run [--same-terminal | --new-window | --auto] [vault-path]
7
+
8
+ Commands:
9
+ run Start Pi with the vault-local Vault Mind agent environment.
10
+
11
+ Run options:
12
+ --same-terminal Run Pi in the current terminal (default).
13
+ --new-window Open Pi in a new Terminal/iTerm window.
14
+ --auto Use the current terminal when attached to a TTY; otherwise open a new window.
15
+
16
+ When vault-path is omitted, pvm uses the current working directory.`;
17
+
18
+ function fail(message) {
19
+ if (message) console.error(`pvm: ${message}\n`);
20
+ console.error(USAGE);
21
+ process.exitCode = 2;
22
+ }
23
+
24
+ const [command, ...args] = process.argv.slice(2);
25
+
26
+ if (!command || command === "-h" || command === "--help" || command === "help") {
27
+ console.log(USAGE);
28
+ } else if (command !== "run") {
29
+ fail(`unknown command: ${command}`);
30
+ } else {
31
+ let mode = "same-terminal";
32
+ let vaultPath;
33
+ let invalid = false;
34
+
35
+ for (const arg of args) {
36
+ if (arg === "-h" || arg === "--help") {
37
+ console.log(USAGE);
38
+ process.exit(0);
39
+ }
40
+ if (arg === "--same-terminal") {
41
+ mode = "same-terminal";
42
+ continue;
43
+ }
44
+ if (arg === "--new-window") {
45
+ mode = "new-window";
46
+ continue;
47
+ }
48
+ if (arg === "--auto") {
49
+ mode = "auto";
50
+ continue;
51
+ }
52
+ if (arg.startsWith("-")) {
53
+ fail(`unknown option: ${arg}`);
54
+ invalid = true;
55
+ break;
56
+ }
57
+ if (vaultPath !== undefined) {
58
+ fail("run accepts at most one vault path");
59
+ invalid = true;
60
+ break;
61
+ }
62
+ vaultPath = arg;
63
+ }
64
+
65
+ if (!invalid) {
66
+ const launcher = fileURLToPath(new URL("./vault-pi.sh", import.meta.url));
67
+ const launchMode = mode === "auto" ? [] : [`--${mode}`];
68
+ const result = spawnSync("bash", [launcher, ...launchMode, vaultPath ?? process.cwd()], {
69
+ stdio: "inherit",
70
+ env: process.env,
71
+ });
72
+
73
+ if (result.error) {
74
+ console.error(`pvm: failed to start launcher: ${result.error.message}`);
75
+ process.exitCode = 1;
76
+ } else {
77
+ process.exitCode = result.status ?? 1;
78
+ }
79
+ }
80
+ }
@@ -2,20 +2,59 @@
2
2
  # vault-pi.sh — Start pi for a vault, ensuring only one instance runs.
3
3
  #
4
4
  # Usage:
5
- # bash scripts/vault-pi.sh /path/to/vault
5
+ # bash scripts/vault-pi.sh [--same-terminal | --new-window] /path/to/vault
6
6
  #
7
- # Obsidian Shell Commands integration:
8
- # Command: bash /path/to/pi-vault-mind/scripts/vault-pi.sh {{vault_path}}
7
+ # Obsidian Terminal integration:
8
+ # bash /path/to/pi-vault-mind/scripts/vault-pi.sh {{vault_path}}
9
9
  #
10
10
  # The script:
11
11
  # 1. Checks for an existing PID file at <vault>/.vault-mind/.pi/agent/daemon.pid
12
12
  # 2. If the PID is alive → exits silently (already running)
13
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
14
+ # 4. Runs in the current terminal when attached to a TTY
15
+ # 5. Falls back to a new Terminal/iTerm window for non-interactive callers
15
16
 
16
17
  set -euo pipefail
17
18
 
18
- VAULT="${1:?Usage: vault-pi.sh <vault-path>}"
19
+ usage() {
20
+ cat <<'EOF'
21
+ Usage: vault-pi.sh [--same-terminal | --new-window] <vault-path>
22
+
23
+ Launch modes:
24
+ auto Use the current terminal when attached to a TTY; otherwise open a new window.
25
+ --same-terminal Force Pi to run in the current terminal.
26
+ --new-window Force Pi to open in a new Terminal/iTerm window.
27
+ EOF
28
+ }
29
+
30
+ MODE="auto"
31
+ case "${1:-}" in
32
+ --same-terminal)
33
+ MODE="same-terminal"
34
+ shift
35
+ ;;
36
+ --new-window)
37
+ MODE="new-window"
38
+ shift
39
+ ;;
40
+ -h | --help)
41
+ usage
42
+ exit 0
43
+ ;;
44
+ -*)
45
+ usage >&2
46
+ exit 2
47
+ ;;
48
+ esac
49
+
50
+ if [ "$#" -ne 1 ]; then
51
+ usage >&2
52
+ exit 2
53
+ fi
54
+
55
+ VAULT="$1"
56
+ mkdir -p "${VAULT}"
57
+ VAULT="$(cd "${VAULT}" && pwd)"
19
58
  PI_DIR="${VAULT}/.vault-mind/.pi/agent"
20
59
  PID_FILE="${PI_DIR}/daemon.pid"
21
60
 
@@ -37,14 +76,39 @@ fi
37
76
  # extension isn't installed makes pi reject the whole invocation with
38
77
  # "Unknown option: --op-env" — only pass it when both the env file exists
39
78
  # AND pi-1password is actually installed in the vault-local agent dir.
40
- OP_ENV_ARG=""
79
+ USE_OP_ENV="false"
41
80
  if [ -f "${VAULT}/.vault-mind/.env.1pass" ] && [ -d "${PI_DIR}/npm/node_modules/pi-1password" ]; then
42
- OP_ENV_ARG="--op-env .vault-mind/.env.1pass"
81
+ USE_OP_ENV="true"
43
82
  fi
44
83
 
45
- # Start pi in a new terminal window.
46
- # macOS: open -a Terminal or open -a iTerm
47
- # The trap writes the PID and cleans up on exit.
84
+ # Auto mode keeps interactive callers, including Obsidian Terminal, in place.
85
+ # Non-interactive runners retain the legacy new-window behavior.
86
+ if [ "${MODE}" = "auto" ]; then
87
+ if [ -t 0 ] && [ -t 1 ]; then
88
+ MODE="same-terminal"
89
+ else
90
+ MODE="new-window"
91
+ fi
92
+ fi
93
+
94
+ if [ "${MODE}" = "same-terminal" ]; then
95
+ echo "$$" > "${PID_FILE}"
96
+ trap 'rm -f "${PID_FILE}"' EXIT
97
+ cd "${VAULT}"
98
+ echo "[vault-pi] Starting pi for ${VAULT} in the current terminal"
99
+ if [ "${USE_OP_ENV}" = "true" ]; then
100
+ PI_CODING_AGENT_DIR="${PI_DIR}" pi --op-env .vault-mind/.env.1pass
101
+ else
102
+ PI_CODING_AGENT_DIR="${PI_DIR}" pi
103
+ fi
104
+ exit $?
105
+ fi
106
+
107
+ # New-window mode is retained for non-interactive launchers and explicit use.
108
+ OP_ENV_ARG=""
109
+ if [ "${USE_OP_ENV}" = "true" ]; then
110
+ OP_ENV_ARG="--op-env .vault-mind/.env.1pass"
111
+ fi
48
112
  PI_CMD="PI_CODING_AGENT_DIR='${PI_DIR}' pi ${OP_ENV_ARG}; rm -f '${PID_FILE}'"
49
113
 
50
114
  if [ -d "/Applications/iTerm.app" ]; then
@@ -52,8 +116,8 @@ if [ -d "/Applications/iTerm.app" ]; then
52
116
  elif [ -d "/Applications/Utilities/Terminal.app" ] || [ -d "/System/Applications/Utilities/Terminal.app" ]; then
53
117
  osascript -e "tell application \"Terminal\" to do script \"echo \$\$ > '${PID_FILE}'; cd '${VAULT}'; ${PI_CMD}\""
54
118
  else
55
- echo "[vault-pi] No supported terminal found (iTerm or Terminal.app)"
119
+ echo "[vault-pi] No supported terminal found (iTerm or Terminal.app)" >&2
56
120
  exit 1
57
121
  fi
58
122
 
59
- echo "[vault-pi] Started pi for ${VAULT}"
123
+ echo "[vault-pi] Started pi for ${VAULT} in a new terminal window"