@aventara/cli
v1.0.0-rc.12
Published
Scaffolds an Aventara server: `aventara new` for a new NestJS project, `aventara init` for an existing one.
Readme
@aventara/cli
The aventara command: it scaffolds an Aventara server on NestJS 12 or Next.js 16.
npm i -g @aventara/cli@rc # or run it once: npx @aventara/cli@rc <command>
aventara new my-api # a new NestJS project with Aventara
aventara new my-app --host next # a new Next.js app with Aventara (create-next-app, then init)
aventara init # Aventara added to the NestJS 12 or Next.js 16 project in this directory
aventara skill # refresh the agent skill for the installed @aventara/coreIt is run once, globally or through npx; it is not a dependency of the projects it writes. The frontend's side
(framework.client.ts and the generated client) belongs to @aventara/client (npx @aventara/client@rc init).
aventara new <name>
Runs the pinned nest new (npx -y @nestjs/[email protected] new …, or pnpm dlx … under pnpm), checks that its
src/app.module.ts is what that version writes, then runs exactly aventara init's pipeline over the new project and
installs it. A directory that exists and is not empty is refused before anything runs.
Once installed, the project gets its first git commit, Initial Aventara Scaffold, with the lockfile in it and
without .env (it is in .gitignore). No commit is made, and one line says why, when git is not installed, when git has no
user.name/user.email, or when the directory is already inside a git repository (a monorepo: commit it there).
--skip-git makes no repository and no commit.
aventara new <name> --host next
Runs create-next-app@latest <name> --ts --app --src-dir --import-alias "@/*" --use-<npm|pnpm> --skip-install, then
aventara init's pipeline over the new app, then the install. create-next-app asks its own questions on your terminal;
with --yes it takes its defaults instead (and is given --yes too), which is also what a run with no terminal needs.
Its own flags go after --, as they are: aventara new my-app --host next --yes -- --tailwind --eslint. --skip-git
becomes its --disable-git. --host defaults to nest.
aventara init
Runs inside an existing NestJS 12 or Next.js 16 project, telling them apart by package.json: @nestjs/core is
NestJS, next is Next.js, and a project depending on both or neither is refused in one sentence. A Next.js app must use
the App Router under src/app/ and map @/* to ./src/* in tsconfig.json (as create-next-app writes it), and have
no ORM yet. The rest of this section is NestJS's; a Next.js app gets its own files.
In a NestJS project, it refuses another Nest major, TypeScript 7, a yarn-only lockfile, or a project already initialized.
In a project with no ORM yet, the ORM and the database are asked (or taken from the flags), and everything below is written.
In a project that already uses Prisma, the installed Prisma must be one an Aventara adapter supports, or the run is
refused in one sentence with nothing written. Today that is Prisma 7 (^7.10.1) on SQLite or PostgreSQL, with a driver
adapter and the prisma-client generator. Your schema, Prisma config and service are reused and never written, and
your .env only gains a CORS_ORIGINS line at its end when it has none. Init finds the class extending PrismaClient
and the module exporting it, or asks.
Questions and flags
Every question has a flag. On a terminal, unanswered questions are asked; anywhere else (CI, a script) pass the flags
or --yes, or the run stops in one sentence naming what is missing, before writing anything. A question with one
possible answer is shown, not asked.
| Flag | Question | Default |
|---|---|---|
| <name> (new only) | The project directory. | required |
| --host <nest\|next> (new only) | The framework to build on; init reads it from package.json. Not asked. | nest |
| --orm <alias> | The ORM, as an adapter's alias: prisma7 (Prisma 7, @aventara/prisma7-adapter). --help lists the aliases that exist. | the only one there is |
| --db <provider> | The database: the adapter's providers (sqlite, postgresql). | sqlite |
| --package-manager <npm\|pnpm> | The package manager. | the lockfile's, else the one that launched the CLI, else npm |
| --prisma-service <path#Export> (init) | Your Prisma service, when more than one, or none, is found. | the one found; none → write src/prisma.service.ts |
| --prisma-module <path#Export> (init) | The Nest module that provides and exports it. | the one found |
| --skip-install | Write the files; print the install instead of running it. | |
| --skip-git (new) | Passed to nest new. | |
| -y, --yes | Accept every unanswered default, and replace existing content that differs. | |
| --verbose | Show the output of nest new and the install as they run. | |
| -- <args> (new --host next) | Passed to create-next-app as they are, after its own. | |
While it works, each step is shown on stderr, such as [3/4] Installing dependencies (npm install). On a terminal it
has a spinner and the seconds so far; in a pipe, in CI (CI) or with NO_COLOR it is one plain line per step
(… done (12.4s)). The output of nest new and the install is shown only if it fails, or with --verbose.
Existing content that differs from what would be written (a .env key, a script, a file) is listed, and replaced only
when you confirm, or with --yes; where nobody can be asked, the run stops and touches nothing.
What it writes (no ORM yet, SQLite, npm)
| Path | Owner afterwards |
|---|---|
| prisma/schema.prisma: prisma-client generating into src/generated/prisma, a User model | you |
| prisma.config.ts: loads .env itself (Prisma does not) | you |
| src/prisma.service.ts: PrismaService extending the client with the driver adapter, refusing a missing DATABASE_URL before the server listens; PrismaModule | you |
| src/aventara.config.ts: aventaraConfig(prisma): the api mount path (/api), a diagnostics sink that logs the raw error of a server failure, with its stack, through Nest's Logger, and the exposed/exemptions, pipelines and resources you add | you |
| src/app.module.ts: one edit that adds the imports, PrismaModule and AventaraModule.forRootAsync({ … }); printed instead when the file is not the shape expected | you |
| test/app.e2e-spec.ts: one edit that adds a GET /api/_contract → 200 test after Nest's GET / test; left alone when the file is not Nest's | you |
| src/main.ts: one edit after NestFactory.create: app.enableCors(…) for the comma-separated origins in CORS_ORIGINS, and CORS off when it is unset or empty, then a commented-out platform-level rate limiter (express-rate-limit; Nest's @nestjs/throttler is a guard, which does not run on Aventara's routes); left alone when the file already configures CORS, printed when it has no such line | you |
| .env (DATABASE_URL, and CORS_ORIGINS="http://localhost:5173,http://localhost:3001" appended unless a line sets it), .gitignore (/src/generated/, /dev.db*, .env) | you |
| package.json: @aventara/* at this CLI's version, prisma, @prisma/client and the driver at the adapter's declared range, so the first install takes the newest Prisma 7; aventara:prepare, run by postinstall and by prebuild, prestart, prestart:dev and prestart:debug, and the .env on the start scripts and test:e2e (--env-file, --env-file-if-exists) | you |
| pnpm-workspace.yaml (pnpm only): allowBuilds for Prisma's and SQLite's install scripts | you |
| src/generated/prisma/**, src/generated/aventara/discovery.artifact.ts | generated by aventara:prepare (run on every install, build and start, so a schema change needs only a restart); never edit |
| .claude/skills/aventara/ (SKILL.md, reference/*.md, .aventara-skill.json): the agent skill your AI coding agent reads | Aventara's: aventara skill refreshes it, and keeps a file you changed unless --force |
| AGENTS.md: an Aventara section between <!-- aventara:start --> and <!-- aventara:end -->, appended to an existing file | you, except the marked section |
What it writes in a Next.js app
Everything Aventara is in one folder, src/aventara/, at the paths @aventara/next declares and aventara-build
reads.
| Path | Owner afterwards |
|---|---|
| prisma/schema.prisma, prisma.config.ts | you (as in a NestJS project) |
| src/prisma.ts: prisma(), the app's one Prisma client with the driver adapter, made on first use and kept across next dev reloads | you |
| src/aventara/config.ts: aventaraConfig, as in a NestJS project, logging through console.error | you |
| src/aventara/adapter.ts: the adapter's description | you |
| src/aventara/server.ts: Aventara({ ...aventaraConfig, adapter, prebuilt, state }) exporting handlers, avClient, avApp and ready; @/aventara/server | you |
| src/app/api/[...aventara]/route.ts: every method of handlers | you |
| src/instrumentation.ts: awaits ready() on the Node.js runtime, so a bad configuration stops next start. An instrumentation file of yours is never replaced, even with --yes: the lines to add are printed | you |
| next.config.ts: serverExternalPackages: ["@aventara/core", "@aventara/prisma7-adapter"] added to its configuration object, so Node.js loads them once per server process (one core and one Prisma client for every route and page). Added to create-next-app's config, or to any whose const nextConfig = { object has no serverExternalPackages; written as a new next.config.ts when the app has none. A config that already names serverExternalPackages, or is written another way, is never changed, even with --yes: the line to add is printed | you |
| package.json: @aventara/core, @aventara/next, the adapter and @aventara/build at this CLI's version, server-only, Prisma and its driver at the adapter's declared range (the newest Prisma 7 on the first install); aventara:prepare (ending in aventara-build --client), run by postinstall and prebuild | you |
| .env (DATABASE_URL), .gitignore (/src/generated/, /dev.db*, /src/aventara/generated/, /src/aventara/client.ts, /src/aventara/universal.ts, .env) | you |
| src/aventara/client.ts, src/aventara/universal.ts, src/aventara/generated/ | generated by aventara:prepare; never edit |
No CORS (the browser calls the same origin), no Pages Router, no proxy, and no MCP route: an mcp endpoint you add on
Next.js needs authorization. Next steps: npx prisma db push, then npm run dev.
Next steps it prints
npx prisma db push # create the tables (init never touches a database); pnpm: pnpm exec prisma db push
npm run start:dev # GET http://localhost:3000/api/_contract
npx @aventara/client@rc init # in your frontend; pnpm: pnpm dlx @aventara/client@rc initThe frontend command is the package manager's own runner (npx, or pnpm dlx under pnpm) and, while the CLI is a
prerelease, carries its dist-tag (@rc), so the client comes from the same release.
With PostgreSQL, set DATABASE_URL in .env first (init writes Prisma's placeholder, and never asks for a secret).
Calling it by hand
With the server running, the starter User model answers at /api:
# 1. The contract. Its protocol.hash is what every resource request sends as Aventara-Contract-Hash.
HASH=$(curl -s http://localhost:3000/api/_contract | node -p 'JSON.parse(require("fs").readFileSync(0, "utf8")).protocol.hash')
# 2. find.many on User, with both identity headers.
curl -s -X POST http://localhost:3000/api/_resources/User/find/many \
-H 'Content-Type: application/json' \
-H 'Aventara-Protocol-Version: 1' \
-H "Aventara-Contract-Hash: $HASH" \
-d '{}'
# {"data":[],"code":"A1000","cause":null}Leave out an identity header and the answer is 400 A2000, naming the header that is missing. The hash changes
whenever the schema or the configuration does: fetch it again, and regenerate the client.
Resource keys are the model names, as written. model User is User everywhere: on the wire
(/_resources/User/find/many) and in the generated client (avClient.User.find.many({})). Nothing is renamed,
lower-cased or pluralized.
Results are read-only. A list result is a readonly array, so type it readonly User[]:
const users: readonly User[] = await avClient.User.find.many({});.
aventara skill
Writes the agent skill for the @aventara/core installed in the project in this directory: its version, and its
ERRORS.md as reference/errors.md. It reads those files and runs none of the project's packages. A skill file it
wrote before is replaced; one you changed is kept, with a warning, unless you pass --force. In AGENTS.md only the
text between the Aventara markers is replaced (or the section appended), and CLAUDE.md is never written. Run it with
the CLI at your @aventara/core version: npx @aventara/cli@<version> skill.
Supported
- NestJS 12.
- Node
^22.23.4 || >=24.22.1. On any other Node the bin refuses in one sentence naming that range. On Node 22 use npm ≥ 11, because Node 22's bundled npm 10 cannot install Nest 12's own scaffold. A CommonJS project's jest e2e needs Node ≥ 24.9, which is Nest's and Jest's limit. - npm and pnpm. Yarn is not supported.
- ESM and CommonJS projects.
- Prisma 7 (
^7.10.1) on SQLite and PostgreSQL.
The ORMs, majors, databases and drivers on offer are generated from the Aventara adapter packages that exist
(src/catalog/adapter.catalog.generated.ts), never listed by hand, so a future @aventara/prisma8-adapter appears
once it exists.
License
PolyForm Shield 1.0.0 with an additional permission. It is free to use, including in commercial applications, but you
may not use it to build a product that competes with Aventara itself. See LICENSE and
LICENSE-ADDITIONAL-PERMISSION.md.
