apteva 0.4.41 → 0.4.44

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 (102) hide show
  1. package/dist/ActivityPage.c48n83h2.js +3 -0
  2. package/dist/ApiDocsPage.yzcxx5ax.js +4 -0
  3. package/dist/App.09yb8t0b.js +1 -0
  4. package/dist/App.152mbs1r.js +4 -0
  5. package/dist/App.3a67nx9w.js +4 -0
  6. package/dist/App.9epx6785.js +4 -0
  7. package/dist/App.d8955awp.js +4 -0
  8. package/dist/App.drwb57jq.js +4 -0
  9. package/dist/App.gssbmajb.js +4 -0
  10. package/dist/App.qw70pc29.js +53 -0
  11. package/dist/{App.7fb3e7mp.js → App.qzbx5wtj.js} +1 -1
  12. package/dist/App.r5serxkt.js +8 -0
  13. package/dist/App.tpmp9020.js +20 -0
  14. package/dist/App.v2wb4d7d.js +61 -0
  15. package/dist/App.vxmaaj0m.js +13 -0
  16. package/dist/App.w4p2tda9.js +4 -0
  17. package/dist/App.wv2ng55q.js +221 -0
  18. package/dist/App.yncnrn0f.js +4 -0
  19. package/dist/ConnectionsPage.k6cspyqq.js +3 -0
  20. package/dist/McpPage.cdxm48xj.js +3 -0
  21. package/dist/SettingsPage.evpv7c2y.js +3 -0
  22. package/dist/SkillsPage.pvzp6c1a.js +3 -0
  23. package/dist/TasksPage.6jnvbpsy.js +3 -0
  24. package/dist/TelemetryPage.t7vk24zc.js +3 -0
  25. package/dist/TestsPage.5x6658aa.js +3 -0
  26. package/dist/ThreadsPage.3fvhtevh.js +3 -0
  27. package/dist/apteva-kit.css +1 -1
  28. package/dist/index.html +1 -1
  29. package/dist/styles.css +1 -1
  30. package/package.json +8 -8
  31. package/src/db.ts +19 -9
  32. package/src/integrations/agentdojo.ts +1 -0
  33. package/src/mcp-platform.ts +418 -63
  34. package/src/openapi.ts +96 -0
  35. package/src/providers.ts +50 -24
  36. package/src/routes/api/agent-utils.ts +0 -1
  37. package/src/routes/api/agents.ts +19 -1
  38. package/src/routes/api/meta-agent.ts +2 -0
  39. package/src/routes/api/system.ts +90 -1
  40. package/src/routes/api/telemetry.ts +19 -1
  41. package/src/routes/share.ts +85 -0
  42. package/src/server.ts +12 -0
  43. package/src/web/App.tsx +89 -11
  44. package/src/web/components/activity/ActivityPage.tsx +14 -14
  45. package/src/web/components/agents/AgentCard.tsx +14 -14
  46. package/src/web/components/agents/AgentPanel.tsx +358 -198
  47. package/src/web/components/agents/AgentsView.tsx +4 -4
  48. package/src/web/components/agents/CreateAgentModal.tsx +21 -79
  49. package/src/web/components/api/ApiDocsPage.tsx +66 -66
  50. package/src/web/components/auth/CreateAccountStep.tsx +16 -16
  51. package/src/web/components/auth/LoginPage.tsx +10 -10
  52. package/src/web/components/common/LoadingSpinner.tsx +2 -2
  53. package/src/web/components/common/Modal.tsx +8 -8
  54. package/src/web/components/common/Select.tsx +9 -9
  55. package/src/web/components/connections/ConnectionsPage.tsx +4 -4
  56. package/src/web/components/connections/IntegrationsTab.tsx +18 -18
  57. package/src/web/components/connections/OverviewTab.tsx +13 -13
  58. package/src/web/components/connections/TriggersTab.tsx +99 -99
  59. package/src/web/components/dashboard/Dashboard.tsx +32 -32
  60. package/src/web/components/layout/Header.tsx +50 -34
  61. package/src/web/components/layout/Sidebar.tsx +34 -15
  62. package/src/web/components/mcp/IntegrationsPanel.tsx +40 -40
  63. package/src/web/components/mcp/McpPage.tsx +208 -208
  64. package/src/web/components/meta-agent/MetaAgent.tsx +12 -10
  65. package/src/web/components/onboarding/OnboardingWizard.tsx +25 -25
  66. package/src/web/components/settings/SettingsPage.tsx +258 -175
  67. package/src/web/components/skills/SkillsPage.tsx +88 -88
  68. package/src/web/components/tasks/TasksPage.tsx +339 -54
  69. package/src/web/components/telemetry/TelemetryPage.tsx +135 -64
  70. package/src/web/components/tests/TestsPage.tsx +50 -50
  71. package/src/web/components/threads/ThreadsPage.tsx +23 -21
  72. package/src/web/context/ProjectContext.tsx +6 -1
  73. package/src/web/context/ThemeContext.tsx +69 -0
  74. package/src/web/context/index.ts +2 -0
  75. package/src/web/styles.css +5 -3
  76. package/src/web/themes.ts +99 -0
  77. package/src/web/types.ts +0 -4
  78. package/dist/ActivityPage.7907h64p.js +0 -3
  79. package/dist/ApiDocsPage.k3jjenpq.js +0 -4
  80. package/dist/App.01nq20st.js +0 -4
  81. package/dist/App.1maqvamf.js +0 -4
  82. package/dist/App.2yjrh32f.js +0 -4
  83. package/dist/App.3qw8nben.js +0 -20
  84. package/dist/App.7sy3wq8c.js +0 -4
  85. package/dist/App.apjrmctz.js +0 -57
  86. package/dist/App.av6t2yhe.js +0 -4
  87. package/dist/App.jqj5a094.js +0 -46
  88. package/dist/App.mc7xf85h.js +0 -4
  89. package/dist/App.myxqcj9x.js +0 -4
  90. package/dist/App.nm91r1mp.js +0 -13
  91. package/dist/App.p02f4ret.js +0 -1
  92. package/dist/App.qcknavjz.js +0 -221
  93. package/dist/App.vc7vfhg4.js +0 -4
  94. package/dist/App.z4s9zkw5.js +0 -4
  95. package/dist/ConnectionsPage.z1pw5xe2.js +0 -3
  96. package/dist/McpPage.8vc97z0b.js +0 -3
  97. package/dist/SettingsPage.p61bz8kd.js +0 -3
  98. package/dist/SkillsPage.r9x43g3g.js +0 -3
  99. package/dist/TasksPage.1e0zkye4.js +0 -3
  100. package/dist/TelemetryPage.p9vbe4gf.js +0 -3
  101. package/dist/TestsPage.d4xy504e.js +0 -3
  102. package/dist/ThreadsPage.m016am3x.js +0 -3
