total-recall-brain 3.41.2 → 3.41.4

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 (128) hide show
  1. package/bin/total-recall.mjs +19 -4
  2. package/frontend/dist/assets/{index-BG9NkVUy.css → index-BIQmqDKP.css} +1 -1
  3. package/frontend/dist/assets/{index-BtuiZqwP.js → index-CRzjQLu7.js} +7 -7
  4. package/frontend/dist/index.html +2 -2
  5. package/frontend/dist/sw.js +8 -5
  6. package/frontend/src/api/auth.ts +0 -3
  7. package/frontend/src/components/plugins/PluginPanel.tsx +7 -3
  8. package/frontend/src/components/plugins/card/CardEngine.tsx +1 -1
  9. package/frontend/src/pages/LoginPage.tsx +1 -41
  10. package/frontend/src/pages/PluginsPage.tsx +14 -7
  11. package/package.json +7 -5
  12. package/scaffold/.agent/skills/plugin/scripts/record.mjs +6 -3
  13. package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +197 -31
  14. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +6 -6
  15. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
  16. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +8 -1
  17. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +48 -0
  18. package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +42 -0
  19. package/scaffold/.agent/skills/total-recall/plugins/auth/src/mesh-secrets.mjs +18 -0
  20. package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/secrets.mjs +5 -2
  21. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-carriers.mjs +76 -0
  22. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-prune.mjs +37 -0
  23. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +53 -17
  24. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +56 -8
  25. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +1068 -204
  26. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/panel.js +4 -2
  27. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +32 -0
  28. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +436 -274
  29. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +12 -12
  30. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
  31. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +16 -2
  32. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +461 -242
  33. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +12 -12
  34. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  35. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -7
  36. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +17 -2
  37. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +426 -300
  38. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +23 -8
  39. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +6 -2
  40. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +32 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +350 -283
  42. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +23 -8
  43. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +5 -1
  44. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +31 -19
  45. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +23 -10
  46. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -3
  47. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  48. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/grants.mjs +7 -3
  49. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +61 -38
  50. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +57 -2
  51. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +220 -36
  52. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/server/routes/mesh.mjs +7 -2
  53. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +65 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +59 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +534 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +139 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +2849 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +26 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +376 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +37 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adapters/dsh.mjs +446 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +23 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +398 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +152 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +82 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +90 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +415 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +36 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +35 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/tracker.mjs +133 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +233 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +32 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +845 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/BRICK.md +64 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/index.mjs +250 -0
  76. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +344 -14
  77. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +2917 -140
  78. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +9 -3
  79. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +223 -5
  80. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +129 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +355 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +375 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +37 -10
  84. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +986 -280
  85. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -1
  86. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +25 -10
  87. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +6 -2
  88. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  89. package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +17 -3
  90. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +38 -15
  91. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +60 -34
  92. package/src/cli/command.mjs +23 -18
  93. package/src/cli/config.mjs +3 -0
  94. package/src/cli/connect.mjs +12 -116
  95. package/src/cli/daemon.mjs +23 -1
  96. package/src/cli/harness.mjs +0 -1
  97. package/src/cli/plugin/copies.mjs +8 -5
  98. package/src/cli/plugin/create.mjs +56 -3
  99. package/src/cli/plugin/index.mjs +40 -5
  100. package/src/cli/plugin/monetization.mjs +232 -12
  101. package/src/cli/preflight.mjs +161 -0
  102. package/src/cli/rebuild.mjs +22 -0
  103. package/src/cli/reset-password.mjs +3 -2
  104. package/src/cli/skill.mjs +8 -2
  105. package/src/cli/status.mjs +25 -7
  106. package/src/core/automatic-brain-updates.mjs +9 -3
  107. package/src/core/brain-updates.mjs +63 -5
  108. package/src/core/cli-commands.mjs +2 -0
  109. package/src/core/context-compiler.mjs +8 -1
  110. package/src/core/default-plugins.mjs +1 -1
  111. package/src/core/global-brain-updates.mjs +21 -1
  112. package/src/core/mesh-brain-updates.mjs +5 -2
  113. package/src/core/meta-harness.mjs +32 -79
  114. package/src/core/plugin-dependencies.mjs +6 -1
  115. package/src/core/plugin-runner.mjs +6 -1
  116. package/src/core/plugin-store.mjs +67 -1
  117. package/src/core/plugin-updates.mjs +1 -1
  118. package/src/core/portable-skills-maintenance.mjs +19 -0
  119. package/src/core/preflight-gate.mjs +77 -0
  120. package/src/core/preflight-receipt.mjs +123 -0
  121. package/src/core/required-plugins.mjs +7 -0
  122. package/src/core/runtime.mjs +0 -3
  123. package/src/core/skills-registry.mjs +33 -3
  124. package/src/core/surface.mjs +38 -1
  125. package/src/server/auth.mjs +41 -1
  126. package/src/server/memory-app.mjs +2 -1
  127. package/src/server/routes/auth.mjs +25 -1
  128. package/src/server/routes/plugins.mjs +17 -4
@@ -1,452 +1,1158 @@
1
1
  /**
2
- * OpenWiki — Multi-Repo Knowledge Graph & Documentation Wiki
3
- * Bespoke Web Component Card Face: Knowledge Amber & Ink Theme.
2
+ * OpenWiki — Knowledge Graph & Multi-Repo Documentation Wiki
3
+ * Bespoke Card Face: Deep Slate & Knowledge Amber Interface.
4
4
  *
5
- * Real hub telemetry, documentation ingest controls, zero fake animations or canvases.
5
+ * Exposes live hub discovery, markdown file inspection, upstream release surveillance,
6
+ * and structured fleet propagation controls. Operates without mock data or boilerplate sinks.
6
7
  */
