@splashcodex/api-key-manager 5.2.0 → 5.3.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/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
+ }
@@ -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
- * Supports:
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
- * # comments
56
- * empty lines
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 vars = new Map();
61
- for (const rawLine of content.split('\n')) {
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
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/env/loader.ts"],"names":[],"mappings":"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"}
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 interface ApiKeyManagerOptions {
52
- storage?: any;
53
- strategy?: LoadBalancingStrategy;
54
- fallbackFn?: () => any;
55
- concurrency?: number;
56
- semanticCache?: {
57
- threshold?: number;
58
- ttlMs?: number;
59
- getEmbedding: (text: string) => Promise<number[]>;
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 maxConcurrency;
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;