pi-vault-mind 0.14.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +1 -0
  3. package/dist/src/agent-queue.js +11 -2
  4. package/dist/src/auth.d.ts +10 -10
  5. package/dist/src/auth.js +14 -18
  6. package/dist/src/autosync.js +1 -1
  7. package/dist/src/commands.d.ts +9 -3
  8. package/dist/src/commands.js +179 -44
  9. package/dist/src/discover-schema.js +4 -4
  10. package/dist/src/events.js +6 -9
  11. package/dist/src/index.js +10 -23
  12. package/dist/src/modal-config.d.ts +21 -4
  13. package/dist/src/modal-config.js +28 -7
  14. package/dist/src/model-router.d.ts +27 -0
  15. package/dist/src/model-router.js +67 -0
  16. package/dist/src/personalize.js +17 -3
  17. package/dist/src/server.d.ts +1 -1
  18. package/dist/src/server.js +34 -46
  19. package/dist/src/settings-ui.d.ts +1 -1
  20. package/dist/src/settings-ui.js +24 -30
  21. package/dist/src/sync.d.ts +3 -3
  22. package/dist/src/sync.js +16 -12
  23. package/dist/src/tools.js +3 -3
  24. package/dist/src/types.d.ts +23 -0
  25. package/dist/src/utils.d.ts +66 -5
  26. package/dist/src/utils.js +164 -25
  27. package/dist/src/vault-writer.js +4 -1
  28. package/dist/src/vm-handlers.js +3 -3
  29. package/dist/src/watcher.js +2 -2
  30. package/dist/test/auth.test.js +28 -30
  31. package/dist/test/bridge-identity.test.js +3 -1
  32. package/dist/test/discover-schema.test.js +8 -5
  33. package/dist/test/dispatch.test.js +6 -2
  34. package/dist/test/index.test.js +4 -2
  35. package/dist/test/modal-config.test.js +8 -8
  36. package/dist/test/model-router.test.js +99 -0
  37. package/dist/test/rest-queue.test.js +15 -16
  38. package/dist/test/rest-setup.test.js +12 -14
  39. package/dist/test/rest-vm.test.js +12 -15
  40. package/dist/test/server-ws.test.js +22 -19
  41. package/dist/test/settings-ui.test.js +66 -3
  42. package/dist/test/sync.test.js +10 -11
  43. package/dist/test/tombstone.test.js +7 -9
  44. package/dist/test/vault-tools.test.js +7 -9
  45. package/package.json +97 -90
  46. package/pi-vault-mind.config.example.json +15 -1
  47. package/scripts/check-deps.sh +0 -0
  48. package/scripts/e2e-ws.mjs +3 -2
  49. package/scripts/fetch-modal-token.sh +4 -3
  50. package/scripts/pi-session-query +0 -0
  51. package/scripts/publish-obsidian-plugin.sh +0 -0
  52. package/scripts/reset-test-vault.sh +2 -2
  53. package/scripts/setup-test-config.sh +0 -0
  54. package/scripts/setup-vault-pi.sh +29 -7
  55. package/scripts/vault-pi.sh +12 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,75 @@
1
1
  # Changelog
2
2
 
