openllmprovider 0.1.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 +192 -0
- package/dist/auth/index.cjs +6 -0
- package/dist/auth/index.d.cts +3 -0
- package/dist/auth/index.d.mts +3 -0
- package/dist/auth/index.mjs +3 -0
- package/dist/auto-C2hXJY13.d.cts +33 -0
- package/dist/auto-C2hXJY13.d.cts.map +1 -0
- package/dist/auto-CBqNYBXs.mjs +48 -0
- package/dist/auto-CBqNYBXs.mjs.map +1 -0
- package/dist/auto-CInerwvs.d.mts +33 -0
- package/dist/auto-CInerwvs.d.mts.map +1 -0
- package/dist/auto-D77wgMqO.cjs +59 -0
- package/dist/auto-D77wgMqO.cjs.map +1 -0
- package/dist/file-DB-rxfzi.mjs +77 -0
- package/dist/file-DB-rxfzi.mjs.map +1 -0
- package/dist/file-DZ7FGcSW.cjs +73 -0
- package/dist/file-DZ7FGcSW.cjs.map +1 -0
- package/dist/index.cjs +1909 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1239 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +1241 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1891 -0
- package/dist/index.mjs.map +1 -0
- package/dist/logger-BsHpI_fH.mjs +11 -0
- package/dist/logger-BsHpI_fH.mjs.map +1 -0
- package/dist/logger-jRimlMFR.cjs +69 -0
- package/dist/logger-jRimlMFR.cjs.map +1 -0
- package/dist/plugin/index.cjs +29 -0
- package/dist/plugin/index.cjs.map +1 -0
- package/dist/plugin/index.d.cts +10 -0
- package/dist/plugin/index.d.cts.map +1 -0
- package/dist/plugin/index.d.mts +10 -0
- package/dist/plugin/index.d.mts.map +1 -0
- package/dist/plugin/index.mjs +25 -0
- package/dist/plugin/index.mjs.map +1 -0
- package/dist/plugin-BkeUu5LW.d.mts +46 -0
- package/dist/plugin-BkeUu5LW.d.mts.map +1 -0
- package/dist/plugin-wK7RmJhZ.d.cts +46 -0
- package/dist/plugin-wK7RmJhZ.d.cts.map +1 -0
- package/dist/resolver-BA7LWSJO.mjs +645 -0
- package/dist/resolver-BA7LWSJO.mjs.map +1 -0
- package/dist/resolver-BMTvzTt9.cjs +662 -0
- package/dist/resolver-BMTvzTt9.cjs.map +1 -0
- package/dist/resolver-MgJryMWG.d.cts +75 -0
- package/dist/resolver-MgJryMWG.d.cts.map +1 -0
- package/dist/resolver-_gfXzr_S.d.mts +76 -0
- package/dist/resolver-_gfXzr_S.d.mts.map +1 -0
- package/dist/storage/index.cjs +7 -0
- package/dist/storage/index.d.cts +12 -0
- package/dist/storage/index.d.cts.map +1 -0
- package/dist/storage/index.d.mts +12 -0
- package/dist/storage/index.d.mts.map +1 -0
- package/dist/storage/index.mjs +4 -0
- package/package.json +137 -0
- package/src/auth/.gitkeep +0 -0
- package/src/auth/index.ts +10 -0
- package/src/auth/resolver.ts +46 -0
- package/src/auth/scanners.ts +462 -0
- package/src/auth/store.ts +357 -0
- package/src/catalog/.gitkeep +0 -0
- package/src/catalog/catalog.ts +302 -0
- package/src/catalog/index.ts +17 -0
- package/src/catalog/mapper.ts +129 -0
- package/src/catalog/merger.ts +99 -0
- package/src/index.ts +37 -0
- package/src/logger.ts +7 -0
- package/src/plugin/.gitkeep +0 -0
- package/src/plugin/anthropic.test.ts +505 -0
- package/src/plugin/anthropic.ts +324 -0
- package/src/plugin/codex.ts +656 -0
- package/src/plugin/copilot.ts +161 -0
- package/src/plugin/google.ts +454 -0
- package/src/plugin/index.ts +30 -0
- package/src/provider/.gitkeep +0 -0
- package/src/provider/bundled.ts +59 -0
- package/src/provider/index.ts +249 -0
- package/src/provider/state.ts +163 -0
- package/src/storage/.gitkeep +0 -0
- package/src/storage/auto.ts +32 -0
- package/src/storage/file.ts +84 -0
- package/src/storage/index.ts +10 -0
- package/src/storage/memory.ts +23 -0
- package/src/types/.gitkeep +0 -0
- package/src/types/auth.ts +18 -0
- package/src/types/errors.ts +87 -0
- package/src/types/index.ts +26 -0
- package/src/types/model.ts +88 -0
- package/src/types/plugin.ts +49 -0
- package/src/types/provider.ts +48 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import { createLogger } from '../logger.js'
|
|
2
|
+
|
|
3
|
+
const log = createLogger('auth:scanners')
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Core types
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
export interface DiskScanResult {
|
|
10
|
+
providerId: string
|
|
11
|
+
source: string
|
|
12
|
+
key?: string
|
|
13
|
+
credentialType?: 'api' | 'oauth' | 'wellknown'
|
|
14
|
+
refresh?: string
|
|
15
|
+
accountId?: string
|
|
16
|
+
expires?: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DiskScanner {
|
|
20
|
+
name: string
|
|
21
|
+
scan(ctx: ScanContext): Promise<DiskScanResult[]>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ScanContext {
|
|
25
|
+
readFile(path: string): Promise<string | undefined>
|
|
26
|
+
homedir(): string
|
|
27
|
+
platform(): string
|
|
28
|
+
env(name: string): string | undefined
|
|
29
|
+
exec?(command: string): Promise<string | undefined>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Default ScanContext (Node.js)
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
export function createNodeScanContext(): ScanContext {
|
|
37
|
+
return {
|
|
38
|
+
async readFile(path: string): Promise<string | undefined> {
|
|
39
|
+
try {
|
|
40
|
+
const fs = await import('node:fs/promises')
|
|
41
|
+
return await fs.readFile(path, 'utf-8')
|
|
42
|
+
} catch {
|
|
43
|
+
return undefined
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
homedir(): string {
|
|
47
|
+
return process.env.HOME ?? process.env.USERPROFILE ?? ''
|
|
48
|
+
},
|
|
49
|
+
platform(): string {
|
|
50
|
+
return process.platform
|
|
51
|
+
},
|
|
52
|
+
env(name: string): string | undefined {
|
|
53
|
+
return process.env[name]
|
|
54
|
+
},
|
|
55
|
+
async exec(command: string): Promise<string | undefined> {
|
|
56
|
+
try {
|
|
57
|
+
const { execSync } = await import('node:child_process')
|
|
58
|
+
return execSync(command, { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim()
|
|
59
|
+
} catch {
|
|
60
|
+
return undefined
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Helpers
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
function join(base: string, ...segments: string[]): string {
|
|
71
|
+
return [base, ...segments].join('/')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseJson(raw: string): Record<string, unknown> | undefined {
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(raw)
|
|
77
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
78
|
+
? (parsed as Record<string, unknown>)
|
|
79
|
+
: undefined
|
|
80
|
+
} catch {
|
|
81
|
+
return undefined
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function stripJsonComments(raw: string): string {
|
|
86
|
+
return raw.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function readJson(ctx: ScanContext, path: string): Promise<Record<string, unknown> | undefined> {
|
|
90
|
+
const raw = await ctx.readFile(path)
|
|
91
|
+
if (!raw) return undefined
|
|
92
|
+
return parseJson(raw) ?? parseJson(stripJsonComments(raw))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function configDir(ctx: ScanContext): string {
|
|
96
|
+
const xdg = ctx.env('XDG_CONFIG_HOME')
|
|
97
|
+
if (xdg) return xdg
|
|
98
|
+
return join(ctx.homedir(), '.config')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Scanners
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
const copilotScanner: DiskScanner = {
|
|
106
|
+
name: 'github-copilot',
|
|
107
|
+
async scan(ctx) {
|
|
108
|
+
const results: DiskScanResult[] = []
|
|
109
|
+
const base = join(configDir(ctx), 'github-copilot')
|
|
110
|
+
const files = ['hosts.json', 'apps.json']
|
|
111
|
+
|
|
112
|
+
for (const file of files) {
|
|
113
|
+
const path = join(base, file)
|
|
114
|
+
const json = await readJson(ctx, path)
|
|
115
|
+
if (!json) continue
|
|
116
|
+
|
|
117
|
+
for (const [key, value] of Object.entries(json)) {
|
|
118
|
+
if (!key.includes('github.com')) continue
|
|
119
|
+
const token = value && typeof value === 'object' ? (value as Record<string, unknown>).oauth_token : undefined
|
|
120
|
+
if (typeof token === 'string' && token.length > 0) {
|
|
121
|
+
log('copilot: found token in %s', path)
|
|
122
|
+
results.push({ providerId: 'github-copilot', source: path, key: token })
|
|
123
|
+
return results
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return results
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const claudeCodeScanner: DiskScanner = {
|
|
133
|
+
name: 'claude-code',
|
|
134
|
+
async scan(ctx) {
|
|
135
|
+
const results: DiskScanResult[] = []
|
|
136
|
+
const base = join(ctx.homedir(), '.claude')
|
|
137
|
+
const files = ['settings.json', 'settings.local.json']
|
|
138
|
+
|
|
139
|
+
for (const file of files) {
|
|
140
|
+
const path = join(base, file)
|
|
141
|
+
const json = await readJson(ctx, path)
|
|
142
|
+
if (!json) continue
|
|
143
|
+
|
|
144
|
+
for (const field of ['anthropicApiKey', 'anthropic_api_key', 'ANTHROPIC_API_KEY', 'apiKey']) {
|
|
145
|
+
const value = json[field]
|
|
146
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
147
|
+
log('claude-code: found key in %s (%s)', path, field)
|
|
148
|
+
results.push({ providerId: 'anthropic', source: path, key: value })
|
|
149
|
+
return results
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return results
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const codexCliScanner: DiskScanner = {
|
|
159
|
+
name: 'codex-cli',
|
|
160
|
+
async scan(ctx) {
|
|
161
|
+
const results: DiskScanResult[] = []
|
|
162
|
+
const authPath = join(ctx.homedir(), '.codex', 'auth.json')
|
|
163
|
+
const json = await readJson(ctx, authPath)
|
|
164
|
+
if (!json) return results
|
|
165
|
+
// Check for OPENAI_API_KEY first (user-set API key takes priority)
|
|
166
|
+
const apiKey = json.OPENAI_API_KEY
|
|
167
|
+
if (typeof apiKey === 'string' && apiKey.length > 0) {
|
|
168
|
+
log('codex-cli: found OPENAI_API_KEY in %s', authPath)
|
|
169
|
+
results.push({ providerId: 'openai', source: authPath, key: apiKey, credentialType: 'api' })
|
|
170
|
+
return results
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// OAuth tokens from codex login
|
|
174
|
+
const tokens = json.tokens
|
|
175
|
+
if (tokens !== null && typeof tokens === 'object' && !Array.isArray(tokens)) {
|
|
176
|
+
const t = tokens as Record<string, unknown>
|
|
177
|
+
const accessToken = t.access_token
|
|
178
|
+
if (typeof accessToken === 'string' && accessToken.length > 0) {
|
|
179
|
+
const refreshToken = typeof t.refresh_token === 'string' ? t.refresh_token : undefined
|
|
180
|
+
const accountId = typeof t.account_id === 'string' ? t.account_id : undefined
|
|
181
|
+
log('codex-cli: found OAuth tokens in %s', authPath)
|
|
182
|
+
results.push({
|
|
183
|
+
providerId: 'openai',
|
|
184
|
+
source: authPath,
|
|
185
|
+
key: accessToken,
|
|
186
|
+
credentialType: 'oauth',
|
|
187
|
+
refresh: refreshToken,
|
|
188
|
+
accountId,
|
|
189
|
+
})
|
|
190
|
+
return results
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Fallback: flat token or apiKey field
|
|
195
|
+
const token = json.token ?? json.apiKey
|
|
196
|
+
if (typeof token === 'string' && token.length > 0) {
|
|
197
|
+
results.push({ providerId: 'openai', source: authPath, key: token })
|
|
198
|
+
}
|
|
199
|
+
return results
|
|
200
|
+
},
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const geminiCliScanner: DiskScanner = {
|
|
204
|
+
name: 'gemini-cli',
|
|
205
|
+
async scan(ctx) {
|
|
206
|
+
const results: DiskScanResult[] = []
|
|
207
|
+
const geminiHome = ctx.env('GEMINI_CLI_HOME') ?? join(ctx.homedir(), '.gemini')
|
|
208
|
+
const files = ['oauth_creds.json', 'google_accounts.json']
|
|
209
|
+
|
|
210
|
+
for (const file of files) {
|
|
211
|
+
const path = join(geminiHome, file)
|
|
212
|
+
const json = await readJson(ctx, path)
|
|
213
|
+
if (!json) continue
|
|
214
|
+
const accessToken = typeof json.access_token === 'string' ? json.access_token : undefined
|
|
215
|
+
const refreshToken = typeof json.refresh_token === 'string' ? json.refresh_token : undefined
|
|
216
|
+
const expiryDate = typeof json.expiry_date === 'number' ? json.expiry_date : undefined
|
|
217
|
+
|
|
218
|
+
if (accessToken || refreshToken) {
|
|
219
|
+
log('gemini-cli: found credentials in %s', path)
|
|
220
|
+
results.push({
|
|
221
|
+
providerId: 'google',
|
|
222
|
+
source: path,
|
|
223
|
+
key: accessToken,
|
|
224
|
+
credentialType: 'oauth',
|
|
225
|
+
refresh: refreshToken,
|
|
226
|
+
expires: expiryDate,
|
|
227
|
+
})
|
|
228
|
+
return results
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Fallback: google_accounts.json with accounts array
|
|
232
|
+
if (Array.isArray(json.accounts) && (json.accounts as unknown[]).length > 0) {
|
|
233
|
+
log('gemini-cli: found credentials in %s', path)
|
|
234
|
+
results.push({ providerId: 'google', source: path })
|
|
235
|
+
return results
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return results
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const gcloudAdcScanner: DiskScanner = {
|
|
244
|
+
name: 'gcloud-adc',
|
|
245
|
+
async scan(ctx) {
|
|
246
|
+
const results: DiskScanResult[] = []
|
|
247
|
+
const cloudSdkConfig = ctx.env('CLOUDSDK_CONFIG')
|
|
248
|
+
const paths = [
|
|
249
|
+
ctx.env('GOOGLE_APPLICATION_CREDENTIALS'),
|
|
250
|
+
cloudSdkConfig ? join(cloudSdkConfig, 'application_default_credentials.json') : undefined,
|
|
251
|
+
join(configDir(ctx), 'gcloud', 'application_default_credentials.json'),
|
|
252
|
+
].filter((p): p is string => typeof p === 'string')
|
|
253
|
+
|
|
254
|
+
for (const path of paths) {
|
|
255
|
+
const json = await readJson(ctx, path)
|
|
256
|
+
if (!json) continue
|
|
257
|
+
|
|
258
|
+
if ('refresh_token' in json || 'type' in json || 'private_key' in json) {
|
|
259
|
+
log('gcloud-adc: found ADC in %s', path)
|
|
260
|
+
results.push({ providerId: 'google-vertex', source: path })
|
|
261
|
+
return results
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return results
|
|
266
|
+
},
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const awsCredentialsScanner: DiskScanner = {
|
|
270
|
+
name: 'aws-credentials',
|
|
271
|
+
async scan(ctx) {
|
|
272
|
+
const results: DiskScanResult[] = []
|
|
273
|
+
const credPath = ctx.env('AWS_SHARED_CREDENTIALS_FILE') ?? join(ctx.homedir(), '.aws', 'credentials')
|
|
274
|
+
const raw = await ctx.readFile(credPath)
|
|
275
|
+
if (!raw) return results
|
|
276
|
+
|
|
277
|
+
const profile = ctx.env('AWS_PROFILE') ?? 'default'
|
|
278
|
+
const key = parseIniProfileKey(raw, profile, 'aws_access_key_id')
|
|
279
|
+
|
|
280
|
+
if (key) {
|
|
281
|
+
log('aws: found credentials for profile [%s] in %s', profile, credPath)
|
|
282
|
+
results.push({ providerId: 'amazon-bedrock', source: credPath })
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return results
|
|
286
|
+
},
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Scan opencode's auth.json for saved credentials (API keys only).
|
|
290
|
+
// NOTE: OAuth credentials from opencode should NOT be reused. Providers like
|
|
291
|
+
// Anthropic use refresh token rotation — each refresh invalidates the old
|
|
292
|
+
// refresh_token and issues a new one. If two applications share the same OAuth
|
|
293
|
+
// credential, whichever refreshes first will break the other. API key credentials
|
|
294
|
+
// (type: 'api') are safe to share since they don't expire or rotate.
|
|
295
|
+
const opencodeAuthScanner: DiskScanner = {
|
|
296
|
+
name: 'opencode-auth',
|
|
297
|
+
async scan(ctx) {
|
|
298
|
+
const results: DiskScanResult[] = []
|
|
299
|
+
const xdgData = ctx.env('XDG_DATA_HOME')
|
|
300
|
+
const home = ctx.homedir()
|
|
301
|
+
const platform = ctx.platform()
|
|
302
|
+
|
|
303
|
+
const paths = [
|
|
304
|
+
xdgData ? join(xdgData, 'opencode', 'auth.json') : undefined,
|
|
305
|
+
platform === 'darwin' ? join(home, 'Library', 'Application Support', 'opencode', 'auth.json') : undefined,
|
|
306
|
+
join(home, '.local', 'share', 'opencode', 'auth.json'),
|
|
307
|
+
join(home, '.config', 'opencode', 'auth.json'),
|
|
308
|
+
].filter((p): p is string => typeof p === 'string')
|
|
309
|
+
|
|
310
|
+
for (const path of paths) {
|
|
311
|
+
const json = await readJson(ctx, path)
|
|
312
|
+
if (!json) continue
|
|
313
|
+
|
|
314
|
+
for (const [providerId, entry] of Object.entries(json)) {
|
|
315
|
+
if (!entry || typeof entry !== 'object') continue
|
|
316
|
+
const typed = entry as Record<string, unknown>
|
|
317
|
+
const type = typed.type
|
|
318
|
+
if (type !== 'api' && type !== 'oauth' && type !== 'wellknown') continue
|
|
319
|
+
|
|
320
|
+
const key = typeof typed.key === 'string' ? typed.key : undefined
|
|
321
|
+
const refresh = typeof typed.refresh === 'string' ? typed.refresh : undefined
|
|
322
|
+
const accountId = typeof typed.accountId === 'string' ? typed.accountId : undefined
|
|
323
|
+
const expires = typeof typed.expires === 'number' ? typed.expires : undefined
|
|
324
|
+
log('opencode-auth: found %s (%s) in %s', providerId, type, path)
|
|
325
|
+
results.push({
|
|
326
|
+
providerId,
|
|
327
|
+
source: path,
|
|
328
|
+
key,
|
|
329
|
+
credentialType: type as DiskScanResult['credentialType'],
|
|
330
|
+
refresh,
|
|
331
|
+
accountId,
|
|
332
|
+
expires,
|
|
333
|
+
})
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (results.length > 0) return results
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return results
|
|
340
|
+
},
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const vscodeSettingsScanner: DiskScanner = {
|
|
344
|
+
name: 'vscode',
|
|
345
|
+
async scan(ctx) {
|
|
346
|
+
const results: DiskScanResult[] = []
|
|
347
|
+
const home = ctx.homedir()
|
|
348
|
+
const platform = ctx.platform()
|
|
349
|
+
|
|
350
|
+
const vscodePaths = [
|
|
351
|
+
platform === 'darwin'
|
|
352
|
+
? join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'github.copilot', 'hosts.json')
|
|
353
|
+
: undefined,
|
|
354
|
+
join(configDir(ctx), 'Code', 'User', 'globalStorage', 'github.copilot', 'hosts.json'),
|
|
355
|
+
].filter((p): p is string => typeof p === 'string')
|
|
356
|
+
|
|
357
|
+
for (const path of vscodePaths) {
|
|
358
|
+
const json = await readJson(ctx, path)
|
|
359
|
+
if (!json) continue
|
|
360
|
+
|
|
361
|
+
for (const [key, value] of Object.entries(json)) {
|
|
362
|
+
if (!key.includes('github.com')) continue
|
|
363
|
+
const token = value && typeof value === 'object' ? (value as Record<string, unknown>).oauth_token : undefined
|
|
364
|
+
if (typeof token === 'string' && token.length > 0) {
|
|
365
|
+
log('vscode: found copilot token in %s', path)
|
|
366
|
+
results.push({ providerId: 'github-copilot', source: path, key: token })
|
|
367
|
+
return results
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return results
|
|
373
|
+
},
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ---------------------------------------------------------------------------
|
|
377
|
+
// INI parser (minimal, for AWS credentials)
|
|
378
|
+
// ---------------------------------------------------------------------------
|
|
379
|
+
|
|
380
|
+
function parseIniProfileKey(raw: string, profile: string, key: string): string | undefined {
|
|
381
|
+
const lines = raw.split('\n')
|
|
382
|
+
let inProfile = false
|
|
383
|
+
|
|
384
|
+
for (const line of lines) {
|
|
385
|
+
const trimmed = line.trim()
|
|
386
|
+
if (trimmed.startsWith('[')) {
|
|
387
|
+
const name = trimmed.slice(1, trimmed.indexOf(']')).trim()
|
|
388
|
+
inProfile = name === profile
|
|
389
|
+
continue
|
|
390
|
+
}
|
|
391
|
+
if (inProfile && trimmed.startsWith(key)) {
|
|
392
|
+
const eq = trimmed.indexOf('=')
|
|
393
|
+
if (eq !== -1) return trimmed.slice(eq + 1).trim()
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return undefined
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
// Registry
|
|
401
|
+
// ---------------------------------------------------------------------------
|
|
402
|
+
|
|
403
|
+
const cursorScanner: DiskScanner = {
|
|
404
|
+
name: 'cursor',
|
|
405
|
+
async scan(ctx) {
|
|
406
|
+
const results: DiskScanResult[] = []
|
|
407
|
+
const home = ctx.homedir()
|
|
408
|
+
const platform = ctx.platform()
|
|
409
|
+
|
|
410
|
+
const dbPaths = [
|
|
411
|
+
platform === 'darwin'
|
|
412
|
+
? join(home, 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'state.vscdb')
|
|
413
|
+
: undefined,
|
|
414
|
+
platform === 'win32'
|
|
415
|
+
? join(ctx.env('APPDATA') ?? join(home, 'AppData', 'Roaming'), 'Cursor', 'User', 'globalStorage', 'state.vscdb')
|
|
416
|
+
: undefined,
|
|
417
|
+
join(configDir(ctx), 'Cursor', 'User', 'globalStorage', 'state.vscdb'),
|
|
418
|
+
].filter((p): p is string => typeof p === 'string')
|
|
419
|
+
|
|
420
|
+
if (!ctx.exec) return results
|
|
421
|
+
|
|
422
|
+
for (const dbPath of dbPaths) {
|
|
423
|
+
const query = `SELECT value FROM ItemTable WHERE key='cursorAuth/openAIKey'`
|
|
424
|
+
const value = await ctx.exec(`sqlite3 "${dbPath}" "${query}"`)
|
|
425
|
+
if (value && value.length > 0) {
|
|
426
|
+
log('cursor: found openAIKey in %s', dbPath)
|
|
427
|
+
results.push({ providerId: 'cursor', source: dbPath, key: value })
|
|
428
|
+
return results
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return results
|
|
433
|
+
},
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export const DEFAULT_SCANNERS: DiskScanner[] = [
|
|
437
|
+
copilotScanner,
|
|
438
|
+
vscodeSettingsScanner,
|
|
439
|
+
claudeCodeScanner,
|
|
440
|
+
codexCliScanner,
|
|
441
|
+
geminiCliScanner,
|
|
442
|
+
gcloudAdcScanner,
|
|
443
|
+
awsCredentialsScanner,
|
|
444
|
+
cursorScanner,
|
|
445
|
+
opencodeAuthScanner,
|
|
446
|
+
]
|
|
447
|
+
|
|
448
|
+
export async function runDiskScanners(scanners: DiskScanner[], ctx?: ScanContext): Promise<DiskScanResult[]> {
|
|
449
|
+
const scanCtx = ctx ?? createNodeScanContext()
|
|
450
|
+
const results: DiskScanResult[] = []
|
|
451
|
+
|
|
452
|
+
for (const scanner of scanners) {
|
|
453
|
+
try {
|
|
454
|
+
const found = await scanner.scan(scanCtx)
|
|
455
|
+
results.push(...found)
|
|
456
|
+
} catch (err: unknown) {
|
|
457
|
+
log('scanner %s failed: %s', scanner.name, err instanceof Error ? err.message : String(err))
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return results
|
|
462
|
+
}
|