pi-vault-mind 0.8.7 → 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.
|
@@ -1,4 +1,12 @@
|
|
|
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;
|
|
2
10
|
export declare const detectVaultFromCwd: (cwd: string) => string | null;
|
|
3
11
|
export declare const createCollectionWizard: (ctx: ExtensionContext) => Promise<void>;
|
|
4
12
|
export declare const createInjectorWizard: (ctx: ExtensionContext) => Promise<void>;
|
package/dist/src/settings-ui.js
CHANGED
|
@@ -1,7 +1,140 @@
|
|
|
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
|
+
};
|
|
5
138
|
export const detectVaultFromCwd = (cwd) => {
|
|
6
139
|
const obsidianDir = path.join(cwd, ".obsidian");
|
|
7
140
|
try {
|
|
@@ -142,6 +275,21 @@ export const setupWizard = async (ctx, cliArgs) => {
|
|
|
142
275
|
lines.push(` Dim: ${cliArgs.dim}`);
|
|
143
276
|
if (cliArgs.workspace)
|
|
144
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
|
+
}
|
|
145
293
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
146
294
|
return;
|
|
147
295
|
}
|
|
@@ -186,6 +334,8 @@ export const setupWizard = async (ctx, cliArgs) => {
|
|
|
186
334
|
return;
|
|
187
335
|
}
|
|
188
336
|
}
|
|
337
|
+
// ── Step 1.5: Obsidian plugin readiness ────────────────────────────────
|
|
338
|
+
await runPluginOnboarding(ctx, vaultPath);
|
|
189
339
|
// ── Step 2: Embedding config ──────────────────────────────────────────
|
|
190
340
|
let remoteUrl = await ctx.ui.input("Remote Embedding URL (e.g. https://.../v1, optional):", "");
|
|
191
341
|
const localUrl = await ctx.ui.input("Local Embedding URL (e.g. http://127.0.0.1:11434/v1, optional):", "");
|
|
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { afterEach, describe, it } from "node:test";
|
|
6
|
-
import { detectVaultFromCwd, setupWizard } from "../src/settings-ui.js";
|
|
6
|
+
import { buildPluginInstallGuidance, detectVaultFromCwd, getMissingOnboardingPlugins, setupWizard, } from "../src/settings-ui.js";
|
|
7
7
|
import { modalUrl } from "../src/modal-config.js";
|
|
8
8
|
import { getGlobalConfigPath } from "../src/utils.js";
|
|
9
9
|
const savedHome = process.env.HOME;
|
|
@@ -37,6 +37,26 @@ describe("settings-ui vault detection", () => {
|
|
|
37
37
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
38
38
|
});
|
|
39
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
|
+
});
|
|
40
60
|
describe("setupWizard CLI defaults", () => {
|
|
41
61
|
it("auto-uses cwd as vault when cwd is an Obsidian vault", async () => {
|
|
42
62
|
const home = mkTmpDir("pvm-home-");
|
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",
|