API Skill Builder — bootstrap a vendor-API CLI + Claude skill with AI-safety baked in
This skill takes you from "I want to drive the <X> REST API safely from a Claude session" to a published alpha that:
- never lets the AI read the API credential,
- classifies every endpoint into a six-tier risk taxonomy and refuses to send a mutation without the matching operator-supplied flag,
- stores the credential in the strongest OS-native secret store available, with file fallback at mode
0600,
- writes an append-only audit log of every mutation,
- ships with cross-platform installers, MIT license, full doc set, CI, issue/discussion templates, and a tracked project-memory layer.
The canonical reference implementation is linode-api-skill at https://github.com/aditya-m-bharadwaj/linode-api-skill. Whenever this skill says "mirror the pattern", read the corresponding file there and adapt it. Do not invent variations of the safety contract — copy it.
Hard rules (non-negotiable for every generated skill)
When you generate a new <vendor>-api-skill repo, the resulting tool MUST satisfy all of these. If a target API makes one of them impossible (e.g. mandatory query-string auth), surface the conflict to the operator and stop — do not silently weaken the contract.
- The API credential never enters the AI's context. Not via env, not via
cat, not via argv, not via dialog stdout the AI can see. Entry path is <bin> setup (TTY-only, hidden prompt) or <bin> gui-setup (native OS password dialog, AI-runnable but renders out-of-band of any pipe the AI reads).
- Storage is OS-native first, file fallback at mode
0600 only. macOS Keychain → Linux Secret Service (libsecret) → file at ~/.<vendor>-api-skill/token (mode 0600, refuses to read if broader). Single-string-secret backends only — if the vendor uses a key+secret pair, store the pair as one URL-encoded or JSON blob in one keystore entry.
- Verify before store. A new credential is validated against a known-good read endpoint (
whoami-equivalent for the API) BEFORE replacing whatever is already in the keystore. A bad credential never overwrites a working one.
- Six-tier safety classifier with required-flag matrix. Every mutation passes through
classify(method, path) which returns one of: read, mutating, destructive, billable, financial, privilege. The CLI itself enforces the required flags; the matrix is the same as linode-api-skill's. Unrecognized endpoints fall through to the strictest applicable default (GET → read, DELETE → destructive, anything else → mutating).
- Append-only audit log of every mutation.
~/.<vendor>-api-skill/audit.log, mode 0600, one JSON line per mutation, with timestamp / user / action / target / parameter metadata — never the token, never request body values, never generated secrets.
- Path / argument hardening. Validate paths against a tight regex; reject
../. traversal segments and URL-encoded %-sequences pre-classification. Refuse --body @file paths that resolve inside the config dir. Strip a leading API-version prefix (e.g. /v4, /v3) at the boundary if the docs canonically include it.
- Stdlib-only Python, single file.
bin/<vendor>-api-skill is one Python 3.8+ file using only the stdlib. No requests, no click, no pydantic, no keyring. This eliminates supply-chain risk on a privileged tool.
- Every mutation needs human confirmation in chat AND the machine
--yes flag. --yes alone is never enough; the AI must obtain explicit human confirmation in chat before sending.
- MIT license, AI-authorship disclosure, commit-trailer convention.
AUTHORS.md discloses the model that wrote the code. Commits carry Prompted-By: <operator> + Co-Authored-By: <model> trailers. See linode-api-skill/CONTRIBUTING.md for the exact format.
- Threat model documented.
SECURITY.md lists what the tool defends and what it does NOT defend; reporting goes through GitHub private security advisories.
The build process
Follow these steps in order. Do not skip "in-flight" steps to land a partial alpha — the safety contract is most useful when complete.
Step 0 — Confirm scope with the operator
Before writing code, agree on:
- API name and slug. Slug is
<vendor>-api-skill (kebab-case, no doubling). Used as the binary name, repo name, and Claude skill name: field.
- Auth model. Bearer token?
apikey + secret pair? OAuth? HMAC-signed requests? This shapes the token storage and gui-setup flow.
- API base URL and version. e.g.
https://api.linode.com/v4, https://api.porkbun.com/api/json/v3, etc. Note whether the canonical docs paths include the version segment (you'll want to strip it at the boundary if so — see _strip_v4 in linode-api-skill).
- The "billable" endpoints. Which paths cost real money when called? Get a list from the operator or the vendor's pricing page. These are the ones that need
--i-understand-billing.
- The "financial" and "privilege" prefixes (if any). Some APIs don't have these tiers — that's fine; leave the prefix lists empty.
Step 1 — Init the repo
mkdir ~/Code/<slug>
cd ~/Code/<slug>
git init -b main
Drop in the LICENSE (MIT, copyright " contributors"), .gitignore (mirror linode-api-skill), and the dir skeleton:
.claude/
├── skills/<slug>/SKILL.md ← the runtime skill (will be authored after the CLI exists)
├── commands/{resume,save}.md ← project-level slash commands
└── settings.json ← graphify PreToolUse hook (installed by `graphify claude install`)
.github/
├── workflows/{ci,codeql}.yml
├── ISSUE_TEMPLATE/{bug_report,feature_request,config}.{md,yml}
├── DISCUSSION_TEMPLATE/{q-and-a,ideas,show-and-tell}.yml
└── FUNDING.yml
bin/<slug> ← the CLI (Python 3.8+, stdlib only)
tests/test_classify.py ← offline classifier tests
docs/
├── README.md
├── progress/{TEMPLATE.md,YYYY-MM-DD-initial-alpha.md}
├── decisions/{TEMPLATE.md,NNNN-*.md}
└── settings.local.json.template ← harness deny-rules template
install.sh
install.ps1
AUTHORS.md
CHANGELOG.md
CLAUDE.md
CONTRIBUTING.md
LICENSE
README.md
SECURITY.md
Step 2 — Design the safety classifier
Author the classifier table in bin/<slug> with five collections (mirror linode-api-skill line ~620+):
_FINANCIAL_PREFIXES — tuple of path prefixes under which any non-GET is financial. Leave empty () if the API has no money-movement endpoints.
_PRIVILEGE_PREFIXES — tuple of path prefixes under which any non-GET is privilege (token/user/oauth management).
_BILLABLE_EXACT — set of (METHOD, normalized_path) tuples that allocate a paid resource.
_MUTATING_EXACT — set of (METHOD, normalized_path) for explicitly-mutating-but-free endpoints (clearer than relying on the default).
_DESTRUCTIVE_EXACT — optional, for non-DELETE destructive endpoints (e.g. POST /…/disable).
Implement _normalize_path(path) — strips the API-version prefix if applicable, then maps numeric segments to {id} for table lookup. Implement classify(method, path) returning (tier, [required_flags], explanation).
Test the classifier offline for every entry. Cross-check each _BILLABLE_EXACT entry against the vendor's pricing page — over-cautious is safe; under-cautious is dangerous.
Step 3 — Implement token storage
Mirror the storage layer from linode-api-skill (look for _kc_get_macos, _kc_set_macos, _kc_get_linux, _file_get, _file_set):
- macOS:
security add-generic-password -U (in-place update, no delete-then-add race).
- Linux:
secret-tool store / secret-tool lookup (libsecret). Falls through to file if not present.
- Windows / fallback: file at
~/.<slug>/token, mode 0600. Refuse to read if POSIX mode is broader than 0600.
For multi-part credentials (apikey + secret), serialize as one JSON blob before storage so each backend stays a single-string store. Encode/decode at the boundary; the rest of the CLI sees one opaque string and decodes inside _request.
Step 4 — Implement gui-setup and setup
setup reads via getpass (hidden prompt) from a TTY. AI agents cannot run this. The install script's curl … | sh invocation re-attaches stdin to /dev/tty so it still works when invoked through a pipe.
gui-setup pops a native OS password dialog:
- macOS:
osascript with display dialog "..." with hidden answer.
- Linux: try
zenity --password, fall back to kdialog --password.
- Windows: PowerShell
Get-Credential (returns a SecureString; convert to plain inside the PS one-liner and emit to stdout).
- The dialog process's stdout is captured into Python memory via
subprocess.run(..., capture_output=True). The token bytes never re-enter stdout that any pipe / observer can see.
- Validate the captured token by calling the API's
whoami-equivalent BEFORE writing it to the keystore (Step 3 contract).
- On rotation: tell the user to revoke the old token at the vendor's dashboard. Local replacement does not invalidate the old token server-side.
Step 5 — Implement named commands + generic api gateway
- Named commands for the most common, highest-value workflows. For a domain registrar (Porkbun), this is
domains, dns-list <domain>, dns-create, dns-delete <id> --confirm-id, pricing, etc. Each named command performs the classification check inline and audits on success.
- Generic
api command for full API coverage: <slug> api <METHOD> <path> [--data …] [--body @file] [--query k=v] [--paginate] [--dry-run] [--json] [--yes] [--confirm-id …] [--i-understand-billing] [--allow-financial] [--allow-privilege]. Filter out Authorization-header overrides in --header-style flags. Refuse --body @file paths that resolve inside the config dir.
- Audit-log every non-read call after success (or after a 4xx that did mutation work).
Step 6 — Write tests
tests/test_classify.py loads bin/<slug> via importlib.machinery.SourceFileLoader (no install needed) and exercises:
- One test per
_BILLABLE_EXACT / _FINANCIAL_PREFIXES / _PRIVILEGE_PREFIXES entry — assert classification and flags.
_normalize_path: numeric segments → {id}, string segments preserved, version-prefix stripping, edge cases (/v40/foo and /foo/v4/bar should NOT be stripped).
_validate_path: traversal rejection, URL-encoded % rejection, ASCII path-char whitelist.
- Method case-insensitivity.
- Default fallback: unknown GET → read, unknown DELETE → destructive, anything else → mutating.
- Platform helpers (
_has_display, _platform).
Aim for ≥25 tests; ≥30 once you've covered all classifier-table entries.
Step 7 — Author the runtime SKILL.md
Author .claude/skills/<slug>/SKILL.md. Use linode-api-skill/.claude/skills/linode-api-skill/SKILL.md as the template. Sections to mirror:
- Frontmatter (
name, description).
- Hard rules.
- How the safety classifier works (the same six-tier table).
- Named commands table.
- Generic
api gateway examples for each tier.
- Resource-category checklist (one row per major API category — what to confirm with the user before calling).
- Token management workflows (add, rotate, remove, diagnose).
- Workflow recipes (the 4–8 most common multi-step operations for this vendor).
- Things you should NOT do.
- When something goes wrong (error → action table).
Step 8 — Author the supporting docs
For each, copy the corresponding file from linode-api-skill and rewrite for the new vendor:
README.md — install one-liners, token management, classifier reference, common workflows, file map, uninstall. Status banner at top: Status: v0.1.0-alpha.1.
AUTHORS.md — AI-authorship attribution + AI-generated-code disclaimer.
SECURITY.md — threat model: defended vs. not-defended, hardening recommendations, private-advisory reporting link.
CONTRIBUTING.md — stdlib-only rule, classifier-extension procedure, test pattern, full commit-message format (Prompted-By + Co-Authored-By trailers).
CHANGELOG.md — one [0.1.0-alpha.1] entry describing the alpha; future work goes under [Unreleased].
CLAUDE.md — project-level hard rules for any AI agent in the repo. Defers to .claude/skills/<slug>/SKILL.md as authoritative.
Step 9 — .github/ metadata
Copy from linode-api-skill/.github/:
workflows/ci.yml — 3 OS × Python 3.8–3.12 matrix (exclude macOS 3.8/3.9 — runner images don't ship them). Steps: py_compile, unittest discover tests, smoke <slug> classify on a read and a destructive path. Separate shellcheck install.sh job on Ubuntu.
workflows/codeql.yml — GitHub's CodeQL static analysis. Matrix over python (the CLI is Python; rules catch path-traversal, injection, weak crypto, etc.) and actions (lints the workflow files themselves for token-scope and untrusted-input issues). Triggers on push to main, PR to main, and a weekly cron. build-mode: none — the CLI is stdlib-only so there's nothing to compile. Pair with "Code scanning" enabled in repo settings (Step 14). The canonical template lives in this skill's own repo at APIskillBuilderSkill/.github/workflows/codeql.yml; for a generated vendor skill, copy it and add python alongside actions in the language matrix.
ISSUE_TEMPLATE/{bug_report,feature_request}.md and config.yml — config.yml disables blank issues and points contact links at the private security advisory page, SECURITY.md, and Discussions.
DISCUSSION_TEMPLATE/{q-and-a,ideas,show-and-tell}.yml.
FUNDING.yml — fill in github: [<operator-username>]; comment out the rest.
Step 10 — Memory layer
Set up the same docs/ layer that linode-api-skill uses:
docs/README.md — human-facing index of the memory layer.
docs/progress/TEMPLATE.md and docs/progress/YYYY-MM-DD-initial-alpha.md — seed entry covering this scaffolding session.
docs/decisions/TEMPLATE.md and ADRs 0001-N. At minimum write:
0001-stdlib-only-python.md — why no requests/click/keyring.
0002-safety-classifier-six-tiers.md — the tier taxonomy.
0003-cross-platform-token-storage.md — backend selection.
0004-ai-safe-token-entry-gui-dialog.md — the out-of-band entry pattern.
0005-prompted-by-trailer-convention.md — commit-trailer rationale.
0006-monolithic-cli-file.md — single-file rationale.
0007-versioning-semver.md — SemVer with -alpha.N.
- One ADR per vendor-specific decision (auth flow, version-prefix, billable-list source).
docs/settings.local.json.template — harness deny-rules template using ~/ paths (NOT /Users/<operator>).
Step 11 — Project-level slash commands + graphify
.claude/commands/resume.md — reads docs/progress/ (most recent), graphify-out/GRAPH_REPORT.md, three most recent docs/decisions/. Adapt from linode-api-skill/.claude/commands/resume.md.
.claude/commands/save.md — writes a new docs/progress/YYYY-MM-DD-<slug>.md, optionally creates an ADR, routes reusable cross-project concepts to ~/.claude/vault/zettel/concepts/. Adapt from linode-api-skill/.claude/commands/save.md.
- After at least one code file exists, run
graphify update . once to populate graphify-out/. Install the post-commit hook: graphify hook install. Install the Claude PreToolUse hook: graphify claude install (writes .claude/settings.json).
Step 12 — Install scripts
install.sh (POSIX) — verify Python 3.8+, clone the repo (or use existing checkout), symlink bin/<slug> into ${LINODE_CTL_PREFIX:-~/.local/bin} (rename the env var to match the vendor slug). Optionally install the Claude skill into ~/.claude/skills/<slug>/. Re-attach stdin to /dev/tty for the optional immediate-setup prompt so curl … | sh still works.
install.ps1 (Windows) — Python check, .cmd shim on PATH, icacls-locked-down ACL for the file fallback if used.
- Verify with
shellcheck install.sh before commit.
Step 13 — Initial commit
One commit with the entire alpha. Subject form: feat: initial public alpha (v0.1.0-alpha.1). Body: 1-sentence summary + bullet list of every major surface (CLI commands, classifier tiers, storage backends, install scripts, skill, docs). Trailers:
Prompted-By: <operator name> <operator email> # from `git config user.name`/`user.email`
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> # actual model id you are running as
Do NOT bypass hooks (--no-verify, --no-gpg-sign). Do NOT commit if the operator has not explicitly told you to — draft the message and stop.
Step 14 — Push and configure the GitHub repo
After the operator authorizes the push and the commit lands on origin/main, configure the GitHub repo. These settings are the alpha default; the operator can tighten them later (e.g. add branch protection) once the project has external contributors.
About panel (top of the repo page):
- Description — one sentence covering what the tool does and the safety pitch. Form: "
<vendor>-api-skill — a safe, single-file Python CLI mediating the <Vendor> API, plus a matching Claude skill. Credential never enters AI context; every mutation is classifier-gated and audit-logged."
- Website — leave blank, or point to the canonical reference (
https://github.com/aditya-m-bharadwaj/linode-api-skill).
- Topics — pick ~12 from:
claude-code, claude-skill, claude-agent-sdk, ai-safety, ai-agents, api-wrapper, cli, python, <vendor> (e.g. linode, porkbun), <vendor>-api, credential-management, audit-log, safety-classifier.
Features (Settings → General → Features):
| Feature |
Default |
Why |
| Issues |
on |
issue templates ship in .github/ISSUE_TEMPLATE/ |
| Discussions |
on |
discussion templates ship in .github/DISCUSSION_TEMPLATE/ |
| Wikis |
on |
enabled by default; the in-repo docs/ memory layer is canonical, but leaving wikis on costs nothing and gives external contributors a low-friction surface |
| Projects |
on |
enabled by default; same reasoning — no cost, optional surface for issue triage |
| Sponsorships |
on |
.github/FUNDING.yml is in place |
| Preserve this repository |
on if eligible (Arctic Code Vault opt-in) |
free, no downside |
Pull Requests (Settings → General → Pull Requests):
- Allow squash merging: on.
- Allow merge commits / rebase merging: off for a solo alpha.
- Always suggest updating PR branches: on.
- Allow auto-merge: on.
- Automatically delete head branches: on.
Code security (Settings → Code security):
- Private vulnerability reporting: enable — this is what
SECURITY.md and ISSUE_TEMPLATE/config.yml direct reporters to.
- Dependabot alerts: enable — catches
actions/* version bumps even though the CLI has no Python deps.
- Dependabot security updates: enable.
- Secret scanning (free on public repos): enable.
- Push protection (blocks pushes containing detected secrets): enable.
- Code scanning (CodeQL): enable. The workflow ships at
.github/workflows/codeql.yml (Step 9) and analyzes the Python CLI and the workflow files themselves. Even on a stdlib-only single-file CLI, CodeQL catches path-traversal, command-injection, weak-crypto, and untrusted-input-into-action patterns that the classifier doesn't see. The weekly cron also re-scans against updated rule packs.
Branch protection (Settings → Branches):
- For a solo v0.x alpha: skip. The operator pushes directly to
main per the established pattern.
- Revisit once external contributors arrive: enable "Require a pull request before merging" + "Require status checks to pass" (CI green) on
main.
Pages / Webhooks / Actions secrets: leave as default. No docs site is published; no secrets are needed by CI.
Bootstrap the wiki. The wiki is a separate git repository (<repo>.wiki.git) that GitHub provisions only after a first page is saved through the web UI. You (the AI) cannot create this repo via gh or any other automated path — gh repo create <slug>.wiki fails with "The repository <slug>.wiki cannot end in .wiki" because GitHub reserves the .wiki suffix for auto-provisioned wiki repos. Cloning <slug>.wiki.git returns "Repository not found" until the operator has saved a page.
→ Stop here and ask the operator to bootstrap the wiki manually. Direct them to:
- Open
https://github.com/<owner>/<slug>/wiki in a browser.
- Click "Create the first page" and save anything (the default "Welcome to the wiki!" body is fine — it will be overwritten in a moment).
- Confirm back to you that the page saved.
Only after the operator confirms can you proceed. Then clone the now-existing wiki repo, replay starter pages over GitHub's placeholder, and push:
# In the project root, AFTER the operator has saved the first wiki page:
git clone https://github.com/<owner>/<slug>.wiki.git wiki
echo 'wiki/' >> .gitignore # main repo never tracks the nested .git
cd wiki
# GitHub-provisioned wikis default to the `master` branch, not `main`.
# Don't fight it; commit on master so push succeeds without surgery.
# Author / copy in the starter pages (this overwrites GitHub's auto Home.md):
# ... write Home.md, _Sidebar.md, _Footer.md, Getting-Started.md, etc.
git add -A
git commit -m "wiki: initial seed (v0.1.0-alpha.1 surface)"
git push -u origin master
The minimum-viable starter page set (mirror the APIskillBuilderSkill wiki):
Home.md — landing page with the safety pitch in one paragraph + navigation.
_Sidebar.md — right-side nav (Concepts / Reference / Contribute / Links).
_Footer.md — short repo / license footer.
Getting-Started.md — install, prerequisites, first invocation.
How-It-Works.md — conceptual overview of how the CLI + runtime skill relate.
Hard-Rules.md — annotated version of the safety contract.
Six-Tier-Safety-Classifier.md — tier table + classification logic.
Build-Process.md — annotated build/use steps if relevant for downstream users.
ADR-Index.md — one-line summaries linking to each docs/decisions/<NNNN>-*.md file.
FAQ.md — open questions and deliberate non-goals.
Roadmap.md — near / medium / long-term + explicitly out-of-scope items.
Contributing-to-the-Wiki.md — clone-edit-push workflow + page conventions.
Page conventions: filenames are Page-Name.md at the wiki root (no subdirectories — GitHub wikis are flat); internal links use [[Page Name]]; links from wiki to the main repo use absolute https://github.com/<owner>/<slug>/blob/main/... URLs because the two repos are separate. Wiki pages summarize and link to canonical content in docs/; they do not replace it. When the wiki and docs/ disagree, docs/ wins.
Add a "Contributing to the wiki" section to the generated project's CONTRIBUTING.md describing the local workflow and the docs/ vs. wiki split — so a future contributor knows how to clone, edit, push, and where canonical content lives.
Run this step right after the first push lands. The default GitHub presets for a brand-new repo are not what the safety contract assumes — at minimum, private vulnerability reporting must be on (or SECURITY.md's instructions are a dead link), and the description / topics must be set so users discover the project at all.
After configuring, paste the live About-panel description and the comma-separated topic list back to the operator for verification.
Step 15 — Live smoke test (if a token is available)
If the operator provides a scoped test credential, exercise the alpha end-to-end before declaring v0.1 done:
- Install via the published
curl … | sh one-liner against a sandbox prefix (LINODE_CTL_PREFIX=/tmp/...-style) so you don't pollute the operator's real install.
<slug> gui-setup via the OS dialog. Confirm whoami works.
- Inventory all existing resources and record them so you can verify nothing got touched.
- Exercise one workflow per tier:
read — list a resource type.
mutating — a reversible change (e.g. toggle a setting).
destructive — delete a test-tagged resource only.
billable — create the cheapest possible resource, tagged for cleanup.
financial / privilege — --dry-run only, do not actually call.
- Verify the safety contract: try a billable call without
--i-understand-billing (expect refusal); try a destructive call with mismatched --confirm-id (expect refusal).
- Clean up all test-tagged resources via the named cleanup command.
- Confirm pre-existing resources are untouched.
- Review the audit log — every mutation should appear, no secrets should appear.
Any defects surfaced by the smoke test are blockers for v0.1, not follow-up issues. Land the fixes (with tests and the corresponding CHANGELOG / SKILL.md / docs updates in the same commit) before tagging.
Safety guarantees the generated skill MUST provide
A reader looking at the generated repo for the first time should be able to verify the following by reading the code, with no external trust:
- The token is never in argv. Search
bin/<slug> for the token variable name — it should only appear in: storage read/write helpers, the Authorization header construction, and the verify-before-store call.
- The token is never in stdout / stderr / log files / audit entries. Search for the token variable name in
print, _emit, _audit, logger, etc. — should appear in none.
- The OS-dialog stdout is captured into Python memory by
subprocess.run(..., capture_output=True) and never re-printed. The CLI prints success metadata (Authenticated as: <username>) and nothing else from that call.
- Every mutation is guarded by an
if not args.yes: raise CtlError(...) check OR the generic api command's flag-check loop. There are no code paths that POST/PUT/PATCH/DELETE without classification + flag gating.
- The audit log only records body keys for generic
api calls — not values. Grep _audit(...) calls; the only body_keys=... shape is keys-only.
Things you should NOT do
- Do not import third-party Python packages. Stdlib only. Even
keyring is out — the CLI's storage layer is intentionally re-implemented against the OS-native CLIs (security, secret-tool, cmdkey).
- Do not support an
LINODE_TOKEN-style environment variable as the primary credential path. The operator can opt-in via documented escape hatches, but the default is keystore-only.
- Do not invent a "lighter" safety classifier with fewer tiers because the target API "seems mostly free". Even free APIs have privilege escalation (issuing scoped tokens), and the tier system is the operator's mental model — don't change it across vendors.
- Do not add a fancy interactive prompt for token entry beyond
setup (TTY) and gui-setup (OS dialog). No browser-based OAuth flows, no QR codes, no expect scripts. The TTY/dialog out-of-band requirement is the contract.
- Do not auto-classify unrecognized endpoints as
read. The default fallback for non-GET is mutating; for DELETE, destructive. Conservative is correct.
- Do not publish to PyPI yet. The default install path is the one-liner
curl … | sh from raw.githubusercontent.com, which can't smuggle in a dependency. If PyPI publishing is added later, switch the version string to PEP 440 (0.1.0a1 instead of 0.1.0-alpha.1).
- Do not create the generated repo as a "monorepo" of multiple vendors. One vendor = one repo. The skill is independent per vendor; cross-vendor sharing happens at the doc level (this skill).
Reference implementation
https://github.com/aditya-m-bharadwaj/linode-api-skill is the canonical implementation. When in doubt, read the corresponding file there. Specifically:
| Concern |
File |
| Safety classifier |
bin/linode-api-skill lines ~620–740 |
| Token storage |
bin/linode-api-skill _kc_* and _file_* helpers |
| GUI dialog |
bin/linode-api-skill _has_display, _prompt_token_gui, _confirm_gui |
| Audit log |
bin/linode-api-skill _audit |
| Verify-before-store |
cmd_setup and cmd_gui_setup (call /profile before persisting) |
| Path validation |
PATH_RE and _validate_path |
| Header / body hardening |
_request (filters Authorization); _load_body (refuses paths under CONFIG_DIR) |
| Skill template |
.claude/skills/linode-api-skill/SKILL.md |
| Memory layer |
docs/ and .claude/commands/{resume,save}.md |
| CI |
.github/workflows/ci.yml |
| Install scripts |
install.sh, install.ps1 |
| Commit-trailer convention |
CONTRIBUTING.md §"Commit message format" |
Open issues / things the build process does not yet automate
These are honest gaps you should surface to the operator rather than fake:
- Pricing introspection. This skill assumes you'll get the billable-endpoint list from a human or the vendor's pricing page. There's no automatic mapping from an OpenAPI spec to "this endpoint costs money".
- OAuth-flow APIs. The
setup / gui-setup pattern assumes a paste-once-and-store credential. For APIs requiring OAuth dance + refresh tokens, you'll need additional design (similar in spirit to gh auth login but adapted).
- Live smoke testing across all tiers. Step 14 prescribes the methodology but the actual recipe is per-vendor — you need to know which resource is cheapest, which can be safely deleted, etc.
- Generator tooling. This skill is markdown that guides an AI to build. There is no
api-skill-builder scaffold <slug> <spec.json> CLI. Building one is reasonable future work; for now, follow the steps by hand.
1---2name: api-skill-builder3description: Bootstrap a new "<vendor>-api-skill" project — a safe, cross-platform single-file Python CLI that mediates a third-party REST API, plus a matching Claude skill that drives it under explicit AI-safety constraints (token never enters AI context; every mutation passes through a six-tier safety classifier; every mutation is recorded in a local audit log). Trigger when the user asks to "build a CLI / skill for <some API>", "wrap the <X> API safely", or to mirror the linode-api-skill pattern for a new vendor.4---56# API Skill Builder — bootstrap a vendor-API CLI + Claude skill with AI-safety baked in78This skill takes you from "I want to drive the <X> REST API safely from a Claude session" to a published alpha that:910- never lets the AI read the API credential,11- classifies every endpoint into a six-tier risk taxonomy and refuses to send a mutation without the matching operator-supplied flag,12- stores the credential in the strongest OS-native secret store available, with file fallback at mode `0600`,13- writes an append-only audit log of every mutation,14- ships with cross-platform installers, MIT license, full doc set, CI, issue/discussion templates, and a tracked project-memory layer.1516The canonical reference implementation is **`linode-api-skill`** at <https://github.com/aditya-m-bharadwaj/linode-api-skill>. Whenever this skill says "mirror the pattern", read the corresponding file there and adapt it. Do not invent variations of the safety contract — copy it.1718## Hard rules (non-negotiable for every generated skill)1920When you generate a new `<vendor>-api-skill` repo, the resulting tool MUST satisfy all of these. If a target API makes one of them impossible (e.g. mandatory query-string auth), surface the conflict to the operator and stop — do not silently weaken the contract.21221. **The API credential never enters the AI's context.** Not via env, not via `cat`, not via argv, not via dialog stdout the AI can see. Entry path is `<bin> setup` (TTY-only, hidden prompt) or `<bin> gui-setup` (native OS password dialog, AI-runnable but renders out-of-band of any pipe the AI reads).232. **Storage is OS-native first, file fallback at mode `0600` only.** macOS Keychain → Linux Secret Service (libsecret) → file at `~/.<vendor>-api-skill/token` (mode `0600`, refuses to read if broader). Single-string-secret backends only — if the vendor uses a key+secret pair, store the pair as one URL-encoded or JSON blob in one keystore entry.243. **Verify before store.** A new credential is validated against a known-good read endpoint (`whoami`-equivalent for the API) BEFORE replacing whatever is already in the keystore. A bad credential never overwrites a working one.254. **Six-tier safety classifier with required-flag matrix.** Every mutation passes through `classify(method, path)` which returns one of: `read`, `mutating`, `destructive`, `billable`, `financial`, `privilege`. The CLI itself enforces the required flags; the matrix is the same as `linode-api-skill`'s. Unrecognized endpoints fall through to the strictest applicable default (GET → read, DELETE → destructive, anything else → mutating).265. **Append-only audit log of every mutation.** `~/.<vendor>-api-skill/audit.log`, mode `0600`, one JSON line per mutation, with timestamp / user / action / target / parameter metadata — **never** the token, never request body values, never generated secrets.276. **Path / argument hardening.** Validate paths against a tight regex; reject `..`/`.` traversal segments and URL-encoded `%`-sequences pre-classification. Refuse `--body @file` paths that resolve inside the config dir. Strip a leading API-version prefix (e.g. `/v4`, `/v3`) at the boundary if the docs canonically include it.287. **Stdlib-only Python, single file.** `bin/<vendor>-api-skill` is one Python 3.8+ file using only the stdlib. No `requests`, no `click`, no `pydantic`, no `keyring`. This eliminates supply-chain risk on a privileged tool.298. **Every mutation needs human confirmation in chat AND the machine `--yes` flag.** `--yes` alone is never enough; the AI must obtain explicit human confirmation in chat before sending.309. **MIT license, AI-authorship disclosure, commit-trailer convention.** `AUTHORS.md` discloses the model that wrote the code. Commits carry `Prompted-By: <operator>` + `Co-Authored-By: <model>` trailers. See `linode-api-skill/CONTRIBUTING.md` for the exact format.3110. **Threat model documented.** `SECURITY.md` lists what the tool defends and what it does NOT defend; reporting goes through GitHub private security advisories.3233## The build process3435Follow these steps in order. Do not skip "in-flight" steps to land a partial alpha — the safety contract is most useful when complete.3637### Step 0 — Confirm scope with the operator3839Before writing code, agree on:4041- **API name and slug.** Slug is `<vendor>-api-skill` (kebab-case, no doubling). Used as the binary name, repo name, and Claude skill `name:` field.42- **Auth model.** Bearer token? `apikey` + `secret` pair? OAuth? HMAC-signed requests? This shapes the token storage and `gui-setup` flow.43- **API base URL and version.** e.g. `https://api.linode.com/v4`, `https://api.porkbun.com/api/json/v3`, etc. Note whether the canonical docs paths include the version segment (you'll want to strip it at the boundary if so — see `_strip_v4` in `linode-api-skill`).44- **The "billable" endpoints.** Which paths cost real money when called? Get a list from the operator or the vendor's pricing page. These are the ones that need `--i-understand-billing`.45- **The "financial" and "privilege" prefixes** (if any). Some APIs don't have these tiers — that's fine; leave the prefix lists empty.4647### Step 1 — Init the repo4849```50mkdir ~/Code/<slug>51cd ~/Code/<slug>52git init -b main53```5455Drop in the LICENSE (MIT, copyright "<slug> contributors"), `.gitignore` (mirror `linode-api-skill`), and the dir skeleton:5657```58.claude/59├── skills/<slug>/SKILL.md ← the runtime skill (will be authored after the CLI exists)60├── commands/{resume,save}.md ← project-level slash commands61└── settings.json ← graphify PreToolUse hook (installed by `graphify claude install`)6263.github/64├── workflows/{ci,codeql}.yml65├── ISSUE_TEMPLATE/{bug_report,feature_request,config}.{md,yml}66├── DISCUSSION_TEMPLATE/{q-and-a,ideas,show-and-tell}.yml67└── FUNDING.yml6869bin/<slug> ← the CLI (Python 3.8+, stdlib only)70tests/test_classify.py ← offline classifier tests71docs/72├── README.md73├── progress/{TEMPLATE.md,YYYY-MM-DD-initial-alpha.md}74├── decisions/{TEMPLATE.md,NNNN-*.md}75└── settings.local.json.template ← harness deny-rules template7677install.sh78install.ps17980AUTHORS.md81CHANGELOG.md82CLAUDE.md83CONTRIBUTING.md84LICENSE85README.md86SECURITY.md87```8889### Step 2 — Design the safety classifier9091Author the classifier table in `bin/<slug>` with five collections (mirror `linode-api-skill` line ~620+):9293- `_FINANCIAL_PREFIXES` — tuple of path prefixes under which any non-GET is `financial`. Leave empty `()` if the API has no money-movement endpoints.94- `_PRIVILEGE_PREFIXES` — tuple of path prefixes under which any non-GET is `privilege` (token/user/oauth management).95- `_BILLABLE_EXACT` — set of `(METHOD, normalized_path)` tuples that allocate a paid resource.96- `_MUTATING_EXACT` — set of `(METHOD, normalized_path)` for explicitly-mutating-but-free endpoints (clearer than relying on the default).97- `_DESTRUCTIVE_EXACT` — optional, for non-DELETE destructive endpoints (e.g. `POST /…/disable`).9899Implement `_normalize_path(path)` — strips the API-version prefix if applicable, then maps numeric segments to `{id}` for table lookup. Implement `classify(method, path)` returning `(tier, [required_flags], explanation)`.100101**Test the classifier offline** for every entry. Cross-check each `_BILLABLE_EXACT` entry against the vendor's pricing page — over-cautious is safe; under-cautious is dangerous.102103### Step 3 — Implement token storage104105Mirror the storage layer from `linode-api-skill` (look for `_kc_get_macos`, `_kc_set_macos`, `_kc_get_linux`, `_file_get`, `_file_set`):106107- macOS: `security add-generic-password -U` (in-place update, no delete-then-add race).108- Linux: `secret-tool store` / `secret-tool lookup` (libsecret). Falls through to file if not present.109- Windows / fallback: file at `~/.<slug>/token`, mode `0600`. **Refuse to read** if POSIX mode is broader than `0600`.110111For multi-part credentials (apikey + secret), serialize as one JSON blob before storage so each backend stays a single-string store. Encode/decode at the boundary; the rest of the CLI sees one opaque string and decodes inside `_request`.112113### Step 4 — Implement `gui-setup` and `setup`114115- `setup` reads via `getpass` (hidden prompt) from a TTY. AI agents cannot run this. The install script's `curl … | sh` invocation re-attaches stdin to `/dev/tty` so it still works when invoked through a pipe.116- `gui-setup` pops a native OS password dialog:117 - macOS: `osascript` with `display dialog "..." with hidden answer`.118 - Linux: try `zenity --password`, fall back to `kdialog --password`.119 - Windows: PowerShell `Get-Credential` (returns a `SecureString`; convert to plain inside the PS one-liner and emit to stdout).120- The dialog process's stdout is captured into Python memory via `subprocess.run(..., capture_output=True)`. The token bytes never re-enter stdout that any pipe / observer can see.121- Validate the captured token by calling the API's `whoami`-equivalent BEFORE writing it to the keystore (Step 3 contract).122- On rotation: tell the user to revoke the old token at the vendor's dashboard. **Local replacement does not invalidate the old token server-side.**123124### Step 5 — Implement named commands + generic `api` gateway125126- **Named commands** for the most common, highest-value workflows. For a domain registrar (Porkbun), this is `domains`, `dns-list <domain>`, `dns-create`, `dns-delete <id> --confirm-id`, `pricing`, etc. Each named command performs the classification check inline and audits on success.127- **Generic `api` command** for full API coverage: `<slug> api <METHOD> <path> [--data …] [--body @file] [--query k=v] [--paginate] [--dry-run] [--json] [--yes] [--confirm-id …] [--i-understand-billing] [--allow-financial] [--allow-privilege]`. Filter out `Authorization`-header overrides in `--header`-style flags. Refuse `--body @file` paths that resolve inside the config dir.128- Audit-log every non-read call after success (or after a 4xx that did mutation work).129130### Step 6 — Write tests131132`tests/test_classify.py` loads `bin/<slug>` via `importlib.machinery.SourceFileLoader` (no install needed) and exercises:133134- One test per `_BILLABLE_EXACT` / `_FINANCIAL_PREFIXES` / `_PRIVILEGE_PREFIXES` entry — assert classification and flags.135- `_normalize_path`: numeric segments → `{id}`, string segments preserved, version-prefix stripping, edge cases (`/v40/foo` and `/foo/v4/bar` should NOT be stripped).136- `_validate_path`: traversal rejection, URL-encoded `%` rejection, ASCII path-char whitelist.137- Method case-insensitivity.138- Default fallback: unknown GET → read, unknown DELETE → destructive, anything else → mutating.139- Platform helpers (`_has_display`, `_platform`).140141Aim for ≥25 tests; ≥30 once you've covered all classifier-table entries.142143### Step 7 — Author the runtime SKILL.md144145Author `.claude/skills/<slug>/SKILL.md`. Use `linode-api-skill/.claude/skills/linode-api-skill/SKILL.md` as the template. Sections to mirror:146147- Frontmatter (`name`, `description`).148- Hard rules.149- How the safety classifier works (the same six-tier table).150- Named commands table.151- Generic `api` gateway examples for each tier.152- Resource-category checklist (one row per major API category — what to confirm with the user before calling).153- Token management workflows (add, rotate, remove, diagnose).154- Workflow recipes (the 4–8 most common multi-step operations for this vendor).155- Things you should NOT do.156- When something goes wrong (error → action table).157158### Step 8 — Author the supporting docs159160For each, copy the corresponding file from `linode-api-skill` and rewrite for the new vendor:161162- `README.md` — install one-liners, token management, classifier reference, common workflows, file map, uninstall. Status banner at top: `Status: v0.1.0-alpha.1`.163- `AUTHORS.md` — AI-authorship attribution + AI-generated-code disclaimer.164- `SECURITY.md` — threat model: defended vs. not-defended, hardening recommendations, private-advisory reporting link.165- `CONTRIBUTING.md` — stdlib-only rule, classifier-extension procedure, test pattern, full commit-message format (Prompted-By + Co-Authored-By trailers).166- `CHANGELOG.md` — one `[0.1.0-alpha.1]` entry describing the alpha; future work goes under `[Unreleased]`.167- `CLAUDE.md` — project-level hard rules for any AI agent in the repo. Defers to `.claude/skills/<slug>/SKILL.md` as authoritative.168169### Step 9 — `.github/` metadata170171Copy from `linode-api-skill/.github/`:172173- `workflows/ci.yml` — 3 OS × Python 3.8–3.12 matrix (exclude macOS 3.8/3.9 — runner images don't ship them). Steps: `py_compile`, `unittest discover tests`, smoke `<slug> classify` on a read and a destructive path. Separate `shellcheck install.sh` job on Ubuntu.174- `workflows/codeql.yml` — GitHub's CodeQL static analysis. Matrix over `python` (the CLI is Python; rules catch path-traversal, injection, weak crypto, etc.) and `actions` (lints the workflow files themselves for token-scope and untrusted-input issues). Triggers on push to `main`, PR to `main`, and a weekly cron. `build-mode: none` — the CLI is stdlib-only so there's nothing to compile. Pair with "Code scanning" enabled in repo settings (Step 14). The canonical template lives in this skill's own repo at `APIskillBuilderSkill/.github/workflows/codeql.yml`; for a generated vendor skill, copy it and add `python` alongside `actions` in the language matrix.175- `ISSUE_TEMPLATE/{bug_report,feature_request}.md` and `config.yml` — `config.yml` disables blank issues and points contact links at the private security advisory page, `SECURITY.md`, and Discussions.176- `DISCUSSION_TEMPLATE/{q-and-a,ideas,show-and-tell}.yml`.177- `FUNDING.yml` — fill in `github: [<operator-username>]`; comment out the rest.178179### Step 10 — Memory layer180181Set up the same `docs/` layer that `linode-api-skill` uses:182183- `docs/README.md` — human-facing index of the memory layer.184- `docs/progress/TEMPLATE.md` and `docs/progress/YYYY-MM-DD-initial-alpha.md` — seed entry covering this scaffolding session.185- `docs/decisions/TEMPLATE.md` and ADRs `0001-N`. At minimum write:186 - `0001-stdlib-only-python.md` — why no requests/click/keyring.187 - `0002-safety-classifier-six-tiers.md` — the tier taxonomy.188 - `0003-cross-platform-token-storage.md` — backend selection.189 - `0004-ai-safe-token-entry-gui-dialog.md` — the out-of-band entry pattern.190 - `0005-prompted-by-trailer-convention.md` — commit-trailer rationale.191 - `0006-monolithic-cli-file.md` — single-file rationale.192 - `0007-versioning-semver.md` — SemVer with `-alpha.N`.193 - One ADR per vendor-specific decision (auth flow, version-prefix, billable-list source).194- `docs/settings.local.json.template` — harness deny-rules template using `~/` paths (NOT `/Users/<operator>`).195196### Step 11 — Project-level slash commands + graphify197198- `.claude/commands/resume.md` — reads `docs/progress/` (most recent), `graphify-out/GRAPH_REPORT.md`, three most recent `docs/decisions/`. Adapt from `linode-api-skill/.claude/commands/resume.md`.199- `.claude/commands/save.md` — writes a new `docs/progress/YYYY-MM-DD-<slug>.md`, optionally creates an ADR, routes reusable cross-project concepts to `~/.claude/vault/zettel/concepts/`. Adapt from `linode-api-skill/.claude/commands/save.md`.200- After at least one code file exists, run `graphify update .` once to populate `graphify-out/`. Install the post-commit hook: `graphify hook install`. Install the Claude PreToolUse hook: `graphify claude install` (writes `.claude/settings.json`).201202### Step 12 — Install scripts203204- `install.sh` (POSIX) — verify Python 3.8+, clone the repo (or use existing checkout), symlink `bin/<slug>` into `${LINODE_CTL_PREFIX:-~/.local/bin}` (rename the env var to match the vendor slug). Optionally install the Claude skill into `~/.claude/skills/<slug>/`. Re-attach stdin to `/dev/tty` for the optional immediate-setup prompt so `curl … | sh` still works.205- `install.ps1` (Windows) — Python check, `.cmd` shim on `PATH`, `icacls`-locked-down ACL for the file fallback if used.206- Verify with `shellcheck install.sh` before commit.207208### Step 13 — Initial commit209210One commit with the entire alpha. Subject form: `feat: initial public alpha (v0.1.0-alpha.1)`. Body: 1-sentence summary + bullet list of every major surface (CLI commands, classifier tiers, storage backends, install scripts, skill, docs). Trailers:211212```213Prompted-By: <operator name> <operator email> # from `git config user.name`/`user.email`214Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> # actual model id you are running as215```216217**Do NOT** bypass hooks (`--no-verify`, `--no-gpg-sign`). **Do NOT** commit if the operator has not explicitly told you to — draft the message and stop.218219### Step 14 — Push and configure the GitHub repo220221After the operator authorizes the push and the commit lands on `origin/main`, configure the GitHub repo. These settings are the alpha default; the operator can tighten them later (e.g. add branch protection) once the project has external contributors.222223**About panel** (top of the repo page):224225- **Description** — one sentence covering what the tool does and the safety pitch. Form: "`<vendor>-api-skill` — a safe, single-file Python CLI mediating the `<Vendor>` API, plus a matching Claude skill. Credential never enters AI context; every mutation is classifier-gated and audit-logged."226- **Website** — leave blank, or point to the canonical reference (`https://github.com/aditya-m-bharadwaj/linode-api-skill`).227- **Topics** — pick ~12 from: `claude-code`, `claude-skill`, `claude-agent-sdk`, `ai-safety`, `ai-agents`, `api-wrapper`, `cli`, `python`, `<vendor>` (e.g. `linode`, `porkbun`), `<vendor>-api`, `credential-management`, `audit-log`, `safety-classifier`.228229**Features** (Settings → General → Features):230231| Feature | Default | Why |232| --- | --- | --- |233| Issues | **on** | issue templates ship in `.github/ISSUE_TEMPLATE/` |234| Discussions | **on** | discussion templates ship in `.github/DISCUSSION_TEMPLATE/` |235| Wikis | **on** | enabled by default; the in-repo `docs/` memory layer is canonical, but leaving wikis on costs nothing and gives external contributors a low-friction surface |236| Projects | **on** | enabled by default; same reasoning — no cost, optional surface for issue triage |237| Sponsorships | **on** | `.github/FUNDING.yml` is in place |238| Preserve this repository | **on** if eligible (Arctic Code Vault opt-in) | free, no downside |239240**Pull Requests** (Settings → General → Pull Requests):241242- Allow **squash** merging: **on**.243- Allow merge commits / rebase merging: off for a solo alpha.244- Always suggest updating PR branches: **on**.245- Allow auto-merge: **on**.246- Automatically delete head branches: **on**.247248**Code security** (Settings → Code security):249250- **Private vulnerability reporting**: **enable** — this is what `SECURITY.md` and `ISSUE_TEMPLATE/config.yml` direct reporters to.251- **Dependabot alerts**: **enable** — catches `actions/*` version bumps even though the CLI has no Python deps.252- **Dependabot security updates**: **enable**.253- **Secret scanning** (free on public repos): **enable**.254- **Push protection** (blocks pushes containing detected secrets): **enable**.255- **Code scanning (CodeQL)**: **enable**. The workflow ships at `.github/workflows/codeql.yml` (Step 9) and analyzes the Python CLI and the workflow files themselves. Even on a stdlib-only single-file CLI, CodeQL catches path-traversal, command-injection, weak-crypto, and untrusted-input-into-action patterns that the classifier doesn't see. The weekly cron also re-scans against updated rule packs.256257**Branch protection** (Settings → Branches):258259- For a solo v0.x alpha: skip. The operator pushes directly to `main` per the established pattern.260- Revisit once external contributors arrive: enable "Require a pull request before merging" + "Require status checks to pass" (CI green) on `main`.261262**Pages / Webhooks / Actions secrets**: leave as default. No docs site is published; no secrets are needed by CI.263264**Bootstrap the wiki.** The wiki is a separate git repository (`<repo>.wiki.git`) that GitHub provisions only after a first page is saved through the web UI. **You (the AI) cannot create this repo via `gh` or any other automated path** — `gh repo create <slug>.wiki` fails with *"The repository <slug>.wiki cannot end in .wiki"* because GitHub reserves the `.wiki` suffix for auto-provisioned wiki repos. Cloning `<slug>.wiki.git` returns *"Repository not found"* until the operator has saved a page.265266**→ Stop here and ask the operator to bootstrap the wiki manually.** Direct them to:2672681. Open `https://github.com/<owner>/<slug>/wiki` in a browser.2692. Click **"Create the first page"** and save anything (the default *"Welcome to the wiki!"* body is fine — it will be overwritten in a moment).2703. Confirm back to you that the page saved.271272Only after the operator confirms can you proceed. Then clone the now-existing wiki repo, replay starter pages over GitHub's placeholder, and push:273274```sh275# In the project root, AFTER the operator has saved the first wiki page:276git clone https://github.com/<owner>/<slug>.wiki.git wiki277echo 'wiki/' >> .gitignore # main repo never tracks the nested .git278cd wiki279# GitHub-provisioned wikis default to the `master` branch, not `main`.280# Don't fight it; commit on master so push succeeds without surgery.281# Author / copy in the starter pages (this overwrites GitHub's auto Home.md):282# ... write Home.md, _Sidebar.md, _Footer.md, Getting-Started.md, etc.283git add -A284git commit -m "wiki: initial seed (v0.1.0-alpha.1 surface)"285git push -u origin master286```287288The minimum-viable starter page set (mirror the `APIskillBuilderSkill` wiki):289290- `Home.md` — landing page with the safety pitch in one paragraph + navigation.291- `_Sidebar.md` — right-side nav (Concepts / Reference / Contribute / Links).292- `_Footer.md` — short repo / license footer.293- `Getting-Started.md` — install, prerequisites, first invocation.294- `How-It-Works.md` — conceptual overview of how the CLI + runtime skill relate.295- `Hard-Rules.md` — annotated version of the safety contract.296- `Six-Tier-Safety-Classifier.md` — tier table + classification logic.297- `Build-Process.md` — annotated build/use steps if relevant for downstream users.298- `ADR-Index.md` — one-line summaries linking to each `docs/decisions/<NNNN>-*.md` file.299- `FAQ.md` — open questions and deliberate non-goals.300- `Roadmap.md` — near / medium / long-term + explicitly out-of-scope items.301- `Contributing-to-the-Wiki.md` — clone-edit-push workflow + page conventions.302303Page conventions: filenames are `Page-Name.md` at the wiki root (no subdirectories — GitHub wikis are flat); internal links use `[[Page Name]]`; links from wiki to the main repo use **absolute** `https://github.com/<owner>/<slug>/blob/main/...` URLs because the two repos are separate. Wiki pages **summarize and link to** canonical content in `docs/`; they do not replace it. When the wiki and `docs/` disagree, `docs/` wins.304305Add a "Contributing to the wiki" section to the generated project's `CONTRIBUTING.md` describing the local workflow and the `docs/` vs. wiki split — so a future contributor knows how to clone, edit, push, and where canonical content lives.306307**Run this step right after the first push lands.** The default GitHub presets for a brand-new repo are not what the safety contract assumes — at minimum, private vulnerability reporting must be on (or `SECURITY.md`'s instructions are a dead link), and the description / topics must be set so users discover the project at all.308309After configuring, paste the live About-panel description and the comma-separated topic list back to the operator for verification.310311### Step 15 — Live smoke test (if a token is available)312313If the operator provides a scoped test credential, exercise the alpha end-to-end before declaring v0.1 done:3143151. Install via the published `curl … | sh` one-liner against a *sandbox prefix* (`LINODE_CTL_PREFIX=/tmp/...`-style) so you don't pollute the operator's real install.3162. `<slug> gui-setup` via the OS dialog. Confirm `whoami` works.3173. Inventory all existing resources and **record them** so you can verify nothing got touched.3184. Exercise one workflow per tier:319 - `read` — list a resource type.320 - `mutating` — a reversible change (e.g. toggle a setting).321 - `destructive` — delete a test-tagged resource only.322 - `billable` — create the cheapest possible resource, tagged for cleanup.323 - `financial` / `privilege` — `--dry-run` only, do not actually call.3245. Verify the safety contract: try a billable call without `--i-understand-billing` (expect refusal); try a destructive call with mismatched `--confirm-id` (expect refusal).3256. Clean up all test-tagged resources via the named cleanup command.3267. Confirm pre-existing resources are untouched.3278. Review the audit log — every mutation should appear, no secrets should appear.328329Any defects surfaced by the smoke test are blockers for v0.1, not follow-up issues. Land the fixes (with tests and the corresponding CHANGELOG / SKILL.md / docs updates **in the same commit**) before tagging.330331## Safety guarantees the generated skill MUST provide332333A reader looking at the generated repo for the first time should be able to verify the following by reading the code, with no external trust:334335- The token is **never** in argv. Search `bin/<slug>` for the token variable name — it should only appear in: storage read/write helpers, the `Authorization` header construction, and the verify-before-store call.336- The token is **never** in stdout / stderr / log files / audit entries. Search for the token variable name in `print`, `_emit`, `_audit`, `logger`, etc. — should appear in none.337- The OS-dialog stdout is captured into Python memory by `subprocess.run(..., capture_output=True)` and never re-printed. The CLI prints success metadata (`Authenticated as: <username>`) and nothing else from that call.338- Every mutation is guarded by an `if not args.yes: raise CtlError(...)` check OR the generic `api` command's flag-check loop. There are no code paths that POST/PUT/PATCH/DELETE without classification + flag gating.339- The audit log only records body *keys* for generic `api` calls — not values. Grep `_audit(...)` calls; the only `body_keys=...` shape is keys-only.340341## Things you should NOT do342343- **Do not** import third-party Python packages. Stdlib only. Even `keyring` is out — the CLI's storage layer is intentionally re-implemented against the OS-native CLIs (`security`, `secret-tool`, `cmdkey`).344- **Do not** support an `LINODE_TOKEN`-style environment variable as the *primary* credential path. The operator can opt-in via documented escape hatches, but the default is keystore-only.345- **Do not** invent a "lighter" safety classifier with fewer tiers because the target API "seems mostly free". Even free APIs have privilege escalation (issuing scoped tokens), and the tier system is the operator's mental model — don't change it across vendors.346- **Do not** add a fancy interactive prompt for token entry beyond `setup` (TTY) and `gui-setup` (OS dialog). No browser-based OAuth flows, no QR codes, no `expect` scripts. The TTY/dialog out-of-band requirement is the contract.347- **Do not** auto-classify unrecognized endpoints as `read`. The default fallback for non-GET is `mutating`; for DELETE, `destructive`. Conservative is correct.348- **Do not** publish to PyPI yet. The default install path is the one-liner `curl … | sh` from raw.githubusercontent.com, which can't smuggle in a dependency. If PyPI publishing is added later, switch the version string to PEP 440 (`0.1.0a1` instead of `0.1.0-alpha.1`).349- **Do not** create the generated repo as a "monorepo" of multiple vendors. One vendor = one repo. The skill is independent per vendor; cross-vendor sharing happens at the doc level (this skill).350351## Reference implementation352353<https://github.com/aditya-m-bharadwaj/linode-api-skill> is the canonical implementation. When in doubt, read the corresponding file there. Specifically:354355| Concern | File |356| --- | --- |357| Safety classifier | `bin/linode-api-skill` lines ~620–740 |358| Token storage | `bin/linode-api-skill` `_kc_*` and `_file_*` helpers |359| GUI dialog | `bin/linode-api-skill` `_has_display`, `_prompt_token_gui`, `_confirm_gui` |360| Audit log | `bin/linode-api-skill` `_audit` |361| Verify-before-store | `cmd_setup` and `cmd_gui_setup` (call `/profile` before persisting) |362| Path validation | `PATH_RE` and `_validate_path` |363| Header / body hardening | `_request` (filters `Authorization`); `_load_body` (refuses paths under `CONFIG_DIR`) |364| Skill template | `.claude/skills/linode-api-skill/SKILL.md` |365| Memory layer | `docs/` and `.claude/commands/{resume,save}.md` |366| CI | `.github/workflows/ci.yml` |367| Install scripts | `install.sh`, `install.ps1` |368| Commit-trailer convention | `CONTRIBUTING.md` §"Commit message format" |369370## Open issues / things the build process does not yet automate371372These are honest gaps you should surface to the operator rather than fake:373374- **Pricing introspection.** This skill assumes you'll get the billable-endpoint list from a human or the vendor's pricing page. There's no automatic mapping from an OpenAPI spec to "this endpoint costs money".375- **OAuth-flow APIs.** The `setup` / `gui-setup` pattern assumes a paste-once-and-store credential. For APIs requiring OAuth dance + refresh tokens, you'll need additional design (similar in spirit to `gh auth login` but adapted).376- **Live smoke testing across all tiers.** Step 14 prescribes the methodology but the actual recipe is per-vendor — you need to know which resource is cheapest, which can be safely deleted, etc.377- **Generator tooling.** This skill is markdown that guides an AI to build. There is no `api-skill-builder scaffold <slug> <spec.json>` CLI. Building one is reasonable future work; for now, follow the steps by hand.