---
name: "dsh-conversation-capture"
description: "Rebuild a local DSH session as a conversation long image or a scrolling video: read session.v4.jsonl.zstd, lay it out directly with Pillow (no browser needed), encode mp4 with ffmpeg. Use for 'screenshot this conversation', 'long conversation screenshot', 'export the session record', 'make it a scrolling video'; it also lists and searches past sessions."
---

> Original skill · commissioned by the user · English edition, 2026-11-12

# DSH Conversation Asset Capture (Long Image + Scrolling Video)

## How this differs from the Codex version (important)

This skill was adapted from `codex-conversation-capture` with three changes for the DSH environment:

| Item | Codex version | This version |
|---|---|---|
| Data source | Codex `thread_history` | DSH `<SESSION_STORE>/**/session.v4.jsonl.zstd` |
| Image engine | Headless Chrome + CDP | **Pillow direct layout, no browser needed** |
| Video | Chrome screen recording | Pillow frames + ffmpeg encoding |

Why the change is mandatory: the local sandbox forbids processes from opening named pipes, so Chrome dies with `platform_channel Check failed` as soon as it starts and the headless-browser route is closed. Pillow direct layout spawns no child process and still produces images inside the sandbox. The cost is layout precision below the browser's (occasional orphan punctuation at line ends).

## Quick start

```powershell
python <SKILL_DIR>\scripts\capture.py doctor

# List recent sessions (title / turn count / time)
capture.py list --limit 15
capture.py list --search "screenshot"

# Produce a full-page long image (default: dark, 961 wide, 2x pixels)
capture.py run --session <id8> --last-turns 4 --out <WORKSPACE>\captures\chat

# Image + scrolling video (1080x1920 portrait)
capture.py run --session <id8> --turns 1-6 --do both

# Only the turns containing a keyword
capture.py run --search <keyword> --grep concurrency --grep-context 1
```

## Choosing a session

| Parameter | Description |
|---|---|
| `--session <id8>` | Session id prefix (first column in `list`), the most reliable option |
| `--search <keyword>` | Searches the full text of sessions; if several match it takes the newest and lists the candidates |
| `--latest` | The default when no argument is given: the latest session |

## Choosing content

| Parameter | Description |
|---|---|
| `--last-turns 4` | Render only the last 4 turns |
| `--turns 2-6` | Render a specified turn range |
| `--grep <word>` | Render only turns containing that word; `--grep-context 1` adds one turn before and after |
| `--mode clean` | Default: question + reply, a clean frame |
| `--mode full` | Additionally counts tool-call steps per turn (`ran N steps`) |

Note: **a turn with no reply is drawn as an empty turn** (that is what a conversation in progress looks like). Before capturing, confirm with `--last-turns` that the last few turns all have replies.

## Visual parameters

| Parameter | Default | Description |
|---|---|---|
| `--width` | 961 | Layout width (CSS px) |
| `--scale` | 2 | Pixel multiplier; `--width 961 --scale 2` produces 1922 wide |
| `--theme` | auto | `auto` (samples the DSH window background and follows the current theme) / `dark` / `light` |
| `--do` | png | `png` / `video` / `both` |
| `--fps` | 25 | Video frame rate |
| `--duration` | automatic | Estimated from page height (about 620px per second, 6–45 seconds) |
| `--video-width` / `--video-height` | 1080 / 1920 | Video frame size |
| `--out` | asset library | Output directory; if omitted it goes to `<WORKSPACE>/conversation-material/<time>_<title>_<id8>/` |

## Outputs

```
<out>/<time>_<title>_<id8>_full.png       # full-page long image
<out>/<time>_<title>_<id8>_scroll.mp4     # scrolling video (with --do both)
<out>/<time>_<title>_<id8>_manifest.json  # session / turns / size / sha256
```

## Acceptance

1. The command prints the image dimensions; `manifest.json` carries sha256 so you can verify.
2. Video: `ffprobe -select_streams v:0 -show_entries stream=width,height,nb_frames -of csv=p=0 x.mp4`.
3. Frame extraction check: `ffmpeg -ss 3 -i x.mp4 -frames:v 1 chk.png` (write it to disk only, do not paste it back into the conversation).
4. Long-image width should be `--width × --scale`; height varies with the number of turns, and if there are no black bars it is correct.

## Known limitations

- When an attachment image cannot be fetched (the sha is not under `<CACHE_DIR>/attachments/v1/objects`) it is skipped without an error.
- Very long sessions (>15000px): narrow it first with `--last-turns` or `--turns`, do not export everything at once.
- Tables are not drawn with ruled lines; they are treated as ordinary paragraphs.
- Indented multi-level lists are flattened by one level.
- `--theme auto` needs the DSH window open and not minimized; if it cannot sample, it falls back to the Windows app light/dark setting.
- When the whole page is shorter than the frame (1080×1920), the video produces a still clip with the background color padded above and below for `--duration`, and does not scroll.

## Dependencies

`python3` + `Pillow` (required); `ffmpeg` (video only); `zstandard` **optional** — the locally bundled python does not include it, and when it is missing the script reads the `.plain` sidecar file: run `node <WORKSPACE>\work\token-audit\dump.js <session.v4.jsonl.zstd>` first to generate it, landing at `<CACHE_DIR>\conversation-capture\<session-dir-name>.plain`. Everything runs locally; session content is never uploaded.

## Two routes: decide what you want first (added later; pixel capture ported from the Codex version)

| What you want | Which route | Command |
|---|---|---|
| Turn a **DSH conversation** into a long image / scrolling video (unobstructed, and archived sessions export too) | **Render** (read `session.v4.jsonl.zstd`, Pillow direct layout) | `capture.py run --session …` |
| What a **window / the whole desktop** looks like right now, and you want it recorded (real pixels, with cursor) | **Pixel capture** (PrintWindow + GDI + ffmpeg gdigrab) | `deskcap.py shot/record` |

Mnemonic: **"conversation" → render; "window / desktop / screen" → pixel capture.**

```powershell
$S = "<SKILL_DIR>\scripts"
python $S\deskcap.py windows                              # list visible windows (pid / size / title)
python $S\deskcap.py shot --pid 1234 --out work\a.png    # capture only that window
python $S\deskcap.py shot --full --out work\full.png     # whole screen
python $S\deskcap.py record --seconds 8 --pid 1234 --out work\a.mp4   # record the window
python $S\deskcap.py record --seconds 8 --out work\full.mp4           # record the whole screen
```

- Default output directory `work\capture\` (process files, not a delivery directory).
- `shot` prints the captured size and the color count; **a color count of ≤1 means a black screen / empty window was captured** — switch to `--full` or use the render route.
- Recording inside DSH goes through `ffmpeg gdigrab` and opens no named pipes; the recorded window does not have to be in the foreground, but **minimized windows cannot be recorded**.
- `<BRAND>`-style screen-recording utilities leave their recording border and red dot out of the capture by default; this script records raw window pixels, so collapse the indicator layer first if you need a clean picture.
