Skip to content

feat(web): open a project's terminal from the chat header - #5339

Open
Brechard wants to merge 3 commits into
pingdotgg:mainfrom
Brechard:terminal-open-action
Open

feat(web): open a project's terminal from the chat header#5339
Brechard wants to merge 3 commits into
pingdotgg:mainfrom
Brechard:terminal-open-action

Conversation

@Brechard

@Brechard Brechard commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What Changed

Adds a Terminal control to the chat header, next to Open-in, that launches the project directory in an installed terminal.

  • Supported: Terminal, iTerm, Warp and Ghostty on macOS via their app bundles, plus Ghostty, WezTerm, kitty, Alacritty and Windows Terminal via their CLIs. Only installed ones appear.
  • The preferred terminal is remembered exactly like the preferred editor (t3code:last-terminal in localStorage, mirroring usePreferredEditor). The chevron menu only renders when more than one terminal is installed, so the common case is a single button.
  • EDITORS gains a working-directory launch style. Its target is resolved to a directory first: a :line:column suffix is stripped, and a file steps up to its containing folder, so the existing "open this file" call sites work unchanged.
  • The commands === null file-manager special case becomes a kind discriminator (file-manager | terminal), so terminals reuse the same detection and launch path instead of adding a parallel one.
  • Terminals are excluded from the Open-in picker's fallback, so a terminal can never become the editor the Open button uses.

macAppName is set only for bundles that actually open a folder argument in that directory. The rest would silently start in the user's home, so they appear only when their CLI is on PATH.

Why

Opening a project in an editor is one click, but opening a shell in that same directory means leaving the app and cd-ing by hand.

It is a separate control rather than a row in the Open-in picker because a terminal opens a shell in the project rather than a file in an app, and choosing one should not move the editor the Open button uses.

UI Changes

Before — header with no terminal affordance:

before

After — Terminal control between Add action and Open:

after

The screenshots are from a machine with only Terminal.app installed, which is why no chevron is shown; with several terminals installed the control gains a chevron menu listing them.

Verification

  • vp run --filter @t3tools/contracts --filter t3 --filter @t3tools/web typecheck clean.
  • New tests in externalLauncher.test.ts (working-directory launch with :line:column stripping, and the CLI-less macOS app-bundle path) and packages/contracts/src/editor.test.ts (terminal classification, launch style, ordering after editors).
  • Checked end to end in a dev build: clicking Terminal spawned a shell whose cwd was the project directory, confirmed with lsof.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes — n/a, no motion

Generated with Claude Sonnet 5 in Claude Code.


Note

Medium Risk
Touches process spawning and path resolution on the server; mistakes could open the wrong directory or mis-detect macOS apps, but scope is limited to external launch helpers with new tests.

Overview
Adds a Terminal control in the chat header (alongside Open-in) that opens the project directory in an installed terminal, with a separate preferred-terminal preference (t3code:last-terminal) so it does not change the Open-in editor default.

Contracts & launcher: EDITORS gains terminal entries and a working-directory launch style with optional cwdArgs / macAppName. ExternalLauncher resolves the cwd from file paths (strip :line:column, use parent dir), detects macOS .app bundles only on darwin, and launches GUI-only terminals via open -a. File manager is distinguished with kind: "file-manager" instead of commands === null alone.

Web: OpenTerminalPicker filters installed terminals from availableEditors; usePreferredEditor / resolveAndPersistPreferredEditor skip terminal IDs so Open-in never defaults to a terminal.

Reviewed by Cursor Bugbot for commit cf08262. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add terminal launcher to the chat header for opening a project directory

  • Adds an OpenTerminalPicker component to ChatHeader.tsx that lets users open a terminal in the current project's working directory, with preference persistence and multi-terminal selection.
  • Defines terminal editor entries (Apple Terminal, iTerm, Ghostty, Warp, WezTerm, kitty, Alacritty, Windows Terminal) in editor.ts using a new working-directory launch style and optional cwdArgs templates.
  • Extends externalLauncher.ts to resolve the working directory from a file path (stripping :line:col suffixes), support GUI-only macOS app bundles via open -a, and skip macOS bundle fallback on non-darwin platforms.
  • Separates terminal preference storage from editor preference in editorPreferences.ts using a new usePreferredTerminal hook so terminals are never auto-selected as the default editor.

