@visa/cli 4.1.0-rc.16 → 4.1.0-rc.161

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 (76) hide show
  1. package/README.md +189 -232
  2. package/dist/checkout-engine/adapters/generic.d.ts +46 -0
  3. package/dist/checkout-engine/adapters/generic.js +188 -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 +39 -4
  19. package/dist/checkout-engine/executor.js +323 -117
  20. package/dist/checkout-engine/hosted-approval.d.ts +124 -7
  21. package/dist/checkout-engine/hosted-approval.js +379 -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 +0 -20
  29. package/dist/checkout-engine/live-fill-approval.js +15 -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 +30 -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/vgs-gateway/server-mint-client.d.ts +53 -1
  46. package/dist/checkout-engine/vgs-gateway/server-mint-client.js +78 -10
  47. package/dist/checkout-engine/vgs-live-instrument.d.ts +38 -35
  48. package/dist/checkout-engine/vgs-live-instrument.js +51 -74
  49. package/dist/checkout-engine/vic-confirmation.d.ts +18 -0
  50. package/dist/checkout-engine/vic-confirmation.js +9 -3
  51. package/dist/checkout-engine/web-bot-auth.d.ts +92 -0
  52. package/dist/checkout-engine/web-bot-auth.js +159 -0
  53. package/dist/cli.js +663 -492
  54. package/dist/mcp-apps/ucp-checkout.html +280 -0
  55. package/dist/mcp-server/index.js +514 -174
  56. package/dist/skills/pair-visa-agent/RUNTIMES.md +92 -0
  57. package/dist/skills/pair-visa-agent/SKILL.md +526 -0
  58. package/dist/skills/pair-visa-agent/scripts/setup.mjs +48 -0
  59. package/dist/subway-direct.mjs +1 -0
  60. package/install.ps1 +3 -41
  61. package/install.sh +4 -36
  62. package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
  63. package/package.json +18 -14
  64. package/server.json +3 -3
  65. package/dist/checkout-engine/inline-target.d.ts +0 -13
  66. package/dist/checkout-engine/inline-target.js +0 -37
  67. package/dist/checkout-engine/pay-args.d.ts +0 -14
  68. package/dist/checkout-engine/pay-args.js +0 -44
  69. package/dist/checkout-engine/pay.d.ts +0 -1
  70. package/dist/checkout-engine/pay.js +0 -13
  71. package/dist/checkout-engine/repo-env.d.ts +0 -11
  72. package/dist/checkout-engine/repo-env.js +0 -23
  73. package/dist/checkout-engine/run-live-fill.d.ts +0 -1
  74. package/dist/checkout-engine/run-live-fill.js +0 -443
  75. package/dist/checkout-engine/vgs-gateway/fetch-credential.d.mts +0 -74
  76. package/dist/checkout-engine/vgs-gateway/fetch-credential.mjs +0 -248
