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
15 changes: 8 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ This is a security-hardened Docker container that runs Claude Code with pre-inst
The container is built on Node.js 22 (LTS) with the following layers:

1. **Base System** - Debian Trixie (glibc 2.41) with hardened security settings
2. **Toolchain (npm)** - All global npm CLIs installed via `npm ci` from `tools/package.json` + `tools/package-lock.json` (sha512-integrity, exact pinned versions, no `@latest`). Bins exposed via PATH (`/opt/toolchain/node_modules/.bin`). Includes Claude Code (2.1.159), OpenSpec (1.3.1), CodeGraph (0.9.8), caveman-shrink (0.1.0), the MCP servers, and dev tools (pnpm 11.5.0, typescript 6.0.3, ts-node 10.9.2, prettier 3.8.3, eslint 10.4.1)
2. **Toolchain (npm)** - All global npm CLIs installed via `npm ci` from `tools/package.json` + `tools/package-lock.json` (sha512-integrity, exact pinned versions, no `@latest`). Bins exposed via PATH (`/opt/toolchain/node_modules/.bin`). Includes Claude Code (2.1.177), OpenSpec (1.4.1), CodeGraph (1.0.0), caveman-shrink (0.1.0), the MCP servers, and dev tools (pnpm 11.6.0, typescript 6.0.3, ts-node 10.9.2, prettier 3.8.4, eslint 10.5.0)
3. **OpenSpec** - initialized into `/workspace` at build time with telemetry disabled via `OPENSPEC_TELEMETRY=0`
4. **RTK** - Rust Token Killer; static musl binary in `/usr/local/bin` (version via `RTK_VERSION` build arg, sha256-verified); `rtk init -g --auto-patch` installs a Claude Code PreToolUse hook that rewrites Bash commands through `rtk`
5. **Caveman** - Output-compression skill for Claude Code, installed at build time via its plugin mechanism (`claude plugin install`), pinned to tag `v1.8.2`
5. **Caveman** - Output-compression skill for Claude Code, installed at build time via its plugin mechanism (`claude plugin install`), pinned to tag `v1.9.0`
6. **CodeGraph** - Code knowledge graph exposed as an MCP server (`@colbymchenry/codegraph`); ships a vendored prebuilt binary, runtime GitHub download disabled via `CODEGRAPH_NO_DOWNLOAD=1`
7. **MCP Servers** - Configured from MCP JSON configs; all stdio servers use pre-installed bins (no runtime `npx`)

Expand Down Expand Up @@ -209,9 +209,9 @@ gate runs each dev tool's `--version` to catch an incompatible engine (this is h
pnpm 11 vs Node 20 mismatch was caught before the base was bumped to Node 22).

**Build-time variables** (set during `docker build`):
- `RTK_VERSION` - Git tag of the RTK release to download (default: `v0.42.0`); RTK is a
- `RTK_VERSION` - Git tag of the RTK release to download (default: `v0.42.4`); RTK is a
GitHub-release binary, not npm. Override directly: `docker build --build-arg RTK_VERSION=...`
- `RTK_SHA256` - sha256 of the RTK tarball (default matches `v0.42.0`); bump together with
- `RTK_SHA256` - sha256 of the RTK tarball (default matches `v0.42.4`); bump together with
`RTK_VERSION` or the integrity check fails by design

