Skip to content

Instantly share code, notes, and snippets.

@jclement
Last active April 2, 2026 15:39
Show Gist options
  • Select an option

  • Save jclement/7a02d8f1979a1c37085bd3ecc3fceb17 to your computer and use it in GitHub Desktop.

Select an option

Save jclement/7a02d8f1979a1c37085bd3ecc3fceb17 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
set -euo pipefail
# ══════════════════════════════════════════════════════════════════════════════
# megaclaude — Sandboxed Claude Code development environments
# ══════════════════════════════════════════════════════════════════════════════
IMAGE_NAME="megaclaude"
IMAGE_TAG="latest"
HOME_VOLUME="megaclaude-home"
# ── Colors & Style ───────────────────────────────────────────────────────────
if [[ -t 1 ]]; then
BOLD='\033[1m'
DIM='\033[2m'
RESET='\033[0m'
RED='\033[1;31m'
GREEN='\033[1;32m'
YELLOW='\033[1;33m'
BLUE='\033[1;34m'
MAGENTA='\033[1;35m'
CYAN='\033[1;36m'
WHITE='\033[1;37m'
GRAY='\033[0;37m'
else
BOLD='' DIM='' RESET='' RED='' GREEN='' YELLOW=''
BLUE='' MAGENTA='' CYAN='' WHITE='' GRAY=''
fi
# ── Pretty Printing ─────────────────────────────────────────────────────────
banner() {
echo ""
echo -e "${MAGENTA} ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓${RESET}"
echo -e "${MAGENTA} ┃${RESET} ${BOLD}${WHITE} ⚡ M E G A C L A U D E ⚡${RESET} ${MAGENTA}┃${RESET}"
echo -e "${MAGENTA} ┃${RESET} ${DIM}${GRAY} Sandboxed Claude Code environments${RESET} ${MAGENTA}┃${RESET}"
echo -e "${MAGENTA} ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛${RESET}"
echo ""
}
info() { echo -e " ${CYAN}▸${RESET} $*"; }
success() { echo -e " ${GREEN}✔${RESET} $*"; }
warn() { echo -e " ${YELLOW}⚠${RESET} $*"; }
error() { echo -e " ${RED}✖${RESET} $*"; }
step() { echo -e " ${BLUE}◆${RESET} ${BOLD}$*${RESET}"; }
# ── Usage ────────────────────────────────────────────────────────────────────
usage() {
banner
echo -e " ${BOLD}Usage:${RESET}"
echo -e " ${WHITE}megaclaude${RESET} ${CYAN}<project-dir>${RESET} ${DIM}[options]${RESET}"
echo ""
echo -e " ${BOLD}Options:${RESET}"
echo -e " ${CYAN}--rebuild${RESET} Full rebuild: image + volumes (nuclear option)"
echo -e " ${CYAN}--resume ${DIM}<id>${RESET} Resume a previous Claude session"
echo -e " ${CYAN}--shell${RESET} Drop into bash instead of launching Claude"
echo -e " ${CYAN}--usage${RESET} Show token usage & cost for all sessions"
echo -e " ${CYAN}--help${RESET} Show this help"
echo ""
echo -e " ${BOLD}Tmux shortcuts${RESET} ${DIM}(inside session):${RESET}"
echo -e " ${CYAN}ctrl+B c${RESET} New Claude Code worker"
echo -e " ${CYAN}ctrl+B C${RESET} New shell"
echo -e " ${CYAN}ctrl+B |${RESET} Split pane horizontal"
echo -e " ${CYAN}ctrl+B -${RESET} Split pane vertical"
echo -e " ${CYAN}ctrl+B n/p${RESET} Next/previous window"
echo ""
echo -e " ${BOLD}Examples:${RESET}"
echo -e " ${DIM}$ megaclaude ./my-project${RESET}"
echo -e " ${DIM}$ megaclaude /path/to/project --rebuild${RESET}"
echo -e " ${DIM}$ megaclaude ./my-project --resume abc123${RESET}"
echo -e " ${DIM}$ megaclaude ./my-project --shell${RESET}"
echo -e " ${DIM}$ megaclaude --usage${RESET}"
echo ""
echo -e " ${BOLD}Volumes:${RESET}"
echo -e " ${DIM}${HOME_VOLUME}${RESET} ${DIM}— persists claude, mise, and auth${RESET}"
echo -e " ${DIM}Authenticate once, reuse across all projects.${RESET}"
echo ""
exit 0
}
# ── Parse Args ───────────────────────────────────────────────────────────────
PROJECT_DIR=""
FORCE_REBUILD=false
SHELL_MODE=false
USAGE_MODE=false
RESUME_ID=""
EXPECT_RESUME=false
for arg in "$@"; do
if $EXPECT_RESUME; then
RESUME_ID="$arg"
EXPECT_RESUME=false
continue
fi
case "$arg" in
--rebuild) FORCE_REBUILD=true ;;
--shell) SHELL_MODE=true ;;
--usage) USAGE_MODE=true ;;
--resume) EXPECT_RESUME=true ;;
--help|-h) usage ;;
-*) error "Unknown option: ${arg}"; usage >&2; exit 1 ;;
*)
if [[ -z "$PROJECT_DIR" ]]; then
PROJECT_DIR="$arg"
else
error "Multiple project directories specified"
exit 1
fi
;;
esac
done
if [[ -z "$PROJECT_DIR" ]] && ! $USAGE_MODE; then
usage
fi
# ── Usage Mode (no project dir needed) ──────────────────────────────────────
if $USAGE_MODE; then
banner
if ! docker image inspect "${IMAGE_NAME}:${IMAGE_TAG}" &>/dev/null; then
error "No megaclaude image found — run a session first"
exit 1
fi
if ! docker volume inspect "$HOME_VOLUME" &>/dev/null; then
error "No home volume found — no sessions to report"
exit 1
fi
HOST_USER="$(id -un)"
docker run --rm \
-v "${HOME_VOLUME}:/home/${HOST_USER}:ro" \
-e "DEV_USER=${HOST_USER}" \
"${IMAGE_NAME}:${IMAGE_TAG}" \
--usage
exit $?
fi
# Resolve to absolute path
PROJECT_DIR="$(cd "$PROJECT_DIR" 2>/dev/null && pwd)" || {
error "Directory not found: ${PROJECT_DIR}"
exit 1
}
PROJECT_NAME="$(basename "$PROJECT_DIR")"
banner
# ── Preflight ────────────────────────────────────────────────────────────────
step "Preflight checks"
if ! command -v docker &>/dev/null; then
error "Docker is not installed or not in PATH"
exit 1
fi
if ! docker info &>/dev/null; then
error "Docker daemon is not running"
exit 1
fi
success "Docker is ready"
HOST_UID="$(id -u)"
HOST_GID="$(id -g)"
HOST_USER="$(id -un)"
info "Project: ${BOLD}${PROJECT_NAME}${RESET} ${DIM}→ /workspace${RESET}"
info "User: ${BOLD}${HOST_USER}${RESET} ${DIM}(uid=${HOST_UID} gid=${HOST_GID})${RESET}"
# ── Build Image ──────────────────────────────────────────────────────────────
image_exists() {
docker image inspect "${IMAGE_NAME}:${IMAGE_TAG}" &>/dev/null
}
build_image() {
step "Building Docker image"
echo ""
local build_dir
build_dir=$(mktemp -d)
cat > "${build_dir}/Dockerfile" <<'DOCKERFILE'
FROM ubuntu:24.04
ARG DEBIAN_FRONTEND=noninteractive
# ── System packages ──────────────────────────────────────────────────────
RUN apt-get update && apt-get install -y --no-install-recommends \
bc \
build-essential \
ca-certificates \
cmake \
curl \
file \
git \
gnupg \
jq \
less \
libssl-dev \
locales \
man-db \
openssh-client \
pkg-config \
python3 \
python3-pip \
python3-venv \
ripgrep \
software-properties-common \
sudo \
tmux \
tree \
unzip \
vim \
wget \
zip \
zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*
# ── Helix editor ────────────────────────────────────────────────────────
RUN HELIX_VERSION=$(curl -fsSL https://api.github.com/repos/helix-editor/helix/releases/latest | jq -r .tag_name) \
&& curl -fsSL "https://github.com/helix-editor/helix/releases/download/${HELIX_VERSION}/helix-${HELIX_VERSION}-x86_64-linux.tar.xz" \
-o /tmp/helix.tar.xz \
&& mkdir -p /opt/helix \
&& tar xf /tmp/helix.tar.xz -C /opt/helix --strip-components=1 \
&& ln -s /opt/helix/hx /usr/local/bin/hx \
&& rm /tmp/helix.tar.xz
# ── Locale ───────────────────────────────────────────────────────────────
RUN locale-gen en_US.UTF-8
ENV LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
# ── Workspace ────────────────────────────────────────────────────────────
RUN mkdir -p /workspace
# ── Entrypoint (runs as root, creates user, drops privileges) ────────────
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
WORKDIR /workspace
ENTRYPOINT ["/entrypoint.sh"]
DOCKERFILE
cat > "${build_dir}/entrypoint.sh" <<'ENTRYPOINT'
#!/usr/bin/env bash
set -e
DEV_UID="${DEV_UID:-1000}"
DEV_GID="${DEV_GID:-1000}"
DEV_USER="${DEV_USER:-dev}"
HOME_DIR="/home/$DEV_USER"
# ── Cost calculation ────────────────────────────────────────────────────────
# Model pricing per million tokens (input, cache-write, cache-read, output)
calc_cost() {
local jsonl_files=("$@")
if [[ ${#jsonl_files[@]} -eq 0 ]]; then
echo "0 0 0 0 0.00"
return
fi
# Extract per-model usage, output one line per model + a total line
cat "${jsonl_files[@]}" 2>/dev/null | jq -r '
select(.type == "assistant" and .message.usage != null) |
.message.model as $model |
.message.usage |
# Pricing per million tokens: [input, cache_write, cache_read, output]
(if ($model | test("opus")) then
[15, 18.75, 1.50, 75]
elif ($model | test("haiku")) then
[0.80, 1.00, 0.08, 4]
else
[3, 3.75, 0.30, 15]
end) as $prices |
# Normalize model name
(if ($model | test("opus")) then "opus"
elif ($model | test("haiku")) then "haiku"
else "sonnet" end) as $tier |
(.input_tokens // 0) as $in |
(.cache_creation_input_tokens // 0) as $cw |
(.cache_read_input_tokens // 0) as $cr |
(.output_tokens // 0) as $out |
{tier: $tier, in: $in, cw: $cw, cr: $cr, out: $out,
cost: (($in * $prices[0] + $cw * $prices[1] + $cr * $prices[2] + $out * $prices[3]) / 1000000)}
' 2>/dev/null | jq -sr '
group_by(.tier) |
map({
tier: .[0].tier,
in: (map(.in) | add // 0),
cw: (map(.cw) | add // 0),
cr: (map(.cr) | add // 0),
out: (map(.out) | add // 0),
cost: (map(.cost) | add // 0)
}) |
. as $models |
($models | map(.in) | add // 0) as $tin |
($models | map(.cw) | add // 0) as $tcw |
($models | map(.cr) | add // 0) as $tcr |
($models | map(.out) | add // 0) as $tout |
($models | map(.cost) | add // 0) as $tcost |
($models | map("\(.tier) \(.in) \(.cw) \(.cr) \(.out) \(.cost)") | join("\n")) +
"\ntotal \($tin) \($tcw) \($tcr) \($tout) \($tcost)"
' 2>/dev/null || echo "total 0 0 0 0 0.00"
}
format_tokens() {
local n=${1%%.*} # truncate to integer
n=${n:-0}
if [[ $n -ge 1000000 ]]; then
printf "%.1fM" "$(echo "$n / 1000000" | bc -l)"
elif [[ $n -ge 1000 ]]; then
printf "%.1fK" "$(echo "$n / 1000" | bc -l)"
else
echo "$n"
fi
}
print_cost_summary() {
local label="$1"
shift
local cost_output="$*"
# Parse model lines and total line
local models=() total_line=""
while IFS= read -r line; do
[[ -z "$line" ]] && continue
local tier="${line%% *}"
if [[ "$tier" == "total" ]]; then
total_line="$line"
else
models+=("$line")
fi
done <<< "$cost_output"
# Parse total
local t_in t_cw t_cr t_out t_cost
read -r _ t_in t_cw t_cr t_out t_cost <<< "$total_line"
t_in=${t_in%%.*}; t_in=${t_in//[^0-9]/}; t_in=${t_in:-0}
t_cw=${t_cw%%.*}; t_cw=${t_cw//[^0-9]/}; t_cw=${t_cw:-0}
t_cr=${t_cr%%.*}; t_cr=${t_cr//[^0-9]/}; t_cr=${t_cr:-0}
t_out=${t_out%%.*}; t_out=${t_out//[^0-9]/}; t_out=${t_out:-0}
t_cost=$(printf "%.2f" "${t_cost:-0}")
local t_total=$((t_in + t_cw + t_cr + t_out))
echo ""
echo -e " \033[1;37m$label\033[0m"
# Per-model breakdown
for mline in "${models[@]}"; do
local tier m_in m_cw m_cr m_out m_cost
read -r tier m_in m_cw m_cr m_out m_cost <<< "$mline"
m_in=${m_in%%.*}; m_in=${m_in//[^0-9]/}; m_in=${m_in:-0}
m_cw=${m_cw%%.*}; m_cw=${m_cw//[^0-9]/}; m_cw=${m_cw:-0}
m_cr=${m_cr%%.*}; m_cr=${m_cr//[^0-9]/}; m_cr=${m_cr:-0}
m_out=${m_out%%.*}; m_out=${m_out//[^0-9]/}; m_out=${m_out:-0}
m_cost=$(printf "%.2f" "${m_cost:-0}")
local m_total=$((m_in + m_cw + m_cr + m_out))
printf " \033[1;35m%-10s\033[0m \033[1;36m%10s\033[0m tokens \033[1;32m\$%s\033[0m\n" "$tier" "$(format_tokens $m_total)" "$m_cost"
done
# Show totals if more than one model
if [[ ${#models[@]} -gt 1 ]]; then
echo -e " \033[0;37m────────────────────────────────────\033[0m"
printf " \033[1;37m%-10s\033[0m \033[1;36m%10s\033[0m tokens \033[1;32m\$%s\033[0m\n" "total" "$(format_tokens $t_total)" "$t_cost"
fi
echo ""
}
# ── Usage mode (read-only, no user setup needed) ────────────────────────────
if [[ "${1:-}" == "--usage" ]]; then
CLAUDE_DIR="$HOME_DIR/.claude"
if [[ ! -d "$CLAUDE_DIR" ]]; then
echo -e "\033[1;31m ✖ No Claude data found\033[0m"
exit 1
fi
echo ""
echo -e "\033[1;35m ⚡ MEGACLAUDE USAGE REPORT\033[0m"
# Per-session breakdown
SESSION_COUNT=0
GRAND_INPUT=0 GRAND_CW=0 GRAND_CR=0 GRAND_OUT=0 GRAND_COST=0
ALL_MODEL_LINES=""
for session_file in "$CLAUDE_DIR"/projects/*/[0-9a-f]*.jsonl; do
[[ -f "$session_file" ]] || continue
SESSION_COUNT=$((SESSION_COUNT + 1))
session_id=$(basename "$session_file" .jsonl)
session_dir=$(dirname "$session_file")/${session_id}
# Gather main + subagent files
files=("$session_file")
if [[ -d "${session_dir}/subagents" ]]; then
for sf in "${session_dir}/subagents"/*.jsonl; do
[[ -f "$sf" ]] && files+=("$sf")
done
fi
cost_output="$(calc_cost "${files[@]}")"
# Extract total line for the per-session list
local total_line
total_line=$(echo "$cost_output" | grep "^total " || echo "total 0 0 0 0 0.00")
read -r _ s_in s_cw s_cr s_out s_cost <<< "$total_line"
# Get session timestamp & first user message
ts=$(jq -r 'select(.type == "user") | .timestamp' "$session_file" 2>/dev/null | head -1)
first_msg=$(jq -r 'select(.type == "user" and .message.role == "user") | .message.content' "$session_file" 2>/dev/null | head -c 60 | head -1)
[[ ${#first_msg} -ge 59 ]] && first_msg="${first_msg}..."
date_str="${ts:0:10} ${ts:11:5}"
s_cost_fmt=$(printf "%.2f" "${s_cost:-0}")
printf "\033[0;37m %s\033[0m \033[1;32m\$%-8s\033[0m \033[0;37m%s\033[0m\n" "$date_str" "$s_cost_fmt" "$first_msg"
GRAND_INPUT=$((GRAND_INPUT + ${s_in%%.*}))
GRAND_CW=$((GRAND_CW + ${s_cw%%.*}))
GRAND_CR=$((GRAND_CR + ${s_cr%%.*}))
GRAND_OUT=$((GRAND_OUT + ${s_out%%.*}))
GRAND_COST=$(echo "$GRAND_COST + ${s_cost:-0}" | bc -l)
# Accumulate per-model lines
ALL_MODEL_LINES+=$(echo "$cost_output" | grep -v "^total ")$'\n'
done
# Re-aggregate model lines across all sessions
GRAND_COST_OUTPUT=$(echo "$ALL_MODEL_LINES" | awk '
NF >= 6 { in_t[$1]+=$2; cw[$1]+=$3; cr[$1]+=$4; out[$1]+=$5; cost[$1]+=$6 }
END {
for (t in cost) printf "%s %d %d %d %d %.6f\n", t, in_t[t], cw[t], cr[t], out[t], cost[t]
tin=0; tcw=0; tcr=0; tout=0; tcost=0
for (t in cost) { tin+=in_t[t]; tcw+=cw[t]; tcr+=cr[t]; tout+=out[t]; tcost+=cost[t] }
printf "total %d %d %d %d %.6f\n", tin, tcw, tcr, tout, tcost
}')
echo ""
echo -e "\033[0;37m ── $SESSION_COUNT session(s) ──\033[0m"
print_cost_summary "All Sessions Total" "$GRAND_COST_OUTPUT"
exit 0
fi
# ── Create group & user matching host IDs ────────────────────────────────
# Remove any existing user/group that conflicts with the host IDs
# (e.g. ubuntu:24.04 ships a default 'ubuntu' user at UID 1000)
EXISTING_USER=$(getent passwd "$DEV_UID" | cut -d: -f1 || true)
if [[ -n "$EXISTING_USER" && "$EXISTING_USER" != "$DEV_USER" ]]; then
userdel "$EXISTING_USER" 2>/dev/null || true
fi
EXISTING_GROUP=$(getent group "$DEV_GID" | cut -d: -f1 || true)
if [[ -n "$EXISTING_GROUP" && "$EXISTING_GROUP" != "$DEV_USER" ]]; then
groupdel "$EXISTING_GROUP" 2>/dev/null || true
fi
# Lower UID/GID minimums for macOS compatibility (macOS UIDs start at 501)
sed -i 's/^UID_MIN.*/UID_MIN 500/' /etc/login.defs 2>/dev/null || true
sed -i 's/^GID_MIN.*/GID_MIN 500/' /etc/login.defs 2>/dev/null || true
if ! getent group "$DEV_GID" >/dev/null 2>&1; then
groupadd -g "$DEV_GID" "$DEV_USER"
fi
if ! id "$DEV_USER" >/dev/null 2>&1; then
useradd -M -u "$DEV_UID" -g "$DEV_GID" -s /bin/bash -d "$HOME_DIR" "$DEV_USER"
fi
echo "$DEV_USER ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/$DEV_USER"
chmod 440 "/etc/sudoers.d/$DEV_USER"
# ── Ensure home dir ownership (volume may be fresh) ──────────────────────
chown "$DEV_UID:$DEV_GID" "$HOME_DIR"
# ── Bootstrap Claude Code + mise (first run only, cached in volume) ──────
CLAUDE_BIN="$HOME_DIR/.local/bin/claude"
MISE_BIN="$HOME_DIR/.local/bin/mise"
run_as_user() { su - "$DEV_USER" -c "$*"; }
if [[ ! -x "$CLAUDE_BIN" ]]; then
echo ""
echo -e "\033[1;35m⚡\033[0m \033[1mFirst run — bootstrapping tools\033[0m"
echo -e "\033[0;37m Installing Claude Code + mise (~30s)\033[0m"
echo -e "\033[0;37m This is cached in a Docker volume for future runs.\033[0m"
echo ""
echo -e "\033[1;36m [1/2]\033[0m Installing Claude Code..."
run_as_user 'curl -fsSL https://claude.ai/install.sh | bash' 2>&1
echo -e "\033[1;36m [2/2]\033[0m Installing mise..."
run_as_user 'curl -fsSL https://mise.run | sh' 2>&1 | tail -1
echo ""
echo -e "\033[1;32m ✔ Bootstrap complete!\033[0m"
echo ""
fi
# ── Ensure claude + mise in bashrc ───────────────────────────────────────
BASHRC="$HOME_DIR/.bashrc"
MEGACLAUDE_MARKER='# --- megaclaude-env ---'
if ! grep -qF "$MEGACLAUDE_MARKER" "$BASHRC" 2>/dev/null; then
{
echo "$MEGACLAUDE_MARKER"
echo 'export PATH="$HOME/.local/bin:$PATH"'
echo 'eval "$($HOME/.local/bin/mise activate bash)"'
} >> "$BASHRC"
chown "$DEV_UID:$DEV_GID" "$BASHRC"
fi
# ── Fix workspace ownership ─────────────────────────────────────────────
chown "$DEV_UID:$DEV_GID" /workspace
# ── Install project tools if mise config present ─────────────────────────
cd /workspace
SETUP_CMD=""
if [[ -f .mise.toml ]] || [[ -f .tool-versions ]] || [[ -f mise.toml ]]; then
SETUP_CMD="$MISE_BIN trust 2>/dev/null; echo -e '\033[1;35m▸\033[0m Installing project tools...'; $MISE_BIN install --yes 2>/dev/null; "
fi
if [[ -n "$SETUP_CMD" ]]; then
su - "$DEV_USER" -c "cd /workspace && ${SETUP_CMD}true"
fi
# ── Drop privileges and launch ───────────────────────────────────────────
export HOME="$HOME_DIR" USER="$DEV_USER" LOGNAME="$DEV_USER"
# ── Parse entrypoint args ───────────────────────────────────────────────
SHELL_MODE=false
RESUME_ID=""
for arg in "$@"; do
case "$arg" in
--shell) SHELL_MODE=true ;;
--resume) :;; # next arg is the ID
*)
# Check if previous arg was --resume
if [[ "${PREV_ARG:-}" == "--resume" ]]; then
RESUME_ID="$arg"
fi
;;
esac
PREV_ARG="$arg"
done
# Also accept MEGA_RESUME env var
RESUME_ID="${RESUME_ID:-${MEGA_RESUME:-}}"
if $SHELL_MODE; then
exec setpriv --reuid="$DEV_UID" --regid="$DEV_GID" --init-groups bash --login
else
# ── Write CLAUDE.md with mission context (read automatically by Claude) ──
if [[ -n "${MEGA_MISSION:-}" ]]; then
mkdir -p "$HOME_DIR/.claude"
cat > "$HOME_DIR/.claude/CLAUDE.md" <<MISSION_EOF
# MEGACLAUDE SESSION
You are running in a fully sandboxed Docker container with --dangerously-skip-permissions.
You have complete freedom to read, write, execute, and install anything — nothing can break.
Be bold and autonomous. Do not ask for permission or confirmation — just do the work.
If you need to install packages, do it. If you need to create files, do it.
If you need to run tests, run them. If something fails, fix it and retry.
Explore the codebase first to understand what you're working with, then execute completely.
## Your Mission
${MEGA_MISSION}
---
Work autonomously until the mission is fully complete. Show your work as you go.
MISSION_EOF
chown -R "$DEV_UID:$DEV_GID" "$HOME_DIR/.claude"
fi
# ── Build claude launch command ───────────────────────────────────────
CLAUDE_ARGS="--dangerously-skip-permissions"
if [[ -n "$RESUME_ID" ]]; then
CLAUDE_ARGS="$CLAUDE_ARGS --resume $RESUME_ID"
fi
echo -e "\033[1;35m▸\033[0m Launching Claude Code \033[0;37m(skip-permissions)\033[0m"
if [[ -n "$RESUME_ID" ]]; then
echo -e "\033[1;35m▸\033[0m Resuming session \033[1;36m${RESUME_ID}\033[0m"
fi
echo ""
# Create a timestamp marker so we can find files from this session
CLAUDE_DIR="$HOME_DIR/.claude"
TIMESTAMP_MARKER=$(mktemp -p "${CLAUDE_DIR}" .ts_marker.XXXXXX 2>/dev/null || mktemp)
chown "$DEV_UID:$DEV_GID" "$TIMESTAMP_MARKER"
# ── Write tmux config ───────────────────────────────────────────────────
TMUX_CONF="$HOME_DIR/.tmux.conf"
cat > "$TMUX_CONF" <<'TMUX_EOF'
set -g default-terminal "screen-256color"
set -ga terminal-overrides ",*256col*:Tc"
set -g mouse on
set -g base-index 1
setw -g pane-base-index 1
set -g renumber-windows on
set -sg escape-time 0
set -g status-interval 1
set -g status-style bg=colour235,fg=colour255
set -g status-left-length 50
set -g status-left "#[fg=colour232,bg=colour39,bold] #S #[fg=colour39,bg=colour235,nobold]"
set -g status-right-length 80
set -g status-right "#[fg=colour245,bg=colour235] ^b c:claude ^b C:shell #[fg=colour240,bg=colour235]#[fg=colour250,bg=colour240] %Y-%m-%d #[fg=colour252]#[fg=colour16,bg=colour252,bold] %H:%M:%S "
setw -g window-status-format "#[fg=colour245,bg=colour235] #I #[fg=colour245,bg=colour235] #W "
setw -g window-status-current-format "#[fg=colour235,bg=colour39]#[fg=colour232,bg=colour39,bold] #I #W #[fg=colour39,bg=colour235,nobold]"
set -g pane-border-style fg=colour240
set -g pane-active-border-style fg=colour39,bg=default
set -g pane-border-status top
set -g pane-border-format "#[fg=colour240,bg=colour235] #P: #T "
setw -g pane-active-border-style fg=colour39,bold
setw -g window-active-style bg=terminal
setw -g window-style bg=colour234
set -g display-panes-colour colour240
set -g display-panes-active-colour colour39
set -g message-style bg=colour235,fg=colour39,bold
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
unbind '"'
unbind %
# ctrl+B c = new Claude Code instance
bind c new-window -n "claude" -c /workspace "$HOME/.local/bin/claude --dangerously-skip-permissions"
# ctrl+B C = new shell
bind C new-window -n "shell" -c /workspace
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5
bind r source-file ~/.tmux.conf \; display "Reloaded!"
setw -g mode-keys vi
bind -T copy-mode-vi v send -X begin-selection
bind -T copy-mode-vi y send -X copy-selection-and-cancel
TMUX_EOF
chown "$DEV_UID:$DEV_GID" "$TMUX_CONF"
# ── Launch inside tmux ──────────────────────────────────────────────────
# Run claude inside tmux so ctrl+B c spawns additional workers
set +e
setpriv --reuid="$DEV_UID" --regid="$DEV_GID" --init-groups \
tmux new-session -s megaclaude -n claude \
"cd /workspace && $CLAUDE_BIN $CLAUDE_ARGS"
CLAUDE_EXIT=$?
set -e
# ── Session cost summary ────────────────────────────────────────────────
# Find conversation JSONL files created/modified during this session
# (UUID-named files only, excludes history.jsonl etc.)
SESSION_FILES=()
while IFS= read -r f; do
[[ -n "$f" ]] && SESSION_FILES+=("$f")
done < <(find "$CLAUDE_DIR" -name "*.jsonl" -newer "$TIMESTAMP_MARKER" \
-regex '.*/[0-9a-f].*\.jsonl' 2>/dev/null)
rm -f "$TIMESTAMP_MARKER"
if [[ ${#SESSION_FILES[@]} -gt 0 ]]; then
cost_output="$(calc_cost "${SESSION_FILES[@]}")"
total_cost=$(echo "$cost_output" | grep "^total " | awk '{print $6}')
total_cost=$(printf "%.2f" "${total_cost:-0}")
if [[ "$total_cost" != "0.00" && "$total_cost" != "0" ]]; then
print_cost_summary "Session Cost" "$cost_output"
fi
fi
exit $CLAUDE_EXIT
fi
ENTRYPOINT
docker build -t "${IMAGE_NAME}:${IMAGE_TAG}" "$build_dir" 2>&1 | while IFS= read -r line; do
if [[ "$line" =~ ^Step ]]; then
echo -e " ${CYAN}${line}${RESET}"
elif [[ "$line" =~ ^"Successfully" ]]; then
echo -e " ${GREEN}${line}${RESET}"
elif [[ "$line" =~ ^"#" ]]; then
echo -e " ${DIM}${line}${RESET}"
fi
done
rm -rf "$build_dir"
if image_exists; then
echo ""
success "Image built successfully"
else
echo ""
error "Image build failed"
exit 1
fi
}
echo ""
if $FORCE_REBUILD; then
step "Full rebuild"
# Nuke everything
if docker volume inspect "$HOME_VOLUME" &>/dev/null; then
docker volume rm "$HOME_VOLUME" >/dev/null 2>&1 || true
warn "Removed home volume"
fi
if image_exists; then
docker rmi "${IMAGE_NAME}:${IMAGE_TAG}" >/dev/null 2>&1 || true
warn "Removed old image"
fi
build_image
elif ! image_exists; then
info "Image not found — building for the first time"
build_image
else
success "Image ${BOLD}${IMAGE_NAME}:${IMAGE_TAG}${RESET} ready"
fi
# ── Handle home volume ───────────────────────────────────────────────────────
echo ""
step "Home volume"
FIRST_RUN=false
if ! docker volume inspect "$HOME_VOLUME" &>/dev/null; then
docker volume create "$HOME_VOLUME" >/dev/null
info "Created ${BOLD}${HOME_VOLUME}${RESET}"
FIRST_RUN=true
else
success "Volume ready ${DIM}(claude, mise cached)${RESET}"
fi
if $FIRST_RUN; then
warn "First run — tools will install on startup (~30s)"
warn "You'll need to authenticate Claude Code once"
fi
# ── Gather Mission ──────────────────────────────────────────────────────────
MISSION=""
if ! $SHELL_MODE && [[ -z "$RESUME_ID" ]]; then
echo ""
step "Mission briefing"
echo ""
echo -e " ${DIM}What should Claude work on? Describe the task, goal, or${RESET}"
echo -e " ${DIM}feature you want built. Be as specific as you like.${RESET}"
echo -e " ${DIM}(Empty = interactive mode, no initial prompt)${RESET}"
echo ""
echo -e " ${BOLD}${WHITE}Mission:${RESET}"
echo -ne " ${CYAN}▸${RESET} "
IFS= read -r MISSION
if [[ -n "$MISSION" ]]; then
echo ""
echo -e " ${DIM}Any constraints, preferences, or context? (optional)${RESET}"
echo -ne " ${CYAN}▸${RESET} "
IFS= read -r CONTEXT
if [[ -n "$CONTEXT" ]]; then
MISSION="${MISSION}
Additional context: ${CONTEXT}"
fi
fi
fi
# ── Launch Container ─────────────────────────────────────────────────────────
echo ""
step "Launching"
CONTAINER_NAME="megaclaude-${PROJECT_NAME}-$$"
EXTRA_ARGS=""
if $SHELL_MODE; then
EXTRA_ARGS="--shell"
info "Mode: ${YELLOW}shell${RESET}"
elif [[ -n "$RESUME_ID" ]]; then
info "Mode: ${MAGENTA}claude --dangerously-skip-permissions --resume${RESET}"
info "Resume: ${CYAN}${RESUME_ID}${RESET}"
elif [[ -n "$MISSION" ]]; then
info "Mode: ${MAGENTA}claude --dangerously-skip-permissions${RESET} ${DIM}(mission loaded)${RESET}"
else
info "Mode: ${MAGENTA}claude --dangerously-skip-permissions${RESET}"
fi
info "Project: ${BOLD}${PROJECT_DIR}${RESET} ${DIM}→ /workspace${RESET}"
echo ""
echo -e " ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo ""
docker run -it --rm \
--name "$CONTAINER_NAME" \
-v "${PROJECT_DIR}:/workspace" \
-v "${HOME_VOLUME}:/home/${HOST_USER}" \
-e "DEV_UID=${HOST_UID}" \
-e "DEV_GID=${HOST_GID}" \
-e "DEV_USER=${HOST_USER}" \
-e MEGA_MISSION="$MISSION" \
-e "MEGA_RESUME=${RESUME_ID}" \
-e "TERM=${TERM:-xterm-256color}" \
-e "LANG=en_US.UTF-8" \
--hostname "megaclaude" \
"${IMAGE_NAME}:${IMAGE_TAG}" \
${EXTRA_ARGS:+"$EXTRA_ARGS"}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment