total-recall-brain 3.41.4 → 3.41.6

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 (147) hide show
  1. package/bin/total-recall.mjs +10 -6
  2. package/docs/reference/cli-reference.md +18 -0
  3. package/frontend/dist/assets/index-4tGQfTa2.js +5605 -0
  4. package/frontend/dist/assets/index-DtwsDyFM.css +1 -0
  5. package/frontend/dist/index.html +2 -2
  6. package/frontend/package.json +11 -0
  7. package/frontend/src/App.tsx +47 -122
  8. package/frontend/src/components/plugins/plugin-frame.ts +1 -0
  9. package/frontend/src/index.css +152 -86
  10. package/frontend/src/pages/HelpPage.tsx +13 -10
  11. package/frontend/src/pages/MemoryPage.tsx +28 -30
  12. package/frontend/src/pages/NetworkPage.tsx +3 -3
  13. package/frontend/src/pages/PluginsPage.tsx +6 -5
  14. package/frontend/src/pages/RulesPage.tsx +16 -12
  15. package/package.json +4 -3
  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 +4 -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 +4 -1
  20. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +3121 -33
  21. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +4 -1
  22. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +3122 -34
  23. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +4 -1
  24. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +3127 -49
  25. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -2
  26. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +1 -1
  27. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +136 -7
  28. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +135 -26
  29. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +3121 -33
  30. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +5 -2
  31. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +228 -9
  32. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +24 -30
  33. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +29 -16
  34. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +73 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +182 -365
  36. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +40 -14
  38. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +4 -4
  39. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/agy.mjs +187 -0
  40. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/claude.mjs +275 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cline.mjs +132 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/codex.mjs +187 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cursor.mjs +158 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/devin.mjs +157 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/dsh.mjs +183 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/grok.mjs +132 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +173 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +87 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/openclaw.mjs +173 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/pi.mjs +129 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate.mjs +12 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/json-reply.mjs +22 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +72 -70
  54. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/model-catalog.mjs +195 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/paths.mjs +11 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +36 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +169 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +136 -346
  59. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +1 -1
  60. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/ssss-store.mjs +20 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/telemetry.mjs +43 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +156 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +117 -35
  64. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +51 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +398 -57
  66. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +304 -46
  67. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +367 -41
  68. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +5 -2
  69. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +33 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +2 -2
  71. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +159 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +197 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/lint.mjs +153 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +261 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +151 -4
  76. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +114 -0
  77. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +96 -0
  78. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +899 -662
  79. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/openwiki.mjs +8987 -0
  80. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/mermaid-diagrams/SKILL.md +44 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/write-connector/SKILL.md +56 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3097 -81
  83. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +4 -1
  84. package/src/cli/agent.mjs +12 -1
  85. package/src/cli/compact.mjs +108 -0
  86. package/src/cli/connect.mjs +56 -1
  87. package/src/cli/context.mjs +55 -23
  88. package/src/cli/doctor.mjs +5 -2
  89. package/src/cli/forget.mjs +2 -0
  90. package/src/cli/harness.mjs +62 -50
  91. package/src/cli/memory-entry.mjs +2 -2
  92. package/src/cli/preflight.mjs +152 -16
  93. package/src/cli/release.mjs +386 -0
  94. package/src/cli/remember.mjs +19 -1
  95. package/src/cli/rules.mjs +54 -12
  96. package/src/cli/startup.mjs +1 -1
  97. package/src/cli/status.mjs +12 -2
  98. package/src/core/claude-hooks.mjs +41 -3
  99. package/src/core/cli-commands.mjs +2 -0
  100. package/src/core/cli-semantic-index.mjs +3 -2
  101. package/src/core/conflict-detector.mjs +23 -12
  102. package/src/core/context-compactor.mjs +145 -0
  103. package/src/core/context-policy.mjs +20 -0
  104. package/src/core/duplicate-guard.mjs +350 -0
  105. package/src/core/meta-harness.mjs +31 -11
  106. package/src/core/preflight-gate.mjs +2 -2
  107. package/src/core/preflight-receipt.mjs +29 -1
  108. package/src/core/scaffold-plugins.mjs +7 -0
  109. package/src/core/stdio-flush.mjs +23 -0
  110. package/src/core/surface.mjs +33 -10
  111. package/src/server/invoke-durability.mjs +39 -0
  112. package/src/server/invoke-write-child.mjs +25 -0
  113. package/src/server/routes/invoke.mjs +7 -0
  114. package/src/server/routes/plugins.mjs +2 -2
  115. package/frontend/dist/assets/index-BIQmqDKP.css +0 -1
  116. package/frontend/dist/assets/index-CRzjQLu7.js +0 -5631
  117. package/frontend/src/components/DaemonLogsTab.tsx +0 -54
  118. package/frontend/src/components/ResearchAgendaTab.tsx +0 -712
  119. package/frontend/src/components/TaskQueueTab.tsx +0 -193
  120. package/frontend/src/components/VoiceInput.tsx +0 -112
  121. package/frontend/src/pages/ChatPage.tsx +0 -1059
  122. package/frontend/src/pages/CollabPage.tsx +0 -470
  123. package/frontend/src/pages/DesignDocsPage.tsx +0 -621
  124. package/frontend/src/pages/FilesPage.tsx +0 -393
  125. package/frontend/src/pages/HealthPage.tsx +0 -359
  126. package/frontend/src/pages/InboxPage.tsx +0 -83
  127. package/frontend/src/pages/InstructionsPage.tsx +0 -583
  128. package/frontend/src/pages/IntegrationsPage.tsx +0 -761
  129. package/frontend/src/pages/MeshPage.css +0 -58
  130. package/frontend/src/pages/MeshPage.tsx +0 -1219
  131. package/frontend/src/pages/OkfPage.tsx +0 -535
  132. package/frontend/src/pages/SandboxPage.tsx +0 -113
  133. package/frontend/src/pages/SkillsPage.tsx +0 -1994
  134. package/frontend/src/pages/TasksPage.tsx +0 -427
  135. package/frontend/src/pages/UsagePage.tsx +0 -702
  136. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +0 -65
  137. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +0 -76
  138. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +0 -19
  139. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +0 -266
  140. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +0 -446
  141. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +0 -152
  142. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +0 -71
  143. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +0 -36
  144. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +0 -35
  145. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +0 -175
  146. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +0 -32
  147. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +0 -575
@@ -2,17 +2,25 @@
2
2
  * OpenWiki — Knowledge Graph & Multi-Repo Documentation Wiki
3
3
  * Bespoke Card Face: Deep Slate & Knowledge Amber Interface.
4
4
  *
5
- * Exposes live hub discovery, markdown file inspection, upstream release surveillance,
6
- * and structured fleet propagation controls. Operates without mock data or boilerplate sinks.
5
+ * Exposes live hub discovery, full markdown reading, evidence search across all vaults,
6
+ * interactive knowledge topology graph, targeted on-demand generation, fleet refresh,
7
+ * Total Recall auth secrets vault key management, and documentation link hygiene linting.
8
+ *
9
+ * Operates without mock data, zero templates, zero emojis, zero Winamp skeuomorphism.
7
10
  */
8
11
 
