@visa/cli 4.1.0-rc.17 → 4.1.0-rc.170

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +189 -232
  2. package/dist/checkout-engine/adapters/generic.d.ts +67 -0
  3. package/dist/checkout-engine/adapters/generic.js +233 -14
  4. package/dist/checkout-engine/adapters/index.d.ts +4 -1
  5. package/dist/checkout-engine/adapters/index.js +10 -3
  6. package/dist/checkout-engine/adapters/shopify.d.ts +55 -0
  7. package/dist/checkout-engine/adapters/shopify.js +514 -0
  8. package/dist/checkout-engine/amount.d.ts +15 -0
  9. package/dist/checkout-engine/amount.js +72 -0
  10. package/dist/checkout-engine/cli-engine.d.ts +259 -4
  11. package/dist/checkout-engine/cli-engine.js +797 -43
  12. package/dist/checkout-engine/confirmed-merchants.d.ts +31 -0
  13. package/dist/checkout-engine/confirmed-merchants.js +165 -0
  14. package/dist/checkout-engine/detect.d.ts +1 -1
  15. package/dist/checkout-engine/detect.js +26 -0
  16. package/dist/checkout-engine/evidence.d.ts +4 -1
  17. package/dist/checkout-engine/evidence.js +51 -6
  18. package/dist/checkout-engine/executor.d.ts +62 -4
  19. package/dist/checkout-engine/executor.js +402 -122
  20. package/dist/checkout-engine/hosted-approval.d.ts +124 -7
  21. package/dist/checkout-engine/hosted-approval.js +381 -54
  22. package/dist/checkout-engine/index.d.ts +8 -2
  23. package/dist/checkout-engine/index.js +7 -1
  24. package/dist/checkout-engine/instrument.d.ts +7 -0
  25. package/dist/checkout-engine/instrument.js +4 -0
  26. package/dist/checkout-engine/known-merchants.d.ts +10 -0
  27. package/dist/checkout-engine/known-merchants.js +38 -0
  28. package/dist/checkout-engine/live-fill-approval.d.ts +5 -20
  29. package/dist/checkout-engine/live-fill-approval.js +20 -51
  30. package/dist/checkout-engine/mandate/card-mandate.d.ts +121 -0
  31. package/dist/checkout-engine/mandate/card-mandate.js +227 -0
  32. package/dist/checkout-engine/mandate/mandate-ledger.d.ts +178 -0
  33. package/dist/checkout-engine/mandate/mandate-ledger.js +395 -0
  34. package/dist/checkout-engine/outcome.d.ts +2 -2
  35. package/dist/checkout-engine/outcome.js +36 -1
  36. package/dist/checkout-engine/owner-only-file.d.ts +9 -0
  37. package/dist/checkout-engine/owner-only-file.js +20 -1
  38. package/dist/checkout-engine/receipt-dir.d.ts +6 -0
  39. package/dist/checkout-engine/receipt-dir.js +8 -0
  40. package/dist/checkout-engine/receipt.d.ts +42 -2
  41. package/dist/checkout-engine/receipt.js +43 -14
  42. package/dist/checkout-engine/trace-handles.d.ts +8 -0
  43. package/dist/checkout-engine/trace-handles.js +12 -0
  44. package/dist/checkout-engine/types.d.ts +20 -2
  45. package/dist/checkout-engine/unresolved-charges.d.ts +34 -0
  46. package/dist/checkout-engine/unresolved-charges.js +125 -0
  47. package/dist/checkout-engine/vgs-gateway/server-mint-client.d.ts +53 -1
  48. package/dist/checkout-engine/vgs-gateway/server-mint-client.js +78 -10
  49. package/dist/checkout-engine/vgs-live-instrument.d.ts +38 -35
  50. package/dist/checkout-engine/vgs-live-instrument.js +51 -74
  51. package/dist/checkout-engine/vic-confirmation.d.ts +18 -0
  52. package/dist/checkout-engine/vic-confirmation.js +9 -3
  53. package/dist/checkout-engine/web-bot-auth.d.ts +92 -0
  54. package/dist/checkout-engine/web-bot-auth.js +159 -0
  55. package/dist/cli.js +665 -502
  56. package/dist/mcp-apps/ucp-checkout.html +280 -0
  57. package/dist/mcp-server/index.js +516 -174
  58. package/dist/skills/pair-visa-agent/RUNTIMES.md +93 -0
  59. package/dist/skills/pair-visa-agent/SKILL.md +465 -221
  60. package/dist/subway-direct.mjs +1 -0
  61. package/install.ps1 +3 -41
  62. package/install.sh +4 -36
  63. package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
  64. package/package.json +18 -14
  65. package/server.json +3 -3
  66. package/dist/checkout-engine/inline-target.d.ts +0 -13
  67. package/dist/checkout-engine/inline-target.js +0 -37
  68. package/dist/checkout-engine/pay-args.d.ts +0 -14
  69. package/dist/checkout-engine/pay-args.js +0 -44
  70. package/dist/checkout-engine/pay.d.ts +0 -1
  71. package/dist/checkout-engine/pay.js +0 -13
  72. package/dist/checkout-engine/repo-env.d.ts +0 -11
  73. package/dist/checkout-engine/repo-env.js +0 -23
  74. package/dist/checkout-engine/run-live-fill.d.ts +0 -1
  75. package/dist/checkout-engine/run-live-fill.js +0 -443
  76. package/dist/checkout-engine/vgs-gateway/fetch-credential.d.mts +0 -74
  77. package/dist/checkout-engine/vgs-gateway/fetch-credential.mjs +0 -248
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: pair-visa-agent
3
- description: Pair a Visa CLI v4 agent identity to this device so the agent can pay on the user's behalf. You paste a link, the user enrolls on mobile web, and the credential auto-pairs back to this device no code is ever typed back. Use when the user says "pair my agent", "enroll my Visa CLI", "connect my Visa wallet", "set up my agent identity", "get me set up to pay", "log in to Visa", or asks to connect/sign up their .visa account.
4
- compatibility: Needs the `visa` CLI (`npm i -g @visa/cli@rc`) — run `node scripts/setup.mjs` to auto-install it if missing — plus network access to the Visa verify-web origin. Works in OpenClaw, Hermes, or any Agent Skills runtime.
3
+ description: Pair a Visa CLI v4 agent identity to this runtime. Sign the owner in first (agent_login), then present one authorization link, the returned public confirmation code, and the full public request-key fingerprint to the human, then poll until the runtime's local Ed25519 identity is activated. Use when the user says "pair my agent", "enroll my Visa CLI", "connect my Visa agent", or "set up my agent identity".
4
+ compatibility: Needs the `visa` CLI (`npm i -g @visa/cli@rc`) — run `node scripts/setup.mjs` to install it if missing — plus network access to the Visa authorization service. Works in OpenClaw, Hermes, or any Agent Skills runtime.
5
5
  allowed-tools: Bash(visa:*) Bash(visa-cli:*) Bash(node:*) Bash(npm:*) Bash(npx:*)
