Skip to content

krites — interface contracts (CLI, studio, MCP)

Status: DRAFT / intent (companion to 0001-krites.md) Owner: Matt Cockayne Last updated: 2026-06-21

0. How to read this

This defines the interface contracts for krites' three surfaces — the CLI (low-level), the studio web UI (primary, §4), and MCP (§5): the commands, their inputs / outputs / side-effects, the studio's functionality + UX, and the MCP tool surface. It exists to anchor tests — the godog scenarios and unit tests of 0001-krites.md §8.

  • The CLI command contracts (§2–§3) are the single source of truth; the studio (§4) and MCP (§5) are alternative drivers of the same operations on the same shoot workspace (0001 §3).
  • These are guidance, not law. Shapes change as we build; when they do, update this doc and the tests together. The contract assertions under each command are the bits worth pinning.
  • Requirement IDs (R-AREA-n) give traceability: each should map to ≥1 unit test or .feature scenario. Stable handles, not a priority order.
  • Keywords: MUST = a contract a test enforces; SHOULD = expected but negotiable; MAY = optional.
  • Consistent with 0001-krites.md. Where they disagree, 0001 wins and this is stale.

1. CLI conventions (cross-cutting)

Apply to every command; per-command sections note only departures.

1.1 Shoot scope

  • R-SCOPE-1 (MUST) A command resolves the current shoot from --shoot <path>, else the nearest enclosing directory containing .krites/, else the configured default; with none resolvable it exits with a usage error and guidance, never guessing.
  • R-SCOPE-2 (MUST) The CLI scopes to one shoot; switching between many shoots is a studio capability (R-UI-2), not a CLI one.
  • R-SCOPE-3 (MUST) A command resolves all relative frame references against the shoot's originals/.

1.2 Global flags

Flag Meaning
--shoot, -s <path> operate on this shoot workspace
--profile <name> cull profile for this run; else the configured default (§0001 §6)
--look <name> look for develop/export; else the default
--json machine-readable output on stdout (GTB pkg/output)
--yes, -y assume yes; never prompt (non-interactive / scripted)
--dry-run validate + report intended actions; no writes, no spend, no pixels
--force bypass the analysis/preview cache; recompute
--config, --log-level, -v GTB-standard config + logging
  • R-GLOBAL-1 (MUST) Output is human-readable by default; --json emits a structured object, and on --json stdout is valid JSON only (logs → stderr).
  • R-GLOBAL-2 (MUST) --yes makes a command fully non-interactive; one that would need input and cannot proceed exits with a usage error, never hangs.
  • R-GLOBAL-3 (MUST) --dry-run performs no writes, no pixel rendering, and no network calls that spend credit, and reports what it would do.
  • R-GLOBAL-4 (MUST) Validation failures exit non-zero before any side effect; errors are reported via the GTB error handler (cockroachdb/errors) with a hint where one helps.
  • R-GLOBAL-5 (SHOULD) Long operations show progress on stderr (N of M frames); under --json, progress is suppressed or structured, never interleaved into the stdout JSON.
  • R-GLOBAL-6 (MUST) Re-running with unchanged inputs reuses cached analysis (same frame bytes + same profile ⇒ same verdict, no recompute); --force overrides.
  • R-GLOBAL-7 (MUST) Deterministic logic (sharpness, exposure, hashing, clustering, straighten angle, crop geometry, verdict resolution) is pure and reproducible — identical input ⇒ identical output, unit-tested directly.
  • R-GLOBAL-8 (MUST) Every provider call (decode, ML, inpaint) has a timeout; a hung backend fails the frame with a reason, never blocks the batch.
  • R-GLOBAL-9 (SHOULD) Batch operations are resumable — interrupting a cull of 4,000 frames and re-running continues from the cache, not from zero.

