# CLI scaffolding (`toolctrl create` / `toolctrl add`) The operator CLI under `packages/cli/` ships scaffolding generators that emit minimal, opinionated skeletons for the most common boilerplate chores. Each generator writes to **new paths only** and supports a `--dry-run` flag for testability. ## Quickstart ```bash # from the repo root, after `pnpm install` pnpm --filter @toolctrl/cli build # show the full command tree node packages/cli/bin/toolctrl-cli.mjs --help ``` ## Command reference ### `toolctrl create module ` Scaffolds a feature-module skeleton under `apps/web/app/modules//`: - `db/.db.server.ts` — tenant-scoped CRUD helpers - `services/Service.ts` — service layer with tenant guards - `components/List.tsx` — list UI stub - `.db.server.test.ts` — vitest smoke test - `README.md` — manual follow-up checklist - `apps/web/app/routes/app.$tenant..tsx` — React-Router-v7 tenant route Flags: - `--permission-prefix ` — override the permission key prefix. - `--dry-run` — print the would-be writes without touching the filesystem. Example: ```bash node packages/cli/bin/toolctrl-cli.mjs create module inventory ``` Manual follow-up (the README in the generated module repeats this): 1. Add a Prisma model and run `pnpm prisma migrate dev`. 2. Replace the `TODO` markers in the db layer. 3. Add the permission keys to `packages/db/src/permissions-seed.server.ts` (see `toolctrl add permission` below). 4. Run `pnpm typecheck` to generate the route types. ### `toolctrl create route ` Scaffolds a single React-Router-v7 route file at `apps/web/app/routes/.tsx` plus a co-located vitest stub. Flags: - `--top-level` — generate a route without the `:tenant` param. - `--dry-run` — print the would-be writes without touching the filesystem. Example: ```bash node packages/cli/bin/toolctrl-cli.mjs create route app.\$tenant.dashboard ``` The generated file imports `Route` types from `./+types/` (NEVER relative paths) and contains loader, action, default component and an `ErrorBoundary` export. Run `pnpm typecheck` after generation to populate the `./+types/` directory. ### `toolctrl create entity ` Emits a Prisma model snippet plus a timestamped migration directory: - `prisma/_generated/.prisma` — model snippet to paste into `prisma/schema.prisma` - `prisma/migrations/_create_entity_/migration.sql` — placeholder SQL - `prisma/migrations/_create_entity_/README.md` — step-by-step apply instructions The generator does NOT modify `prisma/schema.prisma` automatically. After generation: ```bash # paste the model from prisma/_generated/.prisma into prisma/schema.prisma pnpm prisma migrate dev --name create_entity_ pnpm prisma generate ``` ### `toolctrl add hook ` Scaffolds a plugin-hook registration file under `apps/web/app/hooks/_generated/`. The output is a **forward-compatible stub** for generated hook registration. The plugin runtime and `@toolctrl/plugin-sdk` now exist; keep generated hooks on the scaffolded no-op shape until a focused task wires that specific hook into the runtime. Example: ```bash node packages/cli/bin/toolctrl-cli.mjs add hook onContactCreated ``` ### `toolctrl add event-type ` Scaffolds an EntityWebhook event-type definition with a zod schema, an example payload, and an async handler stub under `apps/web/app/modules/workflowEngine/eventTypes/_generated/`. Event names MUST be dotted lowercase, e.g. `crm.contact.created`. Example: ```bash node packages/cli/bin/toolctrl-cli.mjs add event-type crm.contact.created ``` ### `toolctrl add permission ` Scaffolds two fragments for a new permission key: - `packages/db/src/_generated-permissions/.ts` — seed fragment (paste into `defaultPermissionSeeds`). - `apps/web/app/utils/permissions/_generated/.ts` — frontend permission-mapper stub. The generator intentionally does NOT patch the hand-curated `defaultPermissionSeeds` array in `permissions-seed.server.ts`. The README notes spell out the manual paste step. Example: ```bash node packages/cli/bin/toolctrl-cli.mjs add permission app.inventory.view ``` ## Safety guarantees - Generators **never** overwrite existing files. Re-running a generator with the same name throws and exits non-zero. - All generators support `--dry-run`, which prints the would-be writes without touching the filesystem. - Generators only produce new paths inside the repo (or `prisma/_generated/` snippets that you paste in by hand). The hand-curated source-of-truth files (`schema.prisma`, `permissions-seed.server.ts`, `routes.ts`) are never patched automatically. ## Adding a new generator 1. Create `packages/cli/src/commands/{create,add}/.ts` with a `generate` function and a `defineCommand` export. 2. Re-use the helpers from `_lib/scaffold.ts`: - `validateIdentifier`, `pascalCase`, `kebabCase`, `snakeCase` - `createScaffoldResult` + `writeScaffoldFile` 3. Add a vitest suite next to the command file that asserts: - The expected file paths (in dry-run mode). - Content invariants (e.g. correct import paths). - Refuses to overwrite existing files. 4. Register the command in the relevant `create/index.ts` or `add/index.ts` and update the root `index.test.ts` to keep the registry shape pinned.