Paintless
Latest

Architecture

The one-core, one-pipeline design — how capture, routing, execution and delivery fit together.

Paintless is not an AI tool with some plumbing around it. It is a pipeline that captures context from the screen and routes it somewhere, where every stage is a replaceable adapter:

[Capture] ───▶ [Route] ───▶ [Execute?] ───▶ [Deliver]
 picker +        policy        agent           PR / issue /
 context         rules         (optional)      ticket / webhook

Running an agent is one optional stage. A "structured bug report tool" that auto-attaches a screenshot, selector, URL and component location is already useful on its own — and it is how a team adopts Paintless before committing to agents.

Four design principles

One Core, Two Shells

All the behaviour — the picker, context capture, the inspect panel, transports — lives in client-core. The widget (embedded in your app) and the browser extension (zero changes to the target project) are thin shells over that one core. A feature built once appears in both. eslint.config.mjs enforces this: shells may only assemble, never reimplement.

Protocol-First

Packages communicate only through types in @paintless/protocol. The schemas are Zod-first, so schema = type = validation — a single definition is the wire format, the TypeScript type, and the runtime validator. See Protocol Reference.

One Pipeline Engine

The pipeline package holds the route/execute/deliver engine, and both the CLI dev host and the self-hosted server reuse it. Dev and prod differ in how they take input and where they run, not in how a request flows.

Self-Host First

Production is self-hosted: your code and tokens never leave your infrastructure. docker compose up is the whole deployment.

The layers

protocol      types, Zod schemas, adapter interfaces  (depends on nothing)
   ▲
client-core   picker, capture, inspect panel, transports
pipeline      route → execute → deliver engine
   ▲
adapters      agents (claude-code, command) · destinations (github, linear, webhook)
mappers       element → source location (vite, webpack, next, vue)
   ▲
shells        widget · extension · cli · server   (assemble only)

Dependencies point upward only; the boundary is lint-enforced. A feature belongs in client-core or pipeline, never in a shell.

The one currency: ChangeRequest

Every path produces the same object. A ChangeRequest is created by a shell, evaluated by pipeline routes, and consumed by agents and destinations. The dev host and the server move the identical shape — which is why the same click works in a developer's editor and in a stakeholder's browser. Its fields are in Protocol Reference.

Dev vs prod, concretely

Dev host (cli)Server
Runs onthe developer's machineyour infrastructure
TransportWebSocketHTTP POST /api/requests
Working treeedited in place → instant HMRa runner branches paintless/<id>
Approvalyou, in the browser, immediatelya queue + triage dashboard
Storenone (in-memory)SQLite

The engine underneath is the same. Start at Getting Started for dev, or Self-Hosting for prod.

Edit this page

Last updated: