@certen.io/cli 0.4.0 → 0.7.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.
Files changed (87) hide show
  1. package/CHANGELOG.md +440 -0
  2. package/README.md +232 -160
  3. package/dist/chains.d.ts +67 -0
  4. package/dist/chains.js +242 -0
  5. package/dist/chains.js.map +1 -0
  6. package/dist/commands/admin.js +49 -4
  7. package/dist/commands/admin.js.map +1 -1
  8. package/dist/commands/auth.js +145 -12
  9. package/dist/commands/auth.js.map +1 -1
  10. package/dist/commands/billing.d.ts +2 -0
  11. package/dist/commands/billing.js +782 -0
  12. package/dist/commands/billing.js.map +1 -0
  13. package/dist/commands/call.d.ts +2 -0
  14. package/dist/commands/call.js +168 -0
  15. package/dist/commands/call.js.map +1 -0
  16. package/dist/commands/chains.d.ts +2 -0
  17. package/dist/commands/chains.js +123 -0
  18. package/dist/commands/chains.js.map +1 -0
  19. package/dist/commands/doctor.d.ts +2 -0
  20. package/dist/commands/doctor.js +171 -0
  21. package/dist/commands/doctor.js.map +1 -0
  22. package/dist/commands/errors-catalogue.d.ts +2 -0
  23. package/dist/commands/errors-catalogue.js +83 -0
  24. package/dist/commands/errors-catalogue.js.map +1 -0
  25. package/dist/commands/identity.js +269 -19
  26. package/dist/commands/identity.js.map +1 -1
  27. package/dist/commands/init.d.ts +2 -0
  28. package/dist/commands/init.js +307 -0
  29. package/dist/commands/init.js.map +1 -0
  30. package/dist/commands/keys.js +2 -0
  31. package/dist/commands/keys.js.map +1 -1
  32. package/dist/commands/oauth-clients.d.ts +2 -0
  33. package/dist/commands/oauth-clients.js +131 -0
  34. package/dist/commands/oauth-clients.js.map +1 -0
  35. package/dist/commands/orgs.d.ts +2 -0
  36. package/dist/commands/orgs.js +117 -0
  37. package/dist/commands/orgs.js.map +1 -0
  38. package/dist/commands/pending.js +13 -1
  39. package/dist/commands/pending.js.map +1 -1
  40. package/dist/commands/portfolio.js +34 -1
  41. package/dist/commands/portfolio.js.map +1 -1
  42. package/dist/commands/proof.d.ts +2 -0
  43. package/dist/commands/proof.js +400 -0
  44. package/dist/commands/proof.js.map +1 -0
  45. package/dist/commands/signup.d.ts +14 -0
  46. package/dist/commands/signup.js +271 -0
  47. package/dist/commands/signup.js.map +1 -0
  48. package/dist/commands/transaction.js +144 -21
  49. package/dist/commands/transaction.js.map +1 -1
  50. package/dist/commands/webhooks.d.ts +2 -0
  51. package/dist/commands/webhooks.js +200 -0
  52. package/dist/commands/webhooks.js.map +1 -0
  53. package/dist/commands/whoami.d.ts +16 -0
  54. package/dist/commands/whoami.js +98 -0
  55. package/dist/commands/whoami.js.map +1 -0
  56. package/dist/config.d.ts +56 -0
  57. package/dist/config.js +51 -0
  58. package/dist/config.js.map +1 -1
  59. package/dist/errors.d.ts +14 -1
  60. package/dist/errors.js +15 -1
  61. package/dist/errors.js.map +1 -1
  62. package/dist/funding-guard.d.ts +40 -0
  63. package/dist/funding-guard.js +120 -0
  64. package/dist/funding-guard.js.map +1 -0
  65. package/dist/help-root.d.ts +2 -0
  66. package/dist/help-root.js +94 -0
  67. package/dist/help-root.js.map +1 -0
  68. package/dist/index.js +40 -8
  69. package/dist/index.js.map +1 -1
  70. package/dist/output.d.ts +23 -0
  71. package/dist/output.js +90 -0
  72. package/dist/output.js.map +1 -1
  73. package/dist/passphrase.d.ts +18 -0
  74. package/dist/passphrase.js +35 -3
  75. package/dist/passphrase.js.map +1 -1
  76. package/dist/payment-uri.d.ts +76 -0
  77. package/dist/payment-uri.js +135 -0
  78. package/dist/payment-uri.js.map +1 -0
  79. package/dist/signer.js +6 -3
  80. package/dist/signer.js.map +1 -1
  81. package/dist/solidity-args.d.ts +31 -0
  82. package/dist/solidity-args.js +111 -0
  83. package/dist/solidity-args.js.map +1 -0
  84. package/dist/wait.d.ts +79 -0
  85. package/dist/wait.js +168 -0
  86. package/dist/wait.js.map +1 -0
  87. package/package.json +5 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,445 @@
