@zitadel/cli 1.0.0-alpha.23 → 1.0.0-alpha.25

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": "1.0.0-alpha.23",
3
+ "version": "1.0.0-alpha.25",
4
4
  "description": "Agent-friendly Zitadel CLI",
5
5
  "homepage": "https://github.com/zitadel/nextgen/tree/main/apps/cli#readme",
6
6
  "bugs": {
@@ -22,7 +22,7 @@
22
22
  "bin",
23
23
  "dist",
24
24
  "oclif.manifest.json",
25
- "SKILLS.md"
25
+ "skills"
26
26
  ],
27
27
  "publishConfig": {
28
28
  "access": "public"
@@ -71,6 +71,9 @@
71
71
  },
72
72
  "branding": {
73
73
  "description": "Work with login branding revisions."
74
+ },
75
+ "sso": {
76
+ "description": "Manage the identity providers users can sign in with."
74
77
  }
75
78
  }
76
79
  },
@@ -95,25 +98,29 @@
95
98
  "wrap-ansi": "^7.0.0",
96
99
  "zod": "^4.3.6",
97
100
  "picocolors": "^1.1.1",
98
- "@zitadel/api": "1.0.0-alpha.23",
99
- "@zitadel/config": "1.0.0-alpha.23",
100
- "@zitadel/server": "1.0.0-alpha.23"
101
+ "@zitadel/api": "1.0.0-alpha.25",
102
+ "@zitadel/config": "1.0.0-alpha.25",
103
+ "@zitadel/server": "1.0.0-alpha.25"
101
104
  },
102
105
  "devDependencies": {
103
106
  "@types/node": "^25.6.0",
104
107
  "@types/semver": "^7.7.1",
105
108
  "@types/wrap-ansi": "^3.0.0",
109
+ "@vitest/coverage-v8": "^4.1.11",
106
110
  "msw": "^2.14.6",
107
111
  "oclif": "^4.17.46",
108
- "tsdown": "^0.21.10",
109
- "vitest": "^3.0.0",
112
+ "tsdown": "^0.23.0",
113
+ "typescript": "^6.0.3",
114
+ "vitest": "^4.1.11",
110
115
  "@zitadel/api-mock": "0.0.0",
111
- "@zitadel/sdk-next": "1.0.0-alpha.23"
116
+ "@zitadel/sdk-next": "1.0.0-alpha.25"
112
117
  },
113
118
  "scripts": {
114
119
  "build": "tsdown",
115
- "typecheck": "tsc --build tsconfig.app.json",
120
+ "typecheck": "tsc --noEmit -p tsconfig.app.json && tsc --noEmit -p tsconfig.spec.json",
121
+ "pretest": "pnpm run build",
116
122
  "test": "vitest run",
123
+ "test:all": "pnpm run test",
117
124
  "readme": "oclif readme"
118
125
  }
119
126
  }
