total-recall-brain 3.46.2 → 3.47.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 (89) hide show
  1. package/bin/total-recall.mjs +5 -0
  2. package/frontend/dist/assets/index-CpsWeV4X.css +1 -0
  3. package/frontend/dist/assets/{index-D1goIPjC.js → index-poecbHSl.js} +100 -100
  4. package/frontend/dist/brand/total-recall-lockup-transparent.png +0 -0
  5. package/frontend/dist/index.html +2 -2
  6. package/frontend/dist/sw.js +1 -1
  7. package/frontend/src/App.tsx +15 -81
  8. package/frontend/src/components/DashboardOverview.tsx +164 -0
  9. package/frontend/src/components/brand/BrandMark.tsx +8 -6
  10. package/frontend/src/components/plugins/PluginWorkspace.tsx +112 -57
  11. package/frontend/src/components/workspace/PluginHome.tsx +3 -3
  12. package/frontend/src/index.css +43 -11
  13. package/package.json +1 -1
  14. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +4 -4
  15. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +3 -2
  16. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +30 -5
  17. package/scaffold/.agent/skills/total-recall/plugins/auth/skills/auth/SKILL.md +35 -0
  18. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/home.js +494 -79
  19. package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +4 -4
  20. package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +3 -2
  21. package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +33 -8
  22. package/scaffold/.agent/skills/total-recall/plugins/automations/skills/automations/SKILL.md +29 -0
  23. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/dream-consolidation.md +17 -0
  24. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +4 -4
  25. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +3 -2
  26. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +31 -4
  27. package/scaffold/.agent/skills/total-recall/plugins/code-quality/skills/code-quality/SKILL.md +27 -0
  28. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +4 -4
  29. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  30. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +17 -3
  31. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +4 -4
  32. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +3 -2
  33. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +24 -4
  34. package/scaffold/.agent/skills/total-recall/plugins/embeddings/skills/embeddings/SKILL.md +25 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/git/package-lock.json +6 -6
  36. package/scaffold/.agent/skills/total-recall/plugins/git/package.json +3 -2
  37. package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +300 -58
  38. package/scaffold/.agent/skills/total-recall/plugins/git/skills/git/SKILL.md +36 -0
  39. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +5 -33
  40. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/git-fleet-ops.md +16 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/git-publish.md +16 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +4 -4
  43. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -2
  44. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +47 -14
  45. package/scaffold/.agent/skills/total-recall/plugins/mesh/skills/mesh/SKILL.md +36 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +6 -3
  47. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +1 -1
  48. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +5 -2
  49. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +4 -2
  50. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +28 -9
  51. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +7 -1
  52. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/model-selection.mjs +59 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +3 -3
  54. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +9 -3
  55. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +50 -6
  56. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/session-controls.md +16 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +175 -17
  58. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +6 -6
  59. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +3 -2
  60. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +242 -31
  61. package/scaffold/.agent/skills/total-recall/plugins/openwiki/skills/openwiki/SKILL.md +32 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +41 -15
  63. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +29 -22
  64. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +20 -33
  65. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/lint.mjs +42 -49
  66. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/markdown-links.mjs +112 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +3 -2
  68. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +219 -65
  69. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +2 -1
  70. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +21 -3
  71. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +51 -2
  72. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +4 -4
  73. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
  74. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +22 -5
  75. package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +3 -2
  76. package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +169 -37
  77. package/scaffold/.agent/skills/total-recall/plugins/ssss/skills/ssss/SKILL.md +30 -0
  78. package/src/cli/context.mjs +6 -2
  79. package/src/cli/plugin/create.mjs +2 -1
  80. package/src/cli/plugin/monetization.mjs +66 -32
  81. package/src/cli/plugin/search.mjs +25 -3
  82. package/src/cli/preflight.mjs +40 -6
  83. package/src/core/context-compactor.mjs +2 -0
  84. package/src/core/gate-mode.mjs +13 -24
  85. package/src/core/plugin-distribution.mjs +35 -1
  86. package/src/core/preflight-gate.mjs +28 -8
  87. package/src/core/preflight-receipt.mjs +11 -0
  88. package/src/server/routes/vault.mjs +1 -0
  89. package/frontend/dist/assets/index-BRYE1jmv.css +0 -1
