← Commit history
Cargo.toml+3−3
@@ -1,12 +1,12 @@ [package]-name = "adom-video-post"-version = "1.0.7"+name = "video-post"+version = "1.0.1" edition = "2021" description = "Post-process screen recordings: speed up boring sections with overlay captions, voiceover after the fact, publish to the Adom Wiki" license = "MIT"  [[bin]]-name = "adom-video-post"+name = "video-post" path = "src/main.rs"  [dependencies]
README.md+37−51
@@ -1,16 +1,6 @@-# adom-video-post+# video-post -The **AI-driven studio for finishing demo videos.** Capture a demo as a series of clips, then review every snippet, flag and re-record the weak ones ("clip 3 is awful, redo it with…"), speed up the dead air, narrate, mux + auto-level the audio, validate the final cut, and publish to the wiki, driven end-to-end by your AI, no DaVinci Resolve.--![The review studio: every clip in a filmstrip with speedup badges, plus a per-clip review card showing the raw and sped-up takes side by side.](screenshots/review-studio.png)--The Review / Narrate / Publish phases live behind one tab. Narrate over the sped-up cut with a 3-2-1 countdown, then auto-level the audio:--![The voiceover overlay: record a narration over the sped-up video, keep every take, auto-level each one to EBU R128.](docs/voiceover-ui.png)--Slow sections become labelled timelapses with a clear on-screen banner so the viewer always knows what was happening:--![A speedup banner overlaid on a sped-up section: "20x SPEEDUP: Exporting gerbers".](docs/speedup-overlay.png)+A demo recording post-production CLI for the Adom platform.  ## Why this exists @@ -20,19 +10,19 @@ We are building a lot of demo recordings of Adom apps, skills, molecules, librar 2. Walk through a sequence of steps where Claude Code drives the workflow on the user's behalf (open a file, run a search, export gerbers, pull files back to Docker, etc.) 3. Stop the recording and upload it to the Adom Wiki for the artifact you were demoing -This works, but the videos that come out the other end are unwatchable. **80% of the runtime is dead air**, the viewer staring at unchanged screens while Claude Code waits for a Fusion 360 export to complete, or while the Fusion cloud BFS walker enumerates folders, or while a 30-second AI tool call processes in the background. A 12-step demo turns into a 5-minute video where most of the time nothing visible is happening, even though the AI is doing real work the whole time.+This works, but the videos that come out the other end are unwatchable. **80% of the runtime is dead air** — the viewer staring at unchanged screens while Claude Code waits for a Fusion 360 export to complete, or while the Fusion cloud BFS walker enumerates folders, or while a 30-second AI tool call processes in the background. A 12-step demo turns into a 5-minute video where most of the time nothing visible is happening, even though the AI is doing real work the whole time.  We tried recording with manual edits afterwards. Manual editing is slow, requires a separate video tool, breaks the "Claude Code drives the demo end to end" workflow, and is the kind of work no Adom user signed up for. The team should be able to record a demo and end up with a polished video without ever opening DaVinci Resolve. -We also tried recording at a faster monitor refresh rate to make the playback feel snappier. That doesn't help, the slow operations are still slow, and the user still has to wait through them.+We also tried recording at a faster monitor refresh rate to make the playback feel snappier. That doesn't help — the slow operations are still slow, and the user still has to wait through them.  The actual fix is **explicit "this part is boring, speed it up later" markers dropped during the demo flow, then a single post-process pass that turns the marked sections into 10x-100x timelapses with a clear visual indicator** (a "20x SPEEDUP: Exporting gerbers" red banner). The viewer sees the slow part happen, understands what was happening, and isn't asked to sit through 30 seconds of an unchanged screen. -That's adom-video-post phase 1. Phase 2 is the Adom Video Post-Processing app: open the sped-up video in a Hydrogen webview, hit record, narrate over it, auto-level the audio (EBU R128 broadcast standard), keep every take and every auto-leveled version as separate tabs, save whichever you like as the final. Phase 3 adds wiki publishing so the final narrated video gets attached to the wiki page for the artifact you demoed.+That's video-post phase 1. Phase 2 is the Video Post-Processing app: open the sped-up video in a Hydrogen webview, hit record, narrate over it, auto-level the audio (EBU R128 broadcast standard), keep every take and every auto-leveled version as separate tabs, save whichever you like as the final. Phase 3 adds wiki publishing so the final narrated video gets attached to the wiki page for the artifact you demoed. -## What's in the Adom Video Post-Processing app (phase 2)+## What's in the Video Post-Processing app (phase 2) -The voiceover subcommand is a full webview app, not just a "record button". Running `adom-video-post voiceover --input sped-up.webm --markers markers.jsonl` opens a Hydrogen tab with:+The voiceover subcommand is a full webview app, not just a "record button". Running `video-post voiceover --input sped-up.webm --markers markers.jsonl` opens a Hydrogen tab with:  - **Speedup Timeline card** showing the source video as a horizontal bar, speedup regions highlighted, summary stats (source vs output duration, seconds saved, % shorter, number of speedup segments). Populated from the markers file the `process` step wrote. - **Video tab strip** above the player: "Original" (silent source), "Take 1" (your raw voiceover mux), "Take 1 auto-leveled" (same take with loudness normalized), "Take 2", "Take 2 auto-leveled", and so on. Every finalize produces two new tabs at once. Click any tab to play that version. Every tab is a separate file on disk; nothing is ever overwritten.@@ -55,63 +45,59 @@ The user originally asked for this as a "post-process script for the Fusion demo  - Anyone recording any kind of demo (KiCad workflow, pup automation, molecule import, datasheet annotation, library publishing) hits the same dead-air problem - The marker + speedup + overlay technique works on any `.webm` from any Hydrogen recording, not just adom-desktop ones-- The voiceover overlay is also a generic workflow, it doesn't know or care what was being recorded+- The voiceover overlay is also a generic workflow — it doesn't know or care what was being recorded - Wiki publishing should work for any wiki page type (apps, skills, molecules, libraries, datasheets, footprints, symbols, 3dcomps), not just adom-desktop -So it lives in its own repo, follows the standard Rust + clap + SKILL.md + wiki-discovery pattern (the `tool-publisher` skill), and gets installed by any container that needs it via the Adom Wiki auto-discovery flow. Same model as `adom-molecule`, `shotlog`, and other on-demand tools.+So it lives in its own repo, follows the standard Rust + clap + SKILL.md + wiki-discovery pattern documented in `gallia/skills/tool-publisher`, and gets installed by any container that needs it via the Adom Wiki auto-discovery flow. Same model as `adom-molecule`, `shotlog`, and other on-demand tools. -> **Ganging takes into one narrated video** (each take cut to its real-time action by change detection, real durations on screen, narration fitted to the measured cut, brand-font captions on a glow, no cards) is the bundled [`skills/gang-takes/`](skills/gang-takes/SKILL.md) skill and its `gang_takes.py`.--> **Recording skills live here now.** The full demo-recording skill (script → record → mux → storyboard → publish, with the Hydrogen / pup / native-browser capture mechanics) is bundled under [`skills/demo-recording/`](skills/demo-recording/SKILL.md), homed in this repo since gallia was retired. Recording the user's **real, logged-in** browser at full framerate in the background is the adom-browser-extension's `native-browser-recording` skill.--`adom-desktop`'s `scripts/demo-record.sh` is the consumer, it calls `adom-video-post mark init` after Hydrogen recording starts and `adom-video-post process` after it stops. But adom-video-post itself doesn't know about adom-desktop. Any other recording orchestrator (even a hand-rolled bash script for a one-off demo) can use adom-video-post the same way.+`adom-desktop`'s `scripts/demo-record.sh` is the consumer — it calls `video-post mark init` after Hydrogen recording starts and `video-post process` after it stops. But video-post itself doesn't know about adom-desktop. Any other recording orchestrator (even a hand-rolled bash script for a one-off demo) can use video-post the same way.  ## What it does (3 phases) -### Phase 1, Speedup post-processor+### Phase 1 — Speedup post-processor  Drop start/end markers around slow sections during the demo, then run a single command after recording to produce a sped-up version with overlay captions.  ```bash # At the start of recording-adom-video-post mark init+video-post mark init  # Wrap any slow command (this is the most common pattern)-adom-video-post wrap --speed 20 --label "Exporting gerbers" -- adom-desktop fusion_export_gerbers-adom-video-post wrap --speed 15 --label "Pulling source files" -- pull_and_analyze "C:/tmp/foo.fbrd"+video-post wrap --speed 20 --label "Exporting gerbers" -- adom-desktop fusion_export_gerbers+video-post wrap --speed 15 --label "Pulling source files" -- pull_and_analyze "C:/tmp/foo.fbrd"  # Or drop manual start/end markers if you need finer control-adom-video-post mark start --speed 50 --label "Cloud search"+video-post mark start --speed 50 --label "Cloud search" # ... slow operation ...-adom-video-post mark end+video-post mark end  # After Hydrogen recording stops, post-process-adom-video-post process \+video-post process \   --input  /home/adom/project/recordings/demo.webm \-  --markers /tmp/adom-video-post-markers.jsonl+  --markers /tmp/video-post-markers.jsonl # → produces /home/adom/project/recordings/demo-fast.webm ```  The output video has the marked sections accelerated (default cap 100x) with a red "20x SPEEDUP: Exporting gerbers" banner. Normal sections play at 1x. Audio is dropped (phase 1 expects silent recordings). -### Phase 2, Voiceover overlay ✅ shipped+### Phase 2 — Voiceover overlay (coming soon)  ```bash-adom-video-post voiceover --input demo-fast.webm+video-post voiceover --input demo-fast.webm ```  Starts a tiny HTTP server and opens a Hydrogen webview tab with a video player, a record button, and a real-time audio waveform indicator. You watch the sped-up video and narrate over it. When you stop recording, ffmpeg muxes the audio with the video to produce the final narrated output. -### Phase 3, Wiki publish ✅ shipped+### Phase 3 — Wiki publish (coming soon)  ```bash-adom-video-post publish \-  --input demo-fast-narrated.mp4 \-  --page adom/adom-desktop-fusion-bridge \+video-post publish \+  --input demo-fast-narrated.webm \+  --page apps/adom-desktop \   --caption "Fusion 360 deep dive: BQ25792 manufacturing pipeline" ``` -Pushes the final narrated video into the wiki page's repo via `adom-wiki repo push`, so it lands in the page's Files tab and serves at `https://wiki.adom.inc/api/v1/pages/<slug>/files/<file>`. The command returns that served URL so you can embed it. Now the polished demo video lives alongside the artifact it documents, so anyone visiting the page sees the demo, not just the docs. (The old `adom-wiki asset upload` pillar this used to wrap was removed; `repo push` is the supported path.)+Wraps `adom-wiki asset upload` to attach the final narrated video to whichever wiki page you're demoing. Now the polished demo video lives alongside the artifact it documents — anyone visiting the apps/adom-desktop page sees the demo, not just the docs.  ## The full pipeline @@ -119,20 +105,20 @@ Pushes the final narrated video into the wiki page's repo via `adom-wiki repo pu # 1. Record (silent, no voiceover) adom-cli hydrogen sharing request --share screen --reason "Demo recording" adom-cli hydrogen recording start --countdown 3-adom-video-post mark init+video-post mark init -# (run your demo, wrapping slow sections with `adom-video-post wrap`)+# (run your demo, wrapping slow sections with `video-post wrap`)  adom-cli hydrogen recording stop  # → demo.webm  # 2. Speedup pass-adom-video-post process --input demo.webm+video-post process --input demo.webm  # 3. Voiceover (Phase 2)-adom-video-post voiceover --input demo-fast.webm+video-post voiceover --input demo-fast.webm  # 4. Publish (Phase 3)-adom-video-post publish \+video-post publish \   --input demo-fast-narrated.webm \   --page apps/adom-desktop \   --caption "Fusion 360 deep dive"@@ -142,7 +128,7 @@ Four commands. Total wall-clock time under 10 minutes for a demo that captured 3  ## How the AI uses it -Adom users mostly don't run adom-video-post directly, their Claude Code session does. The skill file (`SKILL.md`) is auto-installed to `~/.claude/skills/adom-video-post/` and Claude Code finds it via trigger words like "speed up demo recording", "timelapse video", "voiceover after recording", "publish demo to wiki".+Adom users mostly don't run video-post directly — their Claude Code session does. The skill file (`SKILL.md`) is auto-installed to `~/.claude/skills/video-post/` and Claude Code finds it via trigger words like "speed up demo recording", "timelapse video", "voiceover after recording", "publish demo to wiki".  Once Claude finds the skill, it knows the marker workflow and the four-command pipeline above. The AI drops markers as it runs commands during the demo, then runs `process` automatically after the recording stops, and offers to run `voiceover` and `publish` next. @@ -152,17 +138,17 @@ This is the same pattern as `adom-molecule` (Claude drives the molecule import w  ```bash # From source-git clone https://github.com/adom-inc/adom-video-post-cd adom-video-post+git clone https://github.com/adom-inc/video-post+cd video-post ./build.sh  # After v0.1.0 release: from the wiki auto-discovery flow-gh release download v0.1.0 --repo adom-inc/adom-video-post --pattern adom-video-post -D /usr/local/bin-chmod +x /usr/local/bin/adom-video-post-adom-video-post install+gh release download v0.1.0 --repo adom-inc/video-post --pattern video-post -D /usr/local/bin+chmod +x /usr/local/bin/video-post+video-post install ``` -`adom-video-post install` deploys the SKILL.md to `~/.claude/skills/adom-video-post/` so Claude Code in this container picks it up immediately.+`video-post install` deploys the SKILL.md to `~/.claude/skills/video-post/` so Claude Code in this container picks it up immediately.  ## Prerequisites 
SKILL.md+59−67
@@ -1,5 +1,5 @@ ----name: adom-video-post+name: video-post description: >   Post-production for demo recordings: speed up boring parts with overlay   captions, add voiceover narration after the fact, or publish the final video@@ -11,7 +11,7 @@ description: >   speedup, demo post-process, post-process demo, publish demo video to wiki. --- -# adom-video-post+# video-post  Post-production CLI for demo recordings. For pre-production and recording (writing scripts, setting up panels, driving the recording), use the@@ -32,23 +32,23 @@ the feedback loop from precise ("clip 5 is wrong") to useless ("something's off somewhere"). Don't do that to them.  **The rule, in one line:** after `manifest add` for all clips, the next-command MUST be `adom-video-post storyboard <manifest>`, and you MUST wait+command MUST be `video-post storyboard <manifest>`, and you MUST wait for the user's per-clip feedback before concat / upload.  ```bash # Open storyboard → user reviews → you fix bad clips → repeat → only then concat-adom-video-post storyboard /tmp/my-demo-manifest.json \-  --tab-name 'adom-video-post: <name> review' --port 8797+video-post storyboard /tmp/my-demo-manifest.json \+  --tab-name 'video-post: <name> review' --port 8797 # Activate + screenshot to confirm it rendered-adom-cli hydrogen workspace active-tab --name 'adom-video-post: <name> review'-adom-cli hydrogen screenshot panel --name 'adom-video-post: <name> review' \+adom-cli hydrogen workspace active-tab --name 'video-post: <name> review'+adom-cli hydrogen screenshot panel --name 'video-post: <name> review' \   --reason 'show user the clip storyboard for review' # Then point the user at the tab and wait for "clip N is broken" / "looks good" ```  ### Every clip needs a `--description` -`adom-video-post manifest add --description "<text>"` is how the storyboard+`video-post manifest add --description "<text>"` is how the storyboard UI explains what each clip is supposed to show. Without descriptions, the user is still blind — they see a player but no context. Write descriptions that are **concrete about the intent**, not just the@@ -65,9 +65,9 @@ feature name. Compare: Rule of thumb: if a teammate reading just the description couldn't write the clip's interaction script, the description is too vague. -### Known bug: `adom-video-post concat` drops audio+### Known bug: `video-post concat` drops audio -Today `adom-video-post concat --kind raw` only writes the video stream.+Today `video-post concat --kind raw` only writes the video stream. Until fixed, use ffmpeg's concat demuxer as a drop-in replacement AFTER storyboard approval: @@ -105,7 +105,7 @@ A clip where the video is 2:17 and the narration is 0:30 is broken regardless of **Rule:** **video length ≈ narration length, ±15%.** If they diverge, fix whichever is wrong:  - Narration too long: tighten the script, drop filler ("So…", "What we're looking at here…").-- Video too long: either trim on recording, split the clip into N smaller clips (each with its own matched narration), or apply `adom-video-post process` speedups in the boring bits with caption overlays.+- Video too long: either trim on recording, split the clip into N smaller clips (each with its own matched narration), or apply `video-post process` speedups in the boring bits with caption overlays.  **Split before you speedup when:** - The sub-sections are genuinely different things you'd narrate separately ("open it / click around / close it" → three clips, three narrations).@@ -125,7 +125,7 @@ done  ## 🖥️ Fullscreen review in a Hydrogen webview is blocked — route users to pup -Hydrogen's Web View panel wraps the storyboard URL in an iframe without `allow="fullscreen"` (filed as `adom-inc/hydrogen#329`). The ⛶ Expand button on each clip card calls `element.requestFullscreen()` which throws silently. This is OUT OF scope for `adom-video-post` to fix.+Hydrogen's Web View panel wraps the storyboard URL in an iframe without `allow="fullscreen"` (filed as `adom-inc/hydrogen#329`). The ⛶ Expand button on each clip card calls `element.requestFullscreen()` which throws silently. This is OUT OF scope for `video-post` to fix.  **When the user asks why fullscreen doesn't work** (and they will), tell them: same URL, standalone Chrome window via `pup`: @@ -140,9 +140,9 @@ Do not wait for hydrogen#329 to ship before offering this — the workaround is  Every webm/mp4 you upload to the Adom Wiki must have a keyframe interval of ~1 s (`-g 30 -keyint_min 30` for 30 fps content). Without that, the `<video>` element on the wiki page can play start-to-end but **cannot scrub** — `<video>` seeks only land on keyframes, and a clip with one keyframe at frame 0 is one giant seekable unit. Users want to jump around the timeline; without dense keyframes, they can't. -This bites especially on the **single-shot path** (`adom-cli hydrogen recording stop` straight to `adom-wiki asset upload`) which skips all of adom-video-post and inherits libvpx-vp9's default keyframe-only-at-frame-0 behavior.+This bites especially on the **single-shot path** (`adom-cli hydrogen recording stop` straight to `adom-wiki asset upload`) which skips all of video-post and inherits libvpx-vp9's default keyframe-only-at-frame-0 behavior. -`adom-video-post concat`'s final mux **must** include `-g 30 -keyint_min 30` on every code path that produces a webm bound for the wiki (concat output, snippet narration mux, hero image extract is fine without it). The same rule lives in the `demo-recording` skill — these two skills enforce identical encode contracts on every wiki-bound clip. If you find a code path that skips it, fix it before letting the upload proceed.+`video-post concat`'s final mux **must** include `-g 30 -keyint_min 30` on every code path that produces a webm bound for the wiki (concat output, snippet narration mux, hero image extract is fine without it). The same rule lives in the `demo-recording` skill — these two skills enforce identical encode contracts on every wiki-bound clip. If you find a code path that skips it, fix it before letting the upload proceed.  **Quick verify after encode:** @@ -156,13 +156,13 @@ ffprobe -v error -select_streams v:0 -show_entries packet=pts_time,flags \  ## 🎬 Two flows, pick the right one -`adom-video-post` supports **two** demo-assembly flows. Pick based on what+`video-post` supports **two** demo-assembly flows. Pick based on what you're making:  ### Flow A — Per-section concat (preferred for feature tours)  Each feature / section is recorded as its own short clip with its own-TTS narration. `adom-video-post concat` stitches them. This is the+TTS narration. `video-post concat` stitches them. This is the canonical path for feature-tour demos ("here are 14 things adom-tsci can do") because: @@ -177,7 +177,7 @@ adom-tsci can do") because: The orchestration loop (full details in the demo-recording skill):  ```bash-adom-video-post manifest init --output demo.json+video-post manifest init --output demo.json for each section:   adom-cli hydrogen caption show "<title>" -d 0   adom-cli hydrogen recording start --reason "<title>"@@ -187,12 +187,12 @@ for each section:     --write-media "<id>.mp3"   ffmpeg -y -i <clip>.webm -i <id>.mp3 -map 0:v -map 1:a \     -c:v copy -c:a libopus -shortest <id>-narrated.webm-  adom-video-post manifest add --manifest demo.json \+  video-post manifest add --manifest demo.json \     --id <id> --title "<title>" \     --raw <id>-narrated.webm <id>-narrated.webm   adom-cli hydrogen caption hide done-adom-video-post concat --manifest demo.json --output final.webm --kind raw+video-post concat --manifest demo.json --output final.webm --kind raw ```  **TTS voice:** default `en-US-AndrewNeural` (Microsoft Edge neural,@@ -207,8 +207,8 @@ infrastructure. Until then, `edge-tts` on PATH is the correct default.  ### Flow B — Single long take + speedup markers -A single recording, with `adom-video-post mark start` / `mark end` around-slow operations, post-processed by `adom-video-post process` to compress+A single recording, with `video-post mark start` / `mark end` around+slow operations, post-processed by `video-post process` to compress boring sections. This is the right flow for **live-narration** demos where the speech timing is inseparable from the action, or where the demo has unpredictable pauses (cloud search results, AI tool latency,@@ -217,14 +217,6 @@ fabrication exports) that you want to compress. Flow B is the original pipeline. Still supported, still correct when it's the right fit. -## 🔤 Captions on frames: brand fonts, wrapped, never ffmpeg drawtext with a system font--Text drawn on a frame is Familjen Grotesk (labels), Satoshi (captions) or JetBrains Mono (chips), wrapped to the frame and set on a blurred dark halo. That is what `gang_takes.py` does (it fetches the brand fonts from the wiki and converts them itself). A raw `drawtext` with DejaVu or Arial, or any single unwrapped line, is a defect: on 2026-09-12 one shipped with the caption running off both edges of the video. Split a single take into one clip per beat and give each its label, chip, captions and narration; a demo video is never silent (adom-tts narration fitted to the cut). Full checklist, including the wiki README embedding rules and the rendered-page check: the **demo-video-publishing** skill in the demo-authoring-skillpack.--## 🎞️ Gang several takes into one narrated video: the `gang-takes` skill--When the user wants "one video from these takes", "only show the action", "cut the boring parts and narrate", or "captions over the screen recording in our fonts", use the bundled **gang-takes** skill (`~/.claude/skills/gang-takes/`, tool `gang_takes.py`). It keeps the frames pure (no cards, no letterbox), cuts each take to its action in real time by change detection (idle before, between and after removed, nothing sped up unless asked), states the real duration on screen, measures the cut and fits the adom-tts narration to that length, and draws the labels and captions in the Adom brand fonts on a soft glow. Reference output: the 21 s three-engine routing video on the kicad-bridge page, cut from three raw takes of 12, 38 and 43 s.- ## CLI Reference  A CLI for taking raw screen recordings and turning them into watchable narrated@@ -237,7 +229,7 @@ demo videos. Phases:  ## Why this exists -Demo recordings have long boring stretches: file exports (10-30s), cloud searches (5-30s), AI tool call latency (2-5s per call). A 12-step demo turns into a 5-minute video where 80% of the runtime is the viewer watching unchanged screens. Nobody watches that. adom-video-post compresses the boring parts to ~5% of their wall-clock time with a clear visual indicator that the speedup is happening.+Demo recordings have long boring stretches: file exports (10-30s), cloud searches (5-30s), AI tool call latency (2-5s per call). A 12-step demo turns into a 5-minute video where 80% of the runtime is the viewer watching unchanged screens. Nobody watches that. video-post compresses the boring parts to ~5% of their wall-clock time with a clear visual indicator that the speedup is happening.  For **feature-tour demos** (Flow A), the problem is different: you want each section to be a tidy, narrated clip, and you want the final video to be a clean concatenation rather than one continuous take with timing gymnastics. `manifest` + `concat` does that job. @@ -245,27 +237,27 @@ For **feature-tour demos** (Flow A), the problem is different: you want each sec  ```bash # Health check (verify ffmpeg + ffprobe are installed)-adom-video-post health+video-post health  # Initialize the markers file at the START of a recording session-adom-video-post mark init+video-post mark init  # Wrap any slow command with a speedup marker-adom-video-post wrap --speed 20 --label "Exporting gerbers" -- adom-desktop fusion_export_gerbers-adom-video-post wrap --speed 15 --label "Pulling source files" -- pull_and_analyze "C:/tmp/foo.fbrd"+video-post wrap --speed 20 --label "Exporting gerbers" -- adom-desktop fusion_export_gerbers+video-post wrap --speed 15 --label "Pulling source files" -- pull_and_analyze "C:/tmp/foo.fbrd"  # OR drop manual start/end markers-adom-video-post mark start --speed 50 --label "Cloud search"+video-post mark start --speed 50 --label "Cloud search" # ... run slow operation ...-adom-video-post mark end+video-post mark end  # Inspect the markers you've collected-adom-video-post inspect+video-post inspect  # After the recording stops, post-process it-adom-video-post process \+video-post process \   --input  /home/adom/project/recordings/demo.webm \-  --markers /tmp/adom-video-post-markers.jsonl+  --markers /tmp/video-post-markers.jsonl # → produces /home/adom/project/recordings/demo-fast.webm ``` @@ -274,7 +266,7 @@ adom-video-post process \ ### `health`  ```bash-adom-video-post health+video-post health ```  Verifies `ffmpeg` and `ffprobe` are installed and returns their versions. Exit 0 if ready, 1 with an install hint if missing.@@ -286,7 +278,7 @@ OK: ffmpeg 6.1.1 + ffprobe 6.1.1 ready. ### `mark init`  ```bash-adom-video-post mark init [--file /tmp/adom-video-post-markers.jsonl]+video-post mark init [--file /tmp/video-post-markers.jsonl] ```  Clears the markers file and writes a `recording_start` event with the current epoch. Call this immediately after `adom-cli hydrogen recording start --countdown N` returns. The post-processor uses this timestamp as t=0 for the video.@@ -294,9 +286,9 @@ Clears the markers file and writes a `recording_start` event with the current ep ### `mark start` / `mark end`  ```bash-adom-video-post mark start --speed 20 --label "Exporting gerbers"+video-post mark start --speed 20 --label "Exporting gerbers" # ... run slow operation ...-adom-video-post mark end+video-post mark end ```  Drops a `speedup_start` event (with speed multiplier and caption label) and a matching `speedup_end` event. The post-processor will accelerate everything between these two timestamps and overlay the caption.@@ -308,16 +300,16 @@ Drops a `speedup_start` event (with speed multiplier and caption label) and a ma ### `wrap`  ```bash-adom-video-post wrap --speed 20 --label "Exporting gerbers" -- adom-desktop fusion_export_gerbers+video-post wrap --speed 20 --label "Exporting gerbers" -- adom-desktop fusion_export_gerbers ```  Convenience wrapper that drops a start marker, runs the command, drops the end marker, and returns the command's exit code. Equivalent to:  ```bash-adom-video-post mark start --speed 20 --label "Exporting gerbers"+video-post mark start --speed 20 --label "Exporting gerbers" adom-desktop fusion_export_gerbers RC=$?-adom-video-post mark end+video-post mark end exit $RC ``` @@ -326,7 +318,7 @@ Use this in your demo script for any command that takes more than ~3 seconds and ### `inspect`  ```bash-adom-video-post inspect [--file /tmp/adom-video-post-markers.jsonl]+video-post inspect [--file /tmp/video-post-markers.jsonl] ```  Prints a human-readable summary of the markers file: how long the recording has been running, how many speedup segments are defined, total time saved at the configured speeds.@@ -342,9 +334,9 @@ OK: Recording started 142.3s ago, 4 speedup segments, ~98.7s of source video → ### `process`  ```bash-adom-video-post process \+video-post process \   --input /home/adom/project/recordings/demo.webm \-  --markers /tmp/adom-video-post-markers.jsonl \+  --markers /tmp/video-post-markers.jsonl \   [--output /home/adom/project/recordings/demo-fast.webm] \   [--max-speed 100] ```@@ -360,18 +352,18 @@ Audio is dropped (`-an`). Phase 1 expects silent recordings. Phase 2 (the `voice  Default output is `<input-stem>-fast.webm`. Override with `--output`. -### `voiceover` (Adom Video Post-Processing webview app)+### `voiceover` (Video Post-Processing webview app)  ```bash-adom-video-post voiceover \+video-post voiceover \   --input /home/adom/project/recordings/demo-fast.webm \   [--output /home/adom/project/recordings/demo-fast-narrated.webm] \-  [--markers /tmp/adom-video-post-markers.jsonl] \+  [--markers /tmp/video-post-markers.jsonl] \   [--panel-id <leaf-id>] \-  [--tab-name "Adom Video Post-Processing"]+  [--tab-name "Video Post-Processing"] ``` -This is the full Adom Video Post-Processing app. It ships as a Rust-backed+This is the full Video Post-Processing app. It ships as a Rust-backed HTTP server with a Hydrogen webview frontend, Adom brand-compliant Familjen Grotesk + Satoshi typography, frosted-glass card layout, and 2-way HTTP comms so both the human user and an AI driver can operate@@ -385,7 +377,7 @@ Features:   stats: source duration, after-speedup duration, seconds saved, %   shorter, number of speedup segments. Shown only when `--markers` is   passed so the card reflects the real speedup plan from an earlier-  `adom-video-post process` run. Each segment has a deep tooltip+  `video-post process` run. Each segment has a deep tooltip   explaining EBU R128 loudness parameters in plain English. - **Video tab strip** above the player: "Original" (the silent source),   "Take N" (raw mux for each voiceover take), "Take N auto-leveled"@@ -436,7 +428,7 @@ Features: ### `publish` (Phase 3, coming soon)  ```bash-adom-video-post publish \+video-post publish \   --input /home/adom/project/recordings/demo-fast-narrated.webm \   --page apps/adom-desktop \   --caption "Fusion 360 deep dive: BQ25792 manufacturing pipeline"@@ -447,10 +439,10 @@ Uploads the final video to the wiki page for the artifact you're demoing. Wraps ### `install`  ```bash-adom-video-post install+video-post install ``` -Deploys the embedded `SKILL.md` to `~/.claude/skills/adom-video-post/SKILL.md` so Claude Code finds it automatically via trigger words. Run once after building.+Deploys the embedded `SKILL.md` to `~/.claude/skills/video-post/SKILL.md` so Claude Code finds it automatically via trigger words. Run once after building.  ## The full pipeline (when all 3 phases ship) @@ -458,22 +450,22 @@ Deploys the embedded `SKILL.md` to `~/.claude/skills/adom-video-post/SKILL.md` s # 1. Record (silent — no voiceover) adom-cli hydrogen sharing request --share screen --reason "Demo recording" adom-cli hydrogen recording start --countdown 3-adom-video-post mark init+video-post mark init -# (run your demo, wrapping slow sections with `adom-video-post wrap`)+# (run your demo, wrapping slow sections with `video-post wrap`)  adom-cli hydrogen recording stop  # → /home/adom/project/recordings/demo.webm  # 2. Speedup pass-adom-video-post process --input demo.webm --markers /tmp/adom-video-post-markers.jsonl+video-post process --input demo.webm --markers /tmp/video-post-markers.jsonl # → demo-fast.webm  # 3. Voiceover pass (Phase 2)-adom-video-post voiceover --input demo-fast.webm+video-post voiceover --input demo-fast.webm # → demo-fast-narrated.webm  # 4. Publish to wiki (Phase 3)-adom-video-post publish --input demo-fast-narrated.webm --page apps/adom-desktop \+video-post publish --input demo-fast-narrated.webm --page apps/adom-desktop \   --caption "Fusion 360 deep dive" ``` @@ -491,20 +483,20 @@ JSONL, one event per line. Self-contained — the file embeds the recording star {"action":"speedup_end","epoch":1776038562.456} ``` -Default file path: `$VIDEO_SPEEDUP_MARKERS` env var, or `/tmp/adom-video-post-markers.jsonl` if unset.+Default file path: `$VIDEO_SPEEDUP_MARKERS` env var, or `/tmp/video-post-markers.jsonl` if unset.  ## Integration with adom-desktop demo flow  `adom-desktop`'s `scripts/demo-record.sh` integrates with this tool: -- `demo-record.sh start` calls `adom-video-post mark init` after the Hydrogen recording starts-- `demo-record.sh stop` calls `adom-video-post process` automatically if any speedup markers exist, producing both `demo.webm` and `demo-fast.webm`+- `demo-record.sh start` calls `video-post mark init` after the Hydrogen recording starts+- `demo-record.sh stop` calls `video-post process` automatically if any speedup markers exist, producing both `demo.webm` and `demo-fast.webm`  Per-step demo commands wrap their slow operations:  ```bash step_caption "Step 7/12: Exporting gerbers"-adom-video-post wrap --speed 20 --label "Exporting gerbers" -- bash -c '+video-post wrap --speed 20 --label "Exporting gerbers" -- bash -c '   GERBER_RESP=$(adom-desktop fusion_export_gerbers 2>&1)   pull_and_analyze "$ZIP_PATH" '
build.sh+4−4
@@ -2,8 +2,8 @@ set -euo pipefail cd "$(dirname "$0")" cargo build --release-sudo cp target/release/adom-video-post /usr/local/bin/-chmod +x /usr/local/bin/adom-video-post-adom-video-post install+sudo cp target/release/video-post /usr/local/bin/+chmod +x /usr/local/bin/video-post+video-post install echo ""-echo "OK: adom-video-post installed. Run 'adom-video-post health' to verify."+echo "OK: video-post installed. Run 'video-post health' to verify."
page.json+1−1
@@ -26,7 +26,7 @@     "binary_name": "adom-video-post"   },   "type": "app",-  "version": "1.0.7",+  "version": "1.0.8",   "description": "AI-driven studio for finishing demo videos: review every captured clip, flag and re-record the weak ones, speed up dead air, narrate, mux and auto-level, validate the final cut, and publish straight to the wiki, driven end to end by your AI.",   "bundled": true,   "dependencies": {},
skills/gang-takes/SKILL.md+25−16
@@ -76,6 +76,7 @@ text #e6edf3). } ``` +- A segment may carry its own `region` when its clip has another layout (another box, or a caption band that changes every second, which would otherwise keep every frame). Set it per segment and the manifest's `region` is the default for the rest. - `region` is the part of the frame to watch for change, as fractions (x0, y0, x1, y1). Name   the canvas, not the whole screen, so a clock or a status bar does not count as action. - `events` lets you pass the change moments yourself (seconds into the raw take) when detection@@ -99,22 +100,30 @@ detected events, the kept intervals, each segment's length and how the narration ## Check it before you ship it  Extract frames at the start of each segment, just after a change, and the last second, and-look at them. Look for: the take is visible (record the monitor, not the window, for OpenGL apps-like KiCad, or the canvas is white), the label does not sit on the thing being demonstrated,-every change you expected made it into the cut (compare the report's events with the number of-commits you made), and the narration report says it fit.--## One take, several beats-A single recording works too: cut it into one clip per beat (`ffmpeg -ss A -to B -c copy`) and list-each as a segment with its own label, chip, captions and narration. Do not reach for ffmpeg-`drawtext` instead: a system font and an unwrapped line are exactly what this tool exists to avoid.+look at them. Look for: the take is visible, the label does not sit on the thing being+demonstrated, every change you expected made it into the cut (compare the report's events with+the number of commits you made), and the narration report says it fit.++## Recording without disturbing the user, and when the foreground is worth it++On a user's box, never bring the recorded window to the foreground and never move the mouse for+a video. Record the window (`desktop_record_window_start {hwnd}`), not the monitor, and leave it+behind whatever the user is doing. OpenGL canvases (KiCad's default) do not paint while covered+and come out white in a window capture; for KiCad, set `graphics.canvas_type` to 2 in+`kicad_common.json` while KiCad is closed (keep a copy and restore it after), which is KiCad's+fallback canvas and paints in the background. Verified 2026-09-12 on arav-rog: the editor covered+by the user's Hydrogen window, window capture shows the board and every change. Drive the app+over IPC, menu commands and UI Automation; frames captured with `kicad_screenshot_all` are+background too.++On a box that is yours (a dedicated test or demo machine), the OpenGL canvas in the foreground+gives the better picture: open with `foreground:true` and a reason, maximize, record the+monitor. Use the foreground only when it benefits the recording and nobody is working on that+screen; the background path is the default.  ## Publishing -Push the MP4 and poster through the page's git repo (`adom-wiki repo push <owner>/<slug> --files-...`) and embed with `<video controls preload="metadata" width="1080" poster="<folder>/poster.png"-src="<folder>/final.mp4"></video>`: paths are written from the page root even inside a sub-folder-README, the sanitizer strips `style` so the numeric `width` is what keeps the video inside the-column, and a plain "Download the video" link serves phones. Then verify the RENDERED page: read-its `<video>` tag, HEAD the resolved `src`, and open it in a browser on a test box. A curl of a URL-you typed proves nothing (the **demo-video-publishing** skill has the full checklist).+Push the MP4 and poster to the page and embed with a `<video>` tag whose `src` is+`/blob/app/<page>/docs/videos/<file>.mp4`, with a plain "Watch" link to the+`/api/pages/<owner>/<page>/files/...` URL for phones. Confirm the URL serves with curl before+telling anyone it is live.
skills/gang-takes/gang_takes.py+11−4
@@ -26,7 +26,7 @@ you can write narration to those numbers before the final run. Manifest: {   "size": "1920x1080", "fps": 30,-  "region": [0.26, 0.16, 0.77, 0.86],     # x0,y0,x1,y1 fractions watched for change (default: whole frame)+  "region": [0.26, 0.16, 0.77, 0.86],     # x0,y0,x1,y1 fractions watched for change (default: whole frame); a segment may carry its own "region"   "chart": {                                # optional closing bar chart, brand style, narrated     "title": "Real time to route six nets", "subtitle": ["one line", "another"],     "rows": [{"name": "Freerouting", "value": 3.2, "unit": "s", "note": "one call"}],@@ -161,6 +161,7 @@ def intervals(events, pre, post, last_post):  def cut_video(idx, seg, W, H, fps, work, fonts, region):     src = Path(seg["clip"]); speed = float(seg.get("speed", 1))+    region = seg.get("region") or region   # a clip with its own layout (another box, a caption band) watches its own region     events = seg.get("events") or detect_events(src, region, int(seg.get("min_change_px", 15)), work)     if not events:         sys.exit(f"segment {idx}: no change detected in {src.name}; pass events or lower min_change_px")@@ -229,12 +230,17 @@ def render_chart(path, chart, W, H, fonts):     im = Image.new("RGB", (W, H), BG); d = ImageDraw.Draw(im); m = W / 1920     disp = ImageFont.truetype(str(fonts["display"]), int(64 * m)); body = ImageFont.truetype(str(fonts["body"]), int(34 * m))     mono = ImageFont.truetype(str(fonts["mono"]), int(40 * m)); monoS = ImageFont.truetype(str(fonts["mono"]), int(30 * m))-    d.text((120 * m, 100 * m), chart.get("title", ""), font=disp, fill=TEXT)+    # title: shrink until it fits the frame width (a chart title that runs off the edge is worse than a smaller one)+    title = chart.get("title", ""); size = int(64 * m)+    while size > int(34 * m) and d.textlength(title, font=ImageFont.truetype(str(fonts["display"]), size)) > W - 240 * m:+        size -= 2+    disp = ImageFont.truetype(str(fonts["display"]), size)+    d.text((120 * m, 100 * m), title, font=disp, fill=TEXT)     subs = chart.get("subtitle", []); subs = [subs] if isinstance(subs, str) else subs     for i, line in enumerate(subs[:3]):         d.text((120 * m, (190 + 46 * i) * m), line, font=body, fill=TXT2)     rows = chart.get("rows", []); vmax = float(chart.get("max") or max((float(r.get("value", 0)) for r in rows), default=1) * 1.08)-    x0, y0, bw, rowh = 520 * m, (360 if len(subs) > 1 else 320) * m, 900 * m, min(150 * m, (H - 500 * m) / max(1, len(rows)))+    x0, y0, bw, rowh = 520 * m, (360 if len(subs) > 1 else 320) * m, 900 * m, min(170 * m, (H - 500 * m) / max(1, len(rows)))     for i, r in enumerate(rows):         y = y0 + i * rowh; val = float(r.get("value", 0)); unit = r.get("unit", "")         d.text((120 * m, y + 30 * m), str(r.get("name", "")), font=body, fill=TEXT)@@ -244,7 +250,8 @@ def render_chart(path, chart, W, H, fonts):         label = f"{val:g}{(' ' + unit) if unit else ''}"; lx = x0 + bw + 28 * m         d.text((lx, y + 30 * m), label, font=mono, fill=TEXT)         if r.get("note"):-            d.text((lx + d.textlength(label, font=mono) + 24 * m, y + 38 * m), str(r["note"]), font=monoS, fill=TXT2)+            # the note sits under the bar on its own line, so it can never run off the right edge+            d.text((x0, y + 106 * m), str(r["note"]), font=monoS, fill=TXT2)     if chart.get("footer"):         d.text((120 * m, H - 150 * m), chart["footer"], font=monoS, fill=ACCENT)     im.save(path)
src/app/mod.rs+6−6
@@ -1,5 +1,5 @@-//! Unified `adom-video-post app` subcommand — opens ONE Hydrogen tab-//! named "Adom Video Post" that hosts all three post-production phases+//! Unified `video-post app` subcommand — opens ONE Hydrogen tab+//! named "Video Post" that hosts all three post-production phases //! (Review, Narrate, Publish) behind a phase selector at the top of //! the UI, so the user sees a single tool instead of three. //!@@ -40,14 +40,14 @@ pub struct AppConfig { pub fn run(cfg: AppConfig) -> Result<(), String> {     if !cfg.manifest_path.exists() {         return Err(format!(-            "manifest not found: {}. Hint: create one with `adom-video-post manifest init`, then add clips with `adom-video-post manifest add`.",+            "manifest not found: {}. Hint: create one with `video-post manifest init`, then add clips with `video-post manifest add`.",             cfg.manifest_path.display()         ));     }      let initial = Manifest::load(&cfg.manifest_path)?;     let title = if initial.title.is_empty() {-        "Adom Video Post".to_string()+        "Video Post".to_string()     } else {         initial.title.clone()     };@@ -99,7 +99,7 @@ pub fn run(cfg: AppConfig) -> Result<(), String> {     // Derive the voiceover input: prefer manifest.final_fast_path     // (the concat output), fall back to final_raw_path. If neither     // is set yet, voiceover features are disabled until the user-    // runs `adom-video-post concat` to populate one.+    // runs `video-post concat` to populate one.     let voiceover_input = {         let m = manifest.read().unwrap();         m.final_fast_path.clone().or_else(|| m.final_raw_path.clone())@@ -108,7 +108,7 @@ pub fn run(cfg: AppConfig) -> Result<(), String> {         .as_ref()         .and_then(|p| p.parent().map(|p| p.to_path_buf()))         .unwrap_or_else(|| std::env::temp_dir());-    let voiceover_output = voiceover_output_dir.join("adom-video-post-narrated.webm");+    let voiceover_output = voiceover_output_dir.join("video-post-narrated.webm");      // Build a voiceover routes struct pointing at whichever final     // concat is available. The result_slot here is per-app-session,
src/app/routes.rs+1−1
@@ -1,4 +1,4 @@-//! `impl Routes` for the unified `adom-video-post app` subcommand.+//! `impl Routes` for the unified `video-post app` subcommand. //! //! Dispatches incoming requests across the storyboard and voiceover //! sub-route tables behind prefix-based URL rewriting:
src/app/ui.html+10−10
@@ -2,7 +2,7 @@ <html lang="en"> <head> <meta charset="UTF-8">-<title>Adom Video Post</title>+<title>Video Post</title> <link rel="icon" type="image/svg+xml" href="favicon.svg"> <style> @font-face {@@ -678,7 +678,7 @@       <svg class="logo" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">         <path d="M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4zm2 2v2h2V6H5zm4 0v12h6V6H9zm8 0v2h2V6h-2zM5 10v2h2v-2H5zm12 0v2h2v-2h-2zM5 14v2h2v-2H5zm12 0v2h2v-2h-2zM5 18v0h2v0H5zm12 0v0h2v0h-2zM11 8v8h2V8h-2z"/>       </svg>-      <span class="app-name">adom-video-post</span>+      <span class="app-name">video-post</span>       <span class="sep">·</span>       <span class="app-subtitle">review · narrate · publish</span>     </div>@@ -701,7 +701,7 @@   </div>    <div class="hover-popover" id="app-info" role="dialog" aria-label="App information">-    <div class="popover-title">adom-video-post</div>+    <div class="popover-title">video-post</div>     <div class="popover-row"><span class="k">about</span>       <span class="v">Per-clip review, voiceover narration, and final concat for Adom feature-tour demos.</span></div>     <div class="popover-row"><span class="k">version</span><span class="v">v0.3.0</span></div>@@ -709,8 +709,8 @@     <div class="popover-row"><span class="k">tts</span>       <span class="v"><a class="popover-link" href="https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-tts" target="_blank" rel="noopener">adom-tts ↗</a> — house TTS engine + pronunciation overrides shared by every Adom tool that speaks. Mispronounced terms? Add them once there.</span></div>     <div class="popover-actions">-      <a class="popover-link" href="https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-video-post" target="_blank" rel="noopener">Open wiki ↗</a>-      <a class="popover-link" href="https://github.com/adom-inc/adom-video-post" target="_blank" rel="noopener">GitHub ↗</a>+      <a class="popover-link" href="https://wiki-ufypy5dpx93o.adom.cloud/apps/video-post" target="_blank" rel="noopener">Open wiki ↗</a>+      <a class="popover-link" href="https://github.com/adom-inc/video-post" target="_blank" rel="noopener">GitHub ↗</a>     </div>   </div> </header>@@ -799,7 +799,7 @@     <p>       Fill in the target wiki page and a caption, then copy the       generated prompt and paste it into Claude Code. Claude will-      use the <span class="inline-code">adom-video-post</span> ++      use the <span class="inline-code">video-post</span> +       <span class="inline-code">adom-wiki</span> skills to upload       the narrated final video.     </p>@@ -926,7 +926,7 @@ function renderReview(manifest) {   if (totalSpeedups > 0) parts.push(`${totalSpeedups} speedup${totalSpeedups === 1 ? '' : 's'}`);   if (withFast > 0) parts.push(`${withFast} sped-up`);   document.getElementById('stats').innerHTML = parts.map(esc).join('<span class="sep">·</span>');-  document.getElementById('title').textContent = manifest.title || 'Adom Video Post';+  document.getElementById('title').textContent = manifest.title || 'Video Post';    // Finals   const finalsEl = document.getElementById('finals');@@ -974,7 +974,7 @@ function renderReview(manifest) {   if (clipCount === 0) {     stepsEl.innerHTML = `<div style="padding:40px 32px;color:var(--text3);text-align:center;font-family:'JetBrains Mono',monospace;">       <strong style="color:var(--text);">No clips yet.</strong><br><br>-      Your demo script should call <code>adom-video-post manifest add</code> once per step.<br>+      Your demo script should call <code>video-post manifest add</code> once per step.<br>       The UI live-updates as clips are added.     </div>`;     return;@@ -1158,7 +1158,7 @@ function renderPublishPrompt() {    const videoLine = narratedPath     ? `The narrated final video is at \`${narratedPath}\` (if that's missing, use whatever the latest narrated take is under the same directory).`-    : 'The narrated final video is whatever came out of the Narrate phase of the Adom Video Post app — look in the same directory as the manifest for the most recent `*-narrated*.webm` or `*-take-*-norm.webm`.';+    : 'The narrated final video is whatever came out of the Narrate phase of the Video Post app — look in the same directory as the manifest for the most recent `*-narrated*.webm` or `*-take-*-norm.webm`.';    const prompt = `Publish my narrated "${title}" demo video to the Adom wiki. @@ -1167,7 +1167,7 @@ ${videoLine} Upload it to the \`${page}\` wiki page with the caption: "${caption}" -Use the \`adom-video-post\` and \`adom-wiki\` skills. Confirm the upload succeeded by returning the wiki asset URL.`;+Use the \`video-post\` and \`adom-wiki\` skills. Confirm the upload succeeded by returning the wiki asset URL.`;   pubPrompt.textContent = prompt; } 
src/ctrl.rs+8−8
@@ -1,7 +1,7 @@-//! `adom-video-post list` and `adom-video-post ctrl` — AI control surface for+//! `video-post list` and `video-post ctrl` — AI control surface for //! running webapp instances. //!-//! Every `run_webapp` invocation writes a `~/.adom/adom-video-post/instances/+//! Every `run_webapp` invocation writes a `~/.adom/video-post/instances/ //! {pid}.json` file on startup and deletes it on shutdown. `list` reads //! that directory, filters out stale PIDs (processes that died without //! cleaning up), and prints a table. `ctrl` resolves a symbolic target@@ -15,7 +15,7 @@ //!     --tail N      print only the last N //!     --since SEQ   only messages whose seq > SEQ //!     --follow      long-poll (blocking, exits on Ctrl-C)-//!   eval <code>     POST /eval + poll result (same as `adom-video-post eval`)+//!   eval <code>     POST /eval + poll result (same as `video-post eval`) //!   shutdown        POST /shutdown //!   call M PATH     raw HTTP call, optional --body JSON //!@@ -118,7 +118,7 @@ pub fn resolve_target(target: &str) -> Result<Instance, String> {     let instances = list_instances()?;     if instances.is_empty() {         return Err(format!(-            "no running adom-video-post instances found. Hint: start one with `adom-video-post voiceover --input ...` or `adom-video-post storyboard <manifest>`."+            "no running video-post instances found. Hint: start one with `video-post voiceover --input ...` or `video-post storyboard <manifest>`."         ));     } @@ -159,17 +159,17 @@ pub fn resolve_target(target: &str) -> Result<Instance, String> {         .find(|i| i.subcommand.to_lowercase() == t)         .ok_or_else(|| {             format!(-                "no running instance matching {:?}. Hint: run `adom-video-post list` to see what is running.",+                "no running instance matching {:?}. Hint: run `video-post list` to see what is running.",                 target             )         }) } -/// `adom-video-post list` — print the registry as a human-readable table.+/// `video-post list` — print the registry as a human-readable table. pub fn cmd_list() -> Result<(), String> {     let instances = list_instances()?;     if instances.is_empty() {-        println!("no adom-video-post instances running");+        println!("no video-post instances running");         return Ok(());     }     println!(@@ -208,7 +208,7 @@ fn human_duration(secs: u64) -> String {     } } -/// `adom-video-post ctrl <target> <action> [args...]` dispatcher.+/// `video-post ctrl <target> <action> [args...]` dispatcher. pub fn cmd_ctrl(target: &str, action: &CtrlAction) -> Result<(), String> {     let instance = resolve_target(target)?;     let base = format!("http://127.0.0.1:{}", instance.port);
src/ffmpeg.rs+5−5
@@ -50,7 +50,7 @@ pub fn probe_duration(input: &Path) -> Result<f64, String> {     // proper container duration header; remuxing forces ffmpeg to     // compute and write one.     let tmp = std::env::temp_dir().join(format!(-        "adom-video-post-probe-{}.webm",+        "video-post-probe-{}.webm",         std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_nanos()).unwrap_or(0)     ));     let remux = Command::new("ffmpeg")@@ -477,7 +477,7 @@ pub fn append_still(     // Step 1: encode the still as a short webm matching the source     // dimensions and codec params.     let still_tmp = std::env::temp_dir().join(format!(-        "adom-video-post-still-{}.webm",+        "video-post-still-{}.webm",         std::process::id()     ));     let pad_filter = format!(@@ -522,7 +522,7 @@ pub fn append_still(     // Step 2: concat video + still into output. Try copy first,     // fall back to re-encode.     let list_tmp = std::env::temp_dir().join(format!(-        "adom-video-post-concat-{}.txt",+        "video-post-concat-{}.txt",         std::process::id()     ));     std::fs::write(@@ -582,13 +582,13 @@ pub fn append_still( /// Concatenate multiple webm videos into one. Uses ffmpeg's concat /// demuxer with `-c copy` (lossless, fast) — assumes the inputs /// share codec and timebase, which is true for everything produced-/// by `adom-video-post process` + `append_still`.+/// by `video-post process` + `append_still`. pub fn concat_videos(inputs: &[std::path::PathBuf], output: &Path) -> Result<(), String> {     if inputs.is_empty() {         return Err("concat: no input videos".to_string());     }     let list_tmp = std::env::temp_dir().join(format!(-        "adom-video-post-concat-list-{}.txt",+        "video-post-concat-list-{}.txt",         std::process::id()     ));     let mut body = String::new();
src/install.rs+2−2
@@ -1,4 +1,4 @@-//! Deploy the embedded SKILL.md to ~/.claude/skills/adom-video-post/SKILL.md+//! Deploy the embedded SKILL.md to ~/.claude/skills/video-post/SKILL.md //! so Claude Code finds it via trigger words.  use std::fs;@@ -8,7 +8,7 @@ const SKILL_MD: &str = include_str!("../SKILL.md");  pub fn install() -> Result<PathBuf, String> {     let home = std::env::var("HOME").map_err(|_| "HOME env var not set".to_string())?;-    let dir = PathBuf::from(home).join(".claude").join("skills").join("adom-video-post");+    let dir = PathBuf::from(home).join(".claude").join("skills").join("video-post");     fs::create_dir_all(&dir).map_err(|e| format!("create {}: {}", dir.display(), e))?;     let path = dir.join("SKILL.md");     fs::write(&path, SKILL_MD).map_err(|e| format!("write {}: {}", path.display(), e))?;
src/main.rs+20−20
@@ -1,4 +1,4 @@-//! adom-video-post: post-process screen recordings with speedup overlays.+//! video-post: post-process screen recordings with speedup overlays. //! //! See SKILL.md for the full feature description. @@ -114,7 +114,7 @@ macro_rules! enote { }  #[derive(Parser)]-#[command(name = "adom-video-post", version, about = "AI-driven demo-finishing studio: review every captured clip, flag & re-record the weak ones, speed up dead air, narrate, mux + auto-level, validate the final cut, and publish to the wiki")]+#[command(name = "video-post", version, about = "Post-process demo recordings: speed up boring sections, voiceover, publish")] struct Cli {     #[command(subcommand)]     cmd: Cmd,@@ -138,7 +138,7 @@ enum Cmd {     /// Speedup post-process: read markers + input video, produce sped-up output     Process(ProcessArgs), -    /// Unified Adom Video Post app: opens one Hydrogen tab with all three+    /// Unified Video Post app: opens one Hydrogen tab with all three     /// post-production phases (Review / Narrate / Publish) behind a     /// phase selector. Takes a manifest like `storyboard` does. This     /// is the recommended entrypoint — the standalone `storyboard`@@ -172,25 +172,25 @@ enum Cmd {     /// Publish a final video to a wiki page (wraps adom-wiki asset upload)     Publish(PublishArgs), -    /// Run a JavaScript snippet inside a running adom-video-post web UI and+    /// Run a JavaScript snippet inside a running video-post web UI and     /// print the result. Uses the /eval hot-patch channel from the     /// app-creator skill — works against voiceover, storyboard, and     /// any future webapp subcommand without a rebuild.     Eval(EvalArgs), -    /// List every running adom-video-post web UI (reads the instance-    /// registry in `~/.adom/adom-video-post/instances/`, filters out stale+    /// List every running video-post web UI (reads the instance+    /// registry in `~/.adom/video-post/instances/`, filters out stale     /// PIDs). Shows PID, port, subcommand, title, age.     List, -    /// One-stop control surface for a running adom-video-post web UI.+    /// One-stop control surface for a running video-post web UI.     /// Target can be `latest`, a subcommand name (`voiceover`,     /// `storyboard`), a port, `pid:<pid>`, or a bare pid. Actions     /// include `state`, `console [--tail --since --follow]`,     /// `eval <code>`, `shutdown`, and `call <METHOD> <PATH>`.     Ctrl(CtrlArgs), -    /// Deploy the embedded SKILL.md to ~/.claude/skills/adom-video-post/+    /// Deploy the embedded SKILL.md to ~/.claude/skills/video-post/     Install, } @@ -277,7 +277,7 @@ struct ProcessArgs {     /// Disable the red "Nx SPEEDUP: label" drawtext overlay on speedup     /// sections. Useful when the caller is already burning in its own     /// captions (e.g. desktop_caption step markers) during recording,-    /// so the adom-video-post overlay would just cover them up. When set,+    /// so the video-post overlay would just cover them up. When set,     /// the speedup still happens — only the drawtext caption is skipped.     #[arg(long, default_value_t = false)]     no_overlay: bool,@@ -296,7 +296,7 @@ struct AppArgs {     #[arg(long, default_value_t = 8798)]     port: u16,     /// Hydrogen webview tab name.-    #[arg(long, default_value_t = String::from("Adom Video Post"))]+    #[arg(long, default_value_t = String::from("Video Post"))]     tab_name: String,     /// Force a specific Hydrogen pane id. Omit to auto-pick a pane     /// that does NOT contain VS Code.@@ -429,14 +429,14 @@ struct VoiceoverArgs {     #[arg(long, default_value_t = 8796)]     port: u16,     /// Hydrogen webview tab name-    #[arg(long, default_value_t = String::from("Adom Video Post-Processing"))]+    #[arg(long, default_value_t = String::from("Video Post-Processing"))]     tab_name: String,     /// Hydrogen panel ID to host the tab. Defaults to the focused panel.     /// Use this to host the tab on a specific pane instead of whatever is     /// currently focused.     #[arg(long)]     panel_id: Option<String>,-    /// Optional markers file from an earlier `adom-video-post process` run.+    /// Optional markers file from an earlier `video-post process` run.     /// When present, the app shows a Speedup Timeline card with the     /// source video timeline, highlighted speedup regions, and summary     /// stats (input vs output duration, % saved).@@ -454,7 +454,7 @@ struct EvalArgs {     /// `async function (ctx) { ... }`, so `await` works and `return`     /// at the top level is valid. `ctx` is the UI's `window.__appCtx`.     code: String,-    /// Port of the running adom-video-post web UI. Defaults to 8796+    /// Port of the running video-post web UI. Defaults to 8796     /// (voiceover). For storyboard, pass `--port 8797`.     #[arg(long, default_value_t = 8796)]     port: u16,@@ -469,7 +469,7 @@ struct CtrlArgs {     /// `storyboard`), a port number (e.g. `8796`), a bare pid, or     /// `pid:<pid>`.     target: String,-    /// Action to perform (see `adom-video-post ctrl --help` or the+    /// Action to perform (see `video-post ctrl --help` or the     /// subcommand-level --help for this variant).     #[command(subcommand)]     action: CtrlActionArgs,@@ -620,7 +620,7 @@ fn cmd_wrap(args: WrapArgs) -> ExitCode {     }     if args.command.is_empty() {         err("wrap requires a command after `--`");-        hint("Example: adom-video-post wrap --speed 20 --label 'Exporting' -- adom-desktop fusion_export_gerbers");+        hint("Example: video-post wrap --speed 20 --label 'Exporting' -- adom-desktop fusion_export_gerbers");         return ExitCode::from(1);     }     // Show a live Hydrogen caption so the USER watching the recording@@ -886,7 +886,7 @@ fn cmd_process(args: ProcessArgs) -> Result<(), String> { fn cmd_voiceover(args: VoiceoverArgs) -> Result<(), String> {     if !args.input.exists() {         return Err(format!(-            "input video not found: {}. Hint: produce it with `adom-video-post process` first.",+            "input video not found: {}. Hint: produce it with `video-post process` first.",             args.input.display()         ));     }@@ -956,7 +956,7 @@ fn cmd_app(args: AppArgs) -> Result<(), String> {         no_open: args.no_open,     };     note!(-        "{CYAN}starting adom-video-post app on port {}{RESET}",+        "{CYAN}starting video-post app on port {}{RESET}",         cfg.port     );     app::run(cfg)?;@@ -1089,7 +1089,7 @@ fn cmd_concat(args: ConcatArgs) -> Result<(), String> {     Ok(()) } -/// Run `code` against a live adom-video-post web UI via the /eval hot-patch+/// Run `code` against a live video-post web UI via the /eval hot-patch /// channel. Shells out to curl so we don't need an HTTP-client dep — /// both Docker and macOS always have curl. The flow is: ///@@ -1118,10 +1118,10 @@ fn cmd_eval(args: EvalArgs) -> Result<(), String> {             &format!("{}/eval", base),         ])         .output()-        .map_err(|e| format!("curl spawn failed: {}. Hint: curl is required for `adom-video-post eval`.", e))?;+        .map_err(|e| format!("curl spawn failed: {}. Hint: curl is required for `video-post eval`.", e))?;     if !post.status.success() {         return Err(format!(-            "POST /eval failed: {}. Hint: is a adom-video-post web UI running on port {}? Run `adom-video-post voiceover --input ...` or `adom-video-post storyboard ...` first.",+            "POST /eval failed: {}. Hint: is a video-post web UI running on port {}? Run `video-post voiceover --input ...` or `video-post storyboard ...` first.",             String::from_utf8_lossy(&post.stderr),             args.port         ));
src/markers.rs+3−3
@@ -51,7 +51,7 @@ pub fn now_epoch() -> f64 { /// Default markers file path. Honors $VIDEO_SPEEDUP_MARKERS env var. pub fn default_path() -> String {     std::env::var("VIDEO_SPEEDUP_MARKERS")-        .unwrap_or_else(|_| "/tmp/adom-video-post-markers.jsonl".to_string())+        .unwrap_or_else(|_| "/tmp/video-post-markers.jsonl".to_string()) }  /// Initialize the markers file: clear it and write a recording_start event.@@ -131,11 +131,11 @@ pub fn parse(events: &[Event]) -> Result<ParseResult, String> {         Some(Event::RecordingStart { epoch }) => *epoch,         Some(other) => {             return Err(format!(-                "first event must be recording_start, got {:?}. Hint: call `adom-video-post mark init` before any other markers.",+                "first event must be recording_start, got {:?}. Hint: call `video-post mark init` before any other markers.",                 other             ));         }-        None => return Err("markers file is empty. Hint: call `adom-video-post mark init` first.".to_string()),+        None => return Err("markers file is empty. Hint: call `video-post mark init` first.".to_string()),     };      let mut segments = Vec::new();
src/publish.rs+51−86
@@ -1,20 +1,12 @@ //! Phase 3: publish a final video to the Adom Wiki. //!-//! Pushes the polished demo video into the wiki page's repo via-//! `adom-wiki repo push --files <video> <org/slug> -m "<msg>"`. Binaries push-//! fine: the mp4 lands in the page's Files tab and serves at-//! `https://wiki.adom.inc/api/v1/pages/<slug>/files/<file>`, which is the URL we-//! return so callers can embed it.-//!-//! (The old `adom-wiki asset upload` pillar this used to wrap was removed; the-//! CLI no longer has an `asset` command. For versioned media,-//! `adom-wiki release upload <ref> <ver> <file>` exists, but for a page demo-//! video `repo push` is the right call.)+//! Wraps `adom-wiki asset upload <page-ref> --asset-type video --file <path>`+//! so the polished demo video gets attached to the wiki page for whatever+//! you're demoing (an app, a skill, a molecule, a library, a datasheet). +use std::path::Path; use std::process::Command; -const WIKI_BASE: &str = "https://wiki.adom.inc";- pub struct PublishConfig {     pub input: std::path::PathBuf,     pub page: String,@@ -25,15 +17,23 @@ pub struct PublishConfig { pub fn run(cfg: PublishConfig) -> Result<String, String> {     if !cfg.input.exists() {         return Err(format!(-            "input video not found: {}. Hint: produce it via `adom-video-post voiceover` first.",+            "input video not found: {}. Hint: produce it via `video-post voiceover` first.",             cfg.input.display()         ));     } -    // Validate page-ref shape: <owner>/<slug> (e.g. adom/adom-desktop-fusion-bridge).-    let (_owner, slug) = parse_page_ref(&cfg.page)?;+    // Validate page-ref shape: <type>/<slug>+    let mut parts = cfg.page.splitn(2, '/');+    let page_type = parts.next().unwrap_or("");+    let page_slug = parts.next().unwrap_or("");+    if page_type.is_empty() || page_slug.is_empty() {+        return Err(format!(+            "invalid --page '{}': expected format <type>/<slug> like apps/adom-desktop, skills/symbol-creator, molecules/drv8411a-breakout. Hint: see https://wiki-ufypy5dpx93o.adom.cloud for valid pages.",+            cfg.page+        ));+    } -    // Validate file extension against the asset type.+    // Validate file extension     let ext = cfg         .input         .extension()@@ -60,7 +60,7 @@ pub fn run(cfg: PublishConfig) -> Result<String, String> {         ));     } -    // Check adom-wiki is installed.+    // Check adom-wiki is installed     if which("adom-wiki").is_none() {         return Err(             "adom-wiki CLI not found in PATH. Hint: it ships with every Adom container. Run `which adom-wiki` to verify, or contact your container admin."@@ -68,59 +68,52 @@ pub fn run(cfg: PublishConfig) -> Result<String, String> {         );     } -    let filename = cfg-        .input-        .file_name()-        .and_then(|f| f.to_str())-        .ok_or_else(|| format!("could not read filename from {}", cfg.input.display()))?;--    let message = match &cfg.caption {-        Some(c) => format!("Add {} demo {}: {}", cfg.asset_type, filename, c),-        None => format!("Add {} {} via video-post publish", cfg.asset_type, filename),-    };--    // Build the push command: adom-wiki repo push --files <input> <page> -m "<msg>"+    // Build the upload command     let mut cmd = Command::new("adom-wiki");-    cmd.arg("repo").arg("push");-    cmd.arg("--files").arg(&cfg.input);-    cmd.args(["-m", &message]);-    cmd.arg(&cfg.page);+    cmd.args(["asset", "upload", &cfg.page]);+    cmd.args(["--asset-type", &cfg.asset_type]);+    cmd.args(["--file"]).arg(&cfg.input);+    if let Some(caption) = &cfg.caption {+        cmd.args(["--caption", caption]);+    }      println!("uploading {} to wiki page {}...", cfg.input.display(), cfg.page);      let result = cmd.output().map_err(|e| format!("adom-wiki spawn failed: {}", e))?;     if !result.status.success() {         let stderr = String::from_utf8_lossy(&result.stderr);-        let stdout = String::from_utf8_lossy(&result.stdout);-        return Err(format!(-            "adom-wiki repo push failed:\n{}{}",-            stdout, stderr-        ));+        return Err(format!("adom-wiki upload failed:\n{}", stderr));     } -    // The served URL is deterministic: <base>/api/v1/pages/<slug>/files/<file>.-    Ok(served_file_url(slug, filename))+    let stdout = String::from_utf8_lossy(&result.stdout);+    // Try to extract the asset URL or id from the JSON response+    let url = extract_asset_url(&stdout, &cfg.page);+    Ok(url) } -/// Parse a page ref of the form `<owner>/<slug>` and return (owner, slug).-/// The served file URL keys off the slug only (the last path segment).-fn parse_page_ref(page: &str) -> Result<(&str, &str), String> {-    let mut parts = page.splitn(2, '/');-    let owner = parts.next().unwrap_or("");-    let slug = parts.next().unwrap_or("");-    if owner.is_empty() || slug.is_empty() || slug.contains('/') {-        return Err(format!(-            "invalid --page '{}': expected format <owner>/<slug> like adom/adom-desktop-fusion-bridge, adom/symbol-creator. Hint: browse pages at {}.",-            page, WIKI_BASE-        ));+fn extract_asset_url(stdout: &str, page: &str) -> String {+    // adom-wiki returns JSON like {"asset":{"id":42,"asset_type":"video","filename":"...","caption":"...","url":"..."}}+    // Try to parse and extract the URL or fall back to a wiki page link.+    if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(stdout) {+        if let Some(url) = parsed+            .get("asset")+            .and_then(|a| a.get("url"))+            .and_then(|u| u.as_str())+        {+            return url.to_string();+        }+        if let Some(file) = parsed+            .get("asset")+            .and_then(|a| a.get("filename"))+            .and_then(|f| f.as_str())+        {+            return format!(+                "https://wiki-ufypy5dpx93o.adom.cloud/wiki/{} (asset: {})",+                page, file+            );+        }     }-    Ok((owner, slug))-}--/// Build the URL the pushed file serves at, e.g.-/// https://wiki.adom.inc/api/v1/pages/adom-desktop-fusion-bridge/files/library-demo.mp4-fn served_file_url(slug: &str, filename: &str) -> String {-    format!("{}/api/v1/pages/{}/files/{}", WIKI_BASE, slug, filename)+    format!("https://wiki-ufypy5dpx93o.adom.cloud/wiki/{}", page) }  fn which(cmd: &str) -> Option<std::path::PathBuf> {@@ -133,31 +126,3 @@ fn which(cmd: &str) -> Option<std::path::PathBuf> {     }     None }--#[cfg(test)]-mod tests {-    use super::*;--    #[test]-    fn parses_owner_and_slug() {-        let (owner, slug) = parse_page_ref("adom/adom-desktop-fusion-bridge").unwrap();-        assert_eq!(owner, "adom");-        assert_eq!(slug, "adom-desktop-fusion-bridge");-    }--    #[test]-    fn rejects_missing_slug() {-        assert!(parse_page_ref("adom").is_err());-        assert!(parse_page_ref("").is_err());-        assert!(parse_page_ref("/slug").is_err());-        assert!(parse_page_ref("a/b/c").is_err());-    }--    #[test]-    fn builds_served_url_from_slug_only() {-        assert_eq!(-            served_file_url("adom-desktop-fusion-bridge", "library-demo.mp4"),-            "https://wiki.adom.inc/api/v1/pages/adom-desktop-fusion-bridge/files/library-demo.mp4"-        );-    }-}
src/storyboard/manifest.rs+7−7
@@ -2,11 +2,11 @@ //! //! A manifest is the single source of truth for a multi-clip video //! review session. It's written incrementally by the user's demo-//! script (via `adom-video-post manifest add`, called once per step), and+//! script (via `video-post manifest add`, called once per step), and //! read by every post-processing subcommand (`storyboard`, `process`, //! `voiceover`, `publish`, `concat`). //!-//! The demo script stays 100% custom bash/python — adom-video-post does+//! The demo script stays 100% custom bash/python — video-post does //! not try to execute recording logic itself. Only the handoff //! artifact is structured. @@ -28,18 +28,18 @@ pub struct Manifest {     #[serde(default)]     pub clips: Vec<Clip>, -    /// Absolute path to the raw concat produced by `adom-video-post concat+    /// Absolute path to the raw concat produced by `video-post concat     /// --kind raw`. Set by that subcommand when it runs.     #[serde(default, skip_serializing_if = "Option::is_none")]     pub final_raw_path: Option<PathBuf>, -    /// Absolute path to the fast concat produced by `adom-video-post concat+    /// Absolute path to the fast concat produced by `video-post concat     /// --kind fast`.     #[serde(default, skip_serializing_if = "Option::is_none")]     pub final_fast_path: Option<PathBuf>,      /// Optional publish target — where the final narrated video-    /// should land when the user eventually runs `adom-video-post publish`.+    /// should land when the user eventually runs `video-post publish`.     #[serde(default, skip_serializing_if = "Option::is_none")]     pub publish: Option<PublishTarget>, }@@ -64,7 +64,7 @@ pub struct Clip {     pub raw_path: Option<PathBuf>,      /// Post-speedup version of `raw_path` (absolute path). Produced-    /// by `adom-video-post process`.+    /// by `video-post process`.     #[serde(default, skip_serializing_if = "Option::is_none")]     pub fast_path: Option<PathBuf>, @@ -86,7 +86,7 @@ pub struct Clip {     pub console_png_path: Option<PathBuf>,      /// Zero or more speedup events that were applied (or will be-    /// applied) inside this step's clip. `adom-video-post process` uses+    /// applied) inside this step's clip. `video-post process` uses     /// them to build its ffmpeg filter graph; the storyboard UI     /// surfaces them as colored badges on the card.     #[serde(default, skip_serializing_if = "Vec::is_empty")]
src/storyboard/manifest_cli.rs+2−2
@@ -1,7 +1,7 @@-//! `adom-video-post manifest {init,add,inspect,remove}` handlers.+//! `video-post manifest {init,add,inspect,remove}` handlers. //! //! The demo script calls `add` once per completed step, which is-//! the whole contract between the demo-side bash and adom-video-post's+//! the whole contract between the demo-side bash and video-post's //! post-processing side. Everything else (`storyboard`, `process`, //! `voiceover`, `publish`, `concat`) reads the resulting file. 
src/storyboard/mod.rs+1−1
@@ -43,7 +43,7 @@ pub struct StoryboardConfig { pub fn run(cfg: StoryboardConfig) -> Result<(), String> {     if !cfg.manifest_path.exists() {         return Err(format!(-            "manifest not found: {}. Hint: create one with `adom-video-post manifest init`.",+            "manifest not found: {}. Hint: create one with `video-post manifest init`.",             cfg.manifest_path.display()         ));     }
src/storyboard/routes.rs+2−2
@@ -159,7 +159,7 @@ impl StoryboardRoutes {     /// on failure.     ///     /// This is the endpoint the "Prepare for narration" button in-    /// the unified `adom-video-post app` UI hits to enable the Narrate+    /// the unified `video-post app` UI hits to enable the Narrate     /// phase without the user having to drop back to the CLI.     fn handle_concat(&self, request: Request) -> Result<(), String> {         let inputs: Vec<std::path::PathBuf> = {@@ -194,7 +194,7 @@ impl StoryboardRoutes {                 .to_string()         };         let slug = if title_slug.is_empty() {-            "adom-video-post".to_string()+            "video-post".to_string()         } else {             title_slug         };
src/storyboard/ui.html+5−5
@@ -592,7 +592,7 @@     <svg class="logo" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">       <path d="M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4zm2 2v2h2V6H5zm4 0v12h6V6H9zm8 0v2h2V6h-2zM5 10v2h2v-2H5zm12 0v2h2v-2h-2zM5 14v2h2v-2H5zm12 0v2h2v-2h-2zM5 18v0h2v0H5zm12 0v0h2v0h-2zM11 8v8h2V8h-2z"/>     </svg>-    <span class="app-name">adom-video-post</span>+    <span class="app-name">video-post</span>     <span class="sep">·</span>     <span class="app-subtitle">storyboard</span>   </div>@@ -606,7 +606,7 @@   </div>    <div class="hover-popover" id="app-info" role="dialog" aria-label="App information">-    <div class="popover-title">adom-video-post</div>+    <div class="popover-title">video-post</div>     <div class="popover-row"><span class="k">about</span>       <span class="v">Per-clip review, voiceover narration, and final concat for Adom feature-tour demos.</span></div>     <div class="popover-row"><span class="k">screen</span><span class="v">storyboard · per-clip review</span></div>@@ -615,8 +615,8 @@     <div class="popover-row"><span class="k">tts</span>       <span class="v"><a class="popover-link" href="https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-tts" target="_blank" rel="noopener" data-tooltip="Open the adom-tts wiki page — shared edge-tts wrapper with pronunciation overrides. Run `adom-tts` to launch the playground locally.">adom-tts ↗</a> — house TTS engine + pronunciation overrides shared by every Adom tool that speaks. Mispronounced terms? Add them once there.</span></div>     <div class="popover-actions">-      <a class="popover-link" href="https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-video-post" target="_blank" rel="noopener">Open wiki ↗</a>-      <a class="popover-link" href="https://github.com/adom-inc/adom-video-post" target="_blank" rel="noopener">GitHub ↗</a>+      <a class="popover-link" href="https://wiki-ufypy5dpx93o.adom.cloud/apps/video-post" target="_blank" rel="noopener">Open wiki ↗</a>+      <a class="popover-link" href="https://github.com/adom-inc/video-post" target="_blank" rel="noopener">GitHub ↗</a>     </div>   </div> @@ -891,7 +891,7 @@ function renderManifest(manifest, version) {   if (clipCount === 0) {     stepsEl.innerHTML = `<div class="empty-state">       <strong>No clips yet.</strong><br><br>-      Your demo script should call <code>adom-video-post manifest add</code> once per completed step.<br>+      Your demo script should call <code>video-post manifest add</code> once per completed step.<br>       The UI live-updates as clips are added.     </div>`;     return;
src/voiceover/mod.rs+2−2
@@ -1,4 +1,4 @@-//! Voiceover subcommand — phase 2 of adom-video-post.+//! Voiceover subcommand — phase 2 of video-post. //! //! Plays the input (sped-up) video back in a Hydrogen webview tab, //! captures mic audio via Hydrogen's first-class audio API while@@ -39,7 +39,7 @@ pub struct VoiceoverConfig { pub fn run(cfg: VoiceoverConfig) -> Result<PathBuf, String> {     if !cfg.input.exists() {         return Err(format!(-            "input video not found: {}. Hint: run `adom-video-post process` first to produce a sped-up video.",+            "input video not found: {}. Hint: run `video-post process` first to produce a sped-up video.",             cfg.input.display()         ));     }
src/voiceover/server.rs+1−1
@@ -409,7 +409,7 @@ fn handle_waveform(         let _ = request.respond(text_response(404, "audio file not found"));         return Ok(());     }-    let tmp_png = std::env::temp_dir().join(format!("adom-video-post-waveform-{}.png", which));+    let tmp_png = std::env::temp_dir().join(format!("video-post-waveform-{}.png", which));     let result = Command::new("ffmpeg")         .args(["-y", "-i"])         .arg(&source)
src/voiceover/ui.html+7−8
@@ -2,7 +2,7 @@ <html> <head> <meta charset="utf-8">-<title>Adom Video Post-Processing</title>+<title>Video Post-Processing</title> <link rel="icon" type="image/svg+xml" href="favicon.svg"> <style> @font-face {@@ -757,26 +757,25 @@ span[data-tooltip] { cursor: help; text-decoration: underline dotted var(--text3     <svg class="logo" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">       <path d="M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4zm2 2v2h2V6H5zm4 0v12h6V6H9zm8 0v2h2V6h-2zM5 10v2h2v-2H5zm12 0v2h2v-2h-2zM5 14v2h2v-2H5zm12 0v2h2v-2h-2zM5 18v0h2v0H5zm12 0v0h2v0h-2zM11 8v8h2V8h-2z"/>     </svg>-    <span class="app-name">adom-video-post</span>+    <span class="app-name">video-post</span>     <span class="sep">·</span>     <span class="app-subtitle">voiceover</span>   </div>   <div class="vsep" aria-hidden="true"></div>   <div class="subject">-    <span class="subject-name">Finishing Studio</span>-    <span class="subject-meta">Review every clip · narrate · mux + auto-level · validate · save the final cut</span>+    <span class="subject-name">Voiceover</span>+    <span class="subject-meta">Narrate the sped-up demo · save the final cut</span>   </div>   <div class="hover-popover" id="app-info" role="dialog" aria-label="App information">-    <div class="popover-title">adom-video-post · voiceover</div>+    <div class="popover-title">video-post · voiceover</div>     <div class="popover-row"><span class="k">about</span>-      <span class="v">The finishing pass: review every captured clip, narrate (3-2-1 countdown, then talk while the video plays), mux + auto-level the audio (EBU R128), validate the cut, and save the final.</span></div>+      <span class="v">Mic-driven narration over the sped-up demo. 3-2-1 countdown, then talk while the video plays.</span></div>     <div class="popover-row"><span class="k">version</span><span class="v">v0.3.0</span></div>     <div class="popover-row"><span class="k">author</span><span class="v">Adom Inc · adom.inc</span></div>     <div class="popover-row"><span class="k">tts</span>       <span class="v"><a class="popover-link" href="https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-tts" target="_blank" rel="noopener">adom-tts ↗</a> — for AI narration instead of mic, run `adom-tts say "<text>"`. Pronunciation overrides for Adom terms (KiCad, adom-tsci, etc.) apply uniformly.</span></div>     <div class="popover-actions">-      <a class="popover-link" href="https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-video-post" target="_blank" rel="noopener">Open wiki ↗</a>-      <a class="popover-link" href="https://github.com/adom-inc/adom-video-post" target="_blank" rel="noopener">GitHub ↗</a>+      <a class="popover-link" href="https://wiki-ufypy5dpx93o.adom.cloud/apps/video-post" target="_blank" rel="noopener">Open wiki ↗</a>     </div>   </div> </header>
src/webapp/console.rs+2−2
@@ -4,7 +4,7 @@ //! server via `POST /console` so the CLI operator (and the AI driving //! the tool) can read what the UI is saying without having to open //! devtools in the Hydrogen webview. The same buffer is the target-//! of `adom-video-post ctrl <target> console --tail` / `--follow` so the+//! of `video-post ctrl <target> console --tail` / `--follow` so the //! AI can stream UI logs into the Claude Code Monitor tool.  use std::collections::VecDeque;@@ -46,7 +46,7 @@ pub fn new() -> SharedConsole { /// ring. With `since=N`, returns only messages whose seq is > N. /// With `wait=MS` AND nothing new to return, blocks up to MS on a /// Condvar waiting for a new append. This is the long-poll variant-/// used by `adom-video-post ctrl ... console --follow` to feed realtime+/// used by `video-post ctrl ... console --follow` to feed realtime /// UI logs into the Claude Code Monitor tool. pub fn handle_get(request: Request, console: &SharedConsole, url: &str) -> Result<(), String> {     let query = url.split_once('?').map(|(_, q)| q).unwrap_or("");
src/webapp/eval.rs+1−1
@@ -128,7 +128,7 @@ pub fn handle_get_pending(request: Request, eval: &SharedEval) -> Result<(), Str }  /// `GET /eval/:id/result` — the CLI caller (usually-/// `adom-video-post eval`) polls this to retrieve the snippet's return+/// `video-post eval`) polls this to retrieve the snippet's return /// value. Long-polls up to 25 seconds on a Condvar. Returns 200 + /// the stored result object on success, 204 No Content on timeout. pub fn handle_get_result(request: Request, eval: &SharedEval, id: &str) -> Result<(), String> {
src/webapp/eval_client.js+1−1
@@ -1,5 +1,5 @@ <script>-// adom-video-post frontend eval channel (app-creator SKILL.md §7b).+// video-post frontend eval channel (app-creator SKILL.md §7b). // // Long-polls /eval/pending, runs each pending snippet as an async // function body with `ctx = window.__appCtx || {}`, POSTs the
src/webapp/mod.rs+2−2
@@ -1,4 +1,4 @@-//! Shared HTTP + webview infrastructure for every `adom-video-post` subcommand+//! Shared HTTP + webview infrastructure for every `video-post` subcommand //! that exposes a web UI (voiceover, storyboard, future review apps). //! //! The pattern is: a subcommand implements the `Routes` trait — returning@@ -49,7 +49,7 @@ pub struct WebappConfig {     /// Short identifier of the subcommand for the instance registry     /// ("voiceover", "storyboard", etc).     pub subcommand: String,-    /// Human-readable title shown in `adom-video-post list`. Usually the+    /// Human-readable title shown in `video-post list`. Usually the     /// input filename or manifest title.     pub title: String,     /// When true, start the HTTP server but skip the Hydrogen webview
src/webapp/registry.rs+5−5
@@ -1,10 +1,10 @@-//! Instance registry for running adom-video-post web UIs.+//! Instance registry for running video-post web UIs. //! //! Every `run_webapp` invocation writes a small JSON file to-//! `~/.adom/adom-video-post/instances/{pid}.json` on startup, and+//! `~/.adom/video-post/instances/{pid}.json` on startup, and //! deletes it on clean shutdown or on SIGTERM/SIGINT. The-//! `adom-video-post list` subcommand reads this directory (filtering-//! out stale entries whose pid is dead), and `adom-video-post ctrl`+//! `video-post list` subcommand reads this directory (filtering+//! out stale entries whose pid is dead), and `video-post ctrl` //! resolves symbolic targets like `voiceover` / `storyboard` / //! `latest` to a concrete port by scanning it. //!@@ -57,5 +57,5 @@ pub fn unregister(path: &std::path::Path) {  pub fn instances_dir() -> Result<PathBuf, String> {     let home = std::env::var("HOME").map_err(|_| "HOME not set".to_string())?;-    Ok(PathBuf::from(home).join(".adom/adom-video-post/instances"))+    Ok(PathBuf::from(home).join(".adom/video-post/instances")) }
src/webapp/server.rs+2−2
@@ -1,7 +1,7 @@ //! The shared tiny_http request loop. //! //! `serve` starts a thread-per-request HTTP server on `cfg.port`,-//! registers the instance in `~/.adom/adom-video-post/instances/`,+//! registers the instance in `~/.adom/video-post/instances/`, //! opens a Hydrogen webview tab pointed at the server, and spins //! on incoming requests until a `/shutdown` request arrives or //! the process is killed.@@ -24,7 +24,7 @@ pub fn serve(cfg: WebappConfig, routes: Arc<dyn Routes>) -> Result<(), String> {     let addr = format!("127.0.0.1:{}", cfg.port);     let server = Server::http(&addr).map_err(|e| format!("bind {}: {}", addr, e))?; -    // Register in the instance directory so `adom-video-post list` / ctrl+    // Register in the instance directory so `video-post list` / ctrl     // can find us. Any error here is non-fatal — we log and continue.     let registry_entry = registry::InstanceEntry {         pid: std::process::id(),