securewithtrace 0.23.0 → 0.24.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 +234 -126
  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";
@@ -26977,7 +27046,7 @@ function createOrgListCommand() {
26977
27046
  "Filter expression (repeatable). Fields: name, slug. Operators: = exact, != not exact, : contains, !: not contains",
26978
27047
  (val, prev) => [...prev, val],
26979
27048
  []
26980
- ).option("-w, --web", "Open the organization settings in a browser").addHelpText(
27049
+ ).option("-w, --web", "Open the organization settings in a browser").option("--no-pager", "Disable the interactive arrow-key pager for table output").addHelpText(
26981
27050
  "after",
26982
27051
  `
26983
27052
  Examples:
@@ -26987,6 +27056,7 @@ Examples:
26987
27056
  tracecli org list --limit 5
26988
27057
  tracecli org list --filter "name:acme"
26989
27058
  tracecli org list --filter "slug!=internal"
27059
+ tracecli org list --no-pager
26990
27060
  tracecli org list --web`
26991
27061
  ).action(
26992
27062
  async (options) => {
@@ -27056,10 +27126,16 @@ Examples:
27056
27126
  (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
27057
27127
  );
27058
27128
  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
- }
27129
+ const headerLine = headers.map((h, i) => pad(h, colWidths[i] ?? h.length)).join(" ");
27130
+ const rowLines = rows.map(
27131
+ (row) => row.map((cell, i) => pad(cell, colWidths[i] ?? 0)).join(" ")
27132
+ );
27133
+ await runInteractivePager({
27134
+ items,
27135
+ header: headerLine,
27136
+ renderItem: (_item, i) => [rowLines[i] ?? ""],
27137
+ disabled: options.pager === false
27138
+ });
27063
27139
  if (organizationContext.staleSelectionDetected) {
27064
27140
  console.log(
27065
27141
  "\nYour previous organization selection is no longer available and was reset."
@@ -27717,7 +27793,7 @@ function createListCommand() {
27717
27793
  }
27718
27794
  return Math.min(n, MAX_LIMIT);
27719
27795
  }
27720
- ).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("-w, --web", "Open the pentests dashboard in a browser").addHelpText(
27796
+ ).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
27797
  "after",
27722
27798
  `
27723
27799
  Examples:
@@ -27726,84 +27802,95 @@ Examples:
27726
27802
  tracecli pentest list --limit 10
27727
27803
  tracecli pentest list --output json | jq '.[].title'
27728
27804
  tracecli pentest list --output tsv | cut -f1,2
27805
+ tracecli pentest list --no-pager
27729
27806
  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}`);
