foyer-cli
v0.1.0
Published
Ask your project directories questions from anywhere — no cd, no TUI, nothing lost.
Maintainers
Readme
foyer
どこからでもプロジェクトディレクトリに質問できる — cd なし、TUI なし、結果は失われない。
大変な部分はもう済んでいるはずです: あなたのリポジトリには CLAUDE.md や AGENTS.md、
カスタム skill、MCP サーバー、調整済みの permission がある。でもそのセットアップを使う
には、毎回同じ儀式が要ります — cd で入り、対話セッションを立ち上げ、待ち、打ち込み、
ターミナルを閉じた瞬間に文脈を失う。
foyer は各ディレクトリを、一行の説明付きの名前付き door (玄関) として一度だけ登録 します。それ以降は:
$ foyer ask blog "RSS フィードはどこで、どのテンプレートから生成されている?"これが、そのディレクトリの中で — フル構成が効いた状態で — あなたのエージェント CLI (現在は Claude Code) を、どこからでも実行します。回答はライブでストリームされ、すべての 実行はディスクに記録されるので、ターミナルやブラウザタブを閉じても結果は失われません。 ローカルの web GUI は、同じカタログ・同じ履歴・同じライブストリームを表示します。
foyer はフレームワークではなく玄関です: 頭脳はインストール済みのエージェント CLI、
状態は ~/.foyer の平文ファイル、サーバーは 127.0.0.1 にのみ bind し、何一つあなたの
マシンの外へ出ません。
クイックスタート
必要なもの: Node ≥ 20 と、インストール・ログイン済みの Claude Code CLI (claude)。
npm install -g foyer-cli
foyer doctor # node・claude・データディレクトリ・各 door のディレクトリを検査
foyer add ~/code/blog --desc "my blog — content, build, deploy"
foyer ask blog "このリポジトリは何をするもの?"add は検出結果を表示します — CLAUDE.md ✓ · .claude/ ✓ · .mcp.json ✓ — これこそが
要点です: door の先にあるのは素のエージェントではなく、あなたのセットアップです。
インストールする価値のある部分
回答の途中で Ctrl-C を押してみてください:
^C
still running — foyer watch 01j8zc… · foyer cancel 01j8zc…実行を所有しているのはあなたのターミナルではなく、小さな独立 (detached) プロセスです。
席を離れて、戻ってきて、foyer last — 回答はそこにあります。これが設計の不変条件 #1:
すべての ask は、必ずちょうど 1 つの永続化された結果で終わる。開始したクライアントに
何が起きても、です。
会話 (セッション)
foyer ask blog -s seo "記事タイトルを監査して"
foyer ask blog -s seo "ワースト 3 を直して" # 同じ会話の続き
foyer sessions blog名前付きセッションは、同一のエージェント会話を継続します (--resume の連鎖は foyer が
面倒を見ます)。セッションごとに同時 1 ターンのみ — 並行する 2 つ目の ask は明快な
"busy" で拒否され、黙ってキューに積まれることはありません。
GUI
foyer serve --open # http://127.0.0.1:4664、loopback のみ同じカタログ、同じ履歴、同じライブストリーム — ストアは 1 つ、フロントエンドは 2 つ。 ターミナルで ask を開始し、ブラウザでストリームを眺める。実行中にリロードしても ストリームは最初から再生されます。GUI には mode の操作がありません: GUI から追加された door はマシンの既定スタンスになり、それはあなた自身が常置同意を書いていない限り safe です (Permissions 参照 — 書いてある場合はフォーム上に その旨が表示されます)。
他のエージェントから foyer を呼ぶ
呼び出し側は人間でなくても構いません。すべてのコマンドは headless で機械可読なので、 エージェントセッション — 例えばメインの作業ディレクトリで動いているもの — は、他の プロジェクトディレクトリを「探索しに行く場所」ではなく「呼び出せる専門家」として扱えます。 各 door は自分自身の構成が効いた状態で答えるので、呼び出し側のエージェントはシンプルな まま、調整済みセットアップたちがドメインの仕事をします。
呼び出し側エージェントの CLAUDE.md / AGENTS.md にこんなメモを置いてください:
## Doors to other projects
Other project directories on this machine are registered as foyer doors, each
running its own tuned agent setup (CLAUDE.md, skills, MCP servers, permissions).
- `foyer ls --json` — the catalog: door names, descriptions, busy state
- `foyer ask <door> "question" --json` — one turn inside that project; prints
the run's result record (`status`, `answer`, `permissionDenials`, and
`usage` when the runner reported it)
- Continue a thread with `-s <topic>`. Exit code 3 means that session is busy —
come back later rather than waiting.プログラムが依存してよい契約: stdout には回答のみ (--json なら 1 つの JSON オブジェクト)
が流れ、進捗は stderr へ; 終了コードは 0 ok · 1 実行失敗 · 2 使い方エラー ·
3 busy · 4 detached; busy なセッションは正直な拒否であり、黙ったキューイングでは
ありません。
コードを書く上で押さえておきたい 2 点: usage は runner が報告したときだけレコードに
載ります (失敗・キャンセルされた実行には無い)。また読み取り系の last / show は
レコードが見つかりさえすれば 0 で終了します — 実行そのものの判定はレコードの status
フィールドであり、終了コードがそれを反映するのは watch です。
これが活きる習慣が 1 つ: 各 door の --desc はラベルではなく、読むエージェントのために
書くこと — "blog — content, build, deploy; knows the publishing pipeline" の方が
"my blog" よりも質問を正しくルーティングします。
コマンドリファレンス
| コマンド | 何をするか |
|---|---|
| foyer add <path> [--name n] [--desc "…"] [--model m] [--effort e] [--mode safe\|full] [--timeout 10m] [--yes] [--force] | ディレクトリを door として登録 |
| foyer ls | カタログ: door・説明・busy 状態 |
| foyer ask <door> "…" [-s session] [--model m] [--effort e] [--timeout 10m] [--json] [--quiet] | 1 ターン実行; ライブでストリーム; Ctrl-C で detach |
| foyer watch [askId] | 実行中の run に再接続 (終了済みなら再生) |
| foyer last [door] | 最新の結果 |
| foyer history <door> [-s session] [-n 20] | 実行レコード、新しい順 |
| foyer show <askId> [--events] | レコード 1 件の全体; --events で生ストリームを出力 |
| foyer cancel <askId> | 実行中のターンを停止 (canceled として確定) |
| foyer sessions <door> / sessions rm <door> <s> | 会話の一覧 / 削除 |
| foyer rm <door> [--force] / foyer set <door> k=v… [--yes] | 登録解除 / 設定変更 |
| foyer status | 実行中の run、サーバーの状態 |
| foyer serve [--port 4664] [--host h] [--open] [--unsafe-remote] | GUI + HTTP API を起動 |
| foyer doctor [--probe] | 環境チェック; --probe は実際に 1 往復して確認 |
出力がデータであるコマンドはすべて --json を受け付け、foyer <command> --help は
全フラグを例 2 つ付きで表示します。
パイプは期待どおりに動きます: git diff | foyer ask blog "危ない箇所ある?" は、
パイプ入力をフェンス付きのコンテキストブロックとして追記します。stdout には回答のみが
流れ、進捗やフッターは stderr へ行くので、> out.md はクリーンなままです。
Permissions: safe と full
headless なエージェント実行は対話的な permission プロンプトに答えられないため、各 door は 明示的なスタンスを持ちます:
- safe (既定) — foyer は permission フラグを一切追加せず、あなたの Claude Code 構成が
決めます: allowlist 済みのものはそのまま実行され、対話的な「allow?」が必要になるものは
拒否されます — headless 実行には尋ねる相手がいないからです。拒否は 1 件ずつ結果に記録
され (
permissionDenials、フッターの1 denied表示)、「なぜファイルを編集しなかったのか」 に答えが残ります。 - full — runner の permission バイパスフラグを渡します。full モードの door の登録は
警告を表示して確認を求めます; スクリプト内 (非 TTY) ではプロンプトが出ないため、登録には
明示的な
--yesが必要です。
full をマシン全体の既定にするには、~/.foyer/config.json に書きます:
{ "defaultMode": "full" }このファイルを作ること自体が常置同意です: 以降 foyer add (と foyer set … mode=full) は
警告はするがプロンプトは出さず、door ごとの --mode safe は引き続き優先されます。
fail-closed — ファイルが無い・壊れている・解釈できない場合は safe です。
HTTP API はこの同意に従うだけで、同意を作り出すことは決してできません: スタンス指定なしで
HTTP 経由登録された door は既定を継承し、HTTP 経由の permissions:"full" は config ファイル
が既にそう言っている場合にのみ受理されます — そうでなければ 400 です。つまり
config.json の無いマシンでは、HTTP と GUI からは safe な door しか生まれません; 常置同意の
あるマシンでは GUI からの追加はバイパス door になり、追加フォームにその場でそう表示されます。
既存 door のスタンス変更は CLI 専用の操作のままです (foyer set); HTTP はどちら向きでも
拒否します。
仕組み
~/.foyer/
registry.json カタログ (fail-closed: 登録済みディレクトリのみ到達可能)
server.json 稼働中サーバーの参考記録 (pid, port)
doors/<name>/
door.json この door の状態がどのディレクトリのものか
ledger.jsonl 追記専用の start/result レコード — 真実の源
events/<askId>.ndjson ライブストリーム、いつでも再生可能
runs/ run ごとの request / ack / heartbeat レコード
sessions.json 名前付きセッションごとの会話 id 連鎖
locks/<session>.lock セッションごとに同時 1 ターン状態は名前ではなくディレクトリに属します: door 名を別のパスに付け替えると、旧ファイルは
doors/<name>.moved-<stamp>/ にアーカイブされ (削除ではなく保存)、クリーンな状態で始まる
ので、新しいプロジェクトが別プロジェクトの会話を継承することはありません。
各 ask は、その run を所有する小さな独立 pump プロセスを起動します: door のディレクトリ
で runner の子プロセスを起動し、正規化されたイベントを記録し、期限を強制し (プロセス
グループ全体への SIGTERM → SIGKILL)、ちょうど 1 つの結果レコードを書き、ロックを解放
します — この順序で。CLI と GUI はこれらのファイルを tail しているだけです。デーモンは
存在しません; どの foyer プロセスからでも任意の run を観測でき、孤児 (電源断、kill -9)
は sweeper がストリーム済みの部分テキストとともに lost として確定します。注意点が 1 つ:
イベントジャーナル — およびそれをダンプする foyer show --events — は正規化イベントと
並んで runner の生ストリームを保持しており、そこにはモデルの推論や hook の出力が含まれ
うるため、ダンプは機密として扱い、公開のバグレポートに貼らないでください。
runner は小さなインターフェース (buildCommand + ストリームパーサー) です。このリリース
には claude (Claude Code headless: stream-json イベント、--resume 連鎖、denial 捕捉)
と、テストスイートが使う決定論的な fake runner が同梱されています。別のエージェント CLI
の追加は、runner ファイル 1 つと登録 1 行です。
設計メモ
いくつかのルールが「nothing lost」の約束を支えており、過剰に慎重に見えるかもしれない 挙動の理由でもあります:
- すべての ask はちょうど 1 つの永続化された結果で終わる。 runner が喋る前に start レコードが記録され、すべての終了経路 — ok・error・timeout・canceled・crash — は 1 つの 結果レコードに収束します。読み手は ask ごとに最初の結果を採り、後続の重複は無視します。
- run が
lostとして確定されるのは、所有する pump の死が証明されたときだけ — pid が 消えている、またはレコードがマシンの最終 boot より古い場合。期限超過だけでは決して証拠に なりません: スリープ中のラップトップや一時停止中のプロセスは遅れて見えてもまだ完走しうる し、時計だけで断罪すると 1 つの ask に 2 つの結果が生まれます。停止した run は報告される のであって kill されません; run を終わらせる意図的な手段がfoyer cancelです。 - セッションごとに同時 1 ターン。 排他的に作成され compare-and-delete で解放される ロックファイルが強制します: ロックは、それを取得した run をまだ指している間にのみ削除 されるので、自分の所有でないロックを落とせるプロセスは存在しません。
- レジストリは fail-closed。 foyer から到達できるのは登録済みディレクトリだけで、door を削除すると即座に見えなくなります。
- 台帳は追記専用; スナップショットはアトミック。 結果行はセッション連鎖・ロック解放の 前に fsync され、JSON スナップショットは一時ファイルに書いて fsync してから rename で 設置されます — クラッシュしても残るのは旧ファイルであり、新ファイルの半分ではありません。
- 壊れた台帳行は捨てる。決して推測しない。 読み手は千切れた末尾行を許容し、パースでき ないものはスキップして件数を声に出して数えます — 壊れた行が何を言っていたかを再構築 したりはしません。
- 自分のものだと証明できない相手にシグナルは送らない。
foyer cancelと孤児 reaper は どちらも、シグナル送信前にその run 自身の heartbeat がまだその pid を指していることを 確認します: 再利用された pid は他人のプロセスであり、他人への SIGTERM は取り消せません。
やらないこと (このリリース)
キューイングなし (busy は正直な拒否) · 認証 / マルチユーザー / リモートアクセスなし
(loopback のみ; --unsafe-remote は存在し、起動時に警告し、責任はあなたにあります) ·
対話的 permission の中継なし · ゴールループやスケジューリングなし · データベースなし ·
テレメトリなし、foyer 自身のネットワーク通信は一切なし。
macOS と Linux はテスト済み; Windows はベストエフォートです — cancel や timeout が runner
のプロセスツリー全体にシグナルを送れないため、頑固な孫プロセスが run より長生きすることが
あります。$FOYER_HOME はローカルファイルシステムに置いてください: 追記専用の台帳は
O_APPEND 書き込みが交錯しないことに依存しており、ネットワークファイルシステムはそれを
保証しません。
コントリビュート
ビルド/テストのループと、変更が守るべき不変条件は CONTRIBUTING.md に; CLAUDE.md は
同じ地図をこのリポジトリで作業する AI エージェント向けに書いたものです。セキュリティ報告
は SECURITY.md の手順で — 公開 issue ではなく、非公開で。
In English
foyer registers agent-ready project directories (with their CLAUDE.md, skills, MCP
servers, and tuned permissions) as named doors, and lets you ask them questions from
anywhere — CLI, a local web GUI, or a loopback HTTP API. Every run is owned by a small
detached process, so a closed terminal never loses an answer, and every result is
journaled exactly once to a local ledger. Hand the same commands to your main agent and
your registered directories become callable specialists. Everything stays on your machine.
License
MIT
