total-recall-brain 3.41.2 → 3.41.3

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 (69) hide show
  1. package/bin/total-recall.mjs +9 -3
  2. package/frontend/dist/assets/{index-BG9NkVUy.css → index-BIQmqDKP.css} +1 -1
  3. package/frontend/dist/assets/{index-BtuiZqwP.js → index-DFenQQEQ.js} +74 -79
  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 +2 -2
  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 +4 -4
  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/ui/DESIGN.md +56 -8
  18. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +1068 -204
  19. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/panel.js +4 -2
  20. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +32 -0
  21. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +436 -274
  22. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +4 -4
  23. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
  24. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +16 -2
  25. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +461 -242
  26. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +4 -4
  27. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  28. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -7
  29. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +17 -2
  30. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +426 -300
  31. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +4 -4
  32. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +32 -0
  33. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +350 -283
  34. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +4 -4
  35. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +31 -19
  36. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +4 -4
  37. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +4 -4
  38. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +16 -2
  39. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +380 -298
  40. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -1
  41. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +6 -6
  42. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
  43. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  44. package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +17 -3
  45. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +38 -15
  46. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +60 -34
  47. package/src/cli/command.mjs +23 -18
  48. package/src/cli/daemon.mjs +23 -1
  49. package/src/cli/plugin/copies.mjs +8 -5
  50. package/src/cli/plugin/index.mjs +25 -2
  51. package/src/cli/rebuild.mjs +22 -0
  52. package/src/cli/reset-password.mjs +3 -2
  53. package/src/cli/skill.mjs +8 -2
  54. package/src/cli/status.mjs +25 -7
  55. package/src/core/automatic-brain-updates.mjs +9 -3
  56. package/src/core/brain-updates.mjs +63 -5
  57. package/src/core/global-brain-updates.mjs +21 -1
  58. package/src/core/mesh-brain-updates.mjs +5 -2
  59. package/src/core/meta-harness.mjs +16 -41
  60. package/src/core/plugin-runner.mjs +6 -1
  61. package/src/core/plugin-store.mjs +67 -1
  62. package/src/core/plugin-updates.mjs +1 -1
  63. package/src/core/portable-skills-maintenance.mjs +19 -0
  64. package/src/core/required-plugins.mjs +7 -0
  65. package/src/core/skills-registry.mjs +33 -3
  66. package/src/server/auth.mjs +41 -1
  67. package/src/server/memory-app.mjs +2 -1
  68. package/src/server/routes/auth.mjs +25 -1
  69. package/src/server/routes/plugins.mjs +17 -4
@@ -1,23 +1,35 @@
1
1
  /**
2
- * OpenWiki — Multi-Repo Knowledge Graph & Documentation Wiki
3
- * Bespoke Web Component Card Face: Knowledge Amber & Ink Theme.
2
+ * Brain Dump — Multi-Repo Knowledge Graph & Documentation Console
4
3
  *
5
- * Real hub telemetry, documentation ingest controls, zero fake animations or canvases.
4
+ * Bespoke operator interface for cross-repository documentation and knowledge graphs.
5
+ * Displays real-time hub trees, file metrics, structured ingestion controllers,
6
+ * and bootstrap verification without template wrappers or terminal dumps.
6
7
  */
7
8
 
9
+ const DASH = '\u2014';
10
+
11
+ function escapeHtml(str) {
12
+ if (str === null || str === undefined) return '';
13
+ return String(str).replace(/[&<>"']/g, c => ({
14
+ '&': '&amp;',
15
+ '<': '&lt;',
16
+ '>': '&gt;',
17
+ '"': '&quot;',
18
+ "'": '&#39;'
19
+ }[c]));
20
+ }
21
+
8
22
  export class OpenWikiCard extends HTMLElement {
9
23
  constructor() {
10
24
  super();
11
- this.status = 'READY';
25
+ this.hubs = [];
26
+ this.totalLines = 0;
12
27
  this.executing = false;
13
28
  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
- ];
29
+ this.dryRun = true;
30
+ this.lastResult = null;
31
+ this.screens = [{ id: 'knowledge', label: 'Knowledge Hubs' }];
32
+ this.screen = 'knowledge';
21
33
  }
