@paybox-sh/sdk 0.8.5 → 2.0.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 CHANGED
@@ -3,11 +3,10 @@
3
3
  Typed Node SDK and `paybox` CLI for the PayBox agent API. It mirrors the MCP tool
4
4
  surface — `list_credentials`, `request_payment`, `claim_payment_credentials`,
5
5
  `request_wallet_sign`, `request_secret`, `request_swap`, `discover_services`,
6
- `pay_x402`, `use_service`, `get_portfolio`, `get_buy_link`,
7
- `request_account_change`, `get_request`, and read-only plugin tools (`world_*`,
8
- `hyperliquid_*`) — but talks to the REST API directly, with no MCP host and no
9
- signing iframe.
10
- Geofenced World money tools are MCP-only.
6
+ `pay_x402`, `use_service`, `get_portfolio`, `get_buy_link`, `get_deposit_link`,
7
+ `request_account_change`, `get_request`, and the generic plugin discovery,
8
+ contract, and invocation flow. Core operations use the REST API; plugins use
9
+ the same MCP tools as connector hosts, with no hardcoded per-plugin paths.
11
10
 
12
11
  Wallet signing is **non-custodial and runs in-process**: the SDK reuses the exact
13
12
  signing core from the wallet-sign iframe (`@paybox/mcp-app`), builds the chain
@@ -23,22 +22,22 @@ npx @paybox-sh/sdk login # or use the CLI directly
23
22
 
24
23
  Driving this CLI from an agent? [`SKILL.md`](SKILL.md) ships in the package and
25
24
  describes the install and login flow as an agent skill: what the human has to do
26
- (approve wallets, paste one signing key), and what the agent should not do
27
- around it. Use the CLI on **Grok Bot**, **Grok Computer**, and any agent on a
25
+ (approve wallets, confirm one code), and what the agent should not do
26
+ around it. Use the CLI on **Muse**, **Grok Bot**, **Grok Computer**, and any agent on a
28
27
  computer with no connector screen — not the grok.com MCP connector path.
29
28
 
30
29
  ## CLI
31
30
 
32
31
  ```bash
33
- # Authenticate: OAuth in the browser (approve with a passkey), or store a key.
34
- # `paybox login` is a guided two-step: OAuth, then it opens the app to provision
35
- # a wallet signing key and prompts you to paste the `pbxk1.` token back.
36
- # The OAuth step is a device-code grant, so the browser that approves does not
37
- # have to be on the machine running the CLI.
38
- paybox login # device-code OAuth, then provision a key
39
- paybox login --no-provision # OAuth only, skip the key step
32
+ # Setup is two commands. `login` authenticates; `connect` pairs a signing key.
33
+ # Pairing prints eight emoji and a link; you open the link and pick that same
34
+ # code in PayBox. The key is generated locally and never leaves this machine,
35
+ # so there is nothing to copy. Neither command needs a browser on the machine
36
+ # running the CLI — approve both from a phone if you like.
37
+ paybox login # device-code OAuth (browser + passkey)
40
38
  paybox login --key pbx_live_… # personal API key instead (no OAuth)
41
- PAYBOX_SIGNING_KEY=pbxk1.… paybox login # supply the signing key non-interactively
39
+ paybox connect # pair a signing key
40
+ PAYBOX_SIGNING_KEY=pbxk1.… paybox … # headless: reuse an already-paired key
42
41
 
43
42
  paybox credentials # list usable credentials
44
43
  # `--credential <id>` below is the `credential.id` from that list, not the wallet name.
@@ -60,8 +59,9 @@ paybox account-change --note "need a wallet grant" # manage_url for the user to
60
59
  paybox request <request_id> --wait # poll a pending request
61
60
 
62
61
  # Plugins (enable them in the paybox app first)
63
- paybox world markets --status active # browse prediction markets
64
- paybox hyperliquid markets # spot discovery
62
+ paybox plugins list --status enabled
63
+ paybox plugins contract paybox://plugins/world
64
+ paybox plugins use world world_get_events --input '{"status":"active"}'
65
65
 
66
66
  paybox version # installed version (+ update check)
67
67
  paybox update # update the global install to latest
@@ -77,7 +77,7 @@ its own binary). For project installs use `pnpm up`/`pnpm rm`, or `npx
77
77
  @paybox-sh/sdk@latest` to always run the latest without installing.
78
78
 
79
79
  `sign`, `swap`, `pay-x402`, and `use-service` can complete in-process with a
80
- configured `pbxk1.` key. World buy/sell/redeem are MCP-only.
80
+ configured `pbxk1.` key. Plugin calls return the raw MCP tool result.
81
81
 
82
82
  For a Solana `swap` the SDK re-pins a fresh recent blockhash immediately before
83
83
  signing (parity with the signing window — the quote-time blockhash expires
@@ -85,7 +85,7 @@ within ~60–90s, often before signing), so a swap that dwells for approval no
85
85
  longer broadcasts as `BlockhashNotFound`. EVM swaps are unaffected.
86
86
 
87
87
  `pay-x402` is header mode — on success `output.value.x_payment` is the `X-PAYMENT`
88
- header to replay on the original request. `use-service` is gateway mode — paybox
88
+ header to replay on the original request. `use-service` is gateway mode — PayBox
89
89
  makes the paid call for you and returns the resource's reply on
90
90
  `output.value.response`. Pass `--accepts` from the resource's own 402 (start from
91
91
  the curated Bazaar set with `paybox discover`).
@@ -122,6 +122,15 @@ const services = await paybox.discoverServices("weather api");
122
122
  const rawServices = await paybox.discoverServices("weather api", { raw: true }); // debug only
123
123
  const paid = await paybox.useService({ credentialId: walletId, url: services[0].resource });
124
124
  // paid.response.output.value.response holds the resource's reply
125
+
126
+ const plugins = await paybox.discoverPlugins({ status: "enabled" });
127
+ const selected = plugins.plugins.find((plugin) => plugin.id === "aave");
128
+ const contract = await paybox.getPluginContract(selected!.contract_uri!);
129
+ const result = await paybox.usePlugin({
130
+ pluginId: contract.contract.id,
131
+ toolId: contract.contract.tools[0].id,
132
+ input: {},
133
+ });
125
134
  ```
