Skip to content

chore: record CLI demos against a stub API for PR descriptions - #59

Open
alukach wants to merge 4 commits into
mainfrom
chore/demo-recorder
Open

alukach wants to merge 4 commits into
mainfrom
chore/demo-recorder

Conversation

@alukach

@alukach alukach commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Adds demo/, tooling to record terminal demos of the CLI for PR descriptions, as used for #58's. Each demo runs against a stub of the source.coop API filled with made-up records, so recording never touches a real account, login session or keychain.

cargo build
python3 -I demo/record.py demo/scripts/pr-NN.py             # record to demo/casts/ (ignored by git)
python3 -I demo/record.py demo/scripts/pr-NN.py --upload \
    --gif-base https://github.com/source-cooperative/source-coop-cli/raw/demo-assets/pr-NN   # upload, print PR markdown

What's in it

  • demo/stub.py serves demo/fixtures.json under /api/v1 from memory: products, data connections, /whoami, memberships, and the draft service-account endpoints, including source.coop#665's API keys.
    • Writes persist for the rest of a demo, so create and then view agree.
    • It checks nothing, and isn't meant for tests, which keep using wiremock.
  • demo/fixtures.json holds made-up records: demo-user, who owns demo-org, with four products, two data connections, three service accounts, and their trusts, API keys (one live, one revoked by GitHub's secret scanning) and memberships.
  • demo/record.py runs bash in a 120×34 pty and types each demo's commands like a person.
    • It waits on screen text before every keypress, and keeps real timing, with idle time capped at 2.5s.
    • Every demo gets a fresh stub on a free port, a throwaway HOME and SOURCE_TOKEN=demo.
    • With agg installed, it renders each cast as a GIF.
    • --upload --gif-base URL sends each cast to asciinema.org and prints markdown showing the GIF, linked to the player. GitHub's image proxy can't load asciinema.org's own previews, so the GIFs go on this repo's image-only demo-assets branch, one directory per PR. The README has the steps.
  • Demo scripts are written per PR and not kept. Each PR writes its own in demo/scripts/, which git ignores, showing what that PR changes. A kept script would only go stale between PRs. The stub and fixtures are kept and grow with each PR. The product demos below were recorded with a script that is no longer in the repo.
  • demo/README.md explains how to write a demo and how to use the recordings in a PR.

Demos recorded with it

These were recorded against the stub, hence the localhost URLs.

source-coop product list

product-list

source-coop product view

product-view

source-coop product create (prompted)

product-create

source-coop product list -i

product-browse

Notes

  • The uploads are public to anyone with the link, which is why the stub holds only made-up data. The README says so.
  • Something the recordings turned up on main: product edit reads GET /data-connections as a bare array, but the API answers with a DataConnectionPage. The lookup fails silently, so edit always offers all three visibilities, not just the ones the product's connection allows. That's for a separate fix.

Test plan

🤖 Generated with Claude Code

demo/record.py runs a demo script's commands in a pseudo-terminal, typed
like a person, and saves each as an asciicast; --upload puts them on
asciinema.org and prints markdown for a PR. demo/stub.py serves made-up
records from demo/fixtures.json in memory, and each session gets a fake
SOURCE_TOKEN and a throwaway HOME, so no demo reads the real login session
or keychain. demo/demos/products.py records the product commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Claude finished @alukach's task in 17s —— View job


✅ No blocking issues — safe to merge.

I read demo/record.py and demo/stub.py in full. I didn't read fixtures.json, demo/README.md or .gitignore, and I didn't run anything. The code is dev-only tooling that stays out of the Rust build and tests. I found no correctness or security problems:

  • Isolation: each demo gets a throwaway HOME, SOURCE_TOKEN=demo and a stub bound to 127.0.0.1. The real keychain and session aren't touched, as AGENTS.md requires.
  • Cleanup: the stub and the temp HOME are cleaned up in finally.
  • Uploads: --upload is opt-in and the stub holds only made-up data. The README and PR body both say that uploads are public.
  • Python version: str.removesuffix and the walrus operator need Python 3.9 or later. That's reasonable, but demo/README.md could say so.

Simplify (ponytail)

Advisory only.

  • demo/stub.py:57-171: the stub has handlers for service-account trusts, keys and revocations, but main has no commands that call them. They exist for feat: service-account commands for service accounts, their sign-ins and product access #58. Consider moving them into that PR so this one only carries what its own demos exercise.
  • demo/record.py:60-62, Session.see: the seen offset plus the ponytail: comment is fine. pause() just wraps time.sleep, so demos could call time.sleep directly.
  • demo/record.py:164-170: the asciinema upload wrapper and its URL regex are small and justified. No change.

💰 Estimated review cost: $0.14 · 0m17s · 5 turns

alukach and others added 2 commits October 16, 2026 18:49
… asciinema.org

record.py renders each cast as a GIF with agg, and --upload, which now
takes --gif-base, prints markdown showing the GIF linked to the player.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alukach added a commit that referenced this pull request Oct 17, 2026
asciinema.org's SVG previews show as broken images on GitHub, whose image
proxy can't fetch them, so demos are GIFs on the demo-assets branch linked
to the player, as #59's record.py now prints. Membership lists are pages on
source.coop since its #673, like every other listing. The stub's mechanics
are left to demo/README.md; this keeps the rules for extending it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
demo/demos/products.py was never run between PRs, so it could only go
stale, and whoever changes the CLI knows what to show of it. Scripts now
go in demo/scripts/, which git ignores; the stub, fixtures and recorder
stay and grow with each PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alukach added a commit that referenced this pull request Oct 17, 2026
asciinema.org's SVG previews show as broken images on GitHub, whose image
proxy can't fetch them, so demos are GIFs on the demo-assets branch linked
to the player, as #59's record.py now prints. Membership lists are pages on
source.coop since its #673, like every other listing. The stub's mechanics
are left to demo/README.md; this keeps the rules for extending it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant