total-recall-brain 3.41.3 → 3.41.5

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 (96) hide show
  1. package/bin/total-recall.mjs +12 -2
  2. package/docs/reference/cli-reference.md +18 -0
  3. package/frontend/dist/assets/{index-DFenQQEQ.js → index-WT3gs-x6.js} +75 -70
  4. package/frontend/dist/index.html +1 -1
  5. package/package.json +7 -5
  6. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +2 -2
  7. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
  8. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
  9. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +48 -0
  10. package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +42 -0
  11. package/scaffold/.agent/skills/total-recall/plugins/auth/src/mesh-secrets.mjs +18 -0
  12. package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/secrets.mjs +5 -2
  13. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-carriers.mjs +76 -0
  14. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-prune.mjs +37 -0
  15. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +53 -17
  16. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +3114 -36
  17. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
  18. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +3114 -36
  19. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  20. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +3131 -28
  21. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +6 -2
  22. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +3132 -29
  23. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +5 -1
  24. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +3164 -73
  25. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -3
  26. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  27. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/grants.mjs +7 -3
  28. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +61 -38
  29. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +57 -2
  30. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +220 -36
  31. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/server/routes/mesh.mjs +7 -2
  32. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +65 -0
  33. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +59 -0
  34. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +534 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +139 -0
  36. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +5937 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +26 -0
  38. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +376 -0
  39. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +37 -0
  40. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adapters/dsh.mjs +446 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +23 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +398 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +172 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +82 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +90 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +415 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +36 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +35 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/tracker.mjs +133 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +233 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +32 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +845 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/BRICK.md +64 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/index.mjs +250 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +619 -41
  56. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +3134 -31
  57. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +9 -3
  58. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +413 -14
  59. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +129 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +159 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +197 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/lint.mjs +153 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +261 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +502 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +114 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +96 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +375 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +35 -22
  69. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +1225 -364
  70. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/openwiki.mjs +8987 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3114 -83
  72. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +5 -1
  73. package/src/cli/compact.mjs +108 -0
  74. package/src/cli/config.mjs +3 -0
  75. package/src/cli/connect.mjs +12 -116
  76. package/src/cli/context.mjs +52 -23
  77. package/src/cli/forget.mjs +2 -0
  78. package/src/cli/harness.mjs +0 -1
  79. package/src/cli/plugin/create.mjs +56 -3
  80. package/src/cli/plugin/index.mjs +15 -3
  81. package/src/cli/plugin/monetization.mjs +232 -12
  82. package/src/cli/preflight.mjs +161 -0
  83. package/src/cli/release.mjs +386 -0
  84. package/src/core/cli-commands.mjs +4 -0
  85. package/src/core/context-compactor.mjs +143 -0
  86. package/src/core/context-compiler.mjs +8 -1
  87. package/src/core/default-plugins.mjs +1 -1
  88. package/src/core/meta-harness.mjs +16 -38
  89. package/src/core/plugin-dependencies.mjs +6 -1
  90. package/src/core/preflight-gate.mjs +77 -0
  91. package/src/core/preflight-receipt.mjs +123 -0
  92. package/src/core/runtime.mjs +0 -3
  93. package/src/core/surface.mjs +39 -2
  94. package/src/server/invoke-durability.mjs +39 -0
  95. package/src/server/invoke-write-child.mjs +25 -0
  96. package/src/server/routes/invoke.mjs +7 -0
@@ -3,12 +3,164 @@
3
3
  * OpenWiki CLI Handler
4
4
  * Executed via: npx total-recall openwiki <subcommand>
5
5
  */
6
- // Scaffold: report honestly what exists. Add each real subcommand to
7
- // IMPLEMENTED and to plugin.json "cli.subcommands" as it is built.
8
-
9
6
  import fs from "node:fs";
10
7
  import path from "node:path";
11
8
  import { runBootstrapCommand } from './src/bootstrap.mjs';
