bingocode 1.0.14 → 1.0.16

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.
@@ -229,6 +229,22 @@ export const call: LocalJSXCommandCall = async (onDone, context, args) => {
229
229
  return null;
230
230
  }
231
231
 
232
+ // Same-repo logs didn't find it — search across ALL projects.
233
+ // This handles the case where the user runs `bingo` from a different
234
+ // directory than where the session was originally created (the session
235
+ // file lives under ~/.claude/projects/{originalCwd}/). A direct UUID
236
+ // resume should always be location-independent.
237
+ const allLogs = await loadAllProjectsMessageLogs();
238
+ const allMatchingLogs = allLogs
239
+ .filter(l => getSessionIdFromLog(l) === maybeSessionId)
240
+ .sort((a, b) => b.modified.getTime() - a.modified.getTime());
241
+ if (allMatchingLogs.length > 0) {
242
+ const log = allMatchingLogs[0]!;
243
+ const fullLog = isLiteLog(log) ? await loadFullLog(log) : log;
244
+ void onResume(maybeSessionId, fullLog, 'slash_command_session_id');
245
+ return null;
246
+ }
247
+
232
248
  // Enriched logs didn't find it — try direct file lookup. This handles
233
249
  // sessions filtered out by enrichLogs (e.g., first message >16KB makes
234
250
  // firstPrompt extraction fail, causing the session to be dropped).
