@visa/cli 4.1.0-rc.229 → 4.1.0-rc.230

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.
@@ -1,48 +1,328 @@
1
1
  #!/usr/bin/env node
2
2
  // Portable provisioner for the pair-visa-agent skill.
3
3
  //
4
- // Ensures the `visa` CLI is installed so ANY Agent Skills runtime can pair —
5
- // not just OpenClaw (whose `metadata.openclaw.install` auto-runs). Hermes,
6
- // Claude Code, and any other agentskills.io-compatible runtime run this bundled
7
- // script per the standard's `scripts/` execution stage.
4
+ // Ensures the `visa` CLI is installed and provides the required v4 MCP tools
5
+ // (`agent_handoff_claim`, `agent_connect`, `agent_connect_poll`) so ANY Agent
6
+ // Skills runtime can pair not just OpenClaw (whose `metadata.openclaw.install`
7
+ // auto-runs). Hermes, Claude Code, and any other agentskills.io-compatible
8
+ // runtime run this bundled script per the standard's `scripts/` execution stage.
8
9
  //
9
- // Safe to run repeatedly: it no-ops when `visa` already resolves. Pinned to @rc
10
- // because the v4 agent surface is prerelease (the @latest tag predates the
11
- // `visa agent` commands); drop the tag once 4.1.0 is promoted to latest.
10
+ // Safe to run repeatedly:
11
+ // - Idempotent no-op when a compatible version with required tools is installed.
12
+ // - Upgrades when a stale or incomplete binary (e.g. 4.0.1 without handoff tools) is found.
13
+ // - Installs `@visa/cli@rc` when no binary exists.
12
14
 
