openclacky 0.9.12 → 0.9.13

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.
@@ -1,891 +0,0 @@
1
- #!/bin/bash
2
- # OpenClacky Installation Script
3
- # This script automatically detects your system and installs OpenClacky
4
-
5
- set -e
6
-
7
- # Brand configuration (populated by --brand-name / --command flags)
8
- BRAND_NAME=""
9
- BRAND_COMMAND=""
10
-
11
- # Colors for output
12
- RED='\033[0;31m'
13
- GREEN='\033[0;32m'
14
- YELLOW='\033[1;33m'
15
- BLUE='\033[0;34m'
16
- NC='\033[0m' # No Color
17
-
18
- # Print colored messages
19
- print_info() {
20
- echo -e "${BLUE}ℹ${NC} $1"
21
- }
22
-
23
- print_success() {
24
- echo -e "${GREEN}✓${NC} $1"
25
- }
26
-
27
- print_warning() {
28
- echo -e "${YELLOW}⚠${NC} $1"
29
- }
30
-
31
- print_error() {
32
- echo -e "${RED}✗${NC} $1"
33
- }
34
-
35
- print_step() {
36
- echo -e "\n${BLUE}==>${NC} $1"
37
- }
38
-
39
- # Detect OS
40
- detect_os() {
41
- case "$(uname -s)" in
42
- Linux*) OS=Linux;;
43
- Darwin*) OS=macOS;;
44
- CYGWIN*) OS=Windows;;
45
- MINGW*) OS=Windows;;
46
- *) OS=Unknown;;
47
- esac
48
- print_info "Detected OS: $OS"
49
-
50
- # Detect Linux distribution
51
- if [ "$OS" = "Linux" ]; then
52
- if [ -f /etc/os-release ]; then
53
- . /etc/os-release
54
- DISTRO=$ID
55
- print_info "Detected Linux distribution: $DISTRO"
56
- else
57
- DISTRO=unknown
58
- fi
59
- fi
60
- }
61
-
62
- # Check if command exists
63
- command_exists() {
64
- command -v "$1" >/dev/null 2>&1
65
- }
66
-
67
- # Detect current shell type based on $SHELL environment variable
68
- # Sets CURRENT_SHELL (e.g. "zsh", "bash", "fish") and SHELL_RC (rc file path)
69
- detect_shell() {
70
- local shell_name
71
- shell_name=$(basename "$SHELL")
72
-
73
- case "$shell_name" in
74
- zsh)
75
- CURRENT_SHELL="zsh"
76
- SHELL_RC="$HOME/.zshrc"
77
- ;;
78
- bash)
79
- CURRENT_SHELL="bash"
80
- # macOS uses ~/.bash_profile; Linux uses ~/.bashrc
81
- if [ "$OS" = "macOS" ]; then
82
- SHELL_RC="$HOME/.bash_profile"
83
- else
84
- SHELL_RC="$HOME/.bashrc"
85
- fi
86
- ;;
87
- fish)
88
- CURRENT_SHELL="fish"
89
- SHELL_RC="$HOME/.config/fish/config.fish"
90
- ;;
91
- *)
92
- # Fallback: treat as bash
93
- CURRENT_SHELL="bash"
94
- SHELL_RC="$HOME/.bashrc"
95
- ;;
96
- esac
97
-
98
- print_info "Detected shell: $CURRENT_SHELL (rc file: $SHELL_RC)"
99
- }
100
-
101
- # Network-aware installer source selection
102
- SLOW_THRESHOLD_MS=5000
103
- NETWORK_REGION="global" # "china" | "global" | "unknown"
104
- USE_CN_MIRRORS=false
105
-
106
- # --- Upstream (global) defaults ---
107
- GITHUB_RAW_BASE_URL="https://raw.githubusercontent.com"
108
- HOMEBREW_INSTALL_SCRIPT_URL="${GITHUB_RAW_BASE_URL}/Homebrew/install/HEAD/install.sh"
109
- OPENCLACKY_INSTALL_SCRIPT_URL="${GITHUB_RAW_BASE_URL}/clacky-ai/openclacky/main/scripts/install.sh"
110
- DEFAULT_RUBYGEMS_URL="https://rubygems.org"
111
- DEFAULT_NPM_REGISTRY="https://registry.npmjs.org"
112
- DEFAULT_MISE_INSTALL_URL="https://mise.run"
113
-
114
- CN_CDN_BASE_URL="https://oss.1024code.com" # reverse proxy for raw.githubusercontent.com
115
-
116
- # --- CN source URLs ---
117
- CN_HOMEBREW_INSTALL_SCRIPT_URL="${CN_CDN_BASE_URL}/Homebrew/install/HEAD/install.sh"
118
- CN_MISE_INSTALL_URL="${CN_CDN_BASE_URL}/mise.sh"
119
- CN_RUBY_PRECOMPILED_URL="${CN_CDN_BASE_URL}/ruby/ruby-{version}.{platform}.tar.gz"
120
- CN_RUBYGEMS_URL="https://mirrors.aliyun.com/rubygems/"
121
- CN_NPM_REGISTRY="https://registry.npmmirror.com"
122
- CN_NODE_MIRROR_URL="https://cdn.npmmirror.com/binaries/node/"
123
-
124
- # --- Homebrew CN mirrors (Aliyun) ---
125
- CN_HOMEBREW_BREW_GIT_REMOTE="https://mirrors.aliyun.com/homebrew/brew.git"
126
- CN_HOMEBREW_CORE_GIT_REMOTE="https://mirrors.aliyun.com/homebrew/homebrew-core.git"
127
- CN_HOMEBREW_BOTTLE_DOMAIN="https://mirrors.aliyun.com/homebrew/homebrew-bottles"
128
- CN_HOMEBREW_API_DOMAIN="https://mirrors.aliyun.com/homebrew-bottles/api"
129
-
130
- # --- Active values (overridden by detect_network_region) ---
131
- MISE_INSTALL_URL="$DEFAULT_MISE_INSTALL_URL"
132
- RUBYGEMS_INSTALL_URL="$DEFAULT_RUBYGEMS_URL"
133
- NPM_REGISTRY_URL="$DEFAULT_NPM_REGISTRY"
134
- NODE_MIRROR_URL="" # empty = mise default (nodejs.org)
135
- RUBY_VERSION_SPEC="ruby@3"
136
-
137
- # Probe a single URL; echoes the round-trip time in milliseconds, or "timeout".
138
- _probe_url() {
139
- local url="$1"
140
- local timeout_sec=5
141
- local curl_output http_code total_time elapsed_ms
142
-
143
- curl_output=$(curl -s -o /dev/null -w "%{http_code} %{time_total}" \
144
- --connect-timeout "$timeout_sec" \
145
- --max-time "$timeout_sec" \
146
- "$url" 2>/dev/null) || true
147
- http_code="${curl_output%% *}"
148
- total_time="${curl_output#* }"
149
-
150
- if [ -z "$http_code" ] || [ "$http_code" = "000" ] || [ "$http_code" = "$curl_output" ]; then
151
- echo "timeout"
152
- else
153
- elapsed_ms=$(awk -v seconds="$total_time" 'BEGIN { printf "%d", seconds * 1000 }')
154
- echo "$elapsed_ms"
155
- fi
156
- }
157
-
158
- _is_timed_result() {
159
- local result="$1"
160
-
161
- [ -n "$result" ] && [ "$result" != "timeout" ] && [ "$result" -ge 0 ] 2>/dev/null
162
- }
163
-
164
- _is_slow_or_unreachable() {
165
- local result="$1"
166
-
167
- if [ "$result" = "timeout" ]; then
168
- return 0
169
- fi
170
-
171
- [ "$result" -ge "$SLOW_THRESHOLD_MS" ] 2>/dev/null
172
- }
173
-
174
- _format_probe_time() {
175
- local result="$1"
176
-
177
- if [ "$result" = "timeout" ]; then
178
- echo "timeout"
179
- else
180
- awk -v ms="$result" 'BEGIN { printf "%.1fs", ms / 1000 }'
181
- fi
182
- }
183
-
184
- _print_probe_result() {
185
- local label="$1"
186
- local result="$2"
187
-
188
- if [ "$result" = "timeout" ]; then
189
- print_warning "UNREACHABLE (${result}) ${label}"
190
- elif _is_slow_or_unreachable "$result"; then
191
- print_warning "SLOW ($(_format_probe_time "$result")) ${label}"
192
- else
193
- print_success "OK ($(_format_probe_time "$result")) ${label}"
194
- fi
195
- }
196
-
197
- # Probe a URL up to MAX_RETRIES times; returns the first successful ms or "timeout".
198
- _probe_url_with_retry() {
199
- local url="$1"
200
- local max_retries="${2:-2}"
201
- local attempt result
202
-
203
- for attempt in $(seq 1 "$max_retries"); do
204
- result=$(_probe_url "$url")
205
- if [ "$result" != "timeout" ] && ! _is_slow_or_unreachable "$result"; then
206
- echo "$result"
207
- return 0
208
- fi
209
- # keep last result for reporting
210
- done
211
- echo "$result"
212
- }
213
-
214
- detect_network_region() {
215
- print_step "Network pre-flight check..."
216
- echo ""
217
-
218
- # -----------------------------------------------------------------------
219
- # Step 1: Region detection — google.com vs baidu.com
220
- # google reachable → global
221
- # google unreachable + baidu reachable → china
222
- # both unreachable → unknown (best-effort, fall through)
223
- # -----------------------------------------------------------------------
224
- print_info "Step 1: Detecting network region..."
225
- local google_result baidu_result
226
- google_result=$(_probe_url "https://www.google.com")
227
- baidu_result=$(_probe_url "https://www.baidu.com")
228
-
229
- _print_probe_result "google.com" "$google_result"
230
- _print_probe_result "baidu.com" "$baidu_result"
231
-
232
- local google_ok=false baidu_ok=false
233
- ! _is_slow_or_unreachable "$google_result" && google_ok=true
234
- ! _is_slow_or_unreachable "$baidu_result" && baidu_ok=true
235
-
236
- if [ "$google_ok" = true ]; then
237
- NETWORK_REGION="global"
238
- print_success "Region: global (Google reachable)"
239
- elif [ "$baidu_ok" = true ]; then
240
- NETWORK_REGION="china"
241
- print_success "Region: china (Baidu reachable, Google not)"
242
- else
243
- NETWORK_REGION="unknown"
244
- print_warning "Region: unknown (both Google and Baidu unreachable)"
245
- fi
246
- echo ""
247
-
248
- # -----------------------------------------------------------------------
249
- # Step 2: Probe region-specific sources (retry 2× to avoid false alarms)
250
- # -----------------------------------------------------------------------
251
- print_info "Step 2: Probing installation sources (up to 2 retries each)..."
252
-
253
- if [ "$NETWORK_REGION" = "china" ]; then
254
- # CN sources: CDN (oss.1024code.com) + Aliyun + npmmirror
255
- local cdn_result mirror_result
256
- cdn_result=$(_probe_url_with_retry "$CN_MISE_INSTALL_URL")
257
- mirror_result=$(_probe_url_with_retry "$CN_RUBYGEMS_URL")
258
-
259
- _print_probe_result "Chinese CDN (mise/Ruby)" "$cdn_result"
260
- _print_probe_result "CN mirror (gem/brew)" "$mirror_result"
261
-
262
- local cdn_ok=false mirror_ok=false
263
- ! _is_slow_or_unreachable "$cdn_result" && cdn_ok=true
264
- ! _is_slow_or_unreachable "$mirror_result" && mirror_ok=true
265
-
266
- # Step 3: warn on source failures, but do not abort
267
- if [ "$cdn_ok" = false ]; then
268
- print_warning "Chinese CDN is slow/unreachable. mise and Ruby precompiled binaries may fail."
269
- fi
270
- if [ "$mirror_ok" = false ]; then
271
- print_warning "Aliyun mirror is slow/unreachable. gem/brew installs may be slow."
272
- fi
273
- if [ "$cdn_ok" = false ] && [ "$mirror_ok" = false ]; then
274
- print_warning "All CN mirrors unreachable — falling back to global sources. Expect slow downloads."
275
- fi
276
-
277
- # Step 4: apply CN sources
278
- if [ "$cdn_ok" = true ] || [ "$mirror_ok" = true ]; then
279
- USE_CN_MIRRORS=true
280
- MISE_INSTALL_URL="$CN_MISE_INSTALL_URL"
281
- RUBYGEMS_INSTALL_URL="$CN_RUBYGEMS_URL"
282
- NPM_REGISTRY_URL="$CN_NPM_REGISTRY"
283
- NODE_MIRROR_URL="$CN_NODE_MIRROR_URL"
284
- RUBY_VERSION_SPEC="ruby@3.4.8"
285
- print_info "CN mirrors applied: CDN (mise/Ruby) + Aliyun (gem/brew) + npmmirror (npm/Node)"
286
- else
287
- USE_CN_MIRRORS=false
288
- print_info "Falling back to global sources despite CN region detection."
289
- fi
290
-
291
- else
292
- # Global sources: GitHub / RubyGems / npmjs / mise.run
293
- local github_result rubygems_result npm_result mise_result
294
- github_result=$(_probe_url_with_retry "$GITHUB_RAW_BASE_URL")
295
- rubygems_result=$(_probe_url_with_retry "$DEFAULT_RUBYGEMS_URL")
296
- npm_result=$(_probe_url_with_retry "$DEFAULT_NPM_REGISTRY")
297
- mise_result=$(_probe_url_with_retry "$DEFAULT_MISE_INSTALL_URL")
298
-
299
- _print_probe_result "GitHub raw" "$github_result"
300
- _print_probe_result "RubyGems" "$rubygems_result"
301
- _print_probe_result "npmjs.com" "$npm_result"
302
- _print_probe_result "mise.run" "$mise_result"
303
-
304
- # Step 3: warn on individual source failures
305
- _is_slow_or_unreachable "$github_result" && print_warning "GitHub is slow/unreachable. Homebrew and install script updates may fail."
306
- _is_slow_or_unreachable "$rubygems_result" && print_warning "RubyGems is slow/unreachable. gem install may fail."
307
- _is_slow_or_unreachable "$npm_result" && print_warning "npmjs.com is slow/unreachable. npm install may fail."
308
- _is_slow_or_unreachable "$mise_result" && print_warning "mise.run is slow/unreachable. mise installation may fail."
309
-
310
- USE_CN_MIRRORS=false
311
- MISE_INSTALL_URL="$DEFAULT_MISE_INSTALL_URL"
312
- RUBYGEMS_INSTALL_URL="$DEFAULT_RUBYGEMS_URL"
313
- NPM_REGISTRY_URL="$DEFAULT_NPM_REGISTRY"
314
- NODE_MIRROR_URL=""
315
- RUBY_VERSION_SPEC="ruby@3"
316
- print_info "Using global upstream sources."
317
- fi
318
-
319
- echo ""
320
- }
321
-
322
-
323
- # Compare version strings
324
- version_ge() {
325
- # Returns 0 (true) if $1 >= $2
326
- printf '%s\n%s\n' "$2" "$1" | sort -V -C
327
- }
328
-
329
- # Check Ruby version
330
- check_ruby() {
331
- if command_exists ruby; then
332
- RUBY_VERSION=$(ruby -e 'puts RUBY_VERSION' 2>/dev/null)
333
- print_info "Found Ruby version: $RUBY_VERSION"
334
-
335
- if version_ge "$RUBY_VERSION" "3.1.0"; then
336
- print_success "Ruby version is compatible (>= 3.1.0)"
337
- return 0
338
- else
339
- print_warning "Ruby version $RUBY_VERSION is too old (need >= 3.1.0)"
340
- return 1
341
- fi
342
- else
343
- print_warning "Ruby is not installed"
344
- return 1
345
- fi
346
- }
347
-
348
- # Configure CN mirrors permanently in config files so all subsequent tool
349
- # invocations (gem, npm, mise) automatically use the right source.
350
- # Called once after detect_network_region, only when USE_CN_MIRRORS=true.
351
- restore_mirrors() {
352
- print_step "Restoring original mirror settings..."
353
-
354
- # --- gem: restore ~/.gemrc ---
355
- local gemrc="$HOME/.gemrc"
356
- local gemrc_bak="$HOME/.gemrc_clackybak"
357
- if [ -f "$gemrc_bak" ]; then
358
- mv "$gemrc_bak" "$gemrc"
359
- print_success "~/.gemrc restored from backup"
360
- elif [ -f "$gemrc" ]; then
361
- rm "$gemrc"
362
- print_success "~/.gemrc removed (gem will use default rubygems.org)"
363
- else
364
- print_info "~/.gemrc — nothing to restore"
365
- fi
366
-
367
- # --- npm: restore ~/.npmrc ---
368
- local npmrc="$HOME/.npmrc"
369
- local npmrc_bak="$HOME/.npmrc_clackybak"
370
- if [ -f "$npmrc_bak" ]; then
371
- mv "$npmrc_bak" "$npmrc"
372
- print_success "~/.npmrc restored from backup"
373
- elif [ -f "$npmrc" ]; then
374
- rm "$npmrc"
375
- print_success "~/.npmrc removed (npm will use default registry)"
376
- else
377
- print_info "~/.npmrc — nothing to restore"
378
- fi
379
-
380
- # --- mise: unset node.mirror_url ---
381
- local mise_bin=""
382
- if command_exists mise; then
383
- mise_bin="mise"
384
- elif [ -x "$HOME/.local/bin/mise" ]; then
385
- mise_bin="$HOME/.local/bin/mise"
386
- fi
387
- if [ -n "$mise_bin" ]; then
388
- "$mise_bin" settings unset node.mirror_url 2>/dev/null && \
389
- print_success "mise node.mirror_url unset"
390
- else
391
- print_info "mise not found — node.mirror_url skipped"
392
- fi
393
-
394
- # --- Homebrew CN mirrors: remove from shell rc file ---
395
- if [ -n "$SHELL_RC" ] && [ -f "$SHELL_RC" ] && grep -q "HOMEBREW_BOTTLE_DOMAIN" "$SHELL_RC" 2>/dev/null; then
396
- # Remove the comment line and the three export lines added by the installer
397
- sed -i.bak '/# Homebrew CN mirrors (added by openclacky installer)/d' "$SHELL_RC"
398
- sed -i.bak '/HOMEBREW_BREW_GIT_REMOTE/d' "$SHELL_RC"
399
- sed -i.bak '/HOMEBREW_CORE_GIT_REMOTE/d' "$SHELL_RC"
400
- sed -i.bak '/HOMEBREW_BOTTLE_DOMAIN/d' "$SHELL_RC"
401
- rm -f "${SHELL_RC}.bak"
402
- unset HOMEBREW_BREW_GIT_REMOTE HOMEBREW_CORE_GIT_REMOTE HOMEBREW_BOTTLE_DOMAIN
403
- print_success "Homebrew CN mirrors removed from $SHELL_RC"
404
- else
405
- print_info "Homebrew CN mirrors — nothing to restore"
406
- fi
407
-
408
- echo ""
409
- print_success "Done. All mirror settings restored to original."
410
- echo ""
411
- }
412
-
413
- configure_cn_mirrors() {
414
- [ "$USE_CN_MIRRORS" = true ] || return 0
415
-
416
- print_step "Configuring CN mirrors (permanent)..."
417
-
418
- # --- gem: write ~/.gemrc ---
419
- local gemrc="$HOME/.gemrc"
420
- if [ -f "$gemrc" ] && grep -q "${CN_RUBYGEMS_URL}" "$gemrc" 2>/dev/null; then
421
- # Already configured by us — leave it alone
422
- print_success "gem source already set → ${CN_RUBYGEMS_URL} (~/.gemrc)"
423
- else
424
- # Existing file with different content — back it up then generate a clean one
425
- if [ -f "$gemrc" ]; then
426
- mv "$gemrc" "$HOME/.gemrc_clackybak"
427
- print_info "Backed up existing ~/.gemrc → ~/.gemrc_clackybak"
428
- fi
429
- cat > "$gemrc" <<GEMRC
430
- :sources:
431
- - ${CN_RUBYGEMS_URL}
432
- GEMRC
433
- print_success "gem source → ${CN_RUBYGEMS_URL} (~/.gemrc)"
434
- fi
435
-
436
- # --- npm: write ~/.npmrc ---
437
- local npmrc="$HOME/.npmrc"
438
- local npmrc_bak="$HOME/.npmrc_clackybak"
439
- if [ -f "$npmrc" ] && grep -q "${NPM_REGISTRY_URL}" "$npmrc" 2>/dev/null; then
440
- # Already configured by us — leave it alone
441
- print_success "npm registry already set → ${NPM_REGISTRY_URL} (~/.npmrc)"
442
- else
443
- # Back up existing file (once), then write clean config
444
- if [ -f "$npmrc" ] && [ ! -f "$npmrc_bak" ]; then
445
- cp "$npmrc" "$npmrc_bak"
446
- print_info "Backed up existing ~/.npmrc → ~/.npmrc_clackybak"
447
- fi
448
- if command_exists npm; then
449
- npm config set registry "$NPM_REGISTRY_URL" 2>/dev/null && \
450
- print_success "npm registry → ${NPM_REGISTRY_URL} (~/.npmrc)"
451
- else
452
- echo "registry=${NPM_REGISTRY_URL}" >> "$npmrc"
453
- print_success "npm registry → ${NPM_REGISTRY_URL} (~/.npmrc, pre-set)"
454
- fi
455
- fi
456
-
457
- # --- mise Node mirror ---
458
- # Applied inside install_mise_runtime() after mise binary is available.
459
- # NODE_MIRROR_URL is already set; nothing to do here.
460
-
461
- echo ""
462
- }
463
-
464
- # Install via RubyGems
465
- install_via_gem() {
466
- print_step "Installing via RubyGems..."
467
-
468
- if ! command_exists gem; then
469
- print_error "RubyGems is not available"
470
- return 1
471
- fi
472
-
473
- # Enforce Ruby >= 3.1.0 before attempting gem install
474
- if ! command_exists ruby; then
475
- print_error "Ruby is not available"
476
- return 1
477
- fi
478
- RUBY_VERSION=$(ruby -e 'puts RUBY_VERSION' 2>/dev/null)
479
- if ! version_ge "$RUBY_VERSION" "3.1.0"; then
480
- print_error "Ruby $RUBY_VERSION is too old (>= 3.1.0 required)"
481
- return 1
482
- fi
483
-
484
- print_info "Installing ${DISPLAY_NAME}..."
485
- gem install openclacky --no-document
486
-
487
- if [ $? -eq 0 ]; then
488
- print_success "${DISPLAY_NAME} installed successfully!"
489
- install_chrome_devtools_mcp
490
- return 0
491
- else
492
- print_error "Gem installation failed"
493
- return 1
494
- fi
495
- }
496
-
497
- # Install mise for Ruby version management
498
- install_mise_runtime() {
499
- print_info "Installing mise..."
500
- if ! command_exists mise; then
501
- if curl -fsSL "$MISE_INSTALL_URL" | sh; then
502
- # Add mise activation to the current shell's rc file
503
- local mise_init_line='eval "$(~/.local/bin/mise activate '"$CURRENT_SHELL"')"'
504
- if [ -f "$SHELL_RC" ]; then
505
- echo "$mise_init_line" >> "$SHELL_RC"
506
- else
507
- echo "$mise_init_line" > "$SHELL_RC"
508
- fi
509
- print_info "Added mise activation to $SHELL_RC"
510
-
511
- export PATH="$HOME/.local/bin:$PATH"
512
- # Always activate using bash syntax here (this script runs under bash)
513
- eval "$(~/.local/bin/mise activate bash)"
514
-
515
- print_success "mise installed successfully"
516
- else
517
- print_error "Failed to install mise"
518
- return 1
519
- fi
520
- else
521
- print_success "mise already installed"
522
- fi
523
-
524
- # Apply CN Node mirror permanently — covers any future `mise install node` too.
525
- if [ "$USE_CN_MIRRORS" = true ] && [ -n "$NODE_MIRROR_URL" ]; then
526
- ~/.local/bin/mise settings node.mirror_url="$NODE_MIRROR_URL" 2>/dev/null || true
527
- print_info "mise Node mirror → ${NODE_MIRROR_URL}"
528
- fi
529
- }
530
-
531
- install_ruby_via_mise() {
532
- if check_ruby; then
533
- print_success "Ruby already installed and compatible — skipping Ruby installation"
534
- return 0
535
- fi
536
-
537
- print_info "Installing Ruby 3 via mise..."
538
-
539
- if [ "$USE_CN_MIRRORS" = true ]; then
540
- ~/.local/bin/mise settings ruby.compile=false
541
- ~/.local/bin/mise settings ruby.precompiled_url="$CN_RUBY_PRECOMPILED_URL"
542
- else
543
- ~/.local/bin/mise settings unset ruby.compile >/dev/null 2>&1 || true
544
- ~/.local/bin/mise settings unset ruby.precompiled_url >/dev/null 2>&1 || true
545
- fi
546
-
547
- if ~/.local/bin/mise use -g "$RUBY_VERSION_SPEC"; then
548
- eval "$(~/.local/bin/mise activate bash)"
549
- print_success "Ruby 3 installed successfully"
550
- else
551
- print_error "Failed to install Ruby 3"
552
- return 1
553
- fi
554
- }
555
-
556
- # Install dependencies and Ruby on macOS
557
- install_macos_dependencies() {
558
- print_step "Installing macOS dependencies and Ruby..."
559
- echo ""
560
-
561
- # Configure Homebrew CN mirrors before installing (CN mode only)
562
- if [ "$USE_CN_MIRRORS" = true ]; then
563
- print_info "Configuring Homebrew CN mirrors..."
564
- export HOMEBREW_INSTALL_FROM_API=1
565
- export HOMEBREW_API_DOMAIN="$CN_HOMEBREW_API_DOMAIN"
566
- export HOMEBREW_BREW_GIT_REMOTE="$CN_HOMEBREW_BREW_GIT_REMOTE"
567
- export HOMEBREW_CORE_GIT_REMOTE="$CN_HOMEBREW_CORE_GIT_REMOTE"
568
- export HOMEBREW_BOTTLE_DOMAIN="$CN_HOMEBREW_BOTTLE_DOMAIN"
569
-
570
- # Persist to shell rc file
571
- if ! grep -q "HOMEBREW_BOTTLE_DOMAIN" "$SHELL_RC" 2>/dev/null; then
572
- {
573
- echo ""
574
- echo "# Homebrew CN mirrors (added by openclacky installer)"
575
- echo "export HOMEBREW_INSTALL_FROM_API=1"
576
- echo "export HOMEBREW_API_DOMAIN=\"${CN_HOMEBREW_API_DOMAIN}\""
577
- echo "export HOMEBREW_BREW_GIT_REMOTE=\"${CN_HOMEBREW_BREW_GIT_REMOTE}\""
578
- echo "export HOMEBREW_CORE_GIT_REMOTE=\"${CN_HOMEBREW_CORE_GIT_REMOTE}\""
579
- echo "export HOMEBREW_BOTTLE_DOMAIN=\"${CN_HOMEBREW_BOTTLE_DOMAIN}\""
580
- } >> "$SHELL_RC"
581
- print_success "Homebrew CN mirrors written to $SHELL_RC"
582
- else
583
- print_success "Homebrew CN mirrors already configured in $SHELL_RC"
584
- fi
585
- fi
586
-
587
- # Install Homebrew (it will automatically install Xcode Command Line Tools if needed)
588
- print_info "Checking Homebrew installation..."
589
- if ! command_exists brew; then
590
- print_info "Installing Homebrew..."
591
- local brew_install_url="$HOMEBREW_INSTALL_SCRIPT_URL"
592
- if [ "$USE_CN_MIRRORS" = true ]; then
593
- brew_install_url="$CN_HOMEBREW_INSTALL_SCRIPT_URL"
594
- fi
595
- /bin/bash -c "$(curl -fsSL "$brew_install_url")"
596
-
597
- # Add Homebrew to PATH
598
- echo 'export PATH="/opt/homebrew/bin:$PATH"' >> "$SHELL_RC"
599
- export PATH="/opt/homebrew/bin:$PATH"
600
-
601
- print_success "Homebrew installed successfully"
602
- else
603
- print_success "Homebrew already installed"
604
- fi
605
-
606
- # Install build dependencies
607
- print_info "Installing build dependencies..."
608
- if brew install openssl@3 libyaml gmp; then
609
- print_success "Build dependencies installed"
610
- else
611
- print_error "Failed to install build dependencies"
612
- return 1
613
- fi
614
-
615
- # Install mise for Ruby version management
616
- # Detect current shell before configuring mise
617
- detect_shell
618
-
619
- if ! install_mise_runtime; then
620
- return 1
621
- fi
622
-
623
- if ! install_ruby_via_mise; then
624
- return 1
625
- fi
626
-
627
- # Verify Ruby installation
628
- if check_ruby; then
629
- return 0
630
- else
631
- print_error "Ruby installation verification failed"
632
- return 1
633
- fi
634
- }
635
-
636
- # Install dependencies and Ruby on Ubuntu/Debian
637
- install_ubuntu_dependencies() {
638
- print_step "Installing Ubuntu dependencies and Ruby..."
639
- echo ""
640
-
641
- if [ "$USE_CN_MIRRORS" = true ]; then
642
- print_info "Configuring apt mirror (Aliyun)..."
643
- local codename="${VERSION_CODENAME:-jammy}"
644
- local mirror_base="https://mirrors.aliyun.com/ubuntu/"
645
- local common_components="main restricted universe multiverse"
646
- sudo tee /etc/apt/sources.list > /dev/null <<EOF
647
- deb ${mirror_base} ${codename} ${common_components}
648
- deb ${mirror_base} ${codename}-updates ${common_components}
649
- deb ${mirror_base} ${codename}-backports ${common_components}
650
- deb ${mirror_base} ${codename}-security ${common_components}
651
- EOF
652
- print_success "Mirror configured"
653
- else
654
- print_info "Using default apt sources"
655
- fi
656
-
657
- # Update package list
658
- print_info "Updating package list..."
659
- if sudo apt update; then
660
- print_success "Package list updated"
661
- else
662
- print_error "Failed to update package list"
663
- return 1
664
- fi
665
-
666
- # Install build dependencies
667
- print_info "Installing build dependencies..."
668
- if sudo apt install -y build-essential libssl-dev libyaml-dev zlib1g-dev libgmp-dev git; then
669
- print_success "Build dependencies installed"
670
- else
671
- print_error "Failed to install build dependencies"
672
- return 1
673
- fi
674
-
675
- # Detect current shell before configuring mise
676
- detect_shell
677
-
678
- # In WSL, Windows paths (e.g. /mnt/c/Windows/system32) are appended to PATH.
679
- # mise scans PATH directories for mise.toml and errors on untrusted files found there.
680
- # Auto-trust the Windows system32 directory to suppress those errors.
681
- export MISE_TRUSTED_CONFIG_PATHS="/mnt/c/Windows/system32"
682
-
683
- # Install mise for Ruby version management
684
- if ! install_mise_runtime; then
685
- return 1
686
- fi
687
-
688
- if ! install_ruby_via_mise; then
689
- return 1
690
- fi
691
-
692
- # Verify Ruby installation
693
- if check_ruby; then
694
- return 0
695
- else
696
- print_error "Ruby installation verification failed"
697
- return 1
698
- fi
699
- }
700
-
701
- # Suggest Ruby installation
702
- # Parse command-line arguments.
703
- #
704
- # Supported flags:
705
- # --brand-name=VALUE Human-readable brand name (e.g. "JohnAI")
706
- # --command=VALUE CLI command to install as a wrapper (e.g. "johncli")
707
- #
708
- # Usage from install command:
709
- # /bin/bash -c "$(curl -sSL .../install.sh)" -- --brand-name="JohnAI" --command="johncli"
710
- #
711
- # Note: bash -c passes positional args starting at $0, so real flags are in $@
712
- # when invoked with the `--` separator; they land in $1..$N of the script.
713
- parse_args() {
714
- for arg in "$0" "$@"; do
715
- case "$arg" in
716
- --brand-name=*)
717
- BRAND_NAME="${arg#--brand-name=}"
718
- ;;
719
- --command=*)
720
- BRAND_COMMAND="${arg#--command=}"
721
- ;;
722
- --restore-mirrors)
723
- RESTORE_MIRRORS=true
724
- ;;
725
- esac
726
- done
727
- # Global display name: brand name if provided, otherwise fall back to OpenClacky
728
- DISPLAY_NAME="${BRAND_NAME:-OpenClacky}"
729
- }
730
-
731
- # Write brand configuration and install the wrapper command.
732
- #
733
- # Creates ~/.clacky/brand.yml with brand metadata and, if a custom command
734
- # name was requested, installs a thin wrapper script at ~/.local/bin/<command>
735
- # that simply delegates to openclacky.
736
- setup_brand() {
737
- [ -z "$BRAND_NAME" ] && return 0
738
-
739
- local clacky_dir="$HOME/.clacky"
740
- local brand_file="$clacky_dir/brand.yml"
741
- mkdir -p "$clacky_dir"
742
-
743
- print_step "Configuring brand: $BRAND_NAME"
744
-
745
- # Write brand.yml — minimal entry; license_key is filled in later via
746
- # the CLI or WebUI activation flow.
747
- cat > "$brand_file" <<YAML
748
- product_name: "${BRAND_NAME}"
749
- package_name: "${BRAND_COMMAND}"
750
- YAML
751
-
752
- print_success "Brand configuration written to $brand_file"
753
-
754
- # Install wrapper command if a custom command name was provided.
755
- if [ -n "$BRAND_COMMAND" ]; then
756
- local bin_dir="$HOME/.local/bin"
757
- mkdir -p "$bin_dir"
758
-
759
- local wrapper="$bin_dir/$BRAND_COMMAND"
760
- cat > "$wrapper" <<WRAPPER
761
- #!/bin/sh
762
- exec openclacky "\$@"
763
- WRAPPER
764
- chmod +x "$wrapper"
765
- print_success "Wrapper command installed: $wrapper"
766
-
767
- # Remind user to add ~/.local/bin to PATH if needed.
768
- case ":$PATH:" in
769
- *":$bin_dir:"*) ;;
770
- *)
771
- print_warning "Add the following to your shell profile so '$BRAND_COMMAND' is available:"
772
- echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
773
- ;;
774
- esac
775
- fi
776
- }
777
-
778
- # Main installation logic
779
- main() {
780
- parse_args "$@"
781
-
782
- # --restore-mirrors: undo CN mirror config and exit
783
- if [ "${RESTORE_MIRRORS:-false}" = true ]; then
784
- restore_mirrors
785
- exit 0
786
- fi
787
-
788
- echo ""
789
- echo "${DISPLAY_NAME} Installation"
790
- echo ""
791
-
792
- detect_os
793
- detect_shell
794
- detect_network_region
795
- configure_cn_mirrors
796
-
797
- # Install dependencies and Ruby (always run, handles already-installed gracefully)
798
- if [ "$OS" = "macOS" ]; then
799
- if ! install_macos_dependencies; then
800
- print_error "Failed to install dependencies"
801
- exit 1
802
- fi
803
- elif [ "$OS" = "Linux" ]; then
804
- if [ "$DISTRO" = "ubuntu" ] || [ "$DISTRO" = "debian" ]; then
805
- if ! install_ubuntu_dependencies; then
806
- print_error "Failed to install dependencies"
807
- exit 1
808
- fi
809
- else
810
- print_error "Unsupported Linux distribution: $DISTRO"
811
- print_info "Please install Ruby manually and run: gem install openclacky"
812
- exit 1
813
- fi
814
- else
815
- print_error "Unsupported OS: $OS"
816
- print_info "Please install Ruby manually and run: gem install openclacky"
817
- exit 1
818
- fi
819
-
820
- # Install via gem
821
- if install_via_gem; then
822
- setup_brand
823
- show_post_install_info
824
- exit 0
825
- else
826
- print_error "Failed to install ${DISPLAY_NAME}"
827
- exit 1
828
- fi
829
- }
830
-
831
- # Install agent-browser (browser automation tool)
832
- # This step is optional — failures are silently skipped with a hint.
833
- install_chrome_devtools_mcp() {
834
- print_step "Installing chrome-devtools-mcp..."
835
-
836
- if ! command_exists npm; then
837
- local mise_bin=""
838
- if command_exists mise; then
839
- mise_bin="mise"
840
- elif [ -x "$HOME/.local/bin/mise" ]; then
841
- mise_bin="$HOME/.local/bin/mise"
842
- fi
843
-
844
- if [ -n "$mise_bin" ]; then
845
- print_info "Installing Node.js via mise..."
846
- "$mise_bin" install node@22 > /dev/null 2>&1 || true
847
- "$mise_bin" use -g node@22 > /dev/null 2>&1 || true
848
- eval "$("$mise_bin" activate bash 2>/dev/null)" 2>/dev/null || true
849
- fi
850
- else
851
- print_success "Node.js already installed — $(node --version 2>/dev/null)"
852
- fi
853
-
854
- if ! command_exists npm; then
855
- print_warning "Node.js/npm not found. Browser automation will not be available."
856
- print_info "To enable browser support, install Node.js and run: npm install -g chrome-devtools-mcp"
857
- return 0
858
- fi
859
-
860
- if npm install -g chrome-devtools-mcp > /dev/null 2>&1; then
861
- print_success "chrome-devtools-mcp installed"
862
- else
863
- print_warning "chrome-devtools-mcp installation failed. Browser automation may not work."
864
- print_info "To install manually: npm install -g chrome-devtools-mcp"
865
- fi
866
- }
867
-
868
- # Post-installation information
869
- show_post_install_info() {
870
- local cmd="${BRAND_COMMAND:-openclacky}"
871
-
872
- echo ""
873
- echo -e " ${GREEN}${DISPLAY_NAME} installed successfully!${NC}"
874
- echo ""
875
- echo " First, reload your shell environment:"
876
- echo ""
877
- echo -e " ${YELLOW}source ${SHELL_RC}${NC}"
878
- echo ""
879
- echo " Then pick how you want to start:"
880
- echo ""
881
- echo -e " ${GREEN}Web UI${NC} (recommended):"
882
- echo " $cmd server"
883
- echo " Open http://localhost:7070 in your browser"
884
- echo ""
885
- echo -e " ${GREEN}Terminal mode${NC}:"
886
- echo " $cmd"
887
- echo ""
888
- }
889
-
890
- # Run main installation
891
- main "$@"