securewithtrace 0.22.0 → 0.23.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 (3) hide show
  1. package/README.md +27 -5
  2. package/dist/index.js +283 -77
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -66,7 +66,8 @@ tracecli vulnerabilities list --severity HIGH
66
66
  ```bash
67
67
  tracecli auth login
68
68
  tracecli auth status
69
- tracecli auth logout
69
+ tracecli auth logout # prompts for confirmation interactively
70
+ tracecli auth logout --no-confirm # skip prompt (for CI/scripts)
70
71
  ```
71
72
 
72
73
  ### Organization context
@@ -86,14 +87,23 @@ When your account belongs to multiple organizations, use `tracecli org switch` t
86
87
  # List org repositories known to Trace
87
88
  tracecli repo list
88
89
 
90
+ # Show the next page of repositories
91
+ tracecli repo list --page 2 --limit 30
92
+
93
+ # Show all matching repositories
94
+ tracecli repo list --all
95
+
89
96
  # List only connected GitHub repositories that match "trace"
90
97
  tracecli repo list --platform GITHUB --status CONNECTED --query trace
91
98
 
92
99
  # Advanced filtering syntax (repeatable)
93
100
  tracecli repo list --filter "platform=GITHUB" --filter "repo:trace" --filter "status!=DISCONNECTED"
94
101
 
95
- # Alias command for filtering
102
+ # Alias command for filtering (all repo list flags are supported)
96
103
  tracecli repo filter --filter "language:typescript" --filter "branch=main"
104
+ tracecli repo filter --language typescript --limit 10
105
+ tracecli repo filter --output json | jq '.[].repositoryUrl'
106
+ tracecli repo filter --output tsv | cut -f1
97
107
 
98
108
  # Find repositories that use a specific dependency
99
109
  tracecli repo list --dependency lodash
@@ -105,7 +115,7 @@ tracecli repo list --dependency lodash@4.17.21
105
115
  tracecli repo list --dependency @types/node@20
106
116
 
107
117
  # Dependency results as JSON
108
- tracecli repo list --dependency react --json | jq '.[].fullName'
118
+ tracecli repo list --dependency react --output json | jq '.[].fullName'
109
119
 
110
120
  # Clone by owner/repo shorthand
111
121
  tracecli repo clone securewithtrace/trace
@@ -117,6 +127,16 @@ tracecli repo clone securewithtrace/trace --branch main --depth 1
117
127
  ### Vulnerabilities
118
128
 
119
129
  ```bash
130
+ # Fetch a single vulnerability by display ID, numeric ID, or UUID
131
+ tracecli vulnerabilities get TRACE-123
132
+
133
+ # Open the vulnerability detail page in a browser
134
+ tracecli vulnerabilities get TRACE-123 --web
135
+
136
+ # JSON or TSV output for scripting
137
+ tracecli vulnerabilities get TRACE-123 --output json
138
+ tracecli vulnerabilities get TRACE-123 --output tsv | cut -f1,10
139
+
120
140
  # List vulnerabilities across your organization (default: 30 results)
121
141
  tracecli vulnerabilities list
122
142
 
@@ -173,6 +193,8 @@ Repository filter options:
173
193
  - Supported fields: `platform`, `status`, `url`, `repo`, `branch`, `language`
174
194
  - Supported operators: `=` (exact), `!=` (not exact), `:` (contains), `!:` (not contains)
175
195
  - `--dependency`: filter by dependency name with optional `@version` (e.g. `lodash`, `lodash@4.17`, `@types/node@20`)
196
+ - `--page`: choose which page of results to display when using `--limit` (default: 1)
197
+ - `--all`: display all matching repositories (disables pagination)
176
198
 
177
199
  ### Pentests
178
200
 
@@ -198,7 +220,7 @@ tracecli pentest findings list --pentest PT-3 --output json
198
220
  # Findings as TSV
199
221
  tracecli pentest findings list --pentest PT-3 --output tsv | cut -f1,4
200
222
 
201
- # Open pentests dashboard in browser
223
+ # Open the pentest detail page in a browser
202
224
  tracecli pentest findings list --pentest PT-3 --web
203
225
  ```