126
135
 
127
136
  ## Configuration
@@ -141,6 +150,37 @@ macOS/Linux). On Windows the POSIX mode bits don't restrict access — the file
141
150
  is not access-protected there, so prefer `PAYBOX_API_KEY` / `PAYBOX_SIGNING_KEY`
142
151
  env vars on shared Windows machines.
143
152
 
153
+ ## Headless and CI
154
+
155
+ Pairing needs a human to confirm the code, so CI cannot pair at run time. Pair
156
+ once ahead of time **as the identity CI will use**, then carry both secrets:
157
+
158
+ ```bash
159
+ # On a laptop. The pbx_live_ key comes from the PayBox app (Agents → create an
160
+ # agent, pick its wallets); it is shown once.
161
+ paybox login --key pbx_live_… # authenticate as the client CI will use
162
+ paybox connect # pick the code; approve on any device
163
+ grep signingKey ~/.config/paybox/config.json
164
+ ```
165
+
166
+ ```yaml
167
+ env:
168
+ PAYBOX_API_KEY: ${{ secrets.PAYBOX_API_KEY }} # pbx_live_…
169
+ PAYBOX_SIGNING_KEY: ${{ secrets.PAYBOX_SIGNING_KEY }} # pbxk1.…
170
+ ```
171
+
172
+ `PayboxClient.fromConfig()` reads both from the environment, so the runner needs
173
+ no config file, no browser, and no TTY.
174
+
175
+ Those two secrets are a **matched pair**: a signer is bound to the client that
176
+ paired it. Present a `pbxk1.` to a different client — including the throwaway
177
+ client that a plain `paybox login` registers — and signing fails with `agent
178
+ signer belongs to another client`. It reads like a revocation but means you
179
+ paired under a different identity. Rotating the API key mints a new client, so
180
+ re-pair after rotation — `paybox login` drops the stored key when it does this
181
+ and tells you to run `paybox connect`, rather than leaving one that cannot
182
+ sign.
183
+
144
184
  ## Develop
145
185
 
146
186
  `pnpm-lock.yaml` also contains the `../mcp-app` importer because this SDK
package/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: PayBox CLI setup
3
- version: 0.8.8
3
+ version: 0.8.13
4
4
  description: >-
