@tyhld/conductor
v0.12.138
Published
采配くん管制の見守りアプリ(conductor-agent)。各PCで常駐し、中央(devlog-tracker)へ定期的に生存報告(heartbeat)を送る常駐CLI。
Readme
@tyhld/conductor(見守りアプリ / conductor-agent)
采配くん管制の「見守りアプリ」です。 各PCで動かしておくと、自分がいま作業中であること(生存報告 / heartbeat) を、 30秒ごとに中央サーバー(devlog-tracker)へ自動で送り続けます。
中央の管制画面では、どのPCがどの現場(リポジトリ)で、どのブランチを、 作業中(working)か待機中(idle)かが一覧で見えるようになります。
仕組みはシンプルです。あなたのPCから中央へ「生きてます」という小さな合図を 定期的に送るだけ。難しい設定は要りません。
これは何をするもの?
conductor startを実行すると、その場で常駐します。- 起動した瞬間に1回、そのあとは 30秒ごと(変更可)に中央へ報告します。
- 報告する内容: PC名 / 現場名(リポ名) / ブランチ名 / 状態(working/idle) / 直近の作業メモ。
- 止めたいときは Ctrl+C を押すだけです。
現場名(site)とブランチ名は、コマンドを実行したフォルダの git 情報から自動で判定します。 git のフォルダでなければ、現在のフォルダ名を現場名として使います。
必要なもの
- Node.js 18以上(
node -vで確認できます)
導入(他PC向け / git clone 方式)
メンバーの各PCで conductor を導入・起動する手順です。3人規模では git clone での配布で十分なので、現段階では GitHub Packages への publish は行いません(人数が増えたら publish 配布を再検討します)。
前提
- Node.js 18以上(
node -vで確認) - git
- 対象リポジトリへのアクセス権(GitHub
tyhldorg)
手順
# 1) clone(URL は conductor リポジトリのもの)
git clone https://github.com/tyhld/conductor.git
cd conductor
# 2) 依存インストール
npm install
# 3) ビルド(tsc で dist/ を生成)
npm run build各自で設定する環境変数
| 環境変数 | 値 | 備考 |
|----------|----|------|
| CONDUCTOR_URL | https://<your-conductor-host>(中央) | 接続先 |
| CONDUCTOR_TOKEN | Bitwarden から取得 | テナントトークン。値はここに書きません |
| CONDUCTOR_MACHINE | 任意 | PC識別名。未設定なら os.hostname() |
🔑 鍵(
CONDUCTOR_TOKEN)は Bitwarden の共有項目から取得してください。 コード・リポジトリ・この README には値を一切書きません。
設定して起動する例:
export CONDUCTOR_URL=https://<your-conductor-host>
read -s -p 'tenant token: ' CONDUCTOR_TOKEN && export CONDUCTOR_TOKEN
node dist/cli.js start # もしくは scripts/conductor-start.sh(後述)補足: 配布は公開 npm(
@tyhld/conductor・取得先https://registry.npmjs.org)1本です。各PCの常駐は、入っている配布物(npm install -gで入れた物)だけを動かし、clone した作業ツリーは動かしません(scripts/dist-source.sh)。
- 自動更新が入っているPC(自社PC): 版は更新係(
tyhld-autoupdate)が、管制の決めた版へ段階的にそろえます(先行機 → 自己診断 PASS → 残りのPCへ1台ずつ)。人がnpm install -gで入れると順番が崩れるので打ちません。入れ方は下の「自社PCの自動更新」。- 自動更新が入っていないPC(お客さまのPC・入れる前の機): 人が
npm install -g @tyhld/conductor --@tyhld:registry=https://registry.npmjs.orgで入れ直します。自社PCの自動更新(★自社PCだけ・人が更新の窓で1回)
bash "$(npm root -g)/@tyhld/conductor/scripts/autoupdate-install.sh" # まず確認だけ(書き込みなし) APPLY=1 bash "$(npm root -g)/@tyhld/conductor/scripts/autoupdate-install.sh" # 入れる ~/.tyhld/bin/tyhld-selfcheck --now # 自己診断を1回★更新の窓で打つ行(ノートPC → メインPC → 社員PC・Ubuntu と PowerShell・再起動後の確かめ・止まったときの読み方)は1枚にまとめてあります:
docs/自社PC-更新の窓-手順書.md。仕組みと決まりは ADR-027。鍵(
~/.tyhld/bin/autoupdate/enabled)・更新係と自己診断のタイマー・Claude Code 自身の自動更新を止める設定(DISABLE_AUTOUPDATER=1)を置きます。Claude Code が公式推奨の1方式(curl -fsSL https://claude.ai/install.sh | bash)だけになっていなければ、人が打つ行を出して止まります。お客さまの導入台本(sales-template/install.sh・setup.sh)には入っていません。
新PCの初回セットアップ(ブラウザ検品の道具)★人が1回だけ流す
職人(Claude Code)がブラウザ検品(Playwright)を行うPCでは、最初に1回だけこの2本を流してください。
流していないPCでは、職人が検品のたびに端末へ「サンドボックス外通信の許可(Host: cdn.playwright.dev)」を出して止まります。
この問いは番人を通らないため管制のカードに出せず、端末でしか押せません(→ 便が人待ちになる。経緯は ADR-010)。
# A) 共有ブラウザ … PCに1回だけ Chromium と playwright を入れる(職人は毎便ダウンロードしなくなる)
bash scripts/pw-browser-install.sh # まず予定を表示(書き込みなし)
APPLY=1 bash scripts/pw-browser-install.sh # 実際に入れる
# B) 通信許可(保険) … それでも取得が要るときのため、各現場へ許可ドメインを配る
bash scripts/update-allowed-domains.sh # まず予定を表示(書き込みなし)
APPLY=1 bash scripts/update-allowed-domains.sh # 実際に配る- A は何を置くか:
$HOME/.tyhld/pw-venv(版を固定した playwright)と$HOME/.tyhld/pw-browsers(Chromium の実体)。 番人の$HOME/.tyhld/hooksと同じ「1台に1箇所」の流儀です。入れた後に実行ファイルの実在・サンドボックス内から読めるか・実際に起動するかを実測して表示します。 - 職人は自分で入れません。
$HOME/.tyhldはサンドボックスの中から読めるが書けないので、道具が無ければ職人はそこで止まり「人の手番」と報告します(鉄則8)。 - B は他を触りません。
sandbox.network.allowedDomainsに不足分を追記するだけで、permissions(deny/ask/allow)・hooksは1文字も変えません。scripts/update-guard.sh(全部入り)を流す場合は同じ一覧が同時に配られるので、B は不要です。 - ★2026-09-03(甲)以降、外への通信は全部通します(
allowedDomains: ["*"])。この問いは管制の3ボタンでは押せないと実測で確定したため(Claude Code 2.1.258 本体の作り)、聞かれる前に通して職人を自走させます。- 配るのは
APPLY=1 bash scripts/update-guard.shです(①-e3がホームの~/.claude/settings.jsonへ1か所だけ配る)。B はsandboxを持つ現場しか対象にしないので、B だけでは届かない現場があります。 - 緩むのは「電話をかける」だけです。秘密の持ち出し(
.env/~/.ssh/$DATABASE_URLなど)を止める番人は別の仕組みで、1ミリも緩みません。本番DBへの直結禁止(deniedDomains)も"*"より先に効くため、そのまま閉じたままです。
- 配るのは
- 鉄則(職人の作業規律)を直したときは
APPLY=1 bash scripts/update-guard.shを流してください。 稼働中の機体では~/.claude/skills/conductor-craftsman/SKILL.mdが古い写しのまま残るため、 これを流さないと職人の動きは変わりません(update-guard.sh ①-d。同一なら何もしません)。 setup.shからは呼びません。顧客機の初期設置に大きなダウンロードを足すと設置そのものが止まるためです(ADR-010)。
職人が始まるのに要る物(先頭の確かめ・0.12.82〜)
1行インストーラ(sales-template/install.sh)と scripts/update-guard.sh は、設定を配る前に次を確かめ、足りなければ止まって「人が打つ1行」を出します。★台本は自分で sudo を打たず、何も入れません。
- 職人のソフト(Claude Code)が 2.1.282 以上(えふさん確定 2026-10-02)。中継と同じ場所を同じ順で探し、いちばん新しい版で判定します。上げ方は入れ方で違います。
- ネイティブ導入(
~/.local/bin/claude)…~/.local/bin/claude update - npm 導入(nvm の bin)…
npm install -g @anthropic-ai/claude-code@latest
- ネイティブ導入(
- 囲いの部品(Linux・WSL):
bwrap(bubblewrap)とsocat。無いと職人が安全のため起動を断ります(設定sandbox.failIfUnavailable)。sudo apt-get install -y bubblewrap socat- sudo のパスワードが分からないとき(WSL・Windows の PowerShell で):
wsl.exe -u root apt-get install -y bubblewrap socat - Mac は囲いが OS に組み込みなので求めません。分からない機は止めずに「確かめられませんでした」と出します。
- 確認だけ(
bash scripts/update-guard.sh)のときは、職人が管制へ知らせる口(ai-kana の MCP)がつながっているかも1行で出します(止めません)。
使い方
最低限、次の2つの環境変数を渡して conductor start を実行します。
CONDUCTOR_URL=https://<your-conductor-host> \
CONDUCTOR_TOKEN=(テナントトークン) \
conductor start実行すると、こんな1行ログが定期的に流れます。
[conductor] 開始: machine=my-pc site=kenzokun status=working interval=30s url=https://...
[conductor] 10:00:00 site=kenzokun status=working -> HTTP 200 OK
[conductor] 10:00:30 site=kenzokun status=working -> HTTP 200 OK止めるときは Ctrl+C。
よく使うオプション
# 現場名を手動で指定したいとき
conductor start -s kenzokun
# 待機中として報告したいとき
conductor start -t idle
# 直近の作業メモを添えたいとき
conductor start -a "ログイン画面を実装中"| 引数 | 意味 | 既定値 |
|------|------|--------|
| -s <site> | 現場名(リポ名) | git リポ名 → 無ければカレントフォルダ名 |
| -t <idle\|working> | 状態 | working |
| -a <lastAction> | 直近の作業メモ(自由文) | heartbeat |
環境変数一覧
| 環境変数 | 必須 | 意味 | 既定値 |
|----------|------|------|--------|
| CONDUCTOR_URL | ✅ | 中央(devlog-tracker)のベースURL。例: https://<your-conductor-host> | (なし) |
| CONDUCTOR_TOKEN | ✅ | テナントトークン。中央API側の同名 env と一致させます | (なし) |
| CONDUCTOR_MACHINE | – | PCの識別名 | os.hostname()(PCのホスト名) |
| CONDUCTOR_INTERVAL_SEC | – | heartbeat送信間隔(秒) | 30 |
| CONDUCTOR_RELAY_ENABLED | – | true で relay(指示の取得→アイドル時のみ tmux 送信→完了検出)を有効化 | false |
| CONDUCTOR_RELAY_INTERVAL_SEC | – | relay のポーリング間隔(秒) | 15 |
| CONDUCTOR_NUDGE_DIR | – | 耳 → relay の呼び鈴(空ファイル)の置き場 | $XDG_RUNTIME_DIR/conductor |
| RELAY_FETCH_TIMEOUT_MS | – | 管制への問い合わせ1回の上限(ミリ秒)。返らない通信で relay が固まらないための上限 | 20000 |
| RELAY_RECONNECT_BASE_MS | – | 管制へつながらないときの、最初の再試行までの待ち(ミリ秒)。以後 倍々に伸ばす | 5000 |
| RELAY_RECONNECT_MAX_MS | – | 再試行の待ちの上限(ミリ秒)。★上限に達したあとも永久に試し続けます | 60000 |
管制につながらないとき(★relay は止まりません):管制(中央)が落ちる・切れる・応答しない・ 401 や 5xx を返す——このいずれでも relay は止まらず待って再試行します。待ちは 5秒 → 10 → 20 → 40 → 60秒 と伸び、60秒で頭打ち(以後は60秒おきに永久に)。つながらない間は 画面に
管制につながらない・再試行中(N回目・次は◯秒後)を毎回1行出し続け(黙りません)、 つながったら管制に再接続しましたを1行出して通常の間隔(15秒)へ自動で戻り、 自分の生存報告を1回送り直します。安全のため、つながらない間は職人へ便を配りません (管制と話せないのに送るのは危険)。人が exit → 再起動する必要はありません。
relay(ADR-029 段階3 / 既定 off):
CONDUCTOR_RELAY_ENABLED=trueのときだけ、 中央から自分宛(machine+site)の指示を取得し、送信先のtmuxセッション(名前 = 現場名 site)が 完全にアイドルのときだけ 指示を流し込みます。少しでも作業中っぽければ送らず次回に再挑戦する 保守的な判定(「誤爆より送り遅れ」)です。off の間は従来どおり heartbeat のみで挙動は変わりません。
指示の着信合図(ADR-066):relay 有効時は、上のポーリングに加えて、耳(下記)から届く 「今すぐ1周して」の呼び鈴でも動きます。指示が積まれてから職人へ届くまでの最大15秒の待ちが消えます。 呼び鈴は空ファイルの touch で、指示本文もトークンも載りません。指示の中身は従来どおり 既存の取得APIから取り直します。耳が動いていなくても、relay は上記の間隔のポーリングで これまでどおり完全に動きます(耳は加速装置であって依存先ではありません)。
耳(conductor ear)— このPCに1本の合図受け取り役
conductor ear は、中央からの「指示が積まれた」合図(Supabase Realtime)を PCにつき1本 の接続で
受け取り、その現場の relay(conductor@<現場>)へ「今すぐ1周して」とだけ伝える常駐です。
接続本数がソフト数ではなく PC数 で決まる形にするための集約役です(ADR-066 便2c)。
| | conductor@<現場> | conductor-ear | |---|---|---| | 本数 | 現場ごとに1本 | PCごとに1本 | | 役目 | heartbeat + relay(指示の配達・完了検出) | 合図を受けて relay へ呼び鈴を送るだけ | | 職人(tmux)への書き込み | する | しない(指示も取らない) |
- 耳 → relay の呼び鈴は、置き場(既定
$XDG_RUNTIME_DIR/conductor、CONDUCTOR_NUDGE_DIRで変更可)に 置かれた 空ファイル<現場名>.nudgeを書き直すだけ。relay は起動時に自分のファイルを作り (=「この現場が動いている」の名簿にもなります)、フォルダを見張ります。 - このPCで動いていない現場宛ての合図は、渡さずログだけ残します。
- 接続先URL・公開鍵・購読設定は中央の
POST /api/conductor/realtime-tokenの応答から受け取るため、 追加の環境変数はありません(必要なのはCONDUCTOR_URL/CONDUCTOR_TOKENだけ)。 - 切断は自動で張り直し(5秒→10秒→…最大5分)、トークンは失効前に取り直します。
- Node の版要件は上がりません(18以上のまま)。合図の通信に使う WebSocket は、実行環境に標準品が
あればそれを、無ければ同梱の
wsを使います(Node に標準の WebSocket が入ったのは 22 からで、 20 以下ではNode.js detected but native WebSocket not found.で耳が張れませんでした)。 お客さま機の Node を上げていただく必要はありません。 - 中央にRealtimeが未設定・
@supabase/supabase-js未インストール・耳を止めている——いずれの場合も relay は15秒ポーリングでそのまま動きます。
導入(ADR-066 便2e で自動化済み)
耳はどちらの導入経路でも自動で入ります。人が手でユニットを置く必要はありません。
| 機 | 入り方 |
|---|---|
| お客さま機 | bash setup.sh <現場名> の中で自動(⑥ systemd の段) |
| 自社機 | bash scripts/systemd-install.sh の中で自動 |
| 手動で入れ直す | bash systemd/ear-install.sh |
| 前面で試す | bash scripts/ear-start.sh |
systemctl --user status conductor-ear # 「合図の受け取りを開始しました」が出れば成功
journalctl --user -u conductor-ear -f # ログ
# やめる(relay は15秒ポーリングでそのまま動きます)
systemctl --user disable --now conductor-ear- ユニットの正本は
systemd/conductor-ear.serviceの1つだけ。ExecStartは配置時にsystemd/ear-install.shが「その機で実際に動くコマンド」へ置換します(お客さま機は npm のconductor、自社機はリポジトリのdist/cli.js)。そのままコピーしても動きません。 - 耳が入らない機(systemd が無い・コマンドが見つからない等)でも、セットアップは成功扱いで進みます。 警告を残して先へ進み、relay は15秒ポーリングで従来どおり動きます。
台帳と掃除の順序(便2c の申し送り・便2e で解消):
setup.shの古いユニット掃除は 「台帳に載っている@無しのconductor-*.service」を撤去します。conductor-ear.serviceは その綴りに当たるため、掃除の例外(CURRENT_MACHINE_UNITS)に先に入れてから台帳へ載せています。 順序が崩れると「自分で入れた耳を次回セットアップで自分で消す」壊れ方をするので、test/setup-ear-order.test.tsが順序と振る舞いを固定しています。
司令塔(conductor build-runner)— このPCに1本の試作の段取り役
conductor build-runner は、お客さまが管制で「この内容で試作する →」を押した設計書を拾い、
便(作業指示)に分けて管制へ1本ずつ積み、終わるまで見届ける常駐です(試作完走 便⑥)。
ねらいは「押したあと、人を止めない」こと。
| | conductor@<現場> | conductor-ear | conductor-build-runner | |---|---|---|---| | 本数 | 現場ごとに1本 | PCごとに1本 | PCごとに1本 | | 役目 | 指示の配達・完了検出 | 合図の前倒し | 指示を作って積む | | 職人(tmux)への書き込み | する | しない | しない |
流れ
① 探す GET /api/proto/specs?status=approved&built=false (60秒ごと+合図があれば即)
② 計画 会議(ai-roundtable)に便の一覧を起草させる
★返らなければ設計書(画面・データ・機能)から機械的に組む
③ 書く POST /api/proto/records collection='_build' kind='plan'
★計画を書いた時点で①から外れる=同じ試作を二度始めない
④ 積む POST /api/conductor/commands/enqueue(gate=white・現場 ai-kana-proto)
★同時1本。前の便が終わるまで次を積まない
⑤ 待つ GET /commands/done(結末・報告)/GET /commands/:id(時間切れ等)//commands/orphans
⑥ 書く POST /api/proto/records kind='progress'(何本目・終わった数・結末・URL・通った数)
⑦ 直す 詰まったら同じ便を積み直す(最大3回)。超えたら止めて人へ回す
⑧ 締め 公開の便の報告の `URL:` から触れる場所、動作確認の便の `ACCEPTANCE: n/m` から通った数- 進み具合の正は中央(
'_build'に書いたもの)。この常駐が手元に持つのは 「いまどの試作の何本目を待っているか」だけで、置き場は~/.local/state/conductor/build-runner.json(600)。 - 落ちても続きから。起動時に
GET /api/proto/builds/:projectIdで計画と進み具合を読み直します。 手元の覚え書きが消えていても、積む前に必ず管制へ確かめるので同じ便を二度積みません。 - 会議が使えなくても止まりません(設計書から組み立てて進みます)。
- 記録(
journalctl)には型番も鍵も出しません(redactが伏せます)。
導入(★人が明示的に入れます)
お客さま機の setup.sh からは入りません。試作を回す機だけに人が入れます。
# ~/.conductor.env に4つ足す(値は弊社からお渡しします)
# PROTO_API_BASE=https://ai-kana.ty-hld.com
# CONDUCTOR_PROTO_APP_KEY=… ★司令塔専用の合言葉(試作アプリのものとは別)
# ROUNDTABLE_BASE_URL=…
# ROUNDTABLE_APP_KEY=…
bash systemd/build-runner-install.sh
journalctl --user -u conductor-build-runner -f
systemctl --user disable --now conductor-build-runner # やめる- 4つが揃っていなければ設置台本が入れずに止まり、起動しても何が足りないかを言って止まります (黙って動かないのがいちばん困るため)。
- 配備先は Vercel の試作プロジェクト(人が
vercel.com/newで作り、mainへの push で自動配備)。 用意されていなければ公開の便がその旨を報告し、司令塔は「公開先の準備が要ります」で止めて人へ回します。 - ユニットの正本は
systemd/conductor-build-runner.serviceの1つだけ(耳と同じ作法)。setup.shの掃除の例外(CURRENT_MACHINE_UNITS)にも入れてあります。
CI(GitHub Actions)
main への push と、main を base とする pull request で .github/workflows/ci.yml が回ります。
ジョブは test の1本だけで、次を順に実行します(Node 22 / ubuntu-latest)。
| ステップ | コマンド | 見ているもの |
|----------|---------|-------------|
| Build | npm run build | TypeScript がコンパイルできるか(tsc) |
| Test | npm test | relay 等の単体テスト(node --test) |
| Hook test | python3 scripts/hooks/test_policy.py | 番人(policy.py / cc2_guard.py)の deny/allow/defer 判定 |
| Shell syntax | bash -n(scripts と sales-template の *.sh) | シェルスクリプトの構文エラー |
| Shell static analysis | shellcheck -x --severity=warning(同じ *.sh) | 未定義変数・クォート漏れ・空変数展開など bash -n が見ない欠陥 |
ローカルで同じチェックを回すには:
npm run build
npm test
python3 scripts/hooks/test_policy.py
for f in scripts/*.sh sales-template/*.sh sales-template/systemd/*.sh; do bash -n "$f"; done
shellcheck -x --severity=warning scripts/*.sh sales-template/*.sh sales-template/systemd/*.shlint / format は
package.jsonの scripts に未定義のため CI に載せていません。shellcheckはローカルには未導入です(CI の ubuntu-latest には標準搭載)。手元で回すならsudo apt install shellcheck等で入れてください。
Node のバージョン要件(実行と開発で意図的に違う)
| 要件 | バージョン | どこで表明しているか |
|------|-----------|---------------------|
| 実行要件(常駐を動かす) | Node 18 以上 | package.json の engines.node、sales-template/setup.sh の前提チェック、上の「必要なもの」 |
| 開発・テスト要件(npm test を回す) | Node 22 以上 | .nvmrc(22)、CI の node-version: 22 |
- 常駐(heartbeat / relay)が実行するのは
tscでコンパイル済みのdist/cli.jsなので Node 18 で動きます。実際、この開発機の systemd は/usr/bin/node= v20.20.0 で稼働しています。 - Node 22 が要るのは
npm testが--experimental-strip-typesで.tsを直接実行するときだけです。 - この2つを混同して
engines.nodeに開発要件(22)を書いてはいけません。 Node 18/20 の環境でnpm i -g @tyhld/conductorがEBADENGINE警告を出し、engine-strict=trueの環境では実行できるのにインストールが拒まれます。 - 開発時は
nvm use/fnm useが.nvmrcを読んで 22 に切り替えます。
配布(リリース)★人が npm publish を打つ手順はありません
配布先は公開 npm(registry.npmjs.org)1本です(えふさん決定 2026-09-04)。
やることは1つだけ
package.json の version を上げて main に入れる。 それだけです。
main への push で .github/workflows/release.yml が走り、次のように動きます。
| 判定 | 動き |
|---|---|
| version が公開 npm の latest より新しい | build → テスト → npm publish(公開 npm へ) |
| 同じ・古い | 何もしない(版を上げ忘れても事故りません) |
版を上げる必要があるのは、配布物を変えたときだけです(便 fb248f6e)。
配布物=package.json の files に載る物(README.md も含む)と、dist の元(src・tsconfig.json)、package.json・package-lock.json。
docs/・test/・.github/ だけの変更は版を上げずに緑で合流でき、合流しても公開は「出しません」で何も起きません。
配布物を変えたのに版がそのままだと、試験(test/release-public-npm.test.ts の「★空振り確認」)が赤で止めます。
くらべる相手は公開 npm の latest を出したコミット(gitHead)で、それが確かめられないときは安全側(版上げを求める)に倒れます。
★人が npm publish を打ってはいけません。
公開 npm には 0.3.0 しか出ておらず、0.5.0 は GitHub Packages(非公開) に出ていました。
手元で npm publish を打つと、そのPCの ~/.npmrc の @tyhld スコープ設定(GitHub Packages 向き)が効いてしまうためです。
その結果お客さまは公開 npm の 0.3.0 を掴みます。0.3.0 の中身は古い鍵の名前(CONDUCTOR_SHARED_SECRET)を読むので、いまの接続情報(CONDUCTOR_TOKEN)では「未設定です」で落ちます(migakia の設置で再現)。
根治は「人のPCの設定に依存しない場所から出す」こと。 CI から出せば、誰のPCの .npmrc も関係なくなります。
あわせて package.json に publishConfig(registry = 公開 npm / access = public)を明記してあります。publishConfig は publish のときだけ効き、.npmrc のスコープ設定より優先されます。取得側(他リポの @tyhld/* 部品)は一切変えていません。
どうやって名乗るか — Trusted Publishing(合言葉を持たない)
★合言葉(トークン)は使いません。 GitHub が発行する**身分証(OIDC)**で名乗ります。
npm 側に「この org のこのリポジトリのこのワークフローなら出してよい」とあらかじめ登録しておき、実行のたびに GitHub が短命の身分証を出します。
| なぜトークンをやめたか | |---| | npm の書き込みトークンは最長90日で切れる。切れるたびに人が入れ替える運用は続きません | | npm はトークンでの直接公開を 2027-01 に廃止予定(npm の画面に明記) | | トークンを作らない・置かない・配らない = 漏れて困るものが存在しない |
★証明書(
--provenance)は付けていません。 証明書は公開リポジトリからしか出せない仕組みで、 非公開のこの conductor ではE422(Only public source repositories are supported when publishing with provenance)で弾かれました(実測 2026-09-04・run 33785720505)。 conductor は非公開のままにするので、証明書のほうを外しています。 名乗り方(Trusted Publishing)はそのままで、認証はこれで通っています。
人の手番は1回だけ — npmjs に「出してよい相手」を登録する
https://www.npmjs.com/package/@tyhld/conductor → Settings → Trusted publisher → GitHub Actions で、次の5項目を入れて保存します。
| 入力欄 | 入れる値 |
|---|---|
| Publisher | GitHub Actions |
| Organization or user | tyhld |
| Repository | conductor |
| Workflow filename | release.yml |
| Environment name | (空のまま) |
★Workflow filename は
release.yml(パスではなくファイル名だけ)。ここが違うと、 npm は「知らないワークフローからの publish」として拒みます。 Release はこの詰まり方を見分けて、上の表と同じ値を画面に出します。
★**
NPM_TOKENは使いません。** GitHub Secrets に入っていても参照しません。 「トークンでも出せる」道を残すと、どちらの経路で出たのか分からなくなるためです。 (0.3.0 へのdeprecateだけは npm の画面から人が打ちます。下記)
mainに入ったあと、Actions → Release の実行を開く- 「公開 npm へ出す(Trusted Publishing)」 が緑になっていること(
出しました。が出ます) - https://www.npmjs.com/package/@tyhld/conductor を開き、版が上がっていること
黒い画面で確かめるなら、次の1行で latest が読めます。
curl -s https://registry.npmjs.org/@tyhld/conductor | jq -r '."dist-tags".latest'古い版に「使わないで」の印を付ける(1回だけ・人の手番)
公開 npm に残っている 0.3.0 は動きません。次の1行で印を付けてください(npm login 済みの端末で)。
npm deprecate @tyhld/[email protected] "古い版です。0.5.0 以上を使ってください"消す(
npm unpublish)のではなく deprecate(印を付ける)にします。消すと、その版を掴んでいる機体の再インストールが失敗するためです。
最低版
sales-template/install.sh と sales-template/setup.sh は MIN_CONDUCTOR_VERSION(この2か所だけ・テストで同一を固定)より古い本体を見つけると、更新を試し、それでも古ければ止まって案内します。
よくある質問
Q. オフライン(offline)は送らないの? A. 送りません。中央側は「最後の生存報告からの経過時間」でオフラインを判定する設計です。 そのため Ctrl+C で止めると、しばらく後に中央画面側で自動的にオフライン表示になります。
Q. 鍵(トークン)はどこに書くの? A. コードやリポジトリには絶対に書きません。 実行時に環境変数で渡してください。
職人(CC2)の起動手順 ★現行(2026-07-31 確定)
人が CC2(Claude Code)を起こすときの現行手順です。tmux で「箱」を作り、その中で職人を1コマンドで起動します。
① 箱(tmux)を作って入る
tmux new -s <現場名> -c ~/projects/<現場名> # 例: tmux new -s kenzokun -c ~/projects/kenzokunduplicate session と出たら既存の箱があります(=起動済み)。新しく作らず attach してください(並列禁止)。
tmux attach -t <現場名>② tmux の中で職人を起動する
unset ANTHROPIC_API_KEY && claude --permission-mode acceptEdits1コマンドで、最初から accept edits on の状態で立ち上がります(起動後に Shift+Tab で切り替える運用は不要になりました)。
relay の常駐を確認
systemctl --user is-active conductor@<現場名>.service
# active でなければ
systemctl --user enable --now conductor@<現場名>.service補足
- 画面下の緑の帯=いま tmux の中にいる印。
- 認証ダイアログは Esc で閉じる。
- 初回の
trust確認は Yes。 - 箱から抜けるのは Ctrl+b → d(デタッチ。職人はそのまま動き続けます)。
補助スクリプト: CC2のワンタッチ起動(scripts/cc2.sh)※旧手順(履歴)
現行の起動手順は上の「職人(CC2)の起動手順」です。 本節は経緯として残しています(スクリプト自体は削除していません)。
CC2(人が起動する Claude Code)の起動手順を1コマンドに畳んで、起動ミスを根治するためのスクリプトです。
「箱(tmux)が未起動なのに気づかず指示だけ待機」「cd 忘れ / unset 忘れ / 現場名のタイプミス / 二重起動」を構造的に防ぎます。
bash scripts/cc2.sh <現場名> # 例: bash scripts/cc2.sh kenzokun~/.bashrc に次の alias を足すと、cc2 kenzokun だけで起動できます(.bashrc の自動編集はしません。手動で追記してください):
alias cc2='bash ~/projects/conductor/scripts/cc2.sh'やること:
- 引数なし → usage といま起動できる現場名(
~/projectsにフォルダが在るもの)を表示して終了。 - フォルダが無い現場名 → **似た候補を提示(タイプミス検出)**してから一覧を表示。
- 二重起動防止: 同名 tmux セッションがあれば attach するだけ(並列禁止)。同じディレクトリで動く別名セッションがあれば軽く警告。
- 新規セッションは作業ディレクトリで作成 →
unset ANTHROPIC_API_KEY→claudeを起動 → attach。cd 忘れ・unset 忘れを構造的に不可能にします。
現場名の解決は
scripts/sites.shを単一の真実として再利用します(決め方を二重定義しない)。 ★2026-09-04 変更: 新しい現場はsites.shを直す必要がありません。 起動できるかどうかは~/projects/<現場名>がフォルダとして在るかだけで決まります(mkdir -p ~/projects/<名前>で足りる)。 以前はsites.shの配列に名前を書き足すまで「未知の現場名です」で止まっていました(新現場 migakia で実測)。sites.shの配列に残っているのは ①置き場が既定と違う現場の対応表(plus1→plus1-foryou、jichinavi→~/jichinavi)と ②一括処理(deploy-guard.sh/restart-all-cc2.sh)が回る名簿だけで、 起動可否には使いません。破壊的操作(tmux kill 等)はしません。
補助スクリプト: CC2の起動(scripts/cc2-start.sh)※旧手順(履歴)
現行の起動手順は上の「職人(CC2)の起動手順」です。 本節は経緯として残しています。
人が CC2(Claude Code)を起動するときの「tmux new → cd → unset ANTHROPIC_API_KEY → claude」を1コマンドにまとめた補助です(常駐/報告役とは無関係)。cc2.sh の前身で、タイプミス検出・同ディレクトリ警告は付いていません(基本動作は同じ)。
bash scripts/cc2-start.sh <現場名> # 例: bash scripts/cc2-start.sh kenzokun引数なし・フォルダが無い現場名で実行すると、いま起動できる現場名の一覧を表示します。同名の tmux セッションが既にあればそこへ接続し、無ければ作成して CC2 を起動します。
現場名の解決は
scripts/sites.shに集約し、cc2-start.sh/cc2.sh/conductor-start.shが source して共有します(重複を持たない)。 ★起動できるかは~/projects/<現場名>がフォルダとして在るかで決まります(名前の一覧では決めません)。
補助スクリプト: 報告役の常駐起動(scripts/conductor-start.sh)
報告役(heartbeat、必要なら relay)を1コマンドで起動する補助です。cc2-start.sh と同じ作法・同じ対応表を使います。
bash scripts/conductor-start.sh <現場名> [--relay]
# 例
bash scripts/conductor-start.sh kenzokun # heartbeat のみ
bash scripts/conductor-start.sh kenzokun --relay # heartbeat + relay- 第1引数=現場名(
sites.shの対応表から作業ディレクトリを解決)。 --relay指定時のみ relay(CONDUCTOR_RELAY_ENABLED=true)を有効化します。- 内部では現場のディレクトリへ
cdしてからnode dist/cli.js start -s <現場> -t working -a "<現場>稼働"を実行します(ブランチ名を現場の git から判定するため)。 CONDUCTOR_URL/CONDUCTOR_TOKENは呼び出し元のシェルに設定済み前提で、スクリプトに鍵は書きません。未設定なら案内を出して終了します。- 未知の現場名・引数なしのときは有効な現場名一覧を表示します。
これは 人がログイン後に1回叩く半自動 です。PC起動時の完全自動は次の「自動常駐」を使います。
relay を手動で起動する(テスト・手動運用 / scripts/relay-start.sh)
relay(指示を CC2 に流す役)を 1コマンドで手動起動する補助です。これまで毎回打っていた source ~/.conductor.env → 各種 export → node dist/cli.js start ... を1コマンドにまとめ、source した鍵が子プロセス(node)に渡らない「未設定エラー」事故も set -a で根治しています。
bash scripts/relay-start.sh <現場名> [作業ラベル]
# 例
bash scripts/relay-start.sh relaytest
bash scripts/relay-start.sh relaytest "スタートアップ調査"
# relay 間隔を短くしてテスト(既定 15秒)
CONDUCTOR_RELAY_INTERVAL_SEC=5 bash scripts/relay-start.sh relaytest- 鍵(
CONDUCTOR_URL/CONDUCTOR_TOKEN)は~/.conductor.envから自動読込します。スクリプトには鍵を書かず、画面にも値を出しません。鍵ファイルが無ければ案内して終了します。 CONDUCTOR_RELAY_ENABLED=trueを付けて relay を有効化します。間隔はCONDUCTOR_RELAY_INTERVAL_SEC(既定 15秒)で上書き可。- どのディレクトリから実行しても OK(スクリプト位置からリポルートを解決)。
dist/cli.jsが無ければnpm run buildを案内して終了します。 - 作業ラベル(第2引数)の既定は
relay手動起動です。
PC起動時の自動常駐(第1段 / scripts/autostart.sh)
リストに並べた現場を、それぞれ heartbeat のみ でバックグラウンド常駐させる、人手を介さない起動口です。WSL 上で bash scripts/autostart.sh を叩くと、リストの現場をまとめて常駐させます。
1) 鍵ファイル $HOME/.conductor.env を用意
鍵はスクリプトに書きません。各PCのホームに $HOME/.conductor.env を作り、そこから読み込みます。
cat > "$HOME/.conductor.env" <<'EOF'
CONDUCTOR_URL=https://<your-conductor-host>
CONDUCTOR_TOKEN=(Bitwarden から取得した値を貼る)
EOF
chmod 600 "$HOME/.conductor.env" # 自分だけ読める権限に🔑
CONDUCTOR_TOKENの値は Bitwarden の共有項目から取得してください。$HOME/.conductor.envは git 管理外(リポジトリには入れません)。このファイルが無い/鍵が空のときは案内を出して終了します。
2) 自動起動する現場リスト
「このPCで自動起動する現場」を1行1現場で並べます(# 始まりと空行は無視)。優先順位:
bash scripts/autostart.sh <リストファイル>の第1引数$HOME/.conductor.autostart.list(各PC個別・git管理外)scripts/autostart.list(同梱サンプル)
各PCでは $HOME/.conductor.autostart.list を置く(または同梱の scripts/autostart.list を編集する)運用です。
3) 起動と停止
bash scripts/autostart.sh # リストの現場を heartbeat のみで常駐起動
bash scripts/autostart-stop.sh # 自動常駐(node dist/cli.js start)をまとめて停止- 各現場のログは
$HOME/.conductor-logs/<現場>.logに追記されます。 - 既に同じ現場の自動常駐が動いていれば二重起動しません。未知の現場名は warn を出してスキップします(全体は止めません)。
- relay は有効化しません(heartbeat のみ)。relay が要るときは
conductor-start.sh --relayを使ってください。
停止スクリプトは
dist/cli.js startを含む node を対象にするため、conductor-start.shや手動起動の報告役も一緒に止まります。
補足
- Windows タスクスケジューラからの呼び出しは人間が別途設定します(このリポジトリでは設定しません)。
- 次段では、自動起動する現場の ②管制画面からのトグル化(ファイル編集なしで ON/OFF)を予定しています。
PC起動時の自動常駐(第2段 / systemd ユーザーサービス)
第1段(autostart.sh)を systemd ユーザーサービスに置き換えたものです。各現場を conductor@<現場>.service として登録し、Restart=always で「落ちても自動復活」 します。これが常用、旧 autostart.sh は 手動予備として温存します。
前提
- WSL で systemd が有効(
/etc/wsl.confの[boot] systemd=true、[user] default=fshim)。systemctl --userが使えること。 - 鍵ファイル
$HOME/.conductor.env(chmod 600/ git管理外)が用意済み。中身は第1段と同じ(CONDUCTOR_URL/CONDUCTOR_TOKEN)。鍵はリポジトリに入れません。 - conductor リポで
npm run build済み(dist/cli.jsが存在)。
導入
bash scripts/systemd-install.sh # リストの現場を登録+起動(enable --now)- ユニット
systemd/[email protected]を~/.config/systemd/user/にコピーし、daemon-reload。 - リストファイル(第1段と同じ優先順: 第1引数 →
$HOME/.conductor.autostart.list→scripts/autostart.list)の各現場をsystemctl --user enable --now conductor@<現場>。 loginctl enable-linger $USERを実行(ログオフ中・PC起動直後でもユーザーサービスが動くように)。通常 sudo は不要です。$HOME/.conductor.envが無ければ警告して中断します。- relay は有効化しません(heartbeat のみ)。
WorkingDirectory: 報告役はカレントの git からブランチを判定するため、現場ディレクトリで起動します。既定は
%h/projects/<現場>(現状は「現場名 = ディレクトリ名」で一致)。将来この2つが食い違う現場が出た場合は、install スクリプトがsites.shの対応表を見て drop-in(conductor@<現場>.service.d/override.conf)でWorkingDirectoryを上書きします。テンプレート本体は触りません。
状態確認
systemctl --user status 'conductor@*' # 全現場のサービス状態
systemctl --user list-units 'conductor@*' # 一覧
journalctl --user -u 'conductor@kenzokun' -f # 特定現場のログを追尾停止 / 無効化
bash scripts/systemd-uninstall.sh # 各現場を disable --now +ユニット削除+daemon-reload- linger は外しません(他用途の可能性があるため報告のみ)。完全に戻すときは手動で
loginctl disable-linger "$USER"。
補足
- 落ちても
Restart=always(RestartSec=5)で自動復活します。第1段のnohup &と違い、クラッシュ・OOM 後も systemd が起こし直します。 - 旧
scripts/autostart.shは 手動予備として温存(常用は systemd)。 - Windows タスクスケジューラは「WSL を起こす」役割に変更します(常駐の起動は systemd が担当)。例: ログオン時に
wsl.exe -d Ubuntu -u fshim true等で WSL を立ち上げるだけ。この変更は人間が別途設定します。 - 次段では、自動起動する現場の ②管制画面からのトグル化(ファイル編集なしで ON/OFF)を予定しています。
開発
npm install
npm run build # tsc で dist/ を生成
npm start # = node dist/cli.js start