22
34
 
23
35
  connectedCallback() {
@@ -25,428 +37,498 @@ export class OpenWikiCard extends HTMLElement {
25
37
  this.attachShadow({ mode: 'open' });
26
38
  }
27
39
  this.render();
28
- this.loadHubs();
40
+ this.refreshHubs();
29
41
  }
30
42
 
31
- async loadHubs() {
43
+ async refreshHubs() {
44
+ if (!this.host) return;
32
45
  try {
33
- if (this.host?.run) {
34
- const res = await this.host.run('hubs', ['--json']);
35
- if (res && Array.isArray(res.hubs)) {
36
- this.hubs = res.hubs;
37
- this.updateHubsUI();
38
- }
46
+ const res = await this.host.run('status', ['--json']);
47
+ let parsed = [];
48
+ if (res && typeof res === 'object') {
49
+ if (Array.isArray(res.hubs)) {
50
+ parsed = res.hubs;
51
+ this.totalLines = res.total_lines || 0;
52
+ } else if (Array.isArray(res)) {
53
+ parsed = res;
54
+ }
55
+ } else if (typeof res === 'string' && res.trim().startsWith('{')) {
56
+ const json = JSON.parse(res);
57
+ parsed = Array.isArray(json.hubs) ? json.hubs : [];
58
+ this.totalLines = json.total_lines || 0;
39
59
  }
60
+
61
+ this.hubs = parsed;
62
+ this.render();
40
63
  } catch {
41
- // Retain fallback hubs in disconnected state
64
+ this.hubs = [];
65
+ this.render();
42
66
  }
43
67
  }
44
68
 
45
- updateHubsUI() {
46
- const countEl = this.shadowRoot.querySelector('#hubs-count');
47
- if (countEl) countEl.textContent = `${this.hubs.length} Hubs`;
48
-
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>` : '');
57
- }
58
-
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;
69
+ async runIngest() {
70
+ if (!this.host || this.executing) return;
71
+ this.executing = true;
72
+ this.lastResult = null;
73
+ this.render();
64
74
 
65
- let args = [...customArgs];
66
- if (action === 'ingest' && args.length === 0) {
67
- args = [target || 'openwiki'];
75
+ try {
76
+ const args = [this.targetPath || 'openwiki'];
77
+ if (this.dryRun) args.push('--dry-run');
78
+ args.push('--json');
79
+
80
+ const res = await this.host.run('ingest', args);
81
+ this.lastResult = {
82
+ ok: true,
83
+ message: this.dryRun ? 'Dry-run preview completed' : 'Ingestion completed successfully',
84
+ data: res
85
+ };
86
+ await this.refreshHubs();
87
+ } catch (err) {
88
+ this.lastResult = {
89
+ ok: false,
90
+ message: err instanceof Error ? err.message : 'Ingestion failed'
91
+ };
92
+ } finally {
93
+ this.executing = false;
94
+ this.render();
68
95
  }
96
+ }
69
97
 
70
- if (out) out.textContent = `[OPENWIKI-CLI] Executing openwiki ${action} ${args.join(' ')}...`;
71
- if (badge) {
72
- badge.textContent = 'BUSY';
73
- badge.className = 'badge busy';
74
- }
98
+ async runBootstrap() {
99
+ if (!this.host || this.executing) return;
75
100
  this.executing = true;
101
+ this.lastResult = null;
102
+ this.render();
76
103
 
77
104
  try {
78
- let resultText = '';
79
- 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.');
85
- }
86
- } 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
- }
96
- }
97
-
98
- if (out) out.textContent = resultText.trim();
99
- if (badge) {
100
- badge.textContent = 'READY';
101
- badge.className = 'badge ready';
102
- }
105
+ const res = await this.host.run('bootstrap', ['--json']);
106
+ this.lastResult = {
107
+ ok: true,
108
+ message: 'OpenWiki skeleton bootstrapped',
109
+ data: res
110
+ };
111
+ await this.refreshHubs();
103
112
  } 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
- }
113
+ this.lastResult = {
114
+ ok: false,
115
+ message: err instanceof Error ? err.message : 'Bootstrap failed'
116
+ };
109
117
  } finally {
110
118
  this.executing = false;
119
+ this.render();
111
120
  }
112
121
  }
113
122
 
114
123
  render() {
115
- this.shadowRoot.innerHTML = `
124
+ const root = this.shadowRoot;
125
+ if (!root) return;
126
+
127
+ root.innerHTML = `
116
128
  <style>
117
129
  :host {
130
+ --face-bg: #0f172a;
131
+ --face-surface: #1e293b;
132
+ --face-line: #334155;
133
+ --face-text: #f8fafc;
134
+ --face-muted: #94a3b8;
135
+ --face-brand: #3b82f6;
136
+ --face-brand-hover: #60a5fa;
137
+ --face-accent: #facc15;
138
+ --face-ok: #10b981;
139
+ --face-warn: #f59e0b;
140
+ --face-danger: #ef4444;
141
+ --face-dark: #0f172a;
142
+
118
143
  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;
144
+ font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
145
+ color: var(--text-primary, var(--face-text));
146
+ background: var(--bg-secondary, var(--face-bg));
147
+ border: 1px solid var(--border, var(--face-line));
148
+ border-radius: 8px;
149
+ padding: 16px;
126
150
  box-sizing: border-box;
127
151
  overflow: hidden;
128
152
  }
129
153
 
154
+ @media (prefers-reduced-motion: reduce) {
155
+ *, ::before, ::after {
156
+ animation-duration: 0.01ms !important;
157
+ animation-iteration-count: 1 !important;
158
+ transition-duration: 0.01ms !important;
159
+ }
160
+ }
161
+
162
+ /* Header Bar */
130
163
  .header {
131
164
  display: flex;
165
+ align-items: center;
132
166
  justify-content: space-between;
133
- align-items: flex-start;
134
- margin-bottom: 14px;
167
+ flex-wrap: wrap;
168
+ gap: 12px;
169
+ padding-bottom: 14px;
170
+ border-bottom: 1px solid var(--border, var(--face-line));
171
+ margin-bottom: 16px;
135
172
  }
136
173
 
137
- .brand-meta {
174
+ .brand-group {
138
175
  display: flex;
139
176
  align-items: center;
140
177
  gap: 10px;
141
178
  }
142
179
 
143
- .brand-icon {
144
- width: 32px;
145
- height: 32px;
146
- flex-shrink: 0;
147
- color: #f59e0b;
180
+ .wordmark-badge {
181
+ font-family: var(--font-mono, 'JetBrains Mono', monospace);
182
+ font-size: 13px;
183
+ font-weight: 800;
184
+ letter-spacing: 0.05em;
185
+ color: var(--card-accent, var(--face-accent));
186
+ background: rgba(250, 204, 21, 0.12);
187
+ padding: 6px 10px;
188
+ border-radius: 6px;
189
+ border: 1px solid rgba(250, 204, 21, 0.3);
148
190
  }
149
191
 
150
- .title-block {
192
+ .title-meta {
151
193
  display: flex;
152
194
  flex-direction: column;
153
- gap: 2px;
154
195
  }
155
196
 
156
- .title {
157
- font-size: 15px;
197
+ .plugin-title {
198
+ font-size: 14px;
158
199
  font-weight: 700;
159
- letter-spacing: 0.03em;
160
- text-transform: uppercase;
161
- color: #fde68a;
200
+ color: var(--text-primary, var(--face-text));
162
201
  margin: 0;
163
202
  }
164
203
 
165
- .subtitle {
204
+ .plugin-subtitle {
166
205
  font-size: 11px;
167
- color: #d97706;
206
+ color: var(--text-secondary, var(--face-muted));
168
207
  margin: 0;
169
208
  }
170
209
 
171
- .badge {
172
- display: inline-flex;
210
+ .hub-count-chip {
211
+ display: flex;
173
212
  align-items: center;
174
- font-size: 10px;
213
+ gap: 6px;
214
+ font-family: var(--font-mono, 'JetBrains Mono', monospace);
215
+ font-size: 11px;
175
216
  font-weight: 700;
176
- letter-spacing: 0.08em;
217
+ color: var(--face-accent);
218
+ background: rgba(250, 204, 21, 0.12);
219
+ border: 1px solid rgba(250, 204, 21, 0.3);
220
+ padding: 5px 12px;
221
+ border-radius: 6px;
177
222
  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);
183
223
  }
184
224
 
185
- .badge.ready {
186
- background: rgba(16, 185, 129, 0.15);
187
- color: #34d399;
188
- border-color: rgba(16, 185, 129, 0.35);
225
+ /* Split Layout */
226
+ .workspace-split {
227
+ display: grid;
228
+ grid-template-columns: minmax(260px, 1.4fr) minmax(240px, 1fr);
229
+ gap: 14px;
230
+ margin-bottom: 16px;
189
231
  }
190
232
 
191
- .badge.busy {
192
- background: rgba(56, 189, 248, 0.15);
193
- color: #38bdf8;
194
- border-color: rgba(56, 189, 248, 0.35);
233
+ @media (max-width: 700px) {
234
+ .workspace-split {
235
+ grid-template-columns: 1fr;
236
+ }
195
237
  }
196
238
 
197
- .badge.error {
198
- background: rgba(239, 68, 68, 0.2);
199
- color: #f87171;
200
- border-color: rgba(239, 68, 68, 0.4);
239
+ /* Hub Tree Matrix */
240
+ .hub-panel {
241
+ border: 1px solid var(--border, var(--face-line));
242
+ border-radius: 6px;
243
+ background: rgba(30, 41, 59, 0.4);
244
+ padding: 12px;
245
+ display: flex;
246
+ flex-direction: column;
247
+ gap: 10px;
201
248
  }
202
249
 
203
- .section-label {
204
- font-size: 10px;
205
- text-transform: uppercase;
206
- letter-spacing: 0.06em;
207
- color: #f59e0b;
250
+ .panel-title {
251
+ font-size: 11px;
208
252
  font-weight: 700;
209
- margin-bottom: 6px;
210
- display: flex;
211
- justify-content: space-between;
212
- align-items: center;
253
+ text-transform: uppercase;
254
+ letter-spacing: 0.05em;
255
+ color: var(--text-secondary, var(--face-muted));
256
+ margin: 0;
213
257
  }
214
258
 
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;
259
+ .hub-list {
260
+ display: flex;
261
+ flex-direction: column;
262
+ gap: 6px;
263
+ max-height: 280px;
264
+ overflow-y: auto;
221
265
  }
222
266
 
223
267
  .hub-item {
268
+ padding: 10px 12px;
269
+ background: rgba(15, 23, 42, 0.6);
270
+ border: 1px solid var(--border, var(--face-line));
271
+ border-radius: 5px;
224
272
  display: flex;
225
- justify-content: space-between;
226
273
  align-items: center;
227
- padding: 4px 0;
228
- border-bottom: 1px solid rgba(217, 119, 6, 0.1);
229
- font-size: 11px;
274
+ justify-content: space-between;
275
+ gap: 10px;
230
276
  }
231
277
 
232
- .hub-item:last-child {
233
- border-bottom: none;
278
+ .hub-main {
279
+ display: flex;
280
+ flex-direction: column;
281
+ gap: 2px;
234
282
  }
235
283
 
236
284
  .hub-title {
237
- color: #fde68a;
238
- font-weight: 600;
285
+ font-family: var(--font-mono, 'JetBrains Mono', monospace);
286
+ font-size: 12px;
287
+ font-weight: 700;
288
+ color: var(--text-primary, var(--face-text));
239
289
  }
240
290
 
241
- .hub-meta {
242
- color: #d97706;
243
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
291
+ .hub-path {
244
292
  font-size: 10px;
293
+ color: var(--text-secondary, var(--face-muted));
245
294
  }
246
295
 
247
- .hub-more {
296
+ .hub-metrics {
297
+ display: flex;
298
+ align-items: center;
299
+ gap: 6px;
300
+ font-family: var(--font-mono, 'JetBrains Mono', monospace);
248
301
  font-size: 10px;
249
- color: #f59e0b;
250
- text-align: center;
251
- padding-top: 4px;
302
+ color: var(--face-accent);
252
303
  }
253
304
 
254
- .target-bar {
305
+ /* Ingest Deck Column */
306
+ .ingest-deck {
255
307
  display: flex;
256
- gap: 6px;
257
- align-items: center;
258
- margin-bottom: 12px;
308
+ flex-direction: column;
309
+ gap: 10px;
259
310
  }
260
311
 
261
- .path-input {
262
- flex: 1;
263
- background: #080602;
264
- border: 1px solid rgba(217, 119, 6, 0.3);
312
+ .form-card {
313
+ border: 1px solid var(--border, var(--face-line));
265
314
  border-radius: 6px;
266
- color: #fef3c7;
267
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
268
- font-size: 11px;
269
- padding: 7px 10px;
270
- outline: none;
271
- box-sizing: border-box;
315
+ background: rgba(30, 41, 59, 0.4);
316
+ padding: 12px;
317
+ display: flex;
318
+ flex-direction: column;
319
+ gap: 10px;
272
320
  }
273
321
 
274
- .path-input:focus {
275
- border-color: #f59e0b;
276
- box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.3);
322
+ .input-row {
323
+ display: flex;
324
+ flex-direction: column;
325
+ gap: 4px;
277
326
  }
278
327
 
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;
328
+ .input-label {
284
329
  font-size: 10px;
285
- padding: 6px 8px;
286
- border-radius: 4px;
287
- cursor: pointer;
330
+ text-transform: uppercase;
331
+ color: var(--text-secondary, var(--face-muted));
332
+ letter-spacing: 0.05em;
288
333
  }
289
334
 
290
- .preset-btn:hover {
291
- background: rgba(217, 119, 6, 0.2);
335
+ .text-input {
336
+ min-height: 44px;
337
+ padding: 8px 12px;
338
+ font-family: var(--font-mono, 'JetBrains Mono', monospace);
339
+ font-size: 12px;
340
+ color: var(--text-primary, var(--face-text));
341
+ background: rgba(15, 23, 42, 0.8);
342
+ border: 1px solid var(--border, var(--face-line));
343
+ border-radius: 5px;
344
+ box-sizing: border-box;
345
+ width: 100%;
292
346
  }
293
347
 
294
- .actions-grid {
295
- display: grid;
296
- grid-template-columns: repeat(4, 1fr);
297
- gap: 6px;
298
- margin-bottom: 12px;
348
+ .text-input:focus {
349
+ outline: none;
350
+ border-color: var(--card-accent, var(--face-brand));
299
351
  }
300
352
 
301
- .touch-btn {
302
- min-height: 44px;
303
- min-width: 44px;
304
- display: inline-flex;
353
+ .toggle-row {
354
+ display: flex;
305
355
  align-items: center;
306
- justify-content: center;
307
- background: rgba(217, 119, 6, 0.1);
308
- border: 1px solid rgba(217, 119, 6, 0.28);
309
- color: #fde68a;
356
+ gap: 8px;
310
357
  font-size: 11px;
311
- font-weight: 600;
312
- letter-spacing: 0.03em;
313
- text-transform: uppercase;
314
- border-radius: 6px;
358
+ color: var(--text-secondary, var(--face-muted));
315
359
  cursor: pointer;
316
- transition: background 0.15s ease, border-color 0.15s ease;
317
- padding: 6px;
318
- text-align: center;
319
- }
320
-
321
- .touch-btn:hover {
322
- background: rgba(217, 119, 6, 0.22);
323
- border-color: #f59e0b;
324
360
  }
325
361
 
326
- .touch-btn:active {
327
- transform: translateY(1px);
362
+ .toggle-check {
363
+ min-height: 20px;
364
+ min-width: 20px;
365
+ cursor: pointer;
328
366
  }
329
367
 
330
- .touch-btn.primary {
331
- background: #b45309;
332
- color: #ffffff;
333
- border-color: #f59e0b;
368
+ .btn-action-primary {
369
+ min-height: 44px;
370
+ padding: 8px 16px;
371
+ font-family: var(--font-sans, sans-serif);
372
+ font-size: 12px;
334
373
  font-weight: 700;
374
+ color: var(--face-dark);
375
+ background: var(--face-accent);
376
+ border: none;
377
+ border-radius: 5px;
378
+ cursor: pointer;
379
+ transition: background 0.15s ease;
335
380
  }
336
381
 
337
- .touch-btn.primary:hover {
338
- background: #d97706;
382
+ .btn-action-primary:hover {
383
+ background: #fde047;
339
384
  }
340
385
 
341
- .output-header {
342
- display: flex;
343
- justify-content: space-between;
344
- align-items: center;
345
- margin-bottom: 4px;
386
+ .btn-action-primary:disabled {
387
+ opacity: 0.6;
388
+ cursor: not-allowed;
346
389
  }
347
390
 
348
- .copy-btn {
349
- background: none;
350
- border: none;
351
- color: #f59e0b;
352
- font-size: 10px;
391
+ .btn-action-secondary {
392
+ min-height: 44px;
393
+ padding: 8px 16px;
394
+ font-family: var(--font-sans, sans-serif);
395
+ font-size: 12px;
396
+ font-weight: 700;
397
+ color: var(--text-primary, var(--face-text));
398
+ background: transparent;
399
+ border: 1px solid var(--border, var(--face-line));
400
+ border-radius: 5px;
353
401
  cursor: pointer;
354
- padding: 2px 4px;
355
- text-decoration: underline;
402
+ transition: background 0.15s ease;
356
403
  }
357
404
 
358
- .copy-btn:hover {
359
- color: #fde68a;
405
+ .btn-action-secondary:hover {
406
+ background: rgba(255, 255, 255, 0.05);
360
407
  }
361
408
 
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;
409
+ .feedback-banner {
410
+ padding: 8px 12px;
411
+ border-radius: 5px;
412
+ font-family: var(--font-mono, 'JetBrains Mono', monospace);
368
413
  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;
414
+ }
415
+
416
+ .feedback-ok {
417
+ background: rgba(16, 185, 129, 0.15);
418
+ border: 1px solid rgba(16, 185, 129, 0.4);
419
+ color: var(--face-ok);
420
+ }
421
+
422
+ .feedback-fail {
423
+ background: rgba(239, 68, 68, 0.15);
424
+ border: 1px solid rgba(239, 68, 68, 0.4);
425
+ color: var(--face-danger);
426
+ }
427
+
428
+ .empty-hubs {
429
+ padding: 32px 16px;
430
+ text-align: center;
431
+ font-size: 12px;
432
+ color: var(--text-secondary, var(--face-muted));
433
+ }
434
+
435
+ /* Footer Telemetry */
436
+ .footer-bar {
437
+ display: flex;
438
+ justify-content: space-between;
439
+ align-items: center;
440
+ padding-top: 12px;
441
+ margin-top: 14px;
442
+ border-top: 1px solid var(--border, var(--face-line));
443
+ font-size: 10px;
444
+ color: var(--text-secondary, var(--face-muted));
376
445
  }
377
446
  </style>
378
447
 
379
448
  <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>
449
+ <div class="brand-group">
450
+ <div class="wordmark-badge">[BRAIN//DUMP]</div>
451
+ <div class="title-meta">
452
+ <h3 class="plugin-title">OpenWiki Knowledge Graph</h3>
453
+ <p class="plugin-subtitle">Multi-repository documentation and knowledge ingestion engine</p>
389
454
  </div>
390
455
  </div>
391
- <div id="status-badge" class="badge ready">READY</div>
456
+ <div class="hub-count-chip">
457
+ ${this.hubs.length > 0 ? `${this.hubs.length} Hubs Indexed` : 'No Hubs'}
458
+ </div>
392
459
  </div>
393
460
 
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>
397
- </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>` : ''}
461
+ <div class="workspace-split">
462
+ <div class="hub-panel">
463
+ <h4 class="panel-title">Verified Documentation Hubs</h4>
464
+ <div class="hub-list">
465
+ ${this.hubs.length > 0 ? this.hubs.map(h => `
466
+ <div class="hub-item">
467
+ <div class="hub-main">
468
+ <span class="hub-title">${escapeHtml(h.title || h.path)}</span>
469
+ <span class="hub-path">${escapeHtml(h.path)}</span>
470
+ </div>
471
+ <div class="hub-metrics">
472
+ <span>${h.lines !== undefined ? `${h.lines} lines` : DASH}</span>
473
+ </div>
474
+ </div>
475
+ `).join('') : `
476
+ <div class="empty-hubs">No markdown documentation hubs indexed yet. Click Bootstrap below to initialize.</div>
477
+ `}
478
+ </div>
407
479
  </div>
408
- </div>
409
480
 
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>
415
- </div>
481
+ <div class="ingest-deck">
482
+ <div class="form-card">
483
+ <h4 class="panel-title">Knowledge Ingest</h4>
484
+ <div class="input-row">
485
+ <span class="input-label">Target Hub Path</span>
486
+ <input class="text-input" type="text" id="input-path" placeholder="openwiki" />
487
+ </div>
488
+ <label class="toggle-row">
489
+ <input class="toggle-check" type="checkbox" id="check-dryrun" ${this.dryRun ? 'checked' : ''} />
490
+ <span>Dry Run (Preview changes without writing)</span>
491
+ </label>
492
+ <button class="btn-action-primary" type="button" id="btn-ingest" ${this.executing ? 'disabled' : ''}>
493
+ ${this.executing ? 'Processing...' : 'Run Ingestion'}
494
+ </button>
495
+ <button class="btn-action-secondary" type="button" id="btn-bootstrap" ${this.executing ? 'disabled' : ''}>
496
+ Bootstrap OpenWiki Skeleton
497
+ </button>
498
+ </div>
416
499
 
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>
500
+ ${this.lastResult ? `
501
+ <div class="feedback-banner ${this.lastResult.ok ? 'feedback-ok' : 'feedback-fail'}">
502
+ ${escapeHtml(this.lastResult.message)}
503
+ </div>
504
+ ` : ''}
505
+ </div>
422
506
  </div>
423
507
 
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>
508
+ <div class="footer-bar">
509
+ <span>Engine: OpenWiki v0.1.2</span>
510
+ <span>Storage: Markdown + SSSS Projections</span>
427
511
  </div>
428
- <div id="card-out" class="output-box">Knowledge graph index operational. Ready for cross-repo documentation queries.</div>
429
512
  `;
430
513
 
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';
437
- });
438
-
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'));
443
-
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
- }
449
- });
514
+ // Event listeners
515
+ const pathInput = root.querySelector('#input-path');
516
+ if (pathInput) {
517
+ pathInput.value = this.targetPath;
518
+ pathInput.addEventListener('input', e => {
519
+ this.targetPath = e.target.value;
520
+ });
521
+ }
522
+
523
+ const dryRunCheck = root.querySelector('#check-dryrun');
524
+ if (dryRunCheck) {
525
+ dryRunCheck.addEventListener('change', e => {
526
+ this.dryRun = e.target.checked;
527
+ });
528
+ }
529
+
530
+ root.querySelector('#btn-ingest')?.addEventListener('click', () => this.runIngest());
531
+ root.querySelector('#btn-bootstrap')?.addEventListener('click', () => this.runBootstrap());
450
532
  }
451
533
  }
452
534