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.
- package/README.md +372 -10
- package/connectors/README.md +20 -0
- package/connectors/asana-mcp/README.md +24 -0
- package/connectors/asana-mcp/dist/index.js +427 -0
- package/connectors/asana-mcp/package.json +15 -0
- package/connectors/asana-mcp/src/index.ts +553 -0
- package/connectors/asana-mcp/tsconfig.json +13 -0
- package/connectors/hubspot-mcp/README.md +35 -0
- package/connectors/hubspot-mcp/dist/index.js +454 -0
- package/connectors/hubspot-mcp/package.json +15 -0
- package/connectors/hubspot-mcp/src/index.ts +562 -0
- package/connectors/hubspot-mcp/tsconfig.json +13 -0
- package/connectors/jira-mcp/README.md +49 -0
- package/connectors/jira-mcp/dist/index.js +588 -0
- package/connectors/jira-mcp/package.json +15 -0
- package/connectors/jira-mcp/src/index.ts +711 -0
- package/connectors/jira-mcp/tsconfig.json +13 -0
- package/connectors/linear-mcp/README.md +22 -0
- package/connectors/linear-mcp/dist/index.js +402 -0
- package/connectors/linear-mcp/package.json +15 -0
- package/connectors/linear-mcp/src/index.ts +522 -0
- package/connectors/linear-mcp/tsconfig.json +13 -0
- package/connectors/okta-mcp/README.md +24 -0
- package/connectors/okta-mcp/dist/index.js +411 -0
- package/connectors/okta-mcp/package.json +15 -0
- package/connectors/okta-mcp/src/index.ts +520 -0
- package/connectors/okta-mcp/tsconfig.json +13 -0
- package/connectors/salesforce-mcp/README.md +47 -0
- package/connectors/salesforce-mcp/dist/index.js +584 -0
- package/connectors/salesforce-mcp/package.json +15 -0
- package/connectors/salesforce-mcp/src/index.ts +722 -0
- package/connectors/salesforce-mcp/tsconfig.json +13 -0
- package/connectors/servicenow-mcp/README.md +26 -0
- package/connectors/servicenow-mcp/dist/index.js +400 -0
- package/connectors/servicenow-mcp/package.json +15 -0
- package/connectors/servicenow-mcp/src/index.ts +500 -0
- package/connectors/servicenow-mcp/tsconfig.json +13 -0
- package/connectors/templates/mcp-connector/README.md +31 -0
- package/connectors/templates/mcp-connector/package.json +15 -0
- package/connectors/templates/mcp-connector/src/index.ts +330 -0
- package/connectors/templates/mcp-connector/tsconfig.json +13 -0
- package/connectors/zendesk-mcp/README.md +40 -0
- package/connectors/zendesk-mcp/dist/index.js +431 -0
- package/connectors/zendesk-mcp/package.json +15 -0
- package/connectors/zendesk-mcp/src/index.ts +543 -0
- package/connectors/zendesk-mcp/tsconfig.json +13 -0
- package/dist/electron/electron/agent/custom-skill-loader.js +31 -1
- package/dist/electron/electron/agent/daemon.js +189 -13
- package/dist/electron/electron/agent/executor.js +895 -78
- package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
- package/dist/electron/electron/agent/llm/azure-openai-provider.js +328 -0
- package/dist/electron/electron/agent/llm/bedrock-provider.js +49 -9
- package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
- package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
- package/dist/electron/electron/agent/llm/index.js +13 -1
- package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
- package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
- package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
- package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
- package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
- package/dist/electron/electron/agent/llm/provider-factory.js +350 -4
- package/dist/electron/electron/agent/llm/types.js +66 -1
- package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
- package/dist/electron/electron/agent/search/provider-factory.js +38 -2
- package/dist/electron/electron/agent/tools/box-tools.js +231 -0
- package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
- package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
- package/dist/electron/electron/agent/tools/file-tools.js +66 -3
- package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
- package/dist/electron/electron/agent/tools/grep-tools.js +90 -10
- package/dist/electron/electron/agent/tools/image-tools.js +11 -1
- package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
- package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
- package/dist/electron/electron/agent/tools/registry.js +548 -10
- package/dist/electron/electron/agent/tools/search-tools.js +28 -10
- package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
- package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
- package/dist/electron/electron/agent/tools/x-tools.js +1 -1
- package/dist/electron/electron/agents/agent-dispatch.js +63 -0
- package/dist/electron/electron/database/repositories.js +19 -5
- package/dist/electron/electron/database/schema.js +8 -0
- package/dist/electron/electron/gateway/channels/whatsapp.js +55 -0
- package/dist/electron/electron/gateway/index.js +75 -1
- package/dist/electron/electron/gateway/router.js +209 -154
- package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
- package/dist/electron/electron/ipc/handlers.js +763 -267
- package/dist/electron/electron/main.js +63 -0
- package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
- package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
- package/dist/electron/electron/memory/MemoryService.js +2 -1
- package/dist/electron/electron/preload.js +78 -1
- package/dist/electron/electron/settings/appearance-manager.js +18 -1
- package/dist/electron/electron/settings/box-manager.js +54 -0
- package/dist/electron/electron/settings/dropbox-manager.js +54 -0
- package/dist/electron/electron/settings/google-drive-manager.js +54 -0
- package/dist/electron/electron/settings/notion-manager.js +56 -0
- package/dist/electron/electron/settings/onedrive-manager.js +54 -0
- package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
- package/dist/electron/electron/utils/box-api.js +153 -0
- package/dist/electron/electron/utils/dropbox-api.js +144 -0
- package/dist/electron/electron/utils/env-migration.js +19 -0
- package/dist/electron/electron/utils/google-drive-api.js +152 -0
- package/dist/electron/electron/utils/notion-api.js +103 -0
- package/dist/electron/electron/utils/onedrive-api.js +113 -0
- package/dist/electron/electron/utils/sharepoint-api.js +109 -0
- package/dist/electron/electron/utils/validation.js +98 -3
- package/dist/electron/electron/utils/x-cli.js +1 -1
- package/dist/electron/shared/channelMessages.js +284 -3
- package/dist/electron/shared/llm-provider-catalog.js +198 -0
- package/dist/electron/shared/types.js +90 -1
- package/package.json +14 -3
- package/resources/skills/nano-banana-pro.json +4 -4
- package/resources/skills/openai-image-gen.json +3 -3
- package/resources/skills/scripts/gen.py +163 -0
- package/resources/skills/scripts/generate_image.py +91 -0
- package/src/electron/agent/custom-skill-loader.ts +34 -1
- package/src/electron/agent/daemon.ts +210 -14
- package/src/electron/agent/executor.ts +1124 -85
- package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
- package/src/electron/agent/llm/azure-openai-provider.ts +388 -0
- package/src/electron/agent/llm/bedrock-provider.ts +62 -9
- package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
- package/src/electron/agent/llm/groq-provider.ts +39 -0
- package/src/electron/agent/llm/index.ts +6 -0
- package/src/electron/agent/llm/kimi-provider.ts +39 -0
- package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
- package/src/electron/agent/llm/openai-compatible.ts +133 -0
- package/src/electron/agent/llm/openai-oauth.ts +2 -1
- package/src/electron/agent/llm/openrouter-provider.ts +2 -1
- package/src/electron/agent/llm/provider-factory.ts +459 -6
- package/src/electron/agent/llm/types.ts +95 -1
- package/src/electron/agent/llm/xai-provider.ts +39 -0
- package/src/electron/agent/search/provider-factory.ts +43 -2
- package/src/electron/agent/tools/box-tools.ts +239 -0
- package/src/electron/agent/tools/builtin-settings.ts +36 -0
- package/src/electron/agent/tools/dropbox-tools.ts +237 -0
- package/src/electron/agent/tools/file-tools.ts +66 -3
- package/src/electron/agent/tools/gmail-tools.ts +240 -0
- package/src/electron/agent/tools/google-calendar-tools.ts +258 -0
- package/src/electron/agent/tools/google-drive-tools.ts +228 -0
- package/src/electron/agent/tools/grep-tools.ts +97 -12
- package/src/electron/agent/tools/image-tools.ts +11 -1
- package/src/electron/agent/tools/notion-tools.ts +330 -0
- package/src/electron/agent/tools/onedrive-tools.ts +217 -0
- package/src/electron/agent/tools/registry.ts +794 -10
- package/src/electron/agent/tools/search-tools.ts +29 -11
- package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
- package/src/electron/agent/tools/shell-tools.ts +11 -3
- package/src/electron/agent/tools/x-tools.ts +1 -1
- package/src/electron/agents/agent-dispatch.ts +79 -0
- package/src/electron/database/SecureSettingsRepository.ts +7 -1
- package/src/electron/database/repositories.ts +58 -6
- package/src/electron/database/schema.ts +8 -0
- package/src/electron/gateway/channels/discord.ts +4 -0
- package/src/electron/gateway/channels/google-chat.ts +3 -0
- package/src/electron/gateway/channels/line.ts +3 -0
- package/src/electron/gateway/channels/matrix-client.ts +15 -0
- package/src/electron/gateway/channels/matrix.ts +31 -0
- package/src/electron/gateway/channels/mattermost.ts +3 -0
- package/src/electron/gateway/channels/signal.ts +3 -0
- package/src/electron/gateway/channels/slack.ts +9 -4
- package/src/electron/gateway/channels/teams.ts +4 -0
- package/src/electron/gateway/channels/telegram.ts +2 -0
- package/src/electron/gateway/channels/twitch.ts +2 -0
- package/src/electron/gateway/channels/types.ts +8 -0
- package/src/electron/gateway/channels/whatsapp.ts +66 -0
- package/src/electron/gateway/index.ts +95 -2
- package/src/electron/gateway/router.ts +231 -161
- package/src/electron/gateway/security.ts +21 -9
- package/src/electron/ipc/canvas-handlers.ts +10 -0
- package/src/electron/ipc/handlers.ts +848 -292
- package/src/electron/main.ts +35 -0
- package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
- package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
- package/src/electron/memory/MemoryService.ts +7 -1
- package/src/electron/preload.ts +200 -5
- package/src/electron/settings/appearance-manager.ts +20 -2
- package/src/electron/settings/box-manager.ts +58 -0
- package/src/electron/settings/dropbox-manager.ts +58 -0
- package/src/electron/settings/google-workspace-manager.ts +59 -0
- package/src/electron/settings/notion-manager.ts +60 -0
- package/src/electron/settings/onedrive-manager.ts +58 -0
- package/src/electron/settings/sharepoint-manager.ts +58 -0
- package/src/electron/utils/box-api.ts +184 -0
- package/src/electron/utils/dropbox-api.ts +171 -0
- package/src/electron/utils/env-migration.ts +22 -0
- package/src/electron/utils/gmail-api.ts +121 -0
- package/src/electron/utils/google-calendar-api.ts +115 -0
- package/src/electron/utils/google-workspace-api.ts +228 -0
- package/src/electron/utils/google-workspace-auth.ts +109 -0
- package/src/electron/utils/google-workspace-oauth.ts +232 -0
- package/src/electron/utils/notion-api.ts +126 -0
- package/src/electron/utils/onedrive-api.ts +137 -0
- package/src/electron/utils/sharepoint-api.ts +132 -0
- package/src/electron/utils/validation.ts +128 -1
- package/src/electron/utils/x-cli.ts +1 -1
- package/src/renderer/App.tsx +119 -8
- package/src/renderer/components/ActivityFeedItem.tsx +34 -17
- package/src/renderer/components/AgentWorkingStatePanel.tsx +7 -5
- package/src/renderer/components/AppearanceSettings.tsx +37 -2
- package/src/renderer/components/BlueBubblesSettings.tsx +18 -7
- package/src/renderer/components/BoxSettings.tsx +203 -0
- package/src/renderer/components/BrowserView.tsx +101 -0
- package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
- package/src/renderer/components/CanvasPreview.tsx +68 -1
- package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
- package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
- package/src/renderer/components/ConnectorsSettings.tsx +397 -0
- package/src/renderer/components/ControlPlaneSettings.tsx +2 -0
- package/src/renderer/components/DiscordSettings.tsx +18 -7
- package/src/renderer/components/DropboxSettings.tsx +202 -0
- package/src/renderer/components/EmailSettings.tsx +18 -7
- package/src/renderer/components/FileViewer.tsx +21 -13
- package/src/renderer/components/GoogleChatSettings.tsx +17 -7
- package/src/renderer/components/GoogleWorkspaceSettings.tsx +332 -0
- package/src/renderer/components/ImessageSettings.tsx +22 -11
- package/src/renderer/components/LineIcons.tsx +376 -0
- package/src/renderer/components/LineSettings.tsx +18 -7
- package/src/renderer/components/MCPSettings.tsx +56 -0
- package/src/renderer/components/MainContent.tsx +740 -76
- package/src/renderer/components/MatrixSettings.tsx +18 -7
- package/src/renderer/components/MattermostSettings.tsx +18 -7
- package/src/renderer/components/NodesSettings.tsx +58 -99
- package/src/renderer/components/NotificationPanel.tsx +25 -11
- package/src/renderer/components/NotionSettings.tsx +231 -0
- package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
- package/src/renderer/components/OnboardingModal.tsx +70 -1
- package/src/renderer/components/OneDriveSettings.tsx +212 -0
- package/src/renderer/components/RightPanel.tsx +141 -28
- package/src/renderer/components/ScheduledTasksSettings.tsx +10 -62
- package/src/renderer/components/SearchSettings.tsx +118 -114
- package/src/renderer/components/Settings.tsx +1425 -651
- package/src/renderer/components/SharePointSettings.tsx +224 -0
- package/src/renderer/components/Sidebar.tsx +94 -19
- package/src/renderer/components/SignalSettings.tsx +18 -7
- package/src/renderer/components/SkillHubBrowser.tsx +144 -185
- package/src/renderer/components/SlackSettings.tsx +18 -7
- package/src/renderer/components/TaskQuickActions.tsx +11 -6
- package/src/renderer/components/TaskTimeline.tsx +58 -26
- package/src/renderer/components/TeamsSettings.tsx +18 -7
- package/src/renderer/components/TelegramSettings.tsx +18 -7
- package/src/renderer/components/ThemeIcon.tsx +16 -0
- package/src/renderer/components/TwitchSettings.tsx +18 -7
- package/src/renderer/components/VoiceSettings.tsx +30 -74
- package/src/renderer/components/WhatsAppSettings.tsx +48 -37
- package/src/renderer/components/WorkingStateHistory.tsx +7 -5
- package/src/renderer/components/WorkspaceSelector.tsx +42 -13
- package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
- package/src/renderer/styles/index.css +2333 -209
- package/src/shared/channelMessages.ts +367 -4
- package/src/shared/llm-provider-catalog.ts +217 -0
- package/src/shared/types.ts +251 -2
|
@@ -4,6 +4,8 @@ import ReactMarkdown from 'react-markdown';
|
|
|
4
4
|
import remarkGfm from 'remark-gfm';
|
|
5
5
|
import { FileViewerResult } from '../../electron/preload';
|
|
6
6
|
import { useAgentContext } from '../hooks/useAgentContext';
|
|
7
|
+
import { ThemeIcon } from './ThemeIcon';
|
|
8
|
+
import { AlertTriangleIcon, CodeIcon, FileIcon, FileTextIcon, GlobeIcon, ImageIcon, PresentationIcon } from './LineIcons';
|
|
7
9
|
|
|
8
10
|
interface FileViewerProps {
|
|
9
11
|
filePath: string;
|
|
@@ -66,17 +68,17 @@ export function FileViewer({ filePath, workspacePath, onClose }: FileViewerProps
|
|
|
66
68
|
};
|
|
67
69
|
|
|
68
70
|
// Get file icon based on type
|
|
69
|
-
const getFileIcon = (type?: string):
|
|
71
|
+
const getFileIcon = (type?: string): React.ReactNode => {
|
|
70
72
|
switch (type) {
|
|
71
|
-
case 'markdown': return
|
|
72
|
-
case 'code': return
|
|
73
|
-
case 'text': return
|
|
74
|
-
case 'docx': return
|
|
75
|
-
case 'pdf': return
|
|
76
|
-
case 'image': return
|
|
77
|
-
case 'pptx': return
|
|
78
|
-
case 'html': return
|
|
79
|
-
default: return
|
|
73
|
+
case 'markdown': return <ThemeIcon emoji="📝" icon={<FileTextIcon size={16} />} />;
|
|
74
|
+
case 'code': return <ThemeIcon emoji="💻" icon={<CodeIcon size={16} />} />;
|
|
75
|
+
case 'text': return <ThemeIcon emoji="📄" icon={<FileIcon size={16} />} />;
|
|
76
|
+
case 'docx': return <ThemeIcon emoji="📘" icon={<FileTextIcon size={16} />} />;
|
|
77
|
+
case 'pdf': return <ThemeIcon emoji="📕" icon={<FileTextIcon size={16} />} />;
|
|
78
|
+
case 'image': return <ThemeIcon emoji="🖼️" icon={<ImageIcon size={16} />} />;
|
|
79
|
+
case 'pptx': return <ThemeIcon emoji="📊" icon={<PresentationIcon size={16} />} />;
|
|
80
|
+
case 'html': return <ThemeIcon emoji="🌐" icon={<GlobeIcon size={16} />} />;
|
|
81
|
+
default: return <ThemeIcon emoji="📁" icon={<FileIcon size={16} />} />;
|
|
80
82
|
}
|
|
81
83
|
};
|
|
82
84
|
|
|
@@ -141,7 +143,9 @@ export function FileViewer({ filePath, workspacePath, onClose }: FileViewerProps
|
|
|
141
143
|
case 'pptx':
|
|
142
144
|
return (
|
|
143
145
|
<div className="file-viewer-placeholder">
|
|
144
|
-
<span className="file-viewer-placeholder-icon"
|
|
146
|
+
<span className="file-viewer-placeholder-icon">
|
|
147
|
+
<ThemeIcon emoji="📊" icon={<PresentationIcon size={28} />} />
|
|
148
|
+
</span>
|
|
145
149
|
<p>PowerPoint preview is not available.</p>
|
|
146
150
|
<button onClick={handleOpenExternal} className="file-viewer-open-btn">
|
|
147
151
|
Open in PowerPoint
|
|
@@ -152,7 +156,9 @@ export function FileViewer({ filePath, workspacePath, onClose }: FileViewerProps
|
|
|
152
156
|
default:
|
|
153
157
|
return (
|
|
154
158
|
<div className="file-viewer-placeholder">
|
|
155
|
-
<span className="file-viewer-placeholder-icon"
|
|
159
|
+
<span className="file-viewer-placeholder-icon">
|
|
160
|
+
<ThemeIcon emoji="📁" icon={<FileIcon size={28} />} />
|
|
161
|
+
</span>
|
|
156
162
|
<p>This file type cannot be previewed.</p>
|
|
157
163
|
<button onClick={handleOpenExternal} className="file-viewer-open-btn">
|
|
158
164
|
Open with Default App
|
|
@@ -207,7 +213,9 @@ export function FileViewer({ filePath, workspacePath, onClose }: FileViewerProps
|
|
|
207
213
|
|
|
208
214
|
{error && (
|
|
209
215
|
<div className="file-viewer-error">
|
|
210
|
-
<span className="file-viewer-error-icon"
|
|
216
|
+
<span className="file-viewer-error-icon">
|
|
217
|
+
<ThemeIcon emoji="⚠️" icon={<AlertTriangleIcon size={18} />} />
|
|
218
|
+
</span>
|
|
211
219
|
<p>{error}</p>
|
|
212
220
|
<button onClick={handleOpenExternal} className="file-viewer-open-btn">
|
|
213
221
|
Try Opening with Default App
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import { ChannelData, ChannelUserData, SecurityMode, ContextType, ContextPolicy } from '../../shared/types';
|
|
3
3
|
import { PairingCodeDisplay } from './PairingCodeDisplay';
|
|
4
4
|
import { ContextPolicySettings } from './ContextPolicySettings';
|
|
@@ -32,11 +32,7 @@ export function GoogleChatSettings({ onStatusChange }: GoogleChatSettingsProps)
|
|
|
32
32
|
const [contextPolicies, setContextPolicies] = useState<Record<ContextType, ContextPolicy>>({} as Record<ContextType, ContextPolicy>);
|
|
33
33
|
const [savingPolicy, setSavingPolicy] = useState(false);
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
loadChannel();
|
|
37
|
-
}, []);
|
|
38
|
-
|
|
39
|
-
const loadChannel = async () => {
|
|
35
|
+
const loadChannel = useCallback(async () => {
|
|
40
36
|
try {
|
|
41
37
|
setLoading(true);
|
|
42
38
|
const channels = await window.electronAPI.getGatewayChannels();
|
|
@@ -65,8 +61,22 @@ export function GoogleChatSettings({ onStatusChange }: GoogleChatSettingsProps)
|
|
|
65
61
|
} finally {
|
|
66
62
|
setLoading(false);
|
|
67
63
|
}
|
|
68
|
-
};
|
|
64
|
+
}, [onStatusChange]);
|
|
69
65
|
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
loadChannel();
|
|
68
|
+
}, [loadChannel]);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
const unsubscribe = window.electronAPI?.onGatewayUsersUpdated?.((data) => {
|
|
72
|
+
if (data?.channelType !== 'googlechat') return;
|
|
73
|
+
if (channel && data?.channelId && data.channelId !== channel.id) return;
|
|
74
|
+
loadChannel();
|
|
75
|
+
});
|
|
76
|
+
return () => {
|
|
77
|
+
if (unsubscribe) unsubscribe();
|
|
78
|
+
};
|
|
79
|
+
}, [channel?.id, loadChannel]);
|
|
70
80
|
|
|
71
81
|
const handleAddChannel = async () => {
|
|
72
82
|
if (!serviceAccountKeyPath.trim()) return;
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { GoogleWorkspaceSettingsData } from '../../shared/types';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_SCOPES = [
|
|
5
|
+
'https://www.googleapis.com/auth/drive',
|
|
6
|
+
'https://www.googleapis.com/auth/gmail.readonly',
|
|
7
|
+
'https://www.googleapis.com/auth/gmail.send',
|
|
8
|
+
'https://www.googleapis.com/auth/calendar',
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const DEFAULT_TIMEOUT_MS = 20000;
|
|
12
|
+
|
|
13
|
+
const scopesToText = (scopes?: string[]) => (scopes && scopes.length > 0 ? scopes.join(' ') : DEFAULT_SCOPES.join(' '));
|
|
14
|
+
|
|
15
|
+
const textToScopes = (value: string) =>
|
|
16
|
+
value
|
|
17
|
+
.split(/\s+/)
|
|
18
|
+
.map((scope) => scope.trim())
|
|
19
|
+
.filter(Boolean);
|
|
20
|
+
|
|
21
|
+
export function GoogleWorkspaceSettings() {
|
|
22
|
+
const [settings, setSettings] = useState<GoogleWorkspaceSettingsData | null>(null);
|
|
23
|
+
const [saving, setSaving] = useState(false);
|
|
24
|
+
const [testing, setTesting] = useState(false);
|
|
25
|
+
const [testResult, setTestResult] = useState<{ success: boolean; error?: string; name?: string; userId?: string; email?: string } | null>(null);
|
|
26
|
+
const [status, setStatus] = useState<{ configured: boolean; connected: boolean; name?: string; error?: string } | null>(null);
|
|
27
|
+
const [statusLoading, setStatusLoading] = useState(false);
|
|
28
|
+
const [oauthBusy, setOauthBusy] = useState(false);
|
|
29
|
+
const [oauthError, setOauthError] = useState<string | null>(null);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
loadSettings();
|
|
33
|
+
refreshStatus();
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
const loadSettings = async () => {
|
|
37
|
+
try {
|
|
38
|
+
const loaded = await window.electronAPI.getGoogleWorkspaceSettings();
|
|
39
|
+
setSettings(loaded);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('Failed to load Google Workspace settings:', error);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const updateSettings = (updates: Partial<GoogleWorkspaceSettingsData>) => {
|
|
46
|
+
if (!settings) return;
|
|
47
|
+
setSettings({ ...settings, ...updates });
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleSave = async () => {
|
|
51
|
+
if (!settings) return;
|
|
52
|
+
setSaving(true);
|
|
53
|
+
setTestResult(null);
|
|
54
|
+
try {
|
|
55
|
+
const payload: GoogleWorkspaceSettingsData = { ...settings };
|
|
56
|
+
await window.electronAPI.saveGoogleWorkspaceSettings(payload);
|
|
57
|
+
setSettings(payload);
|
|
58
|
+
await refreshStatus();
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('Failed to save Google Workspace settings:', error);
|
|
61
|
+
} finally {
|
|
62
|
+
setSaving(false);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const refreshStatus = async () => {
|
|
67
|
+
try {
|
|
68
|
+
setStatusLoading(true);
|
|
69
|
+
const result = await window.electronAPI.getGoogleWorkspaceStatus();
|
|
70
|
+
setStatus(result);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('Failed to load Google Workspace status:', error);
|
|
73
|
+
} finally {
|
|
74
|
+
setStatusLoading(false);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const handleTestConnection = async () => {
|
|
79
|
+
setTesting(true);
|
|
80
|
+
setTestResult(null);
|
|
81
|
+
try {
|
|
82
|
+
const result = await window.electronAPI.testGoogleWorkspaceConnection();
|
|
83
|
+
setTestResult(result);
|
|
84
|
+
await refreshStatus();
|
|
85
|
+
} catch (error: any) {
|
|
86
|
+
setTestResult({ success: false, error: error.message || 'Failed to test connection' });
|
|
87
|
+
} finally {
|
|
88
|
+
setTesting(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleOAuthConnect = async () => {
|
|
93
|
+
if (!settings?.clientId) {
|
|
94
|
+
setOauthError('Client ID is required to start OAuth.');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
setOauthBusy(true);
|
|
99
|
+
setOauthError(null);
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const scopes = settings.scopes && settings.scopes.length > 0 ? settings.scopes : DEFAULT_SCOPES;
|
|
103
|
+
const result = await window.electronAPI.startGoogleWorkspaceOAuth({
|
|
104
|
+
clientId: settings.clientId,
|
|
105
|
+
clientSecret: settings.clientSecret || undefined,
|
|
106
|
+
scopes,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const tokenExpiresAt = result.expiresIn ? Date.now() + result.expiresIn * 1000 : settings.tokenExpiresAt;
|
|
110
|
+
|
|
111
|
+
const payload: GoogleWorkspaceSettingsData = {
|
|
112
|
+
...settings,
|
|
113
|
+
enabled: true,
|
|
114
|
+
accessToken: result.accessToken,
|
|
115
|
+
refreshToken: result.refreshToken || settings.refreshToken,
|
|
116
|
+
tokenExpiresAt,
|
|
117
|
+
scopes: result.scopes || scopes,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
await window.electronAPI.saveGoogleWorkspaceSettings(payload);
|
|
121
|
+
setSettings(payload);
|
|
122
|
+
await refreshStatus();
|
|
123
|
+
} catch (error: any) {
|
|
124
|
+
setOauthError(error.message || 'Google Workspace OAuth failed');
|
|
125
|
+
} finally {
|
|
126
|
+
setOauthBusy(false);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
if (!settings) {
|
|
131
|
+
return <div className="settings-loading">Loading Google Workspace settings...</div>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const statusLabel = !status?.configured
|
|
135
|
+
? 'Missing Token'
|
|
136
|
+
: status.connected
|
|
137
|
+
? 'Connected'
|
|
138
|
+
: 'Configured';
|
|
139
|
+
|
|
140
|
+
const statusClass = !status?.configured
|
|
141
|
+
? 'missing'
|
|
142
|
+
: status.connected
|
|
143
|
+
? 'connected'
|
|
144
|
+
: 'configured';
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<div className="google-workspace-settings">
|
|
148
|
+
<div className="settings-section">
|
|
149
|
+
<div className="settings-section-header">
|
|
150
|
+
<div className="settings-title-with-badge">
|
|
151
|
+
<h3>Connect Google Workspace</h3>
|
|
152
|
+
{status && (
|
|
153
|
+
<span
|
|
154
|
+
className={`google-workspace-status-badge ${statusClass}`}
|
|
155
|
+
title={!status.configured ? 'Tokens not configured' : status.connected ? 'Connected to Google Workspace' : 'Configured'}
|
|
156
|
+
>
|
|
157
|
+
{statusLabel}
|
|
158
|
+
</span>
|
|
159
|
+
)}
|
|
160
|
+
{statusLoading && !status && (
|
|
161
|
+
<span className="google-workspace-status-badge configured">Checking…</span>
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
<button className="btn-secondary btn-sm" onClick={refreshStatus} disabled={statusLoading}>
|
|
165
|
+
{statusLoading ? 'Checking...' : 'Refresh Status'}
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
<p className="settings-description">
|
|
169
|
+
Connect Gmail, Calendar, and Drive with a single Google Workspace OAuth flow. After connecting, use
|
|
170
|
+
`google_drive_action`, `gmail_action`, and `calendar_action` tools in tasks.
|
|
171
|
+
</p>
|
|
172
|
+
{status?.error && (
|
|
173
|
+
<p className="settings-hint">Status check: {status.error}</p>
|
|
174
|
+
)}
|
|
175
|
+
{oauthError && (
|
|
176
|
+
<p className="settings-hint">OAuth error: {oauthError}</p>
|
|
177
|
+
)}
|
|
178
|
+
<div className="settings-actions">
|
|
179
|
+
<button
|
|
180
|
+
className="btn-secondary btn-sm"
|
|
181
|
+
onClick={() => window.electronAPI.openExternal('https://console.cloud.google.com/apis/credentials')}
|
|
182
|
+
>
|
|
183
|
+
Open Google Cloud Console
|
|
184
|
+
</button>
|
|
185
|
+
<button
|
|
186
|
+
className="btn-primary btn-sm"
|
|
187
|
+
onClick={handleOAuthConnect}
|
|
188
|
+
disabled={oauthBusy}
|
|
189
|
+
>
|
|
190
|
+
{oauthBusy ? 'Connecting...' : 'Connect'}
|
|
191
|
+
</button>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div className="settings-section">
|
|
196
|
+
<div className="settings-field">
|
|
197
|
+
<label>Enable Integration</label>
|
|
198
|
+
<label className="settings-toggle">
|
|
199
|
+
<input
|
|
200
|
+
type="checkbox"
|
|
201
|
+
checked={settings.enabled}
|
|
202
|
+
onChange={(e) => updateSettings({ enabled: e.target.checked })}
|
|
203
|
+
/>
|
|
204
|
+
<span className="toggle-slider" />
|
|
205
|
+
</label>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<div className="settings-field">
|
|
209
|
+
<label>Client ID</label>
|
|
210
|
+
<input
|
|
211
|
+
type="text"
|
|
212
|
+
className="settings-input"
|
|
213
|
+
placeholder="Google OAuth client ID"
|
|
214
|
+
value={settings.clientId || ''}
|
|
215
|
+
onChange={(e) => updateSettings({ clientId: e.target.value || undefined })}
|
|
216
|
+
/>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<div className="settings-field">
|
|
220
|
+
<label>Client Secret (optional)</label>
|
|
221
|
+
<input
|
|
222
|
+
type="password"
|
|
223
|
+
className="settings-input"
|
|
224
|
+
placeholder="Google OAuth client secret"
|
|
225
|
+
value={settings.clientSecret || ''}
|
|
226
|
+
onChange={(e) => updateSettings({ clientSecret: e.target.value || undefined })}
|
|
227
|
+
/>
|
|
228
|
+
<p className="settings-hint">Use an OAuth client configured for Desktop or Web applications.</p>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<div className="settings-field">
|
|
232
|
+
<label>Scopes</label>
|
|
233
|
+
<textarea
|
|
234
|
+
className="settings-input"
|
|
235
|
+
rows={3}
|
|
236
|
+
value={scopesToText(settings.scopes)}
|
|
237
|
+
onChange={(e) => updateSettings({ scopes: textToScopes(e.target.value) })}
|
|
238
|
+
/>
|
|
239
|
+
<p className="settings-hint">Space-separated scopes used during OAuth.</p>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<div className="settings-field">
|
|
243
|
+
<label>Access Token</label>
|
|
244
|
+
<input
|
|
245
|
+
type="password"
|
|
246
|
+
className="settings-input"
|
|
247
|
+
placeholder="Google OAuth access token"
|
|
248
|
+
value={settings.accessToken || ''}
|
|
249
|
+
onChange={(e) => updateSettings({ accessToken: e.target.value || undefined })}
|
|
250
|
+
/>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<div className="settings-field">
|
|
254
|
+
<label>Refresh Token</label>
|
|
255
|
+
<input
|
|
256
|
+
type="password"
|
|
257
|
+
className="settings-input"
|
|
258
|
+
placeholder="Google OAuth refresh token"
|
|
259
|
+
value={settings.refreshToken || ''}
|
|
260
|
+
onChange={(e) => updateSettings({ refreshToken: e.target.value || undefined })}
|
|
261
|
+
/>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<div className="settings-field">
|
|
265
|
+
<label>Token Expires At (ms)</label>
|
|
266
|
+
<input
|
|
267
|
+
type="number"
|
|
268
|
+
className="settings-input"
|
|
269
|
+
min={0}
|
|
270
|
+
value={settings.tokenExpiresAt ?? ''}
|
|
271
|
+
onChange={(e) => updateSettings({ tokenExpiresAt: Number(e.target.value) || undefined })}
|
|
272
|
+
/>
|
|
273
|
+
<p className="settings-hint">Used for auto-refresh; set automatically after OAuth.</p>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<div className="settings-field">
|
|
277
|
+
<label>Timeout (ms)</label>
|
|
278
|
+
<input
|
|
279
|
+
type="number"
|
|
280
|
+
className="settings-input"
|
|
281
|
+
min={1000}
|
|
282
|
+
max={120000}
|
|
283
|
+
value={settings.timeoutMs ?? DEFAULT_TIMEOUT_MS}
|
|
284
|
+
onChange={(e) => updateSettings({ timeoutMs: Number(e.target.value) })}
|
|
285
|
+
/>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
<div className="settings-actions">
|
|
289
|
+
<button className="btn-secondary btn-sm" onClick={handleTestConnection} disabled={testing}>
|
|
290
|
+
{testing ? 'Testing...' : 'Test Connection'}
|
|
291
|
+
</button>
|
|
292
|
+
<button className="btn-primary btn-sm" onClick={handleSave} disabled={saving}>
|
|
293
|
+
{saving ? 'Saving...' : 'Save Settings'}
|
|
294
|
+
</button>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
{testResult && (
|
|
298
|
+
<div className={`test-result ${testResult.success ? 'success' : 'error'}`}>
|
|
299
|
+
{testResult.success ? (
|
|
300
|
+
<span>Connected{testResult.name ? ` as ${testResult.name}` : ''}</span>
|
|
301
|
+
) : (
|
|
302
|
+
<span>Connection failed: {testResult.error}</span>
|
|
303
|
+
)}
|
|
304
|
+
</div>
|
|
305
|
+
)}
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
<div className="settings-section">
|
|
309
|
+
<h4>Quick Usage</h4>
|
|
310
|
+
<pre className="settings-info-box">{`// Search Drive files
|
|
311
|
+
google_drive_action({
|
|
312
|
+
action: "list_files",
|
|
313
|
+
query: "modifiedTime > '2026-02-01T00:00:00Z'",
|
|
314
|
+
page_size: 10
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Search Gmail
|
|
318
|
+
gmail_action({
|
|
319
|
+
action: "list_messages",
|
|
320
|
+
query: "from:me newer_than:7d"
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// List upcoming calendar events
|
|
324
|
+
calendar_action({
|
|
325
|
+
action: "list_events",
|
|
326
|
+
time_min: "2026-02-05T00:00:00Z",
|
|
327
|
+
max_results: 10
|
|
328
|
+
});`}</pre>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import { ChannelData, ChannelUserData, SecurityMode } from '../../shared/types';
|
|
3
3
|
|
|
4
4
|
interface ImessageSettingsProps {
|
|
@@ -31,15 +31,7 @@ export function ImessageSettings({ onStatusChange }: ImessageSettingsProps) {
|
|
|
31
31
|
// Check if we're on macOS
|
|
32
32
|
const [isMacOS, setIsMacOS] = useState(true);
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
// Check platform
|
|
36
|
-
const platform = navigator.platform.toLowerCase();
|
|
37
|
-
setIsMacOS(platform.includes('mac'));
|
|
38
|
-
|
|
39
|
-
loadChannel();
|
|
40
|
-
}, []);
|
|
41
|
-
|
|
42
|
-
const loadChannel = async () => {
|
|
34
|
+
const loadChannel = useCallback(async () => {
|
|
43
35
|
try {
|
|
44
36
|
setLoading(true);
|
|
45
37
|
const channels = await window.electronAPI.getGatewayChannels();
|
|
@@ -70,7 +62,26 @@ export function ImessageSettings({ onStatusChange }: ImessageSettingsProps) {
|
|
|
70
62
|
} finally {
|
|
71
63
|
setLoading(false);
|
|
72
64
|
}
|
|
73
|
-
};
|
|
65
|
+
}, [onStatusChange]);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
// Check platform
|
|
69
|
+
const platform = navigator.platform.toLowerCase();
|
|
70
|
+
setIsMacOS(platform.includes('mac'));
|
|
71
|
+
|
|
72
|
+
loadChannel();
|
|
73
|
+
}, [loadChannel]);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
const unsubscribe = window.electronAPI?.onGatewayUsersUpdated?.((data) => {
|
|
77
|
+
if (data?.channelType !== 'imessage') return;
|
|
78
|
+
if (channel && data?.channelId && data.channelId !== channel.id) return;
|
|
79
|
+
loadChannel();
|
|
80
|
+
});
|
|
81
|
+
return () => {
|
|
82
|
+
if (unsubscribe) unsubscribe();
|
|
83
|
+
};
|
|
84
|
+
}, [channel?.id, loadChannel]);
|
|
74
85
|
|
|
75
86
|
const handleAddChannel = async () => {
|
|
76
87
|
try {
|