1.3 The non-destructive guarantee (the load-bearing contract)

  • R-ND-1 (MUST) No command ever modifies a file under originals/ after ingest. Tests assert original bytes/mtime are unchanged across cull / develop / retouch / remove.
  • R-ND-2 (MUST) Verdicts and edits are records (verdicts.yaml, edits/); the only command that writes new pixels is export (into export/), and a command that would (e.g. remove's preview) writes into the cache/export, never over an original.
  • R-ND-3 (MUST) Every verdict/edit is reversible: reset returns a frame to its post-ingest state; undo/redo is total in the studio (R-UI-9).
  • R-ND-4 (MUST) Deleting .krites/analysis/ or .krites/previews/ and re-running reproduces them with no loss of decisions (only caches are disposable).

1.4 Privacy

  • R-PRIV-1 (MUST) Every cloud-capable provider is off by default; using one requires explicit config opt-in.
  • R-PRIV-2 (MUST) When a command would send frame data off-machine, it says so (and the studio shows a persistent indicator, R-UI-19); --dry-run reports it without sending.
  • R-PRIV-3 (MUST) Secrets (cloud API keys) live in the GTB keychain / env, never in the shoot or the project config file.

2. Ingest, cull & verdicts

2.1 krites ingest <dir>

Registers a shoot workspace from a source folder.

  • R-ING-1 (MUST) Creates <shoot>/.krites/ and shoot.yaml (id, name, date from EXIF, source, frame count); copies or links sources into originals/ per --copy (default) / --link.
  • R-ING-2 (MUST) Reads EXIF (camera, lens, capture time, orientation) and generates a preview per frame; RAW decodes via the Decoder provider.
  • R-ING-3 (MUST) Idempotent — re-ingesting the same source adds only new frames, never duplicates or disturbs existing verdicts/edits.
  • R-ING-4 (SHOULD) Reports a summary (N frames, formats, date range); --json returns the manifest.

2.2 krites cull [--profile <name>] [--reanalyze]

The judge. Runs analysis → verdicts.

  • R-CULL-1 (MUST) For each frame, computes the profile's enabled signals (sharpness, exposure, eye/blink, dedup cluster; +expression/aesthetic when enabled) and caches the raw analysis (.krites/analysis/<frame>.json).
  • R-CULL-2 (MUST) Resolves a verdict (keep / maybe / reject) from the signals via the named cull profile (0001 §6) — hard gates → reject, the best-of-cluster → keep, others → maybe — and records human-readable reasons per frame in verdicts.yaml.
  • R-CULL-3 (MUST) Never deletes or moves a frame; a reject verdict is a label, not a file operation (R-ND-1).
  • R-CULL-4 (MUST) Verdict resolution is pure given (signals, profile) — unit-tested with golden fixtures (sharp / blurry / blink / burst / over- / under-exposed) independent of the ML backends.
  • R-CULL-5 (MUST) --reanalyze recomputes signals; without it, cached analysis is reused (R-GLOBAL-6), only the verdict is re-resolved if the profile changed.
  • R-CULL-6 (SHOULD) Reports counts per verdict and per dominant reason; --json returns the full verdict set.

2.3 krites verdict <frame> keep|maybe|reject / krites reset <frame>

  • R-VRD-1 (MUST) Overrides krites' verdict for a frame and records that it is a human override (so the Phase 4 learning loop, 0001 §11, can weight it).
  • R-VRD-2 (MUST) reset <frame> clears overrides + edits, returning the frame to post-ingest state (R-ND-3).

2.4 krites dedup [--profile <name>]

  • R-DUP-1 (MUST) Clusters near-duplicate / burst frames (perceptual hash + time proximity) and ranks within each cluster; records the cluster id and the proposed best on each member.
  • R-DUP-2 (MUST) Clustering + ranking are deterministic and unit-tested on a fixture burst.
  • R-DUP-3 (SHOULD) Re-running after a verdict override keeps the human's pick as the cluster's chosen frame.

3. Develop, retouch, remove & export

3.1 krites straighten / krites crop

  • R-DEV-1 (MUST) Each proposes an edit (a level angle; a crop rectangle) recorded on the frame's edit record — never auto-applied destructively; the studio previews it (R-UI-12).
  • R-DEV-2 (MUST) Straighten angle is clamped to a sane maximum and the geometry is pure/unit-tested; crop respects a target aspect and stays within the frame.
  • R-DEV-3 (SHOULD) --all batch-proposes across keepers; each remains individually overridable.

3.2 krites develop [--look <name>]

  • R-LOOK-1 (MUST) Applies a look (white balance + tone + grade/LUT) from the catalog as develop settings on the edit record (not baked pixels until export); --look selects, else the default.
  • R-LOOK-2 (MUST) Looks are config-driven (0001 §6) — no hardcoded grade/curve constants in Go.

3.3 krites retouch (Phase ≥2) / krites remove <frame> --region (Phase ≥3)

  • R-RT-1 (MUST) Retouch applies only the bounded effect set (0001 §4.4), each toggle-able and recorded as reversible settings.
  • R-RM-1 (MUST) remove records a mask + inpaint result reference on the edit record (non-destructive, R-ND-2); the inpaint runs through the Inpainter provider with a timeout (R-GLOBAL-8).
  • R-RM-2 (MUST) If the Inpainter is cloud-backed, R-PRIV-2 disclosure applies.

3.4 krites export

  • R-EXP-1 (MUST) Renders the selected verdict set (default keep) applying each frame's edits (straighten / crop / look / retouch / removals) into export/, to the chosen format/size/quality, carrying EXIF/copyright.
  • R-EXP-2 (MUST) It is the only pixel-producing command (R-ND-2); it never writes outside export/ and never alters originals.
  • R-EXP-3 (SHOULD) Re-export is idempotent for unchanged frames (skips or overwrites deterministically); --dry-run lists what it would render.

3.5 krites xmp write|read

  • R-XMP-1 (MUST, Phase 1) write emits XMP sidecars beside originals carrying the cull verdict → star rating + pick/reject flag + colour label — readable by Lightroom (0001 §4.7, §13-Q5). This is the core of the primary (~90%) finish-in-Lightroom workflow.
  • R-XMP-1b (MUST, Phase 2) Once develop exists, write also carries crop, straighten angle, and white balance into Camera-Raw XMP, so keepers open in Lightroom already leveled/cropped/WB-corrected. Looks / retouch / object- removal are never written to XMP (krites-export-only) — no half-applied Adobe develop mapping.
  • R-XMP-2 (MUST) read ingests existing XMP ratings/labels so krites and Lightroom round-trip without clobbering each other; round-trip is unit-tested.
  • R-XMP-3 (MUST) Writing XMP is opt-in and never touches the originals' pixels (it writes the .xmp companion only).

3.6 krites profile|look list|show|add|edit|rm

  • R-CAT-1 (MUST) Manage the cull-profile and look catalogs through the GTB config layer; init seeds wedding-default + a neutral look (0001 §6).
  • R-CAT-2 (MUST) Edits hot-reload into a running studio; a profile change re-resolves verdicts without re-analysis (R-CULL-5).

4. The studio (web UI) — krites studio [--port N]

The studio is krites' primary surface, and its full interface contract — functional requirements (R-UI-*), UX + mockup, and the HTTP + SSE API contract (R-API-*) — lives in its own spec: 0003-studio.md. That is the authoritative source; this section is a pointer.

In brief: a local, single-user, localhost-bound, desktop-first web app (GTB service lifecycle, go:embed-ed frontend) over the same shoot workspace the CLI uses — UI and CLI are interchangeable, the studio adds no domain logic of its own. The frontend is a Svelte SPA (a non-binding default — the API contract is framework-agnostic) talking to a thin Go JSON+SSE API, browser-served now and Wails-ready for a native macOS app later (0001 §13-Q2). See 0003 for the endpoint table and every requirement.

5. MCP surface

krites exposes its commands as MCP tools (GTB mcp feature) so an assistant can drive the read/analysis loop — a third surface mirroring the CLI.

  • R-MCP-1 (MUST) Read/analysis commands (ingest, cull, dedup, verdict, straighten, crop, develop, profile/look list|show) are exposed as MCP tools mirroring their CLI contracts.
  • R-MCP-2 (MUST) Pixel-producing / destructive-feeling commands are gated off the MCP surface via setup.ExcludeFromMCP — at minimum export, remove, and xmp write (anything that writes pixels/sidecars beside the user's files). The exclusion is build-time only (no runtime lever); toggle with gtb enable|disable mcp <command> + rebuild, never by hand-editing the generated cmd.go.
  • R-MCP-3 (MUST) MCP tools honour the same non-destructive (R-ND-*) and privacy (R-PRIV-*) contracts as the CLI. ```