securewithtrace 0.15.0 → 0.16.1

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 (3) hide show
  1. package/README.md +9 -4
  2. package/dist/index.js +184 -43
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -66,12 +66,17 @@ tracecli vulnerabilities list --severity HIGH
66
66
  tracecli auth login
67
67
  tracecli auth status
68
68
  tracecli auth logout
69
- tracecli auth org list
70
- tracecli auth org current
71
- tracecli auth org switch <organization-id-or-slug>
72
69
  ```
73
70
 
74
- When your account belongs to multiple organizations, use `tracecli auth org switch` to set the active organization context used by repository, vulnerability, and pentest commands.
71
+ ### Organization context
72
+
73
+ ```bash
74
+ tracecli org list
75
+ tracecli org current
76
+ tracecli org switch <organization-id-or-slug>
77
+ ```
78
+
79
+ When your account belongs to multiple organizations, use `tracecli org switch` to set the active organization context used by repository, vulnerability, and pentest commands.
75
80
 
76
81
  ### Repositories
77
82
 
package/dist/index.js CHANGED
@@ -22645,6 +22645,7 @@ var PatchCapabilitiesResponseSchema = external_exports.object({
22645
22645
  });
22646
22646
  var OrganizationResponseSchema = external_exports.object({
22647
22647
  organizationId: external_exports.uuid(),
22648
+ workosOrganizationId: external_exports.string().optional(),
22648
22649
  name: external_exports.string(),
22649
22650
  slug: external_exports.string(),
22650
22651
  logoUrl: external_exports.string().nullable(),
@@ -23942,7 +23943,9 @@ var VulnerabilityAggregatedCountsRequestSchema = external_exports.object({
23942
23943
  excludeStatuses: external_exports.array(VulnerabilityStatus).optional(),
23943
23944
  defaultBranchOnly: external_exports.boolean().default(true).optional(),
23944
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. */
23945
- 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()
23946
23949
  });
23947
23950
  var VulnerabilityAggregatedCountsResponseSchema = external_exports.object({
23948
23951
  byRepoAndSeverity: external_exports.array(RepoSeverityCountSchema),
@@ -24701,17 +24704,21 @@ async function pollForToken(clientId, deviceCode, options = {}) {
24701
24704
  }
24702
24705
  throw new Error("Authorization timed out. Please try again.");
24703
24706
  }
24704
- async function refreshAccessToken(clientId, refreshToken) {
24707
+ async function refreshAccessToken(clientId, refreshToken, organizationId) {
24708
+ const body = new URLSearchParams({
24709
+ grant_type: "refresh_token",
24710
+ client_id: clientId,
24711
+ refresh_token: refreshToken
24712
+ });
24713
+ if (organizationId && organizationId.trim().length > 0) {
24714
+ body.set("organization_id", organizationId);
24715
+ }
24705
24716
  const response = await fetch(WORKOS_TOKEN_URL, {
24706
24717
  method: "POST",
24707
24718
  headers: {
24708
24719
  "Content-Type": "application/x-www-form-urlencoded"
24709
24720
  },
24710
- body: new URLSearchParams({
24711
- grant_type: "refresh_token",
24712
- client_id: clientId,
24713
- refresh_token: refreshToken
24714
- })
24721
+ body
24715
24722
  });
24716
24723
  if (!response.ok) {
24717
24724
  const text = await response.text();
@@ -24899,6 +24906,24 @@ async function refreshStoredAccessToken() {
24899
24906
  return null;
24900
24907
  }
24901
24908
  }
24909
+ async function refreshStoredAccessTokenForOrganization(workosOrganizationId) {
24910
+ const clientId = getWorkosClientId();
24911
+ const refreshToken = readRefreshToken();
24912
+ if (!clientId || !refreshToken) {
24913
+ return false;
24914
+ }
24915
+ try {
24916
+ const tokenResponse = await refreshAccessToken(clientId, refreshToken, workosOrganizationId);
24917
+ const nextRefreshToken = tokenResponse.refresh_token ?? refreshToken;
24918
+ writeTokens(tokenResponse.access_token, nextRefreshToken);
24919
+ return true;
24920
+ } catch (error48) {
24921
+ if (error48 instanceof Error && (error48.message.includes(" 400 ") || error48.message.includes(" 401 "))) {
24922
+ removeToken();
24923
+ }
24924
+ return false;
24925
+ }
24926
+ }
24902
24927
  async function authenticatedRequest(request) {
24903
24928
  const token = readToken();
24904
24929
  if (!token) {
@@ -25027,6 +25052,21 @@ async function resolveOrganizationIdSafe(client) {
25027
25052
  }
25028
25053
  }
25029
25054
 
25055
+ // src/format.ts
25056
+ var SEVERITY_ORDER = {
25057
+ CRITICAL: 0,
25058
+ HIGH: 1,
25059
+ MEDIUM: 2,
25060
+ LOW: 3,
25061
+ INFORMATIONAL: 4
25062
+ };
25063
+ function formatStatus(status) {
25064
+ return status.toLowerCase().replace("_", " ");
25065
+ }
25066
+ function tsvSafe(value) {
25067
+ return (value ?? "").replace(/[\t\r\n]/g, " ");
25068
+ }
25069
+
25030
25070
  // src/auth/commands.ts
25031
25071
  function getClientId() {
25032
25072
  const clientId = "client_01KEZ01W5Q3107GTBY51WQ67HT";
@@ -25172,9 +25212,15 @@ Use an organization ID or exact slug.`
25172
25212
  };
