@visa/cli 5.0.0-rc.415 → 5.0.0-rc.416

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 CHANGED
@@ -1,9 +1,9 @@
1
1
  # @visa/cli
2
2
 
3
- Visa CLI connects a computer to Visa as an **agent**: one durable agent whose
4
- installation key never leaves this machine, approved once by its owner in the
5
- Visa Console. Agents act through the MCP tools this package serves; owners act
6
- in the Console. The terminal only connects.
3
+ Visa CLI connects each app to Visa as its own **agent**. Its installation
4
+ key stays on this machine. The owner approves the agent once in the Visa
5
+ Console. Agents act through the MCP tools this package serves; owners act in
6
+ the Console. The terminal only connects.
7
7
 
8
8
  It speaks the Visa agent API, `/agent/v1`, and nothing else.
9
9
 
@@ -31,8 +31,8 @@ Managed updates certify the installed MCP bundle with `node dist/mcp-server/inde
31
31
  visa connect claude # or cursor, codex, zed, ... (any client id the CLI knows)
32
32
  ```
33
33
 
34
- `connect` mounts the Visa MCP server in the AI client, then sets this computer
35
- up as an agent:
34
+ `connect` mounts the Visa MCP server in the AI client, then sets up that app’s
35
+ agent:
36
36
 
37
37
  1. It makes an Ed25519 installation key in this machine's key store (macOS
38
38
  Keychain, Windows DPAPI, Linux Secret Service, or a 0600 file on a headless
@@ -46,6 +46,17 @@ up as an agent:
46
46
  need a second `connect` call. `status` reports pickup progress and fresh
47
47
  Authority truth; connecting does not mean payments or TAP are ready.
48
48
 
49
+ Connection is separate from spending:
50
+
51
+ ```text
52
+ Connected.
53
+ Next: add funds and set a limit.
54
+ ```
55
+
56
+ A funded wallet needs only a limit. Status exposes the current connection and spending step in `connection`.
57
+
58
+ New spending limits start on **Ask me each time**. On a connected computer, the owner can choose **Pay within my budget**, with suggested limits of $25 per payment and $100 in total. App connections ask before each payment.
59
+
49
60
  Before presenting a saved approval link, connect and status read the setup's current server state, including while another process owns pickup. Ended or already approved setups expose no approval URL. Status never claims a setup; unknown server state stays recoverable.
50
61
 
51
62
  Pickup stays bound to this setup and key. Its bound covers the setup's initial
@@ -59,6 +70,22 @@ store refuses without a fallback or extra prompt. The original setup remains ava
59
70
  for recovery on the next invocation. A missing pickup bundle or failed launch
60
71
  is reported as interrupted, rather than promising automatic completion.
61
72
 
73
+ Reconnecting the same app keeps its agent. Connecting a different app starts
74
+ a separate setup, with its own name, mark, key and limits. Older connections
75
+ first recover their app from authenticated status. If that identity is unavailable,
76
+ retry connect later or select the existing agent explicitly; no new setup is started.
77
+ If Visa confirms that no app was recorded, connect asks you to select the existing
78
+ agent explicitly instead of retrying or inferring its app from the caller.
79
+ MCP tools default to the calling app’s agent when exactly one matches. An explicit
80
+ agent selector overrides that default. Without a matching app, the usual agent
81
+ selection applies.
82
+
83
+ This computer remembers the wallet first returned by connect or status. Signing
84
+ refuses a different wallet, owner or organisation; payments also check the saved
85
+ wallet account. Agents connected before this update save it on their next
86
+ successful connect or status read. Until then they keep the previous behaviour.
87
+ Replacing the same agent on this computer keeps its saved wallet identity.
88
+
62
89
  Every step resumes. Pickup continues after the terminal closes; if it is interrupted,
63
90
  run `visa connect` again to recover the same setup.
64
91
  `--wait 0` starts the setup and returns; `--no-open` prints the link instead.
