securewithtrace 0.16.0 → 0.17.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 +179 -38
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -23943,7 +23943,9 @@ var VulnerabilityAggregatedCountsRequestSchema = external_exports.object({
23943
23943
  excludeStatuses: external_exports.array(VulnerabilityStatus).optional(),
23944
23944
  defaultBranchOnly: external_exports.boolean().default(true).optional(),
23945
23945
  /** Lower bound for time-series data. Only vulnerabilities created on or after this date are included in the timeSeries field. Does not affect byRepoAndSeverity or byStatusAndSeverity. */
23946
- timeSeriesSince: external_exports.coerce.date().optional()
23946
+ timeSeriesSince: external_exports.coerce.date().optional(),
23947
+ /** When provided, only include vulnerabilities belonging to this repository. */
23948
+ repositoryId: external_exports.uuid().optional()
23947
23949
  });
23948
23950
  var VulnerabilityAggregatedCountsResponseSchema = external_exports.object({
23949
23951
  byRepoAndSeverity: external_exports.array(RepoSeverityCountSchema),
@@ -24957,8 +24959,24 @@ async function authenticatedRequestSafe(request) {
24957
24959
  return null;
24958
24960
  }
24959
24961
  }
24962
+ function extractOrganizationIdFromToken(token) {
24963
+ if (!token || token.trim().length === 0) {
24964
+ return null;
24965
+ }
24966
+ const parts = token.split(".");
24967
+ const payload = parts[1];
24968
+ if (!payload) {
24969
+ return null;
24970
+ }
24971
+ try {
24972
+ const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
24973
+ return typeof decoded.org_id === "string" && decoded.org_id.trim().length > 0 ? decoded.org_id : null;
24974
+ } catch {
24975
+ return null;
24976
+ }
24977
+ }
24960
24978
  var SAFE_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
24961
- function determineActiveOrganization(organizations, selectedOrganizationId) {
24979
+ function determineActiveOrganization(organizations, selectedOrganizationId, tokenOrganizationId) {
24962
24980
  if (organizations.length === 0) {
24963
24981
  return null;
24964
24982
  }
@@ -24974,6 +24992,18 @@ function determineActiveOrganization(organizations, selectedOrganizationId) {
24974
24992
  };
24975
24993
  }
24976
24994
  }
24995
+ if (tokenOrganizationId && SAFE_ID_PATTERN.test(tokenOrganizationId)) {
24996
+ const tokenOrganization = organizations.find(
24997
+ (organization) => organization.organizationId === tokenOrganizationId
24998
+ );
24999
+ if (tokenOrganization) {
25000
+ return {
25001
+ organization: tokenOrganization,
25002
+ source: "default",
25003
+ staleSelectionDetected: selectedOrganizationId != null
25004
+ };
25005
+ }
25006
+ }
24977
25007
  const [fallbackOrganization] = organizations;
24978
25008
  if (!fallbackOrganization) {
24979
25009
  return null;
@@ -24994,7 +25024,12 @@ async function resolveOrganizationId(client) {
24994
25024
  }
24995
25025
  const organizations = result.body;
24996
25026
  const selectedOrganizationId = readSelectedOrganizationId();
24997
- const activeOrganization = determineActiveOrganization(organizations, selectedOrganizationId);
25027
+ const tokenOrganizationId = extractOrganizationIdFromToken(readToken());
25028
+ const activeOrganization = determineActiveOrganization(
25029
+ organizations,
25030
+ selectedOrganizationId,
25031
+ tokenOrganizationId
25032
+ );
24998
25033
  if (!activeOrganization) {
24999
25034
  console.error("No organization found for your account.");
25000
25035
  process.exit(1);
@@ -25030,7 +25065,12 @@ async function resolveOrganizationIdSafe(client) {
25030
25065
  }
25031
25066
  const organizations = result.body;
25032
25067
  const selectedOrganizationId = readSelectedOrganizationId();
25033
- const activeOrganization = determineActiveOrganization(organizations, selectedOrganizationId);
25068
+ const tokenOrganizationId = extractOrganizationIdFromToken(readToken());
25069
+ const activeOrganization = determineActiveOrganization(
25070
+ organizations,
25071
+ selectedOrganizationId,
25072
+ tokenOrganizationId
25073
+ );
25034
25074
  if (!activeOrganization) {
25035
25075
  return null;
25036
25076
  }
@@ -25050,6 +25090,21 @@ async function resolveOrganizationIdSafe(client) {
25050
25090
  }
25051
25091
  }
25052
25092
 
25093
+ // src/format.ts
25094
+ var SEVERITY_ORDER = {
25095
+ CRITICAL: 0,
25096
+ HIGH: 1,
25097
+ MEDIUM: 2,
25098
+ LOW: 3,
25099
+ INFORMATIONAL: 4
25100
+ };
25101
+ function formatStatus(status) {
25102
+ return status.toLowerCase().replace("_", " ");
25103
+ }
25104
+ function tsvSafe(value) {
25105
+ return (value ?? "").replace(/[\t\r\n]/g, " ");
25106
+ }
25107
+
25053
25108
  // src/auth/commands.ts
25054
25109
  function getClientId() {
25055
25110
  const clientId = "client_01KEZ01W5Q3107GTBY51WQ67HT";
@@ -25312,7 +25367,14 @@ function createStatusCommand() {
25312
25367
  });
25313
25368
  }
25314
25369
  function createOrgListCommand() {
25315
- return new Command("list").description("List organizations available to your account").option("--json", "Output as JSON").action(async (options) => {
25370
+ return new Command("list").description("List organizations available to your account").option("-o, --output <format>", "Output format: table (default), json, tsv").addHelpText(
25371
+ "after",
25372
+ `
25373
+ Examples:
25374
+ tracecli org list
25375
+ tracecli org list --output json
25376
+ tracecli org list --output tsv | cut -f1`
25377
+ ).action(async (options) => {
25316
25378
  const organizationContext = await fetchOrganizationContext();
25317
25379
  const items = organizationContext.organizations.map(
25318
25380
  (organization) => ({
@@ -25321,10 +25383,23 @@ function createOrgListCommand() {
25321
25383
  selected: organization.organizationId === organizationContext.selectedOrganizationId
25322
25384
  })
25323
25385
  );
25324
- if (options.json) {
25386
+ if (options.output === "json") {
25325
25387
  console.log(JSON.stringify(items, null, 2));
25326
25388
  return;
25327
25389
  }
25390
+ if (options.output === "tsv") {
25391
+ for (const item of items) {
25392
+ console.log(
25393
+ [
25394
+ tsvSafe(item.name),
25395
+ tsvSafe(item.slug),
25396
+ tsvSafe(item.organizationId),
25397
+ item.active ? "true" : "false"
25398
+ ].join(" ")
25399
+ );
25400
+ }
25401
+ return;
25402
+ }
25328
25403
  for (const item of items) {
25329
25404
  const marker = item.active ? "*" : " ";
25330
25405
  const labels = [item.active ? "active" : null, item.selected ? "selected" : null].filter(
@@ -25341,11 +25416,18 @@ function createOrgListCommand() {
25341
25416
  });
25342
25417
  }
25343
25418
  function createOrgCurrentCommand() {
25344
- return new Command("current").description("Show the active organization used by CLI commands").option("--json", "Output as JSON").action(async (options) => {
25419
+ return new Command("current").description("Show the active organization used by CLI commands").option("-o, --output <format>", "Output format: table (default), json, tsv").addHelpText(
25420
+ "after",
25421
+ `
25422
+ Examples:
25423
+ tracecli org current
25424
+ tracecli org current --output json
25425
+ tracecli org current --output tsv`
25426
+ ).action(async (options) => {
25345
25427
  const organizationContext = await fetchOrganizationContext();
25346
25428
  const active = organizationContext.activeOrganization;
25347
25429
  const source = organizationContext.selectedOrganizationId === active.organizationId ? "selected" : "default";
25348
- if (options.json) {
25430
+ if (options.output === "json") {
25349
25431
  console.log(
25350
25432
  JSON.stringify(
25351
25433
  {
@@ -25358,6 +25440,17 @@ function createOrgCurrentCommand() {
25358
25440
  );
25359
25441
  return;
25360
25442
  }
25443
+ if (options.output === "tsv") {
25444
+ console.log(
25445
+ [
25446
+ tsvSafe(active.name),
25447
+ tsvSafe(active.slug),
25448
+ tsvSafe(active.organizationId),
25449
+ tsvSafe(source)
25450
+ ].join(" ")
25451
+ );
25452
+ return;
25453
+ }
25361
25454
  console.log(
25362
25455
  `Active organization: ${sanitizeForOutput(active.name)} (${sanitizeForOutput(active.slug)})`
25363
25456
  );
@@ -25792,22 +25885,10 @@ function createCompletionCommand(program3) {
25792
25885
  });
25793
25886
  }
25794
25887
 
25795
- // src/format.ts
25796
- var SEVERITY_ORDER = {
25797
- CRITICAL: 0,
25798
- HIGH: 1,
25799
- MEDIUM: 2,
25800
- LOW: 3,
25801
- INFORMATIONAL: 4
25802
- };
25803
- function formatStatus(status) {
25804
- return status.toLowerCase().replace("_", " ");
25805
- }
25806
-
25807
25888
  // src/pentest/commands.ts
25808
25889
  var DEFAULT_LIMIT = 30;
25809
25890
  var MAX_LIMIT = 100;
25810
- function tsvSafe(value) {
25891
+ function tsvSafe2(value) {
25811
25892
  return value.replace(/[\t\n\r]/g, " ");
25812
25893
  }
25813
25894
  function formatDate(date5) {
@@ -25926,7 +26007,7 @@ Examples:
25926
26007
  for (const pt of pentests) {
25927
26008
  const cols = [
25928
26009
  `PT-${pt.displayId}`,
25929
- tsvSafe(pt.title),
26010
+ tsvSafe2(pt.title),
25930
26011
  pt.status,
25931
26012
  pt.startedAt ? String(pt.startedAt) : "",
25932
26013
  pt.endedAt ? String(pt.endedAt) : "",
@@ -26009,8 +26090,8 @@ Examples:
26009
26090
  const cols = [
26010
26091
  finding.severity,
26011
26092
  finding.status,
26012
- tsvSafe(finding.category),
26013
- tsvSafe(finding.title),
26093
+ tsvSafe2(finding.category),
26094
+ tsvSafe2(finding.title),
26014
26095
  finding.cweIds.join(";"),
26015
26096
  finding.cvssScore != null ? String(finding.cvssScore) : ""
26016
26097
  ];
@@ -26098,6 +26179,8 @@ function extractRepoName(input) {
26098
26179
  }
26099
26180
  var BRANCH_NAME_PATTERN = /^[a-zA-Z0-9._/+-]+$/;
26100
26181
  var TRACE_GIT_ASKPASS_TOKEN_ENV = "TRACE_GIT_ASKPASS_TOKEN";
26182
+ var DEFAULT_LIMIT2 = 30;
26183
+ var MAX_LIMIT2 = 100;
26101
26184
  var REPOSITORY_PLATFORMS = ["GITHUB", "GITLAB", "BITBUCKET"];
26102
26185
  var REPOSITORY_STATUSES = ["CONNECTED", "DISCONNECTED"];
26103
26186
  function validateBranchName(branch) {
@@ -26304,6 +26387,29 @@ function filterRepositories(repositories, options) {
26304
26387
  return true;
26305
26388
  });
26306
26389
  }
26390
+ function parseListLimit(value) {
26391
+ const n = Number(value);
26392
+ if (!Number.isInteger(n) || n < 1) {
26393
+ throw new Error("--limit must be a positive integer");
26394
+ }
26395
+ if (n > MAX_LIMIT2) {
26396
+ throw new Error(`--limit cannot exceed ${MAX_LIMIT2}`);
26397
+ }
26398
+ return n;
26399
+ }
26400
+ function printRepositoriesTsv(repositories) {
26401
+ for (const repo of repositories) {
26402
+ console.log(
26403
+ [
26404
+ tsvSafe(repo.repositoryUrl),
26405
+ tsvSafe(repo.platform),
26406
+ tsvSafe(repo.status),
26407
+ tsvSafe(repo.defaultBranch),
26408
+ tsvSafe(repo.primaryLanguage)
26409
+ ].join(" ")
26410
+ );
26411
+ }
26412
+ }
26307
26413
  function collectFilterExpression(value, previous) {
26308
26414
  previous.push(value);
26309
26415
  return previous;
@@ -26328,11 +26434,16 @@ function addRepositoryFilterOptions(command) {
26328
26434
  "Filter by status (CONNECTED, DISCONNECTED)",
26329
26435
  (value) => value.toUpperCase()
26330
26436
  ).option("--query <query>", "Case-insensitive search across URL, repo, branch, and language").option("--default-branch <branch>", "Filter by default branch (case-insensitive contains)").option("--language <language>", "Filter by primary language (case-insensitive contains)").option(
26331
- "--filter <expression>",
26332
- "Advanced filter expression. Repeatable. Examples: platform=GITHUB, repo:trace, status!=DISCONNECTED, language!:typescript",
26437
+ "-f, --filter <expression>",
26438
+ "Advanced filter expression. Repeatable. Examples: platform=GITHUB, repo:trace, status!=DISCONNECTED",
26333
26439
  collectFilterExpression,
26334
26440
  []
26335
- ).option("--json", "Output as JSON").option(
26441
+ ).option("-o, --output <format>", "Output format: table (default), json, tsv").option(
26442
+ "-L, --limit <n>",
26443
+ `Maximum repositories to display (default ${DEFAULT_LIMIT2}, max ${MAX_LIMIT2})`,
26444
+ parseListLimit,
26445
+ DEFAULT_LIMIT2
26446
+ ).option(
26336
26447
  "--dependency <pkg>",
26337
26448
  "Filter repositories that use a dependency (e.g. lodash, lodash@4.17.21, @types/node@20)"
26338
26449
  );
@@ -26371,11 +26482,17 @@ async function runRepositoryListCommand(options) {
26371
26482
  console.log("No repositories found.");
26372
26483
  return;
26373
26484
  }
26374
- if (options.json) {
26375
- console.log(JSON.stringify(filteredRepositories, null, 2));
26485
+ const limit = options.limit ?? DEFAULT_LIMIT2;
26486
+ const displayedRepositories = filteredRepositories.slice(0, limit);
26487
+ if (options.output === "json") {
26488
+ console.log(JSON.stringify(displayedRepositories, null, 2));
26376
26489
  return;
26377
26490
  }
26378
- for (const repo of filteredRepositories) {
26491
+ if (options.output === "tsv") {
26492
+ printRepositoriesTsv(displayedRepositories);
26493
+ return;
26494
+ }
26495
+ for (const repo of displayedRepositories) {
26379
26496
  const status = repo.status === "CONNECTED" ? "connected" : "disconnected";
26380
26497
  const url2 = stripAnsi(String(repo.repositoryUrl));
26381
26498
  const platform = stripAnsi(String(repo.platform)).toLowerCase();
@@ -26430,11 +26547,19 @@ async function runDependencyFilter(organizationId, options) {
26430
26547
  console.log(`No repositories found using dependency '${label}'.`);
26431
26548
  return;
26432
26549
  }
26433
- if (options.json) {
26434
- console.log(JSON.stringify(repos, null, 2));
26550
+ const limit = options.limit ?? DEFAULT_LIMIT2;
26551
+ const displayedRepos = repos.slice(0, limit);
26552
+ if (options.output === "json") {
26553
+ console.log(JSON.stringify(displayedRepos, null, 2));
26435
26554
  return;
26436
26555
  }
26437
- for (const repo of repos) {
26556
+ if (options.output === "tsv") {
26557
+ for (const repo of displayedRepos) {
26558
+ console.log(`${tsvSafe(repo.fullName)} ${tsvSafe(repo.versions.join(","))}`);
26559
+ }
26560
+ return;
26561
+ }
26562
+ for (const repo of displayedRepos) {
26438
26563
  const versions = repo.versions.join(", ");
26439
26564
  console.log(`${repo.fullName} (${versions})`);
26440
26565
  }
@@ -26442,6 +26567,16 @@ async function runDependencyFilter(organizationId, options) {
26442
26567
  function createListCommand2() {
26443
26568
  return addRepositoryFilterOptions(
26444
26569
  new Command("list").description("List repositories in your organization")
26570
+ ).addHelpText(
26571
+ "after",
26572
+ `
26573
+ Examples:
26574
+ tracecli repo list
26575
+ tracecli repo list --platform GITHUB --status CONNECTED
26576
+ tracecli repo list --output json | jq '.[].repositoryUrl'
26577
+ tracecli repo list --output tsv | cut -f1
26578
+ tracecli repo list --filter "language:typescript" --limit 10
26579
+ tracecli repo list --dependency lodash@4.17.21`
26445
26580
  ).action(async (options) => {
26446
26581
  await runRepositoryListCommand(options);
26447
26582
  });
@@ -26449,6 +26584,12 @@ function createListCommand2() {
26449
26584
  function createFilterCommand() {
26450
26585
  return addRepositoryFilterOptions(
26451
26586
  new Command("filter").description("Filter repositories in your organization")
26587
+ ).addHelpText(
26588
+ "after",
26589
+ `
26590
+ Examples:
26591
+ tracecli repo filter --filter "platform=GITHUB" --filter "status=CONNECTED"
26592
+ tracecli repo filter --language typescript --output json`
26452
26593
  ).action(async (options) => {
26453
26594
  await runRepositoryListCommand(options);
26454
26595
  });
@@ -26570,8 +26711,8 @@ function createRepoCommand() {
26570
26711
  }
26571
26712
 
26572
26713
  // src/vulnerabilities/commands.ts
26573
- var DEFAULT_LIMIT2 = 30;
26574
- var MAX_LIMIT2 = 100;
26714
+ var DEFAULT_LIMIT3 = 30;
26715
+ var MAX_LIMIT3 = 100;
26575
26716
  async function resolveRepositoryIdAuthenticated(organizationId, repoIdentifier) {
26576
26717
  const result = await authenticatedRequest(
26577
26718
  (client) => client.repository.listRepositories({
@@ -26680,13 +26821,13 @@ function createListCommand3() {
26680
26821
  (value) => value.toUpperCase()
26681
26822
  ).option(
26682
26823
  "-L, --limit <n>",
26683
- `Max results to return (default: ${DEFAULT_LIMIT2}, max: ${MAX_LIMIT2})`,
26824
+ `Max results to return (default: ${DEFAULT_LIMIT3}, max: ${MAX_LIMIT3})`,
26684
26825
  (v) => {
26685
26826
  const n = Number.parseInt(v, 10);
26686
26827
  if (Number.isNaN(n) || n < 1) {
26687
26828
  throw new Error("--limit must be a positive integer");
26688
26829
  }
26689
- return Math.min(n, MAX_LIMIT2);
26830
+ return Math.min(n, MAX_LIMIT3);
26690
26831
  }
26691
26832
  ).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
26692
26833
  "after",
@@ -26717,7 +26858,7 @@ Examples:
26717
26858
  sortBy: "severity",
26718
26859
  sortOrder: "desc"
26719
26860
  };
26720
- const limit = options.limit ?? DEFAULT_LIMIT2;
26861
+ const limit = options.limit ?? DEFAULT_LIMIT3;
26721
26862
  const isJson = options.output === "json";
26722
26863
  const isTsv = options.output === "tsv";
26723
26864
  const collected = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securewithtrace",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Official Trace CLI for vulnerability intelligence in your terminal",
5
5
  "type": "module",
6
6
  "bin": {