@capxul/cli 4.20.0-beta.1 → 4.20.0-beta.10

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/README.md CHANGED
@@ -1,7 +1,9 @@
1
1
  # Capxul CLI
2
2
 
3
- The CLI provides local diagnostics, an online backend check, and one global
4
- collection preference. It requires Node 24 or later on macOS or Linux.
3
+ The CLI provides email OTP login, persistent first-party sessions, terminal-owned
4
+ signup, personal and Organization contacts, a bundled Openfort wallet page,
5
+ backend profile and account reads, diagnostics, and one global collection
6
+ preference. It requires Node 24 or later on macOS or Linux.
5
7
 
6
8
  ```sh
7
9
  capxul --help
@@ -14,27 +16,300 @@ capxul telemetry enable --json
14
16
  capxul doctor --online --timeout-ms 30000 --json
15
17
  ```
16
18
 
17
- Help, version, and completion do not read configuration or contact a server.
19
+ Help and version do not require application credentials or a working backend.
20
+ With no arguments, `capxul` shows the same generated help as `capxul --help`.
21
+ They use the same observation policy as ordinary commands. Shell completion
22
+ machinery and collection controls send no observation records.
18
23
  `doctor` checks local configuration unless `--online` is present. An online
19
24
  check uses the public Capxul SDK and verifies the backend response nonce.
20
25
  It does not authenticate a person or submit a transaction.
21
26
 
