Download

name: adom-shotlog description: "Screenshot log viewer and injector. Use when: logging screenshots during debug loops, documenting visual progress, reviewing screenshot history, opening the screenshot log panel, injecting a screenshot into the log, ingesting an Adom Desktop screenshot response with child windows (owned popups). Trigger words: adom-shotlog, shotlog, screenshot log, shot log, visual log, screenshot history, log screenshot, open shotlog, screenshot viewer."

adom-shotlog - Screenshot Log

A Rust CLI + HTTP server (port 8820) that captures, stores, and displays screenshots in channels. AI agents inject screenshots as they work; you watch them arrive live in a gallery (desktop or mobile), newest first, each with its description, source, and both the stored and original resolution.

Binary: /usr/local/bin/adom-shotlog (the shotlog command is a back-compat alias).

This skill pack (main + sub-skills)

This is the main (user) skill. Deeper topics ship in the same adom/adom-shotlog repo using the wiki's three-tier layout: skills/ = USER (ships + installs for everyone), dev-skills/ = DEV (source-only), and publish-skills/ = PUBLISH (source-only).

Skill Tier What it's for
adom-shotlog-surfacing user surfacing a shot gently per surface (webview activate / pup taskbar flash / phone badge), never stealing foreground; plus AD window-discovery + cue verbs
adom-shotlog-child-windows user capturing a parent window plus its child windows (owned dropdowns/dialogs) via --meta, and how they render inline
adom-shotlog-dev dev build/deploy, the central-broker architecture, and the layout rules (ralph-test the skinny view, no outer-html scrollbars, pretty scrollbars)
adom-shotlog-publishing publish publishing to wiki.adom.inc (org ownership, OSS source, hero, the wiki traps)

⚠ WHY THIS EXISTS: you can't show the human images in a text thread

