@allscale/cli 0.1.0 → 1.0.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.
Files changed (39) hide show
  1. package/README.md +144 -45
  2. package/bin/dev.js +11 -1
  3. package/bin/run-oclif.js +216 -27
  4. package/bin/run.js +19 -1
  5. package/dist/commands/build-info.js +13 -13
  6. package/dist/commands/claim-link/claim.js +14 -14
  7. package/dist/commands/claim-link/create.js +40 -37
  8. package/dist/commands/claim-link/get.js +26 -15
  9. package/dist/commands/claim-link/list.js +15 -15
  10. package/dist/commands/claim-link/status.js +14 -14
  11. package/dist/commands/describe.js +13 -13
  12. package/dist/commands/device-login.js +29 -29
  13. package/dist/commands/invoice/get.js +15 -15
  14. package/dist/commands/invoice/list.js +16 -16
  15. package/dist/commands/invoice/pay.js +46 -43
  16. package/dist/commands/invoice/received.js +16 -16
  17. package/dist/commands/invoice/send.js +42 -34
  18. package/dist/commands/invoice/sent.js +15 -15
  19. package/dist/commands/invoice/update.js +17 -15
  20. package/dist/commands/logout.js +19 -13
  21. package/dist/commands/operations.js +13 -13
  22. package/dist/commands/otp-login.js +21 -20
  23. package/dist/commands/otp-send.js +13 -13
  24. package/dist/commands/payout/send.js +14 -14
  25. package/dist/commands/payout/status.js +15 -15
  26. package/dist/commands/scope.js +19 -18
  27. package/dist/commands/store/create.js +17 -17
  28. package/dist/commands/transaction/get.js +15 -15
  29. package/dist/commands/transaction/list.js +15 -15
  30. package/dist/commands/wallet/list.js +15 -15
  31. package/dist/commands/wallet/send.js +40 -43
  32. package/dist/commands/whoami.js +17 -15
  33. package/dist/hooks/version-suffix.js +1 -1
  34. package/dist/index.js +14 -14
  35. package/dist/lib/help.js +7 -2
  36. package/dist/lib/output/audit-lifecycle.js +7 -0
  37. package/oclif.manifest.json +140 -296
  38. package/package.json +4 -6
  39. package/dist/commands/claim-link/preview.js +0 -18
