dev-mcp-server 0.0.2 → 1.0.0

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 (58) hide show
  1. package/.env.example +23 -55
  2. package/README.md +609 -219
  3. package/cli.js +486 -160
  4. package/package.json +2 -2
  5. package/src/agents/BaseAgent.js +113 -0
  6. package/src/agents/dreamer.js +165 -0
  7. package/src/agents/improver.js +175 -0
  8. package/src/agents/specialists.js +202 -0
  9. package/src/agents/taskDecomposer.js +176 -0
  10. package/src/agents/teamCoordinator.js +153 -0
  11. package/src/api/routes/agents.js +172 -0
  12. package/src/api/routes/extras.js +115 -0
  13. package/src/api/routes/git.js +72 -0
  14. package/src/api/routes/ingest.js +60 -40
  15. package/src/api/routes/knowledge.js +59 -41
  16. package/src/api/routes/memory.js +41 -0
  17. package/src/api/routes/newRoutes.js +168 -0
  18. package/src/api/routes/pipelines.js +41 -0
  19. package/src/api/routes/planner.js +54 -0
  20. package/src/api/routes/query.js +24 -0
  21. package/src/api/routes/sessions.js +54 -0
  22. package/src/api/routes/tasks.js +67 -0
  23. package/src/api/routes/tools.js +85 -0
  24. package/src/api/routes/v5routes.js +196 -0
  25. package/src/api/server.js +133 -5
  26. package/src/context/compactor.js +151 -0
  27. package/src/context/contextEngineer.js +181 -0
  28. package/src/context/contextVisualizer.js +140 -0
  29. package/src/core/conversationEngine.js +231 -0
  30. package/src/core/indexer.js +169 -143
  31. package/src/core/ingester.js +141 -126
  32. package/src/core/queryEngine.js +286 -236
  33. package/src/cron/cronScheduler.js +260 -0
  34. package/src/dashboard/index.html +1181 -0
  35. package/src/lsp/symbolNavigator.js +220 -0
  36. package/src/memory/memoryManager.js +186 -0
  37. package/src/memory/teamMemory.js +111 -0
  38. package/src/messaging/messageBus.js +177 -0
  39. package/src/monitor/proactiveMonitor.js +337 -0
  40. package/src/pipelines/pipelineEngine.js +230 -0
  41. package/src/planner/plannerEngine.js +202 -0
  42. package/src/plugins/builtin/stats-plugin.js +29 -0
  43. package/src/plugins/pluginManager.js +144 -0
  44. package/src/prompts/promptEngineer.js +289 -0
  45. package/src/sessions/sessionManager.js +166 -0
  46. package/src/skills/skillsManager.js +263 -0
  47. package/src/storage/store.js +127 -105
  48. package/src/tasks/taskManager.js +151 -0
  49. package/src/tools/BashTool.js +154 -0
  50. package/src/tools/FileEditTool.js +280 -0
  51. package/src/tools/GitTool.js +212 -0
  52. package/src/tools/GrepTool.js +199 -0
  53. package/src/tools/registry.js +1380 -0
  54. package/src/utils/costTracker.js +69 -0
  55. package/src/utils/fileParser.js +176 -153
  56. package/src/utils/llmClient.js +355 -206
  57. package/src/watcher/fileWatcher.js +137 -0
  58. package/src/worktrees/worktreeManager.js +176 -0