7
8
 
8
9
  export class OpenWikiCard extends HTMLElement {
10
+ static get screens() {
11
+ return [
12
+ { id: 'hubs', label: 'Hubs' },
13
+ { id: 'config', label: 'Configuration' },
14
+ { id: 'refresh', label: 'Fleet Refresh' },
15
+ { id: 'upstream', label: 'Upstream Watcher' }
16
+ ];
17
+ }
18
+
19
+ get screens() {
20
+ return OpenWikiCard.screens;
21
+ }
22
+
9
23
  constructor() {
10
24
  super();
11
- this.status = 'READY';
12
- this.executing = false;
25
+ this.activeScreen = 'hubs';
26
+ this.hubs = [];
27
+ this.loading = false;
28
+ this.statusText = 'READY';
13
29
  this.targetPath = 'openwiki';
14
- this.hubs = [
15
- { title: 'OpenWiki', path: 'openwiki/README.md', lines: 11, size_bytes: 339 },
16
- { title: 'Architecture', path: 'openwiki/architecture.md', lines: 6, size_bytes: 221 },
17
- { title: 'Memory', path: 'openwiki/memory.md', lines: 12, size_bytes: 299 },
18
- { title: 'Secrets & API keys', path: 'openwiki/secrets.md', lines: 8, size_bytes: 286 },
19
- { title: 'Software Factory', path: 'openwiki/software-factory.md', lines: 36, size_bytes: 1833 }
20
- ];
30
+ this.dryRun = false;
31
+ this.executionResult = null;
32
+ this.errorMessage = null;
33
+ this.lastRefreshedAt = null;
34
+ this.upstream = null;
35
+ this.config = {
36
+ provider: 'openrouter',
37
+ model: 'deepseek/deepseek-v4.1-flash',
38
+ fallback_model: 'qwen/qwen-2.5-coder-32b-instruct',
39
+ max_tokens: 4096,
40
+ timeout_ms: 300000
41
+ };
42
+ this.refreshReports = [];
43
+ this.refreshCounts = null;
44
+ this._boundOnScreenChange = this.handleExternalScreenChange.bind(this);
21
45
  }
22
46
 
23
47
  connectedCallback() {
24
48
  if (!this.shadowRoot) {
25
49
  this.attachShadow({ mode: 'open' });
26
50
  }
51
+ this.addEventListener('tr:screen-change', this._boundOnScreenChange);
52
+ this.addEventListener('plugin-show-screen', this._boundOnScreenChange);
27
53
  this.render();
28
- this.loadHubs();
54
+ this.refreshHubs();
55
+ this.loadConfig();
29
56
  }
30
57
 
31
- async loadHubs() {
58
+ disconnectedCallback() {
59
+ this.removeEventListener('tr:screen-change', this._boundOnScreenChange);
60
+ this.removeEventListener('plugin-show-screen', this._boundOnScreenChange);
61
+ }
62
+
63
+ handleExternalScreenChange(event) {
64
+ const screenId = event.detail?.screen || event.detail?.screenId || event.detail?.id;
65
+ if (screenId && OpenWikiCard.screens.some((s) => s.id === screenId)) {
66
+ this.setScreen(screenId);
67
+ }
68
+ }
69
+
70
+ setScreen(id) {
71
+ if (this.activeScreen === id) return;
72
+ this.activeScreen = id;
73
+ this.errorMessage = null;
74
+ this.render();
75
+ if (typeof this.host?.screenChanged === 'function') {
76
+ try {
77
+ this.host.screenChanged({ screen: id });
78
+ } catch {
79
+ // Host screenChanged callback optional
80
+ }
81
+ }
82
+ if (id === 'refresh' && (!this.refreshReports || this.refreshReports.length === 0)) {
83
+ this.loadRefreshReport();
84
+ } else if (id === 'config') {
85
+ this.loadConfig();
86
+ }
87
+ }
88
+
89
+ async loadConfig() {
32
90
  try {
33
- if (this.host?.run) {
91
+ if (this.host && typeof this.host.run === 'function') {
92
+ const res = await this.host.run('config', ['show', '--json']);
93
+ if (res?.config) {
94
+ this.config = res.config;
95
+ this.render();
96
+ }
97
+ }
98
+ } catch {
99
+ // Retain active config defaults if host call fails
100
+ }
101
+ }
102
+
103
+ async setConfigValue(key, value) {
104
+ this.loading = true;
105
+ this.errorMessage = null;
106
+ this.render();
107
+ try {
108
+ if (this.host && typeof this.host.run === 'function') {
109
+ const res = await this.host.run('config', ['set', key, String(value), '--json']);
110
+ if (res?.config) {
111
+ this.config = res.config;
112
+ }
113
+ }
114
+ } catch (err) {
115
+ this.errorMessage = err.message || `Failed to update ${key}`;
116
+ } finally {
117
+ this.loading = false;
118
+ this.render();
119
+ }
120
+ }
121
+
122
+ async resetConfig() {
123
+ this.loading = true;
124
+ this.errorMessage = null;
125
+ this.render();
126
+ try {
127
+ 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
+ }
132
+ }
133
+ } catch (err) {
134
+ this.errorMessage = err.message || 'Failed to reset configuration';
135
+ } finally {
136
+ this.loading = false;
137
+ this.render();
138
+ }
139
+ }
140
+
141
+ async refreshHubs() {
142
+ this.loading = true;
143
+ this.errorMessage = null;
144
+ this.render();
145
+
146
+ try {
147
+ if (this.host && typeof this.host.run === 'function') {
34
148
  const res = await this.host.run('hubs', ['--json']);
35
149
  if (res && Array.isArray(res.hubs)) {
36
150
  this.hubs = res.hubs;
37
- this.updateHubsUI();
151
+ this.statusText = 'SYNCHRONIZED';
152
+ } else {
153
+ this.hubs = [];
154
+ this.statusText = 'UNINDEXED';
155
+ }
156
+
157
+ try {
158
+ const statusRes = await this.host.run('status', ['--json']);
159
+ if (statusRes?.upstream) {
160
+ this.upstream = statusRes.upstream;
161
+ }
162
+ } catch {
163
+ // Upstream metadata optional
38
164
  }
165
+ } else {
166
+ this.statusText = 'DISCONNECTED';
39
167
  }
40
- } catch {
41
- // Retain fallback hubs in disconnected state
168
+ this.lastRefreshedAt = new Date().toLocaleTimeString();
169
+ } catch (err) {
170
+ this.statusText = 'ERROR';
171
+ this.errorMessage = err.message || 'Hub synchronization failure';
172
+ this.hubs = [];
173
+ } finally {
174
+ this.loading = false;
175
+ this.render();
176
+ }
177
+ }
178
+
179
+ async loadRefreshReport() {
180
+ this.loading = true;
181
+ this.errorMessage = null;
182
+ this.render();
183
+ try {
184
+ if (this.host && typeof this.host.run === 'function') {
185
+ const res = await this.host.run('refresh', ['--json']);
186
+ if (res) {
187
+ this.refreshReports = Array.isArray(res.reports) ? res.reports : [];
188
+ this.refreshCounts = res.counts || null;
189
+ }
190
+ }
191
+ } catch (err) {
192
+ this.errorMessage = err.message || 'Fleet status check failure';
193
+ } finally {
194
+ this.loading = false;
195
+ this.render();
42
196
  }
43
197
  }
44
198
 
45
- updateHubsUI() {
46
- const countEl = this.shadowRoot.querySelector('#hubs-count');
47
- if (countEl) countEl.textContent = `${this.hubs.length} Hubs`;
199
+ async executeFleetRefresh() {
200
+ if (this.loading) return;
201
+ this.loading = true;
202
+ this.executionResult = null;
203
+ this.errorMessage = null;
204
+ this.render();
48
205
 
49
- const container = this.shadowRoot.querySelector('#hubs-container');
50
- if (!container) return;
51
- container.innerHTML = this.hubs.slice(0, 4).map((h) => `
52
- <div class="hub-item">
53
- <span class="hub-title">${h.title}</span>
54
- <span class="hub-meta">${h.lines} lines · ${Math.round(h.size_bytes / 1024 * 10) / 10}KB</span>
55
- </div>
56
- `).join('') + (this.hubs.length > 4 ? `<div class="hub-more">+${this.hubs.length - 4} more indexed hubs</div>` : '');
206
+ try {
207
+ if (this.host && typeof this.host.run === 'function') {
208
+ 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
+ if (res?.reports) {
216
+ this.refreshReports = res.reports;
217
+ this.refreshCounts = res.counts;
218
+ }
219
+ } else {
220
+ throw new Error('Host execution context unavailable');
221
+ }
222
+ } catch (err) {
223
+ this.errorMessage = err.message || 'Fleet refresh execution failure';
224
+ } finally {
225
+ this.loading = false;
226
+ this.render();
227
+ }
57
228
  }
58
229
 
59
- async execute(action, customArgs = []) {
60
- const out = this.shadowRoot.querySelector('#card-out');
61
- const badge = this.shadowRoot.querySelector('#status-badge');
62
- const pathInput = this.shadowRoot.querySelector('#path-input');
63
- const target = pathInput ? pathInput.value.trim() : this.targetPath;
230
+ async executeIngest() {
231
+ if (this.loading) return;
232
+ this.loading = true;
233
+ this.executionResult = null;
234
+ this.errorMessage = null;
235
+ this.render();
64
236
 
65
- let args = [...customArgs];
66
- if (action === 'ingest' && args.length === 0) {
67
- args = [target || 'openwiki'];
237
+ const args = [this.targetPath || 'openwiki'];
238
+ if (this.dryRun) args.push('--dry-run');
239
+
240
+ try {
241
+ 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');
252
+ }
253
+ } catch (err) {
254
+ this.errorMessage = err.message || 'Ingestion cycle failure';
255
+ } finally {
256
+ this.loading = false;
257
+ this.refreshHubs();
68
258
  }
259
+ }
69
260
 
70
- if (out) out.textContent = `[OPENWIKI-CLI] Executing openwiki ${action} ${args.join(' ')}...`;
71
- if (badge) {
72
- badge.textContent = 'BUSY';
73
- badge.className = 'badge busy';
261
+ async executeBootstrap() {
262
+ if (this.loading) return;
263
+ this.loading = true;
264
+ this.executionResult = null;
265
+ this.errorMessage = null;
266
+ this.render();
267
+
268
+ try {
269
+ 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');
278
+ }
279
+ } catch (err) {
280
+ this.errorMessage = err.message || 'Bootstrap procedure failure';
281
+ } finally {
282
+ this.loading = false;
283
+ this.refreshHubs();
74
284
  }
75
- this.executing = true;
285
+ }
286
+
287
+ async executeWatch() {
288
+ if (this.loading) return;
289
+ this.loading = true;
290
+ this.executionResult = null;
291
+ this.errorMessage = null;
292
+ this.render();
76
293
 
77
294
  try {
78
- let resultText = '';
79
295
  if (this.host && typeof this.host.run === 'function') {
80
- const res = await this.host.run(action, args);
81
- if (typeof res === 'object') {
82
- resultText = res.output || JSON.stringify(res, null, 2);
83
- } else {
84
- resultText = String(res || 'Execution completed without output.');
296
+ 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
+ if (res?.status) {
303
+ this.upstream = {
304
+ recordedVersion: res.status.currentVersion,
305
+ latestVersion: res.status.latestVersion
306
+ };
85
307
  }
86
308
  } else {
87
- if (action === 'status') {
88
- resultText = 'OpenWiki: implemented subcommands: probe, hubs, ingest';
89
- } else if (action === 'hubs') {
90
- resultText = `Discovered ${this.hubs.length} OpenWiki Hub(s):\n` + this.hubs.map((h) => ` - ${h.title}: ${h.path} (${h.lines} lines)`).join('\n');
91
- } else if (action === 'probe') {
92
- resultText = 'Health probe: HTTP 200 (14ms) from http://127.0.0.1:3000';
93
- } else if (action === 'ingest') {
94
- resultText = `OpenWiki Ingest: Processed ${args[0] || 'openwiki'} into knowledge graph. Ingest completed successfully.`;
95
- }
309
+ throw new Error('Host execution context unavailable');
96
310
  }
311
+ } catch (err) {
312
+ this.errorMessage = err.message || 'Upstream surveillance failure';
313
+ } finally {
314
+ this.loading = false;
315
+ this.refreshHubs();
316
+ }
317
+ }
97
318
 
98
- if (out) out.textContent = resultText.trim();
99
- if (badge) {
100
- badge.textContent = 'READY';
101
- badge.className = 'badge ready';
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');
102
336
  }
103
337
  } catch (err) {
104
- if (out) out.textContent = `[OPENWIKI ERROR] ${err.message || err}`;
105
- if (badge) {
106
- badge.textContent = 'ERROR';
107
- badge.className = 'badge error';
108
- }
338
+ this.errorMessage = err.message || 'Fleet propagation failure';
109
339
  } finally {
110
- this.executing = false;
340
+ this.loading = false;
341
+ this.refreshHubs();
111
342
  }
112
343
  }
113
344
 
114
345
  render() {
346
+ if (!this.shadowRoot) return;
347
+
348
+ const countText = this.hubs.length ? `${this.hubs.length} Hubs` : '—';
349
+ 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';
352
+
115
353
  this.shadowRoot.innerHTML = `
116
354
  <style>
117
355
  :host {
118
356
  display: block;
119
- font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
120
- background: #140f06;
121
- color: #fef3c7;
122
- border-radius: 10px;
123
- border: 1px solid rgba(217, 119, 6, 0.35);
124
- box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45);
125
- padding: 18px;
126
357
  box-sizing: border-box;
127
- overflow: hidden;
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;
372
+ padding: 16px;
373
+ font-family: system-ui, -apple-system, sans-serif;
128
374
  }
129
375
 
130
- .header {
376
+ *, *::before, *::after {
377
+ box-sizing: inherit;
378
+ }
379
+
380
+ .header-bar {
131
381
  display: flex;
382
+ align-items: center;
132
383
  justify-content: space-between;
133
- align-items: flex-start;
134
- margin-bottom: 14px;
384
+ gap: 12px;
385
+ margin-bottom: 12px;
386
+ border-bottom: 1px solid var(--border, var(--face-border));
387
+ padding-bottom: 12px;
135
388
  }
136
389
 
137
- .brand-meta {
390
+ .brand-cluster {
138
391
  display: flex;
139
392
  align-items: center;
140
- gap: 10px;
393
+ gap: 8px;
141
394
  }
142
395
 
143
- .brand-icon {
144
- width: 32px;
145
- height: 32px;
146
- flex-shrink: 0;
147
- color: #f59e0b;
396
+ .wordmark-badge {
397
+ display: inline-flex;
398
+ align-items: center;
399
+ font-family: ui-monospace, monospace;
400
+ font-size: 13px;
401
+ font-weight: 700;
402
+ letter-spacing: 0.05em;
403
+ color: var(--face-accent);
404
+ background: var(--face-surface-subtle);
405
+ padding: 4px 8px;
406
+ border-radius: 4px;
148
407
  }
149
408
 
150
- .title-block {
151
- display: flex;
152
- flex-direction: column;
153
- gap: 2px;
409
+ .system-title {
410
+ font-size: 14px;
411
+ font-weight: 600;
412
+ color: var(--text-primary, var(--face-text));
154
413
  }
155
414
 
156
- .title {
157
- font-size: 15px;
415
+ .status-pill {
416
+ display: inline-flex;
417
+ align-items: center;
418
+ padding: 4px 10px;
419
+ border-radius: 12px;
420
+ font-size: 11px;
158
421
  font-weight: 700;
159
- letter-spacing: 0.03em;
422
+ letter-spacing: 0.04em;
160
423
  text-transform: uppercase;
161
- color: #fde68a;
162
- margin: 0;
424
+ background: var(--face-surface-subtle);
425
+ color: var(--face-text-muted);
426
+ border: 1px solid var(--border, var(--face-border));
163
427
  }
164
428
 
165
- .subtitle {
166
- font-size: 11px;
167
- color: #d97706;
168
- margin: 0;
429
+ .status-pill.ok {
430
+ color: var(--face-ok);
431
+ border-color: var(--face-ok);
432
+ }
433
+
434
+ .status-pill.danger {
435
+ color: var(--face-danger);
436
+ border-color: var(--face-danger);
169
437
  }
170
438
 
171
- .badge {
439
+ .screen-tabs {
440
+ display: flex;
441
+ gap: 6px;
442
+ margin-bottom: 16px;
443
+ border-bottom: 1px solid var(--border, var(--face-border));
444
+ padding-bottom: 8px;
445
+ overflow-x: auto;
446
+ }
447
+
448
+ .screen-tab-btn {
449
+ min-height: 36px;
450
+ padding: 0 12px;
451
+ font-size: 12px;
452
+ font-weight: 600;
453
+ border-radius: 6px;
454
+ border: 1px solid transparent;
455
+ background: transparent;
456
+ color: var(--face-text-muted);
457
+ cursor: pointer;
458
+ transition: all 0.15s ease;
172
459
  display: inline-flex;
173
460
  align-items: center;
174
- font-size: 10px;
175
- font-weight: 700;
176
- letter-spacing: 0.08em;
177
- text-transform: uppercase;
178
- border-radius: 4px;
179
- padding: 4px 8px;
180
- background: rgba(217, 119, 6, 0.15);
181
- color: #fbbf24;
182
- border: 1px solid rgba(217, 119, 6, 0.3);
461
+ white-space: nowrap;
183
462
  }
184
463
 
185
- .badge.ready {
186
- background: rgba(16, 185, 129, 0.15);
187
- color: #34d399;
188
- border-color: rgba(16, 185, 129, 0.35);
464
+ .screen-tab-btn:hover {
465
+ color: var(--face-text);
466
+ background: var(--face-surface-subtle);
189
467
  }
190
468
 
191
- .badge.busy {
192
- background: rgba(56, 189, 248, 0.15);
193
- color: #38bdf8;
194
- border-color: rgba(56, 189, 248, 0.35);
469
+ .screen-tab-btn.active {
470
+ color: var(--face-accent);
471
+ background: var(--face-surface);
472
+ border-color: var(--face-accent);
195
473
  }
196
474
 
197
- .badge.error {
198
- background: rgba(239, 68, 68, 0.2);
199
- color: #f87171;
200
- border-color: rgba(239, 68, 68, 0.4);
475
+ .stats-grid {
476
+ display: grid;
477
+ grid-template-columns: repeat(3, 1fr);
478
+ gap: 8px;
479
+ margin-bottom: 16px;
201
480
  }
202
481
 
203
- .section-label {
204
- font-size: 10px;
482
+ .stat-card {
483
+ background: var(--face-surface);
484
+ border: 1px solid var(--border, var(--face-border));
485
+ border-radius: 6px;
486
+ padding: 10px;
487
+ }
488
+
489
+ .stat-label {
490
+ font-size: 11px;
205
491
  text-transform: uppercase;
206
- letter-spacing: 0.06em;
207
- color: #f59e0b;
492
+ letter-spacing: 0.04em;
493
+ color: var(--face-text-muted);
494
+ margin-bottom: 4px;
495
+ }
496
+
497
+ .stat-value {
498
+ font-size: 16px;
208
499
  font-weight: 700;
209
- margin-bottom: 6px;
500
+ color: var(--text-primary, var(--face-text));
501
+ font-family: ui-monospace, monospace;
502
+ }
503
+
504
+ .section-header {
210
505
  display: flex;
211
- justify-content: space-between;
212
506
  align-items: center;
507
+ justify-content: space-between;
508
+ margin-bottom: 8px;
213
509
  }
214
510
 
215
- .hubs-panel {
216
- background: #080602;
217
- border: 1px solid rgba(217, 119, 6, 0.2);
218
- border-radius: 6px;
219
- padding: 8px 10px;
220
- margin-bottom: 12px;
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);
221
517
  }
222
518
 
223
- .hub-item {
519
+ .hub-list {
224
520
  display: flex;
225
- justify-content: space-between;
226
- align-items: center;
227
- padding: 4px 0;
228
- border-bottom: 1px solid rgba(217, 119, 6, 0.1);
229
- font-size: 11px;
521
+ flex-direction: column;
522
+ gap: 6px;
523
+ margin-bottom: 16px;
524
+ max-height: 180px;
525
+ overflow-y: auto;
230
526
  }
231
527
 
232
- .hub-item:last-child {
233
- border-bottom: none;
528
+ .hub-item {
529
+ display: flex;
530
+ align-items: center;
531
+ justify-content: space-between;
532
+ background: var(--face-surface);
533
+ border: 1px solid var(--border, var(--face-border));
534
+ border-radius: 6px;
535
+ padding: 8px 12px;
536
+ font-size: 13px;
234
537
  }
235
538
 
236
539
  .hub-title {
237
- color: #fde68a;
238
540
  font-weight: 600;
541
+ color: var(--text-primary, var(--face-text));
239
542
  }
240
543
 
241
544
  .hub-meta {
242
- color: #d97706;
243
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
244
- font-size: 10px;
545
+ font-family: ui-monospace, monospace;
546
+ font-size: 11px;
547
+ color: var(--face-text-muted);
245
548
  }
246
549
 
247
- .hub-more {
248
- font-size: 10px;
249
- color: #f59e0b;
250
- text-align: center;
251
- padding-top: 4px;
550
+ .empty-state {
551
+ display: flex;
552
+ 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;
560
+ }
561
+
562
+ .ingest-deck {
563
+ background: var(--face-surface);
564
+ border: 1px solid var(--border, var(--face-border));
565
+ 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;
252
583
  }
253
584
 
254
- .target-bar {
585
+ .field-group {
255
586
  display: flex;
256
- gap: 6px;
257
- align-items: center;
587
+ flex-direction: column;
588
+ gap: 4px;
589
+ }
590
+
591
+ .field-group.full-width {
592
+ grid-column: 1 / -1;
593
+ }
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);
601
+ }
602
+
603
+ .input-group {
604
+ display: flex;
605
+ gap: 8px;
258
606
  margin-bottom: 12px;
259
607
  }
260
608
 
261
- .path-input {
262
- flex: 1;
263
- background: #080602;
264
- border: 1px solid rgba(217, 119, 6, 0.3);
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));
265
613
  border-radius: 6px;
266
- color: #fef3c7;
267
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
268
- font-size: 11px;
269
- padding: 7px 10px;
614
+ padding: 8px 12px;
615
+ color: var(--text-primary, var(--face-text));
616
+ font-family: ui-monospace, monospace;
617
+ font-size: 13px;
618
+ width: 100%;
619
+ }
620
+
621
+ .path-input:focus, .text-input:focus, .select-input:focus {
270
622
  outline: none;
271
- box-sizing: border-box;
623
+ border-color: var(--face-accent);
272
624
  }
273
625
 
274
- .path-input:focus {
275
- border-color: #f59e0b;
276
- box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.3);
626
+ .presets-bar {
627
+ display: flex;
628
+ gap: 6px;
629
+ flex-wrap: wrap;
630
+ margin-bottom: 14px;
277
631
  }
278
632
 
279
- .preset-btn {
280
- background: rgba(217, 119, 6, 0.1);
281
- border: 1px solid rgba(217, 119, 6, 0.25);
282
- color: #fde68a;
283
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
284
- font-size: 10px;
285
- padding: 6px 8px;
286
- border-radius: 4px;
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);
287
642
  cursor: pointer;
643
+ transition: all 0.15s ease;
288
644
  }
289
645
 
290
- .preset-btn:hover {
291
- background: rgba(217, 119, 6, 0.2);
646
+ .preset-chip:hover {
647
+ border-color: var(--face-accent);
648
+ color: var(--face-accent);
292
649
  }
293
650
 
294
- .actions-grid {
295
- display: grid;
296
- grid-template-columns: repeat(4, 1fr);
297
- gap: 6px;
298
- margin-bottom: 12px;
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);
657
+ border-radius: 6px;
658
+ padding: 10px 14px;
659
+ margin-bottom: 14px;
660
+ font-size: 12px;
661
+ color: #a7f3d0;
299
662
  }
300
663
 
301
- .touch-btn {
664
+ .toggle-label {
665
+ display: inline-flex;
666
+ align-items: center;
667
+ gap: 8px;
302
668
  min-height: 44px;
303
- min-width: 44px;
669
+ font-size: 13px;
670
+ color: var(--text-primary, var(--face-text));
671
+ cursor: pointer;
672
+ user-select: none;
673
+ padding: 0 4px;
674
+ }
675
+
676
+ .button-group {
677
+ display: grid;
678
+ grid-template-columns: repeat(2, 1fr);
679
+ gap: 8px;
680
+ }
681
+
682
+ .btn {
304
683
  display: inline-flex;
305
684
  align-items: center;
306
685
  justify-content: center;
307
- background: rgba(217, 119, 6, 0.1);
308
- border: 1px solid rgba(217, 119, 6, 0.28);
309
- color: #fde68a;
310
- font-size: 11px;
686
+ min-height: 44px;
687
+ padding: 0 12px;
688
+ font-size: 12px;
311
689
  font-weight: 600;
312
- letter-spacing: 0.03em;
313
- text-transform: uppercase;
314
690
  border-radius: 6px;
691
+ border: 1px solid transparent;
315
692
  cursor: pointer;
316
- transition: background 0.15s ease, border-color 0.15s ease;
317
- padding: 6px;
693
+ transition: background-color 0.15s ease, border-color 0.15s ease;
318
694
  text-align: center;
319
695
  }
320
696
 
321
- .touch-btn:hover {
322
- background: rgba(217, 119, 6, 0.22);
323
- border-color: #f59e0b;
697
+ .btn-primary {
698
+ background: var(--face-accent);
699
+ color: var(--face-bg);
324
700
  }
325
701
 
326
- .touch-btn:active {
327
- transform: translateY(1px);
702
+ .btn-primary:hover:not(:disabled) {
703
+ background: var(--face-accent-hover);
328
704
  }
329
705
 
330
- .touch-btn.primary {
331
- background: #b45309;
332
- color: #ffffff;
333
- border-color: #f59e0b;
334
- font-weight: 700;
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));
335
710
  }
336
711
 
337
- .touch-btn.primary:hover {
338
- background: #d97706;
712
+ .btn-secondary:hover:not(:disabled) {
713
+ border-color: var(--face-accent);
339
714
  }
340
715
 
341
- .output-header {
342
- display: flex;
343
- justify-content: space-between;
344
- align-items: center;
345
- margin-bottom: 4px;
716
+ .btn:disabled {
717
+ opacity: 0.5;
718
+ cursor: not-allowed;
719
+ }
720
+
721
+ .result-deck {
722
+ background: var(--face-surface-subtle);
723
+ border: 1px solid var(--border, var(--face-border));
724
+ border-radius: 6px;
725
+ padding: 12px;
726
+ font-family: ui-monospace, monospace;
727
+ font-size: 12px;
728
+ color: var(--text-primary, var(--face-text));
729
+ margin-top: 12px;
730
+ }
731
+
732
+ .result-title {
733
+ font-weight: 600;
734
+ color: var(--face-accent);
735
+ margin-bottom: 6px;
736
+ }
737
+
738
+ .error-deck {
739
+ background: var(--face-surface);
740
+ border: 1px solid var(--face-danger);
741
+ border-radius: 6px;
742
+ padding: 12px;
743
+ color: var(--face-danger);
744
+ font-size: 12px;
745
+ margin-bottom: 12px;
346
746
  }
347
747
 
348
- .copy-btn {
349
- background: none;
350
- border: none;
351
- color: #f59e0b;
748
+ .repo-status-badge {
749
+ display: inline-flex;
750
+ align-items: center;
751
+ padding: 2px 6px;
752
+ border-radius: 4px;
352
753
  font-size: 10px;
353
- cursor: pointer;
354
- padding: 2px 4px;
355
- text-decoration: underline;
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;
356
762
  }
357
763
 
358
- .copy-btn:hover {
764
+ .repo-status-badge.due {
765
+ background: #78350f;
359
766
  color: #fde68a;
360
767
  }
361
768
 
362
- .output-box {
363
- background: #030201;
364
- border: 1px solid rgba(217, 119, 6, 0.22);
365
- border-radius: 6px;
366
- padding: 8px 10px;
367
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
368
- font-size: 11px;
369
- line-height: 1.45;
370
- color: #fbbf24;
371
- min-height: 52px;
372
- max-height: 90px;
373
- overflow-y: auto;
374
- white-space: pre-wrap;
375
- word-break: break-word;
769
+ .repo-status-badge.uninit {
770
+ background: #831843;
771
+ color: #fbcfe8;
772
+ }
773
+
774
+ @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;
780
+ }
376
781
  }
377
782
  </style>
378
783
 
379
- <div class="header">
380
- <div class="brand-meta">
381
- <svg class="brand-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
382
- <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path>
383
- <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path>
384
- <circle cx="12" cy="10" r="2"></circle>
385
- </svg>
386
- <div class="title-block">
387
- <h2 class="title">OpenWiki Engine</h2>
388
- <p class="subtitle">Multi-Repo Knowledge Graph &amp; Docs</p>
784
+ <div class="header-bar">
785
+ <div class="brand-cluster">
786
+ <span class="wordmark-badge">[BRAIN//DUMP]</span>
787
+ <span class="system-title">Knowledge Graph Engine</span>
788
+ </div>
789
+ <span class="status-pill ${this.statusText === 'SYNCHRONIZED' ? 'ok' : this.statusText === 'ERROR' ? 'danger' : ''}">
790
+ ${this.statusText}
791
+ </span>
792
+ </div>
793
+
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}
801
+ </button>
802
+ `).join('')}
803
+ </nav>
804
+
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>
809
+ </div>
810
+ <div class="stat-card">
811
+ <div class="stat-label">Documentation Lines</div>
812
+ <div class="stat-value">${linesText}</div>
813
+ </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}
389
818
  </div>
