apteva 0.4.57 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +216 -54
  2. package/cli.js +35 -0
  3. package/install.js +92 -0
  4. package/package.json +12 -79
  5. package/LICENSE +0 -63
  6. package/bin/apteva.js +0 -196
  7. package/dist/ActivityPage.kxzzb4yc.js +0 -3
  8. package/dist/ApiDocsPage.zq998hbm.js +0 -4
  9. package/dist/App.55rea8mn.js +0 -61
  10. package/dist/App.5ywb23z4.js +0 -53
  11. package/dist/App.6thds120.js +0 -4
  12. package/dist/App.9tctxzqm.js +0 -8
  13. package/dist/App.a8r8ttaz.js +0 -4
  14. package/dist/App.agsv5bje.js +0 -4
  15. package/dist/App.cepapqmx.js +0 -4
  16. package/dist/App.dp041gb3.js +0 -221
  17. package/dist/App.fds72zb5.js +0 -4
  18. package/dist/App.fg9qj2dq.js +0 -4
  19. package/dist/App.ndfejbm9.js +0 -4
  20. package/dist/App.nxmfmq1h.js +0 -13
  21. package/dist/App.qdfyt8ba.js +0 -4
  22. package/dist/App.x2d0ygt6.js +0 -4
  23. package/dist/App.yt9p4nr3.js +0 -20
  24. package/dist/App.zn4mw16t.js +0 -1
  25. package/dist/ConnectionsPage.8r96ryw7.js +0 -3
  26. package/dist/McpPage.3cwh0gnd.js +0 -3
  27. package/dist/SettingsPage.ykgdh5ev.js +0 -3
  28. package/dist/SkillsPage.4np1s65b.js +0 -3
  29. package/dist/TasksPage.4g08t7p6.js +0 -3
  30. package/dist/TelemetryPage.72w9pwcp.js +0 -3
  31. package/dist/TestsPage.z4fk3r7r.js +0 -3
  32. package/dist/ThreadsPage.63tcajeh.js +0 -3
  33. package/dist/apteva-kit.css +0 -1
  34. package/dist/icon.png +0 -0
  35. package/dist/index.html +0 -16
  36. package/dist/styles.css +0 -1
  37. package/scripts/postinstall.mjs +0 -102
  38. package/src/auth/index.ts +0 -394
  39. package/src/auth/middleware.ts +0 -213
  40. package/src/binary.ts +0 -536
  41. package/src/channels/index.ts +0 -40
  42. package/src/channels/telegram.ts +0 -311
  43. package/src/crypto.ts +0 -301
  44. package/src/db-tests.ts +0 -174
  45. package/src/db.ts +0 -3133
  46. package/src/integrations/agentdojo.ts +0 -559
  47. package/src/integrations/composio.ts +0 -437
  48. package/src/integrations/index.ts +0 -87
  49. package/src/integrations/skillsmp.ts +0 -318
  50. package/src/mcp-client.ts +0 -605
  51. package/src/mcp-handler.ts +0 -394
  52. package/src/mcp-platform.ts +0 -2403
  53. package/src/openapi.ts +0 -2410
  54. package/src/providers.ts +0 -597
  55. package/src/routes/api/agent-utils.ts +0 -890
  56. package/src/routes/api/agents.ts +0 -916
  57. package/src/routes/api/api-keys.ts +0 -95
  58. package/src/routes/api/channels.ts +0 -182
  59. package/src/routes/api/helpers.ts +0 -12
  60. package/src/routes/api/integrations.ts +0 -639
  61. package/src/routes/api/mcp.ts +0 -574
  62. package/src/routes/api/meta-agent.ts +0 -195
  63. package/src/routes/api/projects.ts +0 -112
  64. package/src/routes/api/providers.ts +0 -424
  65. package/src/routes/api/skills.ts +0 -537
  66. package/src/routes/api/system.ts +0 -333
  67. package/src/routes/api/telemetry.ts +0 -203
  68. package/src/routes/api/tests.ts +0 -148
  69. package/src/routes/api/triggers.ts +0 -518
  70. package/src/routes/api/users.ts +0 -148
  71. package/src/routes/api/webhooks.ts +0 -171
  72. package/src/routes/api.ts +0 -53
  73. package/src/routes/auth.ts +0 -251
  74. package/src/routes/share.ts +0 -86
  75. package/src/routes/static.ts +0 -131
  76. package/src/server.ts +0 -642
  77. package/src/test-runner.ts +0 -598
  78. package/src/triggers/agentdojo.ts +0 -253
  79. package/src/triggers/composio.ts +0 -264
  80. package/src/triggers/index.ts +0 -71
  81. package/src/tui/AgentList.tsx +0 -145
  82. package/src/tui/App.tsx +0 -102
  83. package/src/tui/Login.tsx +0 -104
  84. package/src/tui/api.ts +0 -72
  85. package/src/tui/index.tsx +0 -7
  86. package/src/web/App.tsx +0 -455
  87. package/src/web/components/activity/ActivityPage.tsx +0 -314
  88. package/src/web/components/activity/index.ts +0 -1
  89. package/src/web/components/agents/AgentCard.tsx +0 -189
  90. package/src/web/components/agents/AgentPanel.tsx +0 -2244
  91. package/src/web/components/agents/AgentsView.tsx +0 -180
  92. package/src/web/components/agents/CreateAgentModal.tsx +0 -475
  93. package/src/web/components/agents/index.ts +0 -4
  94. package/src/web/components/api/ApiDocsPage.tsx +0 -842
  95. package/src/web/components/auth/CreateAccountStep.tsx +0 -176
  96. package/src/web/components/auth/LoginPage.tsx +0 -91
  97. package/src/web/components/auth/index.ts +0 -2
  98. package/src/web/components/common/Icons.tsx +0 -250
  99. package/src/web/components/common/LoadingSpinner.tsx +0 -44
  100. package/src/web/components/common/Modal.tsx +0 -199
  101. package/src/web/components/common/Select.tsx +0 -97
  102. package/src/web/components/common/index.ts +0 -20
  103. package/src/web/components/connections/ConnectionsPage.tsx +0 -54
  104. package/src/web/components/connections/IntegrationsTab.tsx +0 -170
  105. package/src/web/components/connections/OverviewTab.tsx +0 -137
  106. package/src/web/components/connections/TriggersTab.tsx +0 -1346
  107. package/src/web/components/dashboard/Dashboard.tsx +0 -572
  108. package/src/web/components/dashboard/index.ts +0 -1
  109. package/src/web/components/index.ts +0 -21
  110. package/src/web/components/layout/ErrorBanner.tsx +0 -18
  111. package/src/web/components/layout/Header.tsx +0 -332
  112. package/src/web/components/layout/Sidebar.tsx +0 -231
  113. package/src/web/components/layout/index.ts +0 -3
  114. package/src/web/components/mcp/IntegrationsPanel.tsx +0 -857
  115. package/src/web/components/mcp/McpPage.tsx +0 -2515
  116. package/src/web/components/mcp/index.ts +0 -1
  117. package/src/web/components/meta-agent/MetaAgent.tsx +0 -245
  118. package/src/web/components/onboarding/OnboardingWizard.tsx +0 -404
  119. package/src/web/components/onboarding/index.ts +0 -1
  120. package/src/web/components/settings/SettingsPage.tsx +0 -2776
  121. package/src/web/components/settings/index.ts +0 -1
  122. package/src/web/components/skills/SkillsPage.tsx +0 -1200
  123. package/src/web/components/tasks/TasksPage.tsx +0 -1116
  124. package/src/web/components/tasks/index.ts +0 -1
  125. package/src/web/components/telemetry/TelemetryPage.tsx +0 -1129
  126. package/src/web/components/tests/TestsPage.tsx +0 -594
  127. package/src/web/components/threads/ThreadsPage.tsx +0 -315
  128. package/src/web/context/AuthContext.tsx +0 -242
  129. package/src/web/context/ProjectContext.tsx +0 -214
  130. package/src/web/context/TelemetryContext.tsx +0 -299
  131. package/src/web/context/ThemeContext.tsx +0 -90
  132. package/src/web/context/UIModeContext.tsx +0 -49
  133. package/src/web/context/index.ts +0 -12
  134. package/src/web/hooks/index.ts +0 -3
  135. package/src/web/hooks/useAgents.ts +0 -115
  136. package/src/web/hooks/useOnboarding.ts +0 -20
  137. package/src/web/hooks/useProviders.ts +0 -75
  138. package/src/web/icon.png +0 -0
  139. package/src/web/index.html +0 -16
  140. package/src/web/styles.css +0 -118
  141. package/src/web/themes.ts +0 -162
  142. package/src/web/types.ts +0 -298
