Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/demo-gifs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: demo-gifs

# Render documentation screencasts (VHS -> GIF) and publish them to the orphan
# `media` branch, which the README embeds via raw URLs. Deliberately never runs
# on pull requests: VHS rendering is slow and would churn the branch. Regenerate
# on demand, and automatically on each release so the docs GIFs track released
# behavior (the release tag doubles as a cache-buster for GitHub's image proxy).
on:
workflow_dispatch:
release:
types: [published]

permissions:
contents: write # push rendered GIFs to the `media` branch

concurrency:
group: demo-gifs
cancel-in-progress: true

jobs:
render:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0

- name: Build rocm + demo mock server
run: |
cargo build --release -p rocm --bin rocm
cargo build --release -p e2e-cucumber --bin rocm-demo-env

# VHS + its runtime deps. ttyd/ffmpeg from apt; vhs pinned via `go install`
# (Go is preinstalled on the runner). Chrome (used by VHS to rasterize
# frames) is preinstalled; the sysctl re-enables its sandbox on Ubuntu 24.04
# runners, where unprivileged user namespaces are AppArmor-restricted.
- name: Install VHS toolchain
run: |
sudo apt-get update
sudo apt-get install -y ttyd ffmpeg
go install github.com/charmbracelet/vhs@v0.11.0
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true

# render.sh starts the mock server + isolated env, then runs vhs against a
# pure command tape (see docs/demos.md). Setup lives here, never in a tape.
- name: Render tapes
run: |
mkdir -p docs/media
for t in version engines services chat; do
docs/tapes/render.sh "docs/tapes/$t.tape"
done

- name: Publish to media branch
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: media
publish_dir: ./docs/media
force_orphan: true # single-commit branch; old GIF blobs are GC'd
user_name: github-actions[bot]
user_email: github-actions[bot]@users.noreply.github.com
commit_message: "chore(media): regenerate demo screencasts"
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ vLLM.
> guarantee of stability. APIs, commands, and behavior may change without
> notice. Intended for experimentation and early feedback only.

## Demos

Chat with a locally served model:

![rocm chat one-shot demo](https://raw.githubusercontent.com/ROCm/rocm-cli/media/chat.gif)

Inspect running model servers:

![rocm services list demo](https://raw.githubusercontent.com/ROCm/rocm-cli/media/services.gif)

<!--
The GIFs above are generated in CI and served from the orphan `media` branch;
they are never committed to source branches. See docs/demos.md to regenerate or
add a demo. Until the demo-gifs workflow has run once, these links 404.
-->

## Installation

The installer downloads a prebuilt bundle, verifies its SHA-256 checksum,
Expand Down
79 changes: 79 additions & 0 deletions docs/demos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<!--
Copyright © Advanced Micro Devices, Inc., or its affiliates.

SPDX-License-Identifier: MIT
-->

# Demo screencasts

The README embeds short terminal screencasts (GIFs) of common `rocm` commands.
They are authored as [VHS](https://github.com/charmbracelet/vhs) tapes, rendered
in CI, and published to the orphan **`media`** branch. GIFs are **never committed
to source branches** — the README references them by absolute raw URL, so the
binaries stay out of `main`'s history.

## Layout

| Path | Purpose |
| --- | --- |
| `docs/tapes/*.tape` | One VHS tape per demo — a **pure command sequence**, no setup. |
| `docs/tapes/render.sh` | Sets up the env + mock server, then runs `vhs <tape>`. |
| `docs/tapes/lib/demo-env.sh` | Sourced by `render.sh`; starts the mock server and points `rocm` at an isolated config. |
| `.github/workflows/demo-gifs.yml` | Builds the binaries, renders every tape, publishes GIFs to the `media` branch. |
| `docs/media/` | Render output directory (git-ignored; present only so `vhs` has a target). |

## Why setup lives in render.sh, not the tapes

VHS types on a fixed clock and **never waits for a command to finish**. If a tape
does its own setup (e.g. `source`-ing a helper that blocks while a server starts),
VHS keeps typing into a busy shell — the following commands never execute cleanly
and the "hidden" setup leaks into the recording. So all setup happens in
`render.sh` *before* `vhs` starts, and every tape is a pure command sequence that
runs against an already-ready shell.

## How the server-backed demos work

`chat` and `services` need a running model endpoint. Instead of a GPU or a real
model, they reuse the end-to-end test harness's mock OpenAI server via the
standalone `rocm-demo-env` binary (`tests/e2e-cucumber/src/bin/rocm-demo-env.rs`).
`render.sh` sources `demo-env.sh`, which:

1. Starts `rocm-demo-env` — boots the axum mock server on a loopback port and
plants a managed-service record into an isolated config dir.
2. Exports `ROCM_CLI_CONFIG_DIR` / `ROCM_CLI_DATA_DIR` / `ROCM_CLI_CACHE_DIR` so
`rocm` discovers the mock, and `ROCM_MOCK_CHAT_REPLY` for a realistic answer.
3. Installs an EXIT trap that stops the server when rendering finishes.

Output is deterministic and needs no hardware, so renders are reproducible.

## Regenerate locally

Install [VHS](https://github.com/charmbracelet/vhs) (plus `ttyd` + `ffmpeg`),
then from the repo root:

```bash
# Build the binaries the tapes invoke.
cargo build --release -p rocm --bin rocm
cargo build --release -p e2e-cucumber --bin rocm-demo-env

# Render one tape (writes docs/media/<name>.gif).
docs/tapes/render.sh docs/tapes/chat.tape
```

`render.sh` resolves binaries from `target/release` by default; override with
`ROCM_BIN_DIR=/path/to/bin`. Change the demo model with `ROCM_DEMO_MODEL`.

## Add a demo

1. Add `docs/tapes/<name>.tape` with `Output docs/media/<name>.gif` — a pure
command sequence (no `Hide`/`source`; `render.sh` provides the env).
2. Add `<name>` to the render loop in `.github/workflows/demo-gifs.yml`.
3. Reference the published GIF from the README:
`https://raw.githubusercontent.com/ROCm/rocm-cli/media/<name>.gif`.

## Triggers

The workflow runs on `workflow_dispatch` and on published releases — never on
pull requests (rendering is slow and would churn the `media` branch). GitHub
proxies README images through a cache; when a regenerated GIF looks stale, append
a cache-buster to the URL (e.g. `...chat.gif?v=<release-tag>`).
5 changes: 5 additions & 0 deletions docs/media/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Rendered demo screencasts are generated in CI and published to the orphan
# `media` branch (see .github/workflows/demo-gifs.yml) — never committed to the
# source branches. This keeps the directory present so `vhs` has an output path.
*.gif
!.gitignore
19 changes: 19 additions & 0 deletions docs/tapes/chat.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# rocm chat (one-shot, against a mock server) — regenerate with:
# docs/tapes/render.sh docs/tapes/chat.tape (see docs/demos.md)
#
# Pure command tape: the mock server + isolated env + PATH are provided by
# render.sh BEFORE vhs starts, so nothing here blocks the shell while VHS types.
Output docs/media/chat.gif

Set Shell "bash"
Set FontSize 18
Set Width 1050
Set Height 420
Set Padding 20
Set Theme "Catppuccin Mocha"
Set TypingSpeed 45ms

Type "rocm chat --provider local --model Qwen/Qwen3.5-0.6B --prompt 'What GPU am I running?'"
Sleep 500ms
Enter
Sleep 6s
18 changes: 18 additions & 0 deletions docs/tapes/engines.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# rocm engines list — regenerate with:
# docs/tapes/render.sh docs/tapes/engines.tape (see docs/demos.md)
#
# Pure command tape: PATH to the `rocm` binary is provided by render.sh.
Output docs/media/engines.gif

Set Shell "bash"
Set FontSize 18
Set Width 1000
Set Height 560
Set Padding 20
Set Theme "Catppuccin Mocha"
Set TypingSpeed 60ms

Type "rocm engines list"
Sleep 500ms
Enter
Sleep 3s
43 changes: 43 additions & 0 deletions docs/tapes/lib/demo-env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright © Advanced Micro Devices, Inc., or its affiliates.
#
# SPDX-License-Identifier: MIT

# shellcheck shell=bash
# Sourced by render.sh (NOT by the tapes themselves) before vhs starts. Starts
# the standalone mock OpenAI server (`rocm-demo-env`, which reuses the e2e test
# harness), puts the binaries on PATH, points `rocm` at an isolated config via env
# vars, and tears everything down when the sourcing shell exits. No GPU or real
# model required — output is deterministic, so the recorded screencast is
# reproducible.
#
# Honours (with defaults):
# ROCM_DEMO_MODEL model id shown in the demo
# ROCM_BIN_DIR dir holding the `rocm` and `rocm-demo-env` binaries
#
# `source` this (do not execute): it exports env into the current shell and sets
# an EXIT trap that stops the mock server.

: "${ROCM_DEMO_MODEL:=Qwen/Qwen3.5-0.6B}"
_repo="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
: "${ROCM_BIN_DIR:=${_repo}/target/release}"
export PATH="${ROCM_BIN_DIR}:${PATH}"

# A realistic canned reply so the recorded chat reads naturally; the mock server
# echoes this instead of its test-only default (see src/mock_server.rs).
export ROCM_MOCK_CHAT_REPLY="You're running on an AMD Instinct MI300X (gfx942)."

_demo_root="$(mktemp -d)"
# The mock ignores SIGINT/SIGHUP (VHS/ttyd emit some during terminal setup) and
# stops on the SIGTERM the trap below sends — so it survives until the tape runs.
rocm-demo-env --root "${_demo_root}" --model "${ROCM_DEMO_MODEL}" >"${_demo_root}/env.sh" 2>/dev/null &
_demo_pid=$!
# shellcheck disable=SC2064
trap "kill ${_demo_pid} 2>/dev/null; rm -rf '${_demo_root}'" EXIT

# Wait for the server to publish its env + readiness marker before continuing.
for _ in $(seq 1 100); do
grep -q 'ready on' "${_demo_root}/env.sh" 2>/dev/null && break
sleep 0.1
done
# shellcheck disable=SC1091
source "${_demo_root}/env.sh"
27 changes: 27 additions & 0 deletions docs/tapes/render.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Copyright © Advanced Micro Devices, Inc., or its affiliates.
#
# SPDX-License-Identifier: MIT

# Render a demo tape with the binaries + mock environment already in place.
#
# docs/tapes/render.sh docs/tapes/chat.tape
#
# Sourcing lib/demo-env.sh first (not from inside the tape) is deliberate: VHS
# types on a fixed clock and never waits for a command to return, so any setup
# done *inside* a tape races the typing. Doing it here means every tape is a pure
# command sequence that runs against a ready shell.
#
# Env overrides: ROCM_BIN_DIR (default target/release), ROCM_DEMO_MODEL.
set -u

cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)" || exit

tape="${1:?usage: render.sh <tape>}"

# Sets PATH to the binaries, exports ROCM_CLI_* at an isolated config, starts the
# mock server, and installs an EXIT trap that stops it when this script exits.
# shellcheck source=docs/tapes/lib/demo-env.sh
source docs/tapes/lib/demo-env.sh

vhs "${tape}"
19 changes: 19 additions & 0 deletions docs/tapes/services.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# rocm services list (against a mock server) — regenerate with:
# docs/tapes/render.sh docs/tapes/services.tape (see docs/demos.md)
#
# Pure command tape: the mock server + isolated env + PATH are provided by
# render.sh BEFORE vhs starts.
Output docs/media/services.gif

Set Shell "bash"
Set FontSize 18
Set Width 1000
Set Height 480
Set Padding 20
Set Theme "Catppuccin Mocha"
Set TypingSpeed 60ms

Type "rocm services list"
Sleep 500ms
Enter
Sleep 4s
18 changes: 18 additions & 0 deletions docs/tapes/version.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# rocm version — regenerate with:
# docs/tapes/render.sh docs/tapes/version.tape (see docs/demos.md)
#
# Pure command tape: PATH to the `rocm` binary is provided by render.sh.
Output docs/media/version.gif

Set Shell "bash"
Set FontSize 20
Set Width 900
Set Height 260
Set Padding 20
Set Theme "Catppuccin Mocha"
Set TypingSpeed 70ms

Type "rocm version"
Sleep 500ms
Enter
Sleep 2s
6 changes: 6 additions & 0 deletions tests/e2e-cucumber/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ publish = false
[lints]
workspace = true

# Standalone mock-environment runner for documentation screencasts (see
# docs/tapes/). Reuses this crate's mock server; not part of the test harness.
[[bin]]
name = "rocm-demo-env"
path = "src/bin/rocm-demo-env.rs"

[dependencies]
axum.workspace = true
cucumber = { version = "0.23", features = ["output-json", "output-junit"] }
Expand Down
Loading
Loading