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
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from "react";
2
- import { DashboardIcon, ActivityIcon, AgentsIcon, TasksIcon, ConnectionsIcon, McpIcon, SkillsIcon, TestsIcon, TelemetryIcon, ApiIcon, SettingsIcon, CloseIcon } from "../common/Icons";
2
+ import { DashboardIcon, ThreadsIcon, AgentsIcon, ActivityIcon, TasksIcon, ConnectionsIcon, McpIcon, SkillsIcon, TestsIcon, TelemetryIcon, ApiIcon, SettingsIcon, CloseIcon } from "../common/Icons";
3
3
  import { useAuth } from "../../context";
4
4
  import type { Route } from "../../types";
5
5
 
@@ -39,20 +39,22 @@ export function Sidebar({ route, agentCount, taskCount, onNavigate, isOpen, onCl
39
39
  {/* Sidebar - hidden on mobile unless open, always visible on md+ */}
40
40
  <aside
41
41
  className={`
42
- fixed inset-y-0 left-0 z-50 w-64 bg-[#0a0a0a] border-r border-[#1a1a1a] p-4 flex flex-col transform transition-transform duration-200 ease-in-out
42
+ fixed inset-y-0 left-0 z-50 w-64 p-4 flex flex-col transform transition-transform duration-200 ease-in-out
43
43
  md:relative md:w-56 md:translate-x-0 md:z-auto
44
44
  ${isOpen ? "translate-x-0" : "-translate-x-full"}
45
45
  `}
46
+ style={{ backgroundColor: "var(--color-bg)", borderRight: "1px solid var(--color-border)" }}
46
47
  >
47
48
  {/* Mobile header with close button */}
48
49
  <div className="flex items-center justify-between mb-4 md:hidden">
49
50
  <div className="flex items-center gap-2">
50
- <span className="text-[#f97316]">&gt;_</span>
51
+ <span style={{ color: "var(--color-accent)" }}>&gt;_</span>
51
52
  <span className="text-lg tracking-wider">apteva</span>
52
53
  </div>
53
54
  <button
54
55
  onClick={onClose}
55
- className="p-2 text-[#666] hover:text-[#e0e0e0] transition"
56
+ className="p-2 transition"
57
+ style={{ color: "var(--color-text-muted)" }}
56
58
  >
57
59
  <CloseIcon />
58
60
  </button>
@@ -72,6 +74,12 @@ export function Sidebar({ route, agentCount, taskCount, onNavigate, isOpen, onCl
72
74
  onClick={() => handleNavigate("agents")}
73
75
  badge={agentCount > 0 ? String(agentCount) : undefined}
74
76
  />
77
+ <NavButton
78
+ icon={<ThreadsIcon />}
79
+ label="Threads"
80
+ active={route === "threads"}
81
+ onClick={() => handleNavigate("threads")}
82
+ />
75
83
  <NavButton
76
84
  icon={<ActivityIcon />}
77
85
  label="Activity"
@@ -131,26 +139,31 @@ export function Sidebar({ route, agentCount, taskCount, onNavigate, isOpen, onCl
131
139
 
132
140
  {/* User profile - pinned to bottom */}
133
141
  {user && (
134
- <div className="relative border-t border-[#1a1a1a] pt-3 mt-3">
142
+ <div className="relative pt-3 mt-3" style={{ borderTop: "1px solid var(--color-border)" }}>
135
143
  <button
136
144
  onClick={() => setShowUserMenu(!showUserMenu)}
137
- className="w-full flex items-center gap-3 px-3 py-2 rounded hover:bg-[#111] transition"
145
+ className="w-full flex items-center gap-3 px-3 py-2 rounded transition"
146
+ style={{ color: "var(--color-text)" }}
147
+ onMouseEnter={e => e.currentTarget.style.backgroundColor = "var(--color-surface)"}
148
+ onMouseLeave={e => e.currentTarget.style.backgroundColor = "transparent"}
138
149
  >
139
- <div className="w-8 h-8 rounded-full bg-[#f97316] flex items-center justify-center text-black font-medium text-sm flex-shrink-0">
150
+ <div className="w-8 h-8 rounded-full flex items-center justify-center text-black font-medium text-sm flex-shrink-0" style={{ backgroundColor: "var(--color-accent)" }}>
140
151
  {user.username.charAt(0).toUpperCase()}
141
152
  </div>
142
153
  <div className="flex-1 min-w-0 text-left">
143
154
  <p className="text-sm font-medium truncate">{user.username}</p>
144
- <p className="text-xs text-[#555]">{user.role}</p>
155
+ <p className="text-xs" style={{ color: "var(--color-text-faint)" }}>{user.role}</p>
145
156
  </div>
146
157
  </button>
147
158
  {showUserMenu && (
148
159
  <>
149
160
  <div className="fixed inset-0 z-40" onClick={() => setShowUserMenu(false)} />
150
- <div className="absolute left-3 bottom-full mb-1 w-48 bg-[#111] border border-[#222] rounded-lg shadow-xl z-50">
161
+ <div className="absolute left-3 bottom-full mb-1 w-48 rounded-lg shadow-xl z-50" style={{ backgroundColor: "var(--color-surface)", border: "1px solid var(--color-border-light)" }}>
151
162
  <button
152
163
  onClick={handleLogout}
153
- className="w-full px-4 py-2.5 text-left text-sm text-red-400 hover:bg-[#1a1a1a] transition rounded-lg"
164
+ className="w-full px-4 py-2.5 text-left text-sm text-red-400 transition rounded-lg"
165
+ onMouseEnter={e => e.currentTarget.style.backgroundColor = "var(--color-surface-raised)"}
166
+ onMouseLeave={e => e.currentTarget.style.backgroundColor = "transparent"}
154
167
  >
155
168
  Sign out
156
169
  </button>
@@ -176,16 +189,28 @@ function NavButton({ icon, label, active, onClick, badge }: NavButtonProps) {
176
189
  return (
177
190
  <button
178
191
  onClick={onClick}
179
- className={`w-full flex items-center gap-3 px-3 py-2 rounded font-medium transition ${
180
- active
181
- ? "bg-[#111] text-[#e0e0e0]"
182
- : "text-[#666] hover:bg-[#111] hover:text-[#888]"
183
- }`}
192
+ className="w-full flex items-center gap-3 px-3 py-2 rounded font-medium transition"
193
+ style={{
194
+ backgroundColor: active ? "var(--color-surface)" : "transparent",
195
+ color: active ? "var(--color-text)" : "var(--color-text-muted)",
196
+ }}
197
+ onMouseEnter={e => {
198
+ if (!active) {
199
+ e.currentTarget.style.backgroundColor = "var(--color-surface)";
200
+ e.currentTarget.style.color = "var(--color-text-secondary)";
201
+ }
202
+ }}
203
+ onMouseLeave={e => {
204
+ if (!active) {
205
+ e.currentTarget.style.backgroundColor = "transparent";
206
+ e.currentTarget.style.color = "var(--color-text-muted)";
207
+ }
208
+ }}
184
209
  >
185
210
  {icon}
186
211
  {label}
187
212
  {badge && (
188
- <span className="ml-auto bg-[#333] text-[#888] text-xs px-2 py-0.5 rounded-full">
213
+ <span className="ml-auto text-xs px-2 py-0.5 rounded-full" style={{ backgroundColor: "var(--color-surface-raised)", color: "var(--color-text-secondary)" }}>
189
214
  {badge}
190
215
  </span>
191
216
  )}
@@ -10,6 +10,12 @@ interface IntegrationApp {
10
10
  logo: string | null;
11
11
  categories: string[];
12
12
  authSchemes: string[];
13
+ providerSlug?: string;
14
+ credentialFields?: {
15
+ name: string;
16
+ description?: string;
17
+ required?: boolean;
18
+ }[];
13
19
  }
14
20
 
15
21
  interface ConnectedAccount {
@@ -68,9 +74,10 @@ export function IntegrationsPanel({
68
74
  const [error, setError] = useState<string | null>(null);
69
75
  // For auth method selection (when app supports both OAuth and API Key)
70
76
  const [authMethodModal, setAuthMethodModal] = useState<{ app: IntegrationApp } | null>(null);
71
- // For API Key modal
77
+ // For API Key / credential modal
72
78
  const [apiKeyModal, setApiKeyModal] = useState<{ app: IntegrationApp } | null>(null);
73
79
  const [apiKeyInput, setApiKeyInput] = useState("");
80
+ const [credentialInputs, setCredentialInputs] = useState<Record<string, string>>({});
74
81
  // For MCP config creation modal
75
82
  const [mcpConfigModal, setMcpConfigModal] = useState<{ app: IntegrationApp } | null>(null);
76
83
  const [mcpConfigName, setMcpConfigName] = useState("");
@@ -153,17 +160,18 @@ export function IntegrationsPanel({
153
160
  }, [pendingConnection, authFetch, providerId, projectId, fetchData, onConnectionComplete]);
154
161
 
155
162
  // Initiate connection
156
- const connectApp = async (app: IntegrationApp, apiKey?: string, forceOAuth?: boolean) => {
163
+ const connectApp = async (app: IntegrationApp, apiKey?: string, forceOAuth?: boolean, fields?: Record<string, string>) => {
157
164
  // If app supports multiple auth methods and user hasn't chosen, show choice
158
- if (hasMultipleAuthMethods(app) && !apiKey && !forceOAuth) {
165
+ if (hasMultipleAuthMethods(app) && !apiKey && !fields && !forceOAuth) {
159
166
  setAuthMethodModal({ app });
160
167
  return;
161
168
  }
162
169
 
163
- // If app supports API key (and user didn't choose OAuth), show API key modal
164
- if (supportsApiKey(app) && !apiKey && !forceOAuth) {
170
+ // If app supports API key (and user didn't choose OAuth), show credential modal
171
+ if (supportsApiKey(app) && !apiKey && !fields && !forceOAuth) {
165
172
  setApiKeyModal({ app });
166
173
  setApiKeyInput("");
174
+ setCredentialInputs({});
167
175
  return;
168
176
  }
169
177
 
@@ -173,7 +181,13 @@ export function IntegrationsPanel({
173
181
  try {
174
182
  // Build request body
175
183
  const body: any = { appSlug: app.slug };
176
- if (apiKey) {
184
+ if (fields && Object.keys(fields).length > 0) {
185
+ // Multi-field credentials
186
+ body.credentials = {
187
+ authScheme: "API_KEY",
188
+ fields,
189
+ };
190
+ } else if (apiKey) {
177
191
  body.credentials = {
178
192
  authScheme: "API_KEY",
179
193
  apiKey,
@@ -231,11 +245,21 @@ export function IntegrationsPanel({
231
245
  }
232
246
  };
233
247
 
234
- // Handle API key form submission
248
+ // Handle API key / credential form submission
235
249
  const handleApiKeySubmit = (e: React.FormEvent) => {
236
250
  e.preventDefault();
237
- if (!apiKeyModal || !apiKeyInput.trim()) return;
238
- connectApp(apiKeyModal.app, apiKeyInput.trim());
251
+ if (!apiKeyModal) return;
252
+ const hasFields = apiKeyModal.app.credentialFields && apiKeyModal.app.credentialFields.length > 0;
253
+ if (hasFields) {
254
+ // Check required fields are filled
255
+ const requiredFields = apiKeyModal.app.credentialFields!.filter(f => f.required !== false);
256
+ const allFilled = requiredFields.every(f => credentialInputs[f.name]?.trim());
257
+ if (!allFilled) return;
258
+ connectApp(apiKeyModal.app, undefined, false, credentialInputs);
259
+ } else {
260
+ if (!apiKeyInput.trim()) return;
261
+ connectApp(apiKeyModal.app, apiKeyInput.trim());
262
+ }
239
263
  };
240
264
 
241
265
  // Disconnect (called after confirmation)
@@ -291,6 +315,20 @@ export function IntegrationsPanel({
291
315
  return;
292
316
  }
293
317
 
318
+ // Auto-add the server locally
319
+ let autoAdded = false;
320
+ if (data.config?.id) {
321
+ try {
322
+ const addRes = await authFetch(
323
+ `/api/integrations/${providerId}/configs/${data.config.id}/add${projectParam}`,
324
+ { method: "POST" }
325
+ );
326
+ autoAdded = addRes.ok;
327
+ } catch {
328
+ // Non-fatal — server was still created on the provider
329
+ }
330
+ }
331
+
294
332
  setMcpConfigSuccess(mcpConfigName);
295
333
  onConnectionComplete?.();
296
334
  } catch (e) {
@@ -311,17 +349,23 @@ export function IntegrationsPanel({
311
349
  });
312
350
  };
313
351
 
314
- // Check if app is connected
315
- const isConnected = (appSlug: string) => {
352
+ // Check if app is connected (also matches via providerSlug for multi-toolkit providers)
353
+ const isConnected = (app: IntegrationApp) => {
316
354
  return connectedAccounts.some(
317
- (a) => a.appId === appSlug && a.status === "active"
355
+ (a) => a.status === "active" && (
356
+ a.appId === app.slug ||
357
+ (app.providerSlug && a.appId === app.providerSlug)
358
+ )
318
359
  );
319
360
  };
320
361
 
321
- // Get connection for app (prefer active account)
322
- const getConnection = (appSlug: string) => {
323
- return connectedAccounts.find((a) => a.appId === appSlug && a.status === "active")
324
- || connectedAccounts.find((a) => a.appId === appSlug);
362
+ // Get connection for app (prefer active account, also matches via providerSlug)
363
+ const getConnection = (app: IntegrationApp) => {
364
+ return connectedAccounts.find((a) => a.appId === app.slug && a.status === "active")
365
+ || (app.providerSlug && connectedAccounts.find((a) => a.appId === app.providerSlug && a.status === "active"))
366
+ || connectedAccounts.find((a) => a.appId === app.slug)
367
+ || (app.providerSlug && connectedAccounts.find((a) => a.appId === app.providerSlug))
368
+ || undefined;
325
369
  };
326
370
 
327
371
  // Filter apps
@@ -337,11 +381,11 @@ export function IntegrationsPanel({
337
381
  });
338
382
 
339
383
  // Group by connected/not connected
340
- const connectedApps = filteredApps.filter((app) => isConnected(app.slug));
341
- const availableApps = filteredApps.filter((app) => !isConnected(app.slug));
384
+ const connectedApps = filteredApps.filter((app) => isConnected(app));
385
+ const availableApps = filteredApps.filter((app) => !isConnected(app));
342
386
 
343
387
  if (loading) {
344
- return <div className="text-center py-8 text-[#666]">Loading apps...</div>;
388
+ return <div className="text-center py-8 text-[var(--color-text-muted)]">Loading apps...</div>;
345
389
  }
346
390
 
347
391
  return (
@@ -349,7 +393,7 @@ export function IntegrationsPanel({
349
393
  {/* Auth Method Choice Modal */}
350
394
  {authMethodModal && (
351
395
  <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
352
- <div className="bg-[#111] border border-[#333] rounded-lg p-6 w-full max-w-md mx-4">
396
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded-lg p-6 w-full max-w-md mx-4">
353
397
  <div className="flex items-center gap-3 mb-4">
354
398
  {authMethodModal.app.logo && (
355
399
  <img
@@ -360,7 +404,7 @@ export function IntegrationsPanel({
360
404
  )}
361
405
  <div>
362
406
  <h3 className="font-medium">Connect {authMethodModal.app.name}</h3>
363
- <p className="text-xs text-[#666]">Choose how to authenticate</p>
407
+ <p className="text-xs text-[var(--color-text-muted)]">Choose how to authenticate</p>
364
408
  </div>
365
409
  </div>
366
410
  <div className="space-y-3">
@@ -369,11 +413,12 @@ export function IntegrationsPanel({
369
413
  setAuthMethodModal(null);
370
414
  setApiKeyModal({ app: authMethodModal.app });
371
415
  setApiKeyInput("");
416
+ setCredentialInputs({});
372
417
  }}
373
- className="w-full text-left p-3 bg-[#0a0a0a] hover:bg-[#1a1a1a] border border-[#333] hover:border-[#f97316] rounded-lg transition"
418
+ className="w-full text-left p-3 bg-[var(--color-bg)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] hover:border-[var(--color-accent)] rounded-lg transition"
374
419
  >
375
420
  <div className="font-medium text-sm">API Key</div>
376
- <div className="text-xs text-[#666] mt-0.5">
421
+ <div className="text-xs text-[var(--color-text-muted)] mt-0.5">
377
422
  Enter your {authMethodModal.app.name} API key directly
378
423
  </div>
379
424
  </button>
@@ -382,17 +427,17 @@ export function IntegrationsPanel({
382
427
  setAuthMethodModal(null);
383
428
  connectApp(authMethodModal.app, undefined, true);
384
429
  }}
385
- className="w-full text-left p-3 bg-[#0a0a0a] hover:bg-[#1a1a1a] border border-[#333] hover:border-[#f97316] rounded-lg transition"
430
+ className="w-full text-left p-3 bg-[var(--color-bg)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] hover:border-[var(--color-accent)] rounded-lg transition"
386
431
  >
387
432
  <div className="font-medium text-sm">OAuth</div>
388
- <div className="text-xs text-[#666] mt-0.5">
433
+ <div className="text-xs text-[var(--color-text-muted)] mt-0.5">
389
434
  Sign in with your {authMethodModal.app.name} account
390
435
  </div>
391
436
  </button>
392
437
  </div>
393
438
  <button
394
439
  onClick={() => setAuthMethodModal(null)}
395
- className="w-full text-sm text-[#666] hover:text-white mt-4 py-2 transition"
440
+ className="w-full text-sm text-[var(--color-text-muted)] hover:text-white mt-4 py-2 transition"
396
441
  >
397
442
  Cancel
398
443
  </button>
@@ -400,10 +445,10 @@ export function IntegrationsPanel({
400
445
  </div>
401
446
  )}
402
447
 
403
- {/* API Key Modal */}
448
+ {/* API Key / Credentials Modal */}
404
449
  {apiKeyModal && (
405
450
  <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
406
- <div className="bg-[#111] border border-[#333] rounded-lg p-6 w-full max-w-md mx-4">
451
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded-lg p-6 w-full max-w-md mx-4">
407
452
  <div className="flex items-center gap-3 mb-4">
408
453
  {apiKeyModal.app.logo && (
409
454
  <img
@@ -414,30 +459,63 @@ export function IntegrationsPanel({
414
459
  )}
415
460
  <div>
416
461
  <h3 className="font-medium">Connect {apiKeyModal.app.name}</h3>
417
- <p className="text-xs text-[#666]">Enter your API key to connect</p>
462
+ <p className="text-xs text-[var(--color-text-muted)]">
463
+ {apiKeyModal.app.credentialFields?.length
464
+ ? "Enter your credentials to connect"
465
+ : "Enter your API key to connect"}
466
+ </p>
418
467
  </div>
419
468
  </div>
420
469
  <form onSubmit={handleApiKeySubmit}>
421
- <input
422
- type="password"
423
- value={apiKeyInput}
424
- onChange={(e) => setApiKeyInput(e.target.value)}
425
- placeholder="Enter API Key..."
426
- className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-4 py-2 mb-4 focus:outline-none focus:border-[#f97316]"
427
- autoFocus
428
- />
470
+ {apiKeyModal.app.credentialFields && apiKeyModal.app.credentialFields.length > 0 ? (
471
+ <div className="space-y-3 mb-4">
472
+ {apiKeyModal.app.credentialFields.map((field, idx) => (
473
+ <div key={field.name}>
474
+ <label className="block text-xs text-[var(--color-text-secondary)] mb-1">
475
+ {field.name.replace(/([A-Z])/g, " $1").replace(/[-_]/g, " ").replace(/\b\w/g, c => c.toUpperCase()).trim()}
476
+ {field.required !== false && <span className="text-red-400 ml-0.5">*</span>}
477
+ </label>
478
+ {field.description && (
479
+ <p className="text-[10px] text-[var(--color-text-faint)] mb-1">{field.description}</p>
480
+ )}
481
+ <input
482
+ type="password"
483
+ value={credentialInputs[field.name] || ""}
484
+ onChange={(e) => setCredentialInputs(prev => ({ ...prev, [field.name]: e.target.value }))}
485
+ placeholder={`Enter ${field.name}...`}
486
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded-lg px-4 py-2 focus:outline-none focus:border-[var(--color-accent)]"
487
+ autoFocus={idx === 0}
488
+ />
489
+ </div>
490
+ ))}
491
+ </div>
492
+ ) : (
493
+ <input
494
+ type="password"
495
+ value={apiKeyInput}
496
+ onChange={(e) => setApiKeyInput(e.target.value)}
497
+ placeholder="Enter API Key..."
498
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded-lg px-4 py-2 mb-4 focus:outline-none focus:border-[var(--color-accent)]"
499
+ autoFocus
500
+ />
501
+ )}
429
502
  <div className="flex gap-2">
430
503
  <button
431
504
  type="button"
432
505
  onClick={() => setApiKeyModal(null)}
433
- className="flex-1 text-sm bg-[#1a1a1a] hover:bg-[#222] border border-[#333] px-4 py-2 rounded transition"
506
+ className="flex-1 text-sm bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] px-4 py-2 rounded transition"
434
507
  >
435
508
  Cancel
436
509
  </button>
437
510
  <button
438
511
  type="submit"
439
- disabled={!apiKeyInput.trim() || connecting === apiKeyModal.app.slug}
440
- className="flex-1 text-sm bg-[#f97316] hover:bg-[#ea580c] text-white px-4 py-2 rounded transition disabled:opacity-50"
512
+ disabled={
513
+ connecting === apiKeyModal.app.slug ||
514
+ (apiKeyModal.app.credentialFields?.length
515
+ ? !apiKeyModal.app.credentialFields.filter(f => f.required !== false).every(f => credentialInputs[f.name]?.trim())
516
+ : !apiKeyInput.trim())
517
+ }
518
+ className="flex-1 text-sm bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-white px-4 py-2 rounded transition disabled:opacity-50"
441
519
  >
442
520
  {connecting === apiKeyModal.app.slug ? "Connecting..." : "Connect"}
443
521
  </button>
@@ -450,7 +528,7 @@ export function IntegrationsPanel({
450
528
  {/* MCP Config Creation Modal */}
451
529
  {mcpConfigModal && (
452
530
  <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
453
- <div className="bg-[#111] border border-[#333] rounded-lg p-6 w-full max-w-md mx-4">
531
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded-lg p-6 w-full max-w-md mx-4">
454
532
  {mcpConfigSuccess ? (
455
533
  <>
456
534
  <div className="text-center mb-4">
@@ -458,11 +536,8 @@ export function IntegrationsPanel({
458
536
  <span className="text-green-400 text-2xl">✓</span>
459
537
  </div>
460
538
  <h3 className="font-medium text-lg">MCP Config Created!</h3>
461
- <p className="text-sm text-[#888] mt-2">
462
- "{mcpConfigSuccess}" has been created successfully.
463
- </p>
464
- <p className="text-xs text-[#666] mt-2">
465
- You can now add it to your agents from the MCP Configs tab.
539
+ <p className="text-sm text-[var(--color-text-secondary)] mt-2">
540
+ "{mcpConfigSuccess}" has been created and added to your servers.
466
541
  </p>
467
542
  </div>
468
543
  <button
@@ -470,7 +545,7 @@ export function IntegrationsPanel({
470
545
  setMcpConfigModal(null);
471
546
  setMcpConfigSuccess(null);
472
547
  }}
473
- className="w-full text-sm bg-[#f97316] hover:bg-[#ea580c] text-white px-4 py-2 rounded transition"
548
+ className="w-full text-sm bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-white px-4 py-2 rounded transition"
474
549
  >
475
550
  Done
476
551
  </button>
@@ -487,19 +562,19 @@ export function IntegrationsPanel({
487
562
  )}
488
563
  <div>
489
564
  <h3 className="font-medium">Create MCP Config</h3>
490
- <p className="text-xs text-[#666]">
565
+ <p className="text-xs text-[var(--color-text-muted)]">
491
566
  Create an MCP config for {mcpConfigModal.app.name}
492
567
  </p>
493
568
  </div>
494
569
  </div>
495
570
  <form onSubmit={(e) => { e.preventDefault(); createMcpConfig(); }}>
496
- <label className="block text-xs text-[#888] mb-1">Config Name</label>
571
+ <label className="block text-xs text-[var(--color-text-secondary)] mb-1">Config Name</label>
497
572
  <input
498
573
  type="text"
499
574
  value={mcpConfigName}
500
575
  onChange={(e) => setMcpConfigName(e.target.value)}
501
576
  placeholder="Enter config name..."
502
- className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-4 py-2 mb-4 focus:outline-none focus:border-[#f97316]"
577
+ className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded-lg px-4 py-2 mb-4 focus:outline-none focus:border-[var(--color-accent)]"
503
578
  autoFocus
504
579
  maxLength={30}
505
580
  />
@@ -507,14 +582,14 @@ export function IntegrationsPanel({
507
582
  <button
508
583
  type="button"
509
584
  onClick={() => setMcpConfigModal(null)}
510
- className="flex-1 text-sm bg-[#1a1a1a] hover:bg-[#222] border border-[#333] px-4 py-2 rounded transition"
585
+ className="flex-1 text-sm bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] px-4 py-2 rounded transition"
511
586
  >
512
587
  Cancel
513
588
  </button>
514
589
  <button
515
590
  type="submit"
516
591
  disabled={!mcpConfigName.trim() || mcpConfigCreating}
517
- className="flex-1 text-sm bg-[#f97316] hover:bg-[#ea580c] text-white px-4 py-2 rounded transition disabled:opacity-50"
592
+ className="flex-1 text-sm bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-white px-4 py-2 rounded transition disabled:opacity-50"
518
593
  >
519
594
  {mcpConfigCreating ? "Creating..." : "Create Config"}
520
595
  </button>
@@ -529,12 +604,12 @@ export function IntegrationsPanel({
529
604
  {/* Confirmation Modal */}
530
605
  {confirmModal && (
531
606
  <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
532
- <div className="bg-[#111] border border-[#333] rounded-lg p-6 w-full max-w-sm mx-4">
607
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded-lg p-6 w-full max-w-sm mx-4">
533
608
  <p className="text-center mb-4">{confirmModal.message}</p>
534
609
  <div className="flex gap-2">
535
610
  <button
536
611
  onClick={() => setConfirmModal(null)}
537
- className="flex-1 text-sm bg-[#1a1a1a] hover:bg-[#222] border border-[#333] px-4 py-2 rounded transition"
612
+ className="flex-1 text-sm bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] px-4 py-2 rounded transition"
538
613
  >
539
614
  Cancel
540
615
  </button>
@@ -574,14 +649,14 @@ export function IntegrationsPanel({
574
649
  value={search}
575
650
  onChange={(e) => setSearch(e.target.value)}
576
651
  placeholder="Search apps..."
577
- className="w-full bg-[#111] border border-[#333] rounded-lg px-4 py-2 focus:outline-none focus:border-[#f97316]"
652
+ className="w-full bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded-lg px-4 py-2 focus:outline-none focus:border-[var(--color-accent)]"
578
653
  />
579
654
  </div>
580
655
 
581
656
  {/* Connected Apps */}
582
657
  {connectedApps.length > 0 && (
583
658
  <div>
584
- <h3 className="text-sm font-medium text-[#888] mb-3">
659
+ <h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-3">
585
660
  Connected ({connectedApps.length})
586
661
  </h3>
587
662
  <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
@@ -589,14 +664,19 @@ export function IntegrationsPanel({
589
664
  <AppCard
590
665
  key={app.id}
591
666
  app={app}
592
- connection={getConnection(app.slug)}
667
+ connection={getConnection(app)}
593
668
  onConnect={() => connectApp(app)}
594
669
  onDisconnect={() => {
595
- const conn = getConnection(app.slug);
670
+ const conn = getConnection(app);
596
671
  if (conn) handleDisconnect(conn);
597
672
  }}
598
673
  onCreateMcpConfig={hideMcpConfig ? undefined : () => openMcpConfigModal(app)}
599
674
  onBrowseTriggers={onBrowseTriggers ? () => onBrowseTriggers(app.slug) : undefined}
675
+ onUpdateKey={supportsApiKey(app) ? () => {
676
+ setApiKeyModal({ app });
677
+ setApiKeyInput("");
678
+ setCredentialInputs({});
679
+ } : undefined}
600
680
  connecting={connecting === app.slug}
601
681
  />
602
682
  ))}
@@ -606,11 +686,11 @@ export function IntegrationsPanel({
606
686
 
607
687
  {/* Available Apps */}
608
688
  <div>
609
- <h3 className="text-sm font-medium text-[#888] mb-3">
689
+ <h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-3">
610
690
  Available Apps ({availableApps.length})
611
691
  </h3>
612
692
  {availableApps.length === 0 ? (
613
- <p className="text-[#666] text-sm">
693
+ <p className="text-[var(--color-text-muted)] text-sm">
614
694
  {search ? "No apps match your search" : "No apps available"}
615
695
  </p>
616
696
  ) : (
@@ -626,7 +706,7 @@ export function IntegrationsPanel({
626
706
  </div>
627
707
  )}
628
708
  {availableApps.length > 50 && (
629
- <p className="text-xs text-[#555] mt-3 text-center">
709
+ <p className="text-xs text-[var(--color-text-faint)] mt-3 text-center">
630
710
  Showing first 50 of {availableApps.length} apps. Use search to find more.
631
711
  </p>
632
712
  )}
@@ -643,6 +723,7 @@ function AppCard({
643
723
  onDisconnect,
644
724
  onCreateMcpConfig,
645
725
  onBrowseTriggers,
726
+ onUpdateKey,
646
727
  connecting,
647
728
  }: {
648
729
  app: IntegrationApp;
@@ -651,6 +732,7 @@ function AppCard({
651
732
  onDisconnect?: () => void;
652
733
  onCreateMcpConfig?: () => void;
653
734
  onBrowseTriggers?: () => void;
735
+ onUpdateKey?: () => void;
654
736
  connecting: boolean;
655
737
  }) {
656
738
  const isConnected = connection?.status === "active";
@@ -660,13 +742,13 @@ function AppCard({
660
742
 
661
743
  return (
662
744
  <div
663
- className={`bg-[#111] border rounded-lg p-3 transition ${
664
- isConnected ? "border-green-500/30" : "border-[#1a1a1a] hover:border-[#333]"
745
+ className={`bg-[var(--color-surface)] border rounded-lg p-3 transition ${
746
+ isConnected ? "border-green-500/30" : "border-[var(--color-border)] hover:border-[var(--color-border-light)]"
665
747
  }`}
666
748
  >
667
749
  <div className="flex items-start gap-3">
668
750
  {/* Logo */}
669
- <div className="w-10 h-10 rounded bg-[#1a1a1a] flex items-center justify-center flex-shrink-0 overflow-hidden">
751
+ <div className="w-10 h-10 rounded bg-[var(--color-surface-raised)] flex items-center justify-center flex-shrink-0 overflow-hidden">
670
752
  {app.logo ? (
671
753
  <img
672
754
  src={app.logo}
@@ -689,7 +771,7 @@ function AppCard({
689
771
  <span className="text-xs text-green-400">✓</span>
690
772
  )}
691
773
  {!isConnected && hasApiKey && !hasOAuth && (
692
- <span className="text-[10px] bg-[#222] text-[#888] px-1.5 py-0.5 rounded" title="Requires API Key">
774
+ <span className="text-[10px] bg-[var(--color-surface-raised)] text-[var(--color-text-secondary)] px-1.5 py-0.5 rounded" title="Requires API Key">
693
775
  API Key
694
776
  </span>
695
777
  )}
@@ -700,7 +782,7 @@ function AppCard({
700
782
  )}
701
783
  </div>
702
784
  {app.description && (
703
- <p className="text-xs text-[#666] line-clamp-2 mt-0.5">
785
+ <p className="text-xs text-[var(--color-text-muted)] line-clamp-2 mt-0.5">
704
786
  {app.description}
705
787
  </p>
706
788
  )}
@@ -709,7 +791,7 @@ function AppCard({
709
791
  {app.categories.slice(0, 2).map((cat) => (
710
792
  <span
711
793
  key={cat}
712
- className="text-[10px] bg-[#1a1a1a] text-[#555] px-1.5 py-0.5 rounded"
794
+ className="text-[10px] bg-[var(--color-surface-raised)] text-[var(--color-text-faint)] px-1.5 py-0.5 rounded"
713
795
  >
714
796
  {cat}
715
797
  </span>
@@ -739,10 +821,19 @@ function AppCard({
739
821
  Browse Triggers
740
822
  </button>
741
823
  )}
824
+ {onUpdateKey && (
825
+ <button
826
+ onClick={onUpdateKey}
827
+ className="text-xs text-[var(--color-text-muted)] hover:text-[var(--color-accent)] transition px-2"
828
+ title="Update API Key"
829
+ >
830
+ Key
831
+ </button>
832
+ )}
742
833
  {onDisconnect && (
743
834
  <button
744
835
  onClick={onDisconnect}
745
- className="text-xs text-[#666] hover:text-red-400 transition px-2"
836
+ className="text-xs text-[var(--color-text-muted)] hover:text-red-400 transition px-2"
746
837
  title="Disconnect"
747
838
  >
748
839
  ×
@@ -753,7 +844,7 @@ function AppCard({
753
844
  <button
754
845
  onClick={onConnect}
755
846
  disabled={connecting}
756
- className="w-full text-xs bg-[#1a1a1a] hover:bg-[#222] border border-[#333] hover:border-[#f97316] px-3 py-1.5 rounded transition disabled:opacity-50"
847
+ className="w-full text-xs bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] hover:border-[var(--color-accent)] px-3 py-1.5 rounded transition disabled:opacity-50"
757
848
  >
758
849
  {connecting ? "Connecting..." : (hasApiKey && !hasOAuth) ? "Enter API Key" : "Connect"}
759
850
  </button>