tr-plugin-auth 0.0.0-stage → 1.0.1

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.
Files changed (60) hide show
  1. package/README.md +45 -2
  2. package/cli.mjs +384 -0
  3. package/package.json +40 -4
  4. package/plugin.json +302 -0
  5. package/src/access-keys.mjs +225 -0
  6. package/src/agent-dir.mjs +3 -0
  7. package/src/browser-session.mjs +240 -0
  8. package/src/cli-secret.mjs +1695 -0
  9. package/src/config.mjs +4 -0
  10. package/src/configuration.mjs +19 -0
  11. package/src/credential-preservation.mjs +28 -0
  12. package/src/crypto.mjs +195 -0
  13. package/src/env-import.mjs +502 -0
  14. package/src/fact-seeker.mjs +3 -0
  15. package/src/frontmatter.mjs +3 -0
  16. package/src/google-oauth.mjs +139 -0
  17. package/src/host-runtime.mjs +7 -0
  18. package/src/leader-election.mjs +9 -0
  19. package/src/logger.mjs +3 -0
  20. package/src/mesh-auth.mjs +7 -0
  21. package/src/mesh-identity.mjs +16 -0
  22. package/src/mesh-secrets.mjs +146 -0
  23. package/src/mesh.mjs +23 -0
  24. package/src/metadata-state.mjs +17 -0
  25. package/src/project-id.mjs +3 -0
  26. package/src/provider-account-sync.mjs +1145 -0
  27. package/src/provider-catalog.mjs +515 -0
  28. package/src/provider-rotation-recipes.mjs +299 -0
  29. package/src/research-gate.mjs +3 -0
  30. package/src/research-queue.mjs +3 -0
  31. package/src/rotation-capability.mjs +189 -0
  32. package/src/routes/auth.mjs +207 -0
  33. package/src/routes/keys.mjs +61 -0
  34. package/src/routes/secrets-runtime.mjs +5 -0
  35. package/src/routes/secrets.mjs +747 -0
  36. package/src/routes/webauthn.mjs +163 -0
  37. package/src/runtime-credentials.mjs +19 -0
  38. package/src/runtime.mjs +3 -0
  39. package/src/secret-integration-research.mjs +340 -0
  40. package/src/secrets-env-diff.mjs +132 -0
  41. package/src/secrets-env-export.mjs +322 -0
  42. package/src/secrets-gateway.mjs +253 -0
  43. package/src/secrets-keychain.mjs +83 -0
  44. package/src/secrets-rekey.mjs +366 -0
  45. package/src/secrets-remote-deploy.mjs +250 -0
  46. package/src/secrets-rotate.mjs +437 -0
  47. package/src/secrets-store.mjs +756 -0
  48. package/src/secrets-sync.mjs +81 -0
  49. package/src/secure-file.mjs +71 -0
  50. package/src/server-auth.mjs +500 -0
  51. package/src/session-credentials.mjs +71 -0
  52. package/src/ssss-store.mjs +13 -0
  53. package/src/stdin.mjs +31 -0
  54. package/src/throttled-fetch.mjs +3 -0
  55. package/src/vault.mjs +3 -0
  56. package/src/vfs-documents.mjs +3 -0
  57. package/src/webauthn-store.mjs +265 -0
  58. package/ui/DESIGN.md +19 -0
  59. package/ui/card.js +274 -0
  60. package/ui/panel.js +338 -0
