# I built a CLI that scaffolds the boring parts of an AI SaaS — here's what it actually generates

> Source: <https://dev.to/armin_burger_ab136b2f8bb1/i-built-a-cli-that-scaffolds-the-boring-parts-of-an-ai-saas-heres-what-it-actually-generates-248f>
> Published: 2026-09-24 09:56:07+00:00

Every AI SaaS project starts with the same two days of nothing-interesting: auth, a users

table, an encrypted place to store provider API keys, a Prisma schema. Then you finally get to

the part you actually wanted to build.

I've been working on a CLI (`@chimerai/cli`) that scaffolds exactly that prefix. This post is

about what it *actually writes to disk* — not what a landing page would claim — because that's

the only thing that matters when you decide whether to adopt a generator.

```
npx @chimerai/cli create my-ai-app
```

Interactive feature selector. Defaults are auth + RBAC + admin dashboard + analytics; AI

features are opt-in. Flags worth knowing:

```
chimerai create my-ai-app --yes              # no prompts, defaults
chimerai create my-ai-app --sqlite           # no Docker needed, DATABASE_URL=file:./dev.db
chimerai create my-ai-app --yes --install    # + npm install
```

`--sqlite` is the one I'd start with if you just want to look at the code. Without it you get

a `docker-compose.yml` for PostgreSQL 16 + Redis 7.

```
my-ai-app/
├── app/
│   ├── layout.tsx
│   ├── page.tsx
│   ├── api/auth/          # NextAuth routes (if auth selected)
│   └── admin/             # admin pages (if selected)
├── components/ui/         # shadcn/ui components
├── lib/
│   ├── prisma.ts          # PrismaClient singleton
│   ├── auth.ts            # auth config
│   └── encryption.ts      # API key encryption
├── prisma/schema.prisma   # only models for selected features
├── .env
├── docker-compose.yml
└── package.json
```

The generated Prisma schema is additive per feature, which is the part I check first in any

scaffolding tool — if it gives me six models when I asked for two, I stop trusting it:

| Selected | Models added | 
|---|---|
| Auth | `User` ,`Account` ,`Session` ,`VerificationToken` | 
| RBAC | `Role` ,`Permission` | 
| Providers | `ModelProvider` | 
| Prompts | `PromptTemplate` | 
| Analytics | `ApiUsage` | 

Feature dependencies are resolved by the CLI rather than left to you: `admin-dashboard`

requires RBAC, `model-providers` requires auth (the encryption key lives in the auth config),

`chat-ui` requires `model-providers`, `rag` requires `model-providers`.

The split is deliberate: the LLM orchestration layer is Python because that's where the

ecosystem lives (LangChain, FAISS, spaCy), and everything type-safe and user-facing stays

TypeScript. The Next.js side proxies to `AI_SERVICE_URL` (default `http://localhost:8002`).

Provider management with encrypted keys, which is the thing everyone re-implements badly:

``` js
const response = await fetch('/api/providers', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'OpenAI GPT-4',
    provider: 'openai',      // | 'anthropic' | 'ollama' | 'custom'
    apiKey: 'sk-...',
    isActive: true,
  }),
});
```

Keys are stored AES-256 encrypted at rest using `ENCRYPTION_KEY` from `.env`, never in the

database in plaintext. `provider: 'custom'` takes any OpenAI-compatible `baseURL`, which is how

you wire up a self-hosted vLLM or an OpenRouter-style gateway without the core knowing about it.

You can also add providers through the admin UI and test the connection before using it — small

thing, saves an annoying debugging loop.

RBAC uses `resource:action` permission strings, checked in API routes:

``` js
import { requirePermission } from '@/lib/auth/require-permission';

export async function GET(req: NextRequest) {
  const permissionError = await requirePermission('posts:read');
  if (permissionError) return permissionError;
  // ...
}
```

`admin:*` is a wildcard. Roles carry permission lists; there's a seeded admin

(`admin@example.com` / `admin123` — remove it, obviously).

`@chimerai/model-providers`, `@chimerai/admin-ui` etc. exist
in the monorepo. `chimerai create` therefore generates `chimerai add` requires Next.js 15+ (generated route handlers use the async `params` API).
The CLI detects the version and warns below 15. Standalone `create` output runs on port 3001
by default to avoid colliding with another dev server.`create-next-app` plus a library like
LangChain is a perfectly reasonable starting point — pick the generator that matches the
problem.
You don't have to start from a generated project:

```
cd my-nextjs-app
npx chimerai add auth
npx chimerai add model-providers
npx chimerai add chat-ui
pnpm install
npx prisma db push && npx prisma db seed
pnpm dev
```

The CLI finds the project root by walking up for a `.chimerai` marker, then falls back to a

registry in `~/.chimerai/projects.json`, then `--dir`. In a monorepo, point `--dir` at the

Next.js app (`apps/frontend`), not the workspace root — the root has no `app/` and no `prisma/`.

`chimerai doctor` runs health checks on env vars, DB connectivity, and installed components,

which is mostly there because I forgot to set `ENCRYPTION_KEY` often enough to be embarrassing.

`chimerai add rag` — FAISS index, chunking, and the `/api/rag/query` round trip
Repo: `github.com/armbur19-collab/chimerai-kickstart` · site: chimerai.dev
