agentaudit 3.9.9 → 3.9.11
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/cli.mjs +58 -28
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -343,38 +343,47 @@ function extractJSON(text) {
|
|
|
343
343
|
// 1. Try parsing the entire text as JSON directly
|
|
344
344
|
try { return JSON.parse(text.trim()); } catch {}
|
|
345
345
|
|
|
346
|
-
// 2. Strip markdown code fences (
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
try {
|
|
346
|
+
// 2. Strip markdown code fences — try last fence first (report is usually at the end)
|
|
347
|
+
const fenceMatches = [...text.matchAll(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/g)];
|
|
348
|
+
for (let i = fenceMatches.length - 1; i >= 0; i--) {
|
|
349
|
+
try {
|
|
350
|
+
const parsed = JSON.parse(fenceMatches[i][1].trim());
|
|
351
|
+
if (parsed && typeof parsed === 'object' && ('risk_score' in parsed || 'findings' in parsed || 'result' in parsed)) return parsed;
|
|
352
|
+
} catch {}
|
|
353
|
+
}
|
|
354
|
+
// Try any fence even without report keys
|
|
355
|
+
for (let i = fenceMatches.length - 1; i >= 0; i--) {
|
|
356
|
+
try { return JSON.parse(fenceMatches[i][1].trim()); } catch {}
|
|
350
357
|
}
|
|
351
358
|
|
|
352
|
-
// 3. Find
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
depth
|
|
367
|
-
if (depth === 0) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
359
|
+
// 3. Find ALL balanced top-level { ... } blocks, try each (prefer largest valid one)
|
|
360
|
+
const blocks = [];
|
|
361
|
+
let searchFrom = 0;
|
|
362
|
+
while (searchFrom < text.length) {
|
|
363
|
+
const start = text.indexOf('{', searchFrom);
|
|
364
|
+
if (start === -1) break;
|
|
365
|
+
let depth = 0, inStr = false, esc = false;
|
|
366
|
+
let end = -1;
|
|
367
|
+
for (let i = start; i < text.length; i++) {
|
|
368
|
+
const ch = text[i];
|
|
369
|
+
if (esc) { esc = false; continue; }
|
|
370
|
+
if (ch === '\\' && inStr) { esc = true; continue; }
|
|
371
|
+
if (ch === '"') { inStr = !inStr; continue; }
|
|
372
|
+
if (inStr) continue;
|
|
373
|
+
if (ch === '{') depth++;
|
|
374
|
+
else if (ch === '}') { depth--; if (depth === 0) { end = i; break; } }
|
|
375
|
+
}
|
|
376
|
+
if (end > start) {
|
|
377
|
+
blocks.push(text.slice(start, end + 1));
|
|
378
|
+
searchFrom = end + 1;
|
|
379
|
+
} else {
|
|
380
|
+
searchFrom = start + 1;
|
|
371
381
|
}
|
|
372
382
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
try { return JSON.parse(greedy[0]); } catch {}
|
|
383
|
+
// Try largest block first (the report JSON is usually the biggest)
|
|
384
|
+
blocks.sort((a, b) => b.length - a.length);
|
|
385
|
+
for (const block of blocks) {
|
|
386
|
+
try { return JSON.parse(block); } catch {}
|
|
378
387
|
}
|
|
379
388
|
|
|
380
389
|
return null;
|
|
@@ -1381,6 +1390,7 @@ async function auditRepo(url) {
|
|
|
1381
1390
|
].join('\n');
|
|
1382
1391
|
|
|
1383
1392
|
let report = null;
|
|
1393
|
+
let _lastLlmText = '';
|
|
1384
1394
|
|
|
1385
1395
|
try {
|
|
1386
1396
|
if (anthropicKey) {
|
|
@@ -1400,7 +1410,14 @@ async function auditRepo(url) {
|
|
|
1400
1410
|
signal: AbortSignal.timeout(120_000),
|
|
1401
1411
|
});
|
|
1402
1412
|
const data = await res.json();
|
|
1413
|
+
if (data.error) {
|
|
1414
|
+
console.log(` ${c.red}failed${c.reset}`);
|
|
1415
|
+
console.log(` ${c.red}API error: ${data.error.message || JSON.stringify(data.error)}${c.reset}`);
|
|
1416
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
1417
|
+
return null;
|
|
1418
|
+
}
|
|
1403
1419
|
const text = data.content?.[0]?.text || '';
|
|
1420
|
+
_lastLlmText = text;
|
|
1404
1421
|
report = extractJSON(text);
|
|
1405
1422
|
} else if (openaiKey) {
|
|
1406
1423
|
const res = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
@@ -1420,7 +1437,14 @@ async function auditRepo(url) {
|
|
|
1420
1437
|
signal: AbortSignal.timeout(120_000),
|
|
1421
1438
|
});
|
|
1422
1439
|
const data = await res.json();
|
|
1440
|
+
if (data.error) {
|
|
1441
|
+
console.log(` ${c.red}failed${c.reset}`);
|
|
1442
|
+
console.log(` ${c.red}API error: ${data.error.message || JSON.stringify(data.error)}${c.reset}`);
|
|
1443
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
1444
|
+
return null;
|
|
1445
|
+
}
|
|
1423
1446
|
const text = data.choices?.[0]?.message?.content || '';
|
|
1447
|
+
_lastLlmText = text;
|
|
1424
1448
|
report = extractJSON(text);
|
|
1425
1449
|
}
|
|
1426
1450
|
|
|
@@ -1437,6 +1461,12 @@ async function auditRepo(url) {
|
|
|
1437
1461
|
|
|
1438
1462
|
if (!report) {
|
|
1439
1463
|
console.log(` ${c.red}Could not parse LLM response as JSON${c.reset}`);
|
|
1464
|
+
console.log(` ${c.dim}Hint: run with --debug to see the raw LLM response${c.reset}`);
|
|
1465
|
+
if (process.argv.includes('--debug')) {
|
|
1466
|
+
console.log(` ${c.dim}--- Raw LLM response (first 2000 chars) ---${c.reset}`);
|
|
1467
|
+
console.log((typeof _lastLlmText === 'string' ? _lastLlmText : '(empty)').slice(0, 2000));
|
|
1468
|
+
console.log(` ${c.dim}--- end ---${c.reset}`);
|
|
1469
|
+
}
|
|
1440
1470
|
return null;
|
|
1441
1471
|
}
|
|
1442
1472
|
|