Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 28 additions & 23 deletions acq
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ print_help() {
cat <<'HELP'
acq — agentic coding quickstart wrapper

acq wraps a sandbox backend (sbx or msb) and applies the shared federal
acq wraps a sandbox backend and applies the shared federal
"kits" (USAi provider, playbook, git-ssh-sign, zscaler CA) to each sandbox.

Usage:
acq [--backend sbx|msb] <subcommand> [args...]
acq [--backend NAME] <subcommand> [args...]

Subcommands:
run AGENT PATH [...] [-- AGENT_ARGS...] Create+attach a sandbox, or re-attach
Expand Down Expand Up @@ -114,7 +114,7 @@ Subcommands:
version Show acq version + backend info.
doctor [NAME] Backend health check, or non-enforcing
image diagnostics for an existing sandbox.
backend list List detected backends (sbx, msb).
backend list List registered backends and availability.
backend set NAME Persist the default backend.
backend unset Clear the persisted default (auto-detect).
configure Interactive setup: pick extra kits and
Expand All @@ -133,7 +133,7 @@ Any other subcommand is passed through to the active backend's CLI (acq prints
a notice first so you know the output/errors below it are the backend's).

Global flags (before the subcommand):
--backend sbx|msb Override the active backend for this invocation.
--backend NAME Override the active backend for this invocation.
Default: the persisted backend (`acq backend set`),
else msb. `msb` runs microVMs via the microsandbox (msb)
CLI; `sbx` runs Docker sandboxes via the sbx CLI.
Expand Down Expand Up @@ -182,6 +182,13 @@ run/create flags:

See docs/howto/acq.md and docs/BACKEND_GUIDE.md for more.
HELP
printf 'Registered: '
local _backend_name _backend_sep=""
for _backend_name in "${ACQ_BACKEND_NAMES[@]}"; do
printf '%s%s' "$_backend_sep" "$_backend_name"
_backend_sep=', '
done
printf '. OpenShell is explicit-only until its adapter is available.\n'
}

# _acq_secret_usage / _acq_kit_usage — acq's OWN per-verb usage blocks. Printed
Expand Down Expand Up @@ -245,8 +252,8 @@ _acq_backend_subcmd_usage() {
acq backend — select the sandbox backend

Usage:
acq backend list List detected/installed backends (sbx, msb)
acq backend set NAME Persist the default backend (sbx | msb)
acq backend list List registered backends and availability
acq backend set NAME Persist the default backend
acq backend unset Clear the persisted default (fall back to auto-detect)
BACKENDUSAGE
}
Expand Down Expand Up @@ -590,7 +597,7 @@ _acq_parse_secret_scope_for_propagation() {
_acq_propagate_secret_to_present_backends() {
local service="$1" scope_name="${2:-}" origin="${3:-set}" current="${ACQ_RESOLVED_BACKEND:-}" be rc=0
[ -n "$service" ] || return 0
for be in msb sbx; do
for be in "${ACQ_AUTODETECT_BACKEND_NAMES[@]}"; do
[ "$be" != "$current" ] || continue
_backend_installed "$be" || continue
if [ "$origin" = "rotate" ] && [ "$current" = "sbx" ] && [ "$be" = "msb" ]; then
Expand Down Expand Up @@ -751,22 +758,20 @@ case "$subcommand" in
# List detected/installed backends. Recover a backend installed in
# ~/.local/bin but not on PATH first, so the listing matches what
# `acq run` auto-detect will find.
_ensure_local_bin_on_path sbx >/dev/null 2>&1 || true
_ensure_local_bin_on_path msb >/dev/null 2>&1 || true
printf 'Installed backends:\n'
if command -v sbx >/dev/null 2>&1; then
local_ver=$(sbx version 2>/dev/null | grep -oE 'v?[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n1 | sed 's/^v//' || echo "?")
printf ' sbx v%s\n' "$local_ver"
else
printf ' sbx (not found)\n'
fi
if command -v msb >/dev/null 2>&1; then
local_ver=$(msb --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n1 || echo "?")
printf ' msb v%s\n' "$local_ver"
else
printf ' msb (not found)\n'
fi
printf '\nDeferred (not scheduled):\n ppp (Podman-Plus-Proxy)\n'
printf 'Registered backends:\n'
for local_backend in "${ACQ_BACKEND_NAMES[@]}"; do
if acq_backend_is_explicit_only "$local_backend"; then
printf ' %s (explicit-only; adapter not yet available)\n' "$local_backend"
else
_ensure_local_bin_on_path "$local_backend" >/dev/null 2>&1 || true
if command -v "$local_backend" >/dev/null 2>&1; then
local_ver=$(acq_backend_cli_version "$local_backend")
printf ' %s v%s\n' "$local_backend" "$local_ver"
else
printf ' %s (not found)\n' "$local_backend"
fi
fi
done
;;
set)
shift
Expand Down
87 changes: 62 additions & 25 deletions acq.backends/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1759,6 +1759,47 @@ _acq_config_write_field() {
return 0
}

# ---------------------------------------------------------------------------
# Backend registry
# ---------------------------------------------------------------------------
# Bash 3.2 does not have associative arrays, so keep backend metadata in one
# registry accessor. OpenShell is deliberately explicit-only until it has an
# adapter; it must never affect the established msb/sbx auto-detection path.
ACQ_BACKEND_NAMES=(sbx msb openshell)
ACQ_AUTODETECT_BACKEND_NAMES=(msb sbx)

acq_backend_registry_field() {
local name="$1" field="$2"
case "${name}:${field}" in
sbx:explicit_only) printf '0\n' ;;
sbx:version_arg) printf 'version\n' ;;
sbx:version_regex) printf 'v?[0-9]+\.[0-9]+(\.[0-9]+)?\n' ;;
msb:explicit_only) printf '0\n' ;;
msb:version_arg) printf '%s\n' '--version' ;;
msb:version_regex) printf '[0-9]+\.[0-9]+(\.[0-9]+)?\n' ;;
openshell:explicit_only) printf '1\n' ;;
openshell:version_arg) printf '%s\n' '--version' ;;
openshell:version_regex) printf '[0-9]+\.[0-9]+(\.[0-9]+)?\n' ;;
*) return 1 ;;
esac
}

acq_backend_is_explicit_only() {
[ "$(acq_backend_registry_field "$1" explicit_only 2>/dev/null || true)" = "1" ]
}

acq_backend_cli_version() {
local name="$1" arg regex version
arg=$(acq_backend_registry_field "$name" version_arg) || return 1
regex=$(acq_backend_registry_field "$name" version_regex) || return 1
version=$(command "$name" "$arg" 2>/dev/null | grep -oE "$regex" | head -n1 | sed 's/^v//' || true)
printf '%s\n' "${version:-?}"
}

acq_backend_registered_names() {
printf '%s\n' "${ACQ_BACKEND_NAMES[@]}"
}

# _ensure_local_bin_on_path — if a backend CLI is not on PATH but IS present in
# ~/.local/bin (the default install location for `curl … | sh` installers, e.g.
# microsandbox), prepend ~/.local/bin to PATH for THIS process so acq and the
Expand Down Expand Up @@ -1841,8 +1882,13 @@ _auto_detect_backend() {
ACQ_AUTODETECT_BACKEND=""
ACQ_AUTODETECT_REASON=""
local have_msb=0 have_sbx=0
_backend_installed msb && have_msb=1
_backend_installed sbx && have_sbx=1
local backend
for backend in "${ACQ_AUTODETECT_BACKEND_NAMES[@]}"; do
case "$backend" in
msb) _backend_installed "$backend" && have_msb=1 ;;
sbx) _backend_installed "$backend" && have_sbx=1 ;;
esac
done

if [ "$have_msb" -eq 1 ] && [ "$have_sbx" -eq 1 ]; then
if _sbx_has_sandboxes; then
Expand Down Expand Up @@ -3631,34 +3677,25 @@ acq_print_image_contract_doctor() {
}

