cowork-os 0.3.21 → 0.3.23

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 (170) hide show
  1. package/README.md +293 -6
  2. package/connectors/README.md +20 -0
  3. package/connectors/asana-mcp/README.md +24 -0
  4. package/connectors/asana-mcp/dist/index.js +427 -0
  5. package/connectors/asana-mcp/package.json +15 -0
  6. package/connectors/asana-mcp/src/index.ts +553 -0
  7. package/connectors/asana-mcp/tsconfig.json +13 -0
  8. package/connectors/hubspot-mcp/README.md +35 -0
  9. package/connectors/hubspot-mcp/dist/index.js +454 -0
  10. package/connectors/hubspot-mcp/package.json +15 -0
  11. package/connectors/hubspot-mcp/src/index.ts +562 -0
  12. package/connectors/hubspot-mcp/tsconfig.json +13 -0
  13. package/connectors/jira-mcp/README.md +49 -0
  14. package/connectors/jira-mcp/dist/index.js +588 -0
  15. package/connectors/jira-mcp/package.json +15 -0
  16. package/connectors/jira-mcp/src/index.ts +711 -0
  17. package/connectors/jira-mcp/tsconfig.json +13 -0
  18. package/connectors/linear-mcp/README.md +22 -0
  19. package/connectors/linear-mcp/dist/index.js +402 -0
  20. package/connectors/linear-mcp/package.json +15 -0
  21. package/connectors/linear-mcp/src/index.ts +522 -0
  22. package/connectors/linear-mcp/tsconfig.json +13 -0
  23. package/connectors/okta-mcp/README.md +24 -0
  24. package/connectors/okta-mcp/dist/index.js +411 -0
  25. package/connectors/okta-mcp/package.json +15 -0
  26. package/connectors/okta-mcp/src/index.ts +520 -0
  27. package/connectors/okta-mcp/tsconfig.json +13 -0
  28. package/connectors/salesforce-mcp/README.md +47 -0
  29. package/connectors/salesforce-mcp/dist/index.js +584 -0
  30. package/connectors/salesforce-mcp/package.json +15 -0
  31. package/connectors/salesforce-mcp/src/index.ts +722 -0
  32. package/connectors/salesforce-mcp/tsconfig.json +13 -0
  33. package/connectors/servicenow-mcp/README.md +26 -0
  34. package/connectors/servicenow-mcp/dist/index.js +400 -0
  35. package/connectors/servicenow-mcp/package.json +15 -0
  36. package/connectors/servicenow-mcp/src/index.ts +500 -0
  37. package/connectors/servicenow-mcp/tsconfig.json +13 -0
  38. package/connectors/templates/mcp-connector/README.md +31 -0
  39. package/connectors/templates/mcp-connector/package.json +15 -0
  40. package/connectors/templates/mcp-connector/src/index.ts +330 -0
  41. package/connectors/templates/mcp-connector/tsconfig.json +13 -0
  42. package/connectors/zendesk-mcp/README.md +40 -0
  43. package/connectors/zendesk-mcp/dist/index.js +431 -0
  44. package/connectors/zendesk-mcp/package.json +15 -0
  45. package/connectors/zendesk-mcp/src/index.ts +543 -0
  46. package/connectors/zendesk-mcp/tsconfig.json +13 -0
  47. package/dist/electron/electron/agent/daemon.js +25 -0
  48. package/dist/electron/electron/agent/executor.js +181 -26
  49. package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
  50. package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
  51. package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
  52. package/dist/electron/electron/agent/llm/index.js +11 -1
  53. package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
  54. package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
  55. package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
  56. package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
  57. package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
  58. package/dist/electron/electron/agent/llm/provider-factory.js +318 -4
  59. package/dist/electron/electron/agent/llm/types.js +66 -1
  60. package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
  61. package/dist/electron/electron/agent/tools/box-tools.js +231 -0
  62. package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
  63. package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
  64. package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
  65. package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
  66. package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
  67. package/dist/electron/electron/agent/tools/registry.js +541 -0
  68. package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
  69. package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
  70. package/dist/electron/electron/agent/tools/x-tools.js +1 -1
  71. package/dist/electron/electron/gateway/index.js +1 -0
  72. package/dist/electron/electron/gateway/router.js +123 -143
  73. package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
  74. package/dist/electron/electron/ipc/handlers.js +627 -158
  75. package/dist/electron/electron/main.js +63 -0
  76. package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
  77. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
  78. package/dist/electron/electron/memory/MemoryService.js +1 -1
  79. package/dist/electron/electron/preload.js +74 -1
  80. package/dist/electron/electron/settings/box-manager.js +54 -0
  81. package/dist/electron/electron/settings/dropbox-manager.js +54 -0
  82. package/dist/electron/electron/settings/google-drive-manager.js +54 -0
  83. package/dist/electron/electron/settings/notion-manager.js +56 -0
  84. package/dist/electron/electron/settings/onedrive-manager.js +54 -0
  85. package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
  86. package/dist/electron/electron/utils/box-api.js +153 -0
  87. package/dist/electron/electron/utils/dropbox-api.js +144 -0
  88. package/dist/electron/electron/utils/env-migration.js +19 -0
  89. package/dist/electron/electron/utils/google-drive-api.js +152 -0
  90. package/dist/electron/electron/utils/notion-api.js +103 -0
  91. package/dist/electron/electron/utils/onedrive-api.js +113 -0
  92. package/dist/electron/electron/utils/sharepoint-api.js +109 -0
  93. package/dist/electron/electron/utils/validation.js +82 -3
  94. package/dist/electron/electron/utils/x-cli.js +1 -1
  95. package/dist/electron/shared/channelMessages.js +284 -3
  96. package/dist/electron/shared/llm-provider-catalog.js +198 -0
  97. package/dist/electron/shared/types.js +88 -1
  98. package/package.json +12 -2
  99. package/src/electron/agent/executor.ts +205 -28
  100. package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
  101. package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
  102. package/src/electron/agent/llm/groq-provider.ts +39 -0
  103. package/src/electron/agent/llm/index.ts +5 -0
  104. package/src/electron/agent/llm/kimi-provider.ts +39 -0
  105. package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
  106. package/src/electron/agent/llm/openai-compatible.ts +133 -0
  107. package/src/electron/agent/llm/openai-oauth.ts +2 -1
  108. package/src/electron/agent/llm/openrouter-provider.ts +2 -1
  109. package/src/electron/agent/llm/provider-factory.ts +414 -6
  110. package/src/electron/agent/llm/types.ts +90 -1
  111. package/src/electron/agent/llm/xai-provider.ts +39 -0
  112. package/src/electron/agent/tools/box-tools.ts +239 -0
  113. package/src/electron/agent/tools/builtin-settings.ts +34 -0
  114. package/src/electron/agent/tools/dropbox-tools.ts +237 -0
  115. package/src/electron/agent/tools/google-drive-tools.ts +228 -0
  116. package/src/electron/agent/tools/notion-tools.ts +330 -0
  117. package/src/electron/agent/tools/onedrive-tools.ts +217 -0
  118. package/src/electron/agent/tools/registry.ts +565 -0
  119. package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
  120. package/src/electron/agent/tools/shell-tools.ts +11 -3
  121. package/src/electron/agent/tools/x-tools.ts +1 -1
  122. package/src/electron/database/SecureSettingsRepository.ts +7 -1
  123. package/src/electron/gateway/index.ts +1 -0
  124. package/src/electron/gateway/router.ts +134 -149
  125. package/src/electron/ipc/canvas-handlers.ts +10 -0
  126. package/src/electron/ipc/handlers.ts +673 -153
  127. package/src/electron/main.ts +35 -0
  128. package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
  129. package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
  130. package/src/electron/memory/MemoryService.ts +5 -1
  131. package/src/electron/preload.ts +167 -4
  132. package/src/electron/settings/box-manager.ts +58 -0
  133. package/src/electron/settings/dropbox-manager.ts +58 -0
  134. package/src/electron/settings/google-drive-manager.ts +58 -0
  135. package/src/electron/settings/notion-manager.ts +60 -0
  136. package/src/electron/settings/onedrive-manager.ts +58 -0
  137. package/src/electron/settings/sharepoint-manager.ts +58 -0
  138. package/src/electron/utils/box-api.ts +184 -0
  139. package/src/electron/utils/dropbox-api.ts +171 -0
  140. package/src/electron/utils/env-migration.ts +22 -0
  141. package/src/electron/utils/google-drive-api.ts +183 -0
  142. package/src/electron/utils/notion-api.ts +126 -0
  143. package/src/electron/utils/onedrive-api.ts +137 -0
  144. package/src/electron/utils/sharepoint-api.ts +132 -0
  145. package/src/electron/utils/validation.ts +102 -1
  146. package/src/electron/utils/x-cli.ts +1 -1
  147. package/src/renderer/App.tsx +20 -2
  148. package/src/renderer/components/BoxSettings.tsx +203 -0
  149. package/src/renderer/components/BrowserView.tsx +101 -0
  150. package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
  151. package/src/renderer/components/CanvasPreview.tsx +68 -1
  152. package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
  153. package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
  154. package/src/renderer/components/ConnectorsSettings.tsx +397 -0
  155. package/src/renderer/components/DropboxSettings.tsx +202 -0
  156. package/src/renderer/components/GoogleDriveSettings.tsx +201 -0
  157. package/src/renderer/components/MCPSettings.tsx +56 -0
  158. package/src/renderer/components/MainContent.tsx +270 -34
  159. package/src/renderer/components/NotionSettings.tsx +231 -0
  160. package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
  161. package/src/renderer/components/OnboardingModal.tsx +70 -1
  162. package/src/renderer/components/OneDriveSettings.tsx +212 -0
  163. package/src/renderer/components/Settings.tsx +611 -8
  164. package/src/renderer/components/SharePointSettings.tsx +224 -0
  165. package/src/renderer/components/Sidebar.tsx +25 -9
  166. package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
  167. package/src/renderer/styles/index.css +438 -25
  168. package/src/shared/channelMessages.ts +367 -4
  169. package/src/shared/llm-provider-catalog.ts +217 -0
  170. package/src/shared/types.ts +226 -1
