hedgequantx 2.5.23 → 2.5.25
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 +87 -14
- package/package.json +1 -1
- package/src/menus/ai-agent.js +142 -176
- package/src/services/ai/client.js +77 -8
- package/src/services/ai/index.js +10 -40
- package/src/services/ai/oauth-anthropic.js +265 -0
- package/src/services/ai/providers/index.js +12 -11
- package/src/services/ai/token-scanner.js +0 -1414
|
@@ -1,1414 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Token Scanner - Ultra Solid Edition
|
|
3
|
-
* Scans for existing AI provider tokens from various IDEs, tools, and configs
|
|
4
|
-
* Supports macOS, Linux, Windows, and headless servers
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const os = require('os');
|
|
10
|
-
const { execSync } = require('child_process');
|
|
11
|
-
|
|
12
|
-
const homeDir = os.homedir();
|
|
13
|
-
const platform = process.platform; // 'darwin', 'linux', 'win32'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Detect if running on a headless server (no GUI)
|
|
17
|
-
*/
|
|
18
|
-
const isHeadlessServer = () => {
|
|
19
|
-
if (platform === 'win32') return false;
|
|
20
|
-
|
|
21
|
-
// Check for common server indicators
|
|
22
|
-
const indicators = [
|
|
23
|
-
!process.env.DISPLAY && !process.env.WAYLAND_DISPLAY, // No display server
|
|
24
|
-
process.env.SSH_CLIENT || process.env.SSH_TTY, // SSH session
|
|
25
|
-
process.env.TERM === 'dumb', // Dumb terminal
|
|
26
|
-
fs.existsSync('/etc/ssh/sshd_config'), // SSH server installed
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
// Check if running in container
|
|
30
|
-
const inContainer = fs.existsSync('/.dockerenv') ||
|
|
31
|
-
fs.existsSync('/run/.containerenv') ||
|
|
32
|
-
(fs.existsSync('/proc/1/cgroup') &&
|
|
33
|
-
fs.readFileSync('/proc/1/cgroup', 'utf8').includes('docker'));
|
|
34
|
-
|
|
35
|
-
return indicators.filter(Boolean).length >= 2 || inContainer;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Get app data directory based on OS
|
|
40
|
-
*/
|
|
41
|
-
const getAppDataDir = () => {
|
|
42
|
-
switch (platform) {
|
|
43
|
-
case 'darwin':
|
|
44
|
-
return path.join(homeDir, 'Library', 'Application Support');
|
|
45
|
-
case 'win32':
|
|
46
|
-
return process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming');
|
|
47
|
-
case 'linux':
|
|
48
|
-
default:
|
|
49
|
-
return process.env.XDG_CONFIG_HOME || path.join(homeDir, '.config');
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Get all possible config directories (for thorough scanning)
|
|
55
|
-
*/
|
|
56
|
-
const getAllConfigDirs = () => {
|
|
57
|
-
const dirs = [homeDir];
|
|
58
|
-
|
|
59
|
-
switch (platform) {
|
|
60
|
-
case 'darwin':
|
|
61
|
-
dirs.push(
|
|
62
|
-
path.join(homeDir, 'Library', 'Application Support'),
|
|
63
|
-
path.join(homeDir, 'Library', 'Preferences'),
|
|
64
|
-
path.join(homeDir, '.config')
|
|
65
|
-
);
|
|
66
|
-
break;
|
|
67
|
-
case 'win32':
|
|
68
|
-
dirs.push(
|
|
69
|
-
process.env.APPDATA,
|
|
70
|
-
process.env.LOCALAPPDATA,
|
|
71
|
-
path.join(homeDir, '.config')
|
|
72
|
-
);
|
|
73
|
-
break;
|
|
74
|
-
case 'linux':
|
|
75
|
-
default:
|
|
76
|
-
dirs.push(
|
|
77
|
-
path.join(homeDir, '.config'),
|
|
78
|
-
path.join(homeDir, '.local', 'share'),
|
|
79
|
-
'/etc' // System-wide configs (server)
|
|
80
|
-
);
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return dirs.filter(d => d && pathExists(d));
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* IDE and tool configurations for token scanning
|
|
89
|
-
*/
|
|
90
|
-
const TOKEN_SOURCES = {
|
|
91
|
-
// ==================== VS CODE FAMILY ====================
|
|
92
|
-
vscode: {
|
|
93
|
-
name: 'VS CODE',
|
|
94
|
-
icon: '💻',
|
|
95
|
-
paths: {
|
|
96
|
-
darwin: [
|
|
97
|
-
path.join(getAppDataDir(), 'Code', 'User', 'globalStorage'),
|
|
98
|
-
path.join(getAppDataDir(), 'Code', 'User')
|
|
99
|
-
],
|
|
100
|
-
linux: [
|
|
101
|
-
path.join(homeDir, '.config', 'Code', 'User', 'globalStorage'),
|
|
102
|
-
path.join(homeDir, '.config', 'Code', 'User'),
|
|
103
|
-
path.join(homeDir, '.vscode')
|
|
104
|
-
],
|
|
105
|
-
win32: [
|
|
106
|
-
path.join(getAppDataDir(), 'Code', 'User', 'globalStorage'),
|
|
107
|
-
path.join(getAppDataDir(), 'Code', 'User')
|
|
108
|
-
]
|
|
109
|
-
},
|
|
110
|
-
extensions: {
|
|
111
|
-
claude: ['anthropic.claude-code', 'anthropic.claude'],
|
|
112
|
-
continue: ['continue.continue'],
|
|
113
|
-
cline: ['saoudrizwan.claude-dev'],
|
|
114
|
-
openai: ['openai.openai-chatgpt']
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
vscodeInsiders: {
|
|
119
|
-
name: 'VS CODE INSIDERS',
|
|
120
|
-
icon: '💻',
|
|
121
|
-
paths: {
|
|
122
|
-
darwin: [path.join(getAppDataDir(), 'Code - Insiders', 'User', 'globalStorage')],
|
|
123
|
-
linux: [path.join(homeDir, '.config', 'Code - Insiders', 'User', 'globalStorage')],
|
|
124
|
-
win32: [path.join(getAppDataDir(), 'Code - Insiders', 'User', 'globalStorage')]
|
|
125
|
-
},
|
|
126
|
-
extensions: {
|
|
127
|
-
claude: ['anthropic.claude-code', 'anthropic.claude'],
|
|
128
|
-
continue: ['continue.continue']
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
|
|
132
|
-
vscodium: {
|
|
133
|
-
name: 'VSCODIUM',
|
|
134
|
-
icon: '💻',
|
|
135
|
-
paths: {
|
|
136
|
-
darwin: [path.join(getAppDataDir(), 'VSCodium', 'User', 'globalStorage')],
|
|
137
|
-
linux: [path.join(homeDir, '.config', 'VSCodium', 'User', 'globalStorage')],
|
|
138
|
-
win32: [path.join(getAppDataDir(), 'VSCodium', 'User', 'globalStorage')]
|
|
139
|
-
},
|
|
140
|
-
extensions: {
|
|
141
|
-
claude: ['anthropic.claude-code'],
|
|
142
|
-
continue: ['continue.continue']
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
|
|
146
|
-
// ==================== AI-FOCUSED EDITORS ====================
|
|
147
|
-
cursor: {
|
|
148
|
-
name: 'CURSOR',
|
|
149
|
-
icon: '🖱️',
|
|
150
|
-
paths: {
|
|
151
|
-
darwin: [
|
|
152
|
-
path.join(getAppDataDir(), 'Cursor', 'User', 'globalStorage'),
|
|
153
|
-
path.join(getAppDataDir(), 'Cursor', 'User'),
|
|
154
|
-
path.join(homeDir, '.cursor')
|
|
155
|
-
],
|
|
156
|
-
linux: [
|
|
157
|
-
path.join(homeDir, '.config', 'Cursor', 'User', 'globalStorage'),
|
|
158
|
-
path.join(homeDir, '.cursor')
|
|
159
|
-
],
|
|
160
|
-
win32: [
|
|
161
|
-
path.join(getAppDataDir(), 'Cursor', 'User', 'globalStorage'),
|
|
162
|
-
path.join(homeDir, '.cursor')
|
|
163
|
-
]
|
|
164
|
-
},
|
|
165
|
-
extensions: {
|
|
166
|
-
claude: ['anthropic.claude-code'],
|
|
167
|
-
continue: ['continue.continue']
|
|
168
|
-
},
|
|
169
|
-
configFiles: ['config.json', 'settings.json', 'credentials.json']
|
|
170
|
-
},
|
|
171
|
-
|
|
172
|
-
windsurf: {
|
|
173
|
-
name: 'WINDSURF',
|
|
174
|
-
icon: '🏄',
|
|
175
|
-
paths: {
|
|
176
|
-
darwin: [
|
|
177
|
-
path.join(getAppDataDir(), 'Windsurf', 'User', 'globalStorage'),
|
|
178
|
-
path.join(getAppDataDir(), 'Windsurf', 'User')
|
|
179
|
-
],
|
|
180
|
-
linux: [
|
|
181
|
-
path.join(homeDir, '.config', 'Windsurf', 'User', 'globalStorage'),
|
|
182
|
-
path.join(homeDir, '.windsurf')
|
|
183
|
-
],
|
|
184
|
-
win32: [
|
|
185
|
-
path.join(getAppDataDir(), 'Windsurf', 'User', 'globalStorage')
|
|
186
|
-
]
|
|
187
|
-
},
|
|
188
|
-
extensions: {
|
|
189
|
-
claude: ['anthropic.claude-code']
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
|
|
193
|
-
zed: {
|
|
194
|
-
name: 'ZED',
|
|
195
|
-
icon: '⚡',
|
|
196
|
-
paths: {
|
|
197
|
-
darwin: [
|
|
198
|
-
path.join(getAppDataDir(), 'Zed'),
|
|
199
|
-
path.join(homeDir, '.zed')
|
|
200
|
-
],
|
|
201
|
-
linux: [
|
|
202
|
-
path.join(homeDir, '.config', 'zed'),
|
|
203
|
-
path.join(homeDir, '.zed')
|
|
204
|
-
],
|
|
205
|
-
win32: [
|
|
206
|
-
path.join(getAppDataDir(), 'Zed')
|
|
207
|
-
]
|
|
208
|
-
},
|
|
209
|
-
configFiles: ['settings.json', 'credentials.json', 'keychain.json']
|
|
210
|
-
},
|
|
211
|
-
|
|
212
|
-
// ==================== CLI TOOLS ====================
|
|
213
|
-
claudeCli: {
|
|
214
|
-
name: 'CLAUDE CLI',
|
|
215
|
-
icon: '🤖',
|
|
216
|
-
paths: {
|
|
217
|
-
darwin: [
|
|
218
|
-
path.join(homeDir, '.claude'),
|
|
219
|
-
path.join(homeDir, '.config', 'claude'),
|
|
220
|
-
path.join(getAppDataDir(), 'Claude')
|
|
221
|
-
],
|
|
222
|
-
linux: [
|
|
223
|
-
path.join(homeDir, '.claude'),
|
|
224
|
-
path.join(homeDir, '.config', 'claude')
|
|
225
|
-
],
|
|
226
|
-
win32: [
|
|
227
|
-
path.join(homeDir, '.claude'),
|
|
228
|
-
path.join(getAppDataDir(), 'Claude')
|
|
229
|
-
]
|
|
230
|
-
},
|
|
231
|
-
configFiles: ['.credentials.json', 'credentials.json', 'config.json', '.credentials', 'settings.json', 'settings.local.json', 'auth.json', 'claude.json']
|
|
232
|
-
},
|
|
233
|
-
|
|
234
|
-
// Claude config in home directory
|
|
235
|
-
claudeHome: {
|
|
236
|
-
name: 'CLAUDE CONFIG',
|
|
237
|
-
icon: '🤖',
|
|
238
|
-
paths: {
|
|
239
|
-
darwin: [homeDir],
|
|
240
|
-
linux: [homeDir],
|
|
241
|
-
win32: [homeDir]
|
|
242
|
-
},
|
|
243
|
-
configFiles: ['.claude.json', '.clauderc', '.claude_credentials']
|
|
244
|
-
},
|
|
245
|
-
|
|
246
|
-
opencode: {
|
|
247
|
-
name: 'OPENCODE',
|
|
248
|
-
icon: '🔓',
|
|
249
|
-
paths: {
|
|
250
|
-
darwin: [path.join(homeDir, '.opencode')],
|
|
251
|
-
linux: [path.join(homeDir, '.opencode')],
|
|
252
|
-
win32: [path.join(homeDir, '.opencode')]
|
|
253
|
-
},
|
|
254
|
-
configFiles: ['config.json', 'credentials.json', 'settings.json', 'auth.json']
|
|
255
|
-
},
|
|
256
|
-
|
|
257
|
-
aider: {
|
|
258
|
-
name: 'AIDER',
|
|
259
|
-
icon: '🔧',
|
|
260
|
-
paths: {
|
|
261
|
-
darwin: [path.join(homeDir, '.aider')],
|
|
262
|
-
linux: [path.join(homeDir, '.aider')],
|
|
263
|
-
win32: [path.join(homeDir, '.aider')]
|
|
264
|
-
},
|
|
265
|
-
configFiles: ['config.yml', '.aider.conf.yml', 'credentials.json']
|
|
266
|
-
},
|
|
267
|
-
|
|
268
|
-
continuedev: {
|
|
269
|
-
name: 'CONTINUE.DEV',
|
|
270
|
-
icon: '▶️',
|
|
271
|
-
paths: {
|
|
272
|
-
darwin: [path.join(homeDir, '.continue')],
|
|
273
|
-
linux: [path.join(homeDir, '.continue')],
|
|
274
|
-
win32: [path.join(homeDir, '.continue')]
|
|
275
|
-
},
|
|
276
|
-
configFiles: ['config.json', 'config.yaml', 'credentials.json']
|
|
277
|
-
},
|
|
278
|
-
|
|
279
|
-
cline: {
|
|
280
|
-
name: 'CLINE',
|
|
281
|
-
icon: '📟',
|
|
282
|
-
paths: {
|
|
283
|
-
darwin: [
|
|
284
|
-
path.join(getAppDataDir(), 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev'),
|
|
285
|
-
path.join(homeDir, '.cline')
|
|
286
|
-
],
|
|
287
|
-
linux: [
|
|
288
|
-
path.join(homeDir, '.config', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev'),
|
|
289
|
-
path.join(homeDir, '.cline')
|
|
290
|
-
],
|
|
291
|
-
win32: [
|
|
292
|
-
path.join(getAppDataDir(), 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev')
|
|
293
|
-
]
|
|
294
|
-
},
|
|
295
|
-
configFiles: ['settings.json', 'config.json']
|
|
296
|
-
},
|
|
297
|
-
|
|
298
|
-
// ==================== ENVIRONMENT VARIABLES ====================
|
|
299
|
-
envVars: {
|
|
300
|
-
name: 'ENVIRONMENT',
|
|
301
|
-
icon: '🌍',
|
|
302
|
-
envKeys: [
|
|
303
|
-
'ANTHROPIC_API_KEY',
|
|
304
|
-
'CLAUDE_API_KEY',
|
|
305
|
-
'OPENAI_API_KEY',
|
|
306
|
-
'OPENROUTER_API_KEY',
|
|
307
|
-
'GOOGLE_API_KEY',
|
|
308
|
-
'GEMINI_API_KEY',
|
|
309
|
-
'GROQ_API_KEY',
|
|
310
|
-
'DEEPSEEK_API_KEY',
|
|
311
|
-
'MISTRAL_API_KEY',
|
|
312
|
-
'PERPLEXITY_API_KEY',
|
|
313
|
-
'TOGETHER_API_KEY',
|
|
314
|
-
'XAI_API_KEY',
|
|
315
|
-
'GROK_API_KEY'
|
|
316
|
-
]
|
|
317
|
-
},
|
|
318
|
-
|
|
319
|
-
// ==================== SHELL CONFIGS (dotfiles) ====================
|
|
320
|
-
shellConfigs: {
|
|
321
|
-
name: 'SHELL CONFIG',
|
|
322
|
-
icon: '🐚',
|
|
323
|
-
paths: {
|
|
324
|
-
darwin: [homeDir],
|
|
325
|
-
linux: [homeDir],
|
|
326
|
-
win32: [homeDir]
|
|
327
|
-
},
|
|
328
|
-
configFiles: [
|
|
329
|
-
'.bashrc', '.bash_profile', '.zshrc', '.zprofile',
|
|
330
|
-
'.profile', '.envrc', '.env', '.env.local',
|
|
331
|
-
'.config/fish/config.fish'
|
|
332
|
-
]
|
|
333
|
-
},
|
|
334
|
-
|
|
335
|
-
// ==================== SERVER-SPECIFIC (Linux) ====================
|
|
336
|
-
serverConfigs: {
|
|
337
|
-
name: 'SERVER CONFIG',
|
|
338
|
-
icon: '🖥️',
|
|
339
|
-
paths: {
|
|
340
|
-
linux: [
|
|
341
|
-
'/etc/environment',
|
|
342
|
-
'/etc/profile.d',
|
|
343
|
-
path.join(homeDir, '.config'),
|
|
344
|
-
'/opt'
|
|
345
|
-
]
|
|
346
|
-
},
|
|
347
|
-
configFiles: ['*.env', '*.conf', 'config.json', 'credentials.json']
|
|
348
|
-
},
|
|
349
|
-
|
|
350
|
-
// ==================== NPM/NODE CONFIGS ====================
|
|
351
|
-
npmConfigs: {
|
|
352
|
-
name: 'NPM CONFIG',
|
|
353
|
-
icon: '📦',
|
|
354
|
-
paths: {
|
|
355
|
-
darwin: [path.join(homeDir, '.npm'), path.join(homeDir, '.npmrc')],
|
|
356
|
-
linux: [path.join(homeDir, '.npm'), path.join(homeDir, '.npmrc')],
|
|
357
|
-
win32: [path.join(homeDir, '.npm'), path.join(getAppDataDir(), 'npm')]
|
|
358
|
-
},
|
|
359
|
-
configFiles: ['.npmrc', 'config.json']
|
|
360
|
-
},
|
|
361
|
-
|
|
362
|
-
// ==================== GIT CONFIGS ====================
|
|
363
|
-
gitConfigs: {
|
|
364
|
-
name: 'GIT CONFIG',
|
|
365
|
-
icon: '📂',
|
|
366
|
-
paths: {
|
|
367
|
-
darwin: [path.join(homeDir, '.config', 'git')],
|
|
368
|
-
linux: [path.join(homeDir, '.config', 'git')],
|
|
369
|
-
win32: [path.join(homeDir, '.config', 'git')]
|
|
370
|
-
},
|
|
371
|
-
configFiles: ['credentials', 'config']
|
|
372
|
-
}
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Provider patterns to search for in config files
|
|
377
|
-
*/
|
|
378
|
-
const PROVIDER_PATTERNS = {
|
|
379
|
-
anthropic: {
|
|
380
|
-
name: 'CLAUDE',
|
|
381
|
-
displayName: 'CLAUDE (ANTHROPIC)',
|
|
382
|
-
keyPatterns: [
|
|
383
|
-
/sk-ant-api\d{2}-[a-zA-Z0-9_-]{80,}/g, // New format API key
|
|
384
|
-
/sk-ant-oat\d{2}-[a-zA-Z0-9_-]{40,}/g, // OAuth access token (from Claude Max/Pro subscription)
|
|
385
|
-
/sk-ant-(?!ort)[a-zA-Z0-9_-]{40,}/g, // Old format API key (excludes refresh tokens: sk-ant-ort...)
|
|
386
|
-
],
|
|
387
|
-
sessionPatterns: [
|
|
388
|
-
/"sessionKey"\s*:\s*"([^"]+)"/gi,
|
|
389
|
-
/'sessionKey'\s*:\s*'([^']+)'/gi,
|
|
390
|
-
/sessionKey\s*[=:]\s*['"]?([a-zA-Z0-9_-]{20,})['"]?/gi,
|
|
391
|
-
/claude[_-]?session[_-]?key\s*[=:]\s*['"]?([^'"}\s]+)['"]?/gi,
|
|
392
|
-
/claude[_-]?session\s*[=:]\s*['"]?([^'"}\s]+)['"]?/gi
|
|
393
|
-
],
|
|
394
|
-
envKey: 'ANTHROPIC_API_KEY'
|
|
395
|
-
},
|
|
396
|
-
|
|
397
|
-
openai: {
|
|
398
|
-
name: 'OPENAI',
|
|
399
|
-
displayName: 'OPENAI (GPT)',
|
|
400
|
-
keyPatterns: [
|
|
401
|
-
/sk-proj-[a-zA-Z0-9_-]{100,}/g, // Project API key (new)
|
|
402
|
-
/sk-(?!ant|or)[a-zA-Z0-9]{48,}/g, // Standard API key (NOT anthropic/openrouter)
|
|
403
|
-
],
|
|
404
|
-
sessionPatterns: [
|
|
405
|
-
/openai[_-]?accessToken\s*[=:]\s*['"]?([^'"}\s]+)['"]?/gi,
|
|
406
|
-
/chatgpt[_-]?session\s*[=:]\s*['"]?([^'"}\s]+)['"]?/gi
|
|
407
|
-
],
|
|
408
|
-
envKey: 'OPENAI_API_KEY'
|
|
409
|
-
},
|
|
410
|
-
|
|
411
|
-
openrouter: {
|
|
412
|
-
name: 'OPENROUTER',
|
|
413
|
-
displayName: 'OPENROUTER',
|
|
414
|
-
keyPatterns: [
|
|
415
|
-
/sk-or-v1-[a-zA-Z0-9]{64}/g, // OpenRouter API key
|
|
416
|
-
/sk-or-[a-zA-Z0-9_-]{40,}/g, // Alt format
|
|
417
|
-
],
|
|
418
|
-
envKey: 'OPENROUTER_API_KEY'
|
|
419
|
-
},
|
|
420
|
-
|
|
421
|
-
gemini: {
|
|
422
|
-
name: 'GEMINI',
|
|
423
|
-
displayName: 'GEMINI (GOOGLE)',
|
|
424
|
-
keyPatterns: [
|
|
425
|
-
/AIza[a-zA-Z0-9_-]{35}/g, // Google API key
|
|
426
|
-
],
|
|
427
|
-
envKey: 'GOOGLE_API_KEY'
|
|
428
|
-
},
|
|
429
|
-
|
|
430
|
-
groq: {
|
|
431
|
-
name: 'GROQ',
|
|
432
|
-
displayName: 'GROQ',
|
|
433
|
-
keyPatterns: [
|
|
434
|
-
/gsk_[a-zA-Z0-9]{52}/g, // Groq API key
|
|
435
|
-
],
|
|
436
|
-
envKey: 'GROQ_API_KEY'
|
|
437
|
-
},
|
|
438
|
-
|
|
439
|
-
deepseek: {
|
|
440
|
-
name: 'DEEPSEEK',
|
|
441
|
-
displayName: 'DEEPSEEK',
|
|
442
|
-
keyPatterns: [
|
|
443
|
-
/sk-[a-f0-9]{32}/g, // DeepSeek API key
|
|
444
|
-
],
|
|
445
|
-
envKey: 'DEEPSEEK_API_KEY'
|
|
446
|
-
},
|
|
447
|
-
|
|
448
|
-
mistral: {
|
|
449
|
-
name: 'MISTRAL',
|
|
450
|
-
displayName: 'MISTRAL',
|
|
451
|
-
keyPatterns: [
|
|
452
|
-
/mistral[_-]?[a-zA-Z0-9]{32}/gi, // Mistral key with prefix
|
|
453
|
-
],
|
|
454
|
-
envKey: 'MISTRAL_API_KEY'
|
|
455
|
-
},
|
|
456
|
-
|
|
457
|
-
perplexity: {
|
|
458
|
-
name: 'PERPLEXITY',
|
|
459
|
-
displayName: 'PERPLEXITY',
|
|
460
|
-
keyPatterns: [
|
|
461
|
-
/pplx-[a-zA-Z0-9]{48}/g, // Perplexity API key
|
|
462
|
-
],
|
|
463
|
-
envKey: 'PERPLEXITY_API_KEY'
|
|
464
|
-
},
|
|
465
|
-
|
|
466
|
-
together: {
|
|
467
|
-
name: 'TOGETHER',
|
|
468
|
-
displayName: 'TOGETHER AI',
|
|
469
|
-
keyPatterns: [
|
|
470
|
-
/together[_-]?[a-f0-9]{64}/gi, // Together API key with prefix
|
|
471
|
-
],
|
|
472
|
-
envKey: 'TOGETHER_API_KEY'
|
|
473
|
-
},
|
|
474
|
-
|
|
475
|
-
xai: {
|
|
476
|
-
name: 'XAI',
|
|
477
|
-
displayName: 'GROK (XAI)',
|
|
478
|
-
keyPatterns: [
|
|
479
|
-
/xai-[a-zA-Z0-9_-]{40,}/g, // xAI key
|
|
480
|
-
],
|
|
481
|
-
envKey: 'XAI_API_KEY'
|
|
482
|
-
}
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
// ==================== UTILITY FUNCTIONS ====================
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Check if a path exists
|
|
489
|
-
*/
|
|
490
|
-
const pathExists = (p) => {
|
|
491
|
-
try {
|
|
492
|
-
fs.accessSync(p);
|
|
493
|
-
return true;
|
|
494
|
-
} catch {
|
|
495
|
-
return false;
|
|
496
|
-
}
|
|
497
|
-
};
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Read file safely
|
|
501
|
-
*/
|
|
502
|
-
const readFileSafe = (filePath) => {
|
|
503
|
-
try {
|
|
504
|
-
return fs.readFileSync(filePath, 'utf8');
|
|
505
|
-
} catch {
|
|
506
|
-
return null;
|
|
507
|
-
}
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
/**
|
|
511
|
-
* Get file modification time
|
|
512
|
-
*/
|
|
513
|
-
const getFileModTime = (filePath) => {
|
|
514
|
-
try {
|
|
515
|
-
const stats = fs.statSync(filePath);
|
|
516
|
-
return stats.mtime;
|
|
517
|
-
} catch {
|
|
518
|
-
return null;
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
/**
|
|
523
|
-
* Known credential entries for AI providers (used by all OS)
|
|
524
|
-
* Organized by IDE/App
|
|
525
|
-
*/
|
|
526
|
-
const CREDENTIAL_ENTRIES = [
|
|
527
|
-
// VS Code
|
|
528
|
-
{ service: 'Claude Code-credentials', provider: 'anthropic', name: 'CLAUDE CODE (VS CODE)', ide: 'vscode' },
|
|
529
|
-
{ service: 'vscode.anthropic-credentials', provider: 'anthropic', name: 'VS CODE ANTHROPIC', ide: 'vscode' },
|
|
530
|
-
{ service: 'vscode-openai', provider: 'openai', name: 'VS CODE OPENAI', ide: 'vscode' },
|
|
531
|
-
{ service: 'copilot-credentials', provider: 'openai', name: 'GITHUB COPILOT', ide: 'vscode' },
|
|
532
|
-
|
|
533
|
-
// VS Code Insiders
|
|
534
|
-
{ service: 'Claude Code-credentials-insiders', provider: 'anthropic', name: 'CLAUDE CODE (INSIDERS)', ide: 'vscode-insiders' },
|
|
535
|
-
|
|
536
|
-
// Cursor
|
|
537
|
-
{ service: 'Cursor-credentials', provider: 'anthropic', name: 'CURSOR (CLAUDE)', ide: 'cursor' },
|
|
538
|
-
{ service: 'cursor.anthropic-credentials', provider: 'anthropic', name: 'CURSOR ANTHROPIC', ide: 'cursor' },
|
|
539
|
-
{ service: 'cursor.openai-credentials', provider: 'openai', name: 'CURSOR OPENAI', ide: 'cursor' },
|
|
540
|
-
|
|
541
|
-
// Windsurf
|
|
542
|
-
{ service: 'Windsurf-credentials', provider: 'anthropic', name: 'WINDSURF (CLAUDE)', ide: 'windsurf' },
|
|
543
|
-
{ service: 'windsurf.anthropic-credentials', provider: 'anthropic', name: 'WINDSURF ANTHROPIC', ide: 'windsurf' },
|
|
544
|
-
|
|
545
|
-
// Zed
|
|
546
|
-
{ service: 'Zed-credentials', provider: 'anthropic', name: 'ZED (CLAUDE)', ide: 'zed' },
|
|
547
|
-
{ service: 'zed.anthropic-credentials', provider: 'anthropic', name: 'ZED ANTHROPIC', ide: 'zed' },
|
|
548
|
-
{ service: 'zed.openai-credentials', provider: 'openai', name: 'ZED OPENAI', ide: 'zed' },
|
|
549
|
-
|
|
550
|
-
// Claude CLI / App
|
|
551
|
-
{ service: 'Claude Safe Storage', provider: 'anthropic', name: 'CLAUDE CLI', ide: 'claude-cli' },
|
|
552
|
-
{ service: 'claude-cli-credentials', provider: 'anthropic', name: 'CLAUDE CLI', ide: 'claude-cli' },
|
|
553
|
-
|
|
554
|
-
// Continue.dev
|
|
555
|
-
{ service: 'Continue-credentials', provider: 'anthropic', name: 'CONTINUE.DEV', ide: 'continue' },
|
|
556
|
-
{ service: 'continue.anthropic-credentials', provider: 'anthropic', name: 'CONTINUE ANTHROPIC', ide: 'continue' },
|
|
557
|
-
{ service: 'continue.openai-credentials', provider: 'openai', name: 'CONTINUE OPENAI', ide: 'continue' },
|
|
558
|
-
|
|
559
|
-
// Cline
|
|
560
|
-
{ service: 'Cline-credentials', provider: 'anthropic', name: 'CLINE', ide: 'cline' },
|
|
561
|
-
{ service: 'saoudrizwan.claude-dev-credentials', provider: 'anthropic', name: 'CLINE (CLAUDE DEV)', ide: 'cline' },
|
|
562
|
-
|
|
563
|
-
// OpenCode
|
|
564
|
-
{ service: 'OpenCode-credentials', provider: 'anthropic', name: 'OPENCODE', ide: 'opencode' },
|
|
565
|
-
{ service: 'opencode.anthropic-credentials', provider: 'anthropic', name: 'OPENCODE ANTHROPIC', ide: 'opencode' },
|
|
566
|
-
|
|
567
|
-
// Aider
|
|
568
|
-
{ service: 'Aider-credentials', provider: 'anthropic', name: 'AIDER', ide: 'aider' },
|
|
569
|
-
{ service: 'aider.anthropic-credentials', provider: 'anthropic', name: 'AIDER ANTHROPIC', ide: 'aider' },
|
|
570
|
-
{ service: 'aider.openai-credentials', provider: 'openai', name: 'AIDER OPENAI', ide: 'aider' },
|
|
571
|
-
|
|
572
|
-
// Generic OpenAI
|
|
573
|
-
{ service: 'openai-credentials', provider: 'openai', name: 'OPENAI', ide: 'generic' },
|
|
574
|
-
|
|
575
|
-
// Generic OpenRouter
|
|
576
|
-
{ service: 'openrouter-credentials', provider: 'openrouter', name: 'OPENROUTER', ide: 'generic' },
|
|
577
|
-
];
|
|
578
|
-
|
|
579
|
-
/**
|
|
580
|
-
* Parse credential JSON and extract tokens
|
|
581
|
-
*/
|
|
582
|
-
const parseCredentialJson = (output, entry) => {
|
|
583
|
-
const results = [];
|
|
584
|
-
|
|
585
|
-
try {
|
|
586
|
-
const data = JSON.parse(output);
|
|
587
|
-
|
|
588
|
-
// Extract Claude OAuth access token
|
|
589
|
-
if (data.claudeAiOauth?.accessToken) {
|
|
590
|
-
results.push({
|
|
591
|
-
source: `SECURE STORAGE - ${entry.name}`,
|
|
592
|
-
sourceId: 'secureStorage',
|
|
593
|
-
icon: '🔐',
|
|
594
|
-
type: data.claudeAiOauth.subscriptionType === 'max' ? 'session' : 'api_key',
|
|
595
|
-
provider: entry.provider,
|
|
596
|
-
providerName: PROVIDER_PATTERNS[entry.provider]?.displayName || entry.provider.toUpperCase(),
|
|
597
|
-
token: data.claudeAiOauth.accessToken,
|
|
598
|
-
refreshToken: data.claudeAiOauth.refreshToken,
|
|
599
|
-
expiresAt: data.claudeAiOauth.expiresAt,
|
|
600
|
-
subscriptionType: data.claudeAiOauth.subscriptionType,
|
|
601
|
-
lastUsed: new Date()
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// Extract OpenAI token
|
|
606
|
-
if (data.accessToken && entry.provider === 'openai') {
|
|
607
|
-
results.push({
|
|
608
|
-
source: `SECURE STORAGE - ${entry.name}`,
|
|
609
|
-
sourceId: 'secureStorage',
|
|
610
|
-
icon: '🔐',
|
|
611
|
-
type: 'session',
|
|
612
|
-
provider: entry.provider,
|
|
613
|
-
providerName: PROVIDER_PATTERNS[entry.provider]?.displayName || entry.provider.toUpperCase(),
|
|
614
|
-
token: data.accessToken,
|
|
615
|
-
lastUsed: new Date()
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// Generic API key in JSON
|
|
620
|
-
if (data.apiKey) {
|
|
621
|
-
results.push({
|
|
622
|
-
source: `SECURE STORAGE - ${entry.name}`,
|
|
623
|
-
sourceId: 'secureStorage',
|
|
624
|
-
icon: '🔐',
|
|
625
|
-
type: 'api_key',
|
|
626
|
-
provider: entry.provider,
|
|
627
|
-
providerName: PROVIDER_PATTERNS[entry.provider]?.displayName || entry.provider.toUpperCase(),
|
|
628
|
-
token: data.apiKey,
|
|
629
|
-
lastUsed: new Date()
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
} catch {
|
|
633
|
-
// Not JSON, treat as raw token
|
|
634
|
-
if (output.length > 20) {
|
|
635
|
-
for (const [providerId, provider] of Object.entries(PROVIDER_PATTERNS)) {
|
|
636
|
-
for (const pattern of provider.keyPatterns) {
|
|
637
|
-
pattern.lastIndex = 0;
|
|
638
|
-
if (pattern.test(output)) {
|
|
639
|
-
results.push({
|
|
640
|
-
source: `SECURE STORAGE - ${entry.name}`,
|
|
641
|
-
sourceId: 'secureStorage',
|
|
642
|
-
icon: '🔐',
|
|
643
|
-
type: 'api_key',
|
|
644
|
-
provider: providerId,
|
|
645
|
-
providerName: provider.displayName,
|
|
646
|
-
token: output,
|
|
647
|
-
lastUsed: new Date()
|
|
648
|
-
});
|
|
649
|
-
break;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
return results;
|
|
657
|
-
};
|
|
658
|
-
|
|
659
|
-
/**
|
|
660
|
-
* Read tokens from macOS Keychain
|
|
661
|
-
* Optimized: stops after finding first valid token per provider to minimize password prompts
|
|
662
|
-
*/
|
|
663
|
-
const readMacOSKeychain = () => {
|
|
664
|
-
if (platform !== 'darwin') return [];
|
|
665
|
-
|
|
666
|
-
const results = [];
|
|
667
|
-
const { execSync } = require('child_process');
|
|
668
|
-
const foundProviders = new Set(); // Track which providers we already found
|
|
669
|
-
|
|
670
|
-
// Sort entries to prioritize most common ones first
|
|
671
|
-
const priorityOrder = ['Claude Code-credentials', 'Cursor-credentials', 'Claude Safe Storage'];
|
|
672
|
-
const sortedEntries = [...CREDENTIAL_ENTRIES].sort((a, b) => {
|
|
673
|
-
const aIdx = priorityOrder.indexOf(a.service);
|
|
674
|
-
const bIdx = priorityOrder.indexOf(b.service);
|
|
675
|
-
if (aIdx === -1 && bIdx === -1) return 0;
|
|
676
|
-
if (aIdx === -1) return 1;
|
|
677
|
-
if (bIdx === -1) return -1;
|
|
678
|
-
return aIdx - bIdx;
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
for (const entry of sortedEntries) {
|
|
682
|
-
// Skip if we already found a token for this provider
|
|
683
|
-
if (foundProviders.has(entry.provider)) {
|
|
684
|
-
continue;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
try {
|
|
688
|
-
const output = execSync(
|
|
689
|
-
`security find-generic-password -s "${entry.service}" -w 2>/dev/null`,
|
|
690
|
-
{ encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
691
|
-
).trim();
|
|
692
|
-
|
|
693
|
-
if (output) {
|
|
694
|
-
const tokens = parseCredentialJson(output, entry);
|
|
695
|
-
if (tokens.length > 0) {
|
|
696
|
-
results.push(...tokens);
|
|
697
|
-
foundProviders.add(entry.provider); // Mark this provider as found
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
} catch {
|
|
701
|
-
// Entry not found or access denied - no password prompt for missing entries
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
return results;
|
|
706
|
-
};
|
|
707
|
-
|
|
708
|
-
/**
|
|
709
|
-
* Read tokens from Linux Secret Service (libsecret/gnome-keyring)
|
|
710
|
-
*/
|
|
711
|
-
const readLinuxSecretService = () => {
|
|
712
|
-
if (platform !== 'linux') return [];
|
|
713
|
-
|
|
714
|
-
const results = [];
|
|
715
|
-
const { execSync } = require('child_process');
|
|
716
|
-
|
|
717
|
-
// Check if secret-tool is available
|
|
718
|
-
try {
|
|
719
|
-
execSync('which secret-tool', { stdio: 'pipe' });
|
|
720
|
-
} catch {
|
|
721
|
-
return results; // secret-tool not available
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
for (const entry of CREDENTIAL_ENTRIES) {
|
|
725
|
-
try {
|
|
726
|
-
// Try different attribute combinations
|
|
727
|
-
const commands = [
|
|
728
|
-
`secret-tool lookup service "${entry.service}" 2>/dev/null`,
|
|
729
|
-
`secret-tool lookup application "${entry.service}" 2>/dev/null`,
|
|
730
|
-
`secret-tool lookup xdg:schema "org.freedesktop.Secret.Generic" service "${entry.service}" 2>/dev/null`,
|
|
731
|
-
];
|
|
732
|
-
|
|
733
|
-
for (const cmd of commands) {
|
|
734
|
-
try {
|
|
735
|
-
const output = execSync(cmd, {
|
|
736
|
-
encoding: 'utf8',
|
|
737
|
-
timeout: 5000,
|
|
738
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
739
|
-
}).trim();
|
|
740
|
-
|
|
741
|
-
if (output) {
|
|
742
|
-
results.push(...parseCredentialJson(output, entry));
|
|
743
|
-
break; // Found it, no need to try other commands
|
|
744
|
-
}
|
|
745
|
-
} catch {
|
|
746
|
-
// Command failed, try next
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
} catch {
|
|
750
|
-
// Entry not found
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// Also check VS Code's pass-based storage on Linux
|
|
755
|
-
const passEntries = [
|
|
756
|
-
'vscode/Claude Code-credentials',
|
|
757
|
-
'vscode/Cursor-credentials',
|
|
758
|
-
'vscode/openai-credentials',
|
|
759
|
-
];
|
|
760
|
-
|
|
761
|
-
for (const passPath of passEntries) {
|
|
762
|
-
try {
|
|
763
|
-
const output = execSync(`pass show "${passPath}" 2>/dev/null`, {
|
|
764
|
-
encoding: 'utf8',
|
|
765
|
-
timeout: 5000,
|
|
766
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
767
|
-
}).trim();
|
|
768
|
-
|
|
769
|
-
if (output) {
|
|
770
|
-
const entry = CREDENTIAL_ENTRIES.find(e => passPath.includes(e.service)) ||
|
|
771
|
-
{ service: passPath, provider: 'unknown', name: passPath };
|
|
772
|
-
results.push(...parseCredentialJson(output, entry));
|
|
773
|
-
}
|
|
774
|
-
} catch {
|
|
775
|
-
// pass entry not found
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
return results;
|
|
780
|
-
};
|
|
781
|
-
|
|
782
|
-
/**
|
|
783
|
-
* Read tokens from Windows Credential Manager
|
|
784
|
-
*/
|
|
785
|
-
const readWindowsCredentialManager = () => {
|
|
786
|
-
if (platform !== 'win32') return [];
|
|
787
|
-
|
|
788
|
-
const results = [];
|
|
789
|
-
const { execSync } = require('child_process');
|
|
790
|
-
|
|
791
|
-
for (const entry of CREDENTIAL_ENTRIES) {
|
|
792
|
-
try {
|
|
793
|
-
// Use PowerShell to read from Credential Manager
|
|
794
|
-
const psCommand = `
|
|
795
|
-
$cred = Get-StoredCredential -Target "${entry.service}" -ErrorAction SilentlyContinue
|
|
796
|
-
if ($cred) {
|
|
797
|
-
[System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
|
|
798
|
-
[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($cred.Password)
|
|
799
|
-
)
|
|
800
|
-
}
|
|
801
|
-
`;
|
|
802
|
-
|
|
803
|
-
const output = execSync(`powershell -Command "${psCommand.replace(/\n/g, ' ')}"`, {
|
|
804
|
-
encoding: 'utf8',
|
|
805
|
-
timeout: 10000,
|
|
806
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
807
|
-
}).trim();
|
|
808
|
-
|
|
809
|
-
if (output) {
|
|
810
|
-
results.push(...parseCredentialJson(output, entry));
|
|
811
|
-
}
|
|
812
|
-
} catch {
|
|
813
|
-
// Try cmdkey as fallback (less reliable for getting password)
|
|
814
|
-
try {
|
|
815
|
-
// cmdkey can list but not retrieve passwords directly
|
|
816
|
-
// VS Code on Windows often uses DPAPI-encrypted files instead
|
|
817
|
-
} catch {
|
|
818
|
-
// Entry not found
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// Also check VS Code's DPAPI-encrypted storage on Windows
|
|
824
|
-
const vscodeCredPath = path.join(
|
|
825
|
-
process.env.APPDATA || '',
|
|
826
|
-
'Code',
|
|
827
|
-
'User',
|
|
828
|
-
'globalStorage',
|
|
829
|
-
'state.vscdb'
|
|
830
|
-
);
|
|
831
|
-
|
|
832
|
-
if (pathExists(vscodeCredPath)) {
|
|
833
|
-
const dbResults = readVSCodeStateDb(vscodeCredPath);
|
|
834
|
-
results.push(...dbResults);
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
return results;
|
|
838
|
-
};
|
|
839
|
-
|
|
840
|
-
/**
|
|
841
|
-
* Read tokens from OS secure storage (unified function)
|
|
842
|
-
*/
|
|
843
|
-
const readSecureStorage = () => {
|
|
844
|
-
switch (platform) {
|
|
845
|
-
case 'darwin':
|
|
846
|
-
return readMacOSKeychain();
|
|
847
|
-
case 'linux':
|
|
848
|
-
return readLinuxSecretService();
|
|
849
|
-
case 'win32':
|
|
850
|
-
return readWindowsCredentialManager();
|
|
851
|
-
default:
|
|
852
|
-
return [];
|
|
853
|
-
}
|
|
854
|
-
};
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* Try to read VS Code SQLite state database
|
|
858
|
-
*/
|
|
859
|
-
const readVSCodeStateDb = (dbPath) => {
|
|
860
|
-
const results = [];
|
|
861
|
-
|
|
862
|
-
try {
|
|
863
|
-
// Try using sqlite3 CLI if available
|
|
864
|
-
const { execSync } = require('child_process');
|
|
865
|
-
|
|
866
|
-
// Check if sqlite3 is available
|
|
867
|
-
try {
|
|
868
|
-
execSync('which sqlite3', { stdio: 'pipe' });
|
|
869
|
-
} catch {
|
|
870
|
-
return results; // sqlite3 not available
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
// Query for keys that might contain tokens
|
|
874
|
-
const queries = [
|
|
875
|
-
"SELECT key, value FROM ItemTable WHERE key LIKE '%apiKey%' OR key LIKE '%token%' OR key LIKE '%credential%' OR key LIKE '%session%'",
|
|
876
|
-
"SELECT key, value FROM ItemTable WHERE key LIKE '%anthropic%' OR key LIKE '%openai%' OR key LIKE '%claude%'"
|
|
877
|
-
];
|
|
878
|
-
|
|
879
|
-
for (const query of queries) {
|
|
880
|
-
try {
|
|
881
|
-
const output = execSync(`sqlite3 "${dbPath}" "${query}"`, {
|
|
882
|
-
encoding: 'utf8',
|
|
883
|
-
timeout: 5000,
|
|
884
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
885
|
-
});
|
|
886
|
-
|
|
887
|
-
if (output) {
|
|
888
|
-
const lines = output.trim().split('\n');
|
|
889
|
-
for (const line of lines) {
|
|
890
|
-
const [key, value] = line.split('|');
|
|
891
|
-
if (value && value.length > 20) {
|
|
892
|
-
// Try to identify provider
|
|
893
|
-
for (const [providerId, provider] of Object.entries(PROVIDER_PATTERNS)) {
|
|
894
|
-
for (const pattern of provider.keyPatterns) {
|
|
895
|
-
pattern.lastIndex = 0;
|
|
896
|
-
if (pattern.test(value)) {
|
|
897
|
-
results.push({
|
|
898
|
-
type: 'api_key',
|
|
899
|
-
provider: providerId,
|
|
900
|
-
providerName: provider.displayName,
|
|
901
|
-
token: value,
|
|
902
|
-
keyPath: key
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
} catch {
|
|
911
|
-
// Query failed, continue
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
} catch {
|
|
915
|
-
// SQLite read failed
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
return results;
|
|
919
|
-
};
|
|
920
|
-
|
|
921
|
-
/**
|
|
922
|
-
* List files in directory (recursive optional)
|
|
923
|
-
*/
|
|
924
|
-
const listFiles = (dir, recursive = false, maxDepth = 3, currentDepth = 0) => {
|
|
925
|
-
if (!pathExists(dir) || currentDepth > maxDepth) return [];
|
|
926
|
-
|
|
927
|
-
try {
|
|
928
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
929
|
-
let files = [];
|
|
930
|
-
|
|
931
|
-
for (const entry of entries) {
|
|
932
|
-
const fullPath = path.join(dir, entry.name);
|
|
933
|
-
|
|
934
|
-
// Skip hidden system directories
|
|
935
|
-
if (entry.name.startsWith('.') && entry.isDirectory() &&
|
|
936
|
-
!['config', '.continue', '.claude', '.opencode', '.cursor', '.zed', '.aider'].some(n => entry.name.includes(n))) {
|
|
937
|
-
continue;
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
if (entry.isFile()) {
|
|
941
|
-
files.push(fullPath);
|
|
942
|
-
} else if (entry.isDirectory() && recursive) {
|
|
943
|
-
files = files.concat(listFiles(fullPath, recursive, maxDepth, currentDepth + 1));
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
return files;
|
|
948
|
-
} catch {
|
|
949
|
-
return [];
|
|
950
|
-
}
|
|
951
|
-
};
|
|
952
|
-
|
|
953
|
-
/**
|
|
954
|
-
* Validate token format
|
|
955
|
-
*/
|
|
956
|
-
const validateToken = (token, providerId) => {
|
|
957
|
-
if (!token || token.length < 10) return false;
|
|
958
|
-
|
|
959
|
-
// Check against known patterns
|
|
960
|
-
const provider = PROVIDER_PATTERNS[providerId];
|
|
961
|
-
if (!provider) return true; // Accept if no pattern defined
|
|
962
|
-
|
|
963
|
-
for (const pattern of provider.keyPatterns) {
|
|
964
|
-
// Reset regex state
|
|
965
|
-
pattern.lastIndex = 0;
|
|
966
|
-
if (pattern.test(token)) return true;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
if (provider.sessionPatterns) {
|
|
970
|
-
for (const pattern of provider.sessionPatterns) {
|
|
971
|
-
pattern.lastIndex = 0;
|
|
972
|
-
if (pattern.test(token)) return true;
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
// Generic validation for session tokens
|
|
977
|
-
if (token.length > 20 && /^[a-zA-Z0-9_-]+$/.test(token)) {
|
|
978
|
-
return true;
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
return false;
|
|
982
|
-
};
|
|
983
|
-
|
|
984
|
-
// ==================== SCANNING FUNCTIONS ====================
|
|
985
|
-
|
|
986
|
-
/**
|
|
987
|
-
* Scan environment variables for API keys
|
|
988
|
-
*/
|
|
989
|
-
const scanEnvironmentVariables = () => {
|
|
990
|
-
const results = [];
|
|
991
|
-
|
|
992
|
-
for (const [providerId, provider] of Object.entries(PROVIDER_PATTERNS)) {
|
|
993
|
-
const envKey = provider.envKey;
|
|
994
|
-
if (envKey && process.env[envKey]) {
|
|
995
|
-
const token = process.env[envKey];
|
|
996
|
-
if (validateToken(token, providerId)) {
|
|
997
|
-
results.push({
|
|
998
|
-
source: 'ENVIRONMENT',
|
|
999
|
-
sourceId: 'envVars',
|
|
1000
|
-
icon: '🌍',
|
|
1001
|
-
type: 'api_key',
|
|
1002
|
-
provider: providerId,
|
|
1003
|
-
providerName: provider.displayName,
|
|
1004
|
-
token: token,
|
|
1005
|
-
filePath: `$${envKey}`,
|
|
1006
|
-
lastUsed: new Date() // Env vars are "current"
|
|
1007
|
-
});
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
// Also check generic key names
|
|
1013
|
-
const genericEnvKeys = ['AI_API_KEY', 'LLM_API_KEY', 'API_KEY'];
|
|
1014
|
-
for (const key of genericEnvKeys) {
|
|
1015
|
-
if (process.env[key]) {
|
|
1016
|
-
// Try to identify the provider
|
|
1017
|
-
const token = process.env[key];
|
|
1018
|
-
for (const [providerId, provider] of Object.entries(PROVIDER_PATTERNS)) {
|
|
1019
|
-
for (const pattern of provider.keyPatterns) {
|
|
1020
|
-
pattern.lastIndex = 0;
|
|
1021
|
-
if (pattern.test(token)) {
|
|
1022
|
-
results.push({
|
|
1023
|
-
source: 'ENVIRONMENT',
|
|
1024
|
-
sourceId: 'envVars',
|
|
1025
|
-
icon: '🌍',
|
|
1026
|
-
type: 'api_key',
|
|
1027
|
-
provider: providerId,
|
|
1028
|
-
providerName: provider.displayName,
|
|
1029
|
-
token: token,
|
|
1030
|
-
filePath: `$${key}`,
|
|
1031
|
-
lastUsed: new Date()
|
|
1032
|
-
});
|
|
1033
|
-
break;
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
return results;
|
|
1041
|
-
};
|
|
1042
|
-
|
|
1043
|
-
/**
|
|
1044
|
-
* Search for tokens in a content string
|
|
1045
|
-
*/
|
|
1046
|
-
const searchTokensInContent = (content, filePath = null) => {
|
|
1047
|
-
const results = [];
|
|
1048
|
-
|
|
1049
|
-
for (const [providerId, provider] of Object.entries(PROVIDER_PATTERNS)) {
|
|
1050
|
-
// Search for API keys
|
|
1051
|
-
for (const pattern of provider.keyPatterns) {
|
|
1052
|
-
pattern.lastIndex = 0;
|
|
1053
|
-
let match;
|
|
1054
|
-
while ((match = pattern.exec(content)) !== null) {
|
|
1055
|
-
const token = match[0];
|
|
1056
|
-
if (token.length > 10 && validateToken(token, providerId)) {
|
|
1057
|
-
results.push({
|
|
1058
|
-
type: 'api_key',
|
|
1059
|
-
provider: providerId,
|
|
1060
|
-
providerName: provider.displayName,
|
|
1061
|
-
token: token
|
|
1062
|
-
});
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
// Search for session tokens
|
|
1068
|
-
if (provider.sessionPatterns) {
|
|
1069
|
-
for (const pattern of provider.sessionPatterns) {
|
|
1070
|
-
pattern.lastIndex = 0;
|
|
1071
|
-
let match;
|
|
1072
|
-
while ((match = pattern.exec(content)) !== null) {
|
|
1073
|
-
const token = match[1] || match[0];
|
|
1074
|
-
if (token.length > 10) {
|
|
1075
|
-
results.push({
|
|
1076
|
-
type: 'session',
|
|
1077
|
-
provider: providerId,
|
|
1078
|
-
providerName: provider.displayName,
|
|
1079
|
-
token: token
|
|
1080
|
-
});
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
return results;
|
|
1088
|
-
};
|
|
1089
|
-
|
|
1090
|
-
/**
|
|
1091
|
-
* Parse JSON config file and extract tokens
|
|
1092
|
-
*/
|
|
1093
|
-
const parseJsonConfig = (content, filePath = null) => {
|
|
1094
|
-
const results = [];
|
|
1095
|
-
|
|
1096
|
-
try {
|
|
1097
|
-
const json = JSON.parse(content);
|
|
1098
|
-
|
|
1099
|
-
const extractFromObject = (obj, prefix = '') => {
|
|
1100
|
-
if (!obj || typeof obj !== 'object') return;
|
|
1101
|
-
|
|
1102
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
1103
|
-
const lowerKey = key.toLowerCase();
|
|
1104
|
-
|
|
1105
|
-
if (typeof value === 'string' && value.length > 10) {
|
|
1106
|
-
// Check if key looks like a credential key
|
|
1107
|
-
const isCredKey = [
|
|
1108
|
-
'apikey', 'api_key', 'api-key',
|
|
1109
|
-
'sessionkey', 'session_key', 'session-key',
|
|
1110
|
-
'accesstoken', 'access_token', 'access-token',
|
|
1111
|
-
'token', 'secret', 'credential', 'auth',
|
|
1112
|
-
'anthropic', 'openai', 'claude', 'gpt'
|
|
1113
|
-
].some(k => lowerKey.includes(k));
|
|
1114
|
-
|
|
1115
|
-
if (isCredKey) {
|
|
1116
|
-
// Identify provider from token format
|
|
1117
|
-
for (const [providerId, provider] of Object.entries(PROVIDER_PATTERNS)) {
|
|
1118
|
-
for (const pattern of provider.keyPatterns) {
|
|
1119
|
-
pattern.lastIndex = 0;
|
|
1120
|
-
if (pattern.test(value)) {
|
|
1121
|
-
results.push({
|
|
1122
|
-
type: 'api_key',
|
|
1123
|
-
provider: providerId,
|
|
1124
|
-
providerName: provider.displayName,
|
|
1125
|
-
token: value,
|
|
1126
|
-
keyPath: prefix + key
|
|
1127
|
-
});
|
|
1128
|
-
break;
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
if (provider.sessionPatterns) {
|
|
1133
|
-
for (const pattern of provider.sessionPatterns) {
|
|
1134
|
-
pattern.lastIndex = 0;
|
|
1135
|
-
if (pattern.test(value) || pattern.test(`"${key}":"${value}"`)) {
|
|
1136
|
-
results.push({
|
|
1137
|
-
type: 'session',
|
|
1138
|
-
provider: providerId,
|
|
1139
|
-
providerName: provider.displayName,
|
|
1140
|
-
token: value,
|
|
1141
|
-
keyPath: prefix + key
|
|
1142
|
-
});
|
|
1143
|
-
break;
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
} else if (typeof value === 'object' && value !== null) {
|
|
1150
|
-
extractFromObject(value, prefix + key + '.');
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
};
|
|
1154
|
-
|
|
1155
|
-
extractFromObject(json);
|
|
1156
|
-
} catch {
|
|
1157
|
-
// Not valid JSON, use regex search
|
|
1158
|
-
return searchTokensInContent(content, filePath);
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
return results;
|
|
1162
|
-
};
|
|
1163
|
-
|
|
1164
|
-
/**
|
|
1165
|
-
* Scan a single source for tokens
|
|
1166
|
-
*/
|
|
1167
|
-
const scanSource = (sourceId) => {
|
|
1168
|
-
const source = TOKEN_SOURCES[sourceId];
|
|
1169
|
-
if (!source) return [];
|
|
1170
|
-
|
|
1171
|
-
const results = [];
|
|
1172
|
-
const paths = source.paths?.[platform] || [];
|
|
1173
|
-
|
|
1174
|
-
// Scan each path
|
|
1175
|
-
for (const basePath of paths) {
|
|
1176
|
-
if (!pathExists(basePath)) continue;
|
|
1177
|
-
|
|
1178
|
-
// Scan extension directories (for VS Code-based editors)
|
|
1179
|
-
if (source.extensions) {
|
|
1180
|
-
for (const [providerHint, extIds] of Object.entries(source.extensions)) {
|
|
1181
|
-
const extIdList = Array.isArray(extIds) ? extIds : [extIds];
|
|
1182
|
-
|
|
1183
|
-
for (const extId of extIdList) {
|
|
1184
|
-
const extPath = path.join(basePath, extId);
|
|
1185
|
-
if (!pathExists(extPath)) continue;
|
|
1186
|
-
|
|
1187
|
-
// Scan all files in extension directory
|
|
1188
|
-
const files = listFiles(extPath, true, 2);
|
|
1189
|
-
for (const filePath of files) {
|
|
1190
|
-
const content = readFileSafe(filePath);
|
|
1191
|
-
if (!content) continue;
|
|
1192
|
-
|
|
1193
|
-
const tokens = filePath.endsWith('.json')
|
|
1194
|
-
? parseJsonConfig(content, filePath)
|
|
1195
|
-
: searchTokensInContent(content, filePath);
|
|
1196
|
-
|
|
1197
|
-
for (const token of tokens) {
|
|
1198
|
-
results.push({
|
|
1199
|
-
source: source.name,
|
|
1200
|
-
sourceId: sourceId,
|
|
1201
|
-
icon: source.icon || '📁',
|
|
1202
|
-
...token,
|
|
1203
|
-
filePath: filePath,
|
|
1204
|
-
lastUsed: getFileModTime(filePath)
|
|
1205
|
-
});
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
// Scan config files
|
|
1213
|
-
if (source.configFiles) {
|
|
1214
|
-
for (const file of source.configFiles) {
|
|
1215
|
-
// Handle wildcards
|
|
1216
|
-
if (file.includes('*')) {
|
|
1217
|
-
const files = listFiles(basePath, false);
|
|
1218
|
-
const regex = new RegExp('^' + file.replace(/\*/g, '.*') + '$');
|
|
1219
|
-
for (const f of files) {
|
|
1220
|
-
if (regex.test(path.basename(f))) {
|
|
1221
|
-
const content = readFileSafe(f);
|
|
1222
|
-
if (content) {
|
|
1223
|
-
const tokens = f.endsWith('.json')
|
|
1224
|
-
? parseJsonConfig(content, f)
|
|
1225
|
-
: searchTokensInContent(content, f);
|
|
1226
|
-
|
|
1227
|
-
for (const token of tokens) {
|
|
1228
|
-
results.push({
|
|
1229
|
-
source: source.name,
|
|
1230
|
-
sourceId: sourceId,
|
|
1231
|
-
icon: source.icon || '📁',
|
|
1232
|
-
...token,
|
|
1233
|
-
filePath: f,
|
|
1234
|
-
lastUsed: getFileModTime(f)
|
|
1235
|
-
});
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
} else {
|
|
1241
|
-
const filePath = path.join(basePath, file);
|
|
1242
|
-
const content = readFileSafe(filePath);
|
|
1243
|
-
if (content) {
|
|
1244
|
-
const tokens = filePath.endsWith('.json')
|
|
1245
|
-
? parseJsonConfig(content, filePath)
|
|
1246
|
-
: searchTokensInContent(content, filePath);
|
|
1247
|
-
|
|
1248
|
-
for (const token of tokens) {
|
|
1249
|
-
results.push({
|
|
1250
|
-
source: source.name,
|
|
1251
|
-
sourceId: sourceId,
|
|
1252
|
-
icon: source.icon || '📁',
|
|
1253
|
-
...token,
|
|
1254
|
-
filePath: filePath,
|
|
1255
|
-
lastUsed: getFileModTime(filePath)
|
|
1256
|
-
});
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
return results;
|
|
1265
|
-
};
|
|
1266
|
-
|
|
1267
|
-
/**
|
|
1268
|
-
* Scan all sources for tokens
|
|
1269
|
-
*/
|
|
1270
|
-
const scanAllSources = () => {
|
|
1271
|
-
const allResults = [];
|
|
1272
|
-
|
|
1273
|
-
// First, scan OS secure storage (Keychain, libsecret, Credential Manager)
|
|
1274
|
-
// This is the most reliable source for IDE tokens
|
|
1275
|
-
try {
|
|
1276
|
-
allResults.push(...readSecureStorage());
|
|
1277
|
-
} catch (err) {
|
|
1278
|
-
// Silent fail
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
// Then scan environment variables
|
|
1282
|
-
allResults.push(...scanEnvironmentVariables());
|
|
1283
|
-
|
|
1284
|
-
// Then scan all tool sources (config files)
|
|
1285
|
-
for (const sourceId of Object.keys(TOKEN_SOURCES)) {
|
|
1286
|
-
if (sourceId === 'envVars') continue; // Already scanned
|
|
1287
|
-
|
|
1288
|
-
try {
|
|
1289
|
-
const results = scanSource(sourceId);
|
|
1290
|
-
allResults.push(...results);
|
|
1291
|
-
} catch (err) {
|
|
1292
|
-
// Silent fail for individual sources
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
// Remove duplicates (same token from multiple sources)
|
|
1297
|
-
const uniqueTokens = new Map();
|
|
1298
|
-
for (const result of allResults) {
|
|
1299
|
-
if (result.token) {
|
|
1300
|
-
const key = `${result.provider}:${result.token}`;
|
|
1301
|
-
if (!uniqueTokens.has(key)) {
|
|
1302
|
-
uniqueTokens.set(key, result);
|
|
1303
|
-
} else {
|
|
1304
|
-
// Keep the more recent one
|
|
1305
|
-
const existing = uniqueTokens.get(key);
|
|
1306
|
-
if (result.lastUsed && (!existing.lastUsed || result.lastUsed > existing.lastUsed)) {
|
|
1307
|
-
uniqueTokens.set(key, result);
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
// Sort by last used (most recent first)
|
|
1314
|
-
return Array.from(uniqueTokens.values()).sort((a, b) => {
|
|
1315
|
-
if (!a.lastUsed) return 1;
|
|
1316
|
-
if (!b.lastUsed) return -1;
|
|
1317
|
-
return b.lastUsed - a.lastUsed;
|
|
1318
|
-
});
|
|
1319
|
-
};
|
|
1320
|
-
|
|
1321
|
-
/**
|
|
1322
|
-
* Scan for a specific provider's tokens
|
|
1323
|
-
*/
|
|
1324
|
-
const scanForProvider = (providerId) => {
|
|
1325
|
-
const allTokens = scanAllSources();
|
|
1326
|
-
return allTokens.filter(t => t.provider === providerId);
|
|
1327
|
-
};
|
|
1328
|
-
|
|
1329
|
-
/**
|
|
1330
|
-
* Get human-readable time ago
|
|
1331
|
-
*/
|
|
1332
|
-
const timeAgo = (date) => {
|
|
1333
|
-
if (!date) return 'UNKNOWN';
|
|
1334
|
-
|
|
1335
|
-
const seconds = Math.floor((new Date() - date) / 1000);
|
|
1336
|
-
|
|
1337
|
-
if (seconds < 60) return 'JUST NOW';
|
|
1338
|
-
if (seconds < 3600) return `${Math.floor(seconds / 60)} MIN AGO`;
|
|
1339
|
-
if (seconds < 86400) return `${Math.floor(seconds / 3600)} HOURS AGO`;
|
|
1340
|
-
if (seconds < 604800) return `${Math.floor(seconds / 86400)} DAYS AGO`;
|
|
1341
|
-
if (seconds < 2592000) return `${Math.floor(seconds / 604800)} WEEKS AGO`;
|
|
1342
|
-
return `${Math.floor(seconds / 2592000)} MONTHS AGO`;
|
|
1343
|
-
};
|
|
1344
|
-
|
|
1345
|
-
/**
|
|
1346
|
-
* Format scan results for display
|
|
1347
|
-
*/
|
|
1348
|
-
const formatResults = (results) => {
|
|
1349
|
-
return results.map((r, i) => ({
|
|
1350
|
-
index: i + 1,
|
|
1351
|
-
source: r.source,
|
|
1352
|
-
icon: r.icon || '📁',
|
|
1353
|
-
provider: r.providerName || PROVIDER_PATTERNS[r.provider]?.displayName || r.provider.toUpperCase(),
|
|
1354
|
-
type: r.type === 'session' ? 'SESSION' : 'API KEY',
|
|
1355
|
-
lastUsed: timeAgo(r.lastUsed),
|
|
1356
|
-
tokenPreview: r.token ? `${r.token.substring(0, 10)}...${r.token.substring(r.token.length - 4)}` : 'N/A'
|
|
1357
|
-
}));
|
|
1358
|
-
};
|
|
1359
|
-
|
|
1360
|
-
/**
|
|
1361
|
-
* Quick check if any tokens exist (fast scan)
|
|
1362
|
-
*/
|
|
1363
|
-
const hasExistingTokens = () => {
|
|
1364
|
-
// Quick check environment variables first
|
|
1365
|
-
for (const provider of Object.values(PROVIDER_PATTERNS)) {
|
|
1366
|
-
if (provider.envKey && process.env[provider.envKey]) {
|
|
1367
|
-
return true;
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
// Quick check common locations
|
|
1372
|
-
const quickPaths = [
|
|
1373
|
-
path.join(homeDir, '.claude'),
|
|
1374
|
-
path.join(homeDir, '.opencode'),
|
|
1375
|
-
path.join(homeDir, '.continue')
|
|
1376
|
-
];
|
|
1377
|
-
|
|
1378
|
-
for (const p of quickPaths) {
|
|
1379
|
-
if (pathExists(p)) return true;
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
return false;
|
|
1383
|
-
};
|
|
1384
|
-
|
|
1385
|
-
/**
|
|
1386
|
-
* Get system info for debugging
|
|
1387
|
-
*/
|
|
1388
|
-
const getSystemInfo = () => {
|
|
1389
|
-
return {
|
|
1390
|
-
platform,
|
|
1391
|
-
homeDir,
|
|
1392
|
-
appDataDir: getAppDataDir(),
|
|
1393
|
-
isHeadless: isHeadlessServer(),
|
|
1394
|
-
nodeVersion: process.version,
|
|
1395
|
-
arch: os.arch()
|
|
1396
|
-
};
|
|
1397
|
-
};
|
|
1398
|
-
|
|
1399
|
-
module.exports = {
|
|
1400
|
-
TOKEN_SOURCES,
|
|
1401
|
-
PROVIDER_PATTERNS,
|
|
1402
|
-
CREDENTIAL_ENTRIES,
|
|
1403
|
-
scanAllSources,
|
|
1404
|
-
scanForProvider,
|
|
1405
|
-
scanSource,
|
|
1406
|
-
scanEnvironmentVariables,
|
|
1407
|
-
readSecureStorage,
|
|
1408
|
-
formatResults,
|
|
1409
|
-
timeAgo,
|
|
1410
|
-
hasExistingTokens,
|
|
1411
|
-
isHeadlessServer,
|
|
1412
|
-
getSystemInfo,
|
|
1413
|
-
validateToken
|
|
1414
|
-
};
|