Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5dfae0696 | ||
|
|
53db7df812 |
@@ -263,8 +263,8 @@ provider "aws" {
|
|||||||
|
|
||||||
| Command | Effect |
|
| Command | Effect |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `setup [options]` | Configure the repository, create the sandbox, open the token form, install AWS profiles, Claude configuration and plugins, and mise |
|
| `setup [options]` | Configure the repository, create the sandbox, open the token form if no token is stored yet, install AWS profiles, Claude configuration and plugins, and mise |
|
||||||
| `token` | Replace the GitHub token for this repository — expiry, revocation, permission change |
|
| `token` | Replace the GitHub token for this repository — expiry, revocation, permission change. Always prompts |
|
||||||
| `run [--launch MODE] [-- args...]` | Refresh AWS credentials and the repository's mise tools, then attach to the agent or to a tmux workspace |
|
| `run [--launch MODE] [-- args...]` | Refresh AWS credentials and the repository's mise tools, then attach to the agent or to a tmux workspace |
|
||||||
| `refresh` | Refresh AWS credentials, without attaching |
|
| `refresh` | Refresh AWS credentials, without attaching |
|
||||||
| `config` | Re-apply your Claude configuration, plugins and dotfiles after the host changes, without recreating the sandbox |
|
| `config` | Re-apply your Claude configuration, plugins and dotfiles after the host changes, without recreating the sandbox |
|
||||||
|
|||||||
+58
-2
@@ -83,8 +83,10 @@ Usage:
|
|||||||
mise run ai:sbx -- status
|
mise run ai:sbx -- status
|
||||||
mise run ai:sbx -- remove
|
mise run ai:sbx -- remove
|
||||||
|
|
||||||
Setup opens a pre-filled GitHub token form in your browser. Use "token" on
|
Setup opens a pre-filled GitHub token form in your browser, unless a token is
|
||||||
its own to replace an expired or revoked token later.
|
already stored for the sandbox. The secret store outlives the sandbox, so
|
||||||
|
recreating one with --replace keeps its token. Use "token" on its own to
|
||||||
|
replace an expired or revoked token.
|
||||||
|
|
||||||
Set AI_SBX_TEMPLATE in your mise config to reuse one custom image across
|
Set AI_SBX_TEMPLATE in your mise config to reuse one custom image across
|
||||||
every repository without repeating --template.
|
every repository without repeating --template.
|
||||||
@@ -242,6 +244,17 @@ read_token() {
|
|||||||
printf '%s' "$token"
|
printf '%s' "$token"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Only a token scoped to this sandbox counts. A global one would authenticate
|
||||||
|
# the agent too, but reaching every repository the token can reach is exactly
|
||||||
|
# what this task exists to prevent, so it is not treated as satisfying setup.
|
||||||
|
sandbox_has_github_token() {
|
||||||
|
sbx secret ls 2>/dev/null |
|
||||||
|
awk -v scope="$SANDBOX_NAME" '
|
||||||
|
$1 == scope && $2 == "service" && $3 == "github" { found = 1 }
|
||||||
|
END { exit !found }
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
install_github_token() {
|
install_github_token() {
|
||||||
local url
|
local url
|
||||||
url="$(token_url)"
|
url="$(token_url)"
|
||||||
@@ -1055,6 +1068,39 @@ EOF
|
|||||||
'
|
'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# A sandbox image ships without a locale, which leaves LC_CTYPE at POSIX. Every
|
||||||
|
# multibyte glyph then degrades to a placeholder: Nerd Font icons in the editor
|
||||||
|
# render as underscores, and bash printf emits \uXXXX escapes literally. LANG
|
||||||
|
# alone is enough, and leaves a user free to override individual categories.
|
||||||
|
install_sandbox_locale() {
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
sbx exec "$SANDBOX_NAME" bash -c '
|
||||||
|
persistent=/etc/sandbox-persistent.sh
|
||||||
|
marker="# BEGIN ai-sbx locale"
|
||||||
|
|
||||||
|
if grep -Fq "$marker" "$persistent" 2>/dev/null; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# locale -a spells these inconsistently across distributions, so probe
|
||||||
|
# each one for usability rather than matching its name.
|
||||||
|
for candidate in C.UTF-8 en_US.UTF-8; do
|
||||||
|
if LC_ALL="$candidate" locale >/dev/null 2>&1; then
|
||||||
|
chosen="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "${chosen:-}" ]] || exit 0
|
||||||
|
|
||||||
|
cat >>"$persistent" <<EOF
|
||||||
|
# BEGIN ai-sbx locale
|
||||||
|
export LANG=$chosen
|
||||||
|
# END ai-sbx locale
|
||||||
|
EOF
|
||||||
|
' </dev/null >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
validate_launch_mode() {
|
validate_launch_mode() {
|
||||||
case "$1" in
|
case "$1" in
|
||||||
agent | tmux) ;;
|
agent | tmux) ;;
|
||||||
@@ -1321,7 +1367,15 @@ setup_command() {
|
|||||||
create_sandbox
|
create_sandbox
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# The secret store outlives the sandbox, so recreating one to change its
|
||||||
|
# image keeps the token. Prompting anyway would train the habit of minting
|
||||||
|
# replacement tokens and never revoking the old ones.
|
||||||
|
if sandbox_has_github_token; then
|
||||||
|
printf 'Keeping the GitHub token already stored for %s. Replace it with: mise run ai:sbx -- token\n' \
|
||||||
|
"$SANDBOX_NAME"
|
||||||
|
else
|
||||||
install_github_token
|
install_github_token
|
||||||
|
fi
|
||||||
|
|
||||||
install_sandbox_aws_files
|
install_sandbox_aws_files
|
||||||
|
|
||||||
@@ -1427,6 +1481,8 @@ run_command() {
|
|||||||
# runs every time rather than only at setup.
|
# runs every time rather than only at setup.
|
||||||
install_sandbox_mise
|
install_sandbox_mise
|
||||||
|
|
||||||
|
install_sandbox_locale
|
||||||
|
|
||||||
if [[ "$launch" == tmux ]]; then
|
if [[ "$launch" == tmux ]]; then
|
||||||
install_sandbox_workspace
|
install_sandbox_workspace
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TASK="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/tasks/ai/sbx"
|
||||||
|
|
||||||
|
# shellcheck source-path=SCRIPTDIR
|
||||||
|
# shellcheck source=tasks/ai/sbx
|
||||||
|
source "$TASK"
|
||||||
|
|
||||||
|
failures=0
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'FAIL: %s\n' "$1" >&2
|
||||||
|
failures=$((failures + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
SANDBOX_NAME=ai-repo-abc123
|
||||||
|
|
||||||
|
listing=""
|
||||||
|
sbx() {
|
||||||
|
printf '%s\n' "$listing"
|
||||||
|
}
|
||||||
|
|
||||||
|
with_listing() {
|
||||||
|
listing="$1"
|
||||||
|
sandbox_has_github_token
|
||||||
|
}
|
||||||
|
|
||||||
|
full_listing() {
|
||||||
|
cat <<'EOF'
|
||||||
|
SCOPE TYPE NAME SECRET
|
||||||
|
ai-repo-abc123 service github (stored)
|
||||||
|
(global) service anthropic (oauth configured)
|
||||||
|
|
||||||
|
CUSTOM SECRETS
|
||||||
|
SCOPE TARGETS ENV PLACEHOLDER SECRET
|
||||||
|
ai-repo-abc123 localstack.cloud LOCALSTACK_AUTH_TOKEN sbx-cs-0c2f39c1 ls-vOL***
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
with_listing "$(full_listing)" ||
|
||||||
|
fail "a sandbox-scoped github token was not detected"
|
||||||
|
|
||||||
|
with_listing "$(
|
||||||
|
cat <<'EOF'
|
||||||
|
SCOPE TYPE NAME SECRET
|
||||||
|
(global) service anthropic (oauth configured)
|
||||||
|
EOF
|
||||||
|
)" && fail "no github token stored, yet setup would have been skipped"
|
||||||
|
|
||||||
|
with_listing "$(
|
||||||
|
cat <<'EOF'
|
||||||
|
SCOPE TYPE NAME SECRET
|
||||||
|
(global) service github (stored)
|
||||||
|
EOF
|
||||||
|
)" && fail "a global github token must not satisfy a per-repository sandbox"
|
||||||
|
|
||||||
|
with_listing "$(
|
||||||
|
cat <<'EOF'
|
||||||
|
SCOPE TYPE NAME SECRET
|
||||||
|
ai-other-sandbox service github (stored)
|
||||||
|
EOF
|
||||||
|
)" && fail "another sandbox's github token must not count as this one's"
|
||||||
|
|
||||||
|
with_listing "$(
|
||||||
|
cat <<'EOF'
|
||||||
|
CUSTOM SECRETS
|
||||||
|
SCOPE TARGETS ENV PLACEHOLDER SECRET
|
||||||
|
ai-repo-abc123 github.com github sbx-cs-abc gh***
|
||||||
|
EOF
|
||||||
|
)" && fail "a custom secret must not be mistaken for the stored service token"
|
||||||
|
|
||||||
|
with_listing "" &&
|
||||||
|
fail "empty output should mean no token, not a stored one"
|
||||||
|
|
||||||
|
grep -q 'if sandbox_has_github_token; then' "$TASK" ||
|
||||||
|
fail "setup no longer guards install_github_token"
|
||||||
|
|
||||||
|
awk '/^token_command\(\)/, /^}/' "$TASK" | grep -q 'install_github_token' ||
|
||||||
|
fail "the token command must always prompt; it is the way to replace one"
|
||||||
|
|
||||||
|
awk '/^token_command\(\)/, /^}/' "$TASK" | grep -q 'sandbox_has_github_token' &&
|
||||||
|
fail "the token command must not skip when a token exists"
|
||||||
|
|
||||||
|
if ((failures)); then
|
||||||
|
printf '%d assertion(s) failed\n' "$failures" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'All GitHub token assertions passed.\n'
|
||||||
@@ -47,8 +47,12 @@ dispatch() {
|
|||||||
cat "$SBX_LOG"
|
cat "$SBX_LOG"
|
||||||
}
|
}
|
||||||
|
|
||||||
[[ "$DEFAULT_LAUNCH" == agent ]] ||
|
(
|
||||||
fail "AI_SBX_LAUNCH unset should default to agent, got: $DEFAULT_LAUNCH"
|
unset AI_SBX_LAUNCH
|
||||||
|
# shellcheck source=tasks/ai/sbx
|
||||||
|
source "$TASK"
|
||||||
|
[[ "$DEFAULT_LAUNCH" == agent ]]
|
||||||
|
) || fail "AI_SBX_LAUNCH unset should default to agent"
|
||||||
|
|
||||||
(
|
(
|
||||||
AI_SBX_LAUNCH=tmux
|
AI_SBX_LAUNCH=tmux
|
||||||
@@ -87,6 +91,13 @@ DEFAULT_LAUNCH=agent
|
|||||||
[[ "$(dispatch -- --launch tmux)" == *"run ai-test -- --launch tmux"* ]] ||
|
[[ "$(dispatch -- --launch tmux)" == *"run ai-test -- --launch tmux"* ]] ||
|
||||||
fail "--launch after -- belongs to the agent, not to the task"
|
fail "--launch after -- belongs to the agent, not to the task"
|
||||||
|
|
||||||
|
DEFAULT_LAUNCH=agent
|
||||||
|
locale_dispatch="$(dispatch)"
|
||||||
|
[[ "$locale_dispatch" == *"BEGIN ai-sbx locale"* ]] ||
|
||||||
|
fail "run should install a UTF-8 locale, or Nerd Font glyphs render as placeholders: $locale_dispatch"
|
||||||
|
[[ "$locale_dispatch" == *"LC_ALL=\"\$candidate\" locale"* ]] ||
|
||||||
|
fail "the locale must be probed for usability, not matched by name against locale -a"
|
||||||
|
|
||||||
if (validate_launch_mode bogus) 2>/dev/null; then
|
if (validate_launch_mode bogus) 2>/dev/null; then
|
||||||
fail "an unknown launch mode was accepted"
|
fail "an unknown launch mode was accepted"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user