# Quickstart for agents

This is the loop: onboard the app once, author a flow, verify it, ship it. Every step below is a
real MCP tool or CLI verb — full field-level detail is in [Reference](/docs/reference/).

> **Note:** These docs describe the v4 toolkit, which needs `weir-toolkit >= 0.4.4` (the current
> published release). Run `weir --version` to check the installed version; do not copy a
> changing "latest" number from prose. The 0.4.0 release
> added `weir experiment-ship-winner` and the `weir components-sync --check` preflight; a toolkit
> older than 0.4.0 cannot run them. 0.4.5 is pending publish and will fix the package's bin alias
> so that `npx -y weir-toolkit init` works without the `-p` flag. If validation demands
> `specVersion: 3` or rejects `specVersion: 4` or a `custom` screen type, your toolkit is pre-v4.
> Upgrade it with `npm install -g weir-toolkit@latest`.

## 1. Onboard the app

Run `weir init --appId <your-app-id> --platform ios`. This offline command
writes a valid `weir/app.json`, a starter flow, and its built baseline bundle. See
[`weir init`](/docs/init/) for its arguments and sample output.

Every host app owns a versioned `weir/app.json`. It is the source of truth for its app id, flows,
baseline bundle locations, feature flag, native fallback, and telemetry intent. It deliberately
holds **no secrets**; signing keys and ingest tokens stay in deployment config. `platform` is
`"ios" | "android" | "reactNative"` and is required. Only iOS has a public runtime acquisition
path. Android Compose and React Native/Expo are private design-partner runtimes: init refuses them
by default. After Weir has granted runtime access, an integrator may explicitly pass
`--acknowledgePrivateRuntime`; that acknowledgement scaffolds the contract but does not install
the runtime. Do not treat either path as self-service.

`weir init` generates an app config with this shape. The example shows why the generated config
uses these fields:

```json
{
  "version": 1,
  "appId": "example-app",
  "appVersion": "1.0.0",
  "platform": "ios",
  "runtimeAccess": "public",
  "flows": [{
    "id": "onboarding",
    "spec": "weir/flows/onboarding.json",
    "placement": "first_launch",
    "baselineBundle": "App/Resources/Weir/Onboarding",
    "featureFlag": "weir_onboarding_enabled",
    "nativeFallback": "native_onboarding",
    "integrationFile": "App/Weir/WeirIntegration.swift"
  }],
  "delivery": { "updateConfig": "remote-config" },
  "telemetry": { "appAnalyticsForwarding": true, "ingestConfiguredAtRuntime": true }
}
```

