files in · plain English · files out
patchling is a small library that turns a plain-English sentence into an applied, multi-file change. Hand it your files, get the changed files back — code, artwork, game state, whatever your app keeps in text. No agent, no server, no filesystem: one function call, and control returns to your code.
npm install patchling
pypip install patchling
Zero-dependency ESM · runs in the browser and Node 18+ · Python too.
◐ working…
Demo mode replays a pre-recorded reply, so the files and goal were reset to match. Use your own key below to transform your own edits.
npm install patchling puts it in your app, or run it live with your own words.
· top = each lab's latest flagship · price = $ in / $ out per 1M · Enter uses exact text
Sign-in is OAuth (PKCE) straight from this page to NanoGPT: the token is issued to your browser, lives in localStorage, and every request goes browser → nano-gpt.com directly. There's no patchling server to trust because there is no patchling server. Prefer a key? nano-gpt.com → Settings → API — pay-per-use, no subscription. NanoGPT links on this page carry our referral code. OAuth needs the page served over http(s).
Built with it
Everything below is the same three calls pointed at different state. None of these are coding tools — they're products with a text box.
A visual AI workflow editor. Describe the change; the graph rewires.
workflow state → English-editableSVG layers + a GSAP timeline. One sentence restyles the whole broadcast; exports for OBS.
layers/*.svg · overlay.js · config.json“Turn it into a glowing crystal” rewrites geometry, material and texture; exports .glb.
object.js · material.js · texture.svg“Raise the stakes” rewrites the art direction and adds panels; the page redraws itself.
config.json · panels/*.jsonA character you direct. Its soul, mood and memory are plain files you own — it can't be deprecated.
soul.md · mood.md · memory.mdHow it works
Your app's state is the input. patchling transforms the map you hand it and hands back a new one — what you do with the result is your code's business.
buildEnvironment(files)Hand it your files as a plain { path: content } object — it becomes the project the model sees. No disk access; runs in the browser and Node 18+.
generateDiff(env, goal)One LLM call returns a unified git diff — minimal, reviewable, streamable. Stream it with onToken, meter it with onUsage, or inject your own client with callLlm.
smartapply(diff, files)Exact context-matched patching first; an LLM rewrite for only the files that fail. Returns a new map — originals are never mutated, and failures never half-apply.
import { buildEnvironment, generateDiff, smartapply } from "patchling"; const files = { "scene.svg": svg, "style.css": css }; // any { path: content } map const diff = await generateDiff(buildEnvironment(files), "Make it a night launch", { apiKey, model }); const next = await smartapply(diff, files); // a new map — files is untouched render(next); // your code decides what "apply" means
A function call, not an agent. patchling never touches disk, never loops, never decides what to do next — one call in, one new file map out, and control returns to your code. That's what makes it embeddable: the AI-edit feature ships in your UI, on your storage, under your rules. Mock it (callLlm), meter it (onUsage), stream it (onToken), and diff its output byte-for-byte before you accept it.
Trust it the way you'd trust any function: verify the output. The count-to-100 demo wraps patchling in the dumbest possible loop — add 1, apply, assert n+1, a hundred times — and shows exactly what every verified step costs. That's the intended shape: patchling is the transformation inside your loop, not a loop of its own.
Why it doesn't flake
Why a diff, and not “just rewrite the file”? A diff is the safest thing an LLM can say. It's minimal — the model writes what changed, not a fresh copy of your state to subtly corrupt. It's reviewable — show it to your user before you apply it. It streams, it's cheap, and untouched files cost nothing.
The catch: LLM diffs are sloppy. Drifted line numbers, context that doesn't quite match, malformed headers. git apply rejects them. smartapply heals them instead — deterministic patching when the hunks match, a scoped per-file LLM repair only when they don't. The sloppiness becomes patchling's problem instead of yours.
@@ line numbers--/++ headers*** Begin Patch style<think> & reasoning preamblesNo lock-in
patchling speaks the OpenAI-compatible chat-completions protocol. Point baseUrl at any provider — or hand it your own client and it never opens a socket at all.
Why NanoGPT is the default: it's the only setup that works everywhere patchling runs. One pay-per-use key covers 600+ models, and OAuth (PKCE) works from a static page — the token is issued to your visitor's browser, so you can ship an AI-edit feature with no backend at all. That's how this page and every demo run. (NanoGPT links here carry our referral code.)
Already have a provider? baseUrl takes any OpenAI-compatible endpoint — OpenAI, OpenRouter, Groq, a vLLM box in your rack, Ollama on localhost. api.anthropic.com is spoken natively. In Node and Python, GPTDIFF_LLM_API_KEY and GPTDIFF_LLM_BASE_URL configure it without touching code.
And the escape hatch is total. Pass callLlm (and callLlmForApply) and patchling makes no network calls of its own — your gateway, your retries, your logging, your mocks in CI. The demo above runs on exactly this: its "model" is a canned reply.
// default — NanoGPT: one key, 600+ models await generateDiff(env, goal, { apiKey }); // any OpenAI-compatible endpoint await generateDiff(env, goal, { apiKey, model: "gpt-5.5", baseUrl: "https://api.openai.com/v1", }); // local & private — Ollama, LM Studio, vLLM… await generateDiff(env, goal, { model: "qwen3:32b", baseUrl: "http://localhost:11434/v1", }); // yours entirely — patchling makes no network calls await generateDiff(env, goal, { callLlm: myClient });
Get it
Zero-dependency ESM. Import from npm or straight off a CDN — this page does.
npm install patchling
# or, in the browser, no build step:
import { generateDiff, smartapply }
from "https://esm.sh/patchling";