25173
25213
  }
25174
25214
  return {
25175
- error: `Organization '${input}' not found. Run \`tracecli auth org list\` to see available organizations.`
25215
+ error: `Organization '${input}' not found. Run \`tracecli org list\` to see available organizations.`
25176
25216
  };
25177
25217
  }
25218
+ async function refreshAfterOrganizationSwitch(workosOrganizationId, refreshForOrganization = (organizationId) => refreshStoredAccessTokenForOrganization(organizationId)) {
25219
+ if (!workosOrganizationId || workosOrganizationId.trim().length === 0) {
25220
+ return false;
25221
+ }
25222
+ return refreshForOrganization(workosOrganizationId);
25223
+ }
25178
25224
  function createLoginCommand() {
25179
25225
  return new Command("login").description("Authenticate with Trace").action(async () => {
25180
25226
  const existingToken = readToken();
@@ -25283,7 +25329,14 @@ function createStatusCommand() {
25283
25329
  });
25284
25330
  }
25285
25331
  function createOrgListCommand() {
25286
- return new Command("list").description("List organizations available to your account").option("--json", "Output as JSON").action(async (options) => {
25332
+ return new Command("list").description("List organizations available to your account").option("-o, --output <format>", "Output format: table (default), json, tsv").addHelpText(
25333
+ "after",
25334
+ `
25335
+ Examples:
25336
+ tracecli org list
25337
+ tracecli org list --output json
25338
+ tracecli org list --output tsv | cut -f1`
25339
+ ).action(async (options) => {
25287
25340
  const organizationContext = await fetchOrganizationContext();
25288
25341
  const items = organizationContext.organizations.map(
25289
25342
  (organization) => ({
@@ -25292,10 +25345,23 @@ function createOrgListCommand() {
25292
25345
  selected: organization.organizationId === organizationContext.selectedOrganizationId
25293
25346
  })
25294
25347
  );
25295
- if (options.json) {
25348
+ if (options.output === "json") {
25296
25349
  console.log(JSON.stringify(items, null, 2));
25297
25350
  return;
25298
25351
  }
25352
+ if (options.output === "tsv") {
25353
+ for (const item of items) {
25354
+ console.log(
25355
+ [
25356
+ tsvSafe(item.name),
25357
+ tsvSafe(item.slug),
25358
+ tsvSafe(item.organizationId),
25359
+ item.active ? "true" : "false"
25360
+ ].join(" ")
25361
+ );
25362
+ }
25363
+ return;
25364
+ }
25299
25365
  for (const item of items) {
25300
25366
  const marker = item.active ? "*" : " ";
25301
25367
  const labels = [item.active ? "active" : null, item.selected ? "selected" : null].filter(
@@ -25312,11 +25378,18 @@ function createOrgListCommand() {
25312
25378
  });
25313
25379
  }
25314
25380
  function createOrgCurrentCommand() {
25315
- return new Command("current").description("Show the active organization used by CLI commands").option("--json", "Output as JSON").action(async (options) => {
25381
+ return new Command("current").description("Show the active organization used by CLI commands").option("-o, --output <format>", "Output format: table (default), json, tsv").addHelpText(
25382
+ "after",
25383
+ `
25384
+ Examples:
25385
+ tracecli org current
25386
+ tracecli org current --output json
25387
+ tracecli org current --output tsv`
25388
+ ).action(async (options) => {
25316
25389
  const organizationContext = await fetchOrganizationContext();
25317
25390
  const active = organizationContext.activeOrganization;
25318
25391
  const source = organizationContext.selectedOrganizationId === active.organizationId ? "selected" : "default";
25319
- if (options.json) {
25392
+ if (options.output === "json") {
25320
25393
  console.log(
25321
25394
  JSON.stringify(
25322
25395
  {
@@ -25329,6 +25402,17 @@ function createOrgCurrentCommand() {
25329
25402
  );
25330
25403
  return;
25331
25404
  }
25405
+ if (options.output === "tsv") {
25406
+ console.log(
25407
+ [
25408
+ tsvSafe(active.name),
25409
+ tsvSafe(active.slug),
25410
+ tsvSafe(active.organizationId),
25411
+ tsvSafe(source)
25412
+ ].join(" ")
25413
+ );
25414
+ return;
25415
+ }
25332
25416
  console.log(
25333
25417
  `Active organization: ${sanitizeForOutput(active.name)} (${sanitizeForOutput(active.slug)})`
25334
25418
  );
@@ -25351,10 +25435,18 @@ function createOrgSwitchCommand() {
25351
25435
  process.exit(1);
25352
25436
  }
25353
25437
  writeSelectedOrganizationId(match.organization.organizationId);
25438
+ const refreshed = await refreshAfterOrganizationSwitch(
25439
+ match.organization.workosOrganizationId
25440
+ );
25354
25441
  console.log(
25355
25442
  `Active organization set to ${sanitizeForOutput(match.organization.name)} (${sanitizeForOutput(match.organization.slug)})`
25356
25443
  );
25357
25444
  console.log(` Org ID: ${match.organization.organizationId}`);
25445
+ if (!refreshed) {
25446
+ console.warn(
25447
+ "Token refresh for the selected organization was not completed. If repository commands fail, run `tracecli auth login` again."
25448
+ );
25449
+ }
25358
25450
  });
25359
25451
  }
25360
25452
  function createOrgCommand() {
@@ -25369,7 +25461,7 @@ function createAuthCommand() {
25369
25461
  auth.addCommand(createLoginCommand());
25370
25462
  auth.addCommand(createLogoutCommand());
25371
25463
  auth.addCommand(createStatusCommand());
25372
- auth.addCommand(createOrgCommand());
25464
+ auth.addCommand(createOrgCommand(), { hidden: true });
25373
25465
  return auth;
25374
25466
  }
25375
25467
 
@@ -25755,22 +25847,10 @@ function createCompletionCommand(program3) {
25755
25847
  });
25756
25848
  }
25757
25849
 
25758
- // src/format.ts
25759
- var SEVERITY_ORDER = {
25760
- CRITICAL: 0,
25761
- HIGH: 1,
25762
- MEDIUM: 2,
25763
- LOW: 3,
25764
- INFORMATIONAL: 4
25765
- };
25766
- function formatStatus(status) {
25767
- return status.toLowerCase().replace("_", " ");
25768
- }
25769
-
25770
25850
  // src/pentest/commands.ts
25771
25851
  var DEFAULT_LIMIT = 30;
25772
25852
  var MAX_LIMIT = 100;
25773
- function tsvSafe(value) {
25853
+ function tsvSafe2(value) {
25774
25854
  return value.replace(/[\t\n\r]/g, " ");
25775
25855
  }
25776
25856
  function formatDate(date5) {
@@ -25889,7 +25969,7 @@ Examples:
25889
25969
  for (const pt of pentests) {
25890
25970
  const cols = [
25891
25971
  `PT-${pt.displayId}`,
25892
- tsvSafe(pt.title),
25972
+ tsvSafe2(pt.title),
25893
25973
  pt.status,
25894
25974
  pt.startedAt ? String(pt.startedAt) : "",
25895
25975
  pt.endedAt ? String(pt.endedAt) : "",
@@ -25972,8 +26052,8 @@ Examples:
25972
26052
  const cols = [
25973
26053
  finding.severity,
25974
26054
  finding.status,
25975
- tsvSafe(finding.category),
25976
- tsvSafe(finding.title),
26055
+ tsvSafe2(finding.category),
26056
+ tsvSafe2(finding.title),
25977
26057
  finding.cweIds.join(";"),
25978
26058
  finding.cvssScore != null ? String(finding.cvssScore) : ""
25979
26059
  ];
@@ -26061,6 +26141,8 @@ function extractRepoName(input) {
26061
26141
  }
26062
26142
  var BRANCH_NAME_PATTERN = /^[a-zA-Z0-9._/+-]+$/;
26063
26143
  var TRACE_GIT_ASKPASS_TOKEN_ENV = "TRACE_GIT_ASKPASS_TOKEN";
26144
+ var DEFAULT_LIMIT2 = 30;
26145
+ var MAX_LIMIT2 = 100;
26064
26146
  var REPOSITORY_PLATFORMS = ["GITHUB", "GITLAB", "BITBUCKET"];
26065
26147
  var REPOSITORY_STATUSES = ["CONNECTED", "DISCONNECTED"];
26066
26148
  function validateBranchName(branch) {
@@ -26267,6 +26349,29 @@ function filterRepositories(repositories, options) {
26267
26349
  return true;
26268
26350
  });
26269
26351
  }
26352
+ function parseListLimit(value) {
26353
+ const n = Number(value);
26354
+ if (!Number.isInteger(n) || n < 1) {
26355
+ throw new Error("--limit must be a positive integer");
26356
+ }
26357
+ if (n > MAX_LIMIT2) {
26358
+ throw new Error(`--limit cannot exceed ${MAX_LIMIT2}`);
26359
+ }
26360
+ return n;
26361
+ }
26362
+ function printRepositoriesTsv(repositories) {
26363
+ for (const repo of repositories) {
26364
+ console.log(
26365
+ [
26366
+ tsvSafe(repo.repositoryUrl),
26367
+ tsvSafe(repo.platform),
26368
+ tsvSafe(repo.status),
26369
+ tsvSafe(repo.defaultBranch),
26370
+ tsvSafe(repo.primaryLanguage)
26371
+ ].join(" ")
26372
+ );
26373
+ }
26374
+ }
26270
26375
  function collectFilterExpression(value, previous) {
26271
26376
  previous.push(value);
26272
26377
  return previous;
@@ -26291,11 +26396,16 @@ function addRepositoryFilterOptions(command) {
26291
26396
  "Filter by status (CONNECTED, DISCONNECTED)",
26292
26397
  (value) => value.toUpperCase()
26293
26398
  ).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(
26294
- "--filter <expression>",
26295
- "Advanced filter expression. Repeatable. Examples: platform=GITHUB, repo:trace, status!=DISCONNECTED, language!:typescript",
26399
+ "-f, --filter <expression>",
26400
+ "Advanced filter expression. Repeatable. Examples: platform=GITHUB, repo:trace, status!=DISCONNECTED",
26296
26401
  collectFilterExpression,
26297
26402
  []
26298
- ).option("--json", "Output as JSON").option(
26403
+ ).option("-o, --output <format>", "Output format: table (default), json, tsv").option(
26404
+ "-L, --limit <n>",
26405
+ `Maximum repositories to display (default ${DEFAULT_LIMIT2}, max ${MAX_LIMIT2})`,
26406
+ parseListLimit,
26407
+ DEFAULT_LIMIT2
26408
+ ).option(
26299
26409
  "--dependency <pkg>",
26300
26410
  "Filter repositories that use a dependency (e.g. lodash, lodash@4.17.21, @types/node@20)"
26301
26411
  );
@@ -26334,11 +26444,17 @@ async function runRepositoryListCommand(options) {
26334
26444
  console.log("No repositories found.");
26335
26445
  return;
26336
26446
  }
26337
- if (options.json) {
26338
- console.log(JSON.stringify(filteredRepositories, null, 2));
26447
+ const limit = options.limit ?? DEFAULT_LIMIT2;
26448
+ const displayedRepositories = filteredRepositories.slice(0, limit);
26449
+ if (options.output === "json") {
26450
+ console.log(JSON.stringify(displayedRepositories, null, 2));
26451
+ return;
26452
+ }
26453
+ if (options.output === "tsv") {
26454
+ printRepositoriesTsv(displayedRepositories);
26339
26455
  return;
26340
26456
  }
26341
- for (const repo of filteredRepositories) {
26457
+ for (const repo of displayedRepositories) {
26342
26458
  const status = repo.status === "CONNECTED" ? "connected" : "disconnected";
26343
26459
  const url2 = stripAnsi(String(repo.repositoryUrl));
26344
26460
  const platform = stripAnsi(String(repo.platform)).toLowerCase();
@@ -26393,11 +26509,19 @@ async function runDependencyFilter(organizationId, options) {
26393
26509
  console.log(`No repositories found using dependency '${label}'.`);
26394
26510
  return;
26395
26511
  }
26396
- if (options.json) {
26397
- console.log(JSON.stringify(repos, null, 2));
26512
+ const limit = options.limit ?? DEFAULT_LIMIT2;
26513
+ const displayedRepos = repos.slice(0, limit);
26514
+ if (options.output === "json") {
26515
+ console.log(JSON.stringify(displayedRepos, null, 2));
26398
26516
  return;
26399
26517
  }
26400
- for (const repo of repos) {
26518
+ if (options.output === "tsv") {
26519
+ for (const repo of displayedRepos) {
26520
+ console.log(`${tsvSafe(repo.fullName)} ${tsvSafe(repo.versions.join(","))}`);
26521
+ }
26522
+ return;
26523
+ }
26524
+ for (const repo of displayedRepos) {
26401
26525
  const versions = repo.versions.join(", ");
26402
26526
  console.log(`${repo.fullName} (${versions})`);
26403
26527
  }
@@ -26405,6 +26529,16 @@ async function runDependencyFilter(organizationId, options) {
26405
26529
  function createListCommand2() {
26406
26530
  return addRepositoryFilterOptions(
26407
26531
  new Command("list").description("List repositories in your organization")
26532
+ ).addHelpText(
26533
+ "after",
26534
+ `
26535
+ Examples:
26536
+ tracecli repo list
26537
+ tracecli repo list --platform GITHUB --status CONNECTED
26538
+ tracecli repo list --output json | jq '.[].repositoryUrl'
26539
+ tracecli repo list --output tsv | cut -f1
26540
+ tracecli repo list --filter "language:typescript" --limit 10
26541
+ tracecli repo list --dependency lodash@4.17.21`
26408
26542
  ).action(async (options) => {
26409
26543
  await runRepositoryListCommand(options);
26410
26544
  });
@@ -26412,6 +26546,12 @@ function createListCommand2() {
26412
26546
  function createFilterCommand() {
26413
26547
  return addRepositoryFilterOptions(
26414
26548
  new Command("filter").description("Filter repositories in your organization")
26549
+ ).addHelpText(
26550
+ "after",
26551
+ `
26552
+ Examples:
26553
+ tracecli repo filter --filter "platform=GITHUB" --filter "status=CONNECTED"
26554
+ tracecli repo filter --language typescript --output json`
26415
26555
  ).action(async (options) => {
26416
26556
  await runRepositoryListCommand(options);
26417
26557
  });
@@ -26533,8 +26673,8 @@ function createRepoCommand() {
26533
26673
  }
26534
26674
 
26535
26675
  // src/vulnerabilities/commands.ts
26536
- var DEFAULT_LIMIT2 = 30;
26537
- var MAX_LIMIT2 = 100;
26676
+ var DEFAULT_LIMIT3 = 30;
26677
+ var MAX_LIMIT3 = 100;
26538
26678
  async function resolveRepositoryIdAuthenticated(organizationId, repoIdentifier) {
26539
26679
  const result = await authenticatedRequest(
26540
26680
  (client) => client.repository.listRepositories({
@@ -26643,13 +26783,13 @@ function createListCommand3() {
26643
26783
  (value) => value.toUpperCase()
26644
26784
  ).option(
26645
26785
  "-L, --limit <n>",
26646
- `Max results to return (default: ${DEFAULT_LIMIT2}, max: ${MAX_LIMIT2})`,
26786
+ `Max results to return (default: ${DEFAULT_LIMIT3}, max: ${MAX_LIMIT3})`,
26647
26787
  (v) => {
26648
26788
  const n = Number.parseInt(v, 10);
26649
26789
  if (Number.isNaN(n) || n < 1) {
26650
26790
  throw new Error("--limit must be a positive integer");
26651
26791
  }
26652
- return Math.min(n, MAX_LIMIT2);
26792
+ return Math.min(n, MAX_LIMIT3);
26653
26793
  }
26654
26794
  ).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
26655
26795
  "after",
@@ -26680,7 +26820,7 @@ Examples:
26680
26820
  sortBy: "severity",
26681
26821
  sortOrder: "desc"
26682
26822
  };
26683
- const limit = options.limit ?? DEFAULT_LIMIT2;
26823
+ const limit = options.limit ?? DEFAULT_LIMIT3;
26684
26824
  const isJson = options.output === "json";
26685
26825
  const isTsv = options.output === "tsv";
26686
26826
  const collected = [];
@@ -26746,6 +26886,7 @@ program2.action(() => {
26746
26886
  program2.outputHelp();
26747
26887
  });
26748
26888
  program2.addCommand(createAuthCommand());
26889
+ program2.addCommand(createOrgCommand());
26749
26890
  program2.addCommand(createRepoCommand());
26750
26891
  program2.addCommand(createVulnerabilitiesCommand());
26751
26892
  program2.addCommand(createPentestCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securewithtrace",
3
- "version": "0.15.0",
3
+ "version": "0.16.1",
4
4
  "description": "Official Trace CLI for vulnerability intelligence in your terminal",
5
5
  "type": "module",
6
6
  "bin": {