@splashcodex/api-key-manager 5.0.2 → 5.2.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 CHANGED
@@ -1,206 +1,206 @@
1
- # @splashcodex/ApiKeyManager v5.0 — Ecosystem Edition
2
-
3
- > Universal API Key Management Gateway with Provider Presets, Built-in Persistence, and Multi-Provider Vault.
4
-
5
- [![npm version](https://img.shields.io/npm/v/@splashcodex/ApiKeyManager)](https://www.npmjs.com/package/@splashcodex/ApiKeyManager)
6
-
7
- ## New in v5.0 (Ecosystem Edition)
8
-
9
- - **Provider Presets** — One-line setup for `GeminiManager`, `OpenAIManager`, and `MultiManager`.
10
- - **Automatic Env Parsing** — Reads `GOOGLE_GEMINI_API_KEY`, `OPENAI_API_KEY`, etc. (supports JSON arrays and comma-separated strings).
11
- - **Built-in Persistence** — `FileStorage` (survives restarts) and `MemoryStorage` included.
12
- - **Singleton Pattern** — Thread-safe singletons with `getInstance()` and `Result<T>` pattern.
13
- - **Multi-Provider Vault** — Manage multiple providers (`gemini`, `openai`, `anthropic`) from a single entry point.
14
- - **Centralized API Gateway** — Built-in Fastify proxy server to centralize AI requests for multiple apps securely.
15
-
16
- ## Features
17
-
18
- - **Circuit Breaker** — Keys transition through `CLOSED → OPEN → HALF_OPEN → DEAD`
19
- - **Error Classification** — Automatic detection of 429 (Quota), 403 (Auth), 5xx (Transient), Timeout, Safety blocks
20
- - **Pluggable Strategies** — `StandardStrategy`, `WeightedStrategy`, `LatencyStrategy`
21
- - **`execute()` Wrapper** — Single method: get key → call → latency → retry → fallback
22
- - **Event Emitter** — Typed lifecycle hooks for monitoring & alerting
23
- - **Auto-Retry with Backoff** — Built-in retry loop with exponential backoff + jitter
24
- - **Semantic Cache** — Cosine-similarity cache with pluggable embeddings
25
- - **Streaming Support** — `executeStream()` with initial retry + cache replay
26
- - **100% Backward Compatible** — v1.x through v4.x code works without changes
27
-
28
- ## Installation
29
-
30
- ```bash
31
- npm install @splashcodex/api-key-manager
32
- ```
33
-
34
- ### 🚀 Auto-Scaffold (New!)
35
- The fastest way to get started is to run the `init` command in your project directory:
36
- ```bash
37
- npx @splashcodex/api-key-manager init
38
- ```
39
- This will automatically create a `.env` template and a `demo.ts` file showing the Gemini Preset in action.
40
-
41
- ## Quick Start (v5 Presets)
42
-
43
- The fastest way to get started in any CodeDex repository.
44
-
45
- ### Gemini Preset
46
- Reads `GOOGLE_GEMINI_API_KEY` or `GEMINI_API_KEY` from environment.
47
-
48
- ```typescript
49
- import { GeminiManager } from '@splashcodex/api-key-manager/presets/gemini';
50
-
51
- const result = GeminiManager.getInstance();
52
- if (!result.success) throw result.error;
53
-
54
- const gemini = result.data;
55
- const response = await gemini.execute(async (key) => {
56
- // result.data is the underlying ApiKeyManager
57
- return await callGemini(key, "Hello!");
58
- });
59
- ```
60
-
61
- ### Centralized API Gateway (New!)
62
-
63
- Run a standalone proxy server that acts as a single point of entry for all your microservices or repositories. It centralizes key management, circuit breaking, and rate limiting across applications.
64
-
65
- 1. Install global/local and export your keys:
66
- ```bash
67
- export GOOGLE_GEMINI_API_KEY="AIzaSy...1,AIzaSy...2"
68
- ```
69
-
70
- 2. Start the gateway:
71
- ```bash
72
- npm run gateway
73
- # Server runs on http://localhost:9000
74
- ```
75
-
76
- 3. Call the proxy from ANY language without managing keys in the client app:
77
- ```bash
78
- curl -X POST http://localhost:9000/v1/generate \
79
- -H "Content-Type: application/json" \
80
- -d '{"provider": "gemini", "prompt": "Hello!"}'
81
- ```
82
- Route supports both `/v1/generate` (Standard JSON) and `/v1/stream` (Server-Sent Events). Monitoring available at `/v1/health` and `/v1/providers`.
83
-
84
- ---
85
-
86
- ### Multi-Provider Vault
87
-
88
- Manage ALL your provider keys across your system from one pool.Perfect for gateways or complex bots handling multiple models.
89
-
90
- ```typescript
91
- import { MultiManager } from '@splashcodex/api-key-manager/presets/multi';
92
-
93
- const vault = MultiManager.getInstance({
94
- providers: {
95
- gemini: { envKeys: ['GOOGLE_GEMINI_API_KEY'] },
96
- openai: { envKeys: ['OPENAI_API_KEY'] }
97
- }
98
- }).data!;
99
-
100
- // Route by provider
101
- const res = await vault.execute(fn, { provider: 'gemini' });
102
- ```
103
-
104
- ---
105
-
106
- ## v5.0 — Architecture & Persistence
107
-
108
- ### Built-in Persistence
109
- State (cooling down keys, dead keys) now survives application restarts by default.
110
-
111
- ```typescript
112
- import { FileStorage } from '@splashcodex/api-key-manager/persistence/file';
113
-
114
- const manager = new ApiKeyManager(keys, {
115
- storage: new FileStorage({
116
- filePath: './api_state.json'
117
- })
118
- });
119
- ```
120
-
121
- ### Subpath Imports
122
- To keep your bundles small, you can import only what you need:
123
-
124
- ```typescript
125
- import { GeminiManager } from '@splashcodex/api-key-manager/presets/gemini';
126
- import { FileStorage } from '@splashcodex/api-key-manager/persistence/file';
127
- ```
128
-
129
- ---
130
-
131
- ## v4.0 — Semantic Cache
132
-
133
- Automatically cache API responses by semantic similarity.
134
-
135
- ```typescript
136
- const manager = new ApiKeyManager(['key1', 'key2'], {
137
- semanticCache: {
138
- threshold: 0.92,
139
- getEmbedding: async (text) => await myModel.embed(text)
140
- }
141
- });
142
-
143
- // Cache HIT if prompt is semantically similar
144
- const r1 = await manager.execute(apiFn, { prompt: 'What is the weather?' });
145
- const r2 = await manager.execute(apiFn, { prompt: 'How is the weather today?' });
146
- ```
147
-
148
- ### v4.1 — Streaming Support
149
- Real-time response handling with the same resilience as `execute()`.
150
-
151
- ```typescript
152
- const stream = await manager.executeStream(async (key) => {
153
- return await gemini.generateContentStream({ prompt: "..." });
154
- }, { prompt: "..." });
155
-
156
- for await (const chunk of stream) {
157
- console.log(chunk.text());
158
- }
159
- ```
160
-
161
- ---
162
-
163
- ## execute() Wrapper
164
-
165
- Wraps the entire lifecycle into one method:
166
-
167
- ```typescript
168
- const result = await manager.execute(
169
- async (key, signal) => {
170
- const res = await fetch(url, { headers: { 'x-api-key': key }, signal });
171
- return res.json();
172
- },
173
- { maxRetries: 3, timeoutMs: 10000 }
174
- );
175
- ```
176
-
177
- ## API Reference
178
-
179
- ### Presets
180
-
181
- | Class | Env Vars | Description |
182
- |-------|----------|-------------|
183
- | `GeminiManager` | `GOOGLE_GEMINI_API_KEY`, `GEMINI_API_KEY` | Gemini-optimized singleton |
184
- | `OpenAIManager` | `OPENAI_API_KEY` | OpenAI-optimized singleton |
185
- | `MultiManager` | Custom | Vault for multiple provider pools |
186
-
187
- ### Persistence
188
-
189
- | Class | Description |
190
- |-------|-------------|
191
- | `FileStorage` | Persists to a JSON file (recommended for servers) |
192
- | `MemoryStorage` | In-memory only (best for serverless/short-lived) |
193
-
194
- ### Core Methods
195
-
196
- | Method | Description |
197
- |--------|-------------|
198
- | `execute(fn, opts)` | Standard wrapper |
199
- | `executeStream(fn, opts)` | Streaming wrapper |
200
- | `getStats()` | Get pool health |
201
- | `getKey()` | Manual key rotation |
202
- | `markFailed(key, err)` | Manual failure reporting |
203
-
204
- ## License
205
-
206
- ISC
1
+ # @splashcodex/ApiKeyManager v5.0 — Ecosystem Edition
2
+
3
+ > Universal API Key Management Gateway with Provider Presets, Built-in Persistence, and Multi-Provider Vault.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@splashcodex/ApiKeyManager)](https://www.npmjs.com/package/@splashcodex/ApiKeyManager)
6
+
7
+ ## New in v5.0 (Ecosystem Edition)
8
+
9
+ - **Provider Presets** — One-line setup for `GeminiManager`, `OpenAIManager`, and `MultiManager`.
10
+ - **Automatic Env Parsing** — Reads `GOOGLE_GEMINI_API_KEY`, `OPENAI_API_KEY`, etc. (supports JSON arrays and comma-separated strings).
11
+ - **Built-in Persistence** — `FileStorage` (survives restarts) and `MemoryStorage` included.
12
+ - **Singleton Pattern** — Thread-safe singletons with `getInstance()` and `Result<T>` pattern.
13
+ - **Multi-Provider Vault** — Manage multiple providers (`gemini`, `openai`, `anthropic`) from a single entry point.
14
+ - **Centralized API Gateway** — Built-in Fastify proxy server to centralize AI requests for multiple apps securely.
15
+
16
+ ## Features
17
+
18
+ - **Circuit Breaker** — Keys transition through `CLOSED → OPEN → HALF_OPEN → DEAD`
19
+ - **Error Classification** — Automatic detection of 429 (Quota), 403 (Auth), 5xx (Transient), Timeout, Safety blocks
20
+ - **Pluggable Strategies** — `StandardStrategy`, `WeightedStrategy`, `LatencyStrategy`
21
+ - **`execute()` Wrapper** — Single method: get key → call → latency → retry → fallback
22
+ - **Event Emitter** — Typed lifecycle hooks for monitoring & alerting
23
+ - **Auto-Retry with Backoff** — Built-in retry loop with exponential backoff + jitter
24
+ - **Semantic Cache** — Cosine-similarity cache with pluggable embeddings
25
+ - **Streaming Support** — `executeStream()` with initial retry + cache replay
26
+ - **100% Backward Compatible** — v1.x through v4.x code works without changes
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install @splashcodex/api-key-manager
32
+ ```
33
+
34
+ ### 🚀 Auto-Scaffold (New!)
35
+ The fastest way to get started is to run the `init` command in your project directory:
36
+ ```bash
37
+ npx @splashcodex/api-key-manager init
38
+ ```
39
+ This will automatically create a `.env` template and a `demo.ts` file showing the Gemini Preset in action.
40
+
41
+ ## Quick Start (v5 Presets)
42
+
43
+ The fastest way to get started in any CodeDex repository.
44
+
45
+ ### Gemini Preset
46
+ Reads `GOOGLE_GEMINI_API_KEY` or `GEMINI_API_KEY` from environment.
47
+
48
+ ```typescript
49
+ import { GeminiManager } from '@splashcodex/api-key-manager/presets/gemini';
50
+
51
+ const result = GeminiManager.getInstance();
52
+ if (!result.success) throw result.error;
53
+
54
+ const gemini = result.data;
55
+ const response = await gemini.execute(async (key) => {
56
+ // result.data is the underlying ApiKeyManager
57
+ return await callGemini(key, "Hello!");
58
+ });
59
+ ```
60
+
61
+ ### Centralized API Gateway (New!)
62
+
63
+ Run a standalone proxy server that acts as a single point of entry for all your microservices or repositories. It centralizes key management, circuit breaking, and rate limiting across applications.
64
+
65
+ 1. Install global/local and export your keys:
66
+ ```bash
67
+ export GOOGLE_GEMINI_API_KEY="AIzaSy...1,AIzaSy...2"
68
+ ```
69
+
70
+ 2. Start the gateway:
71
+ ```bash
72
+ npm run gateway
73
+ # Server runs on http://localhost:9000
74
+ ```
75
+
76
+ 3. Call the proxy from ANY language without managing keys in the client app:
77
+ ```bash
78
+ curl -X POST http://localhost:9000/v1/generate \
79
+ -H "Content-Type: application/json" \
80
+ -d '{"provider": "gemini", "prompt": "Hello!"}'
81
+ ```
82
+ Route supports both `/v1/generate` (Standard JSON) and `/v1/stream` (Server-Sent Events). Monitoring available at `/v1/health` and `/v1/providers`.
83
+
84
+ ---
85
+
86
+ ### Multi-Provider Vault
87
+
88
+ Manage ALL your provider keys across your system from one pool.Perfect for gateways or complex bots handling multiple models.
89
+
90
+ ```typescript
91
+ import { MultiManager } from '@splashcodex/api-key-manager/presets/multi';
92
+
93
+ const vault = MultiManager.getInstance({
94
+ providers: {
95
+ gemini: { envKeys: ['GOOGLE_GEMINI_API_KEY'] },
96
+ openai: { envKeys: ['OPENAI_API_KEY'] }
97
+ }
98
+ }).data!;
99
+
100
+ // Route by provider
101
+ const res = await vault.execute(fn, { provider: 'gemini' });
102
+ ```
103
+
104
+ ---
105
+
106
+ ## v5.0 — Architecture & Persistence
107
+
108
+ ### Built-in Persistence
109
+ State (cooling down keys, dead keys) now survives application restarts by default.
110
+
111
+ ```typescript
112
+ import { FileStorage } from '@splashcodex/api-key-manager/persistence/file';
113
+
114
+ const manager = new ApiKeyManager(keys, {
115
+ storage: new FileStorage({
116
+ filePath: './api_state.json'
117
+ })
118
+ });
119
+ ```
120
+
121
+ ### Subpath Imports
122
+ To keep your bundles small, you can import only what you need:
123
+
124
+ ```typescript
125
+ import { GeminiManager } from '@splashcodex/api-key-manager/presets/gemini';
126
+ import { FileStorage } from '@splashcodex/api-key-manager/persistence/file';
127
+ ```
128
+
129
+ ---
130
+
131
+ ## v4.0 — Semantic Cache
132
+
133
+ Automatically cache API responses by semantic similarity.
134
+
135
+ ```typescript
136
+ const manager = new ApiKeyManager(['key1', 'key2'], {
137
+ semanticCache: {
138
+ threshold: 0.92,
139
+ getEmbedding: async (text) => await myModel.embed(text)
140
+ }
141
+ });
142
+
143
+ // Cache HIT if prompt is semantically similar
144
+ const r1 = await manager.execute(apiFn, { prompt: 'What is the weather?' });
145
+ const r2 = await manager.execute(apiFn, { prompt: 'How is the weather today?' });
146
+ ```
147
+
148
+ ### v4.1 — Streaming Support
149
+ Real-time response handling with the same resilience as `execute()`.
150
+
151
+ ```typescript
152
+ const stream = await manager.executeStream(async (key) => {
153
+ return await gemini.generateContentStream({ prompt: "..." });
154
+ }, { prompt: "..." });
155
+
156
+ for await (const chunk of stream) {
157
+ console.log(chunk.text());
158
+ }
159
+ ```
160
+
161
+ ---
162
+
163
+ ## execute() Wrapper
164
+
165
+ Wraps the entire lifecycle into one method:
166
+
167
+ ```typescript
168
+ const result = await manager.execute(
169
+ async (key, signal) => {
170
+ const res = await fetch(url, { headers: { 'x-api-key': key }, signal });
171
+ return res.json();
172
+ },
173
+ { maxRetries: 3, timeoutMs: 10000 }
174
+ );
175
+ ```
176
+
177
+ ## API Reference
178
+
179
+ ### Presets
180
+
181
+ | Class | Env Vars | Description |
182
+ |-------|----------|-------------|
183
+ | `GeminiManager` | `GOOGLE_GEMINI_API_KEY`, `GEMINI_API_KEY` | Gemini-optimized singleton |
184
+ | `OpenAIManager` | `OPENAI_API_KEY` | OpenAI-optimized singleton |
185
+ | `MultiManager` | Custom | Vault for multiple provider pools |
186
+
187
+ ### Persistence
188
+
189
+ | Class | Description |
190
+ |-------|-------------|
191
+ | `FileStorage` | Persists to a JSON file (recommended for servers) |
192
+ | `MemoryStorage` | In-memory only (best for serverless/short-lived) |
193
+
194
+ ### Core Methods
195
+
196
+ | Method | Description |
197
+ |--------|-------------|
198
+ | `execute(fn, opts)` | Standard wrapper |
199
+ | `executeStream(fn, opts)` | Streaming wrapper |
200
+ | `getStats()` | Get pool health |
201
+ | `getKey()` | Manual key rotation |
202
+ | `markFailed(key, err)` | Manual failure reporting |
203
+
204
+ ## License
205
+
206
+ ISC
package/bin/cli.js CHANGED
@@ -1,50 +1,180 @@
1
- #!/usr/bin/env node
2
- const fs = require('fs');
3
- const path = require('path');
4
-
5
- const args = process.argv.slice(2);
6
- const command = args[0];
7
-
8
- function printDocs() {
9
- console.log(`\x1b[92m🌟 @splashcodex/api-key-manager v5.0\x1b[0m`);
10
- console.log(`\x1b[96m📚 Documentation: https://www.npmjs.com/package/@splashcodex/ApiKeyManager\x1b[0m\n`);
11
- console.log(`\x1b[93m🚀 Commands:\x1b[0m`);
12
- console.log(` npx @splashcodex/api-key-manager init \x1b[90m# Scaffold a demo project in the current directory\x1b[0m`);
13
- console.log(`\n\x1b[95m💡 Tip: Need help? Run init to see it in action!\x1b[0m\n`);
14
- }
15
-
16
- function initProject() {
17
- const cwd = process.cwd();
18
- const envPath = path.join(cwd, '.env');
19
- const demoPath = path.join(cwd, 'demo.ts');
20
-
21
- console.log(`\n\x1b[96m🚀 Initializing @splashcodex/api-key-manager environment...\x1b[0m\n`);
22
-
23
- // Create .env
24
- if (!fs.existsSync(envPath)) {
25
- fs.writeFileSync(envPath, `GOOGLE_GEMINI_API_KEY="your_api_key_1,your_api_key_2"\nOPENAI_API_KEY="sk-..."\n`);
26
- console.log(`\x1b[92m[] Created .env file\x1b[0m`);
27
- } else {
28
- console.log(`\x1b[90m[-] .env already exists. Remember to add GOOGLE_GEMINI_API_KEY!\x1b[0m`);
29
- }
30
-
31
- // Create demo.ts
32
- if (!fs.existsSync(demoPath)) {
33
- const tsCode = `import 'dotenv/config';\nimport { GeminiManager } from '@splashcodex/api-key-manager/presets/gemini';\n\nasync function main() {\n console.log("Initialize GeminiManager...");\n // Automatically parse GOOGLE_GEMINI_API_KEY from env\n const result = GeminiManager.getInstance();\n if (!result.success) {\n console.error("Failed to initialize:", result.error);\n return;\n }\n \n const gemini = result.data;\n \n console.log("Executing resilient request...");\n // Automatically handles key rotation and 429 quota limits\n try {\n const response = await gemini.execute(async (key) => {\n console.log(\`[Network] Sending request with key: \${key.substring(0, 8)}...\`);\n // Replace with actual fetch: await fetch(..., { headers: { 'x-goog-api-key': key } })\n return "Success: Simulated API Response!";\n });\n console.log("Result:", response);\n } catch (e) {\n console.error("All keys exhausted or network failed.", e.message);\n }\n}\n\nmain();\n`;
34
- fs.writeFileSync(demoPath, tsCode);
35
- console.log(`\x1b[92m[✔] Created demo.ts\x1b[0m`);
36
- } else {
37
- console.log(`\x1b[90m[-] demo.ts already exists.\x1b[0m`);
38
- }
39
-
40
- console.log(`\n\x1b[93m🎉 Setup Complete!\x1b[0m`);
41
- console.log(`To run the demo:`);
42
- console.log(`\x1b[36m npm install dotenv\x1b[0m`);
43
- console.log(`\x1b[36m npx ts-node demo.ts\x1b[0m\n`);
44
- }
45
-
46
- if (command === 'init') {
47
- initProject();
48
- } else {
49
- printDocs();
50
- }
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
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Centralized Environment Loader
3
+ * @module env
4
+ */
5
+ export { loadCentralEnv, getCentralEnvVar, getDefaultEnvDir, } from './loader';
6
+ export type { LoadCentralEnvOptions, LoadResult, } from './loader';
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getDefaultEnvDir = exports.getCentralEnvVar = exports.loadCentralEnv = void 0;
4
+ /**
5
+ * Centralized Environment Loader
6
+ * @module env
7
+ */
8
+ var loader_1 = require("./loader");
9
+ Object.defineProperty(exports, "loadCentralEnv", { enumerable: true, get: function () { return loader_1.loadCentralEnv; } });
10
+ Object.defineProperty(exports, "getCentralEnvVar", { enumerable: true, get: function () { return loader_1.getCentralEnvVar; } });
11
+ Object.defineProperty(exports, "getDefaultEnvDir", { enumerable: true, get: function () { return loader_1.getDefaultEnvDir; } });
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,mCAIkB;AAHd,wGAAA,cAAc,OAAA;AACd,0GAAA,gBAAgB,OAAA;AAChB,0GAAA,gBAAgB,OAAA"}