@@ -79,6 +106,12 @@ run `visa connect` again to recover the same setup.
79
106
  `--all` also cancels unfinished local setups and automatic pickup. If an
80
107
  unsettled purchase keeps a connected key, its pending replacement is still cancelled.
81
108
  - **Update**: `visa update` (RC stays on `@visa/cli@rc`).
109
+ Updates check a separate release before switching all entry points; interrupted
110
+ downloads keep the previous release usable. Activation rechecks the current release,
111
+ so an overlapping update keeps the active CLI, shell tools and MCP available.
112
+ Releases that cannot start through the installed MCP launcher are refused
113
+ before activation, including incompatible older releases selected by the channel.
114
+ Supervisor-managed runtimes continue to use `visa-runtime-converge`.
82
115
 
83
116
  | Command | What it does |
84
117
  | -------------------------- | ------------------------------------------------------------------------------------- |
@@ -110,6 +143,8 @@ higher `skill_version`, the app asks its owner for the new text. Muse will not
110
143
  save a program's output as a skill, so its owner pastes the same text from the
111
144
  Console's Muse message.
112
145
 
146
+ A delivered answer stays available from `payment_status` during a temporary read outage. Settlement stays unconfirmed until Visa can read the original purchase again.
147
+
113
148
  ## MCP tools
114
149
 
115
150
  Concurrent calls stay bound to their selected agent for every payment phase;
@@ -122,18 +157,48 @@ stable launcher) serves the laptop column of the one Visa tool catalogue:
122
157
  | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
123
158
  | `status` | Who this agent is, what is ready, and who takes the next step. Call first. |
124
159
  | `connect` | Connect this computer (or replace its key). Returns the owner link; picks up approval automatically. |
125
- | `discover` | Search paid services or probe one URL. Answers `unavailable` until the discovery source is live. |
126
- | `pay` | Pay a URL that answers HTTP 402 with a Tempo charge (OUSD). Needs `max` and a new `request_id` per purchase. **Moves real money.** |
160
+ | `discover` | Search paid services or probe one URL with GET and no body. Answers `unavailable` until the discovery source is live. |
161
+ | `pay` | Pay a URL that answers HTTP 402 with a Tempo charge (OUSD). Needs `max` including the network fee and a new `request_id` per purchase. Optional `userRequest`: the person's words, kept by Visa for product analysis. **Moves real money.** |
162
+ | `task_post` | Post a claim task with a reward in USD and a `request_id`; uses the existing payment limits and approval. |
163
+ | `task_action` | Claim, submit, accept or release a task. Claim and submit need the owner's approval of the signature, then the same call again; submit and accept pay and need `max`. Every action first needs Visa's clearance of that task. **Moves real money.** |
164
+ | `task_list` | List open screened tasks (or one named task with `task_id`) and short-lived clearances for task actions. Never pays. |
165
+ | `task_status` | Read a task’s status, reward and available claimant or submission. Never pays. |
127
166
  | `payment_status` | Read one purchase by its `request_id`, optionally waiting for it to change. |
128
- | `payment_cancel` | Cancel a purchase that has not executed. |
167
+ | `payment_cancel` | Cancel a purchase awaiting owner approval. |
129
168
  | `history` | This agent's purchases, newest first. |
130
169
 
