@swtt/cli
v0.1.2
Published
The swtt binary: scaffold an Expo + Hono + MongoDB monorepo, generate entities across every layer, and check that the boundaries hold.
Maintainers
Readme
@swtt/cli
The swtt binary for swtt-js — a scaffold for full-stack apps that stay workable when an AI agent is doing most of the typing.
npx @swtt/cli init my-app
cd my-app && cp .env.example .env
pnpm install && pnpm dev # API on :8787, Expo on :8081No cloud account. Point it at any MongoDB.
Commands
swtt init [dir] scaffold a new app
swtt g entity <Name> [field ...] generate an entity across every layer
swtt check verify the boundaries and contracts holdZero runtime dependencies — argv is parsed by hand and every check is text-level, so it starts fast enough to run on save.
swtt init
Writes a pnpm monorepo with a hard boundary:
my-app/
CLAUDE.md agent entry point → docs/README.md
docs/decisions/ ADRs — immutable, superseding
docs/systems/ living docs, updated with the behavior
packages/core/ the domain: models, validation, DTOs, operations
packages/api/ Hono — the only package that touches a database
packages/app/ Expo — iOS, Android and web from one source
packages/desktop/ a Tauri shell over app's web exportThe domain never imports a framework, so replacing Hono or Expo means rewriting an adapter rather than the app.
--link <dir> points a generated app at a local swtt-js checkout instead of the registry. --dry prints the plan. --force writes into a non-empty directory.
swtt g entity
swtt g entity Invoice title:text(120) reference:slug! amount:number \
status:enum(draft,sent,paid) customer:ref(Customer) dueAt:date?Seven files, all derived from one field list — a schema, Zod input schemas, a DTO and its converter, five domain operations, a Hono router, TanStack Query hooks, and an ADR stub carrying the questions the generator could not answer for you.
Nothing is written twice, so a schema and its validation and its DTO cannot drift apart.
Field types: text(n), slug, number, boolean, date, url, enum(a,b,c), ref(Model). Suffix ? for optional, ! for unique.
Re-running never clobbers hand edits. Existing files report as skipped or unchanged; --force overwrites source, and deliberately never the ADR.
swtt check
Exits non-zero when the shape has been broken. --strict also fails on warnings.
| Rule | |
|---|---|
| boundary/core-imports-framework | the domain reached for React, Expo or Hono |
| boundary/app-imports-server | the client imported mongoose, core/models or core/domain as a value |
| boundary/api-imports-model | a route handler bypassed the domain layer |
| boundary/api-opens-connection | a route handler called connectDb() |
| contract/catch-reads-message | a catch read .message, discarding details |
| docs/adr-no-status, docs/adr-draft, docs/*-not-indexed | the ADR habit decaying |
Suppress a line with // swtt-ignore <rule>.
The rules are text-level, not AST-level. That keeps this dependency-free, and it means a promise .catch(e => e.message) slips through — the CLI doc lists every blind spot rather than implying there are none.
Documentation
docs/systems/cli.md for the full reference, docs/decisions/ for why any of it is shaped this way.
License
Apache-2.0
