# Getting started

```
bunx create-ultimate myapp && cd myapp && bun run setup && bun run dev
```

Building a blog or an API rather than a dashboard? Add `--shape blog` or `--shape api` to the
first command: [Shapes](/shapes) says what each writes and leaves out.

**Four commands, and `bun run setup` is one of them.** It is the scaffold's own script, six steps —
`bun install`, an `.env.development.local` touch, `x db gen "initial"` when
`packages/db/migrations` holds no `.sql`, `x db migrate`, `x db seed`, `x manifest` — idempotent, so
re-running it after a pull is safe. The last step is not optional either: `x.manifest.json` is a
projection of the loaded app, nothing else ever runs the command, and `x verify`'s `manifest` step
refuses its absence with `X_MANIFEST_MISSING`. `bunx create-ultimate myapp && cd myapp && x dev` is what this page said until 2026-08-23 and
it does not work: `x new` writes files and installs nothing, so the app has no `node_modules`, no `x`
of its own, and `x dev` stops on `X_BUILD_FAILED` — *"Could not resolve `@ultimat3/ui`. Maybe you
need to `bun install`?"*

**PowerShell and cmd run the same commands.** `bun run setup` runs the app's `bin/setup.ts` —
TypeScript that Bun executes, no bash and no WSL — and `bun run dev` and `bun run verify` are
`x dev` and `x verify`. Windows PowerShell 5.1 has no `&&`: type the commands one per line there
(PowerShell 7 and cmd take the line as written).

No Docker daemon, no `.env` scavenger hunt, no service to provision — `.env.development` ships
committed non-secret defaults and an empty `DATABASE_URL` means embedded PGlite.

No service to install either — bun and git are the whole prerequisite list → [Bare VM](/bare-vm).

`bun run setup`, then `x dev`, measured on a fresh scaffold `As of 2026-08-23` (6.7s for setup on a warm
Bun cache) — recorded **before** the `.env.development.local` touch and `x manifest` joined the
script, so neither is in the transcript or the number, and the closing line now reads
`next: bun run dev`:

```
$ bun run setup
  packages/db/migrations/20260823155026_initial.sql
✓ migration 20260823155026_initial generated
✓ migrations applied
✓ 1 seed(s): 2 inserted, 0 updated, 0 already stored
setup complete — next: x dev

$ x dev
  roles web, sync, worker, scheduler
  panels routes, timeline, live, jobs, db, mail, cache, policy, manifest, services, boundaries
  manifest /path/to/myapp/x.manifest.json
  introspect http://localhost:3000/_x
✓ dev ready on http://localhost:3000 — /_x mounted (11 panels), db=embedded events=embedded storage=embedded mail=embedded cdn=none
```

`replicator` is the fifth role and is **opt-in** — it takes a replication slot, which is not
something every `x dev` should do by starting: `x dev --role web,sync,worker,scheduler,replicator`.

## What you get before writing a line

Measured on a fresh scaffold, `As of 2026-08-23`.