@@ -271,7 +271,7 @@ export function TestsPage() {
271
271
  running: "bg-blue-900/50 text-blue-400",
272
272
  };
273
273
  return (
274
- <span className={`px-2 py-0.5 rounded text-xs font-medium ${colors[status] || "bg-[#222] text-[#666]"}`}>
274
+ <span className={`px-2 py-0.5 rounded text-xs font-medium ${colors[status] || "bg-[var(--color-surface-raised)] text-[var(--color-text-muted)]"}`}>
275
275
  {status.toUpperCase()}
276
276
  </span>
277
277
  );
@@ -297,7 +297,7 @@ export function TestsPage() {
297
297
  <div className="flex items-center justify-between mb-6">
298
298
  <div>
299
299
  <h1 className="text-xl font-bold">Tests</h1>
300
- <p className="text-sm text-[#666] mt-1">
300
+ <p className="text-sm text-[var(--color-text-muted)] mt-1">
301
301
  Describe behavior, AI handles the rest
302
302
  </p>
303
303
  </div>
@@ -306,14 +306,14 @@ export function TestsPage() {
306
306
  <button
307
307
  onClick={handleRunAll}
308
308
  disabled={runningAll}
309
- className="px-4 py-2 bg-[#1a1a1a] hover:bg-[#222] text-[#e0e0e0] rounded text-sm font-medium transition disabled:opacity-50"
309
+ className="px-4 py-2 bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] text-[var(--color-text)] rounded text-sm font-medium transition disabled:opacity-50"
310
310
  >
311
311
  {runningAll ? "Running..." : "Run All"}
312
312
  </button>
313
313
  )}
314
314
  <button
315
315
  onClick={openCreate}
316
- className="px-4 py-2 bg-[#f97316] hover:bg-[#fb923c] text-white rounded text-sm font-medium transition"
316
+ className="px-4 py-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-white rounded text-sm font-medium transition"
317
317
  >
318
318
  + New Test
319
319
  </button>
