skalpel 1.0.0 → 1.0.2

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/cli/index.js CHANGED
@@ -481,8 +481,8 @@ async function runReplay(filePaths) {
481
481
 
482
482
  // src/cli/start.ts
483
483
  import { spawn } from "child_process";
484
- import path7 from "path";
485
- import { fileURLToPath } from "url";
484
+ import path9 from "path";
485
+ import { fileURLToPath as fileURLToPath2 } from "url";
486
486
 
487
487
  // src/proxy/config.ts
488
488
  import fs5 from "fs";
@@ -557,6 +557,345 @@ function removePid(pidFile) {
557
557
  }
558
558
  }
559
559
 
560
+ // src/cli/service/install.ts
561
+ import fs7 from "fs";
562
+ import path8 from "path";
563
+ import os4 from "os";
564
+ import { execSync as execSync2 } from "child_process";
565
+ import { fileURLToPath } from "url";
566
+
567
+ // src/cli/service/detect-os.ts
568
+ import os2 from "os";
569
+ import { execSync } from "child_process";
570
+ function detectShell() {
571
+ if (process.platform === "win32") {
572
+ if (process.env.PSModulePath || process.env.POWERSHELL_DISTRIBUTION_CHANNEL) {
573
+ return "powershell";
574
+ }
575
+ return "cmd";
576
+ }
577
+ const shellPath = process.env.SHELL ?? "";
578
+ if (shellPath.includes("zsh")) return "zsh";
579
+ if (shellPath.includes("fish")) return "fish";
580
+ if (shellPath.includes("bash")) return "bash";
581
+ try {
582
+ if (process.platform === "darwin") {
583
+ const result = execSync(`dscl . -read /Users/${os2.userInfo().username} UserShell`, {
584
+ encoding: "utf-8",
585
+ timeout: 3e3
586
+ }).trim();
587
+ const shell = result.split(":").pop()?.trim() ?? "";
588
+ if (shell.includes("zsh")) return "zsh";
589
+ if (shell.includes("fish")) return "fish";
590
+ if (shell.includes("bash")) return "bash";
591
+ } else {
592
+ const result = execSync(`getent passwd ${os2.userInfo().username}`, {
593
+ encoding: "utf-8",
594
+ timeout: 3e3
595
+ }).trim();
596
+ const shell = result.split(":").pop() ?? "";
597
+ if (shell.includes("zsh")) return "zsh";
598
+ if (shell.includes("fish")) return "fish";
599
+ if (shell.includes("bash")) return "bash";
600
+ }
601
+ } catch {
602
+ }
603
+ return "bash";
604
+ }
605
+ function detectOS() {
606
+ let platform;
607
+ switch (process.platform) {
608
+ case "darwin":
609
+ platform = "macos";
610
+ break;
611
+ case "win32":
612
+ platform = "windows";
613
+ break;
614
+ default:
615
+ platform = "linux";
616
+ break;
617
+ }
618
+ return {
619
+ platform,
620
+ shell: detectShell(),
621
+ homeDir: os2.homedir()
622
+ };
623
+ }
624
+
625
+ // src/cli/service/templates.ts
626
+ import os3 from "os";
627
+ import path7 from "path";
628
+ function generateLaunchdPlist(config, proxyRunnerPath) {
629
+ const logDir = path7.join(os3.homedir(), ".skalpel", "logs");
630
+ return `<?xml version="1.0" encoding="UTF-8"?>
631
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
632
+ <plist version="1.0">
633
+ <dict>
634
+ <key>Label</key>
635
+ <string>ai.skalpel.proxy</string>
636
+ <key>ProgramArguments</key>
637
+ <array>
638
+ <string>${process.execPath}</string>
639
+ <string>${proxyRunnerPath}</string>
640
+ </array>
641
+ <key>RunAtLoad</key>
642
+ <true/>
643
+ <key>KeepAlive</key>
644
+ <true/>
645
+ <key>StandardOutPath</key>
646
+ <string>${path7.join(logDir, "proxy-stdout.log")}</string>
647
+ <key>StandardErrorPath</key>
648
+ <string>${path7.join(logDir, "proxy-stderr.log")}</string>
649
+ <key>EnvironmentVariables</key>
650
+ <dict>
651
+ <key>SKALPEL_ANTHROPIC_PORT</key>
652
+ <string>${config.anthropicPort}</string>
653
+ <key>SKALPEL_OPENAI_PORT</key>
654
+ <string>${config.openaiPort}</string>
655
+ </dict>
656
+ </dict>
657
+ </plist>`;
658
+ }
659
+ function generateSystemdUnit(config, proxyRunnerPath) {
660
+ return `[Unit]
661
+ Description=Skalpel Proxy
662
+ After=network.target
663
+
664
+ [Service]
665
+ Type=simple
666
+ ExecStart=${process.execPath} ${proxyRunnerPath}
667
+ Restart=always
668
+ RestartSec=5
669
+ Environment=SKALPEL_ANTHROPIC_PORT=${config.anthropicPort}
670
+ Environment=SKALPEL_OPENAI_PORT=${config.openaiPort}
671
+
672
+ [Install]
673
+ WantedBy=default.target`;
674
+ }
675
+ function generateWindowsTask(config, proxyRunnerPath) {
676
+ return [
677
+ "/create",
678
+ "/tn",
679
+ "SkalpelProxy",
680
+ "/tr",
681
+ `"${process.execPath}" "${proxyRunnerPath}"`,
682
+ "/sc",
683
+ "ONLOGON",
684
+ "/rl",
685
+ "LIMITED",
686
+ "/f"
687
+ ];
688
+ }
689
+
690
+ // src/cli/service/install.ts
691
+ var __dirname = path8.dirname(fileURLToPath(import.meta.url));
692
+ function resolveProxyRunnerPath() {
693
+ const candidates = [
694
+ path8.join(__dirname, "..", "proxy-runner.js"),
695
+ // dist/cli/proxy-runner.js relative to dist/cli/service/
696
+ path8.join(__dirname, "proxy-runner.js"),
697
+ // same dir
698
+ path8.join(__dirname, "..", "..", "cli", "proxy-runner.js")
699
+ // dist/cli/proxy-runner.js from deeper
700
+ ];
701
+ for (const candidate of candidates) {
702
+ if (fs7.existsSync(candidate)) {
703
+ return path8.resolve(candidate);
704
+ }
705
+ }
706
+ try {
707
+ const npmRoot = execSync2("npm root -g", { encoding: "utf-8" }).trim();
708
+ const globalPath = path8.join(npmRoot, "skalpel", "dist", "cli", "proxy-runner.js");
709
+ if (fs7.existsSync(globalPath)) return globalPath;
710
+ } catch {
711
+ }
712
+ const devPath = path8.resolve(process.cwd(), "dist", "cli", "proxy-runner.js");
713
+ return devPath;
714
+ }
715
+ function getMacOSPlistPath() {
716
+ return path8.join(os4.homedir(), "Library", "LaunchAgents", "ai.skalpel.proxy.plist");
717
+ }
718
+ function getLinuxUnitPath() {
719
+ return path8.join(os4.homedir(), ".config", "systemd", "user", "skalpel-proxy.service");
720
+ }
721
+ function installService(config) {
722
+ const osInfo = detectOS();
723
+ const proxyRunnerPath = resolveProxyRunnerPath();
724
+ const logDir = path8.join(os4.homedir(), ".skalpel", "logs");
725
+ fs7.mkdirSync(logDir, { recursive: true });
726
+ switch (osInfo.platform) {
727
+ case "macos": {
728
+ const plistPath = getMacOSPlistPath();
729
+ const plistDir = path8.dirname(plistPath);
730
+ fs7.mkdirSync(plistDir, { recursive: true });
731
+ const plist = generateLaunchdPlist(config, proxyRunnerPath);
732
+ fs7.writeFileSync(plistPath, plist);
733
+ try {
734
+ execSync2(`launchctl unload "${plistPath}" 2>/dev/null || true`, { stdio: "pipe" });
735
+ execSync2(`launchctl load "${plistPath}"`, { stdio: "pipe" });
736
+ } catch (err) {
737
+ const msg = err instanceof Error ? err.message : String(err);
738
+ console.warn(` Warning: Could not register launchd service: ${msg}`);
739
+ console.warn(` You can manually load it: launchctl load "${plistPath}"`);
740
+ }
741
+ break;
742
+ }
743
+ case "linux": {
744
+ const unitPath = getLinuxUnitPath();
745
+ const unitDir = path8.dirname(unitPath);
746
+ fs7.mkdirSync(unitDir, { recursive: true });
747
+ const unit = generateSystemdUnit(config, proxyRunnerPath);
748
+ fs7.writeFileSync(unitPath, unit);
749
+ try {
750
+ execSync2("systemctl --user daemon-reload", { stdio: "pipe" });
751
+ execSync2("systemctl --user enable skalpel-proxy", { stdio: "pipe" });
752
+ execSync2("systemctl --user start skalpel-proxy", { stdio: "pipe" });
753
+ } catch {
754
+ try {
755
+ const autostartDir = path8.join(os4.homedir(), ".config", "autostart");
756
+ fs7.mkdirSync(autostartDir, { recursive: true });
757
+ const desktopEntry = `[Desktop Entry]
758
+ Type=Application
759
+ Name=Skalpel Proxy
760
+ Exec=${process.execPath} ${proxyRunnerPath}
761
+ Hidden=false
762
+ NoDisplay=true
763
+ X-GNOME-Autostart-enabled=true
764
+ `;
765
+ fs7.writeFileSync(path8.join(autostartDir, "skalpel-proxy.desktop"), desktopEntry);
766
+ console.warn(" Warning: systemd --user not available. Created .desktop autostart entry instead.");
767
+ } catch (err2) {
768
+ const msg = err2 instanceof Error ? err2.message : String(err2);
769
+ console.warn(` Warning: Could not register service: ${msg}`);
770
+ console.warn(" You can start the proxy manually: skalpel start");
771
+ }
772
+ }
773
+ break;
774
+ }
775
+ case "windows": {
776
+ const args = generateWindowsTask(config, proxyRunnerPath);
777
+ try {
778
+ execSync2(`schtasks ${args.join(" ")}`, { stdio: "pipe" });
779
+ } catch (err) {
780
+ const msg = err instanceof Error ? err.message : String(err);
781
+ console.warn(` Warning: Could not create scheduled task: ${msg}`);
782
+ console.warn(" You can start the proxy manually: skalpel start");
783
+ }
784
+ break;
785
+ }
786
+ }
787
+ }
788
+ function isServiceInstalled() {
789
+ const osInfo = detectOS();
790
+ switch (osInfo.platform) {
791
+ case "macos": {
792
+ const plistPath = getMacOSPlistPath();
793
+ return fs7.existsSync(plistPath);
794
+ }
795
+ case "linux": {
796
+ const unitPath = getLinuxUnitPath();
797
+ return fs7.existsSync(unitPath);
798
+ }
799
+ case "windows": {
800
+ try {
801
+ execSync2("schtasks /query /tn SkalpelProxy", { stdio: "pipe" });
802
+ return true;
803
+ } catch {
804
+ return false;
805
+ }
806
+ }
807
+ }
808
+ }
809
+ function stopService() {
810
+ const osInfo = detectOS();
811
+ switch (osInfo.platform) {
812
+ case "macos": {
813
+ const plistPath = getMacOSPlistPath();
814
+ if (!fs7.existsSync(plistPath)) return;
815
+ try {
816
+ execSync2(`launchctl unload "${plistPath}"`, { stdio: "pipe" });
817
+ } catch {
818
+ }
819
+ break;
820
+ }
821
+ case "linux": {
822
+ try {
823
+ execSync2("systemctl --user stop skalpel-proxy", { stdio: "pipe" });
824
+ } catch {
825
+ }
826
+ break;
827
+ }
828
+ case "windows": {
829
+ try {
830
+ execSync2("schtasks /end /tn SkalpelProxy", { stdio: "pipe" });
831
+ } catch {
832
+ }
833
+ break;
834
+ }
835
+ }
836
+ }
837
+ function startService() {
838
+ const osInfo = detectOS();
839
+ switch (osInfo.platform) {
840
+ case "macos": {
841
+ const plistPath = getMacOSPlistPath();
842
+ if (!fs7.existsSync(plistPath)) return;
843
+ try {
844
+ execSync2(`launchctl load "${plistPath}"`, { stdio: "pipe" });
845
+ } catch {
846
+ }
847
+ break;
848
+ }
849
+ case "linux": {
850
+ try {
851
+ execSync2("systemctl --user start skalpel-proxy", { stdio: "pipe" });
852
+ } catch {
853
+ }
854
+ break;
855
+ }
856
+ case "windows": {
857
+ try {
858
+ execSync2("schtasks /run /tn SkalpelProxy", { stdio: "pipe" });
859
+ } catch {
860
+ }
861
+ break;
862
+ }
863
+ }
864
+ }
865
+ function uninstallService() {
866
+ const osInfo = detectOS();
867
+ switch (osInfo.platform) {
868
+ case "macos": {
869
+ const plistPath = getMacOSPlistPath();
870
+ try {
871
+ execSync2(`launchctl unload "${plistPath}" 2>/dev/null || true`, { stdio: "pipe" });
872
+ } catch {
873
+ }
874
+ if (fs7.existsSync(plistPath)) fs7.unlinkSync(plistPath);
875
+ break;
876
+ }
877
+ case "linux": {
878
+ try {
879
+ execSync2("systemctl --user stop skalpel-proxy 2>/dev/null || true", { stdio: "pipe" });
880
+ execSync2("systemctl --user disable skalpel-proxy 2>/dev/null || true", { stdio: "pipe" });
881
+ } catch {
882
+ }
883
+ const unitPath = getLinuxUnitPath();
884
+ if (fs7.existsSync(unitPath)) fs7.unlinkSync(unitPath);
885
+ const desktopPath = path8.join(os4.homedir(), ".config", "autostart", "skalpel-proxy.desktop");
886
+ if (fs7.existsSync(desktopPath)) fs7.unlinkSync(desktopPath);
887
+ break;
888
+ }
889
+ case "windows": {
890
+ try {
891
+ execSync2("schtasks /delete /tn SkalpelProxy /f", { stdio: "pipe" });
892
+ } catch {
893
+ }
894
+ break;
895
+ }
896
+ }
897
+ }
898
+
560
899
  // src/cli/start.ts
