Route local AI tools (Claude Code, Codex, anything that talks to the Anthropic or OpenAI API) through your Teleport cluster's LLM gateways, powered by Teleport Beams.
(The name? A prism redirects beams.)
prism claude # Claude Code, routed via Teleport
prism codex # Codex CLI, routed via Teleport
prism exec <cmd> # any other tool, with prism env vars set
No API keys to manage, no shared secrets, no .env files. Auth is
your Teleport identity.
- A Teleport cluster with Beams enabled. Beams provisions
cluster-wide apps (
anthropicandopenai) that proxy traffic to the upstream LLM APIs. Prism tunnels to these apps — it doesn't talk to Anthropic or OpenAI directly. tshon PATH and a validtsh loginto the Beams cluster. Prism usestsh proxy app(ortbotfor unattended use) to establish authenticated tunnels to the Beams apps.- Go ≥ 1.24 (if building from source).
brew install webvictim/tap/prismgit clone https://github.com/webvictim/prism.git
cd prism
make
sudo make install # or: make install PREFIX=$HOME/.localRequires Go ≥ 1.24.
If you already have an interactive tsh login to a Beams-enabled
cluster, you can be running in 30 seconds:
tsh login --proxy=<your-cluster>.beams.run:443
prism config set proxy <your-cluster>.beams.run:443
prism claudePrism logs into both cluster apps, starts a local daemon, and execs
claude with the right env vars. Your tsh session expires after
12-24h; when it does, run tsh login again and prism's daemon
auto-recovers.
For unattended use (overnight agents, long jobs, CI), use tbot instead — it never expires. See the next section.
tbot is Teleport's Machine ID daemon. Prism runs it as a sidecar so
your AI tools have a self-refreshing identity that doesn't expire.
This is the right setup if you ever:
- leave Claude Code or Codex running overnight,
- use prism from CI / scripts / cron,
- don't want to think about
tsh loginever again.
One-time setup (assumes you have tctl admin perms on the cluster):
# 1. Generate Machine ID resource templates and a join token.
prism tbot bootstrap
# 2. Log in with admin perms so tctl can apply them.
tsh login --proxy=<your-cluster>.beams.run:443
# 3. Apply the three generated YAMLs (paths printed by bootstrap).
tctl create -f ~/.config/prism/tbot/role.yaml
tctl create -f ~/.config/prism/tbot/bot.yaml
tctl create -f ~/.config/prism/tbot/token.yaml
# 4. Fetch the registration secret and persist tbot config.
prism tbot configure
# 5. Switch prism over to the tbot backend.
prism config set identity tbot
prism config set tbot.dir ~/.config/prism/tbot
prism up
# 6. (Linux) Optionally install as a systemd user service for persistence.
prism installprism tbot bootstrap prints this exact sequence with your hostname
filled in (resources are named prism-bot-<hostname> so multiple
machines can share a cluster).
On Linux, prism install sets up a systemd user service so the daemon
starts on boot and survives logout — ideal for unattended use. Requires
lingering
enabled for your user (sudo loginctl enable-linger $USER).
Verify it's working:
prism tbot status # validates tbot dir, prints proxy/role/bot/secret state
prism status # shows daemon liveness + listener ports
prism test # smoke-tests both anthropic and openai backendsAfter that, every invocation of prism claude / prism codex /
prism exec uses the tbot identity — no re-login, ever.
prism claude [args...] # run Claude Code through prism
prism codex [args...] # run Codex through prism
prism exec <cmd> [args...] # run any command with prism env vars setAll three auto-start the daemon if it isn't already running, then exec
into the tool with ANTHROPIC_BASE_URL and OPENAI_BASE_URL pointed
at the local router. Any flags pass through:
prism claude --print "what's 2+2?"
prism codex --model gpt-4o
prism exec python my_script.pyFor tools you launch outside prism (an IDE plugin, a shell script), export the env vars yourself:
prism up
eval "$(prism env)"| Command | What it does |
|---|---|
prism claude [args...] |
Ensures the daemon is up; execs claude with prism env. |
prism codex [args...] |
Same, for codex. |
prism exec <cmd> [args...] |
Same, for any command. |
prism up |
Starts the local daemon (tunnels + router). Idempotent. |
prism down |
Stops the daemon and logs out of the apps. |
prism status |
Port assignments, identity state, daemon liveness. |
prism env |
Prints export statements for your shell to eval. |
prism logs |
Tails the local daemon log (request-level logging). |
prism test [anthropic|openai] |
Smoke test against one or both backends. |
prism config [show|set|unset|clear] |
View/edit persistent config (proxy, identity, tbot.dir). |
prism tbot bootstrap |
Generate Machine ID resources for tbot identity. |
prism tbot configure |
Persist the bound-keypair registration secret. |
prism tbot status |
Validate the tbot working directory. |
prism install |
Install as a system service (Linux systemd / macOS LaunchAgent). |
prism uninstall |
Remove the system service. |
prism version |
Print build version. |
The daemon was killed. prism down && prism up.
If the daemon was SIGKILL'd on macOS/Linux, the tbot subprocess gets
reparented to PID 1 and keeps holding its port. pkill -x tbot and
retry. (Windows uses Job Objects, so this can't happen there.)
The bot's role doesn't grant access to the cluster's LLM apps. Re-run
prism tbot bootstrap and re-apply the role:
tctl create -f ~/.config/prism/tbot/role.yamlThe cluster's Anthropic gateway is Bedrock-backed and rejects some upstream fields. Prism strips the known ones; if a new one shows up, turn on debug logging and check the daemon log:
prism down
PRISM_DEBUG=1 prism up
prism logs # in another terminal, reproduce the errorThen add the offending field to stripFields in
internal/router/router.go.
Run tsh login — the daemon detects the refreshed identity and
restarts its subprocesses automatically. If you're tired of this,
switch to tbot (see above).
If prism is installed as a service and misbehaving, these commands help:
macOS (LaunchAgent):
# Check if the job is loaded (modern launchctl)
launchctl print gui/$(id -u)/com.prism.daemon
# Stop and restart
launchctl bootout gui/$(id -u)/com.prism.daemon
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.prism.daemon.plist
# View the plist
cat ~/Library/LaunchAgents/com.prism.daemon.plist
# Tail daemon logs
tail -f ~/.config/prism/daemon.logLinux (systemd):
# Service status + recent logs
systemctl --user status prism
# Full logs (follow mode)
journalctl --user -u prism -f
# Restart
systemctl --user restart prism
# Check if lingering is enabled (survives logout)
ls /var/lib/systemd/linger/$USER┌────────────────────────┐
│ Claude Code / Codex / │ ANTHROPIC_BASE_URL=http://127.0.0.1:7331
│ any AI tool │ OPENAI_BASE_URL=http://127.0.0.1:7331/v1
└──────────┬─────────────┘
│ plain HTTP
▼
┌────────────────────────┐
│ prism router (:7331) │ path-based dispatch + Bedrock scrubbing
│ /v1/messages → :7333 │
│ /v1/chat/* → :7334 │
└────┬──────────────┬────┘
│ │
▼ ▼
┌─────────┐ ┌─────────┐
│ tsh/tbot│ │ tsh/tbot│ tunnel subprocesses
│ proxy │ │ proxy │
│anthropic│ │ openai │
└────┬────┘ └────┬────┘
│ │
▼ ▼
Teleport Teleport Beams-managed LLM gateway apps
anthropic openai
app app
│ │
▼ ▼
Anthropic OpenAI
(Bedrock) API
The router lives in internal/router/router.go and does Bedrock-
compatibility scrubbing on /v1/messages (strips metadata,
context_management, thinking; caps max_tokens at 8192). The
tunnels are tsh proxy app subprocesses, or — in tbot mode — a single
tbot start with two application-tunnel services.
cmd/prism/ local CLI + daemon
internal/router/ HTTP router with path dispatch + scrubbing
internal/tunnel/ subprocess supervisor
internal/tbot/ tbot config rendering and bootstrap
internal/identity/ tsh session expiry watcher
internal/state/ runtime state persistence
internal/config/ persistent machine config
internal/tshwrap/ tsh CLI wrappers
Makefile build targets for all platforms
Apache 2.0 — see LICENSE.