forked from Alishahryar1/free-claude-code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
499 lines (425 loc) · 13.4 KB
/
Copy pathinstall.sh
File metadata and controls
499 lines (425 loc) · 13.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
#!/bin/sh
set -eu
REPO_ARCHIVE_URL="https://github.com/Alishahryar1/free-claude-code/archive/refs/heads/main.zip"
PYTHON_VERSION="3.14.0"
MIN_UV_VERSION="0.11.0"
CLAUDE_INSTALL_URL="https://claude.ai/install.sh"
CODEX_INSTALL_URL="https://chatgpt.com/codex/install.sh"
PI_INSTALL_URL="https://pi.dev/install.sh"
UV_INSTALL_URL="https://astral.sh/uv/install.sh"
dry_run=0
voice_nim=0
voice_local=0
voice_all=0
torch_backend=""
temporary_script=""
show_usage() {
cat <<'USAGE'
Usage: install.sh [options]
Installs Claude Code, Codex, and Pi if missing, ensures a compatible uv, and installs or updates Free Claude Code.
Options:
--voice-nim Install NVIDIA NIM voice transcription support.
--voice-local Install local Whisper voice transcription support.
--voice-all Install all voice transcription backends.
--torch-backend VALUE Use a uv PyTorch backend, such as cu130. Requires local voice.
--dry-run Print commands without running them.
--help Show this help text.
USAGE
}
fail() {
printf 'error: %s\n' "$*" >&2
exit 1
}
step() {
printf '\n==> %s\n' "$1"
}
quote_arg() {
case "$1" in
*[!A-Za-z0-9_./:@%+=,-]*|"")
escaped=$(printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g')
printf '"%s"' "$escaped"
;;
*)
printf '%s' "$1"
;;
esac
}
print_command() {
printf '+'
for arg in "$@"; do
printf ' '
quote_arg "$arg"
done
printf '\n'
}
run() {
print_command "$@"
if [ "$dry_run" -eq 1 ]; then
return 0
fi
if "$@"; then
return 0
else
status=$?
fi
fail "Command failed with exit code $status: $1"
}
cleanup() {
if [ -n "$temporary_script" ] && [ -e "$temporary_script" ]; then
rm -f "$temporary_script"
fi
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' HUP TERM
add_path_entry() {
[ -n "$1" ] || return 0
case ":$PATH:" in
*":$1:"*) ;;
*) PATH="$1:$PATH" ;;
esac
}
add_known_bin_directories() {
if [ -n "${XDG_BIN_HOME:-}" ]; then
add_path_entry "$XDG_BIN_HOME"
fi
if [ -n "${HOME:-}" ]; then
add_path_entry "$HOME/.local/bin"
add_path_entry "$HOME/.cargo/bin"
add_path_entry "${XDG_DATA_HOME:-$HOME/.local/share}/pi-node/current/bin"
fi
export PATH
hash -r 2>/dev/null || true
}
add_pi_bin_directories() {
[ "$dry_run" -eq 0 ] || return 0
add_known_bin_directories
if command -v npm >/dev/null 2>&1; then
pi_npm_prefix=$(npm prefix -g 2>/dev/null || npm config get prefix 2>/dev/null || true)
if [ -n "$pi_npm_prefix" ]; then
add_path_entry "$pi_npm_prefix/bin"
export PATH
hash -r 2>/dev/null || true
fi
fi
}
require_command() {
if [ "$dry_run" -eq 0 ] && ! command -v "$1" >/dev/null 2>&1; then
fail "$1 is required. Install it first, then rerun this installer."
fi
}
download_and_run() {
url=$1
interpreter=$2
label=$3
non_interactive=${4:-0}
if [ "$dry_run" -eq 1 ]; then
print_command curl -fsSL "$url" -o "<temporary-script>"
if [ "$non_interactive" -eq 1 ]; then
printf '+ CODEX_NON_INTERACTIVE=1 '
quote_arg "$interpreter"
printf ' <temporary-script>\n'
else
print_command "$interpreter" "<temporary-script>"
fi
return 0
fi
temporary_script=$(mktemp "${TMPDIR:-/tmp}/fcc-install.XXXXXX") || fail "Unable to create a temporary file for $label."
print_command curl -fsSL "$url" -o "$temporary_script"
if curl -fsSL "$url" -o "$temporary_script"; then
:
else
status=$?
fail "Could not download the $label installer (curl exit code $status)."
fi
if [ ! -s "$temporary_script" ]; then
fail "The downloaded $label installer was empty."
fi
if [ "$non_interactive" -eq 1 ]; then
printf '+ CODEX_NON_INTERACTIVE=1 '
quote_arg "$interpreter"
printf ' '
quote_arg "$temporary_script"
printf '\n'
if CODEX_NON_INTERACTIVE=1 "$interpreter" "$temporary_script"; then
:
else
status=$?
fail "$label installation failed with exit code $status."
fi
else
print_command "$interpreter" "$temporary_script"
if "$interpreter" "$temporary_script"; then
:
else
status=$?
fail "$label installation failed with exit code $status."
fi
fi
rm -f "$temporary_script"
temporary_script=""
}
verify_command() {
command_name=$1
display_name=$2
if [ "$dry_run" -eq 1 ]; then
print_command "$command_name" --version
return 0
fi
command_path=$(command -v "$command_name" 2>/dev/null) || fail "$display_name was installed, but '$command_name' is not available on PATH."
run "$command_path" --version
}
pi_command_is_compatible() {
pi_command_path=$(command -v pi 2>/dev/null) || return 1
pi_help=$("$pi_command_path" --help 2>/dev/null) || return 1
case "$pi_help" in
*--extension*) ;;
*) return 1 ;;
esac
case "$pi_help" in
*--models*) return 0 ;;
*) return 1 ;;
esac
}
verify_pi_command() {
if [ "$dry_run" -eq 1 ]; then
printf '+ pi --help (verify --extension and --models support)\n'
print_command pi --version
return 0
fi
pi_command_path=$(command -v pi 2>/dev/null) || fail "Pi was installed, but 'pi' is not available on PATH."
pi_command_is_compatible || fail "The 'pi' command at $pi_command_path is not a compatible Pi Coding Agent."
run "$pi_command_path" --version
}
ensure_claude() {
if command -v claude >/dev/null 2>&1; then
printf 'Claude Code already found on PATH; verifying it.\n'
else
download_and_run "$CLAUDE_INSTALL_URL" bash "Claude Code"
add_known_bin_directories
fi
verify_command claude "Claude Code"
}
ensure_codex() {
if command -v codex >/dev/null 2>&1; then
printf 'Codex already found on PATH; verifying it.\n'
else
download_and_run "$CODEX_INSTALL_URL" sh "Codex" 1
add_known_bin_directories
fi
verify_command codex "Codex"
}
ensure_pi() {
if [ "$dry_run" -eq 1 ] && command -v pi >/dev/null 2>&1; then
printf 'Pi already found on PATH; verifying it.\n'
elif pi_command_is_compatible; then
printf 'Pi already found on PATH; verifying it.\n'
else
if existing_pi_path=$(command -v pi 2>/dev/null); then
printf "The existing 'pi' command at %s is not Pi Coding Agent; installing Pi.\n" "$existing_pi_path"
fi
download_and_run "$PI_INSTALL_URL" sh "Pi"
add_pi_bin_directories
fi
verify_pi_command
}
current_uv_version() {
if output=$(uv --version); then
:
else
return 1
fi
case "$output" in
uv\ *) version=${output#uv } ;;
*) version=$output ;;
esac
version=${version%% *}
case "$version" in
[0-9]*.[0-9]*.[0-9]*) printf '%s\n' "$version" ;;
*) return 1 ;;
esac
}
version_ge() {
current=${1%%[-+]*}
minimum=${2%%[-+]*}
old_ifs=$IFS
IFS=.
set -- $current
current_major=${1:-0}
current_minor=${2:-0}
current_patch=${3:-0}
set -- $minimum
minimum_major=${1:-0}
minimum_minor=${2:-0}
minimum_patch=${3:-0}
IFS=$old_ifs
case "$current_major$current_minor$current_patch$minimum_major$minimum_minor$minimum_patch" in
*[!0-9]*) return 1 ;;
esac
[ "$current_major" -gt "$minimum_major" ] && return 0
[ "$current_major" -lt "$minimum_major" ] && return 1
[ "$current_minor" -gt "$minimum_minor" ] && return 0
[ "$current_minor" -lt "$minimum_minor" ] && return 1
[ "$current_patch" -ge "$minimum_patch" ]
}
verify_uv() {
if [ "$dry_run" -eq 1 ]; then
print_command uv --version
return 0
fi
command -v uv >/dev/null 2>&1 || fail "uv was installed, but it is not available on PATH."
version=$(current_uv_version) || fail "uv is present, but 'uv --version' did not return a valid version."
if ! version_ge "$version" "$MIN_UV_VERSION"; then
fail "uv $MIN_UV_VERSION or newer is required; found uv $version after installation."
fi
printf 'Verified uv %s.\n' "$version"
}
ensure_uv() {
if [ "$dry_run" -eq 1 ]; then
if command -v uv >/dev/null 2>&1; then
print_command uv --version
printf 'A compatible existing uv will be left unchanged; an obsolete one will be replaced by the standalone installer.\n'
else
printf 'uv is not installed; the current standalone uv would be installed.\n'
download_and_run "$UV_INSTALL_URL" sh "uv"
verify_uv
fi
return 0
fi
if command -v uv >/dev/null 2>&1; then
version=$(current_uv_version) || fail "uv is present, but 'uv --version' did not return a valid version."
if version_ge "$version" "$MIN_UV_VERSION"; then
printf 'uv %s already satisfies >=%s; leaving it unchanged.\n' "$version" "$MIN_UV_VERSION"
return 0
fi
printf 'uv %s is below %s; installing the current standalone uv.\n' "$version" "$MIN_UV_VERSION"
else
printf 'uv is not installed; installing the current standalone uv.\n'
fi
download_and_run "$UV_INSTALL_URL" sh "uv"
add_known_bin_directories
verify_uv
}
parse_args() {
while [ "$#" -gt 0 ]; do
case "$1" in
--voice-nim)
voice_nim=1
;;
--voice-local)
voice_local=1
;;
--voice-all)
voice_all=1
;;
--torch-backend)
shift
[ "$#" -gt 0 ] || fail "--torch-backend requires a value."
torch_backend=$1
[ -n "$torch_backend" ] || fail "--torch-backend requires a non-empty value."
;;
--torch-backend=*)
torch_backend=${1#*=}
[ -n "$torch_backend" ] || fail "--torch-backend requires a non-empty value."
;;
--dry-run)
dry_run=1
;;
--help|-h)
show_usage
exit 0
;;
*)
show_usage >&2
fail "unknown option: $1"
;;
esac
shift
done
}
validate_args() {
include_local=$voice_local
if [ "$voice_all" -eq 1 ]; then
include_local=1
fi
if [ -n "$torch_backend" ] && [ "$include_local" -ne 1 ]; then
fail "--torch-backend requires --voice-local or --voice-all."
fi
}
package_spec() {
include_nim=$voice_nim
include_local=$voice_local
if [ "$voice_all" -eq 1 ]; then
include_nim=1
include_local=1
fi
if [ "$include_nim" -eq 1 ] && [ "$include_local" -eq 1 ]; then
printf 'free-claude-code[voice,voice_local] @ %s' "$REPO_ARCHIVE_URL"
elif [ "$include_nim" -eq 1 ]; then
printf 'free-claude-code[voice] @ %s' "$REPO_ARCHIVE_URL"
elif [ "$include_local" -eq 1 ]; then
printf 'free-claude-code[voice_local] @ %s' "$REPO_ARCHIVE_URL"
else
printf 'free-claude-code @ %s' "$REPO_ARCHIVE_URL"
fi
}
install_free_claude_code() {
spec=$(package_spec)
if [ -n "$torch_backend" ]; then
run uv tool install --force --refresh-package free-claude-code --python "$PYTHON_VERSION" --torch-backend "$torch_backend" "$spec"
else
run uv tool install --force --refresh-package free-claude-code --python "$PYTHON_VERSION" "$spec"
fi
}
configure_and_verify_free_claude_code() {
run uv tool update-shell
if [ "$dry_run" -eq 1 ]; then
print_command uv tool dir --bin
printf '+ verify fcc-server, fcc-claude, fcc-codex, and fcc-pi in the uv tool bin directory\n'
print_command fcc-server --version
return 0
fi
print_command uv tool dir --bin
if tool_bin=$(uv tool dir --bin); then
:
else
status=$?
fail "Could not determine the uv tool bin directory (exit code $status)."
fi
[ -n "$tool_bin" ] || fail "uv returned an empty tool bin directory."
add_path_entry "$tool_bin"
export PATH
hash -r 2>/dev/null || true
for command_name in fcc-server fcc-claude fcc-codex fcc-pi; do
[ -x "$tool_bin/$command_name" ] || fail "Free Claude Code installation did not create $tool_bin/$command_name."
done
run "$tool_bin/fcc-server" --version
}
parse_args "$@"
validate_args
add_known_bin_directories
step "Checking installation prerequisites"
require_command curl
require_command bash
require_command sh
require_command mktemp
step "Ensuring Claude Code is installed"
ensure_claude
step "Ensuring Codex is installed"
ensure_codex
step "Ensuring Pi is installed"
ensure_pi
step "Ensuring uv $MIN_UV_VERSION or newer is installed"
ensure_uv
step "Installing or updating Free Claude Code"
install_free_claude_code
step "Configuring PATH and verifying Free Claude Code"
configure_and_verify_free_claude_code
if [ "$dry_run" -eq 1 ]; then
printf '\nDry run complete. No changes were made.\n'
else
printf '\nFree Claude Code is installed and verified. Start the proxy with: fcc-server\n'
printf 'Run Claude Code with: fcc-claude\n'
printf 'Run Codex with: fcc-codex\n'
printf 'Run Pi with: fcc-pi\n'
fi