@visa/cli 5.0.0-rc.408 → 5.0.0-rc.409
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 +238 -65
- package/dist/cli.js +136 -212
- package/dist/enrollment-finisher.js +13 -101
- package/dist/mcp-server/index.js +465 -407
- package/dist/skills/pair-visa-agent/RUNTIMES.md +3 -2
- package/dist/skills/pair-visa-agent/SKILL.md +71 -18
- package/dist/skills/pair-visa-agent/scripts/__tests__/setup.test.mjs +5 -9
- package/package.json +9 -9
- package/server.json +2 -2
- package/dist/mcp-apps/ucp-checkout.html +0 -280
- package/dist/merchant-ucp-mcp/index.js +0 -7
- package/dist/skills/visa-shopify-checkout/SKILL.md +0 -95
- package/dist/skills/visa-shopify-checkout/references/evidence-and-states.md +0 -43
- package/dist/skills/visa-ucp-shopping/SKILL.md +0 -90
|
@@ -24,8 +24,9 @@ Markdown skill, CLI, and MCP protocol instead.
|
|
|
24
24
|
Both supported surfaces run the same protected enrollment and share the same local
|
|
25
25
|
pending record:
|
|
26
26
|
|
|
27
|
-
1. Visa MCP server: `agent_enroll
|
|
28
|
-
|
|
27
|
+
1. Visa MCP server: `agent_enroll` once; after the owner approves, this computer
|
|
28
|
+
finishes in the background (`get_status` shows `pairing.setup.state` go
|
|
29
|
+
`finishing` → `connected`; do not call `agent_enroll` while it is `finishing`).
|
|
29
30
|
2. Terminal: `visa connect` (add `--format json` for machine output).
|
|
30
31
|
|
|
31
32
|
Each returns one link and a short code for the human. The browser receives only that
|
|
@@ -37,9 +37,11 @@ It has two entrances to the exact same protected enrollment implementation: the
|
|
|
37
37
|
native signer, Authority routes, owner browser approval, and limits. Never invent or mix
|
|
38
38
|
in a second ceremony.
|
|
39
39
|
|
|
40
|
-
- **You have the Visa MCP server mounted:** call `agent_enroll` and
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
- **You have the Visa MCP server mounted:** call `agent_enroll` once and give the owner
|
|
41
|
+
`browserUrl` and `confirmCode` exactly as returned. After their one approval this computer
|
|
42
|
+
finishes in the background. Poll `get_status`: `pairing.setup.state` goes `finishing` →
|
|
43
|
+
`connected`, and `pairing.setup.owed` names any owner step still left (call `agent_enroll`
|
|
44
|
+
once to hand it over). Do not call `agent_enroll` while it is `finishing`.
|
|
43
45
|
- **The owner is at a terminal:** they run the command below.
|
|
44
46
|
|
|
45
47
|
```
|
|
@@ -56,7 +58,10 @@ The installed CLI selects its own service origins from its release channel. Neve
|
|
|
56
58
|
for, guess, or construct a Visa URL.
|
|
57
59
|
|
|
58
60
|
Running the same command again resumes an interrupted enrolment; `--restart` replaces an
|
|
59
|
-
unclaimed request with a fresh code.
|
|
61
|
+
unclaimed request with a fresh code. The local finisher can time out after about 15 minutes; this does not prove server expiry: when `pairing.setup.state` reads `expired`, call `agent_enroll` once without
|
|
62
|
+
`restart` (terminal: `visa connect`): it resumes the original request if the server still
|
|
63
|
+
accepts it, or answers `enrollment_expired` for an already-bound request the server no longer accepts. Only then call it with `restart: true` (terminal:
|
|
64
|
+
`visa connect --restart`) and relay the new link and code. `--ceiling <usd>` and `--per-transaction <usd>`
|
|
60
65
|
propose limits the owner confirms on the approval page.
|
|
61
66
|
|
|
62
67
|
## The tools the Visa MCP server serves
|
|
@@ -226,6 +231,9 @@ starts the enrolment.
|
|
|
226
231
|
code into the sign-in page** — into the browser, never back to you in chat.
|
|
227
232
|
- [ ] Call `agent_enroll` again once they have (the CLI command polls on its own). It
|
|
228
233
|
picks up the approved sign-in on this device and moves on to the enrolment link.
|
|
234
|
+
This second call belongs to the sign-in leg only: nothing in the background picks
|
|
235
|
+
up a sign-in. The background finish starts after the enrolment link, so once the
|
|
236
|
+
owner approves that, do not call while `pairing.setup.state` is `finishing`.
|
|
229
237
|
|
|
230
238
|
Signing in establishes the OWNER's session on this device. It creates no agent and grants
|
|
231
239
|
no spending authority — those come from the enrolment and the approval the owner gives in
|
|
@@ -358,38 +366,79 @@ What to do instead:
|
|
|
358
366
|
without minting a new agent — it renews the device lease and skips every step that
|
|
359
367
|
is already done.
|
|
360
368
|
- [ ] **If the OWNER never approved that authority**, only they can add it. Say so, name
|
|
361
|
-
what is missing, and stop.
|
|
362
|
-
|
|
363
|
-
wallet.
|
|
369
|
+
what is missing, and stop. Follow the returned Payments or limits action for this agent. Do not use
|
|
370
|
+
enrollment or restart to work around a payment refusal.
|
|
364
371
|
|
|
365
|
-
A policy refusal
|
|
366
|
-
|
|
367
|
-
|
|
372
|
+
A policy refusal is a stop: follow its returned remedy. Never raise a human-approved limit yourself. A timeout leaves the payment outcome unknown; use the recovery table below.
|
|
373
|
+
|
|
374
|
+
## Talking to the owner
|
|
375
|
+
|
|
376
|
+
<!-- recovery-guidance: replyStyle -->
|
|
377
|
+
|
|
378
|
+
Keep each owner message to a line or two, result first. Amounts in dollars; for an approval, the price and the link. Skip preamble, announcing next steps, recaps, how Visa works and waiting. No ids or hashes unless something failed; then one id. End with the result and one total, no per-call costs.
|
|
379
|
+
<!-- /recovery-guidance -->
|
|
368
380
|
|
|
369
381
|
## Spending, once a rail is live
|
|
370
382
|
|
|
371
|
-
Wallet: `discover`
|
|
372
|
-
paying), `pay` pays it with a mandatory `max` ceiling, and `history` reads this agent's
|
|
383
|
+
Wallet: `discover` searches with `query`, or probes a URL/listing id with `target`
|
|
384
|
+
without paying (target probes require wallet setup), `pay` pays it with a mandatory `max` ceiling, and `history` reads this agent's
|
|
373
385
|
payments. The limits are the ones the owner approved; the owner changes them in the Visa
|
|
374
386
|
Console, never you. Funding is the owner's, in the Console.
|
|
375
387
|
|
|
376
388
|
Card: Visa-operated browser checkout is retired; another enrollment will not repair it.
|
|
377
|
-
|
|
389
|
+
`pay` is the wallet door: it refuses a merchant checkout URL with `card_checkout_not_on_pay`.
|
|
378
390
|
On local builds that list `purchase_create`,
|
|
379
391
|
`purchase_retrieve` and `purchase_cancel`, the protected card preview uses the
|
|
380
392
|
host's checkout tools and the existing eligible card and agent identity. Follow
|
|
381
393
|
`next`: relay the single owner setup or limits URL, let the owner act, then repeat
|
|
382
|
-
the same purchase call. Never ask for funding-card secrets in chat. Explicit
|
|
394
|
+
the same purchase call. Never ask for funding-card secrets in chat. Use an already returned `purchase.card` under the owner’s disclosure consent; otherwise follow the explicit retrieval action. Explicit
|
|
383
395
|
`include:"card"` may put the issued purchase credential in model/chat history,
|
|
384
396
|
under the owner's disclosure consent. A saved card or tester flag alone does
|
|
385
397
|
not establish eligibility or merchant credential compatibility. UCP discovery
|
|
386
398
|
and preparation remain separate from payment execution; a `not_ready` handoff
|
|
387
399
|
is not permission to pay or to revive the retired browser flow.
|
|
388
400
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
401
|
+
## Payment outcomes and recovery
|
|
402
|
+
|
|
403
|
+
<!-- recovery-guidance: ownerAction, timeout, identity -->
|
|
404
|
+
|
|
405
|
+
Follow the returned code, stage, remedy and owner action. The specific nextAction takes precedence over kind. Platform needs human action or diagnosis. Show the exact approval link once; continue the same payment only when nextAction says so. On denial or expiry, follow its stop or recovery action. Escalate unclassified errors. A timeout does not prove nothing was signed or paid. Keep the same agent and purchase identity: wallet `economicIntentId` and exact arguments, card `resumeId`/`purchaseId`, or an app’s `idempotencyKey`/`draftId`. Never retry uncertainty under a new identity.
|
|
406
|
+
<!-- /recovery-guidance -->
|
|
407
|
+
|
|
408
|
+
<!-- recovery-guidance: localOutcomes -->
|
|
409
|
+
|
|
410
|
+
| Returned code or outcome | Action |
|
|
411
|
+
| ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
412
|
+
| `pending_activation` | Owner finishes the existing connection in the Console. |
|
|
413
|
+
| `spending_limit_required`, `grant_expired` | Owner sets or renews this agent’s limits; no re-pair. |
|
|
414
|
+
| `agent_paused`, `agent_revoked` | Owner resumes the paused agent; a revoked agent cannot pay again. Do not bypass the refusal; reconnecting does not bypass it. |
|
|
415
|
+
| `device_key_replaced` | Follow the returned connection action for this computer. |
|
|
416
|
+
| `owner_approval_required`, `wallet_owner_approval_required` | Show the exact returned approval link, then continue the same purchase after approval. |
|
|
417
|
+
| `owner_declined` | Stop; never retry automatically. |
|
|
418
|
+
| `owner_signature_unconfirmed`, `outcome_unknown`, `unreachable`, `malformed_response` | Read/reconcile the original request. Preserve its identity and token; do not create a replacement. |
|
|
419
|
+
| `cancellation_pending` | Retrieve the same purchase; liability is unresolved, not released. |
|
|
420
|
+
| `delivered` or `reconciling` | Check the recorded financial outcome; delivery is not settlement proof. Never pay the item again. |
|
|
421
|
+
|
|
422
|
+
<!-- /recovery-guidance -->
|
|
423
|
+
|
|
424
|
+
<!-- recovery-guidance: approval -->
|
|
425
|
+
|
|
426
|
+
Read the actual approval mode. In an app, `authority: owner_autonomy` can pay within the owner’s limits. `owner_approval_required` needs the returned owner link. A connected app does not need local `agent_enroll` or `visa connect`.
|
|
427
|
+
<!-- /recovery-guidance -->
|
|
428
|
+
|
|
429
|
+
For agents on the owner’s computer:
|
|
430
|
+
|
|
431
|
+
<!-- recovery-guidance: localOutput -->
|
|
432
|
+
|
|
433
|
+
Save output in an owner-controlled local file; re-read the local saved result.
|
|
434
|
+
<!-- /recovery-guidance -->
|
|
435
|
+
|
|
436
|
+
For apps:
|
|
437
|
+
|
|
438
|
+
<!-- recovery-guidance: missingOutput, noRefetch -->
|
|
439
|
+
|
|
440
|
+
If payment is confirmed but the result is missing (“Paid; result not received. Do not pay again”), tell the owner in one line that it was paid and nothing came back, with the original purchase id. Never ask the merchant again for a purchased result, even for GET requests.
|
|
441
|
+
<!-- /recovery-guidance -->
|
|
393
442
|
|
|
394
443
|
## Retired mesh integration
|
|
395
444
|
|
|
@@ -462,3 +511,7 @@ mailbox, key proof, recovery factor, or permission to spend.
|
|
|
462
511
|
- `RUNTIMES.md` — OpenClaw, Hermes, and raw CLI setup.
|
|
463
512
|
- `docs/agents/ARCHITECTURE.md` — where enrolment sits in the v4 request paths.
|
|
464
513
|
- `visacli.sh/agents` — product-facing agent documentation.
|
|
514
|
+
|
|
515
|
+
### Resuming after a local setup timeout
|
|
516
|
+
|
|
517
|
+
A local timeout is not proof of server expiry. After a plain resume, relay any new `browserUrl` and `confirmCode` returned: an unbound request the server has definitively refused can be replaced automatically. `enrollment_expired` means an already-bound request needs an explicit restart. A transient or ambiguous failure preserves the same request; do not use `restart: true` merely because a request timed out.
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
2
|
-
import { mkdirSync, readFileSync, writeFileSync, rmSync } from 'node:fs'
|
|
2
|
+
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync, rmSync } from 'node:fs'
|
|
3
3
|
import { dirname, join } from 'node:path'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
5
|
import { tmpdir } from 'node:os'
|
|
6
|
-
import { randomUUID } from 'node:crypto'
|
|
7
6
|
|
|
8
7
|
import {
|
|
9
8
|
parseSemver,
|
|
@@ -20,8 +19,7 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
20
19
|
let tempDir
|
|
21
20
|
|
|
22
21
|
beforeEach(() => {
|
|
23
|
-
tempDir = join(tmpdir(),
|
|
24
|
-
mkdirSync(tempDir, { recursive: true })
|
|
22
|
+
tempDir = mkdtempSync(join(tmpdir(), 'setup-test-'))
|
|
25
23
|
})
|
|
26
24
|
|
|
27
25
|
afterEach(() => {
|
|
@@ -129,7 +127,7 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
129
127
|
writeFileSync(
|
|
130
128
|
bundlePath,
|
|
131
129
|
`
|
|
132
|
-
const tools = ['get_status', 'agent_enroll'
|
|
130
|
+
const tools = ['get_status', 'agent_enroll'];
|
|
133
131
|
`
|
|
134
132
|
)
|
|
135
133
|
const probe = probeMcpTools(bundlePath, REQUIRED_MCP_TOOLS)
|
|
@@ -165,6 +163,7 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
165
163
|
})
|
|
166
164
|
|
|
167
165
|
it('requires only served tools: every required tool is a served general tool', () => {
|
|
166
|
+
expect(Object.isFrozen(REQUIRED_MCP_TOOLS)).toBe(true)
|
|
168
167
|
const served = readFileSync(
|
|
169
168
|
join(
|
|
170
169
|
dirname(fileURLToPath(import.meta.url)),
|
|
@@ -276,10 +275,7 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
276
275
|
const mockNpmRoot = vi.fn(() => tempDir)
|
|
277
276
|
|
|
278
277
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
279
|
-
const result = await runSetup(
|
|
280
|
-
{ VISA_RELEASE_CHANNEL: 'rc' },
|
|
281
|
-
{ execFileSync: mockExec, npmRootFn: mockNpmRoot }
|
|
282
|
-
)
|
|
278
|
+
const result = await runSetup({}, { execFileSync: mockExec, npmRootFn: mockNpmRoot })
|
|
283
279
|
logSpy.mockRestore()
|
|
284
280
|
|
|
285
281
|
expect(mockExec).toHaveBeenCalledWith(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@visa/cli",
|
|
3
|
-
"version": "5.0.0-rc.
|
|
3
|
+
"version": "5.0.0-rc.409",
|
|
4
4
|
"description": "Visa CLI runtime for stable agent identity and separately authorized payment capabilities",
|
|
5
5
|
"bin": {
|
|
6
6
|
"visa-cli": "./bin/visa-cli.js",
|
|
@@ -11,21 +11,24 @@
|
|
|
11
11
|
"sync:server-json": "node scripts/sync-server-json.mjs",
|
|
12
12
|
"check:server-json": "node scripts/sync-server-json.mjs --check",
|
|
13
13
|
"prebuild": "node scripts/sync-server-json.mjs",
|
|
14
|
-
"build": "
|
|
14
|
+
"build": "pnpm test:types && node esbuild.config.js",
|
|
15
15
|
"prepack": "node scripts/sync-server-json.mjs --check",
|
|
16
16
|
"dev": "tsc --watch",
|
|
17
17
|
"pretest": "pnpm build",
|
|
18
18
|
"start": "node dist/mcp-server/index.js",
|
|
19
19
|
"test": "jest --config jest.config.js",
|
|
20
|
+
"test:types": "tsc -p tsconfig.test.json",
|
|
21
|
+
"pretest:unit": "pnpm test:types",
|
|
20
22
|
"test:unit": "jest --config jest.config.js",
|
|
23
|
+
"pretest:unit:watch": "pnpm test:types",
|
|
21
24
|
"test:unit:watch": "jest --config jest.config.js --watch",
|
|
25
|
+
"pretest:unit:coverage": "pnpm test:types",
|
|
22
26
|
"test:unit:coverage": "jest --config jest.config.js --coverage",
|
|
23
27
|
"test:smoke": "VISA_AUTH_URL=https://auth.visacli.sh jest --config jest.smoke.config.js",
|
|
24
28
|
"test:packed": "node scripts/packed-cli-smoke.mjs",
|
|
25
|
-
"test:integration": "jest --config jest.integration.config.js",
|
|
26
29
|
"test:e2e": "jest --config jest.e2e.config.js",
|
|
27
30
|
"coinbase:onramp": "pnpm exec tsx tests/e2e/coinbase-onramp-harness/run.ts",
|
|
28
|
-
"test:all": "npm run test:unit && npm run test:
|
|
31
|
+
"test:all": "npm run test:unit && npm run test:e2e",
|
|
29
32
|
"prepublishOnly": "npm run build && npm test",
|
|
30
33
|
"lint": "eslint src/**/*.ts",
|
|
31
34
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
@@ -44,9 +47,7 @@
|
|
|
44
47
|
"author": "Visa Crypto Labs",
|
|
45
48
|
"license": "SEE LICENSE IN LICENSE",
|
|
46
49
|
"dependencies": {
|
|
47
|
-
"@modelcontextprotocol/ext-apps": "1.7.5",
|
|
48
50
|
"@modelcontextprotocol/server": "2.0.0",
|
|
49
|
-
"@ucp-js/sdk": "0.4.4",
|
|
50
51
|
"commander": "^12.1.0",
|
|
51
52
|
"punycode": "^2.3.1",
|
|
52
53
|
"zod": "^3.25.76"
|
|
@@ -54,14 +55,13 @@
|
|
|
54
55
|
"devDependencies": {
|
|
55
56
|
"@types/express": "^5.0.0",
|
|
56
57
|
"@types/jest": "^30.0.0",
|
|
57
|
-
"@types/node": "^
|
|
58
|
+
"@types/node": "^22.20.5",
|
|
58
59
|
"@typescript-eslint/eslint-plugin": "^8.68.0",
|
|
59
60
|
"@typescript-eslint/parser": "^8.68.0",
|
|
60
|
-
"@visa-cli/tools": "workspace:*",
|
|
61
61
|
"@visa/crypto": "workspace:*",
|
|
62
62
|
"@visa/financial-core": "workspace:*",
|
|
63
63
|
"@visa/identity": "workspace:*",
|
|
64
|
-
"@visa/
|
|
64
|
+
"@visa/contracts": "workspace:*",
|
|
65
65
|
"@visa/observability": "workspace:*",
|
|
66
66
|
"@visa/shared": "workspace:*",
|
|
67
67
|
"@visa/wallet": "workspace:*",
|
package/server.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.visa-crypto-labs/visa-cli",
|
|
4
|
-
"version": "5.0.0-rc.
|
|
4
|
+
"version": "5.0.0-rc.409",
|
|
5
5
|
"title": "Visa CLI",
|
|
6
6
|
"description": "Pair a human-approved agent identity, configure payment capabilities separately, and discover and pay x402 services from your AI coding assistant.",
|
|
7
7
|
"websiteUrl": "https://github.com/Visa-Crypto-Labs/Visa-mono/tree/main/packages/cli#readme",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
{
|
|
10
10
|
"registryType": "npm",
|
|
11
11
|
"identifier": "@visa/cli",
|
|
12
|
-
"version": "5.0.0-rc.
|
|
12
|
+
"version": "5.0.0-rc.409",
|
|
13
13
|
"transport": {
|
|
14
14
|
"type": "stdio"
|
|
15
15
|
},
|