From 4e7b2e02885922a29cc2288cf5ba33baa0962a14 Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 20 Jun 2026 11:07:48 -0400 Subject: [PATCH] removed path, moved init to skeleton folder --- README.md | 12 ++-- safeclaude | 116 ++++++++----------------------------- skeleton/.env.example | 4 ++ skeleton/.gitignore | 2 + skeleton/Dockerfile | 16 +++++ skeleton/hooks/10-setup.sh | 17 ++++++ skeleton/hooks/README.md | 12 ++++ 7 files changed, 83 insertions(+), 96 deletions(-) create mode 100644 skeleton/.env.example create mode 100644 skeleton/.gitignore create mode 100644 skeleton/Dockerfile create mode 100755 skeleton/hooks/10-setup.sh create mode 100644 skeleton/hooks/README.md diff --git a/README.md b/README.md index 08a47a9..879c4ca 100644 --- a/README.md +++ b/README.md @@ -28,13 +28,15 @@ safeclaude # launch Claude in this project's container | Command | What it does | | --- | --- | -| `safeclaude [PATH] [claude-args...]` | Launch Claude for a project (default: current dir). Anything extra is passed straight to `claude`. | -| `safeclaude build [PATH]` | Rebuild the project's container from scratch. | -| `safeclaude init [PATH]` | Create a starter `.safeclaude/` folder. | +| `safeclaude [claude-args...]` | Launch Claude for the project here. Anything extra is passed straight to `claude`. | +| `safeclaude build` | Rebuild the project's container from scratch. | +| `safeclaude init` | Create a starter `.safeclaude/` in the current directory. | | `safeclaude envs` | List the containers and stored data safeclaude has created. | +| `safeclaude version` | Print the safeclaude version. | -A "project" is any folder (or a parent of it) that has a `.safeclaude/` folder, -so you can launch from a subdirectory and it'll still find the right one. +safeclaude always works on the project you're in — the current directory, or the +nearest parent that has a `.safeclaude/` folder, so launching from a +subdirectory still finds the right one. ## What goes in `.safeclaude/` diff --git a/safeclaude b/safeclaude index 26c298d..c45adb3 100755 --- a/safeclaude +++ b/safeclaude @@ -7,6 +7,7 @@ set -euo pipefail SAFECLAUDE_VERSION="0.1.0" SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)" +SKELETON_DIR="$SCRIPT_DIR/skeleton" BASE_IMAGE="safeclaude-base:latest" die() { echo "safeclaude: $*" >&2; exit 1; } @@ -16,14 +17,15 @@ usage() { safeclaude — run Claude in a locked-down container, one per project Usage: - safeclaude [PATH] [claude-args...] Launch Claude for a project (default: current dir) - safeclaude build [PATH] Rebuild the project's container from scratch - safeclaude init [PATH] Create a starter .safeclaude/ folder - safeclaude envs List the containers and saved data safeclaude made - safeclaude version Print the safeclaude version - safeclaude help Show this help + safeclaude [claude-args...] Launch Claude for the project here (args pass to claude) + safeclaude build Rebuild the project's container from scratch + safeclaude init Create a starter .safeclaude/ in the current directory + safeclaude envs List the containers and saved data safeclaude made + safeclaude version Print the safeclaude version + safeclaude help Show this help -A project is any folder (or a parent of it) that has a .safeclaude/ folder. +Run from inside a project — the current directory, or the nearest parent that +has a .safeclaude/ folder. EOF } @@ -74,13 +76,9 @@ ensure_project_image() { } cmd_run() { - local target="${1:-.}"; [ $# -ge 1 ] && shift || true - # If the first argument isn't a folder, it was meant for claude — so hand it - # back to claude and use the current directory as the project. - if [ ! -d "$target" ]; then set -- "$target" "$@"; target="."; fi - - local proj; proj="$(find_project_root "$target")" \ - || die "no .safeclaude/ found in '$target' or any parent — run: safeclaude init" + # Always operate on the current directory's project; every argument is for claude. + local proj; proj="$(find_project_root ".")" \ + || die "no .safeclaude/ found here or in any parent — run: safeclaude init" ensure_base local image; image="$(ensure_project_image "$proj")" @@ -116,8 +114,8 @@ cmd_run() { } cmd_build() { - local proj; proj="$(find_project_root "${1:-.}")" \ - || die "no .safeclaude/ found in '${1:-.}' or any parent — run: safeclaude init" + local proj; proj="$(find_project_root ".")" \ + || die "no .safeclaude/ found here or in any parent — run: safeclaude init" ensure_base ensure_project_image "$proj" 1 >/dev/null echo "[safeclaude] env ready for $(proj_name "$proj")" @@ -133,97 +131,33 @@ cmd_envs() { } cmd_init() { - local target="${1:-.}" - [ -d "$target" ] || die "not a directory: $target" - local sc; sc="$(cd "$target" && pwd)/.safeclaude" + local sc; sc="$(pwd)/.safeclaude" [ -e "$sc" ] && die ".safeclaude/ already exists at $sc" - mkdir -p "$sc/hooks" "$sc/cache" + [ -d "$SKELETON_DIR" ] || die "skeleton not found at $SKELETON_DIR" - # Record which safeclaude version created this, for future reference. + # Copy the template files (see skeleton/), then add the bits that are generated + # rather than templated: the cache dir and the version stamp. + mkdir -p "$sc" + cp -R "$SKELETON_DIR/." "$sc/" + mkdir -p "$sc/cache" echo "$SAFECLAUDE_VERSION" > "$sc/version" - cat > "$sc/Dockerfile" <<'EOF' -# safeclaude builds this on top of its shared base. Everything here happens once -# and is cached, so it won't slow down your day-to-day launches. -FROM safeclaude-base:latest - -# Add the system packages and language versions your project needs below. -# (You're root during the build, so apt just works.) -# -# Example — Ruby + Postgres client + headless Chrome (see the repo's example/): -# -# RUN apt-get update && apt-get install -y --no-install-recommends \ -# build-essential libssl-dev libreadline-dev zlib1g-dev \ -# libffi-dev libyaml-dev libpq-dev socat chromium chromium-driver -# -# Tip: pin specific language versions here (install one Ruby, one Node, etc.) -# rather than a version manager — a project only needs one. See the repo's -# example/ for how. -EOF - - cat > "$sc/hooks/README.md" <<'EOF' -# Hooks - -Each `*.sh` file here runs when the container starts, with your project available -at `/code`. They run in name order, so prefix them with numbers (`10-`, `20-`) -to control the sequence. - -Because they run every launch, keep them quick and make them safe to re-run — -check whether the work is already done before doing it. If one fails, startup -stops rather than continuing half-configured. - -Edits take effect on the next launch (no rebuild needed). Rename a -`*.sh.example` file to `*.sh` to turn it on. -EOF - - cat > "$sc/hooks/10-setup.sh" <<'EOF' -#!/bin/bash -# Runs every time the container starts, with your project at /code. Put your -# project's startup setup here — installing dependencies, preparing services, -# and so on. It's empty by default; add what you need. -# -# Keep it safe to run every launch: check whether something is already done -# before doing it, so repeat launches stay quick. -# -# Need to keep things between runs? Write them to /code/.safeclaude/cache. That -# folder lives on the host (not inside the container), so it survives rebuilds -# and `docker volume` resets, and it's gitignored so it won't land in your repo. -# It's a good home for installed dependencies, downloads, or "already did this" -# markers. -# -# For example, you might install gems into the cache, run `npm install`, or wait -# for a service to come up. See the repo's example/ for a worked version. -set -euo pipefail -EOF - - cat > "$sc/.env.example" <<'EOF' -# Put secrets here, then copy this file to .env (which stays out of git). -# Anything in .env is handed to the container when it starts — for example, a -# token for a private gem source: -# BUNDLE_GEMS__GRAPHQL__PRO=user:token -EOF - - cat > "$sc/.gitignore" <<'EOF' -.env -cache/ -EOF - echo "[safeclaude] created $sc" echo " - edit Dockerfile to add the packages and language versions you need" echo " - edit hooks/10-setup.sh for any startup setup" - echo " - then run: safeclaude $target" + echo " - then run: safeclaude" } main() { local cmd="${1:-run}" case "$cmd" in - build) shift; cmd_build "${1:-.}" ;; - init) shift; cmd_init "${1:-.}" ;; + build) cmd_build ;; + init) cmd_init ;; envs|ls|list) cmd_envs ;; version|-v|--version) echo "safeclaude $SAFECLAUDE_VERSION" ;; help|-h|--help) usage ;; run) shift; cmd_run "$@" ;; - *) cmd_run "$@" ;; # default: PATH and/or claude args + *) cmd_run "$@" ;; # default: all args go to claude esac } diff --git a/skeleton/.env.example b/skeleton/.env.example new file mode 100644 index 0000000..063145d --- /dev/null +++ b/skeleton/.env.example @@ -0,0 +1,4 @@ +# Put secrets here, then copy this file to .env (which stays out of git). +# Anything in .env is handed to the container when it starts — for example, a +# token for a private gem source: +# BUNDLE_GEMS__GRAPHQL__PRO=user:token diff --git a/skeleton/.gitignore b/skeleton/.gitignore new file mode 100644 index 0000000..dd7d4ab --- /dev/null +++ b/skeleton/.gitignore @@ -0,0 +1,2 @@ +.env +cache/ diff --git a/skeleton/Dockerfile b/skeleton/Dockerfile new file mode 100644 index 0000000..bf6acc9 --- /dev/null +++ b/skeleton/Dockerfile @@ -0,0 +1,16 @@ +# safeclaude builds this on top of its shared base. Everything here happens once +# and is cached, so it won't slow down your day-to-day launches. +FROM safeclaude-base:latest + +# Add the system packages and language versions your project needs below. +# (You're root during the build, so apt just works.) +# +# Example — Ruby + Postgres client + headless Chrome (see the repo's example/): +# +# RUN apt-get update && apt-get install -y --no-install-recommends \ +# build-essential libssl-dev libreadline-dev zlib1g-dev \ +# libffi-dev libyaml-dev libpq-dev socat chromium chromium-driver +# +# Tip: pin specific language versions here (install one Ruby, one Node, etc.) +# rather than a version manager — a project only needs one. See the repo's +# example/ for how. diff --git a/skeleton/hooks/10-setup.sh b/skeleton/hooks/10-setup.sh new file mode 100755 index 0000000..eafd3a5 --- /dev/null +++ b/skeleton/hooks/10-setup.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Runs every time the container starts, with your project at /code. Put your +# project's startup setup here — installing dependencies, preparing services, +# and so on. It's empty by default; add what you need. +# +# Keep it safe to run every launch: check whether something is already done +# before doing it, so repeat launches stay quick. +# +# Need to keep things between runs? Write them to /code/.safeclaude/cache. That +# folder lives on the host (not inside the container), so it survives rebuilds +# and `docker volume` resets, and it's gitignored so it won't land in your repo. +# It's a good home for installed dependencies, downloads, or "already did this" +# markers. +# +# For example, you might install gems into the cache, run `npm install`, or wait +# for a service to come up. See the repo's example/ for a worked version. +set -euo pipefail diff --git a/skeleton/hooks/README.md b/skeleton/hooks/README.md new file mode 100644 index 0000000..e513e9c --- /dev/null +++ b/skeleton/hooks/README.md @@ -0,0 +1,12 @@ +# Hooks + +Each `*.sh` file here runs when the container starts, with your project available +at `/code`. They run in name order, so prefix them with numbers (`10-`, `20-`) +to control the sequence. + +Because they run every launch, keep them quick and make them safe to re-run — +check whether the work is already done before doing it. If one fails, startup +stops rather than continuing half-configured. + +Edits take effect on the next launch (no rebuild needed). Rename a +`*.sh.example` file to `*.sh` to turn it on.