Skip to content

✨ feat: 🖥️ open-as iterm2 — show a page in an iTerm2 browser tab beside the session - #88

Merged
hadees merged 3 commits into
mainfrom
feat/open-as-iterm2
Sep 25, 2026
Merged

hadees merged 3 commits into
mainfrom
feat/open-as-iterm2

Conversation

@hadees

@hadees hadees commented Sep 25, 2026

Copy link
Copy Markdown
Owner

What

open-as iterm2 <url> opens a URL in an iTerm2 3.7 web-browser tab in the calling session's window, reusing that window's tab on later calls. It's for localhost dashboards, status pages and reports that a Claude session wants to show beside the terminal.

  • How it works. It drives iTerm2's Python API. AppleScript has no URL verb, and handing the URL to iTerm2 through Finicky lands in an untested not-default-browser branch that gives no control over window or reuse.
  • Logins persist. Tabs come from a dynamic profile open-as with a fixed UUID Guid. WebKit keys the cookie jar by the profile's GUID, so you log in once per site.
  • It falls open. When something is missing (not in iTerm2, Python API off, no runtime, script error) it prints one stderr line saying which, then takes the normal path, tagged with browser.tag.iterm2 if an overlay pins one.
  • claude() refuses iterm2 as a profile's browser pin. BROWSER takes the OAuth login too, and iTerm2's browser has no passkeys. claude-doctor reports it as REFUSED.
  • Sessions are told to use it. The shared memory tells them to use open-as iterm2 for viewing. Claude in Chrome stays the automation path.

Decided against

  • Copying Chrome cookies. It would decrypt live session credentials out of Chrome's Keychain-protected store into a second store. Logging in once per site is the better trade.
  • The 1Password extension. It can't run in WKWebView. iTerm2's own Password Manager, with the op backend, fills on request instead.
  • Refreshing the iTerm2 skill for 3.7.3. That's a separate PR. verify.sh is 47/48 on 3.7.3 because the "AI key is stored in the Keychain" sentence is gone from the binary, and that sentence is the safety claim behind tracking the prefs plist.

Verified

  • bats tests: 560 pass. The new tests/open-as.bats drives the real script against a fake iterm2 module.
  • API calls checked against the published iterm2 2.24 package source.
  • Public-clone review: no identity. The GUID is newly minted and the fixtures are invented.
  • /code-review: the first-run profile-load race is fixed with a retry instead of sleep 1.
  • Live run. It needs the iTerm2 Python runtime (Scripts > Manage > Install Python Runtime), which isn't installed yet. Expect a one-time approval prompt per domain. Still to confirm:
    • that the tab lands in the caller's window, i.e. that ITERM_SESSION_ID's UUID is the API's session id
    • that file:// renders
    • that logins survive closing and reopening the tab

🤖 Generated with Claude Code

hadees and others added 3 commits September 25, 2026 13:49
…de the session

A session that wants to show a localhost dashboard or a report next to the
terminal had no way to put it there: iTerm2 3.7's browser tabs cannot be
reached from AppleScript (the dictionary has no URL verb), and handing the
URL to iTerm2 through Finicky lands in an untested not-default-browser
branch with no say over window or reuse. So the reserved alias `iterm2`
drives the Python API directly: find the caller's window from
ITERM_SESSION_ID, reuse that window's open-as tab or create one, load the
URL.

Tabs come from a dynamic profile with a fixed UUID Guid because WebKit keys
the cookie jar by the profile's GUID, which is what makes "log in once per
site" hold. The iterm2 library fetches its own cookie and key; handing it
the raw `request cookie` reply as ITERM2_COOKIE (the first plan) would have
stuffed both halves into one variable. The runtime is checked before
anything talks to iTerm2, which otherwise offers to install it in a dialog
nobody is watching. Every missing piece falls open to the old path with one
stderr line naming it.

Not yet run against a live iTerm2: the Python runtime is not installed on
the development machine. Tests drive the real script against a fake
`iterm2` module.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ake the login

A profile's browser pin becomes BROWSER for the whole session, so it takes
every link Claude Code opens, the OAuth login included. The iTerm2 browser
has no passkey entitlement (WKWebView restriction), so pinning it would send
the login somewhere it may not complete. claude() now refuses the pin out
loud and launches untagged; claude-doctor says REFUSED rather than calling
it an invalid alias, which it is not.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A feature no session knows about never gets used; the shared memory is
what every profile reads, so the instruction lives there, next to the
Claude in Chrome guidance it must not be confused with (viewing, not
automation).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@hadees hadees self-assigned this Sep 25, 2026
@hadees
hadees merged commit 825ac9f into main Sep 25, 2026
5 checks passed
@hadees
hadees deleted the feat/open-as-iterm2 branch September 25, 2026 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant