byok-llm 1.0.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/dist/index.js ADDED
@@ -0,0 +1,848 @@
1
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, chmodSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { homedir } from 'os';
4
+ import { createInterface } from 'readline';
5
+
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+
16
+ // src/providers/anthropic.ts
17
+ var anthropic;
18
+ var init_anthropic = __esm({
19
+ "src/providers/anthropic.ts"() {
20
+ anthropic = {
21
+ id: "anthropic",
22
+ name: "Anthropic",
23
+ envKeys: ["ANTHROPIC_API_KEY"],
24
+ keyPrefix: "sk-ant-",
25
+ baseUrl: "https://api.anthropic.com",
26
+ docsUrl: "https://console.anthropic.com/settings/keys",
27
+ authStyle: "x-api-key",
28
+ authHeader: "x-api-key",
29
+ defaultHeaders: {
30
+ "anthropic-version": "2023-06-01"
31
+ },
32
+ validateEndpoint: "/v1/models",
33
+ openaiCompatible: false
34
+ };
35
+ }
36
+ });
37
+
38
+ // src/providers/openai.ts
39
+ var openai;
40
+ var init_openai = __esm({
41
+ "src/providers/openai.ts"() {
42
+ openai = {
43
+ id: "openai",
44
+ name: "OpenAI",
45
+ envKeys: ["OPENAI_API_KEY"],
46
+ keyPrefix: "sk-",
47
+ baseUrl: "https://api.openai.com",
48
+ docsUrl: "https://platform.openai.com/api-keys",
49
+ authStyle: "bearer",
50
+ validateEndpoint: "/v1/models",
51
+ openaiCompatible: true
52
+ };
53
+ }
54
+ });
55
+
56
+ // src/providers/openrouter.ts
57
+ var openrouter;
58
+ var init_openrouter = __esm({
59
+ "src/providers/openrouter.ts"() {
60
+ openrouter = {
61
+ id: "openrouter",
62
+ name: "OpenRouter",
63
+ envKeys: ["OPENROUTER_API_KEY"],
64
+ keyPrefix: "sk-or-",
65
+ baseUrl: "https://openrouter.ai/api",
66
+ docsUrl: "https://openrouter.ai/keys",
67
+ authStyle: "bearer",
68
+ validateEndpoint: "/v1/auth/key",
69
+ openaiCompatible: true
70
+ };
71
+ }
72
+ });
73
+
74
+ // src/providers/google.ts
75
+ var google;
76
+ var init_google = __esm({
77
+ "src/providers/google.ts"() {
78
+ google = {
79
+ id: "google",
80
+ name: "Google Gemini",
81
+ envKeys: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
82
+ baseUrl: "https://generativelanguage.googleapis.com",
83
+ docsUrl: "https://aistudio.google.com/app/apikey",
84
+ authStyle: "query-param",
85
+ validateEndpoint: "/v1beta/models",
86
+ openaiCompatible: false
87
+ };
88
+ }
89
+ });
90
+
91
+ // src/providers/groq.ts
92
+ var groq;
93
+ var init_groq = __esm({
94
+ "src/providers/groq.ts"() {
95
+ groq = {
96
+ id: "groq",
97
+ name: "Groq",
98
+ envKeys: ["GROQ_API_KEY"],
99
+ keyPrefix: "gsk_",
100
+ baseUrl: "https://api.groq.com/openai",
101
+ docsUrl: "https://console.groq.com/keys",
102
+ authStyle: "bearer",
103
+ validateEndpoint: "/v1/models",
104
+ openaiCompatible: true
105
+ };
106
+ }
107
+ });
108
+
109
+ // src/providers/xai.ts
110
+ var xai;
111
+ var init_xai = __esm({
112
+ "src/providers/xai.ts"() {
113
+ xai = {
114
+ id: "xai",
115
+ name: "xAI (Grok)",
116
+ envKeys: ["XAI_API_KEY"],
117
+ keyPrefix: "xai-",
118
+ baseUrl: "https://api.x.ai",
119
+ docsUrl: "https://console.x.ai/",
120
+ authStyle: "bearer",
121
+ validateEndpoint: "/v1/models",
122
+ openaiCompatible: true
123
+ };
124
+ }
125
+ });
126
+
127
+ // src/providers/mistral.ts
128
+ var mistral;
129
+ var init_mistral = __esm({
130
+ "src/providers/mistral.ts"() {
131
+ mistral = {
132
+ id: "mistral",
133
+ name: "Mistral AI",
134
+ envKeys: ["MISTRAL_API_KEY"],
135
+ baseUrl: "https://api.mistral.ai",
136
+ docsUrl: "https://console.mistral.ai/api-keys",
137
+ authStyle: "bearer",
138
+ validateEndpoint: "/v1/models",
139
+ openaiCompatible: true
140
+ };
141
+ }
142
+ });
143
+
144
+ // src/providers/deepseek.ts
145
+ var deepseek;
146
+ var init_deepseek = __esm({
147
+ "src/providers/deepseek.ts"() {
148
+ deepseek = {
149
+ id: "deepseek",
150
+ name: "DeepSeek",
151
+ envKeys: ["DEEPSEEK_API_KEY"],
152
+ keyPrefix: "sk-",
153
+ baseUrl: "https://api.deepseek.com",
154
+ docsUrl: "https://platform.deepseek.com/api_keys",
155
+ authStyle: "bearer",
156
+ validateEndpoint: "/models",
157
+ openaiCompatible: true
158
+ };
159
+ }
160
+ });
161
+
162
+ // src/providers/registry.ts
163
+ var registry_exports = {};
164
+ __export(registry_exports, {
165
+ DEFAULT_FALLBACK_ORDER: () => DEFAULT_FALLBACK_ORDER,
166
+ getProvider: () => getProvider,
167
+ hasProvider: () => hasProvider,
168
+ listProviders: () => listProviders,
169
+ registerProvider: () => registerProvider
170
+ });
171
+ function registerBuiltins() {
172
+ for (const provider of [anthropic, openai, openrouter, google, groq, xai, mistral, deepseek]) {
173
+ registry.set(provider.id, provider);
174
+ }
175
+ }
176
+ function registerProvider(definition) {
177
+ registry.set(definition.id, definition);
178
+ }
179
+ function getProvider(id) {
180
+ return registry.get(id);
181
+ }
182
+ function listProviders() {
183
+ return Array.from(registry.values());
184
+ }
185
+ function hasProvider(id) {
186
+ return registry.has(id);
187
+ }
188
+ var registry, DEFAULT_FALLBACK_ORDER;
189
+ var init_registry = __esm({
190
+ "src/providers/registry.ts"() {
191
+ init_anthropic();
192
+ init_openai();
193
+ init_openrouter();
194
+ init_google();
195
+ init_groq();
196
+ init_xai();
197
+ init_mistral();
198
+ init_deepseek();
199
+ registry = /* @__PURE__ */ new Map();
200
+ registerBuiltins();
201
+ DEFAULT_FALLBACK_ORDER = [
202
+ "anthropic",
203
+ "openai",
204
+ "openrouter",
205
+ "google",
206
+ "groq",
207
+ "xai",
208
+ "mistral",
209
+ "deepseek"
210
+ ];
211
+ }
212
+ });
213
+
214
+ // src/index.ts
215
+ init_registry();
216
+
217
+ // src/config/resolver.ts
218
+ init_registry();
219
+ function getConfigHome() {
220
+ return process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
221
+ }
222
+ function getUserConfigPath() {
223
+ return join(getConfigHome(), "byok-ai", "config.json");
224
+ }
225
+ function getKeysStorePath() {
226
+ return join(getConfigHome(), "byok-ai", "keys.json");
227
+ }
228
+ function resolveValue(value) {
229
+ const envMatch = value.match(/^\{env:([^}]+)\}$/);
230
+ if (envMatch) {
231
+ return process.env[envMatch[1]] || "";
232
+ }
233
+ const fileMatch = value.match(/^\{file:([^}]+)\}$/);
234
+ if (fileMatch) {
235
+ const filePath = fileMatch[1].replace(/^~/, homedir());
236
+ try {
237
+ return readFileSync(filePath, "utf-8").trim();
238
+ } catch {
239
+ return "";
240
+ }
241
+ }
242
+ return value;
243
+ }
244
+ function readJsonConfig(filePath) {
245
+ try {
246
+ if (!existsSync(filePath)) return null;
247
+ const raw = readFileSync(filePath, "utf-8");
248
+ const parsed = JSON.parse(raw);
249
+ if (parsed.providers) {
250
+ for (const [, config] of Object.entries(parsed.providers)) {
251
+ const providerConf = config;
252
+ if (typeof providerConf.apiKey === "string") {
253
+ providerConf.apiKey = resolveValue(providerConf.apiKey);
254
+ }
255
+ }
256
+ }
257
+ return parsed;
258
+ } catch {
259
+ return null;
260
+ }
261
+ }
262
+ function readPackageJsonConfig() {
263
+ try {
264
+ const pkgPath = join(process.cwd(), "package.json");
265
+ if (!existsSync(pkgPath)) return null;
266
+ const raw = readFileSync(pkgPath, "utf-8");
267
+ const pkg = JSON.parse(raw);
268
+ if (pkg.byok && typeof pkg.byok === "object") {
269
+ return pkg.byok;
270
+ }
271
+ return null;
272
+ } catch {
273
+ return null;
274
+ }
275
+ }
276
+ var PROJECT_CONFIG_NAMES = [".byokrc.json", ".byokrc", "byok.config.json"];
277
+ function findProjectConfig() {
278
+ for (const name of PROJECT_CONFIG_NAMES) {
279
+ const filePath = join(process.cwd(), name);
280
+ const config = readJsonConfig(filePath);
281
+ if (config) return config;
282
+ }
283
+ return readPackageJsonConfig();
284
+ }
285
+ function mergeConfigs(base, override) {
286
+ const merged = { ...base, ...override };
287
+ if (base.providers || override.providers) {
288
+ merged.providers = { ...base.providers };
289
+ if (override.providers) {
290
+ for (const [id, config] of Object.entries(override.providers)) {
291
+ merged.providers[id] = { ...merged.providers[id], ...config };
292
+ }
293
+ }
294
+ }
295
+ return merged;
296
+ }
297
+ function loadConfig(customPath) {
298
+ let config = {};
299
+ const sources = [];
300
+ const userConfig = readJsonConfig(getUserConfigPath());
301
+ if (userConfig) {
302
+ config = mergeConfigs(config, userConfig);
303
+ sources.push("user-config");
304
+ }
305
+ const projectConfig = findProjectConfig();
306
+ if (projectConfig) {
307
+ config = mergeConfigs(config, projectConfig);
308
+ sources.push("project-config");
309
+ }
310
+ if (customPath) {
311
+ const customConfig = readJsonConfig(customPath);
312
+ if (customConfig) {
313
+ config = mergeConfigs(config, customConfig);
314
+ sources.push("custom-config");
315
+ }
316
+ }
317
+ return { config, sources };
318
+ }
319
+ function readStore() {
320
+ const storePath = getKeysStorePath();
321
+ try {
322
+ if (!existsSync(storePath)) return {};
323
+ const raw = readFileSync(storePath, "utf-8");
324
+ return JSON.parse(raw);
325
+ } catch {
326
+ return {};
327
+ }
328
+ }
329
+ function writeStore(store) {
330
+ const storePath = getKeysStorePath();
331
+ const dir = dirname(storePath);
332
+ if (!existsSync(dir)) {
333
+ mkdirSync(dir, { recursive: true, mode: 448 });
334
+ }
335
+ writeFileSync(storePath, JSON.stringify(store, null, 2), { mode: 384 });
336
+ try {
337
+ chmodSync(storePath, 384);
338
+ } catch {
339
+ }
340
+ }
341
+ async function storeKey(providerId, key) {
342
+ const store = readStore();
343
+ store[providerId] = key;
344
+ writeStore(store);
345
+ }
346
+ async function getKey(providerId) {
347
+ const store = readStore();
348
+ return store[providerId] || null;
349
+ }
350
+ async function removeKey(providerId) {
351
+ const store = readStore();
352
+ delete store[providerId];
353
+ writeStore(store);
354
+ }
355
+ async function listStoredKeys() {
356
+ const store = readStore();
357
+ return Object.keys(store);
358
+ }
359
+ async function clearAllKeys() {
360
+ writeStore({});
361
+ }
362
+
363
+ // src/config/helpers.ts
364
+ init_registry();
365
+ function buildAuthHeaders(provider, key, providerConfig) {
366
+ const headers = {};
367
+ switch (provider.authStyle) {
368
+ case "x-api-key":
369
+ headers[provider.authHeader || "x-api-key"] = key;
370
+ break;
371
+ case "bearer":
372
+ headers["Authorization"] = `Bearer ${key}`;
373
+ break;
374
+ }
375
+ if (provider.defaultHeaders) {
376
+ Object.assign(headers, provider.defaultHeaders);
377
+ }
378
+ if (providerConfig?.headers) {
379
+ Object.assign(headers, providerConfig.headers);
380
+ }
381
+ return headers;
382
+ }
383
+ function getEffectiveBaseUrl(providerId, providerConfig) {
384
+ if (providerConfig?.baseUrl) return providerConfig.baseUrl;
385
+ const provider = getProvider(providerId);
386
+ return provider?.baseUrl || "";
387
+ }
388
+ async function getProviderHeaders(providerId) {
389
+ const provider = getProvider(providerId);
390
+ if (!provider) throw new Error(`Unknown provider: ${providerId}`);
391
+ const resolved = await resolveKey(providerId);
392
+ if (!resolved) {
393
+ throw new Error(
394
+ `No API key configured for ${provider.name}. Set ${provider.envKeys[0]} or run the setup wizard.`
395
+ );
396
+ }
397
+ const config = getConfig();
398
+ const providerConfig = config.providers?.[providerId];
399
+ return buildAuthHeaders(provider, resolved.key, providerConfig);
400
+ }
401
+ function getProviderBaseUrl(providerId) {
402
+ const config = getConfig();
403
+ const providerConfig = config.providers?.[providerId];
404
+ return getEffectiveBaseUrl(providerId, providerConfig);
405
+ }
406
+ async function getOpenAICompatibleConfig(providerId) {
407
+ const provider = getProvider(providerId);
408
+ if (!provider) throw new Error(`Unknown provider: ${providerId}`);
409
+ const resolved = await resolveKey(providerId);
410
+ if (!resolved) {
411
+ throw new Error(
412
+ `No API key configured for ${provider.name}. Set ${provider.envKeys[0]} or run the setup wizard.`
413
+ );
414
+ }
415
+ const config = getConfig();
416
+ const providerConfig = config.providers?.[providerId];
417
+ const result = {
418
+ apiKey: resolved.key,
419
+ baseURL: getEffectiveBaseUrl(providerId, providerConfig)
420
+ };
421
+ const extraHeaders = {
422
+ ...provider.defaultHeaders,
423
+ ...providerConfig?.headers
424
+ };
425
+ if (Object.keys(extraHeaders).length > 0) {
426
+ result.defaultHeaders = extraHeaders;
427
+ }
428
+ return result;
429
+ }
430
+ function buildUrl(provider, endpoint, key, providerConfig) {
431
+ const base = provider.baseUrl;
432
+ const url = new URL(endpoint, base);
433
+ if (provider.authStyle === "query-param" && key) {
434
+ url.searchParams.set("key", key);
435
+ }
436
+ return url.toString();
437
+ }
438
+
439
+ // src/config/resolver.ts
440
+ var cachedConfig = null;
441
+ var cachedOptions = null;
442
+ function initConfig(options) {
443
+ const { config } = loadConfig(options?.configPath);
444
+ if (options?.providers) {
445
+ if (!config.providers) config.providers = {};
446
+ for (const [id, providerOpts] of Object.entries(options.providers)) {
447
+ config.providers[id] = { ...config.providers[id], ...providerOpts };
448
+ }
449
+ }
450
+ cachedConfig = config;
451
+ cachedOptions = options || null;
452
+ return config;
453
+ }
454
+ function getConfig() {
455
+ if (!cachedConfig) {
456
+ return initConfig(cachedOptions || void 0);
457
+ }
458
+ return cachedConfig;
459
+ }
460
+ async function resolveKey(providerId, runtimeKey) {
461
+ const provider = getProvider(providerId);
462
+ if (!provider) return null;
463
+ const config = getConfig();
464
+ const providerConfig = config.providers?.[providerId];
465
+ if (providerConfig?.disabled) return null;
466
+ if (runtimeKey) {
467
+ return { key: runtimeKey, source: "runtime", providerId };
468
+ }
469
+ if (providerConfig?.apiKey) {
470
+ return {
471
+ key: providerConfig.apiKey,
472
+ source: "runtime",
473
+ providerId
474
+ };
475
+ }
476
+ for (const envKey of provider.envKeys) {
477
+ const value = process.env[envKey];
478
+ if (value) {
479
+ return { key: value, source: "env", providerId, envVar: envKey };
480
+ }
481
+ }
482
+ const storedKey = await getKey(providerId);
483
+ if (storedKey) {
484
+ return { key: storedKey, source: "user-config", providerId };
485
+ }
486
+ return null;
487
+ }
488
+ async function resolveWithFallback(providerIds) {
489
+ const order = providerIds || getConfig().fallbackOrder || DEFAULT_FALLBACK_ORDER;
490
+ for (const id of order) {
491
+ const provider = getProvider(id);
492
+ if (!provider) continue;
493
+ const resolved = await resolveKey(id);
494
+ if (!resolved) continue;
495
+ const config = getConfig();
496
+ const providerConfig = config.providers?.[id];
497
+ return {
498
+ providerId: id,
499
+ key: resolved.key,
500
+ source: resolved.source,
501
+ baseUrl: getEffectiveBaseUrl(id, providerConfig),
502
+ headers: buildAuthHeaders(provider, resolved.key, providerConfig)
503
+ };
504
+ }
505
+ return null;
506
+ }
507
+ async function getConfiguredProviders() {
508
+ const results = [];
509
+ for (const provider of listProviders()) {
510
+ const resolved = await resolveKey(provider.id);
511
+ if (resolved) {
512
+ results.push(resolved);
513
+ }
514
+ }
515
+ return results;
516
+ }
517
+
518
+ // src/validation/validator.ts
519
+ init_registry();
520
+ function validateKeyFormat(providerId, key) {
521
+ const provider = getProvider(providerId);
522
+ if (!provider) {
523
+ return { valid: false, error: `Unknown provider: ${providerId}` };
524
+ }
525
+ if (!key || key.trim().length === 0) {
526
+ return { valid: false, error: "Key is empty" };
527
+ }
528
+ if (provider.keyPrefix) {
529
+ const prefixes = Array.isArray(provider.keyPrefix) ? provider.keyPrefix : [provider.keyPrefix];
530
+ const hasValidPrefix = prefixes.some((p) => key.startsWith(p));
531
+ if (!hasValidPrefix) {
532
+ return {
533
+ valid: false,
534
+ error: `Key should start with ${prefixes.join(" or ")} for ${provider.name}`
535
+ };
536
+ }
537
+ }
538
+ if (key.length < 10) {
539
+ return { valid: false, error: "Key seems too short" };
540
+ }
541
+ return { valid: true };
542
+ }
543
+ async function validateKey(providerId, key) {
544
+ const formatResult = validateKeyFormat(providerId, key);
545
+ if (!formatResult.valid) return formatResult;
546
+ const provider = getProvider(providerId);
547
+ if (!provider) {
548
+ return { valid: false, error: `Unknown provider: ${providerId}` };
549
+ }
550
+ if (!provider.validateEndpoint) {
551
+ return { valid: true };
552
+ }
553
+ try {
554
+ const url = buildUrl(provider, provider.validateEndpoint, key);
555
+ const headers = buildAuthHeaders(provider, key);
556
+ headers["Content-Type"] = "application/json";
557
+ const response = await fetch(url, {
558
+ method: "GET",
559
+ headers,
560
+ signal: AbortSignal.timeout(1e4)
561
+ // 10 second timeout
562
+ });
563
+ if (response.ok) {
564
+ const meta = {};
565
+ try {
566
+ const data = await response.json();
567
+ if (providerId === "openrouter" && data?.data) {
568
+ const d = data.data;
569
+ meta.usage = d.usage;
570
+ meta.limit = d.limit;
571
+ meta.label = d.label;
572
+ }
573
+ } catch {
574
+ }
575
+ return { valid: true, meta: Object.keys(meta).length > 0 ? meta : void 0 };
576
+ }
577
+ if (response.status === 401 || response.status === 403) {
578
+ return { valid: false, error: "Invalid API key (authentication failed)" };
579
+ }
580
+ if (response.status === 429) {
581
+ return {
582
+ valid: true,
583
+ meta: { warning: "Rate limited \u2014 key is valid but currently throttled" }
584
+ };
585
+ }
586
+ return {
587
+ valid: false,
588
+ error: `Validation failed with status ${response.status}`
589
+ };
590
+ } catch (err) {
591
+ const message = err instanceof Error ? err.message : "Unknown error";
592
+ if (message.includes("timeout") || message.includes("ENOTFOUND")) {
593
+ return {
594
+ valid: true,
595
+ meta: { warning: "Could not reach provider API to validate (network issue)" }
596
+ };
597
+ }
598
+ return { valid: false, error: `Validation error: ${message}` };
599
+ }
600
+ }
601
+ async function validateAllKeys() {
602
+ const { listProviders: listProviders2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
603
+ const results = {};
604
+ const providers = listProviders2();
605
+ const promises = providers.map(async (provider) => {
606
+ const resolved = await resolveKey(provider.id);
607
+ if (!resolved) {
608
+ results[provider.id] = { valid: false, error: "No key configured" };
609
+ return;
610
+ }
611
+ results[provider.id] = await validateKey(provider.id, resolved.key);
612
+ });
613
+ await Promise.all(promises);
614
+ return results;
615
+ }
616
+
617
+ // src/storage/env.ts
618
+ init_registry();
619
+ function hasEnvKey(providerId) {
620
+ const provider = getProvider(providerId);
621
+ if (!provider) return false;
622
+ return provider.envKeys.some((key) => !!process.env[key]);
623
+ }
624
+ function getEnvKey(providerId) {
625
+ const provider = getProvider(providerId);
626
+ if (!provider) return null;
627
+ for (const key of provider.envKeys) {
628
+ const value = process.env[key];
629
+ if (value) return value;
630
+ }
631
+ return null;
632
+ }
633
+ function getEnvVarName(providerId) {
634
+ const provider = getProvider(providerId);
635
+ if (!provider) return null;
636
+ return provider.envKeys[0] || null;
637
+ }
638
+ function generateEnvTemplate(providerIds) {
639
+ const providers = providerIds ? providerIds.map((id) => getProvider(id)).filter(Boolean) : listProviders();
640
+ const lines = [
641
+ "# byok-ai \u2014 API Keys",
642
+ "# Add your API keys below. You only need keys for providers you want to use.",
643
+ ""
644
+ ];
645
+ for (const provider of providers) {
646
+ if (!provider) continue;
647
+ lines.push(`# ${provider.name} \u2014 Get a key at: ${provider.docsUrl}`);
648
+ for (const envKey of provider.envKeys) {
649
+ lines.push(`# ${envKey}=`);
650
+ }
651
+ lines.push("");
652
+ }
653
+ return lines.join("\n");
654
+ }
655
+
656
+ // src/ui/setup-wizard.ts
657
+ init_registry();
658
+ var c = {
659
+ reset: "\x1B[0m",
660
+ bold: "\x1B[1m",
661
+ dim: "\x1B[2m",
662
+ green: "\x1B[32m",
663
+ yellow: "\x1B[33m",
664
+ red: "\x1B[31m",
665
+ cyan: "\x1B[36m",
666
+ magenta: "\x1B[35m"
667
+ };
668
+ function ask(rl, question) {
669
+ return new Promise((resolve) => {
670
+ rl.question(question, (answer) => {
671
+ resolve(answer.trim());
672
+ });
673
+ });
674
+ }
675
+ function maskKey(key) {
676
+ if (key.length <= 8) return "****";
677
+ return key.slice(0, 6) + "..." + key.slice(-4);
678
+ }
679
+ async function setupWizard(options = {}) {
680
+ const { appName = "this app", skipConfigured = true, required = [] } = options;
681
+ const providerIds = options.providers || listProviders().map((p) => p.id);
682
+ const rl = createInterface({
683
+ input: process.stdin,
684
+ output: process.stdout
685
+ });
686
+ try {
687
+ console.log();
688
+ console.log(`${c.bold}${c.cyan} byok-ai${c.reset} \u2014 API Key Setup`);
689
+ console.log(`${c.dim} ${appName} needs an AI provider to work.${c.reset}`);
690
+ console.log(`${c.dim} You only need to configure one provider.${c.reset}`);
691
+ console.log();
692
+ const configured = [];
693
+ const skipped = [];
694
+ for (const id of providerIds) {
695
+ const provider = getProvider(id);
696
+ if (!provider) continue;
697
+ const existing = await resolveKey(id);
698
+ if (existing && skipConfigured) {
699
+ console.log(
700
+ ` ${c.green}\u2713${c.reset} ${c.bold}${provider.name}${c.reset} ${c.dim}(configured via ${existing.source})${c.reset}`
701
+ );
702
+ configured.push(id);
703
+ continue;
704
+ }
705
+ if (existing) {
706
+ console.log(
707
+ ` ${c.green}\u2713${c.reset} ${c.bold}${provider.name}${c.reset} ${c.dim}(${maskKey(existing.key)} via ${existing.source})${c.reset}`
708
+ );
709
+ }
710
+ console.log();
711
+ console.log(
712
+ ` ${c.bold}${provider.name}${c.reset} ${c.dim}\u2014 Get a key at: ${provider.docsUrl}${c.reset}`
713
+ );
714
+ const input = await ask(rl, ` ${c.cyan}API Key${c.reset} (or Enter to skip): `);
715
+ if (!input) {
716
+ skipped.push(id);
717
+ console.log(` ${c.dim}Skipped${c.reset}`);
718
+ continue;
719
+ }
720
+ const formatCheck = validateKeyFormat(id, input);
721
+ if (!formatCheck.valid) {
722
+ console.log(` ${c.yellow}\u26A0 Warning:${c.reset} ${formatCheck.error}`);
723
+ const proceed = await ask(rl, ` ${c.dim}Save anyway? (y/N):${c.reset} `);
724
+ if (proceed.toLowerCase() !== "y") {
725
+ skipped.push(id);
726
+ continue;
727
+ }
728
+ }
729
+ console.log(` ${c.dim}Validating...${c.reset}`);
730
+ const result = await validateKey(id, input);
731
+ if (result.valid) {
732
+ await storeKey(id, input);
733
+ configured.push(id);
734
+ console.log(` ${c.green}\u2713 Valid!${c.reset} Key saved.`);
735
+ if (result.meta?.warning) {
736
+ console.log(` ${c.yellow}\u26A0 ${result.meta.warning}${c.reset}`);
737
+ }
738
+ } else {
739
+ console.log(` ${c.red}\u2717 ${result.error}${c.reset}`);
740
+ const saveAnyway = await ask(rl, ` ${c.dim}Save anyway? (y/N):${c.reset} `);
741
+ if (saveAnyway.toLowerCase() === "y") {
742
+ await storeKey(id, input);
743
+ configured.push(id);
744
+ console.log(` ${c.dim}Key saved.${c.reset}`);
745
+ } else {
746
+ skipped.push(id);
747
+ }
748
+ }
749
+ }
750
+ console.log();
751
+ console.log(`${c.bold} Summary${c.reset}`);
752
+ console.log(`${c.dim} ${"\u2500".repeat(40)}${c.reset}`);
753
+ if (configured.length > 0) {
754
+ console.log(
755
+ ` ${c.green}\u2713${c.reset} ${configured.length} provider(s) configured: ${c.bold}${configured.join(", ")}${c.reset}`
756
+ );
757
+ }
758
+ if (skipped.length > 0) {
759
+ console.log(` ${c.dim}\u25CB ${skipped.length} skipped: ${skipped.join(", ")}${c.reset}`);
760
+ }
761
+ const missingRequired = required.filter((r) => !configured.includes(r));
762
+ if (missingRequired.length > 0) {
763
+ console.log();
764
+ console.log(
765
+ ` ${c.yellow}\u26A0 Required providers not configured: ${c.bold}${missingRequired.join(", ")}${c.reset}`
766
+ );
767
+ console.log(` ${c.dim}${appName} may not work without these.${c.reset}`);
768
+ }
769
+ console.log();
770
+ } finally {
771
+ rl.close();
772
+ }
773
+ }
774
+
775
+ // src/ui/status.ts
776
+ init_registry();
777
+ function maskKey2(key) {
778
+ if (key.length <= 8) return "****";
779
+ return key.slice(0, 6) + "..." + key.slice(-4);
780
+ }
781
+ async function getStatus(providerIds) {
782
+ const providers = providerIds ? providerIds.map((id) => getProvider(id)).filter((p) => !!p) : listProviders();
783
+ const statuses = [];
784
+ for (const provider of providers) {
785
+ const resolved = await resolveKey(provider.id);
786
+ statuses.push({
787
+ id: provider.id,
788
+ name: provider.name,
789
+ configured: !!resolved,
790
+ source: resolved?.source,
791
+ keyPreview: resolved ? maskKey2(resolved.key) : void 0
792
+ });
793
+ }
794
+ const config = getConfig();
795
+ return {
796
+ providers: statuses,
797
+ defaultProvider: config.defaultProvider
798
+ };
799
+ }
800
+ var col = {
801
+ reset: "\x1B[0m",
802
+ bold: "\x1B[1m",
803
+ dim: "\x1B[2m",
804
+ green: "\x1B[32m",
805
+ red: "\x1B[31m",
806
+ cyan: "\x1B[36m"
807
+ };
808
+ async function printStatus(providerIds) {
809
+ const status = await getStatus(providerIds);
810
+ console.log();
811
+ console.log(`${col.bold}${col.cyan} byok-ai${col.reset} \u2014 Provider Status`);
812
+ console.log(`${col.dim} ${"\u2500".repeat(50)}${col.reset}`);
813
+ for (const p of status.providers) {
814
+ const icon = p.configured ? `${col.green}\u2713` : `${col.red}\u2717`;
815
+ const source = p.source ? `${col.dim}(${p.source})${col.reset}` : "";
816
+ const preview = p.keyPreview ? `${col.dim}${p.keyPreview}${col.reset}` : "";
817
+ console.log(
818
+ ` ${icon}${col.reset} ${col.bold}${p.name.padEnd(16)}${col.reset} ${preview} ${source}`
819
+ );
820
+ }
821
+ const total = status.providers.length;
822
+ const active = status.providers.filter((p) => p.configured).length;
823
+ console.log();
824
+ console.log(` ${col.dim}${active}/${total} providers configured${col.reset}`);
825
+ if (status.defaultProvider) {
826
+ console.log(` ${col.dim}Default: ${status.defaultProvider}${col.reset}`);
827
+ }
828
+ console.log();
829
+ }
830
+
831
+ // src/index.ts
832
+ async function init(options = {}) {
833
+ const { providers, appName, interactive = true, ...byokOptions } = options;
834
+ initConfig(byokOptions);
835
+ let resolved = await resolveWithFallback(providers);
836
+ if (!resolved && interactive && process.stdin.isTTY) {
837
+ await setupWizard({
838
+ providers,
839
+ appName
840
+ });
841
+ resolved = await resolveWithFallback(providers);
842
+ }
843
+ return resolved;
844
+ }
845
+
846
+ export { DEFAULT_FALLBACK_ORDER, clearAllKeys, generateEnvTemplate, getConfig, getConfiguredProviders, getEnvKey, getEnvVarName, getKey, getKeysStorePath, getOpenAICompatibleConfig, getProvider, getProviderBaseUrl, getProviderHeaders, getStatus, getUserConfigPath, hasEnvKey, hasProvider, init, initConfig, listProviders, listStoredKeys, printStatus, registerProvider, removeKey, resolveKey, resolveWithFallback, setupWizard, storeKey, validateAllKeys, validateKey, validateKeyFormat };
847
+ //# sourceMappingURL=index.js.map
848
+ //# sourceMappingURL=index.js.map