@auto-engineer/cli 0.7.5 → 0.7.7

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 (41) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +10 -0
  3. package/dist/src/dsl/index.d.ts +28 -0
  4. package/dist/src/dsl/index.d.ts.map +1 -0
  5. package/dist/src/dsl/index.js +113 -0
  6. package/dist/src/dsl/index.js.map +1 -0
  7. package/dist/src/dsl/types.d.ts +19 -0
  8. package/dist/src/dsl/types.d.ts.map +1 -0
  9. package/dist/src/dsl/types.js +2 -0
  10. package/dist/src/dsl/types.js.map +1 -0
  11. package/dist/src/index.d.ts +1 -1
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/index.js +60 -0
  14. package/dist/src/index.js.map +1 -1
  15. package/dist/src/plugin-loader.d.ts +2 -0
  16. package/dist/src/plugin-loader.d.ts.map +1 -1
  17. package/dist/src/plugin-loader.js +11 -2
  18. package/dist/src/plugin-loader.js.map +1 -1
  19. package/dist/src/server/config-loader.d.ts +40 -0
  20. package/dist/src/server/config-loader.d.ts.map +1 -0
  21. package/dist/src/server/config-loader.js +141 -0
  22. package/dist/src/server/config-loader.js.map +1 -0
  23. package/dist/src/server/dashboard copy.html +1187 -0
  24. package/dist/src/server/dashboard-old.html +1187 -0
  25. package/dist/src/server/dashboard.html +1480 -0
  26. package/dist/src/server/file-syncer.d.ts +21 -0
  27. package/dist/src/server/file-syncer.d.ts.map +1 -0
  28. package/dist/src/server/file-syncer.js +101 -0
  29. package/dist/src/server/file-syncer.js.map +1 -0
  30. package/dist/src/server/logo-dark.svg +1 -0
  31. package/dist/src/server/logo-light.svg +24 -0
  32. package/dist/src/server/server.d.ts +88 -0
  33. package/dist/src/server/server.d.ts.map +1 -0
  34. package/dist/src/server/server.js +411 -0
  35. package/dist/src/server/server.js.map +1 -0
  36. package/dist/src/server/state-manager.d.ts +24 -0
  37. package/dist/src/server/state-manager.d.ts.map +1 -0
  38. package/dist/src/server/state-manager.js +56 -0
  39. package/dist/src/server/state-manager.js.map +1 -0
  40. package/dist/tsconfig.tsbuildinfo +1 -1
  41. package/package.json +29 -22
