securewithtrace 0.3.1 → 0.5.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 (2) hide show
  1. package/dist/index.js +318 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3564,6 +3564,124 @@ function createAuthCommand() {
3564
3564
  return auth;
3565
3565
  }
3566
3566
 
3567
+ // src/banner.ts
3568
+ var ESC = "\x1B";
3569
+ var CURSOR_HIDE = `${ESC}[?25l`;
3570
+ var CURSOR_SHOW = `${ESC}[?25h`;
3571
+ function cursorUp(n) {
3572
+ return n > 0 ? `${ESC}[${n}A` : "";
3573
+ }
3574
+ var RESET = `${ESC}[0m`;
3575
+ function rgb(r, g, b) {
3576
+ return `${ESC}[38;2;${r};${g};${b}m`;
3577
+ }
3578
+ var LETTERS = {
3579
+ T: ["########", "########", " ## ", " ## ", " ## ", " ## ", " ## "],
3580
+ R: ["###### ", "## ## ", "## ## ", "###### ", "## ## ", "## ## ", "## ## "],
3581
+ A: [" #### ", " ## ## ", "## ##", "########", "## ##", "## ##", "## ##"],
3582
+ C: [" ###### ", "## ##", "## ", "## ", "## ", "## ##", " ###### "],
3583
+ E: ["########", "## ", "## ", "###### ", "## ", "## ", "########"]
3584
+ };
3585
+ var LETTER_ORDER = ["T", "R", "A", "C", "E"];
3586
+ var LETTER_GAP = " ";
3587
+ var ROWS = 7;
3588
+ function buildGrid() {
3589
+ const rows = [];
3590
+ for (let r = 0; r < ROWS; r++) {
3591
+ const parts = [];
3592
+ for (let i = 0; i < LETTER_ORDER.length; i++) {
3593
+ const letter = LETTER_ORDER[i];
3594
+ const def = LETTERS[letter];
3595
+ if (def) {
3596
+ parts.push(def[r] ?? "");
3597
+ }
3598
+ if (i < LETTER_ORDER.length - 1) {
3599
+ parts.push(LETTER_GAP);
3600
+ }
3601
+ }
3602
+ rows.push(parts.join(""));
3603
+ }
3604
+ return rows;
3605
+ }
3606
+ var SWEEP_CHARS = [" ", "\u2591", "\u2592", "\u2593", "\u2588"];
3607
+ var PALETTE = [
3608
+ { r: 80, g: 200, b: 200 },
3609
+ // bright teal
3610
+ { r: 90, g: 210, b: 210 },
3611
+ { r: 70, g: 190, b: 195 },
3612
+ { r: 100, g: 220, b: 215 },
3613
+ { r: 85, g: 205, b: 205 },
3614
+ { r: 75, g: 195, b: 200 },
3615
+ { r: 95, g: 215, b: 210 }
3616
+ ];
3617
+ function renderFrame(grid, revealCol, sweepPhase) {
3618
+ const lines = [];
3619
+ for (let r = 0; r < grid.length; r++) {
3620
+ const row = grid[r] ?? "";
3621
+ let line = "";
3622
+ const color = PALETTE[r % PALETTE.length] ?? { r: 80, g: 200, b: 200 };
3623
+ line += rgb(color.r, color.g, color.b);
3624
+ for (let c21 = 0; c21 < row.length; c21++) {
3625
+ const ch = row[c21];
3626
+ if (ch === " " || ch === void 0) {
3627
+ line += " ";
3628
+ continue;
3629
+ }
3630
+ if (c21 < revealCol) {
3631
+ line += "\u2588";
3632
+ } else if (c21 < revealCol + 4) {
3633
+ const distFromEdge = c21 - revealCol;
3634
+ const phaseIdx = Math.max(0, sweepPhase - distFromEdge);
3635
+ line += SWEEP_CHARS[Math.min(phaseIdx, SWEEP_CHARS.length - 1)] ?? " ";
3636
+ } else {
3637
+ line += " ";
3638
+ }
3639
+ }
3640
+ line += RESET;
3641
+ lines.push(line);
3642
+ }
3643
+ return lines.join("\n");
3644
+ }
3645
+ function sleep2(ms) {
3646
+ return new Promise((resolve) => setTimeout(resolve, ms));
3647
+ }
3648
+ async function showBanner() {
3649
+ const grid = buildGrid();
3650
+ const totalCols = Math.max(...grid.map((r) => r.length));
3651
+ if (!process.stdout.isTTY) {
3652
+ printStaticBanner(grid);
3653
+ return;
3654
+ }
3655
+ process.stdout.write(CURSOR_HIDE);
3656
+ const emptyFrame = grid.map(() => "").join("\n");
3657
+ process.stdout.write(`${emptyFrame}
3658
+ `);
3659
+ const sweepWidth = 4;
3660
+ const totalSteps = totalCols + sweepWidth + SWEEP_CHARS.length;
3661
+ for (let step = 0; step <= totalSteps; step++) {
3662
+ const revealCol = Math.max(0, step - SWEEP_CHARS.length);
3663
+ const sweepPhase = Math.min(step, SWEEP_CHARS.length);
3664
+ const frame = renderFrame(grid, revealCol, sweepPhase);
3665
+ process.stdout.write(`${cursorUp(grid.length)}\r`);
3666
+ process.stdout.write(`${frame}
3667
+ `);
3668
+ await sleep2(12);
3669
+ }
3670
+ process.stdout.write(CURSOR_SHOW);
3671
+ }
3672
+ function printStaticBanner(grid) {
3673
+ for (let r = 0; r < grid.length; r++) {
3674
+ const row = grid[r] ?? "";
3675
+ const color = PALETTE[r % PALETTE.length] ?? { r: 80, g: 200, b: 200 };
3676
+ let line = rgb(color.r, color.g, color.b);
3677
+ for (const ch of row) {
3678
+ line += ch === "#" ? "\u2588" : " ";
3679
+ }
3680
+ line += RESET;
3681
+ console.log(line);
3682
+ }
3683
+ }
3684
+
3567
3685
  // src/repo/commands.ts