204
226
 
@@ -216,7 +238,7 @@ Flags for `pentest findings list`:
216
238
  |------|-----------|-------------|
217
239
  | `--pentest <id>` | | Pentest display ID (e.g. `PT-3`) or UUID (required) |
218
240
  | `--output <format>` | `-o` | Output format: `table` (default), `json`, `tsv` |
219
- | `--web` | `-w` | Open pentests dashboard in browser |
241
+ | `--web` | `-w` | Open the pentest detail page in a browser |
220
242
 
221
243
  ### Upgrades
222
244
 
package/dist/index.js CHANGED
@@ -25063,7 +25063,7 @@ var WebhookEventTypeSchema = external_exports.enum([
25063
25063
  ]);
25064
25064
  var WebhookEndpointStatusSchema = external_exports.enum(["enabled", "disabled"]);
25065
25065
  var WebhookDeliveryStatusSchema = external_exports.enum(["succeeded", "failed"]);
25066
- var WebhookDeliverySourceSchema = external_exports.enum(["test", "mock"]);
25066
+ var WebhookDeliverySourceSchema = external_exports.enum(["test", "mock", "event"]);
25067
25067
  var WebhookEndpointSchema = external_exports.object({
25068
25068
  webhookId: external_exports.uuid(),
25069
25069
  organizationId: external_exports.uuid(),
@@ -26547,6 +26547,44 @@ function getClientId() {
26547
26547
  );
26548
26548
  }
26549
26549
  var SAFE_ORGANIZATION_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
26550
+ var ORG_FILTER_FIELDS = ["name", "slug"];
26551
+ function parseOrgFilter(expression) {
26552
+ const match = expression.match(/^([^=!:]+)(!=|!:|=|:)(.*)$/);
26553
+ if (!match || !match[1] || !match[2] || match[3] === void 0) {
26554
+ throw new Error(
26555
+ `Invalid filter expression: '${expression}'. Format: <field><operator><value> (e.g. name:acme)`
26556
+ );
26557
+ }
26558
+ const field = match[1].trim();
26559
+ const operator = match[2];
26560
+ const value = match[3].trim();
26561
+ if (!ORG_FILTER_FIELDS.includes(field)) {
26562
+ throw new Error(
26563
+ `Unknown filter field '${field}'. Supported fields: ${ORG_FILTER_FIELDS.join(", ")}`
26564
+ );
26565
+ }
26566
+ if (value.length === 0) {
26567
+ throw new Error(
26568
+ `Invalid filter expression: '${expression}'. Value cannot be empty after the operator.`
26569
+ );
26570
+ }
26571
+ return { field, operator, value };
26572
+ }
26573
+ function matchesOrgFilter(item, filter) {
26574
+ const raw = filter.field === "name" ? item.name : item.slug;
26575
+ const cellLower = raw.toLowerCase();
26576
+ const valueLower = filter.value.toLowerCase();
26577
+ switch (filter.operator) {
26578
+ case "=":
26579
+ return cellLower === valueLower;
26580
+ case "!=":
26581
+ return cellLower !== valueLower;
26582
+ case ":":
26583
+ return cellLower.includes(valueLower);
26584
+ case "!:":
26585
+ return !cellLower.includes(valueLower);
26586
+ }
26587
+ }
26550
26588
  function sanitizeForOutput(value) {
26551
26589
  return value.replace(/[\r\n\t]/g, " ").trim();
26552
26590
  }
@@ -26796,12 +26834,25 @@ ${colorSuccess(`Authenticated as ${displayName} (${tokenResponse.user.email})`)}
26796
26834
  });
26797
26835
  }