6
6
  metadata:
7
7
  author: visa
8
8
  homepage: https://visacli.sh/agents
9
- version: '0.5.0'
9
+ version: '0.7.1'
10
10
  # OpenClaw-namespaced extension (agentskills.io keeps `metadata` free-form, so
11
11
  # non-standard runtime config lives here — `user-invocable` is not a standard
12
12
  # top-level field). OpenClaw auto-installs `install[]` when `requires.bins` are
@@ -15,7 +15,7 @@ metadata:
15
15
  # until 4.1.0 is promoted to latest.
16
16
  openclaw:
17
17
  user-invocable: true
18
- emoji: '💳'
18
+ emoji: '🔐'
19
19
  requires:
20
20
  bins:
21
21
  - visa
@@ -27,80 +27,89 @@ metadata:
27
27
  - visa-cli
28
28
  ---
29
29
 
30
- # Pair a Visa CLI v4 Agent Identity
30
+ # Pair a Visa CLI v4 agent identity
31
+
32
+ Pair this runtime's locally generated identity with a human-approved Visa agent. Pairing
33
+ protocol v2 has one ceremony and three equivalent ways to drive it:
34
+
35
+ - **`pair_agent_start` / `pair_agent_poll`** — OpenClaw plugin tools.
36
+ - **`enroll_agent` with `action: "start"` / `action: "claim"`** — the `visa` MCP server tool.
37
+ - **`visa agent enroll --format json` / `visa agent enroll-claim --format json`** — the raw
38
+ CLI and universal fallback.
39
+
40
+ Use the first surface available. Do not mix this flow with older pairing or login flows;
41
+ all three surfaces above wrap the same v2 enrollment ceremony and local pending state.
42
+
43
+ **Console handoff codes are a separate, compound ceremony.** When the human brings a
44
+ one-time code minted by the Console's Create-agent flow, claim it instead of pairing:
45
+ the code carries server-held, pre-approved terms and resolves identity + spending in one
46
+ run (card auto-activates from the mint consent; wallet still needs one browser approval).
47
+ The MCP tool `agent_handoff_claim` exists only on `@visa/cli` **4.1.0-rc.159 and newer**
48
+ — on an older CLI it is absent from the served tool list and the only path is shelling
49
+ `visa agent handoff-claim <code> --format json`, a different integration with different
50
+ failure modes (the approval URL and verification code arrive mid-run as a structured
51
+ stderr frame). Never assume the tool from documentation alone: check the served list.
52
+
53
+ ## What pairing establishes
54
+
55
+ Pairing activates an agent identity on this runtime:
56
+
57
+ - `agentId` is the server-assigned, stable identifier for the agent.
58
+ - The runtime creates and retains the private Ed25519 identity key. It sends only the
59
+ public JWK to the service.
60
+ - `identityKeyJkt` is the thumbprint of the currently bound Ed25519 public key. It can
61
+ change when that key rotates, so it must never be presented or stored as the stable
62
+ agent identifier.
63
+ - The human receives the authorization URL, stable agent ID, and full public request-key
64
+ fingerprint for exact comparison. Private key material, the local claim token, and signed
65
+ protocol messages stay with the runtime.
66
+ - An `activated` result means **identity paired only**. Payment methods, an email address,
67
+ and tap bindings are separate configuration that may be added later. Do not infer any
68
+ of those capabilities from pairing success.
31
69
 
32
- Connects a Visa v4 identity (`.visa` name + delegated wallet + card) to THIS device
33
- through a mobile-web enrollment link. This is the device-link flow: the credential
34
- pairs back **automatically** to the terminal that started it. **No confirmation code is
35
- ever typed back into you** — the code is an out-of-band check the human reads to confirm
36
- the page they are on is the flow you started.
70
+ ## Getting this skill
37
71
 
38
- Driven through the local `visa` binary. **This skill is runtime-agnostic** — it works in
39
- OpenClaw, Hermes, or any agent runtime that can run the `visa` CLI or mount its MCP
40
- server (see Runtimes). Use whichever pairing surface your runtime exposes; they all wrap
41
- the same two `visa` commands:
72
+ The skill ships inside the public `@visa/cli` npm package. No clone of the private
73
+ monorepo is required:
42
74
 
43
- - **`pair_agent_start` / `pair_agent_poll`** — the OpenClaw plugin tools (parse JSON for you). Use these if present.
44
- - **`enroll_agent`** the `visa` MCP server tool, if the runtime has the server mounted.
45
- - **`visa agent enroll` / `visa agent enroll-claim`** — the raw CLI, always available if `visa` is on PATH. The universal fallback.
75
+ ```sh
76
+ npm install -g @visa/cli@rc
77
+ visa agent skill
78
+ ```
46
79
 
47
- Pick the first one available; if unsure, shell out to the raw CLI commands — every path
48
- hits the same hand-off on this device.
80
+ `visa agent skill` auto-detects OpenClaw, Hermes, Claude Code, and Codex, falling back to
81
+ the project-local `./.agents/skills`. Pass `--runtime <name>` or `--dir <path>` to choose a
82
+ target, `--force` to overwrite, or `--print` to read without writing. Reload or restart
83
+ the agent runtime after installation so it registers the skill.
49
84
 
50
- ## Getting this skill
85
+ Access remains enforced by the Visa service. Installing the public package or skill does
86
+ not authorize an account to pair.
51
87
 
52
- The skill ships **inside the public `@visa/cli` npm package** — no git clone of the
53
- (private) monorepo is required, so any agent box that can reach npm can install it:
88
+ OpenClaw users also receive the skill with the `@visa/visa-cli-openclaw` plugin.
54
89
 
55
- ```
56
- npm install -g @visa/cli@rc # public npm — puts `visa` on PATH
57
- visa agent skill # plants this skill into your runtime's skills dir
58
- ```
90
+ ## Getting set up
91
+
92
+ 1. **Install the prerelease CLI.** Run `npm install -g @visa/cli@rc`, or run the bundled
93
+ idempotent provisioner `node scripts/setup.mjs`. The `@latest` tag may not yet expose
94
+ the v4 agent commands. Installation puts `visa` and `visa-cli` on `PATH` and includes
95
+ `@visa/cli/dist/mcp-server/index.js`.
96
+ 2. **Mount the MCP server when the runtime supports MCP.**
97
+ - **OpenClaw:** installing `@visa/visa-cli-openclaw` auto-mounts the server by writing
98
+ `mcp.servers["visa-cli"]` in `~/.openclaw/openclaw.json`.
99
+ - **Hermes or another supported runtime:** run `visa-cli connect hermes` or
100
+ `visa-cli connect <runtime>`. For Hermes, the entry is written under `mcp_servers`
101
+ in `~/.hermes/config.yaml`.
102
+ - **No MCP integration:** use the raw `visa agent …` commands.
103
+ 3. **Sign the owner in.** Pairing starts are owner-bound — see "Sign in first" below. On a
104
+ fresh runtime this step is required before any pairing command will succeed.
105
+ 4. **Pair.** Follow the core flow below.
106
+
107
+ ## MCP mounting examples
59
108
 