27807
+ ).action(
27808
+ async (options) => {
27809
+ if (options.web) {
27810
+ const slug = await resolveOrganizationSlug();
27811
+ const url2 = `${getAppBaseUrl()}/dashboard/${slug}/pentests`;
27812
+ const opened = await openBrowser(url2);
27813
+ if (!opened) {
27814
+ console.log(`Open in browser: ${url2}`);
27815
+ }
27816
+ return;
27737
27817
  }
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.");
27818
+ const organizationId = await resolveOrganizationId();
27819
+ const result = await authenticatedRequest(
27820
+ (client) => client.pentest.listPentests({
27821
+ params: { organizationId }
27822
+ })
27823
+ );
27824
+ if (result.status === 401) {
27825
+ console.error("Authentication failed. Run `tracecli auth login` to re-authenticate.");
27826
+ process.exit(1);
27769
27827
  }
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(" "));
27828
+ if (result.status !== 200) {
27829
+ console.error(`Failed to list pentests (status ${result.status}).`);
27830
+ process.exit(1);
27788
27831
  }
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}`);
27832
+ let filteredPentests = result.body;
27833
+ if (options.status) {
27834
+ filteredPentests = filteredPentests.filter((p2) => p2.status === options.status);
27835
+ }
27836
+ const limit = options.limit ?? DEFAULT_LIMIT;
27837
+ const pentests = filteredPentests.slice(0, limit);
27838
+ const isJson = options.output === "json";
27839
+ const isTsv = options.output === "tsv";
27840
+ if (pentests.length === 0) {
27841
+ if (isJson) {
27842
+ console.log("[]");
27843
+ } else if (isTsv) {
27844
+ console.log("displayId title status startedAt endedAt findingsTotal");
27845
+ } else {
27846
+ console.log("No pentests found.");
27847
+ }
27848
+ return;
27803
27849
  }
27804
- console.log("");
27850
+ if (isJson) {
27851
+ console.log(JSON.stringify(pentests, null, 2));
27852
+ return;
27853
+ }
27854
+ if (isTsv) {
27855
+ console.log("displayId title status startedAt endedAt findingsTotal");
27856
+ for (const pt of pentests) {
27857
+ const cols = [
27858
+ `PT-${pt.displayId}`,
27859
+ tsvSafe2(pt.title),
27860
+ pt.status,
27861
+ pt.startedAt ? String(pt.startedAt) : "",
27862
+ pt.endedAt ? String(pt.endedAt) : "",
27863
+ String(pt.findingCounts.total)
27864
+ ];
27865
+ console.log(cols.join(" "));
27866
+ }
27867
+ return;
27868
+ }
27869
+ const header = `Found ${pentests.length} pentest${pentests.length === 1 ? "" : "s"}:`;
27870
+ await runInteractivePager({
27871
+ items: pentests,
27872
+ header,
27873
+ renderItem: (pt) => renderPentestLines(pt),
27874
+ disabled: options.pager === false
27875
+ });
27805
27876
  }
27806
- });
27877
+ );
27878
+ }
27879
+ function renderPentestLines(pt) {
27880
+ const status = formatStatus(pt.status);
27881
+ const started = formatDate(pt.startedAt);
27882
+ const ended = formatDate(pt.endedAt);
27883
+ const dateRange = pt.startedAt ? `${started} \u2013 ${ended}` : "Not started";
27884
+ const findings = pt.findingCounts.total;
27885
+ const lines = [
27886
+ ` [PT-${pt.displayId}] ${pt.title}`,
27887
+ ` Status: ${status} | Findings: ${findings} | ${dateRange}`
27888
+ ];
27889
+ if (pt.description) {
27890
+ lines.push(` ${pt.description}`);
27891
+ }
27892
+ lines.push("");
27893
+ return lines;
27807
27894
  }
27808
27895
  function createFindingsListCommand() {
27809
27896
  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 +27911,7 @@ function createFindingsListCommand() {
27824
27911
  }
27825
27912
  return Math.min(n, MAX_LIMIT);
27826
27913
  }
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(
27914
+ ).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
27915
  "after",
27829
27916
  `
27830
27917
  Examples:
@@ -27833,6 +27920,7 @@ Examples:
27833
27920
  tracecli pentest findings list --pentest PT-3 --status OPEN --limit 20
27834
27921
  tracecli pentest findings list --pentest PT-3 --output json
27835
27922
  tracecli pentest findings list --pentest PT-3 --output tsv | cut -f1,4
