@machina.ai/cell-cli 1.0.13-rc9 → 1.0.21-rc1

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 (579) hide show
  1. package/dist/package.json +11 -7
  2. package/dist/src/commands/mcp/add.d.ts +7 -0
  3. package/dist/src/commands/mcp/add.js +166 -0
  4. package/dist/src/commands/mcp/add.js.map +1 -0
  5. package/dist/src/commands/mcp/list.d.ts +8 -0
  6. package/dist/src/commands/mcp/list.js +110 -0
  7. package/dist/src/commands/mcp/list.js.map +1 -0
  8. package/dist/src/commands/mcp/remove.d.ts +7 -0
  9. package/dist/src/commands/mcp/remove.js +44 -0
  10. package/dist/src/commands/mcp/remove.js.map +1 -0
  11. package/dist/src/commands/mcp.d.ts +7 -0
  12. package/dist/src/commands/mcp.js +23 -0
  13. package/dist/src/commands/mcp.js.map +1 -0
  14. package/dist/src/config/auth.test.d.ts +6 -0
  15. package/dist/src/config/auth.test.js +28 -0
  16. package/dist/src/config/auth.test.js.map +1 -0
  17. package/dist/src/config/config.d.ts +6 -3
  18. package/dist/src/config/config.js +177 -90
  19. package/dist/src/config/config.js.map +1 -1
  20. package/dist/src/config/extension.d.ts +1 -0
  21. package/dist/src/config/extension.js +4 -0
  22. package/dist/src/config/extension.js.map +1 -1
  23. package/dist/src/config/keyBindings.d.ts +66 -0
  24. package/dist/src/config/keyBindings.js +141 -0
  25. package/dist/src/config/keyBindings.js.map +1 -0
  26. package/dist/src/config/keyBindings.test.d.ts +6 -0
  27. package/dist/src/config/keyBindings.test.js +51 -0
  28. package/dist/src/config/keyBindings.test.js.map +1 -0
  29. package/dist/src/config/sandboxConfig.js +3 -3
  30. package/dist/src/config/sandboxConfig.js.map +1 -1
  31. package/dist/src/config/settings.d.ts +6 -41
  32. package/dist/src/config/settings.js +137 -52
  33. package/dist/src/config/settings.js.map +1 -1
  34. package/dist/src/config/settingsSchema.d.ts +533 -0
  35. package/dist/src/config/settingsSchema.js +507 -0
  36. package/dist/src/config/settingsSchema.js.map +1 -0
  37. package/dist/src/config/settingsSchema.test.d.ts +6 -0
  38. package/dist/src/config/settingsSchema.test.js +195 -0
  39. package/dist/src/config/settingsSchema.test.js.map +1 -0
  40. package/dist/src/config/trustedFolders.d.ts +37 -0
  41. package/dist/src/config/trustedFolders.js +118 -0
  42. package/dist/src/config/trustedFolders.js.map +1 -0
  43. package/dist/src/config/trustedFolders.test.d.ts +6 -0
  44. package/dist/src/config/trustedFolders.test.js +160 -0
  45. package/dist/src/config/trustedFolders.test.js.map +1 -0
  46. package/dist/src/gemini.d.ts +3 -0
  47. package/dist/src/gemini.js +106 -68
  48. package/dist/src/gemini.js.map +1 -1
  49. package/dist/src/gemini.test.d.ts +6 -0
  50. package/dist/src/gemini.test.js +193 -0
  51. package/dist/src/gemini.test.js.map +1 -0
  52. package/dist/src/generated/git-commit.d.ts +2 -1
  53. package/dist/src/generated/git-commit.js +3 -2
  54. package/dist/src/generated/git-commit.js.map +1 -1
  55. package/dist/src/nonInteractiveCli.js +35 -55
  56. package/dist/src/nonInteractiveCli.js.map +1 -1
  57. package/dist/src/patches/is-in-ci.d.ts +7 -0
  58. package/dist/src/patches/is-in-ci.js +15 -0
  59. package/dist/src/patches/is-in-ci.js.map +1 -0
  60. package/dist/src/services/BuiltinCommandLoader.js +12 -0
  61. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  62. package/dist/src/services/BuiltinCommandLoader.test.d.ts +6 -0
  63. package/dist/src/services/BuiltinCommandLoader.test.js +108 -0
  64. package/dist/src/services/BuiltinCommandLoader.test.js.map +1 -0
  65. package/dist/src/services/CommandService.d.ts +8 -4
  66. package/dist/src/services/CommandService.js +24 -8
  67. package/dist/src/services/CommandService.js.map +1 -1
  68. package/dist/src/services/CommandService.test.d.ts +6 -0
  69. package/dist/src/services/CommandService.test.js +232 -0
  70. package/dist/src/services/CommandService.test.js.map +1 -0
  71. package/dist/src/services/FileCommandLoader.d.ts +15 -3
  72. package/dist/src/services/FileCommandLoader.js +115 -38
  73. package/dist/src/services/FileCommandLoader.js.map +1 -1
  74. package/dist/src/services/McpPromptLoader.d.ts +25 -0
  75. package/dist/src/services/McpPromptLoader.js +192 -0
  76. package/dist/src/services/McpPromptLoader.js.map +1 -0
  77. package/dist/src/services/prompt-processors/argumentProcessor.d.ts +16 -0
  78. package/dist/src/services/prompt-processors/argumentProcessor.js +20 -0
  79. package/dist/src/services/prompt-processors/argumentProcessor.js.map +1 -0
  80. package/dist/src/services/prompt-processors/shellProcessor.d.ts +35 -0
  81. package/dist/src/services/prompt-processors/shellProcessor.js +170 -0
  82. package/dist/src/services/prompt-processors/shellProcessor.js.map +1 -0
  83. package/dist/src/services/prompt-processors/types.d.ts +40 -0
  84. package/dist/src/services/prompt-processors/types.js +16 -0
  85. package/dist/src/services/prompt-processors/types.js.map +1 -0
  86. package/dist/src/test-utils/customMatchers.d.ts +14 -0
  87. package/dist/src/test-utils/customMatchers.js +46 -0
  88. package/dist/src/test-utils/customMatchers.js.map +1 -0
  89. package/dist/src/test-utils/mockCommandContext.d.ts +18 -0
  90. package/dist/src/test-utils/mockCommandContext.js +86 -0
  91. package/dist/src/test-utils/mockCommandContext.js.map +1 -0
  92. package/dist/src/test-utils/mockCommandContext.test.d.ts +6 -0
  93. package/dist/src/test-utils/mockCommandContext.test.js +51 -0
  94. package/dist/src/test-utils/mockCommandContext.test.js.map +1 -0
  95. package/dist/src/test-utils/render.d.ts +8 -0
  96. package/dist/src/test-utils/render.js +10 -0
  97. package/dist/src/test-utils/render.js.map +1 -0
  98. package/dist/src/ui/App.js +229 -67
  99. package/dist/src/ui/App.js.map +1 -1
  100. package/dist/src/ui/IdeIntegrationNudge.d.ts +16 -0
  101. package/dist/src/ui/IdeIntegrationNudge.js +52 -0
  102. package/dist/src/ui/IdeIntegrationNudge.js.map +1 -0
  103. package/dist/src/ui/colors.js +6 -0
  104. package/dist/src/ui/colors.js.map +1 -1
  105. package/dist/src/ui/commands/aboutCommand.js +9 -5
  106. package/dist/src/ui/commands/aboutCommand.js.map +1 -1
  107. package/dist/src/ui/commands/bugCommand.js +13 -5
  108. package/dist/src/ui/commands/bugCommand.js.map +1 -1
  109. package/dist/src/ui/commands/chatCommand.js +66 -6
  110. package/dist/src/ui/commands/chatCommand.js.map +1 -1
  111. package/dist/src/ui/commands/directoryCommand.d.ts +8 -0
  112. package/dist/src/ui/commands/directoryCommand.js +134 -0
  113. package/dist/src/ui/commands/directoryCommand.js.map +1 -0
  114. package/dist/src/ui/commands/docsCommand.js +1 -1
  115. package/dist/src/ui/commands/docsCommand.js.map +1 -1
  116. package/dist/src/ui/commands/helpCommand.js +7 -6
  117. package/dist/src/ui/commands/helpCommand.js.map +1 -1
  118. package/dist/src/ui/commands/ideCommand.js +179 -109
  119. package/dist/src/ui/commands/ideCommand.js.map +1 -1
  120. package/dist/src/ui/commands/initCommand.d.ts +7 -0
  121. package/dist/src/ui/commands/initCommand.js +76 -0
  122. package/dist/src/ui/commands/initCommand.js.map +1 -0
  123. package/dist/src/ui/commands/mcpCommand.js +244 -26
  124. package/dist/src/ui/commands/mcpCommand.js.map +1 -1
  125. package/dist/src/ui/commands/memoryCommand.js +9 -4
  126. package/dist/src/ui/commands/memoryCommand.js.map +1 -1
  127. package/dist/src/ui/commands/settingsCommand.d.ts +7 -0
  128. package/dist/src/ui/commands/settingsCommand.js +16 -0
  129. package/dist/src/ui/commands/settingsCommand.js.map +1 -0
  130. package/dist/src/ui/commands/setupGithubCommand.d.ts +7 -0
  131. package/dist/src/ui/commands/setupGithubCommand.js +115 -0
  132. package/dist/src/ui/commands/setupGithubCommand.js.map +1 -0
  133. package/dist/src/ui/commands/setupGithubCommand.test.d.ts +6 -0
  134. package/dist/src/ui/commands/setupGithubCommand.test.js +74 -0
  135. package/dist/src/ui/commands/setupGithubCommand.test.js.map +1 -0
  136. package/dist/src/ui/commands/terminalSetupCommand.d.ts +13 -0
  137. package/dist/src/ui/commands/terminalSetupCommand.js +41 -0
  138. package/dist/src/ui/commands/terminalSetupCommand.js.map +1 -0
  139. package/dist/src/ui/commands/types.d.ts +42 -3
  140. package/dist/src/ui/commands/types.js +1 -0
  141. package/dist/src/ui/commands/types.js.map +1 -1
  142. package/dist/src/ui/commands/vimCommand.d.ts +7 -0
  143. package/dist/src/ui/commands/vimCommand.js +23 -0
  144. package/dist/src/ui/commands/vimCommand.js.map +1 -0
  145. package/dist/src/ui/components/AboutBox.d.ts +1 -0
  146. package/dist/src/ui/components/AboutBox.js +1 -1
  147. package/dist/src/ui/components/AboutBox.js.map +1 -1
  148. package/dist/src/ui/components/AsciiArt.d.ts +3 -2
  149. package/dist/src/ui/components/AsciiArt.js +23 -12
  150. package/dist/src/ui/components/AsciiArt.js.map +1 -1
  151. package/dist/src/ui/components/AuthDialog.js +12 -6
  152. package/dist/src/ui/components/AuthDialog.js.map +1 -1
  153. package/dist/src/ui/components/AuthDialog.test.d.ts +6 -0
  154. package/dist/src/ui/components/AuthDialog.test.js +114 -0
  155. package/dist/src/ui/components/AuthDialog.test.js.map +1 -0
  156. package/dist/src/ui/components/AuthInProgress.js +5 -4
  157. package/dist/src/ui/components/AuthInProgress.js.map +1 -1
  158. package/dist/src/ui/components/ContextSummaryDisplay.d.ts +2 -2
  159. package/dist/src/ui/components/ContextSummaryDisplay.js +31 -34
  160. package/dist/src/ui/components/ContextSummaryDisplay.js.map +1 -1
  161. package/dist/src/ui/components/ContextUsageDisplay.d.ts +9 -0
  162. package/dist/src/ui/components/ContextUsageDisplay.js +14 -0
  163. package/dist/src/ui/components/ContextUsageDisplay.js.map +1 -0
  164. package/dist/src/ui/components/DebugProfiler.d.ts +6 -0
  165. package/dist/src/ui/components/DebugProfiler.js +27 -0
  166. package/dist/src/ui/components/DebugProfiler.js.map +1 -0
  167. package/dist/src/ui/components/EditorSettingsDialog.js +6 -5
  168. package/dist/src/ui/components/EditorSettingsDialog.js.map +1 -1
  169. package/dist/src/ui/components/FolderTrustDialog.d.ts +16 -0
  170. package/dist/src/ui/components/FolderTrustDialog.js +39 -0
  171. package/dist/src/ui/components/FolderTrustDialog.js.map +1 -0
  172. package/dist/src/ui/components/FolderTrustDialog.test.d.ts +6 -0
  173. package/dist/src/ui/components/FolderTrustDialog.test.js +26 -0
  174. package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -0
  175. package/dist/src/ui/components/Footer.d.ts +2 -0
  176. package/dist/src/ui/components/Footer.js +18 -7
  177. package/dist/src/ui/components/Footer.js.map +1 -1
  178. package/dist/src/ui/components/Header.d.ts +0 -1
  179. package/dist/src/ui/components/Header.js +13 -5
  180. package/dist/src/ui/components/Header.js.map +1 -1
  181. package/dist/src/ui/components/Header.test.d.ts +6 -0
  182. package/dist/src/ui/components/Header.test.js +44 -0
  183. package/dist/src/ui/components/Header.test.js.map +1 -0
  184. package/dist/src/ui/components/Help.js +2 -2
  185. package/dist/src/ui/components/Help.js.map +1 -1
  186. package/dist/src/ui/components/HistoryItemDisplay.d.ts +2 -0
  187. package/dist/src/ui/components/HistoryItemDisplay.js +2 -1
  188. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
  189. package/dist/src/ui/components/HistoryItemDisplay.test.d.ts +6 -0
  190. package/dist/src/ui/components/HistoryItemDisplay.test.js +91 -0
  191. package/dist/src/ui/components/HistoryItemDisplay.test.js.map +1 -0
  192. package/dist/src/ui/components/InputPrompt.d.ts +3 -0
  193. package/dist/src/ui/components/InputPrompt.js +179 -126
  194. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  195. package/dist/src/ui/components/LoadingIndicator.js +10 -5
  196. package/dist/src/ui/components/LoadingIndicator.js.map +1 -1
  197. package/dist/src/ui/components/LoadingIndicator.test.d.ts +6 -0
  198. package/dist/src/ui/components/LoadingIndicator.test.js +190 -0
  199. package/dist/src/ui/components/LoadingIndicator.test.js.map +1 -0
  200. package/dist/src/ui/components/PrepareLabel.d.ts +15 -0
  201. package/dist/src/ui/components/PrepareLabel.js +16 -0
  202. package/dist/src/ui/components/PrepareLabel.js.map +1 -0
  203. package/dist/src/ui/components/SettingsDialog.d.ts +14 -0
  204. package/dist/src/ui/components/SettingsDialog.js +519 -0
  205. package/dist/src/ui/components/SettingsDialog.js.map +1 -0
  206. package/dist/src/ui/components/SettingsDialog.test.d.ts +6 -0
  207. package/dist/src/ui/components/SettingsDialog.test.js +568 -0
  208. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -0
  209. package/dist/src/ui/components/ShellConfirmationDialog.d.ts +15 -0
  210. package/dist/src/ui/components/ShellConfirmationDialog.js +46 -0
  211. package/dist/src/ui/components/ShellConfirmationDialog.js.map +1 -0
  212. package/dist/src/ui/components/ShellConfirmationDialog.test.d.ts +6 -0
  213. package/dist/src/ui/components/ShellConfirmationDialog.test.js +40 -0
  214. package/dist/src/ui/components/ShellConfirmationDialog.test.js.map +1 -0
  215. package/dist/src/ui/components/StatsDisplay.js +8 -7
  216. package/dist/src/ui/components/StatsDisplay.js.map +1 -1
  217. package/dist/src/ui/components/SuggestionsDisplay.d.ts +1 -0
  218. package/dist/src/ui/components/SuggestionsDisplay.js +3 -3
  219. package/dist/src/ui/components/SuggestionsDisplay.js.map +1 -1
  220. package/dist/src/ui/components/ThemeDialog.js +20 -25
  221. package/dist/src/ui/components/ThemeDialog.js.map +1 -1
  222. package/dist/src/ui/components/Tips.js +1 -1
  223. package/dist/src/ui/components/Tips.js.map +1 -1
  224. package/dist/src/ui/components/messages/DiffRenderer.js +21 -11
  225. package/dist/src/ui/components/messages/DiffRenderer.js.map +1 -1
  226. package/dist/src/ui/components/messages/DiffRenderer.test.d.ts +6 -0
  227. package/dist/src/ui/components/messages/DiffRenderer.test.js +239 -0
  228. package/dist/src/ui/components/messages/DiffRenderer.test.js.map +1 -0
  229. package/dist/src/ui/components/messages/InfoMessage.js +2 -1
  230. package/dist/src/ui/components/messages/InfoMessage.js.map +1 -1
  231. package/dist/src/ui/components/messages/ToolConfirmationMessage.js +52 -18
  232. package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
  233. package/dist/src/ui/components/messages/ToolConfirmationMessage.test.d.ts +6 -0
  234. package/dist/src/ui/components/messages/ToolConfirmationMessage.test.js +37 -0
  235. package/dist/src/ui/components/messages/ToolConfirmationMessage.test.js.map +1 -0
  236. package/dist/src/ui/components/messages/ToolMessage.test.d.ts +6 -0
  237. package/dist/src/ui/components/messages/ToolMessage.test.js +118 -0
  238. package/dist/src/ui/components/messages/ToolMessage.test.js.map +1 -0
  239. package/dist/src/ui/components/messages/UserMessage.js +4 -1
  240. package/dist/src/ui/components/messages/UserMessage.js.map +1 -1
  241. package/dist/src/ui/components/shared/MaxSizedBox.js +1 -1
  242. package/dist/src/ui/components/shared/MaxSizedBox.js.map +1 -1
  243. package/dist/src/ui/components/shared/MaxSizedBox.test.d.ts +6 -0
  244. package/dist/src/ui/components/shared/MaxSizedBox.test.js +154 -0
  245. package/dist/src/ui/components/shared/MaxSizedBox.test.js.map +1 -0
  246. package/dist/src/ui/components/shared/RadioButtonSelect.js +11 -9
  247. package/dist/src/ui/components/shared/RadioButtonSelect.js.map +1 -1
  248. package/dist/src/ui/components/shared/RadioButtonSelect.test.d.ts +6 -0
  249. package/dist/src/ui/components/shared/RadioButtonSelect.test.js +111 -0
  250. package/dist/src/ui/components/shared/RadioButtonSelect.test.js.map +1 -0
  251. package/dist/src/ui/components/shared/text-buffer.d.ts +286 -3
  252. package/dist/src/ui/components/shared/text-buffer.js +612 -89
  253. package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
  254. package/dist/src/ui/components/shared/vim-buffer-actions.d.ts +72 -0
  255. package/dist/src/ui/components/shared/vim-buffer-actions.js +552 -0
  256. package/dist/src/ui/components/shared/vim-buffer-actions.js.map +1 -0
  257. package/dist/src/ui/contexts/KeypressContext.d.ts +30 -0
  258. package/dist/src/ui/contexts/KeypressContext.js +309 -0
  259. package/dist/src/ui/contexts/KeypressContext.js.map +1 -0
  260. package/dist/src/ui/contexts/KeypressContext.test.d.ts +6 -0
  261. package/dist/src/ui/contexts/KeypressContext.test.js +219 -0
  262. package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -0
  263. package/dist/src/ui/contexts/SessionContext.d.ts +3 -0
  264. package/dist/src/ui/contexts/SessionContext.js +2 -1
  265. package/dist/src/ui/contexts/SessionContext.js.map +1 -1
  266. package/dist/src/ui/contexts/SettingsContext.d.ts +9 -0
  267. package/dist/src/ui/contexts/SettingsContext.js +15 -0
  268. package/dist/src/ui/contexts/SettingsContext.js.map +1 -0
  269. package/dist/src/ui/contexts/VimModeContext.d.ts +19 -0
  270. package/dist/src/ui/contexts/VimModeContext.js +48 -0
  271. package/dist/src/ui/contexts/VimModeContext.js.map +1 -0
  272. package/dist/src/ui/editors/editorSettingsManager.js +6 -13
  273. package/dist/src/ui/editors/editorSettingsManager.js.map +1 -1
  274. package/dist/src/ui/hooks/atCommandProcessor.js +82 -62
  275. package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
  276. package/dist/src/ui/hooks/atCommandProcessor.test.d.ts +6 -0
  277. package/dist/src/ui/hooks/atCommandProcessor.test.js +809 -0
  278. package/dist/src/ui/hooks/atCommandProcessor.test.js.map +1 -0
  279. package/dist/src/ui/hooks/shellCommandProcessor.d.ts +1 -0
  280. package/dist/src/ui/hooks/shellCommandProcessor.js +139 -196
  281. package/dist/src/ui/hooks/shellCommandProcessor.js.map +1 -1
  282. package/dist/src/ui/hooks/shellCommandProcessor.test.d.ts +6 -0
  283. package/dist/src/ui/hooks/shellCommandProcessor.test.js +328 -0
  284. package/dist/src/ui/hooks/shellCommandProcessor.test.js.map +1 -0
  285. package/dist/src/ui/hooks/slashCommandProcessor.d.ts +11 -3
  286. package/dist/src/ui/hooks/slashCommandProcessor.js +237 -90
  287. package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
  288. package/dist/src/ui/hooks/useAtCompletion.d.ts +23 -0
  289. package/dist/src/ui/hooks/useAtCompletion.js +178 -0
  290. package/dist/src/ui/hooks/useAtCompletion.js.map +1 -0
  291. package/dist/src/ui/hooks/useAutoAcceptIndicator.js +5 -5
  292. package/dist/src/ui/hooks/useAutoAcceptIndicator.js.map +1 -1
  293. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.d.ts +6 -0
  294. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js +191 -0
  295. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js.map +1 -0
  296. package/dist/src/ui/hooks/useCommandCompletion.d.ts +29 -0
  297. package/dist/src/ui/hooks/useCommandCompletion.js +165 -0
  298. package/dist/src/ui/hooks/useCommandCompletion.js.map +1 -0
  299. package/dist/src/ui/hooks/useCompletion.d.ts +5 -3
  300. package/dist/src/ui/hooks/useCompletion.js +7 -312
  301. package/dist/src/ui/hooks/useCompletion.js.map +1 -1
  302. package/dist/src/ui/hooks/useConsoleMessages.js +53 -37
  303. package/dist/src/ui/hooks/useConsoleMessages.js.map +1 -1
  304. package/dist/src/ui/hooks/useEditorSettings.test.d.ts +6 -0
  305. package/dist/src/ui/hooks/useEditorSettings.test.js +164 -0
  306. package/dist/src/ui/hooks/useEditorSettings.test.js.map +1 -0
  307. package/dist/src/ui/hooks/useFocus.d.ts +4 -0
  308. package/dist/src/ui/hooks/useFocus.js +4 -4
  309. package/dist/src/ui/hooks/useFocus.js.map +1 -1
  310. package/dist/src/ui/hooks/useFolderTrust.d.ts +12 -0
  311. package/dist/src/ui/hooks/useFolderTrust.js +55 -0
  312. package/dist/src/ui/hooks/useFolderTrust.js.map +1 -0
  313. package/dist/src/ui/hooks/useGeminiStream.d.ts +2 -1
  314. package/dist/src/ui/hooks/useGeminiStream.js +40 -26
  315. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  316. package/dist/src/ui/hooks/useGitBranchName.test.d.ts +6 -0
  317. package/dist/src/ui/hooks/useGitBranchName.test.js +175 -0
  318. package/dist/src/ui/hooks/useGitBranchName.test.js.map +1 -0
  319. package/dist/src/ui/hooks/useHistoryManager.test.d.ts +6 -0
  320. package/dist/src/ui/hooks/useHistoryManager.test.js +171 -0
  321. package/dist/src/ui/hooks/useHistoryManager.test.js.map +1 -0
  322. package/dist/src/ui/hooks/useInputHistory.d.ts +1 -1
  323. package/dist/src/ui/hooks/useInputHistory.test.d.ts +6 -0
  324. package/dist/src/ui/hooks/useInputHistory.test.js +207 -0
  325. package/dist/src/ui/hooks/useInputHistory.test.js.map +1 -0
  326. package/dist/src/ui/hooks/useKeypress.d.ts +4 -16
  327. package/dist/src/ui/hooks/useKeypress.js +8 -137
  328. package/dist/src/ui/hooks/useKeypress.js.map +1 -1
  329. package/dist/src/ui/hooks/useKittyKeyboardProtocol.d.ts +15 -0
  330. package/dist/src/ui/hooks/useKittyKeyboardProtocol.js +20 -0
  331. package/dist/src/ui/hooks/useKittyKeyboardProtocol.js.map +1 -0
  332. package/dist/src/ui/hooks/useLoadingIndicator.test.d.ts +6 -0
  333. package/dist/src/ui/hooks/useLoadingIndicator.test.js +91 -0
  334. package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -0
  335. package/dist/src/ui/hooks/useMessageQueue.d.ts +22 -0
  336. package/dist/src/ui/hooks/useMessageQueue.js +49 -0
  337. package/dist/src/ui/hooks/useMessageQueue.js.map +1 -0
  338. package/dist/src/ui/hooks/useMessageQueue.test.d.ts +6 -0
  339. package/dist/src/ui/hooks/useMessageQueue.test.js +158 -0
  340. package/dist/src/ui/hooks/useMessageQueue.test.js.map +1 -0
  341. package/dist/src/ui/hooks/usePhraseCycler.js +3 -5
  342. package/dist/src/ui/hooks/usePhraseCycler.js.map +1 -1
  343. package/dist/src/ui/hooks/usePrivacySettings.js +6 -2
  344. package/dist/src/ui/hooks/usePrivacySettings.js.map +1 -1
  345. package/dist/src/ui/hooks/usePrivacySettings.test.d.ts +6 -0
  346. package/dist/src/ui/hooks/usePrivacySettings.test.js +154 -0
  347. package/dist/src/ui/hooks/usePrivacySettings.test.js.map +1 -0
  348. package/dist/src/ui/hooks/useReactToolScheduler.d.ts +1 -1
  349. package/dist/src/ui/hooks/useReactToolScheduler.js +18 -17
  350. package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
  351. package/dist/src/ui/hooks/useReverseSearchCompletion.d.ts +19 -0
  352. package/dist/src/ui/hooks/useReverseSearchCompletion.js +56 -0
  353. package/dist/src/ui/hooks/useReverseSearchCompletion.js.map +1 -0
  354. package/dist/src/ui/hooks/useReverseSearchCompletion.test.d.ts +6 -0
  355. package/dist/src/ui/hooks/useReverseSearchCompletion.test.js +163 -0
  356. package/dist/src/ui/hooks/useReverseSearchCompletion.test.js.map +1 -0
  357. package/dist/src/ui/hooks/useSettingsCommand.d.ts +10 -0
  358. package/dist/src/ui/hooks/useSettingsCommand.js +21 -0
  359. package/dist/src/ui/hooks/useSettingsCommand.js.map +1 -0
  360. package/dist/src/ui/hooks/useShellHistory.d.ts +4 -2
  361. package/dist/src/ui/hooks/useShellHistory.js +30 -7
  362. package/dist/src/ui/hooks/useShellHistory.js.map +1 -1
  363. package/dist/src/ui/hooks/useShellHistory.test.d.ts +6 -0
  364. package/dist/src/ui/hooks/useShellHistory.test.js +159 -0
  365. package/dist/src/ui/hooks/useShellHistory.test.js.map +1 -0
  366. package/dist/src/ui/hooks/useSlashCompletion.d.ts +20 -0
  367. package/dist/src/ui/hooks/useSlashCompletion.js +135 -0
  368. package/dist/src/ui/hooks/useSlashCompletion.js.map +1 -0
  369. package/dist/src/ui/hooks/useSlashCompletion.test.d.ts +6 -0
  370. package/dist/src/ui/hooks/useSlashCompletion.test.js +272 -0
  371. package/dist/src/ui/hooks/useSlashCompletion.test.js.map +1 -0
  372. package/dist/src/ui/hooks/useThemeCommand.js +1 -1
  373. package/dist/src/ui/hooks/useThemeCommand.js.map +1 -1
  374. package/dist/src/ui/hooks/useTimer.test.d.ts +6 -0
  375. package/dist/src/ui/hooks/useTimer.test.js +90 -0
  376. package/dist/src/ui/hooks/useTimer.test.js.map +1 -0
  377. package/dist/src/ui/hooks/useToolScheduler.test.d.ts +6 -0
  378. package/dist/src/ui/hooks/useToolScheduler.test.js +846 -0
  379. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -0
  380. package/dist/src/ui/hooks/vim.d.ts +28 -0
  381. package/dist/src/ui/hooks/vim.js +639 -0
  382. package/dist/src/ui/hooks/vim.js.map +1 -0
  383. package/dist/src/ui/keyMatchers.d.ts +26 -0
  384. package/dist/src/ui/keyMatchers.js +68 -0
  385. package/dist/src/ui/keyMatchers.js.map +1 -0
  386. package/dist/src/ui/keyMatchers.test.d.ts +6 -0
  387. package/dist/src/ui/keyMatchers.test.js +276 -0
  388. package/dist/src/ui/keyMatchers.test.js.map +1 -0
  389. package/dist/src/ui/privacy/CloudFreePrivacyNotice.js +5 -4
  390. package/dist/src/ui/privacy/CloudFreePrivacyNotice.js.map +1 -1
  391. package/dist/src/ui/privacy/CloudPaidPrivacyNotice.js +5 -4
  392. package/dist/src/ui/privacy/CloudPaidPrivacyNotice.js.map +1 -1
  393. package/dist/src/ui/privacy/GeminiPrivacyNotice.js +5 -4
  394. package/dist/src/ui/privacy/GeminiPrivacyNotice.js.map +1 -1
  395. package/dist/src/ui/semantic-colors.d.ts +7 -0
  396. package/dist/src/ui/semantic-colors.js +24 -0
  397. package/dist/src/ui/semantic-colors.js.map +1 -0
  398. package/dist/src/ui/themes/ansi-light.js +4 -1
  399. package/dist/src/ui/themes/ansi-light.js.map +1 -1
  400. package/dist/src/ui/themes/ansi.js +4 -1
  401. package/dist/src/ui/themes/ansi.js.map +1 -1
  402. package/dist/src/ui/themes/atom-one-dark.js +4 -1
  403. package/dist/src/ui/themes/atom-one-dark.js.map +1 -1
  404. package/dist/src/ui/themes/ayu-light.js +5 -2
  405. package/dist/src/ui/themes/ayu-light.js.map +1 -1
  406. package/dist/src/ui/themes/ayu.js +5 -2
  407. package/dist/src/ui/themes/ayu.js.map +1 -1
  408. package/dist/src/ui/themes/color-utils.test.d.ts +6 -0
  409. package/dist/src/ui/themes/color-utils.test.js +197 -0
  410. package/dist/src/ui/themes/color-utils.test.js.map +1 -0
  411. package/dist/src/ui/themes/default-light.js +2 -1
  412. package/dist/src/ui/themes/default-light.js.map +1 -1
  413. package/dist/src/ui/themes/default.js +2 -1
  414. package/dist/src/ui/themes/default.js.map +1 -1
  415. package/dist/src/ui/themes/dracula.js +4 -1
  416. package/dist/src/ui/themes/dracula.js.map +1 -1
  417. package/dist/src/ui/themes/github-dark.js +4 -1
  418. package/dist/src/ui/themes/github-dark.js.map +1 -1
  419. package/dist/src/ui/themes/github-light.js +4 -1
  420. package/dist/src/ui/themes/github-light.js.map +1 -1
  421. package/dist/src/ui/themes/googlecode.js +4 -1
  422. package/dist/src/ui/themes/googlecode.js.map +1 -1
  423. package/dist/src/ui/themes/no-color.js +32 -1
  424. package/dist/src/ui/themes/no-color.js.map +1 -1
  425. package/dist/src/ui/themes/semantic-tokens.d.ts +37 -0
  426. package/dist/src/ui/themes/semantic-tokens.js +94 -0
  427. package/dist/src/ui/themes/semantic-tokens.js.map +1 -0
  428. package/dist/src/ui/themes/shades-of-purple.js +4 -1
  429. package/dist/src/ui/themes/shades-of-purple.js.map +1 -1
  430. package/dist/src/ui/themes/theme-manager.d.ts +6 -0
  431. package/dist/src/ui/themes/theme-manager.js +18 -2
  432. package/dist/src/ui/themes/theme-manager.js.map +1 -1
  433. package/dist/src/ui/themes/theme-manager.test.d.ts +6 -0
  434. package/dist/src/ui/themes/theme-manager.test.js +83 -0
  435. package/dist/src/ui/themes/theme-manager.test.js.map +1 -0
  436. package/dist/src/ui/themes/theme.d.ts +48 -2
  437. package/dist/src/ui/themes/theme.js +102 -92
  438. package/dist/src/ui/themes/theme.js.map +1 -1
  439. package/dist/src/ui/themes/xcode.js +4 -1
  440. package/dist/src/ui/themes/xcode.js.map +1 -1
  441. package/dist/src/ui/types.d.ts +13 -2
  442. package/dist/src/ui/types.js +1 -0
  443. package/dist/src/ui/types.js.map +1 -1
  444. package/dist/src/ui/utils/CodeColorizer.d.ts +3 -1
  445. package/dist/src/ui/utils/CodeColorizer.js +22 -9
  446. package/dist/src/ui/utils/CodeColorizer.js.map +1 -1
  447. package/dist/src/ui/utils/ConsolePatcher.d.ts +3 -1
  448. package/dist/src/ui/utils/ConsolePatcher.js +18 -8
  449. package/dist/src/ui/utils/ConsolePatcher.js.map +1 -1
  450. package/dist/src/ui/utils/InlineMarkdownRenderer.js +8 -1
  451. package/dist/src/ui/utils/InlineMarkdownRenderer.js.map +1 -1
  452. package/dist/src/ui/utils/MarkdownDisplay.js +4 -2
  453. package/dist/src/ui/utils/MarkdownDisplay.js.map +1 -1
  454. package/dist/src/ui/utils/MarkdownDisplay.test.d.ts +6 -0
  455. package/dist/src/ui/utils/MarkdownDisplay.test.js +151 -0
  456. package/dist/src/ui/utils/MarkdownDisplay.test.js.map +1 -0
  457. package/dist/src/ui/utils/clipboardUtils.test.d.ts +6 -0
  458. package/dist/src/ui/utils/clipboardUtils.test.js +65 -0
  459. package/dist/src/ui/utils/clipboardUtils.test.js.map +1 -0
  460. package/dist/src/ui/utils/commandUtils.d.ts +1 -0
  461. package/dist/src/ui/utils/commandUtils.js +22 -1
  462. package/dist/src/ui/utils/commandUtils.js.map +1 -1
  463. package/dist/src/ui/utils/commandUtils.test.d.ts +6 -0
  464. package/dist/src/ui/utils/commandUtils.test.js +294 -0
  465. package/dist/src/ui/utils/commandUtils.test.js.map +1 -0
  466. package/dist/src/ui/utils/computeStats.js +3 -1
  467. package/dist/src/ui/utils/computeStats.js.map +1 -1
  468. package/dist/src/ui/utils/displayUtils.test.d.ts +6 -0
  469. package/dist/src/ui/utils/displayUtils.test.js +42 -0
  470. package/dist/src/ui/utils/displayUtils.test.js.map +1 -0
  471. package/dist/src/ui/utils/formatters.test.d.ts +6 -0
  472. package/dist/src/ui/utils/formatters.test.js +56 -0
  473. package/dist/src/ui/utils/formatters.test.js.map +1 -0
  474. package/dist/src/ui/utils/isNarrowWidth.d.ts +6 -0
  475. package/dist/src/ui/utils/isNarrowWidth.js +9 -0
  476. package/dist/src/ui/utils/isNarrowWidth.js.map +1 -0
  477. package/dist/src/ui/utils/kittyProtocolDetector.d.ts +13 -0
  478. package/dist/src/ui/utils/kittyProtocolDetector.js +88 -0
  479. package/dist/src/ui/utils/kittyProtocolDetector.js.map +1 -0
  480. package/dist/src/ui/utils/markdownUtilities.test.d.ts +6 -0
  481. package/dist/src/ui/utils/markdownUtilities.test.js +42 -0
  482. package/dist/src/ui/utils/markdownUtilities.test.js.map +1 -0
  483. package/dist/src/ui/utils/platformConstants.d.ts +43 -0
  484. package/dist/src/ui/utils/platformConstants.js +44 -0
  485. package/dist/src/ui/utils/platformConstants.js.map +1 -0
  486. package/dist/src/ui/utils/terminalSetup.d.ts +30 -0
  487. package/dist/src/ui/utils/terminalSetup.js +281 -0
  488. package/dist/src/ui/utils/terminalSetup.js.map +1 -0
  489. package/dist/src/ui/utils/textUtils.d.ts +0 -8
  490. package/dist/src/ui/utils/textUtils.js +0 -22
  491. package/dist/src/ui/utils/textUtils.js.map +1 -1
  492. package/dist/src/ui/utils/updateCheck.d.ts +7 -1
  493. package/dist/src/ui/utils/updateCheck.js +47 -9
  494. package/dist/src/ui/utils/updateCheck.js.map +1 -1
  495. package/dist/src/ui/utils/updateCheck.test.d.ts +6 -0
  496. package/dist/src/ui/utils/updateCheck.test.js +145 -0
  497. package/dist/src/ui/utils/updateCheck.test.js.map +1 -0
  498. package/dist/src/utils/checks.d.ts +19 -0
  499. package/dist/src/utils/checks.js +24 -0
  500. package/dist/src/utils/checks.js.map +1 -0
  501. package/dist/src/utils/cleanup.d.ts +2 -2
  502. package/dist/src/utils/cleanup.js +2 -2
  503. package/dist/src/utils/cleanup.js.map +1 -1
  504. package/dist/src/utils/dialogScopeUtils.d.ts +31 -0
  505. package/dist/src/utils/dialogScopeUtils.js +48 -0
  506. package/dist/src/utils/dialogScopeUtils.js.map +1 -0
  507. package/dist/src/utils/events.d.ts +11 -0
  508. package/dist/src/utils/events.js +13 -0
  509. package/dist/src/utils/events.js.map +1 -0
  510. package/dist/src/utils/gitUtils.d.ts +30 -0
  511. package/dist/src/utils/gitUtils.js +89 -0
  512. package/dist/src/utils/gitUtils.js.map +1 -0
  513. package/dist/src/utils/gitUtils.test.d.ts +6 -0
  514. package/dist/src/utils/gitUtils.test.js +113 -0
  515. package/dist/src/utils/gitUtils.test.js.map +1 -0
  516. package/dist/src/utils/handleAutoUpdate.d.ts +11 -0
  517. package/dist/src/utils/handleAutoUpdate.js +101 -0
  518. package/dist/src/utils/handleAutoUpdate.js.map +1 -0
  519. package/dist/src/utils/installationInfo.d.ts +23 -0
  520. package/dist/src/utils/installationInfo.js +154 -0
  521. package/dist/src/utils/installationInfo.js.map +1 -0
  522. package/dist/src/utils/installationInfo.test.d.ts +6 -0
  523. package/dist/src/utils/installationInfo.test.js +242 -0
  524. package/dist/src/utils/installationInfo.test.js.map +1 -0
  525. package/dist/src/utils/readStdin.js +10 -0
  526. package/dist/src/utils/readStdin.js.map +1 -1
  527. package/dist/src/utils/resolvePath.d.ts +6 -0
  528. package/dist/src/utils/resolvePath.js +21 -0
  529. package/dist/src/utils/resolvePath.js.map +1 -0
  530. package/dist/src/utils/sandbox-macos-permissive-closed.sb +6 -0
  531. package/dist/src/utils/sandbox-macos-permissive-open.sb +6 -0
  532. package/dist/src/utils/sandbox-macos-permissive-proxied.sb +6 -0
  533. package/dist/src/utils/sandbox-macos-restrictive-closed.sb +6 -0
  534. package/dist/src/utils/sandbox-macos-restrictive-open.sb +6 -0
  535. package/dist/src/utils/sandbox-macos-restrictive-proxied.sb +6 -0
  536. package/dist/src/utils/sandbox.d.ts +2 -2
  537. package/dist/src/utils/sandbox.js +448 -397
  538. package/dist/src/utils/sandbox.js.map +1 -1
  539. package/dist/src/utils/settingsUtils.d.ts +134 -0
  540. package/dist/src/utils/settingsUtils.js +336 -0
  541. package/dist/src/utils/settingsUtils.js.map +1 -0
  542. package/dist/src/utils/settingsUtils.test.d.ts +6 -0
  543. package/dist/src/utils/settingsUtils.test.js +514 -0
  544. package/dist/src/utils/settingsUtils.test.js.map +1 -0
  545. package/dist/src/utils/spawnWrapper.d.ts +7 -0
  546. package/dist/src/utils/spawnWrapper.js +8 -0
  547. package/dist/src/utils/spawnWrapper.js.map +1 -0
  548. package/dist/src/utils/updateEventEmitter.d.ts +11 -0
  549. package/dist/src/utils/updateEventEmitter.js +12 -0
  550. package/dist/src/utils/updateEventEmitter.js.map +1 -0
  551. package/dist/src/utils/userStartupWarnings.test.d.ts +6 -0
  552. package/dist/src/utils/userStartupWarnings.test.js +67 -0
  553. package/dist/src/utils/userStartupWarnings.test.js.map +1 -0
  554. package/dist/src/utils/version.js +1 -1
  555. package/dist/src/utils/version.js.map +1 -1
  556. package/dist/src/validateNonInterActiveAuth.d.ts +7 -0
  557. package/dist/src/validateNonInterActiveAuth.js +37 -0
  558. package/dist/src/validateNonInterActiveAuth.js.map +1 -0
  559. package/dist/src/zed-integration/acp.d.ts +63 -0
  560. package/dist/src/{acp → zed-integration}/acp.js +76 -44
  561. package/dist/src/zed-integration/acp.js.map +1 -0
  562. package/dist/src/zed-integration/fileSystemService.d.ts +19 -0
  563. package/dist/src/zed-integration/fileSystemService.js +43 -0
  564. package/dist/src/zed-integration/fileSystemService.js.map +1 -0
  565. package/dist/src/zed-integration/schema.d.ts +11679 -0
  566. package/dist/src/zed-integration/schema.js +305 -0
  567. package/dist/src/zed-integration/schema.js.map +1 -0
  568. package/dist/src/zed-integration/zedIntegration.d.ts +10 -0
  569. package/dist/src/{acp/acpPeer.js → zed-integration/zedIntegration.js} +336 -169
  570. package/dist/src/zed-integration/zedIntegration.js.map +1 -0
  571. package/dist/tsconfig.tsbuildinfo +1 -1
  572. package/package.json +13 -8
  573. package/dist/src/acp/acp.d.ts +0 -208
  574. package/dist/src/acp/acp.js.map +0 -1
  575. package/dist/src/acp/acpPeer.d.ts +0 -8
  576. package/dist/src/acp/acpPeer.js.map +0 -1
  577. package/dist/src/ui/utils/errorParsing.d.ts +0 -7
  578. package/dist/src/ui/utils/errorParsing.js +0 -90
  579. package/dist/src/ui/utils/errorParsing.js.map +0 -1
