Files
automaker/libs/platform/src/index.ts
Stefan de Vogelaere a52c0461e5 feat: add external terminal support with cross-platform detection (#565)
* feat(platform): add cross-platform openInTerminal utility

Add utility function to open a terminal in a specified directory:
- macOS: Uses Terminal.app via AppleScript
- Windows: Tries Windows Terminal, falls back to cmd
- Linux: Tries common terminal emulators (gnome-terminal,
  konsole, xfce4-terminal, xterm, x-terminal-emulator)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(server): add open-in-terminal endpoint

Add POST /open-in-terminal endpoint to open a system terminal in the
worktree directory using the cross-platform openInTerminal utility.

The endpoint validates that worktreePath is provided and is an
absolute path for security.

Extracted from PR #558.

* feat(ui): add Open in Terminal action to worktree dropdown

Add "Open in Terminal" option to the worktree actions dropdown menu.
This opens the system terminal in the worktree directory.

Changes:
- Add openInTerminal method to http-api-client
- Add Terminal icon and menu item to worktree-actions-dropdown
- Add onOpenInTerminal prop to WorktreeTab component
- Add handleOpenInTerminal handler to use-worktree-actions hook
- Wire up handler in worktree-panel for both mobile and desktop views

Extracted from PR #558.

* fix(ui): open in terminal navigates to Automaker terminal view

Instead of opening the system terminal, the "Open in Terminal" action
now opens Automaker's built-in terminal with the worktree directory:

- Add pendingTerminalCwd state to app store
- Update use-worktree-actions to set pending cwd and navigate to /terminal
- Add effect in terminal-view to create session with pending cwd

This matches the original PR #558 behavior.

* feat(ui): add terminal open mode setting (new tab vs split)

Add a setting to choose how "Open in Terminal" behaves:
- New Tab: Creates a new tab named after the branch (default)
- Split: Adds to current tab as a split view

Changes:
- Add openTerminalMode setting to terminal state ('newTab' | 'split')
- Update terminal-view to respect the setting
- Add UI in Terminal Settings to toggle the behavior
- Rename pendingTerminalCwd to pendingTerminal with branch name

The new tab mode names tabs after the branch for easy identification.
The split mode is useful for comparing terminals side by side.

* feat(ui): display branch name in terminal header with git icon

- Move branch name display from tab name to terminal header
- Show full branch name (no truncation) with GitBranch icon
- Display branch name for both 'new tab' and 'split' modes
- Persist openTerminalMode setting to server and include in import/export
- Update settings dropdown to simplified "New Tab" label

* feat: add external terminal support with cross-platform detection

Add support for opening worktree directories in external terminals
(iTerm2, Warp, Ghostty, System Terminal, etc.) while retaining the
integrated terminal as the default option.

Changes:
- Add terminal detection for macOS, Windows, and Linux
- Add "Open in Terminal" split-button in worktree dropdown
- Add external terminal selection in Settings > Terminal
- Add default open mode setting (new tab vs split)
- Display branch name in terminal panel header
- Support 20+ terminals across platforms

Part of #558, Closes #550

* fix: address PR review comments

- Add nonce parameter to terminal navigation to allow reopening same
  worktree multiple times
- Fix shell path escaping in editor.ts using single-quote wrapper
- Add validatePathParams middleware to open-in-external-terminal route
- Remove redundant validation block from createOpenInExternalTerminalHandler
- Remove unused pendingTerminal state and setPendingTerminal action
- Remove unused getTerminalInfo function from editor.ts

* fix: address PR review security and validation issues

- Add runtime type check for worktreePath in open-in-terminal handler
- Fix Windows Terminal detection using commandExists before spawn
- Fix xterm shell injection by using sh -c with escapeShellArg
- Use loose equality for null/undefined in useEffectiveDefaultTerminal
- Consolidate duplicate imports from open-in-terminal.js

* chore: update package-lock.json

* fix: use response.json() to prevent disposal race condition in E2E test

Replace response.body() with response.json() in open-existing-project.spec.ts
to fix the "Response has been disposed" error. This matches the pattern used
in other test files.

* Revert "fix: use response.json() to prevent disposal race condition in E2E test"

This reverts commit 36bdf8c24a.

* fix: address PR review feedback for terminal feature

- Add explicit validation for worktreePath in createOpenInExternalTerminalHandler
- Add aria-label to refresh button in terminal settings for accessibility
- Only show "no terminals" message when not refreshing
- Reset initialCwdHandledRef on failure to allow retries
- Use z.coerce.number() for nonce URL param to handle string coercion
- Preserve branchName when creating layout for empty tab
- Update getDefaultTerminal return type to allow null result

---------

Co-authored-by: Kacper <kacperlachowiczwp.pl@wp.pl>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:22:26 +01:00

189 lines
4.1 KiB
TypeScript

/**
* @automaker/platform
* Platform-specific utilities for AutoMaker
*/
// Path utilities
export {
getAutomakerDir,
getFeaturesDir,
getFeatureDir,
getFeatureImagesDir,
getBoardDir,
getImagesDir,
getContextDir,
getWorktreesDir,
getValidationsDir,
getValidationDir,
getValidationPath,
getAppSpecPath,
getBranchTrackingPath,
getExecutionStatePath,
getNotificationsPath,
// Event history paths
getEventHistoryDir,
getEventHistoryIndexPath,
getEventPath,
ensureEventHistoryDir,
ensureAutomakerDir,
getGlobalSettingsPath,
getCredentialsPath,
getProjectSettingsPath,
ensureDataDir,
// Ideation paths
getIdeationDir,
getIdeasDir,
getIdeaDir,
getIdeaPath,
getIdeaAttachmentsDir,
getIdeationSessionsDir,
getIdeationSessionPath,
getIdeationDraftsDir,
getIdeationAnalysisPath,
ensureIdeationDir,
} from './paths.js';
// Subprocess management
export {
spawnJSONLProcess,
spawnProcess,
type SubprocessOptions,
type SubprocessResult,
} from './subprocess.js';
// Security
export {
PathNotAllowedError,
initAllowedPaths,
isPathAllowed,
validatePath,
isPathWithinDirectory,
getAllowedRootDirectory,
getDataDirectory,
getAllowedPaths,
} from './security.js';
// Secure file system (validates paths before I/O operations)
export * as secureFs from './secure-fs.js';
// Node.js executable finder (cross-platform)
export {
findNodeExecutable,
buildEnhancedPath,
type NodeFinderResult,
type NodeFinderOptions,
} from './node-finder.js';
// WSL (Windows Subsystem for Linux) utilities
export {
isWslAvailable,
clearWslCache,
getDefaultWslDistribution,
getWslDistributions,
findCliInWsl,
execInWsl,
createWslCommand,
windowsToWslPath,
wslToWindowsPath,
type WslCliResult,
type WslOptions,
} from './wsl.js';
// System paths for tool detection (GitHub CLI, Claude CLI, Node.js, etc.)
export * as systemPaths from './system-paths.js';
export {
// CLI tool paths
getGitHubCliPaths,
getClaudeCliPaths,
getClaudeConfigDir,
getClaudeCredentialPaths,
getClaudeSettingsPath,
getClaudeStatsCachePath,
getClaudeProjectsDir,
getCodexCliPaths,
getCodexConfigDir,
getCodexAuthPath,
getGitBashPaths,
getOpenCodeCliPaths,
getOpenCodeConfigDir,
getOpenCodeAuthPath,
getShellPaths,
getExtendedPath,
// Node.js paths
getNvmPaths,
getFnmPaths,
getNodeSystemPaths,
getScoopNodePath,
getChocolateyNodePath,
getWslVersionPath,
// System path operations
systemPathExists,
systemPathAccess,
systemPathIsExecutable,
systemPathReadFile,
systemPathReadFileSync,
systemPathWriteFileSync,
systemPathReaddir,
systemPathReaddirSync,
systemPathStatSync,
systemPathStat,
isAllowedSystemPath,
// High-level methods
findFirstExistingPath,
findGitHubCliPath,
findClaudeCliPath,
getClaudeAuthIndicators,
type ClaudeAuthIndicators,
findCodexCliPath,
getCodexAuthIndicators,
type CodexAuthIndicators,
findGitBashPath,
findOpenCodeCliPath,
getOpenCodeAuthIndicators,
type OpenCodeAuthIndicators,
// Electron userData operations
setElectronUserDataPath,
getElectronUserDataPath,
isElectronUserDataPath,
electronUserDataReadFileSync,
electronUserDataWriteFileSync,
electronUserDataExists,
// Script directory operations
setScriptBaseDir,
getScriptBaseDir,
scriptDirExists,
scriptDirMkdirSync,
scriptDirCreateWriteStream,
// Electron app bundle operations
setElectronAppPaths,
electronAppExists,
electronAppReadFileSync,
electronAppStatSync,
electronAppStat,
electronAppReadFile,
} from './system-paths.js';
// Port configuration
export { STATIC_PORT, SERVER_PORT, RESERVED_PORTS } from './config/ports.js';
// Editor detection and launching (cross-platform)
export {
commandExists,
clearEditorCache,
detectAllEditors,
detectDefaultEditor,
findEditorByCommand,
openInEditor,
openInFileManager,
openInTerminal,
} from './editor.js';
// External terminal detection and launching
export {
clearTerminalCache,
detectAllTerminals,
detectDefaultTerminal,
findTerminalById,
openInExternalTerminal,
} from './terminal.js';