Shell A script to install the latest version of claude code without using the linuxulator.

Hello.

Script ready and tested end-to-end (real download, real extraction, SHA256 hash identical to the manual install, idempotency verified) : ./claude-code-native-update.sh

How it works: it queries downloads.claude.ai/claude-code-releases/latest for the current version, downloads the official Linux binary, and extracts the cli.js module by searching for Bun's internal format markers (// @bun @bytecode @bun-cjs\n(function(exports,...)) instead of hand-guessed offsets — so it stays valid even if the bundle size changes in future releases. It verifies the extracted module actually works (--version) before installing it. If the requested version is already installed, nothing gets re-downloaded.

Code:
Usage:
# requires native bun already present :

cd /usr/ports/lang/bun && make install)
./claude-code-native-update.sh          # install/update to latest
./claude-code-native-update.sh 2.1.209  # install a specific version

It installs into /opt/claude-code-native/<version>/, with a current symlink always pointing at the latest, and creates/updates /usr/local/bin/claude. To actually keep it "always up to date," add it to a daily cron/periodic job — it's designed to be re-run as often as you like with no side effects when there's nothing to do.
 

Attachments

thats not a zip file

its a shell script with a .zip extension

so this wont work

Code:
unzip claude-code-native-update.sh.zip

you have to rename the file

Code:
mv claude-code-native-update.sh.zip claude-code-native-update.sh

worth mentioning that

the script is very difficult to read because of the lack of comments above the code
and its not divided into sections so it hard to scan the code

also having the text in Italian and not English
makes it even harder to see whats going on for the rest of us

your script

Code:
#!/bin/sh
#
# claude-code-native-update.sh — installa/aggiorna Claude Code all'ultima
# versione, nativo FreeBSD, senza Linuxulator e senza dipendere da npm
# (che dalla 2.1.2xx in poi non spedisce piu' il bundle JS).
#
# Come funziona: Anthropic compila il binario Linux ufficiale con
# `bun build --compile`, che incorpora il bundle JS dentro l'ELF secondo
# il formato interno di bun (modulo delimitato da marker
# "/$bunfs/root/<path>\0...\0// @bun @bytecode @bun-cjs\n(function(...)").
# Questo script scarica quel binario, estrae il modulo cli.js usando quei
# marker (niente offset indovinati a mano - sono gli stessi marker che usa
# bun internamente, quindi restano validi anche se la dimensione del
# bundle cambia da una release all'altra), e lo esegue con un `bun`
# nativo FreeBSD gia' presente sul sistema (build via /usr/ports/lang/bun).
#
# Uso:
#   ./claude-code-native-update.sh              # installa/aggiorna a latest
#   ./claude-code-native-update.sh 2.1.209       # installa una versione specifica
#
# Rieseguibile: se la versione installata combacia gia' con quella
# richiesta, non riscarica nulla. Pensato per essere richiamato anche da
# cron/periodic per restare sempre aggiornati automaticamente.

set -eu

PREFIX="${CLAUDE_NATIVE_PREFIX:-/opt/claude-code-native}"
BIN_LINK="${CLAUDE_NATIVE_BIN:-/usr/local/bin/claude}"
BUN_BIN="${CLAUDE_NATIVE_BUN:-$(command -v bun || true)}"
RELEASES_BASE="https://downloads.claude.ai/claude-code-releases"

log() { echo "==> $*"; }
err() { echo "errore: $*" >&2; exit 1; }

[ -n "$BUN_BIN" ] || err "bun non trovato in PATH. Installalo prima: cd /usr/ports/lang/bun && make install (o 'pkg install bun' se gia' in repo), oppure imposta CLAUDE_NATIVE_BUN=/percorso/bun"
[ -x "$BUN_BIN" ] || err "bun non eseguibile: $BUN_BIN"

case "$(uname -m)" in
    amd64|x86_64) PLATFORM="linux-x64" ;;
    aarch64|arm64) PLATFORM="linux-arm64" ;;
    *) err "architettura non supportata: $(uname -m)" ;;
