From 24f4c1612509e8bf8f9ed52f88918ad888fa19bc Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Wed, 12 Aug 2026 07:18:12 -0700 Subject: [PATCH] feat(help): layered usage -- one screen, help all, per-command help - usage() is now a 43-line summary: agents, commands, common flags, examples, pointers. The 130-line wall moved to usage_full() behind `deva.sh help all` (also `--help all`) - every management command answers ` --help` / `help ` with its own usage_command() block; token order does not matter because help routing rides the same PRE_ARGS scan that sets MANAGEMENT_MODE - `all` counts only after a help token, so it stays a free word - launch-path help (deva.sh claude --help) keeps the global summary; `-- --help` passthrough to the agent is untouched; tmux keeps its own help (dispatches before the scan) - scripts/test-help-surface.sh: 24 asserts, wired into CI Close #554 Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 3 + CHANGELOG.md | 10 ++ DEV-LOGS.md | 5 + deva.sh | 186 ++++++++++++++++++++++++++++++++++- scripts/test-help-surface.sh | 98 ++++++++++++++++++ 5 files changed, 300 insertions(+), 2 deletions(-) create mode 100755 scripts/test-help-surface.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34a51a6..87d1f63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index db0b2e9..fc7dec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 --help` (also + `deva.sh help `) 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 — diff --git a/DEV-LOGS.md b/DEV-LOGS.md index 0e99702..710cb98 100644 --- a/DEV-LOGS.md +++ b/DEV-LOGS.md @@ -13,6 +13,11 @@ - Minimal markdown markers, no unnecessary formatting, minimal emojis. - Reference issue numbers in the format `#` 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. diff --git a/deva.sh b/deva.sh index c25b649..7b85e5a 100755 --- a/deva.sh +++ b/deva.sh @@ -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 [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 --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 + --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] @@ -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 --help # command-specific help USAGE } @@ -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 ;; --version) echo "deva.sh v${VERSION}" @@ -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}" diff --git a/scripts/test-help-surface.sh b/scripts/test-help-surface.sh new file mode 100755 index 0000000..db6ea6b --- /dev/null +++ b/scripts/test-help-surface.sh @@ -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" " --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"