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.
Files changed (133) hide show
  1. package/ARCHITECTURE.md +4 -6
  2. package/CHANGELOG.md +19 -0
  3. package/CONFIGURATION.md +1 -16
  4. package/README.md +2 -13
  5. package/SETUP.md +1 -7
  6. package/STRUCTURE.md +7 -18
  7. package/dist/cli-legacy.d.ts +2 -1
  8. package/dist/cli-legacy.d.ts.map +1 -1
  9. package/dist/cli-legacy.js +13 -25
  10. package/dist/cli-legacy.js.map +1 -1
  11. package/dist/cli.d.ts +2 -24
  12. package/dist/cli.d.ts.map +1 -1
  13. package/dist/cli.js +29 -271
  14. package/dist/cli.js.map +1 -1
  15. package/dist/commands.d.ts.map +1 -1
  16. package/dist/commands.js +1 -28
  17. package/dist/commands.js.map +1 -1
  18. package/dist/config.d.ts +1 -5
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +0 -5
  21. package/dist/config.js.map +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +6 -64
  24. package/dist/index.js.map +1 -1
  25. package/dist/link-utils.d.ts +1 -1
  26. package/dist/link-utils.js +1 -1
  27. package/dist/processor.d.ts +7 -0
  28. package/dist/processor.d.ts.map +1 -1
  29. package/dist/processor.js +29 -15
  30. package/dist/processor.js.map +1 -1
  31. package/dist/settings.d.ts +0 -3
  32. package/dist/settings.d.ts.map +1 -1
  33. package/dist/settings.js +0 -11
  34. package/dist/settings.js.map +1 -1
  35. package/dist/tools/edit-read.d.ts +18 -0
  36. package/dist/tools/edit-read.d.ts.map +1 -0
  37. package/dist/tools/edit-read.js +161 -0
  38. package/dist/tools/edit-read.js.map +1 -0
  39. package/dist/tools/ingest.d.ts +12 -0
  40. package/dist/tools/ingest.d.ts.map +1 -0
  41. package/dist/tools/ingest.js +48 -0
  42. package/dist/tools/ingest.js.map +1 -0
  43. package/dist/tools/lint.d.ts +10 -0
  44. package/dist/tools/lint.d.ts.map +1 -0
  45. package/dist/tools/lint.js +57 -0
  46. package/dist/tools/lint.js.map +1 -0
  47. package/dist/tools/migrate.d.ts +6 -0
  48. package/dist/tools/migrate.d.ts.map +1 -0
  49. package/dist/tools/migrate.js +36 -0
  50. package/dist/tools/migrate.js.map +1 -0
  51. package/dist/tools/move.d.ts +9 -0
  52. package/dist/tools/move.d.ts.map +1 -0
  53. package/dist/tools/move.js +61 -0
  54. package/dist/tools/move.js.map +1 -0
  55. package/dist/tools/query.d.ts +12 -0
  56. package/dist/tools/query.d.ts.map +1 -0
  57. package/dist/tools/query.js +47 -0
  58. package/dist/tools/query.js.map +1 -0
  59. package/dist/tools/schemas.d.ts +109 -0
  60. package/dist/tools/schemas.d.ts.map +1 -0
  61. package/dist/tools/schemas.js +80 -0
  62. package/dist/tools/schemas.js.map +1 -0
  63. package/dist/tools/shared.d.ts +14 -0
  64. package/dist/tools/shared.d.ts.map +1 -0
  65. package/dist/tools/shared.js +25 -0
  66. package/dist/tools/shared.js.map +1 -0
  67. package/dist/tools/summarize.d.ts +8 -0
  68. package/dist/tools/summarize.d.ts.map +1 -0
  69. package/dist/tools/summarize.js +82 -0
  70. package/dist/tools/summarize.js.map +1 -0
  71. package/dist/tools/write.d.ts +23 -0
  72. package/dist/tools/write.d.ts.map +1 -0
  73. package/dist/tools/write.js +215 -0
  74. package/dist/tools/write.js.map +1 -0
  75. package/dist/tools.d.ts +1 -8
  76. package/dist/tools.d.ts.map +1 -1
  77. package/dist/tools.js +13 -795
  78. package/dist/tools.js.map +1 -1
  79. package/dist/wiki.d.ts.map +1 -1
  80. package/dist/wiki.js +1 -0
  81. package/dist/wiki.js.map +1 -1
  82. package/package.json +2 -6
  83. package/src/cli-legacy.ts +14 -26
  84. package/src/cli.ts +29 -293
  85. package/src/commands.ts +0 -21
  86. package/src/config.ts +1 -6
  87. package/src/index.ts +5 -66
  88. package/src/link-utils.ts +1 -1
  89. package/src/processor.ts +28 -13
  90. package/src/settings.ts +0 -14
  91. package/src/tools/edit-read.ts +193 -0
  92. package/src/tools/ingest.ts +63 -0
  93. package/src/tools/lint.ts +74 -0
  94. package/src/tools/migrate.ts +44 -0
  95. package/src/tools/move.ts +79 -0
  96. package/src/tools/query.ts +63 -0
  97. package/src/tools/schemas.ts +157 -0
  98. package/src/tools/shared.ts +35 -0
  99. package/src/tools/summarize.ts +89 -0
  100. package/src/tools/write.ts +252 -0
  101. package/src/tools.ts +18 -1046
  102. package/src/wiki.ts +1 -0
  103. package/dist/daemon.d.ts +0 -49
  104. package/dist/daemon.d.ts.map +0 -1
  105. package/dist/daemon.js +0 -226
  106. package/dist/daemon.js.map +0 -1
  107. package/dist/link-discovery.d.ts +0 -40
  108. package/dist/link-discovery.d.ts.map +0 -1
  109. package/dist/link-discovery.js +0 -122
  110. package/dist/link-discovery.js.map +0 -1
  111. package/dist/maintainer.d.ts +0 -24
  112. package/dist/maintainer.d.ts.map +0 -1
  113. package/dist/maintainer.js +0 -358
  114. package/dist/maintainer.js.map +0 -1
  115. package/dist/watcher.d.ts +0 -25
  116. package/dist/watcher.d.ts.map +0 -1
  117. package/dist/watcher.js +0 -84
  118. package/dist/watcher.js.map +0 -1
  119. package/dist/webui/client/dist/index.html +0 -1885
  120. package/dist/webui/server.d.ts +0 -31
  121. package/dist/webui/server.d.ts.map +0 -1
  122. package/dist/webui/server.js +0 -566
  123. package/dist/webui/server.js.map +0 -1
  124. package/scripts/build-assets.mjs +0 -17
  125. package/src/daemon.ts +0 -288
  126. package/src/link-discovery.ts +0 -170
  127. package/src/maintainer.ts +0 -404
  128. package/src/python/__pycache__/minimax_lm.cpython-312.pyc +0 -0
  129. package/src/python/minimax_lm.py +0 -198
  130. package/src/python/rlm_capture.py +0 -215
  131. package/src/watcher.ts +0 -101
  132. package/src/webui/client/dist/index.html +0 -1885
  133. 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 old pi-para-daemon commands remain as deprecated compatibility aliases.
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 { pathToFileURL } from "node:url";
9
+ import { fileURLToPath } from "node:url";
10
10
  import { homedir } from "node:os";
