docs: añadir guías paso a paso (01-05) y scripts faltantes

Cubre el flujo completo desde Termux limpio (F-Droid/GitHub) hasta
personalizaciones de Claude Code, con base en la restauración de
2026-06-15 y el handoff curado del 2026-06-10.

- bin/: añade mdns-publish.py y ssh-fallback
- config/: añade smb.conf.example y aliases samba/mDNS en bashrc.snippet
- docs/: nuevos 01-termux-fresh-install, 02-base-packages,
  03-ssh-mdns-tailscale, 04-samba, 05-claude-code
- install.sh: copia los 7 scripts e instala zeroconf vía pip
- README.md: índice de docs y sanitización de identificadores personales

Gotchas documentados:
- Samba msg.lock 0755 tras tar restore
- zeroconf no viaja en backup tar
- fallos silenciosos por 2>/dev/null en .bashrc

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andrés Eduardo García Márquez 2026-06-16 07:58:57 -05:00
parent 0d5d099dee
commit f7ad71bd3c
11 changed files with 557 additions and 6 deletions

View File

@ -21,7 +21,7 @@ Claude Code v2.1.113+ switched to native binaries, breaking Termux/Android compa
## Quick Install ## Quick Install
```bash ```bash
git clone <your-gitea-url>/andres/claude-code-termux.git git clone <your-git-host>/<your-user>/claude-code-termux.git
cd claude-code-termux cd claude-code-termux
bash install.sh bash install.sh
``` ```
@ -40,7 +40,9 @@ bash install.sh --dry-run
| `claude-check-env` | `~/.local/bin/` | Environment validator | | `claude-check-env` | `~/.local/bin/` | Environment validator |
| `claude-update` | `~/.local/bin/` | Safe updater (ceiling-aware) | | `claude-update` | `~/.local/bin/` | Safe updater (ceiling-aware) |
| `mdns-resolve` | `~/.local/bin/` | Pure Python mDNS resolver | | `mdns-resolve` | `~/.local/bin/` | Pure Python mDNS resolver |
| `ssh-mdns-proxy` | `~/.local/bin/` | SSH ProxyCommand with mDNS+Tailscale | | `mdns-publish.py` | `~/.local/bin/` | Publish this device as `movil.local` (needs `zeroconf`) |
| `ssh-mdns-proxy` | `~/.local/bin/` | SSH ProxyCommand: mDNS → Tailscale |
| `ssh-fallback` | `~/.local/bin/` | SSH ProxyCommand: Tailscale → LAN fallback |
| `settings.json` | `~/.claude/` | Disables auto-updater | | `settings.json` | `~/.claude/` | Disables auto-updater |
## Usage ## Usage
@ -71,6 +73,19 @@ export TAILSCALE_DOMAIN="your-tailnet.ts.net"
`claude-update` enforces this automatically. See `docs/TROUBLESHOOTING.md` for details. `claude-update` enforces this automatically. See `docs/TROUBLESHOOTING.md` for details.
## Docs
Step-by-step guides for each piece of the stack:
| # | Doc | Covers |
|---|-----|--------|
| 01 | [docs/01-termux-fresh-install.md](docs/01-termux-fresh-install.md) | Clean Termux install (F-Droid / GitHub Releases, not Play) |
| 02 | [docs/02-base-packages.md](docs/02-base-packages.md) | Required `pkg` / `pip` / `npm` packages |
| 03 | [docs/03-ssh-mdns-tailscale.md](docs/03-ssh-mdns-tailscale.md) | SSH, `.local` resolution, Tailscale |
| 04 | [docs/04-samba.md](docs/04-samba.md) | Samba server on port 4450 + `msg.lock` gotcha |
| 05 | [docs/05-claude-code.md](docs/05-claude-code.md) | Claude Code internals (version ceiling, `clauded`, settings) |
| — | [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) | Common errors and fixes |
## Troubleshooting ## Troubleshooting
See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) for: See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) for:

73
bin/mdns-publish.py Executable file
View File