esac

VERSION="${1:-}"
if [ -z "$VERSION" ]; then
    log "controllo ultima versione disponibile..."
    VERSION="$(fetch -q -o - "${RELEASES_BASE}/latest")" \
        || err "impossibile leggere ${RELEASES_BASE}/latest"
fi
[ -n "$VERSION" ] || err "versione vuota, qualcosa non ha funzionato nel fetch"
log "versione target: $VERSION"

VERDIR="${PREFIX}/${VERSION}"
CURRENT_VERSION=""
[ -f "${PREFIX}/current/VERSION" ] && CURRENT_VERSION="$(cat "${PREFIX}/current/VERSION")"

if [ "$CURRENT_VERSION" = "$VERSION" ] && [ -f "${PREFIX}/current/claude-entry.js" ]; then
    log "gia' alla versione $VERSION, niente da fare"
    exit 0
fi

WORK="$(mktemp -d "${TMPDIR:-/tmp}/claude-native.XXXXXX")"
trap 'rm -rf "$WORK"' EXIT

BINURL="${RELEASES_BASE}/${VERSION}/${PLATFORM}/claude"
log "scarico binario ufficiale: $BINURL"
fetch -o "${WORK}/claude-bin" "$BINURL" || err "download fallito"

log "estraggo il modulo cli.js dal bundle Bun (parsing dei marker /\$bunfs/root/...)"
python3 - "$WORK/claude-bin" "$WORK/claude-cli.js" <<'PYEOF'
import re, sys

src_path, out_path = sys.argv[1], sys.argv[2]
data = open(src_path, "rb").read()

# Ogni modulo compilato da bun e' preceduto da un'intestazione tipo:
#   /$bunfs/root/<path>\x00[/$bunfs/root/<path>\x00]// @bun @bytecode @bun-cjs\n(function(exports, require, module, __filename, __dirname) { ... }
# I byte esatti prima del path variano (non e' sempre uno \x00 secco), quindi
# ancoriamo sulla stringa fissa "// @bun @bytecode @bun-cjs\n(function(...)"
# e verifichiamo solo che "cli.js" compaia poco prima, senza pretendere una
# struttura di byte nulli identica per ogni modulo.
ANCHOR = b"// @bun @bytecode @bun-cjs\n(function(exports, require, module, __filename, __dirname) {"
positions = [mo.start() for mo in re.finditer(re.escape(ANCHOR), data)]
if not positions:
    sys.exit("nessun modulo bun trovato nel binario (marker @bun @bytecode assente) - formato cambiato?")

target = None
for p in positions:
    preceding = data[max(0, p - 300):p]
    if b"cli.js" in preceding:
        target = p
        break
if target is None:
    sys.exit("trovati moduli bun ma nessuno con 'cli.js' nel path poco prima - formato cambiato?")

start = target + len(b"// @bun @bytecode @bun-cjs\n")  # inizio di "(function(exports, ..."
if data[start:start + 10] != b"(function(":
    sys.exit("il contenuto dopo il marker non inizia con il wrapper CJS atteso")

# Fine modulo = intestazione del modulo successivo ("\x00/$bunfs/root/...",
# la stessa struttura vista prima dell'anchor corrente), oppure fine file
# se cli.js fosse l'ultimo modulo del bundle.
next_header = data.find(b"\x00/$bunfs/root/", start)
end = next_header if next_header != -1 else len(data)

module_bytes = data[start:end]
if not module_bytes.rstrip().endswith(b")"):
    sys.exit("il modulo estratto non termina come atteso (bundle troncato?)")

with open(out_path, "wb") as f:
    f.write(module_bytes)

print(f"estratti {len(module_bytes)} byte")
PYEOF

[ -s "${WORK}/claude-cli.js" ] || err "estrazione fallita, cli.js vuoto"