60
- `visa agent skill` auto-detects OpenClaw / Hermes / Claude Code (falling back to the
61
- project-local `./.agents/skills`); pass `--runtime <name>` or `--dir <path>` to target
62
- one explicitly, `--force` to overwrite, or `--print` to read it without writing. After
63
- it lands, **reload/restart your agent** so the runtime registers the skill, then prompt
64
- the agent — "set up my Visa agent to pay" — to run the pairing flow below. (Installing a
65
- skill does not run it; the agent activates it on a matching prompt.)
66
-
67
- Access is gated server-side by the employee allowlist + Turnkey, so shipping the playbook
68
- over public npm exposes no capability — only allowlisted accounts can actually pair.
69
-
70
- (OpenClaw users also get it auto-bundled with the `@visa/visa-cli-openclaw` plugin. The
71
- `npx skills add` open-standard path applies once the skill is published to a public repo.)
72
-
73
- ## Getting set up (install → mount → pair → transact)
74
-
75
- The whole flow, top to bottom:
76
-
77
- 1. **Install the CLI (prerelease)** — `npm install -g @visa/cli@rc`, **or** run the bundled
78
- provisioner `node scripts/setup.mjs` (idempotent — installs `@visa/cli@rc` only if `visa`
79
- is missing; this is how **Hermes / Claude Code / any runtime** self-provisions, and what
80
- OpenClaw runs automatically via the manifest). You **must** use the `@rc` tag: the
81
- `@latest` tag (4.0.x) predates the `visa agent` commands and reports "does not expose
82
- agent enroll". This puts both `visa` and `visa-cli` on PATH and ships the bundled MCP
83
- server (`@visa/cli/dist/mcp-server/index.js`). (Once 4.1.0 is promoted to `latest`, bare
84
- `@visa/cli` will work.)
85
- 2. **Mount the MCP server** — one mount gives this agent the _entire_ v4 toolset:
86
- - **OpenClaw:** installing the `@visa/visa-cli-openclaw` plugin **auto-mounts** the server
87
- (its postinstall writes `mcp.servers["visa-cli"]` into `~/.openclaw/openclaw.json`).
88
- Nothing to do by hand.
89
- - **Hermes / anything else:** run `visa-cli install hermes` (or `visa-cli install <runtime>`).
90
- This writes the server entry idempotently into the runtime's config (Hermes →
91
- `~/.hermes/config.yaml` under `mcp_servers`). See Runtimes below for the exact shape.
92
- - **No runtime integration?** The raw `visa agent …` CLI still works — the universal fallback.
93
- 3. **Pair** — run the Core flow below. This binds a `.visa` identity + delegated wallet + card
94
- to this device.
95
- 4. **Transact** — once paired, use the mounted tools (see "What you can do once paired").
96
-
97
- ## Runtimes
98
-
99
- One artifact, both runtimes. The `visa` CLI is the portable substrate; mount its MCP
100
- server so the runtime exposes the tools (both use the same server entrypoint — replace
101
- `<npm root -g>` with the output of `npm root -g`):
102
-
103
- **OpenClaw** (`~/.openclaw/openclaw.json`):
109
+ Both runtimes use the same server entrypoint. Replace `<npm root -g>` with the output of
110
+ `npm root -g`.
111
+
112
+ OpenClaw (`~/.openclaw/openclaw.json`):
104
113
 
105
114
  ```json
106
115
  {
@@ -115,185 +124,420 @@ server so the runtime exposes the tools (both use the same server entrypoint —
115
124
  }
116
125
  ```
117
126
 
118
- **Hermes** (`~/.hermes/config.yaml`):
127
+ Hermes (`~/.hermes/config.yaml`). **Hermes passes ONLY this `env:` map to the MCP
128
+ subprocess — it does NOT inherit the gateway environment.** Omitting a required variable
129
+ (an RC access code, the right `HOME`, `PATH`) makes the server exit on every start while
130
+ `agent_capabilities` — which reads on-disk grant state, not live tool registration — can
131
+ still report rails as available. Always set the map explicitly:
119
132
 
120
133
  ```yaml
121
134
  mcp_servers:
122
135
  visa-cli:
123
136
  command: node
124
137
  args: ['<npm root -g>/@visa/cli/dist/mcp-server/index.js']
138
+ # Hermes does NOT inherit the gateway env. This map is the entire
139
+ # subprocess environment; omit VISA_RC_CODE and the server exits on boot.
140
+ env:
141
+ HOME: /home/<user> # the home that holds this runtime's .visa-cli state
142
+ VISA_RC_CODE: <access code>
143
+ PATH: /usr/local/bin:/usr/bin:/bin
125
144
  ```
126
145
 
