Spectre capture output

SkillDev tools

Use when working with Spectre atomic captures, capture.json + screenshot.png written by `spectre capture`, the capture MCP tool, or ComposeAutomator.capture. Covers the versioned capture schema, jq recipes over the tree, capture → act → capture → diff verification, node-key lifetime (including Compose Hot Reload invalidation), division of labor vs the Hot Reload MCP, and captures prune cleanup.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Spectre capture output skill

What this skill tells your AI

The instructions your AI receives, as published by rock3r/spectre in skills/spectre-capture/SKILL.md and read by ahel’s review.

Division of labor (HR MCP vs Spectre)

If this agent also has Compose Hot Reload’s MCP server configured, do not flip a coin per call. Use this rule verbatim:

If you have HR available and want quick sanity checks while iterating on a live app, use the HR MCP; in any other case, Spectre is the right choice.

  • HR MCP — quick reload-native sanity checks while iterating on a live HR run.
  • Spectre (CLI / MCP / capture) — semantics tree, node keys, real input, screenshots, recording, atomic capture, attach to any Spectre-enabled JVM, and wait --reload-settled / wait_for_reload_settled when you need settle + re-inspect.

Reload awareness is optional and dev-loop only. It is not part of Spectre’s JUnit :testing surface. Full user guide: https://spectre.sebastiano.dev/guide/hot-reload/

Atomic capture freezes one Compose window into:

FileRole
screenshot.pngWindow pixels at capture time (primary visual evidence)
capture.jsonVersioned semantics tree + window metadata + summary

Only a decision-grade summary is returned to agents (paths, node counts, image size). The full tree stays on disk — query it with jq (or any JSON tool). This skill is the map.

Skill name (for discovery): spectre-capture. Capture summaries and detach leftovers reports reference this name on purpose.

Where captures live

Default root (mode 0700):

$TMPDIR/spectre/captures/NNNN-<timestamp>/
  capture.json
  screenshot.png
  • Sequence numbers are allocated by scanning the root (no shared counter).
  • Client --out-dir / MCP out_dir overrides the root for that capture only.
  • Append-only ledger: $TMPDIR/spectre/capture-ledger.jsonl.

List / prune:

spectre captures list [--all] [--json]
spectre captures prune --keep 20
spectre captures prune --session <session-id>
spectre captures prune --older-than 7d
# never touches live sessions without --force
# never auto-touches --out-dir captures without --include-out-dir

On detach, Spectre reports that session’s leftover paths and the exact prune command.

capture.json schema (schemaVersion 1)

Stable API surface — a schema bump is a skill bump (release checklist).

{
  "schemaVersion": 1,
  "capturedAt": "ISO-8601 UTC",
  "window": {
    "index": 0,
    "surfaceId": "…",
    "title": "…",
    "isPopup": false,
    "boundsScreen": { "x": 0, "y": 0, "width": 0, "height": 0 },
    "densityScaleX": 1.0,
    "densityScaleY": 1.0,
    "imageWidth": 800,
    "imageHeight": 600
  },
  "nodes": [ /* flat DFS list; no parent/child edges in v1 */ ],
  "summary": {
    "nodeCount": 0,
    "taggedNodeCount": 0,
    "textedNodeCount": 0,
    "imageWidth": 800,
    "imageHeight": 600,
    "captureDurationMs": 0
  }
}

Node fields

FieldMeaning
keyOwner-scoped node key for click / friends on the same attach session
testTagCompose Modifier.testTag
text / textsVisible text
editableTextText field contents when present
contentDescriptionA11y description
roleSemantics role string when present
enabled / clickable / focused / selectedFlags
boundsImagePrimary — pixels of screenshot.png
boundsScreenSecondary — screen-space for input targeting

screenshot.png is screen-pixel sized, so on a 2× display it is twice the dp size of the window and window.imageWidthwindow.boundsScreen.width. Use boundsImage to address the PNG and boundsScreen to target input — neither needs a manual density conversion. window.densityScaleX / densityScaleY report the ratio when you need to compare captures taken on displays with different densities.

summary.textedNodeCount counts nodes with non-empty text or editableText.

