@visa/cli 4.1.0-rc.32 → 4.1.0-rc.321
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +293 -57
- package/dist/cli.js +635 -355
- package/dist/managed-runtime/resolve-and-update.mjs +268 -0
- package/dist/managed-runtime/runtime-readiness.mjs +126 -0
- package/dist/managed-runtime/update-and-restart.mjs +1079 -0
- package/dist/mcp-apps/ucp-checkout.html +280 -0
- package/dist/mcp-server/index.js +523 -271
- package/dist/merchant-ucp-mcp/index.js +7 -0
- package/dist/skills/pair-visa-agent/RUNTIMES.md +122 -79
- package/dist/skills/pair-visa-agent/SKILL.md +390 -333
- package/dist/skills/pair-visa-agent/scripts/__tests__/setup.test.mjs +407 -0
- package/dist/skills/pair-visa-agent/scripts/setup.mjs +310 -30
- package/dist/skills/visa-shopify-checkout/SKILL.md +122 -0
- package/dist/skills/visa-shopify-checkout/references/evidence-and-states.md +37 -0
- package/dist/skills/visa-ucp-shopping/SKILL.md +86 -0
- package/dist/subway-direct.mjs +1 -0
- package/install.ps1 +10 -6
- package/install.sh +8 -3
- package/native/bin/darwin-arm64/visa-runtime-signer +0 -0
- package/native/bin/darwin-x64/visa-runtime-signer +0 -0
- package/native/bin/linux-arm64/visa-runtime-signer +0 -0
- package/native/bin/linux-x64/visa-runtime-signer +0 -0
- package/native/bin/win32-arm64/visa-runtime-signer.exe +0 -0
- package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
- package/native/bin/win32-x64/visa-runtime-signer.exe +0 -0
- package/package.json +33 -30
- package/server.json +4 -4
- package/dist/checkout-engine/adapters/generic.d.ts +0 -19
- package/dist/checkout-engine/adapters/generic.js +0 -201
- package/dist/checkout-engine/adapters/index.d.ts +0 -7
- package/dist/checkout-engine/adapters/index.js +0 -17
- package/dist/checkout-engine/adapters/stripe-like.d.ts +0 -10
- package/dist/checkout-engine/adapters/stripe-like.js +0 -21
- package/dist/checkout-engine/browser-launch.d.ts +0 -46
- package/dist/checkout-engine/browser-launch.js +0 -81
- package/dist/checkout-engine/ceremony.d.ts +0 -64
- package/dist/checkout-engine/ceremony.js +0 -261
- package/dist/checkout-engine/cli-engine.d.ts +0 -208
- package/dist/checkout-engine/cli-engine.js +0 -584
- package/dist/checkout-engine/detect.d.ts +0 -61
- package/dist/checkout-engine/detect.js +0 -392
- package/dist/checkout-engine/evidence.d.ts +0 -25
- package/dist/checkout-engine/evidence.js +0 -104
- package/dist/checkout-engine/executor.d.ts +0 -174
- package/dist/checkout-engine/executor.js +0 -1306
- package/dist/checkout-engine/hosted-approval.d.ts +0 -135
- package/dist/checkout-engine/hosted-approval.js +0 -311
- package/dist/checkout-engine/index.d.ts +0 -6
- package/dist/checkout-engine/index.js +0 -8
- package/dist/checkout-engine/inline-target.d.ts +0 -13
- package/dist/checkout-engine/inline-target.js +0 -37
- package/dist/checkout-engine/instrument.d.ts +0 -55
- package/dist/checkout-engine/instrument.js +0 -87
- package/dist/checkout-engine/live-fill-approval.d.ts +0 -43
- package/dist/checkout-engine/live-fill-approval.js +0 -90
- package/dist/checkout-engine/mandate/card-mandate.d.ts +0 -117
- package/dist/checkout-engine/mandate/card-mandate.js +0 -221
- package/dist/checkout-engine/mandate/mandate-ledger.d.ts +0 -135
- package/dist/checkout-engine/mandate/mandate-ledger.js +0 -318
- package/dist/checkout-engine/mandate.d.ts +0 -25
- package/dist/checkout-engine/mandate.js +0 -100
- package/dist/checkout-engine/outcome.d.ts +0 -30
- package/dist/checkout-engine/outcome.js +0 -225
- package/dist/checkout-engine/owner-only-file.d.ts +0 -19
- package/dist/checkout-engine/owner-only-file.js +0 -41
- package/dist/checkout-engine/package.json +0 -3
- package/dist/checkout-engine/pay-args.d.ts +0 -14
- package/dist/checkout-engine/pay-args.js +0 -44
- package/dist/checkout-engine/pay.d.ts +0 -1
- package/dist/checkout-engine/pay.js +0 -13
- package/dist/checkout-engine/receipt.d.ts +0 -81
- package/dist/checkout-engine/receipt.js +0 -109
- package/dist/checkout-engine/repo-env.d.ts +0 -11
- package/dist/checkout-engine/repo-env.js +0 -23
- package/dist/checkout-engine/run-live-fill.d.ts +0 -1
- package/dist/checkout-engine/run-live-fill.js +0 -493
- package/dist/checkout-engine/types.d.ts +0 -39
- package/dist/checkout-engine/types.js +0 -2
- package/dist/checkout-engine/vgs-gateway/fetch-credential.d.mts +0 -74
- package/dist/checkout-engine/vgs-gateway/fetch-credential.mjs +0 -248
- package/dist/checkout-engine/vgs-gateway/server-mint-client.d.ts +0 -82
- package/dist/checkout-engine/vgs-gateway/server-mint-client.js +0 -178
- package/dist/checkout-engine/vgs-live-instrument.d.ts +0 -168
- package/dist/checkout-engine/vgs-live-instrument.js +0 -289
- package/dist/checkout-engine/vic-confirmation.d.ts +0 -34
- package/dist/checkout-engine/vic-confirmation.js +0 -39
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pair-visa-agent
|
|
3
|
-
description:
|
|
4
|
-
compatibility: Needs the `visa` CLI (`npm i -g @visa/cli@rc`) — run `node scripts/setup.mjs` to
|
|
3
|
+
description: Set up a Visa CLI v4 agent through the single protected enrollment implementation (MCP `agent_enroll` or `visa agent enroll`), and report its live capabilities honestly. The owner always approves in their browser. Use when the user says "pair my agent", "connect Visa", "enroll my Visa CLI", "set up my agent", or "let this agent pay".
|
|
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, Claude Code, Codex, 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.
|
|
9
|
+
version: '0.8.0'
|
|
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,123 @@ metadata:
|
|
|
27
27
|
- visa-cli
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
-
#
|
|
30
|
+
# Set up a Visa agent
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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.
|
|
32
|
+
One protected enrollment carries everything: this runtime's identity, the binding to
|
|
33
|
+
**this device**, and the spending limits — behind a **single** owner approval in their
|
|
34
|
+
browser.
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
the same two `visa` commands:
|
|
36
|
+
It has two entrances to the exact same protected enrollment implementation: the same
|
|
37
|
+
native signer, Authority routes, owner browser approval, and limits. Never invent or mix
|
|
38
|
+
in a second ceremony.
|
|
42
39
|
|
|
43
|
-
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
- **You have the Visa MCP server mounted:** call `agent_enroll` and hand the owner the one
|
|
41
|
+
link and short code it returns. Call it again with `wait: true` once they have the page
|
|
42
|
+
open; it finishes activation only after they approve.
|
|
43
|
+
- **The owner is at a terminal:** they run the command below.
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
```
|
|
46
|
+
# the human runs this:
|
|
47
|
+
visa agent enroll --wait
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
→ it prints a URL and a short code
|
|
50
|
+
→ the human opens the URL and enters the code at /agent/enroll/protected-agent
|
|
51
|
+
→ the human approves the device and its spending limits (one approval)
|
|
52
|
+
→ the command finishes; `get_status` now reports pairing.paired: true
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The installed CLI selects its own service origins from its release channel. Never ask
|
|
56
|
+
for, guess, or construct a Visa URL.
|
|
57
|
+
|
|
58
|
+
Running the same command again resumes an interrupted enrolment; `--restart` replaces an
|
|
59
|
+
unclaimed request with a fresh code. `--ceiling <usd>` and `--per-transaction <usd>`
|
|
60
|
+
propose limits the owner confirms on the approval page.
|
|
61
|
+
|
|
62
|
+
## The older ceremonies were deleted — do not call them
|
|
63
|
+
|
|
64
|
+
`setup_start`, `setup_status`, `setup_resume`, `setup_cancel`, `setup_agent`,
|
|
65
|
+
`enroll_agent`, `agent_connect`, `agent_connect_poll`, `agent_connect_cancel`,
|
|
66
|
+
`agent_handoff_claim` and `agent_pairing_cancel` are gone, along with the `visa setup`
|
|
67
|
+
group and `visa agent pair|create|verify|enroll-claim|claim|pairing-resume|connect|grant-card|grant-wallet|grant-activate|grant-claim|handoff-claim`.
|
|
51
68
|
|
|
52
|
-
|
|
53
|
-
(private) monorepo is required, so any agent box that can reach npm can install it:
|
|
69
|
+
Calling any of them returns one refusal:
|
|
54
70
|
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"code": "legacy_door_removed",
|
|
74
|
+
"kind": "caller",
|
|
75
|
+
"fix": "visa agent enroll (shell) or agent_enroll (MCP)"
|
|
76
|
+
}
|
|
55
77
|
```
|
|
56
|
-
|
|
57
|
-
|
|
78
|
+
|
|
79
|
+
That code means the door no longer exists. **Relay the replacement and stop.** Do not
|
|
80
|
+
retry it, do not try a variant spelling, and do not report it to the human as an outage:
|
|
81
|
+
nothing was signed, paired, or paid.
|
|
82
|
+
|
|
83
|
+
## What "set up" means here
|
|
84
|
+
|
|
85
|
+
Each completed protected enrollment mints a **new** agent: a new server-assigned agent,
|
|
86
|
+
a new device-held Ed25519 identity key, and the wallet limits the owner approved.
|
|
87
|
+
|
|
88
|
+
- The runtime keeps the private Ed25519 key and sends only the public JWK.
|
|
89
|
+
- The limits are the ones the owner actually approved on the page. Nothing you asked for
|
|
90
|
+
is granted until they approve it.
|
|
91
|
+
- An email address, a `.visa` mesh name, and TAP bindings remain separate, later
|
|
92
|
+
configuration. Do not infer them from a finished enrolment.
|
|
93
|
+
|
|
94
|
+
**Where the `agentId` comes from — read this before you quote one.** Do not invent it and
|
|
95
|
+
do not read it out of the command's terminal output, which you often cannot see. Read it
|
|
96
|
+
from `get_status` or `agent_capabilities` once the enrolment finishes. Never substitute a
|
|
97
|
+
correlation id, a confirmation code, or an origin for an `agentId`.
|
|
98
|
+
|
|
99
|
+
**Re-running the command does not repair an agent that already exists** — it creates a
|
|
100
|
+
second one. To fix a capability an existing agent is missing, see "Already set up — do NOT
|
|
101
|
+
enrol again" near the end.
|
|
102
|
+
|
|
103
|
+
## Getting this skill
|
|
104
|
+
|
|
105
|
+
The skill ships inside the public `@visa/cli` npm package. No clone of the private
|
|
106
|
+
monorepo is required:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
npm install -g @visa/cli@rc
|
|
110
|
+
visa agent skill
|
|
58
111
|
```
|
|
59
112
|
|
|
60
|
-
`visa agent skill` auto-detects OpenClaw
|
|
61
|
-
project-local `./.agents/skills
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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`):
|
|
113
|
+
`visa agent skill` auto-detects OpenClaw, Hermes, Claude Code, and Codex, falling back to
|
|
114
|
+
the project-local `./.agents/skills`. Pass `--runtime <name>` or `--dir <path>` to choose a
|
|
115
|
+
target, `--force` to overwrite, or `--print` to read without writing. Reload or restart
|
|
116
|
+
the agent runtime after installation so it registers the skill.
|
|
117
|
+
|
|
118
|
+
Access remains enforced by the Visa service. Installing the public package or skill does
|
|
119
|
+
not authorize an account to pair.
|
|
120
|
+
|
|
121
|
+
OpenClaw users receive the skill via `visa agent skill --runtime openclaw` (or through the `@visa/visa-cli-openclaw` plugin when running from source).
|
|
122
|
+
|
|
123
|
+
## Getting set up
|
|
124
|
+
|
|
125
|
+
1. **Install the prerelease CLI.** Run `npm install -g @visa/cli@rc`, or run the bundled
|
|
126
|
+
idempotent provisioner `node scripts/setup.mjs`. The `@latest` tag may not yet expose
|
|
127
|
+
the v4 setup tools. Installation puts `visa` and `visa-cli` on `PATH` and includes
|
|
128
|
+
`@visa/cli/dist/mcp-server/index.js`.
|
|
129
|
+
2. **Mount the MCP server when the runtime supports MCP.**
|
|
130
|
+
- **OpenClaw:** run `visa agent skill --runtime openclaw` (or `visa-cli connect openclaw`)
|
|
131
|
+
to install the skill and write `mcp.servers["visa-cli"]` in `~/.openclaw/openclaw.json`.
|
|
132
|
+
(Installing `@visa/visa-cli-openclaw` from source/tarball also auto-mounts the server).
|
|
133
|
+
- **Hermes or another supported runtime:** run `visa-cli connect hermes` or
|
|
134
|
+
`visa-cli connect <runtime>`. For Hermes, the entry is written under `mcp_servers`
|
|
135
|
+
in `~/.hermes/config.yaml`.
|
|
136
|
+
- **No MCP integration:** everything below still works — the enrolment is a shell
|
|
137
|
+
command, and `--format json` output is available on the management commands.
|
|
138
|
+
3. **Sign the owner in** — see "Sign in first" below.
|
|
139
|
+
4. **Enrol.** Follow the core flow below.
|
|
140
|
+
|
|
141
|
+
## MCP mounting examples
|
|
142
|
+
|
|
143
|
+
Both runtimes use the same server entrypoint. Replace `<npm root -g>` with the output of
|
|
144
|
+
`npm root -g`.
|
|
145
|
+
|
|
146
|
+
OpenClaw (`~/.openclaw/openclaw.json`):
|
|
104
147
|
|
|
105
148
|
```json
|
|
106
149
|
{
|
|
@@ -115,289 +158,303 @@ server so the runtime exposes the tools (both use the same server entrypoint —
|
|
|
115
158
|
}
|
|
116
159
|
```
|
|
117
160
|
|
|
118
|
-
|
|
161
|
+
Hermes (`~/.hermes/config.yaml`). **Hermes passes ONLY this `env:` map to the MCP
|
|
162
|
+
subprocess — it does NOT inherit the gateway environment.** Omitting a required variable
|
|
163
|
+
(an RC access code, the right `HOME`, `PATH`) makes the server exit on every start while
|
|
164
|
+
`agent_capabilities` — which reads on-disk grant state, not live tool registration — can
|
|
165
|
+
still report rails as available. Always set the map explicitly:
|
|
119
166
|
|
|
120
167
|
```yaml
|
|
121
168
|
mcp_servers:
|
|
122
169
|
visa-cli:
|
|
123
170
|
command: node
|
|
124
171
|
args: ['<npm root -g>/@visa/cli/dist/mcp-server/index.js']
|
|
172
|
+
# Hermes does NOT inherit the gateway env. This map is the entire
|
|
173
|
+
# subprocess environment; omit VISA_RC_CODE and the server exits on boot.
|
|
174
|
+
env:
|
|
175
|
+
HOME: /home/<user> # the home that holds this runtime's .visa-cli state
|
|
176
|
+
VISA_RC_CODE: <access code>
|
|
177
|
+
PATH: /usr/local/bin:/usr/bin:/bin
|
|
125
178
|
```
|
|
126
179
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
180
|
+
Hermes also loads skills **per profile** from `~/.hermes/profiles/<profile>/skills/`, not
|
|
181
|
+
from `~/.hermes/skills/`. `visa agent skill --runtime hermes` resolves this automatically:
|
|
182
|
+
it targets the single profile when exactly one exists (or the one named by
|
|
183
|
+
`HERMES_PROFILE`), and **fails loudly** on a multi-profile box instead of planting into
|
|
184
|
+
the flat dir nothing reads — pass `--dir ~/.hermes/profiles/<profile>/skills` to choose.
|
|
185
|
+
|
|
186
|
+
**Hermes sanitizes MCP server names when registering tools.** A server declared
|
|
187
|
+
`visa-cli` in `mcp_servers:` registers its tools as `mcp__visa_cli__<tool>` — with an
|
|
188
|
+
UNDERSCORE, not the declared hyphen. Anything that hardcodes `mcp__visa-cli__<tool>` gets
|
|
189
|
+
`unknown tool` on every call while looking correct in review. Read tool names off the
|
|
190
|
+
live registry; never derive them from the config key.
|
|
130
191
|
|
|
131
|
-
|
|
192
|
+
In Hermes, `hermes claw migrate` can also import this skill and MCP configuration from an
|
|
193
|
+
existing OpenClaw installation. See `RUNTIMES.md` for the complete runtime map.
|
|
132
194
|
|
|
133
|
-
|
|
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).
|
|
195
|
+
## Sign in first — the wallet is owner-bound
|
|
138
196
|
|
|
139
|
-
|
|
197
|
+
The USDC wallet is delegated out of the owner's own wallet, so this runtime needs a live
|
|
198
|
+
owner session. Establish it **before** the enrolment command runs:
|
|
140
199
|
|
|
141
|
-
|
|
142
|
-
|
|
200
|
+
- [ ] Call `agent_login` (MCP, default action `"start"`) or run
|
|
201
|
+
`visa agent login --format json` (CLI). The result carries a sign-in `browserUrl`
|
|
202
|
+
and a short 6-character `confirmCode`.
|
|
203
|
+
- [ ] Relay **both** to the human in your reply — the bare URL on its own line, and the
|
|
204
|
+
code. You are very often not in a terminal they can see; the chat message is the
|
|
205
|
+
only place these values reach them.
|
|
206
|
+
- [ ] The human opens the link, signs in (Google or email), and **types the confirmation
|
|
207
|
+
code into the sign-in page** — into the browser, never back to you in chat.
|
|
208
|
+
- [ ] Claim the session: `agent_login {"action":"claim"}` (the CLI command polls on its
|
|
209
|
+
own). Once claimed, the session token is stored locally.
|
|
210
|
+
|
|
211
|
+
`agent_login` establishes the OWNER's session on this device. It creates no agent and
|
|
212
|
+
grants no spending authority — those come from the enrolment command and the approval the
|
|
213
|
+
owner gives in their browser.
|
|
214
|
+
|
|
215
|
+
If something reports `{"code":"session_required"}` or "Not logged in", that is this
|
|
216
|
+
ordering rule, not a fault: run `agent_login`, drive the sign-in above to a claimed
|
|
217
|
+
session, then continue.
|
|
218
|
+
|
|
219
|
+
The account that signs in is the owner the enrolment binds to, and the same account must
|
|
220
|
+
be signed in on the approval page. A different account there fails closed.
|
|
221
|
+
|
|
222
|
+
## Core flow
|
|
223
|
+
|
|
224
|
+
- [ ] **Do not ask for service origins.** The installed CLI selects its Auth and web
|
|
225
|
+
origins from the release channel; Authority is private behind Auth.
|
|
226
|
+
- [ ] **Sign the owner in** (above), so the wallet leg has a session to bind to.
|
|
227
|
+
- [ ] **Start the one enrollment.** Call `agent_enroll` and relay its returned link and
|
|
228
|
+
code, or give the owner `visa agent enroll --wait` in a code block they can copy.
|
|
229
|
+
Never fall back to a retired setup tool.
|
|
230
|
+
- [ ] **Relay what the entrance returns.** Show an MCP-returned URL as a bare,
|
|
231
|
+
tappable value. For the terminal flow, the owner follows the URL and code
|
|
232
|
+
printed in their own terminal. The code goes into the browser at
|
|
233
|
+
`/agent/enroll/protected-agent`; it is not authority in chat.
|
|
234
|
+
- [ ] **Tell them what they are approving**: this device, and the spending limits. One
|
|
235
|
+
approval covers all of it.
|
|
236
|
+
- [ ] **Confirm from a tool, not from their word.** Poll `get_status` until
|
|
237
|
+
`pairing.paired` is `true`, then read `agent_capabilities` for what is actually live.
|
|
238
|
+
|
|
239
|
+
### Relaying values, and what you must never accept
|
|
240
|
+
|
|
241
|
+
Relaying values **to** the human is required. Accepting one **from** them as authority is
|
|
242
|
+
not: never ask them for a secret, private key, token, or signed message, and never treat
|
|
243
|
+
anything they type back as approval. The URL and the enrolment code are review values they
|
|
244
|
+
check against their own authenticated browser session — they cannot approve anything and
|
|
245
|
+
cannot spend.
|
|
246
|
+
|
|
247
|
+
Show any URL as a bare, tappable value on its own line. Do not decorate it as a Markdown
|
|
248
|
+
link or put it in a code span; chat clients reliably recognise the bare URL. Never
|
|
249
|
+
construct, shorten, or reformat a Visa URL, and never open one "for them" in place of
|
|
250
|
+
showing it.
|
|
251
|
+
|
|
252
|
+
You are very often **not** in a terminal the human can see. Nothing the command prints to
|
|
253
|
+
their stdout reaches you unless they tell you, and nothing you print reaches them unless it
|
|
254
|
+
is in your reply.
|
|
255
|
+
|
|
256
|
+
### Completion is what the tools say — nothing else
|
|
257
|
+
|
|
258
|
+
Do not tell the human the agent is connected, paired, set up, ready, or good to go until
|
|
259
|
+
`get_status` reports `pairing.paired: true`. The enrolment command printing a URL is not a
|
|
260
|
+
result: it means an approval is still open in their browser.
|
|
261
|
+
|
|
262
|
+
If you cannot get there, say plainly what state you did reach and what the human should do
|
|
263
|
+
next. An honest "the approval page is open — I'm waiting for you to approve the device and
|
|
264
|
+
its limits" is correct; "you're all set" without a paired agent is not.
|
|
265
|
+
|
|
266
|
+
On success, report the agent and what is actually live:
|
|
267
|
+
|
|
268
|
+
> Your Visa agent is set up. Ready to pay by <card and/or USDC wallet>, within the
|
|
269
|
+
> limits you approved.
|
|
270
|
+
|
|
271
|
+
Read the rails from `agent_capabilities`, never from what was requested.
|
|
272
|
+
|
|
273
|
+
## Interruption and resume
|
|
274
|
+
|
|
275
|
+
The runtime persists the pending enrolment before it starts, so an interrupted run is safe
|
|
276
|
+
to repeat: **the same command again** resumes it, with the same request and the same
|
|
277
|
+
device key.
|
|
278
|
+
|
|
279
|
+
- Same command, no flags changed — resumes and reprints the URL and code.
|
|
280
|
+
- `--restart` — replaces an unclaimed request with a fresh terminal code. Use it only when
|
|
281
|
+
the previous code is genuinely unusable; it is not a retry button.
|
|
282
|
+
- `--wait` — keeps the command polling until the owner has approved, instead of returning
|
|
283
|
+
after printing the link.
|
|
284
|
+
|
|
285
|
+
Do not tell the human to run a second, different enrolment because the first went quiet.
|
|
286
|
+
Two enrolments mean two agents, two identities, and a confused owner. Do not delete or edit
|
|
287
|
+
local pending files to fix a transient failure, and never copy pending state between
|
|
288
|
+
runtimes.
|
|
289
|
+
|
|
290
|
+
## Management commands (an agent that already exists)
|
|
143
291
|
|
|
144
292
|
```
|
|
145
|
-
visa agent
|
|
293
|
+
visa agent list --format json
|
|
294
|
+
visa agent show <agentId> --format json
|
|
295
|
+
visa agent spendability --format json # can it spend, and what is missing
|
|
296
|
+
visa agent preflight --format json # every gate before a payment
|
|
297
|
+
visa agent pause|resume|revoke <agentId>
|
|
298
|
+
visa agent keychain status|repair # this device's identity custody
|
|
146
299
|
```
|
|
147
300
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
301
|
+
None of these creates an identity. Prefer structured output and parse it; never scrape
|
|
302
|
+
prose.
|
|
303
|
+
|
|
304
|
+
## Already set up — do NOT enrol again
|
|
305
|
+
|
|
306
|
+
If this device already holds an agent, a fresh enrolment is not needed and creates a
|
|
307
|
+
_second, separate_ agent. Do this instead:
|
|
308
|
+
|
|
309
|
+
1. **Tell the user plainly:** "This device is already set up as `<name>`." Read the name
|
|
310
|
+
from `agent_capabilities` or `visa agent list`. Start another enrolment only if they
|
|
311
|
+
explicitly want a second agent.
|
|
312
|
+
2. **Report status honestly — "set up" is several separate things.** Never imply the agent
|
|
313
|
+
can spend just because it exists. Read it live from tools rather than guessing from
|
|
314
|
+
prose: `agent_capabilities` returns the DERIVED capability map, `get_status` reports
|
|
315
|
+
pairing / account / version, `visa agent spendability --format json` answers "can it
|
|
316
|
+
spend, and what is missing", and `agent_login` establishes or confirms the account
|
|
317
|
+
session.
|
|
318
|
+
- **Identity** — bound to _this user's_ account, on _this device_.
|
|
319
|
+
- **Spending** — the limits the owner approved in the browser. You **cannot** self-grant
|
|
320
|
+
either rail, and never self-mint a wallet with `wallet_init` on mainnet — it throws
|
|
321
|
+
`WalletCredentialRequiredError` until the owner's delegation lands.
|
|
322
|
+
- **Mesh (`.visa` messaging)** — separate; `visa register <name>` joins it.
|
|
323
|
+
- **Trusted (TAP)** — follows spend/provisioning; don't promise it before then.
|
|
324
|
+
3. **Scope everything to the user.** The identity is bound to the account they signed in
|
|
325
|
+
with; the wallet and limits are theirs. Speak in terms of "your agent / your account /
|
|
326
|
+
the limits you approved", never a shared identity.
|
|
327
|
+
|
|
328
|
+
## An existing agent is missing a capability
|
|
329
|
+
|
|
330
|
+
There is no rail-add ceremony any more: `visa agent grant-card` / `grant-wallet` /
|
|
331
|
+
`grant-activate` / `grant-claim` and the `agent_connect` tools were deleted, and calling
|
|
332
|
+
one returns `legacy_door_removed`.
|
|
333
|
+
|
|
334
|
+
What to do instead:
|
|
335
|
+
|
|
336
|
+
- [ ] **Diagnose first.** `visa agent spendability --format json` and
|
|
337
|
+
`agent_capabilities` say exactly what is missing. Do not start anything before you
|
|
338
|
+
know which of identity, wallet delegation, card authority or funding is absent.
|
|
339
|
+
- [ ] **If the device's identity custody is broken**, `visa agent keychain connect-device` fixes
|
|
340
|
+
it without minting a new agent.
|
|
341
|
+
- [ ] **If the OWNER never approved that authority**, only they can add it. Say so, name
|
|
342
|
+
what is missing, and stop. Enrolling again mints a NEW agent — it does not upgrade
|
|
343
|
+
this one, and doing it silently leaves the owner with two agents and one funded
|
|
344
|
+
wallet.
|
|
345
|
+
|
|
346
|
+
A policy refusal or timeout means **nothing was signed**: report it and stop; do not retry
|
|
347
|
+
with a different command or a reconstructed URL. Never raise a human-approved limit
|
|
348
|
+
yourself.
|
|
349
|
+
|
|
350
|
+
## Spending, once a rail is live
|
|
351
|
+
|
|
352
|
+
Wallet: set the policy with `wallet_policy_set` (per-transaction / daily / session USD
|
|
353
|
+
caps plus optional network and merchant allow/deny lists that refuse an x402 payment
|
|
354
|
+
BEFORE it is signed), then `wallet_pay`. The served wallet tools are `wallet_discover`
|
|
355
|
+
(search the public x402 Bazaar), `wallet_probe` (read a challenge without paying),
|
|
356
|
+
`wallet_pay` / `wallet_directory_pay` (pay, policy-enforced), `wallet_history` /
|
|
357
|
+
`wallet_reconcile` (local ledger + resolve `reconciling` holds), `wallet_fund` (funding
|
|
358
|
+
address + faucet), and `wallet_export` (export key material — dangerous). All spending is
|
|
359
|
+
gated by the owner-approved local policy caps.
|
|
360
|
+
|
|
361
|
+
Card: not available in this build. Visa CLI ships no browser checkout (#8940), so
|
|
362
|
+
`start_card_mandate` returns `card_browser_checkout_removed` and creates no mandate, and
|
|
363
|
+
every `pay_merchant` action refuses with the same code. Nothing is charged. Do not offer
|
|
364
|
+
card checkout to the user, and do not relay a card approval URL; the wallet rail above is
|
|
365
|
+
the payment path.
|
|
366
|
+
|
|
367
|
+
On a hosted runtime, **every payment requires the owner's browser
|
|
368
|
+
approval** until the protected no-tap executor lands. Enrollment, a session, or a spending
|
|
369
|
+
grant does not approve a later hosted payment. Relay the hosted approval URL and wait for
|
|
370
|
+
the owner's decision before reporting success or retrying.
|
|
371
|
+
|
|
372
|
+
## Optional `.visa` mesh binding (separate from setup)
|
|
373
|
+
|
|
374
|
+
Pairing does not register a `.visa` name, TAP key, or Subway peer. If the operator has
|
|
375
|
+
separately enabled and registered those channel bindings, the mounted `visa-cli` MCP server
|
|
376
|
+
may expose `subway_register`, `subway_send`, `subway_inbox`, and `subway_find`.
|
|
377
|
+
|
|
378
|
+
Mesh messaging is available only on a compatible RC/dev build with `SUBWAY_MESH=visa` and
|
|
379
|
+
a reachable `SUBWAY_RELAY_MULTIADDR`. If those tools or the relay are unavailable, report
|
|
380
|
+
that clearly and stop. Do not imply that connecting an agent granted directory or
|
|
381
|
+
messaging authority, and do not improvise another transport.
|
|
382
|
+
|
|
383
|
+
## Optional agent mailbox (separate from setup)
|
|
384
|
+
|
|
385
|
+
Connecting an agent does not provision an email address or inbox. If the agent needs a
|
|
386
|
+
mailbox — e.g. to receive a merchant's account-signup or one-time-code email —
|
|
387
|
+
connect one explicitly, from the connected runtime, with the raw CLI:
|
|
153
388
|
|
|
154
|
-
|
|
389
|
+
```
|
|
390
|
+
visa agent mail-connect <agentId>
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
This is CLI-only; no pairing step or MCP tool connects a mailbox. It requires an
|
|
394
|
+
already-connected stable-agent identity on this runtime — it reads the local agent
|
|
395
|
+
record and proves the Ed25519 identity to the service. It issues the stable
|
|
396
|
+
agent mailbox if one does not exist, then stores an inbox-scoped credential in an
|
|
397
|
+
owner-only `0600` runtime file so this runtime can read that one inbox.
|
|
398
|
+
|
|
399
|
+
Be honest about scope. A mailbox grants an email address and the ability to read
|
|
400
|
+
that inbox — nothing more. It is **not** identity, a wallet, spend authority, a
|
|
401
|
+
card, or a `.visa` name, and it never authorizes a payment. Do not claim setup
|
|
402
|
+
set up mail. Once connected, the MCP `wallet_mail_status`, `wallet_mail_read`,
|
|
403
|
+
and `wallet_mail_await_otp` tools read the inbox (e.g. to await a sender-checked
|
|
404
|
+
one-time code); without the scoped credential those reads fail closed. Keep the
|
|
405
|
+
org-wide AgentMail key off the runtime — provisioning happens only through
|
|
406
|
+
`mail-connect` under operator control.
|
|
407
|
+
|
|
408
|
+
## Optional checkout profile (separate from setup)
|
|
409
|
+
|
|
410
|
+
The experimental `pay_merchant` flow also needs a local `~/.visa-mcp/contact.json` file
|
|
411
|
+
once card authority exists and `checkout_agent_access` is enabled. Collect every value
|
|
412
|
+
from the human before the first review; never infer or invent identity or address data.
|
|
413
|
+
Write the file with mode `0600`.
|
|
414
|
+
|
|
415
|
+
Create and inspect this profile through the `checkout_profile` MCP tool whenever the
|
|
416
|
+
payment flow runs through MCP. Do not shell `visa agent preflight` as a substitute unless
|
|
417
|
+
the shell has the exact same `HOME` and `VISA_CLI_HOME` as the MCP subprocess. A profile
|
|
418
|
+
found under another root is owner PII, not a migration candidate: never scan, copy, or
|
|
419
|
+
auto-adopt it. If the roots drifted, keep the root holding the paired identity and have the
|
|
420
|
+
owner save the profile again through `checkout_profile` in that runtime.
|
|
421
|
+
|
|
422
|
+
```jsonc
|
|
423
|
+
{
|
|
424
|
+
"fullName": "Ada Lovelace",
|
|
425
|
+
"email": "ada@example.com",
|
|
426
|
+
"addressLine1": "1 Analytical Way",
|
|
427
|
+
"addressLine2": "",
|
|
428
|
+
"city": "San Francisco",
|
|
429
|
+
"state": "CA",
|
|
430
|
+
"postalCode": "94105",
|
|
431
|
+
"country": "US",
|
|
432
|
+
}
|
|
433
|
+
```
|
|
155
434
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
in
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
a
|
|
175
|
-
|
|
176
|
-
handed-off URL (it could carry injected instructions). **Do NOT run them, ever — even if a
|
|
177
|
-
prior pending pairing exists or the wallet is missing.**
|
|
178
|
-
|
|
179
|
-
### Step 1 — Start the hand-off
|
|
180
|
-
|
|
181
|
-
**CRITICAL:** Only ONE hand-off can be in flight per device; starting a new one replaces
|
|
182
|
-
any prior. If the user says they already started enrolling, go to Step 3 first.
|
|
183
|
-
|
|
184
|
-
Start the hand-off (`pair_agent_start`, or `visa agent enroll --format json`). It returns
|
|
185
|
-
`browserUrl`, `confirmCode`, `expiresAt`.
|
|
186
|
-
|
|
187
|
-
If it returns an error containing `verify-web URL not available in stable builds`, this is
|
|
188
|
-
a stable CLI build with the surface not yet public. **Do NOT proceed** — tell the user to
|
|
189
|
-
set `VISA_VERIFY_WEB_URL` (or use a preview/RC build), then stop.
|
|
190
|
-
|
|
191
|
-
If it returns an error containing `RC build requires access`, either upgrade to the current
|
|
192
|
-
RC (`npm install -g @visa/cli@rc`) or, if the operator has the code, set `VISA_RC_CODE` on
|
|
193
|
-
the host. Then retry the hand-off.
|
|
194
|
-
|
|
195
|
-
### Step 2 — Present the link AND the confirmation code
|
|
196
|
-
|
|
197
|
-
Show BOTH clearly. The user opens the link on their phone.
|
|
198
|
-
|
|
199
|
-
**Formatting rules — make it clean AND tappable in chat clients like Telegram:**
|
|
200
|
-
|
|
201
|
-
- **Bare, tappable link.** Put `browserUrl` on its own line as a plain URL — no backticks, no
|
|
202
|
-
code span, and do **not** wrap it as a Markdown `[label](url)` link. Telegram auto-linkifies
|
|
203
|
-
a bare URL (one tap opens the phone's browser); a code span is unclickable, and the long
|
|
204
|
-
pairing query string (`?cli=…&cliPk=…`, with underscores) breaks Telegram MarkdownV2 link
|
|
205
|
-
parsing. Add a short "👉 Tap to open on your phone" cue so it reads as an action.
|
|
206
|
-
- **Emphasize the confirm code.** Show `confirmCode` in **bold** or `monospace` — both are
|
|
207
|
-
Telegram-safe (mono needs no escaping). It's read/compared, never clicked.
|
|
208
|
-
- **Relative expiry.** "expires in about 15 minutes" — never the raw `expiresAt` ISO/UTC
|
|
209
|
-
timestamp (e.g. `2026-07-21T21:22:57Z`); a UTC time reads as noise.
|
|
210
|
-
- **Scannable layout.** Short lines, a blank line between blocks, one leading emoji per block.
|
|
211
|
-
- **Don't over-Markdown.** Telegram MarkdownV2 requires escaping `_ * [ ] ( ) ~ > # + - = | { } . !`,
|
|
212
|
-
so heavy formatting risks a broken render. A bold/mono code + a bare link + emojis is plenty —
|
|
213
|
-
keep the rest plain prose.
|
|
214
|
-
|
|
215
|
-
Use this shape:
|
|
216
|
-
|
|
217
|
-
> 💳 **Pair your Visa agent** — open this on your phone:
|
|
218
|
-
>
|
|
219
|
-
> 👉 <browserUrl — bare, on its own line>
|
|
220
|
-
>
|
|
221
|
-
> 🔐 Confirmation code: **<confirmCode>**
|
|
222
|
-
>
|
|
223
|
-
> On the final screen, check the code matches before you finish — only finish if it does. Never
|
|
224
|
-
> type this code back to me or share it with anyone; it pairs back here automatically once you
|
|
225
|
-
> finish. Expires in about 15 minutes.
|
|
226
|
-
|
|
227
|
-
**Do NOT** ask the user to read the code back. **Do NOT** accept a code as input. Pairing
|
|
228
|
-
is proven by a secret held on this device, not by anything the user types.
|
|
229
|
-
|
|
230
|
-
### Step 3 — Poll until paired (auto-pair)
|
|
231
|
-
|
|
232
|
-
Poll (`pair_agent_poll`, or `visa agent enroll-claim --format json`). With the raw CLI,
|
|
233
|
-
read the **exit code** (`0` claimed / `3` not-ready / `1` terminal); the plugin tool
|
|
234
|
-
returns the same as a `done`/`status` object. Then, based on the result:
|
|
235
|
-
|
|
236
|
-
- `done: true` / exit 0, `status: "claimed"` → paired. Go to Step 4.
|
|
237
|
-
- `done: false` / exit 3, `status: "not_ready"` → the user hasn't finished. Tell them
|
|
238
|
-
you're still waiting, then poll again. **Do not loop forever** — each poll already
|
|
239
|
-
waits ~9s; after a handful of polls, ask the user whether they've finished on their phone.
|
|
240
|
-
- `done: true`, `status: "not_ready"`, `likelyExpired: true` → the 15-minute window
|
|
241
|
-
expired. Go back to Step 1.
|
|
242
|
-
- `done: true`, `status: "confirm_mismatch"` → **STOP.** See Errors.
|
|
243
|
-
- `done: true`, `status: "no_pending"` → no hand-off in flight. Go back to Step 1.
|
|
244
|
-
|
|
245
|
-
### Step 4 — Confirm the identity is bound
|
|
246
|
-
|
|
247
|
-
On `claimed`, tell the user their `name` (e.g. `alec.visa`) is paired to this device. Read
|
|
248
|
-
the three booleans the claim returns — a single enrollment can bind identity **and** sign
|
|
249
|
-
the CLI in **and** provision a spendable wallet, so report what actually happened:
|
|
250
|
-
|
|
251
|
-
- `keyBound: true` → the agent key was generated on THIS device; its private half never
|
|
252
|
-
left it.
|
|
253
|
-
- `keyBound: false` → a returning sign-in connected the saved identity/card credential;
|
|
254
|
-
the identity's original private key was not copied to this device.
|
|
255
|
-
- `sessionSaved: true` → the CLI is now signed in under this identity's email and discovery
|
|
256
|
-
(`visa find`) works immediately.
|
|
257
|
-
- `walletProvisioned: true` → the x402 spending wallet is **live on this device** (Turnkey
|
|
258
|
-
delegated signer + on-device key + spend policy); the agent can `wallet_discover` →
|
|
259
|
-
`wallet_pay` and `visa find`/`pay` **right now** — the only remaining step is funding the
|
|
260
|
-
wallet address.
|
|
261
|
-
- `sessionSaved: false` or `walletProvisioned: false` → report the missing capability
|
|
262
|
-
exactly. Pairing still retained the identity/card credential; do not describe it as a
|
|
263
|
-
total failure and do not fall back to the terminal-only `agent create/claim` ceremony.
|
|
264
|
-
If the session is missing, a later enrollment retry is the supported recovery.
|
|
265
|
-
|
|
266
|
-
There is no automatic fallback wallet ceremony. A fully provisioned hand-off completes
|
|
267
|
-
device enrollment; an honest partial remains paired and names what still is not available.
|
|
268
|
-
|
|
269
|
-
## What you can do once paired
|
|
270
|
-
|
|
271
|
-
Pairing is the on-ramp. v4 is **non-custodial** — a Turnkey-delegated wallet bounded by
|
|
272
|
-
on-device keys and policies, **not** a stored credit line or a server-custodied card.
|
|
273
|
-
Describe it that way to the user. The mounted MCP server exposes:
|
|
274
|
-
|
|
275
|
-
- **x402 wallet spend (the core rail) — ALWAYS drive it through these MCP tools; never
|
|
276
|
-
substitute another client.** The buy sequence:
|
|
277
|
-
1. **`wallet_discover`** — find payable x402 services by outcome (free, directory-backed).
|
|
278
|
-
If it returns empty or `Not logged in`, do NOT switch discovery tools — proceed to step 2
|
|
279
|
-
with any x402 URL the user names (the wallet pays any endpoint, no directory needed).
|
|
280
|
-
2. **`wallet_probe`** — preview a fresh x402 challenge for a discovered listing **or any
|
|
281
|
-
x402 URL** (free, no spend, needs no session). Confirm network (`eip155:8453` / Base),
|
|
282
|
-
asset (Base USDC), and that the price is at or below the user's ceiling.
|
|
283
|
-
3. **`wallet_pay`** (arbitrary URL) or **`wallet_directory_pay`** (a directory listing) —
|
|
284
|
-
a bounded payment settled directly from the delegated wallet over x402, enforcing the
|
|
285
|
-
on-device policy and journaling a receipt. Always pass a hard `max` ceiling.
|
|
286
|
-
|
|
287
|
-
Bounded by the on-device wallet policy; never touches credits, cards, or server-side spend
|
|
288
|
-
controls. These `wallet_*` tools are **default-on in the supported build**. CLI equivalents
|
|
289
|
-
(`visa find` / `inspect` / `pay`) exist, but prefer the MCP tools — and note `visa find` is
|
|
290
|
-
session-gated (it can report `Not logged in`), whereas `wallet_probe` / `wallet_pay` work on
|
|
291
|
-
any x402 URL directly, so use those when discovery is unavailable.
|
|
292
|
-
|
|
293
|
-
**NEVER — to find or pay an x402 service — fall back to any of:** `npx awal` or any
|
|
294
|
-
"bazaar"/third-party discovery client; `curl` or hand-built EIP-3009 signatures / another
|
|
295
|
-
payment client; reading a merchant's OpenAPI / `/docs` to guess an endpoint and pay it
|
|
296
|
-
blind; or any retired catalog/direct-execution surface. If the wallet tools cannot find
|
|
297
|
-
or pay something, report that to the user
|
|
298
|
-
with what you tried and stop — do not improvise another payment path. `wallet_probe` +
|
|
299
|
-
`wallet_pay` already settle ANY x402 endpoint the user gives you.
|
|
300
|
-
|
|
301
|
-
- **Message other agents on the `.visa` mesh (Subway)** — once paired, your agent's Visa
|
|
302
|
-
identity **doubles as its Subway mesh identity** (admission reuses the same Visa-signed
|
|
303
|
-
device-pairing + TAP binding, so a paired, TAP-registered `.visa` agent is already
|
|
304
|
-
admitted — no extra key, no separate install; the Subway SDK is bundled into the mounted
|
|
305
|
-
`visa-cli` MCP server). The tools:
|
|
306
|
-
- `subway_register` — FREE. Claim your handle → `<name>.visa` on the mesh (reuses your
|
|
307
|
-
agent identity). Returns the mesh name + peer id.
|
|
308
|
-
- `subway_send` — FREE. Send a direct **signed** message to another agent: `to` (their
|
|
309
|
-
handle, e.g. `"dee"` → `dee.visa`) + `text`. Reaches other `.visa` mesh peers only.
|
|
310
|
-
(A Telegram bridge is planned — #6119 — but not built yet; do not tell a user they can
|
|
311
|
-
message a Telegram contact through `subway_send`.)
|
|
312
|
-
- `subway_inbox` — FREE. Read recent inbound messages (`limit`, `clear` to drain).
|
|
313
|
-
- `subway_find` — resolve a handle to its `.visa` peer.
|
|
314
|
-
|
|
315
|
-
**Gated (be honest with the user):** mesh messaging is live only on an **RC/dev build**
|
|
316
|
-
with `SUBWAY_MESH=visa` **and a reachable relay** (`SUBWAY_RELAY_MULTIADDR`, a Visa Crypto
|
|
317
|
-
Labs deployment). On a stable build the `subway_*` tools aren't exposed; without a relay
|
|
318
|
-
they no-op (`subway_register` reports the binding is "ready for admission once a relay is
|
|
319
|
-
up"). If a user asks to message another `.visa` agent and the mesh isn't wired, say so
|
|
320
|
-
plainly and stop — do not improvise another transport.
|
|
321
|
-
|
|
322
|
-
- **Real-merchant card checkout (experimental, opt-in)** — `pay_merchant` fills and pays an
|
|
323
|
-
ordinary merchant web checkout with a **Verified Agent card credential**: a one-shot
|
|
324
|
-
network-token cryptogram minted **on this device** (non-custodial) — not a stored card,
|
|
325
|
-
not x402, not server-side spend controls. Two steps: `review` (free; returns merchant +
|
|
326
|
-
exact amount as a `reviewId`) then `pay` (requires `confirm: "PAY <reviewId>"` + a passkey,
|
|
327
|
-
and CHARGES). Prerequisites — the tool errors clearly if any is missing:
|
|
328
|
-
1. **`checkout_agent_access`** flag on your account (email-keyed; an admin grants it via
|
|
329
|
-
`PUT /v1/admin/users/<your-enroll-email>/feature-flags/checkout_agent_access` or the
|
|
330
|
-
admin panel). Distinct from the RC/GitHub allowlist.
|
|
331
|
-
2. **`CHECKOUT_AGENT_ALLOW_SUBMIT=1`** in the MCP server's env. This is the submit opt-in:
|
|
332
|
-
WITHOUT it the agent fills the checkout form but **refuses to press the pay button** (the
|
|
333
|
-
default safe posture — `submit:false`), so a checkout silently never completes. Set it on
|
|
334
|
-
the `visa-cli` MCP server entry (e.g. OpenClaw `mcp.servers["visa-cli"].env`, Hermes
|
|
335
|
-
`mcp_servers.visa-cli.env`, or `claude mcp add … -e CHECKOUT_AGENT_ALLOW_SUBMIT=1`). The
|
|
336
|
-
`visa-cli checkout … --submit` CLI flag sets the same opt-in.
|
|
337
|
-
3. An enrolled agent credential (`enroll_agent`) and a `~/.visa-mcp/contact.json`. This file
|
|
338
|
-
supplies the **cardholder name** the credential is minted with AND the billing details
|
|
339
|
-
filled into the merchant form. If it is missing, or `fullName` is empty/whitespace, the
|
|
340
|
-
checkout dies at the final step with `cardholder name is required` (after mandate
|
|
341
|
-
approval — an expensive late failure). **Before the first checkout, ASK the user for
|
|
342
|
-
these fields and write the file yourself** (0600), with a REAL non-blank `fullName`:
|
|
343
|
-
|
|
344
|
-
```jsonc
|
|
345
|
-
// ~/.visa-mcp/contact.json (chmod 600)
|
|
346
|
-
{
|
|
347
|
-
"fullName": "Ada Lovelace", // REQUIRED, non-blank — the cardholder name
|
|
348
|
-
"email": "ada@example.com", // REQUIRED — used by the approval ceremony
|
|
349
|
-
"addressLine1": "1 Analytical Way",
|
|
350
|
-
"addressLine2": "",
|
|
351
|
-
"city": "London",
|
|
352
|
-
"state": "CA", // 2-letter region where applicable
|
|
353
|
-
"postalCode": "94105",
|
|
354
|
-
"country": "US", // 2-letter ISO
|
|
355
|
-
}
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
Exact keys only (`fullName` | `firstName`+`lastName`, `email`, `addressLine1/2`, `city`,
|
|
359
|
-
`state`, `postalCode`, `country`) — the engine reads these names verbatim. Never invent a
|
|
360
|
-
name; if the user won't provide one, stop and say checkout needs a real cardholder name.
|
|
361
|
-
|
|
362
|
-
**RC/preview builds only**, opt-in, never paired-and-go.
|
|
363
|
-
|
|
364
|
-
If a tool you expect isn't visible, the MCP server isn't mounted (or the v4 wallet runtime
|
|
365
|
-
isn't bundled in this build) — go back to "Getting set up".
|
|
366
|
-
|
|
367
|
-
## Important
|
|
368
|
-
|
|
369
|
-
- **The confirmation code is display-only.** Show it; never ask for it; never accept it as
|
|
370
|
-
input. It lets the human verify the page matches the flow you started.
|
|
371
|
-
- **Never read, print, log, or echo** the credential file or the enrollment pending file,
|
|
372
|
-
or the `browserUrl` query values beyond the single presentation to the user. The link
|
|
373
|
-
carries only a hash + a public key — safe in history — but treat it as one-time.
|
|
374
|
-
- Do not run API-key setup, card enrollment, or a balance top-up as a substitute for
|
|
375
|
-
pairing. Once paired, use the `visa-cli` skill's v4 wallet commands to pay.
|
|
376
|
-
|
|
377
|
-
## Limits
|
|
378
|
-
|
|
379
|
-
| Limit | Value |
|
|
380
|
-
| ------------------------------ | ------------------------------------------------------------ |
|
|
381
|
-
| Hand-off validity | 15 minutes from `pair_agent_start` |
|
|
382
|
-
| In-flight hand-offs per device | 1 (a new start replaces the prior) |
|
|
383
|
-
| Claim | single-shot server-side; once claimed the entry is destroyed |
|
|
384
|
-
| Confirmation code | 6 chars, no ambiguous glyphs; display-only |
|
|
385
|
-
|
|
386
|
-
## Errors
|
|
387
|
-
|
|
388
|
-
All errors are JSON with a non-zero exit code; `enroll-claim` tags them with `status`.
|
|
389
|
-
|
|
390
|
-
| status / symptom | Cause | Recovery |
|
|
391
|
-
| ------------------------------------------------------ | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
392
|
-
| `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. |
|
|
393
|
-
| `error: RC build requires access` | The RC employee gate rejected the bootstrap | Upgrade the RC (`npm install -g @visa/cli@rc`) or set `VISA_RC_CODE`, then retry the hand-off. |
|
|
394
|
-
| `no_pending` | No hand-off in flight (never started, or already claimed/expired) | Start fresh with `pair_agent_start`. |
|
|
395
|
-
| `not_ready`, `likelyExpired: false` | User hasn't finished the mobile flow | Wait, tell the user, poll again. Bounded polling only. |
|
|
396
|
-
| `not_ready`, `likelyExpired: true` | 15-minute window elapsed | Start over with `pair_agent_start`. |
|
|
397
|
-
| `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. |
|
|
398
|
-
| `error` (network / malformed) | Transport or server error | Surface the message. Poll once more; if it persists, start over. |
|
|
435
|
+
`fullName` must be non-blank; `firstName` plus `lastName` is also accepted. The engine reads
|
|
436
|
+
the exact keys `fullName`, `firstName`, `lastName`, `email`, `addressLine1`, `addressLine2`,
|
|
437
|
+
`city`, `state`, `postalCode`, and `country`. The profile supplies checkout/cardholder and
|
|
438
|
+
billing data only. Its `email` value is not the account's verified owner email, an agent
|
|
439
|
+
mailbox, key proof, recovery factor, or permission to spend.
|
|
440
|
+
|
|
441
|
+
## Security rules
|
|
442
|
+
|
|
443
|
+
- Never read, print, log, paste, or transmit the private Ed25519 JWK or any local claim
|
|
444
|
+
token.
|
|
445
|
+
- Never read or echo local pending files. Present only the URL returned by the tool.
|
|
446
|
+
- Never treat `identityKeyJkt` as the stable identity. Use `agentId` for references and
|
|
447
|
+
persistence.
|
|
448
|
+
- Never fetch or submit the review URL on the human's behalf. They review and approve it
|
|
449
|
+
in their own browser.
|
|
450
|
+
- Never invent a secondary path when a call fails. Preserve the local state, surface the
|
|
451
|
+
error, and resume through the same protected entrance: call `agent_enroll` again, or
|
|
452
|
+
repeat the same `visa agent enroll --wait` command.
|
|
453
|
+
- Never call a retired door to "check whether it still works". `legacy_door_removed` is a
|
|
454
|
+
final answer, not a transient failure.
|
|
399
455
|
|
|
400
456
|
## Further docs
|
|
401
457
|
|
|
402
|
-
- `
|
|
403
|
-
- `
|
|
458
|
+
- `RUNTIMES.md` — OpenClaw, Hermes, and raw CLI setup.
|
|
459
|
+
- `docs/agents/ARCHITECTURE.md` — where enrolment sits in the v4 request paths.
|
|
460
|
+
- `visacli.sh/agents` — product-facing agent documentation.
|