@@ -0,0 +1,92 @@
1
+ # Running Visa pairing in OpenClaw and Hermes
2
+
3
+ `@visa/visa-cli-openclaw` is packaged for OpenClaw, but the pairing v2 skill is
4
+ runtime-agnostic. OpenClaw, Hermes, and any runtime that can execute the `visa` CLI or
5
+ mount its MCP server all use the same identity-only enrollment ceremony.
6
+
7
+ ## Plugins are runtime-specific
8
+
9
+ | Axis | OpenClaw | Hermes |
10
+ | --------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
11
+ | Language | JavaScript/TypeScript | Python |
12
+ | Plugin manifest | `openclaw.plugin.json` + `definePluginEntry` (`index.ts`) | `~/.hermes/plugins/<name>/plugin.yaml` + `__init__.py`, `ctx.register_tool(...)` |
13
+ | Config file | `~/.openclaw/openclaw.json` | `~/.hermes/config.yaml` |
14
+ | Skills | Markdown `SKILL.md` | Markdown under `~/.hermes/skills/<name>/` |
15
+ | MCP | `mcp.servers.*` | `mcp_servers.*` |
16
+ | Migration | — | `hermes claw migrate` imports skills, allowlists, compatible MCP servers, memory, and `SOUL.md` |
17
+
18
+ The OpenClaw JavaScript tools do not load as a Hermes plugin. Portability comes from the
19
+ Markdown skill, CLI, and MCP protocol instead.
20
+
21
+ ## One ceremony, three surfaces
22
+
23
+ All supported surfaces share the same local pending record and pairing v2 protocol:
24
+
25
+ 1. OpenClaw: `pair_agent_start` / `pair_agent_poll`.
26
+ 2. Visa MCP server: `enroll_agent` with `action: "start"` / `action: "claim"`.
27
+ 3. Raw CLI: `visa agent enroll --format json` /
28
+ `visa agent enroll-claim --format json`.
29
+
30
+ The start operation returns an authorization URL for the human. The browser receives only
31
+ that URL. The runtime retains its private Ed25519 key and local claim material. Polling
32
+ resumes the same pending identity and safely replays the same signed request after an
33
+ interruption.
34
+
35
+ On activation, use `agentId` as the stable identity. `identityKeyJkt` identifies the
36
+ currently bound Ed25519 public key and can change after key rotation. Activation means the
37
+ identity is paired only; payment methods, email, and tap bindings are separate future or
38
+ follow-up configuration.
39
+
40
+ ## Install and mount
41
+
42
+ Install the prerelease CLI, which provides both command names and the MCP server:
43
+
44
+ ```sh
45
+ npm install -g @visa/cli@rc
46
+ ```
47
+
48
+ There is no `visa mcp` subcommand. Run `visa-cli connect <runtime>` when supported, or
49
+ configure `@visa/cli/dist/mcp-server/index.js` directly. Replace `<npm root -g>` below
50
+ with the output of `npm root -g`.
51
+
52
+ OpenClaw (`~/.openclaw/openclaw.json`):
53
+
54
+ ```json
55
+ {
56
+ "mcp": {
57
+ "servers": {
58
+ "visa-cli": {
59
+ "command": "node",
60
+ "args": ["<npm root -g>/@visa/cli/dist/mcp-server/index.js"]
61
+ }
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ Hermes (`~/.hermes/config.yaml`):
68
+
69
+ ```yaml
70
+ mcp_servers:
71
+ visa-cli:
72
+ command: node
73
+ args: ['<npm root -g>/@visa/cli/dist/mcp-server/index.js']
74
+ ```
75
+
76
+ ## Runtime setup map
77
+
78
+ | Runtime | Setup and pairing surface |
79
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
80
+ | OpenClaw | Install `@visa/visa-cli-openclaw` for the `pair_agent_*` tools and bundled skill. The plugin auto-mounts the Visa MCP server. The MCP or raw CLI surfaces remain valid alternatives. |
81
+ | Hermes | Run `visa-cli connect hermes`, install `pair-visa-agent/SKILL.md` under `~/.hermes/skills/`, and use `enroll_agent`. `hermes claw migrate` can import an existing OpenClaw setup. |
82
+ | Other runtime | Put `visa` on `PATH`, mount the same MCP server if supported, or use the raw `visa agent enroll` and `visa agent enroll-claim` commands as the universal fallback. |
83
+
84
+ The skill itself can be installed with `visa agent skill`. Pass `--runtime <name>` or
85
+ `--dir <path>` when auto-detection is not appropriate, then reload the runtime.
86
+
87
+ ## Optional Hermes wrapper
88
+
89
+ A Hermes Python plugin may expose the same `pair_agent_start` and `pair_agent_poll` names
90
+ by calling the canonical raw CLI commands and registering tools with
91
+ `ctx.register_tool(...)`. It is optional: the MCP server and skill already expose the
92
+ same protocol and persisted pairing state without a second implementation.
@@ -0,0 +1,526 @@
1
+ ---
2
+ name: pair-visa-agent
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
+ allowed-tools: Bash(visa:*) Bash(visa-cli:*) Bash(node:*) Bash(npm:*) Bash(npx:*)
6
+ metadata:
7
+ author: visa
8
+ homepage: https://visacli.sh/agents
9
+ version: '0.7.0'
10
+ # OpenClaw-namespaced extension (agentskills.io keeps `metadata` free-form, so
11
+ # non-standard runtime config lives here — `user-invocable` is not a standard
12
+ # top-level field). OpenClaw auto-installs `install[]` when `requires.bins` are
13
+ # missing; other runtimes (Hermes, Claude Code, …) self-provision via the
14
+ # bundled `scripts/setup.mjs` (see compatibility + Getting set up). @rc pinned
15
+ # until 4.1.0 is promoted to latest.
16
+ openclaw:
17
+ user-invocable: true
18
+ emoji: '🔐'
19
+ requires:
20
+ bins:
21
+ - visa
22
+ install:
23
+ - kind: node
24
+ package: '@visa/cli@rc'
25
+ bins:
26
+ - visa
27
+ - visa-cli
28
+ ---
29
+
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
+ ## What pairing establishes
44
+
45
+ Pairing activates an agent identity on this runtime:
46
+
47
+ - `agentId` is the server-assigned, stable identifier for the agent.
48
+ - The runtime creates and retains the private Ed25519 identity key. It sends only the
49
+ public JWK to the service.
50
+ - `identityKeyJkt` is the thumbprint of the currently bound Ed25519 public key. It can
51
+ change when that key rotates, so it must never be presented or stored as the stable
52
+ agent identifier.
53
+ - The human receives the authorization URL, stable agent ID, and full public request-key
54
+ fingerprint for exact comparison. Private key material, the local claim token, and signed
55
+ protocol messages stay with the runtime.
56
+ - An `activated` result means **identity paired only**. Payment methods, an email address,
57
+ and tap bindings are separate configuration that may be added later. Do not infer any
58
+ of those capabilities from pairing success.
59
+
60
+ ## Getting this skill
61
+
62
+ The skill ships inside the public `@visa/cli` npm package. No clone of the private
63
+ monorepo is required:
64
+
65
+ ```sh
66
+ npm install -g @visa/cli@rc
67
+ visa agent skill
68
+ ```
69
+
70
+ `visa agent skill` auto-detects OpenClaw, Hermes, and Claude Code, falling back to the
71
+ project-local `./.agents/skills`. Pass `--runtime <name>` or `--dir <path>` to choose a
72
+ target, `--force` to overwrite, or `--print` to read without writing. Reload or restart
73
+ the agent runtime after installation so it registers the skill.
74
+
75
+ Access remains enforced by the Visa service. Installing the public package or skill does
76
+ not authorize an account to pair.
77
+
78
+ OpenClaw users also receive the skill with the `@visa/visa-cli-openclaw` plugin.
79
+
80
+ ## Getting set up
81
+
82
+ 1. **Install the prerelease CLI.** Run `npm install -g @visa/cli@rc`, or run the bundled
83
+ idempotent provisioner `node scripts/setup.mjs`. The `@latest` tag may not yet expose
84
+ the v4 agent commands. Installation puts `visa` and `visa-cli` on `PATH` and includes
85
+ `@visa/cli/dist/mcp-server/index.js`.
86
+ 2. **Mount the MCP server when the runtime supports MCP.**
87
+ - **OpenClaw:** installing `@visa/visa-cli-openclaw` auto-mounts the server by writing
88
+ `mcp.servers["visa-cli"]` in `~/.openclaw/openclaw.json`.
89
+ - **Hermes or another supported runtime:** run `visa-cli connect hermes` or
90
+ `visa-cli connect <runtime>`. For Hermes, the entry is written under `mcp_servers`
91
+ in `~/.hermes/config.yaml`.
92
+ - **No MCP integration:** use the raw `visa agent …` commands.
93
+ 3. **Sign the owner in.** Pairing starts are owner-bound — see "Sign in first" below. On a
94
+ fresh runtime this step is required before any pairing command will succeed.
95
+ 4. **Pair.** Follow the core flow below.
96
+
97
+ ## MCP mounting examples
98
+
99
+ Both runtimes use the same server entrypoint. Replace `<npm root -g>` with the output of
100
+ `npm root -g`.
101
+
102
+ OpenClaw (`~/.openclaw/openclaw.json`):
103
+
104
+ ```json
105
+ {
106
+ "mcp": {
107
+ "servers": {
108
+ "visa-cli": {
109
+ "command": "node",
110
+ "args": ["<npm root -g>/@visa/cli/dist/mcp-server/index.js"]
111
+ }
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ Hermes (`~/.hermes/config.yaml`). **Hermes passes ONLY this `env:` map to the MCP
118
+ subprocess — it does NOT inherit the gateway environment.** Omitting a required variable
119
+ (an RC access code, the right `HOME`, `PATH`) makes the server exit on every start while
120
+ `agent_capabilities` — which reads on-disk grant state, not live tool registration — can
121
+ still report rails as available. Always set the map explicitly:
122
+
123
+ ```yaml
124
+ mcp_servers:
125
+ visa-cli:
126
+ command: node
127
+ args: ['<npm root -g>/@visa/cli/dist/mcp-server/index.js']
128
+ # Hermes does NOT inherit the gateway env. This map is the entire
129
+ # subprocess environment; omit VISA_RC_CODE and the server exits on boot.
130
+ env:
131
+ HOME: /home/<user> # the home that holds this runtime's .visa-cli state
132
+ VISA_RC_CODE: <access code>
133
+ PATH: /usr/local/bin:/usr/bin:/bin
134
+ ```
135
+
136
+ Hermes also loads skills **per profile** from `~/.hermes/profiles/<profile>/skills/`, not
137
+ from `~/.hermes/skills/` — on a multi-profile box, install with an explicit target:
138
+ `visa agent skill --dir ~/.hermes/profiles/<profile>/skills`. A flat-dir install exits 0
139
+ and is silently never loaded.
140
+
141
+ In Hermes, `hermes claw migrate` can also import this skill and MCP configuration from an
142
+ existing OpenClaw installation. See `RUNTIMES.md` for the complete runtime map.
143
+
144
+ ## JSON and exit-code contract
145
+
146
+ Prefer structured output and parse it; never scrape prose.
147
+
148
+ - The OpenClaw and MCP tools return structured objects directly.
149
+ - Raw CLI commands support `--format json`.
150
+ - `visa agent enroll-claim` exits `0` when activated, `3` when the human has not finished,
151
+ and `1` on a terminal failure.
152
+
153
+ The start result includes protocol version `2`, `pairingId`, stable `agentId`, full
154
+ `requestKeyFingerprint`, `browserUrl`, and expiry information. Newer builds also return
155
+ `confirmationCode`, a short public code the browser review page displays for comparison.
156
+ Present it when the field is present and skip it when it is absent; an older server or CLI
157
+ simply omits it, which is not an error. The activation result includes protocol version
158
+ `2`, `pairingId`, stable `agentId`, the display name, and `identityKeyJkt`.
159
+
160
+ ## Sign in first — pairing is owner-bound
161
+
162
+ Pairing starts are authenticated as the owner: the service pins the owner account at
163
+ initiation, and the browser review page rejects any other signed-in account
164
+ (`owner_profile_mismatch`). So on a fresh runtime the ceremony has a mandatory first leg —
165
+ establish the owner session **before** any pairing command:
166
+
167
+ - [ ] Call `agent_login` (MCP, default action `"start"`) or run
168
+ `visa agent login --format json` (CLI). The result carries a sign-in `browserUrl`
169
+ and a short 6-character confirmation code.
170
+ - [ ] Relay **both** to the human in your reply — the bare URL on its own line, and the
171
+ code. You are very often not in a terminal they can see; the chat message is the
172
+ only place these values reach them.
173
+ - [ ] The human opens the link, signs in (Google or email), and **types the confirmation
174
+ code into the sign-in page** — into the browser, never back to you in chat.
175
+ - [ ] Claim the session: `agent_login {"action":"claim"}` (the CLI command polls on its
176
+ own). Once claimed, the session token is stored locally and pairing can start.
177
+
178
+ If a pairing command returns "An owner profile is required before pairing" or
179
+ `{"code":"session_required"}`, that is this ordering rule — not a fault. Run `agent_login`,
180
+ drive the sign-in above to a claimed session, then retry the same pairing command once.
181
+
182
+ The account that signs in here is the owner the pairing binds to. The same person must
183
+ approve the pairing link in the next step from that same signed-in browser session;
184
+ a different account on the review page fails closed.
185
+
186
+ ## Fastest path — one shot (`visa agent pair`)
187
+
188
+ If the `visa` CLI is on PATH, prefer the one-shot command — after the owner session above
189
+ exists, it does the whole flow from a single call, so the user types nothing after their
190
+ initial request:
191
+
192
+ ```
193
+ visa agent pair --format json
194
+ ```
195
+
196
+ On a runtime with no owner session it fails with "An owner profile is required before
197
+ pairing" — run the sign-in leg above first, then re-run it.
198
+
199
+ It returns `browserUrl`, `pairingId`, stable `agentId`, the full public
200
+ `requestKeyFingerprint`, and — on newer builds — `confirmationCode` **immediately**. It
201
+ starts a detached activation process only after the pending identity and private key are
202
+ durable. Present the link, returned confirmation code, and fingerprint for browser
203
+ comparison; omit only a confirmation code the result did not provide. When `claiming` is `background`,
204
+ activation completes after approval without another command; if it is `manual`, run the
205
+ returned `recoveryCommand` after approval. Use the step-by-step flow
206
+ below when `pair` is unavailable or when driving the plugin/MCP tools — `enroll_agent`
207
+ action `start` reports the same `claiming` / `recoveryCommand` fields and starts the same
208
+ detached poller.
209
+
210
+ ## Returning & already-connected — do NOT re-pair
211
+
212
+ If `pair`/`enroll` returns **`already_connected`** (or `alreadyConnected: true`), this
213
+ device is **already set up** as an agent — a fresh pairing is neither needed nor
214
+ possible (re-pairing an existing identity silently dead-ends). Do this instead:
215
+
216
+ 1. **Tell the user plainly:** "This device is already connected as `<name>.visa`."
217
+ Read the name from the response. Do **not** start another hand-off. `--new` /
218
+ `force:true` exists only if they explicitly want a _second, separate_ agent.
219
+ 2. **Report status honestly — "connected" is five separate things**, not one. Never
220
+ imply the agent can spend just because it's connected. Don't guess from prose —
221
+ read it live from tools: `agent_capabilities` returns the DERIVED capability map
222
+ (the identity + wallet + mail base plus card/tap/subway availability),
223
+ `get_status` reports enrollment / account / version, and `agent_login` establishes
224
+ or confirms the account session that spending grants require.
225
+ - **Identity** — connected (`.visa` name bound to _this user's_ account). ✓ once `already_connected`.
226
+ - **Spending** — a _separate human approval_, and there are two rails, each
227
+ approved on the **v4 agent dashboard** (`app.visacli.sh/agent/enroll`) — not any
228
+ account-settings page (that legacy surface is retired). The Turnkey
229
+ **wallet:x402** rail (stablecoin) is approved with the owner's **sign-in session**
230
+ (the Google/email auth-proxy) — **no passkey**. The **card:vic** rail is approved
231
+ with a **passkey**. You **cannot** self-grant either; there is no
232
+ `visa agent add-rail` command, and never self-mint a wallet with `wallet_init` on
233
+ mainnet — it throws until the owner's delegation lands.
234
+ - **Mesh (`.visa` messaging)** — with `SUBWAY_MESH=visa`, a returning device now
235
+ registers on `pair`; if `meshRegistered` is false, `visa register <name>` joins it.
236
+ - **Trusted (TAP)** — follows spend/provisioning; don't promise it before then.
237
+ 3. **Scope everything to the user.** The identity is bound to the account the user
238
+ signed in with (their email); the wallet + spend limits are theirs. Speak in terms
239
+ of "your agent / your account / the limits you approved," never a shared identity.
240
+
241
+ Missing **spend** blocks _checkout mandates_, not a delegated x402 wallet — but that
242
+ wallet must first be acquired by owner approval (below); it is never self-minted.
243
+
244
+ ## What you can do once paired — granting spend authority (card or wallet)
245
+
246
+ Pairing binds **identity only**. To let this agent pay, the human owner must **delegate**
247
+ a spend rail — the runtime never self-mints one. There are two rails, and **one** ceremony
248
+ drives both:
249
+
250
+ - **`wallet:x402`** (Turnkey stablecoin) — owner approves with their **sign-in session, no
251
+ passkey**. On mainnet `wallet_init` throws `WalletCredentialRequiredError` until this
252
+ delegation lands, so never call it as a setup step.
253
+ - **`card:vic`** (Visa card checkout) — owner approves with a **passkey**.
254
+
255
+ The contract is identical for both rails, and it is **one command from you, one approval
256
+ from the owner**. You never hand a command, code, or URL back to the human after they
257
+ approve — you poll to completion yourself:
258
+
259
+ 1. **You run one command** naming the rail and caps.
260
+ 2. **The owner opens the returned link and approves once** (they may adjust the amount).
261
+ 3. **You poll to activation** — the owner does nothing further.
262
+
263
+ - [ ] **Pair** the identity (the flow above). Identity only — no rail yet.
264
+ - [ ] **Establish the owner session once.** Grant creation is an account operation, so this
265
+ runtime needs a live owner session: `agent_login` (MCP) or `visa agent login` (CLI).
266
+ If you just paired, the session from the sign-in-first leg is usually still live —
267
+ no re-login needed. This is a short-lived session established once — **not** a
268
+ re-pair, and **not** something the owner repeats per payment. If `agent_connect`
269
+ later returns `{"code":"session_required"}`, the session lapsed — run `agent_login`
270
+ again.
271
+ - [ ] **Initiate the grant from MCP — no shelling, no invented URLs.** Call `agent_connect`
272
+ with the rail and caps: `{"rail":"card","ceiling":"<usd>","perTransaction":"<usd>"}`
273
+ (or `"rail":"wallet"`); omit `agentId` to target the most recently paired agent. It
274
+ returns `{ url, code, attachId, willGrant, expiresAt }`. Present the **bare `url` and
275
+ `code` exactly as returned** — never construct, shorten, or guess a Visa URL, and never
276
+ open it yourself. The crypto approval happens in the owner's browser and cannot run
277
+ inline in chat. (`setup_agent {"rail":"card"|"wallet"}` returns the same next-step map
278
+ if you need it.)
279
+ - [ ] **Owner approves once** on the **v4 agent dashboard** (`app.visacli.sh/agent/enroll`),
280
+ confirming the caps — **card with a passkey, wallet with their sign-in session**. You
281
+ cannot approve on their behalf.
282
+ - [ ] **Poll to activation from MCP.** Call `agent_connect_poll` (`{"attachId":"<from
283
+ agent_connect>"}`; or resume by `agentId`) — one bounded poll per call. It returns
284
+ `{"ok":false,"state":"...","blockedByKind":"awaiting_human_approval"}` while pending;
285
+ call again until `{"ok":true,"state":"grant_activated","caps":...}` (wallet also
286
+ returns `fundAddress`). Activation registers the delegated signer + caps: wallet writes
287
+ the Turnkey credential (`turnkey.json`); card writes the card pointer. It never spends.
288
+ - [ ] **Confirm the rail is live.** Wallet: `wallet_status` / `visa wallet show`
289
+ (`agent_capabilities.wallet.available`). Card: `agent_capabilities.card.available`.
290
+ Only a delegated credential — not the served tool list — means the rail is usable.
291
+ - [ ] **Then spend against the owner-approved caps.** Wallet: set the policy with
292
+ `wallet_policy_set` (per-transaction / daily / session USD caps + optional network and
293
+ merchant allow/deny lists that refuse an x402 payment BEFORE it is signed), then
294
+ `wallet_pay`. Card: `start_card_mandate` then `pay_merchant`. Never raise a
295
+ human-approved limit yourself.
296
+
297
+ **Raw CLI equivalent (one shot).** If you cannot drive MCP, the same ceremony runs from the
298
+ CLI and `--wait` polls to activation in a single call. Present the URL it prints — don't
299
+ invent one:
300
+
301
+ ```
302
+ visa agent grant-card <agentId> --ceiling <usd> --per-transaction <usd> --wait
303
+ visa agent grant-wallet <agentId> --ceiling <usd> --per-transaction <usd> --wait
304
+ ```
305
+
306
+ Rail selection is the owner's call — wallet only, card only, or both. Newer builds also
307
+ accept one compound command that runs the same ceremony per rail in sequence
308
+ (`both` = card, then wallet, under the same caps — expect one extra approval trip):
309
+
310
+ ```
311
+ visa agent connect --authority <card|wallet|both> --ceiling <usd> --per-transaction <usd> --wait
312
+ ```
313
+
314
+ Use the stable `agentId` from pairing or `visa agent list` — never invent or alter the id.
315
+ A policy refusal or timeout means **nothing was signed**: report it and stop; do not retry
316
+ with a different command or a reconstructed URL.
317
+
318
+ Once a wallet is delegated and a policy is set, these are the served wallet tools this agent
319
+ can actually call: `wallet_discover` (search the public x402 Bazaar), `wallet_probe` (read a
320
+ challenge without paying), `wallet_pay` / `wallet_directory_pay` (pay, policy-enforced),
321
+ `wallet_history` / `wallet_reconcile` (local ledger + resolve `reconciling` holds),
322
+ `wallet_fund` (funding address + faucet), and `wallet_export` (export key material —
323
+ dangerous). All spending is gated by the owner-approved local policy caps.
324
+
325
+ ## Core flow
326
+
327
+ - [ ] Establish the owner session ("Sign in first" above) if this runtime does not have
328
+ one yet.
329
+ - [ ] Start the pairing with `pair_agent_start`, `enroll_agent` action `start`, or
330
+ `visa agent enroll --format json`.
331
+ - [ ] Present the authorization URL, stable agent ID, full public fingerprint, and the
332
+ `confirmationCode` when the start result carries one, to the human.
333
+ - [ ] Poll with `pair_agent_poll`, `enroll_agent` action `claim`, or
334
+ `visa agent enroll-claim --format json`.
335
+ - [ ] Report the stable `agentId` and that the identity is paired on this runtime.
336
+
337
+ ### Completion is `activated` — nothing else
338
+
339
+ Only a poll result of `activated` means this runtime is paired. Until you have one, do not
340
+ tell the human the agent is connected, paired, enrolled, set up, ready, or good to go, and
341
+ do not move on to spending, mail, or mesh steps that assume an identity.
342
+
343
+ `start` returning successfully is not completion. It returns a stable `agentId` and a
344
+ `requestKeyFingerprint` **before any human has approved anything** — those are review
345
+ values for the browser comparison, not evidence of pairing. Reporting an `agentId` as
346
+ though it were a finished pairing is the most likely way to mislead the human here, because
347
+ the number looks like a result.
348
+
349
+ If you cannot reach `activated`, say plainly what state you did reach and what the human
350
+ should do next. An honest "approved but not yet activated — I'm still polling" is correct;
351
+ "you're all set" without an `activated` result is not.
352
+
353
+ ### 1. Start
354
+
355
+ Start once. If there is already a pending pairing, poll it before creating another.
356
+
357
+ The result provides `browserUrl`, stable `agentId`, `requestKeyFingerprint`, and — on newer
358
+ builds — `confirmationCode`. Show the URL as a bare, tappable value on its own line. Do not
359
+ decorate it as a Markdown link or put it in a code span; chat clients reliably recognize the
360
+ bare URL. Show the complete fingerprint without truncation and tell the human to approve
361
+ only when every character matches the browser review page.
362
+
363
+ You are very often **not** in a terminal the human can see. Nothing you print to stdout
364
+ reaches them. Every value the browser asks them to compare has to appear in your reply, or
365
+ the comparison silently becomes "click approve and hope" — which is the whole failure this
366
+ step exists to prevent.
367
+
368
+ Use this concise shape:
369
+
370
+ > 🔐 Pair your Visa agent — open this authorization page:
371
+ >
372
+ > 👉 &lt;browserUrl, bare and on its own line&gt;
373
+ >
374
+ > Stable agent ID: &lt;agentId&gt;
375
+ >
376
+ > Confirmation code — this exact code should appear on the page:
377
+ > &lt;confirmationCode&gt;
378
+ >
379
+ > Public request-key fingerprint — compare every character in the browser:
380
+ > &lt;requestKeyFingerprint, complete and untruncated&gt;
381
+ >
382
+ > Approve only if the code and fingerprint both match. The link expires shortly — I'll keep
383
+ > watching and confirm here the moment it activates.
384
+
385
+ Omit the confirmation-code line entirely when the start result has no `confirmationCode`;
386
+ never invent, derive, abbreviate, or reformat one.
387
+
388
+ Relaying these values **to** the human is required. Accepting one **from** the human is not:
389
+ do not ask them for a code, secret, private key, token, or signed message, and do not treat
390
+ anything they type back as approval. The URL, stable agent ID, confirmation code, and public
391
+ fingerprint are review values the human checks against their own authenticated browser
392
+ session. They are not claim credentials, they cannot approve a pairing, and they cannot
393
+ spend. Approval happens only in that browser session, and the only evidence of it is a poll
394
+ result of `activated`.
395
+
396
+ **Do not end your turn here waiting to be told the human is done.** Presenting the link is
397
+ not the end of the ceremony; go straight to the poll in §2 and drive it to a terminal
398
+ state. Asking the human to report back is what strands a pairing: they approve in the
399
+ browser, the server records it, and nothing ever writes the local record — so the ceremony
400
+ expires while both sides believe the other is acting.
401
+
402
+ When the start result reports `claiming: "background"`, a detached poller is already
403
+ finishing activation and it will complete even if this turn ends; poll anyway so you can
404
+ confirm the outcome. When it reports `claiming: "manual"`, that poller could NOT start and
405
+ polling in this turn is the ONLY thing that will complete the pairing.
406
+
407
+ ### 2. Poll
408
+
409
+ Poll the same pending pairing. Interpret results as follows:
410
+
411
+ - `activated` — pairing is complete. Continue to the completion report.
412
+ - `not_ready` with `likelyExpired: false` — the human has not finished. Wait and poll
413
+ again, using bounded retries rather than an endless loop.
414
+ - `not_ready` with `likelyExpired: true` — the authorization window probably expired.
415
+ Start a fresh pairing.
416
+ - `no_pending` — this runtime has no resumable pairing. Start a fresh pairing.
417
+ - `error` — surface the error without exposing local pending data. Retry once if it is a
418
+ transient network failure; otherwise stop and ask the human to start again.
419
+
420
+ ### 3. Report completion
421
+
422
+ On `activated`, report the display name when present and the stable `agentId`. You may
423
+ also report `identityKeyJkt` as the current key fingerprint, but label it clearly as a
424
+ rotatable key identifier.
425
+
426
+ Use precise completion language:
427
+
428
+ > Visa agent &lt;displayName&gt; is paired to this runtime. Stable agent ID: &lt;agentId&gt;.
429
+
430
+ Do not claim that activation configured payments, email, tap bindings, or any other
431
+ product capability. Those are separate follow-up configuration flows.
432
+
433
+ ## Interruption and replay safety
434
+
435
+ The runtime persists its v2 pending record before sending the signed claim. If a request
436
+ or response is interrupted, the next poll resumes the exact same `pairingId`, `agentId`,
437
+ Ed25519 identity, and claim material. This makes an identical retry safe and avoids
438
+ creating a second identity because a response was lost.
439
+
440
+ The pending record remains until the activated agent record has been written durably.
441
+ Therefore:
442
+
443
+ - Poll before starting over.
444
+ - Do not delete or edit pending files to fix a transient failure.
445
+ - Do not regenerate keys for an existing pairing.
446
+ - Do not copy pending state between runtimes.
447
+
448
+ ## Optional `.visa` mesh binding (separate from pairing)
449
+
450
+ Pairing does not register a `.visa` name, TAP key, or Subway peer. If the operator has
451
+ separately enabled and registered those channel bindings, the mounted `visa-cli` MCP server
452
+ may expose `subway_register`, `subway_send`, `subway_inbox`, and `subway_find`.
453
+
454
+ Mesh messaging is available only on a compatible RC/dev build with `SUBWAY_MESH=visa` and
455
+ a reachable `SUBWAY_RELAY_MULTIADDR`. If those tools or the relay are unavailable, report
456
+ that clearly and stop. Do not imply that identity pairing alone granted directory or
457
+ messaging authority, and do not improvise another transport.
458
+
459
+ ## Optional agent mailbox (separate from pairing)
460
+
461
+ Pairing does not provision an email address or inbox. If the agent needs a
462
+ mailbox — e.g. to receive a merchant's account-signup or one-time-code email —
463
+ connect one explicitly, from the paired runtime, with the raw CLI:
464
+
465
+ ```
466
+ visa agent mail-connect <agentId>
467
+ ```
468
+
469
+ This is CLI-only; no pairing step or MCP tool connects a mailbox. It requires an
470
+ already-paired stable-agent identity on this runtime — it reads the local agent
471
+ record and proves the Ed25519 identity to the service. It issues the stable
472
+ agent mailbox if one does not exist, then stores an inbox-scoped credential in an
473
+ owner-only `0600` runtime file so this runtime can read that one inbox.
474
+
475
+ Be honest about scope. A mailbox grants an email address and the ability to read
476
+ that inbox — nothing more. It is **not** identity, a wallet, spend authority, a
477
+ card, or a `.visa` name, and it never authorizes a payment. Do not claim pairing
478
+ set up mail. Once connected, the MCP `wallet_mail_status`, `wallet_mail_read`,
479
+ and `wallet_mail_await_otp` tools read the inbox (e.g. to await a sender-checked
480
+ one-time code); without the scoped credential those reads fail closed. Keep the
481
+ org-wide AgentMail key off the runtime — provisioning happens only through
482
+ `mail-connect` under operator control.
483
+
484
+ ## Optional checkout profile (separate from pairing)
485
+
486
+ Pairing does not enable card checkout. If the operator has separately provisioned card
487
+ authority and enabled `checkout_agent_access`, the experimental `pay_merchant` flow also needs a local
488
+ `~/.visa-mcp/contact.json` file. Collect every value from the human before the first review;
489
+ never infer or invent identity or address data. Write the file with mode `0600`.
490
+
491
+ ```jsonc
492
+ {
493
+ "fullName": "Ada Lovelace",
494
+ "email": "ada@example.com",
495
+ "addressLine1": "1 Analytical Way",
496
+ "addressLine2": "",
497
+ "city": "San Francisco",
498
+ "state": "CA",
499
+ "postalCode": "94105",
500
+ "country": "US",
501
+ }
502
+ ```
503
+
504
+ `fullName` must be non-blank; `firstName` plus `lastName` is also accepted. The engine reads
505
+ the exact keys `fullName`, `firstName`, `lastName`, `email`, `addressLine1`, `addressLine2`,
506
+ `city`, `state`, `postalCode`, and `country`. The profile supplies checkout/cardholder and
507
+ billing data only. Its `email` value is not the account's verified owner email, an agent
508
+ mailbox, key proof, recovery factor, or permission to spend.
509
+
510
+ ## Security rules
511
+
512
+ - Never read, print, log, paste, or transmit the private Ed25519 JWK or local claim token.
513
+ - Never read or echo the pending pairing file. Present only the authorization URL returned
514
+ by the supported command or tool.
515
+ - Never treat `identityKeyJkt` as the stable identity. Use `agentId` for references and
516
+ persistence.
517
+ - Never fetch the authorization URL on the human's behalf. The human reviews and approves
518
+ it in their browser.
519
+ - Never invent a secondary pairing path when polling fails. Preserve the pending state,
520
+ surface the error, and retry or restart through the same canonical enrollment flow.
521
+
522
+ ## Further docs
523
+
524
+ - `RUNTIMES.md` — OpenClaw, Hermes, and raw CLI setup.
525
+ - `docs/agents/ARCHITECTURE.md` — where enrollment sits in the v4 request paths.
526
+ - `visacli.sh/agents` — product-facing agent documentation.
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ // Portable provisioner for the pair-visa-agent skill.
3
+ //
4
+ // Ensures the `visa` CLI is installed so ANY Agent Skills runtime can pair —
5
+ // not just OpenClaw (whose `metadata.openclaw.install` auto-runs). Hermes,
6
+ // Claude Code, and any other agentskills.io-compatible runtime run this bundled
7
+ // script per the standard's `scripts/` execution stage.
8
+ //
9
+ // Safe to run repeatedly: it no-ops when `visa` already resolves. Pinned to @rc
10
+ // because the v4 agent surface is prerelease (the @latest tag predates the
11
+ // `visa agent` commands); drop the tag once 4.1.0 is promoted to latest.
12
+
13
+ import { execSync } from 'node:child_process'
14
+
15
+ function resolves(cmd) {
16
+ try {
17
+ execSync(`${cmd} --version`, { stdio: 'ignore' })
18
+ return true
19
+ } catch {
20
+ return false
21
+ }
22
+ }
23
+
24
+ if (resolves('visa') || resolves('visa-cli')) {
25
+ console.log('✓ visa CLI already installed — nothing to do. Run the pairing flow in SKILL.md.')
26
+ process.exit(0)
27
+ }
28
+
29
+ console.log('Installing @visa/cli@rc (the v4 agent surface is prerelease)…')
30
+ try {
31
+ execSync('npm install -g @visa/cli@rc', { stdio: 'inherit' })
32
+ } catch {
33
+ console.error(
34
+ 'Global install failed. Try `npm install -g @visa/cli@rc` manually (may need sudo, or set a\n' +
35
+ 'user-writable npm prefix: `npm config set prefix ~/.npm-global` and add its `bin` to PATH).'
36
+ )
37
+ process.exit(1)
38
+ }
39
+
40
+ if (!resolves('visa') && !resolves('visa-cli')) {
41
+ console.error(
42
+ 'Installed, but `visa` is not on PATH. Ensure your npm global bin dir is on PATH\n' +
43
+ '(`npm bin -g` shows it), then re-run this script.'
44
+ )
45
+ process.exit(1)
46
+ }
47
+
48
+ console.log('✓ visa CLI ready. Now run the pairing flow in SKILL.md.')