@lunora/cli
Version:
The Lunora CLI: init, dev, deploy, codegen, migrate, seed, doctor, insights, logs, registry, and the rest of the project commands
177 lines (151 loc) • 10.7 kB
Markdown
---
name: lunora
description: Routes general Lunora requests to the right Lunora skill and gives the
shared mental model (codegen loop, generated `api`/`internal` references,
review commands, add-on capabilities, the `@lunora/mcp` server). Use when the
user asks which Lunora skill to use, gives an underspecified task in a project
with a `lunora/` directory or `lunorash`/`@lunora/*` dependencies, asks to
review or audit Lunora code, asks about an add-on without its own skill
(queues, workflows, agents, AI, flags, payments, notifications), or wants to
expose a deployment to an AI agent over MCP.
---
# Lunora
Lunora is a type-safe, real-time backend on Cloudflare Workers + Durable
Objects. Functions are chainable `query` / `mutation` / `action` builders; state
lives in one `ShardDO` per app by default (SQLite, OCC, hibernated WebSocket
subscriptions). `.shardBy(key)` partitions a table across many DOs and
`.global()` replicates it to D1 (or Postgres/MySQL over Hyperdrive) for
cross-region reads. Codegen produces the types the client and server share.
If a project-level `AGENTS.md` / `CLAUDE.md` exists, read it first; it overrides
these defaults. If a more specific Lunora skill matches, switch to it.
## The feedback loop
```bash
lunora codegen # regenerate lunora/_generated/ from lunora/schema.ts + function files
lunora verify # wrangler config + codegen dry-run + tsc --noEmit; writes nothing
```
`lunora codegen` writes `lunora/_generated/` (`api.ts`, `internal.ts`,
`server.ts`, `dataModel.ts`, `shard.ts`, `app.ts`, `openapi.ts`, …; it prints
the exact list) and reports schema advisories and platform diagnostics. It does
not run `tsc`, so use `lunora verify` as the check that the code you wrote is
done. Its `tsc --noEmit` step runs only when the project has a `tsconfig.json`
(otherwise it warns and skips) and is turned off by `--no-typecheck`. `lunora dev` re-runs codegen on save, and `lunora deploy` runs it too.
Scaffolded projects gitignore `lunora/_generated/`; never hand-edit it.
## Route to the right skill
- New project, or adding Lunora to an existing app: `lunora-quickstart`
- Writing or reviewing schema + functions (the core authoring rules):
`lunora-functions`
- Wiring live data into a client (hooks, optimistic updates), or choosing
between a live query, stream, live topic, whisper, room and socket (including
pushing a server event to the browser): `lunora-realtime`
- Authentication (email/password, OAuth, magic link, OTP): `lunora-setup-auth`
- Transactional email: `lunora-setup-mail`
- R2 file storage (signed upload/download): `lunora-setup-storage`
- Deferred work (`ctx.scheduler`) and cron jobs: `lunora-setup-scheduler`
- Querying an existing Postgres/MySQL database from an action (`ctx.sql`,
non-reactive): `lunora-setup-hyperdrive`
- Postgres/MySQL as a reactive `.global()` backend, or migrating a D1
`.global()` dataset onto it: `lunora-setup-hyperdrive-global`
- Building a reusable capability (registry item or `@lunora/*` package):
`lunora-create-package`
- Schema/data migrations: `lunora-migration-helper`
- Deploying (wrangler, bindings, secrets, the drift gate): `lunora-deploy`
- Slow queries, scans, write conflicts: `lunora-performance-audit`
- Launch readiness, surprise Cloudflare bills, public routes reaching paid products: `lunora-cloudflare-audit`
### Reviewing existing `lunora/` code
There is no review skill because two commands do the mechanical part. Run them
before reading code by hand:
```bash
lunora verify # does it build and typecheck
lunora advisor # static + runtime lint set, scored per procedure
```
`lunora advisor` covers RLS coverage, ownership and identity checks, fail-open
guards, input validators, unindexed reads, non-deterministic calls in queries,
SQL interpolation and leaked secrets. It prints each finding's lint id and
category, so read its output rather than guessing rule names. `--entry
<file>#<export>` inspects one procedure; `--min-score` / `--baseline` gate CI.
Once it is clean, a by-hand pass against `lunora-functions` is worth the tokens.
## Core mental model
- Functions live in `lunora/**/*.ts` and import their builders from the
generated server module: `query` (reactive read), `mutation` (transactional
write), `action` (side effects, `fetch`, no direct db). `internalQuery` /
`internalMutation` / `internalAction` are not callable from clients.
- Schema lives in `lunora/schema.ts` (`defineSchema` + `defineTable`, validators
from `v.*`), imported from `lunorash/server` or `@lunora/server`.
- Function references mirror the folder tree: `lunora/billing/invoices.ts`
exporting `create` is `api.billing.invoices.create`. Public references come
from `_generated/api`, internal ones from `_generated/internal` (kept separate
so client bundles never carry internal names). A reference is a plain
`{ __lunoraRef: "billing_invoices:create" }` object; there is no `anyApi`
proxy.
- Reads go through indexes: declare `.index("by_x", ["x"])` and query with
`ctx.db.query("t").withIndex("by_x", (q) => q.eq("x", value))`, not
`.filter(...)`.
- Clients subscribe over WebSocket; `useQuery` re-renders when a mutation
changes the queried rows. Subscriptions run under the socket's verified
identity, so `rls()` and `ctx.auth` apply to live updates too.
## Capabilities without a dedicated skill
Most add-ons install as a registry item: `lunora add <feature>` (or
`lunora registry add <item>`) scaffolds the `lunora/` glue, wrangler bindings
and `.dev.vars` entries, then prints post-install steps. Browse with
`lunora registry list`, preview with `lunora registry view <item>`. Read the
installed item's README and the package's `docs/` for the API.
| Goal | Install / package |
| --------------------------------------------- | ---------------------------------------------------------- |
| Background jobs on Cloudflare Queues | `lunora add queue` → `@lunora/queue` (`ctx.queues`) |
| Durable multi-step workflows | `lunora add workflow` → `@lunora/workflow` (`ctx.runStep`) |
| Durable AI agents (tool loops, HITL, memory) | `@lunora/agent` (`defineAgent`) |
| Workers AI / RAG | `lunora add ai` → `@lunora/ai` (`ctx.ai`, `defineRag`) |
| Feature flags (OpenFeature) | `lunora add flags` → `@lunora/flags` (`ctx.flags`) |
| Payments (Stripe / Polar) | `lunora add payment` → `@lunora/payment` |
| Agent payments (x402) | `@lunora/x402` |
| Push / in-app / webhook notifications | `@lunora/notify` (`ctx.notify`, `ctx.push`) |
| Rate limiting | `lunora add ratelimit` → `@lunora/ratelimit` |
| Headless browser (action-only) | `lunora add browser` → `@lunora/browser` (`ctx.browser`) |
| Cloudflare Containers | `@lunora/container` (`defineContainer`, `ctx.containers`) |
| Presence / who's-here | `lunora add presence` |
| Cloudflare Access (Zero Trust) identity | `lunora add cloudflare-access` |
| Backup / restore | `lunora add backup` |
| Testing (in-memory harness, agent doubles) | `@lunora/testing` (`lunoraTest`) |
| Deterministic seed data | `@lunora/seed` + `lunora seed` |
| Local-first replica / offline mirror | `@lunora/replica` |
| Exposing the deployment to AI agents over MCP | `@lunora/mcp` (below) |
Inside the Lunora monorepo, `vis generate lunora-<kind>` scaffolds code:
`query`, `mutation`, `action`, `http-route`, `table`, `cron`, `container`,
`workflow`, `queue`, `step`, `agent`, `package`, plus the no-name singletons
`flags`, `auth-do` and `collections` (`vis generate --list` for the set). Pass
names as `--name=value`: vis parses `--name foo` as `--name=true` plus a stray
positional.
## Exposing a deployment over MCP
Two entry points:
- `lunora mcp install` wires an editor (Claude Code, Cursor, …) to the hosted
docs server plus this project's dev server; `lunora mcp serve` is the stdio
server those entries spawn (`--allow-writes`, `--token` opt into more).
- The `lunora-mcp` binary from `@lunora/mcp` fronts a deployed Worker. It needs
`LUNORA_URL` and `LUNORA_ADMIN_TOKEN`. Every tool reads admin-gated routes, so
the token cannot be scoped down; safety comes from the gates below.
Always advertised: `lunora_list_functions`, `lunora_list_tables` (`.global()`
tables with row counts), `lunora_get_function_schema`, `lunora_run_query`
(read-only), and `lunora_explain_error`, which explains an error `code` or
message from the catalog compiled into `@lunora/errors` and needs no
deployment. Use it before guessing what a Lunora error means.
Everything else sits behind an env gate that both hides the tool and refuses it
at dispatch. All default to off; enable one only when the answer to "may a model
see this?" is yes.
- `LUNORA_MCP_ALLOW_WRITES` → `lunora_run_mutation`, `lunora_run_action`.
- `LUNORA_MCP_ALLOW_OBSERVABILITY` → the five `lunora_get_*` tools (logs,
issues, advisories, query insights, migration status). Read-only, but they
return production log lines and error messages, which then reach the model
provider.
- `LUNORA_MCP_ALLOW_DATA_READS` → `lunora_find_related`. It returns raw rows
read through the admin writer, with RLS and column masks bypassed. It is kept
separate from observability so enabling logs never also hands over rows.
- `LUNORA_MCP_ALLOW_AGENTS` (with `LUNORA_MCP_AGENTS="name:description;…"`) →
the `agent_<name>` tools and `lunora_agent_status`.
Writes are a two-step handshake. The first `lunora_run_mutation` /
`lunora_run_action` call does not execute; it returns `status:
"action_required"` with `proposedAction`, an `actionDigest`, and an `expiresAt`
ten minutes out. The client shows that to a human, then repeats the call with
identical `functionPath`, `args` and `shardKey` plus `confirmed: true` and the
digest. Any edit needs a fresh proposal, and an expired digest is refused. The
digest proves the write that runs is the one proposed; it does not prove a human
saw it, so enabling writes means trusting the client to ask.