ai-sandbox-autophase could sit forever, printing nothing, in two places that are both before the first unit -- which is why both looked like a script that "does nothing". The rc probe ran `timeout 15 bash -ic` in front of the user's terminal. An interactive bash calls tcsetpgrp() to take that terminal, is in its background, and is stopped for trying; a stopped process does not take SIGTERM, so the timeout waited in front of it and the driver waited on the command substitution behind it. It now runs in its own session (setsid), with stdin closed, under `timeout -k`, and reads the definition from a temp file rather than a pipe, because a grandchild an rc file leaves behind holds a pipe's EOF open long after the shell it came from has been reaped. A launcher is called *with* the run, and a function ending at `--env NAME` -- the shape everyone writes for typing by hand, including every example this repo printed -- discards the flags and the in-container command. The container then starts the image's default harness with no prompt and, with --no-interactive gone, the terminal attached: an agent waiting for keystrokes behind a pipe nobody reads. `ap_forwards_args` checks the captured definition, --status and the preflight name it with the one-line fix, and every child now starts with stdin on /dev/null so a launcher cannot put the sandbox back on the keyboard. Aliases and whole command lines need no forwarding: the shell appends the words to those. Tests: a launcher fixture that drops its arguments, one that leaves a process behind, and shape assertions on ap_probe_rc. LLM-Generated: true
84 lines
3.2 KiB
Bash
Executable File
84 lines
3.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# install.sh -- put ai-sandbox on PATH and wire up an alias.
|
|
#
|
|
# ./install.sh install to ~/.local/bin, print the alias line
|
|
# ./install.sh --alias pi also append the alias to ~/.bashrc
|
|
# ./install.sh --prefix DIR install somewhere else ($AISB_PREFIX does the same)
|
|
# ./install.sh --uninstall remove what this script installed
|
|
#
|
|
# Both programs are installed: ai-sandbox itself, and ai-sandbox-autophase, which runs
|
|
# the .agents/phases/ pipeline with the agent in the sandbox and the git push on the host.
|
|
set -Eeuo pipefail
|
|
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PREFIX="${AISB_PREFIX:-${HOME}/.local/bin}"
|
|
ALIAS_NAME=""
|
|
UNINSTALL=0
|
|
|
|
while (($#)); do
|
|
case "$1" in
|
|
--prefix) PREFIX="${2:?--prefix needs a directory}"; shift 2 ;;
|
|
--prefix=*) PREFIX="${1#*=}"; shift ;;
|
|
--alias) ALIAS_NAME="${2:?--alias needs a name, e.g. --alias pi}"; shift 2 ;;
|
|
--alias=*) ALIAS_NAME="${1#*=}"; shift ;;
|
|
--uninstall) UNINSTALL=1; shift ;;
|
|
-h|--help) sed -n '2,12p' "$0"; exit 0 ;;
|
|
*) echo "unknown option: $1" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
targets=("$PREFIX/ai-sandbox" "$PREFIX/ai-sandbox-autophase")
|
|
target="${targets[0]}"
|
|
rcfile="${HOME}/.bashrc"
|
|
|
|
if (( UNINSTALL )); then
|
|
for t in "${targets[@]}"; do
|
|
[[ -L "$t" || -f "$t" ]] && { rm -f "$t"; echo "removed $t"; } || echo "nothing at $t"
|
|
done
|
|
if [[ -n "$ALIAS_NAME" ]]; then
|
|
tmp="$(mktemp)"
|
|
grep -v "^alias ${ALIAS_NAME}=" "$rcfile" > "$tmp" 2>/dev/null || true
|
|
mv "$tmp" "$rcfile"; echo "removed the ${ALIAS_NAME} alias from $rcfile"
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
command -v podman > /dev/null || echo "warning: podman is not installed; ai-sandbox needs it"
|
|
|
|
mkdir -p "$PREFIX"
|
|
for src in ai-sandbox ai-sandbox-autophase; do
|
|
[[ -x "$HERE/bin/$src" ]] || { echo "bin/$src is missing or not executable" >&2; exit 1; }
|
|
ln -sfn "$HERE/bin/$src" "$PREFIX/$src"
|
|
echo "installed $PREFIX/$src -> $HERE/bin/$src"
|
|
done
|
|
|
|
if [[ -n "$ALIAS_NAME" ]]; then
|
|
line="alias ${ALIAS_NAME}='${target}'"
|
|
if grep -qxF "$line" "$rcfile" 2>/dev/null; then
|
|
echo "alias already present in $rcfile"
|
|
else
|
|
printf '\n# added by ai-sandbox install.sh\n%s\n' "$line" >> "$rcfile"
|
|
echo "added to $rcfile: $line"
|
|
fi
|
|
else
|
|
echo
|
|
echo "To use it as a harness wrapper, add this to $rcfile:"
|
|
echo " alias pi='$target'"
|
|
echo "then: source $rcfile && cd your/project && pi --allow api.example.com --env API_KEY"
|
|
fi
|
|
|
|
echo
|
|
echo "next: $target doctor # sanity-check this machine"
|
|
echo " $target build # build the sandbox image now (otherwise built on first run)"
|
|
echo
|
|
echo "To run a phased project inside the sandbox, wrap this in a function in ~/.bashrc.d"
|
|
echo "(that is what ai-sandbox-autophase looks for, since a script cannot see a shell"
|
|
echo "function it did not define):"
|
|
echo " sandbox-pi () {"
|
|
echo " export AI_API_TOKEN=\$(pass aipi/token)"
|
|
echo ' ai-sandbox --allow api.example.com --env AI_API_TOKEN "$@"'
|
|
echo " }"
|
|
echo ' (the "$@" is not decoration: a run appends its flags, then --, then the command'
|
|
echo " to sandbox, and a function that drops them runs the image default with no prompt)"
|
|
echo "then: cd your/project && ai-sandbox-autophase --status"
|