Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ jobs:
./deva.sh --help
./claude-yolo --help

- name: Test help surface routing
run: ./scripts/test-help-surface.sh

- name: Test version output
run: |
./deva.sh --version
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
- Layered help (#554): `--help` is now a one-screen summary (agents,
commands, common flags, examples) instead of the 130-line wall;
the full reference moved to `deva.sh help all`, and every
management command answers `deva.sh <cmd> --help` (also
`deva.sh help <cmd>`) with its own usage. Launch-path help
(`deva.sh claude --help`) still shows the global summary and
`-- --help` passthrough to the agent is untouched.
scripts/test-help-surface.sh pins the routing (24 asserts)

### Added
- pi (earendil-works/pi, the minimal multi-provider agent harness by
badlogic) as the 7th agent: `deva.sh pi` (#552). One dot-dir —
Expand Down
5 changes: 5 additions & 0 deletions DEV-LOGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
- Minimal markdown markers, no unnecessary formatting, minimal emojis.
- Reference issue numbers in the format `#<issue-number>` for easy linking.

# [2026-08-12] Dev Log: layered help #554
- Why: the help surface was one 130-line wall; `ps --help` printed the same wall; cloak VNC passwords sat next to -v. First shot of the framework-vnext wrapper-UX track (W1).
- What: usage() split into a 43-line summary + usage_full() (`help all`) + usage_command() per management command. Routing rides the existing PRE_ARGS scan: help tokens set HELP_REQUESTED instead of exiting, so the same pass that sees --help already knows MANAGEMENT_MODE — `help ps`, `--help ps`, `ps --help` all converge; `all` only counts after a help token so it stays a free word otherwise. tmux untouched (dispatches before the scan); launch help stays global; sentinel passthrough proven untouched by dry-run assert.
- Result: scripts/test-help-surface.sh 24/24, wired into CI Basic Tests. Port follows with a fixture re-pin (its serve.ts even has per-command help sitting dead behind the old interception).

# [2026-08-12] Dev Log: pi as the 7th agent #552
- Why: pi (earendil-works/pi, ex badlogic/pi-mono) is the cleanest container-fit yet — no permission system at all, its own security doc says "run it in a contained environment". Multi-provider harness rounds out the herd.
- What: agents/pi.sh (oauth mounts ~/.pi rw — tokens auto-refresh; api-key passes provider env keys, mounts nothing, blank-overlays .pi/agent/auth.json which outranks env), deva.sh registration (auth tag, version label, mounts, autolink, status, env scrub of the 5 provider keys), image pin PI_CODING_AGENT_VERSION=0.84.1 (@earendil-works/pi-coding-agent, needs node >= 22.19 — NODE_MAJOR=22 covers it) through versions.env/Makefile/scripts/CI, scripts/test-pi-auth.sh (16 asserts). Trap dodged: @mariozechner scope is dead (moved to @earendil-works), and @mariozechner/pi is an unrelated vLLM tool.
Expand Down
186 changes: 184 additions & 2 deletions deva.sh
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,165 @@ usage() {
cat <<'USAGE'
deva.sh - Docker-based multi-agent launcher (Claude, Codex, Gemini, Grok, Kimi, opencode, pi)

Usage:
deva.sh [flags] [agent] [-- agent-flags]
deva.sh <command> [flags]

Agents:
claude (default), codex, gemini, grok, kimi, opencode, pi

Commands:
ps List containers (current project; -g for all projects)
status Inspect workspace: containers, mounts, agent homes
shell Open a zsh shell in a container
stop Stop container
rm Remove container
clean Remove all stopped containers
sessions Browse agent sessions (ccx passthrough)
insight Generate data report (ccx passthrough)
ccx Run any ccx command inside a container
tmux Host-side tmux bridge lifecycle

Common flags:
--rm Ephemeral: remove container after exit
-g, --global Management commands act across all projects
-v SRC:DEST[:OPT] Mount an extra volume
-e VAR[=VALUE] Pass env var into the container
-c, --config-home DIR Alternate auth/config home
-p, --profile NAME Image profile: base (default), rust, cloak
-Q, --quick Bare mode: no config, no auth mounts, implies --rm
--auth-with METHOD Non-default auth for the agent (docs/authentication.md)
--trace Wrap the agent with cctrace request tracing
--dry-run Print the docker command without executing
-- Everything after this goes to the agent unchanged

Examples:
deva.sh # claude in a persistent container
deva.sh codex -- exec 'fix CI' # one-shot codex task
deva.sh pi -Q -- --version # clean ephemeral smoke check
deva.sh status # what's running, what's mounted

Help:
deva.sh help all # full reference (every flag and mode)
deva.sh <command> --help # command-specific help
USAGE
}

usage_command() {
local mode="$1"
case "$mode" in
ps)
cat <<'USAGE'
Usage: deva.sh ps [-g]

List deva containers for this workspace.

-g, --global List containers from all projects

Example: deva.sh ps -g
USAGE
;;
status)
cat <<'USAGE'
Usage: deva.sh status [-g] [--verbose]

Inspect the workspace: containers, mounts, agent homes, health.

-g, --global Include containers from all projects
--verbose More detail per container

Example: deva.sh status --verbose
USAGE
;;
shell)
cat <<'USAGE'
Usage: deva.sh shell [-g]

Open a zsh shell in a running container for inspection
(picker when several match).

-g, --global Choose among containers from all projects

Example: deva.sh shell
USAGE
;;
stop)
cat <<'USAGE'
Usage: deva.sh stop [-g]

Stop a running container (picker when several match).

-g, --global Choose among containers from all projects

Example: deva.sh stop
USAGE
;;
rm)
cat <<'USAGE'
Usage: deva.sh rm [-g] [--all]

Remove a container (picker when several match).

-g, --global Choose among containers from all projects

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop advertising broken global selection for rm

When the current workspace has any labeled container, deva.sh rm -g does not choose among all projects as documented here: the rm dispatch populates matching_names from the current workspace first and consults GLOBAL_MODE only when that result is empty. With one local container it can therefore remove that container immediately instead of presenting the expected global selection. Either apply the global filter before the workspace lookup or document the actual behavior.

Useful? React with 👍 / 👎.

--all Remove every container for this workspace

Example: deva.sh rm --all
USAGE
;;
clean)
cat <<'USAGE'
Usage: deva.sh clean [-g]

Remove all stopped deva containers.

-g, --global Clean across all projects

Example: deva.sh clean -g
USAGE
;;
sessions)
cat <<'USAGE'
Usage: deva.sh sessions [-g] [args...]

Browse agent sessions. Passthrough: extra args go to `ccx sessions`
inside the container.

-g, --global Pick the container from all projects

Example: deva.sh sessions --limit 10
USAGE
;;
insight)
cat <<'USAGE'
Usage: deva.sh insight [args...]

Generate a data report. Passthrough: extra args go to `ccx insight`
inside the container.

Example: deva.sh insight
USAGE
;;
ccx)
cat <<'USAGE'
Usage: deva.sh ccx [-g] [cmd] [args...]

Run any ccx command inside a container (raw passthrough).

-g, --global Pick the container from all projects

Example: deva.sh ccx sessions --limit 5
USAGE
;;
*)
usage
;;
esac
}

usage_full() {
cat <<'USAGE'
deva.sh - Docker-based multi-agent launcher (Claude, Codex, Gemini, Grok, Kimi, opencode, pi)

Usage:
deva.sh [deva flags] [agent] [-- agent-flags]
deva.sh [agent] [deva flags] [-- agent-flags]
Expand Down Expand Up @@ -237,6 +396,10 @@ Advanced:
deva.sh kimi --trace # Trace kimi requests with cctrace
deva.sh --show-config # Debug configuration
deva.sh --no-docker claude # Disable Docker-in-Docker auto-mount

Help:
deva.sh --help # one-screen summary
deva.sh <command> --help # command-specific help
USAGE
}

Expand Down Expand Up @@ -3634,12 +3797,20 @@ if [ "${PRE_ARGS[0]:-}" = "tmux" ]; then
MANAGEMENT_MODE="tmux"
fi

