The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY in this skill are to be interpreted as described in BCP 14 (RFC 2119, RFC 8174) when, and only when, they appear in all capitals.
Converts any combination of API specs and docs into a runnable mockstar mock project. It accepts OpenAPI 3.x (JSON/YAML), Postman collections, HAR captures, curl command files, GraphQL SDL/introspection, and prose documentation (Markdown, PDF, DOCX, or a URL) — one or several at once — and produces a fully configured mockstar project with realistic scenarios, dynamic handlers, and webhooks, verified by booting the server and smoke-testing every route.
The defining rule: no fabricated endpoints. Every mock traces back to a fetched or provided
source. Behavior that is reasoned from context rather than explicitly documented is tagged
confidence: "inferred" in the Endpoint Inventory and flagged in the coverage report.
Speculation is visible and reviewable; it is never silently promoted to ground truth.
When to use
Use when you need to stand up a mock backend quickly from existing specs or documentation: mocking a third-party API for local development, building a test double from an OpenAPI spec, scaffolding fixtures from a HAR capture of real traffic, or generating stubs from API prose docs. Works on any mix of input types; the skill normalizes them all into one Endpoint Inventory before generating.
When not to use
- mockstar HTTPS proxy mode — this skill generates config-driven mocks; it does not configure the mockstar proxy for intercepting live traffic.
- Native GraphQL server semantics — GraphQL is modeled as a single
POST /graphqlendpoint with operation routing viascenarios[].when.body. If you need a proper GraphQL resolver server (subscriptions, schema stitching, persisted queries), use a dedicated GraphQL toolchain. - Non-mockstar mock servers — MSW, Prism, WireMock, json-server. This skill targets only the mockstar CLI.
Prerequisites
bunx @dhaneshpurohit/mockstaravailable (install:bun add -g @dhaneshpurohit/mockstaror usebunxdirectly with Bun). Package name matters: mockstar is published on npm as the scoped package@dhaneshpurohit/mockstar(latest ≥ 0.2.2). You MUST NOT invoke barebunx mockstar, because the unscopedmockstaron npm is an unrelated project. Equivalent Docker image:ghcr.io/dhanesh/mockstar:latest(--runtime docker). The importer's schema-derived bodies and mixed-segment path-param handling require mockstar ≥ 0.2.2; configurable webhook signing schemes require ≥ 0.3.0 (below that, emit only the pre-0.3 signing fields — see Stage 0 step 2). The coverage report records the resolved runtime and version.uvavailable — used to runassets/extract_text.pyfor binary input conversion.curlavailable — used byassets/smoke.shfor smoke testing routes.- For
--runtime docker: a reachable Docker daemon and theghcr.io/dhanesh/mockstarimage.
Invariants (do not violate)
- No fabricated endpoints. Every endpoint in the output MUST trace to a fetched or provided source. If an endpoint is absent from every input, you MUST NOT emit it, because a consumer would build against a route no source defines.
- Prefer native tooling. You SHOULD use
bunx @dhaneshpurohit/mockstar importfor OpenAPI (and losslessly-liftable Postman/HAR) rather than hand-authoring what the importer can produce. You SHOULD usebunx @dhaneshpurohit/mockstar enhancefor Tier 2 placeholder rewriting rather than hand-tokenizing bodies. - Schema-valid output. Verification (Stage 5) boots the server. You MUST NOT skip the boot with
--no-verifyexcept in CI pre-check mode, where boot is deferred, because a mock project that fails to boot is not a valid deliverable. - No silent truncation. When
--max-endpointscaps the inventory, every dropped endpoint MUST be logged toMOCKSTAR-COVERAGE.mdunder the "Dropped" section with its source. - Read-only inputs. You MUST NOT modify source spec files, HAR archives, or documentation,
because they are the sources every endpoint traces back to.
The only writes are to the
--intooutput directory and toMOCKSTAR-COVERAGE.md.
Flags
--into <dir>— output directory for the generated mockstar project (default:mock-<service-name>in cwd).--tenant <name>— mockstar tenant name used as the mocks subdirectory (default:default). Important: when passed tomockstar import, the equals form--tenant=<name>is required — the space form is silently ignored by the importer.--fidelity full|static—fullgenerates scenarios, dynamic handlers, and webhooks from IR hints;staticemits one default response per endpoint only (default:full).--no-verify— skip Stage 5 boot-and-smoke verification (the generated project is not started).--deterministic— passed through tobunx @dhaneshpurohit/mockstarduring smoke testing; disables random Faker values for reproducible responses.--max-endpoints N— cap the merged Endpoint Inventory at N records; excess are dropped in reverse-priority order and reported.--runtime auto|local|docker— selects how mockstar is invoked (default:auto).autoprefers Docker (theghcr.io/dhanesh/mockstarimage) when the Docker daemon is reachable and the image is available, else falls back to localbunx @dhaneshpurohit/mockstar.localalways usesbunx @dhaneshpurohit/mockstar.dockeralways uses Docker and fails fast if the daemon is down.--image <ref>— the Docker image reference used when--runtimeisdockerorautoresolves to Docker (default:ghcr.io/dhanesh/mockstar:latest). For reproducibility, pin by digest:ghcr.io/dhanesh/mockstar@sha256:<digest>.
Procedure
Python interpreter. Most commands in this skill and its references are written with python3.
Find the interpreter once, before the first of them. If $SKILL_PYTHON is set, use it. Otherwise,
on macOS, if command -v python3 prints /usr/bin/python3 and xcode-select -p fails, that
python3 is the install stub: skip it without running it. Use the first of python3, python and
py -3 that is not skipped and for which
<interpreter> -c 'import sys; sys.exit(sys.version_info < (3, 10))' exits 0. Write the interpreter
you found in place of python3 in every command, and in any Bash(...) permission rule you
suggest. Commands that start with uv run or {python} keep their own form. In a session resumed
under a Bash(...) allowlist, skip this lookup and use the interpreter that the allowlist names. If
none passes, follow this skill's own fallback for running without Python where it gives one;
otherwise stop and tell the user that this skill needs Python 3.10 or newer.
Run these stages in order. Fan out subagents where noted.
Locating asset helpers (do this first). The bundled helpers assets/extract_text.py and
assets/smoke.sh are referenced by paths relative to this skill's directory, but subagents
run from the target repository — a bare assets/ path will not resolve for them. Before
dispatching any subagent, resolve this skill's absolute base directory once and pass the
absolute paths into every subagent prompt:
- Obtain the skill's base directory from the harness (it is provided when the skill loads).
- Set
EXTRACT="$SKILL_DIR/assets/extract_text.py"andSMOKE="$SKILL_DIR/assets/smoke.sh". - If the harness does not expose the base directory, discover it:
find ~/.claude ~/.config ~/.agents -path '*mockstar-mock*/assets/extract_text.py' 2>/dev/null | head -1 - Verify:
python3 "$EXTRACT" --helpshould print usage and exit 0. - You MUST hand subagents the literal absolute
$EXTRACTand$SMOKEvalues, because a relativeassets/-prefixed form will not resolve from the target repo.
Stage 0 — Compatibility preflight
Before any other work, resolve the runtime environment and validate the live CLI surface.
1. Resolve runtime.
Evaluate --runtime:
local— usebunx @dhaneshpurohit/mockstardirectly.docker— use the Docker image (--imagevalue). Fail fast ifdocker version(server) is unreachable.auto(default) — prefer Docker: rundocker versionand confirm the server is reachable, then confirm the image is reachable viadocker manifest inspect <image>. If either check fails, fall back tolocaland note the fallback in the coverage report.
Record the resolved runtime (local or docker) and the image ref when docker is chosen.
2. Detect mockstar version.
- Local:
bunx @dhaneshpurohit/mockstar version— capture the printed version string. - Docker:
docker run --rm <image> version— capture the printed version string.
Record the version. Note the known caveat: the CLI's printed version may lag the package version (treat it as advisory, not definitive).
Feature gate. Webhook signing wire-format fields (signedPayload, signatureTemplate,
digestEncoding, mode) exist only on mockstar ≥ 0.3.0. If the detected version is below
that — or the version could not be determined — do not emit them; fall back to the pre-0.3
signing fields and record the downgrade in the coverage report's "Runtime & compatibility"
section. See references/mockstar-mapping.md § Signing.
3. Validate the live CLI surface.
Run the help command for the chosen runtime:
- Local:
bunx @dhaneshpurohit/mockstar help - Docker:
docker run --rm <image> help
Confirm that import, enhance, and serve-as-default are all present in the output. For
mockstar import, discover the real --tenant flag form from the live help output rather
than assuming. The skill's default assumption is the equals form --tenant=<name> (verified
for current mockstar), but if the live surface shows a different form, prefer the live surface
and note the drift in the coverage report's "Runtime & compatibility" section.
Principle: when the live CLI surface contradicts this skill's baked-in assumptions, prefer the live truth and flag the drift in the coverage report.
4. Record findings.
Record the detected runtime, mockstar version, and image ref/digest in the coverage report's
"Runtime & compatibility" section (see references/coverage-report.md).
Stage 1 — Intake & classify
Examine every provided input and classify each by type using these detection signals:
| Signal | Type |
|---|---|
Top-level openapi: key starting with 3. |
OpenAPI 3.x |
Top-level openapi: key starting with 2. |
OpenAPI 2.x (Swagger) — upgrade to 3.x first |
{"info":{"schema":"https://schema.getpostman.com/...collection/v2.1..."} |
Postman v2.1 |
{"log":{"version":"1.2",...}} |
HAR 1.2 |
Lines starting with curl |
curl command file |
.graphql / .gql extension, or {"data":{"__schema":...}} |
GraphQL SDL / introspection |
.md, .pdf, .docx, plain text, or http(s):// URL |
Prose documentation |
For binary inputs (PDF, DOCX) and local text files, convert to plain text before proceeding:
uv run "$EXTRACT" <local-file>
For documentation URLs, fetch the content first with curl -L (or WebFetch), save it to a
temp file, then pass the temp file to $EXTRACT if it is binary, or use it directly if it is
already plain text or Markdown. You MUST NOT pass a URL directly to $EXTRACT — the helper
handles only local .pdf, .docx, .txt, .md, and .markdown files; a URL causes exit 3
("unsupported extension").
# URL: fetch first, then optionally extract
curl -L -o /tmp/apidoc.md "https://example.com/api-docs"
# /tmp/apidoc.md is already text — use directly as prose source
# Binary (PDF, DOCX): use $EXTRACT
uv run "$EXTRACT" /path/to/spec.pdf
assets/extract_text.py emits the extracted text to stdout; capture it to a temp file and use
that as the prose source for Stage 2. This is the only stage where the absolute $EXTRACT
path is needed for subagents.
Stage 2 — Extract → Endpoint Inventory
Extract each input with the adapter rules in references/input-adapters.md, emitting records
that are valid against references/inventory.schema.json. With two or more input types, fan
out one subagent per input type by default, so the types extract in parallel. With a single
input type, extract it yourself: a subagent re-reads its source and reports back, which costs
more than it saves there. Key rules per adapter:
- OpenAPI — preferred path: if the sole input is a raw OpenAPI 3.x file and no extra
examples need merging, skip manual extraction and hand the file directly to
bunx @dhaneshpurohit/mockstar importin Stage 3. Manual extraction is needed only when merging additional examples or overriding generated config. - Postman — walk
item[]recursively; rewrite{{var}}placeholders to Hono:param. Lift to OpenAPI withpostman-to-openapifirst when saved responses are well-structured. - HAR — group entries by
(method, normalised-path); rewrite numeric/UUID path segments to:id/:uuid. Discard entries outside the target origin. - curl — one record per
curlinvocation; all records areconfidence: "inferred"because curl carries no response information. - GraphQL — model the entire API as a single
POST /graphqlrecord; each named operation becomes aresponses[]entry with awhen.bodypredicate matching onoperationName. - Prose — extract from code fences, Markdown tables, and inline backtick references in that order; you MUST NOT invent endpoints that are absent from the text; otherwise the mock serves a route no source defines.
Once every input is extracted, merge the records into a single Endpoint Inventory array.
Deduplication key: (method, path) (case-insensitive method, exact Hono-style path). Resolve
conflicts in priority order: OpenAPI > Postman/HAR > curl > prose. Union responses[] by
status code. Enforce --max-endpoints by dropping records in reverse-priority order and
logging each dropped (method, path) to the coverage report with reason "dropped: max-endpoints".
Stage 3 — Generate config (hybrid)
For each endpoint in the merged Endpoint Inventory, generate a mockstar JSON mock entry following
references/mockstar-mapping.md. Use the hybrid strategy:
Native path (preferred for OpenAPI and losslessly-liftable inputs):
bunx @dhaneshpurohit/mockstar import <spec-file> <out>/mocks --tenant=<tenant>
The importer writes mock JSON files to <out>/mocks/<tenant>/. Use this path for:
- Raw OpenAPI 3.x inputs (most complete).
- Postman or HAR inputs that have been converted to OpenAPI 3.x without data loss.
Hand-authored path (for everything else):
For curl, GraphQL, prose, and any Postman/HAR that could not be losslessly lifted, generate
mock JSON entries by hand per references/mockstar-mapping.md and write them to <out>/mocks/<tenant>/:
- Map
method+path→match.method+match.path. - Map
auth→match.headerspredicate. - Map
responses[0](nowhen) →responsewithkind: "static". - Map
responses[]entries withwhen→scenarios[]entries. - When
--fidelity full(default):statefulHints→response.kind: "dynamic"+ a minimal TypeScript handler inhandlers/.webhookHints[]→webhooks[]on the triggering entry. When a hint carriessigning, emitwebhooks[].signing— expand a namedprovidervia the cookbook inreferences/mockstar-mapping.mdrather than hand-rolling the wire format, and you MUST NOT emit an inline secret (secretRefMUST be{{ env.NAME }}orfile:/path), because it would be written into the generated project's files, and mockstar rejects it at config load.- For GraphQL: route operations via
scenarios[].when.bodymatching onoperationName(NOT via amatch.body.jsonpathrouter on the parent entry).
Prefer literal example values in the initial output; Tier 2 token rewriting is deferred to Stage 4.
Stage 4 — Enhance
Run mockstar's enhance pass over the generated mocks directory:
bunx @dhaneshpurohit/mockstar enhance <out>/mocks/<tenant>
When an OpenAPI input exists, add --spec <openapi-file> so the enhancer can cross-reference
the schema for more accurate placeholder selection.
The enhancer rewrites hardcoded IDs and timestamps to Tier 2 tokens ({{faker.uuid}},
{{now.iso}}, {{id("prefix_", 14)}}, etc.) and writes a _mockstarGenerated manifest
(idempotent — safe to re-run). It does not validate schema conformance; that is proven
by the Stage-5 boot, where mockstar validates every config file with Zod and fails fast on
any invalid entry. Review the enhancer's diff — the coverage report flags any Tier 2
rewriting that changes confidence from "grounded" to "inferred".
Stage 5 — Verify
Unless --no-verify is set:
-
Write a routes TSV file at
<out>/mocks/routes.tsvwith one line per endpoint:METHOD<TAB>/path<TAB>EXPECTED_STATUSUse the primary (no-
when) status code from the Endpoint Inventory. For endpoints without a grounded status, use200. -
Run the smoke suite using the absolute
$SMOKEpath resolved under "Locating asset helpers" (before Stage 0), passing the resolved runtime environment and the tenant viaMOCKSTAR_SMOKE_TENANT:# local runtime MOCKSTAR_SMOKE_RUNTIME=local MOCKSTAR_SMOKE_TENANT=<tenant> sh "$SMOKE" <out>/mocks <out>/mocks/routes.tsv # docker runtime MOCKSTAR_SMOKE_RUNTIME=docker MOCKSTAR_SMOKE_IMAGE=<image> MOCKSTAR_SMOKE_TENANT=<tenant> \ sh "$SMOKE" <out>/mocks <out>/mocks/routes.tsvTenant selection is mandatory for any non-
defaulttenant. mockstar resolves a tenant before routing (default modes:path+header). Only thedefaulttenant is served at the bare path; a named tenant's routes all 404 at the bare path even though the mocks are valid.smoke.shsendsx-mockstar-tenant: <tenant>on every probe (header mode) so named tenants resolve — do not skipMOCKSTAR_SMOKE_TENANT, or a correct project will look broken and trigger a false fix-loop. See "Tenant selection" under Output layout.assets/smoke.shboots mockstar via the chosen runtime. Both paths pollGET /healthfor liveness before testing routes (/healthis tenant-agnostic;/readyreports drain state). SetMOCKSTAR_SMOKE_PORTfor a non-default smoke port. The server boots via the default command (bunx @dhaneshpurohit/mockstar <config-root>); the explicitservesubcommand (bunx @dhaneshpurohit/mockstar serve <config-root>) is equivalent — both are valid. -
For any
FAILlines from the smoke run, inspect the generated config, fix the entry, and re-run until all routes pass. Do not ship a project with smoke failures. Before editing a mock in response to a 404, confirm the probe carried the tenant selector — a bare-path 404 on a named tenant is a selector problem, not a mock problem.
Stage 6 — Report
Write MOCKSTAR-COVERAGE.md to the output directory root, following the template in
references/coverage-report.md, with these sections:
- Summary — inputs list, tenant name, endpoint count (grounded vs. inferred), boot verdict.
- Endpoints table — one row per mock: method, path, mock file, source, locator, confidence.
- Grounded vs. inferred — counts and explanation.
- Review me (speculative) — every inferred scenario, dynamic handler, webhook, and webhook signing scheme with the reason it was inferred and its source.
- Gaps — endpoints or behaviors present in the source docs but not mocked (e.g. prose that names an endpoint without a path, or a schema type with no example).
- Dropped — endpoints cut by
--max-endpoints, with source. - Conflicts — endpoints described differently across inputs and how conflicts were resolved.
Output layout
The config-root is a mocks/ directory containing one <tenant>/ subdirectory per tenant,
with a sibling handlers/ directory for dynamic handlers. Default layout (when --into is
not set, <service> is derived from the primary spec's title or the target URL hostname):
mock-<service>/
mocks/
<tenant>/ # mockstar mock JSON files (e.g. users.json, orders.json)
handlers/ # TypeScript dynamic handler files (--fidelity full only)
routes.tsv # routes used for smoke testing
MOCKSTAR-COVERAGE.md
With --into <dir>:
<dir>/
mocks/
<tenant>/
handlers/
routes.tsv
MOCKSTAR-COVERAGE.md
Tenant selection (how a consumer reaches the mocks)
mockstar runs tenant resolution as the first routing step, with default modes path and
header. A request that carries no selector falls back to the default tenant. So:
- The
defaulttenant is served at the bare path —GET /pet/findByStatus. - Any named tenant (anything other than
default) is reachable only via a selector:- Path mode: prefix the path with
/t/<tenant>—GET /t/petstore/pet/findByStatus(the/t/<tenant>prefix is stripped before route matching). - Header mode: send
x-mockstar-tenant: <tenant>—GET /pet/findByStatuswith the header. - (Subdomain mode
<tenant>.hostexists but is off by default.)
- Path mode: prefix the path with
Consequence for this skill: if --tenant is not default, every consumer — the smoke test,
the coverage-report examples, and whoever calls the mock — MUST use a selector. The coverage
report's Summary states the tenant and the exact selector to use. When in doubt, use default
as the tenant so bare-path access works with zero configuration. No mockstar.config.json is
required for tenant routing: the CLI serve path always enables path + header modes.
Ways to run mockstar
The generated mocks/ config-root is portable across all of mockstar's distribution channels.
Pick per persona; each honours the same tenant selectors above.
- Developer — bunx (hot reload):
bunx @dhaneshpurohit/mockstar mocks/ --handlers handlers/. Editing amocks/<tenant>/*.jsonfile hot-reloads only that tenant. - Docker (shared/staging/CI): mount
mocks/→/config/mocksandhandlers/→/config/handlers(see "Docker delivery" below). The container binds0.0.0.0:3000and is crash-only — the orchestrator MUST set a restart policy (--restart=always, K8srestartPolicy: Always, or systemdRestart=on-failure). Use/healthfor liveness and/readyfor load-balancer drain. - SDET — library embed (in-process, no daemon): import mockstar into a Jest/Vitest/
bun testsuite and point it at the same config-root:import { launch } from '@dhaneshpurohit/mockstar'; const instance = await launch({ configRoot: './mocks', handlersDir: './handlers', deterministic: true, watch: false, installCrashHandlers: false, }); const res = await instance.server.hono.request('http://localhost/pet/findByStatus', { headers: { 'x-mockstar-tenant': 'petstore' }, // selector, as above }); await instance.stop(); - Offline CI / Windows — compiled binary:
bun build src/cli.ts --compile --outfile mockstarthen./mockstar mocks/(no Bun install needed at run time).
Boot the server locally from the output root (default command; serve is an equivalent alias):
bunx @dhaneshpurohit/mockstar mocks/
bunx @dhaneshpurohit/mockstar serve mocks/ --deterministic --no-watch --port 3000
# named tenant, path mode: curl http://127.0.0.1:3000/t/<tenant>/<path>
# named tenant, header mode: curl -H 'x-mockstar-tenant: <tenant>' http://127.0.0.1:3000/<path>
Docker delivery
When --runtime docker or auto resolves to Docker, the skill emits two delivery artifacts:
(a) Mount command (local dev — no build step):
docker run --rm -p 3000:3000 \
-v "<abs>/mocks:/config/mocks:ro" \
-v "<abs>/handlers:/config/handlers:ro" \
<image>
Drop the -v handlers bind-mount when no dynamic handlers exist. <abs> is the absolute path to the output directory; <image> is the resolved ref from Stage 0 (tag or digest).
(b) Baked image (share / CI):
The skill renders assets/Dockerfile.template — filling <TAG> with the detected image ref/digest from Stage 0 — and writes it as Dockerfile in the output directory root. Build and run with:
docker build -t <name> . && docker run --rm -p 3000:3000 <name>
The template (assets/Dockerfile.template) copies mocks/ into /config/mocks (and optionally handlers/ into /config/handlers) and inherits the base image's ENTRYPOINT/CMD, which serves /config/mocks on port 3000.
Air-gap fallback (Stage 0 runtime resolution): if docker manifest inspect <image> fails due to no network but the image is already present locally, docker image inspect <image> is sufficient to treat docker as available. The coverage report notes whether the image ref was verified remotely or resolved from the local cache.
Output style. Write reports and explanations for the user in about 80% ASD-STE100 Simplified Technical English. Use short sentences, common words, active voice and one action per step. This style is for prose to the user, not for the code, prompts or files that this skill makes.
References
references/input-adapters.md— per-format extraction rules and merge/dedupe logic.references/inventory.schema.json— JSON Schema for Endpoint Inventory records.references/mockstar-mapping.md— IR → mockstar config field mapping, scenarios, handlers, webhooks, Tier 2 tokens.references/coverage-report.md— theMOCKSTAR-COVERAGE.mdtemplate.
Assets
assets/extract_text.py— converts local PDF/DOCX/Markdown/text inputs to plain text (a documentation URL is fetched separately, per stage 1).assets/smoke.sh— boots mockstar and smoke-tests every route in a TSV file.assets/fixtures/— sample inputs used by the test suite (petstore-mini.yaml, routes.tsv).
Subagent dispatch note
The skill's asset helpers (assets/extract_text.py, assets/smoke.sh) live in this skill's
directory, not in the target repository. Subagents launched in Stage 2 and Stage 5 run from the
target repo's working directory, so a relative assets/ path will not resolve for them. The
rule for handing them the absolute paths is in "Locating asset helpers" above. This mirrors the pattern used by the base-in-reality skill for its
fetch_sources.py helper.