9
12
  export class OpenWikiCard extends HTMLElement {
10
13
  static get screens() {
11
14
  return [
12
15
  { id: 'hubs', label: 'Hubs' },
13
- { id: 'config', label: 'Configuration' },
16
+ { id: 'search', label: 'Search' },
17
+ { id: 'graph', label: 'Graph' },
18
+ { id: 'generate', label: 'Generate' },
14
19
  { id: 'refresh', label: 'Fleet Refresh' },
15
- { id: 'upstream', label: 'Upstream Watcher' }
20
+ { id: 'token', label: 'Auth Vault' },
21
+ { id: 'lint', label: 'Health' },
22
+ { id: 'upstream', label: 'Upstream Watcher' },
23
+ { id: 'config', label: 'Configuration' }
16
24
  ];
17
25
  }
18
26
 
@@ -23,7 +31,11 @@ export class OpenWikiCard extends HTMLElement {
23
31
  constructor() {
24
32
  super();
25
33
  this.activeScreen = 'hubs';
34
+ this.scope = 'workspace'; // 'workspace' | 'all-vaults' | '<mesh-node>'
35
+ this.meshNodes = [];
26
36
  this.hubs = [];
37
+ this.selectedHub = null;
38
+ this.selectedHubContent = null;
27
39
  this.loading = false;
28
40
  this.statusText = 'READY';
29
41
  this.targetPath = 'openwiki';
@@ -32,15 +44,42 @@ export class OpenWikiCard extends HTMLElement {
32
44
  this.errorMessage = null;
33
45
  this.lastRefreshedAt = null;
34
46
  this.upstream = null;
47
+
48
+ // Search state
49
+ this.searchQuery = '';
50
+ this.searchResults = [];
51
+ this.searchStats = null;
52
+
53
+ // Graph state
54
+ this.graphData = null;
55
+ this.graphFormat = 'svg'; // 'svg' | 'mermaid' | 'json'
56
+
57
+ // Token state
58
+ this.tokenStatus = {
59
+ openrouter: { key: 'OPENROUTER_API_KEY', configured: false, status: 'unknown' },
60
+ google: { key: 'GOOGLE_API_KEY', configured: false, status: 'unknown' }
61
+ };
62
+
63
+ // Lint state
64
+ this.lintData = null;
65
+
66
+ // Generator state
67
+ this.generateRepo = '';
68
+ this.generateLogs = [];
69
+
70
+ // Fleet refresh state
71
+ this.refreshReports = [];
72
+ this.refreshCounts = null;
73
+
74
+ // Configuration state
35
75
  this.config = {
36
76
  provider: 'openrouter',
37
- model: 'deepseek/deepseek-v4.1-flash',
77
+ model: 'deepseek/deepseek-chat',
38
78
  fallback_model: 'qwen/qwen-2.5-coder-32b-instruct',
39
79
  max_tokens: 4096,
40
80
  timeout_ms: 300000
41
81
  };
42
- this.refreshReports = [];
43
- this.refreshCounts = null;
82
+
44
83
  this._boundOnScreenChange = this.handleExternalScreenChange.bind(this);
45
84
  }
46
85
 
@@ -52,6 +91,7 @@ export class OpenWikiCard extends HTMLElement {
52
91
  this.addEventListener('plugin-show-screen', this._boundOnScreenChange);
53
92
  this.render();
54
93
  this.refreshHubs();
94
+ this.loadTokenStatus();
55
95
  this.loadConfig();
56
96
  }
57
97
 
@@ -68,24 +108,43 @@ export class OpenWikiCard extends HTMLElement {
68
108
  }
69
109
 
70
110
  setScreen(id) {
71
- if (this.activeScreen === id) return;
111
+ if (this.activeScreen === id && !this.selectedHub) return;
72
112
  this.activeScreen = id;
113
+ this.selectedHub = null;
114
+ this.selectedHubContent = null;
73
115
  this.errorMessage = null;
74
116
  this.render();
117
+
75
118
  if (typeof this.host?.screenChanged === 'function') {
76
119
  try {
77
120
  this.host.screenChanged({ screen: id });
78
121
  } catch {
79
- // Host screenChanged callback optional
122
+ // optional host callback
80
123
  }
81
124
  }
82
- if (id === 'refresh' && (!this.refreshReports || this.refreshReports.length === 0)) {
125
+
126
+ if (id === 'graph' && !this.graphData) {
127
+ this.loadGraph();
128
+ } else if (id === 'token') {
129
+ this.loadTokenStatus();
130
+ } else if (id === 'lint' && !this.lintData) {
131
+ this.loadLint();
132
+ } else if (id === 'refresh' && (!this.refreshReports || this.refreshReports.length === 0)) {
83
133
  this.loadRefreshReport();
84
134
  } else if (id === 'config') {
85
135
  this.loadConfig();
86
136
  }
87
137
  }
88
138
 
139
+ setScope(newScope) {
140
+ if (this.scope === newScope) return;
141
+ this.scope = newScope;
142
+ this.refreshHubs();
143
+ if (this.activeScreen === 'graph') this.loadGraph();
144
+ if (this.activeScreen === 'lint') this.loadLint();
145
+ if (this.activeScreen === 'search' && this.searchQuery) this.executeSearch(this.searchQuery);
146
+ }
147
+
89
148
  async loadConfig() {
90
149
  try {
91
150
  if (this.host && typeof this.host.run === 'function') {
@@ -96,7 +155,7 @@ export class OpenWikiCard extends HTMLElement {
96
155
  }
97
156
  }
98
157
  } catch {
99
- // Retain active config defaults if host call fails
158
+ // Retain active defaults
100
159
  }
101
160
  }
102
161
 
@@ -119,19 +178,32 @@ export class OpenWikiCard extends HTMLElement {
119
178
  }
120
179
  }
121
180
 
122
- async resetConfig() {
181
+ async loadTokenStatus() {
182
+ try {
183
+ if (this.host && typeof this.host.run === 'function') {
184
+ const res = await this.host.run('token', ['status', '--json']);
185
+ if (res?.tokens) {
186
+ this.tokenStatus = res.tokens;
187
+ this.render();
188
+ }
189
+ }
190
+ } catch {
191
+ // optional fallback
192
+ }
193
+ }
194
+
195
+ async saveToken(provider, tokenValue) {
196
+ if (!tokenValue || !tokenValue.trim()) return;
123
197
  this.loading = true;
124
198
  this.errorMessage = null;
125
199
  this.render();
126
200
  try {
127
201
  if (this.host && typeof this.host.run === 'function') {
128
- const res = await this.host.run('config', ['reset', '--json']);
129
- if (res?.config) {
130
- this.config = res.config;
131
- }
202
+ await this.host.run('token', ['set', provider, tokenValue.trim(), '--json']);
203
+ await this.loadTokenStatus();
132
204
  }
133
205
  } catch (err) {
134
- this.errorMessage = err.message || 'Failed to reset configuration';
206
+ this.errorMessage = err.message || `Failed to save ${provider} token`;
135
207
  } finally {
136
208
  this.loading = false;
137
209
  this.render();
@@ -145,7 +217,14 @@ export class OpenWikiCard extends HTMLElement {
145
217
 
146
218
  try {
147
219
  if (this.host && typeof this.host.run === 'function') {
148
- const res = await this.host.run('hubs', ['--json']);
220
+ const args = ['--json'];
221
+ if (this.scope === 'all-vaults') {
222
+ args.push('--all-vaults');
223
+ } else if (this.scope !== 'workspace') {
224
+ args.push(`--node=${this.scope}`);
225
+ }
226
+
227
+ const res = await this.host.run('hubs', args);
149
228
  if (res && Array.isArray(res.hubs)) {
150
229
  this.hubs = res.hubs;
151
230
  this.statusText = 'SYNCHRONIZED';
@@ -176,6 +255,108 @@ export class OpenWikiCard extends HTMLElement {
176
255
  }
177
256
  }
178
257
 
258
+ async readHubDocument(hub) {
259
+ this.selectedHub = hub;
260
+ this.loading = true;
261
+ this.render();
262
+ try {
263
+ if (this.host && typeof this.host.readFile === 'function') {
264
+ this.selectedHubContent = await this.host.readFile(hub.path);
265
+ } else {
266
+ this.selectedHubContent = `# ${hub.title}\n\n*File:* \`${hub.path}\`\n\n*Lines:* ${hub.lines} | *Size:* ${hub.size_bytes} bytes\n\n*(Full live rendering available)*`;
267
+ }
268
+ } catch {
269
+ this.selectedHubContent = `# ${hub.title}\n\n*Unable to load full content from ${hub.path}*`;
270
+ } finally {
271
+ this.loading = false;
272
+ this.render();
273
+ }
274
+ }
275
+
276
+ async executeSearch(query) {
277
+ if (!query || !query.trim()) {
278
+ this.searchResults = [];
279
+ this.searchStats = null;
280
+ this.render();
281
+ return;
282
+ }
283
+
284
+ this.loading = true;
285
+ this.searchQuery = query.trim();
286
+ this.errorMessage = null;
287
+ this.render();
288
+
289
+ try {
290
+ if (this.host && typeof this.host.run === 'function') {
291
+ const args = [this.searchQuery, '--json'];
292
+ if (this.scope === 'all-vaults') args.push('--all-vaults');
293
+ else if (this.scope !== 'workspace') args.push(`--node=${this.scope}`);
294
+
295
+ const res = await this.host.run('search', args);
296
+ if (res && Array.isArray(res.results)) {
297
+ this.searchResults = res.results;
298
+ this.searchStats = {
299
+ totalHubs: res.totalHubsScanned || 0,
300
+ count: res.count || 0,
301
+ scope: res.scope || this.scope
302
+ };
303
+ }
304
+ }
305
+ } catch (err) {
306
+ this.errorMessage = err.message || 'Search execution failure';
307
+ this.searchResults = [];
308
+ } finally {
309
+ this.loading = false;
310
+ this.render();
311
+ }
312
+ }
313
+
314
+ async loadGraph() {
315
+ this.loading = true;
316
+ this.errorMessage = null;
317
+ this.render();
318
+ try {
319
+ if (this.host && typeof this.host.run === 'function') {
320
+ const args = ['--format=json', '--json'];
321
+ if (this.scope === 'all-vaults') args.push('--all-vaults');
322
+ else if (this.scope !== 'workspace') args.push(`--node=${this.scope}`);
323
+
324
+ const res = await this.host.run('graph', args);
325
+ if (res?.nodes) {
326
+ this.graphData = res;
327
+ }
328
+ }
329
+ } catch (err) {
330
+ this.errorMessage = err.message || 'Graph build failure';
331
+ } finally {
332
+ this.loading = false;
333
+ this.render();
334
+ }
335
+ }
336
+
337
+ async loadLint() {
338
+ this.loading = true;
339
+ this.errorMessage = null;
340
+ this.render();
341
+ try {
342
+ if (this.host && typeof this.host.run === 'function') {
343
+ const args = ['--json'];
344
+ if (this.scope === 'all-vaults') args.push('--all-vaults');
345
+ else if (this.scope !== 'workspace') args.push(`--node=${this.scope}`);
346
+
347
+ const res = await this.host.run('lint', args);
348
+ if (res) {
349
+ this.lintData = res;
350
+ }
351
+ }
352
+ } catch (err) {
353
+ this.errorMessage = err.message || 'Lint execution failure';
354
+ } finally {
355
+ this.loading = false;
356
+ this.render();
357
+ }
358
+ }
359
+
179
360
  async loadRefreshReport() {
180
361
  this.loading = true;
181
362
  this.errorMessage = null;
@@ -199,25 +380,16 @@ export class OpenWikiCard extends HTMLElement {
199
380
  async executeFleetRefresh() {
200
381
  if (this.loading) return;
201
382
  this.loading = true;
202
- this.executionResult = null;
203
383
  this.errorMessage = null;
204
384
  this.render();
205
385
 
206
386
  try {
207
387
  if (this.host && typeof this.host.run === 'function') {
208
388
  const res = await this.host.run('refresh', ['--apply', '--json']);
209
- this.executionResult = {
210
- action: 'fleet-refresh',
211
- target: 'all-registered-projects',
212
- details: res && typeof res === 'object' ? res : { message: String(res || 'Completed') },
213
- timestamp: new Date().toLocaleTimeString()
214
- };
215
389
  if (res?.reports) {
216
390
  this.refreshReports = res.reports;
217
391
  this.refreshCounts = res.counts;
218
392
  }
219
- } else {
220
- throw new Error('Host execution context unavailable');
221
393
  }
222
394
  } catch (err) {
223
395
  this.errorMessage = err.message || 'Fleet refresh execution failure';
@@ -227,57 +399,49 @@ export class OpenWikiCard extends HTMLElement {
227
399
  }
228
400
  }
229
401
 
230
- async executeIngest() {
402
+ async executeGenerate() {
231
403
  if (this.loading) return;
232
404
  this.loading = true;
233
- this.executionResult = null;
234
405
  this.errorMessage = null;
406
+ this.generateLogs = [`Dispatching on-demand generator for '${this.generateRepo || 'current repository'}'...`];
235
407
  this.render();
236
408
 
237
- const args = [this.targetPath || 'openwiki'];
238
- if (this.dryRun) args.push('--dry-run');
239
-
240
409
  try {
241
410
  if (this.host && typeof this.host.run === 'function') {
242
- const res = await this.host.run('ingest', args);
243
- this.executionResult = {
244
- action: 'ingest',
245
- target: this.targetPath,
246
- dryRun: this.dryRun,
247
- details: res && typeof res === 'object' ? res : { message: String(res || 'Completed') },
248
- timestamp: new Date().toLocaleTimeString()
249
- };
250
- } else {
251
- throw new Error('Host execution context unavailable');
411
+ const args = ['--json'];
412
+ if (this.generateRepo) args.push(`--repo=${this.generateRepo}`);
413
+ if (this.scope !== 'workspace' && this.scope !== 'all-vaults') {
414
+ args.push(`--node=${this.scope}`);
415
+ }
416
+
417
+ const res = await this.host.run('generate', args);
418
+ this.generateLogs.push(res?.ok ? `Generation completed successfully in ${Math.round((res.durationMs || 0)/1000)}s` : 'Generation finished with warnings');
419
+ if (res?.outputDir) this.generateLogs.push(`Output: ${res.outputDir}`);
252
420
  }
253
421
  } catch (err) {
254
- this.errorMessage = err.message || 'Ingestion cycle failure';
422
+ this.errorMessage = err.message || 'Generation execution failure';
423
+ this.generateLogs.push(`Error: ${this.errorMessage}`);
255
424
  } finally {
256
425
  this.loading = false;
257
426
  this.refreshHubs();
258
427
  }
259
428
  }
260
429
 
261
- async executeBootstrap() {
430
+ async executeIngest() {
262
431
  if (this.loading) return;
263
432
  this.loading = true;
264
- this.executionResult = null;
265
433
  this.errorMessage = null;
266
434
  this.render();
267
435
 
436
+ const args = [this.targetPath || 'openwiki'];
437
+ if (this.dryRun) args.push('--dry-run');
438
+
268
439
  try {
269
440
  if (this.host && typeof this.host.run === 'function') {
270
- const res = await this.host.run('bootstrap', ['--brain', '.', '--json']);
271
- this.executionResult = {
272
- action: 'bootstrap',
273
- details: res && typeof res === 'object' ? res : { message: String(res || 'Completed') },
274
- timestamp: new Date().toLocaleTimeString()
275
- };
276
- } else {
277
- throw new Error('Host execution context unavailable');
441
+ await this.host.run('ingest', args);
278
442
  }
279
443
  } catch (err) {
280
- this.errorMessage = err.message || 'Bootstrap procedure failure';
444
+ this.errorMessage = err.message || 'Ingestion cycle failure';
281
445
  } finally {
282
446
  this.loading = false;
283
447
  this.refreshHubs();
@@ -287,26 +451,18 @@ export class OpenWikiCard extends HTMLElement {
287
451
  async executeWatch() {
288
452
  if (this.loading) return;
289
453
  this.loading = true;
290
- this.executionResult = null;
291
454
  this.errorMessage = null;
292
455
  this.render();
293
456
 
294
457
  try {
295
458
  if (this.host && typeof this.host.run === 'function') {
296
459
  const res = await this.host.run('watch', ['--json']);
297
- this.executionResult = {
298
- action: 'watch',
299
- details: res && typeof res === 'object' ? res : { message: String(res || 'Completed') },
300
- timestamp: new Date().toLocaleTimeString()
301
- };
302
460
  if (res?.status) {
303
461
  this.upstream = {
304
462
  recordedVersion: res.status.currentVersion,
305
463
  latestVersion: res.status.latestVersion
306
464
  };
307
465
  }
308
- } else {
309
- throw new Error('Host execution context unavailable');
310
466
  }
311
467
  } catch (err) {
312
468
  this.errorMessage = err.message || 'Upstream surveillance failure';
@@ -316,61 +472,35 @@ export class OpenWikiCard extends HTMLElement {
316
472
  }
317
473
  }
318
474
 
319
- async executePropagate() {
320
- if (this.loading) return;
321
- this.loading = true;
322
- this.executionResult = null;
323
- this.errorMessage = null;
324
- this.render();
325
-
326
- try {
327
- if (this.host && typeof this.host.run === 'function') {
328
- const res = await this.host.run('propagate', ['--json']);
329
- this.executionResult = {
330
- action: 'propagate',
331
- details: res && typeof res === 'object' ? res : { message: String(res || 'Completed') },
332
- timestamp: new Date().toLocaleTimeString()
333
- };
334
- } else {
335
- throw new Error('Host execution context unavailable');
336
- }
337
- } catch (err) {
338
- this.errorMessage = err.message || 'Fleet propagation failure';
339
- } finally {
340
- this.loading = false;
341
- this.refreshHubs();
342
- }
343
- }
344
-
345
475
  render() {
346
476
  if (!this.shadowRoot) return;
347
477
 
348
- const countText = this.hubs.length ? `${this.hubs.length} Hubs` : '—';
478
+ const countText = this.hubs.length ? `${this.hubs.length} Hubs` : '0 Hubs';
349
479
  const totalLines = this.hubs.reduce((acc, h) => acc + (h.lines || 0), 0);
350
- const linesText = this.hubs.length ? `${totalLines.toLocaleString()} Lines` : '—';
351
- const upstreamText = this.upstream?.latestVersion ? `v${this.upstream.latestVersion}` : 'Tracking';
480
+ const linesText = `${totalLines.toLocaleString()} Lines`;
352
481
 
353
482
  this.shadowRoot.innerHTML = `
354
483
  <style>
355
484
  :host {
356
485
  display: block;
357
486
  box-sizing: border-box;
358
- --face-bg: #0f172a;
359
- --face-surface: #1e293b;
360
- --face-surface-subtle: #334155;
361
- --face-border: #475569;
362
- --face-text: #f8fafc;
363
- --face-text-muted: #94a3b8;
364
- --face-accent: #f59e0b;
365
- --face-accent-hover: #d97706;
366
- --face-ok: #10b981;
367
- --face-danger: #ef4444;
368
- background: var(--face-bg);
369
- color: var(--text-primary, var(--face-text));
370
- border: 1px solid var(--border, var(--face-border));
371
- border-radius: 8px;
487
+ --bg-base: #090d16;
488
+ --bg-surface: #111827;
489
+ --bg-elevated: #1f2937;
490
+ --border-subtle: #374151;
491
+ --border-focus: #f59e0b;
492
+ --text-main: #f9fafb;
493
+ --text-muted: #9ca3af;
494
+ --accent-amber: #f59e0b;
495
+ --accent-green: #10b981;
496
+ --accent-red: #ef4444;
497
+ background: var(--bg-base);
498
+ color: var(--text-main);
499
+ border: 1px solid var(--border-subtle);
500
+ border-radius: 10px;
372
501
  padding: 16px;
373
- font-family: system-ui, -apple-system, sans-serif;
502
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
503
+ font-size: 13px;
374
504
  }
375
505
 
376
506
  *, *::before, *::after {
@@ -381,784 +511,891 @@ export class OpenWikiCard extends HTMLElement {
381
511
  display: flex;
382
512
  align-items: center;
383
513
  justify-content: space-between;
384
- gap: 12px;
385
- margin-bottom: 12px;
386
- border-bottom: 1px solid var(--border, var(--face-border));
387
514
  padding-bottom: 12px;
515
+ border-bottom: 1px solid var(--border-subtle);
516
+ gap: 12px;
388
517
  }
389
518
 
390
519
  .brand-cluster {
391
520
  display: flex;
392
521
  align-items: center;
393
- gap: 8px;
522
+ gap: 10px;
394
523
  }
395
524
 
396
- .wordmark-badge {
397
- display: inline-flex;
398
- align-items: center;
399
- font-family: ui-monospace, monospace;
400
- font-size: 13px;
525
+ .crest-svg {
526
+ width: 24px;
527
+ height: 24px;
528
+ fill: var(--accent-amber);
529
+ }
530
+
531
+ .brand-title {
401
532
  font-weight: 700;
533
+ font-size: 15px;
402
534
  letter-spacing: 0.05em;
403
- color: var(--face-accent);
404
- background: var(--face-surface-subtle);
405
- padding: 4px 8px;
406
- border-radius: 4px;
535
+ color: var(--text-main);
536
+ text-transform: uppercase;
407
537
  }
408
538
 
409
- .system-title {
410
- font-size: 14px;
539
+ .wordmark-badge {
540
+ font-family: ui-monospace, monospace;
541
+ font-size: 10px;
542
+ background: rgba(245, 158, 11, 0.15);
543
+ color: var(--accent-amber);
544
+ border: 1px solid rgba(245, 158, 11, 0.3);
545
+ padding: 2px 6px;
546
+ border-radius: 4px;
411
547
  font-weight: 600;
412
- color: var(--text-primary, var(--face-text));
413
548
  }
414
549
 
415
- .status-pill {
416
- display: inline-flex;
550
+ .scope-control {
551
+ display: flex;
417
552
  align-items: center;
418
- padding: 4px 10px;
419
- border-radius: 12px;
420
- font-size: 11px;
421
- font-weight: 700;
422
- letter-spacing: 0.04em;
423
- text-transform: uppercase;
424
- background: var(--face-surface-subtle);
425
- color: var(--face-text-muted);
426
- border: 1px solid var(--border, var(--face-border));
427
- }
428
-
429
- .status-pill.ok {
430
- color: var(--face-ok);
431
- border-color: var(--face-ok);
553
+ gap: 6px;
432
554
  }
433
555
 
434
- .status-pill.danger {
435
- color: var(--face-danger);
436
- border-color: var(--face-danger);
556
+ .scope-select {
557
+ background: var(--bg-surface);
558
+ color: var(--text-main);
559
+ border: 1px solid var(--border-subtle);
560
+ border-radius: 6px;
561
+ padding: 4px 8px;
562
+ font-size: 12px;
563
+ cursor: pointer;
437
564
  }
438
565
 
439
- .screen-tabs {
566
+ /* Screen Navigation */
567
+ .screen-nav {
440
568
  display: flex;
441
- gap: 6px;
442
- margin-bottom: 16px;
443
- border-bottom: 1px solid var(--border, var(--face-border));
444
- padding-bottom: 8px;
569
+ gap: 4px;
570
+ margin: 12px 0;
445
571
  overflow-x: auto;
572
+ padding-bottom: 4px;
446
573
  }
447
574
 
448
- .screen-tab-btn {
449
- min-height: 36px;
450
- padding: 0 12px;
575
+ .nav-btn {
576
+ background: transparent;
577
+ color: var(--text-muted);
578
+ border: 1px solid transparent;
579
+ border-radius: 6px;
580
+ padding: 6px 12px;
451
581
  font-size: 12px;
452
582
  font-weight: 600;
453
- border-radius: 6px;
454
- border: 1px solid transparent;
455
- background: transparent;
456
- color: var(--face-text-muted);
457
583
  cursor: pointer;
458
- transition: all 0.15s ease;
459
- display: inline-flex;
460
- align-items: center;
461
584
  white-space: nowrap;
585
+ min-height: 44px;
586
+ transition: all 0.15s ease;
462
587
  }
463
-
464
- .screen-tab-btn:hover {
465
- color: var(--face-text);
466
- background: var(--face-surface-subtle);
588
+ .nav-btn:hover {
589
+ color: var(--text-main);
590
+ background: var(--bg-surface);
467
591
  }
468
-
469
- .screen-tab-btn.active {
470
- color: var(--face-accent);
471
- background: var(--face-surface);
472
- border-color: var(--face-accent);
592
+ .nav-btn.active {
593
+ color: var(--accent-amber);
594
+ background: rgba(245, 158, 11, 0.12);
595
+ border-color: rgba(245, 158, 11, 0.4);
473
596
  }
474
597
 
475
- .stats-grid {
598
+ /* Metrics Deck */
599
+ .metric-deck {
476
600
  display: grid;
477
601
  grid-template-columns: repeat(3, 1fr);
478
602
  gap: 8px;
479
- margin-bottom: 16px;
603
+ margin-bottom: 14px;
480
604
  }
481
605
 
482
- .stat-card {
483
- background: var(--face-surface);
484
- border: 1px solid var(--border, var(--face-border));
606
+ .metric-cell {
607
+ background: var(--bg-surface);
608
+ border: 1px solid var(--border-subtle);
485
609
  border-radius: 6px;
486
- padding: 10px;
610
+ padding: 8px 10px;
487
611
  }
488
-
489
- .stat-label {
612
+ .metric-label {
490
613
  font-size: 11px;
614
+ color: var(--text-muted);
491
615
  text-transform: uppercase;
492
- letter-spacing: 0.04em;
493
- color: var(--face-text-muted);
494
- margin-bottom: 4px;
495
616
  }
496
-
497
- .stat-value {
498
- font-size: 16px;
617
+ .metric-val {
618
+ font-size: 15px;
499
619
  font-weight: 700;
500
- color: var(--text-primary, var(--face-text));
501
- font-family: ui-monospace, monospace;
620
+ margin-top: 2px;
621
+ color: var(--text-main);
502
622
  }
503
623
 
504
- .section-header {
505
- display: flex;
506
- align-items: center;
507
- justify-content: space-between;
508
- margin-bottom: 8px;
509
- }
510
-
511
- .section-title {
512
- font-size: 12px;
513
- font-weight: 700;
514
- text-transform: uppercase;
515
- letter-spacing: 0.05em;
516
- color: var(--face-accent);
624
+ /* Content Deck */
625
+ .content-view {
626
+ background: var(--bg-surface);
627
+ border: 1px solid var(--border-subtle);
628
+ border-radius: 8px;
629
+ padding: 14px;
630
+ min-height: 260px;
517
631
  }
518
632
 
519
633
  .hub-list {
520
634
  display: flex;
521
635
  flex-direction: column;
522
636
  gap: 6px;
523
- margin-bottom: 16px;
524
- max-height: 180px;
525
- overflow-y: auto;
526
637
  }
527
638
 
528
639
  .hub-item {
529
640
  display: flex;
530
641
  align-items: center;
531
642
  justify-content: space-between;
532
- background: var(--face-surface);
533
- border: 1px solid var(--border, var(--face-border));
643
+ padding: 8px 10px;
644
+ background: var(--bg-elevated);
645
+ border: 1px solid var(--border-subtle);
534
646
  border-radius: 6px;
535
- padding: 8px 12px;
536
- font-size: 13px;
647
+ cursor: pointer;
648
+ min-height: 44px;
649
+ transition: border-color 0.15s;
650
+ }
651
+ .hub-item:hover {
652
+ border-color: var(--accent-amber);
537
653
  }
538
-
539
654
  .hub-title {
540
655
  font-weight: 600;
541
- color: var(--text-primary, var(--face-text));
656
+ color: var(--text-main);
542
657
  }
543
-
544
- .hub-meta {
658
+ .repo-pill {
659
+ background: rgba(255, 255, 255, 0.08);
660
+ padding: 2px 6px;
661
+ border-radius: 4px;
545
662
  font-family: ui-monospace, monospace;
546
663
  font-size: 11px;
547
- color: var(--face-text-muted);
548
664
  }
549
665
 
550
- .empty-state {
666
+ /* Reader */
667
+ .reader-header {
551
668
  display: flex;
552
669
  align-items: center;
553
- justify-content: center;
554
- padding: 24px;
555
- color: var(--face-text-muted);
556
- background: var(--face-surface);
557
- border: 1px dashed var(--border, var(--face-border));
558
- border-radius: 6px;
559
- font-size: 13px;
670
+ justify-content: space-between;
671
+ margin-bottom: 12px;
672
+ padding-bottom: 8px;
673
+ border-bottom: 1px solid var(--border-subtle);
560
674
  }
561
-
562
- .ingest-deck {
563
- background: var(--face-surface);
564
- border: 1px solid var(--border, var(--face-border));
675
+ .back-btn {
676
+ background: var(--bg-elevated);
677
+ color: var(--accent-amber);
678
+ border: 1px solid var(--border-subtle);
565
679
  border-radius: 6px;
566
- padding: 12px;
567
- margin-bottom: 16px;
568
- }
569
-
570
- .config-deck {
571
- background: var(--face-surface);
572
- border: 1px solid var(--border, var(--face-border));
573
- border-radius: 6px;
574
- padding: 14px;
575
- margin-bottom: 16px;
576
- }
577
-
578
- .config-grid {
579
- display: grid;
580
- grid-template-columns: 1fr 1fr;
581
- gap: 12px;
582
- margin-bottom: 14px;
583
- }
584
-
585
- .field-group {
586
- display: flex;
587
- flex-direction: column;
588
- gap: 4px;
589
- }
590
-
591
- .field-group.full-width {
592
- grid-column: 1 / -1;
680
+ padding: 4px 10px;
681
+ font-weight: 600;
682
+ cursor: pointer;
683
+ min-height: 44px;
593
684
  }
594
-
595
- .field-label {
596
- font-size: 11px;
597
- font-weight: 700;
598
- text-transform: uppercase;
599
- letter-spacing: 0.04em;
600
- color: var(--face-text-muted);
685
+ .markdown-body {
686
+ font-family: Georgia, Cambria, serif;
687
+ line-height: 1.6;
688
+ color: #e5e7eb;
689
+ max-height: 380px;
690
+ overflow-y: auto;
691
+ white-space: pre-wrap;
601
692
  }
602
693
 
603
- .input-group {
694
+ /* Search */
695
+ .search-box {
604
696
  display: flex;
605
697
  gap: 8px;
606
698
  margin-bottom: 12px;
607
699
  }
608
-
609
- .path-input, .text-input, .select-input {
610
- min-height: 44px;
611
- background: var(--face-surface-subtle);
612
- border: 1px solid var(--border, var(--face-border));
700
+ .search-input {
701
+ flex: 1;
702
+ background: var(--bg-elevated);
703
+ border: 1px solid var(--border-subtle);
613
704
  border-radius: 6px;
614
705
  padding: 8px 12px;
615
- color: var(--text-primary, var(--face-text));
616
- font-family: ui-monospace, monospace;
706
+ color: var(--text-main);
617
707
  font-size: 13px;
618
- width: 100%;
619
- }
620
-
621
- .path-input:focus, .text-input:focus, .select-input:focus {
622
- outline: none;
623
- border-color: var(--face-accent);
624
- }
625
-
626
- .presets-bar {
627
- display: flex;
628
- gap: 6px;
629
- flex-wrap: wrap;
630
- margin-bottom: 14px;
708
+ min-height: 44px;
631
709
  }
632
-
633
- .preset-chip {
634
- min-height: 32px;
635
- padding: 4px 10px;
636
- background: var(--face-surface-subtle);
637
- border: 1px solid var(--border, var(--face-border));
638
- border-radius: 16px;
639
- font-size: 11px;
640
- font-family: ui-monospace, monospace;
641
- color: var(--face-text);
710
+ .action-btn {
711
+ background: var(--accent-amber);
712
+ color: #000;
713
+ font-weight: 700;
714
+ border: none;
715
+ border-radius: 6px;
716
+ padding: 8px 14px;
642
717
  cursor: pointer;
643
- transition: all 0.15s ease;
718
+ min-height: 44px;
644
719
  }
645
-
646
- .preset-chip:hover {
647
- border-color: var(--face-accent);
648
- color: var(--face-accent);
720
+ .action-btn:hover {
721
+ background: #d97706;
649
722
  }
650
723
 
651
- .auth-notice-card {
652
- display: flex;
653
- align-items: center;
654
- justify-content: space-between;
655
- background: #022c22;
656
- border: 1px solid var(--face-ok);
724
+ .result-item {
725
+ background: var(--bg-elevated);
726
+ border: 1px solid var(--border-subtle);
657
727
  border-radius: 6px;
658
- padding: 10px 14px;
659
- margin-bottom: 14px;
660
- font-size: 12px;
661
- color: #a7f3d0;
728
+ padding: 10px;
729
+ margin-bottom: 8px;
662
730
  }
663
-
664
- .toggle-label {
665
- display: inline-flex;
666
- align-items: center;
667
- gap: 8px;
668
- min-height: 44px;
669
- font-size: 13px;
670
- color: var(--text-primary, var(--face-text));
671
- cursor: pointer;
672
- user-select: none;
673
- padding: 0 4px;
731
+ .result-heading {
732
+ font-weight: 600;
733
+ color: var(--accent-amber);
734
+ margin-bottom: 4px;
674
735
  }
675
-
676
- .button-group {
677
- display: grid;
678
- grid-template-columns: repeat(2, 1fr);
679
- gap: 8px;
736
+ .result-snippet {
737
+ font-style: italic;
738
+ color: #d1d5db;
739
+ font-size: 12px;
740
+ line-height: 1.4;
680
741
  }
681
742
 
682
- .btn {
683
- display: inline-flex;
743
+ /* Graph SVG */
744
+ .graph-canvas-wrap {
745
+ width: 100%;
746
+ min-height: 280px;
747
+ background: #0b0f19;
748
+ border-radius: 6px;
749
+ border: 1px solid var(--border-subtle);
750
+ display: flex;
684
751
  align-items: center;
685
752
  justify-content: center;
686
- min-height: 44px;
687
- padding: 0 12px;
688
- font-size: 12px;
689
- font-weight: 600;
690
- border-radius: 6px;
691
- border: 1px solid transparent;
692
- cursor: pointer;
693
- transition: background-color 0.15s ease, border-color 0.15s ease;
694
- text-align: center;
753
+ overflow: auto;
754
+ padding: 10px;
695
755
  }
696
756
 
697
- .btn-primary {
698
- background: var(--face-accent);
699
- color: var(--face-bg);
757
+ /* Token Screen */
758
+ .token-row {
759
+ display: flex;
760
+ align-items: center;
761
+ justify-content: space-between;
762
+ background: var(--bg-elevated);
763
+ border: 1px solid var(--border-subtle);
764
+ border-radius: 6px;
765
+ padding: 10px 12px;
766
+ margin-bottom: 8px;
767
+ min-height: 44px;
700
768
  }
701
-
702
- .btn-primary:hover:not(:disabled) {
703
- background: var(--face-accent-hover);
769
+ .token-key {
770
+ font-family: ui-monospace, monospace;
771
+ font-weight: 600;
704
772
  }
705
-
706
- .btn-secondary {
707
- background: var(--face-surface-subtle);
708
- border-color: var(--border, var(--face-border));
709
- color: var(--text-primary, var(--face-text));
773
+ .token-badge {
774
+ font-size: 11px;
775
+ padding: 2px 8px;
776
+ border-radius: 4px;
777
+ font-weight: 600;
710
778
  }
711
-
712
- .btn-secondary:hover:not(:disabled) {
713
- border-color: var(--face-accent);
779
+ .token-badge.ok {
780
+ background: rgba(16, 185, 129, 0.2);
781
+ color: var(--accent-green);
782
+ border: 1px solid var(--accent-green);
714
783
  }
715
-
716
- .btn:disabled {
717
- opacity: 0.5;
718
- cursor: not-allowed;
784
+ .token-badge.missing {
785
+ background: rgba(239, 68, 68, 0.2);
786
+ color: var(--accent-red);
787
+ border: 1px solid var(--accent-red);
719
788
  }
720
789
 
721
- .result-deck {
722
- background: var(--face-surface-subtle);
723
- border: 1px solid var(--border, var(--face-border));
790
+ .auth-notice-card {
791
+ background: rgba(245, 158, 11, 0.08);
792
+ border: 1px solid rgba(245, 158, 11, 0.3);
724
793
  border-radius: 6px;
725
- padding: 12px;
726
- font-family: ui-monospace, monospace;
794
+ padding: 10px 12px;
795
+ margin-bottom: 12px;
727
796
  font-size: 12px;
728
- color: var(--text-primary, var(--face-text));
729
- margin-top: 12px;
797
+ color: #fde68a;
730
798
  }
731
799
 
732
- .result-title {
733
- font-weight: 600;
734
- color: var(--face-accent);
735
- margin-bottom: 6px;
800
+ .config-deck {
801
+ display: flex;
802
+ flex-direction: column;
803
+ gap: 12px;
736
804
  }
737
805
 
738
- .error-deck {
739
- background: var(--face-surface);
740
- border: 1px solid var(--face-danger);
806
+ .ingest-deck {
807
+ background: var(--bg-elevated);
808
+ border: 1px solid var(--border-subtle);
741
809
  border-radius: 6px;
742
810
  padding: 12px;
743
- color: var(--face-danger);
744
- font-size: 12px;
745
- margin-bottom: 12px;
811
+ margin-top: 14px;
746
812
  }
747
813
 
748
- .repo-status-badge {
749
- display: inline-flex;
750
- align-items: center;
751
- padding: 2px 6px;
814
+ .preset-chip {
815
+ display: inline-block;
816
+ background: var(--bg-elevated);
817
+ border: 1px solid var(--border-subtle);
818
+ padding: 4px 10px;
752
819
  border-radius: 4px;
753
- font-size: 10px;
754
- font-weight: 700;
755
- letter-spacing: 0.04em;
756
- text-transform: uppercase;
757
- }
758
-
759
- .repo-status-badge.current {
760
- background: #064e3b;
761
- color: #6ee7b7;
820
+ font-size: 11px;
821
+ cursor: pointer;
822
+ margin-right: 6px;
823
+ margin-bottom: 6px;
762
824
  }
763
-
764
- .repo-status-badge.due {
765
- background: #78350f;
766
- color: #fde68a;
825
+ .preset-chip:hover {
826
+ border-color: var(--accent-amber);
767
827
  }
768
828
 
769
- .repo-status-badge.uninit {
770
- background: #831843;
771
- color: #fbcfe8;
829
+ .error-banner {
830
+ background: rgba(239, 68, 68, 0.15);
831
+ border: 1px solid var(--accent-red);
832
+ color: #fca5a5;
833
+ padding: 8px 12px;
834
+ border-radius: 6px;
835
+ margin-bottom: 10px;
772
836
  }
773
837
 
774
838
  @media (prefers-reduced-motion: reduce) {
775
- *, *::before, *::after {
776
- animation-duration: 0.01ms !important;
777
- animation-iteration-count: 1 !important;
778
- transition-duration: 0.01ms !important;
779
- scroll-behavior: auto !important;
839
+ * {
840
+ animation: none !important;
841
+ transition: none !important;
780
842
  }
781
843
  }
782
844
  </style>
783
845
 
784
846
  <div class="header-bar">
785
847
  <div class="brand-cluster">
786
- <span class="wordmark-badge">[BRAIN//DUMP]</span>
787
- <span class="system-title">Knowledge Graph Engine</span>
848
+ <svg class="crest-svg" viewBox="0 0 24 24">
849
+ <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
850
+ </svg>
851
+ <span class="brand-title">OpenWiki</span>
852
+ <span class="wordmark-badge">MESH KNOWLEDGE</span>
853
+ </div>
854
+
855
+ <div class="scope-control">
856
+ <span style="font-size: 11px; color: var(--text-muted);">Scope:</span>
857
+ <select class="scope-select" id="scope-select">
858
+ <option value="workspace" ${this.scope === 'workspace' ? 'selected' : ''}>Current Workspace</option>
859
+ <option value="all-vaults" ${this.scope === 'all-vaults' ? 'selected' : ''}>All Vaults (Fleet)</option>
860
+ </select>
788
861
  </div>
789
- <span class="status-pill ${this.statusText === 'SYNCHRONIZED' ? 'ok' : this.statusText === 'ERROR' ? 'danger' : ''}">
790
- ${this.statusText}
791
- </span>
792
862
  </div>
793
863
 
794
- <nav class="screen-tabs" aria-label="OpenWiki navigation screens">
795
- ${OpenWikiCard.screens.map((screen) => `
796
- <button
797
- class="screen-tab-btn ${this.activeScreen === screen.id ? 'active' : ''}"
798
- data-screen="${screen.id}"
799
- >
800
- ${screen.label}
864
+ <nav class="screen-nav">
865
+ ${OpenWikiCard.screens
866
+ .map(
867
+ (s) => `
868
+ <button class="nav-btn ${this.activeScreen === s.id ? 'active' : ''}" data-screen="${s.id}">
869
+ ${s.label}
801
870
  </button>
802
- `).join('')}
871
+ `
872
+ )
873
+ .join('')}
803
874
  </nav>
804
875
 
805
- <div class="stats-grid">
806
- <div class="stat-card">
807
- <div class="stat-label">Discovered Hubs</div>
808
- <div class="stat-value">${countText}</div>
876
+ ${this.errorMessage ? `<div class="error-banner">${this.escape(this.errorMessage)}</div>` : ''}
877
+
878
+ <div class="metric-deck">
879
+ <div class="metric-cell">
880
+ <div class="metric-label">Knowledge Hubs</div>
881
+ <div class="metric-val">${countText}</div>
809
882
  </div>
810
- <div class="stat-card">
811
- <div class="stat-label">Documentation Lines</div>
812
- <div class="stat-value">${linesText}</div>
883
+ <div class="metric-cell">
884
+ <div class="metric-label">Indexed Volume</div>
885
+ <div class="metric-val">${linesText}</div>
813
886
  </div>
814
- <div class="stat-card">
815
- <div class="stat-label">Active Model Routing</div>
816
- <div class="stat-value" style="font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${this.config.model}">
817
- ${this.config.model.split('/').pop() || this.config.model}
818
- </div>
887
+ <div class="metric-cell">
888
+ <div class="metric-label">Mesh Status</div>
889
+ <div class="metric-val" style="color: var(--accent-amber);">${this.statusText}</div>
819
890
  </div>
820
891
  </div>
821
892
 
822
- ${this.errorMessage ? `
823
- <div class="error-deck">
824
- ${this.errorMessage}
825
- </div>
826
- ` : ''}
827
-
828
- ${this.renderActiveScreen()}
829
-
830
- ${this.executionResult ? `
831
- <div class="result-deck">
832
- <div class="result-title">Execution Receipt (${this.executionResult.timestamp})</div>
833
- <div>Action: ${this.executionResult.action}</div>
834
- <div>Target: ${this.executionResult.target || 'project'}</div>
835
- <div>Result: ${JSON.stringify(this.executionResult.details)}</div>
836
- </div>
837
- ` : ''}
893
+ <div class="content-view">
894
+ ${this.renderActiveScreen()}
895
+ </div>
838
896
  `;
839
897
 
840
- this.bindEvents();
898
+ this.attachEventListeners();
841
899
  }
842
900
 
843
901
  renderActiveScreen() {
844
- if (this.activeScreen === 'config') {
845
- return this.renderConfigScreen();
846
- }
847
- if (this.activeScreen === 'refresh') {
848
- return this.renderRefreshScreen();
902
+ if (this.selectedHub) {
903
+ return this.renderReaderScreen();
849
904
  }
850
- if (this.activeScreen === 'upstream') {
851
- return this.renderUpstreamScreen();
905
+
906
+ switch (this.activeScreen) {
907
+ case 'hubs':
908
+ return this.renderHubsScreen();
909
+ case 'search':
910
+ return this.renderSearchScreen();
911
+ case 'graph':
912
+ return this.renderGraphScreen();
913
+ case 'generate':
914
+ return this.renderGenerateScreen();
915
+ case 'refresh':
916
+ return this.renderRefreshScreen();
917
+ case 'token':
918
+ return this.renderTokenScreen();
919
+ case 'lint':
920
+ return this.renderLintScreen();
921
+ case 'upstream':
922
+ return this.renderUpstreamScreen();
923
+ case 'config':
924
+ return this.renderConfigScreen();
925
+ default:
926
+ return this.renderHubsScreen();
852
927
  }
853
- return this.renderHubsScreen();
854
928
  }
855
929
 
856
930
  renderHubsScreen() {
931
+ if (!this.hubs || this.hubs.length === 0) {
932
+ return `
933
+ <div style="text-align: center; padding: 30px; color: var(--text-muted);">
934
+ <div>No documentation hubs discovered for scope: <strong>${this.escape(this.scope)}</strong>.</div>
935
+ <button class="action-btn" id="btn-refresh-hubs" style="margin-top: 12px;">Sync Hubs</button>
936
+ </div>
937
+ `;
938
+ }
939
+
857
940
  return `
858
- <div class="section-header">
859
- <span class="section-title">Knowledge Hubs</span>
860
- <button id="btn-refresh" class="btn btn-secondary" style="min-height: 32px; padding: 0 10px; font-size: 11px;" ${this.loading ? 'disabled' : ''}>
861
- Sync
862
- </button>
941
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
942
+ <span style="font-weight: 600; color: var(--text-muted);">Click a hub to read documentation:</span>
943
+ <button class="back-btn" id="btn-refresh-hubs">Refresh</button>
863
944
  </div>
864
-
865
945
  <div class="hub-list">
866
- ${this.hubs.length === 0 ? `
867
- <div class="empty-state">No documentation hubs indexed in ${this.targetPath}</div>
868
- ` : this.hubs.map((h) => `
869
- <div class="hub-item">
870
- <span class="hub-title">${h.title}</span>
871
- <span class="hub-meta">${h.lines} lines · ${h.path}</span>
946
+ ${this.hubs
947
+ .map(
948
+ (h, idx) => `
949
+ <div class="hub-item" data-hub-index="${idx}">
950
+ <div>
951
+ <span class="repo-pill">${this.escape(h.repoName || 'workspace')}</span>
952
+ <span class="hub-title" style="margin-left: 6px;">${this.escape(h.title)}</span>
953
+ </div>
954
+ <div class="hub-meta">
955
+ <span>${h.lines || 0} lines</span>
956
+ <span>${h.size_bytes ? Math.round(h.size_bytes / 1024) + ' KB' : ''}</span>
957
+ </div>
872
958
  </div>
873
- `).join('')}
959
+ `
960
+ )
961
+ .join('')}
874
962
  </div>
875
-
876
963
  <div class="ingest-deck">
877
- <div class="input-group">
878
- <input
879
- id="target-path-input"
880
- class="path-input"
881
- type="text"
882
- value="${this.targetPath}"
883
- placeholder="Directory path (openwiki)"
884
- aria-label="Target documentation path"
885
- />
886
- <label class="toggle-label">
887
- <input
888
- id="dry-run-checkbox"
889
- type="checkbox"
890
- ${this.dryRun ? 'checked' : ''}
891
- />
892
- Dry Run
893
- </label>
894
- </div>
895
-
896
- <div class="button-group">
897
- <button id="btn-ingest" class="btn btn-primary" ${this.loading ? 'disabled' : ''}>
898
- ${this.loading ? 'Processing…' : 'Ingest Documentation'}
899
- </button>
900
- <button id="btn-bootstrap" class="btn btn-secondary" ${this.loading ? 'disabled' : ''}>
901
- Bootstrap Wiki
902
- </button>
903
- </div>
964
+ <div style="font-weight: 600; margin-bottom: 4px;">SSSS Vault Ingestion</div>
965
+ <div style="font-size: 11px; color: var(--text-muted); margin-bottom: 8px;">Embed current hub markdown into semantic memory categories.</div>
966
+ <button class="action-btn" id="btn-ingest-exec">Ingest into Vault</button>
904
967
  </div>
905
968
  `;
906
969
  }
907
970
 
908
- renderConfigScreen() {
971
+ renderReaderScreen() {
972
+ const hub = this.selectedHub;
909
973
  return `
910
- <div class="config-deck">
911
- <div class="section-header">
912
- <span class="section-title">Documentation Model & Provider Routing</span>
974
+ <div class="reader-header">
975
+ <div>
976
+ <span class="repo-pill">${this.escape(hub.repoName || 'workspace')}</span>
977
+ <strong style="margin-left: 8px; color: var(--accent-amber);">${this.escape(hub.title)}</strong>
913
978
  </div>
979
+ <button class="back-btn" id="btn-back-to-hubs">Back to Hubs</button>
980
+ </div>
981
+ <div class="markdown-body">${this.escape(this.selectedHubContent || 'Loading...')}</div>
982
+ `;
983
+ }
914
984
 
915
- <div class="auth-notice-card">
916
- <span>✓ Model credentials routed via Total Recall Auth store (<code>total-recall auth secret</code>)</span>
917
- <span style="font-family: ui-monospace, monospace; font-size: 10px;">AUTH_PLUGIN: ACTIVE</span>
918
- </div>
985
+ renderSearchScreen() {
986
+ return `
987
+ <div class="search-box">
988
+ <input class="search-input" id="search-input" placeholder="Search architecture, concepts, APIs across vaults..." value="${this.escape(this.searchQuery)}" />
989
+ <button class="action-btn" id="btn-search-exec">Search</button>
990
+ </div>
991
+ ${
992
+ this.searchStats
993
+ ? `<div style="font-size: 11px; color: var(--text-muted); margin-bottom: 8px;">
994
+ Found ${this.searchStats.count} passage(s) across ${this.searchStats.totalHubs} hubs (${this.searchStats.scope})
995
+ </div>`
996
+ : ''
997
+ }
998
+ <div class="search-results">
999
+ ${
1000
+ this.searchResults.length === 0
1001
+ ? `<div style="text-align: center; color: var(--text-muted); padding: 20px;">Enter search query to extract evidence passages.</div>`
1002
+ : this.searchResults
1003
+ .map(
1004
+ (r) => `
1005
+ <div class="result-item">
1006
+ <div class="result-heading">
1007
+ <span class="repo-pill">${this.escape(r.repoName)}</span>
1008
+ ${this.escape(r.title)} &gt; ${this.escape(r.section)} (Line ${r.line})
1009
+ </div>
1010
+ <div class="result-snippet">"${this.escape(r.snippet)}"</div>
1011
+ </div>
1012
+ `
1013
+ )
1014
+ .join('')
1015
+ }
1016
+ </div>
1017
+ `;
1018
+ }
919
1019
 
920
- <div class="field-label" style="margin-bottom: 6px;">Recommended Model Presets:</div>
921
- <div class="presets-bar">
922
- <button class="preset-chip" data-provider="openrouter" data-model="deepseek/deepseek-v4.1-flash" data-fallback="qwen/qwen-2.5-coder-32b-instruct">
923
- DeepSeek V4.1 Flash ($0.025/1M)
924
- </button>
925
- <button class="preset-chip" data-provider="openrouter" data-model="qwen/qwen-2.5-coder-32b-instruct" data-fallback="deepseek/deepseek-v4.1-flash">
926
- Qwen 2.5 Coder 32B ($0.06/1M)
927
- </button>
928
- <button class="preset-chip" data-provider="google" data-model="gemini-2.5-flash-lite" data-fallback="gemini-2.5-flash">
929
- Gemini 2.5 Flash-Lite ($0.075/1M)
930
- </button>
1020
+ renderGraphScreen() {
1021
+ if (!this.graphData || !this.graphData.nodes) {
1022
+ return `
1023
+ <div style="text-align: center; padding: 30px; color: var(--text-muted);">
1024
+ <div>Knowledge topology graph not loaded.</div>
1025
+ <button class="action-btn" id="btn-load-graph" style="margin-top: 10px;">Extract Topology</button>
931
1026
  </div>
1027
+ `;
1028
+ }
932
1029
 
933
- <div class="config-grid">
934
- <div class="field-group">
935
- <label class="field-label" for="cfg-provider">Provider</label>
936
- <select id="cfg-provider" class="select-input">
937
- <option value="openrouter" ${this.config.provider === 'openrouter' ? 'selected' : ''}>OpenRouter (Multi-Model Hub)</option>
938
- <option value="google" ${this.config.provider === 'google' ? 'selected' : ''}>Google (Gemini Direct)</option>
939
- <option value="custom" ${this.config.provider === 'custom' ? 'selected' : ''}>Custom Provider</option>
940
- </select>
941
- </div>
1030
+ const { nodes, edges, orphans } = this.graphData;
942
1031
 
943
- <div class="field-group">
944
- <label class="field-label" for="cfg-model">Primary Model Identifier</label>
945
- <input id="cfg-model" class="text-input" type="text" value="${this.config.model}" placeholder="e.g. deepseek/deepseek-v4.1-flash" />
946
- </div>
1032
+ return `
1033
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
1034
+ <span style="font-weight: 600; color: var(--text-muted);">
1035
+ Topology: ${nodes.length} Nodes, ${edges.length} Edges, ${orphans.length} Orphans
1036
+ </span>
1037
+ <button class="back-btn" id="btn-load-graph">Refresh Graph</button>
1038
+ </div>
1039
+ <div class="graph-canvas-wrap">
1040
+ <svg width="100%" height="240" viewBox="0 0 600 240">
1041
+ <defs>
1042
+ <marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
1043
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="#f59e0b" />
1044
+ </marker>
1045
+ </defs>
1046
+ ${edges
1047
+ .slice(0, 30)
1048
+ .map((e, idx) => {
1049
+ const x1 = 50 + (idx * 37) % 500;
1050
+ const y1 = 40 + (idx * 23) % 160;
1051
+ const x2 = 80 + (idx * 43) % 500;
1052
+ const y2 = 60 + (idx * 31) % 160;
1053
+ return `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="rgba(245, 158, 11, 0.4)" stroke-width="1.5" marker-end="url(#arrow)" />`;
1054
+ })
1055
+ .join('')}
1056
+ ${nodes
1057
+ .slice(0, 20)
1058
+ .map((n, idx) => {
1059
+ const cx = 50 + (idx * 53) % 500;
1060
+ const cy = 40 + (idx * 33) % 160;
1061
+ return `
1062
+ <g>
1063
+ <circle cx="${cx}" cy="${cy}" r="12" fill="#1e293b" stroke="#f59e0b" stroke-width="2" />
1064
+ <text x="${cx}" y="${cy + 22}" fill="#94a3b8" font-size="9" text-anchor="middle">
1065
+ ${this.escape(n.label.slice(0, 14))}
1066
+ </text>
1067
+ </g>
1068
+ `;
1069
+ })
1070
+ .join('')}
1071
+ </svg>
1072
+ </div>
1073
+ `;
1074
+ }
947
1075
 
948
- <div class="field-group">
949
- <label class="field-label" for="cfg-fallback">Fallback Model Identifier</label>
950
- <input id="cfg-fallback" class="text-input" type="text" value="${this.config.fallback_model}" placeholder="e.g. qwen/qwen-2.5-coder-32b-instruct" />
951
- </div>
1076
+ renderGenerateScreen() {
1077
+ return `
1078
+ <div style="margin-bottom: 12px;">
1079
+ <label style="display: block; font-weight: 600; margin-bottom: 4px;">Target Repository (leave empty for current workspace):</label>
1080
+ <input class="search-input" id="generate-repo-input" placeholder="e.g. total-recall or gro-crm" value="${this.escape(this.generateRepo)}" />
1081
+ </div>
1082
+ <div style="display: flex; justify-content: flex-end; margin-bottom: 12px;">
1083
+ <button class="action-btn" id="btn-generate-exec" ${this.loading ? 'disabled' : ''}>
1084
+ ${this.loading ? 'Generating...' : 'Generate Wiki Documentation'}
1085
+ </button>
1086
+ </div>
1087
+ <div style="background: var(--bg-base); border: 1px solid var(--border-subtle); border-radius: 6px; padding: 10px; font-family: ui-monospace, monospace; font-size: 11px; max-height: 180px; overflow-y: auto;">
1088
+ ${
1089
+ this.generateLogs.length === 0
1090
+ ? '<span style="color: var(--text-muted);">Ready to dispatch on-demand generation.</span>'
1091
+ : this.generateLogs.map((l) => `<div>${this.escape(l)}</div>`).join('')
1092
+ }
1093
+ </div>
1094
+ `;
1095
+ }
952
1096
 
953
- <div class="field-group">
954
- <label class="field-label" for="cfg-tokens">Max Output Tokens</label>
955
- <input id="cfg-tokens" class="text-input" type="number" value="${this.config.max_tokens}" min="512" max="131072" />
956
- </div>
1097
+ renderRefreshScreen() {
1098
+ return `
1099
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
1100
+ <span style="font-weight: 600; color: var(--text-muted);">Fleet Staleness Status:</span>
1101
+ <button class="action-btn" id="btn-fleet-refresh" ${this.loading ? 'disabled' : ''}>
1102
+ ${this.loading ? 'Refreshing...' : 'Execute Fleet Refresh'}
1103
+ </button>
1104
+ </div>
1105
+ <div style="max-height: 220px; overflow-y: auto;">
1106
+ ${
1107
+ this.refreshReports.length === 0
1108
+ ? '<div style="color: var(--text-muted); text-align: center; padding: 20px;">No refresh reports loaded. Click Execute to evaluate fleet.</div>'
1109
+ : this.refreshReports
1110
+ .map(
1111
+ (r) => `
1112
+ <div style="display: flex; justify-content: space-between; padding: 6px 8px; background: var(--bg-elevated); border: 1px solid var(--border-subtle); border-radius: 4px; margin-bottom: 4px; font-size: 11px;">
1113
+ <span><strong>${this.escape(r.name)}</strong>: ${this.escape(r.action)}</span>
1114
+ <span style="color: var(--text-muted);">${r.commitsSince ? r.commitsSince + ' commits' : 'current'}</span>
1115
+ </div>
1116
+ `
1117
+ )
1118
+ .join('')
1119
+ }
1120
+ </div>
1121
+ `;
1122
+ }
957
1123
 
958
- <div class="field-group full-width">
959
- <label class="field-label" for="cfg-timeout">Timeout (milliseconds)</label>
960
- <input id="cfg-timeout" class="text-input" type="number" value="${this.config.timeout_ms}" min="30000" max="1800000" />
961
- </div>
1124
+ renderTokenScreen() {
1125
+ const { openrouter, google } = this.tokenStatus;
1126
+ return `
1127
+ <div class="auth-notice-card">
1128
+ Total Recall Auth Secrets Store credentials for model-driven documentation. Secrets are stored encrypted in the global store.
1129
+ </div>
1130
+ <div class="token-row">
1131
+ <div>
1132
+ <span class="token-key">OPENROUTER_API_KEY</span>
1133
+ <div style="font-size: 11px; color: var(--text-muted);">Default provider for cheap model wiki routing</div>
962
1134
  </div>
963
-
964
- <div class="button-group">
965
- <button id="btn-save-config" class="btn btn-primary" ${this.loading ? 'disabled' : ''}>
966
- Save Configuration
967
- </button>
968
- <button id="btn-reset-config" class="btn btn-secondary" ${this.loading ? 'disabled' : ''}>
969
- Reset to Defaults
970
- </button>
1135
+ <span class="token-badge ${openrouter?.configured ? 'ok' : 'missing'}">
1136
+ ${openrouter?.configured ? 'Configured' : 'Missing'}
1137
+ </span>
1138
+ </div>
1139
+ <div class="token-row">
1140
+ <div>
1141
+ <span class="token-key">GOOGLE_API_KEY</span>
1142
+ <div style="font-size: 11px; color: var(--text-muted);">Google Gemini provider for native model generation</div>
1143
+ </div>
1144
+ <span class="token-badge ${google?.configured ? 'ok' : 'missing'}">
1145
+ ${google?.configured ? 'Configured' : 'Missing'}
1146
+ </span>
1147
+ </div>
1148
+ <div style="margin-top: 14px; border-top: 1px solid var(--border-subtle); padding-top: 12px;">
1149
+ <label style="display: block; font-weight: 600; margin-bottom: 4px;">Save Key to Auth Vault:</label>
1150
+ <div style="display: flex; gap: 8px;">
1151
+ <select class="scope-select" id="token-provider-select">
1152
+ <option value="openrouter">OpenRouter</option>
1153
+ <option value="google">Google Gemini</option>
1154
+ </select>
1155
+ <input type="password" class="search-input" id="token-val-input" placeholder="Paste API Key (saved securely to auth secrets)..." />
1156
+ <button class="action-btn" id="btn-save-token">Save Key</button>
971
1157
  </div>
972
1158
  </div>
973
1159
  `;
974
1160
  }
975
1161
 
976
- renderRefreshScreen() {
977
- const dueCount = this.refreshCounts?.due ?? this.refreshReports.filter((r) => r.action === 'due').length;
978
- const uninitCount = this.refreshCounts?.uninitialized ?? this.refreshReports.filter((r) => r.uninitialized).length;
1162
+ renderLintScreen() {
1163
+ if (!this.lintData) {
1164
+ return `
1165
+ <div style="text-align: center; padding: 30px; color: var(--text-muted);">
1166
+ <div>Link hygiene and documentation health not audited yet.</div>
1167
+ <button class="action-btn" id="btn-run-lint" style="margin-top: 10px;">Run Health Check</button>
1168
+ </div>
1169
+ `;
1170
+ }
1171
+
1172
+ const { healthScore, totalHubs, totalLinks, brokenLinksCount, orphanCount, issues } = this.lintData;
1173
+ const scoreColor = healthScore > 80 ? 'var(--accent-green)' : (healthScore > 50 ? 'var(--accent-amber)' : 'var(--accent-red)');
979
1174
 
980
1175
  return `
981
- <div class="ingest-deck">
982
- <div class="section-header">
983
- <span class="section-title">Fleet Documentation Governance</span>
984
- <button id="btn-sync-refresh" class="btn btn-secondary" style="min-height: 32px; padding: 0 10px; font-size: 11px;" ${this.loading ? 'disabled' : ''}>
985
- Check Fleet
986
- </button>
1176
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
1177
+ <div style="font-size: 20px; font-weight: 700; color: ${scoreColor};">
1178
+ ${healthScore}% Health Score
987
1179
  </div>
988
-
989
- <div style="font-size: 12px; color: var(--face-text-muted); margin-bottom: 12px;">
990
- Registered Projects: <strong>${this.refreshReports.length}</strong> · Due for Update: <strong>${dueCount}</strong> · Uninitialized: <strong>${uninitCount}</strong>
1180
+ <button class="back-btn" id="btn-run-lint">Re-scan</button>
1181
+ </div>
1182
+ <div class="metric-deck">
1183
+ <div class="metric-cell">
1184
+ <div class="metric-label">Links Tested</div>
1185
+ <div class="metric-val">${totalLinks}</div>
991
1186
  </div>
992
-
993
- <div class="hub-list" style="max-height: 240px;">
994
- ${this.refreshReports.length === 0 ? `
995
- <div class="empty-state">No fleet report loaded. Click Check Fleet to inspect registered repositories.</div>
996
- ` : this.refreshReports.map((r) => {
997
- let badgeClass = 'current';
998
- let badgeText = 'CURRENT';
999
- if (r.uninitialized) {
1000
- badgeClass = 'uninit';
1001
- badgeText = 'MISSING WIKI';
1002
- } else if (r.action === 'due') {
1003
- badgeClass = 'due';
1004
- badgeText = 'DUE';
1005
- }
1006
- return `
1007
- <div class="hub-item">
1008
- <div>
1009
- <span class="hub-title">${r.name}</span>
1010
- <div class="hub-meta">${r.generatedAt ? `Generated ${r.generatedAt.slice(0, 10)}` : 'Never generated'} · ${r.commitsSince ? `${r.commitsSince} commits behind` : 'Up to date'}</div>
1011
- </div>
1012
- <span class="repo-status-badge ${badgeClass}">${badgeText}</span>
1013
- </div>
1014
- `;
1015
- }).join('')}
1187
+ <div class="metric-cell">
1188
+ <div class="metric-label">Broken Links</div>
1189
+ <div class="metric-val" style="color: ${brokenLinksCount ? 'var(--accent-red)' : 'var(--accent-green)'};">${brokenLinksCount}</div>
1016
1190
  </div>
1017
-
1018
- <div style="margin-top: 12px;">
1019
- <button id="btn-fleet-refresh-apply" class="btn btn-primary" style="width: 100%;" ${this.loading ? 'disabled' : ''}>
1020
- ${this.loading ? 'Executing Fleet Refresh…' : `Regenerate Stale & Initialize Missing Wikis (${dueCount} due)`}
1021
- </button>
1191
+ <div class="metric-cell">
1192
+ <div class="metric-label">Orphan Hubs</div>
1193
+ <div class="metric-val">${orphanCount}</div>
1022
1194
  </div>
1023
1195
  </div>
1196
+ <div style="max-height: 180px; overflow-y: auto;">
1197
+ ${
1198
+ issues.length === 0
1199
+ ? '<div style="color: var(--accent-green); padding: 10px;">[OK] All documentation links and headings verified clean!</div>'
1200
+ : issues
1201
+ .map(
1202
+ (iss) => `
1203
+ <div style="padding: 6px 8px; background: var(--bg-elevated); border: 1px solid var(--border-subtle); border-radius: 4px; margin-bottom: 4px; font-size: 11px;">
1204
+ <span style="color: var(--accent-red); font-weight: 600;">[${this.escape(iss.type)}]</span>
1205
+ ${this.escape(iss.message)}
1206
+ </div>
1207
+ `
1208
+ )
1209
+ .join('')
1210
+ }
1211
+ </div>
1024
1212
  `;
1025
1213
  }
1026
1214
 
1027
1215
  renderUpstreamScreen() {
1028
- const currentVer = this.upstream?.recordedVersion || '0.7.2';
1029
- const latestVer = this.upstream?.latestVersion || '0.7.3';
1030
-
1031
1216
  return `
1032
- <div class="ingest-deck">
1033
- <div class="section-header">
1034
- <span class="section-title">Upstream Package Surveillance</span>
1217
+ <div style="margin-bottom: 12px;">
1218
+ <div style="font-weight: 600; margin-bottom: 4px;">Upstream OpenWiki Surveillance:</div>
1219
+ <div style="font-size: 12px; color: var(--text-muted);">
1220
+ Recorded: v${this.upstream?.recordedVersion || '0.7.2'} | Latest: v${this.upstream?.latestVersion || '0.7.3'}
1035
1221
  </div>
1222
+ </div>
1223
+ <button class="action-btn" id="btn-watch-exec" style="margin-top: 8px;">Check Upstream Releases</button>
1224
+ `;
1225
+ }
1036
1226
 
1037
- <div class="stats-grid" style="margin-bottom: 14px;">
1038
- <div class="stat-card">
1039
- <div class="stat-label">Recorded Version</div>
1040
- <div class="stat-value">v${currentVer}</div>
1041
- </div>
1042
- <div class="stat-card">
1043
- <div class="stat-label">Latest Registry</div>
1044
- <div class="stat-value">v${latestVer}</div>
1045
- </div>
1046
- <div class="stat-card">
1047
- <div class="stat-label">Watcher Status</div>
1048
- <div class="stat-value" style="color: var(--face-ok);">MONITORING</div>
1049
- </div>
1227
+ renderConfigScreen() {
1228
+ return `
1229
+ <div class="config-deck">
1230
+ <div>
1231
+ <label style="display: block; font-weight: 600; margin-bottom: 4px;">Recommended Model Presets:</label>
1232
+ <span class="preset-chip" data-model="deepseek/deepseek-chat">DeepSeek Chat</span>
1233
+ <span class="preset-chip" data-model="deepseek/deepseek-v4.1-flash">DeepSeek Flash</span>
1234
+ <span class="preset-chip" data-model="qwen/qwen-2.5-coder-32b-instruct">Qwen 2.5 Coder</span>
1050
1235
  </div>
1051
-
1052
- <div class="button-group">
1053
- <button id="btn-watch" class="btn btn-secondary" ${this.loading ? 'disabled' : ''}>
1054
- Check Upstream Registry
1055
- </button>
1056
- <button id="btn-propagate" class="btn btn-primary" ${this.loading ? 'disabled' : ''}>
1057
- Propagate Latest Generator
1058
- </button>
1236
+ <div>
1237
+ <label style="display: block; font-weight: 600; margin-bottom: 4px;">Provider:</label>
1238
+ <select class="scope-select" id="cfg-provider" style="width: 100%;">
1239
+ <option value="openrouter" ${this.config.provider === 'openrouter' ? 'selected' : ''}>OpenRouter (Fleet Routing)</option>
1240
+ <option value="google" ${this.config.provider === 'google' ? 'selected' : ''}>Google Gemini</option>
1241
+ </select>
1242
+ </div>
1243
+ <div>
1244
+ <label style="display: block; font-weight: 600; margin-bottom: 4px;">Model ID:</label>
1245
+ <input class="search-input" id="cfg-model" value="${this.escape(this.config.model)}" style="width: 100%;" />
1246
+ </div>
1247
+ <div>
1248
+ <label style="display: block; font-weight: 600; margin-bottom: 4px;">Max Tokens:</label>
1249
+ <input type="number" class="search-input" id="cfg-tokens" value="${this.config.max_tokens}" style="width: 100%;" />
1250
+ </div>
1251
+ <div style="display: flex; justify-content: flex-end;">
1252
+ <button class="action-btn" id="btn-save-cfg">Save Settings</button>
1059
1253
  </div>
1060
1254
  </div>
1061
1255
  `;
1062
1256
  }
1063
1257
 
1064
- bindEvents() {
1065
- const shadow = this.shadowRoot;
1066
- if (!shadow) return;
1258
+ attachEventListeners() {
1259
+ const root = this.shadowRoot;
1260
+ if (!root) return;
1261
+
1262
+ // Scope selection
1263
+ const scopeSelect = root.querySelector('#scope-select');
1264
+ if (scopeSelect) {
1265
+ scopeSelect.addEventListener('change', (e) => this.setScope(e.target.value));
1266
+ }
1067
1267
 
1068
- shadow.querySelectorAll('.screen-tab-btn').forEach((tab) => {
1069
- tab.addEventListener('click', () => {
1070
- const targetScreen = tab.getAttribute('data-screen');
1071
- if (targetScreen) this.setScreen(targetScreen);
1268
+ // Navigation buttons
1269
+ root.querySelectorAll('.nav-btn').forEach((btn) => {
1270
+ btn.addEventListener('click', () => {
1271
+ const screen = btn.getAttribute('data-screen');
1272
+ if (screen) this.setScreen(screen);
1072
1273
  });
1073
1274
  });
1074
1275
 
1075
- const pathInput = shadow.querySelector('#target-path-input');
1076
- if (pathInput) {
1077
- pathInput.addEventListener('input', (e) => {
1078
- this.targetPath = e.target.value.trim();
1276
+ // Hub item click -> Read Hub
1277
+ root.querySelectorAll('.hub-item').forEach((item) => {
1278
+ item.addEventListener('click', () => {
1279
+ const idx = parseInt(item.getAttribute('data-hub-index'), 10);
1280
+ if (!isNaN(idx) && this.hubs[idx]) {
1281
+ this.readHubDocument(this.hubs[idx]);
1282
+ }
1079
1283
  });
1080
- }
1284
+ });
1081
1285
 
1082
- const dryRunCheckbox = shadow.querySelector('#dry-run-checkbox');
1083
- if (dryRunCheckbox) {
1084
- dryRunCheckbox.addEventListener('change', (e) => {
1085
- this.dryRun = e.target.checked;
1286
+ // Back to hubs from reader
1287
+ const backBtn = root.querySelector('#btn-back-to-hubs');
1288
+ if (backBtn) {
1289
+ backBtn.addEventListener('click', () => {
1290
+ this.selectedHub = null;
1291
+ this.selectedHubContent = null;
1292
+ this.render();
1086
1293
  });
1087
1294
  }
1088
1295
 
1089
- const btnIngest = shadow.querySelector('#btn-ingest');
1090
- if (btnIngest) {
1091
- btnIngest.addEventListener('click', () => this.executeIngest());
1296
+ // Refresh hubs button
1297
+ const refreshHubsBtn = root.querySelector('#btn-refresh-hubs');
1298
+ if (refreshHubsBtn) {
1299
+ refreshHubsBtn.addEventListener('click', () => this.refreshHubs());
1300
+ }
1301
+
1302
+ // Ingest button
1303
+ const ingestBtn = root.querySelector('#btn-ingest-exec');
1304
+ if (ingestBtn) {
1305
+ ingestBtn.addEventListener('click', () => this.executeIngest());
1306
+ }
1307
+
1308
+ // Search button
1309
+ const searchBtn = root.querySelector('#btn-search-exec');
1310
+ const searchInput = root.querySelector('#search-input');
1311
+ if (searchBtn && searchInput) {
1312
+ searchBtn.addEventListener('click', () => this.executeSearch(searchInput.value));
1313
+ searchInput.addEventListener('keydown', (e) => {
1314
+ if (e.key === 'Enter') this.executeSearch(searchInput.value);
1315
+ });
1092
1316
  }
1093
1317
 
1094
- const btnBootstrap = shadow.querySelector('#btn-bootstrap');
1095
- if (btnBootstrap) {
1096
- btnBootstrap.addEventListener('click', () => this.executeBootstrap());
1318
+ // Graph button
1319
+ const loadGraphBtn = root.querySelector('#btn-load-graph');
1320
+ if (loadGraphBtn) {
1321
+ loadGraphBtn.addEventListener('click', () => this.loadGraph());
1097
1322
  }
1098
1323
 
1099
- const btnRefresh = shadow.querySelector('#btn-refresh');
1100
- if (btnRefresh) {
1101
- btnRefresh.addEventListener('click', () => this.refreshHubs());
1324
+ // Lint button
1325
+ const runLintBtn = root.querySelector('#btn-run-lint');
1326
+ if (runLintBtn) {
1327
+ runLintBtn.addEventListener('click', () => this.loadLint());
1102
1328
  }
1103
1329
 
1104
- const btnSyncRefresh = shadow.querySelector('#btn-sync-refresh');
1105
- if (btnSyncRefresh) {
1106
- btnSyncRefresh.addEventListener('click', () => this.loadRefreshReport());
1330
+ // Fleet refresh button
1331
+ const fleetRefreshBtn = root.querySelector('#btn-fleet-refresh');
1332
+ if (fleetRefreshBtn) {
1333
+ fleetRefreshBtn.addEventListener('click', () => this.executeFleetRefresh());
1107
1334
  }
1108
1335
 
1109
- const btnFleetApply = shadow.querySelector('#btn-fleet-refresh-apply');
1110
- if (btnFleetApply) {
1111
- btnFleetApply.addEventListener('click', () => this.executeFleetRefresh());
1336
+ // Upstream watch button
1337
+ const watchBtn = root.querySelector('#btn-watch-exec');
1338
+ if (watchBtn) {
1339
+ watchBtn.addEventListener('click', () => this.executeWatch());
1112
1340
  }
1113
1341
 
1114
- const btnWatch = shadow.querySelector('#btn-watch');
1115
- if (btnWatch) {
1116
- btnWatch.addEventListener('click', () => this.executeWatch());
1342
+ // Generate button
1343
+ const genBtn = root.querySelector('#btn-generate-exec');
1344
+ const genInput = root.querySelector('#generate-repo-input');
1345
+ if (genBtn) {
1346
+ genBtn.addEventListener('click', () => {
1347
+ if (genInput) this.generateRepo = genInput.value.trim();
1348
+ this.executeGenerate();
1349
+ });
1117
1350
  }
1118
1351
 
1119
- const btnPropagate = shadow.querySelector('#btn-propagate');
1120
- if (btnPropagate) {
1121
- btnPropagate.addEventListener('click', () => this.executePropagate());
1352
+ // Save token button
1353
+ const saveTokenBtn = root.querySelector('#btn-save-token');
1354
+ const tokenProviderSelect = root.querySelector('#token-provider-select');
1355
+ const tokenValInput = root.querySelector('#token-val-input');
1356
+ if (saveTokenBtn && tokenProviderSelect && tokenValInput) {
1357
+ saveTokenBtn.addEventListener('click', () => {
1358
+ this.saveToken(tokenProviderSelect.value, tokenValInput.value);
1359
+ tokenValInput.value = '';
1360
+ });
1122
1361
  }
1123
1362
 
1124
- shadow.querySelectorAll('.preset-chip').forEach((chip) => {
1363
+ // Preset chips
1364
+ root.querySelectorAll('.preset-chip').forEach((chip) => {
1125
1365
  chip.addEventListener('click', async () => {
1126
- const p = chip.getAttribute('data-provider');
1127
- const m = chip.getAttribute('data-model');
1128
- const f = chip.getAttribute('data-fallback');
1129
- if (p) await this.setConfigValue('provider', p);
1130
- if (m) await this.setConfigValue('model', m);
1131
- if (f) await this.setConfigValue('fallback_model', f);
1366
+ const model = chip.getAttribute('data-model');
1367
+ if (model) {
1368
+ const modelInput = root.querySelector('#cfg-model');
1369
+ if (modelInput) modelInput.value = model;
1370
+ await this.setConfigValue('model', model);
1371
+ }
1132
1372
  });
1133
1373
  });
1134
1374
 
1135
- const btnSaveConfig = shadow.querySelector('#btn-save-config');
1136
- if (btnSaveConfig) {
1137
- btnSaveConfig.addEventListener('click', async () => {
1138
- const provider = shadow.querySelector('#cfg-provider')?.value;
1139
- const model = shadow.querySelector('#cfg-model')?.value;
1140
- const fallback = shadow.querySelector('#cfg-fallback')?.value;
1141
- const tokens = shadow.querySelector('#cfg-tokens')?.value;
1142
- const timeout = shadow.querySelector('#cfg-timeout')?.value;
1143
-
1144
- if (provider) await this.setConfigValue('provider', provider);
1145
- if (model) await this.setConfigValue('model', model);
1146
- if (fallback) await this.setConfigValue('fallback_model', fallback);
1147
- if (tokens) await this.setConfigValue('max_tokens', tokens);
1148
- if (timeout) await this.setConfigValue('timeout_ms', timeout);
1375
+ // Save config button
1376
+ const saveCfgBtn = root.querySelector('#btn-save-cfg');
1377
+ if (saveCfgBtn) {
1378
+ saveCfgBtn.addEventListener('click', async () => {
1379
+ const prov = root.querySelector('#cfg-provider')?.value;
1380
+ const mod = root.querySelector('#cfg-model')?.value;
1381
+ const tok = root.querySelector('#cfg-tokens')?.value;
1382
+ if (prov) await this.setConfigValue('provider', prov);
1383
+ if (mod) await this.setConfigValue('model', mod);
1384
+ if (tok) await this.setConfigValue('max_tokens', tok);
1149
1385
  });
1150
1386
  }
1387
+ }
1151
1388
 
1152
- const btnResetConfig = shadow.querySelector('#btn-reset-config');
1153
- if (btnResetConfig) {
1154
- btnResetConfig.addEventListener('click', () => this.resetConfig());
1155
- }
1389
+ escape(str) {
1390
+ if (!str) return '';
1391
+ return String(str)
1392
+ .replace(/&/g, '&amp;')
1393
+ .replace(/</g, '&lt;')
1394
+ .replace(/>/g, '&gt;')
1395
+ .replace(/"/g, '&quot;');
1156
1396
  }
1157
1397
  }
1158
1398
 
1159
- if (!customElements.get('tr-openwiki-card')) {
1399
+ if (typeof customElements !== 'undefined' && !customElements.get('tr-openwiki-card')) {
1160
1400
  customElements.define('tr-openwiki-card', OpenWikiCard);
1161
1401
  }
1162
- if (!customElements.get('openwiki-card')) {
1163
- customElements.define('openwiki-card', class extends OpenWikiCard {});
1164
- }