@cocorograph/hub-agent 0.5.25 → 0.5.27

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocorograph/hub-agent",
3
- "version": "0.5.25",
3
+ "version": "0.5.27",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -21,6 +21,13 @@
21
21
 
22
22
  set -euo pipefail
23
23
 
24
+ # brew の auto-update / hint 出力は初回 install で長時間化する原因の最大要因。
25
+ # 環境変数で抑止して 1 発完走率を上げる。最新 Formula が欲しいときは
26
+ # ユーザーが `brew update` を別途実行する前提。
27
+ export HOMEBREW_NO_AUTO_UPDATE=1
28
+ export HOMEBREW_NO_ENV_HINTS=1
29
+ export HOMEBREW_NO_INSTALL_CLEANUP=1
30
+
24
31
  # Node.js のサポート範囲ポリシー(Active LTS のみ)。
25
32
  # - 既存 node のメジャーが [MIN, MAX] に収まっていれば現状維持
26
33
  # - 範囲外なら NODE_DEFAULT_BREW_FORMULA (最新 Active LTS) にアップ / ダウングレード
@@ -42,6 +49,42 @@ color_err() { printf "\033[1;31m✗ %s\033[0m\n" "$1" >&2; }
42
49
 
43
50
  present() { command -v "$1" >/dev/null 2>&1; }
44
51
 
52
+ # step counter (main() で使う)。色は出すが詳細メッセージは関数内に任せる。
53
+ # STEP_TOTAL は main の冒頭で再設定する想定。
54
+ STEP_TOTAL=10
55
+ STEP_NUM=0
56
+ step_header() {
57
+ STEP_NUM=$((STEP_NUM + 1))
58
+ printf "\033[1;36m\n━━━ [%d/%d] %s ━━━\033[0m\n" "$STEP_NUM" "$STEP_TOTAL" "$1"
59
+ }
60
+
61
+ # 指定コマンドを最大 N 回まで指数 backoff で retry する。
62
+ # transient な network / brew / npm 失敗を耐える用途。
63
+ # 使い方: retry 3 brew install foo
64
+ # retry 3 npm install -g bar
65
+ #
66
+ # 重要: bash の `set -e` 下では `if cmd` パターンは cmd の戻り値で分岐するため、
67
+ # cmd 失敗時に script が exit しない (`set -e` の標準仕様)。
68
+ # retry 全体が失敗した時のみ呼び出し元が exit する。
69
+ retry() {
70
+ local max="$1"; shift
71
+ local i=1
72
+ local delay=2
73
+ while true; do
74
+ if "$@"; then
75
+ return 0
76
+ fi
77
+ if (( i >= max )); then
78
+ color_err "コマンドが ${max} 回連続失敗: $*"
79
+ return 1
80
+ fi
81
+ color_warn "失敗 (${i}/${max}) → ${delay}s 後に再試行: $*"
82
+ sleep "$delay"
83
+ i=$((i + 1))
84
+ delay=$((delay * 2))
85
+ done
86
+ }
87
+
45
88
  # 現在 PATH にある brew が、このユーザーで書き込み可能か判定する。
46
89
  # 「brew はあるが Cellar が他ユーザー所有で書き込み不可」というケースを検知して
47
90
  # user-local Homebrew にフォールバックするための判定関数。
