Imported from justintanner/apicity (
AGENTS.md). Install upstream withnpx skills add justintanner/apicity. Copyright stays with the author.
Agent Instructions
This project uses bd (beads) for issue tracking. Run bd prime for full workflow context.
opencode users: the opencode-beads plugin auto-runs bd prime on session start and after compaction. Add it to your user-global opencode config (e.g. ~/.config/opencode/opencode.json: { "plugin": ["opencode-beads"] }). Without it, run bd prime manually at the start of each session.
Quick Reference
bd ready # Find available work
bd show <id> # View issue details
bd update <id> --claim # Claim work atomically
bd close <id> # Complete work
# Remote beads sync is owned by the city's beads-maintenance loop; never run it by hand.
Dolt Sync Recovery
Symptom: bd dolt pull fails with Error 1105: cannot merge with uncommitted changes after a bd schema migration.
Invariant: the ignored_schema_migrations bookkeeping table must stay UNTRACKED and listed in dolt_ignore. dolt_ignore follows git-ignore semantics: it exempts only untracked tables from Dolt's merge preflight. A tracked-but-dirty bookkeeping table blocks every pull.
Validated recovery (the only supported procedure — it preserves all bookkeeping rows in the working set):
CALL DOLT_RM('--cached','ignored_schema_migrations');
CALL DOLT_COMMIT('-m','untrack ignored_schema_migrations');
NEVER dolt add -f / force-commit the table: that re-tracks it and re-creates the hazard on the next migration.
Verification: run scripts/check-bd-dolt-sync.sh for the repeatable scratch + live check.
Non-Interactive Shell Commands
ALWAYS use non-interactive flags with file operations to avoid hanging on confirmation prompts.
Shell commands like cp, mv, and rm may be aliased to include -i (interactive) mode on some systems, causing the agent to hang indefinitely waiting for y/n input.
Use these forms instead:
# Force overwrite without prompting
cp -f source dest # NOT: cp source dest
mv -f source dest # NOT: mv source dest
rm -f file # NOT: rm file
# For recursive operations
rm -rf directory # NOT: rm -r directory
cp -rf source dest # NOT: cp -r source dest
Other commands that may prompt:
scp- use-o BatchMode=yesfor non-interactivessh- use-o BatchMode=yesto fail instead of promptingapt-get- use-yflagbrew- useHOMEBREW_NO_AUTO_UPDATE=1env var
Project Overview
Apicity is a TypeScript monorepo of standalone API provider packages
(@apicity/openai, @apicity/anthropic, @apicity/xai, @apicity/fal,
@apicity/google, @apicity/googleflow, @apicity/kie, @apicity/kimicoding,
@apicity/alibaba, @apicity/fireworks, @apicity/zaicoding, @apicity/binance,
@apicity/polymarket, @apicity/simplefunctions, @apicity/openligadb,
@apicity/thesportsdb, @apicity/openf1, @apicity/elevenlabs, @apicity/s3,
@apicity/b2, @apicity/dropbox, @apicity/dolthub, @apicity/meta,
@apicity/telegram, @apicity/quo, @apicity/x, @apicity/youtube,
@apicity/free-media-upload, @apicity/cost). Each package is self-contained with a minimal dependency
footprint: every provider depends on zod for endpoint .schema definitions,
polymarket also depends on viem for EIP-712 order signing, and the OpenAI
and xAI Realtime server transports use ws.
Method paths mirror upstream API URL paths segment-by-segment; kebab-case becomes camelCase (e.g. /v1/chat/completions → openai.v1.chat.completions()).
.claude/CLAUDE.md is the canonical source for project conventions. This file is the self-sufficient summary for non-Claude harnesses (opencode, other agents). If they disagree, prefer .claude/CLAUDE.md.
apicity CLI
apicity is driven through the apicity CLI on PATH, never an MCP server. Run
apicity --help for the command set and apicity skill for the agent guide
(apicity commands, apicity describe, then the call). Credentials are
configured by the operator; never request, copy, or read them.
The full reference is packages/cli/README.md.
MCP support was removed in September 2026; MCP.md is the migration
note.
Commands
pnpm install # Install dependencies
pnpm run build # Build all packages
pnpm run gen:call-shapes # Regenerate packages/cli/src/call-shapes.ts from the endpoint sources
pnpm run gen:call-shapes:check # Check the generated call-shape table for drift (part of ci:local)
pnpm run lint # Full lint: format check + ESLint + repo checks
pnpm run lint:after-format # Full lint minus format check; safe after pnpm run format
pnpm run lint:provider -- <name-or-path> # Scoped ESLint + provider-relevant checks
pnpm run typecheck:provider -- <name-or-path> # Fast provider typecheck; falls back to full on shared/package diffs
pnpm run check:packed-types # After build: pack every provider, type-check a node16 consumer (part of ci:local)
pnpm run test:run # Run tests once (Polly.js replay; no network)
pnpm run test:affected # Provider tests for provider-only diffs; full fallback otherwise
pnpm run test:provider -- <name-or-path> # Typecheck + replay one provider
pnpm run dev:record -- <file> # Safe record for a NEW test (record-missing + 1Password)
pnpm run dev:rerecord -- <file> # Destructive re-record (file filter required)
pnpm run format:changed -- [paths...] # Prettier only changed or supplied files
pnpm run dev:preflight:fast -- <name-or-path> # Fast provider gate: scoped format+lint, whole tests-project typecheck, provider typecheck+test, cross-cutting repo-wide guard tests
pnpm run dev:preflight:provider -- <name-or-path> # Explicit alias for the fast provider gate
pnpm run dev:preflight:changed -- [paths...] # Changed-file format/lint plus full typecheck+test
pnpm run dev:preflight # Full local gate: format + typecheck + lint:after-format + test:run
pnpm run ci:local # Full CI-style gate: audit + gen:examples:check + gen:call-shapes:check + doc-gen:check + build + check:packed-types + typecheck:tests + lint + test:run
pnpm run harness:telegram -- --dry-run # Preview per-endpoint Telegram messages (changed recordings)
pnpm run harness:telegram -- --all <pattern> --dry-run # Preview ANY recording by name/path substring
pnpm run check:op # Verify 1Password service account is working
pnpm run harness # Local HAR viewer at localhost:3475
Provider-scoped commands accept either openai-style names or paths such as
packages/provider/openai/src/openai.ts and
tests/integration/openai-chat.test.ts. For narrow provider work, use
pnpm run dev:preflight:fast -- <name-or-path>; it prints
the scoped format
and scoped lint steps, the whole tests-project
typecheck, the provider typecheck and
replay, and the
cross-cutting repo-wide guard tests
as it runs them. From inside a provider package, run
pnpm -w run dev:preflight:fast to infer the provider
from pnpm's INIT_CWD. Keep pnpm run dev:preflight and
pnpm run ci:local for shared scripts/config, package metadata, docs, test
harness changes, release prep, or any ambiguous diff that needs the full
repository gate. Changed-file commands infer from origin/main...HEAD,
staged/unstaged changes, and untracked files, or accept explicit paths after
--. Use pnpm run typecheck:provider -- <name-or-path> for typecheck-only
local iteration; it falls back to the full typecheck when the diff, including
staged and unstaged files, touches another package or shared package/TypeScript
config.
Shared Namespaces Across Sibling Slices
Ownership. When one upstream family splits across two or more slices — a
model family, a sub-provider, any nested namespace more than one slice touches —
the decomposition names exactly one slice as the owner of the shared
namespace scaffold. The owner declares the namespace itself (the Object.assign
callable, or the object literal carrying the children) together with its own
leaf; every sibling adds a leaf only and declares no scaffold. Without a
named owner each sibling declares the namespace independently and the
incompatibility exists only between the branches, where no single-tree gate
can see it. That is ac-c2cc4j RF-1 (review finding RR-5): four fal
slices each declared geminiOmniFlash — one as a callable, three as an object
with a single leaf — which merges to TS2717 plus four duplicate keys in one
object literal, and only the callable slice declared the base endpoint, so
neither direction of the repair was expressible on any branch that existed.
Reconciliation covers counts and namespace shape. A reconciliation slice re-derives the run's pinned counts and runs the cross-ref namespace comparison over every sibling ref in its run, attaching the output to its close:
pnpm run namespace-shapes -- --base <base> --ref <ref> --ref <ref> …
Copying that invocation into a bead description is sufficient to widen a future
reconciliation slice's scope. --base is load-bearing: every slice ref carries
the whole tree, so an unbased comparison reports every pre-existing path as
shared and every pre-existing callable leaf as a collision (default:
git merge-base of the supplied refs, falling back to main). --provider
scopes the run, --dir reads a checkout instead of a ref, --json prints the
machine-readable report, and the exit code is 1 when any collision is
reported, so a slice can gate on it. Reading is git show only — nothing is
checked out or merged. SR (ac-kanu4b) is why this clause exists: it ran a
counts-only contract, found the counts clean, and correctly closed no-op while
a TS2717 collision sat in four of its siblings — the right verdict on the
wrong scope. The in-tree half is
tests/unit/provider-namespace-shape.test.ts, which runs in every fast gate's
cross-cutting block.
Adding a New Endpoint
Work through the standard endpoint flow: research the upstream docs → add types/schema/factory → record a HAR fixture → verify replay → preview the Telegram message → run the gates → open the PR (one endpoint per PR).
Two invariants worth knowing:
- URL comment (lint-enforced) — a 2-line comment immediately above each endpoint property in the factory (
// {METHOD} {full upstream URL}then// Docs: {docs URL}). Checked bypnpm run lint:endpoints; the docs hostname must be on the provider's allow-list inscripts/check-endpoint-comments.mjs. For overloaded endpoints, comment the default path. - endpoint-docs.tsv row (docs inventory, not lint-enforced) — also add a matching
(provider, dotPath, method, fullUrl, docsUrl)row inscripts/endpoint-docs.tsv(viapnpm run endpoint-map). Missing rows do not fail lint/CI; they degrade the generated README API Reference. - Call shape (generated) — a new tsv row also needs
pnpm run gen:call-shapes, which re-derivespackages/cli/src/call-shapes.ts(how the CLI binds each flag). When the generator cannot classify a placeholder it names the row and refuses: read the leaf and add anOVERRIDESentry — with awhy— inscripts/lib/call-shapes.mjs, then regenerate.pnpm run gen:call-shapes:checkruns insideci:local. - One endpoint per PR.
Integration Test Recording
All tests use Polly.js HTTP record/replay — no mocks. Recordings live under tests/recordings/ as HAR files and are committed alongside source.
Two recording modes:
record-missing(default, safe) — Only records tests whose HAR files don't exist yet. Existing recordings replay from disk. Use this when adding a new test. Safe without a file filter.record(destructive) — Overwrites existing HAR files. Use only when intentionally re-recording. Hard-errors if run without a test file filter (tests/record.mjsenforces this). Override withPOLLY_FORCE_ALL=1only if you really mean to re-record everything.
Workflow for a new test:
# Record only the new test's fixtures; existing HARs untouched.
pnpm run dev:record -- tests/integration/<file>.test.ts
# Verify pure replay:
pnpm run test:run tests/integration/<file>.test.ts
For provider-only changes, pnpm run test:affected reads the current git diff
(committed, staged, unstaged, and untracked files) and runs the matching
test:provider subset. cli is scoped like a provider and selects the
cli-* test files. It falls back to full pnpm run test:run for shared
scripts/config, package metadata, unit or functional tests, docs, and ambiguous
paths. Run pnpm run test:run directly when you need an explicit full local
replay.
Pitfall: pnpm run dev:rerecord without a file filter will refuse to run (by design). Do not set POLLY_FORCE_ALL=1 to force it — prefer deleting the specific recording directory and re-running dev:record on that one test.
API keys resolve at runtime via the 1Password CLI (op run --env-file=.env). The tracked .env file contains 1Password references only.
Code Conventions
- ES modules; target ES2022; strict mode;
@typescript-eslint/no-explicit-any: "error" - Double quotes, semicolons, trailing commas (ES5), 2-space indent, 80 char width
- PascalCase for types/interfaces/errors, camelCase for functions
- Error classes: extend
Error, includestatusfield, named<Provider>Error - Prefer
interfaceovertypefor object shapes Record<string, unknown>for API request/response bodies
Gascity Handoff
Some bd issues are owned by jwt-gascity[bot]@users.noreply.github.com. Do not reassign those unless you are the bot. Use bd search gascity to surface interop-related work.
Issue Tracking with bd (beads)
IMPORTANT: This project uses bd (beads) for ALL issue tracking. Do NOT use markdown TODOs, task lists, or other tracking methods.
Why bd?
- Dependency-aware: Track blockers and relationships between issues
- Git-friendly: Dolt-powered version control with native sync
- Agent-optimized: JSON output, ready work detection, discovered-from links
- Prevents duplicate tracking systems and confusion
Quick Start
Check for ready work:
bd ready --json
Create new issues:
bd create "Issue title" --description="Detailed context" -t bug|feature|task -p 0-4 --json
bd create "Issue title" --description="What this issue is about" -p 1 --deps discovered-from:bd-123 --json
Claim and update:
bd update <id> --claim --json
bd update bd-42 --priority 1 --json
Complete work:
bd close bd-42 --reason "Completed" --json
Issue Types
bug- Something brokenfeature- New functionalitytask- Work item (tests, docs, refactoring)epic- Large feature with subtaskschore- Maintenance (dependencies, tooling)
Priorities
0- Critical (security, data loss, broken builds)1- High (major features, important bugs)2- Medium (default, nice-to-have)3- Low (polish, optimization)4- Backlog (future ideas)
Workflow for AI Agents
- Check ready work:
bd readyshows unblocked issues - Claim your task atomically:
bd update <id> --claim - Work on it: Implement, test, document
- Discover new work? Create linked issue:
bd create "Found bug" --description="Details about what was found" -p 1 --deps discovered-from:<parent-id>
- Complete:
bd close <id> --reason "Done"
Quality
- Use
--acceptanceand--designfields when creating issues - Use
--validateto check description completeness
Lifecycle
bd defer <id>/bd supersede <id>for issue managementbd stale/bd orphans/bd lintfor hygienebd human <id>to flag for human decisionsbd formula list/bd mol pour <name>for structured workflows
Auto-Sync
bd automatically syncs via Dolt:
- Each write auto-commits to Dolt history
- No manual export/import needed!
Architecture in one line: issues live in a local Dolt DB; sync uses refs/dolt/data on your git remote; .beads/issues.jsonl is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md for details and anti-patterns.
Important Rules
- ✅ Use bd for ALL task tracking
- ✅ Always use
--jsonflag for programmatic use - ✅ Link discovered work with
discovered-fromdependencies - ✅ Check
bd readybefore asking "what should I work on?" - ❌ Do NOT create markdown TODO lists
- ❌ Do NOT use external issue trackers
- ❌ Do NOT duplicate tracking systems
For more details, see README.md and docs/QUICKSTART.md.
Session Completion
When ending a work session, you MUST complete ALL steps below. Work is NOT complete until git push succeeds.
MANDATORY WORKFLOW:
- File issues for remaining work - Create issues for anything that needs follow-up
- Run quality gates (if code changed) - Tests, linters, builds
- Update issue status - Close finished work, update in-progress items
- PUSH TO REMOTE - This is MANDATORY:
git pull --rebase git push git status # MUST show "up to date with origin" - Clean up - Clear stashes, prune remote branches
- Verify - All changes committed AND pushed
- Hand off - Provide context for next session
CRITICAL RULES:
- Work is NOT complete until
git pushsucceeds - NEVER stop before pushing - that leaves work stranded locally
- NEVER say "ready to push when you are" - YOU must push
- If push fails, resolve and retry until it succeeds
Beads Issue Tracker
Use Beads (bd) for durable task tracking in repositories that include it. Use the beads skill at .agents/skills/beads/SKILL.md (project install) or ~/.agents/skills/beads/SKILL.md (global install) for Beads workflow guidance, then use the bd CLI for issue operations.
Quick Reference
bd ready # Find available work
bd show <id> # View issue details
bd update <id> --claim # Claim work
bd close <id> # Complete work
bd prime # Refresh Beads context
Rules
- Use
bdfor all task tracking; do not create markdown TODO lists. - Run
bd primewhen Beads context is missing or stale. Codex 0.129.0+ can load Beads context automatically through native hooks; use/hooksto inspect or toggle them. - Keep persistent project memory in Beads via
bd remember; do not create ad hoc memory files.
Architecture in one line: issues live in a local Dolt DB; sync uses refs/dolt/data on your git remote; .beads/issues.jsonl is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md for details and anti-patterns.
