loki-mode 4.2.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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +691 -0
  3. package/SKILL.md +191 -0
  4. package/VERSION +1 -0
  5. package/autonomy/.loki/dashboard/index.html +2634 -0
  6. package/autonomy/CONSTITUTION.md +508 -0
  7. package/autonomy/README.md +201 -0
  8. package/autonomy/config.example.yaml +152 -0
  9. package/autonomy/loki +526 -0
  10. package/autonomy/run.sh +3636 -0
  11. package/bin/loki-mode.js +26 -0
  12. package/bin/postinstall.js +60 -0
  13. package/docs/ACKNOWLEDGEMENTS.md +234 -0
  14. package/docs/COMPARISON.md +325 -0
  15. package/docs/COMPETITIVE-ANALYSIS.md +333 -0
  16. package/docs/INSTALLATION.md +547 -0
  17. package/docs/auto-claude-comparison.md +276 -0
  18. package/docs/cursor-comparison.md +225 -0
  19. package/docs/dashboard-guide.md +355 -0
  20. package/docs/screenshots/README.md +149 -0
  21. package/docs/screenshots/dashboard-agents.png +0 -0
  22. package/docs/screenshots/dashboard-tasks.png +0 -0
  23. package/docs/thick2thin.md +173 -0
  24. package/package.json +48 -0
  25. package/references/advanced-patterns.md +453 -0
  26. package/references/agent-types.md +243 -0
  27. package/references/agents.md +1043 -0
  28. package/references/business-ops.md +550 -0
  29. package/references/competitive-analysis.md +216 -0
  30. package/references/confidence-routing.md +371 -0
  31. package/references/core-workflow.md +275 -0
  32. package/references/cursor-learnings.md +207 -0
  33. package/references/deployment.md +604 -0
  34. package/references/lab-research-patterns.md +534 -0
  35. package/references/mcp-integration.md +186 -0
  36. package/references/memory-system.md +467 -0
  37. package/references/openai-patterns.md +647 -0
  38. package/references/production-patterns.md +568 -0
  39. package/references/prompt-repetition.md +192 -0
  40. package/references/quality-control.md +437 -0
  41. package/references/sdlc-phases.md +410 -0
  42. package/references/task-queue.md +361 -0
  43. package/references/tool-orchestration.md +691 -0
  44. package/skills/00-index.md +120 -0
  45. package/skills/agents.md +249 -0
  46. package/skills/artifacts.md +174 -0
  47. package/skills/github-integration.md +218 -0
  48. package/skills/model-selection.md +125 -0
  49. package/skills/parallel-workflows.md +526 -0
  50. package/skills/patterns-advanced.md +188 -0
  51. package/skills/production.md +292 -0
  52. package/skills/quality-gates.md +180 -0
  53. package/skills/testing.md +149 -0
  54. package/skills/troubleshooting.md +109 -0
