@visa/cli 5.0.0-rc.364 → 5.0.0-rc.366
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 +1 -1
- package/dist/cli.js +248 -248
- package/dist/mcp-server/index.js +203 -203
- package/dist/merchant-ucp-mcp/index.js +6 -6
- package/dist/skills/pair-visa-agent/RUNTIMES.md +22 -28
- package/dist/skills/pair-visa-agent/SKILL.md +56 -73
- package/dist/skills/pair-visa-agent/scripts/__tests__/setup.test.mjs +62 -43
- package/dist/skills/pair-visa-agent/scripts/setup.mjs +11 -11
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- 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-runtime-signer.exe +0 -0
- package/package.json +1 -5
- package/server.json +2 -2
- package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
- package/native/build-win.bat +0 -42
- package/native/visa-keychain-win.cpp +0 -481
- package/native/visa-keychain.m +0 -367
|
@@ -21,27 +21,23 @@ Markdown skill, CLI, and MCP protocol instead.
|
|
|
21
21
|
|
|
22
22
|
## One ceremony, two surfaces
|
|
23
23
|
|
|
24
|
-
Both supported surfaces
|
|
25
|
-
|
|
24
|
+
Both supported surfaces run the same protected enrollment and share the same local
|
|
25
|
+
pending record:
|
|
26
26
|
|
|
27
|
-
1. Visa MCP server: `
|
|
28
|
-
|
|
29
|
-
2.
|
|
30
|
-
`visa setup status --format json`.
|
|
27
|
+
1. Visa MCP server: `agent_enroll`, then `agent_enroll` with `wait: true` once the
|
|
28
|
+
owner has the page open.
|
|
29
|
+
2. Terminal: `visa connect` (add `--format json` for machine output).
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
Each returns one link and a short code for the human. The browser receives only that
|
|
32
|
+
link. The agent's keys are generated on this device and never leave it, and the
|
|
33
|
+
approval page certifies only the keys this device named in the link. Calling it again
|
|
34
|
+
resumes the same pending enrollment after an interruption.
|
|
36
35
|
|
|
37
36
|
Use `agentId` as the stable identity. `identityKeyJkt` identifies the currently bound
|
|
38
|
-
Ed25519 public key and can change after key rotation.
|
|
39
|
-
operation are configured; email, `.visa` names, and tap bindings remain separate
|
|
40
|
-
follow-up configuration.
|
|
37
|
+
Ed25519 public key and can change after key rotation.
|
|
41
38
|
|
|
42
|
-
The older
|
|
43
|
-
|
|
44
|
-
approval per rail. Do not use them to connect a new agent.
|
|
39
|
+
The older plugin pairing tools and MCP enrollment tools are gone: they drove CLI
|
|
40
|
+
commands that no longer exist. `agent_enroll` (MCP) and `visa connect` are the one door.
|
|
45
41
|
|
|
46
42
|
## Install and mount
|
|
47
43
|
|
|
@@ -98,22 +94,20 @@ For example, with `HOME=/opt/data/home` and no `VISA_CLI_HOME`, identity lives
|
|
|
98
94
|
under `/opt/data/home/.visa-cli` and checkout contact under
|
|
99
95
|
`/opt/data/home/.visa-mcp`. A profile at `/opt/data/.visa-mcp` belongs to a
|
|
100
96
|
different state namespace. Do not scan, copy, or auto-adopt it: contact files
|
|
101
|
-
contain owner PII.
|
|
102
|
-
|
|
103
|
-
`visa agent preflight --format json` reports the non-secret path and source it
|
|
104
|
-
checked.
|
|
97
|
+
contain owner PII. The host passes checkout details to `purchase_create` itself;
|
|
98
|
+
no Visa tool writes the profile.
|
|
105
99
|
|
|
106
100
|
## Runtime setup map
|
|
107
101
|
|
|
108
|
-
| Runtime | Setup surface
|
|
109
|
-
| ------------- |
|
|
110
|
-
| OpenClaw | Install `@visa/cli@rc` (`npm install -g @visa/cli@rc`) and run `visa
|
|
111
|
-
| Hermes | Run `visa
|
|
112
|
-
| Codex | Run `visa
|
|
113
|
-
| Other runtime | Put `visa` on `PATH
|
|
102
|
+
| Runtime | Setup surface |
|
|
103
|
+
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
104
|
+
| OpenClaw | Install `@visa/cli@rc` (`npm install -g @visa/cli@rc`) and run `visa connect openclaw`. It plants the skill under `~/.openclaw/skills/` and mounts the Visa MCP server into `~/.openclaw/openclaw.json`. (The `@visa/visa-cli-openclaw` plugin package is prerelease). |
|
|
105
|
+
| Hermes | Run `visa connect hermes`. It resolves the per-profile skills directory and refuses to guess when several profiles exist — set `HERMES_PROFILE` to choose one. `hermes claw migrate` can import an existing OpenClaw setup. |
|
|
106
|
+
| Codex | Run `visa connect codex`. It plants the skill under `~/.codex/skills/` and mounts the Visa MCP server into `~/.codex/config.toml` in the same step. |
|
|
107
|
+
| Other runtime | Put `visa` on `PATH` and mount the same MCP server by hand; `get_status` shows what is connected. |
|
|
114
108
|
|
|
115
|
-
The skill
|
|
116
|
-
|
|
109
|
+
The skill is planted by `visa connect <client>`, into that client's own skills
|
|
110
|
+
directory. Reload the runtime afterwards.
|
|
117
111
|
|
|
118
112
|
## Optional Hermes wrapper
|
|
119
113
|
|
|
@@ -6,7 +6,7 @@ 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.9.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
|
|
@@ -59,21 +59,28 @@ Running the same command again resumes an interrupted enrolment; `--restart` rep
|
|
|
59
59
|
unclaimed request with a fresh code. `--ceiling <usd>` and `--per-transaction <usd>`
|
|
60
60
|
propose limits the owner confirms on the approval page.
|
|
61
61
|
|
|
62
|
+
## The tools the Visa MCP server serves
|
|
63
|
+
|
|
64
|
+
The Visa MCP server serves exactly these tools: `get_status`, `agent_enroll`,
|
|
65
|
+
`discover`, `pay`, `history` and `feedback`, plus the card preview's
|
|
66
|
+
`purchase_create`, `purchase_retrieve` and `purchase_cancel` where the build lists
|
|
67
|
+
them. Call no other Visa tool name, whatever an older guide, a memory or another
|
|
68
|
+
agent says: every earlier setup, login, pairing, capability and wallet tool is gone.
|
|
69
|
+
Read the live tool list if in doubt.
|
|
70
|
+
|
|
62
71
|
## The older ceremonies were deleted — do not call them
|
|
63
72
|
|
|
64
|
-
`
|
|
65
|
-
`
|
|
66
|
-
`
|
|
67
|
-
|
|
68
|
-
`agent pair`, `agent create`, `agent verify`, `agent enroll-claim`,
|
|
69
|
-
`agent claim`, `agent pairing-resume`, `agent connect`, `agent handoff-claim`
|
|
70
|
-
and every `agent grant-*` verb.
|
|
73
|
+
The whole `visa setup` and `visa agent` command groups are gone too — including the
|
|
74
|
+
retired `agent pair`, `agent create`, `agent verify`, `agent enroll-claim`,
|
|
75
|
+
`agent claim`, `agent pairing-resume`, `agent connect`, `agent handoff-claim` and
|
|
76
|
+
every `agent grant-*` verb.
|
|
71
77
|
|
|
72
78
|
**`visa connect` is the LIVE door and is not on that list.** It is the
|
|
73
79
|
top-level command, not the retired `visa agent connect` subcommand: one is the
|
|
74
80
|
one door, the other is a ceremony that no longer exists.
|
|
75
81
|
|
|
76
|
-
|
|
82
|
+
A retired command, or an older build still answering a retired tool name, returns
|
|
83
|
+
one refusal:
|
|
77
84
|
|
|
78
85
|
```json
|
|
79
86
|
{
|
|
@@ -100,7 +107,7 @@ a new device-held Ed25519 identity key, and the wallet limits the owner approved
|
|
|
100
107
|
|
|
101
108
|
**Where the `agentId` comes from — read this before you quote one.** Do not invent it and
|
|
102
109
|
do not read it out of the command's terminal output, which you often cannot see. Read it
|
|
103
|
-
from `get_status`
|
|
110
|
+
from `get_status` once the enrolment finishes. Never substitute a
|
|
104
111
|
correlation id, a confirmation code, or an origin for an `agentId`.
|
|
105
112
|
|
|
106
113
|
**Re-running the command does not repair an agent that already exists** — it creates a
|
|
@@ -170,9 +177,9 @@ OpenClaw (`~/.openclaw/openclaw.json`):
|
|
|
170
177
|
|
|
171
178
|
Hermes (`~/.hermes/config.yaml`). **Hermes passes ONLY this `env:` map to the MCP
|
|
172
179
|
subprocess — it does NOT inherit the gateway environment.** Omitting a required variable
|
|
173
|
-
(an RC access code, the right `HOME`, `PATH`) makes the server exit on every start
|
|
174
|
-
|
|
175
|
-
|
|
180
|
+
(an RC access code, the right `HOME`, `PATH`) makes the server exit on every start, so no
|
|
181
|
+
Visa tool registers at all, while the terminal — which reads on-disk state with the
|
|
182
|
+
gateway's own environment — can still look healthy. Always set the map explicitly:
|
|
176
183
|
|
|
177
184
|
```yaml
|
|
178
185
|
mcp_servers:
|
|
@@ -205,26 +212,28 @@ existing OpenClaw installation. See `RUNTIMES.md` for the complete runtime map.
|
|
|
205
212
|
## Sign in first — the wallet is owner-bound
|
|
206
213
|
|
|
207
214
|
The USDC wallet is delegated out of the owner's own wallet, so this runtime needs a live
|
|
208
|
-
owner session.
|
|
215
|
+
owner session. The one door takes care of the order: `agent_enroll` (and `visa connect`)
|
|
216
|
+
signs the owner in first whenever the session is missing or has lapsed, and only then
|
|
217
|
+
starts the enrolment.
|
|
209
218
|
|
|
210
|
-
- [ ] Call `
|
|
211
|
-
|
|
212
|
-
|
|
219
|
+
- [ ] Call `agent_enroll` (MCP) or run `visa connect --format json` (CLI). When a sign-in
|
|
220
|
+
is owed, the result carries a sign-in `browserUrl` and a short 6-character
|
|
221
|
+
`confirmCode`.
|
|
213
222
|
- [ ] Relay **both** to the human in your reply — the bare URL on its own line, and the
|
|
214
223
|
code. You are very often not in a terminal they can see; the chat message is the
|
|
215
224
|
only place these values reach them.
|
|
216
225
|
- [ ] The human opens the link, signs in (Google or email), and **types the confirmation
|
|
217
226
|
code into the sign-in page** — into the browser, never back to you in chat.
|
|
218
|
-
- [ ]
|
|
219
|
-
|
|
227
|
+
- [ ] Call `agent_enroll` again once they have (the CLI command polls on its own). It
|
|
228
|
+
picks up the approved sign-in on this device and moves on to the enrolment link.
|
|
220
229
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
230
|
+
Signing in establishes the OWNER's session on this device. It creates no agent and grants
|
|
231
|
+
no spending authority — those come from the enrolment and the approval the owner gives in
|
|
232
|
+
their browser.
|
|
224
233
|
|
|
225
234
|
If something reports `{"code":"session_required"}` or "Not logged in", that is this
|
|
226
|
-
ordering rule, not a fault: run `
|
|
227
|
-
|
|
235
|
+
ordering rule, not a fault: call `agent_enroll` (or run `visa connect`), drive the sign-in
|
|
236
|
+
above, then continue.
|
|
228
237
|
|
|
229
238
|
The account that signs in is the owner the enrolment binds to, and the same account must
|
|
230
239
|
be signed in on the approval page. A different account there fails closed.
|
|
@@ -244,7 +253,7 @@ be signed in on the approval page. A different account there fails closed.
|
|
|
244
253
|
- [ ] **Tell them what they are approving**: this device, and the spending limits. One
|
|
245
254
|
approval covers all of it.
|
|
246
255
|
- [ ] **Confirm from a tool, not from their word.** Poll `get_status` until
|
|
247
|
-
`pairing.paired` is `true
|
|
256
|
+
`pairing.paired` is `true`; the same `get_status` says what is actually live.
|
|
248
257
|
|
|
249
258
|
### Relaying values, and what you must never accept
|
|
250
259
|
|
|
@@ -277,7 +286,7 @@ On success, report the agent and what is actually live:
|
|
|
277
286
|
|
|
278
287
|
> Your Visa agent is set up. <Report only the capabilities the live map confirms.>
|
|
279
288
|
|
|
280
|
-
Read the rails from `
|
|
289
|
+
Read the rails from `get_status`, never from what was requested.
|
|
281
290
|
|
|
282
291
|
## Interruption and resume
|
|
283
292
|
|
|
@@ -323,15 +332,12 @@ _second, separate_ agent. Do this instead:
|
|
|
323
332
|
from `get_status`. Start another enrolment only if they
|
|
324
333
|
explicitly want a second agent.
|
|
325
334
|
2. **Report status honestly — "set up" is several separate things.** Never imply the agent
|
|
326
|
-
can spend just because it exists. Read it live from
|
|
327
|
-
prose:
|
|
328
|
-
|
|
329
|
-
what is missing", and `agent_login` establishes or confirms the account session.
|
|
335
|
+
can spend just because it exists. Read it live from `get_status` rather than guessing
|
|
336
|
+
from prose: it reports pairing, account and version, and answers "can it spend, and
|
|
337
|
+
what is missing".
|
|
330
338
|
- **Identity** — bound to _this user's_ account, on _this device_.
|
|
331
339
|
- **Spending** — the limits the owner approved in the browser. You **cannot** self-grant
|
|
332
|
-
either rail
|
|
333
|
-
`WalletCredentialRequiredError` until the owner's delegation lands.
|
|
334
|
-
- **Mesh (`.visa` messaging)** — separate; `visa register <name>` joins it.
|
|
340
|
+
either rail or self-mint a wallet: the wallet arrives only by the owner's delegation.
|
|
335
341
|
- **Trusted (TAP)** — follows spend/provisioning; don't promise it before then.
|
|
336
342
|
3. **Scope everything to the user.** The identity is bound to the account they signed in
|
|
337
343
|
with; the wallet and limits are theirs. Speak in terms of "your agent / your account /
|
|
@@ -340,8 +346,8 @@ _second, separate_ agent. Do this instead:
|
|
|
340
346
|
## An existing agent is missing a capability
|
|
341
347
|
|
|
342
348
|
There is no rail-add ceremony any more: `visa agent grant-card` / `grant-wallet` /
|
|
343
|
-
`grant-activate` / `grant-claim`
|
|
344
|
-
|
|
349
|
+
`grant-activate` / `grant-claim` were deleted, and calling one returns
|
|
350
|
+
`legacy_door_removed`.
|
|
345
351
|
|
|
346
352
|
What to do instead:
|
|
347
353
|
|
|
@@ -362,17 +368,13 @@ yourself.
|
|
|
362
368
|
|
|
363
369
|
## Spending, once a rail is live
|
|
364
370
|
|
|
365
|
-
Wallet:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
`wallet_pay` / `wallet_directory_pay` (pay, policy-enforced), `wallet_history` /
|
|
370
|
-
`wallet_reconcile` (local ledger + resolve `reconciling` holds), `wallet_fund` (funding
|
|
371
|
-
address + faucet), and `wallet_export` (export key material — dangerous). All spending is
|
|
372
|
-
gated by the owner-approved local policy caps.
|
|
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
|
|
373
|
+
payments. The limits are the ones the owner approved; the owner changes them in the Visa
|
|
374
|
+
Console, never you. Funding is the owner's, in the Console.
|
|
373
375
|
|
|
374
376
|
Card: Visa-operated browser checkout is retired; another enrollment will not repair it.
|
|
375
|
-
|
|
377
|
+
The retired browser-checkout tools refuse with `card_browser_checkout_removed`.
|
|
376
378
|
On local builds that list `purchase_create`,
|
|
377
379
|
`purchase_retrieve` and `purchase_cancel`, the protected card preview uses the
|
|
378
380
|
host's checkout tools and the existing eligible card and agent identity. Follow
|
|
@@ -396,32 +398,14 @@ messaging authority. Do not attempt mesh registration or improvise another trans
|
|
|
396
398
|
|
|
397
399
|
## Optional agent mailbox (separate from setup)
|
|
398
400
|
|
|
399
|
-
Connecting an agent does not provision an email address or inbox
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
```
|
|
404
|
-
agent_mail_connect { "agentId": "<agentId>" }
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
There is no terminal command for this. It requires an
|
|
408
|
-
already-connected stable-agent identity on this runtime — it reads the local agent
|
|
409
|
-
record and proves the Ed25519 identity to the service. It issues the stable
|
|
410
|
-
agent mailbox if one does not exist, then stores an inbox-scoped credential in an
|
|
411
|
-
owner-only `0600` runtime file so this runtime can read that one inbox.
|
|
412
|
-
|
|
413
|
-
Be honest about scope. A mailbox grants an email address and the ability to read
|
|
414
|
-
that inbox — nothing more. It is **not** identity, a wallet, spend authority, a
|
|
415
|
-
card, or a `.visa` name, and it never authorizes a payment. Do not claim setup
|
|
416
|
-
set up mail. Once connected, the MCP `wallet_mail_status`, `wallet_mail_read`,
|
|
417
|
-
and `wallet_mail_await_otp` tools read the inbox (e.g. to await a sender-checked
|
|
418
|
-
one-time code); without the scoped credential those reads fail closed. Keep the
|
|
419
|
-
org-wide AgentMail key off the runtime — provisioning happens only through
|
|
420
|
-
`mail-connect` under operator control.
|
|
401
|
+
Connecting an agent does not provision an email address or inbox, and no Visa tool
|
|
402
|
+
connects or reads one. There is no terminal command for this either. Do not claim setup
|
|
403
|
+
set up mail, and do not improvise a mailbox. A mailbox would grant an email address and
|
|
404
|
+
nothing more: it never authorizes a payment.
|
|
421
405
|
|
|
422
406
|
## Optional checkout profile (separate from setup)
|
|
423
407
|
|
|
424
|
-
Do not collect a checkout profile to
|
|
408
|
+
Do not collect a checkout profile to revive Visa browser checkout: it
|
|
425
409
|
is retired. On local builds that list `purchase_create`, `purchase_retrieve` and
|
|
426
410
|
`purchase_cancel`, the protected card preview reuses this identity and verified
|
|
427
411
|
card. The host supplies checkout and billing details, owns browser submission,
|
|
@@ -433,12 +417,11 @@ chat, bypass eligibility, or retry an uncertain merchant submission. Existing
|
|
|
433
417
|
`~/.visa-mcp/contact.json` files use the format below; preserve them with mode `0600`.
|
|
434
418
|
Never infer or invent identity or address data.
|
|
435
419
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
owner save the profile again through `checkout_profile` in that runtime.
|
|
420
|
+
No Visa tool writes this profile; the host passes checkout and billing details to
|
|
421
|
+
`purchase_create` itself. Any shell that reads an existing profile must have the exact
|
|
422
|
+
same `HOME` and `VISA_CLI_HOME` as the MCP subprocess. A profile found under another root
|
|
423
|
+
is owner PII, not a migration candidate: never scan, copy, or auto-adopt it. If the roots
|
|
424
|
+
drifted, keep the root holding the paired identity.
|
|
442
425
|
|
|
443
426
|
```jsonc
|
|
444
427
|
{
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
2
|
-
import { mkdirSync, writeFileSync, rmSync } from 'node:fs'
|
|
3
|
-
import { join } from 'node:path'
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync, rmSync } from 'node:fs'
|
|
3
|
+
import { dirname, join } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
4
5
|
import { tmpdir } from 'node:os'
|
|
5
6
|
import { randomUUID } from 'node:crypto'
|
|
6
7
|
|
|
@@ -55,7 +56,7 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
55
56
|
|
|
56
57
|
it('accepts exact matching or newer RC versions', () => {
|
|
57
58
|
expect(isSemverCompatible('4.1.0-rc.159', '4.1.0-rc.159')).toBe(true)
|
|
58
|
-
expect(isSemverCompatible('
|
|
59
|
+
expect(isSemverCompatible('5.0.0-rc.360', '4.1.0-rc.159')).toBe(true)
|
|
59
60
|
expect(isSemverCompatible('4.1.0-rc.200', '4.1.0-rc.159')).toBe(true)
|
|
60
61
|
})
|
|
61
62
|
|
|
@@ -66,10 +67,18 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
66
67
|
})
|
|
67
68
|
})
|
|
68
69
|
|
|
70
|
+
describe('default floor', () => {
|
|
71
|
+
it('refuses a 4.x CLI that still served the retired pairing tools', () => {
|
|
72
|
+
expect(isSemverCompatible('4.1.0-rc.166')).toBe(false)
|
|
73
|
+
expect(isSemverCompatible('5.0.0-rc.334')).toBe(false)
|
|
74
|
+
expect(isSemverCompatible('5.0.0-rc.335')).toBe(true)
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
69
78
|
describe('findCliBinary', () => {
|
|
70
79
|
it('returns visa when visa --version succeeds', () => {
|
|
71
80
|
const mockExec = vi.fn((cmd) => {
|
|
72
|
-
if (cmd === 'visa') return 'visa
|
|
81
|
+
if (cmd === 'visa') return 'visa 5.0.0-rc.360'
|
|
73
82
|
throw new Error('command not found')
|
|
74
83
|
})
|
|
75
84
|
expect(findCliBinary(mockExec)).toBe('visa')
|
|
@@ -77,7 +86,7 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
77
86
|
|
|
78
87
|
it('returns visa-cli when only visa-cli succeeds', () => {
|
|
79
88
|
const mockExec = vi.fn((cmd) => {
|
|
80
|
-
if (cmd === 'visa-cli') return 'visa-cli
|
|
89
|
+
if (cmd === 'visa-cli') return 'visa-cli 5.0.0-rc.360'
|
|
81
90
|
throw new Error('command not found')
|
|
82
91
|
})
|
|
83
92
|
expect(findCliBinary(mockExec)).toBe('visa-cli')
|
|
@@ -96,21 +105,21 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
96
105
|
const mockExec = vi.fn((cmd, args) => {
|
|
97
106
|
if (args.includes('capabilities')) {
|
|
98
107
|
return JSON.stringify({
|
|
99
|
-
data: { build: { version: '
|
|
108
|
+
data: { build: { version: '5.0.0-rc.360' } },
|
|
100
109
|
})
|
|
101
110
|
}
|
|
102
|
-
return '
|
|
111
|
+
return '5.0.0-rc.360'
|
|
103
112
|
})
|
|
104
|
-
expect(detectCliVersion('visa', mockExec)).toBe('
|
|
113
|
+
expect(detectCliVersion('visa', mockExec)).toBe('5.0.0-rc.360')
|
|
105
114
|
})
|
|
106
115
|
|
|
107
116
|
it('falls back to --version when capabilities fails', () => {
|
|
108
117
|
const mockExec = vi.fn((cmd, args) => {
|
|
109
118
|
if (args.includes('capabilities')) throw new Error('unknown command')
|
|
110
|
-
if (args.includes('--version')) return '
|
|
119
|
+
if (args.includes('--version')) return 'v5.0.0-rc.360\n'
|
|
111
120
|
throw new Error('unknown')
|
|
112
121
|
})
|
|
113
|
-
expect(detectCliVersion('visa', mockExec)).toBe('
|
|
122
|
+
expect(detectCliVersion('visa', mockExec)).toBe('5.0.0-rc.360')
|
|
114
123
|
})
|
|
115
124
|
})
|
|
116
125
|
|
|
@@ -120,7 +129,7 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
120
129
|
writeFileSync(
|
|
121
130
|
bundlePath,
|
|
122
131
|
`
|
|
123
|
-
const tools = ['
|
|
132
|
+
const tools = ['get_status', 'agent_enroll', 'discover', 'pay', 'history', 'feedback'];
|
|
124
133
|
`
|
|
125
134
|
)
|
|
126
135
|
const probe = probeMcpTools(bundlePath, REQUIRED_MCP_TOOLS)
|
|
@@ -128,7 +137,7 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
128
137
|
expect(probe.missingTools).toEqual([])
|
|
129
138
|
})
|
|
130
139
|
|
|
131
|
-
it('returns missing tools when bundle
|
|
140
|
+
it('returns missing tools when the bundle predates the six served tools (e.g. 4.0.1)', () => {
|
|
132
141
|
const bundlePath = join(tempDir, 'index.js')
|
|
133
142
|
writeFileSync(
|
|
134
143
|
bundlePath,
|
|
@@ -138,11 +147,7 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
138
147
|
)
|
|
139
148
|
const probe = probeMcpTools(bundlePath, REQUIRED_MCP_TOOLS)
|
|
140
149
|
expect(probe.ok).toBe(false)
|
|
141
|
-
expect(probe.missingTools).toEqual([
|
|
142
|
-
'agent_handoff_claim',
|
|
143
|
-
'agent_connect',
|
|
144
|
-
'agent_connect_poll',
|
|
145
|
-
])
|
|
150
|
+
expect(probe.missingTools).toEqual(['get_status', 'agent_enroll'])
|
|
146
151
|
})
|
|
147
152
|
|
|
148
153
|
it('returns false when bundle file is missing', () => {
|
|
@@ -151,13 +156,27 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
151
156
|
expect(probe.missingTools).toEqual([...REQUIRED_MCP_TOOLS])
|
|
152
157
|
})
|
|
153
158
|
|
|
154
|
-
it('does not treat
|
|
159
|
+
it('does not treat a longer tool name as a match for agent_enroll', () => {
|
|
155
160
|
const bundlePath = join(tempDir, 'index.js')
|
|
156
|
-
writeFileSync(bundlePath, `const tools = ['
|
|
161
|
+
writeFileSync(bundlePath, `const tools = ['get_status', 'agent_enroll_claim'];`)
|
|
157
162
|
const probe = probeMcpTools(bundlePath, REQUIRED_MCP_TOOLS)
|
|
158
163
|
expect(probe.ok).toBe(false)
|
|
159
|
-
expect(probe.missingTools).
|
|
160
|
-
|
|
164
|
+
expect(probe.missingTools).toEqual(['agent_enroll'])
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('requires only served tools: every required tool is one of the six', () => {
|
|
168
|
+
const served = readFileSync(
|
|
169
|
+
join(
|
|
170
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
171
|
+
'../../../../../cli/src/mcp-server/six-tools.ts'
|
|
172
|
+
),
|
|
173
|
+
'utf8'
|
|
174
|
+
)
|
|
175
|
+
const block = served.match(/SIX_TOOL_NAMES = \[([^\]]*)\]/)
|
|
176
|
+
expect(block).not.toBeNull()
|
|
177
|
+
const six = [...block[1].matchAll(/'([a-z_]+)'/g)].map((m) => m[1])
|
|
178
|
+
expect(six).toHaveLength(6)
|
|
179
|
+
for (const tool of REQUIRED_MCP_TOOLS) expect(six).toContain(tool)
|
|
161
180
|
})
|
|
162
181
|
})
|
|
163
182
|
|
|
@@ -189,18 +208,18 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
189
208
|
expect(result.version).toBe('4.0.1')
|
|
190
209
|
})
|
|
191
210
|
|
|
192
|
-
it('reports ready when version is >=
|
|
211
|
+
it('reports ready when version is >= 5.0.0-rc.335 and required tools exist', () => {
|
|
193
212
|
const bundleDir = join(tempDir, '@visa', 'cli', 'dist', 'mcp-server')
|
|
194
213
|
mkdirSync(bundleDir, { recursive: true })
|
|
195
214
|
writeFileSync(
|
|
196
215
|
join(bundleDir, 'index.js'),
|
|
197
|
-
`const tools = ['
|
|
216
|
+
`const tools = ['get_status', 'agent_enroll', 'discover', 'pay', 'history', 'feedback'];`
|
|
198
217
|
)
|
|
199
218
|
|
|
200
219
|
const mockExec = vi.fn((cmd, args) => {
|
|
201
|
-
if (args.includes('--version')) return '
|
|
220
|
+
if (args.includes('--version')) return '5.0.0-rc.360'
|
|
202
221
|
if (args.includes('capabilities')) {
|
|
203
|
-
return JSON.stringify({ data: { build: { version: '
|
|
222
|
+
return JSON.stringify({ data: { build: { version: '5.0.0-rc.360' } } })
|
|
204
223
|
}
|
|
205
224
|
throw new Error('unknown')
|
|
206
225
|
})
|
|
@@ -208,15 +227,15 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
208
227
|
|
|
209
228
|
const result = verifyCliReadiness(mockExec, mockNpmRoot)
|
|
210
229
|
expect(result.ready).toBe(true)
|
|
211
|
-
expect(result.version).toBe('
|
|
230
|
+
expect(result.version).toBe('5.0.0-rc.360')
|
|
212
231
|
expect(result.missingTools).toEqual([])
|
|
213
232
|
})
|
|
214
233
|
|
|
215
234
|
it('reports stale_or_incomplete when version is compatible but the MCP bundle is missing', () => {
|
|
216
235
|
const mockExec = vi.fn((cmd, args) => {
|
|
217
|
-
if (args.includes('--version')) return '
|
|
236
|
+
if (args.includes('--version')) return '5.0.0-rc.360'
|
|
218
237
|
if (args.includes('capabilities')) {
|
|
219
|
-
return JSON.stringify({ data: { build: { version: '
|
|
238
|
+
return JSON.stringify({ data: { build: { version: '5.0.0-rc.360' } } })
|
|
220
239
|
}
|
|
221
240
|
throw new Error('unknown')
|
|
222
241
|
})
|
|
@@ -225,7 +244,7 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
225
244
|
const result = verifyCliReadiness(mockExec, mockNpmRoot, () => null)
|
|
226
245
|
expect(result.ready).toBe(false)
|
|
227
246
|
expect(result.reason).toBe('stale_or_incomplete')
|
|
228
|
-
expect(result.version).toBe('
|
|
247
|
+
expect(result.version).toBe('5.0.0-rc.360')
|
|
229
248
|
expect(result.missingTools).toEqual([...REQUIRED_MCP_TOOLS])
|
|
230
249
|
})
|
|
231
250
|
})
|
|
@@ -241,15 +260,15 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
241
260
|
mkdirSync(bundleDir, { recursive: true })
|
|
242
261
|
writeFileSync(
|
|
243
262
|
join(bundleDir, 'index.js'),
|
|
244
|
-
`const tools = ['
|
|
263
|
+
`const tools = ['get_status', 'agent_enroll', 'discover', 'pay', 'history', 'feedback'];`
|
|
245
264
|
)
|
|
246
265
|
return ''
|
|
247
266
|
}
|
|
248
267
|
if (cmd === 'visa') {
|
|
249
268
|
if (!installed) throw new Error('command not found')
|
|
250
|
-
if (args.includes('--version')) return '
|
|
269
|
+
if (args.includes('--version')) return '5.0.0-rc.360'
|
|
251
270
|
if (args.includes('capabilities')) {
|
|
252
|
-
return JSON.stringify({ data: { build: { version: '
|
|
271
|
+
return JSON.stringify({ data: { build: { version: '5.0.0-rc.360' } } })
|
|
253
272
|
}
|
|
254
273
|
}
|
|
255
274
|
throw new Error(`unknown command ${cmd}`)
|
|
@@ -269,7 +288,7 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
269
288
|
expect.any(Object)
|
|
270
289
|
)
|
|
271
290
|
expect(result.status).toBe('installed')
|
|
272
|
-
expect(result.version).toBe('
|
|
291
|
+
expect(result.version).toBe('5.0.0-rc.360')
|
|
273
292
|
})
|
|
274
293
|
|
|
275
294
|
it('scenario 2: stale CLI installed (4.0.1) -> upgrades to @visa/cli@rc and succeeds', async () => {
|
|
@@ -283,12 +302,12 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
283
302
|
upgraded = true
|
|
284
303
|
writeFileSync(
|
|
285
304
|
join(bundleDir, 'index.js'),
|
|
286
|
-
`const tools = ['
|
|
305
|
+
`const tools = ['get_status', 'agent_enroll', 'discover', 'pay', 'history', 'feedback'];`
|
|
287
306
|
)
|
|
288
307
|
return ''
|
|
289
308
|
}
|
|
290
309
|
if (cmd === 'visa') {
|
|
291
|
-
const version = upgraded ? '
|
|
310
|
+
const version = upgraded ? '5.0.0-rc.360' : '4.0.1'
|
|
292
311
|
if (args.includes('--version')) return version
|
|
293
312
|
if (args.includes('capabilities')) {
|
|
294
313
|
return JSON.stringify({ data: { build: { version } } })
|
|
@@ -311,7 +330,7 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
311
330
|
expect.any(Object)
|
|
312
331
|
)
|
|
313
332
|
expect(result.status).toBe('upgraded')
|
|
314
|
-
expect(result.version).toBe('
|
|
333
|
+
expect(result.version).toBe('5.0.0-rc.360')
|
|
315
334
|
})
|
|
316
335
|
|
|
317
336
|
it('scenario 3: current CLI installed -> idempotent no-op', async () => {
|
|
@@ -319,14 +338,14 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
319
338
|
mkdirSync(bundleDir, { recursive: true })
|
|
320
339
|
writeFileSync(
|
|
321
340
|
join(bundleDir, 'index.js'),
|
|
322
|
-
`const tools = ['
|
|
341
|
+
`const tools = ['get_status', 'agent_enroll', 'discover', 'pay', 'history', 'feedback'];`
|
|
323
342
|
)
|
|
324
343
|
|
|
325
344
|
const mockExec = vi.fn((cmd, args) => {
|
|
326
345
|
if (cmd === 'visa') {
|
|
327
|
-
if (args.includes('--version')) return '
|
|
346
|
+
if (args.includes('--version')) return '5.0.0-rc.360'
|
|
328
347
|
if (args.includes('capabilities')) {
|
|
329
|
-
return JSON.stringify({ data: { build: { version: '
|
|
348
|
+
return JSON.stringify({ data: { build: { version: '5.0.0-rc.360' } } })
|
|
330
349
|
}
|
|
331
350
|
}
|
|
332
351
|
throw new Error(`unexpected execution ${cmd}`)
|
|
@@ -342,7 +361,7 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
342
361
|
|
|
343
362
|
expect(mockExec).not.toHaveBeenCalledWith('npm', expect.anything(), expect.anything())
|
|
344
363
|
expect(result.status).toBe('already_ready')
|
|
345
|
-
expect(result.version).toBe('
|
|
364
|
+
expect(result.version).toBe('5.0.0-rc.360')
|
|
346
365
|
})
|
|
347
366
|
|
|
348
367
|
it('scenario 4: install failure reports one explicit upgrade action and exits 1', async () => {
|
|
@@ -375,9 +394,9 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
375
394
|
const mockExec = vi.fn((cmd, args) => {
|
|
376
395
|
if (cmd === 'npm') return ''
|
|
377
396
|
if (cmd === 'visa') {
|
|
378
|
-
if (args.includes('--version')) return '
|
|
397
|
+
if (args.includes('--version')) return '5.0.0-rc.360'
|
|
379
398
|
if (args.includes('capabilities')) {
|
|
380
|
-
return JSON.stringify({ data: { build: { version: '
|
|
399
|
+
return JSON.stringify({ data: { build: { version: '5.0.0-rc.360' } } })
|
|
381
400
|
}
|
|
382
401
|
}
|
|
383
402
|
throw new Error(`unknown command ${cmd}`)
|
|
@@ -395,7 +414,7 @@ describe('setup.mjs portable provisioner', () => {
|
|
|
395
414
|
|
|
396
415
|
expect(errorSpy).toHaveBeenCalledWith(
|
|
397
416
|
expect.stringContaining(
|
|
398
|
-
'Readiness check failed: installed CLI
|
|
417
|
+
'Readiness check failed: installed CLI v5.0.0-rc.360 is missing required tools'
|
|
399
418
|
)
|
|
400
419
|
)
|
|
401
420
|
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('npm install -g @visa/cli@rc'))
|