Skip to content

Commit eeb96fc

Browse files
committed
claude の枠を、コンテナの中のサインインから取れるようにする
登録できるものと、サインインでできることが違った。`claude setup-token` の 長期トークンは推論だけに絞られていて、枠のパネルを出せない —— `/usage` は 会話を始めずに終わった締めの集計を返す(Total cost: $0.0000、num_turns も 0)。 当時「出せない」と判断したのはこの文面で、そこは今も変わっていなかった。 変わったのは、パネルを出せる道があると分かったこと。 登録が空のときだけ、CLI 自身のサインイン結果に任せる(antigravity と同じ形)。 登録があるとそちらを環境変数で置くので、登録したままサインインしても枠は出ない。 サインインもしていなければ CLI 自身が断るので、ここで先回りして断らない。 締めの集計が返ったら、打つ手を添えて返す。0 が並んだ集計をそのまま見せても、 壊れているのか頼み方が違うのかが読めない。数字ではなく語で見分ける —— 数字で見ると、本当に使用量が 0 の日と区別できない。 サインイン結果の置き場を compose に足す。無いと、コンテナを作り直すたびに サインインが消える。名前付きボリュームにする理由は antigravity と同じ。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent f9b872a commit eeb96fc

5 files changed

Lines changed: 114 additions & 7 deletions

File tree

