diff --git a/packages/shared/config/constant.ts b/packages/shared/config/constant.ts index 9ce35e3a5d..3ffe88f08a 100644 --- a/packages/shared/config/constant.ts +++ b/packages/shared/config/constant.ts @@ -368,16 +368,27 @@ export const WINDOWS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [ } ] +// Helper function to escape strings for AppleScript +const escapeForAppleScript = (str: string): string => { + // In AppleScript strings, backslashes and double quotes need to be escaped + // When passed through osascript -e with single quotes, we need: + // 1. Backslash: \ -> \\ + // 2. Double quote: " -> \" + return str + .replace(/\\/g, '\\\\') // Escape backslashes first + .replace(/"/g, '\\"') // Then escape double quotes +} + export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [ { id: terminalApps.systemDefault, name: 'Terminal', bundleId: 'com.apple.Terminal', - command: (directory: string, fullCommand: string) => ({ + command: (_directory: string, fullCommand: string) => ({ command: 'sh', args: [ '-c', - `open -na Terminal && sleep 0.5 && osascript -e 'tell application "Terminal" to activate' -e 'tell application "Terminal" to do script "cd '${directory.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}' && clear && ${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}" in front window'` + `open -na Terminal && sleep 0.5 && osascript -e 'tell application "Terminal" to activate' -e 'tell application "Terminal" to do script "${escapeForAppleScript(fullCommand)}" in front window'` ] }) }, @@ -385,11 +396,11 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [ id: terminalApps.iterm2, name: 'iTerm2', bundleId: 'com.googlecode.iterm2', - command: (directory: string, fullCommand: string) => ({ + command: (_directory: string, fullCommand: string) => ({ command: 'sh', args: [ '-c', - `open -na iTerm && sleep 0.8 && osascript -e 'on waitUntilRunning()\n repeat 50 times\n tell application "System Events"\n if (exists process "iTerm2") then exit repeat\n end tell\n delay 0.1\n end repeat\nend waitUntilRunning\n\nwaitUntilRunning()\n\ntell application "iTerm2"\n if (count of windows) = 0 then\n create window with default profile\n delay 0.3\n else\n tell current window\n create tab with default profile\n end tell\n delay 0.3\n end if\n tell current session of current window to write text "cd '${directory.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}' && clear && ${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"\n activate\nend tell'` + `open -na iTerm && sleep 0.8 && osascript -e 'on waitUntilRunning()\n repeat 50 times\n tell application "System Events"\n if (exists process "iTerm2") then exit repeat\n end tell\n delay 0.1\n end repeat\nend waitUntilRunning\n\nwaitUntilRunning()\n\ntell application "iTerm2"\n if (count of windows) = 0 then\n create window with default profile\n delay 0.3\n else\n tell current window\n create tab with default profile\n end tell\n delay 0.3\n end if\n tell current session of current window to write text "${escapeForAppleScript(fullCommand)}"\n activate\nend tell'` ] }) }, @@ -397,11 +408,11 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [ id: terminalApps.kitty, name: 'kitty', bundleId: 'net.kovidgoyal.kitty', - command: (directory: string, fullCommand: string) => ({ + command: (_directory: string, fullCommand: string) => ({ command: 'sh', args: [ '-c', - `cd "${directory}" && open -na kitty --args --directory="${directory}" sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "kitty" to activate'` + `cd "${_directory}" && open -na kitty --args --directory="${_directory}" sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "kitty" to activate'` ] }) }, @@ -409,11 +420,11 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [ id: terminalApps.alacritty, name: 'Alacritty', bundleId: 'org.alacritty', - command: (directory: string, fullCommand: string) => ({ + command: (_directory: string, fullCommand: string) => ({ command: 'sh', args: [ '-c', - `open -na Alacritty --args --working-directory "${directory}" -e sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "Alacritty" to activate'` + `open -na Alacritty --args --working-directory "${_directory}" -e sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "Alacritty" to activate'` ] }) }, @@ -421,11 +432,11 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [ id: terminalApps.wezterm, name: 'WezTerm', bundleId: 'com.github.wez.wezterm', - command: (directory: string, fullCommand: string) => ({ + command: (_directory: string, fullCommand: string) => ({ command: 'sh', args: [ '-c', - `open -na WezTerm --args start --new-tab --cwd "${directory}" -- sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "WezTerm" to activate'` + `open -na WezTerm --args start --new-tab --cwd "${_directory}" -- sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "WezTerm" to activate'` ] }) }, @@ -433,11 +444,11 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [ id: terminalApps.ghostty, name: 'Ghostty', bundleId: 'com.mitchellh.ghostty', - command: (directory: string, fullCommand: string) => ({ + command: (_directory: string, fullCommand: string) => ({ command: 'sh', args: [ '-c', - `cd "${directory}" && open -na Ghostty --args --working-directory="${directory}" -e sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "Ghostty" to activate'` + `cd "${_directory}" && open -na Ghostty --args --working-directory="${_directory}" -e sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "Ghostty" to activate'` ] }) }, @@ -445,7 +456,7 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [ id: terminalApps.tabby, name: 'Tabby', bundleId: 'org.tabby', - command: (directory: string, fullCommand: string) => ({ + command: (_directory: string, fullCommand: string) => ({ command: 'sh', args: [ '-c', @@ -453,7 +464,7 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [ open -na Tabby --args open && sleep 0.3 else open -na Tabby --args open && sleep 2 - fi && osascript -e 'tell application "Tabby" to activate' -e 'set the clipboard to "cd \\"${directory.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}\\" && clear && ${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"' -e 'tell application "System Events" to tell process "Tabby" to keystroke "v" using {command down}' -e 'tell application "System Events" to key code 36'` + fi && osascript -e 'tell application "Tabby" to activate' -e 'set the clipboard to "${escapeForAppleScript(fullCommand)}"' -e 'tell application "System Events" to tell process "Tabby" to keystroke "v" using {command down}' -e 'tell application "System Events" to key code 36'` ] }) } diff --git a/src/main/services/CodeToolsService.ts b/src/main/services/CodeToolsService.ts index 74fca367fc..486e58c212 100644 --- a/src/main/services/CodeToolsService.ts +++ b/src/main/services/CodeToolsService.ts @@ -666,7 +666,7 @@ class CodeToolsService { const command = envPrefix ? `${envPrefix} && ${baseCommand}` : baseCommand // Combine directory change with the main command to ensure they execute in the same shell session - const fullCommand = `cd '${directory.replace(/'/g, "\\'")}' && clear && ${command}` + const fullCommand = `cd "${directory.replace(/"/g, '\\"')}" && clear && ${command}` const terminalConfig = await this.getTerminalConfig(options.terminal) logger.info(`Using terminal: ${terminalConfig.name} (${terminalConfig.id})`)