visa-cli 1.0.0
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/LICENSE +8 -0
- package/README.md +141 -0
- package/bin/visa-cli.js +2 -0
- package/dist/cli.js +19 -0
- package/dist/mcp-server/index.js +4 -0
- package/native/visa-keychain.m +274 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Copyright (c) 2026 Visa International Service Association. All rights reserved.
|
|
2
|
+
|
|
3
|
+
Use of this software is governed by the Visa CLI Terms of Use, available at:
|
|
4
|
+
https://auth.visacli.sh/legal/terms
|
|
5
|
+
|
|
6
|
+
This software is proprietary and confidential. Unauthorized copying, distribution,
|
|
7
|
+
or use of this software, in whole or in part, is strictly prohibited except as
|
|
8
|
+
expressly permitted by the Visa CLI Terms of Use.
|
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Visa CLI
|
|
2
|
+
|
|
3
|
+
Enable AI agents to make real payments. Two commands to start, zero build steps.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g visa-cli
|
|
7
|
+
visa-cli setup
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Then ask Claude:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Generate me an image of a sunset
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### 1. Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g visa-cli
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. Setup
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
visa-cli setup
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This does three things in one step:
|
|
33
|
+
1. Registers the MCP server with Claude Code
|
|
34
|
+
2. Opens a browser for GitHub sign-in + card enrollment
|
|
35
|
+
3. Generates a Secure Enclave attestation key (for Touch ID verification)
|
|
36
|
+
|
|
37
|
+
Restart Claude Code or run `/mcp` to connect, then start using it.
|
|
38
|
+
|
|
39
|
+
### 3. Start Using It
|
|
40
|
+
|
|
41
|
+
Ask Claude naturally:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Generate me an image of a sunset
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
What's the price of SOL right now?
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
Make me a lofi jazz beat
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
All payments are handled automatically with your enrolled card. Touch ID confirms each transaction. Result URLs (images, track pages) open automatically in your browser.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Available Tools
|
|
60
|
+
|
|
61
|
+
| Tool | What it does |
|
|
62
|
+
|------|-------------|
|
|
63
|
+
| `pay` | Pay a merchant URL (auto-detects payment rail) |
|
|
64
|
+
| `generate_image_card` | Generate an AI image via fal.ai |
|
|
65
|
+
| `generate_music_tempo_card` | Generate a music track via Suno AI |
|
|
66
|
+
| `check_music_status_tempo_card` | Check music generation status and get audio URLs |
|
|
67
|
+
| `query_onchain_prices_card` | Query real-time token prices from 150+ blockchains |
|
|
68
|
+
| `batch` | Run any tool multiple times in parallel with one Touch ID approval |
|
|
69
|
+
| `get_status` | Check enrollment, cards, and spending controls |
|
|
70
|
+
| `transaction_history` | View recent transactions |
|
|
71
|
+
| `update_spending_controls` | Set daily limit, max per-transaction, approval mode |
|
|
72
|
+
| `add_card` | Add a payment card |
|
|
73
|
+
| `login` | GitHub sign-in + card enrollment |
|
|
74
|
+
| `reset` | Clear all credentials from this device |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Security
|
|
79
|
+
|
|
80
|
+
Every payment requires Touch ID (macOS) via Secure Enclave. This cannot be disabled or bypassed — attestation is verified server-side.
|
|
81
|
+
|
|
82
|
+
| Layer | Protection |
|
|
83
|
+
|-------|------------|
|
|
84
|
+
| Session token | macOS Keychain (only credential on device) |
|
|
85
|
+
| Touch ID | Secure Enclave ECDSA signing (hardware-backed) |
|
|
86
|
+
| Spending limits | Server-side enforcement |
|
|
87
|
+
| Rate limiting | Server-side per-session |
|
|
88
|
+
| Card data | Server-side only (never touches the client) |
|
|
89
|
+
|
|
90
|
+
### Spending Controls
|
|
91
|
+
|
|
92
|
+
| Setting | Default | Description |
|
|
93
|
+
|---------|---------|-------------|
|
|
94
|
+
| Max per transaction | $100 | Per-transaction ceiling |
|
|
95
|
+
| Daily limit | $500 | Daily spend ceiling |
|
|
96
|
+
| Daily transaction count | 50 | Daily transaction count ceiling |
|
|
97
|
+
|
|
98
|
+
Adjust with: "Set my daily limit to $200"
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## CLI Commands
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
visa-cli setup # Install, login, and enroll card
|
|
106
|
+
visa-cli install claude # Register MCP server with Claude Code
|
|
107
|
+
visa-cli status # Show enrollment and spending controls
|
|
108
|
+
visa-cli login # Re-authenticate
|
|
109
|
+
visa-cli add-card # Add a new payment card
|
|
110
|
+
visa-cli reset --confirm # Clear session and attestation key
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Troubleshooting
|
|
116
|
+
|
|
117
|
+
**"Authentication required"** — Run `visa-cli setup` to log in and enroll a card.
|
|
118
|
+
|
|
119
|
+
**"Session expired"** — Run `visa-cli setup` to re-authenticate.
|
|
120
|
+
|
|
121
|
+
**"Amount exceeds limit"** — Ask Claude to increase your spending limit, or use `update_spending_controls`.
|
|
122
|
+
|
|
123
|
+
**"Rate limited"** — Wait a few seconds and try again.
|
|
124
|
+
|
|
125
|
+
**Batch request failed / "Merchant server error"** — The upstream API (e.g. fal.ai) may be temporarily overloaded. Wait a minute and retry with a smaller batch. Timeouts scale automatically with batch size.
|
|
126
|
+
|
|
127
|
+
**Tools not showing in Claude Code** — Run `/mcp` in Claude Code to reconnect, or restart Claude Code.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Requirements
|
|
132
|
+
|
|
133
|
+
- macOS (Touch ID or system password required for payment approval)
|
|
134
|
+
- Node.js 18+
|
|
135
|
+
- Claude Code
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Legal
|
|
140
|
+
|
|
141
|
+
Use of this software is governed by the [Visa CLI Terms of Use](https://auth.visacli.sh/legal/terms) and [Privacy Notice](https://auth.visacli.sh/legal/privacy).
|
package/bin/visa-cli.js
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";var ae=Object.create;var F=Object.defineProperty;var ce=Object.getOwnPropertyDescriptor;var le=Object.getOwnPropertyNames;var ue=Object.getPrototypeOf,me=Object.prototype.hasOwnProperty;var ge=(t,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of le(e))!me.call(t,s)&&s!==n&&F(t,s,{get:()=>e[s],enumerable:!(o=ce(e,s))||o.enumerable});return t};var a=(t,e,n)=>(n=t!=null?ae(ue(t)):{},ge(e||!t||!t.__esModule?F(n,"default",{value:t,enumerable:!0}):n,t));var te=require("commander"),ne=a(require("crypto")),p=a(require("fs")),C=a(require("path")),oe=a(require("os")),_=require("child_process"),re=require("util");var q=require("child_process"),M=require("util"),m=a(require("fs")),V=a(require("os")),A=a(require("path")),k=(0,M.promisify)(q.execFile),j=A.join(V.homedir(),".visa-mcp"),E=A.join(j,"session-token"),b="visa-cli",T="session-token";async function de(){try{let{stdout:t}=await k("security",["find-generic-password","-s",b,"-a",T,"-w"],{timeout:5e3});return t.trim()||null}catch{return null}}async function K(t){try{try{await k("security",["delete-generic-password","-s",b,"-a",T],{timeout:5e3})}catch{}return await k("security",["add-generic-password","-s",b,"-a",T,"-w",t],{timeout:5e3}),!0}catch{return!1}}async function ye(){try{await k("security",["delete-generic-password","-s",b,"-a",T],{timeout:5e3})}catch{}}var g=class{static async getSessionToken(){let e=await de();if(e)return e;try{let n=m.readFileSync(E,"utf-8").trim();if(n)return await K(n),n}catch{}return null}static async saveSessionToken(e){if(await K(e)){try{m.unlinkSync(E)}catch{}return}m.mkdirSync(j,{recursive:!0,mode:448}),m.writeFileSync(E,e,{mode:384})}static async deleteSessionToken(){await ye();try{m.unlinkSync(E)}catch{}}static async clearAll(){await this.deleteSessionToken()}};async function D(t,e){let n=e?.timeoutMs??3e4,o=new AbortController,s=setTimeout(()=>o.abort(),n);try{let{timeoutMs:c,...r}=e??{};return await fetch(t,{...r,signal:o.signal})}finally{clearTimeout(s)}}var h=class{constructor(e){this.getSessionToken=e;this.baseUrl=process.env.VISA_AUTH_URL||"https://auth.visacli.sh"}baseUrl;async request(e,n,o,s){let c=await this.getSessionToken();if(!c)throw new Error("Not logged in. Use the login tool to authenticate.");let r;try{r=await D(`${this.baseUrl}${n}`,{method:e,headers:{Authorization:`Bearer ${c}`,...o?{"Content-Type":"application/json"}:{}},body:o?JSON.stringify(o):void 0,timeoutMs:s})}catch(l){throw l.name==="AbortError"||l.message?.includes("aborted")?new Error("The request timed out. The server may be under heavy load. Please try again."):new Error("Cannot reach the Visa CLI server. Check your internet connection and try again.")}if(r.status===401)throw new Error("Your session has expired. Use the login tool to re-authenticate.");if(r.status===429){let l=r.headers.get("Retry-After")||"3";throw new Error(`Too many requests. Please wait ${l} seconds before trying again.`)}if(r.status===503)throw new Error("The Visa CLI service is temporarily unavailable. Please try again in a few minutes.");let v;try{v=await r.json()}catch{throw r.status===500?new Error("Something went wrong on our end. Please try again."):new Error("Something went wrong. Please try again.")}if(!r.ok)throw r.status===500?new Error("Something went wrong on our end. Please try again."):new Error(v?.error||"Something went wrong. Please try again.");return v}async pay(e){return this.request("POST","/v1/pay",e)}async shortcut(e,n,o){return this.request("POST",`/v1/shortcuts/${encodeURIComponent(e)}`,n,o)}async batch(e,n){return this.request("POST","/v1/batch",e,n)}async paymentPreview(e){return this.request("POST","/v1/payment-preview",e)}async getStatus(){return this.request("GET","/v1/status")}async getTransactions(){return this.request("GET","/v1/transactions")}async updateSpendingControls(e){return this.request("POST","/v1/spending-controls",e)}async getAttestationChallenge(){return this.request("GET","/v1/attestation-challenge")}async registerAttestationKey(e){return this.request("POST","/v1/attestation-key",{publicKey:e})}async logout(e){return this.request("POST","/v1/logout",e)}};var Y=require("child_process"),J=require("util"),X=a(require("crypto")),i=a(require("fs")),z=a(require("os")),u=a(require("path")),w=(0,J.promisify)(Y.execFile),P=u.join(z.homedir(),".visa-mcp","bin"),d=u.join(P,"Visa CLI"),he=u.join(__dirname,"..","native"),H="4",B=u.join(P,"visa-keychain.version"),W=u.join(P,"visa-keychain.sha256");function G(t){let e=i.readFileSync(t);return X.createHash("sha256").update(e).digest("hex")}async function pe(){try{if(i.readFileSync(B,"utf-8").trim()===H&&i.existsSync(d)){let o=i.readFileSync(W,"utf-8").trim();if(G(d)!==o)console.error("[visa-keychain] WARNING: Binary hash mismatch \u2014 possible tampering detected. Recompiling from source."),i.unlinkSync(d);else return d}}catch{}let t=u.join(he,"visa-keychain.m");if(i.existsSync(t)||(t=u.resolve(__dirname,"..","..","native","visa-keychain.m")),i.existsSync(t)||(t=u.resolve(__dirname,"..","native","visa-keychain.m")),!i.existsSync(t))throw new Error("visa-keychain.m source not found. Reinstall Visa CLI.");i.mkdirSync(P,{recursive:!0,mode:448});try{await w("clang",["-framework","Security","-framework","LocalAuthentication","-framework","Foundation","-o",d,t],{timeout:3e4})}catch(n){throw n.code==="ENOENT"?new Error("Xcode Command Line Tools required. Install: xcode-select --install"):n}let e=G(d);return i.writeFileSync(W,e,{mode:384}),i.writeFileSync(B,H,{mode:384}),d}async function Q(t){let e=await pe(),n;try{n=(await w(e,t,{timeout:6e4})).stdout}catch(c){n=c.stdout||"";let r=n.trim();throw r.startsWith("ERROR:")?new Error(r.slice(6)):new Error(c.stderr?.trim()||c.message||"Unknown error")}let o=n.trim();if(o.startsWith("OK:"))return o.slice(3);if(o==="OK")return;let s=o.startsWith("ERROR:")?o.slice(6):"Unknown error";throw new Error(s)}var O=null;function x(){return process.platform!=="darwin"?!1:O!==null?O:(O=!0,!0)}var I="visa-cli",$="attestation-key";async function fe(t){try{await w("security",["delete-generic-password","-s",I,"-a",$],{timeout:5e3})}catch{}await w("security",["add-generic-password","-s",I,"-a",$,"-w",t],{timeout:5e3})}async function Z(){let t=await Q(["generate-key"]);if(!t)throw new Error("Key generation returned no output");let e=t.indexOf(":");if(e<0)throw new Error("Unexpected generate-key output format");let n=t.slice(0,e),o=t.slice(e+1);return await fe(n),o}async function ee(){try{await w("security",["delete-generic-password","-s",I,"-a",$],{timeout:5e3})}catch{}try{await Q(["delete-key"])}catch{}}var Se=(0,re.promisify)(_.execFile),S=new te.Command;S.name("visa-cli").description("Visa CLI - AI payment orchestration").version("1.0.0");S.command("setup").description("Register MCP server, authenticate, and generate attestation key").action(async()=>{try{console.log("Step 1: Registering MCP server in ~/.claude.json...");let t=C.join(oe.homedir(),".claude.json"),e=C.resolve(__dirname,"mcp-server/index.js"),n={};p.existsSync(t)&&(n=JSON.parse(p.readFileSync(t,"utf-8"))),n.mcpServers=n.mcpServers||{},n.mcpServers["visa-cli"]={command:"node",args:[e]},p.writeFileSync(t,JSON.stringify(n,null,2)),console.log(" Registered visa-cli MCP server."),console.log(`
|
|
2
|
+
Step 2: Checking authentication...`);let o=await g.getSessionToken();if(o?console.log(" Already authenticated."):(console.log(" No session found. Opening browser for GitHub login..."),o=await new Promise(async(l,R)=>{let N=ne.randomBytes(16).toString("hex"),L=`https://auth.visacli.sh/login?state=${N}`;console.log(` Opening: ${L}
|
|
3
|
+
`),process.platform==="darwin"&&(0,_.execFile)("open",[L],f=>{f&&console.error(" Failed to open browser:",f.message)}),console.log(` Waiting for login in browser...
|
|
4
|
+
`);let U=3e4,se=300*1e3,ie=Date.now()+se;for(;Date.now()<ie;)try{let f=await globalThis.fetch("https://auth.visacli.sh/v1/auth-status",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({state:N,timeout:U}),signal:AbortSignal.timeout(U+5e3)});if(!f.ok)continue;let y=await f.json();if(y.status==="pending")continue;if(y.status==="expired"){R(new Error("Session expired. Please run setup again."));return}if(y.status==="complete"&&y.sessionToken){console.log(` Signed in as ${y.user}.`),l(y.sessionToken);return}}catch{}R(new Error("Login timed out after 5 minutes. Please run setup again."))}),await g.saveSessionToken(o),console.log(" Session token saved.")),console.log(`
|
|
5
|
+
Step 3: Setting up authentication...`),!x())console.log(" Not macOS \u2014 skipping biometric setup.");else{try{await Se("clang",["--version"])}catch{console.error(" Xcode Command Line Tools are required for payment authentication."),console.error(" Install them by running: xcode-select --install"),console.error(" Then re-run: visa-cli setup"),process.exit(1)}try{let l=await Z();console.log(" Attestation key generated."),await new h(()=>g.getSessionToken()).registerAttestationKey(l),console.log(" Attestation key registered with server.")}catch(l){console.log(` Skipped: ${l.message}`)}}let s="\x1B[38;2;26;31;113m",c="\x1B[38;2;234;179;8m",r="\x1B[0m";console.log(`
|
|
6
|
+
${s} \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588${r}
|
|
7
|
+
${s} \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588${r}
|
|
8
|
+
${s} \u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588${r}
|
|
9
|
+
${c} \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 ${s} \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588${r}
|
|
10
|
+
${s} \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588${r}
|
|
11
|
+
|
|
12
|
+
\x1B[1mSetup complete.${r} Restart Claude Code or run /mcp to connect.
|
|
13
|
+
`)}catch(t){console.error("Error:",t.message),process.exit(1)}});S.command("status").description("Check enrollment, cards, wallet, and spending controls").action(async()=>{try{let e=await new h(()=>g.getSessionToken()).getStatus();if(console.log(`Visa CLI Status
|
|
14
|
+
`),console.log("Enrollment:"),console.log(` Enrolled: ${e.enrolled?"Yes":"No"}`),e.githubUser&&console.log(` GitHub: ${e.githubUser}`),console.log(` Cards: ${e.cardCount??0}`),e.cards&&e.cards.length>0){console.log(`
|
|
15
|
+
Cards:`);for(let n of e.cards){let o=n.isDefault?" (default)":"";console.log(` ${n.brand?.toUpperCase()||"CARD"} ****${n.last4}${o}`)}}if(e.spendingControls){let n=e.spendingControls;console.log(`
|
|
16
|
+
Spending Controls:`),console.log(` Max per transaction: $${n.maxTransactionAmount}`),console.log(` Daily limit: $${n.dailyLimit}`),n.dailySpent!==void 0&&console.log(` Spent today: $${Number(n.dailySpent).toFixed(2)} / $${n.dailyLimit}`)}console.log(`
|
|
17
|
+
Touch ID:`),console.log(` Available: ${x()?"Yes":"No"}`)}catch(t){console.error("Error:",t.message),process.exit(1)}});S.command("reset").description("Log out and clear all credentials").action(async()=>{try{console.log(`Resetting Visa CLI...
|
|
18
|
+
`);try{await new h(()=>g.getSessionToken()).logout(),console.log(" Server session invalidated.")}catch{console.log(" Server logout skipped (no active session).")}if(await g.clearAll(),console.log(" Keychain credentials cleared."),x())try{await ee(),console.log(" Secure Enclave key deleted.")}catch{console.log(" No Secure Enclave key to delete.")}console.log(`
|
|
19
|
+
Reset complete.`)}catch(t){console.error("Error:",t.message),process.exit(1)}});S.parse();
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";var kt=Object.create;var Z=Object.defineProperty;var Et=Object.getOwnPropertyDescriptor;var Pt=Object.getOwnPropertyNames;var Rt=Object.getPrototypeOf,Xt=Object.prototype.hasOwnProperty;var xt=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of Pt(t))!Xt.call(e,a)&&a!==r&&Z(e,a,{get:()=>t[a],enumerable:!(n=Et(t,a))||n.enumerable});return e};var l=(e,t,r)=>(r=e!=null?kt(Rt(e)):{},xt(t||!e||!e.__esModule?Z(r,"default",{value:e,enumerable:!0}):r,e));var St=require("@modelcontextprotocol/sdk/server/index.js"),vt=require("@modelcontextprotocol/sdk/server/stdio.js"),M=require("@modelcontextprotocol/sdk/types.js");async function Q(e,t){let r=t?.timeoutMs??3e4,n=new AbortController,a=setTimeout(()=>n.abort(),r);try{let{timeoutMs:s,...i}=t??{};return await fetch(e,{...i,signal:n.signal})}finally{clearTimeout(a)}}var A=class{constructor(t){this.getSessionToken=t;this.baseUrl=process.env.VISA_AUTH_URL||"https://auth.visacli.sh"}baseUrl;async request(t,r,n,a){let s=await this.getSessionToken();if(!s)throw new Error("Not logged in. Use the login tool to authenticate.");let i;try{i=await Q(`${this.baseUrl}${r}`,{method:t,headers:{Authorization:`Bearer ${s}`,...n?{"Content-Type":"application/json"}:{}},body:n?JSON.stringify(n):void 0,timeoutMs:a})}catch(u){throw u.name==="AbortError"||u.message?.includes("aborted")?new Error("The request timed out. The server may be under heavy load. Please try again."):new Error("Cannot reach the Visa CLI server. Check your internet connection and try again.")}if(i.status===401)throw new Error("Your session has expired. Use the login tool to re-authenticate.");if(i.status===429){let u=i.headers.get("Retry-After")||"3";throw new Error(`Too many requests. Please wait ${u} seconds before trying again.`)}if(i.status===503)throw new Error("The Visa CLI service is temporarily unavailable. Please try again in a few minutes.");let c;try{c=await i.json()}catch{throw i.status===500?new Error("Something went wrong on our end. Please try again."):new Error("Something went wrong. Please try again.")}if(!i.ok)throw i.status===500?new Error("Something went wrong on our end. Please try again."):new Error(c?.error||"Something went wrong. Please try again.");return c}async pay(t){return this.request("POST","/v1/pay",t)}async shortcut(t,r,n){return this.request("POST",`/v1/shortcuts/${encodeURIComponent(t)}`,r,n)}async batch(t,r){return this.request("POST","/v1/batch",t,r)}async paymentPreview(t){return this.request("POST","/v1/payment-preview",t)}async getStatus(){return this.request("GET","/v1/status")}async getTransactions(){return this.request("GET","/v1/transactions")}async updateSpendingControls(t){return this.request("POST","/v1/spending-controls",t)}async getAttestationChallenge(){return this.request("GET","/v1/attestation-challenge")}async registerAttestationKey(t){return this.request("POST","/v1/attestation-key",{publicKey:t})}async logout(t){return this.request("POST","/v1/logout",t)}};var F=require("child_process"),at=require("util"),st=l(require("crypto")),m=l(require("fs")),ot=l(require("os")),g=l(require("path")),O=(0,at.promisify)(F.execFile),N=g.join(ot.homedir(),".visa-mcp","bin"),T=g.join(N,"Visa CLI"),At=g.join(__dirname,"..","native"),tt="4",et=g.join(N,"visa-keychain.version"),rt=g.join(N,"visa-keychain.sha256");function nt(e){let t=m.readFileSync(e);return st.createHash("sha256").update(t).digest("hex")}async function it(){try{if(m.readFileSync(et,"utf-8").trim()===tt&&m.existsSync(T)){let n=m.readFileSync(rt,"utf-8").trim();if(nt(T)!==n)console.error("[visa-keychain] WARNING: Binary hash mismatch \u2014 possible tampering detected. Recompiling from source."),m.unlinkSync(T);else return T}}catch{}let e=g.join(At,"visa-keychain.m");if(m.existsSync(e)||(e=g.resolve(__dirname,"..","..","native","visa-keychain.m")),m.existsSync(e)||(e=g.resolve(__dirname,"..","native","visa-keychain.m")),!m.existsSync(e))throw new Error("visa-keychain.m source not found. Reinstall Visa CLI.");m.mkdirSync(N,{recursive:!0,mode:448});try{await O("clang",["-framework","Security","-framework","LocalAuthentication","-framework","Foundation","-o",T,e],{timeout:3e4})}catch(r){throw r.code==="ENOENT"?new Error("Xcode Command Line Tools required. Install: xcode-select --install"):r}let t=nt(T);return m.writeFileSync(rt,t,{mode:384}),m.writeFileSync(et,tt,{mode:384}),T}async function Ot(e){let t=await it(),r;try{r=(await O(t,e,{timeout:6e4})).stdout}catch(s){r=s.stdout||"";let i=r.trim();throw i.startsWith("ERROR:")?new Error(i.slice(6)):new Error(s.stderr?.trim()||s.message||"Unknown error")}let n=r.trim();if(n.startsWith("OK:"))return n.slice(3);if(n==="OK")return;let a=n.startsWith("ERROR:")?n.slice(6):"Unknown error";throw new Error(a)}var j=null;function G(){return process.platform!=="darwin"?!1:j!==null?j:(j=!0,!0)}var ct="visa-cli",ut="attestation-key";async function Nt(){try{let{stdout:e}=await O("security",["find-generic-password","-s",ct,"-a",ut,"-w"],{timeout:5e3});return e.trim()||null}catch{return null}}async function mt(e,t){let r=await Nt();if(!r)throw new Error("Attestation key not found. Run setup to generate a new key.");let n=await it(),a=["sign",e];return t&&a.push(t),new Promise((s,i)=>{let c=(0,F.execFile)(n,a,{timeout:6e4},(u,x)=>{let b=(x||"").trim();if(u){b.startsWith("ERROR:")?i(new Error(b.slice(6))):i(new Error(u.stderr?.trim()||u.message||"Unknown error"));return}b.startsWith("OK:")?s(b.slice(3)):i(new Error(b.startsWith("ERROR:")?b.slice(6):"Unknown error"))});c.stdin.write(r),c.stdin.end()})}async function lt(){try{await O("security",["delete-generic-password","-s",ct,"-a",ut],{timeout:5e3})}catch{}try{await Ot(["delete-key"])}catch{}}var pt=require("child_process"),ht=require("util"),_=l(require("fs")),yt=l(require("os")),H=l(require("path")),B=(0,ht.promisify)(pt.execFile),gt=H.join(yt.homedir(),".visa-mcp"),I=H.join(gt,"session-token"),q="visa-cli",U="session-token";async function Bt(){try{let{stdout:e}=await B("security",["find-generic-password","-s",q,"-a",U,"-w"],{timeout:5e3});return e.trim()||null}catch{return null}}async function dt(e){try{try{await B("security",["delete-generic-password","-s",q,"-a",U],{timeout:5e3})}catch{}return await B("security",["add-generic-password","-s",q,"-a",U,"-w",e],{timeout:5e3}),!0}catch{return!1}}async function qt(){try{await B("security",["delete-generic-password","-s",q,"-a",U],{timeout:5e3})}catch{}}var S=class{static async getSessionToken(){let t=await Bt();if(t)return t;try{let r=_.readFileSync(I,"utf-8").trim();if(r)return await dt(r),r}catch{}return null}static async saveSessionToken(t){if(await dt(t)){try{_.unlinkSync(I)}catch{}return}_.mkdirSync(gt,{recursive:!0,mode:448}),_.writeFileSync(I,t,{mode:384})}static async deleteSessionToken(){await qt();try{_.unlinkSync(I)}catch{}}static async clearAll(){await this.deleteSessionToken()}};var p=l(require("fs")),W=l(require("path")),ft=l(require("os")),K=W.join(ft.homedir(),".visa-mcp"),P=W.join(K,"mcp-server.log"),Ut=5*1024*1024,V=null;function Lt(){p.existsSync(K)||p.mkdirSync(K,{recursive:!0,mode:448})}function Ct(){if(!V){if(Lt(),p.existsSync(P)&&p.statSync(P).size>Ut){let t=P+".1";p.existsSync(t)&&p.unlinkSync(t),p.renameSync(P,t)}V=p.createWriteStream(P,{flags:"a"})}return V}function L(e,...t){let r=new Date().toISOString(),n=t.map(s=>typeof s=="string"?s:JSON.stringify(s,null,2)).join(" "),a=`[${r}] [${e}] ${n}
|
|
3
|
+
`;process.stderr.write(a),Ct().write(a)}var o={debug:(...e)=>L("DEBUG",...e),info:(...e)=>L("INFO",...e),warn:(...e)=>L("WARN",...e),error:(...e)=>L("ERROR",...e)};var R=l(require("crypto")),C=require("child_process"),wt=l(require("os")),Y=process.env.VISA_AUTH_URL||"https://auth.visacli.sh",d=new A(()=>S.getSessionToken());function w(e){if(!e||typeof e!="string"||!e.startsWith("http://")&&!e.startsWith("https://"))return;let t=wt.platform();t==="darwin"?(0,C.execFile)("open",[e]):t==="win32"?(0,C.execFile)("cmd",["/c","start","",e]):(0,C.execFile)("xdg-open",[e])}async function f(e,t,r,n){if(!G()){o.warn("attestation:unavailable",{context:e});return}o.info("attestation:attempt",{context:e,amount:t,merchant:r});try{let{nonce:a}=await d.getAttestationChallenge(),s=Buffer.from(JSON.stringify({nonce:a,amount:t,merchant:r,context:e})).toString("base64");o.info("touchid:prompt",{context:e,amount:t,merchant:r});let i=await mt(s,n);return o.info("attestation:success",{context:e,amount:t,merchant:r}),{signature:i,nonce:a,amount:t,merchant:r}}catch(a){throw o.error("attestation:failure",{context:e,amount:t,merchant:r,error:a.message}),a}}async function v(e,t){let r=await d.paymentPreview({tool:e,url:t});if(!r||!r.merchantName||!r.amount||r.amount<=0)throw new Error("Could not determine payment amount and merchant. Try again.");if(!Number.isFinite(r.amount)||r.amount<0||r.amount>999999)throw new Error(`Invalid payment amount: ${r.amount}. Payment rejected for safety.`);return r}function k(e){return`pay $${e.amount.toFixed(2)} to ${e.merchantName}`}async function Dt(e){let t=await v(void 0,e.url);o.info("payment:attempt",{tool:"pay",amount:t.amount,merchant:t.merchantName,url:e.url});try{let r=await f(e.url||"pay",t.amount,t.merchantName,k(t)),n=await d.pay({url:e.url||"",merchantName:e.merchantName||"Unknown",description:e.description||"",method:e.method,body:e.body,attestation:r,idempotencyKey:R.randomUUID()});return n.success?(o.info("payment:success",{tool:"pay",amount:t.amount,merchant:t.merchantName,rail:n.receipt?.rail}),n.receipt&&se(n.receipt)):o.warn("payment:declined",{tool:"pay",amount:t.amount,merchant:t.merchantName,message:n.message}),n}catch(r){throw o.error("payment:failure",{tool:"pay",amount:t.amount,merchant:t.merchantName,error:r.message}),r}}async function Mt(e){let t=await v("generate_image_card");o.info("payment:attempt",{tool:"generate_image_card",amount:t.amount,merchant:t.merchantName});try{let r=await f("generate_image_card",t.amount,t.merchantName,k(t)),n=await d.shortcut("generate_image_card",{...e,attestation:r},12e4);return o.info("payment:success",{tool:"generate_image_card",amount:t.amount,merchant:t.merchantName}),n.urls?.length&&n.urls.forEach(a=>w(a)),n}catch(r){throw o.error("payment:failure",{tool:"generate_image_card",amount:t.amount,merchant:t.merchantName,error:r.message}),r}}async function jt(e){let t=await v("generate_image_fast_card");o.info("payment:attempt",{tool:"generate_image_fast_card",amount:t.amount,merchant:t.merchantName});try{let r=await f("generate_image_fast_card",t.amount,t.merchantName,k(t)),n=await d.shortcut("generate_image_fast_card",{...e,attestation:r},6e4);return o.info("payment:success",{tool:"generate_image_fast_card",amount:t.amount,merchant:t.merchantName}),n.urls?.length&&n.urls.forEach(a=>w(a)),n}catch(r){throw o.error("payment:failure",{tool:"generate_image_fast_card",amount:t.amount,merchant:t.merchantName,error:r.message}),r}}async function Ft(e){let t=await v("generate_music_tempo_card");o.info("payment:attempt",{tool:"generate_music_tempo_card",amount:t.amount,merchant:t.merchantName});try{let r=await f("generate_music_tempo_card",t.amount,t.merchantName,k(t)),n=await d.shortcut("generate_music_tempo_card",{...e,attestation:r},36e4);return o.info("payment:success",{tool:"generate_music_tempo_card",amount:t.amount,merchant:t.merchantName}),n.urls?.length&&n.urls.forEach(a=>w(a)),n}catch(r){throw o.error("payment:failure",{tool:"generate_music_tempo_card",amount:t.amount,merchant:t.merchantName,error:r.message}),r}}async function Gt(e){let t=await v("check_music_status_tempo_card");o.info("payment:attempt",{tool:"check_music_status_tempo_card",amount:t.amount,merchant:t.merchantName});try{let r=await f("check_music_status_tempo_card",t.amount,t.merchantName,k(t)),n=await d.shortcut("check_music_status_tempo_card",{...e,attestation:r});return o.info("payment:success",{tool:"check_music_status_tempo_card",amount:t.amount,merchant:t.merchantName}),n.urls?.length&&n.urls.forEach(a=>w(a)),n}catch(r){throw o.error("payment:failure",{tool:"check_music_status_tempo_card",amount:t.amount,merchant:t.merchantName,error:r.message}),r}}async function Ht(e){let t=await v("query_onchain_prices_card");o.info("payment:attempt",{tool:"query_onchain_prices_card",amount:t.amount,merchant:t.merchantName});try{let r=await f("query_onchain_prices_card",t.amount,t.merchantName,k(t)),n=await d.shortcut("query_onchain_prices_card",{...e,attestation:r});return o.info("payment:success",{tool:"query_onchain_prices_card",amount:t.amount,merchant:t.merchantName}),n}catch(r){throw o.error("payment:failure",{tool:"query_onchain_prices_card",amount:t.amount,merchant:t.merchantName,error:r.message}),r}}var Vt=["generate_music_tempo_card"],Kt=36e4,Wt=12e4,Yt=2e3;async function Jt(e){let t=e.requests?.length||e.count||0;if(t===0)throw new Error("Batch requires at least one item. Please specify what to generate.");let r=await v(e.tool),n=r.amount*t;o.info("payment:attempt",{tool:"batch",batchTool:e.tool,count:t,totalAmount:n,merchant:r.merchantName});try{let a=`pay $${n.toFixed(2)} to ${r.merchantName} (${t} items, $${r.amount.toFixed(2)} each)`,s=await f(`batch:${e.tool}`,n,r.merchantName,a),i=e.requests||(e.count&&e.params?Array.from({length:e.count},()=>({...e.params})):[]),c=Vt.includes(e.tool)?Kt:Wt+t*Yt,u=await d.batch({tool:e.tool,requests:i,attestation:s,idempotencyKey:R.randomUUID()},c);return o.info("payment:success",{tool:"batch",batchTool:e.tool,count:t,totalAmount:n,merchant:r.merchantName}),u.results&&u.results.forEach(x=>{x.urls&&x.urls.forEach(b=>w(b))}),u}catch(a){throw o.error("payment:failure",{tool:"batch",batchTool:e.tool,count:t,totalAmount:n,merchant:r.merchantName,error:a.message}),a}}async function zt(){return await d.getStatus()}async function Zt(){let t=(await d.getStatus()).cards||[];return t.length===0?{cards:[],message:"No cards enrolled. Use the add_card tool to add a payment card."}:{cards:t}}async function Qt(){return await d.getTransactions()}async function te(e){if(!e.confirm)return{success:!1,message:"Please confirm by setting confirm: true to update spending controls."};o.info("spending_controls:update",{maxTransactionAmount:e.maxTransactionAmount,dailyLimit:e.dailyLimit});try{let t=await f("spending-controls",0,"","update spending controls"),r=await d.updateSpendingControls({maxTransactionAmount:e.maxTransactionAmount,dailyLimit:e.dailyLimit,confirm:!0,attestation:t});return o.info("spending_controls:success",{maxTransactionAmount:e.maxTransactionAmount,dailyLimit:e.dailyLimit}),r}catch(t){throw o.error("spending_controls:failure",{error:t.message}),t}}var _t=3e4,ee=3e5;async function $t(e){let t=R.randomBytes(16).toString("hex"),r=`${e}${e.includes("?")?"&":"?"}state=${t}`;w(r);let n=Date.now()+ee;for(;Date.now()<n;)try{let a=await fetch(`${Y}/v1/auth-status`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({state:t,timeout:_t}),signal:AbortSignal.timeout(_t+5e3)});if(!a.ok)continue;let s=await a.json();if(s.status==="pending")continue;if(s.status==="expired")return{success:!1,message:"Session expired. Please try again."};if(s.status==="error")return{success:!1,message:s.error||"Authentication failed. Please try again."};if(s.status==="complete"){if(s.sessionToken){await S.saveSessionToken(s.sessionToken);let c=s.user||"",u=s.last4||"****";return o.info("auth:login_complete",{user:c,last4:u}),{success:!0,message:`Signed in as ${c}. Card ending in ${u} added.`}}let i=s.last4||"****";return o.info("auth:card_added",{last4:i}),{success:!0,message:`Card ending in ${i} enrolled.`}}}catch{}return{success:!1,message:"Login timed out. Please try again."}}async function re(){return o.info("auth:login_attempt"),$t(`${Y}/login`)}async function ne(){return o.info("auth:add_card_attempt"),await S.getSessionToken()?$t(`${Y}/enroll`):{success:!1,message:"Not logged in. Please call login first."}}async function ae(e){if(!e.confirm)return{success:!1,message:"Please confirm by setting confirm: true to reset"};o.info("reset:attempt");let t=await f("reset",0,"","reset device and remove all credentials");try{await d.logout({attestation:t})}catch{}if(await S.clearAll(),G())try{await lt()}catch{}return o.info("reset:success"),{success:!0,message:"Device reset. All credentials, cards, and keys have been removed. Use the login tool to re-enroll."}}function se(e){let t=["url","resultUrl","imageUrl","audioUrl","trackUrl"];for(let r of t){let n=e[r];n&&typeof n=="string"&&n.startsWith("http")&&w(n)}Array.isArray(e.urls)&&e.urls.forEach(r=>{r&&typeof r=="string"&&r.startsWith("http")&&w(r)})}var y=class{static async getStatus(){return zt()}static async pay(t){return Dt(t)}static async addCard(){return ne()}static async getCards(){return Zt()}static async transactionHistory(){return Qt()}static async updateSpendingControls(t){return te(t)}static async reset(t){return ae(t)}static async login(){return re()}static async batch(t){return Jt(t)}static async shortcut(t,r){switch(t){case"generate_image_card":return Mt(r);case"generate_image_fast_card":return jt(r);case"generate_music_tempo_card":return Ft(r);case"check_music_status_tempo_card":return Gt(r);case"query_onchain_prices_card":return Ht(r);default:{o.info("payment:attempt",{tool:t});try{let n=await f(t,0,""),a=await d.shortcut(t,{...r,attestation:n});return o.info("payment:success",{tool:t}),a.urls?.length&&a.urls.forEach(s=>w(s)),a}catch(n){throw o.error("payment:failure",{tool:t,error:n.message}),n}}}}};var D=l(require("fs")),bt=l(require("path")),Tt=l(require("os"));var J=bt.join(Tt.homedir(),".visa-mcp"),X=class{static ensureConfigDir(){D.existsSync(J)||D.mkdirSync(J,{recursive:!0,mode:448})}static getConfigDir(){return J}static TOOL_STATES={login:!0,get_status:!0,get_cards:!0,add_card:!0,pay:!0,transaction_history:!0,update_spending_controls:!0,enroll_device:!1,verify_otp:!1,reset:!0,batch:!0,generate_image:!1,generate_music:!1,generate_x402_image:!1,browser_launch:!1,browser_navigate:!1,browser_snapshot:!1,browser_click:!1,browser_type:!1,browser_scroll:!1,generate_music_card:!1,generate_image_card:!0,query_onchain_prices_card:!0,generate_music_tempo_card:!0,check_music_status_tempo_card:!0};static loadToolStates(){return{...this.TOOL_STATES}}static getDisabledTools(){let t=new Set;for(let[r,n]of Object.entries(this.TOOL_STATES))n||t.add(r);return t}static isToolDisabled(t){return this.TOOL_STATES[t]===!1}};var E=3e3,$=0,oe={user_context:{type:"string",description:"The exact verbatim request the user made that prompted this tool call. Always include the user's full original message."}};function h(e){return{...e,properties:{...e.properties,...oe},required:[...e.required||[],"user_context"]}}var z=new St.Server({name:"visa-cli",version:"1.0.0"},{capabilities:{tools:{}},instructions:"After any successful payment tool call, the response will contain a _visa_receipt field with a Visa logo made of emoji characters. You MUST display this receipt exactly as provided in your response to the user, preserving all emoji characters and line breaks. Never omit or summarize it."});z.setRequestHandler(M.ListToolsRequestSchema,async()=>({tools:[{name:"get_status",description:"Get enrollment status, enrolled cards, available payment rails, and spending controls for Visa CLI",inputSchema:h({type:"object",properties:{},required:[]})},{name:"add_card",description:"Open a browser window for the user to securely add a payment card via VGS Collect. Replaces any previously enrolled card. Returns immediately \u2014 the card is NOT enrolled until the user completes the form in the browser. After calling this tool, ask the user to confirm when finished, then call get_status to verify. Free, no Touch ID.",inputSchema:h({type:"object",properties:{},required:[]})},{name:"pay",description:"Execute a payment to a merchant URL. The payment amount and rail are auto-detected from the merchant's HTTP 402 response. The user will see a Touch ID prompt showing the exact amount and merchant before approving. If they cancel Touch ID, the payment is aborted.",inputSchema:h({type:"object",properties:{url:{type:"string",description:"The merchant's payment endpoint URL. The payment amount and rail are auto-detected from the merchant's HTTP 402 response."},merchantName:{type:"string",description:"Name of the merchant. Optional \u2014 auto-detected from the payment challenge if omitted."},description:{type:"string",description:"Description of the purchase. Optional \u2014 auto-detected if omitted."},method:{type:"string",enum:["GET","POST"],description:"HTTP method for the merchant request. Default: GET."},body:{type:"string",description:"JSON string request body for POST endpoints."}},required:["url"]})},{name:"get_cards",description:"List enrolled cards (masked, showing only last 4 digits)",inputSchema:h({type:"object",properties:{},required:[]})},{name:"transaction_history",description:"Retrieve payment transaction history. Returns past transactions with amount, merchant, date, status, and any generated media URLs. Free, no Touch ID.",inputSchema:h({type:"object",properties:{},required:[]})},{name:"update_spending_controls",description:"Set spending limits and security preferences. All amounts in USD. Requires confirm: true and biometric verification (Touch ID) before changes are applied. Touch ID is always required for every payment \u2014 this cannot be changed.",inputSchema:h({type:"object",properties:{confirm:{type:"boolean",description:"Must be true to confirm the change. Required."},maxTransactionAmount:{type:"number",description:"Maximum amount per transaction (hard limit, always enforced)"},dailyLimit:{type:"number",description:"Maximum total spending per day (hard limit, always enforced)"}},required:["confirm"]})},{name:"reset",description:"Reset device: clear enrollment and credentials. Requires confirm: true.",inputSchema:h({type:"object",properties:{confirm:{type:"boolean",description:"Must be true to confirm reset"}},required:["confirm"]})},{name:"login",description:"Open a browser window for GitHub OAuth authentication. Returns immediately \u2014 authentication is NOT complete until the user finishes in the browser. After calling this tool, ask the user to confirm when finished, then call get_status to verify the session is active. Free, no Touch ID.",inputSchema:h({type:"object",properties:{},required:[]})},{name:"generate_image_card",description:"Generate an AI image (Ultra tier). FLUX1.1 [pro] ultra \u2014 $0.06, 2K resolution, ~30s. Do NOT call this tool without first asking the user which image tier they want. There are two tiers available and the user must choose.",inputSchema:h({type:"object",properties:{prompt:{type:"string",description:"Text description of the image to generate"},aspect_ratio:{type:"string",enum:["21:9","16:9","3:2","5:4","1:1","4:5","2:3","9:16","9:21"],description:"Output aspect ratio. Default: 16:9"}},required:["prompt"]})},{name:"generate_image_fast_card",description:"Generate an AI image (Pro tier). FLUX1.1 [pro] \u2014 $0.04, 1K resolution, ~10s. Do NOT call this tool without first asking the user which image tier they want. There are two tiers available and the user must choose.",inputSchema:h({type:"object",properties:{prompt:{type:"string",description:"Text description of the image to generate"},aspect_ratio:{type:"string",enum:["21:9","16:9","3:2","5:4","1:1","4:5","2:3","9:16","9:21"],description:"Output aspect ratio. Default: 16:9"}},required:["prompt"]})},{name:"generate_music_tempo_card",description:"Generate a music track using Suno AI via Tempo. Costs ~$0.10, paid with your enrolled card. Requires Touch ID approval. Music generation takes ~2 minutes \u2014 returns a task ID to poll with check_music_status_tempo_card.",inputSchema:h({type:"object",properties:{prompt:{type:"string",description:"Text description of the music to generate"},model:{type:"string",enum:["V4","V4_5","V4_5ALL","V4_5PLUS","V5"],description:"Suno model version. Default: V4."},instrumental:{type:"boolean",description:"Generate instrumental music with no vocals. Default: false"}},required:["prompt"]})},{name:"check_music_status_tempo_card",description:"Check the status of a Suno music generation and retrieve audio URLs when complete. Costs ~$0.01 per check, paid with your enrolled card. Requires Touch ID approval. Do not poll more than once per minute.",inputSchema:h({type:"object",properties:{taskId:{type:"string",description:"The task ID returned from generate_music_tempo_card"}},required:["taskId"]})},{name:"query_onchain_prices_card",description:"Query token prices from 150+ blockchains via Allium. Returns real-time prices by default. For historical prices, provide start_timestamp and end_timestamp (ISO 8601). Costs ~$0.02 per query, paid with your enrolled card. Requires Touch ID approval.",inputSchema:h({type:"object",properties:{chain:{type:"string",description:"Blockchain network (e.g. ethereum, solana, base, polygon, arbitrum)"},token_address:{type:"string",description:"Token contract address on the specified chain"},start_timestamp:{type:"string",description:'Start time for historical prices (ISO 8601, e.g. "2025-03-01T00:00:00Z"). Omit for real-time.'},end_timestamp:{type:"string",description:'End time for historical prices (ISO 8601, e.g. "2025-03-02T00:00:00Z"). Omit for real-time.'},time_granularity:{type:"string",description:'Time granularity for historical data (e.g. "1h", "1d", "1w"). Default: "1d".'}},required:["chain","token_address"]})},{name:"batch",description:"Execute a paid tool multiple times in parallel with a single Touch ID approval for the full batch. Cost is per-item price x count (e.g. 5 images at ~$0.12 = ~$0.60). The total is shown in the Touch ID prompt.",inputSchema:h({type:"object",properties:{tool:{type:"string",enum:["generate_image_card","generate_image_fast_card","generate_music_tempo_card","query_onchain_prices_card"],description:"The paid tool to execute in batch."},count:{type:"number",description:"Number of times to run with identical params. Use with params."},params:{type:"object",description:"Params shared by all runs when using count."},requests:{type:"array",description:"Array of param objects for varied runs (e.g. different prompts).",items:{type:"object"}}},required:["tool"]})}].filter(e=>!X.isToolDisabled(e.name))}));function ie(e,t,r){let i=["\u{1F7E6}\u{1F7E6}\u{1F7E6}\u{1F7E6}\u{1F7E6}\u{1F7E6}\u{1F7E6}\u2B1B\u2B1B\u{1F7E6}\u2B1B\u2B1B\u2B1B\u{1F7E6}\u2B1B\u{1F7E6}\u{1F7E6}\u{1F7E6}\u2B1B\u{1F7E6}\u{1F7E6}\u{1F7E6}\u{1F7E6}\u2B1B\u2B1B\u{1F7E6}\u{1F7E6}\u2B1B","\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u{1F7E6}\u2B1B\u2B1B\u2B1B\u{1F7E6}\u2B1B\u2B1B\u{1F7E6}\u2B1B\u2B1B\u{1F7E6}\u2B1B\u2B1B\u2B1B\u2B1B\u{1F7E6}\u2B1B\u2B1B\u{1F7E6}","\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u{1F7E6}\u2B1B\u{1F7E6}\u2B1B\u2B1B\u2B1B\u{1F7E6}\u2B1B\u2B1B\u{1F7E6}\u{1F7E6}\u{1F7E6}\u{1F7E6}\u2B1B\u{1F7E6}\u{1F7E6}\u{1F7E6}\u{1F7E6}","\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u{1F7E6}\u2B1B\u{1F7E6}\u2B1B\u2B1B\u2B1B\u{1F7E6}\u2B1B\u2B1B\u2B1B\u2B1B\u2B1B\u{1F7E6}\u2B1B\u{1F7E6}\u2B1B\u2B1B\u{1F7E6}","\u{1F7E8}\u{1F7E8}\u{1F7E8}\u{1F7E8}\u{1F7E8}\u{1F7E8}\u{1F7E8}\u2B1B\u2B1B\u2B1B\u2B1B\u{1F7E6}\u2B1B\u2B1B\u2B1B\u{1F7E6}\u{1F7E6}\u{1F7E6}\u2B1B\u{1F7E6}\u{1F7E6}\u{1F7E6}\u{1F7E6}\u2B1B\u{1F7E6}\u2B1B\u2B1B\u{1F7E6}","",`Payment complete $${e.toFixed(2)} \u2192 ${t}`];if(r&&r.length>0){i.push("");for(let c of r)i.push(c)}return i.join(`
|
|
4
|
+
`)}function ce(e){return["pay","generate_image_card","generate_image_fast_card","generate_music_tempo_card","check_music_status_tempo_card","query_onchain_prices_card","batch"].includes(e)}function ue(e){if(!e)return{userPrompt:"",cleanArgs:{}};let{user_context:t,...r}=e;return{userPrompt:typeof t=="string"?t:"",cleanArgs:r}}z.setRequestHandler(M.CallToolRequestSchema,async e=>{let{name:t,arguments:r}=e.params,{cleanArgs:n}=ue(r);try{if(X.isToolDisabled(t))return{content:[{type:"text",text:`The "${t}" tool is currently disabled.`}],isError:!0};let a;switch(t){case"get_status":a=await y.getStatus();break;case"add_card":a=await y.addCard();break;case"pay":{let s=Date.now();if(s-$<E){let i=E-(s-$);return{content:[{type:"text",text:`Rate limited. Please wait ${Math.ceil(i/1e3)} second(s) between payments.`}],isError:!0}}a=await y.pay(n),$=Date.now();break}case"get_cards":a=await y.getCards();break;case"transaction_history":a=await y.transactionHistory();break;case"update_spending_controls":a=await y.updateSpendingControls(n);break;case"reset":a=await y.reset(n);break;case"login":a=await y.login();break;case"generate_image_card":case"generate_image_fast_card":case"generate_music_tempo_card":case"check_music_status_tempo_card":case"query_onchain_prices_card":{let s=Date.now();if(s-$<E){let i=E-(s-$);return{content:[{type:"text",text:`Rate limited. Please wait ${Math.ceil(i/1e3)} second(s) between payments.`}],isError:!0}}a=await y.shortcut(t,n),$=Date.now();break}case"batch":{let s=Date.now();if(s-$<E){let i=E-(s-$);return{content:[{type:"text",text:`Rate limited. Please wait ${Math.ceil(i/1e3)} second(s) between payments.`}],isError:!0}}a=await y.batch(n),$=Date.now();break}default:return{content:[{type:"text",text:`Unknown tool: ${t}`}],isError:!0}}if(ce(t)&&a?.success){let s=a.amount??a.totalCharged??0,i=a.merchantName??t,c=a.urls||[];if(a.results&&Array.isArray(a.results))for(let u of a.results)u.urls&&(c=c.concat(u.urls));a._visa_receipt=ie(s,i,c.length>0?c:void 0)}return{content:[{type:"text",text:JSON.stringify(a,null,2)}]}}catch(a){return{content:[{type:"text",text:a.message||"Tool execution failed"}],isError:!0}}});async function me(){let e=new vt.StdioServerTransport;await z.connect(e),o.info("Visa CLI Server running on stdio")}me().catch(e=>{o.error("Server error:",e),process.exit(1)});
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
#import <Security/Security.h>
|
|
3
|
+
#import <LocalAuthentication/LocalAuthentication.h>
|
|
4
|
+
|
|
5
|
+
// SPKI DER header for P-256 (prime256v1) uncompressed public key
|
|
6
|
+
static const unsigned char SPKI_HEADER[] = {
|
|
7
|
+
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86,
|
|
8
|
+
0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A,
|
|
9
|
+
0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03,
|
|
10
|
+
0x42, 0x00
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
static NSString *kService = @"visa-cli";
|
|
14
|
+
static NSString *kKeyAcct = @"attestation-key";
|
|
15
|
+
static NSString *kTokenAcct = @"session-token";
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Keychain helpers for generic password items
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
static OSStatus storeItem(NSString *account, NSData *data) {
|
|
22
|
+
NSDictionary *delQ = @{
|
|
23
|
+
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
|
24
|
+
(__bridge id)kSecAttrService: kService,
|
|
25
|
+
(__bridge id)kSecAttrAccount: account,
|
|
26
|
+
};
|
|
27
|
+
SecItemDelete((__bridge CFDictionaryRef)delQ);
|
|
28
|
+
|
|
29
|
+
NSDictionary *addQ = @{
|
|
30
|
+
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
|
31
|
+
(__bridge id)kSecAttrService: kService,
|
|
32
|
+
(__bridge id)kSecAttrAccount: account,
|
|
33
|
+
(__bridge id)kSecValueData: data,
|
|
34
|
+
};
|
|
35
|
+
return SecItemAdd((__bridge CFDictionaryRef)addQ, NULL);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static NSData *retrieveItem(NSString *account) {
|
|
39
|
+
NSDictionary *q = @{
|
|
40
|
+
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
|
41
|
+
(__bridge id)kSecAttrService: kService,
|
|
42
|
+
(__bridge id)kSecAttrAccount: account,
|
|
43
|
+
(__bridge id)kSecReturnData: @YES,
|
|
44
|
+
};
|
|
45
|
+
CFTypeRef result = NULL;
|
|
46
|
+
OSStatus st = SecItemCopyMatching((__bridge CFDictionaryRef)q, &result);
|
|
47
|
+
if (st != errSecSuccess) return nil;
|
|
48
|
+
return (__bridge NSData *)result;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static void deleteItem(NSString *account) {
|
|
52
|
+
NSDictionary *q = @{
|
|
53
|
+
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
|
54
|
+
(__bridge id)kSecAttrService: kService,
|
|
55
|
+
(__bridge id)kSecAttrAccount: account,
|
|
56
|
+
};
|
|
57
|
+
SecItemDelete((__bridge CFDictionaryRef)q);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Key operations — raw EC key bytes stored as Keychain generic password,
|
|
62
|
+
// imported as in-memory SecKeyRef for signing (avoids CDSA limitations)
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
int cmd_generate_key(void) {
|
|
66
|
+
// Generate P-256 key pair in memory
|
|
67
|
+
NSDictionary *attrs = @{
|
|
68
|
+
(__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeECSECPrimeRandom,
|
|
69
|
+
(__bridge id)kSecAttrKeySizeInBits: @256,
|
|
70
|
+
(__bridge id)kSecAttrIsPermanent: @NO,
|
|
71
|
+
};
|
|
72
|
+
CFErrorRef keyErr = NULL;
|
|
73
|
+
SecKeyRef privKey = SecKeyCreateRandomKey((__bridge CFDictionaryRef)attrs, &keyErr);
|
|
74
|
+
if (!privKey) {
|
|
75
|
+
if (keyErr) CFRelease(keyErr);
|
|
76
|
+
printf("ERROR:Key generation failed\n");
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Export raw private key bytes (caller stores via `security` CLI)
|
|
81
|
+
CFErrorRef expErr = NULL;
|
|
82
|
+
CFDataRef rawPriv = SecKeyCopyExternalRepresentation(privKey, &expErr);
|
|
83
|
+
if (!rawPriv) {
|
|
84
|
+
CFRelease(privKey);
|
|
85
|
+
if (expErr) CFRelease(expErr);
|
|
86
|
+
printf("ERROR:Failed to export private key\n");
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
NSString *privB64 = [(__bridge NSData *)rawPriv base64EncodedStringWithOptions:0];
|
|
90
|
+
CFRelease(rawPriv);
|
|
91
|
+
|
|
92
|
+
// Export public key in SPKI DER format
|
|
93
|
+
SecKeyRef pubKey = SecKeyCopyPublicKey(privKey);
|
|
94
|
+
CFRelease(privKey);
|
|
95
|
+
if (!pubKey) { printf("ERROR:Failed to get public key\n"); return 1; }
|
|
96
|
+
|
|
97
|
+
CFErrorRef pubErr = NULL;
|
|
98
|
+
CFDataRef rawPub = SecKeyCopyExternalRepresentation(pubKey, &pubErr);
|
|
99
|
+
CFRelease(pubKey);
|
|
100
|
+
if (!rawPub) {
|
|
101
|
+
if (pubErr) CFRelease(pubErr);
|
|
102
|
+
printf("ERROR:Failed to export public key\n");
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
NSMutableData *spki = [NSMutableData dataWithBytes:SPKI_HEADER length:sizeof(SPKI_HEADER)];
|
|
107
|
+
[spki appendData:(__bridge NSData *)rawPub];
|
|
108
|
+
CFRelease(rawPub);
|
|
109
|
+
|
|
110
|
+
// Output both keys: OK:<private-b64>:<public-spki-b64>
|
|
111
|
+
printf("OK:%s:%s\n", privB64.UTF8String,
|
|
112
|
+
[spki base64EncodedStringWithOptions:0].UTF8String);
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
int cmd_sign(const char *challenge, const char *reason) {
|
|
117
|
+
// Read base64-encoded private key from stdin (avoids Keychain ACL issues
|
|
118
|
+
// and keeps the key out of the process argument list visible in `ps`)
|
|
119
|
+
NSFileHandle *input = [NSFileHandle fileHandleWithStandardInput];
|
|
120
|
+
NSData *stdinData = [input readDataToEndOfFile];
|
|
121
|
+
NSString *keyB64 = [[NSString alloc] initWithData:stdinData encoding:NSUTF8StringEncoding];
|
|
122
|
+
keyB64 = [keyB64 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
123
|
+
|
|
124
|
+
if (!keyB64 || keyB64.length == 0) {
|
|
125
|
+
printf("ERROR:No key provided on stdin\n");
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
NSData *rawKey = [[NSData alloc] initWithBase64EncodedString:keyB64 options:0];
|
|
130
|
+
if (!rawKey) {
|
|
131
|
+
printf("ERROR:Invalid base64 key data\n");
|
|
132
|
+
return 1;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Touch ID gate
|
|
136
|
+
LAContext *ctx = [[LAContext alloc] init];
|
|
137
|
+
NSString *reasonStr = reason
|
|
138
|
+
? [NSString stringWithUTF8String:reason]
|
|
139
|
+
: @"approve payment";
|
|
140
|
+
|
|
141
|
+
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
|
|
142
|
+
__block BOOL authOk = NO;
|
|
143
|
+
[ctx evaluatePolicy:LAPolicyDeviceOwnerAuthentication
|
|
144
|
+
localizedReason:reasonStr
|
|
145
|
+
reply:^(BOOL success, NSError *err) {
|
|
146
|
+
authOk = success;
|
|
147
|
+
dispatch_semaphore_signal(sem);
|
|
148
|
+
}];
|
|
149
|
+
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
|
150
|
+
|
|
151
|
+
if (!authOk) {
|
|
152
|
+
printf("ERROR:Authentication cancelled by user\n");
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Import as in-memory SecKeyRef (modern API, not CDSA)
|
|
157
|
+
NSDictionary *keyAttrs = @{
|
|
158
|
+
(__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeECSECPrimeRandom,
|
|
159
|
+
(__bridge id)kSecAttrKeyClass: (__bridge id)kSecAttrKeyClassPrivate,
|
|
160
|
+
(__bridge id)kSecAttrKeySizeInBits: @256,
|
|
161
|
+
};
|
|
162
|
+
CFErrorRef importErr = NULL;
|
|
163
|
+
SecKeyRef privKey = SecKeyCreateWithData(
|
|
164
|
+
(__bridge CFDataRef)rawKey,
|
|
165
|
+
(__bridge CFDictionaryRef)keyAttrs,
|
|
166
|
+
&importErr);
|
|
167
|
+
if (!privKey) {
|
|
168
|
+
if (importErr) CFRelease(importErr);
|
|
169
|
+
printf("ERROR:Failed to import key for signing\n");
|
|
170
|
+
return 1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Sign (ECDSA-SHA256, same format as Node.js crypto.createSign('SHA256'))
|
|
174
|
+
NSData *data = [[NSString stringWithUTF8String:challenge] dataUsingEncoding:NSUTF8StringEncoding];
|
|
175
|
+
CFErrorRef sigErr = NULL;
|
|
176
|
+
CFDataRef sig = SecKeyCreateSignature(
|
|
177
|
+
privKey,
|
|
178
|
+
kSecKeyAlgorithmECDSASignatureMessageX962SHA256,
|
|
179
|
+
(__bridge CFDataRef)data,
|
|
180
|
+
&sigErr);
|
|
181
|
+
CFRelease(privKey);
|
|
182
|
+
|
|
183
|
+
if (!sig) {
|
|
184
|
+
if (sigErr) CFRelease(sigErr);
|
|
185
|
+
printf("ERROR:Signing failed\n");
|
|
186
|
+
return 1;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
printf("OK:%s\n", [(__bridge NSData *)sig base64EncodedStringWithOptions:0].UTF8String);
|
|
190
|
+
CFRelease(sig);
|
|
191
|
+
return 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
int cmd_delete_key(void) {
|
|
195
|
+
// Legacy: also clean up any old keychain item from previous versions
|
|
196
|
+
deleteItem(kKeyAcct);
|
|
197
|
+
printf("OK\n");
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// Session token operations
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
int cmd_store_token(const char *token) {
|
|
206
|
+
NSData *val = [[NSString stringWithUTF8String:token] dataUsingEncoding:NSUTF8StringEncoding];
|
|
207
|
+
OSStatus st = storeItem(kTokenAcct, val);
|
|
208
|
+
if (st != errSecSuccess) {
|
|
209
|
+
printf("ERROR:Failed to store token (status %d)\n", (int)st);
|
|
210
|
+
return 1;
|
|
211
|
+
}
|
|
212
|
+
printf("OK\n");
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
int cmd_get_token(void) {
|
|
217
|
+
NSData *d = retrieveItem(kTokenAcct);
|
|
218
|
+
if (!d) {
|
|
219
|
+
printf("ERROR:not_found\n");
|
|
220
|
+
return 1;
|
|
221
|
+
}
|
|
222
|
+
NSString *tok = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding];
|
|
223
|
+
printf("OK:%s\n", tok.UTF8String);
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
int cmd_delete_token(void) {
|
|
228
|
+
deleteItem(kTokenAcct);
|
|
229
|
+
printf("OK\n");
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
// Check
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
int cmd_check(void) {
|
|
238
|
+
LAContext *ctx = [[LAContext alloc] init];
|
|
239
|
+
NSError *err = nil;
|
|
240
|
+
if (![ctx canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&err]) {
|
|
241
|
+
printf("ERROR:Touch ID unavailable\n");
|
|
242
|
+
return 1;
|
|
243
|
+
}
|
|
244
|
+
printf("OK\n");
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// Main
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
int main(int argc, const char *argv[]) {
|
|
253
|
+
@autoreleasepool {
|
|
254
|
+
if (argc < 2) {
|
|
255
|
+
fprintf(stderr, "Usage: visa-keychain <command> [args...]\n");
|
|
256
|
+
return 1;
|
|
257
|
+
}
|
|
258
|
+
const char *cmd = argv[1];
|
|
259
|
+
if (strcmp(cmd, "generate-key") == 0) return cmd_generate_key();
|
|
260
|
+
else if (strcmp(cmd, "sign") == 0) {
|
|
261
|
+
if (argc < 3) { fprintf(stderr, "Usage: visa-keychain sign <challenge> [reason]\n"); return 1; }
|
|
262
|
+
return cmd_sign(argv[2], argc >= 4 ? argv[3] : NULL);
|
|
263
|
+
}
|
|
264
|
+
else if (strcmp(cmd, "delete-key") == 0) return cmd_delete_key();
|
|
265
|
+
else if (strcmp(cmd, "store-token") == 0) {
|
|
266
|
+
if (argc < 3) { fprintf(stderr, "Usage: visa-keychain store-token <token>\n"); return 1; }
|
|
267
|
+
return cmd_store_token(argv[2]);
|
|
268
|
+
}
|
|
269
|
+
else if (strcmp(cmd, "get-token") == 0) return cmd_get_token();
|
|
270
|
+
else if (strcmp(cmd, "delete-token") == 0) return cmd_delete_token();
|
|
271
|
+
else if (strcmp(cmd, "check") == 0) return cmd_check();
|
|
272
|
+
else { fprintf(stderr, "Unknown command: %s\n", cmd); return 1; }
|
|
273
|
+
}
|
|
274
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "visa-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-powered payments for Claude Code",
|
|
5
|
+
"bin": {
|
|
6
|
+
"visa-cli": "./bin/visa-cli.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc --noEmit && node esbuild.config.js",
|
|
10
|
+
"dev": "tsc --watch",
|
|
11
|
+
"start": "node dist/mcp-server/index.js",
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"test:smoke": "VISA_AUTH_URL=https://auth.visacli.sh jest tests/smoke.test.ts --testTimeout=30000 --testPathIgnorePatterns='[]'",
|
|
14
|
+
"test:integration": "jest --config jest.integration.config.js",
|
|
15
|
+
"prepare": "husky",
|
|
16
|
+
"prepublishOnly": "npm run build && npm test",
|
|
17
|
+
"lint": "eslint src/**/*.ts",
|
|
18
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
19
|
+
"format:check": "prettier --check \"src/**/*.ts\""
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"visa",
|
|
23
|
+
"checkout",
|
|
24
|
+
"mcp",
|
|
25
|
+
"ai-agent",
|
|
26
|
+
"payments",
|
|
27
|
+
"click-to-pay",
|
|
28
|
+
"usdc",
|
|
29
|
+
"stablecoin"
|
|
30
|
+
],
|
|
31
|
+
"author": "Visa Crypto Labs",
|
|
32
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
35
|
+
"commander": "^12.1.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/jest": "^30.0.0",
|
|
39
|
+
"@types/node": "^22.10.0",
|
|
40
|
+
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
41
|
+
"@typescript-eslint/parser": "^8.56.1",
|
|
42
|
+
"esbuild": "^0.27.4",
|
|
43
|
+
"eslint": "^10.0.2",
|
|
44
|
+
"eslint-config-prettier": "^10.1.8",
|
|
45
|
+
"husky": "^9.1.7",
|
|
46
|
+
"jest": "^29.7.0",
|
|
47
|
+
"prettier": "^3.8.1",
|
|
48
|
+
"ts-jest": "^29.2.0",
|
|
49
|
+
"typescript": "^5.7.0"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18.0.0"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"bin/visa-cli.js",
|
|
56
|
+
"dist/",
|
|
57
|
+
"native/visa-keychain.m",
|
|
58
|
+
"README.md",
|
|
59
|
+
"LICENSE"
|
|
60
|
+
]
|
|
61
|
+
}
|