---
name: anitakirkovska/cli-builder
source: https://app.decimal.ai/s/anitakirkovska-cli-builder@1/SKILL.md
source_sha256: 34339928f952
---

# CLI Builder

You're shipping a public CLI tool plus the SKILL.md that tells another agent how to use it. The pairing has to work for an agent you've never met, on a machine you've never seen.

The discipline is **claim only what you've verified**, and **keep personal content out of the public surface**. The rest of this skill is the rules.

## Decision: build, or skip?

Build a CLI + skill when:

- An agent task will repeatedly call the same external service (TTS, prediction markets, calendar, email).
- The existing SDK or CLI is heavy, missing a key flag, or doesn't expose what the agent needs.
- You want a tool that any agent install can pick up cheaply (one `pip install` or equivalent).

Skip when:

- The user wants a one-off script for their own machine. Use a sandbox script, not a public CLI.
- An official CLI already exists and covers the agent's actual needs. Pit the existing tool's gaps before wrapping.
- The service has no public API. No CLI will fix that.

## Anatomy of a CLI that agents can actually call

### Subcommand pattern

Every CLI should fit one of these two shapes:

```
<cli> <verb> [args] [flags]    # a la twilio, vercel, gh
<cli> <verb> <object> [args]    # a la aws, gcloud, gh
```

Avoid the "single command with a `--mode` flag" anti-pattern (e.g. `cli --mode=tts`, `cli --mode=user`). Subcommands are how agents learn what a tool does from `--help`.

### Error model

Three rules:

1. **Errors to stderr.** Echo the message + exit non-zero.
2. **Exit codes have meaning.** `0` = success, `2` = user error (bad input, missing key), `3+` = service error. Don't use 1, it's ambiguous.
3. **Custom exception class** in code, with a `note`-style textual hint (`"missing API key. Set FOO_API_KEY or pass --key-file."`). Agents will grep for that string.

### The `--local` mode (recommended for read-only commands)

For any list/browse command, ship a `--local` flag that returns a curated default from baked-in code, no API call, no key needed. This is what makes the skill usable for an agent that hasn't been configured with credentials yet.

**Critical:** the offline path must actually skip the auth step. Build the network client **lazily**, only inside the commands that hit the network. Constructing the client in `main()` before dispatch will gate your advertised "no key needed" behavior behind the same auth wall as the rest of the tool.

### Defaults

A fresh `tts` call without flags should produce something useful. Pick neutral defaults:

- TTS tools: a calm narrator voice, the fast latency tier.
- Voice libraries: alphabetical or the platform's "default" voice.
- Predictive market tools: the home team by default, or no default at all (require explicit `--side`).

Tell the user which default you picked and why, in one line, before showing output.

### Auth

Two paths, both supported:

- Env var: `SERVICE_API_KEY=...`
- Flag: `--key-file /path/to/key.txt`

A credential-vault reveal is the third path if the agent harness provides one. Document all three in the README.

Never embed a key in code or test fixtures, including the example calls in README.md.

## The companion SKILL.md

This is the file an agent reads when it gets `pip install`d the CLI. Treat it like an API reference for the agent, not a sales brochure.

### Required YAML frontmatter

```yaml
---
name: <kebab-case-name>          # must match parent dir, lowercase + hyphens only
description: <100 token description, keyword-rich>
compatibility:                   # optional
metadata:                        # optional, includes emoji and display-name
---
```

The `description` is what an agent uses to decide whether to load this skill. Include:

