Skmtc CLI
The Skmtc CLI generates code from OpenAPI v3 or GraphQL SDL documents.
It's a Deno binary that wraps a project workspace under
<root>/.skmtc/, fetches generators from JSR, and runs them against a
schema source pinned in each project.
This skill carries what the binary cannot tell you: the workspace
mental model, the agent contract, and the decisions that need intent.
Everything else — the command list, per-command flags, argument
shapes — lives in the binary itself and is always current there;
§3 shows how to pull it on demand. This skill guides using the
CLI; for authoring generator packages see skmtc-generator, for
diagnosing failures see
debug-failing-generation.
1. Mental model
| Concept | Where it lives | Notes |
|---|---|---|
| Skmtc root | nearest ancestor dir containing .skmtc/ |
Created by skmtc init |
| Project | <root>/.skmtc/<project>/ |
One schema + one set of generators |
| Project deps | <root>/.skmtc/<project>/deno.json |
JSR imports of installed generators |
| Schema pin | <root>/.skmtc/<project>/.settings/client.json |
source field — URL or path. Resolution: explicit schema arg → client.json#source → interactive prompt (TTY only; strict mode fails with a recipe error) |
| basePath | client.json#settings.basePath |
Must match the consumer app's @ alias root. Both the on-disk root for generated files AND the alias root in the bundler's resolver. Generators produce @/<subdir>/... paths assuming this alignment. Absolute paths are rejected at init. |
| Bundle | <root>/.skmtc/<project>/bundle.js |
Compiled worker entry. Regenerated by bundle/dev/clone/install. |
| Manifest | <root>/.skmtc/<project>/.settings/manifest.json |
Per-run record of every file written and every (generator × item) outcome |
| Generator | JSR package or local folder | Local: <root>/.skmtc/<project>/<gen-name>/ |
| Global state | ~/.skmtc/ |
auth.json (the hub PAT stored by skmtc login), shadow project state, schema caches. Check this when local state alone doesn't explain a failure. |
A "project" is not the consuming app — it's the generator configuration the consuming app pulls code from.
Generators are opinionated templates, not configurable libraries.
Stock @skmtc/gen-* packages ship hardcoded defaults — export paths,
identifier naming, peer imports, output shapes — and there are no
config flags for any of them, deliberately. To change them,
skmtc clone the generator into the project and edit its source;
that is the customization seam, not a workaround. "Stock generator
hardcodes X" is almost never a CLI bug. Enrichments parameterize a
generator within its shape; cloning changes the shape.
Two engine facts that shape CLI expectations: generator order never affects output (coordination is a memoized cache, not a dependency graph — never sequence generators), and render does not run a formatter (unformatted output is by design; consumers format separately).
2. The agent contract
Every state-touching command supports three modes, picked automatically:
| Mode | When | Behavior |
|---|---|---|
| Interactive | TTY attached and no --json / --no-input |
Ink TUI; prompts for missing args |
| Strict text | Non-TTY (CI / pipes / agents) OR --no-input |
Plain-text result on stdout; missing required args fail with a recipe error on stderr |
| Strict JSON | --json (implies --no-input) |
Single JSON object on stdout; logs on stderr |
For agents: add --json to every command. The CLI auto-degrades
to non-interactive mode on any non-TTY stdin/stdout — no PTY wrappers
needed. (Two exceptions surface in help: dev is long-running and
has no --json; create has no --json yet.)
Exit codes are consistent across all commands: 0 success (including
documented no-ops), 2 required input missing or invalid (recipe
error on stderr), 1 anything else (registry unreachable, schema
parse failure, fatal parseIssue, typecheck failure). For generate --json, an empty errors array is the success condition — not the
exit code alone.
Recipe errors are the discovery mechanism. When a required
argument is missing in strict mode, stderr carries the usage line, a
worked example, and a Discover: line naming the command that lists
the valid values (e.g. ls .skmtc/ for project names). Trust it:
run the command, read the recipe, run the discovery, retry.
3. The command surface lives in the binary
Do not look for a command table in this skill — pull it live, where it is always current with the installed version:
skmtc --help # every command, with real descriptions
skmtc <cmd> -h # full flags for one command
The newer commands' help descriptions (status, eject, adopt,
publish, push, pull) carry their full semantics — read them
there rather than guessing from the names. One naming trap help
cannot intercept: there is no skmtc deploy — stacks are
published (skmtc publish) as immutable semver versions;
deployments and the production alias belong to hub projects and are
driven from the web app, not the CLI.
4. First steps in a workspace
skmtc agent-context --json # enumerate projects, commands, state
skmtc doctor --json # check for known frictions
These two give the full workspace picture without documentation
lookups — agent-context is the snapshot, doctor the diagnostic,
in that order. doctor's summary is the worst status across checks
(error > warning > ok; exit 1 only on error), and every check
carries its own id, status, message, and remediation hint —
the output is self-describing. The check-id catalogue, if you need to
reason about a specific check: reference.md
§"Doctor check ids".
5. The bundle-freshness gotcha
Generation runs the compiled bundle.js, not generator source — a
stale bundle silently shadows source edits:
1. `skmtc clone` triggers an automatic rebundle
2. If `worker.ts` and `deno.json#imports` disagree, strict-mode
generate refuses with a freshness error
3. Remediation: `skmtc bundle <project>`, then re-run `generate`
4. `skmtc doctor --json` surfaces the mismatch as `project-bundle/<project>`
6. Configuration: client.json and filters
.skmtc/<project>/.settings/client.json — top level is
{ source?, settings }; settings carries basePath (required,
relative, no ..), packages, enrichments, skip, include,
generatedSuffix. Full annotated shape, every key:
reference.md §6 — read it before editing the file.
settings.skip / settings.include accept a whole generator, a
per-operation entry (path → method → variant[]), or a per-model
entry (refName → variant[]); [] means every variant. Filters are
where user intent is expressed — never a generator's
isSupported. Semantics and precedence:
reference.md §7.
Every command's --json envelope is one object discriminated by a
type field; per-command shapes: reference.md §8 —
read when parsing output, not before.
7. Task cards
End-to-end workflows (setup, adding generators, enrichments, CI,
publishing, customizing a stock generator, …) live in
task-cards.md — open the one card for the job in
front of you. A single command doesn't need a card; -h covers it.
8. Boundary with other skills
This skill ends at the CLI surface. Hand off when:
- The next step edits a
.ts/.tsxfile under<root>/.skmtc/<project>/<gen-name>/→ skmtc-generator - The user reports something broken and the cause isn't yet known → verify before proposing: manifest, parse issues, then a reproduction (debug-failing-generation, error codes)
Companion files
Loaded on demand with the Read tool, never eagerly:
| File | What it holds | Read it when |
|---|---|---|
reference.md |
§6 client.json shape, §7 filter semantics, §8 JSON envelopes, §11 operational principles, doctor check ids | Editing settings, writing a filter, parsing --json, reasoning about a doctor check |
task-cards.md |
Twelve end-to-end workflow cards | Doing a multi-step job for the first time |