Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ Inside the debug shell, you can run diagnostics manually:
- `.env.example` - Example environment variables for MCP servers
- `.env` - Your local environment variables (create from .env.example)
- `.dockerignore` - Files excluded from Docker build context
- `install.sh` - One-line installer (`curl … | bash`): pulls the GHCR image, stores the OAuth token in `~/.config/claude-standalone/claude.env` (chmod 600), and installs a `claude-box` launcher into `~/.local/bin` (the hardened `docker run` wrapped as an executable; supports `--uninstall` and a non-interactive path via `CLAUDE_CODE_OAUTH_TOKEN`)
- `install.sh` - One-line installer (`curl … | bash`): pulls the GHCR image, stores the OAuth token in `~/.config/claude-standalone/claude.env` (chmod 600), and installs a `claude-box` launcher into `~/.local/bin` (the hardened `docker run` wrapped as an executable; supports `--uninstall` and a non-interactive path via `CLAUDE_CODE_OAUTH_TOKEN`). Also detects host `~/.claude/{agents,commands,skills}` and offers to pass them through — **mount** the live path (default), **copy** a snapshot to `~/.config/claude-standalone/resources/`, or **skip** (override non-interactively with `CLAUDE_RESOURCES_MODE`); the choice is written to `resources.conf`, `claude-box` mounts the paths read-only at `/host-claude/*`, and the entrypoint merges them OVER the baked state (host wins on collision; baked `opsx`/openspec skills survive)
- `run_claude.sh` - Main entry point for running Claude Code (autonomous agent)
- `run_acp.sh` - ACP entry point for IDE use (Zed); launched BY the editor over stdio
- `.devcontainer/devcontainer.json` - Dev Container definition (interactive dev inside the image)
Expand Down
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,16 @@ if [ "$HOME" != "/home/claude" ] && [ ! -e "$HOME/.claude.json" ]; then\n\
mkdir -p "$HOME"\n\
cp -a /home/claude/. "$HOME/" 2>/dev/null || true\n\
fi\n\
# Merge host-provided resources mounted by the launcher at /host-claude/<name>\n\
# OVER the baked state. Unconditional and AFTER the baked copy (so a resumed HOME\n\
# still receives them). Host files win on name collision; baked-only files such as\n\
# commands/opsx and the openspec skills survive because cp merges, not replaces.\n\
for d in agents commands skills; do\n\
if [ -d "/host-claude/$d" ]; then\n\
mkdir -p "$HOME/.claude/$d"\n\
cp -a "/host-claude/$d/." "$HOME/.claude/$d/" 2>/dev/null || true\n\
fi\n\
done\n\
cd /workspace 2>/dev/null || cd "$HOME"\n\
EXTRA_ARGS=""\n\
mode_msg="permission mode: auto (default; falls back to default mode if auto is unavailable)"\n\
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,17 @@ curl -fsSL https://raw.githubusercontent.com/highload-zone/claude-code-standalon
less install.sh && bash install.sh