@@ -0,0 +1,1181 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Dev MCP Server — Dashboard</title>
8
+ <style>
9
+ :root {
10
+ --bg: #0d1117;
11
+ --surface: #161b22;
12
+ --border: #30363d;
13
+ --text: #e6edf3;
14
+ --muted: #8b949e;
15
+ --accent: #58a6ff;
16
+ --green: #3fb950;
17
+ --yellow: #d29922;
18
+ --red: #f85149;
19
+ --purple: #bc8cff;
20
+ --cyan: #39d353;
21
+ }
22
+
23
+ * {
24
+ box-sizing: border-box;
25
+ margin: 0;
26
+ padding: 0;
27
+ }
28
+
29
+ body {
30
+ background: var(--bg);
31
+ color: var(--text);
32
+ font-family: -apple-system, 'Segoe UI', monospace;
33
+ font-size: 14px;
34
+ }
35
+
36
+ header {
37
+ background: var(--surface);
38
+ border-bottom: 1px solid var(--border);
39
+ padding: 14px 24px;
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 16px;
43
+ }
44
+
45
+ header h1 {
46
+ font-size: 16px;
47
+ font-weight: 600;
48
+ color: var(--accent);
49
+ }
50
+
51
+ header .version {
52
+ background: #1f2937;
53
+ color: var(--muted);
54
+ padding: 2px 8px;
55
+ border-radius: 12px;
56
+ font-size: 11px;
57
+ }
58
+
59
+ .status-dot {
60
+ width: 8px;
61
+ height: 8px;
62
+ border-radius: 50%;
63
+ background: var(--green);
64
+ box-shadow: 0 0 6px var(--green);
65
+ margin-left: auto;
66
+ }
67
+
68
+ .layout {
69
+ display: grid;
70
+ grid-template-columns: 220px 1fr;
71
+ min-height: calc(100vh - 50px);
72
+ }
73
+
74
+ nav {
75
+ background: var(--surface);
76
+ border-right: 1px solid var(--border);
77
+ padding: 16px 0;
78
+ }
79
+
80
+ nav button {
81
+ display: block;
82
+ width: 100%;
83
+ text-align: left;
84
+ padding: 9px 20px;
85
+ background: none;
86
+ border: none;
87
+ color: var(--muted);
88
+ cursor: pointer;
89
+ font-size: 13px;
90
+ transition: all 0.15s;
91
+ }
92
+
93
+ nav button:hover {
94
+ background: #1f2937;
95
+ color: var(--text);
96
+ }
97
+
98
+ nav button.active {
99
+ background: rgba(88, 166, 255, 0.1);
100
+ color: var(--accent);
101
+ border-left: 2px solid var(--accent);
102
+ }
103
+
104
+ nav .section-label {
105
+ padding: 16px 20px 6px;
106
+ font-size: 10px;
107
+ text-transform: uppercase;
108
+ letter-spacing: 1px;
109
+ color: var(--muted);
110
+ }
111
+
112
+ main {
113
+ padding: 24px;
114
+ overflow-y: auto;
115
+ }
116
+
117
+ .panel {
118
+ display: none;
119
+ }
120
+
121
+ .panel.active {
122
+ display: block;
123
+ }
124
+
125
+ h2 {
126
+ font-size: 18px;
127
+ margin-bottom: 20px;
128
+ color: var(--text);
129
+ }
130
+
131
+ .grid {
132
+ display: grid;
133
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
134
+ gap: 14px;
135
+ margin-bottom: 24px;
136
+ }
137
+
138
+ .card {
139
+ background: var(--surface);
140
+ border: 1px solid var(--border);
141
+ border-radius: 8px;
142
+ padding: 16px;
143
+ }
144
+
145
+ .card .label {
146
+ font-size: 11px;
147
+ color: var(--muted);
148
+ text-transform: uppercase;
149
+ letter-spacing: 0.5px;
150
+ margin-bottom: 8px;
151
+ }
152
+
153
+ .card .value {
154
+ font-size: 28px;
155
+ font-weight: 700;
156
+ color: var(--accent);
157
+ }
158
+
159
+ .card .sub {
160
+ font-size: 12px;
161
+ color: var(--muted);
162
+ margin-top: 4px;
163
+ }
164
+
165
+ .box {
166
+ background: var(--surface);
167
+ border: 1px solid var(--border);
168
+ border-radius: 8px;
169
+ padding: 20px;
170
+ margin-bottom: 16px;
171
+ }
172
+
173
+ .row {
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 12px;
177
+ padding: 10px 0;
178
+ border-bottom: 1px solid var(--border);
179
+ }
180
+
181
+ .row:last-child {
182
+ border-bottom: none;
183
+ }
184
+
185
+ .badge {
186
+ padding: 2px 8px;
187
+ border-radius: 12px;
188
+ font-size: 11px;
189
+ font-weight: 500;
190
+ }
191
+
192
+ .badge.green {
193
+ background: rgba(63, 185, 80, 0.15);
194
+ color: var(--green);
195
+ }
196
+
197
+ .badge.yellow {
198
+ background: rgba(210, 153, 34, 0.15);
199
+ color: var(--yellow);
200
+ }
201
+
202
+ .badge.red {
203
+ background: rgba(248, 81, 73, 0.15);
204
+ color: var(--red);
205
+ }
206
+
207
+ .badge.blue {
208
+ background: rgba(88, 166, 255, 0.15);
209
+ color: var(--accent);
210
+ }
211
+
212
+ .badge.purple {
213
+ background: rgba(188, 140, 255, 0.15);
214
+ color: var(--purple);
215
+ }
216
+
217
+ input,
218
+ textarea,
219
+ select {
220
+ background: #0d1117;
221
+ border: 1px solid var(--border);
222
+ color: var(--text);
223
+ border-radius: 6px;
224
+ padding: 9px 12px;
225
+ font-size: 13px;
226
+ width: 100%;
227
+ outline: none;
228
+ font-family: inherit;
229
+ }
230
+
231
+ input:focus,
232
+ textarea:focus {
233
+ border-color: var(--accent);
234
+ }
235
+
236
+ textarea {
237
+ resize: vertical;
238
+ min-height: 80px;
239
+ }
240
+
241
+ .btn {
242
+ padding: 8px 16px;
243
+ border-radius: 6px;
244
+ border: none;
245
+ cursor: pointer;
246
+ font-size: 13px;
247
+ font-weight: 500;
248
+ transition: all 0.15s;
249
+ }
250
+
251
+ .btn-primary {
252
+ background: var(--accent);
253
+ color: #000;
254
+ }
255
+
256
+ .btn-primary:hover {
257
+ background: #79c0ff;
258
+ }
259
+
260
+ .btn-outline {
261
+ background: none;
262
+ border: 1px solid var(--border);
263
+ color: var(--text);
264
+ }
265
+
266
+ .btn-outline:hover {
267
+ border-color: var(--accent);
268
+ color: var(--accent);
269
+ }
270
+
271
+ .btn-red {
272
+ background: rgba(248, 81, 73, 0.15);
273
+ border: 1px solid var(--red);
274
+ color: var(--red);
275
+ }
276
+
277
+ .flex {
278
+ display: flex;
279
+ gap: 10px;
280
+ align-items: center;
281
+ }
282
+
283
+ .flex-1 {
284
+ flex: 1;
285
+ }
286
+
287
+ pre {
288
+ background: #0d1117;
289
+ border: 1px solid var(--border);
290
+ border-radius: 6px;
291
+ padding: 16px;
292
+ overflow-x: auto;
293
+ font-size: 12px;
294
+ white-space: pre-wrap;
295
+ word-break: break-word;
296
+ max-height: 400px;
297
+ overflow-y: auto;
298
+ }
299
+
300
+ .answer-box {
301
+ background: rgba(88, 166, 255, 0.05);
302
+ border: 1px solid rgba(88, 166, 255, 0.2);
303
+ border-radius: 8px;
304
+ padding: 16px;
305
+ margin-top: 12px;
306
+ line-height: 1.7;
307
+ white-space: pre-wrap;
308
+ font-size: 13px;
309
+ }
310
+
311
+ .spinner {
312
+ display: inline-block;
313
+ width: 16px;
314
+ height: 16px;
315
+ border: 2px solid var(--border);
316
+ border-top-color: var(--accent);
317
+ border-radius: 50%;
318
+ animation: spin 0.7s linear infinite;
319
+ }
320
+
321
+ @keyframes spin {
322
+ to {
323
+ transform: rotate(360deg);
324
+ }
325
+ }
326
+
327
+ .tags {
328
+ display: flex;
329
+ flex-wrap: wrap;
330
+ gap: 6px;
331
+ margin-top: 8px;
332
+ }
333
+
334
+ .tag {
335
+ background: #1f2937;
336
+ color: var(--muted);
337
+ padding: 2px 8px;
338
+ border-radius: 12px;
339
+ font-size: 11px;
340
+ }
341
+
342
+ .alert-item {
343
+ padding: 12px;
344
+ border-radius: 6px;
345
+ margin-bottom: 8px;
346
+ }
347
+
348
+ .alert-item.critical {
349
+ background: rgba(248, 81, 73, 0.1);
350
+ border-left: 3px solid var(--red);
351
+ }
352
+
353
+ .alert-item.warn {
354
+ background: rgba(210, 153, 34, 0.1);
355
+ border-left: 3px solid var(--yellow);
356
+ }
357
+
358
+ .alert-item.info {
359
+ background: rgba(88, 166, 255, 0.07);
360
+ border-left: 3px solid var(--accent);
361
+ }
362
+
363
+ .check-row {
364
+ display: flex;
365
+ align-items: center;
366
+ padding: 10px 0;
367
+ border-bottom: 1px solid var(--border);
368
+ gap: 12px;
369
+ }
370
+
371
+ table {
372
+ width: 100%;
373
+ border-collapse: collapse;
374
+ }
375
+
376
+ th {
377
+ text-align: left;
378
+ padding: 10px 12px;
379
+ font-size: 11px;
380
+ text-transform: uppercase;
381
+ color: var(--muted);
382
+ border-bottom: 1px solid var(--border);
383
+ }
384
+
385
+ td {
386
+ padding: 10px 12px;
387
+ border-bottom: 1px solid var(--border);
388
+ font-size: 13px;
389
+ }
390
+
391
+ tr:last-child td {
392
+ border-bottom: none;
393
+ }
394
+
395
+ .conv-msg {
396
+ margin-bottom: 14px;
397
+ }
398
+
399
+ .conv-msg .role {
400
+ font-size: 11px;
401
+ color: var(--muted);
402
+ margin-bottom: 4px;
403
+ text-transform: uppercase;
404
+ letter-spacing: 0.5px;
405
+ }
406
+
407
+ .conv-msg.user .bubble {
408
+ background: rgba(88, 166, 255, 0.08);
409
+ border-radius: 6px;
410
+ padding: 10px 14px;
411
+ }
412
+
413
+ .conv-msg.assistant .bubble {
414
+ background: var(--surface);
415
+ border: 1px solid var(--border);
416
+ border-radius: 6px;
417
+ padding: 10px 14px;
418
+ white-space: pre-wrap;
419
+ line-height: 1.65;
420
+ }
421
+
422
+ .pill {
423
+ display: inline-block;
424
+ padding: 3px 10px;
425
+ border-radius: 20px;
426
+ font-size: 11px;
427
+ }
428
+
429
+ #toast {
430
+ position: fixed;
431
+ bottom: 24px;
432
+ right: 24px;
433
+ background: var(--surface);
434
+ border: 1px solid var(--border);
435
+ border-radius: 8px;
436
+ padding: 12px 18px;
437
+ font-size: 13px;
438
+ opacity: 0;
439
+ transition: opacity 0.3s;
440
+ pointer-events: none;
441
+ z-index: 999;
442
+ }
443
+
444
+ #toast.show {
445
+ opacity: 1;
446
+ }
447
+ </style>
448
+ </head>
449
+
450
+ <body>
451
+
452
+ <header>
453
+ <div>🧠</div>
454
+ <h1>Dev MCP Server</h1>
455
+ <span class="version">v1.0</span>
456
+ <div id="serverStatus" class="status-dot" style="background:var(--yellow);box-shadow:0 0 6px var(--yellow)">
457
+ </div>
458
+ <span id="statusText" style="font-size:12px;color:var(--muted)">Connecting...</span>
459
+ </header>
460
+
461
+ <div class="layout">
462
+ <nav>
463
+ <div class="section-label">Overview</div>
464
+ <button class="active" onclick="show('dashboard')">📊 Dashboard</button>
465
+ <div class="section-label">Query</div>
466
+ <button onclick="show('chat')">💬 Conversation</button>
467
+ <button onclick="show('query')">⚡ Quick Query</button>
468
+ <div class="section-label">Agents</div>
469
+ <button onclick="show('agents')">🤖 Specialists</button>
470
+ <button onclick="show('teams')">👥 Teams</button>
471
+ <button onclick="show('pipelines')">🔧 Pipelines</button>
472
+ <div class="section-label">Tools</div>
473
+ <button onclick="show('skills')">🎯 Skills</button>
474
+ <button onclick="show('lsp')">🔍 Symbols</button>
475
+ <button onclick="show('edit')">✏️ File Edit</button>
476
+ <div class="section-label">Intelligence</div>
477
+ <button onclick="show('memory')">🧠 Memory</button>
478
+ <button onclick="show('dreamer')">💤 Dreamer</button>
479
+ <button onclick="show('monitor')">👁️ Monitor</button>
480
+ <div class="section-label">Manage</div>
481
+ <button onclick="show('tasks')">📋 Tasks</button>
482
+ <button onclick="show('knowledge')">📚 Knowledge</button>
483
+ <button onclick="show('cost')">💰 Cost</button>
484
+ </nav>
485
+
486
+ <main>
487
+
488
+ <!-- DASHBOARD -->
489
+ <div id="panel-dashboard" class="panel active">
490
+ <h2>System Overview</h2>
491
+ <div id="statsGrid" class="grid">
492
+ <div class="card">
493
+ <div class="label">Documents</div>
494
+ <div class="value" id="stat-docs">—</div>
495
+ <div class="sub" id="stat-files">— files</div>
496
+ </div>
497
+ <div class="card">
498
+ <div class="label">Memories</div>
499
+ <div class="value" id="stat-mems">—</div>
500
+ <div class="sub">auto-extracted facts</div>
501
+ </div>
502
+ <div class="card">
503
+ <div class="label">Tasks</div>
504
+ <div class="value" id="stat-tasks">—</div>
505
+ <div class="sub" id="stat-open">— open</div>
506
+ </div>
507
+ <div class="card">
508
+ <div class="label">Total Cost</div>
509
+ <div class="value" id="stat-cost">—</div>
510
+ <div class="sub" id="stat-calls">— API calls</div>
511
+ </div>
512
+ </div>
513
+ <div class="box">
514
+ <div style="font-weight:600;margin-bottom:14px">🔍 Quick Search</div>
515
+ <div class="flex">
516
+ <input id="quickSearch" class="flex-1" placeholder="Search your codebase..."
517
+ onkeydown="if(event.key==='Enter')rawSearch()">
518
+ <button class="btn btn-primary" onclick="rawSearch()">Search</button>
519
+ </div>
520
+ <div id="searchResults" style="margin-top:14px"></div>
521
+ </div>
522
+ <div class="box">
523
+ <div style="font-weight:600;margin-bottom:14px">📁 Ingest Files</div>
524
+ <div class="flex">
525
+ <input id="ingestPath" class="flex-1" placeholder="/path/to/your/project or file">
526
+ <button class="btn btn-primary" onclick="ingest()">Ingest</button>
527
+ </div>
528
+ <div id="ingestResult" style="margin-top:12px;color:var(--muted);font-size:12px"></div>
529
+ </div>
530
+ <div class="box">
531
+ <div style="font-weight:600;margin-bottom:12px">📁 Indexed File Types</div>
532
+ <div id="fileTypes"></div>
533
+ </div>
534
+ </div>
535
+
536
+ <!-- CONVERSATION -->
537
+ <div id="panel-chat" class="panel">
538
+ <h2>Multi-turn Conversation</h2>
539
+ <div class="box" style="min-height:300px;max-height:500px;overflow-y:auto" id="chatHistory"></div>
540
+ <div class="flex" style="margin-top:12px">
541
+ <input id="chatInput" class="flex-1" placeholder="Ask anything about your codebase..."
542
+ onkeydown="if(event.key==='Enter'&&!event.shiftKey)sendChat()">
543
+ <button class="btn btn-primary" onclick="sendChat()">Send</button>
544
+ <button class="btn btn-outline" onclick="resetChat()" title="Reset conversation">↺</button>
545
+ </div>
546
+ <div id="chatMeta" style="font-size:11px;color:var(--muted);margin-top:8px"></div>
547
+ </div>
548
+
549
+ <!-- QUERY -->
550
+ <div id="panel-query" class="panel">
551
+ <h2>Quick Query</h2>
552
+ <div class="box">
553
+ <div class="flex" style="margin-bottom:12px">
554
+ <select id="queryMode" style="width:auto">
555
+ <option value="">Auto-detect mode</option>
556
+ <option value="debug">🐛 Debug</option>
557
+ <option value="usage">🔍 Usage</option>
558
+ <option value="impact">💥 Impact</option>
559
+ <option value="general">💬 General</option>
560
+ </select>
561
+ </div>
562
+ <textarea id="queryInput" placeholder="Ask a question about your codebase..."
563
+ style="min-height:100px"></textarea>
564
+ <div class="flex" style="margin-top:10px">
565
+ <button class="btn btn-primary" onclick="runQuery()">Ask</button>
566
+ <span id="querySpinner" style="display:none"><span class="spinner"></span></span>
567
+ </div>
568
+ </div>
569
+ <div id="queryResult"></div>
570
+ </div>
571
+
572
+ <!-- AGENTS -->
573
+ <div id="panel-agents" class="panel">
574
+ <h2>Specialist Agents</h2>
575
+ <div class="flex" style="margin-bottom:16px;gap:8px;flex-wrap:wrap">
576
+ <button class="btn btn-outline" onclick="selectAgent('DebugAgent')">🐛 Debug</button>
577
+ <button class="btn btn-outline" onclick="selectAgent('ArchitectureAgent')">🏗️ Architecture</button>
578
+ <button class="btn btn-outline" onclick="selectAgent('SecurityAgent')">🔐 Security</button>
579
+ <button class="btn btn-outline" onclick="selectAgent('DocumentationAgent')">📝 Docs</button>
580
+ <button class="btn btn-outline" onclick="selectAgent('RefactorAgent')">♻️ Refactor</button>
581
+ <button class="btn btn-outline" onclick="selectAgent('PerformanceAgent')">⚡ Performance</button>
582
+ </div>
583
+ <div class="box">
584
+ <div style="font-size:12px;color:var(--muted);margin-bottom:8px">Agent: <span id="selectedAgent"
585
+ style="color:var(--accent)">DebugAgent</span></div>
586
+ <textarea id="agentTask" placeholder="Describe the task for this agent..."
587
+ style="min-height:100px"></textarea>
588
+ <button class="btn btn-primary" style="margin-top:10px" onclick="runAgent()">Run Agent</button>
589
+ </div>
590
+ <div id="agentResult"></div>
591
+ </div>
592
+
593
+ <!-- TEAMS -->
594
+ <div id="panel-teams" class="panel">
595
+ <h2>Agent Teams</h2>
596
+ <div id="teamsList" class="grid" style="grid-template-columns:repeat(auto-fit,minmax(240px,1fr))"></div>
597
+ <div class="box">
598
+ <div style="font-weight:600;margin-bottom:12px">Run Team</div>
599
+ <select id="teamSelect" style="margin-bottom:10px">
600
+ <option value="auto">🤖 Auto-select best team</option>
601
+ <option value="full-audit">🔍 Full Audit</option>
602
+ <option value="feature-review">✨ Feature Review</option>
603
+ <option value="bug-triage">🐛 Bug Triage</option>
604
+ <option value="onboarding">👋 Onboarding</option>
605
+ <option value="refactor-safe">♻️ Safe Refactor</option>
606
+ </select>
607
+ <textarea id="teamTask" placeholder="Describe the task for the team..."
608
+ style="min-height:80px"></textarea>
609
+ <button class="btn btn-primary" style="margin-top:10px" onclick="runTeam()">Run Team</button>
610
+ </div>
611
+ <div id="teamResult"></div>
612
+ </div>
613
+
614
+ <!-- PIPELINES -->
615
+ <div id="panel-pipelines" class="panel">
616
+ <h2>Pipelines</h2>
617
+ <div id="pipelinesList"></div>
618
+ <div class="box">
619
+ <div style="font-weight:600;margin-bottom:12px">Run Pipeline</div>
620
+ <select id="pipelineSelect" style="margin-bottom:10px">
621
+ <option value="debug-pipeline">🐛 Debug Pipeline</option>
622
+ <option value="security-audit-pipeline">🔐 Security Audit</option>
623
+ <option value="onboarding-pipeline">👋 Onboarding</option>
624
+ <option value="feature-planning-pipeline">🗺️ Feature Planning</option>
625
+ <option value="code-review-pipeline">👀 Code Review</option>
626
+ <option value="impact-analysis-pipeline">💥 Impact Analysis</option>
627
+ </select>
628
+ <textarea id="pipelineTask" placeholder="Task description..." style="min-height:80px"></textarea>
629
+ <button class="btn btn-primary" style="margin-top:10px" onclick="runPipeline()">Run
630
+ Pipeline</button>
631
+ </div>
632
+ <div id="pipelineResult"></div>
633
+ </div>
634
+
635
+ <!-- SKILLS -->
636
+ <div id="panel-skills" class="panel">
637
+ <h2>Skills</h2>
638
+ <div id="skillsList"></div>
639
+ <div class="box">
640
+ <div style="font-weight:600;margin-bottom:12px">Run Skill</div>
641
+ <select id="skillSelect" style="margin-bottom:10px"></select>
642
+ <input id="skillTarget" placeholder="Target (function name, file path, etc.)"
643
+ style="margin-bottom:10px">
644
+ <button class="btn btn-primary" onclick="runSkill()">Run Skill</button>
645
+ </div>
646
+ <div id="skillResult"></div>
647
+ </div>
648
+
649
+ <!-- LSP -->
650
+ <div id="panel-lsp" class="panel">
651
+ <h2>Symbol Navigator</h2>
652
+ <div class="box">
653
+ <div class="flex" style="gap:8px;margin-bottom:14px;flex-wrap:wrap">
654
+ <button class="btn btn-outline" onclick="setLspMode('definition')">Go to Definition</button>
655
+ <button class="btn btn-outline" onclick="setLspMode('references')">Find References</button>
656
+ <button class="btn btn-outline" onclick="setLspMode('hover')">Hover Docs</button>
657
+ <button class="btn btn-outline" onclick="setLspMode('workspace')">Workspace Symbols</button>
658
+ </div>
659
+ <div style="font-size:12px;color:var(--muted);margin-bottom:8px">Mode: <span id="lspMode"
660
+ style="color:var(--accent)">definition</span></div>
661
+ <div class="flex">
662
+ <input id="lspSymbol" class="flex-1" placeholder="Symbol name or query..."
663
+ onkeydown="if(event.key==='Enter')runLsp()">
664
+ <button class="btn btn-primary" onclick="runLsp()">Search</button>
665
+ </div>
666
+ </div>
667
+ <div id="lspResult"></div>
668
+ </div>
669
+
670
+ <!-- FILE EDIT -->
671
+ <div id="panel-edit" class="panel">
672
+ <h2>File Editor</h2>
673
+ <div class="box">
674
+ <input id="editFile" placeholder="File path (e.g. ./src/services/UserService.js)"
675
+ style="margin-bottom:10px">
676
+ <div class="flex" style="gap:8px;margin-bottom:10px">
677
+ <button class="btn btn-outline" onclick="setEditMode('ai')" id="editModeAi"
678
+ style="border-color:var(--accent);color:var(--accent)">✨ AI Edit</button>
679
+ <button class="btn btn-outline" onclick="setEditMode('read')">📖 Read</button>
680
+ </div>
681
+ <textarea id="editInstruction" placeholder="Describe what to change (AI edit mode)..."
682
+ style="min-height:80px"></textarea>
683
+ <div class="flex" style="margin-top:10px">
684
+ <button class="btn btn-outline" onclick="runEdit(true)">Preview (dry run)</button>
685
+ <button class="btn btn-primary" onclick="runEdit(false)">Apply Edit</button>
686
+ </div>
687
+ </div>
688
+ <div id="editResult"></div>
689
+ </div>
690
+
691
+ <!-- MEMORY -->
692
+ <div id="panel-memory" class="panel">
693
+ <h2>Memory</h2>
694
+ <div class="box">
695
+ <div class="flex">
696
+ <input id="memInput" class="flex-1" placeholder="Add a memory manually...">
697
+ <select id="memType" style="width:auto">
698
+ <option value="fact">fact</option>
699
+ <option value="bug">bug</option>
700
+ <option value="pattern">pattern</option>
701
+ <option value="decision">decision</option>
702
+ <option value="preference">preference</option>
703
+ </select>
704
+ <button class="btn btn-primary" onclick="addMemory()">Add</button>
705
+ </div>
706
+ </div>
707
+ <div class="box">
708
+ <div id="memoryList">Loading...</div>
709
+ </div>
710
+ </div>
711
+
712
+ <!-- DREAMER -->
713
+ <div id="panel-dreamer" class="panel">
714
+ <h2>Dreamer</h2>
715
+ <div class="box">
716
+ <div id="dreamerStatus"></div>
717
+ <div class="flex" style="margin-top:14px;gap:8px">
718
+ <button class="btn btn-primary" onclick="triggerDream()">▶ Dream Now</button>
719
+ <button class="btn btn-outline" onclick="loadDreamer()">↺ Refresh</button>
720
+ </div>
721
+ </div>
722
+ <div class="box">
723
+ <div style="font-weight:600;margin-bottom:12px">Recent Dreams</div>
724
+ <div id="dreamHistory"></div>
725
+ </div>
726
+ </div>
727
+
728
+ <!-- MONITOR -->
729
+ <div id="panel-monitor" class="panel">
730
+ <h2>Proactive Monitor</h2>
731
+ <div class="box">
732
+ <div id="monitorStatus"></div>
733
+ <div class="flex" style="margin-top:14px;gap:8px">
734
+ <button class="btn btn-primary" onclick="runAllChecks()">▶ Run All Checks</button>
735
+ <button class="btn btn-outline" onclick="acknowledgeAll()">✓ Ack All</button>
736
+ <button class="btn btn-outline" onclick="loadMonitor()">↺ Refresh</button>
737
+ </div>
738
+ </div>
739
+ <div class="box">
740
+ <div style="font-weight:600;margin-bottom:12px">Active Alerts</div>
741
+ <div id="alertsList">Loading...</div>
742
+ </div>
743
+ </div>
744
+
745
+ <!-- TASKS -->
746
+ <div id="panel-tasks" class="panel">
747
+ <h2>Tasks</h2>
748
+ <div class="box">
749
+ <div class="flex">
750
+ <input id="taskTitle" class="flex-1" placeholder="New task title...">
751
+ <select id="taskPriority" style="width:auto">
752
+ <option value="medium">medium</option>
753
+ <option value="high">high</option>
754
+ <option value="critical">critical</option>
755
+ <option value="low">low</option>
756
+ </select>
757
+ <button class="btn btn-primary" onclick="createTask()">Add</button>
758
+ </div>
759
+ </div>
760
+ <div class="box">
761
+ <div id="taskList">Loading...</div>
762
+ </div>
763
+ </div>
764
+
765
+ <!-- KNOWLEDGE -->
766
+ <div id="panel-knowledge" class="panel">
767
+ <h2>Knowledge Base</h2>
768
+ <div class="box">
769
+ <div id="kbStats">Loading...</div>
770
+ </div>
771
+ <div class="box">
772
+ <div style="font-weight:600;margin-bottom:12px">Indexed Files</div>
773
+ <div id="filesList" style="max-height:400px;overflow-y:auto"></div>
774
+ </div>
775
+ </div>
776
+
777
+ <!-- COST -->
778
+ <div id="panel-cost" class="panel">
779
+ <h2>Cost & Usage</h2>
780
+ <div id="costPanel"></div>
781
+ </div>
782
+
783
+ </main>
784
+ </div>
785
+
786
+ <div id="toast"></div>
787
+
788
+ <script>
789
+ const API = '';
790
+
791
+ // ── Navigation ────────────────────────────────────────────────────────────────
792
+ function show(name) {
793
+ document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
794
+ document.querySelectorAll('nav button').forEach(b => b.classList.remove('active'));
795
+ document.getElementById('panel-' + name).classList.add('active');
796
+ document.querySelector(`[onclick="show('${name}')"]`).classList.add('active');
797
+ if (name === 'dashboard') loadDashboard();
798
+ if (name === 'memory') loadMemory();
799
+ if (name === 'tasks') loadTasks();
800
+ if (name === 'knowledge') loadKnowledge();
801
+ if (name === 'cost') loadCost();
802
+ if (name === 'dreamer') loadDreamer();
803
+ if (name === 'monitor') loadMonitor();
804
+ if (name === 'skills') loadSkills();
805
+ if (name === 'teams') loadTeams();
806
+ if (name === 'pipelines') loadPipelines();
807
+ }
808
+
809
+ // ── Utils ─────────────────────────────────────────────────────────────────────
810
+ async function api(method, path, body) {
811
+ const res = await fetch(API + path, {
812
+ method, headers: { 'Content-Type': 'application/json' },
813
+ body: body ? JSON.stringify(body) : undefined,
814
+ });
815
+ return res.json();
816
+ }
817
+ function toast(msg, color = 'var(--green)') {
818
+ const t = document.getElementById('toast');
819
+ t.textContent = msg; t.style.borderColor = color;
820
+ t.classList.add('show'); setTimeout(() => t.classList.remove('show'), 3000);
821
+ }
822
+ function loading(id, msg = 'Loading...') { document.getElementById(id).innerHTML = `<span class="spinner"></span> ${msg}`; }
823
+ function badge(text, color = 'blue') { return `<span class="badge ${color}">${text}</span>`; }
824
+
825
+ // ── Dashboard ─────────────────────────────────────────────────────────────────
826
+ async function loadDashboard() {
827
+ const [kb, mem, tasks, cost] = await Promise.all([
828
+ api('GET', '/api/knowledge/stats'), api('GET', '/api/memory'),
829
+ api('GET', '/api/tasks'), api('GET', '/api/cost'),
830
+ ]);
831
+ document.getElementById('stat-docs').textContent = kb.totalDocs || 0;
832
+ document.getElementById('stat-files').textContent = `${kb.totalFiles || 0} files`;
833
+ document.getElementById('stat-mems').textContent = mem.stats?.total || 0;
834
+ document.getElementById('stat-tasks').textContent = tasks.stats?.total || 0;
835
+ document.getElementById('stat-open').textContent = `${tasks.stats?.byStatus?.todo || 0} open`;
836
+ const allTime = cost.allTime;
837
+ document.getElementById('stat-cost').textContent = allTime ? `$${allTime.costUsd.toFixed(4)}` : '$0';
838
+ document.getElementById('stat-calls').textContent = `${allTime?.calls || 0} API calls`;
839
+ const ft = document.getElementById('fileTypes');
840
+ ft.innerHTML = Object.entries(kb.fileTypes || {}).map(([k, v]) =>
841
+ `<div class="row"><span>${k}</span><span class="badge blue">${v} chunks</span></div>`).join('');
842
+ document.getElementById('serverStatus').style.background = 'var(--green)';
843
+ document.getElementById('serverStatus').style.boxShadow = '0 0 6px var(--green)';
844
+ document.getElementById('statusText').textContent = 'Online';
845
+ }
846
+ async function rawSearch() {
847
+ const q = document.getElementById('quickSearch').value.trim();
848
+ if (!q) return;
849
+ const res = await api('GET', `/api/knowledge/search?q=${encodeURIComponent(q)}&topK=6`);
850
+ document.getElementById('searchResults').innerHTML = res.results?.map(r =>
851
+ `<div class="row"><div><div>${r.file} ${badge(r.kind)} <span style="color:var(--muted);font-size:11px">${r.relevanceScore}</span></div>
852
+ <div style="color:var(--muted);font-size:12px;margin-top:4px">${r.snippet?.slice(0, 120)}</div></div></div>`
853
+ ).join('') || '<span style="color:var(--muted)">No results</span>';
854
+ }
855
+ async function ingest() {
856
+ const p = document.getElementById('ingestPath').value.trim();
857
+ if (!p) return;
858
+ document.getElementById('ingestResult').textContent = '⏳ Ingesting...';
859
+ const isFile = p.includes('.') && !p.endsWith('/');
860
+ const endpoint = isFile ? '/api/ingest/file' : '/api/ingest/directory';
861
+ const body = isFile ? { filePath: p } : { dirPath: p };
862
+ const res = await api('POST', endpoint, body);
863
+ document.getElementById('ingestResult').textContent = res.success
864
+ ? `✅ Ingested: ${res.result?.ingested || 1} file(s), ${res.result?.totalChunks || res.result?.chunks || 0} chunks`
865
+ : `❌ ${res.error}`;
866
+ }
867
+
868
+ // ── Chat ──────────────────────────────────────────────────────────────────────
869
+ let chatHistory = [];
870
+ async function sendChat() {
871
+ const msg = document.getElementById('chatInput').value.trim();
872
+ if (!msg) return;
873
+ document.getElementById('chatInput').value = '';
874
+ chatHistory.push({ role: 'user', content: msg });
875
+ renderChat();
876
+ const res = await api('POST', '/api/chat', { message: msg, convId: 'dashboard' });
877
+ if (res.answer) {
878
+ chatHistory.push({ role: 'assistant', content: res.answer });
879
+ document.getElementById('chatMeta').textContent = `Turn ${res.turn} | ${res.contextChunks} context chunks | ${res.memoriesUsed} memories | $${(res.usage?.costUsd || 0).toFixed(5)}`;
880
+ }
881
+ renderChat();
882
+ }
883
+ function renderChat() {
884
+ const el = document.getElementById('chatHistory');
885
+ el.innerHTML = chatHistory.map(m =>
886
+ `<div class="conv-msg ${m.role}"><div class="role">${m.role}</div><div class="bubble">${m.content}</div></div>`
887
+ ).join('');
888
+ el.scrollTop = el.scrollHeight;
889
+ }
890
+ async function resetChat() {
891
+ await api('DELETE', '/api/chat/dashboard');
892
+ chatHistory = []; renderChat();
893
+ document.getElementById('chatMeta').textContent = '';
894
+ toast('Conversation reset');
895
+ }
896
+
897
+ // ── Query ─────────────────────────────────────────────────────────────────────
898
+ async function runQuery() {
899
+ const q = document.getElementById('queryInput').value.trim();
900
+ const mode = document.getElementById('queryMode').value;
901
+ if (!q) return;
902
+ document.getElementById('querySpinner').style.display = 'inline';
903
+ document.getElementById('queryResult').innerHTML = '';
904
+ const res = await api('POST', '/api/query', { question: q, mode: mode || undefined });
905
+ document.getElementById('querySpinner').style.display = 'none';
906
+ if (res.answer) {
907
+ document.getElementById('queryResult').innerHTML =
908
+ `<div class="box"><div class="flex" style="margin-bottom:10px">
909
+ ${badge(res.mode, 'blue')} <span style="color:var(--muted);font-size:11px">${res.sources?.length || 0} sources | $${(res.usage?.costUsd || 0).toFixed(5)}</span>
910
+ </div><div class="answer-box">${res.answer}</div>
911
+ <div style="margin-top:10px">${(res.sources || []).map(s => `<div class="row" style="padding:5px 0"><span style="font-size:12px">${s.file}</span>${badge(s.kind)}<span style="color:var(--muted);font-size:11px;margin-left:auto">${s.relevanceScore}</span></div>`).join('')}</div></div>`;
912
+ } else {
913
+ document.getElementById('queryResult').innerHTML = `<div style="color:var(--red)">${res.error || 'Error'}</div>`;
914
+ }
915
+ }
916
+
917
+ // ── Agents ────────────────────────────────────────────────────────────────────
918
+ let selectedAgentName = 'DebugAgent';
919
+ function selectAgent(name) { selectedAgentName = name; document.getElementById('selectedAgent').textContent = name; }
920
+ async function runAgent() {
921
+ const task = document.getElementById('agentTask').value.trim();
922
+ if (!task) return;
923
+ loading('agentResult', `Running ${selectedAgentName}...`);
924
+ const res = await api('POST', `/api/agents/${selectedAgentName}`, { task });
925
+ document.getElementById('agentResult').innerHTML = res.answer
926
+ ? `<div class="box"><div style="color:var(--muted);font-size:11px;margin-bottom:8px">${res.contextChunks} ctx | ${res.memoriesUsed} memories</div><div class="answer-box">${res.answer}</div></div>`
927
+ : `<div style="color:var(--red)">${res.error}</div>`;
928
+ }
929
+
930
+ // ── Teams ─────────────────────────────────────────────────────────────────────
931
+ async function loadTeams() {
932
+ const res = await api('GET', '/api/agents/teams/list');
933
+ document.getElementById('teamsList').innerHTML = (res.teams || []).map(t =>
934
+ `<div class="card" onclick="document.getElementById('teamSelect').value='${t.name}';document.getElementById('teamTask').focus()" style="cursor:pointer">
935
+ <div class="label">${t.name}</div>
936
+ <div style="font-size:12px;color:var(--muted);margin-top:4px">${t.description}</div>
937
+ <div class="tags">${t.agents.map(a => `<span class="tag">${a.replace('Agent', '')}</span>`).join('')}</div>
938
+ </div>`).join('');
939
+ }
940
+ async function runTeam() {
941
+ const team = document.getElementById('teamSelect').value;
942
+ const task = document.getElementById('teamTask').value.trim();
943
+ if (!task) return;
944
+ loading('teamResult', `Running team "${team}"...`);
945
+ const endpoint = team === 'auto' ? '/api/agents/teams/auto' : `/api/agents/teams/${team}`;
946
+ const res = await api('POST', endpoint, { task });
947
+ document.getElementById('teamResult').innerHTML = res.report
948
+ ? `<div class="box"><div style="color:var(--muted);font-size:11px;margin-bottom:8px">Team: ${res.team} | Agents: ${(res.agentsRun || []).join(', ')}</div><div class="answer-box">${res.report}</div></div>`
949
+ : `<div style="color:var(--red)">${res.error || JSON.stringify(res)}</div>`;
950
+ }
951
+
952
+ // ── Pipelines ─────────────────────────────────────────────────────────────────
953
+ async function loadPipelines() {
954
+ const res = await api('GET', '/api/pipelines');
955
+ document.getElementById('pipelinesList').innerHTML = (res.pipelines || []).map(p =>
956
+ `<div class="box" style="margin-bottom:8px">
957
+ <div class="flex"><span style="font-weight:600">${p.name}</span></div>
958
+ <div style="color:var(--muted);font-size:12px;margin:6px 0">${p.description}</div>
959
+ <div class="tags">${p.steps.map(s => `<span class="tag">${s}</span>`).join(' → ')}</div>
960
+ </div>`).join('');
961
+ }
962
+ async function runPipeline() {
963
+ const name = document.getElementById('pipelineSelect').value;
964
+ const task = document.getElementById('pipelineTask').value.trim();
965
+ if (!task) return;
966
+ loading('pipelineResult', `Running pipeline "${name}"...`);
967
+ const res = await api('POST', `/api/pipelines/${name}`, { task });
968
+ const lastAnswer = res.finalOutput?.answer || res.finalOutput?.plan || res.finalOutput?.review || JSON.stringify(res.finalOutput || {}).slice(0, 200);
969
+ document.getElementById('pipelineResult').innerHTML = `<div class="box">
970
+ <div style="color:var(--muted);font-size:11px;margin-bottom:8px">Steps: ${res.successCount}/${res.totalSteps} OK | ${res.durationMs}ms</div>
971
+ <div class="tags">${(res.steps || []).map(s => `<span class="tag" style="color:${s.success ? 'var(--green)' : 'var(--red)'}">${s.step}</span>`).join('')}</div>
972
+ ${lastAnswer ? `<div class="answer-box" style="margin-top:12px">${lastAnswer}</div>` : ''}
973
+ </div>`;
974
+ }
975
+
976
+ // ── Skills ────────────────────────────────────────────────────────────────────
977
+ async function loadSkills() {
978
+ const res = await api('GET', '/api/skills');
979
+ const sel = document.getElementById('skillSelect');
980
+ sel.innerHTML = (res.skills || []).map(s => `<option value="${s.name}">${s.name}${s.builtIn ? ' ⭐' : ''}</option>`).join('');
981
+ document.getElementById('skillsList').innerHTML = (res.skills || []).slice(0, 12).map(s =>
982
+ `<div class="row"><div class="flex-1"><div>${s.name} ${s.builtIn ? badge('built-in', 'green') : badge('custom', 'purple')}</div>
983
+ <div style="color:var(--muted);font-size:12px">${s.description}</div></div></div>`).join('');
984
+ }
985
+ async function runSkill() {
986
+ const skill = document.getElementById('skillSelect').value;
987
+ const target = document.getElementById('skillTarget').value.trim();
988
+ if (!target) return;
989
+ loading('skillResult', `Running skill "${skill}" on "${target}"...`);
990
+ const res = await api('POST', `/api/skills/${skill}`, { target });
991
+ document.getElementById('skillResult').innerHTML = res.result
992
+ ? `<div class="box"><div class="answer-box">${res.result}</div></div>`
993
+ : `<div style="color:var(--red)">${res.error}</div>`;
994
+ }
995
+
996
+ // ── LSP ───────────────────────────────────────────────────────────────────────
997
+ let lspCurrentMode = 'definition';
998
+ function setLspMode(m) { lspCurrentMode = m; document.getElementById('lspMode').textContent = m; }
999
+ async function runLsp() {
1000
+ const sym = document.getElementById('lspSymbol').value.trim();
1001
+ if (!sym) return;
1002
+ loading('lspResult');
1003
+ let res, html = '';
1004
+ if (lspCurrentMode === 'definition') {
1005
+ res = await api('GET', `/api/lsp/definition/${encodeURIComponent(sym)}`);
1006
+ html = `<div class="box"><div style="font-weight:600;margin-bottom:10px">Definitions of "${sym}" (${res.count})</div>
1007
+ ${(res.definitions || []).map(d => `<div class="row"><span style="font-size:12px;color:var(--accent)">${d.file || d.path}</span>${badge(d.kind || 'symbol')}<span style="color:var(--muted);font-size:11px">L${d.lineNumber || '?'}</span></div>`).join('')}</div>`;
1008
+ } else if (lspCurrentMode === 'references') {
1009
+ res = await api('GET', `/api/lsp/references/${encodeURIComponent(sym)}`);
1010
+ html = `<div class="box"><div style="font-weight:600;margin-bottom:10px">${res.total} references to "${sym}"</div>
1011
+ ${(res.references || []).slice(0, 15).map(r => `<div class="row"><span style="color:var(--accent);font-size:12px">${r.file}</span>:${r.line} ${badge(r.type)}<span style="color:var(--muted);font-size:11px;margin-left:8px">${r.text?.slice(0, 60)}</span></div>`).join('')}</div>`;
1012
+ } else if (lspCurrentMode === 'hover') {
1013
+ res = await api('POST', '/api/lsp/hover', { symbol: sym });
1014
+ html = `<div class="box"><div class="answer-box">${res.documentation || res.error}</div></div>`;
1015
+ } else {
1016
+ res = await api('GET', `/api/lsp/symbols?q=${encodeURIComponent(sym)}`);
1017
+ html = `<div class="box"><div style="font-weight:600;margin-bottom:10px">Workspace symbols (${res.symbols?.length || 0})</div>
1018
+ ${(res.symbols || []).map(s => `<div class="row"><span style="color:var(--accent)">${s.name}</span>${badge(s.kind)}<span style="color:var(--muted);font-size:11px">${s.file}</span></div>`).join('')}</div>`;
1019
+ }
1020
+ document.getElementById('lspResult').innerHTML = html;
1021
+ }
1022
+
1023
+ // ── File Edit ─────────────────────────────────────────────────────────────────
1024
+ let editMode = 'ai';
1025
+ function setEditMode(m) {
1026
+ editMode = m;
1027
+ document.getElementById('editModeAi').style.borderColor = m === 'ai' ? 'var(--accent)' : 'var(--border)';
1028
+ document.getElementById('editModeAi').style.color = m === 'ai' ? 'var(--accent)' : 'var(--muted)';
1029
+ }
1030
+ async function runEdit(dryRun) {
1031
+ const filePath = document.getElementById('editFile').value.trim();
1032
+ if (!filePath) return;
1033
+ if (editMode === 'read') {
1034
+ const res = await api('GET', `/api/files/read?path=${encodeURIComponent(filePath)}`);
1035
+ document.getElementById('editResult').innerHTML = `<div class="box"><pre>${res.content || res.error}</pre></div>`;
1036
+ return;
1037
+ }
1038
+ const instruction = document.getElementById('editInstruction').value.trim();
1039
+ if (!instruction) return;
1040
+ loading('editResult', 'Applying AI edit...');
1041
+ const res = await api('POST', '/api/files/ai-edit', { filePath, instruction, dryRun });
1042
+ document.getElementById('editResult').innerHTML = `<div class="box">
1043
+ <div style="font-size:12px;color:var(--muted);margin-bottom:8px">${dryRun ? '🔍 Preview (not saved)' : '✅ Applied'} | ${res.diff?.summary || ''}</div>
1044
+ <pre>${(res.diff?.changes || []).join('\n') || JSON.stringify(res, null, 2).slice(0, 500)}</pre></div>`;
1045
+ }
1046
+
1047
+ // ── Memory ────────────────────────────────────────────────────────────────────
1048
+ async function loadMemory() {
1049
+ const res = await api('GET', '/api/memory');
1050
+ const el = document.getElementById('memoryList');
1051
+ if (!res.memories?.length) { el.innerHTML = '<span style="color:var(--muted)">No memories yet.</span>'; return; }
1052
+ el.innerHTML = res.memories.map(m =>
1053
+ `<div class="row"><div class="flex-1">
1054
+ <div>${badge(m.type)} <span style="font-size:12px">${m.content.slice(0, 100)}</span></div>
1055
+ <div style="font-size:11px;color:var(--muted);margin-top:3px">${m.id} | used ${m.useCount}× | ${m.createdAt?.slice(0, 10)}</div>
1056
+ </div><button class="btn btn-red" style="padding:3px 8px;font-size:11px" onclick="deleteMemory('${m.id}')">×</button></div>`
1057
+ ).join('');
1058
+ }
1059
+ async function addMemory() {
1060
+ const c = document.getElementById('memInput').value.trim();
1061
+ const t = document.getElementById('memType').value;
1062
+ if (!c) return;
1063
+ await api('POST', '/api/memory', { content: c, type: t });
1064
+ document.getElementById('memInput').value = '';
1065
+ toast('Memory saved'); loadMemory();
1066
+ }
1067
+ async function deleteMemory(id) {
1068
+ await api('DELETE', `/api/memory/${id}`);
1069
+ loadMemory();
1070
+ }
1071
+
1072
+ // ── Dreamer ───────────────────────────────────────────────────────────────────
1073
+ async function loadDreamer() {
1074
+ const res = await api('GET', '/api/agents/dreamer/status');
1075
+ document.getElementById('dreamerStatus').innerHTML =
1076
+ `<div class="flex"><div>${badge(res.isRunning ? 'running' : 'stopped', res.isRunning ? 'green' : 'yellow')}</div>
1077
+ <span style="color:var(--muted);font-size:12px">Dream count: ${res.dreamCount} | Last: ${res.lastDream?.slice(0, 16) || 'never'}</span></div>`;
1078
+ document.getElementById('dreamHistory').innerHTML = (res.recentDreams || []).reverse().map(d =>
1079
+ `<div class="row"><span style="font-size:12px">${d.timestamp?.slice(0, 16)}</span>
1080
+ <span style="color:var(--muted);font-size:12px">${d.insightsGenerated} insights, ${d.memoriesPruned} pruned</span>
1081
+ <span style="color:var(--muted);font-size:11px">${d.durationMs}ms</span></div>`
1082
+ ).join('') || '<span style="color:var(--muted)">No dreams yet.</span>';
1083
+ }
1084
+ async function triggerDream() {
1085
+ loading('dreamHistory', 'Dreaming...');
1086
+ await api('POST', '/api/agents/dreamer/now');
1087
+ toast('Dream complete ✨'); loadDreamer();
1088
+ }
1089
+
1090
+ // ── Monitor ───────────────────────────────────────────────────────────────────
1091
+ async function loadMonitor() {
1092
+ const res = await api('GET', '/api/monitor/status');
1093
+ document.getElementById('monitorStatus').innerHTML =
1094
+ `<div class="grid" style="grid-template-columns:repeat(3,1fr);margin-bottom:0">
1095
+ <div class="card"><div class="label">Unacked Alerts</div><div class="value" style="color:var(--yellow)">${res.alerts?.unacknowledged || 0}</div></div>
1096
+ <div class="card"><div class="label">Critical</div><div class="value" style="color:var(--red)">${res.alerts?.critical || 0}</div></div>
1097
+ <div class="card"><div class="label">Warnings</div><div class="value" style="color:var(--yellow)">${res.alerts?.warn || 0}</div></div>
1098
+ </div>`;
1099
+ const alerts = await api('GET', '/api/monitor/alerts?unacknowledged=true&limit=20');
1100
+ document.getElementById('alertsList').innerHTML = (alerts.alerts || []).length
1101
+ ? (alerts.alerts || []).map(a =>
1102
+ `<div class="alert-item ${a.severity}">
1103
+ <div class="flex"><span style="font-weight:600">${a.checkName}</span>${badge(a.severity, a.severity === 'critical' ? 'red' : a.severity === 'warn' ? 'yellow' : 'blue')}
1104
+ <span style="color:var(--muted);font-size:11px;margin-left:auto">${a.triggeredAt?.slice(0, 16)}</span>
1105
+ <button class="btn btn-outline" style="padding:2px 8px;font-size:11px" onclick="ackAlert('${a.id}')">Ack</button></div>
1106
+ <div style="font-size:12px;color:var(--muted);margin-top:6px">${a.findings?.length || 0} findings: ${(a.findings || []).slice(0, 2).map(f => f.file || f.title || f.label || '').join(', ')}</div>
1107
+ </div>`).join('')
1108
+ : '<span style="color:var(--muted)">No unacknowledged alerts.</span>';
1109
+ }
1110
+ async function runAllChecks() {
1111
+ toast('Running all checks...');
1112
+ await api('POST', '/api/monitor/run-all');
1113
+ loadMonitor();
1114
+ }
1115
+ async function acknowledgeAll() {
1116
+ await api('POST', '/api/monitor/acknowledge-all');
1117
+ toast('All acknowledged'); loadMonitor();
1118
+ }
1119
+ async function ackAlert(id) {
1120
+ await api('POST', `/api/monitor/alerts/${id}/acknowledge`);
1121
+ loadMonitor();
1122
+ }
1123
+
1124
+ // ── Tasks ─────────────────────────────────────────────────────────────────────
1125
+ async function loadTasks() {
1126
+ const res = await api('GET', '/api/tasks?includeDone=false');
1127
+ const el = document.getElementById('taskList');
1128
+ if (!res.tasks?.length) { el.innerHTML = '<span style="color:var(--muted)">No open tasks.</span>'; return; }
1129
+ el.innerHTML = `<table><thead><tr><th>#</th><th>Title</th><th>Priority</th><th>Status</th><th></th></tr></thead><tbody>
1130
+ ${res.tasks.map(t => `<tr>
1131
+ <td style="color:var(--muted)">${t.id}</td>
1132
+ <td>${t.title}</td>
1133
+ <td>${badge(t.priority, t.priority === 'critical' ? 'red' : t.priority === 'high' ? 'yellow' : 'blue')}</td>
1134
+ <td>${badge(t.status)}</td>
1135
+ <td><button class="btn btn-outline" style="padding:2px 8px;font-size:11px" onclick="doneTask(${t.id})">Done</button></td>
1136
+ </tr>`).join('')}</tbody></table>`;
1137
+ }
1138
+ async function createTask() {
1139
+ const title = document.getElementById('taskTitle').value.trim();
1140
+ const priority = document.getElementById('taskPriority').value;
1141
+ if (!title) return;
1142
+ await api('POST', '/api/tasks', { title, priority });
1143
+ document.getElementById('taskTitle').value = '';
1144
+ toast('Task created'); loadTasks();
1145
+ }
1146
+ async function doneTask(id) {
1147
+ await api('PATCH', `/api/tasks/${id}`, { status: 'done' });
1148
+ toast('Task done ✓'); loadTasks();
1149
+ }
1150
+
1151
+ // ── Knowledge ─────────────────────────────────────────────────────────────────
1152
+ async function loadKnowledge() {
1153
+ const [stats, files] = await Promise.all([api('GET', '/api/knowledge/stats'), api('GET', '/api/knowledge/files')]);
1154
+ document.getElementById('kbStats').innerHTML =
1155
+ `<div class="grid" style="grid-template-columns:repeat(4,1fr);margin-bottom:0">
1156
+ <div class="card"><div class="label">Documents</div><div class="value">${stats.totalDocs}</div></div>
1157
+ <div class="card"><div class="label">Files</div><div class="value">${stats.totalFiles}</div></div>
1158
+ ${Object.entries(stats.fileTypes || {}).map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value">${v}</div></div>`).join('')}
1159
+ </div>`;
1160
+ document.getElementById('filesList').innerHTML = (files.files || []).map(f =>
1161
+ `<div class="row"><span style="font-size:12px;color:var(--accent)">${f.filename}</span>${badge(f.kind)}<span style="color:var(--muted);font-size:11px">${f.chunks} chunks</span></div>`
1162
+ ).join('');
1163
+ }
1164
+
1165
+ // ── Cost ──────────────────────────────────────────────────────────────────────
1166
+ async function loadCost() {
1167
+ const res = await api('GET', '/api/cost');
1168
+ document.getElementById('costPanel').innerHTML = `
1169
+ <div class="grid">
1170
+ <div class="card"><div class="label">All-time Cost</div><div class="value">$${res.allTime?.costUsd?.toFixed(4) || '0.0000'}</div><div class="sub">${res.allTime?.calls || 0} total calls</div></div>
1171
+ <div class="card"><div class="label">Input Tokens</div><div class="value" style="color:var(--green)">${(res.allTime?.inputTokens || 0).toLocaleString()}</div></div>
1172
+ <div class="card"><div class="label">Output Tokens</div><div class="value" style="color:var(--purple)">${(res.allTime?.outputTokens || 0).toLocaleString()}</div></div>
1173
+ </div>`;
1174
+ }
1175
+
1176
+ // ── Boot ──────────────────────────────────────────────────────────────────────
1177
+ loadDashboard();
1178
+ </script>
1179
+ </body>
1180
+
1181
+ </html>