@@ -0,0 +1,397 @@
1
+ import { useEffect, useMemo, useState } from 'react';
2
+ import { ConnectorSetupModal, ConnectorProvider } from './ConnectorSetupModal';
3
+ import { ConnectorEnvModal, ConnectorEnvField } from './ConnectorEnvModal';
4
+
5
+ // Types (matching preload types)
6
+ type MCPConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error';
7
+
8
+ type MCPServerConfig = {
9
+ id: string;
10
+ name: string;
11
+ description?: string;
12
+ enabled: boolean;
13
+ command?: string;
14
+ args?: string[];
15
+ env?: Record<string, string>;
16
+ };
17
+
18
+ type MCPServerStatus = {
19
+ id: string;
20
+ name: string;
21
+ status: MCPConnectionStatus;
22
+ error?: string;
23
+ tools: Array<{ name: string }>;
24
+ };
25
+
26
+ type MCPSettingsData = {
27
+ servers: MCPServerConfig[];
28
+ };
29
+
30
+ interface ConnectorDefinition {
31
+ key: string;
32
+ name: string;
33
+ registryId: string;
34
+ description: string;
35
+ supportsOAuth: boolean;
36
+ provider?: ConnectorProvider;
37
+ envFields?: ConnectorEnvField[];
38
+ }
39
+
40
+ const CONNECTORS: ConnectorDefinition[] = [
41
+ {
42
+ key: 'salesforce',
43
+ name: 'Salesforce',
44
+ registryId: 'salesforce',
45
+ description: 'CRM (accounts, cases, opportunities).',
46
+ supportsOAuth: true,
47
+ provider: 'salesforce',
48
+ },
49
+ {
50
+ key: 'jira',
51
+ name: 'Jira',
52
+ registryId: 'jira',
53
+ description: 'Issue tracking for teams.',
54
+ supportsOAuth: true,
55
+ provider: 'jira',
56
+ },
57
+ {
58
+ key: 'hubspot',
59
+ name: 'HubSpot',
60
+ registryId: 'hubspot',
61
+ description: 'CRM objects for contacts, companies, deals.',
62
+ supportsOAuth: true,
63
+ provider: 'hubspot',
64
+ },
65
+ {
66
+ key: 'zendesk',
67
+ name: 'Zendesk',
68
+ registryId: 'zendesk',
69
+ description: 'Support tickets and customer operations.',
70
+ supportsOAuth: true,
71
+ provider: 'zendesk',
72
+ },
73
+ {
74
+ key: 'servicenow',
75
+ name: 'ServiceNow',
76
+ registryId: 'servicenow',
77
+ description: 'ITSM records and table APIs.',
78
+ supportsOAuth: false,
79
+ envFields: [
80
+ { key: 'SERVICENOW_INSTANCE_URL', label: 'Instance URL', placeholder: 'https://instance.service-now.com' },
81
+ { key: 'SERVICENOW_INSTANCE', label: 'Instance Subdomain', placeholder: 'dev12345' },
82
+ { key: 'SERVICENOW_USERNAME', label: 'Username' },
83
+ { key: 'SERVICENOW_PASSWORD', label: 'Password', type: 'password' },
84
+ { key: 'SERVICENOW_ACCESS_TOKEN', label: 'Access Token', type: 'password' },
85
+ ],
86
+ },
87
+ {
88
+ key: 'linear',
89
+ name: 'Linear',
90
+ registryId: 'linear',
91
+ description: 'Project and issue tracking (GraphQL).',
92
+ supportsOAuth: false,
93
+ envFields: [
94
+ { key: 'LINEAR_API_KEY', label: 'API Key', type: 'password' },
95
+ ],
96
+ },
97
+ {
98
+ key: 'asana',
99
+ name: 'Asana',
100
+ registryId: 'asana',
101
+ description: 'Work management tasks and projects.',
102
+ supportsOAuth: false,
103
+ envFields: [
104
+ { key: 'ASANA_ACCESS_TOKEN', label: 'Access Token', type: 'password' },
105
+ ],
106
+ },
107
+ {
108
+ key: 'okta',
109
+ name: 'Okta',
110
+ registryId: 'okta',
111
+ description: 'User and directory management.',
112
+ supportsOAuth: false,
113
+ envFields: [
114
+ { key: 'OKTA_BASE_URL', label: 'Okta Base URL', placeholder: 'https://your-org.okta.com' },
115
+ { key: 'OKTA_API_TOKEN', label: 'API Token', type: 'password' },
116
+ ],
117
+ },
118
+ ];
119
+
120
+ const getStatusColor = (status: MCPConnectionStatus): string => {
121
+ switch (status) {
122
+ case 'connected':
123
+ return 'var(--color-success)';
124
+ case 'connecting':
125
+ case 'reconnecting':
126
+ return 'var(--color-warning)';
127
+ case 'error':
128
+ return 'var(--color-error)';
129
+ default:
130
+ return 'var(--color-text-tertiary)';
131
+ }
132
+ };
133
+
134
+ const getStatusText = (status: MCPConnectionStatus): string => {
135
+ switch (status) {
136
+ case 'connected':
137
+ return 'Connected';
138
+ case 'connecting':
139
+ return 'Connecting';
140
+ case 'reconnecting':
141
+ return 'Reconnecting';
142
+ case 'error':
143
+ return 'Error';
144
+ default:
145
+ return 'Disconnected';
146
+ }
147
+ };
148
+
149
+ function matchConnector(config: MCPServerConfig, connector: ConnectorDefinition): boolean {
150
+ const nameMatch = config.name.toLowerCase().includes(connector.key);
151
+ const argsMatch = (config.args || []).some((arg) => arg.toLowerCase().includes(connector.key));
152
+ const commandMatch = (config.command || '').toLowerCase().includes(connector.key);
153
+ return nameMatch || argsMatch || commandMatch;
154
+ }
155
+
156
+ export function ConnectorsSettings() {
157
+ const [settings, setSettings] = useState<MCPSettingsData | null>(null);
158
+ const [serverStatuses, setServerStatuses] = useState<MCPServerStatus[]>([]);
159
+ const [loading, setLoading] = useState(true);
160
+ const [installingId, setInstallingId] = useState<string | null>(null);
161
+ const [connectingServer, setConnectingServer] = useState<string | null>(null);
162
+ const [connectionErrors, setConnectionErrors] = useState<Record<string, string>>({});
163
+
164
+ const [connectorSetup, setConnectorSetup] = useState<{
165
+ provider: ConnectorProvider;
166
+ serverId: string;
167
+ serverName: string;
168
+ env?: Record<string, string>;
169
+ } | null>(null);
170
+
171
+ const [envModal, setEnvModal] = useState<{
172
+ serverId: string;
173
+ serverName: string;
174
+ env?: Record<string, string>;
175
+ fields: ConnectorEnvField[];
176
+ } | null>(null);
177
+
178
+ useEffect(() => {
179
+ loadData();
180
+
181
+ const unsubscribe = window.electronAPI.onMCPStatusChange((statuses) => {
182
+ setServerStatuses(statuses);
183
+ });
184
+
185
+ return () => unsubscribe();
186
+ }, []);
187
+
188
+ const loadData = async () => {
189
+ try {
190
+ setLoading(true);
191
+ const [loadedSettings, statuses] = await Promise.all([
192
+ window.electronAPI.getMCPSettings(),
193
+ window.electronAPI.getMCPStatus(),
194
+ ]);
195
+ setSettings(loadedSettings);
196
+ setServerStatuses(statuses);
197
+ } catch (error) {
198
+ console.error('Failed to load connector settings:', error);
199
+ } finally {
200
+ setLoading(false);
201
+ }
202
+ };
203
+
204
+ const connectorRows = useMemo(() => {
205
+ if (!settings) return [];
206
+ return CONNECTORS.map((connector) => {
207
+ const config = settings.servers.find((server) => matchConnector(server, connector));
208
+ const status = config
209
+ ? serverStatuses.find((s) => s.id === config.id)
210
+ : undefined;
211
+ return { connector, config, status };
212
+ });
213
+ }, [settings, serverStatuses]);
214
+
215
+ const handleInstall = async (connector: ConnectorDefinition) => {
216
+ try {
217
+ setInstallingId(connector.registryId);
218
+ await window.electronAPI.installMCPServer(connector.registryId);
219
+ await loadData();
220
+ } catch (error: any) {
221
+ alert(`Failed to install ${connector.name}: ${error.message}`);
222
+ } finally {
223
+ setInstallingId(null);
224
+ }
225
+ };
226
+
227
+ const handleConnectServer = async (serverId: string) => {
228
+ try {
229
+ setConnectingServer(serverId);
230
+ setConnectionErrors(prev => {
231
+ const { [serverId]: _, ...rest } = prev;
232
+ return rest;
233
+ });
234
+ await window.electronAPI.connectMCPServer(serverId);
235
+ } catch (error: any) {
236
+ setConnectionErrors(prev => ({
237
+ ...prev,
238
+ [serverId]: error.message || 'Connection failed',
239
+ }));
240
+ } finally {
241
+ setConnectingServer(null);
242
+ }
243
+ };
244
+
245
+ const handleDisconnectServer = async (serverId: string) => {
246
+ try {
247
+ setConnectingServer(serverId);
248
+ setConnectionErrors(prev => {
249
+ const { [serverId]: _, ...rest } = prev;
250
+ return rest;
251
+ });
252
+ await window.electronAPI.disconnectMCPServer(serverId);
253
+ } catch (error: any) {
254
+ setConnectionErrors(prev => ({
255
+ ...prev,
256
+ [serverId]: error.message || 'Disconnect failed',
257
+ }));
258
+ } finally {
259
+ setConnectingServer(null);
260
+ }
261
+ };
262
+
263
+ if (loading) {
264
+ return <div className="settings-loading">Loading connector settings...</div>;
265
+ }
266
+
267
+ return (
268
+ <div className="settings-section">
269
+ <div className="settings-section-header">
270
+ <h3>Connectors</h3>
271
+ </div>
272
+ <p className="settings-description">
273
+ Connect enterprise systems to your assistant. Configure credentials and monitor status here.
274
+ </p>
275
+
276
+ <div className="mcp-server-list">
277
+ {connectorRows.map(({ connector, config, status }) => {
278
+ const isInstalled = Boolean(config);
279
+ const serverStatus = status?.status || 'disconnected';
280
+ const isConnecting = connectingServer === config?.id;
281
+ return (
282
+ <div key={connector.key} className="mcp-server-card">
283
+ <div className="mcp-server-header">
284
+ <div className="mcp-server-info">
285
+ <div className="mcp-server-name-row">
286
+ <span className="mcp-server-name">{connector.name}</span>
287
+ <span
288
+ className="mcp-server-status"
289
+ style={{ color: getStatusColor(serverStatus) }}
290
+ >
291
+ <span className="mcp-status-dot" style={{ backgroundColor: getStatusColor(serverStatus) }} />
292
+ {isInstalled ? getStatusText(serverStatus) : 'Not installed'}
293
+ </span>
294
+ </div>
295
+ <span className="mcp-server-command">{connector.description}</span>
296
+ </div>
297
+ </div>
298
+
299
+ {isInstalled && (status?.error || connectionErrors[config!.id]) && (
300
+ <div className="mcp-server-error">
301
+ <span className="mcp-error-icon">⚠</span>
302
+ {connectionErrors[config!.id] || status?.error}
303
+ </div>
304
+ )}
305
+
306
+ <div className="mcp-server-actions">
307
+ {!isInstalled ? (
308
+ <button
309
+ className="button-small button-primary"
310
+ onClick={() => handleInstall(connector)}
311
+ disabled={installingId === connector.registryId}
312
+ >
313
+ {installingId === connector.registryId ? 'Installing...' : 'Install'}
314
+ </button>
315
+ ) : (
316
+ <>
317
+ {serverStatus === 'connected' ? (
318
+ <button
319
+ className="button-small button-secondary"
320
+ onClick={() => handleDisconnectServer(config!.id)}
321
+ disabled={isConnecting}
322
+ >
323
+ {isConnecting ? 'Disconnecting...' : 'Disconnect'}
324
+ </button>
325
+ ) : (
326
+ <button
327
+ className="button-small button-primary"
328
+ onClick={() => handleConnectServer(config!.id)}
329
+ disabled={isConnecting}
330
+ >
331
+ {isConnecting ? 'Connecting...' : 'Connect'}
332
+ </button>
333
+ )}
334
+
335
+ {connector.supportsOAuth && connector.provider && (
336
+ <button
337
+ className="button-small button-primary"
338
+ onClick={() =>
339
+ setConnectorSetup({
340
+ provider: connector.provider!,
341
+ serverId: config!.id,
342
+ serverName: config!.name,
343
+ env: config!.env,
344
+ })
345
+ }
346
+ >
347
+ Setup
348
+ </button>
349
+ )}
350
+
351
+ {!connector.supportsOAuth && connector.envFields && (
352
+ <button
353
+ className="button-small button-secondary"
354
+ onClick={() =>
355
+ setEnvModal({
356
+ serverId: config!.id,
357
+ serverName: config!.name,
358
+ env: config!.env,
359
+ fields: connector.envFields!,
360
+ })
361
+ }
362
+ >
363
+ Configure
364
+ </button>
365
+ )}
366
+ </>
367
+ )}
368
+ </div>
369
+ </div>
370
+ );
371
+ })}
372
+ </div>
373
+
374
+ {connectorSetup && (
375
+ <ConnectorSetupModal
376
+ provider={connectorSetup.provider}
377
+ serverId={connectorSetup.serverId}
378
+ serverName={connectorSetup.serverName}
379
+ initialEnv={connectorSetup.env}
380
+ onClose={() => setConnectorSetup(null)}
381
+ onSaved={loadData}
382
+ />
383
+ )}
384
+
385
+ {envModal && (
386
+ <ConnectorEnvModal
387
+ serverId={envModal.serverId}
388
+ serverName={envModal.serverName}
389
+ initialEnv={envModal.env}
390
+ fields={envModal.fields}
391
+ onClose={() => setEnvModal(null)}
392
+ onSaved={loadData}
393
+ />
394
+ )}
395
+ </div>
396
+ );
397
+ }
@@ -0,0 +1,202 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { DropboxSettingsData } from '../../shared/types';
3
+
4
+ export function DropboxSettings() {
5
+ const [settings, setSettings] = useState<DropboxSettingsData | null>(null);
6
+ const [saving, setSaving] = useState(false);
7
+ const [testing, setTesting] = useState(false);
8
+ const [testResult, setTestResult] = useState<{ success: boolean; error?: string; name?: string; userId?: string; email?: string } | null>(null);
9
+ const [status, setStatus] = useState<{ configured: boolean; connected: boolean; name?: string; error?: string } | null>(null);
10
+ const [statusLoading, setStatusLoading] = useState(false);
11
+
12
+ useEffect(() => {
13
+ loadSettings();
14
+ refreshStatus();
15
+ }, []);
16
+
17
+ const loadSettings = async () => {
18
+ try {
19
+ const loaded = await window.electronAPI.getDropboxSettings();
20
+ setSettings(loaded);
21
+ } catch (error) {
22
+ console.error('Failed to load Dropbox settings:', error);
23
+ }
24
+ };
25
+
26
+ const updateSettings = (updates: Partial<DropboxSettingsData>) => {
27
+ if (!settings) return;
28
+ setSettings({ ...settings, ...updates });
29
+ };
30
+
31
+ const handleSave = async () => {
32
+ if (!settings) return;
33
+ setSaving(true);
34
+ setTestResult(null);
35
+ try {
36
+ const payload: DropboxSettingsData = { ...settings };
37
+ await window.electronAPI.saveDropboxSettings(payload);
38
+ setSettings(payload);
39
+ await refreshStatus();
40
+ } catch (error) {
41
+ console.error('Failed to save Dropbox settings:', error);
42
+ } finally {
43
+ setSaving(false);
44
+ }
45
+ };
46
+
47
+ const refreshStatus = async () => {
48
+ try {
49
+ setStatusLoading(true);
50
+ const result = await window.electronAPI.getDropboxStatus();
51
+ setStatus(result);
52
+ } catch (error) {
53
+ console.error('Failed to load Dropbox status:', error);
54
+ } finally {
55
+ setStatusLoading(false);
56
+ }
57
+ };
58
+
59
+ const handleTestConnection = async () => {
60
+ setTesting(true);
61
+ setTestResult(null);
62
+ try {
63
+ const result = await window.electronAPI.testDropboxConnection();
64
+ setTestResult(result);
65
+ await refreshStatus();
66
+ } catch (error: any) {
67
+ setTestResult({ success: false, error: error.message || 'Failed to test connection' });
68
+ } finally {
69
+ setTesting(false);
70
+ }
71
+ };
72
+
73
+ if (!settings) {
74
+ return <div className="settings-loading">Loading Dropbox settings...</div>;
75
+ }
76
+
77
+ const statusLabel = !status?.configured
78
+ ? 'Missing Token'
79
+ : status.connected
80
+ ? 'Connected'
81
+ : 'Configured';
82
+
83
+ const statusClass = !status?.configured
84
+ ? 'missing'
85
+ : status.connected
86
+ ? 'connected'
87
+ : 'configured';
88
+
89
+ return (
90
+ <div className="dropbox-settings">
91
+ <div className="settings-section">
92
+ <div className="settings-section-header">
93
+ <div className="settings-title-with-badge">
94
+ <h3>Connect Dropbox</h3>
95
+ {status && (
96
+ <span
97
+ className={`dropbox-status-badge ${statusClass}`}
98
+ title={!status.configured ? 'Access token not configured' : status.connected ? 'Connected to Dropbox' : 'Configured'}
99
+ >
100
+ {statusLabel}
101
+ </span>
102
+ )}
103
+ {statusLoading && !status && (
104
+ <span className="dropbox-status-badge configured">Checking…</span>
105
+ )}
106
+ </div>
107
+ <button className="btn-secondary btn-sm" onClick={refreshStatus} disabled={statusLoading}>
108
+ {statusLoading ? 'Checking...' : 'Refresh Status'}
109
+ </button>
110
+ </div>
111
+ <p className="settings-description">
112
+ Connect the agent to Dropbox using an access token, then use the built-in `dropbox_action`
113
+ tool to search and manage files.
114
+ </p>
115
+ {status?.error && (
116
+ <p className="settings-hint">Status check: {status.error}</p>
117
+ )}
118
+ <div className="settings-actions">
119
+ <button
120
+ className="btn-secondary btn-sm"
121
+ onClick={() => window.electronAPI.openExternal('https://www.dropbox.com/developers/apps')}
122
+ >
123
+ Open Dropbox App Console
124
+ </button>
125
+ </div>
126
+ </div>
127
+
128
+ <div className="settings-section">
129
+ <div className="settings-field">
130
+ <label>Enable Integration</label>
131
+ <label className="settings-toggle">
132
+ <input
133
+ type="checkbox"
134
+ checked={settings.enabled}
135
+ onChange={(e) => updateSettings({ enabled: e.target.checked })}
136
+ />
137
+ <span className="toggle-slider" />
138
+ </label>
139
+ </div>
140
+
141
+ <div className="settings-field">
142
+ <label>Access Token</label>
143
+ <input
144
+ type="password"
145
+ className="settings-input"
146
+ placeholder="Dropbox access token"
147
+ value={settings.accessToken || ''}
148
+ onChange={(e) => updateSettings({ accessToken: e.target.value || undefined })}
149
+ />
150
+ <p className="settings-hint">Use a token with files.content.read/write or full access scopes.</p>
151
+ </div>
152
+
153
+ <div className="settings-field">
154
+ <label>Timeout (ms)</label>
155
+ <input
156
+ type="number"
157
+ className="settings-input"
158
+ min={1000}
159
+ max={120000}
160
+ value={settings.timeoutMs ?? 20000}
161
+ onChange={(e) => updateSettings({ timeoutMs: Number(e.target.value) })}
162
+ />
163
+ </div>
164
+
165
+ <div className="settings-actions">
166
+ <button className="btn-secondary btn-sm" onClick={handleTestConnection} disabled={testing}>
167
+ {testing ? 'Testing...' : 'Test Connection'}
168
+ </button>
169
+ <button className="btn-primary btn-sm" onClick={handleSave} disabled={saving}>
170
+ {saving ? 'Saving...' : 'Save Settings'}
171
+ </button>
172
+ </div>
173
+
174
+ {testResult && (
175
+ <div className={`test-result ${testResult.success ? 'success' : 'error'}`}>
176
+ {testResult.success ? (
177
+ <span>Connected{testResult.name ? ` as ${testResult.name}` : ''}</span>
178
+ ) : (
179
+ <span>Connection failed: {testResult.error}</span>
180
+ )}
181
+ </div>
182
+ )}
183
+ </div>
184
+
185
+ <div className="settings-section">
186
+ <h4>Quick Usage</h4>
187
+ <pre className="settings-info-box">{`// List folder contents
188
+ dropbox_action({
189
+ action: "list_folder",
190
+ path: "/Projects"
191
+ });
192
+
193
+ // Upload a file
194
+ dropbox_action({
195
+ action: "upload_file",
196
+ file_path: "reports/summary.pdf",
197
+ path: "/Reports/summary.pdf"
198
+ });`}</pre>
199
+ </div>
200
+ </div>
201
+ );
202
+ }