securewithtrace 0.23.0 → 0.25.0

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.
Files changed (2) hide show
  1. package/dist/index.js +248 -132
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -26536,6 +26536,75 @@ function tsvSafe(value) {
26536
26536
  return (value ?? "").replace(/[\t\r\n]/g, " ");
26537
26537
  }
26538
26538
 
26539
+ // src/pager.ts
26540
+ function isInteractiveEnvironment() {
26541
+ if (process.env["NO_PAGER"] === "1" || process.env["TRACECLI_NO_PAGER"] === "1") {
26542
+ return false;
26543
+ }
26544
+ return Boolean(process.stdout.isTTY && process.stdin.isTTY);
26545
+ }
26546
+ function resolvePagerCommand() {
26547
+ const raw = process.env["PAGER"]?.trim();
26548
+ if (raw && raw.length > 0) {
26549
+ const parts = raw.split(/\s+/);
26550
+ const [command, ...args] = parts;
26551
+ if (command) return [command, ...args];
26552
+ }
26553
+ return ["less"];
26554
+ }
26555
+ function flattenItem(item, index, renderItem) {
26556
+ const rendered = renderItem(item, index);
26557
+ return typeof rendered === "string" ? [rendered] : rendered;
26558
+ }
26559
+ function printWithoutPager(options) {
26560
+ const { items, header, renderItem } = options;
26561
+ if (header !== void 0) {
26562
+ console.log(header);
26563
+ }
26564
+ items.forEach((item, index) => {
26565
+ for (const line of flattenItem(item, index, renderItem)) {
26566
+ console.log(line);
26567
+ }
26568
+ });
26569
+ }
26570
+ async function runInteractivePager(options) {
26571
+ const { items, header, renderItem, disabled } = options;
26572
+ if (disabled || items.length === 0 || !isInteractiveEnvironment()) {
26573
+ printWithoutPager({ items, header, renderItem });
26574
+ return;
26575
+ }
26576
+ const command = resolvePagerCommand();
26577
+ try {
26578
+ const proc = Bun.spawn(command, {
26579
+ stdin: "pipe",
26580
+ stdout: "inherit",
26581
+ stderr: "inherit",
26582
+ env: { LESS: "FRX", ...process.env }
26583
+ });
26584
+ const writer = proc.stdin;
26585
+ try {
26586
+ if (header !== void 0) {
26587
+ writer.write(`${header}
26588
+ `);
26589
+ }
26590
+ items.forEach((item, index) => {
26591
+ for (const line of flattenItem(item, index, renderItem)) {
26592
+ writer.write(`${line}
26593
+ `);
26594
+ }
26595
+ });
26596
+ } catch {
26597
+ }
26598
+ try {
26599
+ writer.end();
26600
+ } catch {
26601
+ }
26602
+ await proc.exited;
26603
+ } catch {
26604
+ printWithoutPager({ items, header, renderItem });
26605
+ }
26606
+ }
26607
+
26539
26608
  // src/auth/commands.ts
26540
26609
  function getClientId() {
26541
26610
  const clientId = "client_01KEZ01W5Q3107GTBY51WQ67HT";
@@ -26965,19 +27034,27 @@ Examples:
26965
27034
  }
26966
27035
  });
26967
27036
  }