5
5
  Install and log in to the PayBox CLI (@paybox-sh/sdk) so an agent can pay,
6
6
  sign, and hold wallet, card, and secret credentials on the user's behalf. Use
@@ -12,11 +12,11 @@ description: >-
12
12
 
13
13
  The human and agent guide is `https://docs.paybox.sh` (start at
14
14
  getting-started and connect/mcp). This file is only the CLI film. Grok
15
- (grok.com) and Grok Bots are different hosts; do not treat one as the other.
15
+ (grok.com), Grok Bots, and Muse are different hosts; do not treat one as another.
16
16
 
17
17
  ## Which host
18
18
 
19
- - **Grok Bot, Grok Computer, or any agent on a computer or VM with no connector
19
+ - **Muse, Grok Bot, Grok Computer, or any agent on a computer or VM with no connector
20
20
  screen:** this skill is the path. Install and run `paybox login` on that
21
21
  machine. Do not send the user to grok.com/connectors and do not paste the MCP
22
22
  URL as setup.
@@ -24,12 +24,67 @@ getting-started and connect/mcp). This file is only the CLI film. Grok
24
24
  grok.com/connectors. Do not run this CLI install flow unless they explicitly
25
25
  want the CLI on a separate machine.
26
26
 
27
- When the user says "install PayBox" and you are Grok Bot or running on a
27
+ When the user says "install PayBox" and you are Muse, Grok Bot, or running on a
28
28
  computer, start here immediately.
29
29
 
30
30
  The user asks to install PayBox. Two things need them: they approve wallet
31
- access with a passkey, and they paste one signing key. Everything else is yours,
32
- and they should not see it happen.
31
+ access with a passkey, and they confirm one emoji code. Everything else is
32
+ yours, and they should not see it happen.
33
+
34
+ No secret ever passes through chat. The signing key is generated on the machine
35
+ running the CLI and stays there; what you show the user is an eight-emoji code
36
+ derived from its public half, which is safe to put in a transcript.
37
+
38
+ ## Muse
39
+
40
+ On Muse this section wins over the Grok Bot phone-app rules below.
41
+ Prod is `https://api.paybox.sh` / `https://app.paybox.sh`. Stay quiet until they
42
+ say install PayBox / install paybox / Install Paybox. English. Few words. No
43
+ secret dumps. No invented click paths.
44
+
45
+ Do not send them to Settings → Connectors → Add custom, or to paste an MCP URL.
46
+ Muse **Manage Connectors** (OpenAI, Anthropic, and the rest) is not where
47
+ PayBox is added. Do not run passkey inside Muse. Muse is a chat host, not a
48
+ Grok Bot computer VM. Do not ask for done, listo, or a screenshot. Do not
49
+ fake ready.
50
+ Never wipe `~/.config/paybox`.
51
+
52
+ Two chat controls, then Welcome. Muse does **not** open those links the way
53
+ Grok Bot does. Every link you send must tell them to tap it. A bare
54
+ markdown pill with no "tap this" line is a failed take.
55
+
56
+ 1. Run device-code login (`paybox login`, or the PayBox capability Authorize
57
+ flow if this host already has it). The moment you have
58
+ `verification_uri_complete` and `user_code`, send **one** message:
59
+ **Tap [Authorize wallets](verification_uri_complete) on your phone
60
+ browser.** plus `Code: XXXX-XXXX`. One short extra line only if the
61
+ PayBox **Capability** screen shows **Authorize**: "Or tap Authorize on
62
+ Paybox under Capabilities." Never mention Add custom. Phone browser
63
+ (Safari or Chrome), not the native PayBox app and not inside Muse.
64
+ You do not open the URL. Poll until OAuth is live. Do not end the
65
+ turn while polling.
66
+ 2. When OAuth is live and the signing key is missing, run `paybox connect`
67
+ and send **one** message: **Tap [Connect signing key](url) on your phone
68
+ browser and pick this code**, plus the eight emoji the CLI printed. Use
69
+ the URL and the code exactly as `connect` printed them — do not build
70
+ the URL yourself. Then poll; `connect` returns once they pick it. Do not
71
+ ask for the key, and do not ask them to come back to chat.
72
+
73
+ Ready only when OAuth or an API key is live **and** `canSign` is true. Then
74
+ Welcome to PayBox with two buttons: **Swap 20 USDC to SOL**, **Pay a Solana
75
+ briefing**. Do the one they tap. Stop when that job is done.
76
+
77
+ If token exchange fails (`refresh_rejected` / egress): one stop line, "Host
78
+ blocks PayBox token exchange. Install NOT complete." Do not offer Settings →
79
+ Add custom as a workaround. If a PayBox capability with **Authorize** is
80
+ visible, point only there. If MCP is already Connected under Manage your MCP
81
+ servers, use that grant — still no Add custom story.
82
+
83
+ Install the CLI only when this host still needs device-code:
84
+ `npm install -g @paybox-sh/sdk@latest`, then `paybox --json version` (2.0.0+;
85
+ older builds pair no signing key and ask for a paste instead).
86
+ Fresh `PAYBOX_CONFIG_DIR`. Unset `PAYBOX_API_URL` `PAYBOX_API_KEY`
87
+ `PAYBOX_SIGNING_KEY` `PAYBOX_ACCESS_TOKEN`.
33
88
 
