@cloudcli-ai/cloudcli 1.35.1 → 1.36.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 (223) hide show
  1. package/README.de.md +17 -18
  2. package/README.ja.md +16 -17
  3. package/README.ko.md +17 -18
  4. package/README.md +19 -20
  5. package/README.ru.md +17 -18
  6. package/README.tr.md +17 -18
  7. package/README.zh-CN.md +17 -18
  8. package/README.zh-TW.md +17 -18
  9. package/dist/api-docs.html +8 -3
  10. package/dist/assets/{index-D1IdCTT6.js → index-Bz9vXV2m.js} +265 -260
  11. package/dist/assets/index-DLmhQ15L.css +32 -0
  12. package/dist/assets/{vendor-xterm-CJZjLICi.js → vendor-xterm-CS4rQ5Mr.js} +12 -12
  13. package/dist/index.html +3 -3
  14. package/dist-server/server/claude-sdk.js +62 -116
  15. package/dist-server/server/claude-sdk.js.map +1 -1
  16. package/dist-server/server/cli.js +1 -4
  17. package/dist-server/server/cli.js.map +1 -1
  18. package/dist-server/server/cursor-cli.js +12 -11
  19. package/dist-server/server/cursor-cli.js.map +1 -1
  20. package/dist-server/server/index.js +10 -141
  21. package/dist-server/server/index.js.map +1 -1
  22. package/dist-server/server/modules/assets/assets.routes.js +89 -0
  23. package/dist-server/server/modules/assets/assets.routes.js.map +1 -0
  24. package/dist-server/server/modules/assets/index.js +4 -0
  25. package/dist-server/server/modules/assets/index.js.map +1 -0
  26. package/dist-server/server/modules/assets/services/image-assets.service.js +57 -0
  27. package/dist-server/server/modules/assets/services/image-assets.service.js.map +1 -0
  28. package/dist-server/server/modules/assets/tests/image-assets.service.test.js +36 -0
  29. package/dist-server/server/modules/assets/tests/image-assets.service.test.js.map +1 -0
  30. package/dist-server/server/modules/browser-use/browser-use.service.js +6 -3
  31. package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
  32. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +2 -2
  33. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -1
  34. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +0 -1
  35. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
  36. package/dist-server/server/modules/projects/services/project-clone.service.js +2 -1
  37. package/dist-server/server/modules/projects/services/project-clone.service.js.map +1 -1
  38. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +70 -1
  39. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  40. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +28 -0
  41. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  42. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +52 -11
  43. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  44. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +54 -1
  45. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  46. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +36 -1
  47. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  48. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +3 -17
  49. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -1
  50. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js +7 -1
  51. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  52. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +49 -13
  53. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
  54. package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js +0 -1
  55. package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js.map +1 -1
  56. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +145 -14
  57. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  58. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +24 -5
  59. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
  60. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +10 -21
  61. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
  62. package/dist-server/server/modules/providers/provider.registry.js +0 -2
  63. package/dist-server/server/modules/providers/provider.registry.js.map +1 -1
  64. package/dist-server/server/modules/providers/provider.routes.js +0 -1
  65. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  66. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +11 -13
  67. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  68. package/dist-server/server/modules/providers/services/provider-models.service.js +2 -2
  69. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
  70. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +3 -77
  71. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
  72. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +0 -1
  73. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  74. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +0 -13
  75. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  76. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +96 -0
  77. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -0
  78. package/dist-server/server/modules/providers/tests/mcp.test.js +3 -29
  79. package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -1
  80. package/dist-server/server/modules/providers/tests/opencode-models.test.js +65 -1
  81. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  82. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +117 -0
  83. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  84. package/dist-server/server/modules/providers/tests/provider-image-history.test.js +147 -0
  85. package/dist-server/server/modules/providers/tests/provider-image-history.test.js.map +1 -0
  86. package/dist-server/server/modules/providers/tests/provider-models.service.test.js +8 -8
  87. package/dist-server/server/modules/providers/tests/skills.test.js +3 -26
  88. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  89. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +14 -0
  90. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
  91. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +36 -2
  92. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  93. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +3 -12
  94. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  95. package/dist-server/server/modules/websocket/tests/chat-image-filter.test.js +33 -0
  96. package/dist-server/server/modules/websocket/tests/chat-image-filter.test.js.map +1 -0
  97. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +37 -4
  98. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
  99. package/dist-server/server/openai-codex.js +16 -9
  100. package/dist-server/server/openai-codex.js.map +1 -1
  101. package/dist-server/server/opencode-cli.js +62 -7
  102. package/dist-server/server/opencode-cli.js.map +1 -1
  103. package/dist-server/server/opencode-cli.test.js +103 -3
  104. package/dist-server/server/opencode-cli.test.js.map +1 -1
  105. package/dist-server/server/routes/agent.js +22 -21
  106. package/dist-server/server/routes/agent.js.map +1 -1
  107. package/dist-server/server/routes/commands.js +1 -2
  108. package/dist-server/server/routes/commands.js.map +1 -1
  109. package/dist-server/server/routes/git.js +179 -53
  110. package/dist-server/server/routes/git.js.map +1 -1
  111. package/dist-server/server/routes/git.test.js +80 -0
  112. package/dist-server/server/routes/git.test.js.map +1 -0
  113. package/dist-server/server/routes/taskmaster.js +3 -1
  114. package/dist-server/server/routes/taskmaster.js.map +1 -1
  115. package/dist-server/server/routes/user.js +2 -1
  116. package/dist-server/server/routes/user.js.map +1 -1
  117. package/dist-server/server/shared/image-attachments.js +270 -0
  118. package/dist-server/server/shared/image-attachments.js.map +1 -0
  119. package/dist-server/server/shared/tests/image-attachments.test.js +220 -0
  120. package/dist-server/server/shared/tests/image-attachments.test.js.map +1 -0
  121. package/dist-server/server/shared/utils.js +43 -0
  122. package/dist-server/server/shared/utils.js.map +1 -1
  123. package/dist-server/server/utils/gitConfig.js +2 -1
  124. package/dist-server/server/utils/gitConfig.js.map +1 -1
  125. package/dist-server/server/utils/plugin-process-manager.js +2 -1
  126. package/dist-server/server/utils/plugin-process-manager.js.map +1 -1
  127. package/electron/launcher/launcher.js +1 -1
  128. package/package.json +3 -3
  129. package/public/api-docs.html +8 -3
  130. package/server/claude-sdk.js +69 -131
  131. package/server/cli.js +1 -4
  132. package/server/cursor-cli.js +13 -12
  133. package/server/index.js +11 -159
  134. package/server/modules/assets/assets.routes.ts +103 -0
  135. package/server/modules/assets/index.ts +3 -0
  136. package/server/modules/assets/services/image-assets.service.ts +82 -0
  137. package/server/modules/assets/tests/image-assets.service.test.ts +46 -0
  138. package/server/modules/browser-use/browser-use.service.ts +7 -3
  139. package/server/modules/database/tests/sessions-provider-mapping.test.ts +2 -2
  140. package/server/modules/notifications/services/notification-orchestrator.service.js +0 -1
  141. package/server/modules/projects/services/project-clone.service.ts +3 -1
  142. package/server/modules/providers/README.md +1 -7
  143. package/server/modules/providers/list/claude/claude-models.provider.ts +73 -1
  144. package/server/modules/providers/list/claude/claude-sessions.provider.ts +30 -0
  145. package/server/modules/providers/list/codex/codex-models.provider.ts +59 -11
  146. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +60 -1
  147. package/server/modules/providers/list/codex/codex-sessions.provider.ts +41 -1
  148. package/server/modules/providers/list/cursor/cursor-models.provider.ts +3 -17
  149. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +7 -1
  150. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +56 -15
  151. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +0 -1
  152. package/server/modules/providers/list/opencode/opencode-models.provider.ts +193 -18
  153. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +25 -4
  154. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +10 -21
  155. package/server/modules/providers/provider.registry.ts +0 -2
  156. package/server/modules/providers/provider.routes.ts +0 -1
  157. package/server/modules/providers/services/provider-capabilities.service.ts +13 -13
  158. package/server/modules/providers/services/provider-models.service.ts +2 -2
  159. package/server/modules/providers/services/session-conversations-search.service.ts +3 -93
  160. package/server/modules/providers/services/session-synchronizer.service.ts +0 -1
  161. package/server/modules/providers/services/sessions-watcher.service.ts +0 -14
  162. package/server/modules/providers/tests/codex-sessions.test.ts +111 -0
  163. package/server/modules/providers/tests/mcp.test.ts +3 -34
  164. package/server/modules/providers/tests/opencode-models.test.ts +69 -0
  165. package/server/modules/providers/tests/opencode-sessions.test.ts +139 -0
  166. package/server/modules/providers/tests/provider-image-history.test.ts +180 -0
  167. package/server/modules/providers/tests/provider-models.service.test.ts +8 -8
  168. package/server/modules/providers/tests/skills.test.ts +3 -42
  169. package/server/modules/websocket/services/chat-run-registry.service.ts +16 -0
  170. package/server/modules/websocket/services/chat-websocket.service.ts +41 -2
  171. package/server/modules/websocket/services/shell-websocket.service.ts +1 -11
  172. package/server/modules/websocket/tests/chat-image-filter.test.ts +44 -0
  173. package/server/modules/websocket/tests/chat-run-registry.test.ts +42 -4
  174. package/server/openai-codex.js +18 -8
  175. package/server/opencode-cli.js +64 -7
  176. package/server/opencode-cli.test.js +112 -3
  177. package/server/routes/agent.js +22 -21
  178. package/server/routes/commands.js +1 -2
  179. package/server/routes/git.js +201 -60
  180. package/server/routes/git.test.js +106 -0
  181. package/server/routes/taskmaster.js +3 -1
  182. package/server/routes/user.js +2 -1
  183. package/server/shared/image-attachments.ts +341 -0
  184. package/server/shared/tests/image-attachments.test.ts +298 -0
  185. package/server/shared/types.ts +8 -1
  186. package/server/shared/utils.ts +45 -0
  187. package/server/utils/gitConfig.js +2 -1
  188. package/server/utils/plugin-process-manager.js +3 -1
  189. package/dist/assets/index-BvX30NW5.css +0 -32
  190. package/dist/icons/gemini-ai-icon.svg +0 -1
  191. package/dist-server/server/gemini-cli.js +0 -551
  192. package/dist-server/server/gemini-cli.js.map +0 -1
  193. package/dist-server/server/gemini-response-handler.js +0 -106
  194. package/dist-server/server/gemini-response-handler.js.map +0 -1
  195. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +0 -254
  196. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js.map +0 -1
  197. package/dist-server/server/modules/providers/list/gemini/gemini-mcp.provider.js +0 -82
  198. package/dist-server/server/modules/providers/list/gemini/gemini-mcp.provider.js.map +0 -1
  199. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +0 -24
  200. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +0 -1
  201. package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js +0 -312
  202. package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js.map +0 -1
  203. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +0 -474
  204. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +0 -1
  205. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +0 -40
  206. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +0 -1
  207. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +0 -19
  208. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js.map +0 -1
  209. package/dist-server/server/routes/gemini.js +0 -21
  210. package/dist-server/server/routes/gemini.js.map +0 -1
  211. package/dist-server/server/sessionManager.js +0 -194
  212. package/dist-server/server/sessionManager.js.map +0 -1
  213. package/server/gemini-cli.js +0 -638
  214. package/server/gemini-response-handler.js +0 -117
  215. package/server/modules/providers/list/gemini/gemini-auth.provider.ts +0 -307
  216. package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +0 -110
  217. package/server/modules/providers/list/gemini/gemini-models.provider.ts +0 -39
  218. package/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.ts +0 -405
  219. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +0 -540
  220. package/server/modules/providers/list/gemini/gemini-skills.provider.ts +0 -44
  221. package/server/modules/providers/list/gemini/gemini.provider.ts +0 -27
  222. package/server/routes/gemini.js +0 -25
  223. package/server/sessionManager.js +0 -226
