apteva 0.4.32 → 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 (113) 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.qzbx5wtj.js +4 -0
  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 +10 -9
  31. package/src/crypto.ts +4 -3
  32. package/src/db.ts +171 -36
  33. package/src/integrations/agentdojo.ts +95 -12
  34. package/src/integrations/index.ts +7 -0
  35. package/src/mcp-platform.ts +870 -142
  36. package/src/openapi.ts +96 -0
  37. package/src/providers.ts +60 -34
  38. package/src/routes/api/agent-utils.ts +59 -47
  39. package/src/routes/api/agents.ts +71 -2
  40. package/src/routes/api/integrations.ts +11 -5
  41. package/src/routes/api/mcp.ts +5 -4
  42. package/src/routes/api/meta-agent.ts +37 -1
  43. package/src/routes/api/projects.ts +3 -3
  44. package/src/routes/api/providers.ts +121 -30
  45. package/src/routes/api/skills.ts +2 -3
  46. package/src/routes/api/system.ts +98 -14
  47. package/src/routes/api/telemetry.ts +19 -1
  48. package/src/routes/share.ts +85 -0
  49. package/src/server.ts +43 -32
  50. package/src/triggers/agentdojo.ts +2 -2
  51. package/src/web/App.tsx +107 -21
  52. package/src/web/components/activity/ActivityPage.tsx +242 -389
  53. package/src/web/components/agents/AgentCard.tsx +19 -27
  54. package/src/web/components/agents/AgentPanel.tsx +358 -198
  55. package/src/web/components/agents/AgentsView.tsx +4 -4
  56. package/src/web/components/agents/CreateAgentModal.tsx +21 -79
  57. package/src/web/components/api/ApiDocsPage.tsx +66 -66
  58. package/src/web/components/auth/CreateAccountStep.tsx +16 -16
  59. package/src/web/components/auth/LoginPage.tsx +10 -10
  60. package/src/web/components/common/Icons.tsx +8 -0
  61. package/src/web/components/common/LoadingSpinner.tsx +2 -2
  62. package/src/web/components/common/Modal.tsx +8 -8
  63. package/src/web/components/common/Select.tsx +11 -10
  64. package/src/web/components/connections/ConnectionsPage.tsx +4 -4
  65. package/src/web/components/connections/IntegrationsTab.tsx +18 -18
  66. package/src/web/components/connections/OverviewTab.tsx +13 -13
  67. package/src/web/components/connections/TriggersTab.tsx +99 -99
  68. package/src/web/components/dashboard/Dashboard.tsx +177 -52
  69. package/src/web/components/index.ts +1 -1
  70. package/src/web/components/layout/Header.tsx +50 -34
  71. package/src/web/components/layout/Sidebar.tsx +41 -16
  72. package/src/web/components/mcp/IntegrationsPanel.tsx +160 -69
  73. package/src/web/components/mcp/McpPage.tsx +218 -209
  74. package/src/web/components/meta-agent/MetaAgent.tsx +15 -11
  75. package/src/web/components/onboarding/OnboardingWizard.tsx +25 -25
  76. package/src/web/components/settings/SettingsPage.tsx +389 -221
  77. package/src/web/components/skills/SkillsPage.tsx +88 -88
  78. package/src/web/components/tasks/TasksPage.tsx +385 -68
  79. package/src/web/components/telemetry/TelemetryPage.tsx +294 -39
  80. package/src/web/components/tests/TestsPage.tsx +50 -50
  81. package/src/web/components/threads/ThreadsPage.tsx +315 -0
  82. package/src/web/context/AuthContext.tsx +3 -3
  83. package/src/web/context/ProjectContext.tsx +8 -3
  84. package/src/web/context/TelemetryContext.tsx +24 -6
  85. package/src/web/context/ThemeContext.tsx +69 -0
  86. package/src/web/context/index.ts +3 -1
  87. package/src/web/styles.css +25 -7
  88. package/src/web/themes.ts +99 -0
  89. package/src/web/types.ts +4 -7
  90. package/dist/ActivityPage.41nbye4r.js +0 -3
  91. package/dist/ApiDocsPage.4smnt8m3.js +0 -4
  92. package/dist/App.0sbax9et.js +0 -4
  93. package/dist/App.0ws427h8.js +0 -4
  94. package/dist/App.6q6bar8b.js +0 -4
  95. package/dist/App.80301vdb.js +0 -4
  96. package/dist/App.af2wg84v.js +0 -267
  97. package/dist/App.ca1rz1ph.js +0 -4
  98. package/dist/App.ensa6z0r.js +0 -4
  99. package/dist/App.f8g7tych.js +0 -13
  100. package/dist/App.mvtqv6qc.js +0 -20
  101. package/dist/App.ncgc9cxy.js +0 -4
  102. package/dist/App.p02f4ret.js +0 -1
  103. package/dist/App.p0fb1pds.js +0 -4
  104. package/dist/App.pmaq48sj.js +0 -4
  105. package/dist/App.yv87t9m5.js +0 -4
  106. package/dist/App.zjmfm8p6.js +0 -4
  107. package/dist/ConnectionsPage.anb3rv9a.js +0 -3
  108. package/dist/McpPage.y396h6fy.js +0 -3
  109. package/dist/SettingsPage.p1hc60gk.js +0 -3
  110. package/dist/SkillsPage.yj3xdsay.js +0 -3
  111. package/dist/TasksPage.sjv0khtv.js +0 -3
  112. package/dist/TelemetryPage.2qm4w16r.js +0 -3
  113. package/dist/TestsPage.zzs4qfj8.js +0 -3
@@ -2,7 +2,8 @@ import React, { useState, useEffect } from "react";
2
2
  import { CheckIcon, CloseIcon, PlusIcon } from "../common/Icons";
3
3
  import { Modal, useConfirm } from "../common/Modal";
4
4
  import { Select } from "../common/Select";
5
- import { useProjects, useAuth, type Project } from "../../context";
5
+ import { useProjects, useAuth, useTheme, type Project } from "../../context";
6
+ import type { ThemeMode } from "../../themes";
6
7
  import type { Provider } from "../../types";
7
8
 
8
9
  type SettingsTab = "general" | "providers" | "projects" | "channels" | "api-keys" | "account" | "updates" | "data" | "assistant";
