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.
- package/README.md +27 -5
- package/dist/index.js +283 -77
- 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
|
|
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
|
|
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
|
-
|
|
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.
|
|
26878
|
-
|
|
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(
|
|
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(
|
|
26928
|
-
|
|
26929
|
-
|
|
26930
|
-
|
|
26931
|
-
|
|
26932
|
-
|
|
26933
|
-
|
|
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
|
-
|
|
26936
|
-
|
|
26937
|
-
|
|
26938
|
-
|
|
26939
|
-
|
|
26940
|
-
|
|
26941
|
-
|
|
26942
|
-
|
|
26943
|
-
}
|
|
26944
|
-
|
|
26945
|
-
|
|
26946
|
-
|
|
26947
|
-
|
|
26948
|
-
|
|
26949
|
-
|
|
26950
|
-
|
|
26951
|
-
|
|
26952
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
28318
|
-
|
|
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
|
-
|
|
28382
|
-
|
|
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 --
|
|
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({
|