files in · plain English · files out

Give your app a “describe the change” box.

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.

jsnpm install patchling pypip install patchling
GitHub

Zero-dependency ESM · runs in the browser and Node 18+ · Python too.

live demo — the real library, running in this page

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.

the diff the model wrote

      
That was the real engine — streamed diff, deterministic patch, scoped LLM repair. npm install patchling puts it in your app, or run it live with your own words.
View & edit the files behind this demo
Run it for real — your own NanoGPT key
or
model

· 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

The files don't have to be code.

Everything below is the same three calls pointed at different state. None of these are coding tools — they're products with a text box.

How it works

Three calls. No filesystem. No agent loop.

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.

1

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+.

2

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.

3

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.

the whole integration
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 diffs — and why yours will apply.

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.

smartapply survives:

drifted @@ line numbers
context that doesn't quite match
malformed --/++ headers
headerless diffs
*** Begin Patch style
new files & deletions
multi-file diffs, applied concurrently
<think> & reasoning preambles

No lock-in

Default: NanoGPT. Required: nothing.

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.

bring-your-own-model.js
// 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

One primitive, two runtimes.

JavaScript

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";

Python

The same bounded primitive, ported to Python.

pip install patchling

from patchling import (
    generate_diff, smartapply)