@@ -26,7 +27,7 @@ export function SettingsPage() {
26
27
  return (
27
28
  <div className="flex-1 flex flex-col md:flex-row overflow-hidden">
28
29
  {/* Mobile: Horizontal scrolling tabs */}
29
- <div className="md:hidden border-b border-[#1a1a1a] bg-[#0a0a0a]">
30
+ <div className="md:hidden border-b border-[var(--color-border)] bg-[var(--color-bg)]">
30
31
  <div className="flex overflow-x-auto" style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}>
31
32
  {tabs.map(tab => (
32
33
  <button
@@ -34,8 +35,8 @@ export function SettingsPage() {
34
35
  onClick={() => setActiveTab(tab.key)}
35
36
  className={`flex-shrink-0 px-4 py-3 text-sm font-medium border-b-2 transition ${
36
37
  activeTab === tab.key
37
- ? "border-[#f97316] text-[#f97316]"
38
- : "border-transparent text-[#666] hover:text-[#888]"
38
+ ? "border-[var(--color-accent)] text-[var(--color-accent)]"
39
+ : "border-transparent text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]"
39
40
  }`}
40
41
  >
41
42
  {tab.label}
@@ -45,8 +46,8 @@ export function SettingsPage() {
45
46
  </div>
46
47
 
47
48
  {/* Desktop: Settings Sidebar */}
48
- <div className="hidden md:block w-48 border-r border-[#1a1a1a] p-4 flex-shrink-0">
49
- <h2 className="text-sm font-medium text-[#666] uppercase tracking-wider mb-3">Settings</h2>
49
+ <div className="hidden md:block w-48 border-r border-[var(--color-border)] p-4 flex-shrink-0">
50
+ <h2 className="text-sm font-medium text-[var(--color-text-muted)] uppercase tracking-wider mb-3">Settings</h2>
50
51
  <nav className="space-y-1">
51
52
  {tabs.map(tab => (
52
53
  <SettingsNavItem
@@ -89,8 +90,8 @@ function SettingsNavItem({
89
90
  onClick={onClick}
90
91
  className={`w-full text-left px-3 py-2 rounded text-sm transition ${
91
92
  active
92
- ? "bg-[#1a1a1a] text-[#e0e0e0]"
93
- : "text-[#666] hover:bg-[#111] hover:text-[#888]"
93
+ ? "bg-[var(--color-surface-raised)] text-[var(--color-text)]"
94
+ : "text-[var(--color-text-muted)] hover:bg-[var(--color-surface)] hover:text-[var(--color-text-secondary)]"
94
95
  }`}
95
96
  >
96
97
  {label}
@@ -100,6 +101,7 @@ function SettingsNavItem({
100
101
 
101
102
  function GeneralSettings() {
102
103
  const { authFetch } = useAuth();
104
+ const { mode, setMode } = useTheme();
103
105
  const [instanceUrl, setInstanceUrl] = useState("");
104
106
  const [loading, setLoading] = useState(true);
105
107
  const [saving, setSaving] = useState(false);
@@ -141,21 +143,58 @@ function GeneralSettings() {
141
143
  setSaving(false);
142
144
  };
143
145
 
146
+ const themeOptions: { value: ThemeMode; label: string; description: string }[] = [
147
+ { value: "auto", label: "Auto", description: "Follow system preference" },
148
+ { value: "dark", label: "Dark", description: "Dark background" },
149
+ { value: "light", label: "Light", description: "Light background" },
150
+ ];
151
+
144
152
  return (
145
153
  <div className="max-w-4xl w-full">
146
154
  <div className="mb-6">
147
155
  <h1 className="text-2xl font-semibold mb-1">General</h1>
148
- <p className="text-[#666]">Instance configuration.</p>
156
+ <p className="text-[var(--color-text-muted)]">Instance configuration and appearance.</p>
157
+ </div>
158
+
159
+ {/* Theme */}
160
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4 mb-4">
161
+ <h3 className="font-medium mb-2">Theme</h3>
162
+ <p className="text-sm text-[var(--color-text-muted)] mb-4">
163
+ Choose your preferred color scheme. Auto follows your operating system setting.
164
+ </p>
165
+ <div className="flex gap-3">
166
+ {themeOptions.map(opt => (
167
+ <button
168
+ key={opt.value}
169
+ onClick={() => setMode(opt.value)}
170
+ className={`flex-1 max-w-[160px] px-4 py-3 rounded-lg border text-left transition ${
171
+ mode === opt.value
172
+ ? "border-[var(--color-accent)] bg-[var(--color-accent-10)]"
173
+ : "border-[var(--color-border-light)] bg-[var(--color-bg)] hover:border-[var(--color-scrollbar)]"
174
+ }`}
175
+ >
176
+ <div className="flex items-center gap-2 mb-1">
177
+ <div className={`w-4 h-4 rounded-full border-2 flex items-center justify-center ${
178
+ mode === opt.value ? "border-[var(--color-accent)]" : "border-[var(--color-scrollbar)]"
179
+ }`}>
180
+ {mode === opt.value && <div className="w-2 h-2 rounded-full bg-[var(--color-accent)]" />}
181
+ </div>
182
+ <span className="text-sm font-medium">{opt.label}</span>
183
+ </div>
184
+ <p className="text-xs text-[var(--color-text-muted)] ml-6">{opt.description}</p>
185
+ </button>
186
+ ))}
187
+ </div>
149
188
  </div>
150
189
 
151
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4">
190
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
152
191
  <h3 className="font-medium mb-2">Instance URL</h3>
153
- <p className="text-sm text-[#666] mb-4">
192
+ <p className="text-sm text-[var(--color-text-muted)] mb-4">
154
193
  The public HTTPS URL for this instance. Used for webhook callbacks from external services like Composio.
155
194
  </p>
156
195
 
157
196
  {loading ? (
158
- <div className="text-[#666] text-sm">Loading...</div>
197
+ <div className="text-[var(--color-text-muted)] text-sm">Loading...</div>
159
198
  ) : (
160
199
  <div className="space-y-3 max-w-lg">
161
200
  <input
@@ -163,7 +202,7 @@ function GeneralSettings() {
163
202
  value={instanceUrl}
164
203
  onChange={e => setInstanceUrl(e.target.value)}
165
204
  placeholder="https://your-domain.com"
166
- className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316] font-mono text-sm"
205
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)] font-mono text-sm"
167
206
  />
168
207
 
169
208
  {message && (
@@ -179,7 +218,7 @@ function GeneralSettings() {
179
218
  <button
180
219
  onClick={handleSave}
181
220
  disabled={saving}
182
- className="px-4 py-2 bg-[#f97316] hover:bg-[#fb923c] disabled:opacity-50 text-black rounded text-sm font-medium transition"
221
+ className="px-4 py-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 text-black rounded text-sm font-medium transition"
183
222
  >
184
223
  {saving ? "Saving..." : "Save"}
185
224
  </button>
@@ -320,7 +359,7 @@ function ProvidersSettings() {
320
359
  <div>
321
360
  <div className="mb-6">
322
361
  <h1 className="text-2xl font-semibold mb-1">AI Providers</h1>
323
- <p className="text-[#666]">
362
+ <p className="text-[var(--color-text-muted)]">
324
363
  Manage your API keys for AI providers. {llmConfiguredCount} of {llmProviders.length} configured.
325
364
  </p>
326
365
  </div>
@@ -358,7 +397,7 @@ function ProvidersSettings() {
358
397
  <div>
359
398
  <div className="mb-6">
360
399
  <h2 className="text-xl font-semibold mb-1">MCP Integrations</h2>
361
- <p className="text-[#666]">
400
+ <p className="text-[var(--color-text-muted)]">
362
401
  Connect to MCP gateways for tool integrations. {intConfiguredCount} of {integrations.length} configured.
363
402
  </p>
364
403
  </div>
@@ -399,14 +438,14 @@ function ProvidersSettings() {
399
438
  <div>
400
439
  <div className="mb-6">
401
440
  <h2 className="text-xl font-semibold mb-1">Browser Providers</h2>
402
- <p className="text-[#666]">
441
+ <p className="text-[var(--color-text-muted)]">
403
442
  Configure browser environments for operator mode (computer use). {browserConfiguredCount} of {browserProviders.length} configured.
404
443
  </p>
405
444
  </div>
406
445
 
407
446
  <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
408
447
  {browserProviders.map(provider => (
409
- <ProviderKeyCard
448
+ <IntegrationKeyCard
410
449
  key={provider.id}
411
450
  provider={provider}
412
451
  isEditing={selectedProvider === provider.id}
@@ -423,14 +462,14 @@ function ProvidersSettings() {
423
462
  onCancelEdit={() => {
424
463
  setSelectedProvider(null);
425
464
  setApiKey("");
426
- setExtraField("");
427
465
  setError(null);
428
466
  }}
429
467
  onApiKeyChange={setApiKey}
430
468
  onSave={saveKey}
431
469
  onDelete={() => deleteKey(provider.id)}
432
- extraField={extraField}
433
- onExtraFieldChange={setExtraField}
470
+ projectsEnabled={projectsEnabled}
471
+ projects={projects}
472
+ onRefresh={fetchProviders}
434
473
  />
435
474
  ))}
436
475
  </div>
@@ -485,13 +524,13 @@ function ProjectsSettings() {
485
524
  <div className="mb-6 flex items-center justify-between gap-4">
486
525
  <div>
487
526
  <h1 className="text-2xl font-semibold mb-1">Projects</h1>
488
- <p className="text-[#666]">
527
+ <p className="text-[var(--color-text-muted)]">
489
528
  Organize agents into projects for better management.
490
529
  </p>
491
530
  </div>
492
531
  <button
493
532
  onClick={openCreate}
494
- className="flex items-center gap-2 bg-[#f97316] hover:bg-[#fb923c] text-black px-4 py-2 rounded font-medium transition flex-shrink-0"
533
+ className="flex items-center gap-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-black px-4 py-2 rounded font-medium transition flex-shrink-0"
495
534
  >
496
535
  <PlusIcon className="w-4 h-4" />
497
536
  New Project
@@ -500,7 +539,7 @@ function ProjectsSettings() {
500
539
 
501
540
  {/* Project List */}
502
541
  {projects.length === 0 ? (
503
- <div className="text-center py-12 text-[#666]">
542
+ <div className="text-center py-12 text-[var(--color-text-muted)]">
504
543
  <p className="text-lg mb-2">No projects yet</p>
505
544
  <p className="text-sm">Create a project to organize your agents.</p>
506
545
  </div>
@@ -509,7 +548,7 @@ function ProjectsSettings() {
509
548
  {projects.map(project => (
510
549
  <div
511
550
  key={project.id}
512
- className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 flex items-center gap-4"
551
+ className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4 flex items-center gap-4"
513
552
  >
514
553
  <div
515
554
  className="w-4 h-4 rounded-full flex-shrink-0"
@@ -518,16 +557,16 @@ function ProjectsSettings() {
518
557
  <div className="flex-1 min-w-0">
519
558
  <h3 className="font-medium">{project.name}</h3>
520
559
  {project.description && (
521
- <p className="text-sm text-[#666] truncate">{project.description}</p>
560
+ <p className="text-sm text-[var(--color-text-muted)] truncate">{project.description}</p>
522
561
  )}
523
- <p className="text-xs text-[#666] mt-1">
562
+ <p className="text-xs text-[var(--color-text-muted)] mt-1">
524
563
  {project.agentCount} agent{project.agentCount !== 1 ? "s" : ""}
525
564
  </p>
526
565
  </div>
527
566
  <div className="flex items-center gap-2">
528
567
  <button
529
568
  onClick={() => openEdit(project)}
530
- className="text-sm text-[#888] hover:text-[#e0e0e0] px-2 py-1"
569
+ className="text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text)] px-2 py-1"
531
570
  >
532
571
  Edit
533
572
  </button>
@@ -601,30 +640,30 @@ function ProjectModal({ project, onSave, onClose }: ProjectModalProps) {
601
640
 
602
641
  <div className="space-y-4">
603
642
  <div>
604
- <label className="block text-sm text-[#666] mb-1">Name</label>
643
+ <label className="block text-sm text-[var(--color-text-muted)] mb-1">Name</label>
605
644
  <input
606
645
  type="text"
607
646
  value={name}
608
647
  onChange={e => setName(e.target.value)}
609
- className="w-full bg-[#0a0a0a] border border-[#222] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
648
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
610
649
  placeholder="My Project"
611
650
  autoFocus
612
651
  />
613
652
  </div>
614
653
 
615
654
  <div>
616
- <label className="block text-sm text-[#666] mb-1">Description (optional)</label>
655
+ <label className="block text-sm text-[var(--color-text-muted)] mb-1">Description (optional)</label>
617
656
  <input
618
657
  type="text"
619
658
  value={description}
620
659
  onChange={e => setDescription(e.target.value)}
621
- className="w-full bg-[#0a0a0a] border border-[#222] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
660
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
622
661
  placeholder="A short description"
623
662
  />
624
663
  </div>
625
664
 
626
665
  <div>
627
- <label className="block text-sm text-[#666] mb-1">Color</label>
666
+ <label className="block text-sm text-[var(--color-text-muted)] mb-1">Color</label>
628
667
  <div className="flex gap-3 flex-wrap">
629
668
  {DEFAULT_PROJECT_COLORS.map(c => (
630
669
  <button
@@ -646,14 +685,14 @@ function ProjectModal({ project, onSave, onClose }: ProjectModalProps) {
646
685
  <div className="flex gap-3 mt-6">
647
686
  <button
648
687
  onClick={onClose}
649
- className="flex-1 border border-[#333] hover:border-[#f97316] hover:text-[#f97316] px-4 py-2 rounded font-medium transition"
688
+ className="flex-1 border border-[var(--color-border-light)] hover:border-[var(--color-accent)] hover:text-[var(--color-accent)] px-4 py-2 rounded font-medium transition"
650
689
  >
651
690
  Cancel
652
691
  </button>
653
692
  <button
654
693
  onClick={handleSubmit}
655
694
  disabled={saving || !name.trim()}
656
- className="flex-1 bg-[#f97316] hover:bg-[#fb923c] disabled:opacity-50 text-black px-4 py-2 rounded font-medium transition"
695
+ className="flex-1 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 text-black px-4 py-2 rounded font-medium transition"
657
696
  >
658
697
  {saving ? "Saving..." : project ? "Update" : "Create"}
659
698
  </button>
@@ -754,13 +793,13 @@ function UpdatesSettings() {
754
793
  <div className="max-w-4xl w-full">
755
794
  <div className="mb-6">
756
795
  <h1 className="text-2xl font-semibold mb-1">Updates</h1>
757
- <p className="text-[#666]">
796
+ <p className="text-[var(--color-text-muted)]">
758
797
  Check for new versions of apteva and the agent binary.
759
798
  </p>
760
799
  </div>
761
800
 
762
801
  {checking && !versions ? (
763
- <div className="text-[#666]">Checking version info...</div>
802
+ <div className="text-[var(--color-text-muted)]">Checking version info...</div>
764
803
  ) : error && !versions ? (
765
804
  <div className="text-red-400">{error}</div>
766
805
  ) : versions?.isDocker ? (
@@ -773,17 +812,17 @@ function UpdatesSettings() {
773
812
  </svg>
774
813
  <span className="font-medium">Docker Environment</span>
775
814
  </div>
776
- <p className="text-sm text-[#888]">
815
+ <p className="text-sm text-[var(--color-text-secondary)]">
777
816
  Updates are automatic when you pull a new image version.
778
817
  </p>
779
818
  </div>
780
819
 
781
820
  {/* Current Version */}
782
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-5">
821
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-5">
783
822
  <div className="flex items-center justify-between mb-4">
784
823
  <div>
785
824
  <h3 className="font-medium text-lg">Current Version</h3>
786
- <p className="text-sm text-[#666]">apteva + agent binary</p>
825
+ <p className="text-sm text-[var(--color-text-muted)]">apteva + agent binary</p>
787
826
  </div>
788
827
  <div className="text-right">
789
828
  <div className="text-xl font-mono">v{versions.apteva.installed || "?"}</div>
@@ -791,15 +830,15 @@ function UpdatesSettings() {
791
830
  </div>
792
831
 
793
832
  {hasAnyUpdate ? (
794
- <div className="bg-[#f97316]/10 border border-[#f97316]/30 rounded-lg p-4">
795
- <p className="text-sm text-[#888] mb-3">
833
+ <div className="bg-[var(--color-accent-10)] border border-[var(--color-accent-30)] rounded-lg p-4">
834
+ <p className="text-sm text-[var(--color-text-secondary)] mb-3">
796
835
  A newer version (v{versions.apteva.latest}) is available. To update:
797
836
  </p>
798
837
  <div className="space-y-2">
799
- <code className="block bg-[#0a0a0a] px-3 py-2 rounded font-mono text-sm text-[#888]">
838
+ <code className="block bg-[var(--color-bg)] px-3 py-2 rounded font-mono text-sm text-[var(--color-text-secondary)]">
800
839
  docker pull apteva/apteva:latest
801
840
  </code>
802
- <code className="block bg-[#0a0a0a] px-3 py-2 rounded font-mono text-sm text-[#888]">
841
+ <code className="block bg-[var(--color-bg)] px-3 py-2 rounded font-mono text-sm text-[var(--color-text-secondary)]">
803
842
  docker compose up -d
804
843
  </code>
805
844
  </div>
@@ -809,7 +848,7 @@ function UpdatesSettings() {
809
848
  setCopied("docker");
810
849
  setTimeout(() => setCopied(null), 2000);
811
850
  }}
812
- className="mt-3 px-3 py-1.5 bg-[#1a1a1a] hover:bg-[#222] rounded text-sm"
851
+ className="mt-3 px-3 py-1.5 bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] rounded text-sm"
813
852
  >
814
853
  {copied === "docker" ? "Copied!" : "Copy commands"}
815
854
  </button>
@@ -822,7 +861,7 @@ function UpdatesSettings() {
822
861
  )}
823
862
  </div>
824
863
 
825
- <p className="text-xs text-[#555]">
864
+ <p className="text-xs text-[var(--color-text-faint)]">
826
865
  Your data is stored in a Docker volume and persists across updates.
827
866
  </p>
828
867
  </div>
@@ -842,32 +881,32 @@ function UpdatesSettings() {
842
881
  )}
843
882
 
844
883
  {/* Apteva App Version */}
845
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-5">
884
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-5">
846
885
  <div className="flex items-center justify-between mb-4">
847
886
  <div>
848
887
  <h3 className="font-medium text-lg">apteva</h3>
849
- <p className="text-sm text-[#666]">The app you're running</p>
888
+ <p className="text-sm text-[var(--color-text-muted)]">The app you're running</p>
850
889
  </div>
851
890
  <div className="text-right">
852
891
  <div className="text-xl font-mono">v{versions.apteva.installed || "?"}</div>
853
892
  {versions.apteva.updateAvailable && (
854
- <div className="text-sm text-[#f97316]">→ v{versions.apteva.latest}</div>
893
+ <div className="text-sm text-[var(--color-accent)]">→ v{versions.apteva.latest}</div>
855
894
  )}
856
895
  </div>
857
896
  </div>
858
897
 
859
898
  {versions.apteva.updateAvailable ? (
860
- <div className="bg-[#f97316]/10 border border-[#f97316]/30 rounded-lg p-4">
861
- <p className="text-sm text-[#888] mb-3">
899
+ <div className="bg-[var(--color-accent-10)] border border-[var(--color-accent-30)] rounded-lg p-4">
900
+ <p className="text-sm text-[var(--color-text-secondary)] mb-3">
862
901
  Update by running:
863
902
  </p>
864
903
  <div className="flex items-center gap-2">
865
- <code className="flex-1 bg-[#0a0a0a] px-3 py-2 rounded font-mono text-sm text-[#888]">
904
+ <code className="flex-1 bg-[var(--color-bg)] px-3 py-2 rounded font-mono text-sm text-[var(--color-text-secondary)]">
866
905
  npx apteva@latest
867
906
  </code>
868
907
  <button
869
908
  onClick={() => copyCommand("npx apteva@latest", "apteva")}
870
- className="px-3 py-2 bg-[#1a1a1a] hover:bg-[#222] rounded text-sm"
909
+ className="px-3 py-2 bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] rounded text-sm"
871
910
  >
872
911
  {copied === "apteva" ? "Copied!" : "Copy"}
873
912
  </button>
@@ -882,30 +921,30 @@ function UpdatesSettings() {
882
921
  </div>
883
922
 
884
923
  {/* Agent Binary Version */}
885
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-5">
924
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-5">
886
925
  <div className="flex items-center justify-between mb-4">
887
926
  <div>
888
927
  <h3 className="font-medium text-lg">Agent Binary</h3>
889
- <p className="text-sm text-[#666]">The Go binary that runs agents</p>
928
+ <p className="text-sm text-[var(--color-text-muted)]">The Go binary that runs agents</p>
890
929
  </div>
891
930
  <div className="text-right">
892
931
  <div className="text-xl font-mono">v{versions.agent.installed || "?"}</div>
893
932
  {versions.agent.updateAvailable && (
894
- <div className="text-sm text-[#f97316]">→ v{versions.agent.latest}</div>
933
+ <div className="text-sm text-[var(--color-accent)]">→ v{versions.agent.latest}</div>
895
934
  )}
896
935
  </div>
897
936
  </div>
898
937
 
899
938
  {versions.agent.updateAvailable ? (
900
- <div className="bg-[#f97316]/10 border border-[#f97316]/30 rounded-lg p-4">
901
- <p className="text-sm text-[#888] mb-3">
939
+ <div className="bg-[var(--color-accent-10)] border border-[var(--color-accent-30)] rounded-lg p-4">
940
+ <p className="text-sm text-[var(--color-text-secondary)] mb-3">
902
941
  A new version is available. Stop all agents before updating.
903
942
  </p>
904
943
  <div className="flex items-center gap-2">
905
944
  <button
906
945
  onClick={updateAgent}
907
946
  disabled={updatingAgent}
908
- className="px-4 py-2 bg-[#f97316] text-black rounded font-medium text-sm disabled:opacity-50"
947
+ className="px-4 py-2 bg-[var(--color-accent)] text-black rounded font-medium text-sm disabled:opacity-50"
909
948
  >
910
949
  {updatingAgent ? "Updating..." : "Update Agent"}
911
950
  </button>
@@ -929,7 +968,7 @@ function UpdatesSettings() {
929
968
  <button
930
969
  onClick={checkForUpdates}
931
970
  disabled={checking}
932
- className="text-sm text-[#666] hover:text-[#888] disabled:opacity-50"
971
+ className="text-sm text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] disabled:opacity-50"
933
972
  >
934
973
  {checking ? "Checking..." : "Check for updates"}
935
974
  </button>
@@ -956,29 +995,56 @@ function ProviderKeyCard({
956
995
  onExtraFieldChange,
957
996
  }: ProviderKeyCardProps) {
958
997
  const isOllama = provider.id === "ollama";
959
- const isUrlBased = isOllama || provider.id === "browserengine" || provider.id === "chrome";
998
+ const isCDP = provider.id === "cdp";
999
+ const isUrlBased = isOllama || isCDP;
960
1000
  const isBrowser = provider.type === "browser";
961
1001
  const isMultiField = provider.id === "browserbase";
962
- const [ollamaStatus, setOllamaStatus] = React.useState<{ connected: boolean; modelCount?: number } | null>(null);
1002
+ const [ollamaStatus, setOllamaStatus] = React.useState<{ connected: boolean; modelCount?: number; isDocker?: boolean } | null>(null);
1003
+ const [installing, setInstalling] = React.useState(false);
1004
+ const [installResult, setInstallResult] = React.useState<{ success: boolean; message: string } | null>(null);
1005
+
1006
+ // Check Ollama status when configured or after install
1007
+ const checkOllamaStatus = React.useCallback(() => {
1008
+ fetch("/api/providers/ollama/status")
1009
+ .then(res => res.json())
1010
+ .then(data => setOllamaStatus({ connected: data.connected, modelCount: data.modelCount, isDocker: data.isDocker }))
1011
+ .catch(() => setOllamaStatus({ connected: false }));
1012
+ }, []);
963
1013
 
964
- // Check Ollama status when configured
965
1014
  React.useEffect(() => {
966
- if (isOllama && provider.hasKey) {
967
- fetch("/api/providers/ollama/status")
968
- .then(res => res.json())
969
- .then(data => setOllamaStatus({ connected: data.connected, modelCount: data.modelCount }))
970
- .catch(() => setOllamaStatus({ connected: false }));
1015
+ if (isOllama) {
1016
+ checkOllamaStatus();
1017
+ }
1018
+ }, [isOllama, provider.hasKey, checkOllamaStatus]);
1019
+
1020
+ const handleInstallOllama = async () => {
1021
+ setInstalling(true);
1022
+ setInstallResult(null);
1023
+ try {
1024
+ const res = await fetch("/api/providers/ollama/install", { method: "POST" });
1025
+ const data = await res.json();
1026
+ if (data.success) {
1027
+ setInstallResult({ success: true, message: data.message });
1028
+ // Auto-save the default URL and refresh status
1029
+ checkOllamaStatus();
1030
+ } else {
1031
+ setInstallResult({ success: false, message: data.error || "Installation failed" });
1032
+ }
1033
+ } catch {
1034
+ setInstallResult({ success: false, message: "Failed to connect to server" });
1035
+ } finally {
1036
+ setInstalling(false);
971
1037
  }
972
- }, [isOllama, provider.hasKey]);
1038
+ };
973
1039
 
974
1040
  return (
975
- <div className={`bg-[#111] border rounded-lg p-4 ${
976
- provider.hasKey ? 'border-green-500/20' : 'border-[#1a1a1a]'
1041
+ <div className={`bg-[var(--color-surface)] border rounded-lg p-4 ${
1042
+ provider.hasKey ? 'border-green-500/20' : 'border-[var(--color-border)]'
977
1043
  }`}>
978
1044
  <div className="flex items-start justify-between gap-2 mb-2">
979
1045
  <div className="min-w-0">
980
1046
  <h3 className="font-medium">{provider.name}</h3>
981
- <p className="text-sm text-[#666] truncate">
1047
+ <p className="text-sm text-[var(--color-text-muted)] truncate">
982
1048
  {isBrowser
983
1049
  ? (provider.description || "Browser automation")
984
1050
  : provider.type === "integration"
@@ -1009,36 +1075,36 @@ function ProviderKeyCard({
1009
1075
  )}
1010
1076
  </span>
1011
1077
  ) : (
1012
- <span className="text-[#666] text-xs bg-[#1a1a1a] px-2 py-1 rounded whitespace-nowrap flex-shrink-0">
1078
+ <span className="text-[var(--color-text-muted)] text-xs bg-[var(--color-surface-raised)] px-2 py-1 rounded whitespace-nowrap flex-shrink-0">
1013
1079
  Not configured
1014
1080
  </span>
1015
1081
  )}
1016
1082
  </div>
1017
1083
 
1018
- <div className="mt-3 pt-3 border-t border-[#1a1a1a]">
1084
+ <div className="mt-3 pt-3 border-t border-[var(--color-border)]">
1019
1085
  {isEditing ? (
1020
1086
  <div className="space-y-3">
1021
1087
  {isMultiField ? (
1022
1088
  <>
1023
1089
  <div>
1024
- <label className="block text-xs text-[#888] mb-1">API Key</label>
1090
+ <label className="block text-xs text-[var(--color-text-secondary)] mb-1">API Key</label>
1025
1091
  <input
1026
1092
  type="password"
1027
1093
  value={apiKey}
1028
1094
  onChange={e => onApiKeyChange(e.target.value)}
1029
1095
  placeholder={provider.hasKey ? "Enter new API key..." : "Enter API key..."}
1030
1096
  autoFocus
1031
- className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
1097
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
1032
1098
  />
1033
1099
  </div>
1034
1100
  <div>
1035
- <label className="block text-xs text-[#888] mb-1">Project ID</label>
1101
+ <label className="block text-xs text-[var(--color-text-secondary)] mb-1">Project ID</label>
1036
1102
  <input
1037
1103
  type="text"
1038
1104
  value={extraField || ""}
1039
1105
  onChange={e => onExtraFieldChange?.(e.target.value)}
1040
1106
  placeholder="Enter your Browserbase project ID..."
1041
- className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
1107
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
1042
1108
  />
1043
1109
  </div>
1044
1110
  </>
@@ -1049,22 +1115,17 @@ function ProviderKeyCard({
1049
1115
  onChange={e => onApiKeyChange(e.target.value)}
1050
1116
  placeholder={isOllama
1051
1117
  ? "http://localhost:11434"
1052
- : provider.id === "browserengine"
1053
- ? "http://localhost:8098"
1054
- : provider.id === "chrome"
1055
- ? "http://localhost:9222"
1056
- : provider.hasKey ? "Enter new API key..." : "Enter API key..."}
1118
+ : isCDP ? "ws://localhost:9222"
1119
+ : provider.hasKey ? "Enter new API key..." : "Enter API key..."}
1057
1120
  autoFocus
1058
- className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
1121
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
1059
1122
  />
1060
1123
  )}
1061
1124
  {isUrlBased && (
1062
- <p className="text-xs text-[#666]">
1063
- {isOllama
1064
- ? "Enter your Ollama server URL. Default is http://localhost:11434"
1065
- : provider.id === "browserengine"
1066
- ? "Enter your BrowserEngine service URL (e.g., http://localhost:8098)"
1067
- : "Enter your Chrome DevTools URL (e.g., http://localhost:9222)"}
1125
+ <p className="text-xs text-[var(--color-text-muted)]">
1126
+ {isCDP
1127
+ ? "Enter the CDP URL of your browser (e.g., ws://localhost:9222)"
1128
+ : "Enter your Ollama server URL. Default is http://localhost:11434"}
1068
1129
  </p>
1069
1130
  )}
1070
1131
  {error && <p className="text-red-400 text-sm">{error}</p>}
@@ -1072,21 +1133,38 @@ function ProviderKeyCard({
1072
1133
  <div className="flex gap-2">
1073
1134
  <button
1074
1135
  onClick={onCancelEdit}
1075
- className="flex-1 px-3 py-1.5 border border-[#333] rounded text-sm hover:border-[#666]"
1136
+ className="flex-1 px-3 py-1.5 border border-[var(--color-border-light)] rounded text-sm hover:border-[var(--color-text-muted)]"
1076
1137
  >
1077
1138
  Cancel
1078
1139
  </button>
1079
1140
  <button
1080
1141
  onClick={onSave}
1081
1142
  disabled={!apiKey || saving}
1082
- className="flex-1 px-3 py-1.5 bg-[#f97316] text-black rounded text-sm font-medium disabled:opacity-50"
1143
+ className="flex-1 px-3 py-1.5 bg-[var(--color-accent)] text-black rounded text-sm font-medium disabled:opacity-50"
1083
1144
  >
1084
1145
  {testing ? "Validating..." : saving ? "Saving..." : isUrlBased ? "Connect" : "Save"}
1085
1146
  </button>
1086
1147
  </div>
1087
1148
  </div>
1088
1149
  ) : provider.hasKey ? (
1089
- <div className="flex items-center justify-between">
1150
+ <div>
1151
+ {isOllama && ollamaStatus && !ollamaStatus.connected && !ollamaStatus.isDocker && (
1152
+ <div className="mb-3">
1153
+ <button
1154
+ onClick={handleInstallOllama}
1155
+ disabled={installing}
1156
+ className="w-full px-3 py-1.5 bg-yellow-500/20 text-yellow-400 hover:bg-yellow-500/30 rounded text-sm font-medium transition disabled:opacity-50 disabled:cursor-wait"
1157
+ >
1158
+ {installing ? "Starting Ollama..." : "Start Ollama"}
1159
+ </button>
1160
+ {installResult && (
1161
+ <p className={`text-xs mt-1.5 ${installResult.success ? "text-green-400" : "text-red-400"}`}>
1162
+ {installResult.message}
1163
+ </p>
1164
+ )}
1165
+ </div>
1166
+ )}
1167
+ <div className="flex items-center justify-between">
1090
1168
  {provider.docsUrl ? (
1091
1169
  <a
1092
1170
  href={provider.docsUrl}
@@ -1094,7 +1172,7 @@ function ProviderKeyCard({
1094
1172
  rel="noopener noreferrer"
1095
1173
  className="text-sm text-[#3b82f6] hover:underline"
1096
1174
  >
1097
- {isOllama ? "Download Ollama" : "View docs"}
1175
+ {isOllama ? "Ollama docs" : "View docs"}
1098
1176
  </a>
1099
1177
  ) : (
1100
1178
  <span />
@@ -1102,7 +1180,7 @@ function ProviderKeyCard({
1102
1180
  <div className="flex items-center gap-3">
1103
1181
  <button
1104
1182
  onClick={onStartEdit}
1105
- className="text-sm text-[#888] hover:text-[#e0e0e0]"
1183
+ className="text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text)]"
1106
1184
  >
1107
1185
  {isUrlBased ? "Change URL" : "Update key"}
1108
1186
  </button>
@@ -1113,27 +1191,46 @@ function ProviderKeyCard({
1113
1191
  Remove
1114
1192
  </button>
1115
1193
  </div>
1194
+ </div>
1116
1195
  </div>
1117
1196
  ) : (
1118
- <div className="flex items-center justify-between">
1119
- {provider.docsUrl ? (
1120
- <a
1121
- href={provider.docsUrl}
1122
- target="_blank"
1123
- rel="noopener noreferrer"
1124
- className="text-sm text-[#3b82f6] hover:underline"
1125
- >
1126
- {isOllama ? "Download Ollama" : isBrowser ? "View docs" : "Get API key"}
1127
- </a>
1128
- ) : (
1129
- <span />
1197
+ <div>
1198
+ {isOllama && !ollamaStatus?.isDocker && (
1199
+ <div className="mb-3">
1200
+ <button
1201
+ onClick={handleInstallOllama}
1202
+ disabled={installing}
1203
+ className="w-full px-3 py-2 bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30 rounded text-sm font-medium transition disabled:opacity-50 disabled:cursor-wait"
1204
+ >
1205
+ {installing ? "Installing Ollama..." : ollamaStatus?.connected ? "Ollama Running" : "Install Ollama"}
1206
+ </button>
1207
+ {installResult && (
1208
+ <p className={`text-xs mt-1.5 ${installResult.success ? "text-green-400" : "text-red-400"}`}>
1209
+ {installResult.message}
1210
+ </p>
1211
+ )}
1212
+ </div>
1130
1213
  )}
1131
- <button
1132
- onClick={onStartEdit}
1133
- className="text-sm text-[#f97316] hover:text-[#fb923c]"
1134
- >
1135
- {isUrlBased ? "Configure" : "+ Add key"}
1136
- </button>
1214
+ <div className="flex items-center justify-between">
1215
+ {provider.docsUrl ? (
1216
+ <a
1217
+ href={provider.docsUrl}
1218
+ target="_blank"
1219
+ rel="noopener noreferrer"
1220
+ className="text-sm text-[#3b82f6] hover:underline"
1221
+ >
1222
+ {isOllama ? "Manual install" : isBrowser ? "View docs" : "Get API key"}
1223
+ </a>
1224
+ ) : (
1225
+ <span />
1226
+ )}
1227
+ <button
1228
+ onClick={onStartEdit}
1229
+ className="text-sm text-[var(--color-accent)] hover:text-[var(--color-accent-hover)]"
1230
+ >
1231
+ {isUrlBased ? "Configure" : "+ Add key"}
1232
+ </button>
1233
+ </div>
1137
1234
  </div>
1138
1235
  )}
1139
1236
  </div>
@@ -1180,8 +1277,11 @@ function IntegrationKeyCard({
1180
1277
  const [expanded, setExpanded] = useState(false);
1181
1278
  const [localError, setLocalError] = useState<string | null>(null);
1182
1279
  const [localSaving, setLocalSaving] = useState(false);
1280
+ const [bbProjectId, setBbProjectId] = useState(""); // Browserbase project ID (their internal ID)
1183
1281
  const { confirm, ConfirmDialog } = useConfirm();
1184
1282
 
1283
+ const isBrowserbase = provider.id === "browserbase";
1284
+
1185
1285
  // Fetch all keys for this provider
1186
1286
  const fetchKeys = async () => {
1187
1287
  try {
@@ -1212,12 +1312,18 @@ function IntegrationKeyCard({
1212
1312
  setLocalSaving(true);
1213
1313
  setLocalError(null);
1214
1314
 
1315
+ // For Browserbase, combine API key + BB project ID into JSON
1316
+ let keyToSave = apiKey;
1317
+ if (isBrowserbase && bbProjectId) {
1318
+ keyToSave = JSON.stringify({ api_key: apiKey, project_id: bbProjectId });
1319
+ }
1320
+
1215
1321
  try {
1216
1322
  const res = await authFetch(`/api/keys/${provider.id}`, {
1217
1323
  method: "POST",
1218
1324
  headers: { "Content-Type": "application/json" },
1219
1325
  body: JSON.stringify({
1220
- key: apiKey,
1326
+ key: keyToSave,
1221
1327
  project_id: selectedProjectId || null,
1222
1328
  }),
1223
1329
  });
@@ -1226,6 +1332,7 @@ function IntegrationKeyCard({
1226
1332
 
1227
1333
  if (res.ok) {
1228
1334
  onApiKeyChange("");
1335
+ setBbProjectId("");
1229
1336
  setSelectedProjectId("");
1230
1337
  onCancelEdit();
1231
1338
  fetchKeys();
@@ -1264,50 +1371,50 @@ function IntegrationKeyCard({
1264
1371
  // Simple view when projects not enabled
1265
1372
  if (!projectsEnabled) {
1266
1373
  return (
1267
- <div className={`bg-[#111] border rounded-lg p-4 ${
1268
- provider.hasKey ? 'border-[#f97316]/20' : 'border-[#1a1a1a]'
1374
+ <div className={`bg-[var(--color-surface)] border rounded-lg p-4 ${
1375
+ provider.hasKey ? 'border-[var(--color-accent-20)]' : 'border-[var(--color-border)]'
1269
1376
  }`}>
1270
1377
  <div className="flex items-center justify-between mb-2">
1271
1378
  <div>
1272
1379
  <h3 className="font-medium">{provider.name}</h3>
1273
- <p className="text-sm text-[#666]">{provider.description || "MCP integration"}</p>
1380
+ <p className="text-sm text-[var(--color-text-muted)]">{provider.description || "MCP integration"}</p>
1274
1381
  </div>
1275
1382
  {provider.hasKey ? (
1276
- <span className="text-[#f97316] text-xs flex items-center gap-1 bg-[#f97316]/10 px-2 py-1 rounded">
1383
+ <span className="text-[var(--color-accent)] text-xs flex items-center gap-1 bg-[var(--color-accent-10)] px-2 py-1 rounded">
1277
1384
  <CheckIcon className="w-3 h-3" />
1278
1385
  {provider.keyHint}
1279
1386
  </span>
1280
1387
  ) : (
1281
- <span className="text-[#666] text-xs bg-[#1a1a1a] px-2 py-1 rounded">
1388
+ <span className="text-[var(--color-text-muted)] text-xs bg-[var(--color-surface-raised)] px-2 py-1 rounded">
1282
1389
  Not configured
1283
1390
  </span>
1284
1391
  )}
1285
1392
  </div>
1286
1393
 
1287
- <div className="mt-3 pt-3 border-t border-[#1a1a1a]">
1394
+ <div className="mt-3 pt-3 border-t border-[var(--color-border)]">
1288
1395
  {isEditing ? (
1289
1396
  <div className="space-y-3">
1290
1397
  <input
1291
- type="password"
1398
+ type={inputType}
1292
1399
  value={apiKey}
1293
1400
  onChange={e => onApiKeyChange(e.target.value)}
1294
- placeholder={provider.hasKey ? "Enter new API key..." : "Enter API key..."}
1401
+ placeholder={provider.hasKey ? `Enter new ${isUrlBased ? "URL" : "API key"}...` : inputPlaceholder}
1295
1402
  autoFocus
1296
- className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
1403
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
1297
1404
  />
1298
1405
  {error && <p className="text-red-400 text-sm">{error}</p>}
1299
1406
  {success && <p className="text-green-400 text-sm">{success}</p>}
1300
1407
  <div className="flex gap-2">
1301
1408
  <button
1302
1409
  onClick={onCancelEdit}
1303
- className="flex-1 px-3 py-1.5 border border-[#333] rounded text-sm hover:border-[#666]"
1410
+ className="flex-1 px-3 py-1.5 border border-[var(--color-border-light)] rounded text-sm hover:border-[var(--color-text-muted)]"
1304
1411
  >
1305
1412
  Cancel
1306
1413
  </button>
1307
1414
  <button
1308
1415
  onClick={onSave}
1309
1416
  disabled={!apiKey || saving}
1310
- className="flex-1 px-3 py-1.5 bg-[#f97316] text-black rounded text-sm font-medium disabled:opacity-50"
1417
+ className="flex-1 px-3 py-1.5 bg-[var(--color-accent)] text-black rounded text-sm font-medium disabled:opacity-50"
1311
1418
  >
1312
1419
  {testing ? "Validating..." : saving ? "Saving..." : "Save"}
1313
1420
  </button>
@@ -1326,7 +1433,7 @@ function IntegrationKeyCard({
1326
1433
  <div className="flex items-center gap-3">
1327
1434
  <button
1328
1435
  onClick={onStartEdit}
1329
- className="text-sm text-[#888] hover:text-[#e0e0e0]"
1436
+ className="text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text)]"
1330
1437
  >
1331
1438
  Update key
1332
1439
  </button>
@@ -1350,7 +1457,7 @@ function IntegrationKeyCard({
1350
1457
  </a>
1351
1458
  <button
1352
1459
  onClick={onStartEdit}
1353
- className="text-sm text-[#f97316] hover:text-[#fb923c]"
1460
+ className="text-sm text-[var(--color-accent)] hover:text-[var(--color-accent-hover)]"
1354
1461
  >
1355
1462
  + Add key
1356
1463
  </button>
@@ -1361,25 +1468,32 @@ function IntegrationKeyCard({
1361
1468
  );
1362
1469
  }
1363
1470
 
1471
+ // Determine input type and placeholder based on provider
1472
+ const isUrlBased = provider.isLocal;
1473
+ const inputType = isUrlBased ? "text" : "password";
1474
+ const inputPlaceholder = isUrlBased
1475
+ ? (provider.id === "cdp" ? "ws://localhost:9222" : "http://localhost:11434")
1476
+ : "Enter API key...";
1477
+
1364
1478
  // Enhanced view with project support
1365
1479
  return (
1366
1480
  <>
1367
1481
  {ConfirmDialog}
1368
- <div className={`bg-[#111] border rounded-lg p-4 ${
1369
- keys.length > 0 ? 'border-[#f97316]/20' : 'border-[#1a1a1a]'
1482
+ <div className={`bg-[var(--color-surface)] border rounded-lg p-4 ${
1483
+ keys.length > 0 ? 'border-[var(--color-accent-20)]' : 'border-[var(--color-border)]'
1370
1484
  }`}>
1371
1485
  <div className="flex items-center justify-between mb-2">
1372
1486
  <div>
1373
1487
  <h3 className="font-medium">{provider.name}</h3>
1374
- <p className="text-sm text-[#666]">{provider.description || "MCP integration"}</p>
1488
+ <p className="text-sm text-[var(--color-text-muted)]">{provider.description || "MCP integration"}</p>
1375
1489
  </div>
1376
1490
  {keys.length > 0 ? (
1377
- <span className="text-[#f97316] text-xs flex items-center gap-1 bg-[#f97316]/10 px-2 py-1 rounded">
1491
+ <span className="text-[var(--color-accent)] text-xs flex items-center gap-1 bg-[var(--color-accent-10)] px-2 py-1 rounded">
1378
1492
  <CheckIcon className="w-3 h-3" />
1379
1493
  {keys.length} key{keys.length !== 1 ? "s" : ""}
1380
1494
  </span>
1381
1495
  ) : (
1382
- <span className="text-[#666] text-xs bg-[#1a1a1a] px-2 py-1 rounded">
1496
+ <span className="text-[var(--color-text-muted)] text-xs bg-[var(--color-surface-raised)] px-2 py-1 rounded">
1383
1497
  Not configured
1384
1498
  </span>
1385
1499
  )}
@@ -1390,11 +1504,11 @@ function IntegrationKeyCard({
1390
1504
  <div className="mt-3 space-y-2">
1391
1505
  {/* Global Key */}
1392
1506
  {globalKey && (
1393
- <div className="flex items-center justify-between text-sm bg-[#0a0a0a] rounded px-3 py-2">
1507
+ <div className="flex items-center justify-between text-sm bg-[var(--color-bg)] rounded px-3 py-2">
1394
1508
  <div className="flex items-center gap-2">
1395
- <span className="text-[#888]">Global</span>
1396
- <span className="text-[#555]">·</span>
1397
- <span className="text-[#666] font-mono text-xs">{globalKey.key_hint}</span>
1509
+ <span className="text-[var(--color-text-secondary)]">Global</span>
1510
+ <span className="text-[var(--color-text-faint)]">·</span>
1511
+ <span className="text-[var(--color-text-muted)] font-mono text-xs">{globalKey.key_hint}</span>
1398
1512
  </div>
1399
1513
  <button
1400
1514
  onClick={() => handleDeleteKey(globalKey.id, "Global")}
@@ -1407,15 +1521,15 @@ function IntegrationKeyCard({
1407
1521
 
1408
1522
  {/* Project Keys - show first 2, expand for more */}
1409
1523
  {projectKeys.slice(0, expanded ? undefined : 2).map(key => (
1410
- <div key={key.id} className="flex items-center justify-between text-sm bg-[#0a0a0a] rounded px-3 py-2">
1524
+ <div key={key.id} className="flex items-center justify-between text-sm bg-[var(--color-bg)] rounded px-3 py-2">
1411
1525
  <div className="flex items-center gap-2 min-w-0">
1412
1526
  <span
1413
1527
  className="w-2 h-2 rounded-full flex-shrink-0"
1414
1528
  style={{ backgroundColor: getProjectColor(key.project_id!) }}
1415
1529
  />
1416
- <span className="text-[#888] truncate">{key.name || getProjectName(key.project_id!)}</span>
1417
- <span className="text-[#555]">·</span>
1418
- <span className="text-[#666] font-mono text-xs">{key.key_hint}</span>
1530
+ <span className="text-[var(--color-text-secondary)] truncate">{key.name || getProjectName(key.project_id!)}</span>
1531
+ <span className="text-[var(--color-text-faint)]">·</span>
1532
+ <span className="text-[var(--color-text-muted)] font-mono text-xs">{key.key_hint}</span>
1419
1533
  </div>
1420
1534
  <button
1421
1535
  onClick={() => handleDeleteKey(key.id, key.name || getProjectName(key.project_id!))}
@@ -1429,7 +1543,7 @@ function IntegrationKeyCard({
1429
1543
  {projectKeys.length > 2 && !expanded && (
1430
1544
  <button
1431
1545
  onClick={() => setExpanded(true)}
1432
- className="text-xs text-[#666] hover:text-[#888] w-full text-center py-1"
1546
+ className="text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] w-full text-center py-1"
1433
1547
  >
1434
1548
  Show {projectKeys.length - 2} more...
1435
1549
  </button>
@@ -1437,18 +1551,28 @@ function IntegrationKeyCard({
1437
1551
  </div>
1438
1552
  )}
1439
1553
 
1440
- <div className="mt-3 pt-3 border-t border-[#1a1a1a]">
1554
+ <div className="mt-3 pt-3 border-t border-[var(--color-border)]">
1441
1555
  {isEditing ? (
1442
1556
  <div className="space-y-3">
1443
1557
  <input
1444
- type="password"
1558
+ type={inputType}
1445
1559
  value={apiKey}
1446
1560
  onChange={e => onApiKeyChange(e.target.value)}
1447
- placeholder="Enter API key..."
1561
+ placeholder={inputPlaceholder}
1448
1562
  autoFocus
1449
- className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
1563
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
1450
1564
  />
1451
1565
 
1566
+ {isBrowserbase && (
1567
+ <input
1568
+ type="text"
1569
+ value={bbProjectId}
1570
+ onChange={e => setBbProjectId(e.target.value)}
1571
+ placeholder="Browserbase Project ID (optional)"
1572
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)] text-sm"
1573
+ />
1574
+ )}
1575
+
1452
1576
  <Select
1453
1577
  value={selectedProjectId}
1454
1578
  onChange={setSelectedProjectId}
@@ -1468,14 +1592,14 @@ function IntegrationKeyCard({
1468
1592
  setSelectedProjectId("");
1469
1593
  setLocalError(null);
1470
1594
  }}
1471
- className="flex-1 px-3 py-1.5 border border-[#333] rounded text-sm hover:border-[#666]"
1595
+ className="flex-1 px-3 py-1.5 border border-[var(--color-border-light)] rounded text-sm hover:border-[var(--color-text-muted)]"
1472
1596
  >
1473
1597
  Cancel
1474
1598
  </button>
1475
1599
  <button
1476
1600
  onClick={handleSaveWithProject}
1477
1601
  disabled={!apiKey || localSaving}
1478
- className="flex-1 px-3 py-1.5 bg-[#f97316] text-black rounded text-sm font-medium disabled:opacity-50"
1602
+ className="flex-1 px-3 py-1.5 bg-[var(--color-accent)] text-black rounded text-sm font-medium disabled:opacity-50"
1479
1603
  >
1480
1604
  {localSaving ? "Saving..." : "Save"}
1481
1605
  </button>
@@ -1493,7 +1617,7 @@ function IntegrationKeyCard({
1493
1617
  </a>
1494
1618
  <button
1495
1619
  onClick={onStartEdit}
1496
- className="text-sm text-[#f97316] hover:text-[#fb923c]"
1620
+ className="text-sm text-[var(--color-accent)] hover:text-[var(--color-accent-hover)]"
1497
1621
  >
1498
1622
  + Add key
1499
1623
  </button>
@@ -1612,14 +1736,14 @@ function ApiKeysSettings() {
1612
1736
  <div className="mb-6 flex items-center justify-between gap-4">
1613
1737
  <div>
1614
1738
  <h1 className="text-2xl font-semibold mb-1">API Keys</h1>
1615
- <p className="text-[#666]">
1616
- Create personal API keys for programmatic access. Use them with the <code className="text-[#888] bg-[#1a1a1a] px-1 rounded text-xs">X-API-Key</code> header.
1739
+ <p className="text-[var(--color-text-muted)]">
1740
+ Create personal API keys for programmatic access. Use them with the <code className="text-[var(--color-text-secondary)] bg-[var(--color-surface-raised)] px-1 rounded text-xs">X-API-Key</code> header.
1617
1741
  </p>
1618
1742
  </div>
1619
1743
  {!showCreate && !newKey && (
1620
1744
  <button
1621
1745
  onClick={() => { setShowCreate(true); setError(null); }}
1622
- className="flex items-center gap-2 bg-[#f97316] hover:bg-[#fb923c] text-black px-4 py-2 rounded font-medium transition flex-shrink-0"
1746
+ className="flex items-center gap-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-black px-4 py-2 rounded font-medium transition flex-shrink-0"
1623
1747
  >
1624
1748
  <PlusIcon className="w-4 h-4" />
1625
1749
  New Key
@@ -1634,23 +1758,23 @@ function ApiKeysSettings() {
1634
1758
  <CheckIcon className="w-5 h-5" />
1635
1759
  <span className="font-medium">API key created</span>
1636
1760
  </div>
1637
- <p className="text-sm text-[#888] mb-3">
1761
+ <p className="text-sm text-[var(--color-text-secondary)] mb-3">
1638
1762
  Copy this key now. You won't be able to see it again.
1639
1763
  </p>
1640
1764
  <div className="flex items-center gap-2">
1641
- <code className="flex-1 bg-[#0a0a0a] px-3 py-2 rounded font-mono text-sm text-[#e0e0e0] break-all select-all">
1765
+ <code className="flex-1 bg-[var(--color-bg)] px-3 py-2 rounded font-mono text-sm text-[var(--color-text)] break-all select-all">
1642
1766
  {newKey}
1643
1767
  </code>
1644
1768
  <button
1645
1769
  onClick={copyKey}
1646
- className="px-3 py-2 bg-[#1a1a1a] hover:bg-[#222] rounded text-sm flex-shrink-0"
1770
+ className="px-3 py-2 bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] rounded text-sm flex-shrink-0"
1647
1771
  >
1648
1772
  {copied ? "Copied!" : "Copy"}
1649
1773
  </button>
1650
1774
  </div>
1651
1775
  <button
1652
1776
  onClick={() => { setNewKey(null); setShowCreate(false); }}
1653
- className="mt-3 text-sm text-[#666] hover:text-[#888]"
1777
+ className="mt-3 text-sm text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]"
1654
1778
  >
1655
1779
  Done
1656
1780
  </button>
@@ -1659,26 +1783,26 @@ function ApiKeysSettings() {
1659
1783
 
1660
1784
  {/* Create Form */}
1661
1785
  {showCreate && !newKey && (
1662
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 mb-6">
1786
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4 mb-6">
1663
1787
  <h3 className="font-medium mb-4">Create new API key</h3>
1664
1788
  <div className="space-y-4 max-w-md">
1665
1789
  <div>
1666
- <label className="block text-sm text-[#666] mb-1">Name</label>
1790
+ <label className="block text-sm text-[var(--color-text-muted)] mb-1">Name</label>
1667
1791
  <input
1668
1792
  type="text"
1669
1793
  value={name}
1670
1794
  onChange={e => setName(e.target.value)}
1671
1795
  placeholder="e.g. CI Pipeline, My Script"
1672
1796
  autoFocus
1673
- className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
1797
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
1674
1798
  />
1675
1799
  </div>
1676
1800
  <div>
1677
- <label className="block text-sm text-[#666] mb-1">Expiration</label>
1801
+ <label className="block text-sm text-[var(--color-text-muted)] mb-1">Expiration</label>
1678
1802
  <select
1679
1803
  value={expiresInDays}
1680
1804
  onChange={e => setExpiresInDays(e.target.value)}
1681
- className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
1805
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
1682
1806
  >
1683
1807
  <option value="30">30 days</option>
1684
1808
  <option value="90">90 days</option>
@@ -1693,14 +1817,14 @@ function ApiKeysSettings() {
1693
1817
  <div className="flex gap-2">
1694
1818
  <button
1695
1819
  onClick={() => { setShowCreate(false); setError(null); setName(""); }}
1696
- className="flex-1 px-3 py-2 border border-[#333] rounded text-sm hover:border-[#666]"
1820
+ className="flex-1 px-3 py-2 border border-[var(--color-border-light)] rounded text-sm hover:border-[var(--color-text-muted)]"
1697
1821
  >
1698
1822
  Cancel
1699
1823
  </button>
1700
1824
  <button
1701
1825
  onClick={handleCreate}
1702
1826
  disabled={creating || !name.trim()}
1703
- className="flex-1 px-3 py-2 bg-[#f97316] text-black rounded text-sm font-medium disabled:opacity-50"
1827
+ className="flex-1 px-3 py-2 bg-[var(--color-accent)] text-black rounded text-sm font-medium disabled:opacity-50"
1704
1828
  >
1705
1829
  {creating ? "Creating..." : "Create Key"}
1706
1830
  </button>
@@ -1711,7 +1835,7 @@ function ApiKeysSettings() {
1711
1835
 
1712
1836
  {/* Keys List */}
1713
1837
  {keys.length === 0 ? (
1714
- <div className="text-center py-12 text-[#666]">
1838
+ <div className="text-center py-12 text-[var(--color-text-muted)]">
1715
1839
  <p className="text-lg mb-2">No API keys yet</p>
1716
1840
  <p className="text-sm">Create an API key to access apteva programmatically.</p>
1717
1841
  </div>
@@ -1720,8 +1844,8 @@ function ApiKeysSettings() {
1720
1844
  {keys.map(key => (
1721
1845
  <div
1722
1846
  key={key.id}
1723
- className={`bg-[#111] border rounded-lg p-4 flex items-center gap-4 ${
1724
- !key.is_active || isExpired(key.expires_at) ? "border-[#1a1a1a] opacity-60" : "border-[#1a1a1a]"
1847
+ className={`bg-[var(--color-surface)] border rounded-lg p-4 flex items-center gap-4 ${
1848
+ !key.is_active || isExpired(key.expires_at) ? "border-[var(--color-border)] opacity-60" : "border-[var(--color-border)]"
1725
1849
  }`}
1726
1850
  >
1727
1851
  <div className="flex-1 min-w-0">
@@ -1734,8 +1858,8 @@ function ApiKeysSettings() {
1734
1858
  <span className="text-xs text-yellow-400 bg-yellow-500/10 px-2 py-0.5 rounded">Expired</span>
1735
1859
  )}
1736
1860
  </div>
1737
- <div className="flex items-center gap-3 text-sm text-[#666]">
1738
- <code className="font-mono text-xs bg-[#0a0a0a] px-2 py-0.5 rounded">{key.prefix}...</code>
1861
+ <div className="flex items-center gap-3 text-sm text-[var(--color-text-muted)]">
1862
+ <code className="font-mono text-xs bg-[var(--color-bg)] px-2 py-0.5 rounded">{key.prefix}...</code>
1739
1863
  <span>Created {formatDate(key.created_at)}</span>
1740
1864
  {key.expires_at && <span>Expires {formatDate(key.expires_at)}</span>}
1741
1865
  {key.last_used_at && <span>Last used {formatDate(key.last_used_at)}</span>}
@@ -1756,9 +1880,9 @@ function ApiKeysSettings() {
1756
1880
 
1757
1881
  {/* Usage Info */}
1758
1882
  {keys.length > 0 && (
1759
- <div className="mt-6 bg-[#111] border border-[#1a1a1a] rounded-lg p-4">
1883
+ <div className="mt-6 bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
1760
1884
  <h3 className="font-medium mb-2 text-sm">Usage</h3>
1761
- <code className="block bg-[#0a0a0a] px-3 py-2 rounded font-mono text-xs text-[#888]">
1885
+ <code className="block bg-[var(--color-bg)] px-3 py-2 rounded font-mono text-xs text-[var(--color-text-secondary)]">
1762
1886
  curl -H "X-API-Key: apt_..." http://localhost:4280/api/agents
1763
1887
  </code>
1764
1888
  </div>
@@ -1824,26 +1948,26 @@ function AccountSettings() {
1824
1948
  <div className="max-w-4xl w-full">
1825
1949
  <div className="mb-6">
1826
1950
  <h1 className="text-2xl font-semibold mb-1">Account Settings</h1>
1827
- <p className="text-[#666]">Manage your account and security.</p>
1951
+ <p className="text-[var(--color-text-muted)]">Manage your account and security.</p>
1828
1952
  </div>
1829
1953
 
1830
1954
  {/* User Info */}
1831
1955
  {user && (
1832
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 mb-6">
1956
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4 mb-6">
1833
1957
  <h3 className="font-medium mb-3">Profile</h3>
1834
1958
  <div className="space-y-2 text-sm">
1835
1959
  <div className="flex justify-between">
1836
- <span className="text-[#666]">Username</span>
1960
+ <span className="text-[var(--color-text-muted)]">Username</span>
1837
1961
  <span>{user.username}</span>
1838
1962
  </div>
1839
1963
  {user.email && (
1840
1964
  <div className="flex justify-between">
1841
- <span className="text-[#666]">Email</span>
1965
+ <span className="text-[var(--color-text-muted)]">Email</span>
1842
1966
  <span>{user.email}</span>
1843
1967
  </div>
1844
1968
  )}
1845
1969
  <div className="flex justify-between">
1846
- <span className="text-[#666]">Role</span>
1970
+ <span className="text-[var(--color-text-muted)]">Role</span>
1847
1971
  <span className="capitalize">{user.role}</span>
1848
1972
  </div>
1849
1973
  </div>
@@ -1851,37 +1975,37 @@ function AccountSettings() {
1851
1975
  )}
1852
1976
 
1853
1977
  {/* Change Password */}
1854
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4">
1978
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
1855
1979
  <h3 className="font-medium mb-4">Change Password</h3>
1856
1980
 
1857
1981
  <div className="space-y-4 max-w-md">
1858
1982
  <div>
1859
- <label className="block text-sm text-[#666] mb-1">Current Password</label>
1983
+ <label className="block text-sm text-[var(--color-text-muted)] mb-1">Current Password</label>
1860
1984
  <input
1861
1985
  type="password"
1862
1986
  value={currentPassword}
1863
1987
  onChange={(e) => setCurrentPassword(e.target.value)}
1864
- className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
1988
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
1865
1989
  />
1866
1990
  </div>
1867
1991
 
1868
1992
  <div>
1869
- <label className="block text-sm text-[#666] mb-1">New Password</label>
1993
+ <label className="block text-sm text-[var(--color-text-muted)] mb-1">New Password</label>
1870
1994
  <input
1871
1995
  type="password"
1872
1996
  value={newPassword}
1873
1997
  onChange={(e) => setNewPassword(e.target.value)}
1874
- className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
1998
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
1875
1999
  />
1876
2000
  </div>
1877
2001
 
1878
2002
  <div>
1879
- <label className="block text-sm text-[#666] mb-1">Confirm New Password</label>
2003
+ <label className="block text-sm text-[var(--color-text-muted)] mb-1">Confirm New Password</label>
1880
2004
  <input
1881
2005
  type="password"
1882
2006
  value={confirmPassword}
1883
2007
  onChange={(e) => setConfirmPassword(e.target.value)}
1884
- className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
2008
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 focus:outline-none focus:border-[var(--color-accent)]"
1885
2009
  />
1886
2010
  </div>
1887
2011
 
@@ -1898,7 +2022,7 @@ function AccountSettings() {
1898
2022
  <button
1899
2023
  onClick={handleChangePassword}
1900
2024
  disabled={saving || !currentPassword || !newPassword || !confirmPassword}
1901
- className="px-4 py-2 bg-[#f97316] hover:bg-[#fb923c] disabled:opacity-50 disabled:cursor-not-allowed text-black rounded text-sm font-medium transition"
2025
+ className="px-4 py-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 disabled:cursor-not-allowed text-black rounded text-sm font-medium transition"
1902
2026
  >
1903
2027
  {saving ? "Updating..." : "Update Password"}
1904
2028
  </button>
@@ -1959,12 +2083,12 @@ function DataSettings() {
1959
2083
  <div className="max-w-4xl w-full">
1960
2084
  <div className="mb-6">
1961
2085
  <h1 className="text-2xl font-semibold mb-1">Data Management</h1>
1962
- <p className="text-[#666]">Manage stored data and telemetry.</p>
2086
+ <p className="text-[var(--color-text-muted)]">Manage stored data and telemetry.</p>
1963
2087
  </div>
1964
2088
 
1965
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4">
2089
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
1966
2090
  <h3 className="font-medium mb-2">Telemetry Data</h3>
1967
- <p className="text-sm text-[#666] mb-4">
2091
+ <p className="text-sm text-[var(--color-text-muted)] mb-4">
1968
2092
  {eventCount !== null
1969
2093
  ? `${eventCount.toLocaleString()} events stored`
1970
2094
  : "Loading..."}
@@ -2112,7 +2236,7 @@ function ChannelsSettings() {
2112
2236
 
2113
2237
  const statusColors: Record<string, string> = {
2114
2238
  running: "bg-green-500/20 text-green-400",
2115
- stopped: "bg-[#333] text-[#666]",
2239
+ stopped: "bg-[var(--color-surface-raised)] text-[var(--color-text-muted)]",
2116
2240
  error: "bg-red-500/20 text-red-400",
2117
2241
  };
2118
2242
 
@@ -2127,11 +2251,11 @@ function ChannelsSettings() {
2127
2251
  <div className="flex items-center justify-between mb-6">
2128
2252
  <div>
2129
2253
  <h2 className="text-xl font-semibold mb-1">Channels</h2>
2130
- <p className="text-sm text-[#666]">Connect agents to external messaging platforms</p>
2254
+ <p className="text-sm text-[var(--color-text-muted)]">Connect agents to external messaging platforms</p>
2131
2255
  </div>
2132
2256
  <button
2133
2257
  onClick={() => setShowForm(!showForm)}
2134
- className="flex items-center gap-2 bg-[#f97316] hover:bg-[#fb923c] text-black px-3 py-1.5 rounded text-sm font-medium transition"
2258
+ className="flex items-center gap-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-black px-3 py-1.5 rounded text-sm font-medium transition"
2135
2259
  >
2136
2260
  <PlusIcon /> Add Channel
2137
2261
  </button>
@@ -2148,22 +2272,22 @@ function ChannelsSettings() {
2148
2272
 
2149
2273
  {/* Create form */}
2150
2274
  {showForm && (
2151
- <div className="mb-6 bg-[#111] border border-[#1a1a1a] rounded-lg p-4 space-y-3">
2152
- <h3 className="text-sm font-medium text-[#888] mb-2">New Telegram Channel</h3>
2275
+ <div className="mb-6 bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4 space-y-3">
2276
+ <h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-2">New Telegram Channel</h3>
2153
2277
 
2154
2278
  <div>
2155
- <label className="block text-xs text-[#666] mb-1">Channel Name</label>
2279
+ <label className="block text-xs text-[var(--color-text-muted)] mb-1">Channel Name</label>
2156
2280
  <input
2157
2281
  type="text"
2158
2282
  value={formData.name}
2159
2283
  onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))}
2160
2284
  placeholder="e.g. My Telegram Bot"
2161
- className="w-full bg-[#0a0a0a] border border-[#222] rounded px-3 py-2 text-sm focus:outline-none focus:border-[#f97316]"
2285
+ 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)]"
2162
2286
  />
2163
2287
  </div>
2164
2288
 
2165
2289
  <div>
2166
- <label className="block text-xs text-[#666] mb-1">Agent</label>
2290
+ <label className="block text-xs text-[var(--color-text-muted)] mb-1">Agent</label>
2167
2291
  <Select
2168
2292
  value={formData.agent_id}
2169
2293
  options={agents.map(a => ({ value: a.id, label: a.name }))}
@@ -2173,16 +2297,16 @@ function ChannelsSettings() {
2173
2297
  </div>
2174
2298
 
2175
2299
  <div>
2176
- <label className="block text-xs text-[#666] mb-1">Bot Token</label>
2300
+ <label className="block text-xs text-[var(--color-text-muted)] mb-1">Bot Token</label>
2177
2301
  <input
2178
2302
  type="password"
2179
2303
  value={formData.botToken}
2180
2304
  onChange={e => setFormData(prev => ({ ...prev, botToken: e.target.value }))}
2181
2305
  placeholder="From @BotFather on Telegram"
2182
- className="w-full bg-[#0a0a0a] border border-[#222] rounded px-3 py-2 text-sm focus:outline-none focus:border-[#f97316]"
2306
+ 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)]"
2183
2307
  />
2184
- <p className="text-xs text-[#555] mt-1">
2185
- Create a bot via <a href="https://t.me/BotFather" target="_blank" className="text-[#f97316] hover:underline">@BotFather</a> on Telegram to get a token.
2308
+ <p className="text-xs text-[var(--color-text-faint)] mt-1">
2309
+ Create a bot via <a href="https://t.me/BotFather" target="_blank" className="text-[var(--color-accent)] hover:underline">@BotFather</a> on Telegram to get a token.
2186
2310
  </p>
2187
2311
  </div>
2188
2312
 
@@ -2190,13 +2314,13 @@ function ChannelsSettings() {
2190
2314
  <button
2191
2315
  onClick={createChannel}
2192
2316
  disabled={creating || !formData.name || !formData.agent_id || !formData.botToken}
2193
- className="bg-[#f97316] hover:bg-[#fb923c] disabled:opacity-50 text-black px-4 py-1.5 rounded text-sm font-medium transition"
2317
+ className="bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 text-black px-4 py-1.5 rounded text-sm font-medium transition"
2194
2318
  >
2195
2319
  {creating ? "Creating..." : "Create"}
2196
2320
  </button>
2197
2321
  <button
2198
2322
  onClick={() => { setShowForm(false); setFormData({ name: "", agent_id: "", botToken: "" }); }}
2199
- className="border border-[#333] hover:border-[#444] px-4 py-1.5 rounded text-sm transition"
2323
+ className="border border-[var(--color-border-light)] hover:border-[var(--color-scrollbar)] px-4 py-1.5 rounded text-sm transition"
2200
2324
  >
2201
2325
  Cancel
2202
2326
  </button>
@@ -2206,16 +2330,16 @@ function ChannelsSettings() {
2206
2330
 
2207
2331
  {/* Channel list */}
2208
2332
  {loading ? (
2209
- <p className="text-[#666] text-sm">Loading channels...</p>
2333
+ <p className="text-[var(--color-text-muted)] text-sm">Loading channels...</p>
2210
2334
  ) : channels.length === 0 ? (
2211
- <div className="text-center py-12 text-[#666]">
2335
+ <div className="text-center py-12 text-[var(--color-text-muted)]">
2212
2336
  <p className="text-lg mb-2">No channels configured</p>
2213
2337
  <p className="text-sm">Add a Telegram channel to let users message your agents directly.</p>
2214
2338
  </div>
2215
2339
  ) : (
2216
2340
  <div className="space-y-3">
2217
2341
  {channels.map(channel => (
2218
- <div key={channel.id} className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4">
2342
+ <div key={channel.id} className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
2219
2343
  <div className="flex items-start justify-between">
2220
2344
  <div className="flex-1 min-w-0">
2221
2345
  <div className="flex items-center gap-2 mb-1">
@@ -2224,7 +2348,7 @@ function ChannelsSettings() {
2224
2348
  {channel.status}
2225
2349
  </span>
2226
2350
  </div>
2227
- <p className="text-sm text-[#666]">
2351
+ <p className="text-sm text-[var(--color-text-muted)]">
2228
2352
  {channel.type === "telegram" ? "Telegram" : channel.type} → {getAgentName(channel.agent_id)}
2229
2353
  </p>
2230
2354
  {channel.status === "error" && channel.error && (
@@ -2236,7 +2360,7 @@ function ChannelsSettings() {
2236
2360
  onClick={() => toggleChannel(channel)}
2237
2361
  className={`px-3 py-1 rounded text-xs font-medium transition ${
2238
2362
  channel.status === "running"
2239
- ? "bg-[#f97316]/20 text-[#f97316] hover:bg-[#f97316]/30"
2363
+ ? "bg-[var(--color-accent-20)] text-[var(--color-accent)] hover:bg-[var(--color-accent-30)]"
2240
2364
  : "bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30"
2241
2365
  }`}
2242
2366
  >
@@ -2244,7 +2368,7 @@ function ChannelsSettings() {
2244
2368
  </button>
2245
2369
  <button
2246
2370
  onClick={() => deleteChannel(channel)}
2247
- className="text-[#666] hover:text-red-400 transition text-sm"
2371
+ className="text-[var(--color-text-muted)] hover:text-red-400 transition text-sm"
2248
2372
  >
2249
2373
  ×
2250
2374
  </button>
@@ -2270,9 +2394,11 @@ function AssistantSettings() {
2270
2394
  const [saving, setSaving] = useState(false);
2271
2395
  const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
2272
2396
  const [starting, setStarting] = useState(false);
2397
+ const [webSearch, setWebSearch] = useState(false);
2398
+ const [webFetch, setWebFetch] = useState(false);
2273
2399
 
2274
2400
  // Original values for change detection
2275
- const [original, setOriginal] = useState({ provider: "", model: "", systemPrompt: "" });
2401
+ const [original, setOriginal] = useState({ provider: "", model: "", systemPrompt: "", webSearch: false, webFetch: false });
2276
2402
 
2277
2403
  useEffect(() => {
2278
2404
  const fetchData = async () => {
@@ -2291,7 +2417,11 @@ function AssistantSettings() {
2291
2417
  setModel(a.model || "");
2292
2418
  setSystemPrompt(a.systemPrompt || "");
2293
2419
  setStatus(a.status || "stopped");
2294
- setOriginal({ provider: a.provider || "", model: a.model || "", systemPrompt: a.systemPrompt || "" });
2420
+ const ws = a.features?.builtinTools?.webSearch || false;
2421
+ const wf = a.features?.builtinTools?.webFetch || false;
2422
+ setWebSearch(ws);
2423
+ setWebFetch(wf);
2424
+ setOriginal({ provider: a.provider || "", model: a.model || "", systemPrompt: a.systemPrompt || "", webSearch: ws, webFetch: wf });
2295
2425
  }
2296
2426
  } catch {
2297
2427
  setMessage({ type: "error", text: "Failed to load assistant config" });
@@ -2312,7 +2442,7 @@ function AssistantSettings() {
2312
2442
  setModel(defaultModel);
2313
2443
  };
2314
2444
 
2315
- const hasChanges = provider !== original.provider || model !== original.model || systemPrompt !== original.systemPrompt;
2445
+ const hasChanges = provider !== original.provider || model !== original.model || systemPrompt !== original.systemPrompt || webSearch !== original.webSearch || webFetch !== original.webFetch;
2316
2446
 
2317
2447
  const handleSave = async () => {
2318
2448
  setSaving(true);
@@ -2321,10 +2451,10 @@ function AssistantSettings() {
2321
2451
  const res = await authFetch("/api/agents/apteva-assistant", {
2322
2452
  method: "PUT",
2323
2453
  headers: { "Content-Type": "application/json" },
2324
- body: JSON.stringify({ provider, model, systemPrompt }),
2454
+ body: JSON.stringify({ provider, model, systemPrompt, features: { builtinTools: { webSearch, webFetch } } }),
2325
2455
  });
2326
2456
  if (res.ok) {
2327
- setOriginal({ provider, model, systemPrompt });
2457
+ setOriginal({ provider, model, systemPrompt, webSearch, webFetch });
2328
2458
  setMessage({ type: "success", text: "Assistant settings saved" });
2329
2459
  setTimeout(() => setMessage(null), 3000);
2330
2460
  } else {
@@ -2358,13 +2488,13 @@ function AssistantSettings() {
2358
2488
  };
2359
2489
 
2360
2490
  if (loading) {
2361
- return <div className="text-[#666]">Loading assistant settings...</div>;
2491
+ return <div className="text-[var(--color-text-muted)]">Loading assistant settings...</div>;
2362
2492
  }
2363
2493
 
2364
2494
  return (
2365
2495
  <div className="max-w-2xl">
2366
2496
  <h2 className="text-lg font-medium mb-1">Apteva Assistant</h2>
2367
- <p className="text-sm text-[#666] mb-6">Configure the built-in AI assistant that manages your agents and platform.</p>
2497
+ <p className="text-sm text-[var(--color-text-muted)] mb-6">Configure the built-in AI assistant that manages your agents and platform.</p>
2368
2498
 
2369
2499
  {message && (
2370
2500
  <div className={`mb-4 px-3 py-2 rounded text-sm ${
@@ -2376,9 +2506,9 @@ function AssistantSettings() {
2376
2506
 
2377
2507
  {/* Status */}
2378
2508
  <div className="mb-6 flex items-center gap-3">
2379
- <span className="text-sm text-[#666]">Status:</span>
2509
+ <span className="text-sm text-[var(--color-text-muted)]">Status:</span>
2380
2510
  <span className={`px-2 py-1 rounded text-xs font-medium ${
2381
- status === "running" ? "bg-[#3b82f6]/20 text-[#3b82f6]" : "bg-[#333] text-[#666]"
2511
+ status === "running" ? "bg-[#3b82f6]/20 text-[#3b82f6]" : "bg-[var(--color-surface-raised)] text-[var(--color-text-muted)]"
2382
2512
  }`}>
2383
2513
  {status}
2384
2514
  </span>
@@ -2387,7 +2517,7 @@ function AssistantSettings() {
2387
2517
  disabled={starting}
2388
2518
  className={`px-3 py-1.5 rounded text-sm font-medium transition ${
2389
2519
  status === "running"
2390
- ? "bg-[#f97316]/20 text-[#f97316] hover:bg-[#f97316]/30"
2520
+ ? "bg-[var(--color-accent-20)] text-[var(--color-accent)] hover:bg-[var(--color-accent-30)]"
2391
2521
  : "bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30"
2392
2522
  } disabled:opacity-50`}
2393
2523
  >
@@ -2397,7 +2527,7 @@ function AssistantSettings() {
2397
2527
 
2398
2528
  {/* Provider */}
2399
2529
  <div className="mb-4">
2400
- <label className="block text-sm text-[#666] mb-1">Provider</label>
2530
+ <label className="block text-sm text-[var(--color-text-muted)] mb-1">Provider</label>
2401
2531
  <Select
2402
2532
  value={provider}
2403
2533
  onChange={handleProviderChange}
@@ -2408,7 +2538,7 @@ function AssistantSettings() {
2408
2538
 
2409
2539
  {/* Model */}
2410
2540
  <div className="mb-4">
2411
- <label className="block text-sm text-[#666] mb-1">Model</label>
2541
+ <label className="block text-sm text-[var(--color-text-muted)] mb-1">Model</label>
2412
2542
  <Select
2413
2543
  value={model}
2414
2544
  onChange={setModel}
@@ -2417,14 +2547,52 @@ function AssistantSettings() {
2417
2547
  />
2418
2548
  </div>
2419
2549
 
2550
+ {/* Built-in Tools - Anthropic only */}
2551
+ {provider === "anthropic" && (
2552
+ <div className="mb-4">
2553
+ <label className="block text-sm text-[var(--color-text-muted)] mb-1">Built-in Tools</label>
2554
+ <div className="flex flex-wrap gap-2">
2555
+ <button
2556
+ type="button"
2557
+ onClick={() => setWebSearch(!webSearch)}
2558
+ className={`flex items-center gap-2 px-3 py-2 rounded border transition ${
2559
+ webSearch
2560
+ ? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]"
2561
+ : "border-[var(--color-border-light)] hover:border-[var(--color-border-light)] text-[var(--color-text-secondary)]"
2562
+ }`}
2563
+ >
2564
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2565
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
2566
+ </svg>
2567
+ <span className="text-sm">Web Search</span>
2568
+ </button>
2569
+ <button
2570
+ type="button"
2571
+ onClick={() => setWebFetch(!webFetch)}
2572
+ className={`flex items-center gap-2 px-3 py-2 rounded border transition ${
2573
+ webFetch
2574
+ ? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]"
2575
+ : "border-[var(--color-border-light)] hover:border-[var(--color-border-light)] text-[var(--color-text-secondary)]"
2576
+ }`}
2577
+ >
2578
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2579
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
2580
+ </svg>
2581
+ <span className="text-sm">Web Fetch</span>
2582
+ </button>
2583
+ </div>
2584
+ <p className="text-xs text-[var(--color-text-faint)] mt-2">Provider-native tools for real-time web access</p>
2585
+ </div>
2586
+ )}
2587
+
2420
2588
  {/* System Prompt */}
2421
2589
  <div className="mb-6">
2422
- <label className="block text-sm text-[#666] mb-1">System Prompt</label>
2590
+ <label className="block text-sm text-[var(--color-text-muted)] mb-1">System Prompt</label>
2423
2591
  <textarea
2424
2592
  value={systemPrompt}
2425
2593
  onChange={e => setSystemPrompt(e.target.value)}
2426
2594
  rows={12}
2427
- className="w-full bg-[#111] border border-[#1a1a1a] rounded px-3 py-2 text-sm font-mono focus:outline-none focus:border-[#f97316] resize-y"
2595
+ className="w-full bg-[var(--color-surface)] border border-[var(--color-border)] rounded px-3 py-2 text-sm font-mono focus:outline-none focus:border-[var(--color-accent)] resize-y"
2428
2596
  />
2429
2597
  </div>
2430
2598
 
@@ -2432,13 +2600,13 @@ function AssistantSettings() {
2432
2600
  <button
2433
2601
  onClick={handleSave}
2434
2602
  disabled={!hasChanges || saving}
2435
- className="bg-[#f97316] hover:bg-[#fb923c] disabled:opacity-50 disabled:cursor-not-allowed text-black px-4 py-2 rounded font-medium transition"
2603
+ className="bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 disabled:cursor-not-allowed text-black px-4 py-2 rounded font-medium transition"
2436
2604
  >
2437
2605
  {saving ? "Saving..." : "Save Changes"}
2438
2606
  </button>
2439
2607
 
2440
2608
  {status === "running" && hasChanges && (
2441
- <p className="text-xs text-[#666] mt-2">Changes will be applied to the running assistant</p>
2609
+ <p className="text-xs text-[var(--color-text-muted)] mt-2">Changes will be applied to the running assistant</p>
2442
2610
  )}
2443
2611
  </div>
2444
2612
  );