CLAUDE_CODE_OAUTH_TOKEN=... bash install.sh # non-interactive (skips the token prompt)
CLAUDE_RESOURCES_MODE=mount bash install.sh # non-interactive resource choice: mount | copy | skip
bash install.sh --uninstall # remove the launcher (config is left in place)
```

**Your local agents, commands, and skills.** If the installer finds `~/.claude/agents`,
`~/.claude/commands`, or `~/.claude/skills` on the host, it offers to pass them through to the
container: **mount** the live path (default — edits on the host show up next run), **copy** a
snapshot into `~/.config/claude-standalone/resources/`, or **skip**. `claude-box` mounts the chosen
paths read-only and the container merges them **over** its baked state, so your resources win on a
name clash while the image's own commands/skills (e.g. `opsx`) still work.

`claude-box` forwards your host git identity (so commits are attributed to you) and, if you set
`DEPLOY_KEY=/path/to/scoped_key`, mounts it read-only to enable `git push` (see [SECURITY.md](./SECURITY.md)).

Expand Down
67 changes: 67 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,58 @@ chmod 600 "$ENV_FILE"
say "Token saved to $ENV_FILE (chmod 600)."
say "Optional MCP keys: add 'CONTEXT7_API_KEY=...' / 'PERPLEXITY_API_KEY=...' lines to that file."

# ----------------------------------------------------------------------------
# Host resources: detect ~/.claude/{agents,commands,skills} and pass them through
# (the launcher mounts them at /host-claude/*; the container entrypoint merges
# them OVER the baked state — host wins, baked-only files like opsx survive).
# Default is to mount the live path; copy takes a snapshot; skip opts out.
# ----------------------------------------------------------------------------
RES_CONF="$CONFIG_DIR/resources.conf"
RES_SNAPSHOT="$CONFIG_DIR/resources"
HOST_CLAUDE="${CLAUDE_HOME:-$HOME/.claude}"
: > "$RES_CONF"

detected=""
for d in agents commands skills; do
if [ -d "$HOST_CLAUDE/$d" ] && [ -n "$(ls -A "$HOST_CLAUDE/$d" 2>/dev/null)" ]; then
detected="$detected $d"
fi
done
detected="${detected# }"

if [ -n "$detected" ]; then
say ""
say "Found local Claude resources in $HOST_CLAUDE: $detected"
mode="${CLAUDE_RESOURCES_MODE:-}"
if [ -z "$mode" ]; then
if [ -r /dev/tty ]; then
printf 'Pass them to the container? [M]ount live path (default) / [C]opy snapshot / [S]kip: ' >&2
read -r ans < /dev/tty
case "$ans" in [Cc]*) mode="copy";; [Ss]*) mode="skip";; *) mode="mount";; esac
else
mode="mount" # non-interactive default
fi
fi
if [ "$mode" = "skip" ]; then
say "Skipping host resource passthrough."
else
rm -rf "$RES_SNAPSHOT"
for d in $detected; do
if [ "$mode" = "copy" ]; then
mkdir -p "$RES_SNAPSHOT/$d"
cp -a "$HOST_CLAUDE/$d/." "$RES_SNAPSHOT/$d/" 2>/dev/null || true
src="$RES_SNAPSHOT/$d"
else
src="$HOST_CLAUDE/$d"
fi
printf 'CLAUDE_RES_%s="%s"\n' "$(printf '%s' "$d" | tr 'a-z' 'A-Z')" "$src" >> "$RES_CONF"
done
say "Host resources ($mode): $detected — will be merged into the container's ~/.claude/ on launch."
fi
else
say "No local ~/.claude/{agents,commands,skills} detected — skipping resource passthrough."
fi

# ----------------------------------------------------------------------------
# Install the launcher (regenerated every run = upgrade path)
# ----------------------------------------------------------------------------
Expand All @@ -105,6 +157,7 @@ set -euo pipefail

IMAGE="${CLAUDE_IMAGE:-ghcr.io/highload-zone/claude-code-standalone:latest}"
ENV_FILE="${CLAUDE_ENV_FILE:-${XDG_CONFIG_HOME:-$HOME/.config}/claude-standalone/claude.env}"
RES_CONF="${XDG_CONFIG_HOME:-$HOME/.config}/claude-standalone/resources.conf"

# Footgun guards (not a defense against a hostile operator — see SECURITY.md).
for a in "$@"; do
Expand Down Expand Up @@ -135,6 +188,20 @@ else
echo "claude-box: no env-file at $ENV_FILE — set CLAUDE_CODE_OAUTH_TOKEN or re-run install.sh." >&2
fi

# Host resources (agents/commands/skills), configured by install.sh. Mounted
# read-only at /host-claude/<name>; the container entrypoint merges them over the
# baked state. Paths come from resources.conf (live path, or a copied snapshot).
if [ -f "$RES_CONF" ]; then
. "$RES_CONF"
for d in agents commands skills; do
var="CLAUDE_RES_$(printf '%s' "$d" | tr 'a-z' 'A-Z')"
eval "p=\${$var:-}"
if [ -n "$p" ] && [ -d "$p" ]; then
args+=( -v "$p:/host-claude/$d:ro" )
fi
done
fi

# git commit identity from the host (so commits are attributed to you).
gn="$(git config --get user.name 2>/dev/null || true)"
ge="$(git config --get user.email 2>/dev/null || true)"
Expand Down
Loading