**Runtime variables** (set when running container):
Expand Down Expand Up @@ -289,19 +289,19 @@ Two extra entrypoints exist beside the autonomous `run_claude.sh`; they share th
- Telemetry is opt-out only via the `OPENSPEC_TELEMETRY=0` env var (no `telemetry.enabled` config key exists); set as baked-in ENV, covering build and runtime
- Source: https://github.com/Fission-AI/OpenSpec
- **RTK** - Rust Token Killer; CLI proxy that filters/compresses command output to cut LLM token usage (`rtk` binary)
- Static musl binary downloaded from GitHub releases into `/usr/local/bin`; pinned via `RTK_VERSION` build arg (default `v0.42.0`), no Rust toolchain needed
- Static musl binary downloaded from GitHub releases into `/usr/local/bin`; pinned via `RTK_VERSION` build arg (default `v0.42.4`), no Rust toolchain needed
- `rtk init -g --auto-patch` runs at build time (as the `claude` user): installs a **Claude Code PreToolUse hook** that transparently rewrites Bash commands (`git status` → `rtk git status`), writes `~/RTK.md`, and patches `~/.bashrc`
- `-g` targets Claude Code (there is no `--agent claude`); `--auto-patch` makes init non-interactive
- Runtime needs only the `rtk` binary in PATH + the hook; no daemon. Optional config at `~/.config/rtk/config.toml`
- Source: https://github.com/rtk-ai/rtk
- **Caveman** - Output-compression skill for Claude Code (terse "caveman-speak"), reduces output tokens (~65%)
- Installed at build time via `npx -y github:JuliusBrussee/caveman#v1.8.2 --non-interactive --only claude` (as the `claude` user; requires Node.js >= 18)
- Installed at build time via `npx -y github:JuliusBrussee/caveman#v1.9.0 --non-interactive --only claude` (as the `claude` user; requires Node.js >= 18)
- For the `claude` provider the installer uses the Claude Code **plugin mechanism** (`claude plugin marketplace add` + `claude plugin install caveman@caveman`) and wires hooks (by default it would also add a `caveman-shrink` MCP entry — suppressed here with `--no-mcp-shrink`, see below)
- **Verified by build:** the `claude plugin marketplace add` + `claude plugin install` steps succeed during `docker build` — `marketplace add` is a public HTTPS git clone and `plugin install` is a local copy, so neither hits the Claude auth API (and `configure-claude.sh` has already written `~/.claude.json` by that layer). Skill + hooks are installed. Not made best-effort, so any future failure stays visible
- Installed with **`--no-mcp-shrink`**: caveman's auto-registration wired `caveman-shrink` as a standalone MCP server with no upstream command, which always `✗ Failed to connect` (it is middleware, not a server). Instead `caveman-shrink` is pre-installed globally and applied as a wrapper around the codegraph MCP server (see CodeGraph / MCP Servers)
- Source: https://github.com/JuliusBrussee/caveman
- **CodeGraph** - Pre-indexed code knowledge graph (symbols, call graph, impact) served to agents over MCP (`codegraph` binary)
- Installed via `npm ci` from the locked toolchain (`@colbymchenry/codegraph@0.9.8`); also registered as the `codegraph` MCP server, wrapped by `caveman-shrink` to compress its (verbose) tool descriptions — verified `✓ Connected` (see "MCP Servers")
- Installed via `npm ci` from the locked toolchain (`@colbymchenry/codegraph@1.0.0`); also registered as the `codegraph` MCP server, wrapped by `caveman-shrink` to compress its (verbose) tool descriptions — verified `✓ Connected` (see "MCP Servers")
- **Not pure JS:** the npm package is a thin shim; the real artifact is a per-platform optionalDependency (`@colbymchenry/codegraph-linux-x64`) bundling a vendored Node 24 runtime + prebuilt binary. `codegraph --help` at build time verifies the binary runs (**verified**: the vendored Node 24 binary runs on `node:22-trixie-slim`)
- `CODEGRAPH_NO_DOWNLOAD=1` (baked-in ENV) forbids the shim's runtime fallback that fetches the binary from GitHub Releases — the binary must come from the npm registry only
- 100% local: local SQLite index (`.codegraph/codegraph.db`, FTS5), no API keys, no external services
Expand Down Expand Up @@ -386,6 +386,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`)
- `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: 5 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ ARG USER_NAME=claude
# dev tools) are NOT build args — they are pinned in tools/package.json and locked
# in tools/package-lock.json (installed via `npm ci`). Change versions there.
# RTK is a GitHub-release binary (not npm), so it keeps a version + sha256 arg.
ARG RTK_VERSION=v0.42.0
ARG RTK_VERSION=v0.42.4