cat > "${WORK}/claude-entry.js" <<'EOF'
const fs = require('fs');
const path = require('path');
const code = fs.readFileSync(path.join(__dirname, 'claude-cli.js'), 'utf8');
const fn = eval(code);
fn(exports, require, module, __filename, __dirname);
EOF

log "verifico che l'estratto funzioni davvero (claude --version)"
GOTVER="$("$BUN_BIN" "${WORK}/claude-entry.js" --version 2>&1 | head -1)" \
    || err "il bundle estratto non parte con bun: $GOTVER"
case "$GOTVER" in
    ${VERSION}*) ;;
    *) err "versione inattesa dopo l'estrazione: '$GOTVER' (atteso $VERSION)" ;;
esac
log "OK: $GOTVER"

log "installo in ${VERDIR}"
if [ "$(id -u)" != 0 ] && [ ! -w "$(dirname "$PREFIX")" ]; then
    SUDO="sudo"
else
    SUDO=""
fi
$SUDO mkdir -p "$VERDIR"
$SUDO cp "${WORK}/claude-cli.js" "${WORK}/claude-entry.js" "${WORK}/claude-bin" "$VERDIR/"
$SUDO chmod +x "${VERDIR}/claude-bin"
echo "$VERSION" | $SUDO tee "${VERDIR}/VERSION" > /dev/null

$SUDO ln -sfn "$VERDIR" "${PREFIX}/current"

$SUDO tee "$BIN_LINK" > /dev/null <<EOF
#!/bin/sh
export USE_BUILTIN_RIPGREP=0
export DISABLE_AUTOUPDATER=1
exec "$BUN_BIN" "${PREFIX}/current/claude-entry.js" "\$@"
EOF
$SUDO chmod +x "$BIN_LINK"

log "fatto: $BIN_LINK -> Claude Code $VERSION (nativo FreeBSD, bun, no Linuxulator)"


compared to one of my scripts

this is how i format my scripts for readability

headers above each section follow by a single space and then the code
after the code 2 spaces before the next heading

much easier to read

Code:
#!/bin/sh


#===============================================================================
# mux-local - stream a local video with srt, video must be h264/aac
#===============================================================================


#===============================================================================
# script usage
#===============================================================================

usage () {
# if argument passed to function echo it
[ -z "${1}" ] || echo "! ${1}"
# display help
echo "\
$(basename "$0") -f input -s 00:00:00 -t 00:00:00 -i ip -p port"
exit 2
}


#===============================================================================
# error messages
#===============================================================================

INVALID_OPT_ERR='Invalid option:'
REQ_ARG_ERR='requires an argument'
WRONG_ARGS_ERR='wrong number of arguments passed to script'


#===============================================================================
# check number of arguments passed to script
#===============================================================================

[ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}"


#===============================================================================
# getopts check options passed to script
#===============================================================================

while getopts 'f:i:p:s:t:h' opt
do
  case ${opt} in
     f) input="${OPTARG}";;
     i) ip="${OPTARG}";;
     p) port="${OPTARG}";;
     s) start="${OPTARG}";;
     t) end="${OPTARG}";;
     h) usage;;
     \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;;
     :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;;
  esac
done
shift $((OPTIND-1))


#===============================================================================
# defaults
#===============================================================================

# default ip
ip_default='192.168.1.131'

# default port
port_default='6882'


#===============================================================================
# calculate duration using sexagesimal math
#===============================================================================

get_duration() {
    printf "%s %s\n" "$1" "$2" | awk '
        {
          start = $1
          end = $2
          if (start ~ /:/) {
            split(start, t, ":")
            sseconds = (t[1] * 3600) + (t[2] * 60) + t[3]
          }
          if (end ~ /:/) {
            split(end, t, ":")
            eseconds = (t[1] * 3600) + (t[2] * 60) + t[3]
          }
          duration = eseconds - sseconds
          printf("%s\n"), duration
        }' \
    | awk -F. '
        NF==1 { printf("%02d:%02d:%02d\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60) }\
        NF==2 { printf("%02d:%02d:%02d.%s\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60), ($2) }'
}