@ -0,0 +1,73 @@
#!/data/data/com.termux/files/usr/bin/python
"""Publica servicios via mDNS/Zeroconf: SSH + Samba"""
import socket
import signal
import sys
from zeroconf import ServiceInfo, Zeroconf, IPVersion
HOSTNAME = 'movil'
SERVICES = [
{
'type': '_ssh._tcp.local.',
'port': 8022,
'props': {'description': 'Termux SSH'},
},
{
'type': '_smb._tcp.local.',
'port': 4450,
'props': {'description': 'Termux Samba', 'path': '/'},
},
]
def get_local_ip():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
ip = s.getsockname()[0]
s.close()
return ip
except Exception:
return '127.0.0.1'
def main():
ip = get_local_ip()
addr = socket.inet_aton(ip)
zc = Zeroconf(ip_version=IPVersion.V4Only)
infos = []
for svc in SERVICES:
info = ServiceInfo(
svc['type'],
f'{HOSTNAME}.{svc["type"]}',
addresses=[addr],
port=svc['port'],
properties=svc['props'],
server=f'{HOSTNAME}.local.',
)
zc.register_service(info)
infos.append(info)
print(f'Publicando {HOSTNAME}.local {svc["type"]} -> {ip}:{svc["port"]}')
def cleanup(sig, frame):
print('Deteniendo mDNS...')
for info in infos:
zc.unregister_service(info)
zc.close()
sys.exit(0)
signal.signal(signal.SIGINT, cleanup)
signal.signal(signal.SIGTERM, cleanup)
try:
signal.pause()
except AttributeError:
import time
while True:
time.sleep(3600)
if __name__ == '__main__':
main()

23
bin/ssh-fallback Executable file
View File

@ -0,0 +1,23 @@
#!/data/data/com.termux/files/usr/bin/bash
#
# ssh-fallback: Proxy para SSH con fallback automático
# Usado como ProxyCommand en ~/.ssh/config
#
# Uso: ssh-fallback <tailscale_host> <lan_ip> <port>
TAILSCALE_HOST="$1"
LAN_IP="$2"
PORT="${3:-22}"
# Intentar Tailscale primero (1 segundo timeout)
if ping -c 1 -W 1 "$TAILSCALE_HOST" &>/dev/null 2>&1; then
exec nc -w 5 "$TAILSCALE_HOST" "$PORT"
fi
# Fallback a IP LAN
if ping -c 1 -W 1 "$LAN_IP" &>/dev/null 2>&1; then
exec nc -w 5 "$LAN_IP" "$PORT"
fi
# Último intento: Tailscale sin verificar
exec nc -w 10 "$TAILSCALE_HOST" "$PORT"

View File

@ -13,3 +13,24 @@ export PATH="$HOME/.local/bin:$PATH"
# Prevent Claude Code auto-updater (enforced at shell level too) # Prevent Claude Code auto-updater (enforced at shell level too)
export DISABLE_AUTOUPDATER=1 export DISABLE_AUTOUPDATER=1
# === Optional: helper aliases for samba + mDNS services ===
# Only useful if you set up smbd and mdns-publish (see docs/04-samba.md, docs/03-ssh-mdns-tailscale.md).
alias smb-start='smbd -D -s ~/.config/samba/smb.conf 2>>~/.config/samba/samba-startup.log && echo "Samba started on port 4450"'
alias smb-stop='pkill smbd && echo "Samba stopped"'
alias smb-restart='pkill smbd 2>/dev/null; sleep 1; smbd -D -s ~/.config/samba/smb.conf 2>>~/.config/samba/samba-startup.log && echo "Samba restarted"'
alias smb-status='pgrep -a smbd && echo "Port: 4450" || echo "Samba is not running"'
alias smb-log='tail -50 ~/.config/samba/samba.log'
alias mdns-status='pgrep -af mdns-publish || echo "mDNS publisher is not running"'
alias mdns-restart='pkill -f mdns-publish; sleep 1; nohup python ~/.local/bin/mdns-publish.py >>~/.cache/mdns.log 2>&1 & disown && echo "mDNS restarted"'
alias mdns-stop='pkill -f mdns-publish && echo "mDNS stopped"'
# === Optional: auto-start samba + mDNS on shell login ===
# Uncomment the block below if you want services up automatically.
# Failures are logged (NOT silenced) — silent failures after a tar restore are the worst class of bug.
#
# mkdir -p ~/.cache
# pgrep -x smbd >/dev/null 2>&1 || smbd -D -s ~/.config/samba/smb.conf 2>>~/.config/samba/samba-startup.log
# pgrep -f "mdns-publish" >/dev/null 2>&1 || nohup python ~/.local/bin/mdns-publish.py >>~/.cache/mdns.log 2>&1 & disown

