pi-para 0.6.7 → 0.7.1
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 +4 -6
- package/CHANGELOG.md +19 -0
- package/CONFIGURATION.md +1 -16
- package/README.md +2 -13
- package/SETUP.md +1 -7
- package/STRUCTURE.md +7 -18
- package/dist/cli-legacy.d.ts +2 -1
- package/dist/cli-legacy.d.ts.map +1 -1
- package/dist/cli-legacy.js +13 -25
- package/dist/cli-legacy.js.map +1 -1
- package/dist/cli.d.ts +2 -24
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +29 -271
- package/dist/cli.js.map +1 -1
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +1 -28
- package/dist/commands.js.map +1 -1
- package/dist/config.d.ts +1 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -5
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -64
- package/dist/index.js.map +1 -1
- package/dist/link-utils.d.ts +1 -1
- package/dist/link-utils.js +1 -1
- package/dist/processor.d.ts +7 -0
- package/dist/processor.d.ts.map +1 -1
- package/dist/processor.js +29 -15
- package/dist/processor.js.map +1 -1
- package/dist/settings.d.ts +0 -3
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +0 -11
- package/dist/settings.js.map +1 -1
- package/dist/tools/edit-read.d.ts +18 -0
- package/dist/tools/edit-read.d.ts.map +1 -0
- package/dist/tools/edit-read.js +161 -0
- package/dist/tools/edit-read.js.map +1 -0
- package/dist/tools/ingest.d.ts +12 -0
- package/dist/tools/ingest.d.ts.map +1 -0
- package/dist/tools/ingest.js +48 -0
- package/dist/tools/ingest.js.map +1 -0
- package/dist/tools/lint.d.ts +10 -0
- package/dist/tools/lint.d.ts.map +1 -0
- package/dist/tools/lint.js +57 -0
- package/dist/tools/lint.js.map +1 -0
- package/dist/tools/migrate.d.ts +6 -0
- package/dist/tools/migrate.d.ts.map +1 -0
- package/dist/tools/migrate.js +36 -0
- package/dist/tools/migrate.js.map +1 -0
- package/dist/tools/move.d.ts +9 -0
- package/dist/tools/move.d.ts.map +1 -0
- package/dist/tools/move.js +61 -0
- package/dist/tools/move.js.map +1 -0
- package/dist/tools/query.d.ts +12 -0
- package/dist/tools/query.d.ts.map +1 -0
- package/dist/tools/query.js +47 -0
- package/dist/tools/query.js.map +1 -0
- package/dist/tools/schemas.d.ts +109 -0
- package/dist/tools/schemas.d.ts.map +1 -0
- package/dist/tools/schemas.js +80 -0
- package/dist/tools/schemas.js.map +1 -0
- package/dist/tools/shared.d.ts +14 -0
- package/dist/tools/shared.d.ts.map +1 -0
- package/dist/tools/shared.js +25 -0
- package/dist/tools/shared.js.map +1 -0
- package/dist/tools/summarize.d.ts +8 -0
- package/dist/tools/summarize.d.ts.map +1 -0
- package/dist/tools/summarize.js +82 -0
- package/dist/tools/summarize.js.map +1 -0
- package/dist/tools/write.d.ts +23 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +215 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/tools.d.ts +1 -8
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +13 -795
- package/dist/tools.js.map +1 -1
- package/dist/wiki.d.ts.map +1 -1
- package/dist/wiki.js +1 -0
- package/dist/wiki.js.map +1 -1
- package/package.json +2 -6
- package/src/cli-legacy.ts +14 -26
- package/src/cli.ts +29 -293
- package/src/commands.ts +0 -21
- package/src/config.ts +1 -6
- package/src/index.ts +5 -66
- package/src/link-utils.ts +1 -1
- package/src/processor.ts +28 -13
- package/src/settings.ts +0 -14
- package/src/tools/edit-read.ts +193 -0
- package/src/tools/ingest.ts +63 -0
- package/src/tools/lint.ts +74 -0
- package/src/tools/migrate.ts +44 -0
- package/src/tools/move.ts +79 -0
- package/src/tools/query.ts +63 -0
- package/src/tools/schemas.ts +157 -0
- package/src/tools/shared.ts +35 -0
- package/src/tools/summarize.ts +89 -0
- package/src/tools/write.ts +252 -0
- package/src/tools.ts +18 -1046
- package/src/wiki.ts +1 -0
- 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/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/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/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/scripts/build-assets.mjs +0 -17
- package/src/daemon.ts +0 -288
- package/src/link-discovery.ts +0 -170
- package/src/maintainer.ts +0 -404
- package/src/python/__pycache__/minimax_lm.cpython-312.pyc +0 -0
- package/src/python/minimax_lm.py +0 -198
- package/src/python/rlm_capture.py +0 -215
- package/src/watcher.ts +0 -101
- package/src/webui/client/dist/index.html +0 -1885
- package/src/webui/server.ts +0 -765
package/src/cli.ts
CHANGED
|
@@ -2,137 +2,35 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* CLI for pi-para.
|
|
4
4
|
*
|
|
5
|
-
* The
|
|
5
|
+
* The pi-para-daemon binary remains only to report the legacy daemon removal.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import {
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
|
-
import {
|
|
11
|
+
import { realpathSync } from "node:fs";
|
|
12
|
+
import { formatLegacyDaemonRemoval, isLegacyDaemonBinary, isLegacyDaemonCommand } from "./cli-legacy.js";
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
import { StateDB } from "./state.js";
|
|
15
|
-
import { RegistryWatcher } from "./watcher.js";
|
|
16
|
-
import { formatLegacyDaemonWarning, isLegacyDaemonCommand } from "./cli-legacy.js";
|
|
17
|
-
|
|
18
|
-
// -- Model setup -------------------------------------------------------------
|
|
19
|
-
// Use MiniMax via the same config as qmd-engine
|
|
20
|
-
|
|
21
|
-
export async function createModel(modelArg?: string) {
|
|
22
|
-
const { parse } = await import("yaml");
|
|
23
|
-
const { readFileSync } = await import("node:fs");
|
|
24
|
-
const { getProviders, getEnvApiKey } = await import("@earendil-works/pi-ai/compat");
|
|
25
|
-
const { createPiModelServices, parseProviderModelSpec } = await import("./model-resolver.js");
|
|
26
|
-
const { loadParaConfig } = await import("./config.js");
|
|
27
|
-
const { resolveCredentialRef } = await import("./credentials.js");
|
|
28
|
-
|
|
29
|
-
if (!modelArg) {
|
|
30
|
-
try {
|
|
31
|
-
const configPath = join(homedir(), ".pi", "wiki", "config.json");
|
|
32
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
33
|
-
if (config.daemonModel) {
|
|
34
|
-
modelArg = config.daemonModel;
|
|
35
|
-
console.log(`[daemon] Using model from config.json: ${modelArg}`);
|
|
36
|
-
}
|
|
37
|
-
} catch {
|
|
38
|
-
// No legacy daemon model configured.
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const services = await createPiModelServices();
|
|
43
|
-
let configuredCapture: Awaited<ReturnType<typeof loadParaConfig>> | null = null;
|
|
44
|
-
try { configuredCapture = await loadParaConfig(); } catch { /* optional fallback */ }
|
|
45
|
-
const configuredSelection = configuredCapture?.config.models.capture;
|
|
46
|
-
if (!modelArg && configuredSelection && configuredSelection !== "auto" && configuredSelection.model) {
|
|
47
|
-
modelArg = `${configuredSelection.provider}/${configuredSelection.model}`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const configuredSecret = async (provider: string): Promise<string | undefined> => {
|
|
51
|
-
const selection = configuredCapture?.config.models.capture;
|
|
52
|
-
if (!selection || selection === "auto" || selection.provider !== provider || !selection.credentialRef.startsWith("secret:")) return undefined;
|
|
53
|
-
const resolved = await resolveCredentialRef(selection.credentialRef, { secretsPath: configuredCapture?.paths.secretsPath });
|
|
54
|
-
return resolved.ok ? resolved.value : undefined;
|
|
55
|
-
};
|
|
56
|
-
const getDurableKey = async (provider: string): Promise<string | undefined> =>
|
|
57
|
-
await services?.credentials.getApiKey(provider) ?? await configuredSecret(provider);
|
|
58
|
-
|
|
59
|
-
if (modelArg && !services) {
|
|
60
|
-
console.error("Error: Pi credential runtime is unavailable.");
|
|
61
|
-
process.exit(1);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (modelArg && services) {
|
|
65
|
-
const spec = parseProviderModelSpec(modelArg);
|
|
66
|
-
const model = spec ? services.modelRegistry.find(spec.provider, spec.modelId) : undefined;
|
|
67
|
-
if (model && model.provider !== "node-llama-cpp" && model.provider !== "local") {
|
|
68
|
-
const key = await getDurableKey(model.provider) ?? getEnvApiKey(model.provider);
|
|
69
|
-
if (key) {
|
|
70
|
-
const getApiKey = async (provider: string) => await getDurableKey(provider) ?? getEnvApiKey(provider);
|
|
71
|
-
console.log(`[daemon] Using pi model: ${model.provider}/${model.id} (context: ${model.contextWindow})`);
|
|
72
|
-
return { model, getApiKey };
|
|
73
|
-
}
|
|
74
|
-
console.error(`Error: No credential available for Pi model: ${modelArg}`);
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
console.error(`Error: Unknown Pi model: ${modelArg}`);
|
|
78
|
-
process.exit(1);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (services) {
|
|
82
|
-
const preferredProviders = ["anthropic", "openai", "openrouter", "google-antigravity", "github-copilot"];
|
|
83
|
-
for (const provider of preferredProviders) {
|
|
84
|
-
if (!services.credentials.hasStoredCredential(provider) && !await configuredSecret(provider)) continue;
|
|
85
|
-
const models = services.modelRegistry.getAll().filter((model) => model.provider === provider && model.provider !== "node-llama-cpp" && model.provider !== "local");
|
|
86
|
-
const sorted = [...models].sort((a, b) => (b.contextWindow ?? 0) - (a.contextWindow ?? 0));
|
|
87
|
-
const picked = sorted.find((model) => !model.reasoning) ?? sorted[0];
|
|
88
|
-
if (picked) {
|
|
89
|
-
const getApiKey = async (requestedProvider: string) => await getDurableKey(requestedProvider) ?? getEnvApiKey(requestedProvider);
|
|
90
|
-
console.log(`[daemon] Using persisted Pi auth: ${provider}/${picked.id} (context: ${picked.contextWindow})`);
|
|
91
|
-
return { model: picked, getApiKey };
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
14
|
+
// -- CLI ---------------------------------------------------------------------
|
|
95
15
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (picked) return { model: picked, getApiKey: async (requestedProvider: string) => getEnvApiKey(requestedProvider) };
|
|
16
|
+
async function resolveWikiDir(): Promise<string> {
|
|
17
|
+
const defaultWikiDir = join(homedir(), ".pi", "wiki");
|
|
18
|
+
try {
|
|
19
|
+
const { loadParaConfig } = await import("./config.js");
|
|
20
|
+
return (await loadParaConfig()).paths.wikiDir;
|
|
21
|
+
} catch {
|
|
22
|
+
return defaultWikiDir;
|
|
104
23
|
}
|
|
105
|
-
|
|
106
|
-
// Preserve the legacy CLI's QMD compatibility fallback.
|
|
107
|
-
const configPath = join(homedir(), ".config", "qmd", "index.yml");
|
|
108
|
-
if (existsSync(configPath)) {
|
|
109
|
-
const cfg = parse(readFileSync(configPath, "utf-8"));
|
|
110
|
-
const chat = cfg?.providers?.chat;
|
|
111
|
-
if (chat?.url && chat?.key) {
|
|
112
|
-
const model = {
|
|
113
|
-
id: chat.model || "MiniMax-M2.7-highspeed", name: chat.model || "MiniMax-M2.7-highspeed", provider: "custom",
|
|
114
|
-
api: chat.api === "anthropic" ? "anthropic-messages" as const : "openai-completions" as const,
|
|
115
|
-
baseUrl: chat.url, reasoning: false, input: ["text" as const],
|
|
116
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 196000, maxTokens: 8192,
|
|
117
|
-
};
|
|
118
|
-
return { model, getApiKey: async (_provider: string) => chat.key as string };
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
console.error("Error: No LLM available. Configure persisted Pi auth or ~/.pi/para/secrets.json (legacy env/QMD configuration is also supported).");
|
|
123
|
-
process.exit(1);
|
|
124
24
|
}
|
|
125
25
|
|
|
126
|
-
// -- CLI ---------------------------------------------------------------------
|
|
127
|
-
|
|
128
26
|
async function main() {
|
|
129
27
|
const args = process.argv.slice(2);
|
|
130
28
|
const command = args[0];
|
|
131
|
-
const wikiDir = join(homedir(), ".pi", "wiki");
|
|
132
29
|
|
|
133
|
-
if (isLegacyDaemonCommand(command)) {
|
|
134
|
-
console.
|
|
135
|
-
|
|
30
|
+
if (isLegacyDaemonBinary(process.argv[1]) || isLegacyDaemonCommand(command)) {
|
|
31
|
+
console.error(formatLegacyDaemonRemoval(command ?? "pi-para-daemon"));
|
|
32
|
+
process.exitCode = 1;
|
|
33
|
+
return;
|
|
136
34
|
}
|
|
137
35
|
|
|
138
36
|
switch (command) {
|
|
@@ -158,6 +56,7 @@ async function main() {
|
|
|
158
56
|
}
|
|
159
57
|
|
|
160
58
|
case "tasks": {
|
|
59
|
+
const wikiDir = await resolveWikiDir();
|
|
161
60
|
const sub = args[1] ?? "list";
|
|
162
61
|
const {
|
|
163
62
|
listSchedulerTasks,
|
|
@@ -192,6 +91,7 @@ async function main() {
|
|
|
192
91
|
}
|
|
193
92
|
|
|
194
93
|
case "capture-recent": {
|
|
94
|
+
const wikiDir = await resolveWikiDir();
|
|
195
95
|
const hoursIdx = args.indexOf("--hours");
|
|
196
96
|
const hours = hoursIdx >= 0 ? parseInt(args[hoursIdx + 1] ?? "24") : 24;
|
|
197
97
|
const { queueCaptureRecent, formatQueueCaptureRecentResult } = await import("./scheduler/controls.js");
|
|
@@ -226,64 +126,6 @@ async function main() {
|
|
|
226
126
|
break;
|
|
227
127
|
}
|
|
228
128
|
|
|
229
|
-
case "start": {
|
|
230
|
-
const modelIdx = args.indexOf("--model");
|
|
231
|
-
const modelArg = modelIdx >= 0 ? args[modelIdx + 1] : undefined;
|
|
232
|
-
const { model, getApiKey } = await createModel(modelArg);
|
|
233
|
-
|
|
234
|
-
// Load web wiki config
|
|
235
|
-
let webWiki: { enabled: boolean; host: string; port: number } | undefined;
|
|
236
|
-
try {
|
|
237
|
-
const { readFileSync } = await import("node:fs");
|
|
238
|
-
const configPath = join(homedir(), ".pi", "wiki", "config.json");
|
|
239
|
-
const raw = readFileSync(configPath, "utf-8");
|
|
240
|
-
const config = JSON.parse(raw);
|
|
241
|
-
if (config.webWiki?.enabled) {
|
|
242
|
-
webWiki = {
|
|
243
|
-
enabled: true,
|
|
244
|
-
host: config.webWiki.host ?? "0.0.0.0",
|
|
245
|
-
port: config.webWiki.port ?? 10973,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
} catch {
|
|
249
|
-
// No config or parse error — skip web wiki
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const daemon = new Daemon({ wikiDir, model: model as any, getApiKey, webWiki });
|
|
253
|
-
|
|
254
|
-
process.on("SIGINT", async () => {
|
|
255
|
-
await daemon.stop();
|
|
256
|
-
process.exit(0);
|
|
257
|
-
});
|
|
258
|
-
process.on("SIGTERM", async () => {
|
|
259
|
-
await daemon.stop();
|
|
260
|
-
process.exit(0);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
await daemon.start();
|
|
264
|
-
|
|
265
|
-
// Keep process alive
|
|
266
|
-
await new Promise(() => {});
|
|
267
|
-
break;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
case "stop": {
|
|
271
|
-
const state = new StateDB(wikiDir);
|
|
272
|
-
const pid = state.getState("daemon_pid");
|
|
273
|
-
state.close();
|
|
274
|
-
if (pid) {
|
|
275
|
-
try {
|
|
276
|
-
process.kill(parseInt(pid), "SIGTERM");
|
|
277
|
-
console.log(`Sent SIGTERM to daemon (PID ${pid})`);
|
|
278
|
-
} catch {
|
|
279
|
-
console.log(`Daemon not running (PID ${pid} not found)`);
|
|
280
|
-
}
|
|
281
|
-
} else {
|
|
282
|
-
console.log("No daemon PID recorded.");
|
|
283
|
-
}
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
129
|
case "status": {
|
|
288
130
|
const json = args.includes("--json");
|
|
289
131
|
const { getPiParaStatus, formatPiParaStatus } = await import("./status.js");
|
|
@@ -292,114 +134,6 @@ async function main() {
|
|
|
292
134
|
break;
|
|
293
135
|
}
|
|
294
136
|
|
|
295
|
-
case "legacy-status": {
|
|
296
|
-
const state = new StateDB(wikiDir);
|
|
297
|
-
const pid = state.getState("daemon_pid");
|
|
298
|
-
const startedAt = state.getState("daemon_started_at");
|
|
299
|
-
const history = state.getHistory(undefined, 5);
|
|
300
|
-
const failed = state.getFailed();
|
|
301
|
-
state.close();
|
|
302
|
-
|
|
303
|
-
let running = false;
|
|
304
|
-
if (pid) {
|
|
305
|
-
try {
|
|
306
|
-
process.kill(parseInt(pid), 0);
|
|
307
|
-
running = true;
|
|
308
|
-
} catch {}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
console.log(`Daemon: ${running ? `running (PID ${pid})` : "not running"}`);
|
|
312
|
-
if (startedAt) console.log(`Started: ${startedAt}`);
|
|
313
|
-
console.log(`Failed: ${failed.length}`);
|
|
314
|
-
console.log(`\nRecent history:`);
|
|
315
|
-
for (const h of history) {
|
|
316
|
-
const status = h.error ? `ERROR: ${h.error.slice(0, 50)}` : `${h.pagesCreated.length} pages`;
|
|
317
|
-
console.log(` ${h.processedAt} | ${h.scope} | ${status}`);
|
|
318
|
-
}
|
|
319
|
-
break;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
case "process": {
|
|
323
|
-
const sessionFile = args[1];
|
|
324
|
-
if (!sessionFile) {
|
|
325
|
-
console.error("Usage: pi-para-daemon process <session_file>");
|
|
326
|
-
process.exit(1);
|
|
327
|
-
}
|
|
328
|
-
const modelIdx2 = args.indexOf("--model");
|
|
329
|
-
const modelArg2 = modelIdx2 >= 0 ? args[modelIdx2 + 1] : undefined;
|
|
330
|
-
const { model, getApiKey } = await createModel(modelArg2);
|
|
331
|
-
const daemon = new Daemon({ wikiDir, model: model as any, getApiKey });
|
|
332
|
-
await daemon.start();
|
|
333
|
-
await daemon.processOne(sessionFile);
|
|
334
|
-
await daemon.stop();
|
|
335
|
-
break;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
case "process-recent": {
|
|
339
|
-
const hoursIdx = args.indexOf("--hours");
|
|
340
|
-
const hours = hoursIdx >= 0 ? parseInt(args[hoursIdx + 1] ?? "24") : 24;
|
|
341
|
-
const cutoff = Date.now() - hours * 60 * 60 * 1000;
|
|
342
|
-
|
|
343
|
-
// Find recent sessions from registry
|
|
344
|
-
const watcher = new RegistryWatcher(wikiDir, () => {});
|
|
345
|
-
const entries = watcher.getAllEntries().filter((e) => {
|
|
346
|
-
const ts = new Date(e.timestamp).getTime();
|
|
347
|
-
return ts > cutoff;
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
const state = new StateDB(wikiDir);
|
|
351
|
-
const unprocessed = entries.filter((e) => !state.isProcessed(e.sessionPath));
|
|
352
|
-
state.close();
|
|
353
|
-
|
|
354
|
-
if (unprocessed.length === 0) {
|
|
355
|
-
console.log(`No unprocessed sessions in the last ${hours} hour(s).`);
|
|
356
|
-
break;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
console.log(`Found ${unprocessed.length} unprocessed session(s)`);
|
|
360
|
-
const { model, getApiKey } = await createModel();
|
|
361
|
-
const daemon = new Daemon({ wikiDir, model: model as any, getApiKey });
|
|
362
|
-
await daemon.start();
|
|
363
|
-
for (const entry of unprocessed) {
|
|
364
|
-
await daemon.processOne(entry.sessionPath);
|
|
365
|
-
}
|
|
366
|
-
await daemon.stop();
|
|
367
|
-
break;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
case "retry-failed": {
|
|
371
|
-
const { model, getApiKey } = await createModel();
|
|
372
|
-
const daemon = new Daemon({ wikiDir, model: model as any, getApiKey });
|
|
373
|
-
await daemon.start();
|
|
374
|
-
await daemon.retryFailed();
|
|
375
|
-
await daemon.stop();
|
|
376
|
-
break;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
case "history": {
|
|
380
|
-
const scopeIdx = args.indexOf("--scope");
|
|
381
|
-
const scope = scopeIdx >= 0 ? args[scopeIdx + 1] : undefined;
|
|
382
|
-
const state = new StateDB(wikiDir);
|
|
383
|
-
const history = state.getHistory(scope, 20);
|
|
384
|
-
state.close();
|
|
385
|
-
|
|
386
|
-
if (history.length === 0) {
|
|
387
|
-
console.log("No processing history.");
|
|
388
|
-
break;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
for (const h of history) {
|
|
392
|
-
const status = h.error
|
|
393
|
-
? `ERROR: ${h.error.slice(0, 60)}`
|
|
394
|
-
: h.pagesCreated.length > 0
|
|
395
|
-
? `${h.pagesCreated.length} page(s): ${h.pagesCreated.join(", ")}`
|
|
396
|
-
: "skipped";
|
|
397
|
-
console.log(`${h.processedAt} | ${h.scope} | ${status}`);
|
|
398
|
-
}
|
|
399
|
-
break;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
403
137
|
default:
|
|
404
138
|
console.log(`pi-para — PARA wiki extension CLI
|
|
405
139
|
|
|
@@ -416,14 +150,7 @@ Primary commands:
|
|
|
416
150
|
providers set-secret <name> <value>
|
|
417
151
|
providers remove-secret <name>
|
|
418
152
|
|
|
419
|
-
|
|
420
|
-
start Deprecated daemon foreground mode
|
|
421
|
-
stop Deprecated daemon stop
|
|
422
|
-
legacy-status Legacy daemon status and recent history
|
|
423
|
-
process <file> Process a single session file
|
|
424
|
-
process-recent Process unprocessed sessions (--hours N, default 24)
|
|
425
|
-
retry-failed Retry all failed sessions
|
|
426
|
-
history Show processing history (--scope NAME to filter)
|
|
153
|
+
The legacy daemon commands were removed in 0.7; use tasks and doctor instead.
|
|
427
154
|
|
|
428
155
|
Credential policy:
|
|
429
156
|
Use persisted Pi auth or ~/.pi/para/secrets.json. Setup does not use env vars for API keys.`);
|
|
@@ -431,7 +158,16 @@ Credential policy:
|
|
|
431
158
|
}
|
|
432
159
|
}
|
|
433
160
|
|
|
434
|
-
|
|
161
|
+
function isMainModule(entryPath: string | undefined): boolean {
|
|
162
|
+
if (!entryPath) return false;
|
|
163
|
+
try {
|
|
164
|
+
return fileURLToPath(import.meta.url) === realpathSync(entryPath);
|
|
165
|
+
} catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (isMainModule(process.argv[1])) {
|
|
435
171
|
main().catch((err) => {
|
|
436
172
|
console.error(err);
|
|
437
173
|
process.exit(1);
|
package/src/commands.ts
CHANGED
|
@@ -653,9 +653,6 @@ ${goal} — verified and complete.
|
|
|
653
653
|
toggleLintAutoFix,
|
|
654
654
|
setCaptureModelAuto,
|
|
655
655
|
setCaptureModel,
|
|
656
|
-
setWebWikiEnabled,
|
|
657
|
-
setWebWikiHost,
|
|
658
|
-
setWebWikiPort,
|
|
659
656
|
ensureEmbeddingProfile,
|
|
660
657
|
ensureRerankProfile,
|
|
661
658
|
disableRerank,
|
|
@@ -689,7 +686,6 @@ ${goal} — verified and complete.
|
|
|
689
686
|
`[Search] Limit: ${config.context.searchLimit}, Graph boost: ${config.context.searchGraphBoost}`,
|
|
690
687
|
`[Lint] Auto-fix: ${config.lint.autoFix}, Stale days: ${config.lint.staleDays}`,
|
|
691
688
|
`[Capture] Model: ${modelSelectionLabel(config.models.capture)}`,
|
|
692
|
-
`[WebWiki] ${config.webWiki.enabled ? `Enabled at http://${config.webWiki.host}:${config.webWiki.port}` : "Disabled"}`,
|
|
693
689
|
`[Embedding] ${providerProfileLabel(config.qmd.embedding)}`,
|
|
694
690
|
`[Rerank] ${providerProfileLabel(config.qmd.rerank)}`,
|
|
695
691
|
`[Secrets] ${secretsLabel}`,
|
|
@@ -783,23 +779,6 @@ ${goal} — verified and complete.
|
|
|
783
779
|
await persist();
|
|
784
780
|
ctx.ui.notify(`Set capture model = ${modelSelectionLabel(config.models.capture)}. Restart open Pi sessions to apply.`, "info");
|
|
785
781
|
}
|
|
786
|
-
} else if (choice.startsWith("[WebWiki]")) {
|
|
787
|
-
const sub = await ctx.ui.select("Web Wiki Settings", [
|
|
788
|
-
`Enabled: ${config.webWiki.enabled}`,
|
|
789
|
-
`Host: ${config.webWiki.host}`,
|
|
790
|
-
`Port: ${config.webWiki.port}`,
|
|
791
|
-
]);
|
|
792
|
-
if (sub?.startsWith("Enabled")) {
|
|
793
|
-
setWebWikiEnabled(config, !config.webWiki.enabled);
|
|
794
|
-
await persist();
|
|
795
|
-
ctx.ui.notify(`Web Wiki ${config.webWiki.enabled ? "enabled" : "disabled"}. Restart open Pi sessions to apply.`, "info");
|
|
796
|
-
} else if (sub?.startsWith("Host")) {
|
|
797
|
-
const val = await ctx.ui.input("Host:", config.webWiki.host);
|
|
798
|
-
if (val) { setWebWikiHost(config, val); await persist(); }
|
|
799
|
-
} else if (sub?.startsWith("Port")) {
|
|
800
|
-
const val = await ctx.ui.input("Port:", String(config.webWiki.port));
|
|
801
|
-
if (val) { setWebWikiPort(config, val); await persist(); }
|
|
802
|
-
}
|
|
803
782
|
} else if (choice.startsWith("[Secrets]")) {
|
|
804
783
|
const entries = Object.entries(secretStore.secrets);
|
|
805
784
|
const sub = await ctx.ui.select(
|
package/src/config.ts
CHANGED
|
@@ -52,6 +52,7 @@ export interface ParaUserConfig {
|
|
|
52
52
|
autoFix: boolean;
|
|
53
53
|
staleDays: number;
|
|
54
54
|
};
|
|
55
|
+
/** Deprecated compatibility input. Preserved when loading but ignored by the runtime. */
|
|
55
56
|
webWiki: {
|
|
56
57
|
enabled: boolean;
|
|
57
58
|
host: string;
|
|
@@ -71,7 +72,6 @@ export interface LegacyParaRuntimeConfig {
|
|
|
71
72
|
searchIncludeArchives: boolean;
|
|
72
73
|
searchGraphBoost: boolean;
|
|
73
74
|
daemonModel: string | null;
|
|
74
|
-
webWiki: { enabled: boolean; host: string; port: number };
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
export interface LoadedParaConfig {
|
|
@@ -292,11 +292,6 @@ export function toLegacyRuntimeConfig(config: ParaUserConfig): LegacyParaRuntime
|
|
|
292
292
|
searchIncludeArchives: false,
|
|
293
293
|
searchGraphBoost: config.context.searchGraphBoost,
|
|
294
294
|
daemonModel,
|
|
295
|
-
webWiki: {
|
|
296
|
-
enabled: config.webWiki.enabled,
|
|
297
|
-
host: config.webWiki.host,
|
|
298
|
-
port: config.webWiki.port,
|
|
299
|
-
},
|
|
300
295
|
};
|
|
301
296
|
}
|
|
302
297
|
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { join } from "node:path";
|
|
12
|
-
import { homedir
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
13
|
|
|
14
14
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
15
15
|
import type { QMDStore } from "qmd-engine";
|
|
@@ -22,7 +22,6 @@ import { registerTools } from "./tools.js";
|
|
|
22
22
|
import { setupContextInjection, markContextDirty } from "./context.js";
|
|
23
23
|
import type { ContextConfig } from "./context.js";
|
|
24
24
|
import { registerCommands } from "./commands.js";
|
|
25
|
-
import { createServer } from "node:http";
|
|
26
25
|
import { startWikiScheduler, stopWikiScheduler } from "./scheduler/index.js";
|
|
27
26
|
import {
|
|
28
27
|
appendCompletedSession,
|
|
@@ -59,12 +58,6 @@ interface ParaConfig {
|
|
|
59
58
|
/** Legacy capture LLM: "provider/model-id" (e.g. "anthropic/claude-sonnet-4").
|
|
60
59
|
* New config stores this as models.capture; legacy field remains for migration. */
|
|
61
60
|
daemonModel: string | null;
|
|
62
|
-
/** Web wiki UI settings */
|
|
63
|
-
webWiki: {
|
|
64
|
-
enabled: boolean;
|
|
65
|
-
host: string;
|
|
66
|
-
port: number;
|
|
67
|
-
};
|
|
68
61
|
}
|
|
69
62
|
|
|
70
63
|
function getDefaultConfig(): ParaConfig {
|
|
@@ -79,11 +72,6 @@ function getDefaultConfig(): ParaConfig {
|
|
|
79
72
|
searchIncludeArchives: false,
|
|
80
73
|
searchGraphBoost: true,
|
|
81
74
|
daemonModel: null,
|
|
82
|
-
webWiki: {
|
|
83
|
-
enabled: false,
|
|
84
|
-
host: "0.0.0.0",
|
|
85
|
-
port: 10973,
|
|
86
|
-
},
|
|
87
75
|
};
|
|
88
76
|
}
|
|
89
77
|
|
|
@@ -107,43 +95,10 @@ async function loadConfig(): Promise<LoadedRuntimeConfig> {
|
|
|
107
95
|
}
|
|
108
96
|
}
|
|
109
97
|
|
|
110
|
-
// -- LAN IP detection --------------------------------------------------------
|
|
111
|
-
|
|
112
|
-
/** Check if the optional web wiki server is alive on the given port. */
|
|
113
|
-
function checkWebServerAlive(port: number): Promise<boolean> {
|
|
114
|
-
return new Promise((resolve) => {
|
|
115
|
-
const req = createServer().listen(port, "127.0.0.1");
|
|
116
|
-
req.on("error", (err: NodeJS.ErrnoException) => {
|
|
117
|
-
req.close();
|
|
118
|
-
// EADDRINUSE means something is already listening — daemon is running
|
|
119
|
-
resolve(err.code === "EADDRINUSE");
|
|
120
|
-
});
|
|
121
|
-
req.on("listening", () => {
|
|
122
|
-
// Port is free — no daemon web server
|
|
123
|
-
req.close();
|
|
124
|
-
resolve(false);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
98
|
function isPrintModeProcess(argv = process.argv): boolean {
|
|
130
99
|
return argv.includes("-p") || argv.includes("--print") || argv.includes("--mode") && argv[argv.indexOf("--mode") + 1] === "json";
|
|
131
100
|
}
|
|
132
101
|
|
|
133
|
-
function getLanIp(): string | null {
|
|
134
|
-
const interfaces = networkInterfaces();
|
|
135
|
-
for (const name of Object.keys(interfaces)) {
|
|
136
|
-
const addrs = interfaces[name];
|
|
137
|
-
if (!addrs) continue;
|
|
138
|
-
for (const addr of addrs) {
|
|
139
|
-
if (addr.family === "IPv4" && !addr.internal) {
|
|
140
|
-
return addr.address;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
102
|
// -- Session state -----------------------------------------------------------
|
|
148
103
|
|
|
149
104
|
interface ParaSessionState {
|
|
@@ -370,27 +325,11 @@ export default async function piPara(pi: ExtensionAPI): Promise<void> {
|
|
|
370
325
|
}
|
|
371
326
|
}
|
|
372
327
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const port = config.webWiki.port;
|
|
376
|
-
try {
|
|
377
|
-
const alive = await checkWebServerAlive(port);
|
|
378
|
-
if (alive) {
|
|
379
|
-
const lanIp = getLanIp();
|
|
380
|
-
const url = lanIp ? `http://${lanIp}:${port}` : `http://localhost:${port}`;
|
|
381
|
-
ctx.ui.setStatus("pi-para", `wiki: ${url}`);
|
|
382
|
-
} else {
|
|
383
|
-
ctx.ui.setStatus("pi-para", "wiki: ready (web UI disabled)");
|
|
384
|
-
if (!printMode) setTimeout(() => ctx.ui.setStatus("pi-para", undefined), 5000);
|
|
385
|
-
}
|
|
386
|
-
} catch {
|
|
387
|
-
ctx.ui.setStatus("pi-para", "wiki: ready");
|
|
388
|
-
if (!printMode) setTimeout(() => ctx.ui.setStatus("pi-para", undefined), 3000);
|
|
389
|
-
}
|
|
390
|
-
} else {
|
|
391
|
-
ctx.ui.setStatus("pi-para", "wiki: ready");
|
|
392
|
-
if (!printMode) setTimeout(() => ctx.ui.setStatus("pi-para", undefined), 3000);
|
|
328
|
+
if (loadedConfig.userConfig?.webWiki.enabled && !printMode) {
|
|
329
|
+
ctx.ui.notify("webWiki configuration is ignored because Web Wiki was removed in 0.7.", "warning");
|
|
393
330
|
}
|
|
331
|
+
ctx.ui.setStatus("pi-para", "wiki: ready");
|
|
332
|
+
if (!printMode) setTimeout(() => ctx.ui.setStatus("pi-para", undefined), 3000);
|
|
394
333
|
});
|
|
395
334
|
|
|
396
335
|
// -- session_tree ----------------------------------------------------------
|
package/src/link-utils.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Shared wikilink utilities — extraction, auto-linking, and sync.
|
|
3
3
|
*
|
|
4
4
|
* Centralizes the [[wikilink]] logic that was duplicated across
|
|
5
|
-
* tools.ts, lint.ts,
|
|
5
|
+
* tools.ts, lint.ts, and processor.ts.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
// -- Wikilink extraction ----------------------------------------------------
|
package/src/processor.ts
CHANGED
|
@@ -29,6 +29,30 @@ import { StringEnum } from "@earendil-works/pi-ai";
|
|
|
29
29
|
|
|
30
30
|
import { loadSession, createSessionTools } from "./session-tools.js";
|
|
31
31
|
import type { LoadedSession } from "./session-tools.js";
|
|
32
|
+
import { appendFileSync, renameSync, statSync } from "node:fs";
|
|
33
|
+
import { join } from "node:path";
|
|
34
|
+
|
|
35
|
+
const CAPTURE_LOG_MAX_BYTES = 1024 * 1024;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Capture diagnostics go to a file inside the wiki, never stdout/stderr.
|
|
39
|
+
* The capture scheduler runs inside the live interactive Pi process, where
|
|
40
|
+
* console output bypasses the TUI renderer and leaks session-derived text
|
|
41
|
+
* into whatever session is currently active.
|
|
42
|
+
*/
|
|
43
|
+
export function logCapture(wikiDir: string, message: string): void {
|
|
44
|
+
try {
|
|
45
|
+
const logPath = join(wikiDir, ".capture.log");
|
|
46
|
+
try {
|
|
47
|
+
if (statSync(logPath).size > CAPTURE_LOG_MAX_BYTES) renameSync(logPath, `${logPath}.old`);
|
|
48
|
+
} catch {
|
|
49
|
+
// Missing log file is fine.
|
|
50
|
+
}
|
|
51
|
+
appendFileSync(logPath, `[${new Date().toISOString()}] ${message}\n`, "utf-8");
|
|
52
|
+
} catch {
|
|
53
|
+
// Logging must never break or leak from capture.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
32
56
|
|
|
33
57
|
// -- Types -------------------------------------------------------------------
|
|
34
58
|
|
|
@@ -350,7 +374,7 @@ export async function processSession(
|
|
|
350
374
|
await agent.prompt(promptText);
|
|
351
375
|
} catch (err) {
|
|
352
376
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
353
|
-
|
|
377
|
+
logCapture(wikiDir, `Agent error (${scope}): ${errMsg}`);
|
|
354
378
|
return {
|
|
355
379
|
pagesCreated: [],
|
|
356
380
|
pagesUpdated: [],
|
|
@@ -359,20 +383,11 @@ export async function processSession(
|
|
|
359
383
|
};
|
|
360
384
|
}
|
|
361
385
|
|
|
362
|
-
// Debug:
|
|
386
|
+
// Debug: record counts only — never message content, which is distilled
|
|
387
|
+
// from the captured session and must not leave the wiki directory.
|
|
363
388
|
const msgCount = agent.state.messages.length;
|
|
364
389
|
const toolCalls = agent.state.messages.filter(m => m.role === "toolResult").length;
|
|
365
|
-
|
|
366
|
-
let lastContent = "";
|
|
367
|
-
if (lastMsg && "content" in lastMsg) {
|
|
368
|
-
const c = lastMsg.content;
|
|
369
|
-
if (typeof c === "string") lastContent = c.slice(0, 200);
|
|
370
|
-
else if (Array.isArray(c)) {
|
|
371
|
-
const tb = c.find((b: any) => b.type === "text") as { text: string } | undefined;
|
|
372
|
-
if (tb) lastContent = tb.text.slice(0, 200);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
console.log(`[processor] Agent finished: ${msgCount} messages, ${toolCalls} tool results. Last: ${lastContent}`);
|
|
390
|
+
logCapture(wikiDir, `Agent finished (${scope}): ${msgCount} messages, ${toolCalls} tool results`);
|
|
376
391
|
|
|
377
392
|
// 6. Extract what was written from agent's messages
|
|
378
393
|
const pagesCreated: string[] = [];
|
package/src/settings.ts
CHANGED
|
@@ -54,20 +54,6 @@ export function setCaptureModel(config: ParaUserConfig, provider: string, model:
|
|
|
54
54
|
return profile;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
export function setWebWikiEnabled(config: ParaUserConfig, enabled: boolean): void {
|
|
58
|
-
config.webWiki.enabled = enabled;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function setWebWikiHost(config: ParaUserConfig, host: string): void {
|
|
62
|
-
config.webWiki.host = host.trim() || config.webWiki.host;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function setWebWikiPort(config: ParaUserConfig, value: string): number {
|
|
66
|
-
const parsed = parsePositiveInteger(value, config.webWiki.port);
|
|
67
|
-
config.webWiki.port = parsed;
|
|
68
|
-
return parsed;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
57
|
export function setProviderProfileField(
|
|
72
58
|
profile: ProviderCredentialRef,
|
|
73
59
|
field: "provider" | "baseUrl" | "model" | "apiFormat" | "credentialRef",
|