@getcodesentinel/codesentinel 1.18.0 → 1.19.1

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/dist/index.js CHANGED
@@ -2809,10 +2809,10 @@ var runGit = async (repositoryPath, args) => {
2809
2809
  };
2810
2810
  var tryRunGit = async (repositoryPath, args) => {
2811
2811
  try {
2812
- const { stdout } = await execFileAsync("git", ["-C", repositoryPath, ...args], {
2812
+ const { stdout: stdout2 } = await execFileAsync("git", ["-C", repositoryPath, ...args], {
2813
2813
  encoding: "utf8"
2814
2814
  });
2815
- return { ok: true, stdout: stdout.trim() };
2815
+ return { ok: true, stdout: stdout2.trim() };
2816
2816
  } catch (error) {
2817
2817
  const message = error instanceof Error ? error.message : "unknown git error";
2818
2818
  return { ok: false, message };
@@ -3336,6 +3336,12 @@ var ANSI = {
3336
3336
  green: "\x1B[32m",
3337
3337
  yellow: "\x1B[33m"
3338
3338
  };
3339
+ var hideCursor = () => {
3340
+ stderr.write("\x1B[?25l");
3341
+ };
3342
+ var showCursor = () => {
3343
+ stderr.write("\x1B[?25h");
3344
+ };
3339
3345
  var parsePrereleaseIdentifier = (identifier) => {
3340
3346
  if (/^\d+$/.test(identifier)) {
3341
3347
  return Number.parseInt(identifier, 10);
@@ -3539,7 +3545,7 @@ var renderUpdatePrompt = (packageName, latestVersion, currentVersion, selectedIn
3539
3545
  return `${prefix} ${text}`;
3540
3546
  }),
3541
3547
  "",
3542
- ` ${ANSI.dim}Use \u2191/\u2193 to choose. Press enter to continue${ANSI.reset}`
3548
+ ` ${ANSI.dim}Use \u2191/\u2193 to choose. Press enter to continue. Press q or Ctrl+C to exit.${ANSI.reset}`
3543
3549
  ];
3544
3550
  stderr.write(lines.join("\n"));
3545
3551
  return lines.length;
@@ -3571,6 +3577,7 @@ var promptInstall = async (packageName, latestVersion, currentVersion) => {
3571
3577
  stdin.setRawMode(previousRawMode);
3572
3578
  }
3573
3579
  clearPromptArea();
3580
+ showCursor();
3574
3581
  if (choice === "install") {
3575
3582
  stderr.write(`${ANSI.yellow}${renderUpdateInProgressMessage(packageName)}${ANSI.reset}`);
3576
3583
  } else {
@@ -3580,7 +3587,11 @@ var promptInstall = async (packageName, latestVersion, currentVersion) => {
3580
3587
  };
3581
3588
  const onKeypress = (_str, key) => {
3582
3589
  if (key.ctrl === true && key.name === "c") {
3583
- cleanup("interrupt");
3590
+ cleanup("skip");
3591
+ return;
3592
+ }
3593
+ if (key.name === "q") {
3594
+ cleanup("skip");
3584
3595
  return;
3585
3596
  }
3586
3597
  if (key.name === "up") {
@@ -3599,6 +3610,7 @@ var promptInstall = async (packageName, latestVersion, currentVersion) => {
3599
3610
  };
3600
3611
  stdin.on("keypress", onKeypress);
3601
3612
  if (typeof stdin.setRawMode === "function") {
3613
+ hideCursor();
3602
3614
  stdin.setRawMode(true);
3603
3615
  }
3604
3616
  stdin.resume();
@@ -3625,9 +3637,6 @@ var runManualCliUpdate = async (input) => {
3625
3637
  return 0;
3626
3638
  }
3627
3639
  const choice = await promptInstall(input.packageName, latestVersion, input.currentVersion);
3628
- if (choice === "interrupt") {
3629
- return 130;
3630
- }
3631
3640
  if (choice !== "install") {
3632
3641
  return 0;
3633
3642
  }
@@ -3665,9 +3674,6 @@ var checkForCliUpdates = async (input) => {
3665
3674
  return;
3666
3675
  }
3667
3676
  const choice = await promptInstall(input.packageName, latestVersion, input.currentVersion);
3668
- if (choice === "interrupt") {
3669
- process.exit(130);
3670
- }
3671
3677
  if (choice !== "install") {
3672
3678
  return;
3673
3679
  }
@@ -3684,6 +3690,312 @@ var checkForCliUpdates = async (input) => {
3684
3690
  }
3685
3691
  };
3686
3692
 
3693
+ // src/application/interactive-menu.ts
3694
+ import { spawn as spawn2 } from "child_process";
3695
+ import { stderr as stderr2, stdin as stdin2, stdout } from "process";
3696
+ import { clearScreenDown as clearScreenDown2, cursorTo as cursorTo2, emitKeypressEvents as emitKeypressEvents2, moveCursor } from "readline";
3697
+ import { createInterface as createPromisesInterface } from "readline/promises";
3698
+ var ANSI2 = {
3699
+ reset: "\x1B[0m",
3700
+ bold: "\x1B[1m",
3701
+ dim: "\x1B[2m",
3702
+ cyan: "\x1B[36m",
3703
+ green: "\x1B[32m"
3704
+ };
3705
+ var PROMPT_PADDING = " ";
3706
+ var renderMenu = (currentVersion, actions, selectedIndex) => {
3707
+ const optionLabels = actions.map((action, index) => `${index + 1}. ${action.label}`);
3708
+ const labelWidth = optionLabels.reduce((max, label) => Math.max(max, label.length), 0);
3709
+ const lines = [
3710
+ ` ${ANSI2.bold}${ANSI2.cyan}CodeSentinel${ANSI2.reset} ${ANSI2.dim}v${currentVersion}${ANSI2.reset}`,
3711
+ "",
3712
+ " Choose an action:",
3713
+ "",
3714
+ ...actions.map((action, index) => {
3715
+ const selected = index === selectedIndex;
3716
+ const prefix = selected ? `${ANSI2.green}>${ANSI2.reset}` : " ";
3717
+ const label = optionLabels[index]?.padEnd(labelWidth, " ") ?? "";
3718
+ const renderedLabel = selected ? `${ANSI2.bold}${label}${ANSI2.reset}` : label;
3719
+ return `${prefix} ${renderedLabel} ${ANSI2.dim}${action.description}${ANSI2.reset}`;
3720
+ }),
3721
+ "",
3722
+ ` ${ANSI2.dim}Use \u2191/\u2193 to choose. Press enter to continue. Press q or Ctrl+C to exit.${ANSI2.reset}`
3723
+ ];
3724
+ stderr2.write(lines.join("\n"));
3725
+ };
3726
+ var clearTerminal = () => {
3727
+ cursorTo2(stderr2, 0, 0);
3728
+ clearScreenDown2(stderr2);
3729
+ };
3730
+ var hideCursor2 = () => {
3731
+ stderr2.write("\x1B[?25l");
3732
+ };
3733
+ var showCursor2 = () => {
3734
+ stderr2.write("\x1B[?25h");
3735
+ };
3736
+ var pipeWithPadding = (stream, target, padding) => {
3737
+ if (stream === null) {
3738
+ return;
3739
+ }
3740
+ stream.setEncoding("utf8");
3741
+ let buffer = "";
3742
+ let needsPrefix = true;
3743
+ const writeChunk = (chunk) => {
3744
+ let start = 0;
3745
+ while (start < chunk.length) {
3746
+ if (needsPrefix) {
3747
+ target.write(padding);
3748
+ needsPrefix = false;
3749
+ }
3750
+ const newlineIndex = chunk.indexOf("\n", start);
3751
+ if (newlineIndex === -1) {
3752
+ target.write(chunk.slice(start));
3753
+ return;
3754
+ }
3755
+ target.write(chunk.slice(start, newlineIndex + 1));
3756
+ needsPrefix = true;
3757
+ start = newlineIndex + 1;
3758
+ }
3759
+ };
3760
+ stream.on("data", (chunk) => {
3761
+ buffer += chunk;
3762
+ const lastNewlineIndex = buffer.lastIndexOf("\n");
3763
+ if (lastNewlineIndex === -1) {
3764
+ return;
3765
+ }
3766
+ writeChunk(buffer.slice(0, lastNewlineIndex + 1));
3767
+ buffer = buffer.slice(lastNewlineIndex + 1);
3768
+ });
3769
+ stream.on("end", () => {
3770
+ if (buffer.length > 0) {
3771
+ writeChunk(buffer);
3772
+ buffer = "";
3773
+ }
3774
+ });
3775
+ };
3776
+ var promptSelection = async (currentVersion, actions) => {
3777
+ if (!stdin2.isTTY || !stderr2.isTTY || typeof stdin2.setRawMode !== "function") {
3778
+ return "exit";
3779
+ }
3780
+ return await new Promise((resolve6) => {
3781
+ emitKeypressEvents2(stdin2);
3782
+ let selectedIndex = 0;
3783
+ const previousRawMode = stdin2.isRaw;
3784
+ const redraw = () => {
3785
+ clearTerminal();
3786
+ renderMenu(currentVersion, actions, selectedIndex);
3787
+ moveCursor(stderr2, -1, 0);
3788
+ };
3789
+ const cleanup = (selection) => {
3790
+ stdin2.off("keypress", onKeypress);
3791
+ stdin2.pause();
3792
+ stdin2.setRawMode(previousRawMode);
3793
+ clearTerminal();
3794
+ showCursor2();
3795
+ resolve6(selection);
3796
+ };
3797
+ const onKeypress = (_str, key) => {
3798
+ if (key.ctrl === true && key.name === "c") {
3799
+ cleanup("exit");
3800
+ return;
3801
+ }
3802
+ if (key.name === "q") {
3803
+ cleanup("exit");
3804
+ return;
3805
+ }
3806
+ if (key.name === "up") {
3807
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : actions.length - 1;
3808
+ redraw();
3809
+ return;
3810
+ }
3811
+ if (key.name === "down") {
3812
+ selectedIndex = selectedIndex < actions.length - 1 ? selectedIndex + 1 : 0;
3813
+ redraw();
3814
+ return;
3815
+ }
3816
+ if (key.name === "return" || key.name === "enter") {
3817
+ cleanup(selectedIndex);
3818
+ }
3819
+ };
3820
+ stdin2.on("keypress", onKeypress);
3821
+ hideCursor2();
3822
+ stdin2.setRawMode(true);
3823
+ stdin2.resume();
3824
+ redraw();
3825
+ });
3826
+ };
3827
+ var createPrompt = () => createPromisesInterface({
3828
+ input: stdin2,
3829
+ output: stderr2
3830
+ });
3831
+ var promptText = async (prompt, label, defaultValue) => {
3832
+ const suffix = defaultValue === void 0 || defaultValue.length === 0 ? "" : ` [${defaultValue}]`;
3833
+ let answer;
3834
+ try {
3835
+ answer = await prompt.question(
3836
+ `${PROMPT_PADDING}${label}${suffix}: `
3837
+ );
3838
+ } catch (error) {
3839
+ if (error instanceof Error && "code" in error && error.code === "ABORT_ERR") {
3840
+ stderr2.write("\n");
3841
+ return null;
3842
+ }
3843
+ throw error;
3844
+ }
3845
+ const trimmed = answer.trim();
3846
+ return trimmed.length > 0 ? trimmed : defaultValue ?? "";
3847
+ };
3848
+ var renderDependencyRiskPrompt = (errorMessage) => {
3849
+ clearTerminal();
3850
+ stderr2.write(`${PROMPT_PADDING}${ANSI2.bold}Scan dependency risk${ANSI2.reset}
3851
+ `);
3852
+ if (errorMessage !== void 0) {
3853
+ stderr2.write(`
3854
+ ${PROMPT_PADDING}${errorMessage}
3855
+ `);
3856
+ }
3857
+ stderr2.write("\n");
3858
+ };
3859
+ var buildDependencyRiskArgs = async () => {
3860
+ const prompt = createPrompt();
3861
+ try {
3862
+ let errorMessage;
3863
+ while (true) {
3864
+ renderDependencyRiskPrompt(errorMessage);
3865
+ const dependency = await promptText(prompt, "Package name");
3866
+ if (dependency === null) {
3867
+ return { kind: "cancel" };
3868
+ }
3869
+ if (dependency.length === 0) {
3870
+ errorMessage = "A package name is required.";
3871
+ continue;
3872
+ }
3873
+ return {
3874
+ kind: "run",
3875
+ args: ["dependency-risk", dependency]
3876
+ };
3877
+ }
3878
+ } finally {
3879
+ prompt.close();
3880
+ }
3881
+ };
3882
+ var waitForReturnToMenu = async () => {
3883
+ if (!stdin2.isTTY || !stderr2.isTTY || typeof stdin2.setRawMode !== "function") {
3884
+ return;
3885
+ }
3886
+ stderr2.write(`
3887
+ ${PROMPT_PADDING}Press enter to return to the menu...`);
3888
+ await new Promise((resolve6) => {
3889
+ emitKeypressEvents2(stdin2);
3890
+ const previousRawMode = stdin2.isRaw;
3891
+ const cleanup = () => {
3892
+ stdin2.off("keypress", onKeypress);
3893
+ stdin2.pause();
3894
+ stdin2.setRawMode(previousRawMode);
3895
+ showCursor2();
3896
+ stderr2.write("\n");
3897
+ resolve6();
3898
+ };
3899
+ const onKeypress = (_str, key) => {
3900
+ if (key.ctrl === true && key.name === "c") {
3901
+ cleanup();
3902
+ return;
3903
+ }
3904
+ if (key.name === "return" || key.name === "enter") {
3905
+ cleanup();
3906
+ }
3907
+ };
3908
+ hideCursor2();
3909
+ stdin2.on("keypress", onKeypress);
3910
+ stdin2.setRawMode(true);
3911
+ stdin2.resume();
3912
+ });
3913
+ };
3914
+ var runCliCommand = async (scriptPath2, args) => {
3915
+ return await new Promise((resolve6, reject) => {
3916
+ const child = spawn2(process.execPath, [...process.execArgv, scriptPath2, ...args], {
3917
+ stdio: ["inherit", "pipe", "pipe"],
3918
+ env: {
3919
+ ...process.env,
3920
+ CODESENTINEL_NO_UPDATE_NOTIFIER: "1"
3921
+ }
3922
+ });
3923
+ pipeWithPadding(child.stdout, stdout, PROMPT_PADDING);
3924
+ pipeWithPadding(child.stderr, stderr2, PROMPT_PADDING);
3925
+ child.on("error", (error) => {
3926
+ reject(error);
3927
+ });
3928
+ child.on("close", (code) => {
3929
+ resolve6(code ?? 1);
3930
+ });
3931
+ });
3932
+ };
3933
+ var runInteractiveCliMenu = async (input) => {
3934
+ if (!stdin2.isTTY || !stderr2.isTTY || !stdout.isTTY) {
3935
+ stderr2.write(`${PROMPT_PADDING}Interactive menu requires a TTY.
3936
+ `);
3937
+ return 1;
3938
+ }
3939
+ const actions = [
3940
+ {
3941
+ label: "Run overview",
3942
+ description: "combined analyze + explain + report",
3943
+ commandBuilder: () => ({ kind: "run", args: ["run"] })
3944
+ },
3945
+ {
3946
+ label: "Analyze repository",
3947
+ description: "structural and health scoring summary",
3948
+ commandBuilder: () => ({ kind: "run", args: ["analyze"] })
3949
+ },
3950
+ {
3951
+ label: "Explain hotspots",
3952
+ description: "top findings in markdown by default",
3953
+ commandBuilder: () => ({ kind: "run", args: ["explain", "--format", "md"] })
3954
+ },
3955
+ {
3956
+ label: "Generate report",
3957
+ description: "create a full report for a repository",
3958
+ commandBuilder: () => ({ kind: "run", args: ["report", "--format", "md"] })
3959
+ },
3960
+ {
3961
+ label: "Run policy check",
3962
+ description: "execute governance gates",
3963
+ commandBuilder: () => ({ kind: "run", args: ["check"] })
3964
+ },
3965
+ {
3966
+ label: "Scan dependency risk",
3967
+ description: "inspect a package from the registry",
3968
+ commandBuilder: buildDependencyRiskArgs
3969
+ }
3970
+ ];
3971
+ while (true) {
3972
+ const selectedIndex = await promptSelection(input.currentVersion, actions);
3973
+ if (selectedIndex === "exit") {
3974
+ stderr2.write("\n");
3975
+ return 0;
3976
+ }
3977
+ const selectedAction = actions[selectedIndex];
3978
+ if (selectedAction === void 0) {
3979
+ stderr2.write(`
3980
+ ${PROMPT_PADDING}`);
3981
+ return 1;
3982
+ }
3983
+ const actionResult = await selectedAction.commandBuilder();
3984
+ if (actionResult.kind === "cancel") {
3985
+ continue;
3986
+ }
3987
+ const exitCode = await runCliCommand(input.scriptPath, actionResult.args);
3988
+ if (exitCode !== 0) {
3989
+ stderr2.write(`
3990
+ ${PROMPT_PADDING}Command exited with code ${exitCode}.
3991
+ `);
3992
+ } else {
3993
+ stderr2.write("\n");
3994
+ }
3995
+ await waitForReturnToMenu();
3996
+ }
3997
+ };
3998
+
3687
3999
  // src/application/run-analyze-command.ts
3688
4000
  import { resolve as resolve3 } from "path";
3689
4001
 
@@ -8052,14 +8364,17 @@ program.command("ci").argument("[path]", "path to the project to analyze").addOp
8052
8364
  }
8053
8365
  }
8054
8366
  );
8055
- if (process.argv.length <= 2) {
8056
- program.outputHelp();
8057
- process.exit(0);
8058
- }
8059
8367
  var executablePath = process.argv[0] ?? "";
8060
8368
  var scriptPath = process.argv[1] ?? "";
8061
8369
  var argv = process.argv[2] === "--" ? [executablePath, scriptPath, ...process.argv.slice(3)] : process.argv;
8062
8370
  if (argv.length <= 2) {
8371
+ if (process.stdin.isTTY && process.stdout.isTTY && process.stderr.isTTY) {
8372
+ process.exitCode = await runInteractiveCliMenu({
8373
+ currentVersion: version,
8374
+ scriptPath
8375
+ });
8376
+ process.exit(process.exitCode ?? 0);
8377
+ }
8063
8378
  program.outputHelp();
8064
8379
  process.exit(0);
8065
8380
  }