3
+
4
+ ## 0.15.0 / 0.6.4 — 2026-07-06
5
+
6
+ ### Added
7
+
8
+ - **`/vm setup` now scaffolds `.vault-mind/.pi/model-router.json`** with tuned
9
+ primary/fallback model defaults, extracted verbatim from the Obsidian
10
+ plugin's `scaffoldModelRouterConfig`/`defaultModelRouterChoices` into a new
11
+ `src/model-router.ts` — previously only the plugin's init flow produced
12
+ this file, so a CLI-only setup got no model-router config at all.
13
+ - **`scripts/setup-vault-pi.sh` now installs `pi-model-discovery` and
14
+ `pi-model-router`** alongside `pi-vault-mind`/`pi-context`, matching the
15
+ plugin's install list (previously missing both, so a CLI-only setup never
16
+ got model discovery or routing installed).
17
+
18
+ ### Changed
19
+
20
+ - **Vault isolation** — all pi-vault-mind config, token, and written artifacts now live under `<vault>/.vault-mind/`. The global `~/.pi/agent/vault-mind.config.json` layer is dropped; config is single-layer, vault-local. Legacy config/token is auto-migrated on first session_start (idempotent).
21
+ - **Config consolidation** — `loadConfig` reads only `<vault>/.vault-mind/vault-mind.config.json`; `findConfig` returns a single project path. `getGlobalConfigPath`/`getGlobalConfigDir` removed.
22
+ - **Token env** — `PVM_API_TOKEN` dotenv file moved from `~/.pi/agent/vault-mind.env` to `<vault>/.vault-mind/vault-mind.env`. `auth.ts`, `modal-config.ts`, `commands.ts`, and `settings-ui.ts` all resolve via `getTokenEnvPath(cwd)`.
23
+ - **Framework agent dir** — launch scripts now use `<vault>/.vault-mind/.pi/agent` (relocated from `<vault>/.pi/agent`). `resolveAgentDir(cwd)` honors `PI_CODING_AGENT_DIR` via the SDK.
24
+ - **ACM gate** — auto-ACM now fires only when `isVaultMindReady` is true (config exists + embedding configured), not on any vault with a global config.
25
+ - **Configurable file paths** — `graph.canvasPath`, `files.queue`, `files.serverState`, `files.manualDispatch` in config; `resolveVaultFile` resolves them with defaults under `.vault-mind/`.
26
+ - **Per-vault Modal collection prefix** — `vaults.default.collectionPrefix` (auto-derived from vault slug) isolates server-side Modal tables across vaults sharing a deployment. Local LanceDB tables stay unprefixed.
27
+ - **`/vm setup` consolidation** — now runs `migrateLegacyState` → `setupWizard` → merge-safe scaffold → optional `runPersonalize`. `/vm init` is deprecated (still works, shows notice).
28
+ - **`/vm doctor`** — new command reporting resolved paths, readiness, token source, model-router status, and PASS/WARN for vault isolation.
29
+ - **`GET /vault-mind/paths`** — new auth-gated HTTP endpoint returning absolute + vault-relative paths with `exists` flags.
30
+ - **gitignore** — `.vault-mind/*` + `!.vault-mind/vault-mind.config.json` keeps the hand-tuned config tracked while ignoring local state.
31
+
32
+ ### Fixed
33
+
34
+ - **Config write-target bugs** — `vm_configure`, `discover_schema`, and the
35
+ `POST /vm/init` HTTP handler's scaffold-if-missing path all still wrote to
36
+ the vault-root `pi-vault-mind.config.json` (a path `loadConfig` no longer
37
+ reads) instead of `.vault-mind/vault-mind.config.json`. Fixed to use
38
+ `getConfigPath(cwd)` uniformly.
39
+ - **Stale user/dev-facing config paths** — the "no vaults configured" notify
40
+ string (`commands.ts`), the `discover_schema` tool's prompt guidelines, and
41
+ a `server.ts` doc-comment all still told the reader to edit the retired
42
+ `pi-vault-mind.config.json` instead of `.vault-mind/vault-mind.config.json`.
43
+ - **`/vm setup` CLI-mode arg parsing** — when `--vault` was the first flag
44
+ (exactly the form documented in `CLI_ONLY_WALKTHROUGH.md`), its leading
45
+ `--` was never stripped by the tokenizer, so `cliArgs.vault` was silently
46
+ never set and `vaults.default.path` was left empty. Fixed the split to
47
+ normalize the leading `--` before tokenizing.
48
+
49
+ ### Removed
50
+
51
+ - Dead `events.ts` hidden `/acm` sendMessage path (never enabled compaction).
52
+ - `handleVaultMindConfig` HTTP response no longer splits `.global`/`.project` (single `config` key).
53
+
54
+ ### Plugin (`packages/obsidian/`)
55
+
56
+ - **Isolation parity with the extension** — `resolvePluginAgentDir(vaultPath)`
57
+ now resolves `<vault>/.vault-mind/.pi/agent`, threaded through `main.ts`
58
+ (init flow, config paths, hasExtensions checks, token/1Password flow),
59
+ `model-router-config.ts` (models.json + model-router.json paths),
60
+ `pi-config-reader.ts`, `chat-tab.ts`, `status-tab.ts`, and `panel.ts`.
61
+ - **`.vault-mind/vault-mind.config.json`** — `config.ts`'s `readExtensionConfig`
62
+ now reads the single vault-scoped config (previously read the removed
63
+ global `~/.pi/agent/vault-mind.config.json` + vault-root
64
+ `pi-vault-mind.config.json`).
65
+ - **`.env.1pass` relocated to `.vault-mind/.env.1pass`**, with `--op-env`
66
+ wired into every pi launch site (`PiConnection` in `chat-tab.ts`, the TUI
67
+ launcher in `status-tab.ts`, and `scripts/vault-pi.sh`/`setup-vault-pi.sh`) —
68
+ gated on the file existing AND `pi-1password` being installed, since
69
+ `--op-env` is a flag `pi-1password` registers; passing it unconditionally
70
+ would make `pi` reject the whole invocation with "Unknown option" when the
71
+ extension isn't installed.
72
+
3
73
  ## 0.5.1 — 2026-06-28