127
- No MCP server configured? The raw `visa agent …` CLI commands below work as long as `visa`
128
- is on PATH. (In Hermes, `hermes claw migrate` also imports this skill from an existing
129
- OpenClaw install into `~/.hermes/skills/`.) See `RUNTIMES.md` for the full plugin/config map.
146
+ Hermes also loads skills **per profile** from `~/.hermes/profiles/<profile>/skills/`, not
147
+ from `~/.hermes/skills/`. `visa agent skill --runtime hermes` resolves this automatically:
148
+ it targets the single profile when exactly one exists (or the one named by
149
+ `HERMES_PROFILE`), and **fails loudly** on a multi-profile box instead of planting into
150
+ the flat dir nothing reads — pass `--dir ~/.hermes/profiles/<profile>/skills` to choose.
151
+
152
+ **Hermes sanitizes MCP server names when registering tools.** A server declared
153
+ `visa-cli` in `mcp_servers:` registers its tools as `mcp__visa_cli__<tool>` — with an
154
+ UNDERSCORE, not the declared hyphen. Anything that hardcodes `mcp__visa-cli__<tool>` gets
155
+ `unknown tool` on every call while looking correct in review. Read tool names off the
156
+ live registry; never derive them from the config key.
157
+
158
+ In Hermes, `hermes claw migrate` can also import this skill and MCP configuration from an
159
+ existing OpenClaw installation. See `RUNTIMES.md` for the complete runtime map.
160
+
161
+ ## JSON and exit-code contract
162
+
163
+ Prefer structured output and parse it; never scrape prose.
164
+
165
+ - The OpenClaw and MCP tools return structured objects directly.
166
+ - Raw CLI commands support `--format json`.
167
+ - `visa agent enroll-claim` exits `0` when activated, `3` when the human has not finished,
168
+ and `1` on a terminal failure.
169
+
170
+ The start result includes protocol version `2`, `pairingId`, stable `agentId`, full
171
+ `requestKeyFingerprint`, `browserUrl`, and expiry information. Newer builds also return
172
+ `confirmationCode`, a short public code the browser review page displays for comparison.
173
+ Present it when the field is present and skip it when it is absent; an older server or CLI
174
+ simply omits it, which is not an error. The activation result includes protocol version
175
+ `2`, `pairingId`, stable `agentId`, the display name, and `identityKeyJkt`.
176
+
177
+ ## Sign in first — pairing is owner-bound
178
+
179
+ Pairing starts are authenticated as the owner: the service pins the owner account at
180
+ initiation, and the browser review page rejects any other signed-in account
181
+ (`owner_profile_mismatch`). So on a fresh runtime the ceremony has a mandatory first leg —
182
+ establish the owner session **before** any pairing command:
183
+
184
+ - [ ] Call `agent_login` (MCP, default action `"start"`) or run
185
+ `visa agent login --format json` (CLI). The result carries a sign-in `browserUrl`
186
+ and a short 6-character confirmation code.
187
+ - [ ] Relay **both** to the human in your reply — the bare URL on its own line, and the
188
+ code. You are very often not in a terminal they can see; the chat message is the
189
+ only place these values reach them.
190
+ - [ ] The human opens the link, signs in (Google or email), and **types the confirmation
191
+ code into the sign-in page** — into the browser, never back to you in chat.
192
+ - [ ] Claim the session: `agent_login {"action":"claim"}` (the CLI command polls on its
193
+ own). Once claimed, the session token is stored locally and pairing can start.
194
+
195
+ If a pairing command returns "An owner profile is required before pairing" or
196
+ `{"code":"session_required"}`, that is this ordering rule — not a fault. Run `agent_login`,
197
+ drive the sign-in above to a claimed session, then retry the same pairing command once.
198
+
199
+ The account that signs in here is the owner the pairing binds to. The same person must
200
+ approve the pairing link in the next step from that same signed-in browser session;
201
+ a different account on the review page fails closed.
202
+
203
+ ## Fastest path — one shot (`visa agent pair`)
204
+
205
+ If the `visa` CLI is on PATH, prefer the one-shot command — after the owner session above
206
+ exists, it does the whole flow from a single call, so the user types nothing after their
207
+ initial request:
130
208
 
131
- ## Running commands
209
+ ```
210
+ visa agent pair --format json
211
+ ```
132
212
 
