hedgequantx 2.5.6 → 2.5.8
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/package.json +1 -1
- package/src/menus/ai-agent.js +3 -3
- package/src/menus/dashboard.js +26 -12
- package/src/services/ai/token-scanner.js +415 -6
- package/src/ui/box.js +12 -1
- package/src/ui/index.js +2 -0
package/package.json
CHANGED
package/src/menus/ai-agent.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
const chalk = require('chalk');
|
|
7
7
|
const ora = require('ora');
|
|
8
8
|
|
|
9
|
-
const { getLogoWidth, drawBoxHeader, drawBoxFooter, displayBanner } = require('../ui');
|
|
9
|
+
const { getLogoWidth, drawBoxHeader, drawBoxHeaderContinue, drawBoxFooter, displayBanner } = require('../ui');
|
|
10
10
|
const { prompts } = require('../utils');
|
|
11
11
|
const aiService = require('../services/ai');
|
|
12
12
|
const { getCategories, getProvidersByCategory } = require('../services/ai/providers');
|
|
@@ -31,7 +31,7 @@ const aiAgentMenu = async () => {
|
|
|
31
31
|
|
|
32
32
|
console.clear();
|
|
33
33
|
displayBanner();
|
|
34
|
-
|
|
34
|
+
drawBoxHeaderContinue('AI AGENT', boxWidth);
|
|
35
35
|
|
|
36
36
|
// Show current status
|
|
37
37
|
const connection = aiService.getConnection();
|
|
@@ -104,7 +104,7 @@ const showExistingTokens = async () => {
|
|
|
104
104
|
|
|
105
105
|
console.clear();
|
|
106
106
|
displayBanner();
|
|
107
|
-
|
|
107
|
+
drawBoxHeaderContinue('SCANNING FOR EXISTING SESSIONS...', boxWidth);
|
|
108
108
|
console.log(makeLine(''));
|
|
109
109
|
console.log(makeLine(chalk.gray('CHECKING VS CODE, CURSOR, CLAUDE CLI, OPENCODE...')));
|
|
110
110
|
console.log(makeLine(''));
|
package/src/menus/dashboard.js
CHANGED
|
@@ -80,20 +80,34 @@ const dashboardMenu = async (service) => {
|
|
|
80
80
|
|
|
81
81
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
82
82
|
|
|
83
|
-
// Menu in
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
83
|
+
// Menu in 3 columns
|
|
84
|
+
const colWidth = Math.floor(W / 3);
|
|
85
|
+
|
|
86
|
+
const menuRow3 = (col1, col2, col3) => {
|
|
87
|
+
const c1Plain = col1.replace(/\x1b\[[0-9;]*m/g, '');
|
|
88
|
+
const c2Plain = col2.replace(/\x1b\[[0-9;]*m/g, '');
|
|
89
|
+
const c3Plain = col3.replace(/\x1b\[[0-9;]*m/g, '');
|
|
90
|
+
|
|
91
|
+
const c1Padded = ' ' + col1 + ' '.repeat(Math.max(0, colWidth - c1Plain.length - 2));
|
|
92
|
+
const c2Padded = col2 + ' '.repeat(Math.max(0, colWidth - c2Plain.length));
|
|
93
|
+
const c3Padded = col3 + ' '.repeat(Math.max(0, W - colWidth * 2 - c3Plain.length));
|
|
94
|
+
|
|
95
|
+
console.log(chalk.cyan('║') + c1Padded + c2Padded + c3Padded + chalk.cyan('║'));
|
|
91
96
|
};
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
const centerLine = (content) => {
|
|
99
|
+
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
100
|
+
const padding = W - plainLen;
|
|
101
|
+
const leftPad = Math.floor(padding / 2);
|
|
102
|
+
console.log(chalk.cyan('║') + ' '.repeat(leftPad) + content + ' '.repeat(padding - leftPad) + chalk.cyan('║'));
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
menuRow3(chalk.cyan('[1] VIEW ACCOUNTS'), chalk.cyan('[2] VIEW STATS'), chalk.cyan('[+] ADD ACCOUNT'));
|
|
106
|
+
menuRow3(chalk.magenta('[A] ALGO TRADING'), chalk.magenta('[I] AI AGENT'), chalk.yellow('[U] UPDATE HQX'));
|
|
107
|
+
|
|
108
|
+
// Separator and disconnect button centered
|
|
109
|
+
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
110
|
+
centerLine(chalk.red('[X] DISCONNECT'));
|
|
97
111
|
|
|
98
112
|
console.log(chalk.cyan('╚' + '═'.repeat(W) + '╝'));
|
|
99
113
|
|
|
@@ -214,11 +214,33 @@ const TOKEN_SOURCES = {
|
|
|
214
214
|
name: 'CLAUDE CLI',
|
|
215
215
|
icon: '🤖',
|
|
216
216
|
paths: {
|
|
217
|
-
darwin: [
|
|
218
|
-
|
|
219
|
-
|
|
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
|
+
]
|
|
220
230
|
},
|
|
221
|
-
configFiles: ['.credentials.json', 'credentials.json', 'config.json', '.credentials', 'settings.json', 'settings.local.json', 'auth.json']
|
|
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']
|
|
222
244
|
},
|
|
223
245
|
|
|
224
246
|
opencode: {
|
|
@@ -496,6 +518,383 @@ const getFileModTime = (filePath) => {
|
|
|
496
518
|
}
|
|
497
519
|
};
|
|
498
520
|
|
|
521
|
+
/**
|
|
522
|
+
* Known credential entries for AI providers (used by all OS)
|
|
523
|
+
* Organized by IDE/App
|
|
524
|
+
*/
|
|
525
|
+
const CREDENTIAL_ENTRIES = [
|
|
526
|
+
// VS Code
|
|
527
|
+
{ service: 'Claude Code-credentials', provider: 'anthropic', name: 'CLAUDE CODE (VS CODE)', ide: 'vscode' },
|
|
528
|
+
{ service: 'vscode.anthropic-credentials', provider: 'anthropic', name: 'VS CODE ANTHROPIC', ide: 'vscode' },
|
|
529
|
+
{ service: 'vscode-openai', provider: 'openai', name: 'VS CODE OPENAI', ide: 'vscode' },
|
|
530
|
+
{ service: 'copilot-credentials', provider: 'openai', name: 'GITHUB COPILOT', ide: 'vscode' },
|
|
531
|
+
|
|
532
|
+
// VS Code Insiders
|
|
533
|
+
{ service: 'Claude Code-credentials-insiders', provider: 'anthropic', name: 'CLAUDE CODE (INSIDERS)', ide: 'vscode-insiders' },
|
|
534
|
+
|
|
535
|
+
// Cursor
|
|
536
|
+
{ service: 'Cursor-credentials', provider: 'anthropic', name: 'CURSOR (CLAUDE)', ide: 'cursor' },
|
|
537
|
+
{ service: 'cursor.anthropic-credentials', provider: 'anthropic', name: 'CURSOR ANTHROPIC', ide: 'cursor' },
|
|
538
|
+
{ service: 'cursor.openai-credentials', provider: 'openai', name: 'CURSOR OPENAI', ide: 'cursor' },
|
|
539
|
+
|
|
540
|
+
// Windsurf
|
|
541
|
+
{ service: 'Windsurf-credentials', provider: 'anthropic', name: 'WINDSURF (CLAUDE)', ide: 'windsurf' },
|
|
542
|
+
{ service: 'windsurf.anthropic-credentials', provider: 'anthropic', name: 'WINDSURF ANTHROPIC', ide: 'windsurf' },
|
|
543
|
+
|
|
544
|
+
// Zed
|
|
545
|
+
{ service: 'Zed-credentials', provider: 'anthropic', name: 'ZED (CLAUDE)', ide: 'zed' },
|
|
546
|
+
{ service: 'zed.anthropic-credentials', provider: 'anthropic', name: 'ZED ANTHROPIC', ide: 'zed' },
|
|
547
|
+
{ service: 'zed.openai-credentials', provider: 'openai', name: 'ZED OPENAI', ide: 'zed' },
|
|
548
|
+
|
|
549
|
+
// Claude CLI / App
|
|
550
|
+
{ service: 'Claude Safe Storage', provider: 'anthropic', name: 'CLAUDE CLI', ide: 'claude-cli' },
|
|
551
|
+
{ service: 'claude-cli-credentials', provider: 'anthropic', name: 'CLAUDE CLI', ide: 'claude-cli' },
|
|
552
|
+
|
|
553
|
+
// Continue.dev
|
|
554
|
+
{ service: 'Continue-credentials', provider: 'anthropic', name: 'CONTINUE.DEV', ide: 'continue' },
|
|
555
|
+
{ service: 'continue.anthropic-credentials', provider: 'anthropic', name: 'CONTINUE ANTHROPIC', ide: 'continue' },
|
|
556
|
+
{ service: 'continue.openai-credentials', provider: 'openai', name: 'CONTINUE OPENAI', ide: 'continue' },
|
|
557
|
+
|
|
558
|
+
// Cline
|
|
559
|
+
{ service: 'Cline-credentials', provider: 'anthropic', name: 'CLINE', ide: 'cline' },
|
|
560
|
+
{ service: 'saoudrizwan.claude-dev-credentials', provider: 'anthropic', name: 'CLINE (CLAUDE DEV)', ide: 'cline' },
|
|
561
|
+
|
|
562
|
+
// OpenCode
|
|
563
|
+
{ service: 'OpenCode-credentials', provider: 'anthropic', name: 'OPENCODE', ide: 'opencode' },
|
|
564
|
+
{ service: 'opencode.anthropic-credentials', provider: 'anthropic', name: 'OPENCODE ANTHROPIC', ide: 'opencode' },
|
|
565
|
+
|
|
566
|
+
// Aider
|
|
567
|
+
{ service: 'Aider-credentials', provider: 'anthropic', name: 'AIDER', ide: 'aider' },
|
|
568
|
+
{ service: 'aider.anthropic-credentials', provider: 'anthropic', name: 'AIDER ANTHROPIC', ide: 'aider' },
|
|
569
|
+
{ service: 'aider.openai-credentials', provider: 'openai', name: 'AIDER OPENAI', ide: 'aider' },
|
|
570
|
+
|
|
571
|
+
// Generic OpenAI
|
|
572
|
+
{ service: 'openai-credentials', provider: 'openai', name: 'OPENAI', ide: 'generic' },
|
|
573
|
+
|
|
574
|
+
// Generic OpenRouter
|
|
575
|
+
{ service: 'openrouter-credentials', provider: 'openrouter', name: 'OPENROUTER', ide: 'generic' },
|
|
576
|
+
];
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Parse credential JSON and extract tokens
|
|
580
|
+
*/
|
|
581
|
+
const parseCredentialJson = (output, entry) => {
|
|
582
|
+
const results = [];
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
const data = JSON.parse(output);
|
|
586
|
+
|
|
587
|
+
// Extract Claude OAuth access token
|
|
588
|
+
if (data.claudeAiOauth?.accessToken) {
|
|
589
|
+
results.push({
|
|
590
|
+
source: `SECURE STORAGE - ${entry.name}`,
|
|
591
|
+
sourceId: 'secureStorage',
|
|
592
|
+
icon: '🔐',
|
|
593
|
+
type: data.claudeAiOauth.subscriptionType === 'max' ? 'session' : 'api_key',
|
|
594
|
+
provider: entry.provider,
|
|
595
|
+
providerName: PROVIDER_PATTERNS[entry.provider]?.displayName || entry.provider.toUpperCase(),
|
|
596
|
+
token: data.claudeAiOauth.accessToken,
|
|
597
|
+
refreshToken: data.claudeAiOauth.refreshToken,
|
|
598
|
+
expiresAt: data.claudeAiOauth.expiresAt,
|
|
599
|
+
subscriptionType: data.claudeAiOauth.subscriptionType,
|
|
600
|
+
lastUsed: new Date()
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Extract OpenAI token
|
|
605
|
+
if (data.accessToken && entry.provider === 'openai') {
|
|
606
|
+
results.push({
|
|
607
|
+
source: `SECURE STORAGE - ${entry.name}`,
|
|
608
|
+
sourceId: 'secureStorage',
|
|
609
|
+
icon: '🔐',
|
|
610
|
+
type: 'session',
|
|
611
|
+
provider: entry.provider,
|
|
612
|
+
providerName: PROVIDER_PATTERNS[entry.provider]?.displayName || entry.provider.toUpperCase(),
|
|
613
|
+
token: data.accessToken,
|
|
614
|
+
lastUsed: new Date()
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Generic API key in JSON
|
|
619
|
+
if (data.apiKey) {
|
|
620
|
+
results.push({
|
|
621
|
+
source: `SECURE STORAGE - ${entry.name}`,
|
|
622
|
+
sourceId: 'secureStorage',
|
|
623
|
+
icon: '🔐',
|
|
624
|
+
type: 'api_key',
|
|
625
|
+
provider: entry.provider,
|
|
626
|
+
providerName: PROVIDER_PATTERNS[entry.provider]?.displayName || entry.provider.toUpperCase(),
|
|
627
|
+
token: data.apiKey,
|
|
628
|
+
lastUsed: new Date()
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
} catch {
|
|
632
|
+
// Not JSON, treat as raw token
|
|
633
|
+
if (output.length > 20) {
|
|
634
|
+
for (const [providerId, provider] of Object.entries(PROVIDER_PATTERNS)) {
|
|
635
|
+
for (const pattern of provider.keyPatterns) {
|
|
636
|
+
pattern.lastIndex = 0;
|
|
637
|
+
if (pattern.test(output)) {
|
|
638
|
+
results.push({
|
|
639
|
+
source: `SECURE STORAGE - ${entry.name}`,
|
|
640
|
+
sourceId: 'secureStorage',
|
|
641
|
+
icon: '🔐',
|
|
642
|
+
type: 'api_key',
|
|
643
|
+
provider: providerId,
|
|
644
|
+
providerName: provider.displayName,
|
|
645
|
+
token: output,
|
|
646
|
+
lastUsed: new Date()
|
|
647
|
+
});
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return results;
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Read tokens from macOS Keychain
|
|
660
|
+
*/
|
|
661
|
+
const readMacOSKeychain = () => {
|
|
662
|
+
if (platform !== 'darwin') return [];
|
|
663
|
+
|
|
664
|
+
const results = [];
|
|
665
|
+
const { execSync } = require('child_process');
|
|
666
|
+
|
|
667
|
+
for (const entry of CREDENTIAL_ENTRIES) {
|
|
668
|
+
try {
|
|
669
|
+
const output = execSync(
|
|
670
|
+
`security find-generic-password -s "${entry.service}" -w 2>/dev/null`,
|
|
671
|
+
{ encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
672
|
+
).trim();
|
|
673
|
+
|
|
674
|
+
if (output) {
|
|
675
|
+
results.push(...parseCredentialJson(output, entry));
|
|
676
|
+
}
|
|
677
|
+
} catch {
|
|
678
|
+
// Entry not found or access denied
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
return results;
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Read tokens from Linux Secret Service (libsecret/gnome-keyring)
|
|
687
|
+
*/
|
|
688
|
+
const readLinuxSecretService = () => {
|
|
689
|
+
if (platform !== 'linux') return [];
|
|
690
|
+
|
|
691
|
+
const results = [];
|
|
692
|
+
const { execSync } = require('child_process');
|
|
693
|
+
|
|
694
|
+
// Check if secret-tool is available
|
|
695
|
+
try {
|
|
696
|
+
execSync('which secret-tool', { stdio: 'pipe' });
|
|
697
|
+
} catch {
|
|
698
|
+
return results; // secret-tool not available
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
for (const entry of CREDENTIAL_ENTRIES) {
|
|
702
|
+
try {
|
|
703
|
+
// Try different attribute combinations
|
|
704
|
+
const commands = [
|
|
705
|
+
`secret-tool lookup service "${entry.service}" 2>/dev/null`,
|
|
706
|
+
`secret-tool lookup application "${entry.service}" 2>/dev/null`,
|
|
707
|
+
`secret-tool lookup xdg:schema "org.freedesktop.Secret.Generic" service "${entry.service}" 2>/dev/null`,
|
|
708
|
+
];
|
|
709
|
+
|
|
710
|
+
for (const cmd of commands) {
|
|
711
|
+
try {
|
|
712
|
+
const output = execSync(cmd, {
|
|
713
|
+
encoding: 'utf8',
|
|
714
|
+
timeout: 5000,
|
|
715
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
716
|
+
}).trim();
|
|
717
|
+
|
|
718
|
+
if (output) {
|
|
719
|
+
results.push(...parseCredentialJson(output, entry));
|
|
720
|
+
break; // Found it, no need to try other commands
|
|
721
|
+
}
|
|
722
|
+
} catch {
|
|
723
|
+
// Command failed, try next
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
} catch {
|
|
727
|
+
// Entry not found
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Also check VS Code's pass-based storage on Linux
|
|
732
|
+
const passEntries = [
|
|
733
|
+
'vscode/Claude Code-credentials',
|
|
734
|
+
'vscode/Cursor-credentials',
|
|
735
|
+
'vscode/openai-credentials',
|
|
736
|
+
];
|
|
737
|
+
|
|
738
|
+
for (const passPath of passEntries) {
|
|
739
|
+
try {
|
|
740
|
+
const output = execSync(`pass show "${passPath}" 2>/dev/null`, {
|
|
741
|
+
encoding: 'utf8',
|
|
742
|
+
timeout: 5000,
|
|
743
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
744
|
+
}).trim();
|
|
745
|
+
|
|
746
|
+
if (output) {
|
|
747
|
+
const entry = CREDENTIAL_ENTRIES.find(e => passPath.includes(e.service)) ||
|
|
748
|
+
{ service: passPath, provider: 'unknown', name: passPath };
|
|
749
|
+
results.push(...parseCredentialJson(output, entry));
|
|
750
|
+
}
|
|
751
|
+
} catch {
|
|
752
|
+
// pass entry not found
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
return results;
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Read tokens from Windows Credential Manager
|
|
761
|
+
*/
|
|
762
|
+
const readWindowsCredentialManager = () => {
|
|
763
|
+
if (platform !== 'win32') return [];
|
|
764
|
+
|
|
765
|
+
const results = [];
|
|
766
|
+
const { execSync } = require('child_process');
|
|
767
|
+
|
|
768
|
+
for (const entry of CREDENTIAL_ENTRIES) {
|
|
769
|
+
try {
|
|
770
|
+
// Use PowerShell to read from Credential Manager
|
|
771
|
+
const psCommand = `
|
|
772
|
+
$cred = Get-StoredCredential -Target "${entry.service}" -ErrorAction SilentlyContinue
|
|
773
|
+
if ($cred) {
|
|
774
|
+
[System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
|
|
775
|
+
[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($cred.Password)
|
|
776
|
+
)
|
|
777
|
+
}
|
|
778
|
+
`;
|
|
779
|
+
|
|
780
|
+
const output = execSync(`powershell -Command "${psCommand.replace(/\n/g, ' ')}"`, {
|
|
781
|
+
encoding: 'utf8',
|
|
782
|
+
timeout: 10000,
|
|
783
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
784
|
+
}).trim();
|
|
785
|
+
|
|
786
|
+
if (output) {
|
|
787
|
+
results.push(...parseCredentialJson(output, entry));
|
|
788
|
+
}
|
|
789
|
+
} catch {
|
|
790
|
+
// Try cmdkey as fallback (less reliable for getting password)
|
|
791
|
+
try {
|
|
792
|
+
// cmdkey can list but not retrieve passwords directly
|
|
793
|
+
// VS Code on Windows often uses DPAPI-encrypted files instead
|
|
794
|
+
} catch {
|
|
795
|
+
// Entry not found
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Also check VS Code's DPAPI-encrypted storage on Windows
|
|
801
|
+
const vscodeCredPath = path.join(
|
|
802
|
+
process.env.APPDATA || '',
|
|
803
|
+
'Code',
|
|
804
|
+
'User',
|
|
805
|
+
'globalStorage',
|
|
806
|
+
'state.vscdb'
|
|
807
|
+
);
|
|
808
|
+
|
|
809
|
+
if (pathExists(vscodeCredPath)) {
|
|
810
|
+
const dbResults = readVSCodeStateDb(vscodeCredPath);
|
|
811
|
+
results.push(...dbResults);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
return results;
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Read tokens from OS secure storage (unified function)
|
|
819
|
+
*/
|
|
820
|
+
const readSecureStorage = () => {
|
|
821
|
+
switch (platform) {
|
|
822
|
+
case 'darwin':
|
|
823
|
+
return readMacOSKeychain();
|
|
824
|
+
case 'linux':
|
|
825
|
+
return readLinuxSecretService();
|
|
826
|
+
case 'win32':
|
|
827
|
+
return readWindowsCredentialManager();
|
|
828
|
+
default:
|
|
829
|
+
return [];
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Try to read VS Code SQLite state database
|
|
835
|
+
*/
|
|
836
|
+
const readVSCodeStateDb = (dbPath) => {
|
|
837
|
+
const results = [];
|
|
838
|
+
|
|
839
|
+
try {
|
|
840
|
+
// Try using sqlite3 CLI if available
|
|
841
|
+
const { execSync } = require('child_process');
|
|
842
|
+
|
|
843
|
+
// Check if sqlite3 is available
|
|
844
|
+
try {
|
|
845
|
+
execSync('which sqlite3', { stdio: 'pipe' });
|
|
846
|
+
} catch {
|
|
847
|
+
return results; // sqlite3 not available
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Query for keys that might contain tokens
|
|
851
|
+
const queries = [
|
|
852
|
+
"SELECT key, value FROM ItemTable WHERE key LIKE '%apiKey%' OR key LIKE '%token%' OR key LIKE '%credential%' OR key LIKE '%session%'",
|
|
853
|
+
"SELECT key, value FROM ItemTable WHERE key LIKE '%anthropic%' OR key LIKE '%openai%' OR key LIKE '%claude%'"
|
|
854
|
+
];
|
|
855
|
+
|
|
856
|
+
for (const query of queries) {
|
|
857
|
+
try {
|
|
858
|
+
const output = execSync(`sqlite3 "${dbPath}" "${query}"`, {
|
|
859
|
+
encoding: 'utf8',
|
|
860
|
+
timeout: 5000,
|
|
861
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
if (output) {
|
|
865
|
+
const lines = output.trim().split('\n');
|
|
866
|
+
for (const line of lines) {
|
|
867
|
+
const [key, value] = line.split('|');
|
|
868
|
+
if (value && value.length > 20) {
|
|
869
|
+
// Try to identify provider
|
|
870
|
+
for (const [providerId, provider] of Object.entries(PROVIDER_PATTERNS)) {
|
|
871
|
+
for (const pattern of provider.keyPatterns) {
|
|
872
|
+
pattern.lastIndex = 0;
|
|
873
|
+
if (pattern.test(value)) {
|
|
874
|
+
results.push({
|
|
875
|
+
type: 'api_key',
|
|
876
|
+
provider: providerId,
|
|
877
|
+
providerName: provider.displayName,
|
|
878
|
+
token: value,
|
|
879
|
+
keyPath: key
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
} catch {
|
|
888
|
+
// Query failed, continue
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
} catch {
|
|
892
|
+
// SQLite read failed
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
return results;
|
|
896
|
+
};
|
|
897
|
+
|
|
499
898
|
/**
|
|
500
899
|
* List files in directory (recursive optional)
|
|
501
900
|
*/
|
|
@@ -848,10 +1247,18 @@ const scanSource = (sourceId) => {
|
|
|
848
1247
|
const scanAllSources = () => {
|
|
849
1248
|
const allResults = [];
|
|
850
1249
|
|
|
851
|
-
// First, scan
|
|
1250
|
+
// First, scan OS secure storage (Keychain, libsecret, Credential Manager)
|
|
1251
|
+
// This is the most reliable source for IDE tokens
|
|
1252
|
+
try {
|
|
1253
|
+
allResults.push(...readSecureStorage());
|
|
1254
|
+
} catch (err) {
|
|
1255
|
+
// Silent fail
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// Then scan environment variables
|
|
852
1259
|
allResults.push(...scanEnvironmentVariables());
|
|
853
1260
|
|
|
854
|
-
// Then scan all tool sources
|
|
1261
|
+
// Then scan all tool sources (config files)
|
|
855
1262
|
for (const sourceId of Object.keys(TOKEN_SOURCES)) {
|
|
856
1263
|
if (sourceId === 'envVars') continue; // Already scanned
|
|
857
1264
|
|
|
@@ -969,10 +1376,12 @@ const getSystemInfo = () => {
|
|
|
969
1376
|
module.exports = {
|
|
970
1377
|
TOKEN_SOURCES,
|
|
971
1378
|
PROVIDER_PATTERNS,
|
|
1379
|
+
CREDENTIAL_ENTRIES,
|
|
972
1380
|
scanAllSources,
|
|
973
1381
|
scanForProvider,
|
|
974
1382
|
scanSource,
|
|
975
1383
|
scanEnvironmentVariables,
|
|
1384
|
+
readSecureStorage,
|
|
976
1385
|
formatResults,
|
|
977
1386
|
timeAgo,
|
|
978
1387
|
hasExistingTokens,
|
package/src/ui/box.js
CHANGED
|
@@ -55,7 +55,7 @@ const padText = (text, width) => {
|
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
* Draw box header with title
|
|
58
|
+
* Draw box header with title (starts new box with ╔)
|
|
59
59
|
*/
|
|
60
60
|
const drawBoxHeader = (title, width) => {
|
|
61
61
|
const innerWidth = width - 2;
|
|
@@ -64,6 +64,16 @@ const drawBoxHeader = (title, width) => {
|
|
|
64
64
|
console.log(chalk.cyan('\u2560' + '\u2550'.repeat(innerWidth) + '\u2563'));
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Draw box header that continues from previous box (uses ╠ instead of ╔)
|
|
69
|
+
*/
|
|
70
|
+
const drawBoxHeaderContinue = (title, width) => {
|
|
71
|
+
const innerWidth = width - 2;
|
|
72
|
+
console.log(chalk.cyan('\u2560' + '\u2550'.repeat(innerWidth) + '\u2563'));
|
|
73
|
+
console.log(chalk.cyan('\u2551') + chalk.cyan.bold(centerText(title, innerWidth)) + chalk.cyan('\u2551'));
|
|
74
|
+
console.log(chalk.cyan('\u2560' + '\u2550'.repeat(innerWidth) + '\u2563'));
|
|
75
|
+
};
|
|
76
|
+
|
|
67
77
|
/**
|
|
68
78
|
* Draw box footer
|
|
69
79
|
*/
|
|
@@ -104,6 +114,7 @@ module.exports = {
|
|
|
104
114
|
centerText,
|
|
105
115
|
padText,
|
|
106
116
|
drawBoxHeader,
|
|
117
|
+
drawBoxHeaderContinue,
|
|
107
118
|
drawBoxFooter,
|
|
108
119
|
drawBoxRow,
|
|
109
120
|
drawBoxSeparator,
|
package/src/ui/index.js
CHANGED
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
centerText,
|
|
11
11
|
padText,
|
|
12
12
|
drawBoxHeader,
|
|
13
|
+
drawBoxHeaderContinue,
|
|
13
14
|
drawBoxFooter,
|
|
14
15
|
drawBoxRow,
|
|
15
16
|
drawBoxSeparator,
|
|
@@ -105,6 +106,7 @@ module.exports = {
|
|
|
105
106
|
centerText,
|
|
106
107
|
padText,
|
|
107
108
|
drawBoxHeader,
|
|
109
|
+
drawBoxHeaderContinue,
|
|
108
110
|
drawBoxFooter,
|
|
109
111
|
drawBoxRow,
|
|
110
112
|
drawBoxSeparator,
|