docs(demo): add end-to-end demo with recorded GIF
Squashes the demo-build arc: initial GIF + scripts, refactor to drive recording through real cli.py, theme/timing tweaks, and the switch to prompt-driven probes.
This commit is contained in:
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
# Record docs/demo.gif via VHS. Runs setup, invokes `vhs docs/demo.tape`,
|
||||
# always tears down. Requires `vhs` (brew install vhs).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if ! command -v vhs >/dev/null 2>&1; then
|
||||
echo "demo-record: vhs not found on PATH (brew install vhs)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${CLAUDE_BOTTLE_OAUTH_TOKEN:-}" ]; then
|
||||
echo "demo-record: CLAUDE_BOTTLE_OAUTH_TOKEN is unset; claude inside the bottle will not auth" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v ffmpeg >/dev/null 2>&1; then
|
||||
echo "demo-record: ffmpeg not found on PATH (brew install ffmpeg) — needed for decimation pass" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bash scripts/demo-setup.sh
|
||||
trap 'bash scripts/demo-teardown.sh' EXIT
|
||||
|
||||
vhs docs/demo.tape
|
||||
|
||||
# VHS records in real time, which leaves long static stretches while
|
||||
# the bottle launches and commands wait for output. Run mpdecimate to
|
||||
# drop duplicate consecutive frames (TUI dead time) and re-time at
|
||||
# 12 fps. tpad clones the final frame for 4s so the gitleaks
|
||||
# rejection on the last beat dwells long enough to read on each GIF
|
||||
# loop. Re-encode through a 64-color palette to keep the file small.
|
||||
tmp=$(mktemp -d)
|
||||
trap 'bash scripts/demo-teardown.sh; rm -rf "$tmp"' EXIT
|
||||
cp docs/demo.gif "$tmp/raw.gif"
|
||||
ffmpeg -y -i "$tmp/raw.gif" \
|
||||
-vf "mpdecimate,setpts=N/12/TB,tpad=stop_duration=4:stop_mode=clone,scale=960:-1:flags=lanczos,palettegen=max_colors=64" \
|
||||
"$tmp/palette.png" -loglevel error
|
||||
ffmpeg -y -i "$tmp/raw.gif" -i "$tmp/palette.png" \
|
||||
-lavfi "mpdecimate,setpts=N/12/TB,tpad=stop_duration=4:stop_mode=clone,scale=960:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=bayer:bayer_scale=5" \
|
||||
docs/demo.gif -loglevel error
|
||||
|
||||
echo "demo-record: wrote $(ls -lh docs/demo.gif | awk '{print $5}') ($(ffprobe -v error -show_entries stream=duration -of default=nk=1:nw=1 docs/demo.gif | cut -d. -f1)s)"
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
# Prepare the working directory to run the recorded demo via cli.py:
|
||||
# - back up any existing claude-bottle.json so the user's real config
|
||||
# isn't clobbered
|
||||
# - install claude-bottle.demo.json as claude-bottle.json
|
||||
# - create a dummy SSH identity at the path the demo manifest expects
|
||||
# - pre-warm the bottle + git-gate images quietly so the recording
|
||||
# doesn't spend its first 30s in BuildKit output
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
echo "demo-setup: docker daemon not reachable" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Back up an existing local manifest (untouched if absent). Stored
|
||||
# alongside the manifest with a deterministic name so teardown can
|
||||
# find it without state files.
|
||||
if [ -f claude-bottle.json ]; then
|
||||
cp claude-bottle.json claude-bottle.json.demo-backup
|
||||
fi
|
||||
cp claude-bottle.demo.json claude-bottle.json
|
||||
|
||||
# Dummy SSH identity — the git-gate validator wants a readable file at
|
||||
# the IdentityFile path. Contents don't matter for the demo: the
|
||||
# unreachable upstream means the gate never actually uses the key.
|
||||
fake_key_dir="$HOME/.cache/claude-bottle-demo"
|
||||
mkdir -p "$fake_key_dir"
|
||||
chmod 700 "$fake_key_dir"
|
||||
printf 'not-a-real-key\n' > "$fake_key_dir/fake-key"
|
||||
chmod 600 "$fake_key_dir/fake-key"
|
||||
|
||||
# Build the image graph quietly so the recorded run shows only the
|
||||
# bottle launch and the four `!` probes, not BuildKit progress.
|
||||
docker build -q -t claude-bottle:latest . >/dev/null 2>&1 || true
|
||||
docker build -q -f Dockerfile.git-gate -t claude-bottle-git-gate:latest . >/dev/null 2>&1 || true
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Undo what demo-setup.sh did. Restores any pre-existing
|
||||
# claude-bottle.json, removes the dummy SSH identity. Idempotent.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
rm -f claude-bottle.json
|
||||
if [ -f claude-bottle.json.demo-backup ]; then
|
||||
mv claude-bottle.json.demo-backup claude-bottle.json
|
||||
fi
|
||||
|
||||
rm -rf "$HOME/.cache/claude-bottle-demo"
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# Human-runnable demo wrapper. Stages the demo manifest and dummy
|
||||
# identity (see scripts/demo-setup.sh), launches `./cli.py start demo`
|
||||
# interactively, then restores prior state. The recorded GIF
|
||||
# (docs/demo.gif) goes through the same flow via docs/demo.tape.
|
||||
#
|
||||
# Once attached to claude inside the bottle, use the `!` prefix to run
|
||||
# bash directly — e.g.
|
||||
# ! curl --proxy "$HTTPS_PROXY" -sw 'status=%{http_code}\n' \
|
||||
# -o /dev/null http://example.com/
|
||||
# returns 403 because example.com is not on the bottle's allowlist.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [ -z "${CLAUDE_BOTTLE_OAUTH_TOKEN:-}" ]; then
|
||||
cat <<'EOF' >&2
|
||||
demo: CLAUDE_BOTTLE_OAUTH_TOKEN is unset. The bottle launches claude,
|
||||
which needs the token to authenticate. Set it in your shell env (e.g.
|
||||
~/.zshrc) — see README §Auth — then re-run.
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bash scripts/demo-setup.sh
|
||||
trap 'bash scripts/demo-teardown.sh' EXIT
|
||||
|
||||
./cli.py start demo
|
||||
Reference in New Issue
Block a user