26798
26836
  function createLogoutCommand() {
26799
- return new Command("logout").description("Log out of Trace").addHelpText(
26837
+ return new Command("logout").description("Log out of Trace").option("--no-confirm", "Skip confirmation prompt (for CI/scripts)").addHelpText(
26800
26838
  "after",
26801
26839
  `
26802
26840
  Examples:
26803
- tracecli auth logout`
26804
- ).action(() => {
26841
+ tracecli auth logout
26842
+ tracecli auth logout --no-confirm`
26843
+ ).action(async (options) => {
26844
+ if (options.confirm && process.stdin.isTTY && process.stdout.isTTY) {
26845
+ const readline = createInterface2({ input: process.stdin, output: process.stdout });
26846
+ try {
26847
+ const answer = await readline.question("Are you sure you want to log out? [Y/n] ");
26848
+ if (answer.trim().toLowerCase() === "n" || answer.trim().toLowerCase() === "no") {
26849
+ console.log("Logout cancelled.");
26850
+ return;
26851
+ }
26852
+ } finally {
26853
+ readline.close();
26854
+ }
26855
+ }
26805
26856
  const removed = removeToken();
26806
26857
  if (removed) {
26807
26858
  console.log(
@@ -26857,6 +26908,9 @@ Examples:
26857
26908
  const info = buildAuthStatusInfo(token, refreshToken, selectedOrganizationId);
26858
26909
  if (options.output === "json") {
26859
26910
  console.log(JSON.stringify(info, null, 2));
26911
+ if (!info.loggedIn) {
26912
+ process.exit(1);
26913
+ }
26860
26914
  return;
26861
26915
  }
26862
26916
  if (options.output === "tsv") {
@@ -26871,11 +26925,14 @@ Examples:
26871
26925
  info.activeOrg ?? ""
26872
26926
  ].join(" ")
26873
26927
  );
26928
+ if (!info.loggedIn) {
26929
+ process.exit(1);
26930
+ }
26874
26931
  return;
26875
26932
  }
26876
26933
  if (!info.loggedIn) {
26877
- console.log(colorError("Not logged in. Run `tracecli auth login` to authenticate."));
26878
- return;
26934
+ console.error(colorError("Not logged in. Run `tracecli auth login` to authenticate."));
26935
+ process.exit(1);
26879
26936
  }
26880
26937
  console.log("Logged in to Trace");
26881
26938
  if (info.userId) {
@@ -26915,7 +26972,12 @@ function createOrgListCommand() {
26915
26972
  throw new Error("--limit must be a positive integer");
26916
26973
  }
26917
26974
  return n;
26918
- }).option("-w, --web", "Open the organization settings in a browser").addHelpText(
26975
+ }).option(
26976
+ "-f, --filter <expression>",
26977
+ "Filter expression (repeatable). Fields: name, slug. Operators: = exact, != not exact, : contains, !: not contains",
26978
+ (val, prev) => [...prev, val],
26979
+ []
26980
+ ).option("-w, --web", "Open the organization settings in a browser").addHelpText(
26919
26981
  "after",
26920
26982
  `
26921
26983
  Examples:
@@ -26923,70 +26985,88 @@ Examples:
26923
26985
  tracecli org list --output json
26924
26986
  tracecli org list --output tsv | cut -f1
26925
26987
  tracecli org list --limit 5
26988
+ tracecli org list --filter "name:acme"
26989
+ tracecli org list --filter "slug!=internal"
26926
26990
  tracecli org list --web`
26927
- ).action(async (options) => {
26928
- if (options.web) {
26929
- const slug = await resolveOrganizationSlug();
26930
- const url2 = `${getAppBaseUrl()}/dashboard/${slug}/settings`;
26931
- const opened = await openBrowser(url2);
26932
- if (!opened) {
26933
- console.log(`Open in browser: ${url2}`);
26991
+ ).action(
26992
+ async (options) => {
26993
+ if (options.web) {
26994
+ const slug = await resolveOrganizationSlug();
26995
+ const url2 = `${getAppBaseUrl()}/dashboard/${slug}/settings`;
26996
+ const opened = await openBrowser(url2);
26997
+ if (!opened) {
26998
+ console.log(`Open in browser: ${url2}`);
26999
+ }
27000
+ return;
26934
27001
  }
26935
- return;
26936
- }
26937
- const organizationContext = await fetchOrganizationContext();
26938
- const allItems = organizationContext.organizations.map(
26939
- (organization) => ({
26940
- ...organization,
26941
- active: organization.organizationId === organizationContext.activeOrganization.organizationId,
26942
- selected: organization.organizationId === organizationContext.selectedOrganizationId
26943
- })
26944
- );
26945
- const items = options.limit !== void 0 ? allItems.slice(0, options.limit) : allItems;
26946
- if (options.output === "json") {
26947
- console.log(JSON.stringify(items, null, 2));
26948
- return;
26949
- }
26950
- if (options.output === "tsv") {
26951
- console.log("name slug organizationId active");
26952
- for (const item of items) {
27002
+ const parsedFilters = [];
27003
+ for (const expr of options.filter) {
27004
+ try {
27005
+ parsedFilters.push(parseOrgFilter(expr));
27006
+ } catch (err) {
27007
+ console.error(err instanceof Error ? err.message : String(err));
27008
+ process.exit(1);
27009
+ }
27010
+ }
27011
+ const organizationContext = await fetchOrganizationContext();
27012
+ const allItems = organizationContext.organizations.map(
27013
+ (organization) => ({
27014
+ ...organization,
27015
+ active: organization.organizationId === organizationContext.activeOrganization.organizationId,
27016
+ selected: organization.organizationId === organizationContext.selectedOrganizationId
27017
+ })
27018
+ );
27019
+ const filteredItems = allItems.filter(
27020
+ (item) => parsedFilters.every((f) => matchesOrgFilter(item, f))
27021
+ );
27022
+ const items = options.limit !== void 0 ? filteredItems.slice(0, options.limit) : filteredItems;
27023
+ if (options.output === "json") {
27024
+ console.log(JSON.stringify(items, null, 2));
27025
+ return;
27026
+ }
27027
+ if (options.output === "tsv") {
27028
+ console.log("name slug organizationId active");
27029
+ for (const item of items) {
27030
+ console.log(
27031
+ [
27032
+ tsvSafe(item.name),
27033
+ tsvSafe(item.slug),
27034
+ tsvSafe(item.organizationId),
27035
+ item.active ? "true" : "false"
27036
+ ].join(" ")
27037
+ );
27038
+ }
27039
+ return;
27040
+ }
27041
+ const headers = ["", "Name", "Slug", "Org ID"];
27042
+ const rows = items.map((item) => {
27043
+ const labels = [item.active ? "active" : null, item.selected ? "selected" : null].filter(
27044
+ Boolean
27045
+ );
27046
+ const marker = item.active ? "*" : " ";
27047
+ const nameSuffix = labels.length > 0 ? ` [${labels.join(", ")}]` : "";
27048
+ return [
27049
+ marker,
27050
+ sanitizeForOutput(item.name) + nameSuffix,
27051
+ sanitizeForOutput(item.slug),
27052
+ item.organizationId
27053
+ ];
27054
+ });
27055
+ const colWidths = headers.map(
27056
+ (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
27057
+ );
27058
+ 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
+ }
27063
+ if (organizationContext.staleSelectionDetected) {
26953
27064
  console.log(
26954
- [
26955
- tsvSafe(item.name),
26956
- tsvSafe(item.slug),
26957
- tsvSafe(item.organizationId),
26958
- item.active ? "true" : "false"
26959
- ].join(" ")
27065
+ "\nYour previous organization selection is no longer available and was reset."
26960
27066
  );
26961
27067
  }
26962
- return;
26963
27068
  }
26964
- const headers = ["", "Name", "Slug", "Org ID"];
26965
- const rows = items.map((item) => {
26966
- const labels = [item.active ? "active" : null, item.selected ? "selected" : null].filter(
26967
- Boolean
26968
- );
26969
- const marker = item.active ? "*" : " ";
26970
- const nameSuffix = labels.length > 0 ? ` [${labels.join(", ")}]` : "";
26971
- return [
26972
- marker,
26973
- sanitizeForOutput(item.name) + nameSuffix,
26974
- sanitizeForOutput(item.slug),
26975
- item.organizationId
26976
- ];
26977
- });
26978
- const colWidths = headers.map(
26979
- (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
26980
- );
26981
- const pad = (s, w3) => s.padEnd(w3);
26982
- console.log(headers.map((h, i) => pad(h, colWidths[i] ?? h.length)).join(" "));
26983
- for (const row of rows) {
26984
- console.log(row.map((cell, i) => pad(cell, colWidths[i] ?? 0)).join(" "));
26985
- }
26986
- if (organizationContext.staleSelectionDetected) {
26987
- console.log("\nYour previous organization selection is no longer available and was reset.");
26988
- }
26989
- });
27069
+ );
26990
27070
  }
26991
27071
  function createOrgCurrentCommand() {
26992
27072
  return new Command("current").description("Show the active organization used by CLI commands").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("-w, --web", "Open the organization settings in a browser").addHelpText(
@@ -27043,15 +27123,42 @@ Examples:
27043
27123
  });
27044
27124
  }
27045
27125
  function createOrgSwitchCommand() {
27046
- return new Command("switch").description("Switch the active organization for subsequent CLI commands").argument("[organization]", "Organization ID, slug, name, or menu selection index").addHelpText(
27126
+ return new Command("switch").description("Switch the active organization for subsequent CLI commands").argument("[organization]", "Organization ID, slug, name, or menu selection index").option("-w, --web", "Open the organization settings page in a browser instead of switching").addHelpText(
27047
27127
  "after",
27048
27128
  `
27049
27129
  Examples:
27050
27130
  tracecli org switch
27051
27131
  tracecli org switch engineering
27052
- tracecli org switch org_01KJTJFFG497Z1C54Z07AP6NA3`
27053
- ).action(async (organization) => {
27132
+ tracecli org switch org_01KJTJFFG497Z1C54Z07AP6NA3
27133
+ tracecli org switch engineering --web`
27134
+ ).action(async (organization, options) => {
27054
27135
  const normalizedOrganizationInput = organization?.trim() ?? "";
27136
+ if (options.web) {
27137
+ if (normalizedOrganizationInput.length > 0) {
27138
+ const organizationContext2 = await fetchOrganizationContext();
27139
+ const selection = resolveOrganizationSelectionInput(
27140
+ organizationContext2.organizations,
27141
+ normalizedOrganizationInput
27142
+ );
27143
+ if ("error" in selection) {
27144
+ console.error(selection.error);
27145
+ process.exit(1);
27146
+ }
27147
+ const url2 = `${getAppBaseUrl()}/dashboard/${selection.organization.slug}/settings`;
27148
+ const opened = await openBrowser(url2);
27149
+ if (!opened) {
27150
+ console.log(`Open in browser: ${url2}`);
27151
+ }
27152
+ } else {
27153
+ const slug = await resolveOrganizationSlug();
27154
+ const url2 = `${getAppBaseUrl()}/dashboard/${slug}/settings`;
27155
+ const opened = await openBrowser(url2);
27156
+ if (!opened) {
27157
+ console.log(`Open in browser: ${url2}`);
27158
+ }
27159
+ }
27160
+ return;
27161
+ }
27055
27162
  if (normalizedOrganizationInput.length === 0 && (!process.stdin.isTTY || !process.stdout.isTTY)) {
27056
27163
  console.error(
27057
27164
  "Missing required argument 'organization'. Run `tracecli org switch <organization>` in non-interactive environments."
@@ -27717,7 +27824,7 @@ function createFindingsListCommand() {
27717
27824
  }
27718
27825
  return Math.min(n, MAX_LIMIT);
27719
27826
  }
27720
- ).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("-w, --web", "Open the pentests dashboard in a browser").addHelpText(
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(
27721
27828
  "after",
27722
27829
  `
27723
27830
  Examples:
@@ -27731,7 +27838,9 @@ Examples:
27731
27838
  async (options) => {
27732
27839
  if (options.web) {
27733
27840
  const slug = await resolveOrganizationSlug();
27734
- const url2 = `${getAppBaseUrl()}/dashboard/${slug}/pentests`;
27841
+ const numericMatch = options.pentest.match(/^(?:PT-)?(\d+)$/i);
27842
+ const pentestUrlId = numericMatch?.[1] ?? options.pentest;
27843
+ const url2 = `${getAppBaseUrl()}/dashboard/${slug}/pentests/${pentestUrlId}`;
27735
27844
  const opened = await openBrowser(url2);
27736
27845
  if (!opened) {
27737
27846
  console.log(`Open in browser: ${url2}`);
@@ -27885,6 +27994,7 @@ function extractRepoName(input) {
27885
27994
  var BRANCH_NAME_PATTERN = /^[a-zA-Z0-9._/+-]+$/;
27886
27995
  var TRACE_GIT_ASKPASS_TOKEN_ENV = "TRACE_GIT_ASKPASS_TOKEN";
27887
27996
  var DEFAULT_LIMIT2 = 30;
27997
+ var DEFAULT_PAGE = 1;
27888
27998
  var MAX_LIMIT2 = 100;
27889
27999
  var REPOSITORY_PLATFORMS = ["GITHUB", "GITLAB", "BITBUCKET"];
27890
28000
  var REPOSITORY_STATUSES = ["CONNECTED", "DISCONNECTED"];
@@ -28102,6 +28212,44 @@ function parseListLimit(value) {
28102
28212
  }
28103
28213
  return n;
28104
28214
  }
28215
+ function parseListPage(value) {
28216
+ const n = Number(value);
28217
+ if (!Number.isInteger(n) || n < 1) {
28218
+ throw new Error("--page must be a positive integer");
28219
+ }
28220
+ return n;
28221
+ }
28222
+ function paginateResults(items, options) {
28223
+ if (options.all) {
28224
+ return {
28225
+ items: [...items],
28226
+ pagination: null
28227
+ };
28228
+ }
28229
+ const limit = options.limit ?? DEFAULT_LIMIT2;
28230
+ const page = options.page ?? DEFAULT_PAGE;
28231
+ const totalItems = items.length;
28232
+ const totalPages = Math.max(1, Math.ceil(totalItems / limit));
28233
+ if (page > totalPages) {
28234
+ throw new Error(
28235
+ `--page ${page} is out of range for ${totalItems} matching results with --limit ${limit}. Use --page between 1 and ${totalPages}, or pass --all.`
28236
+ );
28237
+ }
28238
+ const startIndex = (page - 1) * limit;
28239
+ const endIndex = Math.min(startIndex + limit, totalItems);
28240
+ return {
28241
+ items: items.slice(startIndex, endIndex),
28242
+ pagination: {
28243
+ totalItems,
28244
+ totalPages,
28245
+ page,
28246
+ hasNextPage: page < totalPages,
28247
+ hasPreviousPage: page > 1,
28248
+ startIndex,
28249
+ endIndex
28250
+ }
28251
+ };
28252
+ }
28105
28253
  function printRepositoriesTsv(repositories) {
28106
28254
  console.log("repositoryUrl platform status defaultBranch language");
28107
28255
  for (const repo of repositories) {
@@ -28138,6 +28286,19 @@ function collectFilterExpression(value, previous) {
28138
28286
  previous.push(value);
28139
28287
  return previous;
28140
28288
  }
28289
+ function printPaginationHint(pagination, noun, nextPageFilterHint) {
28290
+ if (!pagination || pagination.totalPages <= 1) {
28291
+ return;
28292
+ }
28293
+ const shownStart = pagination.startIndex + 1;
28294
+ const shownEnd = pagination.endIndex;
28295
+ console.log(
28296
+ `Showing ${shownStart}-${shownEnd} of ${pagination.totalItems} ${noun} (page ${pagination.page}/${pagination.totalPages}).`
28297
+ );
28298
+ if (pagination.hasNextPage) {
28299
+ console.log(`Use --page ${pagination.page + 1}${nextPageFilterHint} (or --all) to see more.`);
28300
+ }
28301
+ }
28141
28302
  function extractErrorMessage(body) {
28142
28303
  if (typeof body === "object" && body !== null && "message" in body && typeof body.message === "string") {
28143
28304
  const message = body.message.trim();
@@ -28263,6 +28424,11 @@ function addRepositoryFilterOptions(command) {
28263
28424
  parseListLimit,
28264
28425
  DEFAULT_LIMIT2
28265
28426
  ).option(
28427
+ "--page <n>",
28428
+ `Page number to display when results exceed --limit (default ${DEFAULT_PAGE})`,
28429
+ parseListPage,
28430
+ DEFAULT_PAGE
28431
+ ).option("--all", "Display all matching repositories (disables pagination)").option(
28266
28432
  "--dependency <pkg>",
28267
28433
  "Filter repositories that use a dependency (e.g. lodash, lodash@4.17.21, @types/node@20)"
28268
28434
  ).option("-w, --web", "Open the repositories dashboard in a browser");
@@ -28314,8 +28480,17 @@ async function runRepositoryListCommand(options) {
28314
28480
  console.log("No repositories found.");
28315
28481
  return;
28316
28482
  }
28317
- const limit = options.limit ?? DEFAULT_LIMIT2;
28318
- const displayedRepositories = filteredRepositories.slice(0, limit);
28483
+ let displayedRepositories;
28484
+ let pagination = null;
28485
+ try {
28486
+ const paginatedResult = paginateResults(filteredRepositories, options);
28487
+ displayedRepositories = paginatedResult.items;
28488
+ pagination = paginatedResult.pagination;
28489
+ } catch (error48) {
28490
+ const message = error48 instanceof Error ? error48.message : "Unknown pagination error.";
28491
+ console.error(`Invalid pagination options: ${message}`);
28492
+ process.exit(1);
28493
+ }
28319
28494
  if (options.output === "json") {
28320
28495
  console.log(JSON.stringify(displayedRepositories, null, 2));
28321
28496
  return;
@@ -28325,6 +28500,11 @@ async function runRepositoryListCommand(options) {
28325
28500
  return;
28326
28501
  }
28327
28502
  printRepositoriesTable(displayedRepositories);
28503
+ printPaginationHint(
28504
+ pagination,
28505
+ "repositories",
28506
+ options.filter.length > 0 ? " with the same --filter flags" : ""
28507
+ );
28328
28508
  }
28329
28509
  async function runDependencyFilter(organizationId, options) {
28330
28510
  const dependency = options.dependency;
@@ -28378,8 +28558,17 @@ async function runDependencyFilter(organizationId, options) {
28378
28558
  console.log(`No repositories found using dependency '${label}'.`);
28379
28559
  return;
28380
28560
  }
28381
- const limit = options.limit ?? DEFAULT_LIMIT2;
28382
- const displayedRepos = repos.slice(0, limit);
28561
+ let displayedRepos;
28562
+ let pagination = null;
28563
+ try {
28564
+ const paginatedResult = paginateResults(repos, options);
28565
+ displayedRepos = paginatedResult.items;
28566
+ pagination = paginatedResult.pagination;
28567
+ } catch (error48) {
28568
+ const message = error48 instanceof Error ? error48.message : "Unknown pagination error.";
28569
+ console.error(`Invalid pagination options: ${message}`);
28570
+ process.exit(1);
28571
+ }
28383
28572
  if (options.output === "json") {
28384
28573
  console.log(JSON.stringify(displayedRepos, null, 2));
28385
28574
  return;
@@ -28395,6 +28584,7 @@ async function runDependencyFilter(organizationId, options) {
28395
28584
  const versions = repo.versions.join(", ");
28396
28585
  console.log(`${repo.fullName} (${versions})`);
28397
28586
  }
28587
+ printPaginationHint(pagination, "repositories", "");
28398
28588
  }
28399
28589
  function createListCommand2() {
28400
28590
  return addRepositoryFilterOptions(
@@ -28405,6 +28595,8 @@ function createListCommand2() {
28405
28595
  Examples:
28406
28596
  tracecli repo list
28407
28597
  tracecli repo list --platform GITHUB --status CONNECTED
28598
+ tracecli repo list --page 2 --limit 30
28599
+ tracecli repo list --all
28408
28600
  tracecli repo list --output json | jq '.[].repositoryUrl'
28409
28601
  tracecli repo list --output tsv | cut -f1
28410
28602
  tracecli repo list --filter "language:typescript" --limit 10
@@ -28422,7 +28614,11 @@ function createFilterCommand() {
28422
28614
  `
28423
28615
  Examples:
28424
28616
  tracecli repo filter --filter "platform=GITHUB" --filter "status=CONNECTED"
28425
- tracecli repo filter --language typescript --output json
28617
+ tracecli repo filter --page 2 --limit 30
28618
+ tracecli repo filter --all
28619
+ tracecli repo filter --language typescript --limit 10
28620
+ tracecli repo filter --output json | jq '.[].repositoryUrl'
28621
+ tracecli repo filter --output tsv | cut -f1
28426
28622
  tracecli repo filter --web`
28427
28623
  ).action(async (options) => {
28428
28624
  await runRepositoryListCommand(options);
@@ -29177,7 +29373,7 @@ function printVulnerabilityDetailTsv(vulnerability) {
29177
29373
  console.log(cols.join(" "));
29178
29374
  }
29179
29375
  function createGetCommand() {
29180
- return new Command("get").description("Get a vulnerability by display ID or UUID").argument("<id>", "Vulnerability display ID (e.g. TRACE-123 or 123) or UUID").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
29376
+ return new Command("get").description("Get a vulnerability by display ID or UUID").argument("<id>", "Vulnerability display ID (e.g. TRACE-123 or 123) or UUID").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("-w, --web", "Open the vulnerability detail page in a browser").addHelpText(
29181
29377
  "after",
29182
29378
  `
29183
29379
  Examples:
@@ -29185,8 +29381,18 @@ Examples:
29185
29381
  tracecli vulnerabilities get 123
29186
29382
  tracecli vulnerabilities get 00000000-0000-4000-8000-000000000123
29187
29383
  tracecli vulnerabilities get TRACE-123 --output json
29188
- tracecli vulnerabilities get TRACE-123 --output tsv`
29384
+ tracecli vulnerabilities get TRACE-123 --output tsv
29385
+ tracecli vulnerabilities get TRACE-123 --web`
29189
29386
  ).action(async (identifier, options) => {
29387
+ if (options.web) {
29388
+ const slug = await resolveOrganizationSlug();
29389
+ const url2 = `${getAppBaseUrl()}/dashboard/${slug}/vulnerabilities/${identifier}`;
29390
+ const opened = await openBrowser(url2);
29391
+ if (!opened) {
29392
+ console.log(`Open in browser: ${url2}`);
29393
+ }
29394
+ return;
29395
+ }
29190
29396
  const organizationId = await resolveOrganizationId();
29191
29397
  const result = await authenticatedRequest(
29192
29398
  (client) => client.vulnerability.getVulnerability({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securewithtrace",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Official Trace CLI for vulnerability intelligence in your terminal",
5
5
  "type": "module",
6
6
  "bin": {