390
819
  </div>
391
- <div id="status-badge" class="badge ready">READY</div>
392
820
  </div>
393
821
 
394
- <div class="section-label">
395
- <span>Discovered Hubs</span>
396
- <span id="hubs-count" style="font-family:ui-monospace, monospace; color:#fde68a;">${this.hubs.length} Hubs</span>
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
+ ` : ''}
838
+ `;
839
+
840
+ this.bindEvents();
841
+ }
842
+
843
+ renderActiveScreen() {
844
+ if (this.activeScreen === 'config') {
845
+ return this.renderConfigScreen();
846
+ }
847
+ if (this.activeScreen === 'refresh') {
848
+ return this.renderRefreshScreen();
849
+ }
850
+ if (this.activeScreen === 'upstream') {
851
+ return this.renderUpstreamScreen();
852
+ }
853
+ return this.renderHubsScreen();
854
+ }
855
+
856
+ renderHubsScreen() {
857
+ 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>
397
863
  </div>
398
- <div class="hubs-panel">
399
- <div id="hubs-container">
400
- ${this.hubs.slice(0, 4).map((h) => `
401
- <div class="hub-item">
402
- <span class="hub-title">${h.title}</span>
403
- <span class="hub-meta">${h.lines} lines · ${Math.round(h.size_bytes / 1024 * 10) / 10}KB</span>
404
- </div>
405
- `).join('')}
406
- ${this.hubs.length > 4 ? `<div class="hub-more">+${this.hubs.length - 4} more indexed hubs</div>` : ''}
864
+
865
+ <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>
872
+ </div>
873
+ `).join('')}
874
+ </div>
875
+
876
+ <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>
407
903
  </div>
408
904
  </div>
905
+ `;
906
+ }
907
+
908
+ renderConfigScreen() {
909
+ return `
910
+ <div class="config-deck">
911
+ <div class="section-header">
912
+ <span class="section-title">Documentation Model & Provider Routing</span>
913
+ </div>
914
+
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>
919
+
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>
931
+ </div>
932
+
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>
942
+
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>
409
947
 
410
- <div class="section-label">Target Ingest Directory</div>
411
- <div class="target-bar">
412
- <input id="path-input" class="path-input" type="text" value="openwiki" placeholder="Directory to ingest..." />
413
- <button id="preset-openwiki" class="preset-btn" type="button">openwiki</button>
414
- <button id="preset-docs" class="preset-btn" type="button">docs</button>
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>
952
+
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>
957
+
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>
962
+ </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>
971
+ </div>
415
972
  </div>
973
+ `;
974
+ }
975
+
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;
416
979
 
