pi-vault-mind 0.7.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.
- package/LICENSE +21 -0
- package/README.md +428 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/src/commands.d.ts +9 -0
- package/dist/src/commands.js +813 -0
- package/dist/src/events.d.ts +13 -0
- package/dist/src/events.js +236 -0
- package/dist/src/graph.d.ts +3 -0
- package/dist/src/graph.js +234 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +61 -0
- package/dist/src/lance.d.ts +40 -0
- package/dist/src/lance.js +409 -0
- package/dist/src/server.d.ts +25 -0
- package/dist/src/server.js +180 -0
- package/dist/src/settings-ui.d.ts +9 -0
- package/dist/src/settings-ui.js +313 -0
- package/dist/src/state.d.ts +2 -0
- package/dist/src/state.js +16 -0
- package/dist/src/tools.d.ts +2 -0
- package/dist/src/tools.js +772 -0
- package/dist/src/types.d.ts +103 -0
- package/dist/src/types.js +51 -0
- package/dist/src/utils.d.ts +17 -0
- package/dist/src/utils.js +102 -0
- package/dist/src/vault-writer.d.ts +17 -0
- package/dist/src/vault-writer.js +141 -0
- package/dist/src/watcher.d.ts +91 -0
- package/dist/src/watcher.js +411 -0
- package/dist/src/widget.d.ts +3 -0
- package/dist/src/widget.js +12 -0
- package/dist/test/index.test.d.ts +1 -0
- package/dist/test/index.test.js +368 -0
- package/package.json +83 -0
- package/skills/vault-mind/SKILL.md +260 -0
- package/skills/vault-mind/references/tool-reference.md +53 -0
- package/skills/vault-mind-broadcaster/SKILL.md +112 -0
- package/skills/vault-mind-heavy-lifter/SKILL.md +34 -0
- package/skills/vault-mind-manager/SKILL.md +35 -0
- package/skills/vault-mind-miner/SKILL.md +40 -0
- package/skills/vault-mind-setup/SKILL.md +385 -0
- package/skills/vault-mind-setup/references/obsidian-cli-and-plugins.md +269 -0
- package/skills/vault-mind-setup/references/obsidian-vault-structure.md +106 -0
- package/skills/vault-mind-setup/references/pi-extension-wiring.md +236 -0
- package/skills/vault-mind-setup/references/troubleshooting-tree.md +147 -0
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { Container, SelectList, Text } from "@earendil-works/pi-tui";
|
|
4
|
+
import * as lancedb from "@lancedb/lancedb";
|
|
5
|
+
import { getActiveCollection, setActiveCollection } from "./state.js";
|
|
6
|
+
import { DEFAULT_CONFIG } from "./types.js";
|
|
7
|
+
import { CONFIG_FILES, EXT_ROOT, collectionNames, ensureDir, findConfig, getPiContextConfig, hasPiContextTools, loadConfig, } from "./utils.js";
|
|
8
|
+
import { updateActiveCollectionWidget } from "./widget.js";
|
|
9
|
+
import { connect, pullOllamaModel, testOllamaConnection } from "./lance.js";
|
|
10
|
+
import { createServerState } from "./server.js";
|
|
11
|
+
import { createCollectionWizard, createInjectorWizard, openSettingsDashboard, setupWizard, } from "./settings-ui.js";
|
|
12
|
+
import { createWatcherState, getWatcherStatus, startWatcher, stopWatcher } from "./watcher.js";
|
|
13
|
+
// ── Shared helpers ───────────────────────────────────────────────────────────
|
|
14
|
+
const WIKI_USAGE = [
|
|
15
|
+
"**pi-vault-mind Commands**",
|
|
16
|
+
"",
|
|
17
|
+
" /wiki init Scaffold config + collections",
|
|
18
|
+
" /wiki validate Check LanceDB, config, and collection health",
|
|
19
|
+
" /wiki approve [collection] Batch-review pending entries",
|
|
20
|
+
" /wiki settings Open interactive settings dashboard",
|
|
21
|
+
" /wiki audit Audit config for missing defaults",
|
|
22
|
+
" /wiki reindex Rebuild FTS + vector indexes (replaces old /qmd-index)",
|
|
23
|
+
" /wiki collection select Select active collection",
|
|
24
|
+
" /wiki collection create Create a new collection (wizard)",
|
|
25
|
+
" /wiki injector create Create a new injector (wizard)",
|
|
26
|
+
" /wiki context enable|disable Enable/disable pi-context integration",
|
|
27
|
+
" /wiki context status Show pi-context integration status",
|
|
28
|
+
" /wiki embedding status Show embedding config + Ollama models",
|
|
29
|
+
" /wiki embedding use Switch provider (ollama | transformers)",
|
|
30
|
+
" /wiki embedding model Set Ollama embedding model",
|
|
31
|
+
" /wiki embedding models List available Ollama models",
|
|
32
|
+
" /wiki embedding pull Pull a model from Ollama",
|
|
33
|
+
" /wiki watcher start Start passive file watcher",
|
|
34
|
+
" /wiki watcher stop Stop passive file watcher",
|
|
35
|
+
" /wiki watcher status Show watcher status",
|
|
36
|
+
" /wiki server status Show HTTP server status + port",
|
|
37
|
+
" /wiki setup Interactive global config wizard",
|
|
38
|
+
" /wiki setup --vault <path> CLI: set vault path",
|
|
39
|
+
" /wiki setup --provider <p> CLI: set embedding provider",
|
|
40
|
+
" /wiki help Show this help",
|
|
41
|
+
].join("\n");
|
|
42
|
+
export const auditConfig = async (ctx) => {
|
|
43
|
+
const { project: cfgPath } = findConfig(ctx.cwd);
|
|
44
|
+
if (!cfgPath) {
|
|
45
|
+
ctx.ui.notify("No project config file found to audit. Run /wiki init first.", "info");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const projectCfg = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
49
|
+
const missingCollections = [];
|
|
50
|
+
const missingInjectors = [];
|
|
51
|
+
for (const name of Object.keys(DEFAULT_CONFIG.collections)) {
|
|
52
|
+
if (!projectCfg.collections?.[name])
|
|
53
|
+
missingCollections.push(name);
|
|
54
|
+
}
|
|
55
|
+
for (const ij of DEFAULT_CONFIG.injectors) {
|
|
56
|
+
if (!projectCfg.injectors?.some((pij) => pij.name === ij.name)) {
|
|
57
|
+
missingInjectors.push(ij.name);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (missingCollections.length === 0 && missingInjectors.length === 0) {
|
|
61
|
+
ctx.ui.notify("✅ Project config is up-to-date with all default collections and injectors.", "info");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const report = [
|
|
65
|
+
"**Config Audit Report**",
|
|
66
|
+
"",
|
|
67
|
+
`Project Config: ${path.relative(ctx.cwd, cfgPath)}`,
|
|
68
|
+
"",
|
|
69
|
+
`Missing Collections (${missingCollections.length}):`,
|
|
70
|
+
...missingCollections.map((l) => ` • ${l}`),
|
|
71
|
+
"",
|
|
72
|
+
`Missing Injectors (${missingInjectors.length}):`,
|
|
73
|
+
...missingInjectors.map((i) => ` • ${i}`),
|
|
74
|
+
"",
|
|
75
|
+
"Would you like to repair the config by adding the missing defaults?",
|
|
76
|
+
].join("\n");
|
|
77
|
+
const repair = await ctx.ui.confirm("Repair Config", report);
|
|
78
|
+
if (repair) {
|
|
79
|
+
const updatedCfg = { ...projectCfg };
|
|
80
|
+
updatedCfg.collections = { ...(projectCfg.collections || {}), ...DEFAULT_CONFIG.collections };
|
|
81
|
+
const injectors = [...(projectCfg.injectors || [])];
|
|
82
|
+
for (const ij of DEFAULT_CONFIG.injectors) {
|
|
83
|
+
if (!injectors.some((pij) => pij.name === ij.name)) {
|
|
84
|
+
injectors.push(ij);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
updatedCfg.injectors = injectors;
|
|
88
|
+
fs.writeFileSync(cfgPath, `${JSON.stringify(updatedCfg, null, 2)}\n`, "utf-8");
|
|
89
|
+
ctx.ui.notify("✅ Project config repaired with missing defaults.", "info");
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
ctx.ui.notify("Audit complete. No changes made.", "info");
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
export const selectActiveCollection = async (ctx) => {
|
|
96
|
+
const cfg = loadConfig(ctx.cwd);
|
|
97
|
+
const names = collectionNames(cfg);
|
|
98
|
+
if (!ctx.hasUI) {
|
|
99
|
+
const next = names.find((n) => n !== getActiveCollection(ctx.cwd)) || names[0];
|
|
100
|
+
if (next) {
|
|
101
|
+
setActiveCollection(next);
|
|
102
|
+
updateActiveCollectionWidget(ctx);
|
|
103
|
+
ctx.ui.notify(`Active collection set to: ${next}`, "info");
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (names.length === 0) {
|
|
108
|
+
ctx.ui.notify("No collections configured.", "warning");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const items = names.map((name) => ({ label: name, value: name }));
|
|
112
|
+
const result = await ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
113
|
+
const container = new Container();
|
|
114
|
+
container.addChild(new Text(theme.fg("accent", "Select Active Collection"), 1, 1));
|
|
115
|
+
const selectList = new SelectList(items, Math.min(items.length, 10), {
|
|
116
|
+
selectedPrefix: (t) => theme.fg("accent", t),
|
|
117
|
+
selectedText: (t) => theme.fg("accent", t),
|
|
118
|
+
description: (t) => theme.fg("muted", t),
|
|
119
|
+
scrollInfo: (t) => theme.fg("dim", t),
|
|
120
|
+
noMatch: (t) => theme.fg("warning", t),
|
|
121
|
+
});
|
|
122
|
+
selectList.onSelect = (item) => done(item.value);
|
|
123
|
+
selectList.onCancel = () => done(null);
|
|
124
|
+
container.addChild(selectList);
|
|
125
|
+
container.addChild(new Text(theme.fg("dim", "↑↓ navigate • enter select • esc cancel"), 1, 0));
|
|
126
|
+
return {
|
|
127
|
+
render: (w) => container.render(w),
|
|
128
|
+
handleInput: (data) => {
|
|
129
|
+
selectList.handleInput?.(data);
|
|
130
|
+
tui.requestRender();
|
|
131
|
+
},
|
|
132
|
+
invalidate: () => container.invalidate(),
|
|
133
|
+
};
|
|
134
|
+
}, { overlay: true });
|
|
135
|
+
if (result) {
|
|
136
|
+
setActiveCollection(result);
|
|
137
|
+
updateActiveCollectionWidget(ctx);
|
|
138
|
+
ctx.ui.notify(`Active collection set to: ${result}`, "info");
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
// ── /wiki init ──────────────────────────────────────────────────────────────
|
|
142
|
+
const handleInit = async (_args, ctx, pi) => {
|
|
143
|
+
const cfg = loadConfig(ctx.cwd);
|
|
144
|
+
const created = [];
|
|
145
|
+
const skipped = [];
|
|
146
|
+
const ensureFile = (dest, tmpl) => {
|
|
147
|
+
if (fs.existsSync(dest)) {
|
|
148
|
+
skipped.push(dest);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
ensureDir(dest);
|
|
152
|
+
const src = path.join(EXT_ROOT, "templates", tmpl);
|
|
153
|
+
fs.writeFileSync(dest, fs.existsSync(src) ? fs.readFileSync(src) : "", "utf-8");
|
|
154
|
+
created.push(dest);
|
|
155
|
+
};
|
|
156
|
+
const cfgDest = path.join(ctx.cwd, CONFIG_FILES[0]);
|
|
157
|
+
if (!fs.existsSync(cfgDest)) {
|
|
158
|
+
const tmpl = {
|
|
159
|
+
version: 2,
|
|
160
|
+
collections: {
|
|
161
|
+
main: {
|
|
162
|
+
path: "collections/main.jsonl",
|
|
163
|
+
schema: ["id", "domain", "source", "fact", "tag", "artifact"],
|
|
164
|
+
dedupField: "fact",
|
|
165
|
+
},
|
|
166
|
+
pending: { path: "collections/pending.jsonl", schema: "main" },
|
|
167
|
+
context_events: {
|
|
168
|
+
path: "collections/context_events.jsonl",
|
|
169
|
+
schema: ["id", "type", "session_entry_id", "content", "timestamp", "tags"],
|
|
170
|
+
dedupField: "id",
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
injectors: [
|
|
174
|
+
{
|
|
175
|
+
name: "draft-context",
|
|
176
|
+
regex: "draft\\s+(\\S+)",
|
|
177
|
+
collection: "main",
|
|
178
|
+
filterField: "tag",
|
|
179
|
+
artifactPath: "collections/artifact.md",
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
wiki: {
|
|
183
|
+
dataDir: ".lancedb",
|
|
184
|
+
embedding: {
|
|
185
|
+
provider: "transformers",
|
|
186
|
+
ollamaModel: "embeddinggemma",
|
|
187
|
+
ollamaHost: "http://127.0.0.1:11434",
|
|
188
|
+
},
|
|
189
|
+
ftsEnabled: true,
|
|
190
|
+
graph: { enabled: true, canvasSync: false },
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
fs.writeFileSync(cfgDest, `${JSON.stringify(tmpl, null, 2)}\n`, "utf-8");
|
|
194
|
+
created.push(cfgDest);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
skipped.push(cfgDest);
|
|
198
|
+
}
|
|
199
|
+
for (const [name, def] of Object.entries(cfg.collections)) {
|
|
200
|
+
ensureFile(def.path, name === "pending" ? "PENDING_LEDGER.jsonl" : "UCL_LEDGER.jsonl");
|
|
201
|
+
}
|
|
202
|
+
for (const ij of cfg.injectors) {
|
|
203
|
+
if (ij.artifactPath)
|
|
204
|
+
ensureFile(ij.artifactPath, "ARTIFACT.md");
|
|
205
|
+
}
|
|
206
|
+
const msg = [
|
|
207
|
+
"Wiki scaffolding complete.",
|
|
208
|
+
"",
|
|
209
|
+
"Created:",
|
|
210
|
+
...created.map((c) => ` • ${path.relative(ctx.cwd, c)}`),
|
|
211
|
+
];
|
|
212
|
+
if (skipped.length) {
|
|
213
|
+
msg.push("", "Skipped (already exist):", ...skipped.map((s) => ` • ${path.relative(ctx.cwd, s)}`));
|
|
214
|
+
}
|
|
215
|
+
msg.push("", '💡 Tip: Tell the agent "Remember: [fact]" to auto-append to the main collection!');
|
|
216
|
+
if (ctx.hasUI) {
|
|
217
|
+
ctx.ui.notify(msg.join("\n"), "info");
|
|
218
|
+
if (hasPiContextTools(pi)) {
|
|
219
|
+
const enable = await ctx.ui.confirm("Enable pi-context integration?", "pi-context tools were detected. Would you like to enable the integration now to capture session context?");
|
|
220
|
+
if (enable) {
|
|
221
|
+
const existing = JSON.parse(fs.readFileSync(cfgDest, "utf-8"));
|
|
222
|
+
existing.extensionCompatibility = existing.extensionCompatibility || {};
|
|
223
|
+
existing.extensionCompatibility["pi-context"] = {
|
|
224
|
+
tagPatterns: [],
|
|
225
|
+
enhanceInjectors: false,
|
|
226
|
+
autoEnableAcm: true,
|
|
227
|
+
indexContextEvents: true,
|
|
228
|
+
...existing.extensionCompatibility["pi-context"],
|
|
229
|
+
enabled: true,
|
|
230
|
+
};
|
|
231
|
+
fs.writeFileSync(cfgDest, `${JSON.stringify(existing, null, 2)}\n`, "utf-8");
|
|
232
|
+
ctx.ui.notify("✅ pi-context integration ENABLED in config", "info");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
// ── /wiki validate ───────────────────────────────────────────────────────────
|
|
238
|
+
const handleValidate = async (_args, ctx, pi) => {
|
|
239
|
+
const cfg = loadConfig(ctx.cwd);
|
|
240
|
+
const lines = [];
|
|
241
|
+
try {
|
|
242
|
+
await connect(cfg.wiki.dataDir);
|
|
243
|
+
lines.push(`✅ LanceDB: Connected to ${cfg.wiki.dataDir}`);
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
lines.push(`❌ LanceDB: Connection failed. ${err.message}`);
|
|
247
|
+
}
|
|
248
|
+
// Check LanceDB tables
|
|
249
|
+
try {
|
|
250
|
+
const db = await connect(cfg.wiki.dataDir);
|
|
251
|
+
const tableNames = await db.tableNames();
|
|
252
|
+
lines.push(`📊 LanceDB tables: ${tableNames.length > 0 ? tableNames.join(", ") : "(none — created on first append)"}`);
|
|
253
|
+
}
|
|
254
|
+
catch { }
|
|
255
|
+
const { project: cfgPath } = findConfig(ctx.cwd);
|
|
256
|
+
lines.push(cfgPath ? `✅ Config: ${cfgPath}` : `⚠️ No config file (checked ${CONFIG_FILES.join(", ")}).`);
|
|
257
|
+
for (const [name, def] of Object.entries(cfg.collections)) {
|
|
258
|
+
const exists = fs.existsSync(def.path);
|
|
259
|
+
lines.push(`${exists ? "✅" : "⚠️"} Collection "${name}": ${def.path} ${exists ? "" : "(missing)"}`);
|
|
260
|
+
if (exists && def.dedupField)
|
|
261
|
+
lines.push(` └─ dedupField: ${def.dedupField}`);
|
|
262
|
+
}
|
|
263
|
+
lines.push(`📡 Injectors (${cfg.injectors.length}):`);
|
|
264
|
+
for (const ij of cfg.injectors) {
|
|
265
|
+
lines.push(` • "${ij.name}" → collection:"${ij.collection}" regex:${ij.regex}`);
|
|
266
|
+
}
|
|
267
|
+
const hasContext = hasPiContextTools(pi);
|
|
268
|
+
const contextCfg = getPiContextConfig(cfg);
|
|
269
|
+
const willCapture = hasContext && contextCfg.enabled;
|
|
270
|
+
lines.push("", "🧩 Extension Integrations:", " • pi-context:");
|
|
271
|
+
lines.push(` - Tools installed: ${hasContext ? "✅ Yes (context_tag, context_log, context_checkout)" : "❌ No"}`);
|
|
272
|
+
lines.push(` - Config enabled: ${contextCfg.enabled ? "✅ Yes" : "❌ No"}`);
|
|
273
|
+
lines.push(` - Will capture events: ${willCapture ? "✅ Yes — on next turn" : "❌ No"}`);
|
|
274
|
+
if (hasContext && !contextCfg.enabled)
|
|
275
|
+
lines.push(" → Run /wiki context enable to activate");
|
|
276
|
+
if (!hasContext && contextCfg.enabled)
|
|
277
|
+
lines.push(" ⚠️ Config says enabled but tools missing. Install with: pi install npm:pi-context");
|
|
278
|
+
if (ctx.hasUI)
|
|
279
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
280
|
+
};
|
|
281
|
+
// ── /wiki approve ────────────────────────────────────────────────────────────
|
|
282
|
+
const handleApprove = async (args, ctx) => {
|
|
283
|
+
const cfg = loadConfig(ctx.cwd);
|
|
284
|
+
const pending = cfg.collections.pending;
|
|
285
|
+
if (!pending || !fs.existsSync(pending.path)) {
|
|
286
|
+
ctx.ui.notify("No pending collection configured or empty.", "info");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const targetName = args.trim() || "main";
|
|
290
|
+
const target = cfg.collections[targetName];
|
|
291
|
+
if (!target) {
|
|
292
|
+
ctx.ui.notify(`Unknown target collection "${targetName}".`, "error");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const lines = fs.readFileSync(pending.path, "utf-8").split("\n").filter(Boolean);
|
|
296
|
+
if (lines.length === 0) {
|
|
297
|
+
ctx.ui.notify("No pending entries.", "info");
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
let approved = 0;
|
|
301
|
+
const rejected = [];
|
|
302
|
+
for (const line of lines) {
|
|
303
|
+
const ok = await ctx.ui.confirm("Pending Entry", `${line}\n\nApprove migration to "${targetName}"?`);
|
|
304
|
+
if (ok) {
|
|
305
|
+
let actualTargetName = targetName;
|
|
306
|
+
try {
|
|
307
|
+
const entry = JSON.parse(line);
|
|
308
|
+
if (entry._promotion_target)
|
|
309
|
+
actualTargetName = entry._promotion_target;
|
|
310
|
+
}
|
|
311
|
+
catch { }
|
|
312
|
+
const actualTarget = cfg.collections[actualTargetName];
|
|
313
|
+
if (!actualTarget) {
|
|
314
|
+
ctx.ui.notify(`Promotion target "${actualTargetName}" is no longer valid.`, "error");
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
ensureDir(actualTarget.path);
|
|
318
|
+
fs.appendFileSync(actualTarget.path, `${line}\n`);
|
|
319
|
+
approved++;
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
rejected.push(line);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
fs.writeFileSync(pending.path, rejected.join("\n") + (rejected.length > 0 ? "\n" : ""));
|
|
326
|
+
ctx.ui.notify(`Approved ${approved} → "${targetName}". Rejected ${rejected.length}.`, "info");
|
|
327
|
+
};
|
|
328
|
+
// ── /wiki reindex ────────────────────────────────────────────────────────────
|
|
329
|
+
const handleReindex = async (args, ctx, pi) => {
|
|
330
|
+
const cfg = loadConfig(ctx.cwd);
|
|
331
|
+
const subcommand = args.trim().split(/\s+/)[0]?.toLowerCase() || "";
|
|
332
|
+
const rebuildEmbeddings = subcommand === "--reembed" || subcommand === "--full";
|
|
333
|
+
const reindexAll = subcommand === "--all";
|
|
334
|
+
const collectionFilter = reindexAll || rebuildEmbeddings ? null : subcommand || null;
|
|
335
|
+
ctx.ui.notify(rebuildEmbeddings
|
|
336
|
+
? "Reindexing: regenerating embeddings + rebuilding indexes..."
|
|
337
|
+
: "Reindexing: rebuilding FTS + vector indexes...", "info");
|
|
338
|
+
const lines = [];
|
|
339
|
+
const names = collectionFilter ? [collectionFilter] : Object.keys(cfg.collections);
|
|
340
|
+
for (const name of names) {
|
|
341
|
+
if (!cfg.collections[name])
|
|
342
|
+
continue;
|
|
343
|
+
const tableName = `collection_${name}`;
|
|
344
|
+
try {
|
|
345
|
+
const db = await connect(cfg.wiki.dataDir);
|
|
346
|
+
const existing = await db.tableNames();
|
|
347
|
+
if (!existing.includes(tableName)) {
|
|
348
|
+
lines.push(` ⚠️ ${tableName}: table not found (created on first append)`);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
const table = await db.openTable(tableName);
|
|
352
|
+
// If --reembed, drop and recreate with fresh embeddings
|
|
353
|
+
if (rebuildEmbeddings) {
|
|
354
|
+
// Read all existing rows
|
|
355
|
+
const rows = await table.query().toArray();
|
|
356
|
+
lines.push(` 📦 ${tableName}: ${rows.length} rows — re-embedding...`);
|
|
357
|
+
// Drop and recreate (LanceDB auto-embeds on add)
|
|
358
|
+
await db.dropTable(tableName);
|
|
359
|
+
// The table will be recreated on next append/upsert with current embedding config
|
|
360
|
+
lines.push(` ✅ ${tableName}: dropped — will re-embed on next append`);
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
// Rebuild FTS index
|
|
364
|
+
try {
|
|
365
|
+
if (cfg.wiki.ftsEnabled !== false) {
|
|
366
|
+
await table.createIndex("fact", { config: lancedb.Index.fts(), replace: true });
|
|
367
|
+
lines.push(` ✅ ${tableName}: FTS index rebuilt`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
lines.push(` ⚠️ ${tableName}: FTS reindex failed — ${err.message}`);
|
|
372
|
+
}
|
|
373
|
+
// Rebuild vector ANN index for larger tables
|
|
374
|
+
try {
|
|
375
|
+
const count = await table.countRows();
|
|
376
|
+
if (count > 1000) {
|
|
377
|
+
await table.createIndex("vector");
|
|
378
|
+
lines.push(` ✅ ${tableName}: vector ANN index created (${count} rows)`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch (err) {
|
|
382
|
+
lines.push(` ⚠️ ${tableName}: vector index skipped — ${err.message}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch (err) {
|
|
387
|
+
lines.push(` ❌ ${tableName}: ${err.message}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// Also reindex graph tables if --all
|
|
391
|
+
if (reindexAll) {
|
|
392
|
+
for (const graphTable of ["entities", "relations"]) {
|
|
393
|
+
try {
|
|
394
|
+
const db = await connect(cfg.wiki.dataDir);
|
|
395
|
+
const existing = await db.tableNames();
|
|
396
|
+
if (existing.includes(graphTable)) {
|
|
397
|
+
const table = await db.openTable(graphTable);
|
|
398
|
+
if (rebuildEmbeddings) {
|
|
399
|
+
const rows = await table.query().toArray();
|
|
400
|
+
lines.push(` 📦 ${graphTable}: ${rows.length} rows — re-embedding...`);
|
|
401
|
+
await db.dropTable(graphTable);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch (err) {
|
|
406
|
+
lines.push(` ⚠️ ${graphTable}: ${err.message}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
lines.unshift("**Reindex Report:**", "");
|
|
411
|
+
lines.push("", rebuildEmbeddings
|
|
412
|
+
? "Entries will be re-embedded with current embedding model on next append."
|
|
413
|
+
: "Use /wiki reindex --reembed to regenerate embeddings after model switch.");
|
|
414
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
415
|
+
};
|
|
416
|
+
// ── /wiki embedding ──────────────────────────────────────────────────────────
|
|
417
|
+
const handleEmbedding = async (args, ctx, pi) => {
|
|
418
|
+
const cfg = loadConfig(ctx.cwd);
|
|
419
|
+
const parts = args.trim().split(/\s+/g);
|
|
420
|
+
const subcommand = parts[0]?.toLowerCase() || "status";
|
|
421
|
+
const value = parts.slice(1).join(" ");
|
|
422
|
+
const { project: cfgPath } = findConfig(ctx.cwd);
|
|
423
|
+
if (!cfgPath) {
|
|
424
|
+
ctx.ui.notify("No config found. Run /wiki init first.", "error");
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
switch (subcommand) {
|
|
428
|
+
case "status": {
|
|
429
|
+
const lines = [
|
|
430
|
+
"**Embedding Configuration**",
|
|
431
|
+
"",
|
|
432
|
+
`Provider: ${cfg.wiki.embedding.provider}`,
|
|
433
|
+
cfg.wiki.embedding.provider === "ollama"
|
|
434
|
+
? `Model: ${cfg.wiki.embedding.ollamaModel || "embeddinggemma"}`
|
|
435
|
+
: "Model: all-MiniLM-L6-v2 (384 dims)",
|
|
436
|
+
`FTS: ${cfg.wiki.ftsEnabled !== false ? "enabled" : "disabled"}`,
|
|
437
|
+
`Graph: ${cfg.wiki.graph?.enabled !== false ? "enabled" : "disabled"}`,
|
|
438
|
+
`Data Dir: ${cfg.wiki.dataDir}`,
|
|
439
|
+
];
|
|
440
|
+
if (cfg.wiki.embedding.provider === "ollama" || !cfg.wiki.embedding.provider) {
|
|
441
|
+
const conn = await testOllamaConnection(pi);
|
|
442
|
+
lines.push("", "**Ollama Status:**", ` Reachable: ${conn.reachable ? "✅ Yes" : "❌ No"}`);
|
|
443
|
+
if (conn.error)
|
|
444
|
+
lines.push(` Error: ${conn.error}`);
|
|
445
|
+
if (conn.models.length > 0) {
|
|
446
|
+
lines.push(` Available models (${conn.models.length}):`);
|
|
447
|
+
for (const m of conn.models.slice(0, 10)) {
|
|
448
|
+
const isCurrent = m.name === cfg.wiki.embedding.ollamaModel;
|
|
449
|
+
lines.push(` ${isCurrent ? "★" : " "} ${m.name} (${m.size})`);
|
|
450
|
+
}
|
|
451
|
+
if (conn.models.length > 10)
|
|
452
|
+
lines.push(` ... and ${conn.models.length - 10} more`);
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
lines.push(" No models found. Pull one with: /wiki embedding pull embeddinggemma");
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
lines.push("", "**Transformers:** Using Xenova/all-MiniLM-L6-v2 (384 dims)", " Downloads on first use and caches locally.");
|
|
460
|
+
}
|
|
461
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
case "use": {
|
|
465
|
+
if (!value || !["ollama", "transformers"].includes(value)) {
|
|
466
|
+
ctx.ui.notify("/wiki embedding use <ollama|transformers>", "error");
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (value === "ollama") {
|
|
470
|
+
const conn = await testOllamaConnection(pi);
|
|
471
|
+
if (!conn.reachable) {
|
|
472
|
+
const confirm = await ctx.ui.confirm("Ollama Not Reachable", "Cannot connect to Ollama. Switch anyway?\n\nVerify with:\n ollama list\n ollama pull embeddinggemma");
|
|
473
|
+
if (!confirm)
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const existing = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
478
|
+
existing.wiki = existing.wiki || {};
|
|
479
|
+
existing.wiki.embedding = existing.wiki.embedding || {};
|
|
480
|
+
existing.wiki.embedding.provider = value;
|
|
481
|
+
fs.writeFileSync(cfgPath, `${JSON.stringify(existing, null, 2)}\n`, "utf-8");
|
|
482
|
+
ctx.ui.notify(`✅ Embedding provider set to: ${value}. Restart session for changes to take effect.`, "info");
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
case "model": {
|
|
486
|
+
if (!value) {
|
|
487
|
+
ctx.ui.notify(`Current: ${cfg.wiki.embedding.ollamaModel || "embeddinggemma"}. Usage: /wiki embedding model <name>`, "info");
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const existing = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
491
|
+
existing.wiki = existing.wiki || {};
|
|
492
|
+
existing.wiki.embedding = existing.wiki.embedding || {};
|
|
493
|
+
existing.wiki.embedding.ollamaModel = value;
|
|
494
|
+
fs.writeFileSync(cfgPath, `${JSON.stringify(existing, null, 2)}\n`, "utf-8");
|
|
495
|
+
ctx.ui.notify(`✅ Ollama embedding model set to: ${value}`, "info");
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
case "models": {
|
|
499
|
+
const conn = await testOllamaConnection(pi);
|
|
500
|
+
if (!conn.reachable) {
|
|
501
|
+
ctx.ui.notify("❌ Ollama not reachable.", "error");
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (conn.models.length === 0) {
|
|
505
|
+
ctx.ui.notify("No models found. Pull one with: /wiki embedding pull embeddinggemma", "info");
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const lines = ["**Available Ollama Models:**", ""];
|
|
509
|
+
for (const m of conn.models) {
|
|
510
|
+
lines.push(` ${m.name === cfg.wiki.embedding.ollamaModel ? "★" : " "} ${m.name} (${m.size})`);
|
|
511
|
+
}
|
|
512
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
case "pull": {
|
|
516
|
+
if (!value) {
|
|
517
|
+
ctx.ui.notify("/wiki embedding pull <model>. Try: embeddinggemma", "error");
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
ctx.ui.notify(`Pulling "${value}" from Ollama... (this may take a while)`, "info");
|
|
521
|
+
const result = await pullOllamaModel(value, pi);
|
|
522
|
+
ctx.ui.notify(result.message, result.success ? "info" : "error");
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
default: {
|
|
526
|
+
ctx.ui.notify(`Unknown: ${subcommand}. Try: status, use, model, models, pull`, "error");
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
// ── /wiki context ────────────────────────────────────────────────────────────
|
|
532
|
+
const handleContext = async (args, ctx, pi) => {
|
|
533
|
+
const parts = args.trim().split(/\s+/g);
|
|
534
|
+
const subcommand = parts[0]?.toLowerCase() || "status";
|
|
535
|
+
const value = parts[1]?.toLowerCase();
|
|
536
|
+
const { project: cfgPath } = findConfig(ctx.cwd);
|
|
537
|
+
if (!cfgPath) {
|
|
538
|
+
ctx.ui.notify("No config found. Run /wiki init first.", "error");
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
switch (subcommand) {
|
|
542
|
+
case "status":
|
|
543
|
+
case "show": {
|
|
544
|
+
const cfg = loadConfig(ctx.cwd);
|
|
545
|
+
const piContextCfg = getPiContextConfig(cfg);
|
|
546
|
+
const hasTools = hasPiContextTools(pi);
|
|
547
|
+
const willCapture = hasTools && piContextCfg.enabled;
|
|
548
|
+
const lines = [
|
|
549
|
+
"**pi-context Integration**",
|
|
550
|
+
"",
|
|
551
|
+
`Status: ${willCapture ? "✅ ACTIVE" : "❌ INACTIVE"}`,
|
|
552
|
+
` - Tools installed: ${hasTools ? "✅" : "❌"}`,
|
|
553
|
+
` - Config enabled: ${piContextCfg.enabled ? "✅" : "❌"}`,
|
|
554
|
+
` - Will capture on next turn: ${willCapture ? "✅ Yes" : "❌ No"}`,
|
|
555
|
+
"",
|
|
556
|
+
`Tag Patterns: ${piContextCfg.tagPatterns?.length || 0}`,
|
|
557
|
+
...(piContextCfg.tagPatterns?.length
|
|
558
|
+
? piContextCfg.tagPatterns.map((p) => ` • "${p}"`)
|
|
559
|
+
: [" (none — no tag-based lookups)"]),
|
|
560
|
+
"",
|
|
561
|
+
`Enhance Injectors: ${piContextCfg.enhanceInjectors ? "Yes" : "No"}`,
|
|
562
|
+
`autoEnableAcm: ${piContextCfg.autoEnableAcm ? "Yes" : "No"}`,
|
|
563
|
+
`indexContextEvents: ${piContextCfg.indexContextEvents ? "Yes" : "No"}`,
|
|
564
|
+
];
|
|
565
|
+
ctx.ui.notify(lines.join("\n"), willCapture ? "info" : "warning");
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
case "enable": {
|
|
569
|
+
_toggleContext(cfgPath, true);
|
|
570
|
+
ctx.ui.notify("✅ pi-context integration ENABLED", "info");
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
case "disable": {
|
|
574
|
+
_toggleContext(cfgPath, false);
|
|
575
|
+
ctx.ui.notify("pi-context integration DISABLED", "info");
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
default: {
|
|
579
|
+
if (["enable", "on", "true", "yes", "1"].includes(subcommand)) {
|
|
580
|
+
_toggleContext(cfgPath, true);
|
|
581
|
+
ctx.ui.notify("✅ pi-context integration ENABLED", "info");
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (["disable", "off", "false", "no", "0"].includes(subcommand)) {
|
|
585
|
+
_toggleContext(cfgPath, false);
|
|
586
|
+
ctx.ui.notify("pi-context integration DISABLED", "info");
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
ctx.ui.notify(`Unknown: ${subcommand}. Try: status, enable, disable`, "error");
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
const _toggleContext = (cfgPath, enabled) => {
|
|
594
|
+
const existing = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
595
|
+
existing.extensionCompatibility = existing.extensionCompatibility || {};
|
|
596
|
+
existing.extensionCompatibility["pi-context"] = {
|
|
597
|
+
tagPatterns: [],
|
|
598
|
+
enhanceInjectors: false,
|
|
599
|
+
autoEnableAcm: true,
|
|
600
|
+
indexContextEvents: true,
|
|
601
|
+
...existing.extensionCompatibility["pi-context"],
|
|
602
|
+
enabled,
|
|
603
|
+
};
|
|
604
|
+
fs.writeFileSync(cfgPath, `${JSON.stringify(existing, null, 2)}\n`, "utf-8");
|
|
605
|
+
};
|
|
606
|
+
// ── /wiki collection ─────────────────────────────────────────────────────────────
|
|
607
|
+
const handleCollection = async (args, ctx) => {
|
|
608
|
+
const parts = args.trim().split(/\s+/g);
|
|
609
|
+
const subcommand = parts[0]?.toLowerCase() || "select";
|
|
610
|
+
switch (subcommand) {
|
|
611
|
+
case "select":
|
|
612
|
+
await selectActiveCollection(ctx);
|
|
613
|
+
return;
|
|
614
|
+
case "create":
|
|
615
|
+
await createCollectionWizard(ctx);
|
|
616
|
+
return;
|
|
617
|
+
default:
|
|
618
|
+
ctx.ui.notify(`Unknown: ${subcommand}. Try: select, create`, "error");
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
// ── /wiki injector ───────────────────────────────────────────────────────────
|
|
622
|
+
const handleInjector = async (args, ctx) => {
|
|
623
|
+
const parts = args.trim().split(/\s+/g);
|
|
624
|
+
const subcommand = parts[0]?.toLowerCase() || "create";
|
|
625
|
+
switch (subcommand) {
|
|
626
|
+
case "create":
|
|
627
|
+
await createInjectorWizard(ctx);
|
|
628
|
+
return;
|
|
629
|
+
default:
|
|
630
|
+
ctx.ui.notify(`Unknown: ${subcommand}. Try: create`, "error");
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
// ── Watcher ──────────────────────────────────────────────────────────────────
|
|
634
|
+
export const watcherState = createWatcherState();
|
|
635
|
+
/** Module-level server state, referenced by the /wiki server command.
|
|
636
|
+
* Port is set when the server starts in index.ts. */
|
|
637
|
+
export const serverState = createServerState();
|
|
638
|
+
const handleServer = (ctx) => {
|
|
639
|
+
const lines = [
|
|
640
|
+
`HTTP server: ${serverState.server ? "RUNNING" : "STOPPED"}`,
|
|
641
|
+
`Port: ${serverState.port}`,
|
|
642
|
+
"Bind: 127.0.0.1",
|
|
643
|
+
];
|
|
644
|
+
if (serverState.startTime > 0) {
|
|
645
|
+
const uptime = Math.floor((Date.now() - serverState.startTime) / 1000);
|
|
646
|
+
lines.push(`Uptime: ${uptime}s`);
|
|
647
|
+
lines.push("");
|
|
648
|
+
lines.push("Endpoints:");
|
|
649
|
+
lines.push(` GET http://127.0.0.1:${serverState.port}/vault-mind/status`);
|
|
650
|
+
lines.push(` POST http://127.0.0.1:${serverState.port}/vault-mind/scan`);
|
|
651
|
+
lines.push(` POST http://127.0.0.1:${serverState.port}/vault-mind/dispatch`);
|
|
652
|
+
}
|
|
653
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
654
|
+
};
|
|
655
|
+
const handleWatcher = async (args, ctx, pi) => {
|
|
656
|
+
const parts = args.trim().split(/\s+/g);
|
|
657
|
+
const subcommand = parts[0]?.toLowerCase() || "status";
|
|
658
|
+
switch (subcommand) {
|
|
659
|
+
case "start": {
|
|
660
|
+
const cfg = loadConfig(ctx.cwd);
|
|
661
|
+
if (!cfg.wiki.vaults || Object.keys(cfg.wiki.vaults).length === 0) {
|
|
662
|
+
ctx.ui.notify("No vaults configured. Add vaults to wiki.vaults in pi-vault-mind.config.json first.", "error");
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (watcherState.running) {
|
|
666
|
+
ctx.ui.notify("Watcher is already running.", "info");
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
startWatcher(pi, cfg.wiki.vaults, watcherState);
|
|
670
|
+
ctx.ui.notify(getWatcherStatus(watcherState), "info");
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
case "stop": {
|
|
674
|
+
if (!watcherState.running) {
|
|
675
|
+
ctx.ui.notify("Watcher is not running.", "info");
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
stopWatcher(watcherState);
|
|
679
|
+
ctx.ui.notify("Watcher stopped.", "info");
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
default: {
|
|
683
|
+
ctx.ui.notify(getWatcherStatus(watcherState), "info");
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
// ── Setup ────────────────────────────────────────────────────────────────────
|
|
689
|
+
const handleSetup = async (args, ctx) => {
|
|
690
|
+
// Parse optional CLI-style args: --vault <path> --provider <name> --model <name>
|
|
691
|
+
const cliArgs = {};
|
|
692
|
+
const parts = args.trim().split(/\s+--/);
|
|
693
|
+
for (const part of parts) {
|
|
694
|
+
const trimmed = part.trim();
|
|
695
|
+
if (trimmed.startsWith("vault "))
|
|
696
|
+
cliArgs.vault = trimmed.slice(6).trim();
|
|
697
|
+
else if (trimmed === "vault") {
|
|
698
|
+
/* flag without value, handled by interactive */
|
|
699
|
+
}
|
|
700
|
+
else if (trimmed.startsWith("provider "))
|
|
701
|
+
cliArgs.provider = trimmed.slice(9).trim();
|
|
702
|
+
else if (trimmed.startsWith("model "))
|
|
703
|
+
cliArgs.model = trimmed.slice(6).trim();
|
|
704
|
+
}
|
|
705
|
+
const hasCliArgs = cliArgs.vault || cliArgs.provider || cliArgs.model;
|
|
706
|
+
await setupWizard(ctx, hasCliArgs ? cliArgs : undefined);
|
|
707
|
+
};
|
|
708
|
+
// ── Main /wiki command ───────────────────────────────────────────────────────
|
|
709
|
+
export const registerCommands = (pi) => {
|
|
710
|
+
pi.registerCommand("wiki", {
|
|
711
|
+
description: "pi-vault-mind: manage collections, embedding, reindexing, and config.",
|
|
712
|
+
getArgumentCompletions: (_prefix) => {
|
|
713
|
+
const words = _prefix.trim().split(/\s+/);
|
|
714
|
+
const top = [
|
|
715
|
+
"init",
|
|
716
|
+
"validate",
|
|
717
|
+
"approve",
|
|
718
|
+
"settings",
|
|
719
|
+
"audit",
|
|
720
|
+
"help",
|
|
721
|
+
"reindex",
|
|
722
|
+
"collection",
|
|
723
|
+
"injector",
|
|
724
|
+
"context",
|
|
725
|
+
"embedding",
|
|
726
|
+
"watcher",
|
|
727
|
+
"setup",
|
|
728
|
+
];
|
|
729
|
+
if (words.length === 1 || (words.length === 2 && words[1] === "")) {
|
|
730
|
+
return top
|
|
731
|
+
.filter((c) => c.startsWith(words[0] || ""))
|
|
732
|
+
.map((c) => ({ label: c, value: c, description: `wiki ${c}` }));
|
|
733
|
+
}
|
|
734
|
+
// Subcommand completions
|
|
735
|
+
const subcommand = words[0];
|
|
736
|
+
if (words.length >= 2) {
|
|
737
|
+
const prefix = words[1] || "";
|
|
738
|
+
if (subcommand === "embedding") {
|
|
739
|
+
return ["status", "use", "model", "models", "pull"]
|
|
740
|
+
.filter((c) => c.startsWith(prefix))
|
|
741
|
+
.map((c) => ({ label: c, value: c, description: `embedding ${c}` }));
|
|
742
|
+
}
|
|
743
|
+
if (subcommand === "context") {
|
|
744
|
+
return ["status", "enable", "disable"]
|
|
745
|
+
.filter((c) => c.startsWith(prefix))
|
|
746
|
+
.map((c) => ({ label: c, value: c, description: `context ${c}` }));
|
|
747
|
+
}
|
|
748
|
+
if (subcommand === "collection") {
|
|
749
|
+
return ["select", "create"]
|
|
750
|
+
.filter((c) => c.startsWith(prefix))
|
|
751
|
+
.map((c) => ({ label: c, value: c, description: `collection ${c}` }));
|
|
752
|
+
}
|
|
753
|
+
if (subcommand === "injector") {
|
|
754
|
+
return ["create"]
|
|
755
|
+
.filter((c) => c.startsWith(prefix))
|
|
756
|
+
.map((c) => ({ label: c, value: c, description: `injector ${c}` }));
|
|
757
|
+
}
|
|
758
|
+
if (subcommand === "reindex") {
|
|
759
|
+
return ["--all", "--reembed"]
|
|
760
|
+
.filter((c) => c.startsWith(prefix))
|
|
761
|
+
.map((c) => ({ label: c, value: c, description: `reindex ${c}` }));
|
|
762
|
+
}
|
|
763
|
+
if (subcommand === "watcher") {
|
|
764
|
+
return ["start", "stop", "status"]
|
|
765
|
+
.filter((c) => c.startsWith(prefix))
|
|
766
|
+
.map((c) => ({ label: c, value: c, description: `watcher ${c}` }));
|
|
767
|
+
}
|
|
768
|
+
if (subcommand === "setup") {
|
|
769
|
+
return ["--vault", "--provider", "--model"]
|
|
770
|
+
.filter((c) => c.startsWith(prefix))
|
|
771
|
+
.map((c) => ({ label: c, value: c, description: `setup ${c}` }));
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return [];
|
|
775
|
+
},
|
|
776
|
+
handler: async (args, ctx) => {
|
|
777
|
+
const parts = args.trim().split(/\s+/g);
|
|
778
|
+
const subcommand = parts[0]?.toLowerCase() || "help";
|
|
779
|
+
const rest = parts.slice(1).join(" ");
|
|
780
|
+
switch (subcommand) {
|
|
781
|
+
case "init":
|
|
782
|
+
return handleInit(rest, ctx, pi);
|
|
783
|
+
case "validate":
|
|
784
|
+
return handleValidate(rest, ctx, pi);
|
|
785
|
+
case "approve":
|
|
786
|
+
return handleApprove(rest, ctx);
|
|
787
|
+
case "settings":
|
|
788
|
+
return openSettingsDashboard(ctx);
|
|
789
|
+
case "audit":
|
|
790
|
+
return auditConfig(ctx);
|
|
791
|
+
case "reindex":
|
|
792
|
+
return handleReindex(rest, ctx, pi);
|
|
793
|
+
case "collection":
|
|
794
|
+
return handleCollection(rest, ctx);
|
|
795
|
+
case "injector":
|
|
796
|
+
return handleInjector(rest, ctx);
|
|
797
|
+
case "context":
|
|
798
|
+
return handleContext(rest, ctx, pi);
|
|
799
|
+
case "embedding":
|
|
800
|
+
return handleEmbedding(rest, ctx, pi);
|
|
801
|
+
case "server":
|
|
802
|
+
return handleServer(ctx);
|
|
803
|
+
case "watcher":
|
|
804
|
+
return handleWatcher(rest, ctx, pi);
|
|
805
|
+
case "setup":
|
|
806
|
+
return handleSetup(rest, ctx);
|
|
807
|
+
default:
|
|
808
|
+
ctx.ui.notify(WIKI_USAGE, "info");
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
});
|
|
812
|
+
// Legacy aliases — /wiki-* commands delegate to /wiki subcommand handlers
|
|
813
|
+
};
|