3568
3686
  import { chmodSync, mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
3569
3687
  import { tmpdir } from "os";
@@ -23847,11 +23965,11 @@ async function resolveOrganizationIdSafe(client) {
23847
23965
 
23848
23966
  // src/repo/commands.ts
23849
23967
  function stripAnsi(str) {
23850
- const ESC = String.fromCharCode(27);
23968
+ const ESC2 = String.fromCharCode(27);
23851
23969
  const CSI = String.fromCharCode(155);
23852
23970
  const BEL = String.fromCharCode(7);
23853
- const csi = `[${ESC}${CSI}][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]`;
23854
- const osc = `${ESC}\\].*?(?:${BEL}|${ESC}\\\\)`;
23971
+ const csi = `[${ESC2}${CSI}][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]`;
23972
+ const osc = `${ESC2}\\].*?(?:${BEL}|${ESC2}\\\\)`;
23855
23973
  const pattern = new RegExp(`${osc}|${csi}`, "g");
23856
23974
  return str.replace(pattern, "");
23857
23975
  }
@@ -24030,12 +24148,209 @@ function createRepoCommand() {
24030
24148
  return repo;
24031
24149
  }
24032
24150
 
24151
+ // src/vulnerabilities/commands.ts
24152
+ import { createInterface } from "readline";
24153
+ var SEVERITY_ORDER = {
24154
+ CRITICAL: 0,
24155
+ HIGH: 1,
24156
+ MEDIUM: 2,
24157
+ LOW: 3
24158
+ };
24159
+ var DEFAULT_PAGE_SIZE = 25;
24160
+ async function resolveRepositoryId(client, organizationId, repoIdentifier) {
24161
+ const result = await client.repository.listRepositories({
24162
+ params: { organizationId }
24163
+ });
24164
+ if (result.status !== 200) {
24165
+ console.error(`Failed to list repositories (status ${result.status}).`);
24166
+ process.exit(1);
24167
+ }
24168
+ const repos = result.body;
24169
+ const match = repos.find((r) => {
24170
+ const url2 = r.repositoryUrl;
24171
+ if (url2.endsWith(`/${repoIdentifier}`)) {
24172
+ return true;
24173
+ }
24174
+ if (url2.endsWith(`/${repoIdentifier}.git`)) {
24175
+ return true;
24176
+ }
24177
+ if (url2 === repoIdentifier) {
24178
+ return true;
24179
+ }
24180
+ return false;
24181
+ });
24182
+ if (match) {
24183
+ return match.repositoryId;
24184
+ }
24185
+ const byName = repos.filter((r) => {
24186
+ const segments = r.repositoryUrl.replace(/\.git$/, "").split("/");
24187
+ const name = segments[segments.length - 1];
24188
+ return name === repoIdentifier;
24189
+ });
24190
+ if (byName.length === 1 && byName[0]) {
24191
+ return byName[0].repositoryId;
24192
+ }
24193
+ if (byName.length > 1) {
24194
+ console.error(
24195
+ `Ambiguous repository name '${repoIdentifier}'. Multiple matches found:
24196
+ ${byName.map((r) => ` - ${r.repositoryUrl}`).join("\n")}
24197
+ Please use the full owner/repo format.`
24198
+ );
24199
+ process.exit(1);
24200
+ }
24201
+ console.error(
24202
+ `Repository '${repoIdentifier}' not found.
24203
+ Run \`tracecli repo list\` to see available repositories.`
24204
+ );
24205
+ process.exit(1);
24206
+ }
24207
+ function formatSeverity(severity) {
24208
+ switch (severity) {
24209
+ case "CRITICAL":
24210
+ return "CRIT";
24211
+ case "HIGH":
24212
+ return "HIGH";
24213
+ case "MEDIUM":
24214
+ return "MED";
24215
+ case "LOW":
24216
+ return "LOW";
24217
+ default:
24218
+ return severity;
24219
+ }
24220
+ }
24221
+ function printVulnerabilities(vulnerabilities) {
24222
+ const sorted = [...vulnerabilities].sort(
24223
+ (a, b) => (SEVERITY_ORDER[a.severity] ?? 99) - (SEVERITY_ORDER[b.severity] ?? 99)
24224
+ );
24225
+ for (const vuln of sorted) {
24226
+ const sev = formatSeverity(vuln.severity);
24227
+ const status = vuln.status.toLowerCase().replace("_", " ");
24228
+ const repo = vuln.repositoryName;
24229
+ const file2 = vuln.codeSnippet.filePath;
24230
+ const line = vuln.codeSnippet.startLine;
24231
+ console.log(` [${sev}] ${vuln.title}`);
24232
+ console.log(` ${repo} \xB7 ${file2}:${line} \xB7 ${status}`);
24233
+ console.log(` ${vuln.traceId}`);
24234
+ console.log("");
24235
+ }
24236
+ }
24237
+ async function promptContinue(displayed, total) {
24238
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
24239
+ return new Promise((resolve) => {
24240
+ rl.question(`Showing ${displayed} of ${total}. Show more? [Y/n] `, (answer) => {
24241
+ rl.close();
24242
+ const trimmed = answer.trim().toLowerCase();
24243
+ resolve(trimmed === "" || trimmed === "y" || trimmed === "yes");
24244
+ });
24245
+ });
24246
+ }
24247
+ function createListCommand2() {
24248
+ return new Command("list").description("List vulnerabilities for your organization").option("--repo <repo>", "Filter by repository name or owner/repo").option(
24249
+ "--severity <severity>",
24250
+ "Filter by severity (CRITICAL, HIGH, MEDIUM, LOW)",
24251
+ (value) => value.toUpperCase()
24252
+ ).option(
24253
+ "--status <status>",
24254
+ "Filter by status (OPEN, IN_PROGRESS, FIXED, IGNORED, FALSE_POSITIVE)",
24255
+ (value) => value.toUpperCase()
24256
+ ).option("--page-size <size>", "Number of results per page", (v) => {
24257
+ const n = Number.parseInt(v, 10);
24258
+ if (Number.isNaN(n) || n < 1) {
24259
+ throw new Error("--page-size must be a positive integer");
24260
+ }
24261
+ return Math.min(n, 200);
24262
+ }).option("--json", "Output as JSON").action(
24263
+ async (options) => {
24264
+ const { token } = requireAuth();
24265
+ const client = createClient(token);
24266
+ const organizationId = await resolveOrganizationId(client);
24267
+ const conditions = [];
24268
+ if (options.repo) {
24269
+ const repositoryId = await resolveRepositoryId(client, organizationId, options.repo);
24270
+ conditions.push({ field: "repositoryId", operator: "eq", value: repositoryId });
24271
+ }
24272
+ if (options.severity) {
24273
+ conditions.push({ field: "severity", operator: "eq", value: options.severity });
24274
+ }
24275
+ if (options.status) {
24276
+ conditions.push({ field: "status", operator: "eq", value: options.status });
24277
+ }
24278
+ const filterBody = {
24279
+ filters: conditions.length > 0 ? { operator: "and", conditions } : void 0,
24280
+ defaultBranchOnly: true,
24281
+ sortBy: "severity",
24282
+ sortOrder: "desc"
24283
+ };
24284
+ const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
24285
+ let page = 1;
24286
+ let totalDisplayed = 0;
24287
+ let total = 0;
24288
+ const allJsonVulnerabilities = [];
24289
+ while (true) {
24290
+ const result = await client.vulnerability.filterVulnerabilities({
24291
+ params: { organizationId },
24292
+ body: { ...filterBody, page, pageSize }
24293
+ });
24294
+ if (result.status !== 200) {
24295
+ console.error(`Failed to list vulnerabilities (status ${result.status}).`);
24296
+ process.exit(1);
24297
+ }
24298
+ total = result.body.total;
24299
+ const vulnerabilities = result.body.statusGroups.flatMap((g) => g.vulnerabilities);
24300
+ if (page === 1 && vulnerabilities.length === 0) {
24301
+ if (options.json) {
24302
+ console.log("[]");
24303
+ } else {
24304
+ console.log("No vulnerabilities found.");
24305
+ }
24306
+ return;
24307
+ }
24308
+ if (options.json) {
24309
+ allJsonVulnerabilities.push(...vulnerabilities);
24310
+ } else {
24311
+ if (page === 1) {
24312
+ console.log(`Found ${total} vulnerabilit${total === 1 ? "y" : "ies"}:
24313
+ `);
24314
+ }
24315
+ printVulnerabilities(vulnerabilities);
24316
+ }
24317
+ totalDisplayed += vulnerabilities.length;
24318
+ if (totalDisplayed >= total || vulnerabilities.length < pageSize) {
24319
+ break;
24320
+ }
24321
+ if (options.json) {
24322
+ page++;
24323
+ continue;
24324
+ }
24325
+ const wantMore = await promptContinue(totalDisplayed, total);
24326
+ if (!wantMore) {
24327
+ break;
24328
+ }
24329
+ page++;
24330
+ }
24331
+ if (options.json) {
24332
+ console.log(JSON.stringify(allJsonVulnerabilities, null, 2));
24333
+ }
24334
+ }
24335
+ );
24336
+ }
24337
+ function createVulnerabilitiesCommand() {
24338
+ const vulnerabilities = new Command("vulnerabilities").description("Manage vulnerabilities");
24339
+ vulnerabilities.addCommand(createListCommand2());
24340
+ return vulnerabilities;
24341
+ }
24342
+
24033
24343
  // src/index.ts
24034
24344
  var program2 = new Command();
24035
24345
  program2.name("tracecli").description("Trace CLI").version("0.0.1");
24346
+ program2.action(async () => {
24347
+ await showBanner();
24348
+ program2.outputHelp();
24349
+ });
24036
24350
  program2.command("echo").description("Echo a message back to the terminal").argument("<message...>", "message to echo").action((messageParts) => {
24037
24351
  console.log(messageParts.join(" "));
24038
24352
  });
24039
24353
  program2.addCommand(createAuthCommand());
24040
24354
  program2.addCommand(createRepoCommand());
24355
+ program2.addCommand(createVulnerabilitiesCommand());
24041
24356
  program2.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securewithtrace",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "Trace CLI",
5
5
  "type": "module",
6
6
  "bin": {