@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 +206 -206
- package/bin/cli.js +180 -50
- package/dist/env/index.d.ts +6 -0
- package/dist/env/index.js +12 -0
- package/dist/env/index.js.map +1 -0
- package/dist/env/loader.d.ts +95 -0
- package/dist/env/loader.js +188 -0
- package/dist/env/loader.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +61 -2
- package/dist/index.js.map +1 -1
- package/dist/presets/anthropic.d.ts +54 -0
- package/dist/presets/anthropic.js +71 -0
- package/dist/presets/anthropic.js.map +1 -0
- package/dist/presets/base.js +25 -6
- package/dist/presets/base.js.map +1 -1
- package/dist/presets/index.d.ts +1 -0
- package/dist/presets/index.js +3 -1
- package/dist/presets/index.js.map +1 -1
- package/dist/presets/multi.js +12 -3
- package/dist/presets/multi.js.map +1 -1
- package/package.json +151 -123
- package/src/env/index.ts +14 -0
- package/src/env/loader.ts +249 -0
- package/src/index.ts +1071 -1005
- package/src/persistence/file.ts +89 -89
- package/src/persistence/index.ts +7 -7
- package/src/persistence/memory.ts +43 -43
- package/src/presets/anthropic.ts +78 -0
- package/src/presets/base.ts +344 -323
- package/src/presets/gemini.ts +84 -84
- package/src/presets/index.ts +11 -10
- package/src/presets/multi.ts +290 -280
- package/src/presets/openai.ts +69 -69
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
|
-
[](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
|
+
[](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
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
console.log(
|
|
13
|
-
console.log(`\
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (!fs.existsSync(
|
|
25
|
-
fs.
|
|
26
|
-
console.log(`\x1b[92m[
|
|
27
|
-
} else {
|
|
28
|
-
console.log(`\x1b[90m[-]
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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,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"}
|