securewithtrace 0.20.1 → 0.20.3

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 +142 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -26173,6 +26173,7 @@ function colorAccentUnderline(text, colorizer = source_default) {
26173
26173
  var readTokenSafe = readToken;
26174
26174
  var PRODUCTION_API_URL = "https://api.securewithtrace.com";
26175
26175
  var UNAUTHORIZED_STATUS = 401;
26176
+ var REFRESH_AUTH_FAILURE_STATUS_CODES = /* @__PURE__ */ new Set([400, 401]);
26176
26177
  function getApiBaseUrl() {
26177
26178
  const raw = process.env["TRACE_API_URL"];
26178
26179
  if (!raw) {
@@ -26217,9 +26218,24 @@ function getWorkosClientId() {
26217
26218
  }
26218
26219
  return clientId;
26219
26220
  }
26221
+ function parseRefreshFailureStatus(error48) {
26222
+ if (!(error48 instanceof Error)) {
26223
+ return null;
26224
+ }
26225
+ const statusMatch = error48.message.match(/\b([1-5]\d{2})\b/);
26226
+ if (!statusMatch?.[1]) {
26227
+ return null;
26228
+ }
26229
+ return Number.parseInt(statusMatch[1], 10);
26230
+ }
26231
+ function isRefreshAuthFailure(error48) {
26232
+ const status = parseRefreshFailureStatus(error48);
26233
+ return status !== null && REFRESH_AUTH_FAILURE_STATUS_CODES.has(status);
26234
+ }
26220
26235
  async function refreshStoredAccessToken() {
26221
26236
  const clientId = getWorkosClientId();
26222
26237
  const refreshToken = readRefreshToken();
26238
+ const tokenOrganizationId = extractOrganizationIdFromToken(readToken());
26223
26239
  if (!clientId || !refreshToken) {
26224
26240
  return null;
26225
26241
  }
@@ -26229,7 +26245,20 @@ async function refreshStoredAccessToken() {
26229
26245
  writeTokens(tokenResponse.access_token, nextRefreshToken);
26230
26246
  return tokenResponse.access_token;
26231
26247
  } catch (error48) {
26232
- if (error48 instanceof Error && (error48.message.includes(" 400 ") || error48.message.includes(" 401 "))) {
26248
+ if (tokenOrganizationId && isRefreshAuthFailure(error48)) {
26249
+ try {
26250
+ const tokenResponse = await refreshAccessToken(clientId, refreshToken, tokenOrganizationId);
26251
+ const nextRefreshToken = tokenResponse.refresh_token ?? refreshToken;
26252
+ writeTokens(tokenResponse.access_token, nextRefreshToken);
26253
+ return tokenResponse.access_token;
26254
+ } catch (orgRefreshError) {
26255
+ if (isRefreshAuthFailure(orgRefreshError)) {
26256
+ removeToken();
26257
+ }
26258
+ return null;
26259
+ }
26260
+ }
26261
+ if (isRefreshAuthFailure(error48)) {
26233
26262
  removeToken();
26234
26263
  }
26235
26264
  return null;
@@ -26247,7 +26276,7 @@ async function refreshStoredAccessTokenForOrganization(workosOrganizationId) {
26247
26276
  writeTokens(tokenResponse.access_token, nextRefreshToken);
26248
26277
  return true;
26249
26278
  } catch (error48) {
26250
- if (error48 instanceof Error && (error48.message.includes(" 400 ") || error48.message.includes(" 401 "))) {
26279
+ if (isRefreshAuthFailure(error48)) {
26251
26280
  removeToken();
26252
26281
  }
26253
26282
  return false;
@@ -26383,6 +26412,32 @@ async function resolveOrganizationId(client) {
26383
26412
  }
26384
26413
  return id;
26385
26414
  }
26415
+ async function resolveOrganizationSlug() {
26416
+ const result = await authenticatedRequest((client) => client.organization.listMyOrganizations());
26417
+ if (result.status === 401) {
26418
+ console.error(
26419
+ colorError("Authentication failed. Run `tracecli auth login` to re-authenticate.")
26420
+ );
26421
+ process.exit(1);
26422
+ }
26423
+ if (result.status !== 200) {
26424
+ console.error(colorError(`Failed to resolve organization (status ${result.status}).`));
26425
+ process.exit(1);
26426
+ }
26427
+ const organizations = result.body;
26428
+ const selectedOrganizationId = readSelectedOrganizationId();
26429
+ const tokenOrganizationId = extractOrganizationIdFromToken(readToken());
26430
+ const activeOrganization = determineActiveOrganization(
26431
+ organizations,
26432
+ selectedOrganizationId,
26433
+ tokenOrganizationId
26434
+ );
26435
+ if (!activeOrganization) {
26436
+ console.error(colorError("No organization found for your account."));
26437
+ process.exit(1);
26438
+ }
26439
+ return activeOrganization.organization.slug;
26440
+ }
26386
26441
  async function resolveOrganizationIdSafe(client) {
26387
26442
  try {
26388
26443
  const result = client ? await client.organization.listMyOrganizations() : await authenticatedRequestSafe(
@@ -26502,6 +26557,10 @@ function isSafeOrganizationId(organizationId) {
26502
26557
  }
26503
26558
  async function fetchOrganizationContext() {
26504
26559
  const result = await authenticatedRequest((client) => client.organization.listMyOrganizations());
26560
+ if (result.status === 401) {
26561
+ console.error("Authentication failed. Run `tracecli auth login` to re-authenticate.");
26562
+ process.exit(1);
26563
+ }
26505
26564
  if (result.status !== 200) {
26506
26565
  console.error(`Failed to list organizations (status ${result.status}).`);
26507
26566
  process.exit(1);
@@ -26828,22 +26887,30 @@ Examples:
26828
26887
  });
26829
26888
  }
26830
26889
  function createOrgListCommand() {
26831
- return new Command("list").description("List organizations available to your account").option("-o, --output <format>", "Output format: table (default), json, tsv").addHelpText(
26890
+ return new Command("list").description("List organizations available to your account").option("-o, --output <format>", "Output format: table (default), json, tsv").option("-L, --limit <n>", "Max organizations to display (default: all)", (v) => {
26891
+ const n = Number.parseInt(v, 10);
26892
+ if (Number.isNaN(n) || n < 1) {
26893
+ throw new Error("--limit must be a positive integer");
26894
+ }
26895
+ return n;
26896
+ }).addHelpText(
26832
26897
  "after",
26833
26898
  `
26834
26899
  Examples:
26835
26900
  tracecli org list
26836
26901
  tracecli org list --output json
26837
- tracecli org list --output tsv | cut -f1`
26902
+ tracecli org list --output tsv | cut -f1
26903
+ tracecli org list --limit 5`
26838
26904
  ).action(async (options) => {
26839
26905
  const organizationContext = await fetchOrganizationContext();
26840
- const items = organizationContext.organizations.map(
26906
+ const allItems = organizationContext.organizations.map(
26841
26907
  (organization) => ({
26842
26908
  ...organization,
26843
26909
  active: organization.organizationId === organizationContext.activeOrganization.organizationId,
26844
26910
  selected: organization.organizationId === organizationContext.selectedOrganizationId
26845
26911
  })
26846
26912
  );
26913
+ const items = options.limit !== void 0 ? allItems.slice(0, options.limit) : allItems;
26847
26914
  if (options.output === "json") {
26848
26915
  console.log(JSON.stringify(items, null, 2));
26849
26916
  return;
@@ -26862,15 +26929,27 @@ Examples:
26862
26929
  }
26863
26930
  return;
26864
26931
  }
26865
- for (const item of items) {
26866
- const marker = item.active ? "*" : " ";
26932
+ const headers = ["", "Name", "Slug", "Org ID"];
26933
+ const rows = items.map((item) => {
26867
26934
  const labels = [item.active ? "active" : null, item.selected ? "selected" : null].filter(
26868
26935
  Boolean
26869
26936
  );
26870
- const suffix = labels.length > 0 ? ` [${labels.join(", ")}]` : "";
26871
- console.log(
26872
- `${marker} ${sanitizeForOutput(item.name)} (${sanitizeForOutput(item.slug)}) - ${item.organizationId}${suffix}`
26873
- );
26937
+ const marker = item.active ? "*" : " ";
26938
+ const nameSuffix = labels.length > 0 ? ` [${labels.join(", ")}]` : "";
26939
+ return [
26940
+ marker,
26941
+ sanitizeForOutput(item.name) + nameSuffix,
26942
+ sanitizeForOutput(item.slug),
26943
+ item.organizationId
26944
+ ];
26945
+ });
26946
+ const colWidths = headers.map(
26947
+ (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
26948
+ );
26949
+ const pad = (s, w3) => s.padEnd(w3);
26950
+ console.log(headers.map((h, i) => pad(h, colWidths[i] ?? h.length)).join(" "));
26951
+ for (const row of rows) {
26952
+ console.log(row.map((cell, i) => pad(cell, colWidths[i] ?? 0)).join(" "));
26874
26953
  }
26875
26954
  if (organizationContext.staleSelectionDetected) {
26876
26955
  console.log("\nYour previous organization selection is no longer available and was reset.");
@@ -27698,6 +27777,41 @@ function createPentestCommand() {
27698
27777
  import { chmodSync, mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
27699
27778
  import { tmpdir } from "os";
27700
27779
  import { join as join4 } from "path";
27780
+
27781
+ // src/browser.ts
27782
+ var DEFAULT_APP_BASE_URL = "https://app.securewithtrace.com";
27783
+ function getAppBaseUrl() {
27784
+ const override = process.env["TRACE_APP_URL"];
27785
+ if (typeof override === "string" && override.trim().length > 0) {
27786
+ try {
27787
+ const parsed = new URL(override);
27788
+ if (parsed.protocol === "https:") {
27789
+ return override.replace(/\/+$/, "");
27790
+ }
27791
+ } catch {
27792
+ }
27793
+ }
27794
+ return DEFAULT_APP_BASE_URL;
27795
+ }
27796
+ async function openBrowser2(url2) {
27797
+ try {
27798
+ const parsed = new URL(url2);
27799
+ if (parsed.protocol !== "https:") {
27800
+ return false;
27801
+ }
27802
+ const proc = Bun.spawn(["open", url2], { stdout: "ignore", stderr: "ignore" });
27803
+ const exitCode = await proc.exited;
27804
+ if (exitCode !== 0) {
27805
+ const linuxProc = Bun.spawn(["xdg-open", url2], { stdout: "ignore", stderr: "ignore" });
27806
+ return await linuxProc.exited === 0;
27807
+ }
27808
+ return true;
27809
+ } catch {
27810
+ return false;
27811
+ }
27812
+ }
27813
+
27814
+ // src/repo/commands.ts
27701
27815
  function stripAnsi(str) {
27702
27816
  const ESC2 = String.fromCharCode(27);
27703
27817
  const CSI2 = String.fromCharCode(155);
@@ -27984,7 +28098,9 @@ function printRepositoriesTable(repositories) {
27984
28098
  repo.status === "CONNECTED" ? "connected" : "disconnected"
27985
28099
  ]
27986
28100
  );
27987
- const colWidths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
28101
+ const colWidths = headers.map(
28102
+ (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
28103
+ );
27988
28104
  const pad = (s, w3) => s.padEnd(w3);
27989
28105
  console.log(headers.map((h, i) => pad(h, colWidths[i] ?? h.length)).join(" "));
27990
28106
  for (const row of rows) {
@@ -28122,9 +28238,18 @@ function addRepositoryFilterOptions(command) {
28122
28238
  ).option(
28123
28239
  "--dependency <pkg>",
28124
28240
  "Filter repositories that use a dependency (e.g. lodash, lodash@4.17.21, @types/node@20)"
28125
- );
28241
+ ).option("-w, --web", "Open the repositories dashboard in a browser");
28126
28242
  }
28127
28243
  async function runRepositoryListCommand(options) {
28244
+ if (options.web) {
28245
+ const slug = await resolveOrganizationSlug();
28246
+ const url2 = `${getAppBaseUrl()}/dashboard/${slug}/repositories`;
28247
+ const opened = await openBrowser2(url2);
28248
+ if (!opened) {
28249
+ console.log(`Open in browser: ${url2}`);
28250
+ }
28251
+ return;
28252
+ }
28128
28253
  const organizationId = await resolveOrganizationId();
28129
28254
  if (options.dependency) {
28130
28255
  await runDependencyFilter(organizationId, {
@@ -28256,7 +28381,8 @@ Examples:
28256
28381
  tracecli repo list --output json | jq '.[].repositoryUrl'
28257
28382
  tracecli repo list --output tsv | cut -f1
28258
28383
  tracecli repo list --filter "language:typescript" --limit 10
28259
- tracecli repo list --dependency lodash@4.17.21`
28384
+ tracecli repo list --dependency lodash@4.17.21
28385
+ tracecli repo list --web`
28260
28386
  ).action(async (options) => {
28261
28387
  await runRepositoryListCommand(options);
28262
28388
  });
@@ -28269,7 +28395,8 @@ function createFilterCommand() {
28269
28395
  `
28270
28396
  Examples:
28271
28397
  tracecli repo filter --filter "platform=GITHUB" --filter "status=CONNECTED"
28272
- tracecli repo filter --language typescript --output json`
28398
+ tracecli repo filter --language typescript --output json
28399
+ tracecli repo filter --web`
28273
28400
  ).action(async (options) => {
28274
28401
  await runRepositoryListCommand(options);
28275
28402
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securewithtrace",
3
- "version": "0.20.1",
3
+ "version": "0.20.3",
4
4
  "description": "Official Trace CLI for vulnerability intelligence in your terminal",
5
5
  "type": "module",
6
6
  "bin": {