learn-secrets-sdk 1.4.0 → 1.5.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.
@@ -1,19 +0,0 @@
1
- export interface EnvVariable {
2
- key: string;
3
- value: string;
4
- }
5
- /**
6
- * Parse a .env file into key-value pairs
7
- * Supports:
8
- * - KEY=value
9
- * - KEY="value"
10
- * - KEY='value'
11
- * - # comments
12
- * - Empty lines
13
- */
14
- export declare function parseEnvFile(filePath: string): EnvVariable[];
15
- /**
16
- * Load environment variables from a .env file
17
- * Returns a Map for easy lookup
18
- */
19
- export declare function loadEnvFile(filePath: string): Map<string, string>;
@@ -1,47 +0,0 @@
1
- import { readFileSync, existsSync } from 'fs';
2
- import { resolve } from 'path';
3
- /**
4
- * Parse a .env file into key-value pairs
5
- * Supports:
6
- * - KEY=value
7
- * - KEY="value"
8
- * - KEY='value'
9
- * - # comments
10
- * - Empty lines
11
- */
12
- export function parseEnvFile(filePath) {
13
- const resolvedPath = resolve(filePath);
14
- if (!existsSync(resolvedPath)) {
15
- throw new Error(`File not found: ${resolvedPath}`);
16
- }
17
- const content = readFileSync(resolvedPath, 'utf-8');
18
- const lines = content.split('\n');
19
- const variables = [];
20
- for (const line of lines) {
21
- const trimmed = line.trim();
22
- // Skip empty lines and comments
23
- if (!trimmed || trimmed.startsWith('#')) {
24
- continue;
25
- }
26
- // Match KEY=value, KEY="value", or KEY='value'
27
- const match = trimmed.match(/^([A-Z0-9_]+)\s*=\s*(['"]?)(.+?)\2$/i);
28
- if (match) {
29
- const key = match[1];
30
- const value = match[3];
31
- variables.push({ key, value });
32
- }
33
- }
34
- return variables;
35
- }
36
- /**
37
- * Load environment variables from a .env file
38
- * Returns a Map for easy lookup
39
- */
40
- export function loadEnvFile(filePath) {
41
- const variables = parseEnvFile(filePath);
42
- const map = new Map();
43
- for (const { key, value } of variables) {
44
- map.set(key, value);
45
- }
46
- return map;
47
- }
@@ -1,33 +0,0 @@
1
- import type { SecretConfig } from '../../types.js';
2
- import type { EnvVariable } from './env-parser.js';
3
- interface ProviderPattern {
4
- pattern: RegExp;
5
- provider: string;
6
- baseUrl?: string;
7
- authHeader?: string;
8
- authPrefix?: string;
9
- }
10
- /**
11
- * Detect if an environment variable is likely an API key
12
- */
13
- export declare function isLikelyApiKey(key: string, value: string): boolean;
14
- /**
15
- * Detect provider from environment variable name
16
- */
17
- export declare function detectProvider(key: string): ProviderPattern | null;
18
- /**
19
- * Convert environment variable to secret name
20
- * OPENAI_API_KEY -> openai
21
- * MY_API_KEY -> my-api
22
- */
23
- export declare function envKeyToSecretName(key: string): string;
24
- /**
25
- * Detect API keys from environment variables
26
- * Returns SecretConfig array ready for import
27
- */
28
- export declare function detectApiKeys(envVars: EnvVariable[]): SecretConfig[];
29
- /**
30
- * Display detected secrets summary (for user confirmation)
31
- */
32
- export declare function summarizeDetectedSecrets(secrets: SecretConfig[]): string;
33
- export {};
@@ -1,153 +0,0 @@
1
- /**
2
- * Common API key patterns and their providers
3
- */
4
- const PROVIDER_PATTERNS = [
5
- {
6
- pattern: /^OPENAI_API_KEY$/i,
7
- provider: 'openai',
8
- baseUrl: 'https://api.openai.com',
9
- authHeader: 'Authorization',
10
- authPrefix: 'Bearer '
11
- },
12
- {
13
- pattern: /^ANTHROPIC_API_KEY$/i,
14
- provider: 'anthropic',
15
- baseUrl: 'https://api.anthropic.com',
16
- authHeader: 'x-api-key',
17
- authPrefix: ''
18
- },
19
- {
20
- pattern: /^STRIPE_(SECRET_)?KEY$/i,
21
- provider: 'stripe',
22
- baseUrl: 'https://api.stripe.com',
23
- authHeader: 'Authorization',
24
- authPrefix: 'Bearer '
25
- },
26
- {
27
- pattern: /^GITHUB_TOKEN$/i,
28
- provider: 'github',
29
- baseUrl: 'https://api.github.com',
30
- authHeader: 'Authorization',
31
- authPrefix: 'Bearer '
32
- },
33
- {
34
- pattern: /^GOOGLE_API_KEY$/i,
35
- provider: 'google',
36
- baseUrl: 'https://www.googleapis.com',
37
- authHeader: 'Authorization',
38
- authPrefix: 'Bearer '
39
- }
40
- ];
41
- /**
42
- * Detect if an environment variable is likely an API key
43
- */
44
- export function isLikelyApiKey(key, value) {
45
- // Check common patterns
46
- const keyPatterns = [
47
- /_API_KEY$/i,
48
- /_SECRET$/i,
49
- /_TOKEN$/i,
50
- /^API_KEY_/i,
51
- /^SECRET_/i,
52
- /^TOKEN_/i
53
- ];
54
- for (const pattern of keyPatterns) {
55
- if (pattern.test(key)) {
56
- return true;
57
- }
58
- }
59
- // Check value patterns (looks like a key/token)
60
- const valuePatterns = [
61
- /^sk-[a-zA-Z0-9_-]+$/, // OpenAI, Stripe
62
- /^sk-ant-[a-zA-Z0-9_-]+$/, // Anthropic
63
- /^ghp_[a-zA-Z0-9]+$/, // GitHub
64
- /^ya29\.[a-zA-Z0-9_-]+$/, // Google OAuth
65
- /^[a-f0-9]{32}$/, // 32-char hex
66
- /^[a-f0-9]{40}$/, // 40-char hex (like GitHub)
67
- /^[a-zA-Z0-9_-]{40,}$/ // Long random string
68
- ];
69
- for (const pattern of valuePatterns) {
70
- if (pattern.test(value)) {
71
- return true;
72
- }
73
- }
74
- return false;
75
- }
76
- /**
77
- * Detect provider from environment variable name
78
- */
79
- export function detectProvider(key) {
80
- for (const pattern of PROVIDER_PATTERNS) {
81
- if (pattern.pattern.test(key)) {
82
- return pattern;
83
- }
84
- }
85
- return null;
86
- }
87
- /**
88
- * Convert environment variable to secret name
89
- * OPENAI_API_KEY -> openai
90
- * MY_API_KEY -> my-api
91
- */
92
- export function envKeyToSecretName(key) {
93
- return key
94
- .toLowerCase()
95
- .replace(/_api_key$/i, '')
96
- .replace(/_secret$/i, '')
97
- .replace(/_token$/i, '')
98
- .replace(/_key$/i, '')
99
- .replace(/_/g, '-');
100
- }
101
- /**
102
- * Detect API keys from environment variables
103
- * Returns SecretConfig array ready for import
104
- */
105
- export function detectApiKeys(envVars) {
106
- const secrets = [];
107
- for (const { key, value } of envVars) {
108
- // Skip if doesn't look like an API key
109
- if (!isLikelyApiKey(key, value)) {
110
- continue;
111
- }
112
- // Detect provider
113
- const providerInfo = detectProvider(key);
114
- const secretName = envKeyToSecretName(key);
115
- if (providerInfo) {
116
- // Known provider
117
- secrets.push({
118
- name: secretName,
119
- provider: providerInfo.provider,
120
- api_key: value,
121
- base_url: providerInfo.baseUrl,
122
- auth_header: providerInfo.authHeader,
123
- auth_prefix: providerInfo.authPrefix
124
- });
125
- }
126
- else {
127
- // Custom/unknown provider
128
- secrets.push({
129
- name: secretName,
130
- provider: 'custom',
131
- api_key: value,
132
- base_url: '', // User must configure
133
- auth_header: 'Authorization',
134
- auth_prefix: 'Bearer '
135
- });
136
- }
137
- }
138
- return secrets;
139
- }
140
- /**
141
- * Display detected secrets summary (for user confirmation)
142
- */
143
- export function summarizeDetectedSecrets(secrets) {
144
- if (secrets.length === 0) {
145
- return 'No API keys detected in .env file';
146
- }
147
- const lines = ['Detected API keys:'];
148
- for (const secret of secrets) {
149
- const masked = secret.api_key.substring(0, 8) + '...';
150
- lines.push(` - ${secret.name} (${secret.provider}): ${masked}`);
151
- }
152
- return lines.join('\n');
153
- }
package/dist/client.d.ts DELETED
@@ -1,65 +0,0 @@
1
- import type { SecretsSDKOptions, ProxyRequest, RateLimitInfo } from './types.js';
2
- export declare class SecretsSDK {
3
- private appId;
4
- private token;
5
- private baseUrl;
6
- private timeout;
7
- private retryOn429;
8
- private rateLimitInfo;
9
- private zeroConfigMode;
10
- /**
11
- * Create a new SecretsSDK instance
12
- *
13
- * Zero-config mode (recommended for static sites):
14
- * const sdk = new SecretsSDK();
15
- * // Origin header is used for authentication
16
- *
17
- * Token mode (for backward compatibility):
18
- * const sdk = new SecretsSDK({ appId: '...', token: 'sk_live_...' });
19
- */
20
- constructor(options?: SecretsSDKOptions);
21
- /**
22
- * Get current rate limit information
23
- */
24
- getUsage(): RateLimitInfo | null;
25
- /**
26
- * Parse rate limit headers from response
27
- */
28
- private parseRateLimitHeaders;
29
- /**
30
- * Sleep utility for retry backoff
31
- */
32
- private sleep;
33
- /**
34
- * Make fetch request with timeout
35
- */
36
- private fetchWithTimeout;
37
- /**
38
- * Call an external API securely through the proxy
39
- * @param keyName - Name of the API key to use
40
- * @param endpoint - API endpoint path (e.g., '/v1/chat/completions')
41
- * @param options - Request options (method, body, headers)
42
- * @returns Promise with the API response
43
- */
44
- call<T = any>(keyName: string, endpoint: string, options?: Omit<ProxyRequest, 'keyName' | 'endpoint'>): Promise<T>;
45
- /**
46
- * Make a GET request
47
- */
48
- get<T = any>(keyName: string, endpoint: string, headers?: Record<string, string>): Promise<T>;
49
- /**
50
- * Make a POST request
51
- */
52
- post<T = any>(keyName: string, endpoint: string, body: any, headers?: Record<string, string>): Promise<T>;
53
- /**
54
- * Make a PUT request
55
- */
56
- put<T = any>(keyName: string, endpoint: string, body: any, headers?: Record<string, string>): Promise<T>;
57
- /**
58
- * Make a DELETE request
59
- */
60
- delete<T = any>(keyName: string, endpoint: string, headers?: Record<string, string>): Promise<T>;
61
- /**
62
- * Make a PATCH request
63
- */
64
- patch<T = any>(keyName: string, endpoint: string, body: any, headers?: Record<string, string>): Promise<T>;
65
- }
package/dist/client.js DELETED
@@ -1,172 +0,0 @@
1
- import { SecretsSDKError, OriginMismatchError, RateLimitError, InvalidTokenError } from './types.js';
2
- export class SecretsSDK {
3
- /**
4
- * Create a new SecretsSDK instance
5
- *
6
- * Zero-config mode (recommended for static sites):
7
- * const sdk = new SecretsSDK();
8
- * // Origin header is used for authentication
9
- *
10
- * Token mode (for backward compatibility):
11
- * const sdk = new SecretsSDK({ appId: '...', token: 'sk_live_...' });
12
- */
13
- constructor(options = {}) {
14
- this.rateLimitInfo = null;
15
- this.appId = options.appId || null;
16
- this.token = options.token || options.sessionToken || null;
17
- this.baseUrl = options.baseUrl || 'https://ctklearn.carsontkempf.workers.dev';
18
- this.timeout = options.timeout || 30000;
19
- this.retryOn429 = options.retryOn429 !== undefined ? options.retryOn429 : true;
20
- // Zero-config mode: no appId or token required
21
- // Authentication is based on Origin header
22
- this.zeroConfigMode = !this.appId && !this.token;
23
- }
24
- /**
25
- * Get current rate limit information
26
- */
27
- getUsage() {
28
- return this.rateLimitInfo;
29
- }
30
- /**
31
- * Parse rate limit headers from response
32
- */
33
- parseRateLimitHeaders(headers) {
34
- const limit = headers.get('X-RateLimit-Limit');
35
- const remaining = headers.get('X-RateLimit-Remaining');
36
- const reset = headers.get('X-RateLimit-Reset');
37
- if (limit && remaining && reset) {
38
- this.rateLimitInfo = {
39
- limit: parseInt(limit),
40
- remaining: parseInt(remaining),
41
- reset: parseInt(reset)
42
- };
43
- }
44
- }
45
- /**
46
- * Sleep utility for retry backoff
47
- */
48
- sleep(ms) {
49
- return new Promise((resolve) => setTimeout(resolve, ms));
50
- }
51
- /**
52
- * Make fetch request with timeout
53
- */
54
- async fetchWithTimeout(url, options, timeout) {
55
- const controller = new AbortController();
56
- const timeoutId = setTimeout(() => controller.abort(), timeout);
57
- try {
58
- const response = await fetch(url, {
59
- ...options,
60
- signal: controller.signal
61
- });
62
- return response;
63
- }
64
- finally {
65
- clearTimeout(timeoutId);
66
- }
67
- }
68
- /**
69
- * Call an external API securely through the proxy
70
- * @param keyName - Name of the API key to use
71
- * @param endpoint - API endpoint path (e.g., '/v1/chat/completions')
72
- * @param options - Request options (method, body, headers)
73
- * @returns Promise with the API response
74
- */
75
- async call(keyName, endpoint, options = {}) {
76
- let retries = 0;
77
- const maxRetries = this.retryOn429 ? 3 : 0;
78
- while (true) {
79
- try {
80
- // Build URL based on mode
81
- const proxyUrl = this.zeroConfigMode
82
- ? `${this.baseUrl}/api/sdk/proxy`
83
- : `${this.baseUrl}/api/sdk/${this.appId}/proxy`;
84
- // Build headers based on mode
85
- const requestHeaders = {
86
- 'Content-Type': 'application/json'
87
- };
88
- if (!this.zeroConfigMode && this.token) {
89
- requestHeaders['Authorization'] = `Bearer ${this.token}`;
90
- }
91
- const response = await this.fetchWithTimeout(proxyUrl, {
92
- method: 'POST',
93
- headers: requestHeaders,
94
- body: JSON.stringify({
95
- keyName,
96
- endpoint,
97
- method: options.method || 'GET',
98
- body: options.body,
99
- headers: options.headers
100
- })
101
- }, this.timeout);
102
- this.parseRateLimitHeaders(response.headers);
103
- const result = await response.json();
104
- if (!response.ok) {
105
- const errorMessage = result.data?.message ||
106
- result?.message ||
107
- `Request failed with status ${response.status}`;
108
- if (response.status === 403 && errorMessage.includes('Origin')) {
109
- throw new OriginMismatchError(errorMessage);
110
- }
111
- if (response.status === 401) {
112
- throw new InvalidTokenError(errorMessage);
113
- }
114
- if (response.status === 429) {
115
- const retryAfter = this.rateLimitInfo
116
- ? this.rateLimitInfo.reset - Math.floor(Date.now() / 1000)
117
- : 60;
118
- const error = new RateLimitError(errorMessage, retryAfter, this.rateLimitInfo?.remaining || 0, this.rateLimitInfo?.limit || 100);
119
- if (this.retryOn429 && retries < maxRetries) {
120
- retries++;
121
- const backoffMs = Math.min(1000 * Math.pow(2, retries - 1), 8000);
122
- await this.sleep(backoffMs);
123
- continue;
124
- }
125
- throw error;
126
- }
127
- throw new SecretsSDKError(errorMessage, response.status, result);
128
- }
129
- return result.data;
130
- }
131
- catch (err) {
132
- if (err instanceof SecretsSDKError) {
133
- throw err;
134
- }
135
- if (err instanceof Error && err.name === 'AbortError') {
136
- throw new SecretsSDKError('Request timeout', 408);
137
- }
138
- throw new SecretsSDKError(err instanceof Error ? err.message : 'Unknown error occurred', 500);
139
- }
140
- }
141
- }
142
- /**
143
- * Make a GET request
144
- */
145
- async get(keyName, endpoint, headers) {
146
- return this.call(keyName, endpoint, { method: 'GET', headers });
147
- }
148
- /**
149
- * Make a POST request
150
- */
151
- async post(keyName, endpoint, body, headers) {
152
- return this.call(keyName, endpoint, { method: 'POST', body, headers });
153
- }
154
- /**
155
- * Make a PUT request
156
- */
157
- async put(keyName, endpoint, body, headers) {
158
- return this.call(keyName, endpoint, { method: 'PUT', body, headers });
159
- }
160
- /**
161
- * Make a DELETE request
162
- */
163
- async delete(keyName, endpoint, headers) {
164
- return this.call(keyName, endpoint, { method: 'DELETE', headers });
165
- }
166
- /**
167
- * Make a PATCH request
168
- */
169
- async patch(keyName, endpoint, body, headers) {
170
- return this.call(keyName, endpoint, { method: 'PATCH', body, headers });
171
- }
172
- }
@@ -1,24 +0,0 @@
1
- import type { CLICredentials } from './types.js';
2
- /**
3
- * Get the path to the credentials file
4
- * Defaults to ~/.learn/credentials.json
5
- */
6
- export declare function getCredentialsPath(): string;
7
- /**
8
- * Read credentials from file
9
- * Returns null if file doesn't exist or is invalid
10
- */
11
- export declare function readCredentials(): CLICredentials | null;
12
- /**
13
- * Write credentials to file
14
- * Creates ~/.learn directory if it doesn't exist
15
- */
16
- export declare function writeCredentials(creds: CLICredentials): void;
17
- /**
18
- * Delete credentials file
19
- */
20
- export declare function deleteCredentials(): void;
21
- /**
22
- * Check if credentials exist and are not expired
23
- */
24
- export declare function hasValidCredentials(): boolean;
@@ -1,86 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import * as os from 'os';
4
- /**
5
- * Get the path to the credentials file
6
- * Defaults to ~/.learn/credentials.json
7
- */
8
- export function getCredentialsPath() {
9
- const homeDir = os.homedir();
10
- const learnDir = path.join(homeDir, '.learn');
11
- return path.join(learnDir, 'credentials.json');
12
- }
13
- /**
14
- * Ensure the ~/.learn directory exists
15
- */
16
- function ensureLearnDir() {
17
- const homeDir = os.homedir();
18
- const learnDir = path.join(homeDir, '.learn');
19
- if (!fs.existsSync(learnDir)) {
20
- fs.mkdirSync(learnDir, { recursive: true, mode: 0o700 }); // Owner-only permissions
21
- }
22
- }
23
- /**
24
- * Read credentials from file
25
- * Returns null if file doesn't exist or is invalid
26
- */
27
- export function readCredentials() {
28
- try {
29
- const credPath = getCredentialsPath();
30
- if (!fs.existsSync(credPath)) {
31
- return null;
32
- }
33
- const data = fs.readFileSync(credPath, 'utf-8');
34
- const creds = JSON.parse(data);
35
- // Validate required fields
36
- if (!creds.access_token ||
37
- !creds.refresh_token ||
38
- !creds.expires_at ||
39
- !creds.user_id) {
40
- return null;
41
- }
42
- return creds;
43
- }
44
- catch (err) {
45
- // Invalid JSON or read error
46
- return null;
47
- }
48
- }
49
- /**
50
- * Write credentials to file
51
- * Creates ~/.learn directory if it doesn't exist
52
- */
53
- export function writeCredentials(creds) {
54
- ensureLearnDir();
55
- const credPath = getCredentialsPath();
56
- const data = JSON.stringify(creds, null, 2);
57
- // Write with owner-only permissions
58
- fs.writeFileSync(credPath, data, { mode: 0o600 });
59
- }
60
- /**
61
- * Delete credentials file
62
- */
63
- export function deleteCredentials() {
64
- try {
65
- const credPath = getCredentialsPath();
66
- if (fs.existsSync(credPath)) {
67
- fs.unlinkSync(credPath);
68
- }
69
- }
70
- catch (err) {
71
- // Ignore errors (file might not exist)
72
- }
73
- }
74
- /**
75
- * Check if credentials exist and are not expired
76
- */
77
- export function hasValidCredentials() {
78
- const creds = readCredentials();
79
- if (!creds) {
80
- return false;
81
- }
82
- // Check if expired
83
- const expiresAt = new Date(creds.expires_at);
84
- const now = new Date();
85
- return expiresAt > now;
86
- }
@@ -1,23 +0,0 @@
1
- import type { SecretConfig } from './types.js';
2
- /**
3
- * Resolve environment variable references in a string
4
- * Supports ${VAR} and $VAR syntax
5
- */
6
- export declare function resolveEnvString(value: string): string;
7
- /**
8
- * Resolve environment variables in a single secret config
9
- */
10
- export declare function resolveEnvInSecret(secret: SecretConfig): SecretConfig;
11
- /**
12
- * Resolve environment variables in an array of secrets
13
- */
14
- export declare function resolveEnvInSecrets(secrets: SecretConfig[]): SecretConfig[];
15
- /**
16
- * Load and parse a secrets config file
17
- * Resolves environment variables in the config
18
- */
19
- export declare function loadSecretsConfig(configPath: string): {
20
- version: string;
21
- project: string;
22
- secrets: SecretConfig[];
23
- };