@@ -89,7 +132,9 @@ ensure_brew() {
89
132
  _install_user_local_brew
90
133
  else
91
134
  color_step "Homebrew をシステムインストール"
92
- /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
135
+ # Homebrew 公式 install.sh をダウンロードして実行。
136
+ # curl は GitHub raw のレート制限や transient 502 を retry でカバーする。
137
+ retry 3 bash -c '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
93
138
  # Apple Silicon の brew はデフォルト PATH に入らないので追加
94
139
  if [[ -d /opt/homebrew/bin ]]; then
95
140
  export PATH="/opt/homebrew/bin:$PATH"
@@ -107,7 +152,9 @@ _install_user_local_brew() {
107
152
  else
108
153
  color_step "Homebrew をユーザーローカルインストール (\$HOME/homebrew)"
109
154
  mkdir -p "$HOME/homebrew"
110
- curl -L https://github.com/Homebrew/brew/tarball/master | tar xz --strip 1 -C "$HOME/homebrew"
155
+ # GitHub tarball ダウンロード + tar 展開を 1 つの bash -c に包んで retry にかける。
156
+ # pipe 途中の curl 失敗を retry のスコープに収めるため。
157
+ retry 3 bash -c 'curl -fsSL https://github.com/Homebrew/brew/tarball/master | tar xz --strip 1 -C "$HOME/homebrew"'
111
158
  fi
112
159
  # PATH を user-local 優先で並べる(既存 /usr/local/bin/brew より先)
113
160
  export PATH="$HOME/homebrew/bin:$PATH"
@@ -398,14 +445,15 @@ ensure_pkg() {
398
445
  if present "$cmd"; then color_ok "$cmd already installed"; return 0; fi
399
446
  color_step "$cmd をインストール"
400
447
  if [[ "$(uname)" == "Darwin" ]]; then
401
- brew install "$brew_pkg"
448
+ # brew install は transient (network / hash mismatch / pour 失敗) を retry で吸収
449
+ retry 3 brew install "$brew_pkg"
402
450
  elif present apt-get; then
403
- sudo apt-get update -y
404
- sudo apt-get install -y "$apt_pkg"
451
+ retry 3 sudo apt-get update -y
452
+ retry 3 sudo apt-get install -y "$apt_pkg"
405
453
  elif present dnf; then
406
- sudo dnf install -y "$apt_pkg"
454
+ retry 3 sudo dnf install -y "$apt_pkg"
407
455
  elif present pacman; then
408
- sudo pacman -S --noconfirm "$apt_pkg"
456
+ retry 3 sudo pacman -S --noconfirm "$apt_pkg"
409
457
  else
410
458
  color_err "対応するパッケージマネージャ (brew/apt/dnf/pacman) が見つかりません。$cmd を手動で install してください"
411
459
  exit 1
@@ -417,7 +465,7 @@ ensure_pkg() {
417
465
  _install_node_lts_brew() {
418
466
  local formula="$NODE_DEFAULT_BREW_FORMULA"
419
467
  color_step "$formula (Active LTS) を install"
420
- brew install "$formula"
468
+ retry 3 brew install "$formula"
421
469
  # 既存無印 node が link されていれば外す(v26 current 等を退かす)
422
470
  if brew list node >/dev/null 2>&1; then
423
471
  color_step "既存 'node' formula を unlink ($formula を優先するため)"
@@ -465,15 +513,19 @@ ensure_node_version() {
465
513
  }
466
514
 
467
515
  ensure_global_install() {
516
+ # @latest 明示 + --force で npm cache stale 起因の「同 version 判定 skip」を回避。
517
+ # 過去事例: 0.5.24 が既に入っているマシンで `npm i -g xxx` が 0.5.26 に更新
518
+ # しない事象があった (npm の "no changes needed" 誤発火)。--force でこの判定を
519
+ # 飛ばし、@latest で確実に最新版を解決する。
468
520
  if present hub-agent; then
469
521
  local cur
470
522
  cur=$(hub-agent --version 2>/dev/null || echo "unknown")
471
523
  color_step "hub-agent (現在 $cur) を最新版にアップデート"
472
- npm install -g "$PACKAGE_NAME"
473
524
  else
474
525
  color_step "$PACKAGE_NAME を install"
475
- npm install -g "$PACKAGE_NAME"
476
526
  fi
527
+ retry 3 npm install -g "${PACKAGE_NAME}@latest" --force
528
+ hash -r 2>/dev/null || true
477
529
  color_ok "hub-agent $(hub-agent --version)"
478
530
  }
479
531
 
@@ -485,11 +537,14 @@ ensure_claude_code() {
485
537
  local cur
486
538
  cur=$(claude --version 2>/dev/null || echo "unknown")
487
539
  color_step "Claude Code (現在 $cur) を最新版にアップデート"
488
- npm install -g "$CLAUDE_CODE_PACKAGE" || color_warn "Claude Code upgrade に失敗 (既存版で継続)"
540
+ # claude Anthropic 配信。失敗しても既存版で継続 (warning のみ)
541
+ retry 2 npm install -g "${CLAUDE_CODE_PACKAGE}@latest" --force \
542
+ || color_warn "Claude Code upgrade に失敗 (既存版で継続)"
489
543
  else
490
544
  color_step "$CLAUDE_CODE_PACKAGE を install"
491
- npm install -g "$CLAUDE_CODE_PACKAGE"
545
+ retry 3 npm install -g "${CLAUDE_CODE_PACKAGE}@latest" --force
492
546
  fi
547
+ hash -r 2>/dev/null || true
493
548
  if present claude; then
494
549
  color_ok "claude $(claude --version 2>/dev/null || echo 'installed')"
495
550
  else
@@ -503,20 +558,88 @@ do_enroll() {
503
558
  color_warn " 手動で実行: hub-agent enroll <token> --hub-url ${HUB_AGENT_URL:-https://api.hub.cocorograph.com}"
504
559
  return 0
505
560
  fi
561
+ local hub_url="${HUB_AGENT_URL:-https://api.hub.cocorograph.com}"
562
+ # enroll は token が短命の可能性があるため retry は 1 回のみ。
563
+ # (transient network なら retry が効くが、token 期限切れだと回数を増やしても無駄)
506
564
  if [[ -f "$HOME/.hub/agent.json" ]]; then
507
565
  color_warn "~/.hub/agent.json が既にあります。--force で上書きします"
508
- hub-agent enroll "$HUB_AGENT_TOKEN" --hub-url "${HUB_AGENT_URL:-https://api.hub.cocorograph.com}" --force
566
+ retry 2 hub-agent enroll "$HUB_AGENT_TOKEN" --hub-url "$hub_url" --force
509
567
  else
510
- hub-agent enroll "$HUB_AGENT_TOKEN" --hub-url "${HUB_AGENT_URL:-https://api.hub.cocorograph.com}"
568
+ retry 2 hub-agent enroll "$HUB_AGENT_TOKEN" --hub-url "$hub_url"
569
+ fi
570
+
571
+ # enroll 内部の syncBundle 失敗は warning だけで握りつぶされる (enroll 自体は
572
+ # success 扱い) ため、ここで bundle 展開状況を verify する。
573
+ # CLAUDE.md の HUB-AI-RULES マーカーが入っていれば bundle 反映済みと判定。
574
+ if [[ ! -f "$HOME/.claude/CLAUDE.md" ]] \
575
+ || ! grep -q "BEGIN HUB-AI-RULES" "$HOME/.claude/CLAUDE.md" 2>/dev/null; then
576
+ color_warn "Hub AI bundle が ~/.claude に展開されていない可能性。sync-bundle を再試行"
577
+ if ! retry 2 hub-agent sync-bundle; then
578
+ color_warn "hub-agent sync-bundle が継続失敗。あとで手動実行してください:"
579
+ color_warn " hub-agent sync-bundle"
580
+ fi
511
581
  fi
512
582
  }
513
583
 
514
584
  do_install_service() {
515
585
  color_step "OS サービスとして自動起動を登録"
516
- hub-agent install-service
586
+ # install-service は launchctl bootout/bootstrap の transient で初回失敗する
587
+ # ことがある (既存 unit の残骸 + 同名 bootstrap 競合 等)。retry でカバー。
588
+ retry 2 hub-agent install-service
517
589
  color_ok "install-service 完了。ログ: ~/.hub/agent.log"
518
590
  }
519
591
 
592
+ # セットアップ最終検証。
593
+ # 「online には見えるがバンドル未配信」「サービス起動失敗」等の半完了状態を
594
+ # 検知してユーザーに次の手を案内する。返り値は 0 (errors > 0 でも継続)。
595
+ verify_setup() {
596
+ local errors=0
597
+
598
+ # 1. hub-agent CLI
599
+ if present hub-agent; then
600
+ color_ok "hub-agent CLI: $(hub-agent --version 2>/dev/null || echo 'installed')"
601
+ else
602
+ color_err "hub-agent コマンドが PATH に見つかりません"
603
+ errors=$((errors + 1))
604
+ fi
605
+
606
+ # 2. Claude Code (任意なので warn のみ)
607
+ if present claude; then
608
+ color_ok "Claude Code: $(claude --version 2>/dev/null || echo 'installed')"
609
+ else
610
+ color_warn "Claude Code (claude) コマンドが見つかりません (cockpit から claude を呼ぶ場合は必要)"
611
+ fi
612
+
613
+ # 3. Hub AI bundle 展開
614
+ if [[ -f "$HOME/.claude/CLAUDE.md" ]] \
615
+ && grep -q "BEGIN HUB-AI-RULES" "$HOME/.claude/CLAUDE.md" 2>/dev/null; then
616
+ color_ok "Hub AI bundle 展開済み (~/.claude/CLAUDE.md)"
617
+ else
618
+ color_warn "Hub AI bundle が ~/.claude に展開されていません"
619
+ color_warn " 手動再同期: hub-agent sync-bundle"
620
+ errors=$((errors + 1))
621
+ fi
622
+
623
+ # 4. OS サービス起動 (Darwin 限定の確認。Linux は systemctl --user で別途)
624
+ if [[ "$(uname)" == "Darwin" ]]; then
625
+ if launchctl list 2>/dev/null | grep -q "co.cocorograph.hub-agent"; then
626
+ color_ok "launchd service 起動中 (co.cocorograph.hub-agent)"
627
+ else
628
+ color_warn "launchd service が見つかりません"
629
+ color_warn " 手動起動: hub-agent install-service"
630
+ errors=$((errors + 1))
631
+ fi
632
+ fi
633
+
634
+ if (( errors > 0 )); then
635
+ color_warn ""
636
+ color_warn "${errors} 件の不整合を検出。Hub UI で agent が online でない場合は以下を試してください:"
637
+ color_warn " hub-agent restart # サービス再起動"
638
+ color_warn " hub-agent sync-bundle # bundle 再配信"
639
+ color_warn " tail -20 ~/.hub/agent.log # 詳細ログ"
640
+ fi
641
+ }
642
+
520
643
  macos_perm_guidance() {
521
644
  [[ "$(uname)" != "Darwin" ]] && return 0
522
645
  # 現在 PATH 上の node 実体パスを案内に埋め込む(フルディスクアクセス登録時に役立つ)
@@ -544,19 +667,34 @@ EOF
544
667
 
545
668
  main() {
546
669
  color_step "hub-agent ワンライナーセットアップを開始"
670
+ STEP_TOTAL=10
671
+ STEP_NUM=0
672
+
673
+ step_header "Homebrew"
547
674
  ensure_brew
675
+ step_header "tmux"
548
676
  ensure_pkg tmux tmux tmux
677
+ step_header "Node.js (Active LTS)"
549
678
  ensure_node_version
679
+ step_header "npm global prefix"
550
680
  ensure_npm_user_prefix
551
681
  # ensure_node_version 直後だと brew link でシェルの command hash がズレている
552
682
  # 場合があるため、prefix 設定後にまとめて TLS pre-flight check + 自動 fallback
553
683
  # を行う。失敗時はガイダンス付きで exit するので、後段の npm install で TLS
554
684
  # エラーを再度浴びる経路はカットされる。
685
+ step_header "Node TLS pre-flight"
555
686
  ensure_node_tls_works
687
+ step_header "hub-agent install"
556
688
  ensure_global_install
689
+ step_header "Claude Code install"
557
690
  ensure_claude_code
691
+ step_header "enroll + bundle 同期"
558
692
  do_enroll
693
+ step_header "OS サービス化"
559
694
  do_install_service
695
+ step_header "最終検証"
696
+ verify_setup
697
+
560
698
  echo ""
561
699
  color_ok "セットアップ完了。Hub UI で online 表示を確認してください"
562
700
  echo " https://hub.cocorograph.com/user/cockpit/agents"
@@ -41,11 +41,43 @@ async function readTemplate(name) {
41
41
  return fs.readFile(p, "utf-8")
42
42
  }
43
43
 
44
+ function escapeXmlText(s) {
45
+ // plist の <string> 中身を埋めるための最小エスケープ。
46
+ // パスに XML 特殊文字が入っているケースは現実にはほぼ無いが、念のため。
47
+ return String(s)
48
+ .replaceAll("&", "&amp;")
49
+ .replaceAll("<", "&lt;")
50
+ .replaceAll(">", "&gt;")
51
+ }
52
+
44
53
  function expandTemplate(text, hubAgentBin) {
54
+ // hub-agent デーモンの runtime 環境に必須な追加 env を、各プラットフォームの
55
+ // 起動ファイル形式 (plist / systemd) に合わせて差し込むためのプレースホルダ展開。
56
+ //
57
+ // NODE_EXTRA_CA_CERTS: install.sh の TLS pre-flight で OS Keychain → PEM
58
+ // エクスポート経由で設定された場合 (e.g. MITM 環境)、シェル profile の export
59
+ // だけだと launchd / systemd には届かない。install-service 実行時の env に
60
+ // 残っている値を plist/unit に永続化する。未 set なら関連行を空に置換するので
61
+ // 通常環境への副作用はゼロ。
62
+ const nodeExtraCa = process.env.NODE_EXTRA_CA_CERTS || ""
63
+ let plistEntry = ""
64
+ let systemdLine = ""
65
+ if (nodeExtraCa) {
66
+ // plist 側: EnvironmentVariables dict の inside に <key>...</key><string>...</string>
67
+ // 2 行を追加 (インデント 4 スペースは既存 PATH/HOME と揃える)。末尾に \n を付けて
68
+ // テンプレートの "__...__\n" を綺麗に消費する。
69
+ plistEntry =
70
+ ` <key>NODE_EXTRA_CA_CERTS</key>\n` +
71
+ ` <string>${escapeXmlText(nodeExtraCa)}</string>\n`
72
+ // systemd 側: Environment=KEY=VALUE の 1 行。末尾 \n でテンプレ "__...__\n" を吸収。
73
+ systemdLine = `Environment=NODE_EXTRA_CA_CERTS=${nodeExtraCa}\n`
74
+ }
45
75
  return text
46
76
  .replaceAll("__HUB_AGENT_BIN__", hubAgentBin)
47
77
  .replaceAll("__HOME__", os.homedir())
48
78
  .replaceAll("__PATH__", process.env.PATH || "/usr/local/bin:/usr/bin:/bin")
79
+ .replaceAll("__NODE_EXTRA_CA_CERTS_PLIST_ENTRY__", plistEntry)
80
+ .replaceAll("__NODE_EXTRA_CA_CERTS_SYSTEMD_LINE__", systemdLine)
49
81
  }
50
82
 
51
83
  async function ensureDir(p) {
@@ -31,13 +31,21 @@
31
31
  <key>WorkingDirectory</key>
32
32
  <string>__HOME__</string>
33
33
 
34
+ <!--
35
+ EnvironmentVariables は launchd 起動時の env を完全に置き換える (ログイン
36
+ シェルや .zprofile の export を継承しない)。シェル profile に export した
37
+ cert 系 env 等を agent デーモンに届けるには plist に明示する必要がある。
38
+ 下の placeholder 行は install-service 実行時に expandTemplate() が「env が
39
+ set されていれば NODE_EXTRA_CA_CERTS entry に、未 set なら空文字列に」展開する。
40
+ 未 set 時は当該行ごと消えるので、通常環境への副作用はゼロ。
41
+ -->
34
42
  <key>EnvironmentVariables</key>
35
43
  <dict>
36
44
  <key>PATH</key>
37
45
  <string>__PATH__</string>
38
46
  <key>HOME</key>
39
47
  <string>__HOME__</string>
40
- </dict>
48
+ __NODE_EXTRA_CA_CERTS_PLIST_ENTRY__ </dict>
41
49
 
42
50
  <!-- KeepAlive で過剰再起動した時に 10 秒スロットルする -->
43
51
  <key>ThrottleInterval</key>
@@ -5,9 +5,14 @@ Wants=network-online.target
5
5
 
6
6
  [Service]
7
7
  Type=simple
8
+ # Environment は systemd ユニット起動時の env を明示指定する (ログインシェルや
9
+ # .bashrc / .profile の export を継承しないため、agent デーモンに必要な env は
10
+ # ここに書く必要がある)。下の placeholder 行は install-service 実行時に
11
+ # expandTemplate() が「env が set されていれば Environment=NODE_EXTRA_CA_CERTS=...
12
+ # 行に、未 set なら空文字列に」展開する。
8
13
  Environment=PATH=__PATH__
9
14
  Environment=HOME=__HOME__
10
- WorkingDirectory=__HOME__
15
+ __NODE_EXTRA_CA_CERTS_SYSTEMD_LINE__WorkingDirectory=__HOME__
11
16
  ExecStart=__HUB_AGENT_BIN__ start
12
17
  Restart=always
13
18
  RestartSec=10