27
+ ## Contact commands
28
+
29
+ ```sh
30
+ capxul contact list [--include-hidden]
31
+ capxul contact get --entry-id PARTY_ID
32
+ capxul contact add --input FILE|- [--confirm]
33
+ capxul contact label --entry-id PARTY_ID --label TEXT [--confirm]
34
+ capxul contact hide --entry-id PARTY_ID [--confirm]
35
+ capxul contact unhide --entry-id PARTY_ID [--confirm]
36
+
37
+ capxul org contact list --org ORGANIZATION_ID [--include-hidden]
38
+ capxul org contact get --org ORGANIZATION_ID --entry-id PARTY_ID
39
+ capxul org contact add --org ORGANIZATION_ID --input FILE|- [--confirm]
40
+ capxul org contact label --org ORGANIZATION_ID --entry-id PARTY_ID --label TEXT [--confirm]
41
+ capxul org contact hide --org ORGANIZATION_ID --entry-id PARTY_ID [--confirm]
42
+ capxul org contact unhide --org ORGANIZATION_ID --entry-id PARTY_ID [--confirm]
43
+ ```
44
+
45
+ Personal commands use the authenticated Account address book. Organization
46
+ commands require one explicit `--org` or `--org-id`; they never use the saved
47
+ Organization read default. `list` omits hidden entries unless
48
+ `--include-hidden` is present. `get`, `label`, `hide`, and `unhide` address one
49
+ stable Party with `--entry-id`.
50
+
51
+ `add --input` reads the existing `AddressBookAddInput` JSON shape. For example:
52
+
53
+ ```json
54
+ {
55
+ "ref": { "kind": "email", "email": "supplier@example.test" },
56
+ "label": "New Supplier"
57
+ }
58
+ ```
59
+
60
+ Use `--input -` to read the same object from stdin. An interactive `add` can
61
+ collect the reference kind, value, and optional label instead. Interactive
62
+ writes show the actor and exact contact change, then ask a default-no question.
63
+ JSON, CI, and other noninteractive writes require `--confirm` before the CLI
64
+ creates a client. Contact writes do not start a browser signer.
65
+
66
+ Success data is the SDK value without a CLI wrapper: `list` returns
67
+ `AddressBookEntry[]`, `get` returns `AddressBookEntry | null`, and each mutation
68
+ returns `AddressBookEntry`. Human `get` prints `No contact found` for null and
69
+ exits successfully. Entries retain their stable PartyId, exact reference,
70
+ relationships, hidden state, and last activity time.
71
+
72
+ ## Organization commands
73
+
74
+ ```sh
75
+ capxul org list --json
76
+ capxul org use --org ORGANIZATION_ID
77
+ capxul org status --org ORGANIZATION_ID --json
78
+ capxul org get --json
79
+ capxul org me --json
80
+ capxul org members --json
81
+ capxul org member list --json
82
+ capxul org member get --account-id ACCOUNT_ID --json
83
+ capxul org wait --timeout-seconds 120 --json
84
+ capxul org create --name NAME --handle HANDLE --country CC [--bio TEXT] [--size TEXT] [--confirm]
85
+ capxul org retry --org ORGANIZATION_ID [--confirm]
86
+ ```
87
+
88
+ These commands accept `--email`. Without it, they restore the protected current
89
+ session. Organization reads accept `--org` or its alias `--org-id`. Identical
90
+ repeated IDs are accepted. Different IDs are refused.
91
+
92
+ A read without `--org` resolves one exact Organization in this order: the saved
93
+ read default, then the recovery handle this machine recorded for the person's
94
+ Organization journey. A recovery handle can also name an Organization whose
95
+ setup lane still runs, which the own-access list does not carry yet; the read
96
+ reports that exact lane, and it applies the usual current-access rule again once
97
+ the Organization reports ready. The read refuses an Organization it cannot
98
+ reach, and it never substitutes another one.
99
+
100
+ `org use` requires an explicit Organization ID. It checks current access and
101
+ saves a protected per-person read default. The default never grants authority.
102
+ Each read checks current access again. Without an explicit ID or saved default,
103
+ interactive mode asks you to select an Organization. JSON, CI, and noninteractive
104
+ mode refuse missing scope. No command starts a signer or changes backend state.
105
+
106
+ `org get` reports the canonical command `org.status`. Both member list forms
107
+ report `org.members` and retain pending members with `accountId: null`.
108
+ `org member get` matches the full AccountId, not a Safe address or ID prefix.
109
+ No match returns exit 2. Duplicate matches return exit 1.
110
+
111
+ `org wait` reads the same lifecycle every second until it reports `ready` or
112
+ `failed`. It never retries setup. The timeout accepts integers from 1 to 3600
113
+ seconds and defaults to 120. Timeout returns exit 5; interruption returns 130.
114
+ A successful read can report a failed or pending domain state.
115
+
116
+ ## Invitation commands
117
+
118
+ ```sh
119
+ capxul org invite list [--mine] [--limit N] [--cursor C] [--phase PHASE ...] [--org O]
120
+ capxul org invite get --invitation-id I [--org O]
121
+ capxul org invite wait --invitation-id I [--org O] [--timeout-seconds N]
122
+ capxul org invite review --invitation-id I --org O [--timeout-seconds N]
123
+ capxul org invite accept --invitation-id I --offer-digest D --org O [--confirm] [--timeout-seconds N]
124
+ capxul org invite decline --invitation-id I --org O [--confirm]
125
+ capxul org invite cancel --invitation-id I --org O [--confirm]
126
+ capxul org invite resend --invitation-id I --org O [--confirm]
127
+ capxul org invite retry --invitation-id I --org O [--confirm]
128
+ capxul org member invite (--to-email E | --account-id ID) [--budget-id B ...] [--permission-id M ...] [--new-budget-name N --asset A (--limit Q | --unlimited) (--recipient-account ID ... | --any-recipient) --actions pay[,commitments]] --org O [--request-key K] [--preview] [--confirm]
129
+ ```
130
+
131
+ `org invite list` returns your own offers. It accepts `--limit` (1--100),
132
+ `--cursor`, and repeated `--phase` values, and its result carries `invitations`,
133
+ `nextCursor`, and `observedAt`: pass `nextCursor` back as `--cursor`, and change
134
+ no other filter between the two calls. `--mine` names the own-offer projection,
135
+ which is already the default. With `--org O` the command returns that
136
+ Organization's invitations for a current Admin instead; the Organization list is
137
+ not filtered to you, so `--mine` together with `--org` refuses with exit 2. The
138
+ other invitation commands require an exact `--invitation-id`.
139
+
140
+ `org invite get` and `org invite wait` use `--org` when you name it. Without
141
+ `--org` they walk your own-offer pages, 100 rows per request, until they find
142
+ the invitation or the pages end. They never consult the saved read default, and
143
+ an offer you cannot see is refused. `--timeout-seconds` bounds the resolution
144
+ and the read together on both commands.
145
+
146
+ `org invite wait` only reads. It finishes when the offer settles or when the
147
+ recovery asks a person to act. It never retries, resends, or accepts.
148
+
149
+ `org invite review` requires `--org`, returns the current `InvitationView`
150
+ directly in `data`, and performs no write or signing.
151
+
152
+ The four transitions require `--org` in both modes, as `accept` does. They never
153
+ author a new offer. A noninteractive or JSON run requires `--confirm`. An
154
+ interactive run shows the resolved current offer and asks a default-no question.
155
+
156
+ `org member invite` authorizes one exact offer. `--preview` writes nothing and
157
+ needs neither a request key nor confirmation. A noninteractive write requires
158
+ `--request-key` and `--confirm`. An interactive run prints the request key it
159
+ used before it submits, so a lost response is recoverable with the same
160
+ identity. At most one `--new-budget-name` is accepted per command.
161
+
162
+ `org invite accept` is the exact grantee's consent commit. It takes the exact
163
+ digest the offer shows as `--offer-digest`: a noninteractive or JSON run must
164
+ supply it, and without it the command refuses with exit 2 before it creates a
165
+ client. An interactive run may omit it, and then reads the offer and fills the
166
+ digest from the value it displayed. A digest you state is never replaced by the
167
+ displayed one. `--expected-offer` remains a compatibility spelling, and both
168
+ spellings must match when supplied together. The command returns the
169
+ `InvitationView` directly in `data`. The grant is executed by the deployment's
170
+ technical executor, so the command needs no browser bridge and works in a
171
+ headless or CI session. A repeat with the stored digest returns the same
172
+ accepted result; a different digest refuses and cannot overwrite consent. The
173
+ returned view is the authorization-time view of the consent commit
174
+ (`pending_grant`); read the settled `active` state with `get` or `wait`.
175
+
176
+ ## Organization writes
177
+
178
+ `org create` and `org retry` restore the protected current session and start the
179
+ existing command-scoped browser signer. They never read or write the saved read
180
+ default. `org create` writes the person's Organization recovery handle. That
181
+ handle holds the exact business handle and the exact Organization ID, and it
182
+ holds nothing else. Local input checks run before any client, browser, or
183
+ backend work, and they include the confirmation gate:
184
+
185
+ - A noninteractive or JSON write requires `--confirm`. Without it the command
186
+ refuses with exit 2 before any client, browser, or mutation work.
187
+ - A terminal write verifies the session and performs its Organization reads
188
+ first, then prints the resolved preview and asks a default-no prompt, even when
189
+ `--confirm` is supplied. Rejection refuses with exit 2. An absent session is
190
+ exit 3 before any preview. The browser signer is built only after confirmation
191
+ succeeds, so a refused write never starts it.
192
+
193
+ `--email` selects the session explicitly. Otherwise the protected
194
+ current-session pointer decides. An invalid explicit email refuses with exit 2.
195
+
196
+ `--timeout-seconds` bounds the command's own wait, not backend execution. It
197
+ accepts integers from 1 to 3600 and defaults to 120, and it is one deadline for
198
+ the whole write: the `completeOrganization` or `retrySetup` call and the
199
+ settlement that follows share it. While a lane settles the command reads the
200
+ exact Organization lifecycle every second with no overlapping read. At the
201
+ deadline the active SDK signal is aborted, so the command stops waiting and
202
+ forwards no further signature, while the durable backend lane keeps running.
203
+ Committed lifecycle progress is flushed first, so the Organization ID stays on
204
+ stderr and the same Organization resumes with `org retry --org`. Exceeding the
205
+ deadline returns exit 5.
206
+
207
+ `org create` validates `--name`, the `--handle` grammar, and the ISO `--country`
208
+ code first, and its terminal preview also lists the Organizations the person
209
+ already has. It subscribes to the onboarding Organization-state projection, then
210
+ completes the Organization with an explicit create intent. The first committed
211
+ Organization ID is retained, and each committed lifecycle change is written to
212
+ stderr before the next Organization authorization digest reaches the signer.
213
+ JSON mode writes one newline-delimited progress object per change:
214
+
215
+ ```json
216
+ { "type": "organization.lifecycle", "organizationId": "O", "status": "settingUp" }
217
+ ```
218
+
219
+ Human mode prints the full Organization ID and lifecycle status. A refused or
220
+ failed lane still reports the Organization it is about. The command returns
221
+ success only after an exact `org(O).getLifecycle()` read reports `ready`. It
222
+ never saves a read default and never returns a different ready Organization.
223
+
224
+ `org create` continues the Organization journey this machine already prepared.
225
+ Before the first durable write it records the exact business handle as the safe
226
+ recovery handle. It adds the first committed Organization ID to that handle
227
+ before it forwards any founder authorization digest, and it keeps that handle
228
+ after a failure, an interruption, or a ready result. A later create therefore
229
+ resolves exactly one Organization:
230
+
231
+ - The same handle resumes the exact recorded Organization. It calls the SDK
232
+ recovery owner once per attempt and settles the same lifecycle. It never
233
+ submits a second bootstrap, and it never creates a second Organization or
234
+ treasury.
235
+ - A different handle refuses with exit 2 while the recorded Organization is not
236
+ ready. The refusal names the exact Organization.
237
+ - A different handle creates one Organization only after the recorded
238
+ Organization reaches ready.
239
+ - A recorded Organization that is already ready, and still in the person's
240
+ current access, returns unchanged, with no write and no signature. A ready
241
+ result the person can no longer reach refuses, and the committed recovery
242
+ handle stays recorded for exact recovery.
243
+ - A create that continues a recorded handle checks current access before it
244
+ reports success. The committed Organization ID becomes durable first, so a
245
+ refusal or an interruption keeps the exact recovery handle, and a ready
246
+ lifecycle line reaches stderr only after the check resolves.
247
+ - A terminal preview reads the recorded Organization's lifecycle only for the
248
+ handle that continues it. A different handle is a new creation, so an
249
+ unreachable recorded Organization never blocks it.
250
+ - A handle the backend refuses is refused before any Organization write. The
251
+ command releases the prepared handle, so the person can correct the handle and
252
+ create. Every other failure keeps the handle, because its Organization may
253
+ already exist.
254
+
255
+ The recovery line names the exact Organization and the safe handle. JSON mode
256
+ writes it as one newline-delimited progress object:
257
+
258
+ ```json
259
+ { "type": "organization.recovery", "organizationId": "O", "handle": "acme" }
260
+ ```
261
+
262
+ `org retry` stays explicit: `--org` (or its alias `--org-id`) is the one write
263
+ scope, and no read default or recovery handle is consulted.
264
+
265
+ `org retry` requires an explicit `--org` (or `--org-id`). It validates the ID
266
+ with the public `toOrgId` constructor, reads that exact Organization's lifecycle
267
+ for its terminal preview, resumes that same durable setup lane, then settles the
268
+ same Organization's lifecycle. A resumed lane that asks the
269
+ signer to reset its session clears the stale readiness, closes that browser
270
+ bridge, and opens the next one for the same command. `org retry` never creates a
271
+ replacement Organization and never changes the read default. An already-ready
272
+ Organization returns that exact readiness without a signature. A conflicting
273
+ failed or in-flight lane is preserved and refused.
274
+
275
+ The result data for both commands is `{ organizationId, lifecycle }`. A known
276
+ Organization ID on a `WRONG_STATE` refusal appears only in the SDK-supplied
277
+ `error.details.organizationId` field, and the refusal copy names the recovery
278
+ action instead of authentication guidance. No token, OTP, signature, or signer
279
+ capability is ever printed. Interruption returns 130 and closes both the
280
+ subscription and the browser bridge.
281
+
22
282
  ## Configuration
