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