Overview
Consumer projects pin the CLI in go.mod with the Go 1.24+ tool directive (written by new project), so every teammate runs the same version:
go tool gpsystem <command>
gpsystem
├── new project <name> --module-path <path> [--dir] [--db pgx|bun]
├── new module <name> [--surface <name>]
├── new migration <name>
├── add surface <module> <surface> [--db <name>]
├── add domain <module> [--db <name>]
├── add handler <module> <surface> <operation> --method GET --path "/x/{id}"
├── add auth [--module auth] [--social discord,facebook,apple,google]
├── add event <module> <name>
├── add listener <module> <event> <name> [--queue <q>]
├── add job <module> <name> --cron "0 3 * * *"
├── add mail <module> <name>
├── add notification <module> <name>
├── add worker
├── add realtime
├── add db <module> <name> [--connector pgx|bun]
├── add seeder <name>
├── add compose
├── add docker
└── version
A quick per-command orientation:
new modulegenerates a module: entry point, first surface, contract, plus the module root package and the module-levelrepository/skeleton.add surfaceadds another surface to an existing module.add handleradds one operation to an existing surface.add authscaffolds a complete auth module (DB-backed RBAC schema, register/verify-email/login/refresh/logout/password-reset endpoints, plus a social login layer) and wires authentication automatically: it insertsapi.Use(server.IdentityMiddleware(cfg.JWT))into every module entry point at thegpsystem:api-middlewareanchor, and recordsauth: <module>ingpsystem.yaml, so modules generated later render the line themselves. On an older project whosemain.golacks the anchor, it prints the line to paste in instead of failing. See Authentication.add eventcreates an event and wires an outbox dispatcher into the module.add listenersubscribes to an event (--queueroutes it to a named asynq queue).add jobschedules a recurring handler.add event/add listener/add jobscaffold background work for thecmd/workerbinary (see the worker concepts); every project includes the worker, so these commands are ready to use right away.add mailgenerates a module-owned mail notifier (payload + templates) and, on a project's first notifier, wires a sharedmail.Mailerinto every entry point.add notificationgenerates a module-owned, multi-channel notification (payload +Via+ per-channel content) and, on a project's first notification, wires a sharednotify.Senderinto every entry point plus thenotificationstable migration.add realtimescaffolds the dedicated, horizontally scalablecmd/realtimegateway that delivers notifications live to connected clients.add workeradds the worker layer (cmd/worker, per-modulelisteners//jobs/subpackages, the outbox migration) to a project that doesn't have one yet.new migrationcreates a numbered goose SQL migration in themigrations/directory.add dbadds another, independent database connection to a module (its own pool, config andDependenciesfields, pgx or bun);add surface --dbbinds the module's shared repository skeleton to one.add domainadds the module root package's domain files (<module>.go,errors.go) and the module-levelrepository/skeleton to a module that doesn't have them yet (purely additive, touches no anchors).add composeregeneratescompose.ymlstandalone (base stack + one service per manifest module);add dockerregenerates the Dockerfile/.dockerignore.
Global flags on every command: --dry-run (print the plan, write nothing) and --force (allow overwriting). new project additionally accepts --replace/--replace-dev, a contributor/dev-mode feature for pointing generated go.mod replace directives at local module checkouts; ordinary consumer projects never need them, see new project: contributor mode.
Safety rules
- The full file plan is computed up front; if any target exists, the whole run aborts before writing anything (unless
--force). No partial writes. With--forcethe run proceeds but prints anoverwriting <path>line for every existing file it replaces first, so a regeneration never clobbers files silently. - Generated
.gofiles are formatted with goimports; a template that renders invalid Go fails loudly. - Existing files are modified only at anchor comments, never anywhere else.
- Insertions are idempotent: re-running a generator never duplicates a block.
Anchors
| Anchor | File | Inserted by |
|---|---|---|
gpsystem:modules | spec/typespec/main.tsp | new module / add surface |
gpsystem:imports, gpsystem:surfaces | surfaces/register.go | new module / add surface |
gpsystem:operations | <surface>.tsp | add handler |
gpsystem:modules, gpsystem:deps-init(<module>) | cmd/worker/main.go | new module / add worker / add event |
gpsystem:listeners | listeners/register.go | add listener |
gpsystem:jobs | jobs/register.go | add job |
gpsystem:pools, gpsystem:closers | cmd/<module>/main.go, cmd/worker/main.go | add db (named connection's pool / closer), add mail (shared mailer construction), add notification (shared notifier + publisher construction/closer) |
gpsystem:config | internal/platform/config/config.go | add db (named connection's config field), add mail (Mail smtp.Config), add realtime (Realtime realtime.Config) |
gpsystem:dependencies-init, gpsystem:deps-init(<module>) | cmd/<module>/main.go, cmd/worker/main.go | add mail (Mailer: mailer,), add notification (Notifier: notifier,) |
gpsystem:topics | cmd/realtime/main.go | add realtime (shared topic-channel authorization) |
gpsystem:api-middleware | cmd/<module>/main.go | add auth (api.Use(server.IdentityMiddleware(cfg.JWT)) on every module's api router) |
Two further anchors are seams for your own code rather than generator targets: gpsystem:middleware (in the server.Run option list, where WithStack/WithMiddleware options go; see the framework server) and gpsystem:surface-middleware (the first line of each surface's Route block, the place for route-group middleware like r.Use(server.RequireRole("admin"))).
The manifest: gpsystem.yaml
Written by new project at the project root; the CLI finds the project by walking up to it.
version: 1
name: shop
module: github.com/acme/shop
framework: github.com/gp-system/framework@v0.0.0
db: pgx
auth: auth # the module add auth generated; absent until it runs
modules:
news:
surfaces: [api, admin]
dbs:
- name: analytics
connector: bun
surfaceDBs:
admin: analytics
It is the generators' source of truth for what exists (add surface news admin fails fast if the module is missing or the surface already exists), and it cross-checks module against go.mod. dbs/surfaceDBs are only present once a module has named connections (via add db); manifests without them load unchanged. auth appears once add auth has run and names the module it generated: new module reads it to render the identity-middleware line into new entry points automatically.
--engine flag.