Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions configs/cli/claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
- UTCではなくJST(日本時間)を使用

### ブラウザ操作
常用ブラウザはZen。自動化は用途ごとに4段で、上から順に安い方を選ぶ
常用ブラウザはZen。自動化は3つで、まず1、足りなければ2、見せる必要があれば3
Chromeは2026-08-30に撤去した(Claude in Chrome拡張ごと)。拡張ができることはPlaywrightで
全部できたうえ、拡張は`--remote-debugging-port`を開けている間ずっと、localhostの誰にでも
ブラウザを明け渡す状態を作っていた。
Expand All @@ -17,21 +17,29 @@ Chromeは2026-08-30に撤去した(Claude in Chrome拡張ごと)。拡張が
2. **Lightpanda**(常駐、CDP=9223 / Playwright MCP=`http://localhost:8932/mcp`)
裏で回す既定。実測14MB。見えないし軽いので常駐させたままでよい。
ただし実装していないAPIに触るSPAは**エラーではなく空で返る**。取れた内容が
空だったらここを疑い、下に落とす
3. **terminal-browser** — 見せる担当。Electron同梱の実Chromiumなので描画の穴がなく、
`terminal-browser open --split right <url>` で会話の隣に並ぶ。操作は
空だったらここを疑い、3へ落とす
3. **terminal-browser** — 実Chromium(Electron同梱)なので描画の穴がない。
`terminal-browser open --split right <url>` で会話の隣に並び、操作は
`terminal-browser action -- snapshot|click|fill|eval`。ssh越しも可
(`open --ssh user@host <url>`)。TTYが無くても見えるペインを開くので、
**ユーザーの目視が要らない場面では使わない**。
4. **Helium**(ungoogled-chromium)— フルのChromiumが要るとき。
`open -gjn -a Helium --args --user-data-dir=... --no-startup-window --remote-debugging-port=9222`
で起こすと`http://localhost:8931/mcp`のPlaywright MCPが掴む。常駐させない。
9222はローカルに開いたポートなので、使い終わったら落とす。
ungoogledなのでGoogleログインは通らない見込み。それが要る仕事は今この機械にはない。
(`open --ssh user@host <url>`)。終わったら`terminal-browser shutdown`。
**必ず見えるペインを開く**(TTYが無くても右に分割する)ので、ユーザーの
目視が要らない場面では2で粘る。
Playwrightからは掴めない: CDPは出しているが**ポートが毎回変わる**
(実測 53218 → 53337)ので、固定の`--cdp-endpoint`にできない。`action`で操作する。
herdrの`[experimental] kitty_graphics`が要る(既定false)。切れているとペインは
作られてブラウザも生きているのに**何も描かれない**。無言で失敗するので注意。
見えているかの確認は`terminal-browser ls`ではなくherdrに聞く。`ls`は自分の
モデルを喋るだけで描画を保証しない(`pane`も`viewport`も返るのに真っ白だった)。
`herdr pane process-info --pane <id>`の前面プロセスがterminal-browserなら生きて
いて、`herdr pane read <id>`が**空なら正常**(絵はテキストに写らない)。逆に
コマンド行とプロンプトが読めたらそれは死骸のシェル。
`shutdown`は残骸を残す。次の`open`がそれを拾うと`tty`も`cdpPort`も無いJSONを
返して終わり、空のペインだけが積もる。`pkill -f agent-browser`してから開き直す。

- 速度は描画ではなく往復回数で決まる。読み取りだけなら1と2で足りる。
- `--headless=new`をChromeで試した記録が残っているが(2026-08-10、拡張のnative hostが
起動しない)、Chromeごと無くなったので過去の話。Lightpandaは最初からheadless。
- Heliumはこの動線には出てこない。ヘッドレスでWebGL/スクリーンショットが要る
検証(vrma-lab の`scripts/chrome-lab.sh`)専用に残してある。Lightpandaは
レンダラを持たないのでそこは代われず、terminal-browserは必ず見えてしまう。
- ポートの確認は`netstat -an | grep LISTEN`で。**この機械に`/usr/bin/lsof`は無い**ので、
lsofは黙って空を返し、空きポートに見えてしまう(9333で実際に踏んだ)。

Expand Down
7 changes: 7 additions & 0 deletions configs/cli/herdr/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,10 @@ show_agent_labels_on_pane_borders = true
# そのときは rclone の死んだ NFS マウントが残っていて mds が getattrlistbulk で
# 止まっていた)。OS 通知自体は Claude Code の Stop フック (agent-notify) が osascript で
# 出しているので、外しても手元に届く通知は変わらない。むしろ二重通知が一本になる。