@@ -1,93 +1,105 @@
1
- // Provider presets — loaded from providers.yaml at startup
2
- // Original work inspired by cc-switch (https://github.com/farion1231/cc-switch) by Jason Young, MIT License
3
-
4
- //@C:ID=M.PP.providerPresets;K=M;V=2.0;P=Import dependencies;D=API;M=Providers;S=ModelConfiguration
5
- import { readFileSync } from 'fs'
6
- import { fileURLToPath } from 'url'
7
- import { parse } from 'yaml'
8
- import path from 'path'
9
- import type { ApiFormat } from '../types/provider.js'
10
-
11
- //@C:ID=T.PP.ModelMapping;K=T;V=1.0;P=Define model type mappings;D=API;M=Providers;S=ModelConfiguration
12
- export type ModelMapping = {
13
- main: string
14
- haiku: string
15
- sonnet: string
16
- opus: string
17
- }
18
-
19
- //@C:ID=T.PP.ProviderField;K=T;V=1.0;P=Define per-provider field descriptor;D=API;M=Providers;S=ModelConfiguration
20
- export type ProviderField = {
21
- /** Field key: 'name' | 'apiKey' | 'baseUrl' map to top-level fields; others go into extra.<key> */
22
- key: string
23
- /** Human-readable label shown in the CLI form */
24
- label: string
25
- required?: boolean
26
- /** If true, input is masked in the terminal */
27
- secret?: boolean
28
- placeholder?: string
29
- /** Default value pre-filled in the form */
30
- default?: string
31
- }
32
-
33
- //@C:ID=T.PP.ProviderPreset;K=T;V=2.0;P=Define provider preset structure;D=API;M=Providers;S=ModelConfiguration
34
- export type ProviderPreset = {
35
- id: string
36
- name: string
37
- baseUrl: string
38
- apiFormat: ApiFormat
39
- defaultModels: ModelMapping
40
- needsApiKey: boolean
41
- websiteUrl: string
42
- /** Ordered list of fields to render when adding a new provider from this preset */
43
- fields: ProviderField[]
44
- }
45
-
46
- //@C:ID=D.PP.PROVIDER_PRESETS;K=D;V=2.0;P=Load provider presets from yaml;D=API;M=Providers;S=ModelConfiguration
47
- function loadPresetsFromYaml(): ProviderPreset[] {
48
- try {
49
- const yamlPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'providers.yaml')
50
- const raw = parse(readFileSync(yamlPath, 'utf-8')) as { presets?: ProviderPreset[] }
51
- const presets = raw?.presets
52
- if (!Array.isArray(presets) || presets.length === 0) {
53
- throw new Error('providers.yaml missing presets array')
54
- }
55
- // Ensure fields is always an array
56
- return presets.map(p => ({ ...p, fields: Array.isArray(p.fields) ? p.fields : [] }))
57
- } catch (err) {
58
- console.error('[providerPresets] Failed to load providers.yaml, falling back to defaults:', err)
59
- // Minimal fallback so the server can still start
60
- return [
61
- {
62
- id: 'official',
63
- name: 'Claude Official',
64
- baseUrl: '',
65
- apiFormat: 'anthropic',
66
- defaultModels: { main: '', haiku: '', sonnet: '', opus: '' },
67
- needsApiKey: false,
68
- websiteUrl: 'https://www.anthropic.com/claude-code',
69
- fields: [{ key: 'name', label: 'Provider 昵称', required: true }],
70
- },
71
- {
72
- id: 'custom',
73
- name: 'Custom',
74
- baseUrl: '',
75
- apiFormat: 'anthropic',
76
- defaultModels: { main: '', haiku: '', sonnet: '', opus: '' },
77
- needsApiKey: true,
78
- websiteUrl: '',
79
- fields: [
80
- { key: 'name', label: 'Provider 昵称', required: true },
81
- { key: 'baseUrl', label: 'Base URL', required: true },
82
- { key: 'apiKey', label: 'API Key', required: false, secret: true },
83
- ],
84
- },
85
- ]
86
- }
87
- }
88
-
89
- export const PROVIDER_PRESETS: ProviderPreset[] = loadPresetsFromYaml()
90
-
91
- export async function loadProviderPresets(): Promise<ProviderPreset[]> {
92
- return PROVIDER_PRESETS
93
- }
1
+ // Provider presets — loaded from providers.yaml at startup
2
+
3
+ import { readFileSync } from 'fs'
4
+ import { fileURLToPath } from 'url'
5
+ import { parse } from 'yaml'
6
+ import path from 'path'
7
+ import type { ApiFormat } from '../types/provider.js'
8
+
9
+ export type ProviderField = {
10
+ /** Field key: 'name' | 'apiKey' | 'baseUrl' map to top-level fields; others go into extra.<key> */
11
+ key: string
12
+ /** Human-readable label shown in the CLI form */
13
+ label: string
14
+ required?: boolean
15
+ /** If true, input is masked in the terminal */
16
+ secret?: boolean
17
+ placeholder?: string
18
+ /** Default value pre-filled in the form */
19
+ default?: string
20
+ }
21
+
22
+ export type ProviderPreset = {
23
+ id: string
24
+ name: string
25
+ /** Default base URL for this provider (can be overridden by user) */
26
+ baseUrl: string
27
+ apiFormat: ApiFormat
28
+ needsApiKey: boolean
29
+ websiteUrl: string
30
+ /**
31
+ * Relative path to the models list endpoint, e.g. '/v1/models'.
32
+ * Empty string means dynamic model fetching is not supported.
33
+ */
34
+ modelsUrl: string
35
+ /**
36
+ * Auth header style for the models list request:
37
+ * 'bearer' → Authorization: Bearer <apiKey>
38
+ * 'x-api-key' → x-api-key: <apiKey> (+ anthropic-version header)
39
+ */
40
+ modelsAuthStyle: 'bearer' | 'x-api-key'
41
+ /**
42
+ * Field name in the response JSON that contains the model array.
43
+ * Almost always 'data' (OpenAI-compatible standard).
44
+ */
45
+ modelsDataPath: string
46
+ /** Ordered list of fields to render when adding a new provider from this preset */
47
+ fields: ProviderField[]
48
+ }
49
+
50
+ function loadPresetsFromYaml(): ProviderPreset[] {
51
+ try {
52
+ const yamlPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'providers.yaml')
53
+ const raw = parse(readFileSync(yamlPath, 'utf-8')) as { presets?: ProviderPreset[] }
54
+ const presets = raw?.presets
55
+ if (!Array.isArray(presets) || presets.length === 0) {
56
+ throw new Error('providers.yaml missing presets array')
57
+ }
58
+ // Ensure fields is always an array and apply defaults for optional fields
59
+ return presets.map(p => ({
60
+ modelsUrl: '',
61
+ modelsAuthStyle: 'bearer' as const,
62
+ modelsDataPath: 'data',
63
+ ...p,
64
+ fields: Array.isArray(p.fields) ? p.fields : [],
65
+ }))
66
+ } catch (err) {
67
+ console.error('[providerPresets] Failed to load providers.yaml, falling back to defaults:', err)
68
+ return [
69
+ {
70
+ id: 'official',
71
+ name: 'Claude Official',
72
+ baseUrl: '',
73
+ apiFormat: 'anthropic',
74
+ needsApiKey: false,
75
+ websiteUrl: 'https://www.anthropic.com/claude-code',
76
+ modelsUrl: '/v1/models',
77
+ modelsAuthStyle: 'x-api-key',
78
+ modelsDataPath: 'data',
79
+ fields: [{ key: 'name', label: 'Provider 昵称', required: true }],
80
+ },
81
+ {
82
+ id: 'custom',
83
+ name: 'Custom',
84
+ baseUrl: '',
85
+ apiFormat: 'anthropic',
86
+ needsApiKey: true,
87
+ websiteUrl: '',
88
+ modelsUrl: '/v1/models',
89
+ modelsAuthStyle: 'bearer',
90
+ modelsDataPath: 'data',
91
+ fields: [
92
+ { key: 'name', label: 'Provider 昵称', required: true },
93
+ { key: 'baseUrl', label: 'Base URL', required: true },
94
+ { key: 'apiKey', label: 'API Key', required: false, secret: true },
95
+ ],
96
+ },
97
+ ]
98
+ }
99
+ }
100
+
101
+ export const PROVIDER_PRESETS: ProviderPreset[] = loadPresetsFromYaml()
102
+
103
+ export async function loadProviderPresets(): Promise<ProviderPreset[]> {
104
+ return PROVIDER_PRESETS
105
+ }
@@ -1,145 +1,138 @@
1
- version: 2
2
-
3
- # Provider 预设配置
4
- # fields 数组声明新增时需填写的字段
5
- # key: 'name' | 'apiKey' | 'baseUrl' 直接映射到顶层字段,其余存入 extra.<key>
6
- # secret: true 时前端使用密码掩码显示
7
-
8
- presets:
9
- - id: official
10
- name: Claude Official
11
- baseUrl: ''
12
- apiFormat: anthropic
13
- needsApiKey: false
14
- websiteUrl: https://www.anthropic.com/claude-code
15
- defaultModels:
16
- main: ''
17
- haiku: ''
18
- sonnet: ''
19
- opus: ''
20
- fields:
21
- - key: name
22
- label: Provider 昵称
23
- required: true
24
- secret: false
25
- placeholder: 'e.g. Claude Official'
26
-
27
- - id: deepseek
28
- name: DeepSeek
29
- baseUrl: https://api.deepseek.com/anthropic
30
- apiFormat: anthropic
31
- needsApiKey: true
32
- websiteUrl: https://platform.deepseek.com
33
- defaultModels:
34
- main: DeepSeek-V3.2
35
- haiku: DeepSeek-V3.2
36
- sonnet: DeepSeek-V3.2
37
- opus: DeepSeek-V3.2
38
- fields:
39
- - key: name
40
- label: Provider 昵称
41
- required: true
42
- secret: false
43
- placeholder: 'e.g. My DeepSeek'
44
- - key: apiKey
45
- label: API Key
46
- required: true
47
- secret: true
48
- placeholder: 'sk-...'
49
-
50
- - id: zhipuglm
51
- name: Zhipu GLM
52
- baseUrl: https://open.bigmodel.cn/api/anthropic
53
- apiFormat: anthropic
54
- needsApiKey: true
55
- websiteUrl: https://open.bigmodel.cn
56
- defaultModels:
57
- main: glm-5
58
- haiku: glm-5
59
- sonnet: glm-5
60
- opus: glm-5
61
- fields:
62
- - key: name
63
- label: Provider 昵称
64
- required: true
65
- secret: false
66
- placeholder: 'e.g. My GLM'
67
- - key: apiKey
68
- label: API Key
69
- required: true
70
- secret: true
71
- placeholder: '智谱 API Key'
72
-
73
- - id: kimi
74
- name: Kimi
75
- baseUrl: https://api.moonshot.cn/anthropic
76
- apiFormat: anthropic
77
- needsApiKey: true
78
- websiteUrl: https://platform.moonshot.cn
79
- defaultModels:
80
- main: kimi-k2.5
81
- haiku: kimi-k2.5
82
- sonnet: kimi-k2.5
83
- opus: kimi-k2.5
84
- fields:
85
- - key: name
86
- label: Provider 昵称
87
- required: true
88
- secret: false
89
- placeholder: 'e.g. My Kimi'
90
- - key: apiKey
91
- label: API Key
92
- required: true
93
- secret: true
94
- placeholder: 'Moonshot API Key'
95
-
96
- - id: minimax
97
- name: MiniMax
98
- baseUrl: https://api.minimaxi.com/anthropic
99
- apiFormat: anthropic
100
- needsApiKey: true
101
- websiteUrl: https://platform.minimaxi.com
102
- defaultModels:
103
- main: MiniMax-M2.7
104
- haiku: MiniMax-M2.7
105
- sonnet: MiniMax-M2.7
106
- opus: MiniMax-M2.7
107
- fields:
108
- - key: name
109
- label: Provider 昵称
110
- required: true
111
- secret: false
112
- placeholder: 'e.g. My MiniMax'
113
- - key: apiKey
114
- label: API Key
115
- required: true
116
- secret: true
117
- placeholder: 'MiniMax API Key'
118
-
119
- - id: custom
120
- name: Custom
121
- baseUrl: ''
122
- apiFormat: anthropic
123
- needsApiKey: true
124
- websiteUrl: ''
125
- defaultModels:
126
- main: ''
127
- haiku: ''
128
- sonnet: ''
129
- opus: ''
130
- fields:
131
- - key: name
132
- label: Provider 昵称
133
- required: true
134
- secret: false
135
- placeholder: 'e.g. My Custom Provider'
136
- - key: baseUrl
137
- label: Base URL
138
- required: true
139
- secret: false
140
- placeholder: 'https://your-api-endpoint.com/anthropic'
141
- - key: apiKey
142
- label: API Key
143
- required: false
144
- secret: true
145
- placeholder: '(可选)API Key'
1
+ version: 2
2
+
3
+ # Provider 预设配置
4
+ # fields 数组声明新增时需填写的字段
5
+ # key: 'name' | 'apiKey' | 'baseUrl' 直接映射到顶层字段,其余存入 extra.<key>
6
+ # secret: true 时前端使用密码掩码显示
7
+ #
8
+ # modelsUrl: 相对于 baseUrl 的模型列表路径,空字符串表示不支持动态拉取
9
+ # modelsAuthStyle: bearer → Authorization: Bearer <apiKey>
10
+ # x-api-key → x-api-key: <apiKey> + anthropic-version header
11
+ # modelsDataPath: 响应 JSON 中模型数组的字段名(几乎总是 'data'
12
+
13
+ presets:
14
+ - id: official
15
+ name: Claude Official
16
+ baseUrl: ''
17
+ apiFormat: anthropic
18
+ needsApiKey: false
19
+ websiteUrl: https://www.anthropic.com/claude-code
20
+ modelsUrl: /v1/models
21
+ modelsAuthStyle: x-api-key
22
+ modelsDataPath: data
23
+ fields:
24
+ - key: name
25
+ label: Provider 昵称
26
+ required: true
27
+ secret: false
28
+ placeholder: 'e.g. Claude Official'
29
+
30
+ - id: deepseek
31
+ name: DeepSeek
32
+ baseUrl: https://api.deepseek.com/anthropic
33
+ apiFormat: anthropic
34
+ needsApiKey: true
35
+ websiteUrl: https://platform.deepseek.com
36
+ modelsUrl: /v1/models
37
+ modelsAuthStyle: bearer
38
+ modelsDataPath: data
39
+ fields:
40
+ - key: name
41
+ label: Provider 昵称
42
+ required: true
43
+ secret: false
44
+ placeholder: 'e.g. My DeepSeek'
45
+ - key: apiKey
46
+ label: API Key
47
+ required: true
48
+ secret: true
49
+ placeholder: 'sk-...'
50
+
51
+ - id: zhipuglm
52
+ name: Zhipu GLM
53
+ baseUrl: https://open.bigmodel.cn/api/anthropic
54
+ apiFormat: anthropic
55
+ needsApiKey: true
56
+ websiteUrl: https://open.bigmodel.cn
57
+ modelsUrl: /v1/models
58
+ modelsAuthStyle: bearer
59
+ modelsDataPath: data
60
+ fields:
61
+ - key: name
62
+ label: Provider 昵称
63
+ required: true
64
+ secret: false
65
+ placeholder: 'e.g. My GLM'
66
+ - key: apiKey
67
+ label: API Key
68
+ required: true
69
+ secret: true
70
+ placeholder: '智谱 API Key'
71
+
72
+ - id: kimi
73
+ name: Kimi
74
+ baseUrl: https://api.moonshot.cn/anthropic
75
+ apiFormat: anthropic
76
+ needsApiKey: true
77
+ websiteUrl: https://platform.moonshot.cn
78
+ modelsUrl: /v1/models
79
+ modelsAuthStyle: bearer
80
+ modelsDataPath: data
81
+ fields:
82
+ - key: name
83
+ label: Provider 昵称
84
+ required: true
85
+ secret: false
86
+ placeholder: 'e.g. My Kimi'
87
+ - key: apiKey
88
+ label: API Key
89
+ required: true
90
+ secret: true
91
+ placeholder: 'Moonshot API Key'
92
+
93
+ - id: minimax
94
+ name: MiniMax
95
+ baseUrl: https://api.minimaxi.com/anthropic
96
+ apiFormat: anthropic
97
+ needsApiKey: true
98
+ websiteUrl: https://platform.minimaxi.com
99
+ modelsUrl: /v1/models
100
+ modelsAuthStyle: bearer
101
+ modelsDataPath: data
102
+ fields:
103
+ - key: name
104
+ label: Provider 昵称
105
+ required: true
106
+ secret: false
107
+ placeholder: 'e.g. My MiniMax'
108
+ - key: apiKey
109
+ label: API Key
110
+ required: true
111
+ secret: true
112
+ placeholder: 'MiniMax API Key'
113
+
114
+ - id: custom
115
+ name: Custom
116
+ baseUrl: ''
117
+ apiFormat: openai_chat
118
+ needsApiKey: true
119
+ websiteUrl: ''
120
+ modelsUrl: /v1/models
121
+ modelsAuthStyle: bearer
122
+ modelsDataPath: data
123
+ fields:
124
+ - key: name
125
+ label: Provider 昵称
126
+ required: true
127
+ secret: false
128
+ placeholder: 'e.g. My Custom Provider'
129
+ - key: baseUrl
130
+ label: Base URL
131
+ required: true
132
+ secret: false
133
+ placeholder: 'https://your-api-endpoint.com/v1'
134
+ - key: apiKey
135
+ label: API Key
136
+ required: false
137
+ secret: true
138
+ placeholder: '(可选)API Key'
@@ -9,12 +9,14 @@
9
9
  import * as fs from 'fs/promises'