#===============================================================================
# one stream containing audio and video
#===============================================================================

one_stream () {
    ffmpeg \
    -hide_banner \
    -stats -v panic \
    -y \
    -re \
    -i "${input}" \
    -c:a copy -c:v copy \
    -tune zerolatency \
    -f mpegts "srt://${ip:=${ip_default}}:${port:=${port_default}}"
}


#===============================================================================
# one stream container audio and video - trim with stream copy
#===============================================================================

one_stream_trim () {
    if [ -n "${start}" ] && [ -n "${end}" ]; then
        duration=$(get_duration "${start}" "${end}")
        duration_arg="-t ${duration}"
    elif [ -n "${end}" ]; then
        duration_arg="-t ${end}"
    fi

    # shellcheck disable=SC2086
    ffmpeg \
    -hide_banner \
    -stats -v panic \
    -y \
    -ss "${start}" \
    -re \
    -i "${input}" \
    ${duration_arg} \
    -c:a copy -c:v copy \
    -tune zerolatency \
    -f mpegts "srt://${ip:=${ip_default}}:${port:=${port_default}}"
}


#===============================================================================
# case statement check if playing the whole video or trimming
#===============================================================================

# start, end, or both were provided
if [ -n "${start}" ] || [ -n "${end}" ]; then
    one_stream_trim
else
    one_stream
fi
 
Code:
#!/bin/sh
#
# claude-code-native-update.sh — installs/updates Claude Code to the latest
# version, running natively on FreeBSD, without Linuxulator and without
# depending on npm (which, starting from version 2.1.2xx, no longer ships
# the JavaScript bundle).
#
# How it works: Anthropic builds the official Linux binary using
# `bun build --compile`, which embeds the JavaScript bundle directly into
# the ELF executable using Bun's internal format (a module delimited by
# markers such as "/$bunfs/root/<path>\0...\0// @bun @bytecode @bun-cjs\n(function(...)").

# This script downloads that binary, extracts the cli.js module using
# those markers (without relying on manually guessed offsets—the same
# markers are used internally by Bun, so they remain valid even if the
# bundle size changes between releases), and executes it with a native
# FreeBSD `bun` already installed on the system (built from
# /usr/ports/lang/bun).

# Usage:

#   ./claude-code-native-update.sh              # installs/updates to the latest version
#   ./claude-code-native-update.sh 2.1.209      # installs a specific version
#

# Re-runnable: if the installed version already matches the requested
# version, nothing is downloaded again. Designed to be safely invoked
# from cron/periodic as well, so it can keep itself automatically updated.

set -eu

PREFIX="${CLAUDE_NATIVE_PREFIX:-/opt/claude-code-native}"
BIN_LINK="${CLAUDE_NATIVE_BIN:-/usr/local/bin/claude}"
BUN_BIN="${CLAUDE_NATIVE_BUN:-$(command -v bun || true)}"
RELEASES_BASE="https://downloads.claude.ai/claude-code-releases"

log() { echo "==> $*"; }
err() { echo "error: $*" >&2; exit 1; }

[ -n "$BUN_BIN" ] || err "bun not found in PATH. Install it first: cd /usr/ports/lang/bun && make install (or 'pkg install bun' if it is already in the repository), or set CLAUDE_NATIVE_BUN=/path/to/bun"
[ -x "$BUN_BIN" ] || err "bun is not executable: $BUN_BIN"

case "$(uname -m)" in
    amd64|x86_64) PLATFORM="linux-x64" ;;
    aarch64|arm64) PLATFORM="linux-arm64" ;;
    *) err "unsupported architecture: $(uname -m)" ;;
esac

VERSION="${1:-}"
if [ -z "$VERSION" ]; then
    log "checking latest available version..."
    VERSION="$(fetch -q -o - "${RELEASES_BASE}/latest")" \
        || err "unable to read ${RELEASES_BASE}/latest"