@@ -0,0 +1,1187 @@
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
+ <title>Auto Engineer</title>
7
+ <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
9
+
10
+ * {
11
+ margin: 0;
12
+ padding: 0;
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ :root {
17
+ /* Auto Brand Colors */
18
+ --brand-red: #ec3f4a;
19
+ --brand-orange: #ff8a1d;
20
+ --brand-green: #5ec72d;
21
+ --brand-blue: #42c3f7;
22
+ --brand-primary: #0057dd;
23
+ }
24
+
25
+ /* Dark theme (default) */
26
+ [data-theme="dark"] {
27
+ --bg-primary: #1a1a1a;
28
+ --bg-secondary: #18181b;
29
+ --bg-tertiary: #27272a;
30
+ --bg-hover: #333337;
31
+ --border: #3a3a3d;
32
+ --border-light: #4a4a4d;
33
+ --text-primary: #ffffff;
34
+ --text-secondary: #a1a1aa;
35
+ --text-tertiary: #71717a;
36
+ --accent: #0057dd;
37
+ --accent-hover: #0046b5;
38
+ --success: #5ec72d;
39
+ --error: #ec3f4a;
40
+ --warning: #ff8a1d;
41
+ --code-bg: #0d0e11;
42
+ }
43
+
44
+ /* Light theme */
45
+ [data-theme="light"] {
46
+ --bg-primary: #fafafa;
47
+ --bg-secondary: #ffffff;
48
+ --bg-tertiary: #f4f4f5;
49
+ --bg-hover: #e4e4e7;
50
+ --border: #e4e4e7;
51
+ --border-light: #d4d4d8;
52
+ --text-primary: #18181b;
53
+ --text-secondary: #52525b;
54
+ --text-tertiary: #71717a;
55
+ --accent: #0057dd;
56
+ --accent-hover: #0046b5;
57
+ --success: #5ec72d;
58
+ --error: #ec3f4a;
59
+ --warning: #ff8a1d;
60
+ --code-bg: #f4f4f5;
61
+ }
62
+
63
+ body {
64
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
65
+ background: var(--bg-primary);
66
+ color: var(--text-primary);
67
+ min-height: 100vh;
68
+ font-size: 14px;
69
+ line-height: 1.6;
70
+ -webkit-font-smoothing: antialiased;
71
+ -moz-osx-font-smoothing: grayscale;
72
+ transition: background-color 0.3s ease, color 0.3s ease;
73
+ }
74
+
75
+ /* Tab color indicators */
76
+ .tab-indicator {
77
+ display: inline-block;
78
+ width: 8px;
79
+ height: 8px;
80
+ border-radius: 2px;
81
+ margin-right: 8px;
82
+ }
83
+
84
+ .tab-indicator.command { background: var(--brand-primary); }
85
+ .tab-indicator.event { background: var(--brand-orange); }
86
+ .tab-indicator.state { background: var(--brand-green); }
87
+
88
+ .header {
89
+ background: var(--bg-secondary);
90
+ border-bottom: 1px solid var(--border);
91
+ padding: 0 24px;
92
+ height: 56px;
93
+ display: flex;
94
+ align-items: center;
95
+ justify-content: space-between;
96
+ position: relative;
97
+ transition: background-color 0.3s ease;
98
+ }
99
+
100
+ .header-logo {
101
+ display: flex;
102
+ align-items: center;
103
+ gap: 12px;
104
+ }
105
+
106
+ .logo-mark {
107
+ width: 40px;
108
+ height: 40px;
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ }
113
+
114
+ .logo-mark svg {
115
+ width: 40px;
116
+ height: 40px;
117
+ }
118
+
119
+ .logo-text {
120
+ font-size: 18px;
121
+ font-weight: 600;
122
+ color: var(--text-primary);
123
+ letter-spacing: -0.02em;
124
+ }
125
+
126
+ .logo-subtitle {
127
+ color: var(--text-tertiary);
128
+ font-size: 12px;
129
+ font-weight: 400;
130
+ margin-left: 8px;
131
+ }
132
+
133
+ .header-controls {
134
+ display: flex;
135
+ align-items: center;
136
+ gap: 24px;
137
+ }
138
+
139
+ .header-stats {
140
+ display: flex;
141
+ gap: 32px;
142
+ align-items: center;
143
+ }
144
+
145
+ .stat {
146
+ display: flex;
147
+ align-items: center;
148
+ gap: 8px;
149
+ }
150
+
151
+ .stat-value {
152
+ color: var(--text-primary);
153
+ font-weight: 600;
154
+ font-size: 16px;
155
+ }
156
+
157
+ .stat-label {
158
+ color: var(--text-tertiary);
159
+ font-size: 12px;
160
+ text-transform: uppercase;
161
+ letter-spacing: 0.05em;
162
+ }
163
+
164
+ /* Theme switcher */
165
+ .theme-switcher {
166
+ display: flex;
167
+ background: var(--bg-tertiary);
168
+ border: 1px solid var(--border);
169
+ border-radius: 8px;
170
+ padding: 2px;
171
+ gap: 2px;
172
+ }
173
+
174
+ .theme-option {
175
+ padding: 6px 12px;
176
+ border: none;
177
+ background: transparent;
178
+ color: var(--text-tertiary);
179
+ font-size: 12px;
180
+ font-weight: 500;
181
+ cursor: pointer;
182
+ border-radius: 6px;
183
+ transition: all 0.2s ease;
184
+ display: flex;
185
+ align-items: center;
186
+ gap: 6px;
187
+ }
188
+
189
+ .theme-option:hover {
190
+ color: var(--text-secondary);
191
+ }
192
+
193
+ .theme-option.active {
194
+ background: var(--bg-secondary);
195
+ color: var(--text-primary);
196
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
197
+ }
198
+
199
+ .theme-icon {
200
+ width: 14px;
201
+ height: 14px;
202
+ }
203
+
204
+ .container {
205
+ display: flex;
206
+ height: calc(100vh - 56px);
207
+ }
208
+
209
+ .sidebar {
210
+ width: 320px;
211
+ background: var(--bg-secondary);
212
+ border-right: 1px solid var(--border);
213
+ overflow-y: auto;
214
+ transition: background-color 0.3s ease;
215
+ }
216
+
217
+ .section {
218
+ border-bottom: 1px solid var(--border);
219
+ }
220
+
221
+ .section-header {
222
+ padding: 14px 20px;
223
+ display: flex;
224
+ align-items: center;
225
+ justify-content: space-between;
226
+ background: var(--bg-tertiary);
227
+ border-bottom: 1px solid var(--border);
228
+ transition: background-color 0.3s ease;
229
+ }
230
+
231
+ .section-title {
232
+ font-size: 11px;
233
+ font-weight: 600;
234
+ text-transform: uppercase;
235
+ letter-spacing: 0.08em;
236
+ color: var(--text-secondary);
237
+ }
238
+
239
+ .section-count {
240
+ font-size: 11px;
241
+ padding: 3px 8px;
242
+ background: var(--bg-primary);
243
+ border-radius: 12px;
244
+ color: var(--text-tertiary);
245
+ font-weight: 500;
246
+ transition: background-color 0.3s ease;
247
+ }
248
+
249
+ .item-list {
250
+ max-height: 240px;
251
+ overflow-y: auto;
252
+ }
253
+
254
+ .item {
255
+ padding: 10px 20px;
256
+ border-bottom: 1px solid var(--border);
257
+ font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
258
+ font-size: 12px;
259
+ color: var(--text-secondary);
260
+ transition: all 0.15s ease;
261
+ position: relative;
262
+ }
263
+
264
+ .item::before {
265
+ content: '';
266
+ position: absolute;
267
+ left: 0;
268
+ top: 0;
269
+ bottom: 0;
270
+ width: 3px;
271
+ background: transparent;
272
+ transition: background 0.15s ease;
273
+ }
274
+
275
+ .item:hover {
276
+ background: var(--bg-hover);
277
+ color: var(--text-primary);
278
+ }
279
+
280
+ .item:hover::before {
281
+ background: var(--accent);
282
+ }
283
+
284
+ .item:last-child {
285
+ border-bottom: none;
286
+ }
287
+
288
+ .empty {
289
+ padding: 32px;
290
+ text-align: center;
291
+ color: var(--text-tertiary);
292
+ font-size: 12px;
293
+ }
294
+
295
+ .main {
296
+ flex: 1;
297
+ display: flex;
298
+ flex-direction: column;
299
+ overflow: hidden;
300
+ }
301
+
302
+ .tabs {
303
+ background: var(--bg-secondary);
304
+ border-bottom: 1px solid var(--border);
305
+ display: flex;
306
+ padding: 0 24px;
307
+ transition: background-color 0.3s ease;
308
+ }
309
+
310
+ .tab {
311
+ padding: 14px 20px;
312
+ color: var(--text-tertiary);
313
+ cursor: pointer;
314
+ border-bottom: 3px solid transparent;
315
+ transition: all 0.15s ease;
316
+ font-size: 13px;
317
+ font-weight: 500;
318
+ position: relative;
319
+ }
320
+
321
+ .tab:hover {
322
+ color: var(--text-secondary);
323
+ }
324
+
325
+ .tab.active {
326
+ color: var(--text-primary);
327
+ border-bottom-color: var(--accent);
328
+ }
329
+
330
+ .tab-content {
331
+ flex: 1;
332
+ padding: 32px;
333
+ overflow-y: auto;
334
+ display: none;
335
+ background: var(--bg-primary);
336
+ transition: background-color 0.3s ease;
337
+ }
338
+
339
+ .tab-content.active {
340
+ display: block;
341
+ }
342
+
343
+ .form-group {
344
+ margin-bottom: 24px;
345
+ }
346
+
347
+ .form-group label {
348
+ display: block;
349
+ margin-bottom: 8px;
350
+ color: var(--text-secondary);
351
+ font-size: 11px;
352
+ font-weight: 600;
353
+ text-transform: uppercase;
354
+ letter-spacing: 0.08em;
355
+ }
356
+
357
+ .input, .textarea {
358
+ width: 100%;
359
+ background: var(--bg-tertiary);
360
+ border: 1px solid var(--border);
361
+ border-radius: 8px;
362
+ padding: 10px 14px;
363
+ color: var(--text-primary);
364
+ font-size: 13px;
365
+ transition: all 0.15s ease;
366
+ }
367
+
368
+ .input:focus, .textarea:focus {
369
+ outline: none;
370
+ border-color: var(--accent);
371
+ background: var(--bg-secondary);
372
+ box-shadow: 0 0 0 3px rgba(0, 87, 221, 0.1);
373
+ }
374
+
375
+ .textarea {
376
+ font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
377
+ font-size: 12px;
378
+ min-height: 200px;
379
+ resize: vertical;
380
+ }
381
+
382
+ .button {
383
+ background: var(--accent);
384
+ color: white;
385
+ border: none;
386
+ padding: 10px 20px;
387
+ border-radius: 8px;
388
+ font-size: 13px;
389
+ font-weight: 600;
390
+ cursor: pointer;
391
+ transition: all 0.15s ease;
392
+ display: inline-flex;
393
+ align-items: center;
394
+ gap: 8px;
395
+ text-transform: uppercase;
396
+ letter-spacing: 0.05em;
397
+ }
398
+
399
+ .button:hover {
400
+ background: var(--accent-hover);
401
+ transform: translateY(-1px);
402
+ box-shadow: 0 4px 12px rgba(0, 87, 221, 0.3);
403
+ }
404
+
405
+ .button:active {
406
+ transform: translateY(0);
407
+ }
408
+
409
+ .button-secondary {
410
+ background: var(--bg-tertiary);
411
+ color: var(--text-primary);
412
+ }
413
+
414
+ .button-secondary:hover {
415
+ background: var(--bg-hover);
416
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
417
+ }
418
+
419
+ .code-block {
420
+ background: var(--code-bg);
421
+ border: 1px solid var(--border);
422
+ border-radius: 8px;
423
+ padding: 20px;
424
+ font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
425
+ font-size: 12px;
426
+ line-height: 1.6;
427
+ overflow-x: auto;
428
+ white-space: pre-wrap;
429
+ word-break: break-word;
430
+ transition: background-color 0.3s ease;
431
+ }
432
+
433
+ .response-header {
434
+ display: flex;
435
+ align-items: center;
436
+ justify-content: space-between;
437
+ margin-bottom: 16px;
438
+ }
439
+
440
+ .response-status {
441
+ display: flex;
442
+ align-items: center;
443
+ gap: 8px;
444
+ font-size: 12px;
445
+ font-weight: 600;
446
+ text-transform: uppercase;
447
+ letter-spacing: 0.05em;
448
+ }
449
+
450
+ .status-dot {
451
+ width: 8px;
452
+ height: 8px;
453
+ border-radius: 50%;
454
+ background: var(--text-tertiary);
455
+ }
456
+
457
+ .status-dot.success {
458
+ background: var(--success);
459
+ box-shadow: 0 0 8px rgba(94, 199, 45, 0.5);
460
+ }
461
+
462
+ .status-dot.error {
463
+ background: var(--error);
464
+ box-shadow: 0 0 8px rgba(236, 63, 74, 0.5);
465
+ }
466
+
467
+ .response-time {
468
+ color: var(--text-tertiary);
469
+ font-size: 11px;
470
+ }
471
+
472
+ .state-controls {
473
+ display: flex;
474
+ justify-content: space-between;
475
+ align-items: center;
476
+ margin-bottom: 20px;
477
+ }
478
+
479
+ .search-box {
480
+ flex: 1;
481
+ max-width: 300px;
482
+ position: relative;
483
+ }
484
+
485
+ .search-input {
486
+ width: 100%;
487
+ padding: 8px 14px 8px 36px;
488
+ background: var(--bg-tertiary);
489
+ border: 1px solid var(--border);
490
+ border-radius: 8px;
491
+ color: var(--text-primary);
492
+ font-size: 12px;
493
+ transition: all 0.15s ease;
494
+ }
495
+
496
+ .search-icon {
497
+ position: absolute;
498
+ left: 12px;
499
+ top: 50%;
500
+ transform: translateY(-50%);
501
+ color: var(--text-tertiary);
502
+ font-size: 14px;
503
+ }
504
+
505
+ .divider {
506
+ height: 1px;
507
+ background: var(--border);
508
+ margin: 24px 0;
509
+ }
510
+
511
+ .keyboard-shortcut {
512
+ display: inline-flex;
513
+ align-items: center;
514
+ gap: 2px;
515
+ padding: 2px 6px;
516
+ background: var(--bg-tertiary);
517
+ border: 1px solid var(--border);
518
+ border-radius: 4px;
519
+ font-size: 10px;
520
+ color: var(--text-tertiary);
521
+ font-family: 'SF Mono', monospace;
522
+ }
523
+
524
+ .events-container {
525
+ max-height: calc(100vh - 200px);
526
+ overflow-y: auto;
527
+ }
528
+
529
+ .event-item {
530
+ background: var(--bg-tertiary);
531
+ border: 1px solid var(--border);
532
+ border-radius: 8px;
533
+ padding: 16px;
534
+ margin-bottom: 12px;
535
+ transition: all 0.15s ease;
536
+ position: relative;
537
+ overflow: hidden;
538
+ }
539
+
540
+ .event-item::before {
541
+ content: '';
542
+ position: absolute;
543
+ left: 0;
544
+ top: 0;
545
+ bottom: 0;
546
+ width: 4px;
547
+ background: linear-gradient(180deg, var(--brand-red), var(--brand-orange), var(--brand-green), var(--brand-blue));
548
+ opacity: 0;
549
+ transition: opacity 0.15s ease;
550
+ }
551
+
552
+ .event-item:hover {
553
+ background: var(--bg-hover);
554
+ border-color: var(--border-light);
555
+ transform: translateX(2px);
556
+ }
557
+
558
+ .event-item:hover::before {
559
+ opacity: 1;
560
+ }
561
+
562
+ .event-header {
563
+ display: flex;
564
+ justify-content: space-between;
565
+ align-items: center;
566
+ margin-bottom: 12px;
567
+ }
568
+
569
+ .event-type {
570
+ font-weight: 600;
571
+ color: var(--accent);
572
+ font-size: 13px;
573
+ }
574
+
575
+ .event-time {
576
+ color: var(--text-tertiary);
577
+ font-size: 11px;
578
+ }
579
+
580
+ .event-data {
581
+ background: var(--code-bg);
582
+ border: 1px solid var(--border);
583
+ border-radius: 6px;
584
+ padding: 12px;
585
+ font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
586
+ font-size: 11px;
587
+ color: var(--text-secondary);
588
+ white-space: pre-wrap;
589
+ word-break: break-word;
590
+ max-height: 150px;
591
+ overflow-y: auto;
592
+ transition: background-color 0.3s ease;
593
+ }
594
+
595
+ /* WebSocket status indicator */
596
+ .ws-status {
597
+ position: fixed;
598
+ bottom: 24px;
599
+ right: 24px;
600
+ padding: 10px 16px;
601
+ background: var(--bg-tertiary);
602
+ border: 1px solid var(--border);
603
+ border-radius: 8px;
604
+ display: flex;
605
+ align-items: center;
606
+ gap: 10px;
607
+ font-size: 12px;
608
+ color: var(--text-secondary);
609
+ backdrop-filter: blur(10px);
610
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
611
+ transition: background-color 0.3s ease;
612
+ }
613
+
614
+ .ws-dot {
615
+ width: 8px;
616
+ height: 8px;
617
+ border-radius: 50%;
618
+ background: var(--text-tertiary);
619
+ animation: pulse 2s infinite;
620
+ }
621
+
622
+ .ws-dot.connected {
623
+ background: var(--success);
624
+ box-shadow: 0 0 8px rgba(94, 199, 45, 0.5);
625
+ }
626
+
627
+ @keyframes pulse {
628
+ 0%, 100% { opacity: 1; }
629
+ 50% { opacity: 0.5; }
630
+ }
631
+
632
+ /* Scrollbar styling */
633
+ ::-webkit-scrollbar {
634
+ width: 10px;
635
+ height: 10px;
636
+ }
637
+
638
+ ::-webkit-scrollbar-track {
639
+ background: var(--bg-secondary);
640
+ }
641
+
642
+ ::-webkit-scrollbar-thumb {
643
+ background: var(--border-light);
644
+ border-radius: 5px;
645
+ }
646
+
647
+ ::-webkit-scrollbar-thumb:hover {
648
+ background: var(--text-tertiary);
649
+ }
650
+
651
+ @media (max-width: 768px) {
652
+ .sidebar {
653
+ display: none;
654
+ }
655
+
656
+ .header-stats {
657
+ display: none;
658
+ }
659
+
660
+ .theme-switcher span {
661
+ display: none;
662
+ }
663
+ }
664
+ </style>
665
+ </head>
666
+ <body data-theme="dark">
667
+ <div class="header">
668
+ <div class="header-logo">
669
+ <div class="logo-mark" id="logoMark">
670
+ </div>
671
+ <div>
672
+ <span class="logo-text">auto engineer</span>
673
+ </div>
674
+ </div>
675
+ <div class="header-controls">
676
+ <div class="header-stats">
677
+ <div class="stat">
678
+ <span class="stat-value" id="handlerCount">0</span>
679
+ <span class="stat-label">handlers</span>
680
+ </div>
681
+ <div class="stat">
682
+ <span class="stat-value" id="foldCount">0</span>
683
+ <span class="stat-label">folds</span>
684
+ </div>
685
+ <div class="stat">
686
+ <span class="stat-value" id="commandHandlerCount">0</span>
687
+ <span class="stat-label">commands</span>
688
+ </div>
689
+ </div>
690
+ <div class="theme-switcher">
691
+ <button class="theme-option" data-theme="light" onclick="setTheme('light')">
692
+ <svg class="theme-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
693
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
694
+ </svg>
695
+ <span>Light</span>
696
+ </button>
697
+ <button class="theme-option active" data-theme="dark" onclick="setTheme('dark')">
698
+ <svg class="theme-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
699
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
700
+ </svg>
701
+ <span>Dark</span>
702
+ </button>
703
+ <button class="theme-option" data-theme="system" onclick="setTheme('system')">
704
+ <svg class="theme-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
705
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
706
+ </svg>
707
+ <span>System</span>
708
+ </button>
709
+ </div>
710
+ </div>
711
+ </div>
712
+
713
+ <div class="container">
714
+ <div class="sidebar">
715
+ <div class="section">
716
+ <div class="section-header">
717
+ <span class="section-title">Event Handlers</span>
718
+ <span class="section-count" id="eventHandlerCount">0</span>
719
+ </div>
720
+ <div class="item-list" id="eventHandlers">
721
+ <div class="empty">No handlers registered</div>
722
+ </div>
723
+ </div>
724
+
725
+ <div class="section">
726
+ <div class="section-header">
727
+ <span class="section-title">Fold Functions</span>
728
+ <span class="section-count" id="foldFunctionCount">0</span>
729
+ </div>
730
+ <div class="item-list" id="foldFunctions">
731
+ <div class="empty">No folds registered</div>
732
+ </div>
733
+ </div>
734
+
735
+ <div class="section">
736
+ <div class="section-header">
737
+ <span class="section-title">Command Handlers</span>
738
+ <span class="section-count" id="commandHandlerCountSidebar">0</span>
739
+ </div>
740
+ <div class="item-list" id="commandHandlers">
741
+ <div class="empty">No commands registered</div>
742
+ </div>
743
+ </div>
744
+ </div>
745
+
746
+ <div class="main">
747
+ <div class="tabs">
748
+ <div class="tab active" onclick="switchTab('command')"><span class="tab-indicator command"></span>Command</div>
749
+ <div class="tab" onclick="switchTab('state')"><span class="tab-indicator state"></span>State</div>
750
+ <div class="tab" onclick="switchTab('events')"><span class="tab-indicator event"></span>Events</div>
751
+ </div>
752
+
753
+ <div class="tab-content active" id="command-tab">
754
+ <form id="commandForm">
755
+ <div class="form-group">
756
+ <label for="commandType">Command Type</label>
757
+ <input type="text" class="input" id="commandType" placeholder="CreateUser" required>
758
+ </div>
759
+
760
+ <div class="form-group">
761
+ <label for="commandData">Data</label>
762
+ <textarea class="textarea" id="commandData" placeholder='{"name": "John Doe", "email": "john@example.com"}' required>{}</textarea>
763
+ </div>
764
+
765
+ <div class="form-group">
766
+ <label for="requestId">Request ID <span style="color: var(--text-tertiary); font-weight: normal;">(optional)</span></label>
767
+ <input type="text" class="input" id="requestId" placeholder="Auto-generated if empty">
768
+ </div>
769
+
770
+ <button type="submit" class="button">
771
+ Send Command
772
+ <span class="keyboard-shortcut">⌘ ↵</span>
773
+ </button>
774
+ </form>
775
+
776
+ <div class="divider"></div>
777
+
778
+ <div id="responseSection" style="display: none;">
779
+ <div class="response-header">
780
+ <div class="response-status">
781
+ <div class="status-dot" id="statusDot"></div>
782
+ <span id="statusText">Response</span>
783
+ </div>
784
+ <span class="response-time" id="responseTime"></span>
785
+ </div>
786
+ <div class="code-block" id="responseContent"></div>
787
+ </div>
788
+ </div>
789
+
790
+ <div class="tab-content" id="state-tab">
791
+ <div class="state-controls">
792
+ <div class="search-box">
793
+ <span class="search-icon">🔍</span>
794
+ <input type="text" class="search-input" id="stateSearch" placeholder="Search state...">
795
+ </div>
796
+ <button class="button button-secondary" onclick="loadState()">Refresh</button>
797
+ </div>
798
+ <div class="code-block" id="stateContent">Loading...</div>
799
+ </div>
800
+
801
+ <div class="tab-content" id="events-tab">
802
+ <div class="state-controls">
803
+ <div class="search-box">
804
+ <span class="search-icon">🔍</span>
805
+ <input type="text" class="search-input" id="eventSearch" placeholder="Search events...">
806
+ </div>
807
+ <div style="display: flex; gap: 8px;">
808
+ <button class="button button-secondary" onclick="loadEvents()">Refresh</button>
809
+ <button class="button button-secondary" onclick="clearEvents()">Clear</button>
810
+ </div>
811
+ </div>
812
+ <div class="events-container" id="eventsContainer">
813
+ <div class="empty">Loading events...</div>
814
+ </div>
815
+ </div>
816
+ </div>
817
+ </div>
818
+
819
+ <div class="ws-status">
820
+ <div class="ws-dot" id="wsDot"></div>
821
+ <span id="wsText">Connecting...</span>
822
+ </div>
823
+
824
+ <script>
825
+ const API_BASE = window.location.origin;
826
+ let currentState = {};
827
+ let currentEvents = [];
828
+ let responseStartTime;
829
+
830
+ // Logo SVGs - exact content from logo files
831
+ const logoLight = `<svg width="637" height="637" viewBox="0 0 637 637" fill="none" xmlns="http://www.w3.org/2000/svg">
832
+ <g clip-path="url(#clip0_1042_573)">
833
+ <rect width="637" height="637"/>
834
+ <path d="M693.161 -427.743L-623.632 1532.31L-591.107 1552.06L725.686 -407.988L693.161 -427.743Z" fill="#EC3F4A"/>
835
+ <path d="M725.695 -407.962L-591.097 1552.09L-571.511 1563.98L745.281 -396.066L725.695 -407.962Z" fill="white"/>
836
+ <path d="M745.281 -396.071L-571.511 1563.98L-538.986 1583.73L777.807 -376.315L745.281 -396.071Z" fill="#FF8A1D"/>
837
+ <path d="M777.815 -376.302L-538.978 1583.75L-519.391 1595.64L797.402 -364.406L777.815 -376.302Z" fill="white"/>
838
+ <path d="M797.394 -364.425L-519.399 1595.62L-486.874 1615.38L829.919 -344.67L797.394 -364.425Z" fill="#5EC72D"/>
839
+ <path d="M829.913 -344.684L-486.879 1615.37L-467.293 1627.26L849.499 -332.787L829.913 -344.684Z" fill="white"/>
840
+ <path d="M849.467 -332.791L-467.326 1627.26L-434.801 1647.01L881.992 -313.035L849.467 -332.791Z" fill="#42C3F7"/>
841
+ <path d="M882 -313.038L-434.792 1647.01L-415.206 1658.91L901.587 -301.142L882 -313.038Z" fill="white"/>
842
+ <path d="M673.55 -439.635L-643.242 1520.42L-623.656 1532.31L693.136 -427.738L673.55 -439.635Z" fill="white"/>
843
+ <path d="M512.644 100H121.718C109.652 100 100 109.667 100 121.751V513.277C100 525.361 109.652 535.028 121.718 535.028H512.644C524.71 535.028 534.362 525.361 534.362 513.277V121.751C534.362 109.667 524.71 100 512.644 100Z" fill="#1A1A1A"/>
844
+ <path d="M413.706 481.86C451.11 481.86 481.274 451.649 481.274 414.189C481.274 376.728 451.11 346.518 413.706 346.518C376.303 346.518 346.139 376.728 346.139 414.189C346.139 451.649 376.303 481.86 413.706 481.86Z" fill="white"/>
845
+ <path d="M153.089 347.968C153.089 347.243 153.813 346.518 154.537 346.518H287.017C287.741 346.518 288.465 347.243 288.465 347.968V412.98C288.465 413.705 287.741 414.43 287.017 414.43H257.095C256.371 414.43 255.647 415.155 255.647 415.88V480.893C255.647 481.618 254.923 482.343 254.199 482.343H188.321C187.597 482.343 186.873 481.618 186.873 480.893V415.88C186.873 415.155 186.149 414.43 185.425 414.43H154.296C153.572 414.43 152.848 413.705 152.848 412.98V347.968H153.089Z" fill="white"/>
846
+ <path d="M347.587 153.17C346.863 153.17 346.139 153.895 346.139 154.621V223.017C346.139 259.269 376.303 288.513 413.706 288.513C451.11 288.513 481.274 259.269 481.274 223.017V154.621C481.274 153.895 480.55 153.17 479.826 153.17H347.345H347.587Z" fill="white"/>
847
+ <path d="M153.33 286.339C152.848 287.306 153.33 288.272 154.537 288.272H286.776C287.742 288.272 288.465 287.306 287.983 286.339L221.863 154.139C221.381 153.172 219.933 153.172 219.45 154.139L153.33 286.339Z" fill="white"/>
848
+ </g>
849
+ <defs>
850
+ <clipPath id="clip0_1042_573">
851
+ <rect width="637" height="637" fill="white"/>
852
+ </clipPath>
853
+ </defs>
854
+ </svg>`;
855
+
856
+ const logoDark = `<svg width="637" height="637" viewBox="0 0 637 637" fill="none" xmlns="http://www.w3.org/2000/svg">
857
+ <g clip-path="url(#clip0_1042_512)">
858
+ <rect width="637" height="637"/>
859
+ <path d="M693.161 -427.743L-623.632 1532.31L-591.107 1552.06L725.686 -407.988L693.161 -427.743Z" fill="#EC3F4A"/>
860
+ <path d="M725.695 -407.962L-591.097 1552.09L-571.511 1563.98L745.281 -396.066L725.695 -407.962Z" fill="#1A1A1A"/>
861
+ <path d="M745.281 -396.071L-571.511 1563.98L-538.986 1583.73L777.807 -376.315L745.281 -396.071Z" fill="#FF8A1D"/>
862
+ <path d="M777.815 -376.302L-538.978 1583.75L-519.391 1595.64L797.402 -364.406L777.815 -376.302Z" fill="#1A1A1A"/>
863
+ <path d="M797.394 -364.425L-519.399 1595.62L-486.874 1615.38L829.919 -344.67L797.394 -364.425Z" fill="#5EC72D"/>
864
+ <path d="M829.913 -344.684L-486.879 1615.37L-467.293 1627.26L849.499 -332.787L829.913 -344.684Z" fill="#1A1A1A"/>
865
+ <path d="M849.467 -332.791L-467.326 1627.26L-434.801 1647.01L881.992 -313.035L849.467 -332.791Z" fill="#42C3F7"/>
866
+ <path d="M882 -313.038L-434.792 1647.01L-415.206 1658.91L901.587 -301.142L882 -313.038Z" fill="#1A1A1A"/>
867
+ <path d="M673.55 -439.635L-643.242 1520.42L-623.656 1532.31L693.136 -427.738L673.55 -439.635Z" fill="#1A1A1A"/>
868
+ <path d="M512.644 100H121.718C109.652 100 100 109.667 100 121.751V513.277C100 525.361 109.652 535.028 121.718 535.028H512.644C524.71 535.028 534.362 525.361 534.362 513.277V121.751C534.362 109.667 524.71 100 512.644 100Z" fill="white"/>
869
+ <path d="M413.706 481.86C451.11 481.86 481.274 451.649 481.274 414.189C481.274 376.728 451.11 346.518 413.706 346.518C376.303 346.518 346.139 376.728 346.139 414.189C346.139 451.649 376.303 481.86 413.706 481.86Z" fill="#1A1A1A"/>
870
+ <path d="M153.089 347.968C153.089 347.243 153.813 346.518 154.537 346.518H287.017C287.741 346.518 288.465 347.243 288.465 347.968V412.98C288.465 413.705 287.741 414.43 287.017 414.43H257.095C256.371 414.43 255.647 415.155 255.647 415.88V480.893C255.647 481.618 254.923 482.343 254.199 482.343H188.321C187.597 482.343 186.873 481.618 186.873 480.893V415.88C186.873 415.155 186.149 414.43 185.425 414.43H154.296C153.572 414.43 152.848 413.705 152.848 412.98V347.968H153.089Z" fill="#1A1A1A"/>
871
+ <path d="M347.587 153.17C346.863 153.17 346.139 153.895 346.139 154.621V223.017C346.139 259.269 376.303 288.513 413.706 288.513C451.11 288.513 481.274 259.269 481.274 223.017V154.621C481.274 153.895 480.55 153.17 479.826 153.17H347.345H347.587Z" fill="#1A1A1A"/>
872
+ <path d="M153.33 286.339C152.848 287.306 153.33 288.272 154.537 288.272H286.776C287.742 288.272 288.465 287.306 287.983 286.339L221.863 154.139C221.381 153.172 219.933 153.172 219.45 154.139L153.33 286.339Z" fill="#1A1A1A"/>
873
+ </g>
874
+ <defs>
875
+ <clipPath id="clip0_1042_512">
876
+ <rect width="637" height="637" fill="white"/>
877
+ </clipPath>
878
+ </defs>
879
+ </svg>`;
880
+
881
+ // Theme management
882
+ function getSystemTheme() {
883
+ return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
884
+ }
885
+
886
+ function applyTheme(theme) {
887
+ if (theme === 'system') {
888
+ theme = getSystemTheme();
889
+ }
890
+ document.body.setAttribute('data-theme', theme);
891
+
892
+ // Update logo based on theme
893
+ const logoMark = document.getElementById('logoMark');
894
+ if (logoMark) {
895
+ logoMark.innerHTML = theme === 'dark' ? logoDark : logoLight;
896
+ }
897
+ }
898
+
899
+ function setTheme(theme) {
900
+ localStorage.setItem('theme', theme);
901
+ applyTheme(theme);
902
+
903
+ // Update button states
904
+ document.querySelectorAll('.theme-option').forEach(btn => {
905
+ btn.classList.toggle('active', btn.getAttribute('data-theme') === theme);
906
+ });
907
+ }
908
+
909
+ // Initialize theme
910
+ const savedTheme = localStorage.getItem('theme') || 'dark';
911
+ setTheme(savedTheme);
912
+
913
+ // Listen for system theme changes
914
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
915
+ if (localStorage.getItem('theme') === 'system') {
916
+ applyTheme('system');
917
+ }
918
+ });
919
+
920
+ function switchTab(tab) {
921
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
922
+ document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
923
+
924
+ const tabIndex = tab === 'command' ? 1 : tab === 'state' ? 2 : 3;
925
+ document.querySelector(`.tab:nth-child(${tabIndex})`).classList.add('active');
926
+ document.getElementById(`${tab}-tab`).classList.add('active');
927
+
928
+ if (tab === 'events') {
929
+ loadEvents();
930
+ }
931
+ }
932
+
933
+ async function loadRegistry() {
934
+ try {
935
+ const response = await fetch(`${API_BASE}/registry`);
936
+ const data = await response.json();
937
+
938
+ // Update event handlers
939
+ const eventHandlersEl = document.getElementById('eventHandlers');
940
+ const eventHandlerCount = data.eventHandlers?.length || 0;
941
+ document.getElementById('eventHandlerCount').textContent = eventHandlerCount;
942
+ document.getElementById('handlerCount').textContent = eventHandlerCount;
943
+
944
+ if (eventHandlerCount > 0) {
945
+ eventHandlersEl.innerHTML = data.eventHandlers
946
+ .map(handler => `<div class="item">${handler}</div>`)
947
+ .join('');
948
+ } else {
949
+ eventHandlersEl.innerHTML = '<div class="empty">No handlers registered</div>';
950
+ }
951
+
952
+ // Update fold functions
953
+ const foldFunctionsEl = document.getElementById('foldFunctions');
954
+ const foldCount = data.folds?.length || 0;
955
+ document.getElementById('foldFunctionCount').textContent = foldCount;
956
+ document.getElementById('foldCount').textContent = foldCount;
957
+
958
+ if (foldCount > 0) {
959
+ foldFunctionsEl.innerHTML = data.folds
960
+ .map(fold => `<div class="item">${fold}</div>`)
961
+ .join('');
962
+ } else {
963
+ foldFunctionsEl.innerHTML = '<div class="empty">No folds registered</div>';
964
+ }
965
+
966
+ // Update command handlers
967
+ const commandHandlersEl = document.getElementById('commandHandlers');
968
+ const commandHandlerCount = data.commandHandlers?.length || 0;
969
+ document.getElementById('commandHandlerCountSidebar').textContent = commandHandlerCount;
970
+ document.getElementById('commandHandlerCount').textContent = commandHandlerCount;
971
+
972
+ if (commandHandlerCount > 0) {
973
+ commandHandlersEl.innerHTML = data.commandHandlers
974
+ .map(handler => `<div class="item">${handler}</div>`)
975
+ .join('');
976
+ } else {
977
+ commandHandlersEl.innerHTML = '<div class="empty">No commands registered</div>';
978
+ }
979
+
980
+ } catch (error) {
981
+ console.error('Failed to load registry:', error);
982
+ }
983
+ }
984
+
985
+ async function loadState() {
986
+ try {
987
+ const response = await fetch(`${API_BASE}/state`);
988
+ currentState = await response.json();
989
+ displayState();
990
+ } catch (error) {
991
+ console.error('Failed to load state:', error);
992
+ document.getElementById('stateContent').textContent = 'Failed to load state';
993
+ }
994
+ }
995
+
996
+ function displayState() {
997
+ const searchTerm = document.getElementById('stateSearch').value.toLowerCase();
998
+ let stateToDisplay = currentState;
999
+
1000
+ if (searchTerm) {
1001
+ const filtered = JSON.stringify(currentState, null, 2);
1002
+ if (filtered.toLowerCase().includes(searchTerm)) {
1003
+ document.getElementById('stateContent').textContent = filtered;
1004
+ } else {
1005
+ document.getElementById('stateContent').textContent = 'No matches found';
1006
+ }
1007
+ } else {
1008
+ document.getElementById('stateContent').textContent = JSON.stringify(currentState, null, 2);
1009
+ }
1010
+ }
1011
+
1012
+ document.getElementById('stateSearch').addEventListener('input', displayState);
1013
+
1014
+ async function loadEvents() {
1015
+ try {
1016
+ const response = await fetch(`${API_BASE}/events`);
1017
+ currentEvents = await response.json();
1018
+ displayEvents();
1019
+ } catch (error) {
1020
+ console.error('Failed to load events:', error);
1021
+ document.getElementById('eventsContainer').innerHTML = '<div class="empty">Failed to load events</div>';
1022
+ }
1023
+ }
1024
+
1025
+ function displayEvents() {
1026
+ const searchTerm = document.getElementById('eventSearch').value.toLowerCase();
1027
+ const container = document.getElementById('eventsContainer');
1028
+
1029
+ if (currentEvents.length === 0) {
1030
+ container.innerHTML = '<div class="empty">No events yet</div>';
1031
+ return;
1032
+ }
1033
+
1034
+ let filteredEvents = currentEvents;
1035
+ if (searchTerm) {
1036
+ filteredEvents = currentEvents.filter(item => {
1037
+ const eventStr = JSON.stringify(item.event).toLowerCase();
1038
+ return eventStr.includes(searchTerm) || item.event.type.toLowerCase().includes(searchTerm);
1039
+ });
1040
+ }
1041
+
1042
+ if (filteredEvents.length === 0) {
1043
+ container.innerHTML = '<div class="empty">No matching events</div>';
1044
+ return;
1045
+ }
1046
+
1047
+ // Display events in reverse chronological order (newest first)
1048
+ const eventsHtml = filteredEvents
1049
+ .slice()
1050
+ .reverse()
1051
+ .map((item, index) => {
1052
+ const time = new Date(item.timestamp).toLocaleTimeString();
1053
+ return `
1054
+ <div class="event-item">
1055
+ <div class="event-header">
1056
+ <div class="event-type">${item.event.type}</div>
1057
+ <div class="event-time">${time}</div>
1058
+ </div>
1059
+ <div class="event-data">${JSON.stringify(item.event.data, null, 2)}</div>
1060
+ </div>
1061
+ `;
1062
+ })
1063
+ .join('');
1064
+
1065
+ container.innerHTML = eventsHtml;
1066
+ }
1067
+
1068
+ document.getElementById('eventSearch').addEventListener('input', displayEvents);
1069
+
1070
+ async function clearEvents() {
1071
+ currentEvents = [];
1072
+ displayEvents();
1073
+ }
1074
+
1075
+ document.getElementById('commandForm').addEventListener('submit', async (e) => {
1076
+ e.preventDefault();
1077
+
1078
+ const commandType = document.getElementById('commandType').value;
1079
+ const commandDataStr = document.getElementById('commandData').value;
1080
+ const requestId = document.getElementById('requestId').value;
1081
+
1082
+ responseStartTime = Date.now();
1083
+
1084
+ try {
1085
+ const commandData = JSON.parse(commandDataStr);
1086
+
1087
+ const command = {
1088
+ type: commandType,
1089
+ data: commandData
1090
+ };
1091
+
1092
+ if (requestId) {
1093
+ command.requestId = requestId;
1094
+ }
1095
+
1096
+ const response = await fetch(`${API_BASE}/command`, {
1097
+ method: 'POST',
1098
+ headers: {
1099
+ 'Content-Type': 'application/json'
1100
+ },
1101
+ body: JSON.stringify(command)
1102
+ });
1103
+
1104
+ const responseTime = Date.now() - responseStartTime;
1105
+ const result = await response.json();
1106
+
1107
+ document.getElementById('responseSection').style.display = 'block';
1108
+ document.getElementById('responseContent').textContent = JSON.stringify(result, null, 2);
1109
+ document.getElementById('responseTime').textContent = `${responseTime}ms`;
1110
+
1111
+ const statusDot = document.getElementById('statusDot');
1112
+ const statusText = document.getElementById('statusText');
1113
+
1114
+ if (response.ok && result.status === 'ack') {
1115
+ statusDot.className = 'status-dot success';
1116
+ statusText.textContent = 'Success';
1117
+ } else {
1118
+ statusDot.className = 'status-dot error';
1119
+ statusText.textContent = 'Error';
1120
+ }
1121
+
1122
+ // Reload state after command
1123
+ setTimeout(() => loadState(), 500);
1124
+
1125
+ } catch (error) {
1126
+ const responseTime = Date.now() - responseStartTime;
1127
+
1128
+ document.getElementById('responseSection').style.display = 'block';
1129
+ document.getElementById('responseContent').textContent = error.message;
1130
+ document.getElementById('responseTime').textContent = `${responseTime}ms`;
1131
+
1132
+ document.getElementById('statusDot').className = 'status-dot error';
1133
+ document.getElementById('statusText').textContent = 'Error';
1134
+ }
1135
+ });
1136
+
1137
+ // Keyboard shortcut for submit
1138
+ document.addEventListener('keydown', (e) => {
1139
+ if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
1140
+ const form = document.getElementById('commandForm');
1141
+ if (document.activeElement.form === form) {
1142
+ form.dispatchEvent(new Event('submit'));
1143
+ }
1144
+ }
1145
+ });
1146
+
1147
+ // WebSocket connection for real-time updates
1148
+ const ws = new WebSocket(`ws://${window.location.hostname}:${window.location.port}`);
1149
+
1150
+ ws.onopen = () => {
1151
+ console.log('WebSocket connected');
1152
+ document.getElementById('wsDot').classList.add('connected');
1153
+ document.getElementById('wsText').textContent = 'Connected';
1154
+ };
1155
+
1156
+ ws.onmessage = (event) => {
1157
+ const message = JSON.parse(event.data);
1158
+ if (message.type === 'event' || message.type === 'state') {
1159
+ // Reload state when events occur
1160
+ loadState();
1161
+ // Also reload events if on events tab
1162
+ if (document.getElementById('events-tab').classList.contains('active')) {
1163
+ loadEvents();
1164
+ }
1165
+ }
1166
+ };
1167
+
1168
+ ws.onerror = (error) => {
1169
+ console.error('WebSocket error:', error);
1170
+ document.getElementById('wsDot').classList.remove('connected');
1171
+ document.getElementById('wsText').textContent = 'Disconnected';
1172
+ };
1173
+
1174
+ ws.onclose = () => {
1175
+ document.getElementById('wsDot').classList.remove('connected');
1176
+ document.getElementById('wsText').textContent = 'Disconnected';
1177
+ };
1178
+
1179
+ // Initial load
1180
+ loadRegistry();
1181
+ loadState();
1182
+
1183
+ // Refresh registry every 5 seconds
1184
+ setInterval(loadRegistry, 5000);
1185
+ </script>
1186
+ </body>
1187
+ </html>