@cloudcli-ai/cloudcli 1.34.0 → 1.35.1

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 (247) 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 +23 -5
  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-BvX30NW5.css +32 -0
  11. package/dist/assets/index-D1IdCTT6.js +1418 -0
  12. package/dist/index.html +12 -4
  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/cursor-cli.js +27 -11
  20. package/dist-server/server/cursor-cli.js.map +1 -1
  21. package/dist-server/server/gemini-cli.js +17 -2
  22. package/dist-server/server/gemini-cli.js.map +1 -1
  23. package/dist-server/server/index.js +136 -40
  24. package/dist-server/server/index.js.map +1 -1
  25. package/dist-server/server/load-env.js +1 -1
  26. package/dist-server/server/load-env.js.map +1 -1
  27. package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js +107 -0
  28. package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js.map +1 -0
  29. package/dist-server/server/modules/browser-use/browser-use.routes.js +93 -0
  30. package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -0
  31. package/dist-server/server/modules/browser-use/browser-use.service.js +688 -0
  32. package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -0
  33. package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js +8 -0
  34. package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js.map +1 -0
  35. package/dist-server/server/modules/database/index.js +1 -0
  36. package/dist-server/server/modules/database/index.js.map +1 -1
  37. package/dist-server/server/modules/database/migrations.js +23 -1
  38. package/dist-server/server/modules/database/migrations.js.map +1 -1
  39. package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +100 -0
  40. package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js.map +1 -0
  41. package/dist-server/server/modules/database/repositories/notification-preferences.js +8 -0
  42. package/dist-server/server/modules/database/repositories/notification-preferences.js.map +1 -1
  43. package/dist-server/server/modules/database/repositories/sessions.db.js +173 -17
  44. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  45. package/dist-server/server/modules/database/schema.js +26 -0
  46. package/dist-server/server/modules/database/schema.js.map +1 -1
  47. package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -0
  48. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +78 -0
  49. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -0
  50. package/dist-server/server/modules/database/{repositories → tests}/sessions.db.integration.test.js +10 -0
  51. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -0
  52. package/dist-server/server/modules/notifications/index.js +4 -0
  53. package/dist-server/server/modules/notifications/index.js.map +1 -0
  54. package/dist-server/server/modules/notifications/notifications.routes.js +115 -0
  55. package/dist-server/server/modules/notifications/notifications.routes.js.map +1 -0
  56. package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js +97 -0
  57. package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js.map +1 -0
  58. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +229 -0
  59. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -0
  60. package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js +79 -0
  61. package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js.map +1 -0
  62. package/dist-server/server/modules/projects/services/project-management.service.js +0 -4
  63. package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -1
  64. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +10 -38
  65. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
  66. package/dist-server/server/modules/providers/index.js +1 -0
  67. package/dist-server/server/modules/providers/index.js.map +1 -1
  68. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +24 -1
  69. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
  70. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +9 -12
  71. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  72. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +7 -0
  73. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
  74. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +7 -3
  75. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  76. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +3 -9
  77. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  78. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +7 -0
  79. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -1
  80. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +13 -22
  81. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
  82. package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +7 -0
  83. package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -1
  84. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +5 -8
  85. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +1 -1
  86. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +6 -6
  87. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
  88. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +7 -0
  89. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +1 -1
  90. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +17 -3
  91. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
  92. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +9 -10
  93. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
  94. package/dist-server/server/modules/providers/provider.routes.js +123 -1
  95. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  96. package/dist-server/server/modules/providers/services/mcp.service.js +23 -0
  97. package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -1
  98. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +65 -0
  99. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -0
  100. package/dist-server/server/modules/providers/services/provider-models.service.js +1 -1
  101. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
  102. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +62 -23
  103. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  104. package/dist-server/server/modules/providers/services/sessions.service.js +62 -4
  105. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  106. package/dist-server/server/modules/providers/services/skills.service.js +11 -0
  107. package/dist-server/server/modules/providers/services/skills.service.js.map +1 -1
  108. package/dist-server/server/modules/providers/shared/skills/skills.provider.js +163 -1
  109. package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -1
  110. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +45 -0
  111. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  112. package/dist-server/server/modules/providers/tests/skills.test.js +160 -0
  113. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  114. package/dist-server/server/modules/websocket/index.js +1 -0
  115. package/dist-server/server/modules/websocket/index.js.map +1 -1
  116. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +252 -0
  117. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -0
  118. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +105 -0
  119. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -0
  120. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +218 -156
  121. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  122. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +83 -31
  123. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  124. package/dist-server/server/modules/websocket/services/websocket-server.service.js +5 -0
  125. package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -1
  126. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +216 -0
  127. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -0
  128. package/dist-server/server/openai-codex.js +24 -14
  129. package/dist-server/server/openai-codex.js.map +1 -1
  130. package/dist-server/server/opencode-cli.js +21 -8
  131. package/dist-server/server/opencode-cli.js.map +1 -1
  132. package/dist-server/server/opencode-cli.test.js +19 -1
  133. package/dist-server/server/opencode-cli.test.js.map +1 -1
  134. package/dist-server/server/routes/settings.js +1 -1
  135. package/dist-server/server/routes/settings.js.map +1 -1
  136. package/dist-server/server/services/notification-orchestrator.js +1 -176
  137. package/dist-server/server/services/notification-orchestrator.js.map +1 -1
  138. package/dist-server/server/services/tests/notification-orchestrator.test.js +64 -0
  139. package/dist-server/server/services/tests/notification-orchestrator.test.js.map +1 -0
  140. package/dist-server/server/shared/{claude-cli-path.test.js → tests/claude-cli-path.test.js} +1 -1
  141. package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -0
  142. package/dist-server/server/shared/tests/slice-tail-page.test.js +35 -0
  143. package/dist-server/server/shared/tests/slice-tail-page.test.js.map +1 -0
  144. package/dist-server/server/shared/utils.js +73 -1
  145. package/dist-server/server/shared/utils.js.map +1 -1
  146. package/dist-server/server/voice-proxy.js +226 -0
  147. package/dist-server/server/voice-proxy.js.map +1 -0
  148. package/electron/assets/logo-macos.icns +0 -0
  149. package/electron/assets/logo-macos.png +0 -0
  150. package/electron/assets/logo-windows.ico +0 -0
  151. package/electron/cloud.js +260 -0
  152. package/electron/desktopNotifications.js +378 -0
  153. package/electron/desktopWindow.js +766 -0
  154. package/electron/launcher/index.html +14 -0
  155. package/electron/launcher/launcher.css +801 -0
  156. package/electron/launcher/launcher.js +687 -0
  157. package/electron/localServer.js +550 -0
  158. package/electron/main.js +944 -0
  159. package/electron/preload.cjs +60 -0
  160. package/electron/scripts/generate-macos-icon.js +62 -0
  161. package/electron/serverInstaller.js +277 -0
  162. package/electron/tabs.js +87 -0
  163. package/electron/viewHost.js +331 -0
  164. package/package.json +79 -3
  165. package/public/api-docs.html +36 -18
  166. package/scripts/release/build-server-bundle.js +176 -0
  167. package/scripts/release/prepare-desktop-app.js +152 -0
  168. package/server/browser-use-mcp.ts +384 -0
  169. package/server/claude-sdk.js +27 -5
  170. package/server/cli.js +15 -6
  171. package/server/cursor-cli.js +27 -11
  172. package/server/gemini-cli.js +17 -2
  173. package/server/index.js +137 -47
  174. package/server/load-env.js +1 -1
  175. package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -0
  176. package/server/modules/browser-use/browser-use.routes.ts +96 -0
  177. package/server/modules/browser-use/browser-use.service.ts +836 -0
  178. package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -0
  179. package/server/modules/database/index.ts +1 -0
  180. package/server/modules/database/migrations.ts +25 -0
  181. package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -0
  182. package/server/modules/database/repositories/notification-preferences.ts +12 -1
  183. package/server/modules/database/repositories/sessions.db.ts +227 -23
  184. package/server/modules/database/schema.ts +27 -0
  185. package/server/modules/database/tests/sessions-provider-mapping.test.ts +108 -0
  186. package/server/modules/database/{repositories → tests}/sessions.db.integration.test.ts +12 -0
  187. package/server/modules/notifications/index.ts +13 -0
  188. package/server/modules/notifications/notifications.routes.ts +127 -0
  189. package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -0
  190. package/server/modules/notifications/services/notification-orchestrator.service.js +288 -0
  191. package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -0
  192. package/server/modules/projects/services/project-management.service.ts +0 -8
  193. package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +12 -53
  194. package/server/modules/providers/README.md +1 -1
  195. package/server/modules/providers/index.ts +1 -0
  196. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +26 -1
  197. package/server/modules/providers/list/claude/claude-sessions.provider.ts +9 -14
  198. package/server/modules/providers/list/claude/claude-skills.provider.ts +8 -0
  199. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +7 -3
  200. package/server/modules/providers/list/codex/codex-sessions.provider.ts +3 -12
  201. package/server/modules/providers/list/codex/codex-skills.provider.ts +8 -0
  202. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +13 -22
  203. package/server/modules/providers/list/cursor/cursor-skills.provider.ts +8 -0
  204. package/server/modules/providers/list/gemini/gemini-models.provider.ts +5 -8
  205. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +6 -6
  206. package/server/modules/providers/list/gemini/gemini-skills.provider.ts +8 -0
  207. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +18 -4
  208. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +9 -12
  209. package/server/modules/providers/provider.routes.ts +167 -1
  210. package/server/modules/providers/services/mcp.service.ts +26 -0
  211. package/server/modules/providers/services/provider-capabilities.service.ts +91 -0
  212. package/server/modules/providers/services/provider-models.service.ts +1 -1
  213. package/server/modules/providers/services/sessions-watcher.service.ts +71 -25
  214. package/server/modules/providers/services/sessions.service.ts +79 -4
  215. package/server/modules/providers/services/skills.service.ts +25 -1
  216. package/server/modules/providers/shared/skills/skills.provider.ts +223 -0
  217. package/server/modules/providers/tests/opencode-sessions.test.ts +49 -0
  218. package/server/modules/providers/tests/skills.test.ts +212 -0
  219. package/server/modules/websocket/README.md +33 -27
  220. package/server/modules/websocket/index.ts +1 -0
  221. package/server/modules/websocket/services/chat-run-registry.service.ts +327 -0
  222. package/server/modules/websocket/services/chat-session-writer.service.ts +145 -0
  223. package/server/modules/websocket/services/chat-websocket.service.ts +292 -207
  224. package/server/modules/websocket/services/shell-websocket.service.ts +108 -32
  225. package/server/modules/websocket/services/websocket-server.service.ts +6 -0
  226. package/server/modules/websocket/tests/chat-run-registry.test.ts +244 -0
  227. package/server/openai-codex.js +24 -14
  228. package/server/opencode-cli.js +21 -8
  229. package/server/opencode-cli.test.js +20 -1
  230. package/server/routes/settings.js +6 -1
  231. package/server/services/notification-orchestrator.js +3 -224
  232. package/server/services/tests/notification-orchestrator.test.js +80 -0
  233. package/server/shared/interfaces.ts +15 -0
  234. package/server/shared/tests/slice-tail-page.test.ts +42 -0
  235. package/server/shared/types.ts +84 -1
  236. package/server/shared/utils.ts +95 -1
  237. package/server/voice-proxy.js +224 -0
  238. package/dist/assets/index-73Xw3i9v.css +0 -32
  239. package/dist/assets/index-BIHaviaA.js +0 -1361
  240. package/dist/modelConstants.js +0 -854
  241. package/dist-server/server/modules/database/repositories/projects.db.integration.test.js.map +0 -1
  242. package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js.map +0 -1
  243. package/dist-server/server/shared/claude-cli-path.test.js.map +0 -1
  244. package/public/modelConstants.js +0 -854
  245. /package/dist-server/server/modules/database/{repositories → tests}/projects.db.integration.test.js +0 -0
  246. /package/server/modules/database/{repositories → tests}/projects.db.integration.test.ts +0 -0
  247. /package/server/shared/{claude-cli-path.test.ts → tests/claude-cli-path.test.ts} +0 -0
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+ import crypto from 'node:crypto';
3
+ import { createReadStream, readFileSync } from 'node:fs';
4
+ import fs from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import { spawn } from 'node:child_process';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const rootDir = path.resolve(__dirname, '..', '..');
11
+ const packageJson = JSON.parse(
12
+ await fs.readFile(path.join(rootDir, 'package.json'), 'utf8'),
13
+ );
14
+
15
+ function getElectronVersion() {
16
+ try {
17
+ return JSON.parse(
18
+ readFileSync(path.join(rootDir, 'node_modules', 'electron', 'package.json'), 'utf8'),
19
+ ).version;
20
+ } catch {
21
+ try {
22
+ return JSON.parse(
23
+ readFileSync(path.join(rootDir, 'package-lock.json'), 'utf8'),
24
+ ).packages['node_modules/electron'].version;
25
+ } catch {
26
+ throw new Error('Could not resolve an exact Electron version for server native rebuild.');
27
+ }
28
+ }
29
+ }
30
+
31
+ function mapArch(arch = process.arch) {
32
+ return arch === 'arm64' ? 'arm64' : 'x64';
33
+ }
34
+
35
+ function mapPlatform(platform = process.platform) {
36
+ if (platform === 'darwin') return 'mac';
37
+ if (platform === 'win32') return 'win';
38
+ return 'linux';
39
+ }
40
+
41
+ function run(command, args, options = {}) {
42
+ return new Promise((resolve, reject) => {
43
+ const child = spawn(command, args, {
44
+ stdio: 'inherit',
45
+ shell: process.platform === 'win32',
46
+ ...options,
47
+ });
48
+ child.once('error', reject);
49
+ child.once('exit', (code) => {
50
+ if (code === 0) resolve();
51
+ else reject(new Error(`${command} ${args.join(' ')} exited with code ${code}`));
52
+ });
53
+ });
54
+ }
55
+
56
+ async function pathExists(filePath) {
57
+ try {
58
+ await fs.access(filePath);
59
+ return true;
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ async function copyRequired(stageDir, relativePath) {
66
+ const from = path.join(rootDir, relativePath);
67
+ if (!(await pathExists(from))) {
68
+ throw new Error(`Required server bundle input is missing: ${relativePath}`);
69
+ }
70
+ await fs.cp(from, path.join(stageDir, relativePath), { recursive: true });
71
+ }
72
+
73
+ async function copyIfExists(stageDir, relativePath) {
74
+ const from = path.join(rootDir, relativePath);
75
+ if (!(await pathExists(from))) return;
76
+ await fs.cp(from, path.join(stageDir, relativePath), { recursive: true });
77
+ }
78
+
79
+ async function writeServerPackageJson(stageDir) {
80
+ const stagedPackageJson = {
81
+ ...packageJson,
82
+ scripts: {
83
+ ...(packageJson.scripts || {}),
84
+ },
85
+ };
86
+ // The bundle stage is not a git checkout with dev dependencies, so lifecycle
87
+ // scripts such as Husky prepare must not run there. Dependency install scripts
88
+ // still run; native modules need them before the Electron ABI rebuild below.
89
+ delete stagedPackageJson.scripts.postinstall;
90
+ delete stagedPackageJson.scripts.prepare;
91
+ delete stagedPackageJson.scripts.prepublishOnly;
92
+ await fs.writeFile(
93
+ path.join(stageDir, 'package.json'),
94
+ `${JSON.stringify(stagedPackageJson, null, 2)}\n`,
95
+ 'utf8',
96
+ );
97
+ }
98
+
99
+ function sha256(filePath) {
100
+ return new Promise((resolve, reject) => {
101
+ const hash = crypto.createHash('sha256');
102
+ const stream = createReadStream(filePath);
103
+ stream.on('data', (chunk) => hash.update(chunk));
104
+ stream.on('end', () => resolve(hash.digest('hex')));
105
+ stream.on('error', reject);
106
+ });
107
+ }
108
+
109
+ const platform = mapPlatform(process.env.CLOUDCLI_BUNDLE_PLATFORM || process.platform);
110
+ const arch = mapArch(process.env.CLOUDCLI_BUNDLE_ARCH || process.arch);
111
+ const version = packageJson.version;
112
+ const bundleName = `cloudcli-local-server-${version}-${platform}-${arch}.tar.gz`;
113
+ const bundleRoot = path.join(rootDir, 'release', 'local-server');
114
+ const stageDir = path.join(bundleRoot, `.stage-${version}-${platform}-${arch}`);
115
+ const archivePath = path.join(bundleRoot, bundleName);
116
+
117
+ await fs.rm(stageDir, { recursive: true, force: true });
118
+ await fs.mkdir(stageDir, { recursive: true });
119
+ await fs.mkdir(bundleRoot, { recursive: true });
120
+
121
+ await copyRequired(stageDir, 'dist');
122
+ await copyRequired(stageDir, 'dist-server');
123
+ await copyRequired(stageDir, 'public');
124
+ await copyRequired(stageDir, 'shared');
125
+ await copyRequired(stageDir, 'package-lock.json');
126
+ await copyIfExists(stageDir, 'scripts/fix-node-pty.js');
127
+ await writeServerPackageJson(stageDir);
128
+
129
+ console.log('Installing production server dependencies into bundle stage...');
130
+ await run('npm', ['ci', '--omit=dev'], {
131
+ cwd: stageDir,
132
+ env: {
133
+ ...process.env,
134
+ npm_config_audit: 'false',
135
+ npm_config_fund: 'false',
136
+ },
137
+ });
138
+
139
+ const electronVersion = getElectronVersion();
140
+ const electronRebuild = process.platform === 'win32'
141
+ ? path.join(rootDir, 'node_modules', '.bin', 'electron-rebuild.cmd')
142
+ : path.join(rootDir, 'node_modules', '.bin', 'electron-rebuild');
143
+ console.log(`Rebuilding native server dependencies for Electron ${electronVersion} (${arch})...`);
144
+ await run(electronRebuild, ['--version', electronVersion, '--module-dir', stageDir, '--arch', arch, '--force'], {
145
+ cwd: rootDir,
146
+ env: {
147
+ ...process.env,
148
+ npm_config_audit: 'false',
149
+ npm_config_fund: 'false',
150
+ },
151
+ });
152
+
153
+ if (await pathExists(path.join(stageDir, 'scripts', 'fix-node-pty.js'))) {
154
+ await run(process.execPath, ['scripts/fix-node-pty.js'], { cwd: stageDir });
155
+ }
156
+
157
+ await fs.writeFile(
158
+ path.join(stageDir, '.installed.json'),
159
+ JSON.stringify({ version, platform, arch, builtAt: new Date().toISOString() }, null, 2),
160
+ 'utf8',
161
+ );
162
+
163
+ await fs.rm(archivePath, { force: true });
164
+ const tarArgs = process.platform === 'win32'
165
+ ? ['-czf', archivePath, '-C', stageDir, '.']
166
+ : ['-czf', archivePath, '-C', stageDir, '.'];
167
+ await run('tar', tarArgs);
168
+
169
+ const digest = await sha256(archivePath);
170
+ const checksumPath = `${archivePath}.sha256`;
171
+ await fs.writeFile(checksumPath, `${digest} ${bundleName}\n`, 'utf8');
172
+ await fs.rm(stageDir, { recursive: true, force: true });
173
+
174
+ const size = (await fs.stat(archivePath)).size / 1024 / 1024;
175
+ console.log(`Wrote ${path.relative(rootDir, archivePath)} (${size.toFixed(1)} MB)`);
176
+ console.log(`Wrote ${path.relative(rootDir, checksumPath)}`);
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const rootDir = path.resolve(__dirname, '..', '..');
9
+ const stageDir = path.join(rootDir, '.desktop-build', 'desktop-app');
10
+
11
+ const packageJson = JSON.parse(
12
+ await fs.readFile(path.join(rootDir, 'package.json'), 'utf8'),
13
+ );
14
+
15
+ function getElectronVersion() {
16
+ try {
17
+ return JSON.parse(
18
+ readFileSync(path.join(rootDir, 'node_modules', 'electron', 'package.json'), 'utf8'),
19
+ ).version;
20
+ } catch {
21
+ try {
22
+ return JSON.parse(
23
+ readFileSync(path.join(rootDir, 'package-lock.json'), 'utf8'),
24
+ ).packages['node_modules/electron'].version;
25
+ } catch {
26
+ throw new Error('Could not resolve an exact Electron version for desktop packaging.');
27
+ }
28
+ }
29
+ }
30
+
31
+ async function pathExists(filePath) {
32
+ try {
33
+ await fs.access(filePath);
34
+ return true;
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ async function copyRequired(relativePath) {
41
+ const from = path.join(rootDir, relativePath);
42
+ const to = path.join(stageDir, relativePath);
43
+ if (!(await pathExists(from))) {
44
+ throw new Error(`Required desktop build input is missing: ${relativePath}`);
45
+ }
46
+ await fs.cp(from, to, { recursive: true });
47
+ }
48
+
49
+ async function copyIfExists(relativePath) {
50
+ const from = path.join(rootDir, relativePath);
51
+ if (!(await pathExists(from))) return false;
52
+ await fs.cp(from, path.join(stageDir, relativePath), { recursive: true });
53
+ return true;
54
+ }
55
+
56
+ async function copyNodeModule(packageName) {
57
+ const parts = packageName.split('/');
58
+ const source = path.join(rootDir, 'node_modules', ...parts);
59
+ if (!(await pathExists(source))) return false;
60
+
61
+ const target = path.join(stageDir, 'node_modules', ...parts);
62
+ await fs.mkdir(path.dirname(target), { recursive: true });
63
+ await fs.cp(source, target, { recursive: true });
64
+ return true;
65
+ }
66
+
67
+ function buildDesktopPackageJson(copiedOptionalDependencies) {
68
+ return {
69
+ name: `${packageJson.name}-desktop`,
70
+ version: packageJson.version,
71
+ productName: packageJson.productName,
72
+ description: `${packageJson.productName} desktop shell`,
73
+ author: packageJson.author,
74
+ license: packageJson.license,
75
+ type: 'module',
76
+ main: 'electron/main.js',
77
+ dependencies: {
78
+ ws: packageJson.dependencies.ws,
79
+ },
80
+ optionalDependencies: copiedOptionalDependencies,
81
+ build: {
82
+ appId: packageJson.build.appId,
83
+ productName: packageJson.build.productName,
84
+ asar: packageJson.build.asar,
85
+ artifactName: packageJson.build.artifactName,
86
+ electronVersion: getElectronVersion(),
87
+ directories: {
88
+ output: '../../release/desktop',
89
+ },
90
+ extraMetadata: {
91
+ main: 'electron/main.js',
92
+ },
93
+ files: [
94
+ 'electron/**',
95
+ 'public/**',
96
+ 'dist/**',
97
+ 'dist-server/**',
98
+ 'node_modules/**',
99
+ 'package.json',
100
+ ],
101
+ protocols: packageJson.build.protocols,
102
+ mac: packageJson.build.mac,
103
+ win: packageJson.build.win,
104
+ nsis: packageJson.build.nsis,
105
+ },
106
+ };
107
+ }
108
+
109
+ await fs.rm(stageDir, { recursive: true, force: true });
110
+ await fs.mkdir(stageDir, { recursive: true });
111
+
112
+ await copyRequired('electron');
113
+ await copyRequired('dist');
114
+ await copyRequired('public');
115
+
116
+ const copiedRuntimeDependencies = [];
117
+ if (await copyNodeModule('ws')) {
118
+ copiedRuntimeDependencies.push('ws');
119
+ } else {
120
+ throw new Error('Required desktop dependency is missing from node_modules: ws');
121
+ }
122
+
123
+ const copiedOptionalDependencies = {};
124
+ for (const [name, version] of Object.entries(packageJson.optionalDependencies || {})) {
125
+ if (await copyNodeModule(name)) {
126
+ copiedOptionalDependencies[name] = version;
127
+ }
128
+ }
129
+
130
+ for (const name of [
131
+ '@nut-tree-fork/default-clipboard-provider',
132
+ '@nut-tree-fork/libnut',
133
+ '@nut-tree-fork/provider-interfaces',
134
+ '@nut-tree-fork/shared',
135
+ 'jimp',
136
+ 'node-abort-controller',
137
+ 'temp',
138
+ ]) {
139
+ await copyNodeModule(name);
140
+ }
141
+
142
+ await fs.writeFile(
143
+ path.join(stageDir, 'package.json'),
144
+ `${JSON.stringify(buildDesktopPackageJson(copiedOptionalDependencies), null, 2)}\n`,
145
+ 'utf8',
146
+ );
147
+
148
+ console.log(`Prepared thin desktop app at ${path.relative(rootDir, stageDir)}`);
149
+ console.log(`Runtime dependencies: ${copiedRuntimeDependencies.join(', ')}`);
150
+ if (Object.keys(copiedOptionalDependencies).length) {
151
+ console.log(`Optional dependencies: ${Object.keys(copiedOptionalDependencies).join(', ')}`);
152
+ }
@@ -0,0 +1,384 @@
1
+ #!/usr/bin/env node
2
+ import './load-env.js';
3
+
4
+ type JsonRpcRequest = {
5
+ jsonrpc: '2.0';
6
+ id?: string | number | null;
7
+ method: string;
8
+ params?: Record<string, unknown>;
9
+ };
10
+
11
+ type ToolDefinition = {
12
+ name: string;
13
+ description: string;
14
+ inputSchema: Record<string, unknown>;
15
+ };
16
+
17
+ const textResponse = (text: string) => ({
18
+ content: [{ type: 'text', text }],
19
+ });
20
+
21
+ const jsonResponse = (value: unknown) => textResponse(JSON.stringify(value, null, 2));
22
+
23
+ const readString = (value: unknown, name: string): string => {
24
+ if (typeof value !== 'string' || value.trim() === '') {
25
+ throw new Error(`${name} is required.`);
26
+ }
27
+ return value.trim();
28
+ };
29
+
30
+ const readOptionalString = (value: unknown): string | undefined =>
31
+ typeof value === 'string' && value.trim() ? value.trim() : undefined;
32
+
33
+ const readNumber = (value: unknown): number | undefined =>
34
+ typeof value === 'number' && Number.isFinite(value) ? value : undefined;
35
+
36
+ const apiUrl = (process.env.CLOUDCLI_BROWSER_USE_API_URL || 'http://127.0.0.1:3001/api/browser-use-mcp').replace(/\/$/, '');
37
+ const apiToken = process.env.CLOUDCLI_BROWSER_USE_MCP_TOKEN || '';
38
+ const API_TIMEOUT_MS = Number.parseInt(process.env.CLOUDCLI_BROWSER_USE_API_TIMEOUT_MS || '60000', 10);
39
+
40
+ async function callBrowserUseApi(toolName: string, input: Record<string, unknown>) {
41
+ if (!apiToken) {
42
+ throw new Error('CLOUDCLI_BROWSER_USE_MCP_TOKEN is not configured.');
43
+ }
44
+
45
+ const response = await fetch(`${apiUrl}/tools/${encodeURIComponent(toolName)}`, {
46
+ method: 'POST',
47
+ headers: {
48
+ Authorization: `Bearer ${apiToken}`,
49
+ 'Content-Type': 'application/json',
50
+ },
51
+ body: JSON.stringify(input),
52
+ signal: AbortSignal.timeout(API_TIMEOUT_MS),
53
+ });
54
+ const data = await response.json() as { success?: boolean; data?: unknown; error?: string };
55
+ if (!response.ok || data.success === false) {
56
+ throw new Error(data.error || `Browser API request failed (${response.status})`);
57
+ }
58
+ return data.data;
59
+ }
60
+
61
+ const sessionIdSchema = {
62
+ type: 'object',
63
+ properties: {
64
+ sessionId: { type: 'string', description: 'Browser session id.' },
65
+ },
66
+ required: ['sessionId'],
67
+ };
68
+
69
+ const tools: ToolDefinition[] = [
70
+ {
71
+ name: 'browser_create_session',
72
+ description: 'Create a temporary Browser session that the agent can control. Optionally provide a background profileName to reuse cookies and storage.',
73
+ inputSchema: {
74
+ type: 'object',
75
+ properties: {
76
+ profileName: { type: 'string', description: 'Optional background profile name for persistent browser storage.' },
77
+ },
78
+ },
79
+ },
80
+ {
81
+ name: 'browser_list_sessions',
82
+ description: 'List Browser sessions currently available to agents.',
83
+ inputSchema: { type: 'object', properties: {} },
84
+ },
85
+ {
86
+ name: 'browser_snapshot',
87
+ description: 'Capture current page metadata, screenshot data URL, and visible body text for a Browser session.',
88
+ inputSchema: sessionIdSchema,
89
+ },
90
+ {
91
+ name: 'browser_take_screenshot',
92
+ description: 'Capture the latest screenshot for a Browser session.',
93
+ inputSchema: sessionIdSchema,
94
+ },
95
+ {
96
+ name: 'browser_navigate',
97
+ description: 'Navigate a Browser session to an HTTP or HTTPS URL.',
98
+ inputSchema: {
99
+ type: 'object',
100
+ properties: {
101
+ sessionId: { type: 'string' },
102
+ url: { type: 'string' },
103
+ },
104
+ required: ['sessionId', 'url'],
105
+ },
106
+ },
107
+ {
108
+ name: 'browser_click',
109
+ description: 'Click an element by CSS selector, visible text, or x/y coordinates.',
110
+ inputSchema: {
111
+ type: 'object',
112
+ properties: {
113
+ sessionId: { type: 'string' },
114
+ selector: { type: 'string' },
115
+ text: { type: 'string' },
116
+ x: { type: 'number' },
117
+ y: { type: 'number' },
118
+ },
119
+ required: ['sessionId'],
120
+ },
121
+ },
122
+ {
123
+ name: 'browser_type',
124
+ description: 'Type text into the focused page or fill a CSS selector. Set submit to press Enter after typing.',
125
+ inputSchema: {
126
+ type: 'object',
127
+ properties: {
128
+ sessionId: { type: 'string' },
129
+ selector: { type: 'string' },
130
+ text: { type: 'string' },
131
+ submit: { type: 'boolean' },
132
+ },
133
+ required: ['sessionId', 'text'],
134
+ },
135
+ },
136
+ {
137
+ name: 'browser_fill_form',
138
+ description: 'Fill multiple form fields using CSS selectors.',
139
+ inputSchema: {
140
+ type: 'object',
141
+ properties: {
142
+ sessionId: { type: 'string' },
143
+ fields: {
144
+ type: 'array',
145
+ items: {
146
+ type: 'object',
147
+ properties: {
148
+ selector: { type: 'string' },
149
+ value: { type: 'string' },
150
+ },
151
+ required: ['selector', 'value'],
152
+ },
153
+ },
154
+ },
155
+ required: ['sessionId', 'fields'],
156
+ },
157
+ },
158
+ {
159
+ name: 'browser_press_key',
160
+ description: 'Press a keyboard key, for example Enter, Escape, Tab, or Control+A.',
161
+ inputSchema: {
162
+ type: 'object',
163
+ properties: {
164
+ sessionId: { type: 'string' },
165
+ key: { type: 'string' },
166
+ },
167
+ required: ['sessionId', 'key'],
168
+ },
169
+ },
170
+ {
171
+ name: 'browser_select_option',
172
+ description: 'Select option values in a select element found by CSS selector.',
173
+ inputSchema: {
174
+ type: 'object',
175
+ properties: {
176
+ sessionId: { type: 'string' },
177
+ selector: { type: 'string' },
178
+ values: { type: 'array', items: { type: 'string' } },
179
+ },
180
+ required: ['sessionId', 'selector', 'values'],
181
+ },
182
+ },
183
+ {
184
+ name: 'browser_wait_for',
185
+ description: 'Wait for visible text, a URL pattern, or a short timeout.',
186
+ inputSchema: {
187
+ type: 'object',
188
+ properties: {
189
+ sessionId: { type: 'string' },
190
+ text: { type: 'string' },
191
+ url: { type: 'string' },
192
+ timeoutMs: { type: 'number' },
193
+ },
194
+ required: ['sessionId'],
195
+ },
196
+ },
197
+ {
198
+ name: 'browser_tabs',
199
+ description: 'List, open, select, or close tabs in a Browser session.',
200
+ inputSchema: {
201
+ type: 'object',
202
+ properties: {
203
+ sessionId: { type: 'string' },
204
+ action: { type: 'string', enum: ['list', 'new', 'select', 'close'] },
205
+ index: { type: 'number' },
206
+ url: { type: 'string' },
207
+ },
208
+ required: ['sessionId'],
209
+ },
210
+ },
211
+ {
212
+ name: 'browser_close_session',
213
+ description: 'Stop a Browser session controlled by agents.',
214
+ inputSchema: sessionIdSchema,
215
+ },
216
+ ];
217
+
218
+ async function callTool(name: string, args: Record<string, unknown>) {
219
+ switch (name) {
220
+ case 'browser_create_session':
221
+ return jsonResponse(await callBrowserUseApi(name, {
222
+ profileName: readOptionalString(args.profileName),
223
+ }));
224
+ case 'browser_list_sessions':
225
+ return jsonResponse(await callBrowserUseApi(name, {}));
226
+ case 'browser_snapshot':
227
+ return jsonResponse(await callBrowserUseApi(name, { sessionId: readString(args.sessionId, 'sessionId') }));
228
+ case 'browser_take_screenshot': {
229
+ return jsonResponse(await callBrowserUseApi(name, { sessionId: readString(args.sessionId, 'sessionId') }));
230
+ }
231
+ case 'browser_navigate':
232
+ return jsonResponse(await callBrowserUseApi(name, {
233
+ sessionId: readString(args.sessionId, 'sessionId'),
234
+ url: readString(args.url, 'url'),
235
+ }));
236
+ case 'browser_click':
237
+ return jsonResponse(await callBrowserUseApi(name, {
238
+ sessionId: readString(args.sessionId, 'sessionId'),
239
+ selector: readOptionalString(args.selector),
240
+ text: readOptionalString(args.text),
241
+ x: readNumber(args.x),
242
+ y: readNumber(args.y),
243
+ }));
244
+ case 'browser_type':
245
+ return jsonResponse(await callBrowserUseApi(name, {
246
+ sessionId: readString(args.sessionId, 'sessionId'),
247
+ selector: readOptionalString(args.selector),
248
+ text: readString(args.text, 'text'),
249
+ submit: args.submit === true,
250
+ }));
251
+ case 'browser_fill_form': {
252
+ const fields = Array.isArray(args.fields)
253
+ ? args.fields.map((field) => {
254
+ const record = field as Record<string, unknown>;
255
+ return {
256
+ selector: readString(record.selector, 'field.selector'),
257
+ value: readString(record.value, 'field.value'),
258
+ };
259
+ })
260
+ : [];
261
+ return jsonResponse(await callBrowserUseApi(name, {
262
+ sessionId: readString(args.sessionId, 'sessionId'),
263
+ fields,
264
+ }));
265
+ }
266
+ case 'browser_press_key':
267
+ return jsonResponse(await callBrowserUseApi(name, {
268
+ sessionId: readString(args.sessionId, 'sessionId'),
269
+ key: readString(args.key, 'key'),
270
+ }));
271
+ case 'browser_select_option':
272
+ return jsonResponse(await callBrowserUseApi(name, {
273
+ sessionId: readString(args.sessionId, 'sessionId'),
274
+ selector: readString(args.selector, 'selector'),
275
+ values: Array.isArray(args.values) ? args.values.filter((value): value is string => typeof value === 'string') : [],
276
+ }));
277
+ case 'browser_wait_for':
278
+ return jsonResponse(await callBrowserUseApi(name, {
279
+ sessionId: readString(args.sessionId, 'sessionId'),
280
+ text: readOptionalString(args.text),
281
+ url: readOptionalString(args.url),
282
+ timeoutMs: readNumber(args.timeoutMs),
283
+ }));
284
+ case 'browser_tabs':
285
+ return jsonResponse(await callBrowserUseApi(name, {
286
+ sessionId: readString(args.sessionId, 'sessionId'),
287
+ action: args.action === 'new' || args.action === 'select' || args.action === 'close' || args.action === 'list'
288
+ ? args.action
289
+ : undefined,
290
+ index: readNumber(args.index),
291
+ url: readOptionalString(args.url),
292
+ }));
293
+ case 'browser_close_session':
294
+ return jsonResponse(await callBrowserUseApi(name, { sessionId: readString(args.sessionId, 'sessionId') }));
295
+ default:
296
+ throw new Error(`Unknown tool: ${name}`);
297
+ }
298
+ }
299
+
300
+ async function handleMessage(message: JsonRpcRequest) {
301
+ if (message.method === 'initialize') {
302
+ return {
303
+ protocolVersion: '2024-11-05',
304
+ capabilities: { tools: {} },
305
+ serverInfo: { name: 'cloudcli-browser', version: '1.0.0' },
306
+ };
307
+ }
308
+
309
+ if (message.method === 'tools/list') {
310
+ return { tools };
311
+ }
312
+
313
+ if (message.method === 'tools/call') {
314
+ const params = message.params || {};
315
+ const name = readString(params.name, 'name');
316
+ const args = (params.arguments && typeof params.arguments === 'object'
317
+ ? params.arguments
318
+ : {}) as Record<string, unknown>;
319
+ return callTool(name, args);
320
+ }
321
+
322
+ if (message.method.startsWith('notifications/')) {
323
+ return undefined;
324
+ }
325
+
326
+ throw new Error(`Unsupported method: ${message.method}`);
327
+ }
328
+
329
+ function writeMessage(message: Record<string, unknown>) {
330
+ // MCP stdio transport uses newline-delimited JSON (one JSON-RPC message per line,
331
+ // no embedded newlines). This is NOT the LSP Content-Length framing.
332
+ process.stdout.write(`${JSON.stringify(message)}\n`);
333
+ }
334
+
335
+ function sendResult(id: string | number | null | undefined, result: unknown) {
336
+ if (id === undefined) {
337
+ return;
338
+ }
339
+ writeMessage({ jsonrpc: '2.0', id, result });
340
+ }
341
+
342
+ function sendError(id: string | number | null | undefined, error: unknown) {
343
+ if (id === undefined) {
344
+ return;
345
+ }
346
+ writeMessage({
347
+ jsonrpc: '2.0',
348
+ id,
349
+ error: {
350
+ code: -32000,
351
+ message: error instanceof Error ? error.message : String(error),
352
+ },
353
+ });
354
+ }
355
+
356
+ let buffer = '';
357
+
358
+ process.stdin.on('data', (chunk) => {
359
+ buffer += chunk.toString('utf8');
360
+ let newlineIndex: number;
361
+ while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
362
+ const rawMessage = buffer.slice(0, newlineIndex).trim();
363
+ buffer = buffer.slice(newlineIndex + 1);
364
+ if (!rawMessage) {
365
+ continue;
366
+ }
367
+
368
+ void (async () => {
369
+ let request: JsonRpcRequest;
370
+ try {
371
+ request = JSON.parse(rawMessage) as JsonRpcRequest;
372
+ } catch (error) {
373
+ sendError(null, error);
374
+ return;
375
+ }
376
+ try {
377
+ const result = await handleMessage(request);
378
+ sendResult(request.id, result);
379
+ } catch (error) {
380
+ sendError(request.id, error);
381
+ }
382
+ })();
383
+ }
384
+ });