13
- import { execSync } from 'node:child_process'
15
+ import { execFileSync } from 'node:child_process'
16
+ import { existsSync, readFileSync } from 'node:fs'
17
+ import { dirname, join } from 'node:path'
18
+ import { createRequire } from 'node:module'
19
+ import { pathToFileURL } from 'node:url'
20
+
21
+ export const DEFAULT_RELEASE_CHANNEL = 'rc'
22
+ export const MIN_COMPATIBLE_VERSION = '4.1.0-rc.159'
23
+ export const REQUIRED_MCP_TOOLS = Object.freeze([
24
+ 'agent_handoff_claim',
25
+ 'agent_connect',
26
+ 'agent_connect_poll',
27
+ ])
28
+
29
+ const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/
30
+
31
+ export function parseSemver(v) {
32
+ if (typeof v !== 'string') return null
33
+ const cleaned = v.trim().replace(/^v/, '')
34
+ const m = cleaned.match(SEMVER_RE)
35
+ if (!m) return null
36
+ return {
37
+ major: Number(m[1]),
38
+ minor: Number(m[2]),
39
+ patch: Number(m[3]),
40
+ pre: m[4] ?? null,
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Returns true if `version` satisfies >= `minVersion`.
46
+ * Rules (semver 2.0.0):
47
+ * - Release (no prerelease) outranks any prerelease of the same major.minor.patch.
48
+ * - For identical major.minor.patch with prerelease `rc.N`, compares N numerically.
49
+ */
50
+ export function isSemverCompatible(version, minVersion = MIN_COMPATIBLE_VERSION) {
51
+ const cur = parseSemver(version)
52
+ const min = parseSemver(minVersion)
53
+ if (!cur || !min) return false
54
+
55
+ if (cur.major !== min.major) return cur.major > min.major
56
+ if (cur.minor !== min.minor) return cur.minor > min.minor
57
+ if (cur.patch !== min.patch) return cur.patch > min.patch
58
+
59
+ // Both main versions are equal.
60
+ // A release version (no prerelease) outranks any prerelease of the same main version.
61
+ if (!cur.pre && min.pre) return true
62
+ if (cur.pre && !min.pre) return false
63
+ if (!cur.pre && !min.pre) return true
64
+
65
+ // Both have prereleases. Compare prerelease identifiers.
66
+ const curParts = cur.pre.split('.')
67
+ const minParts = min.pre.split('.')
68
+ const len = Math.max(curParts.length, minParts.length)
69
+
70
+ for (let i = 0; i < len; i++) {
71
+ if (i >= curParts.length) return false
72
+ if (i >= minParts.length) return true
73
+ const a = curParts[i]
74
+ const b = minParts[i]
75
+ const aNum = /^\d+$/.test(a)
76
+ const bNum = /^\d+$/.test(b)
77
+ if (aNum && bNum) {
78
+ const numA = Number(a)
79
+ const numB = Number(b)
80
+ if (numA !== numB) return numA > numB
81
+ } else if (aNum && !bNum) {
82
+ return false
83
+ } else if (!aNum && bNum) {
84
+ return true
85
+ } else if (a !== b) {
86
+ return a > b
87
+ }
88
+ }
89
+ return true
90
+ }
91
+
92
+ export function findCliBinary(execFn = execFileSync) {
93
+ for (const bin of ['visa', 'visa-cli']) {
94
+ try {
95
+ execFn(bin, ['--version'], { stdio: 'ignore', timeout: 5000 })
96
+ return bin
97
+ } catch {
98
+ // not resolvable or failed
99
+ }
100
+ }
101
+ return null
102
+ }
103
+
104
+ export function detectCliVersion(bin, execFn = execFileSync) {
105
+ if (!bin) return null
106
+
107
+ try {
108
+ const rawCap = execFn(bin, ['capabilities', '--format', 'json'], {
109
+ encoding: 'utf-8',
110
+ timeout: 5000,
111
+ })
112
+ const cap = JSON.parse(rawCap)
113
+ const version = cap?.data?.build?.version ?? cap?.build?.version
114
+ if (typeof version === 'string' && version.trim()) {
115
+ return version.trim()
116
+ }
117
+ } catch {
118
+ // fallback to --version
119
+ }
14
120
 
15
- function resolves(cmd) {
16
121
  try {
17
- execSync(`${cmd} --version`, { stdio: 'ignore' })
18
- return true
122
+ const raw = execFn(bin, ['--version'], {
123
+ encoding: 'utf-8',
124
+ timeout: 5000,
125
+ })
126
+ const cleaned = raw.trim().replace(/^v/, '')
127
+ if (cleaned) return cleaned
19
128
  } catch {
20
- return false
129
+ // unable to detect version
21
130
  }
131
+
132
+ return null
22
133
  }
23
134
 
24
- if (resolves('visa') || resolves('visa-cli')) {
25
- console.log('✓ visa CLI already installed — nothing to do. Run the pairing flow in SKILL.md.')
26
- process.exit(0)
135
+ function defaultLocalMcpBundlePath() {
136
+ try {
137
+ const req = createRequire(import.meta.url)
138
+ const pkgJson = req.resolve('@visa/cli/package.json')
139
+ return join(dirname(pkgJson), 'dist', 'mcp-server', 'index.js')
140
+ } catch {
141
+ return null
142
+ }
27
143
  }
28
144
 
29
- console.log('Installing @visa/cli@rc (the v4 agent surface is prerelease)…')
30
- try {
31
- execSync('npm install -g @visa/cli@rc', { stdio: 'inherit' })
32
- } catch {
33
- console.error(
34
- 'Global install failed. Try `npm install -g @visa/cli@rc` manually (may need sudo, or set a\n' +
35
- 'user-writable npm prefix: `npm config set prefix ~/.npm-global` and add its `bin` to PATH).'
36
- )
37
- process.exit(1)
145
+ export function resolveMcpBundlePath(
146
+ npmRootFn = () => execFileSync('npm', ['root', '-g'], { encoding: 'utf-8' }),
147
+ localBundleFn = defaultLocalMcpBundlePath
148
+ ) {
149
+ const candidates = []
150
+
151
+ try {
152
+ const globalRoot = npmRootFn().trim()
153
+ if (globalRoot) {
154
+ candidates.push(join(globalRoot, '@visa', 'cli', 'dist', 'mcp-server', 'index.js'))
155
+ }
156
+ } catch {
157
+ // npm root -g failed
158
+ }
159
+
160
+ const local = localBundleFn()
161
+ if (local) candidates.push(local)
162
+
163
+ for (const candidate of candidates) {
164
+ if (existsSync(candidate)) return candidate
165
+ }
166
+ return null
167
+ }
168
+
169
+ export function probeMcpTools(mcpPath, requiredTools = REQUIRED_MCP_TOOLS) {
170
+ if (!mcpPath || !existsSync(mcpPath)) {
171
+ return { ok: false, missingTools: [...requiredTools] }
172
+ }
173
+
174
+ try {
175
+ const content = readFileSync(mcpPath, 'utf-8')
176
+ const missing = requiredTools.filter((tool) => {
177
+ const exact = new RegExp(`(?:^|[^A-Za-z0-9_])${tool}(?:[^A-Za-z0-9_]|$)`)
178
+ return !exact.test(content)
179
+ })
180
+ return {
181
+ ok: missing.length === 0,
182
+ missingTools: missing,
183
+ }
184
+ } catch {
185
+ return { ok: false, missingTools: [...requiredTools] }
186
+ }
38
187
  }
39
188
 
40
- if (!resolves('visa') && !resolves('visa-cli')) {
41
- console.error(
42
- 'Installed, but `visa` is not on PATH. Ensure your npm global bin dir is on PATH\n' +
43
- '(`npm bin -g` shows it), then re-run this script.'
189
+ export function verifyCliReadiness(
190
+ execFn = execFileSync,
191
+ npmRootFn = () => execFileSync('npm', ['root', '-g'], { encoding: 'utf-8' }),
192
+ localBundleFn
193
+ ) {
194
+ const bin = findCliBinary(execFn)
195
+ if (!bin) {
196
+ return {
197
+ ready: false,
198
+ reason: 'not_installed',
199
+ bin: null,
200
+ version: null,
201
+ missingTools: [...REQUIRED_MCP_TOOLS],
202
+ }
203
+ }
204
+
205
+ const version = detectCliVersion(bin, execFn)
206
+ const isVersionOk = Boolean(version && isSemverCompatible(version, MIN_COMPATIBLE_VERSION))
207
+
208
+ const mcpPath =
209
+ localBundleFn === undefined
210
+ ? resolveMcpBundlePath(npmRootFn)
211
+ : resolveMcpBundlePath(npmRootFn, localBundleFn)
212
+ const toolProbe = mcpPath
213
+ ? probeMcpTools(mcpPath, REQUIRED_MCP_TOOLS)
214
+ : { ok: false, missingTools: [...REQUIRED_MCP_TOOLS] }
215
+
216
+ if (isVersionOk && toolProbe.ok) {
217
+ return {
218
+ ready: true,
219
+ bin,
220
+ version: version || 'unknown',
221
+ missingTools: [],
222
+ }
223
+ }
224
+
225
+ return {
226
+ ready: false,
227
+ reason: 'stale_or_incomplete',
228
+ bin,
229
+ version,
230
+ missingTools:
231
+ toolProbe.missingTools.length > 0
232
+ ? toolProbe.missingTools
233
+ : isVersionOk
234
+ ? []
235
+ : [...REQUIRED_MCP_TOOLS],
236
+ }
237
+ }
238
+
239
+ export function performInstallOrUpgrade(pkgName, execFn = execFileSync) {
240
+ execFn('npm', ['install', '-g', pkgName], {
241
+ stdio: 'inherit',
242
+ timeout: 120_000,
243
+ })
244
+ }
245
+
246
+ export async function runSetup(env = process.env, execs = {}) {
247
+ const execFn = execs.execFileSync ?? execFileSync
248
+ const npmRootFn = execs.npmRootFn ?? (() => execFn('npm', ['root', '-g'], { encoding: 'utf-8' }))
249
+
250
+ const channel = env.VISA_RELEASE_CHANNEL || DEFAULT_RELEASE_CHANNEL
251
+ const pkgName = `@visa/cli@${channel}`
252
+
253
+ const initial = verifyCliReadiness(execFn, npmRootFn)
254
+
255
+ if (initial.ready) {
256
+ console.log(
257
+ `✓ visa CLI v${initial.version} already installed and verified (${REQUIRED_MCP_TOOLS.join(', ')} present) — nothing to do. Run the pairing flow in SKILL.md.`
258
+ )
259
+ return {
260
+ status: 'already_ready',
261
+ version: initial.version,
262
+ bin: initial.bin,
263
+ }
264
+ }
265
+
266
+ if (initial.reason === 'stale_or_incomplete') {
267
+ const missingStr =
268
+ initial.missingTools.length > 0 ? initial.missingTools.join(', ') : 'v4 tool generation'
269
+ console.log(
270
+ `Detected stale or incomplete CLI (v${initial.version || 'unknown'} lacks required tools: ${missingStr}). Upgrading to ${pkgName}…`
271
+ )
272
+ } else {
273
+ console.log(`Installing ${pkgName} (the v4 agent surface is prerelease)…`)
274
+ }
275
+
276
+ try {
277
+ performInstallOrUpgrade(pkgName, execFn)
278
+ } catch (err) {
279
+ console.error(
280
+ `Global install failed. Try \`npm install -g ${pkgName}\` manually (may need sudo, or set a\n` +
281
+ 'user-writable npm prefix: `npm config set prefix ~/.npm-global` and add its `bin` to PATH).'
282
+ )
283
+ process.exit(1)
284
+ }
285
+
286
+ const post = verifyCliReadiness(execFn, npmRootFn)
287
+
288
+ if (!post.bin) {
289
+ console.error(
290
+ 'Installed, but `visa` is not on PATH. Ensure your npm global bin dir is on PATH\n' +
291
+ '(`npm bin -g` shows it), then re-run this script.'
292
+ )
293
+ process.exit(1)
294
+ }
295
+
296
+ if (!post.ready) {
297
+ const missingStr =
298
+ post.missingTools.length > 0 ? post.missingTools.join(', ') : 'v4 tool generation'
299
+ console.error(
300
+ `Readiness check failed: installed CLI v${post.version || 'unknown'} is missing required tools (${missingStr}).\n` +
301
+ `Explicit upgrade action:\n` +
302
+ ` npm install -g ${pkgName}`
303
+ )
304
+ process.exit(1)
305
+ }
306
+
307
+ const status = initial.reason === 'not_installed' ? 'installed' : 'upgraded'
308
+ console.log(
309
+ `✓ visa CLI ready (v${post.version}, ${REQUIRED_MCP_TOOLS.join(', ')} present). Now run the pairing flow in SKILL.md.`
44
310
  )
45
- process.exit(1)
311
+ return {
312
+ status,
313
+ version: post.version,
314
+ bin: post.bin,
315
+ }
46
316
  }
47
317
 
48
- console.log('✓ visa CLI ready. Now run the pairing flow in SKILL.md.')
318
+ const isMain =
319
+ typeof process !== 'undefined' &&
320
+ process.argv[1] &&
321
+ (import.meta.url === pathToFileURL(process.argv[1]).href || process.argv[1].endsWith('setup.mjs'))
322
+
323
+ if (isMain) {
324
+ runSetup().catch((err) => {
325
+ console.error(`Provisioning error: ${err && err.message ? err.message : String(err)}`)
326
+ process.exit(1)
327
+ })
328
+ }
package/install.ps1 CHANGED
@@ -137,8 +137,9 @@ try {
137
137
  }
138
138
 
139
139
  # Install via npm
140
- Write-Host " Running: npm install -g @visa/cli" -ForegroundColor Gray
141
- $npmOutput = & npm install -g @visa/cli 2>&1
140
+ $Package = '@visa/cli@rc'
141
+ Write-Host " Running: npm install -g $Package" -ForegroundColor Gray
142
+ $npmOutput = & npm install -g $Package 2>&1
142
143
  $npmExitCode = $LASTEXITCODE
143
144
  $npmText = ($npmOutput | Out-String)
144
145
  $npmOutput | Out-Host
@@ -158,7 +159,7 @@ if ($npmExitCode -ne 0) {
158
159
  Write-Host " Do not disable TLS verification globally." -ForegroundColor Yellow
159
160
  } else {
160
161
  Write-Host " Run manually for the full error, then retry:" -ForegroundColor Yellow
161
- Write-Host " npm install -g @visa/cli" -ForegroundColor Yellow
162
+ Write-Host " npm install -g $Package" -ForegroundColor Yellow
162
163
  }
163
164
 
164
165
  Wait-BeforeExit
@@ -188,7 +189,7 @@ Write-Host ""
188
189
  if ($verifiedCommand) {
189
190
  Write-Host " Visa CLI $visaVersion installed." -ForegroundColor Green
190
191
  Write-Host " Connect an AI client with: $verifiedCommand connect <client>" -ForegroundColor Cyan
191
- Write-Host " Then ask your agent to call enroll_agent." -ForegroundColor Cyan
192
+ Write-Host " Then ask your agent to call setup_start." -ForegroundColor Cyan
192
193
  Write-Host ""
193
194
  } else {
194
195
  $primaryCommand = $cliCommandNames | Select-Object -First 1
package/install.sh CHANGED
@@ -109,7 +109,7 @@ echo ""
109
109
  if [ -n "$VISA_VERSION" ]; then
110
110
  ok "Visa CLI ${VISA_VERSION} installed."
111
111
  info "Connect an AI client with: visa-cli connect <client>"
112
- info "Then ask your agent to call enroll_agent."
112
+ info "Then ask your agent to call setup_start."
113
113
  else
114
114
  warn "Installed, but 'visa-cli' was not found on PATH."
115
115
  warn "Restart your shell, then run: visa-cli connect <client>"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visa/cli",
3
- "version": "4.1.0-rc.229",
3
+ "version": "4.1.0-rc.230",
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",
@@ -42,32 +42,31 @@
42
42
  "author": "Visa Crypto Labs",
43
43
  "license": "SEE LICENSE IN LICENSE",
44
44
  "dependencies": {
45
- "@modelcontextprotocol/core": "2.0.0",
46
45
  "@modelcontextprotocol/ext-apps": "1.7.5",
47
46
  "@modelcontextprotocol/server": "2.0.0",
48
47
  "@ucp-js/sdk": "0.4.4",
49
48
  "commander": "^12.1.0",
50
49
  "punycode": "^2.3.1",
50
+ "toml": "^4.3.0",
51
+ "yaml": "^2.9.0",
51
52
  "zod": "^3.25.76"
52
53
  },
53
54
  "optionalDependencies": {
54
55
  "@chainsafe/libp2p-noise": "^17.0.0",
55
56
  "@chainsafe/libp2p-yamux": "^8.0.1",
56
- "@libp2p/crypto": "^5.1.22",
57
+ "@libp2p/crypto": "^5.1.23",
57
58
  "@libp2p/tcp": "^11.0.26",
58
59
  "@multiformats/multiaddr": "^13.0.3",
59
- "libp2p": "^3.3.8",
60
- "playwright-core": "^1.48.2",
60
+ "libp2p": "^3.3.9",
61
+ "playwright-core": "^1.62.1",
61
62
  "uint8arrays": "^6.1.1"
62
63
  },
63
64
  "devDependencies": {
64
- "@changesets/changelog-git": "^0.2.1",
65
65
  "@types/express": "^5.0.0",
66
- "@changesets/cli": "^2.31.1",
67
66
  "@types/jest": "^30.0.0",
68
67
  "@types/node": "^26.1.0",
69
- "@typescript-eslint/eslint-plugin": "^8.65.0",
70
- "@typescript-eslint/parser": "^8.65.0",
68
+ "@typescript-eslint/eslint-plugin": "^8.68.0",
69
+ "@typescript-eslint/parser": "^8.68.0",
71
70
  "@visa-cli/tools": "workspace:*",
72
71
  "@visa/agent-mail": "workspace:*",
73
72
  "@visa/checkout-engine": "workspace:*",
@@ -80,7 +79,6 @@
80
79
  "@visa/wallet-tools": "workspace:*",
81
80
  "esbuild": "^0.28.1",
82
81
  "eslint": "^10.0.2",
83
- "eslint-config-prettier": "^10.1.8",
84
82
  "express": "^4.21.0",
85
83
  "jest": "^29.7.0",
86
84
  "prettier": "^3.9.6",
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": "4.1.0-rc.229",
4
+ "version": "4.1.0-rc.230",
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": "4.1.0-rc.229",
12
+ "version": "4.1.0-rc.230",
13
13
  "transport": {
14
14
  "type": "stdio"
15
15
  },