gitarsenal-cli 1.9.77 → 1.9.80
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/.venv_status.json +1 -1
- package/bin/gitarsenal.js +381 -18
- package/digest.txt +11446 -0
- package/gitingest-integration.js +274 -0
- package/kill_claude/prompts/claude-code-tool-prompts.md +2 -1
- package/kill_claude/tools/__pycache__/bash_output_tool.cpython-312.pyc +0 -0
- package/kill_claude/tools/__pycache__/bash_tool.cpython-312.pyc +0 -0
- package/kill_claude/tools/__pycache__/edit_tool.cpython-312.pyc +0 -0
- package/kill_claude/tools/__pycache__/exit_plan_mode_tool.cpython-312.pyc +0 -0
- package/kill_claude/tools/__pycache__/glob_tool.cpython-312.pyc +0 -0
- package/kill_claude/tools/__pycache__/grep_tool.cpython-312.pyc +0 -0
- package/kill_claude/tools/__pycache__/kill_bash_tool.cpython-312.pyc +0 -0
- package/kill_claude/tools/__pycache__/ls_tool.cpython-312.pyc +0 -0
- package/kill_claude/tools/__pycache__/multiedit_tool.cpython-312.pyc +0 -0
- package/kill_claude/tools/__pycache__/notebook_edit_tool.cpython-312.pyc +0 -0
- package/kill_claude/tools/__pycache__/read_tool.cpython-312.pyc +0 -0
- package/kill_claude/tools/__pycache__/task_tool.cpython-312.pyc +0 -0
- package/kill_claude/tools/__pycache__/todo_write_tool.cpython-312.pyc +0 -0
- package/kill_claude/tools/__pycache__/web_fetch_tool.cpython-312.pyc +0 -0
- package/kill_claude/tools/__pycache__/web_search_tool.cpython-312.pyc +0 -0
- package/kill_claude/tools/__pycache__/write_tool.cpython-312.pyc +0 -0
- package/package.json +1 -1
- package/python/__pycache__/analyze_repo_api_keys.cpython-312.pyc +0 -0
- package/python/__pycache__/credentials_manager.cpython-312.pyc +0 -0
- package/python/credentials_manager.py +0 -169
- package/python/gitarsenal_keys.py +8 -2
package/.venv_status.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"created":"2025-08-
|
|
1
|
+
{"created":"2025-08-18T04:28:55.195Z","packages":["modal","gitingest","requests","anthropic"],"uv_version":"uv 0.8.4 (Homebrew 2025-07-30)"}
|
package/bin/gitarsenal.js
CHANGED
|
@@ -15,6 +15,7 @@ const { spawn } = require('child_process');
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const https = require('https');
|
|
17
17
|
const http = require('http');
|
|
18
|
+
const { fetchGitIngestData } = require('../gitingest-integration');
|
|
18
19
|
|
|
19
20
|
// Function to activate virtual environment
|
|
20
21
|
function activateVirtualEnvironment() {
|
|
@@ -22,7 +23,7 @@ function activateVirtualEnvironment() {
|
|
|
22
23
|
const venvPath = path.join(__dirname, '..', '.venv');
|
|
23
24
|
const statusFile = path.join(__dirname, '..', '.venv_status.json');
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
// Check if virtual environment exists
|
|
26
27
|
if (!fs.existsSync(venvPath)) {
|
|
27
28
|
console.log(chalk.red('❌ Virtual environment not found. Please reinstall the package:'));
|
|
28
29
|
console.log(chalk.yellow(' npm uninstall -g gitarsenal-cli'));
|
|
@@ -57,13 +58,7 @@ function activateVirtualEnvironment() {
|
|
|
57
58
|
const traditionalPipPath = isWindows ?
|
|
58
59
|
path.join(venvPath, 'Scripts', 'pip.exe') :
|
|
59
60
|
path.join(venvPath, 'bin', 'pip');
|
|
60
|
-
|
|
61
|
-
// Determine which structure exists
|
|
62
|
-
// console.log(chalk.gray(`🔍 Checking virtual environment structure:`));
|
|
63
|
-
// console.log(chalk.gray(` Python: ${uvPythonPath} (exists: ${fs.existsSync(uvPythonPath)})`));
|
|
64
|
-
// console.log(chalk.gray(` Pip: ${uvPipPath} (exists: ${fs.existsSync(uvPipPath)})`));
|
|
65
|
-
|
|
66
|
-
// For uv virtual environments, we only need Python to exist
|
|
61
|
+
|
|
67
62
|
// uv doesn't create a pip executable, it uses 'uv pip' instead
|
|
68
63
|
if (fs.existsSync(uvPythonPath)) {
|
|
69
64
|
pythonPath = uvPythonPath;
|
|
@@ -261,9 +256,271 @@ function printGpuTorchCudaSummary(result) {
|
|
|
261
256
|
if (gpu.notes) console.log(` - Notes: ${gpu.notes}`);
|
|
262
257
|
console.log();
|
|
263
258
|
}
|
|
259
|
+
|
|
260
|
+
// Print API key requirements if available
|
|
261
|
+
if (result.requiredApiKeys && Array.isArray(result.requiredApiKeys) && result.requiredApiKeys.length > 0) {
|
|
262
|
+
// Separate critical and optional API keys
|
|
263
|
+
const criticalKeys = result.requiredApiKeys.filter(key => key.priority === 'critical' || key.required);
|
|
264
|
+
const optionalKeys = result.requiredApiKeys.filter(key => key.priority === 'optional' || !key.required);
|
|
265
|
+
|
|
266
|
+
if (criticalKeys.length > 0) {
|
|
267
|
+
console.log(chalk.bold('🔑 CRITICAL API KEYS'));
|
|
268
|
+
criticalKeys.forEach(apiKey => {
|
|
269
|
+
console.log(` - ${apiKey.name} ${chalk.red('(REQUIRED)')}`);
|
|
270
|
+
console.log(` Service: ${apiKey.service}`);
|
|
271
|
+
console.log(` Purpose: ${apiKey.description}`);
|
|
272
|
+
if (apiKey.example) console.log(` Format: ${apiKey.example}`);
|
|
273
|
+
if (apiKey.documentation_url) console.log(` Docs: ${apiKey.documentation_url}`);
|
|
274
|
+
console.log();
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (optionalKeys.length > 0) {
|
|
279
|
+
console.log(chalk.bold('🔧 OPTIONAL API KEYS'));
|
|
280
|
+
optionalKeys.forEach(apiKey => {
|
|
281
|
+
console.log(` - ${apiKey.name} ${chalk.yellow('(OPTIONAL)')}`);
|
|
282
|
+
console.log(` Service: ${apiKey.service}`);
|
|
283
|
+
console.log(` Purpose: ${apiKey.description}`);
|
|
284
|
+
if (apiKey.example) console.log(` Format: ${apiKey.example}`);
|
|
285
|
+
if (apiKey.documentation_url) console.log(` Docs: ${apiKey.documentation_url}`);
|
|
286
|
+
console.log();
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
264
290
|
} catch {}
|
|
265
291
|
}
|
|
266
292
|
|
|
293
|
+
// Function to load stored API keys
|
|
294
|
+
async function loadStoredApiKeys() {
|
|
295
|
+
try {
|
|
296
|
+
const scriptPath = path.join(__dirname, '..', 'python', 'gitarsenal_keys.py');
|
|
297
|
+
const pythonExecutable = process.env.PYTHON_EXECUTABLE || 'python';
|
|
298
|
+
|
|
299
|
+
return new Promise((resolve) => {
|
|
300
|
+
const pythonProcess = spawn(pythonExecutable, [
|
|
301
|
+
scriptPath,
|
|
302
|
+
'list',
|
|
303
|
+
'--json'
|
|
304
|
+
], { stdio: 'pipe' });
|
|
305
|
+
|
|
306
|
+
let output = '';
|
|
307
|
+
pythonProcess.stdout.on('data', (data) => {
|
|
308
|
+
output += data.toString();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
pythonProcess.on('close', (code) => {
|
|
312
|
+
if (code === 0) {
|
|
313
|
+
try {
|
|
314
|
+
const keys = JSON.parse(output);
|
|
315
|
+
resolve(keys);
|
|
316
|
+
} catch (e) {
|
|
317
|
+
resolve({});
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
resolve({});
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return {};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Function to prompt for missing required API keys
|
|
330
|
+
async function promptForMissingApiKeys(requiredApiKeys, storedKeys) {
|
|
331
|
+
if (!requiredApiKeys || !Array.isArray(requiredApiKeys) || requiredApiKeys.length === 0) {
|
|
332
|
+
return {};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const missingKeys = {};
|
|
336
|
+
|
|
337
|
+
// Separate critical and optional missing keys
|
|
338
|
+
const criticalMissingKeys = requiredApiKeys.filter(apiKey =>
|
|
339
|
+
(apiKey.priority === 'critical' || apiKey.required) && !storedKeys[apiKey.service.toLowerCase()]
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
const optionalMissingKeys = requiredApiKeys.filter(apiKey =>
|
|
343
|
+
(apiKey.priority === 'optional' || !apiKey.required) && !storedKeys[apiKey.service.toLowerCase()]
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// Handle critical keys first
|
|
347
|
+
if (criticalMissingKeys.length > 0) {
|
|
348
|
+
console.log(chalk.red('\n🔑 Critical API Keys Required'));
|
|
349
|
+
console.log(chalk.gray('These API keys are required for core functionality:'));
|
|
350
|
+
|
|
351
|
+
for (const apiKey of criticalMissingKeys) {
|
|
352
|
+
console.log(chalk.bold(`\n📝 ${apiKey.name} (${apiKey.service})`));
|
|
353
|
+
console.log(chalk.gray(`Purpose: ${apiKey.description}`));
|
|
354
|
+
if (apiKey.documentation_url) {
|
|
355
|
+
console.log(chalk.blue(`Documentation: ${apiKey.documentation_url}`));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const answers = await inquirer.prompt([
|
|
359
|
+
{
|
|
360
|
+
type: 'confirm',
|
|
361
|
+
name: 'provideKey',
|
|
362
|
+
message: `Do you want to provide your ${apiKey.service} API key now?`,
|
|
363
|
+
default: true
|
|
364
|
+
}
|
|
365
|
+
]);
|
|
366
|
+
|
|
367
|
+
if (answers.provideKey) {
|
|
368
|
+
const keyAnswer = await inquirer.prompt([
|
|
369
|
+
{
|
|
370
|
+
type: 'password',
|
|
371
|
+
name: 'key',
|
|
372
|
+
message: `Enter your ${apiKey.service} API key:`,
|
|
373
|
+
mask: '*',
|
|
374
|
+
validate: (input) => {
|
|
375
|
+
const key = input.trim();
|
|
376
|
+
if (key === '') return `${apiKey.service} API key is required`;
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
]);
|
|
381
|
+
|
|
382
|
+
const storeAnswer = await inquirer.prompt([
|
|
383
|
+
{
|
|
384
|
+
type: 'confirm',
|
|
385
|
+
name: 'store',
|
|
386
|
+
message: `Store this API key locally for future use?`,
|
|
387
|
+
default: true
|
|
388
|
+
}
|
|
389
|
+
]);
|
|
390
|
+
|
|
391
|
+
missingKeys[apiKey.service.toLowerCase()] = keyAnswer.key;
|
|
392
|
+
|
|
393
|
+
if (storeAnswer.store) {
|
|
394
|
+
// Store the key using the existing key management system
|
|
395
|
+
try {
|
|
396
|
+
const scriptPath = path.join(__dirname, '..', 'python', 'gitarsenal_keys.py');
|
|
397
|
+
const pythonExecutable = process.env.PYTHON_EXECUTABLE || 'python';
|
|
398
|
+
|
|
399
|
+
await new Promise((resolve, reject) => {
|
|
400
|
+
const pythonProcess = spawn(pythonExecutable, [
|
|
401
|
+
scriptPath,
|
|
402
|
+
'add',
|
|
403
|
+
'--service', apiKey.service.toLowerCase(),
|
|
404
|
+
'--key', keyAnswer.key
|
|
405
|
+
], { stdio: 'pipe' });
|
|
406
|
+
|
|
407
|
+
pythonProcess.on('close', (code) => {
|
|
408
|
+
if (code === 0) {
|
|
409
|
+
console.log(chalk.green(`✅ ${apiKey.service} API key stored successfully`));
|
|
410
|
+
resolve();
|
|
411
|
+
} else {
|
|
412
|
+
console.log(chalk.yellow(`⚠️ Could not store ${apiKey.service} API key locally`));
|
|
413
|
+
resolve(); // Don't fail the whole process
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
} catch (error) {
|
|
418
|
+
console.log(chalk.yellow(`⚠️ Could not store ${apiKey.service} API key: ${error.message}`));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
} else {
|
|
422
|
+
console.log(chalk.yellow(`⚠️ Skipping ${apiKey.service} API key. Repository setup may fail without it.`));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Handle optional keys
|
|
428
|
+
if (optionalMissingKeys.length > 0) {
|
|
429
|
+
const setupOptional = await inquirer.prompt([
|
|
430
|
+
{
|
|
431
|
+
type: 'confirm',
|
|
432
|
+
name: 'wantOptional',
|
|
433
|
+
message: `${optionalMissingKeys.length} optional API key(s) available. Set them up for enhanced features?`,
|
|
434
|
+
default: false
|
|
435
|
+
}
|
|
436
|
+
]);
|
|
437
|
+
|
|
438
|
+
if (setupOptional.wantOptional) {
|
|
439
|
+
console.log(chalk.blue('\n🔧 Optional API Keys Setup'));
|
|
440
|
+
console.log(chalk.gray('These enhance functionality but are not required:'));
|
|
441
|
+
|
|
442
|
+
for (const apiKey of optionalMissingKeys) {
|
|
443
|
+
console.log(chalk.bold(`\n📝 ${apiKey.name} (${apiKey.service}) - Optional`));
|
|
444
|
+
console.log(chalk.gray(`Purpose: ${apiKey.description}`));
|
|
445
|
+
if (apiKey.documentation_url) {
|
|
446
|
+
console.log(chalk.blue(`Documentation: ${apiKey.documentation_url}`));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const answers = await inquirer.prompt([
|
|
450
|
+
{
|
|
451
|
+
type: 'confirm',
|
|
452
|
+
name: 'provideKey',
|
|
453
|
+
message: `Provide ${apiKey.service} API key for enhanced features?`,
|
|
454
|
+
default: false
|
|
455
|
+
}
|
|
456
|
+
]);
|
|
457
|
+
|
|
458
|
+
if (answers.provideKey) {
|
|
459
|
+
const keyAnswer = await inquirer.prompt([
|
|
460
|
+
{
|
|
461
|
+
type: 'password',
|
|
462
|
+
name: 'key',
|
|
463
|
+
message: `Enter your ${apiKey.service} API key:`,
|
|
464
|
+
mask: '*',
|
|
465
|
+
validate: (input) => {
|
|
466
|
+
const key = input.trim();
|
|
467
|
+
if (key === '') return `${apiKey.service} API key cannot be empty`;
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
]);
|
|
472
|
+
|
|
473
|
+
const storeAnswer = await inquirer.prompt([
|
|
474
|
+
{
|
|
475
|
+
type: 'confirm',
|
|
476
|
+
name: 'store',
|
|
477
|
+
message: `Store this API key locally for future use?`,
|
|
478
|
+
default: true
|
|
479
|
+
}
|
|
480
|
+
]);
|
|
481
|
+
|
|
482
|
+
missingKeys[apiKey.service.toLowerCase()] = keyAnswer.key;
|
|
483
|
+
|
|
484
|
+
if (storeAnswer.store) {
|
|
485
|
+
try {
|
|
486
|
+
const scriptPath = path.join(__dirname, '..', 'python', 'gitarsenal_keys.py');
|
|
487
|
+
const pythonExecutable = process.env.PYTHON_EXECUTABLE || 'python';
|
|
488
|
+
|
|
489
|
+
await new Promise((resolve, reject) => {
|
|
490
|
+
const pythonProcess = spawn(pythonExecutable, [
|
|
491
|
+
scriptPath,
|
|
492
|
+
'add',
|
|
493
|
+
'--service', apiKey.service.toLowerCase(),
|
|
494
|
+
'--key', keyAnswer.key
|
|
495
|
+
], { stdio: 'pipe' });
|
|
496
|
+
|
|
497
|
+
pythonProcess.on('close', (code) => {
|
|
498
|
+
if (code === 0) {
|
|
499
|
+
console.log(chalk.green(`✅ ${apiKey.service} API key stored successfully`));
|
|
500
|
+
resolve();
|
|
501
|
+
} else {
|
|
502
|
+
console.log(chalk.yellow(`⚠️ Could not store ${apiKey.service} API key locally`));
|
|
503
|
+
resolve();
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
} catch (error) {
|
|
508
|
+
console.log(chalk.yellow(`⚠️ Could not store ${apiKey.service} API key: ${error.message}`));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Check if all critical keys are handled
|
|
517
|
+
if (criticalMissingKeys.length === 0) {
|
|
518
|
+
console.log(chalk.green('✅ All critical API keys are available'));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return missingKeys;
|
|
522
|
+
}
|
|
523
|
+
|
|
267
524
|
// Helper to derive a default volume name from the repository URL
|
|
268
525
|
function getDefaultVolumeName(repoUrl) {
|
|
269
526
|
try {
|
|
@@ -321,10 +578,96 @@ function getDefaultVolumeName(repoUrl) {
|
|
|
321
578
|
}
|
|
322
579
|
|
|
323
580
|
// Full fetch to get both setup commands and recommendations in one request
|
|
324
|
-
async function fetchFullSetupAndRecs(repoUrl) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
581
|
+
async function fetchFullSetupAndRecs(repoUrl, storedCredentials = null) {
|
|
582
|
+
const spinner = ora('Analyzing repository with GitIngest...').start();
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
// Try to use local GitIngest CLI first
|
|
586
|
+
spinner.text = 'Running GitIngest analysis...';
|
|
587
|
+
const gitingestData = await fetchGitIngestData(repoUrl);
|
|
588
|
+
|
|
589
|
+
let finalGitingestData;
|
|
590
|
+
if (!gitingestData) {
|
|
591
|
+
spinner.warn('GitIngest CLI not available, using basic analysis');
|
|
592
|
+
// Fallback to basic data
|
|
593
|
+
finalGitingestData = {
|
|
594
|
+
system_info: {
|
|
595
|
+
platform: process.platform,
|
|
596
|
+
python_version: process.version,
|
|
597
|
+
detected_language: 'Unknown',
|
|
598
|
+
detected_technologies: [],
|
|
599
|
+
file_count: 0,
|
|
600
|
+
repo_stars: 0,
|
|
601
|
+
repo_forks: 0,
|
|
602
|
+
primary_package_manager: 'Unknown',
|
|
603
|
+
complexity_level: 'Unknown'
|
|
604
|
+
},
|
|
605
|
+
repository_analysis: {
|
|
606
|
+
summary: `Repository: ${repoUrl}`,
|
|
607
|
+
tree: '',
|
|
608
|
+
content_preview: ''
|
|
609
|
+
},
|
|
610
|
+
success: false
|
|
611
|
+
};
|
|
612
|
+
} else {
|
|
613
|
+
finalGitingestData = gitingestData;
|
|
614
|
+
spinner.text = 'GitIngest complete, generating AI recommendations...';
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const envUrl = process.env.GITARSENAL_API_URL;
|
|
618
|
+
const endpoints = envUrl ? [envUrl] : ['https://www.gitarsenal.dev/api/best_gpu'];
|
|
619
|
+
|
|
620
|
+
const payload = {
|
|
621
|
+
repoUrl,
|
|
622
|
+
gitingestData: finalGitingestData,
|
|
623
|
+
storedCredentials,
|
|
624
|
+
preview: false // This is a full analysis, not preview
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
let data = null;
|
|
628
|
+
let lastErrorText = '';
|
|
629
|
+
|
|
630
|
+
for (const url of endpoints) {
|
|
631
|
+
try {
|
|
632
|
+
spinner.text = `Analyzing repository: ${url}`;
|
|
633
|
+
const res = await fetch(url, {
|
|
634
|
+
method: 'POST',
|
|
635
|
+
headers: { 'Content-Type': 'application/json', 'User-Agent': 'GitArsenal-CLI/1.0' },
|
|
636
|
+
body: JSON.stringify(payload),
|
|
637
|
+
redirect: 'follow'
|
|
638
|
+
});
|
|
639
|
+
if (!res.ok) {
|
|
640
|
+
const text = await res.text().catch(() => '');
|
|
641
|
+
lastErrorText = `${res.status} ${text.slice(0, 300)}`;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
data = await res.json().catch(() => null);
|
|
645
|
+
if (data) {
|
|
646
|
+
console.log(chalk.gray('🐛 DEBUG: Received response from API:'));
|
|
647
|
+
console.log(chalk.gray(' - Response has commands:', !!data.commands));
|
|
648
|
+
console.log(chalk.gray(' - Commands count:', data.commands ? data.commands.length : 0));
|
|
649
|
+
console.log(chalk.gray(' - Response has API keys:', !!data.requiredApiKeys));
|
|
650
|
+
console.log(chalk.gray(' - API keys count:', data.requiredApiKeys ? data.requiredApiKeys.length : 0));
|
|
651
|
+
console.log(chalk.gray(' - Response has GPU rec:', !!data.gpuRecommendation));
|
|
652
|
+
console.log(chalk.gray(' - Response has CUDA rec:', !!data.cudaRecommendation));
|
|
653
|
+
console.log(chalk.gray(' - Response has Torch rec:', !!data.torchRecommendation));
|
|
654
|
+
spinner.succeed('Repository analysis complete');
|
|
655
|
+
return data;
|
|
656
|
+
}
|
|
657
|
+
} catch (err) {
|
|
658
|
+
lastErrorText = err && err.message ? err.message : 'request failed';
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
spinner.fail('Failed to analyze repository');
|
|
664
|
+
if (lastErrorText) console.log(chalk.gray(`Reason: ${lastErrorText}`));
|
|
665
|
+
return null;
|
|
666
|
+
|
|
667
|
+
} catch (e) {
|
|
668
|
+
spinner.fail(`Analysis failed: ${e.message}`);
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
328
671
|
}
|
|
329
672
|
|
|
330
673
|
// Function to send user data to web application
|
|
@@ -761,8 +1104,15 @@ async function runContainerCommand(options) {
|
|
|
761
1104
|
repoUrl = answers.repoUrl;
|
|
762
1105
|
}
|
|
763
1106
|
|
|
764
|
-
// Analyze repository for GPU recommendations
|
|
1107
|
+
// Analyze repository for GPU recommendations and API key requirements
|
|
1108
|
+
let analysisData = null;
|
|
1109
|
+
let collectedApiKeys = {};
|
|
1110
|
+
|
|
765
1111
|
if (repoUrl) {
|
|
1112
|
+
// Load stored API keys first
|
|
1113
|
+
console.log(chalk.blue('🔍 Loading stored API keys...'));
|
|
1114
|
+
const storedKeys = await loadStoredApiKeys();
|
|
1115
|
+
|
|
766
1116
|
// Start a main spinner that will show overall progress
|
|
767
1117
|
const mainSpinner = ora('Analyzing repository...').start();
|
|
768
1118
|
|
|
@@ -773,16 +1123,24 @@ async function runContainerCommand(options) {
|
|
|
773
1123
|
mainSpinner.text = 'Analyzing repository for GPU/Torch/CUDA recommendations...';
|
|
774
1124
|
const previewPromise = previewRecommendations(repoUrl, { showSummary: false, abortSignal: previewAbort.signal, hideSpinner: true }).catch(() => null);
|
|
775
1125
|
|
|
776
|
-
// Run full fetch in parallel; prefer its results if available.
|
|
777
|
-
mainSpinner.text = 'Finding the best machine for your code...';
|
|
778
|
-
const fullData = await fetchFullSetupAndRecs(repoUrl).catch(() => null);
|
|
1126
|
+
// Run full fetch in parallel with stored credentials; prefer its results if available.
|
|
1127
|
+
mainSpinner.text = 'Finding the best machine for your code and detecting API requirements...';
|
|
1128
|
+
const fullData = await fetchFullSetupAndRecs(repoUrl, storedKeys).catch(() => null);
|
|
779
1129
|
|
|
780
1130
|
if (fullData) {
|
|
781
1131
|
// Stop preview spinner immediately since we have a response
|
|
782
1132
|
previewAbort.abort();
|
|
783
1133
|
mainSpinner.succeed('Analysis complete!');
|
|
784
1134
|
printGpuTorchCudaSummary(fullData);
|
|
785
|
-
|
|
1135
|
+
analysisData = fullData;
|
|
1136
|
+
|
|
1137
|
+
// Handle API key requirements
|
|
1138
|
+
if (fullData.requiredApiKeys && Array.isArray(fullData.requiredApiKeys) && fullData.requiredApiKeys.length > 0) {
|
|
1139
|
+
const missingKeys = await promptForMissingApiKeys(fullData.requiredApiKeys, storedKeys);
|
|
1140
|
+
collectedApiKeys = { ...storedKeys, ...missingKeys };
|
|
1141
|
+
} else {
|
|
1142
|
+
collectedApiKeys = storedKeys;
|
|
1143
|
+
}
|
|
786
1144
|
} else {
|
|
787
1145
|
// Full fetch failed, wait for preview and show its results
|
|
788
1146
|
mainSpinner.text = 'Waiting for preview analysis to complete...';
|
|
@@ -790,16 +1148,19 @@ async function runContainerCommand(options) {
|
|
|
790
1148
|
if (previewData) {
|
|
791
1149
|
mainSpinner.succeed('Preview analysis complete!');
|
|
792
1150
|
printGpuTorchCudaSummary(previewData);
|
|
1151
|
+
analysisData = previewData;
|
|
793
1152
|
} else {
|
|
794
1153
|
mainSpinner.fail('Analysis failed - both preview and full analysis timed out or failed');
|
|
795
1154
|
console.log(chalk.yellow('⚠️ Unable to analyze repository automatically.'));
|
|
796
1155
|
console.log(chalk.gray('Repository setup will still be handled by Agent in container.'));
|
|
797
1156
|
}
|
|
1157
|
+
collectedApiKeys = storedKeys;
|
|
798
1158
|
}
|
|
799
1159
|
} catch (error) {
|
|
800
1160
|
mainSpinner.fail(`Analysis failed: ${error.message}`);
|
|
801
1161
|
console.log(chalk.yellow('⚠️ Unable to analyze repository automatically.'));
|
|
802
1162
|
console.log(chalk.gray('Repository setup will still be handled by Agent in container.'));
|
|
1163
|
+
collectedApiKeys = await loadStoredApiKeys();
|
|
803
1164
|
}
|
|
804
1165
|
}
|
|
805
1166
|
|
|
@@ -934,7 +1295,9 @@ async function runContainerCommand(options) {
|
|
|
934
1295
|
yes: skipConfirmation,
|
|
935
1296
|
userId,
|
|
936
1297
|
userName,
|
|
937
|
-
userEmail
|
|
1298
|
+
userEmail,
|
|
1299
|
+
apiKeys: collectedApiKeys,
|
|
1300
|
+
analysisData
|
|
938
1301
|
});
|
|
939
1302
|
|
|
940
1303
|
} catch (containerError) {
|