4
74
 
5
75
  ### Fixed
package/README.md CHANGED
@@ -375,6 +375,7 @@ Edit `pi-vault-mind.config.json` to match your domain:
375
375
  | [docs/getting-started/NEW_VAULT_WALKTHROUGH.md](docs/getting-started/NEW_VAULT_WALKTHROUGH.md) | **Fresh Mac → new vault → Modal → sync → verified**, one linear path (incl. multi-vault storage isolation) |
376
376
  | [docs/getting-started/INSTALL.md](docs/getting-started/INSTALL.md) | **Canonical install playbook** — all 5 layers (pi ext, skills, Obsidian, config, external CLIs) |
377
377
  | [docs/getting-started/GETTING_STARTED.md](docs/getting-started/GETTING_STARTED.md) | End-to-end workflow + daily "drop & forget" usage |
378
+ | [docs/getting-started/CLI_ONLY_WALKTHROUGH.md](docs/getting-started/CLI_ONLY_WALKTHROUGH.md) | Same setup + daily commands, entirely CLI — no Obsidian plugin required |
378
379
  | [docs/getting-started/WALKTHROUGH_PROMPT.md](docs/getting-started/WALKTHROUGH_PROMPT.md) | Paste-into-pi guided setup with checkpoints between phases |
379
380
 
380
381
  ### Architecture & design
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import * as fs from "node:fs";
14
14
  import * as path from "node:path";
15
+ import { loadConfig, resolveVaultFile } from "./utils.js";
15
16
  const queueSubscribers = new Set();
16
17
  /** Subscribe to queue events. Returns an unsubscribe function. */