@@ -3,7 +3,7 @@
3
3
  "build-info": {
4
4
  "aliases": [],
5
5
  "args": {},
6
- "description": "Show which API base (if any) this build is bound to, and its signing-key id. Never prints the signing key itself — only its (non-secret, already-forgeable) id.",
6
+ "description": "Show which API base (if any) this build is bound to, and its signing-key id. Never prints the signing key itself — only its non-secret id.",
7
7
  "examples": [
8
8
  "$ allscale build-info"
9
9
  ],
@@ -18,6 +18,7 @@
18
18
  "profile": {
19
19
  "description": "Named profile from ~/.allscale/config.toml",
20
20
  "helpGroup": "GLOBAL",
21
+ "hidden": true,
21
22
  "name": "profile",
22
23
  "hasDynamicHelp": false,
23
24
  "multiple": false,
@@ -148,7 +149,7 @@
148
149
  "device-login": {
149
150
  "aliases": [],
150
151
  "args": {},
151
- "description": "Log in via browser-confirmed device pairing. Use an existing web session or sign in there with Passkey or Email (enter the Email OTP on the webpage), then approve the CLI. The backend mints a scoped agent key — the only credential this CLI issues; no web sign-in secret is entered in the terminal.",
152
+ "description": "Log in via browser-confirmed device pairing. Use an existing web session or sign in there with Passkey or Email (enter the Email OTP on the webpage), then approve the CLI. The backend mints a scoped agent key — the credential issued by the current CLI login flows; no web sign-in secret is entered in the terminal.",
152
153
  "examples": [
153
154
  "$ allscale device-login",
154
155
  "$ allscale device-login --device-label tom-mbp-2026",
@@ -203,7 +204,7 @@
203
204
  "type": "option"
204
205
  },
205
206
  "scopes": {
206
- "description": "Requested scopes for the eventual agent key, repeatable. Scope strings use `<category>:<tier>`; `:all` implies `:read_only` for the same category. Pairs the approval screen can grant: invoice:{read_only,all}, contact:{read_only,all}, transaction:read_only, wallet:read_only, claim_link:{read_only,all}, store:all. The final set is chosen — and can be overridden — on that screen, and is ENFORCED server-side on every request. The Wallets toggle is on by default and grants `wallet:read_only`, which `invoice pay` and `wallet list` both need.",
207
+ "description": "Requested scopes for the eventual agent key, repeatable. Scope strings use `<category>:<tier>`; `:all` implies `:read_only` for the same category. Pairs the approval screen can grant: invoice:{read_only,all}, contact:{read_only,all}, transaction:read_only, wallet:{read_only,all}, claim_link:{read_only,all}, store:{read_only,all}. The final set is chosen — and can be overridden — on that screen, and is checked by the backend on each request. The Wallets read toggle is on by default and grants `wallet:read_only`, which `wallet list` needs. `invoice pay` needs `invoice:all` AND `wallet:all` — its withdrawal registration rides the same broker op as `wallet send` (backend gates the op, not the verb), and the write tier covers its payer-address read via the implication — so request both (`--scopes invoice:all --scopes wallet:all`) and tick the Invoices write tier plus the Wallets Allow-changes tier on the screen (Invoices starts at Read; Allow-changes starts OFF). `wallet send` requires `wallet:all` the same way; every transfer still requires your in-browser confirmation. Values are checked locally first: an unrecognised `<category>:<tier>` string exits 2 before any pairing session is registered — the same check, and the same exit code, as `otp-login --scopes`. A recognised scope the screen has no toggle for is also rejected locally, with the remediation that actually works: otp-login for operational scopes (or `--allow-unrenderable-scopes` when your web app is newer than this CLI's catalog), nothing for payroll/admin:* — the approval page refuses the whole authorization when even one requested scope is unrenderable, so deferring the answer to the browser would strand you after a pairing session already exists. An explicitly requested `cli_op:*` is not an error: the backend appends it to every pairing-minted key, so it is omitted from the request with a note and the resulting key is identical. Only a scope the screen renders and the user declines is reported after the key is minted.",
207
208
  "name": "scopes",
208
209
  "hasDynamicHelp": false,
209
210
  "multiple": true,
@@ -215,6 +216,12 @@
215
216
  "allowNo": false,
216
217
  "type": "boolean"
217
218
  },
219
+ "allow-unrenderable-scopes": {
220
+ "description": "Forward a scope to the approval screen even when this CLI's catalog says the screen has no toggle for it. Escape hatch for a stale catalog: the screen-grantable list above is a MIRROR of the web app's approval screen and can lag a frontend release, and without this flag a lagging mirror makes such a scope impossible to request until a CLI update ships. The screen stays the authority — if it truly cannot render the scope it refuses the WHOLE authorization after the pairing session exists (the stranding the local check normally prevents), so use this only when you have reason to believe the web app is newer than this CLI. Scopes no login path can mint (payroll/admin:*) are still rejected locally; this flag does not touch them.",
221
+ "name": "allow-unrenderable-scopes",
222
+ "allowNo": false,
223
+ "type": "boolean"
224
+ },
218
225
  "yes": {
219
226
  "char": "y",
220
227
  "description": "Skip the interactive confirmation and start the pairing immediately. Only a BARE invocation is ever prompted, so passing any flag already skips it; use this to say so explicitly. Automation that allocates a pty for stdin/stdout/stderr and runs the command bare is indistinguishable from a human — that is the case this flag exists for.",
@@ -246,10 +253,10 @@
246
253
  "logout": {
247
254
  "aliases": [],
248
255
  "args": {},
249
- "description": "Clear cached credentials (session token + store keys) for a profile. By default, clears BOTH the OS keychain and plaintext file store. With --insecure-storage or ALLSCALE_INSECURE_STORAGE=1, clears only plaintext files and never probes the keychain; omit/unset that opt-in when a full-machine sweep is wanted. If the keychain is suppressed or unavailable, a bare logout fails closed after attempting the plaintext-file cleanup.",
256
+ "description": "Clear cached credentials (session token + store keys) for a profile. By default, clears BOTH the OS keychain and plaintext file store. With --insecure-storage or ALLSCALE_INSECURE_STORAGE=1, clears only plaintext files and never probes the keychain; omit/unset that opt-in when a full-machine sweep is wanted. If the keychain is suppressed or unavailable, a bare logout fails closed after attempting the plaintext-file cleanup. A bare interactive invocation asks for confirmation before anything is removed; any flag (or --yes) skips the prompt, and machine callers are never prompted.",
250
257
  "examples": [
251
- "$ allscale logout",
252
- "$ allscale logout --profile staging"
258
+ "$ allscale logout --profile staging",
259
+ "$ allscale logout --yes"
253
260
  ],
254
261
  "flags": {
255
262
  "json": {
@@ -291,6 +298,13 @@
291
298
  "name": "insecure-storage",
292
299
  "allowNo": false,
293
300
  "type": "boolean"
301
+ },
302
+ "yes": {
303
+ "char": "y",
304
+ "description": "Skip the interactive confirmation and clear the credentials immediately. Only a BARE invocation is ever prompted, so passing any flag already skips it; use this to say so explicitly. Automation that allocates a pty for stdin/stdout/stderr and runs the command bare is indistinguishable from a human — that is the case this flag exists for.",
305
+ "name": "yes",
306
+ "allowNo": false,
307
+ "type": "boolean"
294
308
  }
295
309
  },
296
310
  "hasDynamicHelp": false,
@@ -407,12 +421,12 @@
407
421
  "otp-login": {
408
422
  "aliases": [],
409
423
  "args": {},
410
- "description": "Terminal-only Email OTP login for headless / scripted shells. Works with any account email (external domains included; the account must already exist). Sends the code (unless --otp-id is supplied), reads it in the terminal, and mints a scope-ENFORCED agent API key — the only credential this CLI issues. Scopes are set with --scopes (required; no full-permission default exists).",
424
+ "description": "Terminal-only Email OTP login for headless / scripted shells. Works with any account email (external domains included; the account must already exist). Sends the code (unless --otp-id is supplied), reads it in the terminal, and mints a scoped agent API key whose granted scopes the backend checks on each request — the credential issued by the current CLI login flows. Scopes are set with --scopes (required; no full-permission default exists).",
411
425
  "examples": [
412
426
  "$ allscale otp-login --email me@example.com --scopes invoice:read_only # interactive: prompts on stdin",
413
427
  "$ printf '%s' \"$OTP\" | allscale otp-login --email me@example.com --scopes invoice:all --otp-stdin",
414
428
  "$ ALLSCALE_OTP=$(read-from-mailbox) allscale otp-login --email me@example.com --scopes claim_link:all",
415
- "$ allscale otp-login --email me@example.com --scopes invoice:read_only --otp-id <id> --otp <code>"
429
+ "$ allscale otp-login --email me@example.com --scopes invoice:read_only --otp-id otp-request-id --otp 123456"
416
430
  ],
417
431
  "flags": {
418
432
  "json": {
@@ -466,7 +480,7 @@
466
480
  "type": "option"
467
481
  },
468
482
  "scopes": {
469
- "description": "Scopes the minted agent key should carry, repeatable. Scope strings use `<category>:<tier>`; `:all` implies `:read_only` for the same category. Pairs that reach a real operation today: invoice:{read_only,all}, contact:{read_only,all}, claim_link:{read_only,all}, transaction:read_only, store:all, wallet:read_only. Other pairs are accepted by the catalog but grant nothing, and this command does NOT warn when a requested scope is clipped — verify with `allscale scope` after minting. Required — the backend deliberately has no full-permission default. Requested scopes are clipped to your account's role; keys are ENFORCED server-side on every request.",
483
+ "description": "Scopes the minted agent key should carry, repeatable. Scope strings use `<category>:<tier>`; `:all` implies `:read_only` for the same category. Pairs that reach a real operation today: invoice:{read_only,all}, contact:{read_only,all}, claim_link:{read_only,all}, transaction:{read_only,all}, store:{read_only,all}, wallet:{read_only,all}. transaction:all — read-only in practice: no write operation is wired there today, so the `:read_only` tier grants exactly as much; prefer it unless you have a reason to hold the wider scope. `wallet:all` additionally lets the key register `wallet send` transfers; every transfer still requires your in-browser confirmation. Other pairs are accepted by the catalog but grant nothing, and this command does NOT warn when a requested scope is clipped — verify with `allscale scope` after minting. Values are checked locally first: an unrecognised `<category>:<tier>` string exits 2 before any OTP is sent — the same check, and the same exit code, as `device-login --scopes`. Required — the backend deliberately has no full-permission default. Requested scopes are clipped to your account's role; granted scopes are checked by the backend on each request.",
470
484
  "helpGroup": "REQUIRED",
471
485
  "name": "scopes",
472
486
  "required": true,
@@ -624,7 +638,7 @@
624
638
  "scope": {
625
639
  "aliases": [],
626
640
  "args": {},
627
- "description": "Show the capabilities granted to the current CLI session (chosen on the browser approval screen at `device-login`, or via --scopes on `otp-login`). Scopes are ENFORCED server-side on every request. Read from the cached token bundle; no network call.",
641
+ "description": "Show the capabilities granted to the current CLI session (chosen on the browser approval screen at `device-login`, or via --scopes on `otp-login`). Scopes are checked by the backend on each request. Scope controls reduce access but do not prevent credential compromise, misuse within a granted scope, configuration errors, or unauthorized actions by an otherwise authenticated caller. Read from the cached token bundle; no network call.",
628
642
  "examples": [
629
643
  "$ allscale scope",
630
644
  "$ allscale scope --profile prod"
@@ -646,7 +660,7 @@
646
660
  "type": "option"
647
661
  },
648
662
  "token": {
649
- "description": "Override the access token for this invocation (precedence: --token > ALLSCALE_TOKEN > --profile)",
663
+ "description": "Not supported by this command. `scope` reads scopes recorded with a stored profile, so an arbitrary token has no authoritative scope source and is rejected locally. Use --profile to select the stored credential whose scopes should be shown.",
650
664
  "helpGroup": "GLOBAL",
651
665
  "hidden": true,
652
666
  "name": "token",
@@ -694,7 +708,7 @@
694
708
  "whoami": {
695
709
  "aliases": [],
696
710
  "args": {},
697
- "description": "Show the current authenticated identity from the stored credential (agent-key sessions report business_id / scopes / expiry; legacy password sessions decode the cached JWT). No network call.",
711
+ "description": "Show the current authenticated identity from the stored credential (agent-key sessions report business_id / scopes / expiry; legacy password sessions decode the cached JWT). No network call. The reported `api_base` is the endpoint this session resolves to — its saved profile pin, else this build's own environment. A published build ignores ALLSCALE_API_BASE, and says so on stderr rather than echoing it as the session endpoint; an internal build honours the variable, so it is echoed there. Either way, a resolved endpoint that networked commands would refuse — an untrusted origin, or a real AllScale environment outside this build's signing binding, whether it came from the variable or from the profile's saved pin — is disclosed on stderr. This command stays usable offline and never fails closed.",
698
712
  "examples": [
699
713
  "$ allscale whoami",
700
714
  "$ allscale whoami --profile prod"
@@ -764,10 +778,10 @@
764
778
  "claim-link:claim": {
765
779
  "aliases": [],
766
780
  "args": {},
767
- "description": "Claim a Claim Link. Anonymous claims require --to; authenticated business receivers can use --to-wallet. Only an on-chain-proven claimed outcome exits 0.",
781
+ "description": "Claim a Claim Link. Path-A claims (no AllScale account sign-in) require --to; authenticated business receivers can use --to-wallet. Only an on-chain-proven claimed outcome exits 0.",
768
782
  "examples": [
769
- "$ allscale claim-link claim --claim-token <token> --to 0xabc...",
770
- "$ allscale claim-link claim --claim-url https://app.allscale.io/claim/<token> --to-wallet"
783
+ "$ allscale claim-link claim --claim-token claim-token-from-share-link --to 0x1234567890abcdef1234567890abcdef12345678",
784
+ "$ allscale claim-link claim --claim-url https://app.allscale.io/claim/claim-token-from-share-link --to-wallet"
771
785
  ],
772
786
  "flags": {
773
787
  "json": {
@@ -812,7 +826,7 @@
812
826
  },
813
827
  "claim-token": {
814
828
  "description": "Raw bearer claim token. Secret: prefer env/history-safe invocation.",
815
- "helpGroup": "REQUIRED (ONE OF)",
829
+ "helpGroup": "REQUIRED SOURCE (ONE OF)",
816
830
  "name": "claim-token",
817
831
  "hasDynamicHelp": false,
818
832
  "multiple": false,
@@ -820,18 +834,18 @@
820
834
  },
821
835
  "claim-url": {
822
836
  "description": "Canonical claim URL, or a same-origin /s/ short URL resolved with one unauthenticated 302.",
823
- "helpGroup": "REQUIRED (ONE OF)",
837
+ "helpGroup": "REQUIRED SOURCE (ONE OF)",
824
838
  "name": "claim-url",
825
839
  "hasDynamicHelp": false,
826
840
  "multiple": false,
827
841
  "type": "option"
828
842
  },
829
843
  "to": {
830
- "description": "Receiver EVM address for anonymous Path-A claims.",
844
+ "description": "Receiver EVM address for Path-A claims (no AllScale account sign-in). This does not make the transaction anonymous: network, request, compliance, and public blockchain data may still be processed or observable.",
831
845
  "exclusive": [
832
846
  "to-wallet"
833
847
  ],
834
- "helpGroup": "REQUIRED (ONE OF)",
848
+ "helpGroup": "REQUIRED DESTINATION (ONE OF)",
835
849
  "name": "to",
836
850
  "hasDynamicHelp": false,
837
851
  "multiple": false,
@@ -842,7 +856,7 @@
842
856
  "exclusive": [
843
857
  "to"
844
858
  ],
845
- "helpGroup": "REQUIRED (ONE OF)",
859
+ "helpGroup": "REQUIRED DESTINATION (ONE OF)",
846
860
  "name": "to-wallet",
847
861
  "allowNo": false,
848
862
  "type": "boolean"
@@ -855,7 +869,7 @@
855
869
  "pluginName": "@allscale/cli",
856
870
  "pluginType": "core",
857
871
  "strict": true,
858
- "usage": "claim-link claim (--claim-token <value> | --claim-url <value>) [--json] [--profile <value>] [--insecure-storage] [--to <value> | --to-wallet]",
872
+ "usage": "claim-link claim (--claim-token <value> | --claim-url <value>) [--json] [--profile <value>] [--insecure-storage] (--to <value> | --to-wallet)",
859
873
  "enableJsonFlag": true,
860
874
  "legacyStagingNoticeShown": false,
861
875
  "emitsOwnOutput": false,
@@ -997,7 +1011,7 @@
997
1011
  "type": "option"
998
1012
  },
999
1013
  "bridge-timeout": {
1000
- "description": "Shorten the browser confirmation deadline, in milliseconds. By default the CLI waits for the broker op TTL.",
1014
+ "description": "Shorten the browser confirmation deadline, in milliseconds. By default the CLI waits the broker op's remaining life. It can only shorten the wait — a value above that is clamped to it, because the confirmation link cannot outlive its broker op. If the server's response carries no timestamp to measure that life against, the CLI says so on stderr and bounds the wait instead: with no --bridge-timeout this receipt-aware flow waits up to 240s and may be shortened by the best-effort bound from the stated expiry; an explicit value is honoured up to that same bound (clamped to 60s-15min), or 15min when the expiry itself is unreadable.",
1001
1015
  "name": "bridge-timeout",
1002
1016
  "hasDynamicHelp": false,
1003
1017
  "multiple": false,
@@ -1042,15 +1056,15 @@
1042
1056
  "aliases": [],
1043
1057
  "args": {
1044
1058
  "id": {
1045
- "description": "Claim Link id_str.",
1059
+ "description": "Claim Link id.",
1046
1060
  "name": "id",
1047
1061
  "required": true
1048
1062
  }
1049
1063
  },
1050
- "description": "Fetch one of your own Claim Links by id. Unknown or non-owned ids are indistinguishable on the backend.",
1064
+ "description": "Fetch one of your own Claim Links by id. Unknown or non-owned ids are indistinguishable on the backend. Requires claim_link:read_only for an agent-key session.",
1051
1065
  "examples": [
1052
- "$ allscale claim-link get 65ab1234567890abcdef0123",
1053
- "$ allscale claim-link get 65ab... --select 'id status amount token_symbol chain'"
1066
+ "Fetch the first Claim Link you own (requires jq and at least one existing link):\n$ CLAIM_LINK_ID=\"$(\n allscale claim-link list --input '{\"limit\":1}' |\n jq -er '.data.items[0].id'\n )\" &&\n allscale claim-link get \"$CLAIM_LINK_ID\"",
1067
+ "Fetch selected fields from the first Claim Link you own (requires jq and at least one existing link):\n$ CLAIM_LINK_ID=\"$(\n allscale claim-link list --input '{\"limit\":1}' |\n jq -er '.data.items[0].id'\n )\" &&\n allscale claim-link get \"$CLAIM_LINK_ID\" \\\n --select 'id status amount token_symbol chain'"
1054
1068
  ],
1055
1069
  "flags": {
1056
1070
  "json": {
@@ -1132,7 +1146,7 @@
1132
1146
  "claim-link:list": {
1133
1147
  "aliases": [],
1134
1148
  "args": {},
1135
- "description": "List your own Claim Links newest first. Defaults to the first 50 rows; pass --input or --all to override.",
1149
+ "description": "List your own Claim Links newest first. Defaults to the first 50 rows; pass --input or --all to override. Requires claim_link:read_only for an agent-key session.",
1136
1150
  "examples": [
1137
1151
  "$ allscale claim-link list",
1138
1152
  "$ allscale claim-link list --status link_sent --input '{\"limit\":20}'",
@@ -1180,7 +1194,7 @@
1180
1194
  "type": "boolean"
1181
1195
  },
1182
1196
  "status": {
1183
- "description": "Optional ClaimLinkStatus filter. Accepts 1..6 or pending_deposit/link_sent/claiming/claimed/expired/cancelled.",
1197
+ "description": "Optional ClaimLinkStatus filter. Accepts an integer from 1 through 6 or pending_deposit/link_sent/claiming/claimed/expired/cancelled.",
1184
1198
  "name": "status",
1185
1199
  "hasDynamicHelp": false,
1186
1200
  "multiple": false,
@@ -1242,136 +1256,13 @@
1242
1256
  "list.js"
1243
1257
  ]
1244
1258
  },
1245
- "claim-link:preview": {
1246
- "aliases": [],
1247
- "args": {},
1248
- "description": "Preview Claim Link fees without creating a link. Mirrors create_claim_link validation.",
1249
- "examples": [
1250
- "$ allscale claim-link preview --amount 10 --chain base --per-transfer-gas 0.02"
1251
- ],
1252
- "flags": {
1253
- "json": {
1254
- "description": "Format output as json.",
1255
- "helpGroup": "GLOBAL",
1256
- "name": "json",
1257
- "allowNo": false,
1258
- "type": "boolean"
1259
- },
1260
- "profile": {
1261
- "description": "Named profile from ~/.allscale/config.toml",
1262
- "helpGroup": "GLOBAL",
1263
- "name": "profile",
1264
- "hasDynamicHelp": false,
1265
- "multiple": false,
1266
- "type": "option"
1267
- },
1268
- "token": {
1269
- "description": "Override the access token for this invocation (precedence: --token > ALLSCALE_TOKEN > --profile)",
1270
- "helpGroup": "GLOBAL",
1271
- "hidden": true,
1272
- "name": "token",
1273
- "hasDynamicHelp": false,
1274
- "multiple": false,
1275
- "type": "option"
1276
- },
1277
- "api-base": {
1278
- "description": "Override the API base URL (public builds accept only https://app.allscale.io)",
1279
- "helpGroup": "GLOBAL",
1280
- "hidden": true,
1281
- "name": "api-base",
1282
- "hasDynamicHelp": false,
1283
- "multiple": false,
1284
- "type": "option"
1285
- },
1286
- "insecure-storage": {
1287
- "description": "Force POSIX plaintext token storage at ~/.allscale/credentials.json (mode 0600) instead of the OS keychain — for headless / CI / agent use. Unavailable on Windows because Node cannot verify owner-only ACL/link safety. Also via ALLSCALE_INSECURE_STORAGE=1.",
1288
- "helpGroup": "GLOBAL",
1289
- "name": "insecure-storage",
1290
- "allowNo": false,
1291
- "type": "boolean"
1292
- },
1293
- "amount": {
1294
- "description": "Receiver amount as a decimal string in token units; at most 6 effective decimals, or 18 on BSC.",
1295
- "helpGroup": "REQUIRED",
1296
- "name": "amount",
1297
- "required": true,
1298
- "hasDynamicHelp": false,
1299
- "multiple": false,
1300
- "type": "option"
1301
- },
1302
- "chain": {
1303
- "description": "EVM chain for the claim link.",
1304
- "helpGroup": "REQUIRED",
1305
- "name": "chain",
1306
- "required": true,
1307
- "hasDynamicHelp": false,
1308
- "multiple": false,
1309
- "options": [
1310
- "ethereum",
1311
- "bsc",
1312
- "base",
1313
- "polygon",
1314
- "arbitrum",
1315
- "optimism"
1316
- ],
1317
- "type": "option"
1318
- },
1319
- "per-transfer-gas": {
1320
- "description": "One ERC-20 transfer's gas estimate in token units.",
1321
- "helpGroup": "REQUIRED",
1322
- "name": "per-transfer-gas",
1323
- "required": true,
1324
- "hasDynamicHelp": false,
1325
- "multiple": false,
1326
- "type": "option"
1327
- },
1328
- "stable-coin": {
1329
- "description": "Stablecoin for the link.",
1330
- "name": "stable-coin",
1331
- "default": "USDT",
1332
- "hasDynamicHelp": false,
1333
- "multiple": false,
1334
- "options": [
1335
- "USDT",
1336
- "USDC"
1337
- ],
1338
- "type": "option"
1339
- },
1340
- "show-doc": {
1341
- "description": "Print the GraphQL document to stderr.",
1342
- "name": "show-doc",
1343
- "allowNo": false,
1344
- "type": "boolean"
1345
- }
1346
- },
1347
- "hasDynamicHelp": false,
1348
- "hiddenAliases": [],
1349
- "id": "claim-link:preview",
1350
- "pluginAlias": "@allscale/cli",
1351
- "pluginName": "@allscale/cli",
1352
- "pluginType": "core",
1353
- "strict": true,
1354
- "enableJsonFlag": true,
1355
- "legacyStagingNoticeShown": false,
1356
- "emitsOwnOutput": false,
1357
- "requiresEscapeHatch": false,
1358
- "requiresInput": false,
1359
- "isLocalOnly": false,
1360
- "isESM": false,
1361
- "relativePath": [
1362
- "dist",
1363
- "commands",
1364
- "claim-link",
1365
- "preview.js"
1366
- ]
1367
- },
1368
1259
  "claim-link:status": {
1369
1260
  "aliases": [],
1370
1261
  "args": {},
1371
1262
  "description": "Read the public receiver-facing status snapshot for a Claim Link bearer token or URL.",
1372
1263
  "examples": [
1373
- "$ allscale claim-link status --claim-token <token>",
1374
- "$ allscale claim-link status --claim-url https://app.allscale.io/claim/<token>"
1264
+ "$ allscale claim-link status --claim-token claim-token-from-share-link",
1265
+ "$ allscale claim-link status --claim-url https://app.allscale.io/claim/claim-token-from-share-link"
1375
1266
  ],
1376
1267
  "flags": {
1377
1268
  "json": {
@@ -1457,7 +1348,7 @@
1457
1348
  "aliases": [],
1458
1349
  "args": {
1459
1350
  "id": {
1460
- "description": "Invoice id_str (the backend's Payment.id_str)",
1351
+ "description": "Invoice id, as returned by `invoice send` and listed by `invoice list`",
1461
1352
  "name": "id",
1462
1353
  "required": true
1463
1354
  }
@@ -1465,7 +1356,7 @@
1465
1356
  "description": "Fetch one invoice by id. Wraps the `payment` GraphQL query.",
1466
1357
  "examples": [
1467
1358
  "$ allscale invoice get 65ab1234567890abcdef0123",
1468
- "$ allscale invoice get 65ab... --select 'id status amount currency'"
1359
+ "$ allscale invoice get 65ab1234567890abcdef0123 --select 'id status amount_coins display_amount payment_type'"
1469
1360
  ],
1470
1361
  "flags": {
1471
1362
  "json": {
@@ -1550,10 +1441,10 @@
1550
1441
  "description": "List invoices for a business. Wraps the `payments` GraphQL query. Defaults to the first 50 rows; pass --input '{\"limit\":N,\"skip\":M}' to paginate, or --all to request one unpaginated response. Filter with --status / --payment-type (repeatable), the creation-date range --from / --to (half-open: --from inclusive, --to exclusive), and --to-email, which narrows this both-directions view to the invoices you ISSUED to that contact.",
1551
1442
  "examples": [
1552
1443
  "$ allscale invoice list",
1553
- "$ allscale invoice list --business-id <id>",
1444
+ "$ allscale invoice list --business-id 65ab1234567890abcdef0123",
1554
1445
  "$ allscale invoice list --input '{\"limit\":50,\"skip\":50}' # second page",
1555
1446
  "$ allscale invoice list --all # everything (be careful)",
1556
- "$ allscale invoice list --input '{\"limit\":5}' --select 'count items { id status amount }'",
1447
+ "$ allscale invoice list --payment-type USDT --input '{\"limit\":5}' --select 'count items { id status amount_coins }'",
1557
1448
  "$ allscale invoice list --status SENT --status OVERDUE # unpaid work",
1558
1449
  "$ allscale invoice list --payment-type USDC # only USDC invoices",
1559
1450
  "$ allscale invoice list --from 2026-07-01 --to 2026-08-01 # exactly July (--to is exclusive)",
@@ -1602,7 +1493,7 @@
1602
1493
  },
1603
1494
  "business-id": {
1604
1495
  "char": "b",
1605
- "description": "Business id_str. Defaults to the logged-in user's business when omitted.",
1496
+ "description": "Business id. Defaults to the logged-in user's business when omitted.",
1606
1497
  "name": "business-id",
1607
1498
  "hasDynamicHelp": false,
1608
1499
  "multiple": false,
@@ -1703,15 +1594,15 @@
1703
1594
  "aliases": [],
1704
1595
  "args": {
1705
1596
  "id": {
1706
- "description": "Invoice id_str (the backend Payment.id_str) to pay.",
1597
+ "description": "Invoice id to pay, as listed by `invoice sent`.",
1707
1598
  "name": "id",
1708
1599
  "required": true
1709
1600
  }
1710
1601
  },
1711
- "description": "Pay a received invoice from your AllScale wallet. Resolves the invoice's destination + amount, moves the funds via the same signing path as `wallet send` (confirmed in the browser at /cli), then reports and confirms the payment. Needs TWO scopes: `invoice:all` to create, report and confirm the pay intent, and `wallet:read_only` to look up the address you are paying FROM. Neither flow grants `invoice:all` for you: on `device-login` the Invoices toggle starts at Read, so tick its write tier on the approval screen; on `otp-login` pass BOTH --scopes invoice:all --scopes wallet:read_only, because naming any scope turns every unnamed category off.",
1602
+ "description": "Pay an invoice where you are the payer, from your AllScale wallet. Use `allscale invoice sent` to list the invoices you owe (add --status SENT --status ACKNOWLEDGED --status FAILED for the ones you can start paying). An OVERDUE invoice cannot start a new payment: if an earlier attempt of yours may still be in flight, reconcile that first — otherwise ask the issuer to extend its due date, which revives it. Resolves the invoice's destination + amount, moves the funds via the same signing path as `wallet send` (the transfer is confirmed in the browser), then reports and confirms the payment. Needs TWO scopes: `invoice:all` to create, report and confirm the pay intent, and `wallet:all` to register the withdrawal — the same broker op `wallet send` uses (backend gates the op, not the verb); it also covers the payer-address lookup, since `:all` implies `:read_only`. Neither LOGIN flow grants either write tier for you: on `device-login` the Invoices toggle starts at Read and the Wallets Allow-changes tier starts OFF, so tick both on the approval screen; on `otp-login` pass BOTH --scopes invoice:all --scopes wallet:all, because naming any scope turns every unnamed category off.",
1712
1603
  "examples": [
1713
1604
  "$ allscale invoice pay 65ab1234567890abcdef0123",
1714
- "$ allscale invoice pay 65ab... --chain bsc --stable-coin USDT"
1605
+ "$ allscale invoice pay 65ab1234567890abcdef0123 --chain bsc --stable-coin USDT"
1715
1606
  ],
1716
1607
  "flags": {
1717
1608
  "json": {
@@ -1755,6 +1646,7 @@
1755
1646
  "type": "boolean"
1756
1647
  },
1757
1648
  "business-id": {
1649
+ "char": "b",
1758
1650
  "description": "Your own business id. Optional and redundant — payment always comes from the wallet of the business you are signed in as. Supplying a different business id is rejected rather than silently paying from your own wallet; omit the flag unless a script needs to assert which business it is acting as.",
1759
1651
  "name": "business-id",
1760
1652
  "hasDynamicHelp": false,
@@ -1788,60 +1680,15 @@
1788
1680
  "type": "option"
1789
1681
  },
1790
1682
  "browser-base": {
1791
- "description": "Override the base URL of the /cli confirmation link (defaults to the session API base). (Browser-bridge only — not valid with --headless.)",
1792
- "exclusive": [
1793
- "headless"
1794
- ],
1683
+ "description": "Override the base URL of the /cli confirmation link (defaults to the session API base).",
1795
1684
  "name": "browser-base",
1796
1685
  "hasDynamicHelp": false,
1797
1686
  "multiple": false,
1798
1687
  "type": "option"
1799
1688
  },
1800
1689
  "bridge-timeout": {
1801
- "description": "Override the confirmation deadline in milliseconds (default 180000). (Browser-bridge only not valid with --headless.)",
1802
- "exclusive": [
1803
- "headless"
1804
- ],
1690
+ "description": "Shorten the confirmation deadline, in milliseconds. By default the CLI waits the broker op's remaining life (~5 min). The browser only returns after the on-chain receipt confirms (it may poll for up to ~2 min after signing), so a shortened deadline must budget for the signing ceremony PLUS that receipt wait. Capped by the broker op TTL: a larger value is clamped, because the confirmation link cannot outlive its broker op. If no usable server-relative TTL is available, a withdraw waits up to 240s and may be shortened by the best-effort bound from the stated expiry.",
1805
1691
  "name": "bridge-timeout",
1806
- "default": 180000,
1807
- "hasDynamicHelp": false,
1808
- "multiple": false,
1809
- "type": "option"
1810
- },
1811
- "headless": {
1812
- "description": "Pay without a browser via Turnkey email-OTP (accounts with email-OTP enabled and no passkey; passkey accounts must use the browser flow). Supply the OTP via --otp-stdin (recommended for automation) or interactively; --otp / ALLSCALE_OTP work ONLY combined with --otp-id. This removes the in-browser human confirmation, so use it only on a trusted machine. Requires an existing legacy JWT session, which this build cannot create — use the browser-bridge flow instead.",
1813
- "name": "headless",
1814
- "allowNo": false,
1815
- "type": "boolean"
1816
- },
1817
- "otp": {
1818
- "dependsOn": [
1819
- "headless"
1820
- ],
1821
- "description": "Turnkey signing OTP code (with --headless). Only works combined with --otp-id (an externally-triggered flow): without --otp-id the CLI emails a FRESH code at run time, so any pre-set --otp / ALLSCALE_OTP value is stale and fails with 'Invalid or expired OTP code'. For automation prefer --otp-stdin (CLI emails; pipe the fresh code back). Passing --otp as a flag also exposes it in shell history AND the process list (ps / /proc/<pid>/cmdline).",
1822
- "exclusive": [
1823
- "otp-stdin"
1824
- ],
1825
- "name": "otp",
1826
- "hasDynamicHelp": false,
1827
- "multiple": false,
1828
- "type": "option"
1829
- },
1830
- "otp-stdin": {
1831
- "dependsOn": [
1832
- "headless"
1833
- ],
1834
- "description": "Read the Turnkey signing OTP as one line from stdin (with --headless).",
1835
- "name": "otp-stdin",
1836
- "allowNo": false,
1837
- "type": "boolean"
1838
- },
1839
- "otp-id": {
1840
- "dependsOn": [
1841
- "headless"
1842
- ],
1843
- "description": "Skip the OTP-send step and reuse a prior Turnkey otpId (with --headless). The OTP code itself is still required via --otp / ALLSCALE_OTP / --otp-stdin.",
1844
- "name": "otp-id",
1845
1692
  "hasDynamicHelp": false,
1846
1693
  "multiple": false,
1847
1694
  "type": "option"
@@ -1874,7 +1721,7 @@
1874
1721
  "description": "List invoices you are the PAYEE on — where the money is owed to your business. Wraps `recipient_payments` (the backend's `recipient` is the payee). This INCLUDES every invoice you issued with `invoice send`. Defaults to the first 50 rows; pass --input or --all to override. Filter with --status / --payment-type (repeatable), the creation-date range --from / --to (half-open: --from inclusive, --to exclusive), and --to-email to narrow to the invoices you billed to one contact.",
1875
1722
  "examples": [
1876
1723
  "$ allscale invoice received",
1877
- "$ allscale invoice received --business-id <id> --input '{\"limit\":10}'",
1724
+ "$ allscale invoice received --business-id 65ab1234567890abcdef0123 --input '{\"limit\":10}'",
1878
1725
  "$ allscale invoice received --all",
1879
1726
  "$ allscale invoice received --status SENT --status OVERDUE",
1880
1727
  "$ allscale invoice received --from 2026-07-01 --to 2026-08-01",
@@ -1923,7 +1770,7 @@
1923
1770
  },
1924
1771
  "business-id": {
1925
1772
  "char": "b",
1926
- "description": "Business id_str. Defaults to the logged-in user's business when omitted.",
1773
+ "description": "Business id. Defaults to the logged-in user's business when omitted.",
1927
1774
  "name": "business-id",
1928
1775
  "hasDynamicHelp": false,
1929
1776
  "multiple": false,
@@ -2023,15 +1870,15 @@
2023
1870
  "invoice:send": {
2024
1871
  "aliases": [],
2025
1872
  "args": {},
2026
- "description": "Send an invoice by email. Resolves the contact, selects receiving wallets, renders a minimal HTML invoice, and submits create_payment through the authenticated session. No browser ceremony — the payer signs when they pay, not when you issue. Returns a fixed summary (payment_id, contact, amount, wallet count) rather than a GraphQL selection, which is why there is no --select here; read the created invoice back with `invoice get <payment_id> --select ...`. Note the direction: an invoice you send lists YOU as the payee, so it appears under `invoice received`, not `invoice sent`.",
1873
+ "description": "Send an invoice by email. Resolves the contact, selects receiving wallets, renders a minimal HTML invoice, and submits create_payment through the authenticated session. No browser ceremony — the payer signs when they pay, not when you issue. Returns a fixed summary (payment_id, contact, amount, wallet count) rather than a GraphQL selection, which is why there is no --select here; read the created invoice back with `invoice get \"$PAYMENT_ID\" --select 'id status amount_coins display_amount payment_type'`, using the payment_id returned by this command. The follow-up reflects the default USDT path; display_amount remains the denomination-aware value. Note the direction: an invoice you send lists YOU as the payee, so it appears under `invoice received`, not `invoice sent`.",
2027
1874
  "examples": [
2028
1875
  "$ allscale invoice send --to-email client@example.com --amount 1.00",
2029
- "$ allscale invoice send --to-email client@example.com --amount 250 --payment-type 2 --currency-label USDC --memo \"January retainer\" --due 2026-06-01",
1876
+ "$ allscale invoice send --to-email client@example.com --amount 250 --payment-type 2 --currency-label USDC --memo \"January retainer\" --due \"$(node -p 'new Date(Date.now()+30*864e5).toISOString().slice(0,10)')\"",
2030
1877
  "$ allscale invoice send --to-contact-id 695da2cd3b9598f5edcdcb56 --amount 50 --line \"Design|1|30.00\" --line \"Copy|1|20.00\"",
2031
1878
  "$ allscale invoice send --to-email client@example.com --amount 75 --line \"Consulting retainer\"",
2032
1879
  "$ allscale invoice send --to-email client@example.com --amount 350 --line \"Discovery (4h)|4|25.00\" --line \"Implementation (10h)|10|25.00\"",
2033
1880
  "$ allscale invoice send --to-email client@example.com --line \"Discovery (4h)|4|25.00\" --line \"Implementation (10h)|10|25.00\"",
2034
- "$ allscale invoice send --to-email client@example.com --amount 100 --wallet-id 66ac....",
1881
+ "$ allscale invoice send --to-email client@example.com --amount 100 --wallet-id 66ac1234567890abcdef0123",
2035
1882
  "$ allscale invoice send --to-email newclient@example.com --amount 100 --auto-create-contact",
2036
1883
  "$ allscale invoice send --to-email billing@acme.com --amount 100 --auto-create-contact --contact-name \"Acme Inc\""
2037
1884
  ],
@@ -2088,7 +1935,7 @@
2088
1935
  "type": "option"
2089
1936
  },
2090
1937
  "to-contact-id": {
2091
- "description": "Contact id_str directly (skip the email lookup). Mutually exclusive with --to-email.",
1938
+ "description": "Contact id directly (skip the email lookup). Mutually exclusive with --to-email.",
2092
1939
  "exclusive": [
2093
1940
  "to-email"
2094
1941
  ],
@@ -2099,7 +1946,7 @@
2099
1946
  "type": "option"
2100
1947
  },
2101
1948
  "amount": {
2102
- "description": "Decimal amount in coin units (e.g. \"1.00\" for one USDT; minimum 0.10 for USDT/USDC). Optional when every --line is the three-field \"<description>|<quantity>|<amount>\" form — in that case the CLI sums the lines to derive the invoice total. Required otherwise.",
1949
+ "description": "Exact decimal amount: USDT/USDC accept up to 6 effective decimal places (minimum 0.10), while fiat accepts up to 2. Redundant trailing zeroes are allowed and canonicalized; the CLI never rounds or truncates. Optional when every --line is the three-field \"<description>|<quantity>|<amount>\" form — in that case the CLI sums the lines to derive the invoice total. Required otherwise.",
2103
1950
  "helpGroup": "CONDITIONAL",
2104
1951
  "name": "amount",
2105
1952
  "hasDynamicHelp": false,
@@ -2116,7 +1963,7 @@
2116
1963
  "type": "boolean"
2117
1964
  },
2118
1965
  "contact-name": {
2119
- "description": "Display name to use when --auto-create-contact creates a new contact. Optional — defaults to the full email address (unique enough to avoid the BE's per-business name-uniqueness collision). Has no effect when the contact already exists or when --auto-create-contact is not set.",
1966
+ "description": "Display name to use when --auto-create-contact creates a new contact. Optional — defaults to the full email address (unique enough to avoid a per-business name collision). Has no effect when the contact already exists or when --auto-create-contact is not set.",
2120
1967
  "exclusive": [
2121
1968
  "to-contact-id"
2122
1969
  ],
@@ -2126,15 +1973,14 @@
2126
1973
  "type": "option"
2127
1974
  },
2128
1975
  "currency-label": {
2129
- "description": "Label shown next to the amount in the rendered invoice (e.g. USDT, USDC). Display only; select the stablecoin with --payment-type=1 (USDT) or --payment-type=2 (USDC). --currency-int applies only to --payment-type=0 fiat invoices. Default: USDT.",
1976
+ "description": "Label shown after each amount in the rendered invoice (e.g. \"250.00 USDT\"). Display only it changes no amount, no asset, and nothing the payer is charged. Which stablecoins the invoice ACCEPTS is decided by --payment-type when you pass it explicitly (=1 USDT, =2 USDC); omit that flag and the invoice accepts whatever your business is configured for. Defaults to the label matching --payment-type (USDT or USDC; USD for --payment-type=0 fiat pass this flag explicitly for a non-USD --currency-int).",
2130
1977
  "name": "currency-label",
2131
- "default": "USDT",
2132
1978
  "hasDynamicHelp": false,
2133
1979
  "multiple": false,
2134
1980
  "type": "option"
2135
1981
  },
2136
1982
  "currency-int": {
2137
- "description": "Backend Currency enum value (USD=1, AED=2, ...). Only sent when --payment-type=0 (CURRENCY/fiat invoice); ignored for stablecoin payment types. Default: 1 (USD).",
1983
+ "description": "Fiat currency code, such as USD=1 or AED=2. Only sent when --payment-type=0 (fiat invoice); ignored for stablecoin payment types. Default: 1 (USD).",
2138
1984
  "name": "currency-int",
2139
1985
  "default": 1,
2140
1986
  "hasDynamicHelp": false,
@@ -2142,7 +1988,7 @@
2142
1988
  "type": "option"
2143
1989
  },
2144
1990
  "payment-type": {
2145
- "description": "CreatePaymentInput.payment_type. Run `allscale describe create_payment` for the enum. Default: 1.",
1991
+ "description": "What the invoice is denominated in AND which stablecoin it may be paid with: 0 = fiat (pair with --currency-int), 1 = USDT, 2 = USDC. Passing 1 or 2 EXPLICITLY pins the invoice to that coin — a payment arriving in the other stablecoin is rejected, and the payer's email names only this coin. Omit the flag and the invoice accepts whichever stablecoins your business is configured for (it is still denominated in USDT, the default). For 0 the amount is fiat but settlement is still in a stablecoin, so the payer may likewise use any coin your business accepts. Any other value is rejected locally (exit 2) — the backend would otherwise issue an invoice in an undocumented asset. Default: 1 (denomination only — omitting the flag pins nothing).",
2146
1992
  "name": "payment-type",
2147
1993
  "default": 1,
2148
1994
  "hasDynamicHelp": false,
@@ -2150,14 +1996,14 @@
2150
1996
  "type": "option"
2151
1997
  },
2152
1998
  "memo": {
2153
- "description": "Free-form invoice memo / description shown to the payer.",
1999
+ "description": "Free-form invoice memo / description shown to the payer. When omitted, defaults to the same note the web invoice editor generates: \"Thank you for your business! Payment is due within N days.\" with N derived from the due date (30 unless --due says otherwise).",
2154
2000
  "name": "memo",
2155
2001
  "hasDynamicHelp": false,
2156
2002
  "multiple": false,
2157
2003
  "type": "option"
2158
2004
  },
2159
2005
  "due": {
2160
- "description": "Invoice due date / datetime (ISO 8601), e.g. 2026-06-01, 2026-06-01T09:30:00, 2026-06-01T09:30:00Z, or 2026-06-01T09:30:00+08:00. A value without a UTC offset is read as UTC (so 2026-06-01 means 2026-06-01T00:00:00Z) — stated explicitly because interpreting it in the local timezone would make the same command mean different things on different machines. Rendered in the HTML body exactly as you typed it.",
2006
+ "description": "Invoice due date / datetime (ISO 8601), e.g. YYYY-MM-DD, YYYY-MM-DDTHH:mm:ss, YYYY-MM-DDTHH:mm:ssZ, or YYYY-MM-DDTHH:mm:ss+08:00. A value without a UTC offset is read as UTC (so a date-only value means midnight UTC) — stated explicitly because interpreting it in the local timezone would make the same command mean different things on different machines. When omitted, defaults to 30 days from now, matching the web invoice editor. The rendered document shows the NORMALIZED date in the web's display format (\"MMM DD, YYYY\"), not the raw flag text. The examples compute the date with POSIX command substitution, which sh/bash/zsh and PowerShell expand but cmd.exe does not; on cmd.exe pass an explicit date instead.",
2161
2007
  "name": "due",
2162
2008
  "hasDynamicHelp": false,
2163
2009
  "multiple": false,
@@ -2171,21 +2017,22 @@
2171
2017
  "type": "option"
2172
2018
  },
2173
2019
  "line": {
2174
- "description": "Line item on the rendered invoice. Repeatable. Two forms: \"<description>\" (qty=1, amount=full --amount) or \"<description>|<quantity>|<amount>\" (all explicit; same decimal units as --amount). Use the three-field form for more than one line: the single-field form gives EVERY line the full --amount, so two such lines sum to twice --amount and trigger the totals-mismatch warning. With three-field lines you may omit --amount entirely and the CLI sums them. The backend charges --amount regardless; --line only shapes the rendered HTML.",
2020
+ "description": "Line item on the rendered invoice. Repeatable. Two forms: \"<description>\" (qty=1, amount=full --amount) or \"<description>|<quantity>|<amount>\" (all explicit; same exact decimal precision as --amount). Use the three-field form for more than one line: the single-field form gives EVERY line the full --amount, so two such lines sum to twice --amount and trigger the totals-mismatch warning. With three-field lines you may omit --amount entirely and the CLI sums them. Every line total (quantity x amount) must fit the selected denomination's precision, whether or not --amount is given; the CLI never rounds it.",
2175
2021
  "name": "line",
2176
2022
  "hasDynamicHelp": false,
2177
2023
  "multiple": true,
2178
2024
  "type": "option"
2179
2025
  },
2180
2026
  "wallet-id": {
2181
- "description": "id_str of a wallet to receive payment. Repeatable. When given, the backend resolves and validates the ids (no wallet read needed — works with an invoice-scope-only agent key). When omitted, the CLI fetches your wallets via the narrow `business_wallets` query, which requires the wallet:read_only scope on an agent key.",
2027
+ "description": "id of a wallet to receive payment, as listed by `wallet list`. Repeatable. When given, the backend resolves and validates the ids (no wallet read needed — works with an invoice-scope-only agent key). When omitted, the CLI fetches your wallets via the narrow `business_wallets` query, which requires the wallet:read_only scope on an agent key.",
2182
2028
  "name": "wallet-id",
2183
2029
  "hasDynamicHelp": false,
2184
2030
  "multiple": true,
2185
2031
  "type": "option"
2186
2032
  },
2187
2033
  "business-id": {
2188
- "description": "Your own business id_str. Optional and redundant — the invoice is always issued as the business you are signed in as, and contacts and wallets always resolve against it. Supplying a different business id is rejected rather than silently ignored; omit the flag unless a script needs to assert which business it is acting as.",
2034
+ "char": "b",
2035
+ "description": "Your own business id. Optional and redundant — the invoice is always issued as the business you are signed in as, and contacts and wallets always resolve against it. Supplying a different business id is rejected rather than silently ignored; omit the flag unless a script needs to assert which business it is acting as.",
2189
2036
  "name": "business-id",
2190
2037
  "hasDynamicHelp": false,
2191
2038
  "multiple": false,
@@ -2225,7 +2072,7 @@
2225
2072
  "description": "List invoices you are the PAYER on — where your business owes the money. Wraps `sender_payments` (the backend's `sender` is the payer). NOTE: invoices you issued with `invoice send` are NOT here — you are the payee on those, so they appear under `invoice received`. Defaults to the first 50 rows; pass --input or --all to override. Filter with --status / --payment-type (repeatable) and the creation-date range --from / --to, which is half-open: --from is inclusive, --to exclusive. There is no --to-email here: an invoice's contact record belongs to the business that issued it, so filtering the invoices you OWE by your own contacts is not possible — use `invoice received --to-email`.",
2226
2073
  "examples": [
2227
2074
  "$ allscale invoice sent",
2228
- "$ allscale invoice sent --business-id <id> --input '{\"limit\":10}'",
2075
+ "$ allscale invoice sent --business-id 65ab1234567890abcdef0123 --input '{\"limit\":10}'",
2229
2076
  "$ allscale invoice sent --all",
2230
2077
  "$ allscale invoice sent --status SENT --status OVERDUE",
2231
2078
  "$ allscale invoice sent --from 2026-07-01 --to 2026-08-01"
@@ -2273,7 +2120,7 @@
2273
2120
  },
2274
2121
  "business-id": {
2275
2122
  "char": "b",
2276
- "description": "Business id_str. Defaults to the logged-in user's business when omitted.",
2123
+ "description": "Business id. Defaults to the logged-in user's business when omitted.",
2277
2124
  "name": "business-id",
2278
2125
  "hasDynamicHelp": false,
2279
2126
  "multiple": false,
@@ -2375,16 +2222,15 @@
2375
2222
  "aliases": [],
2376
2223
  "args": {
2377
2224
  "id": {
2378
- "description": "Invoice id_str (the backend's Payment.id_str)",
2225
+ "description": "Invoice id, as returned by `invoice send` and listed by `invoice list`",
2379
2226
  "name": "id",
2380
2227
  "required": true
2381
2228
  }
2382
2229
  },
2383
- "description": "Update an existing invoice's memo, payer, or (for an overdue invoice) its due date. Only those three fields can be changed; anything else is rejected by the backend.",
2230
+ "description": "Update an existing invoice's memo or (for an overdue invoice) its due date. Although sender_business_id exists in the GraphQL input type, the backend rejects attempts to change it; do not send that field.",
2384
2231
  "examples": [
2385
- "$ allscale invoice update 65ab... --input '{\"memo\":\"corrected memo\"}'",
2386
- "$ allscale invoice update 65ab... --input '{\"due_datetime\":\"2026-12-31T00:00:00Z\"}'",
2387
- "$ allscale invoice update 65ab... --input '{\"sender_business_id\":\"6a288fb5f9b4689d8b09a50a\"}'"
2232
+ "$ allscale invoice update 65ab1234567890abcdef0123 --input '{\"memo\":\"corrected memo\"}'",
2233
+ "OVERDUE only: due_datetime may move FORWARD only (example: 30 days out):\n$ DUE=\"$(node -p 'new Date(Date.now()+30*864e5).toISOString()')\"\n$ allscale invoice update 65ab1234567890abcdef0123 -i \"{\\\"due_datetime\\\":\\\"$DUE\\\"}\""
2388
2234
  ],
2389
2235
  "flags": {
2390
2236
  "json": {
@@ -2429,7 +2275,7 @@
2429
2275
  },
2430
2276
  "input": {
2431
2277
  "char": "i",
2432
- "description": "JSON object of fields to change. Exactly three are accepted: \"memo\" (free text), \"sender_business_id\" (the payer's business id), and \"due_datetime\" (ISO 8601, e.g. 2026-12-31 or 2026-12-31T00:00:00Z — a value without a UTC offset is read as UTC, exactly as `invoice send --due` does). Note that due_datetime only applies to an OVERDUE invoice and may only move FORWARD; extending past now also revives the invoice so buyers can pay again. Any other field is rejected by the backend.",
2278
+ "description": "JSON object of fields to change. Use \"memo\" (free text) or \"due_datetime\" (ISO 8601, e.g. YYYY-MM-DD or YYYY-MM-DDTHH:mm:ssZ — a value without a UTC offset is read as UTC, exactly as `invoice send --due` does). The GraphQL input also exposes \"sender_business_id\", but the backend rejects attempts to change it; do not send that field. Note that due_datetime only applies to an OVERDUE invoice and may only move FORWARD; extending past now also revives the invoice so buyers can pay again. Any other field is rejected by the backend. The example computes the value with POSIX command substitution, which sh/bash/zsh and PowerShell expand but cmd.exe does not; on cmd.exe pass an explicit ISO value.",
2433
2279
  "helpGroup": "REQUIRED",
2434
2280
  "name": "input",
2435
2281
  "required": true,
@@ -2478,8 +2324,8 @@
2478
2324
  "args": {},
2479
2325
  "description": "Create and auto-fund a claim link from this store's enabled auto-payout wallet. Authenticates with the store API key/secret (NOT your login), so it needs a store that has completed Payout onboarding. Idempotent on --reference-id.",
2480
2326
  "examples": [
2481
- "$ ALLSCALE_STORE_API_SECRET=… allscale payout send --api-key ak_… --amount 10 --chain base --reference-id order-1042",
2482
- "$ allscale payout send --payout-api-base https://openapi.allscale.io --api-key ak_… --api-secret --amount 25 --stable-coin USDC --chain polygon --reference-id inv-77 --receiver-email client@example.com"
2327
+ "$ ALLSCALE_STORE_API_SECRET=your_store_api_secret allscale payout send --api-key ak_your_api_key --amount 10 --chain base --reference-id order-1042",
2328
+ "$ allscale payout send --payout-api-base https://openapi.allscale.io --api-key ak_your_api_key --api-secret your_store_api_secret --amount 25 --stable-coin USDC --chain polygon --reference-id inv-77 --receiver-email client@example.com"
2483
2329
  ],
2484
2330
  "flags": {
2485
2331
  "json": {
@@ -2721,11 +2567,11 @@
2721
2567
  "store:create": {
2722
2568
  "aliases": [],
2723
2569
  "args": {},
2724
- "description": "Create a store for the logged-in business. Returns the API key and one-time API secret in stdout; capture the result securely. Business must have checkout enabled; only one sandbox store is allowed per business.",
2570
+ "description": "Create a store for the logged-in business. Returns the API key and an API secret that is shown only once (it cannot be retrieved later — capture it now). The secret itself stays valid for use — it just cannot be displayed again. Business must have checkout enabled; only one sandbox store is allowed per business.",
2725
2571
  "examples": [
2726
2572
  "$ allscale store create --name \"My Shop\"",
2727
2573
  "$ allscale store create --name \"Live Shop\" --live",
2728
- "$ allscale store create --name \"Acme\" --business-id 665f0c..."
2574
+ "$ allscale store create --name \"Acme\" --business-id 665f0c1234567890abcdef12"
2729
2575
  ],
2730
2576
  "flags": {
2731
2577
  "json": {
@@ -2778,6 +2624,7 @@
2778
2624
  "type": "option"
2779
2625
  },
2780
2626
  "business-id": {
2627
+ "char": "b",
2781
2628
  "description": "Owning business id. Defaults to the logged-in business (decoded from the session token). Admins creating for another business must pass this.",
2782
2629
  "name": "business-id",
2783
2630
  "hasDynamicHelp": false,
@@ -2791,7 +2638,7 @@
2791
2638
  "type": "boolean"
2792
2639
  },
2793
2640
  "type": {
2794
- "description": "Store type (backend StoreType int). Omit for the default.",
2641
+ "description": "Store type. 1 = checkout (the default). Payout credential stores are provisioned by Payout onboarding, not by this command. Omit for checkout.",
2795
2642
  "name": "type",
2796
2643
  "hasDynamicHelp": false,
2797
2644
  "multiple": false,
@@ -2830,7 +2677,7 @@
2830
2677
  "aliases": [],
2831
2678
  "args": {
2832
2679
  "id": {
2833
- "description": "Transaction id_str",
2680
+ "description": "Transaction id",
2834
2681
  "name": "id",
2835
2682
  "required": true
2836
2683
  }
@@ -2838,7 +2685,7 @@
2838
2685
  "description": "Fetch one transaction by id. Wraps the `transaction` query.",
2839
2686
  "examples": [
2840
2687
  "$ allscale transaction get 65ab1234567890abcdef0123",
2841
- "$ allscale transaction get 65ab... --select 'id status type direction'"
2688
+ "$ allscale transaction get 65ab1234567890abcdef0123 --select 'id status transaction_type direction'"
2842
2689
  ],
2843
2690
  "flags": {
2844
2691
  "json": {
@@ -2920,11 +2767,15 @@
2920
2767
  "transaction:list": {
2921
2768
  "aliases": [],
2922
2769
  "args": {},
2923
- "description": "List transactions. --scope picks between mine (default; backend my_transactions), business (business_transaction_records), or activities. All scopes default to the first 50 rows; pass --input or --all to override.",
2770
+ "description": "List transactions. --scope picks between mine (default; backend my_transactions), business (business_transaction_records), or activities. All scopes default to the first 50 rows; pass --input or --all to override. Filter by time with --from / --to (all scopes; half-open: --from inclusive, --to exclusive), and with --direction / --transaction-type / --status under --scope business.",
2924
2771
  "examples": [
2925
2772
  "$ allscale transaction list",
2773
+ "$ allscale transaction list --from 2026-07-01 --to 2026-08-01 # exactly July (--to is exclusive)",
2774
+ "$ allscale transaction list --scope business --direction INFLOW --status SUCCESS",
2775
+ "$ allscale transaction list --scope business --transaction-type CLAIM_LINK",
2926
2776
  "$ allscale transaction list --scope business --input '{\"limit\":20}'",
2927
2777
  "$ allscale transaction list --scope activities --skip 0 --limit 25",
2778
+ "$ allscale transaction list --scope activities --from 2026-07-01",
2928
2779
  "$ allscale transaction list --scope activities --all"
2929
2780
  ],
2930
2781
  "flags": {
@@ -3009,12 +2860,50 @@
3009
2860
  "type": "option"
3010
2861
  },
3011
2862
  "limit": {
3012
- "description": "Pagination limit (activities scope only). Defaults to 50; --all requests one unpaginated response.",
2863
+ "description": "Positive pagination limit (activities scope only). Defaults to 50; --all requests one unpaginated response.",
2864
+ "exclusive": [
2865
+ "all"
2866
+ ],
3013
2867
  "name": "limit",
3014
2868
  "hasDynamicHelp": false,
3015
2869
  "multiple": false,
3016
2870
  "type": "option"
3017
2871
  },
2872
+ "from": {
2873
+ "description": "Only transactions at or after this time. INCLUSIVE. Filters created_at under --scope mine/business and transaction_time under --scope activities. Accepts a date (2026-08-05), a naive datetime (2026-08-05T12:30:00, read as UTC), or an offset datetime (2026-08-05T20:30:00+08:00); the CLI converts to UTC and pads it to the backend's required YYYY-MM-DDTHH:MM:SS.ffffffZ.",
2874
+ "name": "from",
2875
+ "hasDynamicHelp": false,
2876
+ "multiple": false,
2877
+ "type": "option"
2878
+ },
2879
+ "to": {
2880
+ "description": "Only transactions strictly before this time. EXCLUSIVE — --from 2026-07-01 --to 2026-08-01 is exactly July and consecutive ranges never overlap. Filters created_at under --scope mine/business and transaction_time under --scope activities. Accepts a date (2026-08-05), a naive datetime (2026-08-05T12:30:00, read as UTC), or an offset datetime (2026-08-05T20:30:00+08:00); the CLI converts to UTC and pads it to the backend's required YYYY-MM-DDTHH:MM:SS.ffffffZ.",
2881
+ "name": "to",
2882
+ "hasDynamicHelp": false,
2883
+ "multiple": false,
2884
+ "type": "option"
2885
+ },
2886
+ "direction": {
2887
+ "description": "Only transactions in this direction (--scope business only). Accepted: INFLOW, OUTFLOW (case-insensitive), or a raw integer (1=INFLOW, -1=OUTFLOW).",
2888
+ "name": "direction",
2889
+ "hasDynamicHelp": false,
2890
+ "multiple": false,
2891
+ "type": "option"
2892
+ },
2893
+ "transaction-type": {
2894
+ "description": "Only transactions of this type (--scope business only). Accepted: PAYMENT, REWARD, WITHDRAWAL, SERVICE_FEE, CHECKOUT_INTENT_EPHEMERAL, CHECKOUT_INTENT, ON_CHAIN_TX, CHECKOUT_INTENT_EPHEMERAL_REFUND, CHECKOUT_INTENT_SERVICE_FEE, CHECKOUT_INTENT_EPHEMERAL_NET_INCOME, CLAIM_LINK (case-insensitive), or a raw integer.",
2895
+ "name": "transaction-type",
2896
+ "hasDynamicHelp": false,
2897
+ "multiple": false,
2898
+ "type": "option"
2899
+ },
2900
+ "status": {
2901
+ "description": "Only transactions in this status (--scope business only). Accepted: NEW, SUCCESS, FAILED, CONFIRMING (case-insensitive), or a raw integer. These are transaction statuses, not invoice statuses.",
2902
+ "name": "status",
2903
+ "hasDynamicHelp": false,
2904
+ "multiple": false,
2905
+ "type": "option"
2906
+ },
3018
2907
  "select": {
3019
2908
  "char": "s",
3020
2909
  "description": "Override the default selection set with a GraphQL fragment. Default (--scope mine, --scope business): 'count items { created_at updated_at id owner_type owner_business_id owner_employee_id transaction_type direction reference_type payment_id counterparty_business_id counterparty_employee_id counterparty_name payment_type currency amount_cents amount_coins status memo transaction_time tx_hash tx_block_chain tx_from tx_to tx_block_number total_fee network_fee service_fee }'. Default (--scope activities): 'count items { transaction_id amount_coins amount_cents transaction_time created_at transaction_status payment_id transaction_type direction wallet_address tx_hash tx_block_chain tx_block_number memo payment_type currency total_fee network_fee service_fee counterparty_business_id claim_link_id }'. Pass --show-doc to print the exact GraphQL document that gets sent.",
@@ -3139,13 +3028,12 @@
3139
3028
  "wallet:send": {
3140
3029
  "aliases": [],
3141
3030
  "args": {},
3142
- "description": "Withdraw a stablecoin from your AllScale wallet to an EVM address. Confirms in the browser by default; the CLI prints the transaction hash and explorer URL when the network accepts the transfer. Accounts with Turnkey email-OTP enabled and no passkey can sign without a browser via --headless. Requires an existing legacy JWT session, which this build cannot create — use the browser-bridge flow instead.",
3031
+ "description": "Withdraw a stablecoin from your AllScale wallet to an EVM address. The transfer is confirmed in the browser; the CLI prints the transaction hash and explorer URL only after its on-chain receipt confirms. Needs `wallet:all` on an agent key: it registers the withdrawal op (`wallet:read_only` alone cannot). Every transfer still requires your in-browser confirmation.",
3143
3032
  "examples": [
3144
- "$ allscale wallet send --idempotency-key order-1042 --to 0xabc... --amount 100 --chain ethereum",
3145
- "$ allscale wallet send --idempotency-key order-1043 --to 0xabc... --amount 50 --chain polygon --stable-coin USDC",
3146
- "$ allscale wallet send --idempotency-key order-1044 --to 0xabc... --amount 1 --chain base --bridge-timeout 300000",
3147
- "$ allscale wallet send --idempotency-key order-1045 --to 0xabc... --amount 1 --chain bsc # over SSH: open the printed URL on any device, paste the tx hash back",
3148
- "$ allscale wallet send --to 0xabc... --amount 1 --chain base --headless --otp-stdin --idempotency-key order-1042 # reuse the same key only when retrying this operation"
3033
+ "$ allscale wallet send --idempotency-key order-1042 --to 0x1234567890abcdef1234567890abcdef12345678 --amount 100 --chain ethereum",
3034
+ "$ allscale wallet send --idempotency-key order-1043 --to 0x1234567890abcdef1234567890abcdef12345678 --amount 50 --chain polygon --stable-coin USDC",
3035
+ "$ allscale wallet send --idempotency-key order-1044 --to 0x1234567890abcdef1234567890abcdef12345678 --amount 1 --chain base --bridge-timeout 240000",
3036
+ "$ allscale wallet send --idempotency-key order-1045 --to 0x1234567890abcdef1234567890abcdef12345678 --amount 1 --chain bsc # over SSH: open the printed URL on any device, paste the full result JSON back"
3149
3037
  ],
3150
3038
  "flags": {
3151
3039
  "json": {
@@ -3189,7 +3077,7 @@
3189
3077
  "type": "boolean"
3190
3078
  },
3191
3079
  "to": {
3192
- "description": "Recipient EVM address (0x 40-hex).",
3080
+ "description": "Recipient EVM address (0x followed by 40 hex characters).",
3193
3081
  "helpGroup": "REQUIRED",
3194
3082
  "name": "to",
3195
3083
  "required": true,
@@ -3245,62 +3133,18 @@
3245
3133
  "type": "option"
3246
3134
  },
3247
3135
  "bridge-timeout": {
3248
- "description": "Shorten the confirmation deadline, in milliseconds. By default the CLI waits the full broker op TTL (~5 min, shared by the loopback and paste channels); pass this to abort sooner. (Browser-bridge only — not valid with --headless.)",
3249
- "exclusive": [
3250
- "headless"
3251
- ],
3136
+ "description": "Shorten the confirmation deadline, in milliseconds. By default the CLI waits the broker op's remaining life (~5 min, shared by the loopback and paste channels); pass this to abort sooner. The browser only returns after the on-chain receipt confirms (it may poll for up to ~2 min after signing), so a shortened deadline must budget for the signing ceremony PLUS that receipt wait. It can only shorten the wait a value above the op's remaining life is clamped to it, because the confirmation link cannot outlive its broker op. If the server's response carries no timestamp to measure that life against, the CLI says so on stderr and bounds the wait instead: with no --bridge-timeout a withdraw waits up to 240s (budgeting the signing ceremony plus the receipt wait) and may be shortened by the best-effort bound from the stated expiry; an explicit value is honoured up to that same bound (clamped to 60s-15min), or 15min when the expiry itself is unreadable.",
3252
3137
  "name": "bridge-timeout",
3253
3138
  "hasDynamicHelp": false,
3254
3139
  "multiple": false,
3255
3140
  "type": "option"
3256
3141
  },
3257
3142
  "browser-base": {
3258
- "description": "Override the base URL of the printed /cli confirmation link (defaults to the session API base). (Browser-bridge only not valid with --headless.)",
3259
- "exclusive": [
3260
- "headless"
3261
- ],
3143
+ "description": "Override the base URL of the printed /cli confirmation link (defaults to the session API base). Accepts only the trusted AllScale origin; invalid values report the allowed origin.",
3262
3144
  "name": "browser-base",
3263
3145
  "hasDynamicHelp": false,
3264
3146
  "multiple": false,
3265
3147
  "type": "option"
3266
- },
3267
- "headless": {
3268
- "description": "Sign without a browser via Turnkey email-OTP (accounts with email-OTP enabled and no passkey; passkey accounts must use the browser flow). Supply the OTP via --otp-stdin (recommended for automation) or interactively; --otp / ALLSCALE_OTP work ONLY combined with --otp-id. This removes the in-browser human confirmation, so use it only on a trusted machine. Requires an existing legacy JWT session, which this build cannot create — use the browser-bridge flow instead.",
3269
- "name": "headless",
3270
- "allowNo": false,
3271
- "type": "boolean"
3272
- },
3273
- "otp": {
3274
- "dependsOn": [
3275
- "headless"
3276
- ],
3277
- "description": "Turnkey signing OTP code (with --headless). Only works combined with --otp-id (an externally-triggered flow): without --otp-id the CLI emails a FRESH code at run time, so any pre-set --otp / ALLSCALE_OTP value is stale and fails with 'Invalid or expired OTP code'. For automation prefer --otp-stdin (CLI emails; pipe the fresh code back). Passing --otp as a flag also exposes it in shell history AND the process list (ps / /proc/<pid>/cmdline).",
3278
- "exclusive": [
3279
- "otp-stdin"
3280
- ],
3281
- "name": "otp",
3282
- "hasDynamicHelp": false,
3283
- "multiple": false,
3284
- "type": "option"
3285
- },
3286
- "otp-stdin": {
3287
- "dependsOn": [
3288
- "headless"
3289
- ],
3290
- "description": "Read the Turnkey signing OTP as one line from stdin (with --headless).",
3291
- "name": "otp-stdin",
3292
- "allowNo": false,
3293
- "type": "boolean"
3294
- },
3295
- "otp-id": {
3296
- "dependsOn": [
3297
- "headless"
3298
- ],
3299
- "description": "Skip the OTP-send step and reuse a prior Turnkey otpId (with --headless). The OTP code itself is still required via --otp / ALLSCALE_OTP / --otp-stdin.",
3300
- "name": "otp-id",
3301
- "hasDynamicHelp": false,
3302
- "multiple": false,
3303
- "type": "option"
3304
3148
  }
3305
3149
  },
3306
3150
  "hasDynamicHelp": false,
@@ -3325,5 +3169,5 @@
3325
3169
  ]
3326
3170
  }
3327
3171
  },
3328
- "version": "0.1.0"
3172
+ "version": "1.0.2"
3329
3173
  }