PackagescliMenu

@intelligo-dev/cli

The Intelligo CLI: scaffold an AI SaaS app, generate owned source, and check its migrations and upgrades.

Install

Terminal
pnpm add -D @intelligo-dev/cli

create needs no install — run it with pnpm dlx. The scaffold adds the CLI to the new application, which is where intelligo comes from afterwards.

Commands

Terminal
pnpm dlx @intelligo-dev/cli create my-app   # a registry-ready Next.js app, plus the pages you pick
pnpm dlx @intelligo-dev/cli create my-app --items chat,billing-settings --yes   # no questions
pnpm dlx @intelligo-dev/cli create . --name "Acme Audit"   # into the current, empty directory (a .git may be there)
intelligo add <feature>                # generate consumer-owned source (admin-page, maintenance, vitest)
intelligo doctor                       # what is misconfigured, and why it matters
intelligo migrate                      # apply the framework chain
intelligo migrate --check [--json]     # compare the chain with the database, change nothing
intelligo admin grant <email>          # make a signed-up user a platform admin
intelligo upgrade --check              # what a template upgrade would change
intelligo sync [items…]                # install registry pages from this release, seams kept
intelligo sync --check                 # exit 1 when an installed page drifted from the registry

sync: installed pages stay the registry’s

A page arrives as source, through shadcn add, and nothing stops it drifting afterwards — an edit here, a release skipped there. intelligo sync installs items from the registry bundled with this CLI, so the pages match the @intelligo-dev/* packages of the same version, in dependency order. Files an item ships once for you to own (lib/*-config, lib/nav-config.ts… — the seams in the registry’s requires.json) are put back after the install, and message files are merged key by key with your copy winning. The result is recorded in intelligo.manifest.json.

Name the items once — intelligo sync intelligo app-shell chat … — and afterwards a bare intelligo sync updates them. It refuses to overwrite a file you edited by hand unless you pass --force: move the change into a seam first. intelligo sync --check installs nothing, lists every file that is missing, edited, outdated, messages-behind or locale-behind, and exits 1 on any — the gate to run in CI.

Other locales

The registry ships English (messages/en/<item>.json). When the app’s i18n/routing.ts lists more locales, sync --check compares each other locale’s copy of every shipped namespace — messages/<locale>/<item>.json — with the app’s English file on disk (the merged copy, your own keys included) and reports a missing file or missing keys as locale-behind, with the keys. sync never writes another locale: after it adds English keys, translate them, and the check passes again.

create installs the pages you pick the same way: the dependencies first (from the root of a parent pnpm workspace when the new app is one of its members, so the workspace keeps one lockfile), then intelligo sync of the design-system base and every item, one shadcn add each. Scaffold files an item replaces — app/globals.css, the theme provider, lib/utils.ts — move from the app-scaffold record to the registry’s (the feature’s handedOver list), so upgrade --check does not report them as yours forever.

The chosen items are recorded in intelligo.manifest.json before anything is installed, and sync treats a file the scaffold wrote and nobody edited as the registry’s to replace, so when the install is declined or fails, a bare intelligo sync picks up where create stopped. The project name is the directory’s unless --name says otherwise; its slug is the package name, the billing product and the default page title. The scaffold carries its own .gitignore. Under pnpm, an app outside any workspace also gets a pnpm-workspace.yaml that declines the dependency build scripts pnpm 11 would stop the install over and allows the shadcn CLI’s pnpm add at the root (with an .npmrc saying the same to pnpm 9). An app created inside a pnpm workspace gets neither; its next.config.mjs reads the workspace root’s .env.local and .env instead, as doctor and migrate do. When another package in that workspace already has the app’s name (a root named after the product, say), the app’s package is @<scope>/<directory> instead, the scope being the root’s scope or name.

Where doctor, migrate, upgrade and admin read env from

They run outside Next, so they load the env files themselves, never overriding a variable that is already set. Highest precedence first: the shell, the app’s .env.local, the app’s .env, then — when the app is a member of a pnpm workspace — the workspace root’s .env.local and .env. An app in a monorepo whose next.config falls back to the repository root’s .env is therefore checked and migrated against the same DATABASE_URL it boots with.

admin grant: the first platform admin

PLATFORM_ADMIN_EMAILS promotes an address only once it is verified, and a development setup without an email provider verifies none (the console provider logs the verification link instead, outside production). intelligo admin grant <email> writes users.role for a user who has signed up and records an admin.platform_admin.granted audit event, in one transaction. With NODE_ENV=production it refuses unless --force is passed.

Why migrate is not drizzle-kit migrate

Drizzle applies migrations in journal-timestamp order and skips anything numbered out of sequence, so a migration published behind the last applied timestamp would never run and nothing would say so. intelligo migrate selects pending work by content hash, applies it in one transaction, and records it in the same table drizzle uses so both tools agree afterwards.

It also refuses a database that has the schema but no records — the state drizzle-kit push leaves behind. Applying the chain to tables that already exist fails part-way; --check reports the database as unmanaged instead, to be baselined first.

migrate --check exits 1 whenever anything is pending or the database is ahead, which an empty database and a stale one share. A deploy gate that must tell them apart reads --json: one object on stdout whose state is up_to_date, pending, fresh (empty database), unmanaged, ahead or legacy, beside exitCode, chain, applied, pending, unknown, legacy, legacyMissing and adoptable.

add maintenance and its schedule

The route reconciles executions that are ten minutes stale, so it is meant to run every five minutes. add maintenance writes that schedule to a new vercel.json; an existing one is never touched — the entry to add is printed instead. Vercel Hobby runs a cron at most once a day and refuses a deployment that asks for more: there, or on any other host, call the route from your own scheduler with Authorization: Bearer $CRON_SECRET.

Entry points

  • @intelligo-dev/cli

npm · source