acq_print_doctor() {
local sbx_status msb_status
local backend status

# Recover a backend installed in ~/.local/bin but not on PATH, so doctor's
# verdict matches what `acq run` will actually find (self-repair parity).
_ensure_local_bin_on_path sbx >/dev/null 2>&1 || true
_ensure_local_bin_on_path msb >/dev/null 2>&1 || true

# Check sbx
if command -v sbx >/dev/null 2>&1; then
local sbx_ver
sbx_ver=$(sbx version 2>/dev/null | grep -oE 'v?[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n1 | sed 's/^v//' || echo "?")
sbx_status="installed v${sbx_ver}"
else
sbx_status="not found"
fi

# Check msb (microsandbox).
if command -v msb >/dev/null 2>&1; then
local msb_ver
msb_ver=$(msb --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n1 || echo "?")
msb_status="installed v${msb_ver}"
else
msb_status="not found"
fi
for backend in "${ACQ_BACKEND_NAMES[@]}"; do
acq_backend_is_explicit_only "$backend" || _ensure_local_bin_on_path "$backend" >/dev/null 2>&1 || true
done

echo "acq: backend health check"
echo " [sbx: ${sbx_status}]"
echo " [msb: ${msb_status}]"
for backend in "${ACQ_BACKEND_NAMES[@]}"; do
if acq_backend_is_explicit_only "$backend"; then
status="explicit-only; adapter not yet available"
elif command -v "$backend" >/dev/null 2>&1; then
status="installed v$(acq_backend_cli_version "$backend")"
else
status="not found"
fi
echo " [$backend: $status]"
done
echo ""

local config_file
Expand Down
17 changes: 17 additions & 0 deletions test/bats/20-backend-resolution.bats
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,23 @@ _resolve() { # ADAPTER EXPLICIT -> prints "<resolved>|<stderr-note>"
refute_output --partial 'acq: using'
}

@test "backend: OpenShell registration is explicit-only and never auto-detected" {
unset ACQ_BACKEND
export XDG_CONFIG_HOME="$STUBDIR/noconfig"
export ACQ_TEST_INSTALLED_BACKENDS="openshell"
run _resolve sbx ''
assert_failure

# _resolve captures resolver stderr in $STUBDIR/note while asserting the
# in-process exit status. The CLI invocation below checks the user-facing error.
run cat "$STUBDIR/note"
assert_output --partial 'no backend detected'

run env ACQ_BACKEND=openshell "$ACQ" ls
assert_failure
assert_output --partial "no backend adapter found for 'openshell'"
}

@test "backend: both installed, no sbx sandboxes -> msb + notice" {
unset ACQ_BACKEND
export XDG_CONFIG_HOME="$STUBDIR/noconfig"
Expand Down
5 changes: 3 additions & 2 deletions test/bats/60-help.bats
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ teardown() { acq_teardown_stubs; }

load 'helper'

@test "help: acq help shows the banner, --backend list, and appends backend help" {
@test "help: acq help shows the banner, registered backends, and appends backend help" {
run env ACQ_BACKEND=sbx "$ACQ" help
assert_success
assert_output --partial 'acq — agentic coding quickstart wrapper'
assert_output --partial '--backend sbx|msb'
assert_output --partial '--backend NAME'
assert_output --partial 'Registered: sbx, msb, openshell'
assert_output --partial 'microsandbox (msb)'
assert_output --partial 'SBX-TOPLEVEL-HELP'
}
Expand Down
4 changes: 2 additions & 2 deletions test/bats/70-msb-backend.bats
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ _with_adapter() { # ADAPTER BODY
assert_output --partial '0.6.9'
run env "$ACQ" backend list
assert_output --partial 'msb v0.6.9'
refute_output --partial 'Coming in 1.2.x'
assert_output --partial 'openshell (explicit-only; adapter not yet available)'
}

@test "backend set/unset: persists a default then clears it (idempotent)" {
Expand All @@ -109,7 +109,7 @@ _with_adapter() { # ADAPTER BODY
@test "msb: doctor shows the installed msb version, not the old placeholder" {
run bash -c 'printf "n\n" | "$1" doctor' _ "$ACQ"
assert_output --partial 'msb: installed v0.6.9'
refute_output --partial 'coming in 1.2.x'
assert_output --partial 'openshell: explicit-only; adapter not yet available'
}

@test "msb: acq_backend_prepare is silent on a ready host" {
Expand Down
Loading