27923
+ tracecli pentest findings list --pentest PT-3 --no-pager
27836
27924
  tracecli pentest findings list --pentest PT-3 --web`
27837
27925
  ).action(
27838
27926
  async (options) => {
@@ -27911,27 +27999,33 @@ Examples:
27911
27999
  }
27912
28000
  return;
27913
28001
  }
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
- }
28002
+ const header = `Found ${findings.length} finding${findings.length === 1 ? "" : "s"}:`;
28003
+ await runInteractivePager({
28004
+ items: findings,
28005
+ header,
28006
+ renderItem: (finding) => renderPentestFindingLines(finding),
28007
+ disabled: options.pager === false
28008
+ });
27932
28009
  }
27933
28010
  );
27934
28011
  }
28012
+ function renderPentestFindingLines(finding) {
28013
+ const sev = formatFindingSeverity(finding.severity);
28014
+ const severityLabel = colorSeverity(finding.severity, sev);
28015
+ const status = formatStatus(finding.status);
28016
+ const cwes = finding.cweIds.length > 0 ? ` | ${finding.cweIds.join(", ")}` : "";
28017
+ const score = finding.cvssScore != null ? ` | CVSS ${finding.cvssScore.toFixed(1)}` : "";
28018
+ const lines = [
28019
+ ` [${severityLabel}] ${finding.title}`,
28020
+ ` ${finding.category} \xB7 ${status}${cwes}${score}`
28021
+ ];
28022
+ for (const repo of finding.repositories) {
28023
+ const location = repo.filePath ? `${repo.repositoryName} \xB7 ${repo.filePath}${repo.startLine != null ? `:${repo.startLine}` : ""}` : repo.repositoryName;
28024
+ lines.push(` ${location}`);
28025
+ }
28026
+ lines.push("");
28027
+ return lines;
28028
+ }
27935
28029
  function createFindingsCommand() {
27936
28030
  const findings = new Command("findings").description("Manage pentest findings");
27937
28031
  findings.addCommand(createFindingsListCommand());
@@ -28264,7 +28358,7 @@ function printRepositoriesTsv(repositories) {
28264
28358
  );
28265
28359
  }
28266
28360
  }
28267
- function printRepositoriesTable(repositories) {
28361
+ function buildRepositoriesTable(repositories) {
28268
28362
  const headers = ["Repository URL", "Platform", "Status"];
28269
28363
  const rows = repositories.map(
28270
28364
  (repo) => [
@@ -28277,10 +28371,9 @@ function printRepositoriesTable(repositories) {
28277
28371
  (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
28278
28372
  );
28279
28373
  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
- }
28374
+ const headerLine = headers.map((h, i) => pad(h, colWidths[i] ?? h.length)).join(" ");
28375
+ const rowLines = rows.map((row) => row.map((cell, i) => pad(cell, colWidths[i] ?? 0)).join(" "));
28376
+ return { headerLine, rowLines };
28284
28377
  }
28285
28378
  function collectFilterExpression(value, previous) {
28286
28379
  previous.push(value);
@@ -28431,7 +28524,7 @@ function addRepositoryFilterOptions(command) {
28431
28524
  ).option("--all", "Display all matching repositories (disables pagination)").option(
28432
28525
  "--dependency <pkg>",
28433
28526
  "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");
28527
+ ).option("-w, --web", "Open the repositories dashboard in a browser").option("--no-pager", "Disable the interactive arrow-key pager for table output");
28435
28528
  }
28436
28529
  async function runRepositoryListCommand(options) {
28437
28530
  if (options.web) {
@@ -28499,7 +28592,13 @@ async function runRepositoryListCommand(options) {
28499
28592
  printRepositoriesTsv(displayedRepositories);
28500
28593
  return;
28501
28594
  }
28502
- printRepositoriesTable(displayedRepositories);
28595
+ const { headerLine, rowLines } = buildRepositoriesTable(displayedRepositories);
28596
+ await runInteractivePager({
28597
+ items: displayedRepositories,
28598
+ header: headerLine,
28599
+ renderItem: (_repo, i) => [rowLines[i] ?? ""],
28600
+ disabled: options.pager === false
28601
+ });
28503
28602
  printPaginationHint(
28504
28603
  pagination,
28505
28604
  "repositories",
@@ -28580,10 +28679,11 @@ async function runDependencyFilter(organizationId, options) {
28580
28679
  }
28581
28680
  return;
28582
28681
  }
28583
- for (const repo of displayedRepos) {
28584
- const versions = repo.versions.join(", ");
28585
- console.log(`${repo.fullName} (${versions})`);
28586
- }
28682
+ await runInteractivePager({
28683
+ items: displayedRepos,
28684
+ renderItem: (repo) => [`${repo.fullName} (${repo.versions.join(", ")})`],
28685
+ disabled: options.pager === false
28686
+ });
28587
28687
  printPaginationHint(pagination, "repositories", "");
28588
28688
  }
28589
28689
  function createListCommand2() {
@@ -29289,22 +29389,24 @@ function formatSeverity(severity) {
29289
29389
  return severity;
29290
29390
  }
29291
29391
  }
29292
- function printVulnerabilities(vulnerabilities) {
29293
- const sorted = [...vulnerabilities].sort(
29392
+ function renderVulnerabilityLines(vuln) {
29393
+ const sev = formatSeverity(vuln.severity);
29394
+ const displaySeverity = colorSeverity(vuln.severity, sev);
29395
+ const status = formatStatus(vuln.status);
29396
+ const repo = vuln.repositoryName;
29397
+ const file2 = vuln.codeSnippet.filePath;
29398
+ const line = vuln.codeSnippet.startLine;
29399
+ return [
29400
+ ` [${displaySeverity}] ${vuln.title}`,
29401
+ ` ${repo} \xB7 ${file2}:${line} \xB7 ${status}`,
29402
+ ` ${vuln.traceId}`,
29403
+ ""
29404
+ ];
29405
+ }
29406
+ function sortVulnerabilitiesBySeverity(vulnerabilities) {
29407
+ return [...vulnerabilities].sort(
29294
29408
  (a, b) => (SEVERITY_ORDER[a.severity] ?? 99) - (SEVERITY_ORDER[b.severity] ?? 99)
29295
29409
  );
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
29410
  }
29309
29411
  function sanitizeTsvField(value) {
29310
29412
  return value.replace(/[\t\n\r]/g, " ");
@@ -29455,7 +29557,7 @@ function createListCommand3() {
29455
29557
  "Additional filter expression: field=value or field!=value. Repeatable. Supported fields: vulnerabilityType, cweId, scanMode (and any other API-supported field).",
29456
29558
  (value, previous) => [...previous, value],
29457
29559
  []
29458
- ).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("-w, --web", "Open the vulnerabilities dashboard in a browser").addHelpText(
29560
+ ).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
29561
  "after",
29460
29562
  `
