@allscale/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +482 -0
- package/bin/dev.js +11 -0
- package/bin/run-oclif.js +69 -0
- package/bin/run.js +6 -0
- package/dist/commands/build-info.js +16 -0
- package/dist/commands/claim-link/claim.js +17 -0
- package/dist/commands/claim-link/create.js +75 -0
- package/dist/commands/claim-link/get.js +18 -0
- package/dist/commands/claim-link/list.js +18 -0
- package/dist/commands/claim-link/preview.js +18 -0
- package/dist/commands/claim-link/status.js +17 -0
- package/dist/commands/describe.js +16 -0
- package/dist/commands/device-login.js +40 -0
- package/dist/commands/invoice/get.js +18 -0
- package/dist/commands/invoice/list.js +19 -0
- package/dist/commands/invoice/pay.js +66 -0
- package/dist/commands/invoice/received.js +19 -0
- package/dist/commands/invoice/send.js +167 -0
- package/dist/commands/invoice/sent.js +18 -0
- package/dist/commands/invoice/update.js +18 -0
- package/dist/commands/logout.js +17 -0
- package/dist/commands/operations.js +16 -0
- package/dist/commands/otp-login.js +23 -0
- package/dist/commands/otp-send.js +17 -0
- package/dist/commands/payout/send.js +23 -0
- package/dist/commands/payout/status.js +18 -0
- package/dist/commands/scope.js +22 -0
- package/dist/commands/store/create.js +28 -0
- package/dist/commands/transaction/get.js +18 -0
- package/dist/commands/transaction/list.js +18 -0
- package/dist/commands/wallet/list.js +18 -0
- package/dist/commands/wallet/send.js +63 -0
- package/dist/commands/whoami.js +18 -0
- package/dist/hooks/version-suffix.js +2 -0
- package/dist/index.js +17 -0
- package/dist/lib/help.js +3 -0
- package/oclif.manifest.json +3329 -0
- package/package.json +135 -0
package/README.md
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
# `@allscale/cli`
|
|
2
|
+
|
|
3
|
+
Command-line interface for AllScale — drives the same actions a user can take in [app.allscale.io](https://app.allscale.io). Designed for AI agents and humans alike.
|
|
4
|
+
|
|
5
|
+
Sign in, issue and pay invoices, create claim links, inspect transactions, and withdraw stablecoins from your AllScale wallet — with JSON on stdout and stable exit codes, so scripts and agents can drive it without screen-scraping.
|
|
6
|
+
|
|
7
|
+
## Important legal, risk, security, and privacy notice
|
|
8
|
+
|
|
9
|
+
This CLI provides command-line access to supported AllScale features. Availability depends on your account, permissions, jurisdiction, applicable law, risk controls, and service version. Use of AllScale services is governed by the [Terms of Use](https://www.allscale.io/agreement), [Privacy Policy](https://www.allscale.io/policy), and applicable risk disclosures. AllScale's role and the non-custodial features of the services are described in the Terms of Use.
|
|
10
|
+
|
|
11
|
+
AllScale Services are available only to eligible users in supported jurisdictions. Transactions may be delayed, rejected, suspended, returned, or require additional information to comply with applicable law, sanctions, AML/CTF requirements, fraud controls, network conditions, or service-provider requirements.
|
|
12
|
+
|
|
13
|
+
Digital assets and stablecoins are not bank deposits, may not be protected by deposit insurance, and may lose value. Blockchain transactions may be irreversible. Verify the recipient, wallet address, network, token, amount, and fees before approving any transaction. AllScale does not provide investment, legal, tax, or accounting advice.
|
|
14
|
+
|
|
15
|
+
Do not expose passwords, agent keys, OTPs, API secrets, Claim Link tokens, or personal data in shell history, logs, prompts, tickets, or shared files. CLI output and optional event logs may contain personal, confidential, and transaction data.
|
|
16
|
+
|
|
17
|
+
### AI and automation use
|
|
18
|
+
|
|
19
|
+
Commands submitted through scripts, AI agents, or other automation may create legal, financial, and irreversible consequences. Scope controls limit categories of permitted calls but do not verify an agent's authority, accuracy, business purpose, recipient, legality, or safety. You are responsible for configuring and supervising third-party agents and models. Use least-privilege scopes, short-lived credentials, idempotency controls, testing, and appropriate human review for irreversible actions.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g @allscale/cli
|
|
25
|
+
allscale --help
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Requires Node.js >= 20.10.
|
|
29
|
+
|
|
30
|
+
To upgrade: `npm install -g @allscale/cli@latest`. To remove: `npm uninstall -g @allscale/cli`.
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Device-pairing login — the browser approves the CLI session. Use an existing
|
|
36
|
+
# web session or sign in there with Passkey or Email; enter any Email OTP on the
|
|
37
|
+
# webpage, never in this terminal flow.
|
|
38
|
+
# The current CLI login flows issue a SCOPED AGENT KEY:
|
|
39
|
+
# the backend checks requests against the scopes chosen on the approval screen, and the
|
|
40
|
+
# key expires (re-run device-login to renew).
|
|
41
|
+
# A BARE run at a terminal first says what it is about to do (register a
|
|
42
|
+
# pairing session, open the browser, wait for approval) and asks to confirm;
|
|
43
|
+
# declining exits 12 having created nothing. Any flag skips the prompt, as do
|
|
44
|
+
# piped/--json/non-tty-CI callers and a redirected stderr. (A pty-allocating
|
|
45
|
+
# harness running it BARE looks human and is prompted — pass --yes.)
|
|
46
|
+
# Polling stops at the
|
|
47
|
+
# pairing's expiry or after 15 minutes, whichever comes first — the CLI prints
|
|
48
|
+
# that deadline before it starts waiting.
|
|
49
|
+
allscale device-login # opens browser; sign in there if needed, then approve
|
|
50
|
+
allscale device-login --yes # explicit "don't prompt me"
|
|
51
|
+
allscale device-login --device-label my-laptop # add a label shown in the approval UI
|
|
52
|
+
|
|
53
|
+
# Or, terminal-only Email OTP (headless/scripted). The OTP enters this CLI and
|
|
54
|
+
# the backend mints the same scoped agent key — pass the scopes with --scopes
|
|
55
|
+
# (required; there is deliberately no full-permission default).
|
|
56
|
+
allscale otp-login --email me@example.com --scopes invoice:read_only # interactive: prompts for OTP
|
|
57
|
+
allscale otp-login --email me@example.com --scopes invoice:all --otp-id <id> --otp <code> # scripted: reuse a prior `otp-send`
|
|
58
|
+
|
|
59
|
+
# Who am I signed in as?
|
|
60
|
+
allscale whoami
|
|
61
|
+
|
|
62
|
+
# What capabilities did this CLI session get? (chosen on the approval screen)
|
|
63
|
+
allscale scope
|
|
64
|
+
|
|
65
|
+
# Create a store for your business — returns its one-time API secret on stdout.
|
|
66
|
+
# `payout send` requires a live store credential from Payout onboarding.
|
|
67
|
+
allscale store create --name "My Shop"
|
|
68
|
+
|
|
69
|
+
# List your invoices (defaults to 50 rows; --all requests one bounded response)
|
|
70
|
+
allscale invoice list
|
|
71
|
+
allscale invoice list --input '{"limit":50,"skip":50}' # second page
|
|
72
|
+
|
|
73
|
+
# Filter without hand-writing a query: repeat a flag to match any of its values.
|
|
74
|
+
allscale invoice list --status SENT --status OVERDUE # still owed
|
|
75
|
+
allscale invoice list --payment-type USDC # only USDC invoices
|
|
76
|
+
allscale invoice list --from 2026-07-01 --to 2026-08-01 # exactly July: --from is
|
|
77
|
+
# inclusive, --to exclusive
|
|
78
|
+
allscale invoice list --to-email client@example.com # what you billed that
|
|
79
|
+
# customer (email must match
|
|
80
|
+
# an existing contact)
|
|
81
|
+
|
|
82
|
+
# IMPORTANT: Financial commands are available only to eligible users in supported
|
|
83
|
+
# jurisdictions and are subject to applicable law, sanctions/AML/CTF checks,
|
|
84
|
+
# risk controls, network conditions, and service-provider requirements.
|
|
85
|
+
# Transactions may be delayed, rejected, suspended, returned, or require
|
|
86
|
+
# additional information.
|
|
87
|
+
|
|
88
|
+
# Send a new invoice — authenticated `create_payment` mutation, no browser
|
|
89
|
+
# ceremony. USDT/USDC totals must be at least 0.10. --wallet-id is OPTIONAL:
|
|
90
|
+
# pin one or more receiving wallets (the backend resolves them, so an
|
|
91
|
+
# invoice-scope-only key works), or omit it to auto-select your eligible
|
|
92
|
+
# wallets (that read needs the wallet:read_only scope on an agent key).
|
|
93
|
+
allscale invoice send --to-email client@example.com --amount 1.00
|
|
94
|
+
allscale invoice send --to-email client@example.com --amount 1.00 --wallet-id <wallet-id>
|
|
95
|
+
|
|
96
|
+
# Itemized invoice — `--line "<description>|<quantity>|<amount>"` carries per-line
|
|
97
|
+
# qty + amount. `--amount` is optional when EVERY --line uses the 3-field form —
|
|
98
|
+
# the CLI sums the lines to derive the total.
|
|
99
|
+
allscale invoice send --to-email client@example.com \
|
|
100
|
+
--wallet-id <wallet-id> \
|
|
101
|
+
--line "Discovery (4h)|4|25.00" --line "Implementation (10h)|10|25.00" \
|
|
102
|
+
--memo "Q2 engagement"
|
|
103
|
+
|
|
104
|
+
# Mixed — explicit --amount + display-only lines. With single-field --line,
|
|
105
|
+
# `--amount` is required (no way to derive); three-field display rows round
|
|
106
|
+
# their quantity × rate to cents and warn if the visible total differs.
|
|
107
|
+
allscale invoice send --to-email client@example.com --amount 250 --wallet-id <wallet-id> \
|
|
108
|
+
--payment-type 2 --currency-label USDC --memo "January retainer" --due 2026-06-01 \
|
|
109
|
+
--line "Discovery (4h)" --line "Implementation (10h)"
|
|
110
|
+
|
|
111
|
+
# First-time invoice to a new email? Add --auto-create-contact to create the
|
|
112
|
+
# contact in one shot. Name defaults to the FULL email address (essentially
|
|
113
|
+
# unique — sidesteps the per-business name-uniqueness rule).
|
|
114
|
+
# Override with --contact-name for a curated display name.
|
|
115
|
+
# If the contact already exists, no new contact is created. Before using this
|
|
116
|
+
# option, confirm that you are authorized to provide the recipient's information
|
|
117
|
+
# and send the invoice, and comply with applicable privacy, marketing, and
|
|
118
|
+
# recordkeeping requirements.
|
|
119
|
+
allscale invoice send --to-email newclient@example.com --amount 100 \
|
|
120
|
+
--wallet-id <wallet-id> --auto-create-contact
|
|
121
|
+
allscale invoice send --to-email billing@acme.com --amount 100 \
|
|
122
|
+
--wallet-id <wallet-id> --auto-create-contact --contact-name "Acme Inc"
|
|
123
|
+
|
|
124
|
+
# Send a stablecoin from your AllScale wallet to an external EVM address
|
|
125
|
+
allscale wallet send --idempotency-key order-1042 \
|
|
126
|
+
--to 0xabc... --amount 0.1 --chain base --stable-coin USDC
|
|
127
|
+
|
|
128
|
+
# (`wallet send --headless` skips the browser, but it signs with the user token
|
|
129
|
+
# of a legacy JWT session — a credential this build's logins never mint, so it
|
|
130
|
+
# cannot succeed here. Use the browser-bridge form above.)
|
|
131
|
+
|
|
132
|
+
# Drop credentials
|
|
133
|
+
allscale logout
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Commands
|
|
137
|
+
|
|
138
|
+
### Auth
|
|
139
|
+
| Command | Description |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `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. |
|
|
142
|
+
| `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). |
|
|
143
|
+
| `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. |
|
|
144
|
+
| `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. |
|
|
145
|
+
| `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. |
|
|
146
|
+
| `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. |
|
|
147
|
+
|
|
148
|
+
### Authorization model
|
|
149
|
+
|
|
150
|
+
One page, no archaeology:
|
|
151
|
+
|
|
152
|
+
- **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.
|
|
153
|
+
- **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.
|
|
154
|
+
- **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).
|
|
155
|
+
- **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. Headless Turnkey signing (`--headless` on either `wallet send` or `invoice pay`) is unavailable in this build at all: it needs the user token of a legacy JWT session, which neither login flow mints. 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:read_only` to look up the payer address. Neither login flow grants `invoice:all` for you: on `device-login` the **Invoices** toggle starts at Read, so tick its write tier on the approval screen (the **Wallets** toggle that grants `wallet:read_only` *is* on by default); with `otp-login`, pass BOTH `--scopes invoice:all --scopes wallet:read_only`, because naming any scope turns every unnamed category off.
|
|
156
|
+
- **Password login is not part of this CLI.** AllScale Pay Accounts are passkey-only and have no usable password; `device-login` and `otp-login` are the two ways in, and both mint a scoped agent key.
|
|
157
|
+
|
|
158
|
+
### Discovery
|
|
159
|
+
|
|
160
|
+
The schema-introspection commands below are opt-in (default off) — enable with `ALLSCALE_ALLOW_RAW=1`.
|
|
161
|
+
|
|
162
|
+
| Command | Description |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `operations` | List every operation in the schema (filter by `--kind`, `--grep`). |
|
|
165
|
+
| `describe <op>` | Full type info for one operation: args, return type, example document. |
|
|
166
|
+
|
|
167
|
+
### Ergonomic wrappers
|
|
168
|
+
|
|
169
|
+
The CLI's user-facing surface matches the AllScale web app: what users call an **invoice** is internally a `Payment` in the backend GraphQL. The `invoice *` commands below wrap `payments` / `payment` / `sender_payments` / `recipient_payments` / `create_payment` / `update_payment` accordingly.
|
|
170
|
+
|
|
171
|
+
**Important:** The financial commands below are available only to eligible users in supported jurisdictions. Execution may be delayed, rejected, suspended, returned, or require additional information to comply with applicable law, sanctions, AML/CTF requirements, fraud controls, network conditions, or service-provider requirements.
|
|
172
|
+
|
|
173
|
+
| Command | Description |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `invoice list` | List invoices for a business. Wraps `payments`. Defaults `--business-id` from the JWT. **Defaults to the first 50 rows** — pass `--input '{"limit":N,"skip":M}'` to paginate, or `--all` to request the backend's unpaginated result in one response. Read-only responses remain subject to the CLI's 32 MiB safety limit. Filter with `--status` / `--payment-type` (both repeatable — repeated values match any of them) and the creation-date range `--from` / `--to`. **The range is half-open: `--from` is inclusive, `--to` is exclusive**, so `--from 2026-07-01 --to 2026-08-01` is exactly July and consecutive ranges tile without double-counting a boundary row. Both accept a plain date, a naive datetime (read as UTC), or an offset datetime, and are normalized to the backend's required `YYYY-MM-DDTHH:MM:SS.ffffffZ`. `--to-email <email>` narrows to the invoices you **issued** to that contact — an invoice's contact record belongs to the business that issued it, so on this both-directions view the flag selects the issued half. The email is resolved to an existing contact first, and an email with no contact is a structured `input.invalid` error (exit 2), not an empty list. On an agent key that lookup additionally needs the `contact:read_only` scope. `--input` accepts only the documented QueryInput fields. |
|
|
176
|
+
| `invoice get <id>` | Fetch one invoice by id. Wraps `payment`. |
|
|
177
|
+
| `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. |
|
|
178
|
+
| `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. |
|
|
179
|
+
| `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. USDT/USDC totals below 0.10 are rejected before contact or wallet lookup. 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 (needs `wallet:read_only` on an agent key). **No browser ceremony** — the payer signs when paying; the inviter doesn't sign at issue. |
|
|
180
|
+
| `invoice update <id>` | Update an existing invoice. Wraps `update_payment`. |
|
|
181
|
+
| `invoice pay <id>` | 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` (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:read_only` for the payer-address lookup (the narrow `business_wallets` query). Neither login 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. |
|
|
182
|
+
| `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). |
|
|
183
|
+
| `transaction get <id>` | Fetch one transaction by id. |
|
|
184
|
+
| `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. |
|
|
185
|
+
| `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, returns `tx_hash` through the loopback listener. The browser is the only path this build offers: `--headless` exists but signs with the user token of a legacy JWT session, **which this build cannot create**, so it fails closed with exit 6 (`auth.permission_denied`). |
|
|
186
|
+
|
|
187
|
+
### Claim links
|
|
188
|
+
|
|
189
|
+
**Important:** The financial commands below are available only to eligible users in supported jurisdictions. Execution may be delayed, rejected, suspended, returned, or require additional information to comply with applicable law, sanctions, AML/CTF requirements, fraud controls, network conditions, or service-provider requirements.
|
|
190
|
+
|
|
191
|
+
Sender-side reads (`get` / `list` / `preview`) need a CLI login and `claim_link:read_only` under an agent-key session. `claim-link create` needs `claim_link:all`: the CLI fixes the amount, stablecoin, chain, claim window, optional sender metadata, and caller-stable idempotency key before registering the `create_claim_link` broker op. The AllScale-hosted browser approval page shows that immutable intent as a read-only review, then performs creation, signing, and funding after the payer authorizes it; verify the exact AllScale origin before approval. It does not ask the payer to re-enter or edit the payment. Before opening the browser and again after any callback, cancellation, invalid receipt, or timeout, the CLI reconciles the complete intent through `claim_link_funding_by_idempotency_key`; the browser receipt is advisory. `status` and `claim` drive the public receiver-facing REST surface — no login; the bearer claim token IS the credential.
|
|
192
|
+
|
|
193
|
+
| Command | Description |
|
|
194
|
+
|---|---|
|
|
195
|
+
| `claim-link create` | Create and fund a fixed-intent Claim Link through an AllScale-hosted browser approval flow. Verify the exact AllScale origin before approval. Requires `--idempotency-key` (1–128 trimmed characters), `--amount` (exact decimal string, 0.1–10000, at most 6 effective decimal places), `--chain`, and agent-key scope `claim_link:all`; `--stable-coin` defaults to USDT and `--expires` to 14d. `--no-browser` skips only local auto-open: the CLI still prints the `/cli` URL and waits for callback/paste. A recovered key must match every immutable field; a pre-browser mismatch exits 2 as `claim_link.intent_conflict` without opening the browser. Success is emitted only when the authoritative lookup returns the same intent, a validated AllScale claim URL, and either `LINK_SENT`, `CLAIMING`, or `CLAIMED`, or returns `EXPIRED` or `CANCELLED` with a validated non-empty funding transaction hash. A signed/broadcast/unknown, still-pending, failed-reconciliation, post-browser intent race, or `EXPIRED`/`CANCELLED` result without that funding proof exits 9 as `claim_link.funding_ambiguous`; follow its retry metadata and never substitute a new key. Only an explicit cancellation before creation plus a successful authoritative miss exits 12 as `claim_link.browser_cancelled`. |
|
|
196
|
+
| `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`. |
|
|
197
|
+
| `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`. |
|
|
198
|
+
| `claim-link preview` | Fee breakdown for a would-be link without creating it. Wraps `claim_link_fee_preview`; mirrors create's asset validation. Agent-key scope: `claim_link:read_only`. |
|
|
199
|
+
| `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. |
|
|
200
|
+
| `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; unavailable/expired links exit 12, while pending/unproven payouts exit 9 and must be reconciled before another claim. |
|
|
201
|
+
|
|
202
|
+
### Store
|
|
203
|
+
|
|
204
|
+
| Command | Description |
|
|
205
|
+
|---|---|
|
|
206
|
+
| `store create` | Create a store for the logged-in business (no store key, no browser ceremony). Wraps `create_store`. Defaults `--business-id` from the session token — the JWT `user_id` for cookie sessions, or the owner business id stored at login for agent-key sessions (re-run `device-login` if an older agent-key login has none stored); `--live` creates a live store (default is sandbox). Returns the API key and **one-time** API secret on stdout; capture it securely. This command does not enroll the store for Payout. Business must have checkout enabled; only one sandbox store is allowed per business. |
|
|
207
|
+
|
|
208
|
+
### Payout
|
|
209
|
+
|
|
210
|
+
**Important:** The financial commands below are available only to eligible users in supported jurisdictions. Execution may be delayed, rejected, suspended, returned, or require additional information to comply with applicable law, sanctions, AML/CTF requirements, fraud controls, network conditions, or service-provider requirements.
|
|
211
|
+
|
|
212
|
+
**Authorization is set up in the dashboard, not here.** Enabling auto-payout,
|
|
213
|
+
changing its limits, and turning it off all complete with a passkey in a human
|
|
214
|
+
web session — a credential this CLI's logins never mint. Do those in **Store
|
|
215
|
+
Settings → Payout Authorization**. The CLI reads the resulting session and
|
|
216
|
+
spends from it.
|
|
217
|
+
|
|
218
|
+
| Command | Description |
|
|
219
|
+
|---|---|
|
|
220
|
+
| `payout status` | Read the current claim-link auto-payout session, limits, remaining budget, and expiry. |
|
|
221
|
+
| `payout send` | Create and synchronously fund a claim link using a live, Payout-onboarded store key. `--reference-id` is required for idempotent retry. Store HMACs go only to the standalone partner API at `https://openapi.allscale.io`, which is the default — `--payout-api-base` / `ALLSCALE_PAYOUT_API_BASE` exist to set it explicitly, not because you need to change it. |
|
|
222
|
+
|
|
223
|
+
### Keys
|
|
224
|
+
|
|
225
|
+
Agent API keys are managed in the **AllScale web dashboard**: Settings → Security → Sessions · Agents & Devices. It lists every credential and offers per-key revoke plus a revoke-all kill-switch. Creating a key stays where it always was — the login flows below — and rotation is revoke-then-log-in-again; there is no separate rotate control.
|
|
226
|
+
|
|
227
|
+
The CLI mints a key for its own session when you log in (`device-login` / `otp-login`) and prints its scopes and expiry via `allscale scope` / `allscale whoami`. It does not provide key administration: those are control-plane actions that the current backend is designed to reject for an agent-key session, which is the credential type issued by the current CLI login flows.
|
|
228
|
+
|
|
229
|
+
## Configuration
|
|
230
|
+
|
|
231
|
+
- **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.
|
|
232
|
+
- **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.
|
|
233
|
+
- **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.
|
|
234
|
+
- **Profile metadata**: `~/.allscale/config.toml` (mode 0600) records `api_base` and `user_type` per profile. Non-secret.
|
|
235
|
+
- **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.
|
|
236
|
+
- **Default backend**: `https://app.allscale.io` (production). Published builds are configured for their bound AllScale environment. The current implementation accepts a credential-bearing api-base only for exact AllScale origins (plus an explicit loopback origin for development), and validates session credentials against the origin recorded at login. `payout send` uses a separate approved partner API origin selected with `--payout-api-base` / `ALLSCALE_PAYOUT_API_BASE`. Embedded URL userinfo (`user:password@host`) is rejected. Browser targets must be `https://`, or `http://` to loopback only. The current implementation is designed not to follow redirects when a request carries credentials; callers should still verify the destination origin and TLS before approving or transmitting sensitive data.
|
|
237
|
+
- **Sidecar event log** (opt-in, off by default): set **`ALLSCALE_OUTPUT_FILE_PATH=/path/to/run.ndjson`** to append a machine-readable NDJSON event stream for the run, or **`ALLSCALE_OUTPUT_FILE_DIRECTORY=/path/to/dir`** to give every run its own randomly named file in that directory. Events may cover the command, argv fields designed to redact specified authentication secrets, request URLs, responses, refreshes, bridge steps, and errors — intended for agent supervisors and orchestrators that need structured progress without parsing stdout. Redaction is not a guarantee that the log contains no sensitive information.
|
|
238
|
+
|
|
239
|
+
**Important:** Sidecar logs are designed to redact specified authentication secrets, but may still contain personal, business-confidential, and transaction data. Treat them as sensitive. Do not place them in shared directories or transmit them to third-party agents or support channels without review. Configure appropriate access controls, retention, and secure deletion.
|
|
240
|
+
|
|
241
|
+
On POSIX systems, the current implementation attempts to create files with mode 0600. A directory selected through `ALLSCALE_OUTPUT_FILE_DIRECTORY` is intended to be created owner-only; if it already grants group/other access, the CLI is designed to refuse writing rather than change a potentially shared directory such as `/tmp`. For an explicit `_PATH`, the current implementation does not change permissions on an existing parent directory, so choose a private parent yourself; it is designed to refuse an existing output file unless it is owner-only (rotate/remove an older loose file first). Sidecar output is currently disabled on Windows because Node's mode APIs cannot reliably enforce or verify owner-only NTFS ACLs. **It is still a file on disk that describes your session**: prefer `_DIRECTORY`, which generates an unpredictable filename, over a fixed `_PATH` on a shared machine. A `_PATH` that is a symlink is designed to be refused rather than written through. The file is append-only with no rotation and is currently capped at 64 MB, after which the sidecar is designed to disable itself for the run with a warning on stderr.
|
|
242
|
+
|
|
243
|
+
## Output contract
|
|
244
|
+
|
|
245
|
+
JSON to stdout by default. Stable shapes — agents can parse without quoting hacks.
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
$ allscale whoami
|
|
249
|
+
{
|
|
250
|
+
"data": {
|
|
251
|
+
"profile": "default",
|
|
252
|
+
"api_base": "https://app.allscale.io",
|
|
253
|
+
"credential": "cookie",
|
|
254
|
+
"user_id": "...",
|
|
255
|
+
"user_email": "you@example.com",
|
|
256
|
+
"expires_at": "2026-05-07T03:35:02.000Z",
|
|
257
|
+
"expired": false,
|
|
258
|
+
"storage": "keychain"
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
In an agent-key session (no JWT) the shape is key-mode instead:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
$ allscale whoami
|
|
267
|
+
{
|
|
268
|
+
"data": {
|
|
269
|
+
"profile": "default",
|
|
270
|
+
"api_base": "https://app.allscale.io",
|
|
271
|
+
"credential": "agent_key",
|
|
272
|
+
"business_id": "...",
|
|
273
|
+
"user_type": "business",
|
|
274
|
+
"scopes": ["invoice:all", "contact:read_only"],
|
|
275
|
+
"expires_at": "2026-05-07T03:35:02.000Z",
|
|
276
|
+
"expired": false,
|
|
277
|
+
"storage": "keychain"
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Errors go to stderr as JSON with stable codes:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
$ allscale whoami
|
|
286
|
+
# stderr:
|
|
287
|
+
{
|
|
288
|
+
"error": {
|
|
289
|
+
"code": "auth.no_token",
|
|
290
|
+
"message": "No tokens stored for profile 'default'. Run `allscale device-login --profile <profile>` with the same profile (recommended), use `allscale otp-login --profile <profile> --email <you@example.com> --scopes <scope> --otp-stdin` for headless automation."
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
# exit code: 4
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Exit codes
|
|
297
|
+
|
|
298
|
+
Stable contract — branch on these. Every error code the CLI can emit maps to
|
|
299
|
+
exactly one of them, and a mapping is never renumbered once shipped.
|
|
300
|
+
|
|
301
|
+
| Exit | Meaning | Error codes |
|
|
302
|
+
| ---- | ------- | ----------- |
|
|
303
|
+
| `0` | success | — |
|
|
304
|
+
| `1` | generic / unexpected | `internal` |
|
|
305
|
+
| `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` |
|
|
306
|
+
| `3` | network / transport failure (**see the retry warning below**) | `transport.network`, `transport.timeout`, `auth.credential_changed`, `wallet.bridge_timeout` |
|
|
307
|
+
| `4` | not authenticated — run `allscale device-login` | `auth.no_token` |
|
|
308
|
+
| `5` | credential expired — re-authenticate | `auth.token_expired` |
|
|
309
|
+
| `6` | not fixable by this invocation: permission denied, or a capability gap | `auth.permission_denied`, `wallet.requires_browser_auth`, `wallet.headless_not_supported`, `wallet.headless_setup_required`, `wallet.headless_config_missing` |
|
|
310
|
+
| `7` | not found — the object you named does not exist | `not_found` |
|
|
311
|
+
| `8` | rate limited | `rate_limited` |
|
|
312
|
+
| `9` | backend internal, or an **ambiguous** mutation result | `backend.internal`, `claim.payout_ambiguous`, `claim_link.funding_ambiguous`, `wallet.bridge_invalid_response`, `wallet.headless_sign_failed` |
|
|
313
|
+
| `10` | escape hatch disabled — export `ALLSCALE_ALLOW_RAW=1` | `raw.disabled` |
|
|
314
|
+
| `11` | this build's request signature was rejected — **upgrade the CLI** (re-login does not help) | `auth.signature_rejected` |
|
|
315
|
+
| `12` | did not complete; nothing was created | `claim.not_claimable`, `claim.expired`, `claim_link.browser_cancelled`, `user.cancelled` |
|
|
316
|
+
|
|
317
|
+
> **An exit code is NOT a retry-safety signal. Branch on the `code` string when
|
|
318
|
+
> you need to decide whether re-running is safe.**
|
|
319
|
+
>
|
|
320
|
+
> Exit `3` is the trap: `transport.network` never reached the server, but
|
|
321
|
+
> `wallet.bridge_timeout` is **post-signing** — the browser may already have
|
|
322
|
+
> signed and the backend may already have broadcast. Retrying blindly on exit
|
|
323
|
+
> `3` can **double-pay**. Exit `9` mixes a plain 5xx with the `*_ambiguous`
|
|
324
|
+
> codes and `wallet.headless_sign_failed`, all of which must be reconciled
|
|
325
|
+
> against the original idempotency key rather than retried.
|
|
326
|
+
|
|
327
|
+
Note that exit `6` and exit `9` each cover five distinct codes, so a branch that
|
|
328
|
+
must tell them apart reads `error.code`. **Credential precedence** below states
|
|
329
|
+
the same caveat for exit `2`.
|
|
330
|
+
|
|
331
|
+
**Under `--json`, stderr is parseable too — for `payout send` and
|
|
332
|
+
`device-login`.** On those, human progress prose is suppressed entirely and
|
|
333
|
+
every stderr line is one complete JSON object: the `{error}` envelope above is
|
|
334
|
+
rendered compact in that mode, and notices you may need before the stdout
|
|
335
|
+
document exists arrive as structured event lines:
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
$ allscale payout send --json --amount 10 --chain base ...
|
|
339
|
+
# stderr:
|
|
340
|
+
{"version":"1","event":"payout_destination","payout_api_base":"https://..."}
|
|
341
|
+
{"error":{"code":"input.invalid","message":"Missing store API key..."}}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Branch on `version` (currently `"1"`), ignore an `event` type you don't
|
|
345
|
+
recognise, and note that a stderr JSON line is not necessarily an error —
|
|
346
|
+
check for the `error` key. Event fields are designed to be scrubbed and length-bounded, so
|
|
347
|
+
treat them as best-effort: an oversize record degrades to
|
|
348
|
+
`{"version", "event", "output_truncated": true}`.
|
|
349
|
+
|
|
350
|
+
**Treat the two commands named above as an allow-list, not a caveat: any other
|
|
351
|
+
command still writes human prose to stderr, so do not read its stderr as
|
|
352
|
+
NDJSON.** That includes the browser-bridge money paths (`wallet send`,
|
|
353
|
+
`invoice pay`, `claim-link create`) and the ordinary read and auth commands
|
|
354
|
+
(`scope`, `whoami`, `otp-send`, `otp-login`, `invoice send`, `store create`).
|
|
355
|
+
Converting those is tracked separately. The `--show-doc` debug flag also prints
|
|
356
|
+
a raw GraphQL document to stderr wherever it is offered; that one is
|
|
357
|
+
caller-requested and stays that way, so don't combine it with a machine-parsed
|
|
358
|
+
stderr. stdout stays a single JSON document on every command either way — this
|
|
359
|
+
rule is about stderr only, and the compact `{error}` envelope is global.
|
|
360
|
+
|
|
361
|
+
One deliberate exception to "every failure is an `{error}` document": a command
|
|
362
|
+
that cannot run without input, invoked with **no arguments at all**, prints its
|
|
363
|
+
help on stdout and exits 0 instead — the same as a bare topic
|
|
364
|
+
(`allscale wallet`). Machine callers never take that path. A partial call still
|
|
365
|
+
exits 2 with `input.invalid`, and a bare call is treated as machine mode —
|
|
366
|
+
error document, exit 2 — whenever **any** of these applies:
|
|
367
|
+
|
|
368
|
+
- **stdin and stdout are not both a terminal** — a pipe, `$(...)`, a CI runner,
|
|
369
|
+
an agent harness, or a terminal script with either stream redirected
|
|
370
|
+
(`allscale wallet send < /dev/null`). This is the one signal that needs no
|
|
371
|
+
opt-in, and it is how most machine callers show up.
|
|
372
|
+
- `--json`
|
|
373
|
+
- `ALLSCALE_CONTENT_TYPE=json`
|
|
374
|
+
- a configured sidecar (`ALLSCALE_OUTPUT_FILE_PATH` / `ALLSCALE_OUTPUT_FILE_DIRECTORY`)
|
|
375
|
+
|
|
376
|
+
The last three matter even at a terminal, because none of them appear in argv:
|
|
377
|
+
`--json` is an argument so it never reaches a bare call, while oclif resolves
|
|
378
|
+
JSON mode from its env var before it inspects argv, and the sidecar is pure
|
|
379
|
+
environment.
|
|
380
|
+
|
|
381
|
+
The sidecar counts because it is the audit stream a supervisor consumes: a bare
|
|
382
|
+
call there must record `command-end` with `ok: false`, never a success for a
|
|
383
|
+
command that did not run. **Setting the variable is what counts** — a sidecar the
|
|
384
|
+
CLI *refuses* to open (Windows, a `_DIRECTORY` that is not owner-only, a stale
|
|
385
|
+
`0644` file) still means a supervisor is watching, and that is the worst moment
|
|
386
|
+
to change the outcome, since no stream then exists to contradict the exit code.
|
|
387
|
+
|
|
388
|
+
**Known limit:** two terminals are treated as a human, and that is not a proof.
|
|
389
|
+
A harness that allocates a pty for *both* streams — `expect`, a PTY-configured CI
|
|
390
|
+
job — will get help and exit 0 on a bare invocation. Set any one of the three
|
|
391
|
+
opt-ins above in those harnesses to restore the `{error}` / exit 2 contract.
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
$ allscale wallet send # at a terminal: help on stdout, exit 0
|
|
395
|
+
$ allscale wallet send | cat # piped: {"error": ...} on stderr, exit 2
|
|
396
|
+
$ allscale wallet send --json # → {"error": {"code": "input.invalid", ...}}, exit 2
|
|
397
|
+
$ ALLSCALE_CONTENT_TYPE=json allscale wallet send # → same {"error": ...}, exit 2
|
|
398
|
+
$ ALLSCALE_OUTPUT_FILE_PATH=run.ndjson allscale wallet send # → same, and the stream ends ok:false
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Auto-refresh
|
|
402
|
+
|
|
403
|
+
Cookie-session tokens are refreshed automatically when the backend returns 401 — single-flight per process (concurrent calls share one refresh round-trip). When the refresh token itself is rejected, the CLI exits with `auth.token_expired` (code 5) and asks the user to re-login. If the profile's credential changes to an agent key while a refresh is in flight, the refresh is abandoned rather than overwriting it and the CLI exits with `auth.credential_changed` (code 3) — a valid credential exists, so the command just needs re-running, not a re-login.
|
|
404
|
+
|
|
405
|
+
**Agent keys don't refresh.** A 401 in agent-key mode means the key expired or was revoked; the CLI surfaces `auth.token_expired` with a clear "re-run `allscale device-login`" message and does not attempt the refresh endpoint in the current implementation.
|
|
406
|
+
|
|
407
|
+
## Upgrade-required signal
|
|
408
|
+
|
|
409
|
+
Requests include a build identifier and an `x-allscale-cli-version` header. The service may use these values to enforce minimum supported versions, but they are not proof that a client build, environment, or request is uncompromised. If a release is no longer accepted, the CLI exits with **`auth.signature_rejected` (code 11)** and directs the user to install the latest version with `npm install -g @allscale/cli@latest`. If the latest version is already installed and the same code persists, contact AllScale support. When running from source or an internal build without a baked key for the selected destination, configure `ALLSCALE_SIGNING_KEY` or `ALLSCALE_BUILD_PAIRS`; if no signing key resolves for that destination, the CLI exits **2** (`signing.key_unavailable`). Do not rely on this version-enforcement mechanism as an authentication or integrity boundary.
|
|
410
|
+
|
|
411
|
+
## Credential precedence
|
|
412
|
+
|
|
413
|
+
Highest priority first:
|
|
414
|
+
|
|
415
|
+
1. `ALLSCALE_TOKEN` env var
|
|
416
|
+
2. `--profile <name>` flag → keychain entry for that profile. A name with no
|
|
417
|
+
entry in `~/.allscale/config.toml` errors with `auth.unknown_profile`
|
|
418
|
+
(exit 2) when no credential is found for it and at least one profile is
|
|
419
|
+
saved, listing the saved profiles — a typo'd profile is an input error,
|
|
420
|
+
not a login prompt. (An empty registry is a fresh machine: that stays
|
|
421
|
+
`auth.no_token`.)
|
|
422
|
+
3. `ALLSCALE_PROFILE` env var → keychain entry (same `auth.unknown_profile`
|
|
423
|
+
rule as the flag).
|
|
424
|
+
4. Default profile from `~/.allscale/config.toml`
|
|
425
|
+
5. Otherwise → `auth.no_token` (exit 4)
|
|
426
|
+
|
|
427
|
+
`auth.unknown_profile` deliberately shares exit 2 with other input errors
|
|
428
|
+
(e.g. `input.invalid`, `storage.unavailable`); when an exit-2 branch needs to
|
|
429
|
+
know which one fired, the `error.code` field is the load-bearing
|
|
430
|
+
discriminator, not the exit number.
|
|
431
|
+
|
|
432
|
+
## Capabilities
|
|
433
|
+
|
|
434
|
+
- Codegen-driven `operations` / `describe` discovery commands (opt-in escape hatches).
|
|
435
|
+
- Ergonomic wrappers: `invoice`, `transaction`, `claim-link`, `wallet list`.
|
|
436
|
+
- Agent-key invoice creation: explicit, backend-resolved `--wallet-id`s, or automatic wallet selection via the narrow `business_wallets` query; invoice issuance itself needs no browser ceremony.
|
|
437
|
+
- `device-login` (device-pairing OAuth) / `otp-login` (terminal OTP for headless shells), and `wallet send` (Turnkey-direct EVM withdraw through the `/cli` bridge).
|
|
438
|
+
- Current CLI login flows issue scoped agent-key sessions: `device-login` and `otp-login` both mint an agent API key checked against its granted scopes; `scope` reports the granted set + key expiry.
|
|
439
|
+
- Agent-key sessions store the owner `business_id` at login, so `--business-id` auto-fills (no JWT needed) and `whoami` prints a key-mode identity instead of erroring.
|
|
440
|
+
- Store creation returns its one-time API secret through the structured stdout result.
|
|
441
|
+
|
|
442
|
+
## Security
|
|
443
|
+
|
|
444
|
+
Report a vulnerability by email to **security@allscale.io** — please do **not**
|
|
445
|
+
open a public issue. Include the impact, a minimal reproduction (commands,
|
|
446
|
+
environment, `allscale --version`), and whether it is exploitable without an
|
|
447
|
+
AllScale account. We aim to acknowledge within 3 business days and to ship a fix
|
|
448
|
+
or mitigation for high-severity issues within 30 days, coordinating disclosure
|
|
449
|
+
with you.
|
|
450
|
+
|
|
451
|
+
In scope: this package and its bundled code — the loopback browser bridge, token
|
|
452
|
+
storage (keychain vs. plaintext-file fallback and its file permissions), and
|
|
453
|
+
argv/URL redaction in the sidecar event log. Out of scope, and best reported to
|
|
454
|
+
the AllScale platform team through the same address: the backend API and the web
|
|
455
|
+
app at `https://app.allscale.io`.
|
|
456
|
+
|
|
457
|
+
**Two values baked into the shipped bundle are not secrets, by design**: the
|
|
458
|
+
request-signing key and the static API bearer. Both are recoverable from any
|
|
459
|
+
installed copy and are build-identity markers only — they assert "this request
|
|
460
|
+
came from an official AllScale client build" and are not intended as an
|
|
461
|
+
authorization boundary. In the current implementation, the backend is designed
|
|
462
|
+
to authorize requests using the per-user credential — a session JWT, or an agent
|
|
463
|
+
key whose granted scopes it checks on each request.
|
|
464
|
+
Extracting either value from the bundle is expected behavior rather than a
|
|
465
|
+
finding, unless you can also show the backend making an **authorization**
|
|
466
|
+
decision from one of them.
|
|
467
|
+
|
|
468
|
+
Only the latest published version on npm receives security fixes; pin a recent
|
|
469
|
+
version in CI.
|
|
470
|
+
|
|
471
|
+
## License
|
|
472
|
+
|
|
473
|
+
Proprietary — `@allscale/cli` is licensed, not sold, and is not open source.
|
|
474
|
+
Your use of it, and of the AllScale services it reaches, is governed by the
|
|
475
|
+
AllScale User Agreement & Terms of Service:
|
|
476
|
+
|
|
477
|
+
https://www.allscale.io/agreement
|
|
478
|
+
|
|
479
|
+
That agreement is the whole of the terms — this package ships no separate
|
|
480
|
+
licence file, and `package.json` declares `UNLICENSED` to say there is no
|
|
481
|
+
open-source grant. Runtime dependencies are installed separately by your
|
|
482
|
+
package manager and keep their own licenses.
|
package/bin/dev.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Dev entry — TS-aware via ts-node, no build step required.
|
|
3
|
+
process.env.NODE_ENV = "development";
|
|
4
|
+
|
|
5
|
+
require("ts-node/register");
|
|
6
|
+
|
|
7
|
+
const oclif = require("@oclif/core");
|
|
8
|
+
const { runOclif } = require("./run-oclif");
|
|
9
|
+
|
|
10
|
+
oclif.settings.debug = true;
|
|
11
|
+
void runOclif(oclif, { dir: __dirname });
|
package/bin/run-oclif.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function isUnknownCommandError(oclif, error) {
|
|
4
|
+
return (
|
|
5
|
+
error instanceof oclif.Errors.CLIError &&
|
|
6
|
+
error.oclif?.exit === 2 &&
|
|
7
|
+
typeof error.message === "string" &&
|
|
8
|
+
// Deliberately NARROW: only oclif's command-RESOLUTION error, which is
|
|
9
|
+
// lowercase and period-less (`config.js`: `command ${id} not found`). The
|
|
10
|
+
// help path's capitalised variant is handled in AllscaleHelp, where its
|
|
11
|
+
// provenance is known — widening this pattern to cover both would relabel
|
|
12
|
+
// any CLIError whose text happened to end that way, and since every CLIError
|
|
13
|
+
// defaults to exit 2, nothing here could tell them apart.
|
|
14
|
+
/^command .+ not found$/s.test(error.message)
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function emitUnknownCommandError() {
|
|
19
|
+
// Match `serializeErrorEnvelope`'s mode-dependent rendering:
|
|
20
|
+
// compact for a machine reading stderr as one JSON object per line, indented
|
|
21
|
+
// for a human. This runs before oclif resolves a command, so `jsonEnabled()`
|
|
22
|
+
// does not exist yet — read the same two signals it would from argv/env,
|
|
23
|
+
// resolved exactly as `AllscaleHelp.jsonModeRequested` does (`help.ts`), the
|
|
24
|
+
// other place that has to answer this without a Command. Keeping the two
|
|
25
|
+
// answers identical is the point: a caller must not get compact output from
|
|
26
|
+
// one path and indented from the other for the same command line.
|
|
27
|
+
const raw = process.argv.slice(2);
|
|
28
|
+
// After `--` everything is a passthrough operand, so a literal `--json`
|
|
29
|
+
// there is data, not a flag.
|
|
30
|
+
const passThrough = raw.indexOf("--");
|
|
31
|
+
const json = raw.indexOf("--json");
|
|
32
|
+
// Case-insensitive, matching oclif's own `CONTENT_TYPE` check and help.ts:
|
|
33
|
+
// a caller who exported `CONTENT_TYPE=JSON` gets machine output from every
|
|
34
|
+
// other path, and must not get an indented document from this one. The
|
|
35
|
+
// `ALLSCALE_` prefix is hard-coded because this file is plain JS and cannot
|
|
36
|
+
// import the TS helper or reach `config.scopedEnvVar`; it is safe because
|
|
37
|
+
// `oclif.bin` is pinned to "allscale" in package.json.
|
|
38
|
+
const machine =
|
|
39
|
+
(json !== -1 && (passThrough === -1 || json < passThrough)) ||
|
|
40
|
+
process.env.ALLSCALE_CONTENT_TYPE?.toLowerCase() === "json";
|
|
41
|
+
process.stderr.write(
|
|
42
|
+
`${JSON.stringify(
|
|
43
|
+
{
|
|
44
|
+
error: {
|
|
45
|
+
code: "input.invalid",
|
|
46
|
+
message: "Unknown command. Run `allscale --help` to list available commands.",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
null,
|
|
50
|
+
machine ? undefined : 2,
|
|
51
|
+
)}\n`,
|
|
52
|
+
);
|
|
53
|
+
process.exitCode = 2;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function runOclif(oclif, options = {}) {
|
|
57
|
+
try {
|
|
58
|
+
await oclif.run(process.argv.slice(2), options.dir);
|
|
59
|
+
await oclif.flush();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (isUnknownCommandError(oclif, error)) {
|
|
62
|
+
emitUnknownCommandError();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
await oclif.Errors.handle(error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = { runOclif };
|