repowisestage 0.0.68 → 0.0.70

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/bin/repowise.js +1497 -1348
  2. package/package.json +1 -1
@@ -620,388 +620,69 @@ var init_process_manager = __esm({
620
620
  }
621
621
  });
622
622
 
623
- // ../listener/dist/service-installer.js
624
- var service_installer_exports = {};
625
- __export(service_installer_exports, {
626
- install: () => install,
627
- isInstalled: () => isInstalled,
628
- isServiceRunning: () => isServiceRunning,
629
- stopService: () => stopService,
630
- uninstall: () => uninstall
631
- });
632
- import { execFile as execFile4 } from "child_process";
633
- import { writeFile as writeFile8, mkdir as mkdir8, unlink as unlink6 } from "fs/promises";
634
- import { homedir as homedir3 } from "os";
635
- import { join as join13 } from "path";
636
- function exec(cmd, args) {
637
- return new Promise((resolve5, reject) => {
638
- execFile4(cmd, args, (err, stdout) => {
639
- if (err) {
640
- reject(err);
641
- return;
642
- }
643
- resolve5(String(stdout ?? ""));
644
- });
645
- });
646
- }
647
- function plistPath() {
648
- return join13(homedir3(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
649
- }
650
- function logDir() {
651
- return join13(getConfigDir(), "logs");
652
- }
653
- function buildPlist() {
654
- const cmd = resolveListenerCommand();
655
- const logs = logDir();
656
- const programArgs = [process.execPath, cmd.script, ...cmd.args].map((a) => ` <string>${a}</string>`).join("\n");
657
- return `<?xml version="1.0" encoding="UTF-8"?>
658
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
659
- "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
660
- <plist version="1.0">
661
- <dict>
662
- <key>Label</key>
663
- <string>${PLIST_LABEL}</string>
664
- <key>ProgramArguments</key>
665
- <array>
666
- ${programArgs}
667
- </array>
668
- <key>RunAtLoad</key>
669
- <true/>
670
- <key>KeepAlive</key>
671
- <true/>
672
- <key>StandardOutPath</key>
673
- <string>${join13(logs, "listener-stdout.log")}</string>
674
- <key>StandardErrorPath</key>
675
- <string>${join13(logs, "listener-stderr.log")}</string>
676
- <key>ProcessType</key>
677
- <string>Background</string>
678
- <!--
679
- SoftResourceLimits: raise the open-file descriptor limit from the macOS
680
- default of 256 to 4096. When multiple LSP child processes (rust-analyzer,
681
- jdtls, etc.) share the listener's fd budget they can exhaust the default
682
- very quickly, causing EMFILE errors. 4096 matches the system-wide default
683
- set by /etc/launchd.conf on modern macOS and gives enough headroom for
684
- ~10 concurrent LSP servers each opening ~300 fds.
685
-
686
- MemoryLimit: intentionally NOT set here. The launchd MemoryLimit key
687
- (available since macOS 12.3) terminates the entire LaunchAgent when RSS
688
- exceeds the threshold \u2014 it cannot be scoped to individual child processes.
689
- Killing the listener to evict a memory-hungry rust-analyzer instance is
690
- undesirable. Per-child memory pressure is better handled by the LSP
691
- process manager (process-manager.ts) or by OS-level memory pressure
692
- signals (SIGTERM on macOS, OOM killer on Linux).
693
- -->
694
- <key>SoftResourceLimits</key>
695
- <dict>
696
- <key>NumberOfFiles</key>
697
- <integer>4096</integer>
698
- </dict>
699
- </dict>
700
- </plist>`;
701
- }
702
- async function darwinInstall() {
703
- await mkdir8(logDir(), { recursive: true });
704
- await mkdir8(join13(homedir3(), "Library", "LaunchAgents"), { recursive: true });
705
- try {
706
- await exec("launchctl", ["unload", plistPath()]);
707
- } catch {
708
- }
709
- await writeFile8(plistPath(), buildPlist());
710
- await exec("launchctl", ["load", plistPath()]);
711
- }
712
- async function darwinUninstall() {
713
- try {
714
- await exec("launchctl", ["unload", plistPath()]);
715
- } catch {
716
- }
717
- try {
718
- await unlink6(plistPath());
719
- } catch {
720
- }
721
- }
722
- async function darwinIsInstalled() {
723
- try {
724
- const stdout = await exec("launchctl", ["list"]);
725
- return stdout.includes(PLIST_LABEL);
726
- } catch {
727
- return false;
728
- }
729
- }
730
- function unitPath() {
731
- return join13(homedir3(), ".config", "systemd", "user", `${SYSTEMD_SERVICE}.service`);
732
- }
733
- function buildUnit() {
734
- const cmd = resolveListenerCommand();
735
- const execStart = [process.execPath, cmd.script, ...cmd.args].join(" ");
736
- const logs = logDir();
737
- return `[Unit]
738
- Description=RepoWise Listener
739
- After=network-online.target
740
- Wants=network-online.target
741
-
742
- [Service]
743
- Type=simple
744
- ExecStart=${execStart}
745
- Restart=always
746
- RestartSec=10
747
- StandardOutput=append:${join13(logs, "listener-stdout.log")}
748
- StandardError=append:${join13(logs, "listener-stderr.log")}
749
-
750
- [Install]
751
- WantedBy=default.target`;
752
- }
753
- async function linuxInstall() {
754
- await mkdir8(logDir(), { recursive: true });
755
- await mkdir8(join13(homedir3(), ".config", "systemd", "user"), { recursive: true });
756
- await writeFile8(unitPath(), buildUnit());
757
- await exec("systemctl", ["--user", "daemon-reload"]);
758
- await exec("systemctl", ["--user", "enable", SYSTEMD_SERVICE]);
759
- await exec("systemctl", ["--user", "start", SYSTEMD_SERVICE]);
760
- }
761
- async function linuxUninstall() {
762
- try {
763
- await exec("systemctl", ["--user", "stop", SYSTEMD_SERVICE]);
764
- } catch {
765
- }
766
- try {
767
- await exec("systemctl", ["--user", "disable", SYSTEMD_SERVICE]);
768
- } catch {
769
- }
770
- try {
771
- await unlink6(unitPath());
772
- } catch {
773
- }
774
- try {
775
- await exec("systemctl", ["--user", "daemon-reload"]);
776
- } catch {
777
- }
623
+ // ../listener/dist/lsp/registry.js
624
+ function resolvePhpLspOverride(env = process.env, configOverrides) {
625
+ const envChoice = env["PHP_LSP"];
626
+ const configChoice = configOverrides?.["php"];
627
+ const choice = envChoice ?? configChoice;
628
+ if (choice !== "intelephense")
629
+ return null;
630
+ return {
631
+ id: "intelephense",
632
+ displayName: "intelephense (user-provided)",
633
+ command: "intelephense",
634
+ args: ["--stdio"],
635
+ extensions: [".php"],
636
+ lspLanguageId: "php",
637
+ installHint: "Intelephense override is enabled \u2014 install it yourself: `npm i -g intelephense` (commercial licence required for premium features)."
638
+ };
778
639
  }
779
- async function linuxIsInstalled() {
780
- try {
781
- const stdout = await exec("systemctl", ["--user", "is-enabled", SYSTEMD_SERVICE]);
782
- return stdout.trim() === "enabled";
783
- } catch {
784
- return false;
640
+ function getEffectiveConfig(lang, env = process.env, configOverrides) {
641
+ if (lang === "php") {
642
+ const override = resolvePhpLspOverride(env, configOverrides);
643
+ if (override)
644
+ return override;
785
645
  }
646
+ return LSP_REGISTRY[lang]?.[0];
786
647
  }
787
- async function win32Install() {
788
- await mkdir8(logDir(), { recursive: true });
789
- const cmd = resolveListenerCommand();
790
- const taskCmd = [process.execPath, cmd.script, ...cmd.args].map((a) => `"${a}"`).join(" ");
791
- await exec("schtasks", [
792
- "/create",
793
- "/tn",
794
- TASK_NAME,
795
- "/tr",
796
- taskCmd,
797
- "/sc",
798
- "onlogon",
799
- "/ru",
800
- process.env.USERNAME ?? "",
801
- "/f"
802
- ]);
803
- await exec("schtasks", ["/run", "/tn", TASK_NAME]);
804
- }
805
- async function win32Uninstall() {
806
- try {
807
- await exec("schtasks", ["/end", "/tn", TASK_NAME]);
808
- } catch {
809
- }
810
- try {
811
- await exec("schtasks", ["/delete", "/tn", TASK_NAME, "/f"]);
812
- } catch {
648
+ function getEffectiveConfigList(lang, env = process.env, configOverrides) {
649
+ if (lang === "php") {
650
+ const override = resolvePhpLspOverride(env, configOverrides);
651
+ if (override)
652
+ return [override];
813
653
  }
654
+ return LSP_REGISTRY[lang] ?? [];
814
655
  }
815
- async function win32IsInstalled() {
816
- try {
817
- await exec("schtasks", ["/query", "/tn", TASK_NAME]);
818
- return true;
819
- } catch {
820
- return false;
656
+ function detectLanguage(path) {
657
+ const dot = path.lastIndexOf(".");
658
+ if (dot < 0)
659
+ return null;
660
+ const ext = path.slice(dot).toLowerCase();
661
+ for (const [lang, configs] of Object.entries(LSP_REGISTRY)) {
662
+ if (configs.some((c) => c.extensions.includes(ext))) {
663
+ return lang;
664
+ }
821
665
  }
666
+ return null;
822
667
  }
823
- async function darwinStop() {
824
- try {
825
- await exec("launchctl", ["unload", plistPath()]);
826
- } catch {
668
+ async function probeServers(isAvailable, env = process.env, configOverrides) {
669
+ const out = [];
670
+ for (const lang of Object.keys(LSP_REGISTRY)) {
671
+ const configs = getEffectiveConfigList(lang, env, configOverrides);
672
+ const checkedCommands = configs.map((c) => c.command);
673
+ let found = null;
674
+ for (const config2 of configs) {
675
+ if (await isAvailable(config2.command)) {
676
+ found = config2;
677
+ break;
678
+ }
679
+ }
680
+ out.push({ language: lang, config: found, checkedCommands });
827
681
  }
682
+ return out;
828
683
  }
829
- async function linuxStop() {
830
- await exec("systemctl", ["--user", "stop", SYSTEMD_SERVICE]);
831
- }
832
- async function win32Stop() {
833
- await exec("schtasks", ["/end", "/tn", TASK_NAME]);
834
- }
835
- async function darwinIsServiceRunning() {
836
- try {
837
- const stdout = await exec("launchctl", ["list", PLIST_LABEL]);
838
- const pidMatch = stdout.match(/"PID"\s*=\s*(\d+)/);
839
- return pidMatch !== null && parseInt(pidMatch[1], 10) > 0;
840
- } catch {
841
- return false;
842
- }
843
- }
844
- async function linuxIsServiceRunning() {
845
- try {
846
- const stdout = await exec("systemctl", ["--user", "is-active", SYSTEMD_SERVICE]);
847
- return stdout.trim() === "active";
848
- } catch {
849
- return false;
850
- }
851
- }
852
- async function win32IsServiceRunning() {
853
- try {
854
- const stdout = await exec("schtasks", ["/query", "/tn", TASK_NAME, "/fo", "CSV"]);
855
- return stdout.includes("Running");
856
- } catch {
857
- return false;
858
- }
859
- }
860
- async function stopService() {
861
- switch (process.platform) {
862
- case "darwin":
863
- await darwinStop();
864
- break;
865
- case "linux":
866
- await linuxStop();
867
- break;
868
- case "win32":
869
- await win32Stop();
870
- break;
871
- default:
872
- throw new Error(`Unsupported platform: ${process.platform}`);
873
- }
874
- }
875
- async function install() {
876
- switch (process.platform) {
877
- case "darwin":
878
- await darwinInstall();
879
- break;
880
- case "linux":
881
- await linuxInstall();
882
- break;
883
- case "win32":
884
- await win32Install();
885
- break;
886
- default:
887
- throw new Error(`Unsupported platform: ${process.platform}`);
888
- }
889
- }
890
- async function uninstall() {
891
- switch (process.platform) {
892
- case "darwin":
893
- await darwinUninstall();
894
- break;
895
- case "linux":
896
- await linuxUninstall();
897
- break;
898
- case "win32":
899
- await win32Uninstall();
900
- break;
901
- default:
902
- throw new Error(`Unsupported platform: ${process.platform}`);
903
- }
904
- }
905
- async function isInstalled() {
906
- switch (process.platform) {
907
- case "darwin":
908
- return darwinIsInstalled();
909
- case "linux":
910
- return linuxIsInstalled();
911
- case "win32":
912
- return win32IsInstalled();
913
- default:
914
- return false;
915
- }
916
- }
917
- async function isServiceRunning() {
918
- switch (process.platform) {
919
- case "darwin":
920
- return darwinIsServiceRunning();
921
- case "linux":
922
- return linuxIsServiceRunning();
923
- case "win32":
924
- return win32IsServiceRunning();
925
- default:
926
- return false;
927
- }
928
- }
929
- var IS_STAGING, PLIST_LABEL, SYSTEMD_SERVICE, TASK_NAME;
930
- var init_service_installer = __esm({
931
- "../listener/dist/service-installer.js"() {
932
- "use strict";
933
- init_config_dir();
934
- init_process_manager();
935
- IS_STAGING = true ? true : false;
936
- PLIST_LABEL = IS_STAGING ? "com.repowise-staging.listener" : "com.repowise.listener";
937
- SYSTEMD_SERVICE = IS_STAGING ? "repowise-staging-listener" : "repowise-listener";
938
- TASK_NAME = IS_STAGING ? "RepoWise Staging Listener" : "RepoWise Listener";
939
- }
940
- });
941
-
942
- // ../listener/dist/lsp/registry.js
943
- function resolvePhpLspOverride(env = process.env, configOverrides) {
944
- const envChoice = env["PHP_LSP"];
945
- const configChoice = configOverrides?.["php"];
946
- const choice = envChoice ?? configChoice;
947
- if (choice !== "intelephense")
948
- return null;
949
- return {
950
- id: "intelephense",
951
- displayName: "intelephense (user-provided)",
952
- command: "intelephense",
953
- args: ["--stdio"],
954
- extensions: [".php"],
955
- lspLanguageId: "php",
956
- installHint: "Intelephense override is enabled \u2014 install it yourself: `npm i -g intelephense` (commercial licence required for premium features)."
957
- };
958
- }
959
- function getEffectiveConfig(lang, env = process.env, configOverrides) {
960
- if (lang === "php") {
961
- const override = resolvePhpLspOverride(env, configOverrides);
962
- if (override)
963
- return override;
964
- }
965
- return LSP_REGISTRY[lang]?.[0];
966
- }
967
- function getEffectiveConfigList(lang, env = process.env, configOverrides) {
968
- if (lang === "php") {
969
- const override = resolvePhpLspOverride(env, configOverrides);
970
- if (override)
971
- return [override];
972
- }
973
- return LSP_REGISTRY[lang] ?? [];
974
- }
975
- function detectLanguage(path) {
976
- const dot = path.lastIndexOf(".");
977
- if (dot < 0)
978
- return null;
979
- const ext = path.slice(dot).toLowerCase();
980
- for (const [lang, configs] of Object.entries(LSP_REGISTRY)) {
981
- if (configs.some((c) => c.extensions.includes(ext))) {
982
- return lang;
983
- }
984
- }
985
- return null;
986
- }
987
- async function probeServers(isAvailable, env = process.env, configOverrides) {
988
- const out = [];
989
- for (const lang of Object.keys(LSP_REGISTRY)) {
990
- const configs = getEffectiveConfigList(lang, env, configOverrides);
991
- const checkedCommands = configs.map((c) => c.command);
992
- let found = null;
993
- for (const config2 of configs) {
994
- if (await isAvailable(config2.command)) {
995
- found = config2;
996
- break;
997
- }
998
- }
999
- out.push({ language: lang, config: found, checkedCommands });
1000
- }
1001
- return out;
1002
- }
1003
- function getBuildSpec(language) {
1004
- return BUILD_SPECS[language] ?? null;
684
+ function getBuildSpec(language) {
685
+ return BUILD_SPECS[language] ?? null;
1005
686
  }
1006
687
  var LSP_REGISTRY, BUILD_SPECS;
1007
688
  var init_registry = __esm({
@@ -1415,7 +1096,7 @@ var init_registry = __esm({
1415
1096
  });
1416
1097
 
1417
1098
  // ../listener/dist/lsp/jdk-probe.js
1418
- import { spawn as spawn3 } from "child_process";
1099
+ import { spawn as spawn2 } from "child_process";
1419
1100
  function parseJavaMajorVersion(stderr) {
1420
1101
  const modernMatch = /\b(?:openjdk|java) version "(\d+)(?:\.\d+)*(?:[._]\w+)?"/.exec(stderr);
1421
1102
  if (!modernMatch?.[1])
@@ -1430,166 +1111,503 @@ function parseJavaMajorVersion(stderr) {
1430
1111
  return Number.isFinite(legacy) ? legacy : void 0;
1431
1112
  }
1432
1113
  }
1433
- return first;
1114
+ return first;
1115
+ }
1116
+ async function probeJdk(options = {}) {
1117
+ const timeoutMs = options.timeoutMs ?? 5e3;
1118
+ return new Promise((resolve5) => {
1119
+ let stderr = "";
1120
+ let settled = false;
1121
+ const child = spawn2("java", ["-version"], { stdio: ["ignore", "pipe", "pipe"] });
1122
+ const timer = setTimeout(() => {
1123
+ if (settled)
1124
+ return;
1125
+ settled = true;
1126
+ try {
1127
+ child.kill("SIGKILL");
1128
+ } catch {
1129
+ }
1130
+ resolve5({ present: false });
1131
+ }, timeoutMs);
1132
+ child.stderr.on("data", (chunk) => {
1133
+ stderr += chunk.toString("utf8");
1134
+ });
1135
+ child.on("error", () => {
1136
+ if (settled)
1137
+ return;
1138
+ settled = true;
1139
+ clearTimeout(timer);
1140
+ resolve5({ present: false });
1141
+ });
1142
+ child.on("close", (code) => {
1143
+ if (settled)
1144
+ return;
1145
+ settled = true;
1146
+ clearTimeout(timer);
1147
+ if (code !== 0) {
1148
+ resolve5({ present: false, rawOutput: stderr.trim() });
1149
+ return;
1150
+ }
1151
+ const major = parseJavaMajorVersion(stderr);
1152
+ if (major === void 0) {
1153
+ resolve5({ present: true, rawOutput: stderr.trim() });
1154
+ return;
1155
+ }
1156
+ resolve5({ present: true, majorVersion: major, rawOutput: stderr.trim() });
1157
+ });
1158
+ });
1159
+ }
1160
+ var init_jdk_probe = __esm({
1161
+ "../listener/dist/lsp/jdk-probe.js"() {
1162
+ "use strict";
1163
+ }
1164
+ });
1165
+
1166
+ // ../listener/dist/lsp/native-installer.js
1167
+ import { createWriteStream } from "fs";
1168
+ import { promises as fs } from "fs";
1169
+ import { createGunzip } from "zlib";
1170
+ import { spawn as spawn3 } from "child_process";
1171
+ import { pipeline } from "stream/promises";
1172
+ import { createHash as createHash3 } from "crypto";
1173
+ import { join as join13, dirname as dirname5 } from "path";
1174
+ function getNativeLspDir() {
1175
+ return join13(getLspInstallDir(), "native");
1176
+ }
1177
+ function getNativeLspBinDir() {
1178
+ return join13(getNativeLspDir(), "bin");
1179
+ }
1180
+ async function ensureNativeLspInstalled(lspKey) {
1181
+ const entry = NATIVE_LSP_VERSIONS[lspKey];
1182
+ if (!entry)
1183
+ return { installed: false, alreadyPresent: false, skipped: "no-native-version" };
1184
+ const platformKey = getCurrentPlatformKey();
1185
+ if (!platformKey) {
1186
+ return { installed: false, alreadyPresent: false, skipped: "unsupported-platform" };
1187
+ }
1188
+ const asset = entry.assets[platformKey];
1189
+ if (!asset) {
1190
+ return { installed: false, alreadyPresent: false, skipped: "unsupported-platform" };
1191
+ }
1192
+ if (entry.requiresJdk !== void 0) {
1193
+ const jdk = await probeJdk();
1194
+ if (!jdk.present || jdk.majorVersion === void 0 || jdk.majorVersion < entry.requiresJdk) {
1195
+ return {
1196
+ installed: false,
1197
+ alreadyPresent: false,
1198
+ skipped: "jvm-missing",
1199
+ error: `${lspKey} requires JDK ${entry.requiresJdk.toString()}+ (found: ${jdk.present ? `Java ${jdk.majorVersion?.toString() ?? "?"}` : "no java on PATH"}). Install with: brew install openjdk@21 (macOS) or apt install openjdk-21-jdk (Linux)`
1200
+ };
1201
+ }
1202
+ }
1203
+ const usesWrapper = Boolean(asset.launcherJarGlob && asset.wrapperBinaryName);
1204
+ const versionDir = join13(getNativeLspDir(), lspKey, entry.version);
1205
+ if (usesWrapper && asset.launcherJarGlob && asset.wrapperBinaryName) {
1206
+ const jarPath = await resolveGlobInsideDir(versionDir, asset.launcherJarGlob);
1207
+ const shimPath = join13(getNativeLspBinDir(), asset.wrapperBinaryName);
1208
+ if (jarPath && await pathExists(shimPath)) {
1209
+ return { installed: false, alreadyPresent: true, binaryPath: shimPath };
1210
+ }
1211
+ } else {
1212
+ const installedBinary = join13(versionDir, asset.binaryPath);
1213
+ if (await pathExists(installedBinary)) {
1214
+ await ensureSymlinkInBin(lspKey, installedBinary, asset.binaryPath);
1215
+ return { installed: false, alreadyPresent: true, binaryPath: installedBinary };
1216
+ }
1217
+ }
1218
+ try {
1219
+ await fs.mkdir(versionDir, { recursive: true });
1220
+ const tmpDownload = join13(versionDir, `.${asset.filename}.download`);
1221
+ const downloadUrl = entry.source.kind === "github-release" ? `https://github.com/${entry.source.repo}/releases/download/${entry.version}/${asset.filename}` : asset.url ?? "";
1222
+ if (!downloadUrl) {
1223
+ return {
1224
+ installed: false,
1225
+ alreadyPresent: false,
1226
+ error: `direct-url asset for ${lspKey} missing required \`url\` field`
1227
+ };
1228
+ }
1229
+ await downloadFile(downloadUrl, tmpDownload);
1230
+ if (asset.sha256) {
1231
+ const actual = await sha256File(tmpDownload);
1232
+ if (actual !== asset.sha256) {
1233
+ await fs.unlink(tmpDownload).catch(() => void 0);
1234
+ return {
1235
+ installed: false,
1236
+ alreadyPresent: false,
1237
+ error: `SHA256 mismatch for ${lspKey}: expected ${asset.sha256}, got ${actual}`
1238
+ };
1239
+ }
1240
+ } else {
1241
+ console.warn(`[lsp-install] WARN: SHA256 verification skipped for ${lspKey} (no pin in NATIVE_LSP_VERSIONS) \u2014 run apps/listener/scripts/pin-native-shas.ts to record one`);
1242
+ }
1243
+ await extractAsset(tmpDownload, versionDir, asset);
1244
+ await fs.unlink(tmpDownload).catch(() => void 0);
1245
+ if (usesWrapper && asset.launcherJarGlob && asset.wrapperBinaryName) {
1246
+ const jarPath = await resolveGlobInsideDir(versionDir, asset.launcherJarGlob);
1247
+ if (!jarPath) {
1248
+ return {
1249
+ installed: false,
1250
+ alreadyPresent: false,
1251
+ error: `extract completed but no JAR matched ${asset.launcherJarGlob} in ${versionDir}`
1252
+ };
1253
+ }
1254
+ const shimPath = await writeJdtlsWrapper({
1255
+ binaryName: asset.wrapperBinaryName,
1256
+ versionDir,
1257
+ launcherJarPath: jarPath
1258
+ });
1259
+ await pruneStaleVersions(lspKey, entry.version);
1260
+ return { installed: true, alreadyPresent: false, binaryPath: shimPath };
1261
+ }
1262
+ const installedBinary = join13(versionDir, asset.binaryPath);
1263
+ if (!await pathExists(installedBinary)) {
1264
+ return {
1265
+ installed: false,
1266
+ alreadyPresent: false,
1267
+ error: `extract completed but binary missing at ${installedBinary}`
1268
+ };
1269
+ }
1270
+ await fs.chmod(installedBinary, 493).catch(() => void 0);
1271
+ await ensureSymlinkInBin(lspKey, installedBinary, asset.binaryPath);
1272
+ await pruneStaleVersions(lspKey, entry.version);
1273
+ return { installed: true, alreadyPresent: false, binaryPath: installedBinary };
1274
+ } catch (err) {
1275
+ return {
1276
+ installed: false,
1277
+ alreadyPresent: false,
1278
+ error: err instanceof Error ? err.message : String(err)
1279
+ };
1280
+ }
1281
+ }
1282
+ async function resolveGlobInsideDir(versionDir, glob) {
1283
+ const parts = glob.split("/");
1284
+ let dir = versionDir;
1285
+ for (let i = 0; i < parts.length - 1; i += 1) {
1286
+ dir = join13(dir, parts[i]);
1287
+ }
1288
+ const pattern = parts[parts.length - 1];
1289
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
1290
+ const re = new RegExp(`^${escaped}$`);
1291
+ let entries;
1292
+ try {
1293
+ entries = await fs.readdir(dir);
1294
+ } catch {
1295
+ return null;
1296
+ }
1297
+ const matches = entries.filter((e) => re.test(e)).sort();
1298
+ const last = matches[matches.length - 1];
1299
+ return last ? join13(dir, last) : null;
1300
+ }
1301
+ async function pruneStaleVersions(lspKey, currentVersion) {
1302
+ const lspDir = join13(getNativeLspDir(), lspKey);
1303
+ let entries;
1304
+ try {
1305
+ entries = await fs.readdir(lspDir);
1306
+ } catch {
1307
+ return;
1308
+ }
1309
+ await Promise.all(entries.filter((name) => name !== currentVersion).map(async (name) => {
1310
+ try {
1311
+ await fs.rm(join13(lspDir, name), { recursive: true, force: true });
1312
+ } catch {
1313
+ }
1314
+ }));
1315
+ }
1316
+ function shQuote(value) {
1317
+ return value.replace(/[\\"$`]/g, (ch) => `\\${ch}`);
1318
+ }
1319
+ function cmdQuote(value) {
1320
+ return value.replace(/"/g, '""').replace(/%/g, "%%");
1321
+ }
1322
+ async function writeJdtlsWrapper(input) {
1323
+ const binDir = getNativeLspBinDir();
1324
+ await fs.mkdir(binDir, { recursive: true });
1325
+ const shimPath = join13(binDir, input.binaryName);
1326
+ const platformCfgDir = process.platform === "darwin" ? "config_mac" : process.platform === "win32" ? "config_win" : "config_linux";
1327
+ const workspaceBase = join13(input.versionDir, "workspaces");
1328
+ const qLauncher = shQuote(input.launcherJarPath);
1329
+ const qConfig = shQuote(`${input.versionDir}/${platformCfgDir}`);
1330
+ const qWorkspaceBase = shQuote(workspaceBase);
1331
+ const unixScript = `#!/usr/bin/env bash
1332
+ # Generated by repowise listener \u2014 do not edit by hand.
1333
+ # Invokes Eclipse JDT.LS via the equinox launcher.
1334
+ set -euo pipefail
1335
+ WS_NAME="\${REPOWISE_JDTLS_WS_NAME:-default}"
1336
+ WS_DIR="${qWorkspaceBase}/\${WS_NAME}"
1337
+ mkdir -p "$WS_DIR"
1338
+ exec java \\
1339
+ -Declipse.application=org.eclipse.jdt.ls.core.id1 \\
1340
+ -Dosgi.bundles.defaultStartLevel=4 \\
1341
+ -Declipse.product=org.eclipse.jdt.ls.core.product \\
1342
+ -Dlog.level=ALL \\
1343
+ -Xms1g \\
1344
+ --add-modules=ALL-SYSTEM \\
1345
+ --add-opens java.base/java.util=ALL-UNNAMED \\
1346
+ --add-opens java.base/java.lang=ALL-UNNAMED \\
1347
+ -jar "${qLauncher}" \\
1348
+ -configuration "${qConfig}" \\
1349
+ -data "$WS_DIR" \\
1350
+ "$@"
1351
+ `;
1352
+ await fs.writeFile(shimPath, unixScript);
1353
+ await fs.chmod(shimPath, 493);
1354
+ if (process.platform === "win32") {
1355
+ const winShim = `${shimPath}.cmd`;
1356
+ const cqLauncher = cmdQuote(input.launcherJarPath);
1357
+ const cqConfig = cmdQuote(`${input.versionDir}\\${platformCfgDir}`);
1358
+ const cqWorkspaceBase = cmdQuote(workspaceBase);
1359
+ const winScript = `@echo off\r
1360
+ rem Generated by repowise listener\r
1361
+ if "%REPOWISE_JDTLS_WS_NAME%"=="" set REPOWISE_JDTLS_WS_NAME=default\r
1362
+ set WS_DIR=${cqWorkspaceBase}\\%REPOWISE_JDTLS_WS_NAME%\r
1363
+ if not exist "%WS_DIR%" mkdir "%WS_DIR%"\r
1364
+ java ^\r
1365
+ -Declipse.application=org.eclipse.jdt.ls.core.id1 ^\r
1366
+ -Dosgi.bundles.defaultStartLevel=4 ^\r
1367
+ -Declipse.product=org.eclipse.jdt.ls.core.product ^\r
1368
+ -Dlog.level=ALL ^\r
1369
+ -Xms1g ^\r
1370
+ --add-modules=ALL-SYSTEM ^\r
1371
+ --add-opens java.base/java.util=ALL-UNNAMED ^\r
1372
+ --add-opens java.base/java.lang=ALL-UNNAMED ^\r
1373
+ -jar "${cqLauncher}" ^\r
1374
+ -configuration "${cqConfig}" ^\r
1375
+ -data "%WS_DIR%" ^\r
1376
+ %*\r
1377
+ `;
1378
+ await fs.writeFile(winShim, winScript);
1379
+ }
1380
+ return shimPath;
1381
+ }
1382
+ async function ensureSymlinkInBin(lspKey, target, sourceAssetPath) {
1383
+ const binDir = getNativeLspBinDir();
1384
+ await fs.mkdir(binDir, { recursive: true });
1385
+ const winExtMatch = process.platform === "win32" && sourceAssetPath ? /\.(exe|bat|cmd|ps1)$/i.exec(sourceAssetPath) : null;
1386
+ const linkName = winExtMatch ? `${lspKey}${winExtMatch[0].toLowerCase()}` : lspKey;
1387
+ const linkPath = join13(binDir, linkName);
1388
+ try {
1389
+ await fs.unlink(linkPath);
1390
+ } catch {
1391
+ }
1392
+ try {
1393
+ await fs.symlink(target, linkPath);
1394
+ } catch (err) {
1395
+ if (err.code === "EPERM" || process.platform === "win32") {
1396
+ await fs.copyFile(target, linkPath);
1397
+ } else {
1398
+ throw err;
1399
+ }
1400
+ }
1401
+ }
1402
+ async function downloadFile(url, dest) {
1403
+ const res = await fetch(url);
1404
+ if (!res.ok || !res.body) {
1405
+ throw new Error(`download ${url} failed: HTTP ${res.status.toString()}`);
1406
+ }
1407
+ await fs.mkdir(dirname5(dest), { recursive: true });
1408
+ const sink = createWriteStream(dest);
1409
+ await pipeline(res.body, sink);
1410
+ }
1411
+ async function sha256File(path) {
1412
+ const hash = createHash3("sha256");
1413
+ const data = await fs.readFile(path);
1414
+ hash.update(data);
1415
+ return hash.digest("hex");
1416
+ }
1417
+ async function extractAsset(source, destDir, asset) {
1418
+ const filename = asset.filename.toLowerCase();
1419
+ if (filename.endsWith(".tar.gz") || filename.endsWith(".tgz")) {
1420
+ await runExternal("tar", ["-xzf", source, "-C", destDir]);
1421
+ return;
1422
+ }
1423
+ if (filename.endsWith(".zip")) {
1424
+ if (process.platform === "win32") {
1425
+ await runExternal("powershell.exe", [
1426
+ "-NoProfile",
1427
+ "-Command",
1428
+ `Expand-Archive -Path '${source}' -DestinationPath '${destDir}' -Force`
1429
+ ]);
1430
+ } else {
1431
+ await runExternal("unzip", ["-o", "-q", source, "-d", destDir]);
1432
+ }
1433
+ return;
1434
+ }
1435
+ if (filename.endsWith(".gz")) {
1436
+ const outPath = join13(destDir, asset.binaryPath);
1437
+ await fs.mkdir(dirname5(outPath), { recursive: true });
1438
+ await pipeline((await fs.open(source)).createReadStream(), createGunzip(), createWriteStream(outPath));
1439
+ return;
1440
+ }
1441
+ await fs.copyFile(source, join13(destDir, asset.binaryPath));
1434
1442
  }
1435
- async function probeJdk(options = {}) {
1436
- const timeoutMs = options.timeoutMs ?? 5e3;
1437
- return new Promise((resolve5) => {
1443
+ async function runExternal(cmd, args) {
1444
+ return new Promise((resolve5, reject) => {
1445
+ const child = spawn3(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
1438
1446
  let stderr = "";
1439
- let settled = false;
1440
- const child = spawn3("java", ["-version"], { stdio: ["ignore", "pipe", "pipe"] });
1441
- const timer = setTimeout(() => {
1442
- if (settled)
1443
- return;
1444
- settled = true;
1445
- try {
1446
- child.kill("SIGKILL");
1447
- } catch {
1448
- }
1449
- resolve5({ present: false });
1450
- }, timeoutMs);
1451
1447
  child.stderr.on("data", (chunk) => {
1452
- stderr += chunk.toString("utf8");
1453
- });
1454
- child.on("error", () => {
1455
- if (settled)
1456
- return;
1457
- settled = true;
1458
- clearTimeout(timer);
1459
- resolve5({ present: false });
1448
+ stderr += chunk.toString();
1460
1449
  });
1450
+ child.on("error", reject);
1461
1451
  child.on("close", (code) => {
1462
- if (settled)
1463
- return;
1464
- settled = true;
1465
- clearTimeout(timer);
1466
- if (code !== 0) {
1467
- resolve5({ present: false, rawOutput: stderr.trim() });
1468
- return;
1469
- }
1470
- const major = parseJavaMajorVersion(stderr);
1471
- if (major === void 0) {
1472
- resolve5({ present: true, rawOutput: stderr.trim() });
1473
- return;
1474
- }
1475
- resolve5({ present: true, majorVersion: major, rawOutput: stderr.trim() });
1452
+ if (code === 0)
1453
+ resolve5();
1454
+ else
1455
+ reject(new Error(`${cmd} ${args.join(" ")} exited ${(code ?? -1).toString()}: ${stderr.split("\n").slice(0, 3).join(" ")}`));
1476
1456
  });
1477
1457
  });
1478
1458
  }
1479
- var init_jdk_probe = __esm({
1480
- "../listener/dist/lsp/jdk-probe.js"() {
1459
+ async function pathExists(p) {
1460
+ try {
1461
+ await fs.access(p);
1462
+ return true;
1463
+ } catch {
1464
+ return false;
1465
+ }
1466
+ }
1467
+ var init_native_installer = __esm({
1468
+ "../listener/dist/lsp/native-installer.js"() {
1481
1469
  "use strict";
1470
+ init_src();
1471
+ init_installer();
1472
+ init_jdk_probe();
1482
1473
  }
1483
1474
  });
1484
1475
 
1485
- // ../listener/dist/lsp/native-installer.js
1486
- import { createWriteStream } from "fs";
1487
- import { promises as fs } from "fs";
1488
- import { createGunzip } from "zlib";
1476
+ // ../listener/dist/lsp/coursier-installer.js
1477
+ import { promises as fs2 } from "fs";
1489
1478
  import { spawn as spawn4 } from "child_process";
1490
- import { pipeline } from "stream/promises";
1491
- import { createHash as createHash3 } from "crypto";
1492
- import { join as join15, dirname as dirname5 } from "path";
1493
- function getNativeLspDir() {
1494
- return join15(getLspInstallDir(), "native");
1495
- }
1496
- function getNativeLspBinDir() {
1497
- return join15(getNativeLspDir(), "bin");
1479
+ import { join as join14 } from "path";
1480
+ function getCoursierBinDir() {
1481
+ return join14(getLspInstallDir(), "coursier-bin");
1498
1482
  }
1499
- async function ensureNativeLspInstalled(lspKey) {
1500
- const entry = NATIVE_LSP_VERSIONS[lspKey];
1501
- if (!entry)
1502
- return { installed: false, alreadyPresent: false, skipped: "no-native-version" };
1503
- const platformKey = getCurrentPlatformKey();
1504
- if (!platformKey) {
1505
- return { installed: false, alreadyPresent: false, skipped: "unsupported-platform" };
1483
+ async function ensureCoursierLspInstalled(key) {
1484
+ const entry = COURSIER_LSPS[key];
1485
+ if (!entry) {
1486
+ return { installed: false, alreadyPresent: false, skipped: "no-coursier-entry" };
1506
1487
  }
1507
- const asset = entry.assets[platformKey];
1508
- if (!asset) {
1509
- return { installed: false, alreadyPresent: false, skipped: "unsupported-platform" };
1488
+ const binDir = getCoursierBinDir();
1489
+ const binaryPath = join14(binDir, entry.appName);
1490
+ const binaryPathExe = join14(binDir, `${entry.appName}.bat`);
1491
+ if (await pathExists2(binaryPath) || await pathExists2(binaryPathExe)) {
1492
+ return { installed: false, alreadyPresent: true, binaryPath };
1510
1493
  }
1511
- if (entry.requiresJdk !== void 0) {
1512
- const jdk = await probeJdk();
1513
- if (!jdk.present || jdk.majorVersion === void 0 || jdk.majorVersion < entry.requiresJdk) {
1514
- return {
1515
- installed: false,
1516
- alreadyPresent: false,
1517
- skipped: "jvm-missing",
1518
- error: `${lspKey} requires JDK ${entry.requiresJdk.toString()}+ (found: ${jdk.present ? `Java ${jdk.majorVersion?.toString() ?? "?"}` : "no java on PATH"}). Install with: brew install openjdk@21 (macOS) or apt install openjdk-21-jdk (Linux)`
1519
- };
1520
- }
1494
+ const csBootstrap = await ensureNativeLspInstalled("coursier");
1495
+ if (csBootstrap.skipped || !csBootstrap.installed && !csBootstrap.alreadyPresent) {
1496
+ return {
1497
+ installed: false,
1498
+ alreadyPresent: false,
1499
+ skipped: "coursier-bootstrap-failed",
1500
+ error: csBootstrap.error ?? csBootstrap.skipped
1501
+ };
1521
1502
  }
1522
- const usesWrapper = Boolean(asset.launcherJarGlob && asset.wrapperBinaryName);
1523
- const versionDir = join15(getNativeLspDir(), lspKey, entry.version);
1524
- if (usesWrapper && asset.launcherJarGlob && asset.wrapperBinaryName) {
1525
- const jarPath = await resolveGlobInsideDir(versionDir, asset.launcherJarGlob);
1526
- const shimPath = join15(getNativeLspBinDir(), asset.wrapperBinaryName);
1527
- if (jarPath && await pathExists(shimPath)) {
1528
- return { installed: false, alreadyPresent: true, binaryPath: shimPath };
1529
- }
1530
- } else {
1531
- const installedBinary = join15(versionDir, asset.binaryPath);
1532
- if (await pathExists(installedBinary)) {
1533
- await ensureSymlinkInBin(lspKey, installedBinary, asset.binaryPath);
1534
- return { installed: false, alreadyPresent: true, binaryPath: installedBinary };
1535
- }
1503
+ const csPath = join14(getNativeLspBinDir(), "coursier");
1504
+ const csPathExe = join14(getNativeLspBinDir(), "coursier.exe");
1505
+ const cs = await pathExists2(csPath) ? csPath : await pathExists2(csPathExe) ? csPathExe : null;
1506
+ if (!cs) {
1507
+ return {
1508
+ installed: false,
1509
+ alreadyPresent: false,
1510
+ skipped: "coursier-bootstrap-failed",
1511
+ error: `coursier launcher not found at ${csPath} after bootstrap`
1512
+ };
1536
1513
  }
1537
1514
  try {
1538
- await fs.mkdir(versionDir, { recursive: true });
1539
- const tmpDownload = join15(versionDir, `.${asset.filename}.download`);
1540
- const downloadUrl = entry.source.kind === "github-release" ? `https://github.com/${entry.source.repo}/releases/download/${entry.version}/${asset.filename}` : asset.url ?? "";
1541
- if (!downloadUrl) {
1515
+ await fs2.mkdir(binDir, { recursive: true });
1516
+ await runCs(cs, ["install", "--install-dir", binDir, entry.appName]);
1517
+ if (!await pathExists2(binaryPath) && !await pathExists2(binaryPathExe)) {
1542
1518
  return {
1543
1519
  installed: false,
1544
1520
  alreadyPresent: false,
1545
- error: `direct-url asset for ${lspKey} missing required \`url\` field`
1521
+ error: `cs install ${entry.appName} completed but launcher missing at ${binaryPath}`
1546
1522
  };
1547
1523
  }
1548
- await downloadFile(downloadUrl, tmpDownload);
1549
- if (asset.sha256) {
1550
- const actual = await sha256File(tmpDownload);
1551
- if (actual !== asset.sha256) {
1552
- await fs.unlink(tmpDownload).catch(() => void 0);
1553
- return {
1554
- installed: false,
1555
- alreadyPresent: false,
1556
- error: `SHA256 mismatch for ${lspKey}: expected ${asset.sha256}, got ${actual}`
1557
- };
1558
- }
1559
- } else {
1560
- console.warn(`[lsp-install] WARN: SHA256 verification skipped for ${lspKey} (no pin in NATIVE_LSP_VERSIONS) \u2014 run apps/listener/scripts/pin-native-shas.ts to record one`);
1561
- }
1562
- await extractAsset(tmpDownload, versionDir, asset);
1563
- await fs.unlink(tmpDownload).catch(() => void 0);
1564
- if (usesWrapper && asset.launcherJarGlob && asset.wrapperBinaryName) {
1565
- const jarPath = await resolveGlobInsideDir(versionDir, asset.launcherJarGlob);
1566
- if (!jarPath) {
1567
- return {
1568
- installed: false,
1569
- alreadyPresent: false,
1570
- error: `extract completed but no JAR matched ${asset.launcherJarGlob} in ${versionDir}`
1571
- };
1572
- }
1573
- const shimPath = await writeJdtlsWrapper({
1574
- binaryName: asset.wrapperBinaryName,
1575
- versionDir,
1576
- launcherJarPath: jarPath
1577
- });
1578
- await pruneStaleVersions(lspKey, entry.version);
1579
- return { installed: true, alreadyPresent: false, binaryPath: shimPath };
1580
- }
1581
- const installedBinary = join15(versionDir, asset.binaryPath);
1582
- if (!await pathExists(installedBinary)) {
1583
- return {
1584
- installed: false,
1585
- alreadyPresent: false,
1586
- error: `extract completed but binary missing at ${installedBinary}`
1587
- };
1524
+ return { installed: true, alreadyPresent: false, binaryPath };
1525
+ } catch (err) {
1526
+ return {
1527
+ installed: false,
1528
+ alreadyPresent: false,
1529
+ error: err instanceof Error ? err.message : String(err)
1530
+ };
1531
+ }
1532
+ }
1533
+ async function runCs(cs, args) {
1534
+ return new Promise((resolve5, reject) => {
1535
+ const child = spawn4(cs, args, { stdio: ["ignore", "pipe", "pipe"] });
1536
+ let stderr = "";
1537
+ child.stderr.on("data", (chunk) => {
1538
+ stderr += chunk.toString();
1539
+ });
1540
+ child.on("error", reject);
1541
+ child.on("close", (code) => {
1542
+ if (code === 0)
1543
+ resolve5();
1544
+ else
1545
+ reject(new Error(`cs ${args.join(" ")} exited ${(code ?? -1).toString()}: ${stderr.split("\n").slice(0, 3).join(" ")}`));
1546
+ });
1547
+ });
1548
+ }
1549
+ async function pathExists2(p) {
1550
+ try {
1551
+ await fs2.access(p);
1552
+ return true;
1553
+ } catch {
1554
+ return false;
1555
+ }
1556
+ }
1557
+ var init_coursier_installer = __esm({
1558
+ "../listener/dist/lsp/coursier-installer.js"() {
1559
+ "use strict";
1560
+ init_src();
1561
+ init_installer();
1562
+ init_native_installer();
1563
+ }
1564
+ });
1565
+
1566
+ // ../listener/dist/lsp/toolchain-installer.js
1567
+ import { spawn as spawn5 } from "child_process";
1568
+ import { promises as fs3 } from "fs";
1569
+ import { homedir as homedir3 } from "os";
1570
+ import { join as join15 } from "path";
1571
+ async function ensureToolchainLspInstalled(key) {
1572
+ const entry = TOOLCHAIN_LSPS[key];
1573
+ if (!entry) {
1574
+ return { installed: false, alreadyPresent: false, skipped: "no-toolchain-entry" };
1575
+ }
1576
+ if (entry.bundled) {
1577
+ const found = await isCommandOnPath(entry.binaryName);
1578
+ if (found)
1579
+ return { installed: false, alreadyPresent: true };
1580
+ return { installed: false, alreadyPresent: false, skipped: "bundled-not-installable" };
1581
+ }
1582
+ if (await isCommandOnPath(entry.binaryName)) {
1583
+ return { installed: false, alreadyPresent: true, skipped: "already-on-path" };
1584
+ }
1585
+ if (!entry.toolchain) {
1586
+ return { installed: false, alreadyPresent: false, skipped: "toolchain-missing" };
1587
+ }
1588
+ if (!await isCommandOnPath(entry.toolchain)) {
1589
+ return {
1590
+ installed: false,
1591
+ alreadyPresent: false,
1592
+ skipped: "toolchain-missing",
1593
+ error: `${entry.toolchain} toolchain not on PATH`
1594
+ };
1595
+ }
1596
+ let binDir;
1597
+ try {
1598
+ binDir = await resolveToolchainBinDir(entry);
1599
+ } catch (err) {
1600
+ return {
1601
+ installed: false,
1602
+ alreadyPresent: false,
1603
+ error: err instanceof Error ? err.message : String(err)
1604
+ };
1605
+ }
1606
+ try {
1607
+ if (!entry.installArgs) {
1608
+ return { installed: false, alreadyPresent: false, skipped: "toolchain-missing" };
1588
1609
  }
1589
- await fs.chmod(installedBinary, 493).catch(() => void 0);
1590
- await ensureSymlinkInBin(lspKey, installedBinary, asset.binaryPath);
1591
- await pruneStaleVersions(lspKey, entry.version);
1592
- return { installed: true, alreadyPresent: false, binaryPath: installedBinary };
1610
+ await runToolchain(entry.toolchain, entry.installArgs);
1593
1611
  } catch (err) {
1594
1612
  return {
1595
1613
  installed: false,
@@ -1597,714 +1615,766 @@ async function ensureNativeLspInstalled(lspKey) {
1597
1615
  error: err instanceof Error ? err.message : String(err)
1598
1616
  };
1599
1617
  }
1618
+ const binaryPath = join15(binDir, entry.binaryName);
1619
+ if (!await pathExists3(binaryPath)) {
1620
+ return {
1621
+ installed: false,
1622
+ alreadyPresent: false,
1623
+ error: `${entry.toolchain} install of ${entry.binaryName} completed but binary missing at ${binaryPath}`,
1624
+ binDir
1625
+ };
1626
+ }
1627
+ return { installed: true, alreadyPresent: false, binaryPath, binDir };
1600
1628
  }
1601
- async function resolveGlobInsideDir(versionDir, glob) {
1602
- const parts = glob.split("/");
1603
- let dir = versionDir;
1604
- for (let i = 0; i < parts.length - 1; i += 1) {
1605
- dir = join15(dir, parts[i]);
1629
+ async function resolveToolchainBinDir(entry) {
1630
+ if (!entry.toolchain) {
1631
+ throw new Error("cannot resolve bin dir for entry without toolchain");
1606
1632
  }
1607
- const pattern = parts[parts.length - 1];
1608
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
1609
- const re = new RegExp(`^${escaped}$`);
1610
- let entries;
1611
- try {
1612
- entries = await fs.readdir(dir);
1613
- } catch {
1614
- return null;
1633
+ if (entry.toolchainBinDir) {
1634
+ if (entry.toolchain === "go") {
1635
+ const gopath = process.env["GOPATH"] ?? join15(homedir3(), "go");
1636
+ return join15(gopath, "bin");
1637
+ }
1638
+ return join15(homedir3(), entry.toolchainBinDir);
1615
1639
  }
1616
- const matches = entries.filter((e) => re.test(e)).sort();
1617
- const last = matches[matches.length - 1];
1618
- return last ? join15(dir, last) : null;
1619
- }
1620
- async function pruneStaleVersions(lspKey, currentVersion) {
1621
- const lspDir = join15(getNativeLspDir(), lspKey);
1622
- let entries;
1623
- try {
1624
- entries = await fs.readdir(lspDir);
1625
- } catch {
1626
- return;
1640
+ if (entry.toolchain === "gem") {
1641
+ const userdir = await captureStdout("gem", ["env", "userdir"]);
1642
+ return join15(userdir.trim(), "bin");
1627
1643
  }
1628
- await Promise.all(entries.filter((name) => name !== currentVersion).map(async (name) => {
1629
- try {
1630
- await fs.rm(join15(lspDir, name), { recursive: true, force: true });
1631
- } catch {
1644
+ throw new Error(`no bin-dir resolution for toolchain ${entry.toolchain}`);
1645
+ }
1646
+ function getToolchainBinDirs() {
1647
+ const dirs = /* @__PURE__ */ new Set();
1648
+ for (const entry of Object.values(TOOLCHAIN_LSPS)) {
1649
+ if (entry.bundled || !entry.toolchain || !entry.toolchainBinDir)
1650
+ continue;
1651
+ if (entry.toolchain === "go") {
1652
+ const gopath = process.env["GOPATH"] ?? join15(homedir3(), "go");
1653
+ dirs.add(join15(gopath, "bin"));
1654
+ } else {
1655
+ dirs.add(join15(homedir3(), entry.toolchainBinDir));
1632
1656
  }
1633
- }));
1657
+ }
1658
+ return [...dirs];
1634
1659
  }
1635
- function shQuote(value) {
1636
- return value.replace(/[\\"$`]/g, (ch) => `\\${ch}`);
1660
+ function runToolchain(cmd, args) {
1661
+ return new Promise((resolve5, reject) => {
1662
+ const child = spawn5(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
1663
+ let stderr = "";
1664
+ child.stderr.on("data", (chunk) => {
1665
+ stderr += chunk.toString();
1666
+ });
1667
+ child.on("error", reject);
1668
+ child.on("close", (code) => {
1669
+ if (code === 0)
1670
+ resolve5();
1671
+ else
1672
+ reject(new Error(`${cmd} ${args.join(" ")} exited ${(code ?? -1).toString()}: ${stderr.split("\n").slice(0, 3).join(" ")}`));
1673
+ });
1674
+ });
1637
1675
  }
1638
- function cmdQuote(value) {
1639
- return value.replace(/"/g, '""').replace(/%/g, "%%");
1676
+ function captureStdout(cmd, args) {
1677
+ return new Promise((resolve5, reject) => {
1678
+ const child = spawn5(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
1679
+ let stdout = "";
1680
+ let stderr = "";
1681
+ child.stdout.on("data", (chunk) => {
1682
+ stdout += chunk.toString();
1683
+ });
1684
+ child.stderr.on("data", (chunk) => {
1685
+ stderr += chunk.toString();
1686
+ });
1687
+ child.on("error", reject);
1688
+ child.on("close", (code) => {
1689
+ if (code === 0)
1690
+ resolve5(stdout);
1691
+ else
1692
+ reject(new Error(`${cmd} ${args.join(" ")} exited ${(code ?? -1).toString()}: ${stderr}`));
1693
+ });
1694
+ });
1640
1695
  }
1641
- async function writeJdtlsWrapper(input) {
1642
- const binDir = getNativeLspBinDir();
1643
- await fs.mkdir(binDir, { recursive: true });
1644
- const shimPath = join15(binDir, input.binaryName);
1645
- const platformCfgDir = process.platform === "darwin" ? "config_mac" : process.platform === "win32" ? "config_win" : "config_linux";
1646
- const workspaceBase = join15(input.versionDir, "workspaces");
1647
- const qLauncher = shQuote(input.launcherJarPath);
1648
- const qConfig = shQuote(`${input.versionDir}/${platformCfgDir}`);
1649
- const qWorkspaceBase = shQuote(workspaceBase);
1650
- const unixScript = `#!/usr/bin/env bash
1651
- # Generated by repowise listener \u2014 do not edit by hand.
1652
- # Invokes Eclipse JDT.LS via the equinox launcher.
1653
- set -euo pipefail
1654
- WS_NAME="\${REPOWISE_JDTLS_WS_NAME:-default}"
1655
- WS_DIR="${qWorkspaceBase}/\${WS_NAME}"
1656
- mkdir -p "$WS_DIR"
1657
- exec java \\
1658
- -Declipse.application=org.eclipse.jdt.ls.core.id1 \\
1659
- -Dosgi.bundles.defaultStartLevel=4 \\
1660
- -Declipse.product=org.eclipse.jdt.ls.core.product \\
1661
- -Dlog.level=ALL \\
1662
- -Xms1g \\
1663
- --add-modules=ALL-SYSTEM \\
1664
- --add-opens java.base/java.util=ALL-UNNAMED \\
1665
- --add-opens java.base/java.lang=ALL-UNNAMED \\
1666
- -jar "${qLauncher}" \\
1667
- -configuration "${qConfig}" \\
1668
- -data "$WS_DIR" \\
1669
- "$@"
1670
- `;
1671
- await fs.writeFile(shimPath, unixScript);
1672
- await fs.chmod(shimPath, 493);
1673
- if (process.platform === "win32") {
1674
- const winShim = `${shimPath}.cmd`;
1675
- const cqLauncher = cmdQuote(input.launcherJarPath);
1676
- const cqConfig = cmdQuote(`${input.versionDir}\\${platformCfgDir}`);
1677
- const cqWorkspaceBase = cmdQuote(workspaceBase);
1678
- const winScript = `@echo off\r
1679
- rem Generated by repowise listener\r
1680
- if "%REPOWISE_JDTLS_WS_NAME%"=="" set REPOWISE_JDTLS_WS_NAME=default\r
1681
- set WS_DIR=${cqWorkspaceBase}\\%REPOWISE_JDTLS_WS_NAME%\r
1682
- if not exist "%WS_DIR%" mkdir "%WS_DIR%"\r
1683
- java ^\r
1684
- -Declipse.application=org.eclipse.jdt.ls.core.id1 ^\r
1685
- -Dosgi.bundles.defaultStartLevel=4 ^\r
1686
- -Declipse.product=org.eclipse.jdt.ls.core.product ^\r
1687
- -Dlog.level=ALL ^\r
1688
- -Xms1g ^\r
1689
- --add-modules=ALL-SYSTEM ^\r
1690
- --add-opens java.base/java.util=ALL-UNNAMED ^\r
1691
- --add-opens java.base/java.lang=ALL-UNNAMED ^\r
1692
- -jar "${cqLauncher}" ^\r
1693
- -configuration "${cqConfig}" ^\r
1694
- -data "%WS_DIR%" ^\r
1695
- %*\r
1696
- `;
1697
- await fs.writeFile(winShim, winScript);
1698
- }
1699
- return shimPath;
1696
+ async function isCommandOnPath(command) {
1697
+ if (/[^\w./+-]/.test(command))
1698
+ return false;
1699
+ const probeCmd = process.platform === "win32" ? "where" : "which";
1700
+ return new Promise((resolve5) => {
1701
+ const child = spawn5(probeCmd, [command], { stdio: "ignore", timeout: 5e3 });
1702
+ child.on("error", () => resolve5(false));
1703
+ child.on("close", (code) => resolve5(code === 0));
1704
+ });
1700
1705
  }
1701
- async function ensureSymlinkInBin(lspKey, target, sourceAssetPath) {
1702
- const binDir = getNativeLspBinDir();
1703
- await fs.mkdir(binDir, { recursive: true });
1704
- const winExtMatch = process.platform === "win32" && sourceAssetPath ? /\.(exe|bat|cmd|ps1)$/i.exec(sourceAssetPath) : null;
1705
- const linkName = winExtMatch ? `${lspKey}${winExtMatch[0].toLowerCase()}` : lspKey;
1706
- const linkPath = join15(binDir, linkName);
1706
+ async function pathExists3(p) {
1707
1707
  try {
1708
- await fs.unlink(linkPath);
1708
+ await fs3.access(p);
1709
+ return true;
1709
1710
  } catch {
1710
- }
1711
- try {
1712
- await fs.symlink(target, linkPath);
1713
- } catch (err) {
1714
- if (err.code === "EPERM" || process.platform === "win32") {
1715
- await fs.copyFile(target, linkPath);
1716
- } else {
1717
- throw err;
1718
- }
1711
+ return false;
1719
1712
  }
1720
1713
  }
1721
- async function downloadFile(url, dest) {
1722
- const res = await fetch(url);
1723
- if (!res.ok || !res.body) {
1724
- throw new Error(`download ${url} failed: HTTP ${res.status.toString()}`);
1714
+ var init_toolchain_installer = __esm({
1715
+ "../listener/dist/lsp/toolchain-installer.js"() {
1716
+ "use strict";
1717
+ init_src();
1725
1718
  }
1726
- await fs.mkdir(dirname5(dest), { recursive: true });
1727
- const sink = createWriteStream(dest);
1728
- await pipeline(res.body, sink);
1719
+ });
1720
+
1721
+ // ../listener/dist/lsp/installer.js
1722
+ import { promises as fs4 } from "fs";
1723
+ import { join as join16, dirname as dirname6 } from "path";
1724
+ import { spawn as spawn6 } from "child_process";
1725
+ import lockfile3 from "proper-lockfile";
1726
+ function getLspInstallDir() {
1727
+ return join16(getConfigDir(), "lsp-servers");
1729
1728
  }
1730
- async function sha256File(path) {
1731
- const hash = createHash3("sha256");
1732
- const data = await fs.readFile(path);
1733
- hash.update(data);
1734
- return hash.digest("hex");
1729
+ function getLspBinDir() {
1730
+ return join16(getLspInstallDir(), "node_modules", ".bin");
1735
1731
  }
1736
- async function extractAsset(source, destDir, asset) {
1737
- const filename = asset.filename.toLowerCase();
1738
- if (filename.endsWith(".tar.gz") || filename.endsWith(".tgz")) {
1739
- await runExternal("tar", ["-xzf", source, "-C", destDir]);
1740
- return;
1732
+ async function ensureNpmLspInstalled(config2) {
1733
+ if (!config2.npmPackage)
1734
+ return { installed: false, skipped: "no-npm-package" };
1735
+ const installDir = getLspInstallDir();
1736
+ const binPath = join16(installDir, "node_modules", ".bin", config2.command);
1737
+ if (await pathExists4(binPath)) {
1738
+ return { installed: false, skipped: "already-present" };
1739
+ }
1740
+ try {
1741
+ await fs4.mkdir(installDir, { recursive: true });
1742
+ let release = null;
1743
+ try {
1744
+ release = await lockfile3.lock(installDir, {
1745
+ stale: 18e4,
1746
+ retries: { retries: 20, factor: 1.5, minTimeout: 500, maxTimeout: 5e3 },
1747
+ realpath: false
1748
+ });
1749
+ } catch {
1750
+ if (await pathExists4(binPath)) {
1751
+ return { installed: false, skipped: "already-present" };
1752
+ }
1753
+ }
1754
+ try {
1755
+ if (await pathExists4(binPath)) {
1756
+ return { installed: false, skipped: "already-present" };
1757
+ }
1758
+ const pkgJsonPath = join16(installDir, "package.json");
1759
+ if (!await pathExists4(pkgJsonPath)) {
1760
+ await fs4.writeFile(pkgJsonPath, JSON.stringify({ name: "repowise-lsp-servers", private: true, version: "0.0.0" }, null, 2), "utf-8");
1761
+ }
1762
+ await runNpmInstall(installDir, config2.npmPackage);
1763
+ if (!await pathExists4(binPath)) {
1764
+ return {
1765
+ installed: false,
1766
+ error: `npm install completed but ${config2.command} still not at ${binPath}`
1767
+ };
1768
+ }
1769
+ return { installed: true };
1770
+ } finally {
1771
+ if (release) {
1772
+ try {
1773
+ await release();
1774
+ } catch {
1775
+ }
1776
+ }
1777
+ }
1778
+ } catch (err) {
1779
+ return {
1780
+ installed: false,
1781
+ error: err instanceof Error ? err.message : String(err)
1782
+ };
1741
1783
  }
1742
- if (filename.endsWith(".zip")) {
1743
- if (process.platform === "win32") {
1744
- await runExternal("powershell.exe", [
1745
- "-NoProfile",
1746
- "-Command",
1747
- `Expand-Archive -Path '${source}' -DestinationPath '${destDir}' -Force`
1748
- ]);
1749
- } else {
1750
- await runExternal("unzip", ["-o", "-q", source, "-d", destDir]);
1784
+ }
1785
+ async function resolveNpmCommand() {
1786
+ const nodeDir = dirname6(process.execPath);
1787
+ for (const candidate of [join16(nodeDir, "npm"), join16(nodeDir, "npm.cmd")]) {
1788
+ try {
1789
+ await fs4.access(candidate);
1790
+ return candidate;
1791
+ } catch {
1751
1792
  }
1752
- return;
1753
1793
  }
1754
- if (filename.endsWith(".gz")) {
1755
- const outPath = join15(destDir, asset.binaryPath);
1756
- await fs.mkdir(dirname5(outPath), { recursive: true });
1757
- await pipeline((await fs.open(source)).createReadStream(), createGunzip(), createWriteStream(outPath));
1758
- return;
1794
+ for (const candidate of ["/opt/homebrew/bin/npm", "/usr/local/bin/npm", "/usr/bin/npm"]) {
1795
+ try {
1796
+ await fs4.access(candidate);
1797
+ return candidate;
1798
+ } catch {
1799
+ }
1759
1800
  }
1760
- await fs.copyFile(source, join15(destDir, asset.binaryPath));
1801
+ return "npm";
1761
1802
  }
1762
- async function runExternal(cmd, args) {
1803
+ async function runNpmInstall(cwd, pkg2) {
1804
+ const npmCmd = await resolveNpmCommand();
1805
+ const nodeDir = dirname6(process.execPath);
1806
+ const augmentedPath = process.env.PATH ? `${nodeDir}:${process.env.PATH}` : nodeDir;
1763
1807
  return new Promise((resolve5, reject) => {
1764
- const child = spawn4(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
1808
+ const child = spawn6(npmCmd, ["install", "--no-audit", "--no-fund", "--silent", pkg2], {
1809
+ cwd,
1810
+ stdio: ["ignore", "pipe", "pipe"],
1811
+ env: { ...process.env, PATH: augmentedPath }
1812
+ });
1765
1813
  let stderr = "";
1766
1814
  child.stderr.on("data", (chunk) => {
1767
1815
  stderr += chunk.toString();
1768
1816
  });
1769
- child.on("error", reject);
1817
+ child.on("error", (err) => reject(err));
1770
1818
  child.on("close", (code) => {
1771
- if (code === 0)
1819
+ if (code === 0) {
1772
1820
  resolve5();
1773
- else
1774
- reject(new Error(`${cmd} ${args.join(" ")} exited ${(code ?? -1).toString()}: ${stderr.split("\n").slice(0, 3).join(" ")}`));
1821
+ } else {
1822
+ reject(new Error(`npm install ${pkg2} exited ${(code ?? -1).toString()}: ${sanitizeNpmStderr(stderr)}`));
1823
+ }
1775
1824
  });
1776
1825
  });
1777
1826
  }
1778
- async function pathExists(p) {
1827
+ function sanitizeNpmStderr(raw) {
1828
+ const truncated = raw.split("\n").slice(0, 3).join(" ").trim();
1829
+ return truncated.replace(/(https?:\/\/)[^@\s/]*@/g, (_match, scheme) => `${scheme}<redacted>@`);
1830
+ }
1831
+ async function pathExists4(p) {
1779
1832
  try {
1780
- await fs.access(p);
1833
+ await fs4.access(p);
1781
1834
  return true;
1782
1835
  } catch {
1783
1836
  return false;
1784
1837
  }
1785
1838
  }
1786
- var init_native_installer = __esm({
1787
- "../listener/dist/lsp/native-installer.js"() {
1788
- "use strict";
1789
- init_src();
1790
- init_installer();
1791
- init_jdk_probe();
1792
- }
1793
- });
1794
-
1795
- // ../listener/dist/lsp/coursier-installer.js
1796
- import { promises as fs2 } from "fs";
1797
- import { spawn as spawn5 } from "child_process";
1798
- import { join as join16 } from "path";
1799
- function getCoursierBinDir() {
1800
- return join16(getLspInstallDir(), "coursier-bin");
1801
- }
1802
- async function ensureCoursierLspInstalled(key) {
1803
- const entry = COURSIER_LSPS[key];
1804
- if (!entry) {
1805
- return { installed: false, alreadyPresent: false, skipped: "no-coursier-entry" };
1806
- }
1807
- const binDir = getCoursierBinDir();
1808
- const binaryPath = join16(binDir, entry.appName);
1809
- const binaryPathExe = join16(binDir, `${entry.appName}.bat`);
1810
- if (await pathExists2(binaryPath) || await pathExists2(binaryPathExe)) {
1811
- return { installed: false, alreadyPresent: true, binaryPath };
1812
- }
1813
- const csBootstrap = await ensureNativeLspInstalled("coursier");
1814
- if (csBootstrap.skipped || !csBootstrap.installed && !csBootstrap.alreadyPresent) {
1815
- return {
1816
- installed: false,
1817
- alreadyPresent: false,
1818
- skipped: "coursier-bootstrap-failed",
1819
- error: csBootstrap.error ?? csBootstrap.skipped
1820
- };
1821
- }
1822
- const csPath = join16(getNativeLspBinDir(), "coursier");
1823
- const csPathExe = join16(getNativeLspBinDir(), "coursier.exe");
1824
- const cs = await pathExists2(csPath) ? csPath : await pathExists2(csPathExe) ? csPathExe : null;
1825
- if (!cs) {
1826
- return {
1827
- installed: false,
1828
- alreadyPresent: false,
1829
- skipped: "coursier-bootstrap-failed",
1830
- error: `coursier launcher not found at ${csPath} after bootstrap`
1831
- };
1839
+ async function detectRepoLanguages(repoRoot) {
1840
+ const found = /* @__PURE__ */ new Set();
1841
+ let inspected = 0;
1842
+ const MAX_INSPECT = 200;
1843
+ async function inspect(dir) {
1844
+ if (inspected >= MAX_INSPECT)
1845
+ return;
1846
+ let entries;
1847
+ try {
1848
+ entries = await fs4.readdir(dir);
1849
+ } catch {
1850
+ return;
1851
+ }
1852
+ for (const name of entries) {
1853
+ if (inspected >= MAX_INSPECT)
1854
+ return;
1855
+ if (name.startsWith("."))
1856
+ continue;
1857
+ if (name === "node_modules" || name === "dist" || name === "build")
1858
+ continue;
1859
+ const lang = detectLanguage(name);
1860
+ if (lang) {
1861
+ found.add(lang);
1862
+ inspected += 1;
1863
+ }
1864
+ }
1832
1865
  }
1866
+ await inspect(repoRoot);
1867
+ let topEntries = [];
1833
1868
  try {
1834
- await fs2.mkdir(binDir, { recursive: true });
1835
- await runCs(cs, ["install", "--install-dir", binDir, entry.appName]);
1836
- if (!await pathExists2(binaryPath) && !await pathExists2(binaryPathExe)) {
1837
- return {
1838
- installed: false,
1839
- alreadyPresent: false,
1840
- error: `cs install ${entry.appName} completed but launcher missing at ${binaryPath}`
1841
- };
1869
+ topEntries = await fs4.readdir(repoRoot);
1870
+ } catch {
1871
+ return found;
1872
+ }
1873
+ for (const name of topEntries) {
1874
+ if (name.startsWith("."))
1875
+ continue;
1876
+ if (name === "node_modules" || name === "dist" || name === "build")
1877
+ continue;
1878
+ const sub = join16(repoRoot, name);
1879
+ try {
1880
+ const stat8 = await fs4.stat(sub);
1881
+ if (stat8.isDirectory())
1882
+ await inspect(sub);
1883
+ } catch {
1842
1884
  }
1843
- return { installed: true, alreadyPresent: false, binaryPath };
1844
- } catch (err) {
1845
- return {
1846
- installed: false,
1847
- alreadyPresent: false,
1848
- error: err instanceof Error ? err.message : String(err)
1849
- };
1850
1885
  }
1886
+ return found;
1851
1887
  }
1852
- async function runCs(cs, args) {
1853
- return new Promise((resolve5, reject) => {
1854
- const child = spawn5(cs, args, { stdio: ["ignore", "pipe", "pipe"] });
1855
- let stderr = "";
1856
- child.stderr.on("data", (chunk) => {
1857
- stderr += chunk.toString();
1858
- });
1859
- child.on("error", reject);
1860
- child.on("close", (code) => {
1861
- if (code === 0)
1862
- resolve5();
1863
- else
1864
- reject(new Error(`cs ${args.join(" ")} exited ${(code ?? -1).toString()}: ${stderr.split("\n").slice(0, 3).join(" ")}`));
1888
+ async function prepareLspServersForRepos(repos, options = {}) {
1889
+ const detected = /* @__PURE__ */ new Set();
1890
+ for (const r of repos) {
1891
+ if (!r.localPath)
1892
+ continue;
1893
+ try {
1894
+ const stat8 = await fs4.stat(r.localPath);
1895
+ if (!stat8.isDirectory())
1896
+ continue;
1897
+ } catch {
1898
+ continue;
1899
+ }
1900
+ try {
1901
+ const langs = await detectRepoLanguages(r.localPath);
1902
+ for (const l of langs)
1903
+ detected.add(l);
1904
+ } catch {
1905
+ }
1906
+ }
1907
+ const results = [];
1908
+ for (const language of detected) {
1909
+ const configs = getEffectiveConfigList(language, process.env, options.lspOverrides);
1910
+ const npmConfig = configs.find((c) => c.npmPackage);
1911
+ if (npmConfig) {
1912
+ const outcome = await ensureNpmLspInstalled(npmConfig);
1913
+ results.push({
1914
+ language,
1915
+ installed: outcome.installed,
1916
+ alreadyPresent: outcome.skipped === "already-present",
1917
+ skippedNoNpmPackage: false,
1918
+ installMethod: "npm",
1919
+ error: outcome.error
1920
+ });
1921
+ continue;
1922
+ }
1923
+ const nativeConfig = configs.find((c) => c.nativeInstallKey);
1924
+ if (nativeConfig?.nativeInstallKey) {
1925
+ const outcome = await ensureNativeLspInstalled(nativeConfig.nativeInstallKey);
1926
+ results.push({
1927
+ language,
1928
+ installed: outcome.installed,
1929
+ alreadyPresent: outcome.alreadyPresent,
1930
+ skippedNoNpmPackage: false,
1931
+ installMethod: "native",
1932
+ skipped: outcome.skipped,
1933
+ hint: outcome.skipped ? nativeConfig.installHint : void 0,
1934
+ error: outcome.error
1935
+ });
1936
+ continue;
1937
+ }
1938
+ const coursierConfig = configs.find((c) => c.coursierInstallKey);
1939
+ if (coursierConfig?.coursierInstallKey) {
1940
+ const outcome = await ensureCoursierLspInstalled(coursierConfig.coursierInstallKey);
1941
+ results.push({
1942
+ language,
1943
+ installed: outcome.installed,
1944
+ alreadyPresent: outcome.alreadyPresent,
1945
+ skippedNoNpmPackage: false,
1946
+ installMethod: "coursier",
1947
+ skipped: outcome.skipped,
1948
+ hint: outcome.skipped ? coursierConfig.installHint : void 0,
1949
+ error: outcome.error
1950
+ });
1951
+ continue;
1952
+ }
1953
+ const toolchainConfig = configs.find((c) => c.toolchainInstallKey);
1954
+ if (toolchainConfig?.toolchainInstallKey) {
1955
+ const outcome = await ensureToolchainLspInstalled(toolchainConfig.toolchainInstallKey);
1956
+ results.push({
1957
+ language,
1958
+ installed: outcome.installed,
1959
+ alreadyPresent: outcome.alreadyPresent,
1960
+ skippedNoNpmPackage: false,
1961
+ installMethod: "toolchain",
1962
+ skipped: outcome.skipped,
1963
+ // Surface the install hint for any skip reason — toolchain
1964
+ // missing means user needs to install it; bundled SDK means
1965
+ // user needs the SDK.
1966
+ hint: outcome.skipped ? toolchainConfig.installHint : void 0,
1967
+ error: outcome.error
1968
+ });
1969
+ continue;
1970
+ }
1971
+ results.push({
1972
+ language,
1973
+ installed: false,
1974
+ alreadyPresent: false,
1975
+ skippedNoNpmPackage: true,
1976
+ hint: configs[0]?.installHint
1865
1977
  });
1866
- });
1867
- }
1868
- async function pathExists2(p) {
1869
- try {
1870
- await fs2.access(p);
1871
- return true;
1872
- } catch {
1873
- return false;
1874
1978
  }
1979
+ return results;
1875
1980
  }
1876
- var init_coursier_installer = __esm({
1877
- "../listener/dist/lsp/coursier-installer.js"() {
1981
+ var init_installer = __esm({
1982
+ "../listener/dist/lsp/installer.js"() {
1878
1983
  "use strict";
1879
- init_src();
1880
- init_installer();
1984
+ init_config_dir();
1985
+ init_registry();
1881
1986
  init_native_installer();
1987
+ init_coursier_installer();
1988
+ init_toolchain_installer();
1882
1989
  }
1883
1990
  });
1884
1991
 
1885
- // ../listener/dist/lsp/toolchain-installer.js
1886
- import { spawn as spawn6 } from "child_process";
1887
- import { promises as fs3 } from "fs";
1992
+ // ../listener/dist/lsp/daemon-path.js
1888
1993
  import { homedir as homedir4 } from "os";
1889
- import { join as join17 } from "path";
1890
- async function ensureToolchainLspInstalled(key) {
1891
- const entry = TOOLCHAIN_LSPS[key];
1892
- if (!entry) {
1893
- return { installed: false, alreadyPresent: false, skipped: "no-toolchain-entry" };
1894
- }
1895
- if (entry.bundled) {
1896
- const found = await isCommandOnPath(entry.binaryName);
1897
- if (found)
1898
- return { installed: false, alreadyPresent: true };
1899
- return { installed: false, alreadyPresent: false, skipped: "bundled-not-installable" };
1900
- }
1901
- if (await isCommandOnPath(entry.binaryName)) {
1902
- return { installed: false, alreadyPresent: true, skipped: "already-on-path" };
1903
- }
1904
- if (!entry.toolchain) {
1905
- return { installed: false, alreadyPresent: false, skipped: "toolchain-missing" };
1906
- }
1907
- if (!await isCommandOnPath(entry.toolchain)) {
1908
- return {
1909
- installed: false,
1910
- alreadyPresent: false,
1911
- skipped: "toolchain-missing",
1912
- error: `${entry.toolchain} toolchain not on PATH`
1913
- };
1914
- }
1915
- let binDir;
1916
- try {
1917
- binDir = await resolveToolchainBinDir(entry);
1918
- } catch (err) {
1919
- return {
1920
- installed: false,
1921
- alreadyPresent: false,
1922
- error: err instanceof Error ? err.message : String(err)
1923
- };
1994
+ import { delimiter, dirname as dirname7, join as join17 } from "path";
1995
+ function commonBinDirs() {
1996
+ const home = homedir4();
1997
+ return [
1998
+ "/opt/homebrew/bin",
1999
+ // Apple Silicon Homebrew
2000
+ "/opt/homebrew/sbin",
2001
+ "/usr/local/bin",
2002
+ // Intel Homebrew / common installs
2003
+ "/usr/bin",
2004
+ "/bin",
2005
+ "/usr/sbin",
2006
+ "/sbin",
2007
+ join17(home, ".local", "bin"),
2008
+ // pipx / pip --user / uv
2009
+ join17(home, ".npm-global", "bin"),
2010
+ // common npm global prefix
2011
+ join17(home, "go", "bin"),
2012
+ // gopls
2013
+ join17(home, ".cargo", "bin")
2014
+ // rust-analyzer via cargo
2015
+ ];
2016
+ }
2017
+ function managedBinDirs() {
2018
+ return [getLspBinDir(), getNativeLspBinDir(), getCoursierBinDir(), ...getToolchainBinDirs()];
2019
+ }
2020
+ function dedup(dirs) {
2021
+ return [...new Set(dirs.filter((d) => d.length > 0))];
2022
+ }
2023
+ function daemonPathDirs() {
2024
+ return dedup([dirname7(process.execPath), ...commonBinDirs(), ...managedBinDirs()]);
2025
+ }
2026
+ function buildDaemonPath() {
2027
+ return daemonPathDirs().join(delimiter);
2028
+ }
2029
+ var init_daemon_path = __esm({
2030
+ "../listener/dist/lsp/daemon-path.js"() {
2031
+ "use strict";
2032
+ init_installer();
2033
+ init_native_installer();
2034
+ init_coursier_installer();
2035
+ init_toolchain_installer();
1924
2036
  }
2037
+ });
2038
+
2039
+ // ../listener/dist/service-installer.js
2040
+ var service_installer_exports = {};
2041
+ __export(service_installer_exports, {
2042
+ install: () => install,
2043
+ isInstalled: () => isInstalled,
2044
+ isServiceRunning: () => isServiceRunning,
2045
+ stopService: () => stopService,
2046
+ uninstall: () => uninstall
2047
+ });
2048
+ import { execFile as execFile4 } from "child_process";
2049
+ import { writeFile as writeFile8, mkdir as mkdir8, unlink as unlink6 } from "fs/promises";
2050
+ import { homedir as homedir5 } from "os";
2051
+ import { join as join18 } from "path";
2052
+ function exec(cmd, args) {
2053
+ return new Promise((resolve5, reject) => {
2054
+ execFile4(cmd, args, (err, stdout) => {
2055
+ if (err) {
2056
+ reject(err);
2057
+ return;
2058
+ }
2059
+ resolve5(String(stdout ?? ""));
2060
+ });
2061
+ });
2062
+ }
2063
+ function xmlEscape(value) {
2064
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
2065
+ }
2066
+ function sanitizeEnvValue(value) {
2067
+ return Array.from(value).filter((ch) => ch.charCodeAt(0) >= 32).join("");
2068
+ }
2069
+ function plistPath() {
2070
+ return join18(homedir5(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
2071
+ }
2072
+ function logDir() {
2073
+ return join18(getConfigDir(), "logs");
2074
+ }
2075
+ function buildPlist() {
2076
+ const cmd = resolveListenerCommand();
2077
+ const logs = logDir();
2078
+ const programArgs = [process.execPath, cmd.script, ...cmd.args].map((a) => ` <string>${xmlEscape(a)}</string>`).join("\n");
2079
+ return `<?xml version="1.0" encoding="UTF-8"?>
2080
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
2081
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2082
+ <plist version="1.0">
2083
+ <dict>
2084
+ <key>Label</key>
2085
+ <string>${xmlEscape(PLIST_LABEL)}</string>
2086
+ <key>ProgramArguments</key>
2087
+ <array>
2088
+ ${programArgs}
2089
+ </array>
2090
+ <key>RunAtLoad</key>
2091
+ <true/>
2092
+ <key>KeepAlive</key>
2093
+ <true/>
2094
+ <!--
2095
+ FIX-8: launchd inherits a minimal PATH (/usr/bin:/bin:\u2026) that excludes
2096
+ /opt/homebrew/bin + the npm-global bin, so user-installed LSP servers
2097
+ (pyright, rust-analyzer, \u2026) cannot be spawned and the typed-resolution
2098
+ which-probe fails. Inject a rich PATH (single source of truth:
2099
+ buildDaemonPath in lsp/daemon-path.ts) so the daemon resolves them.
2100
+ -->
2101
+ <key>EnvironmentVariables</key>
2102
+ <dict>
2103
+ <key>PATH</key>
2104
+ <string>${xmlEscape(buildDaemonPath())}</string>
2105
+ </dict>
2106
+ <key>StandardOutPath</key>
2107
+ <string>${xmlEscape(join18(logs, "listener-stdout.log"))}</string>
2108
+ <key>StandardErrorPath</key>
2109
+ <string>${xmlEscape(join18(logs, "listener-stderr.log"))}</string>
2110
+ <key>ProcessType</key>
2111
+ <string>Background</string>
2112
+ <!--
2113
+ SoftResourceLimits: raise the open-file descriptor limit from the macOS
2114
+ default of 256 to 4096. When multiple LSP child processes (rust-analyzer,
2115
+ jdtls, etc.) share the listener's fd budget they can exhaust the default
2116
+ very quickly, causing EMFILE errors. 4096 matches the system-wide default
2117
+ set by /etc/launchd.conf on modern macOS and gives enough headroom for
2118
+ ~10 concurrent LSP servers each opening ~300 fds.
2119
+
2120
+ MemoryLimit: intentionally NOT set here. The launchd MemoryLimit key
2121
+ (available since macOS 12.3) terminates the entire LaunchAgent when RSS
2122
+ exceeds the threshold \u2014 it cannot be scoped to individual child processes.
2123
+ Killing the listener to evict a memory-hungry rust-analyzer instance is
2124
+ undesirable. Per-child memory pressure is better handled by the LSP
2125
+ process manager (process-manager.ts) or by OS-level memory pressure
2126
+ signals (SIGTERM on macOS, OOM killer on Linux).
2127
+ -->
2128
+ <key>SoftResourceLimits</key>
2129
+ <dict>
2130
+ <key>NumberOfFiles</key>
2131
+ <integer>4096</integer>
2132
+ </dict>
2133
+ </dict>
2134
+ </plist>`;
2135
+ }
2136
+ async function darwinInstall() {
2137
+ await mkdir8(logDir(), { recursive: true });
2138
+ await mkdir8(join18(homedir5(), "Library", "LaunchAgents"), { recursive: true });
1925
2139
  try {
1926
- if (!entry.installArgs) {
1927
- return { installed: false, alreadyPresent: false, skipped: "toolchain-missing" };
1928
- }
1929
- await runToolchain(entry.toolchain, entry.installArgs);
1930
- } catch (err) {
1931
- return {
1932
- installed: false,
1933
- alreadyPresent: false,
1934
- error: err instanceof Error ? err.message : String(err)
1935
- };
1936
- }
1937
- const binaryPath = join17(binDir, entry.binaryName);
1938
- if (!await pathExists3(binaryPath)) {
1939
- return {
1940
- installed: false,
1941
- alreadyPresent: false,
1942
- error: `${entry.toolchain} install of ${entry.binaryName} completed but binary missing at ${binaryPath}`,
1943
- binDir
1944
- };
2140
+ await exec("launchctl", ["unload", plistPath()]);
2141
+ } catch {
1945
2142
  }
1946
- return { installed: true, alreadyPresent: false, binaryPath, binDir };
2143
+ await writeFile8(plistPath(), buildPlist());
2144
+ await exec("launchctl", ["load", plistPath()]);
1947
2145
  }
1948
- async function resolveToolchainBinDir(entry) {
1949
- if (!entry.toolchain) {
1950
- throw new Error("cannot resolve bin dir for entry without toolchain");
1951
- }
1952
- if (entry.toolchainBinDir) {
1953
- if (entry.toolchain === "go") {
1954
- const gopath = process.env["GOPATH"] ?? join17(homedir4(), "go");
1955
- return join17(gopath, "bin");
1956
- }
1957
- return join17(homedir4(), entry.toolchainBinDir);
2146
+ async function darwinUninstall() {
2147
+ try {
2148
+ await exec("launchctl", ["unload", plistPath()]);
2149
+ } catch {
1958
2150
  }
1959
- if (entry.toolchain === "gem") {
1960
- const userdir = await captureStdout("gem", ["env", "userdir"]);
1961
- return join17(userdir.trim(), "bin");
2151
+ try {
2152
+ await unlink6(plistPath());
2153
+ } catch {
1962
2154
  }
1963
- throw new Error(`no bin-dir resolution for toolchain ${entry.toolchain}`);
1964
2155
  }
1965
- function getToolchainBinDirs() {
1966
- const dirs = /* @__PURE__ */ new Set();
1967
- for (const entry of Object.values(TOOLCHAIN_LSPS)) {
1968
- if (entry.bundled || !entry.toolchain || !entry.toolchainBinDir)
1969
- continue;
1970
- if (entry.toolchain === "go") {
1971
- const gopath = process.env["GOPATH"] ?? join17(homedir4(), "go");
1972
- dirs.add(join17(gopath, "bin"));
1973
- } else {
1974
- dirs.add(join17(homedir4(), entry.toolchainBinDir));
1975
- }
2156
+ async function darwinIsInstalled() {
2157
+ try {
2158
+ const stdout = await exec("launchctl", ["list"]);
2159
+ return stdout.includes(PLIST_LABEL);
2160
+ } catch {
2161
+ return false;
1976
2162
  }
1977
- return [...dirs];
1978
2163
  }
1979
- function runToolchain(cmd, args) {
1980
- return new Promise((resolve5, reject) => {
1981
- const child = spawn6(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
1982
- let stderr = "";
1983
- child.stderr.on("data", (chunk) => {
1984
- stderr += chunk.toString();
1985
- });
1986
- child.on("error", reject);
1987
- child.on("close", (code) => {
1988
- if (code === 0)
1989
- resolve5();
1990
- else
1991
- reject(new Error(`${cmd} ${args.join(" ")} exited ${(code ?? -1).toString()}: ${stderr.split("\n").slice(0, 3).join(" ")}`));
1992
- });
1993
- });
2164
+ function unitPath() {
2165
+ return join18(homedir5(), ".config", "systemd", "user", `${SYSTEMD_SERVICE}.service`);
1994
2166
  }
1995
- function captureStdout(cmd, args) {
1996
- return new Promise((resolve5, reject) => {
1997
- const child = spawn6(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
1998
- let stdout = "";
1999
- let stderr = "";
2000
- child.stdout.on("data", (chunk) => {
2001
- stdout += chunk.toString();
2002
- });
2003
- child.stderr.on("data", (chunk) => {
2004
- stderr += chunk.toString();
2005
- });
2006
- child.on("error", reject);
2007
- child.on("close", (code) => {
2008
- if (code === 0)
2009
- resolve5(stdout);
2010
- else
2011
- reject(new Error(`${cmd} ${args.join(" ")} exited ${(code ?? -1).toString()}: ${stderr}`));
2012
- });
2013
- });
2167
+ function buildUnit() {
2168
+ const cmd = resolveListenerCommand();
2169
+ const execStart = [process.execPath, cmd.script, ...cmd.args].join(" ");
2170
+ const logs = logDir();
2171
+ return `[Unit]
2172
+ Description=RepoWise Listener
2173
+ After=network-online.target
2174
+ Wants=network-online.target
2175
+
2176
+ [Service]
2177
+ Type=simple
2178
+ # FIX-8: systemd user services run with a minimal PATH and don't load shell
2179
+ # profiles. Inject a rich PATH so LSP servers + the typed-resolution probe
2180
+ # resolve (single source of truth: buildDaemonPath() in lsp/daemon-path.ts).
2181
+ Environment=PATH=${sanitizeEnvValue(buildDaemonPath())}
2182
+ ExecStart=${execStart}
2183
+ Restart=always
2184
+ RestartSec=10
2185
+ StandardOutput=append:${join18(logs, "listener-stdout.log")}
2186
+ StandardError=append:${join18(logs, "listener-stderr.log")}
2187
+
2188
+ [Install]
2189
+ WantedBy=default.target`;
2014
2190
  }
2015
- async function isCommandOnPath(command) {
2016
- if (/[^\w./+-]/.test(command))
2017
- return false;
2018
- const probeCmd = process.platform === "win32" ? "where" : "which";
2019
- return new Promise((resolve5) => {
2020
- const child = spawn6(probeCmd, [command], { stdio: "ignore", timeout: 5e3 });
2021
- child.on("error", () => resolve5(false));
2022
- child.on("close", (code) => resolve5(code === 0));
2023
- });
2191
+ async function linuxInstall() {
2192
+ await mkdir8(logDir(), { recursive: true });
2193
+ await mkdir8(join18(homedir5(), ".config", "systemd", "user"), { recursive: true });
2194
+ await writeFile8(unitPath(), buildUnit());
2195
+ await exec("systemctl", ["--user", "daemon-reload"]);
2196
+ await exec("systemctl", ["--user", "enable", SYSTEMD_SERVICE]);
2197
+ await exec("systemctl", ["--user", "start", SYSTEMD_SERVICE]);
2024
2198
  }
2025
- async function pathExists3(p) {
2199
+ async function linuxUninstall() {
2026
2200
  try {
2027
- await fs3.access(p);
2028
- return true;
2201
+ await exec("systemctl", ["--user", "stop", SYSTEMD_SERVICE]);
2202
+ } catch {
2203
+ }
2204
+ try {
2205
+ await exec("systemctl", ["--user", "disable", SYSTEMD_SERVICE]);
2206
+ } catch {
2207
+ }
2208
+ try {
2209
+ await unlink6(unitPath());
2210
+ } catch {
2211
+ }
2212
+ try {
2213
+ await exec("systemctl", ["--user", "daemon-reload"]);
2029
2214
  } catch {
2030
- return false;
2031
2215
  }
2032
2216
  }
2033
- var init_toolchain_installer = __esm({
2034
- "../listener/dist/lsp/toolchain-installer.js"() {
2035
- "use strict";
2036
- init_src();
2217
+ async function linuxIsInstalled() {
2218
+ try {
2219
+ const stdout = await exec("systemctl", ["--user", "is-enabled", SYSTEMD_SERVICE]);
2220
+ return stdout.trim() === "enabled";
2221
+ } catch {
2222
+ return false;
2037
2223
  }
2038
- });
2039
-
2040
- // ../listener/dist/lsp/installer.js
2041
- import { promises as fs4 } from "fs";
2042
- import { join as join18, dirname as dirname6 } from "path";
2043
- import { spawn as spawn7 } from "child_process";
2044
- import lockfile3 from "proper-lockfile";
2045
- function getLspInstallDir() {
2046
- return join18(getConfigDir(), "lsp-servers");
2047
2224
  }
2048
- function getLspBinDir() {
2049
- return join18(getLspInstallDir(), "node_modules", ".bin");
2225
+ async function win32Install() {
2226
+ await mkdir8(logDir(), { recursive: true });
2227
+ const cmd = resolveListenerCommand();
2228
+ const taskCmd = [process.execPath, cmd.script, ...cmd.args].map((a) => `"${a}"`).join(" ");
2229
+ await exec("schtasks", [
2230
+ "/create",
2231
+ "/tn",
2232
+ TASK_NAME,
2233
+ "/tr",
2234
+ taskCmd,
2235
+ "/sc",
2236
+ "onlogon",
2237
+ "/ru",
2238
+ process.env.USERNAME ?? "",
2239
+ "/f"
2240
+ ]);
2241
+ await exec("schtasks", ["/run", "/tn", TASK_NAME]);
2050
2242
  }
2051
- async function ensureNpmLspInstalled(config2) {
2052
- if (!config2.npmPackage)
2053
- return { installed: false, skipped: "no-npm-package" };
2054
- const installDir = getLspInstallDir();
2055
- const binPath = join18(installDir, "node_modules", ".bin", config2.command);
2056
- if (await pathExists4(binPath)) {
2057
- return { installed: false, skipped: "already-present" };
2243
+ async function win32Uninstall() {
2244
+ try {
2245
+ await exec("schtasks", ["/end", "/tn", TASK_NAME]);
2246
+ } catch {
2058
2247
  }
2059
2248
  try {
2060
- await fs4.mkdir(installDir, { recursive: true });
2061
- let release = null;
2062
- try {
2063
- release = await lockfile3.lock(installDir, {
2064
- stale: 18e4,
2065
- retries: { retries: 20, factor: 1.5, minTimeout: 500, maxTimeout: 5e3 },
2066
- realpath: false
2067
- });
2068
- } catch {
2069
- if (await pathExists4(binPath)) {
2070
- return { installed: false, skipped: "already-present" };
2071
- }
2072
- }
2073
- try {
2074
- if (await pathExists4(binPath)) {
2075
- return { installed: false, skipped: "already-present" };
2076
- }
2077
- const pkgJsonPath = join18(installDir, "package.json");
2078
- if (!await pathExists4(pkgJsonPath)) {
2079
- await fs4.writeFile(pkgJsonPath, JSON.stringify({ name: "repowise-lsp-servers", private: true, version: "0.0.0" }, null, 2), "utf-8");
2080
- }
2081
- await runNpmInstall(installDir, config2.npmPackage);
2082
- if (!await pathExists4(binPath)) {
2083
- return {
2084
- installed: false,
2085
- error: `npm install completed but ${config2.command} still not at ${binPath}`
2086
- };
2087
- }
2088
- return { installed: true };
2089
- } finally {
2090
- if (release) {
2091
- try {
2092
- await release();
2093
- } catch {
2094
- }
2095
- }
2096
- }
2097
- } catch (err) {
2098
- return {
2099
- installed: false,
2100
- error: err instanceof Error ? err.message : String(err)
2101
- };
2249
+ await exec("schtasks", ["/delete", "/tn", TASK_NAME, "/f"]);
2250
+ } catch {
2102
2251
  }
2103
2252
  }
2104
- async function resolveNpmCommand() {
2105
- const nodeDir = dirname6(process.execPath);
2106
- for (const candidate of [join18(nodeDir, "npm"), join18(nodeDir, "npm.cmd")]) {
2107
- try {
2108
- await fs4.access(candidate);
2109
- return candidate;
2110
- } catch {
2111
- }
2253
+ async function win32IsInstalled() {
2254
+ try {
2255
+ await exec("schtasks", ["/query", "/tn", TASK_NAME]);
2256
+ return true;
2257
+ } catch {
2258
+ return false;
2112
2259
  }
2113
- for (const candidate of ["/opt/homebrew/bin/npm", "/usr/local/bin/npm", "/usr/bin/npm"]) {
2114
- try {
2115
- await fs4.access(candidate);
2116
- return candidate;
2117
- } catch {
2118
- }
2260
+ }
2261
+ async function darwinStop() {
2262
+ try {
2263
+ await exec("launchctl", ["unload", plistPath()]);
2264
+ } catch {
2119
2265
  }
2120
- return "npm";
2121
2266
  }
2122
- async function runNpmInstall(cwd, pkg2) {
2123
- const npmCmd = await resolveNpmCommand();
2124
- const nodeDir = dirname6(process.execPath);
2125
- const augmentedPath = process.env.PATH ? `${nodeDir}:${process.env.PATH}` : nodeDir;
2126
- return new Promise((resolve5, reject) => {
2127
- const child = spawn7(npmCmd, ["install", "--no-audit", "--no-fund", "--silent", pkg2], {
2128
- cwd,
2129
- stdio: ["ignore", "pipe", "pipe"],
2130
- env: { ...process.env, PATH: augmentedPath }
2131
- });
2132
- let stderr = "";
2133
- child.stderr.on("data", (chunk) => {
2134
- stderr += chunk.toString();
2135
- });
2136
- child.on("error", (err) => reject(err));
2137
- child.on("close", (code) => {
2138
- if (code === 0) {
2139
- resolve5();
2140
- } else {
2141
- reject(new Error(`npm install ${pkg2} exited ${(code ?? -1).toString()}: ${sanitizeNpmStderr(stderr)}`));
2142
- }
2143
- });
2144
- });
2267
+ async function linuxStop() {
2268
+ await exec("systemctl", ["--user", "stop", SYSTEMD_SERVICE]);
2145
2269
  }
2146
- function sanitizeNpmStderr(raw) {
2147
- const truncated = raw.split("\n").slice(0, 3).join(" ").trim();
2148
- return truncated.replace(/(https?:\/\/)[^@\s/]*@/g, (_match, scheme) => `${scheme}<redacted>@`);
2270
+ async function win32Stop() {
2271
+ await exec("schtasks", ["/end", "/tn", TASK_NAME]);
2149
2272
  }
2150
- async function pathExists4(p) {
2273
+ async function darwinIsServiceRunning() {
2151
2274
  try {
2152
- await fs4.access(p);
2153
- return true;
2275
+ const stdout = await exec("launchctl", ["list", PLIST_LABEL]);
2276
+ const pidMatch = stdout.match(/"PID"\s*=\s*(\d+)/);
2277
+ return pidMatch !== null && parseInt(pidMatch[1], 10) > 0;
2154
2278
  } catch {
2155
2279
  return false;
2156
2280
  }
2157
2281
  }
2158
- async function detectRepoLanguages(repoRoot) {
2159
- const found = /* @__PURE__ */ new Set();
2160
- let inspected = 0;
2161
- const MAX_INSPECT = 200;
2162
- async function inspect(dir) {
2163
- if (inspected >= MAX_INSPECT)
2164
- return;
2165
- let entries;
2166
- try {
2167
- entries = await fs4.readdir(dir);
2168
- } catch {
2169
- return;
2170
- }
2171
- for (const name of entries) {
2172
- if (inspected >= MAX_INSPECT)
2173
- return;
2174
- if (name.startsWith("."))
2175
- continue;
2176
- if (name === "node_modules" || name === "dist" || name === "build")
2177
- continue;
2178
- const lang = detectLanguage(name);
2179
- if (lang) {
2180
- found.add(lang);
2181
- inspected += 1;
2182
- }
2183
- }
2282
+ async function linuxIsServiceRunning() {
2283
+ try {
2284
+ const stdout = await exec("systemctl", ["--user", "is-active", SYSTEMD_SERVICE]);
2285
+ return stdout.trim() === "active";
2286
+ } catch {
2287
+ return false;
2184
2288
  }
2185
- await inspect(repoRoot);
2186
- let topEntries = [];
2289
+ }
2290
+ async function win32IsServiceRunning() {
2187
2291
  try {
2188
- topEntries = await fs4.readdir(repoRoot);
2292
+ const stdout = await exec("schtasks", ["/query", "/tn", TASK_NAME, "/fo", "CSV"]);
2293
+ return stdout.includes("Running");
2189
2294
  } catch {
2190
- return found;
2295
+ return false;
2296
+ }
2297
+ }
2298
+ async function stopService() {
2299
+ switch (process.platform) {
2300
+ case "darwin":
2301
+ await darwinStop();
2302
+ break;
2303
+ case "linux":
2304
+ await linuxStop();
2305
+ break;
2306
+ case "win32":
2307
+ await win32Stop();
2308
+ break;
2309
+ default:
2310
+ throw new Error(`Unsupported platform: ${process.platform}`);
2311
+ }
2312
+ }
2313
+ async function install() {
2314
+ switch (process.platform) {
2315
+ case "darwin":
2316
+ await darwinInstall();
2317
+ break;
2318
+ case "linux":
2319
+ await linuxInstall();
2320
+ break;
2321
+ case "win32":
2322
+ await win32Install();
2323
+ break;
2324
+ default:
2325
+ throw new Error(`Unsupported platform: ${process.platform}`);
2191
2326
  }
2192
- for (const name of topEntries) {
2193
- if (name.startsWith("."))
2194
- continue;
2195
- if (name === "node_modules" || name === "dist" || name === "build")
2196
- continue;
2197
- const sub = join18(repoRoot, name);
2198
- try {
2199
- const stat8 = await fs4.stat(sub);
2200
- if (stat8.isDirectory())
2201
- await inspect(sub);
2202
- } catch {
2203
- }
2327
+ }
2328
+ async function uninstall() {
2329
+ switch (process.platform) {
2330
+ case "darwin":
2331
+ await darwinUninstall();
2332
+ break;
2333
+ case "linux":
2334
+ await linuxUninstall();
2335
+ break;
2336
+ case "win32":
2337
+ await win32Uninstall();
2338
+ break;
2339
+ default:
2340
+ throw new Error(`Unsupported platform: ${process.platform}`);
2204
2341
  }
2205
- return found;
2206
2342
  }
2207
- async function prepareLspServersForRepos(repos, options = {}) {
2208
- const detected = /* @__PURE__ */ new Set();
2209
- for (const r of repos) {
2210
- if (!r.localPath)
2211
- continue;
2212
- try {
2213
- const stat8 = await fs4.stat(r.localPath);
2214
- if (!stat8.isDirectory())
2215
- continue;
2216
- } catch {
2217
- continue;
2218
- }
2219
- try {
2220
- const langs = await detectRepoLanguages(r.localPath);
2221
- for (const l of langs)
2222
- detected.add(l);
2223
- } catch {
2224
- }
2343
+ async function isInstalled() {
2344
+ switch (process.platform) {
2345
+ case "darwin":
2346
+ return darwinIsInstalled();
2347
+ case "linux":
2348
+ return linuxIsInstalled();
2349
+ case "win32":
2350
+ return win32IsInstalled();
2351
+ default:
2352
+ return false;
2225
2353
  }
2226
- const results = [];
2227
- for (const language of detected) {
2228
- const configs = getEffectiveConfigList(language, process.env, options.lspOverrides);
2229
- const npmConfig = configs.find((c) => c.npmPackage);
2230
- if (npmConfig) {
2231
- const outcome = await ensureNpmLspInstalled(npmConfig);
2232
- results.push({
2233
- language,
2234
- installed: outcome.installed,
2235
- alreadyPresent: outcome.skipped === "already-present",
2236
- skippedNoNpmPackage: false,
2237
- installMethod: "npm",
2238
- error: outcome.error
2239
- });
2240
- continue;
2241
- }
2242
- const nativeConfig = configs.find((c) => c.nativeInstallKey);
2243
- if (nativeConfig?.nativeInstallKey) {
2244
- const outcome = await ensureNativeLspInstalled(nativeConfig.nativeInstallKey);
2245
- results.push({
2246
- language,
2247
- installed: outcome.installed,
2248
- alreadyPresent: outcome.alreadyPresent,
2249
- skippedNoNpmPackage: false,
2250
- installMethod: "native",
2251
- skipped: outcome.skipped,
2252
- hint: outcome.skipped ? nativeConfig.installHint : void 0,
2253
- error: outcome.error
2254
- });
2255
- continue;
2256
- }
2257
- const coursierConfig = configs.find((c) => c.coursierInstallKey);
2258
- if (coursierConfig?.coursierInstallKey) {
2259
- const outcome = await ensureCoursierLspInstalled(coursierConfig.coursierInstallKey);
2260
- results.push({
2261
- language,
2262
- installed: outcome.installed,
2263
- alreadyPresent: outcome.alreadyPresent,
2264
- skippedNoNpmPackage: false,
2265
- installMethod: "coursier",
2266
- skipped: outcome.skipped,
2267
- hint: outcome.skipped ? coursierConfig.installHint : void 0,
2268
- error: outcome.error
2269
- });
2270
- continue;
2271
- }
2272
- const toolchainConfig = configs.find((c) => c.toolchainInstallKey);
2273
- if (toolchainConfig?.toolchainInstallKey) {
2274
- const outcome = await ensureToolchainLspInstalled(toolchainConfig.toolchainInstallKey);
2275
- results.push({
2276
- language,
2277
- installed: outcome.installed,
2278
- alreadyPresent: outcome.alreadyPresent,
2279
- skippedNoNpmPackage: false,
2280
- installMethod: "toolchain",
2281
- skipped: outcome.skipped,
2282
- // Surface the install hint for any skip reason — toolchain
2283
- // missing means user needs to install it; bundled SDK means
2284
- // user needs the SDK.
2285
- hint: outcome.skipped ? toolchainConfig.installHint : void 0,
2286
- error: outcome.error
2287
- });
2288
- continue;
2289
- }
2290
- results.push({
2291
- language,
2292
- installed: false,
2293
- alreadyPresent: false,
2294
- skippedNoNpmPackage: true,
2295
- hint: configs[0]?.installHint
2296
- });
2354
+ }
2355
+ async function isServiceRunning() {
2356
+ switch (process.platform) {
2357
+ case "darwin":
2358
+ return darwinIsServiceRunning();
2359
+ case "linux":
2360
+ return linuxIsServiceRunning();
2361
+ case "win32":
2362
+ return win32IsServiceRunning();
2363
+ default:
2364
+ return false;
2297
2365
  }
2298
- return results;
2299
2366
  }
2300
- var init_installer = __esm({
2301
- "../listener/dist/lsp/installer.js"() {
2367
+ var IS_STAGING, PLIST_LABEL, SYSTEMD_SERVICE, TASK_NAME;
2368
+ var init_service_installer = __esm({
2369
+ "../listener/dist/service-installer.js"() {
2302
2370
  "use strict";
2303
2371
  init_config_dir();
2304
- init_registry();
2305
- init_native_installer();
2306
- init_coursier_installer();
2307
- init_toolchain_installer();
2372
+ init_process_manager();
2373
+ init_daemon_path();
2374
+ IS_STAGING = true ? true : false;
2375
+ PLIST_LABEL = IS_STAGING ? "com.repowise-staging.listener" : "com.repowise.listener";
2376
+ SYSTEMD_SERVICE = IS_STAGING ? "repowise-staging-listener" : "repowise-listener";
2377
+ TASK_NAME = IS_STAGING ? "RepoWise Staging Listener" : "RepoWise Listener";
2308
2378
  }
2309
2379
  });
2310
2380
 
@@ -2316,7 +2386,7 @@ __export(sidecar_cache_exports, {
2316
2386
  sidecarCachePaths: () => sidecarCachePaths
2317
2387
  });
2318
2388
  import { mkdir as mkdir9, readFile as readFile8, readdir as readdir3, stat as stat2, unlink as unlink8, writeFile as writeFile9 } from "fs/promises";
2319
- import { dirname as dirname8, join as join19 } from "path";
2389
+ import { dirname as dirname9, join as join20 } from "path";
2320
2390
  function repoIdSafe(repoId) {
2321
2391
  return /^[A-Za-z0-9_.-]{1,128}$/.test(repoId) && !repoId.startsWith(".");
2322
2392
  }
@@ -2330,8 +2400,8 @@ function sidecarCachePaths(repoId, commitSha) {
2330
2400
  if (!commitShaSafe(commitSha)) {
2331
2401
  throw new Error(`unsafe commitSha: ${commitSha}`);
2332
2402
  }
2333
- const repoDir = join19(getConfigDir(), "typed-resolution", repoId);
2334
- const fullPath = join19(repoDir, `${commitSha}.jsonl`);
2403
+ const repoDir = join20(getConfigDir(), "typed-resolution", repoId);
2404
+ const fullPath = join20(repoDir, `${commitSha}.jsonl`);
2335
2405
  return { fullPath, repoDir };
2336
2406
  }
2337
2407
  async function persistMergedSidecar(repoId, commitSha, sidecar) {
@@ -2354,7 +2424,7 @@ async function sweepSidecarDir(repoDir) {
2354
2424
  const withMtime = [];
2355
2425
  for (const name of jsonlFiles) {
2356
2426
  try {
2357
- const s = await stat2(join19(repoDir, name));
2427
+ const s = await stat2(join20(repoDir, name));
2358
2428
  withMtime.push({ name, mtimeMs: s.mtimeMs });
2359
2429
  } catch {
2360
2430
  }
@@ -2365,7 +2435,7 @@ async function sweepSidecarDir(repoDir) {
2365
2435
  for (const { name, mtimeMs } of candidates) {
2366
2436
  if (now - mtimeMs >= SWEEP_MAX_AGE_MS) {
2367
2437
  try {
2368
- await unlink8(join19(repoDir, name));
2438
+ await unlink8(join20(repoDir, name));
2369
2439
  } catch {
2370
2440
  }
2371
2441
  }
@@ -2469,7 +2539,10 @@ async function uploadSidecar(req, fetchImpl = fetch) {
2469
2539
  throw new Error("upload-url response missing `data.url`");
2470
2540
  const putRes = await fetchImpl(presignedUrl, {
2471
2541
  method: "PUT",
2472
- headers: { "content-type": "application/json" },
2542
+ headers: {
2543
+ "content-type": "application/json",
2544
+ "x-amz-server-side-encryption": "aws:kms"
2545
+ },
2473
2546
  body
2474
2547
  });
2475
2548
  if (!putRes.ok) {
@@ -3060,13 +3133,13 @@ var init_telemetry = __esm({
3060
3133
  // bin/repowise.ts
3061
3134
  import { readFileSync as readFileSync3 } from "fs";
3062
3135
  import { fileURLToPath as fileURLToPath4 } from "url";
3063
- import { dirname as dirname21, join as join60 } from "path";
3136
+ import { dirname as dirname23, join as join61 } from "path";
3064
3137
  import { Command } from "commander";
3065
3138
 
3066
3139
  // ../listener/dist/main.js
3067
3140
  init_config_dir();
3068
3141
  import { readFile as readFile13, writeFile as writeFile15, mkdir as mkdir16, stat as fsStat } from "fs/promises";
3069
- import { join as join40, dirname as dirname15 } from "path";
3142
+ import { join as join41, dirname as dirname17 } from "path";
3070
3143
  import { fileURLToPath as fileURLToPath3 } from "url";
3071
3144
  import lockfile4 from "proper-lockfile";
3072
3145
 
@@ -4877,7 +4950,7 @@ init_config_dir();
4877
4950
  init_process_manager();
4878
4951
  init_service_installer();
4879
4952
  import { unlink as unlink7 } from "fs/promises";
4880
- import { join as join14 } from "path";
4953
+ import { join as join19 } from "path";
4881
4954
  async function getListenerStatus() {
4882
4955
  const pid = await readPid();
4883
4956
  if (pid !== null) {
@@ -4886,7 +4959,7 @@ async function getListenerStatus() {
4886
4959
  return { running: true, method: "pid", pid, serviceInstalled: serviceInstalled2 };
4887
4960
  }
4888
4961
  try {
4889
- await unlink7(join14(getConfigDir(), "listener.pid"));
4962
+ await unlink7(join19(getConfigDir(), "listener.pid"));
4890
4963
  } catch {
4891
4964
  }
4892
4965
  }
@@ -4931,15 +5004,15 @@ async function stopListener() {
4931
5004
 
4932
5005
  // ../listener/dist/mcp/bootstrap.js
4933
5006
  init_config_dir();
4934
- import { join as join22 } from "path";
5007
+ import { join as join23 } from "path";
4935
5008
 
4936
5009
  // ../listener/dist/lsp/workspace-session.js
4937
5010
  import { pathToFileURL } from "url";
4938
5011
  import { readFile as readFile7 } from "fs/promises";
4939
- import { resolve as pathResolve2, sep as pathSep, dirname as dirname7 } from "path";
5012
+ import { resolve as pathResolve2, sep as pathSep, dirname as dirname8, delimiter as delimiter2 } from "path";
4940
5013
 
4941
5014
  // ../listener/dist/lsp/lsp-client.js
4942
- import { spawn as spawn2 } from "child_process";
5015
+ import { spawn as spawn7 } from "child_process";
4943
5016
  import { EventEmitter, once } from "events";
4944
5017
  import { Buffer as Buffer2 } from "buffer";
4945
5018
  var LspTimeoutError = class extends Error {
@@ -4984,7 +5057,7 @@ var LspClient = class extends EventEmitter {
4984
5057
  closed = false;
4985
5058
  constructor(options) {
4986
5059
  super();
4987
- this.child = options.spawn ? options.spawn() : spawn2(options.command, [...options.args ?? []], {
5060
+ this.child = options.spawn ? options.spawn() : spawn7(options.command, [...options.args ?? []], {
4988
5061
  cwd: options.cwd,
4989
5062
  env: options.env ?? process.env,
4990
5063
  stdio: ["pipe", "pipe", "pipe"]
@@ -5207,10 +5280,7 @@ var LspClient = class extends EventEmitter {
5207
5280
  };
5208
5281
 
5209
5282
  // ../listener/dist/lsp/workspace-session.js
5210
- init_installer();
5211
- init_native_installer();
5212
- init_coursier_installer();
5213
- init_toolchain_installer();
5283
+ init_daemon_path();
5214
5284
 
5215
5285
  // ../listener/dist/lsp/bloop-lifecycle.js
5216
5286
  import { spawn as spawn8 } from "child_process";
@@ -5270,13 +5340,7 @@ var WorkspaceManager = class {
5270
5340
  this.rssSampler = opts.rssSampler ?? (() => process.memoryUsage().rss);
5271
5341
  this.clientFactory = opts.clientFactory ?? ((config2, cwd) => {
5272
5342
  const env = { ...process.env };
5273
- const lspBinDirs = [
5274
- getLspBinDir(),
5275
- getNativeLspBinDir(),
5276
- getCoursierBinDir(),
5277
- ...getToolchainBinDirs()
5278
- ];
5279
- env.PATH = buildLspSpawnPath(env.PATH, dirname7(process.execPath), lspBinDirs);
5343
+ env.PATH = buildLspSpawnPath(env.PATH, dirname8(process.execPath), managedBinDirs());
5280
5344
  const client = new LspClient({
5281
5345
  command: config2.command,
5282
5346
  args: config2.args,
@@ -5645,14 +5709,19 @@ var PathEscapeError = class extends Error {
5645
5709
  }
5646
5710
  };
5647
5711
  function buildLspSpawnPath(basePath, nodeDir, lspBinDirs) {
5648
- const extra = lspBinDirs.join(":");
5649
- return basePath ? `${nodeDir}:${basePath}:${extra}` : `${nodeDir}:${extra}`;
5712
+ const parts = [
5713
+ nodeDir,
5714
+ ...basePath ? basePath.split(delimiter2) : [],
5715
+ ...commonBinDirs(),
5716
+ ...lspBinDirs
5717
+ ];
5718
+ return [...new Set(parts.filter((d) => d.length > 0))].join(delimiter2);
5650
5719
  }
5651
5720
 
5652
5721
  // ../listener/dist/mcp/graph-cache.js
5653
5722
  init_config_dir();
5654
5723
  import { readFile as readFile9, stat as stat3 } from "fs/promises";
5655
- import { join as join20 } from "path";
5724
+ import { join as join21 } from "path";
5656
5725
  var EVICT_DEBOUNCE_MS = 6e4;
5657
5726
  function assertSafeRepoId(repoId) {
5658
5727
  if (!repoId || typeof repoId !== "string") {
@@ -5667,7 +5736,7 @@ function assertSafeRepoId(repoId) {
5667
5736
  }
5668
5737
  function createGraphCache(options = {}) {
5669
5738
  const evictMs = options.evictDebounceMs ?? EVICT_DEBOUNCE_MS;
5670
- const resolvePath = options.resolveGraphPath ?? ((repoId) => join20(defaultRepoWiseHome(), "graphs", `${repoId}.json`));
5739
+ const resolvePath = options.resolveGraphPath ?? ((repoId) => join21(defaultRepoWiseHome(), "graphs", `${repoId}.json`));
5671
5740
  const entries = /* @__PURE__ */ new Map();
5672
5741
  const inFlight = /* @__PURE__ */ new Map();
5673
5742
  async function loadFromDisk(repoId) {
@@ -5761,7 +5830,7 @@ function defaultRepoWiseHome() {
5761
5830
 
5762
5831
  // ../listener/dist/mcp/graph-downloader.js
5763
5832
  import { mkdir as mkdir10, rename as rename3, writeFile as writeFile10 } from "fs/promises";
5764
- import { dirname as dirname9 } from "path";
5833
+ import { dirname as dirname10 } from "path";
5765
5834
  function createGraphDownloader(options) {
5766
5835
  const fetchFn = options.fetchImpl ?? fetch;
5767
5836
  let lastSha = null;
@@ -5812,7 +5881,7 @@ function createGraphDownloader(options) {
5812
5881
  message: `graph parse: ${err instanceof Error ? err.message : String(err)}`
5813
5882
  };
5814
5883
  }
5815
- await mkdir10(dirname9(options.targetPath), { recursive: true });
5884
+ await mkdir10(dirname10(options.targetPath), { recursive: true });
5816
5885
  const tmpPath = `${options.targetPath}.${Date.now()}.tmp`;
5817
5886
  await writeFile10(tmpPath, body, { encoding: "utf-8", mode: 384 });
5818
5887
  await rename3(tmpPath, options.targetPath);
@@ -5838,7 +5907,7 @@ function createGraphDownloader(options) {
5838
5907
  // ../listener/dist/mcp/log-encryption.js
5839
5908
  import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
5840
5909
  import { mkdir as mkdir11, readFile as readFile10, stat as stat4, writeFile as writeFile11 } from "fs/promises";
5841
- import { dirname as dirname10 } from "path";
5910
+ import { dirname as dirname11 } from "path";
5842
5911
  var KEY_BYTES = 32;
5843
5912
  var IV_BYTES = 12;
5844
5913
  var TAG_BYTES = 16;
@@ -5885,7 +5954,7 @@ function createFileKeyStore(keyPath) {
5885
5954
  return parsed;
5886
5955
  }
5887
5956
  const fresh = randomBytes(KEY_BYTES);
5888
- await mkdir11(dirname10(keyPath), { recursive: true });
5957
+ await mkdir11(dirname11(keyPath), { recursive: true });
5889
5958
  const tmp = `${keyPath}.tmp`;
5890
5959
  await writeFile11(tmp, fresh.toString("base64") + "\n", {
5891
5960
  encoding: "utf-8",
@@ -5927,7 +5996,7 @@ function decryptLine(encoded, key) {
5927
5996
 
5928
5997
  // ../listener/dist/mcp/mcp-logger.js
5929
5998
  import { appendFile, mkdir as mkdir12, stat as stat5, unlink as unlink9, writeFile as writeFile12, readFile as readFile11 } from "fs/promises";
5930
- import { dirname as dirname11 } from "path";
5999
+ import { dirname as dirname12 } from "path";
5931
6000
  var DEFAULT_MAX_BYTES = 100 * 1024 * 1024;
5932
6001
  function createMcpLogger(options) {
5933
6002
  const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
@@ -5950,7 +6019,7 @@ function createMcpLogger(options) {
5950
6019
  await migrateLegacyPlaintext();
5951
6020
  const key = await options.keyStore.getKey();
5952
6021
  const encoded = encryptLine(JSON.stringify(entry), key) + "\n";
5953
- await mkdir12(dirname11(options.filePath), { recursive: true });
6022
+ await mkdir12(dirname12(options.filePath), { recursive: true });
5954
6023
  await appendFile(options.filePath, encoded, { encoding: "utf-8", mode: 384 });
5955
6024
  try {
5956
6025
  const s = await stat5(options.filePath);
@@ -5984,7 +6053,7 @@ async function rotate(path, size) {
5984
6053
  // ../listener/dist/mcp/mcp-log-uploader.js
5985
6054
  import { randomUUID } from "crypto";
5986
6055
  import { readFile as readFile12, writeFile as writeFile13, mkdir as mkdir13 } from "fs/promises";
5987
- import { dirname as dirname12 } from "path";
6056
+ import { dirname as dirname13 } from "path";
5988
6057
  var DEFAULT_MAX_ENTRIES = 5e3;
5989
6058
  var DEFAULT_MAX_BYTES2 = 1 * 1024 * 1024;
5990
6059
  function createMcpLogUploader(options) {
@@ -6134,7 +6203,7 @@ async function readPendingBatch(watermarkPath) {
6134
6203
  }
6135
6204
  }
6136
6205
  async function writePendingBatch(watermarkPath, batchId) {
6137
- await mkdir13(dirname12(watermarkPath), { recursive: true });
6206
+ await mkdir13(dirname13(watermarkPath), { recursive: true });
6138
6207
  await writeFile13(pendingPath(watermarkPath), batchId, { encoding: "utf-8", mode: 384 });
6139
6208
  }
6140
6209
  async function clearPendingBatch(watermarkPath) {
@@ -6156,7 +6225,7 @@ async function readWatermark(path) {
6156
6225
  }
6157
6226
  }
6158
6227
  async function writeWatermark(path, offset) {
6159
- await mkdir13(dirname12(path), { recursive: true });
6228
+ await mkdir13(dirname13(path), { recursive: true });
6160
6229
  const tmp = `${path}.tmp`;
6161
6230
  await writeFile13(tmp, offset.toString() + "\n", { encoding: "utf-8", mode: 384 });
6162
6231
  const { rename: rename5 } = await import("fs/promises");
@@ -6176,7 +6245,7 @@ async function isLocallyConsented(flagPath2) {
6176
6245
  init_config_dir();
6177
6246
  import { createServer } from "http";
6178
6247
  import { mkdir as mkdir14, writeFile as writeFile14 } from "fs/promises";
6179
- import { dirname as dirname13, join as join21 } from "path";
6248
+ import { dirname as dirname14, join as join22 } from "path";
6180
6249
  import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
6181
6250
 
6182
6251
  // ../listener/dist/mcp/sanitize.js
@@ -7694,7 +7763,7 @@ async function readJson(req) {
7694
7763
  }
7695
7764
  }
7696
7765
  async function writeEndpointFile(path, endpoint, secret) {
7697
- await mkdir14(dirname13(path), { recursive: true });
7766
+ await mkdir14(dirname14(path), { recursive: true });
7698
7767
  await writeFile14(path, formatEndpointFile({ endpoint, secret }), {
7699
7768
  encoding: "utf-8",
7700
7769
  mode: 384
@@ -7708,7 +7777,7 @@ function extractBearer(header) {
7708
7777
  return m ? m[1] : null;
7709
7778
  }
7710
7779
  function defaultEndpointFile() {
7711
- return join21(getConfigDir(), "listener.endpoint");
7780
+ return join22(getConfigDir(), "listener.endpoint");
7712
7781
  }
7713
7782
 
7714
7783
  // ../listener/dist/mcp/bootstrap.js
@@ -7716,7 +7785,7 @@ async function startMcp(options) {
7716
7785
  const disabled = process.env.REPOWISE_MCP_DISABLED === "true";
7717
7786
  const graphsDir = options.graphsDir ?? defaultGraphsDir();
7718
7787
  const graphCache = createGraphCache({
7719
- resolveGraphPath: (repoId) => join22(graphsDir, `${repoId}.json`)
7788
+ resolveGraphPath: (repoId) => join23(graphsDir, `${repoId}.json`)
7720
7789
  });
7721
7790
  if (disabled) {
7722
7791
  return {
@@ -7739,12 +7808,12 @@ async function startMcp(options) {
7739
7808
  }
7740
7809
  const firstRepoLocal = options.repos.find((r) => r.localPath)?.localPath;
7741
7810
  const mcpHome = defaultMcpHome();
7742
- const logFilePath = join22(mcpHome, "mcp-log.jsonl.enc");
7743
- const keyStore = createFileKeyStore(join22(mcpHome, "mcp-log.key"));
7811
+ const logFilePath = join23(mcpHome, "mcp-log.jsonl.enc");
7812
+ const keyStore = createFileKeyStore(join23(mcpHome, "mcp-log.key"));
7744
7813
  const mcpLogger = createMcpLogger({ filePath: logFilePath, keyStore });
7745
- const flagFilePath = join22(mcpHome, "mcp-log.flag");
7746
- const watermarkFilePath = join22(mcpHome, "mcp-log.watermark");
7747
- const consentSentMarkerPath = join22(mcpHome, "mcp-log.consent-sent");
7814
+ const flagFilePath = join23(mcpHome, "mcp-log.flag");
7815
+ const watermarkFilePath = join23(mcpHome, "mcp-log.watermark");
7816
+ const consentSentMarkerPath = join23(mcpHome, "mcp-log.consent-sent");
7748
7817
  let uploader = null;
7749
7818
  let lastConsentState = false;
7750
7819
  let serverConsentEnsuredThisProcess = false;
@@ -7791,7 +7860,7 @@ async function startMcp(options) {
7791
7860
  apiBaseUrl: repo.apiUrl,
7792
7861
  getAuthToken: options.getAuthToken,
7793
7862
  repoId: repo.repoId,
7794
- targetPath: join22(graphsDir, `${repo.repoId}.json`),
7863
+ targetPath: join23(graphsDir, `${repo.repoId}.json`),
7795
7864
  graphCache,
7796
7865
  ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
7797
7866
  }));
@@ -7876,7 +7945,7 @@ async function startMcp(options) {
7876
7945
  apiBaseUrl: repo.apiUrl,
7877
7946
  getAuthToken: options.getAuthToken,
7878
7947
  repoId: repo.repoId,
7879
- targetPath: join22(graphsDir, `${repo.repoId}.json`),
7948
+ targetPath: join23(graphsDir, `${repo.repoId}.json`),
7880
7949
  graphCache,
7881
7950
  ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
7882
7951
  }));
@@ -7897,7 +7966,7 @@ async function startMcp(options) {
7897
7966
  };
7898
7967
  }
7899
7968
  function defaultGraphsDir() {
7900
- return join22(getConfigDir(), "graphs");
7969
+ return join23(getConfigDir(), "graphs");
7901
7970
  }
7902
7971
  function defaultMcpHome() {
7903
7972
  return getConfigDir();
@@ -7950,16 +8019,16 @@ async function ensureServerConsent(opts) {
7950
8019
  }
7951
8020
 
7952
8021
  // ../listener/dist/mcp/auto-config/index.js
7953
- import { homedir as homedir5 } from "os";
8022
+ import { homedir as homedir6 } from "os";
7954
8023
  import { basename as basename3 } from "path";
7955
8024
 
7956
8025
  // ../listener/dist/mcp/auto-config/writers/claude-code.js
7957
8026
  import { promises as fs6 } from "fs";
7958
- import { join as join23 } from "path";
8027
+ import { join as join24 } from "path";
7959
8028
 
7960
8029
  // ../listener/dist/mcp/auto-config/markers.js
7961
8030
  import { promises as fs5 } from "fs";
7962
- import { dirname as dirname14 } from "path";
8031
+ import { dirname as dirname15 } from "path";
7963
8032
  async function writeMergedConfig(params) {
7964
8033
  const current = await readConfig(params.path);
7965
8034
  const servers = { ...current.mcpServers ?? {} };
@@ -7969,7 +8038,7 @@ async function writeMergedConfig(params) {
7969
8038
  const existing = safeExistingContent(params.path, current);
7970
8039
  if (existing === canonical)
7971
8040
  return "unchanged";
7972
- await fs5.mkdir(dirname14(params.path), { recursive: true });
8041
+ await fs5.mkdir(dirname15(params.path), { recursive: true });
7973
8042
  await fs5.writeFile(params.path, canonical, "utf-8");
7974
8043
  return "written";
7975
8044
  }
@@ -8035,10 +8104,10 @@ function safeExistingContent(path, current) {
8035
8104
  var claudeCodeWriter = {
8036
8105
  tool: "claude-code",
8037
8106
  async detect(repoRoot, home) {
8038
- return await fileExists2(join23(repoRoot, "CLAUDE.md")) || await hasClaudeBinary(home);
8107
+ return await fileExists2(join24(repoRoot, "CLAUDE.md")) || await hasClaudeBinary(home);
8039
8108
  },
8040
8109
  async write(ctx) {
8041
- const path = join23(ctx.repoRoot, ".mcp.json");
8110
+ const path = join24(ctx.repoRoot, ".mcp.json");
8042
8111
  await removeFromConfig(path, `repowise-${ctx.repoId}`);
8043
8112
  const status2 = await writeMergedConfig({
8044
8113
  path,
@@ -8048,7 +8117,7 @@ var claudeCodeWriter = {
8048
8117
  return { status: status2, path };
8049
8118
  },
8050
8119
  async remove(ctx) {
8051
- const path = join23(ctx.repoRoot, ".mcp.json");
8120
+ const path = join24(ctx.repoRoot, ".mcp.json");
8052
8121
  await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
8053
8122
  await removeFromConfig(path, `repowise-${ctx.repoId}`);
8054
8123
  }
@@ -8062,20 +8131,20 @@ async function fileExists2(path) {
8062
8131
  }
8063
8132
  }
8064
8133
  async function hasClaudeBinary(home) {
8065
- return fileExists2(join23(home, ".claude", "claude.json"));
8134
+ return fileExists2(join24(home, ".claude", "claude.json"));
8066
8135
  }
8067
8136
 
8068
8137
  // ../listener/dist/mcp/auto-config/writers/claude-code-hooks.js
8069
8138
  import { promises as fs7 } from "fs";
8070
- import { join as join24 } from "path";
8139
+ import { join as join25 } from "path";
8071
8140
  var claudeCodeHooksWriter = {
8072
8141
  tool: "claude-code-hooks",
8073
8142
  async detect(repoRoot, home) {
8074
- return await fileExists3(join24(repoRoot, "CLAUDE.md")) || await fileExists3(join24(home, ".claude.json"));
8143
+ return await fileExists3(join25(repoRoot, "CLAUDE.md")) || await fileExists3(join25(home, ".claude.json"));
8075
8144
  },
8076
8145
  async write(ctx) {
8077
8146
  const result = await writeClaudeHooksToRepo(ctx.repoRoot, ctx.contextFolder ?? "repowise-context", ctx.variant);
8078
- return { status: result.status, path: join24(ctx.repoRoot, result.relPath) };
8147
+ return { status: result.status, path: join25(ctx.repoRoot, result.relPath) };
8079
8148
  },
8080
8149
  async remove(ctx) {
8081
8150
  await removeClaudeHooksFromRepo(ctx.repoRoot);
@@ -8092,14 +8161,14 @@ async function fileExists3(path) {
8092
8161
 
8093
8162
  // ../listener/dist/mcp/auto-config/writers/cline.js
8094
8163
  import { promises as fs8 } from "fs";
8095
- import { join as join25 } from "path";
8164
+ import { join as join26 } from "path";
8096
8165
  var clineWriter = {
8097
8166
  tool: "cline",
8098
8167
  async detect(repoRoot, home) {
8099
- return await fileExists4(join25(repoRoot, ".clinerules")) || await fileExists4(join25(home, ".cline"));
8168
+ return await fileExists4(join26(repoRoot, ".clinerules")) || await fileExists4(join26(home, ".cline"));
8100
8169
  },
8101
8170
  async write(ctx) {
8102
- const path = join25(ctx.home, ".cline", "mcp.json");
8171
+ const path = join26(ctx.home, ".cline", "mcp.json");
8103
8172
  await removeFromConfig(path, `repowise-${ctx.repoId}`);
8104
8173
  const status2 = await writeMergedConfig({
8105
8174
  path,
@@ -8109,7 +8178,7 @@ var clineWriter = {
8109
8178
  return { status: status2, path };
8110
8179
  },
8111
8180
  async remove(ctx) {
8112
- const path = join25(ctx.home, ".cline", "mcp.json");
8181
+ const path = join26(ctx.home, ".cline", "mcp.json");
8113
8182
  await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
8114
8183
  await removeFromConfig(path, `repowise-${ctx.repoId}`);
8115
8184
  }
@@ -8125,13 +8194,13 @@ async function fileExists4(path) {
8125
8194
 
8126
8195
  // ../listener/dist/mcp/auto-config/writers/cline-hooks.js
8127
8196
  import { promises as fs9 } from "fs";
8128
- import { join as join26 } from "path";
8197
+ import { join as join27 } from "path";
8129
8198
  var SCRIPT_REL = ".clinerules/hooks/UserPromptSubmit";
8130
8199
  var OWNERSHIP_MARKER = "managed by RepoWise";
8131
8200
  var clineHooksWriter = {
8132
8201
  tool: "cline-hooks",
8133
8202
  async detect(repoRoot) {
8134
- return fileExists5(join26(repoRoot, ".clinerules"));
8203
+ return fileExists5(join27(repoRoot, ".clinerules"));
8135
8204
  },
8136
8205
  async write(ctx) {
8137
8206
  if (await isGitTracked(ctx.repoRoot, SCRIPT_REL)) {
@@ -8141,7 +8210,7 @@ var clineHooksWriter = {
8141
8210
  reason: `${SCRIPT_REL} is git-tracked; update it manually to opt in`
8142
8211
  };
8143
8212
  }
8144
- const path = join26(ctx.repoRoot, SCRIPT_REL);
8213
+ const path = join27(ctx.repoRoot, SCRIPT_REL);
8145
8214
  const next = buildClineHookScript({
8146
8215
  repoName: ctx.repoName,
8147
8216
  contextFolder: ctx.contextFolder ?? "repowise-context",
@@ -8158,14 +8227,14 @@ var clineHooksWriter = {
8158
8227
  await ensureExecutable(path);
8159
8228
  return { status: "unchanged", path };
8160
8229
  }
8161
- await fs9.mkdir(join26(ctx.repoRoot, ".clinerules", "hooks"), { recursive: true });
8230
+ await fs9.mkdir(join27(ctx.repoRoot, ".clinerules", "hooks"), { recursive: true });
8162
8231
  await fs9.writeFile(path, next, { encoding: "utf-8", mode: 493 });
8163
8232
  await ensureExecutable(path);
8164
8233
  await applyGitignoreChanges(ctx.repoRoot, { add: [SCRIPT_REL] });
8165
8234
  return { status: "written", path };
8166
8235
  },
8167
8236
  async remove(ctx) {
8168
- const path = join26(ctx.repoRoot, SCRIPT_REL);
8237
+ const path = join27(ctx.repoRoot, SCRIPT_REL);
8169
8238
  const existing = await readOrNull(path);
8170
8239
  if (existing !== null && existing.includes(OWNERSHIP_MARKER)) {
8171
8240
  await fs9.unlink(path);
@@ -8200,14 +8269,14 @@ async function readOrNull(path) {
8200
8269
 
8201
8270
  // ../listener/dist/mcp/auto-config/writers/codex.js
8202
8271
  import { promises as fs10 } from "fs";
8203
- import { join as join27 } from "path";
8272
+ import { join as join28 } from "path";
8204
8273
  var codexWriter = {
8205
8274
  tool: "codex",
8206
8275
  async detect(_repoRoot, home) {
8207
- return fileExists6(join27(home, ".codex"));
8276
+ return fileExists6(join28(home, ".codex"));
8208
8277
  },
8209
8278
  async write(ctx) {
8210
- const path = join27(ctx.home, ".codex", "mcp.json");
8279
+ const path = join28(ctx.home, ".codex", "mcp.json");
8211
8280
  await removeFromConfig(path, `repowise-${ctx.repoId}`);
8212
8281
  const status2 = await writeMergedConfig({
8213
8282
  path,
@@ -8217,7 +8286,7 @@ var codexWriter = {
8217
8286
  return { status: status2, path };
8218
8287
  },
8219
8288
  async remove(ctx) {
8220
- const path = join27(ctx.home, ".codex", "mcp.json");
8289
+ const path = join28(ctx.home, ".codex", "mcp.json");
8221
8290
  await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
8222
8291
  await removeFromConfig(path, `repowise-${ctx.repoId}`);
8223
8292
  }
@@ -8233,15 +8302,15 @@ async function fileExists6(path) {
8233
8302
 
8234
8303
  // ../listener/dist/mcp/auto-config/writers/codex-hooks.js
8235
8304
  import { promises as fs11 } from "fs";
8236
- import { join as join28 } from "path";
8305
+ import { join as join29 } from "path";
8237
8306
  var HOOKS_REL = ".codex/hooks.json";
8238
8307
  var codexHooksWriter = {
8239
8308
  tool: "codex-hooks",
8240
8309
  async detect(repoRoot, home) {
8241
- return await fileExists7(join28(home, ".codex")) || await fileExists7(join28(repoRoot, ".codex"));
8310
+ return await fileExists7(join29(home, ".codex")) || await fileExists7(join29(repoRoot, ".codex"));
8242
8311
  },
8243
8312
  async write(ctx) {
8244
- const repoSignal = await fileExists7(join28(ctx.repoRoot, ".codex"));
8313
+ const repoSignal = await fileExists7(join29(ctx.repoRoot, ".codex"));
8245
8314
  const selected = ctx.selectedTools?.includes("codex") ?? false;
8246
8315
  if (!repoSignal && !selected) {
8247
8316
  return { status: "skipped", reason: "no repo-scoped Codex signal and not selected" };
@@ -8253,7 +8322,7 @@ var codexHooksWriter = {
8253
8322
  reason: `${HOOKS_REL} is git-tracked; add the RepoWise UserPromptSubmit hook manually to opt in`
8254
8323
  };
8255
8324
  }
8256
- const path = join28(ctx.repoRoot, HOOKS_REL);
8325
+ const path = join29(ctx.repoRoot, HOOKS_REL);
8257
8326
  const existing = await readOrNull2(path);
8258
8327
  const merged = mergeCodexHookSettings(existing, {
8259
8328
  repoName: ctx.repoName,
@@ -8263,13 +8332,13 @@ var codexHooksWriter = {
8263
8332
  if (merged === existing) {
8264
8333
  return { status: "unchanged", path };
8265
8334
  }
8266
- await fs11.mkdir(join28(ctx.repoRoot, ".codex"), { recursive: true });
8335
+ await fs11.mkdir(join29(ctx.repoRoot, ".codex"), { recursive: true });
8267
8336
  await fs11.writeFile(path, merged, "utf-8");
8268
8337
  await applyGitignoreChanges(ctx.repoRoot, { add: [HOOKS_REL] });
8269
8338
  return { status: "written", path };
8270
8339
  },
8271
8340
  async remove(ctx) {
8272
- const path = join28(ctx.repoRoot, HOOKS_REL);
8341
+ const path = join29(ctx.repoRoot, HOOKS_REL);
8273
8342
  const existing = await readOrNull2(path);
8274
8343
  if (existing !== null) {
8275
8344
  const next = removeCodexHookSettings(existing);
@@ -8300,14 +8369,14 @@ async function readOrNull2(path) {
8300
8369
 
8301
8370
  // ../listener/dist/mcp/auto-config/writers/copilot.js
8302
8371
  import { promises as fs12 } from "fs";
8303
- import { join as join29 } from "path";
8372
+ import { join as join30 } from "path";
8304
8373
  var copilotWriter = {
8305
8374
  tool: "copilot",
8306
8375
  async detect(repoRoot) {
8307
- return fileExists8(join29(repoRoot, ".vscode"));
8376
+ return fileExists8(join30(repoRoot, ".vscode"));
8308
8377
  },
8309
8378
  async write(ctx) {
8310
- const path = join29(ctx.repoRoot, ".vscode", "mcp.json");
8379
+ const path = join30(ctx.repoRoot, ".vscode", "mcp.json");
8311
8380
  await removeFromConfig(path, `repowise-${ctx.repoId}`);
8312
8381
  const status2 = await writeMergedConfig({
8313
8382
  path,
@@ -8317,7 +8386,7 @@ var copilotWriter = {
8317
8386
  return { status: status2, path };
8318
8387
  },
8319
8388
  async remove(ctx) {
8320
- const path = join29(ctx.repoRoot, ".vscode", "mcp.json");
8389
+ const path = join30(ctx.repoRoot, ".vscode", "mcp.json");
8321
8390
  await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
8322
8391
  await removeFromConfig(path, `repowise-${ctx.repoId}`);
8323
8392
  }
@@ -8333,15 +8402,15 @@ async function fileExists8(path) {
8333
8402
 
8334
8403
  // ../listener/dist/mcp/auto-config/writers/copilot-hooks.js
8335
8404
  import { promises as fs13 } from "fs";
8336
- import { join as join30 } from "path";
8405
+ import { join as join31 } from "path";
8337
8406
  var HOOKS_REL2 = ".github/hooks/repowise.json";
8338
8407
  var copilotHooksWriter = {
8339
8408
  tool: "copilot-hooks",
8340
8409
  async detect(repoRoot) {
8341
- return fileExists9(join30(repoRoot, ".github", "copilot-instructions.md"));
8410
+ return fileExists9(join31(repoRoot, ".github", "copilot-instructions.md"));
8342
8411
  },
8343
8412
  async write(ctx) {
8344
- const path = join30(ctx.repoRoot, HOOKS_REL2);
8413
+ const path = join31(ctx.repoRoot, HOOKS_REL2);
8345
8414
  const next = buildCopilotHooksFile({
8346
8415
  repoName: ctx.repoName,
8347
8416
  contextFolder: ctx.contextFolder ?? "repowise-context",
@@ -8361,14 +8430,14 @@ var copilotHooksWriter = {
8361
8430
  if (existing === next) {
8362
8431
  return { status: "unchanged", path };
8363
8432
  }
8364
- await fs13.mkdir(join30(ctx.repoRoot, ".github", "hooks"), { recursive: true });
8433
+ await fs13.mkdir(join31(ctx.repoRoot, ".github", "hooks"), { recursive: true });
8365
8434
  await fs13.writeFile(path, next, "utf-8");
8366
8435
  const userSelected = ctx.selectedTools?.includes("copilot") ?? false;
8367
8436
  await applyGitignoreChanges(ctx.repoRoot, userSelected ? { remove: [HOOKS_REL2] } : { add: [HOOKS_REL2] });
8368
8437
  return { status: "written", path };
8369
8438
  },
8370
8439
  async remove(ctx) {
8371
- const path = join30(ctx.repoRoot, HOOKS_REL2);
8440
+ const path = join31(ctx.repoRoot, HOOKS_REL2);
8372
8441
  if (!await isGitTracked(ctx.repoRoot, HOOKS_REL2)) {
8373
8442
  await fs13.unlink(path).catch(() => {
8374
8443
  });
@@ -8394,14 +8463,14 @@ async function readOrNull3(path) {
8394
8463
 
8395
8464
  // ../listener/dist/mcp/auto-config/writers/cursor.js
8396
8465
  import { promises as fs14 } from "fs";
8397
- import { join as join31 } from "path";
8466
+ import { join as join32 } from "path";
8398
8467
  var cursorWriter = {
8399
8468
  tool: "cursor",
8400
8469
  async detect(repoRoot) {
8401
- return await fileExists10(join31(repoRoot, ".cursor")) || await fileExists10(join31(repoRoot, ".cursorrules"));
8470
+ return await fileExists10(join32(repoRoot, ".cursor")) || await fileExists10(join32(repoRoot, ".cursorrules"));
8402
8471
  },
8403
8472
  async write(ctx) {
8404
- const path = join31(ctx.repoRoot, ".cursor", "mcp.json");
8473
+ const path = join32(ctx.repoRoot, ".cursor", "mcp.json");
8405
8474
  await removeFromConfig(path, `repowise-${ctx.repoId}`);
8406
8475
  const status2 = await writeMergedConfig({
8407
8476
  path,
@@ -8411,7 +8480,7 @@ var cursorWriter = {
8411
8480
  return { status: status2, path };
8412
8481
  },
8413
8482
  async remove(ctx) {
8414
- const path = join31(ctx.repoRoot, ".cursor", "mcp.json");
8483
+ const path = join32(ctx.repoRoot, ".cursor", "mcp.json");
8415
8484
  await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
8416
8485
  await removeFromConfig(path, `repowise-${ctx.repoId}`);
8417
8486
  }
@@ -8427,14 +8496,14 @@ async function fileExists10(path) {
8427
8496
 
8428
8497
  // ../listener/dist/mcp/auto-config/writers/gemini-cli.js
8429
8498
  import { promises as fs15 } from "fs";
8430
- import { join as join32 } from "path";
8499
+ import { join as join33 } from "path";
8431
8500
  var geminiCliWriter = {
8432
8501
  tool: "gemini-cli",
8433
8502
  async detect(_repoRoot, home) {
8434
- return fileExists11(join32(home, ".gemini"));
8503
+ return fileExists11(join33(home, ".gemini"));
8435
8504
  },
8436
8505
  async write(ctx) {
8437
- const path = join32(ctx.home, ".gemini", "settings.json");
8506
+ const path = join33(ctx.home, ".gemini", "settings.json");
8438
8507
  await removeFromConfig(path, `repowise-${ctx.repoId}`);
8439
8508
  const status2 = await writeMergedConfig({
8440
8509
  path,
@@ -8444,7 +8513,7 @@ var geminiCliWriter = {
8444
8513
  return { status: status2, path };
8445
8514
  },
8446
8515
  async remove(ctx) {
8447
- const path = join32(ctx.home, ".gemini", "settings.json");
8516
+ const path = join33(ctx.home, ".gemini", "settings.json");
8448
8517
  await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
8449
8518
  await removeFromConfig(path, `repowise-${ctx.repoId}`);
8450
8519
  }
@@ -8460,15 +8529,15 @@ async function fileExists11(path) {
8460
8529
 
8461
8530
  // ../listener/dist/mcp/auto-config/writers/gemini-hooks.js
8462
8531
  import { promises as fs16 } from "fs";
8463
- import { join as join33 } from "path";
8532
+ import { join as join34 } from "path";
8464
8533
  var SETTINGS_REL = ".gemini/settings.json";
8465
8534
  var geminiHooksWriter = {
8466
8535
  tool: "gemini-hooks",
8467
8536
  async detect(repoRoot, home) {
8468
- return await fileExists12(join33(home, ".gemini")) || await fileExists12(join33(repoRoot, ".gemini"));
8537
+ return await fileExists12(join34(home, ".gemini")) || await fileExists12(join34(repoRoot, ".gemini"));
8469
8538
  },
8470
8539
  async write(ctx) {
8471
- const repoSignal = await fileExists12(join33(ctx.repoRoot, ".gemini")) || await fileExists12(join33(ctx.repoRoot, "GEMINI.md"));
8540
+ const repoSignal = await fileExists12(join34(ctx.repoRoot, ".gemini")) || await fileExists12(join34(ctx.repoRoot, "GEMINI.md"));
8472
8541
  const selected = ctx.selectedTools?.includes("gemini") ?? false;
8473
8542
  if (!repoSignal && !selected) {
8474
8543
  return { status: "skipped", reason: "no repo-scoped Gemini signal and not selected" };
@@ -8480,7 +8549,7 @@ var geminiHooksWriter = {
8480
8549
  reason: `${SETTINGS_REL} is git-tracked; add the RepoWise BeforeAgent hook manually to opt in`
8481
8550
  };
8482
8551
  }
8483
- const path = join33(ctx.repoRoot, SETTINGS_REL);
8552
+ const path = join34(ctx.repoRoot, SETTINGS_REL);
8484
8553
  const existing = await readOrNull4(path);
8485
8554
  const merged = mergeGeminiHookSettings(existing, {
8486
8555
  repoName: ctx.repoName,
@@ -8490,13 +8559,13 @@ var geminiHooksWriter = {
8490
8559
  if (merged === existing) {
8491
8560
  return { status: "unchanged", path };
8492
8561
  }
8493
- await fs16.mkdir(join33(ctx.repoRoot, ".gemini"), { recursive: true });
8562
+ await fs16.mkdir(join34(ctx.repoRoot, ".gemini"), { recursive: true });
8494
8563
  await fs16.writeFile(path, merged, "utf-8");
8495
8564
  await applyGitignoreChanges(ctx.repoRoot, { add: [SETTINGS_REL] });
8496
8565
  return { status: "written", path };
8497
8566
  },
8498
8567
  async remove(ctx) {
8499
- const path = join33(ctx.repoRoot, SETTINGS_REL);
8568
+ const path = join34(ctx.repoRoot, SETTINGS_REL);
8500
8569
  const existing = await readOrNull4(path);
8501
8570
  if (existing !== null) {
8502
8571
  const next = removeGeminiHookSettings(existing);
@@ -8527,14 +8596,14 @@ async function readOrNull4(path) {
8527
8596
 
8528
8597
  // ../listener/dist/mcp/auto-config/writers/roo.js
8529
8598
  import { promises as fs17 } from "fs";
8530
- import { join as join34 } from "path";
8599
+ import { join as join35 } from "path";
8531
8600
  var rooWriter = {
8532
8601
  tool: "roo",
8533
8602
  async detect(repoRoot, home) {
8534
- return await fileExists13(join34(repoRoot, ".roo")) || await fileExists13(join34(home, ".roo"));
8603
+ return await fileExists13(join35(repoRoot, ".roo")) || await fileExists13(join35(home, ".roo"));
8535
8604
  },
8536
8605
  async write(ctx) {
8537
- const repoConfig = join34(ctx.repoRoot, ".roo", "mcp.json");
8606
+ const repoConfig = join35(ctx.repoRoot, ".roo", "mcp.json");
8538
8607
  const before = await readOrNull5(repoConfig);
8539
8608
  await this.remove(ctx);
8540
8609
  const after = await readOrNull5(repoConfig);
@@ -8544,9 +8613,9 @@ var rooWriter = {
8544
8613
  };
8545
8614
  },
8546
8615
  async remove(ctx) {
8547
- const repoConfig = join34(ctx.repoRoot, ".roo", "mcp.json");
8548
- const homeConfig = join34(ctx.home, ".roo", "mcp.json");
8549
- const homeSettings = join34(ctx.home, ".roo", "settings.json");
8616
+ const repoConfig = join35(ctx.repoRoot, ".roo", "mcp.json");
8617
+ const homeConfig = join35(ctx.home, ".roo", "mcp.json");
8618
+ const homeSettings = join35(ctx.home, ".roo", "settings.json");
8550
8619
  for (const path of [repoConfig, homeConfig, homeSettings]) {
8551
8620
  await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`).catch(() => {
8552
8621
  });
@@ -8573,14 +8642,14 @@ async function readOrNull5(path) {
8573
8642
 
8574
8643
  // ../listener/dist/mcp/auto-config/writers/windsurf.js
8575
8644
  import { promises as fs18 } from "fs";
8576
- import { join as join35 } from "path";
8645
+ import { join as join36 } from "path";
8577
8646
  var windsurfWriter = {
8578
8647
  tool: "windsurf",
8579
8648
  async detect(_repoRoot, home) {
8580
- return fileExists14(join35(home, ".codeium", "windsurf"));
8649
+ return fileExists14(join36(home, ".codeium", "windsurf"));
8581
8650
  },
8582
8651
  async write(ctx) {
8583
- const path = join35(ctx.home, ".codeium", "windsurf", "mcp_config.json");
8652
+ const path = join36(ctx.home, ".codeium", "windsurf", "mcp_config.json");
8584
8653
  await removeFromConfig(path, `repowise-${ctx.repoId}`);
8585
8654
  const status2 = await writeMergedConfig({
8586
8655
  path,
@@ -8590,7 +8659,7 @@ var windsurfWriter = {
8590
8659
  return { status: status2, path };
8591
8660
  },
8592
8661
  async remove(ctx) {
8593
- const path = join35(ctx.home, ".codeium", "windsurf", "mcp_config.json");
8662
+ const path = join36(ctx.home, ".codeium", "windsurf", "mcp_config.json");
8594
8663
  await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
8595
8664
  await removeFromConfig(path, `repowise-${ctx.repoId}`);
8596
8665
  }
@@ -8623,7 +8692,7 @@ var WRITERS = [
8623
8692
 
8624
8693
  // ../listener/dist/mcp/auto-config/index.js
8625
8694
  async function runAutoConfig(opts) {
8626
- const home = opts.home ?? homedir5();
8695
+ const home = opts.home ?? homedir6();
8627
8696
  const writers = opts.writers ?? WRITERS;
8628
8697
  const results = [];
8629
8698
  for (const writer of writers) {
@@ -8672,7 +8741,7 @@ init_installer();
8672
8741
  init_registry();
8673
8742
  init_lsp_tools();
8674
8743
  import { promises as fs19 } from "fs";
8675
- import { join as join36 } from "path";
8744
+ import { join as join37 } from "path";
8676
8745
  async function collectRepresentativeFiles(repoRoot) {
8677
8746
  const reps = /* @__PURE__ */ new Map();
8678
8747
  let inspected = 0;
@@ -8713,7 +8782,7 @@ async function collectRepresentativeFiles(repoRoot) {
8713
8782
  continue;
8714
8783
  if (name === "node_modules" || name === "dist" || name === "build")
8715
8784
  continue;
8716
- const sub = join36(repoRoot, name);
8785
+ const sub = join37(repoRoot, name);
8717
8786
  try {
8718
8787
  const stat8 = await fs19.stat(sub);
8719
8788
  if (stat8.isDirectory())
@@ -8761,7 +8830,7 @@ init_config_dir();
8761
8830
  import { promises as fs20 } from "fs";
8762
8831
  import { spawn as spawn9, execFile as execFile5 } from "child_process";
8763
8832
  import { createWriteStream as createWriteStream2 } from "fs";
8764
- import { join as join37 } from "path";
8833
+ import { join as join38 } from "path";
8765
8834
  import { promisify as promisify3 } from "util";
8766
8835
  var execFileAsync3 = promisify3(execFile5);
8767
8836
  async function isCommandOnPath2(cmd) {
@@ -8825,7 +8894,7 @@ async function maybeWriteClangdStub(repoRoot, repoId) {
8825
8894
  if (e.isDirectory()) {
8826
8895
  if (skip.has(e.name) || e.name.startsWith("."))
8827
8896
  continue;
8828
- await rec(join37(dir, e.name));
8897
+ await rec(join38(dir, e.name));
8829
8898
  continue;
8830
8899
  }
8831
8900
  const dot = e.name.lastIndexOf(".");
@@ -8834,7 +8903,7 @@ async function maybeWriteClangdStub(repoRoot, repoId) {
8834
8903
  const ext = e.name.slice(dot);
8835
8904
  if (!C_EXTS.has(ext) && !CPP_EXTS.has(ext))
8836
8905
  continue;
8837
- sources.push({ path: join37(dir, e.name), ext });
8906
+ sources.push({ path: join38(dir, e.name), ext });
8838
8907
  }
8839
8908
  }
8840
8909
  await rec(repoRoot);
@@ -8850,9 +8919,9 @@ async function maybeWriteClangdStub(repoRoot, repoId) {
8850
8919
  command: `${driver} ${std} -I. -I./include -c "${src}"`
8851
8920
  };
8852
8921
  });
8853
- const outDir = join37(repoRoot, ".repowise");
8922
+ const outDir = join38(repoRoot, ".repowise");
8854
8923
  await fs20.mkdir(outDir, { recursive: true });
8855
- const outPath = join37(outDir, "compile_commands.json");
8924
+ const outPath = join38(outDir, "compile_commands.json");
8856
8925
  await fs20.writeFile(outPath, JSON.stringify(entries, null, 2));
8857
8926
  const capHit = sources.length >= MAX_STUB_SOURCES;
8858
8927
  console.log(`[workspace-prep] ${repoId}: wrote ${entries.length.toString()}-entry compile_commands stub to .repowise/${capHit ? " (cap reached \u2014 additional sources truncated)" : ""}`);
@@ -8861,7 +8930,7 @@ async function maybeWriteClangdStub(repoRoot, repoId) {
8861
8930
  var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
8862
8931
  async function fileExists15(repoRoot, name) {
8863
8932
  try {
8864
- await fs20.access(join37(repoRoot, name));
8933
+ await fs20.access(join38(repoRoot, name));
8865
8934
  return true;
8866
8935
  } catch {
8867
8936
  return false;
@@ -8872,7 +8941,7 @@ async function detectWorkspaceDeps(repoRoot) {
8872
8941
  if (await fileExists15(repoRoot, "package.json")) {
8873
8942
  let pkgRaw = "";
8874
8943
  try {
8875
- pkgRaw = await fs20.readFile(join37(repoRoot, "package.json"), "utf8");
8944
+ pkgRaw = await fs20.readFile(join38(repoRoot, "package.json"), "utf8");
8876
8945
  } catch {
8877
8946
  }
8878
8947
  if (pkgRaw) {
@@ -8975,7 +9044,7 @@ async function runWorkspacePreps(opts) {
8975
9044
  if (opts.missing.length === 0)
8976
9045
  return [];
8977
9046
  const safeRepoId = opts.repoId.replace(/[^a-zA-Z0-9_-]/g, "_");
8978
- const logPath2 = join37(getConfigDir(), `install-log.${safeRepoId}.txt`);
9047
+ const logPath2 = join38(getConfigDir(), `install-log.${safeRepoId}.txt`);
8979
9048
  await fs20.mkdir(getConfigDir(), { recursive: true });
8980
9049
  const stream = opts.logStream ?? createWriteStream2(logPath2, { flags: "a" });
8981
9050
  stream.on("error", () => {
@@ -9126,7 +9195,7 @@ async function prepareWorkspaceDepsForRepos(repos) {
9126
9195
  init_registry();
9127
9196
  import { execFile as execFileCb, spawn as spawn10 } from "child_process";
9128
9197
  import { access as access3, readdir as readdir4 } from "fs/promises";
9129
- import { join as join38 } from "path";
9198
+ import { join as join39 } from "path";
9130
9199
  import { promisify as promisify4 } from "util";
9131
9200
  var execFile6 = promisify4(execFileCb);
9132
9201
  var DEFAULT_BUILD_TIMEOUT_MS = 10 * 60 * 1e3;
@@ -9179,7 +9248,7 @@ async function ensureIndexable(indexRoot, language, options = {}) {
9179
9248
  }
9180
9249
  }
9181
9250
  try {
9182
- await access3(join38(indexRoot, spec.indexStoreProbe));
9251
+ await access3(join39(indexRoot, spec.indexStoreProbe));
9183
9252
  return { ready: true, built: false };
9184
9253
  } catch {
9185
9254
  }
@@ -9219,7 +9288,9 @@ async function ensureIndexable(indexRoot, language, options = {}) {
9219
9288
  init_src();
9220
9289
  init_registry();
9221
9290
  import { execFile as execFileCb2 } from "child_process";
9291
+ import { dirname as dirname16 } from "path";
9222
9292
  import { promisify as promisify5 } from "util";
9293
+ init_daemon_path();
9223
9294
 
9224
9295
  // ../listener/dist/typed-resolution/lsp-upgrade-strategy.js
9225
9296
  var goStrategy = {
@@ -9701,7 +9772,13 @@ async function isCommandOnPath4(command) {
9701
9772
  return false;
9702
9773
  const probeCmd = process.platform === "win32" ? "where" : "which";
9703
9774
  try {
9704
- await execFile7(probeCmd, [command], { timeout: 5e3 });
9775
+ await execFile7(probeCmd, [command], {
9776
+ timeout: 5e3,
9777
+ env: {
9778
+ ...process.env,
9779
+ PATH: buildLspSpawnPath(process.env.PATH, dirname16(process.execPath), managedBinDirs())
9780
+ }
9781
+ });
9705
9782
  return true;
9706
9783
  } catch {
9707
9784
  return false;
@@ -9905,13 +9982,13 @@ init_config_dir();
9905
9982
  import { execFile as execFileCb3 } from "child_process";
9906
9983
  import { createHash as createHash5 } from "crypto";
9907
9984
  import { mkdir as mkdir15, readdir as readdir5, rm, stat as stat6 } from "fs/promises";
9908
- import { join as join39 } from "path";
9985
+ import { join as join40 } from "path";
9909
9986
  import { promisify as promisify6 } from "util";
9910
9987
  var execFile8 = promisify6(execFileCb3);
9911
9988
  var GIT_OP_TIMEOUT_MS = 6e4;
9912
9989
  var MAX_CONCURRENT_WORKTREES = 3;
9913
9990
  function worktreeBaseDir() {
9914
- return join39(getConfigDir(), "lsp-worktrees");
9991
+ return join40(getConfigDir(), "lsp-worktrees");
9915
9992
  }
9916
9993
  async function reapOrphanWorktrees(maxAgeMs = 24 * 60 * 60 * 1e3, base = worktreeBaseDir()) {
9917
9994
  let reaped = 0;
@@ -9923,7 +10000,7 @@ async function reapOrphanWorktrees(maxAgeMs = 24 * 60 * 60 * 1e3, base = worktre
9923
10000
  }
9924
10001
  const cutoff = Date.now() - maxAgeMs;
9925
10002
  for (const entry of entries) {
9926
- const dir = join39(base, entry);
10003
+ const dir = join40(base, entry);
9927
10004
  try {
9928
10005
  const info = await stat6(dir);
9929
10006
  if (!info.isDirectory() || info.mtimeMs > cutoff)
@@ -9956,7 +10033,7 @@ async function prepareCommittedTree(repoRoot, commitSha, options = {}) {
9956
10033
  }
9957
10034
  const base = options.worktreeBase ?? worktreeBaseDir();
9958
10035
  const dirName = createHash5("sha256").update(`${repoRoot}\0${commitSha}`).digest("hex").slice(0, 16) + "-" + commitSha.slice(0, 12);
9959
- const worktreePath = join39(base, dirName);
10036
+ const worktreePath = join40(base, dirName);
9960
10037
  try {
9961
10038
  await mkdir15(base, { recursive: true });
9962
10039
  await git(["-C", repoRoot, "worktree", "prune"]).catch(() => void 0);
@@ -10292,7 +10369,7 @@ var CRASH_LOOP_WINDOW_MS = 3e4;
10292
10369
  var CRASH_LOOP_THRESHOLD = 3;
10293
10370
  async function readRawToolConfig() {
10294
10371
  try {
10295
- const configPath = join40(getConfigDir(), "config.json");
10372
+ const configPath = join41(getConfigDir(), "config.json");
10296
10373
  const data = await readFile13(configPath, "utf-8");
10297
10374
  const raw = JSON.parse(data);
10298
10375
  return {
@@ -10367,11 +10444,11 @@ function resolveAuditRoots() {
10367
10444
  return override.split(":").map((s) => s.trim()).filter(Boolean);
10368
10445
  }
10369
10446
  const out = /* @__PURE__ */ new Set();
10370
- let dir = dirname15(process.execPath);
10447
+ let dir = dirname17(process.execPath);
10371
10448
  for (let i = 0; i < 8; i += 1) {
10372
- out.add(join40(dir, "node_modules"));
10373
- out.add(join40(dir, "lib", "node_modules"));
10374
- const parent = dirname15(dir);
10449
+ out.add(join41(dir, "node_modules"));
10450
+ out.add(join41(dir, "lib", "node_modules"));
10451
+ const parent = dirname17(dir);
10375
10452
  if (parent === dir)
10376
10453
  break;
10377
10454
  dir = parent;
@@ -10635,7 +10712,7 @@ async function checkStaleContext(repos, state, groups) {
10635
10712
  if (group?.offline.isOffline)
10636
10713
  continue;
10637
10714
  const { statSync: statSync2, readdirSync: readdirSync2 } = await import("fs");
10638
- const contextPath = join40(repo.localPath, "repowise-context");
10715
+ const contextPath = join41(repo.localPath, "repowise-context");
10639
10716
  let isMissingOrEmpty = false;
10640
10717
  try {
10641
10718
  const s = statSync2(contextPath);
@@ -10700,7 +10777,7 @@ async function reconcileAgentInstructions(repos) {
10700
10777
  const { graphOnly } = await readRawToolConfig();
10701
10778
  const variant = graphOnly ? "graph" : "full";
10702
10779
  for (const repo of repos) {
10703
- const path = join40(repo.localPath, "repowise-context", "project-overview.md");
10780
+ const path = join41(repo.localPath, "repowise-context", "project-overview.md");
10704
10781
  let content;
10705
10782
  try {
10706
10783
  content = await readFile13(path, "utf-8");
@@ -10784,7 +10861,7 @@ async function startListener() {
10784
10861
  }
10785
10862
  const configDir = getConfigDir();
10786
10863
  await mkdir16(configDir, { recursive: true });
10787
- const lockPath = join40(configDir, "listener.lock");
10864
+ const lockPath = join41(configDir, "listener.lock");
10788
10865
  await writeFile15(lockPath, "", { flag: "a" });
10789
10866
  let lockIsHeld = false;
10790
10867
  try {
@@ -10827,7 +10904,7 @@ async function startListener() {
10827
10904
  return;
10828
10905
  }
10829
10906
  if (config2.repos.length === 0 && !config2.autoDiscoverRepos) {
10830
- console.error(`No repos configured. Add repos to ${join40(configDir, "config.json")}`);
10907
+ console.error(`No repos configured. Add repos to ${join41(configDir, "config.json")}`);
10831
10908
  await releaseLockAndExit();
10832
10909
  process.exitCode = 1;
10833
10910
  return;
@@ -10894,8 +10971,8 @@ async function startListener() {
10894
10971
  const packageName = true ? "repowisestage" : "repowise";
10895
10972
  let currentVersion = "";
10896
10973
  try {
10897
- const selfDir = dirname15(fileURLToPath3(import.meta.url));
10898
- const pkgJsonPath = join40(selfDir, "..", "..", "package.json");
10974
+ const selfDir = dirname17(fileURLToPath3(import.meta.url));
10975
+ const pkgJsonPath = join41(selfDir, "..", "..", "package.json");
10899
10976
  const pkgJson = JSON.parse(await readFile13(pkgJsonPath, "utf-8"));
10900
10977
  currentVersion = pkgJson.version;
10901
10978
  } catch (err) {
@@ -11126,6 +11203,9 @@ async function startListener() {
11126
11203
  void warmReposLsp(newRepos, mcpRuntime.lspWorkspaces, config2.lspOverrides).catch(() => {
11127
11204
  });
11128
11205
  }
11206
+ void reconcileMcpConfigs(newRepos, packageName).catch((err) => {
11207
+ console.warn("[mcp-config] front-load for newly-registered repo(s) failed:", err instanceof Error ? err.message : String(err));
11208
+ });
11129
11209
  }
11130
11210
  } catch {
11131
11211
  }
@@ -11398,7 +11478,7 @@ async function startListener() {
11398
11478
  } catch {
11399
11479
  }
11400
11480
  }
11401
- const credentialsPath = join40(getConfigDir(), "credentials.json");
11481
+ const credentialsPath = join41(getConfigDir(), "credentials.json");
11402
11482
  let credentialsChanged = false;
11403
11483
  let watcher = null;
11404
11484
  try {
@@ -11443,8 +11523,8 @@ if (isDirectRun) {
11443
11523
  }
11444
11524
 
11445
11525
  // src/lib/env.ts
11446
- import { homedir as homedir6 } from "os";
11447
- import { join as join41 } from "path";
11526
+ import { homedir as homedir7 } from "os";
11527
+ import { join as join42 } from "path";
11448
11528
  var IS_STAGING2 = true ? true : false;
11449
11529
  var PRODUCTION = {
11450
11530
  apiUrl: "https://api.repowise.ai",
@@ -11464,7 +11544,7 @@ function getEnvConfig() {
11464
11544
  return IS_STAGING2 ? STAGING : PRODUCTION;
11465
11545
  }
11466
11546
  function getConfigDir2() {
11467
- return join41(homedir6(), IS_STAGING2 ? ".repowise-staging" : ".repowise");
11547
+ return join42(homedir7(), IS_STAGING2 ? ".repowise-staging" : ".repowise");
11468
11548
  }
11469
11549
  function getPackageName() {
11470
11550
  return true ? "repowisestage" : "repowise";
@@ -11475,11 +11555,11 @@ import chalk from "chalk";
11475
11555
 
11476
11556
  // src/lib/config.ts
11477
11557
  import { readFile as readFile14, writeFile as writeFile16, mkdir as mkdir17, rename as rename4, unlink as unlink10 } from "fs/promises";
11478
- import { join as join42 } from "path";
11558
+ import { join as join43 } from "path";
11479
11559
  import lockfile5 from "proper-lockfile";
11480
11560
  async function getConfig() {
11481
11561
  try {
11482
- const data = await readFile14(join42(getConfigDir2(), "config.json"), "utf-8");
11562
+ const data = await readFile14(join43(getConfigDir2(), "config.json"), "utf-8");
11483
11563
  return JSON.parse(data);
11484
11564
  } catch {
11485
11565
  return {};
@@ -11487,7 +11567,7 @@ async function getConfig() {
11487
11567
  }
11488
11568
  async function saveConfig(config2) {
11489
11569
  const dir = getConfigDir2();
11490
- const path = join42(dir, "config.json");
11570
+ const path = join43(dir, "config.json");
11491
11571
  await mkdir17(dir, { recursive: true });
11492
11572
  const tmpPath = path + ".tmp";
11493
11573
  try {
@@ -11503,7 +11583,7 @@ async function saveConfig(config2) {
11503
11583
  }
11504
11584
  async function mergeAndSaveConfig(updates) {
11505
11585
  const dir = getConfigDir2();
11506
- const path = join42(dir, "config.json");
11586
+ const path = join43(dir, "config.json");
11507
11587
  await mkdir17(dir, { recursive: true });
11508
11588
  try {
11509
11589
  await writeFile16(path, "", { flag: "a" });
@@ -11574,7 +11654,7 @@ async function showWelcome(currentVersion) {
11574
11654
 
11575
11655
  // src/commands/create.ts
11576
11656
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
11577
- import { dirname as dirname17, join as join48 } from "path";
11657
+ import { dirname as dirname19, join as join49 } from "path";
11578
11658
  init_src();
11579
11659
  import chalk8 from "chalk";
11580
11660
  import ora from "ora";
@@ -11583,7 +11663,7 @@ import ora from "ora";
11583
11663
  import { createHash as createHash6, randomBytes as randomBytes3 } from "crypto";
11584
11664
  import { readFile as readFile15, writeFile as writeFile17, mkdir as mkdir18, chmod as chmod4, unlink as unlink11 } from "fs/promises";
11585
11665
  import http from "http";
11586
- import { join as join43 } from "path";
11666
+ import { join as join44 } from "path";
11587
11667
  var CLI_CALLBACK_PORT = 19876;
11588
11668
  var CALLBACK_TIMEOUT_MS = 12e4;
11589
11669
  function getCognitoConfigForStorage() {
@@ -11749,7 +11829,7 @@ async function refreshTokens2(refreshToken) {
11749
11829
  }
11750
11830
  async function getStoredCredentials2() {
11751
11831
  try {
11752
- const credPath = join43(getConfigDir2(), "credentials.json");
11832
+ const credPath = join44(getConfigDir2(), "credentials.json");
11753
11833
  const data = await readFile15(credPath, "utf-8");
11754
11834
  return JSON.parse(data);
11755
11835
  } catch (err) {
@@ -11761,14 +11841,14 @@ async function getStoredCredentials2() {
11761
11841
  }
11762
11842
  async function storeCredentials2(credentials) {
11763
11843
  const dir = getConfigDir2();
11764
- const credPath = join43(dir, "credentials.json");
11844
+ const credPath = join44(dir, "credentials.json");
11765
11845
  await mkdir18(dir, { recursive: true, mode: 448 });
11766
11846
  await writeFile17(credPath, JSON.stringify(credentials, null, 2));
11767
11847
  await chmod4(credPath, 384);
11768
11848
  }
11769
11849
  async function clearCredentials() {
11770
11850
  try {
11771
- await unlink11(join43(getConfigDir2(), "credentials.json"));
11851
+ await unlink11(join44(getConfigDir2(), "credentials.json"));
11772
11852
  } catch (err) {
11773
11853
  if (err.code !== "ENOENT") throw err;
11774
11854
  }
@@ -11976,9 +12056,9 @@ async function selectAiTools() {
11976
12056
 
11977
12057
  // src/lib/gitignore.ts
11978
12058
  import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2 } from "fs";
11979
- import { join as join44 } from "path";
12059
+ import { join as join45 } from "path";
11980
12060
  function ensureGitignore(repoRoot, entry) {
11981
- const gitignorePath = join44(repoRoot, ".gitignore");
12061
+ const gitignorePath = join45(repoRoot, ".gitignore");
11982
12062
  if (existsSync2(gitignorePath)) {
11983
12063
  const content = readFileSync2(gitignorePath, "utf-8");
11984
12064
  const lines = content.split("\n").map((l) => l.trim());
@@ -11995,7 +12075,7 @@ function ensureGitignore(repoRoot, entry) {
11995
12075
  // src/lib/graph-cache.ts
11996
12076
  init_config_dir();
11997
12077
  import { mkdirSync, writeFileSync as writeFileSync2, renameSync, unlinkSync } from "fs";
11998
- import { dirname as dirname16, join as join45 } from "path";
12078
+ import { dirname as dirname18, join as join46 } from "path";
11999
12079
  var SAFE_REPO_ID = /^[A-Za-z0-9_.-]{1,128}$/;
12000
12080
  function assertSafeRepoId2(repoId) {
12001
12081
  if (!repoId || typeof repoId !== "string" || !SAFE_REPO_ID.test(repoId) || repoId === "." || repoId === ".." || repoId.startsWith(".")) {
@@ -12004,7 +12084,7 @@ function assertSafeRepoId2(repoId) {
12004
12084
  }
12005
12085
  function graphCachePath(repoId) {
12006
12086
  assertSafeRepoId2(repoId);
12007
- return join45(getConfigDir(), "graphs", `${repoId}.json`);
12087
+ return join46(getConfigDir(), "graphs", `${repoId}.json`);
12008
12088
  }
12009
12089
  function isUsableGraph(parsed) {
12010
12090
  if (typeof parsed !== "object" || parsed === null) return false;
@@ -12022,9 +12102,27 @@ function readErrorCode(body) {
12022
12102
  return void 0;
12023
12103
  }
12024
12104
  }
12105
+ async function readBodyBounded(res, ctrl, bodyTimeoutMs) {
12106
+ const bodyPromise = res.text();
12107
+ void bodyPromise.catch(() => {
12108
+ });
12109
+ let timer;
12110
+ const timeout = new Promise((_, reject) => {
12111
+ timer = setTimeout(() => {
12112
+ ctrl.abort();
12113
+ reject(new Error("GRAPH_BODY_TIMEOUT"));
12114
+ }, bodyTimeoutMs);
12115
+ });
12116
+ try {
12117
+ return await Promise.race([bodyPromise, timeout]);
12118
+ } finally {
12119
+ if (timer) clearTimeout(timer);
12120
+ }
12121
+ }
12025
12122
  async function ensureGraphDownloaded(opts) {
12026
12123
  const fetchFn = opts.fetchFn ?? fetch;
12027
12124
  const timeoutMs = opts.timeoutMs ?? 1e4;
12125
+ const bodyTimeoutMs = opts.bodyTimeoutMs ?? 12e4;
12028
12126
  const maxRetries = opts.maxRetries ?? 3;
12029
12127
  const sleep2 = opts.sleepFn ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
12030
12128
  let targetPath;
@@ -12081,7 +12179,16 @@ async function ensureGraphDownloaded(opts) {
12081
12179
  if (!res.ok) {
12082
12180
  return { status: "error", message: `HTTP ${String(res.status)}` };
12083
12181
  }
12084
- const body = await res.text();
12182
+ let body;
12183
+ try {
12184
+ body = await readBodyBounded(res, ctrl, bodyTimeoutMs);
12185
+ } catch (err) {
12186
+ if (attempt < maxRetries) {
12187
+ await sleep2(2e3 * attempt);
12188
+ continue;
12189
+ }
12190
+ return { status: "error", message: err instanceof Error ? err.message : String(err) };
12191
+ }
12085
12192
  let parsed;
12086
12193
  try {
12087
12194
  parsed = JSON.parse(body);
@@ -12096,7 +12203,7 @@ async function ensureGraphDownloaded(opts) {
12096
12203
  return { status: "not_found", message: "EMPTY_GRAPH" };
12097
12204
  }
12098
12205
  try {
12099
- mkdirSync(dirname16(targetPath), { recursive: true });
12206
+ mkdirSync(dirname18(targetPath), { recursive: true });
12100
12207
  const tmp = `${targetPath}.${String(process.pid)}.${Math.random().toString(36).slice(2)}.tmp`;
12101
12208
  try {
12102
12209
  writeFileSync2(tmp, body, { encoding: "utf-8", mode: 384 });
@@ -12247,7 +12354,7 @@ async function handleInterview(syncId, questionId, questionText, questionContext
12247
12354
  const message = err instanceof Error ? err.message : String(err);
12248
12355
  if (message.includes("not awaiting input") || message.includes("expired")) {
12249
12356
  console.log(chalk3.dim(" Pipeline has already moved on \u2014 continuing."));
12250
- return;
12357
+ return { finished: true };
12251
12358
  }
12252
12359
  try {
12253
12360
  await new Promise((r) => setTimeout(r, 1e3));
@@ -12257,7 +12364,7 @@ async function handleInterview(syncId, questionId, questionText, questionContext
12257
12364
  });
12258
12365
  } catch {
12259
12366
  console.log(chalk3.yellow(" Could not submit answer \u2014 pipeline will continue."));
12260
- return;
12367
+ return { finished: false };
12261
12368
  }
12262
12369
  }
12263
12370
  if (action === "done") {
@@ -12268,6 +12375,7 @@ async function handleInterview(syncId, questionId, questionText, questionContext
12268
12375
  console.log(chalk3.dim(" Answer recorded."));
12269
12376
  }
12270
12377
  console.log("");
12378
+ return { finished: action === "done" || questionCounter >= MAX_QUESTIONS };
12271
12379
  }
12272
12380
 
12273
12381
  // src/lib/progress-renderer.ts
@@ -12300,6 +12408,12 @@ var ProgressRenderer = class {
12300
12408
  privacyShieldShown = false;
12301
12409
  discoveryShown = false;
12302
12410
  interviewCompleteShown = false;
12411
+ // FIX-10: the scan runs in PARALLEL with the interview, so scanProgress
12412
+ // arrives mid-interview. Hold scan rendering until the interview is
12413
+ // definitively over (setInterviewComplete) so the coffee/Code-Analysis/graph
12414
+ // block never interrupts the Q&A. Buffer the latest progress meanwhile.
12415
+ interviewComplete = false;
12416
+ pendingScanProgress = null;
12303
12417
  scanHeaderShown = false;
12304
12418
  scanSummaryShown = false;
12305
12419
  graphSubtitleShown = false;
@@ -12601,6 +12715,21 @@ var ProgressRenderer = class {
12601
12715
  }
12602
12716
  return `${stepLabel}... ${chalk4.dim(`(${overallPct}%)`)}`;
12603
12717
  }
12718
+ /**
12719
+ * FIX-10 — mark the interview definitively over. Unlocks scan-progress
12720
+ * rendering and flushes any progress buffered during the interview, so the
12721
+ * coffee → Code-Analysis → graph-ready block appears in order AFTER the last
12722
+ * question (renderScanProgress is idempotent via its *Shown latches).
12723
+ * Idempotent; safe to call from multiple completion paths.
12724
+ */
12725
+ setInterviewComplete(spinner) {
12726
+ if (this.interviewComplete) return;
12727
+ this.interviewComplete = true;
12728
+ if (this.pendingScanProgress) {
12729
+ this.renderScanProgress(this.pendingScanProgress, spinner);
12730
+ this.pendingScanProgress = null;
12731
+ }
12732
+ }
12604
12733
  update(syncResult, spinner) {
12605
12734
  if (syncResult.privacyShieldEnabled !== void 0) {
12606
12735
  this.renderPrivacyShield(syncResult.privacyShieldEnabled, spinner);
@@ -12609,7 +12738,11 @@ var ProgressRenderer = class {
12609
12738
  this.renderDiscovery(syncResult.discoveryResult, spinner);
12610
12739
  }
12611
12740
  if (syncResult.scanProgress) {
12612
- this.renderScanProgress(syncResult.scanProgress, spinner);
12741
+ if (this.interviewComplete) {
12742
+ this.renderScanProgress(syncResult.scanProgress, spinner);
12743
+ } else {
12744
+ this.pendingScanProgress = syncResult.scanProgress;
12745
+ }
12613
12746
  }
12614
12747
  if (syncResult.generationProgress?.fileStatuses && syncResult.generationProgress.fileStatuses.length > 0) {
12615
12748
  this.renderFileStatuses(syncResult.generationProgress.fileStatuses, spinner);
@@ -12669,7 +12802,7 @@ import chalk6 from "chalk";
12669
12802
 
12670
12803
  // src/lib/dep-installer.ts
12671
12804
  import { promises as fs21 } from "fs";
12672
- import { join as join46 } from "path";
12805
+ import { join as join47 } from "path";
12673
12806
  var SUPPORTED_DEP_LANGUAGES = /* @__PURE__ */ new Set([
12674
12807
  "typescript",
12675
12808
  "javascript",
@@ -12686,7 +12819,7 @@ var exists = async (p) => {
12686
12819
  }
12687
12820
  };
12688
12821
  async function fileExists16(repoRoot, name) {
12689
- return exists(join46(repoRoot, name));
12822
+ return exists(join47(repoRoot, name));
12690
12823
  }
12691
12824
  async function detectNodePackageManager(repoRoot) {
12692
12825
  if (await fileExists16(repoRoot, "pnpm-lock.yaml")) {
@@ -12764,11 +12897,11 @@ async function detectMissingDeps(repoRoot, scopedLanguages) {
12764
12897
  import { spawn as spawn11 } from "child_process";
12765
12898
  import { createWriteStream as createWriteStream3 } from "fs";
12766
12899
  import { promises as fs22 } from "fs";
12767
- import { join as join47 } from "path";
12900
+ import { join as join48 } from "path";
12768
12901
  var DEFAULT_INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
12769
12902
  async function runMissingDepInstalls(opts) {
12770
12903
  const safeRepoId = opts.repoId.replace(/[^a-zA-Z0-9_-]/g, "_");
12771
- const logPath2 = join47(getConfigDir2(), `install-log.${safeRepoId}.txt`);
12904
+ const logPath2 = join48(getConfigDir2(), `install-log.${safeRepoId}.txt`);
12772
12905
  await fs22.mkdir(getConfigDir2(), { recursive: true });
12773
12906
  const stream = opts.logStream ?? createWriteStream3(logPath2, { flags: "a" });
12774
12907
  stream.on("error", () => {
@@ -12815,7 +12948,7 @@ async function runOne2(dep, repoRoot, stream, timeoutMs) {
12815
12948
  }
12816
12949
  async function runPipFlow(repoRoot, stream, timeoutMs) {
12817
12950
  await runSimple(["python3", "-m", "venv", ".venv"], repoRoot, stream, timeoutMs);
12818
- const venvPip = process.platform === "win32" ? join47(".venv", "Scripts", "pip.exe") : join47(".venv", "bin", "pip");
12951
+ const venvPip = process.platform === "win32" ? join48(".venv", "Scripts", "pip.exe") : join48(".venv", "bin", "pip");
12819
12952
  await runSimple([venvPip, "install", "-r", "requirements.txt"], repoRoot, stream, timeoutMs);
12820
12953
  }
12821
12954
  function runSimple(cmd, repoRoot, stream, timeoutMs) {
@@ -13229,6 +13362,8 @@ async function create() {
13229
13362
  const progressRenderer = new ProgressRenderer();
13230
13363
  let depInstallShown = false;
13231
13364
  let seenAwaitingInput = false;
13365
+ let interviewComplete = false;
13366
+ let interviewQuestionsAnswered = 0;
13232
13367
  let lspWaitStartedAt = null;
13233
13368
  let lspFellBackNoticeShown = false;
13234
13369
  let depInstallPromise = null;
@@ -13269,6 +13404,8 @@ async function create() {
13269
13404
  try {
13270
13405
  while (true) {
13271
13406
  if (++pollAttempts > MAX_POLL_ATTEMPTS) {
13407
+ progressRenderer.setInterviewComplete(spinner);
13408
+ progressRenderer.finalize();
13272
13409
  spinner.fail(chalk8.red("Pipeline timed out. Check dashboard for status."));
13273
13410
  process.exitCode = 1;
13274
13411
  return;
@@ -13288,6 +13425,10 @@ async function create() {
13288
13425
  if (sigintBusy) {
13289
13426
  continue;
13290
13427
  }
13428
+ if (!interviewComplete && graphOnly) {
13429
+ interviewComplete = true;
13430
+ progressRenderer.setInterviewComplete(spinner);
13431
+ }
13291
13432
  progressRenderer.update(syncResult, spinner);
13292
13433
  if (!graphOnly && syncResult.status === "in_progress") {
13293
13434
  if (syncResult.lspWaitState === "awaiting" || syncResult.lspWaitState === "analyzing") {
@@ -13307,13 +13448,19 @@ async function create() {
13307
13448
  }
13308
13449
  if (syncResult.status === "awaiting_input" && syncResult.questionId && syncResult.questionText) {
13309
13450
  spinner.stop();
13310
- await handleInterview(
13451
+ const estimatedQ = syncResult.discoveryResult?.estimatedInterviewQuestions;
13452
+ const { finished } = await handleInterview(
13311
13453
  syncId,
13312
13454
  syncResult.questionId,
13313
13455
  syncResult.questionText,
13314
13456
  syncResult.questionContext ?? void 0,
13315
- syncResult.discoveryResult?.estimatedInterviewQuestions
13457
+ estimatedQ
13316
13458
  );
13459
+ interviewQuestionsAnswered++;
13460
+ if (!interviewComplete && (finished || estimatedQ !== void 0 && estimatedQ > 0 && interviewQuestionsAnswered >= estimatedQ)) {
13461
+ interviewComplete = true;
13462
+ progressRenderer.setInterviewComplete(spinner);
13463
+ }
13317
13464
  spinner.start("Resuming pipeline...");
13318
13465
  continue;
13319
13466
  }
@@ -13333,6 +13480,7 @@ async function create() {
13333
13480
  depInstallPromise = result.installPromise;
13334
13481
  }
13335
13482
  if (syncResult.status === "completed") {
13483
+ progressRenderer.setInterviewComplete(spinner);
13336
13484
  progressRenderer.finalize();
13337
13485
  const generatedFiles = syncResult.filesGenerated ?? [];
13338
13486
  const fileCount = generatedFiles.length;
@@ -13357,6 +13505,7 @@ async function create() {
13357
13505
  break;
13358
13506
  }
13359
13507
  if (syncResult.status === "failed") {
13508
+ progressRenderer.setInterviewComplete(spinner);
13360
13509
  progressRenderer.finalize();
13361
13510
  spinner.fail(chalk8.red(`Pipeline failed: ${syncResult.error ?? "Unknown error"}`));
13362
13511
  process.exitCode = 1;
@@ -13372,7 +13521,7 @@ async function create() {
13372
13521
  const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
13373
13522
  const files = listResult.data?.files ?? listResult.files ?? [];
13374
13523
  if (files.length > 0) {
13375
- const contextDir = join48(repoRoot, DEFAULT_CONTEXT_FOLDER);
13524
+ const contextDir = join49(repoRoot, DEFAULT_CONTEXT_FOLDER);
13376
13525
  mkdirSync2(contextDir, { recursive: true });
13377
13526
  let downloadedCount = 0;
13378
13527
  let failedCount = 0;
@@ -13386,8 +13535,8 @@ async function create() {
13386
13535
  const response = await fetch(presignedUrl);
13387
13536
  if (response.ok) {
13388
13537
  const content = await response.text();
13389
- const filePath = join48(contextDir, file.fileName);
13390
- mkdirSync2(dirname17(filePath), { recursive: true });
13538
+ const filePath = join49(contextDir, file.fileName);
13539
+ mkdirSync2(dirname19(filePath), { recursive: true });
13391
13540
  writeFileSync3(filePath, content, "utf-8");
13392
13541
  downloadedCount++;
13393
13542
  } else {
@@ -13637,7 +13786,7 @@ Files are stored on our servers (not in git). Retry when online.`
13637
13786
 
13638
13787
  // src/commands/member.ts
13639
13788
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
13640
- import { dirname as dirname18, join as join49, resolve, sep } from "path";
13789
+ import { dirname as dirname20, join as join50, resolve, sep } from "path";
13641
13790
  import chalk9 from "chalk";
13642
13791
  import ora2 from "ora";
13643
13792
  var DEFAULT_CONTEXT_FOLDER2 = "repowise-context";
@@ -13791,7 +13940,7 @@ async function member() {
13791
13940
  spinner.succeed(`Found ${chalk9.bold(files.length)} context files on server`);
13792
13941
  const { tools } = await selectAiTools();
13793
13942
  spinner.start("Downloading context files...");
13794
- const contextDir = join49(repoRoot, DEFAULT_CONTEXT_FOLDER2);
13943
+ const contextDir = join50(repoRoot, DEFAULT_CONTEXT_FOLDER2);
13795
13944
  mkdirSync3(contextDir, { recursive: true });
13796
13945
  let downloadedCount = 0;
13797
13946
  let failedCount = 0;
@@ -13812,7 +13961,7 @@ async function member() {
13812
13961
  const response = await fetch(presignedUrl);
13813
13962
  if (response.ok) {
13814
13963
  const content = await response.text();
13815
- mkdirSync3(dirname18(safePath), { recursive: true });
13964
+ mkdirSync3(dirname20(safePath), { recursive: true });
13816
13965
  writeFileSync4(safePath, content, "utf-8");
13817
13966
  downloadedCount++;
13818
13967
  } else {
@@ -13990,10 +14139,10 @@ import ora3 from "ora";
13990
14139
 
13991
14140
  // src/lib/tenant-graph-purge.ts
13992
14141
  import { promises as fs23 } from "fs";
13993
- import { homedir as homedir7 } from "os";
13994
- import { join as join50 } from "path";
13995
- async function purgeForeignGraphs(validRepoIds, home = homedir7()) {
13996
- const graphsDir = join50(home, ".repowise", "graphs");
14142
+ import { homedir as homedir8 } from "os";
14143
+ import { join as join51 } from "path";
14144
+ async function purgeForeignGraphs(validRepoIds, home = homedir8()) {
14145
+ const graphsDir = join51(home, ".repowise", "graphs");
13997
14146
  const result = { kept: [], removed: [] };
13998
14147
  let entries;
13999
14148
  try {
@@ -14011,7 +14160,7 @@ async function purgeForeignGraphs(validRepoIds, home = homedir7()) {
14011
14160
  result.kept.push(entry);
14012
14161
  continue;
14013
14162
  }
14014
- const path = join50(graphsDir, entry);
14163
+ const path = join51(graphsDir, entry);
14015
14164
  try {
14016
14165
  const stat8 = await fs23.lstat(path);
14017
14166
  if (stat8.isSymbolicLink()) {
@@ -14104,11 +14253,11 @@ async function logout() {
14104
14253
 
14105
14254
  // src/commands/status.ts
14106
14255
  import { readFile as readFile16 } from "fs/promises";
14107
- import { basename as basename4, join as join51 } from "path";
14256
+ import { basename as basename4, join as join52 } from "path";
14108
14257
  async function status() {
14109
14258
  const configDir = getConfigDir2();
14110
- const STATE_PATH = join51(configDir, "listener-state.json");
14111
- const CONFIG_PATH = join51(configDir, "config.json");
14259
+ const STATE_PATH = join52(configDir, "listener-state.json");
14260
+ const CONFIG_PATH = join52(configDir, "config.json");
14112
14261
  let state = null;
14113
14262
  try {
14114
14263
  const data = await readFile16(STATE_PATH, "utf-8");
@@ -14156,7 +14305,7 @@ async function status() {
14156
14305
 
14157
14306
  // src/commands/sync.ts
14158
14307
  import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
14159
- import { dirname as dirname19, join as join52 } from "path";
14308
+ import { dirname as dirname21, join as join53 } from "path";
14160
14309
  import chalk12 from "chalk";
14161
14310
  import ora4 from "ora";
14162
14311
  var POLL_INTERVAL_MS2 = 3e3;
@@ -14305,7 +14454,7 @@ async function sync() {
14305
14454
  const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
14306
14455
  const files = listResult.data?.files ?? listResult.files ?? [];
14307
14456
  if (files.length > 0) {
14308
- const contextDir = join52(repoRoot, DEFAULT_CONTEXT_FOLDER3);
14457
+ const contextDir = join53(repoRoot, DEFAULT_CONTEXT_FOLDER3);
14309
14458
  mkdirSync4(contextDir, { recursive: true });
14310
14459
  let downloadedCount = 0;
14311
14460
  let failedCount = 0;
@@ -14319,8 +14468,8 @@ async function sync() {
14319
14468
  const response = await fetch(presignedUrl);
14320
14469
  if (response.ok) {
14321
14470
  const content = await response.text();
14322
- const filePath = join52(contextDir, file.fileName);
14323
- mkdirSync4(dirname19(filePath), { recursive: true });
14471
+ const filePath = join53(contextDir, file.fileName);
14472
+ mkdirSync4(dirname21(filePath), { recursive: true });
14324
14473
  writeFileSync5(filePath, content, "utf-8");
14325
14474
  downloadedCount++;
14326
14475
  } else {
@@ -14575,7 +14724,7 @@ async function config() {
14575
14724
  // src/commands/mcp-log.ts
14576
14725
  import { createDecipheriv as createDecipheriv2 } from "crypto";
14577
14726
  import { mkdir as mkdir19, readFile as readFile17, stat as stat7, writeFile as writeFile18 } from "fs/promises";
14578
- import { dirname as dirname20, join as join53 } from "path";
14727
+ import { dirname as dirname22, join as join54 } from "path";
14579
14728
  var FLAG_FILE = "mcp-log.flag";
14580
14729
  var LOG_FILE = "mcp-log.jsonl.enc";
14581
14730
  var KEY_FILE = "mcp-log.key";
@@ -14583,14 +14732,14 @@ var ENDPOINT_FILE = "listener.endpoint";
14583
14732
  var IV_BYTES2 = 12;
14584
14733
  var TAG_BYTES2 = 16;
14585
14734
  function flagPath() {
14586
- return join53(getConfigDir2(), FLAG_FILE);
14735
+ return join54(getConfigDir2(), FLAG_FILE);
14587
14736
  }
14588
14737
  function logPath() {
14589
- return join53(getConfigDir2(), LOG_FILE);
14738
+ return join54(getConfigDir2(), LOG_FILE);
14590
14739
  }
14591
14740
  async function writeFlag(flag) {
14592
14741
  const path = flagPath();
14593
- await mkdir19(dirname20(path), { recursive: true });
14742
+ await mkdir19(dirname22(path), { recursive: true });
14594
14743
  await writeFile18(path, JSON.stringify(flag, null, 2), { encoding: "utf-8", mode: 384 });
14595
14744
  }
14596
14745
  async function mcpLogOn() {
@@ -14627,14 +14776,14 @@ async function trySendConsentToServer() {
14627
14776
  let apiUrl = null;
14628
14777
  let token = null;
14629
14778
  try {
14630
- const body = await readFile17(join53(getConfigDir2(), "config.json"), "utf-8");
14779
+ const body = await readFile17(join54(getConfigDir2(), "config.json"), "utf-8");
14631
14780
  const parsed = JSON.parse(body);
14632
14781
  apiUrl = parsed.repos?.find((r) => Boolean(r.apiUrl))?.apiUrl ?? parsed.defaultApiUrl ?? null;
14633
14782
  } catch {
14634
14783
  return false;
14635
14784
  }
14636
14785
  try {
14637
- const body = await readFile17(join53(getConfigDir2(), "credentials.json"), "utf-8");
14786
+ const body = await readFile17(join54(getConfigDir2(), "credentials.json"), "utf-8");
14638
14787
  const parsed = JSON.parse(body);
14639
14788
  token = parsed.idToken ?? null;
14640
14789
  } catch {
@@ -14704,7 +14853,7 @@ async function mcpLogStatus() {
14704
14853
  process.stderr.write("Log size: no file yet\n");
14705
14854
  }
14706
14855
  try {
14707
- const endpointBody = await readFile17(join53(getConfigDir2(), ENDPOINT_FILE), "utf-8");
14856
+ const endpointBody = await readFile17(join54(getConfigDir2(), ENDPOINT_FILE), "utf-8");
14708
14857
  const match = /endpoint=([^\n]+)/.exec(endpointBody);
14709
14858
  process.stderr.write(`MCP endpoint: ${match?.[1] ?? "(malformed endpoint file)"}
14710
14859
  `);
@@ -14735,7 +14884,7 @@ async function mcpLogViewingFlags(flags = {}) {
14735
14884
  const key = await readKey();
14736
14885
  if (!key) {
14737
14886
  process.stderr.write(
14738
- `No encryption key at ${join53(getConfigDir2(), KEY_FILE)} \u2014 listener may not have started yet.
14887
+ `No encryption key at ${join54(getConfigDir2(), KEY_FILE)} \u2014 listener may not have started yet.
14739
14888
  `
14740
14889
  );
14741
14890
  return;
@@ -14811,7 +14960,7 @@ async function mcpLogViewingFlags(flags = {}) {
14811
14960
  }
14812
14961
  async function readKey() {
14813
14962
  try {
14814
- const body = await readFile17(join53(getConfigDir2(), KEY_FILE), "utf-8");
14963
+ const body = await readFile17(join54(getConfigDir2(), KEY_FILE), "utf-8");
14815
14964
  const parsed = Buffer.from(body.trim(), "base64");
14816
14965
  if (parsed.length !== 32) return null;
14817
14966
  return parsed;
@@ -14855,7 +15004,7 @@ import chalk14 from "chalk";
14855
15004
 
14856
15005
  // src/lib/graph-loader.ts
14857
15006
  import { promises as fs24 } from "fs";
14858
- import { join as join54, resolve as resolve2 } from "path";
15007
+ import { join as join55, resolve as resolve2 } from "path";
14859
15008
  import { gunzipSync } from "zlib";
14860
15009
  var RELATIVE_GRAPH_PATH = "repowise-context/.meta/dependency-graph.json";
14861
15010
  var GZIPPED_GRAPH_PATH = "repowise-context/.meta/dependency-graph.json.gz";
@@ -14872,8 +15021,8 @@ var GraphNotFoundError = class extends Error {
14872
15021
  var cache = /* @__PURE__ */ new Map();
14873
15022
  async function loadGraph(repoRoot = process.cwd()) {
14874
15023
  const root = resolve2(repoRoot);
14875
- const gzPath = join54(root, GZIPPED_GRAPH_PATH);
14876
- const plainPath = join54(root, RELATIVE_GRAPH_PATH);
15024
+ const gzPath = join55(root, GZIPPED_GRAPH_PATH);
15025
+ const plainPath = join55(root, RELATIVE_GRAPH_PATH);
14877
15026
  const cached = cache.get(root);
14878
15027
  if (cached) {
14879
15028
  return { graph: cached, path: plainPath, bytes: 0, parseMs: 0, fromCache: true };
@@ -15288,13 +15437,13 @@ function registerQueryCommand(program2) {
15288
15437
 
15289
15438
  // src/commands/uninstall.ts
15290
15439
  import { promises as fs28 } from "fs";
15291
- import { homedir as homedir9 } from "os";
15292
- import { join as join58 } from "path";
15440
+ import { homedir as homedir10 } from "os";
15441
+ import { join as join59 } from "path";
15293
15442
  import chalk15 from "chalk";
15294
15443
 
15295
15444
  // src/lib/cleanup/marker-blocks.ts
15296
15445
  import { promises as fs25 } from "fs";
15297
- import { join as join55 } from "path";
15446
+ import { join as join56 } from "path";
15298
15447
  var MARKER_START = "<!-- repowise-start -->";
15299
15448
  var MARKER_END = "<!-- repowise-end -->";
15300
15449
  var CONTEXT_FILES = [
@@ -15334,7 +15483,7 @@ async function stripMarkerBlock(filePath) {
15334
15483
  async function stripAllMarkerBlocks(repoRoot) {
15335
15484
  const out = [];
15336
15485
  for (const relative of CONTEXT_FILES) {
15337
- const full = join55(repoRoot, relative);
15486
+ const full = join56(repoRoot, relative);
15338
15487
  const result = await stripMarkerBlock(full).catch((err) => ({
15339
15488
  path: full,
15340
15489
  status: "untouched",
@@ -15347,18 +15496,18 @@ async function stripAllMarkerBlocks(repoRoot) {
15347
15496
 
15348
15497
  // src/lib/cleanup/mcp-configs.ts
15349
15498
  import { promises as fs26 } from "fs";
15350
- import { join as join56 } from "path";
15499
+ import { join as join57 } from "path";
15351
15500
  function mcpConfigPaths(repoRoot, home) {
15352
15501
  return [
15353
- join56(repoRoot, ".mcp.json"),
15354
- join56(repoRoot, ".cursor", "mcp.json"),
15355
- join56(repoRoot, ".vscode", "mcp.json"),
15356
- join56(repoRoot, ".roo", "mcp.json"),
15357
- join56(home, ".cline", "mcp.json"),
15358
- join56(home, ".codeium", "windsurf", "mcp_config.json"),
15359
- join56(home, ".gemini", "settings.json"),
15360
- join56(home, ".codex", "mcp.json"),
15361
- join56(home, ".roo", "mcp.json")
15502
+ join57(repoRoot, ".mcp.json"),
15503
+ join57(repoRoot, ".cursor", "mcp.json"),
15504
+ join57(repoRoot, ".vscode", "mcp.json"),
15505
+ join57(repoRoot, ".roo", "mcp.json"),
15506
+ join57(home, ".cline", "mcp.json"),
15507
+ join57(home, ".codeium", "windsurf", "mcp_config.json"),
15508
+ join57(home, ".gemini", "settings.json"),
15509
+ join57(home, ".codex", "mcp.json"),
15510
+ join57(home, ".roo", "mcp.json")
15362
15511
  ];
15363
15512
  }
15364
15513
  async function removeRepowiseFromConfig(path, serverName) {
@@ -15398,11 +15547,11 @@ async function removeAllMcpEntries(repoRoot, home, repoId) {
15398
15547
 
15399
15548
  // src/lib/cleanup/local-state.ts
15400
15549
  import { promises as fs27 } from "fs";
15401
- import { homedir as homedir8 } from "os";
15402
- import { join as join57, resolve as resolve3 } from "path";
15550
+ import { homedir as homedir9 } from "os";
15551
+ import { join as join58, resolve as resolve3 } from "path";
15403
15552
  async function clearLocalState(homeOverride) {
15404
- const home = homeOverride ?? homedir8();
15405
- const target = resolve3(join57(home, ".repowise"));
15553
+ const home = homeOverride ?? homedir9();
15554
+ const target = resolve3(join58(home, ".repowise"));
15406
15555
  if (target === resolve3(home) || !target.startsWith(resolve3(home))) {
15407
15556
  return { path: target, status: "error", error: "refused: not under home" };
15408
15557
  }
@@ -15437,7 +15586,7 @@ async function stopAndUninstallService(uninstaller) {
15437
15586
  // src/commands/uninstall.ts
15438
15587
  async function uninstall2(opts = {}) {
15439
15588
  const tier = opts.tier ?? "uninstall";
15440
- const home = opts.home ?? homedir9();
15589
+ const home = opts.home ?? homedir10();
15441
15590
  const repoRoot = opts.repoRoot ?? process.cwd();
15442
15591
  const loadRepoIds = opts.loadRepoIds ?? defaultLoadRepoIds;
15443
15592
  const report = { tier, removed: [], preserved: [], skipped: [] };
@@ -15446,7 +15595,7 @@ async function uninstall2(opts = {}) {
15446
15595
  else if (svc.error) report.skipped.push({ path: "listener service", reason: svc.error });
15447
15596
  if (tier === "stop") return report;
15448
15597
  try {
15449
- await fs28.unlink(join58(home, ".repowise", "credentials.json"));
15598
+ await fs28.unlink(join59(home, ".repowise", "credentials.json"));
15450
15599
  report.removed.push("credentials");
15451
15600
  } catch (err) {
15452
15601
  if (err.code !== "ENOENT") {
@@ -15485,7 +15634,7 @@ async function uninstall2(opts = {}) {
15485
15634
  }
15486
15635
  async function defaultLoadRepoIds(home) {
15487
15636
  try {
15488
- const raw = await fs28.readFile(join58(home, ".repowise", "config.json"), "utf-8");
15637
+ const raw = await fs28.readFile(join59(home, ".repowise", "config.json"), "utf-8");
15489
15638
  const parsed = JSON.parse(raw);
15490
15639
  return (parsed.repos ?? []).map((r) => r.repoId);
15491
15640
  } catch {
@@ -15534,10 +15683,10 @@ Done \u2014 ${report.removed.length} removed, ${report.skipped.length} skipped.
15534
15683
  init_config_dir();
15535
15684
  import { promises as fs29 } from "fs";
15536
15685
  import { createInterface as createInterface2 } from "readline";
15537
- import { join as join59 } from "path";
15686
+ import { join as join60 } from "path";
15538
15687
  var DEFAULT_MAX = 200 * 1024;
15539
15688
  async function mcpShim(opts) {
15540
- const endpointPath = opts.endpointFile ?? join59(getConfigDir(), "listener.endpoint");
15689
+ const endpointPath = opts.endpointFile ?? join60(getConfigDir(), "listener.endpoint");
15541
15690
  const stdin = opts.stdin ?? process.stdin;
15542
15691
  const stdout = opts.stdout ?? process.stdout;
15543
15692
  const stderr = opts.stderr ?? process.stderr;
@@ -16285,8 +16434,8 @@ async function lspOn() {
16285
16434
 
16286
16435
  // bin/repowise.ts
16287
16436
  var __filename = fileURLToPath4(import.meta.url);
16288
- var __dirname = dirname21(__filename);
16289
- var pkg = JSON.parse(readFileSync3(join60(__dirname, "..", "..", "package.json"), "utf-8"));
16437
+ var __dirname = dirname23(__filename);
16438
+ var pkg = JSON.parse(readFileSync3(join61(__dirname, "..", "..", "package.json"), "utf-8"));
16290
16439
  var program = new Command();
16291
16440
  program.name(getPackageName()).description("AI-optimized codebase context generator").version(pkg.version).hook("preAction", async () => {
16292
16441
  await showWelcome(pkg.version);