@@ -5,10 +5,13 @@
5
5
  */
6
6
  import fs from "node:fs";
7
7
  import path from "node:path";
8
+ import { pathToFileURL } from "node:url";
8
9
  import { runBootstrapCommand } from './src/bootstrap.mjs';
9
- import { describeRetention, hasWiki, isDue, readProjects, refreshProject, refreshProjectAsync, resolveKey, staleness } from './src/refresh.mjs';
10
+ import { CREDIT_REFUSAL, NO_MODEL, describeRetention, hasWiki, isDue, readProjects, refreshProject, refreshProjectAsync, resolveKey, staleness } from './src/refresh.mjs';
11
+
12
+ const DEFAULT_REPO_CONCURRENCY = 2;
10
13
  import { watchUpstream, propagateUpdates, readUpstreamState, bundledGeneratorVersion } from './src/upstream-watcher.mjs';
11
- import { loadOpenWikiConfig, setOpenWikiConfigValue, resetOpenWikiConfig, resolveConfigPath, ALLOWED_CONFIG_KEYS, ALLOWED_PROVIDERS } from './src/config.mjs';
14
+ import { loadOpenWikiConfig, setOpenWikiConfigValue, resetOpenWikiConfig, resolveConfigPath, setExcludedRoots, ALLOWED_CONFIG_KEYS, ALLOWED_PROVIDERS } from './src/config.mjs';
12
15
  import { discoverAllHubs } from './src/mesh-vaults.mjs';
13
16
  import { searchWikiHubs } from './src/search.mjs';
14
17
  import { buildKnowledgeGraph } from './src/graph.mjs';
@@ -26,12 +29,14 @@ import { refreshDiagnostics } from './src/refresh-diagnostics.mjs';
26
29
  * Defaults to a report; --apply performs the refreshes, because regenerating a
27
30
  * repository's documentation is a write to that repository.
28
31
  */
