detached 781dead

docs(demo): showcase remote sessions

commit
781dead95fdd5033723e971249f8b8e9afe16b6c
author
brice · · 2026-08-11 08:23 UTC
committer
brice · · 2026-08-11 10:01 UTC
parent
da1bbb1
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
10 files · +154 -88
A .dockerignore +4 -0
@@ -0,0 +1,4 @@
1+.git
2+.worktrees
3+scripts/demo/out
4+docs/assets/demo.gif
@@ -0,0 +1,4 @@
1+.git
2+.worktrees
3+scripts/demo/out
4+docs/assets/demo.gif
M Makefile +3 -4
@@ -53,10 +53,9 @@ demo:
5353 -y -v error -i /out/demo.gif -i /out/border.png -i /out/mask.png \
5454 -filter_complex "[0:v][1:v]overlay=0:0[bordered];[2:v]format=gray[m];[bordered][m]alphamerge,split[a][b];[a]palettegen=reserve_transparent=1[p];[b][p]paletteuse=alpha_threshold=128" \
5555 /out/demo-masked.gif
56- # --lossy above 30 smears the near-identical darks of the window chrome
57- # (window bar vs terminal background) into visible banding, and the
58- # conserve-memory fallback for huge inputs roughly doubles the output.
59- docker run --rm --entrypoint gifsicle -v "$(CURDIR)/scripts/demo/out:/out" vev-demo --no-conserve-memory -O3 --lossy=30 -o /out/demo.gif /out/demo-masked.gif
56+ # Keep the final GIF lossless: the streaming FFmpeg pass has already
57+ # bounded memory, and preserving terminal chrome avoids color banding.
58+ docker run --rm --entrypoint gifsicle -v "$(CURDIR)/scripts/demo/out:/out" vev-demo --no-conserve-memory -O3 -o /out/demo.gif /out/demo-masked.gif
6059 # Temp files are owned by the container's subuid, so remove them from
6160 # inside a container rather than the host.
6261 docker run --rm --entrypoint sh -v "$(CURDIR)/scripts/demo/out:/out" vev-demo -c 'rm /out/demo-masked.gif /out/mask.png /out/border.png'
@@ -53,10 +53,9 @@ demo:
53 -y -v error -i /out/demo.gif -i /out/border.png -i /out/mask.png \53 -y -v error -i /out/demo.gif -i /out/border.png -i /out/mask.png \
54 -filter_complex "[0:v][1:v]overlay=0:0[bordered];[2:v]format=gray[m];[bordered][m]alphamerge,split[a][b];[a]palettegen=reserve_transparent=1[p];[b][p]paletteuse=alpha_threshold=128" \54 -filter_complex "[0:v][1:v]overlay=0:0[bordered];[2:v]format=gray[m];[bordered][m]alphamerge,split[a][b];[a]palettegen=reserve_transparent=1[p];[b][p]paletteuse=alpha_threshold=128" \
55 /out/demo-masked.gif55 /out/demo-masked.gif
56- # --lossy above 30 smears the near-identical darks of the window chrome56+ # Keep the final GIF lossless: the streaming FFmpeg pass has already
57- # (window bar vs terminal background) into visible banding, and the57+ # bounded memory, and preserving terminal chrome avoids color banding.
58- # conserve-memory fallback for huge inputs roughly doubles the output.58+ docker run --rm --entrypoint gifsicle -v "$(CURDIR)/scripts/demo/out:/out" vev-demo --no-conserve-memory -O3 -o /out/demo.gif /out/demo-masked.gif
59- docker run --rm --entrypoint gifsicle -v "$(CURDIR)/scripts/demo/out:/out" vev-demo --no-conserve-memory -O3 --lossy=30 -o /out/demo.gif /out/demo-masked.gif
60 # Temp files are owned by the container's subuid, so remove them from59 # Temp files are owned by the container's subuid, so remove them from
61 # inside a container rather than the host.60 # inside a container rather than the host.
62 docker run --rm --entrypoint sh -v "$(CURDIR)/scripts/demo/out:/out" vev-demo -c 'rm /out/demo-masked.gif /out/mask.png /out/border.png'61 docker run --rm --entrypoint sh -v "$(CURDIR)/scripts/demo/out:/out" vev-demo -c 'rm /out/demo-masked.gif /out/mask.png /out/border.png'
M README.md +2 -2
@@ -12,7 +12,7 @@
1212 <p align="center">A terminal multiplexer for Linux and macOS.<br>
1313 Like tmux, minus the prefix key. Like mosh, minus mosh. One binary.</p>
1414
15-<p align="center"><img src="docs/assets/demo.gif" alt="vev demo: named sessions, splits, stacked panes, floating window, agent bell notification, detach and re-attach" width="800"></p>
15+<p align="center"><img src="docs/assets/demo.gif" alt="vev demo: local and remote sessions, splits, stacked panes, floating window, notifications, detach and re-attach" width="800"></p>
1616
1717 ---
1818
@@ -140,5 +140,5 @@ Optional file at `~/.config/vev/config`, reloaded live. Themes, key bindings, pa
140140 make test # go test ./... -race
141141 make lint # goimports check, go vet
142142 make mocks # regenerate mocks
143-make demo # regenerate docs/assets/demo.gif (needs Docker and ~/.claude credentials)
143+make demo # regenerate docs/assets/demo.gif (needs Docker)
144144 ```
@@ -12,7 +12,7 @@
12 <p align="center">A terminal multiplexer for Linux and macOS.<br>12 <p align="center">A terminal multiplexer for Linux and macOS.<br>
13 Like tmux, minus the prefix key. Like mosh, minus mosh. One binary.</p>13 Like tmux, minus the prefix key. Like mosh, minus mosh. One binary.</p>
14 14
15-<p align="center"><img src="docs/assets/demo.gif" alt="vev demo: named sessions, splits, stacked panes, floating window, agent bell notification, detach and re-attach" width="800"></p>15+<p align="center"><img src="docs/assets/demo.gif" alt="vev demo: local and remote sessions, splits, stacked panes, floating window, notifications, detach and re-attach" width="800"></p>
16 16
17 ---17 ---
18 18
@@ -140,5 +140,5 @@ Optional file at `~/.config/vev/config`, reloaded live. Themes, key bindings, pa
140 make test # go test ./... -race140 make test # go test ./... -race
141 make lint # goimports check, go vet141 make lint # goimports check, go vet
142 make mocks # regenerate mocks142 make mocks # regenerate mocks
143-make demo # regenerate docs/assets/demo.gif (needs Docker and ~/.claude credentials)143+make demo # regenerate docs/assets/demo.gif (needs Docker)
144 ```144 ```
M docs/assets/demo.gif +0 -0 binary file: no text diff

binary file: no text diff

M scripts/demo/Dockerfile +23 -9
@@ -7,23 +7,37 @@ COPY . .
77 RUN CGO_ENABLED=0 go build -o /out/vev .
88
99 FROM archlinux:latest
10-# The base image ships a pacman.conf that skips extracting man/info pages
11-# (NoExtract) to stay small; the demo wants a real `man ls`, so drop that
12-# rule and reinstall coreutils to pull its man page onto disk.
13-RUN pacman -Sy --noconfirm archlinux-keyring \
10+RUN --mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
11+ pacman -Sy --noconfirm archlinux-keyring \
1412 && sed -i '/^NoExtract.*usr\/share\/man/d' /etc/pacman.conf \
1513 && pacman -Syu --noconfirm \
1614 vhs ttyd ffmpeg chromium \
1715 htop fastfetch gifsicle imagemagick \
1816 eza neovim man-db man-pages coreutils \
19- nodejs npm ncurses \
20- ttf-jetbrains-mono-nerd \
21- && pacman -Scc --noconfirm
22-RUN npm install -g --allow-scripts=@anthropic-ai/claude-code @anthropic-ai/claude-code
17+ ncurses openssh util-linux \
18+ ttf-jetbrains-mono-nerd
2319 RUN useradd -m -u 1000 demo
2420 COPY --from=build /out/vev /usr/local/bin/vev
2521 COPY scripts/demo/ticker.sh /usr/local/bin/ticker
26-RUN chmod +x /usr/local/bin/ticker
22+COPY scripts/demo/demo-shell.sh /usr/local/bin/demo-shell
23+RUN chmod +x /usr/local/bin/ticker /usr/local/bin/demo-shell \
24+ && install -d -o demo -g demo -m 0700 /home/demo/.ssh \
25+ && install -d -m 0755 /run/sshd \
26+ && ssh-keygen -A \
27+ && ssh-keygen -q -t ed25519 -N '' -C vev-demo -f /home/demo/.ssh/id_ed25519 \
28+ && cp /home/demo/.ssh/id_ed25519.pub /home/demo/.ssh/authorized_keys \
29+ && { printf 'remote '; cut -d' ' -f1-2 /etc/ssh/ssh_host_ed25519_key.pub; } > /home/demo/.ssh/known_hosts \
30+ && printf '%s\n' \
31+ 'Host remote' \
32+ ' User demo' \
33+ ' IdentityFile ~/.ssh/id_ed25519' \
34+ ' IdentitiesOnly yes' \
35+ ' StrictHostKeyChecking yes' \
36+ ' UserKnownHostsFile ~/.ssh/known_hosts' \
37+ ' LogLevel ERROR' \
38+ > /home/demo/.ssh/config \
39+ && chmod 0600 /home/demo/.ssh/id_ed25519 /home/demo/.ssh/authorized_keys /home/demo/.ssh/known_hosts /home/demo/.ssh/config \
40+ && chown -R demo:demo /home/demo/.ssh
2741 USER demo
2842 WORKDIR /home/demo
2943 ENV VHS_NO_SANDBOX=true
@@ -7,23 +7,37 @@ COPY . .
7 RUN CGO_ENABLED=0 go build -o /out/vev .7 RUN CGO_ENABLED=0 go build -o /out/vev .
8 8
9 FROM archlinux:latest9 FROM archlinux:latest
10-# The base image ships a pacman.conf that skips extracting man/info pages10+RUN --mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \
11-# (NoExtract) to stay small; the demo wants a real `man ls`, so drop that11+ pacman -Sy --noconfirm archlinux-keyring \
12-# rule and reinstall coreutils to pull its man page onto disk.
13-RUN pacman -Sy --noconfirm archlinux-keyring \
14 && sed -i '/^NoExtract.*usr\/share\/man/d' /etc/pacman.conf \12 && sed -i '/^NoExtract.*usr\/share\/man/d' /etc/pacman.conf \
15 && pacman -Syu --noconfirm \13 && pacman -Syu --noconfirm \
16 vhs ttyd ffmpeg chromium \14 vhs ttyd ffmpeg chromium \
17 htop fastfetch gifsicle imagemagick \15 htop fastfetch gifsicle imagemagick \
18 eza neovim man-db man-pages coreutils \16 eza neovim man-db man-pages coreutils \
19- nodejs npm ncurses \17+ ncurses openssh util-linux \
20- ttf-jetbrains-mono-nerd \18+ ttf-jetbrains-mono-nerd
21- && pacman -Scc --noconfirm
22-RUN npm install -g --allow-scripts=@anthropic-ai/claude-code @anthropic-ai/claude-code
23 RUN useradd -m -u 1000 demo19 RUN useradd -m -u 1000 demo
24 COPY --from=build /out/vev /usr/local/bin/vev20 COPY --from=build /out/vev /usr/local/bin/vev
25 COPY scripts/demo/ticker.sh /usr/local/bin/ticker21 COPY scripts/demo/ticker.sh /usr/local/bin/ticker
26-RUN chmod +x /usr/local/bin/ticker22+COPY scripts/demo/demo-shell.sh /usr/local/bin/demo-shell
23+RUN chmod +x /usr/local/bin/ticker /usr/local/bin/demo-shell \
24+ && install -d -o demo -g demo -m 0700 /home/demo/.ssh \
25+ && install -d -m 0755 /run/sshd \
26+ && ssh-keygen -A \
27+ && ssh-keygen -q -t ed25519 -N '' -C vev-demo -f /home/demo/.ssh/id_ed25519 \
28+ && cp /home/demo/.ssh/id_ed25519.pub /home/demo/.ssh/authorized_keys \
29+ && { printf 'remote '; cut -d' ' -f1-2 /etc/ssh/ssh_host_ed25519_key.pub; } > /home/demo/.ssh/known_hosts \
30+ && printf '%s\n' \
31+ 'Host remote' \
32+ ' User demo' \
33+ ' IdentityFile ~/.ssh/id_ed25519' \
34+ ' IdentitiesOnly yes' \
35+ ' StrictHostKeyChecking yes' \
36+ ' UserKnownHostsFile ~/.ssh/known_hosts' \
37+ ' LogLevel ERROR' \
38+ > /home/demo/.ssh/config \
39+ && chmod 0600 /home/demo/.ssh/id_ed25519 /home/demo/.ssh/authorized_keys /home/demo/.ssh/known_hosts /home/demo/.ssh/config \
40+ && chown -R demo:demo /home/demo/.ssh
27 USER demo41 USER demo
28 WORKDIR /home/demo42 WORKDIR /home/demo
29 ENV VHS_NO_SANDBOX=true43 ENV VHS_NO_SANDBOX=true
D scripts/demo/claude-settings.json +0 -6
@@ -1,6 +0,0 @@
1-{
2- "preferredNotifChannel": "terminal_bell",
3- "permissions": {
4- "allow": ["Bash(sleep:*)"]
5- }
6-}
@@ -1,6 +0,0 @@
1-{
2- "preferredNotifChannel": "terminal_bell",
3- "permissions": {
4- "allow": ["Bash(sleep:*)"]
5- }
6-}
A scripts/demo/compose.yaml +17 -0
@@ -0,0 +1,17 @@
1+name: vev-demo
2+
3+services:
4+ remote:
5+ image: vev-demo
6+ hostname: remote
7+ user: root
8+ entrypoint: ["/usr/bin/sshd", "-D", "-e"]
9+
10+ client:
11+ image: vev-demo
12+ depends_on:
13+ - remote
14+ volumes:
15+ - ../../scripts/demo:/tape:ro
16+ - ../../scripts/demo/out:/home/demo/out
17+ - ../../scripts/demo/out/local:/home/demo/.local
@@ -0,0 +1,17 @@
1+name: vev-demo
2+
3+services:
4+ remote:
5+ image: vev-demo
6+ hostname: remote
7+ user: root
8+ entrypoint: ["/usr/bin/sshd", "-D", "-e"]
9+
10+ client:
11+ image: vev-demo
12+ depends_on:
13+ - remote
14+ volumes:
15+ - ../../scripts/demo:/tape:ro
16+ - ../../scripts/demo/out:/home/demo/out
17+ - ../../scripts/demo/out/local:/home/demo/.local
A scripts/demo/demo-shell.sh +26 -0
@@ -0,0 +1,26 @@
1+#!/usr/bin/env bash
2+set -euo pipefail
3+session=${VEV:-}
4+unset VEV
5+
6+case "${VEV_DEMO_SCENE:-}" in
7+remote-deploys)
8+ work_marker=/tmp/vev-demo-work-shown
9+ if [[ $session != session=work,* || -e $work_marker ]]; then
10+ exec /bin/bash --noprofile --norc
11+ fi
12+ : >"$work_marker"
13+ printf '\033]0;deploys · remote\007'
14+ printf '\n\033[1;36mDeploy queue · remote\033[0m\n\n'
15+ printf ' \033[32m✓\033[0m api-gateway production 3m ago\n'
16+ printf ' \033[32m✓\033[0m web-console production 7m ago\n'
17+ printf ' \033[33m•\033[0m worker-pool staging running\n\n'
18+ printf 'Use the session picker to jump between this host and local work.\n\n'
19+ exec /bin/bash --noprofile --norc
20+ ;;
21+remote-ticker)
22+ exec ticker
23+ ;;
24+esac
25+
26+exec /bin/bash --noprofile --norc
@@ -0,0 +1,26 @@
1+#!/usr/bin/env bash
2+set -euo pipefail
3+session=${VEV:-}
4+unset VEV
5+
6+case "${VEV_DEMO_SCENE:-}" in
7+remote-deploys)
8+ work_marker=/tmp/vev-demo-work-shown
9+ if [[ $session != session=work,* || -e $work_marker ]]; then
10+ exec /bin/bash --noprofile --norc
11+ fi
12+ : >"$work_marker"
13+ printf '\033]0;deploys · remote\007'
14+ printf '\n\033[1;36mDeploy queue · remote\033[0m\n\n'
15+ printf ' \033[32m✓\033[0m api-gateway production 3m ago\n'
16+ printf ' \033[32m✓\033[0m web-console production 7m ago\n'
17+ printf ' \033[33m•\033[0m worker-pool staging running\n\n'
18+ printf 'Use the session picker to jump between this host and local work.\n\n'
19+ exec /bin/bash --noprofile --norc
20+ ;;
21+remote-ticker)
22+ exec ticker
23+ ;;
24+esac
25+
26+exec /bin/bash --noprofile --norc
M scripts/demo/demo.tape +36 -26
@@ -20,13 +20,34 @@ Set WaitTimeout 30s
2020 # (70-160ms) and short hesitations at word boundaries so the rhythm
2121 # reads irregular/human; palette codes (CNS/SSP/…) stay single-speed.
2222
23-# ── Session "work": a colourful split (eza + ticker) ─────
24-Type@90ms "vev "
25-Sleep 160ms
26-Type@75ms "new "
27-Type@140ms "work"
23+# ── Local and remote history, shown immediately ──────────
24+Type@180ms "vev"
25+Sleep 600ms
2826 Enter
2927 Sleep 1.1s
28+# Keep the initial local shell so the final detach/attach cycle is meaningful.
29+Alt+" "
30+Sleep 550ms
31+Type "RNS"
32+Enter
33+Sleep 550ms
34+Backspace
35+Type@95ms "local"
36+Enter
37+Sleep 1s
38+
39+# The picker starts with the local shell plus two ready remote sessions.
40+Alt+" "
41+Sleep 550ms
42+Type "SSP"
43+Enter
44+Sleep 1.5s
45+Down 2
46+Sleep 700ms
47+Enter
48+Sleep 2.5s
49+
50+# The selected work session belongs to the remote container.
3051 Type@85ms "eza "
3152 Sleep 200ms
3253 Type@75ms "-la "
@@ -44,9 +65,7 @@ Enter
4465 Sleep 1.5s
4566
4667 # ── Session "agents": launch the bell job early so its
47-# sleep overlaps the middle scenes. claude -p prints the
48-# answer but emits no bell of its own (verified), so the
49-# pane rings it with printf. ───────────────────────────
68+# sleep overlaps the middle scenes. ─────────────────────
5069 Alt+" "
5170 Sleep 550ms
5271 Type "CNS"
@@ -56,13 +75,9 @@ Type@95ms "agents"
5675 Enter
5776 Sleep 900ms
5877 Type@80ms "sleep "
59-Type@140ms "10 "
60-Sleep 170ms
61-Type@90ms "&& claude "
62-Type@150ms "-p "
63-Type@105ms `"say hello world" `
78+Type@140ms "10"
6479 Sleep 220ms
65-Type@85ms "&& printf "
80+Type@85ms " && printf "
6681 Type@130ms `'\a'`
6782 Enter
6883 Sleep 1s
@@ -148,35 +163,30 @@ Sleep 900ms
148163 Alt+a
149164 Sleep 1.9s
150165
151-# ── Session picker now lists all three sessions ──────────
166+# ── Session picker now lists local and remote sessions ───
152167 Alt+" "
153168 Sleep 550ms
154169 Type "SSP"
155170 Enter
156-Sleep 1.2s
157-Down
158-Sleep 450ms
159-Down
160-Sleep 450ms
161-Down
171+Sleep 1.5s
172+Escape
162173 Sleep 650ms
163-Enter
164-Sleep 1.2s
165174
166-# ── Detach / re-attach: three sessions survive, ticker on ─
175+# ── Detach / re-attach: local sessions survive, ticker on ─
167176 Alt+" "
168177 Sleep 550ms
169178 Type "DET"
170179 Enter
171180 Sleep 1s
172181 Type@90ms "vev "
173-Type@75ms "ls"
182+Type@75ms "ls "
183+Type@120ms "--all"
174184 Enter
175185 Sleep 1.8s
176186 Type@90ms "vev "
177187 Sleep 150ms
178188 Type@80ms "attach "
179-Type@140ms "work"
189+Type@140ms "local"
180190 Enter
181191 Sleep 2.2s
182192 Sleep 400ms
@@ -20,13 +20,34 @@ Set WaitTimeout 30s
20 # (70-160ms) and short hesitations at word boundaries so the rhythm20 # (70-160ms) and short hesitations at word boundaries so the rhythm
21 # reads irregular/human; palette codes (CNS/SSP/…) stay single-speed.21 # reads irregular/human; palette codes (CNS/SSP/…) stay single-speed.
22 22
23-# ── Session "work": a colourful split (eza + ticker) ─────23+# ── Local and remote history, shown immediately ──────────
24-Type@90ms "vev "24+Type@180ms "vev"
25-Sleep 160ms25+Sleep 600ms
26-Type@75ms "new "
27-Type@140ms "work"
28 Enter26 Enter
29 Sleep 1.1s27 Sleep 1.1s
28+# Keep the initial local shell so the final detach/attach cycle is meaningful.
29+Alt+" "
30+Sleep 550ms
31+Type "RNS"
32+Enter
33+Sleep 550ms
34+Backspace
35+Type@95ms "local"
36+Enter
37+Sleep 1s
38+
39+# The picker starts with the local shell plus two ready remote sessions.
40+Alt+" "
41+Sleep 550ms
42+Type "SSP"
43+Enter
44+Sleep 1.5s
45+Down 2
46+Sleep 700ms
47+Enter
48+Sleep 2.5s
49+
50+# The selected work session belongs to the remote container.
30 Type@85ms "eza "51 Type@85ms "eza "
31 Sleep 200ms52 Sleep 200ms
32 Type@75ms "-la "53 Type@75ms "-la "
@@ -44,9 +65,7 @@ Enter
44 Sleep 1.5s65 Sleep 1.5s
45 66
46 # ── Session "agents": launch the bell job early so its67 # ── Session "agents": launch the bell job early so its
47-# sleep overlaps the middle scenes. claude -p prints the68+# sleep overlaps the middle scenes. ─────────────────────
48-# answer but emits no bell of its own (verified), so the
49-# pane rings it with printf. ───────────────────────────
50 Alt+" "69 Alt+" "
51 Sleep 550ms70 Sleep 550ms
52 Type "CNS"71 Type "CNS"
@@ -56,13 +75,9 @@ Type@95ms "agents"
56 Enter75 Enter
57 Sleep 900ms76 Sleep 900ms
58 Type@80ms "sleep "77 Type@80ms "sleep "
59-Type@140ms "10 "78+Type@140ms "10"
60-Sleep 170ms
61-Type@90ms "&& claude "
62-Type@150ms "-p "
63-Type@105ms `"say hello world" `
64 Sleep 220ms79 Sleep 220ms
65-Type@85ms "&& printf "80+Type@85ms " && printf "
66 Type@130ms `'\a'`81 Type@130ms `'\a'`
67 Enter82 Enter
68 Sleep 1s83 Sleep 1s
@@ -148,35 +163,30 @@ Sleep 900ms
148 Alt+a163 Alt+a
149 Sleep 1.9s164 Sleep 1.9s
150 165
151-# ── Session picker now lists all three sessions ──────────166+# ── Session picker now lists local and remote sessions ───
152 Alt+" "167 Alt+" "
153 Sleep 550ms168 Sleep 550ms
154 Type "SSP"169 Type "SSP"
155 Enter170 Enter
156-Sleep 1.2s171+Sleep 1.5s
157-Down172+Escape
158-Sleep 450ms
159-Down
160-Sleep 450ms
161-Down
162 Sleep 650ms173 Sleep 650ms
163-Enter
164-Sleep 1.2s
165 174
166-# ── Detach / re-attach: three sessions survive, ticker on ─175+# ── Detach / re-attach: local sessions survive, ticker on ─
167 Alt+" "176 Alt+" "
168 Sleep 550ms177 Sleep 550ms
169 Type "DET"178 Type "DET"
170 Enter179 Enter
171 Sleep 1s180 Sleep 1s
172 Type@90ms "vev "181 Type@90ms "vev "
173-Type@75ms "ls"182+Type@75ms "ls "
183+Type@120ms "--all"
174 Enter184 Enter
175 Sleep 1.8s185 Sleep 1.8s
176 Type@90ms "vev "186 Type@90ms "vev "
177 Sleep 150ms187 Sleep 150ms
178 Type@80ms "attach "188 Type@80ms "attach "
179-Type@140ms "work"189+Type@140ms "local"
180 Enter190 Enter
181 Sleep 2.2s191 Sleep 2.2s
182 Sleep 400ms192 Sleep 400ms
M scripts/demo/run.sh +43 -41
@@ -3,50 +3,52 @@ set -euo pipefail
33 cd "$(dirname "$0")/../.."
44
55 TAPE="${1:-demo.tape}"
6+COMPOSE_FILE="scripts/demo/compose.yaml"
67 install -d -m 0777 scripts/demo/out
8+# Demo containers use a rootless subuid mapping, so reset generated state
9+# from the same container identity rather than relying on host ownership.
10+docker run --rm --entrypoint sh -v "$PWD/scripts/demo/out:/out" vev-demo -c 'rm -rf /out/local && mkdir -m 0777 /out/local'
11+
12+compose() {
13+ docker compose -f "$COMPOSE_FILE" "$@"
14+}
715
8-CLAUDE_TMP=$(mktemp -d)
9-# claude writes files into the temp HOME as the container's demo user, which
10-# rootless docker maps to a subuid the host cannot delete directly. Clean up
11-# through the container first, where the creating uid can unlink its own
12-# files, then remove the (now-empty) temp dir from the host. This never
13-# suppresses a leftover: if anything still remains, it's reported so a
14-# credentials-bearing directory doesn't silently linger in /tmp.
1516 cleanup() {
16- # Every step here is best-effort: under `set -e`, an unguarded failure in an
17- # EXIT trap would abort the trap early and replace the script's real exit
18- # status with the failing cleanup command's. Guard each step with `|| true`
19- # so cleanup always runs to completion and never masks the vhs run's result;
20- # the final check below is what surfaces an unrecovered leftover.
21- rm -rf "$CLAUDE_TMP" 2>/dev/null || true
22- if [ -d "$CLAUDE_TMP" ]; then
23- docker run --rm -v "$CLAUDE_TMP:/cleanup" --entrypoint sh vev-demo \
24- -c 'rm -rf /cleanup/.claude /cleanup/.claude.json' 2>/dev/null || true
25- rmdir "$CLAUDE_TMP" 2>/dev/null || true
17+ compose down -v --remove-orphans >/dev/null 2>&1 || true
18+}
19+trap cleanup EXIT
20+
21+compose up -d remote
22+
23+ready=""
24+for _ in $(seq 60); do
25+ if compose run --rm --entrypoint ssh client remote true >/dev/null 2>&1; then
26+ ready=yes
27+ break
2628 fi
27- if [ -d "$CLAUDE_TMP" ]; then
28- echo "warning: could not remove claude temp home, left at $CLAUDE_TMP" >&2
29+ sleep 0.5
30+done
31+if [ -z "$ready" ]; then
32+ echo "error: sshd on the remote container never accepted a connection" >&2
33+ compose logs remote >&2 || true
34+ exit 1
35+fi
36+
37+start_remote_session() {
38+ local name=$1 scene=$2
39+ if ! compose exec -u demo -T remote env VEV=demo-bootstrap SHELL=/usr/local/bin/demo-shell VEV_DEMO_SCENE="$scene" vev new "$name"; then
40+ echo "error: could not create remote session '$name'" >&2
41+ return 1
42+ fi
43+ if ! compose exec -u demo -T remote vev ls | grep -q "^$name"; then
44+ echo "error: remote session '$name' was not retained" >&2
45+ compose exec -u demo -T remote vev ls >&2 || true
46+ return 1
2947 fi
30- return 0
3148 }
32-trap cleanup EXIT
33-mkdir -p "$CLAUDE_TMP/.claude"
34-cp "$HOME/.claude/.credentials.json" "$CLAUDE_TMP/.claude/"
35-cp scripts/demo/claude-settings.json "$CLAUDE_TMP/.claude/settings.json"
36-printf '{"hasCompletedOnboarding": true, "theme": "dark", "projects": {"/home/demo": {"hasTrustDialogAccepted": true, "hasCompletedProjectOnboarding": true, "projectOnboardingSeenCount": 1}}}\n' > "$CLAUDE_TMP/.claude.json"
37-
38-# Rootless docker maps host-owned files (uid 1000) to container root, but the
39-# container runs as demo (uid 1000 -> a subuid), so demo cannot read the
40-# credentials or write its config dir. Open up the throwaway home's contents so
41-# claude can authenticate and write transcripts, then restore the mktemp
42-# parent to 0700: bind mounts hand the container the inner paths directly, but
43-# other host users would have to traverse the parent, which now blocks them.
44-chmod -R a+rwX "$CLAUDE_TMP"
45-chmod 0700 "$CLAUDE_TMP"
46-
47-docker run --rm --hostname vev-demo \
48- -v "$PWD/scripts/demo:/tape:ro" \
49- -v "$PWD/scripts/demo/out:/home/demo/out" \
50- -v "$CLAUDE_TMP/.claude:/home/demo/.claude" \
51- -v "$CLAUDE_TMP/.claude.json:/home/demo/.claude.json" \
52- vev-demo "/tape/$TAPE"
49+
50+start_remote_session work remote-deploys
51+start_remote_session ticker remote-ticker
52+
53+compose run --rm --entrypoint vev client host add remote
54+compose run --rm client "/tape/$TAPE"
@@ -3,50 +3,52 @@ set -euo pipefail
3 cd "$(dirname "$0")/../.."3 cd "$(dirname "$0")/../.."
4 4
5 TAPE="${1:-demo.tape}"5 TAPE="${1:-demo.tape}"
6+COMPOSE_FILE="scripts/demo/compose.yaml"
6 install -d -m 0777 scripts/demo/out7 install -d -m 0777 scripts/demo/out
8+# Demo containers use a rootless subuid mapping, so reset generated state
9+# from the same container identity rather than relying on host ownership.
10+docker run --rm --entrypoint sh -v "$PWD/scripts/demo/out:/out" vev-demo -c 'rm -rf /out/local && mkdir -m 0777 /out/local'
11+
12+compose() {
13+ docker compose -f "$COMPOSE_FILE" "$@"
14+}
7 15
8-CLAUDE_TMP=$(mktemp -d)
9-# claude writes files into the temp HOME as the container's demo user, which
10-# rootless docker maps to a subuid the host cannot delete directly. Clean up
11-# through the container first, where the creating uid can unlink its own
12-# files, then remove the (now-empty) temp dir from the host. This never
13-# suppresses a leftover: if anything still remains, it's reported so a
14-# credentials-bearing directory doesn't silently linger in /tmp.
15 cleanup() {16 cleanup() {
16- # Every step here is best-effort: under `set -e`, an unguarded failure in an17+ compose down -v --remove-orphans >/dev/null 2>&1 || true
17- # EXIT trap would abort the trap early and replace the script's real exit18+}
18- # status with the failing cleanup command's. Guard each step with `|| true`19+trap cleanup EXIT
19- # so cleanup always runs to completion and never masks the vhs run's result;20+
20- # the final check below is what surfaces an unrecovered leftover.21+compose up -d remote
21- rm -rf "$CLAUDE_TMP" 2>/dev/null || true22+
22- if [ -d "$CLAUDE_TMP" ]; then23+ready=""
23- docker run --rm -v "$CLAUDE_TMP:/cleanup" --entrypoint sh vev-demo \24+for _ in $(seq 60); do
24- -c 'rm -rf /cleanup/.claude /cleanup/.claude.json' 2>/dev/null || true25+ if compose run --rm --entrypoint ssh client remote true >/dev/null 2>&1; then
25- rmdir "$CLAUDE_TMP" 2>/dev/null || true26+ ready=yes
27+ break
26 fi28 fi
27- if [ -d "$CLAUDE_TMP" ]; then29+ sleep 0.5
28- echo "warning: could not remove claude temp home, left at $CLAUDE_TMP" >&230+done
31+if [ -z "$ready" ]; then
32+ echo "error: sshd on the remote container never accepted a connection" >&2
33+ compose logs remote >&2 || true
34+ exit 1
35+fi
36+
37+start_remote_session() {
38+ local name=$1 scene=$2
39+ if ! compose exec -u demo -T remote env VEV=demo-bootstrap SHELL=/usr/local/bin/demo-shell VEV_DEMO_SCENE="$scene" vev new "$name"; then
40+ echo "error: could not create remote session '$name'" >&2
41+ return 1
42+ fi
43+ if ! compose exec -u demo -T remote vev ls | grep -q "^$name"; then
44+ echo "error: remote session '$name' was not retained" >&2
45+ compose exec -u demo -T remote vev ls >&2 || true
46+ return 1
29 fi47 fi
30- return 0
31 }48 }
32-trap cleanup EXIT49+
33-mkdir -p "$CLAUDE_TMP/.claude"50+start_remote_session work remote-deploys
34-cp "$HOME/.claude/.credentials.json" "$CLAUDE_TMP/.claude/"51+start_remote_session ticker remote-ticker
35-cp scripts/demo/claude-settings.json "$CLAUDE_TMP/.claude/settings.json"52+
36-printf '{"hasCompletedOnboarding": true, "theme": "dark", "projects": {"/home/demo": {"hasTrustDialogAccepted": true, "hasCompletedProjectOnboarding": true, "projectOnboardingSeenCount": 1}}}\n' > "$CLAUDE_TMP/.claude.json"53+compose run --rm --entrypoint vev client host add remote
37-54+compose run --rm client "/tape/$TAPE"
38-# Rootless docker maps host-owned files (uid 1000) to container root, but the
39-# container runs as demo (uid 1000 -> a subuid), so demo cannot read the
40-# credentials or write its config dir. Open up the throwaway home's contents so
41-# claude can authenticate and write transcripts, then restore the mktemp
42-# parent to 0700: bind mounts hand the container the inner paths directly, but
43-# other host users would have to traverse the parent, which now blocks them.
44-chmod -R a+rwX "$CLAUDE_TMP"
45-chmod 0700 "$CLAUDE_TMP"
46-
47-docker run --rm --hostname vev-demo \
48- -v "$PWD/scripts/demo:/tape:ro" \
49- -v "$PWD/scripts/demo/out:/home/demo/out" \
50- -v "$CLAUDE_TMP/.claude:/home/demo/.claude" \
51- -v "$CLAUDE_TMP/.claude.json:/home/demo/.claude.json" \
52- vev-demo "/tape/$TAPE"

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close