@triedotdev/mcp 1.0.59 → 1.0.60
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 +17 -13
- package/dist/{agent-smith-O7RG7WQ3.js → agent-smith-4IJZHSNQ.js} +3 -2
- package/dist/{agent-smith-runner-EW4C4PTF.js → agent-smith-runner-4PE3GOUX.js} +5 -3
- package/dist/{agent-smith-runner-EW4C4PTF.js.map → agent-smith-runner-4PE3GOUX.js.map} +1 -1
- package/dist/{chunk-HUQQSQOD.js → chunk-3XWSM2DU.js} +73 -44
- package/dist/chunk-3XWSM2DU.js.map +1 -0
- package/dist/{chunk-KLKY34RE.js → chunk-4EHBRED6.js} +139 -12
- package/dist/chunk-4EHBRED6.js.map +1 -0
- package/dist/{chunk-6ODT4U4O.js → chunk-C7LXN754.js} +539 -194
- package/dist/chunk-C7LXN754.js.map +1 -0
- package/dist/{chunk-AVOMCNBD.js → chunk-DV3JQTLQ.js} +23 -86
- package/dist/chunk-DV3JQTLQ.js.map +1 -0
- package/dist/{chunk-3CS6Z2SL.js → chunk-MVUCBUBR.js} +7 -2
- package/dist/chunk-MVUCBUBR.js.map +1 -0
- package/dist/{chunk-3B2JBLSP.js → chunk-NM7ZVUHO.js} +682 -37
- package/dist/chunk-NM7ZVUHO.js.map +1 -0
- package/dist/chunk-RAZUNSBI.js +171 -0
- package/dist/chunk-RAZUNSBI.js.map +1 -0
- package/dist/{chunk-MR755QGT.js → chunk-ULOW5HSH.js} +7 -2
- package/dist/chunk-ULOW5HSH.js.map +1 -0
- package/dist/cli/main.js +6 -5
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/yolo-daemon.js +10 -9
- package/dist/cli/yolo-daemon.js.map +1 -1
- package/dist/index.js +30 -18
- package/dist/index.js.map +1 -1
- package/dist/{vibe-code-signatures-4CBHUSI7.js → vibe-code-signatures-TGMQXYGO.js} +3 -2
- package/dist/{vulnerability-signatures-J3CUQ7VR.js → vulnerability-signatures-GOVD4Q24.js} +3 -2
- package/dist/workers/agent-worker.js +4 -3
- package/dist/workers/agent-worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-3B2JBLSP.js.map +0 -1
- package/dist/chunk-3CS6Z2SL.js.map +0 -1
- package/dist/chunk-6ODT4U4O.js.map +0 -1
- package/dist/chunk-AVOMCNBD.js.map +0 -1
- package/dist/chunk-HUQQSQOD.js.map +0 -1
- package/dist/chunk-KLKY34RE.js.map +0 -1
- package/dist/chunk-MR755QGT.js.map +0 -1
- /package/dist/{agent-smith-O7RG7WQ3.js.map → agent-smith-4IJZHSNQ.js.map} +0 -0
- /package/dist/{vibe-code-signatures-4CBHUSI7.js.map → vibe-code-signatures-TGMQXYGO.js.map} +0 -0
- /package/dist/{vulnerability-signatures-J3CUQ7VR.js.map → vulnerability-signatures-GOVD4Q24.js.map} +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Executor,
|
|
3
|
-
Triager
|
|
4
|
-
|
|
3
|
+
Triager,
|
|
4
|
+
getChangedFilesSinceTimestamp,
|
|
5
|
+
isGitRepo
|
|
6
|
+
} from "./chunk-4EHBRED6.js";
|
|
5
7
|
import {
|
|
6
8
|
getSkillRegistry,
|
|
7
9
|
updateContextAfterScan
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-3XWSM2DU.js";
|
|
9
11
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
setInteractiveMode
|
|
13
|
-
} from "./chunk-6ODT4U4O.js";
|
|
12
|
+
getOutputManager
|
|
13
|
+
} from "./chunk-C7LXN754.js";
|
|
14
14
|
import {
|
|
15
15
|
getWorkingDirectory
|
|
16
16
|
} from "./chunk-ASGSTVVF.js";
|
|
@@ -18,10 +18,15 @@ import {
|
|
|
18
18
|
getVulnerabilityStats,
|
|
19
19
|
getVulnerabilityTrie,
|
|
20
20
|
scanForVulnerabilities
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-ULOW5HSH.js";
|
|
22
22
|
import {
|
|
23
23
|
Trie
|
|
24
24
|
} from "./chunk-6NLHFIYA.js";
|
|
25
|
+
import {
|
|
26
|
+
ProgressReporter,
|
|
27
|
+
isInteractiveMode,
|
|
28
|
+
setInteractiveMode
|
|
29
|
+
} from "./chunk-RAZUNSBI.js";
|
|
25
30
|
import {
|
|
26
31
|
__require
|
|
27
32
|
} from "./chunk-DGUM43GV.js";
|
|
@@ -76,7 +81,9 @@ var ContextAnalyzer = class {
|
|
|
76
81
|
for (const file of files) {
|
|
77
82
|
try {
|
|
78
83
|
if (!existsSync(file)) {
|
|
79
|
-
|
|
84
|
+
if (!isInteractiveMode()) {
|
|
85
|
+
console.error(`File not found: ${file}`);
|
|
86
|
+
}
|
|
80
87
|
continue;
|
|
81
88
|
}
|
|
82
89
|
if (this.isSignatureFile(file)) {
|
|
@@ -119,7 +126,9 @@ var ContextAnalyzer = class {
|
|
|
119
126
|
const fileComplexity = await this.analyzeCodeContent(content, file, context);
|
|
120
127
|
totalComplexity += fileComplexity;
|
|
121
128
|
} catch (error) {
|
|
122
|
-
|
|
129
|
+
if (!isInteractiveMode()) {
|
|
130
|
+
console.error(`Error analyzing file ${file}:`, error);
|
|
131
|
+
}
|
|
123
132
|
}
|
|
124
133
|
}
|
|
125
134
|
context.linesChanged = totalLines;
|
|
@@ -2327,17 +2336,21 @@ function getSymbolIndex() {
|
|
|
2327
2336
|
|
|
2328
2337
|
// src/trie/incremental-scanner.ts
|
|
2329
2338
|
import { createHash } from "crypto";
|
|
2330
|
-
import { readFile as readFile5, writeFile, mkdir } from "fs/promises";
|
|
2339
|
+
import { readFile as readFile5, writeFile, mkdir, stat } from "fs/promises";
|
|
2331
2340
|
import { existsSync as existsSync2 } from "fs";
|
|
2332
2341
|
import { join as join2 } from "path";
|
|
2333
|
-
var
|
|
2342
|
+
var SCAN_CONCURRENCY = 10;
|
|
2343
|
+
var CACHE_VERSION = "1.1.0";
|
|
2334
2344
|
var CACHE_FILE = ".trie-cache.json";
|
|
2335
2345
|
var IncrementalScanner = class {
|
|
2336
2346
|
cache;
|
|
2337
2347
|
symbolIndex;
|
|
2338
2348
|
cacheDir;
|
|
2349
|
+
projectRoot;
|
|
2339
2350
|
dirty = false;
|
|
2351
|
+
isGitRepoCache = null;
|
|
2340
2352
|
constructor(projectRoot) {
|
|
2353
|
+
this.projectRoot = projectRoot;
|
|
2341
2354
|
this.cacheDir = join2(projectRoot, ".trie");
|
|
2342
2355
|
this.symbolIndex = getSymbolIndex();
|
|
2343
2356
|
this.cache = {
|
|
@@ -2359,14 +2372,20 @@ var IncrementalScanner = class {
|
|
|
2359
2372
|
const data = await readFile5(cachePath, "utf-8");
|
|
2360
2373
|
const parsed = JSON.parse(data);
|
|
2361
2374
|
if (parsed.version !== CACHE_VERSION) {
|
|
2362
|
-
|
|
2375
|
+
if (!isInteractiveMode()) {
|
|
2376
|
+
console.error(" \u26A0\uFE0F Cache version mismatch, rebuilding...");
|
|
2377
|
+
}
|
|
2363
2378
|
return false;
|
|
2364
2379
|
}
|
|
2365
2380
|
this.cache = parsed;
|
|
2366
|
-
|
|
2381
|
+
if (!isInteractiveMode()) {
|
|
2382
|
+
console.error(` \u2705 Loaded cache with ${Object.keys(this.cache.files).length} files`);
|
|
2383
|
+
}
|
|
2367
2384
|
return true;
|
|
2368
2385
|
} catch (error) {
|
|
2369
|
-
|
|
2386
|
+
if (!isInteractiveMode()) {
|
|
2387
|
+
console.error(" \u26A0\uFE0F Failed to load cache, rebuilding...");
|
|
2388
|
+
}
|
|
2370
2389
|
return false;
|
|
2371
2390
|
}
|
|
2372
2391
|
}
|
|
@@ -2382,7 +2401,9 @@ var IncrementalScanner = class {
|
|
|
2382
2401
|
await writeFile(cachePath, JSON.stringify(this.cache, null, 2));
|
|
2383
2402
|
this.dirty = false;
|
|
2384
2403
|
} catch (error) {
|
|
2385
|
-
|
|
2404
|
+
if (!isInteractiveMode()) {
|
|
2405
|
+
console.error(" \u26A0\uFE0F Failed to save cache");
|
|
2406
|
+
}
|
|
2386
2407
|
}
|
|
2387
2408
|
}
|
|
2388
2409
|
/**
|
|
@@ -2392,6 +2413,57 @@ var IncrementalScanner = class {
|
|
|
2392
2413
|
computeHash(content) {
|
|
2393
2414
|
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
2394
2415
|
}
|
|
2416
|
+
/**
|
|
2417
|
+
* Fast pre-check using file stats (mtime + size)
|
|
2418
|
+
* Returns true if file might have changed (needs hash check)
|
|
2419
|
+
* Returns false if file definitely hasn't changed (skip hash)
|
|
2420
|
+
* O(1) - just a stat call, no file read
|
|
2421
|
+
*/
|
|
2422
|
+
async fastPreCheck(filePath) {
|
|
2423
|
+
const cached = this.cache.files[filePath];
|
|
2424
|
+
if (!cached || cached.mtime === void 0 || cached.size === void 0) {
|
|
2425
|
+
try {
|
|
2426
|
+
const stats = await stat(filePath);
|
|
2427
|
+
return { changed: true, stats: { mtime: stats.mtimeMs, size: stats.size } };
|
|
2428
|
+
} catch {
|
|
2429
|
+
return { changed: true };
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
try {
|
|
2433
|
+
const stats = await stat(filePath);
|
|
2434
|
+
const currentMtime = stats.mtimeMs;
|
|
2435
|
+
const currentSize = stats.size;
|
|
2436
|
+
if (currentMtime === cached.mtime && currentSize === cached.size) {
|
|
2437
|
+
return { changed: false, stats: { mtime: currentMtime, size: currentSize } };
|
|
2438
|
+
}
|
|
2439
|
+
return { changed: true, stats: { mtime: currentMtime, size: currentSize } };
|
|
2440
|
+
} catch {
|
|
2441
|
+
return { changed: true };
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Check if this project is a git repo (cached)
|
|
2446
|
+
*/
|
|
2447
|
+
async checkIsGitRepo() {
|
|
2448
|
+
if (this.isGitRepoCache !== null) return this.isGitRepoCache;
|
|
2449
|
+
this.isGitRepoCache = await isGitRepo(this.projectRoot);
|
|
2450
|
+
return this.isGitRepoCache;
|
|
2451
|
+
}
|
|
2452
|
+
/**
|
|
2453
|
+
* Get files changed since last scan using git (fast path)
|
|
2454
|
+
* Returns null if not a git repo or git detection fails
|
|
2455
|
+
*/
|
|
2456
|
+
async getGitChangedFiles() {
|
|
2457
|
+
if (!await this.checkIsGitRepo()) return null;
|
|
2458
|
+
const lastScanTime = this.cache.lastFullScanTime;
|
|
2459
|
+
if (!lastScanTime) return null;
|
|
2460
|
+
try {
|
|
2461
|
+
const changedFiles = await getChangedFilesSinceTimestamp(this.projectRoot, lastScanTime);
|
|
2462
|
+
return changedFiles ? new Set(changedFiles) : null;
|
|
2463
|
+
} catch {
|
|
2464
|
+
return null;
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2395
2467
|
/**
|
|
2396
2468
|
* Check if a file has changed since last scan
|
|
2397
2469
|
* O(1) for cache lookup + O(n) for hash if needed
|
|
@@ -2412,17 +2484,39 @@ var IncrementalScanner = class {
|
|
|
2412
2484
|
/**
|
|
2413
2485
|
* Scan a single file incrementally
|
|
2414
2486
|
* Returns cached results if file unchanged
|
|
2487
|
+
*
|
|
2488
|
+
* Optimization layers:
|
|
2489
|
+
* 1. Fast pre-check (mtime + size) - O(1), avoids file read
|
|
2490
|
+
* 2. Hash comparison - O(n), only if pre-check fails
|
|
2415
2491
|
*/
|
|
2416
2492
|
async scanFile(filePath, forceRescan = false) {
|
|
2493
|
+
const cached = this.cache.files[filePath];
|
|
2494
|
+
if (!forceRescan && cached) {
|
|
2495
|
+
const preCheck = await this.fastPreCheck(filePath);
|
|
2496
|
+
if (!preCheck.changed) {
|
|
2497
|
+
return {
|
|
2498
|
+
vulnerabilities: cached.vulnerabilities,
|
|
2499
|
+
fromCache: true,
|
|
2500
|
+
symbolCount: cached.symbolCount
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2417
2504
|
let content;
|
|
2505
|
+
let fileStats;
|
|
2418
2506
|
try {
|
|
2507
|
+
const stats = await stat(filePath);
|
|
2508
|
+
fileStats = { mtime: stats.mtimeMs, size: stats.size };
|
|
2419
2509
|
content = await readFile5(filePath, "utf-8");
|
|
2420
2510
|
} catch {
|
|
2421
2511
|
return { vulnerabilities: [], fromCache: false, symbolCount: 0 };
|
|
2422
2512
|
}
|
|
2423
2513
|
const hash = this.computeHash(content);
|
|
2424
|
-
const cached = this.cache.files[filePath];
|
|
2425
2514
|
if (!forceRescan && cached && cached.hash === hash) {
|
|
2515
|
+
if (fileStats) {
|
|
2516
|
+
cached.mtime = fileStats.mtime;
|
|
2517
|
+
cached.size = fileStats.size;
|
|
2518
|
+
this.dirty = true;
|
|
2519
|
+
}
|
|
2426
2520
|
return {
|
|
2427
2521
|
vulnerabilities: cached.vulnerabilities,
|
|
2428
2522
|
fromCache: true,
|
|
@@ -2438,7 +2532,9 @@ var IncrementalScanner = class {
|
|
|
2438
2532
|
lastScanned: Date.now(),
|
|
2439
2533
|
lineCount: content.split("\n").length,
|
|
2440
2534
|
vulnerabilities,
|
|
2441
|
-
symbolCount: symbols.length
|
|
2535
|
+
symbolCount: symbols.length,
|
|
2536
|
+
mtime: fileStats?.mtime,
|
|
2537
|
+
size: fileStats?.size
|
|
2442
2538
|
};
|
|
2443
2539
|
this.dirty = true;
|
|
2444
2540
|
return {
|
|
@@ -2449,6 +2545,11 @@ var IncrementalScanner = class {
|
|
|
2449
2545
|
}
|
|
2450
2546
|
/**
|
|
2451
2547
|
* Scan multiple files with incremental caching
|
|
2548
|
+
*
|
|
2549
|
+
* Optimizations:
|
|
2550
|
+
* 1. Git-based change detection (when available) - only scan changed files
|
|
2551
|
+
* 2. Parallel file scanning with concurrency limit
|
|
2552
|
+
* 3. Fast pre-check (mtime + size) per file
|
|
2452
2553
|
*/
|
|
2453
2554
|
async scanFiles(filePaths, forceRescan = false, onFileProgress) {
|
|
2454
2555
|
const startTime = Date.now();
|
|
@@ -2462,9 +2563,20 @@ var IncrementalScanner = class {
|
|
|
2462
2563
|
for (const [path, state] of Object.entries(this.cache.files)) {
|
|
2463
2564
|
previousVulnerabilities.set(path, state.vulnerabilities);
|
|
2464
2565
|
}
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2566
|
+
let gitChangedFiles = null;
|
|
2567
|
+
if (!forceRescan) {
|
|
2568
|
+
gitChangedFiles = await this.getGitChangedFiles();
|
|
2569
|
+
if (gitChangedFiles && !isInteractiveMode()) {
|
|
2570
|
+
console.error(` \u26A1 Git detected ${gitChangedFiles.size} changed files`);
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
const scanResults = await this.parallelScan(
|
|
2574
|
+
filePaths,
|
|
2575
|
+
forceRescan,
|
|
2576
|
+
gitChangedFiles,
|
|
2577
|
+
onFileProgress
|
|
2578
|
+
);
|
|
2579
|
+
for (const { filePath, result } of scanResults) {
|
|
2468
2580
|
allVulnerabilities.push(...result.vulnerabilities);
|
|
2469
2581
|
if (result.fromCache) {
|
|
2470
2582
|
filesSkipped++;
|
|
@@ -2493,6 +2605,8 @@ var IncrementalScanner = class {
|
|
|
2493
2605
|
}
|
|
2494
2606
|
}
|
|
2495
2607
|
}
|
|
2608
|
+
this.cache.lastFullScanTime = Date.now();
|
|
2609
|
+
this.dirty = true;
|
|
2496
2610
|
const scanTimeMs = Date.now() - startTime;
|
|
2497
2611
|
const totalFiles = filesScanned + filesSkipped;
|
|
2498
2612
|
const cacheHitRate = totalFiles > 0 ? filesSkipped / totalFiles * 100 : 0;
|
|
@@ -2507,6 +2621,38 @@ var IncrementalScanner = class {
|
|
|
2507
2621
|
cacheHitRate
|
|
2508
2622
|
};
|
|
2509
2623
|
}
|
|
2624
|
+
/**
|
|
2625
|
+
* Parallel file scanning with concurrency limit
|
|
2626
|
+
* Processes SCAN_CONCURRENCY files at a time
|
|
2627
|
+
*/
|
|
2628
|
+
async parallelScan(filePaths, forceRescan, gitChangedFiles, onFileProgress) {
|
|
2629
|
+
const results = [];
|
|
2630
|
+
for (let i = 0; i < filePaths.length; i += SCAN_CONCURRENCY) {
|
|
2631
|
+
const batch = filePaths.slice(i, i + SCAN_CONCURRENCY);
|
|
2632
|
+
const batchPromises = batch.map(async (filePath) => {
|
|
2633
|
+
if (gitChangedFiles && !gitChangedFiles.has(filePath)) {
|
|
2634
|
+
const cached = this.cache.files[filePath];
|
|
2635
|
+
if (cached) {
|
|
2636
|
+
onFileProgress?.(filePath, true);
|
|
2637
|
+
return {
|
|
2638
|
+
filePath,
|
|
2639
|
+
result: {
|
|
2640
|
+
vulnerabilities: cached.vulnerabilities,
|
|
2641
|
+
fromCache: true,
|
|
2642
|
+
symbolCount: cached.symbolCount
|
|
2643
|
+
}
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
const result = await this.scanFile(filePath, forceRescan);
|
|
2648
|
+
onFileProgress?.(filePath, result.fromCache);
|
|
2649
|
+
return { filePath, result };
|
|
2650
|
+
});
|
|
2651
|
+
const batchResults = await Promise.all(batchPromises);
|
|
2652
|
+
results.push(...batchResults);
|
|
2653
|
+
}
|
|
2654
|
+
return results;
|
|
2655
|
+
}
|
|
2510
2656
|
/**
|
|
2511
2657
|
* Remove a file from the cache
|
|
2512
2658
|
*/
|
|
@@ -2645,6 +2791,7 @@ ${"\u2501".repeat(60)}
|
|
|
2645
2791
|
}
|
|
2646
2792
|
|
|
2647
2793
|
// src/utils/streaming.ts
|
|
2794
|
+
var shouldSuppressConsole = () => isInteractiveMode();
|
|
2648
2795
|
var StreamingManager = class {
|
|
2649
2796
|
listeners = /* @__PURE__ */ new Set();
|
|
2650
2797
|
progress = {
|
|
@@ -2675,7 +2822,9 @@ var StreamingManager = class {
|
|
|
2675
2822
|
try {
|
|
2676
2823
|
listener(update);
|
|
2677
2824
|
} catch (error) {
|
|
2678
|
-
|
|
2825
|
+
if (!shouldSuppressConsole()) {
|
|
2826
|
+
console.warn("Stream listener error:", error);
|
|
2827
|
+
}
|
|
2679
2828
|
}
|
|
2680
2829
|
});
|
|
2681
2830
|
}
|
|
@@ -2806,6 +2955,51 @@ var StreamingManager = class {
|
|
|
2806
2955
|
reportMemory(action, details) {
|
|
2807
2956
|
this.emit("memory", { action, details });
|
|
2808
2957
|
}
|
|
2958
|
+
// ============================================
|
|
2959
|
+
// Rich Content Events (for TUI panes)
|
|
2960
|
+
// ============================================
|
|
2961
|
+
/**
|
|
2962
|
+
* Report a code snippet for display
|
|
2963
|
+
*/
|
|
2964
|
+
reportSnippet(snippet) {
|
|
2965
|
+
this.emit("snippet", snippet);
|
|
2966
|
+
}
|
|
2967
|
+
/**
|
|
2968
|
+
* Report cost estimate from Moneybags
|
|
2969
|
+
*/
|
|
2970
|
+
reportCost(cost) {
|
|
2971
|
+
this.emit("cost_report", cost);
|
|
2972
|
+
}
|
|
2973
|
+
/**
|
|
2974
|
+
* Report production readiness score
|
|
2975
|
+
*/
|
|
2976
|
+
reportReadiness(readiness) {
|
|
2977
|
+
this.emit("readiness_report", readiness);
|
|
2978
|
+
}
|
|
2979
|
+
/**
|
|
2980
|
+
* Report semantic analysis results
|
|
2981
|
+
*/
|
|
2982
|
+
reportSemantic(semantic) {
|
|
2983
|
+
this.emit("semantic_report", semantic);
|
|
2984
|
+
}
|
|
2985
|
+
/**
|
|
2986
|
+
* Report attack surface analysis
|
|
2987
|
+
*/
|
|
2988
|
+
reportAttackSurface(attack) {
|
|
2989
|
+
this.emit("attack_surface", attack);
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* Report skill banner (ASCII art + quote)
|
|
2993
|
+
*/
|
|
2994
|
+
reportBanner(banner) {
|
|
2995
|
+
this.emit("skill_banner", banner);
|
|
2996
|
+
}
|
|
2997
|
+
/**
|
|
2998
|
+
* Report raw log entry
|
|
2999
|
+
*/
|
|
3000
|
+
reportRawLog(level, message) {
|
|
3001
|
+
this.emit("raw_log", { level, message, time: (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }) });
|
|
3002
|
+
}
|
|
2809
3003
|
};
|
|
2810
3004
|
function formatConsoleUpdate(update) {
|
|
2811
3005
|
if (isInteractiveMode()) {
|
|
@@ -3185,7 +3379,9 @@ var colors = {
|
|
|
3185
3379
|
alert: (s) => pc.bold(pc.red(s)),
|
|
3186
3380
|
// Interactive
|
|
3187
3381
|
selected: (s) => pc.bold(pc.magenta(s)),
|
|
3188
|
-
highlight: (s) => pc.bold(pc.white(s))
|
|
3382
|
+
highlight: (s) => pc.bold(pc.white(s)),
|
|
3383
|
+
// Additional
|
|
3384
|
+
yellow: (s) => pc.yellow(s)
|
|
3189
3385
|
};
|
|
3190
3386
|
var stripAnsi = (s) => s.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
|
|
3191
3387
|
var visibleLength = (s) => {
|
|
@@ -3271,7 +3467,27 @@ var InteractiveDashboard = class {
|
|
|
3271
3467
|
directories: 0,
|
|
3272
3468
|
recentChanges: []
|
|
3273
3469
|
},
|
|
3274
|
-
agents: {}
|
|
3470
|
+
agents: {},
|
|
3471
|
+
// Rich content state
|
|
3472
|
+
codeSnippets: /* @__PURE__ */ new Map(),
|
|
3473
|
+
costReport: null,
|
|
3474
|
+
readiness: null,
|
|
3475
|
+
semanticAnalysis: null,
|
|
3476
|
+
attackSurface: null,
|
|
3477
|
+
skillBanners: [],
|
|
3478
|
+
rawLog: [],
|
|
3479
|
+
rawLogPage: 0,
|
|
3480
|
+
scrollPositions: {
|
|
3481
|
+
overview: 0,
|
|
3482
|
+
issues: 0,
|
|
3483
|
+
agents: 0,
|
|
3484
|
+
files: 0,
|
|
3485
|
+
details: 0,
|
|
3486
|
+
costs: 0,
|
|
3487
|
+
readiness: 0,
|
|
3488
|
+
analysis: 0,
|
|
3489
|
+
rawlog: 0
|
|
3490
|
+
}
|
|
3275
3491
|
};
|
|
3276
3492
|
}
|
|
3277
3493
|
/**
|
|
@@ -3283,8 +3499,64 @@ var InteractiveDashboard = class {
|
|
|
3283
3499
|
this.addActivity("Guardian started");
|
|
3284
3500
|
this.setupKeyboardHandlers();
|
|
3285
3501
|
this.startUpdateLoop();
|
|
3502
|
+
this.registerOutputManagerCallbacks();
|
|
3286
3503
|
this.render();
|
|
3287
3504
|
}
|
|
3505
|
+
/**
|
|
3506
|
+
* Register callbacks with OutputManager for rich content
|
|
3507
|
+
*/
|
|
3508
|
+
registerOutputManagerCallbacks() {
|
|
3509
|
+
const outputManager = getOutputManager();
|
|
3510
|
+
outputManager.setMode("tui");
|
|
3511
|
+
outputManager.registerTUICallbacks({
|
|
3512
|
+
onBanner: (banner) => {
|
|
3513
|
+
this.state.skillBanners.unshift(banner);
|
|
3514
|
+
if (this.state.skillBanners.length > 5) {
|
|
3515
|
+
this.state.skillBanners.pop();
|
|
3516
|
+
}
|
|
3517
|
+
this.addActivity(`[BANNER] ${banner.skill}${banner.quote ? ": " + banner.quote.slice(0, 40) + "..." : ""}`);
|
|
3518
|
+
this.render();
|
|
3519
|
+
},
|
|
3520
|
+
onSnippet: (snippet) => {
|
|
3521
|
+
this.state.codeSnippets.set(this.state.selectedIssue, snippet);
|
|
3522
|
+
this.render();
|
|
3523
|
+
},
|
|
3524
|
+
onCost: (cost) => {
|
|
3525
|
+
this.state.costReport = cost;
|
|
3526
|
+
this.addActivity(`[COST] Fix: $${cost.fixNowCost} | Prod: $${cost.productionCost}`);
|
|
3527
|
+
this.render();
|
|
3528
|
+
},
|
|
3529
|
+
onReadiness: (readiness) => {
|
|
3530
|
+
this.state.readiness = readiness;
|
|
3531
|
+
this.addActivity(`[READY] Score: ${readiness.score}/100 - ${readiness.status.toUpperCase()}`);
|
|
3532
|
+
this.render();
|
|
3533
|
+
},
|
|
3534
|
+
onSemantic: (semantic) => {
|
|
3535
|
+
this.state.semanticAnalysis = semantic;
|
|
3536
|
+
this.addActivity(`[SEMANTIC] DataFlow: ${semantic.dataFlowIssues} | Race: ${semantic.raceConditions} | Auth: ${semantic.authIssues}`);
|
|
3537
|
+
this.render();
|
|
3538
|
+
},
|
|
3539
|
+
onAttack: (attack) => {
|
|
3540
|
+
this.state.attackSurface = attack;
|
|
3541
|
+
this.addActivity(`[ATTACK] ${attack.totalEndpoints} endpoints (${attack.unprotected} unprotected)`);
|
|
3542
|
+
this.render();
|
|
3543
|
+
},
|
|
3544
|
+
onActivity: (message) => {
|
|
3545
|
+
this.addActivity(message);
|
|
3546
|
+
this.render();
|
|
3547
|
+
},
|
|
3548
|
+
onLog: (level, message) => {
|
|
3549
|
+
const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
3550
|
+
this.state.rawLog.unshift({ time, level, message });
|
|
3551
|
+
if (this.state.rawLog.length > 500) {
|
|
3552
|
+
this.state.rawLog.pop();
|
|
3553
|
+
}
|
|
3554
|
+
if (this.state.view === "rawlog") {
|
|
3555
|
+
this.render();
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
});
|
|
3559
|
+
}
|
|
3288
3560
|
/**
|
|
3289
3561
|
* Stop the dashboard
|
|
3290
3562
|
*/
|
|
@@ -3294,6 +3566,9 @@ var InteractiveDashboard = class {
|
|
|
3294
3566
|
clearInterval(this.updateInterval);
|
|
3295
3567
|
}
|
|
3296
3568
|
this.rl.close();
|
|
3569
|
+
const outputManager = getOutputManager();
|
|
3570
|
+
outputManager.clearTUICallbacks();
|
|
3571
|
+
outputManager.setMode("console");
|
|
3297
3572
|
process.stdout.write("\x1B[2J\x1B[H\x1B[?25h");
|
|
3298
3573
|
}
|
|
3299
3574
|
/**
|
|
@@ -3404,6 +3679,53 @@ var InteractiveDashboard = class {
|
|
|
3404
3679
|
this.addActivity(`[~] Learning: ${update.data.details || "analyzing patterns"}`);
|
|
3405
3680
|
}
|
|
3406
3681
|
break;
|
|
3682
|
+
// === Rich Content Events ===
|
|
3683
|
+
case "snippet":
|
|
3684
|
+
this.state.codeSnippets.set(this.state.selectedIssue, update.data);
|
|
3685
|
+
break;
|
|
3686
|
+
case "cost_report":
|
|
3687
|
+
this.state.costReport = update.data;
|
|
3688
|
+
this.addActivity(`[COST] Fix now: $${update.data.fixNowCost} | Production: $${update.data.productionCost}`);
|
|
3689
|
+
break;
|
|
3690
|
+
case "readiness_report":
|
|
3691
|
+
this.state.readiness = update.data;
|
|
3692
|
+
this.addActivity(`[READINESS] ${update.data.score}/100 - ${update.data.status}`);
|
|
3693
|
+
break;
|
|
3694
|
+
case "semantic_report":
|
|
3695
|
+
this.state.semanticAnalysis = update.data;
|
|
3696
|
+
{
|
|
3697
|
+
const sem = update.data;
|
|
3698
|
+
const total = sem.dataFlowIssues + sem.raceConditions + sem.authIssues;
|
|
3699
|
+
if (total > 0) {
|
|
3700
|
+
this.addActivity(`[SEMANTIC] ${total} issues (${sem.dataFlowIssues} dataflow, ${sem.raceConditions} race, ${sem.authIssues} auth)`);
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
break;
|
|
3704
|
+
case "attack_surface":
|
|
3705
|
+
this.state.attackSurface = update.data;
|
|
3706
|
+
{
|
|
3707
|
+
const atk = update.data;
|
|
3708
|
+
this.addActivity(`[ATTACK] ${atk.totalEndpoints} endpoints, ${atk.unprotected} unprotected, risk ${atk.riskScore}/100`);
|
|
3709
|
+
}
|
|
3710
|
+
break;
|
|
3711
|
+
case "skill_banner":
|
|
3712
|
+
const banner = update.data;
|
|
3713
|
+
this.state.skillBanners.unshift(banner);
|
|
3714
|
+
if (this.state.skillBanners.length > 5) {
|
|
3715
|
+
this.state.skillBanners.pop();
|
|
3716
|
+
}
|
|
3717
|
+
this.addActivity(`[SKILL] ${banner.skill}${banner.quote ? ': "' + banner.quote.slice(0, 30) + '..."' : ""}`);
|
|
3718
|
+
break;
|
|
3719
|
+
case "raw_log":
|
|
3720
|
+
this.state.rawLog.unshift({
|
|
3721
|
+
time: update.data.time || new Date(update.timestamp).toLocaleTimeString("en-US", { hour12: false }),
|
|
3722
|
+
level: update.data.level,
|
|
3723
|
+
message: update.data.message
|
|
3724
|
+
});
|
|
3725
|
+
if (this.state.rawLog.length > 500) {
|
|
3726
|
+
this.state.rawLog.pop();
|
|
3727
|
+
}
|
|
3728
|
+
break;
|
|
3407
3729
|
}
|
|
3408
3730
|
if (this.isActive) {
|
|
3409
3731
|
this.render();
|
|
@@ -3477,6 +3799,30 @@ var InteractiveDashboard = class {
|
|
|
3477
3799
|
case "?":
|
|
3478
3800
|
this.showHelp();
|
|
3479
3801
|
break;
|
|
3802
|
+
// === NEW VIEW SHORTCUTS ===
|
|
3803
|
+
case "d":
|
|
3804
|
+
if (this.state.scanComplete && this.state.issues.length > 0) {
|
|
3805
|
+
this.goToView("details");
|
|
3806
|
+
}
|
|
3807
|
+
break;
|
|
3808
|
+
case "m":
|
|
3809
|
+
if (this.state.costReport) {
|
|
3810
|
+
this.goToView("costs");
|
|
3811
|
+
}
|
|
3812
|
+
break;
|
|
3813
|
+
case "r":
|
|
3814
|
+
if (this.state.readiness) {
|
|
3815
|
+
this.goToView("readiness");
|
|
3816
|
+
}
|
|
3817
|
+
break;
|
|
3818
|
+
case "a":
|
|
3819
|
+
if (this.state.semanticAnalysis || this.state.attackSurface) {
|
|
3820
|
+
this.goToView("analysis");
|
|
3821
|
+
}
|
|
3822
|
+
break;
|
|
3823
|
+
case "l":
|
|
3824
|
+
this.goToView("rawlog");
|
|
3825
|
+
break;
|
|
3480
3826
|
}
|
|
3481
3827
|
this.render();
|
|
3482
3828
|
});
|
|
@@ -3629,6 +3975,22 @@ var InteractiveDashboard = class {
|
|
|
3629
3975
|
case "files":
|
|
3630
3976
|
this.renderFilesView(width);
|
|
3631
3977
|
break;
|
|
3978
|
+
// === NEW VIEWS ===
|
|
3979
|
+
case "details":
|
|
3980
|
+
this.renderDetailsView(width, height);
|
|
3981
|
+
break;
|
|
3982
|
+
case "costs":
|
|
3983
|
+
this.renderCostsView(width, height);
|
|
3984
|
+
break;
|
|
3985
|
+
case "readiness":
|
|
3986
|
+
this.renderReadinessView(width, height);
|
|
3987
|
+
break;
|
|
3988
|
+
case "analysis":
|
|
3989
|
+
this.renderAnalysisView(width, height);
|
|
3990
|
+
break;
|
|
3991
|
+
case "rawlog":
|
|
3992
|
+
this.renderRawLogView(width, height);
|
|
3993
|
+
break;
|
|
3632
3994
|
}
|
|
3633
3995
|
}
|
|
3634
3996
|
this.renderFooter(width);
|
|
@@ -3966,6 +4328,240 @@ var InteractiveDashboard = class {
|
|
|
3966
4328
|
this.renderLine("", width);
|
|
3967
4329
|
}
|
|
3968
4330
|
}
|
|
4331
|
+
// ============================================
|
|
4332
|
+
// NEW VIEWS
|
|
4333
|
+
// ============================================
|
|
4334
|
+
/**
|
|
4335
|
+
* Render details view - selected issue with code snippet
|
|
4336
|
+
*/
|
|
4337
|
+
renderDetailsView(width, _height) {
|
|
4338
|
+
const sectionBorder = colors.border(box.leftT + this.line(width - 2) + box.rightT);
|
|
4339
|
+
const filteredIssues = this.getFilteredIssues();
|
|
4340
|
+
const issue = filteredIssues[this.state.selectedIssue];
|
|
4341
|
+
if (!issue) {
|
|
4342
|
+
this.renderLine(" " + colors.dim("No issue selected. Press [b] to go back."), width);
|
|
4343
|
+
return;
|
|
4344
|
+
}
|
|
4345
|
+
const severityColor = issue.severity === "critical" ? colors.critical : issue.severity === "serious" ? colors.serious : issue.severity === "moderate" ? colors.moderate : colors.low;
|
|
4346
|
+
this.renderLine(" " + colors.header("ISSUE DETAILS") + " " + severityColor(`[${issue.severity.toUpperCase()}]`), width);
|
|
4347
|
+
this.bufferLine(sectionBorder);
|
|
4348
|
+
this.renderLine(" " + colors.dim("File:") + " " + issue.file, width);
|
|
4349
|
+
this.renderLine(" " + colors.dim("Line:") + " " + (issue.line || "?") + " " + colors.dim("Scout:") + " " + (issue.agent || "unknown"), width);
|
|
4350
|
+
this.renderLine("", width);
|
|
4351
|
+
this.renderLine(" " + colors.header("Issue"), width);
|
|
4352
|
+
const issueLines = this.wrapText(issue.issue, width - 8);
|
|
4353
|
+
for (const line of issueLines.slice(0, 4)) {
|
|
4354
|
+
this.renderLine(" " + severityColor(line), width);
|
|
4355
|
+
}
|
|
4356
|
+
this.renderLine("", width);
|
|
4357
|
+
this.bufferLine(sectionBorder);
|
|
4358
|
+
this.renderLine(" " + colors.header("CODE SNIPPET"), width);
|
|
4359
|
+
const snippet = this.state.codeSnippets.get(this.state.selectedIssue);
|
|
4360
|
+
if (snippet) {
|
|
4361
|
+
this.renderLine(" " + colors.dim(this.line(width - 6)), width);
|
|
4362
|
+
const maxLines = Math.min(snippet.lines.length, 10);
|
|
4363
|
+
for (let i = 0; i < maxLines; i++) {
|
|
4364
|
+
const lineNum = snippet.startLine + i;
|
|
4365
|
+
const isHighlight = lineNum === snippet.highlightLine;
|
|
4366
|
+
const prefix = isHighlight ? colors.critical("\u2192") : " ";
|
|
4367
|
+
const lineNumStr = colors.dim(lineNum.toString().padStart(4));
|
|
4368
|
+
const lineContent = isHighlight ? colors.yellow(snippet.lines[i] || "") : snippet.lines[i] || "";
|
|
4369
|
+
this.renderLine(" " + prefix + " " + lineNumStr + " " + colors.dim("\u2502") + " " + lineContent, width);
|
|
4370
|
+
}
|
|
4371
|
+
if (snippet.lines.length > 10) {
|
|
4372
|
+
this.renderLine(" " + colors.dim(` ... ${snippet.lines.length - 10} more lines`), width);
|
|
4373
|
+
}
|
|
4374
|
+
} else {
|
|
4375
|
+
this.renderLine(" " + colors.dim("No code snippet available. Snippet is loaded when issue is selected."), width);
|
|
4376
|
+
}
|
|
4377
|
+
this.renderLine("", width);
|
|
4378
|
+
this.bufferLine(sectionBorder);
|
|
4379
|
+
this.renderLine(" " + colors.success("SUGGESTED FIX"), width);
|
|
4380
|
+
const fixLines = this.wrapText(issue.fix, width - 8);
|
|
4381
|
+
for (const line of fixLines.slice(0, 4)) {
|
|
4382
|
+
this.renderLine(" " + colors.success(line), width);
|
|
4383
|
+
}
|
|
4384
|
+
if (issue.cwe || issue.owasp || issue.effort) {
|
|
4385
|
+
this.renderLine("", width);
|
|
4386
|
+
this.bufferLine(sectionBorder);
|
|
4387
|
+
this.renderLine(" " + colors.header("METADATA"), width);
|
|
4388
|
+
if (issue.cwe) this.renderLine(" " + colors.dim("CWE:") + " " + issue.cwe, width);
|
|
4389
|
+
if (issue.owasp) this.renderLine(" " + colors.dim("OWASP:") + " " + issue.owasp, width);
|
|
4390
|
+
if (issue.effort) this.renderLine(" " + colors.dim("Effort:") + " " + issue.effort, width);
|
|
4391
|
+
}
|
|
4392
|
+
}
|
|
4393
|
+
/**
|
|
4394
|
+
* Render costs view - Moneybags cost breakdown
|
|
4395
|
+
*/
|
|
4396
|
+
renderCostsView(width, _height) {
|
|
4397
|
+
const sectionBorder = colors.border(box.leftT + this.line(width - 2) + box.rightT);
|
|
4398
|
+
this.renderLine(" " + colors.header("\u{1F4B0} COST ANALYSIS") + " " + colors.dim("(Moneybags)"), width);
|
|
4399
|
+
this.bufferLine(sectionBorder);
|
|
4400
|
+
const cost = this.state.costReport;
|
|
4401
|
+
if (!cost) {
|
|
4402
|
+
this.renderLine(" " + colors.dim("No cost data available. Run a full scan to get cost estimates."), width);
|
|
4403
|
+
this.renderLine("", width);
|
|
4404
|
+
return;
|
|
4405
|
+
}
|
|
4406
|
+
const fmt = (n) => n >= 1e6 ? `$${(n / 1e6).toFixed(2)}M` : n >= 1e3 ? `$${(n / 1e3).toFixed(1)}k` : `$${n}`;
|
|
4407
|
+
this.renderLine("", width);
|
|
4408
|
+
this.renderLine(" " + colors.header("SUMMARY"), width);
|
|
4409
|
+
this.renderLine(" " + colors.dim(this.line(width - 6)), width);
|
|
4410
|
+
const fixNowLabel = colors.success("Fix Now Cost:");
|
|
4411
|
+
const prodLabel = colors.critical("Production Cost:");
|
|
4412
|
+
const savingsLabel = colors.running("Potential Savings:");
|
|
4413
|
+
this.renderLine(" " + fixNowLabel + " " + colors.success(fmt(cost.fixNowCost)), width);
|
|
4414
|
+
this.renderLine(" " + prodLabel + " " + colors.critical(fmt(cost.productionCost)), width);
|
|
4415
|
+
this.renderLine(" " + savingsLabel + " " + colors.running(fmt(cost.savings)), width);
|
|
4416
|
+
this.renderLine("", width);
|
|
4417
|
+
const roi = cost.fixNowCost > 0 ? Math.round(cost.savings / cost.fixNowCost * 100) : 0;
|
|
4418
|
+
this.renderLine(" " + colors.dim("ROI:") + " " + colors.highlight(`${roi}%`) + " " + colors.dim("return on fixing these issues now"), width);
|
|
4419
|
+
this.renderLine("", width);
|
|
4420
|
+
if (cost.perIssue && cost.perIssue.length > 0) {
|
|
4421
|
+
this.bufferLine(sectionBorder);
|
|
4422
|
+
this.renderLine(" " + colors.header("PER-ISSUE BREAKDOWN"), width);
|
|
4423
|
+
this.renderLine(" " + colors.dim(this.line(width - 6)), width);
|
|
4424
|
+
const maxRows = Math.min(cost.perIssue.length, 8);
|
|
4425
|
+
for (let i = 0; i < maxRows; i++) {
|
|
4426
|
+
const item = cost.perIssue[i];
|
|
4427
|
+
if (!item) continue;
|
|
4428
|
+
const issueName = item.issue.slice(0, 40) + (item.issue.length > 40 ? "..." : "");
|
|
4429
|
+
this.renderLine(" " + colors.dim("\u2022") + " " + issueName.padEnd(45) + " " + colors.serious(fmt(item.cost)), width);
|
|
4430
|
+
}
|
|
4431
|
+
if (cost.perIssue.length > 8) {
|
|
4432
|
+
this.renderLine(" " + colors.dim(`... and ${cost.perIssue.length - 8} more`), width);
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
this.renderLine("", width);
|
|
4436
|
+
}
|
|
4437
|
+
/**
|
|
4438
|
+
* Render readiness view - Production readiness checklist
|
|
4439
|
+
*/
|
|
4440
|
+
renderReadinessView(width, _height) {
|
|
4441
|
+
const sectionBorder = colors.border(box.leftT + this.line(width - 2) + box.rightT);
|
|
4442
|
+
this.renderLine(" " + colors.header("\u{1F4CA} PRODUCTION READINESS"), width);
|
|
4443
|
+
this.bufferLine(sectionBorder);
|
|
4444
|
+
const readiness = this.state.readiness;
|
|
4445
|
+
if (!readiness) {
|
|
4446
|
+
this.renderLine(" " + colors.dim("No readiness data. Run production-ready agent for assessment."), width);
|
|
4447
|
+
return;
|
|
4448
|
+
}
|
|
4449
|
+
const statusColor = readiness.status === "ready" ? colors.success : readiness.status === "caution" ? colors.running : colors.critical;
|
|
4450
|
+
const statusIcon = readiness.status === "ready" ? "\u2713" : readiness.status === "caution" ? "~" : "\u2717";
|
|
4451
|
+
this.renderLine("", width);
|
|
4452
|
+
this.renderLine(" " + colors.header("STATUS"), width);
|
|
4453
|
+
this.renderLine(" " + colors.dim(this.line(width - 6)), width);
|
|
4454
|
+
this.renderLine(" " + statusColor(`[${statusIcon}] ${readiness.status.toUpperCase()}`), width);
|
|
4455
|
+
this.renderLine("", width);
|
|
4456
|
+
const scoreWidth = 40;
|
|
4457
|
+
const scoreFilled = Math.round(readiness.score / 100 * scoreWidth);
|
|
4458
|
+
const scoreEmpty = scoreWidth - scoreFilled;
|
|
4459
|
+
const scoreBar = statusColor("\u2588".repeat(scoreFilled)) + colors.dim("\u2591".repeat(scoreEmpty));
|
|
4460
|
+
this.renderLine(" " + colors.dim("Score:") + " " + scoreBar + " " + statusColor(`${readiness.score}/100`), width);
|
|
4461
|
+
this.renderLine("", width);
|
|
4462
|
+
this.bufferLine(sectionBorder);
|
|
4463
|
+
this.renderLine(" " + colors.header("REQUIREMENTS"), width);
|
|
4464
|
+
this.renderLine(" " + colors.dim(this.line(width - 6)), width);
|
|
4465
|
+
this.renderLine(" " + colors.dim("Met:") + " " + colors.highlight(`${readiness.requirementsMet}/${readiness.total}`), width);
|
|
4466
|
+
this.renderLine("", width);
|
|
4467
|
+
if (readiness.requirements && readiness.requirements.length > 0) {
|
|
4468
|
+
for (const req of readiness.requirements.slice(0, 10)) {
|
|
4469
|
+
const icon = req.met ? colors.success("\u2713") : colors.critical("\u2717");
|
|
4470
|
+
const text = req.met ? colors.dim(req.name) : req.name;
|
|
4471
|
+
this.renderLine(" " + icon + " " + text, width);
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
this.renderLine("", width);
|
|
4475
|
+
}
|
|
4476
|
+
/**
|
|
4477
|
+
* Render analysis view - Semantic + Attack Surface
|
|
4478
|
+
*/
|
|
4479
|
+
renderAnalysisView(width, _height) {
|
|
4480
|
+
const sectionBorder = colors.border(box.leftT + this.line(width - 2) + box.rightT);
|
|
4481
|
+
this.renderLine(" " + colors.header("\u{1F50D} ANALYSIS REPORT"), width);
|
|
4482
|
+
this.bufferLine(sectionBorder);
|
|
4483
|
+
const semantic = this.state.semanticAnalysis;
|
|
4484
|
+
if (semantic) {
|
|
4485
|
+
this.renderLine(" " + colors.header("SEMANTIC ANALYSIS"), width);
|
|
4486
|
+
this.renderLine(" " + colors.dim(this.line(width - 6)), width);
|
|
4487
|
+
if (semantic.dataFlowIssues > 0) {
|
|
4488
|
+
this.renderLine(" " + colors.critical("[!]") + " " + colors.critical(`${semantic.dataFlowIssues} Data Flow Vulnerabilities`), width);
|
|
4489
|
+
this.renderLine(" " + colors.dim("Untrusted input reaching dangerous sinks"), width);
|
|
4490
|
+
}
|
|
4491
|
+
if (semantic.raceConditions > 0) {
|
|
4492
|
+
this.renderLine(" " + colors.running("[~]") + " " + colors.running(`${semantic.raceConditions} Race Conditions`), width);
|
|
4493
|
+
this.renderLine(" " + colors.dim("TOCTOU, double-spend risks"), width);
|
|
4494
|
+
}
|
|
4495
|
+
if (semantic.authIssues > 0) {
|
|
4496
|
+
this.renderLine(" " + colors.critical("[!]") + " " + colors.critical(`${semantic.authIssues} Authentication Issues`), width);
|
|
4497
|
+
this.renderLine(" " + colors.dim("Missing or bypassable auth"), width);
|
|
4498
|
+
}
|
|
4499
|
+
const total = semantic.dataFlowIssues + semantic.raceConditions + semantic.authIssues;
|
|
4500
|
+
if (total === 0) {
|
|
4501
|
+
this.renderLine(" " + colors.success("\u2713 No semantic issues detected"), width);
|
|
4502
|
+
}
|
|
4503
|
+
this.renderLine("", width);
|
|
4504
|
+
}
|
|
4505
|
+
const attack = this.state.attackSurface;
|
|
4506
|
+
if (attack) {
|
|
4507
|
+
this.bufferLine(sectionBorder);
|
|
4508
|
+
this.renderLine(" " + colors.header("ATTACK SURFACE"), width);
|
|
4509
|
+
this.renderLine(" " + colors.dim(this.line(width - 6)), width);
|
|
4510
|
+
this.renderLine(" " + colors.dim("Endpoints:") + " " + colors.highlight(attack.totalEndpoints.toString()), width);
|
|
4511
|
+
if (attack.unprotected > 0) {
|
|
4512
|
+
this.renderLine(" " + colors.critical("Unprotected:") + " " + colors.critical(attack.unprotected.toString()), width);
|
|
4513
|
+
} else {
|
|
4514
|
+
this.renderLine(" " + colors.success("Unprotected:") + " " + colors.success("0"), width);
|
|
4515
|
+
}
|
|
4516
|
+
const riskWidth = 30;
|
|
4517
|
+
const riskFilled = Math.round(attack.riskScore / 100 * riskWidth);
|
|
4518
|
+
const riskEmpty = riskWidth - riskFilled;
|
|
4519
|
+
const riskColor = attack.riskScore > 70 ? colors.critical : attack.riskScore > 40 ? colors.running : colors.success;
|
|
4520
|
+
const riskBar = riskColor("\u2588".repeat(riskFilled)) + colors.dim("\u2591".repeat(riskEmpty));
|
|
4521
|
+
this.renderLine(" " + colors.dim("Risk Score:") + " " + riskBar + " " + riskColor(`${attack.riskScore}/100`), width);
|
|
4522
|
+
this.renderLine("", width);
|
|
4523
|
+
if (attack.endpoints && attack.endpoints.length > 0) {
|
|
4524
|
+
this.renderLine(" " + colors.dim("Top Endpoints:"), width);
|
|
4525
|
+
for (const ep of attack.endpoints.slice(0, 5)) {
|
|
4526
|
+
const authIcon = ep.auth ? colors.success("\u{1F512}") : colors.critical("\u{1F513}");
|
|
4527
|
+
const method = colors.dim(ep.method.padEnd(6));
|
|
4528
|
+
const path = ep.sensitive ? colors.serious(ep.path) : ep.path;
|
|
4529
|
+
this.renderLine(" " + authIcon + " " + method + " " + path, width);
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
if (!semantic && !attack) {
|
|
4534
|
+
this.renderLine(" " + colors.dim("No analysis data available. Run a full scan for semantic analysis."), width);
|
|
4535
|
+
}
|
|
4536
|
+
this.renderLine("", width);
|
|
4537
|
+
}
|
|
4538
|
+
/**
|
|
4539
|
+
* Render raw log view - All console output
|
|
4540
|
+
*/
|
|
4541
|
+
renderRawLogView(width, height) {
|
|
4542
|
+
const sectionBorder = colors.border(box.leftT + this.line(width - 2) + box.rightT);
|
|
4543
|
+
const pageSize = Math.max(10, height - 15);
|
|
4544
|
+
const totalPages = Math.max(1, Math.ceil(this.state.rawLog.length / pageSize));
|
|
4545
|
+
this.renderLine(" " + colors.header("\u{1F4CB} RAW LOG") + " " + colors.dim(`Page ${this.state.rawLogPage + 1}/${totalPages}`) + " " + colors.highlight(`${this.state.rawLog.length}`) + colors.dim(" entries"), width);
|
|
4546
|
+
this.bufferLine(sectionBorder);
|
|
4547
|
+
if (this.state.rawLog.length === 0) {
|
|
4548
|
+
this.renderLine(" " + colors.dim("No log entries yet."), width);
|
|
4549
|
+
this.renderLine("", width);
|
|
4550
|
+
return;
|
|
4551
|
+
}
|
|
4552
|
+
const startIdx = this.state.rawLogPage * pageSize;
|
|
4553
|
+
const logs = this.state.rawLog.slice(startIdx, startIdx + pageSize);
|
|
4554
|
+
for (const entry of logs) {
|
|
4555
|
+
const levelColor = entry.level === "error" ? colors.critical : entry.level === "warn" ? colors.running : entry.level === "info" ? colors.success : colors.dim;
|
|
4556
|
+
const levelStr = levelColor(`[${entry.level.toUpperCase().padEnd(5)}]`);
|
|
4557
|
+
const timeStr = colors.dim(entry.time);
|
|
4558
|
+
const message = entry.message.slice(0, width - 25);
|
|
4559
|
+
this.renderLine(" " + timeStr + " " + levelStr + " " + message, width);
|
|
4560
|
+
}
|
|
4561
|
+
for (let i = logs.length; i < pageSize; i++) {
|
|
4562
|
+
this.renderLine("", width);
|
|
4563
|
+
}
|
|
4564
|
+
}
|
|
3969
4565
|
/**
|
|
3970
4566
|
* Render footer with controls
|
|
3971
4567
|
*/
|
|
@@ -3974,25 +4570,44 @@ var InteractiveDashboard = class {
|
|
|
3974
4570
|
this.bufferLine(bottomBorder);
|
|
3975
4571
|
const key = (k) => colors.border("[") + colors.highlight(k) + colors.border("]");
|
|
3976
4572
|
const hint = (k, desc) => key(k) + colors.dim(` ${desc}`);
|
|
4573
|
+
const paneHints = [];
|
|
4574
|
+
if (this.state.issues.length > 0) paneHints.push(hint("d", "Details"));
|
|
4575
|
+
if (this.state.costReport) paneHints.push(hint("m", "Cost"));
|
|
4576
|
+
if (this.state.readiness) paneHints.push(hint("r", "Ready"));
|
|
4577
|
+
if (this.state.semanticAnalysis || this.state.attackSurface) paneHints.push(hint("a", "Analysis"));
|
|
4578
|
+
paneHints.push(hint("l", "Log"));
|
|
4579
|
+
const paneStr = paneHints.length > 0 ? " " + paneHints.join(" ") : "";
|
|
3977
4580
|
if (!this.state.scanComplete) {
|
|
3978
|
-
this.bufferLine(" " + hint("q", "Quit") + " " + hint("?", "Help"));
|
|
4581
|
+
this.bufferLine(" " + hint("l", "Log") + " " + hint("q", "Quit") + " " + hint("?", "Help"));
|
|
4582
|
+
} else if (this.state.view === "details" || this.state.view === "costs" || this.state.view === "readiness" || this.state.view === "analysis" || this.state.view === "rawlog") {
|
|
4583
|
+
this.bufferLine(" " + hint("b", "Back") + " " + hint("n/p", "Pages") + " " + hint("Tab", "Main") + " " + hint("?", "Help") + " " + hint("q", "Quit"));
|
|
3979
4584
|
} else if (this.state.view === "agents") {
|
|
3980
|
-
this.bufferLine(" " + hint("j/k", "Select") + " " + hint("Enter", "View
|
|
3981
|
-
} else if (this.state.view === "issues" && this.state.previousView === "agents") {
|
|
3982
|
-
this.bufferLine(" " + hint("b", "Back") + " " + hint("j/k", "Nav") + " " + hint("Enter", "Details") + " " + hint("1-4", "Severity") + " " + hint("f", "Search") + " " + hint("?", "Help") + " " + hint("q", "Quit"));
|
|
4585
|
+
this.bufferLine(" " + hint("j/k", "Select") + " " + hint("Enter", "View") + " " + hint("n/p", "Pages") + " " + hint("Tab", "Views") + paneStr + " " + hint("?", "Help") + " " + hint("q", "Quit"));
|
|
3983
4586
|
} else if (this.state.view === "issues") {
|
|
3984
|
-
this.bufferLine(" " + hint("j/k", "Nav") + " " + hint("Enter", "Details") + " " + hint("1-4", "
|
|
4587
|
+
this.bufferLine(" " + hint("j/k", "Nav") + " " + hint("Enter", "Details") + " " + hint("1-4", "Sev") + " " + hint("f", "Find") + " " + hint("Tab", "Views") + paneStr + " " + hint("?", "Help") + " " + hint("q", "Quit"));
|
|
3985
4588
|
} else {
|
|
3986
|
-
this.bufferLine(" " + hint("Tab", "Views") + " " + hint("n/p", "Pages") + " " + hint("?", "Help") + " " + hint("q", "Quit"));
|
|
4589
|
+
this.bufferLine(" " + hint("Tab", "Views") + " " + hint("n/p", "Pages") + paneStr + " " + hint("?", "Help") + " " + hint("q", "Quit"));
|
|
3987
4590
|
}
|
|
3988
4591
|
}
|
|
3989
4592
|
// Helper methods
|
|
3990
4593
|
switchView() {
|
|
3991
4594
|
if (!this.state.scanComplete) return;
|
|
3992
|
-
const
|
|
3993
|
-
const currentIndex =
|
|
3994
|
-
|
|
3995
|
-
|
|
4595
|
+
const mainViews = ["overview", "issues", "agents", "files"];
|
|
4596
|
+
const currentIndex = mainViews.indexOf(this.state.view);
|
|
4597
|
+
if (currentIndex >= 0) {
|
|
4598
|
+
const nextIndex = (currentIndex + 1) % mainViews.length;
|
|
4599
|
+
this.state.view = mainViews[nextIndex] ?? "overview";
|
|
4600
|
+
} else {
|
|
4601
|
+
this.state.view = "overview";
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
/**
|
|
4605
|
+
* Navigate to a specific view
|
|
4606
|
+
*/
|
|
4607
|
+
goToView(view) {
|
|
4608
|
+
if (!this.state.scanComplete && view !== "rawlog") return;
|
|
4609
|
+
this.state.previousView = this.state.view;
|
|
4610
|
+
this.state.view = view;
|
|
3996
4611
|
}
|
|
3997
4612
|
navigateUp() {
|
|
3998
4613
|
if (this.state.view === "issues") {
|
|
@@ -4233,11 +4848,18 @@ var InteractiveDashboard = class {
|
|
|
4233
4848
|
pLine(" " + colors.brand("TRIE GUARDIAN") + " " + colors.dim("-") + " " + colors.header("HELP"));
|
|
4234
4849
|
console.log(colors.border(box.leftT + this.line(w - 2) + box.rightT));
|
|
4235
4850
|
pLine(" " + colors.header("Navigation"));
|
|
4236
|
-
keyLine("Tab", "
|
|
4851
|
+
keyLine("Tab", "Cycle main views (overview/issues/scouts/files)");
|
|
4237
4852
|
keyLine("j/k", "Navigate up/down in lists");
|
|
4238
4853
|
keyLine("n/p", "Next/previous page");
|
|
4239
4854
|
keyLine("Enter", "View details / drill into scout");
|
|
4240
|
-
keyLine("b", "Go back
|
|
4855
|
+
keyLine("b", "Go back to previous view");
|
|
4856
|
+
pLine("");
|
|
4857
|
+
pLine(" " + colors.header("Panes"));
|
|
4858
|
+
keyLine("d", "Issue Details with code snippet");
|
|
4859
|
+
keyLine("m", "Cost Analysis (Moneybags)");
|
|
4860
|
+
keyLine("r", "Production Readiness score");
|
|
4861
|
+
keyLine("a", "Analysis (Semantic + Attack Surface)");
|
|
4862
|
+
keyLine("l", "Raw Log viewer");
|
|
4241
4863
|
pLine("");
|
|
4242
4864
|
pLine(" " + colors.header("Filters"));
|
|
4243
4865
|
keyLine("1-4", "Filter by severity (1=critical, 4=low)");
|
|
@@ -5585,6 +6207,13 @@ var TrieScanTool = class {
|
|
|
5585
6207
|
const dashboard = interactiveEnabled ? args?.dashboard instanceof InteractiveDashboard ? args.dashboard : new InteractiveDashboard() : void 0;
|
|
5586
6208
|
if (interactiveEnabled) {
|
|
5587
6209
|
setInteractiveMode(true);
|
|
6210
|
+
getOutputManager().setMode("tui");
|
|
6211
|
+
} else {
|
|
6212
|
+
const isMCPContext = !process.stdout.isTTY;
|
|
6213
|
+
getOutputManager().setMode(isMCPContext ? "mcp" : "console");
|
|
6214
|
+
}
|
|
6215
|
+
if (streamingManager) {
|
|
6216
|
+
getOutputManager().setStreamingManager(streamingManager);
|
|
5588
6217
|
}
|
|
5589
6218
|
if (dashboard && streamingManager) {
|
|
5590
6219
|
if (!(args?.dashboard instanceof InteractiveDashboard)) {
|
|
@@ -6243,6 +6872,22 @@ ${issue.fix}
|
|
|
6243
6872
|
|
|
6244
6873
|
`;
|
|
6245
6874
|
}
|
|
6875
|
+
const outputManager = getOutputManager();
|
|
6876
|
+
if (outputManager.getMode() === "mcp") {
|
|
6877
|
+
const accumulatedMarkdown = outputManager.getMarkdown();
|
|
6878
|
+
if (accumulatedMarkdown.trim()) {
|
|
6879
|
+
output2 += `---
|
|
6880
|
+
|
|
6881
|
+
`;
|
|
6882
|
+
output2 += `## Additional Analysis
|
|
6883
|
+
|
|
6884
|
+
`;
|
|
6885
|
+
output2 += accumulatedMarkdown;
|
|
6886
|
+
output2 += `
|
|
6887
|
+
`;
|
|
6888
|
+
}
|
|
6889
|
+
outputManager.clearBuffers();
|
|
6890
|
+
}
|
|
6246
6891
|
output2 += `---
|
|
6247
6892
|
`;
|
|
6248
6893
|
output2 += `*Powered by Trie Agent \u2014 showing you what matters.*
|
|
@@ -6332,4 +6977,4 @@ export {
|
|
|
6332
6977
|
InteractiveDashboard,
|
|
6333
6978
|
TrieScanTool
|
|
6334
6979
|
};
|
|
6335
|
-
//# sourceMappingURL=chunk-
|
|
6980
|
+
//# sourceMappingURL=chunk-NM7ZVUHO.js.map
|