Skip to content

Add shim command for transparent claude→proxy routing#10

Open
teocns wants to merge 1 commit into
KarpelesLab:masterfrom
teocns:feat/shim
Open

Add shim command for transparent claude→proxy routing#10
teocns wants to merge 1 commit into
KarpelesLab:masterfrom
teocns:feat/shim

Conversation

@teocns
Copy link
Copy Markdown

@teocns teocns commented May 7, 2026

Summary

Adds a teamclaude shim subcommand that drops a tiny bash wrapper at $XDG_DATA_HOME/teamclaude-shim/claude and wires it onto every shell's PATH via the rustup-style env-file pattern. Plain claude invocations then probe the proxy port locally — applying teamclaude env and exec'ing the real claude if the proxy is up, or exec'ing the real claude directly if it isn't.

The shim lives in its own directory, separate from where Claude Code's auto-updater rewrites its binary, so it survives claude self-updates indefinitely (same trick rbenv, asdf, and mise use to survive language-version updates).

Why

teamclaude run covers ad-hoc invocations, but anyone who types claude directly — from a shell, an editor, a script, a hotkey — bypasses the proxy. A naive shadow-symlink in the same dir as claude works for a day, then gets clobbered the next time Claude Code self-updates. The PATH-prepended shim dir avoids that collision class entirely.

Surface

teamclaude shim install     [--no-rc] [--shim-dir PATH]
teamclaude shim uninstall
teamclaude shim status      # also: bare `teamclaude shim`

Shell coverage

Modeled on ~/.cargo/env (the rustup install pattern). Three files written under the shim dir:

  • claude — the wrapper (executable)
  • env — POSIX sh loader, idempotent at source-time via case ":${PATH}:" in *:"...":*) ;;
  • env.fish — fish loader using fish_add_path-style logic

Then a single . "<shim-dir>/env" line is appended to each rc:

Shell rc file(s) wired
bash ~/.bashrc AND ~/.bash_profile (covers macOS Terminal's login-shell precedence)
zsh ~/.zshrc
POSIX sh ~/.profile
fish ~/.config/fish/conf.d/teamclaude-shim.fish (auto-loaded; no rc edit)

The loaders are idempotent at source time (PATH dedup via case statement / not contains) so re-sourcing is safe across shell reloads. Uninstall surgically strips the comment + source line from each rc and removes all written files.

Implementation notes

  • Zero new dependencies. New module src/shim.js uses only node:fs, node:path, node:os. The bash wrapper and shell loaders are embedded as template literals and written at install time — no shell scripts in the repo, consistent with the existing pure-Node layout.
  • Dispatch. Adds one case 'shim' to the switch in src/index.js. New shimCommand() parses install / uninstall / status and forwards flags. Help text and README updated to match existing CLI vocabulary.
  • Smart rc selection. Existing rc files are always wired; non-existent ones are only created if they match the user's current $SHELL (avoids creating .bashrc on a zsh-only machine).
  • $HOME portability. When the shim dir lives under $HOME, env files reference it as $HOME/... (rustup convention) for portability across machines on shared/network homes.
  • Real-binary resolution. Wrapper walks PATH, skipping any entry whose realpath matches itself — works regardless of where Claude Code installs (npm, homebrew, native installer).

Tested

  • node --check on both modified files; existing eslint clean (no new warnings).
  • End-to-end install → idempotent re-install → uninstall against a sandboxed $HOME populated with pre-existing rc content — original content preserved verbatim, no duplicate source lines on re-install, full cleanup on uninstall.
  • Functional shell sourcing verified: sourcing env from bash exports PATH correctly; sourcing it twice does not duplicate the entry (case-statement idempotence works).
  • Live execution against a running proxy: shim probes :3456, eval's teamclaude env, exec's real claude --version successfully through the proxy.
  • Live execution with proxy down: shim skips env injection, exec's real claude --version directly with a clean env.

Drops a tiny bash wrapper at $XDG_DATA_HOME/teamclaude-shim/claude and
prepends that directory to the user's shell PATH. Plain `claude`
invocations then probe the proxy port; if it's up, the shim applies
`teamclaude env` and execs the real claude, otherwise it execs the
real claude directly.

The shim lives in its own directory, separate from where Claude Code's
auto-updater rewrites its binary, so it survives self-updates
indefinitely (same trick rbenv/asdf/mise use).

Surface:
  teamclaude shim install [--no-rc] [--shim-dir PATH]
  teamclaude shim uninstall
  teamclaude shim status

Zero new dependencies.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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