133
- - Every command supports `--format json`; the tools use it. On success JSON goes to
134
- stdout; on failure a JSON object with `error` (and, for claim, a `status`) goes to
135
- stdout with a non-zero exit code. **Always parse the JSON — never scrape prose.**
136
- - `enroll-claim` exit codes are load-bearing: `0` = claimed (done), `3` = not ready yet
137
- (poll again), `1` = terminal failure (stop, recover).
213
+ On a runtime with no owner session it fails with "An owner profile is required before
214
+ pairing" run the sign-in leg above first, then re-run it.
215
+
216
+ It returns `browserUrl`, `pairingId`, stable `agentId`, the full public
217
+ `requestKeyFingerprint`, and on newer builds `confirmationCode` **immediately**. It
218
+ starts a detached activation process only after the pending identity and private key are
219
+ durable. Present the link, returned confirmation code, and fingerprint for browser
220
+ comparison; omit only a confirmation code the result did not provide. When `claiming` is `background`,
221
+ activation completes after approval without another command; if it is `manual`, run the
222
+ returned `recoveryCommand` after approval. Use the step-by-step flow
223
+ below when `pair` is unavailable or when driving the plugin/MCP tools — `enroll_agent`
224
+ action `start` reports the same `claiming` / `recoveryCommand` fields and starts the same
225
+ detached poller.
226
+
227
+ ## Returning & already-connected — do NOT re-pair
228
+
229
+ If `pair`/`enroll` returns **`already_connected`** (or `alreadyConnected: true`), this
230
+ device is **already set up** as an agent — a fresh pairing is neither needed nor
231
+ possible (re-pairing an existing identity silently dead-ends). Do this instead:
232
+
233
+ 1. **Tell the user plainly:** "This device is already connected as `<name>.visa`."
234
+ Read the name from the response. Do **not** start another hand-off. `--new` /
235
+ `force:true` exists only if they explicitly want a _second, separate_ agent.
236
+ 2. **Report status honestly — "connected" is five separate things**, not one. Never
237
+ imply the agent can spend just because it's connected. Don't guess from prose —
238
+ read it live from tools: `agent_capabilities` returns the DERIVED capability map
239
+ (the identity + wallet + mail base plus card/tap/subway availability),
240
+ `get_status` reports enrollment / account / version, and `agent_login` establishes
241
+ or confirms the account session that spending grants require.
242
+ - **Identity** — connected (`.visa` name bound to _this user's_ account). ✓ once `already_connected`.
243
+ - **Spending** — a _separate human approval_, and there are two rails, each
244
+ approved on the **v4 agent dashboard** (`app.visacli.sh/agent/enroll`) — not any
245
+ account-settings page (that legacy surface is retired). The Turnkey
246
+ **wallet:x402** rail (stablecoin) is approved with the owner's **sign-in session**
247
+ (the Google/email auth-proxy) — **no passkey**. The **card:vic** rail is approved
248
+ with a **passkey**. You **cannot** self-grant either; there is no
249
+ `visa agent add-rail` command, and never self-mint a wallet with `wallet_init` on
250
+ mainnet — it throws until the owner's delegation lands.
251
+ - **Mesh (`.visa` messaging)** — with `SUBWAY_MESH=visa`, a returning device now
252
+ registers on `pair`; if `meshRegistered` is false, `visa register <name>` joins it.
253
+ - **Trusted (TAP)** — follows spend/provisioning; don't promise it before then.
254
+ 3. **Scope everything to the user.** The identity is bound to the account the user
255
+ signed in with (their email); the wallet + spend limits are theirs. Speak in terms
256
+ of "your agent / your account / the limits you approved," never a shared identity.
257
+
258
+ Missing **spend** blocks _checkout mandates_, not a delegated x402 wallet — but that
259
+ wallet must first be acquired by owner approval (below); it is never self-minted.
260
+
261
+ ## What you can do once paired — granting spend authority (card or wallet)
262
+
263
+ Pairing binds **identity only**. To let this agent pay, the human owner must **delegate**
264
+ a spend rail — the runtime never self-mints one. There are two rails, and **one** ceremony
265
+ drives both:
266
+
267
+ - **`wallet:x402`** (Turnkey stablecoin) — owner approves with their **sign-in session, no
268
+ passkey**. On mainnet `wallet_init` throws `WalletCredentialRequiredError` until this
269
+ delegation lands, so never call it as a setup step.
270
+ - **`card:vic`** (Visa card checkout) — owner approves with a **passkey**.
271
+
272
+ The contract is identical for both rails, and it is **one command from you, one approval
273
+ from the owner**. You never hand a command, code, or URL back to the human after they
274
+ approve — you poll to completion yourself:
275
+
276
+ 1. **You run one command** naming the rail and caps.
277
+ 2. **The owner opens the returned link and approves once** (they may adjust the amount).
278
+ 3. **You poll to activation** — the owner does nothing further.
279
+
280
+ - [ ] **Pair** the identity (the flow above). Identity only — no rail yet.
281
+ - [ ] **Establish the owner session once.** Grant creation is an account operation, so this
282
+ runtime needs a live owner session: `agent_login` (MCP) or `visa agent login` (CLI).
283
+ If you just paired, the session from the sign-in-first leg is usually still live —
284
+ no re-login needed. This is a short-lived session established once — **not** a
285
+ re-pair, and **not** something the owner repeats per payment. If `agent_connect`
286
+ later returns `{"code":"session_required"}`, the session lapsed — run `agent_login`
287
+ again.
288
+ - [ ] **Initiate the grant from MCP — no shelling, no invented URLs.** Call `agent_connect`
289
+ with the rail and caps: `{"rail":"card","ceiling":"<usd>","perTransaction":"<usd>"}`
290
+ (or `"rail":"wallet"`); omit `agentId` to target the most recently paired agent. It
291
+ returns `{ url, code, attachId, willGrant, expiresAt }`. Present the **bare `url` and
292
+ `code` exactly as returned** — never construct, shorten, or guess a Visa URL, and never
293
+ open it yourself. The crypto approval happens in the owner's browser and cannot run
294
+ inline in chat. (`setup_agent {"rail":"card"|"wallet"}` returns the same next-step map
295
+ if you need it.)
296
+ - [ ] **Owner approves once** on the **v4 agent dashboard** (`app.visacli.sh/agent/enroll`),
297
+ confirming the caps — **card with a passkey, wallet with their sign-in session**. You
298
+ cannot approve on their behalf.
299
+ - [ ] **Poll to activation from MCP.** Call `agent_connect_poll` (`{"attachId":"<from
300
+ agent_connect>"}`; or resume by `agentId`) — one bounded poll per call. It returns
301
+ `{"ok":false,"state":"...","blockedByKind":"awaiting_human_approval"}` while pending;
302
+ call again until `{"ok":true,"state":"grant_activated","caps":...}` (wallet also
303
+ returns `fundAddress`). Activation registers the delegated signer + caps: wallet writes
304
+ the Turnkey credential (`turnkey.json`); card writes the card pointer. It never spends.
305
+ - [ ] **Confirm the rail is live.** Wallet: `wallet_status` / `visa wallet show`
306
+ (`agent_capabilities.wallet.available`). Card: `agent_capabilities.card.available`.
307
+ Only a delegated credential — not the served tool list — means the rail is usable.
308
+ - [ ] **Then spend against the owner-approved caps.** Wallet: set the policy with
309
+ `wallet_policy_set` (per-transaction / daily / session USD caps + optional network and
310
+ merchant allow/deny lists that refuse an x402 payment BEFORE it is signed), then
311
+ `wallet_pay`. Card: `start_card_mandate` then `pay_merchant`. Never raise a
312
+ human-approved limit yourself.
313
+
314
+ **Raw CLI equivalent (one shot).** If you cannot drive MCP, the same ceremony runs from the
315
+ CLI and `--wait` polls to activation in a single call. Present the URL it prints — don't
316
+ invent one:
138
317
 
139
- ## Core flow
318
+ ```
319
+ visa agent grant-card <agentId> --ceiling <usd> --per-transaction <usd> --wait
320
+ visa agent grant-wallet <agentId> --ceiling <usd> --per-transaction <usd> --wait
321
+ ```
140
322
 
141
- Copy this checklist and track progress:
323
+ Rail selection is the owner's call — wallet only, card only, or both. Newer builds also
324
+ accept one compound command that runs the same ceremony per rail in sequence
325
+ (`both` = card, then wallet, under the same caps — expect one extra approval trip):
142
326
 
143
- - [ ] Step 1 — Start the hand-off (`pair_agent_start` / `enroll_agent` / `visa agent enroll --format json`)
144
- - [ ] Step 2 Present the link AND the confirmation code to the user
145
- - [ ] Step 3 — Poll until paired (`pair_agent_poll` / `visa agent enroll-claim --format json`) — auto-pairs, no code entry
146
- - [ ] Step 4 — Confirm the identity is bound to this device
327
+ ```
328
+ visa agent connect --authority <card|wallet|both> --ceiling <usd> --per-transaction <usd> --wait
329
+ ```
147
330
 
148
- Below, "start" and "poll" mean whichever surface your runtime exposes (see the top of
149
- this skill). The returned fields and the poll semantics are identical across all of them.
331
+ Use the stable `agentId` from pairing or `visa agent list` never invent or alter the id.
332
+ A policy refusal or timeout means **nothing was signed**: report it and stop; do not retry
333
+ with a different command or a reconstructed URL.
150
334
 
151
- ### Step 1 Start the hand-off
335
+ Once a wallet is delegated and a policy is set, these are the served wallet tools this agent
336
+ can actually call: `wallet_discover` (search the public x402 Bazaar), `wallet_probe` (read a
337
+ challenge without paying), `wallet_pay` / `wallet_directory_pay` (pay, policy-enforced),
338
+ `wallet_history` / `wallet_reconcile` (local ledger + resolve `reconciling` holds),
339
+ `wallet_fund` (funding address + faucet), and `wallet_export` (export key material —
340
+ dangerous). All spending is gated by the owner-approved local policy caps.
341
+
342
+ ## Core flow
152
343
 
153
- **CRITICAL:** Only ONE hand-off can be in flight per device; starting a new one replaces
154
- any prior. If the user says they already started enrolling, go to Step 3 first.
344
+ - [ ] Establish the owner session ("Sign in first" above) if this runtime does not have
345
+ one yet.
346
+ - [ ] Start the pairing with `pair_agent_start`, `enroll_agent` action `start`, or
347
+ `visa agent enroll --format json`.
348
+ - [ ] Present the authorization URL, stable agent ID, full public fingerprint, and the
349
+ `confirmationCode` when the start result carries one, to the human.
350
+ - [ ] Poll with `pair_agent_poll`, `enroll_agent` action `claim`, or
351
+ `visa agent enroll-claim --format json`.
352
+ - [ ] Report the stable `agentId` and that the identity is paired on this runtime.
155
353
 
156
- Start the hand-off (`pair_agent_start`, or `visa agent enroll --format json`). It returns
157
- `browserUrl`, `confirmCode`, `expiresAt`.
354
+ ### Completion is `activated` nothing else
158
355
 
159
- If it returns an error containing `verify-web URL not available in stable builds`, this is
160
- a stable CLI build with the surface not yet public. **Do NOT proceed** tell the user to
161
- set `VISA_VERIFY_WEB_URL` (or use a preview/RC build), then stop.
356
+ Only a poll result of `activated` means this runtime is paired. Until you have one, do not
357
+ tell the human the agent is connected, paired, enrolled, set up, ready, or good to go, and
358
+ do not move on to spending, mail, or mesh steps that assume an identity.
162
359
 
163
- If it returns an error containing `RC build requires access` (often suffixed `Run: visa-cli
164
- setup`), this is an **older RC build** whose employee gate still fires on the pairing
165
- bootstrap. **Do NOT run `visa-cli setup`** that is the legacy v3 GitHub-OAuth path, it is
166
- NOT how v4 pairing authenticates, and it dead-ends on the same gate. Recover by either
167
- upgrading to a current RC (`npm install -g @visa/cli@rc` — recent RCs exempt `agent enroll`
168
- / `agent enroll-claim` from this gate) or, if the operator has the code, setting the
169
- `VISA_RC_CODE` env var on the host. Then retry the hand-off. Never substitute `setup` for
170
- pairing.
360
+ `start` returning successfully is not completion. It returns a stable `agentId` and a
361
+ `requestKeyFingerprint` **before any human has approved anything** those are review
362
+ values for the browser comparison, not evidence of pairing. Reporting an `agentId` as
363
+ though it were a finished pairing is the most likely way to mislead the human here, because
364
+ the number looks like a result.
171
365
 
172
- ### Step 2 Present the link AND the confirmation code
366
+ If you cannot reach `activated`, say plainly what state you did reach and what the human
367
+ should do next. An honest "approved but not yet activated — I'm still polling" is correct;
368
+ "you're all set" without an `activated` result is not.
173
369
 
174
- Show BOTH clearly. The user opens the link on their phone.
370
+ ### 1. Start
175
371
 
176
- **Formatting rules make it clean AND tappable in chat clients like Telegram:**
372
+ Start once. If there is already a pending pairing, poll it before creating another.
177
373
 
178
- - **Bare, tappable link.** Put `browserUrl` on its own line as a plain URL no backticks, no
179
- code span, and do **not** wrap it as a Markdown `[label](url)` link. Telegram auto-linkifies
180
- a bare URL (one tap opens the phone's browser); a code span is unclickable, and the long
181
- pairing query string (`?cli=…&cliPk=…`, with underscores) breaks Telegram MarkdownV2 link
182
- parsing. Add a short "👉 Tap to open on your phone" cue so it reads as an action.
183
- - **Emphasize the confirm code.** Show `confirmCode` in **bold** or `monospace` — both are
184
- Telegram-safe (mono needs no escaping). It's read/compared, never clicked.
185
- - **Relative expiry.** "expires in about 15 minutes" — never the raw `expiresAt` ISO/UTC
186
- timestamp (e.g. `2026-07-21T21:22:57Z`); a UTC time reads as noise.
187
- - **Scannable layout.** Short lines, a blank line between blocks, one leading emoji per block.
188
- - **Don't over-Markdown.** Telegram MarkdownV2 requires escaping `_ * [ ] ( ) ~ > # + - = | { } . !`,
189
- so heavy formatting risks a broken render. A bold/mono code + a bare link + emojis is plenty —
190
- keep the rest plain prose.
374
+ The result provides `browserUrl`, stable `agentId`, `requestKeyFingerprint`, andon newer
375
+ builds `confirmationCode`. Show the URL as a bare, tappable value on its own line. Do not
376
+ decorate it as a Markdown link or put it in a code span; chat clients reliably recognize the
377
+ bare URL. Show the complete fingerprint without truncation and tell the human to approve
378
+ only when every character matches the browser review page.
191
379
 
192
- Use this shape:
380
+ You are very often **not** in a terminal the human can see. Nothing you print to stdout
381
+ reaches them. Every value the browser asks them to compare has to appear in your reply, or
382
+ the comparison silently becomes "click approve and hope" — which is the whole failure this
383
+ step exists to prevent.
193
384
 
194
- > 💳 **Pair your Visa agent** — open this on your phone:
385
+ Use this concise shape:
386
+
387
+ > 🔐 Pair your Visa agent — open this authorization page:
388
+ >
389
+ > 👉 &lt;browserUrl, bare and on its own line&gt;
390
+ >
391
+ > Stable agent ID: &lt;agentId&gt;
195
392
  >
196
- > 👉 &lt;browserUrlbare, on its own line&gt;
393
+ > Confirmation codethis exact code should appear on the page:
394
+ > &lt;confirmationCode&gt;
197
395
  >
198
- > 🔐 Confirmation code: **&lt;confirmCode&gt;**
396
+ > Public request-key fingerprint — compare every character in the browser:
397
+ > &lt;requestKeyFingerprint, complete and untruncated&gt;
199
398
  >
200
- > On the final screen, check the code matches before you finish only finish if it does. Never
201
- > type this code back to me or share it with anyone; it pairs back here automatically once you
202
- > finish. Expires in about 15 minutes.
203
-
204
- **Do NOT** ask the user to read the code back. **Do NOT** accept a code as input. Pairing
205
- is proven by a secret held on this device, not by anything the user types.
206
-
207
- ### Step 3 Poll until paired (auto-pair)
208
-
209
- Poll (`pair_agent_poll`, or `visa agent enroll-claim --format json`). With the raw CLI,
210
- read the **exit code** (`0` claimed / `3` not-ready / `1` terminal); the plugin tool
211
- returns the same as a `done`/`status` object. Then, based on the result:
212
-
213
- - `done: true` / exit 0, `status: "claimed"` → paired. Go to Step 4.
214
- - `done: false` / exit 3, `status: "not_ready"` the user hasn't finished. Tell them
215
- you're still waiting, then poll again. **Do not loop forever** each poll already
216
- waits ~9s; after a handful of polls, ask the user whether they've finished on their phone.
217
- - `done: true`, `status: "not_ready"`, `likelyExpired: true` the 15-minute window
218
- expired. Go back to Step 1.
219
- - `done: true`, `status: "confirm_mismatch"` → **STOP.** See Errors.
220
- - `done: true`, `status: "no_pending"` no hand-off in flight. Go back to Step 1.
221
-
222
- ### Step 4 Confirm the identity is bound
223
-
224
- On `claimed`, tell the user their `name` (e.g. `alec.visa`) is paired to this device. Read
225
- `keyBound`:
226
-
227
- - `keyBound: true` → the agent key was generated on THIS device; its private half never
228
- left it. Highest assurance.
229
- - `keyBound: false` an existing agent was connected from a returning sign-in; no agent
230
- key is held locally. Normal and expected for returning users the credential is fully
231
- valid.
232
-
233
- ## What you can do once paired
234
-
235
- Pairing is the on-ramp. v4 is **non-custodial** a Turnkey-delegated wallet bounded by
236
- on-device keys and policies, **not** a stored credit line or a server-custodied card.
237
- Describe it that way to the user. The mounted MCP server exposes:
238
-
239
- - **x402 wallet spend (the core rail)** — `wallet_discover` (find payable x402 services by
240
- outcome; free) `wallet_probe` (preview a fresh x402 challenge; free) `wallet_pay` /
241
- `wallet_directory_pay` (a bounded payment settled directly from the delegated wallet over
242
- x402, wallet→endpoint — never touching credits, cards, or server-side spend controls).
243
- These `wallet_*` tools are **default-on in the supported build** (the v4 wallet runtime is
244
- bundled); spend stays bounded by the on-device wallet policy. The same rail is on the CLI
245
- as `visa find` / `inspect` / `pay`.
246
- - **Visa catalog (curated tools)** `discover_tools` `execute_tool` (or `visa tools` /
247
- `visa run`): enumerate the curated catalog and run a tool; paid ones show a preview and
248
- settle inline. Distinct from the open x402 directory above.
249
- - **Subway mesh** `subway_*` route calls across the agent mesh. **Gated:** present but only
250
- reach the mesh once the `.visa` relay is wired (`SUBWAY_MESH` / `SUBWAY_RELAY_MULTIADDR`);
251
- until then they no-op against an unreachable relay.
252
- - **Real-merchant card checkout (experimental, opt-in)** — `pay_merchant` fills and pays an
253
- ordinary merchant web checkout with a **Verified Agent card credential**: a one-shot
254
- network-token cryptogram minted **on this device** (non-custodial) not a stored card,
255
- not x402, not server-side spend controls. Two steps: `review` (free; returns merchant +
256
- exact amount as a `reviewId`) then `pay` (requires `confirm: "PAY <reviewId>"` + a passkey,
257
- and CHARGES). **Tester-gated** (`checkout_agent_access`) and **RC/preview builds only** —
258
- opt-in, never paired-and-go.
259
-
260
- If a tool you expect isn't visible, the MCP server isn't mounted (or the v4 wallet runtime
261
- isn't bundled in this build) — go back to "Getting set up".
262
-
263
- ## Important
264
-
265
- - **The confirmation code is display-only.** Show it; never ask for it; never accept it as
266
- input. It lets the human verify the page matches the flow you started.
267
- - **Never read, print, log, or echo** the credential file or the enrollment pending file,
268
- or the `browserUrl` query values beyond the single presentation to the user. The link
269
- carries only a hash + a public key safe in history — but treat it as one-time.
270
- - Do not run API-key setup, card enrollment, or a balance top-up as a substitute for
271
- pairing. Once paired, use the `visa-cli` skill's Rail 1 commands to pay.
272
-
273
- ## Limits
274
-
275
- | Limit | Value |
276
- | ------------------------------ | ------------------------------------------------------------ |
277
- | Hand-off validity | 15 minutes from `pair_agent_start` |
278
- | In-flight hand-offs per device | 1 (a new start replaces the prior) |
279
- | Claim | single-shot server-side; once claimed the entry is destroyed |
280
- | Confirmation code | 6 chars, no ambiguous glyphs; display-only |
281
-
282
- ## Errors
283
-
284
- All errors are JSON with a non-zero exit code; `enroll-claim` tags them with `status`.
285
-
286
- | status / symptom | Cause | Recovery |
287
- | ----------------------------------------------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
288
- | `error: verify-web URL not available in stable builds` | Stable CLI build, surface not public | Set `VISA_VERIFY_WEB_URL` or use a preview build. Do not proceed otherwise. |
289
- | `error: RC build requires access` (may say `Run: visa-cli setup`) | Older RC build whose employee gate still fires on the bootstrap | **Do NOT run `visa-cli setup`** (legacy v3 GitHub-OAuth path; dead-ends on the same gate). Upgrade the RC (`npm install -g @visa/cli@rc`) or set `VISA_RC_CODE`, then retry the hand-off. |
290
- | `no_pending` | No hand-off in flight (never started, or already claimed/expired) | Start fresh with `pair_agent_start`. |
291
- | `not_ready`, `likelyExpired: false` | User hasn't finished the mobile flow | Wait, tell the user, poll again. Bounded polling only. |
292
- | `not_ready`, `likelyExpired: true` | 15-minute window elapsed | Start over with `pair_agent_start`. |
293
- | `confirm_mismatch` | The claim didn't match THIS device's secret | STOP. Show the re-derived `confirmCode`. If the user did not just finish the flow, someone else may hold their link — start over. Never retry blindly. |
294
- | `error` (network / malformed) | Transport or server error | Surface the message. Poll once more; if it persists, start over. |
399
+ > Approve only if the code and fingerprint both match. The link expires shortly I'll keep
400
+ > watching and confirm here the moment it activates.
401
+
402
+ Omit the confirmation-code line entirely when the start result has no `confirmationCode`;
403
+ never invent, derive, abbreviate, or reformat one.
404
+
405
+ Relaying these values **to** the human is required. Accepting one **from** the human is not:
406
+ do not ask them for a code, secret, private key, token, or signed message, and do not treat
407
+ anything they type back as approval. The URL, stable agent ID, confirmation code, and public
408
+ fingerprint are review values the human checks against their own authenticated browser
409
+ session. They are not claim credentials, they cannot approve a pairing, and they cannot
410
+ spend. Approval happens only in that browser session, and the only evidence of it is a poll
411
+ result of `activated`.
412
+
413
+ **Do not end your turn here waiting to be told the human is done.** Presenting the link is
414
+ not the end of the ceremony; go straight to the poll in §2 and drive it to a terminal
415
+ state. Asking the human to report back is what strands a pairing: they approve in the
416
+ browser, the server records it, and nothing ever writes the local record — so the ceremony
417
+ expires while both sides believe the other is acting.
418
+
419
+ When the start result reports `claiming: "background"`, a detached poller is already
420
+ finishing activation and it will complete even if this turn ends; poll anyway so you can
421
+ confirm the outcome. When it reports `claiming: "manual"`, that poller could NOT start and
422
+ polling in this turn is the ONLY thing that will complete the pairing.
423
+
424
+ ### 2. Poll
425
+
426
+ Poll the same pending pairing. Interpret results as follows:
427
+
428
+ - `activated` pairing is complete. Continue to the completion report.
429
+ - `not_ready` with `likelyExpired: false` the human has not finished. Wait and poll
430
+ again, using bounded retries rather than an endless loop.
431
+ - `not_ready` with `likelyExpired: true` — the authorization window probably expired.
432
+ Start a fresh pairing.
433
+ - `no_pending` — this runtime has no resumable pairing. Start a fresh pairing.
434
+ - `error` — surface the error without exposing local pending data. Retry once if it is a
435
+ transient network failure; otherwise stop and ask the human to start again.
436
+
437
+ ### 3. Report completion
438
+
439
+ On `activated`, report the display name when present and the stable `agentId`. You may
440
+ also report `identityKeyJkt` as the current key fingerprint, but label it clearly as a
441
+ rotatable key identifier.
442
+
443
+ Use precise completion language:
444
+
445
+ > Visa agent &lt;displayName&gt; is paired to this runtime. Stable agent ID: &lt;agentId&gt;.
446
+
447
+ Do not claim that activation configured payments, email, tap bindings, or any other
448
+ product capability. Those are separate follow-up configuration flows.
449
+
450
+ ## Interruption and replay safety
451
+
452
+ The runtime persists its v2 pending record before sending the signed claim. If a request
453
+ or response is interrupted, the next poll resumes the exact same `pairingId`, `agentId`,
454
+ Ed25519 identity, and claim material. This makes an identical retry safe and avoids
455
+ creating a second identity because a response was lost.
456
+
457
+ The pending record remains until the activated agent record has been written durably.
458
+ Therefore:
459
+
460
+ - Poll before starting over.
461
+ - Do not delete or edit pending files to fix a transient failure.
462
+ - Do not regenerate keys for an existing pairing.
463
+ - Do not copy pending state between runtimes.
464
+
465
+ ## Optional `.visa` mesh binding (separate from pairing)
466
+
467
+ Pairing does not register a `.visa` name, TAP key, or Subway peer. If the operator has
468
+ separately enabled and registered those channel bindings, the mounted `visa-cli` MCP server
469
+ may expose `subway_register`, `subway_send`, `subway_inbox`, and `subway_find`.
470
+
471
+ Mesh messaging is available only on a compatible RC/dev build with `SUBWAY_MESH=visa` and
472
+ a reachable `SUBWAY_RELAY_MULTIADDR`. If those tools or the relay are unavailable, report
473
+ that clearly and stop. Do not imply that identity pairing alone granted directory or
474
+ messaging authority, and do not improvise another transport.
475
+
476
+ ## Optional agent mailbox (separate from pairing)
477
+
478
+ Pairing does not provision an email address or inbox. If the agent needs a
479
+ mailbox e.g. to receive a merchant's account-signup or one-time-code email —
480
+ connect one explicitly, from the paired runtime, with the raw CLI:
481
+
482
+ ```
483
+ visa agent mail-connect <agentId>
484
+ ```
485
+
486
+ This is CLI-only; no pairing step or MCP tool connects a mailbox. It requires an
487
+ already-paired stable-agent identity on this runtime it reads the local agent
488
+ record and proves the Ed25519 identity to the service. It issues the stable
489
+ agent mailbox if one does not exist, then stores an inbox-scoped credential in an
490
+ owner-only `0600` runtime file so this runtime can read that one inbox.
491
+
492
+ Be honest about scope. A mailbox grants an email address and the ability to read
493
+ that inbox nothing more. It is **not** identity, a wallet, spend authority, a
494
+ card, or a `.visa` name, and it never authorizes a payment. Do not claim pairing
495
+ set up mail. Once connected, the MCP `wallet_mail_status`, `wallet_mail_read`,
496
+ and `wallet_mail_await_otp` tools read the inbox (e.g. to await a sender-checked
497
+ one-time code); without the scoped credential those reads fail closed. Keep the
498
+ org-wide AgentMail key off the runtime — provisioning happens only through
499
+ `mail-connect` under operator control.
500
+
501
+ ## Optional checkout profile (separate from pairing)
502
+
503
+ Pairing does not enable card checkout. If the operator has separately provisioned card
504
+ authority and enabled `checkout_agent_access`, the experimental `pay_merchant` flow also needs a local
505
+ `~/.visa-mcp/contact.json` file. Collect every value from the human before the first review;
506
+ never infer or invent identity or address data. Write the file with mode `0600`.
507
+
508
+ ```jsonc
509
+ {
510
+ "fullName": "Ada Lovelace",
511
+ "email": "ada@example.com",
512
+ "addressLine1": "1 Analytical Way",
513
+ "addressLine2": "",
514
+ "city": "San Francisco",
515
+ "state": "CA",
516
+ "postalCode": "94105",
517
+ "country": "US",
518
+ }
519
+ ```
520
+
521
+ `fullName` must be non-blank; `firstName` plus `lastName` is also accepted. The engine reads
522
+ the exact keys `fullName`, `firstName`, `lastName`, `email`, `addressLine1`, `addressLine2`,
523
+ `city`, `state`, `postalCode`, and `country`. The profile supplies checkout/cardholder and
524
+ billing data only. Its `email` value is not the account's verified owner email, an agent
525
+ mailbox, key proof, recovery factor, or permission to spend.
526
+
527
+ ## Security rules
528
+
529
+ - Never read, print, log, paste, or transmit the private Ed25519 JWK or local claim token.
530
+ - Never read or echo the pending pairing file. Present only the authorization URL returned
531
+ by the supported command or tool.
532
+ - Never treat `identityKeyJkt` as the stable identity. Use `agentId` for references and
533
+ persistence.
534
+ - Never fetch the authorization URL on the human's behalf. The human reviews and approves
535
+ it in their browser.
536
+ - Never invent a secondary pairing path when polling fails. Preserve the pending state,
537
+ surface the error, and retry or restart through the same canonical enrollment flow.
295
538
 
296
539
  ## Further docs
297
540
 
298
- - `docs/agents/ARCHITECTURE.md` — where the enroll hand-off sits in the v4 request paths.
299
- - `visacli.sh/agents` — product-facing agent docs.
541
+ - `RUNTIMES.md` — OpenClaw, Hermes, and raw CLI setup.
542
+ - `docs/agents/ARCHITECTURE.md` — where enrollment sits in the v4 request paths.
543
+ - `visacli.sh/agents` — product-facing agent documentation.