Demo agent
The /demo slash command launches a visual demo of your project. Junie CLI spins up a disposable virtual machine, builds and starts your app inside it, and then drives the running UI — clicking, typing, and taking screenshots — to show that a feature or recent change works. The result is a structured Markdown answer in the TUI plus a folder of output artifacts — screenshots, a screen recording, and a self-contained HTML report — that you can share or attach to a PR.
Think of it as 'show me, don't tell me': instead of asking Junie to read code and explain a change, you ask Junie to use the app and prove the change is real on screen.
Prerequisites
/demo requires:
Docker running on your machine. This is the most important prerequisite — the demo VM is a Docker container, and
/demowill not start without a working Docker daemon. Ifdocker psdoesn't work in your terminal,/demowon't work either. Start Docker Desktop before running/demo. Junie checks this before a run starts and tells you right away rather than failing halfway through, on every path that can launch a demo.Access to a model with Computer Use support. GPT‑6 Astra, GPT‑5.6 SOL, GPT‑5.5, and GPT‑5.4 are supported. If your active model is supported and available,
/demokeeps it. Otherwise, it picks the first available model in that order, preferring GPT‑6 Astra. If none are available,/demorefuses to start and shows the supported list. See Junie CLI Model selection.A demo VM image at
.junie/vms/<name>/Dockerfile, layered on top of the base imageregistry.jetbrains.team/p/junie-cli/containers/demo-base:2. The base is Debian bookworm and ships Chromium, Node.js, xterm, ffmpeg, xdotool, screenshot tooling and a headless Xvfb desktop with a window manager — everything Computer Use needs to drive a desktop or web app. When/demoruns in a project that has no VM template, Junie seeds a starter one for you — see First-run setup. Add the runtimes your app needs on top, as shown in Customizing the demo.
Quick start
You have two ways to set up /demo for a project: author the files by hand as shown below, or skip authoring and let /demo create starter versions for you (see First-run setup).
For projects that need anything beyond "open a browser at localhost", you'll typically set up three things in your repo:
A custom image on top of
demo-base. Create.junie/vms/<your-vm>/Dockerfilethat starts withFROM registry.jetbrains.team/p/junie-cli/containers/demo-base:2and adds the runtimes your app needs (JDK, Node, Python, system packages…). The base already handles the display stack — don't reinstall it.Node example:
# .junie/vms/node-vm/Dockerfile FROM registry.jetbrains.team/p/junie-cli/containers/demo-base:2 RUN apt-get update && apt-get install -y --no-install-recommends nodejs npmPython example:
# .junie/vms/python-vm/Dockerfile FROM registry.jetbrains.team/p/junie-cli/containers/demo-base:2 RUN apt-get update && apt-get install -y --no-install-recommends python3 python3-pipMounts (optional). The project root is always mounted at
/workspaceautomatically — you don't need to configure that. For extra bind mounts (host credentials, fixture data, prebuilt artifacts on the host), create a text file at.junie/vms/<your-vm>/mounts(no extension, not a directory) with one mount per line:# <hostPath>:<guestPath>[:ro|:rw] # $HOME and ${HOME} are expanded from the environment. # Lines starting with # are ignored. $HOME/.config/my-app:/root/.config/my-app:ro /tmp/my-fixtures:/workspace/fixturesMissing host paths are skipped with a warning — Junie won't refuse to start the VM because of a stale mount line.
A
.junie/demo.mdfile telling the agent which VM to use and how to build/launch the app. Thevm:value must match the subdirectory name you created in step 1 — that's how Junie binds the file to your Dockerfile.Node project —
vm: node-vmmatches.junie/vms/node-vm/:# My web app vm: node-vm ## Build inside the VM npm install && npm run build ## Launch npm run start & # ready when http://localhost:3000 responds 200Python project —
vm: python-vmmatches.junie/vms/python-vm/:# My Python app vm: python-vm ## Build inside the VM pip install -r requirements.txt ## Launch python app.py & # ready when http://localhost:5000 responds 200Shortcut: if you only ever need one VM, you can skip the subdirectory and put the Dockerfile straight at
.junie/vms/Dockerfile. Then reference it in.junie/demo.mdasvm: default.
Now /demo will pick up your custom image, mounts, and launch instructions automatically.
Basic usage
The command never starts a demo straight away — it always opens the picker:
Choosing what to demo
Junie asks what you would like it to demo and offers a short list of scopes to choose from, with a text field right below it for describing the demo yourself (navigate with the arrow keys, Enter to pick, Esc to cancel):
Changes of this branch (vs
origin/main) — everything the current branch adds on top of the default branch, including changes you haven't committed yet. The ref in the label is the default branch Junie resolved for your repository (origin/main,origin/master,main, ormaster).Changes from this session — the messages you exchanged, the files Junie touched, the task it just finished. You don't have to repeat what was done; the demo agent already sees it.
Uncommitted changes — your working tree compared to
HEAD. Files that aren't tracked by git yet are picked up too.Last commit — the last commit compared to its parent.
Smoke test — find anything broken — Junie drives the app's main flows looking for breakage and reports what works and what doesn't.
The list adapts to your project: the branch scope only shows up when you're not on the default branch, Uncommitted changes only when the working tree is dirty, Last commit only when there is a commit to compare against. Outside a git repository (or when git isn't installed) you get the session scope and the smoke test.
For the three git scopes the diff is the specification of the demo: Junie reads it first, lists every user-visible change in it, and demonstrates them one by one instead of stopping at the first one. This is the most common way to use /demo: you've just had Junie implement or fix something, and you want to see it working before you commit — type /demo, hit Enter, and pick the scope that matches what you want to see.
Describing the demo yourself
None of the scopes has to fit: the picker always has a free‑form field below the list. Press ↓ past the last scope to get into it (or type the request right after /demo to land in it directly), and type what you want to see in plain natural language:
Enter starts the run, ↑ takes you back to the list of scopes, Esc cancels. The request is passed to the demo agent verbatim, so the more concrete it is, the tighter the demo. Such a request is treated as self‑contained — Junie won't go hunting through git history to find unrelated context.
Demoing a specific feature from scratch
When you want a demo of an existing feature (not a recent change), name the feature explicitly in the same text field:
Junie will resolve how to reach it (menu item, hotkey, URL, etc.) and walk through it.
What happens when you run /demo
A /demo run goes through four phases. Knowing the phases helps you interpret what's on screen at any moment.
1. Plan
Before any VM starts, Junie writes a short visible demo plan — an ordered list of user‑visible milestones, e.g.:
When the run is scoped to a diff — one of the git scopes of the picker, or a review-style request — Junie reads that diff before planning and turns every user-visible change in it into a milestone, so the plan covers the whole change set.
The plan is your contract with the agent. If a step looks wrong, interrupt with Esc and adjust the request before the VM starts.
2. Build and launch
Junie:
(Optionally) builds artifacts on your host machine — but only if
.junie/demo.mdexplicitly asks for it. By default nothing is built on the host.Starts the VM (
vm_start). Your project root is mounted at/workspaceinside it.Builds the project inside the VM (commands come from
.junie/demo.mdif present, otherwise Junie figures them out).Launches the app in the background and waits for it to be ready (health endpoint, log line, or just a visual wait).
A blank screen right after vm_start is normal — the app hasn't started yet.
3. Demonstrate
Junie drives the running app via Computer Use: clicks, keystrokes, screenshots. The TUI shows a Working… esc to stop indicator while the agent is acting. Each action is followed by an automatic screenshot, so you'll see the run progress step by step.
If a visual attempt fails 2–3 times and Junie can't figure out the next step from the screen, it falls back to targeted code lookups (grep + open file). This is allowed but rare — the primary source of truth is the running app, not the source code.
4. Finish
Junie CLI shuts down the VM (vm_stop) and writes a structured Markdown report with two main sections:
What has been tested: the steps that ran and what was visible.
Result: pass / fail / partial, plus any issues spotted.
The screenshots, video, and HTML report from the run are saved alongside the session — see Output artifacts for the exact paths.
Stopping a demo
Press Esc while the
Working…indicator is visible to interrupt the agent. The VM is shut down cleanly.The agent won't launch the VM twice in one session unless you explicitly ask.
Output artifacts
After a /demo run finishes, you get a folder of artifacts on disk that you can share, attach to a PR, or rewatch later. They live under your Junie home directory, grouped by session:
What each one contains:
report.html: the main artifact. A single self-contained page with the user request, the agent's Markdown answer ('What has been tested' + result), the embedded video, and the screenshots inline. This is what you usually attach to a PR or send to a teammate.demo.mp4: the screen recording. Only present if the recorder started and was stopped cleanly before VM teardown. Embedded insidereport.htmltoo.captions.vtt: WebVTT caption track generated from screenshot timestamps. Used byreport.htmlto label moments in the video.screenshots/: every screenshot Computer Use took during the run, one PNG percomputercall.
The Markdown answer the agent shows in the TUI is not written to a separate file — it lives in the session transcript and inside report.html. If you want the raw text, copy it from the TUI or open the report.
Persistence: artifacts are kept across sessions — nothing is auto-cleaned. If you want them gone, delete the session folder under ~/.junie/sessions/ manually.
Customizing the demo
.junie/demo.md tells the demo agent project‑specific build and launch commands. Use it when 'how to start the app' isn't obvious from the source tree. The Quick start above shows the basic structure; this section lists the kinds of things you can usefully put inside.
Common things to put in .junie/demo.md:
vm: <name>— required. The agent passes this value tovm_startas the template name, and the action fails if it's missing. The name must resolve to a Dockerfile under.junie/vms/: either a subdirectory (.junie/vms/<name>/Dockerfile) or the literal valuedefaultfor the top-level.junie/vms/Dockerfile.The exact build command(s) to run inside the VM.
The launch command — always background it (
&ornohup … &).A health check Junie can wait on before interacting with the UI, e.g.
# ready when http://localhost:3000 responds 200.A preflight check for services you run on the host yourself — inside the VM your host machine is reachable as
host.docker.internal, so a line likecurl -sf http://host.docker.internal:9400/healthlets the demo fail fast when the backend isn't up.Test users the agent may log in with, and known quirks it should avoid. Never put secret values in
demo.md— the file is committed; pass secrets to the VM through the template'smountsfile instead.Whether the jar/binary should be built on the host first (and the exact
bashcommand to do it — Junie will only run it if you explicitly ask).
First-run setup
Before every run, /demo checks that your project has both halves of a working configuration:
a VM template — some
.junie/vms/<name>/Dockerfile, anda launch command in
.junie/demo.md.
If either is missing, the run won't start a real demo. Instead, Junie creates whatever isn't there yet and asks how you want to proceed. This is not a one-shot: a demo.md that was seeded and never touched counts as no setup at all, so the prompt keeps coming back until you edit the file. Junie never rewrites a file that already exists.
Up to two files appear in your project:
What gets seeded
.junie/demo.md— the project-level guide that tells the demo agent which VM to use and how to launch your app. The file is free-form Markdown; the agent reads it as instructions before doing anything. Two things in it are essential:vm:: the name of a subdirectory under.junie/vms/. The seeded file already hasvm: template-vmwired up.The launch command: how to start your app inside the VM. Goes under the
## Running inside the VMsection.
Everything else is optional. Useful things to grow
demo.mdwith later include: host-side build steps you want Junie to run, environment variables, test users, known quirks, or links to other docs..junie/vms/template-vm/Dockerfile— the VM image. Extendsregistry.jetbrains.team/p/junie-cli/containers/demo-base:2— see Prerequisites for what the base ships. Add the apt packages and language runtimes your app needs on top.
Filling it in
Right after seeding, Junie shows you the two files and offers a choice (navigate with the arrow keys, enter to pick):
Yes, fill them in for me — Junie inspects your project (package manager, dev/start command, port, runtime version requirements, backing services, login walls, required tokens), asks you to confirm the resulting plan, and only then fills in
demo.mdand theDockerfile— plus amountsline when a host-side credential has to reach the VM. Review what it wrote, then re-run/demo.No, I'll do it myself — Junie leaves the seeded files untouched so you can edit them by hand.
You can also trigger the automatic fill-in later — ask Junie to "set up /demo ", or invoke the demo-setup skill with $demo-setup. Either way Junie edits only demo.md, the VM Dockerfile, and — when a host credential must reach the VM — the template's mounts file, and leaves the final review and the real demo run to you.
After editing
Once both files are filled in, re-run /demo. Junie will pick up the VM template and start a real demo run. From then on your configuration is treated as authoritative and Junie leaves it alone — the check is simply whether demo.md still looks exactly like the file that was seeded. Edit it in any way and it is yours; Junie stops asking and never second-guesses what you wrote.
To go through the setup again, either ask Junie to "set up /demo ", or clear the configuration and re-run /demo: emptying .junie/demo.md brings back the prompt while keeping your VM template, and deleting both .junie/vms/ and .junie/demo.md reseeds them from scratch.
If you'd rather author .junie/demo.md and the Dockerfile from scratch without using the seeded starter, just create them yourself before the first /demo run — the seed only fires when nothing is there.
Multiple VM templates in one project
.junie/vms/ is a directory — you can put as many VM subdirectories under it as you want, each with its own Dockerfile (and optionally its own mounts file). A typical layout for a project that ships several VMs side by side:
All templates discovered under .junie/vms/ are listed to the demo agent on every /demo run, so it can pick the right one according to the instructions in .junie/demo.md.
Examples
Verify a fix you just made
Junie picks up the change, opens the search box, types several characters, and shows that focus stays.
Demo a feature in a fresh repo
Useful for onboarding videos or PR descriptions.
Compare a 'before / after' change
If the session history doesn't contain a clear "before" state, Junie will note that in the report instead of inventing one.
Troubleshooting
- 'Demo agent requires access to a model with computer use support…'
None of the supported models are available for your account: GPT‑6 Astra, GPT‑5.6 SOL, GPT‑5.5, or GPT‑5.4. Check model availability via Junie CLI Model selection.
- The VM starts but the screen stays blank
Normal in the first few seconds after
vm_start. If it persists, the app probably failed to launch — check.junie/demo.mdfor the right build/launch commands, or add a health check Junie can wait on.- Junie keeps reading source files instead of clicking
This usually means the entry path (how to reach the feature) isn't obvious. Describe the entry path yourself in the request field, for example
open Settings via Cmd+, and toggle X./demois disabled / greyed outEither Docker isn't running, or no Computer Use model is available for your account.
Related
Slash commands — full list of slash commands.
Junie CLI Model selection — picking a model that supports Computer Use.