34
89
  ## Install
35
90
 
@@ -90,9 +145,9 @@ Send that link even though the CLI also tries to open a browser. When the CLI
90
145
  is not on the user's machine, that local open is useless or invisible, and your
91
146
  message is the only way they reach the page.
92
147
 
93
- On a phone, Authorize wallets should open the PayBox app. That is the
94
- correct path. After they tap Connect CLI, the app opens Generate a signing
95
- key by itself. Do not tell them to come back to chat first.
148
+ On Grok Bot, Authorize wallets should open the PayBox app. That is the
149
+ correct path there. Do not tell them to come back to chat first. On Muse
150
+ use the Muse section: phone browser, not the native app, not inside Muse.
96
151
 
97
152
  Then wait. The poll is the continue signal. Do not ask the user to type `done`,
98
153
  confirm, or send a screenshot, and do not end your turn while login is running.
@@ -101,43 +156,47 @@ one rather than reusing the old link.
101
156
 
102
157
  A `clientId` already on disk when this `paybox login` starts is the previous
103
158
  client. Ignore it. `login` registers a new client and only writes `oauth` after
104
- the poll succeeds. If you restart login for a fresh code, drop any signing-key
105
- link you already sent.
106
-
107
- After **this** login writes `oauth` (or prints `/agent-key?client_id=`), send
108
- the signing-key controls. Prefer that printed URL. If stderr never printed it
109
- but this run did save `oauth`, build
110
- `https://app.paybox.sh/agent-key?client_id=<cid>` from the `cid` claim on the
111
- access token this login just wrote. Never use `oauth.clientId`, a `pbx-oauth-`
112
- id, or a `clientId` that was already in `config.json` when the command started.
113
- Those are the OAuth registration, not the vault agent. `/agent-key` will say
114
- the agent was not found. Do not wait for a dropped Windows executor to
115
- reconnect before they see the box. Merge the key when the machine is back;
116
- showing the box does not need that machine.
117
-
118
- ### Step 2 — Paste the signing key
119
-
120
- The moment **this** login has an agent-key URL (stderr, or `cid` from the oauth
121
- it just saved), put **Generate Signing Key** in chat as that link plus a
122
- **masked input** for the `pbxk1.` value, in the same turn. Seconds after they
123
- finish Authorize wallets, not minutes. That chat link must use the JWT `cid`
124
- and must open the PayBox app on the first tap, the same way Authorize wallets
125
- does. Do not `Start-Process`, `open`, `rundll32`, or `xdg-open` the authorize,
126
- connect, or agent-key URL yourself. Write what they give you to login's
127
- prompt stdin when that prompt is alive. If login is wedged after this oauth
128
- write, keep the box on screen and merge `signingKey` when the host can write
129
- files again.
130
-
131
- Pass it through the prompt, or set `PAYBOX_SIGNING_KEY` before running `login`
132
- when nothing is attached to stdin. Never pass it as `login --signing-key <value>`
133
- on the command line: an argument lands in shell history and is visible to anyone
134
- who can list processes.
135
-
136
- Do not ask the user to click a signing-key URL that never arrived. Do not open
137
- the page twice. Do not hold the box until login narrates success.
138
-
139
- Wallet signing is non-custodial: the key stays on the user's machine and PayBox
140
- never holds it. Never print, log, or echo it.
159
+ the poll succeeds. If you restart login for a fresh code, drop any pairing link
160
+ and code you already sent — a pairing is bound to the client that started it.
161
+
162
+ ### Step 2 — Confirm the pairing code
163
+
164
+ Run `paybox connect` as soon as `login` returns — it does not pair for you.
165
+ Run it again any time the key is missing later. The CLI prints eight emoji and
166
+ a link to stderr:
167
+
168
+ ```
169
+ 🍕 🎸 🦊 🚀 🌵 🥁 🐙 🪐
170
+ Open this on any device and pick that code to connect this machine:
171
+ https://app.paybox.sh/agent-key?client_id=…&pairing_id=…
172
+ ```
173
+
174
+ Relay both in one message, seconds after they finish Authorize wallets, not
175
+ minutes: the link plus that exact code. PayBox shows three codes and they pick
176
+ yours. Use the printed URL verbatim — it carries the `pairing_id`, and the
177
+ `client_id` comes from the server, so there is no id for you to choose. On Grok
178
+ Bot the link must open the PayBox app on the first tap, the same way Authorize
179
+ wallets does. On Muse tell them to tap it in the phone browser.
180
+
181
+ Do not `Start-Process`, `open`, `rundll32`, or `xdg-open` the authorize,
182
+ connect, or agent-key URL yourself. Then wait — `connect` polls and returns on
183
+ its own. Do not ask the user to type `done`, do not send the link twice, and do
184
+ not re-run `connect` while one is still polling; a second pairing invalidates
185
+ the code they are looking at.
186
+
187
+ Never ask the user for the key itself, and never accept one if they offer it —
188
+ there is no step in this flow that needs it. The emoji code is safe to send;
189
+ a `pbxk1.` value is not. Wallet signing is non-custodial: the key is generated
190
+ on the user's machine and stays there, and PayBox never holds it.
191
+
192
+ `PAYBOX_SIGNING_KEY` exists for headless runs that reuse a key paired earlier
193
+ on another machine. It is not part of this flow, and there is no flag that
194
+ takes a key on the command line — an argument would land in shell history and
195
+ be visible to anyone who can list processes.
196
+
197
+ `login` clears any stored signing key, because it registers a new client and a
198
+ key paired to the previous one cannot sign. That is why pairing follows login
199
+ rather than preceding it.
141
200
 
