Components¶
The building blocks of krites and how they fit together.
The deterministic engine¶
krites' value is its engine, and it lives in the pkg/ tree as a deliberate
public Go API. The engine is pure Go and cgo-free, and compiles to
GOOS=js GOARCH=wasm today. It never imports a native backend directly; those
sit behind provider interfaces.
| Package | Role |
|---|---|
analyze/quality |
Sharpness and exposure maths |
analyze/dedup |
Perceptual hashing and clustering of near-duplicate bursts |
cull |
Verdict resolution from a cull profile |
develop (straighten, crop, look, bake) |
The geometry and colour edit records, and the pure-Go export bake |
shoot |
The workspace model: manifest, sidecar, verdicts, edit records, removal patches |
xmp |
The Lightroom XMP sidecar |
export |
The render — the only pixel-producing path |
Why the WASM constraint is load-bearing¶
Keeping the engine WASM-compilable is not about shipping krites to a browser tomorrow. It is a structural check that the judgement logic has no hidden dependencies.
The moment a verdict depends on a native library, the verdict becomes platform-dependent — the same shoot could cull differently on two machines, and the reason would be buried in a linker. Compiling to WebAssembly, where none of that is available, makes any such dependency a build failure rather than a mystery.
It is enforced as a build target in the project's own CI, so it cannot quietly rot.
Provider seams¶
Every heavy or native backend is injected behind a narrow interface, faked in tests, and selected from config. Each adapter is its own package, so native code never leaks into the WASM-compilable engine.
| Interface | Backend |
|---|---|
Decoder |
Image decode and image operations |
FaceAnalyzer |
Face and eye landmarks, expression and pose — local ONNX |
AestheticScorer |
Learned whole-frame aesthetic scoring — no backend ships yet |
Inpainter |
Generative object removal — LaMa or MI-GAN, local ONNX |
Clusterer |
Burst clustering |
Adding a backend is purely additive: a new adapter package behind the same
interface, with no call-site changes. face.detector selecting between two face
detectors, and remove.backend between two inpainters, are that seam showing in
configuration.
The seam is also what makes "off by default" a real state rather than a skipped call — with a provider disabled the native code is never loaded. See Why the heavy providers are opt-in.
The studio¶
krites studio is the primary surface: a local,
single-user, localhost-bound web UI — a Svelte single-page app embedded into
the binary — served over an HTTP and SSE API onto the same shoot files the CLI
uses. The cull-review grid stays responsive on 4,000-plus frames because previews
are cached and the grid is virtualised.
The studio adds no domain logic of its own. Every endpoint maps onto an engine operation, which is what keeps the CLI and the studio genuinely interchangeable mid-shoot rather than approximately so. Its API is documented in the studio API reference.
Because the SPA is embedded at build time, a binary built without the generate
step serves a placeholder — which is why go install produces a broken studio.
Built on go-tool-base¶
The root command, the dependency-injection container, the Cobra command tree, the config and keychain layers, the MCP server and the self-update machinery all come from go-tool-base. krites is a downstream consumer that leans on those primitives rather than re-implementing them.
That is why krites has commands it never wrote — init,
config, doctor,
update — and why they behave identically in
every phpboyscout tool. It is also why the four
global flags exist on every command
without krites declaring them.
Generated files carry a DO NOT EDIT banner; new commands are scaffolded rather
than hand-written, which is why every command has the same flag conventions and
the same --shoot semantics.