10
10
  import * as path from 'path'
11
11
  import * as os from 'os'
12
+ import { getDirectFetchOptions } from '../../utils/proxy.ts'
12
13
  import { ApiError } from '../middleware/errorHandler.js'
13
14
  import { anthropicToOpenaiChat } from '../proxy/transform/anthropicToOpenaiChat.js'
14
15
  import { anthropicToOpenaiResponses } from '../proxy/transform/anthropicToOpenaiResponses.js'
15
16
  import { openaiChatToAnthropic } from '../proxy/transform/openaiChatToAnthropic.js'
16
17
  import { openaiResponsesToAnthropic } from '../proxy/transform/openaiResponsesToAnthropic.js'
17
18
  import type { AnthropicRequest, AnthropicResponse } from '../proxy/transform/types.js'
19
+ import { PROVIDER_PRESETS } from '../config/providerPresets.js'
18
20
  import type {
19
21
  SavedProvider,
20
22
  ProvidersIndex,
@@ -379,29 +381,37 @@ export class ProviderService {
379
381
 
380
382
  async fetchProviderModels(id: string): Promise<string[]> {
381
383
  const provider = await this.getProvider(id)
384
+ const preset = PROVIDER_PRESETS.find(p => p.id === provider.presetId)
385
+
382
386
  const base = provider.baseUrl.replace(/\/+$/, '')
383
387
  if (!base) return []
384
388
 
385
- const url = `${base}/v1/models`
389
+ const modelsUrl = preset?.modelsUrl || '/v1/models'
390
+ const url = `${base}${modelsUrl}`
391
+
386
392
  const headers: Record<string, string> = {
387
393
  'Content-Type': 'application/json',
388
- Authorization: `Bearer ${provider.apiKey}`,
389
394
  }
390
- // anthropic format uses x-api-key header style
391
- if (provider.apiFormat === 'anthropic') {
395
+
396
+ const authStyle = preset?.modelsAuthStyle || (provider.apiFormat === 'anthropic' ? 'x-api-key' : 'bearer')
397
+ if (authStyle === 'x-api-key') {
392
398
  headers['x-api-key'] = provider.apiKey
393
- delete headers['Authorization']
394
399
  headers['anthropic-version'] = '2023-06-01'
400
+ } else {
401
+ headers['Authorization'] = `Bearer ${provider.apiKey}`
395
402
  }
396
403
 
397
404
  try {
398
- const res = await fetch(url, { headers, signal: AbortSignal.timeout(10000) })
405
+ const directOpts = getDirectFetchOptions()
406
+ const res = await fetch(url, { headers, signal: AbortSignal.timeout(10000), ...directOpts })
399
407
  if (!res.ok) {
400
408
  return []
401
409
  }
402
- const data = await res.json() as { data?: { id: string }[]; models?: { id: string }[] }
403
- const list = data.data ?? data.models ?? []
404
- return list.map((m: { id: string }) => m.id).filter(Boolean)
410
+ const data = await res.json() as any
411
+ const dataPath = preset?.modelsDataPath || 'data'
412
+ const list = data[dataPath] ?? data.data ?? data.models ?? []
413
+ if (!Array.isArray(list)) return []
414
+ return list.map((m: any) => (typeof m === 'string' ? m : m.id)).filter(Boolean)
405
415
  } catch {
406
416
  return []
407
417
  }
@@ -464,11 +474,13 @@ export class ProviderService {
464
474
  const start = Date.now()
465
475
  try {
466
476
  const { url, headers, body } = buildDirectTestRequest(base, apiKey, modelId, format)
477
+ const directOpts = getDirectFetchOptions()
467
478
  const response = await fetch(url, {
468
479
  method: 'POST',
469
480
  headers,
470
481
  body: JSON.stringify(body),
471
482
  signal: AbortSignal.timeout(30000),
483
+ ...directOpts,
472
484
  })
473
485
 
474
486
  const latencyMs = Date.now() - start
@@ -526,11 +538,13 @@ export class ProviderService {
526
538
  }
527
539
 
528
540
  // Call upstream with transformed request
541
+ const directOpts = getDirectFetchOptions()
529
542
  const response = await fetch(upstreamUrl, {
530
543
  method: 'POST',
531
544
  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
532
545
  body: JSON.stringify(transformedBody),
533
546
  signal: AbortSignal.timeout(30000),
547
+ ...directOpts,
534
548
  })
535
549
 
536
550
  if (!response.ok) {
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Provider types — preset-based provider configuration.
3
3
  *
4
- * Providers are stored in ~/.claude/cc-haha/providers.json as a lightweight index.
5
- * The active provider's env vars are written to ~/.claude/settings.json.
4
+ * Providers are stored in ~/.claude/bingo/providers.json as a lightweight index.
5
+ * The active provider's env vars are written to ~/.claude/bingo/settings.json.
6
6
  */
7
7
 
8
8
  import { z } from 'zod'
@@ -44,7 +44,7 @@ export const CreateProviderSchema = z.object({
44
44
  apiKey: z.string(),
45
45
  baseUrl: z.string(),
46
46
  apiFormat: ApiFormatSchema.default('anthropic'),
47
- models: ModelMappingSchema,
47
+ models: ModelMappingSchema.default({ main: '', haiku: '', sonnet: '', opus: '' }).optional(),
48
48
  notes: z.string().optional(),
49
49
  extra: z.record(z.any()).optional(),
50
50
  }).catchall(z.any())
@@ -93,15 +93,15 @@ function filterSettingsEnv(
93
93
  }
94
94
 
95
95
  /**
96
- * Read env vars from ~/.claude/cc-haha/settings.json (Haha-specific provider
96
+ * Read env vars from ~/.claude/bingo/settings.json (Bingo-specific provider
97
97
  * config). This file is written by ProviderService.syncToSettings() and
98
98
  * contains ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, model defaults, etc.
99
99
  * Returns an empty object if the file doesn't exist or is invalid.
100
100
  */
101
101
  function getCcHahaSettingsEnv(): Record<string, string> {
102
102
  try {
103
- const ccHahaSettings = join(getClaudeConfigHomeDir(), 'cc-haha', 'settings.json')
104
- const raw = readFileSync(ccHahaSettings, 'utf-8')
103
+ const bingoSettings = join(getClaudeConfigHomeDir(), 'bingo', 'settings.json')
104
+ const raw = readFileSync(bingoSettings, 'utf-8')
105
105
  const parsed = JSON.parse(raw) as { env?: Record<string, string> }
106
106
  return parsed.env ?? {}
107
107
  } catch {