170
+ `task_action` is optional; use only tools advertised by this connection.
171
+
172
+ Each tool advertises an extensible object `outputSchema`. Local and hosted MCP
173
+ return that object in `structuredContent`, with a two-line summary first in
174
+ `content` and the complete serialized JSON in a second text block. Text-only
175
+ clients should read the JSON block; display clients should show the summary.
176
+ Existing payload fields and refusal references remain available in the object.
177
+ The shell adapter, `visa-tool`, prints the object as JSON and appends a
178
+ `Reference: <id>` line on refusals; it does not print the MCP display summary.
179
+
180
+ When a `discover` query finds no services, its `next` hint offers `task_post` with
181
+ the query as the suggested description. Posting requires an explicit `reward`
182
+ (USD decimal), `request_id`, optional `duration_seconds` (default 3600) and an
183
+ optional documented `terms_preset` (`standard`, `fast` or `flexible`; a staked task
184
+ names one). Unknown fields are refused. Local rules give fast feedback; the edge must allow the exact draft before paying.
185
+ Tasks needing review are not posted. The CLI obtains fresh clearance before each
186
+ task action; callers do not supply it to `task_action`. Submit needs `text` and
187
+ `max`; accept needs `max`. A missing or invalid `max` is corrected on the same task action with the same `request_id`. Reuse the same arguments and request ID to resume; a task response
188
+ does not prove payment settlement. Listed tasks show `net_reward` and any stake;
189
+ a task that pays the worker nothing is never listed. A task outage or malformed task answer is named in
190
+ `task_screening`, while successful service results remain available. `task_status` reads `/api/tasks/:id` without paying.
191
+ Taskmarket defaults to its testnet on Tempo Moderato and to `api.taskmarket.dev`
192
+ on mainnet; `VISA_TASKMARKET_URL=off` turns tasks off. The live testnet currently refuses access-key signatures;
193
+ the harness models requested support separately.
194
+
131
195
  How `pay` works: the CLI builds the exact Tempo charge from the merchant's 402,
132
196
  asks Visa to admit it with this installation's proof, and, when the wallet
133
197
  provider needs this laptop's own approval, reads the signing activity from the
134
198
  provider itself, checks it against its own charge and votes. The paid request
135
199
  carries the agent's TAP signature; with no TAP key the CLI refuses before
136
- anything is signed. Reusing a `request_id` never pays twice: Visa's record
200
+ anything is signed. Each paying merchant request and its paid retry send `Idempotency-Key` with the same
201
+ `request_id`. Unpaid price previews send no idempotency header. Reusing a `request_id` never pays twice: Visa's record
137
202
  answers. When Visa verifies that the old execution was unpaid and explicitly
138
203
  returns the agent's `resume_payment` continuation, `payment_status` directs this
139
204
  computer to call `pay` with the same arguments and `request_id`. It prepares a
@@ -150,15 +215,22 @@ An authorized purchase without a signing continuation remains unknown and read-o
150
215
 
151
216
  When Visa reconciles a merchant-wallet hold, the settled purchase remains paid and cannot be retried. `pay` preserves Visa's guidance, and `payment_status` exposes its existing owner support continuation. Recovery depends on the merchant's settings; this continuation does not authorize a claim. A missing receipt, unavailable answer or client delivery report does not establish a merchant hold.
152
217
 
218
+ A native payment awaiting approval resumes with `pay`, the same `request_id` and arguments after the owner approves.
219
+
153
220
  Only Visa’s canonical settled state is reported as paid; a merchant answer does not prove settlement. An unknown outcome is never reported as "nothing was paid". Free merchant answers are returned as a preview up to 1 MiB without payment or a kept file; the cap ends the read without waiting for EOF. If a paid answer file or its final journal cannot be saved, the original call still returns its bounded preview and canonical payment result; retries never pay again. A preview with no file does not promise local retention. If a payment read fails through a network interruption or a body interruption after a successful HTTP response, or returns retryable `503 unavailable`, a verified delivered answer remains available locally with settlement unknown; prepared, sending and incomplete receiving attempts still require the original read to succeed. Explicit HTTP refusals and complete malformed Visa responses remain errors. Kept paid answers are checked against the digest recorded as they arrived. A missing, changed, or previously undigested answer is unavailable; its purchase history remains and the paid request is never resent.
154
221
 
155
222
  Refusals are JSON with a `code` from the Visa error families (for example
156
223
  `setup_required`, `limit_reached`, `not_enabled`), a `message` and a `fix`.
