DEV Community

Cover image for I built a CLI that scaffolds the boring parts of an AI SaaS — here's what it actually generates
Armin Burger
Armin Burger

Posted on

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

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.

The one command

npx @chimerai/cli create my-ai-app
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

--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.

What's in the output

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
Enter fullscreen mode Exit fullscreen mode

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.

Stack, stated plainly

  • Next.js 14/15 App Router, API routes serve as the backend — no separate Node service
  • TypeScript everywhere except the AI service, which is Python (FastAPI + LiteLLM)
  • Prisma on PostgreSQL, NextAuth for credentials + OAuth
  • Tailwind + shadcn/ui

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).

The bit I'd point at as "not boilerplate"

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

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,
  }),
});
Enter fullscreen mode Exit fullscreen mode

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:

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

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

admin:* is a wildcard. Roles carry permission lists; there's a seeded admin
(admin@example.com / admin123 — remove it, obviously).

Honest limitations

  • Packages aren't on npm yet. @chimerai/model-providers, @chimerai/admin-ui etc. exist in the monorepo. chimerai create therefore generates standalone projects with the selected features inlined as code templates — you get readable code you own, not a dependency you can't inspect. For the full monorepo with shared packages you'd clone the repo instead.
  • 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.
  • The feature set is opinionated toward the SaaS shape (auth, providers, prompts, chat, RAG, admin). If your app doesn't need that shape, a plain create-next-app plus a library like LangChain is a perfectly reasonable starting point — pick the generator that matches the problem.

Adding to an existing project instead

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
Enter fullscreen mode Exit fullscreen mode

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.

Next in this series

  1. Streaming chat: SSE from FastAPI through a Next.js proxy route
  2. RAG with chimerai add rag — FAISS index, chunking, and the /api/rag/query round trip
  3. Prompt templates in the DB instead of in git, and what breaks when you do that

Repo: github.com/armbur19-collab/chimerai-kickstart · site: chimerai.dev

Top comments (0)