jfl 0.2.4 → 0.3.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 (105) hide show
  1. package/README.md +16 -0
  2. package/dist/commands/agent.d.ts +7 -0
  3. package/dist/commands/agent.d.ts.map +1 -0
  4. package/dist/commands/agent.js +170 -0
  5. package/dist/commands/agent.js.map +1 -0
  6. package/dist/commands/context-hub.d.ts.map +1 -1
  7. package/dist/commands/context-hub.js +274 -27
  8. package/dist/commands/context-hub.js.map +1 -1
  9. package/dist/commands/digest.d.ts.map +1 -1
  10. package/dist/commands/digest.js +45 -0
  11. package/dist/commands/digest.js.map +1 -1
  12. package/dist/commands/doctor.d.ts +7 -0
  13. package/dist/commands/doctor.d.ts.map +1 -0
  14. package/dist/commands/doctor.js +236 -0
  15. package/dist/commands/doctor.js.map +1 -0
  16. package/dist/commands/eval.d.ts +6 -0
  17. package/dist/commands/eval.d.ts.map +1 -0
  18. package/dist/commands/eval.js +236 -0
  19. package/dist/commands/eval.js.map +1 -0
  20. package/dist/commands/hooks.d.ts +2 -0
  21. package/dist/commands/hooks.d.ts.map +1 -1
  22. package/dist/commands/hooks.js +1 -0
  23. package/dist/commands/hooks.js.map +1 -1
  24. package/dist/commands/init.d.ts.map +1 -1
  25. package/dist/commands/init.js +230 -145
  26. package/dist/commands/init.js.map +1 -1
  27. package/dist/commands/portfolio.d.ts +6 -0
  28. package/dist/commands/portfolio.d.ts.map +1 -0
  29. package/dist/commands/portfolio.js +296 -0
  30. package/dist/commands/portfolio.js.map +1 -0
  31. package/dist/commands/scope.d.ts +1 -0
  32. package/dist/commands/scope.d.ts.map +1 -1
  33. package/dist/commands/scope.js +189 -2
  34. package/dist/commands/scope.js.map +1 -1
  35. package/dist/commands/update.d.ts.map +1 -1
  36. package/dist/commands/update.js +6 -0
  37. package/dist/commands/update.js.map +1 -1
  38. package/dist/commands/voice.js.map +1 -1
  39. package/dist/dashboard/components.d.ts +1 -1
  40. package/dist/dashboard/components.d.ts.map +1 -1
  41. package/dist/dashboard/components.js +418 -6
  42. package/dist/dashboard/components.js.map +1 -1
  43. package/dist/dashboard/index.d.ts.map +1 -1
  44. package/dist/dashboard/index.js +32 -5
  45. package/dist/dashboard/index.js.map +1 -1
  46. package/dist/dashboard/pages.d.ts +1 -1
  47. package/dist/dashboard/pages.d.ts.map +1 -1
  48. package/dist/dashboard/pages.js +961 -123
  49. package/dist/dashboard/pages.js.map +1 -1
  50. package/dist/dashboard/styles.d.ts +1 -1
  51. package/dist/dashboard/styles.d.ts.map +1 -1
  52. package/dist/dashboard/styles.js +701 -88
  53. package/dist/dashboard/styles.js.map +1 -1
  54. package/dist/index.js +88 -45
  55. package/dist/index.js.map +1 -1
  56. package/dist/lib/agent-manifest.d.ts +35 -0
  57. package/dist/lib/agent-manifest.d.ts.map +1 -0
  58. package/dist/lib/agent-manifest.js +75 -0
  59. package/dist/lib/agent-manifest.js.map +1 -0
  60. package/dist/lib/eval-store.d.ts +15 -0
  61. package/dist/lib/eval-store.d.ts.map +1 -0
  62. package/dist/lib/eval-store.js +179 -0
  63. package/dist/lib/eval-store.js.map +1 -0
  64. package/dist/lib/flow-engine.d.ts +12 -0
  65. package/dist/lib/flow-engine.d.ts.map +1 -1
  66. package/dist/lib/flow-engine.js +181 -4
  67. package/dist/lib/flow-engine.js.map +1 -1
  68. package/dist/lib/kuva.d.ts +45 -0
  69. package/dist/lib/kuva.d.ts.map +1 -0
  70. package/dist/lib/kuva.js +131 -0
  71. package/dist/lib/kuva.js.map +1 -0
  72. package/dist/lib/service-gtm.d.ts +10 -1
  73. package/dist/lib/service-gtm.d.ts.map +1 -1
  74. package/dist/lib/service-gtm.js +35 -2
  75. package/dist/lib/service-gtm.js.map +1 -1
  76. package/dist/lib/trajectory-loader.d.ts +82 -0
  77. package/dist/lib/trajectory-loader.d.ts.map +1 -0
  78. package/dist/lib/trajectory-loader.js +406 -0
  79. package/dist/lib/trajectory-loader.js.map +1 -0
  80. package/dist/mcp/context-hub-mcp.js +60 -0
  81. package/dist/mcp/context-hub-mcp.js.map +1 -1
  82. package/dist/types/eval.d.ts +18 -0
  83. package/dist/types/eval.d.ts.map +1 -0
  84. package/dist/types/eval.js +5 -0
  85. package/dist/types/eval.js.map +1 -0
  86. package/dist/types/flows.d.ts +7 -0
  87. package/dist/types/flows.d.ts.map +1 -1
  88. package/dist/types/journal.d.ts +133 -0
  89. package/dist/types/journal.d.ts.map +1 -0
  90. package/dist/types/journal.js +59 -0
  91. package/dist/types/journal.js.map +1 -0
  92. package/dist/types/map.d.ts +1 -1
  93. package/dist/types/map.d.ts.map +1 -1
  94. package/dist/types/map.js.map +1 -1
  95. package/dist/ui/service-dashboard.js.map +1 -1
  96. package/dist/utils/wallet.js.map +1 -1
  97. package/package.json +1 -1
  98. package/scripts/migrate-to-branch-sessions.sh +201 -0
  99. package/scripts/session/fix-tracked-logs.sh +97 -0
  100. package/scripts/session/session-cleanup.sh +28 -10
  101. package/scripts/session/session-end.sh +0 -10
  102. package/scripts/session/session-init.sh +0 -16
  103. package/template/THEORY.md +26 -0
  104. package/template/scripts/session/session-cleanup.sh +28 -10
  105. package/template/scripts/session/session-sync.sh +10 -0
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Dashboard reusable Preact components
3
3
  *
