@tyhld/conductor 0.3.0 → 0.7.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.
Files changed (46) hide show
  1. package/README.md +301 -19
  2. package/dist/cli.js +143 -18
  3. package/dist/ear-routing.js +57 -0
  4. package/dist/ear.js +57 -0
  5. package/dist/env-file-perm.js +67 -0
  6. package/dist/nudge.js +172 -0
  7. package/dist/realtime-parse.js +116 -0
  8. package/dist/realtime.js +251 -0
  9. package/dist/relay-runner.js +65 -0
  10. package/dist/relay.js +911 -134
  11. package/dist/websocket-transport.js +66 -0
  12. package/launchd/ear-install.sh +96 -0
  13. package/package.json +30 -1
  14. package/sales-template/README.md +185 -25
  15. package/sales-template/install.sh +890 -0
  16. package/sales-template/launchd/install.sh +151 -0
  17. package/sales-template/settings.json +26 -97
  18. package/sales-template/setup.sh +754 -117
  19. package/sales-template/systemd/README.md +28 -4
  20. package/sales-template/systemd/install.sh +54 -5
  21. package/sales-template/systemd/paste-cache-prune-install.sh +75 -0
  22. package/sales-template/systemd/tyhld-paste-cache-prune.service +25 -0
  23. package/sales-template/systemd/tyhld-paste-cache-prune.timer +19 -0
  24. package/sales-template/uninstall.sh +245 -0
  25. package/scripts/hooks/README.md +246 -0
  26. package/scripts/hooks/cc2_guard.py +145 -0
  27. package/scripts/hooks/codex-hooks.sample.json +58 -0
  28. package/scripts/hooks/hook_datalink.py +440 -0
  29. package/scripts/hooks/install-codex-hooks.sh +127 -0
  30. package/scripts/hooks/notification_hook.py +167 -0
  31. package/scripts/hooks/permission_request_hook.py +207 -0
  32. package/scripts/hooks/policy.py +759 -0
  33. package/scripts/hooks/settings.sample.json +142 -0
  34. package/scripts/hooks/stop_hook.py +275 -0
  35. package/scripts/hooks/summary_ja.py +155 -0
  36. package/scripts/hooks/test_hook_datalink.py +282 -0
  37. package/scripts/hooks/test_policy.py +1241 -0
  38. package/skills/conductor-craftsman/SKILL.md +40 -0
  39. package/systemd/conductor-ear.service +63 -0
  40. package/systemd/conductor@.service +62 -0
  41. package/systemd/ear-install.sh +131 -0
  42. package/systemd/guard-sync-install.sh +94 -0
  43. package/systemd/tyhld-guard-sync.service +28 -0
  44. package/systemd/tyhld-guard-sync.timer +25 -0
  45. package/sales-template/cc2_guard.py +0 -395
  46. package/sales-template/systemd/conductor@.service +0 -47
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # install.sh(launchd 版)— Mac で采配くんの常駐を設置する。★ADR-019
4
+ #
5
+ # ★これは sales-template/systemd/install.sh の「Mac 版の相方」です。
6
+ # 共通部分(前提チェック・番人の配置・鍵・自己点検)は setup.sh 1本のままで、
7
+ # OS で違う「常駐の設置」だけをこちらに分けています(案C)。
8
+ #
9
+ # 使い方:
10
+ # bash install.sh <プロジェクト名> [プロジェクトディレクトリ]
11
+ #
12
+ # やること(すべて冪等:何度実行しても同じ結果):
13
+ # 1. node と采配くん本体の【絶対パス】を確かめる(launchd は PATH を引き継がないため)
14
+ # 2. 鍵(~/.conductor.env)を読んでから起動する「入れ物」スクリプトを置く
15
+ # 3. 現場ごとの plist を作って ~/Library/LaunchAgents へ置く
16
+ # 4. launchctl で読み込み直す(既に動いていれば入れ替え)
17
+ # 5. 撤去用の台帳へ登録する
18
+ #
19
+ # ★systemd との違い(お客さまに影響するもの):
20
+ # ・EnvironmentFile が無い → 2 の「入れ物」で解いた
21
+ # ・テンプレート(%i)が無い → 3 で現場ごとに plist を作って解いた
22
+ # ・Persistent=true が無い → 定期実行の取りこぼしは追い実行されない(タイマー側の話)
23
+ # ・enable-linger が無い → ★LaunchAgent は【ログイン中だけ】動く。ログアウト中は止まる。
24
+ # 常時動かすには root の LaunchDaemon が要るが、それは別の判断。
25
+ #
26
+ set -euo pipefail
27
+
28
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
29
+
30
+ fail() { echo "[launchd-install] エラー: $1" >&2; exit 1; }
31
+ warn() { echo "[launchd-install] warn: $1" >&2; }
32
+
33
+ [[ $# -ge 1 && -n "${1:-}" ]] || fail "使い方: bash install.sh <プロジェクト名> [プロジェクトディレクトリ]"
34
+ SITE="$1"
35
+ PROJECT_DIR="${2:-$HOME/projects/$SITE}"
36
+ LABEL="com.tyhld.conductor.${SITE}"
37
+
38
+ [[ "$(uname -s)" == "Darwin" ]] || fail "これは Mac 用です。Linux/WSL では systemd 版(../systemd/install.sh)を使います。"
39
+ command -v launchctl >/dev/null 2>&1 || fail "launchctl が見つかりません。"
40
+
41
+ # ★BSD でも GNU でも同じに動く実体パス解決(readlink -f は BSD に無い版がある)。
42
+ resolve_path() {
43
+ python3 -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$1" 2>/dev/null || printf '%s' "$1"
44
+ }
45
+
46
+ # --- 1) node と采配くん本体の絶対パス ----------------------------------------
47
+ # ★launchd はログインシェルの PATH を引き継がない。`#!/usr/bin/env node` も解決できないので、
48
+ # plist には「node の絶対パス」+「cli.js の絶対パス」を書く。
49
+ # =Linux 版のような /usr/local/bin/node へのリンク(sudo が要る)が【不要】になる。
50
+ command -v node >/dev/null 2>&1 || fail "node が見つかりません。"
51
+ NODE_BIN="$(resolve_path "$(command -v node)")"
52
+ command -v conductor >/dev/null 2>&1 || fail "conductor が見つかりません。先に npm i -g @tyhld/conductor を実行してください。"
53
+ CONDUCTOR_JS="$(resolve_path "$(command -v conductor)")"
54
+ echo "[launchd-install] node: $NODE_BIN"
55
+ echo "[launchd-install] 采配くん本体: $CONDUCTOR_JS"
56
+
57
+ # --- 2) 鍵を読んでから起動する「入れ物」(★EnvironmentFile が無いことへの答え) ---
58
+ # launchd の plist には「このファイルを読んでから起動して」に相当する指定が無い。
59
+ # かといって plist に鍵を直接書くと、平文が ~/Library/LaunchAgents に残る(誰でも読める)。
60
+ # ★そこで、鍵を読むだけの小さな入れ物を1つ置き、plist はそれを呼ぶ。
61
+ # 鍵は今までどおり ~/.conductor.env(本人だけが読める権限)1か所にとどまる。
62
+ RUNNER_DIR="$HOME/.tyhld/bin"
63
+ RUNNER="$RUNNER_DIR/conductor-run.sh"
64
+ mkdir -p "$RUNNER_DIR"
65
+ cat > "$RUNNER" <<RUNEOF
66
+ #!/bin/bash
67
+ # 采配くんの常駐を起動する入れ物(launchd 用・install.sh が生成)。
68
+ # ★やることは2つだけ: 鍵を読む → 本体を起動する。
69
+ # plist に鍵を書かないための仕組みです(鍵は ~/.conductor.env の1か所だけに置く)。
70
+ set -eu
71
+ if [ -f "\$HOME/.conductor.env" ]; then
72
+ set -a
73
+ . "\$HOME/.conductor.env"
74
+ set +a
75
+ fi
76
+ exec "$NODE_BIN" "$CONDUCTOR_JS" "\$@"
77
+ RUNEOF
78
+ chmod 700 "$RUNNER"
79
+ echo "[launchd-install] 起動の入れ物を配置: $RUNNER"
80
+
81
+ # --- 3) 現場ごとの plist を作る(★テンプレート機能が無いことへの答え) --------
82
+ # systemd は conductor@<現場> の1つの定義で現場を増やせるが、launchd に %i は無い。
83
+ # ★だから現場ごとに1枚ずつ作る。作り方はここ1か所なので、増えても手作業にはならない。
84
+ AGENT_DIR="$HOME/Library/LaunchAgents"
85
+ PLIST="$AGENT_DIR/${LABEL}.plist"
86
+ LOG_DIR="$HOME/Library/Logs/tyhld"
87
+ mkdir -p "$AGENT_DIR" "$LOG_DIR"
88
+
89
+ # ★ログの置き場。launchd には journalctl が無いので、ファイルに出しておかないと後から読めない。
90
+ cat > "$PLIST" <<PLISTEOF
91
+ <?xml version="1.0" encoding="UTF-8"?>
92
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
93
+ <plist version="1.0">
94
+ <dict>
95
+ <key>Label</key><string>${LABEL}</string>
96
+
97
+ <!-- 起動するもの。入れ物が鍵を読んでから本体を起動する。 -->
98
+ <key>ProgramArguments</key>
99
+ <array>
100
+ <string>${RUNNER}</string>
101
+ <string>start</string>
102
+ <string>-s</string><string>${SITE}</string>
103
+ <string>-t</string><string>working</string>
104
+ <string>-a</string><string>${SITE}常駐</string>
105
+ </array>
106
+
107
+ <!-- systemd の Environment= に相当(鍵以外の設定はここに書いてよい)。 -->
108
+ <key>EnvironmentVariables</key>
109
+ <dict>
110
+ <key>CONDUCTOR_RELAY_ENABLED</key><string>true</string>
111
+ <key>CONDUCTOR_RELAY_INTERVAL_SEC</key><string>15</string>
112
+ </dict>
113
+
114
+ <key>WorkingDirectory</key><string>${PROJECT_DIR}</string>
115
+
116
+ <!-- ログイン時に起動し、落ちたら起こし直す(systemd の WantedBy + Restart=always 相当)。 -->
117
+ <key>RunAtLoad</key><true/>
118
+ <key>KeepAlive</key><true/>
119
+ <key>ThrottleInterval</key><integer>10</integer>
120
+
121
+ <!-- launchd に journalctl は無いので、記録はファイルに残す。 -->
122
+ <key>StandardOutPath</key><string>${LOG_DIR}/conductor-${SITE}.log</string>
123
+ <key>StandardErrorPath</key><string>${LOG_DIR}/conductor-${SITE}.log</string>
124
+ </dict>
125
+ </plist>
126
+ PLISTEOF
127
+ echo "[launchd-install] 配置: $PLIST"
128
+
129
+ # --- 4) 読み込み直す(冪等: 既に動いていれば入れ替え) ------------------------
130
+ UID_NUM="$(id -u)"
131
+ launchctl bootout "gui/${UID_NUM}/${LABEL}" >/dev/null 2>&1 || true
132
+ if launchctl bootstrap "gui/${UID_NUM}" "$PLIST" >/dev/null 2>&1; then
133
+ echo "[launchd-install] 起動しました: $LABEL"
134
+ else
135
+ # 古い macOS では bootstrap が使えないことがあるので、従来の load へ落とす。
136
+ launchctl unload "$PLIST" >/dev/null 2>&1 || true
137
+ launchctl load -w "$PLIST" >/dev/null 2>&1 \
138
+ && echo "[launchd-install] 起動しました(load 方式): $LABEL" \
139
+ || warn "起動に失敗しました。次で確かめてください: launchctl list | grep $LABEL"
140
+ fi
141
+
142
+ # --- 5) 撤去用の台帳へ登録(systemd 版と同じ作法) ----------------------------
143
+ LEDGER_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/conductor"
144
+ LEDGER_FILE="$LEDGER_DIR/installed-launchd.txt"
145
+ mkdir -p "$LEDGER_DIR"
146
+ touch "$LEDGER_FILE"
147
+ grep -qxF "$LABEL" "$LEDGER_FILE" || echo "$LABEL" >> "$LEDGER_FILE"
148
+
149
+ echo "[launchd-install] 完了(現場: $SITE / フォルダ: $PROJECT_DIR)"
150
+ echo "[launchd-install] 状態を見る: launchctl list | grep $LABEL"
151
+ echo "[launchd-install] 記録を見る: tail -f $LOG_DIR/conductor-${SITE}.log"
@@ -1,89 +1,13 @@
1
1
  {
2
2
  "_note": "采配くん販売用テンプレート — プロジェクトの .claude/settings.json に配置する。<PLACEHOLDER> を顧客環境に合わせて差し替えること。差し替え箇所は README.md の「プレースホルダ一覧」を参照。",
3
+ "_note_guard": "★段2(2026-07-23): 許可/確認の判定は番人(~/.tyhld/hooks の cc2_guard.py ほか3フック)が持つようになった。既定は allow(危険と関門だけを番人が deny/ask 相当で止め、それ以外は自走)。そのため settings 側の allow・ask 列挙は不要になり、空にしてある。番人フックはこのファイルではなく顧客PCの ~/.claude/settings.json に1本だけ登録する(setup.sh ⑤)。この現場ファイルには権限ルール(deny)と現場固有の sandbox だけを置く。",
4
+ "_note_deny": "★deny は番人の allow 既定より常に先に効く(Claude Code の評価順 deny→ask→allow)。ここに残す床(.env/.ssh/鍵/.tyhld/settings.json/hooks と Supabase 書込3種など)は allow を空にしても当然そのまま優先される。deny は消さない。",
5
+ "_note_network": "★外への通信は全部通す(allowedDomains: [\"*\"]・えふさん決定 2026-09-03=甲)。販売版もお客さま機で自走するのが既定(2026-08-23 決定)。理由: サンドボックスの通信許可の問いは管制の3ボタンでは押せず(Claude Code 2.1.258 本体の作り・実測 2026-09-03)、端末に人が居ないと職人が止まったままになるため。★緩むのは『電話をかける』だけ。『金庫を持ち出す』(秘密の持ち出し)を止めるのは番人 cc2_guard.py の別判定で、通信設定とは独立に効き続ける。★下の deniedDomains は allowedDomains より先に判定されるので、本番DBへの直結禁止は \"*\" を入れても効いたまま(実測で確認)。この行を消すと本番DBが開くので消さないこと。",
3
6
  "permissions": {
4
7
  "defaultMode": "default",
5
8
  "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
- ],
9
+ "allow": [],
10
+ "ask": [],
87
11
  "deny": [
88
12
  "Bash(git push --force:*)",
89
13
  "Bash(git push -f:*)",
@@ -126,30 +50,35 @@
126
50
  "Bash(echo $DIRECT_URL:*)",
127
51
  "mcp__claude_ai_Supabase__execute_sql",
128
52
  "mcp__claude_ai_Supabase__apply_migration",
129
- "mcp__claude_ai_Supabase__deploy_edge_function"
53
+ "mcp__claude_ai_Supabase__deploy_edge_function",
54
+ "Write(**/.env)",
55
+ "Edit(**/.env)",
56
+ "Write(**/.env.*)",
57
+ "Edit(**/.env.*)",
58
+ "Write(**/.claude/settings.json)",
59
+ "Edit(**/.claude/settings.json)",
60
+ "Write(**/.claude/hooks/**)",
61
+ "Edit(**/.claude/hooks/**)",
62
+ "Write(**/.tyhld/**)",
63
+ "Edit(**/.tyhld/**)",
64
+ "Write(**/.ssh/**)",
65
+ "Edit(**/.ssh/**)",
66
+ "Write(**/*.pem)",
67
+ "Edit(**/*.pem)",
68
+ "Write(**/*.key)",
69
+ "Edit(**/*.key)",
70
+ "Write(**/.npmrc)",
71
+ "Edit(**/.npmrc)"
130
72
  ]
131
73
  },
132
74
  "sandbox": {
133
75
  "enabled": true,
134
76
  "failIfUnavailable": true,
135
77
  "autoAllowBashIfSandboxed": true,
78
+ "allowUnsandboxedCommands": false,
136
79
  "network": {
137
80
  "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"
81
+ "*"
153
82
  ],
154
83
  "deniedDomains": [
155
84
  "db.<SUPABASE_PROJECT_ID>.supabase.co",