27037
+ var ORG_LIST_MAX_LIMIT = 100;
26968
27038
  function createOrgListCommand() {
26969
- return new Command("list").description("List organizations available to your account").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("-L, --limit <n>", "Max organizations to display (default: all)", (v) => {
26970
- const n = Number.parseInt(v, 10);
26971
- if (Number.isNaN(n) || n < 1) {
26972
- throw new Error("--limit must be a positive integer");
27039
+ return new Command("list").description("List organizations available to your account").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option(
27040
+ "-L, --limit <n>",
27041
+ `Max organizations to display, max ${ORG_LIST_MAX_LIMIT} (default: all)`,
27042
+ (v) => {
27043
+ const n = Number.parseInt(v, 10);
27044
+ if (Number.isNaN(n) || n < 1) {
27045
+ throw new Error("--limit must be a positive integer");
27046
+ }
27047
+ if (n > ORG_LIST_MAX_LIMIT) {
27048
+ throw new Error(`--limit cannot exceed ${ORG_LIST_MAX_LIMIT}`);
27049
+ }
27050
+ return n;
26973
27051
  }
26974
- return n;
26975
- }).option(
27052
+ ).option(
26976
27053
  "-f, --filter <expression>",
26977
27054
  "Filter expression (repeatable). Fields: name, slug. Operators: = exact, != not exact, : contains, !: not contains",
26978
27055
  (val, prev) => [...prev, val],
26979
27056
  []
26980
- ).option("-w, --web", "Open the organization settings in a browser").addHelpText(
27057
+ ).option("-w, --web", "Open the organization settings in a browser").option("--no-pager", "Disable the interactive arrow-key pager for table output").addHelpText(
26981
27058
  "after",
26982
27059
  `
26983
27060
  Examples:
@@ -26987,6 +27064,7 @@ Examples:
26987
27064
  tracecli org list --limit 5
26988
27065
  tracecli org list --filter "name:acme"
26989
27066
  tracecli org list --filter "slug!=internal"
27067
+ tracecli org list --no-pager
26990
27068
  tracecli org list --web`
26991
27069
  ).action(
26992
27070
  async (options) => {
@@ -27056,10 +27134,16 @@ Examples:
27056
27134
  (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
27057
27135
  );
27058
27136
  const pad = (s, w3) => s.padEnd(w3);
27059
- console.log(headers.map((h, i) => pad(h, colWidths[i] ?? h.length)).join(" "));
27060
- for (const row of rows) {
27061
- console.log(row.map((cell, i) => pad(cell, colWidths[i] ?? 0)).join(" "));
27062
- }
27137
+ const headerLine = headers.map((h, i) => pad(h, colWidths[i] ?? h.length)).join(" ");
27138
+ const rowLines = rows.map(
27139
+ (row) => row.map((cell, i) => pad(cell, colWidths[i] ?? 0)).join(" ")
27140
+ );
27141
+ await runInteractivePager({
27142
+ items,
27143
+ header: headerLine,
27144
+ renderItem: (_item, i) => [rowLines[i] ?? ""],
27145
+ disabled: options.pager === false
27146
+ });
27063
27147
  if (organizationContext.staleSelectionDetected) {
27064
27148
  console.log(
27065
27149
  "\nYour previous organization selection is no longer available and was reset."
@@ -27717,7 +27801,7 @@ function createListCommand() {
27717
27801
  }
27718
27802
  return Math.min(n, MAX_LIMIT);
27719
27803
  }
27720
- ).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("-w, --web", "Open the pentests dashboard in a browser").addHelpText(
27804
+ ).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("-w, --web", "Open the pentests dashboard in a browser").option("--no-pager", "Disable the interactive arrow-key pager for table output").addHelpText(
27721
27805
  "after",
27722
27806
  `
27723
27807
  Examples:
@@ -27726,84 +27810,95 @@ Examples:
27726
27810
  tracecli pentest list --limit 10
27727
27811
  tracecli pentest list --output json | jq '.[].title'
27728
27812
  tracecli pentest list --output tsv | cut -f1,2
27813
+ tracecli pentest list --no-pager
27729
27814
  tracecli pentest list --web`
27730
- ).action(async (options) => {
27731
- if (options.web) {
27732
- const slug = await resolveOrganizationSlug();
27733
- const url2 = `${getAppBaseUrl()}/dashboard/${slug}/pentests`;
27734
- const opened = await openBrowser(url2);
27735
- if (!opened) {
27736
- console.log(`Open in browser: ${url2}`);
27815
+ ).action(
27816
+ async (options) => {
27817
+ if (options.web) {
27818
+ const slug = await resolveOrganizationSlug();
27819
+ const url2 = `${getAppBaseUrl()}/dashboard/${slug}/pentests`;
27820
+ const opened = await openBrowser(url2);
27821
+ if (!opened) {
27822
+ console.log(`Open in browser: ${url2}`);
27823
+ }
27824
+ return;
27737
27825
  }
27738
- return;
27739
- }
27740
- const organizationId = await resolveOrganizationId();
27741
- const result = await authenticatedRequest(
27742
- (client) => client.pentest.listPentests({
27743
- params: { organizationId }
27744
- })
27745
- );
27746
- if (result.status === 401) {
27747
- console.error("Authentication failed. Run `tracecli auth login` to re-authenticate.");
27748
- process.exit(1);
27749
- }
27750
- if (result.status !== 200) {
27751
- console.error(`Failed to list pentests (status ${result.status}).`);
27752
- process.exit(1);
27753
- }
27754
- let filteredPentests = result.body;
27755
- if (options.status) {
27756
- filteredPentests = filteredPentests.filter((p2) => p2.status === options.status);
27757
- }
27758
- const limit = options.limit ?? DEFAULT_LIMIT;
27759
- const pentests = filteredPentests.slice(0, limit);
27760
- const isJson = options.output === "json";
27761
- const isTsv = options.output === "tsv";
27762
- if (pentests.length === 0) {
27763
- if (isJson) {
27764
- console.log("[]");
27765
- } else if (isTsv) {
27766
- console.log("displayId title status startedAt endedAt findingsTotal");
27767
- } else {
27768
- console.log("No pentests found.");
27826
+ const organizationId = await resolveOrganizationId();
27827
+ const result = await authenticatedRequest(
27828
+ (client) => client.pentest.listPentests({
27829
+ params: { organizationId }
27830
+ })
27831
+ );
27832
+ if (result.status === 401) {
27833
+ console.error("Authentication failed. Run `tracecli auth login` to re-authenticate.");
27834
+ process.exit(1);
27769
27835
  }
27770
- return;
27771
- }
27772
- if (isJson) {
27773
- console.log(JSON.stringify(pentests, null, 2));
27774
- return;
27775
- }
27776
- if (isTsv) {
27777
- console.log("displayId title status startedAt endedAt findingsTotal");
27778
- for (const pt of pentests) {
27779
- const cols = [
27780
- `PT-${pt.displayId}`,
27781
- tsvSafe2(pt.title),
27782
- pt.status,
27783
- pt.startedAt ? String(pt.startedAt) : "",
27784
- pt.endedAt ? String(pt.endedAt) : "",
27785
- String(pt.findingCounts.total)
27786
- ];
27787
- console.log(cols.join(" "));
27836
+ if (result.status !== 200) {
27837
+ console.error(`Failed to list pentests (status ${result.status}).`);
27838
+ process.exit(1);
27788
27839
  }
27789
- return;
27790
- }
27791
- console.log(`Found ${pentests.length} pentest${pentests.length === 1 ? "" : "s"}:
27792
- `);
27793
- for (const pt of pentests) {
27794
- const status = formatStatus(pt.status);
27795
- const started = formatDate(pt.startedAt);
27796
- const ended = formatDate(pt.endedAt);
27797
- const dateRange = pt.startedAt ? `${started} \u2013 ${ended}` : "Not started";
27798
- const findings = pt.findingCounts.total;
27799
- console.log(` [PT-${pt.displayId}] ${pt.title}`);
27800
- console.log(` Status: ${status} | Findings: ${findings} | ${dateRange}`);
27801
- if (pt.description) {
27802
- console.log(` ${pt.description}`);
27840
+ let filteredPentests = result.body;
27841
+ if (options.status) {
27842
+ filteredPentests = filteredPentests.filter((p2) => p2.status === options.status);
27843
+ }
27844
+ const limit = options.limit ?? DEFAULT_LIMIT;
27845
+ const pentests = filteredPentests.slice(0, limit);
27846
+ const isJson = options.output === "json";
27847
+ const isTsv = options.output === "tsv";
27848
+ if (pentests.length === 0) {
27849
+ if (isJson) {
27850
+ console.log("[]");
27851
+ } else if (isTsv) {
27852
+ console.log("displayId title status startedAt endedAt findingsTotal");
27853
+ } else {
27854
+ console.log("No pentests found.");
27855
+ }
27856
+ return;
27803
27857
  }
27804
- console.log("");
27858
+ if (isJson) {
27859
+ console.log(JSON.stringify(pentests, null, 2));
27860
+ return;
27861
+ }
27862
+ if (isTsv) {
27863
+ console.log("displayId title status startedAt endedAt findingsTotal");
27864
+ for (const pt of pentests) {
27865
+ const cols = [
27866
+ `PT-${pt.displayId}`,
27867
+ tsvSafe2(pt.title),
27868
+ pt.status,
27869
+ pt.startedAt ? String(pt.startedAt) : "",
27870
+ pt.endedAt ? String(pt.endedAt) : "",
27871
+ String(pt.findingCounts.total)
27872
+ ];
27873
+ console.log(cols.join(" "));
27874
+ }
27875
+ return;
27876
+ }
27877
+ const header = `Found ${pentests.length} pentest${pentests.length === 1 ? "" : "s"}:`;
27878
+ await runInteractivePager({
27879
+ items: pentests,
27880
+ header,
27881
+ renderItem: (pt) => renderPentestLines(pt),
27882
+ disabled: options.pager === false
27883
+ });
27805
27884
  }
27806
- });
27885
+ );
27886
+ }
27887
+ function renderPentestLines(pt) {
27888
+ const status = formatStatus(pt.status);
27889
+ const started = formatDate(pt.startedAt);
27890
+ const ended = formatDate(pt.endedAt);
27891
+ const dateRange = pt.startedAt ? `${started} \u2013 ${ended}` : "Not started";
27892
+ const findings = pt.findingCounts.total;
27893
+ const lines = [
27894
+ ` [PT-${pt.displayId}] ${pt.title}`,
27895
+ ` Status: ${status} | Findings: ${findings} | ${dateRange}`
27896
+ ];
27897
+ if (pt.description) {
27898
+ lines.push(` ${pt.description}`);
27899
+ }
27900
+ lines.push("");
27901
+ return lines;
27807
27902
  }
27808
27903
  function createFindingsListCommand() {
27809
27904
  return new Command("list").description("List findings for a pentest").requiredOption("--pentest <id>", "Pentest display ID (e.g. PT-3) or UUID").option(
@@ -27824,7 +27919,7 @@ function createFindingsListCommand() {
27824
27919
  }
27825
27920
  return Math.min(n, MAX_LIMIT);
27826
27921
  }
27827
- ).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("-w, --web", "Open the pentest detail page in a browser").addHelpText(
27922
+ ).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("-w, --web", "Open the pentest detail page in a browser").option("--no-pager", "Disable the interactive arrow-key pager for table output").addHelpText(
27828
27923
  "after",
27829
27924
  `
27830
27925
  Examples:
@@ -27833,6 +27928,7 @@ Examples:
27833
27928
  tracecli pentest findings list --pentest PT-3 --status OPEN --limit 20
27834
27929
  tracecli pentest findings list --pentest PT-3 --output json
27835
27930
  tracecli pentest findings list --pentest PT-3 --output tsv | cut -f1,4
27931
+ tracecli pentest findings list --pentest PT-3 --no-pager
27836
27932
  tracecli pentest findings list --pentest PT-3 --web`
27837
27933
  ).action(
27838
27934
  async (options) => {
@@ -27911,27 +28007,33 @@ Examples:
27911
28007
  }
27912
28008
  return;
27913
28009
  }
27914
- console.log(`Found ${findings.length} finding${findings.length === 1 ? "" : "s"}:
27915
- `);
27916
- for (const finding of findings) {
27917
- const sev = formatFindingSeverity(finding.severity);
27918
- const severityLabel = colorSeverity(finding.severity, sev);
27919
- const status = formatStatus(finding.status);
27920
- const cwes = finding.cweIds.length > 0 ? ` | ${finding.cweIds.join(", ")}` : "";
27921
- const score = finding.cvssScore != null ? ` | CVSS ${finding.cvssScore.toFixed(1)}` : "";
27922
- console.log(` [${severityLabel}] ${finding.title}`);
27923
- console.log(` ${finding.category} \xB7 ${status}${cwes}${score}`);
27924
- if (finding.repositories.length > 0) {
27925
- for (const repo of finding.repositories) {
27926
- const location = repo.filePath ? `${repo.repositoryName} \xB7 ${repo.filePath}${repo.startLine != null ? `:${repo.startLine}` : ""}` : repo.repositoryName;
27927
- console.log(` ${location}`);
27928
- }
27929
- }
27930
- console.log("");
27931
- }
28010
+ const header = `Found ${findings.length} finding${findings.length === 1 ? "" : "s"}:`;
28011
+ await runInteractivePager({
28012
+ items: findings,
28013
+ header,
28014
+ renderItem: (finding) => renderPentestFindingLines(finding),
28015
+ disabled: options.pager === false
28016
+ });
27932
28017
  }
27933
28018
  );
27934
28019
  }
28020
+ function renderPentestFindingLines(finding) {
28021
+ const sev = formatFindingSeverity(finding.severity);
28022
+ const severityLabel = colorSeverity(finding.severity, sev);
28023
+ const status = formatStatus(finding.status);
28024
+ const cwes = finding.cweIds.length > 0 ? ` | ${finding.cweIds.join(", ")}` : "";
28025
+ const score = finding.cvssScore != null ? ` | CVSS ${finding.cvssScore.toFixed(1)}` : "";
28026
+ const lines = [
28027
+ ` [${severityLabel}] ${finding.title}`,
28028
+ ` ${finding.category} \xB7 ${status}${cwes}${score}`
28029
+ ];
28030
+ for (const repo of finding.repositories) {
28031
+ const location = repo.filePath ? `${repo.repositoryName} \xB7 ${repo.filePath}${repo.startLine != null ? `:${repo.startLine}` : ""}` : repo.repositoryName;
28032
+ lines.push(` ${location}`);
28033
+ }
28034
+ lines.push("");
28035
+ return lines;
28036
+ }
27935
28037
  function createFindingsCommand() {
27936
28038
  const findings = new Command("findings").description("Manage pentest findings");
27937
28039
  findings.addCommand(createFindingsListCommand());
@@ -28264,7 +28366,7 @@ function printRepositoriesTsv(repositories) {
28264
28366
  );
28265
28367
  }
28266
28368
  }
28267
- function printRepositoriesTable(repositories) {
28369
+ function buildRepositoriesTable(repositories) {
28268
28370
  const headers = ["Repository URL", "Platform", "Status"];
28269
28371
  const rows = repositories.map(
28270
28372
  (repo) => [
@@ -28277,10 +28379,9 @@ function printRepositoriesTable(repositories) {
28277
28379
  (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
28278
28380
  );
28279
28381
  const pad = (s, w3) => s.padEnd(w3);
28280
- console.log(headers.map((h, i) => pad(h, colWidths[i] ?? h.length)).join(" "));
28281
- for (const row of rows) {
28282
- console.log(row.map((cell, i) => pad(cell, colWidths[i] ?? 0)).join(" "));
28283
- }
28382
+ const headerLine = headers.map((h, i) => pad(h, colWidths[i] ?? h.length)).join(" ");
28383
+ const rowLines = rows.map((row) => row.map((cell, i) => pad(cell, colWidths[i] ?? 0)).join(" "));
28384
+ return { headerLine, rowLines };
28284
28385
  }
28285
28386
  function collectFilterExpression(value, previous) {
28286
28387
  previous.push(value);
@@ -28431,7 +28532,7 @@ function addRepositoryFilterOptions(command) {
28431
28532
  ).option("--all", "Display all matching repositories (disables pagination)").option(
28432
28533
  "--dependency <pkg>",
28433
28534
  "Filter repositories that use a dependency (e.g. lodash, lodash@4.17.21, @types/node@20)"
28434
- ).option("-w, --web", "Open the repositories dashboard in a browser");
28535
+ ).option("-w, --web", "Open the repositories dashboard in a browser").option("--no-pager", "Disable the interactive arrow-key pager for table output");
28435
28536
  }
28436
28537
  async function runRepositoryListCommand(options) {
28437
28538
  if (options.web) {
@@ -28499,7 +28600,13 @@ async function runRepositoryListCommand(options) {
28499
28600
  printRepositoriesTsv(displayedRepositories);
28500
28601
  return;
28501
28602
  }
28502
- printRepositoriesTable(displayedRepositories);
28603
+ const { headerLine, rowLines } = buildRepositoriesTable(displayedRepositories);
28604
+ await runInteractivePager({
28605
+ items: displayedRepositories,
28606
+ header: headerLine,
28607
+ renderItem: (_repo, i) => [rowLines[i] ?? ""],
28608
+ disabled: options.pager === false
28609
+ });
28503
28610
  printPaginationHint(
28504
28611
  pagination,
28505
28612
  "repositories",
@@ -28580,10 +28687,11 @@ async function runDependencyFilter(organizationId, options) {
28580
28687
  }
28581
28688
  return;
28582
28689
  }
28583
- for (const repo of displayedRepos) {
28584
- const versions = repo.versions.join(", ");
28585
- console.log(`${repo.fullName} (${versions})`);
28586
- }
28690
+ await runInteractivePager({
28691
+ items: displayedRepos,
28692
+ renderItem: (repo) => [`${repo.fullName} (${repo.versions.join(", ")})`],
28693
+ disabled: options.pager === false
28694
+ });
28587
28695
  printPaginationHint(pagination, "repositories", "");
28588
28696
  }
28589
28697
  function createListCommand2() {
@@ -29289,22 +29397,24 @@ function formatSeverity(severity) {
29289
29397
  return severity;
29290
29398
  }
29291
29399
  }
29292
- function printVulnerabilities(vulnerabilities) {
29293
- const sorted = [...vulnerabilities].sort(
29400
+ function renderVulnerabilityLines(vuln) {
29401
+ const sev = formatSeverity(vuln.severity);
29402
+ const displaySeverity = colorSeverity(vuln.severity, sev);
29403
+ const status = formatStatus(vuln.status);
29404
+ const repo = vuln.repositoryName;
29405
+ const file2 = vuln.codeSnippet.filePath;
29406
+ const line = vuln.codeSnippet.startLine;
29407
+ return [
29408
+ ` [${displaySeverity}] ${vuln.title}`,
29409
+ ` ${repo} \xB7 ${file2}:${line} \xB7 ${status}`,
29410
+ ` ${vuln.traceId}`,
29411
+ ""
29412
+ ];
29413
+ }
29414
+ function sortVulnerabilitiesBySeverity(vulnerabilities) {
29415
+ return [...vulnerabilities].sort(
29294
29416
  (a, b) => (SEVERITY_ORDER[a.severity] ?? 99) - (SEVERITY_ORDER[b.severity] ?? 99)
29295
29417
  );
29296
- for (const vuln of sorted) {
29297
- const sev = formatSeverity(vuln.severity);
29298
- const displaySeverity = colorSeverity(vuln.severity, sev);
29299
- const status = formatStatus(vuln.status);
29300
- const repo = vuln.repositoryName;
29301
- const file2 = vuln.codeSnippet.filePath;
29302
- const line = vuln.codeSnippet.startLine;
29303
- console.log(` [${displaySeverity}] ${vuln.title}`);
29304
- console.log(` ${repo} \xB7 ${file2}:${line} \xB7 ${status}`);
29305
- console.log(` ${vuln.traceId}`);
29306
- console.log("");
29307
- }
29308
29418
  }
29309
29419
  function sanitizeTsvField(value) {
29310
29420
  return value.replace(/[\t\n\r]/g, " ");
@@ -29455,7 +29565,7 @@ function createListCommand3() {
29455
29565
  "Additional filter expression: field=value or field!=value. Repeatable. Supported fields: vulnerabilityType, cweId, scanMode (and any other API-supported field).",
29456
29566
  (value, previous) => [...previous, value],
29457
29567
  []
29458
- ).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("-w, --web", "Open the vulnerabilities dashboard in a browser").addHelpText(
29568
+ ).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("-w, --web", "Open the vulnerabilities dashboard in a browser").option("--no-pager", "Disable the interactive arrow-key pager for table output").addHelpText(
29459
29569
  "after",
29460
29570
  `
29461
29571
  Examples:
@@ -29466,6 +29576,7 @@ Examples:
29466
29576
  tracecli vulnerabilities list --filter vulnerabilityType=SAST --filter scanMode=sast
29467
29577
  tracecli vulnerabilities list --output json | jq '.[].title'
29468
29578
  tracecli vulnerabilities list --output tsv | cut -f1,7
29579
+ tracecli vulnerabilities list --no-pager
29469
29580
  tracecli vulnerabilities list --web`
29470
29581
  ).action(
29471
29582
  async (options) => {
@@ -29548,10 +29659,15 @@ Examples:
29548
29659
  } else if (isTsv) {
29549
29660
  printVulnerabilitiesTsv(items);
29550
29661
  } else {
29662
+ const sorted = sortVulnerabilitiesBySeverity(items);
29551
29663
  const total = collected.length;
29552
- console.log(`Found ${total} vulnerabilit${total === 1 ? "y" : "ies"}:
29553
- `);
29554
- printVulnerabilities(items);
29664
+ const header = `Found ${total} vulnerabilit${total === 1 ? "y" : "ies"}:`;
29665
+ await runInteractivePager({
29666
+ items: sorted,
29667
+ header,
29668
+ renderItem: (vuln) => renderVulnerabilityLines(vuln),
29669
+ disabled: options.pager === false
29670
+ });
29555
29671
  }
29556
29672
  }
29557
29673
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securewithtrace",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "description": "Official Trace CLI for vulnerability intelligence in your terminal",
5
5
  "type": "module",
6
6
  "bin": {