shunt は、上流の Claude、Codex/ChatGPT、Antigravity のアカウントをプロビジョニングし、claude_oauth、chatgpt_oauth、antigravity_oauth のアカウントプール状態を表示する、管理者認証付きの Web サーフェスを公開できます。これはオプトインです。[server.admin] がなければ /admin* ルートは一切登録されず、shunt のデフォルトの HTTP サーフェスは変わりません。
これは Anthropic マルチアカウントのストアと選択の挙動の上に成り立っています。ブラウザーフォームでは、リフレッシュ可能な Full OAuth アカウントまたは 1 年間・推論専用の setup token アカウントを作成できます。既存の Claude Code credential ファイルのインポートは CLI 専用です。
管理サーフェスを有効化する
オプションのテーブルを追加し、設定した環境変数から管理者の認証情報を 1 つ以上提供します。
[server.admin] # すべてのキーは省略可能。値はデフォルト
header = "x-shunt-admin-token"
tokens_env = "SHUNT_ADMIN_TOKENS"
session_ttl_secs = 3600
pending_ttl_secs = 600
# hide_observed = false # (デフォルト) ホストの CLI/アプリのログインをスキャンexport SHUNT_ADMIN_TOKENS="ops:$(openssl rand -hex 32)"
shunt check
shunt run認証情報は SHUNT_CLIENT_TOKENS と同じカンマ区切りの name:token 形式を使いますが、別個のセキュリティ境界です。[server.auth] のクライアントトークンを管理トークンとして再利用しないでください。[server.admin] が存在するのに 3 つの認証情報ソース(トークン環境変数または tokens_file、[[server.admin.write_keys]]、[[server.admin.read_keys]])がすべて空または不正な場合、起動はフェイルクローズします。name:token ペアは write ティアで、read_keys のエントリーは管理サーフェスのすべての GET を通過しますが、すべての変更操作では拒否されます。ダッシュボードにサインインすることはでき、そのセッションは読み取り専用です。
すべてのキーとデフォルトは設定リファレンスを参照してください。ブラウザーおよび JSON のルートはエンドポイントリファレンスに一覧があります。
ダッシュボードを共有したりリモートから利用したりしていて、ゲートウェイホスト上のプロバイダーのログインを表示すべきでない — それどころか読み取るべきでもない — 場合は、hide_observed = true を設定します。すると shunt はホストの CLI/アプリの認証情報の検出を完全に省きます: GET /admin/api/observed は引き続き管理認証情報を必要としますが空のリストを返し、ダッシュボードはこれを要求しません。Accounts and usage 表は管理対象のプールアカウントの使用量が表示される場所でもあるためページに残り、管理対象のプールアカウントのみを一覧表示します。この変更は設定のホットリロードで適用されます。すでに開いているダッシュボードはページの読み込み時にこのフラグを読むため、ページを再読み込みすると新しい値に従います。[server.admin] 自体の追加・削除には引き続き再起動が必要です。
ブラウザーで Claude アカウントをプロビジョニングする
-
/adminを開き、管理トークンでサインインします。 -
小文字・数字・ハイフンのみからなるアカウント名を入力します。
-
Full OAuth (refreshable)(ダッシュボードのデフォルト)または Setup token (1-year, inference-only) を選び、Start を選択します。
-
表示された認可 URL を別のタブで開きます。対象の Claude アカウントにサインインし、アクセスを承認します。
-
得られた
<code>#<state>の値を管理ページに貼り付け、Complete を選択します。 -
shunt がアカウントを保存します。
accountsリストが空の provider は、再起動なしで次のリクエスト時にそれを拾います。そうでなければ、名前だけのエントリーを追加して reload します。[[providers.anthropic.accounts]] name = "backup"
開始されたフローは pending_ttl_secs(デフォルト 10 分)の間有効で、オペレーターが認可ページを開いて結果を貼り付ける時間を確保できます。サーバーは選択された mode を pending attempt とともに記録するため、completion リクエストで token の種類を切り替えることはできません。Start を選んだ時点からフローが完了するまで、ダッシュボードはログイン方法のラジオボタンを無効化し、その理由も表示します。pending attempt の mode はリクエストを送る時点で固定されるためです。Start をもう一度選んでも解除されません。フローを完了するか、ページを再読み込みすると再び選べるようになります。また Start を選ぶとすでに開いている認可ステップも閉じられるため、置き換えのリクエストを送った時点でリンクと Complete フォームは消えます。その置き換えが失敗した場合は — 拒否されたときも、応答がまったくないときも — 失敗メッセージが前のステップを閉じたことも伝えます。コードをすでに送信していたステップについては、その交換自体が失敗したと分かってから初めて伝えます。それまではアカウントが保存される可能性が残っているためです。そのため交換に時間がかかると、この一文は失敗メッセージが表示された少し後に追記されます。サーバー側に何が残るかは場合によって異なります。拒否された置き換えは前の pending ログインに触れないため、残りの pending_ttl_secs の間そのまま有効です。一方、応答がなかったリクエストはサーバーに届いていた可能性があり、同じアカウント名であればそのログインをすでに置き換えています。いずれにせよページからは到達できないため、フローをやり直してください。Full OAuth は access token と refresh token を保存し、credential kind は imported と表示されます。setup-token mode は kind が setup_token の静的 credential を保存します。完了レスポンスは、アカウントが保存されたか、そして現在の provider 設定でそれが有効(live)になるかを報告します。
アカウントストアの変更はリクエストごとに検出されるため、スキャンモードのプロバイダーはアカウントの追加・削除後に再起動する必要がありません。
ブラウザーで Codex アカウントをプロビジョニングする
- Add Codex account で小文字のアカウント名を入力し、Start Codex login を選択します。
- 認可 URL を開き、対象の ChatGPT アカウントにサインインしてアクセスを承認します。
- ブラウザーは
http://localhost:1455/auth/callbackへ移動します。ローカルページが読み込めなくても正常です。 - ブラウザーのアドレスバーから URL 全体をコピーして管理ページへ貼り付け、Complete Codex login を選択します。JSON API では
<code>#<state>も使用できます。 - shunt が code を交換し、リフレッシュ可能な Codex credential を非公開のアカウントファイルへ保存します。
accounts が空の chatgpt_oauth provider(デフォルトの codex を含む)は、次のリクエストで新しいアカウントを検出します。明示的なアカウント一覧を使う場合は、名前だけの entry を追加してください。SHUNT_CODEX_TOKEN_URL はローカル統合テスト用の token endpoint override です。production では設定しないでください。
ブラウザーで Antigravity アカウントをプロビジョニングする
- Add Antigravity account で小文字のアカウント名を入力し、Start Antigravity login を選択します。
- authorize URL を開き、対象の Google アカウントでサインインしてアクセスを承認します。
- ブラウザーは
http://localhost:51121/oauth-callbackにリダイレクトされます。ローカルページが読み込めないのは想定どおりです。 - ブラウザーのアドレスバーの URL 全体 をコピーして管理ページに貼り付け、Complete Antigravity login を選択します。JSON API では
<code>#<state>も使えます。 - shunt が code を交換し、Code Assist project を解決した上で(失敗しても致命的ではなく、アカウントの最初のリクエストで再試行されます)、非公開のアカウントファイルへ保存します。
accounts が空の antigravity_oauth provider(デフォルトの antigravity を含む)は、次のリクエストで新しいアカウントを検出します。明示的なアカウント一覧を使う場合は、名前だけの entry を追加してください。Codex と異なり、Antigravity の表には Refresh ボタンもあり、保存された refresh グラントをその場で実行してログインがまだ生きているかを報告します。SHUNT_ANTIGRAVITY_TOKEN_URL はローカル統合テスト用の token endpoint override です。production では設定しないでください。
プールの健全性を確認する
ダッシュボードは、auth = "claude_oauth"、auth = "chatgpt_oauth"、または auth = "antigravity_oauth" の provider について、アカウントストアのメタデータと現在の状態を表示します。Claude の行には、上流の応答から観測した 5 時間・共有 7 日・7d_oi の使用率が、ステータスやクールダウン状態とともに表示されます。上流がリセット時刻を報告している場合は、各ウィンドウのセルにリセットまでの残り時間(例: 3d 4h)も表示され、ホバーすると絶対リセット時刻を確認できます。Codex の行には、x-codex-* 応答ヘッダーで報告される 5 時間・7 日のウィンドウが表示されます。未対応のウィンドウは無視され、Codex には相当するものがないため 7d_oi は — のままです。Codex の使用量は、Claude の使用量とまったく同じように、クォータを考慮したアカウント選択にも供給されます(issue #195)。
死んだクレデンシャルと Refresh ボタン
オペレーターの介入なしにはどのリトライでも回復しないアカウントは、cooling ではなく Needs re-login と表示されます。主テーブルと高度なプールテーブルの両方でです。この区別が重要なのは、クールダウンは 5 分で自然に失効してアカウントが再び試されるため、恒久的に死んだクレデンシャルがクォータ一時停止と永久に同じに見えてしまうからです。この印は、プロバイダーが refresh グラントを終端的に拒否したとき、リフレッシュ自体が不可能なクレデンシャル(token_env トークンや期限切れの setup token)が拒否されたとき、そしてリフレッシュ直後の bearer で再試行したリクエストがなお拒否されたときに立ちます。クールダウンとは独立に報告され、メモリ上のみのため、再起動するとそのアカウントが次に失敗するまでクリアされます。この印は、つまずいたプロバイダーエントリではなくクレデンシャルに従います。1 つの保存済みアカウントを複数のプロバイダーテーブルで名前によって有効化している場合、その行はすべて一緒に切り替わります。自分の credentials ファイルや token_env を指すアカウントは、名前が同じでも別のクレデンシャルなので、そこでの失敗は他の何にも印を付けません。
回復手段はアカウントを削除して追加し直すことです。imported アカウントの行には Refresh ボタンもあります。そのアカウントの refresh グラントを即座に実行してログインがまだ生きているかを報告するので、トラフィックを待たずに再ログインの成否を確認するのに便利です。プロバイダーのトークンエンドポイントを叩くためレート制限があり、共有クレデンシャルストアを必ず経由するのでプロキシ自身のリフレッシュと競合せず、通常の利用と同じように保存された refresh token をローテーションします。新しい有効期限のみを返し、トークン本体は一切返しません。setup_token アカウントは refresh グラントを持たないため、プロバイダーに接続せず 400 で応答します。 このボタンが表示されるのは書き込み権限のセッションだけです(下記参照)。
プローブの成功が印を消すのは、グラントの失敗が印を立てた場合のみです。プロバイダーが既に発行した bearer を拒否したことでアカウントが死んだと判定された場合、グラントが動くという事実は推論を提供できることを何ら証明しないため、印は残り、レスポンスもそう伝えます。
アカウント一覧が公開するのはメタデータのみです。アカウント名、認証情報の種類(setup_token または imported)、有効期限、UUID。トークン本体を返すことはありません。shunt がアカウント選択時にクォータ状態・クールダウン・モデルを認識した週次バケットをどう使うかは Anthropic マルチアカウントを参照してください。
ダッシュボードの Claude テーブルは、この有効期限を認証情報の種類から導出した Status として表示します。生の値は約 8 時間の access token の期限であり、種類によって意味が正反対になるためです。imported アカウントは refresh token を持ち shunt が必要に応じて更新するので Auto-refreshes と表示され(生のタイムスタンプはホバーで確認)、過去の日付でも正常です。setup_token アカウントは更新自体ができないため、1 年の有効期限は実際に対処が必要です。更新バッファより余裕があれば Valid until と日付、バッファ内に入るか日付を過ぎていれば danger スタイルの Expired と再ログインの案内が表示されます。バッファはルーティングが適用するものと同じ 5 分(EXPIRY_BUFFER)なので、ルーティングがすでに refresh token なしの経路で失敗させる区間を、ダッシュボードが利用可能と表示することはありません。各行には Re-login ボタンもあり、上の追加フォームにそのアカウントの名前とログイン方法を入力します — 既存の名前で通常のフローを完了すると、そのアカウントはその場で上書きされます。Codex テーブルにも同じ Status 列がありますが、こちらは無条件です。その store には更新できない種類が存在せず(書き込み経路が 2 つとも refresh token を必須とします)、shunt がすべての Codex アカウントを更新するため、各行は Auto-refreshes と表示され、生のタイムスタンプは同様にホバーで確認できます。Codex の行にも同じ Re-login ボタンがありますが、ログイン方法の事前選択はありません — その store への入口は ChatGPT OAuth だけだからです。Antigravity テーブルも同様に無条件で Auto-refreshes を表示し、Re-login ボタンに加えて上で説明した Refresh ボタンも持ちます。上部の使用量テーブルの読み取り専用の行は影響を受けません。そちらの認証情報を更新するのは shunt ではなく、所有している provider クライアントです。 ここでの行アクションはすべて書き込み権限です。read_keys でログインした場合、同じテーブルと同じ Status 列は表示されますが、行アクションのボタンとアカウント追加フォームは表示されません: サーバー側の require_write がその変更を拒否するため、ダッシュボードは最初から提示しません。
アカウントの metadata、プールの健全性、プロビジョニング、またはアカウント削除に API/curl でアクセスするには、設定したヘッダー(デフォルト x-shunt-admin-token)または x-api-key で管理認証情報を送り(どちらのスロットも管理ルートでのみ受け付けられ、推論ルートでは受け付けられません)、HTTP エンドポイントに記載の JSON route を使ってください。ヘッダー認証されたリクエストはブラウザーセッションを使わず、CSRF チェックの対象外です。プロビジョニングの開始時には { "name": "backup", "mode": "oauth" } または mode: "setup_token" を送ります。mode を省略した場合は、API の後方互換性のため setup_token がデフォルトです。完了呼び出しのアップストリームトークン交換には、サーバー側で 30 秒の上限があります。これを超えると shunt は 502 と Claude token exchange timed out(Codex では Codex token exchange timed out)を返し、何も保存しません。タイムアウトしても保留中のログインは残り、その試行も 5 回の上限に数えられますが、認可コードはアップストリームですでに消費されている可能性があります。同じコードを再送するのではなく、フローをやり直して新しいコードを取得してください。
CLI と SSH のフォールバック
shunt ホストにブラウザーで到達できない場合は CLI を使ってください。Full OAuth は通常、ブラウザーを開き、一時的な 127.0.0.1 callback で完了します。SSH または headless 環境では、管理ページと同じ手動貼り付け redirect を強制します。
shunt login claude --name backup --mode oauth --manual代わりにホストの現在のリフレッシュ可能な Claude Code ログインをインポートするには、次を実行します。
shunt login claude --name primary --mode import1 年間・推論専用の credential を作成するには、次を実行します。
shunt login claude --name ci --mode setup-token--long-lived は --mode setup-token の deprecated alias です。管理サーフェスは Claude の Full OAuth/setup-token、Codex の ChatGPT OAuth、Antigravity OAuth のプロビジョニングをサポートします。既存 credential ファイルの import だけはホストへのアクセスが必要なため CLI 専用です。
セキュリティ
- 管理サーフェスは HTTPS、または WireGuard や Tailscale のような信頼できるトンネルの背後に置いてください。shunt 自身は平文 HTTP を提供します。リモートに公開する場合は前段で TLS 終端を使ってください。
- 強力な管理トークンを生成し、
[server.auth]のクライアント認証情報とは分けて管理してください。管理アクセスは上流アカウントを追加・削除できます。 - ブラウザーログインは HttpOnly かつ SameSite=Strict のセッションクッキーを作成します。クッキーはループバックホストを除いて Secure なので、ローカルの HTTP 開発は引き続き動作します。
- 変更を伴うブラウザーリクエストにはセッションごとの
x-csrf-tokenが必要で、同一オリジンチェックを通過します。API/curl 呼び出しは代わりに管理ヘッダーで認証し、アンビエントなクッキー権限を持ちません。 - プロビジョニングの完了はレート制限されます。shunt はトークン本体をログにも応答にも出さず、アカウントの追加と削除はアカウント名で監査ログに記録されます。
[server.admin] がなければ、これらのルートは存在しません。これは未使用のダッシュボードを認証なしで放置するより強力です。明示的に有効化しない限り、管理サーフェスは存在しません。