CLAUDE.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,10 +1224,16 @@ GeoNames 全世界地名辞典 = `geonames`(いずれも 348 言語版・195 か
12241224
**口が無いのではなく、叩く口を間違えていた** —— `claude -p "/usage"` で取れる
12251225
(実測。print モードのスラッシュコマンドで、会話を始めずに CLI 自身の報告が
12261226
`result` に入って返る)。**「取れない」と書いてある理由は、疑ってから消すこと。**
1227-
**そのあと一度「出せない側」に戻していたが、それも古かった** —— 当時は
1228-
締めの集計しか返らなかった(`Total cost: $0.0000` / `num_turns` も 0)のが、
1229-
CLI の版が上がってパネルが `result` に入るようになった(実測の文面は
1230-
`app/providers.py`)。**戻る時刻に年が書かれていない**ので、ブリッジが
1227+
**出せるのはコンテナの中でサインインしたときだけ** —— 登録できる
1228+
`claude setup-token` の長期トークンは推論だけに絞られていて、`/usage`
1229+
締めの集計を返す(`Total cost: $0.0000` / `num_turns` も 0。当時「出せない」と
1230+
判断したのはこの文面で、そこは今も変わっていない)。**登録が空のときだけ
1231+
CLI 自身のサインイン結果に任せる**(`apply_credential`。antigravity と同じ形)
1232+
—— 登録があるとそちらを環境変数で置くので、登録したままサインインしても
1233+
枠は出ない。**`/srv/bridge/home` を名前付きボリュームにしておくこと**
1234+
(無いとコンテナを作り直すたびにサインインが消える)。
1235+
**締めの集計が返ったら、打つ手を添えて返す**(`_looks_like_a_wrap_up`)——
1236+
0 が並んだ集計をそのまま見せても、壊れているのか頼み方が違うのか読めない。**戻る時刻に年が書かれていない**ので、ブリッジが
12311237
いちばん近い将来として ISO に直す(`_claude_reset_at``(UTC)`
12321238
書かれているときだけ直し、別の呼び方には手を出さない)。
12331239

bridge/cli_bridge.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,15 @@ def apply_credential() -> str:
276276
# API キー方式が無く、置くものが無い(サインイン結果は HOME のキャッシュにある)。
277277
return ""
278278
value = stored_credential()
279+
if not value and CLI == "claude":
280+
# **登録が無ければ、コンテナ内のサインイン結果に任せる**(antigravity と同じ形)。
281+
# 断らないのは、**登録できるものとサインインでできることが違う**ため ——
282+
# `claude setup-token` の長期トークンは推論だけに絞られていて、枠のパネルを
283+
# 出せない(実測: `/usage` が締めの集計を返す)。枠まで欲しいなら、
284+
# コンテナの中で 1 回サインインするほうを選べる必要がある。
285+
# **サインインもしていなければ CLI 自身が断る**(`Not logged in · Please run
286+
# /login` が返り、`is_error` で捕まる)ので、ここで先回りして断らない。
287+
return ""
279288
if not value:
280289
return (
281290
f"{CLI} の認証情報が未登録です。"
@@ -1717,6 +1726,19 @@ async def _talk() -> tuple[list[dict], str]:
17171726
REASON_MAX = 600
17181727

17191728

1729+
# 会話を始めずに終わった print モードの締め。**枠のパネルではない。**
1730+
# 数字はどれも 0 で、見せられても打つ手が分からない(実測の文面をそのまま判定に使う)。
1731+
_WRAP_UP_MARKS = ("Total cost:", "Usage:")
1732+
1733+
1734+
def _looks_like_a_wrap_up(text: str) -> bool:
1735+
"""枠のパネルではなく、print モードの締めの集計かどうか。
1736+
1737+
**語で見る。** 数字で見分けようとすると、本当に使用量が 0 の日と区別できない。
1738+
"""
1739+
return all(mark in text for mark in _WRAP_UP_MARKS)
1740+
1741+
17201742
def _usage_reason(raw: str) -> str:
17211743
"""窓を組めなかったときに Chiezo へ渡す理由。**人向けの報告のほうを渡す。**
17221744
@@ -1732,7 +1754,16 @@ def _usage_reason(raw: str) -> str:
17321754
if isinstance(payload.get(key), str) and payload[key].strip():
17331755
text = payload[key]
17341756
break
1735-
return text.strip()[:REASON_MAX]
1757+
text = text.strip()
1758+
if _looks_like_a_wrap_up(text):
1759+
# **そのまま出しても打つ手が分からない。** 0 が並んだ集計を見せられても、
1760+
# 壊れているのか、そもそも出せない頼み方をしているのかが読めない。
1761+
return ("枠のパネルではなく、会話を始めずに終わった締めの集計が返っています。"
1762+
"登録してある `claude setup-token` の長期トークンは推論だけに"
1763+
"絞られていて、枠を出せません —— コンテナの中で 1 回サインインし"
1764+
"(`docker exec -it <ブリッジのコンテナ> claude` で `/login`)、"
1765+
"管理画面の登録を空にしてください。")
1766+
return text[:REASON_MAX]
17361767

17371768

17381769
async def _read_usage() -> dict:

docker-compose.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,12 @@ services:
269269
# # **ブリッジは `/state/settings.db` を決め打ちで読む**(CHIEZO_BRIDGE_STATE_DB の
270270
# # 既定)。app 側の置き場が data/ の下へ移っても、こちらのマウント先は /state のまま
271271
# - ./data/state:/state:ro
272+
# # **サインイン結果の置き場。** 登録を空にしてコンテナの中でサインインすると、
273+
# # 枠(`/usage`)まで取れる —— 登録できる `claude setup-token` の長期トークンは
274+
# # 推論だけに絞られていて、枠のパネルを出せない。ここが無いと、コンテナを
275+
# # 作り直すたびにサインインが消える。名前付きボリュームにする理由は
276+
# # antigravity のところに書いてある(非 root で動くため)
277+
# - chiezo-claude-home:/srv/bridge/home
272278
# environment:
273279
# - CHIEZO_BRIDGE_CLI=claude
274280
# - CHIEZO_BRIDGE_MCP_URL=http://chiezo-app:7010/mcp/knowledge/
@@ -389,4 +395,5 @@ configs:
389395
# 無いときに Docker が root 所有で作ってしまい、非 root のブリッジが書けなくなるため。
390396
volumes:
391397
chiezo-antigravity-home:
398+
chiezo-claude-home:
392399
chiezo-codex-home:

docs/ai.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -822,8 +822,26 @@ Current week (all models): 24% used · resets Sep 20, 6am (UTC)
822822
Current week (Fable): 0% used · resets Sep 20, 6am (UTC)
823823
```
824824

825-
当時の観測は会話を始めずに終わった締めの集計だけで(`Total cost: $0.0000 /
826-
Usage: 0 input, 0 output``num_turns` も 0)、欲しい数字がそもそも入っていませんでした。
825+
**ただし、登録してあるトークンでは出せません。** `claude setup-token` の長期トークンは
826+
推論だけに絞られていて、`/usage` は会話を始めずに終わった締めの集計を返します
827+
`Total cost: $0.0000 / Usage: 0 input, 0 output``num_turns` も 0)。当時「出せない」と
828+
判断したのはこの文面で、**そこは今も変わっていません**——変わったのは、パネルを出せる
829+
道ができたことのほうです。
830+
831+
**枠まで欲しいときは、コンテナの中で 1 回サインインします。**
832+
833+
```bash
834+
# 管理画面(/admin/ai)で claude の登録を空にしてから
835+
docker exec -it chiezo-bridge-claude claude # /login
836+
```
837+
838+
登録が空のときだけ、ブリッジは CLI 自身のサインイン結果に任せます(Antigravity と
839+
同じ形)。登録があるとそちらを環境変数で置くので、**登録したままサインインしても
840+
枠は出ません****`/srv/bridge/home` を名前付きボリュームにしておいてください**——
841+
無いと、コンテナを作り直すたびにサインインが消えます。
842+
843+
会話のほうはどちらでも動きます。枠が要るかどうかで選んでください。
844+
827845
**「取れない」と書いてある理由は、疑ってから消してください** ——あのときも口が
828846
無かったのではなく、叩く口を間違えていました(`/api/oauth/usage` のほうは今も
829847
`user:profile` を要求するので 403 のままです)。

tests/test_bridge.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1639,3 +1639,48 @@ def test_the_panel_survives_a_noisy_line(self, bridge):
16391639
})
16401640

16411641
assert [w["used_percent"] for w in server._claude_windows(raw)] == [20.0, 25.0]
1642+
1643+
1644+
class TestClaudeCanUseItsOwnSignIn:
1645+
"""登録できるものと、サインインでできることが違う。
1646+
1647+
`claude setup-token` の長期トークンは推論だけに絞られていて、**枠のパネルを
1648+
出せない**(実測: `/usage` が会話を始めずに終わった締めの集計を返す)。
1649+
枠まで欲しい人が、コンテナの中のサインインを選べる必要がある。
1650+
"""
1651+
1652+
def test_nothing_registered_is_not_an_error(self, bridge, monkeypatch):
1653+
server = bridge(CHIEZO_BRIDGE_CLI="claude")
1654+
monkeypatch.setattr(server, "stored_credential", lambda: "")
1655+
1656+
assert server.apply_credential() == ""
1657+
1658+
def test_a_registered_token_still_wins(self, bridge, monkeypatch):
1659+
server = bridge(CHIEZO_BRIDGE_CLI="claude")
1660+
monkeypatch.setattr(server, "stored_credential", lambda: "sk-ant-oat01-test")
1661+
monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False)
1662+
1663+
assert server.apply_credential() == ""
1664+
assert server.os.environ["CLAUDE_CODE_OAUTH_TOKEN"] == "sk-ant-oat01-test"
1665+
1666+
def test_codex_still_needs_something_registered(self, bridge, monkeypatch):
1667+
"""あちらは認証ファイルを置く方式で、置くものが無ければ動かない。"""
1668+
server = bridge(CHIEZO_BRIDGE_CLI="codex")
1669+
monkeypatch.setattr(server, "stored_credential", lambda: "")
1670+
1671+
assert "未登録" in server.apply_credential()
1672+
1673+
def test_a_wrap_up_says_what_to_do(self, bridge):
1674+
"""0 が並んだ集計を見せられても、壊れているのか頼み方が違うのか読めない。"""
1675+
server = bridge(CHIEZO_BRIDGE_CLI="claude")
1676+
raw = json.dumps({"result": (
1677+
"Total cost: $0.0000\nTotal duration (API): 0s\n"
1678+
"Usage: 0 input, 0 output, 0 cache read, 0 cache write")})
1679+
1680+
assert "サインイン" in server._usage_reason(raw)
1681+
1682+
def test_a_real_panel_is_not_mistaken_for_one(self, bridge):
1683+
server = bridge(CHIEZO_BRIDGE_CLI="claude")
1684+
raw = json.dumps({"result": "Current session: 20% used"})
1685+
1686+
assert server._usage_reason(raw) == "Current session: 20% used"

0 commit comments

Comments
 (0)