561
900
  function print5(msg) {
562
901
  console.log(msg);
@@ -572,8 +911,13 @@ async function runStart() {
572
911
  print5(` Proxy is already running (pid=${existingPid}).`);
573
912
  return;
574
913
  }
575
- const dirname = path7.dirname(fileURLToPath(import.meta.url));
576
- const runnerScript = path7.resolve(dirname, "proxy-runner.js");
914
+ if (isServiceInstalled()) {
915
+ startService();
916
+ print5(` Skalpel proxy started via system service on ports ${config.anthropicPort} and ${config.openaiPort}`);
917
+ return;
918
+ }
919
+ const dirname = path9.dirname(fileURLToPath2(import.meta.url));
920
+ const runnerScript = path9.resolve(dirname, "proxy-runner.js");
577
921
  const child = spawn(process.execPath, [runnerScript], {
578
922
  detached: true,
579
923
  stdio: "ignore"
@@ -586,8 +930,8 @@ async function runStart() {
586
930
  import http from "http";
587
931
 
588
932
  // src/proxy/logger.ts
589
- import fs7 from "fs";
590
- import path8 from "path";
933
+ import fs8 from "fs";
934
+ import path10 from "path";
591
935
  var MAX_SIZE = 5 * 1024 * 1024;
592
936
 
593
937
  // src/proxy/server.ts
@@ -619,6 +963,9 @@ function print6(msg) {
619
963
  }
620
964
  async function runStop() {
621
965
  const config = loadConfig();
966
+ if (isServiceInstalled()) {
967
+ stopService();
968
+ }
622
969
  const stopped = stopProxy(config);
623
970
  if (stopped) {
624
971
  print6(" Skalpel proxy stopped.");
@@ -648,7 +995,7 @@ async function runStatus() {
648
995
  }
649
996
 
650
997
  // src/cli/logs.ts
651
- import fs8 from "fs";
998
+ import fs9 from "fs";
652
999
  function print8(msg) {
653
1000
  console.log(msg);
654
1001
  }
@@ -656,26 +1003,26 @@ async function runLogs(options) {
656
1003
  const config = loadConfig();
657
1004
  const logFile = config.logFile;
658
1005
  const lineCount = parseInt(options.lines ?? "50", 10);
659
- if (!fs8.existsSync(logFile)) {
1006
+ if (!fs9.existsSync(logFile)) {
660
1007
  print8(` No log file found at ${logFile}`);
661
1008
  return;
662
1009
  }
663
- const content = fs8.readFileSync(logFile, "utf-8");
1010
+ const content = fs9.readFileSync(logFile, "utf-8");
664
1011
  const lines = content.trimEnd().split("\n");
665
1012
  const tail = lines.slice(-lineCount);
666
1013
  for (const line of tail) {
667
1014
  print8(line);
668
1015
  }
669
1016
  if (options.follow) {
670
- let position = fs8.statSync(logFile).size;
671
- fs8.watchFile(logFile, { interval: 500 }, () => {
1017
+ let position = fs9.statSync(logFile).size;
1018
+ fs9.watchFile(logFile, { interval: 500 }, () => {
672
1019
  try {
673
- const stat = fs8.statSync(logFile);
1020
+ const stat = fs9.statSync(logFile);
674
1021
  if (stat.size > position) {
675
- const fd = fs8.openSync(logFile, "r");
1022
+ const fd = fs9.openSync(logFile, "r");
676
1023
  const buf = Buffer.alloc(stat.size - position);
677
- fs8.readSync(fd, buf, 0, buf.length, position);
678
- fs8.closeSync(fd);
1024
+ fs9.readSync(fd, buf, 0, buf.length, position);
1025
+ fs9.closeSync(fd);
679
1026
  process.stdout.write(buf.toString("utf-8"));
680
1027
  position = stat.size;
681
1028
  }
@@ -786,16 +1133,16 @@ import * as os8 from "os";
786
1133
  import { execSync as execSync4 } from "child_process";
787
1134
 
788
1135
  // src/cli/agents/detect.ts
789
- import { execSync } from "child_process";
790
- import fs9 from "fs";
791
- import path9 from "path";
792
- import os2 from "os";
1136
+ import { execSync as execSync3 } from "child_process";
1137
+ import fs10 from "fs";
1138
+ import path11 from "path";
1139
+ import os5 from "os";
793
1140
  function whichCommand() {
794
1141
  return process.platform === "win32" ? "where" : "which";
795
1142
  }
796
1143
  function tryExec(cmd) {
797
1144
  try {
798
- return execSync(cmd, { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
1145
+ return execSync3(cmd, { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
799
1146
  } catch {
800
1147
  return null;
801
1148
  }
@@ -809,8 +1156,8 @@ function detectClaudeCode() {
809
1156
  };
810
1157
  const binaryPath = tryExec(`${whichCommand()} claude`);
811
1158
  const hasBinary = binaryPath !== null && binaryPath.length > 0;
812
- const claudeDir = path9.join(os2.homedir(), ".claude");
813
- const hasConfigDir = fs9.existsSync(claudeDir);
1159
+ const claudeDir = path11.join(os5.homedir(), ".claude");
1160
+ const hasConfigDir = fs10.existsSync(claudeDir);
814
1161
  agent.installed = hasBinary || hasConfigDir;
815
1162
  if (hasBinary) {
816
1163
  const versionOutput = tryExec("claude --version");
@@ -819,8 +1166,8 @@ function detectClaudeCode() {
819
1166
  agent.version = match ? match[1] : versionOutput;
820
1167
  }
821
1168
  }
822
- const settingsPath = path9.join(claudeDir, "settings.json");
823
- if (fs9.existsSync(settingsPath)) {
1169
+ const settingsPath = path11.join(claudeDir, "settings.json");
1170
+ if (fs10.existsSync(settingsPath)) {
824
1171
  agent.configPath = settingsPath;
825
1172
  } else if (hasConfigDir) {
826
1173
  agent.configPath = settingsPath;
@@ -836,8 +1183,8 @@ function detectCodex() {
836
1183
  };
837
1184
  const binaryPath = tryExec(`${whichCommand()} codex`);
838
1185
  const hasBinary = binaryPath !== null && binaryPath.length > 0;
839
- const codexConfigDir = process.platform === "win32" ? path9.join(os2.homedir(), "AppData", "Roaming", "codex") : path9.join(os2.homedir(), ".codex");
840
- const hasConfigDir = fs9.existsSync(codexConfigDir);
1186
+ const codexConfigDir = process.platform === "win32" ? path11.join(os5.homedir(), "AppData", "Roaming", "codex") : path11.join(os5.homedir(), ".codex");
1187
+ const hasConfigDir = fs10.existsSync(codexConfigDir);
841
1188
  agent.installed = hasBinary || hasConfigDir;
842
1189
  if (hasBinary) {
843
1190
  const versionOutput = tryExec("codex --version");
@@ -846,8 +1193,8 @@ function detectCodex() {
846
1193
  agent.version = match ? match[1] : versionOutput;
847
1194
  }
848
1195
  }
849
- const configFile = path9.join(codexConfigDir, "config.json");
850
- if (fs9.existsSync(configFile)) {
1196
+ const configFile = path11.join(codexConfigDir, "config.json");
1197
+ if (fs10.existsSync(configFile)) {
851
1198
  agent.configPath = configFile;
852
1199
  } else if (hasConfigDir) {
853
1200
  agent.configPath = configFile;
@@ -859,31 +1206,31 @@ function detectAgents() {
859
1206
  }
860
1207
 
861
1208
  // src/cli/agents/shell.ts
862
- import fs10 from "fs";
863
- import path10 from "path";
864
- import os3 from "os";
1209
+ import fs11 from "fs";
1210
+ import path12 from "path";
1211
+ import os6 from "os";
865
1212
  var BEGIN_MARKER = "# BEGIN SKALPEL PROXY - do not edit manually";
866
1213
  var END_MARKER = "# END SKALPEL PROXY";
867
1214
  var PS_BEGIN_MARKER = "# BEGIN SKALPEL PROXY - do not edit manually";
868
1215
  var PS_END_MARKER = "# END SKALPEL PROXY";
869
1216
  function getUnixProfilePaths() {
870
- const home = os3.homedir();
1217
+ const home = os6.homedir();
871
1218
  const candidates = [
872
- path10.join(home, ".bashrc"),
873
- path10.join(home, ".zshrc"),
874
- path10.join(home, ".bash_profile"),
875
- path10.join(home, ".profile")
1219
+ path12.join(home, ".bashrc"),
1220
+ path12.join(home, ".zshrc"),
1221
+ path12.join(home, ".bash_profile"),
1222
+ path12.join(home, ".profile")
876
1223
  ];
877
- return candidates.filter((p) => fs10.existsSync(p));
1224
+ return candidates.filter((p) => fs11.existsSync(p));
878
1225
  }
879
1226
  function getPowerShellProfilePath() {
880
1227
  if (process.platform !== "win32") return null;
881
1228
  if (process.env.PROFILE) return process.env.PROFILE;
882
- const docsDir = path10.join(os3.homedir(), "Documents");
883
- const psProfile = path10.join(docsDir, "PowerShell", "Microsoft.PowerShell_profile.ps1");
884
- const wpProfile = path10.join(docsDir, "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
885
- if (fs10.existsSync(psProfile)) return psProfile;
886
- if (fs10.existsSync(wpProfile)) return wpProfile;
1229
+ const docsDir = path12.join(os6.homedir(), "Documents");
1230
+ const psProfile = path12.join(docsDir, "PowerShell", "Microsoft.PowerShell_profile.ps1");
1231
+ const wpProfile = path12.join(docsDir, "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
1232
+ if (fs11.existsSync(psProfile)) return psProfile;
1233
+ if (fs11.existsSync(wpProfile)) return wpProfile;
887
1234
  return psProfile;
888
1235
  }
889
1236
  function generateUnixBlock(proxyConfig) {
@@ -904,13 +1251,13 @@ function generatePowerShellBlock(proxyConfig) {
904
1251
  }
905
1252
  function createBackup(filePath) {
906
1253
  const backupPath = `${filePath}.skalpel-backup`;
907
- fs10.copyFileSync(filePath, backupPath);
1254
+ fs11.copyFileSync(filePath, backupPath);
908
1255
  }
909
1256
  function updateProfileFile(filePath, block, beginMarker, endMarker) {
910
- if (fs10.existsSync(filePath)) {
1257
+ if (fs11.existsSync(filePath)) {
911
1258
  createBackup(filePath);
912
1259
  }
913
- let content = fs10.existsSync(filePath) ? fs10.readFileSync(filePath, "utf-8") : "";
1260
+ let content = fs11.existsSync(filePath) ? fs11.readFileSync(filePath, "utf-8") : "";
914
1261
  const beginIdx = content.indexOf(beginMarker);
915
1262
  const endIdx = content.indexOf(endMarker);
916
1263
  if (beginIdx !== -1 && endIdx !== -1) {
@@ -923,15 +1270,15 @@ function updateProfileFile(filePath, block, beginMarker, endMarker) {
923
1270
  content = block + "\n";
924
1271
  }
925
1272
  }
926
- fs10.writeFileSync(filePath, content);
1273
+ fs11.writeFileSync(filePath, content);
927
1274
  }
928
1275
  function configureShellEnvVars(_agents, proxyConfig) {
929
1276
  const modified = [];
930
1277
  if (process.platform === "win32") {
931
1278
  const psProfile = getPowerShellProfilePath();
932
1279
  if (psProfile) {
933
- const dir = path10.dirname(psProfile);
934
- fs10.mkdirSync(dir, { recursive: true });
1280
+ const dir = path12.dirname(psProfile);
1281
+ fs11.mkdirSync(dir, { recursive: true });
935
1282
  const block = generatePowerShellBlock(proxyConfig);
936
1283
  updateProfileFile(psProfile, block, PS_BEGIN_MARKER, PS_END_MARKER);
937
1284
  modified.push(psProfile);
@@ -948,32 +1295,32 @@ function configureShellEnvVars(_agents, proxyConfig) {
948
1295
  }
949
1296
  function removeShellEnvVars() {
950
1297
  const restored = [];
951
- const home = os3.homedir();
1298
+ const home = os6.homedir();
952
1299
  const allProfiles = [
953
- path10.join(home, ".bashrc"),
954
- path10.join(home, ".zshrc"),
955
- path10.join(home, ".bash_profile"),
956
- path10.join(home, ".profile")
1300
+ path12.join(home, ".bashrc"),
1301
+ path12.join(home, ".zshrc"),
1302
+ path12.join(home, ".bash_profile"),
1303
+ path12.join(home, ".profile")
957
1304
  ];
958
1305
  if (process.platform === "win32") {
959
1306
  const psProfile = getPowerShellProfilePath();
960
1307
  if (psProfile) allProfiles.push(psProfile);
961
1308
  }
962
1309
  for (const profilePath of allProfiles) {
963
- if (!fs10.existsSync(profilePath)) continue;
964
- const content = fs10.readFileSync(profilePath, "utf-8");
1310
+ if (!fs11.existsSync(profilePath)) continue;
1311
+ const content = fs11.readFileSync(profilePath, "utf-8");
965
1312
  const beginIdx = content.indexOf(BEGIN_MARKER);
966
1313
  const endIdx = content.indexOf(END_MARKER);
967
1314
  if (beginIdx === -1 || endIdx === -1) continue;
968
1315
  const backupPath = `${profilePath}.skalpel-backup`;
969
- if (fs10.existsSync(backupPath)) {
970
- fs10.copyFileSync(backupPath, profilePath);
971
- fs10.unlinkSync(backupPath);
1316
+ if (fs11.existsSync(backupPath)) {
1317
+ fs11.copyFileSync(backupPath, profilePath);
1318
+ fs11.unlinkSync(backupPath);
972
1319
  } else {
973
1320
  const before = content.slice(0, beginIdx);
974
1321
  const after = content.slice(endIdx + END_MARKER.length);
975
1322
  const cleaned = (before.replace(/\n+$/, "") + after.replace(/^\n+/, "\n")).trimEnd() + "\n";
976
- fs10.writeFileSync(profilePath, cleaned);
1323
+ fs11.writeFileSync(profilePath, cleaned);
977
1324
  }
978
1325
  restored.push(profilePath);
979
1326
  }
@@ -981,27 +1328,27 @@ function removeShellEnvVars() {
981
1328
  }
982
1329
 
983
1330
  // src/cli/agents/configure.ts
984
- import fs11 from "fs";
985
- import path11 from "path";
986
- import os4 from "os";
1331
+ import fs12 from "fs";
1332
+ import path13 from "path";
1333
+ import os7 from "os";
987
1334
  function ensureDir(dir) {
988
- fs11.mkdirSync(dir, { recursive: true });
1335
+ fs12.mkdirSync(dir, { recursive: true });
989
1336
  }
990
1337
  function createBackup2(filePath) {
991
- if (fs11.existsSync(filePath)) {
992
- fs11.copyFileSync(filePath, `${filePath}.skalpel-backup`);
1338
+ if (fs12.existsSync(filePath)) {
1339
+ fs12.copyFileSync(filePath, `${filePath}.skalpel-backup`);
993
1340
  }
994
1341
  }
995
1342
  function readJsonFile(filePath) {
996
1343
  try {
997
- return JSON.parse(fs11.readFileSync(filePath, "utf-8"));
1344
+ return JSON.parse(fs12.readFileSync(filePath, "utf-8"));
998
1345
  } catch {
999
1346
  return {};
1000
1347
  }
1001
1348
  }
1002
1349
  function configureClaudeCode(agent, proxyConfig) {
1003
- const configPath = agent.configPath ?? path11.join(os4.homedir(), ".claude", "settings.json");
1004
- const configDir = path11.dirname(configPath);
1350
+ const configPath = agent.configPath ?? path13.join(os7.homedir(), ".claude", "settings.json");
1351
+ const configDir = path13.dirname(configPath);
1005
1352
  ensureDir(configDir);
1006
1353
  createBackup2(configPath);
1007
1354
  const config = readJsonFile(configPath);
@@ -1009,16 +1356,16 @@ function configureClaudeCode(agent, proxyConfig) {
1009
1356
  config.env = {};
1010
1357
  }
1011
1358
  config.env.ANTHROPIC_BASE_URL = `http://localhost:${proxyConfig.anthropicPort}`;
1012
- fs11.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
1359
+ fs12.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
1013
1360
  }
1014
1361
  function configureCodex(agent, proxyConfig) {
1015
- const configDir = process.platform === "win32" ? path11.join(os4.homedir(), "AppData", "Roaming", "codex") : path11.join(os4.homedir(), ".codex");
1016
- const configPath = agent.configPath ?? path11.join(configDir, "config.json");
1017
- ensureDir(path11.dirname(configPath));
1362
+ const configDir = process.platform === "win32" ? path13.join(os7.homedir(), "AppData", "Roaming", "codex") : path13.join(os7.homedir(), ".codex");
1363
+ const configPath = agent.configPath ?? path13.join(configDir, "config.json");
1364
+ ensureDir(path13.dirname(configPath));
1018
1365
  createBackup2(configPath);
1019
1366
  const config = readJsonFile(configPath);
1020
1367
  config.apiBaseUrl = `http://localhost:${proxyConfig.openaiPort}`;
1021
- fs11.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
1368
+ fs12.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
1022
1369
  }
1023
1370
  function configureAgent(agent, proxyConfig) {
1024
1371
  switch (agent.name) {
@@ -1031,14 +1378,14 @@ function configureAgent(agent, proxyConfig) {
1031
1378
  }
1032
1379
  }
1033
1380
  function unconfigureClaudeCode(agent) {
1034
- const configPath = agent.configPath ?? path11.join(os4.homedir(), ".claude", "settings.json");
1381
+ const configPath = agent.configPath ?? path13.join(os7.homedir(), ".claude", "settings.json");
1035
1382
  const backupPath = `${configPath}.skalpel-backup`;
1036
- if (fs11.existsSync(backupPath)) {
1037
- fs11.copyFileSync(backupPath, configPath);
1038
- fs11.unlinkSync(backupPath);
1383
+ if (fs12.existsSync(backupPath)) {
1384
+ fs12.copyFileSync(backupPath, configPath);
1385
+ fs12.unlinkSync(backupPath);
1039
1386
  return;
1040
1387
  }
1041
- if (!fs11.existsSync(configPath)) return;
1388
+ if (!fs12.existsSync(configPath)) return;
1042
1389
  const config = readJsonFile(configPath);
1043
1390
  if (config.env && typeof config.env === "object") {
1044
1391
  delete config.env.ANTHROPIC_BASE_URL;
@@ -1046,21 +1393,21 @@ function unconfigureClaudeCode(agent) {
1046
1393
  delete config.env;
1047
1394
  }
1048
1395
  }
1049
- fs11.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
1396
+ fs12.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
1050
1397
  }
1051
1398
  function unconfigureCodex(agent) {
1052
- const configDir = process.platform === "win32" ? path11.join(os4.homedir(), "AppData", "Roaming", "codex") : path11.join(os4.homedir(), ".codex");
1053
- const configPath = agent.configPath ?? path11.join(configDir, "config.json");
1399
+ const configDir = process.platform === "win32" ? path13.join(os7.homedir(), "AppData", "Roaming", "codex") : path13.join(os7.homedir(), ".codex");
1400
+ const configPath = agent.configPath ?? path13.join(configDir, "config.json");
1054
1401
  const backupPath = `${configPath}.skalpel-backup`;
1055
- if (fs11.existsSync(backupPath)) {
1056
- fs11.copyFileSync(backupPath, configPath);
1057
- fs11.unlinkSync(backupPath);
1402
+ if (fs12.existsSync(backupPath)) {
1403
+ fs12.copyFileSync(backupPath, configPath);
1404
+ fs12.unlinkSync(backupPath);
1058
1405
  return;
1059
1406
  }
1060
- if (!fs11.existsSync(configPath)) return;
1407
+ if (!fs12.existsSync(configPath)) return;
1061
1408
  const config = readJsonFile(configPath);
1062
1409
  delete config.apiBaseUrl;
1063
- fs11.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
1410
+ fs12.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
1064
1411
  }
1065
1412
  function unconfigureAgent(agent) {
1066
1413
  switch (agent.name) {
@@ -1073,268 +1420,6 @@ function unconfigureAgent(agent) {
1073
1420
  }
1074
1421
  }
1075
1422
 
1076
- // src/cli/service/install.ts
1077
- import fs12 from "fs";
1078
- import path13 from "path";
1079
- import os7 from "os";
1080
- import { execSync as execSync3 } from "child_process";
1081
- import { fileURLToPath as fileURLToPath2 } from "url";
1082
-
1083
- // src/cli/service/detect-os.ts
1084
- import os5 from "os";
1085
- import { execSync as execSync2 } from "child_process";
1086
- function detectShell() {
1087
- if (process.platform === "win32") {
1088
- if (process.env.PSModulePath || process.env.POWERSHELL_DISTRIBUTION_CHANNEL) {
1089
- return "powershell";
1090
- }
1091
- return "cmd";
1092
- }
1093
- const shellPath = process.env.SHELL ?? "";
1094
- if (shellPath.includes("zsh")) return "zsh";
1095
- if (shellPath.includes("fish")) return "fish";
1096
- if (shellPath.includes("bash")) return "bash";
1097
- try {
1098
- if (process.platform === "darwin") {
1099
- const result = execSync2(`dscl . -read /Users/${os5.userInfo().username} UserShell`, {
1100
- encoding: "utf-8",
1101
- timeout: 3e3
1102
- }).trim();
1103
- const shell = result.split(":").pop()?.trim() ?? "";
1104
- if (shell.includes("zsh")) return "zsh";
1105
- if (shell.includes("fish")) return "fish";
1106
- if (shell.includes("bash")) return "bash";
1107
- } else {
1108
- const result = execSync2(`getent passwd ${os5.userInfo().username}`, {
1109
- encoding: "utf-8",
1110
- timeout: 3e3
1111
- }).trim();
1112
- const shell = result.split(":").pop() ?? "";
1113
- if (shell.includes("zsh")) return "zsh";
1114
- if (shell.includes("fish")) return "fish";
1115
- if (shell.includes("bash")) return "bash";
1116
- }
1117
- } catch {
1118
- }
1119
- return "bash";
1120
- }
1121
- function detectOS() {
1122
- let platform;
1123
- switch (process.platform) {
1124
- case "darwin":
1125
- platform = "macos";
1126
- break;
1127
- case "win32":
1128
- platform = "windows";
1129
- break;
1130
- default:
1131
- platform = "linux";
1132
- break;
1133
- }
1134
- return {
1135
- platform,
1136
- shell: detectShell(),
1137
- homeDir: os5.homedir()
1138
- };
1139
- }
1140
-
1141
- // src/cli/service/templates.ts
1142
- import os6 from "os";
1143
- import path12 from "path";
1144
- function generateLaunchdPlist(config, proxyRunnerPath) {
1145
- const logDir = path12.join(os6.homedir(), ".skalpel", "logs");
1146
- return `<?xml version="1.0" encoding="UTF-8"?>
1147
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1148
- <plist version="1.0">
1149
- <dict>
1150
- <key>Label</key>
1151
- <string>ai.skalpel.proxy</string>
1152
- <key>ProgramArguments</key>
1153
- <array>
1154
- <string>${process.execPath}</string>
1155
- <string>${proxyRunnerPath}</string>
1156
- </array>
1157
- <key>RunAtLoad</key>
1158
- <true/>
1159
- <key>KeepAlive</key>
1160
- <true/>
1161
- <key>StandardOutPath</key>
1162
- <string>${path12.join(logDir, "proxy-stdout.log")}</string>
1163
- <key>StandardErrorPath</key>
1164
- <string>${path12.join(logDir, "proxy-stderr.log")}</string>
1165
- <key>EnvironmentVariables</key>
1166
- <dict>
1167
- <key>SKALPEL_ANTHROPIC_PORT</key>
1168
- <string>${config.anthropicPort}</string>
1169
- <key>SKALPEL_OPENAI_PORT</key>
1170
- <string>${config.openaiPort}</string>
1171
- </dict>
1172
- </dict>
1173
- </plist>`;
1174
- }
1175
- function generateSystemdUnit(config, proxyRunnerPath) {
1176
- return `[Unit]
1177
- Description=Skalpel Proxy
1178
- After=network.target
1179
-
1180
- [Service]
1181
- Type=simple
1182
- ExecStart=${process.execPath} ${proxyRunnerPath}
1183
- Restart=always
1184
- RestartSec=5
1185
- Environment=SKALPEL_ANTHROPIC_PORT=${config.anthropicPort}
1186
- Environment=SKALPEL_OPENAI_PORT=${config.openaiPort}
1187
-
1188
- [Install]
1189
- WantedBy=default.target`;
1190
- }
1191
- function generateWindowsTask(config, proxyRunnerPath) {
1192
- return [
1193
- "/create",
1194
- "/tn",
1195
- "SkalpelProxy",
1196
- "/tr",
1197
- `"${process.execPath}" "${proxyRunnerPath}"`,
1198
- "/sc",
1199
- "ONLOGON",
1200
- "/rl",
1201
- "LIMITED",
1202
- "/f"
1203
- ];
1204
- }
1205
-
1206
- // src/cli/service/install.ts
1207
- var __dirname = path13.dirname(fileURLToPath2(import.meta.url));
1208
- function resolveProxyRunnerPath() {
1209
- const candidates = [
1210
- path13.join(__dirname, "..", "proxy-runner.js"),
1211
- // dist/cli/proxy-runner.js relative to dist/cli/service/
1212
- path13.join(__dirname, "proxy-runner.js"),
1213
- // same dir
1214
- path13.join(__dirname, "..", "..", "cli", "proxy-runner.js")
1215
- // dist/cli/proxy-runner.js from deeper
1216
- ];
1217
- for (const candidate of candidates) {
1218
- if (fs12.existsSync(candidate)) {
1219
- return path13.resolve(candidate);
1220
- }
1221
- }
1222
- try {
1223
- const npmRoot = execSync3("npm root -g", { encoding: "utf-8" }).trim();
1224
- const globalPath = path13.join(npmRoot, "skalpel", "dist", "cli", "proxy-runner.js");
1225
- if (fs12.existsSync(globalPath)) return globalPath;
1226
- } catch {
1227
- }
1228
- const devPath = path13.resolve(process.cwd(), "dist", "cli", "proxy-runner.js");
1229
- return devPath;
1230
- }
1231
- function getMacOSPlistPath() {
1232
- return path13.join(os7.homedir(), "Library", "LaunchAgents", "ai.skalpel.proxy.plist");
1233
- }
1234
- function getLinuxUnitPath() {
1235
- return path13.join(os7.homedir(), ".config", "systemd", "user", "skalpel-proxy.service");
1236
- }
1237
- function installService(config) {
1238
- const osInfo = detectOS();
1239
- const proxyRunnerPath = resolveProxyRunnerPath();
1240
- const logDir = path13.join(os7.homedir(), ".skalpel", "logs");
1241
- fs12.mkdirSync(logDir, { recursive: true });
1242
- switch (osInfo.platform) {
1243
- case "macos": {
1244
- const plistPath = getMacOSPlistPath();
1245
- const plistDir = path13.dirname(plistPath);
1246
- fs12.mkdirSync(plistDir, { recursive: true });
1247
- const plist = generateLaunchdPlist(config, proxyRunnerPath);
1248
- fs12.writeFileSync(plistPath, plist);
1249
- try {
1250
- execSync3(`launchctl unload "${plistPath}" 2>/dev/null || true`, { stdio: "pipe" });
1251
- execSync3(`launchctl load "${plistPath}"`, { stdio: "pipe" });
1252
- } catch (err) {
1253
- const msg = err instanceof Error ? err.message : String(err);
1254
- console.warn(` Warning: Could not register launchd service: ${msg}`);
1255
- console.warn(` You can manually load it: launchctl load "${plistPath}"`);
1256
- }
1257
- break;
1258
- }
1259
- case "linux": {
1260
- const unitPath = getLinuxUnitPath();
1261
- const unitDir = path13.dirname(unitPath);
1262
- fs12.mkdirSync(unitDir, { recursive: true });
1263
- const unit = generateSystemdUnit(config, proxyRunnerPath);
1264
- fs12.writeFileSync(unitPath, unit);
1265
- try {
1266
- execSync3("systemctl --user daemon-reload", { stdio: "pipe" });
1267
- execSync3("systemctl --user enable skalpel-proxy", { stdio: "pipe" });
1268
- execSync3("systemctl --user start skalpel-proxy", { stdio: "pipe" });
1269
- } catch {
1270
- try {
1271
- const autostartDir = path13.join(os7.homedir(), ".config", "autostart");
1272
- fs12.mkdirSync(autostartDir, { recursive: true });
1273
- const desktopEntry = `[Desktop Entry]
1274
- Type=Application
1275
- Name=Skalpel Proxy
1276
- Exec=${process.execPath} ${proxyRunnerPath}
1277
- Hidden=false
1278
- NoDisplay=true
1279
- X-GNOME-Autostart-enabled=true
1280
- `;
1281
- fs12.writeFileSync(path13.join(autostartDir, "skalpel-proxy.desktop"), desktopEntry);
1282
- console.warn(" Warning: systemd --user not available. Created .desktop autostart entry instead.");
1283
- } catch (err2) {
1284
- const msg = err2 instanceof Error ? err2.message : String(err2);
1285
- console.warn(` Warning: Could not register service: ${msg}`);
1286
- console.warn(" You can start the proxy manually: skalpel start");
1287
- }
1288
- }
1289
- break;
1290
- }
1291
- case "windows": {
1292
- const args = generateWindowsTask(config, proxyRunnerPath);
1293
- try {
1294
- execSync3(`schtasks ${args.join(" ")}`, { stdio: "pipe" });
1295
- } catch (err) {
1296
- const msg = err instanceof Error ? err.message : String(err);
1297
- console.warn(` Warning: Could not create scheduled task: ${msg}`);
1298
- console.warn(" You can start the proxy manually: skalpel start");
1299
- }
1300
- break;
1301
- }
1302
- }
1303
- }
1304
- function uninstallService() {
1305
- const osInfo = detectOS();
1306
- switch (osInfo.platform) {
1307
- case "macos": {
1308
- const plistPath = getMacOSPlistPath();
1309
- try {
1310
- execSync3(`launchctl unload "${plistPath}" 2>/dev/null || true`, { stdio: "pipe" });
1311
- } catch {
1312
- }
1313
- if (fs12.existsSync(plistPath)) fs12.unlinkSync(plistPath);
1314
- break;
1315
- }
1316
- case "linux": {
1317
- try {
1318
- execSync3("systemctl --user stop skalpel-proxy 2>/dev/null || true", { stdio: "pipe" });
1319
- execSync3("systemctl --user disable skalpel-proxy 2>/dev/null || true", { stdio: "pipe" });
1320
- } catch {
1321
- }
1322
- const unitPath = getLinuxUnitPath();
1323
- if (fs12.existsSync(unitPath)) fs12.unlinkSync(unitPath);
1324
- const desktopPath = path13.join(os7.homedir(), ".config", "autostart", "skalpel-proxy.desktop");
1325
- if (fs12.existsSync(desktopPath)) fs12.unlinkSync(desktopPath);
1326
- break;
1327
- }
1328
- case "windows": {
1329
- try {
1330
- execSync3("schtasks /delete /tn SkalpelProxy /f", { stdio: "pipe" });
1331
- } catch {
1332
- }
1333
- break;
1334
- }
1335
- }
1336
- }
1337
-
1338
1423
  // src/cli/wizard.ts
1339
1424
  function print11(msg) {
1340
1425
  console.log(msg);