@@ -0,0 +1,2634 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="description" content="Loki Mode Dashboard - Multi-agent autonomous system monitor">
7
+ <meta name="theme-color" content="#d97757">
8
+ <title>Loki Mode Dashboard</title>
9
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect fill='%23d97757' rx='15' width='100' height='100'/><text x='50' y='72' font-size='60' font-weight='bold' text-anchor='middle' fill='white'>L</text></svg>">
10
+ <link rel="preconnect" href="https://fonts.googleapis.com">
11
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
13
+ <style>
14
+ /* ============================================
15
+ Anthropic Design Language - Light & Dark
16
+ Based on anthropic.com color system
17
+ ============================================ */
18
+ :root {
19
+ /* Light Mode (default) - Anthropic cream theme */
20
+ --bg-primary: #faf9f0;
21
+ --bg-secondary: #f5f4eb;
22
+ --bg-tertiary: #eeeddf;
23
+ --bg-card: #ffffff;
24
+ --bg-hover: #f0efe6;
25
+ --accent: #d97757;
26
+ --accent-light: #e8956f;
27
+ --accent-muted: rgba(217, 119, 87, 0.12);
28
+ --text-primary: #1a1a1a;
29
+ --text-secondary: #5c5c5c;
30
+ --text-muted: #8a8a8a;
31
+ --border: #e5e3de;
32
+ --border-light: #d4d2cb;
33
+
34
+ /* Status Colors */
35
+ --green: #16a34a;
36
+ --green-muted: rgba(22, 163, 74, 0.12);
37
+ --yellow: #ca8a04;
38
+ --yellow-muted: rgba(202, 138, 4, 0.12);
39
+ --red: #dc2626;
40
+ --red-muted: rgba(220, 38, 38, 0.12);
41
+ --blue: #2563eb;
42
+ --blue-muted: rgba(37, 99, 235, 0.12);
43
+ --purple: #9333ea;
44
+ --purple-muted: rgba(147, 51, 234, 0.12);
45
+
46
+ /* Model Colors */
47
+ --opus: #d97706;
48
+ --sonnet: #4f46e5;
49
+ --haiku: #059669;
50
+
51
+ /* Transition */
52
+ --transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
53
+ }
54
+
55
+ [data-theme="dark"] {
56
+ /* Dark Mode - Anthropic dark theme */
57
+ --bg-primary: #131314;
58
+ --bg-secondary: #1a1a1b;
59
+ --bg-tertiary: #232325;
60
+ --bg-card: #1e1e20;
61
+ --bg-hover: #2a2a2d;
62
+ --accent: #d97757;
63
+ --accent-light: #e8956f;
64
+ --accent-muted: rgba(217, 119, 87, 0.15);
65
+ --text-primary: #f5f5f5;
66
+ --text-secondary: #a1a1a6;
67
+ --text-muted: #6b6b70;
68
+ --border: #2d2d30;
69
+ --border-light: #3d3d42;
70
+
71
+ --green: #22c55e;
72
+ --green-muted: rgba(34, 197, 94, 0.15);
73
+ --yellow: #eab308;
74
+ --yellow-muted: rgba(234, 179, 8, 0.15);
75
+ --red: #ef4444;
76
+ --red-muted: rgba(239, 68, 68, 0.15);
77
+ --blue: #3b82f6;
78
+ --blue-muted: rgba(59, 130, 246, 0.15);
79
+ --purple: #a855f7;
80
+ --purple-muted: rgba(168, 85, 247, 0.15);
81
+
82
+ --opus: #f59e0b;
83
+ --sonnet: #6366f1;
84
+ --haiku: #10b981;
85
+ }
86
+
87
+ * {
88
+ margin: 0;
89
+ padding: 0;
90
+ box-sizing: border-box;
91
+ }
92
+
93
+ body {
94
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
95
+ background: var(--bg-primary);
96
+ color: var(--text-primary);
97
+ min-height: 100vh;
98
+ line-height: 1.5;
99
+ -webkit-font-smoothing: antialiased;
100
+ transition: background var(--transition), color var(--transition);
101
+ }
102
+
103
+ /* Layout */
104
+ .dashboard {
105
+ display: grid;
106
+ grid-template-columns: 260px 1fr;
107
+ min-height: 100vh;
108
+ }
109
+
110
+ /* Sidebar */
111
+ .sidebar {
112
+ background: var(--bg-secondary);
113
+ border-right: 1px solid var(--border);
114
+ padding: 20px 12px;
115
+ display: flex;
116
+ flex-direction: column;
117
+ gap: 20px;
118
+ position: sticky;
119
+ top: 0;
120
+ height: 100vh;
121
+ overflow-y: auto;
122
+ transition: background var(--transition), border-color var(--transition);
123
+ }
124
+
125
+ .logo {
126
+ display: flex;
127
+ align-items: center;
128
+ gap: 10px;
129
+ padding: 0 8px;
130
+ }
131
+
132
+ .logo-icon {
133
+ width: 36px;
134
+ height: 36px;
135
+ background: linear-gradient(135deg, var(--accent), var(--accent-light));
136
+ border-radius: 8px;
137
+ display: flex;
138
+ align-items: center;
139
+ justify-content: center;
140
+ font-weight: 700;
141
+ font-size: 16px;
142
+ color: white;
143
+ }
144
+
145
+ .logo-text {
146
+ font-size: 18px;
147
+ font-weight: 600;
148
+ }
149
+
150
+ .logo-version {
151
+ font-size: 10px;
152
+ color: var(--text-muted);
153
+ font-weight: 400;
154
+ }
155
+
156
+ /* Theme Toggle */
157
+ .theme-toggle {
158
+ display: flex;
159
+ align-items: center;
160
+ gap: 8px;
161
+ padding: 8px 12px;
162
+ background: var(--bg-tertiary);
163
+ border-radius: 8px;
164
+ font-size: 12px;
165
+ color: var(--text-secondary);
166
+ }
167
+
168
+ .theme-toggle-btn {
169
+ width: 44px;
170
+ height: 24px;
171
+ background: var(--border);
172
+ border-radius: 12px;
173
+ border: none;
174
+ cursor: pointer;
175
+ position: relative;
176
+ transition: background var(--transition);
177
+ }
178
+
179
+ .theme-toggle-btn::after {
180
+ content: '';
181
+ position: absolute;
182
+ top: 2px;
183
+ left: 2px;
184
+ width: 20px;
185
+ height: 20px;
186
+ background: var(--bg-card);
187
+ border-radius: 50%;
188
+ transition: transform var(--transition);
189
+ box-shadow: 0 1px 3px rgba(0,0,0,0.2);
190
+ }
191
+
192
+ [data-theme="dark"] .theme-toggle-btn {
193
+ background: var(--accent);
194
+ }
195
+
196
+ [data-theme="dark"] .theme-toggle-btn::after {
197
+ transform: translateX(20px);
198
+ }
199
+
200
+ /* Connection Status */
201
+ .connection-status {
202
+ display: flex;
203
+ align-items: center;
204
+ gap: 6px;
205
+ padding: 6px 12px;
206
+ background: var(--bg-tertiary);
207
+ border-radius: 6px;
208
+ font-size: 11px;
209
+ color: var(--text-muted);
210
+ }
211
+
212
+ .connection-dot {
213
+ width: 6px;
214
+ height: 6px;
215
+ border-radius: 50%;
216
+ background: var(--red);
217
+ }
218
+
219
+ .connection-dot.connected {
220
+ background: var(--green);
221
+ animation: pulse 2s infinite;
222
+ }
223
+
224
+ @keyframes pulse {
225
+ 0%, 100% { opacity: 1; }
226
+ 50% { opacity: 0.5; }
227
+ }
228
+
229
+ /* Sidebar Navigation */
230
+ .sidebar-section {
231
+ display: flex;
232
+ flex-direction: column;
233
+ gap: 4px;
234
+ }
235
+
236
+ .sidebar-title {
237
+ font-size: 10px;
238
+ font-weight: 600;
239
+ text-transform: uppercase;
240
+ letter-spacing: 0.5px;
241
+ color: var(--text-muted);
242
+ padding: 0 12px 4px;
243
+ }
244
+
245
+ .sidebar-item {
246
+ display: flex;
247
+ align-items: center;
248
+ gap: 10px;
249
+ padding: 8px 12px;
250
+ border-radius: 6px;
251
+ cursor: pointer;
252
+ transition: all var(--transition);
253
+ color: var(--text-secondary);
254
+ font-size: 13px;
255
+ font-weight: 500;
256
+ }
257
+
258
+ .sidebar-item:hover {
259
+ background: var(--bg-hover);
260
+ color: var(--text-primary);
261
+ }
262
+
263
+ .sidebar-item.active {
264
+ background: var(--accent-muted);
265
+ color: var(--accent);
266
+ }
267
+
268
+ .sidebar-icon {
269
+ width: 18px;
270
+ height: 18px;
271
+ stroke: currentColor;
272
+ stroke-width: 1.5;
273
+ fill: none;
274
+ }
275
+
276
+ /* Status Panel */
277
+ .status-panel {
278
+ background: var(--bg-tertiary);
279
+ border-radius: 10px;
280
+ padding: 14px;
281
+ display: flex;
282
+ flex-direction: column;
283
+ gap: 10px;
284
+ transition: background var(--transition);
285
+ }
286
+
287
+ .status-row {
288
+ display: flex;
289
+ justify-content: space-between;
290
+ align-items: center;
291
+ font-size: 12px;
292
+ }
293
+
294
+ .status-label {
295
+ color: var(--text-secondary);
296
+ }
297
+
298
+ .status-value {
299
+ font-weight: 500;
300
+ display: flex;
301
+ align-items: center;
302
+ gap: 6px;
303
+ }
304
+
305
+ .status-dot {
306
+ width: 8px;
307
+ height: 8px;
308
+ border-radius: 50%;
309
+ }
310
+
311
+ .status-dot.active { background: var(--green); animation: pulse 2s infinite; }
312
+ .status-dot.idle { background: var(--text-muted); }
313
+ .status-dot.paused { background: var(--yellow); }
314
+ .status-dot.stopped { background: var(--red); }
315
+ .status-dot.error { background: var(--red); }
316
+ .status-dot.offline { background: var(--text-muted); }
317
+
318
+ /* Intervention Controls */
319
+ .intervention-controls {
320
+ display: flex;
321
+ gap: 6px;
322
+ margin-top: 6px;
323
+ }
324
+
325
+ .intervention-btn {
326
+ flex: 1;
327
+ padding: 6px 10px;
328
+ border-radius: 6px;
329
+ border: 1px solid var(--border);
330
+ background: var(--bg-card);
331
+ color: var(--text-secondary);
332
+ font-size: 11px;
333
+ font-weight: 500;
334
+ cursor: pointer;
335
+ transition: all var(--transition);
336
+ display: flex;
337
+ align-items: center;
338
+ justify-content: center;
339
+ gap: 4px;
340
+ }
341
+
342
+ .intervention-btn:hover {
343
+ background: var(--bg-hover);
344
+ color: var(--text-primary);
345
+ }
346
+
347
+ .intervention-btn.pause:hover {
348
+ background: var(--yellow-muted);
349
+ color: var(--yellow);
350
+ border-color: var(--yellow);
351
+ }
352
+
353
+ .intervention-btn.stop:hover {
354
+ background: var(--red-muted);
355
+ color: var(--red);
356
+ border-color: var(--red);
357
+ }
358
+
359
+ /* Main Content */
360
+ .main-content {
361
+ padding: 20px 24px;
362
+ overflow-y: auto;
363
+ }
364
+
365
+ .header {
366
+ display: flex;
367
+ justify-content: space-between;
368
+ align-items: center;
369
+ margin-bottom: 24px;
370
+ }
371
+
372
+ .header-title {
373
+ font-size: 22px;
374
+ font-weight: 600;
375
+ }
376
+
377
+ .header-actions {
378
+ display: flex;
379
+ gap: 10px;
380
+ }
381
+
382
+ .btn {
383
+ padding: 8px 14px;
384
+ border-radius: 6px;
385
+ font-size: 13px;
386
+ font-weight: 500;
387
+ cursor: pointer;
388
+ transition: all var(--transition);
389
+ display: flex;
390
+ align-items: center;
391
+ gap: 6px;
392
+ border: none;
393
+ }
394
+
395
+ .btn-primary {
396
+ background: var(--accent);
397
+ color: white;
398
+ }
399
+
400
+ .btn-primary:hover {
401
+ background: var(--accent-light);
402
+ }
403
+
404
+ .btn-secondary {
405
+ background: var(--bg-tertiary);
406
+ color: var(--text-primary);
407
+ border: 1px solid var(--border);
408
+ }
409
+
410
+ .btn-secondary:hover {
411
+ background: var(--bg-hover);
412
+ }
413
+
414
+ /* Stats Row */
415
+ .stats-row {
416
+ display: grid;
417
+ grid-template-columns: repeat(5, 1fr);
418
+ gap: 12px;
419
+ margin-bottom: 24px;
420
+ }
421
+
422
+ .stat-card {
423
+ background: var(--bg-card);
424
+ border: 1px solid var(--border);
425
+ border-radius: 10px;
426
+ padding: 16px;
427
+ transition: all var(--transition);
428
+ }
429
+
430
+ .stat-label {
431
+ font-size: 12px;
432
+ color: var(--text-secondary);
433
+ margin-bottom: 6px;
434
+ }
435
+
436
+ .stat-value {
437
+ font-size: 24px;
438
+ font-weight: 600;
439
+ font-family: 'JetBrains Mono', monospace;
440
+ }
441
+
442
+ .stat-change {
443
+ font-size: 11px;
444
+ margin-top: 6px;
445
+ display: flex;
446
+ align-items: center;
447
+ gap: 4px;
448
+ }
449
+
450
+ .stat-change.positive { color: var(--green); }
451
+ .stat-change.negative { color: var(--red); }
452
+
453
+ /* Kanban Board */
454
+ .kanban-container {
455
+ margin-bottom: 24px;
456
+ }
457
+
458
+ .section-header {
459
+ display: flex;
460
+ justify-content: space-between;
461
+ align-items: center;
462
+ margin-bottom: 16px;
463
+ }
464
+
465
+ .section-title {
466
+ font-size: 16px;
467
+ font-weight: 600;
468
+ }
469
+
470
+ .kanban-board {
471
+ display: grid;
472
+ grid-template-columns: repeat(4, 1fr);
473
+ gap: 12px;
474
+ min-height: 350px;
475
+ }
476
+
477
+ .kanban-column {
478
+ background: var(--bg-secondary);
479
+ border-radius: 10px;
480
+ padding: 12px;
481
+ display: flex;
482
+ flex-direction: column;
483
+ transition: background var(--transition);
484
+ }
485
+
486
+ .kanban-column-header {
487
+ display: flex;
488
+ justify-content: space-between;
489
+ align-items: center;
490
+ margin-bottom: 12px;
491
+ padding-bottom: 10px;
492
+ border-bottom: 2px solid var(--border);
493
+ }
494
+
495
+ .kanban-column.pending .kanban-column-header { border-color: var(--text-muted); }
496
+ .kanban-column.in-progress .kanban-column-header { border-color: var(--blue); }
497
+ .kanban-column.review .kanban-column-header { border-color: var(--purple); }
498
+ .kanban-column.completed .kanban-column-header { border-color: var(--green); }
499
+
500
+ .kanban-column-title {
501
+ font-size: 13px;
502
+ font-weight: 600;
503
+ display: flex;
504
+ align-items: center;
505
+ gap: 6px;
506
+ }
507
+
508
+ .kanban-column-count {
509
+ background: var(--bg-tertiary);
510
+ padding: 2px 8px;
511
+ border-radius: 10px;
512
+ font-size: 11px;
513
+ font-weight: 600;
514
+ font-family: 'JetBrains Mono', monospace;
515
+ }
516
+
517
+ .kanban-tasks {
518
+ flex: 1;
519
+ display: flex;
520
+ flex-direction: column;
521
+ gap: 8px;
522
+ min-height: 80px;
523
+ transition: background var(--transition);
524
+ border-radius: 6px;
525
+ padding: 4px;
526
+ }
527
+
528
+ .kanban-tasks.drag-over {
529
+ background: var(--bg-hover);
530
+ }
531
+
532
+ .task-card {
533
+ background: var(--bg-card);
534
+ border: 1px solid var(--border);
535
+ border-radius: 6px;
536
+ padding: 10px;
537
+ cursor: grab;
538
+ transition: all var(--transition);
539
+ }
540
+
541
+ .task-card:hover {
542
+ border-color: var(--border-light);
543
+ transform: translateY(-1px);
544
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
545
+ }
546
+
547
+ [data-theme="dark"] .task-card:hover {
548
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
549
+ }
550
+
551
+ .task-card.dragging {
552
+ opacity: 0.5;
553
+ cursor: grabbing;
554
+ }
555
+
556
+ .task-card.from-server {
557
+ border-left: 3px solid var(--accent);
558
+ }
559
+
560
+ .task-card-header {
561
+ display: flex;
562
+ justify-content: space-between;
563
+ align-items: flex-start;
564
+ margin-bottom: 6px;
565
+ }
566
+
567
+ .task-id {
568
+ font-size: 11px;
569
+ font-weight: 600;
570
+ font-family: 'JetBrains Mono', monospace;
571
+ color: var(--accent);
572
+ }
573
+
574
+ .task-priority {
575
+ font-size: 9px;
576
+ padding: 2px 5px;
577
+ border-radius: 3px;
578
+ font-weight: 500;
579
+ text-transform: uppercase;
580
+ }
581
+
582
+ .task-priority.high {
583
+ background: var(--red-muted);
584
+ color: var(--red);
585
+ }
586
+
587
+ .task-priority.medium {
588
+ background: var(--yellow-muted);
589
+ color: var(--yellow);
590
+ }
591
+
592
+ .task-priority.low {
593
+ background: var(--green-muted);
594
+ color: var(--green);
595
+ }
596
+
597
+ .task-title {
598
+ font-size: 12px;
599
+ font-weight: 500;
600
+ margin-bottom: 6px;
601
+ line-height: 1.4;
602
+ }
603
+
604
+ .task-meta {
605
+ display: flex;
606
+ justify-content: space-between;
607
+ align-items: center;
608
+ font-size: 10px;
609
+ color: var(--text-muted);
610
+ }
611
+
612
+ .task-type {
613
+ background: var(--bg-tertiary);
614
+ padding: 2px 6px;
615
+ border-radius: 3px;
616
+ }
617
+
618
+ .add-task-btn {
619
+ background: transparent;
620
+ border: 1px dashed var(--border);
621
+ border-radius: 6px;
622
+ padding: 10px;
623
+ color: var(--text-muted);
624
+ font-size: 12px;
625
+ cursor: pointer;
626
+ transition: all var(--transition);
627
+ display: flex;
628
+ align-items: center;
629
+ justify-content: center;
630
+ gap: 6px;
631
+ margin-top: 8px;
632
+ }
633
+
634
+ .add-task-btn:hover {
635
+ border-color: var(--accent);
636
+ color: var(--accent);
637
+ background: var(--accent-muted);
638
+ }
639
+
640
+ /* Agents Grid */
641
+ .agents-section {
642
+ margin-bottom: 24px;
643
+ }
644
+
645
+ .agents-grid {
646
+ display: grid;
647
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
648
+ gap: 12px;
649
+ }
650
+
651
+ .agent-card {
652
+ background: var(--bg-card);
653
+ border: 1px solid var(--border);
654
+ border-radius: 10px;
655
+ padding: 16px;
656
+ cursor: pointer;
657
+ transition: all var(--transition);
658
+ }
659
+
660
+ .agent-card:hover {
661
+ border-color: var(--border-light);
662
+ transform: translateY(-1px);
663
+ }
664
+
665
+ .agent-card.from-server {
666
+ border-left: 3px solid var(--green);
667
+ }
668
+
669
+ .agent-header {
670
+ display: flex;
671
+ justify-content: space-between;
672
+ align-items: flex-start;
673
+ margin-bottom: 10px;
674
+ }
675
+
676
+ .agent-info h3 {
677
+ font-size: 13px;
678
+ font-weight: 600;
679
+ margin-bottom: 2px;
680
+ font-family: 'JetBrains Mono', monospace;
681
+ }
682
+
683
+ .agent-type {
684
+ font-size: 11px;
685
+ color: var(--text-secondary);
686
+ }
687
+
688
+ .model-badge {
689
+ padding: 3px 8px;
690
+ border-radius: 10px;
691
+ font-size: 10px;
692
+ font-weight: 600;
693
+ text-transform: uppercase;
694
+ }
695
+
696
+ .model-badge.opus {
697
+ background: rgba(217, 119, 6, 0.12);
698
+ color: var(--opus);
699
+ }
700
+
701
+ .model-badge.sonnet {
702
+ background: rgba(79, 70, 229, 0.12);
703
+ color: var(--sonnet);
704
+ }
705
+
706
+ .model-badge.haiku {
707
+ background: rgba(5, 150, 105, 0.12);
708
+ color: var(--haiku);
709
+ }
710
+
711
+ .agent-work {
712
+ font-size: 12px;
713
+ color: var(--text-secondary);
714
+ margin-bottom: 12px;
715
+ line-height: 1.5;
716
+ }
717
+
718
+ .agent-stats {
719
+ display: flex;
720
+ gap: 12px;
721
+ font-size: 11px;
722
+ color: var(--text-muted);
723
+ }
724
+
725
+ .agent-stat {
726
+ display: flex;
727
+ align-items: center;
728
+ gap: 4px;
729
+ }
730
+
731
+ .agent-status {
732
+ display: flex;
733
+ align-items: center;
734
+ gap: 6px;
735
+ margin-top: 10px;
736
+ padding-top: 10px;
737
+ border-top: 1px solid var(--border);
738
+ font-size: 11px;
739
+ }
740
+
741
+ .agent-status.active { color: var(--green); }
742
+ .agent-status.idle { color: var(--text-muted); }
743
+ .agent-status.completed { color: var(--purple); }
744
+ .agent-status.error { color: var(--red); }
745
+
746
+ /* System Grid */
747
+ .system-grid {
748
+ display: grid;
749
+ grid-template-columns: repeat(3, 1fr);
750
+ gap: 12px;
751
+ margin-bottom: 24px;
752
+ }
753
+
754
+ .system-card {
755
+ background: var(--bg-card);
756
+ border: 1px solid var(--border);
757
+ border-radius: 10px;
758
+ padding: 16px;
759
+ transition: all var(--transition);
760
+ }
761
+
762
+ .system-card-header {
763
+ display: flex;
764
+ justify-content: space-between;
765
+ align-items: center;
766
+ margin-bottom: 12px;
767
+ }
768
+
769
+ .system-card-title {
770
+ font-size: 13px;
771
+ font-weight: 600;
772
+ display: flex;
773
+ align-items: center;
774
+ gap: 6px;
775
+ }
776
+
777
+ .system-card-status {
778
+ font-size: 10px;
779
+ padding: 3px 8px;
780
+ border-radius: 10px;
781
+ }
782
+
783
+ .system-card-status.healthy {
784
+ background: var(--green-muted);
785
+ color: var(--green);
786
+ }
787
+
788
+ .system-card-status.warning {
789
+ background: var(--yellow-muted);
790
+ color: var(--yellow);
791
+ }
792
+
793
+ /* RARV Cycle */
794
+ .rarv-cycle {
795
+ display: flex;
796
+ justify-content: space-between;
797
+ gap: 8px;
798
+ }
799
+
800
+ .rarv-step {
801
+ flex: 1;
802
+ text-align: center;
803
+ padding: 10px 8px;
804
+ background: var(--bg-tertiary);
805
+ border-radius: 6px;
806
+ font-size: 11px;
807
+ font-weight: 500;
808
+ position: relative;
809
+ transition: all var(--transition);
810
+ }
811
+
812
+ .rarv-step.active {
813
+ background: var(--accent-muted);
814
+ color: var(--accent);
815
+ }
816
+
817
+ .rarv-step::after {
818
+ content: '';
819
+ position: absolute;
820
+ right: -6px;
821
+ top: 50%;
822
+ transform: translateY(-50%);
823
+ width: 0;
824
+ height: 0;
825
+ border-left: 4px solid var(--text-muted);
826
+ border-top: 4px solid transparent;
827
+ border-bottom: 4px solid transparent;
828
+ }
829
+
830
+ .rarv-step:last-child::after {
831
+ display: none;
832
+ }
833
+
834
+ /* Memory Bars */
835
+ .memory-bars {
836
+ display: flex;
837
+ flex-direction: column;
838
+ gap: 10px;
839
+ }
840
+
841
+ .memory-bar {
842
+ display: flex;
843
+ flex-direction: column;
844
+ gap: 4px;
845
+ }
846
+
847
+ .memory-bar-header {
848
+ display: flex;
849
+ justify-content: space-between;
850
+ font-size: 11px;
851
+ }
852
+
853
+ .memory-bar-label {
854
+ color: var(--text-secondary);
855
+ }
856
+
857
+ .memory-bar-value {
858
+ font-family: 'JetBrains Mono', monospace;
859
+ color: var(--text-muted);
860
+ }
861
+
862
+ .memory-bar-track {
863
+ height: 5px;
864
+ background: var(--bg-tertiary);
865
+ border-radius: 3px;
866
+ overflow: hidden;
867
+ }
868
+
869
+ .memory-bar-fill {
870
+ height: 100%;
871
+ border-radius: 3px;
872
+ transition: width 0.3s ease;
873
+ }
874
+
875
+ .memory-bar-fill.episodic { background: var(--blue); }
876
+ .memory-bar-fill.semantic { background: var(--purple); }
877
+ .memory-bar-fill.procedural { background: var(--green); }
878
+
879
+ /* Quality Gates */
880
+ .quality-gates {
881
+ display: grid;
882
+ grid-template-columns: repeat(2, 1fr);
883
+ gap: 6px;
884
+ }
885
+
886
+ .quality-gate {
887
+ display: flex;
888
+ align-items: center;
889
+ gap: 6px;
890
+ padding: 6px 10px;
891
+ background: var(--bg-tertiary);
892
+ border-radius: 5px;
893
+ font-size: 11px;
894
+ }
895
+
896
+ .gate-icon {
897
+ width: 14px;
898
+ height: 14px;
899
+ }
900
+
901
+ .gate-icon.pass { color: var(--green); }
902
+ .gate-icon.fail { color: var(--red); }
903
+ .gate-icon.pending { color: var(--yellow); }
904
+
905
+ /* Modal */
906
+ .modal-overlay {
907
+ position: fixed;
908
+ top: 0;
909
+ left: 0;
910
+ right: 0;
911
+ bottom: 0;
912
+ background: rgba(0, 0, 0, 0.5);
913
+ display: flex;
914
+ align-items: center;
915
+ justify-content: center;
916
+ z-index: 1000;
917
+ opacity: 0;
918
+ visibility: hidden;
919
+ transition: all var(--transition);
920
+ }
921
+
922
+ [data-theme="dark"] .modal-overlay {
923
+ background: rgba(0, 0, 0, 0.7);
924
+ }
925
+
926
+ .modal-overlay.active {
927
+ opacity: 1;
928
+ visibility: visible;
929
+ }
930
+
931
+ .modal {
932
+ background: var(--bg-secondary);
933
+ border: 1px solid var(--border);
934
+ border-radius: 12px;
935
+ width: 90%;
936
+ max-width: 480px;
937
+ padding: 20px;
938
+ transform: scale(0.95);
939
+ transition: transform var(--transition);
940
+ }
941
+
942
+ .modal-overlay.active .modal {
943
+ transform: scale(1);
944
+ }
945
+
946
+ .modal-header {
947
+ display: flex;
948
+ justify-content: space-between;
949
+ align-items: center;
950
+ margin-bottom: 16px;
951
+ }
952
+
953
+ .modal-title {
954
+ font-size: 16px;
955
+ font-weight: 600;
956
+ }
957
+
958
+ .modal-close {
959
+ background: none;
960
+ border: none;
961
+ color: var(--text-muted);
962
+ cursor: pointer;
963
+ padding: 4px;
964
+ }
965
+
966
+ .modal-close:hover {
967
+ color: var(--text-primary);
968
+ }
969
+
970
+ .form-group {
971
+ margin-bottom: 14px;
972
+ }
973
+
974
+ .form-label {
975
+ display: block;
976
+ font-size: 12px;
977
+ font-weight: 500;
978
+ margin-bottom: 6px;
979
+ color: var(--text-secondary);
980
+ }
981
+
982
+ .form-input, .form-select, .form-textarea {
983
+ width: 100%;
984
+ padding: 8px 10px;
985
+ background: var(--bg-tertiary);
986
+ border: 1px solid var(--border);
987
+ border-radius: 6px;
988
+ color: var(--text-primary);
989
+ font-size: 13px;
990
+ font-family: inherit;
991
+ transition: border-color var(--transition);
992
+ }
993
+
994
+ .form-input:focus, .form-select:focus, .form-textarea:focus {
995
+ outline: none;
996
+ border-color: var(--accent);
997
+ }
998
+
999
+ .form-textarea {
1000
+ min-height: 70px;
1001
+ resize: vertical;
1002
+ }
1003
+
1004
+ .modal-actions {
1005
+ display: flex;
1006
+ justify-content: flex-end;
1007
+ gap: 10px;
1008
+ margin-top: 20px;
1009
+ }
1010
+
1011
+ /* Empty State */
1012
+ .empty-state {
1013
+ text-align: center;
1014
+ padding: 24px;
1015
+ color: var(--text-muted);
1016
+ font-size: 12px;
1017
+ }
1018
+
1019
+ /* Responsive */
1020
+ @media (max-width: 1400px) {
1021
+ .stats-row { grid-template-columns: repeat(3, 1fr); }
1022
+ .system-grid { grid-template-columns: repeat(2, 1fr); }
1023
+ }
1024
+
1025
+ @media (max-width: 1200px) {
1026
+ .kanban-board { grid-template-columns: repeat(2, 1fr); }
1027
+ }
1028
+
1029
+ @media (max-width: 1024px) {
1030
+ .dashboard { grid-template-columns: 1fr; }
1031
+ .sidebar { display: none; }
1032
+ .mobile-header { display: flex !important; }
1033
+ .stats-row { grid-template-columns: repeat(2, 1fr); }
1034
+ }
1035
+
1036
+ .mobile-header {
1037
+ display: none;
1038
+ justify-content: space-between;
1039
+ align-items: center;
1040
+ padding: 12px 16px;
1041
+ background: var(--bg-secondary);
1042
+ border-bottom: 1px solid var(--border);
1043
+ position: sticky;
1044
+ top: 0;
1045
+ z-index: 100;
1046
+ }
1047
+
1048
+ .mobile-logo {
1049
+ display: flex;
1050
+ align-items: center;
1051
+ gap: 8px;
1052
+ font-weight: 600;
1053
+ font-size: 16px;
1054
+ }
1055
+
1056
+ .mobile-logo-icon {
1057
+ width: 28px;
1058
+ height: 28px;
1059
+ background: linear-gradient(135deg, var(--accent), var(--accent-light));
1060
+ border-radius: 6px;
1061
+ display: flex;
1062
+ align-items: center;
1063
+ justify-content: center;
1064
+ font-weight: 700;
1065
+ font-size: 14px;
1066
+ color: white;
1067
+ }
1068
+
1069
+ .mobile-controls {
1070
+ display: flex;
1071
+ align-items: center;
1072
+ gap: 12px;
1073
+ }
1074
+
1075
+ .mobile-status {
1076
+ display: flex;
1077
+ align-items: center;
1078
+ gap: 6px;
1079
+ font-size: 11px;
1080
+ color: var(--text-muted);
1081
+ }
1082
+
1083
+ @media (max-width: 768px) {
1084
+ .main-content { padding: 16px; }
1085
+ .kanban-board { grid-template-columns: 1fr; }
1086
+ .system-grid { grid-template-columns: 1fr; }
1087
+ .stats-row { grid-template-columns: 1fr; }
1088
+ }
1089
+
1090
+ /* Scrollbar */
1091
+ ::-webkit-scrollbar { width: 6px; }
1092
+ ::-webkit-scrollbar-track { background: var(--bg-primary); }
1093
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
1094
+ ::-webkit-scrollbar-thumb:hover { background: var(--border-light); }
1095
+
1096
+ /* Terminal Output Panel */
1097
+ .terminal-section {
1098
+ margin-bottom: 24px;
1099
+ }
1100
+
1101
+ .terminal-container {
1102
+ background: #1a1a1b;
1103
+ border: 1px solid var(--border);
1104
+ border-radius: 10px;
1105
+ overflow: hidden;
1106
+ }
1107
+
1108
+ .terminal-header {
1109
+ display: flex;
1110
+ justify-content: space-between;
1111
+ align-items: center;
1112
+ padding: 10px 14px;
1113
+ background: #232325;
1114
+ border-bottom: 1px solid var(--border);
1115
+ }
1116
+
1117
+ .terminal-title {
1118
+ display: flex;
1119
+ align-items: center;
1120
+ gap: 8px;
1121
+ font-size: 12px;
1122
+ font-weight: 600;
1123
+ color: #a1a1a6;
1124
+ }
1125
+
1126
+ .terminal-dots {
1127
+ display: flex;
1128
+ gap: 6px;
1129
+ }
1130
+
1131
+ .terminal-dot {
1132
+ width: 10px;
1133
+ height: 10px;
1134
+ border-radius: 50%;
1135
+ }
1136
+
1137
+ .terminal-dot.red { background: #ff5f56; }
1138
+ .terminal-dot.yellow { background: #ffbd2e; }
1139
+ .terminal-dot.green { background: #27c93f; }
1140
+
1141
+ .terminal-controls {
1142
+ display: flex;
1143
+ gap: 8px;
1144
+ }
1145
+
1146
+ .terminal-btn {
1147
+ padding: 4px 10px;
1148
+ background: #2a2a2d;
1149
+ border: 1px solid #3d3d42;
1150
+ border-radius: 4px;
1151
+ color: #a1a1a6;
1152
+ font-size: 11px;
1153
+ cursor: pointer;
1154
+ transition: all var(--transition);
1155
+ }
1156
+
1157
+ .terminal-btn:hover {
1158
+ background: #3d3d42;
1159
+ color: #f5f5f5;
1160
+ }
1161
+
1162
+ .terminal-btn.active {
1163
+ background: var(--accent);
1164
+ border-color: var(--accent);
1165
+ color: white;
1166
+ }
1167
+
1168
+ .terminal-output {
1169
+ padding: 14px;
1170
+ max-height: 350px;
1171
+ overflow-y: auto;
1172
+ font-family: 'JetBrains Mono', monospace;
1173
+ font-size: 12px;
1174
+ line-height: 1.6;
1175
+ color: #e5e5e5;
1176
+ background: #1a1a1b;
1177
+ }
1178
+
1179
+ .terminal-line {
1180
+ display: flex;
1181
+ gap: 10px;
1182
+ white-space: pre-wrap;
1183
+ word-break: break-all;
1184
+ }
1185
+
1186
+ .terminal-line .timestamp {
1187
+ color: #6b6b70;
1188
+ flex-shrink: 0;
1189
+ }
1190
+
1191
+ .terminal-line .level-info { color: #3b82f6; }
1192
+ .terminal-line .level-success { color: #22c55e; }
1193
+ .terminal-line .level-warning { color: #eab308; }
1194
+ .terminal-line .level-error { color: #ef4444; }
1195
+ .terminal-line .level-step { color: #a855f7; }
1196
+ .terminal-line .level-agent { color: #d97757; }
1197
+
1198
+ .terminal-line .message {
1199
+ flex: 1;
1200
+ }
1201
+
1202
+ .terminal-empty {
1203
+ color: #6b6b70;
1204
+ text-align: center;
1205
+ padding: 40px;
1206
+ }
1207
+
1208
+ /* Quick Actions Bar */
1209
+ .quick-actions {
1210
+ display: flex;
1211
+ gap: 10px;
1212
+ margin-bottom: 20px;
1213
+ padding: 12px;
1214
+ background: var(--bg-secondary);
1215
+ border: 1px solid var(--border);
1216
+ border-radius: 10px;
1217
+ }
1218
+
1219
+ .quick-action-btn {
1220
+ flex: 1;
1221
+ display: flex;
1222
+ align-items: center;
1223
+ justify-content: center;
1224
+ gap: 8px;
1225
+ padding: 10px 14px;
1226
+ background: var(--bg-card);
1227
+ border: 1px solid var(--border);
1228
+ border-radius: 8px;
1229
+ color: var(--text-secondary);
1230
+ font-size: 12px;
1231
+ font-weight: 500;
1232
+ cursor: pointer;
1233
+ transition: all var(--transition);
1234
+ }
1235
+
1236
+ .quick-action-btn:hover {
1237
+ background: var(--bg-hover);
1238
+ color: var(--text-primary);
1239
+ }
1240
+
1241
+ .quick-action-btn.pause:hover {
1242
+ background: var(--yellow-muted);
1243
+ color: var(--yellow);
1244
+ border-color: var(--yellow);
1245
+ }
1246
+
1247
+ .quick-action-btn.resume:hover {
1248
+ background: var(--green-muted);
1249
+ color: var(--green);
1250
+ border-color: var(--green);
1251
+ }
1252
+
1253
+ .quick-action-btn.github:hover {
1254
+ background: var(--purple-muted);
1255
+ color: var(--purple);
1256
+ border-color: var(--purple);
1257
+ }
1258
+
1259
+ .quick-action-btn.export:hover {
1260
+ background: var(--blue-muted);
1261
+ color: var(--blue);
1262
+ border-color: var(--blue);
1263
+ }
1264
+
1265
+ /* GitHub Import Modal */
1266
+ .github-form .form-row {
1267
+ display: grid;
1268
+ grid-template-columns: 1fr 1fr;
1269
+ gap: 12px;
1270
+ }
1271
+
1272
+ .github-preview {
1273
+ margin-top: 14px;
1274
+ padding: 12px;
1275
+ background: var(--bg-tertiary);
1276
+ border-radius: 6px;
1277
+ font-size: 12px;
1278
+ max-height: 150px;
1279
+ overflow-y: auto;
1280
+ }
1281
+
1282
+ .github-preview-title {
1283
+ font-weight: 600;
1284
+ margin-bottom: 8px;
1285
+ color: var(--text-secondary);
1286
+ }
1287
+
1288
+ .github-issue-item {
1289
+ display: flex;
1290
+ align-items: center;
1291
+ gap: 8px;
1292
+ padding: 6px 0;
1293
+ border-bottom: 1px solid var(--border);
1294
+ }
1295
+
1296
+ .github-issue-item:last-child {
1297
+ border-bottom: none;
1298
+ }
1299
+
1300
+ .github-issue-number {
1301
+ font-family: 'JetBrains Mono', monospace;
1302
+ color: var(--accent);
1303
+ font-weight: 500;
1304
+ }
1305
+
1306
+ .github-issue-title {
1307
+ flex: 1;
1308
+ overflow: hidden;
1309
+ text-overflow: ellipsis;
1310
+ white-space: nowrap;
1311
+ }
1312
+
1313
+ @media (max-width: 768px) {
1314
+ .quick-actions {
1315
+ flex-wrap: wrap;
1316
+ }
1317
+ .quick-action-btn {
1318
+ flex: 1 1 45%;
1319
+ }
1320
+ .github-form .form-row {
1321
+ grid-template-columns: 1fr;
1322
+ }
1323
+ }
1324
+ </style>
1325
+ </head>
1326
+ <body>
1327
+ <!-- Mobile Header (visible on small screens) -->
1328
+ <div class="mobile-header">
1329
+ <div class="mobile-logo">
1330
+ <div class="mobile-logo-icon">L</div>
1331
+ Loki Mode
1332
+ </div>
1333
+ <div class="mobile-controls">
1334
+ <div class="mobile-status">
1335
+ <span class="connection-dot" id="mobile-connection-dot"></span>
1336
+ <span id="mobile-mode-text">--</span>
1337
+ </div>
1338
+ <button class="theme-toggle-btn" id="mobile-theme-toggle" aria-label="Toggle theme"></button>
1339
+ </div>
1340
+ </div>
1341
+
1342
+ <div class="dashboard">
1343
+ <!-- Sidebar -->
1344
+ <aside class="sidebar">
1345
+ <div class="logo">
1346
+ <div class="logo-icon">L</div>
1347
+ <div>
1348
+ <div class="logo-text">Loki Mode</div>
1349
+ <div class="logo-version" id="version">v4.1.0</div>
1350
+ </div>
1351
+ </div>
1352
+
1353
+ <!-- Theme Toggle -->
1354
+ <div class="theme-toggle">
1355
+ <span>Theme</span>
1356
+ <button class="theme-toggle-btn" id="theme-toggle" aria-label="Toggle theme"></button>
1357
+ </div>
1358
+
1359
+ <!-- Connection Status -->
1360
+ <div class="connection-status">
1361
+ <span class="connection-dot" id="connection-dot"></span>
1362
+ <span id="connection-text">Connecting...</span>
1363
+ </div>
1364
+ <div class="connection-status" style="font-size: 10px; margin-top: -10px;">
1365
+ <span id="last-sync">Last sync: --</span>
1366
+ </div>
1367
+
1368
+ <!-- Navigation -->
1369
+ <nav class="sidebar-section">
1370
+ <div class="sidebar-title">Navigation</div>
1371
+ <div class="sidebar-item active" data-section="kanban">
1372
+ <svg class="sidebar-icon" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
1373
+ Kanban Board
1374
+ </div>
1375
+ <div class="sidebar-item" data-section="agents">
1376
+ <svg class="sidebar-icon" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M20 21a8 8 0 10-16 0"/></svg>
1377
+ Active Agents
1378
+ </div>
1379
+ <div class="sidebar-item" data-section="system">
1380
+ <svg class="sidebar-icon" viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
1381
+ System Status
1382
+ </div>
1383
+ <div class="sidebar-item" data-section="terminal">
1384
+ <svg class="sidebar-icon" viewBox="0 0 24 24"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
1385
+ Terminal Output
1386
+ </div>
1387
+ </nav>
1388
+
1389
+ <!-- Status Panel -->
1390
+ <div class="status-panel">
1391
+ <div class="sidebar-title" style="padding: 0; margin-bottom: 4px;">System Status</div>
1392
+ <div class="status-row">
1393
+ <span class="status-label">Mode</span>
1394
+ <span class="status-value">
1395
+ <span class="status-dot offline" id="mode-dot"></span>
1396
+ <span id="mode-text">OFFLINE</span>
1397
+ </span>
1398
+ </div>
1399
+ <div class="status-row">
1400
+ <span class="status-label">Phase</span>
1401
+ <span class="status-value" id="phase-text">--</span>
1402
+ </div>
1403
+ <div class="status-row">
1404
+ <span class="status-label">Complexity</span>
1405
+ <span class="status-value" id="complexity-text">--</span>
1406
+ </div>
1407
+ <div class="status-row">
1408
+ <span class="status-label">Iteration</span>
1409
+ <span class="status-value" id="iteration-text">--</span>
1410
+ </div>
1411
+ <div class="intervention-controls">
1412
+ <button class="intervention-btn pause" onclick="triggerPause()">
1413
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
1414
+ PAUSE
1415
+ </button>
1416
+ <button class="intervention-btn stop" onclick="triggerStop()">
1417
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor"><rect x="4" y="4" width="16" height="16" rx="2"/></svg>
1418
+ STOP
1419
+ </button>
1420
+ </div>
1421
+ </div>
1422
+
1423
+ <!-- Quick Stats -->
1424
+ <div class="sidebar-section">
1425
+ <div class="sidebar-title">Resources</div>
1426
+ <div class="status-panel" style="gap: 6px;">
1427
+ <div class="status-row">
1428
+ <span class="status-label">CPU</span>
1429
+ <span class="status-value" id="cpu-usage">--%</span>
1430
+ </div>
1431
+ <div class="status-row">
1432
+ <span class="status-label">Memory</span>
1433
+ <span class="status-value" id="mem-usage">--%</span>
1434
+ </div>
1435
+ </div>
1436
+ </div>
1437
+ </aside>
1438
+
1439
+ <!-- Main Content -->
1440
+ <main class="main-content">
1441
+ <div class="header">
1442
+ <h1 class="header-title">Dashboard</h1>
1443
+ <div class="header-actions">
1444
+ <button class="btn btn-secondary" onclick="exportTasks()">
1445
+ <svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>
1446
+ Export
1447
+ </button>
1448
+ <button class="btn btn-primary" onclick="openAddTaskModal()">
1449
+ <svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
1450
+ Add Task
1451
+ </button>
1452
+ </div>
1453
+ </div>
1454
+
1455
+ <!-- Stats Row -->
1456
+ <div class="stats-row">
1457
+ <div class="stat-card">
1458
+ <div class="stat-label">Total Tasks</div>
1459
+ <div class="stat-value" id="stat-total">0</div>
1460
+ </div>
1461
+ <div class="stat-card">
1462
+ <div class="stat-label">In Progress</div>
1463
+ <div class="stat-value" id="stat-progress">0</div>
1464
+ </div>
1465
+ <div class="stat-card">
1466
+ <div class="stat-label">Completed</div>
1467
+ <div class="stat-value" id="stat-completed">0</div>
1468
+ </div>
1469
+ <div class="stat-card">
1470
+ <div class="stat-label">Active Agents</div>
1471
+ <div class="stat-value" id="stat-agents">0</div>
1472
+ </div>
1473
+ <div class="stat-card">
1474
+ <div class="stat-label">Failed</div>
1475
+ <div class="stat-value" id="stat-failed">0</div>
1476
+ </div>
1477
+ </div>
1478
+
1479
+ <!-- Quick Actions -->
1480
+ <div class="quick-actions">
1481
+ <button class="quick-action-btn pause" onclick="triggerPause()">
1482
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
1483
+ Pause All
1484
+ </button>
1485
+ <button class="quick-action-btn resume" onclick="triggerResume()">
1486
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>
1487
+ Resume
1488
+ </button>
1489
+ <button class="quick-action-btn github" onclick="openGitHubModal()">
1490
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
1491
+ Import GitHub
1492
+ </button>
1493
+ <button class="quick-action-btn export" onclick="exportTasks()">
1494
+ <svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>
1495
+ Export Report
1496
+ </button>
1497
+ </div>
1498
+
1499
+ <!-- Kanban Board -->
1500
+ <div class="kanban-container" id="section-kanban">
1501
+ <div class="section-header">
1502
+ <h2 class="section-title">Task Queue</h2>
1503
+ </div>
1504
+ <div class="kanban-board" id="kanban-board">
1505
+ <div class="kanban-column pending" data-status="pending">
1506
+ <div class="kanban-column-header">
1507
+ <span class="kanban-column-title">
1508
+ <svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/></svg>
1509
+ Pending
1510
+ </span>
1511
+ <span class="kanban-column-count" id="pending-count">0</span>
1512
+ </div>
1513
+ <div class="kanban-tasks" id="pending-tasks"></div>
1514
+ <button class="add-task-btn" onclick="openAddTaskModal('pending')">+ Add Task</button>
1515
+ </div>
1516
+ <div class="kanban-column in-progress" data-status="in-progress">
1517
+ <div class="kanban-column-header">
1518
+ <span class="kanban-column-title">
1519
+ <svg width="14" height="14" viewBox="0 0 24 24" stroke="var(--blue)" stroke-width="2" fill="none"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/></svg>
1520
+ In Progress
1521
+ </span>
1522
+ <span class="kanban-column-count" id="in-progress-count">0</span>
1523
+ </div>
1524
+ <div class="kanban-tasks" id="in-progress-tasks"></div>
1525
+ </div>
1526
+ <div class="kanban-column review" data-status="review">
1527
+ <div class="kanban-column-header">
1528
+ <span class="kanban-column-title">
1529
+ <svg width="14" height="14" viewBox="0 0 24 24" stroke="var(--purple)" stroke-width="2" fill="none"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
1530
+ In Review
1531
+ </span>
1532
+ <span class="kanban-column-count" id="review-count">0</span>
1533
+ </div>
1534
+ <div class="kanban-tasks" id="review-tasks"></div>
1535
+ </div>
1536
+ <div class="kanban-column completed" data-status="completed">
1537
+ <div class="kanban-column-header">
1538
+ <span class="kanban-column-title">
1539
+ <svg width="14" height="14" viewBox="0 0 24 24" stroke="var(--green)" stroke-width="2" fill="none"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
1540
+ Completed
1541
+ </span>
1542
+ <span class="kanban-column-count" id="completed-count">0</span>
1543
+ </div>
1544
+ <div class="kanban-tasks" id="completed-tasks"></div>
1545
+ </div>
1546
+ </div>
1547
+ </div>
1548
+
1549
+ <!-- Active Agents -->
1550
+ <div class="agents-section" id="section-agents">
1551
+ <div class="section-header">
1552
+ <h2 class="section-title">Active Agents</h2>
1553
+ </div>
1554
+ <div class="agents-grid" id="agents-grid">
1555
+ <div class="empty-state">No active agents. Agents will appear when Loki Mode is running.</div>
1556
+ </div>
1557
+ </div>
1558
+
1559
+ <!-- System Components -->
1560
+ <div class="system-grid" id="section-system">
1561
+ <!-- RARV Cycle -->
1562
+ <div class="system-card">
1563
+ <div class="system-card-header">
1564
+ <span class="system-card-title">
1565
+ <svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>
1566
+ RARV Cycle
1567
+ </span>
1568
+ <span class="system-card-status healthy" id="rarv-status">Active</span>
1569
+ </div>
1570
+ <div class="rarv-cycle">
1571
+ <div class="rarv-step" id="rarv-reason">Reason</div>
1572
+ <div class="rarv-step" id="rarv-act">Act</div>
1573
+ <div class="rarv-step" id="rarv-reflect">Reflect</div>
1574
+ <div class="rarv-step" id="rarv-verify">Verify</div>
1575
+ </div>
1576
+ </div>
1577
+
1578
+ <!-- Memory System -->
1579
+ <div class="system-card">
1580
+ <div class="system-card-header">
1581
+ <span class="system-card-title">
1582
+ <svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/></svg>
1583
+ Memory System
1584
+ </span>
1585
+ <span class="system-card-status healthy">Healthy</span>
1586
+ </div>
1587
+ <div class="memory-bars">
1588
+ <div class="memory-bar">
1589
+ <div class="memory-bar-header">
1590
+ <span class="memory-bar-label">Episodic</span>
1591
+ <span class="memory-bar-value" id="memory-episodic">0 entries</span>
1592
+ </div>
1593
+ <div class="memory-bar-track">
1594
+ <div class="memory-bar-fill episodic" id="memory-episodic-bar" style="width: 0%;"></div>
1595
+ </div>
1596
+ </div>
1597
+ <div class="memory-bar">
1598
+ <div class="memory-bar-header">
1599
+ <span class="memory-bar-label">Semantic</span>
1600
+ <span class="memory-bar-value" id="memory-semantic">0 patterns</span>
1601
+ </div>
1602
+ <div class="memory-bar-track">
1603
+ <div class="memory-bar-fill semantic" id="memory-semantic-bar" style="width: 0%;"></div>
1604
+ </div>
1605
+ </div>
1606
+ <div class="memory-bar">
1607
+ <div class="memory-bar-header">
1608
+ <span class="memory-bar-label">Procedural</span>
1609
+ <span class="memory-bar-value" id="memory-procedural">0 skills</span>
1610
+ </div>
1611
+ <div class="memory-bar-track">
1612
+ <div class="memory-bar-fill procedural" id="memory-procedural-bar" style="width: 0%;"></div>
1613
+ </div>
1614
+ </div>
1615
+ </div>
1616
+ </div>
1617
+
1618
+ <!-- Quality Gates -->
1619
+ <div class="system-card">
1620
+ <div class="system-card-header">
1621
+ <span class="system-card-title">
1622
+ <svg width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
1623
+ Quality Gates
1624
+ </span>
1625
+ <span class="system-card-status healthy" id="quality-status">--</span>
1626
+ </div>
1627
+ <div class="quality-gates" id="quality-gates">
1628
+ <div class="quality-gate"><svg class="gate-icon pending" width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/></svg> Static Analysis</div>
1629
+ <div class="quality-gate"><svg class="gate-icon pending" width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/></svg> 3-Reviewer</div>
1630
+ <div class="quality-gate"><svg class="gate-icon pending" width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/></svg> Anti-Sycophancy</div>
1631
+ <div class="quality-gate"><svg class="gate-icon pending" width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/></svg> Test Coverage</div>
1632
+ <div class="quality-gate"><svg class="gate-icon pending" width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/></svg> Security Scan</div>
1633
+ <div class="quality-gate"><svg class="gate-icon pending" width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/></svg> Performance</div>
1634
+ </div>
1635
+ </div>
1636
+ </div>
1637
+
1638
+ <!-- Terminal Output -->
1639
+ <div class="terminal-section" id="section-terminal">
1640
+ <div class="section-header">
1641
+ <h2 class="section-title">Terminal Output</h2>
1642
+ </div>
1643
+ <div class="terminal-container">
1644
+ <div class="terminal-header">
1645
+ <div class="terminal-title">
1646
+ <div class="terminal-dots">
1647
+ <span class="terminal-dot red"></span>
1648
+ <span class="terminal-dot yellow"></span>
1649
+ <span class="terminal-dot green"></span>
1650
+ </div>
1651
+ loki-mode -- agent output
1652
+ </div>
1653
+ <div class="terminal-controls">
1654
+ <button class="terminal-btn active" id="terminal-auto-scroll" onclick="toggleAutoScroll()">Auto-scroll</button>
1655
+ <button class="terminal-btn" onclick="clearTerminal()">Clear</button>
1656
+ <button class="terminal-btn" onclick="downloadLogs()">Download</button>
1657
+ </div>
1658
+ </div>
1659
+ <div class="terminal-output" id="terminal-output">
1660
+ <div class="terminal-empty">No log output yet. Terminal will update when Loki Mode is running.</div>
1661
+ </div>
1662
+ </div>
1663
+ </div>
1664
+ </main>
1665
+ </div>
1666
+
1667
+ <!-- Add Task Modal -->
1668
+ <div class="modal-overlay" id="add-task-modal">
1669
+ <div class="modal">
1670
+ <div class="modal-header">
1671
+ <h3 class="modal-title">Add New Task</h3>
1672
+ <button class="modal-close" onclick="closeAddTaskModal()">
1673
+ <svg width="18" height="18" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
1674
+ </button>
1675
+ </div>
1676
+ <form onsubmit="addTask(event)">
1677
+ <div class="form-group">
1678
+ <label class="form-label">Task Title</label>
1679
+ <input type="text" class="form-input" id="task-title" placeholder="e.g., Implement user authentication" required>
1680
+ </div>
1681
+ <div class="form-group">
1682
+ <label class="form-label">Description</label>
1683
+ <textarea class="form-textarea" id="task-description" placeholder="Describe what needs to be done..."></textarea>
1684
+ </div>
1685
+ <div class="form-group">
1686
+ <label class="form-label">Type</label>
1687
+ <select class="form-select" id="task-type">
1688
+ <option value="eng-backend">Engineering - Backend</option>
1689
+ <option value="eng-frontend">Engineering - Frontend</option>
1690
+ <option value="eng-database">Engineering - Database</option>
1691
+ <option value="qa-testing">QA - Testing</option>
1692
+ <option value="ops-devops">Operations - DevOps</option>
1693
+ <option value="security">Security</option>
1694
+ <option value="docs">Documentation</option>
1695
+ </select>
1696
+ </div>
1697
+ <div class="form-group">
1698
+ <label class="form-label">Priority</label>
1699
+ <select class="form-select" id="task-priority">
1700
+ <option value="medium">Medium</option>
1701
+ <option value="high">High</option>
1702
+ <option value="low">Low</option>
1703
+ </select>
1704
+ </div>
1705
+ <input type="hidden" id="task-initial-status" value="pending">
1706
+ <div class="modal-actions">
1707
+ <button type="button" class="btn btn-secondary" onclick="closeAddTaskModal()">Cancel</button>
1708
+ <button type="submit" class="btn btn-primary">Add Task</button>
1709
+ </div>
1710
+ </form>
1711
+ </div>
1712
+ </div>
1713
+
1714
+ <!-- GitHub Import Modal -->
1715
+ <div class="modal-overlay" id="github-modal">
1716
+ <div class="modal">
1717
+ <div class="modal-header">
1718
+ <h3 class="modal-title">Import from GitHub</h3>
1719
+ <button class="modal-close" onclick="closeGitHubModal()">
1720
+ <svg width="18" height="18" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
1721
+ </button>
1722
+ </div>
1723
+ <form class="github-form" onsubmit="importGitHubIssues(event)">
1724
+ <div class="form-group">
1725
+ <label class="form-label">Repository</label>
1726
+ <input type="text" class="form-input" id="github-repo" placeholder="owner/repo (leave empty for auto-detect)">
1727
+ </div>
1728
+ <div class="form-row">
1729
+ <div class="form-group">
1730
+ <label class="form-label">Labels (comma-separated)</label>
1731
+ <input type="text" class="form-input" id="github-labels" placeholder="bug,enhancement">
1732
+ </div>
1733
+ <div class="form-group">
1734
+ <label class="form-label">Milestone</label>
1735
+ <input type="text" class="form-input" id="github-milestone" placeholder="v1.0.0">
1736
+ </div>
1737
+ </div>
1738
+ <div class="form-row">
1739
+ <div class="form-group">
1740
+ <label class="form-label">Assignee</label>
1741
+ <input type="text" class="form-input" id="github-assignee" placeholder="@me or username">
1742
+ </div>
1743
+ <div class="form-group">
1744
+ <label class="form-label">Limit</label>
1745
+ <input type="number" class="form-input" id="github-limit" value="20" min="1" max="100">
1746
+ </div>
1747
+ </div>
1748
+ <div class="form-group">
1749
+ <label class="form-label">State</label>
1750
+ <select class="form-select" id="github-state">
1751
+ <option value="open">Open issues only</option>
1752
+ <option value="closed">Closed issues only</option>
1753
+ <option value="all">All issues</option>
1754
+ </select>
1755
+ </div>
1756
+ <div class="github-preview" id="github-preview" style="display: none;">
1757
+ <div class="github-preview-title">Preview: <span id="github-preview-count">0</span> issues found</div>
1758
+ <div id="github-preview-list"></div>
1759
+ </div>
1760
+ <div class="modal-actions">
1761
+ <button type="button" class="btn btn-secondary" onclick="previewGitHubIssues()">Preview</button>
1762
+ <button type="button" class="btn btn-secondary" onclick="closeGitHubModal()">Cancel</button>
1763
+ <button type="submit" class="btn btn-primary">Import</button>
1764
+ </div>
1765
+ </form>
1766
+ </div>
1767
+ </div>
1768
+
1769
+ <script>
1770
+ // ============================================
1771
+ // Configuration
1772
+ // ============================================
1773
+ const CONFIG = {
1774
+ POLL_INTERVAL: 2000, // Poll every 2 seconds
1775
+ STATE_FILE: '../dashboard-state.json',
1776
+ LOCAL_STORAGE_KEY: 'loki-dashboard-local'
1777
+ };
1778
+
1779
+ // ============================================
1780
+ // State Management
1781
+ // ============================================
1782
+ let serverState = null;
1783
+ let localState = {
1784
+ tasks: [],
1785
+ lastUpdated: null
1786
+ };
1787
+ let isConnected = false;
1788
+ let pollInterval = null;
1789
+
1790
+ // ============================================
1791
+ // Theme Management
1792
+ // ============================================
1793
+ function initTheme() {
1794
+ const saved = localStorage.getItem('loki-theme');
1795
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
1796
+ const theme = saved || (prefersDark ? 'dark' : 'light');
1797
+ document.documentElement.setAttribute('data-theme', theme);
1798
+ }
1799
+
1800
+ function toggleTheme() {
1801
+ const current = document.documentElement.getAttribute('data-theme');
1802
+ const next = current === 'dark' ? 'light' : 'dark';
1803
+ document.documentElement.setAttribute('data-theme', next);
1804
+ localStorage.setItem('loki-theme', next);
1805
+ }
1806
+
1807
+ // ============================================
1808
+ // File-Based Sync (Realtime Polling)
1809
+ // ============================================
1810
+ async function fetchServerState() {
1811
+ try {
1812
+ const response = await fetch(CONFIG.STATE_FILE + '?t=' + Date.now());
1813
+ if (!response.ok) {
1814
+ throw new Error('State file not found');
1815
+ }
1816
+ const data = await response.json();
1817
+ serverState = data;
1818
+ setConnected(true);
1819
+ updateUI();
1820
+ return data;
1821
+ } catch (error) {
1822
+ setConnected(false);
1823
+ // Keep showing local state if server unavailable
1824
+ if (localState.tasks.length > 0) {
1825
+ renderKanban();
1826
+ }
1827
+ return null;
1828
+ }
1829
+ }
1830
+
1831
+ function setConnected(connected) {
1832
+ isConnected = connected;
1833
+ const dot = document.getElementById('connection-dot');
1834
+ const text = document.getElementById('connection-text');
1835
+ const syncText = document.getElementById('last-sync');
1836
+ const mobileDot = document.getElementById('mobile-connection-dot');
1837
+ const mobileModeText = document.getElementById('mobile-mode-text');
1838
+
1839
+ if (connected) {
1840
+ dot.classList.add('connected');
1841
+ mobileDot?.classList.add('connected');
1842
+ text.textContent = 'Live - syncing';
1843
+ syncText.textContent = 'Last sync: ' + new Date().toLocaleTimeString();
1844
+ if (mobileModeText && serverState) {
1845
+ mobileModeText.textContent = serverState.mode?.toUpperCase() || 'LIVE';
1846
+ }
1847
+ } else {
1848
+ dot.classList.remove('connected');
1849
+ mobileDot?.classList.remove('connected');
1850
+ text.textContent = 'Offline - local only';
1851
+ if (mobileModeText) {
1852
+ mobileModeText.textContent = 'OFFLINE';
1853
+ }
1854
+ }
1855
+ }
1856
+
1857
+ function startPolling() {
1858
+ fetchServerState();
1859
+ fetchTerminalLogs();
1860
+ pollInterval = setInterval(() => {
1861
+ fetchServerState();
1862
+ fetchTerminalLogs();
1863
+ }, CONFIG.POLL_INTERVAL);
1864
+ }
1865
+
1866
+ function stopPolling() {
1867
+ if (pollInterval) {
1868
+ clearInterval(pollInterval);
1869
+ }
1870
+ }
1871
+
1872
+ // ============================================
1873
+ // Local Storage
1874
+ // ============================================
1875
+ function loadLocalState() {
1876
+ const saved = localStorage.getItem(CONFIG.LOCAL_STORAGE_KEY);
1877
+ if (saved) {
1878
+ localState = JSON.parse(saved);
1879
+ }
1880
+ }
1881
+
1882
+ function saveLocalState() {
1883
+ localState.lastUpdated = Date.now();
1884
+ localStorage.setItem(CONFIG.LOCAL_STORAGE_KEY, JSON.stringify(localState));
1885
+ }
1886
+
1887
+ // ============================================
1888
+ // UI Update Functions
1889
+ // ============================================
1890
+ function updateUI() {
1891
+ if (!serverState) return;
1892
+
1893
+ // Update version
1894
+ document.getElementById('version').textContent = 'v' + (serverState.version || '4.1.0');
1895
+
1896
+ // Update status panel
1897
+ updateStatusPanel();
1898
+
1899
+ // Update stats
1900
+ updateStats();
1901
+
1902
+ // Render kanban with merged tasks
1903
+ renderKanban();
1904
+
1905
+ // Render agents
1906
+ renderAgents();
1907
+
1908
+ // Update system components
1909
+ updateSystemComponents();
1910
+ }
1911
+
1912
+ function updateStatusPanel() {
1913
+ if (!serverState) return;
1914
+
1915
+ const modeMap = {
1916
+ 'autonomous': { text: 'AUTONOMOUS', class: 'active' },
1917
+ 'paused': { text: 'PAUSED', class: 'paused' },
1918
+ 'stopped': { text: 'STOPPED', class: 'stopped' }
1919
+ };
1920
+
1921
+ const mode = modeMap[serverState.mode] || modeMap['autonomous'];
1922
+ document.getElementById('mode-text').textContent = mode.text;
1923
+ document.getElementById('mode-dot').className = 'status-dot ' + mode.class;
1924
+
1925
+ document.getElementById('phase-text').textContent = serverState.phase || '--';
1926
+ document.getElementById('complexity-text').textContent = (serverState.complexity || '--').toUpperCase();
1927
+ document.getElementById('iteration-text').textContent = serverState.iteration || '--';
1928
+
1929
+ // Resources
1930
+ document.getElementById('cpu-usage').textContent = (serverState.metrics?.cpuUsage || 0) + '%';
1931
+ document.getElementById('mem-usage').textContent = (serverState.metrics?.memoryUsage || 0) + '%';
1932
+ }
1933
+
1934
+ function updateStats() {
1935
+ const tasks = serverState?.tasks || {};
1936
+ const pending = tasks.pending?.length || 0;
1937
+ const inProgress = tasks.inProgress?.length || 0;
1938
+ const review = tasks.review?.length || 0;
1939
+ const completed = tasks.completed?.length || 0;
1940
+ const failed = tasks.failed?.length || 0;
1941
+ const total = pending + inProgress + review + completed + failed + localState.tasks.length;
1942
+
1943
+ document.getElementById('stat-total').textContent = total;
1944
+ document.getElementById('stat-progress').textContent = inProgress;
1945
+ document.getElementById('stat-completed').textContent = completed;
1946
+ document.getElementById('stat-failed').textContent = failed;
1947
+ document.getElementById('stat-agents').textContent = serverState?.agents?.length || 0;
1948
+ }
1949
+
1950
+ // ============================================
1951
+ // Kanban Rendering
1952
+ // ============================================
1953
+ function renderKanban() {
1954
+ const columns = {
1955
+ pending: document.getElementById('pending-tasks'),
1956
+ 'in-progress': document.getElementById('in-progress-tasks'),
1957
+ review: document.getElementById('review-tasks'),
1958
+ completed: document.getElementById('completed-tasks')
1959
+ };
1960
+
1961
+ // Clear columns
1962
+ Object.values(columns).forEach(col => col.innerHTML = '');
1963
+
1964
+ // Render server tasks
1965
+ if (serverState?.tasks) {
1966
+ renderTasksToColumn(serverState.tasks.pending || [], columns.pending, true);
1967
+ renderTasksToColumn(serverState.tasks.inProgress || [], columns['in-progress'], true);
1968
+ renderTasksToColumn(serverState.tasks.review || [], columns.review, true);
1969
+ renderTasksToColumn(serverState.tasks.completed || [], columns.completed, true);
1970
+ }
1971
+
1972
+ // Render local tasks
1973
+ localState.tasks.forEach(task => {
1974
+ const column = columns[task.status];
1975
+ if (column) {
1976
+ renderTaskCard(task, column, false);
1977
+ }
1978
+ });
1979
+
1980
+ // Update counts
1981
+ updateCounts();
1982
+
1983
+ // Setup drag-drop
1984
+ setupDragDrop();
1985
+ }
1986
+
1987
+ function renderTasksToColumn(tasks, column, fromServer) {
1988
+ tasks.forEach(task => renderTaskCard(task, column, fromServer));
1989
+ }
1990
+
1991
+ function renderTaskCard(task, column, fromServer) {
1992
+ const card = document.createElement('div');
1993
+ card.className = 'task-card' + (fromServer ? ' from-server' : '');
1994
+ card.draggable = !fromServer; // Only local tasks are draggable
1995
+ card.dataset.taskId = task.id;
1996
+ card.dataset.fromServer = fromServer;
1997
+
1998
+ const priority = task.priority || 'medium';
1999
+ const type = task.type || 'unknown';
2000
+
2001
+ card.innerHTML = `
2002
+ <div class="task-card-header">
2003
+ <span class="task-id">${task.id || 'local-' + Date.now()}</span>
2004
+ <span class="task-priority ${priority}">${priority}</span>
2005
+ </div>
2006
+ <div class="task-title">${task.title || task.description || 'Untitled'}</div>
2007
+ <div class="task-meta">
2008
+ <span class="task-type">${type}</span>
2009
+ ${task.agent ? `<span>${task.agent}</span>` : ''}
2010
+ </div>
2011
+ `;
2012
+
2013
+ if (!fromServer) {
2014
+ card.addEventListener('dragstart', handleDragStart);
2015
+ card.addEventListener('dragend', handleDragEnd);
2016
+ }
2017
+
2018
+ column.appendChild(card);
2019
+ }
2020
+
2021
+ function updateCounts() {
2022
+ const serverTasks = serverState?.tasks || {};
2023
+ const localByStatus = {
2024
+ pending: localState.tasks.filter(t => t.status === 'pending').length,
2025
+ 'in-progress': localState.tasks.filter(t => t.status === 'in-progress').length,
2026
+ review: localState.tasks.filter(t => t.status === 'review').length,
2027
+ completed: localState.tasks.filter(t => t.status === 'completed').length
2028
+ };
2029
+
2030
+ document.getElementById('pending-count').textContent =
2031
+ (serverTasks.pending?.length || 0) + localByStatus.pending;
2032
+ document.getElementById('in-progress-count').textContent =
2033
+ (serverTasks.inProgress?.length || 0) + localByStatus['in-progress'];
2034
+ document.getElementById('review-count').textContent =
2035
+ (serverTasks.review?.length || 0) + localByStatus.review;
2036
+ document.getElementById('completed-count').textContent =
2037
+ (serverTasks.completed?.length || 0) + localByStatus.completed;
2038
+ }
2039
+
2040
+ // ============================================
2041
+ // Drag and Drop
2042
+ // ============================================
2043
+ let draggedTask = null;
2044
+
2045
+ function handleDragStart(e) {
2046
+ draggedTask = e.target;
2047
+ e.target.classList.add('dragging');
2048
+ e.dataTransfer.effectAllowed = 'move';
2049
+ e.dataTransfer.setData('text/plain', e.target.dataset.taskId);
2050
+ }
2051
+
2052
+ function handleDragEnd(e) {
2053
+ e.target.classList.remove('dragging');
2054
+ document.querySelectorAll('.kanban-tasks').forEach(col => col.classList.remove('drag-over'));
2055
+ draggedTask = null;
2056
+ }
2057
+
2058
+ function setupDragDrop() {
2059
+ document.querySelectorAll('.kanban-tasks').forEach(zone => {
2060
+ zone.addEventListener('dragover', e => {
2061
+ e.preventDefault();
2062
+ e.dataTransfer.dropEffect = 'move';
2063
+ });
2064
+ zone.addEventListener('dragenter', e => {
2065
+ e.preventDefault();
2066
+ zone.classList.add('drag-over');
2067
+ });
2068
+ zone.addEventListener('dragleave', e => {
2069
+ if (!zone.contains(e.relatedTarget)) {
2070
+ zone.classList.remove('drag-over');
2071
+ }
2072
+ });
2073
+ zone.addEventListener('drop', e => {
2074
+ e.preventDefault();
2075
+ zone.classList.remove('drag-over');
2076
+
2077
+ const taskId = e.dataTransfer.getData('text/plain');
2078
+ const newStatus = zone.closest('.kanban-column').dataset.status;
2079
+
2080
+ // Update local task status
2081
+ const task = localState.tasks.find(t => t.id === taskId);
2082
+ if (task && task.status !== newStatus) {
2083
+ task.status = newStatus;
2084
+ saveLocalState();
2085
+ renderKanban();
2086
+ }
2087
+ });
2088
+ });
2089
+ }
2090
+
2091
+ // ============================================
2092
+ // Agents Rendering
2093
+ // ============================================
2094
+ function renderAgents() {
2095
+ const grid = document.getElementById('agents-grid');
2096
+ const agents = serverState?.agents || [];
2097
+
2098
+ if (agents.length === 0) {
2099
+ grid.innerHTML = '<div class="empty-state">No active agents. Agents will appear when Loki Mode is running.</div>';
2100
+ return;
2101
+ }
2102
+
2103
+ grid.innerHTML = agents.map(agent => `
2104
+ <div class="agent-card from-server">
2105
+ <div class="agent-header">
2106
+ <div class="agent-info">
2107
+ <h3>${agent.id || 'unknown'}</h3>
2108
+ <div class="agent-type">${agent.type || 'Agent'}</div>
2109
+ </div>
2110
+ <span class="model-badge ${(agent.model || 'sonnet').toLowerCase()}">${agent.model || 'sonnet'}</span>
2111
+ </div>
2112
+ <div class="agent-work">${agent.work || agent.currentTask || (agent.status === 'idle' ? 'Waiting for tasks...' : 'Working...')}</div>
2113
+ <div class="agent-stats">
2114
+ <span class="agent-stat">
2115
+ <svg width="10" height="10" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
2116
+ ${agent.runtime || '--'}
2117
+ </span>
2118
+ <span class="agent-stat">
2119
+ <svg width="10" height="10" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
2120
+ ${agent.tasks || 0} tasks
2121
+ </span>
2122
+ </div>
2123
+ <div class="agent-status ${agent.status || 'active'}">
2124
+ <span class="status-dot ${agent.status || 'active'}"></span>
2125
+ ${agent.status === 'active' ? 'Active' : agent.status === 'idle' ? 'Idle' : agent.status === 'error' ? 'Error' : 'Completed'}
2126
+ </div>
2127
+ </div>
2128
+ `).join('');
2129
+ }
2130
+
2131
+ // ============================================
2132
+ // System Components
2133
+ // ============================================
2134
+ function updateSystemComponents() {
2135
+ if (!serverState) return;
2136
+
2137
+ // RARV Cycle - supports both string step name and numeric index
2138
+ const rarvStepMap = { 'REASON': 0, 'ACT': 1, 'REFLECT': 2, 'VERIFY': 3 };
2139
+ let rarvStep = 0;
2140
+ if (serverState.rarv) {
2141
+ if (typeof serverState.rarv.currentStep === 'number') {
2142
+ rarvStep = serverState.rarv.currentStep;
2143
+ } else if (typeof serverState.rarv.step === 'string') {
2144
+ rarvStep = rarvStepMap[serverState.rarv.step.toUpperCase()] || 0;
2145
+ }
2146
+ }
2147
+ const rarvIds = ['rarv-reason', 'rarv-act', 'rarv-reflect', 'rarv-verify'];
2148
+ rarvIds.forEach((id, i) => {
2149
+ document.getElementById(id).classList.toggle('active', i === rarvStep);
2150
+ });
2151
+
2152
+ // Memory System
2153
+ const memory = serverState.memory || {};
2154
+ const maxMemory = 100; // Assume max for percentage
2155
+
2156
+ document.getElementById('memory-episodic').textContent = (memory.episodic || 0) + ' entries';
2157
+ document.getElementById('memory-episodic-bar').style.width = Math.min((memory.episodic || 0) / maxMemory * 100, 100) + '%';
2158
+
2159
+ document.getElementById('memory-semantic').textContent = (memory.semantic || 0) + ' patterns';
2160
+ document.getElementById('memory-semantic-bar').style.width = Math.min((memory.semantic || 0) / maxMemory * 100, 100) + '%';
2161
+
2162
+ document.getElementById('memory-procedural').textContent = (memory.procedural || 0) + ' skills';
2163
+ document.getElementById('memory-procedural-bar').style.width = Math.min((memory.procedural || 0) / maxMemory * 100, 100) + '%';
2164
+
2165
+ // Quality Gates - render actual status from server
2166
+ const gates = serverState.qualityGates || {};
2167
+ const gateContainer = document.getElementById('quality-gates');
2168
+ const gateConfigs = [
2169
+ { key: 'staticAnalysis', label: 'Static Analysis' },
2170
+ { key: 'codeReview', label: '3-Reviewer' },
2171
+ { key: 'antiSycophancy', label: 'Anti-Sycophancy' },
2172
+ { key: 'testCoverage', label: 'Test Coverage' },
2173
+ { key: 'securityScan', label: 'Security Scan' },
2174
+ { key: 'performance', label: 'Performance' }
2175
+ ];
2176
+
2177
+ gateContainer.innerHTML = gateConfigs.map(gate => {
2178
+ const status = gates[gate.key] || 'pending';
2179
+ const statusClass = status === 'passed' ? 'pass' :
2180
+ status === 'failed' ? 'fail' : 'pending';
2181
+ const icon = status === 'passed'
2182
+ ? '<polyline points="22 4 12 14.01 9 11.01"/><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/>'
2183
+ : status === 'failed'
2184
+ ? '<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>'
2185
+ : '<circle cx="12" cy="12" r="10"/>';
2186
+ return `<div class="quality-gate"><svg class="gate-icon ${statusClass}" width="14" height="14" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" fill="none">${icon}</svg> ${gate.label}</div>`;
2187
+ }).join('');
2188
+
2189
+ // Update overall quality status
2190
+ const passedCount = Object.values(gates).filter(s => s === 'passed').length;
2191
+ const failedCount = Object.values(gates).filter(s => s === 'failed').length;
2192
+ const statusEl = document.getElementById('quality-status');
2193
+ if (failedCount > 0) {
2194
+ statusEl.textContent = failedCount + ' Failed';
2195
+ statusEl.className = 'system-card-status warning';
2196
+ } else if (passedCount === gateConfigs.length) {
2197
+ statusEl.textContent = 'All Passed';
2198
+ statusEl.className = 'system-card-status healthy';
2199
+ } else {
2200
+ statusEl.textContent = passedCount + '/' + gateConfigs.length;
2201
+ statusEl.className = 'system-card-status healthy';
2202
+ }
2203
+ }
2204
+
2205
+ // ============================================
2206
+ // Task Management
2207
+ // ============================================
2208
+ function openAddTaskModal(initialStatus = 'pending') {
2209
+ document.getElementById('task-initial-status').value = initialStatus;
2210
+ document.getElementById('add-task-modal').classList.add('active');
2211
+ }
2212
+
2213
+ function closeAddTaskModal() {
2214
+ document.getElementById('add-task-modal').classList.remove('active');
2215
+ document.getElementById('task-title').value = '';
2216
+ document.getElementById('task-description').value = '';
2217
+ }
2218
+
2219
+ function addTask(e) {
2220
+ e.preventDefault();
2221
+
2222
+ const newTask = {
2223
+ id: 'local-' + Date.now(),
2224
+ title: document.getElementById('task-title').value,
2225
+ description: document.getElementById('task-description').value,
2226
+ type: document.getElementById('task-type').value,
2227
+ priority: document.getElementById('task-priority').value,
2228
+ status: document.getElementById('task-initial-status').value,
2229
+ createdAt: Date.now(),
2230
+ local: true
2231
+ };
2232
+
2233
+ localState.tasks.push(newTask);
2234
+ saveLocalState();
2235
+ renderKanban();
2236
+ updateStats();
2237
+ closeAddTaskModal();
2238
+ }
2239
+
2240
+ // ============================================
2241
+ // Human Intervention
2242
+ // ============================================
2243
+ function triggerPause() {
2244
+ alert('To pause Loki Mode, create this file:\ntouch .loki/PAUSE\n\nOr press Ctrl+C once in the terminal.');
2245
+ }
2246
+
2247
+ function triggerStop() {
2248
+ alert('To stop Loki Mode, create this file:\ntouch .loki/STOP\n\nOr press Ctrl+C twice in the terminal.');
2249
+ }
2250
+
2251
+ function triggerResume() {
2252
+ alert('To resume Loki Mode, remove the pause file:\nrm .loki/PAUSE\n\nOr use: loki resume');
2253
+ }
2254
+
2255
+ // ============================================
2256
+ // Terminal Output
2257
+ // ============================================
2258
+ const MAX_TERMINAL_LINES = 1000;
2259
+ let terminalLines = [];
2260
+ let autoScroll = true;
2261
+ let lastLogPosition = 0;
2262
+
2263
+ function toggleAutoScroll() {
2264
+ autoScroll = !autoScroll;
2265
+ const btn = document.getElementById('terminal-auto-scroll');
2266
+ btn.classList.toggle('active', autoScroll);
2267
+ if (autoScroll) {
2268
+ scrollTerminalToBottom();
2269
+ }
2270
+ }
2271
+
2272
+ function scrollTerminalToBottom() {
2273
+ const output = document.getElementById('terminal-output');
2274
+ output.scrollTop = output.scrollHeight;
2275
+ }
2276
+
2277
+ function clearTerminal() {
2278
+ terminalLines = [];
2279
+ renderTerminal();
2280
+ }
2281
+
2282
+ function downloadLogs() {
2283
+ const content = terminalLines.map(line =>
2284
+ `[${line.timestamp}] [${line.level.toUpperCase()}] ${line.message}`
2285
+ ).join('\n');
2286
+ const blob = new Blob([content], { type: 'text/plain' });
2287
+ const url = URL.createObjectURL(blob);
2288
+ const a = document.createElement('a');
2289
+ a.href = url;
2290
+ a.download = 'loki-mode-logs-' + Date.now() + '.txt';
2291
+ a.click();
2292
+ URL.revokeObjectURL(url);
2293
+ }
2294
+
2295
+ function parseLogLine(line) {
2296
+ // Parse log format: [TIMESTAMP] [LEVEL] MESSAGE or similar
2297
+ const patterns = [
2298
+ /^\[(\d{2}:\d{2}:\d{2})\]\s*\[(\w+)\]\s*(.*)$/,
2299
+ /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})\s*\[(\w+)\]\s*(.*)$/,
2300
+ /^\[(\w+)\]\s*(.*)$/
2301
+ ];
2302
+
2303
+ for (const pattern of patterns) {
2304
+ const match = line.match(pattern);
2305
+ if (match) {
2306
+ if (match.length === 4) {
2307
+ return { timestamp: match[1], level: match[2].toLowerCase(), message: match[3] };
2308
+ } else if (match.length === 3) {
2309
+ return { timestamp: new Date().toLocaleTimeString(), level: match[1].toLowerCase(), message: match[2] };
2310
+ }
2311
+ }
2312
+ }
2313
+
2314
+ // Default: treat as info
2315
+ return { timestamp: new Date().toLocaleTimeString(), level: 'info', message: line };
2316
+ }
2317
+
2318
+ function getLevelClass(level) {
2319
+ const levelMap = {
2320
+ 'info': 'level-info',
2321
+ 'success': 'level-success',
2322
+ 'warn': 'level-warning',
2323
+ 'warning': 'level-warning',
2324
+ 'error': 'level-error',
2325
+ 'err': 'level-error',
2326
+ 'step': 'level-step',
2327
+ 'agent': 'level-agent',
2328
+ 'debug': 'level-info'
2329
+ };
2330
+ return levelMap[level] || 'level-info';
2331
+ }
2332
+
2333
+ function renderTerminal() {
2334
+ const output = document.getElementById('terminal-output');
2335
+
2336
+ if (terminalLines.length === 0) {
2337
+ output.innerHTML = '<div class="terminal-empty">No log output yet. Terminal will update when Loki Mode is running.</div>';
2338
+ return;
2339
+ }
2340
+
2341
+ output.innerHTML = terminalLines.map(line => `
2342
+ <div class="terminal-line">
2343
+ <span class="timestamp">[${line.timestamp}]</span>
2344
+ <span class="${getLevelClass(line.level)}">[${line.level.toUpperCase()}]</span>
2345
+ <span class="message">${escapeHtml(line.message)}</span>
2346
+ </div>
2347
+ `).join('');
2348
+
2349
+ if (autoScroll) {
2350
+ scrollTerminalToBottom();
2351
+ }
2352
+ }
2353
+
2354
+ function escapeHtml(text) {
2355
+ const div = document.createElement('div');
2356
+ div.textContent = text;
2357
+ return div.innerHTML;
2358
+ }
2359
+
2360
+ async function fetchTerminalLogs() {
2361
+ try {
2362
+ const response = await fetch('../logs/agent.log?t=' + Date.now());
2363
+ if (!response.ok) {
2364
+ return;
2365
+ }
2366
+ const text = await response.text();
2367
+ const lines = text.split('\n').filter(line => line.trim());
2368
+
2369
+ // Only add new lines
2370
+ if (lines.length > terminalLines.length) {
2371
+ const newLines = lines.slice(terminalLines.length);
2372
+ newLines.forEach(line => {
2373
+ terminalLines.push(parseLogLine(line));
2374
+ });
2375
+
2376
+ // Limit memory growth - keep only the last MAX_TERMINAL_LINES
2377
+ if (terminalLines.length > MAX_TERMINAL_LINES) {
2378
+ terminalLines = terminalLines.slice(-MAX_TERMINAL_LINES);
2379
+ }
2380
+
2381
+ renderTerminal();
2382
+ }
2383
+ } catch (error) {
2384
+ // Silently fail - log file may not exist yet
2385
+ }
2386
+ }
2387
+
2388
+ // ============================================
2389
+ // GitHub Import
2390
+ // ============================================
2391
+ let githubPreviewIssues = [];
2392
+
2393
+ // Sanitize shell arguments to prevent command injection
2394
+ function sanitizeShellArg(input) {
2395
+ if (!input) return '';
2396
+ // Remove dangerous shell metacharacters
2397
+ return input.replace(/[`$"\\!;&|><(){}[\]\n\r]/g, '').trim();
2398
+ }
2399
+
2400
+ // Validate repository format (owner/repo)
2401
+ function validateRepoFormat(repo) {
2402
+ if (!repo) return true; // Empty is valid (auto-detect)
2403
+ return /^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(repo);
2404
+ }
2405
+
2406
+ function openGitHubModal() {
2407
+ document.getElementById('github-modal').classList.add('active');
2408
+ document.getElementById('github-preview').style.display = 'none';
2409
+ githubPreviewIssues = [];
2410
+ }
2411
+
2412
+ function closeGitHubModal() {
2413
+ document.getElementById('github-modal').classList.remove('active');
2414
+ document.getElementById('github-repo').value = '';
2415
+ document.getElementById('github-labels').value = '';
2416
+ document.getElementById('github-milestone').value = '';
2417
+ document.getElementById('github-assignee').value = '';
2418
+ document.getElementById('github-preview').style.display = 'none';
2419
+ githubPreviewIssues = [];
2420
+ }
2421
+
2422
+ function buildGitHubCommand() {
2423
+ const repo = sanitizeShellArg(document.getElementById('github-repo').value);
2424
+ const labels = sanitizeShellArg(document.getElementById('github-labels').value);
2425
+ const milestone = sanitizeShellArg(document.getElementById('github-milestone').value);
2426
+ const assignee = sanitizeShellArg(document.getElementById('github-assignee').value);
2427
+ const limitVal = parseInt(document.getElementById('github-limit').value, 10);
2428
+ const limit = Math.max(1, Math.min(100, isNaN(limitVal) ? 20 : limitVal));
2429
+ const state = document.getElementById('github-state').value;
2430
+
2431
+ // Validate repo format if provided
2432
+ if (repo && !validateRepoFormat(repo)) {
2433
+ return { error: 'Invalid repository format. Use: owner/repo' };
2434
+ }
2435
+
2436
+ let cmd = 'gh issue list --json number,title,body,labels,assignees,milestone --state ' + state + ' --limit ' + limit;
2437
+
2438
+ if (repo) {
2439
+ cmd += ' --repo ' + repo;
2440
+ }
2441
+ if (labels) {
2442
+ labels.split(',').forEach(label => {
2443
+ const sanitizedLabel = sanitizeShellArg(label);
2444
+ if (sanitizedLabel) {
2445
+ cmd += ' --label "' + sanitizedLabel + '"';
2446
+ }
2447
+ });
2448
+ }
2449
+ if (milestone) {
2450
+ cmd += ' --milestone "' + milestone + '"';
2451
+ }
2452
+ if (assignee) {
2453
+ cmd += ' --assignee "' + assignee + '"';
2454
+ }
2455
+
2456
+ return cmd;
2457
+ }
2458
+
2459
+ async function previewGitHubIssues() {
2460
+ const previewDiv = document.getElementById('github-preview');
2461
+ const listDiv = document.getElementById('github-preview-list');
2462
+ const countSpan = document.getElementById('github-preview-count');
2463
+
2464
+ // Show loading
2465
+ previewDiv.style.display = 'block';
2466
+ listDiv.innerHTML = '<div style="color: var(--text-muted);">Loading issues... (requires gh CLI)</div>';
2467
+
2468
+ // In a real implementation, this would call the backend
2469
+ // For the dashboard, we show instructions
2470
+ const cmd = buildGitHubCommand();
2471
+
2472
+ // Check for validation error
2473
+ if (cmd && cmd.error) {
2474
+ listDiv.innerHTML = `<div style="color: var(--red);">${escapeHtml(cmd.error)}</div>`;
2475
+ countSpan.textContent = '0';
2476
+ return;
2477
+ }
2478
+
2479
+ listDiv.innerHTML = `
2480
+ <div style="color: var(--text-muted); font-size: 11px;">
2481
+ <p style="margin-bottom: 8px;">To preview issues, run this command in terminal:</p>
2482
+ <code style="display: block; background: #2a2a2d; padding: 8px; border-radius: 4px; word-break: break-all; color: #e5e5e5;">${escapeHtml(cmd)}</code>
2483
+ <p style="margin-top: 8px;">Or use the CLI: <code>loki import --preview</code></p>
2484
+ </div>
2485
+ `;
2486
+ countSpan.textContent = '?';
2487
+ }
2488
+
2489
+ function importGitHubIssues(e) {
2490
+ e.preventDefault();
2491
+
2492
+ // Sanitize all inputs to prevent command injection
2493
+ const repo = sanitizeShellArg(document.getElementById('github-repo').value);
2494
+ const labels = sanitizeShellArg(document.getElementById('github-labels').value);
2495
+ const milestone = sanitizeShellArg(document.getElementById('github-milestone').value);
2496
+ const assignee = sanitizeShellArg(document.getElementById('github-assignee').value);
2497
+ const limitVal = parseInt(document.getElementById('github-limit').value, 10);
2498
+ const limit = Math.max(1, Math.min(100, isNaN(limitVal) ? 20 : limitVal));
2499
+ const state = document.getElementById('github-state').value;
2500
+
2501
+ // Validate repo format
2502
+ if (repo && !validateRepoFormat(repo)) {
2503
+ alert('Invalid repository format. Use: owner/repo');
2504
+ return;
2505
+ }
2506
+
2507
+ // Build the import command for the user
2508
+ let cliCmd = 'loki import';
2509
+ if (repo) cliCmd += ' --repo ' + repo;
2510
+ if (labels) cliCmd += ' --labels "' + labels + '"';
2511
+ if (milestone) cliCmd += ' --milestone "' + milestone + '"';
2512
+ if (assignee) cliCmd += ' --assignee "' + assignee + '"';
2513
+ if (limit !== 20) cliCmd += ' --limit ' + limit;
2514
+ if (state !== 'open') cliCmd += ' --state ' + state;
2515
+
2516
+ // Also build env vars for run.sh
2517
+ let envSetup = 'export LOKI_GITHUB_IMPORT=true';
2518
+ if (repo) envSetup += '\nexport LOKI_GITHUB_REPO="' + repo + '"';
2519
+ if (labels) envSetup += '\nexport LOKI_GITHUB_LABELS="' + labels + '"';
2520
+ if (milestone) envSetup += '\nexport LOKI_GITHUB_MILESTONE="' + milestone + '"';
2521
+ if (assignee) envSetup += '\nexport LOKI_GITHUB_ASSIGNEE="' + assignee + '"';
2522
+ if (limit !== 20) envSetup += '\nexport LOKI_GITHUB_LIMIT=' + limit;
2523
+
2524
+ const message = `To import GitHub issues, run one of these commands:
2525
+
2526
+ Option 1 - Using CLI:
2527
+ ${cliCmd}
2528
+
2529
+ Option 2 - Using environment variables:
2530
+ ${envSetup}
2531
+
2532
+ Then start Loki Mode and issues will be imported automatically.`;
2533
+
2534
+ alert(message);
2535
+ closeGitHubModal();
2536
+ }
2537
+
2538
+ // ============================================
2539
+ // Export
2540
+ // ============================================
2541
+ function exportTasks() {
2542
+ const exportData = {
2543
+ serverState: serverState,
2544
+ localState: localState,
2545
+ exportedAt: new Date().toISOString()
2546
+ };
2547
+ const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
2548
+ const url = URL.createObjectURL(blob);
2549
+ const a = document.createElement('a');
2550
+ a.href = url;
2551
+ a.download = 'loki-mode-export-' + Date.now() + '.json';
2552
+ a.click();
2553
+ URL.revokeObjectURL(url);
2554
+ }
2555
+
2556
+ // ============================================
2557
+ // Initialize
2558
+ // ============================================
2559
+ document.addEventListener('DOMContentLoaded', () => {
2560
+ initTheme();
2561
+ loadLocalState();
2562
+ startPolling();
2563
+
2564
+ // Theme toggle (both desktop and mobile)
2565
+ document.getElementById('theme-toggle').addEventListener('click', toggleTheme);
2566
+ document.getElementById('mobile-theme-toggle').addEventListener('click', toggleTheme);
2567
+
2568
+ // Sidebar navigation - scroll to sections
2569
+ document.querySelectorAll('.sidebar-item[data-section]').forEach(item => {
2570
+ item.addEventListener('click', () => {
2571
+ const section = item.dataset.section;
2572
+ const targetElement = document.getElementById('section-' + section);
2573
+
2574
+ if (targetElement) {
2575
+ targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
2576
+ // Update active state
2577
+ document.querySelectorAll('.sidebar-item').forEach(i => i.classList.remove('active'));
2578
+ item.classList.add('active');
2579
+ }
2580
+ });
2581
+ });
2582
+
2583
+ // Scroll spy - update sidebar active state based on scroll position
2584
+ const sections = ['kanban', 'agents', 'system', 'terminal'];
2585
+ const mainContent = document.querySelector('.main-content');
2586
+ if (mainContent) {
2587
+ mainContent.addEventListener('scroll', () => {
2588
+ let currentSection = 'kanban';
2589
+ sections.forEach(section => {
2590
+ const el = document.getElementById('section-' + section);
2591
+ if (el && el.getBoundingClientRect().top < 200) {
2592
+ currentSection = section;
2593
+ }
2594
+ });
2595
+ document.querySelectorAll('.sidebar-item[data-section]').forEach(item => {
2596
+ item.classList.toggle('active', item.dataset.section === currentSection);
2597
+ });
2598
+ });
2599
+ }
2600
+
2601
+ // Modal backdrop click
2602
+ document.getElementById('add-task-modal').addEventListener('click', e => {
2603
+ if (e.target.classList.contains('modal-overlay')) {
2604
+ closeAddTaskModal();
2605
+ }
2606
+ });
2607
+
2608
+ document.getElementById('github-modal').addEventListener('click', e => {
2609
+ if (e.target.classList.contains('modal-overlay')) {
2610
+ closeGitHubModal();
2611
+ }
2612
+ });
2613
+
2614
+ // Keyboard shortcuts
2615
+ document.addEventListener('keydown', e => {
2616
+ if (e.key === 'Escape') {
2617
+ closeAddTaskModal();
2618
+ closeGitHubModal();
2619
+ }
2620
+ if (e.key === 'n' && (e.metaKey || e.ctrlKey)) {
2621
+ e.preventDefault();
2622
+ openAddTaskModal();
2623
+ }
2624
+ });
2625
+
2626
+ // Initial render with local data
2627
+ renderKanban();
2628
+ });
2629
+
2630
+ // Cleanup on page unload
2631
+ window.addEventListener('beforeunload', stopPolling);
2632
+ </script>
2633
+ </body>
2634
+ </html>