fi
[ -n "$VERSION" ] || err "empty version, something went wrong while fetching"
log "target version: $VERSION"

VERDIR="${PREFIX}/${VERSION}"
CURRENT_VERSION=""
[ -f "${PREFIX}/current/VERSION" ] && CURRENT_VERSION="$(cat "${PREFIX}/current/VERSION")"

if [ "$CURRENT_VERSION" = "$VERSION" ] && [ -f "${PREFIX}/current/claude-entry.js" ]; then
    log "already at version $VERSION, nothing to do"
    exit 0
fi

WORK="$(mktemp -d "${TMPDIR:-/tmp}/claude-native.XXXXXX")"
trap 'rm -rf "$WORK"' EXIT

BINURL="${RELEASES_BASE}/${VERSION}/${PLATFORM}/claude"
log "downloading official binary: $BINURL"
fetch -o "${WORK}/claude-bin" "$BINURL" || err "download failed"

log "extracting the cli.js module from the Bun bundle (parsing /\$bunfs/root/ markers)"
python3 - "$WORK/claude-bin" "$WORK/claude-cli.js" <<'PYEOF'
import re, sys

src_path, out_path = sys.argv[1], sys.argv[2]
data = open(src_path, "rb").read()

# Every module compiled by Bun is preceded by a header similar to:
#   /$bunfs/root/<path>\x00[/$bunfs/root/<path>\x00]// @bun @bytecode @bun-cjs\n(function(exports, require, module, __filename, __dirname) { ... }
# The exact bytes preceding the path may vary (it is not always a plain \x00), so
# we anchor on the fixed string "// @bun @bytecode @bun-cjs\n(function(...)"
# and simply verify that "cli.js" appears shortly before it, without
# assuming an identical null-byte layout for every module.
ANCHOR = b"// @bun @bytecode @bun-cjs\n(function(exports, require, module, __filename, __dirname) {"
positions = [mo.start() for mo in re.finditer(re.escape(ANCHOR), data)]
if not positions:
    sys.exit("no Bun modules found in the binary (@bun @bytecode marker missing) - format changed?")

target = None
for p in positions:
    preceding = data[max(0, p - 300):p]
    if b"cli.js" in preceding:
        target = p
        break
if target is None:
    sys.exit("Bun modules found, but none with 'cli.js' in the path shortly before it - format changed?")

start = target + len(b"// @bun @bytecode @bun-cjs\n")  # beginning of "(function(exports, ..."
if data[start:start + 10] != b"(function(":
    sys.exit("the content after the marker does not start with the expected CJS wrapper")

# End of module = header of the next module ("\x00/$bunfs/root/...",
# the same structure seen before the current anchor), or end of file
# if cli.js is the last module in the bundle.
next_header = data.find(b"\x00/$bunfs/root/", start)
end = next_header if next_header != -1 else len(data)

module_bytes = data[start:end]
if not module_bytes.rstrip().endswith(b")"):
    sys.exit("the extracted module does not end as expected (truncated bundle?)")

with open(out_path, "wb") as f:
    f.write(module_bytes)

print(f"extracted {len(module_bytes)} bytes")
PYEOF

[ -s "${WORK}/claude-cli.js" ] || err "extraction failed, cli.js is empty"

cat > "${WORK}/claude-entry.js" <<'EOF'
const fs = require('fs');
const path = require('path');
const code = fs.readFileSync(path.join(__dirname, 'claude-cli.js'), 'utf8');
const fn = eval(code);
fn(exports, require, module, __filename, __dirname);
EOF

log "verifying that the extracted bundle actually works (claude --version)"
GOTVER="$("$BUN_BIN" "${WORK}/claude-entry.js" --version 2>&1 | head -1)" \
    || err "the extracted bundle does not start with bun: $GOTVER"