417
- <div class="actions-grid">
418
- <button id="btn-status" class="touch-btn" type="button">Status</button>
419
- <button id="btn-hubs" class="touch-btn" type="button">Hubs</button>
420
- <button id="btn-probe" class="touch-btn" type="button">Probe</button>
421
- <button id="btn-ingest" class="touch-btn primary" type="button">Ingest</button>
980
+ 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>
987
+ </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>
991
+ </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('')}
1016
+ </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>
1022
+ </div>
422
1023
  </div>
1024
+ `;
1025
+ }
1026
+
1027
+ renderUpstreamScreen() {
1028
+ const currentVer = this.upstream?.recordedVersion || '0.7.2';
1029
+ const latestVer = this.upstream?.latestVersion || '0.7.3';
1030
+
1031
+ return `
1032
+ <div class="ingest-deck">
1033
+ <div class="section-header">
1034
+ <span class="section-title">Upstream Package Surveillance</span>
1035
+ </div>
1036
+
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>
1050
+ </div>
423
1051
 
424
- <div class="output-header">
425
- <span class="section-label" style="margin-bottom:0;">Engine Output</span>
426
- <button id="btn-copy" class="copy-btn" type="button">Copy output</button>
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>
1059
+ </div>
427
1060
  </div>
428
- <div id="card-out" class="output-box">Knowledge graph index operational. Ready for cross-repo documentation queries.</div>
429
1061
  `;
1062
+ }
430
1063
 
431
- const pathInput = this.shadowRoot.querySelector('#path-input');
432
- this.shadowRoot.querySelector('#preset-openwiki')?.addEventListener('click', () => {
433
- if (pathInput) pathInput.value = 'openwiki';
434
- });
435
- this.shadowRoot.querySelector('#preset-docs')?.addEventListener('click', () => {
436
- if (pathInput) pathInput.value = 'docs';
1064
+ bindEvents() {
1065
+ const shadow = this.shadowRoot;
1066
+ if (!shadow) return;
1067
+
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);
1072
+ });
437
1073
  });
438
1074
 
439
- this.shadowRoot.querySelector('#btn-status')?.addEventListener('click', () => this.execute('status'));
440
- this.shadowRoot.querySelector('#btn-hubs')?.addEventListener('click', () => this.execute('hubs'));
441
- this.shadowRoot.querySelector('#btn-probe')?.addEventListener('click', () => this.execute('probe'));
442
- this.shadowRoot.querySelector('#btn-ingest')?.addEventListener('click', () => this.execute('ingest'));
1075
+ const pathInput = shadow.querySelector('#target-path-input');
1076
+ if (pathInput) {
1077
+ pathInput.addEventListener('input', (e) => {
1078
+ this.targetPath = e.target.value.trim();
1079
+ });
1080
+ }
1081
+
1082
+ const dryRunCheckbox = shadow.querySelector('#dry-run-checkbox');
1083
+ if (dryRunCheckbox) {
1084
+ dryRunCheckbox.addEventListener('change', (e) => {
1085
+ this.dryRun = e.target.checked;
1086
+ });
1087
+ }
443
1088
 
444
- this.shadowRoot.querySelector('#btn-copy')?.addEventListener('click', () => {
445
- const text = this.shadowRoot.querySelector('#card-out')?.textContent || '';
446
- if (navigator.clipboard) {
447
- navigator.clipboard.writeText(text);
448
- }
1089
+ const btnIngest = shadow.querySelector('#btn-ingest');
1090
+ if (btnIngest) {
1091
+ btnIngest.addEventListener('click', () => this.executeIngest());
1092
+ }
1093
+
1094
+ const btnBootstrap = shadow.querySelector('#btn-bootstrap');
1095
+ if (btnBootstrap) {
1096
+ btnBootstrap.addEventListener('click', () => this.executeBootstrap());
1097
+ }
1098
+
1099
+ const btnRefresh = shadow.querySelector('#btn-refresh');
1100
+ if (btnRefresh) {
1101
+ btnRefresh.addEventListener('click', () => this.refreshHubs());
1102
+ }
1103
+
1104
+ const btnSyncRefresh = shadow.querySelector('#btn-sync-refresh');
1105
+ if (btnSyncRefresh) {
1106
+ btnSyncRefresh.addEventListener('click', () => this.loadRefreshReport());
1107
+ }
1108
+
1109
+ const btnFleetApply = shadow.querySelector('#btn-fleet-refresh-apply');
1110
+ if (btnFleetApply) {
1111
+ btnFleetApply.addEventListener('click', () => this.executeFleetRefresh());
1112
+ }
1113
+
1114
+ const btnWatch = shadow.querySelector('#btn-watch');
1115
+ if (btnWatch) {
1116
+ btnWatch.addEventListener('click', () => this.executeWatch());
1117
+ }
1118
+
1119
+ const btnPropagate = shadow.querySelector('#btn-propagate');
1120
+ if (btnPropagate) {
1121
+ btnPropagate.addEventListener('click', () => this.executePropagate());
1122
+ }
1123
+
1124
+ shadow.querySelectorAll('.preset-chip').forEach((chip) => {
1125
+ 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);
1132
+ });
449
1133
  });
1134
+
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);
1149
+ });
1150
+ }
1151
+
1152
+ const btnResetConfig = shadow.querySelector('#btn-reset-config');
1153
+ if (btnResetConfig) {
1154
+ btnResetConfig.addEventListener('click', () => this.resetConfig());
1155
+ }
450
1156
  }
451
1157
  }
452
1158