# Create non-root user with specific UID/GID.
# Free the requested UID/GID if the base image already uses it (node:22 ships a
Expand Down Expand Up @@ -68,9 +68,9 @@ RUN DELTA_VERSION="0.19.2" && \
# contains a single binary `rtk` placed in /usr/local/bin.
RUN case "$TARGETARCH" in \
amd64) RTK_ASSET="rtk-x86_64-unknown-linux-musl.tar.gz"; \
RTK_SHA256="cdd4f87ac97ce958f71b53a991880d6adcc41cc5bca1044175a64630980152be";; \
RTK_SHA256="34975116da11e09e502501daf758143e0b22ed3a42a10eb67fb693a6270d9e36";; \
arm64) RTK_ASSET="rtk-aarch64-unknown-linux-gnu.tar.gz"; \
RTK_SHA256="62bb749df1ed64f09149998c31de864932f047a1be4e0f882a8ceada849e0871";; \
RTK_SHA256="cc2b91c064eb670c097c184913c8fbcb1a943d53d7fe505375e96ba0c5b6459f";; \
*) echo "unsupported TARGETARCH for RTK: $TARGETARCH" >&2; exit 1;; \
esac && \
curl -fsSL "https://github.com/rtk-ai/rtk/releases/download/${RTK_VERSION}/${RTK_ASSET}" -o /tmp/rtk.tar.gz && \
Expand Down Expand Up @@ -214,7 +214,7 @@ RUN rtk init -g --auto-patch
# Caveman: output-compression skill for Claude Code. For the `claude` provider
# the installer uses the Claude Code plugin mechanism (`claude plugin marketplace
# add` + `claude plugin install caveman@caveman`) and also wires hooks.
# Pinned to tag v1.8.2 (non-interactive, claude only). NOTE: a commit-SHA ref
# Pinned to tag v1.9.0 (non-interactive, claude only). NOTE: a commit-SHA ref
# (`#a025122…`) would be more immutable, but `npx github:…#<40-char-sha>` fails
# with "GitFetcher requires an Arborist constructor to pack a tarball" (npm git
# fetcher limitation) — the tag ref is what actually installs. The tag's mutability
Expand All @@ -239,7 +239,7 @@ RUN rtk init -g --auto-patch
# auth API); caveman's SessionStart/UserPromptSubmit hooks merge into the same
# settings.json alongside RTK's PreToolUse hook. Not made best-effort so any
# future failure stays visible.
RUN npx -y github:JuliusBrussee/caveman#v1.8.2 --non-interactive --only claude --no-mcp-shrink
RUN npx -y github:JuliusBrussee/caveman#v1.9.0 --non-interactive --only claude --no-mcp-shrink

# Create simple startup script for runtime.
# --remote-control: start with Remote Control enabled by default (per project
Expand Down
101 changes: 95 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,95 @@ as an autonomous agent over your project. Built on Node.js 22 LTS (Debian Trixie
multi-arch (linux/amd64 + linux/arm64), with a pinned, lockfile-controlled CLI toolchain and a
curated set of MCP servers.

## Getting started

The prebuilt multi-arch image is published to GHCR — **you don't clone this repo or build anything**.
Requires Docker and a Claude Code OAuth token (`claude setup-token`).

### Quick install (Linux / macOS)

```bash
curl -fsSL https://raw.githubusercontent.com/highload-zone/claude-code-standalone/main/install.sh | bash
```

The installer pulls the GHCR image, asks for your OAuth token once (stored in
`~/.config/claude-standalone/claude.env`, `chmod 600`), and installs a `claude-box` launcher into
`~/.local/bin`. Then, from any project directory (mounted **read-write**):

```bash
claude-box # hardened agent over the current directory
claude-box --model opus # extra args pass through to claude
```

If `~/.local/bin` isn't on your `PATH`, the installer prints the line to add (e.g.
`echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc`).

```bash
# inspect before running (it's curl | bash, after all):
curl -fsSL https://raw.githubusercontent.com/highload-zone/claude-code-standalone/main/install.sh -o install.sh
less install.sh && bash install.sh

CLAUDE_CODE_OAUTH_TOKEN=... bash install.sh # non-interactive (skips the token prompt)
bash install.sh --uninstall # remove the launcher (config is left in place)
```

`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)).

### Without the installer — one `docker run`

Save your token once, then run the image directly. The token file is read by `--env-file`, so it
must be raw `KEY=value` (no quotes, no `export`):

```bash
mkdir -p ~/.config/claude-standalone
printf 'CLAUDE_CODE_OAUTH_TOKEN=%s\n' 'YOUR_TOKEN' > ~/.config/claude-standalone/claude.env
chmod 600 ~/.config/claude-standalone/claude.env
# optional MCP keys: add CONTEXT7_API_KEY=... / PERPLEXITY_API_KEY=... lines