32
+ // OpenRouter answers 402 when the balance cannot cover a request.
33
+
29
34
  export async function refreshCommand(args, {
30
35
  read = readProjects, dueFor = isDue, refresh = refreshProjectAsync,
31
- getKey = resolveKey, configFor = loadOpenWikiConfig,
36
+ getKey = resolveKey, configFor = loadOpenWikiConfig, clock = Date.now,
32
37
  } = {}) {
33
38
  if (args.includes('--help') || args.includes('-h')) {
34
- console.log('Usage: total-recall openwiki refresh [--repo <name>] [--apply] [--force] [--diagnostics [--slot <local-minute>]] [--json]');
39
+ console.log('Usage: total-recall openwiki refresh [--repo <name>] [--apply [--budget-ms <n>]] [--force] [--diagnostics [--slot <local-minute>]] [--json]');
35
40
  return;
36
41
  }
37
42
  const json = args.includes('--json');
@@ -78,8 +83,14 @@ export async function refreshCommand(args, {
78
83
  continue;
79
84
  }
80
85
  if (!state.due) {
81
- if (state.skipped) {
86
+ if (state.excluded) {
87
+ reports.push({ ...project, action: 'excluded', reason: state.reason });
88
+ } else if (state.skipped) {
82
89
  reports.push({ ...project, action: 'skipped', reason: state.reason });
90
+ } else if (state.uninitialized && !state.held) {
91
+ reports.push({ ...project, action: 'no-wiki', uninitialized: true, reason: state.reason, generatedAt: state.generatedAt });
92
+ } else if (state.held) {
93
+ reports.push({ ...project, action: 'held', uninitialized: Boolean(state.uninitialized), reason: state.reason, generationCommit: state.generationCommit, generationTime: state.generationTime, generatedAt: state.generatedAt, commitsSince: state.commitsSince });
83
94
  } else {
84
95
  reports.push({ ...project, action: 'current', reason: state.reason, generationCommit: state.generationCommit, generationTime: state.generationTime, generatedAt: state.generatedAt, commitsSince: state.commitsSince });
85
96
  }
@@ -102,6 +113,12 @@ export async function refreshCommand(args, {
102
113
  let payloadBudgetNote = null;
103
114
  if (apply && due.length) {
104
115
  const config = configFor();
116
+ if (!String(config.model || '').trim()) {
117
+ if (json) console.log(JSON.stringify({ ok: false, error: NO_MODEL, reports }));
118
+ else console.error(`OpenWiki refresh: ${NO_MODEL}`);
119
+ process.exitCode = 1;
120
+ return;
121
+ }
105
122
  const { key, from } = getKey({ provider: config.provider });
106
123
  if (!key) {
107
124
  const keyName = config.provider === 'google' ? 'GOOGLE_API_KEY' : 'OPENROUTER_API_KEY';
@@ -111,21 +128,37 @@ export async function refreshCommand(args, {
111
128
  process.exitCode = 1;
112
129
  return;
113
130
  }
131
+ // Repositories refreshed at once. Each one is a separate paid generator run, so
132
+ // the default stays low; OpenWiki's own page concurrency is left at its default.
114
133
  const concurrencyArg = args.find((a) => a.startsWith('--concurrency='));
115
134
  const concurrencyIndex = args.indexOf('--concurrency');
116
135
  const concurrency = Math.max(1, parseInt(
117
- concurrencyArg ? concurrencyArg.slice('--concurrency='.length) : (concurrencyIndex >= 0 ? args[concurrencyIndex + 1] : '8'),
136
+ concurrencyArg ? concurrencyArg.slice('--concurrency='.length) : (concurrencyIndex >= 0 ? args[concurrencyIndex + 1] : String(DEFAULT_REPO_CONCURRENCY)),
118
137
  10
119
- ) || 8);
138
+ ) || DEFAULT_REPO_CONCURRENCY);
139
+
140
+ // A scheduled run passes the time it has left before the task runner kills
141
+ // it. A generation started without room to finish is killed part-way, marked
142
+ // interrupted and paid for again on the next attempt, so none is started.
143
+ const budgetArg = args.find((a) => a.startsWith('--budget-ms='));
144
+ const budgetIndex = args.indexOf('--budget-ms');
145
+ const budgetMs = Number(budgetArg ? budgetArg.slice('--budget-ms='.length) : (budgetIndex >= 0 ? args[budgetIndex + 1] : NaN));
146
+ const generationMs = Number.isInteger(config.timeout_ms) ? config.timeout_ms : 300000;
147
+ const dispatchStarted = clock();
148
+ let stopReason = null;
120
149
 
121
150
  if (!json) {
122
- console.log(`OpenWiki refresh (apply): dispatching ${due.length} projects with concurrency ${concurrency}`);
151
+ console.log(`OpenWiki refresh (apply): dispatching ${due.length} projects with concurrency ${concurrency}${Number.isFinite(budgetMs) ? ` within ${Math.round(budgetMs / 1000)}s` : ''}`);
123
152
  }
124
153
 
125
154
  let queueIndex = 0;
126
155
  let completedCount = 0;
127
156
  const worker = async () => {
128
157
  while (queueIndex < due.length) {
158
+ if (!stopReason && Number.isFinite(budgetMs) && clock() - dispatchStarted + generationMs > budgetMs) {
159
+ stopReason = `not started: fewer than ${Math.round(generationMs / 1000)}s left in this run's ${Math.round(budgetMs / 1000)}s budget`;
160
+ }
161
+ if (stopReason) break;
129
162
  const itemIndex = queueIndex++;
130
163
  const entry = due[itemIndex];
131
164
  const mode = entry.mode || (entry.uninitialized ? 'init' : 'update');
@@ -142,6 +175,9 @@ export async function refreshCommand(args, {
142
175
  result = { action: 'failed', reason: error.message.split(key).join('[REDACTED]') };
143
176
  }
144
177
  completedCount++;
178
+ if (!stopReason && CREDIT_REFUSAL.test(String(result.reason || ''))) {
179
+ stopReason = `not started: the provider refused ${entry.name} for credit; add credit, then refresh again`;
180
+ }
145
181
  const index = reports.findIndex((r) => r.root === entry.root);
146
182
  reports[index] = { ...reports[index], ...result, credential: from };
147
183
  if (!json) {
@@ -154,6 +190,11 @@ export async function refreshCommand(args, {
154
190
 
155
191
  const workers = Array.from({ length: Math.min(concurrency, due.length) }, () => worker());
156
192
  await Promise.all(workers);
193
+ for (const entry of due.slice(queueIndex)) {
194
+ const index = reports.findIndex((r) => r.root === entry.root);
195
+ reports[index] = { ...reports[index], action: 'deferred', reason: stopReason };
196
+ }
197
+ if (stopReason && queueIndex < due.length) payloadBudgetNote = `${due.length - queueIndex} project(s) ${stopReason}.`;
157
198
  }
158
199
 
159
200
  const failed = reports.filter((r) => r.action === 'failed');
@@ -165,12 +206,15 @@ export async function refreshCommand(args, {
165
206
  note: payloadBudgetNote,
166
207
  counts: {
167
208
  registered: projects.length,
168
- withPages: reports.filter((r) => r.action !== 'skipped' && !r.uninitialized).length,
209
+ withPages: reports.filter((r) => !['skipped', 'excluded'].includes(r.action) && !r.uninitialized).length,
169
210
  due: due.length,
170
211
  uninitialized: reports.filter((r) => r.uninitialized).length,
212
+ excluded: reports.filter((r) => r.action === 'excluded').length,
171
213
  refreshed: reports.filter((r) => r.action === 'refreshed' || r.action === 'initialized').length,
172
214
  failed: failed.length,
173
215
  partial: partial.length,
216
+ held: reports.filter((r) => r.action === 'held').length,
217
+ deferred: reports.filter((r) => r.action === 'deferred').length,
174
218
  skipped: reports.filter((r) => r.action === 'skipped').length,
175
219
  current: reports.filter((r) => r.action === 'current').length,
176
220
  },
@@ -178,11 +222,11 @@ export async function refreshCommand(args, {
178
222
  };
179
223
  if (json) console.log(JSON.stringify(payload, null, 2));
180
224
  else {
181
- console.log(`OpenWiki refresh (${apply ? 'apply' : 'report'}): ${payload.counts.registered} registered, ${payload.counts.due} due (${payload.counts.uninitialized} uninitialized)`);
225
+ console.log(`OpenWiki refresh (${apply ? 'apply' : 'report'}): ${payload.counts.registered} registered, ${payload.counts.due} due, ${payload.counts.uninitialized} without a finished wiki, ${payload.counts.excluded} excluded`);
182
226
  // Put failures last so bounded scheduler tails retain their causes.
183
227
  for (const r of [...reports].sort((a, b) => Number(['failed', 'partial'].includes(a.action)) - Number(['failed', 'partial'].includes(b.action)))) {
184
- if (r.action === 'current') continue;
185
- const age = r.action === 'skipped' ? 'outside repository generation' : r.generationTime ? `generated ${r.generationTime} at ${r.generationCommit}` : r.uninitialized ? 'uninitialized' : (r.generatedAt ? `last attempt ${r.generatedAt}; generation commit unknown` : 'never generated');
228
+ if (r.action === 'current' || r.action === 'excluded') continue;
229
+ const age = r.action === 'skipped' ? 'outside repository generation' : r.generationTime ? `generated ${r.generationTime} at ${r.generationCommit}` : r.uninitialized ? 'no finished wiki' : (r.generatedAt ? `last attempt ${r.generatedAt}; generation commit unknown` : 'never generated');
186
230
  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}` : ''}`);
187
231
  }
188
232
  if (payloadBudgetNote) console.log(payloadBudgetNote);
@@ -438,6 +482,8 @@ async function generateCommand(args) {
438
482
 
439
483
  if (json) {
440
484
  console.log(JSON.stringify(res, null, 2));
485
+ } else if (res.dryRun) {
486
+ console.log(`\n${res.message}\n`);
441
487
  } else {
442
488
  console.log(`\n✔ Generated documentation for ${res.repoName} (${Math.round((res.durationMs || 0)/1000)}s) via ${res.generatorVia}`);
443
489
  console.log(` Output: ${res.outputDir}\n`);
@@ -572,8 +618,8 @@ async function configCommand(args) {
572
618
  else {
573
619
  console.log(`\nOpenWiki Configuration (${configPath}):`);
574
620
  console.log(` Provider : ${config.provider}`);
575
- console.log(` Model : ${config.model}`);
576
- console.log(` Fallback Model : ${config.fallback_model}`);
621
+ console.log(` Model : ${config.model || 'not set (refreshes stop until one is chosen)'}`);
622
+ console.log(` Fallback Model : ${config.fallback_model || 'not set'}`);
577
623
  console.log(` Max Tokens : ${config.max_tokens}`);
578
624
  console.log(` Timeout (ms) : ${config.timeout_ms}\n`);
579
625
  }
@@ -624,9 +670,116 @@ async function configCommand(args) {
624
670
  }
625
671
  }
626
672
 
673
+ /**
674
+ * The scheduled run. The Automations runner kills it at the task's declared
675
+ * timeout_ms, so it dispatches only generations that can finish a minute
676
+ * before that.
677
+ */
678
+ export function autoRefreshBudgetMs(manifestPath = new URL('./plugin.json', import.meta.url)) {
679
+ let timeout = null;
680
+ try {
681
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
682
+ timeout = manifest.tasks?.find((task) => task.command === 'auto-refresh')?.timeout_ms ?? null;
683
+ } catch { /* no readable manifest: use the runner's own limit */ }
684
+ return Math.max(60000, (Number.isInteger(timeout) ? timeout : 600000) - 60000);
685
+ }
686
+
687
+ async function loadScheduleCore() {
688
+ const root = process.env.TR_PACKAGE_ROOT;
689
+ if (!root) throw new Error('Run through total-recall openwiki schedule');
690
+ const load = (module) => import(pathToFileURL(path.join(root, 'src', 'core', module)).href);
691
+ const [loader, store, mesh] = await Promise.all([load('plugin-loader.mjs'), load('plugin-store.mjs'), load('mesh.mjs')]);
692
+ return { getPlugin: loader.getPlugin, readPluginRecord: store.readPluginRecord, patchPluginRecord: store.patchPluginRecord, getMeshSelf: mesh.getMeshSelf };
693
+ }
694
+
695
+ /**
696
+ * Where the nightly refresh runs. Like other selected-node tasks, the placement is
697
+ * task_nodes['auto-refresh'] in this plugin's record; with none, the task stays idle.
698
+ * Pick the node that holds the repository checkouts.
699
+ */
700
+ export async function scheduleCommand(args = [], { core = null, config = loadOpenWikiConfig } = {}) {
701
+ const json = args.includes('--json');
702
+ const usage = 'Usage: total-recall openwiki schedule [--node <mesh hostname>|--node self|--off] [--json]';
703
+ try {
704
+ if (args.includes('--help') || args.includes('-h')) { console.log(usage); return; }
705
+ const nodeIndex = args.indexOf('--node');
706
+ const nodeArg = nodeIndex >= 0 ? args[nodeIndex + 1] : null;
707
+ if (nodeIndex >= 0 && (!nodeArg || nodeArg.startsWith('--'))) throw new Error(usage);
708
+ const off = args.includes('--off');
709
+ if (off && nodeArg) throw new Error(usage);
710
+ const owner = core || await loadScheduleCore();
711
+ const plugin = owner.getPlugin('openwiki');
712
+ if (!plugin?.valid) throw new Error('Install a valid OpenWiki plugin first');
713
+ const record = owner.readPluginRecord(plugin) || {};
714
+ const task = plugin.manifest?.tasks?.find((entry) => entry.command === 'auto-refresh') || null;
715
+ let node = record.task_nodes?.['auto-refresh'] || null;
716
+ if (off || nodeArg) {
717
+ node = off ? null : nodeArg === 'self' ? owner.getMeshSelf()?.hostname || null : nodeArg;
718
+ if (!off && !node) throw new Error('This machine has no mesh hostname; name the node with --node <hostname>');
719
+ const taskNodes = { ...(record.task_nodes || {}) };
720
+ if (node) taskNodes['auto-refresh'] = node;
721
+ else delete taskNodes['auto-refresh'];
722
+ await owner.patchPluginRecord(plugin, { task_nodes: taskNodes });
723
+ }
724
+ const model = config().model || null;
725
+ const payload = { ok: true, task: 'auto-refresh', schedule: task?.schedule || null, placement: task?.placement || null, node, model, ready: Boolean(node && model) };
726
+ if (json) console.log(JSON.stringify(payload, null, 2));
727
+ else {
728
+ console.log(`OpenWiki nightly refresh: ${node ? `runs on ${node}` : 'idle (no node selected)'} at "${payload.schedule}"`);
729
+ console.log(` Model: ${model || `not set — ${NO_MODEL}`}`);
730
+ }
731
+ } catch (err) {
732
+ if (json) console.log(JSON.stringify({ ok: false, error: err.message }));
733
+ else console.error(`OpenWiki schedule: ${err.message}`);
734
+ process.exitCode = 1;
735
+ }
736
+ }
737
+
738
+ /**
739
+ * List, add or remove repositories the refresh never touches. Takes registered
740
+ * project names or absolute paths; with none, lists the current exclusions.
741
+ */
742
+ export async function excludeCommand(args = [], { read = readProjects, load = loadOpenWikiConfig, save = setExcludedRoots } = {}) {
743
+ const json = args.includes('--json');
744
+ const usage = 'Usage: total-recall openwiki exclude [<name|absolute path>...] [--remove] [--json]';
745
+ if (args.includes('--help') || args.includes('-h')) { console.log(usage); return; }
746
+ const remove = args.includes('--remove');
747
+ const targets = args.filter((a) => !a.startsWith('--'));
748
+ try {
749
+ if (remove && !targets.length) throw new Error(usage);
750
+ let roots = load().exclude || [];
751
+ if (targets.length) {
752
+ let projects = [];
753
+ try { projects = read({ exclude: [] }); } catch { projects = []; }
754
+ const chosen = [];
755
+ for (const target of targets) {
756
+ const matches = projects.filter((p) => p.name === target || (path.isAbsolute(target) && path.resolve(p.root) === path.resolve(target)));
757
+ if (matches.length > 1) throw new Error(`${target} names ${matches.length} registered projects; pass its absolute path`);
758
+ if (matches.length === 1) chosen.push(path.resolve(matches[0].root));
759
+ else if (path.isAbsolute(target)) chosen.push(path.resolve(target));
760
+ else throw new Error(`${target} is not a registered project name or an absolute path`);
761
+ }
762
+ roots = save(remove ? roots.filter((root) => !chosen.includes(root)) : [...roots, ...chosen]).exclude;
763
+ }
764
+ const payload = { ok: true, changed: targets.length > 0, exclude: roots };
765
+ if (json) console.log(JSON.stringify(payload, null, 2));
766
+ else {
767
+ console.log(`OpenWiki never refreshes ${roots.length} ${roots.length === 1 ? 'repository' : 'repositories'}${roots.length ? ':' : '.'}`);
768
+ for (const root of roots) console.log(` ${root}`);
769
+ }
770
+ return payload;
771
+ } catch (err) {
772
+ if (json) console.log(JSON.stringify({ ok: false, error: err.message }));
773
+ else console.error(`OpenWiki exclude: ${err.message}`);
774
+ process.exitCode = 1;
775
+ }
776
+ }
777
+
627
778
  async function autoRefreshCommand(args = []) {
628
- const hasApply = args.includes('--apply');
629
- const forwardArgs = hasApply ? args : ['--apply', ...args];
779
+ const forwardArgs = args.includes('--apply') ? [...args] : ['--apply', ...args];
780
+ if (!forwardArgs.some((a) => a === '--budget-ms' || a.startsWith('--budget-ms='))) {
781
+ forwardArgs.push(`--budget-ms=${autoRefreshBudgetMs()}`);
782
+ }
630
783
  return refreshCommand(forwardArgs);
631
784
  }
632
785
 
@@ -651,6 +804,8 @@ IMPLEMENTED.autorefresh = autoRefreshCommand;
651
804
  IMPLEMENTED.watch = watchCommand;
652
805
  IMPLEMENTED.propagate = propagateCommand;
653
806
  IMPLEMENTED.config = configCommand;
807
+ IMPLEMENTED.schedule = scheduleCommand;
808
+ IMPLEMENTED.exclude = excludeCommand;
654
809
  IMPLEMENTED.search = searchCommand;
655
810
  IMPLEMENTED.graph = graphCommand;
656
811
  IMPLEMENTED.token = tokenCommand;
@@ -721,7 +876,10 @@ export async function run(argv = process.argv) {
721
876
  Create missing plugin-owned documentation pages
722
877
  refresh [--repo <name>] [--apply] [--force] [--json]
723
878
  Report which registered projects' generated pages are stale;
724
- --apply regenerates stale pages (writes openwiki/ only)
879
+ --apply regenerates stale pages (writes openwiki/ only).
880
+ Never creates a wiki: use generate for that
881
+ exclude [<name|path>...] [--remove] [--json]
882
+ List, add or remove repositories the refresh never touches
725
883
  auto-refresh [--repo <name>] [--force] [--json]
726
884
  Automated daemon trigger to regenerate stale pages across repositories
727
885
  config [show|get <key>|set <key> <value>|reset] [--json]
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "tr-plugin-openwiki",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "tr-plugin-openwiki",
9
- "version": "0.2.0",
9
+ "version": "0.4.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@gregiteen/ssss-cli": "0.14.5"
13
13
  },
14
14
  "devDependencies": {
15
- "total-recall-brain": "3.45.0"
15
+ "total-recall-brain": "3.47.0"
16
16
  }
17
17
  },
18
18
  "node_modules/@alcalzone/ansi-tokenize": {
@@ -5529,9 +5529,9 @@
5529
5529
  }
5530
5530
  },
5531
5531
  "node_modules/total-recall-brain": {
5532
- "version": "3.45.0",
5533
- "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.45.0.tgz",
5534
- "integrity": "sha512-HDYfROazZbsLHQsFPnlBcSpe1HL1QEi7JG8x/eNRUI06RxVQsZWl8LwBo2lTWjWRDrKmCy7BUmSXQDuWF3lrEw==",
5532
+ "version": "3.47.0",
5533
+ "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.47.0.tgz",
5534
+ "integrity": "sha512-S4UIdbbTyxw7C4+rix+7oHMm/6gYkcxwNBcP6kRDySX40p5IgqVTKgHk1n8rmaKiyYFoIX/HZHlHSjNrHgn0Gw==",
5535
5535
  "dev": true,
5536
5536
  "license": "MIT",
5537
5537
  "workspaces": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tr-plugin-openwiki",
3
- "version": "0.2.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "description": "Multi-repo knowledge graph and documentation wiki engine for Total Recall",
6
6
  "scripts": {
@@ -18,7 +18,8 @@
18
18
  "src/",
19
19
  "vendor/",
20
20
  "capability/",
21
- "templates/"
21
+ "templates/",
22
+ "skills/"
22
23
  ],
23
24
  "dependencies": {
24
25
  "@gregiteen/ssss-cli": "0.14.5"