[experimental]
# terminal-browser がペインに絵を出すのに要る。既定は false で、切っていると
# ペインは作られてブラウザのプロセスも前面に居座るのに、中身が何も描かれない。
# エラーも出ないので「開いたのに見えない」という形でしか気づけない。
# herdr はクライアント側で kitty graphics を合成して Ghostty へ渡す。
kitty_graphics = true
80 changes: 10 additions & 70 deletions nix/modules/home/darwin-services.nix
Original file line number Diff line number Diff line change
Expand Up @@ -55,50 +55,22 @@ in
# The MCP entry in ~/.config/claude/.claude.json used to be `stdio`, which means every session
# spawns its own server process: measured 2026-08-15 at 15 sessions = 15 node processes, 176MB,
# and ~120MB each once a session actually drives the browser. All of them attach over CDP to the
# same Chrome on 9222 anyway, so the isolation bought nothing. Listening on 8931 and pointing
# every session at `http://localhost:8931/mcp` collapses that to one process (70MB idle).
# same browser anyway, so the isolation bought nothing. Listening on a port and pointing every
# session at it collapses that to one process (70MB idle).
#
# Sessions still share the browser, so a parallel run must give each session its own tab
# (`browser_tabs {action:"new"}` before navigating) — verified: without it two sessions grab the
# same page and the second navigation wins.
#
# localhost-bound by the server itself, and it rejects any request whose Host is not
# `localhost:8931` (127.0.0.1 in the URL gets a 4xx — write the URL with localhost).
# The connection is lazy, so this stays cheap while nothing is on 9222 — the normal state.
# Chrome used to be what listened there; it was dropped on 2026-08-30 and Helium takes the
# role, started only for a job (see CLAUDE.md) and killed after.
# Binary comes from nixpkgs (0.0.69). It used to be the pnpm global install, but that store
# evaporated: both ~/Library/pnpm/bin/node and the @playwright/mcp package were symlinks into
# store paths that no longer exist, so the agent died with "node: not found" and then
# "Cannot find module .../cli.js". npm's latest is 0.0.79 — a patch ahead, which is a cheap
# price for not depending on a global store nothing declares.
# Lightpanda: the cheap CDP target for background work. 14MB resident against the 296MB a full
# Chromium takes (both measured here), so unlike that one this can just stay up — there is no
# "start it for the job and kill it after" dance. The heavy browser stays the exception, for
# the SPAs Lightpanda renders empty.
launchd.agents.lightpanda = {
enable = true;
config = {
ProgramArguments = [
"/run/current-system/sw/bin/lp"
"serve"
# 9333 ではない。あそこには別に立てたヘッドレス Chrome が既に居て
# (/json/version が Chrome/152 を返す)、Lightpanda が AddressInUse で
# 起動できなかった。9223 は 9222 の実 Chrome の隣という意味。
"--port"
"9223"
];
RunAtLoad = true;
KeepAlive = true;
ProcessType = "Background";
StandardErrorPath = "/tmp/lightpanda.err";
StandardOutPath = "/tmp/lightpanda.log";
};
};

# Second playwright-mcp, pointed at Lightpanda instead of Chrome. Two instances rather than
# switching one: the Chrome-backed 8931 keeps working exactly as before, and the caller picks
# a port instead of a mode. Both connect lazily, so an idle one costs nothing.
# `localhost:8932` (127.0.0.1 in the URL gets a 4xx — write the URL with localhost).
#
# There used to be a second instance on 8931 aimed at 9222, for whatever full browser was
# started for a job. It went away with Chrome: terminal-browser took that role and cannot be
# driven this way — it does expose CDP, but on a port that changes every launch (measured
# 53218 → 53337), so no static --cdp-endpoint can find it. It is driven by its own
# `terminal-browser action` instead. Binary comes from nixpkgs (0.0.69).
# Playwright MCP, pointed at Lightpanda. Connects lazily, so it costs nothing while idle.
launchd.agents.playwright-mcp-light = {
enable = true;
config = {
Expand All @@ -120,38 +92,6 @@ in
};
};

launchd.agents.playwright-mcp = {
enable = true;
config = {
ProgramArguments = [
"${lib.getExe pkgs.playwright-mcp}"
"--cdp-endpoint"
"http://127.0.0.1:9222"
"--port"
"8931"
"--output-dir"
"${config.home.homeDirectory}/tmp/playwright-mcp"
];
EnvironmentVariables.PATH = mcpPath;
RunAtLoad = true;
KeepAlive = true;
ProcessType = "Background";
StandardErrorPath = "/tmp/playwright-mcp.err";
StandardOutPath = "/tmp/playwright-mcp.log";
};
};

# Shell-independent env distribution: GUI apps / processes under launchd don't go through
# zsh's .zshenv (hm-session-vars.sh is effectively only read by zsh), so they receive none of
# home.sessionVariables' env. The most notable case is the accident where an unset GNUPGHOME makes
# gpg regenerate an empty ~/.gnupg, but EDITOR/PAGER/various telemetry opt-outs/XDG bases/CARGO_HOME etc.
# are also missed on the GUI side. At login, push them into the whole session via launchctl setenv to cut the zsh dependency.
#
# Auto-generated from home.sessionVariables rather than hardcoded (single source, drift prevention).
# Values are made safe with escapeShellArg (handles values with spaces/quotes like MANPAGER).
# Variables whose value contains "$" (e.g. the TERMINFO_DIRS that home-manager injects,
# "...:$TERMINFO_DIRS${TERMINFO_DIRS:+:}...") assume shell expansion at export time and
# break under the non-expanding launchctl setenv (a literal $ gets in), so they're excluded and left to zsh.
launchd.agents.session-env = {
enable = true;
config = {
Expand Down
Loading