diff --git a/README.md b/README.md
index a80cbae..7759c69 100644
--- a/README.md
+++ b/README.md
@@ -11,9 +11,9 @@ brew install open330/tap/aas
```
-

+
-
Rendered from the reproducible VHS tape.
+
Rendered from reproducible VHS scenes against a throwaway fixture.
- **Isolated sessions:** `aas exec work` runs the agent under another account for one session and
@@ -299,8 +299,8 @@ cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-targets --all-features
-# Regenerate the README CLI demo (requires VHS)
-vhs docs/assets/cli-demo.tape
+# Regenerate the README CLI demo (requires VHS). --mode intro|changes, --channel readme|social|square
+docs/demo/render.sh
```
See [CONTRIBUTING.md](CONTRIBUTING.md) for the complete development workflow,
diff --git a/docs/assets/cli-demo.gif b/docs/assets/cli-demo.gif
new file mode 100644
index 0000000..62752c9
Binary files /dev/null and b/docs/assets/cli-demo.gif differ
diff --git a/docs/assets/cli-demo.tape b/docs/assets/cli-demo.tape
deleted file mode 100644
index 9815ab1..0000000
--- a/docs/assets/cli-demo.tape
+++ /dev/null
@@ -1,69 +0,0 @@
-Require cargo
-
-Output docs/assets/cli-usage-sessions.gif
-
-Set Shell "bash"
-Set FontSize 16
-Set FontFamily "Menlo"
-Set LetterSpacing 0
-Set LineHeight 1.2
-Set Width 920
-Set Height 680
-Set Padding 24
-Set Margin 18
-Set MarginFill "#161821"
-Set BorderRadius 12
-Set WindowBar Colorful
-Set Theme "Catppuccin Mocha"
-Set TypingSpeed 25ms
-Set CursorBlink false
-Set Framerate 30
-
-Env AAS_CONFIG_DIR "/tmp/aas-readme-demo"
-Env CODEX_HOME "/tmp/aas-readme-native-codex"
-Env PS1 "❯ "
-
-Hide
-Set TypingSpeed 2ms
-Type "cargo build --release --quiet"
-Enter
-Type `rm -rf "$AAS_CONFIG_DIR" /tmp/aas-readme-bin /tmp/aas-readme-native-codex`
-Enter
-Type `mkdir -p "$AAS_CONFIG_DIR/profiles/codex-personal" "$AAS_CONFIG_DIR/profiles/codex-team" /tmp/aas-readme-bin /tmp/aas-readme-native-codex`
-Enter
-Type `printf '%s\n' '{"version":1,"accounts":[{"provider":"codex","name":"team","label":"Team","addedAt":"2026-07-12T02:00:00.000Z","profileType":"isolated"},{"provider":"claude","name":"work","label":"Work","addedAt":"2026-07-12T00:00:00.000Z","profileType":"isolated"},{"provider":"codex","name":"personal","label":"Personal","addedAt":"2026-07-12T01:00:00.000Z","profileType":"isolated"}]}' > "$AAS_CONFIG_DIR/accounts.json"`
-Enter
-Type `printf '%s\n' '{"claude":"work","codex":"personal","updated":"2026-07-12T03:00:00.000Z"}' > "$AAS_CONFIG_DIR/.active.json"`
-Enter
-Type `printf '%s\n' '{"tokens":{"access_token":"demo-personal"}}' > "$AAS_CONFIG_DIR/profiles/codex-personal/auth.json"`
-Enter
-Type `printf '%s\n' '{"tokens":{"access_token":"demo-team"}}' > "$AAS_CONFIG_DIR/profiles/codex-team/auth.json"`
-Enter
-Type `now_ms=$(($(date +%s) * 1000)); reset_5h=$((now_ms + 3 * 60 * 60 * 1000)); reset_7d=$((now_ms + 5 * 24 * 60 * 60 * 1000)); printf '{"version":1,"entries":{"claude/work":{"fetchedAtMs":%s,"usage":{"headline":"subscription=max tier=default_claude_max_20x","plan":"max","meters":[{"label":"5h","used_pct":18,"reset_ms":%s},{"label":"7d","used_pct":42,"reset_ms":%s}],"notes":[],"error":null}},"codex/personal":{"fetchedAtMs":%s,"usage":{"headline":"codex pro","plan":"pro","meters":[{"label":"5h","used_pct":8,"reset_ms":%s},{"label":"7d","used_pct":31,"reset_ms":%s}],"notes":[],"error":null}},"codex/team":{"fetchedAtMs":%s,"usage":{"headline":"codex team","plan":"team","meters":[{"label":"5h","used_pct":27,"reset_ms":%s},{"label":"7d","used_pct":54,"reset_ms":%s}],"notes":[],"error":null}}}}\n' "$now_ms" "$reset_5h" "$reset_7d" "$now_ms" "$reset_5h" "$reset_7d" "$now_ms" "$reset_5h" "$reset_7d" > "$AAS_CONFIG_DIR/usage-cache.json"`
-Enter
-Type `printf '%s\n' '#!/bin/sh' 'profile=${CODEX_HOME##*/}' 'account=${profile#codex-}' 'printf "✓ isolated Codex session\n account: %s\n profile: %s\n" "$account" "$profile"' > /tmp/aas-readme-bin/codex && chmod +x /tmp/aas-readme-bin/codex`
-Enter
-Type `export PATH="/tmp/aas-readme-bin:$PWD/target/release:$PATH"`
-Enter
-Type "clear"
-Enter
-Sleep 500ms
-Type "aas usage"
-Enter
-Sleep 1s
-Set TypingSpeed 25ms
-Show
-
-Sleep 1.5s
-
-Type "aas e personal"
-Enter
-Sleep 1.2s
-
-Type "aas e team"
-Enter
-Sleep 2s
-
-Hide
-Type `rm -rf "$AAS_CONFIG_DIR" /tmp/aas-readme-bin /tmp/aas-readme-native-codex`
-Enter
diff --git a/docs/assets/cli-usage-sessions.gif b/docs/assets/cli-usage-sessions.gif
deleted file mode 100644
index e3930b3..0000000
Binary files a/docs/assets/cli-usage-sessions.gif and /dev/null differ
diff --git a/docs/demo/fixture.sh b/docs/demo/fixture.sh
new file mode 100644
index 0000000..0903e40
--- /dev/null
+++ b/docs/demo/fixture.sh
@@ -0,0 +1,51 @@
+# shellcheck shell=bash
+# Demo fixture for the README/social recordings. Sourced (hidden) by the generated VHS tape.
+# Builds a throwaway aas state with three accounts and stub agent CLIs; touches nothing real.
+export AAS_CONFIG_DIR=/tmp/aas-demo/config
+export CODEX_HOME=/tmp/aas-demo/native-codex
+export CLAUDE_CONFIG_DIR=/tmp/aas-demo/native-claude
+rm -rf /tmp/aas-demo
+mkdir -p "$AAS_CONFIG_DIR/profiles/codex-personal" "$AAS_CONFIG_DIR/profiles/codex-team" \
+ "$AAS_CONFIG_DIR/profiles/claude-work" /tmp/aas-demo/bin "$CODEX_HOME" "$CLAUDE_CONFIG_DIR"
+
+cat > "$AAS_CONFIG_DIR/accounts.json" <<'JSON'
+{"version":1,"accounts":[
+ {"provider":"claude","name":"work","label":"Work","addedAt":"2026-07-12T00:00:00.000Z","profileType":"isolated"},
+ {"provider":"codex","name":"personal","label":"Personal","addedAt":"2026-07-12T01:00:00.000Z","profileType":"isolated"},
+ {"provider":"codex","name":"team","label":"Team","addedAt":"2026-07-12T02:00:00.000Z","profileType":"isolated"}]}
+JSON
+printf '%s\n' '{"claude":"work","codex":"personal","updated":"2026-07-12T03:00:00.000Z"}' > "$AAS_CONFIG_DIR/.active.json"
+printf '%s\n' '{"tokens":{"access_token":"demo-personal"}}' > "$AAS_CONFIG_DIR/profiles/codex-personal/auth.json"
+printf '%s\n' '{"tokens":{"access_token":"demo-team"}}' > "$AAS_CONFIG_DIR/profiles/codex-team/auth.json"
+
+now=$(($(date +%s) * 1000)); h=$((60 * 60 * 1000))
+meter() { printf '{"label":"%s","used_pct":%s,"reset_ms":%s}' "$1" "$2" $((now + $3 * h / 60)); }
+entry() { printf '"%s":{"fetchedAtMs":%s,"usage":{"headline":"%s","plan":"%s","meters":[%s,%s],"notes":[],"error":null}}' \
+ "$1" "$now" "$2" "$3" "$4" "$5"; }
+{
+ printf '{"version":1,"entries":{'
+ entry claude/work "subscription=max tier=default_claude_max_20x" max \
+ "$(meter 5h 86 47)" "$(meter 7d 62 $((52 * 60)))"; printf ','
+ entry codex/personal "codex pro" pro "$(meter 5h 12 $((3 * 60 + 12)))" "$(meter 7d 31 $((118 * 60)))"; printf ','
+ entry codex/team "codex team" team "$(meter 5h 41 $((1 * 60 + 38)))" "$(meter 7d 57 $((76 * 60)))"
+ printf '}}\n'
+} > "$AAS_CONFIG_DIR/usage-cache.json"
+
+# Stub agent CLIs: report which profile they were launched with instead of starting a real session.
+for agent in codex claude; do
+ cat > "/tmp/aas-demo/bin/$agent" <.tape. docs/demo/fixture.sh builds a throwaway state
+# under /tmp/aas-demo, so no real account is touched. Requires vhs and cargo. If vhs stops at
+# "could not open ttyd: EOF", run with VHS_NO_SANDBOX=true.
+set -euo pipefail
+
+mode=intro channel=readme out=""
+while [ $# -gt 0 ]; do
+ case "$1" in
+ --mode) mode="$2"; shift 2 ;;
+ --channel) channel="$2"; shift 2 ;;
+ --out) out="$2"; shift 2 ;;
+ -h|--help) sed -n '2,14p' "$0"; exit 0 ;;
+ *) echo "unknown argument: $1" >&2; exit 2 ;;
+ esac
+done
+
+root="$(cd "$(dirname "$0")/../.." && pwd)"
+scenes="docs/demo/scenes/$mode.tape"
+[ -f "$root/$scenes" ] || { echo "no scenes for mode '$mode' ($scenes)" >&2; exit 2; }
+
+case "$channel" in
+ readme) width=1000 height=560 font=16 pad=28 margin=24 ext=gif ;;
+ social) width=1280 height=720 font=20 pad=28 margin=24 ext=mp4 ;;
+ # On-screen text size depends on how many columns the canvas holds, not on its pixels: the
+ # usage table needs 80, so this is sized to 81 (measured with `tput cols` under VHS) and the
+ # feed scales it up.
+ square) width=860 height=860 font=15 pad=8 margin=0 ext=mp4 ;;
+ *) echo "unknown channel: $channel" >&2; exit 2 ;;
+esac
+
+# The intro GIF is the README asset; everything else is a build output for posting.
+if [ -z "$out" ]; then
+ if [ "$mode-$channel" = "intro-readme" ]; then
+ out="docs/assets/cli-demo.gif"
+ else
+ out="target/demo/aas-$mode-$channel.$ext"
+ fi
+fi
+
+cd "$root"
+mkdir -p "$(dirname "$out")"
+cargo build --release --quiet
+
+tmp="$(mktemp -d "${TMPDIR:-/tmp}/aas-demo.XXXXXX")"
+trap 'rm -rf "$tmp"' EXIT
+tape="$tmp/demo.tape"
+{
+ cat < "$tape"
+
+vhs "$tape"
+echo "wrote $out"
diff --git a/docs/demo/scenes/changes.tape b/docs/demo/scenes/changes.tape
new file mode 100644
index 0000000..1ff9561
--- /dev/null
+++ b/docs/demo/scenes/changes.tape
@@ -0,0 +1,34 @@
+# changes: what is new since the last release (release notes, update posts).
+# Rewrite these scenes for each release from CHANGELOG.md's [Unreleased] section.
+
+Hide
+Type `scene "New: a provider name runs its active account"`
+Enter
+Show
+Type "aas active codex"
+Sleep 300ms
+Enter
+Sleep 1.2s
+Type "aas exec codex"
+Sleep 300ms
+Enter
+Sleep 2.5s
+
+Hide
+Type `scene "Switch, and the provider name follows"`
+Enter
+Show
+Type "aas switch codex team"
+Sleep 300ms
+Enter
+Sleep 1s
+Type "aas exec codex"
+Sleep 300ms
+Enter
+Sleep 3s
+
+Hide
+Type `end_card "Update: brew upgrade aas" "Release notes: github.com/Open330/aas/releases"`
+Enter
+Show
+Sleep 3s
diff --git a/docs/demo/scenes/intro.tape b/docs/demo/scenes/intro.tape
new file mode 100644
index 0000000..b02f0f3
--- /dev/null
+++ b/docs/demo/scenes/intro.tape
@@ -0,0 +1,47 @@
+# intro: first-time introduction of aas (README, launch posts, landing pages).
+
+Hide
+Type `scene "Several agent accounts on one machine"`
+Enter
+Show
+Type "aas list"
+Sleep 300ms
+Enter
+Sleep 2.5s
+
+Hide
+Type `scene "Every account's remaining usage at a glance"`
+Enter
+Show
+Type "aas usage"
+Sleep 300ms
+Enter
+Sleep 4s
+
+Hide
+Type `scene "One session on another account, default login untouched"`
+Enter
+Show
+Type "aas exec team"
+Sleep 300ms
+Enter
+Sleep 1.5s
+Type "codex"
+Sleep 300ms
+Enter
+Sleep 2.5s
+
+Hide
+Type `scene "Or switch the default in one command"`
+Enter
+Show
+Type "aas switch codex team"
+Sleep 300ms
+Enter
+Sleep 2.5s
+
+Hide
+Type `end_card "aas: Agent Account Switcher" "brew install open330/tap/aas" "github.com/Open330/aas"`
+Enter
+Show
+Sleep 3.5s