{"_id":"mocktown","_rev":"6-97d92ac6e0e30ec9be9224a702127719","name":"mocktown","dist-tags":{"latest":"0.4.1"},"versions":{"0.0.1":{"name":"mocktown","version":"0.0.1","author":"","license":"MIT","_id":"mocktown@0.0.1","maintainers":[{"name":"kurtgokhan","email":"krtgkn@gmail.com"}],"dist":{"shasum":"941a1e6cd034ebf8a8800a9c41291578247aa0dc","tarball":"https://registry.npmjs.org/mocktown/-/mocktown-0.0.1.tgz","fileCount":2,"integrity":"sha512-IAsjcNomIrR8aQv225pBujZtm866DylCfjmaGVE50nlXqieKGYBb4iHctYiCUZwdUVhPsWLs555uULTQcwWjlA==","signatures":[{"sig":"MEUCIBMbzq89ML61cde6W3YPooCAvC4A0UHM6qTfIMA1ar8rAiEAy90BVhjQEFeOTmbMj9Wz/OcpCHiHUCfl9jEIOaCfEMs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":346},"main":"index.js","type":"commonjs","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"_npmUser":{"name":"kurtgokhan","email":"krtgkn@gmail.com"},"_npmVersion":"11.19.0","description":"Coming soon","directories":{},"_nodeVersion":"24.20.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/mocktown_0.0.1_1788177212214_0.986851898096198","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"mocktown","version":"0.1.0","keywords":["mocking","http","proxy","testing","sandbox","agents","mcp","bun"],"author":{"url":"https://github.com/gkurt","name":"Gokhan Kurt"},"license":"MIT","_id":"mocktown@0.1.0","maintainers":[{"name":"kurtgokhan","email":"krtgkn@gmail.com"}],"homepage":"https://github.com/gkurt/mocktown","bugs":{"url":"https://github.com/gkurt/mocktown/issues"},"bin":{"mocktown":"src/cli/index.ts"},"dist":{"shasum":"b1378e69e2df7564cac13a79ed0ad70ce01324d6","tarball":"https://registry.npmjs.org/mocktown/-/mocktown-0.1.0.tgz","fileCount":89,"integrity":"sha512-icuYc+g/JzgodyZawsanQ8QteXyQ0bV4yrd4PYE8DWAO44QJdjmmzwjQjmL+/AFjWVRq+FP5ShAHk5Qy+UXhIQ==","signatures":[{"sig":"MEQCIGbL+NlaIEIQHcRdAgB6Frd45b5wK6xPQ4qdCCacKHKeAiBPmul6j8bBCrSmmNTqnAGDhYVZysjva3Wt4qccTj2G5Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQClNIyWiM94Nrxu1UGdHWHRTINDkETZX6PsHfOQVK1uqwIhAJb94X1nX/urae4tnzjdtGv4+YocLbHwAuDVX6fSvj9h","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/mocktown@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1407973},"type":"module","engines":{"bun":">=1.4.0"},"exports":{".":"./src/index.ts","./mock":"./src/mocks/types.ts","./contract":"./src/contract/index.ts"},"gitHead":"ea35f0ea6587eefa39eeb47646b4a2bb7b91acec","imports":{"#*":"./*"},"scripts":{"test":"bun test","daemon":"bun src/daemon/index.ts","prepack":"bun ../../scripts/prepack.mts","typecheck":"tsc","db:generate":"drizzle-kit generate"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2f3f86ca-4407-403b-a051-f1156fdd28db"}},"repository":{"url":"git+https://github.com/gkurt/mocktown.git","type":"git","directory":"packages/mocktown"},"_npmVersion":"12.0.2","description":"Record an app's outbound traffic, serve it back as stateful mocks, and keep them alive as the real services change.","directories":{},"_nodeVersion":"24.20.0","dependencies":{"zod":"4.5.4","mockttp":"4.6.1","@orpc/zod":"1.15.0","commander":"15.0.0","drizzle-orm":"0.45.2","drizzle-zod":"0.8.3","@orpc/client":"1.15.0","@orpc/server":"1.15.0","@orpc/openapi":"1.15.0","@orpc/contract":"1.15.0","@orpc/openapi-client":"1.15.0","@modelcontextprotocol/sdk":"1.30.0"},"_hasShrinkwrap":false,"devDependencies":{"@types/bun":"^1.4.0","@types/node":"^24.0.0","drizzle-kit":"0.31.10"},"optionalDependencies":{"@libsql/client":"0.17.4"},"_npmOperationalInternal":{"tmp":"tmp/mocktown_0.1.0_1788965099010_0.08872119188774885","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"mocktown","version":"0.2.0","keywords":["mocking","http","proxy","testing","sandbox","agents","mcp","bun"],"author":{"url":"https://github.com/gkurt","name":"Gokhan Kurt"},"license":"MIT","_id":"mocktown@0.2.0","maintainers":[{"name":"kurtgokhan","email":"krtgkn@gmail.com"}],"homepage":"https://github.com/gkurt/mocktown","bugs":{"url":"https://github.com/gkurt/mocktown/issues"},"bin":{"mocktown":"src/cli/index.ts"},"dist":{"shasum":"6b206f9991b3caf3a2bb1a37892862af5249b002","tarball":"https://registry.npmjs.org/mocktown/-/mocktown-0.2.0.tgz","fileCount":90,"integrity":"sha512-GHsmJqdA9y6DJHWGnL3QIAjEGG/PZoEEeRplNJuVpSqK0rZptn7QrwQbufTZWLQMjj0fhuPJEF4A/WM+eCeQZg==","signatures":[{"sig":"MEYCIQDYsawwT9fJmkgz2WM9ArfGLBT8prYO/wfmxWtww8HhPQIhAMPb9sorb1ogS6qAJWv+Z1sV84T6XMPILSnDGUTRzr/V","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQCSHHpzGVlqSwQ+jE8OAad6fl22+uKvdwpzzszmMH98BwIgRS1xPmXpcG+7gSM6RCw+iwdPpZmzJEeO5Ks4H8QZ5aA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/mocktown@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1736300},"type":"module","engines":{"bun":">=1.4.0"},"exports":{".":"./src/index.ts","./mock":"./src/mocks/types.ts","./contract":"./src/contract/index.ts"},"gitHead":"16779654c439abea180b05c37e7886c50b2de3b0","imports":{"#*":"./*"},"scripts":{"test":"bun test","daemon":"bun src/daemon/index.ts","prepack":"bun ../../scripts/prepack.mts","typecheck":"tsc","db:generate":"drizzle-kit generate"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2f3f86ca-4407-403b-a051-f1156fdd28db"}},"repository":{"url":"git+https://github.com/gkurt/mocktown.git","type":"git","directory":"packages/mocktown"},"_npmVersion":"12.0.2","description":"Record an app's outbound traffic, serve it back as stateful mocks, and keep them alive as the real services change.","directories":{},"_nodeVersion":"24.20.0","dependencies":{"zod":"4.5.4","mockttp":"4.6.1","@orpc/zod":"1.15.0","commander":"15.0.0","drizzle-orm":"0.45.2","drizzle-zod":"0.8.3","@orpc/client":"1.15.0","@orpc/server":"1.15.0","@orpc/openapi":"1.15.0","@orpc/contract":"1.15.0","@orpc/openapi-client":"1.15.0","@modelcontextprotocol/sdk":"1.30.0"},"_hasShrinkwrap":false,"devDependencies":{"@types/bun":"^1.4.0","@types/node":"^24.0.0","drizzle-kit":"0.31.10"},"optionalDependencies":{"@libsql/client":"0.17.4"},"_npmOperationalInternal":{"tmp":"tmp/mocktown_0.2.0_1788977431871_0.8084075366053038","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"mocktown","version":"0.3.0","keywords":["mocking","http","proxy","testing","sandbox","agents","mcp","bun"],"author":{"url":"https://github.com/gkurt","name":"Gokhan Kurt"},"license":"MIT","_id":"mocktown@0.3.0","maintainers":[{"name":"kurtgokhan","email":"krtgkn@gmail.com"}],"homepage":"https://github.com/gkurt/mocktown","bugs":{"url":"https://github.com/gkurt/mocktown/issues"},"bin":{"mocktown":"src/cli/index.ts"},"dist":{"shasum":"1dec6d42952c4e7cf54926c030547dcbc22b49fc","tarball":"https://registry.npmjs.org/mocktown/-/mocktown-0.3.0.tgz","fileCount":91,"integrity":"sha512-9MMUkfIemQmjVy4VMd/9asfYLysK8XM0u4dcrqKStYxKziwrmC4z/V9RuMcOY3d1jzjwnx4NKGtDOFH2M24PtA==","signatures":[{"sig":"MEUCIQCie4efSuXZQ0zKUR5XostPqcz/3XqJ19jsxaNfQmHQRQIgJ1PAoSl7GLQs/ilEPrs4PBT24ZF78tTGPcKKEHoMmWA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIFH6BIZniu63lDgPb1y/hNrwr5h9wh/20uGaXa+R35aKAiEAn39X3HJCSgJMILg6L/UL9Hf5lZG3nFA561DrUeGcsoY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/mocktown@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1755984},"type":"module","engines":{"bun":">=1.4.0"},"exports":{".":"./src/index.ts","./mock":"./src/mocks/types.ts","./contract":"./src/contract/index.ts"},"gitHead":"f0cb8bacae2f0b047fea231f214b538a1460af73","imports":{"#*":"./*"},"scripts":{"test":"bun test","daemon":"bun src/daemon/index.ts","prepack":"bun ../../scripts/prepack.mts","typecheck":"tsc","db:generate":"drizzle-kit generate"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2f3f86ca-4407-403b-a051-f1156fdd28db"}},"repository":{"url":"git+https://github.com/gkurt/mocktown.git","type":"git","directory":"packages/mocktown"},"_npmVersion":"12.0.2","description":"Record an app's outbound traffic, serve it back as stateful mocks, and keep them alive as the real services change.","directories":{},"_nodeVersion":"24.20.0","dependencies":{"zod":"4.5.4","mockttp":"4.6.1","@orpc/zod":"1.15.0","commander":"15.0.0","drizzle-orm":"0.45.2","drizzle-zod":"0.8.3","@orpc/client":"1.15.0","@orpc/server":"1.15.0","@orpc/openapi":"1.15.0","@orpc/contract":"1.15.0","@orpc/openapi-client":"1.15.0","@modelcontextprotocol/sdk":"1.30.0"},"_hasShrinkwrap":false,"devDependencies":{"@types/bun":"^1.4.0","@types/node":"^24.0.0","drizzle-kit":"0.31.10"},"optionalDependencies":{"@libsql/client":"0.17.4"},"_npmOperationalInternal":{"tmp":"tmp/mocktown_0.3.0_1789231148150_0.7322840655449157","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"mocktown","version":"0.4.0","keywords":["mocking","http","proxy","testing","sandbox","agents","mcp","bun"],"author":{"url":"https://github.com/gkurt","name":"Gokhan Kurt"},"license":"MIT","_id":"mocktown@0.4.0","maintainers":[{"name":"kurtgokhan","email":"krtgkn@gmail.com"}],"homepage":"https://github.com/gkurt/mocktown","bugs":{"url":"https://github.com/gkurt/mocktown/issues"},"bin":{"mocktown":"src/cli/index.ts"},"dist":{"shasum":"ff1bd0a91ec625e28fa0908848c338d208fa9b0f","tarball":"https://registry.npmjs.org/mocktown/-/mocktown-0.4.0.tgz","fileCount":91,"integrity":"sha512-zELPdilv0Ngbg1N1v+wjS7aZF3fVxC91QSOD9iPpmSVb6fXer5F8q2+h9wtHD29l3/nB6MNJqqwOA0vfzB2CzA==","signatures":[{"sig":"MEUCIQDiKBnCQ8WtZe3saC+e2yjQmk3KrKwMw8gL73HTZ81W2wIgbOmdH7C1gSIQ9iQNubYC7WyE2W1c653JJn8rborOw10=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQCeaVl4dUjc6DSn9Wj07zrGJenxiWANJTSormwasLiibAIgOE1yta8deuWObnHj2YV3jz88wciSHPTeCKkYzCHwf5Y=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/mocktown@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1762083},"type":"module","engines":{"bun":">=1.4.0"},"exports":{".":"./src/index.ts","./mock":"./src/mocks/types.ts","./contract":"./src/contract/index.ts"},"gitHead":"27589525bd0af5beab3b9b9e68cff1b04e8552d7","imports":{"#*":"./*"},"scripts":{"test":"bun test","daemon":"bun src/daemon/index.ts","prepack":"bun ../../scripts/prepack.mts","typecheck":"tsc","db:generate":"drizzle-kit generate"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2f3f86ca-4407-403b-a051-f1156fdd28db"}},"repository":{"url":"git+https://github.com/gkurt/mocktown.git","type":"git","directory":"packages/mocktown"},"_npmVersion":"12.0.2","description":"Record an app's outbound traffic, serve it back as stateful mocks, and keep them alive as the real services change.","directories":{},"_nodeVersion":"24.20.0","dependencies":{"zod":"4.5.4","mockttp":"4.6.1","@orpc/zod":"1.15.0","commander":"15.0.0","drizzle-orm":"0.45.2","drizzle-zod":"0.8.3","@orpc/client":"1.15.0","@orpc/server":"1.15.0","@orpc/openapi":"1.15.0","@orpc/contract":"1.15.0","@orpc/openapi-client":"1.15.0","@modelcontextprotocol/sdk":"1.30.0"},"_hasShrinkwrap":false,"devDependencies":{"@types/bun":"^1.4.0","@types/node":"^24.0.0","drizzle-kit":"0.31.10"},"optionalDependencies":{"@libsql/client":"0.17.4"},"_npmOperationalInternal":{"tmp":"tmp/mocktown_0.4.0_1789299648800_0.35459996624295886","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"_id":"mocktown@0.4.1","bin":{"mocktown":"src/cli/index.ts"},"bugs":{"url":"https://github.com/gkurt/mocktown/issues"},"dist":{"shasum":"41c3649d915121006e70872ed4d6ff52b3f6c24f","tarball":"https://registry.npmjs.org/mocktown/-/mocktown-0.4.1.tgz","fileCount":92,"integrity":"sha512-/ZlsrzjrrZ76rSbDPkE6io3NIPVm1FcC3Wgv4KfMfrRuzvlBgTC7IGAm+oSeA1QS/2/WmIC1kNN4sBnp27NBUQ==","signatures":[{"sig":"MEQCIA8Wd770SLzT4SSiFZ5FTOxfL8dkJaOHm/q1XbPNaTAKAiBv7pXzTQYONZY/OLLNIJPQJqMcgMT3vHywgKMzFyiKCw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIHlcOAPkjphLkhq7DjKXMkNOf9rHrkQxK8Iol1Z+bauzAiEA7O37KsFW/thr5YVpmGjPFUgeNS4LrWuAiDlDnf0LL7k="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/mocktown@0.4.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1764084},"name":"mocktown","type":"module","author":{"url":"https://github.com/gkurt","name":"Gokhan Kurt"},"engines":{"bun":">=1.4.0"},"exports":{".":"./src/index.ts","./mock":"./src/mocks/types.ts","./contract":"./src/contract/index.ts"},"gitHead":"e4f3585040da086a5c44a5cd18ced421d018ce22","imports":{"#*":"./*"},"license":"MIT","scripts":{"test":"bun test","daemon":"bun src/daemon/index.ts","prepack":"bun ../../scripts/prepack.mts","typecheck":"tsc","db:generate":"drizzle-kit generate"},"version":"0.4.1","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2f3f86ca-4407-403b-a051-f1156fdd28db"}},"homepage":"https://github.com/gkurt/mocktown","keywords":["mocking","http","proxy","testing","sandbox","agents","mcp","bun"],"repository":{"url":"git+https://github.com/gkurt/mocktown.git","type":"git","directory":"packages/mocktown"},"_npmVersion":"12.0.2","description":"Record an app's outbound traffic, serve it back as stateful mocks, and keep them alive as the real services change.","directories":{},"maintainers":[{"name":"kurtgokhan","email":"krtgkn@gmail.com"}],"_nodeVersion":"24.20.0","dependencies":{"zod":"4.5.4","mockttp":"4.6.1","@orpc/zod":"1.15.0","commander":"15.0.0","drizzle-orm":"0.45.2","drizzle-zod":"0.8.3","@orpc/client":"1.15.0","@orpc/server":"1.15.0","@orpc/openapi":"1.15.0","@orpc/contract":"1.15.0","@orpc/openapi-client":"1.15.0","@modelcontextprotocol/sdk":"1.30.0"},"_hasShrinkwrap":false,"devDependencies":{"@types/bun":"^1.4.0","@types/node":"^24.0.0","drizzle-kit":"0.31.10"},"optionalDependencies":{"@libsql/client":"0.17.4"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mocktown_0.4.1_1789300780312_0.3979334905215508"}}},"time":{"created":"2026-08-31T11:53:32.163Z","modified":"2026-09-13T11:59:40.693Z","0.0.1":"2026-08-31T11:53:32.357Z","0.1.0":"2026-09-09T14:44:59.100Z","0.2.0":"2026-09-09T18:10:31.990Z","0.3.0":"2026-09-12T16:39:08.237Z","0.4.0":"2026-09-13T11:40:48.897Z","0.4.1":"2026-09-13T11:59:40.402Z"},"bugs":{"url":"https://github.com/gkurt/mocktown/issues"},"author":{"url":"https://github.com/gkurt","name":"Gokhan Kurt"},"license":"MIT","homepage":"https://github.com/gkurt/mocktown","keywords":["mocking","http","proxy","testing","sandbox","agents","mcp","bun"],"repository":{"url":"git+https://github.com/gkurt/mocktown.git","type":"git","directory":"packages/mocktown"},"description":"Record an app's outbound traffic, serve it back as stateful mocks, and keep them alive as the real services change.","maintainers":[{"name":"kurtgokhan","email":"krtgkn@gmail.com"}],"readme":"# mocktown\n\nRecord an app's outbound traffic, serve it back as stateful mocks, and keep those mocks\nalive as the real services change.\n\nThis package is the whole product as of phases 1–4: the daemon, the front door, the\ncorpus, the providers, the issue engine, the sealed sandbox, drift watch, and the surfaces\n(CLI, HTTP API, MCP, and the GUI shell, which is built from `packages/gui` and ships inside\nthis package).\nDesign rationale lives in\n[`docs/design`](https://github.com/gkurt/mocktown/blob/main/docs/design/README.md) — read\nthe file for the subsystem you are touching, not all of them.\n\n## Install\n\n```bash\nbun add -g mocktown\n```\n\nMocktown runs on **Bun** — the CLI is TypeScript executed directly, so `bun` has to be the\nthing that installs and runs it. Additionally, the front door is Mockttp in a Node process,\nso a `node` binary has to be on the PATH of the shell that first starts the daemon — Bun\nalone is not enough. `MOCKTOWN_NODE=<path>` names one explicitly.\n\nWorking on Mocktown itself instead? From the repo root:\n\n```bash\nbun install && cd packages/mocktown && bun link\n```\n\n## Quick start\n\nIn the app you want to mock:\n\n```bash\nmocktown init\n```\n\nIf a coding agent is going to do the work, give it the skill:\n\n```bash\nmocktown skills install\n```\n\nThat writes the pack into `.mocktown/skills/` and hands the directory to\n[`skills`](https://github.com/vercel-labs/skills), which knows where each agent looks —\n`.claude/skills/mocktown/` for Claude Code. The pack lives at this repo's root, so the same\ninstaller reaches it without mocktown installed at all:\n\n```bash\nnpx skills add gkurt/mocktown\n```\n\nIt is one skill with the job as its argument:\n`/mocktown generate-mock`, `/mocktown fix-issues`, `/mocktown record-flow`,\n`/mocktown apply-redirects`, `/mocktown write-panel`. `--agent claude-code` picks the target\nrather than being asked, `--global` installs for every project, and re-running it is how a\nnewer pack lands. Without an installer, `mocktown skills export` writes the same directory\nand `mocktown skills get --name mocktown --topic <job>` prints one file.\n\nRecord a run, then look at what it captured:\n\n```bash\nmocktown record --label first-run -- node server.js\n```\n\n```bash\nmocktown recordings list\n```\n\nEverything is scrubbed **before** it reaches disk, so the corpus you browse is the corpus\nthat exists. `mocktown scrub audit` re-scans it with the current rules.\n\n## The loop\n\n1. `mocktown record -- <cmd>` captures real traffic through the front door.\n2. `mocktown mocks scaffold --service <host>` writes a `BRIEF.md` and a module stub from\n   the corpus.\n3. A coding agent fills in the module — `mocktown skills install` puts the `mocktown`\n   skill where the agent will find it, and `/mocktown generate-mock` is the prompt pack\n   for exactly that job.\n4. `mocktown serve start --sealed` serves it — a mock you have written takes over the\n   service the recorder discovered, without a registry edit. Anything unserved is **denied\n   loudly** and filed: `mocktown issues list --status outstanding` — open plus the ones\n   whose last fix failed verification.\n5. `mocktown mocks verify --service <host>` replays the corpus against the mock,\n   comparing status class and response *shape* — never values.\n\nResolving an issue is a patch to a mock plus `mocktown issues resolve --id <id>`. A fix\nthat does not hold reopens the same issue rather than filing a new one.\n\nA mock that is broken — will not import, or throws while seeding — leaves *its* service\ndenied and says why in `mocktown providers list`; the rest keep serving. Any host still\npointed at a real upstream is named in `mocktown serve start`'s warnings, so serving and\nescaping never look alike.\n\n> **`NO_PROXY` is computed from the project, and every entry in it is a hole.** Proxy\n> clients match it by domain suffix, so a blanket `localhost` entry takes every\n> `*.localhost` name with it — a `.localhost` upstream would record nothing while looking\n> perfectly wired up. Mocktown drops that entry as soon as a service sits under the suffix,\n> keeps `127.0.0.1` and `::1` unconditionally, and lets the app name its own local services:\n>\n> ```jsonc\n> { \"noProxy\": [\"localhost\", \"db.internal\"] }\n> ```\n>\n> The trade is stated, never assumed: `record start` and `serve start` warn that a service\n> reached as `localhost:<port>` by name now goes through the front door, and a request for a\n> loopback name that hits the wall is filed as *your own service* — with `noProxy` as the\n> fix — rather than as a missing dependency. A collision that cannot be resolved is named\n> too: under portless the TLD has to bypass, so a `.localhost` upstream is unrecordable in\n> that mode.\n\n## Recording browser traffic\n\n`mocktown record -- <cmd>` covers server-side SDKs by injecting proxy variables into a\nchild process. For the traffic a *page* makes, rung 2 of the ladder launches the browser\nitself:\n\n```bash\nmocktown browser launch --url http://localhost:3000\n```\n\nThe front door has to be running first — `record start` to capture what the browser does,\n`serve start` to browse against mocks. **No system extension, and nothing added to the\nsystem trust store.** The window gets a profile the project owns\n(`--user-data-dir`), and the CA is passed as `--ignore-certificate-errors-spki-list`,\nwhich names *one* public key for *this launch*: a stray HTTPS error in that window is\nstill an error, and no trust is written to disk. Chromium-family only.\n\nLoopback is in Chrome's default proxy bypass, which is what you want — your dev server\nstays direct while third-party scripts transit the front door.\n\n### Driving it\n\n`--debug-port` publishes a CDP endpoint so a driver — `agent-browser`, Playwright,\nPuppeteer — can attach to that window and keep its capture:\n\n```bash\nmocktown browser launch --debug-port 0 --json\n```\n\n```bash\nagent-browser connect ws://127.0.0.1:<port>/devtools/browser/<id>\n```\n\n`0` asks for an ephemeral port; the response carries the resolved\n`debug.webSocketDebuggerUrl`, which is also what `chromium.connectOverCDP()` takes. It is\noff by default because the endpoint is a **capability**: anything that reaches it drives\nthe browser, reads the profile's cookies and navigates it anywhere, with no further\nauthentication. Chrome's only defence is that it binds to loopback.\n\n### Driving a browser of its own\n\nA CLI browser that brings its own Chromium is rung 1, not rung 2: run it with the recorded\nenv and it transits the front door like any other child process. `agent-browser` reads\n`HTTP_PROXY` and `NO_PROXY` on its own; what it cannot work out is the certificate, because\nChrome's verifier reads none of the CA variables. So the recorded env also carries the\nproject CA's public key in the form Chrome's own flag takes:\n\n```\nMOCKTOWN_CA_SPKI=<base64 sha256 of the CA's SubjectPublicKeyInfo>\nAGENT_BROWSER_ARGS=--ignore-certificate-errors-spki-list=<the same key>\n```\n\nThat names one key for that process. **Never substitute `--ignore-https-errors`**: it\naccepts any certificate at all, and a certificate error in a recorded run is information —\nthe wrong project's CA, or a pinned endpoint that belongs in an issue.\n\nA flow is usually more than one command, so open the session first and load the env:\n\n```bash\nmocktown record start --label checkout\nmocktown env write\nset -a && . ./.env.mocktown && set +a\nagent-browser open https://app.example.com/checkout\nagent-browser snapshot -i\nmocktown record stop\n```\n\nThe **first** agent-browser command launches the browser and fixes its proxy and its trust\nfor every command after it — `agent-browser close` before a differently configured run, or\nyou drive the previous one and record nothing. `/mocktown record-flow` is this page written\nfor an agent.\n\n### What is not recorded\n\nA real browser talks to its own vendor constantly, and none of it is evidence about a\ndependency. Measured on one attended session against a staging app: **17 of the 22\ndiscovered services were Chrome's own** — component updates, Safe Browsing lists, sign-in\nprobes, new-tab-page furniture, telemetry — and 5 were the app's. Two mechanisms handle it,\nboth on by default:\n\n1. **The launch does not make the requests.** `browser launch` passes the flags that switch\n   this traffic off at the source, and starts on `about:blank` rather than the new tab page.\n2. **The corpus refuses the rest.** A known-noise request is not recorded, does not become\n   a discovered service, and files no issue.\n\nFiltering decides what is *written down*, never what is allowed out: an ignored request in\nserve mode still hits the deny wall, it just does not queue an issue for a browser update.\n\n**Most patterns are path-scoped, because these hostnames are shared.** The same session\nthat produced the list also contained `fonts.googleapis.com/css2` — the app's own web font\n— and `accounts.google.com` is where a real OAuth flow lives. So `accounts.google.com/ListAccounts`\nis noise and `accounts.google.com/o/oauth2/*` is not; `www.gstatic.com/og/` is noise and\n`www.gstatic.com/your-app/` is not. A blanket `*.googleapis.com` rule would have silently\neaten a real dependency, which is worse than the noise it cleaned up.\n\nNothing is dropped silently. `record stop` reports the count and the reason per pattern, and\na session that dropped more than it kept says so as a warning.\n\n```jsonc\n// mocktown.json\n{\n  \"capture\": {\n    \"ignoreNoise\": true,                        // the built-in list; true by default\n    \"ignore\": [\"telemetry.vendor.com\", \"cdn.vendor.com/beacon\"],\n    \"keep\": [\"accounts.google.com\"]             // record it anyway — a real OAuth dependency\n  }\n}\n```\n\n`keep` wins over everything, per host or per path prefix. `ignoreNoise: false` drops the\nbuilt-in list entirely and leaves your own `ignore` entries in force.\n\n### Taking recordings back out\n\nThe corpus accretes, and sometimes it accretes something you do not want: a route recorded\nagainst the wrong environment, a stray host, or — the case this exists for — a capture whose\nsecret the scrubber's rules did not match.\n\n```bash\nmocktown recordings delete --service noise.test --dry-run   # counts, writes nothing\nmocktown recordings delete --service noise.test\nmocktown recordings delete --service api.test --method GET --path-template '/v1/orders/{orderId}'\nmocktown recordings delete --session ses_...                # one recording run\nmocktown recordings delete --id rec_...                     # one exchange\n```\n\nA delete needs a subject — `--id`, `--session` or `--service`. `--method` and\n`--path-template` narrow one of those and are not filters on their own: \"every GET this\nproject ever recorded\" reads like a filter and behaves like a wipe. There is no way to empty\nthe corpus by leaving arguments out.\n\nFour things are worth knowing before you run it:\n\n- **A spilled body shared with a surviving recording is not unlinked.** Large bodies are\n  content-addressed, so a delete reference-counts rather than deleting by hash.\n- **Emptying a service is reported loudly.** With no recordings left, a generated mock for\n  that service is unbacked and `mocktown mocks verify` passes because it has nothing to\n  replay — the one outcome here that could be mistaken for success.\n- **An issue that cited a deleted row loses the link, not the issue.** The dead link is\n  stripped and the issue's file rewritten; the issue keeps the whole scrubbed request it\n  carries inline, so it stays actionable.\n- **Generated mocks, the registry and the seal stamp are untouched.** A delete narrows the\n  evidence, not the setup — a mock whose evidence is gone keeps serving exactly as before.\n\nThe GUI's Corpus page does the same thing per route and per service, armed by a first click\nand run by a second.\n\n### What still escapes this window\n\nThe note on every launch says the traffic is captured *cooperatively and best-effort*, and\nthat is meant literally — a flag on a process we asked nicely, not a boundary. Specifically:\n\n- **Loopback.** By design, per above — but it means a third-party service reached under a\n  `.localhost` name records nothing while looking perfectly wired up, the same trap the\n  `NO_PROXY` note describes. Nothing at loopback is captured.\n- **The HTTP cache and service workers.** A response served from disk cache or Cache\n  Storage never touches the network, so it never reaches the front door. The profile\n  **persists across launches**, so a second `browser launch` records less than the first.\n  Delete the profile dir for a cold run, or drive `Network.setCacheDisabled` over CDP.\n- **Anything that is not HTTP.** WebRTC's ICE/STUN/TURN is UDP and goes direct; an HTTP\n  proxy cannot carry it.\n- **Managed-Chrome proxy policy**, which takes precedence over command-line switches. On a\n  corporate-managed machine the flag can be silently overridden.\n- **Pinned and HSTS-preloaded endpoints**, which refuse the MITM certificate. Detected and\n  filed as a `pinned-client` issue, out of scope by design.\n- **The user.** It is an ordinary browser window with a normal route to the internet: a new\n  browser process it spawns, an extension installed into that persistent profile, or the\n  proxy settings edited in that window all reach the real internet.\n\nOnly the sandbox closes these, because inside it there is no route out to close. Never\npresent a launched browser as carrying the seal's guarantee. For traffic you can only\nobserve with another tool, `mocktown import --path <file.har>` is rung 4.\n\n## The seal\n\nEverything above is cooperative: an SDK that ignores proxy variables reaches the real API\nwithout ever touching the front door. The sandbox is where that stops being possible.\n\n```bash\nmocktown sandbox up              # sealed network, DNS catch-all, CA in the trust store\nmocktown sandbox exec -- bun test\nmocktown sandbox verify          # the escape attempts, against a negative control\nmocktown seal verify             # run the flows inside, stamp the result — exits non-zero\n```\n\nInside the boundary there is no route out except the front door, so an unregistered\ndependency hits the deny wall and becomes an issue instead of reaching production. That is\nalso why `mocktown seal verify` refuses to run outside the sandbox: with no container\nengine it reports `unverifiable`, never a pass. `mocktown sandbox devcontainer` writes the\nsame boundary as a devcontainer feature for a repo that already has one.\n\n## Watching it work\n\nThe live feed is one procedure, so it is on every surface:\n\n```bash\nmocktown feed --follow\n```\n\n```bash\nmocktown ui\n```\n\n`mocktown ui` (`mocktown gui` still works) opens the shell the daemon serves on its own port\n— dashboard, live feed, issues, services, corpus, provider state, seal and sandbox, and\npanels. When portless has claimed a stable name it opens `http://ui.mocktown` and prints the\nloopback address beside it; `--loopback` forces the direct one. The bearer token is injected\nby the daemon as it serves the page, so the build on disk carries no capability. The shell\nships inside the published package; working from a checkout, build it first (once) with\n`bun run gui:build` from the repo root.\n\nA **panel** is one self-contained HTML file plus a manifest in `.mocktown/panels/`:\n\n```jsonc\n// .mocktown/panels/stripe-state.json\n{ \"name\": \"Stripe state\", \"service\": \"api.stripe.com\", \"entry\": \"stripe-state.html\" }\n```\n\nThe shell lists panels and iframes them. A panel reads the API base and token from the\n`<meta name=\"mocktown-boot\">` element the daemon injects, and is served under a CSP with no\nexternal origin in it — it can talk to this daemon and nowhere else. `mocktown panels list`\nshows what was found, and why any manifest could not be used. The shipped\n`provider-state.html` panel is the worked example to copy.\n\n## Keeping mocks honest as the real services change\n\n```bash\nmocktown drift check\n```\n\nA drift run re-records your own flows against the **real** services, replays that fresh\nevidence against your mocks, and files `provider-drift` issues for the divergences. It is a\nre-record rather than a replay because the corpus holds no credentials — only your app can\nauthenticate. It spends real quota, so the schedule is off until `mocktown.json` asks for it:\n\n```jsonc\n{ \"drift\": { \"enabled\": true, \"intervalHours\": 24, \"flows\": [\"bun run test:integration\"] } }\n```\n\n## Projects, and removing one\n\nA project is a name, a data directory under `~/.local/share/mocktown/<name>/`, and a\nregistry entry in `~/.config/mocktown/config.json`. `mocktown init` writes the committed\n`mocktown.json`; every command afterwards re-registers whatever project it resolves to, so\ncloning a repo and running anything just works.\n\n```bash\nmocktown project list                                # every registered project, and the default\nmocktown project use <name>                          # set the global default\nmocktown project remove --name <name>                # unregister; the data stays\nmocktown project remove --name <name> --data --confirm <name>   # delete the data too\n```\n\nUnregistering is the common one and it is safe: a registry entry outlives the checkout it\nnames, and `project list` marks a workspace that has gone. `--data` is the destructive one —\nthe corpus, the issue history, the seal stamps and the project's **root CA** all live in that\ndirectory — so it will not run unless `--confirm` repeats the project's name. That is an\nargument rather than a flag on purpose: `--data` alone is one keystroke from a command that\nonly meant to unregister, and an agent calling the MCP tool has no way to mean it that a bare\nflag would not also satisfy by accident.\n\nIt refuses in three more cases, each with the fix in the message: while that project's front\ndoor is running, while it has a sandbox up, and when the project is the one the command\nitself resolved to — every command re-registers the project it resolves to, so removing it\nthere would be undone by the next one. Run it from another directory, or with `--project`\nnaming a different project.\n\nTwo things it tells you rather than hides: a repo whose `mocktown.json` still names the\nproject will register it again on the next command run there, and removing the global default\nmoves it back to `main`.\n\nThere is no GUI button for this. Every GUI page is scoped to one project, and the safety here\nis a typed name, which belongs in a terminal.\n\n## Stable local names (optional)\n\nWith [portless](https://github.com/vercel-labs/portless) installed and\n`portless.enabled` in `mocktown.json`, each service gets a stable\n`https://<service>.<project>.<tld>` name instead of a fresh loopback port on every\ndaemon restart, and `.env.mocktown` uses it. Mocktown proves the whole path works — it\nregisters a throwaway name and fetches it back through the proxy — before it claims a name,\nand reports the reason if it cannot. `mocktown env portless get` shows the verdict.\n\n`portless.tlds` is an ordered list, `[\"mocktown.localhost\"]` by default. URLs are built from the\nfirst one that *works*, not the first one configured: each is probed through the proxy, so\n`.env.mocktown` picks up a fallback by itself on a machine where the preferred name cannot be\nreached. `mocktown env portless get` lists both what answered and what did not, with the fix for\neach — a TLD the proxy is not serving needs the proxy restarted, one it serves that does not\nresolve needs `portless hosts sync`.\n\nA bare `.mocktown` is supported, just not the default:\n\n```json\n{ \"portless\": { \"enabled\": true, \"tlds\": [\"mocktown\", \"mocktown.localhost\"] } }\n```\n\nIt reads better and it is the same proxy, but it resolves only because portless writes\n`/etc/hosts`, and it is under no reserved TLD — so keep the `.localhost` spelling behind it, and\nexpect your dev server to need it allowlisted (Vite, for one, permits `.localhost` and nothing\nelse by default).\n\nOne override: while the proxy has no TLS, a `.localhost` spelling leads even if you preferred\nanother. Browsers only keep a `Secure` cookie on a trustworthy origin, and over plain http\nthat means `localhost` and its subdomains — so a session on `http://…mocktown` is dropped\nsilently and the app bounces back to its login page. Start the proxy with TLS to get your\npreference back.\n\nThe daemon's GUI is claimed the same way, as `ui.mocktown.localhost` (or `ui.mocktown`, under a\nproject that prefers it).\n`portless alias` takes a name and not a TLD, so the proxy serves that name under every TLD it\nhas — including any you did not configure. What mocktown will not do is take the name from\nsomething else: a `ui.*` route already pointing at another port is left alone and reported.\n\n## Layout\n\n| Path | What lives there |\n|---|---|\n| `src/contract/` | The one procedure definition. CLI, HTTP API and MCP are all walks of it |\n| `src/daemon/` | The runtime state machine, the oRPC router, the `Bun.serve` server |\n| `src/frontdoor/` | The Node sidecar and the controller that drives Mockttp over its admin protocol |\n| `src/capture/` | Recorder, HAR import, URL normalization, the launch wrapper |\n| `src/scrub/` | Rules and the two-pass scrubber, with `reinject()` for replay |\n| `src/providers/` | The provider interface, the emulate supervisor, the generated-mock host |\n| `src/mocks/` | The public mock-authoring API, matching/diagnosis, corpus export, replay verify |\n| `src/issues/` | The issue engine and the `.mocktown/issues` file queue |\n| `src/db/` | Drizzle schema and the per-project SQLite client |\n| `src/sandbox/` | The container engine seam, the generated images, the topology, the escape-attempt harness |\n| `src/seal/` | Seal certification and its staleness-aware stamp |\n| `src/skills/` | Serving the shipped skill — the pack itself is `skills/mocktown/` at the repo root |\n| `src/drift/` | Drift watch: the re-record run and the daemon-side schedule |\n| `src/redirect/` | The portless seam — stable local names, wrapped and optional |\n| `src/gui/` | Serving the shell and the panels, plus the built-in panels themselves |\n\n## House rules worth knowing before you edit\n\nThese are enforced by tests over the contract walk (`tests/surfaces.test.ts`), not by\nreview:\n\n- **Every procedure is on all three surfaces**, with a summary, a REST route, and\n  `project` in its input.\n- **`--json` is the raw response; human output is a projection of it, never richer.** A\n  procedure with no renderer fails the suite.\n- **`readOnlyHint` follows the HTTP method.** A `GET` that mutates anything is a defect,\n  not a style choice — that is why `env.get` and `env.write` are separate procedures.\n\nAnd two the front door enforces structurally, because getting them wrong leaks traffic to\nthe real upstream:\n\n- Every Mockttp rule is `always()`; a consumed rule silently forwards to production.\n- The fallthrough **denies and files**; it never passes through.\n\nOne more, because CI depends on it:\n\n- **A response carrying `ok: false` exits non-zero.** The verdict is a field of the\n  contract, not a flag on a command.\n\n## Testing\n\n```bash\nbun test\n```\n\n`tests/loop.test.ts` runs a real front door against a real upstream and asserts both phase\nexit criteria. `tests/emulate.test.ts` spawns a real `emulate` process. `tests/sandbox.test.ts`\nbuilds real images and asserts the seal against a negative control, skipping itself when no\ncontainer engine is installed. `tests/sockets.test.ts` holds a real WebSocket conversation\nwith a real mock host and captures another through the real front door.\n`tests/gui.test.ts` fetches the shell and a panel from a real daemon to check the injected\ntoken, the CSP and path containment. `tests/portless.test.ts` runs the portless seam against\na stub binary and a Host-routing reverse proxy, because portless itself binds 443 with sudo\nand installs a CA — not something a test suite gets to do to a machine. None of it is mocked,\nwhich is the point: the failures this product must not have are integration failures.\n","readmeFilename":"README.md"}