**Validation rules** (`weirAppConfigSchema`, the toolkit's app-config validator) — the shape a
schema enforces beyond what the one example JSON shows, so adapting it (not just copying it) does
not trip an opaque Zod error:

| Field | Rule |
|---|---|
| `version` | must be exactly `1` (the literal, not any other number) |
| `appId` | non-empty string |
| `appVersion` | optional semantic marketing version in exact `x.y.z` form; generated configs include it |
| `platform` | one of exactly `"ios"` | `"android"` | `"reactNative"` |
| `runtimeAccess` | optional `"public"` or `"design-partner"`; Android/RN doctor checks require the latter and still warn that package acquisition is unproven |
| `flows` | array, **must be non-empty** — at least one flow is required |
| `flows[].id`, `.spec`, `.placement`, `.baselineBundle`, `.featureFlag`, `.nativeFallback` | all non-empty strings, all required |
| `flows[].integrationFile` | non-empty string, but **optional** |
| `delivery.manifestBaseURL` | optional, but if present **must be a valid URL** (`z.string().url()`) — never include a signing private key here |
| `delivery.updateConfig` | one of exactly `"build-settings"` | `"remote-config"` | `"debug-launch-arguments"` | `"none"` (four values only — no underscore variant like `"remote_config"`); defaults to `"build-settings"` if `delivery` is omitted entirely |
| `telemetry.appAnalyticsForwarding`, `.ingestConfiguredAtRuntime` | booleans, both default `true` if `telemetry` is omitted |

A validation failure reports as `invalid <path>: <field>: <message>` for every failing field at
once (not just the first) — the `field` there is the dotted path from the table above.

Use `weir init` for a new integration. Hand-author `weir/app.json` only when your existing app
structure does not fit its scaffold. If you do, adapt the example for your app: its `appId`, flow
`spec` path, `baselineBundle` location, `featureFlag`, and `nativeFallback`. Then validate it
any time with:

```
weir app-doctor --appRoot .
```

`weir_app_doctor` returns `readinessKind: "source-contract-only"` and
`sourceContractReady`. It checks each flow spec, its embedded baseline bundle, the declared Swift
integration file, the delivery-mode declaration, and telemetry/fallback config — read-only, never
writes or reads secrets. Its legacy `ready` alias is not a runtime or ship-ready claim; only a
built artifact checked by `weir ship-ready` plus a device journey can establish that.

## 2. Author a flow spec

A flow spec is JSON: a `theme`, a list of typed `variables`, and a list of `screens` — 13 stock
types plus `custom` (see [Reference → Node types](/docs/reference/#node-types)), each with an
`id` and an optional `next` branch. Screens read/write variables; later screens interpolate them
with `{{variableId}}`. Put it at the path your app's `weir/app.json` declares
(`weir/flows/<flowId>.json`). `specVersion` must be exactly `4`.

If any screen is `type: "custom"`, register that component once with the app before authoring
against it — see [`weir components sync`](/docs/concepts/#custom-components-and-the-component-manifest).
A flow built only from the 13 stock types needs no sync step at all.

## 3. Preview it

```
weir dev --specPath weir/flows/onboarding.json --serve
```

`weir_dev` serves the spec in-browser with mocked native behavior — fake permission/purchase
dialogs, a mock localized product catalog, safe-area/notch overlay, keyboard-inset simulation —
for every **stock** screen type. A `custom` screen has no browser preview (there is no way to
preview a native SwiftUI view in a browser); `weir dev` shows a placeholder for it instead. Pass
`--persona "notifications=deny,purchase=cancel"` to force a specific outcome while you look at it.

> **Warning:** Expo Go cannot load the required third-party native module. Use a development or release build.

## 4. Walk every branch, headlessly

> **One-time prerequisite:** `weir dev` (step 3) needs no browser, but `weir walk`, `weir conform`,
> and `weir release` drive a real headless Chromium. Install it once before the first walk:
>
> ```bash
> npx playwright install chromium
> ```

```
weir walk --specPath weir/flows/onboarding.json --outDir .eval-out/walk
```

`weir_walk` drives the same mocked preview for one or more persona configs, screenshotting every
screen each persona's path visits, and writes a `walk-report.json` (path, native-call events,
final variables) per persona. This is how you verify branches and personalization actually
resolve — **read the screenshots**, don't just check the tool exited 0.

## 5. Build the embedded (offline) baseline

```
weir embed --appRoot . --flowId onboarding
weir conform --bundleDir .eval-out/mcp/embed-build/onboarding
```

This is a separate artifact from the remote publish path in step 6, not a prerequisite for it —
`weir embed` builds the flow into the app's own resources as an offline fallback (see
[Concepts → Embedded baseline config](/docs/concepts/#embedded-baseline-config)), and
`weir_release` builds and gates its own copy internally when you ship remotely.
Build this whenever you want that fallback current, not on every iteration.

Pass `--flowId` (the flow `id` from `weir/app.json`, matching the example above), not
`--appResourcesDir`/`--bundleName`: `weir_embed` then reads that flow's `baselineBundle` (and
`spec`) straight out of `weir/app.json` and copies the built bundle exactly there, so there is
nothing to keep in sync by hand. It also prints the exact scratch build directory to pass to
`weir_conform`. `weir_conform` runs the full contract eval against that **built bundle
directory**: a persona walk reaches `complete()`, the auto-emitted event stream matches the
manifest byte-for-byte, every declared variable gets set, purchase/permission calls only go
through the native call layer (never a literal currency string), no network on the paint path,
and perf budgets hold. `weir_release` runs this exact gate for you before it
publish (next step) — running it here separately is for proving the offline fallback itself
works, not a required step before shipping.

## 6. Ship it

Validate the whole gate **fully offline first**, before any credential setup:

```
weir release --appRoot . --flowId onboarding --dryRun
```

`--dryRun` resolves the flow from `weir/app.json`, runs the full conform gate, and stops there —
every step except the call to the delivery API. It needs no `WEIR_API_URL`/`WEIR_API_TOKEN`, so
run it before anything else in this step: it surfaces conform problems (a broken flow spec, a
failed persona walk, a perf-budget breach) separately from credential problems, so you fix one
kind of blocker at a time. Only when the dry run is green do you need a reachable server and a
token.

For a real release, set the delivery origin and app token before the non-dry-run command:

    export WEIR_API_URL="https://your-weir-delivery-origin"
    export WEIR_API_TOKEN="<token for example-app>"

Keep both outside weir/app.json and out of source control. WEIR_API_URL defaults to
http://localhost:8787 for a local API. WEIR_API_TOKEN may be omitted only when that local server
has no token authentication configured; a production service with WEIR_API_TOKENS requires the
token matching this app's appId. If an operator has not issued one yet, stop after the dry run and
request it — the current server configuration is the token-issuance authority.

```
weir release --appRoot . --flowId onboarding
```

`weir_release` is the one command that does the whole gate correctly: it resolves the flow from
`weir/app.json`, runs the full conform gate, and **only publishes the signed config if conform
passes** — then fetches the returned manifest back and verifies its version and files. That is
the same gate the dry run above exercised, minus the publish step. What actually gets published
is `config.json` (the flow spec itself, RFC-010 §2) plus any `customAssets[]` files it
references — never an HTML bundle.

The already-installed app picks the new config up on its next launch/foreground update check —
no Xcode rebuild, no App Store review. See [Concepts → Delivery model](/docs/concepts/#delivery-model)
for what happens between "publish" and "the device renders it."

## 7. Check it landed

```
weir funnel --flowId onboarding
weir health --flowId onboarding
weir app-status --appRoot .
```

`weir_funnel` is the step-through funnel with drop-off per screen, completion rate, and
time-to-paywall. `weir_health` is reliability: crash-free flow-session rate, render time
percentiles, native-call error rate, fallback count. `weir_app_status` combines the currently
published manifest with both reads for every flow declared in `weir/app.json` — the fastest
single command to answer "is the flow I just shipped actually live and healthy."

## What's deliberately not a tool call

There is no automated compliance/App Review lint in the current toolkit (an earlier
`weir_compliance_lint` was removed) — use manual judgment on paywall copy and permission priming
before shipping. There is also no tool that clicks through a flow interactively for you beyond
`weir_walk`'s scripted personas; anything past that is a manual trace of every branch and
interpolated variable downstream of a branch before you call a flow done.
