@tyhld/conductor 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,161 @@
1
+ {
2
+ "_note": "采配くん販売用テンプレート — プロジェクトの .claude/settings.json に配置する。<PLACEHOLDER> を顧客環境に合わせて差し替えること。差し替え箇所は README.md の「プレースホルダ一覧」を参照。",
3
+ "permissions": {
4
+ "defaultMode": "default",
5
+ "disableBypassPermissionsMode": "disable",
6
+ "allow": [
7
+ "Read",
8
+ "Edit(<PROJECT_DIR>/**)",
9
+ "Write(<PROJECT_DIR>/**)",
10
+
11
+ "Bash(npm run build:*)",
12
+ "Bash(npm run dev:*)",
13
+ "Bash(npm run lint:*)",
14
+ "Bash(npm run test:*)",
15
+ "Bash(npm install:*)",
16
+ "Bash(npm ci:*)",
17
+ "Bash(npx tsc:*)",
18
+ "Bash(npx tsc --noEmit:*)",
19
+ "Bash(npx eslint:*)",
20
+ "Bash(npx next lint:*)",
21
+ "Bash(npx next build:*)",
22
+ "Bash(npx vitest:*)",
23
+ "Bash(npx prettier:*)",
24
+ "Bash(pnpm install:*)",
25
+ "Bash(pnpm build:*)",
26
+ "Bash(pnpm lint:*)",
27
+ "Bash(pnpm test:*)",
28
+ "Bash(pnpm typecheck:*)",
29
+ "Bash(pnpm format:*)",
30
+ "Bash(pnpm run build:*)",
31
+ "Bash(pnpm run lint:*)",
32
+ "Bash(pnpm run test:*)",
33
+ "Bash(pnpm run typecheck:*)",
34
+ "Bash(tsc:*)",
35
+
36
+ "Bash(git status:*)",
37
+ "Bash(git log:*)",
38
+ "Bash(git diff:*)",
39
+ "Bash(git show:*)",
40
+ "Bash(git branch:*)",
41
+ "Bash(git fetch:*)",
42
+ "Bash(git add:*)",
43
+ "Bash(git commit:*)",
44
+ "Bash(git checkout -b:*)",
45
+ "Bash(git switch -c:*)",
46
+ "Bash(git stash list:*)",
47
+ "Bash(git rev-parse:*)",
48
+ "Bash(git ls-files:*)",
49
+ "Bash(git remote -v:*)",
50
+ "Bash(git push:*)",
51
+
52
+ "Bash(grep:*)",
53
+ "Bash(find:*)",
54
+ "Bash(cat:*)",
55
+ "Bash(ls:*)",
56
+ "Bash(head:*)",
57
+ "Bash(tail:*)",
58
+ "Bash(wc:*)",
59
+ "Bash(which:*)",
60
+ "Bash(pwd:*)",
61
+ "Bash(ps:*)",
62
+
63
+ "Bash(gh pr view:*)",
64
+ "Bash(gh pr checks:*)",
65
+ "Bash(gh pr diff:*)",
66
+ "Bash(gh pr list:*)",
67
+ "Bash(gh pr status:*)",
68
+ "Bash(gh run list:*)",
69
+ "Bash(gh run view:*)",
70
+ "Bash(gh repo view:*)"
71
+ ],
72
+ "ask": [
73
+ "Bash(gh pr merge:*)",
74
+ "Bash(gh pr create:*)",
75
+ "Bash(git push origin main:*)",
76
+ "Bash(git push -u origin main:*)",
77
+ "Bash(git push --set-upstream origin main:*)",
78
+ "Bash(git merge:*)",
79
+ "Bash(vercel --prod:*)",
80
+ "Bash(vercel deploy --prod:*)",
81
+ "Bash(npx vercel --prod:*)",
82
+ "Bash(npm publish:*)",
83
+ "Bash(pnpm publish:*)",
84
+ "Bash(supabase functions deploy:*)",
85
+ "Bash(npx supabase functions deploy:*)"
86
+ ],
87
+ "deny": [
88
+ "Bash(git push --force:*)",
89
+ "Bash(git push -f:*)",
90
+ "Bash(git push --force-with-lease:*)",
91
+ "Bash(git push * --force:*)",
92
+ "Bash(git push * -f:*)",
93
+ "Bash(git push * --force-with-lease:*)",
94
+ "Bash(rm -rf:*)",
95
+ "Bash(rm -fr:*)",
96
+ "Bash(rm -r -f:*)",
97
+ "Bash(rm -f -r:*)",
98
+ "Bash(rm -R:*)",
99
+ "Bash(rm --recursive:*)",
100
+ "Bash(rm --force --recursive:*)",
101
+ "Bash(supabase db push:*)",
102
+ "Bash(npx supabase db push:*)",
103
+ "Bash(supabase db reset:*)",
104
+ "Bash(npx supabase db reset:*)",
105
+ "Bash(supabase migration up:*)",
106
+ "Bash(npx supabase migration up:*)",
107
+ "Bash(prisma migrate deploy:*)",
108
+ "Bash(npx prisma migrate deploy:*)",
109
+ "Bash(prisma migrate reset:*)",
110
+ "Bash(npx prisma migrate reset:*)",
111
+ "Bash(prisma db push:*)",
112
+ "Bash(npx prisma db push:*)",
113
+ "Bash(drizzle-kit push:*)",
114
+ "Bash(npx drizzle-kit push:*)",
115
+ "Bash(npm run migrate:*)",
116
+ "Bash(pnpm migrate:*)",
117
+ "Bash(pnpm run migrate:*)",
118
+ "Bash(*DROP TABLE*)",
119
+ "Bash(*TRUNCATE*)",
120
+ "Bash(*rest/v1/rpc/exec_sql*)",
121
+ "Bash(cat .env:*)",
122
+ "Bash(cat .env.*:*)",
123
+ "Bash(cat *.env:*)",
124
+ "Bash(printenv:*)",
125
+ "Bash(echo $DATABASE_URL:*)",
126
+ "Bash(echo $DIRECT_URL:*)",
127
+ "mcp__claude_ai_Supabase__execute_sql",
128
+ "mcp__claude_ai_Supabase__apply_migration",
129
+ "mcp__claude_ai_Supabase__deploy_edge_function"
130
+ ]
131
+ },
132
+ "sandbox": {
133
+ "enabled": true,
134
+ "failIfUnavailable": true,
135
+ "autoAllowBashIfSandboxed": true,
136
+ "network": {
137
+ "allowedDomains": [
138
+ "<SUPABASE_PROJECT_ID>.supabase.co",
139
+ "api.supabase.com",
140
+ "supabase.com",
141
+ "registry.npmjs.org",
142
+ "npm.pkg.github.com",
143
+ "github.com",
144
+ "api.github.com",
145
+ "codeload.github.com",
146
+ "objects.githubusercontent.com",
147
+ "raw.githubusercontent.com",
148
+ "vercel.com",
149
+ "api.vercel.com",
150
+ "localhost",
151
+ "127.0.0.1",
152
+ "::1"
153
+ ],
154
+ "deniedDomains": [
155
+ "db.<SUPABASE_PROJECT_ID>.supabase.co",
156
+ "aws-0-ap-northeast-1.pooler.supabase.com",
157
+ "aws-1-ap-northeast-1.pooler.supabase.com"
158
+ ]
159
+ }
160
+ }
161
+ }
@@ -0,0 +1,350 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # setup.sh — 采配くん一式を顧客PCに1コマンドでインストールする統合インストーラ。
4
+ #
5
+ # 使い方:
6
+ # bash setup.sh <プロジェクト名> [プロジェクトディレクトリ]
7
+ #
8
+ # 例:
9
+ # bash setup.sh my-app # ~/projects/my-app を想定
10
+ # bash setup.sh my-app /home/user/work/my-app # ディレクトリを明示指定
11
+ #
12
+ # やること(順番に・冪等・失敗時は止めて報告):
13
+ # ① 前提チェック(node>=18 / tmux / git / python3>=3.8 / systemd / sudo)
14
+ # ② conductor 本体インストール(npm i -g @tyhld/conductor)
15
+ # ③ node symlink(/usr/local/bin/node → 実体・sudo)
16
+ # ④ .conductor.env 作成(対話入力・chmod 600)
17
+ # ⑤ .claude/ 配置(cc2_guard.py + settings.json + hooks 設定)
18
+ # ⑥ systemd install(enable + linger)
19
+ # ⑦ 職人起動の案内表示
20
+ #
21
+ set -euo pipefail
22
+
23
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24
+
25
+ # ─── 引数チェック ─────────────────────────────────────────────────────────────
26
+ if [[ $# -lt 1 || -z "${1:-}" ]]; then
27
+ cat >&2 <<'USAGE'
28
+ 采配くんセットアップ
29
+
30
+ 使い方: bash setup.sh <プロジェクト名> [プロジェクトディレクトリ]
31
+
32
+ 例:
33
+ bash setup.sh my-app # ~/projects/my-app を想定
34
+ bash setup.sh my-app /home/user/work/my-app # ディレクトリを明示指定
35
+ USAGE
36
+ exit 1
37
+ fi
38
+
39
+ SITE="$1"
40
+ PROJECT_DIR="${2:-$HOME/projects/$SITE}"
41
+
42
+ # ─── ヘルパ ───────────────────────────────────────────────────────────────────
43
+ fail() { echo; echo "[setup] エラー: $1" >&2; exit 1; }
44
+ warn() { echo "[setup] warn: $1" >&2; }
45
+ step() { echo; echo "══════════════════════════════════════════════════════════"; echo "[setup] $1"; echo "══════════════════════════════════════════════════════════"; }
46
+
47
+ # ─── ① 前提チェック ──────────────────────────────────────────────────────────
48
+ step "① 前提チェック"
49
+
50
+ errors=()
51
+
52
+ # node >= 18
53
+ if command -v node >/dev/null 2>&1; then
54
+ NODE_VER="$(node --version 2>/dev/null || echo 'v0')"
55
+ NODE_MAJOR="${NODE_VER#v}"
56
+ NODE_MAJOR="${NODE_MAJOR%%.*}"
57
+ if [[ "$NODE_MAJOR" -lt 18 ]] 2>/dev/null; then
58
+ errors+=("node $NODE_VER が古すぎます。18 以上が必要です。")
59
+ else
60
+ echo "[setup] node: $NODE_VER OK"
61
+ fi
62
+ else
63
+ errors+=("node が見つかりません。Node.js 18 以上をインストールしてください(fnm / nvm / volta / apt)。")
64
+ fi
65
+
66
+ # tmux
67
+ if command -v tmux >/dev/null 2>&1; then
68
+ echo "[setup] tmux: $(tmux -V 2>/dev/null || echo 'OK')"
69
+ else
70
+ errors+=("tmux が見つかりません。sudo apt install tmux でインストールしてください。")
71
+ fi
72
+
73
+ # git
74
+ if command -v git >/dev/null 2>&1; then
75
+ echo "[setup] git: $(git --version 2>/dev/null)"
76
+ else
77
+ errors+=("git が見つかりません。sudo apt install git でインストールしてください。")
78
+ fi
79
+
80
+ # python3 >= 3.8
81
+ if command -v python3 >/dev/null 2>&1; then
82
+ PY_VER="$(python3 --version 2>/dev/null || echo 'Python 0')"
83
+ PY_MINOR="$(echo "$PY_VER" | sed -E 's/Python ([0-9]+)\.([0-9]+).*/\2/')"
84
+ PY_MAJOR="$(echo "$PY_VER" | sed -E 's/Python ([0-9]+)\..*/\1/')"
85
+ if [[ "$PY_MAJOR" -lt 3 ]] 2>/dev/null || { [[ "$PY_MAJOR" -eq 3 ]] && [[ "$PY_MINOR" -lt 8 ]]; } 2>/dev/null; then
86
+ errors+=("$PY_VER が古すぎます。Python 3.8 以上が必要です。")
87
+ else
88
+ echo "[setup] python3: $PY_VER OK"
89
+ fi
90
+ else
91
+ errors+=("python3 が見つかりません。sudo apt install python3 でインストールしてください。")
92
+ fi
93
+
94
+ # systemd
95
+ if command -v systemctl >/dev/null 2>&1; then
96
+ if systemctl --user show-environment >/dev/null 2>&1; then
97
+ echo "[setup] systemd: ユーザーセッション OK"
98
+ else
99
+ errors+=("systemd ユーザーセッションに接続できません。WSL の場合: /etc/wsl.conf に [boot] systemd=true を設定し wsl --shutdown で再起動してください。")
100
+ fi
101
+ else
102
+ errors+=("systemctl が見つかりません。systemd が有効か確認してください。")
103
+ fi
104
+
105
+ # sudo
106
+ if command -v sudo >/dev/null 2>&1; then
107
+ echo "[setup] sudo: OK"
108
+ else
109
+ errors+=("sudo が見つかりません。node symlink の作成(③)で必要です。")
110
+ fi
111
+
112
+ # プロジェクトディレクトリ
113
+ if [[ -d "$PROJECT_DIR" ]]; then
114
+ echo "[setup] プロジェクトディレクトリ: $PROJECT_DIR OK"
115
+ else
116
+ errors+=("プロジェクトディレクトリが存在しません: $PROJECT_DIR")
117
+ fi
118
+
119
+ if [[ ${#errors[@]} -gt 0 ]]; then
120
+ echo
121
+ echo "[setup] 前提チェックに失敗しました:" >&2
122
+ for e in "${errors[@]}"; do
123
+ echo " - $e" >&2
124
+ done
125
+ exit 1
126
+ fi
127
+
128
+ # ─── ② conductor 本体インストール ────────────────────────────────────────────
129
+ step "② conductor 本体インストール"
130
+
131
+ # @tyhld/conductor は公開 npm (registry.npmjs.org) で配布されるため、
132
+ # PAT や .npmrc の設定は不要。無認証で npm i -g できる。
133
+ if command -v conductor >/dev/null 2>&1; then
134
+ echo "[setup] conductor は既にインストール済みです。更新します..."
135
+ npm update -g @tyhld/conductor || warn "npm update に失敗しました。続行します。"
136
+ else
137
+ echo "[setup] @tyhld/conductor をグローバルインストールします..."
138
+ npm install -g @tyhld/conductor || fail "npm install -g @tyhld/conductor に失敗しました。ネットワーク接続を確認してください。"
139
+ fi
140
+
141
+ # conductor コマンドの存在を再確認
142
+ if ! command -v conductor >/dev/null 2>&1; then
143
+ fail "conductor コマンドが見つかりません。npm グローバルインストールの PATH を確認してください。"
144
+ fi
145
+ echo "[setup] conductor: $(conductor --version 2>/dev/null || command -v conductor)"
146
+
147
+ # ─── ③ node symlink ──────────────────────────────────────────────────────────
148
+ step "③ node symlink(systemd PATH 対応)"
149
+
150
+ NODE_PATH="$(readlink -f "$(command -v node)")"
151
+ SYMLINK_PATH="/usr/local/bin/node"
152
+
153
+ need_symlink=true
154
+ if [[ -f "$SYMLINK_PATH" || -L "$SYMLINK_PATH" ]]; then
155
+ existing="$(readlink -f "$SYMLINK_PATH" 2>/dev/null || echo '')"
156
+ if [[ "$existing" == "$NODE_PATH" ]]; then
157
+ echo "[setup] $SYMLINK_PATH は既に正しいリンクです → スキップ"
158
+ need_symlink=false
159
+ fi
160
+ fi
161
+
162
+ if $need_symlink; then
163
+ echo "[setup] $SYMLINK_PATH → $NODE_PATH のシンボリックリンクを作成します(sudo)"
164
+ if [[ -w "$(dirname "$SYMLINK_PATH")" ]]; then
165
+ ln -sf "$NODE_PATH" "$SYMLINK_PATH"
166
+ else
167
+ sudo ln -sf "$NODE_PATH" "$SYMLINK_PATH"
168
+ fi
169
+ echo "[setup] symlink 作成完了"
170
+ fi
171
+
172
+ # ─── ④ .conductor.env 作成 ───────────────────────────────────────────────────
173
+ step "④ .conductor.env(管制サーバ接続情報)"
174
+
175
+ ENV_FILE="$HOME/.conductor.env"
176
+ if [[ -f "$ENV_FILE" ]]; then
177
+ echo "[setup] $ENV_FILE は既に存在します → スキップ"
178
+ echo "[setup] 内容を変更する場合は手動で編集してください。"
179
+ else
180
+ echo "[setup] 管制サーバへの接続情報を入力してください。"
181
+ echo
182
+
183
+ read -r -p " CONDUCTOR_URL(例: https://your-conductor.vercel.app): " input_url
184
+ [[ -z "$input_url" ]] && fail "CONDUCTOR_URL が空です。"
185
+
186
+ read -r -p " CONDUCTOR_SHARED_SECRET: " input_secret
187
+ [[ -z "$input_secret" ]] && fail "CONDUCTOR_SHARED_SECRET が空です。"
188
+
189
+ cat > "$ENV_FILE" <<ENVEOF
190
+ CONDUCTOR_URL=${input_url}
191
+ CONDUCTOR_SHARED_SECRET=${input_secret}
192
+ ENVEOF
193
+ chmod 600 "$ENV_FILE"
194
+ echo "[setup] $ENV_FILE を作成しました(chmod 600)"
195
+ fi
196
+
197
+ # ─── ⑤ .claude/ 配置(PERM ルール + Hook) ──────────────────────────────────
198
+ step "⑤ .claude/ 配置(権限ルール + Hook)"
199
+
200
+ CLAUDE_DIR="$PROJECT_DIR/.claude"
201
+ HOOKS_DIR="$CLAUDE_DIR/hooks"
202
+ mkdir -p "$HOOKS_DIR"
203
+
204
+ # --- cc2_guard.py ---
205
+ GUARD_SRC="$SCRIPT_DIR/cc2_guard.py"
206
+ GUARD_DST="$HOOKS_DIR/cc2_guard.py"
207
+ if [[ -f "$GUARD_SRC" ]]; then
208
+ cp -f "$GUARD_SRC" "$GUARD_DST"
209
+ chmod +x "$GUARD_DST"
210
+ echo "[setup] 配置: $GUARD_DST"
211
+ else
212
+ warn "cc2_guard.py が見つかりません: $GUARD_SRC(スキップ)"
213
+ fi
214
+
215
+ # --- settings.json ---
216
+ SETTINGS_SRC="$SCRIPT_DIR/settings.json"
217
+ SETTINGS_DST="$CLAUDE_DIR/settings.json"
218
+ if [[ -f "$SETTINGS_SRC" ]]; then
219
+ if [[ -f "$SETTINGS_DST" ]]; then
220
+ echo "[setup] $SETTINGS_DST は既に存在します。上書きしますか?"
221
+ read -r -p " 上書き (y/N): " overwrite
222
+ if [[ "${overwrite,,}" != "y" ]]; then
223
+ echo "[setup] settings.json をスキップしました。"
224
+ else
225
+ cp -f "$SETTINGS_SRC" "$SETTINGS_DST"
226
+ echo "[setup] 配置: $SETTINGS_DST(上書き)"
227
+ fi
228
+ else
229
+ cp -f "$SETTINGS_SRC" "$SETTINGS_DST"
230
+ echo "[setup] 配置: $SETTINGS_DST"
231
+ fi
232
+
233
+ # <PROJECT_DIR> をプロジェクトの絶対パスに置換
234
+ if [[ -f "$SETTINGS_DST" ]] && grep -q '<PROJECT_DIR>' "$SETTINGS_DST"; then
235
+ ABS_PROJECT_DIR="$(cd "$PROJECT_DIR" && pwd)"
236
+ sed -i "s|<PROJECT_DIR>|${ABS_PROJECT_DIR}|g" "$SETTINGS_DST"
237
+ echo "[setup] <PROJECT_DIR> → $ABS_PROJECT_DIR に置換"
238
+ fi
239
+
240
+ # Supabase 設定
241
+ if [[ -f "$SETTINGS_DST" ]] && grep -q '<SUPABASE_PROJECT_ID>' "$SETTINGS_DST"; then
242
+ echo
243
+ read -r -p "[setup] Supabase を使いますか? (y/N): " use_supabase
244
+ if [[ "${use_supabase,,}" == "y" ]]; then
245
+ read -r -p " Supabase Project Reference ID: " supabase_id
246
+ if [[ -n "$supabase_id" ]]; then
247
+ sed -i "s|<SUPABASE_PROJECT_ID>|${supabase_id}|g" "$SETTINGS_DST"
248
+ echo "[setup] <SUPABASE_PROJECT_ID> → $supabase_id に置換"
249
+ else
250
+ warn "Supabase ID が空のため、sandbox 設定にプレースホルダが残ります。"
251
+ fi
252
+ else
253
+ # sandbox セクションを削除(Supabase 不使用)
254
+ # JSON から "sandbox" キー全体を除去するのは sed では困難なため、
255
+ # python3 で安全に処理する。
256
+ if python3 -c "
257
+ import json, sys
258
+ with open('$SETTINGS_DST') as f:
259
+ d = json.load(f)
260
+ d.pop('sandbox', None)
261
+ with open('$SETTINGS_DST', 'w') as f:
262
+ json.dump(d, f, indent=2, ensure_ascii=False)
263
+ f.write('\n')
264
+ " 2>/dev/null; then
265
+ echo "[setup] Supabase 不使用のため sandbox セクションを削除しました"
266
+ else
267
+ warn "sandbox セクションの自動削除に失敗しました。手動で .claude/settings.json を編集してください。"
268
+ fi
269
+ fi
270
+ fi
271
+ else
272
+ warn "settings.json が見つかりません: $SETTINGS_SRC(スキップ)"
273
+ fi
274
+
275
+ # --- hooks 設定を settings.json に注入 ---
276
+ if [[ -f "$SETTINGS_DST" ]]; then
277
+ # hooks キーが既に存在するか確認
278
+ if ! python3 -c "
279
+ import json
280
+ with open('$SETTINGS_DST') as f:
281
+ d = json.load(f)
282
+ print('has_hooks' if 'hooks' in d else 'no_hooks')
283
+ " 2>/dev/null | grep -q 'has_hooks'; then
284
+ # hooks セクションを追加
285
+ python3 -c "
286
+ import json
287
+ with open('$SETTINGS_DST') as f:
288
+ d = json.load(f)
289
+ d['hooks'] = {
290
+ 'PreToolUse': [
291
+ {
292
+ 'matcher': 'Bash',
293
+ 'hooks': [
294
+ {
295
+ 'type': 'command',
296
+ 'command': 'python3 .claude/hooks/cc2_guard.py'
297
+ }
298
+ ]
299
+ }
300
+ ]
301
+ }
302
+ with open('$SETTINGS_DST', 'w') as f:
303
+ json.dump(d, f, indent=2, ensure_ascii=False)
304
+ f.write('\n')
305
+ " 2>/dev/null && echo "[setup] hooks 設定を settings.json に注入しました" \
306
+ || warn "hooks 設定の注入に失敗しました。手動で追加してください。"
307
+ else
308
+ echo "[setup] hooks 設定は既に存在します → スキップ"
309
+ fi
310
+ fi
311
+
312
+ # ─── ⑥ systemd install ──────────────────────────────────────────────────────
313
+ step "⑥ systemd 常駐サービスの設定"
314
+
315
+ SYSTEMD_INSTALL="$SCRIPT_DIR/systemd/install.sh"
316
+ if [[ -f "$SYSTEMD_INSTALL" ]]; then
317
+ bash "$SYSTEMD_INSTALL" "$SITE" "$PROJECT_DIR"
318
+ else
319
+ warn "systemd/install.sh が見つかりません: $SYSTEMD_INSTALL(スキップ)"
320
+ echo "[setup] 手動で systemd を設定する場合は sales-template/systemd/README.md を参照してください。"
321
+ fi
322
+
323
+ # ─── ⑦ 職人起動の案内 ────────────────────────────────────────────────────────
324
+ step "⑦ 完了! 職人(Claude Code)の起動方法"
325
+
326
+ cat <<GUIDE
327
+
328
+ 采配くんのセットアップが完了しました。
329
+
330
+ 次のステップ: Claude Code(職人)を起動してください。
331
+
332
+ # 1. tmux セッションを作成
333
+ tmux new-session -d -s "$SITE" -c "$PROJECT_DIR"
334
+
335
+ # 2. ANTHROPIC_API_KEY を無効化して Claude Code を起動
336
+ tmux send-keys -t "$SITE" "unset ANTHROPIC_API_KEY && claude" C-m
337
+
338
+ # 3. セッションに接続
339
+ tmux attach -t "$SITE"
340
+
341
+ Claude Code が起動したら、管制サーバから指示を送ることができます。
342
+ relay が15秒ごとに指示をポーリングし、Claude Code がアイドル状態のときに自動で流し込みます。
343
+
344
+ --- 管理コマンド ---
345
+ 状態確認: systemctl --user status conductor@${SITE}
346
+ ログ確認: journalctl --user -u conductor@${SITE} -f
347
+ 再起動: systemctl --user restart conductor@${SITE}
348
+ 停止: systemctl --user disable --now conductor@${SITE}
349
+
350
+ GUIDE
@@ -0,0 +1,169 @@
1
+ # 采配くん常駐サービス(systemd テンプレート)
2
+
3
+ PC の起動時に采配くんの relay(指示の自動流し込み)と heartbeat(生存報告)を自動で常駐させるための systemd ユーザーサービスです。
4
+
5
+ ## これは何をするか
6
+
7
+ ```
8
+ PC起動
9
+
10
+
11
+ systemd が conductor@<プロジェクト> を自動起動
12
+
13
+ ├── heartbeat: 管制サーバに「このPCは生きている」を定期報告
14
+
15
+ └── relay: 管制サーバから指示をポーリング
16
+ └── tmux セッション内の Claude Code がアイドルなら指示を流し込む
17
+ (ビジー・セッションなしならスキップ=誤爆なし)
18
+ ```
19
+
20
+ - **落ちても自動復活**: `Restart=always` で5秒後に再起動
21
+ - **ログオフしても常駐**: `enable-linger` で SSH 切断後も動き続ける
22
+ - **CC1B(LLM判断層)は不要**: 権限制御は cc2_guard.py + settings.json で行う(ADR-001)
23
+
24
+ ## ファイル一覧
25
+
26
+ | ファイル | 役割 |
27
+ |----------|------|
28
+ | `conductor@.service` | systemd ユニットファイル(テンプレート) |
29
+ | `install.sh` | インストーラ(node検出→symlink→配置→enable→linger) |
30
+ | `README.md` | このファイル |
31
+
32
+ **conductor-orchestrator.service は含まない**: ADR-001 の決定により CC1B(LLM判断層)は撤去。orchestrator サービスは販売構成では不要。
33
+
34
+ ## セットアップ手順
35
+
36
+ ### 前提条件
37
+
38
+ - Linux(WSL 含む)で systemd が有効
39
+ - Node.js 18 以上がインストール済み(fnm / nvm / volta / apt いずれでも可)
40
+ - `~/.conductor.env` が用意済み
41
+ - conductor リポで `npm run build` 済み
42
+
43
+ ### 1. 鍵ファイルを用意
44
+
45
+ ```bash
46
+ cat > "$HOME/.conductor.env" <<'EOF'
47
+ CONDUCTOR_URL=https://<采配くん管制サーバURL>
48
+ CONDUCTOR_SHARED_SECRET=<シークレット>
49
+ EOF
50
+ chmod 600 "$HOME/.conductor.env"
51
+ ```
52
+
53
+ ### 2. インストール実行
54
+
55
+ ```bash
56
+ # プロジェクトが ~/projects/<名前> にある場合(既定パス)
57
+ bash install.sh my-app
58
+
59
+ # プロジェクトが別の場所にある場合
60
+ bash install.sh my-app /home/user/work/my-app
61
+ ```
62
+
63
+ ### 3. 動作確認
64
+
65
+ ```bash
66
+ # サービスの状態を確認
67
+ systemctl --user status conductor@my-app
68
+
69
+ # ログをリアルタイムで確認
70
+ journalctl --user -u conductor@my-app -f
71
+ ```
72
+
73
+ ### 複数プロジェクトの場合
74
+
75
+ プロジェクトごとに install.sh を実行してください:
76
+
77
+ ```bash
78
+ bash install.sh project-a
79
+ bash install.sh project-b /home/user/work/project-b
80
+ ```
81
+
82
+ ## node PATH 問題とその対処
83
+
84
+ ### 問題
85
+
86
+ systemd ユーザーサービスはシェルの PATH を継承しません。既定 PATH は:
87
+
88
+ ```
89
+ /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
90
+ ```
91
+
92
+ fnm / nvm / volta でインストールした node はこのパス上にないため、
93
+ ユニットの `ExecStart` で `node` が見つからず起動に失敗します。
94
+ `Restart=always` と組み合わさると**無限再起動ループ**になります。
95
+
96
+ ### 対処(install.sh が自動で行う)
97
+
98
+ install.sh はシェルで使っている node の実体パスを検出し、
99
+ `/usr/local/bin/node` にシンボリックリンクを作成します。
100
+
101
+ ```
102
+ /usr/local/bin/node → /home/user/.local/share/fnm/node-versions/v22.x.x/bin/node
103
+ ```
104
+
105
+ これにより systemd の既定 PATH で node が解決されます。
106
+
107
+ | node 管理方式 | 検出される実体パス | 対応状況 |
108
+ |--------------|-------------------|----------|
109
+ | fnm | `~/.local/share/fnm/node-versions/vXX/installation/bin/node` | OK |
110
+ | nvm | `~/.nvm/versions/node/vXX/bin/node` | OK |
111
+ | volta | `~/.volta/tools/image/node/XX/bin/node` | OK |
112
+ | apt / システム | `/usr/bin/node` | OK(既に PATH 上にあるが symlink も作成) |
113
+
114
+ **注意**: node のバージョンを fnm/nvm で切り替えた場合、symlink が旧バージョンを指したままになります。
115
+ その場合は `install.sh` を再実行してください(冪等に動作します)。
116
+
117
+ ## 顧客が用意するもの
118
+
119
+ | 項目 | 説明 | 例 |
120
+ |------|------|-----|
121
+ | `~/.conductor.env` | 管制サーバへの接続情報 | `CONDUCTOR_URL=https://...` / `CONDUCTOR_SHARED_SECRET=...` |
122
+ | プロジェクト名 | install.sh の第1引数 | `my-app` |
123
+ | プロジェクトディレクトリ | install.sh の第2引数(既定は `~/projects/<名前>`) | `/home/user/work/my-app` |
124
+ | sudo パスワード | symlink 作成時に必要(初回のみ) | — |
125
+
126
+ ## 管理コマンド
127
+
128
+ ```bash
129
+ # 状態確認
130
+ systemctl --user status conductor@my-app
131
+
132
+ # ログ確認
133
+ journalctl --user -u conductor@my-app -f
134
+
135
+ # 再起動
136
+ systemctl --user restart conductor@my-app
137
+
138
+ # 停止(常駐を解除)
139
+ systemctl --user disable --now conductor@my-app
140
+
141
+ # 完全削除
142
+ systemctl --user disable --now conductor@my-app
143
+ rm ~/.config/systemd/user/conductor@.service
144
+ rm -rf ~/.config/systemd/user/conductor@my-app.service.d
145
+ systemctl --user daemon-reload
146
+ ```
147
+
148
+ ## トラブルシューティング
149
+
150
+ ### サービスが起動しない
151
+
152
+ ```bash
153
+ # ログで原因を確認
154
+ journalctl --user -u conductor@my-app --no-pager -n 50
155
+
156
+ # よくある原因:
157
+ # - node not found → install.sh を再実行
158
+ # - dist/cli.js が無い → cd ~/projects/conductor && npm run build
159
+ # - .conductor.env が無い → 上記の手順で作成
160
+ ```
161
+
162
+ ### WSL で systemd が動かない
163
+
164
+ `/etc/wsl.conf` に以下を追加し、`wsl --shutdown` で再起動:
165
+
166
+ ```ini
167
+ [boot]
168
+ systemd=true
169
+ ```