@cloudcli-ai/cloudcli 1.34.0 → 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.
Files changed (324) hide show
  1. package/README.de.md +9 -1
  2. package/README.ja.md +8 -0
  3. package/README.ko.md +9 -1
  4. package/README.md +16 -3
  5. package/README.ru.md +9 -1
  6. package/README.tr.md +8 -1
  7. package/README.zh-CN.md +9 -1
  8. package/README.zh-TW.md +9 -1
  9. package/dist/api-docs.html +36 -18
  10. package/dist/assets/index-CtwxqzGG.js +1407 -0
  11. package/dist/assets/index-DlvtxFyt.css +32 -0
  12. package/dist/index.html +2 -2
  13. package/dist-server/server/browser-use-mcp.js +350 -0
  14. package/dist-server/server/browser-use-mcp.js.map +1 -0
  15. package/dist-server/server/claude-sdk.js +25 -5
  16. package/dist-server/server/claude-sdk.js.map +1 -1
  17. package/dist-server/server/cli.js +14 -6
  18. package/dist-server/server/cli.js.map +1 -1
  19. package/dist-server/server/computer-use-agent.js +242 -0
  20. package/dist-server/server/computer-use-agent.js.map +1 -0
  21. package/dist-server/server/computer-use-mcp.js +519 -0
  22. package/dist-server/server/computer-use-mcp.js.map +1 -0
  23. package/dist-server/server/cursor-cli.js +27 -11
  24. package/dist-server/server/cursor-cli.js.map +1 -1
  25. package/dist-server/server/gemini-cli.js +17 -2
  26. package/dist-server/server/gemini-cli.js.map +1 -1
  27. package/dist-server/server/index.js +149 -40
  28. package/dist-server/server/index.js.map +1 -1
  29. package/dist-server/server/load-env.js +1 -1
  30. package/dist-server/server/load-env.js.map +1 -1
  31. package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js +107 -0
  32. package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js.map +1 -0
  33. package/dist-server/server/modules/browser-use/browser-use.routes.js +93 -0
  34. package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -0
  35. package/dist-server/server/modules/browser-use/browser-use.service.js +688 -0
  36. package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -0
  37. package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js +8 -0
  38. package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js.map +1 -0
  39. package/dist-server/server/modules/computer-use/actions/raw-action-dispatcher.js +55 -0
  40. package/dist-server/server/modules/computer-use/actions/raw-action-dispatcher.js.map +1 -0
  41. package/dist-server/server/modules/computer-use/actions/raw-action-types.js +2 -0
  42. package/dist-server/server/modules/computer-use/actions/raw-action-types.js.map +1 -0
  43. package/dist-server/server/modules/computer-use/computer-executor.js +212 -0
  44. package/dist-server/server/modules/computer-use/computer-executor.js.map +1 -0
  45. package/dist-server/server/modules/computer-use/computer-semantics.service.js +422 -0
  46. package/dist-server/server/modules/computer-use/computer-semantics.service.js.map +1 -0
  47. package/dist-server/server/modules/computer-use/computer-use-mcp.routes.js +126 -0
  48. package/dist-server/server/modules/computer-use/computer-use-mcp.routes.js.map +1 -0
  49. package/dist-server/server/modules/computer-use/computer-use.routes.js +196 -0
  50. package/dist-server/server/modules/computer-use/computer-use.routes.js.map +1 -0
  51. package/dist-server/server/modules/computer-use/computer-use.service.js +759 -0
  52. package/dist-server/server/modules/computer-use/computer-use.service.js.map +1 -0
  53. package/dist-server/server/modules/computer-use/desktop-agent-relay.service.js +126 -0
  54. package/dist-server/server/modules/computer-use/desktop-agent-relay.service.js.map +1 -0
  55. package/dist-server/server/modules/computer-use/index.js +3 -0
  56. package/dist-server/server/modules/computer-use/index.js.map +1 -0
  57. package/dist-server/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.js +59 -0
  58. package/dist-server/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.js.map +1 -0
  59. package/dist-server/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.js +5 -0
  60. package/dist-server/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.js.map +1 -0
  61. package/dist-server/server/modules/computer-use/semantics/adapters/semantic-adapter.js +2 -0
  62. package/dist-server/server/modules/computer-use/semantics/adapters/semantic-adapter.js.map +1 -0
  63. package/dist-server/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.js +5 -0
  64. package/dist-server/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.js.map +1 -0
  65. package/dist-server/server/modules/computer-use/semantics/bin/darwin-arm64/CloudCLISemantics +0 -0
  66. package/dist-server/server/modules/computer-use/semantics/bin/darwin-x64/CloudCLISemantics +0 -0
  67. package/dist-server/server/modules/computer-use/semantics/bin/win32-arm64/CloudCLISemantics.exe +0 -0
  68. package/dist-server/server/modules/computer-use/semantics/bin/win32-x64/CloudCLISemantics.exe +0 -0
  69. package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-process.js +106 -0
  70. package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-process.js.map +1 -0
  71. package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.js +78 -0
  72. package/dist-server/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.js.map +1 -0
  73. package/dist-server/server/modules/computer-use/semantics/semantic-session-store.js +68 -0
  74. package/dist-server/server/modules/computer-use/semantics/semantic-session-store.js.map +1 -0
  75. package/dist-server/server/modules/computer-use/semantics/semantic-tool-dispatcher.js +16 -0
  76. package/dist-server/server/modules/computer-use/semantics/semantic-tool-dispatcher.js.map +1 -0
  77. package/dist-server/server/modules/computer-use/semantics/semantic-types.js +2 -0
  78. package/dist-server/server/modules/computer-use/semantics/semantic-types.js.map +1 -0
  79. package/dist-server/server/modules/database/index.js +1 -0
  80. package/dist-server/server/modules/database/index.js.map +1 -1
  81. package/dist-server/server/modules/database/migrations.js +23 -1
  82. package/dist-server/server/modules/database/migrations.js.map +1 -1
  83. package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +100 -0
  84. package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js.map +1 -0
  85. package/dist-server/server/modules/database/repositories/notification-preferences.js +8 -0
  86. package/dist-server/server/modules/database/repositories/notification-preferences.js.map +1 -1
  87. package/dist-server/server/modules/database/repositories/sessions.db.js +173 -17
  88. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  89. package/dist-server/server/modules/database/schema.js +26 -0
  90. package/dist-server/server/modules/database/schema.js.map +1 -1
  91. package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -0
  92. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +78 -0
  93. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -0
  94. package/dist-server/server/modules/database/{repositories → tests}/sessions.db.integration.test.js +10 -0
  95. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -0
  96. package/dist-server/server/modules/notifications/index.js +4 -0
  97. package/dist-server/server/modules/notifications/index.js.map +1 -0
  98. package/dist-server/server/modules/notifications/notifications.routes.js +115 -0
  99. package/dist-server/server/modules/notifications/notifications.routes.js.map +1 -0
  100. package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js +97 -0
  101. package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js.map +1 -0
  102. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +229 -0
  103. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -0
  104. package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js +79 -0
  105. package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js.map +1 -0
  106. package/dist-server/server/modules/projects/services/project-management.service.js +0 -4
  107. package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -1
  108. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +10 -38
  109. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
  110. package/dist-server/server/modules/providers/index.js +1 -0
  111. package/dist-server/server/modules/providers/index.js.map +1 -1
  112. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +24 -1
  113. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
  114. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +9 -12
  115. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  116. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +7 -0
  117. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
  118. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +7 -3
  119. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  120. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +3 -9
  121. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  122. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +7 -0
  123. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -1
  124. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +13 -22
  125. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
  126. package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +7 -0
  127. package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -1
  128. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +5 -8
  129. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +1 -1
  130. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +6 -6
  131. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
  132. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +7 -0
  133. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +1 -1
  134. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +17 -3
  135. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
  136. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +9 -10
  137. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
  138. package/dist-server/server/modules/providers/provider.routes.js +123 -1
  139. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  140. package/dist-server/server/modules/providers/services/mcp.service.js +23 -0
  141. package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -1
  142. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +65 -0
  143. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -0
  144. package/dist-server/server/modules/providers/services/provider-models.service.js +1 -1
  145. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
  146. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +62 -23
  147. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  148. package/dist-server/server/modules/providers/services/sessions.service.js +62 -4
  149. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  150. package/dist-server/server/modules/providers/services/skills.service.js +11 -0
  151. package/dist-server/server/modules/providers/services/skills.service.js.map +1 -1
  152. package/dist-server/server/modules/providers/shared/skills/skills.provider.js +163 -1
  153. package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -1
  154. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +45 -0
  155. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  156. package/dist-server/server/modules/providers/tests/skills.test.js +160 -0
  157. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  158. package/dist-server/server/modules/websocket/index.js +1 -0
  159. package/dist-server/server/modules/websocket/index.js.map +1 -1
  160. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +252 -0
  161. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -0
  162. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +105 -0
  163. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -0
  164. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +218 -156
  165. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  166. package/dist-server/server/modules/websocket/services/desktop-agent-websocket.service.js +39 -0
  167. package/dist-server/server/modules/websocket/services/desktop-agent-websocket.service.js.map +1 -0
  168. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +83 -31
  169. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  170. package/dist-server/server/modules/websocket/services/websocket-server.service.js +10 -0
  171. package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -1
  172. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +216 -0
  173. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -0
  174. package/dist-server/server/openai-codex.js +24 -14
  175. package/dist-server/server/openai-codex.js.map +1 -1
  176. package/dist-server/server/opencode-cli.js +21 -8
  177. package/dist-server/server/opencode-cli.js.map +1 -1
  178. package/dist-server/server/opencode-cli.test.js +19 -1
  179. package/dist-server/server/opencode-cli.test.js.map +1 -1
  180. package/dist-server/server/routes/settings.js +1 -1
  181. package/dist-server/server/routes/settings.js.map +1 -1
  182. package/dist-server/server/services/notification-orchestrator.js +1 -176
  183. package/dist-server/server/services/notification-orchestrator.js.map +1 -1
  184. package/dist-server/server/services/tests/notification-orchestrator.test.js +64 -0
  185. package/dist-server/server/services/tests/notification-orchestrator.test.js.map +1 -0
  186. package/dist-server/server/shared/{claude-cli-path.test.js → tests/claude-cli-path.test.js} +1 -1
  187. package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -0
  188. package/dist-server/server/shared/tests/slice-tail-page.test.js +35 -0
  189. package/dist-server/server/shared/tests/slice-tail-page.test.js.map +1 -0
  190. package/dist-server/server/shared/utils.js +73 -1
  191. package/dist-server/server/shared/utils.js.map +1 -1
  192. package/dist-server/server/voice-proxy.js +226 -0
  193. package/dist-server/server/voice-proxy.js.map +1 -0
  194. package/electron/assets/logo-macos.icns +0 -0
  195. package/electron/assets/logo-macos.png +0 -0
  196. package/electron/assets/logo-windows.ico +0 -0
  197. package/electron/cloud.js +260 -0
  198. package/electron/computerAgent.js +290 -0
  199. package/electron/desktopNotifications.js +378 -0
  200. package/electron/desktopWindow.js +781 -0
  201. package/electron/launcher/index.html +14 -0
  202. package/electron/launcher/launcher.css +801 -0
  203. package/electron/launcher/launcher.js +829 -0
  204. package/electron/localServer.js +550 -0
  205. package/electron/main.js +1060 -0
  206. package/electron/preload.cjs +63 -0
  207. package/electron/scripts/generate-macos-icon.js +62 -0
  208. package/electron/serverInstaller.js +277 -0
  209. package/electron/tabs.js +87 -0
  210. package/electron/viewHost.js +331 -0
  211. package/package.json +86 -5
  212. package/public/api-docs.html +36 -18
  213. package/scripts/build-computer-semantics.mjs +133 -0
  214. package/scripts/copy-computer-semantics-bin.mjs +24 -0
  215. package/scripts/release/build-server-bundle.js +176 -0
  216. package/scripts/release/prepare-desktop-app.js +158 -0
  217. package/server/browser-use-mcp.ts +384 -0
  218. package/server/claude-sdk.js +27 -5
  219. package/server/cli.js +15 -6
  220. package/server/computer-use-agent.ts +279 -0
  221. package/server/computer-use-mcp.ts +574 -0
  222. package/server/cursor-cli.js +27 -11
  223. package/server/gemini-cli.js +17 -2
  224. package/server/index.js +151 -47
  225. package/server/load-env.js +1 -1
  226. package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -0
  227. package/server/modules/browser-use/browser-use.routes.ts +96 -0
  228. package/server/modules/browser-use/browser-use.service.ts +836 -0
  229. package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -0
  230. package/server/modules/computer-use/actions/raw-action-dispatcher.ts +67 -0
  231. package/server/modules/computer-use/actions/raw-action-types.ts +28 -0
  232. package/server/modules/computer-use/computer-executor.ts +242 -0
  233. package/server/modules/computer-use/computer-semantics.service.ts +460 -0
  234. package/server/modules/computer-use/computer-use-mcp.routes.ts +141 -0
  235. package/server/modules/computer-use/computer-use.routes.ts +211 -0
  236. package/server/modules/computer-use/computer-use.service.ts +920 -0
  237. package/server/modules/computer-use/desktop-agent-relay.service.ts +158 -0
  238. package/server/modules/computer-use/index.ts +2 -0
  239. package/server/modules/computer-use/semantics/adapters/helper-semantic-adapter.ts +82 -0
  240. package/server/modules/computer-use/semantics/adapters/macos/macos-semantic-adapter.ts +5 -0
  241. package/server/modules/computer-use/semantics/adapters/semantic-adapter.ts +23 -0
  242. package/server/modules/computer-use/semantics/adapters/windows/windows-semantic-adapter.ts +5 -0
  243. package/server/modules/computer-use/semantics/bin/darwin-arm64/CloudCLISemantics +0 -0
  244. package/server/modules/computer-use/semantics/bin/darwin-x64/CloudCLISemantics +0 -0
  245. package/server/modules/computer-use/semantics/bin/win32-arm64/CloudCLISemantics.exe +0 -0
  246. package/server/modules/computer-use/semantics/bin/win32-x64/CloudCLISemantics.exe +0 -0
  247. package/server/modules/computer-use/semantics/helpers/macos/CloudCLISemantics.swift +467 -0
  248. package/server/modules/computer-use/semantics/helpers/semantic-helper-process.ts +124 -0
  249. package/server/modules/computer-use/semantics/helpers/semantic-helper-resolver.ts +97 -0
  250. package/server/modules/computer-use/semantics/helpers/windows/CloudCLISemantics.csproj +11 -0
  251. package/server/modules/computer-use/semantics/helpers/windows/Program.cs +534 -0
  252. package/server/modules/computer-use/semantics/semantic-session-store.ts +87 -0
  253. package/server/modules/computer-use/semantics/semantic-tool-dispatcher.ts +17 -0
  254. package/server/modules/computer-use/semantics/semantic-types.ts +58 -0
  255. package/server/modules/database/index.ts +1 -0
  256. package/server/modules/database/migrations.ts +25 -0
  257. package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -0
  258. package/server/modules/database/repositories/notification-preferences.ts +12 -1
  259. package/server/modules/database/repositories/sessions.db.ts +227 -23
  260. package/server/modules/database/schema.ts +27 -0
  261. package/server/modules/database/tests/sessions-provider-mapping.test.ts +108 -0
  262. package/server/modules/database/{repositories → tests}/sessions.db.integration.test.ts +12 -0
  263. package/server/modules/notifications/index.ts +13 -0
  264. package/server/modules/notifications/notifications.routes.ts +127 -0
  265. package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -0
  266. package/server/modules/notifications/services/notification-orchestrator.service.js +288 -0
  267. package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -0
  268. package/server/modules/projects/services/project-management.service.ts +0 -8
  269. package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +12 -53
  270. package/server/modules/providers/README.md +1 -1
  271. package/server/modules/providers/index.ts +1 -0
  272. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +26 -1
  273. package/server/modules/providers/list/claude/claude-sessions.provider.ts +9 -14
  274. package/server/modules/providers/list/claude/claude-skills.provider.ts +8 -0
  275. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +7 -3
  276. package/server/modules/providers/list/codex/codex-sessions.provider.ts +3 -12
  277. package/server/modules/providers/list/codex/codex-skills.provider.ts +8 -0
  278. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +13 -22
  279. package/server/modules/providers/list/cursor/cursor-skills.provider.ts +8 -0
  280. package/server/modules/providers/list/gemini/gemini-models.provider.ts +5 -8
  281. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +6 -6
  282. package/server/modules/providers/list/gemini/gemini-skills.provider.ts +8 -0
  283. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +18 -4
  284. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +9 -12
  285. package/server/modules/providers/provider.routes.ts +167 -1
  286. package/server/modules/providers/services/mcp.service.ts +26 -0
  287. package/server/modules/providers/services/provider-capabilities.service.ts +91 -0
  288. package/server/modules/providers/services/provider-models.service.ts +1 -1
  289. package/server/modules/providers/services/sessions-watcher.service.ts +71 -25
  290. package/server/modules/providers/services/sessions.service.ts +79 -4
  291. package/server/modules/providers/services/skills.service.ts +25 -1
  292. package/server/modules/providers/shared/skills/skills.provider.ts +223 -0
  293. package/server/modules/providers/tests/opencode-sessions.test.ts +49 -0
  294. package/server/modules/providers/tests/skills.test.ts +212 -0
  295. package/server/modules/websocket/README.md +33 -27
  296. package/server/modules/websocket/index.ts +1 -0
  297. package/server/modules/websocket/services/chat-run-registry.service.ts +327 -0
  298. package/server/modules/websocket/services/chat-session-writer.service.ts +145 -0
  299. package/server/modules/websocket/services/chat-websocket.service.ts +292 -207
  300. package/server/modules/websocket/services/desktop-agent-websocket.service.ts +51 -0
  301. package/server/modules/websocket/services/shell-websocket.service.ts +108 -32
  302. package/server/modules/websocket/services/websocket-server.service.ts +12 -0
  303. package/server/modules/websocket/tests/chat-run-registry.test.ts +244 -0
  304. package/server/openai-codex.js +24 -14
  305. package/server/opencode-cli.js +21 -8
  306. package/server/opencode-cli.test.js +20 -1
  307. package/server/routes/settings.js +6 -1
  308. package/server/services/notification-orchestrator.js +3 -224
  309. package/server/services/tests/notification-orchestrator.test.js +80 -0
  310. package/server/shared/interfaces.ts +15 -0
  311. package/server/shared/tests/slice-tail-page.test.ts +42 -0
  312. package/server/shared/types.ts +84 -1
  313. package/server/shared/utils.ts +95 -1
  314. package/server/voice-proxy.js +224 -0
  315. package/dist/assets/index-73Xw3i9v.css +0 -32
  316. package/dist/assets/index-BIHaviaA.js +0 -1361
  317. package/dist/modelConstants.js +0 -854
  318. package/dist-server/server/modules/database/repositories/projects.db.integration.test.js.map +0 -1
  319. package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js.map +0 -1
  320. package/dist-server/server/shared/claude-cli-path.test.js.map +0 -1
  321. package/public/modelConstants.js +0 -854
  322. /package/dist-server/server/modules/database/{repositories → tests}/projects.db.integration.test.js +0 -0
  323. /package/server/modules/database/{repositories → tests}/projects.db.integration.test.ts +0 -0
  324. /package/server/shared/{claude-cli-path.test.ts → tests/claude-cli-path.test.ts} +0 -0
