@visa/cli 2.7.0-rc.2 → 2.7.0-rc.4

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.
@@ -0,0 +1,42 @@
1
+ @echo off
2
+ REM build-win.bat — compile visa-keychain-win.exe
3
+ REM Requires Visual Studio Build Tools 2019+ (cl.exe in PATH)
4
+ REM Invoke from Developer Command Prompt or after running vcvarsall.bat
5
+ REM
6
+ REM Usage:
7
+ REM build-win.bat (builds to .\bin\win32-x64\)
8
+ REM build-win.bat --ci (also computes SHA-256 hash file)
9
+
10
+ setlocal enabledelayedexpansion
11
+
12
+ set "SRC=%~dp0visa-keychain-win.cpp"
13
+ set "OUT_DIR=%~dp0bin\win32-x64"
14
+ set "OUT=%OUT_DIR%\visa-keychain-win.exe"
15
+
16
+ if not exist "%OUT_DIR%" mkdir "%OUT_DIR%"
17
+
18
+ echo [visa-cli] Compiling visa-keychain-win.exe...
19
+
20
+ cl /nologo /EHsc /std:c++17 /O2 /DNDEBUG /W3 ^
21
+ "%SRC%" ^
22
+ /Fe:"%OUT%" ^
23
+ /link /SUBSYSTEM:CONSOLE bcrypt.lib ncrypt.lib ole32.lib
24
+
25
+ if %ERRORLEVEL% neq 0 (
26
+ echo ERROR: Compilation failed. Ensure Visual Studio Build Tools are installed.
27
+ echo Install from: https://visualstudio.microsoft.com/visual-cpp-build-tools/
28
+ exit /b 1
29
+ )
30
+
31
+ REM Clean up intermediate files
32
+ if exist "%~dp0visa-keychain-win.obj" del "%~dp0visa-keychain-win.obj"
33
+
34
+ echo [visa-cli] Built: %OUT%
35
+
36
+ REM Generate SHA-256 hash for integrity verification
37
+ if "%1"=="--ci" (
38
+ certutil -hashfile "%OUT%" SHA256 | findstr /v ":" > "%OUT%.sha256"
39
+ echo [visa-cli] Hash: %OUT%.sha256
40
+ )
41
+
42
+ echo [visa-cli] Done.
@@ -0,0 +1,481 @@
1
+ /**
2
+ * visa-keychain-win.cpp — Windows Hello attestation for Visa CLI.
3
+ *
4
+ * Mirrors the macOS visa-keychain.m CLI contract:
5
+ * generate-key → creates P-256 key in CNG store, returns SPKI DER public key
6
+ * public-key → returns SPKI DER public key for stored key
7
+ * sign <chal> → Windows Hello prompt, ECDSA-SHA256 signature
8
+ * delete-key → removes stored key
9
+ * check → verifies Windows Hello availability
10
+ *
11
+ * Build:
12
+ * cl /EHsc /std:c++17 /O2 visa-keychain-win.cpp
13
+ * /Fe:visa-keychain-win.exe /link bcrypt.lib ncrypt.lib ole32.lib
14
+ *
15
+ * Key storage: CNG persisted key (TPM-backed if available, software fallback).
16
+ * The private key never leaves the CNG store — signing happens in-place.
17
+ * Windows Hello provides user-presence verification (fingerprint/face/PIN).
18
+ */
19
+
20
+ #ifndef UNICODE
21
+ #define UNICODE
22
+ #endif
23
+ #ifndef _UNICODE
24
+ #define _UNICODE
25
+ #endif
26
+
27
+ #include <windows.h>
28
+ #include <bcrypt.h>
29
+ #include <ncrypt.h>
30
+ #include <wincred.h>
31
+ #include <stdio.h>
32
+ #include <stdlib.h>
33
+ #include <string.h>
34
+ #include <string>
35
+ #include <vector>
36
+
37
+ #pragma comment(lib, "bcrypt.lib")
38
+ #pragma comment(lib, "ncrypt.lib")
39
+ #pragma comment(lib, "ole32.lib")
40
+
41
+ static const wchar_t* KEY_NAME = L"visa-cli-attestation";
42
+
43
+ // P-256 SPKI DER header (26 bytes) — wraps a 65-byte uncompressed EC point
44
+ static const unsigned char SPKI_HEADER[] = {
45
+ 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86,
46
+ 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A,
47
+ 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03,
48
+ 0x42, 0x00
49
+ };
50
+
51
+ // ── Base64 encode/decode ────────────────────────────────────────────────────
52
+
53
+ static const char B64_TABLE[] =
54
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
55
+
56
+ std::string base64_encode(const unsigned char* data, size_t len) {
57
+ std::string out;
58
+ out.reserve(((len + 2) / 3) * 4);
59
+ for (size_t i = 0; i < len; i += 3) {
60
+ unsigned int n = ((unsigned int)data[i]) << 16;
61
+ if (i + 1 < len) n |= ((unsigned int)data[i + 1]) << 8;
62
+ if (i + 2 < len) n |= (unsigned int)data[i + 2];
63
+ out.push_back(B64_TABLE[(n >> 18) & 0x3F]);
64
+ out.push_back(B64_TABLE[(n >> 12) & 0x3F]);
65
+ out.push_back((i + 1 < len) ? B64_TABLE[(n >> 6) & 0x3F] : '=');
66
+ out.push_back((i + 2 < len) ? B64_TABLE[n & 0x3F] : '=');
67
+ }
68
+ return out;
69
+ }
70
+
71
+ static int b64_decode_char(char c) {
72
+ if (c >= 'A' && c <= 'Z') return c - 'A';
73
+ if (c >= 'a' && c <= 'z') return c - 'a' + 26;
74
+ if (c >= '0' && c <= '9') return c - '0' + 52;
75
+ if (c == '+') return 62;
76
+ if (c == '/') return 63;
77
+ return -1;
78
+ }
79
+
80
+ std::vector<unsigned char> base64_decode(const char* input) {
81
+ std::vector<unsigned char> out;
82
+ size_t len = strlen(input);
83
+ if (len == 0) return out;
84
+ out.reserve((len / 4) * 3);
85
+ for (size_t i = 0; i < len; i += 4) {
86
+ int a = b64_decode_char(input[i]);
87
+ int b = (i + 1 < len) ? b64_decode_char(input[i + 1]) : 0;
88
+ int c = (i + 2 < len) ? b64_decode_char(input[i + 2]) : 0;
89
+ int d = (i + 3 < len) ? b64_decode_char(input[i + 3]) : 0;
90
+ if (a < 0) a = 0;
91
+ if (b < 0) b = 0;
92
+ unsigned int triple = (a << 18) | (b << 12) | (c << 6) | d;
93
+ out.push_back((triple >> 16) & 0xFF);
94
+ if (i + 2 < len && input[i + 2] != '=')
95
+ out.push_back((triple >> 8) & 0xFF);
96
+ if (i + 3 < len && input[i + 3] != '=')
97
+ out.push_back(triple & 0xFF);
98
+ }
99
+ return out;
100
+ }
101
+
102
+ // ── CNG helpers ─────────────────────────────────────────────────────────────
103
+
104
+ /**
105
+ * Open the best available key storage provider.
106
+ * Tries TPM (MS_PLATFORM_CRYPTO_PROVIDER) first, falls back to software.
107
+ */
108
+ static SECURITY_STATUS open_provider(NCRYPT_PROV_HANDLE* phProv) {
109
+ SECURITY_STATUS s = NCryptOpenStorageProvider(phProv,
110
+ MS_PLATFORM_CRYPTO_PROVIDER, 0);
111
+ if (s == ERROR_SUCCESS) return s;
112
+ return NCryptOpenStorageProvider(phProv, MS_KEY_STORAGE_PROVIDER, 0);
113
+ }
114
+
115
+ /**
116
+ * Export the public key portion of a CNG key handle as SPKI DER bytes.
117
+ * Returns empty vector on failure.
118
+ */
119
+ static std::vector<unsigned char> export_spki(NCRYPT_KEY_HANDLE hKey) {
120
+ std::vector<unsigned char> result;
121
+
122
+ // Export as ECC public blob
123
+ DWORD cbBlob = 0;
124
+ SECURITY_STATUS s = NCryptExportKey(hKey, 0, BCRYPT_ECCPUBLIC_BLOB,
125
+ NULL, NULL, 0, &cbBlob, 0);
126
+ if (s != ERROR_SUCCESS || cbBlob == 0) return result;
127
+
128
+ std::vector<unsigned char> blob(cbBlob);
129
+ s = NCryptExportKey(hKey, 0, BCRYPT_ECCPUBLIC_BLOB,
130
+ NULL, blob.data(), cbBlob, &cbBlob, 0);
131
+ if (s != ERROR_SUCCESS) return result;
132
+
133
+ // BCRYPT_ECCPUBLIC_BLOB layout:
134
+ // BCRYPT_ECCKEY_BLOB header (8 bytes): magic(4) + cbKey(4)
135
+ // X coordinate (cbKey bytes)
136
+ // Y coordinate (cbKey bytes)
137
+ BCRYPT_ECCKEY_BLOB* pHeader = (BCRYPT_ECCKEY_BLOB*)blob.data();
138
+ DWORD cbKey = pHeader->cbKey; // 32 for P-256
139
+
140
+ if (cbKey != 32) return result; // Only P-256 supported
141
+
142
+ // Build uncompressed EC point: 0x04 || X || Y (65 bytes)
143
+ unsigned char* pX = blob.data() + sizeof(BCRYPT_ECCKEY_BLOB);
144
+ unsigned char* pY = pX + cbKey;
145
+
146
+ // SPKI DER = header (26 bytes) + uncompressed point (65 bytes) = 91 bytes
147
+ result.resize(sizeof(SPKI_HEADER) + 1 + 2 * cbKey);
148
+ memcpy(result.data(), SPKI_HEADER, sizeof(SPKI_HEADER));
149
+ result[sizeof(SPKI_HEADER)] = 0x04; // uncompressed point marker
150
+ memcpy(result.data() + sizeof(SPKI_HEADER) + 1, pX, cbKey);
151
+ memcpy(result.data() + sizeof(SPKI_HEADER) + 1 + cbKey, pY, cbKey);
152
+
153
+ return result;
154
+ }
155
+
156
+ /**
157
+ * Convert raw r||s signature (64 bytes for P-256) to DER-encoded ECDSA-Sig-Value.
158
+ * Node.js crypto.createVerify expects DER format.
159
+ */
160
+ static std::vector<unsigned char> raw_sig_to_der(const unsigned char* sig, DWORD sigLen) {
161
+ std::vector<unsigned char> der;
162
+ if (sigLen != 64) return der;
163
+
164
+ const unsigned char* r = sig;
165
+ const unsigned char* s = sig + 32;
166
+
167
+ // Each integer: skip leading zeros, add 0x00 padding if high bit set
168
+ auto encode_int = [](const unsigned char* val, int len) -> std::vector<unsigned char> {
169
+ std::vector<unsigned char> out;
170
+ int start = 0;
171
+ while (start < len - 1 && val[start] == 0) start++;
172
+ bool pad = (val[start] & 0x80) != 0;
173
+ out.push_back(0x02); // INTEGER tag
174
+ out.push_back((unsigned char)(len - start + (pad ? 1 : 0)));
175
+ if (pad) out.push_back(0x00);
176
+ for (int i = start; i < len; i++) out.push_back(val[i]);
177
+ return out;
178
+ };
179
+
180
+ auto r_der = encode_int(r, 32);
181
+ auto s_der = encode_int(s, 32);
182
+
183
+ // SEQUENCE tag + length + r + s
184
+ der.push_back(0x30);
185
+ der.push_back((unsigned char)(r_der.size() + s_der.size()));
186
+ der.insert(der.end(), r_der.begin(), r_der.end());
187
+ der.insert(der.end(), s_der.begin(), s_der.end());
188
+
189
+ return der;
190
+ }
191
+
192
+ // ── Commands ────────────────────────────────────────────────────────────────
193
+
194
+ /**
195
+ * check — verify Windows Hello / CNG availability.
196
+ * Outputs "OK" if key storage is available, "ERROR:<msg>" otherwise.
197
+ */
198
+ static int cmd_check() {
199
+ NCRYPT_PROV_HANDLE hProv = 0;
200
+ SECURITY_STATUS s = open_provider(&hProv);
201
+ if (s != ERROR_SUCCESS) {
202
+ printf("ERROR:Key storage provider unavailable (0x%08lx)\n", s);
203
+ return 1;
204
+ }
205
+ NCryptFreeObject(hProv);
206
+ printf("OK\n");
207
+ return 0;
208
+ }
209
+
210
+ /**
211
+ * generate-key — create P-256 key in CNG store.
212
+ * Output: "OK:<public-spki-b64>"
213
+ * The key is persisted under KEY_NAME with UI protection (Windows Hello consent).
214
+ */
215
+ static int cmd_generate_key() {
216
+ NCRYPT_PROV_HANDLE hProv = 0;
217
+ NCRYPT_KEY_HANDLE hKey = 0;
218
+ SECURITY_STATUS s;
219
+
220
+ s = open_provider(&hProv);
221
+ if (s != ERROR_SUCCESS) {
222
+ printf("ERROR:Cannot open key storage provider (0x%08lx)\n", s);
223
+ return 1;
224
+ }
225
+
226
+ // Delete existing key if present (fresh start, like macOS delete-then-add)
227
+ NCRYPT_KEY_HANDLE hOld = 0;
228
+ if (NCryptOpenKey(hProv, &hOld, KEY_NAME, 0, 0) == ERROR_SUCCESS) {
229
+ NCryptDeleteKey(hOld, 0);
230
+ }
231
+
232
+ // Create P-256 key with user-consent requirement
233
+ s = NCryptCreatePersistedKey(hProv, &hKey,
234
+ BCRYPT_ECDSA_P256_ALGORITHM, KEY_NAME, 0,
235
+ NCRYPT_OVERWRITE_KEY_FLAG);
236
+ if (s != ERROR_SUCCESS) {
237
+ printf("ERROR:Key creation failed (0x%08lx)\n", s);
238
+ NCryptFreeObject(hProv);
239
+ return 1;
240
+ }
241
+
242
+ // Require Windows Hello consent for sign operations
243
+ NCRYPT_UI_POLICY uiPolicy = {};
244
+ uiPolicy.dwVersion = 1;
245
+ uiPolicy.dwFlags = NCRYPT_UI_PROTECT_KEY_FLAG;
246
+ uiPolicy.pszCreationTitle = L"Visa CLI";
247
+ uiPolicy.pszDescription = L"Biometric key for payment attestation";
248
+ s = NCryptSetProperty(hKey, NCRYPT_UI_POLICY_PROPERTY,
249
+ (PBYTE)&uiPolicy, sizeof(uiPolicy), 0);
250
+ if (s != ERROR_SUCCESS) {
251
+ // Non-fatal: key works without UI protection (CI/headless scenarios)
252
+ }
253
+
254
+ // Finalize (persist to store)
255
+ s = NCryptFinalizeKey(hKey, 0);
256
+ if (s != ERROR_SUCCESS) {
257
+ printf("ERROR:Key finalization failed (0x%08lx)\n", s);
258
+ NCryptFreeObject(hKey);
259
+ NCryptFreeObject(hProv);
260
+ return 1;
261
+ }
262
+
263
+ // Export public key as SPKI DER
264
+ auto spki = export_spki(hKey);
265
+ if (spki.empty()) {
266
+ printf("ERROR:Failed to export public key\n");
267
+ NCryptFreeObject(hKey);
268
+ NCryptFreeObject(hProv);
269
+ return 1;
270
+ }
271
+
272
+ std::string pub_b64 = base64_encode(spki.data(), spki.size());
273
+ printf("OK:%s\n", pub_b64.c_str());
274
+
275
+ NCryptFreeObject(hKey);
276
+ NCryptFreeObject(hProv);
277
+ return 0;
278
+ }
279
+
280
+ /**
281
+ * public-key — export SPKI DER public key for the stored attestation key.
282
+ * Does NOT require Windows Hello consent (read-only metadata export).
283
+ * Output: "OK:<public-spki-b64>"
284
+ */
285
+ static int cmd_public_key() {
286
+ NCRYPT_PROV_HANDLE hProv = 0;
287
+ NCRYPT_KEY_HANDLE hKey = 0;
288
+ SECURITY_STATUS s;
289
+
290
+ s = open_provider(&hProv);
291
+ if (s != ERROR_SUCCESS) {
292
+ printf("ERROR:Cannot open key storage provider (0x%08lx)\n", s);
293
+ return 1;
294
+ }
295
+
296
+ s = NCryptOpenKey(hProv, &hKey, KEY_NAME, 0, 0);
297
+ if (s != ERROR_SUCCESS) {
298
+ printf("ERROR:Attestation key not found. Run setup to generate a new key.\n");
299
+ NCryptFreeObject(hProv);
300
+ return 1;
301
+ }
302
+
303
+ auto spki = export_spki(hKey);
304
+ if (spki.empty()) {
305
+ printf("ERROR:Failed to export public key\n");
306
+ NCryptFreeObject(hKey);
307
+ NCryptFreeObject(hProv);
308
+ return 1;
309
+ }
310
+
311
+ std::string pub_b64 = base64_encode(spki.data(), spki.size());
312
+ printf("OK:%s\n", pub_b64.c_str());
313
+
314
+ NCryptFreeObject(hKey);
315
+ NCryptFreeObject(hProv);
316
+ return 0;
317
+ }
318
+
319
+ /**
320
+ * sign <challenge> [reason] — hash + sign with Windows Hello consent.
321
+ * The challenge string is SHA-256 hashed, then signed with ECDSA P-256.
322
+ * Windows Hello UI blocks until user confirms (fingerprint/face/PIN).
323
+ * Output: "OK:<der-signature-b64>"
324
+ */
325
+ static int cmd_sign(const char* challenge, const char* reason) {
326
+ NCRYPT_PROV_HANDLE hProv = 0;
327
+ NCRYPT_KEY_HANDLE hKey = 0;
328
+ SECURITY_STATUS s;
329
+
330
+ s = open_provider(&hProv);
331
+ if (s != ERROR_SUCCESS) {
332
+ printf("ERROR:Cannot open key storage provider (0x%08lx)\n", s);
333
+ return 1;
334
+ }
335
+
336
+ s = NCryptOpenKey(hProv, &hKey, KEY_NAME, 0, 0);
337
+ if (s != ERROR_SUCCESS) {
338
+ printf("ERROR:Attestation key not found. Run setup to generate a new key.\n");
339
+ NCryptFreeObject(hProv);
340
+ return 1;
341
+ }
342
+
343
+ // Hash the challenge with SHA-256
344
+ BCRYPT_ALG_HANDLE hAlg = 0;
345
+ NTSTATUS ns = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM,
346
+ NULL, 0);
347
+ if (ns != 0) {
348
+ printf("ERROR:Cannot open SHA-256 provider\n");
349
+ NCryptFreeObject(hKey);
350
+ NCryptFreeObject(hProv);
351
+ return 1;
352
+ }
353
+
354
+ unsigned char hash[32];
355
+ ns = BCryptHash(hAlg, NULL, 0,
356
+ (PUCHAR)challenge, (ULONG)strlen(challenge),
357
+ hash, sizeof(hash));
358
+ BCryptCloseAlgorithmProvider(hAlg, 0);
359
+
360
+ if (ns != 0) {
361
+ printf("ERROR:SHA-256 hash failed\n");
362
+ NCryptFreeObject(hKey);
363
+ NCryptFreeObject(hProv);
364
+ return 1;
365
+ }
366
+
367
+ // Sign the hash — this triggers the Windows Hello consent UI.
368
+ // NCryptSignHash with a UI-protected key will show the biometric prompt.
369
+ DWORD cbSig = 0;
370
+ s = NCryptSignHash(hKey, NULL, hash, sizeof(hash),
371
+ NULL, 0, &cbSig, 0);
372
+ if (s != ERROR_SUCCESS || cbSig == 0) {
373
+ printf("ERROR:Cannot determine signature size (0x%08lx)\n", s);
374
+ NCryptFreeObject(hKey);
375
+ NCryptFreeObject(hProv);
376
+ return 1;
377
+ }
378
+
379
+ std::vector<unsigned char> sig(cbSig);
380
+ s = NCryptSignHash(hKey, NULL, hash, sizeof(hash),
381
+ sig.data(), cbSig, &cbSig, 0);
382
+
383
+ if (s == (SECURITY_STATUS)NTE_USER_CANCELLED) {
384
+ printf("ERROR:Authentication cancelled by user\n");
385
+ NCryptFreeObject(hKey);
386
+ NCryptFreeObject(hProv);
387
+ return 1;
388
+ }
389
+ if (s != ERROR_SUCCESS) {
390
+ printf("ERROR:Signing failed (0x%08lx)\n", s);
391
+ NCryptFreeObject(hKey);
392
+ NCryptFreeObject(hProv);
393
+ return 1;
394
+ }
395
+
396
+ // CNG outputs raw r||s (64 bytes for P-256). Convert to DER for Node.js.
397
+ auto der_sig = raw_sig_to_der(sig.data(), cbSig);
398
+ if (der_sig.empty()) {
399
+ printf("ERROR:Signature DER encoding failed\n");
400
+ NCryptFreeObject(hKey);
401
+ NCryptFreeObject(hProv);
402
+ return 1;
403
+ }
404
+
405
+ std::string sig_b64 = base64_encode(der_sig.data(), der_sig.size());
406
+ printf("OK:%s\n", sig_b64.c_str());
407
+
408
+ NCryptFreeObject(hKey);
409
+ NCryptFreeObject(hProv);
410
+ return 0;
411
+ }
412
+
413
+ /**
414
+ * delete-key — remove the stored attestation key from CNG.
415
+ * Output: "OK" on success, "ERROR:<msg>" on failure.
416
+ */
417
+ static int cmd_delete_key() {
418
+ NCRYPT_PROV_HANDLE hProv = 0;
419
+ NCRYPT_KEY_HANDLE hKey = 0;
420
+ SECURITY_STATUS s;
421
+
422
+ s = open_provider(&hProv);
423
+ if (s != ERROR_SUCCESS) {
424
+ printf("OK\n"); // No provider = no key = success
425
+ return 0;
426
+ }
427
+
428
+ s = NCryptOpenKey(hProv, &hKey, KEY_NAME, 0, 0);
429
+ if (s != ERROR_SUCCESS) {
430
+ NCryptFreeObject(hProv);
431
+ printf("OK\n"); // Key doesn't exist = success
432
+ return 0;
433
+ }
434
+
435
+ s = NCryptDeleteKey(hKey, 0);
436
+ NCryptFreeObject(hProv);
437
+
438
+ if (s != ERROR_SUCCESS) {
439
+ printf("ERROR:Failed to delete key (0x%08lx)\n", s);
440
+ return 1;
441
+ }
442
+
443
+ printf("OK\n");
444
+ return 0;
445
+ }
446
+
447
+ // ── Main ────────────────────────────────────────────────────────────────────
448
+
449
+ int main(int argc, char* argv[]) {
450
+ if (argc < 2) {
451
+ fprintf(stderr, "Usage: visa-keychain-win <command> [args...]\n");
452
+ fprintf(stderr, "Commands: check, generate-key, public-key, sign, delete-key\n");
453
+ return 1;
454
+ }
455
+
456
+ const char* cmd = argv[1];
457
+
458
+ if (strcmp(cmd, "check") == 0) {
459
+ return cmd_check();
460
+ }
461
+ if (strcmp(cmd, "generate-key") == 0) {
462
+ return cmd_generate_key();
463
+ }
464
+ if (strcmp(cmd, "public-key") == 0) {
465
+ return cmd_public_key();
466
+ }
467
+ if (strcmp(cmd, "sign") == 0) {
468
+ if (argc < 3) {
469
+ printf("ERROR:Usage: sign <challenge> [reason]\n");
470
+ return 1;
471
+ }
472
+ const char* reason = (argc >= 4) ? argv[3] : NULL;
473
+ return cmd_sign(argv[2], reason);
474
+ }
475
+ if (strcmp(cmd, "delete-key") == 0) {
476
+ return cmd_delete_key();
477
+ }
478
+
479
+ fprintf(stderr, "ERROR:Unknown command: %s\n", cmd);
480
+ return 1;
481
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visa/cli",
3
- "version": "2.7.0-rc.2",
3
+ "version": "2.7.0-rc.4",
4
4
  "description": "AI-powered payments for Claude Code",
5
5
  "bin": {
6
6
  "visa-cli": "./bin/visa-cli.js"
@@ -75,6 +75,9 @@
75
75
  "install.ps1",
76
76
  "install.sh",
77
77
  "native/visa-keychain.m",
78
+ "native/visa-keychain-win.cpp",
79
+ "native/build-win.bat",
80
+ "native/bin/win32-x64/visa-keychain-win.exe",
78
81
  "server.json",
79
82
  "README.md",
80
83
  "LICENSE"
package/server.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
3
3
  "name": "io.github.visa-crypto-labs/visa-cli",
4
- "version": "2.7.0-rc.2",
4
+ "version": "2.7.0-rc.4",
5
5
  "title": "Visa CLI",
6
6
  "description": "AI-powered payments and creative tools for coding agents. Generate images, music, video, query crypto prices, and make purchases — all 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": "2.7.0-rc.2",
12
+ "version": "2.7.0-rc.4",
13
13
  "transport": {
14
14
  "type": "stdio"
15
15
  },