skmtc-cli
Use the Skmtc CLI to scaffold projects, install or clone generators from JSR, configure schema sources and enrichments, and produce code artifacts from an OpenAPI v3 or GraphQL SDL schema. Teaches the workspace mental model (`<root>/.skmtc/<project>/`, client.json, bundle, manife
Install
npx skills add https://github.com/skmtc/skmtc/tree/main/deno/docs/skills/skmtc-cli
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install skmtc-skmtc@llmmart
git clone https://github.com/skmtc/skmtc.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole skmtc/skmtc collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
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 supply the
settings a generator's author declared it needs — its enrichments.ts
schema is the whole contract a consumer can fill; 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.
packages (optional) routes output into monorepo packages. Each
entry { rootPath, moduleName? } is a folder forward from basePath,
which is then the common ancestor of every package, not a bundler
alias. Inside a root, imports render @/ from that root; from
outside, they render the root's moduleName. Nested roots are subpath
exports (@app/sdk/models) that share the outer package's @. Config
load rejects .., a repeated root and the workspace root; render
fails on an outside import of a root with no moduleName. Task page:
docs/using/how-to/generate-into-multiple-packages.md.
Enrichment misaddressing never errors. When a customization does not
land, read manifest.enrichmentWarnings (printed after generate,
and re-read by skmtc doctor as project-enrichments/<project>): a
typo'd id, path, method, model name or leaf key is reported with the
nearest match. Routing is the literal path plus lowercase method,
never operationId, under a main variant key. A wrong-typed value
fails only that item, recorded as error in the manifest.
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 |
Files (skmtc)
-
reference.md 19.5 KB
# skmtc-cli — reference companion Pull-loaded detail for the `skmtc-cli` skill: the settings file's full shape, the filter semantics, the JSON envelopes agents parse, the user-facing operational principles, and the doctor check ids. Sections keep their historical numbers (§6/§7/§8/§11), which SKILL.md cites directly; they do not line up with SKILL.md's own numbering. Read this when you are editing `client.json`, writing a filter, parsing `--json` output, or checking a principle — not before. ## 6. The client.json shape ```jsonc { // Optional. URL or path to the OpenAPI / GraphQL schema. // When set, `skmtc generate <project>` doesn't need a schema arg. "source": "./openapi.json", "settings": { // The on-disk anchor for generated output. Required, relative, // no `..` segments. Single-package: the consumer app's bundler // `@` alias root — generators produce `@/<subdir>/...` paths // assuming this aligns with the bundler's alias config. // Multi-package (see `packages` below): a common ancestor of // every package — the monorepo root — not a bundler alias. "basePath": "mobile-app/src", // Per-generator and per-item user overrides. Routing // keys depend on factory: // - OAS operation: [path][method][variant] // - GraphQL op: [rootKind][fieldName][variant] // - Model: [refName][variant] // // The trailing `[variant]` level is `'main'` by default. Most // consumers write just one variant; a variants-aware generator // like gen-shadcn-form (operation) or a coercive zod variant // (model) can produce N artifacts per item by declaring extra // variant keys. `'main'` MUST be present whenever any variant // is declared — the engine throws at start otherwise. "enrichments": { "@skmtc/gen-shadcn-form": { "/contacts": { "post": { "main": { "title": "Create Contact", "submitLabel": "Save" } } }, // Multi-variant operation example: one PATCH endpoint, // several section-edit forms with different field subsets. "/quotes/{id}": { "patch": { "main": { "title": "Edit Quote" }, "customer": { "title": "Customer details" }, "location": { "title": "Location" } } } }, // Multi-variant model example: same component schema produces // a strict and a coercive zod schema in adjacent files. "@scope/gen-zod-variants": { "Customer": { "main": { "coerce": false }, "coercive": { "coerce": true } } } }, // Allow-list. Empty array = no filter. See §7. "include": [], // Deny-list. Applied after include. See §7. "skip": [], // Optional. Filename suffix the engine injects into every // projection export path before the extension (`CreateForm.tsx` // → `CreateForm.generated.tsx`). Defaults to ".generated"; set // "" to disable. Injection is idempotent, and the suffix marks a // file as engine-owned — it is the seam the eject/adopt flow // renames across. Usually leave it alone. "generatedSuffix": ".generated", // Optional. Multi-package output — route generated files into // separate packages of a monorepo. Each entry is // `{ rootPath, moduleName? }` with a FORWARD `rootPath` (relative // to basePath). A root is a folder, not a prefix. Rejected at // config load: a `..` segment, one root listed twice (in any // spelling), and the workspace root itself (`.`) — leave // `packages` unset to import everything through `@/` from // basePath. When `packages` is set, basePath is the monorepo root // and `@` is per-package: intra-package imports render `@/…` // (rooted at that package), cross-package imports render the // target's `moduleName`. Roots may nest: a nested root is a // subpath export of the package around it (`@app/sdk/models`), // sharing the outer package's `@`. See // `reference/settings/client-json-schema.md`. "packages": [ { "rootPath": "packages/sdk/src", "moduleName": "@app/sdk" }, { "rootPath": "packages/sdk/src/models", "moduleName": "@app/sdk/models" } ] } } ``` To know what enrichment keys a generator accepts, **read its `gen-x/src/enrichments.ts`** — Valibot schema is canonical. ### Three enrichment scopes The `enrichments` namespace carries **per-subject** enrichments (the original, unchanged form) plus **two reserved `_`-prefixed scopes**. Three scopes in all, distinguished by key-depth: | Scope | Where the key sits | Reserved key | Lifetime | |---|---|---|---| | **subject** | `[id][subject][variant]` | — (customer subject names) | per item (model / operation) | | **generator** | `[id]._generator` | `_generator` | run-constant for that one generator | | **stack** | `._stack` | `_stack` | run-constant shared across every generator | `_stack` is a **top-level** key — a sibling of the generator-id keys. `_generator` lives **inside a generator's slot** — a sibling of the subject keys. **Reserved-key rule:** customer keys (generator ids at the top level, subject names inside a slot) **must not start with `_`**. The only reserved keys are `_stack` and `_generator`; any other `_`-prefixed key fails config validation at start. ```jsonc "enrichments": { // Stack scope — one leaf shared across every generator. Valid only // when every generator in the run declares `stack` in its umbrella. "_stack": { "apiTitle": "Billing API" }, "@acme/gen-zod-strict": { // Generator scope — a run-constant for this generator only. "_generator": { "strict": true }, // Subject scope — per-model, unchanged. `'main'` is the // default variant. "Pagination": { "main": { "coerce": true } } } } ``` In `enrichments.ts` the same three scopes are the umbrella members `subject`, `generator` and `stack` — no underscore. A generator opts into a scope by declaring it; `v.undefined()` rejects any value at that key. Which scopes a generator declares is in its `enrichments.ts` — the stock generators declare `subject` only — so the `_generator` / `_stack` blocks above need cloned generators that declare them, and `_stack` needs every generator in the run to. Per-subject enrichments are otherwise unchanged: the routing keys and the mandatory `'main'` variant level (see the `client.json` shape above and §7) work exactly as before. A generator reads each scope by known key through typed helpers (`toStackEnrichment` / `toGeneratorEnrichment` / the per-subject path) — it never iterates the enrichments record itself. `packages` is optional; omit it for the common single-`basePath` project. With `packages` set, point `basePath` at a common ancestor of every package (the monorepo root) so each `rootPath` — and every generator's `toExportPath` — is a plain forward path. A `..` segment in `basePath` or any `rootPath` is rejected at config load: it means `basePath` is too deep, and hand-counting `../` segments is the silent-misplacement footgun the forward-path rule removes. Two more rejections at config load: a root listed twice (spellings such as `./packages/sdk/`, `@/packages/sdk` and `packages/sdk` are one root) and the workspace root itself as a package root — with no `packages` every import already goes through `@/` from `basePath`. Roots may nest; a nested root is a subpath export of the package around it, named by its own `moduleName` for importers outside the package and sharing the outer package's `@` for files inside it. ## 7. Skip and include filters Both `skip` and `include` accept three entry shapes: ```jsonc [ // 1. Whole generator (string) "@skmtc/gen-zod", // 2. Per-operation (path → method → variant[]) // `[]` means "every variant of this method". // `["customer", "main"]` means "only those variants". { "@skmtc/gen-shadcn-form": { "/customers": { "post": [] }, "/quotes/{id}": { "patch": ["customer", "location"] } } }, // 3. Per-model (refName → variant[]) // `[]` means "every variant of this refName". // `["coercive", "main"]` means "only those variants". { "@scope/gen-zod-variants": { "Customer": [], "Order": ["coercive"] } } ] ``` Order of evaluation in `GenerateContext.toArtifacts`: **`isSupported` (capability) → `include` (allow) → `skip` (deny).** - `include` is **per-generator**, not document-global. A generator with a per-operation `include` entry runs in allow-list mode (only the listed items); a generator absent from `include` is unaffected and runs default-on. There is no whole-generator exclusion. - `include === []` or undefined → no filter active; everything runs - A per-operation `include` entry → only matched items run for that generator; non-matching items emit `skipped` - A **bare-string** `include` entry (`"@skmtc/gen-zod"`) carries no per-operation filter and is a no-op — the generator runs default-on either way. Whole-generator opt-out is `skip`. - `include` + `skip` on the same item → **`skip` wins** (`final = include_set \ skip_set`) — `skip` is the deny-list, always decisive - Matching is exact — no wildcards, on path, method, OR variant name - The variant array is the third axis: `[]` matches every variant of the named method; a populated array matches only those variant names > **Migration note.** Earlier `@skmtc/core` treated a non-empty > `include` as document-global: every generator not mentioned was > silently excluded. `include` is now per-generator. If a project > relied on the global behaviour — listing a few generators in > `include` to switch the rest off — turn those off explicitly with > whole-generator `skip` entries instead. Use `include` for opt-in generators (forms, tables, page shells) where a blanket run would produce dozens of files the team doesn't want. Use a variant array under a method to narrow the allow/deny to specific variants of a multi-variant operation. ## 8. Common JSON output shapes Agents drive on these shapes. Discriminator field is usually `type`. ### `install` ```jsonc { "projectName": "my-api", "installed": ["@skmtc/gen-zod"], "bundle": { "type": "bundled", "projectName": "my-api", "bundlePath": "..." }, "verifyWith": "cat .skmtc/my-api/deno.json" } ``` The post-install rebundle runs for every project — remote-only and hybrid alike — so `bundle.type` is always `"bundled"`. ### `clone` ```jsonc { "projectName": "my-api", "cloned": [ { "moduleName": "@skmtc/gen-typescript", "version": "0.0.55" } ], "bundle": { "type": "bundled", "projectName": "my-api", "bundlePath": "..." }, "verifyWith": "ls .skmtc/my-api/" } ``` `clone` includes a **pre-flight `@skmtc/core` peer-pin check** — if the project's pin doesn't match the CLI's major.minor, the command refuses with exit 2 before any state mutation. `--force` overrides (at the user's risk). ### `bundle` ```jsonc // Wrote bundle.js — the only outcome; every project (remote-only // included) builds a local bundle, since `generate` loads it: { "type": "bundled", "projectName": "my-api", "bundlePath": "..." } ``` ### `generate` ```jsonc { "type": "generated", "projectName": "my-api", "basePath": "mobile-app/src", "manifestPath": ".skmtc/my-api/.settings/manifest.json", "stats": { "tokens": 201029, "lines": 1234, "files": 753, "totalTimeMs": 180 }, "files": ["mobile-app/src/types/User.generated.ts", "..."], // errors: array of paths through manifest.results ending at 'error' leaves. // Shape: [traceId, spanId, "generate", generatorId, identifier] "errors": [ ["trace-1778185255674", "span-1778185255674", "generate", "@skmtc/gen-zod", "BrokenModel"] ], "parseIssues": [ { "protocol": "oas", "level": "warning", "type": "MISSING_OBJECT_TYPE", "location": "components:schemas:User", "message": "..." } ] } ``` With `--typecheck`, gains a `typecheck` field (`{ type: "passed" | "failed" | "no-tsconfig" | "tsc-error" | "skipped", ... }`). A `failed` typecheck → exit 1; generated files stay on disk. **`--watch` and `--json` are mutually exclusive.** `--json` writes a single result and exits; `--watch` is a long-running stream. Passing both → exit 2 with a recipe error. ### `publish` ```jsonc // Success — a StackVersion was published. No deploymentId/shortId: // versions are addressed by semver. { "type": "published", "projectName": "my-api", "bundlePath": ".skmtc/my-api/server.js", "bundleBytes": 1228801, "bundleSha256": "e3b0c44298fc1c14...", "stack": { "account": "ada", "slug": "my-api" }, "version": "3.0.1", "versionUrl": "https://skmtc.dev/ada/stacks/my-api/versions/3.0.1", "sourceFileCount": 28, "sourceTotalBytes": 96512 } // Failure — `stage` pinpoints where: // "version" — no deno.json#version and no --version (fails before // any network call) // "identity" — GET /v1/user failed (usually a bad PAT) // "bundle" — local Deno bundling / source collection failed // "publish" — POST .../versions failed (commonly 409: that version // is already published; versions are immutable — bump // and re-publish) { "type": "failed", "projectName": "my-api", "reason": "version 3.0.1 is already published for ada/my-api — ...", "stage": "publish" } ``` ## 11. Operational principles (user-facing subset) Selected from the full operational principles in `llms.md`. These are the ones most likely to override default LLM intuitions during CLI work: | Default intuition | Skmtc's stance | |---|---| | Add a config flag to customize a stock generator | Use `skmtc clone` and edit the source | | Run Prettier in the pipeline | Don't — produce valid TS; consumer formats separately | | Restart from scratch when something's off | Run `skmtc doctor --json` first; targeted fix beats nuke-and-pave | | Manually edit `bundle.js` or `worker.ts` | They're derived; run `skmtc bundle` to regenerate | | Mock the database in tests | Use real Supabase / real DB (project convention) | | Use `process.env.X` | Use `Deno.env.get('X')` — Deno codebase | | Use `skmtc deploy` to put a stack on the hub | The command is `skmtc publish` — `deploy` no longer exists. Stacks are published as immutable semver versions (`POST /v1/stacks/{account}/{stack}/versions`); there is no deploymentId/shortId/production alias in the CLI. Deployments and the `production` alias belong to hub *projects*, driven from the web app. | | After bumping to `@skmtc/core@0.5.0+`, treat the existing operation-level enrichment as still-valid | Wrap each `[id][path][method]` block in `{ "main": { … } }`. The variant level is now mandatory whenever an operation-level block exists — the engine throws at start with `"must include a 'main' variant"` if it's missing. See `concepts/variants.md`. | | Switch a generator between `install` and `clone` by editing only deno.json (or only the on-disk folder) | They're mutually exclusive states. A `jsr:` import in deno.json AND a `gen-X/` folder for the same name in the project root is a silent-failure footgun — deno's workspace resolver picks the local folder over the JSR pin, so the engine runs the vendored source even though the user thinks they're running the pinned version. See the *Imported vs cloned exclusivity* section below. | ### Imported vs cloned: mutually exclusive states For every `gen-*` generator referenced in a project's `deno.json#imports`, **exactly one** of the following must be true: | State | `deno.json#imports[…]` value | On-disk `gen-X/` folder | Source served by | |---|---|---|---| | Imported | `"jsr:@scope/gen-X@^1"` | **MUST NOT exist** | JSR (proxied via `/v1/generators/.../source`) | | Cloned | `"./gen-X/mod.ts"` | **MUST contain `mod.ts`** | hub R2 (uploaded with the release) | Mixed states are silently broken: - **`jsr:` import + folder both present**: deno's workspace resolver picks the local folder. The engine runs the vendored source. The pinned JSR version is ignored. No warning at runtime. - **`./path` import + no folder**: deno resolution fails at bundle time with a "module not found" — loud, easy to spot. - **Folder present + no import for it**: stale artefact from a previous `clone` that the user has since `install`-replaced without `rm -rf`'ing the directory. Harmless until the next `clone` of the same name re-uses the directory. **How states get out of sync:** - `skmtc clone @scope/gen-X` rewrites the import to `./gen-X/mod.ts` AND creates the folder. Then `skmtc install @scope/gen-X` rewrites the import back to `jsr:` — but doesn't remove the folder. The user is now in the silent-shadowing state. - Hand-edited `deno.json` divergent from disk reality. - Cloning into a previously-vendored directory without first cleaning it. **Detecting + correcting:** - `skmtc doctor` (when the consistency check lands) emits an `error` for each mismatch with the fix in the hint. - The hub validates at upload (`POST .../source` returns 422 with a precise reason if the uploaded `deno.json` contradicts the uploaded folder tree). - Manual remediation: pick the desired state, fix BOTH sides: - To use the pinned JSR version → import is `jsr:`, remove the folder. - To use cloned source → import is `./gen-X/mod.ts`, ensure the folder + `gen-X/mod.ts` exist. Full list in [`llms.md`, "Operational principles for proposing changes"](https://github.com/skmtc/skmtc/blob/main/deno/docs/llms.md#operational-principles-for-proposing-changes). ## Doctor check ids `skmtc doctor --json` output is self-describing (every check carries `id`, `status`, `message`, `hint`); this catalogue exists for reasoning about a specific check without running it. | Check id | What it inspects | |---|---| | `cli-version-current` | The running CLI vs the newest published `@skmtc/cli` — the only check that reaches the network (2s bound, `skipped` when unreachable, `--offline` skips it). Names Deno's 24h minimum-dependency-age window when the newest release is still inside it, since a reinstall without `--minimum-dependency-age=0` silently resolves an older one | | `install-lockfile` | `~/.deno/bin/.skmtc/deno.lock` — the installed CLI's version pin of `@skmtc/cli` and `@skmtc/core` | | `deno-version` | Running Deno is ≥ 2.4.0 — the floor for the esbuild-based `deno bundle` | | `hub-auth` | `~/.skmtc/auth.json` parses to `{ host, token }` — offline only; `skipped` when not logged in, `warning` + logout/login hint when malformed; never reports more than the token's last 4 chars | | `project-deno-json/<project>` | `deno.json` exists and parses | | `project-base-path/<project>` | `client.json#settings.basePath` present and relative | | `project-core-pin/<project>` | Project's `@skmtc/core` pin matches the CLI's major.minor | | `project-bundle/<project>` | `bundle.js` exists — every project (remote-only included) generates from it; warning with a `skmtc bundle` hint when missing | | `project-enrichments/<project>` | Last generate's `manifest.enrichmentWarnings` has no `warning`-level entries — dead enrichment config (typo'd generator id, path, method or model name) surfaces here between runs; `info` entries keep it `ok` | | `project-worker-pin/<project>` | If `worker.ts` exists, `@skmtc/worker` is pinned (the generated worker imports it); ok-noop before the first bundle | | `project-manifest/<project>` | `manifest.json` matches the current `@skmtc/core` schema | | `anchors-config/<project>` | `client.json#settings.anchors` shape; gen-maps are opt-in via `settings.anchors.enabled` | | `anchors-coverage/<project>` | Share of manifest files carrying an attribution sidecar; `warning` below threshold | | `anchors-staleness/<project>` | Sidecars on disk are current for the last run | -
SKILL.md 10.9 KB
--- name: skmtc-cli version: 0.5.1 description: | Use the Skmtc CLI to scaffold projects, install or clone generators from JSR, configure schema sources and enrichments, and produce code artifacts from an OpenAPI v3 or GraphQL SDL schema. Teaches the workspace mental model (`<root>/.skmtc/<project>/`, client.json, bundle, manifest) and the agent contract (strict text / strict JSON modes, exit codes, recipe errors, `agent-context` + `doctor`); the command surface itself is discovered from the binary — `skmtc --help`, `skmtc <cmd> -h` — rather than carried in this skill. Use this skill when the user asks to "run skmtc", "generate code from an OpenAPI schema", "install a skmtc generator", "scaffold a skmtc project", "watch a skmtc project", "configure enrichments", "publish a stack", "deploy to skmtc-hub" (the command is `publish`; there is no `deploy`), "skmtc in CI", or invokes any CLI subcommand. For *authoring* a generator package (Projections, Snippets, transform functions), defer to `skmtc-generator`. When something is broken (no output, wrong output, error messages, stale bundle), verify before proposing a fix: read the manifest and the parse issues, and reproduce the failure first. allowed-tools: - Bash - Read - Glob - Grep - Write - Edit metadata: describes: '@skmtc/cli': '0.9' --- # 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](https://github.com/skmtc/skmtc/blob/main/deno/docs/using/how-to/debug-failing-generation.md). ## 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 supply the settings a generator's author declared it needs — its `enrichments.ts` schema is the whole contract a consumer can fill; 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: ```bash 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 ```bash 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`](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](reference.md) — read it before editing the file. `packages` (optional) routes output into monorepo packages. Each entry `{ rootPath, moduleName? }` is a folder forward from `basePath`, which is then the common ancestor of every package, not a bundler alias. Inside a root, imports render `@/` from that root; from outside, they render the root's `moduleName`. Nested roots are subpath exports (`@app/sdk/models`) that share the outer package's `@`. Config load rejects `..`, a repeated root and the workspace root; render fails on an outside import of a root with no `moduleName`. Task page: `docs/using/how-to/generate-into-multiple-packages.md`. Enrichment misaddressing never errors. When a customization does not land, read `manifest.enrichmentWarnings` (printed after `generate`, and re-read by `skmtc doctor` as `project-enrichments/<project>`): a typo'd id, path, method, model name or leaf key is reported with the nearest match. Routing is the literal path plus lowercase method, never `operationId`, under a `main` variant key. A wrong-typed value fails only that item, recorded as `error` in the manifest. `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](reference.md). Every command's `--json` envelope is one object discriminated by a `type` field; per-command shapes: [`reference.md` §8](reference.md) — 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`](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`/`.tsx` file 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](https://github.com/skmtc/skmtc/blob/main/deno/docs/using/how-to/debug-failing-generation.md), [error codes](https://github.com/skmtc/skmtc/blob/main/deno/docs/reference/error-codes.md)) ## Companion files Loaded on demand with the Read tool, never eagerly: | File | What it holds | Read it when | |---|---|---| | [`reference.md`](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`](task-cards.md) | Twelve end-to-end workflow cards | Doing a multi-step job for the first time | -
task-cards.md 16.9 KB
# skmtc-cli — task cards Pull-loaded workflows for the `skmtc-cli` skill: end-to-end recipes for the common jobs, referenced from SKILL.md §7. Read the card for the job in front of you. The command surface lives in the binary (`skmtc --help`, `skmtc <cmd> -h`) — you do not need a card to run one command. ## Task cards ### Card: Setting up Skmtc in a project ```bash cd path/to/your-app # this becomes the Skmtc root skmtc init my-api ./src --json # creates .skmtc/my-api/ skmtc install @skmtc/gen-zod @skmtc/gen-typescript my-api --json # Edit .skmtc/my-api/.settings/client.json — set "source" to the schema URL/path skmtc generate my-api --json # one-shot generation ``` The final `--json` run returns `{ files, stats, errors, parseIssues, ... }`. Inspect `errors` and `parseIssues` for any non-success outcomes. ### Card: Adding a generator to an existing project ```bash skmtc install @skmtc/gen-<name> <project> --json ``` If `installed` is non-empty and `bundle.type === "bundled"` → ready to `generate`; the rebundle ran automatically (remote-only and hybrid projects alike). ### Card: Configuring enrichments 1. Read the target generator's `gen-x/src/enrichments.ts` (in `skmtc-generators/` or via `deno info`) to learn the accepted *per-variant inner* shape. The variant axis is core-owned; generator schemas describe what goes inside a single variant. 2. Edit `.skmtc/<project>/.settings/client.json` → `settings.enrichments[generatorId][...routingKeys][variant]`. Routing keys depend on the generator's factory: `[path][method][variant]` for OAS ops, `[refName][variant]` for models, `[rootKind][fieldName][variant]` for GraphQL ops. The variant level defaults to `'main'`; declare extra variants to get N artifacts per item from a variants-aware generator. 3. Single-variant case (most common): ```jsonc { "@skmtc/gen-shadcn-form": { "/contacts": { "post": { "main": { "title": "Create Contact" } } } } } ``` 4. Multi-variant case (variants-aware generators only): ```jsonc { "@skmtc/gen-shadcn-form": { "/quotes/{id}": { "patch": { "main": { "title": "Edit Quote" }, "customer": { "title": "Customer section" } } } } } ``` 5. `skmtc generate <project>` — no rebundle needed; enrichments are runtime config. If you see `must include a 'main' variant` at engine start, you wrote non-`'main'` variant keys without `'main'`. Add it (often `"main": {}` is enough) or remove the other variants. ### Card: Pinning the schema source 1. Edit `.skmtc/<project>/.settings/client.json` → add `"source"` at the top level. 2. After this, `skmtc generate <project>` works without the schema positional arg. ### Card: Cleaning a project's generated output Use when stale output has accumulated and you want a guaranteed fresh tree, or before deleting a project. `clean` reads the manifest, deletes every file it recorded, prunes the directories those deletions emptied, and removes the manifest. ```bash skmtc clean <project> --dry-run --verbose # preview: lists files + dirs, touches nothing skmtc clean <project> --json # apply; returns { deleted, removedDirs, manifestRemoved, ... } ``` Then, for a clean-slate regeneration: ```bash skmtc clean <project> --json && skmtc generate <project> --json ``` Key facts: - **`clean` is the full delete; `generate`'s internal prune is incremental.** `generate` only deletes the files the *next* run won't rewrite (stale artifacts from a removed generator). `clean` deletes the *entire* manifest-recorded set. Both now also prune the directories they empty. - **Directory pruning is self-limiting and anchored.** It removes only dirs it emptied, stops at the first non-empty ancestor, and never removes `basePath` or a `packages[].rootPath`. If `basePath` is unset in `client.json`, dir pruning is skipped entirely. - **`clean` touches only generated output.** It never rebundles, contacts JSR, or edits `client.json` / `deno.json`. To uninstall a *generator*, use `remove`, not `clean`. - **No confirmation prompt** (no Ink variant). `--dry-run` is the safety valve; deletion is irreversible. - A project with no manifest (never generated, or already cleaned) → no-op, exit 0, `noManifest: true`. ### Card: Filtering operations (opt-in form generator pattern) ```jsonc // In client.json#settings: { "include": [ { "@skmtc/gen-shadcn-form": { "/customers": { "post": [] }, "/locations": { "post": [] } } } ] } ``` This produces forms only for the listed (path, method) pairs. The empty variant array (`[]`) means "every variant of this method" — i.e. the standard "all" allow. To narrow to specific variants of a multi-variant operation, list them by name: ```jsonc "include": [{ "@skmtc/gen-shadcn-form": { "/quotes/{id}": { "patch": ["customer", "location"] } } }] ``` Other operations route through other generators normally. Other generators not mentioned in `include` are unaffected — they continue producing their normal output. ### Card: Customizing a published generator ```bash skmtc clone <project> -g @skmtc/gen-<name> --json # Inspect: ls .skmtc/<project>/<gen-name>/src/ # Edit src/base.ts (paths, identifiers) or src/<Main>.ts (output shape) skmtc dev <project> # rebundle + regenerate on save ``` Hand off to `skmtc-generator` for the editing work. ### Card: Registering an agent-authored local generator (programmatic / sandbox use) Use this when generator **source already exists on disk** (authored programmatically, not scaffolded) and you want the CLI to run it — e.g. a sandbox handed "a folder of generator source + a schema". **`skmtc create` is agent-usable for Kotlin.** In a non-TTY session it runs headlessly from its command-line args, and `skmtc create <project> <name> model --lang kotlin` writes a WORKING baseline generator: a plain-signature `toKtValue` router with one module per scaffolded case (string/integer/number/boolean/array/ref/ object), `protocol.ts` carrying the value-field contracts, decision cases throwing loudly (union, unknown, map-shaped object), `enrichments.ts`, and a real root `mod.ts` default export, plus the project `deno.json` registration — scaffold-then-customise is the preferred flow. The **TypeScript** templates (`--lang typescript`, the default) still scaffold stubs and leave the package root `mod.ts` empty — for TS, the direct-registration flow below remains the practical path. **The CLI discovers a local generator ONLY via the project `deno.json#imports`.** `toGeneratorIds()` = the import *keys* whose package name starts with `gen-`; `worker.ts` is generated by importing exactly those ids. A folder on disk not referenced from `deno.json#imports` is invisible. **`worker.ts` and `bundle.js` are the only derived artifacts** — never hand-write them. `bundle` regenerates `worker.ts` from `deno.json#imports`, then runs `deno bundle -o bundle.js worker.ts`. `deno.json` and `.settings/client.json` are *config*, not derived — hand-writing those is correct and expected. ```bash skmtc init lab <basePath> --json # writes .skmtc/lab/deno.json ({}) + .settings/client.json ``` Then, **by hand**, write under `.skmtc/lab/`: 1. The generator folder `.skmtc/lab/<gen-dir>/`: ``` <gen-dir>/ deno.json # { "name": "@<scope>/gen-<name>", "version": "0.0.1", # "exports": "./mod.ts", "imports": { ...cross-generator deps... } } mod.ts # re-export the entry as DEFAULT: # export { xEntry as default } from './src/mod.ts' src/ mod.ts # toOasOperationEntry({ id, transform, ... }) / # toModelEntry({ id, transform }) — pure pipeline # config, NO `lang` field (core 0.8.0+) base.ts # imports its projection-base veneer from the lang # package (e.g. toTsOasOperationProjectionBase from # @skmtc/lang-typescript; toKt*/toCs* for the Kotlin # / C# lang packages) — the import graph declares the # language *.ts ``` - Package name **must** be `@<scope>/gen-<name>` — the `gen-` prefix is the discovery filter. - Root `mod.ts` **must have a default export that is the entry object** — the worker does `import g from '@scope/gen-x'` and reads `g.id`. - `src/mod.ts`'s entry `id` **must equal the package name** — it's the key `worker.ts` (`g.id`) and `client.json` enrichments/skip/include route on. 2. Patch `.skmtc/lab/deno.json`: ```jsonc { "imports": { "@<scope>/gen-<name>": "./<gen-dir>/mod.ts", // local generator "@skmtc/core": "jsr:@skmtc/core@<pin>", // peer deps the "@skmtc/worker": "jsr:@skmtc/worker@<pin>", // generator src "@skmtc/lang-typescript": "jsr:@skmtc/lang-typescript@<pin>", // the lang package the base file imports "@std/path": "jsr:@std/path@^1", // imports by bare "tiny-invariant":"npm:tiny-invariant@^1.3.3" // specifier // ...valibot, ts-pattern, etc. as the source needs }, "workspace": ["./<gen-dir>"] } ``` `init` writes an empty `{}`. `skmtc bundle` (and any command that rebundles — `clone`, `dev`) now adds the `@skmtc/core` and `@skmtc/worker` pins automatically, at the CLI's own versions, when it generates `worker.ts` — so you no longer hand-pin those two. You **do** still pin every *other* bare specifier the generator source imports (`@std/path`, `valibot`, `tiny-invariant`, …) — `bundle` only knows about the worker peer deps. Remote-only projects need no hand-pinning beyond the `jsr:` generator entries — published packages carry their own dependencies. 3. Set the schema in `.skmtc/lab/.settings/client.json#source` (or pass it as the `generate` positional). `basePath` is set by `init`. ```bash skmtc bundle lab --json # → { type: "bundled", bundlePath } — writes worker.ts AND bundle.js skmtc generate lab <schema> --json --typecheck ``` `bundle` returns `type: "bundled"` for every project. To confirm the step-2 wiring took, check the generated `worker.ts` imports the local `@<scope>/gen-<name>` id — a missing entry means the import key isn't a `gen-*` entry in `deno.json#imports`. ### Card: Using Skmtc in CI ```bash # Setup (once per CI run) — the installer bootstraps Deno if needed; # SKMTC_VERSION pins the CLI so runs are reproducible: SKMTC_VERSION=<version> curl -fsSL https://skmtc.dev/install | sh # Build the project's bundle.js (required for every project unless a # fresh one is committed/cached): skmtc bundle <project> # Run: skmtc generate <project> --json --no-input --typecheck # Exit 0 on success, 1 on fatal parseIssue or typecheck failure. # Archive for forensics: cp <basePath>/../.skmtc/<project>/.settings/manifest.json ci-artifacts/ ``` `--unstable-worker-options` is required: `@skmtc/worker` constructs each per-project Worker with `new Worker(..., { deno: { permissions: {...} } })`. That uses Deno's `Worker.deno.permissions` API, which is gated behind this flag on current Deno releases. Without it the first `skmtc generate` exits at runtime with `Unstable API 'Worker.deno.permissions'. The --unstable-worker-options flag must be provided.` The flag has to be passed at install time — `deno install` bakes the runtime flags into the installed CLI binary at `~/.deno/bin/skmtc`. If a previously-installed binary is missing the flag, reinstall with `-f` to overwrite it; adding the flag to invocations of the existing binary does not work. The install uses **scoped permissions, not `-A`**: `--allow-read --allow-write --allow-net --allow-env --allow-run=deno,sh --allow-sys=homedir`. skmtc reads/writes project files, fetches schemas + packages over the network (the schema `source` can be any URL, so `--allow-net` stays unscoped), reads a few env vars, spawns only `deno` (bundle) and `sh` (typecheck), and needs `homedir` to locate the workspace root. It uses no FFI and no remote imports, so those grants are dropped. Empirically validated against `doctor` / `generate` / `bundle`. ### Card: Publishing a stack version to skmtc-hub Use when the project should be shared on the hub as a stack package. Publishing creates an immutable semver version of the stack; a hub *project* later pins that version and runs it (deployments and the `production` alias are project concerns, driven from the web app). ```bash # 1. Set the version — `version` in .skmtc/<project>/deno.json, # or pass --version. The CLI never invents or auto-bumps one. # 2a. One-off: log in once (paste a PAT from Settings → Access tokens; # write:releases scope is enough), then publish with no token flags: skmtc login # or: echo $PAT | skmtc login --with-token skmtc publish <project> --json # 2b. CI: pass the token explicitly (flag or env beats the stored login): skmtc publish <project> --token $SKMTC_HUB_TOKEN --json ``` Key facts: - **Token resolution is `--token` → `$SKMTC_HUB_TOKEN` → the `skmtc login` store** (`~/.skmtc/auth.json`). When the token comes from the store, the store's `host` is also the default hub URL — a token minted against a local dev hub is never silently sent to production. Explicit `--origin` / `$SKMTC_ORIGIN` always win. - **The stack identity is the project `deno.json#name`** (`@account/slug`, the JSR-style package name) — the `@account` scope may be a user OR an **org**, so org-owned stacks are reachable: the PAT authenticates, the hub authorizes you as a `writer` on that account/stack. `name` is required — recipe error (stage `identity`) if missing or not a scoped `@account/slug`. - **The hub auto-creates the stack on first publish** ("git push creates the repo"). - **Versions are immutable.** Re-publishing an existing semver → `409`, surfaced as `stage: "publish"` with an "already published" reason. Bump the version and re-run. - **Missing version fails fast** (`stage: "version"`, exit 1, before any network call) with the recipe: set `deno.json#version` or pass `--version`. - The upload is atomic: `version` + compiled `server.js` bundle + the source tree (filtered by built-in defaults + `.skmtcignore`) in one multipart request. The root `deno.json` must be in the upload — the hub reconciles the version's generator composition from it. - Read `version` / `versionUrl` from the JSON output. There is no `deploymentId` or `shortId` anymore. Full reference: [`reference/cli/publish.md`](https://github.com/skmtc/skmtc/blob/main/deno/docs/reference/cli/publish.md). ### Card: Pushing a project's config to skmtc-hub Use when local `client.json` edits (config + enrichments) should land on the project's hub project — the project-level counterpart to `publish`. `push` overwrites the hub project's config; it never creates a project (create it in the web app first). ```bash # Destination is the `project: "@account/slug"` field in client.json. skmtc login # once; stores PAT + origin skmtc push <project> --json # First push to an org project — records the destination for next time: skmtc push <project> --project @acme-org/petstore-client --json ``` Key facts: - **`<project>` is the LOCAL project** (`.skmtc/<project>/`) — the source. The **hub destination** is `client.json#project` (or `--project @account/slug`), decoupled from your identity like a git remote. The account may be an org; the hub slug can differ from the local dir name. - **Destination resolution:** `--project` → `client.json#project` → recipe error (no silent fallback to your handle). An explicit `--project` is written back into client.json (the `git push -u` ergonomic). - **Overwrites** the hub project's config. In a TTY it confirms first when config already exists (`--force` skips); in strict/`--json` it overwrites and reports `overwroteExistingConfig`. - **Never creates a project** — a `404` means "create it in the web app first". Authorization is checked against the destination account (org writers pass). - **`--base-files`** also pushes the app tree (package.json, components, css…) to `/preview/base-files`. Collected from the **app root** (`dirname(basePath)`) via the same `.skmtcignore` methodology as publish, minus `.skmtc/` and the manifest's generated files. Default push is config-only (config changes often, base files rarely). - Token + origin resolve exactly like `publish` (`--token` / `$SKMTC_HUB_TOKEN` / store; `--origin` / `$SKMTC_ORIGIN` / store host). Full reference: [`reference/cli/push.md`](https://github.com/skmtc/skmtc/blob/main/deno/docs/reference/cli/push.md). ### Card: When to hand off - "I want to edit this generator" → `skmtc-generator` - "Why is my generation failing / wrong / empty" → verify before proposing a fix: manifest first, then parse issues, then a reproduction — [debug-failing-generation](https://github.com/skmtc/skmtc/blob/main/deno/docs/using/how-to/debug-failing-generation.md)
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.