| Thing | Where | Detail |
|---|---|---|
| Embedded Postgres | `.x/pgdata` | PGlite in-process, no local install; `bun run setup` migrates and seeds it |
| In-process events | same process | `events=embedded`; the same seam NATS fills in prod |
| S3 | `./.x/storage` | real S3 API surface via `Bun.s3` |
| Mail | `/_x` inbox | captured, never sent |
| Redis (the shared cache tier) | not started | the scaffold's `cache.tiers` is `['request-memo', 'lru']`; adding `'redis'` is what builds the shared rung |
| `/_x` dev panel | `http://localhost:3000/_x` | 11 panels: routes, timeline, live, jobs, db, mail, cache, policy, manifest, services, boundaries |
| MCP server | `x mcp serve` | 19 dev tools over `stdio` or `http` (14 framework tools plus five `ui.*`). **Not started by `x dev`** — `/mcp` on the dev server is 404 |
| Landing page | `apps/web/site/page.tsx` | `render: 'static'`, `hydrate: 'never'`, `budget: { js: '0kb' }`, real meta + JSON-LD |
| Dashboard | `apps/web/app/dashboard/page.tsx` | `render: 'stream'`, `hydrate: 'visible'`, `budget: { js: '60kb' }` (one island, the theme toggle), behind `policy: { permission: 'dashboard:read' }`. A `<Hole>` is the next read it does not wait for ([Routes and render modes](/routes-and-render-modes#what-a-hole-does)) |
| Admin app | `apps/admin/` | one `ssr` page; your actions reach an agent through `mcp: { expose: true }` and `x mcp serve`, not through this app |
| Green gate | `bun run verify` — `x verify` | 21 steps, in this order: typecheck, lint, boundaries, filesize, package-shape, errors, unit, contract, live, job, exercise, e2e, eval, drift, contract-diff, budgets, seo, i18n, policy, manifest, roadmap. `budgets` weighs `.x/build-stats.json` and runs `x build --target static` itself when that file is missing or older than the source, so `x verify` alone is the gate on an app nobody has built (`As of 30.0`). The first-run verdict on a fresh scaffold is measured by CI, never asserted here |

`x dev` runs **every role in one process** with isolation simulated, not skipped: separate ALS contexts, a real Postgres queue, real logical replication, a real SIGTERM drain on `x dev restart`. Nothing in the framework branches on `if (dev)` — only the drivers differ.

## 1. Write your first action

```
x g action publish-post
```

```ts
export const publishPost = action({
  input:  t.object({ postId: t.uuid, notify: t.boolean.default(true) }),
  output: PostView,
  policy: can('post:publish', ({ input, actor }) => ownsPost(actor, input.postId)),
  cache:  { invalidates: [tag.post, tag.feed] },
  mcp:    { expose: true, description: 'Publish a draft post' },
  async handle({ input, ctx }) {
    const post = await ctx.posts.publish(input.postId);
    if (input.notify) await notifySubscribers.enqueue({ postId: post.id, orgId: post.orgId });
    return post;
  },
});
```

That one declaration emits six artifacts — HTTP route, OpenAPI operation, typed client function, job handle, MCP tool, test scaffold. See [Actions](/actions).

Register it, with every other primitive, in one call. Importing `api/index.ts` IS the boot.

```ts
// apps/web/api/index.ts
import { defineApi } from '@ultimat3/action';
import * as postActions from '../app/posts/actions';
import * as postJobs from '../app/posts/jobs';
import * as postQueries from '../app/posts/live';
import * as postMutators from '../app/posts/mutator';
import * as scheduledTasks from './tasks';

export const api = defineApi({
  actions: [postActions],
  mutators: [postMutators],
  queries: [postQueries],
  jobs: [postJobs],
  tasks: [scheduledTasks],
});

export type Api = typeof api;
```

## 2. Call it from `app/`

`app/` may import `api/` **types only**. The typed client is derived from those types; there is no codegen step to remember and no `fetch`.

```ts
// apps/web/shared/client.ts
import { rpc } from '@ultimat3/action';
import type { Api } from '../api';

export const api = rpc<Api['actions']>({ baseUrl: '/' });
```

```tsx
// apps/web/app/posts/ui/publish-button.tsx
import { api } from '../../../shared/client';
import { t as translate } from '@ultimat3/i18n';
import type { PostView } from '@myapp/domain';

export function PublishButton(props: { post: PostView }) {
  const publish = async (): Promise<void> => {
    await api.publishPost({ postId: props.post.id, notify: true });
  };
  return (
    <button type="button" onClick={publish}>
      {translate('post.publish')}
    </button>
  );
}
```

Rename `postId` in the action and this file fails typecheck. One rename, N errors, all real work.

## 3. Drive it from an agent over MCP

**`x dev` does not serve MCP** — measured `As of 2026-08-23`, `GET /mcp` on the dev server answers
404 and nothing listens on 9229. The MCP server is its own command, and it prints the bearer token
your client must send:

```
x mcp serve --transport stdio                  # a client that spawns the server
x mcp serve --transport http --port 9229       # POST /mcp, bearer token printed at boot
```

`stdio` and `http` are the two transports (`x help mcp`); there is no `ws` transport, and a client
configured for one gets no connection. Register the printed URL and token with whatever MCP client
you use.

The agent now sees `publishPost` as a tool with JSON Schema from `input`, the description from `mcp.description`, and **the action's own `policy` as its authorization** — unwrapped, identical to the HTTP path. A denial is the same code in all three encodings:

```json
{ "code": "X_FORBIDDEN", "cause": "actor user_2 lacks post:publish on post_9",
  "fix": "grant post:publish to the actor's role, or call as the post owner",
  "docs": "https://github.com/developerz-ai/ultimate/wiki/Error-Codes" }
```

Introspection an agent should use instead of grepping:

| Want | Command | MCP tool |
|---|---|---|
| every action + schemas | `x actions list --json` | `actions.describe` (no arguments — every action and query) |
| one action in detail | `x actions show publishPost --json` | `actions.describe`, then pick the entry |
| is this protected | `x policy explain publishPost --json` | none — `policies.list` returns the catalog, not the per-declaration matrix |
| the whole app as data | `x manifest --json` | `manifest.read` |
| what an `X_*` code means | `x errors explain X_FORBIDDEN` | `errors.explain` |
| how the framework does X — **ask this first**, before stating a limit | `x docs "send email"` (offline: the installed packages and this wiki) | none |

## 4. `x verify`

One command. Green means shippable.

```text
$ x verify
  ✓ typecheck  ✓ lint  ✓ boundaries  ✓ filesize  ✓ package-shape  ✓ errors
  ✓ unit  ✓ contract  ✓ live  ✓ job  ✓ e2e  ✓ eval
  ✗ drift
      X_DB_DRIFT: schema differs from migrations
        cause: table "posts" has column "publish_at" not present in any migration
        fix:   x db gen "add publish_at"
```

`x verify --json` emits the same content machine-readably. CI runs exactly `x verify` — a check that lives only in CI is a check you cannot run.

## Where next

| Page | Read it for |
|---|---|
| [Installation](/installation) | prerequisites, `x new` flags, typed env, MCP client setup |
| [Project layout](/project-layout) | the generated tree, surfaces, the `site/` → `app/` boundary |
| [The eight primitives](/the-eight-primitives) | the whole conceptual surface, in eight lines |
| [Actions](/actions) | every field, all six projections, introspection |
| [Entities and migrations](/entities-and-migrations) | schema, drift, branch DBs |
| [Policies and authz](/policies-and-authz) | one authz system, tenancy, denials |
| [Queries and live queries](/queries-and-live-queries) | reads, `live: true` |
| [Jobs and workflows](/jobs-and-workflows) | durable steps, idempotency |
| [Routes and render modes](/routes-and-render-modes) | `static` / `isr` / `ssr` / `stream` |
| [Testing](/testing) | six test types, DB-clone parallelism |
| [CLI reference](/cli-reference) | every command, every `--json` |
| [Error codes](/error-codes) | code → cause → fix |
| [Troubleshooting](/troubleshooting) | first-run failures |

## Status

`As of 2026-08-23`. Stable API — semver from here ([Upgrading](/upgrading); `x upgrade` moves one major per run). The repository holds 30 `@ultimat3/*` packages plus the unscoped `create-ultimate` and versions all 30 in lockstep. Every major so far has been a correctness sweep with no codemod, so each `BREAKING —` entry names its own manual edit; [Upgrading](/upgrading) walks all of them, oldest first, and each section states its own count. Only the [footer](#footer) stamps a version number, and this page states none. **What `bunx create-ultimate myapp` installs is npm's `latest`**, one version for all 31 workspaces, each published by the release workflow over OIDC with a provenance attestation. Resolve it, do not trust this line — `npm view @ultimat3/core version`, and `npm view @ultimat3/core dist.attestations` for the attestation. **No publication holes**: `@ultimat3/scraping` was the last never-published package, bootstrapped by hand at 2.0.0, so `bun add @ultimat3/scraping` resolves. Milestones 0–10 are ✅; milestone 11 is 🚧, open on its two-platform deploy proof. Realtime tiers 1–2 ship, and tier 3 (local-first) shipped in 21.0.0, opt-in per entity (`entity(name, { persist: true })`). The 50k-socket forced-restart benchmark **is measured and committed** — first patch on the reconnected socket at p50 54.0s / p90 105.5s, on one node; delivery is a second run, 10,000 clients, 1,666,882 patches, 0 observed sequence gaps ([Realtime](/realtime)). Status markers come from [`docs/idea/14-roadmap.md`](https://github.com/developerz-ai/ultimate/blob/main/docs/idea/14-roadmap.md). See [FAQ](/faq).
