@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.
@@ -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`, then `agent_enroll` with `wait: true` once the
28
- owner has the page open.
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 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.
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. `--ceiling <usd>` and `--per-transaction <usd>`
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. Enrolling again mints a NEW agent — it does not upgrade
362
- this one, and doing it silently leaves the owner with two agents and one funded
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 or timeout means **nothing was signed**: report it and stop; do not retry
366
- with a different command or a reconstructed URL. Never raise a human-approved limit
367
- yourself.
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` finds what to buy (a query, or a listing id to read one without
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
- The retired browser-checkout tools refuse with `card_browser_checkout_removed`.
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
- On a hosted runtime, **every payment requires the owner's browser
390
- approval** until the protected no-tap executor lands. Enrollment, a session, or a spending
391
- grant does not approve a later hosted payment. Relay the hosted approval URL and wait for
392
- the owner's decision before reporting success or retrying.
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(), `setup-test-${randomUUID()}`)
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', 'discover', 'pay', 'history', 'feedback'];
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.408",
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": "tsc --noEmit && node esbuild.config.js",
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:integration && npm run test:e2e",
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": "^26.1.0",
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/money": "workspace:*",
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.408",
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.408",
12
+ "version": "5.0.0-rc.409",
13
13
  "transport": {
14
14
  "type": "stdio"
15
15
  },