cowork-os 0.3.21 → 0.3.25

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 (252) hide show
  1. package/README.md +372 -10
  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/custom-skill-loader.js +31 -1
  48. package/dist/electron/electron/agent/daemon.js +189 -13
  49. package/dist/electron/electron/agent/executor.js +895 -78
  50. package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
  51. package/dist/electron/electron/agent/llm/azure-openai-provider.js +328 -0
  52. package/dist/electron/electron/agent/llm/bedrock-provider.js +49 -9
  53. package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
  54. package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
  55. package/dist/electron/electron/agent/llm/index.js +13 -1
  56. package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
  57. package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
  58. package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
  59. package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
  60. package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
  61. package/dist/electron/electron/agent/llm/provider-factory.js +350 -4
  62. package/dist/electron/electron/agent/llm/types.js +66 -1
  63. package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
  64. package/dist/electron/electron/agent/search/provider-factory.js +38 -2
  65. package/dist/electron/electron/agent/tools/box-tools.js +231 -0
  66. package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
  67. package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
  68. package/dist/electron/electron/agent/tools/file-tools.js +66 -3
  69. package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
  70. package/dist/electron/electron/agent/tools/grep-tools.js +90 -10
  71. package/dist/electron/electron/agent/tools/image-tools.js +11 -1
  72. package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
  73. package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
  74. package/dist/electron/electron/agent/tools/registry.js +548 -10
  75. package/dist/electron/electron/agent/tools/search-tools.js +28 -10
  76. package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
  77. package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
  78. package/dist/electron/electron/agent/tools/x-tools.js +1 -1
  79. package/dist/electron/electron/agents/agent-dispatch.js +63 -0
  80. package/dist/electron/electron/database/repositories.js +19 -5
  81. package/dist/electron/electron/database/schema.js +8 -0
  82. package/dist/electron/electron/gateway/channels/whatsapp.js +55 -0
  83. package/dist/electron/electron/gateway/index.js +75 -1
  84. package/dist/electron/electron/gateway/router.js +209 -154
  85. package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
  86. package/dist/electron/electron/ipc/handlers.js +763 -267
  87. package/dist/electron/electron/main.js +63 -0
  88. package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
  89. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
  90. package/dist/electron/electron/memory/MemoryService.js +2 -1
  91. package/dist/electron/electron/preload.js +78 -1
  92. package/dist/electron/electron/settings/appearance-manager.js +18 -1
  93. package/dist/electron/electron/settings/box-manager.js +54 -0
  94. package/dist/electron/electron/settings/dropbox-manager.js +54 -0
  95. package/dist/electron/electron/settings/google-drive-manager.js +54 -0
  96. package/dist/electron/electron/settings/notion-manager.js +56 -0
  97. package/dist/electron/electron/settings/onedrive-manager.js +54 -0
  98. package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
  99. package/dist/electron/electron/utils/box-api.js +153 -0
  100. package/dist/electron/electron/utils/dropbox-api.js +144 -0
  101. package/dist/electron/electron/utils/env-migration.js +19 -0
  102. package/dist/electron/electron/utils/google-drive-api.js +152 -0
  103. package/dist/electron/electron/utils/notion-api.js +103 -0
  104. package/dist/electron/electron/utils/onedrive-api.js +113 -0
  105. package/dist/electron/electron/utils/sharepoint-api.js +109 -0
  106. package/dist/electron/electron/utils/validation.js +98 -3
  107. package/dist/electron/electron/utils/x-cli.js +1 -1
  108. package/dist/electron/shared/channelMessages.js +284 -3
  109. package/dist/electron/shared/llm-provider-catalog.js +198 -0
  110. package/dist/electron/shared/types.js +90 -1
  111. package/package.json +14 -3
  112. package/resources/skills/nano-banana-pro.json +4 -4
  113. package/resources/skills/openai-image-gen.json +3 -3
  114. package/resources/skills/scripts/gen.py +163 -0
  115. package/resources/skills/scripts/generate_image.py +91 -0
  116. package/src/electron/agent/custom-skill-loader.ts +34 -1
  117. package/src/electron/agent/daemon.ts +210 -14
  118. package/src/electron/agent/executor.ts +1124 -85
  119. package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
  120. package/src/electron/agent/llm/azure-openai-provider.ts +388 -0
  121. package/src/electron/agent/llm/bedrock-provider.ts +62 -9
  122. package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
  123. package/src/electron/agent/llm/groq-provider.ts +39 -0
  124. package/src/electron/agent/llm/index.ts +6 -0
  125. package/src/electron/agent/llm/kimi-provider.ts +39 -0
  126. package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
  127. package/src/electron/agent/llm/openai-compatible.ts +133 -0
  128. package/src/electron/agent/llm/openai-oauth.ts +2 -1
  129. package/src/electron/agent/llm/openrouter-provider.ts +2 -1
  130. package/src/electron/agent/llm/provider-factory.ts +459 -6
  131. package/src/electron/agent/llm/types.ts +95 -1
  132. package/src/electron/agent/llm/xai-provider.ts +39 -0
  133. package/src/electron/agent/search/provider-factory.ts +43 -2
  134. package/src/electron/agent/tools/box-tools.ts +239 -0
  135. package/src/electron/agent/tools/builtin-settings.ts +36 -0
  136. package/src/electron/agent/tools/dropbox-tools.ts +237 -0
  137. package/src/electron/agent/tools/file-tools.ts +66 -3
  138. package/src/electron/agent/tools/gmail-tools.ts +240 -0
  139. package/src/electron/agent/tools/google-calendar-tools.ts +258 -0
  140. package/src/electron/agent/tools/google-drive-tools.ts +228 -0
  141. package/src/electron/agent/tools/grep-tools.ts +97 -12
  142. package/src/electron/agent/tools/image-tools.ts +11 -1
  143. package/src/electron/agent/tools/notion-tools.ts +330 -0
  144. package/src/electron/agent/tools/onedrive-tools.ts +217 -0
  145. package/src/electron/agent/tools/registry.ts +794 -10
  146. package/src/electron/agent/tools/search-tools.ts +29 -11
  147. package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
  148. package/src/electron/agent/tools/shell-tools.ts +11 -3
  149. package/src/electron/agent/tools/x-tools.ts +1 -1
  150. package/src/electron/agents/agent-dispatch.ts +79 -0
  151. package/src/electron/database/SecureSettingsRepository.ts +7 -1
  152. package/src/electron/database/repositories.ts +58 -6
  153. package/src/electron/database/schema.ts +8 -0
  154. package/src/electron/gateway/channels/discord.ts +4 -0
  155. package/src/electron/gateway/channels/google-chat.ts +3 -0
  156. package/src/electron/gateway/channels/line.ts +3 -0
  157. package/src/electron/gateway/channels/matrix-client.ts +15 -0
  158. package/src/electron/gateway/channels/matrix.ts +31 -0
  159. package/src/electron/gateway/channels/mattermost.ts +3 -0
  160. package/src/electron/gateway/channels/signal.ts +3 -0
  161. package/src/electron/gateway/channels/slack.ts +9 -4
  162. package/src/electron/gateway/channels/teams.ts +4 -0
  163. package/src/electron/gateway/channels/telegram.ts +2 -0
  164. package/src/electron/gateway/channels/twitch.ts +2 -0
  165. package/src/electron/gateway/channels/types.ts +8 -0
  166. package/src/electron/gateway/channels/whatsapp.ts +66 -0
  167. package/src/electron/gateway/index.ts +95 -2
  168. package/src/electron/gateway/router.ts +231 -161
  169. package/src/electron/gateway/security.ts +21 -9
  170. package/src/electron/ipc/canvas-handlers.ts +10 -0
  171. package/src/electron/ipc/handlers.ts +848 -292
  172. package/src/electron/main.ts +35 -0
  173. package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
  174. package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
  175. package/src/electron/memory/MemoryService.ts +7 -1
  176. package/src/electron/preload.ts +200 -5
  177. package/src/electron/settings/appearance-manager.ts +20 -2
  178. package/src/electron/settings/box-manager.ts +58 -0
  179. package/src/electron/settings/dropbox-manager.ts +58 -0
  180. package/src/electron/settings/google-workspace-manager.ts +59 -0
  181. package/src/electron/settings/notion-manager.ts +60 -0
  182. package/src/electron/settings/onedrive-manager.ts +58 -0
  183. package/src/electron/settings/sharepoint-manager.ts +58 -0
  184. package/src/electron/utils/box-api.ts +184 -0
  185. package/src/electron/utils/dropbox-api.ts +171 -0
  186. package/src/electron/utils/env-migration.ts +22 -0
  187. package/src/electron/utils/gmail-api.ts +121 -0
  188. package/src/electron/utils/google-calendar-api.ts +115 -0
  189. package/src/electron/utils/google-workspace-api.ts +228 -0
  190. package/src/electron/utils/google-workspace-auth.ts +109 -0
  191. package/src/electron/utils/google-workspace-oauth.ts +232 -0
  192. package/src/electron/utils/notion-api.ts +126 -0
  193. package/src/electron/utils/onedrive-api.ts +137 -0
  194. package/src/electron/utils/sharepoint-api.ts +132 -0
  195. package/src/electron/utils/validation.ts +128 -1
  196. package/src/electron/utils/x-cli.ts +1 -1
  197. package/src/renderer/App.tsx +119 -8
  198. package/src/renderer/components/ActivityFeedItem.tsx +34 -17
  199. package/src/renderer/components/AgentWorkingStatePanel.tsx +7 -5
  200. package/src/renderer/components/AppearanceSettings.tsx +37 -2
  201. package/src/renderer/components/BlueBubblesSettings.tsx +18 -7
  202. package/src/renderer/components/BoxSettings.tsx +203 -0
  203. package/src/renderer/components/BrowserView.tsx +101 -0
  204. package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
  205. package/src/renderer/components/CanvasPreview.tsx +68 -1
  206. package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
  207. package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
  208. package/src/renderer/components/ConnectorsSettings.tsx +397 -0
  209. package/src/renderer/components/ControlPlaneSettings.tsx +2 -0
  210. package/src/renderer/components/DiscordSettings.tsx +18 -7
  211. package/src/renderer/components/DropboxSettings.tsx +202 -0
  212. package/src/renderer/components/EmailSettings.tsx +18 -7
  213. package/src/renderer/components/FileViewer.tsx +21 -13
  214. package/src/renderer/components/GoogleChatSettings.tsx +17 -7
  215. package/src/renderer/components/GoogleWorkspaceSettings.tsx +332 -0
  216. package/src/renderer/components/ImessageSettings.tsx +22 -11
  217. package/src/renderer/components/LineIcons.tsx +376 -0
  218. package/src/renderer/components/LineSettings.tsx +18 -7
  219. package/src/renderer/components/MCPSettings.tsx +56 -0
  220. package/src/renderer/components/MainContent.tsx +740 -76
  221. package/src/renderer/components/MatrixSettings.tsx +18 -7
  222. package/src/renderer/components/MattermostSettings.tsx +18 -7
  223. package/src/renderer/components/NodesSettings.tsx +58 -99
  224. package/src/renderer/components/NotificationPanel.tsx +25 -11
  225. package/src/renderer/components/NotionSettings.tsx +231 -0
  226. package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
  227. package/src/renderer/components/OnboardingModal.tsx +70 -1
  228. package/src/renderer/components/OneDriveSettings.tsx +212 -0
  229. package/src/renderer/components/RightPanel.tsx +141 -28
  230. package/src/renderer/components/ScheduledTasksSettings.tsx +10 -62
  231. package/src/renderer/components/SearchSettings.tsx +118 -114
  232. package/src/renderer/components/Settings.tsx +1425 -651
  233. package/src/renderer/components/SharePointSettings.tsx +224 -0
  234. package/src/renderer/components/Sidebar.tsx +94 -19
  235. package/src/renderer/components/SignalSettings.tsx +18 -7
  236. package/src/renderer/components/SkillHubBrowser.tsx +144 -185
  237. package/src/renderer/components/SlackSettings.tsx +18 -7
  238. package/src/renderer/components/TaskQuickActions.tsx +11 -6
  239. package/src/renderer/components/TaskTimeline.tsx +58 -26
  240. package/src/renderer/components/TeamsSettings.tsx +18 -7
  241. package/src/renderer/components/TelegramSettings.tsx +18 -7
  242. package/src/renderer/components/ThemeIcon.tsx +16 -0
  243. package/src/renderer/components/TwitchSettings.tsx +18 -7
  244. package/src/renderer/components/VoiceSettings.tsx +30 -74
  245. package/src/renderer/components/WhatsAppSettings.tsx +48 -37
  246. package/src/renderer/components/WorkingStateHistory.tsx +7 -5
  247. package/src/renderer/components/WorkspaceSelector.tsx +42 -13
  248. package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
  249. package/src/renderer/styles/index.css +2333 -209
  250. package/src/shared/channelMessages.ts +367 -4
  251. package/src/shared/llm-provider-catalog.ts +217 -0
  252. package/src/shared/types.ts +251 -2
