Skip to content

Commit 45b5eda

Browse files
committed
docs: add VHS demo screencast pipeline
Generate terminal demo GIFs for the README from VHS tapes, rendered in CI and published to an orphan `media` branch so no binaries land in the source history. The README embeds them by absolute raw URL. Server-backed demos (chat, services) reuse the e2e harness's mock OpenAI server through a new standalone `rocm-demo-env` binary: it starts the mock, plants a managed-service record into an isolated config, and prints the env that points `rocm` at it — no GPU or real model needed, so renders are deterministic. The service-record schema is factored into a shared `write_service_record` so the cucumber World and the demo runner can't drift. The mock's chat reply is overridable via ROCM_MOCK_CHAT_REPLY for natural-reading screencasts. Rendering runs on workflow_dispatch and on release (never on PRs, where it would be slow and churn the branch); the release tag doubles as an image-cache buster. Stacked on #69 (the e2e harness this reuses); keep as draft until it lands. Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
1 parent 2075ec3 commit 45b5eda

13 files changed

Lines changed: 429 additions & 35 deletions

File tree

.github/workflows/demo-gifs.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
name: demo-gifs
2+
3+
# Render documentation screencasts (VHS -> GIF) and publish them to the orphan
4+
# `media` branch, which the README embeds via raw URLs. Deliberately never runs
5+
# on pull requests: VHS rendering is slow and would churn the branch. Regenerate
6+
# on demand, and automatically on each release so the docs GIFs track released
7+
# behavior (the release tag doubles as a cache-buster for GitHub's image proxy).
8+
on:
9+
workflow_dispatch:
10+
release:
11+
types: [published]
12+
13+
permissions:
14+
contents: write # push rendered GIFs to the `media` branch
15+
16+
concurrency:
17+
group: demo-gifs
18+
cancel-in-progress: true
19+
20+
jobs:
21+
render:
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v6
25+
26+
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
27+
28+
- name: Build rocm + demo mock server
29+
run: |
30+
cargo build --release -p rocm --bin rocm
31+
cargo build --release -p e2e-cucumber --bin rocm-demo-env
32+
33+
- name: Prepare output directory
34+
run: mkdir -p docs/media
35+
36+
# One step per tape: vhs-action renders a single .tape. The server-backed
37+
# tapes source docs/tapes/lib/demo-env.sh, which starts the mock server and
38+
# puts target/release on PATH.
39+
- name: Render version
40+
uses: charmbracelet/vhs-action@59641cdc7fadf3978db65eb8c6937ea2752f4ec3 # v2.1.0
41+
with:
42+
path: docs/tapes/version.tape
43+
- name: Render engines
44+
uses: charmbracelet/vhs-action@59641cdc7fadf3978db65eb8c6937ea2752f4ec3 # v2.1.0
45+
with:
46+
path: docs/tapes/engines.tape
47+
- name: Render services
48+
uses: charmbracelet/vhs-action@59641cdc7fadf3978db65eb8c6937ea2752f4ec3 # v2.1.0
49+
with:
50+
path: docs/tapes/services.tape
51+
- name: Render chat
52+
uses: charmbracelet/vhs-action@59641cdc7fadf3978db65eb8c6937ea2752f4ec3 # v2.1.0
53+
with:
54+
path: docs/tapes/chat.tape
55+
56+
- name: Publish to media branch
57+
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
58+
with:
59+
github_token: ${{ secrets.GITHUB_TOKEN }}
60+
publish_branch: media
61+
publish_dir: ./docs/media
62+
force_orphan: true # single-commit branch; old GIF blobs are GC'd
63+
user_name: github-actions[bot]
64+
user_email: github-actions[bot]@users.noreply.github.com
65+
commit_message: "chore(media): regenerate demo screencasts"

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,22 @@ vLLM.
3232
> guarantee of stability. APIs, commands, and behavior may change without
3333
> notice. Intended for experimentation and early feedback only.
3434
35+
## Demos
36+
37+
Chat with a locally served model:
38+
39+
![rocm chat one-shot demo](https://raw.githubusercontent.com/ROCm/rocm-cli/media/chat.gif)
40+
41+
Inspect running model servers:
42+
43+
![rocm services list demo](https://raw.githubusercontent.com/ROCm/rocm-cli/media/services.gif)
44+
45+
<!--
46+
The GIFs above are generated in CI and served from the orphan `media` branch;
47+
they are never committed to source branches. See docs/demos.md to regenerate or
48+
add a demo. Until the demo-gifs workflow has run once, these links 404.
49+
-->
50+
3551
## Installation
3652

3753
The installer downloads a prebuilt bundle, verifies its SHA-256 checksum,

docs/demos.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<!--
2+
Copyright © Advanced Micro Devices, Inc., or its affiliates.
3+
4+
SPDX-License-Identifier: MIT
5+
-->
6+
7+
# Demo screencasts
8+
9+
The README embeds short terminal screencasts (GIFs) of common `rocm` commands.
10+
They are authored as [VHS](https://github.com/charmbracelet/vhs) tapes, rendered
11+
in CI, and published to the orphan **`media`** branch. GIFs are **never committed
12+
to source branches** — the README references them by absolute raw URL, so the
13+
binaries stay out of `main`'s history.
14+
15+
## Layout
16+
17+
| Path | Purpose |
18+
| --- | --- |
19+
| `docs/tapes/*.tape` | One VHS tape per demo (the recorded script). |
20+
| `docs/tapes/lib/demo-env.sh` | Sourced by server-backed tapes; starts the mock server and points `rocm` at an isolated config. |
21+
| `.github/workflows/demo-gifs.yml` | Builds the binaries, renders every tape, publishes GIFs to the `media` branch. |
22+
| `docs/media/` | Render output directory (git-ignored; present only so `vhs` has a target). |
23+
24+
## How the server-backed demos work
25+
26+
`chat` and `services` need a running model endpoint. Instead of a GPU or a real
27+
model, they reuse the end-to-end test harness's mock OpenAI server via the
28+
standalone `rocm-demo-env` binary (`tests/e2e-cucumber/src/bin/rocm-demo-env.rs`):
29+
30+
1. `demo-env.sh` starts `rocm-demo-env`, which boots the axum mock server on a
31+
loopback port and plants a managed-service record into an isolated config dir.
32+
2. It exports `ROCM_CLI_CONFIG_DIR` / `ROCM_CLI_DATA_DIR` / `ROCM_CLI_CACHE_DIR`
33+
so `rocm` discovers the mock, and sets `ROCM_MOCK_CHAT_REPLY` for a realistic
34+
canned answer.
35+
3. The tape runs the real `rocm` command; the server is killed on shell exit.
36+
37+
Output is deterministic and needs no hardware, so renders are reproducible.
38+
39+
## Regenerate locally
40+
41+
Install VHS (see its README), then from the repo root:
42+
43+
```bash
44+
# Build the binaries the tapes invoke.
45+
cargo build --release -p rocm --bin rocm
46+
cargo build --release -p e2e-cucumber --bin rocm-demo-env
47+
48+
# Render one tape (writes docs/media/<name>.gif).
49+
vhs docs/tapes/chat.tape
50+
```
51+
52+
Tapes resolve binaries from `target/release` by default; override with
53+
`ROCM_BIN_DIR=/path/to/bin`.
54+
55+
## Add a demo
56+
57+
1. Add `docs/tapes/<name>.tape` with `Output docs/media/<name>.gif`. For a
58+
server-backed demo, `source docs/tapes/lib/demo-env.sh` inside a `Hide` block.
59+
2. Add a `Render <name>` step to `.github/workflows/demo-gifs.yml`.
60+
3. Reference the published GIF from the README:
61+
`https://raw.githubusercontent.com/ROCm/rocm-cli/media/<name>.gif`.
62+
63+
## Triggers
64+
65+
The workflow runs on `workflow_dispatch` and on published releases — never on
66+
pull requests (rendering is slow and would churn the `media` branch). GitHub
67+
proxies README images through a cache; when a regenerated GIF looks stale, append
68+
a cache-buster to the URL (e.g. `...chat.gif?v=<release-tag>`).

docs/media/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Rendered demo screencasts are generated in CI and published to the orphan
2+
# `media` branch (see .github/workflows/demo-gifs.yml) — never committed to the
3+
# source branches. This keeps the directory present so `vhs` has an output path.
4+
*.gif
5+
!.gitignore

docs/tapes/chat.tape

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# rocm chat (one-shot, against a mock server) — regenerate with:
2+
# vhs docs/tapes/chat.tape (see docs/demos.md)
3+
Output docs/media/chat.gif
4+
5+
Set Shell "bash"
6+
Set FontSize 18
7+
Set Width 1050
8+
Set Height 420
9+
Set Padding 20
10+
Set Theme "Catppuccin Mocha"
11+
Set TypingSpeed 45ms
12+
13+
# Start the mock OpenAI server + isolated config, then point `rocm` at it.
14+
# All setup runs under Hide; `clear` + a short Sleep guarantee a clean frame
15+
# before Show resumes capture (without the Sleep, capture can resume before the
16+
# terminal has actually cleared).
17+
Hide
18+
Type "source docs/tapes/lib/demo-env.sh"
19+
Enter
20+
Sleep 2s
21+
Type "clear"
22+
Enter
23+
Sleep 500ms
24+
Show
25+
26+
Type "rocm chat --provider local --model Qwen/Qwen3.5-0.6B --prompt 'What GPU am I running?'"
27+
Sleep 500ms
28+
Enter
29+
Sleep 5s

docs/tapes/engines.tape

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# rocm engines list — regenerate with: vhs docs/tapes/engines.tape (see docs/demos.md)
2+
Output docs/media/engines.gif
3+
4+
Set Shell "bash"
5+
Set FontSize 18
6+
Set Width 1000
7+
Set Height 560
8+
Set Padding 20
9+
Set Theme "Catppuccin Mocha"
10+
Set TypingSpeed 60ms
11+
12+
Hide
13+
Type "export PATH=${ROCM_BIN_DIR:-$(git rev-parse --show-toplevel)/target/release}:$PATH"
14+
Enter
15+
Type "clear"
16+
Enter
17+
Sleep 500ms
18+
Show
19+
20+
Type "rocm engines list"
21+
Sleep 500ms
22+
Enter
23+
Sleep 3s

docs/tapes/lib/demo-env.sh

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright © Advanced Micro Devices, Inc., or its affiliates.
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
# shellcheck shell=bash
6+
# Sourced by the server-backed demo tapes (chat, services). Starts the standalone
7+
# mock OpenAI server (`rocm-demo-env`, which reuses the e2e test harness), points
8+
# `rocm` at an isolated config via env vars, and tears everything down when the
9+
# shell exits. No GPU or real model required — output is deterministic, so the
10+
# recorded screencast is reproducible.
11+
#
12+
# Honours (with defaults):
13+
# ROCM_DEMO_MODEL model id shown in the demo
14+
# ROCM_BIN_DIR dir holding the `rocm` and `rocm-demo-env` binaries
15+
#
16+
# `source` this (do not execute): it exports env into the current shell and sets
17+
# an EXIT trap that stops the mock server.
18+
19+
: "${ROCM_DEMO_MODEL:=Qwen/Qwen3.5-0.6B}"
20+
_repo="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
21+
: "${ROCM_BIN_DIR:=${_repo}/target/release}"
22+
export PATH="${ROCM_BIN_DIR}:${PATH}"
23+
24+
# A realistic canned reply so the recorded chat reads naturally; the mock server
25+
# echoes this instead of its test-only default (see src/mock_server.rs).
26+
export ROCM_MOCK_CHAT_REPLY="You're running on an AMD Instinct MI300X (gfx942)."
27+
28+
_demo_root="$(mktemp -d)"
29+
rocm-demo-env --root "${_demo_root}" --model "${ROCM_DEMO_MODEL}" >"${_demo_root}/env.sh" 2>/dev/null &
30+
_demo_pid=$!
31+
# shellcheck disable=SC2064
32+
trap "kill ${_demo_pid} 2>/dev/null; rm -rf '${_demo_root}'" EXIT
33+
34+
# Wait for the server to publish its env + readiness marker before continuing.
35+
for _ in $(seq 1 100); do
36+
grep -q 'ready on' "${_demo_root}/env.sh" 2>/dev/null && break
37+
sleep 0.1
38+
done
39+
# shellcheck disable=SC1091
40+
source "${_demo_root}/env.sh"

docs/tapes/services.tape

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# rocm services list (against a mock server) — regenerate with:
2+
# vhs docs/tapes/services.tape (see docs/demos.md)
3+
Output docs/media/services.gif
4+
5+
Set Shell "bash"
6+
Set FontSize 18
7+
Set Width 1000
8+
Set Height 480
9+
Set Padding 20
10+
Set Theme "Catppuccin Mocha"
11+
Set TypingSpeed 60ms
12+
13+
# Start the mock OpenAI server + isolated config, then point `rocm` at it.
14+
# All setup runs under Hide; `clear` + a short Sleep guarantee a clean frame
15+
# before Show resumes capture.
16+
Hide
17+
Type "source docs/tapes/lib/demo-env.sh"
18+
Enter
19+
Sleep 2s
20+
Type "clear"
21+
Enter
22+
Sleep 500ms
23+
Show
24+
25+
Type "rocm services list"
26+
Sleep 500ms
27+
Enter
28+
Sleep 4s

docs/tapes/version.tape

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# rocm version — regenerate with: vhs docs/tapes/version.tape (see docs/demos.md)
2+
Output docs/media/version.gif
3+
4+
Set Shell "bash"
5+
Set FontSize 20
6+
Set Width 900
7+
Set Height 260
8+
Set Padding 20
9+
Set Theme "Catppuccin Mocha"
10+
Set TypingSpeed 70ms
11+
12+
Hide
13+
Type "export PATH=${ROCM_BIN_DIR:-$(git rev-parse --show-toplevel)/target/release}:$PATH"
14+
Enter
15+
Type "clear"
16+
Enter
17+
Sleep 500ms
18+
Show
19+
20+
Type "rocm version"
21+
Sleep 500ms
22+
Enter
23+
Sleep 2s

tests/e2e-cucumber/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ publish = false
1010
[lints]
1111
workspace = true
1212

13+
# Standalone mock-environment runner for documentation screencasts (see
14+
# docs/tapes/). Reuses this crate's mock server; not part of the test harness.
15+
[[bin]]
16+
name = "rocm-demo-env"
17+
path = "src/bin/rocm-demo-env.rs"
18+
1319
[dependencies]
1420
axum.workspace = true
1521
cucumber = { version = "0.23", features = ["output-json", "output-junit"] }

0 commit comments

Comments
 (0)