Pass a custom template and mixin kits through to sbx

Sandboxes ignore the host ~/.claude by design: the agent runs as a separate
user with HOME elsewhere, so even a read-only mount is not picked up. Skills
can be shared with sbx skills import, but plugins carry commands, hooks and
MCP servers that only a custom image can deliver.

Adds --template, --stock-template and a repeatable --kit, persisted per
repository so run and refresh reuse them. AI_SBX_TEMPLATE supplies the default
image, so one custom template can be declared once in the user's mise config
and apply to every repository, with --template overriding it per repository
and --stock-template opting out.

save_config now packs two arrays into one argument list separated by a count,
so it ships with a round-trip test covering empty arrays, values containing
spaces, and the boundary between kits and AWS profiles.
This commit is contained in:
2026-07-30 16:29:25 -05:00
parent 4af8c1c153
commit ace4e81f97
3 changed files with 184 additions and 3 deletions
+69 -3
View File
@@ -6,6 +6,7 @@ CONFIG_ROOT="${XDG_CONFIG_HOME:-$HOME/.config}/ai-sbx"
DEFAULT_AGENT="${AI_SBX_AGENT:-codex}"
DEFAULT_MODE="${AI_SBX_MODE:-clone}"
DEFAULT_TOKEN_DAYS="${AI_SBX_TOKEN_DAYS:-30}"
DEFAULT_TEMPLATE="${AI_SBX_TEMPLATE:-}"
# GitHub accepts these as query parameters on the token creation form. A write
# level implies read, so only the highest level is listed. "workflows" is
@@ -43,6 +44,9 @@ Usage:
Setup opens a pre-filled GitHub token form in your browser. Use "token" on
its own to replace an expired or revoked token later.
Set AI_SBX_TEMPLATE in your mise config to reuse one custom image across
every repository without repeating --template.
Setup options:
--aws-profile NAME Host AWS profile to expose inside the sandbox.
May be supplied more than once. A trailing
@@ -54,6 +58,12 @@ Setup options:
of the repository, mounted read-only.
Its commits reach the host through the
sandbox-<name> git remote. This is the default.
--template REF Custom sandbox image. Defaults to
AI_SBX_TEMPLATE when set.
--stock-template Ignore AI_SBX_TEMPLATE and use the agent's
stock image.
--kit PATH Mixin kit to apply. May be supplied more
than once.
--replace Replace the existing sandbox.
Examples:
@@ -292,13 +302,22 @@ load_config() {
declare -p CONFIG_AWS_PROFILES >/dev/null 2>&1 ||
CONFIG_AWS_PROFILES=()
declare -p CONFIG_KITS >/dev/null 2>&1 ||
CONFIG_KITS=()
CONFIG_TEMPLATE="${CONFIG_TEMPLATE:-}"
}
save_config() {
local agent="$1"
local mode="$2"
shift 2
local -a profiles=("$@")
local template="$3"
local kit_count="$4"
shift 4
local -a kits=("${@:1:kit_count}")
local -a profiles=("${@:kit_count + 1}")
mkdir -p "$REPO_CONFIG_DIR"
chmod 700 "$CONFIG_ROOT" "$CONFIG_ROOT/repos" "$REPO_CONFIG_DIR" 2>/dev/null || true
@@ -308,6 +327,14 @@ save_config() {
printf 'CONFIG_SANDBOX=%q\n' "$SANDBOX_NAME"
printf 'CONFIG_AGENT=%q\n' "$agent"
printf 'CONFIG_MODE=%q\n' "$mode"
printf 'CONFIG_TEMPLATE=%q\n' "$template"
printf 'CONFIG_KITS=('
local kit
for kit in ${kits[@]+"${kits[@]}"}; do
printf ' %q' "$kit"
done
printf ' )\n'
printf 'CONFIG_AWS_PROFILES=('
local profile
@@ -517,6 +544,15 @@ create_sandbox() {
create_args+=(--clone)
fi
if [[ -n "$CONFIG_TEMPLATE" ]]; then
create_args+=(--template "$CONFIG_TEMPLATE")
fi
local kit
for kit in ${CONFIG_KITS[@]+"${CONFIG_KITS[@]}"}; do
create_args+=(--kit "$kit")
done
create_args+=(
"$CONFIG_AGENT"
"$REPO_ROOT"
@@ -528,8 +564,10 @@ create_sandbox() {
setup_command() {
local agent="$DEFAULT_AGENT"
local mode="$DEFAULT_MODE"
local template="$DEFAULT_TEMPLATE"
local replace=false
local -a aws_profiles=()
local -a kits=()
while (($#)); do
case "$1" in
@@ -551,6 +589,20 @@ setup_command() {
mode="direct"
shift
;;
--template)
(($# >= 2)) || die "--template requires a value"
template="$2"
shift 2
;;
--stock-template)
template=""
shift
;;
--kit)
(($# >= 2)) || die "--kit requires a value"
kits+=("$2")
shift 2
;;
--replace)
replace=true
shift
@@ -572,7 +624,14 @@ setup_command() {
validate_aws_profile "$profile"
done
save_config "$agent" "$mode" "${aws_profiles[@]}"
local kit
for kit in ${kits[@]+"${kits[@]}"}; do
[[ -e "$kit" ]] ||
die "Kit does not exist: $kit"
done
save_config "$agent" "$mode" "$template" "${#kits[@]}" \
${kits[@]+"${kits[@]}"} ${aws_profiles[@]+"${aws_profiles[@]}"}
if sandbox_exists; then
if [[ "$replace" == true ]]; then
@@ -656,6 +715,13 @@ status_command() {
printf 'Agent: %s\n' "$CONFIG_AGENT"
printf 'Mode: %s\n' "$CONFIG_MODE"
printf 'Template: %s\n' "${CONFIG_TEMPLATE:-stock}"
if ((${#CONFIG_KITS[@]})); then
printf 'Kits:\n'
printf ' %s\n' "${CONFIG_KITS[@]}"
fi
printf 'Token days: %s\n' "$DEFAULT_TOKEN_DAYS"
printf 'AWS profiles (host -> sandbox):\n'