@zitadel/cli 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zitadel/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Agent-friendly Zitadel CLI",
5
5
  "homepage": "https://github.com/zitadel/nextgen/tree/main/apps/cli#readme",
6
6
  "bugs": {
@@ -49,31 +49,31 @@
49
49
  ],
50
50
  "topics": {
51
51
  "users": {
52
- "description": "Manage the users of the project."
52
+ "description": "Create and manage users."
53
53
  },
54
54
  "teams": {
55
- "description": "Manage the teams of the project."
55
+ "description": "Create and manage teams."
56
56
  },
57
57
  "sessions": {
58
- "description": "Inspect and revoke the sessions of the project."
58
+ "description": "View and revoke sessions."
59
59
  },
60
60
  "events": {
61
- "description": "Read the audit event stream of the project."
61
+ "description": "View events."
62
62
  },
63
63
  "grants": {
64
- "description": "Manage collaboration grants on the project."
64
+ "description": "Manage access grants."
65
65
  },
66
66
  "projects": {
67
- "description": "Inspect and update the projects visible to this credential."
67
+ "description": "View and update projects."
68
68
  },
69
69
  "schemas": {
70
- "description": "Inspect user-schema revisions."
70
+ "description": "View user schemas."
71
71
  },
72
72
  "branding": {
73
- "description": "Work with login branding revisions."
73
+ "description": "Manage the login branding."
74
74
  },
75
75
  "auth-method": {
76
- "description": "Turn sign-in methods on or off for a user schema."
76
+ "description": "Configure how users sign in."
77
77
  },
78
78
  "auth-method:password": {
79
79
  "description": "Turn password sign-in on or off."
@@ -83,6 +83,36 @@
83
83
  },
84
84
  "auth-method:sso": {
85
85
  "description": "Add or remove identity providers."
86
+ },
87
+ "origin": {
88
+ "description": "Manage the project's allowed origins."
89
+ },
90
+ "release": {
91
+ "description": "Build and manage releases."
92
+ },
93
+ "deployment": {
94
+ "description": "Deploy releases and roll them back."
95
+ },
96
+ "preview": {
97
+ "description": "Deploy and manage pull-request preview URLs."
98
+ },
99
+ "variable": {
100
+ "description": "Manage project variables."
101
+ },
102
+ "idps": {
103
+ "description": "Manage identity provider connections."
104
+ },
105
+ "flow-definitions": {
106
+ "description": "View login flows."
107
+ },
108
+ "policies": {
109
+ "description": "View policies."
110
+ },
111
+ "env": {
112
+ "description": "Show and bind environments."
113
+ },
114
+ "api": {
115
+ "description": "Call any operation of the Zitadel API."
86
116
  }
87
117
  }
88
118
  },
@@ -97,20 +127,20 @@
97
127
  "@oclif/plugin-search": "^1.2.50",
98
128
  "@oclif/plugin-version": "^2.2.46",
99
129
  "@oclif/plugin-which": "^3.2.55",
100
- "@zitadel/api": "0.1.0",
101
- "@zitadel/config": "0.1.0",
102
- "@zitadel/server": "0.1.0",
130
+ "@zitadel/api": "0.2.0",
131
+ "@zitadel/config": "0.2.0",
132
+ "@zitadel/server": "0.2.0",
103
133
  "ajv": "^8.20.0",
104
134
  "consola": "^3.4.2",
105
135
  "magicast": "^0.5.3",
106
136
  "mixpanel": "^0.18.1",
137
+ "picocolors": "^1.1.1",
138
+ "rosie-skills": "^0.8.5",
107
139
  "safe-stable-stringify": "^2.5.0",
108
140
  "semver": "^7.7.3",
109
141
  "undici": "^7.29.1",
110
142
  "wrap-ansi": "^7.0.0",
111
- "zod": "^4.3.6",
112
- "picocolors": "^1.1.1",
113
- "rosie-skills": "^0.8.5"
143
+ "zod": "^4.3.6"
114
144
  },
115
145
  "devDependencies": {
116
146
  "@types/node": "^24.19.1",
@@ -118,13 +148,14 @@
118
148
  "@types/wrap-ansi": "^3.0.0",
119
149
  "@vitest/coverage-v8": "^4.1.11",
120
150
  "@zitadel/api-mock": "0.0.0",
121
- "@zitadel/sdk-next": "0.1.0",
151
+ "@zitadel/sdk-next": "0.2.0",
122
152
  "msw": "^2.14.6",
123
153
  "oclif": "^4.17.46",
124
154
  "tsdown": "^0.23.0",
125
155
  "typescript": "^6.0.3",
126
156
  "vite": "^8.3.1",
127
- "vitest": "^4.1.11"
157
+ "vitest": "^4.1.11",
158
+ "yaml": "^2.9.0"
128
159
  },