jq recipes

Assume CAP=…/capture.json.

# All clickable nodes with text matching a substring (case-insensitive)
jq -r --arg q 'save' '
  .nodes[]
  | select(.clickable and .enabled)
  | select((.text // "" | ascii_downcase | contains($q))
        or (.editableText // "" | ascii_downcase | contains($q)))
  | "\(.key)\t\(.testTag // "-")\t\(.text // .editableText // "")"
' "$CAP"

# Bounds of a test tag (image pixels)
jq -r --arg tag 'submit' '
  .nodes[] | select(.testTag == $tag)
  | "\(.key) image=\(.boundsImage) screen=\(.boundsScreen)"
' "$CAP"

# Node key for click after finding by tag
KEY=$(jq -r --arg tag 'submit' '
  .nodes[] | select(.testTag == $tag and .clickable) | .key
' "$CAP" | head -n1)

# Diff two captures' node keys + tags + text (structural smoke)
jq -r '.nodes[] | "\(.key)\t\(.testTag // "")\t\(.text // .editableText // "")"' "$CAP_BEFORE" \
  | sort > /tmp/before.txt
jq -r '.nodes[] | "\(.key)\t\(.testTag // "")\t\(.text // .editableText // "")"' "$CAP_AFTER" \
  | sort > /tmp/after.txt
diff -u /tmp/before.txt /tmp/after.txt || true

# Summary only
jq '.summary' "$CAP"

Workflow: capture → act → capture → diff

  1. Settle the UI yourself (waitForIdle / waitForVisualIdle in-process, or wait in the target app). Capture does not auto-idle.
  2. Capture before:
    spectre capture <session-id> --json
    # note directory / captureJson from the summary
    
  3. Find a target with jq on capture.json (prefer testTag, then text).
  4. Act using the node key from that same capture while the session stays attached:
    spectre click <session-id> "$KEY"
    
  5. Capture after and diff trees (or re-query for the expected text/tag).
  6. Prune when done:
    spectre captures prune --session <session-id>
    

Node-key lifetime (critical)

  • Keys are valid for the current attach session and the tree they came from.
  • After navigation, re-composition, or re-attach, re-capture and re-resolve keys.
  • Do not cache keys across detach/attach cycles.
  • Compose Hot Reload: on reload-aware attaches, keys from tree / find are generation-stamped and cleared after a successful reload settle. Workflow:
    1. Start spectre wait --reload-settled <session-id> (or MCP wait_for_reload_settled) before triggering the reload (the wait must be armed to observe the settle chain)
    2. Trigger the reload; when wait returns, run tree / find and resolve keys from that response
    3. Never reuse pre-reload keys — they fail as nodeNotFound
    4. capture.json remains useful for evidence and jq, but its keys are not stamped for post-reload click dispatch on reload-aware sessions — prefer tree / find for input keys

Capture from MCP / CLI / library

SurfaceHow
CLIspectre capture <session-id> [--window N] [--out-dir DIR] [--json]
MCPcapture tool (session_id, optional window_index, out_dir, include_image)
In-processComposeAutomator.capture(windowIndex) → files via your own writer, or agent AttachedAutomator.capture

Prefer files over inlining the tree. Return summary fields to the agent; read capture.json on demand.

Cleanup guidance

  • Default root is lazy-capped (keep last 50 closed captures). Live sessions are never auto-pruned.
  • Explicit --out-dir captures are never auto-deleted; only captures prune --include-out-dir.
  • Prefer session-scoped prune from the detach report over blanket --all.

Manual validation recipe

With only this skill + the Spectre CLI (no source reading):

  1. Start agent-test-fixture (or any tagged Compose Desktop fixture).
  2. spectre ps --jsonspectre attach <pid> --json.
  3. spectre capture <session-id> --json → open capture.json with jq.
  4. Resolve a clickable node by testTag or text; spectre click <session-id> <key>.
  5. Capture again; confirm the expected text/tag change with jq or diff.
  6. spectre detach <session-id>; run the printed prune command.

Related

Signals

GitHub stars
35
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
spectre-capture
Source
github.com/rock3r/spectre