pi-vault-mind 0.8.5 → 0.8.8
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 +3 -3
- package/dist/src/commands.js +26 -23
- package/dist/src/events.js +2 -6
- package/dist/src/server.d.ts +2 -2
- package/dist/src/server.js +2 -2
- package/dist/src/settings-ui.d.ts +9 -0
- package/dist/src/settings-ui.js +311 -35
- package/dist/src/types.js +1 -2
- package/dist/src/utils.js +2 -2
- package/dist/test/index.test.js +1 -1
- package/dist/test/settings-ui.test.js +91 -0
- package/package.json +1 -1
- package/scripts/setup-vault-pi.sh +32 -0
- package/scripts/vault-pi.sh +50 -0
- package/skills/vault-mind/SKILL.md +5 -5
- package/skills/vault-mind-setup/references/obsidian-cli-and-plugins.md +1 -1
- package/skills/vault-mind-setup/references/obsidian-vault-structure.md +1 -1
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=
|
|
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
|
-
| **
|
|
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
|
|
package/dist/src/commands.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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.
|
|
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:
|
package/dist/src/events.js
CHANGED
|
@@ -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
|
|
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) {
|
package/dist/src/server.d.ts
CHANGED
|
@@ -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
|
|
15
|
-
*
|
|
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,
|
package/dist/src/server.js
CHANGED
|
@@ -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
|
|
15
|
-
*
|
|
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,13 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
export interface ObsidianPluginRequirement {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
required: boolean;
|
|
6
|
+
reason: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const getMissingOnboardingPlugins: (vaultPath: string) => ObsidianPluginRequirement[];
|
|
9
|
+
export declare const buildPluginInstallGuidance: (plugins: ObsidianPluginRequirement[]) => string;
|
|
10
|
+
export declare const detectVaultFromCwd: (cwd: string) => string | null;
|
|
2
11
|
export declare const createCollectionWizard: (ctx: ExtensionContext) => Promise<void>;
|
|
3
12
|
export declare const createInjectorWizard: (ctx: ExtensionContext) => Promise<void>;
|
|
4
13
|
export declare const setupWizard: (ctx: ExtensionContext, cliArgs?: {
|
package/dist/src/settings-ui.js
CHANGED
|
@@ -1,7 +1,149 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { MODAL_TOKEN_ENV, createModalClient, modalUrl, resolveModalToken } from "./modal-config.js";
|
|
4
5
|
import { collectionNames, findConfig, getGlobalConfigPath, loadConfig, shrinkHome, } from "./utils.js";
|
|
6
|
+
const ONBOARDING_PLUGIN_REQUIREMENTS = [
|
|
7
|
+
{
|
|
8
|
+
id: "obsidian-pi-vault-mind",
|
|
9
|
+
label: "Vault Mind plugin",
|
|
10
|
+
required: true,
|
|
11
|
+
reason: "native setup/status/chat UI and local bridge controls",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: "actions-uri",
|
|
15
|
+
label: "Actions URI",
|
|
16
|
+
required: false,
|
|
17
|
+
reason: "deep-link automation and capture shortcuts",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "obsidian-git",
|
|
21
|
+
label: "Obsidian Git",
|
|
22
|
+
required: false,
|
|
23
|
+
reason: "vault backup and change history safety net",
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
const readEnabledCommunityPlugins = (vaultPath) => {
|
|
27
|
+
const communityPluginsPath = path.join(vaultPath, ".obsidian", "community-plugins.json");
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(fs.readFileSync(communityPluginsPath, "utf-8"));
|
|
30
|
+
if (!Array.isArray(parsed))
|
|
31
|
+
return new Set();
|
|
32
|
+
return new Set(parsed.filter((value) => typeof value === "string"));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return new Set();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
export const getMissingOnboardingPlugins = (vaultPath) => {
|
|
39
|
+
const enabled = readEnabledCommunityPlugins(vaultPath);
|
|
40
|
+
return ONBOARDING_PLUGIN_REQUIREMENTS.filter((plugin) => !enabled.has(plugin.id));
|
|
41
|
+
};
|
|
42
|
+
const hasObsidianCli = () => {
|
|
43
|
+
try {
|
|
44
|
+
execFileSync("obsidian", ["help"], {
|
|
45
|
+
encoding: "utf-8",
|
|
46
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
47
|
+
});
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const formatExecError = (err) => {
|
|
55
|
+
if (err && typeof err === "object" && "stderr" in err) {
|
|
56
|
+
const stderr = err.stderr;
|
|
57
|
+
if (typeof stderr === "string" && stderr.trim())
|
|
58
|
+
return stderr.trim();
|
|
59
|
+
if (stderr instanceof Buffer && stderr.length > 0)
|
|
60
|
+
return stderr.toString("utf-8").trim();
|
|
61
|
+
}
|
|
62
|
+
return err instanceof Error ? err.message : String(err);
|
|
63
|
+
};
|
|
64
|
+
const installObsidianPlugin = (pluginId) => {
|
|
65
|
+
try {
|
|
66
|
+
execFileSync("obsidian", ["plugin:install", `id=${pluginId}`, "enable"], {
|
|
67
|
+
encoding: "utf-8",
|
|
68
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
69
|
+
});
|
|
70
|
+
return { ok: true };
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
return { ok: false, error: formatExecError(err) };
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
export const buildPluginInstallGuidance = (plugins) => {
|
|
77
|
+
const lines = [
|
|
78
|
+
"Install missing Obsidian plugins:",
|
|
79
|
+
"",
|
|
80
|
+
"CLI (when Obsidian CLI is available):",
|
|
81
|
+
...plugins.map((plugin) => `obsidian plugin:install id=${plugin.id} enable`),
|
|
82
|
+
"",
|
|
83
|
+
"Deep links (open each URI):",
|
|
84
|
+
...plugins.map((plugin) => `obsidian://show-plugin?id=${encodeURIComponent(plugin.id)}`),
|
|
85
|
+
"",
|
|
86
|
+
"GUI fallback: Obsidian → Settings → Community plugins → Browse",
|
|
87
|
+
];
|
|
88
|
+
return lines.join("\n");
|
|
89
|
+
};
|
|
90
|
+
const runPluginOnboarding = async (ctx, vaultPath) => {
|
|
91
|
+
const missingPlugins = getMissingOnboardingPlugins(vaultPath);
|
|
92
|
+
if (missingPlugins.length === 0) {
|
|
93
|
+
ctx.ui.notify("Obsidian plugin check: required integration plugins already enabled.", "info");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
ctx.ui.notify([
|
|
97
|
+
"Missing Obsidian plugins detected:",
|
|
98
|
+
...missingPlugins.map((plugin) => `- ${plugin.label} (${plugin.id})${plugin.required ? " [required]" : " [recommended]"} — ${plugin.reason}`),
|
|
99
|
+
].join("\n"), "warning");
|
|
100
|
+
const cliAvailable = hasObsidianCli();
|
|
101
|
+
const options = cliAvailable
|
|
102
|
+
? [
|
|
103
|
+
"Install missing plugins now (Obsidian CLI)",
|
|
104
|
+
"I'll install manually (show commands + URIs)",
|
|
105
|
+
"Skip plugin setup for now",
|
|
106
|
+
]
|
|
107
|
+
: ["I'll install manually (show commands + URIs)", "Skip plugin setup for now"];
|
|
108
|
+
const choice = await ctx.ui.select("Obsidian plugin onboarding", options);
|
|
109
|
+
if (!choice || choice === "Skip plugin setup for now")
|
|
110
|
+
return;
|
|
111
|
+
if (choice === "Install missing plugins now (Obsidian CLI)") {
|
|
112
|
+
const installed = [];
|
|
113
|
+
const failed = [];
|
|
114
|
+
for (const plugin of missingPlugins) {
|
|
115
|
+
const result = installObsidianPlugin(plugin.id);
|
|
116
|
+
if (result.ok) {
|
|
117
|
+
installed.push(plugin.id);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
failed.push({ plugin, error: result.error || "Unknown error" });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (installed.length > 0) {
|
|
124
|
+
ctx.ui.notify([`Installed plugins (${installed.length}):`, ...installed.map((id) => `- ${id}`)].join("\n"), "info");
|
|
125
|
+
}
|
|
126
|
+
if (failed.length > 0) {
|
|
127
|
+
ctx.ui.notify([
|
|
128
|
+
"Some plugin installs failed:",
|
|
129
|
+
...failed.map(({ plugin, error }) => `- ${plugin.id}: ${error}`),
|
|
130
|
+
"",
|
|
131
|
+
buildPluginInstallGuidance(failed.map(({ plugin }) => plugin)),
|
|
132
|
+
].join("\n"), "warning");
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
ctx.ui.notify(buildPluginInstallGuidance(missingPlugins), "info");
|
|
137
|
+
};
|
|
138
|
+
export const detectVaultFromCwd = (cwd) => {
|
|
139
|
+
const obsidianDir = path.join(cwd, ".obsidian");
|
|
140
|
+
try {
|
|
141
|
+
return fs.statSync(obsidianDir).isDirectory() ? cwd : null;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
5
147
|
export const createCollectionWizard = async (ctx) => {
|
|
6
148
|
const { project: cfgPath } = findConfig(ctx.cwd);
|
|
7
149
|
if (!cfgPath) {
|
|
@@ -84,6 +226,7 @@ export const createInjectorWizard = async (ctx) => {
|
|
|
84
226
|
};
|
|
85
227
|
export const setupWizard = async (ctx, cliArgs) => {
|
|
86
228
|
const existingGlobal = fs.existsSync(getGlobalConfigPath());
|
|
229
|
+
const detectedVaultPath = detectVaultFromCwd(ctx.cwd);
|
|
87
230
|
// ── Non-interactive / CLI mode ──────────────────────────────────────────
|
|
88
231
|
if (cliArgs && (cliArgs.vault || cliArgs.remoteUrl || cliArgs.localUrl || cliArgs.workspace)) {
|
|
89
232
|
const config = existingGlobal
|
|
@@ -92,8 +235,9 @@ export const setupWizard = async (ctx, cliArgs) => {
|
|
|
92
235
|
config.vaultMind = config.vaultMind || {};
|
|
93
236
|
config.vaultMind.embedding = config.vaultMind.embedding || {};
|
|
94
237
|
config.vaultMind.vaults = config.vaultMind.vaults || {};
|
|
95
|
-
|
|
96
|
-
|
|
238
|
+
const effectiveVaultPath = cliArgs.vault || detectedVaultPath || undefined;
|
|
239
|
+
if (effectiveVaultPath) {
|
|
240
|
+
config.vaultMind.vaults.default = { path: shrinkHome(effectiveVaultPath), autoSync: true };
|
|
97
241
|
}
|
|
98
242
|
if (cliArgs.remoteUrl) {
|
|
99
243
|
config.vaultMind.embedding.remoteUrl = cliArgs.remoteUrl;
|
|
@@ -110,16 +254,19 @@ export const setupWizard = async (ctx, cliArgs) => {
|
|
|
110
254
|
if (cliArgs.workspace) {
|
|
111
255
|
config.vaultMind.embedding.modal = config.vaultMind.embedding.modal || {};
|
|
112
256
|
config.vaultMind.embedding.modal.workspace = cliArgs.workspace;
|
|
257
|
+
if (!cliArgs.remoteUrl) {
|
|
258
|
+
config.vaultMind.embedding.remoteUrl = modalUrl(cliArgs.workspace);
|
|
259
|
+
}
|
|
113
260
|
}
|
|
114
261
|
const dir = path.dirname(getGlobalConfigPath());
|
|
115
262
|
if (!fs.existsSync(dir))
|
|
116
263
|
fs.mkdirSync(dir, { recursive: true });
|
|
117
264
|
fs.writeFileSync(getGlobalConfigPath(), `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
|
118
265
|
const lines = ["✅ Global config written:", ` ${getGlobalConfigPath()}`, ""];
|
|
119
|
-
if (
|
|
120
|
-
lines.push(` Vault: ${
|
|
121
|
-
if (
|
|
122
|
-
lines.push(` Remote URL: ${
|
|
266
|
+
if (effectiveVaultPath)
|
|
267
|
+
lines.push(` Vault: ${effectiveVaultPath}`);
|
|
268
|
+
if (config.vaultMind.embedding.remoteUrl)
|
|
269
|
+
lines.push(` Remote URL: ${config.vaultMind.embedding.remoteUrl}`);
|
|
123
270
|
if (cliArgs.localUrl)
|
|
124
271
|
lines.push(` Local URL: ${cliArgs.localUrl}`);
|
|
125
272
|
if (cliArgs.model)
|
|
@@ -128,6 +275,21 @@ export const setupWizard = async (ctx, cliArgs) => {
|
|
|
128
275
|
lines.push(` Dim: ${cliArgs.dim}`);
|
|
129
276
|
if (cliArgs.workspace)
|
|
130
277
|
lines.push(` Modal workspace: ${cliArgs.workspace}`);
|
|
278
|
+
if (effectiveVaultPath) {
|
|
279
|
+
const obsidianDir = path.join(effectiveVaultPath, ".obsidian");
|
|
280
|
+
if (fs.existsSync(obsidianDir)) {
|
|
281
|
+
const missingPlugins = getMissingOnboardingPlugins(effectiveVaultPath);
|
|
282
|
+
if (missingPlugins.length === 0) {
|
|
283
|
+
lines.push("", "Obsidian plugin check: required integration plugins already enabled.");
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
lines.push("", "Missing Obsidian plugins:", ...missingPlugins.map((plugin) => `- ${plugin.label} (${plugin.id})`), "", buildPluginInstallGuidance(missingPlugins));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
lines.push("", "Obsidian plugin check skipped: .obsidian/ not found.", "Open the vault in Obsidian once, then re-run /vm setup for plugin guidance.");
|
|
291
|
+
}
|
|
292
|
+
}
|
|
131
293
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
132
294
|
return;
|
|
133
295
|
}
|
|
@@ -152,7 +314,12 @@ export const setupWizard = async (ctx, cliArgs) => {
|
|
|
152
314
|
}
|
|
153
315
|
}
|
|
154
316
|
// ── Step 1: Vault path ─────────────────────────────────────────────────
|
|
155
|
-
const
|
|
317
|
+
const autoVaultPath = cliArgs?.vault?.trim() || detectedVaultPath || "";
|
|
318
|
+
if (autoVaultPath) {
|
|
319
|
+
ctx.ui.notify(`Using current vault path: ${autoVaultPath}`, "info");
|
|
320
|
+
}
|
|
321
|
+
const vaultPath = autoVaultPath ||
|
|
322
|
+
(await ctx.ui.input("Obsidian vault path (absolute):", `/home/${process.env.USER || "you"}/Obsidian/`));
|
|
156
323
|
if (!vaultPath) {
|
|
157
324
|
ctx.ui.notify("Setup cancelled — vault path is required.", "warning");
|
|
158
325
|
return;
|
|
@@ -167,29 +334,72 @@ export const setupWizard = async (ctx, cliArgs) => {
|
|
|
167
334
|
return;
|
|
168
335
|
}
|
|
169
336
|
}
|
|
337
|
+
// ── Step 1.5: Obsidian plugin readiness ────────────────────────────────
|
|
338
|
+
await runPluginOnboarding(ctx, vaultPath);
|
|
170
339
|
// ── Step 2: Embedding config ──────────────────────────────────────────
|
|
171
|
-
|
|
340
|
+
let remoteUrl = await ctx.ui.input("Remote Embedding URL (e.g. https://.../v1, optional):", "");
|
|
172
341
|
const localUrl = await ctx.ui.input("Local Embedding URL (e.g. http://127.0.0.1:11434/v1, optional):", "");
|
|
173
342
|
let useTransformers = false;
|
|
343
|
+
let guidedModalWorkspace;
|
|
174
344
|
if (!remoteUrl && !localUrl) {
|
|
175
|
-
const
|
|
176
|
-
|
|
345
|
+
const route = await ctx.ui.select("No embedding URL configured", [
|
|
346
|
+
"Use local transformers (offline)",
|
|
347
|
+
"Guide me through Modal setup",
|
|
348
|
+
"Cancel setup",
|
|
349
|
+
]);
|
|
350
|
+
if (!route || route === "Cancel setup") {
|
|
177
351
|
ctx.ui.notify("Setup cancelled — at least one URL or transformers is required.", "warning");
|
|
178
352
|
return;
|
|
179
353
|
}
|
|
180
|
-
|
|
354
|
+
if (route === "Use local transformers (offline)") {
|
|
355
|
+
useTransformers = true;
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
const workspaceSlug = await ctx.ui.input("Modal workspace slug:", "");
|
|
359
|
+
if (!workspaceSlug) {
|
|
360
|
+
ctx.ui.notify("Setup cancelled — workspace is required for Modal guidance.", "warning");
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
guidedModalWorkspace = workspaceSlug.trim();
|
|
364
|
+
remoteUrl = modalUrl(guidedModalWorkspace);
|
|
365
|
+
ctx.ui.notify([
|
|
366
|
+
"Modal endpoint discovery:",
|
|
367
|
+
`- Derived remote URL: ${remoteUrl}`,
|
|
368
|
+
"- Set token now with one of:",
|
|
369
|
+
" /vm remote token",
|
|
370
|
+
" ./scripts/fetch-modal-token.sh --write",
|
|
371
|
+
"- Reference docs:",
|
|
372
|
+
" docs/getting-started/NEW_VAULT_WALKTHROUGH.md",
|
|
373
|
+
" docs/integrations/MODAL_EMBEDDING.md",
|
|
374
|
+
].join("\n"), "info");
|
|
375
|
+
}
|
|
181
376
|
}
|
|
182
|
-
const model = await ctx.ui.input("
|
|
183
|
-
if (
|
|
184
|
-
ctx.ui.notify("Setup cancelled
|
|
377
|
+
const model = (await ctx.ui.input("Embedding model key (optional):", ""))?.trim();
|
|
378
|
+
if (model === undefined) {
|
|
379
|
+
ctx.ui.notify("Setup cancelled.", "warning");
|
|
185
380
|
return;
|
|
186
381
|
}
|
|
187
382
|
const dimStr = await ctx.ui.input("Dimensions (optional):", "");
|
|
188
383
|
const dim = dimStr ? Number.parseInt(dimStr, 10) : undefined;
|
|
189
|
-
let workspace;
|
|
190
|
-
if (remoteUrl?.includes("modal.run")) {
|
|
384
|
+
let workspace = guidedModalWorkspace;
|
|
385
|
+
if (!workspace && remoteUrl?.includes("modal.run")) {
|
|
191
386
|
workspace = await ctx.ui.input("Modal workspace (optional):", "");
|
|
192
387
|
}
|
|
388
|
+
// ── Step 3: Deterministic runtime settings ──────────────────────────────
|
|
389
|
+
const enableContextAutomation = await ctx.ui.confirm("Context automation", "Enable pi-context integration with auto-/acm trigger and event indexing?");
|
|
390
|
+
const enableAutoStart = await ctx.ui.confirm("Vault auto-start", "Auto-start watcher/server for this vault when pi session starts?");
|
|
391
|
+
const apply = await ctx.ui.confirm("Apply setup", [
|
|
392
|
+
`Vault: ${vaultPath}`,
|
|
393
|
+
`Remote URL: ${remoteUrl || "none"}`,
|
|
394
|
+
`Local URL: ${localUrl || "none"}`,
|
|
395
|
+
`Model: ${model || "(auto/server default)"}`,
|
|
396
|
+
`Context automation: ${enableContextAutomation ? "enabled" : "disabled"}`,
|
|
397
|
+
`Vault auto-start: ${enableAutoStart ? "enabled" : "disabled"}`,
|
|
398
|
+
].join("\n"));
|
|
399
|
+
if (!apply) {
|
|
400
|
+
ctx.ui.notify("Setup cancelled — no changes written.", "warning");
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
193
403
|
// ── Write config ────────────────────────────────────────────────────────
|
|
194
404
|
const config = existingGlobal
|
|
195
405
|
? JSON.parse(fs.readFileSync(getGlobalConfigPath(), "utf-8"))
|
|
@@ -201,7 +411,8 @@ export const setupWizard = async (ctx, cliArgs) => {
|
|
|
201
411
|
config.vaultMind.embedding.remoteUrl = remoteUrl;
|
|
202
412
|
if (localUrl)
|
|
203
413
|
config.vaultMind.embedding.localUrl = localUrl;
|
|
204
|
-
|
|
414
|
+
if (model)
|
|
415
|
+
config.vaultMind.embedding.model = model;
|
|
205
416
|
if (useTransformers)
|
|
206
417
|
config.vaultMind.embedding.useTransformers = true;
|
|
207
418
|
if (dim)
|
|
@@ -210,9 +421,22 @@ export const setupWizard = async (ctx, cliArgs) => {
|
|
|
210
421
|
config.vaultMind.embedding.modal = config.vaultMind.embedding.modal || {};
|
|
211
422
|
config.vaultMind.embedding.modal.workspace = workspace;
|
|
212
423
|
}
|
|
213
|
-
config.vaultMind.vaults.default = {
|
|
424
|
+
config.vaultMind.vaults.default = {
|
|
425
|
+
path: shrinkHome(vaultPath),
|
|
426
|
+
autoSync: true,
|
|
427
|
+
autoStart: enableAutoStart,
|
|
428
|
+
};
|
|
214
429
|
config.vaultMind.graph = config.vaultMind.graph || { enabled: true, canvasSync: true };
|
|
215
430
|
config.vaultMind.ftsEnabled = config.vaultMind.ftsEnabled !== false;
|
|
431
|
+
config.extensionCompatibility = config.extensionCompatibility || {};
|
|
432
|
+
config.extensionCompatibility["pi-context"] = {
|
|
433
|
+
tagPatterns: [],
|
|
434
|
+
enhanceInjectors: false,
|
|
435
|
+
autoEnableAcm: true,
|
|
436
|
+
indexContextEvents: true,
|
|
437
|
+
...(config.extensionCompatibility["pi-context"] || {}),
|
|
438
|
+
enabled: enableContextAutomation,
|
|
439
|
+
};
|
|
216
440
|
const dir = path.dirname(getGlobalConfigPath());
|
|
217
441
|
if (!fs.existsSync(dir))
|
|
218
442
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -224,10 +448,19 @@ export const setupWizard = async (ctx, cliArgs) => {
|
|
|
224
448
|
` Vault: ${vaultPath}`,
|
|
225
449
|
` Remote: ${remoteUrl || "none"}`,
|
|
226
450
|
` Local: ${localUrl || "none"}`,
|
|
227
|
-
` Model: ${model}`,
|
|
451
|
+
` Model: ${model || "(auto/server default)"}`,
|
|
452
|
+
` Context automation: ${enableContextAutomation ? "enabled" : "disabled"}`,
|
|
453
|
+
` Vault auto-start: ${enableAutoStart ? "enabled" : "disabled"}`,
|
|
228
454
|
"",
|
|
229
455
|
"Next: /vm watcher start (or restart pi for auto-start)",
|
|
230
456
|
].join("\n"), "info");
|
|
457
|
+
if (remoteUrl?.includes("modal.run")) {
|
|
458
|
+
ctx.ui.notify([
|
|
459
|
+
"Modal token reminder:",
|
|
460
|
+
"- /vm remote token",
|
|
461
|
+
"- ./scripts/fetch-modal-token.sh --write",
|
|
462
|
+
].join("\n"), "info");
|
|
463
|
+
}
|
|
231
464
|
};
|
|
232
465
|
/**
|
|
233
466
|
* Interactive Modal embedding configuration + "Test connection" action.
|
|
@@ -347,30 +580,68 @@ export const openSettingsDashboard = async (ctx) => {
|
|
|
347
580
|
let running = true;
|
|
348
581
|
while (running) {
|
|
349
582
|
const choice = await ctx.ui.select("pi-vault-mind Settings", [
|
|
350
|
-
"
|
|
351
|
-
"
|
|
352
|
-
"
|
|
353
|
-
"
|
|
583
|
+
"Collections (storage + search targets)",
|
|
584
|
+
"Injectors (regex capture rules)",
|
|
585
|
+
"Embedding + remote provider",
|
|
586
|
+
"Context automation (pi-context)",
|
|
587
|
+
"Config (view active values)",
|
|
588
|
+
"Obsidian plugin settings (what to configure)",
|
|
354
589
|
"Exit",
|
|
355
590
|
]);
|
|
356
591
|
if (!choice || choice === "Exit") {
|
|
357
592
|
running = false;
|
|
358
593
|
break;
|
|
359
594
|
}
|
|
360
|
-
if (choice === "
|
|
595
|
+
if (choice === "Collections (storage + search targets)") {
|
|
361
596
|
await manageCollections(ctx, cfgPath);
|
|
362
597
|
}
|
|
363
|
-
else if (choice === "
|
|
598
|
+
else if (choice === "Injectors (regex capture rules)") {
|
|
364
599
|
await manageInjectors(ctx, cfgPath);
|
|
365
600
|
}
|
|
366
|
-
else if (choice === "
|
|
601
|
+
else if (choice === "Embedding + remote provider") {
|
|
367
602
|
await configureModalWizard(ctx);
|
|
368
603
|
}
|
|
369
|
-
else if (choice === "
|
|
604
|
+
else if (choice === "Context automation (pi-context)") {
|
|
370
605
|
await manageExtensions(ctx, cfgPath);
|
|
371
606
|
}
|
|
607
|
+
else if (choice === "Config (view active values)") {
|
|
608
|
+
await showConfigSettings(ctx, cfgPath);
|
|
609
|
+
}
|
|
610
|
+
else if (choice === "Obsidian plugin settings (what to configure)") {
|
|
611
|
+
showObsidianSettingsHelp(ctx);
|
|
612
|
+
}
|
|
372
613
|
}
|
|
373
614
|
};
|
|
615
|
+
const showConfigSettings = async (ctx, cfgPath) => {
|
|
616
|
+
const merged = loadConfig(ctx.cwd);
|
|
617
|
+
const paths = findConfig(ctx.cwd);
|
|
618
|
+
const lines = [
|
|
619
|
+
"Config sources:",
|
|
620
|
+
`- Global: ${paths.global || "(none)"}`,
|
|
621
|
+
`- Project: ${paths.project || "(none)"}`,
|
|
622
|
+
"",
|
|
623
|
+
"Merged runtime config:",
|
|
624
|
+
JSON.stringify(merged.vaultMind, null, 2),
|
|
625
|
+
];
|
|
626
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
627
|
+
const projectRaw = fs.readFileSync(cfgPath, "utf-8");
|
|
628
|
+
ctx.ui.notify(["Project config JSON:", projectRaw].join("\n"), "info");
|
|
629
|
+
};
|
|
630
|
+
const showObsidianSettingsHelp = (ctx) => {
|
|
631
|
+
ctx.ui.notify([
|
|
632
|
+
"Obsidian plugin settings (Vault Mind):",
|
|
633
|
+
"- Host / Port",
|
|
634
|
+
"- Pi binary path",
|
|
635
|
+
"- Check extension on startup",
|
|
636
|
+
"- Include editor context",
|
|
637
|
+
"- Include file picker",
|
|
638
|
+
"- Include slash commands",
|
|
639
|
+
"- Folder layout: Inbox / Library / Presentations / Journal",
|
|
640
|
+
"- API token (Import from dotenv)",
|
|
641
|
+
"",
|
|
642
|
+
"Location: Obsidian → Settings → Community plugins → Vault Mind",
|
|
643
|
+
].join("\n"), "info");
|
|
644
|
+
};
|
|
374
645
|
const manageCollections = async (ctx, cfgPath) => {
|
|
375
646
|
const existing = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
376
647
|
const names = Object.keys(existing.collections || {});
|
|
@@ -422,22 +693,27 @@ const manageInjectors = async (ctx, cfgPath) => {
|
|
|
422
693
|
const manageExtensions = async (ctx, cfgPath) => {
|
|
423
694
|
const existing = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
424
695
|
existing.extensionCompatibility = existing.extensionCompatibility || {};
|
|
425
|
-
const piCtx =
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
696
|
+
const piCtx = {
|
|
697
|
+
enabled: true,
|
|
698
|
+
autoEnableAcm: true,
|
|
699
|
+
indexContextEvents: true,
|
|
700
|
+
...(existing.extensionCompatibility["pi-context"] || {}),
|
|
701
|
+
};
|
|
702
|
+
const choice = await ctx.ui.select("Context automation (pi-context)", [
|
|
703
|
+
`Toggle integration (currently ${piCtx.enabled ? "ON" : "OFF"})`,
|
|
704
|
+
`Toggle auto-/acm trigger (currently ${piCtx.autoEnableAcm !== false ? "ON" : "OFF"})`,
|
|
705
|
+
`Toggle context event indexing (currently ${piCtx.indexContextEvents !== false ? "ON" : "OFF"})`,
|
|
430
706
|
"Back",
|
|
431
707
|
]);
|
|
432
708
|
if (!choice || choice === "Back")
|
|
433
709
|
return;
|
|
434
|
-
if (choice.startsWith("Toggle
|
|
710
|
+
if (choice.startsWith("Toggle integration")) {
|
|
435
711
|
piCtx.enabled = !piCtx.enabled;
|
|
436
712
|
}
|
|
437
|
-
else if (choice.startsWith("Toggle
|
|
713
|
+
else if (choice.startsWith("Toggle auto-/acm trigger")) {
|
|
438
714
|
piCtx.autoEnableAcm = piCtx.autoEnableAcm === false;
|
|
439
715
|
}
|
|
440
|
-
else if (choice.startsWith("Toggle
|
|
716
|
+
else if (choice.startsWith("Toggle context event indexing")) {
|
|
441
717
|
piCtx.indexContextEvents = piCtx.indexContextEvents === false;
|
|
442
718
|
}
|
|
443
719
|
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:
|
|
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:
|
|
126
|
+
enabled: true,
|
|
127
127
|
tagPatterns: [],
|
|
128
128
|
enhanceInjectors: false,
|
|
129
129
|
autoEnableAcm: true,
|
package/dist/test/index.test.js
CHANGED
|
@@ -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,
|
|
102
|
+
assert.equal(types.DEFAULT_CONFIG.vaultMind.embedding.model, undefined);
|
|
103
103
|
}
|
|
104
104
|
finally {
|
|
105
105
|
process.chdir(cwd);
|
|
@@ -0,0 +1,91 @@
|
|
|
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 { buildPluginInstallGuidance, detectVaultFromCwd, getMissingOnboardingPlugins, 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 plugin onboarding helpers", () => {
|
|
41
|
+
it("reports only missing community plugins", () => {
|
|
42
|
+
const vault = mkTmpDir("pvm-plugin-check-");
|
|
43
|
+
const obsidianDir = path.join(vault, ".obsidian");
|
|
44
|
+
fs.mkdirSync(obsidianDir, { recursive: true });
|
|
45
|
+
fs.writeFileSync(path.join(obsidianDir, "community-plugins.json"), JSON.stringify(["obsidian-pi-vault-mind"], null, 2), "utf-8");
|
|
46
|
+
const missing = getMissingOnboardingPlugins(vault).map((plugin) => plugin.id);
|
|
47
|
+
assert.equal(missing.includes("obsidian-pi-vault-mind"), false);
|
|
48
|
+
assert.equal(missing.includes("actions-uri"), true);
|
|
49
|
+
assert.equal(missing.includes("obsidian-git"), true);
|
|
50
|
+
fs.rmSync(vault, { recursive: true, force: true });
|
|
51
|
+
});
|
|
52
|
+
it("builds manual guidance with CLI commands and Obsidian URIs", () => {
|
|
53
|
+
const vault = mkTmpDir("pvm-plugin-guide-");
|
|
54
|
+
const guidance = buildPluginInstallGuidance(getMissingOnboardingPlugins(vault));
|
|
55
|
+
assert.match(guidance, /obsidian plugin:install id=actions-uri enable/);
|
|
56
|
+
assert.match(guidance, /obsidian:\/\/show-plugin\?id=obsidian-pi-vault-mind/);
|
|
57
|
+
fs.rmSync(vault, { recursive: true, force: true });
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe("setupWizard CLI defaults", () => {
|
|
61
|
+
it("auto-uses cwd as vault when cwd is an Obsidian vault", async () => {
|
|
62
|
+
const home = mkTmpDir("pvm-home-");
|
|
63
|
+
const vault = path.join(home, "Vault");
|
|
64
|
+
fs.mkdirSync(path.join(vault, ".obsidian"), { recursive: true });
|
|
65
|
+
process.env.HOME = home;
|
|
66
|
+
await setupWizard(makeCtx(vault), {
|
|
67
|
+
remoteUrl: "https://example.com/v1",
|
|
68
|
+
model: "embeddinggemma",
|
|
69
|
+
});
|
|
70
|
+
const cfg = JSON.parse(fs.readFileSync(getGlobalConfigPath(), "utf-8"));
|
|
71
|
+
const stored = cfg.vaultMind?.vaults?.default?.path;
|
|
72
|
+
assert.ok(stored, "default vault path should be written");
|
|
73
|
+
assert.equal(expandMaybeHome(stored, home), vault);
|
|
74
|
+
assert.equal(cfg.vaultMind?.embedding?.remoteUrl, "https://example.com/v1");
|
|
75
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
76
|
+
});
|
|
77
|
+
it("derives remoteUrl from workspace in CLI mode", async () => {
|
|
78
|
+
const home = mkTmpDir("pvm-home-");
|
|
79
|
+
const vault = path.join(home, "Vault");
|
|
80
|
+
fs.mkdirSync(path.join(vault, ".obsidian"), { recursive: true });
|
|
81
|
+
process.env.HOME = home;
|
|
82
|
+
await setupWizard(makeCtx(vault), {
|
|
83
|
+
workspace: "acme-workspace",
|
|
84
|
+
model: "embeddinggemma",
|
|
85
|
+
});
|
|
86
|
+
const cfg = JSON.parse(fs.readFileSync(getGlobalConfigPath(), "utf-8"));
|
|
87
|
+
assert.equal(cfg.vaultMind?.embedding?.modal?.workspace, "acme-workspace");
|
|
88
|
+
assert.equal(cfg.vaultMind?.embedding?.remoteUrl, modalUrl("acme-workspace"));
|
|
89
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
90
|
+
});
|
|
91
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-vault-mind",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.8",
|
|
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
|
|
87
|
-
events to `/vault-mind/scan` for explicit, reliable
|
|
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
|
-
#
|
|
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":"
|
|
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=
|
|
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
|
-
- `
|
|
106
|
+
- `Vault Mind plugin` — native setup/status/chat + local bridge trigger
|