157
- When the merchant's charge exceeds `pay.max`, the local `limit_reached` refusal
224
+ For an OUSD charge, allow up to $0.01 above the quoted price for the network fee; a USDC.e swap also needs its funding ceiling.
225
+ When the charge plus network fee ceiling exceeds `pay.max`, the local `limit_reached` refusal
158
226
  names the price above max; budget and cap refusals keep their own diagnostics.
159
227
  A saved merchant challenge that differs from its prepared payment is refused
160
228
  with a charge-mismatch diagnostic before signing or sending the paid request.
161
229
 
230
+ The known Muse shell connection leaves other apps’ MCP settings and status lines untouched.
231
+
232
+ Live status refreshes a renamed agent’s name on this computer. Owner steps for limits and paused apps link to that app in the Console. Unavailable tools direct the agent to the available tools; the owner handles withdrawals and conversions in Wallet.
233
+
162
234
  ## Status line
163
235
 
164
236
  `connect` installs a Claude Code status line (`visa-cli config statusline`).
@@ -190,6 +262,7 @@ and honours `NO_COLOR`.
190
262
  | `VISA_CLI_HOME` | Where this CLI keeps its state (default `~/.visa-cli`). |
191
263
  | `VISA_RUNTIME_CUSTODY_PROFILE` | `os` (default) or `headless` (a 0600 file key store). |
192
264
  | `VISA_SUPPRESS_BROWSER=1` | Print links instead of opening a browser. |
265
+ | `VISA_TASKMARKET_URL` | Task service base URL; defaults to the Taskmarket testnet on Moderato and `https://api.taskmarket.dev` on mainnet. `off` disables tasks. |
193
266
  | `VISA_TEMPO_RPC_URL` | A Tempo RPC for building charges (https, or this machine). |
194
267
  | `VISA_RC_CODE` | The access code a release-candidate build asks for. |
195
268
  | `VISA_CLI_HUD_THEME` | `light` or `dark` status line colours, when the terminal does not say (`COLORFGBG`). |
@@ -201,7 +274,7 @@ and honours `NO_COLOR`.
201
274
  | OS key store, or `~/.visa-cli/agent-keys/` (headless) | The installation key. |
202
275
  | `~/.visa-cli/agent.json` | This computer's agent (ids, name) and, while a setup is open, its one-time setup secret (0600). |
203
276
  | `~/.visa-cli/hud-facts.json` | The status line's cache: the agent's last read state, never keys or tokens. |
204
- | `~/.visa-cli/purchases/` | Server- and agent-scoped records needed to finish after interruption, and merchant answers up to 50 MiB (0600 in a 0700 directory). Canonical settlement or a final unpaid outcome removes the prepared charge and unsigned transaction. Purchase records and already-kept answers are not automatically deleted, including on `visa disconnect --all`. Credential/security lifetimes and terminal preparation cleanup remain separate. |
277
+ | `~/.visa-cli/purchases/` | Server- and agent-scoped records needed to finish after interruption, and merchant answers up to 50 MiB (0600 in a 0700 directory). When local storage can be updated, canonical settlement or a final unpaid outcome removes the prepared charge and unsigned transaction. Purchase records and already-kept answers are not automatically deleted, including on `visa disconnect --all`. Credential/security lifetimes and terminal preparation cleanup remain separate. |
205
278
 
206
279
  ### Managed runtime convergence
207
280
 
@@ -212,7 +285,7 @@ OpenClaw/Hermes deployments. See
212
285
  ### Cutover B hosted approval context
213
286
 
214
287
  The target hosted REST and MCP `pay` input accepts optional `original`, using the
215
- same canonical v1 terms and transient public replay request as payment prepare.
288
+ same canonical v1 terms and transient confidential replay request as payment prepare.
216
289
  Supply the actual original signing intent; a description or commitment hash
217
290
  cannot reconstruct it. Reuse the existing `request_id` to resume the purchase.
218
291
 
@@ -225,3 +298,101 @@ or telemetry. The owner and original payment/approval/deadline bindings remain
225
298
  required. Hosted Tempo payments ask the owner each time and resume the same
226
299
  approved purchase. Internal admission and current spending checks still apply.
227
300
  This source contract does not establish deployed availability.
301
+
302
+ ### Protected message signing
303
+
304
+ Status separates deployment `message_signing.configured` from this agent's
305
+ `message_signing.readiness`. Follow an incomplete readiness continuation.
306
+ `ready` means prerequisites are present, not that a message is authorized;
307
+ service admission and separate owner consent still apply. Older status answers
308
+ without readiness do not establish it from connection or configuration alone.
309
+
310
+ `visa-tool sign_action` and the local/hosted MCP tool accept `request_id`,
311
+ `scheme: "personal_sign"`, the original UTF-8 `message` and an HTTPS-origin
312
+ `audience`. Use the same request ID and original inputs to resume. The selected
313
+ agent determines the wallet and Tempo access key. The signature is an
314
+ account-bound Tempo keychain V2 envelope; a service must support that envelope.
315
+ Audience and network metadata constrain issuance. The external signature binds
316
+ them only when the service includes them in its signed message.
317
+
318
+ Message permission is separate from payment limits. The owner can approve the
319
+ exact message or, on a device, an explicit literal prefix using
320
+ `permission_scope`. Matching device requests name that `permission_id`.
321
+ Permissions currently last at most five minutes and the current key expiry;
322
+ renewal needs new owner consent. Hosted signatures require exact owner consent
323
+ and an independent owner vote each time. Signing may authorize escrow or other
324
+ financial effects at a service even though no new payment occurs.
325
+
326
+ `GET /agent/v1/signatures/:request_id` reports metadata only. Repeating the
327
+ original `sign_action` recovers a known provider activity and verifies the
328
+ signature again before delivery. A dispatch with a lost activity locator stays
329
+ unknown and is never replaced. The private owner page can revoke consent and
330
+ dispose of its message context; commitments and dispatch fences remain.
331
+
332
+ The production signer refuses unconfigured, unknown or ambiguous message
333
+ namespaces. Trusted policy configuration and protected-provider qualification
334
+ are activation requirements. No marketplace commands, EIP-712, root-wallet
335
+ signing, ERC-8004 registration or automatic legal acceptance are added.
336
+
337
+ ### HTTP identity
338
+
339
+ Protected signer preparation retains an assigned directory Key ID, registered
340
+ agent domain and known public directory origin when the registry supplies them.
341
+ The current signing profile still uses the cryptographic key thumbprint in its
342
+ signed headers. Directory UUIDs require a protected signing-contract upgrade.
343
+ HTTP identity v5 can cover a caller-supplied `Signature-Agent` header, but this
344
+ metadata is not automatically sent. Retaining metadata alone does not establish
345
+ merchant interoperability.
346
+
347
+ `visa-tool attest_http_request` and local/hosted MCP accept a canonical HTTPS
348
+ `url`, `method` (`GET`, `HEAD`, `POST`), application `headers` as pairs and
349
+ `body_sha256` as a base64url SHA-256 digest. They return the exact normalized
350
+ request and short-lived TAP identity headers. The agent sends them with its own
351
+ HTTP client. Nothing is sent or paid by this tool; a 402 still needs `pay`.
352
+
353
+ This uses the TAP identity key and standing owner TAP consent. `sign_action`
354
+ continues to govern wallet-key messages. Requests can authorize service actions,
355
+ so use the identity deliberately. Requires gate v5; older deployments refuse.
356
+ See [the protocol, SDK and merchant contract](../../docs/design/target/http-attestation.md).
357
+
358
+ ### Merchant headers for Tempo MPP
359
+
360
+ `pay` accepts `headers` as a string map or name/value pairs. Supply the merchant's
361
+ required receipt and stable operation key, such as `Idempotency-Key`, unchanged
362
+ on recovery. They are independent of Visa's purchase UUID. Permitted names are
363
+ case-insensitive, values stay exact, and request commitments detect changes.
364
+ Reserved transport, wallet/payment authorization and Visa headers are refused.
365
+ Raw values stay transient and do not enter logs or public history. Redirects are
366
+ not followed with these headers. Native ATUM currently refuses caller-supplied
367
+ headers before funding because its closed plan does not bind them.
368
+
369
+ Registry documentation follows the package release channel: prerelease builds use preview docs; stable builds use production docs.
370
+
371
+ `status` accepts only the optional `agent` selector. Unknown arguments are refused;
372
+ correct them on the same tool.
373
+
374
+ Correct a malformed request_id on the same tool that refused it. Resuming an existing operation keeps its original request_id.
375
+
376
+ Hosted discover searches by query. To price a URL, use discover with target in Visa CLI on a connected computer.
377
+
378
+ A validation refusal describes the rejected input. It does not establish an earlier purchase’s outcome; read that purchase with payment_status and its original reference.
379
+
380
+ A refused or unavailable task directs the agent to another permitted task. A task needing review directs it to Visa support; these refusals do not direct purchase-status reads.
381
+
382
+ For a signing input conflict, repeat sign_action with its original inputs and request_id. The repeat_original_sign_action continuation refers to signing; payment_status retrieves purchases.
383
+
384
+ An account-wide daily limit offers a smaller payment, a later attempt or Visa
385
+ support. Raising an agent's spending limit cannot change that account limit.
386
+
387
+ `history` accepts the previous answer's `page.next_cursor` as `cursor`, unchanged,
388
+ to read the next page on local and hosted tools.
389
+
390
+ `discover` accepts `limit` from 1 to 50. A search returns the matching services
391
+ available, which can be fewer than the requested limit.
392
+
393
+ A final `payment_status` or `payment_cancel` read updates this computer's purchase
394
+ record. Disconnect releases a key once its purchases are final; an uncertain or
395
+ in-flight purchase keeps its key and any saved merchant answer.
396
+
397
+ HTTP identity requests for a paused agent retain the owner's resume step,
398
+ including when its public identity entry is no longer available.
@@ -0,0 +1,15 @@
1
+ // The bootstrap package stays installed while a replacement is downloaded.
2
+ const fs = require('node:fs')
3
+ const path = require('node:path')
4
+ module.exports = function activePackage(bootstrap) {
5
+ const root = path.join(bootstrap, '.self-update')
6
+ let version
7
+ try {
8
+ version = JSON.parse(fs.readFileSync(path.join(root, 'active.json'), 'utf8')).version
9
+ } catch (error) {
10
+ if (error.code === 'ENOENT') return bootstrap
11
+ throw error
12
+ }
13
+ if (!/^\d+\.\d+\.\d+(?:-rc\.\d+)?$/.test(version)) throw new Error('Invalid CLI release')
14
+ return path.join(root, 'releases', version, 'node_modules', '@visa', 'cli')
15
+ }
package/bin/update.cjs ADDED
@@ -0,0 +1,92 @@
1
+ const fs = require('node:fs')
2
+ const path = require('node:path')
3
+ const { pathToFileURL } = require('node:url')
4
+ const { execFileSync } = require('node:child_process')
5
+ const { randomUUID } = require('node:crypto')
6
+
7
+ module.exports = async function update(bootstrap, tag, logger) {
8
+ const activePackage = require('./active-package.cjs')
9
+ const root = path.join(bootstrap, '.self-update')
10
+ const helpers = await import(
11
+ pathToFileURL(path.join(__dirname, '..', 'dist', 'managed-runtime', 'update-and-restart.mjs'))
12
+ .href
13
+ )
14
+ if (process.env.VISA_MANAGED_RUNTIME_DIR) {
15
+ const managed = await import(
16
+ pathToFileURL(path.join(__dirname, '..', 'dist', 'managed-runtime', 'resolve-and-update.mjs'))
17
+ .href
18
+ )
19
+ await managed.runManagedRuntimeConvergence(process.env, {
20
+ logger: { log: (message) => logger.info('update:managed', { message }) },
21
+ })
22
+ return
23
+ }
24
+ const id = randomUUID()
25
+ const options = { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 300_000 }
26
+ // Four registry/install commands and two probes must finish before this lease can expire.
27
+ const unlock = helpers.acquireUpdateLock(root, id, { leaseMs: options.timeout * 6 + 60_000 })
28
+ const npm = (command, args, opts) => {
29
+ const output = execFileSync(command, args, opts)
30
+ logger.info('update:package_manager', { command: args[0], output: String(output ?? '') })
31
+ return output
32
+ }
33
+ try {
34
+ // A preceding updater may have activated a release while this one loaded.
35
+ const active = activePackage(bootstrap)
36
+ const version = JSON.parse(
37
+ npm('npm', ['view', `@visa/cli@${tag}`, 'version', '--json'], options)
38
+ )
39
+ if (
40
+ typeof version !== 'string' ||
41
+ !/^\d+\.\d+\.\d+(?:-rc\.\d+)?$/.test(version) ||
42
+ version.includes('-rc.') !== (tag === 'rc')
43
+ )
44
+ throw new Error('Registry did not return an exact release on this channel')
45
+ if (JSON.parse(fs.readFileSync(path.join(active, 'package.json'), 'utf8')).version === version)
46
+ return
47
+ const release = helpers.stageExactRelease(
48
+ {
49
+ managedDir: root,
50
+ exactVersion: version,
51
+ commandTimeoutMs: options.timeout,
52
+ correlationId: id,
53
+ },
54
+ {
55
+ execFileSync: npm,
56
+ logger: { log: (message) => logger.info('update:stage', { message }) },
57
+ }
58
+ )
59
+ const installed = path.join(release, 'node_modules', '@visa', 'cli')
60
+ const reported = execFileSync(
61
+ process.execPath,
62
+ [path.join(installed, 'bin', 'visa-cli.js'), '--version'],
63
+ options
64
+ ).trim()
65
+ if (reported !== version) throw new Error('Staged CLI version did not match')
66
+ const checked = JSON.parse(
67
+ execFileSync(
68
+ process.execPath,
69
+ [
70
+ '-e',
71
+ `const server = require(process.argv[1]);
72
+ if (typeof server.startMcpServer !== 'function') throw new Error('Incompatible MCP launcher');
73
+ server.startMcpServer();`,
74
+ path.join(installed, 'dist', 'mcp-server', 'index.js'),
75
+ '--self-check',
76
+ ],
77
+ options
78
+ )
79
+ )
80
+ if (checked.ok !== true || checked.build?.version !== version)
81
+ throw new Error('Staged MCP server did not load')
82
+ fs.accessSync(path.join(installed, 'dist', 'cli.js'))
83
+ fs.accessSync(path.join(installed, 'dist', 'tool-call.js'))
84
+ // Readers see the complete old release or the complete new release. The
85
+ // old package and every previous release remain usable after interruption.
86
+ const next = path.join(root, `.active-${id}.json`)
87
+ fs.writeFileSync(next, JSON.stringify({ version }), { mode: 0o600, flag: 'wx' })
88
+ fs.renameSync(next, path.join(root, 'active.json'))
89
+ } finally {
90
+ unlock()
91
+ }
92
+ }
package/bin/visa-cli.js CHANGED
@@ -1,2 +1,4 @@
1
1
  #!/usr/bin/env node
2
- require('../dist/cli.js');
2
+ const path = require('node:path')
3
+ const active = require('./active-package.cjs')(path.dirname(__dirname))
4
+ require(path.join(active, 'dist', 'cli.js'))
package/bin/visa-tool.js CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
- require('../dist/tool-call.js')
2
+ const path = require('node:path')
3
+ const active = require('./active-package.cjs')(path.dirname(__dirname))
4
+ require(path.join(active, 'dist', 'tool-call.js'))
3
5
  .main(process.argv.slice(2))
4
6
  .then((code) => {
5
7
  process.exitCode = code