@@ -0,0 +1,515 @@
1
+ /**
2
+ * Static provider catalog — docs, schema hints, default subscription fields.
3
+ * Used by secrets UI/CLI; no secret values stored here.
4
+ */
5
+
6
+ /** @typedef {{
7
+ * id: string,
8
+ * name: string,
9
+ * docs_url: string,
10
+ * pricing_url?: string,
11
+ * console_url?: string,
12
+ * key_patterns: string[],
13
+ * schema: { auth: string, header?: string, env_keys: string[], notes?: string },
14
+ * rotation?: {
15
+ * class?: 'provider_api' | 'provider_browser' | 'self_generated' | 'manual',
16
+ * high_risk?: boolean,
17
+ * manual_keys?: string[],
18
+ * },
19
+ * default_monthly_cap_usd?: number | null,
20
+ * tiers?: { id: string, label: string, monthly_usd?: number | null }[],
21
+ * }} ProviderDef */
22
+
23
+ /** @type {ProviderDef[]} */
24
+ export const PROVIDER_CATALOG = [
25
+ {
26
+ id: 'openai',
27
+ name: 'OpenAI',
28
+ docs_url: 'https://platform.openai.com/docs/api-reference',
29
+ pricing_url: 'https://openai.com/api/pricing/',
30
+ console_url: 'https://platform.openai.com/api-keys',
31
+ key_patterns: ['OPENAI_API_KEY', 'AZURE_OPENAI_API_KEY'],
32
+ schema: {
33
+ auth: 'bearer',
34
+ header: 'Authorization: Bearer <key>',
35
+ env_keys: ['OPENAI_API_KEY'],
36
+ notes: 'Chat Completions + Responses APIs; org optional OPENAI_ORG_ID',
37
+ },
38
+ default_monthly_cap_usd: 50,
39
+ tiers: [
40
+ { id: 'free', label: 'Free / trial', monthly_usd: 0 },
41
+ { id: 'payg', label: 'Pay as you go', monthly_usd: null },
42
+ { id: 'team', label: 'Team', monthly_usd: null },
43
+ ],
44
+ },
45
+ {
46
+ id: 'anthropic',
47
+ name: 'Anthropic',
48
+ docs_url: 'https://docs.anthropic.com/en/api/getting-started',
49
+ pricing_url: 'https://www.anthropic.com/pricing',
50
+ console_url: 'https://console.anthropic.com/settings/keys',
51
+ key_patterns: ['ANTHROPIC_API_KEY'],
52
+ schema: {
53
+ auth: 'header',
54
+ header: 'x-api-key: <key>',
55
+ env_keys: ['ANTHROPIC_API_KEY'],
56
+ notes: 'Messages API; anthropic-version header required',
57
+ },
58
+ default_monthly_cap_usd: 50,
59
+ tiers: [
60
+ { id: 'free', label: 'Free', monthly_usd: 0 },
61
+ { id: 'build', label: 'Build / payg', monthly_usd: null },
62
+ { id: 'scale', label: 'Scale', monthly_usd: null },
63
+ ],
64
+ },
65
+ {
66
+ id: 'google',
67
+ name: 'Google AI / Gemini',
68
+ docs_url: 'https://ai.google.dev/gemini-api/docs',
69
+ pricing_url: 'https://ai.google.dev/pricing',
70
+ console_url: 'https://aistudio.google.com/apikey',
71
+ key_patterns: ['GOOGLE_API_KEY', 'GEMINI_API_KEY', 'GOOGLE_GENERATIVE_AI_API_KEY'],
72
+ schema: {
73
+ auth: 'query_or_header',
74
+ header: 'x-goog-api-key: <key>',
75
+ env_keys: ['GOOGLE_API_KEY', 'GEMINI_API_KEY'],
76
+ },
77
+ default_monthly_cap_usd: 25,
78
+ tiers: [
79
+ { id: 'free', label: 'Free tier', monthly_usd: 0 },
80
+ { id: 'payg', label: 'Paid', monthly_usd: null },
81
+ ],
82
+ },
83
+ {
84
+ id: 'openrouter',
85
+ name: 'OpenRouter',
86
+ docs_url: 'https://openrouter.ai/docs',
87
+ pricing_url: 'https://openrouter.ai/models',
88
+ console_url: 'https://openrouter.ai/keys',
89
+ key_patterns: ['OPENROUTER_API_KEY'],
90
+ schema: {
91
+ auth: 'bearer',
92
+ header: 'Authorization: Bearer <key>',
93
+ env_keys: ['OPENROUTER_API_KEY'],
94
+ notes: 'OpenAI-compatible base URL https://openrouter.ai/api/v1',
95
+ },
96
+ default_monthly_cap_usd: 30,
97
+ tiers: [{ id: 'payg', label: 'Pay as you go', monthly_usd: null }],
98
+ },
99
+ {
100
+ id: 'xai',
101
+ name: 'xAI (Grok)',
102
+ docs_url: 'https://docs.x.ai/docs',
103
+ console_url: 'https://console.x.ai',
104
+ key_patterns: ['XAI_API_KEY', 'GROK_API_KEY'],
105
+ schema: {
106
+ auth: 'bearer',
107
+ header: 'Authorization: Bearer <key>',
108
+ env_keys: ['XAI_API_KEY'],
109
+ },
110
+ default_monthly_cap_usd: 30,
111
+ tiers: [{ id: 'payg', label: 'Pay as you go', monthly_usd: null }],
112
+ },
113
+ {
114
+ id: 'brave',
115
+ name: 'Brave Search',
116
+ docs_url: 'https://api-dashboard.search.brave.com/app/documentation',
117
+ console_url: 'https://api-dashboard.search.brave.com/',
118
+ key_patterns: ['BRAVE_SEARCH_API_KEY', 'BRAVE_API_KEY'],
119
+ schema: {
120
+ auth: 'header',
121
+ header: 'X-Subscription-Token: <key>',
122
+ env_keys: ['BRAVE_SEARCH_API_KEY'],
123
+ },
124
+ default_monthly_cap_usd: 5,
125
+ tiers: [
126
+ { id: 'free', label: 'Free', monthly_usd: 0 },
127
+ { id: 'base', label: 'Base', monthly_usd: 5 },
128
+ { id: 'pro', label: 'Pro', monthly_usd: null },
129
+ ],
130
+ },
131
+ {
132
+ id: 'elevenlabs',
133
+ name: 'ElevenLabs',
134
+ docs_url: 'https://elevenlabs.io/docs/api-reference',
135
+ pricing_url: 'https://elevenlabs.io/pricing',
136
+ console_url: 'https://elevenlabs.io/app/settings/api-keys',
137
+ key_patterns: ['ELEVENLABS_API_KEY', 'ELEVEN_LABS_API_KEY'],
138
+ schema: {
139
+ auth: 'header',
140
+ header: 'xi-api-key: <key>',
141
+ env_keys: ['ELEVENLABS_API_KEY'],
142
+ },
143
+ default_monthly_cap_usd: 22,
144
+ tiers: [
145
+ { id: 'free', label: 'Free', monthly_usd: 0 },
146
+ { id: 'starter', label: 'Starter', monthly_usd: 5 },
147
+ { id: 'creator', label: 'Creator', monthly_usd: 22 },
148
+ { id: 'pro', label: 'Pro', monthly_usd: 99 },
149
+ ],
150
+ },
151
+ {
152
+ id: 'github',
153
+ name: 'GitHub',
154
+ docs_url: 'https://docs.github.com/en/rest',
155
+ console_url: 'https://github.com/settings/tokens',
156
+ key_patterns: ['GITHUB_TOKEN', 'GH_TOKEN'],
157
+ schema: {
158
+ auth: 'bearer',
159
+ header: 'Authorization: Bearer <token>',
160
+ env_keys: ['GITHUB_TOKEN', 'GH_TOKEN'],
161
+ },
162
+ default_monthly_cap_usd: null,
163
+ tiers: [
164
+ { id: 'free', label: 'Free', monthly_usd: 0 },
165
+ { id: 'pro', label: 'Pro', monthly_usd: 4 },
166
+ { id: 'team', label: 'Team', monthly_usd: 4 },
167
+ ],
168
+ },
169
+ {
170
+ id: 'supabase',
171
+ name: 'Supabase',
172
+ docs_url: 'https://supabase.com/docs/reference/javascript/introduction',
173
+ console_url: 'https://supabase.com/dashboard',
174
+ key_patterns: ['SUPABASE_URL', 'SUPABASE_ANON_KEY', 'SUPABASE_SERVICE_ROLE_KEY'],
175
+ schema: {
176
+ auth: 'header',
177
+ header: 'apikey + Authorization Bearer',
178
+ env_keys: ['SUPABASE_URL', 'SUPABASE_ANON_KEY', 'SUPABASE_SERVICE_ROLE_KEY'],
179
+ },
180
+ default_monthly_cap_usd: 25,
181
+ tiers: [
182
+ { id: 'free', label: 'Free', monthly_usd: 0 },
183
+ { id: 'pro', label: 'Pro', monthly_usd: 25 },
184
+ { id: 'team', label: 'Team', monthly_usd: 599 },
185
+ ],
186
+ },
187
+ {
188
+ id: 'digitalocean',
189
+ name: 'DigitalOcean',
190
+ docs_url: 'https://docs.digitalocean.com/reference/api/',
191
+ console_url: 'https://cloud.digitalocean.com/account/api/tokens',
192
+ key_patterns: ['DIGITALOCEAN_API_TOKEN', 'DO_API_TOKEN', 'DIGITALOCEAN_ACCESS_TOKEN'],
193
+ schema: {
194
+ auth: 'bearer',
195
+ header: 'Authorization: Bearer <token>',
196
+ env_keys: ['DIGITALOCEAN_API_TOKEN'],
197
+ },
198
+ default_monthly_cap_usd: null,
199
+ tiers: [{ id: 'payg', label: 'Pay as you go', monthly_usd: null }],
200
+ },
201
+ {
202
+ id: 'huggingface',
203
+ name: 'Hugging Face',
204
+ docs_url: 'https://huggingface.co/docs/api-inference',
205
+ console_url: 'https://huggingface.co/settings/tokens',
206
+ key_patterns: ['HUGGINGFACE_API_KEY', 'HF_TOKEN'],
207
+ schema: {
208
+ auth: 'bearer',
209
+ header: 'Authorization: Bearer <token>',
210
+ env_keys: ['HF_TOKEN'],
211
+ },
212
+ default_monthly_cap_usd: 9,
213
+ tiers: [
214
+ { id: 'free', label: 'Free', monthly_usd: 0 },
215
+ { id: 'pro', label: 'Pro', monthly_usd: 9 },
216
+ ],
217
+ },
218
+ {
219
+ id: 'serper',
220
+ name: 'Serper',
221
+ docs_url: 'https://serper.dev/docs',
222
+ console_url: 'https://serper.dev',
223
+ key_patterns: ['SERPER_API_KEY'],
224
+ schema: {
225
+ auth: 'header',
226
+ header: 'X-API-KEY: <key>',
227
+ env_keys: ['SERPER_API_KEY'],
228
+ },
229
+ default_monthly_cap_usd: 50,
230
+ tiers: [{ id: 'payg', label: 'Credits', monthly_usd: null }],
231
+ },
232
+ {
233
+ id: 'telegram',
234
+ name: 'Telegram Bot',
235
+ docs_url: 'https://core.telegram.org/bots/api',
236
+ console_url: 'https://t.me/BotFather',
237
+ key_patterns: ['TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID'],
238
+ schema: {
239
+ auth: 'path',
240
+ env_keys: ['TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID'],
241
+ notes: 'Token from BotFather; chat_id for notifications',
242
+ },
243
+ default_monthly_cap_usd: 0,
244
+ tiers: [{ id: 'free', label: 'Free', monthly_usd: 0 }],
245
+ },
246
+ {
247
+ id: 'total-recall',
248
+ name: 'Total Recall',
249
+ docs_url: 'https://github.com/gregiteen/total-recall',
250
+ key_patterns: ['TOTAL_RECALL_TOKEN', 'TR_TOKEN', 'TR_SECRETS_PASSWORD'],
251
+ schema: {
252
+ auth: 'bearer',
253
+ header: 'Authorization: Bearer <pat>',
254
+ env_keys: ['TOTAL_RECALL_TOKEN'],
255
+ notes: 'Dashboard PATs via /api/keys; secrets.enc for provider keys',
256
+ },
257
+ default_monthly_cap_usd: 0,
258
+ tiers: [{ id: 'self-hosted', label: 'Self-hosted', monthly_usd: 0 }],
259
+ },
260
+ {
261
+ id: 'smtp2go',
262
+ name: 'SMTP2GO',
263
+ docs_url: 'https://www.smtp2go.com/docs/',
264
+ console_url: 'https://app.smtp2go.com/settings/api_keys/',
265
+ key_patterns: ['SMTP2GO_API_KEY', 'SMTP2GO_PASSWORD'],
266
+ schema: {
267
+ auth: 'header',
268
+ header: 'X-Smtp2go-Api-Key: <key>',
269
+ env_keys: ['SMTP2GO_API_KEY'],
270
+ },
271
+ default_monthly_cap_usd: 15,
272
+ tiers: [
273
+ { id: 'free', label: 'Free', monthly_usd: 0 },
274
+ { id: 'starter', label: 'Starter', monthly_usd: 15 },
275
+ ],
276
+ },
277
+ {
278
+ id: 'mailcow',
279
+ name: 'Mailcow',
280
+ docs_url: 'https://mailcow.github.io/mailcow-dockerized-docs/api/',
281
+ key_patterns: ['MAILCOW_API_KEY', 'MAILCOW_ADMIN_PASSWORD', 'MAILCOW_SMTP_PASSWORD', 'MAILCOW_DOVECOT_MASTER_PASSWORD'],
282
+ schema: {
283
+ auth: 'header',
284
+ header: 'X-API-Key: <key>',
285
+ env_keys: ['MAILCOW_API_KEY'],
286
+ },
287
+ default_monthly_cap_usd: 0,
288
+ tiers: [{ id: 'self-hosted', label: 'Self-hosted', monthly_usd: 0 }],
289
+ },
290
+ {
291
+ id: 'headscale',
292
+ name: 'Headscale',
293
+ docs_url: 'https://github.com/juanfont/headscale',
294
+ key_patterns: ['HEADSCALE_API_KEY', 'HEADSCALE_KEY'],
295
+ schema: {
296
+ auth: 'bearer',
297
+ header: 'Authorization: Bearer <key>',
298
+ env_keys: ['HEADSCALE_API_KEY'],
299
+ },
300
+ default_monthly_cap_usd: 0,
301
+ tiers: [{ id: 'self-hosted', label: 'Self-hosted', monthly_usd: 0 }],
302
+ },
303
+ {
304
+ id: 'stripe',
305
+ name: 'Stripe',
306
+ docs_url: 'https://docs.stripe.com/api',
307
+ pricing_url: 'https://stripe.com/pricing',
308
+ console_url: 'https://dashboard.stripe.com/apikeys',
309
+ key_patterns: ['STRIPE_SECRET_KEY', 'STRIPE_API_KEY', 'STRIPE_PUBLISHABLE_KEY', 'STRIPE_WEBHOOK_SECRET'],
310
+ schema: {
311
+ auth: 'bearer',
312
+ header: 'Authorization: Bearer <key>',
313
+ env_keys: ['STRIPE_SECRET_KEY'],
314
+ notes: 'Live keys are payment-critical. Roll with a grace window; never revoke before export confirms.',
315
+ },
316
+ // Stripe exposes no API to mint or roll secret keys — dashboard only, by design.
317
+ rotation: { class: 'provider_browser', high_risk: true },
318
+ default_monthly_cap_usd: null,
319
+ tiers: [{ id: 'payg', label: 'Pay as you go', monthly_usd: null }],
320
+ },
321
+ {
322
+ id: 'vercel',
323
+ name: 'Vercel',
324
+ docs_url: 'https://vercel.com/docs/rest-api',
325
+ console_url: 'https://vercel.com/account/settings/tokens',
326
+ key_patterns: ['VERCEL_API_TOKEN', 'VERCEL_TOKEN'],
327
+ schema: { auth: 'bearer', header: 'Authorization: Bearer <token>', env_keys: ['VERCEL_API_TOKEN'] },
328
+ rotation: { class: 'provider_browser' },
329
+ default_monthly_cap_usd: 20,
330
+ tiers: [{ id: 'hobby', label: 'Hobby', monthly_usd: 0 }, { id: 'pro', label: 'Pro', monthly_usd: 20 }],
331
+ },
332
+ {
333
+ id: 'telnyx',
334
+ name: 'Telnyx',
335
+ docs_url: 'https://developers.telnyx.com/api',
336
+ console_url: 'https://portal.telnyx.com/#/app/api-keys',
337
+ key_patterns: ['TELNYX_API_KEY', 'TELNYX_SIP_PASSWORD'],
338
+ schema: { auth: 'bearer', header: 'Authorization: Bearer <key>', env_keys: ['TELNYX_API_KEY'] },
339
+ rotation: { class: 'provider_browser' },
340
+ default_monthly_cap_usd: null,
341
+ tiers: [{ id: 'payg', label: 'Pay as you go', monthly_usd: null }],
342
+ },
343
+ {
344
+ id: 'fal',
345
+ name: 'fal.ai',
346
+ docs_url: 'https://docs.fal.ai',
347
+ console_url: 'https://fal.ai/dashboard/keys',
348
+ key_patterns: ['FAL_KEY', 'FAL_AI_API_KEY'],
349
+ schema: { auth: 'header', header: 'Authorization: Key <key>', env_keys: ['FAL_KEY'] },
350
+ rotation: { class: 'provider_browser' },
351
+ default_monthly_cap_usd: 20,
352
+ tiers: [{ id: 'payg', label: 'Pay as you go', monthly_usd: null }],
353
+ },
354
+ {
355
+ id: 'tavily',
356
+ name: 'Tavily',
357
+ docs_url: 'https://docs.tavily.com',
358
+ console_url: 'https://app.tavily.com/home',
359
+ key_patterns: ['TAVILY_API_KEY'],
360
+ schema: { auth: 'bearer', header: 'Authorization: Bearer <key>', env_keys: ['TAVILY_API_KEY'] },
361
+ rotation: { class: 'provider_browser' },
362
+ default_monthly_cap_usd: 10,
363
+ tiers: [{ id: 'free', label: 'Free', monthly_usd: 0 }],
364
+ },
365
+ {
366
+ id: 'exa',
367
+ name: 'Exa',
368
+ docs_url: 'https://docs.exa.ai',
369
+ console_url: 'https://dashboard.exa.ai/api-keys',
370
+ key_patterns: ['EXA_API_KEY'],
371
+ schema: { auth: 'header', header: 'x-api-key: <key>', env_keys: ['EXA_API_KEY'] },
372
+ rotation: { class: 'provider_browser' },
373
+ default_monthly_cap_usd: 10,
374
+ tiers: [{ id: 'payg', label: 'Pay as you go', monthly_usd: null }],
375
+ },
376
+ {
377
+ id: 'npm',
378
+ name: 'npm',
379
+ docs_url: 'https://docs.npmjs.com/about-access-tokens',
380
+ console_url: 'https://www.npmjs.com/settings/~/tokens',
381
+ key_patterns: ['NPM_TOKEN', 'NPM_RECOVERY_CODE'],
382
+ schema: { auth: 'bearer', header: 'Authorization: Bearer <token>', env_keys: ['NPM_TOKEN'] },
383
+ // Recovery codes are single-issue artifacts, never machine-rotatable.
384
+ rotation: { class: 'provider_browser', manual_keys: ['NPM_RECOVERY_CODE'] },
385
+ default_monthly_cap_usd: 0,
386
+ tiers: [{ id: 'free', label: 'Free', monthly_usd: 0 }],
387
+ },
388
+ {
389
+ id: 'pexels',
390
+ name: 'Pexels',
391
+ docs_url: 'https://www.pexels.com/api/documentation/',
392
+ console_url: 'https://www.pexels.com/api/new/',
393
+ key_patterns: ['PEXELS_API_KEY'],
394
+ schema: { auth: 'header', header: 'Authorization: <key>', env_keys: ['PEXELS_API_KEY'] },
395
+ rotation: { class: 'provider_browser' },
396
+ default_monthly_cap_usd: 0,
397
+ tiers: [{ id: 'free', label: 'Free', monthly_usd: 0 }],
398
+ },
399
+ {
400
+ id: 'artificial-analysis',
401
+ name: 'Artificial Analysis',
402
+ docs_url: 'https://artificialanalysis.ai/documentation',
403
+ console_url: 'https://artificialanalysis.ai/account/api-keys',
404
+ key_patterns: ['ARTIFICIAL_ANALYSIS_API_KEY'],
405
+ schema: { auth: 'header', header: 'x-api-key: <key>', env_keys: ['ARTIFICIAL_ANALYSIS_API_KEY'] },
406
+ rotation: { class: 'provider_browser' },
407
+ default_monthly_cap_usd: 0,
408
+ tiers: [{ id: 'free', label: 'Free', monthly_usd: 0 }],
409
+ },
410
+ {
411
+ id: 'browser-use',
412
+ name: 'Browser Use',
413
+ docs_url: 'https://docs.browser-use.com',
414
+ console_url: 'https://cloud.browser-use.com/settings/api-keys',
415
+ key_patterns: ['BROWSER_USE_API_KEY'],
416
+ schema: { auth: 'bearer', header: 'Authorization: Bearer <key>', env_keys: ['BROWSER_USE_API_KEY'] },
417
+ rotation: { class: 'provider_browser' },
418
+ default_monthly_cap_usd: 10,
419
+ tiers: [{ id: 'payg', label: 'Pay as you go', monthly_usd: null }],
420
+ },
421
+ ];
422
+
423
+ const byId = new Map(PROVIDER_CATALOG.map((p) => [p.id, p]));
424
+
425
+ /**
426
+ * @param {string} providerId
427
+ */
428
+ export function getProvider(providerId) {
429
+ if (!providerId) return null;
430
+ return byId.get(String(providerId).toLowerCase()) || null;
431
+ }
432
+
433
+ /**
434
+ * @param {string} keyName
435
+ */
436
+ /**
437
+ * Minimum length for a derived stem before it may be used for a fuzzy match.
438
+ * Guards against short stems swallowing unrelated keys — e.g. the pattern
439
+ * `TR_TOKEN` reduces to the stem `TR`, which as a bare substring matches
440
+ * S-TR-IPE_SECRET_KEY and mis-attributes Stripe credentials to Total Recall.
441
+ */
442
+ const MIN_FUZZY_STEM = 4;
443
+
444
+ /** Does `k` contain `stem` on an underscore token boundary (not mid-word)? */
445
+ function matchesOnTokenBoundary(k, stem) {
446
+ return (
447
+ k === stem ||
448
+ k.startsWith(stem + '_') ||
449
+ k.endsWith('_' + stem) ||
450
+ k.includes('_' + stem + '_')
451
+ );
452
+ }
453
+
454
+ /**
455
+ * Resolve the provider that owns a secret key name.
456
+ *
457
+ * Matching is tiered strongest→weakest so an exact pattern always wins over a
458
+ * fuzzy stem. Fuzzy tiers require a token boundary and a minimum stem length;
459
+ * bare substring matching is never used.
460
+ *
461
+ * @param {string} keyName
462
+ * @returns {ProviderDef|null}
463
+ */
464
+ export function providerForKeyName(keyName) {
465
+ // Strip packaging prefixes so DEVELOPER_BRAVE_SEARCH_API_KEY → BRAVE_SEARCH_API_KEY
466
+ let k = String(keyName || '').toUpperCase();
467
+ k = k.replace(/^(DEVELOPER_|VITE_|NEXT_PUBLIC_|PUBLIC_|NUXT_PUBLIC_)+/g, '');
468
+
469
+ // Tier 1 — exact pattern match.
470
+ for (const p of PROVIDER_CATALOG) {
471
+ if (p.key_patterns.some((pat) => pat.toUpperCase() === k)) return p;
472
+ }
473
+
474
+ // Tier 2 — key is a suffixed variant of a full pattern (FOO_OPENAI_API_KEY).
475
+ for (const p of PROVIDER_CATALOG) {
476
+ if (p.key_patterns.some((pat) => k.endsWith('_' + pat.toUpperCase()))) return p;
477
+ }
478
+
479
+ // Tier 3 — pattern stem on a token boundary (GITHUB_TOKEN → GITHUB_APP_CLIENT_SECRET).
480
+ for (const p of PROVIDER_CATALOG) {
481
+ if (
482
+ p.key_patterns.some((pat) => {
483
+ const stem = pat.toUpperCase().replace(/_API_KEY$|_TOKEN$|_KEY$|_SECRET$/, '');
484
+ if (stem.length < MIN_FUZZY_STEM) return false;
485
+ return matchesOnTokenBoundary(k, stem);
486
+ })
487
+ ) {
488
+ return p;
489
+ }
490
+ }
491
+
492
+ // Tier 4 — provider id on a token boundary.
493
+ const compact = k.replace(/-/g, '_');
494
+ for (const p of PROVIDER_CATALOG) {
495
+ const id = p.id.toUpperCase().replace(/-/g, '_');
496
+ if (id.length < MIN_FUZZY_STEM) continue;
497
+ if (matchesOnTokenBoundary(compact, id)) return p;
498
+ }
499
+
500
+ return null;
501
+ }
502
+
503
+ export function listProviders() {
504
+ return PROVIDER_CATALOG.map((p) => ({
505
+ id: p.id,
506
+ name: p.name,
507
+ docs_url: p.docs_url,
508
+ pricing_url: p.pricing_url || null,
509
+ console_url: p.console_url || null,
510
+ key_patterns: p.key_patterns,
511
+ schema: p.schema,
512
+ default_monthly_cap_usd: p.default_monthly_cap_usd ?? null,
513
+ tiers: p.tiers || [],
514
+ }));
515
+ }