@@ -8,9 +8,10 @@ import os from 'node:os';
8
8
  import path from 'node:path';
9
9
  import fs from 'node:fs';
10
10
  import { readFile } from 'node:fs/promises';
11
- import { quote } from 'shell-quote';
11
+ import { quote, parse } from 'shell-quote';
12
12
  import { USER_SETTINGS_DIR, SETTINGS_DIRECTORY_NAME, } from '../config/settings.js';
13
13
  import { promisify } from 'util';
14
+ import { ConsolePatcher } from '../ui/utils/ConsolePatcher.js';
14
15
  const execAsync = promisify(exec);
15
16
  function getContainerPath(hostPath) {
16
17
  if (os.platform() !== 'win32') {
@@ -53,7 +54,7 @@ const BUILTIN_SEATBELT_PROFILES = [
53
54
  * @returns {Promise<boolean>} A promise that resolves to true if the current user's UID/GID should be used, false otherwise.
54
55
  */
55
56
  async function shouldUseCurrentUserInSandbox() {
56
- const envVar = process.env.SANDBOX_SET_UID_GID?.toLowerCase().trim();
57
+ const envVar = process.env['SANDBOX_SET_UID_GID']?.toLowerCase().trim();
57
58
  if (envVar === '1' || envVar === 'true') {
58
59
  return true;
59
60
  }
@@ -90,7 +91,7 @@ function parseImageName(image) {
90
91
  return tag ? `${name}-${tag}` : name;
91
92
  }
92
93
  function ports() {
93
- return (process.env.SANDBOX_PORTS ?? '')
94
+ return (process.env['SANDBOX_PORTS'] ?? '')
94
95
  .split(',')
95
96
  .filter((p) => p.trim())
96
97
  .map((p) => p.trim());
@@ -101,8 +102,8 @@ function entrypoint(workdir) {
101
102
  const shellCmds = [];
102
103
  const pathSeparator = isWindows ? ';' : ':';
103
104
  let pathSuffix = '';
104
- if (process.env.PATH) {
105
- const paths = process.env.PATH.split(pathSeparator);
105
+ if (process.env['PATH']) {
106
+ const paths = process.env['PATH'].split(pathSeparator);
106
107
  for (const p of paths) {
107
108
  const containerPath = getContainerPath(p);
108
109
  if (containerPath.toLowerCase().startsWith(containerWorkdir.toLowerCase())) {
@@ -114,8 +115,8 @@ function entrypoint(workdir) {
114
115
  shellCmds.push(`export PATH="$PATH${pathSuffix}";`);
115
116
  }
116
117
  let pythonPathSuffix = '';
117
- if (process.env.PYTHONPATH) {
118
- const paths = process.env.PYTHONPATH.split(pathSeparator);
118
+ if (process.env['PYTHONPATH']) {
119
+ const paths = process.env['PYTHONPATH'].split(pathSeparator);
119
120
  for (const p of paths) {
120
121
  const containerPath = getContainerPath(p);
121
122
  if (containerPath.toLowerCase().startsWith(containerWorkdir.toLowerCase())) {
@@ -132,91 +133,451 @@ function entrypoint(workdir) {
132
133
  }
133
134
  ports().forEach((p) => shellCmds.push(`socat TCP4-LISTEN:${p},bind=$(hostname -i),fork,reuseaddr TCP4:127.0.0.1:${p} 2> /dev/null &`));
134
135
  const cliArgs = process.argv.slice(2).map((arg) => quote([arg]));
135
- const cliCmd = process.env.NODE_ENV === 'development'
136
- ? process.env.DEBUG
136
+ const cliCmd = process.env['NODE_ENV'] === 'development'
137
+ ? process.env['DEBUG']
137
138
  ? 'npm run debug --'
138
139
  : 'npm rebuild && npm run start --'
139
- : process.env.DEBUG
140
- ? `node --inspect-brk=0.0.0.0:${process.env.DEBUG_PORT || '9229'} $(which gemini)`
140
+ : process.env['DEBUG']
141
+ ? `node --inspect-brk=0.0.0.0:${process.env['DEBUG_PORT'] || '9229'} $(which gemini)`
141
142
  : 'gemini';
142
143
  const args = [...shellCmds, cliCmd, ...cliArgs];
143
144
  return ['bash', '-c', args.join(' ')];
144
145
  }
145
- export async function start_sandbox(config, nodeArgs = []) {
146
- if (config.command === 'sandbox-exec') {
147
- // disallow BUILD_SANDBOX
148
- if (process.env.BUILD_SANDBOX) {
149
- console.error('ERROR: cannot BUILD_SANDBOX when using macOS Seatbelt');
150
- process.exit(1);
146
+ export async function start_sandbox(config, nodeArgs = [], cliConfig) {
147
+ const patcher = new ConsolePatcher({
148
+ debugMode: cliConfig?.getDebugMode() || !!process.env['DEBUG'],
149
+ stderr: true,
150
+ });
151
+ patcher.patch();
152
+ try {
153
+ if (config.command === 'sandbox-exec') {
154
+ // disallow BUILD_SANDBOX
155
+ if (process.env['BUILD_SANDBOX']) {
156
+ console.error('ERROR: cannot BUILD_SANDBOX when using macOS Seatbelt');
157
+ process.exit(1);
158
+ }
159
+ const profile = (process.env['SEATBELT_PROFILE'] ??= 'permissive-open');
160
+ let profileFile = new URL(`sandbox-macos-${profile}.sb`, import.meta.url)
161
+ .pathname;
162
+ // if profile name is not recognized, then look for file under project settings directory
163
+ if (!BUILTIN_SEATBELT_PROFILES.includes(profile)) {
164
+ profileFile = path.join(SETTINGS_DIRECTORY_NAME, `sandbox-macos-${profile}.sb`);
165
+ }
166
+ if (!fs.existsSync(profileFile)) {
167
+ console.error(`ERROR: missing macos seatbelt profile file '${profileFile}'`);
168
+ process.exit(1);
169
+ }
170
+ // Log on STDERR so it doesn't clutter the output on STDOUT
171
+ console.error(`using macos seatbelt (profile: ${profile}) ...`);
172
+ // if DEBUG is set, convert to --inspect-brk in NODE_OPTIONS
173
+ const nodeOptions = [
174
+ ...(process.env['DEBUG'] ? ['--inspect-brk'] : []),
175
+ ...nodeArgs,
176
+ ].join(' ');
177
+ const args = [
178
+ '-D',
179
+ `TARGET_DIR=${fs.realpathSync(process.cwd())}`,
180
+ '-D',
181
+ `TMP_DIR=${fs.realpathSync(os.tmpdir())}`,
182
+ '-D',
183
+ `HOME_DIR=${fs.realpathSync(os.homedir())}`,
184
+ '-D',
185
+ `CACHE_DIR=${fs.realpathSync(execSync(`getconf DARWIN_USER_CACHE_DIR`).toString().trim())}`,
186
+ ];
187
+ // Add included directories from the workspace context
188
+ // Always add 5 INCLUDE_DIR parameters to ensure .sb files can reference them
189
+ const MAX_INCLUDE_DIRS = 5;
190
+ const targetDir = fs.realpathSync(cliConfig?.getTargetDir() || '');
191
+ const includedDirs = [];
192
+ if (cliConfig) {
193
+ const workspaceContext = cliConfig.getWorkspaceContext();
194
+ const directories = workspaceContext.getDirectories();
195
+ // Filter out TARGET_DIR
196
+ for (const dir of directories) {
197
+ const realDir = fs.realpathSync(dir);
198
+ if (realDir !== targetDir) {
199
+ includedDirs.push(realDir);
200
+ }
201
+ }
202
+ }
203
+ for (let i = 0; i < MAX_INCLUDE_DIRS; i++) {
204
+ let dirPath = '/dev/null'; // Default to a safe path that won't cause issues
205
+ if (i < includedDirs.length) {
206
+ dirPath = includedDirs[i];
207
+ }
208
+ args.push('-D', `INCLUDE_DIR_${i}=${dirPath}`);
209
+ }
210
+ args.push('-f', profileFile, 'sh', '-c', [
211
+ `SANDBOX=sandbox-exec`,
212
+ `NODE_OPTIONS="${nodeOptions}"`,
213
+ ...process.argv.map((arg) => quote([arg])),
214
+ ].join(' '));
215
+ // start and set up proxy if GEMINI_SANDBOX_PROXY_COMMAND is set
216
+ const proxyCommand = process.env['GEMINI_SANDBOX_PROXY_COMMAND'];
217
+ let proxyProcess = undefined;
218
+ let sandboxProcess = undefined;
219
+ const sandboxEnv = { ...process.env };
220
+ if (proxyCommand) {
221
+ const proxy = process.env['HTTPS_PROXY'] ||
222
+ process.env['https_proxy'] ||
223
+ process.env['HTTP_PROXY'] ||
224
+ process.env['http_proxy'] ||
225
+ 'http://localhost:8877';
226
+ sandboxEnv['HTTPS_PROXY'] = proxy;
227
+ sandboxEnv['https_proxy'] = proxy; // lower-case can be required, e.g. for curl
228
+ sandboxEnv['HTTP_PROXY'] = proxy;
229
+ sandboxEnv['http_proxy'] = proxy;
230
+ const noProxy = process.env['NO_PROXY'] || process.env['no_proxy'];
231
+ if (noProxy) {
232
+ sandboxEnv['NO_PROXY'] = noProxy;
233
+ sandboxEnv['no_proxy'] = noProxy;
234
+ }
235
+ proxyProcess = spawn(proxyCommand, {
236
+ stdio: ['ignore', 'pipe', 'pipe'],
237
+ shell: true,
238
+ detached: true,
239
+ });
240
+ // install handlers to stop proxy on exit/signal
241
+ const stopProxy = () => {
242
+ console.log('stopping proxy ...');
243
+ if (proxyProcess?.pid) {
244
+ process.kill(-proxyProcess.pid, 'SIGTERM');
245
+ }
246
+ };
247
+ process.on('exit', stopProxy);
248
+ process.on('SIGINT', stopProxy);
249
+ process.on('SIGTERM', stopProxy);
250
+ // commented out as it disrupts ink rendering
251
+ // proxyProcess.stdout?.on('data', (data) => {
252
+ // console.info(data.toString());
253
+ // });
254
+ proxyProcess.stderr?.on('data', (data) => {
255
+ console.error(data.toString());
256
+ });
257
+ proxyProcess.on('close', (code, signal) => {
258
+ console.error(`ERROR: proxy command '${proxyCommand}' exited with code ${code}, signal ${signal}`);
259
+ if (sandboxProcess?.pid) {
260
+ process.kill(-sandboxProcess.pid, 'SIGTERM');
261
+ }
262
+ process.exit(1);
263
+ });
264
+ console.log('waiting for proxy to start ...');
265
+ await execAsync(`until timeout 0.25 curl -s http://localhost:8877; do sleep 0.25; done`);
266
+ }
267
+ // spawn child and let it inherit stdio
268
+ sandboxProcess = spawn(config.command, args, {
269
+ stdio: 'inherit',
270
+ });
271
+ await new Promise((resolve) => sandboxProcess?.on('close', resolve));
272
+ return;
151
273
  }
152
- const profile = (process.env.SEATBELT_PROFILE ??= 'permissive-open');
153
- let profileFile = new URL(`sandbox-macos-${profile}.sb`, import.meta.url)
154
- .pathname;
155
- // if profile name is not recognized, then look for file under project settings directory
156
- if (!BUILTIN_SEATBELT_PROFILES.includes(profile)) {
157
- profileFile = path.join(SETTINGS_DIRECTORY_NAME, `sandbox-macos-${profile}.sb`);
274
+ console.error(`hopping into sandbox (command: ${config.command}) ...`);
275
+ // determine full path for gemini-cli to distinguish linked vs installed setting
276
+ const gcPath = fs.realpathSync(process.argv[1]);
277
+ const projectSandboxDockerfile = path.join(SETTINGS_DIRECTORY_NAME, 'sandbox.Dockerfile');
278
+ const isCustomProjectSandbox = fs.existsSync(projectSandboxDockerfile);
279
+ const image = config.image;
280
+ const workdir = path.resolve(process.cwd());
281
+ const containerWorkdir = getContainerPath(workdir);
282
+ // if BUILD_SANDBOX is set, then call scripts/build_sandbox.js under gemini-cli repo
283
+ //
284
+ // note this can only be done with binary linked from gemini-cli repo
285
+ if (process.env['BUILD_SANDBOX']) {
286
+ if (!gcPath.includes('gemini-cli/packages/')) {
287
+ console.error('ERROR: cannot build sandbox using installed gemini binary; ' +
288
+ 'run `npm link ./packages/cli` under gemini-cli repo to switch to linked binary.');
289
+ process.exit(1);
290
+ }
291
+ else {
292
+ console.error('building sandbox ...');
293
+ const gcRoot = gcPath.split('/packages/')[0];
294
+ // if project folder has sandbox.Dockerfile under project settings folder, use that
295
+ let buildArgs = '';
296
+ const projectSandboxDockerfile = path.join(SETTINGS_DIRECTORY_NAME, 'sandbox.Dockerfile');
297
+ if (isCustomProjectSandbox) {
298
+ console.error(`using ${projectSandboxDockerfile} for sandbox`);
299
+ buildArgs += `-f ${path.resolve(projectSandboxDockerfile)} -i ${image}`;
300
+ }
301
+ execSync(`cd ${gcRoot} && node scripts/build_sandbox.js -s ${buildArgs}`, {
302
+ stdio: 'inherit',
303
+ env: {
304
+ ...process.env,
305
+ GEMINI_SANDBOX: config.command, // in case sandbox is enabled via flags (see config.ts under cli package)
306
+ },
307
+ });
308
+ }
158
309
  }
159
- if (!fs.existsSync(profileFile)) {
160
- console.error(`ERROR: missing macos seatbelt profile file '${profileFile}'`);
310
+ // stop if image is missing
311
+ if (!(await ensureSandboxImageIsPresent(config.command, image))) {
312
+ const remedy = image === LOCAL_DEV_SANDBOX_IMAGE_NAME
313
+ ? 'Try running `npm run build:all` or `npm run build:sandbox` under the gemini-cli repo to build it locally, or check the image name and your network connection.'
314
+ : 'Please check the image name, your network connection, or notify gemini-cli-dev@google.com if the issue persists.';
315
+ console.error(`ERROR: Sandbox image '${image}' is missing or could not be pulled. ${remedy}`);
161
316
  process.exit(1);
162
317
  }
163
- // Log on STDERR so it doesn't clutter the output on STDOUT
164
- console.error(`using macos seatbelt (profile: ${profile}) ...`);
165
- // if DEBUG is set, convert to --inspect-brk in NODE_OPTIONS
166
- const nodeOptions = [
167
- ...(process.env.DEBUG ? ['--inspect-brk'] : []),
318
+ // use interactive mode and auto-remove container on exit
319
+ // run init binary inside container to forward signals & reap zombies
320
+ const args = ['run', '-i', '--rm', '--init', '--workdir', containerWorkdir];
321
+ // add custom flags from SANDBOX_FLAGS
322
+ if (process.env['SANDBOX_FLAGS']) {
323
+ const flags = parse(process.env['SANDBOX_FLAGS'], process.env).filter((f) => typeof f === 'string');
324
+ args.push(...flags);
325
+ }
326
+ // add TTY only if stdin is TTY as well, i.e. for piped input don't init TTY in container
327
+ if (process.stdin.isTTY) {
328
+ args.push('-t');
329
+ }
330
+ // mount current directory as working directory in sandbox (set via --workdir)
331
+ args.push('--volume', `${workdir}:${containerWorkdir}`);
332
+ // mount user settings directory inside container, after creating if missing
333
+ // note user/home changes inside sandbox and we mount at BOTH paths for consistency
334
+ const userSettingsDirOnHost = USER_SETTINGS_DIR;
335
+ const userSettingsDirInSandbox = getContainerPath(`/home/node/${SETTINGS_DIRECTORY_NAME}`);
336
+ if (!fs.existsSync(userSettingsDirOnHost)) {
337
+ fs.mkdirSync(userSettingsDirOnHost);
338
+ }
339
+ args.push('--volume', `${userSettingsDirOnHost}:${userSettingsDirInSandbox}`);
340
+ if (userSettingsDirInSandbox !== userSettingsDirOnHost) {
341
+ args.push('--volume', `${userSettingsDirOnHost}:${getContainerPath(userSettingsDirOnHost)}`);
342
+ }
343
+ // mount os.tmpdir() as os.tmpdir() inside container
344
+ args.push('--volume', `${os.tmpdir()}:${getContainerPath(os.tmpdir())}`);
345
+ // mount gcloud config directory if it exists
346
+ const gcloudConfigDir = path.join(os.homedir(), '.config', 'gcloud');
347
+ if (fs.existsSync(gcloudConfigDir)) {
348
+ args.push('--volume', `${gcloudConfigDir}:${getContainerPath(gcloudConfigDir)}:ro`);
349
+ }
350
+ // mount ADC file if GOOGLE_APPLICATION_CREDENTIALS is set
351
+ if (process.env['GOOGLE_APPLICATION_CREDENTIALS']) {
352
+ const adcFile = process.env['GOOGLE_APPLICATION_CREDENTIALS'];
353
+ if (fs.existsSync(adcFile)) {
354
+ args.push('--volume', `${adcFile}:${getContainerPath(adcFile)}:ro`);
355
+ args.push('--env', `GOOGLE_APPLICATION_CREDENTIALS=${getContainerPath(adcFile)}`);
356
+ }
357
+ }
358
+ // mount paths listed in SANDBOX_MOUNTS
359
+ if (process.env['SANDBOX_MOUNTS']) {
360
+ for (let mount of process.env['SANDBOX_MOUNTS'].split(',')) {
361
+ if (mount.trim()) {
362
+ // parse mount as from:to:opts
363
+ let [from, to, opts] = mount.trim().split(':');
364
+ to = to || from; // default to mount at same path inside container
365
+ opts = opts || 'ro'; // default to read-only
366
+ mount = `${from}:${to}:${opts}`;
367
+ // check that from path is absolute
368
+ if (!path.isAbsolute(from)) {
369
+ console.error(`ERROR: path '${from}' listed in SANDBOX_MOUNTS must be absolute`);
370
+ process.exit(1);
371
+ }
372
+ // check that from path exists on host
373
+ if (!fs.existsSync(from)) {
374
+ console.error(`ERROR: missing mount path '${from}' listed in SANDBOX_MOUNTS`);
375
+ process.exit(1);
376
+ }
377
+ console.error(`SANDBOX_MOUNTS: ${from} -> ${to} (${opts})`);
378
+ args.push('--volume', mount);
379
+ }
380
+ }
381
+ }
382
+ // expose env-specified ports on the sandbox
383
+ ports().forEach((p) => args.push('--publish', `${p}:${p}`));
384
+ // if DEBUG is set, expose debugging port
385
+ if (process.env['DEBUG']) {
386
+ const debugPort = process.env['DEBUG_PORT'] || '9229';
387
+ args.push(`--publish`, `${debugPort}:${debugPort}`);
388
+ }
389
+ // copy proxy environment variables, replacing localhost with SANDBOX_PROXY_NAME
390
+ // copy as both upper-case and lower-case as is required by some utilities
391
+ // GEMINI_SANDBOX_PROXY_COMMAND implies HTTPS_PROXY unless HTTP_PROXY is set
392
+ const proxyCommand = process.env['GEMINI_SANDBOX_PROXY_COMMAND'];
393
+ if (proxyCommand) {
394
+ let proxy = process.env['HTTPS_PROXY'] ||
395
+ process.env['https_proxy'] ||
396
+ process.env['HTTP_PROXY'] ||
397
+ process.env['http_proxy'] ||
398
+ 'http://localhost:8877';
399
+ proxy = proxy.replace('localhost', SANDBOX_PROXY_NAME);
400
+ if (proxy) {
401
+ args.push('--env', `HTTPS_PROXY=${proxy}`);
402
+ args.push('--env', `https_proxy=${proxy}`); // lower-case can be required, e.g. for curl
403
+ args.push('--env', `HTTP_PROXY=${proxy}`);
404
+ args.push('--env', `http_proxy=${proxy}`);
405
+ }
406
+ const noProxy = process.env['NO_PROXY'] || process.env['no_proxy'];
407
+ if (noProxy) {
408
+ args.push('--env', `NO_PROXY=${noProxy}`);
409
+ args.push('--env', `no_proxy=${noProxy}`);
410
+ }
411
+ // if using proxy, switch to internal networking through proxy
412
+ if (proxy) {
413
+ execSync(`${config.command} network inspect ${SANDBOX_NETWORK_NAME} || ${config.command} network create --internal ${SANDBOX_NETWORK_NAME}`);
414
+ args.push('--network', SANDBOX_NETWORK_NAME);
415
+ // if proxy command is set, create a separate network w/ host access (i.e. non-internal)
416
+ // we will run proxy in its own container connected to both host network and internal network
417
+ // this allows proxy to work even on rootless podman on macos with host<->vm<->container isolation
418
+ if (proxyCommand) {
419
+ execSync(`${config.command} network inspect ${SANDBOX_PROXY_NAME} || ${config.command} network create ${SANDBOX_PROXY_NAME}`);
420
+ }
421
+ }
422
+ }
423
+ // name container after image, plus numeric suffix to avoid conflicts
424
+ const imageName = parseImageName(image);
425
+ let index = 0;
426
+ const containerNameCheck = execSync(`${config.command} ps -a --format "{{.Names}}"`)
427
+ .toString()
428
+ .trim();
429
+ while (containerNameCheck.includes(`${imageName}-${index}`)) {
430
+ index++;
431
+ }
432
+ const containerName = `${imageName}-${index}`;
433
+ args.push('--name', containerName, '--hostname', containerName);
434
+ // copy GEMINI_API_KEY(s)
435
+ if (process.env['GEMINI_API_KEY']) {
436
+ args.push('--env', `GEMINI_API_KEY=${process.env['GEMINI_API_KEY']}`);
437
+ }
438
+ if (process.env['GOOGLE_API_KEY']) {
439
+ args.push('--env', `GOOGLE_API_KEY=${process.env['GOOGLE_API_KEY']}`);
440
+ }
441
+ // copy GOOGLE_GENAI_USE_VERTEXAI
442
+ if (process.env['GOOGLE_GENAI_USE_VERTEXAI']) {
443
+ args.push('--env', `GOOGLE_GENAI_USE_VERTEXAI=${process.env['GOOGLE_GENAI_USE_VERTEXAI']}`);
444
+ }
445
+ // copy GOOGLE_GENAI_USE_GCA
446
+ if (process.env['GOOGLE_GENAI_USE_GCA']) {
447
+ args.push('--env', `GOOGLE_GENAI_USE_GCA=${process.env['GOOGLE_GENAI_USE_GCA']}`);
448
+ }
449
+ // copy GOOGLE_CLOUD_PROJECT
450
+ if (process.env['GOOGLE_CLOUD_PROJECT']) {
451
+ args.push('--env', `GOOGLE_CLOUD_PROJECT=${process.env['GOOGLE_CLOUD_PROJECT']}`);
452
+ }
453
+ // copy GOOGLE_CLOUD_LOCATION
454
+ if (process.env['GOOGLE_CLOUD_LOCATION']) {
455
+ args.push('--env', `GOOGLE_CLOUD_LOCATION=${process.env['GOOGLE_CLOUD_LOCATION']}`);
456
+ }
457
+ // copy GEMINI_MODEL
458
+ if (process.env['GEMINI_MODEL']) {
459
+ args.push('--env', `GEMINI_MODEL=${process.env['GEMINI_MODEL']}`);
460
+ }
461
+ // copy TERM and COLORTERM to try to maintain terminal setup
462
+ if (process.env['TERM']) {
463
+ args.push('--env', `TERM=${process.env['TERM']}`);
464
+ }
465
+ if (process.env['COLORTERM']) {
466
+ args.push('--env', `COLORTERM=${process.env['COLORTERM']}`);
467
+ }
468
+ // Pass through IDE mode environment variables
469
+ for (const envVar of [
470
+ 'CELL_CLI_IDE_SERVER_PORT',
471
+ 'CELL_CLI_IDE_WORKSPACE_PATH',
472
+ 'TERM_PROGRAM',
473
+ ]) {
474
+ if (process.env[envVar]) {
475
+ args.push('--env', `${envVar}=${process.env[envVar]}`);
476
+ }
477
+ }
478
+ // copy VIRTUAL_ENV if under working directory
479
+ // also mount-replace VIRTUAL_ENV directory with <project_settings>/sandbox.venv
480
+ // sandbox can then set up this new VIRTUAL_ENV directory using sandbox.bashrc (see below)
481
+ // directory will be empty if not set up, which is still preferable to having host binaries
482
+ if (process.env['VIRTUAL_ENV']
483
+ ?.toLowerCase()
484
+ .startsWith(workdir.toLowerCase())) {
485
+ const sandboxVenvPath = path.resolve(SETTINGS_DIRECTORY_NAME, 'sandbox.venv');
486
+ if (!fs.existsSync(sandboxVenvPath)) {
487
+ fs.mkdirSync(sandboxVenvPath, { recursive: true });
488
+ }
489
+ args.push('--volume', `${sandboxVenvPath}:${getContainerPath(process.env['VIRTUAL_ENV'])}`);
490
+ args.push('--env', `VIRTUAL_ENV=${getContainerPath(process.env['VIRTUAL_ENV'])}`);
491
+ }
492
+ // copy additional environment variables from SANDBOX_ENV
493
+ if (process.env['SANDBOX_ENV']) {
494
+ for (let env of process.env['SANDBOX_ENV'].split(',')) {
495
+ if ((env = env.trim())) {
496
+ if (env.includes('=')) {
497
+ console.error(`SANDBOX_ENV: ${env}`);
498
+ args.push('--env', env);
499
+ }
500
+ else {
501
+ console.error('ERROR: SANDBOX_ENV must be a comma-separated list of key=value pairs');
502
+ process.exit(1);
503
+ }
504
+ }
505
+ }
506
+ }
507
+ // copy NODE_OPTIONS
508
+ const existingNodeOptions = process.env['NODE_OPTIONS'] || '';
509
+ const allNodeOptions = [
510
+ ...(existingNodeOptions ? [existingNodeOptions] : []),
168
511
  ...nodeArgs,
169
512
  ].join(' ');
170
- const args = [
171
- '-D',
172
- `TARGET_DIR=${fs.realpathSync(process.cwd())}`,
173
- '-D',
174
- `TMP_DIR=${fs.realpathSync(os.tmpdir())}`,
175
- '-D',
176
- `HOME_DIR=${fs.realpathSync(os.homedir())}`,
177
- '-D',
178
- `CACHE_DIR=${fs.realpathSync(execSync(`getconf DARWIN_USER_CACHE_DIR`).toString().trim())}`,
179
- '-f',
180
- profileFile,
181
- 'sh',
182
- '-c',
183
- [
184
- `SANDBOX=sandbox-exec`,
185
- `NODE_OPTIONS="${nodeOptions}"`,
186
- ...process.argv.map((arg) => quote([arg])),
187
- ].join(' '),
188
- ];
513
+ if (allNodeOptions.length > 0) {
514
+ args.push('--env', `NODE_OPTIONS="${allNodeOptions}"`);
515
+ }
516
+ // set SANDBOX as container name
517
+ args.push('--env', `SANDBOX=${containerName}`);
518
+ // for podman only, use empty --authfile to skip unnecessary auth refresh overhead
519
+ if (config.command === 'podman') {
520
+ const emptyAuthFilePath = path.join(os.tmpdir(), 'empty_auth.json');
521
+ fs.writeFileSync(emptyAuthFilePath, '{}', 'utf-8');
522
+ args.push('--authfile', emptyAuthFilePath);
523
+ }
524
+ // Determine if the current user's UID/GID should be passed to the sandbox.
525
+ // See shouldUseCurrentUserInSandbox for more details.
526
+ let userFlag = '';
527
+ const finalEntrypoint = entrypoint(workdir);
528
+ if (process.env['GEMINI_CLI_INTEGRATION_TEST'] === 'true') {
529
+ args.push('--user', 'root');
530
+ userFlag = '--user root';
531
+ }
532
+ else if (await shouldUseCurrentUserInSandbox()) {
533
+ // For the user-creation logic to work, the container must start as root.
534
+ // The entrypoint script then handles dropping privileges to the correct user.
535
+ args.push('--user', 'root');
536
+ const uid = execSync('id -u').toString().trim();
537
+ const gid = execSync('id -g').toString().trim();
538
+ // Instead of passing --user to the main sandbox container, we let it
539
+ // start as root, then create a user with the host's UID/GID, and
540
+ // finally switch to that user to run the gemini process. This is
541
+ // necessary on Linux to ensure the user exists within the
542
+ // container's /etc/passwd file, which is required by os.userInfo().
543
+ const username = 'gemini';
544
+ const homeDir = getContainerPath(os.homedir());
545
+ const setupUserCommands = [
546
+ // Use -f with groupadd to avoid errors if the group already exists.
547
+ `groupadd -f -g ${gid} ${username}`,
548
+ // Create user only if it doesn't exist. Use -o for non-unique UID.
549
+ `id -u ${username} &>/dev/null || useradd -o -u ${uid} -g ${gid} -d ${homeDir} -s /bin/bash ${username}`,
550
+ ].join(' && ');
551
+ const originalCommand = finalEntrypoint[2];
552
+ const escapedOriginalCommand = originalCommand.replace(/'/g, "'\\''");
553
+ // Use `su -p` to preserve the environment.
554
+ const suCommand = `su -p ${username} -c '${escapedOriginalCommand}'`;
555
+ // The entrypoint is always `['bash', '-c', '<command>']`, so we modify the command part.
556
+ finalEntrypoint[2] = `${setupUserCommands} && ${suCommand}`;
557
+ // We still need userFlag for the simpler proxy container, which does not have this issue.
558
+ userFlag = `--user ${uid}:${gid}`;
559
+ // When forcing a UID in the sandbox, $HOME can be reset to '/', so we copy $HOME as well.
560
+ args.push('--env', `HOME=${os.homedir()}`);
561
+ }
562
+ // push container image name
563
+ args.push(image);
564
+ // push container entrypoint (including args)
565
+ args.push(...finalEntrypoint);
189
566
  // start and set up proxy if GEMINI_SANDBOX_PROXY_COMMAND is set
190
- const proxyCommand = process.env.GEMINI_SANDBOX_PROXY_COMMAND;
191
567
  let proxyProcess = undefined;
192
568
  let sandboxProcess = undefined;
193
- const sandboxEnv = { ...process.env };
194
569
  if (proxyCommand) {
195
- const proxy = process.env.HTTPS_PROXY ||
196
- process.env.https_proxy ||
197
- process.env.HTTP_PROXY ||
198
- process.env.http_proxy ||
199
- 'http://localhost:8877';
200
- sandboxEnv['HTTPS_PROXY'] = proxy;
201
- sandboxEnv['https_proxy'] = proxy; // lower-case can be required, e.g. for curl
202
- sandboxEnv['HTTP_PROXY'] = proxy;
203
- sandboxEnv['http_proxy'] = proxy;
204
- const noProxy = process.env.NO_PROXY || process.env.no_proxy;
205
- if (noProxy) {
206
- sandboxEnv['NO_PROXY'] = noProxy;
207
- sandboxEnv['no_proxy'] = noProxy;
208
- }
209
- proxyProcess = spawn(proxyCommand, {
570
+ // run proxyCommand in its own container
571
+ const proxyContainerCommand = `${config.command} run --rm --init ${userFlag} --name ${SANDBOX_PROXY_NAME} --network ${SANDBOX_PROXY_NAME} -p 8877:8877 -v ${process.cwd()}:${workdir} --workdir ${workdir} ${image} ${proxyCommand}`;
572
+ proxyProcess = spawn(proxyContainerCommand, {
210
573
  stdio: ['ignore', 'pipe', 'pipe'],
211
574
  shell: true,
212
575
  detached: true,
213
576
  });
214
577
  // install handlers to stop proxy on exit/signal
215
578
  const stopProxy = () => {
216
- console.log('stopping proxy ...');
217
- if (proxyProcess?.pid) {
218
- process.kill(-proxyProcess.pid, 'SIGTERM');
219
- }
579
+ console.log('stopping proxy container ...');
580
+ execSync(`${config.command} rm -f ${SANDBOX_PROXY_NAME}`);
220
581
  };
221
582
  process.on('exit', stopProxy);
222
583
  process.on('SIGINT', stopProxy);
@@ -226,10 +587,10 @@ export async function start_sandbox(config, nodeArgs = []) {
226
587
  // console.info(data.toString());
227
588
  // });
228
589
  proxyProcess.stderr?.on('data', (data) => {
229
- console.error(data.toString());
590
+ console.error(data.toString().trim());
230
591
  });
231
592
  proxyProcess.on('close', (code, signal) => {
232
- console.error(`ERROR: proxy command '${proxyCommand}' exited with code ${code}, signal ${signal}`);
593
+ console.error(`ERROR: proxy container command '${proxyContainerCommand}' exited with code ${code}, signal ${signal}`);
233
594
  if (sandboxProcess?.pid) {
234
595
  process.kill(-sandboxProcess.pid, 'SIGTERM');
235
596
  }
@@ -237,339 +598,29 @@ export async function start_sandbox(config, nodeArgs = []) {
237
598
  });
238
599
  console.log('waiting for proxy to start ...');
239
600
  await execAsync(`until timeout 0.25 curl -s http://localhost:8877; do sleep 0.25; done`);
601
+ // connect proxy container to sandbox network
602
+ // (workaround for older versions of docker that don't support multiple --network args)
603
+ await execAsync(`${config.command} network connect ${SANDBOX_NETWORK_NAME} ${SANDBOX_PROXY_NAME}`);
240
604
  }
241
605
  // spawn child and let it inherit stdio
242
606
  sandboxProcess = spawn(config.command, args, {
243
607
  stdio: 'inherit',
244
608
  });
245
- await new Promise((resolve) => sandboxProcess?.on('close', resolve));
246
- return;
247
- }
248
- console.error(`hopping into sandbox (command: ${config.command}) ...`);
249
- // determine full path for gemini-cli to distinguish linked vs installed setting
250
- const gcPath = fs.realpathSync(process.argv[1]);
251
- const projectSandboxDockerfile = path.join(SETTINGS_DIRECTORY_NAME, 'sandbox.Dockerfile');
252
- const isCustomProjectSandbox = fs.existsSync(projectSandboxDockerfile);
253
- const image = config.image;
254
- const workdir = path.resolve(process.cwd());
255
- const containerWorkdir = getContainerPath(workdir);
256
- // if BUILD_SANDBOX is set, then call scripts/build_sandbox.js under gemini-cli repo
257
- //
258
- // note this can only be done with binary linked from gemini-cli repo
259
- if (process.env.BUILD_SANDBOX) {
260
- if (!gcPath.includes('gemini-cli/packages/')) {
261
- console.error('ERROR: cannot build sandbox using installed gemini binary; ' +
262
- 'run `npm link ./packages/cli` under gemini-cli repo to switch to linked binary.');
263
- process.exit(1);
264
- }
265
- else {
266
- console.error('building sandbox ...');
267
- const gcRoot = gcPath.split('/packages/')[0];
268
- // if project folder has sandbox.Dockerfile under project settings folder, use that
269
- let buildArgs = '';
270
- const projectSandboxDockerfile = path.join(SETTINGS_DIRECTORY_NAME, 'sandbox.Dockerfile');
271
- if (isCustomProjectSandbox) {
272
- console.error(`using ${projectSandboxDockerfile} for sandbox`);
273
- buildArgs += `-f ${path.resolve(projectSandboxDockerfile)} -i ${image}`;
274
- }
275
- execSync(`cd ${gcRoot} && node scripts/build_sandbox.js -s ${buildArgs}`, {
276
- stdio: 'inherit',
277
- env: {
278
- ...process.env,
279
- GEMINI_SANDBOX: config.command, // in case sandbox is enabled via flags (see config.ts under cli package)
280
- },
281
- });
282
- }
283
- }
284
- // stop if image is missing
285
- if (!(await ensureSandboxImageIsPresent(config.command, image))) {
286
- const remedy = image === LOCAL_DEV_SANDBOX_IMAGE_NAME
287
- ? 'Try running `npm run build:all` or `npm run build:sandbox` under the gemini-cli repo to build it locally, or check the image name and your network connection.'
288
- : 'Please check the image name, your network connection, or notify gemini-cli-dev@google.com if the issue persists.';
289
- console.error(`ERROR: Sandbox image '${image}' is missing or could not be pulled. ${remedy}`);
290
- process.exit(1);
291
- }
292
- // use interactive mode and auto-remove container on exit
293
- // run init binary inside container to forward signals & reap zombies
294
- const args = ['run', '-i', '--rm', '--init', '--workdir', containerWorkdir];
295
- // add TTY only if stdin is TTY as well, i.e. for piped input don't init TTY in container
296
- if (process.stdin.isTTY) {
297
- args.push('-t');
298
- }
299
- // mount current directory as working directory in sandbox (set via --workdir)
300
- args.push('--volume', `${workdir}:${containerWorkdir}`);
301
- // mount user settings directory inside container, after creating if missing
302
- // note user/home changes inside sandbox and we mount at BOTH paths for consistency
303
- const userSettingsDirOnHost = USER_SETTINGS_DIR;
304
- const userSettingsDirInSandbox = getContainerPath(`/home/node/${SETTINGS_DIRECTORY_NAME}`);
305
- if (!fs.existsSync(userSettingsDirOnHost)) {
306
- fs.mkdirSync(userSettingsDirOnHost);
307
- }
308
- args.push('--volume', `${userSettingsDirOnHost}:${userSettingsDirInSandbox}`);
309
- if (userSettingsDirInSandbox !== userSettingsDirOnHost) {
310
- args.push('--volume', `${userSettingsDirOnHost}:${getContainerPath(userSettingsDirOnHost)}`);
311
- }
312
- // mount os.tmpdir() as os.tmpdir() inside container
313
- args.push('--volume', `${os.tmpdir()}:${getContainerPath(os.tmpdir())}`);
314
- // mount gcloud config directory if it exists
315
- const gcloudConfigDir = path.join(os.homedir(), '.config', 'gcloud');
316
- if (fs.existsSync(gcloudConfigDir)) {
317
- args.push('--volume', `${gcloudConfigDir}:${getContainerPath(gcloudConfigDir)}:ro`);
318
- }
319
- // mount ADC file if GOOGLE_APPLICATION_CREDENTIALS is set
320
- if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
321
- const adcFile = process.env.GOOGLE_APPLICATION_CREDENTIALS;
322
- if (fs.existsSync(adcFile)) {
323
- args.push('--volume', `${adcFile}:${getContainerPath(adcFile)}:ro`);
324
- args.push('--env', `GOOGLE_APPLICATION_CREDENTIALS=${getContainerPath(adcFile)}`);
325
- }
326
- }
327
- // mount paths listed in SANDBOX_MOUNTS
328
- if (process.env.SANDBOX_MOUNTS) {
329
- for (let mount of process.env.SANDBOX_MOUNTS.split(',')) {
330
- if (mount.trim()) {
331
- // parse mount as from:to:opts
332
- let [from, to, opts] = mount.trim().split(':');
333
- to = to || from; // default to mount at same path inside container
334
- opts = opts || 'ro'; // default to read-only
335
- mount = `${from}:${to}:${opts}`;
336
- // check that from path is absolute
337
- if (!path.isAbsolute(from)) {
338
- console.error(`ERROR: path '${from}' listed in SANDBOX_MOUNTS must be absolute`);
339
- process.exit(1);
340
- }
341
- // check that from path exists on host
342
- if (!fs.existsSync(from)) {
343
- console.error(`ERROR: missing mount path '${from}' listed in SANDBOX_MOUNTS`);
344
- process.exit(1);
345
- }
346
- console.error(`SANDBOX_MOUNTS: ${from} -> ${to} (${opts})`);
347
- args.push('--volume', mount);
348
- }
349
- }
350
- }
351
- // expose env-specified ports on the sandbox
352
- ports().forEach((p) => args.push('--publish', `${p}:${p}`));
353
- // if DEBUG is set, expose debugging port
354
- if (process.env.DEBUG) {
355
- const debugPort = process.env.DEBUG_PORT || '9229';
356
- args.push(`--publish`, `${debugPort}:${debugPort}`);
357
- }
358
- // copy proxy environment variables, replacing localhost with SANDBOX_PROXY_NAME
359
- // copy as both upper-case and lower-case as is required by some utilities
360
- // GEMINI_SANDBOX_PROXY_COMMAND implies HTTPS_PROXY unless HTTP_PROXY is set
361
- const proxyCommand = process.env.GEMINI_SANDBOX_PROXY_COMMAND;
362
- if (proxyCommand) {
363
- let proxy = process.env.HTTPS_PROXY ||
364
- process.env.https_proxy ||
365
- process.env.HTTP_PROXY ||
366
- process.env.http_proxy ||
367
- 'http://localhost:8877';
368
- proxy = proxy.replace('localhost', SANDBOX_PROXY_NAME);
369
- if (proxy) {
370
- args.push('--env', `HTTPS_PROXY=${proxy}`);
371
- args.push('--env', `https_proxy=${proxy}`); // lower-case can be required, e.g. for curl
372
- args.push('--env', `HTTP_PROXY=${proxy}`);
373
- args.push('--env', `http_proxy=${proxy}`);
374
- }
375
- const noProxy = process.env.NO_PROXY || process.env.no_proxy;
376
- if (noProxy) {
377
- args.push('--env', `NO_PROXY=${noProxy}`);
378
- args.push('--env', `no_proxy=${noProxy}`);
379
- }
380
- // if using proxy, switch to internal networking through proxy
381
- if (proxy) {
382
- execSync(`${config.command} network inspect ${SANDBOX_NETWORK_NAME} || ${config.command} network create --internal ${SANDBOX_NETWORK_NAME}`);
383
- args.push('--network', SANDBOX_NETWORK_NAME);
384
- // if proxy command is set, create a separate network w/ host access (i.e. non-internal)
385
- // we will run proxy in its own container connected to both host network and internal network
386
- // this allows proxy to work even on rootless podman on macos with host<->vm<->container isolation
387
- if (proxyCommand) {
388
- execSync(`${config.command} network inspect ${SANDBOX_PROXY_NAME} || ${config.command} network create ${SANDBOX_PROXY_NAME}`);
389
- }
390
- }
391
- }
392
- // name container after image, plus numeric suffix to avoid conflicts
393
- const imageName = parseImageName(image);
394
- let index = 0;
395
- const containerNameCheck = execSync(`${config.command} ps -a --format "{{.Names}}"`)
396
- .toString()
397
- .trim();
398
- while (containerNameCheck.includes(`${imageName}-${index}`)) {
399
- index++;
400
- }
401
- const containerName = `${imageName}-${index}`;
402
- args.push('--name', containerName, '--hostname', containerName);
403
- // copy GEMINI_API_KEY(s)
404
- if (process.env.GEMINI_API_KEY) {
405
- args.push('--env', `GEMINI_API_KEY=${process.env.GEMINI_API_KEY}`);
406
- }
407
- if (process.env.GOOGLE_API_KEY) {
408
- args.push('--env', `GOOGLE_API_KEY=${process.env.GOOGLE_API_KEY}`);
409
- }
410
- // copy GOOGLE_GENAI_USE_VERTEXAI
411
- if (process.env.GOOGLE_GENAI_USE_VERTEXAI) {
412
- args.push('--env', `GOOGLE_GENAI_USE_VERTEXAI=${process.env.GOOGLE_GENAI_USE_VERTEXAI}`);
413
- }
414
- // copy GOOGLE_CLOUD_PROJECT
415
- if (process.env.GOOGLE_CLOUD_PROJECT) {
416
- args.push('--env', `GOOGLE_CLOUD_PROJECT=${process.env.GOOGLE_CLOUD_PROJECT}`);
417
- }
418
- // copy GOOGLE_CLOUD_LOCATION
419
- if (process.env.GOOGLE_CLOUD_LOCATION) {
420
- args.push('--env', `GOOGLE_CLOUD_LOCATION=${process.env.GOOGLE_CLOUD_LOCATION}`);
421
- }
422
- // copy GEMINI_MODEL
423
- if (process.env.GEMINI_MODEL) {
424
- args.push('--env', `GEMINI_MODEL=${process.env.GEMINI_MODEL}`);
425
- }
426
- // copy TERM and COLORTERM to try to maintain terminal setup
427
- if (process.env.TERM) {
428
- args.push('--env', `TERM=${process.env.TERM}`);
429
- }
430
- if (process.env.COLORTERM) {
431
- args.push('--env', `COLORTERM=${process.env.COLORTERM}`);
432
- }
433
- // copy VIRTUAL_ENV if under working directory
434
- // also mount-replace VIRTUAL_ENV directory with <project_settings>/sandbox.venv
435
- // sandbox can then set up this new VIRTUAL_ENV directory using sandbox.bashrc (see below)
436
- // directory will be empty if not set up, which is still preferable to having host binaries
437
- if (process.env.VIRTUAL_ENV?.toLowerCase().startsWith(workdir.toLowerCase())) {
438
- const sandboxVenvPath = path.resolve(SETTINGS_DIRECTORY_NAME, 'sandbox.venv');
439
- if (!fs.existsSync(sandboxVenvPath)) {
440
- fs.mkdirSync(sandboxVenvPath, { recursive: true });
441
- }
442
- args.push('--volume', `${sandboxVenvPath}:${getContainerPath(process.env.VIRTUAL_ENV)}`);
443
- args.push('--env', `VIRTUAL_ENV=${getContainerPath(process.env.VIRTUAL_ENV)}`);
444
- }
445
- // copy additional environment variables from SANDBOX_ENV
446
- if (process.env.SANDBOX_ENV) {
447
- for (let env of process.env.SANDBOX_ENV.split(',')) {
448
- if ((env = env.trim())) {
449
- if (env.includes('=')) {
450
- console.error(`SANDBOX_ENV: ${env}`);
451
- args.push('--env', env);
452
- }
453
- else {
454
- console.error('ERROR: SANDBOX_ENV must be a comma-separated list of key=value pairs');
455
- process.exit(1);
456
- }
457
- }
458
- }
459
- }
460
- // copy NODE_OPTIONS
461
- const existingNodeOptions = process.env.NODE_OPTIONS || '';
462
- const allNodeOptions = [
463
- ...(existingNodeOptions ? [existingNodeOptions] : []),
464
- ...nodeArgs,
465
- ].join(' ');
466
- if (allNodeOptions.length > 0) {
467
- args.push('--env', `NODE_OPTIONS="${allNodeOptions}"`);
468
- }
469
- // set SANDBOX as container name
470
- args.push('--env', `SANDBOX=${containerName}`);
471
- // for podman only, use empty --authfile to skip unnecessary auth refresh overhead
472
- if (config.command === 'podman') {
473
- const emptyAuthFilePath = path.join(os.tmpdir(), 'empty_auth.json');
474
- fs.writeFileSync(emptyAuthFilePath, '{}', 'utf-8');
475
- args.push('--authfile', emptyAuthFilePath);
476
- }
477
- // Determine if the current user's UID/GID should be passed to the sandbox.
478
- // See shouldUseCurrentUserInSandbox for more details.
479
- let userFlag = '';
480
- const finalEntrypoint = entrypoint(workdir);
481
- if (process.env.GEMINI_CLI_INTEGRATION_TEST === 'true') {
482
- args.push('--user', 'root');
483
- userFlag = '--user root';
484
- }
485
- else if (await shouldUseCurrentUserInSandbox()) {
486
- // For the user-creation logic to work, the container must start as root.
487
- // The entrypoint script then handles dropping privileges to the correct user.
488
- args.push('--user', 'root');
489
- const uid = execSync('id -u').toString().trim();
490
- const gid = execSync('id -g').toString().trim();
491
- // Instead of passing --user to the main sandbox container, we let it
492
- // start as root, then create a user with the host's UID/GID, and
493
- // finally switch to that user to run the gemini process. This is
494
- // necessary on Linux to ensure the user exists within the
495
- // container's /etc/passwd file, which is required by os.userInfo().
496
- const username = 'gemini';
497
- const homeDir = getContainerPath(os.homedir());
498
- const setupUserCommands = [
499
- // Use -f with groupadd to avoid errors if the group already exists.
500
- `groupadd -f -g ${gid} ${username}`,
501
- // Create user only if it doesn't exist. Use -o for non-unique UID.
502
- `id -u ${username} &>/dev/null || useradd -o -u ${uid} -g ${gid} -d ${homeDir} -s /bin/bash ${username}`,
503
- ].join(' && ');
504
- const originalCommand = finalEntrypoint[2];
505
- const escapedOriginalCommand = originalCommand.replace(/'/g, "'\\''");
506
- // Use `su -p` to preserve the environment.
507
- const suCommand = `su -p ${username} -c '${escapedOriginalCommand}'`;
508
- // The entrypoint is always `['bash', '-c', '<command>']`, so we modify the command part.
509
- finalEntrypoint[2] = `${setupUserCommands} && ${suCommand}`;
510
- // We still need userFlag for the simpler proxy container, which does not have this issue.
511
- userFlag = `--user ${uid}:${gid}`;
512
- // When forcing a UID in the sandbox, $HOME can be reset to '/', so we copy $HOME as well.
513
- args.push('--env', `HOME=${os.homedir()}`);
514
- }
515
- // push container image name
516
- args.push(image);
517
- // push container entrypoint (including args)
518
- args.push(...finalEntrypoint);
519
- // start and set up proxy if GEMINI_SANDBOX_PROXY_COMMAND is set
520
- let proxyProcess = undefined;
521
- let sandboxProcess = undefined;
522
- if (proxyCommand) {
523
- // run proxyCommand in its own container
524
- const proxyContainerCommand = `${config.command} run --rm --init ${userFlag} --name ${SANDBOX_PROXY_NAME} --network ${SANDBOX_PROXY_NAME} -p 8877:8877 -v ${process.cwd()}:${workdir} --workdir ${workdir} ${image} ${proxyCommand}`;
525
- proxyProcess = spawn(proxyContainerCommand, {
526
- stdio: ['ignore', 'pipe', 'pipe'],
527
- shell: true,
528
- detached: true,
529
- });
530
- // install handlers to stop proxy on exit/signal
531
- const stopProxy = () => {
532
- console.log('stopping proxy container ...');
533
- execSync(`${config.command} rm -f ${SANDBOX_PROXY_NAME}`);
534
- };
535
- process.on('exit', stopProxy);
536
- process.on('SIGINT', stopProxy);
537
- process.on('SIGTERM', stopProxy);
538
- // commented out as it disrupts ink rendering
539
- // proxyProcess.stdout?.on('data', (data) => {
540
- // console.info(data.toString());
541
- // });
542
- proxyProcess.stderr?.on('data', (data) => {
543
- console.error(data.toString().trim());
609
+ sandboxProcess.on('error', (err) => {
610
+ console.error('Sandbox process error:', err);
544
611
  });
545
- proxyProcess.on('close', (code, signal) => {
546
- console.error(`ERROR: proxy container command '${proxyContainerCommand}' exited with code ${code}, signal ${signal}`);
547
- if (sandboxProcess?.pid) {
548
- process.kill(-sandboxProcess.pid, 'SIGTERM');
549
- }
550
- process.exit(1);
612
+ await new Promise((resolve) => {
613
+ sandboxProcess?.on('close', (code, signal) => {
614
+ if (code !== 0) {
615
+ console.log(`Sandbox process exited with code: ${code}, signal: ${signal}`);
616
+ }
617
+ resolve();
618
+ });
551
619
  });
552
- console.log('waiting for proxy to start ...');
553
- await execAsync(`until timeout 0.25 curl -s http://localhost:8877; do sleep 0.25; done`);
554
- // connect proxy container to sandbox network
555
- // (workaround for older versions of docker that don't support multiple --network args)
556
- await execAsync(`${config.command} network connect ${SANDBOX_NETWORK_NAME} ${SANDBOX_PROXY_NAME}`);
557
620
  }
558
- // spawn child and let it inherit stdio
559
- sandboxProcess = spawn(config.command, args, {
560
- stdio: 'inherit',
561
- });
562
- sandboxProcess.on('error', (err) => {
563
- console.error('Sandbox process error:', err);
564
- });
565
- await new Promise((resolve) => {
566
- sandboxProcess?.on('close', (code, signal) => {
567
- if (code !== 0) {
568
- console.log(`Sandbox process exited with code: ${code}, signal: ${signal}`);
569
- }
570
- resolve();
571
- });
572
- });
621
+ finally {
622
+ patcher.cleanup();
623
+ }
573
624
  }
574
625
  // Helper functions to ensure sandbox image is present
575
626
  async function imageExists(sandbox, image) {