@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.
- package/README.md +339 -0
- package/dist/cli.js +262 -0
- package/dist/relay.js +1315 -0
- package/package.json +35 -0
- package/sales-template/README.md +199 -0
- package/sales-template/cc2_guard.py +395 -0
- package/sales-template/settings.json +161 -0
- package/sales-template/setup.sh +350 -0
- package/sales-template/systemd/README.md +169 -0
- package/sales-template/systemd/conductor@.service +47 -0
- package/sales-template/systemd/install.sh +179 -0
|
@@ -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
|
+
```
|