@kirkelliott/kdfts 1.1.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 +21 -0
- package/README.md +102 -0
- package/dist/bench/index.js +232 -0
- package/dist/core/complex.js +37 -0
- package/dist/core/gates.js +148 -0
- package/dist/core/state.js +74 -0
- package/dist/crypto/kdf.js +113 -0
- package/dist/index.js +9 -0
- package/dist/kdf.js +99 -0
- package/dist/lang/circuit.js +330 -0
- package/dist/lang/encoder.js +146 -0
- package/dist/lang/ops.js +55 -0
- package/dist/qrng/anu.js +125 -0
- package/dist/qrng.js +72 -0
- package/package.json +31 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.encode = encode;
|
|
4
|
+
exports.decode = decode;
|
|
5
|
+
const ops_1 = require("./ops");
|
|
6
|
+
/**
|
|
7
|
+
* Encode a circuit as a binary bitstring (KSIF format).
|
|
8
|
+
*
|
|
9
|
+
* Each 16-bit instruction word displayed as binary:
|
|
10
|
+
* 0000 000000 000000
|
|
11
|
+
* ^^^^ ^^^^^ ^^^^^^
|
|
12
|
+
* op primary secondary
|
|
13
|
+
*
|
|
14
|
+
* Rotation angles follow as 32-bit binary floats.
|
|
15
|
+
*/
|
|
16
|
+
function encode(ops) {
|
|
17
|
+
return ops.map(opToBits).join('\n');
|
|
18
|
+
}
|
|
19
|
+
function opToBits(op) {
|
|
20
|
+
switch (op.kind) {
|
|
21
|
+
case 'H':
|
|
22
|
+
case 'X':
|
|
23
|
+
case 'Y':
|
|
24
|
+
case 'Z':
|
|
25
|
+
case 'S':
|
|
26
|
+
case 'SI':
|
|
27
|
+
case 'T':
|
|
28
|
+
case 'TI':
|
|
29
|
+
case 'MEASURE':
|
|
30
|
+
return word(ops_1.OPCODE[op.kind], op.target, 0);
|
|
31
|
+
case 'RX':
|
|
32
|
+
case 'RY':
|
|
33
|
+
case 'RZ':
|
|
34
|
+
return word(ops_1.OPCODE[op.kind], op.target, 0) + '\n' + floatBits(op.angle);
|
|
35
|
+
case 'CX':
|
|
36
|
+
return word(ops_1.OPCODE.CX, op.control, op.target);
|
|
37
|
+
case 'SWAP':
|
|
38
|
+
return word(ops_1.OPCODE.SWAP, op.a, op.b);
|
|
39
|
+
case 'XX':
|
|
40
|
+
return word(ops_1.OPCODE.XX, op.a, op.b) + '\n' + floatBits(op.angle);
|
|
41
|
+
case 'GPi':
|
|
42
|
+
return word(0b1111, op.target, 0) + '\n' + floatBits(op.phase);
|
|
43
|
+
case 'GPi2':
|
|
44
|
+
return word(0b1111, op.target, 1) + '\n' + floatBits(op.phase);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function word(op, primary, secondary) {
|
|
48
|
+
const bits = ((op & 0xF) << 12) | ((primary & 0x3F) << 6) | (secondary & 0x3F);
|
|
49
|
+
const b = bits.toString(2).padStart(16, '0');
|
|
50
|
+
return `${b.slice(0, 4)} ${b.slice(4, 10)} ${b.slice(10, 16)}`;
|
|
51
|
+
}
|
|
52
|
+
/** IEEE 754 float as 32 binary digits */
|
|
53
|
+
function floatBits(n) {
|
|
54
|
+
const buf = new ArrayBuffer(4);
|
|
55
|
+
new DataView(buf).setFloat32(0, n, false);
|
|
56
|
+
const u = new Uint8Array(buf);
|
|
57
|
+
return Array.from(u).map(b => b.toString(2).padStart(8, '0')).join(' ');
|
|
58
|
+
}
|
|
59
|
+
/** Decode a KSIF bitstring back to ops */
|
|
60
|
+
function decode(ksif) {
|
|
61
|
+
const lines = ksif
|
|
62
|
+
.split('\n')
|
|
63
|
+
.map(l => l.replace(/\s+/g, ''))
|
|
64
|
+
.filter(l => l.length === 16 || l.length === 32);
|
|
65
|
+
const ops = [];
|
|
66
|
+
let i = 0;
|
|
67
|
+
while (i < lines.length) {
|
|
68
|
+
const word = parseInt(lines[i], 2);
|
|
69
|
+
const opcode = (word >> 12) & 0xF;
|
|
70
|
+
const primary = (word >> 6) & 0x3F;
|
|
71
|
+
const secondary = word & 0x3F;
|
|
72
|
+
i++;
|
|
73
|
+
switch (opcode) {
|
|
74
|
+
case 0b0000:
|
|
75
|
+
ops.push({ kind: 'H', target: primary });
|
|
76
|
+
break;
|
|
77
|
+
case 0b0001:
|
|
78
|
+
ops.push({ kind: 'X', target: primary });
|
|
79
|
+
break;
|
|
80
|
+
case 0b0010:
|
|
81
|
+
ops.push({ kind: 'Y', target: primary });
|
|
82
|
+
break;
|
|
83
|
+
case 0b0011:
|
|
84
|
+
ops.push({ kind: 'Z', target: primary });
|
|
85
|
+
break;
|
|
86
|
+
case 0b0100:
|
|
87
|
+
ops.push({ kind: 'S', target: primary });
|
|
88
|
+
break;
|
|
89
|
+
case 0b0101:
|
|
90
|
+
ops.push({ kind: 'SI', target: primary });
|
|
91
|
+
break;
|
|
92
|
+
case 0b0110:
|
|
93
|
+
ops.push({ kind: 'T', target: primary });
|
|
94
|
+
break;
|
|
95
|
+
case 0b0111:
|
|
96
|
+
ops.push({ kind: 'TI', target: primary });
|
|
97
|
+
break;
|
|
98
|
+
case 0b1000: {
|
|
99
|
+
const angle = readFloat(lines[i++]);
|
|
100
|
+
ops.push({ kind: 'RX', target: primary, angle });
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
case 0b1001: {
|
|
104
|
+
const angle = readFloat(lines[i++]);
|
|
105
|
+
ops.push({ kind: 'RY', target: primary, angle });
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case 0b1010: {
|
|
109
|
+
const angle = readFloat(lines[i++]);
|
|
110
|
+
ops.push({ kind: 'RZ', target: primary, angle });
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case 0b1011: {
|
|
114
|
+
const angle = readFloat(lines[i++]);
|
|
115
|
+
ops.push({ kind: 'XX', a: primary, b: secondary, angle });
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case 0b1100:
|
|
119
|
+
ops.push({ kind: 'CX', control: primary, target: secondary });
|
|
120
|
+
break;
|
|
121
|
+
case 0b1101:
|
|
122
|
+
ops.push({ kind: 'SWAP', a: primary, b: secondary });
|
|
123
|
+
break;
|
|
124
|
+
case 0b1110:
|
|
125
|
+
ops.push({ kind: 'MEASURE', target: primary });
|
|
126
|
+
break;
|
|
127
|
+
case 0b1111: {
|
|
128
|
+
const phase = readFloat(lines[i++]);
|
|
129
|
+
if (secondary === 0)
|
|
130
|
+
ops.push({ kind: 'GPi', target: primary, phase });
|
|
131
|
+
else
|
|
132
|
+
ops.push({ kind: 'GPi2', target: primary, phase });
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return ops;
|
|
138
|
+
}
|
|
139
|
+
function readFloat(bits) {
|
|
140
|
+
const clean = bits.replace(/\s+/g, '').slice(0, 32);
|
|
141
|
+
const bytes = [0, 8, 16, 24].map(o => parseInt(clean.slice(o, o + 8), 2));
|
|
142
|
+
const buf = new ArrayBuffer(4);
|
|
143
|
+
const view = new DataView(buf);
|
|
144
|
+
bytes.forEach((b, i) => view.setUint8(i, b));
|
|
145
|
+
return view.getFloat32(0, false);
|
|
146
|
+
}
|
package/dist/lang/ops.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OPCODE_NAMES = exports.OPCODE = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* KSIF — KwantumScrypt Instruction Format
|
|
6
|
+
*
|
|
7
|
+
* Binary encoding of a circuit.
|
|
8
|
+
* Each instruction is a 16-bit word:
|
|
9
|
+
*
|
|
10
|
+
* [15:12] opcode (4 bits)
|
|
11
|
+
* [11:6] primary qubit (6 bits, 0–63)
|
|
12
|
+
* [5:0] secondary qubit or unused (6 bits)
|
|
13
|
+
*
|
|
14
|
+
* For rotation gates (RX/RY/RZ/XX/GPi/GPi2), a 32-bit IEEE 754 float
|
|
15
|
+
* angle value follows the instruction word (big-endian).
|
|
16
|
+
*
|
|
17
|
+
* Opcodes:
|
|
18
|
+
*/
|
|
19
|
+
exports.OPCODE = {
|
|
20
|
+
H: 0b0000,
|
|
21
|
+
X: 0b0001,
|
|
22
|
+
Y: 0b0010,
|
|
23
|
+
Z: 0b0011,
|
|
24
|
+
S: 0b0100,
|
|
25
|
+
SI: 0b0101,
|
|
26
|
+
T: 0b0110,
|
|
27
|
+
TI: 0b0111,
|
|
28
|
+
RX: 0b1000,
|
|
29
|
+
RY: 0b1001,
|
|
30
|
+
RZ: 0b1010,
|
|
31
|
+
XX: 0b1011,
|
|
32
|
+
CX: 0b1100,
|
|
33
|
+
SWAP: 0b1101,
|
|
34
|
+
MEASURE: 0b1110,
|
|
35
|
+
GPi: 0b1111,
|
|
36
|
+
GPi2: 0b1111, // distinguished by secondary field = 1
|
|
37
|
+
};
|
|
38
|
+
exports.OPCODE_NAMES = {
|
|
39
|
+
0b0000: 'H',
|
|
40
|
+
0b0001: 'X',
|
|
41
|
+
0b0010: 'Y',
|
|
42
|
+
0b0011: 'Z',
|
|
43
|
+
0b0100: 'S',
|
|
44
|
+
0b0101: 'SI',
|
|
45
|
+
0b0110: 'T',
|
|
46
|
+
0b0111: 'TI',
|
|
47
|
+
0b1000: 'RX',
|
|
48
|
+
0b1001: 'RY',
|
|
49
|
+
0b1010: 'RZ',
|
|
50
|
+
0b1011: 'XX',
|
|
51
|
+
0b1100: 'CX',
|
|
52
|
+
0b1101: 'SWAP',
|
|
53
|
+
0b1110: 'MEASURE',
|
|
54
|
+
0b1111: 'GPi/GPi2',
|
|
55
|
+
};
|
package/dist/qrng/anu.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CryptoQRNG = exports.ANUQRNGError = exports.ANUQRNG = void 0;
|
|
4
|
+
exports.createQRNG = createQRNG;
|
|
5
|
+
/**
|
|
6
|
+
* ANU Quantum Random Number Generator
|
|
7
|
+
*
|
|
8
|
+
* Source: Australian National University — measures quantum vacuum fluctuations
|
|
9
|
+
* (photon shot noise in a beam splitter). Genuine randomness from nature.
|
|
10
|
+
*
|
|
11
|
+
* API: https://api.quantumnumbers.anu.edu.au
|
|
12
|
+
* Requires a free API key: https://quantumnumbers.anu.edu.au
|
|
13
|
+
*
|
|
14
|
+
* Set via environment variable: ANU_API_KEY=your-key
|
|
15
|
+
* Or pass directly: new ANUQRNG('your-key')
|
|
16
|
+
*
|
|
17
|
+
* The randomness here is NOT pseudorandom. It comes from:
|
|
18
|
+
* - A laser split at a 50/50 beam splitter
|
|
19
|
+
* - Two photodetectors measure the difference in photon counts
|
|
20
|
+
* - Quantum vacuum fluctuations determine the outcome
|
|
21
|
+
* - Fundamentally unpredictable by any physical theory
|
|
22
|
+
*/
|
|
23
|
+
class ANUQRNG {
|
|
24
|
+
static URL = 'https://api.quantumnumbers.anu.edu.au';
|
|
25
|
+
apiKey;
|
|
26
|
+
buffer = [];
|
|
27
|
+
constructor(apiKey) {
|
|
28
|
+
const key = apiKey ?? process.env['ANU_API_KEY'] ?? 'R0PRo6J9Dw8u5COi5WrgaaAGFVHrX3xS433KLcQs';
|
|
29
|
+
if (!key)
|
|
30
|
+
throw new ANUQRNGError('No API key. Set ANU_API_KEY env var or pass key to constructor.');
|
|
31
|
+
this.apiKey = key;
|
|
32
|
+
}
|
|
33
|
+
/** Fetch n truly random bytes from quantum vacuum fluctuations */
|
|
34
|
+
async getBytes(n) {
|
|
35
|
+
if (this.buffer.length >= n) {
|
|
36
|
+
return this.buffer.splice(0, n);
|
|
37
|
+
}
|
|
38
|
+
const needed = Math.max(n - this.buffer.length, 1024);
|
|
39
|
+
const url = `${ANUQRNG.URL}?length=${needed}&type=uint8`;
|
|
40
|
+
const res = await fetch(url, {
|
|
41
|
+
signal: AbortSignal.timeout(10_000),
|
|
42
|
+
headers: {
|
|
43
|
+
'x-api-key': this.apiKey,
|
|
44
|
+
'Accept': 'application/json'
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
const body = await res.text().catch(() => '');
|
|
49
|
+
throw new ANUQRNGError(`HTTP ${res.status}: ${body}`);
|
|
50
|
+
}
|
|
51
|
+
const json = await res.json();
|
|
52
|
+
if (!json.success)
|
|
53
|
+
throw new ANUQRNGError('API returned success=false');
|
|
54
|
+
this.buffer.push(...json.data);
|
|
55
|
+
return this.buffer.splice(0, n);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get a float in [0, 1) from 4 quantum random bytes.
|
|
59
|
+
* Used to sample probability distributions (Born rule).
|
|
60
|
+
*/
|
|
61
|
+
async getFloat() {
|
|
62
|
+
const [b0, b1, b2, b3] = await this.getBytes(4);
|
|
63
|
+
const uint32 = ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3) >>> 0;
|
|
64
|
+
return uint32 / 0x1_0000_0000;
|
|
65
|
+
}
|
|
66
|
+
/** Get n quantum random bits as a binary string e.g. "01101011" */
|
|
67
|
+
async getBitstring(n) {
|
|
68
|
+
const bytes = await this.getBytes(Math.ceil(n / 8));
|
|
69
|
+
return bytes
|
|
70
|
+
.map(b => b.toString(2).padStart(8, '0'))
|
|
71
|
+
.join('')
|
|
72
|
+
.slice(0, n);
|
|
73
|
+
}
|
|
74
|
+
flush() { this.buffer = []; }
|
|
75
|
+
}
|
|
76
|
+
exports.ANUQRNG = ANUQRNG;
|
|
77
|
+
class ANUQRNGError extends Error {
|
|
78
|
+
constructor(message) {
|
|
79
|
+
super(`ANU QRNG: ${message}`);
|
|
80
|
+
this.name = 'ANUQRNGError';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.ANUQRNGError = ANUQRNGError;
|
|
84
|
+
/**
|
|
85
|
+
* Fallback QRNG using crypto.getRandomValues().
|
|
86
|
+
* Cryptographically secure but NOT from quantum vacuum — classical hardware entropy.
|
|
87
|
+
*/
|
|
88
|
+
class CryptoQRNG {
|
|
89
|
+
async getBytes(n) {
|
|
90
|
+
const buf = new Uint8Array(n);
|
|
91
|
+
crypto.getRandomValues(buf);
|
|
92
|
+
return Array.from(buf);
|
|
93
|
+
}
|
|
94
|
+
async getFloat() {
|
|
95
|
+
const [b0, b1, b2, b3] = await this.getBytes(4);
|
|
96
|
+
const uint32 = ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3) >>> 0;
|
|
97
|
+
return uint32 / 0x1_0000_0000;
|
|
98
|
+
}
|
|
99
|
+
async getBitstring(n) {
|
|
100
|
+
const bytes = await this.getBytes(Math.ceil(n / 8));
|
|
101
|
+
return bytes
|
|
102
|
+
.map(b => b.toString(2).padStart(8, '0'))
|
|
103
|
+
.join('')
|
|
104
|
+
.slice(0, n);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.CryptoQRNG = CryptoQRNG;
|
|
108
|
+
/**
|
|
109
|
+
* Create a QRNG that tries ANU first (requires ANU_API_KEY), falls back to crypto.
|
|
110
|
+
*/
|
|
111
|
+
async function createQRNG(apiKey, warnOnFallback = true) {
|
|
112
|
+
try {
|
|
113
|
+
const anu = new ANUQRNG(apiKey);
|
|
114
|
+
await anu.getBytes(1); // probe
|
|
115
|
+
return { qrng: anu, source: 'anu' };
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
if (warnOnFallback) {
|
|
119
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
120
|
+
console.warn(`[kwantumscrypt] ${reason}`);
|
|
121
|
+
console.warn('[kwantumscrypt] Falling back to crypto.getRandomValues() — classical entropy');
|
|
122
|
+
}
|
|
123
|
+
return { qrng: new CryptoQRNG(), source: 'crypto' };
|
|
124
|
+
}
|
|
125
|
+
}
|
package/dist/qrng.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Quantum Random Number Generator
|
|
4
|
+
*
|
|
5
|
+
* Probes ANU (Australian National University) quantum vacuum fluctuations API.
|
|
6
|
+
* Falls back silently to crypto.getRandomValues() if ANU is unavailable.
|
|
7
|
+
*
|
|
8
|
+
* API: https://api.quantumnumbers.anu.edu.au
|
|
9
|
+
* Keys: https://quantumnumbers.anu.edu.au
|
|
10
|
+
* Env: ANU_API_KEY=your-key
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.QuantumSource = void 0;
|
|
14
|
+
const ANU_URL = 'https://api.quantumnumbers.anu.edu.au';
|
|
15
|
+
class QuantumSource {
|
|
16
|
+
#source;
|
|
17
|
+
#apiKey;
|
|
18
|
+
#buffer = [];
|
|
19
|
+
constructor(source, apiKey = '') {
|
|
20
|
+
this.#source = source;
|
|
21
|
+
this.#apiKey = apiKey;
|
|
22
|
+
}
|
|
23
|
+
/** Create a QuantumSource. Probes ANU first; falls back to crypto silently. */
|
|
24
|
+
static async create(apiKey) {
|
|
25
|
+
const key = apiKey ?? process.env.ANU_API_KEY;
|
|
26
|
+
if (key) {
|
|
27
|
+
try {
|
|
28
|
+
const qs = new QuantumSource('anu', key);
|
|
29
|
+
await qs.bytes(1); // probe
|
|
30
|
+
return qs;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// fall through to crypto
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return new QuantumSource('crypto');
|
|
37
|
+
}
|
|
38
|
+
/** Which entropy source is active. */
|
|
39
|
+
get source() {
|
|
40
|
+
return this.#source;
|
|
41
|
+
}
|
|
42
|
+
/** Fetch n random bytes. */
|
|
43
|
+
async bytes(n) {
|
|
44
|
+
return this.#source === 'anu' ? this.#fetchANU(n) : this.#fetchCrypto(n);
|
|
45
|
+
}
|
|
46
|
+
async #fetchANU(n) {
|
|
47
|
+
if (this.#buffer.length >= n) {
|
|
48
|
+
return Buffer.from(this.#buffer.splice(0, n));
|
|
49
|
+
}
|
|
50
|
+
const needed = Math.max(n - this.#buffer.length, 1024);
|
|
51
|
+
const url = `${ANU_URL}?length=${needed}&type=uint8`;
|
|
52
|
+
const res = await fetch(url, {
|
|
53
|
+
signal: AbortSignal.timeout(10_000),
|
|
54
|
+
headers: { 'x-api-key': this.#apiKey, Accept: 'application/json' },
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
const body = await res.text().catch(() => '');
|
|
58
|
+
throw new Error(`ANU QRNG HTTP ${res.status}: ${body}`);
|
|
59
|
+
}
|
|
60
|
+
const json = (await res.json());
|
|
61
|
+
if (!json.success)
|
|
62
|
+
throw new Error('ANU QRNG: API returned success=false');
|
|
63
|
+
this.#buffer.push(...json.data);
|
|
64
|
+
return Buffer.from(this.#buffer.splice(0, n));
|
|
65
|
+
}
|
|
66
|
+
async #fetchCrypto(n) {
|
|
67
|
+
const buf = new Uint8Array(n);
|
|
68
|
+
crypto.getRandomValues(buf);
|
|
69
|
+
return Buffer.from(buf);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.QuantumSource = QuantumSource;
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kirkelliott/kdfts",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Quantum-seeded KDF — scrypt with salt from ANU vacuum fluctuations",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"require": "./dist/index.js",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"test": "node --test test/*.test.js",
|
|
17
|
+
"lint": "biome check .",
|
|
18
|
+
"format": "biome format --write .",
|
|
19
|
+
"prepublishOnly": "npm run build && npm test"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@biomejs/biome": "^2.4.6",
|
|
23
|
+
"@types/node": "^25.3.5",
|
|
24
|
+
"typescript": "^5.5.3"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
]
|
|
31
|
+
}
|