Metadata-Version: 2.4
Name: third-option
Version: 0.3.0
License-File: LICENSE-APACHE
License-File: LICENSE-MIT
Summary: Sign Claude Code in to a Third Option gateway from anywhere: the gateway's device-grant login, and the apiKeyHelper and otelHeadersHelper that keep the session fresh.
Keywords: gateway,oidc,device-flow,enterprise,cli
Home-Page: https://third-option.com
License: MIT OR Apache-2.0
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://third-option.com
Project-URL: Source Code, https://github.com/3rd-option/monorepo

# third-option

Signs Claude Code in to a Third Option gateway from anywhere, and keeps the
session fresh.

Claude Code's own gateway login accepts only a gateway on a private network:
every address the host resolves to must be RFC 1918, CGNAT or loopback, and
no setting widens that. A gateway with a public address — a hosted one, or a
company's reached without the VPN — has no interactive way in. This is that
way in.

## Install

```sh
cargo install third-option
npm install -g third-option
uv tool install third-option        # or: pipx install third-option
```

Prebuilt for Linux x64 and arm64 (glibc) and Apple Silicon; `cargo install`
builds anywhere Rust does.

## Use

```sh
third-option enroll https://gateway.example.com
claude --settings ~/.config/third-option/claude-settings.json
```

`enroll` opens the gateway's sign-in page, waits for you to confirm the code
it printed, then writes a settings file of its own
(`$THIRD_OPTION_HOME/claude-settings.json`, default `~/.config/third-option/`)
that points one launch of Claude Code at the gateway. Nothing under `~/.claude`
changes, so a trial, a demo, or a second gateway beside your everyday setup
leaves every other session alone.

```sh
third-option enroll --global https://gateway.example.com
```

writes the same keys into Claude Code's own `settings.json`
(`$CLAUDE_CONFIG_DIR`, else `~/.claude`) instead, so every session on the
machine uses the gateway. Either way the file ends up with:

- `env.ANTHROPIC_BASE_URL` is the gateway and model discovery is on, so the
  model picker shows what the gateway serves;
- `apiKeyHelper` is `third-option token`, which prints the session token and
  renews it through the gateway's refresh grant before it runs out;
- whatever the gateway pushes from `/managed/settings` — model, permissions,
  the telemetry exporter — is merged in, the operator's keys winning and
  lists such as `permissions.deny` unioned;
- when telemetry is pushed, `otelHeadersHelper` is `third-option
  otel-headers`, so exports to the gateway carry the session and land.

Nothing else in the file is touched. Run `enroll` again to pick up a changed
policy. `third-option status` shows the session, when it ends, and what Claude
Code points at; `third-option models` lists what this identity may use.

| Verb | Does |
| --- | --- |
| `enroll [url]` | sign in if needed, then write the settings that point Claude Code at the gateway: a file for `claude --settings`, or with `--global` Claude Code's own |
| `login <url>` | sign in only — for a scripted client that sets `ANTHROPIC_BASE_URL` itself |
| `token` | print a fresh session token (`apiKeyHelper`) |
| `otel-headers` | print `{"Authorization": "Bearer …"}` (`otelHeadersHelper`) |
| `status`, `models`, `logout [--all]` | |

`--gateway <url>` picks a gateway; otherwise `$ANTHROPIC_BASE_URL` — which
Claude Code sets from the `env` block when it runs a helper — then the last
login. `--no-browser` prints the link instead of opening it.

## Where the session is kept

The session is a credential, so it lives in the OS secure store: the Keychain
on macOS, the Secret Service (GNOME Keyring, KWallet) on Linux, the Credential
Manager on Windows — service `third-option`, account the gateway's origin, so
it is the entry you see in Keychain Access or Seahorse. macOS asks once per
new build of the binary whether it may read its own entry; "Always Allow" is
the answer. `credentials.json` under `$THIRD_OPTION_HOME`, else
`~/.config/third-option`, only records which gateways have a session and which
was used last.

Where no secure store is reachable — a server with no session bus, a CI
runner — `login` keeps the session in that file instead, readable only by
you, and says so. `THIRD_OPTION_STORAGE=file` chooses the file outright;
`THIRD_OPTION_STORAGE=keyring` refuses the fallback. `status` shows where each
session is.

## Two modes, one gateway

| | Public — this CLI | Private network — Claude Code's own login |
| --- | --- | --- |
| Reaches the gateway over | any TLS route | private addresses only |
| Enrollment | `third-option enroll` | `/login`, pinned by a machine policy file |
| Credential | `apiKeyHelper` | the pinned gateway session |
| Managed settings | merged into the file `enroll` writes | pushed live, not editable |
| Telemetry | signed by `otelHeadersHelper` | signed by the session |

They are exclusive on one machine: the policy file that turns on Claude
Code's own gateway login (`/etc/claude-code/managed-settings.json`;
`/Library/Application Support/ClaudeCode/` on macOS) makes it refuse every
helper credential. `enroll` and `status` say so when they see one. The
private-network mode is the stronger one, so a fleet on its own network
should use it; this CLI is for everyone else.

## Scripted use

```sh
export ANTHROPIC_BASE_URL=https://gateway.example.com
export ANTHROPIC_AUTH_TOKEN="$(third-option token)"
```

`token` renews the session when it has under fifteen minutes left, so a
long-running script can call it again rather than cache the value.

## License

MIT OR Apache-2.0.

