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.
data/scripts/install.sh CHANGED
@@ -15,6 +15,7 @@ YELLOW='\033[1;33m'
15
15
  BLUE='\033[0;34m'
16
16
  NC='\033[0m' # No Color
17
17
 
18
+ # Print colored messages
18
19
  print_info() {
19
20
  echo -e "${BLUE}ℹ${NC} $1"
20
21
  }
@@ -46,6 +47,7 @@ detect_os() {
46
47
  esac
47
48
  print_info "Detected OS: $OS"
48
49
 
50
+ # Detect Linux distribution
49
51
  if [ "$OS" = "Linux" ]; then
50
52
  if [ -f /etc/os-release ]; then
51
53
  . /etc/os-release
@@ -62,7 +64,8 @@ command_exists() {
62
64
  command -v "$1" >/dev/null 2>&1
63
65
  }
64
66
 
65
- # Detect current shell type
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)
66
69
  detect_shell() {
67
70
  local shell_name
68
71
  shell_name=$(basename "$SHELL")
@@ -74,6 +77,7 @@ detect_shell() {
74
77
  ;;
75
78
  bash)
76
79
  CURRENT_SHELL="bash"
80
+ # macOS uses ~/.bash_profile; Linux uses ~/.bashrc
77
81
  if [ "$OS" = "macOS" ]; then
78
82
  SHELL_RC="$HOME/.bash_profile"
79
83
  else
@@ -85,6 +89,7 @@ detect_shell() {
85
89
  SHELL_RC="$HOME/.config/fish/config.fish"
86
90
  ;;
87
91
  *)
92
+ # Fallback: treat as bash
88
93
  CURRENT_SHELL="bash"
89
94
  SHELL_RC="$HOME/.bashrc"
90
95
  ;;
@@ -93,27 +98,43 @@ detect_shell() {
93
98
  print_info "Detected shell: $CURRENT_SHELL (rc file: $SHELL_RC)"
94
99
  }
95
100
 
96
- # --------------------------------------------------------------------------
97
- # Network region detection & mirror selection
98
- # --------------------------------------------------------------------------
101
+ # Network-aware installer source selection
99
102
  SLOW_THRESHOLD_MS=5000
100
- NETWORK_REGION="global"
103
+ NETWORK_REGION="global" # "china" | "global" | "unknown"
101
104
  USE_CN_MIRRORS=false
102
105
 
106
+ # --- Upstream (global) defaults ---
103
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"
104
110
  DEFAULT_RUBYGEMS_URL="https://rubygems.org"
111
+ DEFAULT_NPM_REGISTRY="https://registry.npmjs.org"
105
112
  DEFAULT_MISE_INSTALL_URL="https://mise.run"
106
113
 
107
- CN_CDN_BASE_URL="https://oss.1024code.com"
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"
108
118
  CN_MISE_INSTALL_URL="${CN_CDN_BASE_URL}/mise.sh"
109
119
  CN_RUBY_PRECOMPILED_URL="${CN_CDN_BASE_URL}/ruby/ruby-{version}.{platform}.tar.gz"
110
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"
111
129
 
112
- # Active values (overridden by detect_network_region)
130
+ # --- Active values (overridden by detect_network_region) ---
113
131
  MISE_INSTALL_URL="$DEFAULT_MISE_INSTALL_URL"
114
132
  RUBYGEMS_INSTALL_URL="$DEFAULT_RUBYGEMS_URL"
115
- RUBY_VERSION_SPEC="ruby@3" # mise will pick latest stable
133
+ NPM_REGISTRY_URL="$DEFAULT_NPM_REGISTRY"
134
+ NODE_MIRROR_URL="" # empty = mise default (nodejs.org)
135
+ RUBY_VERSION_SPEC="ruby@3"
116
136
 
137
+ # Probe a single URL; echoes the round-trip time in milliseconds, or "timeout".
117
138
  _probe_url() {
118
139
  local url="$1"
119
140
  local timeout_sec=5
@@ -134,16 +155,25 @@ _probe_url() {
134
155
  fi
135
156
  }
136
157
 
158
+ _is_timed_result() {
159
+ local result="$1"
160
+
161
+ [ -n "$result" ] && [ "$result" != "timeout" ] && [ "$result" -ge 0 ] 2>/dev/null
162
+ }
163
+
137
164
  _is_slow_or_unreachable() {
138
165
  local result="$1"
166
+
139
167
  if [ "$result" = "timeout" ]; then
140
168
  return 0
141
169
  fi
170
+
142
171
  [ "$result" -ge "$SLOW_THRESHOLD_MS" ] 2>/dev/null
143
172
  }
144
173
 
145
174
  _format_probe_time() {
146
175
  local result="$1"
176
+
147
177
  if [ "$result" = "timeout" ]; then
148
178
  echo "timeout"
149
179
  else
@@ -156,7 +186,7 @@ _print_probe_result() {
156
186
  local result="$2"
157
187
 
158
188
  if [ "$result" = "timeout" ]; then
159
- print_warning "UNREACHABLE ${label}"
189
+ print_warning "UNREACHABLE (${result}) ${label}"
160
190
  elif _is_slow_or_unreachable "$result"; then
161
191
  print_warning "SLOW ($(_format_probe_time "$result")) ${label}"
162
192
  else
@@ -164,17 +194,19 @@ _print_probe_result() {
164
194
  fi
165
195
  }
166
196
 
197
+ # Probe a URL up to MAX_RETRIES times; returns the first successful ms or "timeout".
167
198
  _probe_url_with_retry() {
168
199
  local url="$1"
169
200
  local max_retries="${2:-2}"
170
- local result
201
+ local attempt result
171
202
 
172
- for _ in $(seq 1 "$max_retries"); do
203
+ for attempt in $(seq 1 "$max_retries"); do
173
204
  result=$(_probe_url "$url")
174
- if ! _is_slow_or_unreachable "$result"; then
205
+ if [ "$result" != "timeout" ] && ! _is_slow_or_unreachable "$result"; then
175
206
  echo "$result"
176
207
  return 0
177
208
  fi
209
+ # keep last result for reporting
178
210
  done
179
211
  echo "$result"
180
212
  }
@@ -183,7 +215,13 @@ detect_network_region() {
183
215
  print_step "Network pre-flight check..."
184
216
  echo ""
185
217
 
186
- print_info "Detecting network region..."
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..."
187
225
  local google_result baidu_result
188
226
  google_result=$(_probe_url "https://www.google.com")
189
227
  baidu_result=$(_probe_url "https://www.baidu.com")
@@ -197,107 +235,271 @@ detect_network_region() {
197
235
 
198
236
  if [ "$google_ok" = true ]; then
199
237
  NETWORK_REGION="global"
200
- print_success "Region: global"
238
+ print_success "Region: global (Google reachable)"
201
239
  elif [ "$baidu_ok" = true ]; then
202
240
  NETWORK_REGION="china"
203
- print_success "Region: china"
241
+ print_success "Region: china (Baidu reachable, Google not)"
204
242
  else
205
243
  NETWORK_REGION="unknown"
206
- print_warning "Region: unknown (both unreachable)"
244
+ print_warning "Region: unknown (both Google and Baidu unreachable)"
207
245
  fi
208
246
  echo ""
209
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
+
210
253
  if [ "$NETWORK_REGION" = "china" ]; then
254
+ # CN sources: CDN (oss.1024code.com) + Aliyun + npmmirror
211
255
  local cdn_result mirror_result
212
256
  cdn_result=$(_probe_url_with_retry "$CN_MISE_INSTALL_URL")
213
257
  mirror_result=$(_probe_url_with_retry "$CN_RUBYGEMS_URL")
214
258
 
215
- _print_probe_result "CN CDN (mise/Ruby)" "$cdn_result"
216
- _print_probe_result "Aliyun (gem)" "$mirror_result"
259
+ _print_probe_result "Chinese CDN (mise/Ruby)" "$cdn_result"
260
+ _print_probe_result "CN mirror (gem/brew)" "$mirror_result"
217
261
 
218
262
  local cdn_ok=false mirror_ok=false
219
263
  ! _is_slow_or_unreachable "$cdn_result" && cdn_ok=true
220
264
  ! _is_slow_or_unreachable "$mirror_result" && mirror_ok=true
221
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
222
278
  if [ "$cdn_ok" = true ] || [ "$mirror_ok" = true ]; then
223
279
  USE_CN_MIRRORS=true
224
280
  MISE_INSTALL_URL="$CN_MISE_INSTALL_URL"
225
281
  RUBYGEMS_INSTALL_URL="$CN_RUBYGEMS_URL"
282
+ NPM_REGISTRY_URL="$CN_NPM_REGISTRY"
283
+ NODE_MIRROR_URL="$CN_NODE_MIRROR_URL"
226
284
  RUBY_VERSION_SPEC="ruby@3.4.8"
227
- print_info "CN mirrors applied."
285
+ print_info "CN mirrors applied: CDN (mise/Ruby) + Aliyun (gem/brew) + npmmirror (npm/Node)"
228
286
  else
229
- print_warning "CN mirrors unreachable — falling back to global sources."
287
+ USE_CN_MIRRORS=false
288
+ print_info "Falling back to global sources despite CN region detection."
230
289
  fi
290
+
231
291
  else
232
- local rubygems_result mise_result
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")
233
295
  rubygems_result=$(_probe_url_with_retry "$DEFAULT_RUBYGEMS_URL")
296
+ npm_result=$(_probe_url_with_retry "$DEFAULT_NPM_REGISTRY")
234
297
  mise_result=$(_probe_url_with_retry "$DEFAULT_MISE_INSTALL_URL")
235
298
 
236
- _print_probe_result "RubyGems" "$rubygems_result"
237
- _print_probe_result "mise.run" "$mise_result"
238
-
239
- _is_slow_or_unreachable "$rubygems_result" && print_warning "RubyGems is slow/unreachable."
240
- _is_slow_or_unreachable "$mise_result" && print_warning "mise.run is slow/unreachable."
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."
241
317
  fi
242
318
 
243
319
  echo ""
244
320
  }
245
321
 
246
- # --------------------------------------------------------------------------
247
- # Version comparison
248
- # --------------------------------------------------------------------------
322
+
323
+ # Compare version strings
249
324
  version_ge() {
325
+ # Returns 0 (true) if $1 >= $2
250
326
  printf '%s\n%s\n' "$2" "$1" | sort -V -C
251
327
  }
252
328
 
253
- # --------------------------------------------------------------------------
254
- # Ruby check (>= 2.6.0)
255
- # --------------------------------------------------------------------------
329
+ # Check Ruby version
256
330
  check_ruby() {
257
331
  if command_exists ruby; then
258
332
  RUBY_VERSION=$(ruby -e 'puts RUBY_VERSION' 2>/dev/null)
259
- print_info "Found Ruby $RUBY_VERSION"
333
+ print_info "Found Ruby version: $RUBY_VERSION"
260
334
 
261
- if version_ge "$RUBY_VERSION" "2.6.0"; then
262
- print_success "Ruby $RUBY_VERSION is compatible (>= 2.6.0)"
335
+ if version_ge "$RUBY_VERSION" "3.1.0"; then
336
+ print_success "Ruby version is compatible (>= 3.1.0)"
263
337
  return 0
264
338
  else
265
- print_warning "Ruby $RUBY_VERSION is too old (need >= 2.6.0)"
339
+ print_warning "Ruby version $RUBY_VERSION is too old (need >= 3.1.0)"
266
340
  return 1
267
341
  fi
268
342
  else
269
- print_warning "Ruby not found"
343
+ print_warning "Ruby is not installed"
270
344
  return 1
271
345
  fi
272
346
  }
273
347
 
274
- # --------------------------------------------------------------------------
275
- # Configure gem source (CN mirror if needed)
276
- # --------------------------------------------------------------------------
277
- configure_gem_source() {
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() {
278
414
  [ "$USE_CN_MIRRORS" = true ] || return 0
279
415
 
416
+ print_step "Configuring CN mirrors (permanent)..."
417
+
418
+ # --- gem: write ~/.gemrc ---
280
419
  local gemrc="$HOME/.gemrc"
281
420
  if [ -f "$gemrc" ] && grep -q "${CN_RUBYGEMS_URL}" "$gemrc" 2>/dev/null; then
282
- print_success "gem source already ${CN_RUBYGEMS_URL}"
421
+ # Already configured by us — leave it alone
422
+ print_success "gem source already set → ${CN_RUBYGEMS_URL} (~/.gemrc)"
283
423
  else
284
- [ -f "$gemrc" ] && mv "$gemrc" "$HOME/.gemrc_clackybak"
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
285
429
  cat > "$gemrc" <<GEMRC
286
430
  :sources:
287
431
  - ${CN_RUBYGEMS_URL}
288
432
  GEMRC
289
- print_success "gem source → ${CN_RUBYGEMS_URL}"
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
290
494
  fi
291
495
  }
292
496
 
293
- # --------------------------------------------------------------------------
294
- # mise: install (if missing) and optionally activate
295
- # --------------------------------------------------------------------------
296
- install_mise() {
297
- if ! command_exists mise && [ ! -x "$HOME/.local/bin/mise" ]; then
298
- print_info "Installing mise..."
497
+ # Install mise for Ruby version management
498
+ install_mise_runtime() {
499
+ print_info "Installing mise..."
500
+ if ! command_exists mise; then
299
501
  if curl -fsSL "$MISE_INSTALL_URL" | sh; then
300
- # Add mise activation to shell rc
502
+ # Add mise activation to the current shell's rc file
301
503
  local mise_init_line='eval "$(~/.local/bin/mise activate '"$CURRENT_SHELL"')"'
302
504
  if [ -f "$SHELL_RC" ]; then
303
505
  echo "$mise_init_line" >> "$SHELL_RC"
@@ -305,59 +507,116 @@ install_mise() {
305
507
  echo "$mise_init_line" > "$SHELL_RC"
306
508
  fi
307
509
  print_info "Added mise activation to $SHELL_RC"
510
+
308
511
  export PATH="$HOME/.local/bin:$PATH"
512
+ # Always activate using bash syntax here (this script runs under bash)
309
513
  eval "$(~/.local/bin/mise activate bash)"
310
- print_success "mise installed"
514
+
515
+ print_success "mise installed successfully"
311
516
  else
312
517
  print_error "Failed to install mise"
313
518
  return 1
314
519
  fi
315
520
  else
316
- export PATH="$HOME/.local/bin:$PATH"
317
- eval "$(~/.local/bin/mise activate bash)" 2>/dev/null || true
318
521
  print_success "mise already installed"
319
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
320
529
  }
321
530
 
322
- # --------------------------------------------------------------------------
323
- # Install Ruby via mise (precompiled when CN mirrors active)
324
- # --------------------------------------------------------------------------
325
531
  install_ruby_via_mise() {
326
- print_info "Installing Ruby via mise ($RUBY_VERSION_SPEC)..."
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..."
327
538
 
328
539
  if [ "$USE_CN_MIRRORS" = true ]; then
329
540
  ~/.local/bin/mise settings ruby.compile=false
330
541
  ~/.local/bin/mise settings ruby.precompiled_url="$CN_RUBY_PRECOMPILED_URL"
331
- print_info "Using precompiled Ruby from CN CDN"
332
542
  else
333
- ~/.local/bin/mise settings unset ruby.compile >/dev/null 2>&1 || true
543
+ ~/.local/bin/mise settings unset ruby.compile >/dev/null 2>&1 || true
334
544
  ~/.local/bin/mise settings unset ruby.precompiled_url >/dev/null 2>&1 || true
335
545
  fi
336
546
 
337
547
  if ~/.local/bin/mise use -g "$RUBY_VERSION_SPEC"; then
338
548
  eval "$(~/.local/bin/mise activate bash)"
339
- print_success "Ruby installed via mise"
549
+ print_success "Ruby 3 installed successfully"
340
550
  else
341
- print_error "Failed to install Ruby via mise"
551
+ print_error "Failed to install Ruby 3"
342
552
  return 1
343
553
  fi
344
554
  }
345
555
 
346
- # --------------------------------------------------------------------------
347
- # Ensure Ruby >= 2.6 is available (install if needed)
348
- # --------------------------------------------------------------------------
349
- ensure_ruby() {
350
- print_step "Checking Ruby..."
556
+ # Install dependencies and Ruby on macOS
557
+ install_macos_dependencies() {
558
+ print_step "Installing macOS dependencies and Ruby..."
559
+ echo ""
351
560
 
352
- if check_ruby; then
353
- return 0
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
354
585
  fi
355
586
 
356
- # No suitable Ruby install via mise
357
- print_step "Installing Ruby via mise..."
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
358
617
  detect_shell
359
618
 
360
- if ! install_mise; then
619
+ if ! install_mise_runtime; then
361
620
  return 1
362
621
  fi
363
622
 
@@ -365,7 +624,7 @@ ensure_ruby() {
365
624
  return 1
366
625
  fi
367
626
 
368
- # Verify
627
+ # Verify Ruby installation
369
628
  if check_ruby; then
370
629
  return 0
371
630
  else
@@ -374,91 +633,83 @@ ensure_ruby() {
374
633
  fi
375
634
  }
376
635
 
377
- # --------------------------------------------------------------------------
378
- # Linux: install build deps before mise/Ruby (compile fallback)
379
- # --------------------------------------------------------------------------
380
- install_linux_build_deps() {
381
- if [ "$DISTRO" = "ubuntu" ] || [ "$DISTRO" = "debian" ]; then
382
- print_step "Installing build dependencies..."
636
+ # Install dependencies and Ruby on Ubuntu/Debian
637
+ install_ubuntu_dependencies() {
638
+ print_step "Installing Ubuntu dependencies and Ruby..."
639
+ echo ""
383
640
 
384
- if [ "$USE_CN_MIRRORS" = true ]; then
385
- print_info "Configuring apt mirror (Aliyun)..."
386
- local codename="${VERSION_CODENAME:-jammy}"
387
- local mirror_base="https://mirrors.aliyun.com/ubuntu/"
388
- local components="main restricted universe multiverse"
389
- sudo tee /etc/apt/sources.list > /dev/null <<EOF
390
- deb ${mirror_base} ${codename} ${components}
391
- deb ${mirror_base} ${codename}-updates ${components}
392
- deb ${mirror_base} ${codename}-backports ${components}
393
- deb ${mirror_base} ${codename}-security ${components}
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}
394
651
  EOF
395
- fi
396
-
397
- sudo apt update
398
- sudo apt install -y build-essential libssl-dev libyaml-dev zlib1g-dev libgmp-dev git
399
- print_success "Build dependencies installed"
652
+ print_success "Mirror configured"
653
+ else
654
+ print_info "Using default apt sources"
400
655
  fi
401
- }
402
656
 
403
- # --------------------------------------------------------------------------
404
- # Set GEM_HOME to user directory when system Ruby gem dir is not writable
405
- # (avoids needing sudo for gem install)
406
- # --------------------------------------------------------------------------
407
- setup_gem_home() {
408
- local gem_dir
409
- gem_dir=$(gem environment gemdir 2>/dev/null || true)
410
-
411
- # Gem dir is writable (e.g. mise-managed Ruby) — nothing to do
412
- if [ -w "$gem_dir" ]; then
413
- return 0
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
414
664
  fi
415
665
 
416
- # System Ruby: redirect gems to ~/.gem/ruby/<api_version>
417
- local ruby_api
418
- ruby_api=$(ruby -e 'puts RbConfig::CONFIG["ruby_version"]' 2>/dev/null)
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
419
674
 
420
- export GEM_HOME="$HOME/.gem/ruby/${ruby_api}"
421
- export GEM_PATH="$HOME/.gem/ruby/${ruby_api}"
422
- export PATH="$HOME/.gem/ruby/${ruby_api}/bin:$PATH"
675
+ # Detect current shell before configuring mise
676
+ detect_shell
423
677
 
424
- print_info "System Ruby detected gems will install to ~/.gem/ruby/${ruby_api}"
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"
425
682
 
426
- # Persist to shell rc (use $HOME so the line is portable)
427
- if [ -n "$SHELL_RC" ] && ! grep -q "GEM_HOME" "$SHELL_RC" 2>/dev/null; then
428
- {
429
- echo ""
430
- echo "# Ruby user gem dir (added by openclacky installer)"
431
- echo "export GEM_HOME=\"\$HOME/.gem/ruby/${ruby_api}\""
432
- echo "export GEM_PATH=\"\$HOME/.gem/ruby/${ruby_api}\""
433
- echo "export PATH=\"\$HOME/.gem/ruby/${ruby_api}/bin:\$PATH\""
434
- } >> "$SHELL_RC"
435
- print_info "GEM_HOME written to $SHELL_RC"
683
+ # Install mise for Ruby version management
684
+ if ! install_mise_runtime; then
685
+ return 1
436
686
  fi
437
- }
438
-
439
- # --------------------------------------------------------------------------
440
- # gem install openclacky
441
- # --------------------------------------------------------------------------
442
- install_via_gem() {
443
- print_step "Installing ${DISPLAY_NAME} via gem..."
444
-
445
- configure_gem_source
446
- setup_gem_home
447
687
 
448
- gem install openclacky --no-document
688
+ if ! install_ruby_via_mise; then
689
+ return 1
690
+ fi
449
691
 
450
- if [ $? -eq 0 ]; then
451
- print_success "${DISPLAY_NAME} installed successfully!"
692
+ # Verify Ruby installation
693
+ if check_ruby; then
452
694
  return 0
453
695
  else
454
- print_error "gem install failed"
696
+ print_error "Ruby installation verification failed"
455
697
  return 1
456
698
  fi
457
699
  }
458
700
 
459
- # --------------------------------------------------------------------------
460
- # Parse CLI args
461
- # --------------------------------------------------------------------------
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.
462
713
  parse_args() {
463
714
  for arg in "$0" "$@"; do
464
715
  case "$arg" in
@@ -468,14 +719,20 @@ parse_args() {
468
719
  --command=*)
469
720
  BRAND_COMMAND="${arg#--command=}"
470
721
  ;;
722
+ --restore-mirrors)
723
+ RESTORE_MIRRORS=true
724
+ ;;
471
725
  esac
472
726
  done
727
+ # Global display name: brand name if provided, otherwise fall back to OpenClacky
473
728
  DISPLAY_NAME="${BRAND_NAME:-OpenClacky}"
474
729
  }
475
730
 
476
- # --------------------------------------------------------------------------
477
- # Brand setup
478
- # --------------------------------------------------------------------------
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.
479
736
  setup_brand() {
480
737
  [ -z "$BRAND_NAME" ] && return 0
481
738
 
@@ -485,60 +742,49 @@ setup_brand() {
485
742
 
486
743
  print_step "Configuring brand: $BRAND_NAME"
487
744
 
745
+ # Write brand.yml — minimal entry; license_key is filled in later via
746
+ # the CLI or WebUI activation flow.
488
747
  cat > "$brand_file" <<YAML
489
748
  product_name: "${BRAND_NAME}"
490
749
  package_name: "${BRAND_COMMAND}"
491
750
  YAML
492
- print_success "Brand config written to $brand_file"
493
751
 
752
+ print_success "Brand configuration written to $brand_file"
753
+
754
+ # Install wrapper command if a custom command name was provided.
494
755
  if [ -n "$BRAND_COMMAND" ]; then
495
756
  local bin_dir="$HOME/.local/bin"
496
757
  mkdir -p "$bin_dir"
758
+
497
759
  local wrapper="$bin_dir/$BRAND_COMMAND"
498
760
  cat > "$wrapper" <<WRAPPER
499
761
  #!/bin/sh
500
762
  exec openclacky "\$@"
501
763
  WRAPPER
502
764
  chmod +x "$wrapper"
503
- print_success "Wrapper installed: $wrapper"
765
+ print_success "Wrapper command installed: $wrapper"
504
766
 
767
+ # Remind user to add ~/.local/bin to PATH if needed.
505
768
  case ":$PATH:" in
506
769
  *":$bin_dir:"*) ;;
507
770
  *)
508
- print_warning "Add to your shell profile: export PATH=\"\$HOME/.local/bin:\$PATH\""
771
+ print_warning "Add the following to your shell profile so '$BRAND_COMMAND' is available:"
772
+ echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
509
773
  ;;
510
774
  esac
511
775
  fi
512
776
  }
513
777
 
514
- # --------------------------------------------------------------------------
515
- # Post-install info
516
- # --------------------------------------------------------------------------
517
- show_post_install_info() {
518
- local cmd="${BRAND_COMMAND:-openclacky}"
519
-
520
- echo ""
521
- echo -e " ${GREEN}${DISPLAY_NAME} installed successfully!${NC}"
522
- echo ""
523
- echo " Reload your shell:"
524
- echo ""
525
- echo -e " ${YELLOW}source ${SHELL_RC}${NC}"
526
- echo ""
527
- echo -e " ${GREEN}Web UI${NC} (recommended):"
528
- echo " $cmd server"
529
- echo " Open http://localhost:7070"
530
- echo ""
531
- echo -e " ${GREEN}Terminal${NC}:"
532
- echo " $cmd"
533
- echo ""
534
- }
535
-
536
- # --------------------------------------------------------------------------
537
- # Main
538
- # --------------------------------------------------------------------------
778
+ # Main installation logic
539
779
  main() {
540
780
  parse_args "$@"
541
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
+
542
788
  echo ""
543
789
  echo "${DISPLAY_NAME} Installation"
544
790
  echo ""
@@ -546,29 +792,32 @@ main() {
546
792
  detect_os
547
793
  detect_shell
548
794
  detect_network_region
795
+ configure_cn_mirrors
549
796
 
550
- # Linux: install build deps first (needed if mise has to compile Ruby)
551
- if [ "$OS" = "Linux" ]; then
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
552
804
  if [ "$DISTRO" = "ubuntu" ] || [ "$DISTRO" = "debian" ]; then
553
- install_linux_build_deps
805
+ if ! install_ubuntu_dependencies; then
806
+ print_error "Failed to install dependencies"
807
+ exit 1
808
+ fi
554
809
  else
555
810
  print_error "Unsupported Linux distribution: $DISTRO"
556
- print_info "Please install Ruby >= 2.6.0 manually and run: gem install openclacky"
811
+ print_info "Please install Ruby manually and run: gem install openclacky"
557
812
  exit 1
558
813
  fi
559
- # WSL: auto-trust Windows system32 to suppress mise warnings
560
- export MISE_TRUSTED_CONFIG_PATHS="/mnt/c/Windows/system32"
561
- elif [ "$OS" != "macOS" ]; then
814
+ else
562
815
  print_error "Unsupported OS: $OS"
563
- print_info "Please install Ruby >= 2.6.0 manually and run: gem install openclacky"
564
- exit 1
565
- fi
566
-
567
- if ! ensure_ruby; then
568
- print_error "Could not install a compatible Ruby"
816
+ print_info "Please install Ruby manually and run: gem install openclacky"
569
817
  exit 1
570
818
  fi
571
819
 
820
+ # Install via gem
572
821
  if install_via_gem; then
573
822
  setup_brand
574
823
  show_post_install_info
@@ -579,4 +828,64 @@ main() {
579
828
  fi
580
829
  }
581
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
582
891
  main "$@"