@splashcodex/api-key-manager 5.2.0 → 5.4.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/README.md +211 -206
- package/bin/cli.js +180 -180
- package/dist/env/loader.js +14 -30
- package/dist/env/loader.js.map +1 -1
- package/dist/index.d.ts +30 -13
- package/dist/index.js +106 -31
- package/dist/index.js.map +1 -1
- package/dist/persistence/file.js +12 -1
- package/dist/persistence/file.js.map +1 -1
- package/dist/presets/base.d.ts +6 -0
- package/dist/presets/base.js +37 -1
- package/dist/presets/base.js.map +1 -1
- package/package.json +155 -151
- package/src/env/index.ts +14 -14
- package/src/env/loader.ts +225 -249
- package/src/index.ts +1155 -1071
- package/src/persistence/file.ts +96 -89
- package/src/persistence/index.ts +7 -7
- package/src/persistence/memory.ts +43 -43
- package/src/presets/anthropic.ts +78 -78
- package/src/presets/base.ts +383 -344
- package/src/presets/gemini.ts +84 -84
- package/src/presets/index.ts +11 -11
- package/src/presets/multi.ts +290 -290
- package/src/presets/openai.ts +69 -69
package/bin/cli.js
CHANGED
|
@@ -1,180 +1,180 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const os = require('os');
|
|
5
|
-
|
|
6
|
-
const args = process.argv.slice(2);
|
|
7
|
-
const command = args[0];
|
|
8
|
-
|
|
9
|
-
const ENV_DIR = path.join(os.homedir(), 'codedex', 'env');
|
|
10
|
-
|
|
11
|
-
function printDocs() {
|
|
12
|
-
console.log(`\x1b[92m@splashcodex/api-key-manager v5.1\x1b[0m`);
|
|
13
|
-
console.log(`\x1b[96mDocs: https://www.npmjs.com/package/@splashcodex/ApiKeyManager\x1b[0m\n`);
|
|
14
|
-
console.log(`\x1b[93mCommands:\x1b[0m`);
|
|
15
|
-
console.log(` npx @splashcodex/api-key-manager init \x1b[90m# Scaffold demo + central env directory\x1b[0m`);
|
|
16
|
-
console.log(` npx @splashcodex/api-key-manager setup \x1b[90m# Create ~/codedex/env/ with template files\x1b[0m`);
|
|
17
|
-
console.log(` npx @splashcodex/api-key-manager status \x1b[90m# Show which env files are loaded\x1b[0m`);
|
|
18
|
-
console.log('');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function setupEnvDir() {
|
|
22
|
-
console.log(`\n\x1b[96mSetting up centralized env directory...\x1b[0m\n`);
|
|
23
|
-
|
|
24
|
-
if (!fs.existsSync(ENV_DIR)) {
|
|
25
|
-
fs.mkdirSync(ENV_DIR, { recursive: true });
|
|
26
|
-
console.log(`\x1b[92m[+] Created ${ENV_DIR}\x1b[0m`);
|
|
27
|
-
} else {
|
|
28
|
-
console.log(`\x1b[90m[-] ${ENV_DIR} already exists\x1b[0m`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const templates = {
|
|
32
|
-
'common.env': [
|
|
33
|
-
'# Common environment variables shared across all projects',
|
|
34
|
-
'# DATABASE_URL="postgresql://user:pass@localhost:5432/mydb"',
|
|
35
|
-
'# REDIS_URL="redis://localhost:6379"',
|
|
36
|
-
'# SUPABASE_URL="https://your-project.supabase.co"',
|
|
37
|
-
'# SUPABASE_ANON_KEY="your-anon-key"',
|
|
38
|
-
'',
|
|
39
|
-
].join('\n'),
|
|
40
|
-
'llm.env': [
|
|
41
|
-
'# LLM API keys — supports comma-separated or JSON arrays',
|
|
42
|
-
'# GOOGLE_GEMINI_API_KEY="key1,key2,key3"',
|
|
43
|
-
'# OPENAI_API_KEY="sk-..."',
|
|
44
|
-
'# ANTHROPIC_API_KEY="sk-ant-..."',
|
|
45
|
-
'',
|
|
46
|
-
].join('\n'),
|
|
47
|
-
'stripe.env': [
|
|
48
|
-
'# Stripe payment keys',
|
|
49
|
-
'# STRIPE_SECRET_KEY="sk_test_..."',
|
|
50
|
-
'# STRIPE_WEBHOOK_SECRET="whsec_..."',
|
|
51
|
-
'',
|
|
52
|
-
].join('\n'),
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
for (const [name, content] of Object.entries(templates)) {
|
|
56
|
-
const filePath = path.join(ENV_DIR, name);
|
|
57
|
-
if (!fs.existsSync(filePath)) {
|
|
58
|
-
fs.writeFileSync(filePath, content);
|
|
59
|
-
console.log(`\x1b[92m[+] Created ${name}\x1b[0m`);
|
|
60
|
-
} else {
|
|
61
|
-
console.log(`\x1b[90m[-] ${name} already exists\x1b[0m`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
console.log(`\n\x1b[93mNext steps:\x1b[0m`);
|
|
66
|
-
console.log(` 1. Add your real keys to the files in ${ENV_DIR}`);
|
|
67
|
-
console.log(` 2. Back up the folder to Google Drive for portability`);
|
|
68
|
-
console.log(` 3. In your projects, add at the entry point:`);
|
|
69
|
-
console.log(`\x1b[36m import { loadCentralEnv } from '@splashcodex/api-key-manager/env';\x1b[0m`);
|
|
70
|
-
console.log(`\x1b[36m loadCentralEnv();\x1b[0m\n`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function showStatus() {
|
|
74
|
-
console.log(`\n\x1b[96mCentralized env status\x1b[0m\n`);
|
|
75
|
-
console.log(` Directory: ${ENV_DIR}`);
|
|
76
|
-
|
|
77
|
-
if (!fs.existsSync(ENV_DIR)) {
|
|
78
|
-
console.log(` \x1b[31mNot found.\x1b[0m Run: npx @splashcodex/api-key-manager setup\n`);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const files = fs.readdirSync(ENV_DIR).filter(f => f.endsWith('.env')).sort();
|
|
83
|
-
if (files.length === 0) {
|
|
84
|
-
console.log(` \x1b[33mNo .env files found.\x1b[0m\n`);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
let totalVars = 0;
|
|
89
|
-
for (const file of files) {
|
|
90
|
-
const content = fs.readFileSync(path.join(ENV_DIR, file), 'utf-8');
|
|
91
|
-
const vars = content.split('\n')
|
|
92
|
-
.filter(l => l.trim() && !l.trim().startsWith('#'))
|
|
93
|
-
.filter(l => l.includes('='));
|
|
94
|
-
totalVars += vars.length;
|
|
95
|
-
|
|
96
|
-
const icon = vars.length > 0 ? '\x1b[92m' : '\x1b[90m';
|
|
97
|
-
console.log(` ${icon}${file}\x1b[0m — ${vars.length} variable(s)`);
|
|
98
|
-
for (const v of vars) {
|
|
99
|
-
const key = v.split('=')[0].trim();
|
|
100
|
-
console.log(` \x1b[90m${key}\x1b[0m`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
console.log(`\n Total: ${files.length} file(s), ${totalVars} variable(s)\n`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function initProject() {
|
|
108
|
-
const cwd = process.cwd();
|
|
109
|
-
const demoPath = path.join(cwd, 'demo.ts');
|
|
110
|
-
|
|
111
|
-
console.log(`\n\x1b[96mInitializing @splashcodex/api-key-manager...\x1b[0m\n`);
|
|
112
|
-
|
|
113
|
-
// Ensure central env directory exists
|
|
114
|
-
setupEnvDir();
|
|
115
|
-
|
|
116
|
-
// Create demo.ts
|
|
117
|
-
if (!fs.existsSync(demoPath)) {
|
|
118
|
-
const tsCode = `/**
|
|
119
|
-
* Demo: @splashcodex/api-key-manager v5.1
|
|
120
|
-
*
|
|
121
|
-
* Shows the two-layer pattern:
|
|
122
|
-
* Layer 1: loadCentralEnv() loads keys from ~/codedex/env/
|
|
123
|
-
* Layer 2: GeminiManager handles rotation, retries, circuit breaking
|
|
124
|
-
*/
|
|
125
|
-
import { loadCentralEnv } from '@splashcodex/api-key-manager/env';
|
|
126
|
-
import { GeminiManager } from '@splashcodex/api-key-manager/presets/gemini';
|
|
127
|
-
|
|
128
|
-
// Layer 1: Load centralized environment
|
|
129
|
-
const envResult = loadCentralEnv();
|
|
130
|
-
console.log(\`Loaded \${envResult.varsSet} env vars from \${envResult.filesLoaded.length} files\`);
|
|
131
|
-
|
|
132
|
-
async function main() {
|
|
133
|
-
// Layer 2: Initialize key manager (reads from process.env)
|
|
134
|
-
const result = GeminiManager.getInstance();
|
|
135
|
-
if (!result.success) {
|
|
136
|
-
console.error("Failed:", result.error.message);
|
|
137
|
-
console.error("Add your Gemini key to ~/codedex/env/llm.env");
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const gemini = result.data;
|
|
142
|
-
console.log(\`GeminiManager ready: \${gemini.getKeyCount()} key(s)\`);
|
|
143
|
-
|
|
144
|
-
try {
|
|
145
|
-
const response = await gemini.execute(async (key) => {
|
|
146
|
-
console.log(\`Using key: \${key.substring(0, 8)}...\`);
|
|
147
|
-
// Replace with actual API call
|
|
148
|
-
return "Simulated API Response";
|
|
149
|
-
}, { maxRetries: 3, timeoutMs: 30000 });
|
|
150
|
-
console.log("Result:", response);
|
|
151
|
-
} catch (e: any) {
|
|
152
|
-
console.error("Failed:", e.message);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
main();
|
|
157
|
-
`;
|
|
158
|
-
fs.writeFileSync(demoPath, tsCode);
|
|
159
|
-
console.log(`\x1b[92m[+] Created demo.ts\x1b[0m`);
|
|
160
|
-
} else {
|
|
161
|
-
console.log(`\x1b[90m[-] demo.ts already exists\x1b[0m`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
console.log(`\n\x1b[93mTo run the demo:\x1b[0m`);
|
|
165
|
-
console.log(`\x1b[36m npx ts-node demo.ts\x1b[0m\n`);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
switch (command) {
|
|
169
|
-
case 'init':
|
|
170
|
-
initProject();
|
|
171
|
-
break;
|
|
172
|
-
case 'setup':
|
|
173
|
-
setupEnvDir();
|
|
174
|
-
break;
|
|
175
|
-
case 'status':
|
|
176
|
-
showStatus();
|
|
177
|
-
break;
|
|
178
|
-
default:
|
|
179
|
-
printDocs();
|
|
180
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const command = args[0];
|
|
8
|
+
|
|
9
|
+
const ENV_DIR = path.join(os.homedir(), 'codedex', 'env');
|
|
10
|
+
|
|
11
|
+
function printDocs() {
|
|
12
|
+
console.log(`\x1b[92m@splashcodex/api-key-manager v5.1\x1b[0m`);
|
|
13
|
+
console.log(`\x1b[96mDocs: https://www.npmjs.com/package/@splashcodex/ApiKeyManager\x1b[0m\n`);
|
|
14
|
+
console.log(`\x1b[93mCommands:\x1b[0m`);
|
|
15
|
+
console.log(` npx @splashcodex/api-key-manager init \x1b[90m# Scaffold demo + central env directory\x1b[0m`);
|
|
16
|
+
console.log(` npx @splashcodex/api-key-manager setup \x1b[90m# Create ~/codedex/env/ with template files\x1b[0m`);
|
|
17
|
+
console.log(` npx @splashcodex/api-key-manager status \x1b[90m# Show which env files are loaded\x1b[0m`);
|
|
18
|
+
console.log('');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function setupEnvDir() {
|
|
22
|
+
console.log(`\n\x1b[96mSetting up centralized env directory...\x1b[0m\n`);
|
|
23
|
+
|
|
24
|
+
if (!fs.existsSync(ENV_DIR)) {
|
|
25
|
+
fs.mkdirSync(ENV_DIR, { recursive: true });
|
|
26
|
+
console.log(`\x1b[92m[+] Created ${ENV_DIR}\x1b[0m`);
|
|
27
|
+
} else {
|
|
28
|
+
console.log(`\x1b[90m[-] ${ENV_DIR} already exists\x1b[0m`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const templates = {
|
|
32
|
+
'common.env': [
|
|
33
|
+
'# Common environment variables shared across all projects',
|
|
34
|
+
'# DATABASE_URL="postgresql://user:pass@localhost:5432/mydb"',
|
|
35
|
+
'# REDIS_URL="redis://localhost:6379"',
|
|
36
|
+
'# SUPABASE_URL="https://your-project.supabase.co"',
|
|
37
|
+
'# SUPABASE_ANON_KEY="your-anon-key"',
|
|
38
|
+
'',
|
|
39
|
+
].join('\n'),
|
|
40
|
+
'llm.env': [
|
|
41
|
+
'# LLM API keys — supports comma-separated or JSON arrays',
|
|
42
|
+
'# GOOGLE_GEMINI_API_KEY="key1,key2,key3"',
|
|
43
|
+
'# OPENAI_API_KEY="sk-..."',
|
|
44
|
+
'# ANTHROPIC_API_KEY="sk-ant-..."',
|
|
45
|
+
'',
|
|
46
|
+
].join('\n'),
|
|
47
|
+
'stripe.env': [
|
|
48
|
+
'# Stripe payment keys',
|
|
49
|
+
'# STRIPE_SECRET_KEY="sk_test_..."',
|
|
50
|
+
'# STRIPE_WEBHOOK_SECRET="whsec_..."',
|
|
51
|
+
'',
|
|
52
|
+
].join('\n'),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
for (const [name, content] of Object.entries(templates)) {
|
|
56
|
+
const filePath = path.join(ENV_DIR, name);
|
|
57
|
+
if (!fs.existsSync(filePath)) {
|
|
58
|
+
fs.writeFileSync(filePath, content);
|
|
59
|
+
console.log(`\x1b[92m[+] Created ${name}\x1b[0m`);
|
|
60
|
+
} else {
|
|
61
|
+
console.log(`\x1b[90m[-] ${name} already exists\x1b[0m`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(`\n\x1b[93mNext steps:\x1b[0m`);
|
|
66
|
+
console.log(` 1. Add your real keys to the files in ${ENV_DIR}`);
|
|
67
|
+
console.log(` 2. Back up the folder to Google Drive for portability`);
|
|
68
|
+
console.log(` 3. In your projects, add at the entry point:`);
|
|
69
|
+
console.log(`\x1b[36m import { loadCentralEnv } from '@splashcodex/api-key-manager/env';\x1b[0m`);
|
|
70
|
+
console.log(`\x1b[36m loadCentralEnv();\x1b[0m\n`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function showStatus() {
|
|
74
|
+
console.log(`\n\x1b[96mCentralized env status\x1b[0m\n`);
|
|
75
|
+
console.log(` Directory: ${ENV_DIR}`);
|
|
76
|
+
|
|
77
|
+
if (!fs.existsSync(ENV_DIR)) {
|
|
78
|
+
console.log(` \x1b[31mNot found.\x1b[0m Run: npx @splashcodex/api-key-manager setup\n`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const files = fs.readdirSync(ENV_DIR).filter(f => f.endsWith('.env')).sort();
|
|
83
|
+
if (files.length === 0) {
|
|
84
|
+
console.log(` \x1b[33mNo .env files found.\x1b[0m\n`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let totalVars = 0;
|
|
89
|
+
for (const file of files) {
|
|
90
|
+
const content = fs.readFileSync(path.join(ENV_DIR, file), 'utf-8');
|
|
91
|
+
const vars = content.split('\n')
|
|
92
|
+
.filter(l => l.trim() && !l.trim().startsWith('#'))
|
|
93
|
+
.filter(l => l.includes('='));
|
|
94
|
+
totalVars += vars.length;
|
|
95
|
+
|
|
96
|
+
const icon = vars.length > 0 ? '\x1b[92m' : '\x1b[90m';
|
|
97
|
+
console.log(` ${icon}${file}\x1b[0m — ${vars.length} variable(s)`);
|
|
98
|
+
for (const v of vars) {
|
|
99
|
+
const key = v.split('=')[0].trim();
|
|
100
|
+
console.log(` \x1b[90m${key}\x1b[0m`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log(`\n Total: ${files.length} file(s), ${totalVars} variable(s)\n`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function initProject() {
|
|
108
|
+
const cwd = process.cwd();
|
|
109
|
+
const demoPath = path.join(cwd, 'demo.ts');
|
|
110
|
+
|
|
111
|
+
console.log(`\n\x1b[96mInitializing @splashcodex/api-key-manager...\x1b[0m\n`);
|
|
112
|
+
|
|
113
|
+
// Ensure central env directory exists
|
|
114
|
+
setupEnvDir();
|
|
115
|
+
|
|
116
|
+
// Create demo.ts
|
|
117
|
+
if (!fs.existsSync(demoPath)) {
|
|
118
|
+
const tsCode = `/**
|
|
119
|
+
* Demo: @splashcodex/api-key-manager v5.1
|
|
120
|
+
*
|
|
121
|
+
* Shows the two-layer pattern:
|
|
122
|
+
* Layer 1: loadCentralEnv() loads keys from ~/codedex/env/
|
|
123
|
+
* Layer 2: GeminiManager handles rotation, retries, circuit breaking
|
|
124
|
+
*/
|
|
125
|
+
import { loadCentralEnv } from '@splashcodex/api-key-manager/env';
|
|
126
|
+
import { GeminiManager } from '@splashcodex/api-key-manager/presets/gemini';
|
|
127
|
+
|
|
128
|
+
// Layer 1: Load centralized environment
|
|
129
|
+
const envResult = loadCentralEnv();
|
|
130
|
+
console.log(\`Loaded \${envResult.varsSet} env vars from \${envResult.filesLoaded.length} files\`);
|
|
131
|
+
|
|
132
|
+
async function main() {
|
|
133
|
+
// Layer 2: Initialize key manager (reads from process.env)
|
|
134
|
+
const result = GeminiManager.getInstance();
|
|
135
|
+
if (!result.success) {
|
|
136
|
+
console.error("Failed:", result.error.message);
|
|
137
|
+
console.error("Add your Gemini key to ~/codedex/env/llm.env");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const gemini = result.data;
|
|
142
|
+
console.log(\`GeminiManager ready: \${gemini.getKeyCount()} key(s)\`);
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const response = await gemini.execute(async (key) => {
|
|
146
|
+
console.log(\`Using key: \${key.substring(0, 8)}...\`);
|
|
147
|
+
// Replace with actual API call
|
|
148
|
+
return "Simulated API Response";
|
|
149
|
+
}, { maxRetries: 3, timeoutMs: 30000 });
|
|
150
|
+
console.log("Result:", response);
|
|
151
|
+
} catch (e: any) {
|
|
152
|
+
console.error("Failed:", e.message);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
main();
|
|
157
|
+
`;
|
|
158
|
+
fs.writeFileSync(demoPath, tsCode);
|
|
159
|
+
console.log(`\x1b[92m[+] Created demo.ts\x1b[0m`);
|
|
160
|
+
} else {
|
|
161
|
+
console.log(`\x1b[90m[-] demo.ts already exists\x1b[0m`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log(`\n\x1b[93mTo run the demo:\x1b[0m`);
|
|
165
|
+
console.log(`\x1b[36m npx ts-node demo.ts\x1b[0m\n`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
switch (command) {
|
|
169
|
+
case 'init':
|
|
170
|
+
initProject();
|
|
171
|
+
break;
|
|
172
|
+
case 'setup':
|
|
173
|
+
setupEnvDir();
|
|
174
|
+
break;
|
|
175
|
+
case 'status':
|
|
176
|
+
showStatus();
|
|
177
|
+
break;
|
|
178
|
+
default:
|
|
179
|
+
printDocs();
|
|
180
|
+
}
|
package/dist/env/loader.js
CHANGED
|
@@ -45,43 +45,27 @@ exports.getCentralEnvVar = getCentralEnvVar;
|
|
|
45
45
|
const fs_1 = require("fs");
|
|
46
46
|
const path_1 = require("path");
|
|
47
47
|
const os_1 = require("os");
|
|
48
|
+
const dotenv_1 = require("dotenv");
|
|
48
49
|
// ─── Parser ──────────────────────────────────────────────────────────────────
|
|
49
50
|
/**
|
|
50
|
-
* Parse a .env file into key-value pairs.
|
|
51
|
-
*
|
|
51
|
+
* Parse a .env file content string into key-value pairs.
|
|
52
|
+
*
|
|
53
|
+
* Delegates to `dotenv.parse()` for battle-tested parsing that handles:
|
|
52
54
|
* KEY=value
|
|
53
|
-
* KEY="quoted value"
|
|
55
|
+
* KEY="quoted value with spaces"
|
|
54
56
|
* KEY='single quoted'
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
+
* KEY=`backtick quoted`
|
|
58
|
+
* MULTILINE="line1\nline2" (real newlines inside double quotes)
|
|
59
|
+
* KEY=value # inline comment
|
|
57
60
|
* export KEY=value (bash-style)
|
|
61
|
+
* # full-line comments
|
|
62
|
+
* \n, \r, \t escape sequences
|
|
63
|
+
*
|
|
64
|
+
* Returns a Map<string, string> for compatibility with the rest of the loader.
|
|
58
65
|
*/
|
|
59
66
|
function parseEnvFile(content) {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
const line = rawLine.trim();
|
|
63
|
-
// Skip empty lines and comments
|
|
64
|
-
if (!line || line.startsWith('#'))
|
|
65
|
-
continue;
|
|
66
|
-
// Strip optional 'export ' prefix
|
|
67
|
-
const stripped = line.startsWith('export ') ? line.slice(7) : line;
|
|
68
|
-
// Find the first '=' that separates key from value
|
|
69
|
-
const eqIndex = stripped.indexOf('=');
|
|
70
|
-
if (eqIndex === -1)
|
|
71
|
-
continue;
|
|
72
|
-
const key = stripped.slice(0, eqIndex).trim();
|
|
73
|
-
let value = stripped.slice(eqIndex + 1).trim();
|
|
74
|
-
// Strip surrounding quotes
|
|
75
|
-
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
76
|
-
(value.startsWith("'") && value.endsWith("'"))) {
|
|
77
|
-
value = value.slice(1, -1);
|
|
78
|
-
}
|
|
79
|
-
// Skip empty keys
|
|
80
|
-
if (!key)
|
|
81
|
-
continue;
|
|
82
|
-
vars.set(key, value);
|
|
83
|
-
}
|
|
84
|
-
return vars;
|
|
67
|
+
const parsed = (0, dotenv_1.parse)(content);
|
|
68
|
+
return new Map(Object.entries(parsed));
|
|
85
69
|
}
|
|
86
70
|
// ─── Loader ──────────────────────────────────────────────────────────────────
|
|
87
71
|
/**
|
package/dist/env/loader.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/env/loader.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;;
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/env/loader.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* NEW in v5.0: Provider Presets (GeminiManager, OpenAIManager, MultiManager),
|
|
7
7
|
* Built-in Persistence (FileStorage, MemoryStorage)
|
|
8
8
|
* Gemini-Specific: finishReason handling, Safety blocks, RECITATION detection
|
|
9
|
+
* Infrastructure: cockatiel (Bulkhead queueing + ExponentialBackoff w/ decorrelated jitter)
|
|
9
10
|
*/
|
|
10
11
|
import { EventEmitter } from 'events';
|
|
12
|
+
import { z } from 'zod';
|
|
11
13
|
export { FileStorage } from './persistence/file';
|
|
12
14
|
export type { FileStorageOptions } from './persistence/file';
|
|
13
15
|
export { MemoryStorage } from './persistence/memory';
|
|
@@ -48,17 +50,19 @@ export interface ExecuteOptions {
|
|
|
48
50
|
finishReason?: string;
|
|
49
51
|
provider?: string;
|
|
50
52
|
}
|
|
51
|
-
export
|
|
52
|
-
storage
|
|
53
|
-
strategy
|
|
54
|
-
fallbackFn
|
|
55
|
-
concurrency
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
53
|
+
export declare const ApiKeyManagerOptionsSchema: z.ZodObject<{
|
|
54
|
+
storage: z.ZodOptional<z.ZodAny>;
|
|
55
|
+
strategy: z.ZodOptional<z.ZodAny>;
|
|
56
|
+
fallbackFn: z.ZodOptional<z.ZodCustom<() => any, () => any>>;
|
|
57
|
+
concurrency: z.ZodOptional<z.ZodNumber>;
|
|
58
|
+
concurrencyQueueSize: z.ZodOptional<z.ZodNumber>;
|
|
59
|
+
semanticCache: z.ZodOptional<z.ZodObject<{
|
|
60
|
+
threshold: z.ZodOptional<z.ZodNumber>;
|
|
61
|
+
ttlMs: z.ZodOptional<z.ZodNumber>;
|
|
62
|
+
getEmbedding: z.ZodCustom<(text: string) => Promise<number[]>, (text: string) => Promise<number[]>>;
|
|
63
|
+
}, z.core.$strip>>;
|
|
64
|
+
}, z.core.$loose>;
|
|
65
|
+
export type ApiKeyManagerOptions = z.infer<typeof ApiKeyManagerOptionsSchema>;
|
|
62
66
|
export interface CacheEntry {
|
|
63
67
|
vector: number[];
|
|
64
68
|
prompt: string;
|
|
@@ -135,8 +139,7 @@ export declare class ApiKeyManager extends EventEmitter {
|
|
|
135
139
|
private storage;
|
|
136
140
|
private strategy;
|
|
137
141
|
private fallbackFn?;
|
|
138
|
-
private
|
|
139
|
-
private activeCalls;
|
|
142
|
+
private bulkheadPolicy;
|
|
140
143
|
private healthCheckFn?;
|
|
141
144
|
private healthCheckInterval?;
|
|
142
145
|
private _saveTimer?;
|
|
@@ -177,6 +180,15 @@ export declare class ApiKeyManager extends EventEmitter {
|
|
|
177
180
|
markSuccess(key: string, durationMs?: number): void;
|
|
178
181
|
markFailed(key: string, classification: ErrorClassification): void;
|
|
179
182
|
markFailedLegacy(key: string, isQuota?: boolean): void;
|
|
183
|
+
/**
|
|
184
|
+
* Calculate exponential backoff with decorrelated jitter using cockatiel.
|
|
185
|
+
* Decorrelated jitter avoids thundering-herd by randomizing retry intervals
|
|
186
|
+
* independent of the previous delay, which is statistically superior to
|
|
187
|
+
* simple `random * exponential` jitter.
|
|
188
|
+
*
|
|
189
|
+
* @param attempt - Zero-indexed attempt number
|
|
190
|
+
*/
|
|
191
|
+
private readonly _backoffFactory;
|
|
180
192
|
calculateBackoff(attempt: number): number;
|
|
181
193
|
getStats(): ApiKeyManagerStats;
|
|
182
194
|
_getKeys(): KeyState[];
|
|
@@ -207,6 +219,11 @@ export declare class ApiKeyManager extends EventEmitter {
|
|
|
207
219
|
executeStream<T>(fn: (key: string, signal?: AbortSignal) => AsyncGenerator<T, any, unknown>, options?: ExecuteOptions & {
|
|
208
220
|
prompt?: string;
|
|
209
221
|
}): AsyncGenerator<T, any, unknown>;
|
|
222
|
+
/**
|
|
223
|
+
* Helper: stores result in semantic cache then returns it.
|
|
224
|
+
* Called by the bulkhead execute() callback and the no-bulkhead path.
|
|
225
|
+
*/
|
|
226
|
+
private _executeWithSemanticAndRetry;
|
|
210
227
|
private _executeWithRetry;
|
|
211
228
|
private _executeWithTimeout;
|
|
212
229
|
private _sleep;
|