@@ -12,11 +12,14 @@
12
12
  * - WebSocket message streaming
13
13
  */
14
14
 
15
- import { query } from '@anthropic-ai/claude-agent-sdk';
16
15
  import crypto from 'crypto';
17
16
  import { promises as fs } from 'fs';
18
- import path from 'path';
19
17
  import os from 'os';
18
+ import path from 'path';
19
+
20
+ import { query } from '@anthropic-ai/claude-agent-sdk';
21
+
22
+ import { buildClaudeUserContent, normalizeImageDescriptors } from './shared/image-attachments.js';
20
23
  import { CLAUDE_FALLBACK_MODELS } from './modules/providers/list/claude/claude-models.provider.js';
21
24
  import { providerModelsService } from './modules/providers/services/provider-models.service.js';
22
25
  import { resolveClaudeCodeExecutablePath } from './shared/claude-cli-path.js';
@@ -41,6 +44,15 @@ const TOOL_APPROVAL_TIMEOUT_MS = parseInt(process.env.CLAUDE_TOOL_APPROVAL_TIMEO
41
44
 
42
45
  const TOOLS_REQUIRING_INTERACTION = new Set(['AskUserQuestion', 'ExitPlanMode']);
43
46
 
47
+ function resolveClaudeEffort(model, effort, modelsDefinition = CLAUDE_FALLBACK_MODELS) {
48
+ const selectedModel = modelsDefinition?.OPTIONS?.find((option) => option.value === model) || null;
49
+ const allowedEfforts = selectedModel?.effort?.values
50
+ ?.map((value) => value.value) || [];
51
+ return typeof effort === 'string' && effort !== 'default' && allowedEfforts.includes(effort)
52
+ ? effort
53
+ : undefined;
54
+ }
55
+
44
56
  function createRequestId() {
45
57
  if (typeof crypto.randomUUID === 'function') {
46
58
  return crypto.randomUUID();
@@ -145,13 +157,8 @@ function matchesToolPermission(entry, toolName, input) {
145
157
  return false;
146
158
  }
147
159
 
148
- /**
149
- * Maps CLI options to SDK-compatible options format
150
- * @param {Object} options - CLI options
151
- * @returns {Object} SDK-compatible options
152
- */
153
160
  function mapCliOptionsToSDK(options = {}) {
154
- const { sessionId, cwd, toolsSettings, permissionMode } = options;
161
+ const { sessionId, cwd, toolsSettings, permissionMode, effort } = options;
155
162
 
156
163
  const sdkOptions = {};
157
164
 
@@ -163,32 +170,26 @@ function mapCliOptionsToSDK(options = {}) {
163
170
  // which does not reliably follow npm's shell wrappers like cross-spawn does.
164
171
  sdkOptions.pathToClaudeCodeExecutable = resolveClaudeCodeExecutablePath(process.env.CLAUDE_CLI_PATH);
165
172
 
166
- // Map working directory
167
173
  if (cwd) {
168
174
  sdkOptions.cwd = cwd;
169
175
  }
170
176
 
171
- // Map permission mode
172
177
  if (permissionMode && permissionMode !== 'default') {
173
178
  sdkOptions.permissionMode = permissionMode;
174
179
  }
175
180
 
176
- // Map tool settings
177
181
  const settings = toolsSettings || {
178
182
  allowedTools: [],
179
183
  disallowedTools: [],
180
184
  skipPermissions: false
181
185
  };
182
186
 
183
- // Handle tool permissions
184
187
  if (settings.skipPermissions && permissionMode !== 'plan') {
185
- // When skipping permissions, use bypassPermissions mode
186
188
  sdkOptions.permissionMode = 'bypassPermissions';
187
189
  }
188
190
 
189
191
  let allowedTools = [...(settings.allowedTools || [])];
190
192
 
191
- // Add plan mode default tools
192
193
  if (permissionMode === 'plan') {
193
194
  const planModeTools = ['Read', 'Task', 'exit_plan_mode', 'TodoRead', 'TodoWrite', 'WebFetch', 'WebSearch'];
194
195
  for (const tool of planModeTools) {
@@ -207,22 +208,24 @@ function mapCliOptionsToSDK(options = {}) {
207
208
 
208
209
  sdkOptions.disallowedTools = settings.disallowedTools || [];
209
210
 
210
- // Map model (default to sonnet)
211
- // Valid models: sonnet, opus, haiku, opusplan, sonnet[1m], fable
212
211
  sdkOptions.model = options.model || CLAUDE_FALLBACK_MODELS.DEFAULT;
213
- // Model logged at query start below
214
212
 
215
- // Map system prompt configuration
213
+ const resolvedEffort = resolveClaudeEffort(
214
+ sdkOptions.model,
215
+ effort,
216
+ options.effortModels || CLAUDE_FALLBACK_MODELS,
217
+ );
218
+ if (resolvedEffort) {
219
+ sdkOptions.effort = resolvedEffort;
220
+ }
221
+
216
222
  sdkOptions.systemPrompt = {
217
223
  type: 'preset',
218
- preset: 'claude_code' // Required to use CLAUDE.md
224
+ preset: 'claude_code'
219
225
  };
220
226
 
221
- // Map setting sources for CLAUDE.md loading
222
- // This loads CLAUDE.md from project, user (~/.config/claude/CLAUDE.md), and local directories
223
227
  sdkOptions.settingSources = ['project', 'user', 'local'];
224
228
 
225
- // Map resume session
226
229
  if (sessionId) {
227
230
  sdkOptions.resume = sessionId;
228
231
  }
@@ -234,16 +237,13 @@ function mapCliOptionsToSDK(options = {}) {
234
237
  * Adds a session to the active sessions map
235
238
  * @param {string} sessionId - Session identifier
236
239
  * @param {Object} queryInstance - SDK query instance
237
- * @param {Array<string>} tempImagePaths - Temp image file paths for cleanup
238
- * @param {string} tempDir - Temp directory for cleanup
240
+ * @param {Object} writer - WebSocket writer for reconnect support
239
241
  */
240
- function addSession(sessionId, queryInstance, tempImagePaths = [], tempDir = null, writer = null) {
242
+ function addSession(sessionId, queryInstance, writer = null) {
241
243
  activeSessions.set(sessionId, {
242
244
  instance: queryInstance,
243
245
  startTime: Date.now(),
244
246
  status: 'active',
245
- tempImagePaths,
246
- tempDir,
247
247
  writer
248
248
  });
249
249
  }
@@ -362,90 +362,35 @@ function extractTokenBudget(sdkMessage) {
362
362
  }
363
363
 
364
364
  /**
365
- * Handles image processing for SDK queries
366
- * Saves base64 images to temporary files and returns modified prompt with file paths
367
- * @param {string} command - Original user prompt
368
- * @param {Array} images - Array of image objects with base64 data
369
- * @param {string} cwd - Working directory for temp file creation
370
- * @returns {Promise<Object>} {modifiedCommand, tempImagePaths, tempDir}
371
- */
372
- async function handleImages(command, images, cwd) {
373
- const tempImagePaths = [];
374
- let tempDir = null;
375
-
376
- if (!images || images.length === 0) {
377
- return { modifiedCommand: command, tempImagePaths, tempDir };
378
- }
379
-
380
- try {
381
- // Create temp directory in the project directory
382
- const workingDir = cwd || process.cwd();
383
- tempDir = path.join(workingDir, '.tmp', 'images', Date.now().toString());
384
- await fs.mkdir(tempDir, { recursive: true });
385
-
386
- // Save each image to a temp file
387
- for (const [index, image] of images.entries()) {
388
- // Extract base64 data and mime type
389
- const matches = image.data.match(/^data:([^;]+);base64,(.+)$/);
390
- if (!matches) {
391
- console.error('Invalid image data format');
392
- continue;
393
- }
394
-
395
- const [, mimeType, base64Data] = matches;
396
- const extension = mimeType.split('/')[1] || 'png';
397
- const filename = `image_${index}.${extension}`;
398
- const filepath = path.join(tempDir, filename);
399
-
400
- // Write base64 data to file
401
- await fs.writeFile(filepath, Buffer.from(base64Data, 'base64'));
402
- tempImagePaths.push(filepath);
403
- }
404
-
405
- // Include the full image paths in the prompt
406
- let modifiedCommand = command;
407
- if (tempImagePaths.length > 0 && command && command.trim()) {
408
- const imageNote = `\n\n[Images provided at the following paths:]\n${tempImagePaths.map((p, i) => `${i + 1}. ${p}`).join('\n')}`;
409
- modifiedCommand = command + imageNote;
410
- }
411
-
412
- // Images processed
413
- return { modifiedCommand, tempImagePaths, tempDir };
414
- } catch (error) {
415
- console.error('Error processing images for SDK:', error);
416
- return { modifiedCommand: command, tempImagePaths, tempDir };
417
- }
418
- }
419
-
420
- /**
421
- * Cleans up temporary image files
422
- * @param {Array<string>} tempImagePaths - Array of temp file paths to delete
423
- * @param {string} tempDir - Temp directory to remove
365
+ * Builds the SDK `prompt` payload for one turn.
366
+ *
367
+ * Plain text turns pass the string through unchanged. Turns with image
368
+ * attachments use the SDK's streaming-input mode: a single SDKUserMessage
369
+ * whose content carries the prompt text plus one base64 `image` block per
370
+ * attachment (read from the global `~/.cloudcli/assets` folder).
371
+ *
372
+ * @param {string} command - User prompt
373
+ * @param {Array} images - Image descriptors ({ path, name?, mimeType? })
374
+ * @param {string} cwd - Project working directory image paths resolve against
375
+ * @returns {Promise<string|AsyncIterable>} SDK prompt payload
424
376
  */
425
- async function cleanupTempFiles(tempImagePaths, tempDir) {
426
- if (!tempImagePaths || tempImagePaths.length === 0) {
427
- return;
377
+ async function buildPromptPayload(command, images, cwd) {
378
+ if (normalizeImageDescriptors(images).length === 0) {
379
+ return command;
428
380
  }
429
381
 
430
- try {
431
- // Delete individual temp files
432
- for (const imagePath of tempImagePaths) {
433
- await fs.unlink(imagePath).catch(err =>
434
- console.error(`Failed to delete temp image ${imagePath}:`, err)
435
- );
436
- }
437
-
438
- // Delete temp directory
439
- if (tempDir) {
440
- await fs.rm(tempDir, { recursive: true, force: true }).catch(err =>
441
- console.error(`Failed to delete temp directory ${tempDir}:`, err)
442
- );
443
- }
444
-
445
- // Temp files cleaned
446
- } catch (error) {
447
- console.error('Error during temp file cleanup:', error);
448
- }
382
+ const content = await buildClaudeUserContent(command, images, cwd);
383
+ return (async function* () {
384
+ yield {
385
+ type: 'user',
386
+ message: {
387
+ role: 'user',
388
+ content
389
+ },
390
+ parent_tool_use_id: null,
391
+ timestamp: new Date().toISOString()
392
+ };
393
+ })();
449
394
  }
450
395
 
451
396
  /**
@@ -516,8 +461,6 @@ async function queryClaudeSDK(command, options = {}, ws) {
516
461
  const { sessionId, sessionSummary } = options;
517
462
  let capturedSessionId = sessionId;
518
463
  let sessionCreatedSent = false;
519
- let tempImagePaths = [];
520
- let tempDir = null;
521
464
 
522
465
  const emitNotification = (event) => {
523
466
  notifyUserIfEnabled({
@@ -533,24 +476,28 @@ async function queryClaudeSDK(command, options = {}, ws) {
533
476
  sessionId,
534
477
  options.model,
535
478
  );
479
+ let effortModels = CLAUDE_FALLBACK_MODELS;
480
+ try {
481
+ effortModels = (await providerModelsService.getProviderModels('claude')).models;
482
+ } catch (error) {
483
+ console.warn('[Claude SDK] Unable to load provider models for effort validation:', error);
484
+ }
536
485
 
537
- // Map CLI options to SDK format
538
486
  const sdkOptions = mapCliOptionsToSDK({
539
487
  ...options,
540
488
  model: resolvedModel || options.model,
489
+ effortModels,
541
490
  });
542
491
 
543
- // Load MCP configuration
544
492
  const mcpServers = await loadMcpConfig(options.cwd);
545
493
  if (mcpServers) {
546
494
  sdkOptions.mcpServers = mcpServers;
547
495
  }
548
496
 
549
- // Handle images - save to temp files and modify prompt
550
- const imageResult = await handleImages(command, options.images, options.cwd);
551
- const finalCommand = imageResult.modifiedCommand;
552
- tempImagePaths = imageResult.tempImagePaths;
553
- tempDir = imageResult.tempDir;
497
+ // Turns with image attachments switch to streaming input so the images
498
+ // ride along as real content blocks. Built per query attempt because an
499
+ // async generator cannot be replayed once consumed.
500
+ const createPrompt = () => buildPromptPayload(command, options.images, options.cwd);
554
501
 
555
502
  sdkOptions.hooks = {
556
503
  Notification: [{
@@ -650,14 +597,14 @@ async function queryClaudeSDK(command, options = {}, ws) {
650
597
  return { behavior: 'deny', message: decision.message ?? 'User denied tool use' };
651
598
  };
652
599
 
653
- // Set stream-close timeout for interactive tools (Query constructor reads it synchronously). Claude Agent SDK has a default of 5s and this overrides it
600
+ // Query constructor reads this synchronously.
654
601
  const prevStreamTimeout = process.env.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT;
655
602
  process.env.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT = '300000';
656
603
 
657
604
  let queryInstance;
658
605
  try {
659
606
  queryInstance = query({
660
- prompt: finalCommand,
607
+ prompt: await createPrompt(),
661
608
  options: sdkOptions
662
609
  });
663
610
  } catch (hookError) {
@@ -666,7 +613,7 @@ async function queryClaudeSDK(command, options = {}, ws) {
666
613
  console.warn('Failed to initialize Claude query with hooks, retrying without hooks:', hookError?.message || hookError);
667
614
  delete sdkOptions.hooks;
668
615
  queryInstance = query({
669
- prompt: finalCommand,
616
+ prompt: await createPrompt(),
670
617
  options: sdkOptions
671
618
  });
672
619
  }
@@ -680,7 +627,7 @@ async function queryClaudeSDK(command, options = {}, ws) {
680
627
 
681
628
  // Track the query instance for abort capability
682
629
  if (capturedSessionId) {
683
- addSession(capturedSessionId, queryInstance, tempImagePaths, tempDir, ws);
630
+ addSession(capturedSessionId, queryInstance, ws);
684
631
  }
685
632
 
686
633
  // Process streaming messages
@@ -690,7 +637,7 @@ async function queryClaudeSDK(command, options = {}, ws) {
690
637
  if (message.session_id && !capturedSessionId) {
691
638
 
692
639
  capturedSessionId = message.session_id;
693
- addSession(capturedSessionId, queryInstance, tempImagePaths, tempDir, ws);
640
+ addSession(capturedSessionId, queryInstance, ws);
694
641
 
695
642
  // Set session ID on writer
696
643
  if (ws.setSessionId && typeof ws.setSessionId === 'function') {
@@ -732,9 +679,6 @@ async function queryClaudeSDK(command, options = {}, ws) {
732
679
  removeSession(capturedSessionId);
733
680
  }
734
681
 
735
- // Clean up temporary image files
736
- await cleanupTempFiles(tempImagePaths, tempDir);
737
-
738
682
  // Send the terminal completion event — skipped for aborted runs, whose
739
683
  // terminal `complete` (aborted: true) was already sent by abort-session.
740
684
  const wasAborted = capturedSessionId ? abortedSessionIds.delete(capturedSessionId) : false;
@@ -758,9 +702,6 @@ async function queryClaudeSDK(command, options = {}, ws) {
758
702
  removeSession(capturedSessionId);
759
703
  }
760
704
 
761
- // Clean up temporary image files on error
762
- await cleanupTempFiles(tempImagePaths, tempDir);
763
-
764
705
  const wasAborted = capturedSessionId ? abortedSessionIds.delete(capturedSessionId) : false;
765
706
  if (wasAborted) {
766
707
  // The abort already produced the terminal complete; a generator throw
@@ -813,9 +754,6 @@ async function abortClaudeSDKSession(sessionId) {
813
754
  // Update session status
814
755
  session.status = 'aborted';
815
756
 
816
- // Clean up temporary image files
817
- await cleanupTempFiles(session.tempImagePaths, session.tempDir);
818
-
819
757
  // Clean up session
820
758
  removeSession(sessionId);
821
759
 
package/server/cli.js CHANGED
@@ -256,13 +256,11 @@ async function updatePackage() {
256
256
  const SANDBOX_TEMPLATES = {
257
257
  claude: 'docker.io/cloudcliai/sandbox:claude-code',
258
258
  codex: 'docker.io/cloudcliai/sandbox:codex',
259
- gemini: 'docker.io/cloudcliai/sandbox:gemini',
260
259
  };
261
260
 
262
261
  const SANDBOX_SECRETS = {
263
262
  claude: 'anthropic',
264
263
  codex: 'openai',
265
- gemini: 'google',
266
264
  };
267
265
 
268
266
  function parseSandboxArgs(args) {
@@ -338,7 +336,7 @@ Subcommands:
338
336
  ${c.bright('help')} Show this help
339
337
 
340
338
  Options:
341
- -a, --agent <agent> Agent to use: claude, codex, gemini (default: claude)
339
+ -a, --agent <agent> Agent to use: claude, codex (default: claude)
342
340
  -n, --name <name> Sandbox name (default: derived from workspace folder)
343
341
  -t, --template <image> Custom template image
344
342
  -e, --env <KEY=VALUE> Set environment variable (repeatable)
@@ -359,7 +357,6 @@ Prerequisites:
359
357
  sbx login
360
358
  sbx secret set -g anthropic # for Claude
361
359
  sbx secret set -g openai # for Codex
362
- sbx secret set -g google # for Gemini
363
360
 
364
361
  Advanced usage:
365
362
  For branch mode, multiple workspaces, memory limits, network policies,
@@ -1,13 +1,15 @@
1
- import { spawn } from 'child_process';
2
1
  import crossSpawn from 'cross-spawn';
2
+
3
+ import { appendImagesInputTag } from './shared/image-attachments.js';
3
4
  import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js';
4
5
  import { sessionsService } from './modules/providers/services/sessions.service.js';
5
6
  import { providerAuthService } from './modules/providers/services/provider-auth.service.js';
6
7
  import { providerModelsService } from './modules/providers/services/provider-models.service.js';
7
- import { createCompleteMessage, createNormalizedMessage } from './shared/utils.js';
8
+ import { createCompleteMessage, createNormalizedMessage, flattenPromptForWindowsShell } from './shared/utils.js';
8
9
 
9
- // Use cross-spawn on Windows for better command execution
10
- const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn;
10
+ // cross-spawn resolves .cmd shims/PATHEXT on Windows and delegates to
11
+ // child_process.spawn everywhere else.
12
+ const spawnFunction = crossSpawn;
11
13
 
12
14
  let activeCursorProcesses = new Map(); // Track active processes by session ID
13
15
 
@@ -28,7 +30,7 @@ function isWorkspaceTrustPrompt(text = '') {
28
30
 
29
31
  async function spawnCursor(command, options = {}, ws) {
30
32
  return new Promise(async (resolve, reject) => {
31
- const { sessionId, projectPath, cwd, resume, toolsSettings, skipPermissions, model, sessionSummary } = options;
33
+ const { sessionId, projectPath, cwd, toolsSettings, skipPermissions, model, sessionSummary, images } = options;
32
34
  const resolvedModel = await providerModelsService.resolveResumeModel('cursor', sessionId, model);
33
35
  let capturedSessionId = sessionId; // Track session ID throughout the process
34
36
  let sessionCreatedSent = false; // Track if we've already sent session-created event
@@ -55,8 +57,12 @@ async function spawnCursor(command, options = {}, ws) {
55
57
  }
56
58
 
57
59
  if (command && command.trim()) {
58
- // Provide a prompt (works for both new and resumed sessions)
59
- baseArgs.push('-p', command);
60
+ // Provide a prompt (works for both new and resumed sessions). Image
61
+ // attachments ride along as an <images_input> path list appended to the
62
+ // prompt; the session history reader strips the tag back out for display.
63
+ // cursor-agent is a .cmd shim on Windows, so the whole argument must be
64
+ // newline-free or cmd.exe silently truncates it at the first newline.
65
+ baseArgs.push('-p', flattenPromptForWindowsShell(appendImagesInputTag(command, images)));
60
66
 
61
67
  // Model overrides are applied to both new and resumed sessions so a
62
68
  // session-scoped change request can take effect on the next turn.
@@ -71,7 +77,6 @@ async function spawnCursor(command, options = {}, ws) {
71
77
  // Add skip permissions flag if enabled
72
78
  if (skipPermissions || settings.skipPermissions) {
73
79
  baseArgs.push('-f');
74
- console.log('Using -f flag (skip permissions)');
75
80
  }
76
81
 
77
82
  // Use cwd (actual project directory) instead of projectPath
@@ -126,10 +131,6 @@ async function spawnCursor(command, options = {}, ws) {
126
131
  console.log('Retrying Cursor CLI with --trust after workspace trust prompt');
127
132
  }
128
133
 
129
- console.log('Spawning Cursor CLI:', 'cursor-agent', args.join(' '));
130
- console.log('Working directory:', workingDir);
131
- console.log('Session info - Input sessionId:', sessionId, 'Resume:', resume);
132
-
133
134
  const cursorProcess = spawnFunction('cursor-agent', args, {
134
135
  cwd: workingDir,
135
136
  stdio: ['pipe', 'pipe', 'pipe'],