@nicotinetool/o7-cli 1.1.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.
- package/README.md +44 -0
- package/bin/o7 +244 -0
- package/bin/o7-doctor +379 -0
- package/bin/o7-setup +143 -0
- package/bin/o7-setup.js +143 -0
- package/bin/o7.js +244 -0
- package/installer/com.unified-mc.plist.tmpl +35 -0
- package/installer/install.sh +297 -0
- package/installer/lib/antigravity-standalone.sh +301 -0
- package/installer/lib/antigravity.sh +140 -0
- package/installer/lib/auth.sh +286 -0
- package/installer/lib/checks.sh +177 -0
- package/installer/lib/ui.sh +120 -0
- package/installer/lib/validate.sh +87 -0
- package/installer/onboard.mjs +966 -0
- package/installer/templates/agents.md.tmpl +34 -0
- package/installer/templates/heartbeat.md.tmpl +63 -0
- package/installer/templates/openclaw.json.tmpl +116 -0
- package/installer/templates/soul.md.tmpl +45 -0
- package/installer/templates/user.md.tmpl +25 -0
- package/package.json +25 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================
|
|
3
|
+
# O7 OpenClaw macOS Installer
|
|
4
|
+
# One-click setup for the Optimum7 team
|
|
5
|
+
# Usage: bash install.sh
|
|
6
|
+
# ============================================================
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
INSTALLER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
|
|
11
|
+
# ── Profile isolation ────────────────────────────────────────
|
|
12
|
+
PROFILE="default"
|
|
13
|
+
while [[ $# -gt 0 ]]; do
|
|
14
|
+
case "$1" in
|
|
15
|
+
--profile) PROFILE="$2"; shift 2 ;;
|
|
16
|
+
*) shift ;;
|
|
17
|
+
esac
|
|
18
|
+
done
|
|
19
|
+
if [[ "$PROFILE" != "default" ]]; then
|
|
20
|
+
export OPENCLAW_HOME="${HOME}/.openclaw-${PROFILE}"
|
|
21
|
+
mkdir -p "$OPENCLAW_HOME"
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
source "${INSTALLER_DIR}/lib/ui.sh"
|
|
25
|
+
source "${INSTALLER_DIR}/lib/checks.sh"
|
|
26
|
+
source "${INSTALLER_DIR}/lib/validate.sh"
|
|
27
|
+
source "${INSTALLER_DIR}/lib/auth.sh"
|
|
28
|
+
source "${INSTALLER_DIR}/lib/antigravity.sh"
|
|
29
|
+
|
|
30
|
+
# State file for resuming partial installs
|
|
31
|
+
STATE_FILE="${HOME}/.openclaw/.install-state"
|
|
32
|
+
mkdir -p "$(dirname "$STATE_FILE")"
|
|
33
|
+
|
|
34
|
+
state_done() { echo "$1" >> "$STATE_FILE"; }
|
|
35
|
+
state_check() { grep -q "^$1$" "$STATE_FILE" 2>/dev/null; }
|
|
36
|
+
|
|
37
|
+
# ── PHASE 1: Welcome ─────────────────────────────────────────
|
|
38
|
+
banner
|
|
39
|
+
|
|
40
|
+
echo -e " This installer will set up ${BOLD}OpenClaw + Mission Control${RESET} on your Mac."
|
|
41
|
+
echo -e " It handles everything: dependencies, AI model login, services, and health checks."
|
|
42
|
+
echo -e " Takes about ${BOLD}5-10 minutes${RESET} on a good connection."
|
|
43
|
+
echo
|
|
44
|
+
|
|
45
|
+
if [[ -f "$STATE_FILE" ]]; then
|
|
46
|
+
warn "Found a previous install attempt. Resuming from where it left off."
|
|
47
|
+
info "Delete ${STATE_FILE} to start fresh."
|
|
48
|
+
echo
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
confirm "Ready to start?" || { echo "Aborted."; exit 0; }
|
|
52
|
+
log "=== INSTALL STARTED $(date) ==="
|
|
53
|
+
|
|
54
|
+
# ── PHASE 2: System Checks ───────────────────────────────────
|
|
55
|
+
if ! state_check "checks"; then
|
|
56
|
+
run_all_checks || exit 1
|
|
57
|
+
state_done "checks"
|
|
58
|
+
else
|
|
59
|
+
ok "System checks already passed — skipping"
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# ── PHASE 3: Install OpenClaw ────────────────────────────────
|
|
63
|
+
section 2 "Install OpenClaw"
|
|
64
|
+
|
|
65
|
+
if ! state_check "openclaw"; then
|
|
66
|
+
if command -v openclaw &>/dev/null; then
|
|
67
|
+
local_ver=$(openclaw --version 2>/dev/null | awk '{print $1}')
|
|
68
|
+
ok "OpenClaw already installed (${local_ver})"
|
|
69
|
+
info "Checking for updates..."
|
|
70
|
+
npm install -g openclaw@latest &>/dev/null &
|
|
71
|
+
spin $! "Updating OpenClaw..."
|
|
72
|
+
wait $!
|
|
73
|
+
else
|
|
74
|
+
info "Installing OpenClaw..."
|
|
75
|
+
npm install -g openclaw@latest &
|
|
76
|
+
spin $! "Installing OpenClaw (this may take a minute)..."
|
|
77
|
+
wait $!
|
|
78
|
+
if ! command -v openclaw &>/dev/null; then
|
|
79
|
+
fail "OpenClaw install failed."
|
|
80
|
+
heal_install_error "npm install -g openclaw@latest failed"
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
fi
|
|
84
|
+
ok "OpenClaw $(openclaw --version 2>/dev/null)"
|
|
85
|
+
log "OK: OpenClaw installed"
|
|
86
|
+
state_done "openclaw"
|
|
87
|
+
else
|
|
88
|
+
ok "OpenClaw already installed — skipping"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# ── PHASE 4: AI Model Auth ───────────────────────────────────
|
|
92
|
+
if ! state_check "auth"; then
|
|
93
|
+
run_auth_setup || exit 1
|
|
94
|
+
state_done "auth"
|
|
95
|
+
else
|
|
96
|
+
ok "Auth already configured — skipping"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# ── PHASE 5: Role Selection ──────────────────────────────────
|
|
100
|
+
section 4 "Your Role"
|
|
101
|
+
echo
|
|
102
|
+
echo -e " ${BOLD}What's your role at Optimum7?${RESET}"
|
|
103
|
+
echo -e " ${DIM}This pre-configures the right tools for you.${RESET}"
|
|
104
|
+
echo
|
|
105
|
+
|
|
106
|
+
ROLE_IDX=$(menu " Select your role:" \
|
|
107
|
+
"📧 Marketing / Email (Klaviyo, campaigns, flows)" \
|
|
108
|
+
"📈 PPC / Ads (Google Ads, Meta, reporting)" \
|
|
109
|
+
"💻 Developer (coding agents, GitHub, CI/CD)" \
|
|
110
|
+
"👑 Admin (full access, everything)" \
|
|
111
|
+
"🎛️ Custom (pick and choose later)")
|
|
112
|
+
|
|
113
|
+
ROLES=("marketing" "ppc" "dev" "admin" "custom")
|
|
114
|
+
SELECTED_ROLE="${ROLES[$ROLE_IDX]}"
|
|
115
|
+
ok "Role set: ${SELECTED_ROLE}"
|
|
116
|
+
log "ROLE: ${SELECTED_ROLE}"
|
|
117
|
+
|
|
118
|
+
# ── PHASE 6: OpenClaw Gateway Daemon ─────────────────────────
|
|
119
|
+
section 5 "OpenClaw Gateway"
|
|
120
|
+
|
|
121
|
+
if ! state_check "gateway"; then
|
|
122
|
+
info "Setting up OpenClaw gateway service (auto-starts on boot)..."
|
|
123
|
+
|
|
124
|
+
GATEWAY_TOKEN=$(openssl rand -base64 32 | tr -d /=+ | head -c 40)
|
|
125
|
+
GATEWAY_PORT=18789
|
|
126
|
+
|
|
127
|
+
openclaw onboard \
|
|
128
|
+
--non-interactive \
|
|
129
|
+
--accept-risk \
|
|
130
|
+
--install-daemon \
|
|
131
|
+
--gateway-auth token \
|
|
132
|
+
--gateway-token "$GATEWAY_TOKEN" \
|
|
133
|
+
--gateway-port "$GATEWAY_PORT" \
|
|
134
|
+
--gateway-bind loopback \
|
|
135
|
+
--flow quickstart \
|
|
136
|
+
2>&1 | while read -r line; do
|
|
137
|
+
echo -e " ${DIM}${line}${RESET}"
|
|
138
|
+
done
|
|
139
|
+
|
|
140
|
+
sleep 3
|
|
141
|
+
|
|
142
|
+
if curl -s "http://localhost:${GATEWAY_PORT}/health" &>/dev/null; then
|
|
143
|
+
ok "OpenClaw gateway running on port ${GATEWAY_PORT}"
|
|
144
|
+
log "OK: Gateway started on ${GATEWAY_PORT}"
|
|
145
|
+
state_done "gateway"
|
|
146
|
+
else
|
|
147
|
+
warn "Gateway may still be starting up..."
|
|
148
|
+
heal_install_error "openclaw gateway not responding on port ${GATEWAY_PORT}"
|
|
149
|
+
fi
|
|
150
|
+
else
|
|
151
|
+
ok "Gateway already configured — skipping"
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# ── PHASE 7: Mission Control ──────────────────────────────────
|
|
155
|
+
section 6 "Mission Control Dashboard"
|
|
156
|
+
|
|
157
|
+
MC_DIR="${HOME}/projects/unified-mc"
|
|
158
|
+
MC_REPO="https://github.com/erenes1667/unified-mc.git"
|
|
159
|
+
|
|
160
|
+
if ! state_check "mc"; then
|
|
161
|
+
if [[ -d "$MC_DIR/.git" ]]; then
|
|
162
|
+
info "Mission Control already cloned. Pulling latest..."
|
|
163
|
+
git -C "$MC_DIR" pull --quiet &
|
|
164
|
+
spin $! "Updating Mission Control..."
|
|
165
|
+
wait $!
|
|
166
|
+
else
|
|
167
|
+
info "Cloning Mission Control..."
|
|
168
|
+
mkdir -p "$(dirname "$MC_DIR")"
|
|
169
|
+
git clone --quiet "$MC_REPO" "$MC_DIR" &
|
|
170
|
+
spin $! "Cloning Mission Control..."
|
|
171
|
+
wait $!
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
if [[ ! -d "$MC_DIR" ]]; then
|
|
175
|
+
fail "Could not get Mission Control from ${MC_REPO}"
|
|
176
|
+
info "Ask Enes to share access to the repo."
|
|
177
|
+
log "FAIL: MC clone failed"
|
|
178
|
+
else
|
|
179
|
+
info "Installing dependencies..."
|
|
180
|
+
cd "$MC_DIR"
|
|
181
|
+
pnpm install --silent &
|
|
182
|
+
spin $! "Installing dependencies..."
|
|
183
|
+
wait $!
|
|
184
|
+
|
|
185
|
+
ok "Mission Control ready"
|
|
186
|
+
log "OK: Mission Control installed at ${MC_DIR}"
|
|
187
|
+
state_done "mc"
|
|
188
|
+
fi
|
|
189
|
+
else
|
|
190
|
+
ok "Mission Control already set up — skipping"
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# ── PHASE 8: Mission Control launchd Service ──────────────────
|
|
194
|
+
section 7 "Mission Control Service"
|
|
195
|
+
|
|
196
|
+
if ! state_check "mc-daemon" && [[ -d "$MC_DIR" ]]; then
|
|
197
|
+
MC_PLIST="${HOME}/Library/LaunchAgents/com.o7.mission-control.plist"
|
|
198
|
+
NODE_BIN=$(which node)
|
|
199
|
+
NODE_DIR=$(dirname "$NODE_BIN")
|
|
200
|
+
|
|
201
|
+
cat > "$MC_PLIST" << PLIST
|
|
202
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
203
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
204
|
+
<plist version="1.0">
|
|
205
|
+
<dict>
|
|
206
|
+
<key>Label</key>
|
|
207
|
+
<string>com.o7.mission-control</string>
|
|
208
|
+
<key>WorkingDirectory</key>
|
|
209
|
+
<string>${MC_DIR}</string>
|
|
210
|
+
<key>ProgramArguments</key>
|
|
211
|
+
<array>
|
|
212
|
+
<string>${NODE_BIN}</string>
|
|
213
|
+
<string>node_modules/next/dist/bin/next</string>
|
|
214
|
+
<string>dev</string>
|
|
215
|
+
<string>--hostname</string>
|
|
216
|
+
<string>127.0.0.1</string>
|
|
217
|
+
</array>
|
|
218
|
+
<key>EnvironmentVariables</key>
|
|
219
|
+
<dict>
|
|
220
|
+
<key>PATH</key>
|
|
221
|
+
<string>${NODE_DIR}:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
222
|
+
<key>NODE_ENV</key>
|
|
223
|
+
<string>development</string>
|
|
224
|
+
</dict>
|
|
225
|
+
<key>RunAtLoad</key>
|
|
226
|
+
<true/>
|
|
227
|
+
<key>KeepAlive</key>
|
|
228
|
+
<true/>
|
|
229
|
+
<key>StandardOutPath</key>
|
|
230
|
+
<string>/tmp/mission-control.log</string>
|
|
231
|
+
<key>StandardErrorPath</key>
|
|
232
|
+
<string>/tmp/mission-control.err</string>
|
|
233
|
+
</dict>
|
|
234
|
+
</plist>
|
|
235
|
+
PLIST
|
|
236
|
+
|
|
237
|
+
launchctl unload "$MC_PLIST" 2>/dev/null || true
|
|
238
|
+
launchctl load "$MC_PLIST"
|
|
239
|
+
|
|
240
|
+
sleep 5
|
|
241
|
+
if curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 --max-time 10 | grep -qE "200|307"; then
|
|
242
|
+
ok "Mission Control running at http://localhost:3000"
|
|
243
|
+
log "OK: Mission Control daemon installed"
|
|
244
|
+
state_done "mc-daemon"
|
|
245
|
+
open "http://localhost:3000" 2>/dev/null || true
|
|
246
|
+
else
|
|
247
|
+
warn "Mission Control is starting (can take 30s on first boot)"
|
|
248
|
+
info "It will be available at http://localhost:3000 shortly"
|
|
249
|
+
log "WARN: MC not yet responding, but daemon installed"
|
|
250
|
+
state_done "mc-daemon"
|
|
251
|
+
fi
|
|
252
|
+
else
|
|
253
|
+
ok "Mission Control service already configured — skipping"
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
# ── PHASE 9: Antigravity ──────────────────────────────────────
|
|
257
|
+
setup_antigravity
|
|
258
|
+
|
|
259
|
+
# ── PHASE 10: Summary ─────────────────────────────────────────
|
|
260
|
+
section 10 "Setup Complete"
|
|
261
|
+
echo
|
|
262
|
+
echo -e "${BOLD}${GREEN} 🎉 OpenClaw is installed and running!${RESET}"
|
|
263
|
+
echo
|
|
264
|
+
echo -e "${BOLD} What's set up:${RESET}"
|
|
265
|
+
|
|
266
|
+
# Auth results
|
|
267
|
+
for result in "${AUTH_RESULTS[@]}"; do
|
|
268
|
+
provider=$(echo "$result" | cut -d: -f1)
|
|
269
|
+
status=$(echo "$result" | cut -d: -f2)
|
|
270
|
+
case "$status" in
|
|
271
|
+
ok) summary_row " ✅ ${provider^}" "working" ;;
|
|
272
|
+
skipped) summary_row " ⏭️ ${provider^}" "skipped (set up later)" ;;
|
|
273
|
+
failed) summary_row " ❌ ${provider^}" "failed (needs attention)" ;;
|
|
274
|
+
esac
|
|
275
|
+
done
|
|
276
|
+
|
|
277
|
+
echo
|
|
278
|
+
echo -e "${BOLD} Services:${RESET}"
|
|
279
|
+
summary_row " 🦞 OpenClaw Gateway" "localhost:18789 (auto-start)"
|
|
280
|
+
summary_row " 📊 Mission Control" "http://localhost:3000 (auto-start)"
|
|
281
|
+
summary_row " 🛡️ Antigravity" "every 30 min (auto-heal)"
|
|
282
|
+
echo
|
|
283
|
+
echo -e "${BOLD} Quick links:${RESET}"
|
|
284
|
+
echo -e " ${CYAN}→ Mission Control:${RESET} http://localhost:3000"
|
|
285
|
+
echo -e " ${CYAN}→ Docs:${RESET} https://docs.openclaw.ai"
|
|
286
|
+
echo -e " ${CYAN}→ Discord:${RESET} https://discord.gg/clawd"
|
|
287
|
+
echo
|
|
288
|
+
echo -e "${BOLD} Useful commands:${RESET}"
|
|
289
|
+
echo -e " ${DIM}openclaw gateway status${RESET} — check gateway"
|
|
290
|
+
echo -e " ${DIM}openclaw configure${RESET} — add/fix auth profiles"
|
|
291
|
+
echo -e " ${DIM}openclaw agent --message 'hi'${RESET} — test your AI"
|
|
292
|
+
echo
|
|
293
|
+
echo -e "${DIM} Install log: ${HOME}/.openclaw/install.log${RESET}"
|
|
294
|
+
echo
|
|
295
|
+
|
|
296
|
+
rm -f "$STATE_FILE"
|
|
297
|
+
log "=== INSTALL COMPLETE $(date) role=${SELECTED_ROLE} ==="
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Antigravity Standalone Health Checker
|
|
3
|
+
# Bundled with the installer — works on a fresh machine with just Node + OpenClaw.
|
|
4
|
+
# No external dependencies. No workspace assumptions.
|
|
5
|
+
# Checks the basics and fixes what it can. Calls Gemini for complex issues.
|
|
6
|
+
|
|
7
|
+
OPENCLAW_DIR="${HOME}/.openclaw"
|
|
8
|
+
ANTIGRAVITY_DIR="${OPENCLAW_DIR}/antigravity"
|
|
9
|
+
HEAL_LOG="${ANTIGRAVITY_DIR}/heal-history.jsonl"
|
|
10
|
+
HEALTH_LOG="${ANTIGRAVITY_DIR}/health.log"
|
|
11
|
+
|
|
12
|
+
mkdir -p "$ANTIGRAVITY_DIR"
|
|
13
|
+
|
|
14
|
+
# ─── Colors ────────────────────────────────────────────────────────────────────
|
|
15
|
+
if [[ -t 1 ]]; then
|
|
16
|
+
GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[1;33m'
|
|
17
|
+
CYAN='\033[0;36m' DIM='\033[2m' BOLD='\033[1m' RESET='\033[0m'
|
|
18
|
+
else
|
|
19
|
+
GREEN='' RED='' YELLOW='' CYAN='' DIM='' BOLD='' RESET=''
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
ok() { echo -e "${GREEN}✅ $*${RESET}"; }
|
|
23
|
+
warn() { echo -e "${YELLOW}⚠️ $*${RESET}"; }
|
|
24
|
+
fail() { echo -e "${RED}❌ $*${RESET}"; }
|
|
25
|
+
info() { echo -e "${CYAN}ℹ️ $*${RESET}"; }
|
|
26
|
+
|
|
27
|
+
heal_log() {
|
|
28
|
+
echo "{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"check\":\"$1\",\"status\":\"$2\",\"detail\":\"$3\"}" >> "$HEAL_LOG"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# ─── Health Checks (all self-contained) ───────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
check_openclaw_installed() {
|
|
34
|
+
if command -v openclaw &>/dev/null; then
|
|
35
|
+
ok "OpenClaw installed: $(openclaw --version 2>/dev/null)"
|
|
36
|
+
heal_log "openclaw_installed" "ok" "$(openclaw --version 2>/dev/null)"
|
|
37
|
+
return 0
|
|
38
|
+
fi
|
|
39
|
+
fail "OpenClaw not found in PATH"
|
|
40
|
+
heal_log "openclaw_installed" "fail" "not in PATH"
|
|
41
|
+
|
|
42
|
+
# Auto-fix: try installing
|
|
43
|
+
if command -v npm &>/dev/null; then
|
|
44
|
+
warn "Attempting auto-fix: npm install -g openclaw@latest"
|
|
45
|
+
npm install -g openclaw@latest &>/dev/null
|
|
46
|
+
if command -v openclaw &>/dev/null; then
|
|
47
|
+
ok "Auto-fixed: OpenClaw installed"
|
|
48
|
+
heal_log "openclaw_installed" "healed" "npm install succeeded"
|
|
49
|
+
return 0
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
52
|
+
return 1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
check_gateway_running() {
|
|
56
|
+
local port=${OPENCLAW_GATEWAY_PORT:-18789}
|
|
57
|
+
if curl -s --max-time 3 "http://localhost:${port}/health" &>/dev/null; then
|
|
58
|
+
ok "Gateway responding on port ${port}"
|
|
59
|
+
heal_log "gateway_running" "ok" "port ${port}"
|
|
60
|
+
return 0
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
fail "Gateway not responding on port ${port}"
|
|
64
|
+
heal_log "gateway_running" "fail" "port ${port} no response"
|
|
65
|
+
|
|
66
|
+
# Auto-fix: try starting
|
|
67
|
+
warn "Attempting auto-fix: openclaw gateway start"
|
|
68
|
+
openclaw gateway start &>/dev/null &
|
|
69
|
+
sleep 5
|
|
70
|
+
if curl -s --max-time 3 "http://localhost:${port}/health" &>/dev/null; then
|
|
71
|
+
ok "Auto-fixed: Gateway started"
|
|
72
|
+
heal_log "gateway_running" "healed" "gateway start succeeded"
|
|
73
|
+
return 0
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# Check if launchd service exists
|
|
77
|
+
if launchctl list 2>/dev/null | grep -q "openclaw"; then
|
|
78
|
+
warn "Attempting: launchctl kickstart"
|
|
79
|
+
launchctl kickstart -k "gui/$(id -u)/com.openclaw.gateway" 2>/dev/null
|
|
80
|
+
sleep 5
|
|
81
|
+
if curl -s --max-time 3 "http://localhost:${port}/health" &>/dev/null; then
|
|
82
|
+
ok "Auto-fixed: Gateway kickstarted via launchd"
|
|
83
|
+
heal_log "gateway_running" "healed" "launchctl kickstart"
|
|
84
|
+
return 0
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
return 1
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
check_config_valid() {
|
|
91
|
+
local config="${OPENCLAW_DIR}/openclaw.json"
|
|
92
|
+
if [[ ! -f "$config" ]]; then
|
|
93
|
+
fail "No openclaw.json found at ${config}"
|
|
94
|
+
heal_log "config_valid" "fail" "file missing"
|
|
95
|
+
return 1
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# Check valid JSON
|
|
99
|
+
if node -e "JSON.parse(require('fs').readFileSync('${config}','utf8'))" 2>/dev/null; then
|
|
100
|
+
ok "openclaw.json is valid JSON"
|
|
101
|
+
heal_log "config_valid" "ok" ""
|
|
102
|
+
return 0
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
fail "openclaw.json is invalid JSON"
|
|
106
|
+
heal_log "config_valid" "fail" "parse error"
|
|
107
|
+
|
|
108
|
+
# Auto-fix: try to find backup
|
|
109
|
+
if [[ -f "${config}.bak" ]]; then
|
|
110
|
+
warn "Restoring from backup: openclaw.json.bak"
|
|
111
|
+
cp "${config}.bak" "$config"
|
|
112
|
+
if node -e "JSON.parse(require('fs').readFileSync('${config}','utf8'))" 2>/dev/null; then
|
|
113
|
+
ok "Auto-fixed: restored from backup"
|
|
114
|
+
heal_log "config_valid" "healed" "restored .bak"
|
|
115
|
+
return 0
|
|
116
|
+
fi
|
|
117
|
+
fi
|
|
118
|
+
return 1
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
check_disk_space() {
|
|
122
|
+
local free_gb
|
|
123
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
124
|
+
free_gb=$(df -g / | tail -1 | awk '{print $4}')
|
|
125
|
+
else
|
|
126
|
+
free_gb=$(df -BG / | tail -1 | awk '{print $4}' | tr -d 'G')
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
if (( free_gb < 1 )); then
|
|
130
|
+
fail "Critically low disk space: ${free_gb}GB free"
|
|
131
|
+
heal_log "disk_space" "fail" "${free_gb}GB"
|
|
132
|
+
return 1
|
|
133
|
+
elif (( free_gb < 3 )); then
|
|
134
|
+
warn "Low disk space: ${free_gb}GB free (recommend 5GB+)"
|
|
135
|
+
heal_log "disk_space" "warn" "${free_gb}GB"
|
|
136
|
+
return 0
|
|
137
|
+
fi
|
|
138
|
+
ok "Disk space: ${free_gb}GB free"
|
|
139
|
+
heal_log "disk_space" "ok" "${free_gb}GB"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
check_node_version() {
|
|
143
|
+
if ! command -v node &>/dev/null; then
|
|
144
|
+
fail "Node.js not installed"
|
|
145
|
+
heal_log "node_version" "fail" "not installed"
|
|
146
|
+
return 1
|
|
147
|
+
fi
|
|
148
|
+
local ver major
|
|
149
|
+
ver=$(node -v | sed 's/v//')
|
|
150
|
+
major=$(echo "$ver" | cut -d. -f1)
|
|
151
|
+
if (( major < 22 )); then
|
|
152
|
+
warn "Node.js v${ver} (v22+ recommended)"
|
|
153
|
+
heal_log "node_version" "warn" "v${ver}"
|
|
154
|
+
return 0
|
|
155
|
+
fi
|
|
156
|
+
ok "Node.js v${ver}"
|
|
157
|
+
heal_log "node_version" "ok" "v${ver}"
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
check_auth_profiles() {
|
|
161
|
+
local config="${OPENCLAW_DIR}/openclaw.json"
|
|
162
|
+
[[ ! -f "$config" ]] && return 1
|
|
163
|
+
|
|
164
|
+
local profile_count
|
|
165
|
+
profile_count=$(node -e "
|
|
166
|
+
try {
|
|
167
|
+
const c = JSON.parse(require('fs').readFileSync('${config}','utf8'));
|
|
168
|
+
const profiles = c.auth?.profiles || {};
|
|
169
|
+
console.log(Object.keys(profiles).length);
|
|
170
|
+
} catch(e) { console.log(0); }
|
|
171
|
+
" 2>/dev/null)
|
|
172
|
+
|
|
173
|
+
if (( profile_count == 0 )); then
|
|
174
|
+
warn "No auth profiles configured. Run: openclaw configure"
|
|
175
|
+
heal_log "auth_profiles" "warn" "0 profiles"
|
|
176
|
+
return 0
|
|
177
|
+
fi
|
|
178
|
+
ok "${profile_count} auth profile(s) configured"
|
|
179
|
+
heal_log "auth_profiles" "ok" "${profile_count} profiles"
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
check_mission_control() {
|
|
183
|
+
if curl -s -o /dev/null --max-time 3 http://localhost:3000; then
|
|
184
|
+
ok "Mission Control responding on port 3000"
|
|
185
|
+
heal_log "mission_control" "ok" "port 3000"
|
|
186
|
+
return 0
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# Not critical, just warn
|
|
190
|
+
info "Mission Control not running (optional)"
|
|
191
|
+
heal_log "mission_control" "info" "not running"
|
|
192
|
+
return 0
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
# ─── Gemini-powered diagnosis (for complex issues) ────────────────────────────
|
|
196
|
+
|
|
197
|
+
diagnose_with_gemini() {
|
|
198
|
+
local error_desc="$1"
|
|
199
|
+
# Try loading from .env file
|
|
200
|
+
local gemini_key="${GEMINI_API_KEY:-}"
|
|
201
|
+
if [[ -z "$gemini_key" && -f "${ANTIGRAVITY_DIR}/.env" ]]; then
|
|
202
|
+
gemini_key=$(grep "^GEMINI_API_KEY=" "${ANTIGRAVITY_DIR}/.env" 2>/dev/null | cut -d= -f2-)
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
if [[ -z "$gemini_key" ]]; then
|
|
206
|
+
info "Gemini API key not set. Skipping AI diagnosis."
|
|
207
|
+
info "Set GEMINI_API_KEY for smart auto-healing."
|
|
208
|
+
return 1
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
info "Asking Gemini to diagnose: ${error_desc}"
|
|
212
|
+
|
|
213
|
+
local system_info
|
|
214
|
+
system_info=$(cat <<EOF
|
|
215
|
+
macOS: $(sw_vers -productVersion 2>/dev/null || echo "unknown")
|
|
216
|
+
Node: $(node -v 2>/dev/null || echo "not installed")
|
|
217
|
+
OpenClaw: $(openclaw --version 2>/dev/null || echo "not installed")
|
|
218
|
+
Disk free: $(df -g / 2>/dev/null | tail -1 | awk '{print $4}')GB
|
|
219
|
+
Gateway port: ${OPENCLAW_GATEWAY_PORT:-18789}
|
|
220
|
+
Config exists: $(test -f ~/.openclaw/openclaw.json && echo "yes" || echo "no")
|
|
221
|
+
EOF
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
local payload
|
|
225
|
+
payload=$(node -e "
|
|
226
|
+
console.log(JSON.stringify({
|
|
227
|
+
contents: [{
|
|
228
|
+
parts: [{
|
|
229
|
+
text: 'You are a system admin for OpenClaw (AI assistant platform). Diagnose this issue and suggest a specific shell command to fix it. Be concise. One command if possible.\n\nSystem info:\n${system_info}\n\nError: ${error_desc}\n\nRespond with just the fix command, nothing else. If no command can fix it, say MANUAL: and explain in one sentence.'
|
|
230
|
+
}]
|
|
231
|
+
}]
|
|
232
|
+
}));
|
|
233
|
+
" 2>/dev/null)
|
|
234
|
+
|
|
235
|
+
local response
|
|
236
|
+
response=$(curl -s --max-time 15 \
|
|
237
|
+
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent?key=${gemini_key}" \
|
|
238
|
+
-H "Content-Type: application/json" \
|
|
239
|
+
-d "$payload" 2>/dev/null)
|
|
240
|
+
|
|
241
|
+
local fix
|
|
242
|
+
fix=$(node -e "
|
|
243
|
+
try {
|
|
244
|
+
const r = JSON.parse('$(echo "$response" | sed "s/'/\\\\'/g")');
|
|
245
|
+
console.log(r.candidates?.[0]?.content?.parts?.[0]?.text || 'MANUAL: Could not parse response');
|
|
246
|
+
} catch(e) { console.log('MANUAL: API error'); }
|
|
247
|
+
" 2>/dev/null)
|
|
248
|
+
|
|
249
|
+
if [[ "$fix" == MANUAL:* ]]; then
|
|
250
|
+
info "${fix#MANUAL: }"
|
|
251
|
+
heal_log "gemini_diagnosis" "manual" "$fix"
|
|
252
|
+
return 1
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
warn "Gemini suggests: ${fix}"
|
|
256
|
+
heal_log "gemini_diagnosis" "suggestion" "$fix"
|
|
257
|
+
echo "$fix"
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
# ─── Main Entry Points ────────────────────────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
run_health_check() {
|
|
263
|
+
echo -e "\n${BOLD}🛡️ Antigravity Health Check${RESET}"
|
|
264
|
+
echo -e "${DIM}$(date)${RESET}\n"
|
|
265
|
+
|
|
266
|
+
local issues=0
|
|
267
|
+
check_node_version || ((issues++))
|
|
268
|
+
check_openclaw_installed || ((issues++))
|
|
269
|
+
check_config_valid || ((issues++))
|
|
270
|
+
check_gateway_running || ((issues++))
|
|
271
|
+
check_auth_profiles
|
|
272
|
+
check_disk_space || ((issues++))
|
|
273
|
+
check_mission_control
|
|
274
|
+
|
|
275
|
+
echo
|
|
276
|
+
if (( issues == 0 )); then
|
|
277
|
+
ok "All health checks passed"
|
|
278
|
+
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) HEALTH_OK" >> "$HEALTH_LOG"
|
|
279
|
+
else
|
|
280
|
+
warn "${issues} issue(s) found (auto-fix attempted where possible)"
|
|
281
|
+
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) HEALTH_ISSUES=${issues}" >> "$HEALTH_LOG"
|
|
282
|
+
fi
|
|
283
|
+
return $issues
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
run_heal() {
|
|
287
|
+
local error_desc="$1"
|
|
288
|
+
if [[ -z "$error_desc" ]]; then
|
|
289
|
+
echo "Usage: antigravity-standalone.sh heal 'description of the problem'"
|
|
290
|
+
return 1
|
|
291
|
+
fi
|
|
292
|
+
diagnose_with_gemini "$error_desc"
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# ─── CLI ───────────────────────────────────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
case "${1:-health}" in
|
|
298
|
+
health) run_health_check ;;
|
|
299
|
+
heal) run_heal "$2" ;;
|
|
300
|
+
*) echo "Usage: $0 {health|heal 'error description'}" ;;
|
|
301
|
+
esac
|