@@ -0,0 +1,260 @@
1
+ ---
2
+ name: zitadel-cli
3
+ description: >-
4
+ Set up and manage Zitadel authentication in a local project with the
5
+ agent-friendly `zitadel` CLI. Use when the user wants to add login,
6
+ registration, or session handling, create a Zitadel project, scaffold auth
7
+ for a Next.js, React, Vue, Angular, Nuxt, Solid, Svelte, or Qwik app, or plan and apply Zitadel
8
+ config changes from repo state.
9
+ ---
10
+
11
+ # Zitadel CLI
12
+
13
+ The `zitadel` CLI integrates Zitadel auth into an existing project and keeps the
14
+ local config (`zitadel.json` and `.zitadel/**`) as the source of truth. Every
15
+ command emits a JSON envelope, so you should drive it non-interactively and
16
+ parse the result rather than scraping human output.
17
+
18
+ This skill teaches how to *drive* the CLI — how to invoke it, its output modes,
19
+ and its envelope. It deliberately does **not** enumerate the commands. The CLI
20
+ is self-describing, and this skill and the installed CLI version move
21
+ independently: a user may install the skill once and upgrade the CLI later, so
22
+ any command or flag list hardcoded here would drift out of lockstep and mislead.
23
+ Ask the CLI for its current surface (see [Discovering commands](#discovering-commands))
24
+ and treat what it prints as authoritative over anything you remember.
25
+
26
+ ## Installing and invoking
27
+
28
+ Run the CLI with `npx`, which fetches and caches it on demand — there is nothing
29
+ to install first:
30
+
31
+ ```sh
32
+ npx @zitadel/cli@alpha <command> --non-interactive --json
33
+ ```
34
+
35
+ During the public alpha, bare `npx @zitadel/cli` is promoted to the same tested
36
+ alpha CLI so discovery works for first-time users. Prefer `@alpha`, or an exact
37
+ `0.1.0-alpha.N` selector in agent scripts and bug reports, for reproducibility.
38
+ The CLI requires Node.js 24+.
39
+
40
+ ## Using the CLI
41
+
42
+ - **Drive it non-interactively.** Always pass `--non-interactive --json` (short:
43
+ `-n`). The JSON object on stdout is the machine contract — parse it rather than
44
+ scraping human output. `--non-interactive` also disables every prompt, so a
45
+ command that would otherwise ask for an answer fails fast with `E_VALIDATION`
46
+ naming the flag to pass instead of blocking. Supply each answer as a flag; run
47
+ the command's `--help` to see which flags exist and which are required.
48
+ - **Output modes.** `--json` is the agent contract and is unaffected by the
49
+ terminal — color and width never apply to it, so you do **not** need
50
+ `--no-color` or other cosmetic flags when you parse JSON; don't sprinkle them
51
+ on every call. Human mode adapts to the terminal: a TTY gets aligned tables,
52
+ while a piped or redirected run (or `--plain`) gets one tab-separated record
53
+ per line and nothing else. If you ever read human output, set `NO_COLOR=1`
54
+ once in the environment rather than repeating `--no-color` per command.
55
+ - **Don't truncate `--json` with `head`/`tail`.** The envelope is one bounded
56
+ object; slicing it with `head -c`/`head -n` yields invalid JSON and silently
57
+ drops records or fields, so you end up reasoning from partial data. It is
58
+ finite — read it whole, or narrow it *at the source*: use a `list` verb's
59
+ `filter_fields` and pagination to fetch fewer records, or pipe to `jq` to pull
60
+ just what you need (e.g. `… resources --json | jq '.data.resources[].name'`).
61
+ `--help` output is bounded too — read it in full rather than `head`-ing it, or
62
+ you will miss flags further down.
63
+ - **Working directory.** Add `--cwd <path>` when operating outside the current
64
+ working directory.
65
+ - **Telemetry.** The CLI sends anonymous usage telemetry by default, and
66
+ agent-driven runs are exactly the ones worth learning from — leave it on.
67
+ Don't add `--no-telemetry` to commands. An opt-out exists for users who need
68
+ it (`ZITADEL_TELEMETRY=0`, or the standard `DO_NOT_TRACK`, in their
69
+ environment), but that is the user's environment choice, not a flag to attach
70
+ to every call.
71
+ - **Flag conventions.** Flags follow what a model already expects from curl, ssh
72
+ and wget: `--help`/`-h`, `--version`, `-v`/`--verbose`, kebab-case long flags,
73
+ both `--flag value` and `--flag=value`, and `--no-telemetry`-style negation.
74
+ Two deliberate deviations worth knowing: `-n` is `--non-interactive` (not
75
+ `--dry-run`, which is long-only), and machine output is `--json` (not
76
+ `--output json`).
77
+ - **Never put secrets on the command line.** A property whose name reads as a
78
+ credential (`password`, `client_secret`, `api_key`, …) is refused anywhere on
79
+ argv — as an `--attributes` entry and inside an inline `--data` body alike,
80
+ since argv is visible to other processes and kept in shell history. Send such
81
+ bodies with `--file <path>` or `--file -` (stdin), which never pass through
82
+ argv.
83
+ - **Streams.** Capture stdout and stderr separately when scripting. The machine
84
+ contract is one parseable JSON object on stdout; installer, audit, and
85
+ package-manager progress belongs on stderr.
86
+
87
+ ## Discovering commands
88
+
89
+ The CLI ships its own command list and per-command flags, always correct for the
90
+ installed version. **Do not guess a command name.** Before running any command you
91
+ are not certain of, run `zitadel --help` first and use the exact name it prints:
92
+ command and resource names — including whether a resource is singular or plural
93
+ and the exact verb — are whatever `--help` shows, never what you would assume. A
94
+ wrong guess costs a failed call and a round of recovery; one cheap `--help` up
95
+ front avoids it. `zitadel --help` is a flat list and cheap to read, so reach for
96
+ it first; treat the CLI's own output as authoritative over anything you remember:
97
+
98
+ ```sh
99
+ npx @zitadel/cli@alpha --help # every command, grouped by purpose
100
+ npx @zitadel/cli@alpha commands # flat list of all commands
101
+ npx @zitadel/cli@alpha <command> --help # a command's flags, which are required, and examples
102
+ npx @zitadel/cli@alpha resources --json # the resource surface in one call (see below); contacts no server
103
+ ```
104
+
105
+ `--help` groups the surface into project commands (create a project and scaffold
106
+ auth), local-server commands (run a local Zitadel), configuration commands
107
+ (`plan`/`apply` plus the config resources), and resource commands (the uniform
108
+ `<resource> <verb>` surface). Read the groups from `--help` rather than from
109
+ memory, since they change as the CLI gains commands.
110
+
111
+ Use `zitadel --help` to find *which* command to run. Use `resources --json` when
112
+ you need a resource's *fields*: it reports the whole runtime-resource surface at
113
+ once — every resource, its verbs, each verb's `create_fields` / `update_fields`
114
+ (flag name, kind, `required`, any closed value set), its `filter_fields` /
115
+ `sort_fields`, and the `delete_outcome` a destructive verb returns — so prefer it
116
+ over reading `<resource> --help` one at a time. A `list` verb's envelope carries
117
+ `data.count`, so a "how many …?" question is one `list --json` call: read the
118
+ count, do not page and tally.
119
+
120
+ ## Reading the envelope
121
+
122
+ Each invocation prints one JSON object:
123
+
124
+ - `status`: `ok` | `skipped` | `error`.
125
+ - `cli_version`, `command`, `source`: always present.
126
+ - On success: `data` with the command-specific payload.
127
+ - On a no-op: `reason` (e.g. `no-framework-detected`, `orphaned-config`).
128
+ - On failure: `code` (e.g. `E_VALIDATION`, `E_NETWORK`, `E_NOT_FOUND`,
129
+ `E_CONFLICT`) and `message`.
130
+ - `next_commands`: the suggested follow-ups. Prefer these over free-text hints.
131
+ - `warnings`: things that went wrong without stopping the command, such as a
132
+ value that was not published. Always present on success (often empty), and
133
+ present on a no-op or failure when there are any. Read them before you report
134
+ success: a warning usually means a follow-up step is needed.
135
+ - A `list` verb emits `data: { items, count, next_page_token }`; when a page
136
+ remains, `data.next_commands` carries the exact command for the next page.
137
+ - `plan` and `apply` emit `data.changes`: one row per touched resource
138
+ (`{kind, action, file, id?, previous_id?}`, action ∈ create/update/revision/
139
+ delete). Plan rows preview; apply rows report, with the resulting platform
140
+ ids. Use it to verify an edit did what you intended.
141
+ - A write verb's `--dry-run` reports what it would send without calling the
142
+ platform, so you can preview a mutation before making it.
143
+
144
+ Common error codes point at their own fix in `next_commands`:
145
+
146
+ - `E_VALIDATION`: the body or flags are wrong; `message` names what, and
147
+ `details` often lists the missing or available values.
148
+ - `E_LOCAL_SERVER_NOT_RUNNING`: start the local runtime, then retry with
149
+ `--server local`.
150
+ - `E_NOT_FOUND`: an HTTP 404 — a missing resource, or a `--server` that is not a
151
+ Zitadel platform API. Follow `next_commands`.
152
+ - `E_PORT_IN_USE`: the requested local runtime port already has a listener; stop
153
+ that process or choose another port.
154
+
155
+ In human mode the output follows the terminal (TTY table, or tab-separated lines
156
+ when piped or `--plain`); `--json` is unaffected and remains the contract.
157
+
158
+ Text the server returns is escaped before it is printed, in `--json` as in human
159
+ mode, so a stored value cannot drive the reader's terminal. Control, format and
160
+ bidi characters appear as visible `\xNN`, `\uNNNN` or `\u{NNNNN}` text, so a
161
+ value that contained one is not the stored value byte for byte — do not send it
162
+ back in an update as if it were. When you need the exact value, call the platform
163
+ API directly with the project secret.
164
+
165
+ Exit codes mirror the error class (3 = validation, 4 = network or not-found,
166
+ 5 = conflict, 1 = auth, 2 = not-implemented, 130 = cancelled). `E_NETWORK`
167
+ covers a server that answered 5xx and one that never answered — refused,
168
+ unresolvable, or silent past the 30-second request deadline. Ctrl-C exits
169
+ 130: a command waiting on the server stops at once with `E_CANCELLED`, and
170
+ any other command exits within a second, possibly without an envelope. An
171
+ unknown command is handled by the CLI's help layer, not the envelope.
172
+
173
+ ## Clarify before you configure
174
+
175
+ Driving the CLI non-interactively does not mean deciding *for* the user. When
176
+ the user asks to add authentication but has not said *how*, don't silently pick
177
+ defaults — ask them first, in one short batched question, then run the CLI
178
+ non-interactively with their answers. The two things sit on different sides of
179
+ the conversation: you clarify with the human, then drive the CLI headlessly.
180
+
181
+ Ask about:
182
+
183
+ - **Sign-in methods** — password (the usual default), passkeys, and single
184
+ sign-on. If they want SSO, which providers?
185
+ - **Profile fields** — the user schema starts with first name, last name, and
186
+ the login identifier plus a password. Which additional fields do they want
187
+ (for example phone, username, or a display name)?
188
+
189
+ Offer only what the installed CLI actually supports: read the real sign-in
190
+ presets, profile fields, and SSO options from the CLI first (`setup --help`,
191
+ `resources --json`) and present those, not a list you remember — the options
192
+ move with the CLI version.
193
+
194
+ Skip the questions and proceed with the defaults above — stating the assumptions
195
+ you made so the user can correct them — in exactly two cases: the user already
196
+ told you what they want, or you are running non-interactively (a one-shot/headless
197
+ invocation) or were explicitly told to just set it up.
198
+
199
+ ## Answering the user
200
+
201
+ Report what changed, in the user's terms — not how you drove the CLI. The
202
+ flags, `--json`, the `plan`/`apply` split, exit codes, and the number of
203
+ commands you ran are your internal business; the user asked for an outcome, not
204
+ a transcript. Say what they got.
205
+
206
+ - Good: "Added an optional phone number to your user schema and deployed it."
207
+ - Avoid: "I ran `plan` and `apply`, both with `--non-interactive --json`, and
208
+ both returned ok."
209
+
210
+ Quote a command or flag only when the user asks *how* something works, or when
211
+ they need to run something themselves. Otherwise keep the mechanics out of the
212
+ answer.
213
+
214
+ ## Golden path
215
+
216
+ The high-level workflow is stable even as individual flags evolve — run each
217
+ command's `--help` for its current flags (for example, `setup --help` lists the
218
+ framework, sign-in preset, and profile-field options):
219
+
220
+ ```sh
221
+ npx @zitadel/cli@alpha doctor --non-interactive --json
222
+ npx @zitadel/cli@alpha start --non-interactive --json
223
+ npx @zitadel/cli@alpha setup --framework next --server local --force --non-interactive --json
224
+ npx @zitadel/cli@alpha doctor --non-interactive --json
225
+ npx @zitadel/cli@alpha plan --non-interactive --json
226
+ npx @zitadel/cli@alpha apply --non-interactive --json
227
+ ```
228
+
229
+ `setup` scaffolds into an empty directory, or patches an existing app project
230
+ where it detects a framework. It stops on any other non-empty directory and
231
+ asks for `--force` — which the golden path passes because `start` has already
232
+ written `.zitadel/local` into the directory. If you run `setup` before `start`,
233
+ in a truly empty directory, `--force` is not needed.
234
+
235
+ The app may also live in a subdirectory of the one `start` ran in: `setup`,
236
+ `console` and `--server local` read `.zitadel/local/` from the working
237
+ directory or the nearest parent that has it (up to the home directory), so
238
+ `mkdir my-app && cd my-app` after `start` works without `--force`. On a local
239
+ server that hosts the platform project, `setup` attaches the project to the
240
+ local admin's team and ends `data.next_commands` with `console`; when it finds
241
+ no local admin for that server on the way up, it says so in the envelope's
242
+ `warnings` and the project stays unattached, so the local console does not
243
+ list it.
244
+
245
+ After `setup`, follow `data.next_commands` to start the app. Prove the generated
246
+ auth flow in a visible browser by registering a unique user, logging out, logging
247
+ back in with the same email/password, and ending on the signed-in profile page.
248
+ Do not treat a rendered login or registration form as completion.
249
+
250
+ Repo config is authoritative: edit `zitadel.json` or files under `.zitadel/`,
251
+ then re-run `plan` and `apply`. See the reference below for driving the login UI
252
+ and the config-sync details.
253
+
254
+ ## Reference files
255
+
256
+ Load on demand:
257
+
258
+ - [`references/driving-login-ui.md`](references/driving-login-ui.md) — driving the
259
+ `<zitadel-login>` component in a browser, and the authoritative repo-config
260
+ workflow (schemas, flows, branding).
@@ -0,0 +1,72 @@
1
+ ### Driving the login UI
2
+
3
+ `<zitadel-login>` and `<zitadel-logout>` are Lit elements with open shadow
4
+ roots. The stable automation hooks live inside nested shadow roots, so a flat
5
+ `document.querySelector('[data-testid="zitadel-input-email"]')` will not find
6
+ the native control. Browser drivers with shadow-DOM-aware locators, such as
7
+ Playwright, can target the hooks directly. Generic DOM-eval drivers should
8
+ pierce shadow roots recursively:
9
+
10
+ ```js
11
+ function deepQuery(sel, root = document) {
12
+ const hit = root.querySelector(sel);
13
+ if (hit) return hit;
14
+ for (const el of root.querySelectorAll("*")) {
15
+ if (el.shadowRoot) {
16
+ const result = deepQuery(sel, el.shadowRoot);
17
+ if (result) return result;
18
+ }
19
+ }
20
+ return null;
21
+ }
22
+ ```
23
+
24
+ Use host hooks such as `zitadel-field-email`, `zitadel-field-password`, and
25
+ `zitadel-action-submit` when targeting the Lit atoms. Use native shadow-control
26
+ hooks such as `zitadel-input-email`, `zitadel-input-password`, and
27
+ `zitadel-action-submit-button` when filling or clicking the underlying input or
28
+ button. Hooks stay method-named even when the flow engine names a credential
29
+ field `x-auth-methods#<method>`; only the `name` attribute carries that raw
30
+ form key. Enter inside a field submits the step's primary action, but only for
31
+ key events that carry `key: "Enter"` — drivers whose synthesized key events
32
+ omit it (some CDP wrappers) should click `zitadel-action-submit` instead. For sign-out, open the user menu button if needed, then pierce to
33
+ `.signout-btn`; Playwright-style locators may use `zitadel-logout .signout-btn`.
34
+ The canonical component hook list lives in `packages/components/README.md`.
35
+
36
+ The checked-in automated regression path is `moon run workspace:journey`, which
37
+ exercises fresh-app setup plus registration, logout, and login across the
38
+ supported frameworks.
39
+
40
+ Repo config is authoritative: edit `zitadel.json` or files under `.zitadel/`,
41
+ then re-run `plan` and `apply`. Schema and flow files are synced from
42
+ `.zitadel/schemas/*.json` and `.zitadel/flows/*.json`. Login templates
43
+ (branding) are synced from `.zitadel/branding/`: a single `branding.json`
44
+ descriptor (layout, asset URLs) plus a sibling `login.liquid` LiquidJS
45
+ template referenced as `"liquid_template": { "$file": "./login.liquid" }`. Scaffold them with the
46
+ `branding eject` command (`--design centered|minimal`,
47
+ interactive picker on a TTY); setup never scaffolds them. Branding is
48
+ revisioned and immutable: every edit — including a `.liquid`-only edit —
49
+ plans as a `revise` and `apply` publishes a new revision; the login serves
50
+ the newest one. `plan` validates templates with the authoritative LiquidJS
51
+ validator (`E_VALIDATION` lists rule ids such as `no-script-tag` and
52
+ `mandatory-gates`; every template must keep a trailing
53
+ `{% mandatory_gates %}` tag). `font_url` is not writable yet; asset URLs
54
+ must be absolute `https://`. Keep exactly one descriptor in
55
+ `.zitadel/branding/` — extra `*.json` files there fail the scan.
56
+ Server-provisioned defaults remain a fallback for non-CLI project
57
+ creation, but CLI-created projects are authored from local files first. Flows are
58
+ revisioned like branding: an edit plans as a `revise` and `apply` publishes a new
59
+ immutable flow revision; a schema revise re-publishes the flows pinned to it with
60
+ the new `user_schema` in the same run. A login pinned by `flow-name` serves that
61
+ flow's newest revision; an unpinned login serves the newest active unscoped flow
62
+ in the project, whatever its name. Removing a flow file does not retire the
63
+ flow. Managed files carry a marker comment; `eject` removes only
64
+ files that still carry it, preserving anything the user replaced. For app-local
65
+ development, `--server local` resolves through `.zitadel/local/runtime.json` and
66
+ requires a healthy
67
+ `npx @zitadel/cli@alpha start` runtime. Because `start` writes `.zitadel/local/`
68
+ into the directory, it is no longer empty, so a same-directory `setup` that
69
+ scaffolds must be passed `--force` (this is exactly what the golden path does).
70
+ `setup` installs dependencies
71
+ with the detected package manager by default; pass `--skip-install` when the
72
+ agent or host workflow will install dependencies separately.