29461
29563
  Examples:
@@ -29466,6 +29568,7 @@ Examples:
29466
29568
  tracecli vulnerabilities list --filter vulnerabilityType=SAST --filter scanMode=sast
29467
29569
  tracecli vulnerabilities list --output json | jq '.[].title'
29468
29570
  tracecli vulnerabilities list --output tsv | cut -f1,7
29571
+ tracecli vulnerabilities list --no-pager
29469
29572
  tracecli vulnerabilities list --web`
29470
29573
  ).action(
29471
29574
  async (options) => {
@@ -29548,10 +29651,15 @@ Examples:
29548
29651
  } else if (isTsv) {
29549
29652
  printVulnerabilitiesTsv(items);
29550
29653
  } else {
29654
+ const sorted = sortVulnerabilitiesBySeverity(items);
29551
29655
  const total = collected.length;
29552
- console.log(`Found ${total} vulnerabilit${total === 1 ? "y" : "ies"}:
29553
- `);
29554
- printVulnerabilities(items);
29656
+ const header = `Found ${total} vulnerabilit${total === 1 ? "y" : "ies"}:`;
29657
+ await runInteractivePager({
29658
+ items: sorted,
29659
+ header,
29660
+ renderItem: (vuln) => renderVulnerabilityLines(vuln),
29661
+ disabled: options.pager === false
29662
+ });
29555
29663
  }
29556
29664
  }
29557
29665
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securewithtrace",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Official Trace CLI for vulnerability intelligence in your terminal",
5
5
  "type": "module",
6
6
  "bin": {