@cloudcli-ai/cloudcli 1.33.3 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +9 -1
- package/README.ja.md +8 -0
- package/README.ko.md +9 -1
- package/README.md +16 -3
- package/README.ru.md +9 -1
- package/README.tr.md +8 -1
- package/README.zh-CN.md +9 -1
- package/README.zh-TW.md +9 -1
- package/dist/api-docs.html +36 -18
- package/dist/assets/index-CtwxqzGG.js +1407 -0
- package/dist/assets/index-DlvtxFyt.css +32 -0
- package/dist/index.html +2 -2
- package/dist-server/server/browser-use-mcp.js +350 -0
- package/dist-server/server/browser-use-mcp.js.map +1 -0
- package/dist-server/server/claude-sdk.js +26 -6
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/cli.js +14 -6
- package/dist-server/server/cli.js.map +1 -1
- package/dist-server/server/computer-use-agent.js +242 -0
- package/dist-server/server/computer-use-agent.js.map +1 -0
- package/dist-server/server/computer-use-mcp.js +519 -0
- package/dist-server/server/computer-use-mcp.js.map +1 -0
- package/dist-server/server/cursor-cli.js +27 -11
- package/dist-server/server/cursor-cli.js.map +1 -1
- package/dist-server/server/gemini-cli.js +17 -2
- package/dist-server/server/gemini-cli.js.map +1 -1
- package/dist-server/server/index.js +149 -40
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/load-env.js +1 -1
- package/dist-server/server/load-env.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js +107 -0
- package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js +93 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js +688 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -0
- package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js +8 -0
- package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js.map +1 -0
- package/dist-server/server/modules/computer-use/actions/raw-action-dispatcher.js +55 -0
- package/dist-server/server/modules/computer-use/actions/raw-action-dispatcher.js.map +1 -0
- package/dist-server/server/modules/computer-use/actions/raw-action-types.js +2 -0
- package/dist-server/server/modules/computer-use/actions/raw-action-types.js.map +1 -0
- package/dist-server/server/modules/computer-use/computer-executor.js +212 -0
- package/dist-server/server/modules/computer-use/computer-executor.js.map +1 -0
- package/dist-server/server/modules/computer-use/computer-semantics.service.js +422 -0
- package/dist-server/server/modules/computer-use/computer-semantics.service.js.map +1 -0
- package/dist-server/server/modules/computer-use/computer-use-mcp.routes.js +126 -0
- package/dist-server/server/modules/computer-use/computer-use-mcp.routes.js.map +1 -0
- package/dist-server/server/modules/computer-use/computer-use.routes.js +196 -0
- package/dist-server/server/modules/computer-use/computer-use.routes.js.map +1 -0
- package/dist-server/server/modules/computer-use/computer-use.service.js +759 -0
- package/dist-server/server/modules/computer-use/computer-use.service.js.map +1 -0
- package/dist-server/server/modules/computer-use/desktop-agent-relay.service.js +126 -0
- package/dist-server/server/modules/computer-use/desktop-agent-relay.service.js.map +1 -0
- package/dist-server/server/modules/computer-use/index.js +3 -0
- package/dist-server/server/modules/computer-use/index.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.js +59 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.js +5 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/semantic-adapter.js +2 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/semantic-adapter.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.js +5 -0
- package/dist-server/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/bin/darwin-arm64/CloudCLISemantics +0 -0
- package/dist-server/server/modules/computer-use/semantics/bin/darwin-x64/CloudCLISemantics +0 -0
- package/dist-server/server/modules/computer-use/semantics/bin/win32-arm64/CloudCLISemantics.exe +0 -0
- package/dist-server/server/modules/computer-use/semantics/bin/win32-x64/CloudCLISemantics.exe +0 -0
- package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-process.js +106 -0
- package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-process.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.js +78 -0
- package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-session-store.js +68 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-session-store.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-tool-dispatcher.js +16 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-tool-dispatcher.js.map +1 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-types.js +2 -0
- package/dist-server/server/modules/computer-use/semantics/semantic-types.js.map +1 -0
- package/dist-server/server/modules/database/index.js +1 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +23 -1
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +100 -0
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js.map +1 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js +8 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js.map +1 -1
- package/dist-server/server/modules/database/repositories/sessions.db.js +173 -17
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/schema.js +26 -0
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +78 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -0
- package/dist-server/server/modules/database/{repositories → tests}/sessions.db.integration.test.js +10 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/notifications/index.js +4 -0
- package/dist-server/server/modules/notifications/index.js.map +1 -0
- package/dist-server/server/modules/notifications/notifications.routes.js +115 -0
- package/dist-server/server/modules/notifications/notifications.routes.js.map +1 -0
- package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js +97 -0
- package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js.map +1 -0
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +229 -0
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -0
- package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js +79 -0
- package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-management.service.js +0 -4
- package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +10 -38
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
- package/dist-server/server/modules/providers/index.js +1 -0
- package/dist-server/server/modules/providers/index.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +5 -0
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +24 -1
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +9 -12
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +7 -3
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +3 -9
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +13 -22
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +5 -8
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +6 -6
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +17 -3
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +9 -10
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +123 -1
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/mcp.service.js +23 -0
- package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js +65 -0
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-models.service.js +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +62 -23
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +62 -4
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/skills.service.js +11 -0
- package/dist-server/server/modules/providers/services/skills.service.js.map +1 -1
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js +163 -1
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +45 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/skills.test.js +160 -0
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
- package/dist-server/server/modules/websocket/index.js +1 -0
- package/dist-server/server/modules/websocket/index.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +252 -0
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +105 -0
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +218 -156
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/desktop-agent-websocket.service.js +39 -0
- package/dist-server/server/modules/websocket/services/desktop-agent-websocket.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +83 -31
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/websocket-server.service.js +10 -0
- package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -1
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +216 -0
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -0
- package/dist-server/server/openai-codex.js +24 -14
- package/dist-server/server/openai-codex.js.map +1 -1
- package/dist-server/server/opencode-cli.js +21 -8
- package/dist-server/server/opencode-cli.js.map +1 -1
- package/dist-server/server/opencode-cli.test.js +19 -1
- package/dist-server/server/opencode-cli.test.js.map +1 -1
- package/dist-server/server/routes/agent.js +1 -1
- package/dist-server/server/routes/settings.js +1 -1
- package/dist-server/server/routes/settings.js.map +1 -1
- package/dist-server/server/services/notification-orchestrator.js +1 -176
- package/dist-server/server/services/notification-orchestrator.js.map +1 -1
- package/dist-server/server/services/tests/notification-orchestrator.test.js +64 -0
- package/dist-server/server/services/tests/notification-orchestrator.test.js.map +1 -0
- package/dist-server/server/shared/{claude-cli-path.test.js → tests/claude-cli-path.test.js} +1 -1
- package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -0
- package/dist-server/server/shared/tests/slice-tail-page.test.js +35 -0
- package/dist-server/server/shared/tests/slice-tail-page.test.js.map +1 -0
- package/dist-server/server/shared/utils.js +73 -1
- package/dist-server/server/shared/utils.js.map +1 -1
- package/dist-server/server/voice-proxy.js +226 -0
- package/dist-server/server/voice-proxy.js.map +1 -0
- package/electron/assets/logo-macos.icns +0 -0
- package/electron/assets/logo-macos.png +0 -0
- package/electron/assets/logo-windows.ico +0 -0
- package/electron/cloud.js +260 -0
- package/electron/computerAgent.js +290 -0
- package/electron/desktopNotifications.js +378 -0
- package/electron/desktopWindow.js +781 -0
- package/electron/launcher/index.html +14 -0
- package/electron/launcher/launcher.css +801 -0
- package/electron/launcher/launcher.js +829 -0
- package/electron/localServer.js +550 -0
- package/electron/main.js +1060 -0
- package/electron/preload.cjs +63 -0
- package/electron/scripts/generate-macos-icon.js +62 -0
- package/electron/serverInstaller.js +277 -0
- package/electron/tabs.js +87 -0
- package/electron/viewHost.js +331 -0
- package/package.json +86 -5
- package/public/api-docs.html +36 -18
- package/scripts/build-computer-semantics.mjs +133 -0
- package/scripts/copy-computer-semantics-bin.mjs +24 -0
- package/scripts/release/build-server-bundle.js +176 -0
- package/scripts/release/prepare-desktop-app.js +158 -0
- package/server/browser-use-mcp.ts +384 -0
- package/server/claude-sdk.js +28 -6
- package/server/cli.js +15 -6
- package/server/computer-use-agent.ts +279 -0
- package/server/computer-use-mcp.ts +574 -0
- package/server/cursor-cli.js +27 -11
- package/server/gemini-cli.js +17 -2
- package/server/index.js +151 -47
- package/server/load-env.js +1 -1
- package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -0
- package/server/modules/browser-use/browser-use.routes.ts +96 -0
- package/server/modules/browser-use/browser-use.service.ts +836 -0
- package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -0
- package/server/modules/computer-use/actions/raw-action-dispatcher.ts +67 -0
- package/server/modules/computer-use/actions/raw-action-types.ts +28 -0
- package/server/modules/computer-use/computer-executor.ts +242 -0
- package/server/modules/computer-use/computer-semantics.service.ts +460 -0
- package/server/modules/computer-use/computer-use-mcp.routes.ts +141 -0
- package/server/modules/computer-use/computer-use.routes.ts +211 -0
- package/server/modules/computer-use/computer-use.service.ts +920 -0
- package/server/modules/computer-use/desktop-agent-relay.service.ts +158 -0
- package/server/modules/computer-use/index.ts +2 -0
- package/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.ts +82 -0
- package/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.ts +5 -0
- package/server/modules/computer-use/semantics/adapters/semantic-adapter.ts +23 -0
- package/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.ts +5 -0
- package/server/modules/computer-use/semantics/bin/darwin-arm64/CloudCLISemantics +0 -0
- package/server/modules/computer-use/semantics/bin/darwin-x64/CloudCLISemantics +0 -0
- package/server/modules/computer-use/semantics/bin/win32-arm64/CloudCLISemantics.exe +0 -0
- package/server/modules/computer-use/semantics/bin/win32-x64/CloudCLISemantics.exe +0 -0
- package/server/modules/computer-use/semantics/helpers/macos/CloudCLISemantics.swift +467 -0
- package/server/modules/computer-use/semantics/helpers/semantic-helper-process.ts +124 -0
- package/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.ts +97 -0
- package/server/modules/computer-use/semantics/helpers/windows/CloudCLISemantics.csproj +11 -0
- package/server/modules/computer-use/semantics/helpers/windows/Program.cs +534 -0
- package/server/modules/computer-use/semantics/semantic-session-store.ts +87 -0
- package/server/modules/computer-use/semantics/semantic-tool-dispatcher.ts +17 -0
- package/server/modules/computer-use/semantics/semantic-types.ts +58 -0
- package/server/modules/database/index.ts +1 -0
- package/server/modules/database/migrations.ts +25 -0
- package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -0
- package/server/modules/database/repositories/notification-preferences.ts +12 -1
- package/server/modules/database/repositories/sessions.db.ts +227 -23
- package/server/modules/database/schema.ts +27 -0
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +108 -0
- package/server/modules/database/{repositories → tests}/sessions.db.integration.test.ts +12 -0
- package/server/modules/notifications/index.ts +13 -0
- package/server/modules/notifications/notifications.routes.ts +127 -0
- package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -0
- package/server/modules/notifications/services/notification-orchestrator.service.js +288 -0
- package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -0
- package/server/modules/projects/services/project-management.service.ts +0 -8
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +12 -53
- package/server/modules/providers/README.md +1 -1
- package/server/modules/providers/index.ts +1 -0
- package/server/modules/providers/list/claude/claude-models.provider.ts +5 -0
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +26 -1
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +9 -14
- package/server/modules/providers/list/claude/claude-skills.provider.ts +8 -0
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +7 -3
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +3 -12
- package/server/modules/providers/list/codex/codex-skills.provider.ts +8 -0
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +13 -22
- package/server/modules/providers/list/cursor/cursor-skills.provider.ts +8 -0
- package/server/modules/providers/list/gemini/gemini-models.provider.ts +5 -8
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +6 -6
- package/server/modules/providers/list/gemini/gemini-skills.provider.ts +8 -0
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +18 -4
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +9 -12
- package/server/modules/providers/provider.routes.ts +167 -1
- package/server/modules/providers/services/mcp.service.ts +26 -0
- package/server/modules/providers/services/provider-capabilities.service.ts +91 -0
- package/server/modules/providers/services/provider-models.service.ts +1 -1
- package/server/modules/providers/services/sessions-watcher.service.ts +71 -25
- package/server/modules/providers/services/sessions.service.ts +79 -4
- package/server/modules/providers/services/skills.service.ts +25 -1
- package/server/modules/providers/shared/skills/skills.provider.ts +223 -0
- package/server/modules/providers/tests/opencode-sessions.test.ts +49 -0
- package/server/modules/providers/tests/skills.test.ts +212 -0
- package/server/modules/websocket/README.md +33 -27
- package/server/modules/websocket/index.ts +1 -0
- package/server/modules/websocket/services/chat-run-registry.service.ts +327 -0
- package/server/modules/websocket/services/chat-session-writer.service.ts +145 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +292 -207
- package/server/modules/websocket/services/desktop-agent-websocket.service.ts +51 -0
- package/server/modules/websocket/services/shell-websocket.service.ts +108 -32
- package/server/modules/websocket/services/websocket-server.service.ts +12 -0
- package/server/modules/websocket/tests/chat-run-registry.test.ts +244 -0
- package/server/openai-codex.js +24 -14
- package/server/opencode-cli.js +21 -8
- package/server/opencode-cli.test.js +20 -1
- package/server/routes/agent.js +1 -1
- package/server/routes/settings.js +6 -1
- package/server/services/notification-orchestrator.js +3 -224
- package/server/services/tests/notification-orchestrator.test.js +80 -0
- package/server/shared/interfaces.ts +15 -0
- package/server/shared/tests/slice-tail-page.test.ts +42 -0
- package/server/shared/types.ts +84 -1
- package/server/shared/utils.ts +95 -1
- package/server/voice-proxy.js +224 -0
- package/dist/assets/index-73Xw3i9v.css +0 -32
- package/dist/assets/index-9n7A21AT.js +0 -1361
- package/dist/modelConstants.js +0 -848
- package/dist-server/server/modules/database/repositories/projects.db.integration.test.js.map +0 -1
- package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js.map +0 -1
- package/dist-server/server/shared/claude-cli-path.test.js.map +0 -1
- package/public/modelConstants.js +0 -848
- /package/dist-server/server/modules/database/{repositories → tests}/projects.db.integration.test.js +0 -0
- /package/server/modules/database/{repositories → tests}/projects.db.integration.test.ts +0 -0
- /package/server/shared/{claude-cli-path.test.ts → tests/claude-cli-path.test.ts} +0 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import ApplicationServices
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
typealias JSON = [String: Any]
|
|
6
|
+
|
|
7
|
+
struct ElementRecord {
|
|
8
|
+
let index: String
|
|
9
|
+
let role: String
|
|
10
|
+
let title: String?
|
|
11
|
+
let value: String?
|
|
12
|
+
let bounds: [String: Double]?
|
|
13
|
+
let actions: [String]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
var stateElements: [String: [ElementRecord]] = [:]
|
|
17
|
+
var stateAxElements: [String: [String: AXUIElement]] = [:]
|
|
18
|
+
var stateOrder: [String] = []
|
|
19
|
+
let maxStoredStates = 100
|
|
20
|
+
|
|
21
|
+
func jsonLine(_ object: Any) {
|
|
22
|
+
guard JSONSerialization.isValidJSONObject(object),
|
|
23
|
+
let data = try? JSONSerialization.data(withJSONObject: object),
|
|
24
|
+
let text = String(data: data, encoding: .utf8)
|
|
25
|
+
else {
|
|
26
|
+
print("{\"error\":\"Failed to encode JSON\"}")
|
|
27
|
+
fflush(stdout)
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
print(text)
|
|
31
|
+
fflush(stdout)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func respond(id: Any?, result: Any) {
|
|
35
|
+
jsonLine(["id": id ?? NSNull(), "result": result])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func respondError(id: Any?, _ message: String) {
|
|
39
|
+
jsonLine(["id": id ?? NSNull(), "error": message])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func stringAttr(_ element: AXUIElement, _ attr: CFString) -> String? {
|
|
43
|
+
var value: CFTypeRef?
|
|
44
|
+
guard AXUIElementCopyAttributeValue(element, attr, &value) == .success else { return nil }
|
|
45
|
+
return value as? String
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func boolAttr(_ element: AXUIElement, _ attr: CFString) -> Bool? {
|
|
49
|
+
var value: CFTypeRef?
|
|
50
|
+
guard AXUIElementCopyAttributeValue(element, attr, &value) == .success else { return nil }
|
|
51
|
+
return value as? Bool
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func arrayAttr(_ element: AXUIElement, _ attr: CFString) -> [AXUIElement] {
|
|
55
|
+
var value: CFTypeRef?
|
|
56
|
+
guard AXUIElementCopyAttributeValue(element, attr, &value) == .success else { return [] }
|
|
57
|
+
return value as? [AXUIElement] ?? []
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func actions(_ element: AXUIElement) -> [String] {
|
|
61
|
+
var names: CFArray?
|
|
62
|
+
guard AXUIElementCopyActionNames(element, &names) == .success else { return [] }
|
|
63
|
+
return names as? [String] ?? []
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func bounds(_ element: AXUIElement) -> [String: Double]? {
|
|
67
|
+
var positionRef: CFTypeRef?
|
|
68
|
+
var sizeRef: CFTypeRef?
|
|
69
|
+
guard AXUIElementCopyAttributeValue(element, kAXPositionAttribute as CFString, &positionRef) == .success,
|
|
70
|
+
AXUIElementCopyAttributeValue(element, kAXSizeAttribute as CFString, &sizeRef) == .success,
|
|
71
|
+
let positionValue = positionRef,
|
|
72
|
+
let sizeValue = sizeRef
|
|
73
|
+
else { return nil }
|
|
74
|
+
|
|
75
|
+
var point = CGPoint.zero
|
|
76
|
+
var size = CGSize.zero
|
|
77
|
+
guard CFGetTypeID(positionValue) == AXValueGetTypeID(),
|
|
78
|
+
CFGetTypeID(sizeValue) == AXValueGetTypeID()
|
|
79
|
+
else { return nil }
|
|
80
|
+
|
|
81
|
+
let positionAxValue = positionValue as! AXValue
|
|
82
|
+
let sizeAxValue = sizeValue as! AXValue
|
|
83
|
+
guard AXValueGetValue(positionAxValue, .cgPoint, &point),
|
|
84
|
+
AXValueGetValue(sizeAxValue, .cgSize, &size)
|
|
85
|
+
else { return nil }
|
|
86
|
+
|
|
87
|
+
return [
|
|
88
|
+
"x": Double(point.x),
|
|
89
|
+
"y": Double(point.y),
|
|
90
|
+
"width": Double(size.width),
|
|
91
|
+
"height": Double(size.height),
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func record(_ element: AXUIElement, index: String) -> ElementRecord {
|
|
96
|
+
ElementRecord(
|
|
97
|
+
index: index,
|
|
98
|
+
role: stringAttr(element, kAXRoleAttribute as CFString) ?? "AXUnknown",
|
|
99
|
+
title: stringAttr(element, kAXTitleAttribute as CFString) ?? stringAttr(element, kAXDescriptionAttribute as CFString),
|
|
100
|
+
value: stringAttr(element, kAXValueAttribute as CFString),
|
|
101
|
+
bounds: bounds(element),
|
|
102
|
+
actions: actions(element)
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func cachedElement(_ params: JSON) -> AXUIElement? {
|
|
107
|
+
guard let stateId = params["stateId"] as? String,
|
|
108
|
+
let elementIndex = params["element_index"] as? String
|
|
109
|
+
else {
|
|
110
|
+
return nil
|
|
111
|
+
}
|
|
112
|
+
return stateAxElements[stateId]?[elementIndex]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
func dictionary(_ record: ElementRecord) -> JSON {
|
|
116
|
+
var output: JSON = [
|
|
117
|
+
"index": record.index,
|
|
118
|
+
"role": record.role,
|
|
119
|
+
"actions": record.actions,
|
|
120
|
+
]
|
|
121
|
+
if let title = record.title { output["title"] = title }
|
|
122
|
+
if let value = record.value { output["value"] = value }
|
|
123
|
+
if let bounds = record.bounds { output["bounds"] = bounds }
|
|
124
|
+
return output
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
func pruneStoredStates() {
|
|
128
|
+
while stateOrder.count > maxStoredStates {
|
|
129
|
+
let evicted = stateOrder.removeFirst()
|
|
130
|
+
stateElements.removeValue(forKey: evicted)
|
|
131
|
+
stateAxElements.removeValue(forKey: evicted)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
func resolveApp(_ query: String) throws -> NSRunningApplication {
|
|
136
|
+
let normalized = query.lowercased()
|
|
137
|
+
let apps = NSWorkspace.shared.runningApplications.filter { app in
|
|
138
|
+
app.activationPolicy == .regular
|
|
139
|
+
}
|
|
140
|
+
if let app = apps.first(where: { $0.bundleIdentifier?.lowercased() == normalized }) {
|
|
141
|
+
return app
|
|
142
|
+
}
|
|
143
|
+
if let app = apps.first(where: { ($0.localizedName ?? "").lowercased() == normalized }) {
|
|
144
|
+
return app
|
|
145
|
+
}
|
|
146
|
+
if let app = apps.first(where: { ($0.localizedName ?? "").lowercased().contains(normalized) }) {
|
|
147
|
+
return app
|
|
148
|
+
}
|
|
149
|
+
throw NSError(domain: "CloudCLISemantics", code: 404, userInfo: [NSLocalizedDescriptionKey: "App is not running: \(query)"])
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
func listApps() -> [[String: Any]] {
|
|
153
|
+
NSWorkspace.shared.runningApplications
|
|
154
|
+
.filter { $0.activationPolicy == .regular }
|
|
155
|
+
.map { app in
|
|
156
|
+
[
|
|
157
|
+
"id": app.bundleIdentifier ?? app.localizedName ?? "\(app.processIdentifier)",
|
|
158
|
+
"name": app.localizedName ?? app.bundleIdentifier ?? "Unknown",
|
|
159
|
+
"bundleIdentifier": app.bundleIdentifier ?? "",
|
|
160
|
+
"pid": Int(app.processIdentifier),
|
|
161
|
+
"running": true,
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
func walk(_ element: AXUIElement, depth: Int, maxDepth: Int, records: inout [ElementRecord], axRecords: inout [String: AXUIElement], limit: Int) {
|
|
167
|
+
if depth > maxDepth || records.count >= limit { return }
|
|
168
|
+
let index = "\(records.count + 1)"
|
|
169
|
+
records.append(record(element, index: index))
|
|
170
|
+
axRecords[index] = element
|
|
171
|
+
for child in arrayAttr(element, kAXChildrenAttribute as CFString) {
|
|
172
|
+
walk(child, depth: depth + 1, maxDepth: maxDepth, records: &records, axRecords: &axRecords, limit: limit)
|
|
173
|
+
if records.count >= limit { return }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
func pngDataUrlForMainDisplay() -> String? {
|
|
178
|
+
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("cloudcli-semantics-\(UUID().uuidString).png")
|
|
179
|
+
let process = Process()
|
|
180
|
+
process.executableURL = URL(fileURLWithPath: "/usr/sbin/screencapture")
|
|
181
|
+
process.arguments = ["-x", "-t", "png", fileURL.path]
|
|
182
|
+
|
|
183
|
+
do {
|
|
184
|
+
try process.run()
|
|
185
|
+
process.waitUntilExit()
|
|
186
|
+
guard process.terminationStatus == 0 else { return nil }
|
|
187
|
+
let png = try Data(contentsOf: fileURL)
|
|
188
|
+
try? FileManager.default.removeItem(at: fileURL)
|
|
189
|
+
return png.isEmpty ? nil : "data:image/png;base64,\(png.base64EncodedString())"
|
|
190
|
+
} catch {
|
|
191
|
+
try? FileManager.default.removeItem(at: fileURL)
|
|
192
|
+
return nil
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
func getAppState(_ params: JSON) throws -> JSON {
|
|
197
|
+
let appName = params["app"] as? String ?? ""
|
|
198
|
+
let app = try resolveApp(appName)
|
|
199
|
+
let axApp = AXUIElementCreateApplication(app.processIdentifier)
|
|
200
|
+
let windows = arrayAttr(axApp, kAXWindowsAttribute as CFString)
|
|
201
|
+
let root = windows.first ?? axApp
|
|
202
|
+
var records: [ElementRecord] = []
|
|
203
|
+
var axRecords: [String: AXUIElement] = [:]
|
|
204
|
+
walk(root, depth: 0, maxDepth: 5, records: &records, axRecords: &axRecords, limit: 300)
|
|
205
|
+
let stateId = "state_\(UUID().uuidString)"
|
|
206
|
+
stateElements[stateId] = records
|
|
207
|
+
stateAxElements[stateId] = axRecords
|
|
208
|
+
stateOrder.append(stateId)
|
|
209
|
+
pruneStoredStates()
|
|
210
|
+
|
|
211
|
+
let elements = records.map(dictionary)
|
|
212
|
+
return [
|
|
213
|
+
"stateId": stateId,
|
|
214
|
+
"app": app.localizedName ?? app.bundleIdentifier ?? appName,
|
|
215
|
+
"platform": "darwin",
|
|
216
|
+
"screenshotDataUrl": pngDataUrlForMainDisplay() ?? NSNull(),
|
|
217
|
+
"displaySize": [
|
|
218
|
+
"width": Int(CGDisplayPixelsWide(CGMainDisplayID())),
|
|
219
|
+
"height": Int(CGDisplayPixelsHigh(CGMainDisplayID())),
|
|
220
|
+
],
|
|
221
|
+
"elements": elements,
|
|
222
|
+
"accessibilityTree": elements,
|
|
223
|
+
"treeText": elements.map { "\($0["index"] ?? "") \($0["role"] ?? "") \($0["title"] ?? "")" }.joined(separator: "\n"),
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
func cgMouseButton(_ value: Any?) -> CGMouseButton {
|
|
228
|
+
guard let button = value as? String else { return .left }
|
|
229
|
+
switch button {
|
|
230
|
+
case "right": return .right
|
|
231
|
+
case "middle": return .center
|
|
232
|
+
default: return .left
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
func mouseEventTypes(_ button: CGMouseButton) -> (CGEventType, CGEventType) {
|
|
237
|
+
switch button {
|
|
238
|
+
case .right: return (.rightMouseDown, .rightMouseUp)
|
|
239
|
+
case .center: return (.otherMouseDown, .otherMouseUp)
|
|
240
|
+
default: return (.leftMouseDown, .leftMouseUp)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
func postMouseClick(point: CGPoint, button: CGMouseButton, clickCount: Int = 1) throws {
|
|
245
|
+
guard let source = CGEventSource(stateID: .combinedSessionState) else {
|
|
246
|
+
throw NSError(domain: "CloudCLISemantics", code: 500, userInfo: [NSLocalizedDescriptionKey: "Failed to create CGEventSource"])
|
|
247
|
+
}
|
|
248
|
+
let eventTypes = mouseEventTypes(button)
|
|
249
|
+
for _ in 0..<max(1, clickCount) {
|
|
250
|
+
let down = CGEvent(mouseEventSource: source, mouseType: eventTypes.0, mouseCursorPosition: point, mouseButton: button)
|
|
251
|
+
let up = CGEvent(mouseEventSource: source, mouseType: eventTypes.1, mouseCursorPosition: point, mouseButton: button)
|
|
252
|
+
down?.post(tap: .cghidEventTap)
|
|
253
|
+
up?.post(tap: .cghidEventTap)
|
|
254
|
+
usleep(80_000)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
func postDrag(from: CGPoint, to: CGPoint, button: CGMouseButton) throws {
|
|
259
|
+
guard let source = CGEventSource(stateID: .combinedSessionState) else {
|
|
260
|
+
throw NSError(domain: "CloudCLISemantics", code: 500, userInfo: [NSLocalizedDescriptionKey: "Failed to create CGEventSource"])
|
|
261
|
+
}
|
|
262
|
+
let eventTypes = mouseEventTypes(button)
|
|
263
|
+
CGEvent(mouseEventSource: source, mouseType: eventTypes.0, mouseCursorPosition: from, mouseButton: button)?.post(tap: .cghidEventTap)
|
|
264
|
+
usleep(80_000)
|
|
265
|
+
CGEvent(mouseEventSource: source, mouseType: .leftMouseDragged, mouseCursorPosition: to, mouseButton: button)?.post(tap: .cghidEventTap)
|
|
266
|
+
usleep(80_000)
|
|
267
|
+
CGEvent(mouseEventSource: source, mouseType: eventTypes.1, mouseCursorPosition: to, mouseButton: button)?.post(tap: .cghidEventTap)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
func runAppleScript(_ script: String) throws {
|
|
271
|
+
let process = Process()
|
|
272
|
+
process.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
|
|
273
|
+
process.arguments = ["-e", script]
|
|
274
|
+
process.standardOutput = Pipe()
|
|
275
|
+
let stderr = Pipe()
|
|
276
|
+
process.standardError = stderr
|
|
277
|
+
try process.run()
|
|
278
|
+
process.waitUntilExit()
|
|
279
|
+
if process.terminationStatus != 0 {
|
|
280
|
+
let data = stderr.fileHandleForReading.readDataToEndOfFile()
|
|
281
|
+
let message = String(data: data, encoding: .utf8) ?? "AppleScript failed."
|
|
282
|
+
throw NSError(domain: "CloudCLISemantics", code: Int(process.terminationStatus), userInfo: [NSLocalizedDescriptionKey: message])
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
func escapedAppleScriptString(_ value: String) -> String {
|
|
287
|
+
value.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\"")
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
func pointForElement(_ params: JSON) -> CGPoint? {
|
|
291
|
+
if let x = params["x"] as? Double, let y = params["y"] as? Double {
|
|
292
|
+
return CGPoint(x: x, y: y)
|
|
293
|
+
}
|
|
294
|
+
guard let stateId = params["stateId"] as? String,
|
|
295
|
+
let elementIndex = params["element_index"] as? String,
|
|
296
|
+
let element = stateElements[stateId]?.first(where: { $0.index == elementIndex }),
|
|
297
|
+
let b = element.bounds,
|
|
298
|
+
let x = b["x"], let y = b["y"], let width = b["width"], let height = b["height"]
|
|
299
|
+
else {
|
|
300
|
+
return nil
|
|
301
|
+
}
|
|
302
|
+
return CGPoint(x: x + width / 2, y: y + height / 2)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
func click(_ params: JSON) throws -> JSON {
|
|
306
|
+
if let element = cachedElement(params),
|
|
307
|
+
cgMouseButton(params["mouse_button"]) == .left,
|
|
308
|
+
(params["click_count"] as? Int ?? 1) == 1,
|
|
309
|
+
actions(element).contains(kAXPressAction as String),
|
|
310
|
+
AXUIElementPerformAction(element, kAXPressAction as CFString) == .success {
|
|
311
|
+
return try getAppState(params)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
guard let point = pointForElement(params) else {
|
|
315
|
+
throw NSError(domain: "CloudCLISemantics", code: 400, userInfo: [NSLocalizedDescriptionKey: "click_element requires x/y or stateId + element_index"])
|
|
316
|
+
}
|
|
317
|
+
let clickCount = params["click_count"] as? Int ?? 1
|
|
318
|
+
try postMouseClick(point: point, button: cgMouseButton(params["mouse_button"]), clickCount: clickCount)
|
|
319
|
+
return try getAppState(params)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
func performSecondaryAction(_ params: JSON) throws -> JSON {
|
|
323
|
+
if let element = cachedElement(params),
|
|
324
|
+
actions(element).contains(kAXShowMenuAction as String),
|
|
325
|
+
AXUIElementPerformAction(element, kAXShowMenuAction as CFString) == .success {
|
|
326
|
+
return try getAppState(params)
|
|
327
|
+
}
|
|
328
|
+
guard let point = pointForElement(params) else {
|
|
329
|
+
throw NSError(domain: "CloudCLISemantics", code: 400, userInfo: [NSLocalizedDescriptionKey: "perform_secondary_action requires x/y or stateId + element_index"])
|
|
330
|
+
}
|
|
331
|
+
try postMouseClick(point: point, button: .right)
|
|
332
|
+
return try getAppState(params)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
func setValue(_ params: JSON) throws -> JSON {
|
|
336
|
+
guard let value = params["value"] as? String else {
|
|
337
|
+
throw NSError(domain: "CloudCLISemantics", code: 400, userInfo: [NSLocalizedDescriptionKey: "set_value requires value"])
|
|
338
|
+
}
|
|
339
|
+
if let element = cachedElement(params),
|
|
340
|
+
AXUIElementSetAttributeValue(element, kAXValueAttribute as CFString, value as CFTypeRef) == .success {
|
|
341
|
+
return try getAppState(params)
|
|
342
|
+
}
|
|
343
|
+
guard let point = pointForElement(params) else {
|
|
344
|
+
throw NSError(domain: "CloudCLISemantics", code: 400, userInfo: [NSLocalizedDescriptionKey: "set_value requires x/y or stateId + element_index"])
|
|
345
|
+
}
|
|
346
|
+
try postMouseClick(point: point, button: .left)
|
|
347
|
+
try runAppleScript("tell application \"System Events\" to keystroke \"a\" using command down")
|
|
348
|
+
try runAppleScript("tell application \"System Events\" to keystroke \"\(escapedAppleScriptString(value))\"")
|
|
349
|
+
return try getAppState(params)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
func typeText(_ params: JSON) throws -> JSON {
|
|
353
|
+
let text = params["text"] as? String ?? ""
|
|
354
|
+
try runAppleScript("tell application \"System Events\" to keystroke \"\(escapedAppleScriptString(text))\"")
|
|
355
|
+
return try getAppState(params)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
func appleScriptModifiers(_ parts: [String]) -> String {
|
|
359
|
+
let modifiers = parts.compactMap { part -> String? in
|
|
360
|
+
switch part.lowercased() {
|
|
361
|
+
case "cmd", "command", "meta": return "command down"
|
|
362
|
+
case "ctrl", "control": return "control down"
|
|
363
|
+
case "alt", "option": return "option down"
|
|
364
|
+
case "shift": return "shift down"
|
|
365
|
+
default: return nil
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return modifiers.isEmpty ? "" : " using {\(modifiers.joined(separator: ", "))}"
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
func appleScriptKeyCode(_ key: String) -> Int? {
|
|
372
|
+
switch key.lowercased() {
|
|
373
|
+
case "return", "enter": return 36
|
|
374
|
+
case "tab": return 48
|
|
375
|
+
case "space": return 49
|
|
376
|
+
case "delete", "backspace": return 51
|
|
377
|
+
case "escape", "esc": return 53
|
|
378
|
+
case "left": return 123
|
|
379
|
+
case "right": return 124
|
|
380
|
+
case "down": return 125
|
|
381
|
+
case "up": return 126
|
|
382
|
+
default: return nil
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
func pressKey(_ params: JSON) throws -> JSON {
|
|
387
|
+
let raw = params["key"] as? String ?? ""
|
|
388
|
+
let parts = raw.split(separator: "+").map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) }.filter { !$0.isEmpty }
|
|
389
|
+
let key = parts.last ?? raw
|
|
390
|
+
let modifiers = appleScriptModifiers(Array(parts.dropLast()))
|
|
391
|
+
if let keyCode = appleScriptKeyCode(key) {
|
|
392
|
+
try runAppleScript("tell application \"System Events\" to key code \(keyCode)\(modifiers)")
|
|
393
|
+
} else {
|
|
394
|
+
try runAppleScript("tell application \"System Events\" to keystroke \"\(escapedAppleScriptString(key))\"\(modifiers)")
|
|
395
|
+
}
|
|
396
|
+
return try getAppState(params)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
func scrollElement(_ params: JSON) throws -> JSON {
|
|
400
|
+
guard let point = pointForElement(params) else {
|
|
401
|
+
throw NSError(domain: "CloudCLISemantics", code: 400, userInfo: [NSLocalizedDescriptionKey: "scroll_element requires x/y or stateId + element_index"])
|
|
402
|
+
}
|
|
403
|
+
CGWarpMouseCursorPosition(point)
|
|
404
|
+
let direction = params["direction"] as? String ?? "down"
|
|
405
|
+
let pages = params["pages"] as? Double ?? 1.0
|
|
406
|
+
let amount = Int32(max(1.0, abs(pages) * 8.0))
|
|
407
|
+
let vertical = direction == "up" ? amount : direction == "down" ? -amount : 0
|
|
408
|
+
let horizontal = direction == "left" ? amount : direction == "right" ? -amount : 0
|
|
409
|
+
CGEvent(scrollWheelEvent2Source: nil, units: .line, wheelCount: 2, wheel1: vertical, wheel2: horizontal, wheel3: 0)?.post(tap: .cghidEventTap)
|
|
410
|
+
return try getAppState(params)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
func drag(_ params: JSON) throws -> JSON {
|
|
414
|
+
guard let fromX = params["from_x"] as? Double,
|
|
415
|
+
let fromY = params["from_y"] as? Double,
|
|
416
|
+
let toX = params["to_x"] as? Double,
|
|
417
|
+
let toY = params["to_y"] as? Double
|
|
418
|
+
else {
|
|
419
|
+
throw NSError(domain: "CloudCLISemantics", code: 400, userInfo: [NSLocalizedDescriptionKey: "drag requires from_x/from_y/to_x/to_y"])
|
|
420
|
+
}
|
|
421
|
+
try postDrag(from: CGPoint(x: fromX, y: fromY), to: CGPoint(x: toX, y: toY), button: cgMouseButton(params["mouse_button"]))
|
|
422
|
+
return try getAppState(params)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
func handle(_ request: JSON) {
|
|
426
|
+
let id = request["id"]
|
|
427
|
+
let method = request["method"] as? String ?? ""
|
|
428
|
+
let params = request["params"] as? JSON ?? [:]
|
|
429
|
+
|
|
430
|
+
do {
|
|
431
|
+
switch method {
|
|
432
|
+
case "list_apps":
|
|
433
|
+
respond(id: id, result: listApps())
|
|
434
|
+
case "get_app_state":
|
|
435
|
+
respond(id: id, result: try getAppState(params))
|
|
436
|
+
case "click_element":
|
|
437
|
+
respond(id: id, result: try click(params))
|
|
438
|
+
case "perform_secondary_action":
|
|
439
|
+
respond(id: id, result: try performSecondaryAction(params))
|
|
440
|
+
case "set_value":
|
|
441
|
+
respond(id: id, result: try setValue(params))
|
|
442
|
+
case "type_text":
|
|
443
|
+
respond(id: id, result: try typeText(params))
|
|
444
|
+
case "press_key":
|
|
445
|
+
respond(id: id, result: try pressKey(params))
|
|
446
|
+
case "scroll_element":
|
|
447
|
+
respond(id: id, result: try scrollElement(params))
|
|
448
|
+
case "drag":
|
|
449
|
+
respond(id: id, result: try drag(params))
|
|
450
|
+
default:
|
|
451
|
+
respondError(id: id, "Method is not implemented yet: \(method)")
|
|
452
|
+
}
|
|
453
|
+
} catch {
|
|
454
|
+
respondError(id: id, error.localizedDescription)
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
while let line = readLine() {
|
|
459
|
+
guard let data = line.data(using: .utf8),
|
|
460
|
+
let object = try? JSONSerialization.jsonObject(with: data),
|
|
461
|
+
let request = object as? JSON
|
|
462
|
+
else {
|
|
463
|
+
respondError(id: nil, "Invalid JSON request")
|
|
464
|
+
continue
|
|
465
|
+
}
|
|
466
|
+
handle(request)
|
|
467
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
|
+
import readline from 'node:readline';
|
|
3
|
+
|
|
4
|
+
type JsonRecord = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
type PendingRequest = {
|
|
7
|
+
resolve: (value: unknown) => void;
|
|
8
|
+
reject: (error: Error) => void;
|
|
9
|
+
timer: ReturnType<typeof setTimeout>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const DEFAULT_TIMEOUT_MS = Number.parseInt(process.env.CLOUDCLI_SEMANTICS_HELPER_TIMEOUT_MS || '60000', 10);
|
|
13
|
+
|
|
14
|
+
function timeoutMs(): number {
|
|
15
|
+
return Number.isFinite(DEFAULT_TIMEOUT_MS) && DEFAULT_TIMEOUT_MS > 0 ? DEFAULT_TIMEOUT_MS : 60000;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function errorMessage(error: unknown): string {
|
|
19
|
+
return error instanceof Error ? error.message : String(error);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class SemanticHelperProcess {
|
|
23
|
+
private child: ChildProcessWithoutNullStreams | null = null;
|
|
24
|
+
private reader: readline.Interface | null = null;
|
|
25
|
+
private nextId = 1;
|
|
26
|
+
private pending = new Map<number, PendingRequest>();
|
|
27
|
+
|
|
28
|
+
constructor(private readonly executablePath: string) {}
|
|
29
|
+
|
|
30
|
+
async request(method: string, params: JsonRecord): Promise<unknown> {
|
|
31
|
+
this.ensureStarted();
|
|
32
|
+
const child = this.child;
|
|
33
|
+
if (!child?.stdin.writable) {
|
|
34
|
+
throw new Error('Semantic helper process is not running.');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const id = this.nextId++;
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const timer = setTimeout(() => {
|
|
40
|
+
this.pending.delete(id);
|
|
41
|
+
reject(new Error(`Semantic helper request timed out: ${method}`));
|
|
42
|
+
}, timeoutMs());
|
|
43
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
44
|
+
child.stdin.write(`${JSON.stringify({ id, method, params })}\n`);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
stop(): void {
|
|
49
|
+
const child = this.child;
|
|
50
|
+
this.child = null;
|
|
51
|
+
this.reader?.close();
|
|
52
|
+
this.reader = null;
|
|
53
|
+
this.rejectAll('Semantic helper stopped.');
|
|
54
|
+
if (child) {
|
|
55
|
+
try { child.kill('SIGTERM'); } catch { /* noop */ }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private ensureStarted(): void {
|
|
60
|
+
if (this.child) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.child = spawn(this.executablePath, [], {
|
|
65
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
66
|
+
windowsHide: true,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
this.reader = readline.createInterface({ input: this.child.stdout });
|
|
70
|
+
this.reader.on('line', (line) => this.handleLine(line));
|
|
71
|
+
|
|
72
|
+
this.child.stderr.on('data', (chunk) => {
|
|
73
|
+
const text = String(chunk).trim();
|
|
74
|
+
if (text) {
|
|
75
|
+
console.error('[SemanticHelper]', text);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
this.child.once('error', (error) => {
|
|
80
|
+
this.child = null;
|
|
81
|
+
this.rejectAll(`Failed to start semantic helper: ${error.message}`);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
this.child.once('exit', (code) => {
|
|
85
|
+
this.child = null;
|
|
86
|
+
this.rejectAll(`Semantic helper exited with code ${code ?? 'null'}.`);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private handleLine(line: string): void {
|
|
91
|
+
let message: JsonRecord;
|
|
92
|
+
try {
|
|
93
|
+
message = JSON.parse(line) as JsonRecord;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('[SemanticHelper] Invalid JSON response:', errorMessage(error));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const id = typeof message.id === 'number' ? message.id : null;
|
|
100
|
+
if (id === null) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const pending = this.pending.get(id);
|
|
104
|
+
if (!pending) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
clearTimeout(pending.timer);
|
|
108
|
+
this.pending.delete(id);
|
|
109
|
+
|
|
110
|
+
if (message.error) {
|
|
111
|
+
pending.reject(new Error(typeof message.error === 'string' ? message.error : 'Semantic helper request failed.'));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
pending.resolve(message.result);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private rejectAll(reason: string): void {
|
|
118
|
+
for (const [id, request] of this.pending.entries()) {
|
|
119
|
+
clearTimeout(request.timer);
|
|
120
|
+
request.reject(new Error(reason));
|
|
121
|
+
this.pending.delete(id);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
export type SemanticHelperPlatform = 'darwin' | 'win32';
|
|
8
|
+
|
|
9
|
+
export type SemanticHelperResolution = {
|
|
10
|
+
available: boolean;
|
|
11
|
+
path: string | null;
|
|
12
|
+
source: 'bundled' | 'dev' | 'missing';
|
|
13
|
+
platform: NodeJS.Platform;
|
|
14
|
+
arch: NodeJS.Architecture;
|
|
15
|
+
reason?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function helperExecutableName(platform: NodeJS.Platform): string | null {
|
|
19
|
+
if (platform === 'darwin') {
|
|
20
|
+
return 'CloudCLISemantics';
|
|
21
|
+
}
|
|
22
|
+
if (platform === 'win32') {
|
|
23
|
+
return 'CloudCLISemantics.exe';
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function pathExists(filePath: string): boolean {
|
|
29
|
+
try {
|
|
30
|
+
fs.accessSync(filePath, fs.constants.X_OK);
|
|
31
|
+
return true;
|
|
32
|
+
} catch {
|
|
33
|
+
try {
|
|
34
|
+
fs.accessSync(filePath, fs.constants.F_OK);
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function candidatePaths(platform: NodeJS.Platform, arch: NodeJS.Architecture): Array<{ source: 'bundled' | 'dev'; path: string }> {
|
|
43
|
+
const executable = helperExecutableName(platform);
|
|
44
|
+
if (!executable) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const platformArch = `${platform}-${arch}`;
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
source: 'bundled',
|
|
52
|
+
path: path.resolve(__dirname, '..', 'bin', platformArch, executable),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
source: 'dev',
|
|
56
|
+
path: path.resolve(process.cwd(), 'server', 'modules', 'computer-use', 'semantics', 'bin', platformArch, executable),
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function resolveSemanticHelper(
|
|
62
|
+
platform: NodeJS.Platform = process.platform,
|
|
63
|
+
arch: NodeJS.Architecture = process.arch,
|
|
64
|
+
): SemanticHelperResolution {
|
|
65
|
+
const executable = helperExecutableName(platform);
|
|
66
|
+
if (!executable) {
|
|
67
|
+
return {
|
|
68
|
+
available: false,
|
|
69
|
+
path: null,
|
|
70
|
+
source: 'missing',
|
|
71
|
+
platform,
|
|
72
|
+
arch,
|
|
73
|
+
reason: `Semantic Computer Use helper is not supported on ${platform}.`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const candidate of candidatePaths(platform, arch)) {
|
|
78
|
+
if (pathExists(candidate.path)) {
|
|
79
|
+
return {
|
|
80
|
+
available: true,
|
|
81
|
+
path: candidate.path,
|
|
82
|
+
source: candidate.source,
|
|
83
|
+
platform,
|
|
84
|
+
arch,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
available: false,
|
|
91
|
+
path: null,
|
|
92
|
+
source: 'missing',
|
|
93
|
+
platform,
|
|
94
|
+
arch,
|
|
95
|
+
reason: `Bundled semantic helper was not found for ${platform}-${arch} (${executable}).`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
2
|
+
<PropertyGroup>
|
|
3
|
+
<OutputType>Exe</OutputType>
|
|
4
|
+
<TargetFramework>net8.0-windows</TargetFramework>
|
|
5
|
+
<Nullable>enable</Nullable>
|
|
6
|
+
<ImplicitUsings>enable</ImplicitUsings>
|
|
7
|
+
<UseWindowsForms>true</UseWindowsForms>
|
|
8
|
+
<UseWPF>true</UseWPF>
|
|
9
|
+
<AssemblyName>CloudCLISemantics</AssemblyName>
|
|
10
|
+
</PropertyGroup>
|
|
11
|
+
</Project>
|