23
283
 
24
284
  | Variable | Use |
25
285
  | ------------------------------ | ------------------------------------------------------------------------------------------------------------ |
26
286
  | `CAPXUL_CLI_HOME` | Directory for global CLI settings. Defaults to `$XDG_CONFIG_HOME/capxul/cli`, or `$HOME/.config/capxul/cli`. |
27
- | `CAPXUL_PUBLISHABLE_KEY` | Application publishable key, required for an online check. |
287
+ | `CAPXUL_PUBLISHABLE_KEY` | Optional developer override for the bundled first-party staging key. |
28
288
  | `CAPXUL_BOOTSTRAP_URL` | Bootstrap origin. Defaults to `https://api.staging.capxul.com`. Convex Cloud origins are rejected. |
29
- | `CAPXUL_POSTHOG_HOST` | PostHog ingestion origin. |
30
- | `CAPXUL_POSTHOG_PROJECT_TOKEN` | Project ingestion token. Both PostHog variables are required for export. |
289
+ | `CAPXUL_POSTHOG_HOST` | Optional development override for the built-in public PostHog ingestion origin. |
290
+ | `CAPXUL_POSTHOG_PROJECT_TOKEN` | Optional development override for the built-in public project ingestion token. |
31
291
  | `CAPXUL_TELEMETRY_DISABLED` | Set to `true` to disable remote observation regardless of the saved preference. |