9
+ import { describeRetention, hasWiki, isDue, readProjects, refreshProject, refreshProjectAsync, resolveKey, staleness } from './src/refresh.mjs';
10
+ import { announceNewVersion, checkUpstreamStatus, propagateUpdates, readUpstreamState } from './src/upstream-watcher.mjs';
11
+ import { loadOpenWikiConfig, setOpenWikiConfigValue, resetOpenWikiConfig, resolveConfigPath, ALLOWED_CONFIG_KEYS, ALLOWED_PROVIDERS } from './src/config.mjs';
12
+ import { discoverAllHubs } from './src/mesh-vaults.mjs';
13
+ import { searchWikiHubs } from './src/search.mjs';
14
+ import { buildKnowledgeGraph } from './src/graph.mjs';
15
+ import { getTokenStatus, setTokenValue } from './src/token.mjs';
16
+ import { lintWikiHubs } from './src/lint.mjs';
17
+ import { generateWiki } from './src/generate.mjs';
18
+
19
+ /**
20
+ * Regenerate the generated pages of every registered project that carries them.
21
+ *
22
+ * Fleet-wide by design: the plugin ships from the shared brain, so one
23
+ * implementation covers every repository and every node that resolves it.
24
+ * Defaults to a report; --apply performs the refreshes, because regenerating a
25
+ * repository's documentation is a write to that repository.
26
+ */
27
+ async function refreshCommand(args) {
28
+ const json = args.includes('--json');
29
+ const apply = args.includes('--apply');
30
+ const force = args.includes('--force');
31
+ const repoIndex = args.indexOf('--repo');
32
+ const onlyArg = args.find((a) => a.startsWith('--repo='));
33
+ const only = onlyArg
34
+ ? onlyArg.slice('--repo='.length)
35
+ : (repoIndex >= 0 && args[repoIndex + 1] && !args[repoIndex + 1].startsWith('--') ? args[repoIndex + 1] : null);
36
+
37
+ let projects;
38
+ try {
39
+ projects = readProjects();
40
+ } catch (err) {
41
+ if (json) console.log(JSON.stringify({ ok: false, error: err.message }));
42
+ else console.error(`OpenWiki refresh: ${err.message}`);
43
+ process.exitCode = 2;
44
+ return;
45
+ }
46
+ if (only) projects = projects.filter((p) => p.name === only || p.root === only);
47
+ if (!projects.length) {
48
+ if (json) console.log(JSON.stringify({ ok: false, error: only ? `No registered project matches ${only}` : 'No registered projects' }));
49
+ else console.error(`OpenWiki refresh: ${only ? `no registered project matches ${only}` : 'no registered projects'}`);
50
+ process.exitCode = 2;
51
+ return;
52
+ }
53
+
54
+ const reports = [];
55
+ for (const project of projects) {
56
+ let state;
57
+ try {
58
+ state = force ? { due: true, ...staleness(project.root) } : isDue(project);
59
+ } catch (err) {
60
+ reports.push({ ...project, action: 'skipped', reason: err.message });
61
+ continue;
62
+ }
63
+ if (!state.due) {
64
+ if (state.skipped) {
65
+ reports.push({ ...project, action: 'skipped', reason: state.reason });
66
+ } else {
67
+ reports.push({ ...project, action: 'current', generatedAt: state.generatedAt, commitsSince: state.commitsSince });
68
+ }
69
+ continue;
70
+ }
71
+ reports.push({
72
+ ...project,
73
+ action: 'due',
74
+ uninitialized: Boolean(state.uninitialized),
75
+ generatedAt: state.generatedAt,
76
+ commitsSince: state.commitsSince,
77
+ reason: state.reason
78
+ });
79
+ }
80
+
81
+ const due = reports.filter((r) => r.action === 'due');
82
+ let payloadBudgetNote = null;
83
+ if (apply && due.length) {
84
+ const config = loadOpenWikiConfig();
85
+ const { key, from } = resolveKey({ provider: config.provider });
86
+ if (!key) {
87
+ const keyName = config.provider === 'google' ? 'GOOGLE_API_KEY' : 'OPENROUTER_API_KEY';
88
+ const error = `No model credential for provider '${config.provider}'. Save via: total-recall auth secret set ${keyName} --stdin`;
89
+ if (json) console.log(JSON.stringify({ ok: false, error, reports }));
90
+ else console.error(`OpenWiki refresh: ${error}`);
91
+ process.exitCode = 1;
92
+ return;
93
+ }
94
+ const concurrencyArg = args.find((a) => a.startsWith('--concurrency='));
95
+ const concurrencyIndex = args.indexOf('--concurrency');
96
+ const concurrency = Math.max(1, parseInt(
97
+ concurrencyArg ? concurrencyArg.slice('--concurrency='.length) : (concurrencyIndex >= 0 ? args[concurrencyIndex + 1] : '8'),
98
+ 10
99
+ ) || 8);
100
+
101
+ if (!json) {
102
+ console.log(`OpenWiki refresh (apply): dispatching ${due.length} projects with concurrency ${concurrency}`);
103
+ }
104
+
105
+ let queueIndex = 0;
106
+ let completedCount = 0;
107
+ const worker = async () => {
108
+ while (queueIndex < due.length) {
109
+ const itemIndex = queueIndex++;
110
+ const entry = due[itemIndex];
111
+ const mode = entry.uninitialized ? 'init' : 'update';
112
+ if (!json) {
113
+ console.log(`[${itemIndex + 1}/${due.length}] ▶ ${entry.name.padEnd(28)} starting ${mode}...`);
114
+ }
115
+ const result = await refreshProjectAsync(
116
+ { name: entry.name, root: entry.root },
117
+ { key, model: config.model, provider: config.provider, timeoutMs: config.timeout_ms }
118
+ );
119
+ completedCount++;
120
+ const index = reports.findIndex((r) => r.root === entry.root);
121
+ reports[index] = { ...reports[index], ...result, credential: from };
122
+ if (!json) {
123
+ const duration = Math.round((Date.now() - (result.startedAt || Date.now())) / 1000);
124
+ const icon = result.action === 'failed' ? '✖' : '✔';
125
+ console.log(`[${completedCount}/${due.length}] ${icon} ${entry.name.padEnd(28)} ${result.action} (${duration}s)${result.reason ? ` — ${result.reason}` : ''}`);
126
+ }
127
+ }
128
+ };
129
+
130
+ const workers = Array.from({ length: Math.min(concurrency, due.length) }, () => worker());
131
+ await Promise.all(workers);
132
+ }
133
+
134
+ const failed = reports.filter((r) => r.action === 'failed');
135
+ const payload = {
136
+ ok: failed.length === 0,
137
+ applied: apply,
138
+ retention: describeRetention(),
139
+ note: payloadBudgetNote,
140
+ counts: {
141
+ registered: projects.length,
142
+ withPages: reports.filter((r) => r.action !== 'skipped' && !r.uninitialized).length,
143
+ due: due.length,
144
+ uninitialized: reports.filter((r) => r.uninitialized).length,
145
+ refreshed: reports.filter((r) => r.action === 'refreshed' || r.action === 'initialized').length,
146
+ failed: failed.length,
147
+ current: reports.filter((r) => r.action === 'current').length,
148
+ },
149
+ reports,
150
+ };
151
+ if (json) console.log(JSON.stringify(payload, null, 2));
152
+ else {
153
+ console.log(`OpenWiki refresh (${apply ? 'apply' : 'report'}): ${payload.counts.registered} registered, ${payload.counts.due} due (${payload.counts.uninitialized} uninitialized)`);
154
+ for (const r of reports) {
155
+ if (r.action === 'current') continue;
156
+ const age = r.uninitialized ? 'uninitialized' : (r.generatedAt ? `generated ${r.generatedAt.slice(0, 10)}` : 'never generated');
157
+ console.log(` ${r.action.padEnd(9)} ${r.name.padEnd(28)} ${age}${Number.isFinite(r.commitsSince) && r.commitsSince !== Infinity ? `, ${r.commitsSince} commits since` : ''}${r.reason ? ` — ${r.reason}` : ''}`);
158
+ }
159
+ if (payloadBudgetNote) console.log(payloadBudgetNote);
160
+ if (!apply && due.length) console.log('Re-run with --apply to regenerate. Pages are a projection; vault content is never touched.');
161
+ }
162
+ if (failed.length) process.exitCode = 1;
163
+ }
12
164
 
