You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 337b406
Browse filesBrowse the repository at this point in the historyBrowse files
chore: keep the repository's helpers under docs/ and .github/
demo/, docs/ and preview/ were three top-level folders of helpers. The
demo recorder only produces docs/demos/, so it moves beside it as
docs/recorder/, with its one kept script, guide.py, at its top. The
preview installer is only used by the Preview build workflow, so it moves
to .github/preview/. docs/site/ and docs/demos/ stay put: the README links
the recordings there, and republishing a release runs docs/site/build.py
from that release's tag.
The Docs workflow skips docs/recorder/, so changing only the recorder
builds no docs preview, as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: AGENTS.md
+6-6Lines changed: 6 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@ How this CLI behaves, so every command behaves the same way. Each rule names the
10
10
-**Behaviour:** prompts, keys, output formats and defaults.
11
11
-**Files and environment variables** it reads or writes.
12
12
-**The docs site is the README.**`docs/site/build.py` turns it into the site at <https://source-cooperative.github.io/source-coop-cli/>, one page for each `##` section, so a new `##` heading is a new page. A `<!-- group: NAME -->` line (GitHub doesn't show it) starts a group of sections, listed under NAME in the site's sidebar and the README's contents: put a new section in the group a reader would look in, and add it to the contents list. The team's own workflows, preview builds and setup for whoever runs the sign-in server, are in CONTRIBUTING.md, not the README. Each published release publishes it as that version and, for the newest stable release, as `latest/`. Build it locally with `uv run --with markdown-it-py python docs/site/build.py --out target/site`.
13
-
-**Demos:** the README embeds recordings from `docs/demos/`, made by `demo/demos/guide.py`. When a change alters what one shows, update its script and re-record it (`demo/README.md` says how).
13
+
-**Demos:** the README embeds recordings from `docs/demos/`, made by `docs/recorder/guide.py`. When a change alters what one shows, update its script and re-record it (`docs/recorder/README.md` says how).
14
14
15
15
Before committing, read the README sections your change touches and fix anything now untrue, including text written before your change. The README isn't a changelog: describe how the CLI works now, not what changed. `every_command_is_in_the_readme` in `src/main.rs` fails if a command is missing from it, but it can't tell whether the description is right. That part is on you.
16
16
@@ -127,11 +127,11 @@ A reviewer should see what a change is like to use without building it. A PR tha
127
127
128
128
### Demos
129
129
130
-
Record with `demo/`: `demo/README.md` says how to write a demo, record it and get it into the PR.
130
+
Record with `docs/recorder/`: `docs/recorder/README.md` says how to write a demo, record it and get it into the PR.
131
131
132
-
-**Write the demo script for your change:** one per PR, in `demo/scripts/` (git ignores it). You know what your change does, so script that. The only kept scripts are the README's, in `demo/demos/`: re-record those your change affects, but don't add PR demos to them. Grow `demo/stub.py` and `demo/fixtures.json` with what your demos need, and commit those.
132
+
-**Write the demo script for your change:** one per PR, in `docs/recorder/scripts/` (git ignores it). You know what your change does, so script that. The only kept script is the README's, `docs/recorder/guide.py`: re-record the demos your change affects, but don't add PR demos to it. Grow `docs/recorder/stub.py` and `docs/recorder/fixtures.json` with what your demos need, and commit those.
133
133
-**One demo per command or flow,** each with a bold title. Show the interactive paths: prompts, ← back, the review, and `-i` screens with their keys. Show the failure paths too: a refusal, a typed confirmation typed wrong, and an exit code (`; echo "exit $?"`). Pipe `--json` through `jq -c` to keep it short.
134
-
-**Attach each as an SVG.** Paste what `record.py` prints into the description and create or edit the PR with `--attach` for each SVG: `gh` uploads them and points the markdown at the uploads. `demo/README.md` has the command. Use the SVGs `record.py` writes, not svg-term's own: GitHub's CSP stops those animating when opened directly. Don't commit them, and don't host them elsewhere. The README's demos, in `docs/demos/`, are the only SVGs committed.
134
+
-**Attach each as an SVG.** Paste what `record.py` prints into the description and create or edit the PR with `--attach` for each SVG: `gh` uploads them and points the markdown at the uploads. `docs/recorder/README.md` has the command. Use the SVGs `record.py` writes, not svg-term's own: GitHub's CSP stops those animating when opened directly. Don't commit them, and don't host them elsewhere. The README's demos, in `docs/demos/`, are the only SVGs committed.
135
135
-**Say in the PR that the demos run against the stub.**`record.py` draws the stub's URLs as production's (`source.coop`, `data.source.coop`) and the throwaway `HOME` as `~`, so the records in them could be mistaken for real ones.
136
136
-**Attachments are public:** this repository is public, and so are its PRs. Record only the stub's made-up data, never a real account or token.
137
137
-**Recording is a review pass.** An awkward message, or a screen titled wrongly, is a bug: fix it in the PR and re-record. Check what each recording shows, not only that it finished.
@@ -145,10 +145,10 @@ A PR that changes `README.md` or `docs/` gets a preview of the docs site at `htt
145
145
146
146
### The stub server
147
147
148
-
`demo/stub.py` stands in for the source.coop API when recording, serving the made-up records in `demo/fixtures.json`. `demo/README.md` explains how it runs. It isn't for tests, which mock the API with `wiremock`. When a demo needs more of it:
148
+
`docs/recorder/stub.py` stands in for the source.coop API when recording, serving the made-up records in `docs/recorder/fixtures.json`. `docs/recorder/README.md` explains how it runs. It isn't for tests, which mock the API with `wiremock`. When a demo needs more of it:
149
149
150
150
-**Never point a demo at a real environment instead.** Every session runs with `SOURCE_TOKEN=demo`, a throwaway `HOME` and `SOURCE_PROXY_URL` set to the stub, which gives it an environment of its own, so it can't read or remove a real login. Keep it that way, and leave sign-in out of demos: a saved session still lands in the OS keyring, under the stub's environment.
151
151
-**Keep fixtures obviously fake,** under `demo-user` and its organization `demo-org`. Add the records a new demo needs there.
152
152
-**Add endpoints as demos need them.** Anything else gets a 404 saying `The demo stub has no METHOD PATH`, so a missing endpoint is plain from the recording. Add a branch to `handle()` for each new path.
153
-
-**Match the API's shapes, not the CLI's guesses.** Take paths, bodies, status codes and response shapes from the OpenAPI spec (`https://staging.source.coop/api/openapi`) or source.coop's `src/app/api/v1` routes. `demo/check_stub.py` checks the stub's answers against that spec, and CI runs it on PRs that change `demo/`: give each new route a request in its `requests()`, so it's checked too. Listings come in pages (`{"items", "next_cursor"}`): products, data connections, memberships, service accounts, trusts and API keys. A stub that answers in a shape the API doesn't use makes the demo show behaviour real users never see, and hides the CLI bugs a demo should catch.
153
+
-**Match the API's shapes, not the CLI's guesses.** Take paths, bodies, status codes and response shapes from the OpenAPI spec (`https://staging.source.coop/api/openapi`) or source.coop's `src/app/api/v1` routes. `docs/recorder/check_stub.py` checks the stub's answers against that spec, and CI runs it on PRs that change `docs/recorder/`: give each new route a request in its `requests()`, so it's checked too. Listings come in pages (`{"items", "next_cursor"}`): products, data connections, memberships, service accounts, trusts and API keys. A stub that answers in a shape the API doesn't use makes the demo show behaviour real users never see, and hides the CLI bugs a demo should catch.
154
154
-**It checks almost nothing.** It answers errors as the API does, as RFC 9457 problems (`application/problem+json`, made by `error()`, with field errors as JSON Pointers), and only where a demo shows a refusal, such as a 409 for an ID that's taken or a key revoked twice. Add those per demo rather than reimplementing the API's rules.
Copy file name to clipboardExpand all lines: CONTRIBUTING.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -32,7 +32,7 @@ Running it again updates the preview.
32
32
-**It's for testing, not a release.** A preview's pre-release is never marked latest, so the release installers and Homebrew keep installing the real release, and neither the *Release* nor the *Docs* workflow runs for it.
33
33
-**Remove a preview you're done with:**`gh release delete NAME --cleanup-tag`, and `rm ~/.local/bin/source-coop-NAME` to uninstall it.
34
34
35
-
On a PR that changes the workflow or `preview/install.sh`, *Preview build* only builds the default preview, without publishing.
35
+
On a PR that changes the workflow or `.github/preview/install.sh`, *Preview build* only builds the default preview, without publishing.
Copy file name to clipboardExpand all lines: docs/recorder/README.md
+14-14Lines changed: 14 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,12 +1,12 @@
1
1
# Recording demos for PR descriptions
2
2
3
-
`demo/` records terminal demos of the CLI as [asciicasts](https://docs.asciinema.org/manual/asciicast/v2/), so a PR can show what a change looks like to use. Each recording runs against a stub of the source.coop API filled with made-up records, so nothing touches a real account or a real login.
3
+
`docs/recorder/` records terminal demos of the CLI as [asciicasts](https://docs.asciinema.org/manual/asciicast/v2/), so a PR can show what a change looks like to use. Each recording runs against a stub of the source.coop API filled with made-up records, so nothing touches a real account or a real login.
4
4
5
5
```bash
6
6
cargo build
7
-
# write demo/scripts/pr-60.py for your change (see "Writing a demo"), then:
8
-
python3 -I demo/record.py demo/scripts/pr-60.py # every demo in the script, to demo/casts/
9
-
python3 -I demo/record.py demo/scripts/pr-60.py product-create # just one
7
+
# write docs/recorder/scripts/pr-60.py for your change (see "Writing a demo"), then:
8
+
python3 -I docs/recorder/record.py docs/recorder/scripts/pr-60.py # every demo in the script, to docs/recorder/casts/
9
+
python3 -I docs/recorder/record.py docs/recorder/scripts/pr-60.py product-create # just one
10
10
```
11
11
12
12
It needs Python 3.10 or later on macOS or Linux, and Node.js for `npx`, which fetches [svg-term-cli](https://github.com/marionebl/svg-term-cli) 2.1.1 to render the SVGs. Putting them in a PR needs [`gh`](https://cli.github.com) 2.103 or later. Demos that pipe through `jq` need it installed.
@@ -15,17 +15,17 @@ It needs Python 3.10 or later on macOS or Linux, and Node.js for `npx`, which fe
15
15
16
16
-`stub.py` serves `fixtures.json` under `/api/v1` from memory. What a demo creates, edits or deletes, the commands after it see. Every demo starts from a fresh copy. It answers what the CLI asks, in the API's shapes, and checks nothing: it is for demos, not tests, which use `wiremock`. Outside `/api/v1` it stands in for the data proxy's S3 listing (ListObjectsV2 of the fixtures' `objects`), so `aws s3 ls` works against it.
17
17
-`fixtures.json` holds the records: the signed-in `demo-user`, who owns the organization `demo-org`, with its products, data connections, service accounts, trusts, API keys, memberships and the objects in its bucket. Add what a new demo needs here.
18
-
-`check_stub.py` makes a request to each of the stub's routes and validates the answers against the API's OpenAPI spec on staging. CI runs it on PRs that change `demo/`. Run it yourself after changing the stub or fixtures: `uv run --with jsonschema python demo/check_stub.py` (or `pip install jsonschema` first). When a demo needs a new route, add a request for it to `requests()`.
18
+
-`check_stub.py` makes a request to each of the stub's routes and validates the answers against the API's OpenAPI spec on staging. CI runs it on PRs that change `docs/recorder/`. Run it yourself after changing the stub or fixtures: `uv run --with jsonschema python docs/recorder/check_stub.py` (or `pip install jsonschema` first). When a demo needs a new route, add a request for it to `requests()`.
19
19
-`record.py` runs bash in a pseudo-terminal at 80×28 and types each demo's commands like a person. It records with real timing, capping idle time at 2.5s. The recording ends at the last prompt. Before it's saved, the stub's URLs are drawn as production's (`https://source.coop` for the site and API, `https://data.source.coop` for the proxy) and the throwaway `HOME` as `~`, so ports and temp paths don't distract; the CLI still only talks to the stub. Each is rendered as an animated SVG beside its cast, with its steps in a JSON file of the same name. The docs site's player marks the steps on its track and lists them: each command run, and each prompt that a key answers. The file also lists each key pressed (`key`, `enter`), which the player shows in the terminal's corner as the demo plays.
20
-
-`demos/guide.py` holds the script for the demos the README shows, which are kept in `docs/demos/` (below).
21
-
- No other demo scripts are kept. Each PR writes its own, for what it changes, in `demo/scripts/`, which git ignores. A script that isn't run between PRs only goes stale, and whoever made a change knows what to show of it.
20
+
-`guide.py` holds the script for the demos the README shows, which are kept in `docs/demos/` (below).
21
+
- No other demo scripts are kept. Each PR writes its own, for what it changes, in `docs/recorder/scripts/`, which git ignores. A script that isn't run between PRs only goes stale, and whoever made a change knows what to show of it.
22
22
23
23
## Writing a demo
24
24
25
25
A script is a Python file defining `DEMOS = {name: (title, function)}`, where each function drives a `Session`. `record.py` provides `DOWN`, `UP`, `RIGHT`, `LEFT`, `ESC` and `BACKSPACE`.
26
26
27
27
```python
28
-
#demo/scripts/pr-60.py
28
+
#docs/recorder/scripts/pr-60.py
29
29
defcreate(s):
30
30
s.run("source-coop product create") # type a command at the prompt and run it
31
31
s.see("Account") # wait for text (a regex) to appear
@@ -48,15 +48,15 @@ Wait with `see` before every keypress that answers a prompt. That prompt and the
48
48
49
49
Each session gets `SOURCE_TOKEN=demo`, `SOURCE_API_URL` and `SOURCE_PROXY_URL` pointing at two copies of the stub (the site and the data proxy, as in production), `SOURCE_PAGER=cat` and a throwaway `HOME`. The proxy URL gives the run an environment of its own: the CLI keys its keyring entries and cache files by environment, so it can't read or remove the real login session, and drafts land in the throwaway `HOME`. A demo that saves a session or credentials still writes an entry for that environment (`127.0.0.1:PORT`) to the OS keyring, so leave sign-in out of demos.
50
50
51
-
To show a signed-in CLI (`auth status`, `auth token`, `creds`, `aws`), call `signed_in(s)` from `demos/guide.py` first. It writes a made-up session, role credentials and an `~/.aws/config` profile into the run's `HOME`, where `s.home`, `s.stub_url` and `s.proxy_url` say. The keyring has nothing for the stub's environment, so the CLI reads those files, and as everything in them is fresh, it only reads: nothing reaches the keyring. Don't let such a demo refresh or sign in. The one thing that does reach the network is the CLI's own lookups elsewhere, such as a GitHub repository's subject claim. Use a public repository for those, like `source-cooperative/source.coop`.
51
+
To show a signed-in CLI (`auth status`, `auth token`, `creds`, `aws`), call `signed_in(s)` from `guide.py` first. It writes a made-up session, role credentials and an `~/.aws/config` profile into the run's `HOME`, where `s.home`, `s.stub_url` and `s.proxy_url` say. The keyring has nothing for the stub's environment, so the CLI reads those files, and as everything in them is fresh, it only reads: nothing reaches the keyring. Don't let such a demo refresh or sign in. The one thing that does reach the network is the CLI's own lookups elsewhere, such as a GitHub repository's subject claim. Use a public repository for those, like `source-cooperative/source.coop`.
52
52
53
53
## In the README
54
54
55
-
The README embeds animated SVGs from `docs/demos/`, the only recordings git keeps. `demos/guide.py` makes them all. When a change alters what one shows, update its function there and re-record just that demo, then check the SVG shows what the README says it does:
55
+
The README embeds animated SVGs from `docs/demos/`, the only recordings git keeps. `guide.py` makes them all. When a change alters what one shows, update its function there and re-record just that demo, then check the SVG shows what the README says it does:
@@ -67,9 +67,9 @@ A new demo needs a line in `guide.py`'s `DEMOS` and an image in the README. Each
67
67
`record.py` prints markdown showing each SVG by its local path. `gh` uploads the files you `--attach` and rewrites those paths to point at the uploads, so the SVGs live on GitHub with the PR and nothing else hosts them:
Run `gh` from the repository root, or keep `body.md` there: `gh` reads the paths relative to the body file, then to the working directory. The demos show production's URLs, but run against the stub: say so in the PR.
0 commit comments