- What the skill does (one sentence).
- The trigger phrases an agent would see before saying "yes, load this."
- What is **out of scope** (so the agent doesn't reach for this skill on the wrong task).

### Body structure

A good SKILL.md has these sections, in this order:

1. **# Title** (one line, matches frontmatter name expanded).
2. **When to use** — concrete scenarios where this skill is the right pick.
3. **Prerequisites** — install command, key requirement, anything the agent needs to have ready.
4. **Companion CLI quick reference** — a table mapping each subcommand to its purpose.
5. **Inputs** — names, types, defaults, units.
6. **Outputs** — shape of what the agent gets back.
7. **Personality (in agent voice)** — if applicable, how the agent should talk about the result.
8. **Examples** — 3-5 worked calls, each ending in a concrete artifact.
9. **Gotchas** — what bites in production. Quota cliffs, latency per model tier, long-text chunking, file format caveats. Agents hit these on the second or third call.
10. **Out of scope (for now)** — features that someone might ask for but which belong elsewhere (cloning flows, streaming WebSocket APIs, audio post-processing).

Keep the `description` under ~100 tokens. Keep the body under 500 lines. Move deeper reference material to `references/` (linked from the SKILL.md) so progressive disclosure kicks in.

### Code examples: live, not invented

Every shell example in `Examples` should be a command you've actually run during development. If you didn't run it, don't show it. Agents will copy-paste these.

## Two-repo hygiene

A CLI + skill that an agent calls falls into one of two categories. Pick one explicitly.

### Public / agent-discoverable

The CLI is `pip install`-able by anyone. Host it in a public GitHub repo. The repo:

- Has a generic name (e.g. `elevenlabs-cli`, `kalshi-cli`).
- Owns no personal content. No API keys, no agent context, no proprietary voice configs, no in-repo strategy docs.
- Uses an MIT license.
- Has a clean README that a stranger can use without reading your blog.

The companion SKILL.md lives at the repo root, not in some private vault.

### Private / single-user

If the CLI is a private tool (e.g. your prediction-market strategy, your bespoke home-automation script), keep it inside a private repo that's strictly separate from any public repo. Don't bleed content across.

**The line:** if the public CLI repo contains any file that only makes sense to you, delete it. Strategy docs, README drafts with your name, internal notes, test fixtures with your data — none of these belong in a public agent-discoverable tool.

## Pre-flight checklist

Before you tell the user "shipped," every item on this list:

- [ ] **Run every advertised command, end to end, on its own.** If a flag is in the README, that flag has been exercised. The `--local` flag specifically: tested with no API key set, confirmed it works.
- [ ] **Hit the live API once.** Don't guess field names from convention. Inspect a real response and write code against the actual shape.
- [ ] **Cross-check README claims against the actual behavior.** Every "no key needed," "stdlib-friendly," "no SDK lock-in," "free tier works" claim has been verified by running the case it qualifies.
- [ ] **Both error paths produce sensible output.** Bad input (exit 2) and bad key (exit 2). Service errors (exit 3+). Don't crash.
- [ ] **SKILL.md examples are commands you ran.** Not commands you imagine would run.
- [ ] **Public repo contains no personal content.** No keys, no agent context, no personal data in tests, no "Why this exists" essay that names the user.
- [ ] **License is set.** MIT or Apache-2.0 are fine. Pick one and commit.
- [ ] **The CLI runs after `pip install .`** or the equivalent for the language you picked.

If any item is unchecked, don't ship. Mark it "needs review" or fix it first.

## What goes wrong (real examples)

- **"No API call" claims that 401.** `main()` constructs the auth client before the dispatcher gets a chance to see `--local`. The offline shortcut dies on the path that's supposed to need nothing. Fix: build the client inside the commands that actually hit the network.
- **Pretty-print reads guessed field names.** API uses `next_character_count_reset_unix`, your code reads `reset_at`. The pretty output silently prints dashes for everything you got wrong. Fix: hit the live API once.
- **Hardcoded `Accept: audio/mpeg`.** Works for mp3, breaks the moment a caller asks for `pcm_*`. Fix: let the query parameter win, don't override Accept.
- **"Stdlib-friendly, no SDK required."** The package still depends on `requests`. The claim reads as marketing: a reader will check and lose trust. Fix: drop the claim, replace with what's true.
- **README description says the filter is "X + Y + Z."** API filter is single-category, returns just X. Fix: grep the API docs for the actual behavior before writing the description.

## Reference implementation

[`elevenlabs-cli`](https://github.com/AnitaKirkovska/elevenlabs-cli) — a generic ElevenLabs TTS CLI plus companion SKILL.md. Useful as a worked example of the pattern: subcommand shape, `--local` mode, curated voice list, env/flag auth, a SKILL.md that includes trigger phrases, inputs/outputs, latency-per-tier gotchas, and out-of-scope flags for features that belong elsewhere.

The v1 of that CLI shipped with bugs that the "Pre-flight checklist" above was written to prevent. Treat the checklist as the lessons learned in compressed form.