pi-para 0.5.2 → 0.6.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/ARCHITECTURE.md +160 -0
- package/CHANGELOG.md +51 -0
- package/CONFIGURATION.md +243 -0
- package/README.md +140 -237
- package/SETUP.md +196 -112
- package/STRUCTURE.md +140 -0
- package/package.json +86 -18
- package/scripts/build-assets.mjs +29 -0
- package/scripts/install.ps1 +55 -0
- package/scripts/install.sh +63 -0
- package/scripts/smoke-install.mjs +63 -0
- package/skills/para/SKILL.md +1 -1
- package/skills/setup/SKILL.md +1 -1
- package/src/atomic-write.ts +17 -0
- package/src/capture.ts +4 -4
- package/src/cli-legacy.ts +43 -0
- package/src/cli.ts +133 -28
- package/src/commands.ts +203 -243
- package/src/config.ts +355 -0
- package/src/context.ts +29 -14
- package/src/credentials.ts +100 -0
- package/src/daemon.ts +1 -1
- package/src/doctor.ts +209 -0
- package/src/gepa/index.ts +1 -1
- package/src/index.ts +119 -40
- package/src/maintainer.ts +4 -4
- package/src/model-resolver.ts +96 -0
- package/src/paths.ts +48 -0
- package/src/processor.ts +4 -4
- package/src/qmd-providers.ts +63 -0
- package/src/query.ts +15 -1
- package/src/repair.ts +49 -0
- package/src/scheduler/controls.ts +125 -0
- package/src/scheduler/index.ts +210 -0
- package/src/scheduler/leases.ts +98 -0
- package/src/scheduler/session-capture.ts +146 -0
- package/src/scheduler/state.ts +304 -0
- package/src/session-tools.ts +1 -1
- package/src/settings.ts +126 -0
- package/src/setup.ts +116 -0
- package/src/status.ts +115 -0
- package/src/store.ts +116 -31
- package/src/summarize.ts +72 -16
- package/src/templates/prompts.ts +1 -1
- package/src/tools.ts +280 -90
- package/src/wiki-tool-guidance.ts +84 -0
- package/src/wiki.ts +98 -26
- package/dist/capture.d.ts +0 -34
- package/dist/capture.d.ts.map +0 -1
- package/dist/capture.js +0 -461
- package/dist/capture.js.map +0 -1
- package/dist/cli.d.ts +0 -15
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -405
- package/dist/cli.js.map +0 -1
- package/dist/commands.d.ts +0 -15
- package/dist/commands.d.ts.map +0 -1
- package/dist/commands.js +0 -786
- package/dist/commands.js.map +0 -1
- package/dist/context.d.ts +0 -61
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -289
- package/dist/context.js.map +0 -1
- package/dist/daemon.d.ts +0 -49
- package/dist/daemon.d.ts.map +0 -1
- package/dist/daemon.js +0 -226
- package/dist/daemon.js.map +0 -1
- package/dist/frontmatter.d.ts +0 -59
- package/dist/frontmatter.d.ts.map +0 -1
- package/dist/frontmatter.js +0 -170
- package/dist/frontmatter.js.map +0 -1
- package/dist/gepa/dataset.d.ts +0 -36
- package/dist/gepa/dataset.d.ts.map +0 -1
- package/dist/gepa/dataset.js +0 -148
- package/dist/gepa/dataset.js.map +0 -1
- package/dist/gepa/evaluator.d.ts +0 -51
- package/dist/gepa/evaluator.d.ts.map +0 -1
- package/dist/gepa/evaluator.js +0 -317
- package/dist/gepa/evaluator.js.map +0 -1
- package/dist/gepa/index.d.ts +0 -66
- package/dist/gepa/index.d.ts.map +0 -1
- package/dist/gepa/index.js +0 -240
- package/dist/gepa/index.js.map +0 -1
- package/dist/gepa/llm.d.ts +0 -35
- package/dist/gepa/llm.d.ts.map +0 -1
- package/dist/gepa/llm.js +0 -268
- package/dist/gepa/llm.js.map +0 -1
- package/dist/gepa/optimized/capture-prompt.txt +0 -65
- package/dist/gepa/optimizer.d.ts +0 -29
- package/dist/gepa/optimizer.d.ts.map +0 -1
- package/dist/gepa/optimizer.js +0 -249
- package/dist/gepa/optimizer.js.map +0 -1
- package/dist/gepa/targets.d.ts +0 -39
- package/dist/gepa/targets.d.ts.map +0 -1
- package/dist/gepa/targets.js +0 -326
- package/dist/gepa/targets.js.map +0 -1
- package/dist/gepa/types.d.ts +0 -168
- package/dist/gepa/types.d.ts.map +0 -1
- package/dist/gepa/types.js +0 -124
- package/dist/gepa/types.js.map +0 -1
- package/dist/index.d.ts +0 -21
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -294
- package/dist/index.js.map +0 -1
- package/dist/ingest.d.ts +0 -88
- package/dist/ingest.d.ts.map +0 -1
- package/dist/ingest.js +0 -105
- package/dist/ingest.js.map +0 -1
- package/dist/link-discovery.d.ts +0 -40
- package/dist/link-discovery.d.ts.map +0 -1
- package/dist/link-discovery.js +0 -122
- package/dist/link-discovery.js.map +0 -1
- package/dist/link-utils.d.ts +0 -30
- package/dist/link-utils.d.ts.map +0 -1
- package/dist/link-utils.js +0 -145
- package/dist/link-utils.js.map +0 -1
- package/dist/lint.d.ts +0 -39
- package/dist/lint.d.ts.map +0 -1
- package/dist/lint.js +0 -827
- package/dist/lint.js.map +0 -1
- package/dist/maintainer.d.ts +0 -24
- package/dist/maintainer.d.ts.map +0 -1
- package/dist/maintainer.js +0 -358
- package/dist/maintainer.js.map +0 -1
- package/dist/processor.d.ts +0 -14
- package/dist/processor.d.ts.map +0 -1
- package/dist/processor.js +0 -361
- package/dist/processor.js.map +0 -1
- package/dist/query.d.ts +0 -69
- package/dist/query.d.ts.map +0 -1
- package/dist/query.js +0 -107
- package/dist/query.js.map +0 -1
- package/dist/raw.d.ts +0 -57
- package/dist/raw.d.ts.map +0 -1
- package/dist/raw.js +0 -219
- package/dist/raw.js.map +0 -1
- package/dist/redact.d.ts +0 -21
- package/dist/redact.d.ts.map +0 -1
- package/dist/redact.js +0 -143
- package/dist/redact.js.map +0 -1
- package/dist/scope.d.ts +0 -54
- package/dist/scope.d.ts.map +0 -1
- package/dist/scope.js +0 -161
- package/dist/scope.js.map +0 -1
- package/dist/scripts/bulk-link-fix.d.ts +0 -9
- package/dist/scripts/bulk-link-fix.d.ts.map +0 -1
- package/dist/scripts/bulk-link-fix.js +0 -106
- package/dist/scripts/bulk-link-fix.js.map +0 -1
- package/dist/session-tools.d.ts +0 -33
- package/dist/session-tools.d.ts.map +0 -1
- package/dist/session-tools.js +0 -211
- package/dist/session-tools.js.map +0 -1
- package/dist/state.d.ts +0 -54
- package/dist/state.d.ts.map +0 -1
- package/dist/state.js +0 -176
- package/dist/state.js.map +0 -1
- package/dist/store.d.ts +0 -56
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js +0 -227
- package/dist/store.js.map +0 -1
- package/dist/summarize.d.ts +0 -56
- package/dist/summarize.d.ts.map +0 -1
- package/dist/summarize.js +0 -231
- package/dist/summarize.js.map +0 -1
- package/dist/tag-registry.d.ts +0 -56
- package/dist/tag-registry.d.ts.map +0 -1
- package/dist/tag-registry.js +0 -146
- package/dist/tag-registry.js.map +0 -1
- package/dist/templates/prompts.d.ts +0 -63
- package/dist/templates/prompts.d.ts.map +0 -1
- package/dist/templates/prompts.js +0 -260
- package/dist/templates/prompts.js.map +0 -1
- package/dist/tools.d.ts +0 -26
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js +0 -1033
- package/dist/tools.js.map +0 -1
- package/dist/watcher.d.ts +0 -25
- package/dist/watcher.d.ts.map +0 -1
- package/dist/watcher.js +0 -84
- package/dist/watcher.js.map +0 -1
- package/dist/webui/client/dist/index.html +0 -1885
- package/dist/webui/server.d.ts +0 -31
- package/dist/webui/server.d.ts.map +0 -1
- package/dist/webui/server.js +0 -566
- package/dist/webui/server.js.map +0 -1
- package/dist/wiki.d.ts +0 -103
- package/dist/wiki.d.ts.map +0 -1
- package/dist/wiki.js +0 -428
- package/dist/wiki.js.map +0 -1
- package/scripts/gepa/__pycache__/dataset.cpython-313.pyc +0 -0
- package/scripts/gepa/__pycache__/lm_providers.cpython-313.pyc +0 -0
- package/scripts/gepa/__pycache__/metric.cpython-313.pyc +0 -0
- package/scripts/gepa/__pycache__/program.cpython-313.pyc +0 -0
- package/scripts/gepa/__pycache__/wiki_reader.cpython-313.pyc +0 -0
- package/scripts/gepa/uv.lock +0 -2241
package/dist/commands.js
DELETED
|
@@ -1,786 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Slash command registrations for user interaction.
|
|
3
|
-
*
|
|
4
|
-
* Commands: /wiki, /wiki-ingest, /wiki-lint, /wiki-capture,
|
|
5
|
-
* /wiki-scope, /wiki-search, /wiki-summarize,
|
|
6
|
-
* /wiki-daemon, /wiki-settings
|
|
7
|
-
*/
|
|
8
|
-
import { readFile, appendFile } from "node:fs/promises";
|
|
9
|
-
import { join } from "node:path";
|
|
10
|
-
import { lintWiki } from "./lint.js";
|
|
11
|
-
import { listPages, readPage, writePage, movePage, writeIndex, PARA_CATEGORIES } from "./wiki.js";
|
|
12
|
-
import { searchWiki } from "./store.js";
|
|
13
|
-
import { CURRENT_SCHEMA_VERSION, migrateToLatest, parseFrontmatter, validateFrontmatter, } from "./frontmatter.js";
|
|
14
|
-
// -- Helpers -----------------------------------------------------------------
|
|
15
|
-
/** Convert raw text to a kebab-case slug. */
|
|
16
|
-
function toKebabSlug(raw) {
|
|
17
|
-
return raw
|
|
18
|
-
.toLowerCase()
|
|
19
|
-
.replace(/[^a-z0-9-]/g, "-")
|
|
20
|
-
.replace(/-+/g, "-")
|
|
21
|
-
.replace(/^-|-$/g, "");
|
|
22
|
-
}
|
|
23
|
-
/** Convert a slug to title case. */
|
|
24
|
-
function slugToTitle(slug) {
|
|
25
|
-
return slug.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
26
|
-
}
|
|
27
|
-
/** Rebuild index.md from all pages on disk. */
|
|
28
|
-
async function rebuildIndexFromDisk(wikiDir) {
|
|
29
|
-
const allPages = await listPages(wikiDir);
|
|
30
|
-
const sections = {
|
|
31
|
-
projects: [],
|
|
32
|
-
areas: [],
|
|
33
|
-
resources: [],
|
|
34
|
-
archives: [],
|
|
35
|
-
};
|
|
36
|
-
for (const ref of allPages) {
|
|
37
|
-
const page = await readPage(wikiDir, ref.category, ref.slug);
|
|
38
|
-
const title = page?.frontmatter.title ?? ref.title;
|
|
39
|
-
const summary = page?.body
|
|
40
|
-
.split("\n")
|
|
41
|
-
.find((l) => l.trim() && !l.startsWith("#") && !l.startsWith("---"))
|
|
42
|
-
?.trim() ?? "";
|
|
43
|
-
const desc = summary.length > 120 ? summary.slice(0, 117) + "..." : summary;
|
|
44
|
-
sections[ref.category].push(`- [[${ref.slug}]] \u2014 ${title}${desc ? ": " + desc : ""}`);
|
|
45
|
-
}
|
|
46
|
-
const indexLines = [
|
|
47
|
-
"# Wiki Index",
|
|
48
|
-
"",
|
|
49
|
-
"## Projects",
|
|
50
|
-
"",
|
|
51
|
-
sections.projects.length > 0
|
|
52
|
-
? sections.projects.join("\n")
|
|
53
|
-
: "_No active projects yet._",
|
|
54
|
-
"",
|
|
55
|
-
"## Areas",
|
|
56
|
-
"",
|
|
57
|
-
sections.areas.length > 0
|
|
58
|
-
? sections.areas.join("\n")
|
|
59
|
-
: "_No areas defined yet._",
|
|
60
|
-
"",
|
|
61
|
-
"## Resources",
|
|
62
|
-
"",
|
|
63
|
-
sections.resources.length > 0
|
|
64
|
-
? sections.resources.join("\n")
|
|
65
|
-
: "_No resources yet._",
|
|
66
|
-
"",
|
|
67
|
-
"## Archives",
|
|
68
|
-
"",
|
|
69
|
-
sections.archives.length > 0
|
|
70
|
-
? sections.archives.join("\n")
|
|
71
|
-
: "_No archived items._",
|
|
72
|
-
];
|
|
73
|
-
await writeIndex(wikiDir, indexLines.join("\n"));
|
|
74
|
-
}
|
|
75
|
-
/** Parse log.md and return the last N entries as text lines. */
|
|
76
|
-
async function readLastLogEntries(wikiDir, count) {
|
|
77
|
-
let content;
|
|
78
|
-
try {
|
|
79
|
-
content = await readFile(join(wikiDir, "log.md"), "utf-8");
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
return [];
|
|
83
|
-
}
|
|
84
|
-
// Find all entry start positions (each begins with "## [")
|
|
85
|
-
const starts = [];
|
|
86
|
-
const pattern = /^## \[/gm;
|
|
87
|
-
let match;
|
|
88
|
-
while ((match = pattern.exec(content)) !== null) {
|
|
89
|
-
starts.push(match.index);
|
|
90
|
-
}
|
|
91
|
-
// Extract the last `count` entries
|
|
92
|
-
const selected = starts.slice(-count);
|
|
93
|
-
const entries = [];
|
|
94
|
-
for (let i = 0; i < selected.length; i++) {
|
|
95
|
-
const start = selected[i];
|
|
96
|
-
const nextIdx = starts.indexOf(start) + 1;
|
|
97
|
-
const end = nextIdx < starts.length ? starts[nextIdx] : content.length;
|
|
98
|
-
entries.push(content.slice(start, end).trim());
|
|
99
|
-
}
|
|
100
|
-
return entries;
|
|
101
|
-
}
|
|
102
|
-
/** Format lint report for display via notify. */
|
|
103
|
-
function formatLintNotify(report) {
|
|
104
|
-
const { issues, fixed, stats } = report;
|
|
105
|
-
const lines = [];
|
|
106
|
-
lines.push(`Pages: ${stats.totalPages} (${PARA_CATEGORIES.map((c) => `${c}: ${stats.byCategory[c]}`).join(", ")})`);
|
|
107
|
-
lines.push(`Links: ${stats.totalLinks} | Broken: ${stats.brokenLinks} | Orphans: ${stats.orphanPages}`);
|
|
108
|
-
if (fixed.length > 0) {
|
|
109
|
-
lines.push(`Auto-fixed: ${fixed.length} issue(s)`);
|
|
110
|
-
for (const f of fixed.slice(0, 5)) {
|
|
111
|
-
lines.push(` [fix] ${f.page ?? ""}: ${f.message}`);
|
|
112
|
-
}
|
|
113
|
-
if (fixed.length > 5) {
|
|
114
|
-
lines.push(` ... and ${fixed.length - 5} more`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (issues.length > 0) {
|
|
118
|
-
lines.push(`Remaining: ${issues.length} issue(s)`);
|
|
119
|
-
for (const issue of issues.slice(0, 10)) {
|
|
120
|
-
lines.push(` [${issue.severity}] ${issue.page ?? issue.category}: ${issue.message}`);
|
|
121
|
-
}
|
|
122
|
-
if (issues.length > 10) {
|
|
123
|
-
lines.push(` ... and ${issues.length - 10} more`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
lines.push("Wiki is healthy.");
|
|
128
|
-
}
|
|
129
|
-
return lines.join("\n");
|
|
130
|
-
}
|
|
131
|
-
// -- Public API --------------------------------------------------------------
|
|
132
|
-
/**
|
|
133
|
-
* Register all slash commands with the pi extension API.
|
|
134
|
-
*/
|
|
135
|
-
export function registerCommands(pi, wikiDir, store, getScope, setScope) {
|
|
136
|
-
// ---- /wiki — status overview ---------------------------------------------
|
|
137
|
-
pi.registerCommand("wiki", {
|
|
138
|
-
description: "Show wiki status overview (scope, page counts, recent log entries)",
|
|
139
|
-
handler: async (_args, ctx) => {
|
|
140
|
-
const scope = getScope();
|
|
141
|
-
// Page counts by category
|
|
142
|
-
const allPages = await listPages(wikiDir);
|
|
143
|
-
const byCat = {};
|
|
144
|
-
for (const cat of PARA_CATEGORIES) {
|
|
145
|
-
byCat[cat] = 0;
|
|
146
|
-
}
|
|
147
|
-
for (const page of allPages) {
|
|
148
|
-
byCat[page.category] = (byCat[page.category] ?? 0) + 1;
|
|
149
|
-
}
|
|
150
|
-
// Last 5 log entries
|
|
151
|
-
const logEntries = await readLastLogEntries(wikiDir, 5);
|
|
152
|
-
const lines = [];
|
|
153
|
-
lines.push(`Scope: ${scope.name} (${scope.source})`);
|
|
154
|
-
lines.push(` include: ${scope.include.join(", ")}`);
|
|
155
|
-
if (scope.exclude.length > 0) {
|
|
156
|
-
lines.push(` exclude: ${scope.exclude.join(", ")}`);
|
|
157
|
-
}
|
|
158
|
-
lines.push("");
|
|
159
|
-
lines.push(`Pages: ${allPages.length} total`);
|
|
160
|
-
for (const cat of PARA_CATEGORIES) {
|
|
161
|
-
lines.push(` ${cat}: ${byCat[cat]}`);
|
|
162
|
-
}
|
|
163
|
-
lines.push("");
|
|
164
|
-
if (logEntries.length > 0) {
|
|
165
|
-
lines.push("Recent activity:");
|
|
166
|
-
for (const entry of logEntries) {
|
|
167
|
-
// Compact each entry to one line
|
|
168
|
-
const firstLine = entry.split("\n")[0];
|
|
169
|
-
lines.push(` ${firstLine.replace(/^## /, "")}`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
lines.push("No activity logged yet.");
|
|
174
|
-
}
|
|
175
|
-
ctx.ui.notify(lines.join("\n"), "info");
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
// ---- /wiki-ingest — quick ingest shortcut --------------------------------
|
|
179
|
-
pi.registerCommand("wiki-ingest", {
|
|
180
|
-
description: "Ingest a URL or file into the wiki",
|
|
181
|
-
handler: async (args, ctx) => {
|
|
182
|
-
const source = args.trim();
|
|
183
|
-
if (!source) {
|
|
184
|
-
ctx.ui.notify("Usage: /wiki-ingest <url-or-file>", "error");
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
await ctx.waitForIdle();
|
|
188
|
-
pi.sendUserMessage(`Ingest this into the wiki: ${source}`);
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
// ---- /wiki-lint — run lint checks ----------------------------------------
|
|
192
|
-
pi.registerCommand("wiki-lint", {
|
|
193
|
-
description: "Run wiki health checks (--report-only to skip auto-fix)",
|
|
194
|
-
getArgumentCompletions: (prefix) => {
|
|
195
|
-
if ("--report-only".startsWith(prefix)) {
|
|
196
|
-
return [{ value: "--report-only", label: "--report-only" }];
|
|
197
|
-
}
|
|
198
|
-
return null;
|
|
199
|
-
},
|
|
200
|
-
handler: async (args, ctx) => {
|
|
201
|
-
const reportOnly = args.trim() === "--report-only";
|
|
202
|
-
ctx.ui.notify("Running wiki lint...", "info");
|
|
203
|
-
const report = await lintWiki(wikiDir, {
|
|
204
|
-
autoFix: !reportOnly,
|
|
205
|
-
});
|
|
206
|
-
ctx.ui.notify(formatLintNotify(report), "info");
|
|
207
|
-
},
|
|
208
|
-
});
|
|
209
|
-
// ---- /wiki-capture — capture via session LLM ----------------------------
|
|
210
|
-
pi.registerCommand("wiki-capture", {
|
|
211
|
-
description: "Capture knowledge from this session into the wiki (runs in background via daemon)",
|
|
212
|
-
handler: async (args, ctx) => {
|
|
213
|
-
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
214
|
-
if (!sessionFile) {
|
|
215
|
-
ctx.ui.notify("No session file found", "warning");
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
// Register the session for daemon capture — runs in background, doesn't block user
|
|
219
|
-
try {
|
|
220
|
-
const registry = join(wikiDir, ".completed-sessions");
|
|
221
|
-
const entry = `${new Date().toISOString()}|${sessionFile}\n`;
|
|
222
|
-
await appendFile(registry, entry);
|
|
223
|
-
ctx.ui.notify("Session queued for wiki capture (daemon will process in background).\n" +
|
|
224
|
-
"Check progress: /wiki-daemon status\n" +
|
|
225
|
-
"Tip: Set \"daemonModel\" in ~/.pi/wiki/config.json for better capture quality.", "info");
|
|
226
|
-
ctx.ui.setStatus("pi-para", "wiki: capture queued");
|
|
227
|
-
setTimeout(() => ctx.ui.setStatus("pi-para", undefined), 5000);
|
|
228
|
-
}
|
|
229
|
-
catch (err) {
|
|
230
|
-
ctx.ui.notify(`Failed to queue capture: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
});
|
|
234
|
-
// ---- /wiki-scope — show or override scope --------------------------------
|
|
235
|
-
pi.registerCommand("wiki-scope", {
|
|
236
|
-
description: "Show or override the current project scope",
|
|
237
|
-
handler: async (args, ctx) => {
|
|
238
|
-
const override = args.trim();
|
|
239
|
-
if (!override) {
|
|
240
|
-
// Show current scope
|
|
241
|
-
const scope = getScope();
|
|
242
|
-
const lines = [
|
|
243
|
-
`Scope: ${scope.name}`,
|
|
244
|
-
`Source: ${scope.source}`,
|
|
245
|
-
`Include: ${scope.include.join(", ")}`,
|
|
246
|
-
];
|
|
247
|
-
if (scope.exclude.length > 0) {
|
|
248
|
-
lines.push(`Exclude: ${scope.exclude.join(", ")}`);
|
|
249
|
-
}
|
|
250
|
-
ctx.ui.notify(lines.join("\n"), "info");
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
// Override scope for this session
|
|
254
|
-
const newScope = {
|
|
255
|
-
name: override,
|
|
256
|
-
include: [override],
|
|
257
|
-
exclude: [],
|
|
258
|
-
source: "config", // treated as explicit override
|
|
259
|
-
};
|
|
260
|
-
setScope(newScope);
|
|
261
|
-
ctx.ui.notify(`Scope overridden to: ${override}`, "info");
|
|
262
|
-
},
|
|
263
|
-
});
|
|
264
|
-
// ---- /wiki-search — quick search -----------------------------------------
|
|
265
|
-
pi.registerCommand("wiki-search", {
|
|
266
|
-
description: "Search the wiki knowledge base",
|
|
267
|
-
handler: async (args, ctx) => {
|
|
268
|
-
const query = args.trim();
|
|
269
|
-
if (!query) {
|
|
270
|
-
ctx.ui.notify("Usage: /wiki-search <query>", "error");
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
const scope = getScope();
|
|
274
|
-
const results = await searchWiki(store, query, {
|
|
275
|
-
scope,
|
|
276
|
-
limit: 10,
|
|
277
|
-
});
|
|
278
|
-
if (results.length === 0) {
|
|
279
|
-
ctx.ui.notify(`No results for "${query}"`, "info");
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
const lines = [`Search: "${query}" (${results.length} result(s))\n`];
|
|
283
|
-
for (const r of results) {
|
|
284
|
-
const fm = r.frontmatter;
|
|
285
|
-
const scorePct = (r.score * 100).toFixed(0);
|
|
286
|
-
lines.push(`[${scorePct}%] ${fm.title} (${r.page.path})`);
|
|
287
|
-
lines.push(` ${fm.para} | ${fm.scope.join(", ")} | ${fm.tags.join(", ")}`);
|
|
288
|
-
// Show first 120 chars of snippet
|
|
289
|
-
const snippet = r.snippet.replace(/\n/g, " ").trim();
|
|
290
|
-
if (snippet) {
|
|
291
|
-
lines.push(` ${snippet.length > 120 ? snippet.slice(0, 117) + "..." : snippet}`);
|
|
292
|
-
}
|
|
293
|
-
lines.push("");
|
|
294
|
-
}
|
|
295
|
-
ctx.ui.notify(lines.join("\n"), "info");
|
|
296
|
-
},
|
|
297
|
-
});
|
|
298
|
-
// ---- /wiki-summarize — summarize pages/categories/wiki -------------------
|
|
299
|
-
pi.registerCommand("wiki-summarize", {
|
|
300
|
-
description: "Summarize a page, category, or the entire wiki",
|
|
301
|
-
getArgumentCompletions: (prefix) => {
|
|
302
|
-
// Offer category names and "all" as completions
|
|
303
|
-
const options = [...PARA_CATEGORIES, "all"];
|
|
304
|
-
const filtered = options.filter((o) => o.startsWith(prefix));
|
|
305
|
-
return filtered.length > 0
|
|
306
|
-
? filtered.map((o) => ({ value: o, label: o }))
|
|
307
|
-
: null;
|
|
308
|
-
},
|
|
309
|
-
handler: async (args, ctx) => {
|
|
310
|
-
const target = args.trim() || "all";
|
|
311
|
-
await ctx.waitForIdle();
|
|
312
|
-
pi.sendUserMessage(`Summarize the wiki: target="${target}". Use wiki_summarize tool with target="${target}".`);
|
|
313
|
-
},
|
|
314
|
-
});
|
|
315
|
-
// ---- /wiki-daemon — daemon management ------------------------------------
|
|
316
|
-
pi.registerCommand("wiki-daemon", {
|
|
317
|
-
description: "Check daemon status and capture history",
|
|
318
|
-
getArgumentCompletions: (prefix) => {
|
|
319
|
-
const cmds = ["status", "history"];
|
|
320
|
-
const filtered = cmds.filter((c) => c.startsWith(prefix));
|
|
321
|
-
return filtered.length > 0 ? filtered.map((c) => ({ value: c, label: c })) : null;
|
|
322
|
-
},
|
|
323
|
-
handler: async (args, ctx) => {
|
|
324
|
-
const subcmd = args.trim().split(/\s+/)[0] || "status";
|
|
325
|
-
// Read state directly from the state DB — fast, no subprocess needed
|
|
326
|
-
const { StateDB } = await import("./state.js");
|
|
327
|
-
const state = new StateDB(wikiDir);
|
|
328
|
-
try {
|
|
329
|
-
switch (subcmd) {
|
|
330
|
-
case "status": {
|
|
331
|
-
const pid = state.getState("daemon_pid");
|
|
332
|
-
const startedAt = state.getState("daemon_started_at");
|
|
333
|
-
const history = state.getHistory(undefined, 5);
|
|
334
|
-
const failed = state.getFailed();
|
|
335
|
-
let running = false;
|
|
336
|
-
if (pid) {
|
|
337
|
-
try {
|
|
338
|
-
process.kill(parseInt(pid), 0);
|
|
339
|
-
running = true;
|
|
340
|
-
}
|
|
341
|
-
catch { }
|
|
342
|
-
}
|
|
343
|
-
const lines = [
|
|
344
|
-
`Daemon: ${running ? `running (PID ${pid})` : "not running"}`,
|
|
345
|
-
startedAt ? `Started: ${startedAt}` : "",
|
|
346
|
-
`Failed: ${failed.length}`,
|
|
347
|
-
"",
|
|
348
|
-
"Recent captures:",
|
|
349
|
-
];
|
|
350
|
-
if (history.length === 0) {
|
|
351
|
-
lines.push(" (none)");
|
|
352
|
-
}
|
|
353
|
-
else {
|
|
354
|
-
for (const h of history) {
|
|
355
|
-
const status = h.error
|
|
356
|
-
? `ERROR: ${h.error.slice(0, 50)}`
|
|
357
|
-
: h.pagesCreated.length > 0
|
|
358
|
-
? `${h.pagesCreated.length} page(s)`
|
|
359
|
-
: "skipped";
|
|
360
|
-
lines.push(` ${h.processedAt.slice(0, 19)} | ${h.scope} | ${status}`);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
ctx.ui.notify(lines.filter(Boolean).join("\n"), "info");
|
|
364
|
-
break;
|
|
365
|
-
}
|
|
366
|
-
case "history": {
|
|
367
|
-
const scope = getScope();
|
|
368
|
-
const history = state.getHistory(scope.name, 15);
|
|
369
|
-
if (history.length === 0) {
|
|
370
|
-
ctx.ui.notify(`No capture history for scope: ${scope.name}`, "info");
|
|
371
|
-
break;
|
|
372
|
-
}
|
|
373
|
-
const lines = [`Capture history (${scope.name}):\n`];
|
|
374
|
-
for (const h of history) {
|
|
375
|
-
const status = h.error
|
|
376
|
-
? `ERROR: ${h.error.slice(0, 60)}`
|
|
377
|
-
: h.pagesCreated.length > 0
|
|
378
|
-
? `${h.pagesCreated.length} page(s): ${h.pagesCreated.join(", ")}`
|
|
379
|
-
: "skipped";
|
|
380
|
-
lines.push(`${h.processedAt.slice(0, 19)} | ${status}`);
|
|
381
|
-
}
|
|
382
|
-
ctx.ui.notify(lines.join("\n"), "info");
|
|
383
|
-
break;
|
|
384
|
-
}
|
|
385
|
-
default:
|
|
386
|
-
ctx.ui.notify(`Usage: /wiki-daemon [status|history]`, "error");
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
finally {
|
|
390
|
-
state.close();
|
|
391
|
-
}
|
|
392
|
-
},
|
|
393
|
-
});
|
|
394
|
-
// ---- /wiki-migrate — batch schema migration ------------------------------
|
|
395
|
-
pi.registerCommand("wiki-migrate", {
|
|
396
|
-
description: "Migrate all wiki pages to the current schema version",
|
|
397
|
-
handler: async (_args, ctx) => {
|
|
398
|
-
ctx.ui.notify(`Migrating wiki pages to schema version ${CURRENT_SCHEMA_VERSION}...`, "info");
|
|
399
|
-
const allPages = await listPages(wikiDir);
|
|
400
|
-
let migratedCount = 0;
|
|
401
|
-
for (const ref of allPages) {
|
|
402
|
-
const page = await readPage(wikiDir, ref.category, ref.slug);
|
|
403
|
-
if (!page)
|
|
404
|
-
continue;
|
|
405
|
-
const version = page.frontmatter.schemaVersion ?? 1;
|
|
406
|
-
if (version >= CURRENT_SCHEMA_VERSION)
|
|
407
|
-
continue;
|
|
408
|
-
const rawObj = page.frontmatter;
|
|
409
|
-
const result = migrateToLatest(rawObj, page.body);
|
|
410
|
-
const migratedFm = result.fm;
|
|
411
|
-
migratedFm.updated = new Date().toISOString();
|
|
412
|
-
await writePage(wikiDir, {
|
|
413
|
-
category: ref.category,
|
|
414
|
-
slug: ref.slug,
|
|
415
|
-
frontmatter: migratedFm,
|
|
416
|
-
body: result.body,
|
|
417
|
-
});
|
|
418
|
-
migratedCount++;
|
|
419
|
-
}
|
|
420
|
-
ctx.ui.notify(migratedCount > 0
|
|
421
|
-
? `Migrated ${migratedCount} page(s) to schema version ${CURRENT_SCHEMA_VERSION}.`
|
|
422
|
-
: `All ${allPages.length} page(s) already at schema version ${CURRENT_SCHEMA_VERSION}.`, "info");
|
|
423
|
-
},
|
|
424
|
-
});
|
|
425
|
-
// ---- /wiki-project — project lifecycle management ------------------------
|
|
426
|
-
pi.registerCommand("wiki-project", {
|
|
427
|
-
description: "Create a project page (/wiki-project <name> <goal>) or archive it (/wiki-project done <name>)",
|
|
428
|
-
handler: async (args, ctx) => {
|
|
429
|
-
const parts = args.trim().split(/\s+/);
|
|
430
|
-
if (parts.length === 0 || !parts[0]) {
|
|
431
|
-
ctx.ui.notify("Usage: /wiki-project <name> <goal>\n /wiki-project done <name>", "error");
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
if (parts[0] === "done") {
|
|
435
|
-
// ---------- Archive a project ----------
|
|
436
|
-
const rawName = parts.slice(1).join("-");
|
|
437
|
-
if (!rawName) {
|
|
438
|
-
ctx.ui.notify("Usage: /wiki-project done <name>", "error");
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
const slug = toKebabSlug(rawName);
|
|
442
|
-
if (!slug) {
|
|
443
|
-
ctx.ui.notify("Invalid project name — must contain at least one alphanumeric character.", "error");
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
const page = await readPage(wikiDir, "projects", slug);
|
|
447
|
-
if (!page) {
|
|
448
|
-
ctx.ui.notify(`Project not found: projects/${slug}`, "error");
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
const ref = {
|
|
452
|
-
category: "projects",
|
|
453
|
-
slug,
|
|
454
|
-
title: page.frontmatter.title,
|
|
455
|
-
path: `projects/${slug}.md`,
|
|
456
|
-
};
|
|
457
|
-
await movePage(wikiDir, ref, "archives");
|
|
458
|
-
await rebuildIndexFromDisk(wikiDir);
|
|
459
|
-
ctx.ui.notify(`Archived project: ${page.frontmatter.title} → archives/${slug}`, "info");
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
// ---------- Create a new project ----------
|
|
463
|
-
const rawName = parts[0];
|
|
464
|
-
const goal = parts.slice(1).join(" ");
|
|
465
|
-
if (!goal) {
|
|
466
|
-
ctx.ui.notify("Usage: /wiki-project <name> <goal>\n /wiki-project done <name>", "error");
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
const slug = toKebabSlug(rawName);
|
|
470
|
-
if (!slug) {
|
|
471
|
-
ctx.ui.notify("Invalid project name — must contain at least one alphanumeric character.", "error");
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
const title = slugToTitle(slug);
|
|
475
|
-
const scope = getScope();
|
|
476
|
-
const now = new Date().toISOString();
|
|
477
|
-
const body = `# ${title}
|
|
478
|
-
|
|
479
|
-
## Goal
|
|
480
|
-
${goal}
|
|
481
|
-
|
|
482
|
-
## Status
|
|
483
|
-
- [ ] Define scope and milestones
|
|
484
|
-
- [ ] Implementation
|
|
485
|
-
- [ ] Verification
|
|
486
|
-
|
|
487
|
-
## End Condition
|
|
488
|
-
${goal} — verified and complete.
|
|
489
|
-
|
|
490
|
-
## Connections
|
|
491
|
-
(add related wiki pages here)`;
|
|
492
|
-
const frontmatter = validateFrontmatter({
|
|
493
|
-
title,
|
|
494
|
-
para: "projects",
|
|
495
|
-
scope: [scope.name],
|
|
496
|
-
tags: [],
|
|
497
|
-
sources: [],
|
|
498
|
-
created: now,
|
|
499
|
-
updated: now,
|
|
500
|
-
links: [],
|
|
501
|
-
});
|
|
502
|
-
await writePage(wikiDir, {
|
|
503
|
-
category: "projects",
|
|
504
|
-
slug,
|
|
505
|
-
frontmatter,
|
|
506
|
-
body,
|
|
507
|
-
});
|
|
508
|
-
await rebuildIndexFromDisk(wikiDir);
|
|
509
|
-
ctx.ui.notify(`Created project: projects/${slug} — "${title}"`, "info");
|
|
510
|
-
},
|
|
511
|
-
});
|
|
512
|
-
// ---- /wiki-settings — interactive configuration --------------------------
|
|
513
|
-
pi.registerCommand("wiki-settings", {
|
|
514
|
-
description: "View and edit wiki configuration",
|
|
515
|
-
handler: async (_args, ctx) => {
|
|
516
|
-
if (!ctx.hasUI) {
|
|
517
|
-
ctx.ui.notify("/wiki-settings requires interactive mode", "error");
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
const { writeFile: writeFileAsync } = await import("node:fs/promises");
|
|
521
|
-
const { parse, stringify } = await import("yaml");
|
|
522
|
-
const { homedir } = await import("node:os");
|
|
523
|
-
const { execSync } = await import("node:child_process");
|
|
524
|
-
// Load current config
|
|
525
|
-
const configPath = join(wikiDir, "config.json");
|
|
526
|
-
let config = {};
|
|
527
|
-
try {
|
|
528
|
-
config = JSON.parse(await readFile(configPath, "utf-8"));
|
|
529
|
-
}
|
|
530
|
-
catch { /* defaults */ }
|
|
531
|
-
// Load qmd config
|
|
532
|
-
const qmdPath = join(homedir(), ".config", "qmd", "index.yml");
|
|
533
|
-
let qmdConfig = {};
|
|
534
|
-
try {
|
|
535
|
-
qmdConfig = parse(await readFile(qmdPath, "utf-8")) ?? {};
|
|
536
|
-
}
|
|
537
|
-
catch { /* no qmd config */ }
|
|
538
|
-
const providers = (qmdConfig.providers ?? {});
|
|
539
|
-
// Check statuses
|
|
540
|
-
let qmdVersion = "";
|
|
541
|
-
try {
|
|
542
|
-
qmdVersion = execSync("qmd --version 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
543
|
-
}
|
|
544
|
-
catch { }
|
|
545
|
-
let daemonStatus = "not running";
|
|
546
|
-
try {
|
|
547
|
-
daemonStatus = execSync("systemctl --user is-active pi-para-daemon 2>/dev/null", { encoding: "utf-8" }).trim();
|
|
548
|
-
}
|
|
549
|
-
catch { }
|
|
550
|
-
// Main menu loop
|
|
551
|
-
while (true) {
|
|
552
|
-
const scope = getScope();
|
|
553
|
-
const pageCount = (await listPages(wikiDir).catch(() => [])).length;
|
|
554
|
-
const choice = await ctx.ui.select("Wiki Settings", [
|
|
555
|
-
`[Context] Max tokens: ${config.contextMaxTokens ?? 4000}`,
|
|
556
|
-
`[Search] Limit: ${config.searchLimit ?? 10}, Graph boost: ${config.searchGraphBoost ?? true}`,
|
|
557
|
-
`[Lint] Auto-fix: ${config.lintAutoFix ?? true}, Stale days: ${config.lintStaleDays ?? 90}`,
|
|
558
|
-
`[Daemon] Model: ${config.daemonModel ?? "auto"}`,
|
|
559
|
-
`[WebWiki] ${config.webWiki?.enabled ? `Enabled at http://${config.webWiki?.host ?? "0.0.0.0"}:${config.webWiki?.port ?? 10973}` : "Disabled"}`,
|
|
560
|
-
`[Embed] ${providers.embed?.model ?? "not configured"} ${providers.embed?.url ? "at " + providers.embed.url : ""}`,
|
|
561
|
-
`[Chat] ${providers.chat?.model ?? "not configured"} ${providers.chat?.url ? "at " + providers.chat.url : ""}`,
|
|
562
|
-
`[Rerank] ${providers.rerank?.model ?? "not configured"} ${providers.rerank?.url ? "at " + providers.rerank.url : ""}`,
|
|
563
|
-
`---`,
|
|
564
|
-
`[Status] Scope: ${scope.name} | Pages: ${pageCount} | qmd: ${qmdVersion || "not installed"} | Daemon: ${daemonStatus}`,
|
|
565
|
-
]);
|
|
566
|
-
if (!choice)
|
|
567
|
-
break;
|
|
568
|
-
if (choice.startsWith("[Context]")) {
|
|
569
|
-
const val = await ctx.ui.input("Context max tokens:", String(config.contextMaxTokens ?? 4000));
|
|
570
|
-
if (val) {
|
|
571
|
-
config.contextMaxTokens = parseInt(val) || 4000;
|
|
572
|
-
await writeFileAsync(configPath, JSON.stringify(config, null, 2));
|
|
573
|
-
ctx.ui.notify(`Set contextMaxTokens = ${config.contextMaxTokens}`, "info");
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
else if (choice.startsWith("[Search]")) {
|
|
577
|
-
const sub = await ctx.ui.select("Search Settings", [
|
|
578
|
-
`Limit: ${config.searchLimit ?? 10}`,
|
|
579
|
-
`Graph boost: ${config.searchGraphBoost ?? true}`,
|
|
580
|
-
]);
|
|
581
|
-
if (sub?.startsWith("Limit")) {
|
|
582
|
-
const val = await ctx.ui.input("Search result limit:", String(config.searchLimit ?? 10));
|
|
583
|
-
if (val) {
|
|
584
|
-
config.searchLimit = parseInt(val) || 10;
|
|
585
|
-
await writeFileAsync(configPath, JSON.stringify(config, null, 2));
|
|
586
|
-
ctx.ui.notify(`Set searchLimit = ${config.searchLimit}`, "info");
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
else if (sub?.startsWith("Graph boost")) {
|
|
590
|
-
config.searchGraphBoost = !(config.searchGraphBoost ?? true);
|
|
591
|
-
await writeFileAsync(configPath, JSON.stringify(config, null, 2));
|
|
592
|
-
ctx.ui.notify(`Set searchGraphBoost = ${config.searchGraphBoost}`, "info");
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
else if (choice.startsWith("[Lint]")) {
|
|
596
|
-
const sub = await ctx.ui.select("Lint Settings", [
|
|
597
|
-
`Auto-fix: ${config.lintAutoFix ?? true}`,
|
|
598
|
-
`Stale days: ${config.lintStaleDays ?? 90}`,
|
|
599
|
-
]);
|
|
600
|
-
if (sub?.startsWith("Auto-fix")) {
|
|
601
|
-
config.lintAutoFix = !(config.lintAutoFix ?? true);
|
|
602
|
-
await writeFileAsync(configPath, JSON.stringify(config, null, 2));
|
|
603
|
-
ctx.ui.notify(`Set lintAutoFix = ${config.lintAutoFix}`, "info");
|
|
604
|
-
}
|
|
605
|
-
else if (sub?.startsWith("Stale")) {
|
|
606
|
-
const val = await ctx.ui.input("Stale days threshold:", String(config.lintStaleDays ?? 90));
|
|
607
|
-
if (val) {
|
|
608
|
-
config.lintStaleDays = parseInt(val) || 90;
|
|
609
|
-
await writeFileAsync(configPath, JSON.stringify(config, null, 2));
|
|
610
|
-
ctx.ui.notify(`Set lintStaleDays = ${config.lintStaleDays}`, "info");
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
else if (choice.startsWith("[Daemon]")) {
|
|
615
|
-
try {
|
|
616
|
-
const { getProviders, getModels } = await import("@mariozechner/pi-ai");
|
|
617
|
-
const { AuthStorage } = await import("@mariozechner/pi-coding-agent");
|
|
618
|
-
const authStorage = AuthStorage.create();
|
|
619
|
-
// Find providers that have auth configured
|
|
620
|
-
const allProviders = getProviders();
|
|
621
|
-
const availableProviders = [];
|
|
622
|
-
for (const p of allProviders) {
|
|
623
|
-
if (authStorage.hasAuth(p))
|
|
624
|
-
availableProviders.push(p);
|
|
625
|
-
}
|
|
626
|
-
if (availableProviders.length === 0) {
|
|
627
|
-
ctx.ui.notify("No providers configured. Log in with pi first.", "warning");
|
|
628
|
-
}
|
|
629
|
-
else {
|
|
630
|
-
// Let user pick provider
|
|
631
|
-
const providerChoice = await ctx.ui.select("Select provider", ["auto (best available)", ...availableProviders]);
|
|
632
|
-
if (providerChoice === "auto (best available)") {
|
|
633
|
-
config.daemonModel = null;
|
|
634
|
-
await writeFileAsync(configPath, JSON.stringify(config, null, 2));
|
|
635
|
-
try {
|
|
636
|
-
execSync("systemctl --user restart pi-para-daemon 2>/dev/null");
|
|
637
|
-
}
|
|
638
|
-
catch { }
|
|
639
|
-
ctx.ui.notify("Set daemonModel = auto (daemon restarted)", "info");
|
|
640
|
-
}
|
|
641
|
-
else if (providerChoice) {
|
|
642
|
-
// Show models for selected provider
|
|
643
|
-
const models = getModels(providerChoice)
|
|
644
|
-
.sort((a, b) => (b.contextWindow ?? 0) - (a.contextWindow ?? 0));
|
|
645
|
-
const modelOptions = models.map((m) => `${m.id} (${Math.round((m.contextWindow ?? 0) / 1000)}k ctx${m.reasoning ? ", reasoning" : ""})`);
|
|
646
|
-
const modelChoice = await ctx.ui.select(`Select ${providerChoice} model`, modelOptions);
|
|
647
|
-
if (modelChoice) {
|
|
648
|
-
const modelId = modelChoice.split(" (")[0];
|
|
649
|
-
config.daemonModel = `${providerChoice}/${modelId}`;
|
|
650
|
-
await writeFileAsync(configPath, JSON.stringify(config, null, 2));
|
|
651
|
-
try {
|
|
652
|
-
execSync("systemctl --user restart pi-para-daemon 2>/dev/null");
|
|
653
|
-
}
|
|
654
|
-
catch { }
|
|
655
|
-
ctx.ui.notify(`Set daemonModel = ${config.daemonModel} (daemon restarted)`, "info");
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
catch (err) {
|
|
661
|
-
// Fallback to manual input if AuthStorage not available
|
|
662
|
-
const val = await ctx.ui.input("Daemon model (provider/model-id, or empty for auto):", config.daemonModel ? String(config.daemonModel) : "");
|
|
663
|
-
config.daemonModel = val?.trim() || null;
|
|
664
|
-
await writeFileAsync(configPath, JSON.stringify(config, null, 2));
|
|
665
|
-
try {
|
|
666
|
-
const { execSync: exec } = await import("node:child_process");
|
|
667
|
-
exec("systemctl --user restart pi-para-daemon 2>/dev/null");
|
|
668
|
-
}
|
|
669
|
-
catch { }
|
|
670
|
-
ctx.ui.notify(`Set daemonModel = ${config.daemonModel ?? "auto"} (daemon restarted)`, "info");
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
else if (choice.startsWith("[WebWiki]")) {
|
|
674
|
-
const webWiki = config.webWiki ?? { enabled: false, host: "0.0.0.0", port: 10973 };
|
|
675
|
-
const sub = await ctx.ui.select("Web Wiki Settings", [
|
|
676
|
-
`Enabled: ${webWiki.enabled}`,
|
|
677
|
-
`Host: ${webWiki.host}`,
|
|
678
|
-
`Port: ${webWiki.port}`,
|
|
679
|
-
]);
|
|
680
|
-
if (sub?.startsWith("Enabled")) {
|
|
681
|
-
webWiki.enabled = !webWiki.enabled;
|
|
682
|
-
config.webWiki = webWiki;
|
|
683
|
-
await writeFileAsync(configPath, JSON.stringify(config, null, 2));
|
|
684
|
-
if (webWiki.enabled) {
|
|
685
|
-
// Detect LAN IP
|
|
686
|
-
const { networkInterfaces } = await import("node:os");
|
|
687
|
-
const nets = networkInterfaces();
|
|
688
|
-
let lanIp = webWiki.host;
|
|
689
|
-
for (const ifaces of Object.values(nets)) {
|
|
690
|
-
for (const iface of ifaces ?? []) {
|
|
691
|
-
if (iface.family === "IPv4" && !iface.internal) {
|
|
692
|
-
lanIp = iface.address;
|
|
693
|
-
break;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
ctx.ui.notify(`Web Wiki enabled at http://${lanIp}:${webWiki.port}\nRestart pi to apply.`, "info");
|
|
698
|
-
}
|
|
699
|
-
else {
|
|
700
|
-
ctx.ui.notify("Web Wiki disabled. Restart pi to apply.", "info");
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
else if (sub?.startsWith("Host")) {
|
|
704
|
-
const val = await ctx.ui.input("Host (0.0.0.0 for LAN, 127.0.0.1 for local only):", webWiki.host);
|
|
705
|
-
if (val) {
|
|
706
|
-
webWiki.host = val;
|
|
707
|
-
config.webWiki = webWiki;
|
|
708
|
-
await writeFileAsync(configPath, JSON.stringify(config, null, 2));
|
|
709
|
-
ctx.ui.notify(`Set webWiki.host = ${webWiki.host}`, "info");
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
else if (sub?.startsWith("Port")) {
|
|
713
|
-
const val = await ctx.ui.input("Port:", String(webWiki.port));
|
|
714
|
-
if (val) {
|
|
715
|
-
webWiki.port = parseInt(val) || 10973;
|
|
716
|
-
config.webWiki = webWiki;
|
|
717
|
-
await writeFileAsync(configPath, JSON.stringify(config, null, 2));
|
|
718
|
-
ctx.ui.notify(`Set webWiki.port = ${webWiki.port}`, "info");
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
else if (choice.startsWith("[Embed]") || choice.startsWith("[Chat]") || choice.startsWith("[Rerank]")) {
|
|
723
|
-
const providerType = choice.startsWith("[Embed]") ? "embed" : choice.startsWith("[Chat]") ? "chat" : "rerank";
|
|
724
|
-
const current = providers[providerType] ?? {};
|
|
725
|
-
const sub = await ctx.ui.select(`${providerType} provider`, [
|
|
726
|
-
`URL: ${current.url ?? "not set"}`,
|
|
727
|
-
`Key: ${current.key ? current.key.slice(0, 8) + "..." : "not set"}`,
|
|
728
|
-
`Model: ${current.model ?? "not set"}`,
|
|
729
|
-
...(providerType === "embed" ? [`Dims: ${current.dims ?? "auto"}`] : []),
|
|
730
|
-
...(providerType === "chat" ? [`API format: ${current.api ?? "openai"}`] : []),
|
|
731
|
-
`Remove this provider`,
|
|
732
|
-
]);
|
|
733
|
-
if (sub?.startsWith("URL")) {
|
|
734
|
-
const val = await ctx.ui.input(`${providerType} URL:`, current.url ?? "");
|
|
735
|
-
if (val) {
|
|
736
|
-
current.url = val;
|
|
737
|
-
providers[providerType] = current;
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
else if (sub?.startsWith("Key")) {
|
|
741
|
-
const val = await ctx.ui.input(`${providerType} API key:`, "");
|
|
742
|
-
if (val) {
|
|
743
|
-
current.key = val;
|
|
744
|
-
providers[providerType] = current;
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
else if (sub?.startsWith("Model")) {
|
|
748
|
-
const val = await ctx.ui.input(`${providerType} model:`, current.model ?? "");
|
|
749
|
-
if (val) {
|
|
750
|
-
current.model = val;
|
|
751
|
-
providers[providerType] = current;
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
else if (sub?.startsWith("Dims")) {
|
|
755
|
-
const val = await ctx.ui.input("Embedding dimensions:", current.dims ?? "");
|
|
756
|
-
if (val) {
|
|
757
|
-
current.dims = val;
|
|
758
|
-
providers[providerType] = current;
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
else if (sub?.startsWith("API format")) {
|
|
762
|
-
const fmt = await ctx.ui.select("API format", ["openai", "anthropic"]);
|
|
763
|
-
if (fmt) {
|
|
764
|
-
current.api = fmt;
|
|
765
|
-
providers[providerType] = current;
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
else if (sub?.startsWith("Remove")) {
|
|
769
|
-
delete providers[providerType];
|
|
770
|
-
}
|
|
771
|
-
// Save qmd config
|
|
772
|
-
qmdConfig.providers = providers;
|
|
773
|
-
const { mkdirSync } = await import("node:fs");
|
|
774
|
-
mkdirSync(join(homedir(), ".config", "qmd"), { recursive: true });
|
|
775
|
-
await writeFileAsync(qmdPath, stringify(qmdConfig));
|
|
776
|
-
ctx.ui.notify(`Updated ${qmdPath}`, "info");
|
|
777
|
-
}
|
|
778
|
-
else if (choice.startsWith("---") || choice.startsWith("[Status]")) {
|
|
779
|
-
// Status row — just show, don't edit
|
|
780
|
-
continue;
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
},
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
//# sourceMappingURL=commands.js.map
|