@@ -1,857 +0,0 @@
1
- import React, { useState, useEffect, useCallback } from "react";
2
- import { useAuth } from "../../context";
3
-
4
- // Types
5
- interface IntegrationApp {
6
- id: string;
7
- name: string;
8
- slug: string;
9
- description: string | null;
10
- logo: string | null;
11
- categories: string[];
12
- authSchemes: string[];
13
- providerSlug?: string;
14
- credentialFields?: {
15
- name: string;
16
- description?: string;
17
- required?: boolean;
18
- }[];
19
- }
20
-
21
- interface ConnectedAccount {
22
- id: string;
23
- appId: string;
24
- appName: string;
25
- status: "active" | "pending" | "failed" | "expired";
26
- createdAt: string;
27
- }
28
-
29
- interface IntegrationProvider {
30
- id: string;
31
- name: string;
32
- connected: boolean;
33
- }
34
-
35
- // Check if app supports API_KEY auth
36
- function supportsApiKey(app: IntegrationApp): boolean {
37
- return app.authSchemes.some(s => s.toUpperCase() === "API_KEY");
38
- }
39
-
40
- // Check if app supports OAuth
41
- function supportsOAuth(app: IntegrationApp): boolean {
42
- return app.authSchemes.some(s => s.toUpperCase() === "OAUTH2");
43
- }
44
-
45
- // Check if app supports multiple auth methods
46
- function hasMultipleAuthMethods(app: IntegrationApp): boolean {
47
- return supportsApiKey(app) && supportsOAuth(app);
48
- }
49
-
50
- // Main component
51
- export function IntegrationsPanel({
52
- providerId = "composio",
53
- projectId,
54
- onConnectionComplete,
55
- onBrowseTriggers,
56
- hideMcpConfig,
57
- }: {
58
- providerId?: string;
59
- projectId?: string | null;
60
- onConnectionComplete?: () => void;
61
- onBrowseTriggers?: (toolkitSlug: string) => void;
62
- hideMcpConfig?: boolean;
63
- }) {
64
- const { authFetch } = useAuth();
65
- const [apps, setApps] = useState<IntegrationApp[]>([]);
66
- const [connectedAccounts, setConnectedAccounts] = useState<ConnectedAccount[]>([]);
67
- const [loading, setLoading] = useState(true);
68
- const [search, setSearch] = useState("");
69
- const [connecting, setConnecting] = useState<string | null>(null);
70
- const [pendingConnection, setPendingConnection] = useState<{
71
- appSlug: string;
72
- connectionId?: string;
73
- } | null>(null);
74
- const [error, setError] = useState<string | null>(null);
75
- // For auth method selection (when app supports both OAuth and API Key)
76
- const [authMethodModal, setAuthMethodModal] = useState<{ app: IntegrationApp } | null>(null);
77
- // For API Key / credential modal
78
- const [apiKeyModal, setApiKeyModal] = useState<{ app: IntegrationApp } | null>(null);
79
- const [apiKeyInput, setApiKeyInput] = useState("");
80
- const [credentialInputs, setCredentialInputs] = useState<Record<string, string>>({});
81
- // For MCP config creation modal
82
- const [mcpConfigModal, setMcpConfigModal] = useState<{ app: IntegrationApp } | null>(null);
83
- const [mcpConfigName, setMcpConfigName] = useState("");
84
- const [mcpConfigCreating, setMcpConfigCreating] = useState(false);
85
- const [mcpConfigSuccess, setMcpConfigSuccess] = useState<string | null>(null);
86
- // For confirmation modal
87
- const [confirmModal, setConfirmModal] = useState<{
88
- message: string;
89
- onConfirm: () => void;
90
- } | null>(null);
91
-
92
- // Fetch apps and connected accounts
93
- const fetchData = useCallback(async () => {
94
- setLoading(true);
95
- setError(null);
96
- const projectParam = projectId && projectId !== "unassigned" ? `?project_id=${projectId}` : "";
97
- try {
98
- const [appsRes, connectedRes] = await Promise.all([
99
- authFetch(`/api/integrations/${providerId}/apps${projectParam}`),
100
- authFetch(`/api/integrations/${providerId}/connected${projectParam}`),
101
- ]);
102
-
103
- const appsData = await appsRes.json();
104
- const connectedData = await connectedRes.json();
105
-
106
- setApps(appsData.apps || []);
107
- setConnectedAccounts(connectedData.accounts || []);
108
- } catch (e) {
109
- console.error("Failed to fetch integrations:", e);
110
- setError("Failed to load integrations");
111
- }
112
- setLoading(false);
113
- }, [authFetch, providerId, projectId]);
114
-
115
- useEffect(() => {
116
- fetchData();
117
- }, [fetchData]);
118
-
119
- // Check for connection completion from URL params
120
- useEffect(() => {
121
- const params = new URLSearchParams(window.location.search);
122
- const connectedApp = params.get("connected");
123
- if (connectedApp) {
124
- // Remove the query param
125
- window.history.replaceState({}, "", window.location.pathname);
126
- // Refresh to show new connection
127
- fetchData();
128
- onConnectionComplete?.();
129
- }
130
- }, [fetchData, onConnectionComplete]);
131
-
132
- // Poll for pending connection status
133
- useEffect(() => {
134
- if (!pendingConnection?.connectionId) return;
135
- const projectParam = projectId && projectId !== "unassigned" ? `?project_id=${projectId}` : "";
136
-
137
- const pollInterval = setInterval(async () => {
138
- try {
139
- const res = await authFetch(
140
- `/api/integrations/${providerId}/connection/${pendingConnection.connectionId}${projectParam}`
141
- );
142
- const data = await res.json();
143
-
144
- if (data.connection?.status === "active") {
145
- setPendingConnection(null);
146
- setConnecting(null);
147
- fetchData();
148
- onConnectionComplete?.();
149
- } else if (data.connection?.status === "failed") {
150
- setPendingConnection(null);
151
- setConnecting(null);
152
- setError(`Connection to ${pendingConnection.appSlug} failed`);
153
- }
154
- } catch (e) {
155
- // Keep polling
156
- }
157
- }, 2000);
158
-
159
- return () => clearInterval(pollInterval);
160
- }, [pendingConnection, authFetch, providerId, projectId, fetchData, onConnectionComplete]);
161
-
162
- // Initiate connection
163
- const connectApp = async (app: IntegrationApp, apiKey?: string, forceOAuth?: boolean, fields?: Record<string, string>) => {
164
- // If app supports multiple auth methods and user hasn't chosen, show choice
165
- if (hasMultipleAuthMethods(app) && !apiKey && !fields && !forceOAuth) {
166
- setAuthMethodModal({ app });
167
- return;
168
- }
169
-
170
- // If app supports API key (and user didn't choose OAuth), show credential modal
171
- if (supportsApiKey(app) && !apiKey && !fields && !forceOAuth) {
172
- setApiKeyModal({ app });
173
- setApiKeyInput("");
174
- setCredentialInputs({});
175
- return;
176
- }
177
-
178
- setConnecting(app.slug);
179
- setError(null);
180
-
181
- try {
182
- // Build request body
183
- const body: any = { appSlug: app.slug };
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) {
191
- body.credentials = {
192
- authScheme: "API_KEY",
193
- apiKey,
194
- };
195
- }
196
-
197
- const projectParam = projectId && projectId !== "unassigned" ? `?project_id=${projectId}` : "";
198
- const res = await authFetch(`/api/integrations/${providerId}/connect${projectParam}`, {
199
- method: "POST",
200
- headers: { "Content-Type": "application/json" },
201
- body: JSON.stringify(body),
202
- });
203
-
204
- const data = await res.json();
205
-
206
- if (!res.ok) {
207
- setError(data.error || "Failed to initiate connection");
208
- setConnecting(null);
209
- setApiKeyModal(null);
210
- return;
211
- }
212
-
213
- // API_KEY connections are immediately active (no redirect)
214
- if (data.status === "active" || !data.redirectUrl) {
215
- setConnecting(null);
216
- setApiKeyModal(null);
217
- fetchData();
218
- onConnectionComplete?.();
219
- return;
220
- }
221
-
222
- if (data.redirectUrl) {
223
- // Store pending connection for polling
224
- setPendingConnection({
225
- appSlug: app.slug,
226
- connectionId: data.connectionId,
227
- });
228
-
229
- // Open OAuth in popup
230
- const popup = window.open(
231
- data.redirectUrl,
232
- `connect-${app.slug}`,
233
- "width=600,height=700,left=200,top=100"
234
- );
235
-
236
- // If popup blocked, redirect instead
237
- if (!popup || popup.closed) {
238
- window.location.href = data.redirectUrl;
239
- }
240
- }
241
- } catch (e) {
242
- setError(`Failed to connect: ${e}`);
243
- setConnecting(null);
244
- setApiKeyModal(null);
245
- }
246
- };
247
-
248
- // Handle API key / credential form submission
249
- const handleApiKeySubmit = (e: React.FormEvent) => {
250
- e.preventDefault();
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
- }
263
- };
264
-
265
- // Disconnect (called after confirmation)
266
- const disconnectApp = async (account: ConnectedAccount) => {
267
- const projectParam = projectId && projectId !== "unassigned" ? `?project_id=${projectId}` : "";
268
- try {
269
- const res = await authFetch(
270
- `/api/integrations/${providerId}/connection/${account.id}${projectParam}`,
271
- { method: "DELETE" }
272
- );
273
-
274
- if (res.ok) {
275
- fetchData();
276
- } else {
277
- const data = await res.json();
278
- setError(data.error || "Failed to disconnect");
279
- }
280
- } catch (e) {
281
- setError(`Failed to disconnect: ${e}`);
282
- }
283
- };
284
-
285
- // Open MCP config creation modal
286
- const openMcpConfigModal = (app: IntegrationApp) => {
287
- setMcpConfigModal({ app });
288
- setMcpConfigName(`${app.name} MCP`);
289
- setMcpConfigSuccess(null);
290
- };
291
-
292
- // Create MCP config from connected app
293
- const createMcpConfig = async () => {
294
- if (!mcpConfigModal || !mcpConfigName.trim()) return;
295
-
296
- setMcpConfigCreating(true);
297
- setError(null);
298
-
299
- try {
300
- const projectParam = projectId && projectId !== "unassigned" ? `?project_id=${projectId}` : "";
301
- const res = await authFetch(`/api/integrations/${providerId}/configs${projectParam}`, {
302
- method: "POST",
303
- headers: { "Content-Type": "application/json" },
304
- body: JSON.stringify({
305
- name: mcpConfigName.replace(/[^a-zA-Z0-9\s-]/g, "").substring(0, 30),
306
- toolkitSlug: mcpConfigModal.app.slug,
307
- }),
308
- });
309
-
310
- const data = await res.json();
311
-
312
- if (!res.ok) {
313
- setError(data.error || "Failed to create MCP config");
314
- setMcpConfigCreating(false);
315
- return;
316
- }
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
-
332
- setMcpConfigSuccess(mcpConfigName);
333
- onConnectionComplete?.();
334
- } catch (e) {
335
- setError(`Failed to create MCP config: ${e}`);
336
- } finally {
337
- setMcpConfigCreating(false);
338
- }
339
- };
340
-
341
- // Handle disconnect with confirmation modal
342
- const handleDisconnect = (account: ConnectedAccount) => {
343
- setConfirmModal({
344
- message: `Disconnect ${account.appName}?`,
345
- onConfirm: () => {
346
- disconnectApp(account);
347
- setConfirmModal(null);
348
- },
349
- });
350
- };
351
-
352
- // Check if app is connected (also matches via providerSlug for multi-toolkit providers)
353
- const isConnected = (app: IntegrationApp) => {
354
- return connectedAccounts.some(
355
- (a) => a.status === "active" && (
356
- a.appId === app.slug ||
357
- (app.providerSlug && a.appId === app.providerSlug)
358
- )
359
- );
360
- };
361
-
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;
369
- };
370
-
371
- // Filter apps
372
- const filteredApps = apps.filter((app) => {
373
- if (!search) return true;
374
- const s = search.toLowerCase();
375
- return (
376
- app.name.toLowerCase().includes(s) ||
377
- app.slug.toLowerCase().includes(s) ||
378
- app.description?.toLowerCase().includes(s) ||
379
- app.categories.some((c) => c.toLowerCase().includes(s))
380
- );
381
- });
382
-
383
- // Group by connected/not connected
384
- const connectedApps = filteredApps.filter((app) => isConnected(app));
385
- const availableApps = filteredApps.filter((app) => !isConnected(app));
386
-
387
- if (loading) {
388
- return <div className="text-center py-8 text-[var(--color-text-muted)]">Loading apps...</div>;
389
- }
390
-
391
- return (
392
- <div className="space-y-6">
393
- {/* Auth Method Choice Modal */}
394
- {authMethodModal && (
395
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
396
- <div className="bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded-lg p-6 w-full max-w-md mx-4">
397
- <div className="flex items-center gap-3 mb-4">
398
- {authMethodModal.app.logo && (
399
- <img
400
- src={authMethodModal.app.logo}
401
- alt={authMethodModal.app.name}
402
- className="w-10 h-10 object-contain"
403
- />
404
- )}
405
- <div>
406
- <h3 className="font-medium">Connect {authMethodModal.app.name}</h3>
407
- <p className="text-xs text-[var(--color-text-muted)]">Choose how to authenticate</p>
408
- </div>
409
- </div>
410
- <div className="space-y-3">
411
- <button
412
- onClick={() => {
413
- setAuthMethodModal(null);
414
- setApiKeyModal({ app: authMethodModal.app });
415
- setApiKeyInput("");
416
- setCredentialInputs({});
417
- }}
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"
419
- >
420
- <div className="font-medium text-sm">API Key</div>
421
- <div className="text-xs text-[var(--color-text-muted)] mt-0.5">
422
- Enter your {authMethodModal.app.name} API key directly
423
- </div>
424
- </button>
425
- <button
426
- onClick={() => {
427
- setAuthMethodModal(null);
428
- connectApp(authMethodModal.app, undefined, true);
429
- }}
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"
431
- >
432
- <div className="font-medium text-sm">OAuth</div>
433
- <div className="text-xs text-[var(--color-text-muted)] mt-0.5">
434
- Sign in with your {authMethodModal.app.name} account
435
- </div>
436
- </button>
437
- </div>
438
- <button
439
- onClick={() => setAuthMethodModal(null)}
440
- className="w-full text-sm text-[var(--color-text-muted)] hover:text-white mt-4 py-2 transition"
441
- >
442
- Cancel
443
- </button>
444
- </div>
445
- </div>
446
- )}
447
-
448
- {/* API Key / Credentials Modal */}
449
- {apiKeyModal && (
450
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
451
- <div className="bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded-lg p-6 w-full max-w-md mx-4">
452
- <div className="flex items-center gap-3 mb-4">
453
- {apiKeyModal.app.logo && (
454
- <img
455
- src={apiKeyModal.app.logo}
456
- alt={apiKeyModal.app.name}
457
- className="w-10 h-10 object-contain"
458
- />
459
- )}
460
- <div>
461
- <h3 className="font-medium">Connect {apiKeyModal.app.name}</h3>
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>
467
- </div>
468
- </div>
469
- <form onSubmit={handleApiKeySubmit}>
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
- )}
502
- <div className="flex gap-2">
503
- <button
504
- type="button"
505
- onClick={() => setApiKeyModal(null)}
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"
507
- >
508
- Cancel
509
- </button>
510
- <button
511
- type="submit"
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"
519
- >
520
- {connecting === apiKeyModal.app.slug ? "Connecting..." : "Connect"}
521
- </button>
522
- </div>
523
- </form>
524
- </div>
525
- </div>
526
- )}
527
-
528
- {/* MCP Config Creation Modal */}
529
- {mcpConfigModal && (
530
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
531
- <div className="bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded-lg p-6 w-full max-w-md mx-4">
532
- {mcpConfigSuccess ? (
533
- <>
534
- <div className="text-center mb-4">
535
- <div className="w-12 h-12 bg-green-500/20 rounded-full flex items-center justify-center mx-auto mb-3">
536
- <span className="text-green-400 text-2xl">✓</span>
537
- </div>
538
- <h3 className="font-medium text-lg">MCP Config Created!</h3>
539
- <p className="text-sm text-[var(--color-text-secondary)] mt-2">
540
- "{mcpConfigSuccess}" has been created and added to your servers.
541
- </p>
542
- </div>
543
- <button
544
- onClick={() => {
545
- setMcpConfigModal(null);
546
- setMcpConfigSuccess(null);
547
- }}
548
- className="w-full text-sm bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-white px-4 py-2 rounded transition"
549
- >
550
- Done
551
- </button>
552
- </>
553
- ) : (
554
- <>
555
- <div className="flex items-center gap-3 mb-4">
556
- {mcpConfigModal.app.logo && (
557
- <img
558
- src={mcpConfigModal.app.logo}
559
- alt={mcpConfigModal.app.name}
560
- className="w-10 h-10 object-contain"
561
- />
562
- )}
563
- <div>
564
- <h3 className="font-medium">Create MCP Config</h3>
565
- <p className="text-xs text-[var(--color-text-muted)]">
566
- Create an MCP config for {mcpConfigModal.app.name}
567
- </p>
568
- </div>
569
- </div>
570
- <form onSubmit={(e) => { e.preventDefault(); createMcpConfig(); }}>
571
- <label className="block text-xs text-[var(--color-text-secondary)] mb-1">Config Name</label>
572
- <input
573
- type="text"
574
- value={mcpConfigName}
575
- onChange={(e) => setMcpConfigName(e.target.value)}
576
- placeholder="Enter config name..."
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)]"
578
- autoFocus
579
- maxLength={30}
580
- />
581
- <div className="flex gap-2">
582
- <button
583
- type="button"
584
- onClick={() => setMcpConfigModal(null)}
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"
586
- >
587
- Cancel
588
- </button>
589
- <button
590
- type="submit"
591
- disabled={!mcpConfigName.trim() || mcpConfigCreating}
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"
593
- >
594
- {mcpConfigCreating ? "Creating..." : "Create Config"}
595
- </button>
596
- </div>
597
- </form>
598
- </>
599
- )}
600
- </div>
601
- </div>
602
- )}
603
-
604
- {/* Confirmation Modal */}
605
- {confirmModal && (
606
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
607
- <div className="bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded-lg p-6 w-full max-w-sm mx-4">
608
- <p className="text-center mb-4">{confirmModal.message}</p>
609
- <div className="flex gap-2">
610
- <button
611
- onClick={() => setConfirmModal(null)}
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"
613
- >
614
- Cancel
615
- </button>
616
- <button
617
- onClick={confirmModal.onConfirm}
618
- className="flex-1 text-sm bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded transition"
619
- >
620
- Confirm
621
- </button>
622
- </div>
623
- </div>
624
- </div>
625
- )}
626
-
627
- {/* Error */}
628
- {error && (
629
- <div className="text-red-400 text-sm p-3 bg-red-500/10 border border-red-500/20 rounded-lg flex items-center justify-between">
630
- <span>{error}</span>
631
- <button onClick={() => setError(null)} className="text-red-400 hover:text-red-300">
632
- ×
633
- </button>
634
- </div>
635
- )}
636
-
637
- {/* Pending connection notice */}
638
- {pendingConnection && (
639
- <div className="text-yellow-400 text-sm p-3 bg-yellow-500/10 border border-yellow-500/20 rounded-lg flex items-center gap-2">
640
- <span className="animate-spin">⟳</span>
641
- <span>Waiting for {pendingConnection.appSlug} authorization...</span>
642
- </div>
643
- )}
644
-
645
- {/* Search */}
646
- <div>
647
- <input
648
- type="text"
649
- value={search}
650
- onChange={(e) => setSearch(e.target.value)}
651
- placeholder="Search apps..."
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)]"
653
- />
654
- </div>
655
-
656
- {/* Connected Apps */}
657
- {connectedApps.length > 0 && (
658
- <div>
659
- <h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-3">
660
- Connected ({connectedApps.length})
661
- </h3>
662
- <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
663
- {connectedApps.map((app) => (
664
- <AppCard
665
- key={app.id}
666
- app={app}
667
- connection={getConnection(app)}
668
- onConnect={() => connectApp(app)}
669
- onDisconnect={() => {
670
- const conn = getConnection(app);
671
- if (conn) handleDisconnect(conn);
672
- }}
673
- onCreateMcpConfig={hideMcpConfig ? undefined : () => openMcpConfigModal(app)}
674
- onBrowseTriggers={onBrowseTriggers ? () => onBrowseTriggers(app.slug) : undefined}
675
- onUpdateKey={supportsApiKey(app) ? () => {
676
- setApiKeyModal({ app });
677
- setApiKeyInput("");
678
- setCredentialInputs({});
679
- } : undefined}
680
- connecting={connecting === app.slug}
681
- />
682
- ))}
683
- </div>
684
- </div>
685
- )}
686
-
687
- {/* Available Apps */}
688
- <div>
689
- <h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-3">
690
- Available Apps ({availableApps.length})
691
- </h3>
692
- {availableApps.length === 0 ? (
693
- <p className="text-[var(--color-text-muted)] text-sm">
694
- {search ? "No apps match your search" : "No apps available"}
695
- </p>
696
- ) : (
697
- <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
698
- {availableApps.slice(0, 50).map((app) => (
699
- <AppCard
700
- key={app.id}
701
- app={app}
702
- onConnect={() => connectApp(app)}
703
- connecting={connecting === app.slug}
704
- />
705
- ))}
706
- </div>
707
- )}
708
- {availableApps.length > 50 && (
709
- <p className="text-xs text-[var(--color-text-faint)] mt-3 text-center">
710
- Showing first 50 of {availableApps.length} apps. Use search to find more.
711
- </p>
712
- )}
713
- </div>
714
- </div>
715
- );
716
- }
717
-
718
- // App card component
719
- function AppCard({
720
- app,
721
- connection,
722
- onConnect,
723
- onDisconnect,
724
- onCreateMcpConfig,
725
- onBrowseTriggers,
726
- onUpdateKey,
727
- connecting,
728
- }: {
729
- app: IntegrationApp;
730
- connection?: ConnectedAccount;
731
- onConnect: () => void;
732
- onDisconnect?: () => void;
733
- onCreateMcpConfig?: () => void;
734
- onBrowseTriggers?: () => void;
735
- onUpdateKey?: () => void;
736
- connecting: boolean;
737
- }) {
738
- const isConnected = connection?.status === "active";
739
- const hasApiKey = supportsApiKey(app);
740
- const hasOAuth = supportsOAuth(app);
741
- const hasBothMethods = hasApiKey && hasOAuth;
742
-
743
- return (
744
- <div
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)]"
747
- }`}
748
- >
749
- <div className="flex items-start gap-3">
750
- {/* Logo */}
751
- <div className="w-10 h-10 rounded bg-[var(--color-surface-raised)] flex items-center justify-center flex-shrink-0 overflow-hidden">
752
- {app.logo ? (
753
- <img
754
- src={app.logo}
755
- alt={app.name}
756
- className="w-8 h-8 object-contain"
757
- onError={(e) => {
758
- (e.target as HTMLImageElement).style.display = "none";
759
- }}
760
- />
761
- ) : (
762
- <span className="text-lg">{app.name[0]?.toUpperCase()}</span>
763
- )}
764
- </div>
765
-
766
- {/* Info */}
767
- <div className="flex-1 min-w-0">
768
- <div className="flex items-center gap-2">
769
- <h4 className="font-medium text-sm truncate">{app.name}</h4>
770
- {isConnected && (
771
- <span className="text-xs text-green-400">✓</span>
772
- )}
773
- {!isConnected && hasApiKey && !hasOAuth && (
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">
775
- API Key
776
- </span>
777
- )}
778
- {!isConnected && hasBothMethods && (
779
- <span className="text-[10px] bg-[#1a2a1a] text-[#6a6] px-1.5 py-0.5 rounded" title="Supports API Key or OAuth">
780
- API Key / OAuth
781
- </span>
782
- )}
783
- </div>
784
- {app.description && (
785
- <p className="text-xs text-[var(--color-text-muted)] line-clamp-2 mt-0.5">
786
- {app.description}
787
- </p>
788
- )}
789
- {app.categories.length > 0 && (
790
- <div className="flex flex-wrap gap-1 mt-1">
791
- {app.categories.slice(0, 2).map((cat) => (
792
- <span
793
- key={cat}
794
- className="text-[10px] bg-[var(--color-surface-raised)] text-[var(--color-text-faint)] px-1.5 py-0.5 rounded"
795
- >
796
- {cat}
797
- </span>
798
- ))}
799
- </div>
800
- )}
801
- </div>
802
- </div>
803
-
804
- {/* Actions */}
805
- <div className="mt-3 flex gap-2">
806
- {isConnected ? (
807
- <>
808
- {onCreateMcpConfig && (
809
- <button
810
- onClick={onCreateMcpConfig}
811
- className="flex-1 text-xs bg-[#1a2a1a] hover:bg-[#1a3a1a] border border-green-500/30 hover:border-green-500/50 text-green-400 px-3 py-1.5 rounded transition"
812
- >
813
- Create MCP Config
814
- </button>
815
- )}
816
- {onBrowseTriggers && (
817
- <button
818
- onClick={onBrowseTriggers}
819
- className="flex-1 text-xs bg-[#1a1a2a] hover:bg-[#1a1a3a] border border-blue-500/30 hover:border-blue-500/50 text-blue-400 px-3 py-1.5 rounded transition"
820
- >
821
- Browse Triggers
822
- </button>
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
- )}
833
- {onDisconnect && (
834
- <button
835
- onClick={onDisconnect}
836
- className="text-xs text-[var(--color-text-muted)] hover:text-red-400 transition px-2"
837
- title="Disconnect"
838
- >
839
- ×
840
- </button>
841
- )}
842
- </>
843
- ) : (
844
- <button
845
- onClick={onConnect}
846
- disabled={connecting}
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"
848
- >
849
- {connecting ? "Connecting..." : (hasApiKey && !hasOAuth) ? "Enter API Key" : "Connect"}
850
- </button>
851
- )}
852
- </div>
853
- </div>
854
- );
855
- }
856
-
857
- export default IntegrationsPanel;