32
292
 
33
- Collection defaults to enabled. Local commands send no remote signals.
293
+ The published CLI includes the verified first-party staging application key and
294
+ Capxul-owned public ingestion configuration. You do not need key or PostHog
295
+ environment variables. A different bootstrap origin requires an explicit key. Collection defaults to enabled for
296
+ ordinary local and online commands, help, version, and safely attributed argument
297
+ refusals. Parser refusals produce a completion without a start. Early native
298
+ global errors with no resolved command route send nothing, because the CLI
299
+ cannot determine whether they belong to a silent collection control.
34
300
  `telemetry disable` saves one preference for the OS user and sends no final
35
301
  remote event. Already running CLI processes check the current preference before
36
302
  each export. Requests already sent cannot be recalled. `telemetry status`
37
303
  reports the stored preference, effective policy, configuration, and reason.
304
+ `--log-level` controls normal diagnostic output, not collection. An eligible
305
+ invocation still produces its remote completion unless collection is disabled.
306
+
307
+ Eligible commands reuse a random anonymous identifier stored in the protected
308
+ CLI directory. Invocation and trace identifiers remain separate. A first-use
309
+ marker records the first eligible observed use, not a download or a verified
310
+ person. If this state cannot be accessed safely, observation stops and the
311
+ command retains its normal result. Delivery is bounded and best-effort; the CLI
312
+ does not keep a persistent activity queue.
38
313
 