13
165
  async function probeCommand(args) {
14
166
  const json = args.includes("--json");
@@ -34,39 +186,241 @@ async function probeCommand(args) {
34
186
 
35
187
  async function hubsCommand(args) {
36
188
  const json = args.includes("--json");
37
- const targetDir = path.resolve(process.cwd(), "openwiki");
38
- const hubs = [];
39
-
40
- if (fs.existsSync(targetDir)) {
41
- const entries = fs.readdirSync(targetDir, { withFileTypes: true });
42
- for (const ent of entries) {
43
- if (ent.isFile() && ent.name.endsWith(".md")) {
44
- const fullPath = path.join(targetDir, ent.name);
45
- const content = fs.readFileSync(fullPath, "utf8");
46
- const titleMatch = content.match(/^#\s+(.+)$/m);
47
- const title = titleMatch ? titleMatch[1].trim() : ent.name.replace(/\.md$/, "");
48
- const lines = content.split("\n").length;
49
- hubs.push({
50
- title,
51
- path: path.relative(process.cwd(), fullPath),
52
- lines,
53
- size_bytes: content.length
54
- });
189
+ const allVaults = args.includes("--all-vaults") || args.includes("--all");
190
+ const nodeArg = args.find((a) => a.startsWith('--node='));
191
+ const nodeIndex = args.indexOf('--node');
192
+ const node = nodeArg
193
+ ? nodeArg.slice('--node='.length)
194
+ : (nodeIndex >= 0 && args[nodeIndex + 1] && !args[nodeIndex + 1].startsWith('--') ? args[nodeIndex + 1] : null);
195
+
196
+ try {
197
+ const res = await discoverAllHubs({ allVaults, node });
198
+ if (json) {
199
+ console.log(JSON.stringify(res, null, 2));
200
+ } else {
201
+ if (!res.hubs || res.hubs.length === 0) {
202
+ console.log(`No markdown hubs found (${res.scope || 'workspace'})`);
203
+ } else {
204
+ const prefix = res.isRemote ? `[Node ${res.node}] ` : '';
205
+ console.log(`${prefix}Discovered ${res.hubs.length} OpenWiki Hub(s) (${res.scope || 'workspace'}):`);
206
+ for (const h of res.hubs) {
207
+ const repoTag = h.repoName ? `[${h.repoName}] ` : '';
208
+ console.log(` - ${repoTag}${h.title}: ${h.path} (${h.lines} lines, ${h.size_bytes} bytes)`);
209
+ }
55
210
  }
56
211
  }
212
+ } catch (err) {
213
+ if (json) console.log(JSON.stringify({ ok: false, error: err.message }));
214
+ else console.error(`OpenWiki hubs error: ${err.message}`);
215
+ process.exitCode = 1;
57
216
  }
217
+ }
58
218
 
59
- if (json) {
60
- console.log(JSON.stringify({ hubs, count: hubs.length, directory: targetDir }));
61
- } else {
62
- if (hubs.length === 0) {
63
- console.log(`No markdown hubs found in ${targetDir}`);
219
+ async function searchCommand(args) {
220
+ const json = args.includes("--json");
221
+ const allVaults = args.includes("--all-vaults") || args.includes("--all");
222
+ const nodeArg = args.find((a) => a.startsWith('--node='));
223
+ const nodeIndex = args.indexOf('--node');
224
+ const node = nodeArg
225
+ ? nodeArg.slice('--node='.length)
226
+ : (nodeIndex >= 0 && args[nodeIndex + 1] && !args[nodeIndex + 1].startsWith('--') ? args[nodeIndex + 1] : null);
227
+
228
+ const limitArg = args.find((a) => a.startsWith('--limit='));
229
+ const limitIndex = args.indexOf('--limit');
230
+ const limit = limitArg
231
+ ? parseInt(limitArg.slice('--limit='.length), 10)
232
+ : (limitIndex >= 0 && args[limitIndex + 1] ? parseInt(args[limitIndex + 1], 10) : 20);
233
+
234
+ const queryTerms = args.filter((a, idx) => {
235
+ if (a.startsWith('--')) return false;
236
+ if (nodeIndex >= 0 && idx === nodeIndex + 1) return false;
237
+ if (limitIndex >= 0 && idx === limitIndex + 1) return false;
238
+ return true;
239
+ });
240
+ const query = queryTerms.join(' ').trim();
241
+
242
+ if (!query) {
243
+ if (json) console.log(JSON.stringify({ ok: false, error: 'Query argument required: openwiki search <query>' }));
244
+ else console.error('OpenWiki search error: query required. Usage: total-recall openwiki search <query> [--all-vaults] [--node <node>]');
245
+ process.exitCode = 1;
246
+ return;
247
+ }
248
+
249
+ try {
250
+ const res = await searchWikiHubs(query, { allVaults, node, limit });
251
+ if (json) {
252
+ console.log(JSON.stringify(res, null, 2));
253
+ } else {
254
+ console.log(`\nOpenWiki Evidence Search: "${query}" (${res.count} match${res.count === 1 ? '' : 'es'} across ${res.totalHubsScanned} hubs, ${res.scope})`);
255
+ if (res.results.length === 0) {
256
+ console.log(' No matching evidence passages found.\n');
257
+ } else {
258
+ for (const r of res.results) {
259
+ console.log(`\n • [${r.repoName}] ${r.title} > ${r.section} (line ${r.line}):`);
260
+ console.log(` "${r.snippet}"`);
261
+ }
262
+ console.log('');
263
+ }
264
+ }
265
+ } catch (err) {
266
+ if (json) console.log(JSON.stringify({ ok: false, error: err.message }));
267
+ else console.error(`OpenWiki search error: ${err.message}`);
268
+ process.exitCode = 1;
269
+ }
270
+ }
271
+
272
+ async function graphCommand(args) {
273
+ const json = args.includes("--json");
274
+ const allVaults = args.includes("--all-vaults") || args.includes("--all");
275
+ const nodeArg = args.find((a) => a.startsWith('--node='));
276
+ const nodeIndex = args.indexOf('--node');
277
+ const node = nodeArg
278
+ ? nodeArg.slice('--node='.length)
279
+ : (nodeIndex >= 0 && args[nodeIndex + 1] && !args[nodeIndex + 1].startsWith('--') ? args[nodeIndex + 1] : null);
280
+
281
+ const formatArg = args.find((a) => a.startsWith('--format='));
282
+ const formatIndex = args.indexOf('--format');
283
+ const format = formatArg
284
+ ? formatArg.slice('--format='.length)
285
+ : (formatIndex >= 0 && args[formatIndex + 1] ? args[formatIndex + 1] : (args.includes('--mermaid') ? 'mermaid' : (args.includes('--dot') ? 'dot' : 'json')));
286
+
287
+ try {
288
+ const res = await buildKnowledgeGraph({ allVaults, node, format });
289
+ if (json || format === 'json') {
290
+ console.log(JSON.stringify(res, null, 2));
291
+ } else if (format === 'mermaid') {
292
+ console.log(res.mermaid);
293
+ } else if (format === 'dot') {
294
+ console.log(res.dot);
295
+ }
296
+ } catch (err) {
297
+ if (json) console.log(JSON.stringify({ ok: false, error: err.message }));
298
+ else console.error(`OpenWiki graph error: ${err.message}`);
299
+ process.exitCode = 1;
300
+ }
301
+ }
302
+
303
+ async function tokenCommand(args) {
304
+ const json = args.includes("--json");
305
+ const verb = args.find((a) => !a.startsWith('--')) || 'status';
306
+
307
+ try {
308
+ if (verb === 'status') {
309
+ const res = getTokenStatus();
310
+ if (json) {
311
+ console.log(JSON.stringify(res, null, 2));
312
+ } else {
313
+ console.log('\nOpenWiki Token Status (Total Recall Auth Store):');
314
+ for (const [provider, t] of Object.entries(res.tokens)) {
315
+ const icon = t.configured ? '✔' : '✖';
316
+ console.log(` ${icon} ${provider.padEnd(12)} : ${t.key} (${t.status})`);
317
+ }
318
+ console.log('\nTo set a token: total-recall openwiki token set <openrouter|google> <key>');
319
+ console.log('Or via stdin: echo "$KEY" | total-recall openwiki token set <provider> --stdin\n');
320
+ }
321
+ return;
322
+ }
323
+
324
+ if (verb === 'set') {
325
+ const clean = args.filter((a) => !a.startsWith('--'));
326
+ const provider = clean[1];
327
+ let value = clean[2];
328
+ if (args.includes('--stdin')) {
329
+ value = fs.readFileSync(0, 'utf8').trim();
330
+ }
331
+ if (!provider || !value) {
332
+ throw new Error('Provider and secret value required: openwiki token set <provider> <value> (or --stdin)');
333
+ }
334
+ const res = setTokenValue(provider, value);
335
+ if (json) console.log(JSON.stringify(res, null, 2));
336
+ else console.log(`✔ ${res.message}`);
337
+ return;
338
+ }
339
+
340
+ throw new Error(`Unknown token action '${verb}'. Supported: status, set`);
341
+ } catch (err) {
342
+ if (json) console.log(JSON.stringify({ ok: false, error: err.message }));
343
+ else console.error(`OpenWiki token error: ${err.message}`);
344
+ process.exitCode = 1;
345
+ }
346
+ }
347
+
348
+ async function lintCommand(args) {
349
+ const json = args.includes("--json");
350
+ const allVaults = args.includes("--all-vaults") || args.includes("--all");
351
+ const nodeArg = args.find((a) => a.startsWith('--node='));
352
+ const nodeIndex = args.indexOf('--node');
353
+ const node = nodeArg
354
+ ? nodeArg.slice('--node='.length)
355
+ : (nodeIndex >= 0 && args[nodeIndex + 1] && !args[nodeIndex + 1].startsWith('--') ? args[nodeIndex + 1] : null);
356
+
357
+ try {
358
+ const res = await lintWikiHubs({ allVaults, node });
359
+ if (json) {
360
+ console.log(JSON.stringify(res, null, 2));
64
361
  } else {
65
- console.log(`Discovered ${hubs.length} OpenWiki Hub(s) in ${targetDir}:`);
66
- for (const h of hubs) {
67
- console.log(` - ${h.title}: ${h.path} (${h.lines} lines, ${h.size_bytes} bytes)`);
362
+ console.log(`\nOpenWiki Documentation Health & Hygiene:`);
363
+ console.log(` Health Score : ${res.healthScore}%`);
364
+ console.log(` Total Hubs Scanned : ${res.totalHubs}`);
365
+ console.log(` Total Links Tested : ${res.totalLinks}`);
366
+ console.log(` Broken Links : ${res.brokenLinksCount}`);
367
+ console.log(` Orphaned Hubs : ${res.orphanCount}`);
368
+
369
+ if (res.issues.length > 0) {
370
+ console.log('\nIssues Found:');
371
+ for (const iss of res.issues) {
372
+ console.log(` ✖ [${iss.type}] in ${path.basename(iss.file)}: ${iss.message}`);
373
+ }
374
+ } else {
375
+ console.log('\n✔ All wiki links and headers verified clean!');
68
376
  }
377
+ console.log('');
378
+ }
379
+ if (!res.ok) process.exitCode = 1;
380
+ } catch (err) {
381
+ if (json) console.log(JSON.stringify({ ok: false, error: err.message }));
382
+ else console.error(`OpenWiki lint error: ${err.message}`);
383
+ process.exitCode = 1;
384
+ }
385
+ }
386
+
387
+ async function generateCommand(args) {
388
+ const json = args.includes("--json");
389
+ const force = args.includes("--force");
390
+ const dryRun = args.includes("--dry-run");
391
+ const repoIndex = args.indexOf('--repo');
392
+ const repoArg = args.find((a) => a.startsWith('--repo='));
393
+ const repo = repoArg
394
+ ? repoArg.slice('--repo='.length)
395
+ : (repoIndex >= 0 && args[repoIndex + 1] && !args[repoIndex + 1].startsWith('--') ? args[repoIndex + 1] : null);
396
+
397
+ const nodeArg = args.find((a) => a.startsWith('--node='));
398
+ const nodeIndex = args.indexOf('--node');
399
+ const node = nodeArg
400
+ ? nodeArg.slice('--node='.length)
401
+ : (nodeIndex >= 0 && args[nodeIndex + 1] && !args[nodeIndex + 1].startsWith('--') ? args[nodeIndex + 1] : null);
402
+
403
+ try {
404
+ const res = await generateWiki({
405
+ repo,
406
+ force,
407
+ dryRun,
408
+ node,
409
+ onProgress: (msg) => {
410
+ if (!json) console.log(` ▶ ${msg}`);
411
+ },
412
+ });
413
+
414
+ if (json) {
415
+ console.log(JSON.stringify(res, null, 2));
416
+ } else {
417
+ console.log(`\n✔ Generated documentation for ${res.repoName} (${Math.round((res.durationMs || 0)/1000)}s) via ${res.generatorVia}`);
418
+ console.log(` Output: ${res.outputDir}\n`);
69
419
  }
420
+ } catch (err) {
421
+ if (json) console.log(JSON.stringify({ ok: false, error: err.message }));
422
+ else console.error(`OpenWiki generate error: ${err.message}`);
423
+ process.exitCode = 1;
70
424
  }
71
425
  }
72
426
 
@@ -79,24 +433,223 @@ async function ingestCommand(args) {
79
433
  }
80
434
  }
81
435
 
436
+ /**
437
+ * Surveillance command for monitoring the upstream open-source OpenWiki package.
438
+ */
439
+ async function watchCommand(args) {
440
+ const json = args.includes('--json');
441
+ const checkOnly = args.includes('--check-only');
442
+ const announce = args.includes('--announce') || (!checkOnly && !args.includes('--no-announce'));
443
+ const propagate = args.includes('--propagate');
444
+
445
+ const status = await checkUpstreamStatus();
446
+ let announcement = null;
447
+ let propagation = null;
448
+
449
+ if (status.ok && status.updateAvailable) {
450
+ if (announce) {
451
+ announcement = await announceNewVersion({
452
+ latestVersion: status.latestVersion,
453
+ currentVersion: status.currentVersion,
454
+ releaseNotesUrl: status.releaseNotesUrl
455
+ });
456
+ }
457
+ if (propagate) {
458
+ propagation = await propagateUpdates({ force: true });
459
+ }
460
+ }
461
+
462
+ const payload = {
463
+ ok: status.ok,
464
+ status,
465
+ announcement,
466
+ propagation
467
+ };
468
+
469
+ if (json) {
470
+ console.log(JSON.stringify(payload, null, 2));
471
+ } else {
472
+ console.log('\nOpenWiki Upstream Watcher:');
473
+ console.log(` Active Recorded Version : v${status.currentVersion}`);
474
+ console.log(` Latest Upstream Version : v${status.latestVersion}`);
475
+ console.log(` Update Available : ${status.updateAvailable ? 'YES (Increment Detected)' : 'No (Current)'}`);
476
+ if (status.releaseNotesUrl) {
477
+ console.log(` Release Notes : ${status.releaseNotesUrl}`);
478
+ }
479
+ if (announcement?.announced) {
480
+ console.log(` Operator Announcement : Dispatched successfully`);
481
+ }
482
+ if (propagation) {
483
+ console.log(` Fleet Propagation : ${propagation.ok ? 'Completed' : 'Failed'} (${propagation.updatedCount} projects updated)`);
484
+ }
485
+ console.log('');
486
+ }
487
+
488
+ if (!status.ok) process.exitCode = 1;
489
+ }
490
+
491
+ /**
492
+ * Propagate updated generator across registered repositories.
493
+ */
494
+ async function propagateCommand(args) {
495
+ if (args.includes('--help') || args.includes('-h')) {
496
+ console.log(`
497
+ Usage:
498
+ total-recall openwiki propagate [options]
499
+
500
+ Options:
501
+ --repo <name> Limit propagation to a specific repository
502
+ --force Force regeneration even if not due
503
+ --dry-run Preview propagation actions without writing
504
+ --json Output result as JSON
505
+ --help, -h Show this help
506
+ `);
507
+ return;
508
+ }
509
+ const json = args.includes('--json');
510
+ const force = args.includes('--force');
511
+ const dryRun = args.includes('--dry-run');
512
+ const repoIndex = args.indexOf('--repo');
513
+ const onlyArg = args.find((a) => a.startsWith('--repo='));
514
+ const repo = onlyArg
515
+ ? onlyArg.slice('--repo='.length)
516
+ : (repoIndex >= 0 && args[repoIndex + 1] && !args[repoIndex + 1].startsWith('--') ? args[repoIndex + 1] : null);
517
+
518
+ const result = await propagateUpdates({ repo, force, dryRun });
519
+
520
+ if (json) {
521
+ console.log(JSON.stringify(result, null, 2));
522
+ } else {
523
+ console.log(`\nOpenWiki Fleet Propagation (${dryRun ? 'DRY RUN' : 'APPLY'}):`);
524
+ console.log(` Eligible Repositories : ${result.eligibleCount || 0}`);
525
+ console.log(` Updated Repositories : ${result.updatedCount || 0}`);
526
+ console.log(` Failed Repositories : ${result.failedCount || 0}`);
527
+ if (Array.isArray(result.reports)) {
528
+ for (const r of result.reports) {
529
+ console.log(` • ${r.name}: ${r.action}${r.reason ? ` — ${r.reason}` : ''}`);
530
+ }
531
+ }
532
+ console.log('');
533
+ }
534
+
535
+ if (!result.ok) process.exitCode = 1;
536
+ }
537
+
538
+ /**
539
+ * Configure OpenWiki generation parameters and cheap model routing.
540
+ */
541
+ async function configCommand(args) {
542
+ const json = args.includes('--json');
543
+ const cleanArgs = args.filter((a) => a !== '--json');
544
+ const verb = cleanArgs[0] || 'show';
545
+
546
+ try {
547
+ if (verb === 'show') {
548
+ const config = loadOpenWikiConfig();
549
+ const configPath = resolveConfigPath();
550
+ const payload = { ok: true, path: configPath, config };
551
+ if (json) console.log(JSON.stringify(payload, null, 2));
552
+ else {
553
+ console.log(`\nOpenWiki Configuration (${configPath}):`);
554
+ console.log(` Provider : ${config.provider}`);
555
+ console.log(` Model : ${config.model}`);
556
+ console.log(` Fallback Model : ${config.fallback_model}`);
557
+ console.log(` Max Tokens : ${config.max_tokens}`);
558
+ console.log(` Timeout (ms) : ${config.timeout_ms}\n`);
559
+ }
560
+ return;
561
+ }
562
+
563
+ if (verb === 'get') {
564
+ const key = cleanArgs[1];
565
+ if (!key) {
566
+ throw new Error('Key argument required: openwiki config get <key>');
567
+ }
568
+ if (!ALLOWED_CONFIG_KEYS.includes(key)) {
569
+ throw new Error(`Unknown configuration key '${key}'. Allowed: ${ALLOWED_CONFIG_KEYS.join(', ')}`);
570
+ }
571
+ const config = loadOpenWikiConfig();
572
+ const value = config[key];
573
+ if (json) console.log(JSON.stringify({ ok: true, key, value }));
574
+ else console.log(value);
575
+ return;
576
+ }
577
+
578
+ if (verb === 'set') {
579
+ const key = cleanArgs[1];
580
+ const value = cleanArgs[2];
581
+ if (!key || value === undefined) {
582
+ throw new Error('Key and value required: openwiki config set <key> <value>');
583
+ }
584
+ const updated = setOpenWikiConfigValue(key, value);
585
+ const payload = { ok: true, key, value: updated[key], config: updated };
586
+ if (json) console.log(JSON.stringify(payload, null, 2));
587
+ else console.log(`OpenWiki configuration updated: ${key} = ${updated[key]}`);
588
+ return;
589
+ }
590
+
591
+ if (verb === 'reset') {
592
+ const reset = resetOpenWikiConfig();
593
+ const payload = { ok: true, message: 'Configuration reset to defaults', config: reset };
594
+ if (json) console.log(JSON.stringify(payload, null, 2));
595
+ else console.log('OpenWiki configuration reset to defaults');
596
+ return;
597
+ }
598
+
599
+ throw new Error(`Unknown config action '${verb}'. Supported verbs: show, get, set, reset`);
600
+ } catch (err) {
601
+ if (json) console.log(JSON.stringify({ ok: false, error: err.message }));
602
+ else console.error(`OpenWiki config error: ${err.message}`);
603
+ process.exitCode = 1;
604
+ }
605
+ }
606
+
82
607
  const IMPLEMENTED = {};
83
608
  IMPLEMENTED.probe = probeCommand;
84
609
  IMPLEMENTED.hubs = hubsCommand;
85
610
  IMPLEMENTED.ingest = ingestCommand;
86
611
  IMPLEMENTED.bootstrap = runBootstrapCommand;
612
+ IMPLEMENTED.refresh = refreshCommand;
613
+ IMPLEMENTED.watch = watchCommand;
614
+ IMPLEMENTED.propagate = propagateCommand;
615
+ IMPLEMENTED.config = configCommand;
616
+ IMPLEMENTED.search = searchCommand;
617
+ IMPLEMENTED.graph = graphCommand;
618
+ IMPLEMENTED.token = tokenCommand;
619
+ IMPLEMENTED.lint = lintCommand;
620
+ IMPLEMENTED.generate = generateCommand;
87
621
 
622
+ export async function run(argv = process.argv) {
623
+ let rawArgs = Array.isArray(argv) && argv.length > 0 ? [...argv] : process.argv.slice(2);
624
+ while (rawArgs.length > 0 && (
625
+ rawArgs[0].endsWith('node') ||
626
+ rawArgs[0].endsWith('total-recall') ||
627
+ rawArgs[0].endsWith('cli.mjs') ||
628
+ rawArgs[0] === 'openwiki'
629
+ )) {
630
+ rawArgs.shift();
631
+ }
88
632
 
89
- export async function run(argv = []) {
90
- const args = Array.isArray(argv) ? argv.slice(3) : [];
91
- const sub = args[0] || "status";
92
- const json = args.includes("--json");
633
+ const sub = rawArgs[0] || "status";
634
+ const json = rawArgs.includes("--json");
93
635
 
94
636
  if (sub === "status") {
95
- const status = { plugin: "openwiki", implemented: Object.keys(IMPLEMENTED) };
96
- if (json) console.log(JSON.stringify(status));
97
- else console.log(status.implemented.length
98
- ? `OpenWiki: implemented subcommands: ${status.implemented.join(", ")}`
99
- : `OpenWiki: scaffold only — no subcommands implemented yet.`);
637
+ const upstream = readUpstreamState();
638
+ const status = {
639
+ plugin: "openwiki",
640
+ implemented: Object.keys(IMPLEMENTED),
641
+ upstream: {
642
+ recordedVersion: upstream.currentRecordedVersion,
643
+ latestVersion: upstream.latestKnownVersion,
644
+ lastChecked: upstream.lastCheckedAt,
645
+ lastPropagated: upstream.lastPropagatedAt
646
+ }
647
+ };
648
+ if (json) console.log(JSON.stringify(status, null, 2));
649
+ else {
650
+ console.log(`OpenWiki: implemented subcommands: ${status.implemented.join(", ")}`);
651
+ console.log(`Upstream Status: recorded v${status.upstream.recordedVersion}, latest v${status.upstream.latestVersion}`);
652
+ }
100
653
  return;
101
654
  }
102
655
 
@@ -108,20 +661,45 @@ export async function run(argv = []) {
108
661
  total-recall openwiki <subcommand> [options]
109
662
 
110
663
  Subcommands:
111
- status Show OpenWiki plugin status
664
+ status Show OpenWiki plugin and upstream status
112
665
  probe [--json] Check subsystem health
113
- hubs [--json] List indexed documentation hubs
666
+ hubs [--all-vaults] [--node <node>] [--json]
667
+ List indexed documentation hubs across workspace, all vaults, or mesh
668
+ search <query> [--all-vaults] [--node <node>] [--limit <n>] [--json]
669
+ Evidence passage search across knowledge hubs and vaults
670
+ graph [--all-vaults] [--node <node>] [--format json|mermaid|dot] [--json]
671
+ Extract knowledge graph topology, nodes, and inter-hub links
672
+ generate [--repo <name>] [--path <dir>] [--output <dir>] [--force] [--dry-run] [--node <node>] [--json]
673
+ Targeted on-demand documentation generation
674
+ token [status|set <provider> <value>] [--stdin] [--json]
675
+ Check or securely configure Total Recall secrets store keys
676
+ lint [--all-vaults] [--node <node>] [--json]
677
+ Validate link integrity, dead targets, and orphaned hubs
114
678
  ingest <path> [options] Ingest Markdown files into knowledge graph
115
679
  bootstrap --brain <path> [--project-root <path>] [--dry-run] [--json]
116
680
  Create missing plugin-owned documentation pages
681
+ refresh [--repo <name>] [--apply] [--force] [--json]
682
+ Report which registered projects' generated pages are stale;
683
+ --apply regenerates stale pages (writes openwiki/ only)
684
+ config [show|get <key>|set <key> <value>|reset] [--json]
685
+ Configure generator provider, model, tokens, and timeout
686
+ watch [--check-only] [--announce] [--propagate] [--json]
687
+ Watch upstream OpenWiki repository and announce new versions
688
+ propagate [--repo <name>] [--force] [--dry-run] [--json]
689
+ Propagate updated generator across registered repositories
117
690
  `);
118
691
  return;
119
692
  }
120
693
 
121
- if (Object.hasOwn(IMPLEMENTED, sub)) return IMPLEMENTED[sub](args.slice(1));
694
+ if (Object.hasOwn(IMPLEMENTED, sub)) return IMPLEMENTED[sub](rawArgs.slice(1));
122
695
 
123
696
  if (json) console.log(JSON.stringify({ ok: false, error: `OpenWiki operation is not implemented: ${sub}` }));
124
697
  console.error(`OpenWiki: "${sub}" is not implemented.`);
125
698
  process.exitCode = 2;
126
699
  }
700
+
701
+ if (process.argv[1] && (process.argv[1].endsWith('cli.mjs') || process.argv[1].endsWith('tr-plugin-openwiki'))) {
702
+ run(process.argv);
703
+ }
704
+
127
705
  export default run;