removed path, moved init to skeleton folder
This commit is contained in:
12
README.md
12
README.md
@ -28,13 +28,15 @@ safeclaude # launch Claude in this project's container
|
|||||||
|
|
||||||
| Command | What it does |
|
| Command | What it does |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `safeclaude [PATH] [claude-args...]` | Launch Claude for a project (default: current dir). Anything extra is passed straight to `claude`. |
|
| `safeclaude [claude-args...]` | Launch Claude for the project here. Anything extra is passed straight to `claude`. |
|
||||||
| `safeclaude build [PATH]` | Rebuild the project's container from scratch. |
|
| `safeclaude build` | Rebuild the project's container from scratch. |
|
||||||
| `safeclaude init [PATH]` | Create a starter `.safeclaude/` folder. |
|
| `safeclaude init` | Create a starter `.safeclaude/` in the current directory. |
|
||||||
| `safeclaude envs` | List the containers and stored data safeclaude has created. |
|
| `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,
|
safeclaude always works on the project you're in — the current directory, or the
|
||||||
so you can launch from a subdirectory and it'll still find the right one.
|
nearest parent that has a `.safeclaude/` folder, so launching from a
|
||||||
|
subdirectory still finds the right one.
|
||||||
|
|
||||||
## What goes in `.safeclaude/`
|
## What goes in `.safeclaude/`
|
||||||
|
|
||||||
|
|||||||
116
safeclaude
116
safeclaude
@ -7,6 +7,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
SAFECLAUDE_VERSION="0.1.0"
|
SAFECLAUDE_VERSION="0.1.0"
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
|
||||||
|
SKELETON_DIR="$SCRIPT_DIR/skeleton"
|
||||||
BASE_IMAGE="safeclaude-base:latest"
|
BASE_IMAGE="safeclaude-base:latest"
|
||||||
|
|
||||||
die() { echo "safeclaude: $*" >&2; exit 1; }
|
die() { echo "safeclaude: $*" >&2; exit 1; }
|
||||||
@ -16,14 +17,15 @@ usage() {
|
|||||||
safeclaude — run Claude in a locked-down container, one per project
|
safeclaude — run Claude in a locked-down container, one per project
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
safeclaude [PATH] [claude-args...] Launch Claude for a project (default: current dir)
|
safeclaude [claude-args...] Launch Claude for the project here (args pass to claude)
|
||||||
safeclaude build [PATH] Rebuild the project's container from scratch
|
safeclaude build Rebuild the project's container from scratch
|
||||||
safeclaude init [PATH] Create a starter .safeclaude/ folder
|
safeclaude init Create a starter .safeclaude/ in the current directory
|
||||||
safeclaude envs List the containers and saved data safeclaude made
|
safeclaude envs List the containers and saved data safeclaude made
|
||||||
safeclaude version Print the safeclaude version
|
safeclaude version Print the safeclaude version
|
||||||
safeclaude help Show this help
|
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
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,13 +76,9 @@ ensure_project_image() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd_run() {
|
cmd_run() {
|
||||||
local target="${1:-.}"; [ $# -ge 1 ] && shift || true
|
# Always operate on the current directory's project; every argument is for claude.
|
||||||
# If the first argument isn't a folder, it was meant for claude — so hand it
|
local proj; proj="$(find_project_root ".")" \
|
||||||
# back to claude and use the current directory as the project.
|
|| die "no .safeclaude/ found here or in any parent — run: safeclaude init"
|
||||||
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"
|
|
||||||
|
|
||||||
ensure_base
|
ensure_base
|
||||||
local image; image="$(ensure_project_image "$proj")"
|
local image; image="$(ensure_project_image "$proj")"
|
||||||
@ -116,8 +114,8 @@ cmd_run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd_build() {
|
cmd_build() {
|
||||||
local proj; proj="$(find_project_root "${1:-.}")" \
|
local proj; proj="$(find_project_root ".")" \
|
||||||
|| die "no .safeclaude/ found in '${1:-.}' or any parent — run: safeclaude init"
|
|| die "no .safeclaude/ found here or in any parent — run: safeclaude init"
|
||||||
ensure_base
|
ensure_base
|
||||||
ensure_project_image "$proj" 1 >/dev/null
|
ensure_project_image "$proj" 1 >/dev/null
|
||||||
echo "[safeclaude] env ready for $(proj_name "$proj")"
|
echo "[safeclaude] env ready for $(proj_name "$proj")"
|
||||||
@ -133,97 +131,33 @@ cmd_envs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd_init() {
|
cmd_init() {
|
||||||
local target="${1:-.}"
|
local sc; sc="$(pwd)/.safeclaude"
|
||||||
[ -d "$target" ] || die "not a directory: $target"
|
|
||||||
local sc; sc="$(cd "$target" && pwd)/.safeclaude"
|
|
||||||
[ -e "$sc" ] && die ".safeclaude/ already exists at $sc"
|
[ -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"
|
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 "[safeclaude] created $sc"
|
||||||
echo " - edit Dockerfile to add the packages and language versions you need"
|
echo " - edit Dockerfile to add the packages and language versions you need"
|
||||||
echo " - edit hooks/10-setup.sh for any startup setup"
|
echo " - edit hooks/10-setup.sh for any startup setup"
|
||||||
echo " - then run: safeclaude $target"
|
echo " - then run: safeclaude"
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
local cmd="${1:-run}"
|
local cmd="${1:-run}"
|
||||||
case "$cmd" in
|
case "$cmd" in
|
||||||
build) shift; cmd_build "${1:-.}" ;;
|
build) cmd_build ;;
|
||||||
init) shift; cmd_init "${1:-.}" ;;
|
init) cmd_init ;;
|
||||||
envs|ls|list) cmd_envs ;;
|
envs|ls|list) cmd_envs ;;
|
||||||
version|-v|--version) echo "safeclaude $SAFECLAUDE_VERSION" ;;
|
version|-v|--version) echo "safeclaude $SAFECLAUDE_VERSION" ;;
|
||||||
help|-h|--help) usage ;;
|
help|-h|--help) usage ;;
|
||||||
run) shift; cmd_run "$@" ;;
|
run) shift; cmd_run "$@" ;;
|
||||||
*) cmd_run "$@" ;; # default: PATH and/or claude args
|
*) cmd_run "$@" ;; # default: all args go to claude
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4
skeleton/.env.example
Normal file
4
skeleton/.env.example
Normal file
@ -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
|
||||||
2
skeleton/.gitignore
vendored
Normal file
2
skeleton/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.env
|
||||||
|
cache/
|
||||||
16
skeleton/Dockerfile
Normal file
16
skeleton/Dockerfile
Normal file
@ -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.
|
||||||
17
skeleton/hooks/10-setup.sh
Executable file
17
skeleton/hooks/10-setup.sh
Executable file
@ -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
|
||||||
12
skeleton/hooks/README.md
Normal file
12
skeleton/hooks/README.md
Normal file
@ -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.
|
||||||
Reference in New Issue
Block a user