39
314
  Settings use a versioned JSON file in a directory with mode `0700`. The file has
40
315
  mode `0600`. Writes are atomic and serialize between processes. A later writer
@@ -43,8 +318,14 @@ unsafe permissions, symlinks, and corrupt state produce a storage failure.
43
318
 
44
319
  ## Output
45
320
 
46
- `--json` writes one version 1 envelope to stdout. Success contains `data`.
47
- Failure contains `error.code` and CLI-owned `error.message`. Each envelope has
321
+ `--json` writes one version 1 envelope to stdout. Success contains `data`, which
322
+ can be an object, array, or null according to the command's SDK result.
323
+ Failure contains `error.code` and CLI-owned `error.message`. A wallet failure
324
+ also includes its known `error.mode` and allowed `error.details`: wallet stage,
325
+ operation, provider, provider code, and HTTP status. An unknown browser failure
326
+ uses mode `unknown`. No provider message, token, signature, or native cause enters
327
+ the result. These fields remain available when collection is off or
328
+ `--log-level none` is set. Each envelope has
48
329
  `command`, `invocationId`, and `outcome`. An invocation ID is null before a
49
330
  command starts. Human errors go to stderr.
50
331
 
@@ -93,6 +374,15 @@ compinit
93
374
  Use `npm update -g @capxul/cli` or `brew upgrade xelmar-tech/tap/capxul` to update.