69
config/smb.conf.example Normal file
View File

@ -0,0 +1,69 @@
# Samba on Termux — port 4450 (445 requires root)
#
# Replace YOUR_USER with the Samba user you created via `smbpasswd -a YOUR_USER`.
# Replace HOST_NAME with whatever you want this device to advertise as.
# Connect from clients with: \\<ip>:4450 or smb://<ip>:4450/
[global]
workgroup = WORKGROUP
server string = Termux Mobile
netbios name = HOST_NAME
# Non-privileged port
smb ports = 4450
security = user
map to guest = never
passdb backend = tdbsam
# SMB2/3 only — Windows 11 and modern Linux/macOS prefer these
client min protocol = SMB2
server min protocol = SMB2
server max protocol = SMB3
client max protocol = SMB3
smb encrypt = desired
server signing = desired
client signing = desired
# Termux paths (these MUST exist before smbd starts)
private dir = /data/data/com.termux/files/home/.config/samba/private
lock directory = /data/data/com.termux/files/home/.config/samba/lock
state directory = /data/data/com.termux/files/home/.config/samba/state
cache directory = /data/data/com.termux/files/home/.config/samba/cache
pid directory = /data/data/com.termux/files/home/.config/samba
log file = /data/data/com.termux/files/home/.config/samba/samba.log
max log size = 500
log level = 1
socket options = TCP_NODELAY IPTOS_LOWDELAY
use sendfile = yes
aio read size = 16384
aio write size = 16384
unix charset = UTF-8
dos charset = CP850
server multi channel support = yes
mdns name = mdns
# Share: Android shared storage (DCIM, Downloads, etc.)
[Storage]
comment = Android shared storage
path = /storage/emulated/0
browseable = yes
writable = yes
valid users = YOUR_USER
create mask = 0644
directory mask = 0755
# Share: Termux $HOME (be careful, this exposes shell config too)
[Termux]
comment = Termux home
path = /data/data/com.termux/files/home
browseable = yes
writable = yes
valid users = YOUR_USER
create mask = 0644
directory mask = 0755

View File