case "$GOTVER" in
    ${VERSION}*) ;;
    *) err "unexpected version after extraction: '$GOTVER' (expected $VERSION)" ;;
esac
log "OK: $GOTVER"

log "installing into ${VERDIR}"
if [ "$(id -u)" != 0 ] && [ ! -w "$(dirname "$PREFIX")" ]; then
    SUDO="sudo"
else
    SUDO=""
fi
$SUDO mkdir -p "$VERDIR"
$SUDO cp "${WORK}/claude-cli.js" "${WORK}/claude-entry.js" "${WORK}/claude-bin" "$VERDIR/"
$SUDO chmod +x "${VERDIR}/claude-bin"
echo "$VERSION" | $SUDO tee "${VERDIR}/VERSION" > /dev/null

$SUDO ln -sfn "$VERDIR" "${PREFIX}/current"

$SUDO tee "$BIN_LINK" > /dev/null <<EOF
#!/bin/sh
export USE_BUILTIN_RIPGREP=0
export DISABLE_AUTOUPDATER=1
exec "$BUN_BIN" "${PREFIX}/current/claude-entry.js" "\$@"
EOF
$SUDO chmod +x "$BIN_LINK"

log "done: $BIN_LINK -> Claude Code $VERSION (native FreeBSD, bun, no Linuxulator)"
 
thats a bit better
but still has some Italian in it

still looks like recipe for spaghetti to me :)

ancoriamo sulla stringa fissa

sounds tasty

when you are writing the script you know whats going on
but when you come back to them at a later date its easy to forget what all the code does

i always comment my scripts as if im as if im explaining them to myself
and i dont know anything about the subject or how it works

it also helps other user understand the code and what it does

im also like using headings to break up the code into sections
it really helps when scrolling through the code

this is much easier to see when scrolling

Code:
#===============================================================================
# script usage
#===============================================================================

than this

Code:
# script usage

i find it forces you to be more organized
and makes it easier to break the script into sections and show the flow of the script more cleanly

when you are editing the code it also makes it easier working section by section

thats just how i like write my scripts

but adding comments in Italian for you favourite spaghetti recipes is also fine
people can cook them while waiting for the code to finish
 
ancoriamo sulla stringa fissa

Do you like "spaghetti al ragù di carne ? " try it. Our language is as beautiful as our cuisine. Italy is the " beautiful land where they say 'sì' («del bel paeseDove 'l sì suona,». (Dante Alighieri, Inferno, canto XXXIII)
 
example code with comments

much easier to follow the recipe

Code:
#!/bin/sh

#===============================================================================
# claude-code-native-update.sh — installs/updates Claude Code to the latest
# version, running natively on FreeBSD, without Linuxulator and without
# depending on npm (which, starting from version 2.1.2xx, no longer ships
# the JavaScript bundle).
#===============================================================================


#===============================================================================
# How it works: Anthropic builds the official Linux binary using
# `bun build --compile`, which embeds the JavaScript bundle directly into
# the ELF executable using Bun's internal format (a module delimited by
# markers such as "/$bunfs/root/<path>\0...\0// @bun @bytecode @bun-cjs\n(function(...)").
#
# This script downloads that binary, extracts the cli.js module using
# those markers (without relying on manually guessed offsets—the same
# markers are used internally by Bun, so they remain valid even if the
# bundle size changes between releases), and executes it with a native
# FreeBSD `bun` already installed on the system (built from
# /usr/ports/lang/bun).
#===============================================================================


#===============================================================================
# Usage:
#   ./claude-code-native-update.sh              # installs/updates to the latest version
#   ./claude-code-native-update.sh 2.1.209      # installs a specific version
#
# Re-runnable: if the installed version already matches the requested
# version, nothing is downloaded again. Designed to be safely invoked
# from cron/periodic as well, so it can keep itself automatically updated.
#===============================================================================


#===============================================================================
# bring a pan of water to the boil
#===============================================================================

set -eu