4
- * @purpose Preact+HTM component JS strings for Nav, Card, StatusBadge, Table, JournalEntry, etc.
4
+ * @purpose Preact+HTM component JS strings for Nav, Card, MetricCard, ChartCard, RunActivityChart, ActivityRow, etc.
5
5
  */
6
6
  export declare function getComponentsJS(): string;
7
7
  //# sourceMappingURL=components.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../src/dashboard/components.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAgB,eAAe,IAAI,MAAM,CA4JxC"}
1
+ {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../src/dashboard/components.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAgB,eAAe,IAAI,MAAM,CAwjBxC"}
@@ -1,24 +1,47 @@
1
1
  /**
2
2
  * Dashboard reusable Preact components
3
3
  *
4
- * @purpose Preact+HTM component JS strings for Nav, Card, StatusBadge, Table, JournalEntry, etc.
4
+ * @purpose Preact+HTM component JS strings for Nav, Card, MetricCard, ChartCard, RunActivityChart, ActivityRow, etc.
5
5
  */
6
6
  export function getComponentsJS() {
7
7
  return `
8
- function Nav({ currentPage, setPage, projectName, port }) {
9
- const pages = [
8
+ function Nav({ currentPage, setPage, projectName, port, mode }) {
9
+ const m = mode || 'gtm'
10
+ const portfolioPages = [
11
+ { id: 'portfolio', label: 'Portfolio Overview' },
12
+ { id: 'evals', label: 'Leaderboard' },
13
+ { id: 'events', label: 'Events' },
14
+ { id: 'costs', label: 'Costs' },
15
+ { id: 'projects', label: 'Health' },
16
+ ]
17
+ const gtmPages = [
10
18
  { id: 'overview', label: 'Command Center' },
11
19
  { id: 'journal', label: 'Journal' },
12
20
  { id: 'agents', label: 'Agents' },
21
+ { id: 'evals', label: 'Evals' },
13
22
  { id: 'events', label: 'Events' },
23
+ { id: 'costs', label: 'Costs' },
24
+ { id: 'flows', label: 'Flows' },
14
25
  { id: 'sessions', label: 'Sessions' },
26
+ { id: 'scope', label: 'Scope' },
15
27
  { id: 'projects', label: 'Projects' },
16
28
  ]
29
+ const servicePages = [
30
+ { id: 'service', label: 'My Status' },
31
+ { id: 'journal', label: 'Journal' },
32
+ { id: 'evals', label: 'My Evals' },
33
+ { id: 'events', label: 'Events' },
34
+ { id: 'costs', label: 'Costs' },
35
+ { id: 'sessions', label: 'Sessions' },
36
+ ]
37
+
38
+ const pages = m === 'portfolio' ? portfolioPages : m === 'service' ? servicePages : gtmPages
17
39
 
18
40
  return html\`
19
41
  <div class="sidebar">
20
42
  <div class="sidebar-brand">
21
43
  <h1>\${projectName || 'Context Hub'}</h1>
44
+ <\${ModeBadge} mode=\${m} />
22
45
  <div class="port-badge">Port \${port}</div>
23
46
  </div>
24
47
  <ul class="nav-items">
@@ -45,6 +68,112 @@ export function getComponentsJS() {
45
68
  \`
46
69
  }
47
70
 
71
+ function MetricCard({ icon, value, label, description, onClick, color }) {
72
+ return html\`
73
+ <div class="metric-card" onClick=\${onClick}>
74
+ \${icon && html\`<div style="font-size: 0.875rem; color: var(--muted-foreground); margin-bottom: 0.5rem;">\${icon}</div>\`}
75
+ <div class="metric-value" style=\${color ? 'color:' + color : ''}>\${value}</div>
76
+ <div class="metric-label">\${label}</div>
77
+ \${description && html\`<div class="metric-description">\${description}</div>\`}
78
+ </div>
79
+ \`
80
+ }
81
+
82
+ function ChartCard({ title, subtitle, children }) {
83
+ return html\`
84
+ <div class="chart-card">
85
+ <div style="margin-bottom: 1rem;">
86
+ <div class="chart-card-title">\${title}</div>
87
+ \${subtitle && html\`<div class="chart-card-subtitle">\${subtitle}</div>\`}
88
+ </div>
89
+ \${children}
90
+ </div>
91
+ \`
92
+ }
93
+
94
+ function RunActivityChart({ events, days }) {
95
+ const numDays = days || 14
96
+ const now = new Date()
97
+ const buckets = []
98
+
99
+ for (let i = numDays - 1; i >= 0; i--) {
100
+ const d = new Date(now)
101
+ d.setDate(d.getDate() - i)
102
+ const key = d.toISOString().slice(0, 10)
103
+ buckets.push({ date: key, success: 0, failed: 0, other: 0, dayIndex: numDays - 1 - i })
104
+ }
105
+
106
+ const dateSet = new Set(buckets.map(b => b.date))
107
+
108
+ for (const e of (events || [])) {
109
+ const ts = e.ts || e.timestamp
110
+ if (!ts) continue
111
+ const day = new Date(ts).toISOString().slice(0, 10)
112
+ const bucket = buckets.find(b => b.date === day)
113
+ if (!bucket) continue
114
+ const t = (e.type || '').toLowerCase()
115
+ if (t.includes('completed') || t.includes('success')) bucket.success++
116
+ else if (t.includes('failed') || t.includes('error')) bucket.failed++
117
+ else bucket.other++
118
+ }
119
+
120
+ const maxCount = Math.max(1, ...buckets.map(b => b.success + b.failed + b.other))
121
+
122
+ return html\`
123
+ <div>
124
+ <div class="stacked-bars">
125
+ \${buckets.map((b, i) => {
126
+ const total = b.success + b.failed + b.other
127
+ const h = 80
128
+ const successH = total > 0 ? Math.max(2, Math.round((b.success / maxCount) * h)) : 0
129
+ const failedH = total > 0 ? Math.max(2, Math.round((b.failed / maxCount) * h)) : 0
130
+ const otherH = total > 0 ? Math.max(2, Math.round((b.other / maxCount) * h)) : 0
131
+
132
+ return html\`
133
+ <div key=\${i} class="stacked-bar" title=\${b.date + ': ' + total + ' events'}>
134
+ \${total === 0 ? html\`<div class="stacked-bar-empty"></div>\` : html\`
135
+ \${successH > 0 && html\`<div class="stacked-bar-segment stacked-bar-segment-success" style=\${'height:' + successH + 'px'}></div>\`}
136
+ \${failedH > 0 && html\`<div class="stacked-bar-segment stacked-bar-segment-error" style=\${'height:' + failedH + 'px'}></div>\`}
137
+ \${otherH > 0 && html\`<div class="stacked-bar-segment stacked-bar-segment-warning" style=\${'height:' + otherH + 'px'}></div>\`}
138
+ \`}
139
+ </div>
140
+ \`
141
+ })}
142
+ </div>
143
+ <div style="display: flex; justify-content: space-between; margin-top: 0.25rem;">
144
+ <span class="stacked-bar-label">\${buckets[0]?.date.slice(5) || ''}</span>
145
+ <span class="stacked-bar-label">\${buckets[Math.floor(numDays / 2)]?.date.slice(5) || ''}</span>
146
+ <span class="stacked-bar-label">\${buckets[numDays - 1]?.date.slice(5) || ''}</span>
147
+ </div>
148
+ </div>
149
+ \`
150
+ }
151
+
152
+ function ActivityRow({ event, isNew }) {
153
+ const time = new Date(event.ts || event.timestamp).toLocaleTimeString()
154
+ const t = (event.type || '').toLowerCase()
155
+ let icon = ''
156
+ if (t.includes('completed') || t.includes('success')) icon = 'var(--success)'
157
+ else if (t.includes('failed') || t.includes('error')) icon = 'var(--error)'
158
+ else if (t.includes('started') || t.includes('start')) icon = 'var(--info)'
159
+ else icon = 'var(--dim)'
160
+
161
+ const detail = event.data
162
+ ? (typeof event.data === 'object'
163
+ ? (event.data.message || event.data.task || event.data.title || JSON.stringify(event.data).slice(0, 80))
164
+ : String(event.data))
165
+ : ''
166
+
167
+ return html\`
168
+ <div class=\${'activity-row' + (isNew ? ' activity-row-enter' : '')}>
169
+ <span style=\${'width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; background:' + icon}></span>
170
+ <span style="font-size: 0.75rem; color: var(--dim); white-space: nowrap; min-width: 60px;">\${time}</span>
171
+ <\${TypeBadge} type=\${event.type} />
172
+ <span style="font-size: 0.8rem; color: var(--muted-foreground); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1;">\${detail}</span>
173
+ </div>
174
+ \`
175
+ }
176
+
48
177
  function StatusBadge({ status }) {
49
178
  const s = (status || 'unknown').toLowerCase()
50
179
  return html\`<span class=\${'badge badge-' + s}>\${status}</span>\`
@@ -133,10 +262,21 @@ export function getComponentsJS() {
133
262
  }
134
263
 
135
264
  function LiveIndicator({ connected }) {
265
+ if (connected) {
266
+ return html\`
267
+ <span style="display: inline-flex; align-items: center; gap: 0.375rem; font-size: 0.8rem; color: var(--muted-foreground);">
268
+ <span class="pulse-dot">
269
+ <span class="pulse-dot-ping" style="background: var(--success);"></span>
270
+ <span class="pulse-dot-core" style="background: var(--success);"></span>
271
+ </span>
272
+ Live
273
+ </span>
274
+ \`
275
+ }
136
276
  return html\`
137
- <span style="display: inline-flex; align-items: center; font-size: 0.8rem;">
138
- <span class="live-dot" style=\${'background: ' + (connected ? 'var(--success)' : 'var(--error)')}></span>
139
- \${connected ? 'Live' : 'Disconnected'}
277
+ <span style="display: inline-flex; align-items: center; gap: 0.375rem; font-size: 0.8rem; color: var(--error);">
278
+ <span style="width: 8px; height: 8px; border-radius: 50%; background: var(--error);"></span>
279
+ Disconnected
140
280
  </span>
141
281
  \`
142
282
  }
@@ -158,6 +298,278 @@ export function getComponentsJS() {
158
298
  </div>
159
299
  \`
160
300
  }
301
+
302
+ function Sparkline({ data, width, height, color }) {
303
+ const w = width || 60
304
+ const h = height || 20
305
+ const c = color || 'var(--accent)'
306
+ if (!data || data.length < 2) {
307
+ return html\`<svg width=\${w} height=\${h} class="sparkline"><line x1="0" y1=\${h/2} x2=\${w} y2=\${h/2} stroke="var(--dim)" stroke-width="1" stroke-dasharray="2,2" /></svg>\`
308
+ }
309
+ const min = Math.min(...data)
310
+ const max = Math.max(...data)
311
+ const range = max - min || 1
312
+ const pad = 2
313
+ const points = data.map((v, i) => {
314
+ const x = (i / (data.length - 1)) * w
315
+ const y = h - pad - ((v - min) / range) * (h - pad * 2)
316
+ return x + ',' + y
317
+ }).join(' ')
318
+ return html\`
319
+ <svg width=\${w} height=\${h} class="sparkline">
320
+ <polyline points=\${points} fill="none" stroke=\${c} stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
321
+ </svg>
322
+ \`
323
+ }
324
+
325
+ function EvalChart({ data, width, height, label }) {
326
+ const w = width || 500
327
+ const h = height || 200
328
+ const pad = { top: 10, right: 10, bottom: 25, left: 45 }
329
+ const cw = w - pad.left - pad.right
330
+ const ch = h - pad.top - pad.bottom
331
+
332
+ if (!data || data.length < 2) {
333
+ return html\`
334
+ <div class="eval-chart-container" style=\${'width:' + w + 'px;height:' + h + 'px;'}>
335
+ <div class="empty-state" style="padding: 2rem;">No eval data yet</div>
336
+ </div>
337
+ \`
338
+ }
339
+
340
+ const values = data.map(d => d.value)
341
+ const min = Math.min(...values) * 0.95
342
+ const max = Math.max(...values) * 1.05
343
+ const range = max - min || 1
344
+
345
+ const points = data.map((d, i) => {
346
+ const x = pad.left + (i / (data.length - 1)) * cw
347
+ const y = pad.top + ch - ((d.value - min) / range) * ch
348
+ return { x, y, value: d.value, ts: d.ts, model: d.model_version }
349
+ })
350
+
351
+ const polyline = points.map(p => p.x + ',' + p.y).join(' ')
352
+ const gradientPath = 'M' + points[0].x + ',' + (pad.top + ch) + ' L' + points.map(p => p.x + ',' + p.y).join(' L') + ' L' + points[points.length - 1].x + ',' + (pad.top + ch) + ' Z'
353
+
354
+ const yTicks = 5
355
+ const yLines = Array.from({ length: yTicks }, (_, i) => {
356
+ const val = min + (range * i) / (yTicks - 1)
357
+ const y = pad.top + ch - (i / (yTicks - 1)) * ch
358
+ return { val, y }
359
+ })
360
+
361
+ return html\`
362
+ <div class="eval-chart-container">
363
+ <svg width=\${w} height=\${h} viewBox=\${'0 0 ' + w + ' ' + h}>
364
+ <defs>
365
+ <linearGradient id="evalGrad" x1="0" y1="0" x2="0" y2="1">
366
+ <stop offset="0%" stop-color="var(--accent)" stop-opacity="0.3" />
367
+ <stop offset="100%" stop-color="var(--accent)" stop-opacity="0" />
368
+ </linearGradient>
369
+ </defs>
370
+ \${yLines.map(t => html\`
371
+ <g key=\${t.val}>
372
+ <line x1=\${pad.left} y1=\${t.y} x2=\${w - pad.right} y2=\${t.y} stroke="var(--border)" stroke-width="0.5" />
373
+ <text x=\${pad.left - 5} y=\${t.y + 3} text-anchor="end" fill="var(--dim)" font-size="10">\${t.val.toFixed(2)}</text>
374
+ </g>
375
+ \`)}
376
+ <path d=\${gradientPath} fill="url(#evalGrad)" />
377
+ <polyline points=\${polyline} fill="none" stroke="var(--accent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
378
+ \${points.map((p, i) => html\`
379
+ <g key=\${i} class="eval-point">
380
+ <circle cx=\${p.x} cy=\${p.y} r="3" fill="var(--card)" stroke="var(--accent)" stroke-width="1.5" />
381
+ <title>\${(p.model || '') + ' ' + p.value.toFixed(4) + '\\n' + new Date(p.ts).toLocaleDateString()}</title>
382
+ </g>
383
+ \`)}
384
+ \${label && html\`<text x=\${pad.left} y=\${h - 3} fill="var(--dim)" font-size="10">\${label}</text>\`}
385
+ </svg>
386
+ </div>
387
+ \`
388
+ }
389
+
390
+ function HealthCard({ child }) {
391
+ const dotClass = child.status === 'ok' ? 'health-dot-ok' : child.status === 'error' ? 'health-dot-error' : 'health-dot-down'
392
+ return html\`
393
+ <div class="card health-card">
394
+ <div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
395
+ <span class=\${'health-dot ' + dotClass}></span>
396
+ <span style="font-weight: 700; font-size: 0.9rem;">\${child.name}</span>
397
+ </div>
398
+ \${child.composite != null && html\`
399
+ <div style="display: flex; align-items: center; gap: 0.75rem;">
400
+ <span class="metric-value" style="font-size: 1.5rem;">\${child.composite.toFixed(2)}</span>
401
+ \${child.delta != null && html\`<\${DeltaBadge} delta=\${child.delta} />\`}
402
+ </div>
403
+ \`}
404
+ \${child.sparkline && child.sparkline.length > 1 && html\`
405
+ <div style="margin-top: 0.5rem;">
406
+ <\${Sparkline} data=\${child.sparkline} width=\${120} height=\${24} />
407
+ </div>
408
+ \`}
409
+ \${child.activeSessions != null && html\`
410
+ <div style="font-size: 0.7rem; color: var(--dim); margin-top: 0.375rem;">\${child.activeSessions} active sessions</div>
411
+ \`}
412
+ <div style="font-size: 0.65rem; color: var(--dim); margin-top: 0.25rem;">Port \${child.port}</div>
413
+ </div>
414
+ \`
415
+ }
416
+
417
+ function DeltaBadge({ delta }) {
418
+ if (delta == null || delta === 0) {
419
+ return html\`<span class="delta-neutral">—</span>\`
420
+ }
421
+ const isUp = delta > 0
422
+ const cls = isUp ? 'delta-up' : 'delta-down'
423
+ const arrow = isUp ? '▲' : '▼'
424
+ return html\`<span class=\${cls}>\${arrow} \${Math.abs(delta).toFixed(3)}</span>\`
425
+ }
426
+
427
+ function CostBar({ prompt, completion }) {
428
+ const total = (prompt || 0) + (completion || 0)
429
+ if (total === 0) return html\`<div class="cost-bar"><div class="cost-bar-empty">—</div></div>\`
430
+ const pPct = Math.round((prompt / total) * 100)
431
+ const cPct = 100 - pPct
432
+ return html\`
433
+ <div class="cost-bar">
434
+ <div class="cost-bar-prompt" style=\${'width:' + pPct + '%'} title=\${'Prompt: ' + prompt.toLocaleString()}></div>
435
+ <div class="cost-bar-completion" style=\${'width:' + cPct + '%'} title=\${'Completion: ' + completion.toLocaleString()}></div>
436
+ </div>
437
+ \`
438
+ }
439
+
440
+ function UtilBar({ value, max, label }) {
441
+ const pct = max > 0 ? Math.min(100, Math.round((value / max) * 100)) : 0
442
+ const cls = pct < 60 ? 'util-bar-green' : pct < 85 ? 'util-bar-yellow' : 'util-bar-red'
443
+ return html\`
444
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
445
+ <div class="util-bar" style="flex: 1;">
446
+ <div class=\${'util-bar-fill ' + cls} style=\${'width:' + pct + '%'}></div>
447
+ </div>
448
+ <span style="font-size: 0.65rem; color: var(--muted-foreground); font-weight: 600; min-width: 28px;">\${pct}%</span>
449
+ </div>
450
+ \`
451
+ }
452
+
453
+ function SuccessRateBar({ rate }) {
454
+ const pct = Math.round((rate || 0) * 100)
455
+ const color = pct >= 95 ? 'var(--success)' : pct >= 80 ? 'var(--warning)' : 'var(--error)'
456
+ return html\`
457
+ <div style="display: flex; align-items: center; gap: 0.375rem;">
458
+ <div class="success-bar">
459
+ <div class="success-bar-fill" style=\${'width:' + pct + '%; background:' + color}></div>
460
+ </div>
461
+ <span style="font-size: 0.65rem; color: var(--muted-foreground); font-weight: 600; min-width: 28px;">\${pct}%</span>
462
+ </div>
463
+ \`
464
+ }
465
+
466
+ function UtilizationRing({ value, max, label }) {
467
+ const pct = max > 0 ? Math.min(1, value / max) : 0
468
+ const r = 30
469
+ const circumference = 2 * Math.PI * r
470
+ const offset = circumference * (1 - pct)
471
+ const color = pct < 0.6 ? 'var(--success)' : pct < 0.85 ? 'var(--warning)' : 'var(--error)'
472
+ return html\`
473
+ <div class="util-ring">
474
+ <svg width="76" height="76" viewBox="0 0 76 76">
475
+ <circle cx="38" cy="38" r=\${r} fill="none" stroke="var(--border)" stroke-width="6" />
476
+ <circle cx="38" cy="38" r=\${r} fill="none" stroke=\${color} stroke-width="6"
477
+ stroke-dasharray=\${circumference} stroke-dashoffset=\${offset}
478
+ stroke-linecap="round" transform="rotate(-90 38 38)" />
479
+ <text x="38" y="35" text-anchor="middle" fill="var(--foreground)" font-size="14" font-weight="700">\${Math.round(pct * 100)}%</text>
480
+ \${label && html\`<text x="38" y="50" text-anchor="middle" fill="var(--dim)" font-size="9">\${label}</text>\`}
481
+ </svg>
482
+ </div>
483
+ \`
484
+ }
485
+
486
+ function ActiveAgentCard({ agent }) {
487
+ const a = agent
488
+ const isLive = a.status === 'active'
489
+ const duration = a.startedAt ? Math.round((Date.now() - new Date(a.startedAt).getTime()) / 1000) : null
490
+ const durationStr = duration !== null
491
+ ? duration < 60 ? duration + 's'
492
+ : duration < 3600 ? Math.round(duration / 60) + 'm'
493
+ : Math.round(duration / 3600) + 'h'
494
+ : ''
495
+
496
+ function feedColor(type) {
497
+ if (!type) return 'feed-line-dim'
498
+ if (type.includes('error') || type.includes('failed')) return 'feed-line-error'
499
+ if (type.includes('warn')) return 'feed-line-warn'
500
+ if (type.includes('health') || type.includes('analysis') || type.includes('info')) return 'feed-line-info'
501
+ if (type.includes('completed') || type.includes('complete') || type.includes('success')) return 'feed-line-success'
502
+ return 'feed-line-dim'
503
+ }
504
+
505
+ return html\`
506
+ <div class=\${'active-agent-card' + (isLive ? ' active-agent-card-live' : '')}>
507
+ <div class="active-agent-header">
508
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
509
+ \${isLive ? html\`
510
+ <span class="pulse-dot">
511
+ <span class="pulse-dot-ping" style="background: var(--success);"></span>
512
+ <span class="pulse-dot-core" style="background: var(--success);"></span>
513
+ </span>
514
+ \` : html\`
515
+ <span class="agent-dot agent-dot-idle"></span>
516
+ \`}
517
+ <span style="font-weight: 700; font-size: 0.9rem;">\${a.name}</span>
518
+ \${a.model && html\`<span class="model-badge">\${a.model}</span>\`}
519
+ </div>
520
+ <span style="font-size: 0.75rem; color: var(--dim);">\${durationStr}</span>
521
+ </div>
522
+ \${a.currentTask && html\`
523
+ <div class="active-agent-task">\${a.currentTask}</div>
524
+ \`}
525
+ \${a.role && html\`<div style="font-size: 0.7rem; color: var(--dim); margin-bottom: 0.5rem;">\${a.role}</div>\`}
526
+ \${a.recentEvents && a.recentEvents.length > 0 && html\`
527
+ <div class="run-feed">
528
+ \${a.recentEvents.slice(0, 5).map((e, i) => html\`
529
+ <div key=\${i} class="run-feed-line">
530
+ <span class="run-feed-seq">\${String(i + 1).padStart(2, '0')}</span>
531
+ <span class=\${'run-feed-type ' + feedColor(e.type)}>\${(e.type || '').split(':').pop()}</span>
532
+ <span class="run-feed-data">\${e.data ? (typeof e.data === 'object' ? (e.data.message || e.data.task || JSON.stringify(e.data).slice(0, 60)) : String(e.data)) : ''}</span>
533
+ </div>
534
+ \`)}
535
+ </div>
536
+ \`}
537
+ </div>
538
+ \`
539
+ }
540
+
541
+ function AgentCard({ agent }) {
542
+ const a = agent
543
+ const age = a.lastSeen ? Math.round((Date.now() - new Date(a.lastSeen).getTime()) / 1000) : null
544
+ const ageStr = age !== null
545
+ ? age < 60 ? age + 's ago'
546
+ : age < 3600 ? Math.round(age / 60) + 'm ago'
547
+ : Math.round(age / 3600) + 'h ago'
548
+ : ''
549
+
550
+ return html\`
551
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid var(--border);">
552
+ <div style="display: flex; align-items: center; gap: 0.625rem;">
553
+ <span class=\${'agent-dot agent-dot-' + a.status}></span>
554
+ <div>
555
+ <div style="font-size: 0.85rem; font-weight: 600;">\${a.name}</div>
556
+ \${a.role && html\`<div style="font-size: 0.7rem; color: var(--dim);">\${a.role}</div>\`}
557
+ </div>
558
+ </div>
559
+ <div style="display: flex; align-items: center; gap: 0.5rem; text-align: right;">
560
+ \${a.model && html\`<span class="model-badge">\${a.model}</span>\`}
561
+ \${a.runtime && a.runtime !== 'local' && html\`<span class="runtime-badge">\${a.runtime}</span>\`}
562
+ <span style="font-size: 0.65rem; color: var(--dim); min-width: 50px;">\${ageStr}</span>
563
+ </div>
564
+ </div>
565
+ \`
566
+ }
567
+
568
+ function ModeBadge({ mode }) {
569
+ const cls = 'mode-badge mode-' + mode
570
+ const label = mode === 'portfolio' ? 'PORTFOLIO' : mode === 'gtm' ? 'GTM' : mode === 'service' ? 'SERVICE' : 'STANDALONE'
571
+ return html\`<span class=\${cls}>\${label}</span>\`
572
+ }
161
573
  `;
162
574
  }
163
575
  //# sourceMappingURL=components.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"components.js","sourceRoot":"","sources":["../../src/dashboard/components.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,UAAU,eAAe;IAC7B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0JN,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"components.js","sourceRoot":"","sources":["../../src/dashboard/components.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,UAAU,eAAe;IAC7B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsjBN,CAAA;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAK5B,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAiG/E;AAWD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GACX,OAAO,CAiBT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAK5B,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CA4H/E;AAWD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GACX,OAAO,CAiBT"}
@@ -63,7 +63,7 @@ export function generateDashboardHTML(projectName, port) {
63
63
  }
64
64
  const res = await fetch(path, { ...opts, headers })
65
65
  if (res.status === 401) {
66
- document.getElementById('app').innerHTML = '<div style="padding: 3rem; text-align: center; color: var(--error);"><h2>Unauthorized</h2><p style="color: var(--text-soft); margin-top: 0.5rem;">Token expired or invalid. Run: jfl context-hub dashboard</p></div>'
66
+ document.getElementById('app').innerHTML = '<div style="padding: 3rem; text-align: center; color: var(--error);"><h2>Unauthorized</h2><p style="color: var(--muted-foreground); margin-top: 0.5rem;">Token expired or invalid. Run: jfl context-hub dashboard</p></div>'
67
67
  throw new Error('Unauthorized')
68
68
  }
69
69
  return res.json()
@@ -77,16 +77,42 @@ export function generateDashboardHTML(projectName, port) {
77
77
 
78
78
  // App
79
79
  function App() {
80
- const [page, setPage] = useState('overview')
80
+ const { mode, config, children, loading: modeLoading } = useWorkspaceMode()
81
+ const defaultPage = mode === 'portfolio' ? 'portfolio' : mode === 'service' ? 'service' : 'overview'
82
+ const [page, setPage] = useState(defaultPage)
81
83
 
82
- const pageComponent = {
84
+ // Update default page when mode loads
85
+ useEffect(() => {
86
+ if (!modeLoading) {
87
+ const dp = mode === 'portfolio' ? 'portfolio' : mode === 'service' ? 'service' : 'overview'
88
+ setPage(prev => {
89
+ // Only update if still on the initial 'overview' default
90
+ if (prev === 'overview' && dp !== 'overview') return dp
91
+ return prev
92
+ })
93
+ }
94
+ }, [mode, modeLoading])
95
+
96
+ const pageMap = {
83
97
  overview: OverviewPage,
84
98
  journal: JournalPage,
85
99
  agents: AgentsPage,
86
100
  events: EventsPage,
87
101
  sessions: SessionsPage,
88
102
  projects: ProjectsPage,
89
- }[page] || OverviewPage
103
+ portfolio: PortfolioOverviewPage,
104
+ evals: EvalsPage,
105
+ scope: ScopePage,
106
+ service: ServiceOverviewPage,
107
+ costs: CostsPage,
108
+ flows: FlowsPage,
109
+ }
110
+
111
+ const pageComponent = pageMap[page] || OverviewPage
112
+
113
+ if (modeLoading) {
114
+ return html\`<div class="loading" style="width: 100%; height: 100vh;">Loading dashboard</div>\`
115
+ }
90
116
 
91
117
  return html\`
92
118
  <\${Nav}
@@ -94,9 +120,10 @@ export function generateDashboardHTML(projectName, port) {
94
120
  setPage=\${setPage}
95
121
  projectName="${escapeHtml(projectName)}"
96
122
  port=\${${port}}
123
+ mode=\${mode}
97
124
  />
98
125
  <div class="main-content">
99
- <\${pageComponent} />
126
+ <\${pageComponent} mode=\${mode} config=\${config} children=\${children} />
100
127
  </div>
101
128
  \`
102
129
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEvC,MAAM,UAAU,qBAAqB,CAAC,WAAmB,EAAE,IAAY;IACrE,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAA;IACnC,MAAM,UAAU,GAAG,eAAe,EAAE,CAAA;IACpC,MAAM,KAAK,GAAG,UAAU,EAAE,CAAA;IAE1B,OAAO;;;;;WAKE,UAAU,CAAC,WAAW,CAAC;;;;;;;;;;;WAWvB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAyCX,UAAU;;;MAGV,KAAK;;;;;;;;;;;;;;;;;;;yBAmBc,UAAU,CAAC,WAAW,CAAC;oBAC5B,IAAI;;;;;;;;;;;QAWhB,CAAA;AACR,CAAC;AAED,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AAC3B,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,GAAyB,EACzB,GAAwB,EACxB,WAAmB,EACnB,IAAY;IAEZ,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,oBAAoB,IAAI,EAAE,CAAC,CAAA;IAE/D,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC3C,OAAO,KAAK,CAAA;IACd,CAAC;IAED,qCAAqC;IACrC,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,aAAa,CAAA;IAEjE,MAAM,IAAI,GAAG,qBAAqB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;IACrD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;QACjB,cAAc,EAAE,0BAA0B;QAC1C,eAAe,EAAE,UAAU;KAC5B,CAAC,CAAA;IACF,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACb,OAAO,IAAI,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEvC,MAAM,UAAU,qBAAqB,CAAC,WAAmB,EAAE,IAAY;IACrE,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAA;IACnC,MAAM,UAAU,GAAG,eAAe,EAAE,CAAA;IACpC,MAAM,KAAK,GAAG,UAAU,EAAE,CAAA;IAE1B,OAAO;;;;;WAKE,UAAU,CAAC,WAAW,CAAC;;;;;;;;;;;WAWvB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAyCX,UAAU;;;MAGV,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA6Cc,UAAU,CAAC,WAAW,CAAC;oBAC5B,IAAI;;;;;;;;;;;;QAYhB,CAAA;AACR,CAAC;AAED,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AAC3B,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,GAAyB,EACzB,GAAwB,EACxB,WAAmB,EACnB,IAAY;IAEZ,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,oBAAoB,IAAI,EAAE,CAAC,CAAA;IAE/D,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC3C,OAAO,KAAK,CAAA;IACd,CAAC;IAED,qCAAqC;IACrC,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,aAAa,CAAA;IAEjE,MAAM,IAAI,GAAG,qBAAqB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;IACrD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;QACjB,cAAc,EAAE,0BAA0B;QAC1C,eAAe,EAAE,UAAU;KAC5B,CAAC,CAAA;IACF,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACb,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Dashboard page components
3
3
  *
4
- * @purpose Command center pages: journal-first overview, events, agents, projects, sessions, journal
4
+ * @purpose Command center pages: mode-aware overview, evals, portfolio, scope, service, events, agents, projects, sessions, journal
5
5
  */
6
6
  export declare function getPagesJS(): string;
7
7
  //# sourceMappingURL=pages.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/dashboard/pages.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAgB,UAAU,IAAI,MAAM,CA+tBnC"}
1
+ {"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/dashboard/pages.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAgB,UAAU,IAAI,MAAM,CAqiDnC"}