Hermes Operations v1.0.0 MIT
Claude CLI Bridge Provider Setup
Use when wiring a Claude CLI bridge into Hermes. The bridge is wired in as a provider. Covers bridge install, custom provider registration, launchd keepalive, and OAuth re-login.
Features
- OpenAI-compatible `/v1/models` and `/v1/chat/completions` shim bound to `127.0.0.1` only.
- A Hermes `custom_providers` entry with a pinned model map that drives the picker.
- Two LaunchAgents: a KeepAlive bridge and a watchdog that heals health and flags auth failures.
- A PTY-driven re-login procedure for an expired OAuth session.
- A truth probe that tests real inference instead of login status text.
Benefits
- Claude models work from Hermes with no API key stored in Hermes config.
- The bridge survives crashes and reboots without a manual restart.
- Auth failures surface in one probe and in the watchdog flag, not in a failed user session.
- Loopback binding keeps the shim off the network.
Install
From the tap (or GitHub path)
hermes skills install shagghiesuperstar/hermes-skills/skills/claude-cli-bridge-provider-setup --category hermes-operations
Direct URL
hermes skills install https://raw.githubusercontent.com/shagghiesuperstar/hermes-skills/main/skills/claude-cli-bridge-provider-setup/SKILL.md --category hermes-operations
Get the skill
Related skills
- Agent OrchestrationClaude Code CLI Model SwitchingUse when choosing or switching the Claude Code CLI model. Covers pinning, effort level, fallback, and exposing extra models through a local bridge.
- Hermes OperationsHermes Model RoutingUse when wiring a model provider or fallback chain. It also applies to auxiliary lanes and to sweeping a retired model from every route. Covers slugs, lanes, pools and quota signals.
Related repos
The skill file
Claude CLI Bridge Provider Setup
Overview
Hermes can use a Claude subscription without storing an Anthropic API key by routing requests through a local OpenAI-compatible shim. The shim listens on loopback and runs claude --print as a subprocess, so inference uses the CLI's own keychain login. This skill covers building the shim, registering it as a Hermes custom provider so its models appear in the model picker, keeping it alive under launchd, and re-authenticating when the OAuth session expires.
When to Use
- A host needs Claude subscription models through the local Claude CLI and must not hold an API key in Hermes.
- The bridge provider is missing from the model picker, or its model list is empty.
- The bridge process is down, or chat fails while the health check still passes.
- Inference returns an OAuth "session expired" error.
Not for ordinary interactive Claude Code sessions, or for the model slug tables (see claude-cli-model-switching).
Features
- OpenAI-compatible
/v1/modelsand/v1/chat/completionsshim bound to127.0.0.1only. - A Hermes
custom_providersentry with a pinned model map that drives the picker. - Two LaunchAgents: a KeepAlive bridge and a watchdog that heals health and flags auth failures.
- A PTY-driven re-login procedure for an expired OAuth session.
- A truth probe that tests real inference instead of login status text.
Benefits
- Claude models work from Hermes with no API key stored in Hermes config.
- The bridge survives crashes and reboots without a manual restart.
- Auth failures surface in one probe and in the watchdog flag, not in a failed user session.
- Loopback binding keeps the shim off the network.
Procedure
Resolve the Claude binary and confirm the CLI login with a real inference call:
CLAUDE_BIN="$(command -v claude || true)" [[ -x "$HOME/.hermes/node/bin/claude" ]] && CLAUDE_BIN="$HOME/.hermes/node/bin/claude" [[ -x "$HOME/.local/bin/claude" && -z "$CLAUDE_BIN" ]] && CLAUDE_BIN="$HOME/.local/bin/claude" "$CLAUDE_BIN" --version "$CLAUDE_BIN" auth status --text "$CLAUDE_BIN" --print --model haiku 'reply with exactly: ok'Done when the last command prints
okand exits 0. If it fails, stop and run the re-login procedure below.Build the bridge tree at
~/tools/cli-bridge/unlessbridge.pyalready exists. The shim,bridge.py, comes from the upstream Daimon-Law/cli-bridge project: take its script from that project, then write the configuration below. If another host has a known-good tree, copy it instead of fetching the shim again:~/tools/cli-bridge/ bridge.py bridge.conf # KEY=VALUE; loaded without overriding existing environment requirements.txt # aiohttp>=3.9 run.sh .venv/ logs/Write
bridge.confwith the loopback host, a free loopback port (shown here as<port>) and the absolute Claude path:CLI_BRIDGE_HOST=127.0.0.1 CLI_BRIDGE_PORT=<port> CLI_BRIDGE_BIN=<absolute-path-to-claude> CLI_BRIDGE_DEFAULT_MODEL=<default-slug> CLI_BRIDGE_TIMEOUT=600 CLI_BRIDGE_MODELS=<comma-separated-slugs> # e.g. the opus, sonnet and haiku slugs your account can runrun.shshouldcdto its own directory andexec .venv/bin/python bridge.py.The shim must:
- answer
GET /healthwith an ok body; - list
CLI_BRIDGE_MODELSatGET /v1/modelsandGET /models; - handle
POST /v1/chat/completionsby runningclaude --print --output-format text --model <id> <prompt>and returning an OpenAI-shapedchoices[0].messageobject; - strip a trailing
-maxfrom the model id and pass--effort max(Opus models only); - refuse bind hosts
0.0.0.0and*, forcing127.0.0.1; - load the config file without overriding variables already set in the environment.
Then create the venv and install dependencies:
cd ~/tools/cli-bridge && python3 -m venv .venv && .venv/bin/pip install -r requirements.txt chmod +x run.sh bridge.py && mkdir -p logs- answer
Start the bridge. For debugging, run it in the foreground with
./run.sh. For persistence on macOS, run it as a LaunchAgent in the user's GUI session (gui/$UID), never a LaunchDaemon: the Claude login lives in the user's login keychain and a root daemon cannot read it. Write the plists withplistlib, then reload with bootout and bootstrap:launchctl bootout gui/$(id -u)/ai.custom.cli-bridge 2>/dev/null launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.custom.cli-bridge.plistGive the bridge
KeepAliveandRunAtLoadwithCLI_BRIDGE_HOST=127.0.0.1in its environment. Give the watchdog aStartIntervalof 120 seconds. Confirm:curl -sS --fail --max-time 5 http://127.0.0.1:<port>/health lsof -nP -iTCP:<port> -sTCP:LISTEN # must show 127.0.0.1, never *:<port> curl -sS http://127.0.0.1:<port>/v1/modelsRegister the Hermes custom provider. Back up the config first:
TS=$(date +%Y%m%d-%H%M%S) cp -a "$(hermes config path)" "$(hermes config path).bak_cli_bridge_${TS}"Append under
custom_providers:. Do not touchmodel.defaultorfallback_providers:custom_providers: - name: claude-cli-bridge-test base_url: http://127.0.0.1:<port>/v1 api_key: cli-bridge-local api_mode: chat_completions model: <opus-slug> models: <opus-slug>: context_length: 200000 "opus[1m]": context_length: 1000000 haiku: context_length: 200000Add one
models:key for everyCLI_BRIDGE_MODELSentry. Edit with PyYAML or thehermes confighelpers, not hand-runsed. Then run:hermes config check hermes model --refreshRun the end-to-end smoke test through the bridge and through Hermes:
curl -sS -m 120 http://127.0.0.1:<port>/v1/chat/completions \ -H 'Content-Type: application/json' \ -d '{"model":"haiku","messages":[{"role":"user","content":"reply with exactly: ok"}]}' hermes chat -q 'reply with exactly: ok' --provider claude-cli-bridge-test --model haikuIf
hermes chatis blocked in your agent shell, report the block and ask the operator to run that command from a terminal. Done when both paths returnok.Expand the model list when a new slug is needed:
- Verify the slug:
"$CLAUDE_BIN" --print --model <slug> 'reply with exactly: ok'. - Add it to
CLI_BRIDGE_MODELSin the config, and to the plist environment if launchd runs the bridge. - Add the same key under
claude-cli-bridge-test→models:with acontext_length. - Reload with bootout and bootstrap, then
curl -s http://127.0.0.1:<port>/v1/modelsandhermes model --refresh. - Run one chat smoke on the new slug.
- Verify the slug:
Keepalive and watchdog
The watchdog runs every 120 seconds and does four things:
- Probes
GET /health. On a miss it runskickstart -k, then bootout and bootstrap. If the port is still held, it stops only a process whose command line is this bridge's own command. It logs that process's PID and command first, and it leaves any other process on the port alone and reports it. - Runs the auth truth probe. On failure it writes
state/auth_flag.jsonwithneed_reauth: true. - Schedules the next truth probe at a randomized 20 to 28 hours. Give each host a different first-check time so a fleet does not probe at the same moment.
- Starts at most one re-login attempt, guarded by a lock file that expires after 30 minutes, so repeated ticks do not launch parallel logins.
Launchd environment rules:
| Change | Correct action |
|---|---|
| Models or env under launchd | Edit the plist (or the config, then re-run the installer), then bootout and bootstrap |
| Models or env, foreground only | Edit the config and restart ./run.sh |
| Any plist change | Never rely on kickstart -k alone for environment changes |
Re-login after OAuth expiry
Symptoms: /health is ok but chat returns process exited rc=1, or the CLI reports that the OAuth session expired and could not be refreshed. auth status can still show a logged-in account.
- Run the truth probe. If it prints
ok, clear the flags in step 5 and stop. - Start
claude auth loginunder a pseudo-terminal driver, run with a Python environment that has the driver library installed. The driver reads the authorize URL the login process prints and gives it to the operator. Send the returned code to the driver's input once it is available. Do not start the login as a background job with its input closed. - Open the authorize URL that this login process printed. Follow the consent pages in order (authorize, sign in, choose the account, authorize again) until a page shows the authentication code. Passwords, two-factor codes, passkeys and billing are entered by a human, never by the agent.
- Save the returned
code#statestring to the code file. Do not print it, paste it into chat, or send it to a notification channel. Verify and clear the flags:
"$CLAUDE_BIN" --print --model haiku 'reply with exactly: ok' curl -sS -m 120 http://127.0.0.1:<port>/v1/chat/completions -H 'Content-Type: application/json' \ -d '{"model":"haiku","messages":[{"role":"user","content":"reply with exactly: ok"}]}' rm -f ~/tools/cli-bridge/state/oauth_code.txtSet
need_reauthtofalseinstate/auth_flag.json, remove the lock file, and if health is ok but chat still fails, runlaunchctl kickstart -k gui/$(id -u)/ai.custom.cli-bridge.
Pitfalls
- Health is not auth.
/healthcan return ok while every chat fails with an expired OAuth session. Always run the haiku probe through the bridge. - An empty picker has three causes. Check the
models:map, whether the bridge is running, and whetherhermes model --refreshran. Check all three before editing anything else. - Editing only the config under launchd leaves a stale environment. Plist
EnvironmentVariableswin over the config loader, so re-run the installer and reload with bootout and bootstrap. - **Never bind
0.0.0.0.** Any other interface exposes your Claude subscription to the network. - Keep the bridge out of the default and fallback chains unless the operator approves it. It is a picker-only provider by default.
- Quote bracketed model names in YAML. An unquoted
opus[1m]is parsed as a flow sequence and breaks the config. - **Do not run
bridge.pyforever in a foreground terminal.** Use launchd or another process manager so it survives restarts. - Do not restart the Hermes gateway to fix the bridge. It is outside this job and it ends other sessions.
- Use a Python environment that has the driver library. Agent shells often run a Python that lacks it.
- Never reuse an OAuth code after its login process has exited. The CLI rejects it with "Invalid state parameter".
- **Never pipe into the login process with a named pipe or a background job with stdin on
/dev/null.** The code cannot reach the process; use a pseudo-terminal driver. - A human completes the OAuth browser steps. The agent does not click through the consent pages or enter any credential.
Verification
Report the verbatim output of each command:
"$CLAUDE_BIN" --version
"$CLAUDE_BIN" --print --model haiku 'reply with exactly: ok'
curl -sS http://127.0.0.1:<port>/health
lsof -nP -iTCP:<port> -sTCP:LISTEN
curl -sS http://127.0.0.1:<port>/v1/models
grep -A40 'claude-cli-bridge-test' "$(hermes config path)" | head -50
PASS when all of these hold:
- health is ok;
- the listener is on
127.0.0.1only; - the models list is non-empty;
- haiku returns
okboth directly and through the bridge; - the provider is present in config;
- default and fallback providers are unchanged.
Attribution
The bridge shim is the upstream Daimon-Law/cli-bridge project's script. This skill covers its configuration, its launchd setup and the keepalive procedure.