142
201
  ### Step 3 — Confirm
143
202
 
@@ -145,17 +204,22 @@ never holds it. Never print, log, or echo it.
145
204
  paybox --json whoami
146
205
  ```
147
206
 
148
- `canSign` must be `true`. If it is `false` the key did not save; redo step 2
149
- rather than continuing, because signing and swaps will fail without it.
207
+ `canSign` must be `true`. If it is `false` the pairing did not complete; run
208
+ `paybox connect` again for a fresh code rather than continuing, because signing
209
+ and swaps will fail without it.
150
210
 
151
211
  ## What the user sees
152
212
 
153
213
  From the install request until `canSign` is `true`, exactly two messages, in
154
214
  this order:
155
215
 
156
- 1. The **Authorize wallets** link plus the code.
157
- 2. **Generate Signing Key** plus the masked `pbxk1.` field, as soon as
158
- **this** login prints `/agent-key?client_id=` or writes a new `oauth`.
216
+ 1. The **Authorize wallets** link plus the code. On Muse, the line says
217
+ to tap that link on the phone browser. Grok Bot can omit that; it
218
+ opens the link itself.
219
+ 2. **Connect signing key** — the link `connect` printed, plus the eight
220
+ emoji, as soon as it prints them. Same on both hosts; there is no
221
+ input field and nothing for them to send back. Grok Bot opens the
222
+ link itself; on Muse tell them to tap it in the phone browser.
159
223
 
160
224
  Nothing else. No "Installing…", "Opening…", "Got it", no CLI version, package
161
225
  manager, config path, or request id, and no narration of a retry or fallback. If
@@ -232,24 +296,30 @@ catalog index. There may be tens or thousands.
232
296
  app, not a host Settings → Plugins screen. Ask them to turn on what
233
297
  they want to use, then say when they are back.
234
298
 
235
- 2. Then call `discover_plugins` with `status` `enabled`, no `query`, and
236
- a small `limit`. That page is what they have on, not the store. From
299
+ 2. Then run `paybox --json plugins list --status enabled --limit 10`. That page
300
+ is what they have on, not the store. From
237
301
  those plugins plus the portfolio, suggest a few concrete next moves
238
302
  (about six, not one row per plugin). Use each result's `name` and
239
303
  contract. Be specific: amounts, which wallet UUID, which market or
240
304
  vault from the contract. Mix two enabled plugins in one suggestion
241
305
  when that serves the same goal (for example fund or swap, then put
242
- the result to work). If nothing is enabled, skip the suggestion list
243
- and stay on the Plugins link.
244
-
245
- When they name a plugin or a job, search: `discover_plugins` with `query`
246
- set to what they asked, `status` `enabled` if they already turned it on,
247
- then page with `next_cursor` only while the current page has no match.
248
- Official plugin money uses MCP: `get_contract` with `contract_uri`
249
- unchanged → `use_plugin` with that contract's `plugin_id`, `tool_id`,
250
- and `input`. Do not invent a `paybox <plugin>` command; only use CLI
251
- groups that `paybox --help` already lists. Enabled plugin tools may also
252
- appear on `tools/list`. Plugin money always pauses for passkey.
306
+ the result to work). On Muse, Welcome is those two buttons first:
307
+ **Swap 20 USDC to SOL** on the granted Solana wallet, then **Pay a
308
+ Solana briefing**. Do not offer 0.001 SOL to USDC. If nothing is
309
+ enabled, skip the extra plugin list and stay on the Plugins link.
310
+
311
+ When they name a plugin or a job, search with `paybox --json plugins list
312
+ <query> --status enabled`, then page with `--cursor <next_cursor>` only while
313
+ the current page has no match. For an official plugin, run `paybox --json
314
+ plugins contract <contract_uri>` with the URI unchanged, then `paybox --json
315
+ plugins use <plugin_id> <tool_id> --input <json>` with input matching that
316
+ contract. Unofficial discovery results already contain their tool schemas;
317
+ reuse `--invocation-id` only to retrieve the exact same ambiguous call. Plugin
318
+ money always pauses for passkey.
319
+
320
+ There are no per-plugin commands. `paybox world …` and `paybox hyperliquid …`
321
+ were removed in 1.0.0; reach World and Hyperliquid through `paybox plugins`
322
+ like every other plugin.
253
323
 
254
324
  If prod MCP (`https://api.paybox.sh/mcp`) is `unreachable` or `needsAuth` while
