@tyhld/conductor 0.12.85 → 0.12.87
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 +11 -1
- package/dist/autoupdate.js +643 -0
- package/dist/cli.js +7 -1
- package/dist/headless.js +157 -10
- package/dist/relay.js +196 -0
- package/dist/selfcheck.js +94 -10
- package/package.json +3 -1
- package/sales-template/install.sh +22 -2
- package/sales-template/setup.sh +201 -0
- package/scripts/autoupdate-install.sh +285 -0
- package/scripts/conductor-run.sh +3 -1
- package/scripts/dist-source.sh +161 -12
- package/scripts/hooks/policy.py +84 -2
- package/scripts/hooks/skill_sync_notify.py +19 -1
- package/scripts/hooks/test_policy.py +64 -0
- package/scripts/hooks/test_skill_sync_notify.py +26 -0
- package/scripts/tyhld-autoupdate.sh +61 -0
- package/scripts/update-guard.sh +29 -7
- package/skills/conductor-craftsman/SKILL.md +13 -5
- package/systemd/build-runner-install.sh +1 -1
- package/systemd/ear-install.sh +1 -1
- package/systemd/guard-sync-install.sh +1 -1
- package/systemd/tyhld-autoupdate-boot.service +27 -0
- package/systemd/tyhld-autoupdate.service +24 -0
- package/systemd/tyhld-autoupdate.timer +20 -0
- package/systemd/tyhld-selfcheck.service +1 -1
- package/systemd/tyhld-selfcheck.timer +1 -1
package/dist/selfcheck.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
* 【結果】手元の控え(~/.local/state/conductor/selfcheck/last.json と history/)に必ず残す。
|
|
22
22
|
* 管制の口が在れば送る(★口が無い・届かないときも落ちない=送れなかったことを控えに書くだけ)。
|
|
23
|
-
* 送る中身は buildSelfcheckPayload の1か所(口の形は ai-kana の便 2f5eb92c
|
|
23
|
+
* 送る中身は buildSelfcheckPayload の1か所(口の形は ai-kana の便 2f5eb92c が決めた形・便 29babc5d で合わせた)。
|
|
24
24
|
*/
|
|
25
25
|
import { spawnSync } from 'node:child_process';
|
|
26
26
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync, } from 'node:fs';
|
|
@@ -33,7 +33,12 @@ import { readDrain } from './autoupdate-flags.js';
|
|
|
33
33
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
34
34
|
// 形
|
|
35
35
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* きっかけの名前。管制が知っているのは daily / after-conductor / after-claude / after-rollback の4つで、
|
|
38
|
+
* 知らない語(manual・candidate)は管制が null に倒す(便 2f5eb92c の決まり)。
|
|
39
|
+
* candidate … 更新係が新しい候補の版を見つけたときに流す(候補を管制へ届けるため・便 29babc5d)
|
|
40
|
+
*/
|
|
41
|
+
export const SELFCHECK_TRIGGERS = ['daily', 'after-conductor', 'after-claude', 'after-rollback', 'manual', 'candidate'];
|
|
37
42
|
export const defaultExec = (cmd, args, opts = {}) => {
|
|
38
43
|
const r = spawnSync(cmd, args, {
|
|
39
44
|
cwd: opts.cwd,
|
|
@@ -57,8 +62,16 @@ export const defaultExec = (cmd, args, opts = {}) => {
|
|
|
57
62
|
export function selfcheckDir(home) {
|
|
58
63
|
return path.join(home, '.local', 'state', 'conductor', 'selfcheck');
|
|
59
64
|
}
|
|
60
|
-
/**
|
|
61
|
-
|
|
65
|
+
/**
|
|
66
|
+
* 管制の受け口(★1か所)。ai-kana の便 2f5eb92c が作った「機体の自己診断の結果と版を受ける口」。
|
|
67
|
+
* ★以前は /api/conductor/selfcheck へ別の形で送っていて、管制に届いていなかった(便 29babc5d で直した)。
|
|
68
|
+
*/
|
|
69
|
+
export const SELFCHECK_ENDPOINT = '/api/conductor/machines/selfcheck';
|
|
70
|
+
/**
|
|
71
|
+
* Claude Code の公式の安定版の番号が置かれている所(★公式の導入台本 https://claude.ai/install.sh が
|
|
72
|
+
* 版を取りに行くのと同じ置き場。2026-10-02 に台本の中身で確かめた・stable=2.1.285)。
|
|
73
|
+
*/
|
|
74
|
+
export const CLAUDE_STABLE_URL = 'https://downloads.claude.ai/claude-code-releases/stable';
|
|
62
75
|
/** 控えの数の上限(古い物から消す)。 */
|
|
63
76
|
export const SELFCHECK_HISTORY_MAX = 30;
|
|
64
77
|
/** 小さな試験の上限時間。 */
|
|
@@ -367,9 +380,10 @@ export function npmBesideNode(execPath = process.execPath) {
|
|
|
367
380
|
const beside = path.join(path.dirname(execPath), 'npm');
|
|
368
381
|
return existsSync(beside) ? beside : null;
|
|
369
382
|
}
|
|
370
|
-
/** ⑤ GitHub・npm
|
|
383
|
+
/** ⑤ GitHub・npm(外側。内側は小さな試験の後で足す)。公開 npm の latest も返す(候補に使う)。 */
|
|
371
384
|
function checkNetworkOutside(d, version) {
|
|
372
385
|
const item = { key: 'network', label: 'GitHub・npm への通信', ok: null, detail: [] };
|
|
386
|
+
let latest = null;
|
|
373
387
|
const npm = d.findNpm();
|
|
374
388
|
if (!npm)
|
|
375
389
|
fail(item, 'fault', `配布物の node の隣に npm がありません(${path.dirname(process.execPath)})`);
|
|
@@ -377,6 +391,8 @@ function checkNetworkOutside(d, version) {
|
|
|
377
391
|
const env = { ...d.env, PATH: `${path.dirname(npm)}:${d.env.PATH ?? ''}` };
|
|
378
392
|
const r = d.exec(npm, ['view', '@tyhld/conductor', 'version', '--@tyhld:registry=https://registry.npmjs.org'], { env, timeoutMs: 60_000 });
|
|
379
393
|
const v = r.stdout.trim().match(/^\d+\.\d+\.\d+\S*/)?.[0];
|
|
394
|
+
if (r.status === 0 && v)
|
|
395
|
+
latest = v;
|
|
380
396
|
if (r.status === 0 && v)
|
|
381
397
|
pass(item, `公開 npm の @tyhld/conductor = ${v}${version && v !== version ? `(この機は ${version})` : ''}`);
|
|
382
398
|
else
|
|
@@ -400,7 +416,35 @@ function checkNetworkOutside(d, version) {
|
|
|
400
416
|
else
|
|
401
417
|
fail(item, 'fault', `GitHub の API へ届きません(終了 ${api.status})`);
|
|
402
418
|
}
|
|
403
|
-
return item;
|
|
419
|
+
return { item, latest };
|
|
420
|
+
}
|
|
421
|
+
const SEMVER = /^\d+\.\d+\.\d+$/;
|
|
422
|
+
/** 公式の安定版の番号を聞く(★取れなければ null・自己診断の合否には使わない)。 */
|
|
423
|
+
export async function fetchClaudeStable(fetchImpl) {
|
|
424
|
+
try {
|
|
425
|
+
const res = await fetchImpl(CLAUDE_STABLE_URL, { redirect: 'manual', signal: AbortSignal.timeout(15_000) });
|
|
426
|
+
if (!res.ok)
|
|
427
|
+
return null;
|
|
428
|
+
const v = (await res.text()).trim();
|
|
429
|
+
return SEMVER.test(v) ? v : null;
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* 候補の版(入っている版より新しい物だけ)。★先行機かどうかはこの機では決めない(管制が決める)。
|
|
437
|
+
* conductor … 公開 npm の latest
|
|
438
|
+
* claude-code … 公式の安定版(えふさん確定 2026-10-02 夜:安定版を追う)
|
|
439
|
+
*/
|
|
440
|
+
export function pickCandidates(installed, found) {
|
|
441
|
+
const out = [];
|
|
442
|
+
const newer = (v, now) => v !== null && SEMVER.test(v) && (now === null || !SEMVER.test(now) || compareVersions(v, now) > 0);
|
|
443
|
+
if (newer(found.conductor, installed.conductor))
|
|
444
|
+
out.push({ component: 'conductor', version: found.conductor });
|
|
445
|
+
if (newer(found.claude, installed.claude))
|
|
446
|
+
out.push({ component: 'claude-code', version: found.claude });
|
|
447
|
+
return out;
|
|
404
448
|
}
|
|
405
449
|
/** 囲いの設定だけを配布物の雛形から取り出す(★作業場所が現場と同じ囲いになるように)。 */
|
|
406
450
|
function siteSettingsFor(distRoot) {
|
|
@@ -499,18 +543,41 @@ function checkGuard(d, work) {
|
|
|
499
543
|
}
|
|
500
544
|
return item;
|
|
501
545
|
}
|
|
502
|
-
/**
|
|
546
|
+
/** 項目ごとの合否(管制の checks)。見られなかった項目は skip。 */
|
|
547
|
+
export function checksOf(r) {
|
|
548
|
+
const out = {};
|
|
549
|
+
for (const i of r.items)
|
|
550
|
+
out[i.key] = i.ok === true ? 'pass' : i.ok === false ? 'fail' : 'skip';
|
|
551
|
+
return out;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* 管制へ送る中身(★1か所)。管制の決まり(便 2f5eb92c・POST /api/conductor/machines/selfcheck)の欄で送る。
|
|
555
|
+
* 必須: machine・result(★小文字の "pass" / "fail")
|
|
556
|
+
* 決まりの欄: trigger・checks・conductorVersion・conductorBin・claudeVersion・claudeBin・rolledBack・
|
|
557
|
+
* humanActionKind・humanActionCommand・candidates
|
|
558
|
+
* ★いままでの詳しい中身(items・humanActions・mcp・drain・claudePin・usage)も一緒に載せる
|
|
559
|
+
* (管制は知らない欄を黙って捨てる=害が無い。手元の控えと同じ中身を残しておける)。
|
|
560
|
+
* ★rolledBack は「直前に旧版へ戻したか」=きっかけが after-rollback のときだけ true。
|
|
561
|
+
* ★人の手番は管制の行が1つなので、最初の1件を載せる(全部は humanActions に在る)。
|
|
562
|
+
*/
|
|
503
563
|
export function buildSelfcheckPayload(r) {
|
|
564
|
+
const first = r.humanActions[0];
|
|
504
565
|
return {
|
|
505
566
|
schema: r.schema,
|
|
506
567
|
machine: r.machine,
|
|
507
568
|
trigger: r.trigger,
|
|
508
|
-
result: r.result,
|
|
569
|
+
result: r.result === 'PASS' ? 'pass' : 'fail',
|
|
570
|
+
checks: checksOf(r),
|
|
509
571
|
startedAt: r.startedAt,
|
|
510
572
|
finishedAt: r.finishedAt,
|
|
511
573
|
conductorVersion: r.conductorVersion,
|
|
574
|
+
conductorBin: r.conductorBin,
|
|
512
575
|
claudeVersion: r.claude.version,
|
|
513
576
|
claudeBin: r.claude.bin,
|
|
577
|
+
rolledBack: r.trigger === 'after-rollback',
|
|
578
|
+
humanActionKind: first ? first.what : null,
|
|
579
|
+
humanActionCommand: first ? first.command : null,
|
|
580
|
+
candidates: r.candidates,
|
|
514
581
|
claudePin: r.claude.pin.state === 'none' ? null : r.claude.pin,
|
|
515
582
|
drain: r.drain,
|
|
516
583
|
mcp: r.mcp,
|
|
@@ -556,8 +623,21 @@ export async function sendSelfcheck(d, r) {
|
|
|
556
623
|
signal: AbortSignal.timeout(20_000),
|
|
557
624
|
});
|
|
558
625
|
const json = (res.headers.get('content-type') ?? '').includes('application/json');
|
|
559
|
-
if (res.ok && json)
|
|
626
|
+
if (res.ok && json) {
|
|
627
|
+
// ★管制は表がまだ無い(SQL を貼る前)とき 200 で { ok:false, note:「まだ準備中」 } と答える。
|
|
628
|
+
// 受け取られていないので「送れた」と数えない(落ちもしない)。
|
|
629
|
+
let body = {};
|
|
630
|
+
try {
|
|
631
|
+
body = (await res.json());
|
|
632
|
+
}
|
|
633
|
+
catch {
|
|
634
|
+
return { sent: false, detail: `HTTP ${res.status}(答えを読めません)` };
|
|
635
|
+
}
|
|
636
|
+
if (body.ok === false) {
|
|
637
|
+
return { sent: false, detail: `HTTP ${res.status}(管制は受け取っていません: ${typeof body.note === 'string' ? body.note : '理由なし'})` };
|
|
638
|
+
}
|
|
560
639
|
return { sent: true, detail: `HTTP ${res.status}` };
|
|
640
|
+
}
|
|
561
641
|
if (res.ok || (res.status >= 300 && res.status < 400)) {
|
|
562
642
|
return { sent: false, detail: `HTTP ${res.status}(JSON の答えではない=管制にまだ受け口がありません)` };
|
|
563
643
|
}
|
|
@@ -578,7 +658,8 @@ export async function runSelfcheck(d, trigger) {
|
|
|
578
658
|
const claude = checkClaude(d);
|
|
579
659
|
const central = await checkCentral(d);
|
|
580
660
|
const sandbox = checkSandboxParts(d);
|
|
581
|
-
const
|
|
661
|
+
const net = checkNetworkOutside(d, version);
|
|
662
|
+
const network = net.item;
|
|
582
663
|
const guard = checkGuard(d, path.join(runDir, 'work'));
|
|
583
664
|
// ⑥ 小さな試験(③の MCP・④の書き込み・⑤の内側の通信もここで見る)
|
|
584
665
|
const headless = { key: 'headless', label: '小さな headless 試験', ok: null, detail: [] };
|
|
@@ -640,6 +721,7 @@ export async function runSelfcheck(d, trigger) {
|
|
|
640
721
|
}
|
|
641
722
|
const items = [conductor, claude.item, central, sandbox, network, headless, guard];
|
|
642
723
|
const humanActions = items.flatMap((i) => i.human ?? []);
|
|
724
|
+
const candidates = pickCandidates({ conductor: version, claude: claude.version }, { conductor: net.latest, claude: await fetchClaudeStable(d.fetchImpl) });
|
|
643
725
|
return {
|
|
644
726
|
schema: 'conductor-selfcheck/1',
|
|
645
727
|
machine: d.machine,
|
|
@@ -661,6 +743,8 @@ export async function runSelfcheck(d, trigger) {
|
|
|
661
743
|
durationMs: sum.result.durationMs,
|
|
662
744
|
}
|
|
663
745
|
: null,
|
|
746
|
+
conductorBin: path.join(d.distRoot, 'dist', 'cli.js'),
|
|
747
|
+
candidates,
|
|
664
748
|
};
|
|
665
749
|
}
|
|
666
750
|
finally {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tyhld/conductor",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.87",
|
|
4
4
|
"description": "采配くん管制の見守りアプリ(conductor-agent)。各PCで常駐し、中央(devlog-tracker)へ定期的に生存報告(heartbeat)を送る常駐CLI。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"scripts/dist-source.sh",
|
|
24
24
|
"scripts/conductor-run.sh",
|
|
25
25
|
"scripts/tyhld-selfcheck.sh",
|
|
26
|
+
"scripts/tyhld-autoupdate.sh",
|
|
27
|
+
"scripts/autoupdate-install.sh",
|
|
26
28
|
"scripts/kc.sh",
|
|
27
29
|
"scripts/codex",
|
|
28
30
|
"!scripts/codex/__pycache__",
|
|
@@ -106,6 +106,12 @@ APT_PACKAGES=(
|
|
|
106
106
|
python3-pip
|
|
107
107
|
python3-venv
|
|
108
108
|
rsync
|
|
109
|
+
# ★囲い(sandbox)の部品。無いと職人が起動を断る(設定 sandbox.failIfUnavailable のため)。
|
|
110
|
+
# えふさん確定 2026-10-02・判断1(b)=1行インストーラでここまで入れる。
|
|
111
|
+
# 実物(2026-10-02・ノートPC): これが無くて便 7901973d が3回続けて起動に失敗した。
|
|
112
|
+
# ★先頭の確かめ(preflight)は残す=入れられなかった機では止まって案内する。
|
|
113
|
+
bubblewrap # 囲いの本体。職人の書き込みを決めた場所だけに閉じる
|
|
114
|
+
socat # 囲いの中と外をつなぐ通信の中継
|
|
109
115
|
)
|
|
110
116
|
|
|
111
117
|
# 確認だけモード(何も入れない・何も聞かない)。
|
|
@@ -387,7 +393,7 @@ run() {
|
|
|
387
393
|
"$@"
|
|
388
394
|
}
|
|
389
395
|
|
|
390
|
-
# >>> preflight >>> ★この印の間が正本。install.sh
|
|
396
|
+
# >>> preflight >>> ★この印の間が正本。install.sh・update-guard.sh・setup.sh の3つで1文字も違わないことをテストが固定する。
|
|
391
397
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
392
398
|
# 先頭の確かめ(職人が始まるのに要る物・便 ff8f4be9)
|
|
393
399
|
#
|
|
@@ -432,6 +438,14 @@ preflight_ver_lt() {
|
|
|
432
438
|
return 1
|
|
433
439
|
}
|
|
434
440
|
|
|
441
|
+
# ホームの外の置き場(★src/headless.ts の CLAUDE_SYSTEM_BIN_DIRS と同じ・テストで一致を固定)。
|
|
442
|
+
# ★検査だけが作り物のフォルダに差し替える(CONDUCTOR_PREFLIGHT_SYSTEM_DIRS)。
|
|
443
|
+
# HOME を隔離してもここは絶対パスなので、機体に claude が在ると検査が機体に依って落ちる
|
|
444
|
+
# (実測 2026-10-02 メインPC: /usr/bin/claude = 2.1.86)。中継の側は便 ff8f4be9 で
|
|
445
|
+
# 同じ差し替え(resolveClaudeBin(systemDirs))を入れてある。作りをそろえる。
|
|
446
|
+
# ★差し替えても安全は変わらない(探す場所が変わるだけ・見つからなければ need では止まる)。
|
|
447
|
+
PREFLIGHT_SYSTEM_BIN_DIRS="${CONDUCTOR_PREFLIGHT_SYSTEM_DIRS-/usr/local/bin:/usr/bin}"
|
|
448
|
+
|
|
435
449
|
# 中継と同じ候補を同じ順で出す(1行に1つ)。
|
|
436
450
|
preflight_claude_candidates() {
|
|
437
451
|
if [[ -n "${CONDUCTOR_CLAUDE_BIN:-}" ]]; then
|
|
@@ -440,6 +454,7 @@ preflight_claude_candidates() {
|
|
|
440
454
|
fi
|
|
441
455
|
local d
|
|
442
456
|
local -a nv=()
|
|
457
|
+
local -a sysdirs=()
|
|
443
458
|
for d in "$HOME/.nvm/versions/node"/v*; do
|
|
444
459
|
[[ -d "$d" ]] && nv+=("${d##*/}")
|
|
445
460
|
done
|
|
@@ -449,7 +464,12 @@ preflight_claude_candidates() {
|
|
|
449
464
|
printf '%s\n' "$HOME/.nvm/versions/node/$d/bin/claude"
|
|
450
465
|
done < <(printf '%s\n' "${nv[@]}" | sort -t. -k1.2,1nr -k2,2nr -k3,3nr)
|
|
451
466
|
fi
|
|
452
|
-
printf '%s\n' "$HOME/.local/bin/claude" "$HOME/.claude/local/claude"
|
|
467
|
+
printf '%s\n' "$HOME/.local/bin/claude" "$HOME/.claude/local/claude"
|
|
468
|
+
IFS=':' read -r -a sysdirs <<< "$PREFLIGHT_SYSTEM_BIN_DIRS"
|
|
469
|
+
for d in ${sysdirs[@]+"${sysdirs[@]}"}; do
|
|
470
|
+
[[ -n "$d" ]] && printf '%s\n' "$d/claude"
|
|
471
|
+
done
|
|
472
|
+
return 0
|
|
453
473
|
}
|
|
454
474
|
|
|
455
475
|
# その claude の入れ方に合う更新の1行(src/headless.ts の claudeUpdateHint と同じ見分け方)。
|
package/sales-template/setup.sh
CHANGED
|
@@ -287,6 +287,207 @@ else
|
|
|
287
287
|
fi
|
|
288
288
|
SVC_RESTART_HINT="${SVC_RESTART_CMD} を実行してください"
|
|
289
289
|
|
|
290
|
+
# ─── ⓪ 職人が始まるのに要る物の確かめ(★install.sh・update-guard.sh と同じ正本) ──
|
|
291
|
+
# 【なぜ setup.sh にも入れるか(えふさん確定 2026-10-02・判断5-1/前便 ff8f4be9 の要判断3)】
|
|
292
|
+
# setup.sh を単独で打つ道(Mac・試作の型・開発機の作り直し)は、1行インストーラを通らない。
|
|
293
|
+
# その道だけ確かめが無いと、囲いの部品が無い機・古い Claude Code の機に設置してしまい、
|
|
294
|
+
# 設置は成功したのに職人が1件も始まらない(実物 2026-10-02・便 7901973d が3回続けて起動失敗)。
|
|
295
|
+
# ★印の間は3つの台本で1文字も違わない(test/preflight.test.ts が固定する)。ここを手で直さない。
|
|
296
|
+
# >>> preflight >>> ★この印の間が正本。install.sh・update-guard.sh・setup.sh の3つで1文字も違わないことをテストが固定する。
|
|
297
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
298
|
+
# 先頭の確かめ(職人が始まるのに要る物・便 ff8f4be9)
|
|
299
|
+
#
|
|
300
|
+
# 【なぜ要るか(実機 2026-10-02・ノートPC)】
|
|
301
|
+
# 便 7901973d が3回続けて起動に失敗した。囲いの部品 bubblewrap・socat が入っておらず、
|
|
302
|
+
# 設定 sandbox.failIfUnavailable のため職人が起動を断っていた。更新までは気づかれずに動き、
|
|
303
|
+
# 更新の後に職人が始まらなくなった。また、最低版(えふさん確定 2.1.282)より古い
|
|
304
|
+
# Claude Code では中継が職人を始めない。=設定を配る前にここで気づき、人が打つ1行を出す。
|
|
305
|
+
#
|
|
306
|
+
# 【決め】
|
|
307
|
+
# ・確かめるだけ。★台本が自分で sudo を打たない・勝手に入れない(人の窓)。
|
|
308
|
+
# ・Claude Code は中継(src/headless.ts の resolveClaudeBin)と同じ場所を同じ順で探し、
|
|
309
|
+
# いちばん新しい版で判定する(CONDUCTOR_CLAUDE_BIN の指定があればそれだけ)。
|
|
310
|
+
# ・囲いの部品は、その機の仕組みに要る物だけを見る。
|
|
311
|
+
# Linux(WSL を含む)… bwrap と socat
|
|
312
|
+
# Mac … OS に組み込み(追加の部品は要らない)=何も求めない
|
|
313
|
+
# それ以外 … 止めずに「確かめられませんでした」と出す
|
|
314
|
+
# ・出す1行は、そのままコピペで動く形(山かっこの穴埋めなし)。
|
|
315
|
+
#
|
|
316
|
+
# 【使い方】preflight_check <claude が無いときの扱い: need|later>
|
|
317
|
+
# 結果: PREFLIGHT_NG(足りない物の説明と1行。空なら通過)/PREFLIGHT_INFO(止めない注意)
|
|
318
|
+
# /PREFLIGHT_CLAUDE_BIN(中継が使うはずの claude・見つからなければ空)
|
|
319
|
+
# ★later … 後の段で Claude Code を入れる台本(1行インストーラ)用。無いことだけは止めない
|
|
320
|
+
# (古いときは止める)。
|
|
321
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
322
|
+
# ★src/headless.ts の CLAUDE_MIN_VERSION と同じ値(テストで一致を固定)。
|
|
323
|
+
PREFLIGHT_CLAUDE_MIN='2.1.282'
|
|
324
|
+
|
|
325
|
+
# a が b より古いか(数字3つだけを見る。読めない字は 0=古い扱い)。
|
|
326
|
+
preflight_ver_lt() {
|
|
327
|
+
local a="${1%%-*}" b="${2%%-*}" i x y
|
|
328
|
+
local -a A B
|
|
329
|
+
IFS='.' read -r -a A <<< "$a"
|
|
330
|
+
IFS='.' read -r -a B <<< "$b"
|
|
331
|
+
for i in 0 1 2; do
|
|
332
|
+
x="${A[i]:-0}"; y="${B[i]:-0}"
|
|
333
|
+
[[ "$x" =~ ^[0-9]+$ ]] || x=0
|
|
334
|
+
[[ "$y" =~ ^[0-9]+$ ]] || y=0
|
|
335
|
+
if (( x < y )); then return 0; fi
|
|
336
|
+
if (( x > y )); then return 1; fi
|
|
337
|
+
done
|
|
338
|
+
return 1
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
# ホームの外の置き場(★src/headless.ts の CLAUDE_SYSTEM_BIN_DIRS と同じ・テストで一致を固定)。
|
|
342
|
+
# ★検査だけが作り物のフォルダに差し替える(CONDUCTOR_PREFLIGHT_SYSTEM_DIRS)。
|
|
343
|
+
# HOME を隔離してもここは絶対パスなので、機体に claude が在ると検査が機体に依って落ちる
|
|
344
|
+
# (実測 2026-10-02 メインPC: /usr/bin/claude = 2.1.86)。中継の側は便 ff8f4be9 で
|
|
345
|
+
# 同じ差し替え(resolveClaudeBin(systemDirs))を入れてある。作りをそろえる。
|
|
346
|
+
# ★差し替えても安全は変わらない(探す場所が変わるだけ・見つからなければ need では止まる)。
|
|
347
|
+
PREFLIGHT_SYSTEM_BIN_DIRS="${CONDUCTOR_PREFLIGHT_SYSTEM_DIRS-/usr/local/bin:/usr/bin}"
|
|
348
|
+
|
|
349
|
+
# 中継と同じ候補を同じ順で出す(1行に1つ)。
|
|
350
|
+
preflight_claude_candidates() {
|
|
351
|
+
if [[ -n "${CONDUCTOR_CLAUDE_BIN:-}" ]]; then
|
|
352
|
+
printf '%s\n' "$CONDUCTOR_CLAUDE_BIN"
|
|
353
|
+
return 0
|
|
354
|
+
fi
|
|
355
|
+
local d
|
|
356
|
+
local -a nv=()
|
|
357
|
+
local -a sysdirs=()
|
|
358
|
+
for d in "$HOME/.nvm/versions/node"/v*; do
|
|
359
|
+
[[ -d "$d" ]] && nv+=("${d##*/}")
|
|
360
|
+
done
|
|
361
|
+
if (( ${#nv[@]} > 0 )); then
|
|
362
|
+
# 新しい node の版から(v24.21.0 → 24 21 0 で降順)
|
|
363
|
+
while IFS= read -r d; do
|
|
364
|
+
printf '%s\n' "$HOME/.nvm/versions/node/$d/bin/claude"
|
|
365
|
+
done < <(printf '%s\n' "${nv[@]}" | sort -t. -k1.2,1nr -k2,2nr -k3,3nr)
|
|
366
|
+
fi
|
|
367
|
+
printf '%s\n' "$HOME/.local/bin/claude" "$HOME/.claude/local/claude"
|
|
368
|
+
IFS=':' read -r -a sysdirs <<< "$PREFLIGHT_SYSTEM_BIN_DIRS"
|
|
369
|
+
for d in ${sysdirs[@]+"${sysdirs[@]}"}; do
|
|
370
|
+
[[ -n "$d" ]] && printf '%s\n' "$d/claude"
|
|
371
|
+
done
|
|
372
|
+
return 0
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
# その claude の入れ方に合う更新の1行(src/headless.ts の claudeUpdateHint と同じ見分け方)。
|
|
376
|
+
preflight_claude_update_line() {
|
|
377
|
+
local bin="$1" real="$1" t i
|
|
378
|
+
# 実体を辿る(★readlink -f は使わない=無いパスでも文字列を返すため。1段ずつ辿る)
|
|
379
|
+
for i in 1 2 3 4 5 6 7 8; do
|
|
380
|
+
[[ -L "$real" ]] || break
|
|
381
|
+
t="$(readlink "$real" 2>/dev/null || true)"
|
|
382
|
+
[[ -n "$t" ]] || break
|
|
383
|
+
case "$t" in
|
|
384
|
+
/*) real="$t" ;;
|
|
385
|
+
*) real="$(dirname "$real")/$t" ;;
|
|
386
|
+
esac
|
|
387
|
+
done
|
|
388
|
+
case "$real$bin" in
|
|
389
|
+
*/node_modules/@anthropic-ai/claude-code/*|*/.nvm/versions/node/*)
|
|
390
|
+
printf ' PATH="%s:$PATH" npm install -g @anthropic-ai/claude-code@latest\n' "$(dirname "$bin")"
|
|
391
|
+
return 0 ;;
|
|
392
|
+
esac
|
|
393
|
+
case "$real" in
|
|
394
|
+
"$HOME"/.local/share/claude/*|"$HOME"/.claude/local/*)
|
|
395
|
+
printf ' "%s" update\n' "$bin"
|
|
396
|
+
return 0 ;;
|
|
397
|
+
esac
|
|
398
|
+
printf ' (入れ方を見分けられませんでした。どちらか合う方を打ってください)\n'
|
|
399
|
+
printf ' ネイティブ導入なら: "%s" update\n' "$bin"
|
|
400
|
+
printf ' npm 導入なら : npm install -g @anthropic-ai/claude-code@latest\n'
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
preflight_check() {
|
|
404
|
+
local claude_mode="${1:-need}" c v best='' best_v='' unknown='' os missing=''
|
|
405
|
+
PREFLIGHT_NG=''
|
|
406
|
+
PREFLIGHT_INFO=''
|
|
407
|
+
PREFLIGHT_CLAUDE_BIN=''
|
|
408
|
+
|
|
409
|
+
# ── ① Claude Code が在り、最低版以上か ──
|
|
410
|
+
while IFS= read -r c; do
|
|
411
|
+
[[ -n "$c" && -x "$c" ]] || continue
|
|
412
|
+
v="$("$c" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1 || true)"
|
|
413
|
+
if [[ -z "$v" ]]; then
|
|
414
|
+
[[ -n "$unknown" ]] || unknown="$c"
|
|
415
|
+
continue
|
|
416
|
+
fi
|
|
417
|
+
if [[ -z "$best_v" ]] || preflight_ver_lt "$best_v" "$v"; then
|
|
418
|
+
best="$c"; best_v="$v"
|
|
419
|
+
fi
|
|
420
|
+
done < <(preflight_claude_candidates)
|
|
421
|
+
|
|
422
|
+
# shellcheck disable=SC2034 # update-guard.sh の ⓪-b が使う(install.sh では使わない)
|
|
423
|
+
PREFLIGHT_CLAUDE_BIN="${best:-$unknown}"
|
|
424
|
+
if [[ -n "$best_v" ]]; then
|
|
425
|
+
if preflight_ver_lt "$best_v" "$PREFLIGHT_CLAUDE_MIN"; then
|
|
426
|
+
PREFLIGHT_NG+="■ 職人のソフト(Claude Code)が古いです(${best} = ${best_v}、必要な版 ${PREFLIGHT_CLAUDE_MIN} 以上)。"$'\n'
|
|
427
|
+
PREFLIGHT_NG+=" この機の入れ方に合う1行:"$'\n'
|
|
428
|
+
PREFLIGHT_NG+="$(preflight_claude_update_line "$best")"$'\n'
|
|
429
|
+
else
|
|
430
|
+
PREFLIGHT_INFO+="✓ 職人のソフト(Claude Code): ${best} = ${best_v}(必要な版 ${PREFLIGHT_CLAUDE_MIN} 以上)"$'\n'
|
|
431
|
+
fi
|
|
432
|
+
elif [[ -n "$unknown" ]]; then
|
|
433
|
+
# ★中継も版の読めない claude は止めずに使う。ここでも止めない(作りをそろえる)。
|
|
434
|
+
PREFLIGHT_INFO+="⚠ 職人のソフト(Claude Code)の版を確かめられませんでした(${unknown})。"$'\n'
|
|
435
|
+
elif [[ "$claude_mode" == "need" ]]; then
|
|
436
|
+
PREFLIGHT_NG+="■ 職人のソフト(Claude Code)が見つかりません。"$'\n'
|
|
437
|
+
PREFLIGHT_NG+=" 入れる1行:"$'\n'
|
|
438
|
+
PREFLIGHT_NG+=" npm install -g @anthropic-ai/claude-code@latest"$'\n'
|
|
439
|
+
fi
|
|
440
|
+
|
|
441
|
+
# ── ② 囲いの部品(その機の仕組みに要る物だけ) ──
|
|
442
|
+
os="$(uname -s 2>/dev/null || echo unknown)"
|
|
443
|
+
case "$os" in
|
|
444
|
+
Linux)
|
|
445
|
+
command -v bwrap >/dev/null 2>&1 || missing+=' bwrap(bubblewrap)'
|
|
446
|
+
command -v socat >/dev/null 2>&1 || missing+=' socat'
|
|
447
|
+
if [[ -n "$missing" ]]; then
|
|
448
|
+
PREFLIGHT_NG+="■ 囲いの部品が足りません:${missing}"$'\n'
|
|
449
|
+
PREFLIGHT_NG+=" (無いと、職人が安全のため起動を断ります)"$'\n'
|
|
450
|
+
if command -v apt-get >/dev/null 2>&1; then
|
|
451
|
+
PREFLIGHT_NG+=" 入れる1行(Ubuntu のパスワードを聞かれます):"$'\n'
|
|
452
|
+
PREFLIGHT_NG+=" sudo apt-get install -y bubblewrap socat"$'\n'
|
|
453
|
+
# ★WSL かどうか(検査は CONDUCTOR_PROC_VERSION_FILE で見本を渡せる・既定は本物)
|
|
454
|
+
if [[ -r "${CONDUCTOR_PROC_VERSION_FILE:-/proc/version}" ]] && grep -qi microsoft "${CONDUCTOR_PROC_VERSION_FILE:-/proc/version}"; then
|
|
455
|
+
PREFLIGHT_NG+=" sudo のパスワードが分からないとき(Windows の PowerShell で打つ):"$'\n'
|
|
456
|
+
PREFLIGHT_NG+=" wsl.exe -u root apt-get install -y bubblewrap socat"$'\n'
|
|
457
|
+
fi
|
|
458
|
+
else
|
|
459
|
+
PREFLIGHT_NG+=" この機の入れ方で bubblewrap と socat を入れてください(apt-get が見つかりません)。"$'\n'
|
|
460
|
+
fi
|
|
461
|
+
else
|
|
462
|
+
PREFLIGHT_INFO+="✓ 囲いの部品: bwrap・socat"$'\n'
|
|
463
|
+
fi
|
|
464
|
+
;;
|
|
465
|
+
Darwin)
|
|
466
|
+
PREFLIGHT_INFO+="✓ 囲いの部品: Mac は OS に組み込み(追加の部品は要りません)"$'\n'
|
|
467
|
+
;;
|
|
468
|
+
*)
|
|
469
|
+
PREFLIGHT_INFO+="⚠ 囲いの部品: この機(${os})では確かめられませんでした(止めずに進みます)。"$'\n'
|
|
470
|
+
;;
|
|
471
|
+
esac
|
|
472
|
+
[[ -z "$PREFLIGHT_NG" ]]
|
|
473
|
+
}
|
|
474
|
+
# <<< preflight <<<
|
|
475
|
+
|
|
476
|
+
step "⓪ 職人が始まるのに要る物の確かめ(★ここで足りなければ1つも置きません)"
|
|
477
|
+
# ★`later`(職人が【無いこと】だけはここで止めない)を使う理由。
|
|
478
|
+
# setup.sh は Claude Code を入れないが、「無い」ときの案内は ① が持っている
|
|
479
|
+
# (入れる2行+ログインの手順=お客さまがそのまま読める形)。ここで `need` にすると
|
|
480
|
+
# その案内に届かず、短い1行だけで終わってしまう。★古い版・囲いの部品不足は、ここで止める。
|
|
481
|
+
if preflight_check later; then
|
|
482
|
+
if [[ -n "$PREFLIGHT_INFO" ]]; then printf '%s' "$PREFLIGHT_INFO"; fi
|
|
483
|
+
else
|
|
484
|
+
printf '%s' "$PREFLIGHT_INFO" >&9
|
|
485
|
+
echo >&9
|
|
486
|
+
printf '%s' "$PREFLIGHT_NG" >&9
|
|
487
|
+
echo "[setup] 上の1行を打ってから、もう一度 setup.sh を実行してください。" >&9
|
|
488
|
+
exit 1
|
|
489
|
+
fi
|
|
490
|
+
|
|
290
491
|
# ─── ① 前提チェック ──────────────────────────────────────────────────────────
|
|
291
492
|
step "① 前提チェック"
|
|
292
493
|
|