When you run as Claude Code (VSCode / any text-only thread) you CANNOT display an image to the human. Calling Read on a PNG, or taking a desktop_screenshot / pup screenshot, loads the image into your context - the human sees nothing in the thread. (Maybe someday Claude Code renders images inline; until then, assume it can't.)

So, as an ironclad rule:

  • NEVER write "see the screenshot above", "look at this image", "as shown below", or any phrase that assumes the human can see a picture in the thread. To them it's a blank reference - this exact failure is the whole reason shotlog exists.
  • The only way the human sees an image is on a surface they actually have open: the shotlog viewer (webview / pup / phone), or a URL you hand them. So inject it into a channel they're viewing, verify they're watching (shotlog viewers -c <ch> - see below), and refer them to that surface ("in the shotlog viewer" / "the pup window on your screen" / a link), never to an inline image.

The CLI teaches you as you go (trust its output)

shotlog is hint-driven IN CODE, not just in this skill. Every inject reply tells you two things:

  • OK: Broker: ... lines = what the broker is ALREADY doing (pulling the channel's webview tab to the front with an orange alert, flashing a pup taskbar, force-opening a slim 20% webview). When you see one, surfacing is handled - do NOT open, refresh, or foreground anything yourself.
  • Hint: mini-skill [...] lines = progressive teaching, revealed at the moment they're relevant (first shot in a channel, an AD capture missing --meta, a pup viewer without a ?session= handle) and then they go quiet. Read and apply them; they are the condensed version of this skill pack.

The read-hook: auto-capture of every image you Read

shotlog ships and manages a PostToolUse(Read) hook: any image file you Read is automatically injected into the shared read-captures channel (source read-hook), so the human sees every screenshot you analyze even if you never call inject. Do not disable or duplicate it. It is installed by adom-shotlog install, re-verified at every serve startup, and self-heals the broker (from $SHOTLOG_HOME, default ~/project) if it is down. If you see read-captures filling up: that is this hook working as designed, and retention keeps it bounded.

Preferences and canvas control (AI-drivable)

Everything the human can click, YOU can do - and the human is told the matching prompts in the UI, so expect requests like "turn the shotlog read-hook off" or "make shotlog default to pup". The broker endpoints (port 8820):

curl -s localhost:8820/api/prefs                                     # read prefs
curl -s -X POST localhost:8820/api/prefs -H 'Content-Type: application/json' \
     -d '{"read_hook": false}'                                       # hook off/on
curl -s -X POST localhost:8820/api/prefs -H 'Content-Type: application/json' \
     -d '{"default_canvas": "pup"}'                                  # global default

Canvas rules the broker enforces (do not fight them):

  • Every channel follows the GLOBAL default_canvas. The only exception is an IN-MEMORY note from the human's explicit click in the viewer (canvas button) or their deliberate pup-window close - forgotten on broker restart.
  • YOUR opens (shotlog open ...) do NOT record any preference. Never try to pin a channel's canvas; if the human wants that, they say so.
  • The skinny panel width comes from the webview_width pref (default 20%); do not pass --ratio unless the human asked for a specific width.
  • shotlog open -c <ch> --pup --swap / --skinny --swap is the switch verb.
  • The viewer is ONE single-window app with ITS OWN channel tabs (read-captures pinned first). You never open one surface per channel anymore - plain shotlog open -c <ch> opens/deep-links THE app with that tab active.

ONE window rule: never open your own shotlog surface

The human runs ONE shotlog window (webview or pup) with a tab per channel INSIDE the app. adom-shotlog open -c CH handles this: if the app is already open anywhere, it switches that window's internal tab (the human sees a toast explaining the consolidation) and opens nothing new. Only when NOTHING is open does it create a surface, on the human's default canvas.

NEVER load the viewer URL into your own pup session or a new window/tab (browser_open_window/browser_open_tab with a /log/ URL) - that gives the human a confusing second shotlog. Always go through adom-shotlog open.

Inject - one command

adom-shotlog inject -c CHANNEL -d "what you see and why" -s SOURCE /path/to/image.png

adom-shotlog always records the original capture resolution - and rejects any inject that doesn't establish it, so the viewer never shows "orig missing".

Resizing before inject is fine and expected. Oversized images crash Claude's vision analysis, so most Adom screenshot verbs return a shrunk copy on purpose. adom-shotlog doesn't fight that - it just requires the original size to travel with the shot. Ways through:

  1. Pre-shrunk + the original dimensions (the common case - your screenshot step reports the pre-resize W×H, you pass them through).
    adom-shotlog inject -c proj -d "…" --orig-w 1920 --orig-h 1080 /tmp/shrunk.png
    
  2. Pre-shrunk + the original image - adom-shotlog measures it (keep the full-res file around for this).
    adom-shotlog inject -c proj -d "…" --orig-file /tmp/fullres.png /tmp/shrunk.png
    
  3. --native - assert the image is at original capture resolution (never resized). Use only when true.
  4. Hand it the full-res image - adom-shotlog resizes anything over 1500px (either edge) down to 1400px and records the original automatically.
  5. --meta <ad-response.json> - ingest an Adom Desktop screenshot response (see below). The original W×H comes from the response's origWidth/origHeight, so this satisfies the original-resolution rule on its own.

The rejection: if FILE is ≤1500px (so adom-shotlog won't resize it) and you give none of --orig-w+--orig-h / --orig-file / --native / --meta, the inject is refused with exit 1 and a hint.

HTTP-only inject: POST /api/paste

No CLI on your machine? POST a data URL. Declare your source (same values as -s: pup, ad-window, hd, ...); undeclared defaults to api (a neutral, chip-less deliberate inject). Do NOT claim clipboard - that chip means a HUMAN pasted with Ctrl+V, and only the viewer's paste handler sends it. thread and context are accepted too and power the viewer's attribution and filters.

curl -s -X POST localhost:8820/api/paste -H 'Content-Type: application/json' -d '{
  "channel":"proj", "dataUrl":"data:image/png;base64,...",
  "description":"what you see and why", "source":"pup",
  "thread":"my thread title", "context":"the prompt being served"}'

--meta - Adom Desktop child-window arrays

As of adom-desktop 1.8.177, desktop_screenshot_window {hwnd} returns the primary window image plus its child windows - the owned dropdowns, confirm bubbles, and native dialogs - each captured separately, plus coordMap / DPI / screen metadata. Pass that whole response JSON via --meta:

adom-shotlog inject -c CH -d "checkout form with autofill open" primary.png --meta ad-response.json

adom-shotlog will:

  • store the coordMap / DPI / screenRect / screen-resolution as a sidecar (revealed in the viewer's ⌗ capture metadata panel, copyable as JSON),
  • read each child-window PNG by its localSafePath, save it next to the primary, and attach it - the card shows a +N child windows badge and a child windows ▸ row rendering each child window inline (no zoom),
  • derive the original resolution from origWidth/origHeight.

The response shape it reads (extra fields are ignored, all optional except a primary image + screenshots[] for child windows):

{
  "shotId": "…", "origWidth": 1920, "origHeight": 1200,
  "width": 1400, "height": 875,
  "coordMap": { "screenRect": {…}, "dpiScale": 1.25, "imageToScreen": {…}, "screenResolution": "1920x1200" },
  "ownedPopupCount": 2,
  "screenshots": [
    { "localSafePath": "/path/popup1.png", "hwnd": 12345, "title": "Autofill", "kind": "dropdown", "coordMap": {…} }
  ]
}

Plain single-image injects (no --meta) work exactly as before - child windows / metadata are additive.

Flag Required Description
-c yes Channel name (groups screenshots by task)
-d yes Why this screenshot was taken (becomes the filename, min 6 chars)
-s no Source: pup_screenshot, desktop_screenshot_window, hd_shot, etc. (default cli)
--orig-file for pre-shrunk images Path to the original full-res image; measured
--orig-w / --orig-h for pre-shrunk images Original dims (alternative to --orig-file)
--native for unresized small images Assert FILE is at original resolution
--thread no Which AI thread produced this shot (human-readable). The read-hook fills it automatically from the session transcript; pass your thread's name when injecting deliberately so the human sees WHO.
--meta optional Adom Desktop screenshot-response JSON: child windows + coordMap + orig dims
FILE yes Primary image path (use - for stdin)

CRITICAL: Always write great descriptions

The -d description becomes the filename (slugified) and is shown prominently on every gallery card. Make it specific.

Bad: "screenshot", "test", "after fix" Good: "PCB layout after routing power traces", "Checkout form with autofill dropdown open"

The viewer

adom-shotlog open -c my-project --skinny   # slim 20% webview panel (ambient watching)
adom-shotlog open -c my-project --pup      # FULL-BROWSER pup canvas (real zoom); --swap closes the webview

The human picks the canvas: webview for ambient, pup when they want images big (zoom in the skinny panel cannot exceed the panel). --pup --swap is the one-command switch; the broker auto-flashes the pup taskbar on updates.

  • Gallery, newest-first. Each card: description, timestamp, size (shown as 1400×788 · orig 1920×1080 when they differ), KB, source, a +N child windows badge when the capture grabbed child windows, and an ⓘ meta chip when it has capture metadata. Child windows show inline; tap a card to zoom + see the copyable metadata panel.
  • Mobile-ready, green LED = a viewer is connected, Ctrl+V pastes a screenshot, live updates over WebSocket.

Do not place a shotlog tab on the VS Code pane. Prefer computing a non-VS-Code --panel-id and calling adom-cli hydrogen workspace add-tab.

Force-open safety net

If a screenshot is injected into a channel with no viewer connected, the server auto-opens a viewer tab so the shot is never logged invisibly.

CRITICAL: verify the HUMAN is actually seeing it

Injecting a shot is not the job - the human seeing it is. shotlog tells you whether a live viewer is connected, and on which surface (webview / pup / mobile / browser), in the inject response itself - no guessing, no extra turns:

  • On-screen: OK: 1 of 1 viewer(s) ON-SCREEN [pup] - a human can see this shot.
  • Open but backgrounded: WARN: 1 viewer(s) connected … but ALL are BACKGROUNDED / minimized - the human is NOT looking right now. Bring the pup window / webview tab / phone to the FOREGROUND, then re-check.
  • Nobody watching: WARN: NO live viewer is connected - the human is NOT seeing this shot. A viewer is force-opened as a fallback, but do not assume it worked. Open the viewer on the human's actual surface and re-check.

The signal is real: each viewer holds a WebSocket open and reports its surface (webview/pup/mobile/browser) AND whether it's actually on-screen (Page Visibility) vs a background tab / minimized window.

Gate programmatically instead of burning turns - shotlog viewers exit codes: 0 = a human has it on-screen, 4 = connected but all backgrounded, 3 = nobody:

shotlog viewers -c my-project              # 0=on-screen, 4=backgrounded, 3=none; prints surfaces
shotlog viewers -c my-project --url "$URL" # also HTTP-checks the endpoint you opened

Typical flow: shotlog open -c ch (or load it in pup / on the phone) → keep injecting → if inject ever reports 0 viewers, stop and re-establish the viewer. The viewer reports its surface automatically; when you open it in pup, append ?surface=pup to the URL so presence labels it correctly.

Other commands

adom-shotlog serve                # start the server (port 8820)
adom-shotlog health               # check server status
adom-shotlog viewers -c CHANNEL   # is a human watching? exit 0=yes, 3=no (+ surfaces)
adom-shotlog resize FILE          # rarely needed - inject auto-resizes
adom-shotlog install              # install binary + this skill + completions + `shotlog` alias

Channel naming

Use the project or task name: board-review, hd-setup-steps, debug-session.