ff-doctor.sh 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. #!/usr/bin/env bash
  2. # ff-doctor.sh - fleetflow preflight: prove the heterogeneous fleet can run.
  3. #
  4. # --offline (default): structural checks only - binaries, sibling launchers,
  5. # script syntax. CI-safe, no network.
  6. # --live: additionally probe each provider - GLM endpoint (via fleet-doctor),
  7. # Codex auth, Anthropic model availability - and report which orchestrator
  8. # tier is available (fable > opus).
  9. # stdout: one TSV line per check: name<TAB>status<TAB>detail. stderr: chatter.
  10. #
  11. # Exit codes: 0 all required checks ok | 2 usage | 7 live probe unreachable
  12. # 10 structural failure
  13. set -u
  14. . "$(dirname "${BASH_SOURCE[0]}")/_env.sh"
  15. FF_VERSION="1.1.0"
  16. usage() {
  17. cat <<'EOF'
  18. Usage: ff-doctor.sh [--offline | --live]
  19. --offline structural checks only (default; CI-safe)
  20. --live also probe GLM endpoint, Codex auth, Anthropic models;
  21. reports orchestrator tier (fable|opus)
  22. EXAMPLES
  23. ff-doctor.sh --offline
  24. ff-doctor.sh --live
  25. EOF
  26. }
  27. MODE="offline"
  28. case "${1:-}" in
  29. --offline|"") MODE="offline" ;;
  30. --live) MODE="live" ;;
  31. -h|--help) usage; exit 0 ;;
  32. *) echo "ff-doctor: unknown argument: $1" >&2; usage >&2; exit 2 ;;
  33. esac
  34. FAIL=0; UNREACH=0
  35. say() { printf '%s\t%s\t%s\n' "$1" "$2" "$3"; }
  36. HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  37. # --- structural ---------------------------------------------------------------
  38. for bin in git jq sha256sum; do
  39. if command -v "$bin" >/dev/null; then say "bin-$bin" ok "found"; else say "bin-$bin" fail "missing"; FAIL=1; fi
  40. done
  41. if command -v claude >/dev/null; then say "bin-claude" ok "found"; else say "bin-claude" fail "missing"; FAIL=1; fi
  42. if command -v codex >/dev/null; then say "bin-codex" ok "$(codex --version 2>/dev/null | head -1)"; else say "bin-codex" advisory "missing - codex brain unavailable"; fi
  43. GROK="${FLEETFLOW_GROK_BIN:-grok}"
  44. if command -v "$GROK" >/dev/null; then say "bin-grok" ok "$("$GROK" --version 2>/dev/null | head -1)"; else say "bin-grok" advisory "missing - grok brain unavailable"; fi
  45. FW="${FLEETFLOW_FLEET_WORKER:-$HOME/.claude/skills/fleet-worker/scripts/fleet-worker}"
  46. if [ -f "$FW" ]; then say "fleet-worker" ok "$FW"; else say "fleet-worker" advisory "not installed - glm brain unavailable"; fi
  47. for s in ff-spawn.sh ff-collect.sh ff-status.sh; do
  48. if bash -n "$HERE/$s" 2>/dev/null; then say "syntax-$s" ok "parses"; else say "syntax-$s" fail "syntax error"; FAIL=1; fi
  49. done
  50. [ -f "$HERE/../assets/guard-preamble.txt" ] && say "guard-preamble" ok "present" || { say "guard-preamble" fail "missing"; FAIL=1; }
  51. # --- install-sync: repo copy vs the installed copy at $HOME/.claude/skills ---
  52. # version-skew guard. Only compares when an installed copy exists AND is a
  53. # different directory from the one being run (running from the install itself
  54. # trivially matches). Drift is advisory, never a hard fail.
  55. INST="$HOME/.claude/skills/fleetflow/scripts"
  56. if [ -d "$INST" ]; then
  57. INST_ABS="$(cd "$INST" 2>/dev/null && pwd)"
  58. if [ -n "$INST_ABS" ] && [ "$INST_ABS" != "$HERE" ]; then
  59. DIFF=0
  60. for s in ff-spawn.sh ff-collect.sh ff-status.sh ff-doctor.sh ff-run.sh ff-clean.sh; do
  61. [ -f "$HERE/$s" ] || continue # not shipped here; skip
  62. if [ ! -f "$INST/$s" ]; then DIFF=1; break; fi
  63. h1="$(sha256sum "$HERE/$s" | cut -d' ' -f1)"
  64. h2="$(sha256sum "$INST/$s" | cut -d' ' -f1)"
  65. [ "$h1" = "$h2" ] || { DIFF=1; break; }
  66. done
  67. if [ "$DIFF" = 0 ]; then say "install-sync" ok "repo and installed copies match"
  68. else say "install-sync" advisory "repo and installed copies differ - re-run install"; fi
  69. else
  70. say "install-sync" ok "running from the installed copy"
  71. fi
  72. else
  73. say "install-sync" advisory "no installed copy at $INST"
  74. fi
  75. [ "$MODE" = "live" ] || { [ "$FAIL" = 0 ] && exit 0 || exit 10; }
  76. # --- live probes ----------------------------------------------------------------
  77. FD="$(dirname "$FW")/fleet-doctor.sh"
  78. if [ -f "$FD" ]; then
  79. if bash "$FD" --live 2>/dev/null | grep -q "live-ping ok"; then
  80. say "glm-endpoint" ok "model resolves"
  81. else
  82. say "glm-endpoint" unreachable "fleet-doctor --live did not confirm (key/endpoint)"; UNREACH=1
  83. fi
  84. else
  85. say "glm-endpoint" advisory "fleet-doctor not installed"
  86. fi
  87. if command -v codex >/dev/null; then
  88. if timeout 30 codex login status 2>&1 | grep -qi "logged in"; then
  89. say "codex-auth" ok "$(timeout 30 codex login status 2>&1 | head -1)"
  90. else
  91. say "codex-auth" unreachable "not logged in (codex login)"; UNREACH=1
  92. fi
  93. fi
  94. # grok auth is the GROK_DEPLOYMENT_KEY env var (no login-status subcommand exists,
  95. # and OAuth on some accounts lacks chat entitlement). We probe key PRESENCE, not
  96. # validity - a real chat call would burn quota. Only when the binary is installed.
  97. if command -v "$GROK" >/dev/null; then
  98. if [ -n "${GROK_DEPLOYMENT_KEY:-}" ]; then
  99. say "grok-auth" ok "GROK_DEPLOYMENT_KEY set"
  100. else
  101. say "grok-auth" unreachable "GROK_DEPLOYMENT_KEY not set (deployment key required)"; UNREACH=1
  102. fi
  103. fi
  104. ORCH="none"
  105. if command -v claude >/dev/null; then
  106. for m in claude-fable-5 opus; do
  107. R="$(timeout 120 claude -p "reply with exactly: ok" --model "$m" --max-turns 1 --output-format json 2>/dev/null)"
  108. if [ -n "$R" ] && [ "$(printf '%s' "$R" | jq -r 'if has("is_error") then (.is_error|tostring) else "true" end' 2>/dev/null)" = "false" ]; then
  109. case "$m" in claude-fable-5) ORCH="fable" ;; *) ORCH="opus" ;; esac
  110. say "model-$m" ok "responds"
  111. break
  112. else
  113. say "model-$m" unavailable "no successful reply"
  114. fi
  115. done
  116. fi
  117. say "orchestrator" "$([ "$ORCH" = none ] && echo unreachable || echo ok)" "$ORCH"
  118. [ "$ORCH" = "none" ] && UNREACH=1
  119. if [ "$FAIL" != 0 ]; then exit 10; fi
  120. [ "$UNREACH" != 0 ] && exit 7
  121. exit 0