129
160
  "scripts": {
130
161
  "build": "tsdown",
@@ -5,7 +5,7 @@ description: >-
5
5
  agent-friendly `zitadel` CLI. Use when the user wants to add login,
6
6
  registration, or session handling, create a Zitadel project, scaffold auth
7
7
  for a Next.js, Nuxt, SvelteKit, SolidStart, Qwik Router, TanStack Start, React,
8
- Vue, Angular, Solid, Svelte, or Qwik app, or plan and apply Zitadel config
8
+ Vue, Angular, Solid, Svelte, or Qwik app, or build and deploy Zitadel config
9
9
  changes from repo state.
10
10
  ---
11
11
 
@@ -67,11 +67,44 @@ upgrade, so upgrade the app and rerun rather than retrying.
67
67
  drops records or fields, so you end up reasoning from partial data. It is
68
68
  finite — read it whole, or narrow it *at the source*: use a `list` verb's
69
69
  `filter_fields` and pagination to fetch fewer records, or pipe to `jq` to pull
70
- just what you need (e.g. `… resources --json | jq '.data.resources[].name'`).
70
+ just what you need (e.g. `… resources --json | jq '.data.items[].topic'`).
71
71
  `--help` output is bounded too — read it in full rather than `head`-ing it, or
72
72
  you will miss flags further down.
73
73
  - **Working directory.** Add `--cwd <path>` when operating outside the current
74
74
  working directory.
75
+ - **Server and project.** A command that talks to a project reads
76
+ `ZITADEL_URL`, `ZITADEL_PROJECT_ID` and `ZITADEL_PROJECT_SECRET`, each from the
77
+ first source that has it: `--server` (the URL only), `--env-file <path>`, the
78
+ process environment, `.env.<env>.local`, `.env.local`, `.env.<env>`, `.env`,
79
+ then `.zitadel/secret`. Only the default `development` environment reads
80
+ `.env.local` and `.zitadel/secret` (setup's project); `--env-file` reads that
81
+ file and the process environment only.
82
+ `<env>` is `--env`, else `ZITADEL_ENV`, else a hosting platform's own signal
83
+ (`VERCEL_ENV`, or Netlify's `CONTEXT` inside a Netlify build), else
84
+ `development`. A missing value fails with `E_VALIDATION` naming the variable
85
+ and the files it looked in; there is no cloud default outside `setup`, and
86
+ the server is never read from `zitadel.json`. `zitadel env` shows what resolved and where
87
+ each value came from; `zitadel env add <name>` binds another environment to a
88
+ project. The project it creates has never had a release, so the environment
89
+ serves the server's own defaults until something is deployed to it: run the
90
+ `deploy` the result's `next_commands` names, against the file it wrote
91
+ (`--env-file .env.<name>.local`). A person at a terminal is asked whether to
92
+ deploy there and then; your runs never are, so they never deploy — run that
93
+ `deploy` yourself. `data.deployment` carries the `release_id`,
94
+ `content_hash`, `deployment_id` and `origins` when a person accepted, and is
95
+ null otherwise — a failed deploy is a warning, never a failed `env add`.
96
+ - **Origins.** `zitadel origin add <url> --kind primary|preview`, `origin list`
97
+ and `origin rm <url>` manage the URLs a project serves: a `primary` origin is
98
+ where the app is served, a `preview` pattern (it may hold a `*`) is where
99
+ pull-request builds run. They need the project secret, not the preview token.
100
+ An empty list admits every origin, so removing the last one opens the project
101
+ up; `origin rm` asks first and needs `--force` when non-interactive. Removing
102
+ a preview pattern leaves its live previews served until they expire.
103
+ `deploy` lands only on primary origins and fails with `E_VALIDATION` without
104
+ one; its `next_commands` name the step that gives the project one. `setup`
105
+ 0.1.0 registered the app's URL (`ZITADEL_ISSUER`) as a `preview` origin,
106
+ which `origin add --kind primary` refuses with `E_CONFLICT`: `doctor` warns
107
+ (`issuer-origin`) and `doctor --fix` makes it primary.
75
108
  - **Telemetry.** The CLI sends anonymous usage telemetry by default, and
76
109
  agent-driven runs are exactly the ones worth learning from — leave it on.
77
110
  Don't add `--no-telemetry` to commands. An opt-out exists for users who need
@@ -122,36 +155,40 @@ upgrade, so upgrade the app and rerun rather than retrying.
122
155
  `${{ NAME }}` reference to a project variable is not a secret and may stay on
123
156
  argv (a connection's `client_secret` takes nothing else); write it in single
124
157
  quotes, since inside double quotes the shell reads `${` as its own expansion.
125
- - **Streams.** Capture stdout and stderr separately when scripting. The machine
126
- contract is one parseable JSON object on stdout; installer, audit, and
127
- package-manager progress belongs on stderr.
158
+ - **Streams.** Capture stdout and stderr separately when scripting, into files
159
+ outside the app directory: `setup` scaffolds a new app only into an empty
160
+ directory. The machine contract is one parseable JSON object on stdout;
161
+ installer, audit, and package-manager progress belongs on stderr.
128
162
 
129
163
  ## Discovering commands
130
164
 
131
165
  The CLI ships its own command list and per-command flags, always correct for the
132
166
  installed version. **Do not guess a command name.** Before running any command you
133
- are not certain of, run `zitadel --help` first and use the exact name it prints:
167
+ are not certain of, look it up first and use the exact name the CLI prints:
134
168
  command and resource names — including whether a resource is singular or plural
135
- and the exact verb — are whatever `--help` shows, never what you would assume. A
136
- wrong guess costs a failed call and a round of recovery; one cheap `--help` up
137
- front avoids it. `zitadel --help` is a flat list and cheap to read, so reach for
138
- it first; treat the CLI's own output as authoritative over anything you remember:
169
+ and the exact verb — are whatever the CLI shows, never what you would assume. A
170
+ wrong guess costs a failed call and a round of recovery; one cheap lookup up
171
+ front avoids it. `zitadel --help` lists each family of commands (`users`,
172
+ `release`) as one row, so it stays short; `zitadel commands` is the flat list of
173
+ every command, verbs included, and `zitadel <family> --help` lists one family's
174
+ verbs. Treat the CLI's own output as authoritative over anything you remember:
139
175
 
140
176
  ```sh
141
- npx @zitadel/cli@latest --help # every command, grouped by purpose
177
+ npx @zitadel/cli@latest --help # every command family, grouped by purpose
142
178
  npx @zitadel/cli@latest commands # flat list of all commands
143
179
  npx @zitadel/cli@latest <command> --help # a command's flags, which are required, and examples
144
180
  npx @zitadel/cli@latest resources --json # the resource surface in one call (see below); contacts no server
145
181
  npx @zitadel/cli@latest api ls --json # every API operation, its parameters, body and paging; contacts no server
146
182
  ```
147
183
 
148
- `--help` groups the surface into project commands (create a project and scaffold
149
- auth), local-server commands (run a local Zitadel), configuration commands
150
- (`plan`/`apply` plus the config resources), and resource commands (the uniform
151
- `<resource> <verb>` surface). Read the groups from `--help` rather than from
152
- memory, since they change as the CLI gains commands.
184
+ `--help` groups the commands by what you are doing: getting started, local
185
+ development, users and teams, authentication, configuration and releases, and
186
+ diagnostics and utilities. A family such as `users` is one row, and
187
+ `zitadel users --help` lists its verbs. Read the groups from `--help` rather than
188
+ from memory, since they change as the CLI gains commands.
153
189
 
154
- Use `zitadel --help` to find *which* command to run. Use `resources --json` when
190
+ Use `zitadel --help` to find *which* family or command to run, and
191
+ `zitadel commands` for the exact verb. Use `resources --json` when
155
192
  you need a resource's *fields*: it reports the whole runtime-resource surface at
156
193
  once — every resource, its verbs, each verb's `create_fields` / `update_fields`
157
194
  (flag name, kind, `required`, any closed value set), its `filter_fields` /
@@ -181,19 +218,24 @@ Each invocation prints one JSON object:
181
218
  success: a warning usually means a follow-up step is needed.
182
219
  - A `list` verb emits `data: { items, count, next_page_token }`; when a page
183
220
  remains, `data.next_commands` carries the exact command for the next page.
184
- - `plan` and `apply` emit `data.changes`: one row per touched resource
185
- (`{kind, action, file, id?, previous_id?, moved_from?}`, action ∈ create/
186
- update/revision/delete/move). A `move` is a file that took over a resource
187
- from a removed one (`moved_from`). Plan rows preview; apply rows report, with
188
- the resulting platform ids. Use it to verify an edit did what you intended.
189
- `data.unsupported_deletions` counts removed files whose resource cannot be
190
- deleted yet (they are kept, and are not part of `total`); on `apply` the files
191
- are listed in `data.unsupported_deletion_files`.
192
- - `apply` leaves the files under `.zitadel/` as written: what the server stores
193
- is never written back, and a field a file leaves out reads as the server's
194
- default, so `plan` reports no change after an `apply`. `data.files_updated`
195
- lists the one rewrite: a flow whose `user_schema` pinned a schema revision id
196
- the apply superseded, re-pinned to the new revision's id.
221
+ - `deploy` emits `data.content_hash` (the release digest), `data.origins`
222
+ (the origins it went live on) and `data.resources` (how many it pinned). A
223
+ re-run on unchanged config returns the same digest. Use it to confirm what
224
+ went live.
225
+ - `preview` is `deploy` for a pull-request build: it deploys the release to the
226
+ build's preview URLs (from the platform, or `--origin`) for `--ttl` and emits
227
+ `data.previews` plus `data.release_env` (the `NEXT_PUBLIC_ZITADEL_RELEASE`
228
+ line to pin). It uses `ZITADEL_PROJECT_SECRET` until the server accepts the narrower preview token (#1575), never the other credential, and
229
+ outside a preview or development build it is a no-op — even with an explicit
230
+ `--origin`, which selects targets but never authorizes a preview deploy. A CI
231
+ build other than Vercel or Netlify must set `ZITADEL_ENV=preview`, or it is
232
+ skipped too. `preview list` and `preview rm <url>` read and end live previews.
233
+ - `deployment rollback` emits `data.release_id` (the release live again),
234
+ `data.origins` and `data.pointers`, the `{kind, handle, revision_id}`
235
+ revisions that release pins; the `schema` one is what new registrations use.
236
+ Users keep their schema revision and attributes: a rollback does not migrate
237
+ them back. Without `data.pointers`, a warning says the release could not be
238
+ read.
197
239
  - A write verb's `--dry-run` reports what it would send without calling the
198
240
  platform, so you can preview a mutation before making it.
199
241
  - `api` emits the response body as `data`, exactly as the API reference
@@ -208,11 +250,12 @@ Common error codes point at their own fix in `next_commands`:
208
250
  `details` often lists the missing or available values.
209
251
  - `E_CREDENTIAL_MISSING`: a connection's `client_id` or `client_secret` names a
210
252
  project variable that does not exist; `details.variable` is the name. Publish it
211
- with `variables set NAME --project-level` (add `--secret` for the secret), then retry.
253
+ with `variable set NAME` (add `--secret` for the secret), then retry.
212
254
  - `E_LOCAL_SERVER_NOT_RUNNING`: start the local runtime, then retry with
213
255
  `--server local`.
214
- - `E_NOT_FOUND`: an HTTP 404 — a missing resource, or a `--server` that is not a
215
- Zitadel platform API. Follow `next_commands`.
256
+ - `E_NOT_FOUND`: an HTTP 404 — a missing resource, an operation the server's
257
+ version does not have (`details.body.code` is `req.not_found`), or a
258
+ `--server` that is not a Zitadel platform API. Follow `next_commands`.
216
259
  - `E_PORT_IN_USE`: the requested local runtime port already has a listener; stop
217
260
  that process or choose another port.
218
261
 
@@ -230,8 +273,9 @@ Exit codes mirror the error class (3 = validation, 4 = network or not-found,
230
273
  5 = conflict, 1 = auth, 2 = not-implemented, 130 = cancelled). `E_NETWORK`
231
274
  covers a server that answered 5xx and one that never answered — refused,
232
275
  unresolvable, or silent past the 30-second request deadline. Ctrl-C exits
233
- 130: a command waiting on the server stops at once with `E_CANCELLED`, and
234
- any other command exits within a second, possibly without an envelope. An
276
+ 130: a command waiting on the server, or on an answer at a prompt, stops at
277
+ once with `E_CANCELLED`, and any other command exits within a second, possibly
278
+ without an envelope. In human mode a cancel prints no error block. An
235
279
  unknown command is handled by the CLI's help layer, not the envelope.
236
280
 
237
281
  ## Clarify before you configure
@@ -276,7 +320,7 @@ like. Read the current sets from `setup --help`, but know the traps:
276
320
  enabled**; `passkey-first` leads with a passkey. Neither one gives you
277
321
  password-only. If the developer does not want passkeys, turn them off
278
322
  yourself: set `x-auth-methods.passkey.enabled` to `false` in
279
- `.zitadel/schemas/*.json`, then `apply`.
323
+ `.zitadel/schemas/*.json`, then `deploy`.
280
324
 
281
325
  Translate the developer's answers into the right axis values, then **verify
282
326
  against what actually deployed**: read the live schema back
@@ -291,13 +335,12 @@ invocation) or were explicitly told to just set it up.
291
335
  ## Answering the user
292
336
 
293
337
  Report what changed, in the user's terms — not how you drove the CLI. The
294
- flags, `--json`, the `plan`/`apply` split, exit codes, and the number of
338
+ flags, `--json`, the deploy flow, exit codes, and the number of
295
339
  commands you ran are your internal business; the user asked for an outcome, not
296
340
  a transcript. Say what they got.
297
341
 
298
342
  - Good: "Added an optional phone number to your user schema and deployed it."
299
- - Avoid: "I ran `plan` and `apply`, both with `--non-interactive --json`, and
300
- both returned ok."
343
+ - Avoid: "I ran `deploy` with `--non-interactive --json` and it returned ok."
301
344
 
302
345
  Quote a command or flag only when the user asks *how* something works, or when
303
346
  they need to run something themselves. Otherwise keep the mechanics out of the
@@ -314,8 +357,7 @@ npx @zitadel/cli@latest doctor --non-interactive --json
314
357
  npx @zitadel/cli@latest start --non-interactive --json
315
358
  npx @zitadel/cli@latest setup --framework next --server local --non-interactive --json
316
359
  npx @zitadel/cli@latest doctor --non-interactive --json
317
- npx @zitadel/cli@latest plan --non-interactive --json
318
- npx @zitadel/cli@latest apply --non-interactive --json
360
+ npx @zitadel/cli@latest deploy -m "<why this release changes>" --non-interactive --json
319
361
  ```
320
362
 
321
363
  `setup` scaffolds into an empty directory, or patches an existing app project
@@ -329,6 +371,11 @@ terminal `setup` cannot ask which framework to scaffold, so pass `--framework`;
329
371
  the error lists the choices in `details.frameworks`. `setup --dry-run` creates
330
372
  no app: where it would scaffold one, it previews the project, and
331
373
  `data.files_written` is empty, because those files depend on the generated app.
374
+ `setup` also writes and publishes the password policy
375
+ (`.zitadel/policies/auth.factor.password.set.json`) with a minimum length of
376
+ 15, NIST's minimum for password-only sign-in and the wizard's default. Pass
377
+ `--password-min-length <n>` only when the user asks for another value (`0` sets
378
+ none), and give test users passwords at least that long.
332
379
  When setup fails after scaffolding a new app, before its default schema and
333
380
  flow are uploaded, it removes the app again and puts back what the directory
334
381
  held, so rerunning the same `setup` starts over.
@@ -347,15 +394,18 @@ replaces a file at a path setup writes, but not the app's root component or
347
394
  request boundary at another path.
348
395
 
349
396
  The app may also live in a subdirectory of the one `start` ran in: `setup`,
350
- `console`, `--server local` and `doctor` read `.zitadel/local/` from the working
397
+ `console`, `logs`, `--server local` and `doctor` read `.zitadel/local/` from the working
351
398
  directory or the nearest parent that has it (up to the home directory), so
352
399
  `mkdir my-app && cd my-app` after `start` works too. `doctor` checks that
353
400
  server and its port, but runs its project checks in the working directory, so run
354
401
  it from the app. When that server is unhealthy it suggests `start` and
355
402
  `reset --force` with `--cwd` set to the directory that holds it (when that path needs
356
403
  shell quoting the commands are left out, the hint names the directory, and
357
- `doctor --json` returns the argument lists in `details.suggested_args` to run as they are). `stop`,
358
- `reset`, `logs` and `status` read only the working directory's
404
+ `doctor --json` returns the argument lists in `details.suggested_args` to run as they are). A
405
+ local server is healthy once its `/readyz` answers, which takes its database
406
+ answering too: `start` waits for that, and the commands that read a server
407
+ treat one whose database does not answer as unhealthy. `stop`,
408
+ `reset` and `status` read only the working directory's
359
409
  `.zitadel/local/`, so run them where `start` ran. On a local
360
410
  server that hosts the platform project, `setup` attaches the project to the
361
411
  local admin's team and ends `data.next_commands` with `console`; when it finds
@@ -363,10 +413,14 @@ no local admin for that server on the way up, it says so in the envelope's
363
413
  `warnings` and the project stays unattached, so the local console does not
364
414
  list it.
365
415
 
366
- When `setup --sso` or `sso enable` adds a social sign-in provider, the user
416
+ For a project set up against a hosted server, `doctor` checks no local
417
+ runtime, unless a local server was started in its directory or a parent, or
418
+ `--port`, `--runtime` or `--image` asks about one.
419
+
420
+ When `setup --sso` or `auth-method sso enable` adds a social sign-in provider, the user
367
421
  must register the connection's redirect URI with the provider themselves: give
368
422
  them `data.sso[].callback_uri` from `setup`, or `data.callback_uri` from
369
- `sso enable`, verbatim. Each connection has its own URI, so a URI from another
423
+ `auth-method sso enable`, verbatim. Each connection has its own URI, so a URI from another
370
424
  provider or an older CLI version does not work.
371
425
 
372
426
  After `setup`, follow `data.next_commands` to start the app. Prove the generated
@@ -374,9 +428,18 @@ auth flow in a visible browser by registering a unique user, logging out, loggin
374
428
  back in with the same email/password, and ending on the signed-in profile page.
375
429
  Do not treat a rendered login or registration form as completion.
376
430
 
377
- To move a set-up app to another dev port (the dev server exits with
378
- `EADDRINUSE`, or another app needs the port), run `setup --dev-port <port>` in
379
- the app; never change the port in one file by hand. In a directory with a
431
+ When a step against the local server fails without saying why (the login says a
432
+ sign-in provider "is not available right now", or a request returns 500), the
433
+ server logged the cause. Read it with `logs --level warn --json`: `data.logs`
434
+ holds the warnings and errors, and `--tail` counts only those lines. `logs
435
+ --follow` streams the log for a person at a terminal, with warnings yellow and
436
+ errors red; it refuses `--json`. `start`, and `doctor` when it finds the server
437
+ running, name it in `data.next_actions`.
438
+
439
+ To move a set-up app to another dev port (setup's `warnings` say the port is
440
+ already in use, the dev server exits with `EADDRINUSE`, or another app needs
441
+ the port), run `setup --dev-port <port>` in the app; never change the port in
442
+ one file by hand. In a directory with a
380
443
  `zitadel.json`, `setup` returns `status: "skipped"` with `reason:
381
444
  "already-initialized"`, unless `--dev-port` is given: then it moves the app.
382
445
  It allows the new origin on the server first, and a refusal changes nothing.
@@ -399,6 +462,13 @@ than one:
399
462
  stdin when non-interactive. Any other OpenID Connect provider is
400
463
  `--provider custom` with `--slug`, `--display-name`, `--issuer` and optionally
401
464
  `--scopes` (default `openid profile email`).
465
+ In a terminal, a custom provider's settings you leave out are asked for, after
466
+ the checks that need no answer; a connection that already has that slug
467
+ supplies its own. A scripted, `--json`, `--dry-run` or `--non-interactive`
468
+ run must pass all three and gets an error naming the missing ones. After
469
+ writing a new connection the text output prints the claim mappings and names
470
+ the file to edit them in; `--json` does not carry them, so read the
471
+ connection file.
402
472
  An issuer on `http://localhost:<port>` (a test provider on this machine)
403
473
  works against a server `zitadel start` runs as a process: `start` sets
404
474
  `NEXTGEN_HTTPCLIENT_ALLOW_LIST=localhost,127.0.0.0/8,::1/128` unless
@@ -410,7 +480,11 @@ than one:
410
480
 
411
481
  The commands change only local files, apart from
412
482
  `auth-method sso enable`, which also stores the provider's credentials on the
413
- project. When they refuse, they write and publish nothing, on a dry run too.
483
+ project. A provider added by `sso enable` is configured locally, not live: its
484
+ `data.title` says so, `data.next_actions` says to deploy, and `data.next_commands`
485
+ holds `deploy --dry-run` then `deploy`. Only run those once the provider's OAuth
486
+ application exists. `data.title` claims the credentials are stored on the project
487
+ only when they reached it. When they refuse, they write and publish nothing, on a dry run too.
414
488
  They refuse to disable a method a login flow still asks for, to change a method
415
489
  while a login flow has errors that prevent checking it, to enable password on a
416
490
  schema without `x-identifier`, to edit a value that is not the shape they edit,
@@ -423,15 +497,37 @@ and warns that no way to sign in would be left. A method that is enabled but not
423
497
  offered by any active flow is reported in `warnings` (and in `data.not_offered`
424
498
  for password and passkey). Every command reports `data.changed`, the
425
499
  project-relative files it wrote (on a dry run, the files it would write), empty
426
- when nothing changed. Follow `data.next_commands` (`plan`, `apply`, and
427
- `variables set` when a credential did not reach the project) to publish the
500
+ when nothing changed. Follow `data.next_commands` (`deploy --dry-run`, `deploy`, and
501
+ `variable set` when a credential did not reach the project) to publish the
428
502
  change. When a path or schema name would need shell quoting, `next_commands` is
429
503
  empty and `data.next_args` (or `details.suggested_args` on a refusal) holds the
430
504
  same commands as argument lists.
431
505
 
432
506
  Repo config is authoritative: edit `zitadel.json` or files under `.zitadel/`,
433
- then re-run `plan` and `apply`. See the reference below for driving the login UI
434
- and the config-sync details.
507
+ then re-run `deploy`. See the reference below for driving the login UI
508
+ and the config-sync details. The editor schemas in `.zitadel/meta/` are
509
+ generated by the CLI, not config: never edit them. `doctor` warns when one is
510
+ missing or outdated (after an upgrade that changed a dialect), and
511
+ `doctor --fix` writes the current ones, unless the project pins a newer
512
+ `@zitadel/*` version (run that version's `doctor --fix`) or a symlink is on
513
+ the path; `deploy --dry-run` validates without them either way.
514
+
515
+ ## Claiming a project
516
+
517
+ A project on a hosted server stays temporary until a person claims it, within
518
+ 14 days of `setup`. Claiming signs that person in with their own Zitadel
519
+ account in a browser, so it is their step: hand them the link, and never open
520
+ it yourself. Plain `claim` waits for the browser step and prints nothing on
521
+ stdout until it ends, so run `claim --no-wait --non-interactive --json`
522
+ instead. It returns at once with `data.claim_url`, the link to give the person
523
+ verbatim, and `data.expires_at`, when it stops working (about 10 minutes).
524
+ Then run the `claim --challenge-id …` command in `data.next_commands`: it
525
+ waits for the person to finish, returns `data.dashboard_url`, and records the
526
+ owning team in `.zitadel/secret`. When the link expires, or `--challenge-id`
527
+ names one the platform does not know (`E_NOT_FOUND`), `next_commands` starts a
528
+ new `--no-wait` link. A project that already has a team is `skipped` with
529
+ `reason: "already-claimed"`; one past its 14 days fails with `E_VALIDATION`
530
+ and no `next_commands`, since a new link cannot reopen it.
435
531
 
436
532
  ## Reference files
437
533
 
@@ -439,4 +535,4 @@ Load on demand:
439
535
 
440
536
  - [`references/driving-login-ui.md`](references/driving-login-ui.md) — driving the
441
537
  `<zitadel-login>` component in a browser, and the authoritative repo-config
442
- workflow (schemas, flows, branding).
538
+ workflow (schemas, flows, branding, policies).
@@ -45,7 +45,8 @@ exercises fresh-app setup plus registration, logout, and login across the
45
45
  supported frameworks.
46
46
 
47
47
  Repo config is authoritative: edit `zitadel.json` or files under `.zitadel/`,
48
- then re-run `plan` and `apply`. Schema and flow files are synced from
48
+ then re-run `deploy` (or `deploy --dry-run` to validate and preview the diff
49
+ without publishing). Schema and flow files are synced from
49
50
  `.zitadel/schemas/*.json` and `.zitadel/flows/*.json`. Login templates
50
51
  (branding) are synced from `.zitadel/branding/`: a single `branding.json`
51
52
  descriptor (layout, asset URLs) plus a sibling `login.liquid` LiquidJS
@@ -53,9 +54,10 @@ template referenced as `"liquid_template": { "$file": "./login.liquid" }`. Scaff
53
54
  `branding eject` command (`--design centered|minimal`,
54
55
  interactive picker on a TTY); setup never scaffolds them. Branding is
55
56
  revisioned and immutable: every edit — including a `.liquid`-only edit —
56
- plans as a `revise` and `apply` publishes a new revision; the login serves
57
- the newest one. `plan` validates templates with the authoritative LiquidJS
58
- validator (`E_VALIDATION` lists rule ids such as `no-script-tag` and
57
+ is published as part of the next release when you `deploy`, and the login
58
+ serves the newest release. `deploy` validates templates with the authoritative
59
+ LiquidJS validator (and `deploy --dry-run` runs the same validation without
60
+ publishing; `E_VALIDATION` lists rule ids such as `no-script-tag` and
59
61
  `mandatory-gates`; every template must keep a `{% mandatory_gates %}` tag,
60
62
  where the login adds any required field and the primary action the template
61
63
  left out). Asset URLs must be absolute `https://`; during development, images
@@ -67,17 +69,35 @@ directory. Keep exactly one descriptor in
67
69
  `.zitadel/branding/` — extra `*.json` files there fail the scan.
68
70
  Server-provisioned defaults remain a fallback for non-CLI project
69
71
  creation, but CLI-created projects are authored from local files first. Flows are
70
- revisioned like branding: an edit plans as a `revise` and `apply` publishes a new
71
- immutable flow revision. A flow names its schema by `objectType` in
72
- `user_schema` and runs against the schema's newest revision, so a schema revise
73
- needs no change to `user_schema`; one that removes or renames a property a step
74
- collects needs that step's `fields` updated, and `plan` checks only the flows it
75
- uploads. A login pinned by `flow-name` serves that flow's newest active
76
- revision; an unpinned login serves the newest active unscoped revision for its
77
- purpose in the project, whatever its flow's name. Older revisions stay active,
78
- so an `audience` added after a flow was applied without one doesn't take that
79
- earlier revision out of the running. Removing a flow file does not retire the
80
- flow. Managed files carry a marker comment; `eject` removes only
72
+ revisioned like branding: an edit is published as a new immutable flow revision
73
+ in the next `deploy`. A flow names its schema by `objectType` in `user_schema`
74
+ and runs against the schema's newest revision, so a schema revise needs no
75
+ change to `user_schema`; one that removes or renames a property a step
76
+ collects needs that step's `fields` updated, and `deploy` checks each flow
77
+ against the schema it names. A login pinned by `flow-name` serves that flow's
78
+ newest active revision; an unpinned login serves the newest active unscoped
79
+ revision for its purpose in the project, whatever its flow's name. Older
80
+ revisions stay active, so an `audience` added after a flow was deployed
81
+ without one doesn't take that earlier revision out of the running. Removing a
82
+ flow file does not retire the
83
+ flow. Operation policies live in `.zitadel/policies/`: one `<operation>.json`
84
+ per guarded operation, named after its `operation`, holding
85
+ `{"$schema": "../meta/policy.json", "operation": "auth.factor.password.set",
86
+ "config": {"min_length": 15}}`. `deploy` checks each file against
87
+ `.zitadel/meta/policy.json` (known operation, only the template's settings,
88
+ within their bounds) and its name before anything is sent; settings that
89
+ contradict each other (a `min_length` above `max_length`) are refused by the
90
+ server's release build with `rel.invalid`, the policy's entry carrying
91
+ `pol.invalid`. A changed file publishes a new revision of the same policy,
92
+ which applies the next time a password is set. A setting left out is off, and
93
+ `"config": {}` switches every rule off. A sign-in or sign-up uses the policy
94
+ the deployed release carries, so deploying without the file switches it off
95
+ there; a password set through the API, or before the first deploy, uses the
96
+ newest revision, which only `"config": {}` switches off. `policies list` and
97
+ `policies get <id|operation>` read them back. `setup` writes and publishes
98
+ the password policy with a `min_length` of 15; pass `--password-min-length <n>`
99
+ only when the user asks for another value (`0` sets none). Managed files carry
100
+ a marker comment; `eject` removes only
81
101
  files that still carry it, preserving anything the user replaced. For app-local
82
102
  development, `--server local` resolves through `.zitadel/local/runtime.json` and
83
103
  requires a healthy