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 on | the developer's machine | your infrastructure |
| Transport | WebSocket | HTTP POST /api/requests |
| Working tree | edited in place → instant HMR | a runner branches paintless/<id> |
| Approval | you, in the browser, immediately | a queue + triage dashboard |
| Store | none (in-memory) | SQLite |
The engine underneath is the same. Start at Getting Started for dev, or Self-Hosting for prod.
