@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/src/persistence/file.ts
CHANGED
|
@@ -1,89 +1,96 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File-Based Storage Adapter
|
|
3
|
-
*
|
|
4
|
-
* Persists API key state to a JSON file on disk.
|
|
5
|
-
* Survives process restarts so keys don't reset to CLOSED
|
|
6
|
-
* when an app reboots.
|
|
7
|
-
*
|
|
8
|
-
* Extracted from the proven WhatsDeX adapter pattern.
|
|
9
|
-
*
|
|
10
|
-
* @module persistence/file
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { writeFileSync, readFileSync, existsSync, mkdirSync, unlinkSync } from 'fs';
|
|
14
|
-
import { dirname, join } from 'path';
|
|
15
|
-
import { tmpdir } from 'os';
|
|
16
|
-
|
|
17
|
-
export interface FileStorageOptions {
|
|
18
|
-
/** Full path to the state file. Defaults to `os.tmpdir()/codedex_api_key_state.json` */
|
|
19
|
-
filePath?: string;
|
|
20
|
-
/** If true, clears any existing state file on construction (fresh start). Default: true */
|
|
21
|
-
clearOnInit?: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* File-based storage adapter for ApiKeyManager.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```ts
|
|
29
|
-
* import { ApiKeyManager } from '@splashcodex/api-key-manager';
|
|
30
|
-
* import { FileStorage } from '@splashcodex/api-key-manager/persistence/file';
|
|
31
|
-
*
|
|
32
|
-
* const manager = new ApiKeyManager(keys, {
|
|
33
|
-
* storage: new FileStorage({ filePath: './state.json' })
|
|
34
|
-
* });
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
export class FileStorage {
|
|
38
|
-
private filePath: string;
|
|
39
|
-
|
|
40
|
-
constructor(options: FileStorageOptions = {}) {
|
|
41
|
-
this.filePath = options.filePath || join(tmpdir(), 'codedex_api_key_state.json');
|
|
42
|
-
|
|
43
|
-
if (options.clearOnInit !== false) {
|
|
44
|
-
this.clear();
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
getItem(_key: string): string | null {
|
|
49
|
-
try {
|
|
50
|
-
if (existsSync(this.filePath)) {
|
|
51
|
-
return readFileSync(this.filePath, 'utf-8');
|
|
52
|
-
}
|
|
53
|
-
} catch {
|
|
54
|
-
// Silently fail — state will be rebuilt from scratch
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
setItem(_key: string, value: string): void {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* File-Based Storage Adapter
|
|
3
|
+
*
|
|
4
|
+
* Persists API key state to a JSON file on disk.
|
|
5
|
+
* Survives process restarts so keys don't reset to CLOSED
|
|
6
|
+
* when an app reboots.
|
|
7
|
+
*
|
|
8
|
+
* Extracted from the proven WhatsDeX adapter pattern.
|
|
9
|
+
*
|
|
10
|
+
* @module persistence/file
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { writeFileSync, readFileSync, existsSync, mkdirSync, unlinkSync, renameSync } from 'fs';
|
|
14
|
+
import { dirname, join } from 'path';
|
|
15
|
+
import { tmpdir } from 'os';
|
|
16
|
+
|
|
17
|
+
export interface FileStorageOptions {
|
|
18
|
+
/** Full path to the state file. Defaults to `os.tmpdir()/codedex_api_key_state.json` */
|
|
19
|
+
filePath?: string;
|
|
20
|
+
/** If true, clears any existing state file on construction (fresh start). Default: true */
|
|
21
|
+
clearOnInit?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* File-based storage adapter for ApiKeyManager.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { ApiKeyManager } from '@splashcodex/api-key-manager';
|
|
30
|
+
* import { FileStorage } from '@splashcodex/api-key-manager/persistence/file';
|
|
31
|
+
*
|
|
32
|
+
* const manager = new ApiKeyManager(keys, {
|
|
33
|
+
* storage: new FileStorage({ filePath: './state.json' })
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export class FileStorage {
|
|
38
|
+
private filePath: string;
|
|
39
|
+
|
|
40
|
+
constructor(options: FileStorageOptions = {}) {
|
|
41
|
+
this.filePath = options.filePath || join(tmpdir(), 'codedex_api_key_state.json');
|
|
42
|
+
|
|
43
|
+
if (options.clearOnInit !== false) {
|
|
44
|
+
this.clear();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getItem(_key: string): string | null {
|
|
49
|
+
try {
|
|
50
|
+
if (existsSync(this.filePath)) {
|
|
51
|
+
return readFileSync(this.filePath, 'utf-8');
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// Silently fail — state will be rebuilt from scratch
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
setItem(_key: string, value: string): void {
|
|
60
|
+
const tmpPath = this.filePath + '.tmp';
|
|
61
|
+
try {
|
|
62
|
+
const dir = dirname(this.filePath);
|
|
63
|
+
if (!existsSync(dir)) {
|
|
64
|
+
mkdirSync(dir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
// Write to a temp file first, then atomically rename to the target.
|
|
67
|
+
// renameSync() is a single syscall — the file is never in a
|
|
68
|
+
// half-written state if the process crashes mid-write.
|
|
69
|
+
writeFileSync(tmpPath, value, 'utf-8');
|
|
70
|
+
renameSync(tmpPath, this.filePath);
|
|
71
|
+
} catch {
|
|
72
|
+
// Clean up temp file if rename failed
|
|
73
|
+
try { if (existsSync(tmpPath)) unlinkSync(tmpPath); } catch { /* silent */ }
|
|
74
|
+
// Silently fail — state will be lost on restart
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Delete the persisted state file.
|
|
80
|
+
* Useful for clearing stale dead-key states from a previous session.
|
|
81
|
+
*/
|
|
82
|
+
clear(): void {
|
|
83
|
+
try {
|
|
84
|
+
if (existsSync(this.filePath)) {
|
|
85
|
+
unlinkSync(this.filePath);
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
// Silently fail
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Get the path to the state file (for debugging/logging) */
|
|
93
|
+
getFilePath(): string {
|
|
94
|
+
return this.filePath;
|
|
95
|
+
}
|
|
96
|
+
}
|
package/src/persistence/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Persistence Adapters
|
|
3
|
-
* @module persistence
|
|
4
|
-
*/
|
|
5
|
-
export { FileStorage } from './file';
|
|
6
|
-
export type { FileStorageOptions } from './file';
|
|
7
|
-
export { MemoryStorage } from './memory';
|
|
1
|
+
/**
|
|
2
|
+
* Persistence Adapters
|
|
3
|
+
* @module persistence
|
|
4
|
+
*/
|
|
5
|
+
export { FileStorage } from './file';
|
|
6
|
+
export type { FileStorageOptions } from './file';
|
|
7
|
+
export { MemoryStorage } from './memory';
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-Memory Storage Adapter
|
|
3
|
-
*
|
|
4
|
-
* Simple key-value storage that lives only for the process lifetime.
|
|
5
|
-
* Useful for testing, serverless functions, or when persistence isn't needed.
|
|
6
|
-
*
|
|
7
|
-
* @module persistence/memory
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* In-memory storage adapter for ApiKeyManager.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```ts
|
|
15
|
-
* import { ApiKeyManager } from '@splashcodex/api-key-manager';
|
|
16
|
-
* import { MemoryStorage } from '@splashcodex/api-key-manager/persistence/memory';
|
|
17
|
-
*
|
|
18
|
-
* const manager = new ApiKeyManager(keys, {
|
|
19
|
-
* storage: new MemoryStorage()
|
|
20
|
-
* });
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
export class MemoryStorage {
|
|
24
|
-
private store: Map<string, string> = new Map();
|
|
25
|
-
|
|
26
|
-
getItem(key: string): string | null {
|
|
27
|
-
return this.store.get(key) ?? null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
setItem(key: string, value: string): void {
|
|
31
|
-
this.store.set(key, value);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Clear all stored state */
|
|
35
|
-
clear(): void {
|
|
36
|
-
this.store.clear();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Get the number of stored entries */
|
|
40
|
-
get size(): number {
|
|
41
|
-
return this.store.size;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory Storage Adapter
|
|
3
|
+
*
|
|
4
|
+
* Simple key-value storage that lives only for the process lifetime.
|
|
5
|
+
* Useful for testing, serverless functions, or when persistence isn't needed.
|
|
6
|
+
*
|
|
7
|
+
* @module persistence/memory
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* In-memory storage adapter for ApiKeyManager.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { ApiKeyManager } from '@splashcodex/api-key-manager';
|
|
16
|
+
* import { MemoryStorage } from '@splashcodex/api-key-manager/persistence/memory';
|
|
17
|
+
*
|
|
18
|
+
* const manager = new ApiKeyManager(keys, {
|
|
19
|
+
* storage: new MemoryStorage()
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export class MemoryStorage {
|
|
24
|
+
private store: Map<string, string> = new Map();
|
|
25
|
+
|
|
26
|
+
getItem(key: string): string | null {
|
|
27
|
+
return this.store.get(key) ?? null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setItem(key: string, value: string): void {
|
|
31
|
+
this.store.set(key, value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Clear all stored state */
|
|
35
|
+
clear(): void {
|
|
36
|
+
this.store.clear();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Get the number of stored entries */
|
|
40
|
+
get size(): number {
|
|
41
|
+
return this.store.size;
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/presets/anthropic.ts
CHANGED
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Anthropic Preset — One-line Claude API key management
|
|
3
|
-
*
|
|
4
|
-
* Pre-configured singleton with:
|
|
5
|
-
* - Reads `ANTHROPIC_API_KEY` from env
|
|
6
|
-
* - LatencyStrategy for optimal key selection
|
|
7
|
-
* - File persistence (survives restarts)
|
|
8
|
-
* - Health checks every 5 minutes
|
|
9
|
-
* - Concurrency limit of 20
|
|
10
|
-
*
|
|
11
|
-
* @module presets/anthropic
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```ts
|
|
15
|
-
* import { AnthropicManager } from '@splashcodex/api-key-manager/presets/anthropic';
|
|
16
|
-
*
|
|
17
|
-
* const result = AnthropicManager.getInstance();
|
|
18
|
-
* if (!result.success) throw result.error;
|
|
19
|
-
* const claude = result.data;
|
|
20
|
-
*
|
|
21
|
-
* const response = await claude.execute(async (key) => {
|
|
22
|
-
* const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
23
|
-
* method: 'POST',
|
|
24
|
-
* headers: {
|
|
25
|
-
* 'x-api-key': key,
|
|
26
|
-
* 'anthropic-version': '2023-06-01',
|
|
27
|
-
* 'content-type': 'application/json',
|
|
28
|
-
* },
|
|
29
|
-
* body: JSON.stringify({
|
|
30
|
-
* model: 'claude-sonnet-4-20250514',
|
|
31
|
-
* max_tokens: 1024,
|
|
32
|
-
* messages: [{ role: 'user', content: 'Hello!' }],
|
|
33
|
-
* }),
|
|
34
|
-
* });
|
|
35
|
-
* const data = await res.json();
|
|
36
|
-
* return data.content[0].text;
|
|
37
|
-
* }, { maxRetries: 3, timeoutMs: 60000 });
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
import { BasePreset, PresetOptions, Result } from './base';
|
|
42
|
-
|
|
43
|
-
export class AnthropicManager extends BasePreset {
|
|
44
|
-
private static readonly PROVIDER = 'anthropic';
|
|
45
|
-
|
|
46
|
-
private constructor(keys: string[], options: PresetOptions) {
|
|
47
|
-
super(keys, options);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
private static getDefaultOptions(): Partial<PresetOptions> {
|
|
51
|
-
return {
|
|
52
|
-
envKeys: ['ANTHROPIC_API_KEY'],
|
|
53
|
-
provider: AnthropicManager.PROVIDER,
|
|
54
|
-
concurrency: 20,
|
|
55
|
-
healthCheckIntervalMs: 300_000,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get or create the singleton AnthropicManager instance.
|
|
61
|
-
*
|
|
62
|
-
* @param overrides - Optional configuration overrides
|
|
63
|
-
* @returns Result<AnthropicManager>
|
|
64
|
-
*/
|
|
65
|
-
static getInstance(overrides?: Partial<PresetOptions>): Result<AnthropicManager> {
|
|
66
|
-
return BasePreset.createInstance(
|
|
67
|
-
AnthropicManager as unknown as new (keys: string[], options: PresetOptions) => AnthropicManager,
|
|
68
|
-
AnthropicManager.getDefaultOptions(),
|
|
69
|
-
overrides
|
|
70
|
-
) as Result<AnthropicManager>;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
static reset(): void {
|
|
74
|
-
BasePreset.resetInstance(AnthropicManager.PROVIDER);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export { Result } from './base';
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic Preset — One-line Claude API key management
|
|
3
|
+
*
|
|
4
|
+
* Pre-configured singleton with:
|
|
5
|
+
* - Reads `ANTHROPIC_API_KEY` from env
|
|
6
|
+
* - LatencyStrategy for optimal key selection
|
|
7
|
+
* - File persistence (survives restarts)
|
|
8
|
+
* - Health checks every 5 minutes
|
|
9
|
+
* - Concurrency limit of 20
|
|
10
|
+
*
|
|
11
|
+
* @module presets/anthropic
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { AnthropicManager } from '@splashcodex/api-key-manager/presets/anthropic';
|
|
16
|
+
*
|
|
17
|
+
* const result = AnthropicManager.getInstance();
|
|
18
|
+
* if (!result.success) throw result.error;
|
|
19
|
+
* const claude = result.data;
|
|
20
|
+
*
|
|
21
|
+
* const response = await claude.execute(async (key) => {
|
|
22
|
+
* const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
23
|
+
* method: 'POST',
|
|
24
|
+
* headers: {
|
|
25
|
+
* 'x-api-key': key,
|
|
26
|
+
* 'anthropic-version': '2023-06-01',
|
|
27
|
+
* 'content-type': 'application/json',
|
|
28
|
+
* },
|
|
29
|
+
* body: JSON.stringify({
|
|
30
|
+
* model: 'claude-sonnet-4-20250514',
|
|
31
|
+
* max_tokens: 1024,
|
|
32
|
+
* messages: [{ role: 'user', content: 'Hello!' }],
|
|
33
|
+
* }),
|
|
34
|
+
* });
|
|
35
|
+
* const data = await res.json();
|
|
36
|
+
* return data.content[0].text;
|
|
37
|
+
* }, { maxRetries: 3, timeoutMs: 60000 });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { BasePreset, PresetOptions, Result } from './base';
|
|
42
|
+
|
|
43
|
+
export class AnthropicManager extends BasePreset {
|
|
44
|
+
private static readonly PROVIDER = 'anthropic';
|
|
45
|
+
|
|
46
|
+
private constructor(keys: string[], options: PresetOptions) {
|
|
47
|
+
super(keys, options);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private static getDefaultOptions(): Partial<PresetOptions> {
|
|
51
|
+
return {
|
|
52
|
+
envKeys: ['ANTHROPIC_API_KEY'],
|
|
53
|
+
provider: AnthropicManager.PROVIDER,
|
|
54
|
+
concurrency: 20,
|
|
55
|
+
healthCheckIntervalMs: 300_000,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get or create the singleton AnthropicManager instance.
|
|
61
|
+
*
|
|
62
|
+
* @param overrides - Optional configuration overrides
|
|
63
|
+
* @returns Result<AnthropicManager>
|
|
64
|
+
*/
|
|
65
|
+
static getInstance(overrides?: Partial<PresetOptions>): Result<AnthropicManager> {
|
|
66
|
+
return BasePreset.createInstance(
|
|
67
|
+
AnthropicManager as unknown as new (keys: string[], options: PresetOptions) => AnthropicManager,
|
|
68
|
+
AnthropicManager.getDefaultOptions(),
|
|
69
|
+
overrides
|
|
70
|
+
) as Result<AnthropicManager>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static reset(): void {
|
|
74
|
+
BasePreset.resetInstance(AnthropicManager.PROVIDER);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { Result } from './base';
|