PREFIX="${CLAUDE_NATIVE_PREFIX:-/opt/claude-code-native}"
BIN_LINK="${CLAUDE_NATIVE_BIN:-/usr/local/bin/claude}"
BUN_BIN="${CLAUDE_NATIVE_BUN:-$(command -v bun || true)}"
RELEASES_BASE="https://downloads.claude.ai/claude-code-releases"

log() { echo "==> $*"; }
err() { echo "errore: $*" >&2; exit 1; }

[ -n "$BUN_BIN" ] || err "bun non trovato in PATH. Installalo prima: cd /usr/ports/lang/bun && make install (o 'pkg install bun' se gia' in repo), oppure imposta CLAUDE_NATIVE_BUN=/percorso/bun"
[ -x "$BUN_BIN" ] || err "bun non eseguibile: $BUN_BIN"


#===============================================================================
# add the spaghetti and cook till soft and mushy
#===============================================================================

case "$(uname -m)" in
    amd64|x86_64) PLATFORM="linux-x64" ;;
    aarch64|arm64) PLATFORM="linux-arm64" ;;
    *) err "architettura non supportata: $(uname -m)" ;;
esac

VERSION="${1:-}"
if [ -z "$VERSION" ]; then
    log "controllo ultima versione disponibile..."
    VERSION="$(fetch -q -o - "${RELEASES_BASE}/latest")" \
        || err "impossibile leggere ${RELEASES_BASE}/latest"
fi
[ -n "$VERSION" ] || err "versione vuota, qualcosa non ha funzionato nel fetch"
log "versione target: $VERSION"


#===============================================================================
# heat up some olive oil in a pan or use some castrol gtk instead
#===============================================================================

VERDIR="${PREFIX}/${VERSION}"
CURRENT_VERSION=""
[ -f "${PREFIX}/current/VERSION" ] && CURRENT_VERSION="$(cat "${PREFIX}/current/VERSION")"

if [ "$CURRENT_VERSION" = "$VERSION" ] && [ -f "${PREFIX}/current/claude-entry.js" ]; then
    log "gia' alla versione $VERSION, niente da fare"
    exit 0
fi


#===============================================================================
# fry some onions, garlic and bacon
#===============================================================================

WORK="$(mktemp -d "${TMPDIR:-/tmp}/claude-native.XXXXXX")"
trap 'rm -rf "$WORK"' EXIT

BINURL="${RELEASES_BASE}/${VERSION}/${PLATFORM}/claude"
log "scarico binario ufficiale: $BINURL"
fetch -o "${WORK}/claude-bin" "$BINURL" || err "download fallito"


#===============================================================================
# add the dolmio sauce to the pan
#===============================================================================

log "estraggo il modulo cli.js dal bundle Bun (parsing dei marker /\$bunfs/root/...)"
python3 - "$WORK/claude-bin" "$WORK/claude-cli.js" <<'PYEOF'
import re, sys

src_path, out_path = sys.argv[1], sys.argv[2]
data = open(src_path, "rb").read()


#===============================================================================
# drain the pasta and pour on the dolmio sauce
#===============================================================================

# Every module compiled by Bun is preceded by a header similar to:
#   /$bunfs/root/<path>\x00[/$bunfs/root/<path>\x00]// @bun @bytecode @bun-cjs\n(function(exports, require, module, __filename, __dirname) { ... }
# The exact bytes preceding the path may vary (it is not always a plain # \x00), so
# we anchor on the fixed string# "// @bun @bytecode @bun-cjs\n(function(...)"
# and simply verify that "cli.js" appears shortly before it, without
# assuming an identical null-byte layout for every module.
ANCHOR = b"// @bun @bytecode @bun-cjs\n(function(exports, require, module, __filename, __dirname) {"
positions = [mo.start() for mo in re.finditer(re.escape(ANCHOR), data)]
if not positions:
    sys.exit("nessun modulo bun trovato nel binario (marker @bun @bytecode assente) - formato cambiato?")

