securewithtrace 0.21.7 → 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 +400 -87
- 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
|
@@ -24600,6 +24600,15 @@ var ScanMode2 = external_exports.enum(ScanModeValues2);
|
|
|
24600
24600
|
var VulnerabilityIdParamsSchema = OrgIdParamsSchema.extend({
|
|
24601
24601
|
vulnerabilityId: external_exports.uuid()
|
|
24602
24602
|
});
|
|
24603
|
+
var VulnerabilityLookupIdSchema = external_exports.union([
|
|
24604
|
+
external_exports.uuid(),
|
|
24605
|
+
external_exports.string().regex(/^(?:TRACE-)?0*[1-9]\d*$/i, {
|
|
24606
|
+
message: "vulnerabilityId must be a UUID, a positive integer, or TRACE-<number>"
|
|
24607
|
+
})
|
|
24608
|
+
]);
|
|
24609
|
+
var VulnerabilityLookupIdParamsSchema = OrgIdParamsSchema.extend({
|
|
24610
|
+
vulnerabilityId: VulnerabilityLookupIdSchema
|
|
24611
|
+
});
|
|
24603
24612
|
var VulnerabilitySourceSchema = external_exports.object({
|
|
24604
24613
|
branch: external_exports.string().nullable(),
|
|
24605
24614
|
commitSha: external_exports.string().nullable(),
|
|
@@ -24802,9 +24811,10 @@ var vulnerabilityContract = c17.router({
|
|
|
24802
24811
|
getVulnerability: {
|
|
24803
24812
|
method: "GET",
|
|
24804
24813
|
path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId",
|
|
24805
|
-
pathParams:
|
|
24814
|
+
pathParams: VulnerabilityLookupIdParamsSchema,
|
|
24806
24815
|
responses: {
|
|
24807
24816
|
200: VulnerabilityResponseSchema,
|
|
24817
|
+
400: ErrorResponseSchema,
|
|
24808
24818
|
401: ErrorResponseSchema,
|
|
24809
24819
|
403: ErrorResponseSchema,
|
|
24810
24820
|
404: ErrorResponseSchema,
|
|
@@ -24814,7 +24824,7 @@ var vulnerabilityContract = c17.router({
|
|
|
24814
24824
|
updateVulnerability: {
|
|
24815
24825
|
method: "PATCH",
|
|
24816
24826
|
path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId",
|
|
24817
|
-
pathParams:
|
|
24827
|
+
pathParams: VulnerabilityLookupIdParamsSchema,
|
|
24818
24828
|
body: UpdateVulnerabilityRequestSchema,
|
|
24819
24829
|
responses: {
|
|
24820
24830
|
200: VulnerabilityResponseSchema,
|
|
@@ -24842,7 +24852,7 @@ var vulnerabilityContract = c17.router({
|
|
|
24842
24852
|
deleteVulnerability: {
|
|
24843
24853
|
method: "DELETE",
|
|
24844
24854
|
path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId",
|
|
24845
|
-
pathParams:
|
|
24855
|
+
pathParams: VulnerabilityLookupIdParamsSchema,
|
|
24846
24856
|
responses: {
|
|
24847
24857
|
204: DeleteResponseSchema,
|
|
24848
24858
|
401: ErrorResponseSchema,
|
|
@@ -24854,7 +24864,7 @@ var vulnerabilityContract = c17.router({
|
|
|
24854
24864
|
retestVulnerability: {
|
|
24855
24865
|
method: "POST",
|
|
24856
24866
|
path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId/retest",
|
|
24857
|
-
pathParams:
|
|
24867
|
+
pathParams: VulnerabilityLookupIdParamsSchema,
|
|
24858
24868
|
body: null,
|
|
24859
24869
|
responses: {
|
|
24860
24870
|
201: ScanResponseSchema,
|
|
@@ -24881,7 +24891,7 @@ var vulnerabilityContract = c17.router({
|
|
|
24881
24891
|
getVulnerabilityRetests: {
|
|
24882
24892
|
method: "GET",
|
|
24883
24893
|
path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId/retests",
|
|
24884
|
-
pathParams:
|
|
24894
|
+
pathParams: VulnerabilityLookupIdParamsSchema,
|
|
24885
24895
|
responses: {
|
|
24886
24896
|
200: ScanArrayResponseSchema,
|
|
24887
24897
|
401: ErrorResponseSchema,
|
|
@@ -24893,10 +24903,10 @@ var vulnerabilityContract = c17.router({
|
|
|
24893
24903
|
});
|
|
24894
24904
|
|
|
24895
24905
|
// ../../packages/api-contracts/src/schemas/api/v1/vulnerability-note.api.ts
|
|
24896
|
-
var
|
|
24897
|
-
vulnerabilityId:
|
|
24906
|
+
var VulnerabilityLookupNoteParamsSchema = OrgIdParamsSchema.extend({
|
|
24907
|
+
vulnerabilityId: VulnerabilityLookupIdSchema
|
|
24898
24908
|
});
|
|
24899
|
-
var
|
|
24909
|
+
var VulnerabilityLookupNoteIdParamsSchema = VulnerabilityLookupNoteParamsSchema.extend({
|
|
24900
24910
|
noteId: external_exports.uuid()
|
|
24901
24911
|
});
|
|
24902
24912
|
var VulnerabilityNoteResponseSchema = external_exports.object({
|
|
@@ -24924,9 +24934,10 @@ var vulnerabilityNoteContract = c18.router({
|
|
|
24924
24934
|
listNotes: {
|
|
24925
24935
|
method: "GET",
|
|
24926
24936
|
path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId/notes",
|
|
24927
|
-
pathParams:
|
|
24937
|
+
pathParams: VulnerabilityLookupNoteParamsSchema,
|
|
24928
24938
|
responses: {
|
|
24929
24939
|
200: ListVulnerabilityNotesResponseSchema,
|
|
24940
|
+
400: ErrorResponseSchema,
|
|
24930
24941
|
401: ErrorResponseSchema,
|
|
24931
24942
|
403: ErrorResponseSchema,
|
|
24932
24943
|
404: ErrorResponseSchema,
|
|
@@ -24936,7 +24947,7 @@ var vulnerabilityNoteContract = c18.router({
|
|
|
24936
24947
|
createNote: {
|
|
24937
24948
|
method: "POST",
|
|
24938
24949
|
path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId/notes",
|
|
24939
|
-
pathParams:
|
|
24950
|
+
pathParams: VulnerabilityLookupNoteParamsSchema,
|
|
24940
24951
|
body: CreateVulnerabilityNoteRequestSchema,
|
|
24941
24952
|
responses: {
|
|
24942
24953
|
201: VulnerabilityNoteResponseSchema,
|
|
@@ -24950,7 +24961,7 @@ var vulnerabilityNoteContract = c18.router({
|
|
|
24950
24961
|
updateNote: {
|
|
24951
24962
|
method: "PATCH",
|
|
24952
24963
|
path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId/notes/:noteId",
|
|
24953
|
-
pathParams:
|
|
24964
|
+
pathParams: VulnerabilityLookupNoteIdParamsSchema,
|
|
24954
24965
|
body: UpdateVulnerabilityNoteRequestSchema,
|
|
24955
24966
|
responses: {
|
|
24956
24967
|
200: VulnerabilityNoteResponseSchema,
|
|
@@ -24964,9 +24975,10 @@ var vulnerabilityNoteContract = c18.router({
|
|
|
24964
24975
|
deleteNote: {
|
|
24965
24976
|
method: "DELETE",
|
|
24966
24977
|
path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId/notes/:noteId",
|
|
24967
|
-
pathParams:
|
|
24978
|
+
pathParams: VulnerabilityLookupNoteIdParamsSchema,
|
|
24968
24979
|
responses: {
|
|
24969
24980
|
204: DeleteResponseSchema,
|
|
24981
|
+
400: ErrorResponseSchema,
|
|
24970
24982
|
401: ErrorResponseSchema,
|
|
24971
24983
|
403: ErrorResponseSchema,
|
|
24972
24984
|
404: ErrorResponseSchema,
|
|
@@ -25051,7 +25063,7 @@ var WebhookEventTypeSchema = external_exports.enum([
|
|
|
25051
25063
|
]);
|
|
25052
25064
|
var WebhookEndpointStatusSchema = external_exports.enum(["enabled", "disabled"]);
|
|
25053
25065
|
var WebhookDeliveryStatusSchema = external_exports.enum(["succeeded", "failed"]);
|
|
25054
|
-
var WebhookDeliverySourceSchema = external_exports.enum(["test", "mock"]);
|
|
25066
|
+
var WebhookDeliverySourceSchema = external_exports.enum(["test", "mock", "event"]);
|
|
25055
25067
|
var WebhookEndpointSchema = external_exports.object({
|
|
25056
25068
|
webhookId: external_exports.uuid(),
|
|
25057
25069
|
organizationId: external_exports.uuid(),
|
|
@@ -26535,6 +26547,44 @@ function getClientId() {
|
|
|
26535
26547
|
);
|
|
26536
26548
|
}
|
|
26537
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
|
+
}
|
|
26538
26588
|
function sanitizeForOutput(value) {
|
|
26539
26589
|
return value.replace(/[\r\n\t]/g, " ").trim();
|
|
26540
26590
|
}
|
|
@@ -26784,12 +26834,25 @@ ${colorSuccess(`Authenticated as ${displayName} (${tokenResponse.user.email})`)}
|
|
|
26784
26834
|
});
|
|
26785
26835
|
}
|
|
26786
26836
|
function createLogoutCommand() {
|
|
26787
|
-
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(
|
|
26788
26838
|
"after",
|
|
26789
26839
|
`
|
|
26790
26840
|
Examples:
|
|
26791
|
-
tracecli auth logout
|
|
26792
|
-
|
|
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
|
+
}
|
|
26793
26856
|
const removed = removeToken();
|
|
26794
26857
|
if (removed) {
|
|
26795
26858
|
console.log(
|
|
@@ -26845,6 +26908,9 @@ Examples:
|
|
|
26845
26908
|
const info = buildAuthStatusInfo(token, refreshToken, selectedOrganizationId);
|
|
26846
26909
|
if (options.output === "json") {
|
|
26847
26910
|
console.log(JSON.stringify(info, null, 2));
|
|
26911
|
+
if (!info.loggedIn) {
|
|
26912
|
+
process.exit(1);
|
|
26913
|
+
}
|
|
26848
26914
|
return;
|
|
26849
26915
|
}
|
|
26850
26916
|
if (options.output === "tsv") {
|
|
@@ -26859,11 +26925,14 @@ Examples:
|
|
|
26859
26925
|
info.activeOrg ?? ""
|
|
26860
26926
|
].join(" ")
|
|
26861
26927
|
);
|
|
26928
|
+
if (!info.loggedIn) {
|
|
26929
|
+
process.exit(1);
|
|
26930
|
+
}
|
|
26862
26931
|
return;
|
|
26863
26932
|
}
|
|
26864
26933
|
if (!info.loggedIn) {
|
|
26865
|
-
console.
|
|
26866
|
-
|
|
26934
|
+
console.error(colorError("Not logged in. Run `tracecli auth login` to authenticate."));
|
|
26935
|
+
process.exit(1);
|
|
26867
26936
|
}
|
|
26868
26937
|
console.log("Logged in to Trace");
|
|
26869
26938
|
if (info.userId) {
|
|
@@ -26903,7 +26972,12 @@ function createOrgListCommand() {
|
|
|
26903
26972
|
throw new Error("--limit must be a positive integer");
|
|
26904
26973
|
}
|
|
26905
26974
|
return n;
|
|
26906
|
-
}).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(
|
|
26907
26981
|
"after",
|
|
26908
26982
|
`
|
|
26909
26983
|
Examples:
|
|
@@ -26911,70 +26985,88 @@ Examples:
|
|
|
26911
26985
|
tracecli org list --output json
|
|
26912
26986
|
tracecli org list --output tsv | cut -f1
|
|
26913
26987
|
tracecli org list --limit 5
|
|
26988
|
+
tracecli org list --filter "name:acme"
|
|
26989
|
+
tracecli org list --filter "slug!=internal"
|
|
26914
26990
|
tracecli org list --web`
|
|
26915
|
-
).action(
|
|
26916
|
-
|
|
26917
|
-
|
|
26918
|
-
|
|
26919
|
-
|
|
26920
|
-
|
|
26921
|
-
|
|
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;
|
|
26922
27001
|
}
|
|
26923
|
-
|
|
26924
|
-
|
|
26925
|
-
|
|
26926
|
-
|
|
26927
|
-
|
|
26928
|
-
|
|
26929
|
-
|
|
26930
|
-
|
|
26931
|
-
}
|
|
26932
|
-
|
|
26933
|
-
|
|
26934
|
-
|
|
26935
|
-
|
|
26936
|
-
|
|
26937
|
-
|
|
26938
|
-
|
|
26939
|
-
|
|
26940
|
-
|
|
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) {
|
|
26941
27064
|
console.log(
|
|
26942
|
-
|
|
26943
|
-
tsvSafe(item.name),
|
|
26944
|
-
tsvSafe(item.slug),
|
|
26945
|
-
tsvSafe(item.organizationId),
|
|
26946
|
-
item.active ? "true" : "false"
|
|
26947
|
-
].join(" ")
|
|
27065
|
+
"\nYour previous organization selection is no longer available and was reset."
|
|
26948
27066
|
);
|
|
26949
27067
|
}
|
|
26950
|
-
return;
|
|
26951
|
-
}
|
|
26952
|
-
const headers = ["", "Name", "Slug", "Org ID"];
|
|
26953
|
-
const rows = items.map((item) => {
|
|
26954
|
-
const labels = [item.active ? "active" : null, item.selected ? "selected" : null].filter(
|
|
26955
|
-
Boolean
|
|
26956
|
-
);
|
|
26957
|
-
const marker = item.active ? "*" : " ";
|
|
26958
|
-
const nameSuffix = labels.length > 0 ? ` [${labels.join(", ")}]` : "";
|
|
26959
|
-
return [
|
|
26960
|
-
marker,
|
|
26961
|
-
sanitizeForOutput(item.name) + nameSuffix,
|
|
26962
|
-
sanitizeForOutput(item.slug),
|
|
26963
|
-
item.organizationId
|
|
26964
|
-
];
|
|
26965
|
-
});
|
|
26966
|
-
const colWidths = headers.map(
|
|
26967
|
-
(h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
|
|
26968
|
-
);
|
|
26969
|
-
const pad = (s, w3) => s.padEnd(w3);
|
|
26970
|
-
console.log(headers.map((h, i) => pad(h, colWidths[i] ?? h.length)).join(" "));
|
|
26971
|
-
for (const row of rows) {
|
|
26972
|
-
console.log(row.map((cell, i) => pad(cell, colWidths[i] ?? 0)).join(" "));
|
|
26973
|
-
}
|
|
26974
|
-
if (organizationContext.staleSelectionDetected) {
|
|
26975
|
-
console.log("\nYour previous organization selection is no longer available and was reset.");
|
|
26976
27068
|
}
|
|
26977
|
-
|
|
27069
|
+
);
|
|
26978
27070
|
}
|
|
26979
27071
|
function createOrgCurrentCommand() {
|
|
26980
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(
|
|
@@ -27031,15 +27123,42 @@ Examples:
|
|
|
27031
27123
|
});
|
|
27032
27124
|
}
|
|
27033
27125
|
function createOrgSwitchCommand() {
|
|
27034
|
-
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(
|
|
27035
27127
|
"after",
|
|
27036
27128
|
`
|
|
27037
27129
|
Examples:
|
|
27038
27130
|
tracecli org switch
|
|
27039
27131
|
tracecli org switch engineering
|
|
27040
|
-
tracecli org switch org_01KJTJFFG497Z1C54Z07AP6NA3
|
|
27041
|
-
|
|
27132
|
+
tracecli org switch org_01KJTJFFG497Z1C54Z07AP6NA3
|
|
27133
|
+
tracecli org switch engineering --web`
|
|
27134
|
+
).action(async (organization, options) => {
|
|
27042
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
|
+
}
|
|
27043
27162
|
if (normalizedOrganizationInput.length === 0 && (!process.stdin.isTTY || !process.stdout.isTTY)) {
|
|
27044
27163
|
console.error(
|
|
27045
27164
|
"Missing required argument 'organization'. Run `tracecli org switch <organization>` in non-interactive environments."
|
|
@@ -27705,7 +27824,7 @@ function createFindingsListCommand() {
|
|
|
27705
27824
|
}
|
|
27706
27825
|
return Math.min(n, MAX_LIMIT);
|
|
27707
27826
|
}
|
|
27708
|
-
).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(
|
|
27709
27828
|
"after",
|
|
27710
27829
|
`
|
|
27711
27830
|
Examples:
|
|
@@ -27719,7 +27838,9 @@ Examples:
|
|
|
27719
27838
|
async (options) => {
|
|
27720
27839
|
if (options.web) {
|
|
27721
27840
|
const slug = await resolveOrganizationSlug();
|
|
27722
|
-
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}`;
|
|
27723
27844
|
const opened = await openBrowser(url2);
|
|
27724
27845
|
if (!opened) {
|
|
27725
27846
|
console.log(`Open in browser: ${url2}`);
|
|
@@ -27873,6 +27994,7 @@ function extractRepoName(input) {
|
|
|
27873
27994
|
var BRANCH_NAME_PATTERN = /^[a-zA-Z0-9._/+-]+$/;
|
|
27874
27995
|
var TRACE_GIT_ASKPASS_TOKEN_ENV = "TRACE_GIT_ASKPASS_TOKEN";
|
|
27875
27996
|
var DEFAULT_LIMIT2 = 30;
|
|
27997
|
+
var DEFAULT_PAGE = 1;
|
|
27876
27998
|
var MAX_LIMIT2 = 100;
|
|
27877
27999
|
var REPOSITORY_PLATFORMS = ["GITHUB", "GITLAB", "BITBUCKET"];
|
|
27878
28000
|
var REPOSITORY_STATUSES = ["CONNECTED", "DISCONNECTED"];
|
|
@@ -28090,6 +28212,44 @@ function parseListLimit(value) {
|
|
|
28090
28212
|
}
|
|
28091
28213
|
return n;
|
|
28092
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
|
+
}
|
|
28093
28253
|
function printRepositoriesTsv(repositories) {
|
|
28094
28254
|
console.log("repositoryUrl platform status defaultBranch language");
|
|
28095
28255
|
for (const repo of repositories) {
|
|
@@ -28126,6 +28286,19 @@ function collectFilterExpression(value, previous) {
|
|
|
28126
28286
|
previous.push(value);
|
|
28127
28287
|
return previous;
|
|
28128
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
|
+
}
|
|
28129
28302
|
function extractErrorMessage(body) {
|
|
28130
28303
|
if (typeof body === "object" && body !== null && "message" in body && typeof body.message === "string") {
|
|
28131
28304
|
const message = body.message.trim();
|
|
@@ -28251,6 +28424,11 @@ function addRepositoryFilterOptions(command) {
|
|
|
28251
28424
|
parseListLimit,
|
|
28252
28425
|
DEFAULT_LIMIT2
|
|
28253
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(
|
|
28254
28432
|
"--dependency <pkg>",
|
|
28255
28433
|
"Filter repositories that use a dependency (e.g. lodash, lodash@4.17.21, @types/node@20)"
|
|
28256
28434
|
).option("-w, --web", "Open the repositories dashboard in a browser");
|
|
@@ -28302,8 +28480,17 @@ async function runRepositoryListCommand(options) {
|
|
|
28302
28480
|
console.log("No repositories found.");
|
|
28303
28481
|
return;
|
|
28304
28482
|
}
|
|
28305
|
-
|
|
28306
|
-
|
|
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
|
+
}
|
|
28307
28494
|
if (options.output === "json") {
|
|
28308
28495
|
console.log(JSON.stringify(displayedRepositories, null, 2));
|
|
28309
28496
|
return;
|
|
@@ -28313,6 +28500,11 @@ async function runRepositoryListCommand(options) {
|
|
|
28313
28500
|
return;
|
|
28314
28501
|
}
|
|
28315
28502
|
printRepositoriesTable(displayedRepositories);
|
|
28503
|
+
printPaginationHint(
|
|
28504
|
+
pagination,
|
|
28505
|
+
"repositories",
|
|
28506
|
+
options.filter.length > 0 ? " with the same --filter flags" : ""
|
|
28507
|
+
);
|
|
28316
28508
|
}
|
|
28317
28509
|
async function runDependencyFilter(organizationId, options) {
|
|
28318
28510
|
const dependency = options.dependency;
|
|
@@ -28366,8 +28558,17 @@ async function runDependencyFilter(organizationId, options) {
|
|
|
28366
28558
|
console.log(`No repositories found using dependency '${label}'.`);
|
|
28367
28559
|
return;
|
|
28368
28560
|
}
|
|
28369
|
-
|
|
28370
|
-
|
|
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
|
+
}
|
|
28371
28572
|
if (options.output === "json") {
|
|
28372
28573
|
console.log(JSON.stringify(displayedRepos, null, 2));
|
|
28373
28574
|
return;
|
|
@@ -28383,6 +28584,7 @@ async function runDependencyFilter(organizationId, options) {
|
|
|
28383
28584
|
const versions = repo.versions.join(", ");
|
|
28384
28585
|
console.log(`${repo.fullName} (${versions})`);
|
|
28385
28586
|
}
|
|
28587
|
+
printPaginationHint(pagination, "repositories", "");
|
|
28386
28588
|
}
|
|
28387
28589
|
function createListCommand2() {
|
|
28388
28590
|
return addRepositoryFilterOptions(
|
|
@@ -28393,6 +28595,8 @@ function createListCommand2() {
|
|
|
28393
28595
|
Examples:
|
|
28394
28596
|
tracecli repo list
|
|
28395
28597
|
tracecli repo list --platform GITHUB --status CONNECTED
|
|
28598
|
+
tracecli repo list --page 2 --limit 30
|
|
28599
|
+
tracecli repo list --all
|
|
28396
28600
|
tracecli repo list --output json | jq '.[].repositoryUrl'
|
|
28397
28601
|
tracecli repo list --output tsv | cut -f1
|
|
28398
28602
|
tracecli repo list --filter "language:typescript" --limit 10
|
|
@@ -28410,7 +28614,11 @@ function createFilterCommand() {
|
|
|
28410
28614
|
`
|
|
28411
28615
|
Examples:
|
|
28412
28616
|
tracecli repo filter --filter "platform=GITHUB" --filter "status=CONNECTED"
|
|
28413
|
-
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
|
|
28414
28622
|
tracecli repo filter --web`
|
|
28415
28623
|
).action(async (options) => {
|
|
28416
28624
|
await runRepositoryListCommand(options);
|
|
@@ -29119,6 +29327,110 @@ function printVulnerabilitiesTsv(vulnerabilities) {
|
|
|
29119
29327
|
console.log(cols.join(" "));
|
|
29120
29328
|
}
|
|
29121
29329
|
}
|
|
29330
|
+
function printVulnerabilityDetail(vulnerability) {
|
|
29331
|
+
const severity = colorSeverity(vulnerability.severity, formatSeverity(vulnerability.severity));
|
|
29332
|
+
const status = formatStatus(vulnerability.status);
|
|
29333
|
+
const metadata = [
|
|
29334
|
+
`Type: ${vulnerability.vulnerabilityType}`,
|
|
29335
|
+
vulnerability.scanMode ? `Scan mode: ${vulnerability.scanMode}` : null,
|
|
29336
|
+
vulnerability.cweId ? vulnerability.cweId : null
|
|
29337
|
+
].filter((value) => value !== null);
|
|
29338
|
+
const sourceDetails = [
|
|
29339
|
+
vulnerability.source.branch ? `Branch: ${vulnerability.source.branch}` : null,
|
|
29340
|
+
vulnerability.source.pullRequestNumber !== null ? `PR #${vulnerability.source.pullRequestNumber}${vulnerability.source.pullRequestTitle ? ` (${vulnerability.source.pullRequestTitle})` : ""}` : null
|
|
29341
|
+
].filter((value) => value !== null);
|
|
29342
|
+
console.log(` [${severity}] ${vulnerability.traceId} ${vulnerability.title}`);
|
|
29343
|
+
console.log(
|
|
29344
|
+
` ${vulnerability.repositoryName} \xB7 ${vulnerability.codeSnippet.filePath}:${vulnerability.codeSnippet.startLine} \xB7 ${status}`
|
|
29345
|
+
);
|
|
29346
|
+
if (metadata.length > 0) {
|
|
29347
|
+
console.log(` ${metadata.join(" | ")}`);
|
|
29348
|
+
}
|
|
29349
|
+
if (sourceDetails.length > 0) {
|
|
29350
|
+
console.log(` ${sourceDetails.join(" | ")}`);
|
|
29351
|
+
}
|
|
29352
|
+
console.log(` Vulnerability ID: ${vulnerability.vulnerabilityId}`);
|
|
29353
|
+
console.log("");
|
|
29354
|
+
console.log(vulnerability.description);
|
|
29355
|
+
}
|
|
29356
|
+
function printVulnerabilityDetailTsv(vulnerability) {
|
|
29357
|
+
console.log(
|
|
29358
|
+
"traceId vulnerabilityId severity status repository file line cweId scanMode title description"
|
|
29359
|
+
);
|
|
29360
|
+
const cols = [
|
|
29361
|
+
vulnerability.traceId,
|
|
29362
|
+
vulnerability.vulnerabilityId,
|
|
29363
|
+
vulnerability.severity,
|
|
29364
|
+
vulnerability.status,
|
|
29365
|
+
vulnerability.repositoryName,
|
|
29366
|
+
vulnerability.codeSnippet.filePath,
|
|
29367
|
+
String(vulnerability.codeSnippet.startLine),
|
|
29368
|
+
vulnerability.cweId ?? "",
|
|
29369
|
+
vulnerability.scanMode ?? "",
|
|
29370
|
+
vulnerability.title,
|
|
29371
|
+
vulnerability.description
|
|
29372
|
+
].map(sanitizeTsvField);
|
|
29373
|
+
console.log(cols.join(" "));
|
|
29374
|
+
}
|
|
29375
|
+
function createGetCommand() {
|
|
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(
|
|
29377
|
+
"after",
|
|
29378
|
+
`
|
|
29379
|
+
Examples:
|
|
29380
|
+
tracecli vulnerabilities get TRACE-123
|
|
29381
|
+
tracecli vulnerabilities get 123
|
|
29382
|
+
tracecli vulnerabilities get 00000000-0000-4000-8000-000000000123
|
|
29383
|
+
tracecli vulnerabilities get TRACE-123 --output json
|
|
29384
|
+
tracecli vulnerabilities get TRACE-123 --output tsv
|
|
29385
|
+
tracecli vulnerabilities get TRACE-123 --web`
|
|
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
|
+
}
|
|
29396
|
+
const organizationId = await resolveOrganizationId();
|
|
29397
|
+
const result = await authenticatedRequest(
|
|
29398
|
+
(client) => client.vulnerability.getVulnerability({
|
|
29399
|
+
params: { organizationId, vulnerabilityId: identifier }
|
|
29400
|
+
})
|
|
29401
|
+
);
|
|
29402
|
+
if (result.status === 401) {
|
|
29403
|
+
console.error("Authentication failed. Run `tracecli auth login` to re-authenticate.");
|
|
29404
|
+
process.exit(1);
|
|
29405
|
+
}
|
|
29406
|
+
if (result.status === 400) {
|
|
29407
|
+
console.error(
|
|
29408
|
+
`Invalid vulnerability identifier '${identifier}'. Use TRACE-<number>, a positive integer, or a UUID.`
|
|
29409
|
+
);
|
|
29410
|
+
process.exit(1);
|
|
29411
|
+
}
|
|
29412
|
+
if (result.status === 404) {
|
|
29413
|
+
console.error(
|
|
29414
|
+
`Vulnerability '${identifier}' not found.
|
|
29415
|
+
Run \`tracecli vulnerabilities list\` to see available vulnerabilities.`
|
|
29416
|
+
);
|
|
29417
|
+
process.exit(1);
|
|
29418
|
+
}
|
|
29419
|
+
if (result.status !== 200) {
|
|
29420
|
+
console.error(`Failed to fetch vulnerability (status ${result.status}).`);
|
|
29421
|
+
process.exit(1);
|
|
29422
|
+
}
|
|
29423
|
+
if (options.output === "json") {
|
|
29424
|
+
console.log(JSON.stringify(result.body, null, 2));
|
|
29425
|
+
return;
|
|
29426
|
+
}
|
|
29427
|
+
if (options.output === "tsv") {
|
|
29428
|
+
printVulnerabilityDetailTsv(result.body);
|
|
29429
|
+
return;
|
|
29430
|
+
}
|
|
29431
|
+
printVulnerabilityDetail(result.body);
|
|
29432
|
+
});
|
|
29433
|
+
}
|
|
29122
29434
|
function createListCommand3() {
|
|
29123
29435
|
return new Command("list").description("List vulnerabilities for your organization").option("--repo <repo>", "Filter by repository name or owner/repo").option(
|
|
29124
29436
|
"--severity <severity>",
|
|
@@ -29246,6 +29558,7 @@ Examples:
|
|
|
29246
29558
|
}
|
|
29247
29559
|
function createVulnerabilitiesCommand() {
|
|
29248
29560
|
const vulnerabilities = new Command("vulnerabilities").description("Manage vulnerabilities");
|
|
29561
|
+
vulnerabilities.addCommand(createGetCommand());
|
|
29249
29562
|
vulnerabilities.addCommand(createListCommand3());
|
|
29250
29563
|
return vulnerabilities;
|
|
29251
29564
|
}
|