@@ -322,19 +322,19 @@ export function TestsPage() {
322
322
 
323
323
  {/* Test list */}
324
324
  {loading ? (
325
- <div className="text-[#666] text-sm">Loading...</div>
325
+ <div className="text-[var(--color-text-muted)] text-sm">Loading...</div>
326
326
  ) : tests.length === 0 ? (
327
327
  <div className="text-center py-16">
328
- <div className="text-[#333] text-4xl mb-4">
328
+ <div className="text-[var(--color-border-light)] text-4xl mb-4">
329
329
  <svg className="w-12 h-12 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
330
330
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
331
331
  </svg>
332
332
  </div>
333
- <p className="text-[#666] mb-2">No tests yet</p>
334
- <p className="text-xs text-[#555] mb-4">Describe what your agents should do and let AI verify it</p>
333
+ <p className="text-[var(--color-text-muted)] mb-2">No tests yet</p>
334
+ <p className="text-xs text-[var(--color-text-faint)] mb-4">Describe what your agents should do and let AI verify it</p>
335
335
  <button
336
336
  onClick={openCreate}
337
- className="px-4 py-2 bg-[#f97316] hover:bg-[#fb923c] text-white rounded text-sm font-medium transition"
337
+ className="px-4 py-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-white rounded text-sm font-medium transition"
338
338
  >
339
339
  Create your first test
340
340
  </button>
@@ -342,7 +342,7 @@ export function TestsPage() {
342
342
  ) : (
343
343
  <div className="space-y-3">
344
344
  {tests.map(tc => (
345
- <div key={tc.id} className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4">
345
+ <div key={tc.id} className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
346
346
  <div className="flex items-start justify-between">
347
347
  <div className="flex-1 min-w-0">
348
348
  <div className="flex items-center gap-2 mb-1">
@@ -352,18 +352,18 @@ export function TestsPage() {
352
352
  : tc.last_run && (<>
353
353
  {statusBadge(tc.last_run.status)}
354
354
  {tc.last_run.score != null && (
355
- <span className="text-xs text-[#888] font-mono">{tc.last_run.score}/10</span>
355
+ <span className="text-xs text-[var(--color-text-secondary)] font-mono">{tc.last_run.score}/10</span>
356
356
  )}
357
357
  </>)
358
358
  }
359
359
  </div>
360
360
  {tc.behavior && (
361
- <p className="text-xs text-[#888] mb-1.5 line-clamp-2">{tc.behavior}</p>
361
+ <p className="text-xs text-[var(--color-text-secondary)] mb-1.5 line-clamp-2">{tc.behavior}</p>
362
362
  )}
363
- <div className="text-xs text-[#666] space-y-0.5">
363
+ <div className="text-xs text-[var(--color-text-muted)] space-y-0.5">
364
364
  <div>
365
365
  Agent:{" "}
366
- <span className="text-[#888]">
366
+ <span className="text-[var(--color-text-secondary)]">
367
367
  {tc.agent_name || (tc.last_run?.selected_agent_name
368
368
  ? `${tc.last_run.selected_agent_name} (auto-selected)`
369
369
  : "Auto (AI picks)")}
@@ -371,18 +371,18 @@ export function TestsPage() {
371
371
  </div>
372
372
  {tc.last_run?.generated_message && (
373
373
  <div className="truncate">
374
- Message: <span className="text-[#888]">"{tc.last_run.generated_message}"</span>
374
+ Message: <span className="text-[var(--color-text-secondary)]">"{tc.last_run.generated_message}"</span>
375
375
  </div>
376
376
  )}
377
377
  {tc.input_message && !tc.last_run?.generated_message && (
378
378
  <div className="truncate">
379
- Message: <span className="text-[#888]">"{tc.input_message}"</span>
379
+ Message: <span className="text-[var(--color-text-secondary)]">"{tc.input_message}"</span>
380
380
  </div>
381
381
  )}
382
382
  {tc.last_run && (
383
383
  <div>
384
384
  Last run:{" "}
385
- <span className="text-[#888]">
385
+ <span className="text-[var(--color-text-secondary)]">
386
386
  {tc.last_run.duration_ms ? `${(tc.last_run.duration_ms / 1000).toFixed(1)}s` : "---"}
387
387
  {tc.last_run.judge_reasoning && ` --- "${tc.last_run.judge_reasoning.slice(0, 80)}${tc.last_run.judge_reasoning.length > 80 ? "..." : ""}"`}
388
388
  </span>
@@ -393,7 +393,7 @@ export function TestsPage() {
393
393
  <div className="flex items-center gap-1 ml-3 shrink-0">
394
394
  <button
395
395
  onClick={() => viewRuns(tc.id)}
396
- className="px-2 py-1 text-xs text-[#666] hover:text-[#888] hover:bg-[#1a1a1a] rounded transition"
396
+ className="px-2 py-1 text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-raised)] rounded transition"
397
397
  title="View run history"
398
398
  >
399
399
  History
@@ -401,19 +401,19 @@ export function TestsPage() {
401
401
  <button
402
402
  onClick={() => handleRun(tc.id)}
403
403
  disabled={runningTests.has(tc.id)}
404
- className="px-3 py-1 text-xs bg-[#1a1a1a] hover:bg-[#222] text-[#e0e0e0] rounded transition disabled:opacity-50"
404
+ className="px-3 py-1 text-xs bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] text-[var(--color-text)] rounded transition disabled:opacity-50"
405
405
  >
406
406
  {runningTests.has(tc.id) ? "Running..." : "Run"}
407
407
  </button>
408
408
  <button
409
409
  onClick={() => openEdit(tc)}
410
- className="px-2 py-1 text-xs text-[#666] hover:text-[#888] hover:bg-[#1a1a1a] rounded transition"
410
+ className="px-2 py-1 text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-raised)] rounded transition"
411
411
  >
412
412
  Edit
413
413
  </button>
414
414
  <button
415
415
  onClick={() => handleDelete(tc.id)}
416
- className="px-2 py-1 text-xs text-[#666] hover:text-red-400 hover:bg-[#1a1a1a] rounded transition"
416
+ className="px-2 py-1 text-xs text-[var(--color-text-muted)] hover:text-red-400 hover:bg-[var(--color-surface-raised)] rounded transition"
417
417
  >
418
418
  Delete
419
419
  </button>
@@ -428,50 +428,50 @@ export function TestsPage() {
428
428
  {selectedRuns && (
429
429
  <div className="mt-6">
430
430
  <div className="flex items-center justify-between mb-3">
431
- <h2 className="text-sm font-bold text-[#888]">Run History</h2>
431
+ <h2 className="text-sm font-bold text-[var(--color-text-secondary)]">Run History</h2>
432
432
  <button
433
433
  onClick={() => { setSelectedRuns(null); setExpandedRun(null); }}
434
- className="text-xs text-[#666] hover:text-[#888]"
434
+ className="text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]"
435
435
  >
436
436
  Close
437
437
  </button>
438
438
  </div>
439
439
  {selectedRuns.runs.length === 0 ? (
440
- <p className="text-sm text-[#666]">No runs yet</p>
440
+ <p className="text-sm text-[var(--color-text-muted)]">No runs yet</p>
441
441
  ) : (
442
442
  <div className="space-y-2">
443
443
  {selectedRuns.runs.map(run => (
444
- <div key={run.id} className="bg-[#0d0d0d] border border-[#1a1a1a] rounded p-3">
444
+ <div key={run.id} className="bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded p-3">
445
445
  <div
446
446
  className="flex items-center justify-between cursor-pointer"
447
447
  onClick={() => setExpandedRun(expandedRun === run.id ? null : run.id)}
448
448
  >
449
449
  <div className="flex items-center gap-3">
450
450
  {statusBadge(run.status)}
451
- <span className="text-xs text-[#666]">
451
+ <span className="text-xs text-[var(--color-text-muted)]">
452
452
  {run.duration_ms ? `${(run.duration_ms / 1000).toFixed(1)}s` : "---"}
453
453
  </span>
454
454
  {run.score != null && (
455
- <span className="text-xs text-[#888] font-mono">{run.score}/10</span>
455
+ <span className="text-xs text-[var(--color-text-secondary)] font-mono">{run.score}/10</span>
456
456
  )}
457
457
  {run.selected_agent_name && (
458
- <span className="text-xs text-[#555]">
458
+ <span className="text-xs text-[var(--color-text-faint)]">
459
459
  Agent: {run.selected_agent_name}
460
460
  </span>
461
461
  )}
462
- <span className="text-xs text-[#555]">
462
+ <span className="text-xs text-[var(--color-text-faint)]">
463
463
  {new Date(run.created_at).toLocaleString()}
464
464
  </span>
465
465
  </div>
466
- <span className="text-xs text-[#555]">{expandedRun === run.id ? "---" : "+"}</span>
466
+ <span className="text-xs text-[var(--color-text-faint)]">{expandedRun === run.id ? "---" : "+"}</span>
467
467
  </div>
468
468
  {expandedRun === run.id && (
469
469
  <div className="mt-3 space-y-2">
470
470
  {run.planner_reasoning && (
471
471
  <div>
472
- <div className="text-xs text-[#666] mb-1">Planner:</div>
473
- <div className="text-sm text-[#aaa] bg-[#0a0a0a] p-2 rounded">
474
- {run.selected_agent_name && <span className="text-[#f97316]">{run.selected_agent_name}</span>}
472
+ <div className="text-xs text-[var(--color-text-muted)] mb-1">Planner:</div>
473
+ <div className="text-sm text-[var(--color-text-secondary)] bg-[var(--color-bg)] p-2 rounded">
474
+ {run.selected_agent_name && <span className="text-[var(--color-accent)]">{run.selected_agent_name}</span>}
475
475
  {run.selected_agent_name && " --- "}
476
476
  {run.planner_reasoning}
477
477
  </div>
@@ -479,26 +479,26 @@ export function TestsPage() {
479
479
  )}
480
480
  {run.generated_message && (
481
481
  <div>
482
- <div className="text-xs text-[#666] mb-1">Generated Message:</div>
483
- <div className="text-sm text-[#aaa] bg-[#0a0a0a] p-2 rounded">"{run.generated_message}"</div>
482
+ <div className="text-xs text-[var(--color-text-muted)] mb-1">Generated Message:</div>
483
+ <div className="text-sm text-[var(--color-text-secondary)] bg-[var(--color-bg)] p-2 rounded">"{run.generated_message}"</div>
484
484
  </div>
485
485
  )}
486
486
  {run.judge_reasoning && (
487
487
  <div>
488
- <div className="text-xs text-[#666] mb-1">Judge:</div>
489
- <div className="text-sm text-[#aaa] bg-[#0a0a0a] p-2 rounded">{run.judge_reasoning}</div>
488
+ <div className="text-xs text-[var(--color-text-muted)] mb-1">Judge:</div>
489
+ <div className="text-sm text-[var(--color-text-secondary)] bg-[var(--color-bg)] p-2 rounded">{run.judge_reasoning}</div>
490
490
  </div>
491
491
  )}
492
492
  {run.error && (
493
493
  <div>
494
494
  <div className="text-xs text-red-400 mb-1">Error:</div>
495
- <div className="text-sm text-red-300 bg-[#0a0a0a] p-2 rounded">{run.error}</div>
495
+ <div className="text-sm text-red-300 bg-[var(--color-bg)] p-2 rounded">{run.error}</div>
496
496
  </div>
497
497
  )}
498
498
  {run.agent_response && (
499
499
  <div>
500
- <div className="text-xs text-[#666] mb-1">Agent Response (Thread):</div>
501
- <pre className="text-xs text-[#888] bg-[#0a0a0a] p-2 rounded overflow-auto max-h-64">
500
+ <div className="text-xs text-[var(--color-text-muted)] mb-1">Agent Response (Thread):</div>
501
+ <pre className="text-xs text-[var(--color-text-secondary)] bg-[var(--color-bg)] p-2 rounded overflow-auto max-h-64">
502
502
  {run.agent_response}
503
503
  </pre>
504
504
  </div>
@@ -515,34 +515,34 @@ export function TestsPage() {
515
515
  {/* Create/Edit Form Modal */}
516
516
  {showForm && (
517
517
  <div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center" onClick={() => setShowForm(false)}>
518
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg w-full max-w-lg mx-4 p-6" onClick={e => e.stopPropagation()}>
518
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg w-full max-w-lg mx-4 p-6" onClick={e => e.stopPropagation()}>
519
519
  <h2 className="text-lg font-bold mb-4">{editingTest ? "Edit Test" : "New Test"}</h2>
520
520
 
521
521
  <div className="space-y-4">
522
522
  <div>
523
- <label className="block text-xs text-[#666] mb-1">Name</label>
523
+ <label className="block text-xs text-[var(--color-text-muted)] mb-1">Name</label>
524
524
  <input
525
525
  value={formName}
526
526
  onChange={e => setFormName(e.target.value)}
527
527
  placeholder="e.g. Social Media Posting"
528
- className="w-full bg-[#0a0a0a] border border-[#222] rounded px-3 py-2 text-sm focus:outline-none focus:border-[#f97316]"
528
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 text-sm focus:outline-none focus:border-[var(--color-accent)]"
529
529
  />
530
530
  </div>
531
531
 
532
532
  <div>
533
- <label className="block text-xs text-[#666] mb-1">Behavior</label>
533
+ <label className="block text-xs text-[var(--color-text-muted)] mb-1">Behavior</label>
534
534
  <textarea
535
535
  value={formBehavior}
536
536
  onChange={e => setFormBehavior(e.target.value)}
537
537
  placeholder="Describe what should happen, e.g. 'When asked to post on social media, the agent creates a proper post with relevant hashtags and confirms it was published'"
538
538
  rows={3}
539
- className="w-full bg-[#0a0a0a] border border-[#222] rounded px-3 py-2 text-sm focus:outline-none focus:border-[#f97316] resize-none"
539
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 text-sm focus:outline-none focus:border-[var(--color-accent)] resize-none"
540
540
  />
541
- <p className="text-xs text-[#555] mt-1">AI will generate the test message and evaluate results based on this</p>
541
+ <p className="text-xs text-[var(--color-text-faint)] mt-1">AI will generate the test message and evaluate results based on this</p>
542
542
  </div>
543
543
 
544
544
  <div>
545
- <label className="block text-xs text-[#666] mb-1">Agent</label>
545
+ <label className="block text-xs text-[var(--color-text-muted)] mb-1">Agent</label>
546
546
  <Select
547
547
  value={formAgentId}
548
548
  onChange={setFormAgentId}
@@ -552,21 +552,21 @@ export function TestsPage() {
552
552
  label: `${a.name} (${a.status})`,
553
553
  }))}
554
554
  />
555
- <p className="text-xs text-[#555] mt-1">Leave empty to let AI choose the right agent</p>
555
+ <p className="text-xs text-[var(--color-text-faint)] mt-1">Leave empty to let AI choose the right agent</p>
556
556
  </div>
557
557
  </div>
558
558
 
559
559
  <div className="flex justify-end gap-2 mt-6">
560
560
  <button
561
561
  onClick={() => setShowForm(false)}
562
- className="px-4 py-2 text-sm text-[#888] hover:text-[#e0e0e0] transition"
562
+ className="px-4 py-2 text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text)] transition"
563
563
  >
564
564
  Cancel
565
565
  </button>
566
566
  <button
567
567
  onClick={handleSave}
568
568
  disabled={!formName || !formBehavior}
569
- className="px-4 py-2 bg-[#f97316] hover:bg-[#fb923c] disabled:opacity-50 text-white rounded text-sm font-medium transition"
569
+ className="px-4 py-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 text-white rounded text-sm font-medium transition"
570
570
  >
571
571
  {editingTest ? "Save" : "Create"}
572
572
  </button>
@@ -1,6 +1,6 @@
1
1
  import React, { useState, useEffect, useCallback, useMemo } from "react";
2
2
  import { Chat, convertApiMessages } from "@apteva/apteva-kit";
3
- import { useAgentActivity, useAuth, useProjects, useTelemetryContext } from "../../context";
3
+ import { useAgentActivity, useAuth, useProjects, useTelemetryContext, useTheme } from "../../context";
4
4
  import type { TelemetryEvent } from "../../context";
5
5
  import type { Agent, Route } from "../../types";
6
6
 
@@ -20,6 +20,7 @@ interface ThreadsPageProps {
20
20
  }
21
21
 
22
22
  export function ThreadsPage({ agents, onNavigate }: ThreadsPageProps) {
23
+ const { theme } = useTheme();
23
24
  const { authFetch } = useAuth();
24
25
  const { currentProjectId } = useProjects();
25
26
  const { events: realtimeEvents, statusChangeCounter } = useTelemetryContext();
@@ -146,7 +147,7 @@ export function ThreadsPage({ agents, onNavigate }: ThreadsPageProps) {
146
147
  <div className="px-6 pt-6 pb-4 shrink-0">
147
148
  <div className="flex items-center justify-between">
148
149
  <h1 className="text-xl font-semibold">Threads</h1>
149
- <span className="text-sm text-[#666]">
150
+ <span className="text-sm text-[var(--color-text-muted)]">
150
151
  {threads.length} threads from {runningCount} running agents
151
152
  </span>
152
153
  </div>
@@ -162,7 +163,7 @@ export function ThreadsPage({ agents, onNavigate }: ThreadsPageProps) {
162
163
  <button
163
164
  onClick={() => setShowAgentPicker(!showAgentPicker)}
164
165
  disabled={runningAgents.length === 0}
165
- className="w-full flex items-center justify-center gap-2 px-3 py-2 rounded-lg bg-[#f97316]/10 text-[#f97316] text-sm font-medium hover:bg-[#f97316]/20 transition disabled:opacity-30 disabled:cursor-not-allowed"
166
+ className="w-full flex items-center justify-center gap-2 px-3 py-2 rounded-lg bg-[var(--color-accent-10)] text-[var(--color-accent)] text-sm font-medium hover:bg-[var(--color-accent-20)] transition disabled:opacity-30 disabled:cursor-not-allowed"
166
167
  >
167
168
  <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
168
169
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
@@ -174,15 +175,15 @@ export function ThreadsPage({ agents, onNavigate }: ThreadsPageProps) {
174
175
  {showAgentPicker && (
175
176
  <>
176
177
  <div className="fixed inset-0 z-40" onClick={() => setShowAgentPicker(false)} />
177
- <div className="absolute top-full left-0 right-0 mt-1 bg-[#111] border border-[#222] rounded-lg shadow-xl z-50 max-h-60 overflow-auto">
178
+ <div className="absolute top-full left-0 right-0 mt-1 bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded-lg shadow-xl z-50 max-h-60 overflow-auto">
178
179
  {runningAgents.map(agent => (
179
180
  <button
180
181
  key={agent.id}
181
182
  onClick={() => startNewChat(agent)}
182
- className="w-full text-left px-3 py-2.5 hover:bg-[#1a1a1a] transition"
183
+ className="w-full text-left px-3 py-2.5 hover:bg-[var(--color-surface-raised)] transition"
183
184
  >
184
185
  <p className="text-sm font-medium truncate">{agent.name}</p>
185
- <p className="text-[10px] text-[#555]">{agent.provider} · {agent.model}</p>
186
+ <p className="text-[10px] text-[var(--color-text-faint)]">{agent.provider} · {agent.model}</p>
186
187
  </button>
187
188
  ))}
188
189
  </div>
@@ -193,11 +194,11 @@ export function ThreadsPage({ agents, onNavigate }: ThreadsPageProps) {
193
194
 
194
195
  <div className="flex-1 overflow-auto px-2 pb-2">
195
196
  {loadingThreads ? (
196
- <div className="p-6 text-center text-[#555] text-sm">Loading threads...</div>
197
+ <div className="p-6 text-center text-[var(--color-text-faint)] text-sm">Loading threads...</div>
197
198
  ) : threads.length === 0 ? (
198
- <div className="p-6 text-center text-[#555] text-sm">
199
+ <div className="p-6 text-center text-[var(--color-text-faint)] text-sm">
199
200
  <p>No threads yet</p>
200
- <p className="mt-1 text-[#444]">Start a conversation or wait for agents</p>
201
+ <p className="mt-1 text-[var(--color-text-faint)]">Start a conversation or wait for agents</p>
201
202
  </div>
202
203
  ) : (
203
204
  <div className="space-y-0.5">
@@ -219,7 +220,7 @@ export function ThreadsPage({ agents, onNavigate }: ThreadsPageProps) {
219
220
  <div className="flex-1 flex flex-col min-h-0 overflow-hidden">
220
221
  {chatAgentId && chatKey ? (
221
222
  loadingMessages ? (
222
- <div className="flex-1 flex items-center justify-center text-[#666]">Loading messages...</div>
223
+ <div className="flex-1 flex items-center justify-center text-[var(--color-text-muted)]">Loading messages...</div>
223
224
  ) : (
224
225
  <Chat
225
226
  key={chatKey}
@@ -230,17 +231,18 @@ export function ThreadsPage({ agents, onNavigate }: ThreadsPageProps) {
230
231
  placeholder={`Message ${chatAgentName}...`}
231
232
  headerTitle={chatAgentName}
232
233
  variant="terminal"
234
+ theme={theme.id as "light" | "dark"}
233
235
  showHeader={true}
234
236
  />
235
237
  )
236
238
  ) : (
237
239
  <div className="flex-1 flex items-center justify-center">
238
- <div className="text-center text-[#555]">
239
- <svg className="w-12 h-12 mx-auto mb-3 text-[#333]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
240
+ <div className="text-center text-[var(--color-text-faint)]">
241
+ <svg className="w-12 h-12 mx-auto mb-3 text-[var(--color-border-light)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
240
242
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
241
243
  </svg>
242
244
  <p className="text-sm">Select a thread or start a new conversation</p>
243
- <p className="text-xs text-[#444] mt-1">Chat with any running agent</p>
245
+ <p className="text-xs text-[var(--color-text-faint)] mt-1">Chat with any running agent</p>
244
246
  </div>
245
247
  </div>
246
248
  )}
@@ -267,32 +269,32 @@ function ThreadRow({ thread, selected, activities, onSelect }: {
267
269
  onClick={onSelect}
268
270
  className={`w-full text-left px-3 py-2.5 rounded-lg transition ${
269
271
  selected
270
- ? "bg-[#f97316]/10"
271
- : "hover:bg-[#151515]"
272
+ ? "bg-[var(--color-accent-10)]"
273
+ : "hover:bg-[var(--color-bg-secondary)]"
272
274
  }`}
273
275
  >
274
276
  <div className="flex items-center justify-between gap-2 mb-1">
275
277
  <span className="text-sm font-medium truncate">
276
278
  {thread.title || `Thread ${thread.id.slice(0, 8)}`}
277
279
  </span>
278
- <span className="text-[10px] text-[#555] shrink-0">{timeAgo(thread.updated_at)}</span>
280
+ <span className="text-[10px] text-[var(--color-text-faint)] shrink-0">{timeAgo(thread.updated_at)}</span>
279
281
  </div>
280
282
  <div className="flex items-center gap-1.5">
281
283
  <span
282
284
  className={`w-1.5 h-1.5 rounded-full shrink-0 ${
283
- isActive ? "bg-green-400 animate-pulse" : "bg-[#444]"
285
+ isActive ? "bg-green-400 animate-pulse" : "bg-[var(--color-scrollbar)]"
284
286
  }`}
285
287
  />
286
- <span className="text-[11px] text-[#f97316]">{thread.agent_name}</span>
288
+ <span className="text-[11px] text-[var(--color-accent)]">{thread.agent_name}</span>
287
289
  {thread.message_count != null && (
288
290
  <>
289
- <span className="text-[#333]">&middot;</span>
290
- <span className="text-[10px] text-[#555]">{thread.message_count} msgs</span>
291
+ <span className="text-[var(--color-border-light)]">&middot;</span>
292
+ <span className="text-[10px] text-[var(--color-text-faint)]">{thread.message_count} msgs</span>
291
293
  </>
292
294
  )}
293
295
  </div>
294
296
  {activityText && (
295
- <p className="text-[11px] text-[#555] truncate mt-1">{activityText}</p>
297
+ <p className="text-[11px] text-[var(--color-text-faint)] truncate mt-1">{activityText}</p>
296
298
  )}
297
299
  </button>
298
300
  );
@@ -20,6 +20,7 @@ interface ProjectContextValue {
20
20
  unassignedCount: number;
21
21
  projectsEnabled: boolean; // Feature flag
22
22
  metaAgentEnabled: boolean; // Feature flag
23
+ costTrackingEnabled: boolean; // Feature flag
23
24
  setCurrentProjectId: (id: string | null) => void;
24
25
  createProject: (data: { name: string; description?: string; color?: string }) => Promise<Project | null>;
25
26
  updateProject: (id: string, data: { name?: string; description?: string; color?: string }) => Promise<Project | null>;
@@ -58,6 +59,7 @@ export function ProjectProvider({ children }: ProjectProviderProps) {
58
59
  const [unassignedCount, setUnassignedCount] = useState(0);
59
60
  const [projectsEnabled, setProjectsEnabled] = useState(false);
60
61
  const [metaAgentEnabled, setMetaAgentEnabled] = useState(false);
62
+ const [costTrackingEnabled, setCostTrackingEnabled] = useState(true);
61
63
 
62
64
  // Fetch feature flags on mount
63
65
  useEffect(() => {
@@ -66,10 +68,12 @@ export function ProjectProvider({ children }: ProjectProviderProps) {
66
68
  .then(data => {
67
69
  setProjectsEnabled(data.projects === true);
68
70
  setMetaAgentEnabled(data.metaAgent === true);
71
+ setCostTrackingEnabled(data.costTracking !== false);
69
72
  })
70
73
  .catch(() => {
71
74
  setProjectsEnabled(false);
72
75
  setMetaAgentEnabled(false);
76
+ setCostTrackingEnabled(true);
73
77
  });
74
78
  }, []);
75
79
 
@@ -198,12 +202,13 @@ export function ProjectProvider({ children }: ProjectProviderProps) {
198
202
  unassignedCount,
199
203
  projectsEnabled,
200
204
  metaAgentEnabled,
205
+ costTrackingEnabled,
201
206
  setCurrentProjectId,
202
207
  createProject,
203
208
  updateProject,
204
209
  deleteProject,
205
210
  refreshProjects,
206
- }), [projects, currentProjectId, currentProject, isLoading, error, unassignedCount, projectsEnabled, metaAgentEnabled, setCurrentProjectId, createProject, updateProject, deleteProject, refreshProjects]);
211
+ }), [projects, currentProjectId, currentProject, isLoading, error, unassignedCount, projectsEnabled, metaAgentEnabled, costTrackingEnabled, setCurrentProjectId, createProject, updateProject, deleteProject, refreshProjects]);
207
212
 
208
213
  return <ProjectContext.Provider value={value}>{children}</ProjectContext.Provider>;
209
214
  }
@@ -0,0 +1,69 @@
1
+ import React, { createContext, useContext, useState, useEffect, useCallback, useMemo, type ReactNode } from "react";
2
+ import { themes, resolveTheme, type ThemeMode, type Theme } from "../themes";
3
+
4
+ interface ThemeContextValue {
5
+ mode: ThemeMode;
6
+ theme: Theme; // resolved theme
7
+ setMode: (mode: ThemeMode) => void;
8
+ }
9
+
10
+ const ThemeContext = createContext<ThemeContextValue | null>(null);
11
+
12
+ export function useTheme(): ThemeContextValue {
13
+ const context = useContext(ThemeContext);
14
+ if (!context) {
15
+ throw new Error("useTheme must be used within a ThemeProvider");
16
+ }
17
+ return context;
18
+ }
19
+
20
+ const STORAGE_KEY = "apteva_theme_mode";
21
+
22
+ function getSystemPrefersDark(): boolean {
23
+ if (typeof window === "undefined") return true;
24
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
25
+ }
26
+
27
+ function applyTheme(theme: Theme) {
28
+ const root = document.documentElement;
29
+ for (const [key, value] of Object.entries(theme.colors)) {
30
+ root.style.setProperty(key, value);
31
+ }
32
+ root.setAttribute("data-theme", theme.id);
33
+ }
34
+
35
+ export function ThemeProvider({ children }: { children: ReactNode }) {
36
+ const [mode, setModeState] = useState<ThemeMode>(() => {
37
+ if (typeof window !== "undefined") {
38
+ const stored = localStorage.getItem(STORAGE_KEY);
39
+ if (stored === "dark" || stored === "light" || stored === "auto") return stored;
40
+ }
41
+ return "auto";
42
+ });
43
+
44
+ const [prefersDark, setPrefersDark] = useState(getSystemPrefersDark);
45
+
46
+ // Listen for system theme changes
47
+ useEffect(() => {
48
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
49
+ const handler = (e: MediaQueryListEvent) => setPrefersDark(e.matches);
50
+ mq.addEventListener("change", handler);
51
+ return () => mq.removeEventListener("change", handler);
52
+ }, []);
53
+
54
+ const theme = useMemo(() => resolveTheme(mode, prefersDark), [mode, prefersDark]);
55
+
56
+ // Apply CSS variables whenever theme changes
57
+ useEffect(() => {
58
+ applyTheme(theme);
59
+ }, [theme]);
60
+
61
+ const setMode = useCallback((newMode: ThemeMode) => {
62
+ setModeState(newMode);
63
+ localStorage.setItem(STORAGE_KEY, newMode);
64
+ }, []);
65
+
66
+ const value = useMemo(() => ({ mode, theme, setMode }), [mode, theme, setMode]);
67
+
68
+ return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
69
+ }
@@ -5,3 +5,5 @@ export { AuthProvider, useAuth, useAuthHeaders } from "./AuthContext";
5
5
 
6
6
  export { ProjectProvider, useProjects } from "./ProjectContext";
7
7
  export type { Project } from "./ProjectContext";
8
+
9
+ export { ThemeProvider, useTheme } from "./ThemeContext";
@@ -3,7 +3,8 @@
3
3
  @tailwind utilities;
4
4
 
5
5
  html, body {
6
- background-color: #0a0a0a;
6
+ background-color: var(--color-bg, #0a0a0a);
7
+ color: var(--color-text, #e0e0e0);
7
8
  min-height: 100%;
8
9
  margin: 0;
9
10
  -webkit-font-smoothing: antialiased;
@@ -20,8 +21,8 @@ html, body {
20
21
  }
21
22
 
22
23
  ::selection {
23
- background-color: #f97316;
24
- color: #0a0a0a;
24
+ background-color: var(--color-selection-bg, #f97316);
25
+ color: var(--color-selection-text, #0a0a0a);
25
26
  }
26
27
 
27
28
  .line-clamp-2 {
@@ -67,3 +68,4 @@ html, body {
67
68
  animation: slideIn 0.5s cubic-bezier(0.16, 1, 0.3, 1), highlightFade 2s 0.5s ease-out;
68
69
  overflow: hidden;
69
70
  }
71
+