@allscale/cli 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -12
- package/dist/commands/build-info.js +12 -12
- package/dist/commands/claim-link/claim.js +10 -10
- package/dist/commands/claim-link/create.js +31 -31
- package/dist/commands/claim-link/get.js +13 -13
- package/dist/commands/claim-link/list.js +12 -12
- package/dist/commands/claim-link/status.js +11 -11
- package/dist/commands/describe.js +11 -11
- package/dist/commands/device-login.js +28 -27
- package/dist/commands/invoice/get.js +12 -12
- package/dist/commands/invoice/list.js +12 -12
- package/dist/commands/invoice/pay.js +41 -37
- package/dist/commands/invoice/received.js +12 -12
- package/dist/commands/invoice/send.js +29 -29
- package/dist/commands/invoice/sent.js +11 -11
- package/dist/commands/invoice/update.js +13 -13
- package/dist/commands/logout.js +19 -13
- package/dist/commands/operations.js +11 -11
- package/dist/commands/otp-login.js +17 -17
- package/dist/commands/otp-send.js +11 -11
- package/dist/commands/payout/send.js +14 -14
- package/dist/commands/payout/status.js +12 -12
- package/dist/commands/scope.js +16 -16
- package/dist/commands/store/create.js +16 -16
- package/dist/commands/transaction/get.js +12 -12
- package/dist/commands/transaction/list.js +12 -12
- package/dist/commands/wallet/list.js +12 -12
- package/dist/commands/wallet/send.js +27 -27
- package/dist/commands/whoami.js +14 -12
- package/dist/hooks/version-suffix.js +1 -1
- package/dist/index.js +11 -11
- package/dist/lib/help.js +10 -7
- package/oclif.manifest.json +24 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -132,8 +132,11 @@ allscale invoice send --to-email billing@acme.com --amount 100 \
|
|
|
132
132
|
allscale wallet send --idempotency-key order-1042 \
|
|
133
133
|
--to 0xabc... --amount 0.1 --chain base --stable-coin USDC
|
|
134
134
|
|
|
135
|
-
# Drop credentials
|
|
136
|
-
allscale logout
|
|
135
|
+
# Drop credentials. `--profile` takes a LITERAL profile name, not "whichever
|
|
136
|
+
# one is currently the default" — substitute yours. A bare `allscale logout`
|
|
137
|
+
# at a terminal now says what it will delete and asks first; `--yes` skips
|
|
138
|
+
# that prompt for a script.
|
|
139
|
+
allscale logout --profile <profile>
|
|
137
140
|
```
|
|
138
141
|
|
|
139
142
|
> **Windows note.** The invoice examples above compute the due date with POSIX
|
|
@@ -147,12 +150,54 @@ allscale logout
|
|
|
147
150
|
### Auth
|
|
148
151
|
| Command | Description |
|
|
149
152
|
|---|---|
|
|
150
|
-
| `device-login` | **Recommended for AllScale Pay Accounts.** Opens the browser; use an existing web-app session or sign in there with **Passkey or Email**. On the Email path, enter the **Email OTP on the webpage**, not in the terminal, then approve the CLI. The backend mints a **scoped agent API key** — the credential issued by the current CLI login flows. Scopes chosen on the approval screen are **checked by the backend**. 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. The key expires and does not refresh (re-run `device-login` when it does; the CLI prints the expiry). A **bare** `device-login` at a terminal first prints what it is about to do and asks to confirm; declining exits **12** (`user.cancelled`) having created nothing. The prompt is bare-invocation only — any flag (including `--yes`) skips it — and never appears for `--json`, a pipe, a sidecar, ordinary non-tty CI, or a redirected stderr. Automation that allocates a pty for all three streams *and* runs the command bare is indistinguishable from a human and is prompted; pass `--yes`. Polling stops at the pairing's expiry or **15 minutes**, whichever is earlier; the CLI prints that deadline before it starts waiting. With a sidecar configured, pairing initiation also appends a sidecar-v2 `type: "device_authorization"` event containing `verification_url`, `verification_code`, and `polling_deadline_at`, so a supervisor need not parse stderr. |
|
|
153
|
+
| `device-login` | **Recommended for AllScale Pay Accounts.** Opens the browser; use an existing web-app session or sign in there with **Passkey or Email**. On the Email path, enter the **Email OTP on the webpage**, not in the terminal, then approve the CLI. The backend mints a **scoped agent API key** — the credential issued by the current CLI login flows. Scopes chosen on the approval screen are **checked by the backend**. 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. The key expires and does not refresh (re-run `device-login` when it does; the CLI prints the expiry). A **bare** `device-login` at a terminal first prints what it is about to do and asks to confirm; declining exits **12** (`user.cancelled`) having created nothing. The prompt is bare-invocation only — any flag (including `--yes`) skips it — and never appears for `--json`, a pipe, a sidecar, ordinary non-tty CI, or a redirected stderr. Automation that allocates a pty for all three streams *and* runs the command bare is indistinguishable from a human and is prompted; pass `--yes`. Polling stops at the pairing's expiry or **15 minutes**, whichever is earlier; the CLI prints that deadline before it starts waiting. If the window closes with nobody approving, that is **`auth.device_pairing_timeout`** / exit **3** — no credential was issued and nothing was written locally; re-run to get a new verification code. (Should you approve in the browser just as the window closes, the pairing can still mint a key that never reaches the CLI, so the message points you at the web dashboard to review and revoke it.) With a sidecar configured, pairing initiation also appends a sidecar-v2 `type: "device_authorization"` event containing `verification_url`, `verification_code`, and `polling_deadline_at`, so a supervisor need not parse stderr. |
|
|
151
154
|
| `otp-login` | **Terminal-only** Email-OTP path for headless / scripted shells. Works with **any account email** — external domains included, not just `@allscale.io` (the account must already exist — sign up in the web app first). Sends the code and reads it in the terminal (flag / env / stdin / interactive prompt), then mints the same **scoped agent API key** with the scopes passed via `--scopes` (required — there is deliberately no full-permission default). |
|
|
152
155
|
| `otp-send` | Granular: send the email OTP only (login only, any account email). Returns the otp_id; pair with `otp-login --otp-id <id> --otp <code>` for scripted flows. |
|
|
153
156
|
| `whoami` | Show the current authenticated identity. No network call. Agent-key sessions (the normal case) print the key-mode identity from the stored bundle: `credential: "agent_key"`, `business_id`, `user_type`, `scopes`, and the key expiry. |
|
|
154
157
|
| `scope` | 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** for 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; the key expiry is shown. No network call. |
|
|
155
|
-
| `logout` | Drop cached session and store credentials for a profile. A bare logout requests a full plaintext + OS-keychain sweep and exits non-zero after file cleanup if the keychain cannot be inspected; `--insecure-storage` performs an explicit file-only cleanup and never probes keychain. JSON output keeps the legacy session-token-only `removed` boolean and adds `removal_status`: `removed` when any session or store credential was deleted, `nothing_to_remove` only when a complete sweep found none, or `skipped` when a file-only sweep deleted nothing because keychain inspection was intentionally skipped. |
|
|
158
|
+
| `logout` | Drop cached session and store credentials for a profile. A **bare** `logout` at a terminal first prints what it is about to delete and asks to confirm; declining exits **12** (`user.cancelled`) having removed nothing. That prompt follows the same rules as `device-login`'s (bare invocation only, `--yes` or any flag skips it, never for `--json` / pipe / sidecar / non-tty CI / redirected stderr; a bare run under a full pty is prompted). A bare logout requests a full plaintext + OS-keychain sweep and exits non-zero after file cleanup if the keychain cannot be inspected; `--insecure-storage` performs an explicit file-only cleanup and never probes keychain. JSON output keeps the legacy session-token-only `removed` boolean and adds `removal_status`: `removed` when any session or store credential was deleted, `nothing_to_remove` only when a complete sweep found none, or `skipped` when a file-only sweep deleted nothing because keychain inspection was intentionally skipped. When a sweep comes back partial, the error `details` also carry `uncleared_backends` (`keychain` / `insecure-file`), so a script can tell "everything readable was cleared, the keychain is unknown" from "nothing was removed". |
|
|
159
|
+
|
|
160
|
+
#### `device-login` failure reasons
|
|
161
|
+
|
|
162
|
+
A terminal `device-login` error may carry `error.details.reason`. The values in
|
|
163
|
+
the table below are a stable, CLI-owned contract for subprocess and agent
|
|
164
|
+
consumers: branch on `error.code` first, then use `reason` to narrow that error.
|
|
165
|
+
The human-readable message is prose and may change. Published values and
|
|
166
|
+
meanings remain backward-compatible; future releases may add values. Not every
|
|
167
|
+
failure has a reason, so consumers must tolerate a missing or unknown value
|
|
168
|
+
rather than treating this table as an exhaustive switch.
|
|
169
|
+
|
|
170
|
+
| `details.reason` | `error.code` / exit | Meaning |
|
|
171
|
+
| --- | --- | --- |
|
|
172
|
+
| `init_fetch_failure` | `transport.network` / 3 | The pairing-init request failed before a response arrived. |
|
|
173
|
+
| `init_request_timeout` | `transport.timeout` / 3 | The pairing-init request reached its request deadline before a response arrived. |
|
|
174
|
+
| `init_body_failure` | `transport.network` / 3 | An init response arrived, but reading its body failed. |
|
|
175
|
+
| `init_body_timeout` | `transport.timeout` / 3 | An init response arrived, but its body did not finish before the init deadline. |
|
|
176
|
+
| `approved_credential_mismatch` | `backend.internal` / 9 | The backend reported approval without the requested scoped agent key. The CLI stores nothing because an older or rolling backend may instead have minted a broader legacy credential; `details.outcome_ambiguous` is `true`. |
|
|
177
|
+
| `pairing_expired_backend_ttl` | `auth.device_pairing_timeout` / 3 | The backend returned business code 7510, its explicit signal that the pairing TTL elapsed. |
|
|
178
|
+
| `pairing_token_unrecognized` | `auth.no_token` / 4 | The backend returned 7511, but the CLI cannot authoritatively prove the window closed. The record may be unknown, evicted, or already consumed; a missing backend `Date` header also keeps this neutral classification. |
|
|
179
|
+
| `pairing_token_unrecognized_after_window_closed` | `auth.device_pairing_timeout` / 3 | With no unresolved transient failure, a poll returned 7511 and the backend's own clock was at or past the backend-minted `expires_at`. |
|
|
180
|
+
| `pairing_deadline_backend_ttl` | `auth.device_pairing_timeout` / 3 | With no unresolved transient failure, the poll loop stopped at an effective deadline selected from the backend's `expires_at`, rather than the CLI ceiling. This identifies the bound that fired; because the loop observes that bound with the local clock, it does not by itself prove what the backend clock says now. |
|
|
181
|
+
| `pairing_deadline_cli_ceiling` | `auth.device_pairing_timeout` / 3 | With no unresolved transient failure, the poll loop reached the CLI's own ceiling before `expires_at`; the pairing may still be live server-side, so a later browser approval can mint a key that this process will not collect. |
|
|
182
|
+
| `invalid_polling_token_after_transient_failure` | `backend.internal` / 9 | After an unresolved transient poll failure, a later poll returned 7511. The one-shot approval response may have been consumed and lost; `details.outcome_ambiguous` is `true`. |
|
|
183
|
+
| `transient_retry_budget_exhausted` | `backend.internal` / 9 | Retryable poll failures exhausted the bounded retry budget without a definitive pending response; `details.outcome_ambiguous` is `true`. |
|
|
184
|
+
| `pairing_deadline_elapsed_after_transient_failure` | `backend.internal` / 9 | The effective pairing deadline elapsed while a transient poll failure remained unresolved; `details.outcome_ambiguous` is `true`. |
|
|
185
|
+
|
|
186
|
+
The single-attempt poll helper also uses six retry-only reasons internally.
|
|
187
|
+
`runDevicePairingLogin` catches these and either receives a later definitive
|
|
188
|
+
response or converts the terminal outcome into one of the three
|
|
189
|
+
`backend.internal` reasons above. They therefore do **not** appear directly in
|
|
190
|
+
the terminal `allscale device-login` subprocess envelope and are not part of its
|
|
191
|
+
stable consumer contract:
|
|
192
|
+
|
|
193
|
+
| Retry-only reason | Meaning before the orchestrator retries |
|
|
194
|
+
| --- | --- |
|
|
195
|
+
| `poll_fetch_failure` | The poll request failed before a response arrived. |
|
|
196
|
+
| `poll_request_timeout` | The poll request reached its request deadline before a response arrived. |
|
|
197
|
+
| `poll_body_failure` | A poll response arrived, but reading its body failed. |
|
|
198
|
+
| `poll_body_timeout` | A poll response arrived, but its body did not finish before the poll deadline. |
|
|
199
|
+
| `empty_response` | The poll returned HTTP 200 with an empty body. |
|
|
200
|
+
| `invalid_response` | The poll returned HTTP 200 with malformed JSON or an unexpected response shape/status. |
|
|
156
201
|
|
|
157
202
|
### Authorization model
|
|
158
203
|
|
|
@@ -161,7 +206,7 @@ One page, no archaeology:
|
|
|
161
206
|
- **The current CLI auth flows issue a scoped agent API key.** Both `device-login` (browser approval) and `otp-login` (terminal OTP, for headless/CI) mint one. There is no cookie mode and no full-permission default — every key carries an explicit scope set.
|
|
162
207
|
- **Scopes** are `<category>:<tier>` strings (e.g. `invoice:read_only`, `claim_link:all`), chosen on the browser approval screen (`device-login`) or via `--scopes` (`otp-login`). Requested scopes are clipped to your account's role at mint; the granted set is echoed back and recorded locally (`allscale scope` shows it without a network call). The backend **checks** the set on each request; an out-of-scope call is expected to fail with exit 6. Scope controls limit permitted call categories but do not verify the caller's authority, purpose, accuracy, legality, or safety. Both commands check the *values* locally first: an unrecognised `<category>:<tier>` string fails with `input.invalid` (exit 2) before anything is sent — no OTP, and no pairing session — so a script can rely on one contract for the flag whichever login it uses.
|
|
163
208
|
- **Keys expire and do not refresh.** The CLI prints the expiry at login and in `scope` / `whoami`; re-run `device-login` or `otp-login` to mint a new key. Revoke keys in the AllScale web dashboard, then log in again for a fresh one — the CLI has no key-management command (see the Keys section).
|
|
164
|
-
- **Some actions stay human-only by design.** Two of them are not CLI commands at all — key management lives in the dashboard (see the Keys section) and payout authorization lives in Store Settings → Payout Authorization. The browser-bridge flows (`wallet send`, `invoice pay`) work under an agent key: the human approves in the browser — note `invoice pay` needs **two** scopes, `invoice:all` to create, report and confirm the pay intent and `wallet:
|
|
209
|
+
- **Some actions stay human-only by design.** Two of them are not CLI commands at all — key management lives in the dashboard (see the Keys section) and payout authorization lives in Store Settings → Payout Authorization. The browser-bridge flows (`wallet send`, `invoice pay`) work under an agent key: the human approves in the browser — note `invoice pay` needs **two** scopes, `invoice:all` to create, report and confirm the pay intent and `wallet:all` to register the withdrawal (the same broker op as `wallet send`; it also covers the payer-address lookup, since `:all` implies `:read_only`). A bare `device-login` starts every category OFF: request BOTH `--scopes invoice:all --scopes wallet:all` to preselect them, or turn on both Allow-changes switches on the approval screen. With `otp-login`, pass the same two `--scopes` values: it requests only explicitly supplied scopes, has no implicit scope defaults, and the final grants are clipped to your account's role.
|
|
165
210
|
- **Password login is not a public flow.** AllScale Pay Accounts are passkey-only and have no usable password; `device-login` and `otp-login` are the two ways in. Accounts specially provisioned with a password authenticate through an internal-only flow that mints the same scoped, expiring agent key (`--scopes` required, no full-permission default).
|
|
166
211
|
|
|
167
212
|
### Discovery
|
|
@@ -185,13 +230,13 @@ The CLI's user-facing surface matches the AllScale web app: what users call an *
|
|
|
185
230
|
| `invoice get <id>` | Fetch one invoice by id. Wraps `payment`. |
|
|
186
231
|
| `invoice sent` | Invoices where **you are the payer** — your business owes the money. Wraps `sender_payments` (the backend's `sender` is the payer). Invoices you issued with `invoice send` are **not** here; see `invoice received`. Same pagination defaults and the same `--status` / `--payment-type` / `--from` / `--to` filters as `invoice list`. **No `--to-email`**: an invoice's contact record belongs to the business that issued it, so your own contacts never appear on the invoices you owe — the flag is refused here with `input.invalid` rather than answering with a misleading empty page. |
|
|
187
232
|
| `invoice received` | Invoices where **you are the payee** — the money is owed to your business. Wraps `recipient_payments` (the backend's `recipient` is the payee). **Includes everything you issued with `invoice send`.** Same pagination defaults and the same `--status` / `--payment-type` / `--from` / `--to` / `--to-email` filters as `invoice list`; this is the command `--to-email` fits exactly, since every row here is one you issued. |
|
|
188
|
-
| `invoice send` | Issue an invoice by email. Resolves `--to-email` to a contact (auto-creates via `--auto-create-contact` if missing), renders the invoice HTML body locally, and submits `create_payment` through the authenticated session. The recipient does **not** need any prior relationship with you — any valid email can be invoiced — but auto-creating its contact record needs `contact:all` on an agent key
|
|
233
|
+
| `invoice send` | Issue an invoice by email. Resolves `--to-email` to a contact (auto-creates via `--auto-create-contact` if missing), renders the invoice HTML body locally, and submits `create_payment` through the authenticated session. The recipient does **not** need any prior relationship with you — any valid email can be invoiced — but auto-creating its contact record needs `contact:all` on an agent key. The full send also needs `invoice:all`, plus `wallet:read_only` when you omit `--wallet-id`. A bare `device-login` starts every category OFF and its new key replaces the old one, so select Contacts write and Invoices Allow changes plus Wallets read (unless using `--wallet-id`) in the same re-login; the CLI detects a key that can't create contacts and fails fast with this complete remediation instead of relaying the backend's scope refusal. USDT/USDC totals below 0.10 are rejected before contact or wallet lookup, and `--payment-type` accepts only its documented values (0 fiat / 1 USDT / 2 USDC) — anything else is rejected locally as `input.invalid` (exit 2) and never sent. Pass `--wallet-id` to pin receiving wallets (backend-resolved — works with an invoice-scope-only key), or omit it to auto-select from your wallets via the narrow `business_wallets` query. **No browser ceremony** — the payer signs when paying; the inviter doesn't sign at issue. |
|
|
189
234
|
| `invoice update <id>` | Update an existing invoice. Wraps `update_payment`. |
|
|
190
|
-
| `invoice pay <id>` | Pay an invoice where **you are the payer**, from your AllScale wallet — list them with `invoice sent`, NOT `invoice received`. Resolves the invoice's destination + amount, moves the funds via the same signing path as `wallet send` (the transfer is 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:
|
|
235
|
+
| `invoice pay <id>` | Pay an invoice where **you are the payer**, from your AllScale wallet — list them with `invoice sent`, NOT `invoice received`. Resolves the invoice's destination + amount, moves the funds via the same signing path as `wallet send` (the transfer is 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:all` to register the withdrawal (the same `withdraw_token` broker op as `wallet send`; it also covers the payer-address lookup via the narrow `business_wallets` query). A bare `device-login` starts every category OFF: request BOTH `--scopes invoice:all --scopes wallet:all` to preselect them, or turn on both Allow-changes switches on the approval screen. On `otp-login`, pass the same two values: it requests only explicitly supplied scopes, has no implicit scope defaults, and the final grants are clipped to your account's role. |
|
|
191
236
|
| `transaction list` | Paginated transactions. `--scope mine\|business\|activities`. Wraps `my_transactions` / `business_transaction_records` / `activities`. Defaults to the first 50 rows; pass `--input` (mine/business) or `--limit N` (activities) to override, or `--all` to request one unpaginated response (still capped at 32 MiB by the CLI). Filter by time with `--from` / `--to` in every scope — the same **half-open** range as `invoice list` (`--from` inclusive, `--to` exclusive, same accepted datetime spellings), applied to `created_at` under `mine`/`business` and to `transaction_time` under `activities`. Under `--scope business`, additionally filter with `--direction` (`INFLOW`/`OUTFLOW`), `--transaction-type` (`PAYMENT`, `WITHDRAWAL`, `CLAIM_LINK`, …) and `--status` (`NEW`/`SUCCESS`/`FAILED`/`CONFIRMING` — transaction statuses, not invoice statuses); these map to `business_transaction_records`' typed GraphQL arguments, each accepting the enum name (case-insensitive) or a raw integer. The other scopes reject them with the typed alternative they do have (`activities` takes list-valued `directions` / `transaction_types` / `transaction_statuses` via `--input`). |
|
|
192
237
|
| `transaction get <id>` | Fetch one transaction by id. |
|
|
193
238
|
| `wallet list` | Your noncustodial wallets and per-coin balances via the narrow `business_wallets` query. Works with an agent key holding `wallet:read_only`; the owner is derived from the authenticated identity. |
|
|
194
|
-
| `wallet send` | Withdraw USDT/USDC from your AllScale wallet to an external EVM address. Registers a `withdraw_token` op with the broker, opens the browser at `/cli` for the passkey + Turnkey signing ceremony, and succeeds only after the browser confirms the on-chain receipt — the receipt-aware result comes back through the loopback listener (a mined revert exits `12`, an unconfirmable receipt exits `9`). The transfer is always confirmed in the browser; over SSH, open the printed URL on any device and paste the full result JSON back. |
|
|
239
|
+
| `wallet send` | Withdraw USDT/USDC from your AllScale wallet to an external EVM address. **Needs `wallet:all`** on an agent key — that scope is what the broker checks before registering the withdrawal (`wallet:read_only` alone cannot). Registers a `withdraw_token` op with the broker, opens the browser at `/cli` for the passkey + Turnkey signing ceremony, and succeeds only after the browser confirms the on-chain receipt — the receipt-aware result comes back through the loopback listener (a mined revert exits `12`, an unconfirmable receipt exits `9`). The transfer is always confirmed in the browser; over SSH, open the printed URL on any device and paste the full result JSON back. |
|
|
195
240
|
|
|
196
241
|
### Claim links
|
|
197
242
|
|
|
@@ -205,7 +250,7 @@ Sender-side reads (`get` / `list`) need a CLI login and `claim_link:read_only` u
|
|
|
205
250
|
| `claim-link get <id>` | Fetch one of your own Claim Links by id. Wraps `claim_link`; a non-owned id is indistinguishable from an unknown one. Agent-key scope: `claim_link:read_only`. |
|
|
206
251
|
| `claim-link list` | Your own Claim Links, newest first. Wraps `my_claim_links`. Defaults to the first 50 rows; `--input` paginates and `--all` requests one unpaginated response (still capped at 32 MiB by the CLI); `--status` filters. Agent-key scope: `claim_link:read_only`. |
|
|
207
252
|
| `claim-link status` | Public receiver-facing status snapshot for a bearer claim token/URL. Canonical URLs are parsed locally; a same-origin `/s/` URL is resolved through one credential-free 302. No login — the token is the credential. |
|
|
208
|
-
| `claim-link claim` | Claim a link from a raw token, canonical URL, or same-origin `/s/` short URL: Path A without AllScale account sign-in with `--to <0x address>` (no login), or `--to-wallet` into the authenticated business's AllScale wallet (cookie session). This does not make the transaction anonymous: network, request, compliance, and public blockchain data may still be processed or observable. Only the on-chain-proven `claimed` outcome exits 0;
|
|
253
|
+
| `claim-link claim` | Claim a link from a raw token, canonical URL, or same-origin `/s/` short URL: Path A without AllScale account sign-in with `--to <0x address>` (no login), or `--to-wallet` into the authenticated business's AllScale wallet (cookie session). This does not make the transaction anonymous: network, request, compliance, and public blockchain data may still be processed or observable. Only the on-chain-proven `claimed` outcome exits 0; an expired or not-claimable link exits 12, a token the backend will not resolve at all exits 7 (404), while pending/unproven payouts exit 9 and must be reconciled before another claim. |
|
|
209
254
|
|
|
210
255
|
### Store
|
|
211
256
|
|
|
@@ -238,6 +283,7 @@ The CLI mints a key for its own session when you log in (`device-login` / `otp-l
|
|
|
238
283
|
|
|
239
284
|
- **Tokens**: stored in the OS keychain (macOS Keychain / Windows Credential Manager / libsecret) by default. On POSIX, pass `--insecure-storage` (or set `ALLSCALE_INSECURE_STORAGE=1`) to **force** a plaintext file at `~/.allscale/credentials.json` (mode 0600) instead of the keychain — it takes precedence and is intended for headless / CI / agent use where a keychain is unavailable. Plaintext credential storage is not supported on Windows in the current implementation because Node's POSIX mode APIs cannot reliably verify owner-only ACL/link safety; use Windows Credential Manager instead. Without the opt-in, the CLI is designed to return an error rather than silently downgrade when the keychain is unavailable. `ALLSCALE_NO_KEYCHAIN=1` makes the CLI treat the keychain as unavailable at runtime and is designed not to probe it.
|
|
240
285
|
- **Store credentials**: `store create` returns the one-time secret on stdout and does not persist it. Historical credentials in the OS keychain or `~/.allscale/store-credentials.json` are still removed by `logout`; the current implementation does not write them to config.toml.
|
|
286
|
+
- **Logout confirmation**: a bare `allscale logout` at an interactive terminal states what it will delete and asks for confirmation first; declining exits 12 (`user.cancelled`) with nothing removed. The prompt is bare-invocation only — any flag (including `--yes`) skips it — and never appears for `--json`, a pipe, a sidecar, ordinary non-tty CI, or a redirected stderr. Automation that allocates a pty for all three streams *and* runs the command bare is indistinguishable from a human and is prompted; pass `--yes`. Unlike `device-login`, whose prompt shipped with the command, `logout`'s bare form predates this gate, so an existing pty-allocating job that relied on it wiping unattended must add `--yes` (without stdin it now declines and exits 12, having removed nothing).
|
|
241
287
|
- **Logout storage scope**: `allscale logout` with no insecure-storage flag or environment opt-in requests a full sweep of both plaintext credential files and the OS keychain. If keychain access is suppressed, missing, or broken, it still clears the files but exits non-zero because historical keychain credentials may remain. `allscale logout --insecure-storage` (or `ALLSCALE_INSECURE_STORAGE=1`) is designed as file-only and does not intentionally probe the keychain, to avoid GUI prompts for headless/CI agents. On Windows, an absent plaintext target is treated as a no-op; logout can remove a valid legacy file when the requested profile is its only remaining profile. A shared, malformed, or unverifiable legacy file is expected to remain unchanged and requires deliberate manual cleanup.
|
|
242
288
|
- **Profile metadata**: `~/.allscale/config.toml` (mode 0600) records `api_base` and `user_type` per profile. Non-secret.
|
|
243
289
|
- **Profiles pin their backend.** Logging in saves the backend it used into `~/.allscale/config.toml`, and a saved profile beats the built-in default. The CLI does **not** switch a configured endpoint automatically: stored credentials are bound to the origin used at login.
|
|
@@ -313,12 +359,12 @@ exactly one of them, and a mapping is never renumbered once shipped.
|
|
|
313
359
|
| `0` | success | — |
|
|
314
360
|
| `1` | generic / unexpected | `internal` |
|
|
315
361
|
| `2` | caller-actionable input or local limit — fix the invocation and re-run | `input.invalid`, `storage.unavailable`, `transport.response_too_large`, `auth.unknown_profile`, `claim_link.intent_conflict`, `signing.key_unavailable` |
|
|
316
|
-
| `3` | network / transport failure (**see the retry warning below**) | `transport.network`, `transport.timeout`, `auth.credential_changed`, `wallet.bridge_timeout`, `wallet.bridge_op_expired` |
|
|
362
|
+
| `3` | network / transport failure, or a confirmation deadline passed (**see the retry warning below**) | `transport.network`, `transport.timeout`, `auth.credential_changed`, `wallet.bridge_timeout`, `wallet.bridge_op_expired`, `auth.device_pairing_timeout` |
|
|
317
363
|
| `4` | not authenticated — run `allscale device-login` | `auth.no_token` |
|
|
318
|
-
| `5` | credential expired — re-authenticate | `auth.token_expired` |
|
|
364
|
+
| `5` | credential expired or malformed — re-authenticate | `auth.token_expired`, `auth.token_invalid` |
|
|
319
365
|
| `6` | not fixable by this invocation: permission denied, or a capability gap | `auth.permission_denied`, `wallet.requires_browser_auth` |
|
|
320
366
|
| `7` | not found — the object you named does not exist | `not_found` |
|
|
321
|
-
| `8` | rate limited | `rate_limited` |
|
|
367
|
+
| `8` | rate limited, or a payout create for the same `reference_id` still in flight — **the two share this code**, so read `details` before deciding whether to retry or to poll | `rate_limited` |
|
|
322
368
|
| `9` | backend internal, or an **ambiguous** mutation or transaction result | `backend.internal`, `claim.payout_ambiguous`, `claim_link.funding_ambiguous`, `wallet.bridge_invalid_response`, `wallet.transaction_status_unknown` |
|
|
323
369
|
| `10` | escape hatch disabled — export `ALLSCALE_ALLOW_RAW=1` | `raw.disabled` |
|
|
324
370
|
| `11` | this build's request signature was rejected — **upgrade the CLI** (re-login does not help) | `auth.signature_rejected` |
|
|
@@ -400,6 +446,40 @@ exit `5`, but that comes from a real `401`, not from translating a backend
|
|
|
400
446
|
"not found" code — so do not branch on exit 5 for these conditions on a
|
|
401
447
|
GraphQL call.)
|
|
402
448
|
|
|
449
|
+
`transaction get` applies the same local rule: an id that is not 24 hexadecimal
|
|
450
|
+
characters is `input.invalid` (exit `2`) and never sent, so a typo cannot come
|
|
451
|
+
back as GraphQL transport text. A well-formed id the backend will not return is
|
|
452
|
+
a normal `not_found` (exit `7`).
|
|
453
|
+
|
|
454
|
+
Claim links follow the same rule on the **sender** side: `claim-link get` exits
|
|
455
|
+
`7` for a **syntactically valid** id (24 hex characters) that the backend will
|
|
456
|
+
not return — unknown, or owned by another business. It answers those two
|
|
457
|
+
identically on purpose, so a link cannot be probed for existence. An id that is
|
|
458
|
+
not 24 hex characters is rejected locally as `input.invalid` (exit `2`) before
|
|
459
|
+
any request is sent, so a malformed argument never reads as a missing link.
|
|
460
|
+
|
|
461
|
+
`claim-link claim`, the **receiver** command, splits along the same seam — the
|
|
462
|
+
line is *"did the backend resolve the token"*, not sender-versus-receiver:
|
|
463
|
+
|
|
464
|
+
- a token that is empty, or not 16–256 characters of `[A-Za-z0-9_-]`, is
|
|
465
|
+
rejected **locally** as `input.invalid` (exit `2`) and never sent, exactly as a
|
|
466
|
+
malformed id is on `claim-link get`;
|
|
467
|
+
- a **syntactically valid** token that reaches the backend and resolves to no
|
|
468
|
+
link the caller may see is a `404` and exits **`7`** — the same code, and the
|
|
469
|
+
same anti-oracle reasoning, as the sender side;
|
|
470
|
+
- only a link that *did* resolve and whose claim attempt was then refused —
|
|
471
|
+
`expired` or `not_claimable`, returned as HTTP `200` with an `outcome` — exits
|
|
472
|
+
**`12`**.
|
|
473
|
+
|
|
474
|
+
So branch on `7` for "no such link" on either command, keep `2` for input you
|
|
475
|
+
got wrong, and reserve `12` for a real link you were not allowed to claim.
|
|
476
|
+
|
|
477
|
+
`claim-link status` reads the same token through the same public route, so it
|
|
478
|
+
answers the same way: a token that resolves to no link is a `404` and exits
|
|
479
|
+
**`7`**, carrying the same written explanation rather than the backend's raw
|
|
480
|
+
envelope. An expired or already-claimed link is not that error — it resolves
|
|
481
|
+
and comes back as an ordinary status snapshot.
|
|
482
|
+
|
|
403
483
|
**Under `--json`, stderr is parseable too — for `payout send` and
|
|
404
484
|
`device-login`.** On those, human progress prose is suppressed entirely and
|
|
405
485
|
every stderr line is one complete JSON object: the `{error}` envelope above is
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`).some(n=>
|
|
3
|
-
`)}var
|
|
4
|
-
`)}else re.stderr.write(n)}var
|
|
5
|
-
`);return}
|
|
6
|
-
`);return}
|
|
7
|
-
`)}catch(r){if(O=!1,f!==null){try{
|
|
8
|
-
`)}catch{}}}function
|
|
9
|
-
The CLI default is now ${
|
|
1
|
+
"use strict";var Qe=Object.create;var U=Object.defineProperty;var en=Object.getOwnPropertyDescriptor;var nn=Object.getOwnPropertyNames;var tn=Object.getPrototypeOf,rn=Object.prototype.hasOwnProperty;var on=(e,n)=>{for(var t in n)U(e,t,{get:n[t],enumerable:!0})},he=(e,n,t,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let i of nn(n))!rn.call(e,i)&&i!==t&&U(e,i,{get:()=>n[i],enumerable:!(r=en(n,i))||r.enumerable});return e};var g=(e,n,t)=>(t=e!=null?Qe(tn(e)):{},he(n||!e||!e.__esModule?U(t,"default",{value:e,enumerable:!0}):t,e)),sn=e=>he(U({},"__esModule",{value:!0}),e);var ht={};on(ht,{default:()=>M});module.exports=sn(ht);var h=require("@oclif/core");var _e="public",Ae="https://app.allscale.io";var me=["https://app.allscale.io"];var j="Override the API base URL (public builds accept only https://app.allscale.io)";var H="Only https://app.allscale.io is allowed, and URL userinfo is forbidden.";function Ee(e){try{let n=new URL(e);return n.username.length>0||n.password.length>0?null:n.protocol==="https:"?n:null}catch{return null}}function be(e){return Ee(e)!==null}function q(e){let n=Ee(e);return n!==null&&me.includes(n.origin)}function b(){return"".trim()||void 0}function _(){return"public"}function S(e){try{let{hostname:n}=new URL(e);return n==="127.0.0.1"||n==="localhost"||n==="[::1]"}catch{return!1}}function z(e){try{return new URL(e).origin}catch{return null}}function L(){{let e="[]".trim();if(e.length===0)return[];try{let n=JSON.parse(e);return Array.isArray(n)?n:[]}catch{return[]}}return W()}function W(){let e=process.env.ALLSCALE_BUILD_PAIRS?.trim()??"";if(e.length>0)try{let r=JSON.parse(e);if(!Array.isArray(r))return[];let i=[];for(let o of r){if(typeof o!="object"||o===null)continue;let{base:s,key:a,keyId:u}=o;if(typeof s!="string"||s.trim().length===0||typeof a!="string"||a.trim().length===0)continue;let d=typeof u=="string"?u.trim():"";i.push({base:s.trim(),key:a,keyId:d.length>0?d:"cli-1"})}return i}catch{return[]}let n=process.env.ALLSCALE_SIGNING_KEY??"",t=process.env.ALLSCALE_BUILD_API_BASE?.trim()??"";if(n.trim().length>0&&t.length>0){let r=process.env.ALLSCALE_SIGNING_KEY_ID?.trim();return[{base:t,key:n,keyId:r&&r.length>0?r:"cli-1"}]}return[]}var an={"input.invalid":2,"storage.unavailable":2,"transport.network":3,"transport.timeout":3,"transport.response_too_large":2,"auth.no_token":4,"auth.unknown_profile":2,"auth.token_invalid":5,"auth.token_expired":5,"auth.device_pairing_timeout":3,"auth.credential_changed":3,"auth.permission_denied":6,"auth.signature_rejected":11,"claim.not_claimable":12,"claim.expired":12,"claim.payout_ambiguous":9,"claim_link.funding_ambiguous":9,"claim_link.intent_conflict":2,"claim_link.browser_cancelled":12,"user.cancelled":12,"signing.key_unavailable":2,not_found:7,rate_limited:8,"backend.internal":9,"wallet.requires_browser_auth":6,"wallet.bridge_timeout":3,"wallet.bridge_op_expired":3,"wallet.bridge_invalid_response":9,"wallet.transaction_reverted":12,"wallet.transaction_status_unknown":9,"raw.disabled":10,internal:1};var cn=new RegExp(["\\u001b\\[[0-9;:<=>?]*[ -/]*[@-~]","\\u009b[0-9;:<=>?]*[ -/]*[@-~]","\\u001b\\][^\\u0007\\u001b\\u009c]*(?:\\u0007|\\u001b\\\\|\\u009c)","\\u009d[^\\u0007\\u001b\\u009c]*(?:\\u0007|\\u001b\\\\|\\u009c)","\\u001b[ -/]+[0-~]","\\u001b[0-Z\\x5c-~]","\\u001b\\[[0-9;:<=>?]*[ -/]*","\\u009b[0-9;:<=>?]*[ -/]*","[\\u0007\\u001b\\u0090\\u0098\\u009b\\u009c\\u009d\\u009e\\u009f]"].join("|"),"g");function Z(e){return(typeof e=="string"?e:String(e??"")).replace(cn,"")}var ln=64;function J(e,n=new Map,t=0){if(typeof e=="string")return Z(e);if(e===null||typeof e!="object")return e;let r=n.get(e);if(r!==void 0)return r;if(t>=ln)return e;if(Array.isArray(e)){let s=[];n.set(e,s);for(let a of e)s.push(J(a,n,t+1));return s}let i=Object.getPrototypeOf(e);if(i!==Object.prototype&&i!==null)return e;let o={};n.set(e,o);for(let[s,a]of Object.entries(e))Object.defineProperty(o,s,{value:J(a,n,t+1),writable:!0,enumerable:!0,configurable:!0});return o}var l=class extends Error{code;details;constructor(n,t,r){super(Z(t)),this.code=n,this.details=r&&J(r),this.name="AllscaleError"}get exitCode(){return an[this.code]}toJSON(){return{error:{code:this.code,message:this.message,...this.details?{details:this.details}:{}}}}};function Le(e){if(e instanceof l)return e;if(e instanceof Error){if(pn(e)){let n=gn(e),t={};return e.name&&e.name!=="Error"&&(t.name=e.name),Object.keys(n).length>0&&(t.oclif=n),new l("input.invalid",e.message,Object.keys(t).length>0?t:void 0)}return new l("internal",e.message,{name:e.name})}return new l("internal","Unknown error",{value:String(e)})}var un=new Set(["CLIError","CLIParseError","RequiredArgsError","RequiredFlagError","UnexpectedArgsError","FlagInvalidOptionError","ArgInvalidOptionError","InvalidArgsSpecError"]),dn=[/^Missing \d+ required arg/i,/^Missing required flag/i,/^Unexpected argument/i,/^Nonexistent flag/i,/^Unexpected arguments?/i,/^Failed parsing/i,/^Parsing --[A-Za-z0-9][A-Za-z0-9-]*(?:\s|$)/i,/^Expected (?:--|-).*/i,/^Flag --[A-Za-z0-9][A-Za-z0-9-]* can only be specified once\.?$/i,/^Flag --[A-Za-z0-9][A-Za-z0-9-]* expects a value$/i,/^Flag --[A-Za-z0-9][A-Za-z0-9-]* expects one of these values: /i,/to be one of: /];function pn(e){if(fn(e))return!0;let n=e.name??"";return un.has(n)?n==="CLIError"?V(e,"parse")||Se(e.message):!0:Se(e.message)}function fn(e){if(!V(e,"oclif"))return!1;let n=e.oclif;return typeof n=="object"&&n!==null&&n.exit===2&&V(e,"parse")}function Se(e){return e?Z(e).split(`
|
|
2
|
+
`).some(n=>dn.some(t=>t.test(n.trim()))):!1}function V(e,n){return typeof e=="object"&&e!==null&&Object.prototype.hasOwnProperty.call(e,n)}function gn(e){let n=e,t={};for(let r of["code","exitCode","suggestions","ref"])n[r]!==void 0&&(t[r]=n[r]);return t}function ye(e){process.stdout.write(`${JSON.stringify({data:e},null,2)}
|
|
3
|
+
`)}var c=g(require("fs")),G=g(require("path")),xe=g(require("crypto"));var hn="...<truncated>",_n=/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]+/g,An=/\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi,te=String.raw`(?:(?:Bearer|Basic)\s+)?(?:"(?:\\.|[^"\\\r\n])*"|'(?:\\.|[^'\\\r\n])*'|[^\s,;)}\]]+)`,X=String.raw`(?:proxy[_-]?)?authorization`,mn=new RegExp(String.raw`(^|[^A-Za-z0-9])((?:"${X}"|'${X}'|${X})\s*[:=]\s*)((?:Bearer|Basic)\s+)?(?!<redacted(?:-jwt)?>)${te}`,"gi"),En=/\b(https?:\/\/)[^\s/?#]+@/gi,bn=String.raw`(?:[A-Za-z0-9]+[_-])*`,Sn=String.raw`(?:[A-Za-z0-9]{1,32}[_-]){0,8}`,we=e=>String.raw`(?:password|passwd|passphrase|otp(?:[_-]?code)?|signature|hmac|sig|session[_-]?id|mnemonic|seed|cookie|bearer|${e}(?:token|secret)|(?:api|access|private|signing)[_-]?key|credential|claim[_-]?url)`,Ie=we(bn),Ln=we(Sn),yn=new RegExp(String.raw`(^|[^A-Za-z0-9])((?:"${Ie}"|'${Ie}'|${Ln})\s*[:=]\s*)(?!<redacted(?:-jwt)?>)${te}`,"gi"),Q=String.raw`(?:[A-Za-z0-9]+(?:Token|Secret)|[Oo][Tt][Pp]Code|[Ss]essionId|(?:api|API|access|Access|private|Private|signing|Signing)Key|[Cc]laimUrl)`,In=new RegExp(String.raw`(^|[^A-Za-z0-9])((?:"${Q}"|'${Q}'|${Q})\s*[:=]\s*)(?!<redacted(?:-jwt)?>)${te}`,"g");function Ce(e){return e.replace(En,"$1<redacted>@").replace(_n,"<redacted-jwt>").replace(mn,"$1$2$3<redacted>").replace(yn,"$1$2<redacted>").replace(In,"$1$2<redacted>").replace(An,"Bearer <redacted>")}var wn=/(token|secret|password|passwd|passphrase|cookie|authorization|bearer|credential|mnemonic|seed|api[_-]?key|apikey|private[_-]?key|privkey|otp|signature|hmac|(?:^|[_-])sig(?:[_-]|$)|session[_-]?id|^input$)/i;function Te(e){let n=e.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2");return wn.test(n)}function Oe(e){let n="";for(let t of e){let r=t.codePointAt(0),i=r<=8||r>=11&&r<=12||r>=14&&r<=31||r>=127&&r<=159;n+=i?`\\u${r.toString(16).padStart(4,"0")}`:t}return n}function ve(e,n,t=hn){if(n<=0)return"";if(Buffer.byteLength(e,"utf8")<=n)return e;let r=Buffer.byteLength(t,"utf8");if(r>=n){let a="",u=0;for(let d of t){let E=Buffer.byteLength(d,"utf8");if(u+E>n)break;a+=d,u+=E}return a}let i=n-r,o="",s=0;for(let a of e){let u=Buffer.byteLength(a,"utf8");if(s+u>i)break;o+=a,s+=u}return`${o}${t}`}function p(e,n=16384){return ve(Oe(Ce(e)),n)}var ee=100,Cn=8,Tn=8*1024;function $(){return{remaining:Tn}}function ne(e,n){let t=Math.min(16384,n.remaining),r=ve(e,t);return n.remaining-=Buffer.byteLength(r,"utf8"),r}function I(e,n,t=0,r={}){if(t>Cn)return ne("<truncated>",n);if(typeof e=="string"){let i=Ce(e);return ne(r.escapeControls?Oe(i):i,n)}if(Array.isArray(e)){let i=e.slice(0,ee),o=i.map(s=>I(s,n,t+1,r));return e.length>i.length&&o.push(ne(`<${e.length-i.length} more items truncated>`,n)),o}if(e!==null&&typeof e=="object"){let i=Object.create(null),o=Object.entries(e);for(let[s,a]of o.slice(0,ee))i[s]=I(a,n,t+1,r);return o.length>ee&&(i.output_truncated=!0),i}return e}var re=require("process");var ie=!1;function oe(e){ie=e}function Ne(){return ie}var Re="1";function C(e,n){if(ie){let t={...e};delete t.version;let r=$(),i={event:t.event,...t},o=I(i,r,0,{escapeControls:!0}),s=JSON.stringify({version:Re,...o});Buffer.byteLength(s,"utf8")+1>65536&&(s=JSON.stringify({version:Re,event:o.event,output_truncated:!0})),re.stderr.write(`${s}
|
|
4
|
+
`)}else re.stderr.write(n)}var ke="2",T=null,f=null,O=!1,N=!1;function On(){let e=process.env.ALLSCALE_OUTPUT_FILE_PATH;if(e)return e;let n=process.env.ALLSCALE_OUTPUT_FILE_DIRECTORY;if(n){let t=new Date().toISOString().replace(/[:.]/g,"-"),r=xe.randomBytes(4).toString("hex");return G.join(n,`allscale-output-${t}-${r}.ndjson`)}return null}var se=64*1024*1024,vn=null;function ae(e={}){if(e.quiet&&(N=!0),O)return;let n=On();if(!n)return;T=null;let t=null;try{if((vn??process.platform)==="win32")throw new Error("owner-only sidecar ACLs cannot be enforced on Windows; sidecar output is disabled");let r=G.dirname(n);if(c.mkdirSync(r,{recursive:!0,mode:448}),!process.env.ALLSCALE_OUTPUT_FILE_PATH&&process.env.ALLSCALE_OUTPUT_FILE_DIRECTORY){let u=c.statSync(r),d=u.mode&511;if(!u.isDirectory()||(d&63)!==0)throw new Error(`output directory must be owner-only (no group/other permissions); current mode is ${d.toString(8).padStart(3,"0")}`)}let i=c.constants.O_NOFOLLOW??0;t=c.openSync(n,c.constants.O_APPEND|c.constants.O_CREAT|c.constants.O_WRONLY|(c.constants.O_NONBLOCK??0)|i,384);let o=c.fstatSync(t),s=c.lstatSync(n);if(!o.isFile()||s.isSymbolicLink()||s.dev!==o.dev||s.ino!==o.ino)throw new Error("target changed while it was being opened or is not a regular file");if(typeof process.geteuid=="function"&&o.uid!==process.geteuid())throw new Error("existing sidecar file is not owned by the current effective user");let a=o.mode&511;if((a&63)!==0)throw new Error(`existing sidecar file must already be owner-only; current mode is ${a.toString(8).padStart(3,"0")}. Rotate or remove it before retrying`);c.fchmodSync(t,384)}catch(r){if(t!==null)try{c.closeSync(t)}catch{}N||C({event:"sidecar_disabled",reason:"open_failed",detail:p(`sidecar path '${n}' could not be opened safely (${r.message})`)},`${p(`[allscale] sidecar path '${n}' could not be opened safely (${r.message}); refusing to write. Sidecar disabled for this run.`)}
|
|
5
|
+
`);return}T=n,f=t,O=!0}var Rn=[/password/i,/secret/i,/token/i,/api[_-]?key/i,/signing[_-]?key/i,/private[_-]?key/i,/credential/i,/otp/i,/signature/i,/(?:^|[_-])sig(?:[_-]|$)/i,/hmac/i,/session[_-]?id/i,/nonce/i,/bearer/i,/authorization/i,/^code$/i,/^auth$/i,/^state$/i];function Nn(e){let n=e.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2");return Rn.some(t=>t.test(n))}function kn(e){try{let n=new URL(e),t=n.username.length>0||n.password.length>0;t&&(n.username="",n.password="");let r=n.pathname;n.pathname=xn(n.pathname);let i=t;n.pathname!==r&&(i=!0);for(let[o]of n.searchParams)Nn(o)&&(n.searchParams.set(o,"<redacted>"),i=!0);return i?n.toString():e}catch{return e}}function xn(e){return e.replace(/(\/(?:api\/)?(?:v1\/)?public\/claim-links\/)([^/:]+)(:claim)?(?=\/?$)/,"$1<redacted>$3").replace(/(\/(?:api\/)?public\/claim-links\/)([^/]+)(\/claim)(?=\/?$)/,"$1<redacted>$3")}function Pn(e){return(e.type==="request"||e.type==="bridge_browser_open")&&typeof e.url=="string"?{...e,url:kn(e.url)}:e}function Bn(e){let n=new Date().toISOString(),t=e,{type:r,...i}=t,o=I(i,$()),s=JSON.stringify({version:ke,timestamp:n,type:r,...o});return Buffer.byteLength(s,"utf8")+1>65536&&(s=JSON.stringify({version:ke,timestamp:n,type:r,output_truncated:!0})),s}function v(e){if(!O||!T||f===null)return;let n=Pn(e),t=Bn(n);try{if(c.fstatSync(f).size+Buffer.byteLength(t,"utf8")+1>se){O=!1;try{c.closeSync(f)}catch{}f=null,N||C({event:"sidecar_disabled",reason:"size_limit",...T===null?{}:{path:p(T)},max_bytes:se},`${p(`[allscale] sidecar exceeded ${se} bytes; disabling it for the rest of this run. Rotate or remove ${T}, or point ALLSCALE_OUTPUT_FILE_DIRECTORY at a directory so each run gets its own file.`)}
|
|
6
|
+
`);return}c.appendFileSync(f,`${t}
|
|
7
|
+
`)}catch(r){if(O=!1,f!==null){try{c.closeSync(f)}catch{}f=null}try{N||C({event:"sidecar_disabled",reason:"write_failed",detail:p(r.message)},`${p(`[allscale] sidecar write failed (${r.message}); disabling sidecar for the rest of this run.`)}
|
|
8
|
+
`)}catch{}}}function Pe(){return!!(process.env.ALLSCALE_OUTPUT_FILE_PATH||process.env.ALLSCALE_OUTPUT_FILE_DIRECTORY)}function k(){if(N=!1,O=!1,f!==null){try{c.closeSync(f)}catch{}f=null}}function Be(e=!1){let n=process.stdin.isTTY===!0&&process.stdout.isTTY===!0;return e||Pe()||!n}var x=new Set(["--password","-p","--token","-t","--otp","--otp-id","--api-base","--payout-api-base","--browser-base","--input","-i","--claim-token","--claim-url","--api-key","--api-secret"]),Dn=new Set(Array.from(x).filter(e=>e.startsWith("-")&&!e.startsWith("--")&&e.length===2)),Un=Array.from(x).filter(e=>e.startsWith("--")).sort((e,n)=>n.length-e.length),$n=new Set(["--otp-stdin","--password-stdin"]);function ce(e){let n=[];for(let t=0;t<e.length;t+=1){let r=e[t];if(r.startsWith("-")){let o=r.indexOf("=");if(o!==-1){let s=r.slice(0,o);if(x.has(s)){n.push(`${s}=<redacted>`);continue}}}if(x.has(r)){n.push(r),t+1<e.length&&(x.has(e[t+1])||(n.push("<redacted>"),t+=1));continue}if(r.startsWith("-")&&!r.startsWith("--")&&r.length>2&&r[1]!=="="){let o=r.slice(0,2);if(Dn.has(o)){n.push(`${o}<redacted>`);continue}}let i=$n.has(r)?void 0:Un.find(o=>r.length>o.length&&r.startsWith(o));if(i){n.push(`${i}<redacted>`);continue}n.push(r)}return n}function De(e){v({type:"error",code:e.code,message:p(e.message)}),v({type:"command-end",ok:!1,exitCode:e.exitCode}),k()}var P=b()??Ae,B={TOKEN:"ALLSCALE_TOKEN",PROFILE:"ALLSCALE_PROFILE",API_BASE:"ALLSCALE_API_BASE",PAYOUT_API_BASE:"ALLSCALE_PAYOUT_API_BASE",PRETTY:"ALLSCALE_PRETTY",LOG_LEVEL:"ALLSCALE_LOG_LEVEL",INSECURE_STORAGE:"ALLSCALE_INSECURE_STORAGE",NO_KEYCHAIN:"ALLSCALE_NO_KEYCHAIN",ALLOW_RAW:"ALLSCALE_ALLOW_RAW",OUTPUT_FILE_PATH:"ALLSCALE_OUTPUT_FILE_PATH",OUTPUT_FILE_DIRECTORY:"ALLSCALE_OUTPUT_FILE_DIRECTORY",STORE_API_KEY:"ALLSCALE_STORE_API_KEY",STORE_API_SECRET:"ALLSCALE_STORE_API_SECRET"};function le(e){return be(e)?new URL(e).origin:null}function A(e){try{let n=new URL(e);return`${n.protocol}//${n.host}`}catch{return"<unparseable URL>"}}function Ue(){let e=process.env[B.TOKEN]?.trim();return e&&e.length>0?e:void 0}function ue(){let e=process.env[B.PROFILE]?.trim();return e&&e.length>0?e:void 0}function D(){let e=process.env[B.API_BASE]?.trim();return e&&e.length>0?e:void 0}function $e(){let e=process.env[B.INSECURE_STORAGE]?.trim().toLowerCase();return e==="1"||e==="true"||e==="yes"}function Ge(){let e=process.env[B.ALLOW_RAW]?.trim().toLowerCase();return e==="1"||e==="true"||e==="yes"}function Fe(e){let{resolvedApiBase:n,fromExplicitOverride:t,profileApiBase:r,profileApiBaseSource:i}=e;if(b()===void 0&&P!==""&&"".length!==0&&!t&&i!=="explicit"&&r===""&&n==="")return`Notice: this profile is pinned to ${""} (staging) in ~/.allscale/config.toml.
|
|
9
|
+
The CLI default is now ${P} (production), but a saved profile wins over the default,
|
|
10
10
|
so this session still uses staging. That pin was written before the default changed, so it may just be
|
|
11
11
|
the old default rather than a deliberate choice.
|
|
12
12
|
To keep staging, re-run \`allscale device-login\` with --api-base ${""} (recorded as deliberate, silences this).
|
|
13
|
-
To move to production, run: allscale device-login --api-base ${
|
|
14
|
-
`}var
|
|
15
|
-
`)}};var
|
|
13
|
+
To move to production, run: allscale device-login --api-base ${P}
|
|
14
|
+
`}var y=g(require("fs")),Ye=g(require("os")),de=g(require("path")),pe=g(require("@iarna/toml"));var Yn=".allscale",Mn="config.toml";function Kn(){return de.join(Ye.homedir(),Yn)}function jn(){return de.join(Kn(),Mn)}function Me(){let e=jn();if(!y.existsSync(e))return{};let n=y.readFileSync(e,"utf-8");try{return pe.parse(n)}catch(t){throw new l("internal",`Failed to parse ${e}: ${t.message}`)}}function Ke(e,n){return e.profile?.[n]}function je(e,n,t){let r=n??t??e.default??"default";if(r.includes(":"))throw new l("input.invalid",`Invalid profile name '${r}': ':' is reserved as the keychain namespace separator \u2014 pick a name without ':'.`);return r}function fe(e){if(!q(e))throw new l("input.invalid",`Refusing to send credentials to API base ${A(e)}. `+H)}function He(e){let n=qn(e);if(n)throw new l("input.invalid",n.message)}function Hn(){let e=b();if(e===void 0)return;let n=L();return n.length>0?{endpoints:n.map(t=>t.base),fromPairs:!0}:{endpoints:[e],fromPairs:!1}}function qn(e){let n=Hn();if(n===void 0||S(e))return;let{endpoints:t,fromPairs:r}=n,i=le(e),o=t.map(a=>le(a)).filter(a=>a!==null);if(i!==null&&o.includes(i))return;let s=t.map(a=>A(a)).join(", ");return{message:r?`Refusing to send to ${A(e)} \u2014 this build only signs for: ${s}. Re-login against one of this build's environments, or use a build that includes the one you need.`:`Refusing to send to ${A(e)} \u2014 this build only signs for ${s}. Re-login against this build's environment, or use a build for that one.`}}var Wn=require("@noble/hashes/sha3");var qe=["USDT","USDC"];var tr=new Set([`0x${"0".repeat(40)}`,`0x${"0".repeat(36)}dead`]);var zn="unknown_otp_id",Jn="invalid_code",Vn="expired_code";function Zn(e){return`'${e.replace(/'/g,"'\\''")}'`}function We(e){return`Run \`allscale otp-send --email ${Zn(e)}\` first \u2014 that command does report a missing account \u2014 then retry with the otp_id it returns and a fresh code.`}var Xn=new Map([[zn,(e,n)=>`${e}: this environment never issued the --otp-id you passed for '${n}', so the verification code was never the problem. The backend decides this by comparing the otp_id against that address alone, so it says nothing about whether the account exists. ${We(n)}`],[Jn,e=>`${e}: the --otp-id is one this environment issued and a live code stands behind it \u2014 the verification code you passed is what does not match. Check for a typo \u2014 the backend invalidates the code after a few wrong answers, so if you are unsure, request a fresh code instead of guessing.`],[Vn,(e,n,t)=>t?`${e}: this run's send request was accepted moments ago, but this attempt could not claim a live code for the --otp-id. Delivery is asynchronous and the code is stored only after the email goes out, so it may still be in flight \u2014 or the send may have failed outright, in which case no code is coming. The code you submitted may also have expired, been used, run out of attempts, or been replaced by a newer send. Check the inbox, including spam, and retry with whatever code arrived. If nothing arrives, confirm the address and wait at least three minutes before requesting one fresh code \u2014 sends are rate-limited per address, and retrying in a tight loop spends that budget even when the send is rejected.`:`${e}: this attempt could not claim a live code for the --otp-id. The code you submitted may have expired, been used, run out of attempts, been replaced by a later send, or never have been requested for this address; the backend does not distinguish those. If a newer code was delivered after the one you entered, use that one. ${We(n)}`]]);var ze=[...Xn.keys()];var Je=require("@oclif/core"),m=class e extends h.Command{static enableJsonFlag=!0;static legacyStagingNoticeShown=!1;static emitsOwnOutput=!1;static requiresEscapeHatch=!1;static requiresInput=!1;static isLocalOnly=!1;static baseFlags={profile:h.Flags.string({description:"Named profile from ~/.allscale/config.toml",helpGroup:"GLOBAL"}),token:h.Flags.string({description:"Override the access token for this invocation (precedence: --token > ALLSCALE_TOKEN > --profile)",helpGroup:"GLOBAL",hidden:_()==="public"}),"api-base":h.Flags.string({description:j,helpGroup:"GLOBAL",hidden:_()==="public"}),"insecure-storage":h.Flags.boolean({description:"Force POSIX plaintext token storage at ~/.allscale/credentials.json (mode 0600) instead of the OS keychain \u2014 for headless / CI / agent use. Unavailable on Windows because Node cannot verify owner-only ACL/link safety. Also via ALLSCALE_INSECURE_STORAGE=1.",default:!1,helpGroup:"GLOBAL"})};async run(){try{oe(this.jsonEnabled()),ae(),v({type:"session-start",command:this.id??"",argv:ce(this.argv)});let n=this.constructor;if(_()==="public"){if(this.argv.some(o=>o==="--token"||o.startsWith("--token=")))throw new l("input.invalid","--token is not supported in this build. Set the ALLSCALE_TOKEN environment variable instead (it does not leak into shell history or process listings).");if(this.argv.some(o=>o==="--api-base"||o.startsWith("--api-base=")))throw new l("input.invalid","--api-base is not supported in this build; it always talks to its bound AllScale environment.");if(!n.isLocalOnly&&D()!==void 0)throw new l("input.invalid","ALLSCALE_API_BASE is not supported in this build; unset it \u2014 this build always talks to its bound AllScale environment.")}let t=Be(this.jsonEnabled()),r=this.argv.length===0&&!t&&at(n)?this.config.findCommand(this.id??""):void 0;if(r){k();let o=await(0,h.loadHelpClass)(this.config),s=this.config.pjson.oclif?.helpOptions??this.config.pjson.helpOptions;await new o(this.config,s).showCommandHelp(r,this.config.topics);return}if(n.requiresEscapeHatch&&!Ge())throw this.argv.length>0&&await this.parse(),new l("raw.disabled","This command is an opt-in escape hatch and is disabled by default. Set ALLSCALE_ALLOW_RAW=1 to enable raw GraphQL/REST passthroughs and schema introspection. Access remains governed by the key's granted scopes, which the backend checks on each request.");let i=await this.runCommand();return v({type:"command-end",ok:!0}),k(),n.emitsOwnOutput?void 0:(this.jsonEnabled()||ye(i),{data:i})}catch(n){let t=Le(n);De({code:t.code,message:t.message,exitCode:t.exitCode}),this.emitError(t),process.exitCode=t.exitCode;return}finally{oe(!1)}}resolveSessionApiBaseChain(n){let t=Me(),r=n.profile??void 0,i=n["api-base"]??void 0,o=r!==void 0||ue()!==void 0,s=je(t,r,ue()),a=Ke(t,s),u=_()==="public"&&D()!==void 0,d=i??(u?void 0:D()),E=d??a?.api_base??P,K=Fe({resolvedApiBase:E,fromExplicitOverride:d!==void 0,profileApiBase:a?.api_base,profileApiBaseSource:a?.api_base_source});return K&&!e.legacyStagingNoticeShown&&(e.legacyStagingNoticeShown=!0,C({event:"legacy_staging_profile",api_base:E,profile:s},K)),{apiBase:E,explicitApiBase:d,envApiBaseIgnored:u,profileApiBase:a?.api_base,profile:s,profileExplicit:o}}resolveSessionApiBaseForPayoutDerivation(n){let{apiBase:t}=this.resolveSessionApiBaseChain(n);return this.constructor.isLocalOnly||fe(t),t}resolveContext(n){if(_()==="public"){if(n.token!==void 0)throw new l("input.invalid","--token is not supported in this build. Set the ALLSCALE_TOKEN environment variable instead (it does not leak into shell history or process listings).");if(n["api-base"]!==void 0)throw new l("input.invalid","--api-base is not supported in this build; it always talks to its bound AllScale environment.");if(!this.constructor.isLocalOnly&&D()!==void 0)throw new l("input.invalid","ALLSCALE_API_BASE is not supported in this build; unset it \u2014 this build always talks to its bound AllScale environment.")}let t=n.token??void 0,r=!!n["insecure-storage"],{apiBase:i,explicitApiBase:o,envApiBaseIgnored:s,profile:a,profileApiBase:u,profileExplicit:d}=this.resolveSessionApiBaseChain(n);return this.constructor.isLocalOnly||(fe(i),He(i)),{apiBase:i,profile:a,token:t??Ue(),insecureStorage:r||$e(),apiBaseSource:o!==void 0?"explicit":u!==void 0?"profile":"default",profileExplicit:d,envApiBaseIgnored:s}}emitError(n){process.stderr.write(`${st(n)}
|
|
15
|
+
`)}};var Qn=/_(removed|cleared|count|total)$/i;function et(e,n){return typeof n=="number"&&Qn.test(e)}var nt=new Set(qe);function tt(e,n){return e==="token_symbol"&&typeof n=="string"&&nt.has(n)}var rt=new Set(ze);function it(e,n){return e==="otp_reason"&&typeof n=="string"&&rt.has(n)}var F=100;function Y(e,n=0){if(n>8)return"<truncated>";if(Array.isArray(e)){let t=e.slice(0,F),r=t.map(i=>Y(i,n+1));return e.length>t.length&&r.push(`<${e.length-t.length} more items truncated>`),r}if(e!==null&&typeof e=="object"){let t=Object.create(null),r=Object.entries(e);for(let[i,o]of r.slice(0,F))t[i]=Te(i)&&!et(i,o)&&!tt(i,o)&&!it(i,o)?"<redacted>":Y(o,n+1);if(r.length>F){let i="<truncated>";for(;i in t;)i=`<${i}>`;t[i]=`${r.length-F} more keys`}return t}if(typeof e=="string"){let t=ot(e);return t!==void 0?p(JSON.stringify(Y(t,n+1))):p(e)}return e}function ot(e){let n=e.trim();if(!n.startsWith("{")&&!n.startsWith("["))return;let t;try{t=JSON.parse(n)}catch{return}return t!==null&&typeof t=="object"?t:void 0}function st(e){let n=Ne()?void 0:2,t=Y(e.toJSON()),r=JSON.stringify(t,null,n);return Buffer.byteLength(r,"utf8")+1<=65536?r:JSON.stringify({error:{code:e.code,message:p(e.message),details:{output_truncated:!0,max_output_bytes:65536}}},null,n)}function at(e){if(e.requiresInput===!0)return!0;let n=i=>typeof i=="object"&&i!==null&&i.required===!0,t=e.flags??{},r=e.args??{};return Object.values(t).some(i=>n(i))||Object.values(r).some(i=>n(i))}var ct=g(require("crypto")),lt=g(require("fs")),ut=g(require("path"));var ge="cli-1",dt="aScaLe_cli_obf_v1",pt="WWoBD3QEZwECUTEuOD4dTgdWawQSKAQ8UlRaaw==";function ft(e,n){let t=Buffer.from(e,"base64"),r=Buffer.from(n,"utf8"),i=Buffer.alloc(t.length);for(let o=0;o<t.length;o++)i[o]=t[o]^r[o%r.length];return i.toString("utf8")}var gt=ft(pt,dt),yr=`Bearer ${gt}`;function Ve(e,n){let t=z(e);if(t!==null)return n.find(r=>z(r.base)===t)}function Ze(e){if("f17372513b233456b90621e793c1d0759bce34f8eec3415abc6c2f08270faef2".trim().length>0)return!0;let n=L();if(n.length>0){if(e===void 0||Ve(e,n))return!0;if(S(e)){let r=process.env.ALLSCALE_SIGNING_KEY;return r&&r.trim().length>0?!0:S(n[0].base)}return!1}let t=process.env.ALLSCALE_SIGNING_KEY;return!!(t&&t.trim().length>0)}function Xe(e){{let r="cli-1".trim();if(r.length>0)return r}let n=L();if(n.length>0){let r=e!==void 0?Ve(e,n):void 0;if(r)return r.keyId;if(e!==void 0&&S(e)){let i=process.env.ALLSCALE_SIGNING_KEY;if(i&&i.trim().length>0){let o=process.env.ALLSCALE_SIGNING_KEY_ID;return o!==void 0?o.trim():ge}if(!S(n[0].base))return ge}return n[0].keyId}let t=process.env.ALLSCALE_SIGNING_KEY_ID;return t!==void 0?t.trim():ge}var M=class e extends m{static isLocalOnly=!0;static description="Show which API base (if any) this build is bound to, and its signing-key id. Never prints the signing key itself \u2014 only its non-secret id.";static examples=["$ allscale build-info"];static flags={...m.baseFlags,profile:{...m.baseFlags.profile,hidden:!0},token:{...m.baseFlags.token,hidden:!0},"api-base":{...m.baseFlags["api-base"],hidden:!0}};async runCommand(){let{flags:n}=await this.parse(e),t=["profile","token","api-base"].filter(i=>n[i]!==void 0).map(i=>`--${i}`);if(t.length>0)throw new l("input.invalid",`${t.join(", ")} cannot be used with build-info. This command reports offline artifact metadata only and does not load or validate profiles, tokens, or API bases.`);let r=b();return{target:_(),api_base:r===void 0?null:A(r),bound:r!==void 0,signing_key_id:Ze()?Xe():null,endpoints:L().map(i=>({base:A(i.base),key_id:i.keyId}))}}};
|
|
16
16
|
//# sourceMappingURL=build-info.js.map
|