11
- import { existsSync, readdirSync, statSync } from "node:fs";
11
+ import { realpathSync } from "node:fs";
12
+ import { formatLegacyDaemonRemoval, isLegacyDaemonBinary, isLegacyDaemonCommand } from "./cli-legacy.js";
12
13
 
13
- import { Daemon } from "./daemon.js";
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
- // Optional legacy environment compatibility.
97
- for (const provider of getProviders()) {
98
- const key = getEnvApiKey(provider);
99
- if (!key) continue;
100
- const models = services?.modelRegistry.getAll().filter((model) => model.provider === provider) ?? [];
101
- const sorted = [...models].sort((a, b) => (b.contextWindow ?? 0) - (a.contextWindow ?? 0));
102
- const picked = sorted.find((model) => !model.reasoning) ?? sorted[0];
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.warn(formatLegacyDaemonWarning(command));
135
- console.warn("");
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
- Legacy compatibility commands:
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
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
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, networkInterfaces } from "node:os";
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
- // Check if web wiki server is running (don't start our own yet)
374
- if (config.webWiki.enabled) {
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, processor.ts, and webui/server.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
- console.error(`[processor] Agent error: ${errMsg}`);
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: log what the agent did
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
- const lastMsg = agent.state.messages[msgCount - 1];
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",