@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,140 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# O7 OpenClaw Installer — Antigravity Self-Healer Integration
|
|
3
|
+
|
|
4
|
+
ANTIGRAVITY_SCRIPT="${HOME}/.openclaw/workspace/projects/o7-os/module-engine/antigravity.js"
|
|
5
|
+
|
|
6
|
+
setup_antigravity() {
|
|
7
|
+
section 9 "Antigravity Self-Healer"
|
|
8
|
+
|
|
9
|
+
# Check if Antigravity exists
|
|
10
|
+
if [[ ! -f "$ANTIGRAVITY_SCRIPT" ]]; then
|
|
11
|
+
warn "Antigravity script not found at expected path."
|
|
12
|
+
info "It will be installed when Unified MC is set up."
|
|
13
|
+
log "ANTIGRAVITY: script not found, skipping"
|
|
14
|
+
return 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Check for Gemini API key (required for Antigravity)
|
|
18
|
+
local gemini_key
|
|
19
|
+
gemini_key=$(grep -o '"GEMINI_API_KEY"[[:space:]]*:[[:space:]]*"[^"]*"' ~/.openclaw/openclaw.json 2>/dev/null | head -1 | sed 's/.*: *"//;s/"//')
|
|
20
|
+
|
|
21
|
+
if [[ -z "$gemini_key" ]]; then
|
|
22
|
+
# Check if they set up Gemini in auth phase
|
|
23
|
+
local has_gemini=false
|
|
24
|
+
for result in "${AUTH_RESULTS[@]}"; do
|
|
25
|
+
if [[ "$result" == "gemini:ok" ]]; then
|
|
26
|
+
has_gemini=true
|
|
27
|
+
break
|
|
28
|
+
fi
|
|
29
|
+
done
|
|
30
|
+
|
|
31
|
+
if [[ "$has_gemini" == "false" ]]; then
|
|
32
|
+
warn "Antigravity needs a Gemini API key (it's free)."
|
|
33
|
+
echo -e " ${CYAN}→ https://aistudio.google.com/apikey${RESET}"
|
|
34
|
+
local ag_key
|
|
35
|
+
ask " Paste your Gemini API key (or press Enter to skip):" ag_key true
|
|
36
|
+
if [[ -n "$ag_key" ]]; then
|
|
37
|
+
gemini_key="$ag_key"
|
|
38
|
+
else
|
|
39
|
+
warn "Skipping Antigravity. You can set it up later."
|
|
40
|
+
log "ANTIGRAVITY: no gemini key, skipped"
|
|
41
|
+
return 0
|
|
42
|
+
fi
|
|
43
|
+
fi
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Run initial health check
|
|
47
|
+
info "Running Antigravity health check..."
|
|
48
|
+
local health_output
|
|
49
|
+
if [[ -n "$gemini_key" ]]; then
|
|
50
|
+
health_output=$(GEMINI_API_KEY="$gemini_key" node "$ANTIGRAVITY_SCRIPT" health 2>&1)
|
|
51
|
+
else
|
|
52
|
+
health_output=$(node "$ANTIGRAVITY_SCRIPT" health 2>&1)
|
|
53
|
+
fi
|
|
54
|
+
local exit_code=$?
|
|
55
|
+
|
|
56
|
+
if (( exit_code == 0 )); then
|
|
57
|
+
ok "Antigravity health check passed"
|
|
58
|
+
else
|
|
59
|
+
warn "Antigravity found issues:"
|
|
60
|
+
echo "$health_output" | while read -r line; do
|
|
61
|
+
echo -e " ${DIM}${line}${RESET}"
|
|
62
|
+
done
|
|
63
|
+
info "Antigravity will auto-heal these on the next cycle."
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# Install launchd timer for periodic health checks (every 30 min)
|
|
67
|
+
setup_antigravity_timer "$gemini_key"
|
|
68
|
+
|
|
69
|
+
log "ANTIGRAVITY: setup complete"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setup_antigravity_timer() {
|
|
73
|
+
local gemini_key="$1"
|
|
74
|
+
info "Setting up Antigravity auto-healer (runs every 30 minutes)..."
|
|
75
|
+
|
|
76
|
+
local plist_path="${HOME}/Library/LaunchAgents/com.o7.antigravity.plist"
|
|
77
|
+
|
|
78
|
+
cat > "$plist_path" << PLIST
|
|
79
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
80
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
81
|
+
<plist version="1.0">
|
|
82
|
+
<dict>
|
|
83
|
+
<key>Label</key>
|
|
84
|
+
<string>com.o7.antigravity</string>
|
|
85
|
+
<key>ProgramArguments</key>
|
|
86
|
+
<array>
|
|
87
|
+
<string>$(which node)</string>
|
|
88
|
+
<string>${ANTIGRAVITY_SCRIPT}</string>
|
|
89
|
+
<string>health</string>
|
|
90
|
+
</array>
|
|
91
|
+
<key>EnvironmentVariables</key>
|
|
92
|
+
<dict>
|
|
93
|
+
<key>PATH</key>
|
|
94
|
+
<string>$(dirname "$(which node)"):/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
95
|
+
PLIST
|
|
96
|
+
|
|
97
|
+
if [[ -n "$gemini_key" ]]; then
|
|
98
|
+
cat >> "$plist_path" << PLIST
|
|
99
|
+
<key>GEMINI_API_KEY</key>
|
|
100
|
+
<string>${gemini_key}</string>
|
|
101
|
+
PLIST
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
cat >> "$plist_path" << PLIST
|
|
105
|
+
</dict>
|
|
106
|
+
<key>StartInterval</key>
|
|
107
|
+
<integer>1800</integer>
|
|
108
|
+
<key>RunAtLoad</key>
|
|
109
|
+
<false/>
|
|
110
|
+
<key>StandardOutPath</key>
|
|
111
|
+
<string>/tmp/antigravity.log</string>
|
|
112
|
+
<key>StandardErrorPath</key>
|
|
113
|
+
<string>/tmp/antigravity.err</string>
|
|
114
|
+
</dict>
|
|
115
|
+
</plist>
|
|
116
|
+
PLIST
|
|
117
|
+
|
|
118
|
+
launchctl unload "$plist_path" 2>/dev/null
|
|
119
|
+
launchctl load "$plist_path" 2>/dev/null
|
|
120
|
+
|
|
121
|
+
if launchctl list | grep -q "com.o7.antigravity"; then
|
|
122
|
+
ok "Antigravity timer installed (every 30 min)"
|
|
123
|
+
log "ANTIGRAVITY: launchd timer installed"
|
|
124
|
+
else
|
|
125
|
+
warn "Antigravity timer couldn't be loaded. You can load it manually:"
|
|
126
|
+
detail "launchctl load $plist_path"
|
|
127
|
+
log "ANTIGRAVITY: launchd timer load failed"
|
|
128
|
+
fi
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Run Antigravity heal on a specific error from the install
|
|
132
|
+
heal_install_error() {
|
|
133
|
+
local error_desc="$1"
|
|
134
|
+
if [[ -f "$ANTIGRAVITY_SCRIPT" ]]; then
|
|
135
|
+
info "Asking Antigravity to diagnose: ${error_desc}"
|
|
136
|
+
node "$ANTIGRAVITY_SCRIPT" heal "$error_desc" 2>&1 | while read -r line; do
|
|
137
|
+
echo -e " ${DIM}${line}${RESET}"
|
|
138
|
+
done
|
|
139
|
+
fi
|
|
140
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# O7 OpenClaw Installer — Smart Auth Setup
|
|
3
|
+
# NEVER asks for raw API keys for Claude/ChatGPT. Uses OAuth/CLI flows.
|
|
4
|
+
|
|
5
|
+
AUTH_RESULTS=() # Track what worked
|
|
6
|
+
|
|
7
|
+
setup_claude() {
|
|
8
|
+
step "Setting up Claude (Anthropic)"
|
|
9
|
+
echo
|
|
10
|
+
echo -e "${BOLD} Claude is the primary AI model. We'll authenticate via browser.${RESET}"
|
|
11
|
+
echo -e "${DIM} This opens your browser — sign in with your Anthropic account.${RESET}"
|
|
12
|
+
echo -e "${DIM} No API key needed. Just your login.${RESET}"
|
|
13
|
+
echo
|
|
14
|
+
|
|
15
|
+
if ! confirm " Ready to authenticate with Claude?"; then
|
|
16
|
+
warn "Skipping Claude setup. You can set it up later with: openclaw configure"
|
|
17
|
+
AUTH_RESULTS+=("claude:skipped")
|
|
18
|
+
return 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
info "Launching browser authentication..."
|
|
22
|
+
openclaw onboard --auth-choice setup-token --non-interactive=false 2>&1 | while read -r line; do
|
|
23
|
+
echo -e " ${DIM}${line}${RESET}"
|
|
24
|
+
done
|
|
25
|
+
local exit_code=${PIPESTATUS[0]}
|
|
26
|
+
|
|
27
|
+
if (( exit_code == 0 )); then
|
|
28
|
+
ok "Claude authentication complete"
|
|
29
|
+
AUTH_RESULTS+=("claude:ok")
|
|
30
|
+
log "AUTH OK: Claude (setup-token)"
|
|
31
|
+
return 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Fallback: try oauth flow
|
|
35
|
+
warn "Setup token failed. Trying OAuth flow..."
|
|
36
|
+
openclaw onboard --auth-choice oauth 2>&1 | while read -r line; do
|
|
37
|
+
echo -e " ${DIM}${line}${RESET}"
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
if (( ${PIPESTATUS[0]} == 0 )); then
|
|
41
|
+
ok "Claude OAuth authentication complete"
|
|
42
|
+
AUTH_RESULTS+=("claude:ok")
|
|
43
|
+
log "AUTH OK: Claude (oauth fallback)"
|
|
44
|
+
return 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
fail "Claude authentication failed."
|
|
48
|
+
info "You can set it up manually later: openclaw configure"
|
|
49
|
+
AUTH_RESULTS+=("claude:failed")
|
|
50
|
+
log "AUTH FAIL: Claude"
|
|
51
|
+
return 1
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
setup_chatgpt() {
|
|
55
|
+
step "Setting up ChatGPT / Codex (OpenAI)"
|
|
56
|
+
echo
|
|
57
|
+
echo -e "${BOLD} ChatGPT/Codex uses OAuth — just sign in with your OpenAI account.${RESET}"
|
|
58
|
+
echo -e "${DIM} This will open your browser. No API key needed.${RESET}"
|
|
59
|
+
echo
|
|
60
|
+
|
|
61
|
+
if ! confirm " Ready to authenticate with OpenAI?"; then
|
|
62
|
+
warn "Skipping ChatGPT/Codex. Set up later: openclaw configure"
|
|
63
|
+
AUTH_RESULTS+=("openai:skipped")
|
|
64
|
+
return 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
info "Launching OpenAI OAuth..."
|
|
68
|
+
openclaw onboard --auth-choice openai-codex 2>&1 | while read -r line; do
|
|
69
|
+
echo -e " ${DIM}${line}${RESET}"
|
|
70
|
+
done
|
|
71
|
+
local exit_code=${PIPESTATUS[0]}
|
|
72
|
+
|
|
73
|
+
if (( exit_code == 0 )); then
|
|
74
|
+
ok "ChatGPT/Codex authentication complete"
|
|
75
|
+
AUTH_RESULTS+=("openai:ok")
|
|
76
|
+
log "AUTH OK: OpenAI (codex oauth)"
|
|
77
|
+
return 0
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
fail "OpenAI authentication failed."
|
|
81
|
+
AUTH_RESULTS+=("openai:failed")
|
|
82
|
+
log "AUTH FAIL: OpenAI"
|
|
83
|
+
return 1
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
setup_gemini() {
|
|
87
|
+
step "Setting up Gemini (Google)"
|
|
88
|
+
echo
|
|
89
|
+
echo -e "${BOLD} Gemini uses Google OAuth — sign in with your Google account.${RESET}"
|
|
90
|
+
echo -e "${DIM} Free tier, no credit card needed. 1M+ context window.${RESET}"
|
|
91
|
+
echo
|
|
92
|
+
|
|
93
|
+
if ! confirm " Ready to authenticate with Google?"; then
|
|
94
|
+
warn "Skipping Gemini. Set up later: openclaw configure"
|
|
95
|
+
AUTH_RESULTS+=("gemini:skipped")
|
|
96
|
+
return 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
info "Launching Gemini CLI OAuth..."
|
|
100
|
+
openclaw onboard --auth-choice google-gemini-cli 2>&1 | while read -r line; do
|
|
101
|
+
echo -e " ${DIM}${line}${RESET}"
|
|
102
|
+
done
|
|
103
|
+
local exit_code=${PIPESTATUS[0]}
|
|
104
|
+
|
|
105
|
+
if (( exit_code == 0 )); then
|
|
106
|
+
ok "Gemini authentication complete"
|
|
107
|
+
AUTH_RESULTS+=("gemini:ok")
|
|
108
|
+
log "AUTH OK: Gemini (cli oauth)"
|
|
109
|
+
return 0
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# Fallback: API key
|
|
113
|
+
warn "OAuth failed. Let's try with an API key instead."
|
|
114
|
+
echo
|
|
115
|
+
echo -e " ${BOLD}Get a free API key:${RESET}"
|
|
116
|
+
echo -e " ${CYAN}→ https://aistudio.google.com/apikey${RESET}"
|
|
117
|
+
echo -e " ${DIM} Sign in with Google, click 'Create API Key', copy it.${RESET}"
|
|
118
|
+
echo
|
|
119
|
+
|
|
120
|
+
local gemini_key
|
|
121
|
+
ask " Paste your Gemini API key:" gemini_key true
|
|
122
|
+
|
|
123
|
+
if [[ -n "$gemini_key" ]]; then
|
|
124
|
+
openclaw onboard --auth-choice gemini-api-key --gemini-api-key "$gemini_key" 2>&1 | while read -r line; do
|
|
125
|
+
echo -e " ${DIM}${line}${RESET}"
|
|
126
|
+
done
|
|
127
|
+
if (( ${PIPESTATUS[0]} == 0 )); then
|
|
128
|
+
ok "Gemini configured with API key"
|
|
129
|
+
AUTH_RESULTS+=("gemini:ok")
|
|
130
|
+
log "AUTH OK: Gemini (api key fallback)"
|
|
131
|
+
return 0
|
|
132
|
+
fi
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
fail "Gemini setup failed."
|
|
136
|
+
AUTH_RESULTS+=("gemini:failed")
|
|
137
|
+
log "AUTH FAIL: Gemini"
|
|
138
|
+
return 1
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
setup_kimi() {
|
|
142
|
+
step "Setting up Kimi K2.5 (Ollama Cloud)"
|
|
143
|
+
echo
|
|
144
|
+
echo -e "${BOLD} Kimi K2.5 is our design AI. Runs via Ollama Cloud.${RESET}"
|
|
145
|
+
echo -e "${DIM} You'll need an API key from ollama.com.${RESET}"
|
|
146
|
+
echo
|
|
147
|
+
echo -e " ${BOLD}Get your key:${RESET}"
|
|
148
|
+
echo -e " ${CYAN}→ https://ollama.com/settings/keys${RESET}"
|
|
149
|
+
echo -e " ${DIM} 1. Sign in or create an account at ollama.com${RESET}"
|
|
150
|
+
echo -e " ${DIM} 2. Go to Settings → API Keys${RESET}"
|
|
151
|
+
echo -e " ${DIM} 3. Click 'Create new key', copy it${RESET}"
|
|
152
|
+
echo
|
|
153
|
+
|
|
154
|
+
if ! confirm " Have your Ollama API key ready?"; then
|
|
155
|
+
warn "Skipping Kimi. Set up later: openclaw configure"
|
|
156
|
+
AUTH_RESULTS+=("kimi:skipped")
|
|
157
|
+
return 1
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
local ollama_key
|
|
161
|
+
ask " Paste your Ollama API key:" ollama_key true
|
|
162
|
+
|
|
163
|
+
if [[ -z "$ollama_key" ]]; then
|
|
164
|
+
warn "No key entered. Skipping Kimi."
|
|
165
|
+
AUTH_RESULTS+=("kimi:skipped")
|
|
166
|
+
return 1
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
# Configure as custom provider
|
|
170
|
+
info "Configuring Kimi K2.5 via Ollama Cloud..."
|
|
171
|
+
openclaw onboard \
|
|
172
|
+
--auth-choice custom-api-key \
|
|
173
|
+
--custom-api-key "$ollama_key" \
|
|
174
|
+
--custom-base-url "https://ollama.com/v1" \
|
|
175
|
+
--custom-provider-id "ollama" \
|
|
176
|
+
--custom-model-id "kimi-k2.5:cloud" \
|
|
177
|
+
--custom-compatibility openai \
|
|
178
|
+
--token-profile-id "ollama:cloud" \
|
|
179
|
+
2>&1 | while read -r line; do
|
|
180
|
+
echo -e " ${DIM}${line}${RESET}"
|
|
181
|
+
done
|
|
182
|
+
local exit_code=${PIPESTATUS[0]}
|
|
183
|
+
|
|
184
|
+
if (( exit_code == 0 )); then
|
|
185
|
+
ok "Kimi K2.5 configured"
|
|
186
|
+
AUTH_RESULTS+=("kimi:ok")
|
|
187
|
+
log "AUTH OK: Kimi K2.5 (ollama cloud)"
|
|
188
|
+
return 0
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
fail "Kimi K2.5 configuration failed."
|
|
192
|
+
info "You can add it manually later in openclaw.json"
|
|
193
|
+
AUTH_RESULTS+=("kimi:failed")
|
|
194
|
+
log "AUTH FAIL: Kimi K2.5"
|
|
195
|
+
return 1
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
setup_github_copilot() {
|
|
199
|
+
step "Setting up GitHub Copilot"
|
|
200
|
+
echo
|
|
201
|
+
echo -e "${BOLD} GitHub Copilot gives access to Claude and GPT models for free.${RESET}"
|
|
202
|
+
echo -e "${DIM} Requires a GitHub account. Browser sign-in.${RESET}"
|
|
203
|
+
echo
|
|
204
|
+
|
|
205
|
+
if ! confirm " Set up GitHub Copilot?"; then
|
|
206
|
+
warn "Skipping GitHub Copilot."
|
|
207
|
+
AUTH_RESULTS+=("copilot:skipped")
|
|
208
|
+
return 1
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# Check if gh CLI is installed
|
|
212
|
+
if ! command -v gh &>/dev/null; then
|
|
213
|
+
info "Installing GitHub CLI..."
|
|
214
|
+
brew install gh &
|
|
215
|
+
spin $! "Installing gh..."
|
|
216
|
+
wait $!
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
# Auth with GitHub first if needed
|
|
220
|
+
if ! gh auth status &>/dev/null 2>&1; then
|
|
221
|
+
info "Authenticating with GitHub..."
|
|
222
|
+
gh auth login -w 2>&1 | while read -r line; do
|
|
223
|
+
echo -e " ${DIM}${line}${RESET}"
|
|
224
|
+
done
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
info "Configuring GitHub Copilot for OpenClaw..."
|
|
228
|
+
openclaw onboard --auth-choice github-copilot 2>&1 | while read -r line; do
|
|
229
|
+
echo -e " ${DIM}${line}${RESET}"
|
|
230
|
+
done
|
|
231
|
+
local exit_code=${PIPESTATUS[0]}
|
|
232
|
+
|
|
233
|
+
if (( exit_code == 0 )); then
|
|
234
|
+
ok "GitHub Copilot configured"
|
|
235
|
+
AUTH_RESULTS+=("copilot:ok")
|
|
236
|
+
log "AUTH OK: GitHub Copilot"
|
|
237
|
+
return 0
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
fail "GitHub Copilot setup failed."
|
|
241
|
+
AUTH_RESULTS+=("copilot:failed")
|
|
242
|
+
log "AUTH FAIL: GitHub Copilot"
|
|
243
|
+
return 1
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
run_auth_setup() {
|
|
247
|
+
section 3 "AI Model Authentication"
|
|
248
|
+
echo
|
|
249
|
+
echo -e "${BOLD} Let's connect your AI models. No raw API keys needed —${RESET}"
|
|
250
|
+
echo -e "${BOLD} just sign in through your browser for each one.${RESET}"
|
|
251
|
+
echo
|
|
252
|
+
echo -e "${DIM} We'll set up the models in order of priority.${RESET}"
|
|
253
|
+
echo -e "${DIM} You need at least ONE working model to continue.${RESET}"
|
|
254
|
+
echo
|
|
255
|
+
|
|
256
|
+
check_internet || return 1
|
|
257
|
+
|
|
258
|
+
# Set up each provider
|
|
259
|
+
setup_claude
|
|
260
|
+
echo
|
|
261
|
+
setup_chatgpt
|
|
262
|
+
echo
|
|
263
|
+
setup_gemini
|
|
264
|
+
echo
|
|
265
|
+
setup_kimi
|
|
266
|
+
echo
|
|
267
|
+
setup_github_copilot
|
|
268
|
+
|
|
269
|
+
# Check if at least one provider works
|
|
270
|
+
local working=0
|
|
271
|
+
for result in "${AUTH_RESULTS[@]}"; do
|
|
272
|
+
if [[ "$result" == *":ok" ]]; then
|
|
273
|
+
((working++))
|
|
274
|
+
fi
|
|
275
|
+
done
|
|
276
|
+
|
|
277
|
+
echo
|
|
278
|
+
if (( working == 0 )); then
|
|
279
|
+
fail "No AI models configured. You need at least one to use OpenClaw."
|
|
280
|
+
info "Re-run the installer or set up manually: openclaw configure"
|
|
281
|
+
return 1
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
ok "${working} model(s) configured successfully"
|
|
285
|
+
log "AUTH SUMMARY: ${working} working — ${AUTH_RESULTS[*]}"
|
|
286
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# O7 OpenClaw Installer — System Checks
|
|
3
|
+
|
|
4
|
+
check_macos_version() {
|
|
5
|
+
step "Checking macOS version"
|
|
6
|
+
local ver
|
|
7
|
+
ver=$(sw_vers -productVersion 2>/dev/null)
|
|
8
|
+
if [[ -z "$ver" ]]; then
|
|
9
|
+
fail "Not running macOS. This installer is for Mac only."
|
|
10
|
+
log "FAIL: Not macOS"
|
|
11
|
+
return 1
|
|
12
|
+
fi
|
|
13
|
+
local major
|
|
14
|
+
major=$(echo "$ver" | cut -d. -f1)
|
|
15
|
+
if (( major < 13 )); then
|
|
16
|
+
fail "macOS $ver detected. Requires macOS 13 (Ventura) or later."
|
|
17
|
+
info "Upgrade your Mac at System Settings → General → Software Update"
|
|
18
|
+
log "FAIL: macOS $ver too old"
|
|
19
|
+
return 1
|
|
20
|
+
fi
|
|
21
|
+
ok "macOS $ver"
|
|
22
|
+
log "OK: macOS $ver"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
check_homebrew() {
|
|
26
|
+
step "Checking Homebrew"
|
|
27
|
+
if command -v brew &>/dev/null; then
|
|
28
|
+
ok "Homebrew $(brew --version | head -1 | awk '{print $2}')"
|
|
29
|
+
log "OK: Homebrew found"
|
|
30
|
+
return 0
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
warn "Homebrew not found. Installing..."
|
|
34
|
+
info "This is the standard macOS package manager. Safe and widely used."
|
|
35
|
+
if ! confirm "Install Homebrew?"; then
|
|
36
|
+
fail "Homebrew is required. Can't continue without it."
|
|
37
|
+
return 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" &
|
|
41
|
+
spin $! "Installing Homebrew..."
|
|
42
|
+
wait $!
|
|
43
|
+
local exit_code=$?
|
|
44
|
+
|
|
45
|
+
if (( exit_code != 0 )); then
|
|
46
|
+
fail "Homebrew installation failed (exit code $exit_code)"
|
|
47
|
+
info "Try running manually: /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
|
|
48
|
+
log "FAIL: Homebrew install exit $exit_code"
|
|
49
|
+
return 1
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Add to PATH for Apple Silicon
|
|
53
|
+
if [[ -f /opt/homebrew/bin/brew ]]; then
|
|
54
|
+
eval "$(/opt/homebrew/bin/brew shellenv)"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
if command -v brew &>/dev/null; then
|
|
58
|
+
ok "Homebrew installed successfully"
|
|
59
|
+
log "OK: Homebrew installed"
|
|
60
|
+
else
|
|
61
|
+
fail "Homebrew installed but not in PATH. Restart your terminal and re-run."
|
|
62
|
+
return 1
|
|
63
|
+
fi
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
check_node() {
|
|
67
|
+
step "Checking Node.js"
|
|
68
|
+
if command -v node &>/dev/null; then
|
|
69
|
+
local node_ver
|
|
70
|
+
node_ver=$(node -v | sed 's/v//')
|
|
71
|
+
local node_major
|
|
72
|
+
node_major=$(echo "$node_ver" | cut -d. -f1)
|
|
73
|
+
if (( node_major >= 22 )); then
|
|
74
|
+
ok "Node.js v${node_ver}"
|
|
75
|
+
log "OK: Node $node_ver"
|
|
76
|
+
return 0
|
|
77
|
+
else
|
|
78
|
+
warn "Node.js v${node_ver} found but v22+ is required."
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
info "Installing Node.js via Homebrew..."
|
|
83
|
+
brew install node &
|
|
84
|
+
spin $! "Installing Node.js..."
|
|
85
|
+
wait $!
|
|
86
|
+
|
|
87
|
+
if command -v node &>/dev/null; then
|
|
88
|
+
local installed_ver
|
|
89
|
+
installed_ver=$(node -v | sed 's/v//')
|
|
90
|
+
ok "Node.js v${installed_ver} installed"
|
|
91
|
+
log "OK: Node $installed_ver installed"
|
|
92
|
+
else
|
|
93
|
+
fail "Node.js installation failed."
|
|
94
|
+
info "Try: brew install node"
|
|
95
|
+
log "FAIL: Node install failed"
|
|
96
|
+
return 1
|
|
97
|
+
fi
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
check_git() {
|
|
101
|
+
step "Checking Git"
|
|
102
|
+
if command -v git &>/dev/null; then
|
|
103
|
+
ok "Git $(git --version | awk '{print $3}')"
|
|
104
|
+
log "OK: Git found"
|
|
105
|
+
return 0
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
info "Installing Git..."
|
|
109
|
+
brew install git &
|
|
110
|
+
spin $! "Installing Git..."
|
|
111
|
+
wait $!
|
|
112
|
+
|
|
113
|
+
if command -v git &>/dev/null; then
|
|
114
|
+
ok "Git installed"
|
|
115
|
+
log "OK: Git installed"
|
|
116
|
+
else
|
|
117
|
+
fail "Git installation failed. Try: brew install git"
|
|
118
|
+
log "FAIL: Git install failed"
|
|
119
|
+
return 1
|
|
120
|
+
fi
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
check_pnpm() {
|
|
124
|
+
step "Checking pnpm"
|
|
125
|
+
if command -v pnpm &>/dev/null; then
|
|
126
|
+
ok "pnpm $(pnpm --version)"
|
|
127
|
+
log "OK: pnpm found"
|
|
128
|
+
return 0
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
info "Installing pnpm..."
|
|
132
|
+
npm install -g pnpm &
|
|
133
|
+
spin $! "Installing pnpm..."
|
|
134
|
+
wait $!
|
|
135
|
+
|
|
136
|
+
if command -v pnpm &>/dev/null; then
|
|
137
|
+
ok "pnpm installed"
|
|
138
|
+
log "OK: pnpm installed"
|
|
139
|
+
else
|
|
140
|
+
fail "pnpm installation failed. Try: npm install -g pnpm"
|
|
141
|
+
log "FAIL: pnpm install failed"
|
|
142
|
+
return 1
|
|
143
|
+
fi
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
check_disk_space() {
|
|
147
|
+
step "Checking disk space"
|
|
148
|
+
local free_gb
|
|
149
|
+
free_gb=$(df -g / | tail -1 | awk '{print $4}')
|
|
150
|
+
if (( free_gb < 2 )); then
|
|
151
|
+
fail "Only ${free_gb}GB free. Need at least 2GB."
|
|
152
|
+
info "Free up disk space and try again."
|
|
153
|
+
log "FAIL: Only ${free_gb}GB free"
|
|
154
|
+
return 1
|
|
155
|
+
fi
|
|
156
|
+
ok "${free_gb}GB free"
|
|
157
|
+
log "OK: ${free_gb}GB free"
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
run_all_checks() {
|
|
161
|
+
section 1 "System Check"
|
|
162
|
+
local failed=0
|
|
163
|
+
check_macos_version || ((failed++))
|
|
164
|
+
check_homebrew || ((failed++))
|
|
165
|
+
check_node || ((failed++))
|
|
166
|
+
check_git || ((failed++))
|
|
167
|
+
check_pnpm || ((failed++))
|
|
168
|
+
check_disk_space || ((failed++))
|
|
169
|
+
|
|
170
|
+
if (( failed > 0 )); then
|
|
171
|
+
echo
|
|
172
|
+
fail "$failed prerequisite(s) failed. Fix the issues above and re-run."
|
|
173
|
+
return 1
|
|
174
|
+
fi
|
|
175
|
+
echo
|
|
176
|
+
ok "All system checks passed!"
|
|
177
|
+
}
|