@@ -0,0 +1,781 @@
1
+ import { BrowserWindow, Menu, Tray, clipboard, nativeImage, nativeTheme, session, webContents as electronWebContents } from 'electron';
2
+
3
+ import { ViewHost } from './viewHost.js';
4
+
5
+ const TITLEBAR_HEIGHT = 44;
6
+ const AUTH_TOKEN_STORAGE_KEY = 'auth-token';
7
+ // TODO: Re-enable Computer Use menus after fixing the MCP server connection
8
+ // between the desktop app and the web UI.
9
+ const COMPUTER_USE_MENUS_ENABLED = false;
10
+
11
+ function isAllowedPermissionOrigin(sourceUrl, controlPlaneUrl) {
12
+ try {
13
+ const source = new URL(sourceUrl);
14
+ if ((source.hostname === '127.0.0.1' || source.hostname === 'localhost') && source.protocol === 'http:') {
15
+ return true;
16
+ }
17
+ if (source.protocol !== 'https:') {
18
+ return false;
19
+ }
20
+ const controlPlane = new URL(controlPlaneUrl);
21
+ return source.origin === controlPlane.origin || source.hostname.endsWith('.cloudcli.ai');
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ function getWebContentsProcessId(contents) {
28
+ return {
29
+ osProcessId: typeof contents.getOSProcessId === 'function' ? contents.getOSProcessId() : null,
30
+ processId: typeof contents.getProcessId === 'function' ? contents.getProcessId() : null,
31
+ };
32
+ }
33
+
34
+ export class DesktopWindowManager {
35
+ constructor({
36
+ appName,
37
+ getWindowIconPath,
38
+ getLauncherPath,
39
+ getPreloadPath,
40
+ openExternalUrl,
41
+ getDesktopState,
42
+ getDisplayTargetName,
43
+ getRemoteEnvironmentMenuItems,
44
+ getCloudState,
45
+ getLocalState,
46
+ actions,
47
+ tabs,
48
+ }) {
49
+ this.appName = appName;
50
+ this.getWindowIconPath = getWindowIconPath;
51
+ this.getLauncherPath = getLauncherPath;
52
+ this.getPreloadPath = getPreloadPath;
53
+ this.openExternalUrl = openExternalUrl;
54
+ this.getDesktopState = getDesktopState;
55
+ this.getDisplayTargetName = getDisplayTargetName;
56
+ this.getRemoteEnvironmentMenuItems = getRemoteEnvironmentMenuItems;
57
+ this.getCloudState = getCloudState;
58
+ this.getLocalState = getLocalState;
59
+ this.actions = actions;
60
+ this.tabs = tabs;
61
+
62
+ this.mainWindow = null;
63
+ this.settingsWindow = null;
64
+ this.tray = null;
65
+ this.launcherLoaded = false;
66
+ this.viewHost = new ViewHost({
67
+ appName: this.appName,
68
+ getMainWindow: () => this.mainWindow,
69
+ getContentViewBounds: () => this.getContentViewBounds(),
70
+ getPreloadPath: this.getPreloadPath,
71
+ openExternalUrl: this.openExternalUrl,
72
+ showError: this.actions.showError,
73
+ });
74
+ }
75
+
76
+ getMainWindow() {
77
+ return this.mainWindow;
78
+ }
79
+
80
+ getTrayImage() {
81
+ const image = nativeImage.createFromPath(this.getWindowIconPath());
82
+ return image.resize({ width: 18, height: 18 });
83
+ }
84
+
85
+ getContentViewBounds() {
86
+ if (!this.mainWindow) return { x: 0, y: TITLEBAR_HEIGHT, width: 0, height: 0 };
87
+ const [width, height] = this.mainWindow.getContentSize();
88
+ return {
89
+ x: 0,
90
+ y: TITLEBAR_HEIGHT,
91
+ width,
92
+ height: Math.max(0, height - TITLEBAR_HEIGHT),
93
+ };
94
+ }
95
+
96
+ detachActiveContentView() {
97
+ this.viewHost.detachAll();
98
+ }
99
+
100
+ async showTabPlaceholder(target, message) {
101
+ const tabId = this.tabs.getTabIdForTarget(target);
102
+ await this.viewHost.showTabPlaceholder(tabId, target, message);
103
+ }
104
+
105
+ async showLocalStartupTarget(target, logs) {
106
+ const tabId = this.tabs.getTabIdForTarget(target);
107
+ await this.viewHost.showLocalStartupTarget(tabId, target, logs);
108
+ }
109
+
110
+ async showContentTarget(target) {
111
+ const tabId = this.tabs.getTabIdForTarget(target);
112
+ await this.viewHost.showContentTarget(tabId, target);
113
+ }
114
+
115
+ destroyTabView(tabId) {
116
+ this.viewHost.destroyTabView(tabId);
117
+ }
118
+
119
+ emitDesktopState() {
120
+ const state = this.getDesktopState();
121
+ if (this.mainWindow && !this.mainWindow.webContents.isDestroyed()) {
122
+ this.mainWindow.webContents.send('cloudcli-desktop:state-updated', state);
123
+ }
124
+ if (this.settingsWindow && !this.settingsWindow.webContents.isDestroyed()) {
125
+ this.settingsWindow.webContents.send('cloudcli-desktop:state-updated', state);
126
+ }
127
+ }
128
+
129
+ emitLauncherCommand(command) {
130
+ if (!this.mainWindow || this.mainWindow.webContents.isDestroyed()) return;
131
+ this.mainWindow.webContents.send('cloudcli-desktop:launcher-command', command);
132
+ }
133
+
134
+ emitSettingsCommand(command) {
135
+ if (!this.settingsWindow || this.settingsWindow.webContents.isDestroyed()) return;
136
+ this.settingsWindow.webContents.send('cloudcli-desktop:launcher-command', command);
137
+ }
138
+
139
+ syncSettingsWindowBounds() {
140
+ if (!this.mainWindow || !this.settingsWindow || this.settingsWindow.isDestroyed()) return;
141
+ this.settingsWindow.setBounds(this.mainWindow.getBounds());
142
+ }
143
+
144
+ async ensureSettingsWindow(sheet = 'desktop-settings') {
145
+ if (!this.mainWindow) return null;
146
+
147
+ if (this.settingsWindow && !this.settingsWindow.isDestroyed()) {
148
+ this.syncSettingsWindowBounds();
149
+ this.emitSettingsCommand({ type: 'open-sheet', sheet });
150
+ this.settingsWindow.focus();
151
+ return this.settingsWindow;
152
+ }
153
+
154
+ this.settingsWindow = new BrowserWindow({
155
+ parent: this.mainWindow,
156
+ show: false,
157
+ frame: false,
158
+ transparent: true,
159
+ hasShadow: false,
160
+ resizable: false,
161
+ minimizable: false,
162
+ maximizable: false,
163
+ fullscreenable: false,
164
+ movable: false,
165
+ skipTaskbar: true,
166
+ backgroundColor: '#00000000',
167
+ webPreferences: {
168
+ contextIsolation: true,
169
+ nodeIntegration: false,
170
+ sandbox: true,
171
+ preload: this.getPreloadPath(),
172
+ },
173
+ });
174
+ this.syncSettingsWindowBounds();
175
+ this.viewHost.configureChildWebContents(this.settingsWindow.webContents);
176
+ this.settingsWindow.once('ready-to-show', () => this.settingsWindow?.show());
177
+ this.settingsWindow.on('closed', () => {
178
+ this.settingsWindow = null;
179
+ });
180
+ await this.settingsWindow.loadFile(this.getLauncherPath(), {
181
+ query: { modal: '1', sheet },
182
+ });
183
+ return this.settingsWindow;
184
+ }
185
+
186
+ closeSettingsWindow() {
187
+ if (!this.settingsWindow || this.settingsWindow.isDestroyed()) return;
188
+ this.settingsWindow.close();
189
+ }
190
+
191
+ async showTarget(target, { trackTab = true } = {}) {
192
+ if (!this.mainWindow) return;
193
+ if (trackTab) {
194
+ this.tabs.upsertTarget(target);
195
+ }
196
+ this.actions.setActiveTarget(target);
197
+ this.buildAppMenu();
198
+ this.mainWindow.setTitle(`${this.appName} - ${target.name}`);
199
+ const finalUrl = await this.showContentTarget(target);
200
+ this.emitDesktopState();
201
+ return finalUrl;
202
+ }
203
+
204
+ async showLauncher() {
205
+ if (!this.mainWindow) return;
206
+ const target = { kind: 'launcher', name: this.appName, url: null };
207
+ this.tabs.upsertTarget(target);
208
+ this.actions.setActiveTarget(target);
209
+ this.detachActiveContentView();
210
+ this.buildAppMenu();
211
+ this.mainWindow.setTitle(this.appName);
212
+ this.mainWindow.webContents.focus();
213
+ if (!this.launcherLoaded) {
214
+ await this.mainWindow.loadFile(this.getLauncherPath());
215
+ this.launcherLoaded = true;
216
+ } else {
217
+ this.emitDesktopState();
218
+ }
219
+ }
220
+
221
+ async switchDesktopTab(tabId) {
222
+ const tab = this.tabs.activate(tabId);
223
+ if (!tab || !this.mainWindow) return this.getDesktopState();
224
+
225
+ if (tab.id === 'home' || tab.kind === 'launcher') {
226
+ await this.showLauncher();
227
+ return this.getDesktopState();
228
+ }
229
+
230
+ if (!tab.target?.url) {
231
+ throw new Error('This tab does not have a target URL.');
232
+ }
233
+
234
+ await this.showTarget(tab.target, { trackTab: false });
235
+ return this.getDesktopState();
236
+ }
237
+
238
+ async reloadActiveTab() {
239
+ const activeTab = this.tabs.getActiveTab();
240
+ if (!activeTab || activeTab.id === 'home' || activeTab.kind === 'launcher') {
241
+ this.emitDesktopState();
242
+ return this.getDesktopState();
243
+ }
244
+
245
+ const reloaded = this.viewHost.reloadTab(activeTab.id);
246
+ if (!reloaded && activeTab.target?.url) {
247
+ await this.showTarget(activeTab.target, { trackTab: false });
248
+ }
249
+ this.emitDesktopState();
250
+ return this.getDesktopState();
251
+ }
252
+
253
+ async navigateActiveView(url) {
254
+ const navigated = await this.viewHost.navigateActiveView(url);
255
+ this.emitDesktopState();
256
+ return navigated;
257
+ }
258
+
259
+ async readAuthTokenForTarget(url) {
260
+ return this.viewHost.readLocalStorageValueForOrigin(url, AUTH_TOKEN_STORAGE_KEY);
261
+ }
262
+
263
+ openActiveTabDevTools() {
264
+ if (this.viewHost.openActiveViewDevTools()) return;
265
+ void this.actions.showError('No active BrowserView', new Error('Switch to a non-launcher tab before opening active tab DevTools.'));
266
+ }
267
+
268
+ reloadActiveBrowserViewForDiagnostics() {
269
+ if (this.viewHost.reloadActiveView()) return;
270
+ void this.actions.showError('No active BrowserView', new Error('Switch to a non-launcher tab before reloading the active BrowserView.'));
271
+ }
272
+
273
+ detachActiveBrowserViewForDiagnostics() {
274
+ if (this.viewHost.detachActiveView()) return;
275
+ void this.actions.showError('No active BrowserView', new Error('Switch to a non-launcher tab before detaching the active BrowserView.'));
276
+ }
277
+
278
+ copyWebContentsDiagnostics() {
279
+ const tabViewDiagnostics = this.viewHost.getTabViewDiagnostics();
280
+ const tabViewByContentsId = new Map(
281
+ tabViewDiagnostics
282
+ .filter((item) => item.webContentsId != null)
283
+ .map((item) => [item.webContentsId, item])
284
+ );
285
+
286
+ const rows = electronWebContents.getAllWebContents().map((contents) => {
287
+ const destroyed = contents.isDestroyed();
288
+ const processIds = destroyed ? { osProcessId: null, processId: null } : getWebContentsProcessId(contents);
289
+ const tabView = tabViewByContentsId.get(contents.id);
290
+ let owner = 'unknown';
291
+ if (this.mainWindow?.webContents?.id === contents.id) {
292
+ owner = 'main-window';
293
+ } else if (this.settingsWindow?.webContents?.id === contents.id) {
294
+ owner = 'settings-window';
295
+ } else if (tabView) {
296
+ owner = `browser-view:${tabView.tabId}`;
297
+ }
298
+
299
+ return {
300
+ id: contents.id,
301
+ owner,
302
+ osProcessId: processIds.osProcessId,
303
+ processId: processIds.processId,
304
+ url: destroyed ? null : contents.getURL(),
305
+ title: destroyed ? null : contents.getTitle(),
306
+ destroyed,
307
+ focused: destroyed || typeof contents.isFocused !== 'function' ? false : contents.isFocused(),
308
+ attached: tabView ? tabView.attached : null,
309
+ active: tabView ? tabView.active : null,
310
+ };
311
+ });
312
+
313
+ const activeTab = this.tabs.getActiveTab();
314
+ const diagnostics = {
315
+ generatedAt: new Date().toISOString(),
316
+ activeTabId: this.tabs.activeTabId,
317
+ activeTab: activeTab
318
+ ? {
319
+ id: activeTab.id,
320
+ title: activeTab.title,
321
+ kind: activeTab.kind,
322
+ targetUrl: activeTab.target?.url || null,
323
+ }
324
+ : null,
325
+ tabViews: tabViewDiagnostics,
326
+ webContents: rows,
327
+ };
328
+
329
+ clipboard.writeText(JSON.stringify(diagnostics, null, 2));
330
+ }
331
+
332
+ async closeDesktopTab(tabId) {
333
+ const tab = this.tabs.remove(tabId);
334
+ if (!tab) return this.getDesktopState();
335
+ this.destroyTabView(tabId);
336
+ if (this.tabs.activeTabId === 'home') {
337
+ await this.showLauncher();
338
+ } else {
339
+ this.emitDesktopState();
340
+ }
341
+ return this.getDesktopState();
342
+ }
343
+
344
+ buildEnvironmentActionsSubmenu(environment) {
345
+ const items = [];
346
+ const statusSuffix = environment.status === 'running' ? '' : ` (${environment.status})`;
347
+ items.push({
348
+ label: 'Open Environment',
349
+ click: () => void this.actions.openEnvironmentInDesktop(environment)
350
+ .catch((error) => this.actions.showError(`Could not open ${environment.name || environment.subdomain}${statusSuffix}`, error)),
351
+ });
352
+ items.push({
353
+ label: 'Open in Browser',
354
+ click: () => void this.actions.openEnvironmentInBrowser(environment)
355
+ .catch((error) => this.actions.showError('Could not open environment in browser', error)),
356
+ });
357
+ items.push({
358
+ label: 'Open in VS Code',
359
+ click: () => void this.actions.openEnvironmentInIde(environment, 'vscode')
360
+ .catch((error) => this.actions.showError('Could not open environment in VS Code', error)),
361
+ });
362
+ items.push({
363
+ label: 'Open in Cursor',
364
+ click: () => void this.actions.openEnvironmentInIde(environment, 'cursor')
365
+ .catch((error) => this.actions.showError('Could not open environment in Cursor', error)),
366
+ });
367
+ items.push({
368
+ label: 'Open SSH Terminal',
369
+ click: () => void this.actions.openEnvironmentInSsh(environment)
370
+ .catch((error) => this.actions.showError('Could not open SSH terminal', error)),
371
+ });
372
+ items.push({
373
+ label: 'Copy Mobile/Web URL',
374
+ click: () => this.actions.copyText(this.actions.getEnvironmentUrl(environment)),
375
+ });
376
+ if (environment.status !== 'running') {
377
+ items.unshift({
378
+ label: environment.status === 'paused' ? 'Resume' : 'Start',
379
+ click: () => void this.actions.startEnvironment(environment)
380
+ .catch((error) => this.actions.showError('Could not start environment', error)),
381
+ });
382
+ }
383
+ if (environment.status === 'running') {
384
+ items.push({
385
+ label: 'Stop',
386
+ click: () => void this.actions.stopEnvironment(environment)
387
+ .catch((error) => this.actions.showError('Could not stop environment', error)),
388
+ });
389
+ }
390
+ return items;
391
+ }
392
+
393
+ buildTrayEnvironmentSection() {
394
+ const cloudState = this.getCloudState();
395
+ if (!cloudState.account?.apiKey) {
396
+ return [
397
+ {
398
+ label: cloudState.account?.email ? `Reconnect ${cloudState.account.email}` : 'Login',
399
+ click: () => void this.actions.connectCloudAccount()
400
+ .catch((error) => this.actions.showError('Could not connect CloudCLI account', error)),
401
+ },
402
+ ];
403
+ }
404
+
405
+ if (!cloudState.environments.length) {
406
+ return [{ label: 'No environments found', enabled: false }];
407
+ }
408
+
409
+ return cloudState.environments.map((environment) => ({
410
+ label: `${environment.name || environment.subdomain} - ${environment.status}`,
411
+ submenu: this.buildEnvironmentActionsSubmenu(environment),
412
+ }));
413
+ }
414
+
415
+ buildAppMenu() {
416
+ if (!this.mainWindow) return;
417
+ const cloudState = this.getCloudState();
418
+ const localState = this.getLocalState();
419
+ const remoteItems = this.getRemoteEnvironmentMenuItems();
420
+ const cloudAccountLabel = cloudState.account?.apiKey
421
+ ? (cloudState.account?.email ? `Connected: ${cloudState.account.email}` : 'CloudCLI Connected')
422
+ : (cloudState.account?.email ? `Reconnect: ${cloudState.account.email}` : 'Connect CloudCLI Account...');
423
+
424
+ const template = [
425
+ {
426
+ label: this.appName,
427
+ submenu: [
428
+ { label: `About ${this.appName}`, role: 'about' },
429
+ { type: 'separator' },
430
+ {
431
+ label: 'Show Launcher',
432
+ accelerator: 'CmdOrCtrl+Shift+L',
433
+ click: () => void this.showLauncher().catch((error) => this.actions.showError('Could not show launcher', error)),
434
+ },
435
+ {
436
+ label: 'Switch Environment',
437
+ accelerator: 'CmdOrCtrl+Shift+E',
438
+ click: () => void this.actions.showEnvironmentPicker().catch((error) => this.actions.showError('Could not switch environment', error)),
439
+ },
440
+ { type: 'separator' },
441
+ {
442
+ label: 'Services',
443
+ visible: COMPUTER_USE_MENUS_ENABLED,
444
+ submenu: [
445
+ {
446
+ label: 'Computer Use',
447
+ click: () => void this.showDesktopSettings(),
448
+ },
449
+ ],
450
+ },
451
+ {
452
+ label: 'Diagnostics',
453
+ submenu: [
454
+ {
455
+ label: 'Copy Diagnostics',
456
+ click: () => void this.actions.copyDiagnostics(),
457
+ },
458
+ ],
459
+ },
460
+ { type: 'separator' },
461
+ {
462
+ label: process.platform === 'darwin' ? `Hide ${this.appName}` : 'Hide',
463
+ role: 'hide',
464
+ visible: process.platform === 'darwin',
465
+ },
466
+ { label: 'Hide Others', role: 'hideOthers', visible: process.platform === 'darwin' },
467
+ { label: 'Show All', role: 'unhide', visible: process.platform === 'darwin' },
468
+ { type: 'separator', visible: process.platform === 'darwin' },
469
+ { label: `Quit ${this.appName}`, accelerator: 'CmdOrCtrl+Q', role: 'quit' },
470
+ ],
471
+ },
472
+ {
473
+ label: 'Environment',
474
+ submenu: [
475
+ {
476
+ label: 'Show Launcher',
477
+ accelerator: 'CmdOrCtrl+Shift+L',
478
+ click: () => void this.showLauncher().catch((error) => this.actions.showError('Could not show launcher', error)),
479
+ },
480
+ {
481
+ label: 'Switch Environment',
482
+ accelerator: 'CmdOrCtrl+Shift+E',
483
+ click: () => void this.actions.showEnvironmentPicker().catch((error) => this.actions.showError('Could not switch environment', error)),
484
+ },
485
+ { type: 'separator' },
486
+ {
487
+ label: 'Open Local CloudCLI',
488
+ accelerator: 'CmdOrCtrl+L',
489
+ click: () => void this.actions.openLocalInDesktop().catch((error) => this.actions.showError('Could not open local CloudCLI', error)),
490
+ },
491
+ {
492
+ label: 'Open Local Web UI in Browser',
493
+ accelerator: 'CmdOrCtrl+Shift+W',
494
+ click: () => void this.actions.openLocalWebUi().catch((error) => this.actions.showError('Could not open local web UI', error)),
495
+ },
496
+ {
497
+ label: 'Copy Local Web URL',
498
+ accelerator: 'CmdOrCtrl+Shift+U',
499
+ click: () => void this.actions.copyLocalWebUrl().catch((error) => this.actions.showError('Could not copy local web URL', error)),
500
+ },
501
+ { type: 'separator' },
502
+ {
503
+ label: 'Keep Local Server Running After Quit',
504
+ type: 'checkbox',
505
+ checked: localState.desktopSettings.keepLocalServerRunning,
506
+ click: (menuItem) => void this.actions.updateDesktopSetting('keepLocalServerRunning', menuItem.checked)
507
+ .catch((error) => this.actions.showError('Could not update desktop setting', error)),
508
+ },
509
+ {
510
+ label: 'Allow LAN Access to Local Server',
511
+ type: 'checkbox',
512
+ checked: localState.desktopSettings.exposeLocalServerOnNetwork,
513
+ click: (menuItem) => void this.actions.updateDesktopSetting('exposeLocalServerOnNetwork', menuItem.checked)
514
+ .catch((error) => this.actions.showError('Could not update desktop setting', error)),
515
+ },
516
+ ],
517
+ },
518
+ {
519
+ label: 'Cloud',
520
+ submenu: [
521
+ {
522
+ label: cloudAccountLabel,
523
+ accelerator: 'CmdOrCtrl+Shift+C',
524
+ click: () => void this.actions.connectCloudAccount().catch((error) => this.actions.showError('Could not connect CloudCLI account', error)),
525
+ },
526
+ {
527
+ label: 'Refresh Cloud Environments',
528
+ click: () => void this.actions.refreshCloudEnvironments().catch((error) => this.actions.showError('Could not load CloudCLI environments', error)),
529
+ enabled: Boolean(cloudState.account?.apiKey),
530
+ },
531
+ {
532
+ label: 'Logout CloudCLI Account',
533
+ click: () => void this.actions.clearCloudAccount().catch((error) => this.actions.showError('Could not logout', error)),
534
+ enabled: Boolean(cloudState.account?.apiKey),
535
+ },
536
+ { type: 'separator' },
537
+ {
538
+ label: 'Remote Environments',
539
+ submenu: remoteItems,
540
+ },
541
+ ],
542
+ },
543
+ {
544
+ label: 'Edit',
545
+ submenu: [
546
+ { role: 'undo' },
547
+ { role: 'redo' },
548
+ { type: 'separator' },
549
+ { role: 'cut' },
550
+ { role: 'copy' },
551
+ { role: 'paste' },
552
+ { role: 'selectAll' },
553
+ ],
554
+ },
555
+ {
556
+ label: 'View',
557
+ submenu: [
558
+ { role: 'reload' },
559
+ { role: 'forceReload' },
560
+ { role: 'toggleDevTools' },
561
+ {
562
+ label: 'Open Active Tab DevTools',
563
+ click: () => this.openActiveTabDevTools(),
564
+ },
565
+ {
566
+ label: 'Copy WebContents Diagnostics',
567
+ click: () => this.copyWebContentsDiagnostics(),
568
+ },
569
+ {
570
+ label: 'Reload Active BrowserView',
571
+ click: () => this.reloadActiveBrowserViewForDiagnostics(),
572
+ },
573
+ {
574
+ label: 'Detach Active BrowserView',
575
+ click: () => this.detachActiveBrowserViewForDiagnostics(),
576
+ },
577
+ { type: 'separator' },
578
+ { role: 'resetZoom' },
579
+ { role: 'zoomIn' },
580
+ { role: 'zoomOut' },
581
+ { type: 'separator' },
582
+ { role: 'togglefullscreen' },
583
+ ],
584
+ },
585
+ {
586
+ label: 'Window',
587
+ submenu: [
588
+ { role: 'minimize' },
589
+ { role: 'zoom' },
590
+ ...(process.platform === 'darwin' ? [{ type: 'separator' }, { role: 'front' }] : []),
591
+ ],
592
+ },
593
+ {
594
+ label: 'Help',
595
+ submenu: [
596
+ {
597
+ label: 'Open cloudcli.ai',
598
+ click: () => void this.actions.openCloudDashboard(),
599
+ },
600
+ {
601
+ label: 'Copy Diagnostics',
602
+ click: () => void this.actions.copyDiagnostics(),
603
+ },
604
+ ],
605
+ },
606
+ ];
607
+
608
+ Menu.setApplicationMenu(Menu.buildFromTemplate(template));
609
+ this.buildTrayMenu();
610
+ }
611
+
612
+ buildTrayMenu() {
613
+ if (!this.tray) return;
614
+ const cloudState = this.getCloudState();
615
+ const localState = this.getLocalState();
616
+
617
+ const template = [
618
+ {
619
+ label: 'Local',
620
+ submenu: [
621
+ {
622
+ label: localState.localServerRunning ? 'Open Local in CloudCLI' : 'Start Local in CloudCLI',
623
+ click: () => void this.actions.openLocalInDesktop().catch((error) => this.actions.showError('Could not open local CloudCLI', error)),
624
+ },
625
+ {
626
+ label: 'Open Local in Browser',
627
+ click: () => void this.actions.openLocalWebUi().catch((error) => this.actions.showError('Could not open local web UI', error)),
628
+ },
629
+ {
630
+ label: 'Copy Local URL',
631
+ click: () => void this.actions.copyLocalWebUrl().catch((error) => this.actions.showError('Could not copy local web URL', error)),
632
+ },
633
+ ],
634
+ },
635
+ {
636
+ label: 'Cloud Environments',
637
+ submenu: this.buildTrayEnvironmentSection(),
638
+ },
639
+ { type: 'separator' },
640
+ {
641
+ label: cloudState.account?.email ? `Connected: ${cloudState.account.email}` : 'Login',
642
+ click: () => void this.actions.connectCloudAccount().catch((error) => this.actions.showError('Could not connect CloudCLI account', error)),
643
+ },
644
+ {
645
+ label: 'Logout CloudCLI Account',
646
+ click: () => void this.actions.clearCloudAccount().catch((error) => this.actions.showError('Could not logout', error)),
647
+ enabled: Boolean(cloudState.account?.apiKey),
648
+ },
649
+ { type: 'separator' },
650
+ {
651
+ label: `Quit ${this.appName}`,
652
+ role: 'quit',
653
+ },
654
+ ];
655
+
656
+ this.tray.setToolTip(`${this.appName}${this.actions.getActiveTarget()?.name ? ` - ${this.actions.getActiveTarget().name}` : ''}`);
657
+ this.tray.setContextMenu(Menu.buildFromTemplate(template));
658
+ }
659
+
660
+ async showDesktopSettings() {
661
+ if (!this.mainWindow) return this.getDesktopState();
662
+ await this.ensureSettingsWindow('desktop-settings');
663
+ return this.getDesktopState();
664
+ }
665
+
666
+ async showLocalSettings() {
667
+ if (!this.mainWindow) return this.getDesktopState();
668
+ await this.ensureSettingsWindow('local-settings');
669
+ return this.getDesktopState();
670
+ }
671
+
672
+ async showActiveEnvironmentActionsMenu() {
673
+ if (!this.mainWindow) return this.getDesktopState();
674
+ const activeTarget = this.actions.getActiveTarget();
675
+ if (activeTarget?.kind !== 'remote') return this.getDesktopState();
676
+
677
+ const environment = this.getCloudState().environments.find((item) => item.id === activeTarget.id);
678
+ if (!environment) return this.getDesktopState();
679
+
680
+ const menu = Menu.buildFromTemplate(this.buildEnvironmentActionsSubmenu(environment));
681
+ menu.popup({ window: this.mainWindow });
682
+ return this.getDesktopState();
683
+ }
684
+
685
+ async showEnvironmentActionsMenu(environmentId) {
686
+ if (!this.mainWindow) return this.getDesktopState();
687
+ const environment = this.getCloudState().environments.find((item) => item.id === environmentId);
688
+ if (!environment) return this.getDesktopState();
689
+
690
+ const menu = Menu.buildFromTemplate(this.buildEnvironmentActionsSubmenu(environment));
691
+ menu.popup({ window: this.mainWindow });
692
+ return this.getDesktopState();
693
+ }
694
+
695
+ configurePermissions() {
696
+ const isAllowedPermission = (webContents, permission) => {
697
+ const sourceUrl = webContents.getURL();
698
+ const allowedPermissions = new Set(['clipboard-read', 'media', 'notifications']);
699
+ return isAllowedPermissionOrigin(sourceUrl, this.getCloudState().controlPlaneUrl) && allowedPermissions.has(permission);
700
+ };
701
+
702
+ session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => {
703
+ callback(isAllowedPermission(webContents, permission));
704
+ });
705
+ session.defaultSession.setPermissionCheckHandler((webContents, permission) => {
706
+ if (!webContents) return false;
707
+ return isAllowedPermission(webContents, permission);
708
+ });
709
+ }
710
+
711
+ createTray() {
712
+ if (this.tray) return;
713
+ this.tray = new Tray(this.getTrayImage());
714
+ this.tray.on('click', () => {
715
+ if (!this.mainWindow) return;
716
+ if (this.mainWindow.isVisible()) {
717
+ this.mainWindow.focus();
718
+ } else {
719
+ this.mainWindow.show();
720
+ }
721
+ });
722
+ this.buildTrayMenu();
723
+ }
724
+
725
+ async createWindow() {
726
+ this.mainWindow = new BrowserWindow({
727
+ width: 1440,
728
+ height: 960,
729
+ minWidth: 1024,
730
+ minHeight: 720,
731
+ show: false,
732
+ backgroundColor: '#0f172a',
733
+ title: this.appName,
734
+ icon: this.getWindowIconPath(),
735
+ titleBarStyle: 'hidden',
736
+ ...(process.platform === 'darwin'
737
+ ? { trafficLightPosition: { x: 18, y: 14 } }
738
+ : {
739
+ titleBarOverlay: {
740
+ color: nativeTheme.shouldUseDarkColors ? '#111111' : '#f7f8fa',
741
+ symbolColor: nativeTheme.shouldUseDarkColors ? '#a1a1a1' : '#5b6470',
742
+ height: 44,
743
+ },
744
+ }),
745
+ webPreferences: {
746
+ contextIsolation: true,
747
+ nodeIntegration: false,
748
+ sandbox: true,
749
+ preload: this.getPreloadPath(),
750
+ },
751
+ });
752
+
753
+ this.mainWindow.once('ready-to-show', () => {
754
+ this.mainWindow?.show();
755
+ });
756
+
757
+ this.mainWindow.webContents.setWindowOpenHandler(({ url }) => {
758
+ void this.openExternalUrl(url).catch((error) => this.actions.showError('Could not open external link', error));
759
+ return { action: 'deny' };
760
+ });
761
+
762
+ this.mainWindow.on('resize', () => {
763
+ this.viewHost.resizeActiveView();
764
+ this.syncSettingsWindowBounds();
765
+ });
766
+
767
+ this.mainWindow.on('move', () => {
768
+ this.syncSettingsWindowBounds();
769
+ });
770
+
771
+ this.mainWindow.on('closed', () => {
772
+ this.viewHost.clear();
773
+ this.settingsWindow = null;
774
+ this.mainWindow = null;
775
+ this.launcherLoaded = false;
776
+ });
777
+
778
+ this.buildAppMenu();
779
+ await this.showLauncher();
780
+ }
781
+ }