target = None
for p in positions:
    preceding = data[max(0, p - 300):p]
    if b"cli.js" in preceding:
        target = p
        break
if target is None:
    sys.exit("trovati moduli bun ma nessuno con 'cli.js' nel path poco prima - formato cambiato?")

start = target + len(b"// @bun @bytecode @bun-cjs\n")  # inizio di "(function(exports, ..."
if data[start:start + 10] != b"(function(":
    sys.exit("il contenuto dopo il marker non inizia con il wrapper CJS atteso")

# Fine modulo = intestazione del modulo successivo ("\x00/$bunfs/root/...",
# la stessa struttura vista prima dell'anchor corrente), oppure fine file
# se cli.js fosse l'ultimo modulo del bundle.
next_header = data.find(b"\x00/$bunfs/root/", start)
end = next_header if next_header != -1 else len(data)

module_bytes = data[start:end]
if not module_bytes.rstrip().endswith(b")"):
    sys.exit("il modulo estratto non termina come atteso (bundle troncato?)")

with open(out_path, "wb") as f:
    f.write(module_bytes)

print(f"estratti {len(module_bytes)} byte")
PYEOF


#===============================================================================
# grate some cheddar cheese over the pasta
#===============================================================================

[ -s "${WORK}/claude-cli.js" ] || err "estrazione fallita, cli.js vuoto"

cat > "${WORK}/claude-entry.js" <<'EOF'
const fs = require('fs');
const path = require('path');
const code = fs.readFileSync(path.join(__dirname, 'claude-cli.js'), 'utf8');
const fn = eval(code);
fn(exports, require, module, __filename, __dirname);
EOF


#===============================================================================
# serve with bread and marmite
#===============================================================================

log "verifico che l'estratto funzioni davvero (claude --version)"
GOTVER="$("$BUN_BIN" "${WORK}/claude-entry.js" --version 2>&1 | head -1)" \
    || err "il bundle estratto non parte con bun: $GOTVER"
case "$GOTVER" in
    ${VERSION}*) ;;
    *) err "versione inattesa dopo l'estrazione: '$GOTVER' (atteso $VERSION)" ;;
esac
log "OK: $GOTVER"

log "installo in ${VERDIR}"
if [ "$(id -u)" != 0 ] && [ ! -w "$(dirname "$PREFIX")" ]; then
    SUDO="sudo"
else
    SUDO=""
fi
$SUDO mkdir -p "$VERDIR"
$SUDO cp "${WORK}/claude-cli.js" "${WORK}/claude-entry.js" "${WORK}/claude-bin" "$VERDIR/"
$SUDO chmod +x "${VERDIR}/claude-bin"
echo "$VERSION" | $SUDO tee "${VERDIR}/VERSION" > /dev/null


#===============================================================================
# take the walls cornetto icecreame out of the freezer for desert
#===============================================================================

$SUDO ln -sfn "$VERDIR" "${PREFIX}/current"

$SUDO tee "$BIN_LINK" > /dev/null <<EOF
#!/bin/sh
export USE_BUILTIN_RIPGREP=0
export DISABLE_AUTOUPDATER=1
exec "$BUN_BIN" "${PREFIX}/current/claude-entry.js" "\$@"
EOF
$SUDO chmod +x "$BIN_LINK"

log "fatto: $BIN_LINK -> Claude Code $VERSION (nativo FreeBSD, bun, no Linuxulator)"
 
Do you like "spaghetti al ragù di carne ? " try it. Our language is as beautiful as our cuisine. Italy is the beautiful country where music is played («del bel paeseDove 'l sì suona,». (Dante Alighieri, Inferno, canto XXXIII)

Yes we have proper spaghetti in the UK
just like in Italy

 
Yes we have proper spaghetti in the UK
just like in Italy


On my God. Please no. We will never eat that kind of spaghetti. Sorry. I hope they are good. Anyway, we'd gladly invite you to try ours.
 
Back
Top