94
375
  Use one installer for the `capxul` executable to avoid conflicting PATH entries.
95
376
 
377
+ After a successful human command, help, or version request, the CLI can show an
378
+ update notice on terminal stderr. It checks the public npm `latest` tag at most
379
+ once per 24 hours. It shows only the command for the verified running npm-global
380
+ or Homebrew installation. It does not execute that command. Unknown installations,
381
+ development versions, JSON output, redirected stderr, CI, shell completion, and
382
+ all `telemetry` commands receive no notice. The optional check has one 500 ms
383
+ budget. Safe failed attempts are cached. Storage or network failures remain
384
+ silent and do not change the command result. This check sends no telemetry.
385
+
96
386
  ## Development commands
97
387
 
98
388
  ```sh
@@ -105,3 +395,103 @@ vp exec node --test --test-name-pattern='@capxul/cli' scripts/__tests__/pack-ins
105
395
  The last command installs the tarball outside the workspace and exercises the
106
396
  installed executable with isolated settings and a local HTTP server. The CI
107
397
  `CLI / Linux / Node 24.0.0` job runs this proof at the declared minimum version.
398
+
399
+ ## Email login and session restoration
400
+
401
+ This CLI establishes a first-party BetterAuth session. It has broader authority
402
+ than the earlier native `account:read` grant. Existing native grants remain
403
+ stored and tagged as `legacy-native-grant` in status. A profile read asks for an
404
+ explicit new login; it never exchanges the old grant for a broader session.
405
+ Logout forgets and attempts to revoke both credential types for the selected email.
406
+
407
+ `capxul auth login` and `capxul auth signup` stay separate commands. On a
408
+ terminal, each command is a wizard. `auth login` asks for the email and a masked
409
+ OTP. It accepts the code as a paste. It retries an invalid code at most three
410
+ times for one request. After sign-in, an incomplete account gets one offer to
411
+ continue setup in the same command. `auth signup` authenticates first, then asks
412
+ for the Profile fields: display name, two-letter country code, and handle. A
413
+ known value is the prompt default, so Enter keeps it.
414
+
415
+ The browser opens only when the shared Core onboarding journey needs Openfort
416
+ wallet readiness. It shows wallet status only. It has no email, OTP, Profile, or
417
+ approval form.
418
+
419
+ Ctrl+C or Ctrl+D at a wizard prompt cancels the command. The command prints no
420
+ success result and exits 130. A step that already finished keeps its result: a
421
+ completed sign-in keeps its session.
422
+
423
+ For an agent, send and verify in separate processes:
424
+
425
+ ```sh
426
+ capxul auth send --email "$TEST_EMAIL" --json
427
+ # Supply the delivered six-digit OTP through stdin, not a command argument.
428
+ capxul auth verify --email "$TEST_EMAIL" --otp-stdin --json
429
+ capxul auth profile --email "$TEST_EMAIL" --json
430
+ # A later process uses the same protected CLI home; no new OTP is required.
431
+ capxul auth profile --email "$TEST_EMAIL" --json
432
+ capxul auth status --email "$TEST_EMAIL" --json
433
+ capxul auth logout --email "$TEST_EMAIL" --json
434
+ ```
435
+
436
+ A machine run never prompts. `auth login` with `--otp-stdin` sends the OTP and
437
+ verifies one code from stdin. It returns the tagged result
438
+ `setupState: "setup-required"` when the account needs setup. It does not start
439
+ setup work, so sign-in never becomes signup.
440
+
441
+ A fresh machine signup does verification and setup in one command. State every
442
+ Profile field; a missing field refuses with exit 2:
443
+
444
+ ```sh
445
+ # Supply only the delivered OTP through stdin.
446
+ capxul auth signup --email "$TEST_EMAIL" --otp-stdin \
447
+ --display-name "Test Person" --country GH --handle test_person --json
448
+ ```
449
+
450
+ `--otp-stdin` reads up to 64 bytes, ending at EOF. Pipe from a secret provider or
451
+ use input redirection from a protected file. An invalid value refuses with exit 2. A missing `--otp-stdin` refuses with exit 2 before the command starts client
452
+ work. OTPs are never accepted in argv, persisted in the continuation, or included
453
+ in output.
454
+
455
+ The local page binds to an OS-selected loopback port. A random launch capability
456
+ is redeemed once and removed from the URL before Openfort starts. The page receives
457
+ only the authenticated wallet token and encryption session in memory. Closing it
458
+ ends the current wallet attempt. Re-run `auth signup` with the same CLI home to
459
+ resume the same Profile and Account without another OTP while the backend session
460
+ remains valid.
461
+
462
+ `auth signup` returns only two readiness results. `setupState: "ready"` is a
463
+ personal Account that the Account readiness owner reads as ready and deployed,
464
+ and its data carries the public identifiers: the Profile, the public wallet
465
+ address (`smartAccount.signerAddress`), the personal Smart Account address
466
+ (`smartAccount.smartAccountAddress`), the Account ID
467
+ (`lifecycle.accountId`), and the ready status. Any other state returns the typed
468
+ recoverable result `setupState: "setup-required"`, with the lifecycle, its
469
+ failure code, and the command that resumes the journey. A ready Account starts
470
+ no wallet work, so a retry keeps the one Profile, wallet, Smart Account, and
471
+ Account. An interruption keeps the stored session and stores no code, so the
472
+ next command resumes the same setup.
473
+
474
+ A ready personal Account continues the same journey into Organization creation
475
+ with `capxul org create --name NAME --handle HANDLE --country CC`. The create
476
+ command records the safe recovery handle for that Organization, so an
477
+ interruption or a restart resumes the exact same Organization.
478
+
479
+ The CLI checks `--display-name`, `--country`, and `--handle` before it opens a
480
+ client, so a malformed value refuses with exit 2 and no code is sent. The handle
481
+ check covers the grammar only: a handle that another person holds, and a reserved
482
+ handle, refuse through the identity owner after sign-in. Both refusals name the
483
+ handle, and neither offers verification-code recovery for a Profile problem.
484
+
485
+ The version 2 `first-party-session` record stores the opaque provider credential
486
+ in the existing protected plaintext store. Its scope includes the application
487
+ key, issuer, environment, and email. Each process validates restoration with the
488
+ backend; cached session data is not authority. Conditional replacement prevents
489
+ a concurrent logout from being undone by a delayed credential save. The backend
490
+ owns expiry and revocation. A failed remote logout is reported as `unconfirmed`;
491
+ local sign-out remains in effect.
492
+
493
+ `auth profile` returns a backend-read Profile and account lifecycle without opening
494
+ the browser. Successful email authentication can return `setupState: "setup-required"`.
495
+ `auth signup` returns `setupState: "ready"` only after Core reads a ready Account.
496
+ After logout, profile reads refuse with `NOT_AUTHENTICATED` and exit 3. An invalid
497
+ or expired OTP refuses with exit 2.
Binary file