1
1
  # Changelog — @certen.io/cli
2
2
 
3
+ ## 0.7.0 — one response shape
4
+
5
+ **Breaking for `--json` consumers.** Requires a gateway from 2026-08 or later.
6
+ ### Added — `certen fund` closes the money path
7
+
8
+ Four changes to the last step of onboarding, which is the only one where a mistake costs money
9
+ rather than time.
10
+
11
+ **A link a wallet can open.** `--uri` emits an EIP-681 request carrying the token contract, the
12
+ numeric chain, the recipient and the amount in the token's smallest unit — so the transfer stops
13
+ being four values transcribed by hand. **A mistyped recipient is the one error in this product that
14
+ loses real money irreversibly.** The amount is converted with string and BigInt arithmetic, never
15
+ floats: `25.10 * 1e6` is `25099999.999999996` in IEEE-754, which truncates to one unit short of the
16
+ amount attribution matches on — a deposit that arrives and is never credited, with nothing on either
17
+ side saying why. It is also in the machine payload unconditionally, so a script building a deep link
18
+ never repeats that arithmetic.
19
+
20
+ **Register the payer when you know which wallet you are sending from.** `--payer 0x…` registers it
21
+ inline, so future deposits credit on sight with no exact-amount match to beat. `init --payer` existed
22
+ but runs before anyone has chosen a wallet. A payer failure can never break the payment: the deposit
23
+ target is valid regardless, and reporting a payer problem as a payment problem would send someone
24
+ hunting for a transfer that was never made.
25
+
26
+ **How long it will take.** `Credited after 3 confirmation(s)` became `— about 6 seconds on
27
+ base-sepolia`, computed from block time and labelled an estimate. A confirmation count alone gives no
28
+ way to tell a slow chain from a broken command, which is when people interrupt and send twice.
29
+
30
+ **One payload per reader.** `fund` printed the raw table *and* the readable instructions, the same
31
+ defect fixed in `balance`. It mattered more here once the table carried the payment URI: a person saw
32
+ the long link whether or not they asked, which buried the deposit address.
33
+
34
+ ### Changed — `certen quote` says how long the price is good for
35
+
36
+ `--id` reported a status and an expiry timestamp, leaving the reader to subtract against a clock to
37
+ answer the only question they had. Now `Valid for another 4m 12s`, with `seconds_remaining` on the
38
+ SDK response so a caller can branch without touching a date.
39
+
40
+ ### Changed — `certen balance` answers the question instead of listing the figures
41
+
42
+ Three things, all on the one command someone runs to find out whether they can keep working.
43
+
44
+ **It printed everything twice.** The raw key/value table rendered alongside the readable summary, so
45
+ every figure appeared once as `available_usd -72.355716` and again as `Available -$72.35` — with
46
+ `credit`, a nested object, showing as a line of raw JSON in between. The useful rendering came
47
+ second, under eleven lines of noise. Machine consumers now get the payload and a person gets the
48
+ summary, which is what every other command in this group already did.
49
+
50
+ **A negative balance is a drawdown, not a fault.** `Available -$72.35` was the first line a credit
51
+ account saw, with nothing to say that drawing on a credit line is how the account is meant to work.
52
+ It now reads `Drawn on credit $72.36`.
53
+
54
+ **The thresholds are stated as a distance.** `Warning at $125.00 drawn · service stops at $250.00`
55
+ published the limits without the number they are measured against, so the reader had to find
56
+ `available_usd`, negate it, and compare by hand — to answer whether their service is about to stop.
57
+ It now reads `Drawn $72.36 of $250.00 (first warning at $125.00)`, and past the warning threshold it
58
+ leads with the headroom and the command to fix it.
59
+
60
+ ### Fixed — money rendered two different ways
61
+
62
+ There were two copies of the currency formatter. `billing.ts` handled a leading minus and
63
+ `whoami.ts` did not, so a drawn-down account rendered `-$72.35` in one command and the malformed
64
+ `$-72.35` in the other. Both are replaced by one exported helper, which also rounds the cents
65
+ half-up rather than truncating — the old one displayed `-72.355716` as `-$72.35`, a cent kinder to
66
+ the account than the truth.
67
+
68
+ ### Added — `certen signup --with-key`
69
+
70
+ Onboarding with nobody in the loop at all.
71
+
72
+ ```
73
+ certen keys create dev # a keypair, never leaves this machine
74
+ certen signup --with-key dev # an organization, in one step
75
+ certen init --key dev # identity, chains, verified
76
+ ```
77
+
78
+ No browser, no email, no waiting for anyone to approve anything. The CLI signs a server-issued nonce
79
+ with a key it already has; CERTEN sees a public key and a signature and never the private half —
80
+ which is what non-custodial should mean at signup, not only afterwards.
81
+
82
+ ### Added — `certen orgs` and `certen signup --token`
83
+
84
+ Onboarding an organization without a browser.
85
+
86
+ ```
87
+ certen orgs invite --name "Acme" --expires 7d # a human, once, in advance
88
+ certen signup --token crt_... # the machine, later, alone
89
+ ```
90
+
91
+ `certen signup` previously printed a code and waited — indefinitely — for somebody to open a
92
+ browser and approve it. That is right for a person at a terminal and a wall for CI, for a platform
93
+ provisioning its customers, and for an agent starting up. `--token` is the same command with the
94
+ human moved earlier in time.
95
+
96
+ `certen orgs list` shows what each token became, which is the link between a decision someone made
97
+ and an organization now on their bill. `certen orgs revoke` stops an unredeemed one; a token that
98
+ has already been redeemed cannot be revoked, and says so rather than implying the organization can
99
+ be un-created.
100
+
101
+ ### Added — `certen identity mnemonic`
102
+
103
+ Collect the mnemonic from a `signing_mode: "provider"` identity. There was no way to do this from
104
+ the CLI at all, and the retrieval token is consumed on first read and expires in about ten minutes —
105
+ so the seed was lost by default unless someone hand-rolled the request.
106
+
107
+ **Writes to a file, mode 0600, rather than printing.** Stdout is scrollback, CI logs, and whatever
108
+ is recording the session; a seed phrase outlives its usefulness there by years. `--print` is there
109
+ for anyone who genuinely wants it on screen, and `--json` gives the value to a script that asked.
110
+
111
+ Takes the `mnemonic_retrieval.url` straight from the create response, or an id and token separately.
112
+ An unparseable target fails before any request is issued, because a half-parsed target would spend a
113
+ one-shot token for nothing.
114
+
115
+ ### Added — `certen oauth-clients`
116
+
117
+ `list`, `create`, `rotate-secret`, `remove`. A deployment could authenticate with client credentials
118
+ and could not create the client those credentials belong to, which is where "automated" stopped
119
+ being true.
120
+
121
+ `rotate-secret --grace <seconds>` is the one worth knowing: the previous secret keeps working while
122
+ the fleet picks up the new one, so changing a credential is not an outage. `remove` is the opposite
123
+ and says so — it revokes every live token immediately.
124
+
125
+ ### Added — `certen errors`
126
+
127
+ `certen errors` lists every code the gateway you are talking to can return; `certen errors <CODE>`
128
+ explains one. Needs no API key. This is the command to run when a code shows up in a log and the
129
+ question is whether to retry, pay, or wake someone — `retryable` answers "can this exact request
130
+ ever work", and a `platform` audience says plainly that there is nothing on your side to change.
131
+
132
+ ### Added — `certen quote --id`
133
+
134
+ Read back a quote you already hold instead of guessing whether it is still good. Same command as
135
+ issuing one, because it is the same question; it answers "still usable" outright rather than leaving
136
+ a status string and a timestamp to compare by hand.
137
+
138
+ ### Added — `certen auth revoke-token`
139
+
140
+ Revoke a leaked OAuth2 token from the terminal. **Works with no API key configured** — the gateway
141
+ authenticates the request with the token itself, and requiring the credential you are trying to
142
+ contain would be backwards.
143
+
144
+ Reads from stdin or a prompt when no argument is given, so a live token does not land in shell
145
+ history or a process listing. `--refresh` marks it as a refresh token, whose revocation also kills
146
+ every access token descended from it.
147
+
148
+ ### Fixed — the CLI test suite no longer flakes (properly, this time)
149
+
150
+ Every test in that package spawns the CLI as a real subprocess, against vitest's 5s default — a
151
+ budget sized for in-process tests. The slowest case measured **4353ms**, about 13% headroom, so
152
+ ordinary scheduling load tipped a passing test over; `doctor.test.ts` failed in a full run and
153
+ passed alone. Raised to 20s, roughly 4.5x the measured worst case.
154
+
155
+ **That fix was inert for a whole phase.** It was written into
156
+ `packages/cli/vitest.config.ts`, and a per-package config only applies when vitest is invoked with
157
+ that package as its root — while `scripts/test-all.mjs` runs a single process from the repository
158
+ root. Its own comment claimed "the root config already globs them"; no root config existed. So every
159
+ run kept using 5s, and the suite flaked twice in one session, both times at exactly 5000ms.
160
+
161
+ The timeout now lives in a root `vitest.config.ts`, which sets **only** the timeouts —
162
+ declaring `include` there would change discovery, and a glob subtly narrower than the default drops
163
+ test files with no failure anywhere. `packages/sdk/test/test-config.test.ts` asserts the root config
164
+ exists, that its timeout is a real number rather than a present key, that discovery is left alone,
165
+ and that all three packages are still found.
166
+
167
+ ### Added — `certen webhooks`
168
+
169
+ `list`, `add`, `remove`, `verify`, `rotate-secret`, `deliveries`, `redeliver`.
170
+
171
+ `certen webhooks deliveries` is the command that earns the group: it shows the status, HTTP code and
172
+ error for each attempt, and prints the exact `redeliver` command for anything that failed. Without
173
+ it a dropped delivery was indistinguishable from an event that never fired. `--failed` narrows to
174
+ just the ones that did not arrive.
175
+
176
+ Registering prints the signing secret with a plain warning that it is shown once — the only
177
+ recovery is rotating, which invalidates whatever the previous secret was already signing.
178
+
179
+ ### Changed — `certen receipts get <id>` replaces `certen receipt <id>`
180
+
181
+ Two top-level commands differing by one character, sitting adjacent in help. Now one group, matching
182
+ `tx` and `identity`: `certen receipts` lists, `certen receipts get <id>` fetches one. `certen verify`
183
+ stays top-level — it is the command that answers "can I prove this charge", and burying it would
184
+ hide it.
185
+
186
+ Both commands were added in this same release and neither has shipped, so nothing external breaks.
187
+
188
+ ### Changed — commands fetch only the identity data they read
189
+
190
+ `certen call`, `certen init` and `certen identity retire` all fetched an identity with every
191
+ enrichment: on-chain governance, per-chain balances, and pending counts. `call` reads the balances
192
+ (for the unfunded-account guard) and never touches governance or pending; the other two need none of
193
+ it. Each unused enrichment is a live query — governance is a network round trip, balances runs once
194
+ per linked chain — and they sat on the critical path of the flagship command.
195
+
196
+ ### Added — `scripts/measure-onboarding.mjs`
197
+
198
+ Records round trips, wall-clock and endpoints touched for each step of the first-run journey, by
199
+ running the real CLI through a counting proxy. Onboarding had never been measured end to end, so
200
+ there was no way to tell whether any of this work reduced friction or moved it.
201
+
202
+ First run: 12 requests, 10.7s. After bounding the gateway health probe: **12 requests, 4.9s** — both
203
+ measured against a local gateway with every downstream absent, so they are a floor for the read path
204
+ and nothing more.
205
+
206
+ Against production (`https://gateway.kompendium.co`, 2026-08-16): **14 requests, 5.5s**. Two of the
207
+ eight steps exit non-zero there — `certen pricing` and `certen scopes` — not because of anything in
208
+ the CLI, but because `GET /v1/pricing` and `GET /v1/scopes` are not deployed yet. The measurement
209
+ found the same gap `npm run check:gateway` reports, from the other direction.
210
+
211
+ ### Changed — `certen whoami` reports your organization and scopes instead of guessing
212
+
213
+ It printed `organization: "not exposed to API keys — see the portal"`, and reported permissions as
214
+ `scopes_observed` — a guess assembled from which probe calls happened to return 200 rather than 403,
215
+ which can only ever describe the scopes it thought to test for.
216
+
217
+ It now reads `GET /v1/me`: the real organization name, the granted scopes, the key id and its rate
218
+ limit. Still two requests, but the third call to `/v1/admin/usage` is gone — it existed only to
219
+ infer whether the key held `admin:read` — so `whoami` no longer needs that scope at all.
220
+
221
+ **Breaking for `--json` consumers:** `scopes_observed` (an object of booleans) is replaced by
222
+ `scopes` (an array of granted scope names), and `organization` is now `{ id, name }` rather than an
223
+ explanatory sentence.
224
+
225
+ ### Added — `certen proof open <link>`
226
+
227
+ Read a proof someone shared with you. **Runs with no API key configured** — the recipient of a
228
+ share link has no CERTEN account, and every other share command (`share`, `shares`, `shares revoke`)
229
+ serves the sender.
230
+
231
+ ```
232
+ certen proof open https://gateway.kompendium.co/v1/proof/shared/<token>
233
+ certen proof open <link> --out proof.json
234
+ ```
235
+
236
+ An expired or revoked link exits non-zero saying to ask for a fresh one, rather than reporting that
237
+ the proof does not exist.
238
+
239
+ ### Added — `certen verify <receipt-id>`
240
+
241
+ Confirm a charge instead of being told it is fine.
242
+
243
+ ```
244
+ PASS digest sha256(canonical_json(body)) matches the stated digest.
245
+ PASS signature ed25519 signature verifies against published key bd4a7a92f29958b9.
246
+ PASS inclusion This receipt is leaf 1254 of 1269.
247
+ PASS root Audit path folds to the root of the independently fetched signed head at 1269.
248
+ SKIP anchor No anchored tree head covers this receipt yet.
249
+ ```
250
+
251
+ The receipt already carried a `verification` block; it is CERTEN checking CERTEN. This recomputes
252
+ everything from published data and compares the folded root against a tree head fetched separately.
253
+
254
+ **It exits non-zero when it did not fully verify — including when checks were merely SKIPPED.** An
255
+ incomplete run is not a pass, and exiting 0 would let a script report an unverified receipt as
256
+ verified. The report survives the failure under `error.details`, matching `certen doctor`.
257
+
258
+ ### Added — `certen ledger`, `certen receipts`
259
+
260
+ Where the money went, and proof of what you were charged — neither was reachable from the terminal.
261
+
262
+ ```
263
+ certen ledger --all # every balance change, paged for you
264
+ certen receipts # NUMBER, WHEN, TYPE, AMOUNT, EVIDENCE
265
+ certen receipts get <id> --proof # signature, and the inclusion proof
266
+ ```
267
+
268
+ `EVIDENCE` shows `signed + logged`, because `logged` is what decides whether an inclusion proof
269
+ exists — without it you ask for one, get a 404, and cannot tell "not yet" from "wrong id".
270
+
271
+ The anchor line reads `covering_head`, not `head`: a receipt whose own tree head is unanchored is
272
+ still anchored by any later root that commits to it, and reporting `head` would call a perfectly
273
+ good receipt unproven for every gap between anchors. An unattested anchor time is labelled a loose
274
+ upper bound rather than presented as the block time.
275
+
276
+ `--all` pages for you and refuses `--offset` alongside it; a non-numeric `--limit` and
277
+ `--tree-size` without `--proof` are both rejected before any network call.
278
+
279
+ ### Added — `certen payers` and `certen payers add`
280
+
281
+ Register a wallet you send from, so deposits credit on sight instead of needing a one-time payment
282
+ opened for the exact amount before every send. This is what a 402 already told you to do, at an
283
+ endpoint no command could reach.
284
+
285
+ ```
286
+ certen payers add 0xAbC… --chain base-sepolia --label treasury
287
+ certen payers # what is registered
288
+ ```
289
+
290
+ A malformed address is rejected before the network call, an empty list says so plainly rather than
291
+ looking like success, and a 409 (the address belongs to another organization on that chain) exits
292
+ non-zero — a funding script must not read it as "attribution is set up".
293
+
294
+ ### Changed — `certen balance` makes one request instead of two
295
+
296
+ It fetched the balance and `/v1/billing/obligations` concurrently, because the balance alone could
297
+ not say how much was actually left to commit. The gateway now reports that with the balance, so the
298
+ second request is gone. Against an older gateway the command still falls back to it — printing
299
+ `spendable_usd` in the "Left to commit" slot would report committed money as available.
300
+
301
+ ### Added — `certen pricing`, and `--sku` on `certen quote`
302
+
303
+ There was no way to ask the CLI what anything costs. `certen quote` prices one operation and takes
304
+ its sku from a vocabulary nothing published — and it had no `--sku` flag at all, so it could only
305
+ ever price the default. Asking "what does CERTEN cost" meant reading the gateway's refusals.
306
+
307
+ ```
308
+ $ certen pricing --chain base-sepolia
309
+
310
+ SKU CHAIN PRICE
311
+ identity.provision * $5.00
312
+ proof.execute base-sepolia $0.35 + gas
313
+ ```
314
+
315
+ `+ gas` marks prices that are completed at execution, so a floor is not read as a total. `*` is the
316
+ entry that applies to any chain without one of its own, and is kept when filtering by chain —
317
+ dropping it would report identity provisioning as unpriced on a chain where it costs $5.
318
+
319
+ `certen quote --sku <sku>` now prices any of them, and exits non-zero when pricing is not
320
+ configured rather than reporting an empty catalogue.
321
+
322
+ ### Changed — `certen call` no longer reads the same balances twice
323
+
324
+ It fetched the identity (it needs `can_sign` before prompting for a passphrase), then fetched
325
+ `/v1/portfolio` for balances the identity response had already returned — a round trip on the
326
+ critical path of the main flow, for numbers it was holding.
327
+
328
+ The guard now takes those balances when the caller has them, and still reads the portfolio when the
329
+ gateway sends none, so an older gateway does not silently lose the guard.
330
+
331
+ ### Added — `certen tx list --all`
332
+
333
+ Fetches every page instead of the first. Answering "how many intents failed this month" previously
334
+ meant a shell loop incrementing `--offset` and knowing when to stop; getting that wrong reads as
335
+ "there were none".
336
+
337
+ With `--all`, `--limit` is the page size rather than a cap, and `--offset` is rejected — `--all`
338
+ starts from the beginning, so the two together have no coherent meaning.
339
+
340
+ ### Breaking — `certen identity` JSON output is no longer wrapped
341
+
342
+ `certen --json identity get <id>` and `certen --json identity create` returned the identity nested
343
+ under an `identity` key. It is now at the top level, matching every other command and the API.
344
+
345
+ ```bash
346
+ # before
347
+ certen --json identity get "$ID" | jq -r '.data.identity.can_sign'
348
+ # after
349
+ certen --json identity get "$ID" | jq -r '.data.can_sign'
350
+ ```
351
+
352
+ The envelope itself (`{ ok, data }` / `{ ok, error }`) and the exit codes are unchanged, as is table
353
+ output, which was never a contract. See `docs/CLI-CONTRACT.md`.
354
+
355
+ ### Fixed — `certen tx status --json` reports absent fields as `null`
356
+
357
+ `proof_id`, `proof_bundle_url`, `accum_tx_hash` and `error_message` came back as `""` when the
358
+ gateway had no value, so `.proof_id != null` was true for a transaction with no proof. They are now
359
+ `null`. A script testing `if .proof_id then` is unaffected; one testing `!= null` was wrong before
360
+ and is right now.
361
+
362
+ ### Fixed — `can_sign` distinguishes "cannot sign" from "could not check"
363
+
364
+ An unreadable key page reported `can_sign: false`. It now reports `null`, and the table prints
365
+ `unknown`. The two have different fixes: one is repairable with `certen identity update
366
+ --public-key`, the other is a retry.
367
+
368
+ ## 0.6.0 — from eighteen steps to four
369
+
370
+ ### Added — `certen login` / `certen signup`
371
+
372
+ The device authorization grant. The CLI prints a short code, you approve it in a portal session you
373
+ already trust, and the key arrives over the CLI's own channel. **The secret is never displayed and
374
+ never passes through a clipboard or shell history.** Requires a gateway that serves
375
+ `/v1/portal/device`; against an older one it says so and points at the portal.
376
+
377
+ ### Added — `certen init`, `call`, `proof`, `chains`, `whoami`, `doctor`, `identity retire`
378
+
379
+ `init` creates only what is missing, waits until the identity can actually sign, and records the id
380
+ so a later run reuses it rather than burning org quota. `call` is a proof-gated contract call in one
381
+ command — it derives the ADI URL, the abstract account and the numeric chain id from the identity,
382
+ and type-checks `--arg` against the Solidity signature before anything is sent. `proof` retrieves,
383
+ bundles, shares and verifies. `doctor` names the one thing blocking you and the command that fixes
384
+ it.
385
+
386
+ ### Behavioural change — usage errors now exit 2
387
+
388
+ A wrong invocation used to exit 1, indistinguishable from a rejected request. Several commands threw
389
+ untyped errors; they now exit 2 as the contract always specified. Scripts branching on any non-zero
390
+ exit are unaffected; scripts that treated 1 as "the gateway said no" should re-check.
391
+
392
+ ### Behavioural change — `auth login` verifies the key before saving it
393
+
394
+ A typo'd or revoked key used to be written and then surface as an opaque 401 at whatever command ran
395
+ next. It is now checked first and **not saved if rejected**. A 403 means the key is real but
396
+ unscoped and is accepted with a note. `--api-key -` reads from stdin; omitting it prompts.
397
+
398
+ ### Behavioural change — human mode waits by default
399
+
400
+ `identity create` and `tx create` poll to a usable state. `--json` keeps the old fire-and-forget
401
+ default so existing scripts do not silently start blocking.
402
+
403
+ ### Added — the unfunded-account guard, and `error.details`
404
+
405
+ A value transfer from an empty abstract account is refused before submitting, naming the faucet;
406
+ `--force` overrides. And a failure that still produced a result carries it under `error.details` —
407
+ `certen --json doctor` returns every check that way, so signalling the failure never costs you the
408
+ diagnosis. See docs/CLI-CONTRACT.md.
409
+
410
+ ## 0.5.0 — money commands, and a refusal that tells you how to fix it
411
+
412
+ ### Added — `certen balance` and `certen fund`
413
+
414
+ `balance` prints available, held, credit line, spendable, and **left to commit** — spendable minus
415
+ what pending intents have already claimed. Showing only the balance would tell you that you can
416
+ afford work that is already spoken for.
417
+
418
+ `fund <amount> --chain <chain>` prints where to send stablecoin and waits until it is credited.
419
+ It never touches a wallet or a key: signing and sending stay with you. `--no-wait` prints the
420
+ details and exits; `--poll-interval` and `--timeout` control the wait. An uncredited or expired
421
+ payment exits non-zero, because a funding script must not read one as paid.
422
+
423
+ Every option is validated before the network call — a typo in `--timeout` used to open a real
424
+ payment intent first.
425
+
426
+ ### Changed — a 402 now prints the way out
427
+
428
+ A refusal for lack of funds shows the shortfall, the address, the exact amount, the reference, both
429
+ `certen fund …` and the portal link, and the quote id to retry with — then states plainly that
430
+ nothing was charged and no work was started. All on stderr; stdout is untouched.
431
+
432
+ In `--json`, the failure envelope gains `shortfall_usd`, `quote_id` and `resolve` on payment
433
+ failures only. They are absent on every other error rather than present as nulls. See
434
+ docs/CLI-CONTRACT.md.
435
+
436
+ ### Fixed — the error reporter no longer flattens SDK errors
437
+
438
+ `handleError` copied a `CertenError` into an object literal before reporting it. The values
439
+ survived; the class identity did not — so a payment refusal could not be recognised and its payment
440
+ target was silently dropped. The fields it copied are all readable on the instance, `isRetryable`
441
+ getter included, so nothing was gained by the copy.
442
+
3
443
  ## 0.4.0 — `--json` is a machine contract
4
444
 
5
445
  Adds a stable, tested output contract for scripts and AI agents. **Human output is unchanged**: if