255
325
  `paybox --json whoami` still works, say that. Do not send the human to Settings.
@@ -258,10 +328,11 @@ Do not switch to staging. Do not report the dollar as sent.
258
328
  ## A setup that went wrong
259
329
 
260
330
  - The user had to prompt you after approving wallet access.
261
- - The key field waited minutes after they finished Authorize wallets.
262
- - The signing-key URL used `oauth.clientId`, a `pbx-oauth-` id, or a
263
- `clientId` that was already on disk before this `paybox login` started.
264
- - The first Generate Signing Key tap said the agent was not in the vault.
331
+ - The pairing code waited minutes after they finished Authorize wallets.
332
+ - You rebuilt the pairing URL instead of relaying the one `connect` printed.
333
+ - You sent a code from an earlier `connect` run, so none of the three
334
+ codes in PayBox matched.
335
+ - You asked the user for the signing key, or accepted one they offered.
265
336
  - You asked the user to type `done`.
266
337
  - You opened either page twice.
267
338
  - You launched authorize, connect, or agent-key with `Start-Process` or an
@@ -272,3 +343,9 @@ Do not switch to staging. Do not report the dollar as sent.
272
343
  got its own window).
273
344
  - Setup chat contained anything besides those two messages.
274
345
  - A token, API key, or `pbxk1.` value reached the transcript.
346
+ - On Muse you sent Settings → Connectors → Add custom, or Manage Connectors.
347
+ - On Muse you opened passkey inside Muse.
348
+ - On Muse Authorize wallets opened in the PayBox app or inside Muse.
349
+ - On Muse you sent a link without telling them to tap it.
350
+ - On Muse you faked ready before `canSign` was true.
351
+ - On Muse token exchange failed and you invented an MCP Add custom path.