API Design
MadAppGang/claude-code
A skill your agent uses when designing REST or GraphQL APIs, defining endpoints, implementing pagination/filtering, handling API versioning, or establishing API documentation with OpenAPI/Swagger.
Build or update a distilled SDK for an API provider — sourcing its OpenAPI/Smithy/GraphQL/discovery description, adding the spec mirror that feeds it, generating packages/<provider, listing it on…
$ npx skills add alchemy-run/distilled --skill distilled-sdk -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install alchemy-run/distilled distilled-sdk --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/alchemy-run/distilled.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/distilled-sdk .claude/skills/distilled-sdk && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "distilled-sdk" agent skill from https://github.com/alchemy-run/distilled/tree/main/.agents/skills/distilled-sdk into .claude/skills/distilled-sdk/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "distilled-sdk", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/alchemy-run/distilled/tree/main/.agents/skills/distilled-sdkType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add alchemy-run/distilled --skill distilled-sdk -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install alchemy-run/distilled distilled-sdk --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/alchemy-run/distilled.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.agents/skills/distilled-sdk .agents/skills/distilled-sdk && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "distilled-sdk" agent skill from https://github.com/alchemy-run/distilled/tree/main/.agents/skills/distilled-sdk into .agents/skills/distilled-sdk/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "distilled-sdk", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add alchemy-run/distilled --skill distilled-sdk -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install alchemy-run/distilled distilled-sdk --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/alchemy-run/distilled.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.agents/skills/distilled-sdk .cursor/skills/distilled-sdk && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "distilled-sdk" agent skill from https://github.com/alchemy-run/distilled/tree/main/.agents/skills/distilled-sdk into .cursor/skills/distilled-sdk/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "distilled-sdk", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/alchemy-run/distilled.git --path .agents/skills/distilled-sdk--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add alchemy-run/distilled --skill distilled-sdk -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install alchemy-run/distilled distilled-sdk --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/alchemy-run/distilled.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.agents/skills/distilled-sdk .gemini/skills/distilled-sdk && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "distilled-sdk" agent skill from https://github.com/alchemy-run/distilled/tree/main/.agents/skills/distilled-sdk into .gemini/skills/distilled-sdk/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "distilled-sdk", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install alchemy-run/distilled distilled-sdkInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add alchemy-run/distilled --skill distilled-sdk -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/alchemy-run/distilled.git skills-src && mkdir -p .github/skills && cp -r skills-src/.agents/skills/distilled-sdk .github/skills/distilled-sdk && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "distilled-sdk" agent skill from https://github.com/alchemy-run/distilled/tree/main/.agents/skills/distilled-sdk into .github/skills/distilled-sdk/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "distilled-sdk", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add alchemy-run/distilled --skill distilled-sdk -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install alchemy-run/distilled distilled-sdk --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/alchemy-run/distilled.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.agents/skills/distilled-sdk .opencode/skills/distilled-sdk && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "distilled-sdk" agent skill from https://github.com/alchemy-run/distilled/tree/main/.agents/skills/distilled-sdk into .opencode/skills/distilled-sdk/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "distilled-sdk", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
distilled-sdkBuild or update a distilled SDK for an API provider — sourcing its OpenAPI/Smithy/GraphQL/discovery description, adding the spec mirror that feeds it, generating packages/<provider, listing it on…
Distilled SDK is an agent skill from alchemy-run/distilled. Build or update a distilled SDK for an API provider — sourcing its OpenAPI/Smithy/GraphQL/discovery description, adding the spec mirror that feeds it, generating packages/<provider, listing it on distilled.cloud with a category and a logo, writing a README with a complete Effect example, opening a GitHub PR whose body includes that same example, and regenerating an existing one. Use for "create a distilled SDK for <provider", adding a provider, writing or fixing a fetch-specs.ts, giving a provider a catalogue…
Its SKILL.md is about 6k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Backend & APIs, covering OpenAPI specifications, GraphQL and Technical documentation. It works with GitHub, OpenAPI, GraphQL and Amazon Web Services. The repository describes itself as: Effect-native SDKs for cloud providers. The licence is Apache-2.0.
11 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 4406d5f. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
pnpmgitghnpmtscFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
github.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Distilled SDK loads about 6k tokens when it runs. Until then it costs about 169 tokens; SKILL.md has 2,774 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from alchemy-run/distilled at commit 4406d5f, republished under its Apache-2.0 licence (© alchemy-run). 2,774 words, ~5,982 tokens.
.claude/skills/distilled-sdk/SKILL.md (or your agent's skills folder). upstream API description (a URL, a git repo, a GraphQL endpoint, docs)
│
│ stacks/distilled-submodules/spec-repos/<pkg>/fetch-specs.ts ← you write this
▼
distilled-mirror/spec-mirror-<pkg> one repo per package, refetched daily
│ layout: .meta/ (machinery) + specs/ (payload)
│ git submodule
▼
packages/<pkg>/specs/spec-mirror-<pkg>/specs/…
│
│ packages/<pkg>/scripts/convert.ts spec dialect → Smithy
▼
packages/<pkg>/.generated-specs/*.json Smithy 2.0 models (committed)
│
│ packages/<pkg>/scripts/generate.ts Smithy → Effect SDK
▼
packages/<pkg>/src/services/*.ts (committed)Two facts follow from this and shape everything below:
generate and never
checks out a submodule. A PR is green with no spec present at all — the
specs only matter to whoever regenerates.github/rest-api-description is 6.7 GB
checked out for one 13 MB file; the mirror holds the file. See
stacks/distilled-submodules/README.md.There is no generic fetch script. Copy the closest existing one and adapt it; this table is the whole decision.
| Upstream publishes | Copy | Technique |
|---|---|---|
| One OpenAPI document at a stable URL | spec-repos/hetzner | fetch() → validate → write |
| A live endpoint serving YAML | spec-repos/posthog | fetch() → yaml parse → write JSON |
| A few files inside a big git repo | spec-repos/github, spec-repos/turso | raw.githubusercontent.com per file — never clone |
| Whole directories inside a huge git repo | spec-repos/aws, spec-repos/azure | blobless (--filter=blob:none) + --no-checkout clone, narrow sparse patterns from the tree, only then fetch blobs |
| An index that enumerates many documents | spec-repos/gcp | crawl the discovery directory, write _manifest.json + one doc per entry |
| A GraphQL endpoint | spec-repos/railway, spec-repos/expo-eas | introspection query → schema.json |
| Only human docs (markdown/HTML) | spec-repos/cloudflare | crawl the docs sidebar, download each page's markdown twin, render the page HTML when that twin is truncated |
A YAML spec does not have to be converted in the mirror: spec-repos/coinbase
mirrors openapi.yaml verbatim and packages/coinbase/scripts/convert.ts
passes parse from the yaml package to runOpenApiConvert. Convert
in the mirror only when the upstream is an endpoint rather than a file.
When the user says "like GitHub" they mean a few files out of a big repo —
raw download, no clone. "Like cloudflare" means the description has to be
scraped out of documentation pages. Copy spec-repos/cloudflare: crawl the
sidebar for every page URL, download each page's index.md twin, and fall
back to rendering the page's HTML when the twin comes back truncated — that
host cuts the markdown off mid-page on its largest schemas. If you are about
to scrape docs, first check whether the provider publishes a real
machine-readable description somewhere — that is nearly always the better
spec.
Rules every fetch-specs.ts follows:
.meta/ and writes to ../specs/ — nothing else.typeof spec.openapi === "string" && spec.paths !== undefined.JSON.stringify(spec, null, 2) + "\n" — so a
whitespace-only change upstream produces no diff and no daily commit.mkdir stacks/distilled-submodules/spec-repos/<pkg>
# fetch-specs.ts, package.json, readme.md — copy the exemplar from step 1Add { package: "<pkg>" } to SPEC_REPOS in
stacks/distilled-submodules/SpecRepos.ts, in alphabetical position.
You cannot create the mirror repository — it lives in the distilled-mirror
org and is created by the distilled-submodules stack, which deploys on merge
to main. That is fine; step 3 replaces it.
pnpm specs:local <pkg>This copies the same scaffold the stack deploys into
packages/<pkg>/specs/.local/ and runs fetch-specs.ts from its .meta
directory, so the result is byte-identical to a checkout of the real mirror:
packages/<pkg>/specs/.local/
├── .meta/ fetch-specs.ts, package.json, tsconfig.json
└── specs/ ← what the generator readsThe directory is gitignored. Re-run the command to refetch after editing the
fetch script (the pnpm install only happens once).
Create packages/<pkg>/ from the closest existing package. In convert.ts,
declare the production spec path and resolve it:
import { resolveSpecPath } from "@distilled.cloud/core/codegen/spec-path";
const specPath = resolveSpecPath(root, "specs/spec-mirror-<pkg>/specs/openapi.json");runOpenApiConvert already does this for its specPath entries, so an
OpenAPI package gets it for free — just declare the mirror path.
Then register the package: pnpm-workspace.yaml needs nothing (it globs
packages/*), but add both tsconfig references to the root tsconfig.json.
Public surface: re-export the generated barrel at the package root
(export * from "./services/index.ts") so callers write Pkg.vms.createVm,
not Pkg.Services.vms.createVm. Do not add a Services namespace. A
single-service package re-exports operations on the root (Pkg.listX) the
same way.
src/credentials.ts is hand-written (copy packages/s2/src/credentials.ts).
Every secret it touches is a Redacted.Redacted<string> from
effect/Redacted: API keys, tokens (access, refresh, bearer, session),
passwords, client secrets, private keys, and signing or HMAC secrets. Base
URLs, account and org IDs, emails, usernames, client IDs and key IDs stay
plain strings.
Redacted<string> only. Type a secret parameter of
fromApiKey, credentials, fromToken and the like, and any callback
that returns one (an OAuth load/refresh), as Redacted.Redacted<string>.
Do not type it string, and do not type it string | Redacted<string>. A
plain string is how a secret ends up in a log or an error, so the caller
wraps it.Redacted. The value the Credentials
service yields, and any cache of it, keeps each secret redacted.Config.Redacted("<ENV>").Redacted.value only where the
header, query string, body or signature is built, and never put a secret
in an error message or an error field. Anything minted at runtime (an
exchanged OAuth token, a signed JWT) is wrapped as soon as it exists.This must print nothing:
grep -niE 'readonly \w*(key|token|secret|password)\??: string' \
packages/<pkg>/src/credentials.tsEvery error class in src/errors.ts must be one the protocol can actually
raise. A class in the operation error union that nothing constructs tells
callers to handle a failure that never happens — that is how ~75 packages
ended up declaring a <Pkg>ParseError no code path created.
src/errors.ts declares, and the operation error union
(<Pkg>OpError / DefaultErrors) includes:
Unknown<Pkg>Error — built by the protocol's unknownError fallback.<Pkg>ParseError with { body: Schema.Unknown, cause: Schema.Unknown },
.pipe(Category.withParseError) — built by the protocol's parseError
(copy packages/s2/src/errors.ts).HTTP_STATUS_MAP,
each wired into the protocol's statusMap.The protocol wires every one of them:
makeRestProtocol requires parseError:
parseError: ({ body, cause }) => new <Pkg>ParseError({ body, cause }).validateResponse(outputAst, value, (cause) => new <Pkg>ParseError({ body, cause })) from
@distilled.cloud/core/response-validation on every 2xx path that returns
the operation output — after wire→TS key mapping, before wrapSensitive.
packages/core/src/protocol-rest.ts is the reference.2xx responses are validated only in strict mode. ResponseValidation
(import { ResponseValidation } from "@distilled.cloud/core") is one context
reference shared by every SDK: lenient by default, switched with
Effect.provide(ResponseValidation.strict) — only ever by layer. A new
protocol reads the mode through validateResponse; it never adds its own
flag or environment variable.
Lenient mode checks only what the protocol needs in order to transform the
body (unwrap an envelope, map keys, wrap sensitive members) and nothing
more: a non-JSON body comes back as text, a body missing members comes back
as read. Never decode against the output schema outside validateResponse.
Strict mode surfaces every spec inaccuracy (an undocumented null, a new
enum member) as a <Pkg>ParseError; that is the cost of opting in, and why
strict is never the default.
Do not add tests to a generated SDK. Generated code is tested once, through
the generator and the protocols in packages/core. packages/core/src/sdks.test.ts
runs against every package and checks the glue a new SDK hand-writes: its
<Pkg>OpError union reaches <Pkg>ParseError (so catchTag on a strict
call typechecks), something outside src/services/ constructs it, and
errors.ts exports it. A new package is covered the moment it exists; a
package that cannot follow the pattern goes in that file's exemption list
with the reason. A package gets a test
only for code someone wrote by hand in it — a custom protocol
(packages/fly-io/src/protocol.ts, everything in packages/aws), or
credentials logic like packages/prisma/test/credentials.test.ts — next to
that code. No live tests: calls against real APIs belong to Alchemy's test
suite.
Before opening the PR, confirm the parse error and the unknown-error fallback are both constructed outside the generated code — this must print two or more lines:
grep -rnE 'new \w+(ParseError|Unknown\w*Error)\(' packages/<pkg>/src \
--include='*.ts' --exclude-dir=servicesFor every other class you add to errors.ts, find where the protocol
raises it (statusMap, a code lookup table, or new). A class nothing
raises comes out of errors.ts and the error union.
DISTILLED_SPECS_LOCAL=1 pnpm generate <pkg>The environment variable re-roots every spec read into .local. It prints a
warning to stderr on every run so a local-spec generation is never mistaken
for a real one, and it lives in one command's environment — never in a
file. Without it the same command reads the submodule, which is what you want
once the mirror exists.
Iterate on convert.ts (spec → Smithy) and generate.ts (Smithy → TS)
separately: pnpm --filter @distilled.cloud/<pkg> run convert stops after the
Smithy models, which is usually where the interesting bugs are.
Patches go in packages/<pkg>/patches/ as RFC-6902 *.json and apply in
convert (finalizeConvert at the end of every dialect — OpenAPI,
GraphQL, discovery, proto, Cloudflare markdown), so .generated-specs is
the patched Smithy model. OpenAPI pointers (/paths, /components) run on
the spec before conversion; Smithy pointers (/shapes, /metadata) run on
the model after. generate does not patch — it only compiles the committed
models.
finalizeConvert is not idempotent — move patches and model transforms
only make sense on freshly converted models — so it stamps
metadata["distilled.finalized"] and refuses to run over a model that
already carries it. To re-apply patches or naming, re-run the package's
convert; never post-process a committed .generated-specs file. It also
fails if any target in a model points at a shape that does not exist.
Stale patch pointers fail the run (they used to warn-and-skip, which is
how Fly's whole chain vanished after the spec-mirror prefixed paths with
/v1). Pass onStalePatch: "warn" only if you truly want skip.
Operation names are convert policy, not patches, and default to
verbNoun (listApps, getApp, createMachine) in the OpenAPI and
GraphQL converters and in finalizeConvert (for dialects with no naming
step of their own). toVerbNoun only reorders ids it can recognise —
go-swagger Apps_list, REST ConfigsList, GraphQL projectCreate — and
leaves anything already verb-first or ambiguous (WatchPodList,
AppGetOrCreate, accountById) unchanged. Irregulars go in
operationNames (lookup by "METHOD path", then operationId) — PUT vs
PATCH that share an upstream id need the path key. Cases live in
packages/core/src/codegen/rewrite-operation-ids.test.ts (pnpm vitest run); add
one before changing the heuristic. Do not RFC-6902-patch
/paths/~1foo/get/operationId; those break when upstream adds a prefix.
Patch the spec, not the generated TypeScript. Writing and checking a
patch is the distilled-sdk-patch skill
(.agents/skills/distilled-sdk-patch/SKILL.md).
pnpm specs:link <pkg>Everything about the entry is derived from the package name
(packages/<pkg>/specs/spec-mirror-<pkg> ←
https://github.com/distilled-mirror/spec-mirror-<pkg>.git), so this is a
convenience, not a decision. Before the mirror exists it writes the
.gitmodules stanza alone. That is inert — git submodule iterates gitlinks
in the index, so a stanza without one is skipped by pnpm specs:sync and by
every submodule update — and it becomes a real submodule when you run the
same command again after the mirror is deployed.
The catalogue on distilled.cloud reads packages/*/package.json at build
time, so a new non-private package appears on its own — in the catch-all
group More, with a generated monogram where a logo should be. Two files
under website/build/ fix that, and both tolerate a package that is not in
the tree yet, so the entries belong in the PR that adds the SDK.
Category — add <pkg> to a group array in GROUPS
(website/build/packages.ts). Order within a group does not matter; the
catalogue sorts each group by package name. While you are there add
SEARCH_HINTS[<pkg>] — extra words the catalogue filter matches on top of the
npm name and the directory, which it already matches (neon: "postgres serverless").
Logo — add a { viewBox, inner } entry to
website/build/data/brand-icons.json, keyed by the same <pkg>:
"neon": {
"viewBox": "0 0 64 64",
"inner": "<path d=\"M63 0.0177…\" fill=\"currentColor\"/>"
}inner is the source SVG's children — no <svg> wrapper, no width or
height. Keep the source's own viewBox or the mark renders cropped.currentColor. Marks are monochrome and
inherit the card's colour, which differs between themes and on hover, so a
hardcoded hex disappears in one of them.build/plugin.ts concatenates the entries into one /icons.svg sprite as
<symbol id="i-<pkg>"> and the markup is injected verbatim. Use a source
you trust, and rename any internal id (clip paths, gradients) — they are
global in the sprite and collide across providers._license entry: svgl.app for
most, Simple Icons (CC0) where svgl lacks the brand, official vector files
otherwise. Add the provenance there when you introduce a new kind.Neither is a build failure — "More" and the monogram exist so a new package never breaks the site, and plenty of providers still run on a monogram — but a provider with both is findable by search and looks finished.
The catalogue only lists packages that export at least one
API.OperationMethod, so a support package (core) never shows up and a
GROUPS entry for one would be dead config.
Every provider package ships packages/<pkg>/README.md. Do not skip it.
Shape it after packages/aws/README.md:
install, a complete Effect program, then auth. listX({}) with no Layer,
credentials, or HTTP client is not an example.
README (packages/<pkg>/README.md):
# @distilled.cloud/<pkg>
Effect-native <Name> SDK, generated from <spec URL>.
## Installation
```bash
npm install @distilled.cloud/<pkg> effect
```
## Quick start
```ts
import { Effect, Layer } from "effect";
import * as FetchHttpClient from "effect/http/FetchHttpClient";
import * as Pkg from "@distilled.cloud/<pkg>";
const program = Effect.gen(function* () {
const result = yield* Pkg.vms.createVm({ firewall: { rules: [] } });
return result;
});
const Live = Layer.mergeAll(
FetchHttpClient.layer,
Pkg.CredentialsFromEnv,
Pkg.PkgProtocol,
);
program.pipe(Effect.provide(Live), Effect.runPromise);
```
## Auth
`<ENV>` as `Authorization: Bearer`. Optional `<ENV>_API_BASE_URL`.The quick-start program must compile against the generated names:
Layer.mergeAll(FetchHttpClient.layer, CredentialsFromEnv, <Pkg>Protocol)Pkg.vms.createVm / Pkg.execVm / Pkg.getX,
not Pkg.Services.… and not only list*)Effect.provide + Effect.runPromiseIf a generated operation cannot work as REST — WebSocket 101,
application/octet-stream bodies, SSE — say so and show the hand-written
helper (or omit that call), never the stub. Keep src/index.ts's @example
the same shortest program.
When credentials exist, run that program live and mention the result in the
PR (execVm status 0, PTY sessionInfo, …). Delete anything the example
created.
The job is not done until step 10 has opened a GitHub PR. npm trusted
publishing (npm-oidc-setup) needs a logged-in npm whoami; skip it and
say so in the PR when this environment is not.
pnpm specs:check # mirror manifest ↔ spec-repos/ ↔ .gitmodules ↔ packages/
pnpm typecheck # tsc -b
pnpm format # generated output is committed formattedpnpm generate formats at the end for a reason: never diff regeneration
results before formatting, or every file looks changed.
A new SDK is not finished in the working tree. Open a GitHub PR. Stage
explicit paths (never git add -A), commit, push, gh pr create.
Title: feat(<pkg>): add the <Name> SDK.
Body, in order:
.gitmodules,
tsconfig, website, lockfile). Note that spec-mirror-<pkg> is created by
the distilled-submodules stack on merge to main.Layer.mergeAll,
CredentialsFromEnv, <Pkg>Protocol, a real call, Effect.runPromise.
A PR whose only snippet is listX({}) is incomplete; paste the README
quick start.Checks: pnpm specs:check green, tsc -b packages/<pkg> --noCheck false
green, DISTILLED_SPECS_LOCAL=1 pnpm generate <pkg> reproduces output,
and the error-construction check from step 4 finds both classes.git push -u origin HEAD
gh pr create --title "feat(<pkg>): add the <Name> SDK" --body-file /tmp/pr.mdThe stack deploys on push to main and creates spec-mirror-<pkg>, seeded
with your fetch script and a workflow that refetches daily. Then, in a
follow-up: pnpm specs:link <pkg> to record the gitlink, delete
packages/<pkg>/specs/.local/, and confirm pnpm generate <pkg> reproduces
the committed output from the submodule.
pnpm specs:sync # every mirror, tip commit only
pnpm --filter @distilled.cloud/<pkg> \
run specs:fetch # just one
pnpm --filter @distilled.cloud/<pkg> \
run specs:update # move it to the mirror's latest
pnpm generate <pkg> [<pkg>…] # convert + generate + format
pnpm generate # everythingEvery one of those is --depth=1 and none is --recursive: a mirror's
history is refetch noise (a commit a day, forever) and no mirror has nested
submodules. .gitmodules sets shallow = true on every entry and
specs:check fails if one loses it.
To iterate on a mirror's fetch script — or to regenerate against today's
upstream without touching submodules — use pnpm specs:local <pkg> and
DISTILLED_SPECS_LOCAL=1, exactly as above.
Moving a package to a newer spec and pruning the patches it no longer
needs is the distilled-sdk-update skill
(.agents/skills/distilled-sdk-update/SKILL.md).
specs/.local. A package that reads
from a gitignored directory is one nobody else can regenerate. Local mode is
an environment variable, never a source edit. pnpm specs:check fails on a
.local path in a string literal.specs/ directory needs a SPEC_REPOS entry. Checked
by pnpm specs:check on the PR, and again by the stack at deploy time so a
new SDK cannot quietly stay on a multi-gigabyte upstream submodule.spec-repos/<pkg>/ needs all three files — fetch-specs.ts,
package.json, readme.md — and a blocked package must have none of
them, because the stack never commits its scaffold..gitmodules mirror entries must match the naming convention.Local mode re-roots a spec path by taking everything after its last specs
segment, which is why every package's declared path is
specs/spec-mirror-<pkg>/specs/<file>: the tail is what the mirror holds.
A path that reads through some other layout resolves to a file the mirror
does not have, and resolveSpecPath says so rather than failing later.
aws convert reads the spec-mirror Smithy models, applies the typed
patches/{sdkId}.json config (applyAwsSpecPatches), copies
partitions.json, and writes .generated-specs/<sdkId>.json. generate
compiles those and reads the patch file only for errorCategories. cloudflare
takes its spec root as a --specs flag whose default is the mirror path, and
resolves it through resolveSpecPath like everything else.
fly-io is the one package that reads more than its mirror: specs/sprites,
specs/mpg and specs/addons are small hand-maintained documents committed
next to the submodule, because Fly publishes nothing to mirror for them.
© alchemy-run, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .agents/skills/distilled-sdk of alchemy-run/distilled.
Open the folder on GitHubat commit 4406d5f
Distilled SDK next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Distilled SDK this skillalchemy-run/distilled | 431 | — | ~6k | Automated safety check: Pass | Apache-2.0 | |
| API DesignMadAppGang/claude-code | 284 | — | ~1.7k | Automated safety check: Pass | MIT | |
| API Documentermajiayu000/claude-skill-registry | 666 | 1 repos | ~767 | Automated safety check: Pass | MIT | |
| API DesignerJeffallan/claude-skills | 12k | 2 repos | ~2k | Automated safety check: Pass | MIT | |
| VibexRaja0sama/vibex | 388 | — | ~7.8k | Automated safety check: Pass | MIT | |
| SpikardGoldziher/spikard | 123 | — | ~799 | Automated safety check: Pass | MIT |
MadAppGang/claude-code
A skill your agent uses when designing REST or GraphQL APIs, defining endpoints, implementing pagination/filtering, handling API versioning, or establishing API documentation with OpenAPI/Swagger.
majiayu000/claude-skill-registry
API documentation specialist who creates comprehensive OpenAPI/Swagger specifications and technical documentation for RESTful APIs, GraphQL schemas, and microservices architectures.
Jeffallan/claude-skills
Designs REST and GraphQL APIs from resource modeling to an OpenAPI 3.1 contract, with versioning, pagination and RFC 7807 error handling.
Raja0sama/vibex
Diagrams and checkable docs from a codebase. An agent skill from Raja0sama/vibex.
Goldziher/spikard
Scaffold Spikard projects and generate code from OpenAPI, AsyncAPI, OpenRPC, GraphQL, and Protobuf schemas using the Spikard CLI or its MCP server.
jeremyosih/pi-executor
Load this skill before using the execute tool. An agent skill from jeremyosih/pi-executor.
alchemy-run/distilled
Add or change a patch in packages/<pkg/patches/ to correct a distilled SDK's upstream spec — a missing error response or typed error (status, code, message, body or header matchers), a field that…
alchemy-run/distilled
Move an existing distilled SDK to its mirror's latest spec, regenerate it, audit packages/<pkg/patches/ with pnpm patches:audit and delete or slim the patches the new spec has absorbed, then open…
Categories
Build or update a distilled SDK for an API provider — sourcing its OpenAPI/Smithy/GraphQL/discovery description, adding the spec mirror that feeds it, generating packages/<provider, listing it on…. Distilled SDK is an agent skill from alchemy-run/distilled.cloud with a category and a logo, writing a README with a complete Effect example, opening a GitHub PR whose body includes that same example, and regenerating an existing one.
Distilled SDK fits situations like: create a distilled SDK for <provider; adding a provider; fixing a fetch-specs.ts; giving a provider a catalogue group.
Run `npx skills add alchemy-run/distilled --skill distilled-sdk -a claude-code`. Or copy the skill folder (.agents/skills/distilled-sdk in alchemy-run/distilled) into .claude/skills/distilled-sdk in your project. Claude Code loads it when a task matches its description.
Run `npx skills add alchemy-run/distilled --skill distilled-sdk -a codex`. Or copy the skill folder (.agents/skills/distilled-sdk in alchemy-run/distilled) into .agents/skills/distilled-sdk in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add alchemy-run/distilled --skill distilled-sdk -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/distilled-sdk, .gemini/skills/distilled-sdk, .github/skills/distilled-sdk and .opencode/skills/distilled-sdk in your project.
Going by SKILL.md and its folder, Distilled SDK needs the command-line tools its instructions call (pnpm, git, gh, npm and tsc).
SKILL.md names 1 domain. In commands or code: github.com; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Distilled SDK is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6k tokens (SKILL.md is roughly 24k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Distilled SDK: API Design (MadAppGang/claude-code, 284 stars), API Documenter (majiayu000/claude-skill-registry, 666 stars), API Designer (Jeffallan/claude-skills, 12k stars) and Vibex (Raja0sama/vibex, 388 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
alchemy-run (a GitHub organization) maintains it in alchemy-run/distilled, which has 431 GitHub stars. The repository holds 3 skills in this directory. The repository was last updated on October 7, 2026.
Source: alchemy-run/distilled on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.