@neonwatty/limner
v0.1.32
Published
Agent-guided visual fidelity workbench for turning images into HTML references and comparing references to real apps.
Maintainers
Readme
Limner
Limner is an agent-guided visual fidelity workbench. It helps a coding agent turn an input image into an approved HTML mockup, then compare that approved mockup against a real implementation.
Limner is model-agnostic. It does not call a vision model and it does not make final pass/fail judgments. It prepares artifacts and schemas for agent-authored UX comparison scores, diffs, and next-iteration guidance.
The older visual spec workflow is archived in docs/archive/visual-spec-workflow.md; active loops use DB-native agent comparison responses.
Install
Use the published CLI:
npm install -g @neonwatty/limner
limner --help
npx @neonwatty/limner --helpInstall from source for development:
npm install
npm run buildRun from source during development:
npm run dev -- --help
npm --silent run dev -- ledger list --active --format jsonUse npm --silent run dev -- ... for agent-ingestible JSON so npm does not prepend lifecycle output.
Before PRs, run npm run check; it covers ESLint, TypeScript, Vitest, build, Knip, and the tracked-file line-count guard.
Polish Demo Fixture
Use the Swissifier fixture to load a synthetic local trajectory with score progression, screenshots, and mixed proof states. The launcher tour maps viewer controls to CLI commands and agent workflows; it is demo data, not proof against a real application. See docs/polish-demo-fixtures.md for details.
npm run dev -- polish view --port 53456
npm --silent run dev -- polish demo seed --fixture swissifier --reset --format json
npm run dev -- polish view --trajectory <trajectory-id> --port 53456
npm --silent run dev -- polish demo remove --fixture swissifier --yes --format jsonCodex Polish Plugin
Fetch the Codex plugin source from GitHub at plugins/limner-polish/; it is intentionally not bundled in the npm package. It provides limner-image-mockup-loop, limner-mockup-implementation-loop, and limner-image-implementation-loop.
The plugin does not replace Limner's CLI or ledger. Its skills and scripts help agents run the canonical loop, preserve LIMNER_LEDGER_HOME, keep comparison work subagent-friendly, and pass Ralph Loop iteration count N through limner loop start --max-iterations <N>.
Generate an agent-ingestible command envelope with:
node plugins/limner-polish/scripts/loop-command-envelope.mjs --mode image-mockup --target replay-boundaries --name replay-boundaries-polish --iterations 5
node plugins/limner-polish/scripts/loop-command-envelope.mjs --mode mockup-implementation --target replay-boundaries --iterations 5 --url http://localhost:3152/replay-boundariesUse node plugins/limner-polish/scripts/response-skeleton.mjs --mode <mode> for a schema-shaped starter response and node plugins/limner-polish/scripts/validate-plugin-surface.mjs for a fast plugin surface check.
Install or refresh the plugin globally for Codex with limner plugin install from the published CLI, or npm run plugin:install from a source checkout.
Basic Workflow
limner init ./ideal.png --target replay-boundaries
limner preview --target replay-boundaries
limner preview status
limner capture reference --target replay-boundaries
limner loop start --mode image-mockup --target replay-boundaries --name replay-boundaries-polish --max-iterations 5
limner loop compare --trajectory traj_... --format json
limner loop response submit --trajectory traj_... --from-run run_... --file ./response.json --format json
limner loop task --trajectory traj_... --executor subagent --format json
limner loop action start --trajectory traj_... --from-run run_... --kind polish --executor subagent --summary "Resize dashboard preview" --format json
limner loop action complete --trajectory traj_... --action act_... --executor subagent --summary "Adjusted preview sizing" --files "src/app/page.tsx"
limner loop status --trajectory traj_...
limner compare image-reference --target replay-boundaries
limner compare reference-implementation --target replay-boundaries --url http://localhost:3152/internal/optimization-lab/wedding-envelope#replay-boundaries --storage-state ./e2e/.auth/user.json
limner report --target replay-boundaries
limner ledger export traj_... --format markdown
limner runs listPreview note: limner preview --target <target> is the static reference server with /__limner status; limner variants view --concept <concept> is the graphical variants workbench with lineage nodes and selected next bases.
Asset Provenance
limner assets add ./ideal.png --source-label imagegen_original --concept replay-boundaries --format json
limner assets reconcile ./candidate.png --format json
limner assets contact-sheet --run-dir ./.limner/runs/<run-id> --format jsonUse source labels imagegen_original, downloaded_copy, derived_edit, html_frame, or reference_screenshot. Loop runs auto-register known visual inputs by default; add --require-registered-assets to limner loop start or limner loop compare for strict workflows. Use assets reconcile before trusting ad hoc images, and use assets contact-sheet to write .limner/runs/<run-id>/assets/contact-sheet.png with asset ID, source label, dimensions, and status. Agent final answers should not claim visual proof from stale artifacts or unregistered image sources.
Availability Diagnostics
Use limner doctor --target <target> --mode <mode> --url <implementation-url> --json, limner target list --json, and limner target status --target <target> --json before claiming proof is blocked. Agents must report cli, target, reference, implementation, auth, and comparison separately. Say "Limner unavailable" only when command -v limner or limner --version fails; otherwise report the exact LIMNER_* blocking reason code.
Workspace Shape
limner-workspace/
limner.config.ts
targets/
replay-boundaries/
source/ideal.png
contract/regions.json
contract/tokens.json
contract/acceptance.md
reference/index.html
reference/styles.css
AGENT_GUIDE.md
.limner/runs/Modes
Loop Ledger
Use limner loop for Ralph Loop-style polishing. Loop work is tracked in a global local-only SQLite ledger at ~/.limner/ledger.sqlite; no telemetry is sent.
limner loop start --mode image-mockup --target replay-boundaries --name replay-boundaries-polish --max-iterations 5
limner loop compare --trajectory traj_... --format json
limner loop task --trajectory traj_... --executor subagent --format json
limner loop action start --trajectory traj_... --from-run run_... --kind polish --executor subagent --summary "Resize dashboard preview" --format json
limner loop action complete --trajectory traj_... --action act_... --executor subagent --summary "Adjusted preview sizing" --files "src/app/page.tsx" --format json
limner loop action skip --trajectory traj_... --from-run run_... --summary "Comparison smoke only; no edit intended" --format json
limner loop response submit --trajectory traj_... --from-run run_... --file ./response.json --format json
limner loop status --trajectory traj_... --feedback "Prompt should mention button contrast."
limner loop next --trajectory traj_...
limner loop close --trajectory traj_...Loop modes:
image-mockup: source image to editable HTML mockup.mockup-implementation: approved mockup to real implementation.image-implementation: source image directly to real implementation. Every meaningful loop interaction writes a ledger event. Agent responses are submitted into local SQLite with the full JSON body, response hash, validation status, and freshness (missing,fresh,stale, orinvalid). When a response validates, Limner rewrites the same run report at.limner/runs/<run-id>/reports/comparison.mdwith the validated score, top fix, and comparison artifacts. Written artifacts that exist on disk are copied into local immutable snapshots under~/.limner/snapshotsand exposed assnapshotPathin ledger exports.agentFeedbackis an optional 255-character field for short process-improvement notes; longer comments belong in notes or project artifacts.
Current comparison artifacts are Limner-owned run state under .limner/runs/<run-id>/. Use the run ID printed by loop compare, or returned by loop compare --format json, to submit responses and inspect the matching screenshots, report, prompt, schema, and examples. Loop run manifests use the loop mode as the primary mode (image-mockup, mockup-implementation, or image-implementation) and keep the legacy comparison command name in comparisonMode when they differ. Legacy targets/<target>/captures/** and targets/<target>/reports/** paths may appear in old ledgers, but new compare runs write current artifacts under the run directory.
When reference and implementation data intentionally differ, add --comparison-note "<fixture caveat>" to loop start or loop compare; Limner includes it in agent prompts and preserves it in task rerun commands. Use --state-policy fixture-expected when populated fixture data is expected, --state-policy current-rendered when the agent should assess whatever the app currently renders, or --state-policy empty-ok when empty/sparse data is acceptable. For hydrated apps, add --wait-for-selector, --wait-for-text, and optional --wait-timeout-ms so implementation screenshots wait for the intended app-ready state. Capture commands hide known local dev overlays by default before screenshots and write *.capture.json beside screenshots with final URL, readiness milestones, hidden selectors, visible text inventory, and region evidence hints. Pass --show-capture-overlays only when those overlays are intentionally part of the comparison.
Region contracts require id, label, and referenceSelector; add appSelector when comparing against an implementation. Example: { "id": "event-list", "label": "Event List", "referenceSelector": "[data-limner-region='event-list']", "appSelector": "[data-testid='event-list']", "checks": ["bounds", "visible", "text"] }. Contracted regions are reported as captured, absent, hidden, zero-size, or offscreen in reports and agent prompts.
Action Logging
After loop compare creates a comparison prompt, have the agent submit structured JSON with limner loop response submit --trajectory traj_... --from-run run_... --file ./response.json --format json. Each generated prompt pack includes agent-response.minimal.json as a schema-valid starter for simple diffs. Once the response validates, use limner loop task --trajectory traj_... --executor subagent to print the next edit brief. The intended loop is:
loop compare -> loop response submit -> loop task --executor subagent -> loop action start -> edit -> loop action complete -> loop compareThe task brief names likely files, selectors, prioritized diffs, next steps, action logging commands, and the compare command to rerun. JSON task output includes taskFreshness, ledgerContext, and a commandEnvelope with LIMNER_LEDGER_HOME-prefixed commands that another agent can run without guessing which local ledger is active. When newer compare runs are unresolved, loop task blocks by default with status: "stale" and no action commands. Submit the newer response first, or intentionally use older guidance with limner loop task --trajectory traj_... --executor subagent --allow-stale --format json. Record loop action start before edits and loop action complete after edits; --from-run must reference an existing run for that trajectory, complete must reference an existing started action, and --summary must stay under 255 characters. Completed, failed, or skipped actions cannot be completed again. Use loop action skip for comparison-only smoke runs where no edit is intended. Limner records executor intent and action claims; it cannot prove an external orchestrator actually used a subagent. --executor codex is accepted as an alias for the generic agent executor. Use --format json when another tool needs machine-readable handoff data.
Use limner ledger to query and export trajectories:
limner ledger list --active
limner ledger list --active --format json
limner ledger current --target homepage-desktop --mode image-mockup --format json
limner ledger status --trajectory traj_...
limner ledger status --trajectory traj_... --format json
limner ledger show traj_...
limner ledger next --trajectory traj_...
limner ledger next --trajectory traj_... --format json
limner ledger export traj_... --format json
limner ledger export traj_... --format markdown
limner ledger delete trajectory --trajectory traj_... --dry-run --format json
limner ledger delete trajectory --trajectory traj_... --yes --backup ./limner-backup.json
limner ledger delete run --trajectory traj_... --run 2026-... --dry-run
limner ledger delete response --response resp_... --dry-run
limner workspace clean-artifacts --target homepage-desktop --legacy --dry-run
limner workspace clean-artifacts --target homepage-desktop --legacy --yesLedger JSON outputs include ledgerContext with the resolved local ledger home, SQLite database path, snapshots directory, and the exact LIMNER_LEDGER_HOME value to reuse in another session.
Ledger deletes are dry-run by default. Pass --yes to actually delete matching local SQLite rows. Use --backup <path> with trajectory deletion to write the trajectory export before hard deletion.
Workspace artifact cleanup is also dry-run by default. --legacy removes only target-scoped captures/ and reports/ roots; current run artifacts under .limner/runs/<run-id>/ are preserved.
Agents can discover the current CLI surface with:
limner --help
limner loop --help
limner loop task --help
limner ledger --help
limner runs list --format json
limner runs show <run-id> --format json
limner workspace --helpImage To Reference
Use this while recreating the ideal image as a standalone HTML/CSS mockup. This mode uses the ideal-to-mockup agent comparison profile.
limner compare image-reference --target replay-boundariesOutputs:
.limner/runs/<run-id>/captures/reference.png.limner/runs/<run-id>/captures/side-by-side.png.limner/runs/<run-id>/agent-comparison/agent-prompt.codex.md.limner/runs/<run-id>/agent-comparison/agent-response.schema.json.limner/runs/<run-id>/reports/comparison.md
In a loop trajectory, submit the agent's structured response with limner loop response submit --trajectory <trajectory-id> --from-run <run-id> --file ./response.json. Limner validates and emits:
.limner/runs/<run-id>/captures/image-comparison.json.limner/runs/<run-id>/captures/comparison-summary.json.limner/runs/<run-id>/reports/comparison.mdrefreshed with the validated result
Agents should inspect the ideal image and reference screenshot separately. The side-by-side image is comparison context, not the source image to parse.
Reference To Implementation
Use this after the HTML mockup is approved and the real implementation needs to match it. This mode uses the mockup-to-implementation agent comparison profile.
limner compare reference-implementation --target replay-boundaries --url http://localhost:3152/... --storage-state ./e2e/.auth/user.jsonOutputs:
.limner/runs/<run-id>/captures/reference.png.limner/runs/<run-id>/captures/implementation.png.limner/runs/<run-id>/captures/side-by-side.png.limner/runs/<run-id>/captures/dom-metrics.json.limner/runs/<run-id>/agent-comparison/agent-prompt.codex.md.limner/runs/<run-id>/agent-comparison/agent-response.schema.json.limner/runs/<run-id>/reports/comparison.md
In a loop trajectory, submit the agent's structured response with limner loop response submit --trajectory <trajectory-id> --from-run <run-id> --file ./response.json. Limner validates and emits:
.limner/runs/<run-id>/captures/image-comparison.json.limner/runs/<run-id>/captures/structure-comparison.json.limner/runs/<run-id>/captures/comparison-summary.json.limner/runs/<run-id>/reports/comparison.mdrefreshed with the validated result
Capture commands default to viewport-only screenshots. Add --full-page when the full scrollable page is the comparison target.
limner init writes .limner-scaffold.json in each target with template and content hashes for generated scaffold files. If every tracked scaffold file still matches its template, the state is reported as an untouched template scaffold.
Local Logs
Limner writes local structured logs only. There is no remote telemetry.
.limner/runs/<run-id>/
manifest.json
events.jsonl
agent-notes.md
captures/
reports/comparison.md
agent-comparison/