@tyhld/conductor 0.12.85 → 0.12.86
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/selfcheck.js +94 -10
- package/package.json +3 -1
- package/scripts/autoupdate-install.sh +285 -0
- package/scripts/conductor-run.sh +3 -1
- package/scripts/dist-source.sh +161 -12
- package/scripts/hooks/skill_sync_notify.py +19 -1
- package/scripts/hooks/test_skill_sync_notify.py +26 -0
- package/scripts/tyhld-autoupdate.sh +61 -0
- package/scripts/update-guard.sh +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/cli.js
CHANGED
|
@@ -26,6 +26,7 @@ import { ensureEnvFilePermissions } from './env-file-perm.js';
|
|
|
26
26
|
import { createCentralPorts } from './build-central.js';
|
|
27
27
|
import { drainReport, readDrain } from './autoupdate-flags.js';
|
|
28
28
|
import { runSelfcheckCommand } from './selfcheck.js';
|
|
29
|
+
import { runAutoupdateCommand } from './autoupdate.js';
|
|
29
30
|
import { BUILD_NUDGE_NAME, REQUIRED_ENV, createBuildRunner, createMemoStore, loadBuildRunnerConfig, } from './build-runner.js';
|
|
30
31
|
/**
|
|
31
32
|
* relay の状態の参照(生存報告に「いま配れるか」を載せるため)。
|
|
@@ -499,8 +500,9 @@ function printUsage() {
|
|
|
499
500
|
' conductor build-runner この機に1本の「司令塔」(試作の段取り役)',
|
|
500
501
|
' conductor setup <現場名> [フォルダ] [--profile <default|proto>] 采配くんをこのPCに設置する(初回の1回だけ)',
|
|
501
502
|
' conductor view <現場名> [--once] [--detail] その現場の今を見るだけの画面(kc の実体)',
|
|
502
|
-
' conductor selfcheck [--trigger <daily|after-conductor|after-claude|after-rollback|manual>] [--json] [--no-send]',
|
|
503
|
+
' conductor selfcheck [--trigger <daily|after-conductor|after-claude|after-rollback|manual|candidate>] [--json] [--no-send]',
|
|
503
504
|
' この機の自己診断(副作用なし・結果は手元の控えと管制へ)',
|
|
505
|
+
' conductor autoupdate [--boot] 自社PCの更新係(鍵が無い機では何もしない・systemd から呼ぶ)',
|
|
504
506
|
'',
|
|
505
507
|
'見るだけの画面(conductor view = kc 現場名):',
|
|
506
508
|
' いま職人が何をしているかを、やさしい日本語で見るだけの画面です。',
|
|
@@ -590,6 +592,10 @@ async function main() {
|
|
|
590
592
|
// ★自己診断(便 b7bd5e43・A1)。副作用なし(commit・push・PR・合流・配備・公開をしない)。
|
|
591
593
|
process.exit(await runSelfcheckCommand(process.argv.slice(3)));
|
|
592
594
|
break;
|
|
595
|
+
case 'autoupdate':
|
|
596
|
+
// ★更新係(便 29babc5d・A3)。鍵(~/.tyhld/bin/autoupdate/enabled)が無い機では何もしない。
|
|
597
|
+
process.exit(await runAutoupdateCommand(process.argv.slice(3)));
|
|
598
|
+
break;
|
|
593
599
|
case 'view':
|
|
594
600
|
// ★見るだけの画面。職人も tmux も relay も起動せず、入力も受け付けない。
|
|
595
601
|
// 既定は「開いたまま流れ続ける」ので、Ctrl+C まで戻らない(--once は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.86",
|
|
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__",
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# autoupdate-install.sh — 自社PCに「自動更新」を入れる(便 29babc5d・設計 3ed9e2b2 の A3)。
|
|
4
|
+
#
|
|
5
|
+
# 【誰が・いつ打つか】★人が、自社PCの更新の窓で1回だけ打つ(えふさん確定 2026-10-02)。
|
|
6
|
+
# まず確認だけ: bash "$(npm root -g)/@tyhld/conductor/scripts/autoupdate-install.sh"
|
|
7
|
+
# 入れる: APPLY=1 bash "$(npm root -g)/@tyhld/conductor/scripts/autoupdate-install.sh"
|
|
8
|
+
# ★sudo は使わない(要る物は「人が打つ1行」として出して止まる)。何度打っても同じ結果になる。
|
|
9
|
+
#
|
|
10
|
+
# 【★お客さまの機には入れない】sales-template/install.sh・setup.sh はこの台本を呼ばない
|
|
11
|
+
# (自社で実績を取ってから、販売版の受入条件として別に判断する=えふさん確定 2026-10-02)。
|
|
12
|
+
#
|
|
13
|
+
# 【やること】
|
|
14
|
+
# ⓪ 確かめ … systemd(ユーザー)が使える/入っている采配くん(公開済みの配布物)/接続設定/
|
|
15
|
+
# 囲いの部品(bwrap・socat)/Claude Code の入り方
|
|
16
|
+
# ★Claude Code が公式推奨の1方式(https://claude.ai/install.sh のネイティブ導入)だけになっていなければ、
|
|
17
|
+
# 人が打つ1行を出して止まる(★この台本は自分で外さない・入れない)。
|
|
18
|
+
# ① 鍵 … ~/.tyhld/bin/autoupdate/enabled(★職人が書けない場所。これが在る機だけ更新係が動く)
|
|
19
|
+
# ② 入口 … ~/.tyhld/bin/tyhld-autoupdate・tyhld-selfcheck(+常駐の入口一式・隣に dist-source.sh)
|
|
20
|
+
# ③ systemd … tyhld-autoupdate-boot.service(起動時1回)・tyhld-autoupdate.timer(数分ごと)・
|
|
21
|
+
# tyhld-selfcheck.timer(毎日)。常駐(conductor@・耳・司令塔)は起動時の1回の後に起きる
|
|
22
|
+
# (常駐の側に After= の drop-in を置く)。撤去の台帳にも載せる。
|
|
23
|
+
# ④ Claude Code 自身の自動更新を止める … ~/.claude/settings.json の env に DISABLE_AUTOUPDATER=1
|
|
24
|
+
# (控えを取ってから。★DISABLE_UPDATES は使わない=人の claude update まで塞ぐため)。
|
|
25
|
+
# ★上げるのは更新係1つだけ(管制の目標の版を、公式の台本で名指しして入れる)。
|
|
26
|
+
# ⑤ Windows 側で打つ2行を表示する(WSL を起こして居続けさせる近道・AC 接続時に眠らせない)。
|
|
27
|
+
#
|
|
28
|
+
# 【試験用の入口】★安全境界ではない(ほかの CONDUCTOR_* と同じ)。
|
|
29
|
+
# AUTOUPDATE_SYSTEM_BIN_DIRS … ホームの外の claude の置き場(既定 "/usr/local/bin /usr/bin")
|
|
30
|
+
# CONDUCTOR_PROC_VERSION_FILE … WSL かどうかを見る見本(既定 /proc/version)
|
|
31
|
+
# AUTOUPDATE_SKIP_SYSTEMD_CHECK=1 … systemd の有無の確かめを飛ばす(作り物の systemctl で流すとき)
|
|
32
|
+
set -uo pipefail
|
|
33
|
+
|
|
34
|
+
APPLY="${APPLY:-0}"
|
|
35
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
36
|
+
. "$HERE/dist-source.sh"
|
|
37
|
+
|
|
38
|
+
UNIT_DIR="$HOME/.config/systemd/user"
|
|
39
|
+
BIN_DIR="$HOME/.tyhld/bin"
|
|
40
|
+
AU_DIR="$BIN_DIR/autoupdate"
|
|
41
|
+
KEY="$AU_DIR/enabled"
|
|
42
|
+
LEDGER_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/conductor"
|
|
43
|
+
LEDGER_FILE="$LEDGER_DIR/installed-units.txt"
|
|
44
|
+
CLAUDE_SETTINGS="$HOME/.claude/settings.json"
|
|
45
|
+
SYSTEM_BIN_DIRS="${AUTOUPDATE_SYSTEM_BIN_DIRS:-/usr/local/bin /usr/bin}"
|
|
46
|
+
UNITS=(tyhld-autoupdate-boot.service tyhld-autoupdate.service tyhld-autoupdate.timer tyhld-selfcheck.service tyhld-selfcheck.timer)
|
|
47
|
+
ORDER_DROPIN_NAME='10-tyhld-autoupdate.conf'
|
|
48
|
+
RELAY_UNITS=(conductor@.service conductor-ear.service conductor-build-runner.service)
|
|
49
|
+
|
|
50
|
+
info() { echo "[autoupdate-install] $*"; }
|
|
51
|
+
warn() { echo "[autoupdate-install] 注意: $*" >&2; }
|
|
52
|
+
|
|
53
|
+
NG=() # 止まる理由(人が打つ1行つき)
|
|
54
|
+
ng() { NG+=("$1"); }
|
|
55
|
+
|
|
56
|
+
if [[ "$APPLY" == "1" ]]; then info "モード: 適用"; else info "モード: 確認のみ(1バイトも書きません。入れるには APPLY=1 を付けて打ってください)"; fi
|
|
57
|
+
|
|
58
|
+
# ─── ⓪ 確かめ ───────────────────────────────────────────────────────────────
|
|
59
|
+
info "⓪ 確かめ"
|
|
60
|
+
|
|
61
|
+
# systemd(ユーザー)
|
|
62
|
+
if [[ "${AUTOUPDATE_SKIP_SYSTEMD_CHECK:-0}" != "1" ]]; then
|
|
63
|
+
if ! command -v systemctl >/dev/null 2>&1 || ! systemctl --user show-environment >/dev/null 2>&1; then
|
|
64
|
+
ng "systemd(ユーザー)が使えません。WSL なら /etc/wsl.conf に [boot] systemd=true が要ります(人の手番・CC1 に知らせてください)"
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# 入っている采配くん(★公開済みの配布物。作業ツリーは使わない)
|
|
69
|
+
if dist_runtime_resolve; then
|
|
70
|
+
info " 采配くん: $DIST_ROOT(版 $DIST_VERSION)"
|
|
71
|
+
else
|
|
72
|
+
ng "動かす采配くんを確かめられません(${DIST_REASON})。
|
|
73
|
+
npm install -g @tyhld/conductor --@tyhld:registry=https://registry.npmjs.org"
|
|
74
|
+
fi
|
|
75
|
+
for f in scripts/tyhld-autoupdate.sh scripts/tyhld-selfcheck.sh systemd/tyhld-autoupdate-boot.service systemd/tyhld-autoupdate.service systemd/tyhld-autoupdate.timer systemd/tyhld-selfcheck.service systemd/tyhld-selfcheck.timer; do
|
|
76
|
+
if [[ -n "$DIST_ROOT" && ! -f "$DIST_ROOT/$f" ]]; then
|
|
77
|
+
ng "入っている采配くん(版 ${DIST_VERSION:-不明})に $f がありません=自動更新が入る前の版です。
|
|
78
|
+
npm install -g @tyhld/conductor --@tyhld:registry=https://registry.npmjs.org"
|
|
79
|
+
break
|
|
80
|
+
fi
|
|
81
|
+
done
|
|
82
|
+
|
|
83
|
+
# 接続設定(管制に目標の版を聞く・自己診断を届けるのに要る)。★中身は読まない。
|
|
84
|
+
if [[ -f "$HOME/.conductor.env" ]]; then
|
|
85
|
+
info " 接続設定: $HOME/.conductor.env が在ります"
|
|
86
|
+
else
|
|
87
|
+
ng "接続設定($HOME/.conductor.env)がありません。この機は采配くんの常駐が入っていません(CC1 に知らせてください)"
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# 囲いの部品(無いと職人が起動を断る)
|
|
91
|
+
if [[ "$(uname -s 2>/dev/null)" == "Linux" ]]; then
|
|
92
|
+
missing=()
|
|
93
|
+
for b in bwrap socat; do command -v "$b" >/dev/null 2>&1 || missing+=("$b"); done
|
|
94
|
+
if (( ${#missing[@]} > 0 )); then
|
|
95
|
+
ng "囲いの部品がありません(${missing[*]})。
|
|
96
|
+
sudo apt-get install -y bubblewrap socat"
|
|
97
|
+
else
|
|
98
|
+
info " 囲いの部品: bwrap・socat が在ります"
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# Claude Code の入り方(★公式推奨=ネイティブ導入の1方式だけにそろえる・えふさん確定 2026-10-02 夜)
|
|
103
|
+
# 公式(code.claude.com/docs/en/setup)で推奨と明記されているのは Native Install (Recommended)。
|
|
104
|
+
# ネイティブ導入は ~/.local/bin/claude → ~/.local/share/claude/versions/<版>。
|
|
105
|
+
# ★ほかの入り方が残っていると、中継がいちばん新しい方を選び、更新係が戻せない(設計 3ed9e2b2 の発見4)。
|
|
106
|
+
NATIVE_LINK="$HOME/.local/bin/claude"
|
|
107
|
+
NATIVE_DIR="$HOME/.local/share/claude/versions"
|
|
108
|
+
native_ok=0
|
|
109
|
+
if [[ -e "$NATIVE_LINK" ]]; then
|
|
110
|
+
real="$(readlink -f "$NATIVE_LINK" 2>/dev/null || printf '%s' "$NATIVE_LINK")"
|
|
111
|
+
[[ "$real" == "$NATIVE_DIR/"* ]] && native_ok=1
|
|
112
|
+
fi
|
|
113
|
+
others=()
|
|
114
|
+
if [[ -d "$HOME/.nvm/versions/node" ]]; then
|
|
115
|
+
for d in "$HOME"/.nvm/versions/node/*/bin; do
|
|
116
|
+
[[ -e "$d/claude" ]] && others+=("$d/claude")
|
|
117
|
+
done
|
|
118
|
+
fi
|
|
119
|
+
[[ -e "$HOME/.claude/local/claude" ]] && others+=("$HOME/.claude/local/claude")
|
|
120
|
+
for d in $SYSTEM_BIN_DIRS; do
|
|
121
|
+
[[ -e "$d/claude" ]] && others+=("$d/claude")
|
|
122
|
+
done
|
|
123
|
+
if (( native_ok == 0 )) && [[ -e "$NATIVE_LINK" ]]; then
|
|
124
|
+
others+=("$NATIVE_LINK")
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
claude_lines=()
|
|
128
|
+
if (( native_ok == 0 )); then
|
|
129
|
+
claude_lines+=("curl -fsSL https://claude.ai/install.sh | bash -s stable")
|
|
130
|
+
fi
|
|
131
|
+
for c in "${others[@]}"; do
|
|
132
|
+
real="$(readlink -f "$c" 2>/dev/null || printf '%s' "$c")"
|
|
133
|
+
case "$c" in
|
|
134
|
+
"$HOME"/.nvm/versions/node/*/bin/claude)
|
|
135
|
+
claude_lines+=("PATH=\"$(dirname "$c"):\$PATH\" npm uninstall -g @anthropic-ai/claude-code")
|
|
136
|
+
;;
|
|
137
|
+
"$HOME/.claude/local/claude")
|
|
138
|
+
claude_lines+=("rm -r \"\$HOME/.claude/local\"")
|
|
139
|
+
;;
|
|
140
|
+
"$NATIVE_LINK")
|
|
141
|
+
claude_lines+=("rm \"\$HOME/.local/bin/claude\"")
|
|
142
|
+
;;
|
|
143
|
+
*)
|
|
144
|
+
if [[ "$real" == */lib/node_modules/@anthropic-ai/claude-code/* ]]; then
|
|
145
|
+
prefix="${real%%/lib/node_modules/*}"
|
|
146
|
+
if [[ -x "$prefix/bin/npm" ]]; then
|
|
147
|
+
claude_lines+=("sudo \"$prefix/bin/npm\" uninstall -g @anthropic-ai/claude-code")
|
|
148
|
+
else
|
|
149
|
+
claude_lines+=("sudo rm \"$c\"" "sudo rm -r \"$prefix/lib/node_modules/@anthropic-ai/claude-code\"")
|
|
150
|
+
fi
|
|
151
|
+
else
|
|
152
|
+
ng "Claude Code の入り方を見分けられない物があります: $c(実体 $real)。この台本は外しません。CC1 に知らせてください。
|
|
153
|
+
ls -l \"$c\""
|
|
154
|
+
fi
|
|
155
|
+
;;
|
|
156
|
+
esac
|
|
157
|
+
done
|
|
158
|
+
if (( ${#claude_lines[@]} > 0 )); then
|
|
159
|
+
msg="Claude Code が公式推奨の1方式(ネイティブ導入)だけになっていません(ネイティブ導入: $([[ $native_ok == 1 ]] && echo 在る || echo 無い)・ほか: ${others[*]:-無し})。
|
|
160
|
+
★この台本は自分で外さない・入れない。上から順に打ってから、もう一度この台本を打ってください:"
|
|
161
|
+
for l in "${claude_lines[@]}"; do msg+=$'\n'" $l"; done
|
|
162
|
+
ng "$msg"
|
|
163
|
+
else
|
|
164
|
+
info " Claude Code: 公式推奨の1方式だけです($(readlink -f "$NATIVE_LINK" 2>/dev/null))"
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
# ─── ⑤ Windows 側で打つ2行(★止まるときも先に見せる)────────────────────────────
|
|
168
|
+
PROC="${CONDUCTOR_PROC_VERSION_FILE:-/proc/version}"
|
|
169
|
+
windows_lines() {
|
|
170
|
+
local distro="${WSL_DISTRO_NAME:-Ubuntu}"
|
|
171
|
+
echo
|
|
172
|
+
info "⑤ Windows の PowerShell で打つ2行(管理者でなくてよい・1回だけ):"
|
|
173
|
+
echo " 1) ログオンのときに WSL(${distro})を起こし、最小化の窓で居続けさせる近道を置く"
|
|
174
|
+
printf '%s\n' "\$s=(New-Object -ComObject WScript.Shell).CreateShortcut(\"\$env:APPDATA\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\tyhld-wsl.lnk\");\$s.TargetPath=\"\$env:WINDIR\\System32\\wsl.exe\";\$s.Arguments=\"-d ${distro} --exec /bin/sleep infinity\";\$s.WindowStyle=7;\$s.Save()"
|
|
175
|
+
echo " 2) 電源(AC)につないでいるあいだは眠らせない"
|
|
176
|
+
echo "powercfg /change standby-timeout-ac 0"
|
|
177
|
+
echo " ★その窓(最小化の wsl)を閉じると WSL が止まり得ます。Windows のログオンは人のままです。"
|
|
178
|
+
}
|
|
179
|
+
IS_WSL=0
|
|
180
|
+
if [[ -r "$PROC" ]] && grep -qi microsoft "$PROC"; then IS_WSL=1; fi
|
|
181
|
+
|
|
182
|
+
if (( ${#NG[@]} > 0 )); then
|
|
183
|
+
echo
|
|
184
|
+
warn "足りない物があります(★1バイトも書いていません)。上から順に直してから、もう一度打ってください:"
|
|
185
|
+
for m in "${NG[@]}"; do echo " ✗ $m" >&2; done
|
|
186
|
+
(( IS_WSL == 1 )) && windows_lines
|
|
187
|
+
exit 1
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
# ─── ①〜④ 入れる ───────────────────────────────────────────────────────────
|
|
191
|
+
if [[ "$APPLY" != "1" ]]; then
|
|
192
|
+
info "①(予定)鍵を置きます: $KEY"
|
|
193
|
+
info "②(予定)入口を置きます: $BIN_DIR/tyhld-autoupdate・tyhld-selfcheck(配布物 $DIST_ROOT から)"
|
|
194
|
+
info "③(予定)systemd: ${UNITS[*]}(起動時の1回・数分ごと・毎日の自己診断)"
|
|
195
|
+
info " (予定)常駐(${RELAY_UNITS[*]})を起動時の1回の後に起こす drop-in"
|
|
196
|
+
info "④(予定)$CLAUDE_SETTINGS の env に DISABLE_AUTOUPDATER=1(控えを取ってから)"
|
|
197
|
+
(( IS_WSL == 1 )) && windows_lines
|
|
198
|
+
echo
|
|
199
|
+
info "確認だけで終わりました。入れるには: APPLY=1 bash \"\$(npm root -g)/@tyhld/conductor/scripts/autoupdate-install.sh\""
|
|
200
|
+
exit 0
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
ledger_add() {
|
|
204
|
+
mkdir -p "$LEDGER_DIR"
|
|
205
|
+
touch "$LEDGER_FILE"
|
|
206
|
+
grep -qxF "$1" "$LEDGER_FILE" || echo "$1" >> "$LEDGER_FILE"
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
# ① 鍵
|
|
210
|
+
mkdir -p "$AU_DIR"
|
|
211
|
+
if [[ ! -f "$KEY" ]]; then
|
|
212
|
+
printf '%s installed-by=autoupdate-install.sh version=%s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z')" "$DIST_VERSION" > "$KEY"
|
|
213
|
+
fi
|
|
214
|
+
info "① 鍵: $KEY"
|
|
215
|
+
|
|
216
|
+
# ② 入口(常駐の入口一式+更新係・自己診断)
|
|
217
|
+
if ! dist_runtime_bundle_install "$DIST_ROOT"; then
|
|
218
|
+
warn "常駐の入口を置けませんでした: $BIN_DIR(★ここで止めます)"
|
|
219
|
+
exit 1
|
|
220
|
+
fi
|
|
221
|
+
dist_runtime_hint_write "$DIST_ROOT"
|
|
222
|
+
for pair in "scripts/tyhld-autoupdate.sh:tyhld-autoupdate" "scripts/tyhld-selfcheck.sh:tyhld-selfcheck"; do
|
|
223
|
+
from="$DIST_ROOT/${pair%%:*}"; to="$BIN_DIR/${pair##*:}"
|
|
224
|
+
cp -f "$from" "$to.tmp" && mv -f "$to.tmp" "$to" && chmod 755 "$to" && cmp -s "$from" "$to" || {
|
|
225
|
+
warn "入口を置けませんでした: $to(★ここで止めます)"; exit 1; }
|
|
226
|
+
done
|
|
227
|
+
info "② 入口: $BIN_DIR/tyhld-autoupdate・$BIN_DIR/tyhld-selfcheck(配布物 $DIST_ROOT・版 $DIST_VERSION)"
|
|
228
|
+
|
|
229
|
+
# ③ systemd
|
|
230
|
+
mkdir -p "$UNIT_DIR"
|
|
231
|
+
for u in "${UNITS[@]}"; do
|
|
232
|
+
cp -f "$DIST_ROOT/systemd/$u" "$UNIT_DIR/$u"
|
|
233
|
+
done
|
|
234
|
+
for r in "${RELAY_UNITS[@]}"; do
|
|
235
|
+
mkdir -p "$UNIT_DIR/$r.d"
|
|
236
|
+
printf '%s\n' \
|
|
237
|
+
'# tyhld: 常駐は、起動時の更新係(tyhld-autoupdate-boot)の後に起きる(autoupdate-install.sh が置いた)。' \
|
|
238
|
+
'# ★After= だけ(更新係が失敗しても常駐は起きる)。' \
|
|
239
|
+
'[Unit]' \
|
|
240
|
+
'After=tyhld-autoupdate-boot.service' > "$UNIT_DIR/$r.d/$ORDER_DROPIN_NAME"
|
|
241
|
+
done
|
|
242
|
+
systemctl --user daemon-reload
|
|
243
|
+
systemctl --user enable tyhld-autoupdate-boot.service >/dev/null 2>&1 || warn "enable に失敗しました: tyhld-autoupdate-boot.service"
|
|
244
|
+
for t in tyhld-autoupdate.timer tyhld-selfcheck.timer; do
|
|
245
|
+
systemctl --user enable --now "$t" >/dev/null 2>&1 || warn "enable --now に失敗しました: $t"
|
|
246
|
+
done
|
|
247
|
+
for u in "${UNITS[@]}"; do ledger_add "$u"; done
|
|
248
|
+
loginctl enable-linger "${USER:-$(id -un)}" >/dev/null 2>&1 || true
|
|
249
|
+
info "③ systemd: ${UNITS[*]}(常駐は起動時の1回の後に起きます)"
|
|
250
|
+
|
|
251
|
+
# ④ Claude Code 自身の自動更新を止める(★上げるのは更新係1つだけ)
|
|
252
|
+
mkdir -p "$(dirname "$CLAUDE_SETTINGS")"
|
|
253
|
+
if [[ -f "$CLAUDE_SETTINGS" ]]; then
|
|
254
|
+
bak="$CLAUDE_SETTINGS.bak.$(date '+%Y%m%d-%H%M%S')"
|
|
255
|
+
cp -p "$CLAUDE_SETTINGS" "$bak"
|
|
256
|
+
info "④ 控え: $bak"
|
|
257
|
+
fi
|
|
258
|
+
if ! python3 - "$CLAUDE_SETTINGS" <<'PY'
|
|
259
|
+
import json, os, sys
|
|
260
|
+
p = sys.argv[1]
|
|
261
|
+
d = {}
|
|
262
|
+
if os.path.exists(p):
|
|
263
|
+
with open(p, encoding='utf-8') as f:
|
|
264
|
+
d = json.load(f)
|
|
265
|
+
if not isinstance(d, dict):
|
|
266
|
+
sys.exit(3)
|
|
267
|
+
env = d.setdefault('env', {})
|
|
268
|
+
if not isinstance(env, dict):
|
|
269
|
+
sys.exit(3)
|
|
270
|
+
env['DISABLE_AUTOUPDATER'] = '1'
|
|
271
|
+
tmp = p + '.tmp'
|
|
272
|
+
with open(tmp, 'w', encoding='utf-8') as f:
|
|
273
|
+
json.dump(d, f, ensure_ascii=False, indent=2)
|
|
274
|
+
f.write('\n')
|
|
275
|
+
os.replace(tmp, p)
|
|
276
|
+
PY
|
|
277
|
+
then
|
|
278
|
+
warn "$CLAUDE_SETTINGS を読めない形なので、DISABLE_AUTOUPDATER を書けませんでした(控えは上)。CC1 に知らせてください"
|
|
279
|
+
exit 1
|
|
280
|
+
fi
|
|
281
|
+
info "④ Claude Code 自身の自動更新を止めました($CLAUDE_SETTINGS の env.DISABLE_AUTOUPDATER=1)"
|
|
282
|
+
|
|
283
|
+
(( IS_WSL == 1 )) && windows_lines
|
|
284
|
+
echo
|
|
285
|
+
info "入りました。続けて自己診断を1回流してください: ~/.tyhld/bin/tyhld-selfcheck --now"
|
package/scripts/conductor-run.sh
CHANGED
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
# systemd は Restart=always なので、入れ直せば次の起動で自動的に戻る。
|
|
31
31
|
#
|
|
32
32
|
# 【★公開 npm には問い合わせない】常駐は回線が切れても動き続ける必要がある。
|
|
33
|
-
#
|
|
33
|
+
# 版をそろえるのは、自動更新が入っている機(自社PC)では更新係(tyhld-autoupdate・便 29babc5d)、
|
|
34
|
+
# 入っていない機では毎時同期が見て知らせる(同じ見張りを2つ作らない)。
|
|
35
|
+
# ★ここで出す「人の手番」は、配布物そのものが無い・欠けている(=更新係も動けない)ときだけ。
|
|
34
36
|
#
|
|
35
37
|
# 【置き場】この入口は ~/.tyhld/bin/conductor-run として置かれ、隣に dist-source.sh と
|
|
36
38
|
# skill_sync_notify.py を置く(導入処理と毎時同期が配布物から写す)。
|