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