@@ -1,6 +1,8 @@
1
1
  import path from 'path';
2
+ import * as fs from 'fs/promises';
2
3
  import { pathToFileURL } from 'url';
3
4
  import { app, BrowserWindow, ipcMain, dialog, session, shell, Notification } from 'electron';
5
+ import mime from 'mime-types';
4
6
  import { DatabaseManager } from './database/schema';
5
7
  import { SecureSettingsRepository } from './database/SecureSettingsRepository';
6
8
  import { setupIpcHandlers, getNotificationService } from './ipc/handlers';
@@ -494,3 +496,36 @@ ipcMain.handle('dialog:selectFolder', async () => {
494
496
  }
495
497
  return null;
496
498
  });
499
+
500
+ // Handle file selection (attachments)
501
+ ipcMain.handle('dialog:selectFiles', async () => {
502
+ const result = await dialog.showOpenDialog({
503
+ properties: ['openFile', 'multiSelections'],
504
+ title: 'Select Files to Upload',
505
+ });
506
+
507
+ if (result.canceled || result.filePaths.length === 0) {
508
+ return [];
509
+ }
510
+
511
+ const entries = await Promise.all(
512
+ result.filePaths.map(async (filePath) => {
513
+ try {
514
+ const stats = await fs.stat(filePath);
515
+ if (!stats.isFile()) {
516
+ return null;
517
+ }
518
+ return {
519
+ path: filePath,
520
+ name: path.basename(filePath),
521
+ size: stats.size,
522
+ mimeType: (mime.lookup(filePath) || undefined) as string | undefined,
523
+ };
524
+ } catch {
525
+ return null;
526
+ }
527
+ })
528
+ );
529
+
530
+ return entries.filter((entry): entry is { path: string; name: string; size: number; mimeType: string | undefined } => Boolean(entry));
531
+ });
@@ -0,0 +1,448 @@
1
+ import { shell } from 'electron';
2
+ import http from 'http';
3
+ import { randomBytes, createHash } from 'crypto';
4
+ import { URL } from 'url';
5
+
6
+ export type ConnectorOAuthProvider = 'salesforce' | 'jira' | 'hubspot' | 'zendesk';
7
+
8
+ export interface ConnectorOAuthRequest {
9
+ provider: ConnectorOAuthProvider;
10
+ clientId: string;
11
+ clientSecret?: string;
12
+ scopes?: string[];
13
+ loginUrl?: string; // Salesforce only
14
+ subdomain?: string; // Zendesk only
15
+ }
16
+
17
+ export interface JiraResource {
18
+ id: string;
19
+ name: string;
20
+ url: string;
21
+ scopes?: string[];
22
+ }
23
+
24
+ export interface ConnectorOAuthResult {
25
+ provider: ConnectorOAuthProvider;
26
+ accessToken: string;
27
+ refreshToken?: string;
28
+ expiresIn?: number;
29
+ tokenType?: string;
30
+ instanceUrl?: string; // Salesforce
31
+ resources?: JiraResource[]; // Jira
32
+ }
33
+
34
+ const DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
35
+ const OAUTH_CALLBACK_PORT = 18765;
36
+
37
+ export async function startConnectorOAuth(request: ConnectorOAuthRequest): Promise<ConnectorOAuthResult> {
38
+ switch (request.provider) {
39
+ case 'salesforce':
40
+ return startSalesforceOAuth(request);
41
+ case 'jira':
42
+ return startJiraOAuth(request);
43
+ case 'hubspot':
44
+ return startHubSpotOAuth(request);
45
+ case 'zendesk':
46
+ return startZendeskOAuth(request);
47
+ default:
48
+ throw new Error(`Unsupported OAuth provider: ${request.provider}`);
49
+ }
50
+ }
51
+
52
+ function createCodeVerifier(): string {
53
+ return base64Url(randomBytes(32));
54
+ }
55
+
56
+ function createCodeChallenge(verifier: string): string {
57
+ const hash = createHash('sha256').update(verifier).digest();
58
+ return base64Url(hash);
59
+ }
60
+
61
+ function base64Url(buffer: Buffer): string {
62
+ return buffer
63
+ .toString('base64')
64
+ .replace(/\+/g, '-')
65
+ .replace(/\//g, '_')
66
+ .replace(/=+$/, '');
67
+ }
68
+
69
+ async function startOAuthCallbackServer(timeoutMs = DEFAULT_TIMEOUT_MS): Promise<{
70
+ redirectUri: string;
71
+ state: string;
72
+ waitForCode: () => Promise<{ code: string; state: string }>;
73
+ }> {
74
+ const state = base64Url(randomBytes(16));
75
+
76
+ return new Promise((resolve, reject) => {
77
+ const server = http.createServer();
78
+
79
+ let resolveCode: (value: { code: string; state: string }) => void = () => {};
80
+ let rejectCode: (error: Error) => void = () => {};
81
+
82
+ const codePromise = new Promise<{ code: string; state: string }>((innerResolve, innerReject) => {
83
+ resolveCode = innerResolve;
84
+ rejectCode = innerReject;
85
+ });
86
+
87
+ const timeout = setTimeout(() => {
88
+ server.close();
89
+ rejectCode(new Error('OAuth timed out. Please try again.'));
90
+ }, timeoutMs);
91
+
92
+ server.on('request', (req, res) => {
93
+ if (!req.url) {
94
+ res.writeHead(400);
95
+ res.end('Invalid request');
96
+ return;
97
+ }
98
+
99
+ const url = new URL(req.url, 'http://127.0.0.1');
100
+ if (url.pathname !== '/oauth/callback') {
101
+ res.writeHead(404);
102
+ res.end('Not found');
103
+ return;
104
+ }
105
+
106
+ const code = url.searchParams.get('code');
107
+ const returnedState = url.searchParams.get('state');
108
+ const error = url.searchParams.get('error');
109
+ const errorDescription = url.searchParams.get('error_description');
110
+
111
+ res.writeHead(200, { 'Content-Type': 'text/html' });
112
+ res.end(`<!DOCTYPE html><html><body style="font-family: sans-serif; padding: 24px;">
113
+ <h2>Authorization complete</h2>
114
+ <p>You can close this window and return to CoWork OS.</p>
115
+ </body></html>`);
116
+
117
+ clearTimeout(timeout);
118
+ server.close();
119
+
120
+ if (error) {
121
+ rejectCode(new Error(errorDescription || error));
122
+ return;
123
+ }
124
+
125
+ if (!code || !returnedState) {
126
+ rejectCode(new Error('Missing OAuth code or state'));
127
+ return;
128
+ }
129
+
130
+ if (returnedState !== state) {
131
+ rejectCode(new Error('OAuth state mismatch'));
132
+ return;
133
+ }
134
+
135
+ resolveCode({ code, state: returnedState });
136
+ });
137
+
138
+ server.on('error', (error: NodeJS.ErrnoException) => {
139
+ clearTimeout(timeout);
140
+ const portMessage = error.code === 'EADDRINUSE'
141
+ ? `Port ${OAUTH_CALLBACK_PORT} is already in use. Close the conflicting app and try again.`
142
+ : error.message;
143
+ reject(new Error(`OAuth callback server failed: ${portMessage}`));
144
+ });
145
+
146
+ server.listen(OAUTH_CALLBACK_PORT, '127.0.0.1', () => {
147
+ const address = server.address();
148
+ if (!address || typeof address === 'string') {
149
+ clearTimeout(timeout);
150
+ server.close();
151
+ reject(new Error('Failed to start OAuth callback server'));
152
+ return;
153
+ }
154
+
155
+ const redirectUri = `http://127.0.0.1:${address.port}/oauth/callback`;
156
+ resolve({
157
+ redirectUri,
158
+ state,
159
+ waitForCode: () => codePromise,
160
+ });
161
+ });
162
+ });
163
+ }
164
+
165
+ async function startSalesforceOAuth(request: ConnectorOAuthRequest): Promise<ConnectorOAuthResult> {
166
+ const loginUrl = request.loginUrl || 'https://login.salesforce.com';
167
+
168
+ if (!request.clientId) {
169
+ throw new Error('Salesforce OAuth requires a client ID');
170
+ }
171
+ if (!request.clientSecret) {
172
+ throw new Error('Salesforce OAuth requires a client secret');
173
+ }
174
+
175
+ const scope = (request.scopes && request.scopes.length > 0)
176
+ ? request.scopes.join(' ')
177
+ : 'api refresh_token';
178
+
179
+ const { redirectUri, waitForCode, state } = await startOAuthCallbackServer();
180
+
181
+ const authUrl = new URL(`${loginUrl.replace(/\/$/, '')}/services/oauth2/authorize`);
182
+ authUrl.searchParams.set('response_type', 'code');
183
+ authUrl.searchParams.set('client_id', request.clientId);
184
+ authUrl.searchParams.set('redirect_uri', redirectUri);
185
+ authUrl.searchParams.set('scope', scope);
186
+ authUrl.searchParams.set('state', state);
187
+
188
+ await shell.openExternal(authUrl.toString());
189
+
190
+ const { code } = await waitForCode();
191
+
192
+ const tokenUrl = `${loginUrl.replace(/\/$/, '')}/services/oauth2/token`;
193
+ const params = new URLSearchParams({
194
+ grant_type: 'authorization_code',
195
+ code,
196
+ client_id: request.clientId,
197
+ client_secret: request.clientSecret,
198
+ redirect_uri: redirectUri,
199
+ });
200
+
201
+ const tokenResponse = await fetch(tokenUrl, {
202
+ method: 'POST',
203
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
204
+ body: params.toString(),
205
+ });
206
+
207
+ if (!tokenResponse.ok) {
208
+ const text = await tokenResponse.text();
209
+ throw new Error(`Salesforce OAuth failed: ${text}`);
210
+ }
211
+
212
+ const tokenData = await tokenResponse.json() as {
213
+ access_token?: string;
214
+ refresh_token?: string;
215
+ instance_url?: string;
216
+ token_type?: string;
217
+ };
218
+ if (!tokenData.access_token) {
219
+ throw new Error('Salesforce OAuth did not return an access_token');
220
+ }
221
+
222
+ return {
223
+ provider: 'salesforce',
224
+ accessToken: tokenData.access_token,
225
+ refreshToken: tokenData.refresh_token,
226
+ instanceUrl: tokenData.instance_url,
227
+ tokenType: tokenData.token_type,
228
+ };
229
+ }
230
+
231
+ async function startJiraOAuth(request: ConnectorOAuthRequest): Promise<ConnectorOAuthResult> {
232
+ if (!request.clientId) {
233
+ throw new Error('Jira OAuth requires a client ID');
234
+ }
235
+ if (!request.clientSecret) {
236
+ throw new Error('Jira OAuth requires a client secret');
237
+ }
238
+
239
+ const scope = (request.scopes && request.scopes.length > 0)
240
+ ? request.scopes.join(' ')
241
+ : 'read:jira-user read:jira-work write:jira-work offline_access';
242
+
243
+ const { redirectUri, waitForCode, state } = await startOAuthCallbackServer();
244
+
245
+ const codeVerifier = createCodeVerifier();
246
+ const codeChallenge = createCodeChallenge(codeVerifier);
247
+
248
+ const authUrl = new URL('https://auth.atlassian.com/authorize');
249
+ authUrl.searchParams.set('audience', 'api.atlassian.com');
250
+ authUrl.searchParams.set('client_id', request.clientId);
251
+ authUrl.searchParams.set('scope', scope);
252
+ authUrl.searchParams.set('redirect_uri', redirectUri);
253
+ authUrl.searchParams.set('response_type', 'code');
254
+ authUrl.searchParams.set('prompt', 'consent');
255
+ authUrl.searchParams.set('state', state);
256
+ authUrl.searchParams.set('code_challenge', codeChallenge);
257
+ authUrl.searchParams.set('code_challenge_method', 'S256');
258
+
259
+ await shell.openExternal(authUrl.toString());
260
+
261
+ const { code } = await waitForCode();
262
+
263
+ const tokenResponse = await fetch('https://auth.atlassian.com/oauth/token', {
264
+ method: 'POST',
265
+ headers: { 'Content-Type': 'application/json' },
266
+ body: JSON.stringify({
267
+ grant_type: 'authorization_code',
268
+ client_id: request.clientId,
269
+ client_secret: request.clientSecret,
270
+ code,
271
+ redirect_uri: redirectUri,
272
+ code_verifier: codeVerifier,
273
+ }),
274
+ });
275
+
276
+ if (!tokenResponse.ok) {
277
+ const text = await tokenResponse.text();
278
+ throw new Error(`Jira OAuth failed: ${text}`);
279
+ }
280
+
281
+ const tokenData = await tokenResponse.json() as {
282
+ access_token?: string;
283
+ refresh_token?: string;
284
+ expires_in?: number;
285
+ token_type?: string;
286
+ };
287
+ if (!tokenData.access_token) {
288
+ throw new Error('Jira OAuth did not return an access_token');
289
+ }
290
+
291
+ const resourcesResponse = await fetch('https://api.atlassian.com/oauth/token/accessible-resources', {
292
+ headers: {
293
+ Authorization: `Bearer ${tokenData.access_token}`,
294
+ Accept: 'application/json',
295
+ },
296
+ });
297
+
298
+ if (!resourcesResponse.ok) {
299
+ const text = await resourcesResponse.text();
300
+ throw new Error(`Jira OAuth resources fetch failed: ${text}`);
301
+ }
302
+
303
+ const resourcesData = await resourcesResponse.json();
304
+ const resources: JiraResource[] = Array.isArray(resourcesData)
305
+ ? resourcesData.map((resource) => ({
306
+ id: resource.id,
307
+ name: resource.name,
308
+ url: resource.url,
309
+ scopes: resource.scopes,
310
+ }))
311
+ : [];
312
+
313
+ return {
314
+ provider: 'jira',
315
+ accessToken: tokenData.access_token,
316
+ refreshToken: tokenData.refresh_token,
317
+ expiresIn: tokenData.expires_in,
318
+ tokenType: tokenData.token_type,
319
+ resources,
320
+ };
321
+ }
322
+
323
+ async function startHubSpotOAuth(request: ConnectorOAuthRequest): Promise<ConnectorOAuthResult> {
324
+ if (!request.clientId) {
325
+ throw new Error('HubSpot OAuth requires a client ID');
326
+ }
327
+ if (!request.clientSecret) {
328
+ throw new Error('HubSpot OAuth requires a client secret');
329
+ }
330
+
331
+ const scope = (request.scopes && request.scopes.length > 0)
332
+ ? request.scopes.join(' ')
333
+ : 'crm.objects.contacts.read crm.objects.contacts.write crm.objects.companies.read crm.objects.companies.write crm.objects.deals.read crm.objects.deals.write';
334
+
335
+ const { redirectUri, waitForCode, state } = await startOAuthCallbackServer();
336
+
337
+ const authUrl = new URL('https://app.hubspot.com/oauth/authorize');
338
+ authUrl.searchParams.set('client_id', request.clientId);
339
+ authUrl.searchParams.set('redirect_uri', redirectUri);
340
+ authUrl.searchParams.set('scope', scope);
341
+ authUrl.searchParams.set('state', state);
342
+
343
+ await shell.openExternal(authUrl.toString());
344
+
345
+ const { code } = await waitForCode();
346
+
347
+ const params = new URLSearchParams({
348
+ grant_type: 'authorization_code',
349
+ client_id: request.clientId,
350
+ client_secret: request.clientSecret,
351
+ redirect_uri: redirectUri,
352
+ code,
353
+ });
354
+
355
+ const tokenResponse = await fetch('https://api.hubapi.com/oauth/v1/token', {
356
+ method: 'POST',
357
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
358
+ body: params.toString(),
359
+ });
360
+
361
+ if (!tokenResponse.ok) {
362
+ const text = await tokenResponse.text();
363
+ throw new Error(`HubSpot OAuth failed: ${text}`);
364
+ }
365
+
366
+ const tokenData = await tokenResponse.json() as {
367
+ access_token?: string;
368
+ refresh_token?: string;
369
+ expires_in?: number;
370
+ token_type?: string;
371
+ };
372
+ if (!tokenData.access_token) {
373
+ throw new Error('HubSpot OAuth did not return an access_token');
374
+ }
375
+
376
+ return {
377
+ provider: 'hubspot',
378
+ accessToken: tokenData.access_token,
379
+ refreshToken: tokenData.refresh_token,
380
+ expiresIn: tokenData.expires_in,
381
+ tokenType: tokenData.token_type,
382
+ };
383
+ }
384
+
385
+ async function startZendeskOAuth(request: ConnectorOAuthRequest): Promise<ConnectorOAuthResult> {
386
+ if (!request.clientId) {
387
+ throw new Error('Zendesk OAuth requires a client ID');
388
+ }
389
+ if (!request.clientSecret) {
390
+ throw new Error('Zendesk OAuth requires a client secret');
391
+ }
392
+ if (!request.subdomain) {
393
+ throw new Error('Zendesk OAuth requires a subdomain');
394
+ }
395
+
396
+ const scope = (request.scopes && request.scopes.length > 0)
397
+ ? request.scopes.join(' ')
398
+ : 'read write';
399
+
400
+ const baseUrl = `https://${request.subdomain}.zendesk.com`;
401
+ const { redirectUri, waitForCode, state } = await startOAuthCallbackServer();
402
+
403
+ const authUrl = new URL(`${baseUrl}/oauth/authorizations/new`);
404
+ authUrl.searchParams.set('response_type', 'code');
405
+ authUrl.searchParams.set('client_id', request.clientId);
406
+ authUrl.searchParams.set('redirect_uri', redirectUri);
407
+ authUrl.searchParams.set('scope', scope);
408
+ authUrl.searchParams.set('state', state);
409
+
410
+ await shell.openExternal(authUrl.toString());
411
+
412
+ const { code } = await waitForCode();
413
+
414
+ const tokenResponse = await fetch(`${baseUrl}/oauth/tokens`, {
415
+ method: 'POST',
416
+ headers: { 'Content-Type': 'application/json' },
417
+ body: JSON.stringify({
418
+ grant_type: 'authorization_code',
419
+ code,
420
+ client_id: request.clientId,
421
+ client_secret: request.clientSecret,
422
+ redirect_uri: redirectUri,
423
+ }),
424
+ });
425
+
426
+ if (!tokenResponse.ok) {
427
+ const text = await tokenResponse.text();
428
+ throw new Error(`Zendesk OAuth failed: ${text}`);
429
+ }
430
+
431
+ const tokenData = await tokenResponse.json() as {
432
+ access_token?: string;
433
+ refresh_token?: string;
434
+ token_type?: string;
435
+ expires_in?: number;
436
+ };
437
+ if (!tokenData.access_token) {
438
+ throw new Error('Zendesk OAuth did not return an access_token');
439
+ }
440
+
441
+ return {
442
+ provider: 'zendesk',
443
+ accessToken: tokenData.access_token,
444
+ refreshToken: tokenData.refresh_token,
445
+ tokenType: tokenData.token_type,
446
+ expiresIn: tokenData.expires_in,
447
+ };
448
+ }