# Help is routed AFTER this scan so `deva.sh ps --help` (and `help ps`)
# can show the command's own usage — the same pass that sees the help
# token also sets MANAGEMENT_MODE. `all` counts only after a help token,
# so `deva.sh help all` works without stealing `all` as a bare word.
HELP_REQUESTED=false
HELP_ALL=false
if [ ${#PRE_ARGS[@]} -gt 0 ] && [ "$MANAGEMENT_MODE" != "tmux" ]; then
for tok in "${PRE_ARGS[@]}"; do
case "$tok" in
help | --help | -h)
usage
exit 0
HELP_REQUESTED=true
;;
all)
[ "$HELP_REQUESTED" = true ] && HELP_ALL=true
;;
Comment on lines +3812 to 3814
--version)
echo "deva.sh v${VERSION}"
Expand Down Expand Up @@ -3680,6 +3851,17 @@ if [ ${#PRE_ARGS[@]} -gt 0 ] && [ "$MANAGEMENT_MODE" != "tmux" ]; then
done
fi

if [ "$HELP_REQUESTED" = true ]; then
if [ "$HELP_ALL" = true ]; then
usage_full
elif [ "$MANAGEMENT_MODE" != "launch" ] && [ "$MANAGEMENT_MODE" != "show-config" ]; then
usage_command "$MANAGEMENT_MODE"
else
usage
fi
exit 0
fi

if [ "$MANAGEMENT_MODE" != "launch" ]; then
if [ "$MANAGEMENT_MODE" = "tmux" ]; then
cmd_tmux "${PRE_ARGS[@]:1}"
Expand Down
98 changes: 98 additions & 0 deletions scripts/test-help-surface.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
# test-help-surface.sh - layered help routing (#554)
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

tmp_home="$(mktemp -d)"
cleanup() { rm -rf "$tmp_home"; }
trap cleanup EXIT

fail=0
run() {
(
cd "$REPO_ROOT"
HOME="$tmp_home" \
XDG_CONFIG_HOME="$tmp_home/.config" \
XDG_CACHE_HOME="$tmp_home/.cache" \
DEVA_NO_DOCKER=1 \
./deva.sh "$@"
) 2>&1
}

want() {
local desc="$1" needle="$2" hay="$3"
if grep -F -- "$needle" <<<"$hay" >/dev/null; then
echo " PASS $desc"
else
echo " FAIL $desc"
echo " expected to find: $needle" >&2
fail=1
fi
}
want_absent() {
local desc="$1" needle="$2" hay="$3"
if grep -F -- "$needle" <<<"$hay" >/dev/null; then
echo " FAIL $desc"
echo " expected absent: $needle" >&2
fail=1
else
echo " PASS $desc"
fi
}

echo "=== short help: one screen with pointers ==="
short_out="$(run --help)"
short_lines="$(wc -l <<<"$short_out")"
if [ "$short_lines" -lt 50 ]; then
echo " PASS short help stays under 50 lines ($short_lines)"
else
echo " FAIL short help is $short_lines lines (wall came back?)"
fail=1
fi
want "lists agents" "claude (default), codex" "$short_out"
want "points at help all" "deva.sh help all" "$short_out"
want "points at command help" "<command> --help" "$short_out"
want_absent "no cloak esoterica in short help" "cloak-vnc" "$short_out"
want_absent "no chrome esoterica in short help" "DEVA_CHROME_PROFILE_PATH" "$short_out"

echo "=== -h and bare help match --help ==="
[ "$(run -h)" = "$short_out" ] && echo " PASS -h matches" || { echo " FAIL -h differs"; fail=1; }
[ "$(run help)" = "$short_out" ] && echo " PASS bare help matches" || { echo " FAIL bare help differs"; fail=1; }

echo "=== help all: the full reference ==="
full_out="$(run help all)"
want "chrome section present" "DEVA_CHROME_PROFILE_PATH" "$full_out"
want "cloak flags present" "--cloak-vnc" "$full_out"
want "naming section present" "Container Naming" "$full_out"
[ "$(run --help all)" = "$full_out" ] && echo " PASS --help all matches" || { echo " FAIL --help all differs"; fail=1; }

echo "=== per-command help ==="
ps_out="$(run ps --help)"
want "ps usage" "Usage: deva.sh ps" "$ps_out"
want_absent "ps help is not the wall" "Container Naming" "$ps_out"
[ "$(run --help ps)" = "$ps_out" ] && echo " PASS token order independent" || { echo " FAIL --help ps differs"; fail=1; }
[ "$(run help ps)" = "$ps_out" ] && echo " PASS help ps matches" || { echo " FAIL help ps differs"; fail=1; }
want "status usage" "Usage: deva.sh status" "$(run status --help)"
want "rm knows --all" "--all" "$(run rm --help)"
want "sessions notes passthrough" "ccx sessions" "$(run sessions --help)"

echo "=== launch-path help stays global ==="
agent_help="$(run claude --help)"
want "agent --help shows global summary" "Common flags" "$agent_help"
want_absent "agent --help is not a command help" "Usage: deva.sh ps" "$agent_help"

echo "=== sentinel passthrough untouched ==="
pass_out="$(run claude --dry-run -- --help || true)"
want "agent argv keeps --help" "--help" "$pass_out"
want_absent "no usage screen on passthrough" "Common flags" "$pass_out"

echo "=== tmux keeps its own help ==="
want "tmux help intact" "deva.sh tmux setup" "$(run tmux --help)"

if [ "$fail" -ne 0 ]; then
echo "FAIL: help surface" >&2
exit 1
fi
echo "OK: help surface"
Loading