@ -0,0 +1,59 @@
# 01 — Fresh Termux install (Android)
This guide covers a clean Termux install **without** Google Play, which is required for Termux:API / Termux:Boot compatibility.
## Why not Google Play
Google Play freezes Termux on an old version. Addons (Termux:API, Termux:Boot, Termux:Widget) require signature parity with the base app, so the Play version is incompatible with the F-Droid / GitHub-Release addons.
**Decision:** install everything from F-Droid or GitHub Releases (Termux team's official builds).
## Recommended source
Use one of:
- [F-Droid](https://f-droid.org/en/packages/com.termux/) — auto-updates, signed by F-Droid
- [GitHub Releases](https://github.com/termux/termux-app/releases) — latest, signed by the Termux team
Both share the same signing key, so addons work across them. **Don't mix Play and F-Droid/GitHub.**
## APK list (download to the device beforehand)
| Package | Purpose | Required |
|---------|---------|----------|
| `termux-app_*.apk` | Base shell | yes |
| `termux-api-app_*.apk` | Sensors, camera, clipboard | recommended |
| `termux-boot-app_*.apk` | Auto-start on boot | optional |
| `termux-widget-app_*.apk` | Home-screen launchers | optional |
## First boot
```bash
# 1. Allow Termux access to shared storage (~/storage symlink)
termux-setup-storage
# 2. Prevent Android from killing the process when screen is off
termux-wake-lock
# 3. Update and install the minimum
pkg update -y
pkg install -y openssh nodejs git proot python
```
## SSH for remote access (optional but useful)
```bash
passwd # set an SSH password
sshd # start the daemon on port 8022
whoami # note the u0_a*** username for remote login
```
From another machine:
```bash
ssh -p 8022 u0_a***@<device-ip>
```
If the device is on Tailscale, use the Tailscale IP instead of the LAN one. Termux itself can't run the Tailscale CLI; use the Android Tailscale app and the device will appear in your tailnet automatically.
## What's next
Continue with `02-base-packages.md` to install the rest of the stack used by Claude Code and helper scripts.

66
docs/02-base-packages.md Normal file
View File

@ -0,0 +1,66 @@
# 02 — Base packages
Minimum set of packages needed by Claude Code and the helper scripts in this repo. Everything here is idempotent — re-running is safe.
## Required (Claude Code itself)
```bash
pkg install -y \
nodejs \
python \
git \
proot \
ripgrep
```
| Package | Why |
|---------|-----|
| `nodejs` | Claude Code runs on Node.js |
| `python` | `mdns-resolve`, `mdns-publish.py` |
| `git` | Cloning this repo, version control inside Claude |
| `proot` | Provides `termux-chroot` used by `clauded` (fixes `/tmp`) |
| `ripgrep` | Faster search inside Claude (the bundled one may not run on aarch64) |
## Recommended (SSH + mDNS)
```bash
pkg install -y openssh termux-api
pip install zeroconf
```
| Package | Why |
|---------|-----|
| `openssh` | SSH server (`sshd`) and client |
| `termux-api` | Backs the `termux-*` Android-bridge commands |
| `zeroconf` (pip) | Required by `mdns-publish.py`; **does not survive a `tar` backup** of `$HOME`, reinstall after restore |
## Optional (file sharing)
```bash
pkg install -y samba
```
See `04-samba.md` for the rest of the setup (custom port 4450, share definitions).
## Optional (image support in Claude)
```bash
npm install -g @img/sharp-wasm32 sharp --force
```
Enables image input (screenshots, diagrams). Sometimes flaky on aarch64; safe to skip.
## Verifying
```bash
node --version # >= 22 LTS recommended
python --version # 3.11+
proot --version
ssh -V
```
If any of these fail, re-run the relevant `pkg install`.
## What's next
`03-ssh-mdns-tailscale.md` — set up dynamic hostname resolution so you never hardcode IPs.

View File

@ -0,0 +1,60 @@
# 03 — SSH, mDNS and Tailscale on Termux
Termux's Bionic libc does **not** resolve `.local` names. This repo ships small Python helpers that fix this without needing root or a custom resolver.
## Scripts involved
| Script | Role |
|--------|------|
| `mdns-resolve` | Resolve `<host>.local` via raw multicast UDP. Returns the IP or non-zero. |
| `mdns-publish.py` | Publish this device on the LAN as `movil.local` (or whatever you choose) using `zeroconf`. |
| `ssh-mdns-proxy` | SSH `ProxyCommand` that tries mDNS first, then Tailscale DNS. Used from `~/.ssh/config`. |
| `ssh-fallback` | Lower-level fallback: tries Tailscale, then LAN. Useful when Tailscale is flaky. |
## SSH config
Drop the contents of `config/ssh_config.example` into `~/.ssh/config` (or merge it).
The key entries:
```ssh-config
Host *.local
ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p
Host lenovo-ideapad
ProxyCommand ~/.local/bin/ssh-mdns-proxy %h %p
Host *.YOUR-TAILNET.ts.net
User YOUR_USER
```
Adjust the username and the tailnet domain. The proxy command will try mDNS first, then fall through to your Tailscale magic-DNS name (env `TAILSCALE_DOMAIN`).
## Publishing this device as `movil.local`
If you want the device to be reachable as `movil.local` from your LAN:
```bash
# One-shot foreground (to see errors):
python ~/.local/bin/mdns-publish.py
# Background (typical use, from ~/.bashrc):
nohup python ~/.local/bin/mdns-publish.py >>~/.cache/mdns.log 2>&1 &
disown
```
**Gotcha:** `zeroconf` (the pip package) is **not** part of a `pkg`-managed install. If you restore Termux from a `tar` backup, `zeroconf` will be missing from `$PREFIX/lib/python*/site-packages` and the script will silently fail with `ModuleNotFoundError`. Re-run `pip install zeroconf` after every restore.
## Logging, not silencing
The `.bashrc` auto-start lines in this repo (`config/bashrc.snippet`) deliberately log to a file instead of `/dev/null`. Silent failures are the single most painful class of bug after a restore — keep the logs.
## Tailscale
Tailscale on Termux runs **as the Android app**, not as a CLI inside Termux. You can't `tailscale up` from a shell. The app provides magic DNS for `*.YOUR-TAILNET.ts.net`, which Bionic-libc resolves fine (it's just a regular DNS query).
When you list hosts in `~/.ssh/config`, prefer:
1. `<host>.local` for fast LAN access
2. `<host>.YOUR-TAILNET.ts.net` for off-LAN
Never hardcode IPs — they rotate.

75
docs/04-samba.md Normal file
View File

@ -0,0 +1,75 @@
# 04 — Samba server on Termux (port 4450)
Optional but useful: expose a few folders over SMB so other devices on the LAN can read/write files without ADB.
## Why port 4450
Ports below 1024 require root. Samba on Termux runs unprivileged, so we use a high port (`4450`) and configure clients to point at it explicitly.
```
\\<device-ip>:4450
```
Most file managers accept `smb://<ip>:4450/`.
## Install
```bash
pkg install -y samba
mkdir -p ~/.config/samba/lock ~/.config/samba/private
```
## Configuration
Copy `config/smb.conf.example` to `~/.config/samba/smb.conf` and edit the share paths to match your device. Then add a Samba user:
```bash
smbpasswd -a YOUR_USER
```
This creates `~/.config/samba/private/passdb.tdb`.
## Start / stop
```bash
smbd -D -s ~/.config/samba/smb.conf 2>>~/.config/samba/samba-startup.log
pkill smbd
```
The aliases in `config/bashrc.snippet` (`smb-start`, `smb-stop`, `smb-restart`, `smb-status`, `smb-log`) wrap these.
## Critical gotcha — `msg.lock` permissions after restore
If you restore `~/.config/samba/` from a `tar` backup, the `lock/msg.lock` directory ends up with mode `0700` because `tar` preserves the source attributes. `smbd` requires `0755` on this directory and refuses to start with:
```
invalid permissions on directory '.../msg.lock': has 0700 should be 0755
```
**Fix:**
```bash
chmod 0755 ~/.config/samba/lock/msg.lock
```
This must be re-run after every restore from a backup that includes the samba lock dir.
## Connecting from another machine
```bash
# Linux
smbclient -L //<device-ip> -p 4450 -U YOUR_USER
mount -t cifs //<device-ip>/Home /mnt -o port=4450,username=YOUR_USER
# Windows: \\<device-ip>:4450 in Explorer
# macOS: Finder → Go → Connect to Server → smb://<device-ip>:4450
```
## Discovery via mDNS
If you also run `mdns-publish.py` (see `03-ssh-mdns-tailscale.md`), the device shows up as `movil.local` and SMB clients can use that name directly.
## Don't bind to `0.0.0.0` if you don't need to
The default `smb.conf` example binds to all interfaces. If the device roams across hostile WiFi networks, restrict `interfaces =` and `bind interfaces only = yes` to the LAN interface (`wlan0` on most devices).

84
docs/05-claude-code.md Normal file
View File

@ -0,0 +1,84 @@
# 05 — Claude Code on Termux
The reason this whole repo exists. Two non-obvious things break Claude Code on Termux:
1. **Version ceiling: v2.1.112.** Starting v2.1.113, the CLI ships as a native binary (SEA) for `linux-x64`, `linux-arm64-glibc`, `linux-arm64-musl`, `darwin-*`, `win32-*`. Termux's `process.platform === 'android'` is not in that list. Even `--force`-installing the `linux-arm64` package fails because the binary needs `/lib/ld-linux-aarch64.so.1` or `/lib/ld-musl-aarch64.so.1`, neither of which exist in Bionic libc.
2. **`/tmp` is hardcoded.** Claude Code writes to `/tmp/claude/...` for background tasks. Termux doesn't have a writable `/tmp`. `termux-chroot` exposes `/tmp -> /usr/tmp` to **all child processes** (which `proot -b` alone does not).
## Install
If you just want it working, run the top-level installer:
```bash
bash install.sh
```
Otherwise, the manual steps are:
```bash
# 1. Pin to the last compatible version
npm install -g @anthropic-ai/claude-code@2.1.112
# 2. Protect against the auto-updater (it can ignore env vars)
chmod -R a-w $PREFIX/lib/node_modules/@anthropic-ai/claude-code/
# 3. Make the wrapper available
cp bin/clauded ~/.local/bin/
chmod +x ~/.local/bin/clauded
```
## Settings
`~/.claude/settings.json`:
```json
{
"env": { "DISABLE_AUTOUPDATER": "1" },
"skipDangerousModePermissionPrompt": true,
"effortLevel": "xhigh"
}
```
`DISABLE_AUTOUPDATER` is also exported from `bashrc.snippet`. Belt and suspenders — the auto-updater has been observed to ignore the env var in some releases, which is why `chmod -R a-w` is the real defense.
## How `clauded` works
```
clauded
└── termux-chroot
└── claude --dangerously-skip-permissions "$@"
```
`termux-chroot` simulates a Linux FHS where `/tmp`, `/etc`, `/usr` etc. point to their Termux equivalents. Crucially, every child process inherits this view, so any background task Claude spawns also sees a working `/tmp`.
## Updating safely
```bash
claude-update
```
This script:
- Checks npm for the latest version.
- Compares against the ceiling (`2.1.112`).
- Refuses to upgrade past the ceiling.
- Reapplies `chmod -R a-w` after a successful install.
If a future Anthropic release re-supports Android (or restores the pure-Node fallback), bump the ceiling in `claude-update` and the README.
## Authenticating
```bash
clauded
# Inside Claude: /login
```
Subscription accounts and API keys both work. The OAuth flow opens a URL; long-press to copy it on Android and paste it in a real browser (Chromium, Firefox).
## When the wheels come off
See `TROUBLESHOOTING.md` for:
- `EACCES: /tmp/claude/...`
- `native binary not installed`
- `claude --version` works but `clauded` errors
- Sharp / image support
- `proot-distro` escape hatch (lets you run newer versions inside a real glibc rootfs)

View File

@ -36,7 +36,7 @@ if [[ -z "$PREFIX" || ! -d "$PREFIX" ]]; then
exit 1 exit 1
fi fi
TOTAL=8 TOTAL=9
echo -e "${GREEN}=== Claude Code for Termux - Installer ===${NC}" echo -e "${GREEN}=== Claude Code for Termux - Installer ===${NC}"
echo "Version ceiling: v$CLAUDE_VERSION" echo "Version ceiling: v$CLAUDE_VERSION"
$DRY_RUN && echo -e "${YELLOW}(dry-run mode - no changes will be made)${NC}" $DRY_RUN && echo -e "${YELLOW}(dry-run mode - no changes will be made)${NC}"
@ -59,7 +59,7 @@ fi
# 4. Install scripts # 4. Install scripts
step 4 "Installing scripts to $BIN_DIR..." step 4 "Installing scripts to $BIN_DIR..."
run mkdir -p "$BIN_DIR" run mkdir -p "$BIN_DIR"
for script in clauded claude-check-env claude-update mdns-resolve ssh-mdns-proxy; do for script in clauded claude-check-env claude-update mdns-resolve mdns-publish.py ssh-mdns-proxy ssh-fallback; do
run cp "$SCRIPT_DIR/bin/$script" "$BIN_DIR/$script" run cp "$SCRIPT_DIR/bin/$script" "$BIN_DIR/$script"
run chmod +x "$BIN_DIR/$script" run chmod +x "$BIN_DIR/$script"
done done
@ -91,8 +91,14 @@ run npm install -g @img/sharp-wasm32 sharp --force 2>/dev/null || {
echo -e " ${YELLOW}Sharp install failed (optional, images won't work)${NC}" echo -e " ${YELLOW}Sharp install failed (optional, images won't work)${NC}"
} }
# 8. Done # 8. Install zeroconf for mDNS publisher (needed by mdns-publish.py)
step 8 "Verifying installation..." step 8 "Installing zeroconf (pip) for mDNS publishing..."
run pip install --quiet zeroconf 2>/dev/null || {
echo -e " ${YELLOW}zeroconf install failed (optional, mdns-publish won't work)${NC}"
}
# 9. Done
step 9 "Verifying installation..."
if ! $DRY_RUN; then if ! $DRY_RUN; then
echo "" echo ""
"$BIN_DIR/claude-check-env" "$BIN_DIR/claude-check-env"