17
18
  export const subscribeQueue = (cb) => {
@@ -35,8 +36,16 @@ const eventTypeFor = (status, prev) => {
35
36
  return "job-completed";
36
37
  return "job-updated";
37
38
  };
38
- const queueFileName = ".vault-mind/agent-queue.jsonl";
39
- const queuePath = (vaultPath) => path.join(vaultPath, queueFileName);
39
+ /** Per-vault queue path, resolved from config once and memoized. */
40
+ const queuePathCache = new Map();
41
+ const queuePath = (vaultPath) => {
42
+ const cached = queuePathCache.get(vaultPath);
43
+ if (cached)
44
+ return cached;
45
+ const resolved = path.join(vaultPath, resolveVaultFile(loadConfig(vaultPath).vaultMind, "queue"));
46
+ queuePathCache.set(vaultPath, resolved);
47
+ return resolved;
48
+ };
40
49
  const ensureQueueDir = (vaultPath) => {
41
50
  const dir = path.dirname(queuePath(vaultPath));
42
51
  if (!fs.existsSync(dir)) {
@@ -3,14 +3,14 @@
3
3
  *
4
4
  * Token resolution order:
5
5
  * 1. `process.env.PVM_API_TOKEN`
6
- * 2. `<homedir>/.pi/agent/vault-mind.env` (key=value, # comments)
6
+ * 2. `<vault>/.vault-mind/vault-mind.env` (key=value, # comments)
7
7
  * 3. `vaultMind.embedding.modal.apiToken` in the loaded config
8
8
  *
9
9
  * Returns `null` if no token is configured (auth is off until set).
10
10
  *
11
- * Cross-platform: `os.homedir()` (never `~/`); `chmod 600` on the
12
- * dotenv file is the caller's responsibility and is a no-op on
13
- * win32 (the file system doesn't honor POSIX modes there).
11
+ * The token env path is resolved from the vault cwd via `getTokenEnvPath`;
12
+ * `chmod 600` on the dotenv file is best-effort and a no-op on win32 (the file
13
+ * system doesn't honor POSIX modes there).
14
14
  */
15
15
  import type { UniversalConfig } from "./types.js";
16
16
  /** Resolve the bearer token. `cfg` is optional; pass the loaded config to fall back to it. */
@@ -58,16 +58,16 @@ export declare const setToken: (value: string) => void;
58
58
  export declare const clearToken: () => void;
59
59
  /**
60
60
  * Revoke a role-specific bearer token by removing `PVM_TOKEN_<IDENTITY>`
61
- * from `~/.pi/agent/vault-mind.env`. Preserves all other lines and comments.
62
- * Returns `true` if the variable was found and removed.
61
+ * from `<vault>/.vault-mind/vault-mind.env`. Preserves all other lines and
62
+ * comments. Returns `true` if the variable was found and removed.
63
63
  */
64
64
  export declare const revokeToken: (identity: string) => boolean;
65
65
  /**
66
66
  * Generate a fresh 32-byte base64url token, persist it to
67
- * `<os.homedir()>/.pi/agent/vault-mind.env` (replacing any existing
68
- * `PVM_API_TOKEN` line while preserving all other lines and comments), create
69
- * the directory + file if missing, apply `chmod 600` (best-effort; no-op on
70
- * win32), update the in-process cache, and return the new token.
67
+ * `<vault>/.vault-mind/vault-mind.env` (replacing any existing `PVM_API_TOKEN`
68
+ * line while preserving all other lines and comments), create the directory +
69
+ * file if missing, apply `chmod 600` (best-effort; no-op on win32), update the
70
+ * in-process cache, and return the new token.
71
71
  *
72
72
  * **Env-var precedence note:** if `PVM_API_TOKEN` is exported as an environment
73
73
  * variable it takes precedence over the dotenv file at resolution time, so the
package/dist/src/auth.js CHANGED
@@ -3,28 +3,24 @@
3
3
  *
4
4
  * Token resolution order:
5
5
  * 1. `process.env.PVM_API_TOKEN`
6
- * 2. `<homedir>/.pi/agent/vault-mind.env` (key=value, # comments)
6
+ * 2. `<vault>/.vault-mind/vault-mind.env` (key=value, # comments)
7
7
  * 3. `vaultMind.embedding.modal.apiToken` in the loaded config
8
8
  *
9
9
  * Returns `null` if no token is configured (auth is off until set).
10
10
  *
11
- * Cross-platform: `os.homedir()` (never `~/`); `chmod 600` on the
12
- * dotenv file is the caller's responsibility and is a no-op on
13
- * win32 (the file system doesn't honor POSIX modes there).
11
+ * The token env path is resolved from the vault cwd via `getTokenEnvPath`;
12
+ * `chmod 600` on the dotenv file is best-effort and a no-op on win32 (the file
13
+ * system doesn't honor POSIX modes there).
14
14
  */
15
15
  import * as crypto from "node:crypto";
16
16
  import * as fs from "node:fs";
17
- import * as os from "node:os";
18
17
  import * as path from "node:path";
18
+ import { getTokenEnvPath } from "./utils.js";
19
19
  const ENV_VAR = "PVM_API_TOKEN";
20
- const DOTENV_REL = path.join(".pi", "agent", "vault-mind.env");
21
20
  /** Cached token resolution. The cache is invalidated by `setToken` / `clearToken`. */
22
21
  let cached = null;
23
22
  const readDotenvToken = () => {
24
- const home = os.homedir();
25
- if (!home)
26
- return null;
27
- const p = path.join(home, DOTENV_REL);
23
+ const p = getTokenEnvPath(process.cwd());
28
24
  try {
29
25
  const text = fs.readFileSync(p, "utf-8");
30
26
  for (const line of text.split(/\r?\n/)) {
@@ -149,12 +145,12 @@ export const clearToken = () => {
149
145
  };
150
146
  /**
151
147
  * Revoke a role-specific bearer token by removing `PVM_TOKEN_<IDENTITY>`
152
- * from `~/.pi/agent/vault-mind.env`. Preserves all other lines and comments.
153
- * Returns `true` if the variable was found and removed.
148
+ * from `<vault>/.vault-mind/vault-mind.env`. Preserves all other lines and
149
+ * comments. Returns `true` if the variable was found and removed.
154
150
  */
155
151
  export const revokeToken = (identity) => {
156
152
  const varName = `PVM_TOKEN_${identity.toUpperCase()}`;
157
- const dotenvPath = path.join(os.homedir(), DOTENV_REL);
153
+ const dotenvPath = getTokenEnvPath(process.cwd());
158
154
  let lines = [];
159
155
  try {
160
156
  lines = fs.readFileSync(dotenvPath, "utf-8").split(/\r?\n/);
@@ -183,10 +179,10 @@ export const revokeToken = (identity) => {
183
179
  };
184
180
  /**
185
181
  * Generate a fresh 32-byte base64url token, persist it to
186
- * `<os.homedir()>/.pi/agent/vault-mind.env` (replacing any existing
187
- * `PVM_API_TOKEN` line while preserving all other lines and comments), create
188
- * the directory + file if missing, apply `chmod 600` (best-effort; no-op on
189
- * win32), update the in-process cache, and return the new token.
182
+ * `<vault>/.vault-mind/vault-mind.env` (replacing any existing `PVM_API_TOKEN`
183
+ * line while preserving all other lines and comments), create the directory +
184
+ * file if missing, apply `chmod 600` (best-effort; no-op on win32), update the
185
+ * in-process cache, and return the new token.
190
186
  *
191
187
  * **Env-var precedence note:** if `PVM_API_TOKEN` is exported as an environment
192
188
  * variable it takes precedence over the dotenv file at resolution time, so the
@@ -195,7 +191,7 @@ export const revokeToken = (identity) => {
195
191
  */
196
192
  export const rotateToken = () => {
197
193
  const newToken = crypto.randomBytes(32).toString("base64url");
198
- const dotenvPath = path.join(os.homedir(), DOTENV_REL);
194
+ const dotenvPath = getTokenEnvPath(process.cwd());
199
195
  const dir = path.dirname(dotenvPath);
200
196
  if (!fs.existsSync(dir))
201
197
  fs.mkdirSync(dir, { recursive: true });
@@ -27,7 +27,7 @@ export const startAutoSync = (intervalMs = 300_000) => {
27
27
  return; // nothing to sync
28
28
  const ms = sync.autoSyncIntervalMs ?? intervalMs;
29
29
  void ms; // cadence is fixed at start; tick honors config toggle
30
- await syncAll(cfg);
30
+ await syncAll(cfg, process.cwd());
31
31
  console.log("[pi-vault-mind] auto-sync tick complete");
32
32
  }
33
33
  catch (err) {
@@ -5,8 +5,10 @@ export declare const selectActiveCollection: (ctx: ExtensionContext) => Promise<
5
5
  /**
6
6
  * Vault-root `.gitignore` entries that keep pi-vault-mind compatible with
7
7
  * `obsidian-git` (and, transitively, Obsidian Sync setups that also back up via
8
- * git): the LanceDB index is a large, per-device, rebuildable binary and must
9
- * never be committed; Obsidian's workspace UI state churns constantly. See
8
+ * git). The `.vault-mind/` directory holds local state (token, index, framework
9
+ * dir) that must never be committed, EXCEPT the hand-tuned config file, which is
10
+ * re-included via a `!` negation. The LanceDB index is a large, per-device,
11
+ * rebuildable binary; Obsidian's workspace UI state churns constantly. See
10
12
  * docs/OBSIDIAN_SETUP.md §6.
11
13
  */
12
14
  export declare const GITIGNORE_ENTRIES: string[];
@@ -15,9 +17,13 @@ export declare const GITIGNORE_ENTRIES: string[];
15
17
  * (`null` if absent). Pure — does no I/O — so it is unit-testable. Creates the
16
18
  * file when missing, appends only the entries that aren't already present
17
19
  * (line-exact match, trimmed), and skips when everything is covered.
20
+ *
21
+ * A bare `.vault-mind/` directory exclude would block the per-file config
22
+ * negation (git cannot re-include a file inside an ignored directory), so when
23
+ * one is present it is rewritten to `.vault-mind/*` and the whole file returned.
18
24
  */
19
25
  export declare const planGitignore: (existing: string | null) => {
20
- action: "create" | "append" | "skip";
26
+ action: "create" | "append" | "rewrite" | "skip";
21
27
  content: string;
22
28
  };
23
29
  export declare const watcherState: import("./watcher.js").WatcherState;