refactor(demo): drive recording through real cli.py instead of a harness
test / unit (push) Successful in 14s
test / integration (push) Successful in 29s

The previous demo harness called the backend Python API directly,
which didn't match what a user typing `./cli.py start <agent>` would
actually see. The recording now goes through the real CLI surface:

- claude-bottle.demo.json + scripts/demo-setup.sh stage a demo
  manifest (one bottle, FAKE_TOKEN env, one unreachable git upstream)
  alongside a dummy SSH identity at ~/.cache/claude-bottle-demo/.
- docs/demo.tape types `./cli.py start demo`, answers the y/N
  preflight, and runs four bash probes via claude's `!` prefix
  (curl x3 + git push), so the recording shows real preflight output
  and real probe results.
- scripts/demo.sh wraps setup -> cli.py -> teardown for human use;
  scripts/demo-record.sh does the same around `vhs docs/demo.tape`.
- .gitignore picks up claude-bottle.json so a user's local manifest
  doesn't get tracked alongside .example / .demo siblings.

scripts/demo_harness.py is removed -- its behavior is fully replaced
by the cli.py + `!` flow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-13 01:26:41 -04:00
parent 4ef1cc58df
commit 030a6bc793
9 changed files with 176 additions and 314 deletions
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 2.4 MiB

+55 -22
View File
@@ -1,38 +1,71 @@
# VHS tape — produces docs/demo.gif from scripts/demo.sh.
# VHS tape — drives `./cli.py start demo` interactively and runs four
# bash probes via claude's `!` prefix. Setup (manifest + dummy SSH key
# + image pre-warm) and teardown happen outside the tape; record via
# `bash scripts/demo-record.sh`, which wraps both.
#
# Usage:
# brew install vhs # if you don't have it
# vhs docs/demo.tape # ~60-90s; writes docs/demo.gif
#
# Re-record on changes:
# - new scenarios → tweak Height below
# - faster overall → shorten the Sleep after the trailing summary
#
# The harness paces itself with its own time.sleep() calls so each
# scenario block has time to be read; VHS only needs to capture the
# whole run end-to-end.
# Re-record when the probe results, manifest, or cli.py preflight
# rendering change.
Output docs/demo.gif
Set Shell "bash"
Set FontSize 14
Set Width 1100
Set FontSize 13
Set Width 1180
Set Height 780
Set Padding 20
Set Theme "Catppuccin Mocha"
Set TypingSpeed 60ms
Set PlaybackSpeed 1.0
Set TypingSpeed 40ms
Hide
Type "clear"
Enter
Show
Type "bash scripts/demo.sh"
Sleep 500ms
# Real cli.py invocation — what a user with claude-bottle.json in cwd
# would type. The bottle declares one allowlist (only baked-in
# defaults), one git upstream (unreachable on purpose so gitleaks runs
# before the gate would forward), and a FAKE_TOKEN env var shaped like
# a GitHub PAT.
Type "./cli.py start demo"
Enter
Sleep 8s
# Confirm the y/N preflight. cli.py reads from /dev/tty.
Type "y"
Enter
# Warm-cache run takes ~14s; first-time runs that build images will be
# longer, but the wrapper pre-warms quietly so the recording sees a
# warm path. Pad a few seconds so the trailing PASS summary holds.
Sleep 20s
# Wait for the bottle to launch: networks created, pipelock + git-gate
# sidecars started, agent container started, claude boots.
Sleep 22s
# Probe 1 — allowlisted HTTPS reaches an allowlisted host via the
# bumped TLS tunnel. Baseline: the proxy isn't just blocking everything.
Type `! curl --proxy "$HTTPS_PROXY" -sw 'status=%{http_code}\n' -o /dev/null https://raw.githubusercontent.com/git/git/master/README.md`
Enter
Sleep 5s
# Probe 2 — non-allowlisted host. Pipelock's host filter refuses to
# forward; DLP doesn't even get a chance to run.
Type `! curl --proxy "$HTTPS_PROXY" -sw 'status=%{http_code}\n' -o /dev/null http://example.com/`
Enter
Sleep 5s
# Probe 3 — allowlisted host BUT body carries a credential pattern.
# api.anthropic.com is on the baked-in allowlist, so the host check
# passes; the DLP body scanner has to catch the ghp_ pattern.
Type `! curl --proxy "$HTTPS_PROXY" -sw 'status=%{http_code}\n' -o /dev/null --data "token=$FAKE_TOKEN" http://api.anthropic.com/dlp-probe`
Enter
Sleep 5s
# Probe 4 — git push of a file containing an AKIA-shaped key. The
# bottle's ~/.gitconfig rewrites the upstream URL to the git-gate via
# `insteadOf`, so this push hits the gate, gitleaks runs in the
# pre-receive hook, and rejects the ref before the gate would forward.
Type `! cd /tmp && rm -rf r && git init -qb main r && cd r && git config user.email demo@x && git config user.name demo && echo AKIAQRJHK7N5ZPM2VXTL > leak.txt && git add . && git commit -qm leak && git push ssh://git@upstream.invalid/path.git main`
Enter
Sleep 10s
# Leave claude. The launcher tears down the container, sidecars, and
# networks on session end.
Ctrl+D
Sleep 4s