Macroscope summarized cf08262.

Opening a project in an editor is one click, but opening a shell in it
means leaving the app and cd-ing by hand. This adds a Terminal control
next to Open-in that launches the project directory in an installed
terminal.

It is a separate control rather than a row in the Open-in picker: a
terminal opens a shell in the project rather than a file in an app, and
choosing one should not move the editor the Open button uses. The
preferred terminal is remembered the same way the preferred editor is,
and the chevron menu only appears when more than one is installed.

Supported: Terminal, iTerm, Warp and Ghostty on macOS through their app
bundles, plus Ghostty, WezTerm, kitty, Alacritty and Windows Terminal
through their CLIs. macAppName is set only for bundles that open a
folder argument in that directory; the rest would silently start in the
user's home, so they appear only when their CLI is on PATH.

Server-side, EDITORS gains a "working-directory" launch style whose
target is resolved to a directory (a `:line:column` suffix is stripped
and a file steps up to its containing folder), and the file-manager
special case becomes a `kind` discriminator so terminals reuse the same
detection and launch path.

Generated with Claude Sonnet 5 in Claude Code.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f5833e61-42d2-4c79-b229-19a2dc9d509e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 4, 2026
Comment thread apps/server/src/process/externalLauncher.ts Outdated
Comment thread apps/server/src/process/externalLauncher.ts
Comment thread apps/server/src/process/externalLauncher.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR adds a new feature: terminal launching from the chat header. It introduces a new UI component, 8 terminal definitions, a new launch style, and cross-cutting server logic — new user-facing capability that warrants human review.

You can customize Macroscope's approvability policy. Learn more.

Addresses two review findings:

- The macOS app-bundle fallback in the CLI branch was reachable on Linux
  and Windows, where `open` does not exist. A stray `/Applications/<name>.app`
  would turn a clean command-not-found into a failed spawn. Guarded with
  `platform === "darwin"`, matching the no-CLI branch.
- `resolveWorkingDirectory` stripped a `:line:column` suffix before testing
  the target, so a real POSIX directory named `project:12` resolved to its
  parent. The target is now tested as-is first.

Generated with Claude Sonnet 5 in Claude Code.
@Brechard

Brechard commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — both findings were real, fixed in e44db99.

  • externalLauncher.ts:405 — guarded the CLI branch's app-bundle fallback with platform === "darwin", matching the no-CLI branch below it. Off darwin this now falls through to the plain command-not-found error instead of an open -a … launch that cannot work there.
  • resolveWorkingDirectory — the target is now stat'd as-is before any position parsing, so a real directory whose name ends in :<number> is left alone. The suffix is only stripped when the target itself is not a directory.

Added a regression test for each.

`open` detaches with stdio ignored, so launching a CLI-less editor whose
bundle is gone (a stale `apple-terminal` preference, say) reported success
while nothing opened. Check the bundle first and fail with
ExternalLauncherUnsupportedEditorError instead.

The existing app-bundle test forced platform "darwin" against the real
filesystem, so gating on availability would have tied it to the host having
Terminal.app. It now stages the bundle under a temp HOME.

Generated with Claude Sonnet 5 in Claude Code.
@Brechard

Brechard commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Good catch, fixed in cf08262. The CLI-less branch now calls isMacAppAvailable first and fails with ExternalLauncherUnsupportedEditorError when the bundle is gone, rather than detaching open and reporting success.

Worth noting it also caught a latent test problem: the existing app-bundle test forced platform: "darwin" against the real filesystem, so gating on availability would have tied it to the host actually having Terminal.app — green on macOS, red on a Linux runner. It now stages the bundle under a temp HOME, so it is host-independent.

I did not add a negative test for the missing-bundle path: isMacAppAvailable also probes the real /Applications, so any such test would pass or fail depending on what the runner happens to have installed. Happy to add one if you'd prefer the probe roots injected instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants