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.
 
I shared the code to the ML and someone replied in a truly unkind and irrational way,telling that "my slop is unwelcome here".

I really don't understand this kind of position. It seems so irrational to me,because :

1) it solves a real problem (in FreeBSD at the moment we can't use the latest version of Claude code).

2) the code that Claude produced is working in my machine

3) if it does not work in different machines,it's not hard at all to be able to fix it

4) Not all AI-generated code is sloppy. Before judging, isn't better to try ? And before rejecting, isn't better to at least figure out if the idea is useful for re-implement it differently ?

I don't see any utility in such behavior. Furthermore it puts us one against each other. A more pragmatic reaction would be invaluable.
 
I shared the code to the ML and someone replied in a truly unkind and irrational way,telling that "my slop is unwelcome here".
You cross-posted it to freebsd-ports. Cross-posting itself is considered bad etiquette. It's not a port, it's not even an attempt at creating a port and it's not an issue with an existing port. So it has zero place on freebsd-ports.

And the thing about mailing lists is, it may have thousands of recipients, you have no idea where it all ends up. And anyone of the recipients can reply directly to you. Made that mistake a long time ago too, posted something stupid to a mailing list, and got royally scolded for it by a dozen or so recipients. And rightfully so.
 
You cross-posted it to freebsd-ports. Cross-posting itself is considered bad etiquette. It's not a port, it's not even an attempt at creating a port and it's not an issue with an existing port. So it has zero place on freebsd-ports.

And the thing about mailing lists is, it may have thousands of recipients, you have no idea where it all ends up. And anyone of the recipients can reply directly to you. Made that mistake a long time ago too, posted something stupid to a mailing list, and got royally scolded for it by a dozen or so recipients. And rightfully so.

I suspect he has an irrational hatred for the vibe coding. So irrational that he transcends the technical argumentation and he in-directly calls into question the person's seriousness. But what I want to emphasize is that we shouldn't get carried away by strong emotions when we don't like something, but rather we should maintain clarity and rationality, because that doesn't mean that what we don't like isn't useful. For me usually what counts more is how / when a tool can be used. Not that we should not use it at all,or,in the worst case scenario,hate it.
 
You really haven't been on the internet for very long have you?

Yes. Since 1994. I suspect that what you want to tell me is that I should don't care at all. Yes,nice suggestion. Infact I haven't replied to him with the same rudeness. Anyway this kind of behavior still amazes me and pushes me to seek a confrontation.
 
Running shell scripts that someone without technical background vibe-coded is the modern version of Russian roulette.

I don't know man. What I know is if someone gives to me a script that I need,before to try it I will give it to Claude to fix the errors. When it says that's good I will run it on my machine inside a protected environment. It is not a Russian Roulette for me. Everything I created until now is working and I'm happy.
 
Well at least cd /usr/ports/misc/claude-code && make find-new-version looks promising.

Pkg does not work. If you install it normally,the claude executable freezes. This is the reason why I looked for the reason of the freeze and I found a new tecnique to fix it.
 
nvm after using it some time, it froze too. currently investigating.

From what I have understood,Anthropic stopped the development of Claude Code via npm and they started using bun. So what ? that only the legacy Claude Code works. The newer ones have been / should be rewritten for Bun. I don't know if Yuri even started. I have found the way to implement Claude code via a specific tecnique. But I'm sure that you will NOT try to look inside my code,because you like to find what's broken and how to fix it by yourself. I can understand it. Keep in consideration that I did the same as you,only on a different level,because I'm not a programmer. But I too have used my time and my energy to find a solution,with and without Claude's help.
 
installing claude desktop on linux
(for a jail or podman) on freebsd

has 2 methods for ubuntu
which is currently the only version of linux supported


1) Add Anthropic's apt repository and install with apt

Code:
sudo apt install curl
sudo curl -fsSLo /usr/share/keyrings/claude-desktop-archive-keyring.asc https://downloads.claude.ai/claude-desktop/key.asc
echo "deb [arch=amd64,arm64 signed-by=/usr/share/keyrings/claude-desktop-archive-keyring.asc] https://downloads.claude.ai/claude-desktop/apt/stable stable main" | sudo tee /etc/apt/sources.list.d/claude-desktop.list
sudo apt update && sudo apt install claude-desktop

which should be possible in a ubuntu podman container

2) or using curl to download a deb file

Code:
curl -fLO "https://downloads.claude.ai/claude-desktop/apt/stable/$(curl -s "https://downloads.claude.ai/claude-desktop/apt/stable/dists/stable/main/binary-$(dpkg --print-architecture)/Packages" | grep '^Filename: pool/main/c/claude-desktop/claude-desktop_' | sort -V | tail -n 1 | cut -d' ' -f2)"

wow looks like they got claude to write that code

and then install the deb

Code:
sudo apt install ./claude-desktop_*.deb
 
Back
Top