docker pull ghcr.io/highload-zone/claude-code-standalone:latest
```

From the project directory you want the agent to work on:

```bash
docker run -it --rm \
--cap-drop=ALL --security-opt=no-new-privileges:true --pids-limit=100 --network=bridge \
--user "$(id -u):$(id -g)" \
--tmpfs /home/agent:exec,mode=1777,size=512m -e HOME=/home/agent \
--tmpfs /tmp:noexec,nosuid,size=100m \
-v "$PWD:/workspace:rw" -w /workspace \
--env-file ~/.config/claude-standalone/claude.env \
ghcr.io/highload-zone/claude-code-standalone:latest
```

> **Why the command is long — and don't shorten it.** The image is self-contained (entrypoint, tools,
> config, MCP servers are all baked in), but the container's *protection* — `--cap-drop=ALL`, the
> non-root `--user`, the `noexec` tmpfs scratch, network isolation — are **`docker run` flags, not
> something an image can carry**: Docker's security model puts these in the operator's hands by
> design. `$(id -u):$(id -g)` (so the agent owns your files) and `$PWD` (which project to mount) are
> likewise resolved on the host at run time. Dropping the hardening flags to make the command shorter
> removes exactly the boundary this image exists to provide — that's why the installer above wraps
> the full command in `claude-box` rather than offering a trimmed-down one.

To attribute commits to **you** and/or enable `git push`, add to the `docker run`:

```bash
-e GIT_AUTHOR_NAME="$(git config user.name)" -e GIT_COMMITTER_NAME="$(git config user.name)" \
-e GIT_AUTHOR_EMAIL="$(git config user.email)" -e GIT_COMMITTER_EMAIL="$(git config user.email)" \
# for push, mount a SCOPED, read-only deploy key (see SECURITY.md):
-v /path/to/repo_deploy_key:/home/agent/deploy_key:ro \
-e GIT_SSH_COMMAND="ssh -i /home/agent/deploy_key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" \
```

> **Token in the system keyring (optional).** To avoid a plaintext `--env-file`, store the token in
> the OS keyring and inject it at run time instead — drop `--env-file` and add
> `-e CLAUDE_CODE_OAUTH_TOKEN="$(secret-tool lookup service claude-code key oauth)"` on Linux
> (libsecret), or `$(security find-generic-password -s claude-code -a oauth -w)` on macOS (Keychain).

See [Requirements](#requirements), [Setup](#setup), and [Run](#run) below for building locally and
the repo's script-based flow.

## Why

Running an autonomous coding agent with broad permissions directly on your host is risky. This image
Expand Down Expand Up @@ -57,15 +146,15 @@ Base: `node:22-trixie-slim` (Node 22 LTS, Debian 13 / glibc 2.41). Multi-arch (a
Toolchain pinned in `tools/package.json`, locked in `tools/package-lock.json` (`npm ci`, sha512
integrity, exact versions):

- `@anthropic-ai/claude-code` (2.1.159), `@fission-ai/openspec` (1.3.1)
- `@agentclientprotocol/claude-agent-acp` (0.39.0) — ACP adapter for IDE use (Zed); reuses
- `@anthropic-ai/claude-code` (2.1.177), `@fission-ai/openspec` (1.4.1)
- `@agentclientprotocol/claude-agent-acp` (0.44.0) — ACP adapter for IDE use (Zed); reuses
the pinned `claude` binary via `CLAUDE_CODE_EXECUTABLE`
- `@colbymchenry/codegraph` (0.9.8, MCP) wrapped by `caveman-shrink` (0.1.0)
- `@colbymchenry/codegraph` (1.0.0, MCP) wrapped by `caveman-shrink` (0.1.0)
- MCP servers: `sequential-thinking`, `context7` (HTTP), `perplexity`
- caveman skill (plugin, tag `v1.8.2`)
- Dev tools: `pnpm` 11.5.0, `typescript` 6.0.3, `ts-node` 10.9.2, `prettier` 3.8.3, `eslint` 10.4.1
- caveman skill (plugin, tag `v1.9.0`)
- Dev tools: `pnpm` 11.6.0, `typescript` 6.0.3, `ts-node` 10.9.2, `prettier` 3.8.4, `eslint` 10.5.0

GitHub-release binaries (per-arch, sha256-pinned): `rtk` (v0.42.0), `git-delta` (0.19.2).
GitHub-release binaries (per-arch, sha256-pinned): `rtk` (v0.42.4), `git-delta` (0.19.2).
CLI utilities: `jq`, `ripgrep`, `fd`, `tree`, `fzf`, `mc`, `gnupg`.

See [CLAUDE.md](./CLAUDE.md) for the full architecture and per-component details.
Expand Down
Loading
Loading