repowisestage 0.0.70 → 0.0.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/repowise.js +1737 -666
- package/package.json +1 -1
package/dist/bin/repowise.js
CHANGED
|
@@ -508,19 +508,19 @@ var init_src = __esm({
|
|
|
508
508
|
// ../listener/dist/process-manager.js
|
|
509
509
|
import { spawn } from "child_process";
|
|
510
510
|
import { openSync, closeSync } from "fs";
|
|
511
|
-
import { readFile as
|
|
511
|
+
import { readFile as readFile7, writeFile as writeFile8, mkdir as mkdir7, unlink as unlink6 } from "fs/promises";
|
|
512
512
|
import { homedir as homedir2 } from "os";
|
|
513
|
-
import { join as
|
|
513
|
+
import { join as join11 } from "path";
|
|
514
514
|
import { createRequire } from "module";
|
|
515
515
|
import { fileURLToPath } from "url";
|
|
516
516
|
function repowiseDir() {
|
|
517
517
|
return getConfigDir();
|
|
518
518
|
}
|
|
519
519
|
function pidPath() {
|
|
520
|
-
return
|
|
520
|
+
return join11(repowiseDir(), "listener.pid");
|
|
521
521
|
}
|
|
522
522
|
function logDirPath() {
|
|
523
|
-
return
|
|
523
|
+
return join11(repowiseDir(), "logs");
|
|
524
524
|
}
|
|
525
525
|
function resolveListenerCommand() {
|
|
526
526
|
try {
|
|
@@ -534,7 +534,7 @@ function resolveListenerCommand() {
|
|
|
534
534
|
}
|
|
535
535
|
async function readPid() {
|
|
536
536
|
try {
|
|
537
|
-
const content = await
|
|
537
|
+
const content = await readFile7(pidPath(), "utf-8");
|
|
538
538
|
const pid = parseInt(content.trim(), 10);
|
|
539
539
|
return Number.isNaN(pid) ? null : pid;
|
|
540
540
|
} catch (err) {
|
|
@@ -560,8 +560,8 @@ async function startBackground() {
|
|
|
560
560
|
const logDir2 = logDirPath();
|
|
561
561
|
await mkdir7(logDir2, { recursive: true });
|
|
562
562
|
const cmd = resolveListenerCommand();
|
|
563
|
-
const stdoutFd = openSync(
|
|
564
|
-
const stderrFd = openSync(
|
|
563
|
+
const stdoutFd = openSync(join11(logDir2, "listener-stdout.log"), "a");
|
|
564
|
+
const stderrFd = openSync(join11(logDir2, "listener-stderr.log"), "a");
|
|
565
565
|
const child = spawn(process.execPath, [cmd.script, ...cmd.args], {
|
|
566
566
|
detached: true,
|
|
567
567
|
stdio: ["ignore", stdoutFd, stderrFd],
|
|
@@ -574,7 +574,7 @@ async function startBackground() {
|
|
|
574
574
|
const pid = child.pid;
|
|
575
575
|
if (!pid)
|
|
576
576
|
throw new Error("Failed to spawn listener process");
|
|
577
|
-
await
|
|
577
|
+
await writeFile8(pidPath(), String(pid));
|
|
578
578
|
return pid;
|
|
579
579
|
}
|
|
580
580
|
async function stopProcess() {
|
|
@@ -609,7 +609,7 @@ async function isRunning() {
|
|
|
609
609
|
}
|
|
610
610
|
async function removePidFile() {
|
|
611
611
|
try {
|
|
612
|
-
await
|
|
612
|
+
await unlink6(pidPath());
|
|
613
613
|
} catch {
|
|
614
614
|
}
|
|
615
615
|
}
|
|
@@ -1170,12 +1170,12 @@ import { createGunzip } from "zlib";
|
|
|
1170
1170
|
import { spawn as spawn3 } from "child_process";
|
|
1171
1171
|
import { pipeline } from "stream/promises";
|
|
1172
1172
|
import { createHash as createHash3 } from "crypto";
|
|
1173
|
-
import { join as
|
|
1173
|
+
import { join as join14, dirname as dirname5 } from "path";
|
|
1174
1174
|
function getNativeLspDir() {
|
|
1175
|
-
return
|
|
1175
|
+
return join14(getLspInstallDir(), "native");
|
|
1176
1176
|
}
|
|
1177
1177
|
function getNativeLspBinDir() {
|
|
1178
|
-
return
|
|
1178
|
+
return join14(getNativeLspDir(), "bin");
|
|
1179
1179
|
}
|
|
1180
1180
|
async function ensureNativeLspInstalled(lspKey) {
|
|
1181
1181
|
const entry = NATIVE_LSP_VERSIONS[lspKey];
|
|
@@ -1201,15 +1201,15 @@ async function ensureNativeLspInstalled(lspKey) {
|
|
|
1201
1201
|
}
|
|
1202
1202
|
}
|
|
1203
1203
|
const usesWrapper = Boolean(asset.launcherJarGlob && asset.wrapperBinaryName);
|
|
1204
|
-
const versionDir =
|
|
1204
|
+
const versionDir = join14(getNativeLspDir(), lspKey, entry.version);
|
|
1205
1205
|
if (usesWrapper && asset.launcherJarGlob && asset.wrapperBinaryName) {
|
|
1206
1206
|
const jarPath = await resolveGlobInsideDir(versionDir, asset.launcherJarGlob);
|
|
1207
|
-
const shimPath =
|
|
1207
|
+
const shimPath = join14(getNativeLspBinDir(), asset.wrapperBinaryName);
|
|
1208
1208
|
if (jarPath && await pathExists(shimPath)) {
|
|
1209
1209
|
return { installed: false, alreadyPresent: true, binaryPath: shimPath };
|
|
1210
1210
|
}
|
|
1211
1211
|
} else {
|
|
1212
|
-
const installedBinary =
|
|
1212
|
+
const installedBinary = join14(versionDir, asset.binaryPath);
|
|
1213
1213
|
if (await pathExists(installedBinary)) {
|
|
1214
1214
|
await ensureSymlinkInBin(lspKey, installedBinary, asset.binaryPath);
|
|
1215
1215
|
return { installed: false, alreadyPresent: true, binaryPath: installedBinary };
|
|
@@ -1217,7 +1217,7 @@ async function ensureNativeLspInstalled(lspKey) {
|
|
|
1217
1217
|
}
|
|
1218
1218
|
try {
|
|
1219
1219
|
await fs.mkdir(versionDir, { recursive: true });
|
|
1220
|
-
const tmpDownload =
|
|
1220
|
+
const tmpDownload = join14(versionDir, `.${asset.filename}.download`);
|
|
1221
1221
|
const downloadUrl = entry.source.kind === "github-release" ? `https://github.com/${entry.source.repo}/releases/download/${entry.version}/${asset.filename}` : asset.url ?? "";
|
|
1222
1222
|
if (!downloadUrl) {
|
|
1223
1223
|
return {
|
|
@@ -1259,7 +1259,7 @@ async function ensureNativeLspInstalled(lspKey) {
|
|
|
1259
1259
|
await pruneStaleVersions(lspKey, entry.version);
|
|
1260
1260
|
return { installed: true, alreadyPresent: false, binaryPath: shimPath };
|
|
1261
1261
|
}
|
|
1262
|
-
const installedBinary =
|
|
1262
|
+
const installedBinary = join14(versionDir, asset.binaryPath);
|
|
1263
1263
|
if (!await pathExists(installedBinary)) {
|
|
1264
1264
|
return {
|
|
1265
1265
|
installed: false,
|
|
@@ -1283,7 +1283,7 @@ async function resolveGlobInsideDir(versionDir, glob) {
|
|
|
1283
1283
|
const parts = glob.split("/");
|
|
1284
1284
|
let dir = versionDir;
|
|
1285
1285
|
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
1286
|
-
dir =
|
|
1286
|
+
dir = join14(dir, parts[i]);
|
|
1287
1287
|
}
|
|
1288
1288
|
const pattern = parts[parts.length - 1];
|
|
1289
1289
|
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
@@ -1296,10 +1296,10 @@ async function resolveGlobInsideDir(versionDir, glob) {
|
|
|
1296
1296
|
}
|
|
1297
1297
|
const matches = entries.filter((e) => re.test(e)).sort();
|
|
1298
1298
|
const last = matches[matches.length - 1];
|
|
1299
|
-
return last ?
|
|
1299
|
+
return last ? join14(dir, last) : null;
|
|
1300
1300
|
}
|
|
1301
1301
|
async function pruneStaleVersions(lspKey, currentVersion) {
|
|
1302
|
-
const lspDir =
|
|
1302
|
+
const lspDir = join14(getNativeLspDir(), lspKey);
|
|
1303
1303
|
let entries;
|
|
1304
1304
|
try {
|
|
1305
1305
|
entries = await fs.readdir(lspDir);
|
|
@@ -1308,7 +1308,7 @@ async function pruneStaleVersions(lspKey, currentVersion) {
|
|
|
1308
1308
|
}
|
|
1309
1309
|
await Promise.all(entries.filter((name) => name !== currentVersion).map(async (name) => {
|
|
1310
1310
|
try {
|
|
1311
|
-
await fs.rm(
|
|
1311
|
+
await fs.rm(join14(lspDir, name), { recursive: true, force: true });
|
|
1312
1312
|
} catch {
|
|
1313
1313
|
}
|
|
1314
1314
|
}));
|
|
@@ -1322,9 +1322,9 @@ function cmdQuote(value) {
|
|
|
1322
1322
|
async function writeJdtlsWrapper(input) {
|
|
1323
1323
|
const binDir = getNativeLspBinDir();
|
|
1324
1324
|
await fs.mkdir(binDir, { recursive: true });
|
|
1325
|
-
const shimPath =
|
|
1325
|
+
const shimPath = join14(binDir, input.binaryName);
|
|
1326
1326
|
const platformCfgDir = process.platform === "darwin" ? "config_mac" : process.platform === "win32" ? "config_win" : "config_linux";
|
|
1327
|
-
const workspaceBase =
|
|
1327
|
+
const workspaceBase = join14(input.versionDir, "workspaces");
|
|
1328
1328
|
const qLauncher = shQuote(input.launcherJarPath);
|
|
1329
1329
|
const qConfig = shQuote(`${input.versionDir}/${platformCfgDir}`);
|
|
1330
1330
|
const qWorkspaceBase = shQuote(workspaceBase);
|
|
@@ -1384,7 +1384,7 @@ async function ensureSymlinkInBin(lspKey, target, sourceAssetPath) {
|
|
|
1384
1384
|
await fs.mkdir(binDir, { recursive: true });
|
|
1385
1385
|
const winExtMatch = process.platform === "win32" && sourceAssetPath ? /\.(exe|bat|cmd|ps1)$/i.exec(sourceAssetPath) : null;
|
|
1386
1386
|
const linkName = winExtMatch ? `${lspKey}${winExtMatch[0].toLowerCase()}` : lspKey;
|
|
1387
|
-
const linkPath =
|
|
1387
|
+
const linkPath = join14(binDir, linkName);
|
|
1388
1388
|
try {
|
|
1389
1389
|
await fs.unlink(linkPath);
|
|
1390
1390
|
} catch {
|
|
@@ -1433,12 +1433,12 @@ async function extractAsset(source, destDir, asset) {
|
|
|
1433
1433
|
return;
|
|
1434
1434
|
}
|
|
1435
1435
|
if (filename.endsWith(".gz")) {
|
|
1436
|
-
const outPath =
|
|
1436
|
+
const outPath = join14(destDir, asset.binaryPath);
|
|
1437
1437
|
await fs.mkdir(dirname5(outPath), { recursive: true });
|
|
1438
1438
|
await pipeline((await fs.open(source)).createReadStream(), createGunzip(), createWriteStream(outPath));
|
|
1439
1439
|
return;
|
|
1440
1440
|
}
|
|
1441
|
-
await fs.copyFile(source,
|
|
1441
|
+
await fs.copyFile(source, join14(destDir, asset.binaryPath));
|
|
1442
1442
|
}
|
|
1443
1443
|
async function runExternal(cmd, args) {
|
|
1444
1444
|
return new Promise((resolve5, reject) => {
|
|
@@ -1476,9 +1476,9 @@ var init_native_installer = __esm({
|
|
|
1476
1476
|
// ../listener/dist/lsp/coursier-installer.js
|
|
1477
1477
|
import { promises as fs2 } from "fs";
|
|
1478
1478
|
import { spawn as spawn4 } from "child_process";
|
|
1479
|
-
import { join as
|
|
1479
|
+
import { join as join15 } from "path";
|
|
1480
1480
|
function getCoursierBinDir() {
|
|
1481
|
-
return
|
|
1481
|
+
return join15(getLspInstallDir(), "coursier-bin");
|
|
1482
1482
|
}
|
|
1483
1483
|
async function ensureCoursierLspInstalled(key) {
|
|
1484
1484
|
const entry = COURSIER_LSPS[key];
|
|
@@ -1486,8 +1486,8 @@ async function ensureCoursierLspInstalled(key) {
|
|
|
1486
1486
|
return { installed: false, alreadyPresent: false, skipped: "no-coursier-entry" };
|
|
1487
1487
|
}
|
|
1488
1488
|
const binDir = getCoursierBinDir();
|
|
1489
|
-
const binaryPath =
|
|
1490
|
-
const binaryPathExe =
|
|
1489
|
+
const binaryPath = join15(binDir, entry.appName);
|
|
1490
|
+
const binaryPathExe = join15(binDir, `${entry.appName}.bat`);
|
|
1491
1491
|
if (await pathExists2(binaryPath) || await pathExists2(binaryPathExe)) {
|
|
1492
1492
|
return { installed: false, alreadyPresent: true, binaryPath };
|
|
1493
1493
|
}
|
|
@@ -1500,8 +1500,8 @@ async function ensureCoursierLspInstalled(key) {
|
|
|
1500
1500
|
error: csBootstrap.error ?? csBootstrap.skipped
|
|
1501
1501
|
};
|
|
1502
1502
|
}
|
|
1503
|
-
const csPath =
|
|
1504
|
-
const csPathExe =
|
|
1503
|
+
const csPath = join15(getNativeLspBinDir(), "coursier");
|
|
1504
|
+
const csPathExe = join15(getNativeLspBinDir(), "coursier.exe");
|
|
1505
1505
|
const cs = await pathExists2(csPath) ? csPath : await pathExists2(csPathExe) ? csPathExe : null;
|
|
1506
1506
|
if (!cs) {
|
|
1507
1507
|
return {
|
|
@@ -1567,7 +1567,7 @@ var init_coursier_installer = __esm({
|
|
|
1567
1567
|
import { spawn as spawn5 } from "child_process";
|
|
1568
1568
|
import { promises as fs3 } from "fs";
|
|
1569
1569
|
import { homedir as homedir3 } from "os";
|
|
1570
|
-
import { join as
|
|
1570
|
+
import { join as join16 } from "path";
|
|
1571
1571
|
async function ensureToolchainLspInstalled(key) {
|
|
1572
1572
|
const entry = TOOLCHAIN_LSPS[key];
|
|
1573
1573
|
if (!entry) {
|
|
@@ -1615,7 +1615,7 @@ async function ensureToolchainLspInstalled(key) {
|
|
|
1615
1615
|
error: err instanceof Error ? err.message : String(err)
|
|
1616
1616
|
};
|
|
1617
1617
|
}
|
|
1618
|
-
const binaryPath =
|
|
1618
|
+
const binaryPath = join16(binDir, entry.binaryName);
|
|
1619
1619
|
if (!await pathExists3(binaryPath)) {
|
|
1620
1620
|
return {
|
|
1621
1621
|
installed: false,
|
|
@@ -1632,14 +1632,14 @@ async function resolveToolchainBinDir(entry) {
|
|
|
1632
1632
|
}
|
|
1633
1633
|
if (entry.toolchainBinDir) {
|
|
1634
1634
|
if (entry.toolchain === "go") {
|
|
1635
|
-
const gopath = process.env["GOPATH"] ??
|
|
1636
|
-
return
|
|
1635
|
+
const gopath = process.env["GOPATH"] ?? join16(homedir3(), "go");
|
|
1636
|
+
return join16(gopath, "bin");
|
|
1637
1637
|
}
|
|
1638
|
-
return
|
|
1638
|
+
return join16(homedir3(), entry.toolchainBinDir);
|
|
1639
1639
|
}
|
|
1640
1640
|
if (entry.toolchain === "gem") {
|
|
1641
1641
|
const userdir = await captureStdout("gem", ["env", "userdir"]);
|
|
1642
|
-
return
|
|
1642
|
+
return join16(userdir.trim(), "bin");
|
|
1643
1643
|
}
|
|
1644
1644
|
throw new Error(`no bin-dir resolution for toolchain ${entry.toolchain}`);
|
|
1645
1645
|
}
|
|
@@ -1649,10 +1649,10 @@ function getToolchainBinDirs() {
|
|
|
1649
1649
|
if (entry.bundled || !entry.toolchain || !entry.toolchainBinDir)
|
|
1650
1650
|
continue;
|
|
1651
1651
|
if (entry.toolchain === "go") {
|
|
1652
|
-
const gopath = process.env["GOPATH"] ??
|
|
1653
|
-
dirs.add(
|
|
1652
|
+
const gopath = process.env["GOPATH"] ?? join16(homedir3(), "go");
|
|
1653
|
+
dirs.add(join16(gopath, "bin"));
|
|
1654
1654
|
} else {
|
|
1655
|
-
dirs.add(
|
|
1655
|
+
dirs.add(join16(homedir3(), entry.toolchainBinDir));
|
|
1656
1656
|
}
|
|
1657
1657
|
}
|
|
1658
1658
|
return [...dirs];
|
|
@@ -1720,20 +1720,20 @@ var init_toolchain_installer = __esm({
|
|
|
1720
1720
|
|
|
1721
1721
|
// ../listener/dist/lsp/installer.js
|
|
1722
1722
|
import { promises as fs4 } from "fs";
|
|
1723
|
-
import { join as
|
|
1723
|
+
import { join as join17, dirname as dirname6 } from "path";
|
|
1724
1724
|
import { spawn as spawn6 } from "child_process";
|
|
1725
|
-
import
|
|
1725
|
+
import lockfile4 from "proper-lockfile";
|
|
1726
1726
|
function getLspInstallDir() {
|
|
1727
|
-
return
|
|
1727
|
+
return join17(getConfigDir(), "lsp-servers");
|
|
1728
1728
|
}
|
|
1729
1729
|
function getLspBinDir() {
|
|
1730
|
-
return
|
|
1730
|
+
return join17(getLspInstallDir(), "node_modules", ".bin");
|
|
1731
1731
|
}
|
|
1732
1732
|
async function ensureNpmLspInstalled(config2) {
|
|
1733
1733
|
if (!config2.npmPackage)
|
|
1734
1734
|
return { installed: false, skipped: "no-npm-package" };
|
|
1735
1735
|
const installDir = getLspInstallDir();
|
|
1736
|
-
const binPath =
|
|
1736
|
+
const binPath = join17(installDir, "node_modules", ".bin", config2.command);
|
|
1737
1737
|
if (await pathExists4(binPath)) {
|
|
1738
1738
|
return { installed: false, skipped: "already-present" };
|
|
1739
1739
|
}
|
|
@@ -1741,7 +1741,7 @@ async function ensureNpmLspInstalled(config2) {
|
|
|
1741
1741
|
await fs4.mkdir(installDir, { recursive: true });
|
|
1742
1742
|
let release = null;
|
|
1743
1743
|
try {
|
|
1744
|
-
release = await
|
|
1744
|
+
release = await lockfile4.lock(installDir, {
|
|
1745
1745
|
stale: 18e4,
|
|
1746
1746
|
retries: { retries: 20, factor: 1.5, minTimeout: 500, maxTimeout: 5e3 },
|
|
1747
1747
|
realpath: false
|
|
@@ -1755,7 +1755,7 @@ async function ensureNpmLspInstalled(config2) {
|
|
|
1755
1755
|
if (await pathExists4(binPath)) {
|
|
1756
1756
|
return { installed: false, skipped: "already-present" };
|
|
1757
1757
|
}
|
|
1758
|
-
const pkgJsonPath =
|
|
1758
|
+
const pkgJsonPath = join17(installDir, "package.json");
|
|
1759
1759
|
if (!await pathExists4(pkgJsonPath)) {
|
|
1760
1760
|
await fs4.writeFile(pkgJsonPath, JSON.stringify({ name: "repowise-lsp-servers", private: true, version: "0.0.0" }, null, 2), "utf-8");
|
|
1761
1761
|
}
|
|
@@ -1784,7 +1784,7 @@ async function ensureNpmLspInstalled(config2) {
|
|
|
1784
1784
|
}
|
|
1785
1785
|
async function resolveNpmCommand() {
|
|
1786
1786
|
const nodeDir = dirname6(process.execPath);
|
|
1787
|
-
for (const candidate of [
|
|
1787
|
+
for (const candidate of [join17(nodeDir, "npm"), join17(nodeDir, "npm.cmd")]) {
|
|
1788
1788
|
try {
|
|
1789
1789
|
await fs4.access(candidate);
|
|
1790
1790
|
return candidate;
|
|
@@ -1875,7 +1875,7 @@ async function detectRepoLanguages(repoRoot) {
|
|
|
1875
1875
|
continue;
|
|
1876
1876
|
if (name === "node_modules" || name === "dist" || name === "build")
|
|
1877
1877
|
continue;
|
|
1878
|
-
const sub =
|
|
1878
|
+
const sub = join17(repoRoot, name);
|
|
1879
1879
|
try {
|
|
1880
1880
|
const stat8 = await fs4.stat(sub);
|
|
1881
1881
|
if (stat8.isDirectory())
|
|
@@ -1991,7 +1991,7 @@ var init_installer = __esm({
|
|
|
1991
1991
|
|
|
1992
1992
|
// ../listener/dist/lsp/daemon-path.js
|
|
1993
1993
|
import { homedir as homedir4 } from "os";
|
|
1994
|
-
import { delimiter, dirname as dirname7, join as
|
|
1994
|
+
import { delimiter, dirname as dirname7, join as join18 } from "path";
|
|
1995
1995
|
function commonBinDirs() {
|
|
1996
1996
|
const home = homedir4();
|
|
1997
1997
|
return [
|
|
@@ -2004,13 +2004,13 @@ function commonBinDirs() {
|
|
|
2004
2004
|
"/bin",
|
|
2005
2005
|
"/usr/sbin",
|
|
2006
2006
|
"/sbin",
|
|
2007
|
-
|
|
2007
|
+
join18(home, ".local", "bin"),
|
|
2008
2008
|
// pipx / pip --user / uv
|
|
2009
|
-
|
|
2009
|
+
join18(home, ".npm-global", "bin"),
|
|
2010
2010
|
// common npm global prefix
|
|
2011
|
-
|
|
2011
|
+
join18(home, "go", "bin"),
|
|
2012
2012
|
// gopls
|
|
2013
|
-
|
|
2013
|
+
join18(home, ".cargo", "bin")
|
|
2014
2014
|
// rust-analyzer via cargo
|
|
2015
2015
|
];
|
|
2016
2016
|
}
|
|
@@ -2046,9 +2046,9 @@ __export(service_installer_exports, {
|
|
|
2046
2046
|
uninstall: () => uninstall
|
|
2047
2047
|
});
|
|
2048
2048
|
import { execFile as execFile4 } from "child_process";
|
|
2049
|
-
import { writeFile as
|
|
2049
|
+
import { writeFile as writeFile9, mkdir as mkdir8, unlink as unlink7 } from "fs/promises";
|
|
2050
2050
|
import { homedir as homedir5 } from "os";
|
|
2051
|
-
import { join as
|
|
2051
|
+
import { join as join19 } from "path";
|
|
2052
2052
|
function exec(cmd, args) {
|
|
2053
2053
|
return new Promise((resolve5, reject) => {
|
|
2054
2054
|
execFile4(cmd, args, (err, stdout) => {
|
|
@@ -2067,10 +2067,10 @@ function sanitizeEnvValue(value) {
|
|
|
2067
2067
|
return Array.from(value).filter((ch) => ch.charCodeAt(0) >= 32).join("");
|
|
2068
2068
|
}
|
|
2069
2069
|
function plistPath() {
|
|
2070
|
-
return
|
|
2070
|
+
return join19(homedir5(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
2071
2071
|
}
|
|
2072
2072
|
function logDir() {
|
|
2073
|
-
return
|
|
2073
|
+
return join19(getConfigDir(), "logs");
|
|
2074
2074
|
}
|
|
2075
2075
|
function buildPlist() {
|
|
2076
2076
|
const cmd = resolveListenerCommand();
|
|
@@ -2104,9 +2104,9 @@ ${programArgs}
|
|
|
2104
2104
|
<string>${xmlEscape(buildDaemonPath())}</string>
|
|
2105
2105
|
</dict>
|
|
2106
2106
|
<key>StandardOutPath</key>
|
|
2107
|
-
<string>${xmlEscape(
|
|
2107
|
+
<string>${xmlEscape(join19(logs, "listener-stdout.log"))}</string>
|
|
2108
2108
|
<key>StandardErrorPath</key>
|
|
2109
|
-
<string>${xmlEscape(
|
|
2109
|
+
<string>${xmlEscape(join19(logs, "listener-stderr.log"))}</string>
|
|
2110
2110
|
<key>ProcessType</key>
|
|
2111
2111
|
<string>Background</string>
|
|
2112
2112
|
<!--
|
|
@@ -2135,12 +2135,12 @@ ${programArgs}
|
|
|
2135
2135
|
}
|
|
2136
2136
|
async function darwinInstall() {
|
|
2137
2137
|
await mkdir8(logDir(), { recursive: true });
|
|
2138
|
-
await mkdir8(
|
|
2138
|
+
await mkdir8(join19(homedir5(), "Library", "LaunchAgents"), { recursive: true });
|
|
2139
2139
|
try {
|
|
2140
2140
|
await exec("launchctl", ["unload", plistPath()]);
|
|
2141
2141
|
} catch {
|
|
2142
2142
|
}
|
|
2143
|
-
await
|
|
2143
|
+
await writeFile9(plistPath(), buildPlist());
|
|
2144
2144
|
await exec("launchctl", ["load", plistPath()]);
|
|
2145
2145
|
}
|
|
2146
2146
|
async function darwinUninstall() {
|
|
@@ -2149,7 +2149,7 @@ async function darwinUninstall() {
|
|
|
2149
2149
|
} catch {
|
|
2150
2150
|
}
|
|
2151
2151
|
try {
|
|
2152
|
-
await
|
|
2152
|
+
await unlink7(plistPath());
|
|
2153
2153
|
} catch {
|
|
2154
2154
|
}
|
|
2155
2155
|
}
|
|
@@ -2162,7 +2162,7 @@ async function darwinIsInstalled() {
|
|
|
2162
2162
|
}
|
|
2163
2163
|
}
|
|
2164
2164
|
function unitPath() {
|
|
2165
|
-
return
|
|
2165
|
+
return join19(homedir5(), ".config", "systemd", "user", `${SYSTEMD_SERVICE}.service`);
|
|
2166
2166
|
}
|
|
2167
2167
|
function buildUnit() {
|
|
2168
2168
|
const cmd = resolveListenerCommand();
|
|
@@ -2182,16 +2182,16 @@ Environment=PATH=${sanitizeEnvValue(buildDaemonPath())}
|
|
|
2182
2182
|
ExecStart=${execStart}
|
|
2183
2183
|
Restart=always
|
|
2184
2184
|
RestartSec=10
|
|
2185
|
-
StandardOutput=append:${
|
|
2186
|
-
StandardError=append:${
|
|
2185
|
+
StandardOutput=append:${join19(logs, "listener-stdout.log")}
|
|
2186
|
+
StandardError=append:${join19(logs, "listener-stderr.log")}
|
|
2187
2187
|
|
|
2188
2188
|
[Install]
|
|
2189
2189
|
WantedBy=default.target`;
|
|
2190
2190
|
}
|
|
2191
2191
|
async function linuxInstall() {
|
|
2192
2192
|
await mkdir8(logDir(), { recursive: true });
|
|
2193
|
-
await mkdir8(
|
|
2194
|
-
await
|
|
2193
|
+
await mkdir8(join19(homedir5(), ".config", "systemd", "user"), { recursive: true });
|
|
2194
|
+
await writeFile9(unitPath(), buildUnit());
|
|
2195
2195
|
await exec("systemctl", ["--user", "daemon-reload"]);
|
|
2196
2196
|
await exec("systemctl", ["--user", "enable", SYSTEMD_SERVICE]);
|
|
2197
2197
|
await exec("systemctl", ["--user", "start", SYSTEMD_SERVICE]);
|
|
@@ -2206,7 +2206,7 @@ async function linuxUninstall() {
|
|
|
2206
2206
|
} catch {
|
|
2207
2207
|
}
|
|
2208
2208
|
try {
|
|
2209
|
-
await
|
|
2209
|
+
await unlink7(unitPath());
|
|
2210
2210
|
} catch {
|
|
2211
2211
|
}
|
|
2212
2212
|
try {
|
|
@@ -2385,8 +2385,8 @@ __export(sidecar_cache_exports, {
|
|
|
2385
2385
|
persistMergedSidecar: () => persistMergedSidecar,
|
|
2386
2386
|
sidecarCachePaths: () => sidecarCachePaths
|
|
2387
2387
|
});
|
|
2388
|
-
import { mkdir as mkdir9, readFile as
|
|
2389
|
-
import { dirname as dirname9, join as
|
|
2388
|
+
import { mkdir as mkdir9, readFile as readFile9, readdir as readdir3, stat as stat2, unlink as unlink9, writeFile as writeFile10 } from "fs/promises";
|
|
2389
|
+
import { dirname as dirname9, join as join21 } from "path";
|
|
2390
2390
|
function repoIdSafe(repoId) {
|
|
2391
2391
|
return /^[A-Za-z0-9_.-]{1,128}$/.test(repoId) && !repoId.startsWith(".");
|
|
2392
2392
|
}
|
|
@@ -2400,17 +2400,17 @@ function sidecarCachePaths(repoId, commitSha) {
|
|
|
2400
2400
|
if (!commitShaSafe(commitSha)) {
|
|
2401
2401
|
throw new Error(`unsafe commitSha: ${commitSha}`);
|
|
2402
2402
|
}
|
|
2403
|
-
const repoDir =
|
|
2404
|
-
const fullPath =
|
|
2403
|
+
const repoDir = join21(getConfigDir(), "typed-resolution", repoId);
|
|
2404
|
+
const fullPath = join21(repoDir, `${commitSha}.jsonl`);
|
|
2405
2405
|
return { fullPath, repoDir };
|
|
2406
2406
|
}
|
|
2407
2407
|
async function persistMergedSidecar(repoId, commitSha, sidecar) {
|
|
2408
2408
|
const { fullPath, repoDir } = sidecarCachePaths(repoId, commitSha);
|
|
2409
2409
|
await mkdir9(repoDir, { recursive: true });
|
|
2410
2410
|
const tmpPath = `${fullPath}.tmp`;
|
|
2411
|
-
await
|
|
2412
|
-
const { rename:
|
|
2413
|
-
await
|
|
2411
|
+
await writeFile10(tmpPath, JSON.stringify(sidecar), "utf-8");
|
|
2412
|
+
const { rename: rename8 } = await import("fs/promises");
|
|
2413
|
+
await rename8(tmpPath, fullPath);
|
|
2414
2414
|
try {
|
|
2415
2415
|
await sweepSidecarDir(repoDir);
|
|
2416
2416
|
} catch {
|
|
@@ -2424,7 +2424,7 @@ async function sweepSidecarDir(repoDir) {
|
|
|
2424
2424
|
const withMtime = [];
|
|
2425
2425
|
for (const name of jsonlFiles) {
|
|
2426
2426
|
try {
|
|
2427
|
-
const s = await stat2(
|
|
2427
|
+
const s = await stat2(join21(repoDir, name));
|
|
2428
2428
|
withMtime.push({ name, mtimeMs: s.mtimeMs });
|
|
2429
2429
|
} catch {
|
|
2430
2430
|
}
|
|
@@ -2435,7 +2435,7 @@ async function sweepSidecarDir(repoDir) {
|
|
|
2435
2435
|
for (const { name, mtimeMs } of candidates) {
|
|
2436
2436
|
if (now - mtimeMs >= SWEEP_MAX_AGE_MS) {
|
|
2437
2437
|
try {
|
|
2438
|
-
await
|
|
2438
|
+
await unlink9(join21(repoDir, name));
|
|
2439
2439
|
} catch {
|
|
2440
2440
|
}
|
|
2441
2441
|
}
|
|
@@ -2461,7 +2461,7 @@ async function loadMergedSidecar(repoId, commitSha) {
|
|
|
2461
2461
|
let body;
|
|
2462
2462
|
try {
|
|
2463
2463
|
const { fullPath } = sidecarCachePaths(repoId, commitSha);
|
|
2464
|
-
body = await
|
|
2464
|
+
body = await readFile9(fullPath, "utf-8");
|
|
2465
2465
|
} catch {
|
|
2466
2466
|
return null;
|
|
2467
2467
|
}
|
|
@@ -3133,15 +3133,15 @@ var init_telemetry = __esm({
|
|
|
3133
3133
|
// bin/repowise.ts
|
|
3134
3134
|
import { readFileSync as readFileSync3 } from "fs";
|
|
3135
3135
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3136
|
-
import { dirname as dirname23, join as
|
|
3136
|
+
import { dirname as dirname23, join as join63 } from "path";
|
|
3137
3137
|
import { Command } from "commander";
|
|
3138
3138
|
|
|
3139
3139
|
// ../listener/dist/main.js
|
|
3140
3140
|
init_config_dir();
|
|
3141
|
-
import { readFile as
|
|
3142
|
-
import { join as
|
|
3141
|
+
import { readFile as readFile14, writeFile as writeFile16, mkdir as mkdir16, stat as fsStat } from "fs/promises";
|
|
3142
|
+
import { join as join42, dirname as dirname17 } from "path";
|
|
3143
3143
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3144
|
-
import
|
|
3144
|
+
import lockfile5 from "proper-lockfile";
|
|
3145
3145
|
|
|
3146
3146
|
// ../../packages/shared/dist/lib/ai-tools.js
|
|
3147
3147
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, readdir, stat, unlink as unlink2 } from "fs/promises";
|
|
@@ -4391,8 +4391,53 @@ function migrateState(state, oldId, newId) {
|
|
|
4391
4391
|
|
|
4392
4392
|
// ../listener/dist/lib/auth.js
|
|
4393
4393
|
init_config_dir();
|
|
4394
|
-
import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir5, chmod as chmod3 } from "fs/promises";
|
|
4394
|
+
import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir5, chmod as chmod3, rename as rename3, open as open3, unlink as unlink5 } from "fs/promises";
|
|
4395
4395
|
import { join as join7 } from "path";
|
|
4396
|
+
|
|
4397
|
+
// ../../packages/shared/dist/lib/creds.js
|
|
4398
|
+
function isCredsHardExpired(creds) {
|
|
4399
|
+
if (!creds || typeof creds.expiresAt !== "number")
|
|
4400
|
+
return true;
|
|
4401
|
+
return Date.now() >= creds.expiresAt;
|
|
4402
|
+
}
|
|
4403
|
+
function classifyRefreshFailure(status2, body) {
|
|
4404
|
+
if (status2 === 400 && body != null && /invalid_grant/i.test(body))
|
|
4405
|
+
return "terminal";
|
|
4406
|
+
return "transient";
|
|
4407
|
+
}
|
|
4408
|
+
var RefreshTokenError = class extends Error {
|
|
4409
|
+
kind;
|
|
4410
|
+
status;
|
|
4411
|
+
constructor(kind, message, status2 = null) {
|
|
4412
|
+
super(message);
|
|
4413
|
+
this.name = "RefreshTokenError";
|
|
4414
|
+
this.kind = kind;
|
|
4415
|
+
this.status = status2;
|
|
4416
|
+
}
|
|
4417
|
+
};
|
|
4418
|
+
|
|
4419
|
+
// ../listener/dist/lib/auth.js
|
|
4420
|
+
import lockfile3 from "proper-lockfile";
|
|
4421
|
+
|
|
4422
|
+
// ../listener/dist/lib/http.js
|
|
4423
|
+
async function fetchWithTimeout(url, init, timeoutMs) {
|
|
4424
|
+
const controller = new AbortController();
|
|
4425
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
4426
|
+
try {
|
|
4427
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
4428
|
+
} finally {
|
|
4429
|
+
clearTimeout(timer);
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4433
|
+
// ../listener/dist/lib/auth.js
|
|
4434
|
+
var TOKEN_ENDPOINT_TIMEOUT_MS = 3e4;
|
|
4435
|
+
var REFRESH_MAX_ATTEMPTS = 3;
|
|
4436
|
+
var lastRefreshOutcome = "none";
|
|
4437
|
+
function getLastRefreshOutcome() {
|
|
4438
|
+
return lastRefreshOutcome;
|
|
4439
|
+
}
|
|
4440
|
+
var sleepMs = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
4396
4441
|
function getTokenUrl(creds) {
|
|
4397
4442
|
const cognito = creds?.cognito;
|
|
4398
4443
|
const domain = process.env["REPOWISE_COGNITO_DOMAIN"] ?? cognito?.domain ?? "auth-repowise-dev";
|
|
@@ -4406,24 +4451,33 @@ function getClientId(creds) {
|
|
|
4406
4451
|
async function refreshTokens(refreshToken, creds) {
|
|
4407
4452
|
const clientId = getClientId(creds);
|
|
4408
4453
|
if (!clientId) {
|
|
4409
|
-
throw new
|
|
4454
|
+
throw new RefreshTokenError("terminal", "No Cognito client ID available. Run `repowise login` first.", null);
|
|
4455
|
+
}
|
|
4456
|
+
let response;
|
|
4457
|
+
try {
|
|
4458
|
+
response = await fetchWithTimeout(getTokenUrl(creds), {
|
|
4459
|
+
method: "POST",
|
|
4460
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
4461
|
+
body: new URLSearchParams({
|
|
4462
|
+
grant_type: "refresh_token",
|
|
4463
|
+
client_id: clientId,
|
|
4464
|
+
refresh_token: refreshToken
|
|
4465
|
+
})
|
|
4466
|
+
}, TOKEN_ENDPOINT_TIMEOUT_MS);
|
|
4467
|
+
} catch (err) {
|
|
4468
|
+
throw new RefreshTokenError("transient", `Token refresh network error: ${err instanceof Error ? err.message : String(err)}`, null);
|
|
4410
4469
|
}
|
|
4411
|
-
const response = await fetch(getTokenUrl(creds), {
|
|
4412
|
-
method: "POST",
|
|
4413
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
4414
|
-
body: new URLSearchParams({
|
|
4415
|
-
grant_type: "refresh_token",
|
|
4416
|
-
client_id: clientId,
|
|
4417
|
-
refresh_token: refreshToken
|
|
4418
|
-
})
|
|
4419
|
-
});
|
|
4420
4470
|
if (!response.ok) {
|
|
4421
|
-
|
|
4471
|
+
const body = await response.text().catch(() => "");
|
|
4472
|
+
throw new RefreshTokenError(classifyRefreshFailure(response.status, body), `Token refresh failed: ${response.status}`, response.status);
|
|
4422
4473
|
}
|
|
4423
4474
|
const data = await response.json();
|
|
4424
4475
|
return {
|
|
4425
4476
|
accessToken: data.access_token,
|
|
4426
|
-
|
|
4477
|
+
// Carry forward a rotated refresh token when Cognito returns one; otherwise
|
|
4478
|
+
// reuse the current token (rotation disabled today). Required before
|
|
4479
|
+
// enabling refresh-token rotation server-side.
|
|
4480
|
+
refreshToken: data.refresh_token ?? refreshToken,
|
|
4427
4481
|
idToken: data.id_token,
|
|
4428
4482
|
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
4429
4483
|
cognito: creds.cognito
|
|
@@ -4445,30 +4499,89 @@ async function storeCredentials(credentials) {
|
|
|
4445
4499
|
const dir = getConfigDir();
|
|
4446
4500
|
const credPath = join7(dir, "credentials.json");
|
|
4447
4501
|
await mkdir5(dir, { recursive: true, mode: 448 });
|
|
4448
|
-
|
|
4449
|
-
|
|
4502
|
+
try {
|
|
4503
|
+
await writeFile5(credPath, "", { flag: "a", mode: 384 });
|
|
4504
|
+
} catch {
|
|
4505
|
+
}
|
|
4506
|
+
let release = null;
|
|
4507
|
+
try {
|
|
4508
|
+
release = await lockfile3.lock(credPath, { stale: 1e4, retries: 3, realpath: false });
|
|
4509
|
+
const tmpPath = credPath + ".tmp";
|
|
4510
|
+
try {
|
|
4511
|
+
await writeFile5(tmpPath, JSON.stringify(credentials, null, 2), { mode: 384 });
|
|
4512
|
+
let fh;
|
|
4513
|
+
try {
|
|
4514
|
+
fh = await open3(tmpPath, "r+");
|
|
4515
|
+
await fh.datasync();
|
|
4516
|
+
} finally {
|
|
4517
|
+
await fh?.close();
|
|
4518
|
+
}
|
|
4519
|
+
await rename3(tmpPath, credPath);
|
|
4520
|
+
await chmod3(credPath, 384);
|
|
4521
|
+
} catch (err) {
|
|
4522
|
+
try {
|
|
4523
|
+
await unlink5(tmpPath);
|
|
4524
|
+
} catch {
|
|
4525
|
+
}
|
|
4526
|
+
throw err;
|
|
4527
|
+
}
|
|
4528
|
+
} finally {
|
|
4529
|
+
if (release) {
|
|
4530
|
+
try {
|
|
4531
|
+
await release();
|
|
4532
|
+
} catch {
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4450
4536
|
}
|
|
4537
|
+
var refreshInFlight = null;
|
|
4451
4538
|
async function getValidCredentials(options) {
|
|
4452
4539
|
const creds = await getStoredCredentials();
|
|
4453
|
-
if (!creds)
|
|
4540
|
+
if (!creds) {
|
|
4541
|
+
lastRefreshOutcome = "no-creds";
|
|
4454
4542
|
return null;
|
|
4543
|
+
}
|
|
4455
4544
|
const now = Date.now();
|
|
4456
4545
|
const needsRefresh = options?.forceRefresh || now > creds.expiresAt - 5 * 60 * 1e3;
|
|
4457
|
-
if (needsRefresh) {
|
|
4546
|
+
if (!needsRefresh) {
|
|
4547
|
+
lastRefreshOutcome = "success";
|
|
4548
|
+
return creds;
|
|
4549
|
+
}
|
|
4550
|
+
if (!refreshInFlight) {
|
|
4551
|
+
refreshInFlight = doRefresh(creds).finally(() => {
|
|
4552
|
+
refreshInFlight = null;
|
|
4553
|
+
});
|
|
4554
|
+
}
|
|
4555
|
+
return refreshInFlight;
|
|
4556
|
+
}
|
|
4557
|
+
async function doRefresh(creds) {
|
|
4558
|
+
for (let attempt = 1; attempt <= REFRESH_MAX_ATTEMPTS; attempt++) {
|
|
4458
4559
|
try {
|
|
4459
4560
|
const refreshed = await refreshTokens(creds.refreshToken, creds);
|
|
4460
4561
|
await storeCredentials(refreshed);
|
|
4562
|
+
lastRefreshOutcome = "success";
|
|
4461
4563
|
return refreshed;
|
|
4462
4564
|
} catch (err) {
|
|
4565
|
+
const kind = err instanceof RefreshTokenError ? err.kind : "transient";
|
|
4463
4566
|
const message = err instanceof Error ? err.message : String(err);
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4567
|
+
if (kind === "terminal") {
|
|
4568
|
+
lastRefreshOutcome = "terminal";
|
|
4569
|
+
console.warn(`Token refresh rejected (terminal): ${message}`);
|
|
4570
|
+
return null;
|
|
4571
|
+
}
|
|
4572
|
+
if (attempt < REFRESH_MAX_ATTEMPTS) {
|
|
4573
|
+
const backoffBase = Number(process.env["REPOWISE_REFRESH_BACKOFF_MS"] ?? 1e3);
|
|
4574
|
+
await sleepMs(backoffBase * attempt);
|
|
4575
|
+
continue;
|
|
4467
4576
|
}
|
|
4577
|
+
lastRefreshOutcome = "transient";
|
|
4578
|
+
console.warn(`Token refresh failed (transient, ${REFRESH_MAX_ATTEMPTS} attempts): ${message}`);
|
|
4579
|
+
if (Date.now() < creds.expiresAt)
|
|
4580
|
+
return creds;
|
|
4468
4581
|
return null;
|
|
4469
4582
|
}
|
|
4470
4583
|
}
|
|
4471
|
-
return
|
|
4584
|
+
return null;
|
|
4472
4585
|
}
|
|
4473
4586
|
|
|
4474
4587
|
// ../listener/dist/poll-client.js
|
|
@@ -4593,6 +4706,17 @@ function notifyContextUpdated(repoId, fileCount) {
|
|
|
4593
4706
|
} catch {
|
|
4594
4707
|
}
|
|
4595
4708
|
}
|
|
4709
|
+
function notifyDocsReady(repoId) {
|
|
4710
|
+
try {
|
|
4711
|
+
notify({
|
|
4712
|
+
title: TITLE,
|
|
4713
|
+
// Start a NEW session (not just /mcp): an open AI session snapshots its
|
|
4714
|
+
// instructions at start, and /mcp only reloads MCP connections.
|
|
4715
|
+
message: `Full context docs are ready for ${repoId} \u2014 start a new AI session (or restart your AI tool) to use them.`
|
|
4716
|
+
});
|
|
4717
|
+
} catch {
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4596
4720
|
|
|
4597
4721
|
// ../listener/dist/context-fetcher.js
|
|
4598
4722
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -4613,6 +4737,7 @@ async function verifyContextFolder(localPath) {
|
|
|
4613
4737
|
}
|
|
4614
4738
|
|
|
4615
4739
|
// ../listener/dist/context-fetcher.js
|
|
4740
|
+
var CONTEXT_FETCH_TIMEOUT_MS = 3e4;
|
|
4616
4741
|
var execFileAsync = promisify(execFile2);
|
|
4617
4742
|
async function fetchContextUpdates(localPath) {
|
|
4618
4743
|
try {
|
|
@@ -4668,7 +4793,10 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
|
|
|
4668
4793
|
let listRes;
|
|
4669
4794
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
4670
4795
|
try {
|
|
4671
|
-
listRes = await fetch(`${apiUrl}/v1/repos/${repoId}/context`, {
|
|
4796
|
+
listRes = await fetch(`${apiUrl}/v1/repos/${repoId}/context`, {
|
|
4797
|
+
headers,
|
|
4798
|
+
signal: AbortSignal.timeout(CONTEXT_FETCH_TIMEOUT_MS)
|
|
4799
|
+
});
|
|
4672
4800
|
if (listRes.status === 401 || listRes.status === 403) {
|
|
4673
4801
|
console.error(`Context list auth error (${listRes.status}) for repo ${repoId}`);
|
|
4674
4802
|
return { success: false, updatedFiles: [] };
|
|
@@ -4700,7 +4828,8 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
|
|
|
4700
4828
|
if (file.fileName.includes(".."))
|
|
4701
4829
|
continue;
|
|
4702
4830
|
const urlRes = await fetch(`${apiUrl}/v1/repos/${repoId}/context/files/${file.fileName}`, {
|
|
4703
|
-
headers
|
|
4831
|
+
headers,
|
|
4832
|
+
signal: AbortSignal.timeout(CONTEXT_FETCH_TIMEOUT_MS)
|
|
4704
4833
|
});
|
|
4705
4834
|
if (!urlRes.ok) {
|
|
4706
4835
|
console.error(`Context fetch for ${repoId}: failed to get URL for ${file.fileName} (${urlRes.status})`);
|
|
@@ -4712,7 +4841,9 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
|
|
|
4712
4841
|
console.error(`Context fetch for ${repoId}: no presigned URL returned for ${file.fileName}`);
|
|
4713
4842
|
continue;
|
|
4714
4843
|
}
|
|
4715
|
-
const contentRes = await fetch(presignedUrl
|
|
4844
|
+
const contentRes = await fetch(presignedUrl, {
|
|
4845
|
+
signal: AbortSignal.timeout(CONTEXT_FETCH_TIMEOUT_MS)
|
|
4846
|
+
});
|
|
4716
4847
|
if (!contentRes.ok) {
|
|
4717
4848
|
console.error(`Context fetch for ${repoId}: download failed for ${file.fileName} (${contentRes.status})`);
|
|
4718
4849
|
continue;
|
|
@@ -4733,6 +4864,53 @@ async function fetchContextFromServer(repoId, localPath, apiUrl) {
|
|
|
4733
4864
|
}
|
|
4734
4865
|
}
|
|
4735
4866
|
|
|
4867
|
+
// ../listener/dist/docs-marker.js
|
|
4868
|
+
import { join as join10 } from "path";
|
|
4869
|
+
import { readFile as readFile6, writeFile as writeFile7, rename as rename4 } from "fs/promises";
|
|
4870
|
+
var DOCS_MARKER_NAME = ".repowise-meta.json";
|
|
4871
|
+
async function readDocsMarker(localPath, contextFolder) {
|
|
4872
|
+
try {
|
|
4873
|
+
const raw = await readFile6(join10(localPath, contextFolder, DOCS_MARKER_NAME), "utf-8");
|
|
4874
|
+
const m = JSON.parse(raw);
|
|
4875
|
+
if (typeof m.fileCount !== "number")
|
|
4876
|
+
return null;
|
|
4877
|
+
return {
|
|
4878
|
+
commitSha: typeof m.commitSha === "string" ? m.commitSha : null,
|
|
4879
|
+
fileCount: m.fileCount,
|
|
4880
|
+
updatedAt: typeof m.updatedAt === "string" ? m.updatedAt : ""
|
|
4881
|
+
};
|
|
4882
|
+
} catch {
|
|
4883
|
+
return null;
|
|
4884
|
+
}
|
|
4885
|
+
}
|
|
4886
|
+
async function writeDocsMarker(localPath, contextFolder, commitSha, fileCount) {
|
|
4887
|
+
const marker = {
|
|
4888
|
+
commitSha,
|
|
4889
|
+
fileCount,
|
|
4890
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4891
|
+
};
|
|
4892
|
+
const finalPath = join10(localPath, contextFolder, DOCS_MARKER_NAME);
|
|
4893
|
+
const tmpPath = `${finalPath}.tmp`;
|
|
4894
|
+
try {
|
|
4895
|
+
await writeFile7(tmpPath, JSON.stringify(marker, null, 2), "utf-8");
|
|
4896
|
+
await rename4(tmpPath, finalPath);
|
|
4897
|
+
} catch (err) {
|
|
4898
|
+
console.warn(`[docs-marker] failed to write provenance marker (${commitSha ?? "unknown commit"}): ${err instanceof Error ? err.message : String(err)}`);
|
|
4899
|
+
}
|
|
4900
|
+
}
|
|
4901
|
+
function effectiveToolVariant(graphOnly, docsPresent, marker, lastSyncCommitSha) {
|
|
4902
|
+
if (graphOnly)
|
|
4903
|
+
return "graph";
|
|
4904
|
+
if (!docsPresent)
|
|
4905
|
+
return "graph";
|
|
4906
|
+
if (!marker)
|
|
4907
|
+
return "full";
|
|
4908
|
+
if (lastSyncCommitSha && marker.commitSha && marker.commitSha !== lastSyncCommitSha) {
|
|
4909
|
+
return "graph";
|
|
4910
|
+
}
|
|
4911
|
+
return "full";
|
|
4912
|
+
}
|
|
4913
|
+
|
|
4736
4914
|
// ../listener/dist/main.js
|
|
4737
4915
|
init_process_manager();
|
|
4738
4916
|
|
|
@@ -4740,7 +4918,7 @@ init_process_manager();
|
|
|
4740
4918
|
import { execFile as execFile3 } from "child_process";
|
|
4741
4919
|
import { readFileSync } from "fs";
|
|
4742
4920
|
import { access as access2, constants, realpath } from "fs/promises";
|
|
4743
|
-
import { dirname as dirname4, join as
|
|
4921
|
+
import { dirname as dirname4, join as join12 } from "path";
|
|
4744
4922
|
import { promisify as promisify2 } from "util";
|
|
4745
4923
|
var execFileAsync2 = promisify2(execFile3);
|
|
4746
4924
|
var installInFlight = false;
|
|
@@ -4764,12 +4942,12 @@ async function installUpdate(currentVersion, packageName, targetVersion) {
|
|
|
4764
4942
|
}
|
|
4765
4943
|
installInFlight = true;
|
|
4766
4944
|
try {
|
|
4767
|
-
const npmWrapper =
|
|
4945
|
+
const npmWrapper = join12(dirname4(process.execPath), "npm");
|
|
4768
4946
|
const npmScript = await realpath(npmWrapper);
|
|
4769
4947
|
const runNpm = (args) => execFileAsync2(process.execPath, [npmScript, ...args], { timeout: 6e4 });
|
|
4770
4948
|
try {
|
|
4771
4949
|
const { stdout: prefix } = await runNpm(["prefix", "-g"]);
|
|
4772
|
-
const npmDir =
|
|
4950
|
+
const npmDir = join12(prefix.trim(), "lib", "node_modules");
|
|
4773
4951
|
const checkDir = process.platform === "win32" ? prefix.trim() : npmDir;
|
|
4774
4952
|
await access2(checkDir, constants.W_OK);
|
|
4775
4953
|
} catch (err) {
|
|
@@ -4787,7 +4965,7 @@ async function installUpdate(currentVersion, packageName, targetVersion) {
|
|
|
4787
4965
|
}
|
|
4788
4966
|
try {
|
|
4789
4967
|
const { stdout: prefix } = await runNpm(["prefix", "-g"]);
|
|
4790
|
-
const installedPkgJson =
|
|
4968
|
+
const installedPkgJson = join12(prefix.trim(), "lib", "node_modules", packageName, "package.json");
|
|
4791
4969
|
const installedVersion = JSON.parse(readFileSync(installedPkgJson, "utf-8")).version;
|
|
4792
4970
|
if (installedVersion !== targetVersion) {
|
|
4793
4971
|
const msg = `post-install check failed: expected ${targetVersion}, found ${installedVersion ?? "unknown"}`;
|
|
@@ -4856,11 +5034,11 @@ function comparePrerelease(a, b) {
|
|
|
4856
5034
|
|
|
4857
5035
|
// ../listener/dist/lib/binary-integrity.js
|
|
4858
5036
|
import { createHash as createHash2 } from "crypto";
|
|
4859
|
-
import { lstat, open as
|
|
5037
|
+
import { lstat, open as open4, readdir as readdir2 } from "fs/promises";
|
|
4860
5038
|
import { constants as fsConstants } from "fs";
|
|
4861
|
-
import { join as
|
|
5039
|
+
import { join as join13, resolve as pathResolve } from "path";
|
|
4862
5040
|
async function verifyBinary(check) {
|
|
4863
|
-
const fh = await
|
|
5041
|
+
const fh = await open4(check.path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
4864
5042
|
const hash = createHash2("sha256");
|
|
4865
5043
|
try {
|
|
4866
5044
|
const stream = fh.createReadStream();
|
|
@@ -4908,7 +5086,7 @@ async function auditNativeBindings(rootDir, expected) {
|
|
|
4908
5086
|
return;
|
|
4909
5087
|
}
|
|
4910
5088
|
for (const entryName of entries) {
|
|
4911
|
-
const full = pathResolve(
|
|
5089
|
+
const full = pathResolve(join13(dir, entryName));
|
|
4912
5090
|
const entryStat = await lstat(full).catch(() => null);
|
|
4913
5091
|
if (!entryStat)
|
|
4914
5092
|
continue;
|
|
@@ -4949,8 +5127,8 @@ async function auditNativeBindings(rootDir, expected) {
|
|
|
4949
5127
|
init_config_dir();
|
|
4950
5128
|
init_process_manager();
|
|
4951
5129
|
init_service_installer();
|
|
4952
|
-
import { unlink as
|
|
4953
|
-
import { join as
|
|
5130
|
+
import { unlink as unlink8 } from "fs/promises";
|
|
5131
|
+
import { join as join20 } from "path";
|
|
4954
5132
|
async function getListenerStatus() {
|
|
4955
5133
|
const pid = await readPid();
|
|
4956
5134
|
if (pid !== null) {
|
|
@@ -4959,7 +5137,7 @@ async function getListenerStatus() {
|
|
|
4959
5137
|
return { running: true, method: "pid", pid, serviceInstalled: serviceInstalled2 };
|
|
4960
5138
|
}
|
|
4961
5139
|
try {
|
|
4962
|
-
await
|
|
5140
|
+
await unlink8(join20(getConfigDir(), "listener.pid"));
|
|
4963
5141
|
} catch {
|
|
4964
5142
|
}
|
|
4965
5143
|
}
|
|
@@ -5004,11 +5182,11 @@ async function stopListener() {
|
|
|
5004
5182
|
|
|
5005
5183
|
// ../listener/dist/mcp/bootstrap.js
|
|
5006
5184
|
init_config_dir();
|
|
5007
|
-
import { join as
|
|
5185
|
+
import { join as join24 } from "path";
|
|
5008
5186
|
|
|
5009
5187
|
// ../listener/dist/lsp/workspace-session.js
|
|
5010
5188
|
import { pathToFileURL } from "url";
|
|
5011
|
-
import { readFile as
|
|
5189
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
5012
5190
|
import { resolve as pathResolve2, sep as pathSep, dirname as dirname8, delimiter as delimiter2 } from "path";
|
|
5013
5191
|
|
|
5014
5192
|
// ../listener/dist/lsp/lsp-client.js
|
|
@@ -5445,7 +5623,7 @@ var WorkspaceManager = class {
|
|
|
5445
5623
|
}
|
|
5446
5624
|
const absolute = this.joinPath(session.repoRoot, repoRelativePath);
|
|
5447
5625
|
const p = (async () => {
|
|
5448
|
-
const text = await
|
|
5626
|
+
const text = await readFile8(absolute, "utf-8");
|
|
5449
5627
|
session.client.notify("textDocument/didOpen", {
|
|
5450
5628
|
textDocument: {
|
|
5451
5629
|
uri,
|
|
@@ -5720,8 +5898,8 @@ function buildLspSpawnPath(basePath, nodeDir, lspBinDirs) {
|
|
|
5720
5898
|
|
|
5721
5899
|
// ../listener/dist/mcp/graph-cache.js
|
|
5722
5900
|
init_config_dir();
|
|
5723
|
-
import { readFile as
|
|
5724
|
-
import { join as
|
|
5901
|
+
import { readFile as readFile10, stat as stat3 } from "fs/promises";
|
|
5902
|
+
import { join as join22 } from "path";
|
|
5725
5903
|
var EVICT_DEBOUNCE_MS = 6e4;
|
|
5726
5904
|
function assertSafeRepoId(repoId) {
|
|
5727
5905
|
if (!repoId || typeof repoId !== "string") {
|
|
@@ -5736,13 +5914,13 @@ function assertSafeRepoId(repoId) {
|
|
|
5736
5914
|
}
|
|
5737
5915
|
function createGraphCache(options = {}) {
|
|
5738
5916
|
const evictMs = options.evictDebounceMs ?? EVICT_DEBOUNCE_MS;
|
|
5739
|
-
const resolvePath = options.resolveGraphPath ?? ((repoId) =>
|
|
5917
|
+
const resolvePath = options.resolveGraphPath ?? ((repoId) => join22(defaultRepoWiseHome(), "graphs", `${repoId}.json`));
|
|
5740
5918
|
const entries = /* @__PURE__ */ new Map();
|
|
5741
5919
|
const inFlight = /* @__PURE__ */ new Map();
|
|
5742
5920
|
async function loadFromDisk(repoId) {
|
|
5743
5921
|
const graphPath = resolvePath(repoId);
|
|
5744
5922
|
const s = await stat3(graphPath);
|
|
5745
|
-
const body = await
|
|
5923
|
+
const body = await readFile10(graphPath, "utf-8");
|
|
5746
5924
|
const graph = JSON.parse(body);
|
|
5747
5925
|
if (graph.commitSha && Array.isArray(graph.edges)) {
|
|
5748
5926
|
try {
|
|
@@ -5829,8 +6007,54 @@ function defaultRepoWiseHome() {
|
|
|
5829
6007
|
}
|
|
5830
6008
|
|
|
5831
6009
|
// ../listener/dist/mcp/graph-downloader.js
|
|
5832
|
-
import { mkdir as mkdir10, rename as
|
|
6010
|
+
import { mkdir as mkdir10, rename as rename5, writeFile as writeFile11 } from "fs/promises";
|
|
5833
6011
|
import { dirname as dirname10 } from "path";
|
|
6012
|
+
var GRAPH_DOWNLOAD_TIMEOUT_MS = 45e3;
|
|
6013
|
+
var GRAPH_IDLE_TIMEOUT_MS = 6e4;
|
|
6014
|
+
async function fetchTextWithIdleTimeout(fetchFn, url, init, idleMs) {
|
|
6015
|
+
const controller = new AbortController();
|
|
6016
|
+
let timer = null;
|
|
6017
|
+
const arm = () => {
|
|
6018
|
+
if (timer)
|
|
6019
|
+
clearTimeout(timer);
|
|
6020
|
+
timer = setTimeout(() => controller.abort(), idleMs);
|
|
6021
|
+
};
|
|
6022
|
+
const disarm = () => {
|
|
6023
|
+
if (timer)
|
|
6024
|
+
clearTimeout(timer);
|
|
6025
|
+
timer = null;
|
|
6026
|
+
};
|
|
6027
|
+
arm();
|
|
6028
|
+
try {
|
|
6029
|
+
const res = await fetchFn(url, { ...init, signal: controller.signal });
|
|
6030
|
+
if (!res.ok) {
|
|
6031
|
+
disarm();
|
|
6032
|
+
return { res, text: "" };
|
|
6033
|
+
}
|
|
6034
|
+
const stream = res.body;
|
|
6035
|
+
if (!stream || typeof stream.getReader !== "function") {
|
|
6036
|
+
const text = await res.text();
|
|
6037
|
+
disarm();
|
|
6038
|
+
return { res, text };
|
|
6039
|
+
}
|
|
6040
|
+
const reader = stream.getReader();
|
|
6041
|
+
const chunks = [];
|
|
6042
|
+
for (; ; ) {
|
|
6043
|
+
const { done, value } = await reader.read();
|
|
6044
|
+
if (done)
|
|
6045
|
+
break;
|
|
6046
|
+
if (value) {
|
|
6047
|
+
chunks.push(value);
|
|
6048
|
+
arm();
|
|
6049
|
+
}
|
|
6050
|
+
}
|
|
6051
|
+
disarm();
|
|
6052
|
+
return { res, text: Buffer.concat(chunks).toString("utf-8") };
|
|
6053
|
+
} catch (err) {
|
|
6054
|
+
disarm();
|
|
6055
|
+
throw err;
|
|
6056
|
+
}
|
|
6057
|
+
}
|
|
5834
6058
|
function createGraphDownloader(options) {
|
|
5835
6059
|
const fetchFn = options.fetchImpl ?? fetch;
|
|
5836
6060
|
let lastSha = null;
|
|
@@ -5840,7 +6064,8 @@ function createGraphDownloader(options) {
|
|
|
5840
6064
|
let repoRes;
|
|
5841
6065
|
try {
|
|
5842
6066
|
repoRes = await fetchFn(`${options.apiBaseUrl}/v1/repos/${options.repoId}`, {
|
|
5843
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
6067
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
6068
|
+
signal: AbortSignal.timeout(GRAPH_DOWNLOAD_TIMEOUT_MS)
|
|
5844
6069
|
});
|
|
5845
6070
|
} catch (err) {
|
|
5846
6071
|
return {
|
|
@@ -5864,15 +6089,23 @@ function createGraphDownloader(options) {
|
|
|
5864
6089
|
return { state: "no-graph" };
|
|
5865
6090
|
if (serverSha === lastSha)
|
|
5866
6091
|
return { state: "unchanged", sha: serverSha };
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
6092
|
+
let graphRes;
|
|
6093
|
+
let body;
|
|
6094
|
+
try {
|
|
6095
|
+
const r = await fetchTextWithIdleTimeout(fetchFn, `${options.apiBaseUrl}/v1/repos/${options.repoId}/graph`, { headers: { Authorization: `Bearer ${token}` } }, GRAPH_IDLE_TIMEOUT_MS);
|
|
6096
|
+
graphRes = r.res;
|
|
6097
|
+
body = r.text;
|
|
6098
|
+
} catch (err) {
|
|
6099
|
+
return {
|
|
6100
|
+
state: "error",
|
|
6101
|
+
message: `graph fetch: ${err instanceof Error ? err.message : String(err)}`
|
|
6102
|
+
};
|
|
6103
|
+
}
|
|
5870
6104
|
if (graphRes.status === 404)
|
|
5871
6105
|
return { state: "no-graph" };
|
|
5872
6106
|
if (!graphRes.ok) {
|
|
5873
6107
|
return { state: "error", message: `graph status ${graphRes.status}` };
|
|
5874
6108
|
}
|
|
5875
|
-
const body = await graphRes.text();
|
|
5876
6109
|
try {
|
|
5877
6110
|
JSON.parse(body);
|
|
5878
6111
|
} catch (err) {
|
|
@@ -5883,8 +6116,8 @@ function createGraphDownloader(options) {
|
|
|
5883
6116
|
}
|
|
5884
6117
|
await mkdir10(dirname10(options.targetPath), { recursive: true });
|
|
5885
6118
|
const tmpPath = `${options.targetPath}.${Date.now()}.tmp`;
|
|
5886
|
-
await
|
|
5887
|
-
await
|
|
6119
|
+
await writeFile11(tmpPath, body, { encoding: "utf-8", mode: 384 });
|
|
6120
|
+
await rename5(tmpPath, options.targetPath);
|
|
5888
6121
|
options.graphCache.invalidate(options.repoId);
|
|
5889
6122
|
lastSha = serverSha;
|
|
5890
6123
|
return { state: "downloaded", sha: serverSha };
|
|
@@ -5904,9 +6137,112 @@ function createGraphDownloader(options) {
|
|
|
5904
6137
|
};
|
|
5905
6138
|
}
|
|
5906
6139
|
|
|
6140
|
+
// ../listener/dist/mcp/heartbeat.js
|
|
6141
|
+
import { setTimeout as delay2 } from "timers/promises";
|
|
6142
|
+
var DEFAULT_WINDOW = 60 * 60 * 1e3;
|
|
6143
|
+
function emptyStatusCounts() {
|
|
6144
|
+
return { ok: 0, error: 0, timeout: 0, rejected: 0 };
|
|
6145
|
+
}
|
|
6146
|
+
function emptyHealthCounters() {
|
|
6147
|
+
return {
|
|
6148
|
+
graphNotLoaded: 0,
|
|
6149
|
+
unknownRepo: 0,
|
|
6150
|
+
lspDisabled: 0,
|
|
6151
|
+
lspUnavailable: 0,
|
|
6152
|
+
connectFail: 0,
|
|
6153
|
+
preAuthFail: 0
|
|
6154
|
+
};
|
|
6155
|
+
}
|
|
6156
|
+
function createHeartbeatEmitter(opts) {
|
|
6157
|
+
const windowMs = opts.windowMs ?? DEFAULT_WINDOW;
|
|
6158
|
+
const now = opts.now ?? (() => Date.now());
|
|
6159
|
+
let bucketStart = Math.floor(now() / windowMs) * windowMs;
|
|
6160
|
+
let toolCounts = /* @__PURE__ */ new Map();
|
|
6161
|
+
let missCounts = /* @__PURE__ */ new Map();
|
|
6162
|
+
let statusCounts = emptyStatusCounts();
|
|
6163
|
+
let healthCounters = emptyHealthCounters();
|
|
6164
|
+
function hasData() {
|
|
6165
|
+
if (toolCounts.size > 0 || missCounts.size > 0)
|
|
6166
|
+
return true;
|
|
6167
|
+
for (const k of Object.keys(statusCounts)) {
|
|
6168
|
+
if (statusCounts[k] > 0)
|
|
6169
|
+
return true;
|
|
6170
|
+
}
|
|
6171
|
+
for (const k of Object.keys(healthCounters)) {
|
|
6172
|
+
if (healthCounters[k] > 0)
|
|
6173
|
+
return true;
|
|
6174
|
+
}
|
|
6175
|
+
return false;
|
|
6176
|
+
}
|
|
6177
|
+
function buildPayload() {
|
|
6178
|
+
return {
|
|
6179
|
+
listenerVersion: opts.listenerVersion,
|
|
6180
|
+
windowStartsAt: new Date(bucketStart).toISOString(),
|
|
6181
|
+
toolCounts: Object.fromEntries(toolCounts),
|
|
6182
|
+
statusCounts,
|
|
6183
|
+
missCounts: Object.fromEntries(missCounts),
|
|
6184
|
+
healthCounters
|
|
6185
|
+
};
|
|
6186
|
+
}
|
|
6187
|
+
function clearCounters() {
|
|
6188
|
+
toolCounts = /* @__PURE__ */ new Map();
|
|
6189
|
+
missCounts = /* @__PURE__ */ new Map();
|
|
6190
|
+
statusCounts = emptyStatusCounts();
|
|
6191
|
+
healthCounters = emptyHealthCounters();
|
|
6192
|
+
}
|
|
6193
|
+
async function flushIfDue(currentBucket) {
|
|
6194
|
+
if (currentBucket === bucketStart)
|
|
6195
|
+
return;
|
|
6196
|
+
const payload = buildPayload();
|
|
6197
|
+
bucketStart = currentBucket;
|
|
6198
|
+
clearCounters();
|
|
6199
|
+
try {
|
|
6200
|
+
await opts.sink.post(payload);
|
|
6201
|
+
} catch {
|
|
6202
|
+
}
|
|
6203
|
+
}
|
|
6204
|
+
return {
|
|
6205
|
+
record(tool, outcome) {
|
|
6206
|
+
toolCounts.set(tool, (toolCounts.get(tool) ?? 0) + 1);
|
|
6207
|
+
if ("health" in outcome) {
|
|
6208
|
+
healthCounters[outcome.health] += 1;
|
|
6209
|
+
} else {
|
|
6210
|
+
statusCounts[outcome.status] += 1;
|
|
6211
|
+
if (outcome.miss)
|
|
6212
|
+
missCounts.set(tool, (missCounts.get(tool) ?? 0) + 1);
|
|
6213
|
+
}
|
|
6214
|
+
},
|
|
6215
|
+
recordHealth(kind) {
|
|
6216
|
+
healthCounters[kind] += 1;
|
|
6217
|
+
},
|
|
6218
|
+
async tick(nowOverride) {
|
|
6219
|
+
const current = Math.floor((nowOverride ?? now()) / windowMs) * windowMs;
|
|
6220
|
+
await flushIfDue(current);
|
|
6221
|
+
},
|
|
6222
|
+
async flush() {
|
|
6223
|
+
if (!hasData())
|
|
6224
|
+
return;
|
|
6225
|
+
const payload = buildPayload();
|
|
6226
|
+
clearCounters();
|
|
6227
|
+
try {
|
|
6228
|
+
await opts.sink.post(payload);
|
|
6229
|
+
} catch {
|
|
6230
|
+
}
|
|
6231
|
+
}
|
|
6232
|
+
};
|
|
6233
|
+
}
|
|
6234
|
+
async function runHeartbeatLoop(emitter, opts = {}) {
|
|
6235
|
+
const tickMs = opts.tickMs ?? 6e4;
|
|
6236
|
+
while (!opts.signal?.aborted) {
|
|
6237
|
+
await emitter.tick();
|
|
6238
|
+
await delay2(tickMs, void 0, { signal: opts.signal }).catch(() => {
|
|
6239
|
+
});
|
|
6240
|
+
}
|
|
6241
|
+
}
|
|
6242
|
+
|
|
5907
6243
|
// ../listener/dist/mcp/log-encryption.js
|
|
5908
6244
|
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
5909
|
-
import { mkdir as mkdir11, readFile as
|
|
6245
|
+
import { mkdir as mkdir11, readFile as readFile11, stat as stat4, writeFile as writeFile12 } from "fs/promises";
|
|
5910
6246
|
import { dirname as dirname11 } from "path";
|
|
5911
6247
|
var KEY_BYTES = 32;
|
|
5912
6248
|
var IV_BYTES = 12;
|
|
@@ -5934,7 +6270,7 @@ function createFileKeyStore(keyPath) {
|
|
|
5934
6270
|
return cached;
|
|
5935
6271
|
let parsed = null;
|
|
5936
6272
|
try {
|
|
5937
|
-
const body = await
|
|
6273
|
+
const body = await readFile11(keyPath, "utf-8");
|
|
5938
6274
|
const decoded = Buffer.from(body.trim(), "base64");
|
|
5939
6275
|
if (decoded.length === KEY_BYTES)
|
|
5940
6276
|
parsed = decoded;
|
|
@@ -5956,12 +6292,12 @@ function createFileKeyStore(keyPath) {
|
|
|
5956
6292
|
const fresh = randomBytes(KEY_BYTES);
|
|
5957
6293
|
await mkdir11(dirname11(keyPath), { recursive: true });
|
|
5958
6294
|
const tmp = `${keyPath}.tmp`;
|
|
5959
|
-
await
|
|
6295
|
+
await writeFile12(tmp, fresh.toString("base64") + "\n", {
|
|
5960
6296
|
encoding: "utf-8",
|
|
5961
6297
|
mode: 384
|
|
5962
6298
|
});
|
|
5963
|
-
const { rename:
|
|
5964
|
-
await
|
|
6299
|
+
const { rename: rename8 } = await import("fs/promises");
|
|
6300
|
+
await rename8(tmp, keyPath);
|
|
5965
6301
|
cached = fresh;
|
|
5966
6302
|
try {
|
|
5967
6303
|
const s = await stat4(keyPath);
|
|
@@ -5995,7 +6331,7 @@ function decryptLine(encoded, key) {
|
|
|
5995
6331
|
}
|
|
5996
6332
|
|
|
5997
6333
|
// ../listener/dist/mcp/mcp-logger.js
|
|
5998
|
-
import { appendFile, mkdir as mkdir12, stat as stat5, unlink as
|
|
6334
|
+
import { appendFile, mkdir as mkdir12, stat as stat5, unlink as unlink10, writeFile as writeFile13, readFile as readFile12 } from "fs/promises";
|
|
5999
6335
|
import { dirname as dirname12 } from "path";
|
|
6000
6336
|
var DEFAULT_MAX_BYTES = 100 * 1024 * 1024;
|
|
6001
6337
|
function createMcpLogger(options) {
|
|
@@ -6010,7 +6346,7 @@ function createMcpLogger(options) {
|
|
|
6010
6346
|
if (legacyPath === options.filePath)
|
|
6011
6347
|
return;
|
|
6012
6348
|
try {
|
|
6013
|
-
await
|
|
6349
|
+
await unlink10(legacyPath);
|
|
6014
6350
|
console.log(`[mcp-logger] removed legacy plaintext log at ${legacyPath} (one-time migration)`);
|
|
6015
6351
|
} catch {
|
|
6016
6352
|
}
|
|
@@ -6041,19 +6377,20 @@ function createMcpLogger(options) {
|
|
|
6041
6377
|
}
|
|
6042
6378
|
async function rotate(path, size) {
|
|
6043
6379
|
const dropAt = Math.floor(size * 0.25);
|
|
6044
|
-
const body = await
|
|
6380
|
+
const body = await readFile12(path, "utf-8");
|
|
6045
6381
|
const idx = body.indexOf("\n", dropAt);
|
|
6046
6382
|
if (idx < 0) {
|
|
6047
|
-
await
|
|
6383
|
+
await writeFile13(path, "", { encoding: "utf-8", mode: 384 });
|
|
6048
6384
|
return;
|
|
6049
6385
|
}
|
|
6050
|
-
await
|
|
6386
|
+
await writeFile13(path, body.slice(idx + 1), { encoding: "utf-8", mode: 384 });
|
|
6051
6387
|
}
|
|
6052
6388
|
|
|
6053
6389
|
// ../listener/dist/mcp/mcp-log-uploader.js
|
|
6054
6390
|
import { randomUUID } from "crypto";
|
|
6055
|
-
import { readFile as
|
|
6391
|
+
import { readFile as readFile13, writeFile as writeFile14, mkdir as mkdir13 } from "fs/promises";
|
|
6056
6392
|
import { dirname as dirname13 } from "path";
|
|
6393
|
+
var MCP_LOG_UPLOAD_TIMEOUT_MS = 3e4;
|
|
6057
6394
|
var DEFAULT_MAX_ENTRIES = 5e3;
|
|
6058
6395
|
var DEFAULT_MAX_BYTES2 = 1 * 1024 * 1024;
|
|
6059
6396
|
function createMcpLogUploader(options) {
|
|
@@ -6078,7 +6415,7 @@ function createMcpLogUploader(options) {
|
|
|
6078
6415
|
}
|
|
6079
6416
|
let body;
|
|
6080
6417
|
try {
|
|
6081
|
-
body = await
|
|
6418
|
+
body = await readFile13(options.logFilePath, "utf-8");
|
|
6082
6419
|
} catch (err) {
|
|
6083
6420
|
if (err.code === "ENOENT") {
|
|
6084
6421
|
return { uploaded: 0, bytesAdvanced: 0 };
|
|
@@ -6150,7 +6487,8 @@ function createMcpLogUploader(options) {
|
|
|
6150
6487
|
"Content-Type": "application/json",
|
|
6151
6488
|
Authorization: `Bearer ${token}`
|
|
6152
6489
|
},
|
|
6153
|
-
body: uploadBody
|
|
6490
|
+
body: uploadBody,
|
|
6491
|
+
signal: AbortSignal.timeout(MCP_LOG_UPLOAD_TIMEOUT_MS)
|
|
6154
6492
|
});
|
|
6155
6493
|
if (res.status === 401 && options.getAuthTokenForceRefresh) {
|
|
6156
6494
|
try {
|
|
@@ -6161,7 +6499,8 @@ function createMcpLogUploader(options) {
|
|
|
6161
6499
|
"Content-Type": "application/json",
|
|
6162
6500
|
Authorization: `Bearer ${freshToken}`
|
|
6163
6501
|
},
|
|
6164
|
-
body: uploadBody
|
|
6502
|
+
body: uploadBody,
|
|
6503
|
+
signal: AbortSignal.timeout(MCP_LOG_UPLOAD_TIMEOUT_MS)
|
|
6165
6504
|
});
|
|
6166
6505
|
} catch {
|
|
6167
6506
|
}
|
|
@@ -6195,7 +6534,7 @@ function pendingPath(watermarkPath) {
|
|
|
6195
6534
|
}
|
|
6196
6535
|
async function readPendingBatch(watermarkPath) {
|
|
6197
6536
|
try {
|
|
6198
|
-
const body = await
|
|
6537
|
+
const body = await readFile13(pendingPath(watermarkPath), "utf-8");
|
|
6199
6538
|
const trimmed = body.trim();
|
|
6200
6539
|
return /^[A-Za-z0-9-]{8,128}$/.test(trimmed) ? trimmed : null;
|
|
6201
6540
|
} catch {
|
|
@@ -6204,18 +6543,18 @@ async function readPendingBatch(watermarkPath) {
|
|
|
6204
6543
|
}
|
|
6205
6544
|
async function writePendingBatch(watermarkPath, batchId) {
|
|
6206
6545
|
await mkdir13(dirname13(watermarkPath), { recursive: true });
|
|
6207
|
-
await
|
|
6546
|
+
await writeFile14(pendingPath(watermarkPath), batchId, { encoding: "utf-8", mode: 384 });
|
|
6208
6547
|
}
|
|
6209
6548
|
async function clearPendingBatch(watermarkPath) {
|
|
6210
|
-
const { unlink:
|
|
6549
|
+
const { unlink: unlink14 } = await import("fs/promises");
|
|
6211
6550
|
try {
|
|
6212
|
-
await
|
|
6551
|
+
await unlink14(pendingPath(watermarkPath));
|
|
6213
6552
|
} catch {
|
|
6214
6553
|
}
|
|
6215
6554
|
}
|
|
6216
6555
|
async function readWatermark(path) {
|
|
6217
6556
|
try {
|
|
6218
|
-
const body = await
|
|
6557
|
+
const body = await readFile13(path, "utf-8");
|
|
6219
6558
|
const parsed = Number.parseInt(body.trim(), 10);
|
|
6220
6559
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
6221
6560
|
} catch (err) {
|
|
@@ -6227,13 +6566,13 @@ async function readWatermark(path) {
|
|
|
6227
6566
|
async function writeWatermark(path, offset) {
|
|
6228
6567
|
await mkdir13(dirname13(path), { recursive: true });
|
|
6229
6568
|
const tmp = `${path}.tmp`;
|
|
6230
|
-
await
|
|
6231
|
-
const { rename:
|
|
6232
|
-
await
|
|
6569
|
+
await writeFile14(tmp, offset.toString() + "\n", { encoding: "utf-8", mode: 384 });
|
|
6570
|
+
const { rename: rename8 } = await import("fs/promises");
|
|
6571
|
+
await rename8(tmp, path);
|
|
6233
6572
|
}
|
|
6234
6573
|
async function isLocallyConsented(flagPath2) {
|
|
6235
6574
|
try {
|
|
6236
|
-
const body = await
|
|
6575
|
+
const body = await readFile13(flagPath2, "utf-8");
|
|
6237
6576
|
const parsed = JSON.parse(body);
|
|
6238
6577
|
return parsed.enabled !== false;
|
|
6239
6578
|
} catch {
|
|
@@ -6244,8 +6583,8 @@ async function isLocallyConsented(flagPath2) {
|
|
|
6244
6583
|
// ../listener/dist/mcp/mcp-server.js
|
|
6245
6584
|
init_config_dir();
|
|
6246
6585
|
import { createServer } from "http";
|
|
6247
|
-
import { mkdir as mkdir14, writeFile as
|
|
6248
|
-
import { dirname as dirname14, join as
|
|
6586
|
+
import { mkdir as mkdir14, writeFile as writeFile15, unlink as unlink11 } from "fs/promises";
|
|
6587
|
+
import { dirname as dirname14, join as join23 } from "path";
|
|
6249
6588
|
import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
|
|
6250
6589
|
|
|
6251
6590
|
// ../listener/dist/mcp/sanitize.js
|
|
@@ -7309,7 +7648,16 @@ async function startMcpServer(options) {
|
|
|
7309
7648
|
secret: secretState.value.current,
|
|
7310
7649
|
close: () => new Promise((resolve5, reject) => {
|
|
7311
7650
|
server.close((err) => err ? reject(err) : resolve5());
|
|
7312
|
-
})
|
|
7651
|
+
}),
|
|
7652
|
+
suspendEndpoint: async () => {
|
|
7653
|
+
try {
|
|
7654
|
+
await unlink11(endpointFilePath);
|
|
7655
|
+
} catch (err) {
|
|
7656
|
+
if (err.code !== "ENOENT")
|
|
7657
|
+
throw err;
|
|
7658
|
+
}
|
|
7659
|
+
},
|
|
7660
|
+
resumeEndpoint: () => writeEndpointFile(endpointFilePath, endpoint, secretState.value.current)
|
|
7313
7661
|
};
|
|
7314
7662
|
}
|
|
7315
7663
|
function handleRequest(req, res, options, sessions, secretCtx) {
|
|
@@ -7333,11 +7681,13 @@ function handleRequest(req, res, options, sessions, secretCtx) {
|
|
|
7333
7681
|
const rawHost = req.headers.host;
|
|
7334
7682
|
const hostHeader = typeof rawHost === "string" ? rawHost : Array.isArray(rawHost) && typeof rawHost[0] === "string" ? rawHost[0] : void 0;
|
|
7335
7683
|
if (!isHostAllowed(hostHeader)) {
|
|
7684
|
+
options.heartbeat?.recordHealth("preAuthFail");
|
|
7336
7685
|
sendJson(res, 403, { error: "invalid_host" });
|
|
7337
7686
|
return;
|
|
7338
7687
|
}
|
|
7339
7688
|
const bearer = extractBearer(req.headers.authorization);
|
|
7340
7689
|
if (!isTokenValid(bearer, secretCtx.getSecret(), secretCtx.clock())) {
|
|
7690
|
+
options.heartbeat?.recordHealth("preAuthFail");
|
|
7341
7691
|
sendJson(res, 401, { error: "invalid_token" });
|
|
7342
7692
|
return;
|
|
7343
7693
|
}
|
|
@@ -7355,6 +7705,7 @@ function handleRequest(req, res, options, sessions, secretCtx) {
|
|
|
7355
7705
|
const rawMsg = err instanceof Error ? err.message : String(err);
|
|
7356
7706
|
const safeMsg = stripAbsolutePaths(rawMsg);
|
|
7357
7707
|
if (err?.code === "ENOENT") {
|
|
7708
|
+
options.heartbeat?.recordHealth("connectFail");
|
|
7358
7709
|
return sendJson(res, 404, { error: "unknown_repo" });
|
|
7359
7710
|
}
|
|
7360
7711
|
sendJson(res, 500, { error: safeMsg });
|
|
@@ -7529,7 +7880,7 @@ function handleRequest(req, res, options, sessions, secretCtx) {
|
|
|
7529
7880
|
})();
|
|
7530
7881
|
if (method === "POST" && lspDispatch[url]) {
|
|
7531
7882
|
const lspHandler = lspDispatch[url];
|
|
7532
|
-
dispatchSessionTool(req, res, url, sessions, options.mcpLogger, async (session, body) => {
|
|
7883
|
+
dispatchSessionTool(req, res, url, sessions, options.mcpLogger, options.heartbeat, async (session, body) => {
|
|
7533
7884
|
if (options.getLspEnabled && options.getLspEnabled() === false) {
|
|
7534
7885
|
return {
|
|
7535
7886
|
ok: true,
|
|
@@ -7551,14 +7902,17 @@ function handleRequest(req, res, options, sessions, secretCtx) {
|
|
|
7551
7902
|
}
|
|
7552
7903
|
};
|
|
7553
7904
|
}
|
|
7554
|
-
const value = await Promise.race([
|
|
7905
|
+
const value = await Promise.race([
|
|
7906
|
+
lspHandler(body, root),
|
|
7907
|
+
timeoutAfter(TOOL_TIMEOUT_MS_LSP)
|
|
7908
|
+
]);
|
|
7555
7909
|
return { ok: true, value };
|
|
7556
7910
|
});
|
|
7557
7911
|
return;
|
|
7558
7912
|
}
|
|
7559
7913
|
if (method === "POST" && toolDispatch[url]) {
|
|
7560
7914
|
const handler = toolDispatch[url];
|
|
7561
|
-
dispatchSessionTool(req, res, url, sessions, options.mcpLogger, async (session, body) => {
|
|
7915
|
+
dispatchSessionTool(req, res, url, sessions, options.mcpLogger, options.heartbeat, async (session, body) => {
|
|
7562
7916
|
const graph = options.graphCache.peek(session.repoId);
|
|
7563
7917
|
if (!graph) {
|
|
7564
7918
|
return {
|
|
@@ -7591,7 +7945,7 @@ function handleRequest(req, res, options, sessions, secretCtx) {
|
|
|
7591
7945
|
}
|
|
7592
7946
|
sendJson(res, 404, { error: "not_found" });
|
|
7593
7947
|
}
|
|
7594
|
-
function dispatchSessionTool(req, res, url, sessions, logger, handler) {
|
|
7948
|
+
function dispatchSessionTool(req, res, url, sessions, logger, heartbeat, handler) {
|
|
7595
7949
|
const tool = url.replace("/mcp/tools/", "");
|
|
7596
7950
|
const startedAt = Date.now();
|
|
7597
7951
|
void readJson(req).then(async (body) => {
|
|
@@ -7610,6 +7964,7 @@ function dispatchSessionTool(req, res, url, sessions, logger, handler) {
|
|
|
7610
7964
|
error: "unknown_session"
|
|
7611
7965
|
});
|
|
7612
7966
|
logToStdout("rejected", tool, null, Date.now() - startedAt, "unknown_session");
|
|
7967
|
+
heartbeat?.record(tool, { status: "rejected" });
|
|
7613
7968
|
return sendJson(res, 404, { error: "unknown_session" });
|
|
7614
7969
|
}
|
|
7615
7970
|
try {
|
|
@@ -7625,6 +7980,8 @@ function dispatchSessionTool(req, res, url, sessions, logger, handler) {
|
|
|
7625
7980
|
latencyMs: Date.now() - startedAt
|
|
7626
7981
|
});
|
|
7627
7982
|
logToStdout("ok", tool, session.repoId, Date.now() - startedAt);
|
|
7983
|
+
const classification = classifyToolResult(tool, result.value);
|
|
7984
|
+
heartbeat?.record(tool, classification.kind === "health" ? { health: classification.health } : { status: classification.status, miss: classification.miss });
|
|
7628
7985
|
return sendJson(res, 200, result.value);
|
|
7629
7986
|
}
|
|
7630
7987
|
const rejectErr = typeof result.body["error"] === "string" ? result.body["error"] : "tool_rejected";
|
|
@@ -7639,9 +7996,11 @@ function dispatchSessionTool(req, res, url, sessions, logger, handler) {
|
|
|
7639
7996
|
error: rejectErr
|
|
7640
7997
|
});
|
|
7641
7998
|
logToStdout("rejected", tool, session.repoId, Date.now() - startedAt, rejectErr);
|
|
7999
|
+
heartbeat?.record(tool, { status: "rejected" });
|
|
7642
8000
|
return sendJson(res, result.status, result.body);
|
|
7643
8001
|
} catch (err) {
|
|
7644
|
-
const
|
|
8002
|
+
const thrown = classifyThrow(err);
|
|
8003
|
+
const isTimeout = thrown.status === "timeout";
|
|
7645
8004
|
const rawMsg = err instanceof Error ? err.message : String(err);
|
|
7646
8005
|
const safeMsg = stripAbsolutePaths(rawMsg);
|
|
7647
8006
|
recordLog(logger, {
|
|
@@ -7655,6 +8014,7 @@ function dispatchSessionTool(req, res, url, sessions, logger, handler) {
|
|
|
7655
8014
|
error: safeMsg
|
|
7656
8015
|
});
|
|
7657
8016
|
logToStdout("error", tool, session.repoId, Date.now() - startedAt, safeMsg);
|
|
8017
|
+
heartbeat?.record(tool, { status: thrown.status });
|
|
7658
8018
|
if (isTimeout) {
|
|
7659
8019
|
return sendJson(res, 200, {
|
|
7660
8020
|
error: `tool-timeout: ${tool} exceeded its execution budget`,
|
|
@@ -7731,6 +8091,73 @@ var ToolTimeoutError = class extends Error {
|
|
|
7731
8091
|
this.timeoutMs = ms;
|
|
7732
8092
|
}
|
|
7733
8093
|
};
|
|
8094
|
+
function asRecord(value) {
|
|
8095
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
8096
|
+
}
|
|
8097
|
+
function arrayLen(rec, key) {
|
|
8098
|
+
const val = rec?.[key];
|
|
8099
|
+
return Array.isArray(val) ? val.length : void 0;
|
|
8100
|
+
}
|
|
8101
|
+
function nested(rec, key) {
|
|
8102
|
+
return asRecord(rec?.[key]);
|
|
8103
|
+
}
|
|
8104
|
+
var MISS_PREDICATES = {
|
|
8105
|
+
find_symbol: (v) => arrayLen(v, "matches") === 0,
|
|
8106
|
+
search_pattern: (v) => arrayLen(v, "matches") === 0,
|
|
8107
|
+
get_symbol: (v) => v?.["symbol"] === null,
|
|
8108
|
+
find_callers: (v) => arrayLen(v, "callers") === 0,
|
|
8109
|
+
find_references: (v) => arrayLen(v, "references") === 0,
|
|
8110
|
+
get_deps: (v) => arrayLen(v, "dependencies") === 0,
|
|
8111
|
+
find_tests_for_symbol: (v) => arrayLen(v, "tests") === 0,
|
|
8112
|
+
get_impact: (v) => arrayLen(v, "impacted") === 0,
|
|
8113
|
+
list_edges: (v) => nested(v, "coverage")?.["filteredEdges"] === 0,
|
|
8114
|
+
get_call_graph: (v) => arrayLen(nested(v, "subgraph"), "edges") === 0,
|
|
8115
|
+
lsp_definition: (v) => arrayLen(v, "locations") === 0,
|
|
8116
|
+
lsp_implementation: (v) => arrayLen(v, "locations") === 0,
|
|
8117
|
+
lsp_references: (v) => arrayLen(v, "locations") === 0,
|
|
8118
|
+
lsp_hover: (v) => !v?.["contents"],
|
|
8119
|
+
lsp_workspace_symbol: (v) => arrayLen(v, "symbols") === 0,
|
|
8120
|
+
lsp_document_symbol: (v) => arrayLen(v, "symbols") === 0,
|
|
8121
|
+
lsp_call_hierarchy: (v) => arrayLen(v, "edges") === 0,
|
|
8122
|
+
lsp_type_hierarchy: (v) => arrayLen(v, "entries") === 0
|
|
8123
|
+
};
|
|
8124
|
+
function isMiss(tool, value) {
|
|
8125
|
+
const pred = MISS_PREDICATES[tool];
|
|
8126
|
+
if (!pred)
|
|
8127
|
+
return false;
|
|
8128
|
+
try {
|
|
8129
|
+
return pred(asRecord(value));
|
|
8130
|
+
} catch {
|
|
8131
|
+
return false;
|
|
8132
|
+
}
|
|
8133
|
+
}
|
|
8134
|
+
function classifyToolResult(tool, value) {
|
|
8135
|
+
const v = asRecord(value);
|
|
8136
|
+
if (v && v["isError"] === true) {
|
|
8137
|
+
const err = typeof v["error"] === "string" ? v["error"] : "";
|
|
8138
|
+
if (err === "graph_not_loaded")
|
|
8139
|
+
return { kind: "health", health: "graphNotLoaded" };
|
|
8140
|
+
if (err === "unknown_repo")
|
|
8141
|
+
return { kind: "health", health: "unknownRepo" };
|
|
8142
|
+
if (err === "lsp_disabled")
|
|
8143
|
+
return { kind: "health", health: "lspDisabled" };
|
|
8144
|
+
if (err.startsWith("no-server-available") || err.startsWith("unsupported-language")) {
|
|
8145
|
+
return { kind: "health", health: "lspUnavailable" };
|
|
8146
|
+
}
|
|
8147
|
+
if (v["invalidPattern"] === true || v["unsafePattern"] === true || / required \(/.test(err)) {
|
|
8148
|
+
return { kind: "status", status: "rejected", miss: false };
|
|
8149
|
+
}
|
|
8150
|
+
return { kind: "status", status: "error", miss: false };
|
|
8151
|
+
}
|
|
8152
|
+
return { kind: "status", status: "ok", miss: isMiss(tool, value) };
|
|
8153
|
+
}
|
|
8154
|
+
function classifyThrow(err) {
|
|
8155
|
+
return {
|
|
8156
|
+
kind: "status",
|
|
8157
|
+
status: err instanceof ToolTimeoutError ? "timeout" : "error",
|
|
8158
|
+
miss: false
|
|
8159
|
+
};
|
|
8160
|
+
}
|
|
7734
8161
|
function stripAbsolutePaths(msg) {
|
|
7735
8162
|
return msg.replace(/(?:^|(?<=\s|[:(,]))\/[\w.\-/]+/g, "<redacted-path>");
|
|
7736
8163
|
}
|
|
@@ -7764,7 +8191,7 @@ async function readJson(req) {
|
|
|
7764
8191
|
}
|
|
7765
8192
|
async function writeEndpointFile(path, endpoint, secret) {
|
|
7766
8193
|
await mkdir14(dirname14(path), { recursive: true });
|
|
7767
|
-
await
|
|
8194
|
+
await writeFile15(path, formatEndpointFile({ endpoint, secret }), {
|
|
7768
8195
|
encoding: "utf-8",
|
|
7769
8196
|
mode: 384
|
|
7770
8197
|
});
|
|
@@ -7777,7 +8204,7 @@ function extractBearer(header) {
|
|
|
7777
8204
|
return m ? m[1] : null;
|
|
7778
8205
|
}
|
|
7779
8206
|
function defaultEndpointFile() {
|
|
7780
|
-
return
|
|
8207
|
+
return join23(getConfigDir(), "listener.endpoint");
|
|
7781
8208
|
}
|
|
7782
8209
|
|
|
7783
8210
|
// ../listener/dist/mcp/bootstrap.js
|
|
@@ -7785,7 +8212,7 @@ async function startMcp(options) {
|
|
|
7785
8212
|
const disabled = process.env.REPOWISE_MCP_DISABLED === "true";
|
|
7786
8213
|
const graphsDir = options.graphsDir ?? defaultGraphsDir();
|
|
7787
8214
|
const graphCache = createGraphCache({
|
|
7788
|
-
resolveGraphPath: (repoId) =>
|
|
8215
|
+
resolveGraphPath: (repoId) => join24(graphsDir, `${repoId}.json`)
|
|
7789
8216
|
});
|
|
7790
8217
|
if (disabled) {
|
|
7791
8218
|
return {
|
|
@@ -7807,13 +8234,14 @@ async function startMcp(options) {
|
|
|
7807
8234
|
repoIdToRoot.set(repo.repoId, repo.localPath);
|
|
7808
8235
|
}
|
|
7809
8236
|
const firstRepoLocal = options.repos.find((r) => r.localPath)?.localPath;
|
|
8237
|
+
const rawMcpLogEnabled = process.env["REPOWISE_MCP_LOG_RAW_ENABLED"] === "true";
|
|
7810
8238
|
const mcpHome = defaultMcpHome();
|
|
7811
|
-
const logFilePath =
|
|
7812
|
-
const keyStore = createFileKeyStore(
|
|
7813
|
-
const mcpLogger = createMcpLogger({ filePath: logFilePath, keyStore });
|
|
7814
|
-
const flagFilePath =
|
|
7815
|
-
const watermarkFilePath =
|
|
7816
|
-
const consentSentMarkerPath =
|
|
8239
|
+
const logFilePath = join24(mcpHome, "mcp-log.jsonl.enc");
|
|
8240
|
+
const keyStore = createFileKeyStore(join24(mcpHome, "mcp-log.key"));
|
|
8241
|
+
const mcpLogger = rawMcpLogEnabled ? createMcpLogger({ filePath: logFilePath, keyStore }) : void 0;
|
|
8242
|
+
const flagFilePath = join24(mcpHome, "mcp-log.flag");
|
|
8243
|
+
const watermarkFilePath = join24(mcpHome, "mcp-log.watermark");
|
|
8244
|
+
const consentSentMarkerPath = join24(mcpHome, "mcp-log.consent-sent");
|
|
7817
8245
|
let uploader = null;
|
|
7818
8246
|
let lastConsentState = false;
|
|
7819
8247
|
let serverConsentEnsuredThisProcess = false;
|
|
@@ -7835,6 +8263,18 @@ async function startMcp(options) {
|
|
|
7835
8263
|
});
|
|
7836
8264
|
return uploader;
|
|
7837
8265
|
}
|
|
8266
|
+
const heartbeatEnabled = process.env["REPOWISE_MCP_HEARTBEAT_DISABLED"] !== "true";
|
|
8267
|
+
const heartbeatApiUrl = options.repos.find((r) => Boolean(r.apiUrl))?.apiUrl ?? options.repos[0]?.apiUrl ?? "";
|
|
8268
|
+
const heartbeatEmitter = heartbeatEnabled ? createHeartbeatEmitter({
|
|
8269
|
+
listenerVersion: options.listenerVersion ?? "",
|
|
8270
|
+
sink: createHeartbeatSink({
|
|
8271
|
+
apiUrl: heartbeatApiUrl,
|
|
8272
|
+
getAuthToken: options.getAuthToken,
|
|
8273
|
+
flagFilePath,
|
|
8274
|
+
...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
|
|
8275
|
+
})
|
|
8276
|
+
}) : null;
|
|
8277
|
+
const heartbeatAbort = heartbeatEmitter ? new AbortController() : null;
|
|
7838
8278
|
const server = await startMcpServer({
|
|
7839
8279
|
graphCache,
|
|
7840
8280
|
...lspWorkspaces ? { lspWorkspaces } : {},
|
|
@@ -7842,8 +8282,14 @@ async function startMcp(options) {
|
|
|
7842
8282
|
repoIdToRoot: () => repoIdToRoot,
|
|
7843
8283
|
...options.getLspOverrides ? { getLspOverrides: options.getLspOverrides } : {},
|
|
7844
8284
|
...options.getLspEnabled ? { getLspEnabled: options.getLspEnabled } : {},
|
|
7845
|
-
|
|
8285
|
+
...heartbeatEmitter ? { heartbeat: heartbeatEmitter } : {},
|
|
8286
|
+
// T11 — undefined unless the raw-log escape hatch is set; recordLog
|
|
8287
|
+
// no-ops on an undefined logger so dispatch is unaffected.
|
|
8288
|
+
...mcpLogger ? { mcpLogger } : {}
|
|
7846
8289
|
});
|
|
8290
|
+
if (heartbeatEmitter && heartbeatAbort) {
|
|
8291
|
+
void runHeartbeatLoop(heartbeatEmitter, { signal: heartbeatAbort.signal });
|
|
8292
|
+
}
|
|
7847
8293
|
const reapInterval = lspWorkspaces ? setInterval(() => {
|
|
7848
8294
|
void lspWorkspaces.reapIdle().catch((err) => {
|
|
7849
8295
|
console.warn(`[lsp] reapIdle failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -7860,7 +8306,7 @@ async function startMcp(options) {
|
|
|
7860
8306
|
apiBaseUrl: repo.apiUrl,
|
|
7861
8307
|
getAuthToken: options.getAuthToken,
|
|
7862
8308
|
repoId: repo.repoId,
|
|
7863
|
-
targetPath:
|
|
8309
|
+
targetPath: join24(graphsDir, `${repo.repoId}.json`),
|
|
7864
8310
|
graphCache,
|
|
7865
8311
|
...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
|
|
7866
8312
|
}));
|
|
@@ -7886,6 +8332,9 @@ async function startMcp(options) {
|
|
|
7886
8332
|
return results;
|
|
7887
8333
|
},
|
|
7888
8334
|
async runMcpLogUpload() {
|
|
8335
|
+
if (!rawMcpLogEnabled) {
|
|
8336
|
+
return { uploaded: 0, skipped: "no-consent" };
|
|
8337
|
+
}
|
|
7889
8338
|
if (process.env["REPOWISE_MCP_LOG_UPLOAD_DISABLED"] === "true") {
|
|
7890
8339
|
return { uploaded: 0, skipped: "no-consent" };
|
|
7891
8340
|
}
|
|
@@ -7945,7 +8394,7 @@ async function startMcp(options) {
|
|
|
7945
8394
|
apiBaseUrl: repo.apiUrl,
|
|
7946
8395
|
getAuthToken: options.getAuthToken,
|
|
7947
8396
|
repoId: repo.repoId,
|
|
7948
|
-
targetPath:
|
|
8397
|
+
targetPath: join24(graphsDir, `${repo.repoId}.json`),
|
|
7949
8398
|
graphCache,
|
|
7950
8399
|
...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
|
|
7951
8400
|
}));
|
|
@@ -7954,6 +8403,10 @@ async function startMcp(options) {
|
|
|
7954
8403
|
async close() {
|
|
7955
8404
|
if (reapInterval)
|
|
7956
8405
|
clearInterval(reapInterval);
|
|
8406
|
+
if (heartbeatAbort)
|
|
8407
|
+
heartbeatAbort.abort();
|
|
8408
|
+
if (heartbeatEmitter)
|
|
8409
|
+
await heartbeatEmitter.flush();
|
|
7957
8410
|
await server.close();
|
|
7958
8411
|
if (lspWorkspaces) {
|
|
7959
8412
|
try {
|
|
@@ -7965,8 +8418,40 @@ async function startMcp(options) {
|
|
|
7965
8418
|
}
|
|
7966
8419
|
};
|
|
7967
8420
|
}
|
|
8421
|
+
function createHeartbeatSink(deps) {
|
|
8422
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
8423
|
+
return {
|
|
8424
|
+
async post(payload) {
|
|
8425
|
+
if (process.env["REPOWISE_MCP_HEARTBEAT_DISABLED"] === "true")
|
|
8426
|
+
return;
|
|
8427
|
+
if (!deps.apiUrl)
|
|
8428
|
+
return;
|
|
8429
|
+
if (!await isLocallyConsented(deps.flagFilePath))
|
|
8430
|
+
return;
|
|
8431
|
+
let token;
|
|
8432
|
+
try {
|
|
8433
|
+
token = await deps.getAuthToken();
|
|
8434
|
+
} catch {
|
|
8435
|
+
return;
|
|
8436
|
+
}
|
|
8437
|
+
if (!token)
|
|
8438
|
+
return;
|
|
8439
|
+
const res = await fetchImpl(`${deps.apiUrl}/v1/listeners/mcp-heartbeat`, {
|
|
8440
|
+
method: "POST",
|
|
8441
|
+
headers: {
|
|
8442
|
+
"Content-Type": "application/json",
|
|
8443
|
+
Authorization: `Bearer ${token}`
|
|
8444
|
+
},
|
|
8445
|
+
body: JSON.stringify(payload)
|
|
8446
|
+
});
|
|
8447
|
+
if (res && !res.ok) {
|
|
8448
|
+
console.warn(`[mcp-heartbeat] upload rejected (HTTP ${res.status}) \u2014 possible payload/contract drift`);
|
|
8449
|
+
}
|
|
8450
|
+
}
|
|
8451
|
+
};
|
|
8452
|
+
}
|
|
7968
8453
|
function defaultGraphsDir() {
|
|
7969
|
-
return
|
|
8454
|
+
return join24(getConfigDir(), "graphs");
|
|
7970
8455
|
}
|
|
7971
8456
|
function defaultMcpHome() {
|
|
7972
8457
|
return getConfigDir();
|
|
@@ -7984,8 +8469,8 @@ async function ensureServerConsent(opts) {
|
|
|
7984
8469
|
if (!opts.apiUrl)
|
|
7985
8470
|
return;
|
|
7986
8471
|
try {
|
|
7987
|
-
const { readFile:
|
|
7988
|
-
await
|
|
8472
|
+
const { readFile: readFile19 } = await import("fs/promises");
|
|
8473
|
+
await readFile19(opts.markerPath, "utf-8");
|
|
7989
8474
|
return;
|
|
7990
8475
|
} catch {
|
|
7991
8476
|
}
|
|
@@ -8007,10 +8492,10 @@ async function ensureServerConsent(opts) {
|
|
|
8007
8492
|
});
|
|
8008
8493
|
if (!res.ok)
|
|
8009
8494
|
return;
|
|
8010
|
-
const
|
|
8495
|
+
const fs31 = await import("fs/promises");
|
|
8011
8496
|
const path = await import("path");
|
|
8012
|
-
await
|
|
8013
|
-
await
|
|
8497
|
+
await fs31.mkdir(path.dirname(opts.markerPath), { recursive: true });
|
|
8498
|
+
await fs31.writeFile(opts.markerPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", {
|
|
8014
8499
|
encoding: "utf-8",
|
|
8015
8500
|
mode: 384
|
|
8016
8501
|
});
|
|
@@ -8024,7 +8509,7 @@ import { basename as basename3 } from "path";
|
|
|
8024
8509
|
|
|
8025
8510
|
// ../listener/dist/mcp/auto-config/writers/claude-code.js
|
|
8026
8511
|
import { promises as fs6 } from "fs";
|
|
8027
|
-
import { join as
|
|
8512
|
+
import { join as join25 } from "path";
|
|
8028
8513
|
|
|
8029
8514
|
// ../listener/dist/mcp/auto-config/markers.js
|
|
8030
8515
|
import { promises as fs5 } from "fs";
|
|
@@ -8104,10 +8589,10 @@ function safeExistingContent(path, current) {
|
|
|
8104
8589
|
var claudeCodeWriter = {
|
|
8105
8590
|
tool: "claude-code",
|
|
8106
8591
|
async detect(repoRoot, home) {
|
|
8107
|
-
return await fileExists2(
|
|
8592
|
+
return await fileExists2(join25(repoRoot, "CLAUDE.md")) || await hasClaudeBinary(home);
|
|
8108
8593
|
},
|
|
8109
8594
|
async write(ctx) {
|
|
8110
|
-
const path =
|
|
8595
|
+
const path = join25(ctx.repoRoot, ".mcp.json");
|
|
8111
8596
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8112
8597
|
const status2 = await writeMergedConfig({
|
|
8113
8598
|
path,
|
|
@@ -8117,7 +8602,7 @@ var claudeCodeWriter = {
|
|
|
8117
8602
|
return { status: status2, path };
|
|
8118
8603
|
},
|
|
8119
8604
|
async remove(ctx) {
|
|
8120
|
-
const path =
|
|
8605
|
+
const path = join25(ctx.repoRoot, ".mcp.json");
|
|
8121
8606
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
|
|
8122
8607
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8123
8608
|
}
|
|
@@ -8131,20 +8616,20 @@ async function fileExists2(path) {
|
|
|
8131
8616
|
}
|
|
8132
8617
|
}
|
|
8133
8618
|
async function hasClaudeBinary(home) {
|
|
8134
|
-
return fileExists2(
|
|
8619
|
+
return fileExists2(join25(home, ".claude", "claude.json"));
|
|
8135
8620
|
}
|
|
8136
8621
|
|
|
8137
8622
|
// ../listener/dist/mcp/auto-config/writers/claude-code-hooks.js
|
|
8138
8623
|
import { promises as fs7 } from "fs";
|
|
8139
|
-
import { join as
|
|
8624
|
+
import { join as join26 } from "path";
|
|
8140
8625
|
var claudeCodeHooksWriter = {
|
|
8141
8626
|
tool: "claude-code-hooks",
|
|
8142
8627
|
async detect(repoRoot, home) {
|
|
8143
|
-
return await fileExists3(
|
|
8628
|
+
return await fileExists3(join26(repoRoot, "CLAUDE.md")) || await fileExists3(join26(home, ".claude.json"));
|
|
8144
8629
|
},
|
|
8145
8630
|
async write(ctx) {
|
|
8146
8631
|
const result = await writeClaudeHooksToRepo(ctx.repoRoot, ctx.contextFolder ?? "repowise-context", ctx.variant);
|
|
8147
|
-
return { status: result.status, path:
|
|
8632
|
+
return { status: result.status, path: join26(ctx.repoRoot, result.relPath) };
|
|
8148
8633
|
},
|
|
8149
8634
|
async remove(ctx) {
|
|
8150
8635
|
await removeClaudeHooksFromRepo(ctx.repoRoot);
|
|
@@ -8161,14 +8646,14 @@ async function fileExists3(path) {
|
|
|
8161
8646
|
|
|
8162
8647
|
// ../listener/dist/mcp/auto-config/writers/cline.js
|
|
8163
8648
|
import { promises as fs8 } from "fs";
|
|
8164
|
-
import { join as
|
|
8649
|
+
import { join as join27 } from "path";
|
|
8165
8650
|
var clineWriter = {
|
|
8166
8651
|
tool: "cline",
|
|
8167
8652
|
async detect(repoRoot, home) {
|
|
8168
|
-
return await fileExists4(
|
|
8653
|
+
return await fileExists4(join27(repoRoot, ".clinerules")) || await fileExists4(join27(home, ".cline"));
|
|
8169
8654
|
},
|
|
8170
8655
|
async write(ctx) {
|
|
8171
|
-
const path =
|
|
8656
|
+
const path = join27(ctx.home, ".cline", "mcp.json");
|
|
8172
8657
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8173
8658
|
const status2 = await writeMergedConfig({
|
|
8174
8659
|
path,
|
|
@@ -8178,7 +8663,7 @@ var clineWriter = {
|
|
|
8178
8663
|
return { status: status2, path };
|
|
8179
8664
|
},
|
|
8180
8665
|
async remove(ctx) {
|
|
8181
|
-
const path =
|
|
8666
|
+
const path = join27(ctx.home, ".cline", "mcp.json");
|
|
8182
8667
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
|
|
8183
8668
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8184
8669
|
}
|
|
@@ -8194,13 +8679,13 @@ async function fileExists4(path) {
|
|
|
8194
8679
|
|
|
8195
8680
|
// ../listener/dist/mcp/auto-config/writers/cline-hooks.js
|
|
8196
8681
|
import { promises as fs9 } from "fs";
|
|
8197
|
-
import { join as
|
|
8682
|
+
import { join as join28 } from "path";
|
|
8198
8683
|
var SCRIPT_REL = ".clinerules/hooks/UserPromptSubmit";
|
|
8199
8684
|
var OWNERSHIP_MARKER = "managed by RepoWise";
|
|
8200
8685
|
var clineHooksWriter = {
|
|
8201
8686
|
tool: "cline-hooks",
|
|
8202
8687
|
async detect(repoRoot) {
|
|
8203
|
-
return fileExists5(
|
|
8688
|
+
return fileExists5(join28(repoRoot, ".clinerules"));
|
|
8204
8689
|
},
|
|
8205
8690
|
async write(ctx) {
|
|
8206
8691
|
if (await isGitTracked(ctx.repoRoot, SCRIPT_REL)) {
|
|
@@ -8210,7 +8695,7 @@ var clineHooksWriter = {
|
|
|
8210
8695
|
reason: `${SCRIPT_REL} is git-tracked; update it manually to opt in`
|
|
8211
8696
|
};
|
|
8212
8697
|
}
|
|
8213
|
-
const path =
|
|
8698
|
+
const path = join28(ctx.repoRoot, SCRIPT_REL);
|
|
8214
8699
|
const next = buildClineHookScript({
|
|
8215
8700
|
repoName: ctx.repoName,
|
|
8216
8701
|
contextFolder: ctx.contextFolder ?? "repowise-context",
|
|
@@ -8227,14 +8712,14 @@ var clineHooksWriter = {
|
|
|
8227
8712
|
await ensureExecutable(path);
|
|
8228
8713
|
return { status: "unchanged", path };
|
|
8229
8714
|
}
|
|
8230
|
-
await fs9.mkdir(
|
|
8715
|
+
await fs9.mkdir(join28(ctx.repoRoot, ".clinerules", "hooks"), { recursive: true });
|
|
8231
8716
|
await fs9.writeFile(path, next, { encoding: "utf-8", mode: 493 });
|
|
8232
8717
|
await ensureExecutable(path);
|
|
8233
8718
|
await applyGitignoreChanges(ctx.repoRoot, { add: [SCRIPT_REL] });
|
|
8234
8719
|
return { status: "written", path };
|
|
8235
8720
|
},
|
|
8236
8721
|
async remove(ctx) {
|
|
8237
|
-
const path =
|
|
8722
|
+
const path = join28(ctx.repoRoot, SCRIPT_REL);
|
|
8238
8723
|
const existing = await readOrNull(path);
|
|
8239
8724
|
if (existing !== null && existing.includes(OWNERSHIP_MARKER)) {
|
|
8240
8725
|
await fs9.unlink(path);
|
|
@@ -8269,14 +8754,14 @@ async function readOrNull(path) {
|
|
|
8269
8754
|
|
|
8270
8755
|
// ../listener/dist/mcp/auto-config/writers/codex.js
|
|
8271
8756
|
import { promises as fs10 } from "fs";
|
|
8272
|
-
import { join as
|
|
8757
|
+
import { join as join29 } from "path";
|
|
8273
8758
|
var codexWriter = {
|
|
8274
8759
|
tool: "codex",
|
|
8275
8760
|
async detect(_repoRoot, home) {
|
|
8276
|
-
return fileExists6(
|
|
8761
|
+
return fileExists6(join29(home, ".codex"));
|
|
8277
8762
|
},
|
|
8278
8763
|
async write(ctx) {
|
|
8279
|
-
const path =
|
|
8764
|
+
const path = join29(ctx.home, ".codex", "mcp.json");
|
|
8280
8765
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8281
8766
|
const status2 = await writeMergedConfig({
|
|
8282
8767
|
path,
|
|
@@ -8286,7 +8771,7 @@ var codexWriter = {
|
|
|
8286
8771
|
return { status: status2, path };
|
|
8287
8772
|
},
|
|
8288
8773
|
async remove(ctx) {
|
|
8289
|
-
const path =
|
|
8774
|
+
const path = join29(ctx.home, ".codex", "mcp.json");
|
|
8290
8775
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
|
|
8291
8776
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8292
8777
|
}
|
|
@@ -8302,15 +8787,15 @@ async function fileExists6(path) {
|
|
|
8302
8787
|
|
|
8303
8788
|
// ../listener/dist/mcp/auto-config/writers/codex-hooks.js
|
|
8304
8789
|
import { promises as fs11 } from "fs";
|
|
8305
|
-
import { join as
|
|
8790
|
+
import { join as join30 } from "path";
|
|
8306
8791
|
var HOOKS_REL = ".codex/hooks.json";
|
|
8307
8792
|
var codexHooksWriter = {
|
|
8308
8793
|
tool: "codex-hooks",
|
|
8309
8794
|
async detect(repoRoot, home) {
|
|
8310
|
-
return await fileExists7(
|
|
8795
|
+
return await fileExists7(join30(home, ".codex")) || await fileExists7(join30(repoRoot, ".codex"));
|
|
8311
8796
|
},
|
|
8312
8797
|
async write(ctx) {
|
|
8313
|
-
const repoSignal = await fileExists7(
|
|
8798
|
+
const repoSignal = await fileExists7(join30(ctx.repoRoot, ".codex"));
|
|
8314
8799
|
const selected = ctx.selectedTools?.includes("codex") ?? false;
|
|
8315
8800
|
if (!repoSignal && !selected) {
|
|
8316
8801
|
return { status: "skipped", reason: "no repo-scoped Codex signal and not selected" };
|
|
@@ -8322,7 +8807,7 @@ var codexHooksWriter = {
|
|
|
8322
8807
|
reason: `${HOOKS_REL} is git-tracked; add the RepoWise UserPromptSubmit hook manually to opt in`
|
|
8323
8808
|
};
|
|
8324
8809
|
}
|
|
8325
|
-
const path =
|
|
8810
|
+
const path = join30(ctx.repoRoot, HOOKS_REL);
|
|
8326
8811
|
const existing = await readOrNull2(path);
|
|
8327
8812
|
const merged = mergeCodexHookSettings(existing, {
|
|
8328
8813
|
repoName: ctx.repoName,
|
|
@@ -8332,13 +8817,13 @@ var codexHooksWriter = {
|
|
|
8332
8817
|
if (merged === existing) {
|
|
8333
8818
|
return { status: "unchanged", path };
|
|
8334
8819
|
}
|
|
8335
|
-
await fs11.mkdir(
|
|
8820
|
+
await fs11.mkdir(join30(ctx.repoRoot, ".codex"), { recursive: true });
|
|
8336
8821
|
await fs11.writeFile(path, merged, "utf-8");
|
|
8337
8822
|
await applyGitignoreChanges(ctx.repoRoot, { add: [HOOKS_REL] });
|
|
8338
8823
|
return { status: "written", path };
|
|
8339
8824
|
},
|
|
8340
8825
|
async remove(ctx) {
|
|
8341
|
-
const path =
|
|
8826
|
+
const path = join30(ctx.repoRoot, HOOKS_REL);
|
|
8342
8827
|
const existing = await readOrNull2(path);
|
|
8343
8828
|
if (existing !== null) {
|
|
8344
8829
|
const next = removeCodexHookSettings(existing);
|
|
@@ -8369,14 +8854,14 @@ async function readOrNull2(path) {
|
|
|
8369
8854
|
|
|
8370
8855
|
// ../listener/dist/mcp/auto-config/writers/copilot.js
|
|
8371
8856
|
import { promises as fs12 } from "fs";
|
|
8372
|
-
import { join as
|
|
8857
|
+
import { join as join31 } from "path";
|
|
8373
8858
|
var copilotWriter = {
|
|
8374
8859
|
tool: "copilot",
|
|
8375
8860
|
async detect(repoRoot) {
|
|
8376
|
-
return fileExists8(
|
|
8861
|
+
return fileExists8(join31(repoRoot, ".vscode"));
|
|
8377
8862
|
},
|
|
8378
8863
|
async write(ctx) {
|
|
8379
|
-
const path =
|
|
8864
|
+
const path = join31(ctx.repoRoot, ".vscode", "mcp.json");
|
|
8380
8865
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8381
8866
|
const status2 = await writeMergedConfig({
|
|
8382
8867
|
path,
|
|
@@ -8386,7 +8871,7 @@ var copilotWriter = {
|
|
|
8386
8871
|
return { status: status2, path };
|
|
8387
8872
|
},
|
|
8388
8873
|
async remove(ctx) {
|
|
8389
|
-
const path =
|
|
8874
|
+
const path = join31(ctx.repoRoot, ".vscode", "mcp.json");
|
|
8390
8875
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
|
|
8391
8876
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8392
8877
|
}
|
|
@@ -8402,15 +8887,15 @@ async function fileExists8(path) {
|
|
|
8402
8887
|
|
|
8403
8888
|
// ../listener/dist/mcp/auto-config/writers/copilot-hooks.js
|
|
8404
8889
|
import { promises as fs13 } from "fs";
|
|
8405
|
-
import { join as
|
|
8890
|
+
import { join as join32 } from "path";
|
|
8406
8891
|
var HOOKS_REL2 = ".github/hooks/repowise.json";
|
|
8407
8892
|
var copilotHooksWriter = {
|
|
8408
8893
|
tool: "copilot-hooks",
|
|
8409
8894
|
async detect(repoRoot) {
|
|
8410
|
-
return fileExists9(
|
|
8895
|
+
return fileExists9(join32(repoRoot, ".github", "copilot-instructions.md"));
|
|
8411
8896
|
},
|
|
8412
8897
|
async write(ctx) {
|
|
8413
|
-
const path =
|
|
8898
|
+
const path = join32(ctx.repoRoot, HOOKS_REL2);
|
|
8414
8899
|
const next = buildCopilotHooksFile({
|
|
8415
8900
|
repoName: ctx.repoName,
|
|
8416
8901
|
contextFolder: ctx.contextFolder ?? "repowise-context",
|
|
@@ -8430,14 +8915,14 @@ var copilotHooksWriter = {
|
|
|
8430
8915
|
if (existing === next) {
|
|
8431
8916
|
return { status: "unchanged", path };
|
|
8432
8917
|
}
|
|
8433
|
-
await fs13.mkdir(
|
|
8918
|
+
await fs13.mkdir(join32(ctx.repoRoot, ".github", "hooks"), { recursive: true });
|
|
8434
8919
|
await fs13.writeFile(path, next, "utf-8");
|
|
8435
8920
|
const userSelected = ctx.selectedTools?.includes("copilot") ?? false;
|
|
8436
8921
|
await applyGitignoreChanges(ctx.repoRoot, userSelected ? { remove: [HOOKS_REL2] } : { add: [HOOKS_REL2] });
|
|
8437
8922
|
return { status: "written", path };
|
|
8438
8923
|
},
|
|
8439
8924
|
async remove(ctx) {
|
|
8440
|
-
const path =
|
|
8925
|
+
const path = join32(ctx.repoRoot, HOOKS_REL2);
|
|
8441
8926
|
if (!await isGitTracked(ctx.repoRoot, HOOKS_REL2)) {
|
|
8442
8927
|
await fs13.unlink(path).catch(() => {
|
|
8443
8928
|
});
|
|
@@ -8463,14 +8948,14 @@ async function readOrNull3(path) {
|
|
|
8463
8948
|
|
|
8464
8949
|
// ../listener/dist/mcp/auto-config/writers/cursor.js
|
|
8465
8950
|
import { promises as fs14 } from "fs";
|
|
8466
|
-
import { join as
|
|
8951
|
+
import { join as join33 } from "path";
|
|
8467
8952
|
var cursorWriter = {
|
|
8468
8953
|
tool: "cursor",
|
|
8469
8954
|
async detect(repoRoot) {
|
|
8470
|
-
return await fileExists10(
|
|
8955
|
+
return await fileExists10(join33(repoRoot, ".cursor")) || await fileExists10(join33(repoRoot, ".cursorrules"));
|
|
8471
8956
|
},
|
|
8472
8957
|
async write(ctx) {
|
|
8473
|
-
const path =
|
|
8958
|
+
const path = join33(ctx.repoRoot, ".cursor", "mcp.json");
|
|
8474
8959
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8475
8960
|
const status2 = await writeMergedConfig({
|
|
8476
8961
|
path,
|
|
@@ -8480,7 +8965,7 @@ var cursorWriter = {
|
|
|
8480
8965
|
return { status: status2, path };
|
|
8481
8966
|
},
|
|
8482
8967
|
async remove(ctx) {
|
|
8483
|
-
const path =
|
|
8968
|
+
const path = join33(ctx.repoRoot, ".cursor", "mcp.json");
|
|
8484
8969
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
|
|
8485
8970
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8486
8971
|
}
|
|
@@ -8496,14 +8981,14 @@ async function fileExists10(path) {
|
|
|
8496
8981
|
|
|
8497
8982
|
// ../listener/dist/mcp/auto-config/writers/gemini-cli.js
|
|
8498
8983
|
import { promises as fs15 } from "fs";
|
|
8499
|
-
import { join as
|
|
8984
|
+
import { join as join34 } from "path";
|
|
8500
8985
|
var geminiCliWriter = {
|
|
8501
8986
|
tool: "gemini-cli",
|
|
8502
8987
|
async detect(_repoRoot, home) {
|
|
8503
|
-
return fileExists11(
|
|
8988
|
+
return fileExists11(join34(home, ".gemini"));
|
|
8504
8989
|
},
|
|
8505
8990
|
async write(ctx) {
|
|
8506
|
-
const path =
|
|
8991
|
+
const path = join34(ctx.home, ".gemini", "settings.json");
|
|
8507
8992
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8508
8993
|
const status2 = await writeMergedConfig({
|
|
8509
8994
|
path,
|
|
@@ -8513,7 +8998,7 @@ var geminiCliWriter = {
|
|
|
8513
8998
|
return { status: status2, path };
|
|
8514
8999
|
},
|
|
8515
9000
|
async remove(ctx) {
|
|
8516
|
-
const path =
|
|
9001
|
+
const path = join34(ctx.home, ".gemini", "settings.json");
|
|
8517
9002
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
|
|
8518
9003
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8519
9004
|
}
|
|
@@ -8529,15 +9014,15 @@ async function fileExists11(path) {
|
|
|
8529
9014
|
|
|
8530
9015
|
// ../listener/dist/mcp/auto-config/writers/gemini-hooks.js
|
|
8531
9016
|
import { promises as fs16 } from "fs";
|
|
8532
|
-
import { join as
|
|
9017
|
+
import { join as join35 } from "path";
|
|
8533
9018
|
var SETTINGS_REL = ".gemini/settings.json";
|
|
8534
9019
|
var geminiHooksWriter = {
|
|
8535
9020
|
tool: "gemini-hooks",
|
|
8536
9021
|
async detect(repoRoot, home) {
|
|
8537
|
-
return await fileExists12(
|
|
9022
|
+
return await fileExists12(join35(home, ".gemini")) || await fileExists12(join35(repoRoot, ".gemini"));
|
|
8538
9023
|
},
|
|
8539
9024
|
async write(ctx) {
|
|
8540
|
-
const repoSignal = await fileExists12(
|
|
9025
|
+
const repoSignal = await fileExists12(join35(ctx.repoRoot, ".gemini")) || await fileExists12(join35(ctx.repoRoot, "GEMINI.md"));
|
|
8541
9026
|
const selected = ctx.selectedTools?.includes("gemini") ?? false;
|
|
8542
9027
|
if (!repoSignal && !selected) {
|
|
8543
9028
|
return { status: "skipped", reason: "no repo-scoped Gemini signal and not selected" };
|
|
@@ -8549,7 +9034,7 @@ var geminiHooksWriter = {
|
|
|
8549
9034
|
reason: `${SETTINGS_REL} is git-tracked; add the RepoWise BeforeAgent hook manually to opt in`
|
|
8550
9035
|
};
|
|
8551
9036
|
}
|
|
8552
|
-
const path =
|
|
9037
|
+
const path = join35(ctx.repoRoot, SETTINGS_REL);
|
|
8553
9038
|
const existing = await readOrNull4(path);
|
|
8554
9039
|
const merged = mergeGeminiHookSettings(existing, {
|
|
8555
9040
|
repoName: ctx.repoName,
|
|
@@ -8559,13 +9044,13 @@ var geminiHooksWriter = {
|
|
|
8559
9044
|
if (merged === existing) {
|
|
8560
9045
|
return { status: "unchanged", path };
|
|
8561
9046
|
}
|
|
8562
|
-
await fs16.mkdir(
|
|
9047
|
+
await fs16.mkdir(join35(ctx.repoRoot, ".gemini"), { recursive: true });
|
|
8563
9048
|
await fs16.writeFile(path, merged, "utf-8");
|
|
8564
9049
|
await applyGitignoreChanges(ctx.repoRoot, { add: [SETTINGS_REL] });
|
|
8565
9050
|
return { status: "written", path };
|
|
8566
9051
|
},
|
|
8567
9052
|
async remove(ctx) {
|
|
8568
|
-
const path =
|
|
9053
|
+
const path = join35(ctx.repoRoot, SETTINGS_REL);
|
|
8569
9054
|
const existing = await readOrNull4(path);
|
|
8570
9055
|
if (existing !== null) {
|
|
8571
9056
|
const next = removeGeminiHookSettings(existing);
|
|
@@ -8596,14 +9081,14 @@ async function readOrNull4(path) {
|
|
|
8596
9081
|
|
|
8597
9082
|
// ../listener/dist/mcp/auto-config/writers/roo.js
|
|
8598
9083
|
import { promises as fs17 } from "fs";
|
|
8599
|
-
import { join as
|
|
9084
|
+
import { join as join36 } from "path";
|
|
8600
9085
|
var rooWriter = {
|
|
8601
9086
|
tool: "roo",
|
|
8602
9087
|
async detect(repoRoot, home) {
|
|
8603
|
-
return await fileExists13(
|
|
9088
|
+
return await fileExists13(join36(repoRoot, ".roo")) || await fileExists13(join36(home, ".roo"));
|
|
8604
9089
|
},
|
|
8605
9090
|
async write(ctx) {
|
|
8606
|
-
const repoConfig =
|
|
9091
|
+
const repoConfig = join36(ctx.repoRoot, ".roo", "mcp.json");
|
|
8607
9092
|
const before = await readOrNull5(repoConfig);
|
|
8608
9093
|
await this.remove(ctx);
|
|
8609
9094
|
const after = await readOrNull5(repoConfig);
|
|
@@ -8613,9 +9098,9 @@ var rooWriter = {
|
|
|
8613
9098
|
};
|
|
8614
9099
|
},
|
|
8615
9100
|
async remove(ctx) {
|
|
8616
|
-
const repoConfig =
|
|
8617
|
-
const homeConfig =
|
|
8618
|
-
const homeSettings =
|
|
9101
|
+
const repoConfig = join36(ctx.repoRoot, ".roo", "mcp.json");
|
|
9102
|
+
const homeConfig = join36(ctx.home, ".roo", "mcp.json");
|
|
9103
|
+
const homeSettings = join36(ctx.home, ".roo", "settings.json");
|
|
8619
9104
|
for (const path of [repoConfig, homeConfig, homeSettings]) {
|
|
8620
9105
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`).catch(() => {
|
|
8621
9106
|
});
|
|
@@ -8642,14 +9127,14 @@ async function readOrNull5(path) {
|
|
|
8642
9127
|
|
|
8643
9128
|
// ../listener/dist/mcp/auto-config/writers/windsurf.js
|
|
8644
9129
|
import { promises as fs18 } from "fs";
|
|
8645
|
-
import { join as
|
|
9130
|
+
import { join as join37 } from "path";
|
|
8646
9131
|
var windsurfWriter = {
|
|
8647
9132
|
tool: "windsurf",
|
|
8648
9133
|
async detect(_repoRoot, home) {
|
|
8649
|
-
return fileExists14(
|
|
9134
|
+
return fileExists14(join37(home, ".codeium", "windsurf"));
|
|
8650
9135
|
},
|
|
8651
9136
|
async write(ctx) {
|
|
8652
|
-
const path =
|
|
9137
|
+
const path = join37(ctx.home, ".codeium", "windsurf", "mcp_config.json");
|
|
8653
9138
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8654
9139
|
const status2 = await writeMergedConfig({
|
|
8655
9140
|
path,
|
|
@@ -8659,7 +9144,7 @@ var windsurfWriter = {
|
|
|
8659
9144
|
return { status: status2, path };
|
|
8660
9145
|
},
|
|
8661
9146
|
async remove(ctx) {
|
|
8662
|
-
const path =
|
|
9147
|
+
const path = join37(ctx.home, ".codeium", "windsurf", "mcp_config.json");
|
|
8663
9148
|
await removeFromConfig(path, `RepoWise MCP for ${ctx.repoName}`);
|
|
8664
9149
|
await removeFromConfig(path, `repowise-${ctx.repoId}`);
|
|
8665
9150
|
}
|
|
@@ -8741,7 +9226,7 @@ init_installer();
|
|
|
8741
9226
|
init_registry();
|
|
8742
9227
|
init_lsp_tools();
|
|
8743
9228
|
import { promises as fs19 } from "fs";
|
|
8744
|
-
import { join as
|
|
9229
|
+
import { join as join38 } from "path";
|
|
8745
9230
|
async function collectRepresentativeFiles(repoRoot) {
|
|
8746
9231
|
const reps = /* @__PURE__ */ new Map();
|
|
8747
9232
|
let inspected = 0;
|
|
@@ -8782,7 +9267,7 @@ async function collectRepresentativeFiles(repoRoot) {
|
|
|
8782
9267
|
continue;
|
|
8783
9268
|
if (name === "node_modules" || name === "dist" || name === "build")
|
|
8784
9269
|
continue;
|
|
8785
|
-
const sub =
|
|
9270
|
+
const sub = join38(repoRoot, name);
|
|
8786
9271
|
try {
|
|
8787
9272
|
const stat8 = await fs19.stat(sub);
|
|
8788
9273
|
if (stat8.isDirectory())
|
|
@@ -8830,7 +9315,7 @@ init_config_dir();
|
|
|
8830
9315
|
import { promises as fs20 } from "fs";
|
|
8831
9316
|
import { spawn as spawn9, execFile as execFile5 } from "child_process";
|
|
8832
9317
|
import { createWriteStream as createWriteStream2 } from "fs";
|
|
8833
|
-
import { join as
|
|
9318
|
+
import { join as join39 } from "path";
|
|
8834
9319
|
import { promisify as promisify3 } from "util";
|
|
8835
9320
|
var execFileAsync3 = promisify3(execFile5);
|
|
8836
9321
|
async function isCommandOnPath2(cmd) {
|
|
@@ -8894,7 +9379,7 @@ async function maybeWriteClangdStub(repoRoot, repoId) {
|
|
|
8894
9379
|
if (e.isDirectory()) {
|
|
8895
9380
|
if (skip.has(e.name) || e.name.startsWith("."))
|
|
8896
9381
|
continue;
|
|
8897
|
-
await rec(
|
|
9382
|
+
await rec(join39(dir, e.name));
|
|
8898
9383
|
continue;
|
|
8899
9384
|
}
|
|
8900
9385
|
const dot = e.name.lastIndexOf(".");
|
|
@@ -8903,7 +9388,7 @@ async function maybeWriteClangdStub(repoRoot, repoId) {
|
|
|
8903
9388
|
const ext = e.name.slice(dot);
|
|
8904
9389
|
if (!C_EXTS.has(ext) && !CPP_EXTS.has(ext))
|
|
8905
9390
|
continue;
|
|
8906
|
-
sources.push({ path:
|
|
9391
|
+
sources.push({ path: join39(dir, e.name), ext });
|
|
8907
9392
|
}
|
|
8908
9393
|
}
|
|
8909
9394
|
await rec(repoRoot);
|
|
@@ -8919,9 +9404,9 @@ async function maybeWriteClangdStub(repoRoot, repoId) {
|
|
|
8919
9404
|
command: `${driver} ${std} -I. -I./include -c "${src}"`
|
|
8920
9405
|
};
|
|
8921
9406
|
});
|
|
8922
|
-
const outDir =
|
|
9407
|
+
const outDir = join39(repoRoot, ".repowise");
|
|
8923
9408
|
await fs20.mkdir(outDir, { recursive: true });
|
|
8924
|
-
const outPath =
|
|
9409
|
+
const outPath = join39(outDir, "compile_commands.json");
|
|
8925
9410
|
await fs20.writeFile(outPath, JSON.stringify(entries, null, 2));
|
|
8926
9411
|
const capHit = sources.length >= MAX_STUB_SOURCES;
|
|
8927
9412
|
console.log(`[workspace-prep] ${repoId}: wrote ${entries.length.toString()}-entry compile_commands stub to .repowise/${capHit ? " (cap reached \u2014 additional sources truncated)" : ""}`);
|
|
@@ -8930,7 +9415,7 @@ async function maybeWriteClangdStub(repoRoot, repoId) {
|
|
|
8930
9415
|
var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
8931
9416
|
async function fileExists15(repoRoot, name) {
|
|
8932
9417
|
try {
|
|
8933
|
-
await fs20.access(
|
|
9418
|
+
await fs20.access(join39(repoRoot, name));
|
|
8934
9419
|
return true;
|
|
8935
9420
|
} catch {
|
|
8936
9421
|
return false;
|
|
@@ -8941,7 +9426,7 @@ async function detectWorkspaceDeps(repoRoot) {
|
|
|
8941
9426
|
if (await fileExists15(repoRoot, "package.json")) {
|
|
8942
9427
|
let pkgRaw = "";
|
|
8943
9428
|
try {
|
|
8944
|
-
pkgRaw = await fs20.readFile(
|
|
9429
|
+
pkgRaw = await fs20.readFile(join39(repoRoot, "package.json"), "utf8");
|
|
8945
9430
|
} catch {
|
|
8946
9431
|
}
|
|
8947
9432
|
if (pkgRaw) {
|
|
@@ -9044,7 +9529,7 @@ async function runWorkspacePreps(opts) {
|
|
|
9044
9529
|
if (opts.missing.length === 0)
|
|
9045
9530
|
return [];
|
|
9046
9531
|
const safeRepoId = opts.repoId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
9047
|
-
const logPath2 =
|
|
9532
|
+
const logPath2 = join39(getConfigDir(), `install-log.${safeRepoId}.txt`);
|
|
9048
9533
|
await fs20.mkdir(getConfigDir(), { recursive: true });
|
|
9049
9534
|
const stream = opts.logStream ?? createWriteStream2(logPath2, { flags: "a" });
|
|
9050
9535
|
stream.on("error", () => {
|
|
@@ -9195,7 +9680,7 @@ async function prepareWorkspaceDepsForRepos(repos) {
|
|
|
9195
9680
|
init_registry();
|
|
9196
9681
|
import { execFile as execFileCb, spawn as spawn10 } from "child_process";
|
|
9197
9682
|
import { access as access3, readdir as readdir4 } from "fs/promises";
|
|
9198
|
-
import { join as
|
|
9683
|
+
import { join as join40 } from "path";
|
|
9199
9684
|
import { promisify as promisify4 } from "util";
|
|
9200
9685
|
var execFile6 = promisify4(execFileCb);
|
|
9201
9686
|
var DEFAULT_BUILD_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
@@ -9248,7 +9733,7 @@ async function ensureIndexable(indexRoot, language, options = {}) {
|
|
|
9248
9733
|
}
|
|
9249
9734
|
}
|
|
9250
9735
|
try {
|
|
9251
|
-
await access3(
|
|
9736
|
+
await access3(join40(indexRoot, spec.indexStoreProbe));
|
|
9252
9737
|
return { ready: true, built: false };
|
|
9253
9738
|
} catch {
|
|
9254
9739
|
}
|
|
@@ -9982,13 +10467,13 @@ init_config_dir();
|
|
|
9982
10467
|
import { execFile as execFileCb3 } from "child_process";
|
|
9983
10468
|
import { createHash as createHash5 } from "crypto";
|
|
9984
10469
|
import { mkdir as mkdir15, readdir as readdir5, rm, stat as stat6 } from "fs/promises";
|
|
9985
|
-
import { join as
|
|
10470
|
+
import { join as join41 } from "path";
|
|
9986
10471
|
import { promisify as promisify6 } from "util";
|
|
9987
10472
|
var execFile8 = promisify6(execFileCb3);
|
|
9988
10473
|
var GIT_OP_TIMEOUT_MS = 6e4;
|
|
9989
10474
|
var MAX_CONCURRENT_WORKTREES = 3;
|
|
9990
10475
|
function worktreeBaseDir() {
|
|
9991
|
-
return
|
|
10476
|
+
return join41(getConfigDir(), "lsp-worktrees");
|
|
9992
10477
|
}
|
|
9993
10478
|
async function reapOrphanWorktrees(maxAgeMs = 24 * 60 * 60 * 1e3, base = worktreeBaseDir()) {
|
|
9994
10479
|
let reaped = 0;
|
|
@@ -10000,7 +10485,7 @@ async function reapOrphanWorktrees(maxAgeMs = 24 * 60 * 60 * 1e3, base = worktre
|
|
|
10000
10485
|
}
|
|
10001
10486
|
const cutoff = Date.now() - maxAgeMs;
|
|
10002
10487
|
for (const entry of entries) {
|
|
10003
|
-
const dir =
|
|
10488
|
+
const dir = join41(base, entry);
|
|
10004
10489
|
try {
|
|
10005
10490
|
const info = await stat6(dir);
|
|
10006
10491
|
if (!info.isDirectory() || info.mtimeMs > cutoff)
|
|
@@ -10033,7 +10518,7 @@ async function prepareCommittedTree(repoRoot, commitSha, options = {}) {
|
|
|
10033
10518
|
}
|
|
10034
10519
|
const base = options.worktreeBase ?? worktreeBaseDir();
|
|
10035
10520
|
const dirName = createHash5("sha256").update(`${repoRoot}\0${commitSha}`).digest("hex").slice(0, 16) + "-" + commitSha.slice(0, 12);
|
|
10036
|
-
const worktreePath =
|
|
10521
|
+
const worktreePath = join41(base, dirName);
|
|
10037
10522
|
try {
|
|
10038
10523
|
await mkdir15(base, { recursive: true });
|
|
10039
10524
|
await git(["-C", repoRoot, "worktree", "prune"]).catch(() => void 0);
|
|
@@ -10369,8 +10854,8 @@ var CRASH_LOOP_WINDOW_MS = 3e4;
|
|
|
10369
10854
|
var CRASH_LOOP_THRESHOLD = 3;
|
|
10370
10855
|
async function readRawToolConfig() {
|
|
10371
10856
|
try {
|
|
10372
|
-
const configPath =
|
|
10373
|
-
const data = await
|
|
10857
|
+
const configPath = join42(getConfigDir(), "config.json");
|
|
10858
|
+
const data = await readFile14(configPath, "utf-8");
|
|
10374
10859
|
const raw = JSON.parse(data);
|
|
10375
10860
|
return {
|
|
10376
10861
|
aiTools: Array.isArray(raw["aiTools"]) ? raw["aiTools"] : void 0,
|
|
@@ -10396,10 +10881,9 @@ async function updateToolConfigsForRepo(localPath, config2, state, repoId) {
|
|
|
10396
10881
|
}
|
|
10397
10882
|
await migrateRooToKilo(localPath).catch(() => {
|
|
10398
10883
|
});
|
|
10399
|
-
const variant = config2.graphOnly ? "graph" : "full";
|
|
10400
10884
|
const contextFiles = await scanLocalContextFiles(localPath, contextFolder);
|
|
10401
|
-
|
|
10402
|
-
|
|
10885
|
+
const marker = await readDocsMarker(localPath, contextFolder);
|
|
10886
|
+
const variant = effectiveToolVariant(!!config2.graphOnly, contextFiles.length > 0, marker, state.repos[repoId]?.lastSyncCommitSha);
|
|
10403
10887
|
const hash = JSON.stringify({
|
|
10404
10888
|
rev: AI_TOOLS_REFERENCE_REV,
|
|
10405
10889
|
block: AGENT_INSTRUCTIONS_HASH,
|
|
@@ -10426,10 +10910,15 @@ async function updateToolConfigsForRepo(localPath, config2, state, repoId) {
|
|
|
10426
10910
|
if (!written.has("AGENTS.md")) {
|
|
10427
10911
|
await updateToolConfig(localPath, "codex", repoName, contextFolder, contextFiles, variant);
|
|
10428
10912
|
}
|
|
10913
|
+
const prevToolVariant = state.repos[repoId]?.lastToolVariant;
|
|
10429
10914
|
if (!state.repos[repoId]) {
|
|
10430
10915
|
state.repos[repoId] = { lastSyncTimestamp: (/* @__PURE__ */ new Date()).toISOString(), lastSyncCommitSha: null };
|
|
10431
10916
|
}
|
|
10432
10917
|
state.repos[repoId].lastToolConfigHash = hash;
|
|
10918
|
+
state.repos[repoId].lastToolVariant = variant;
|
|
10919
|
+
if (!config2.graphOnly && variant === "full" && prevToolVariant === "graph") {
|
|
10920
|
+
notifyDocsReady(repoId);
|
|
10921
|
+
}
|
|
10433
10922
|
console.log(`[ai-tools] Updated tool configs for ${repoId}`);
|
|
10434
10923
|
}
|
|
10435
10924
|
var running = false;
|
|
@@ -10446,8 +10935,8 @@ function resolveAuditRoots() {
|
|
|
10446
10935
|
const out = /* @__PURE__ */ new Set();
|
|
10447
10936
|
let dir = dirname17(process.execPath);
|
|
10448
10937
|
for (let i = 0; i < 8; i += 1) {
|
|
10449
|
-
out.add(
|
|
10450
|
-
out.add(
|
|
10938
|
+
out.add(join42(dir, "node_modules"));
|
|
10939
|
+
out.add(join42(dir, "lib", "node_modules"));
|
|
10451
10940
|
const parent = dirname17(dir);
|
|
10452
10941
|
if (parent === dir)
|
|
10453
10942
|
break;
|
|
@@ -10510,15 +10999,72 @@ function sleep(ms) {
|
|
|
10510
10999
|
}, ms);
|
|
10511
11000
|
});
|
|
10512
11001
|
}
|
|
10513
|
-
function
|
|
11002
|
+
async function waitForCredentials(isRunning2) {
|
|
11003
|
+
let changed = false;
|
|
11004
|
+
let watcher = null;
|
|
10514
11005
|
try {
|
|
10515
|
-
const
|
|
10516
|
-
|
|
11006
|
+
const fs31 = await import("fs");
|
|
11007
|
+
watcher = fs31.watch(getConfigDir(), (_event, filename) => {
|
|
11008
|
+
if (!filename || filename === "credentials.json")
|
|
11009
|
+
changed = true;
|
|
11010
|
+
});
|
|
10517
11011
|
} catch {
|
|
10518
|
-
return null;
|
|
10519
11012
|
}
|
|
10520
|
-
|
|
10521
|
-
|
|
11013
|
+
try {
|
|
11014
|
+
while (isRunning2()) {
|
|
11015
|
+
await sleep(changed ? 0 : 1e4);
|
|
11016
|
+
changed = false;
|
|
11017
|
+
if (!isRunning2())
|
|
11018
|
+
break;
|
|
11019
|
+
const fresh = await getValidCredentials({ forceRefresh: true });
|
|
11020
|
+
if (fresh)
|
|
11021
|
+
return fresh;
|
|
11022
|
+
}
|
|
11023
|
+
return null;
|
|
11024
|
+
} finally {
|
|
11025
|
+
if (watcher)
|
|
11026
|
+
watcher.close();
|
|
11027
|
+
}
|
|
11028
|
+
}
|
|
11029
|
+
async function waitForCredentialsOrTerminal(isRunning2) {
|
|
11030
|
+
let changed = false;
|
|
11031
|
+
let watcher = null;
|
|
11032
|
+
try {
|
|
11033
|
+
const fs31 = await import("fs");
|
|
11034
|
+
watcher = fs31.watch(getConfigDir(), (_event, filename) => {
|
|
11035
|
+
if (!filename || filename === "credentials.json")
|
|
11036
|
+
changed = true;
|
|
11037
|
+
});
|
|
11038
|
+
} catch {
|
|
11039
|
+
}
|
|
11040
|
+
try {
|
|
11041
|
+
while (isRunning2()) {
|
|
11042
|
+
await sleep(changed ? 0 : 1e4);
|
|
11043
|
+
changed = false;
|
|
11044
|
+
if (!isRunning2())
|
|
11045
|
+
break;
|
|
11046
|
+
const fresh = await getValidCredentials({ forceRefresh: true });
|
|
11047
|
+
if (fresh)
|
|
11048
|
+
return { kind: "recovered", creds: fresh };
|
|
11049
|
+
const oc = getLastRefreshOutcome();
|
|
11050
|
+
if (oc === "terminal" || oc === "no-creds")
|
|
11051
|
+
return { kind: "terminal" };
|
|
11052
|
+
}
|
|
11053
|
+
return { kind: "stopped" };
|
|
11054
|
+
} finally {
|
|
11055
|
+
if (watcher)
|
|
11056
|
+
watcher.close();
|
|
11057
|
+
}
|
|
11058
|
+
}
|
|
11059
|
+
function decodeEmailFromIdToken(idToken) {
|
|
11060
|
+
try {
|
|
11061
|
+
const payload = JSON.parse(Buffer.from(idToken.split(".")[1], "base64url").toString());
|
|
11062
|
+
return typeof payload.email === "string" ? payload.email : null;
|
|
11063
|
+
} catch {
|
|
11064
|
+
return null;
|
|
11065
|
+
}
|
|
11066
|
+
}
|
|
11067
|
+
async function handleSyncCompleted(notif, ctx) {
|
|
10522
11068
|
if (!notif.commitSha) {
|
|
10523
11069
|
console.warn(`sync.completed for ${notif.repoId} has no commitSha`);
|
|
10524
11070
|
}
|
|
@@ -10537,13 +11083,14 @@ async function handleSyncCompleted(notif, ctx) {
|
|
|
10537
11083
|
return { stateUpdated: false };
|
|
10538
11084
|
}
|
|
10539
11085
|
notifyContextUpdated(notif.repoId, result.updatedFiles.length);
|
|
10540
|
-
|
|
10541
|
-
await refreshAiToolConfigs(notif, localPath, ctx);
|
|
11086
|
+
await writeDocsMarker(localPath, "repowise-context", notif.commitSha ?? null, result.updatedFiles.length);
|
|
10542
11087
|
ctx.state.repos[notif.repoId] = {
|
|
10543
11088
|
...ctx.state.repos[notif.repoId],
|
|
10544
11089
|
lastSyncTimestamp: notif.createdAt,
|
|
10545
11090
|
lastSyncCommitSha: notif.commitSha ?? ctx.state.repos[notif.repoId]?.lastSyncCommitSha ?? null
|
|
10546
11091
|
};
|
|
11092
|
+
invalidateLspBuffersOnce(notif, localPath, ctx);
|
|
11093
|
+
await refreshAiToolConfigs(notif, localPath, ctx);
|
|
10547
11094
|
await runProducerHook(notif, localPath, ctx);
|
|
10548
11095
|
return { stateUpdated: true };
|
|
10549
11096
|
}
|
|
@@ -10712,14 +11259,14 @@ async function checkStaleContext(repos, state, groups) {
|
|
|
10712
11259
|
if (group?.offline.isOffline)
|
|
10713
11260
|
continue;
|
|
10714
11261
|
const { statSync: statSync2, readdirSync: readdirSync2 } = await import("fs");
|
|
10715
|
-
const contextPath =
|
|
11262
|
+
const contextPath = join42(repo.localPath, "repowise-context");
|
|
10716
11263
|
let isMissingOrEmpty = false;
|
|
10717
11264
|
try {
|
|
10718
11265
|
const s = statSync2(contextPath);
|
|
10719
11266
|
if (!s.isDirectory()) {
|
|
10720
11267
|
isMissingOrEmpty = true;
|
|
10721
11268
|
} else {
|
|
10722
|
-
const entries = readdirSync2(contextPath);
|
|
11269
|
+
const entries = readdirSync2(contextPath).filter((e) => !e.startsWith("."));
|
|
10723
11270
|
isMissingOrEmpty = entries.length === 0;
|
|
10724
11271
|
}
|
|
10725
11272
|
} catch {
|
|
@@ -10735,16 +11282,19 @@ async function checkStaleContext(repos, state, groups) {
|
|
|
10735
11282
|
const result = await fetchContextFromServer(repo.repoId, repo.localPath, apiUrl);
|
|
10736
11283
|
if (result.success && result.updatedFiles.length > 0) {
|
|
10737
11284
|
console.log(`[self-heal] Re-downloaded ${result.updatedFiles.length} files for ${repo.repoId}`);
|
|
11285
|
+
await writeDocsMarker(repo.localPath, "repowise-context", repoState.lastSyncCommitSha ?? null, result.updatedFiles.length);
|
|
10738
11286
|
notifyContextUpdated(repo.repoId, result.updatedFiles.length);
|
|
10739
11287
|
}
|
|
10740
11288
|
}
|
|
10741
11289
|
}
|
|
10742
11290
|
return dirty;
|
|
10743
11291
|
}
|
|
11292
|
+
function mcpAiToolsKey(aiTools) {
|
|
11293
|
+
return JSON.stringify([...aiTools ?? []].sort());
|
|
11294
|
+
}
|
|
10744
11295
|
async function reconcileMcpConfigs(repos, packageName) {
|
|
10745
11296
|
const shimCmd = packageName;
|
|
10746
11297
|
const { contextFolder, aiTools, graphOnly } = await readRawToolConfig();
|
|
10747
|
-
const variant = graphOnly ? "graph" : "full";
|
|
10748
11298
|
for (const repo of repos) {
|
|
10749
11299
|
if (!repo.localPath)
|
|
10750
11300
|
continue;
|
|
@@ -10755,6 +11305,8 @@ async function reconcileMcpConfigs(repos, packageName) {
|
|
|
10755
11305
|
} catch {
|
|
10756
11306
|
continue;
|
|
10757
11307
|
}
|
|
11308
|
+
const docsPresent = (await scanLocalContextFiles(repo.localPath, contextFolder ?? "repowise-context")).length > 0;
|
|
11309
|
+
const variant = effectiveToolVariant(!!graphOnly, docsPresent, null, null);
|
|
10758
11310
|
try {
|
|
10759
11311
|
const results = await runAutoConfig({
|
|
10760
11312
|
repoRoot: repo.localPath,
|
|
@@ -10777,10 +11329,10 @@ async function reconcileAgentInstructions(repos) {
|
|
|
10777
11329
|
const { graphOnly } = await readRawToolConfig();
|
|
10778
11330
|
const variant = graphOnly ? "graph" : "full";
|
|
10779
11331
|
for (const repo of repos) {
|
|
10780
|
-
const path =
|
|
11332
|
+
const path = join42(repo.localPath, "repowise-context", "project-overview.md");
|
|
10781
11333
|
let content;
|
|
10782
11334
|
try {
|
|
10783
|
-
content = await
|
|
11335
|
+
content = await readFile14(path, "utf-8");
|
|
10784
11336
|
} catch (err) {
|
|
10785
11337
|
const code = err?.code;
|
|
10786
11338
|
if (code === "ENOENT" || code === "ENOTDIR" || code === "EACCES" || code === "EPERM" || code === "EISDIR") {
|
|
@@ -10793,7 +11345,7 @@ async function reconcileAgentInstructions(repos) {
|
|
|
10793
11345
|
if (injected === content)
|
|
10794
11346
|
continue;
|
|
10795
11347
|
try {
|
|
10796
|
-
await
|
|
11348
|
+
await writeFile16(path, injected, "utf-8");
|
|
10797
11349
|
console.log(`[reconcile] Reconciled agent instructions for ${repo.repoId}`);
|
|
10798
11350
|
} catch (err) {
|
|
10799
11351
|
console.warn(`[reconcile] write failed for ${repo.repoId}:`, err instanceof Error ? err.message : String(err));
|
|
@@ -10861,11 +11413,11 @@ async function startListener() {
|
|
|
10861
11413
|
}
|
|
10862
11414
|
const configDir = getConfigDir();
|
|
10863
11415
|
await mkdir16(configDir, { recursive: true });
|
|
10864
|
-
const lockPath =
|
|
10865
|
-
await
|
|
11416
|
+
const lockPath = join42(configDir, "listener.lock");
|
|
11417
|
+
await writeFile16(lockPath, "", { flag: "a" });
|
|
10866
11418
|
let lockIsHeld = false;
|
|
10867
11419
|
try {
|
|
10868
|
-
lockIsHeld = await
|
|
11420
|
+
lockIsHeld = await lockfile5.check(lockPath, { stale: 3e4, realpath: false });
|
|
10869
11421
|
} catch {
|
|
10870
11422
|
}
|
|
10871
11423
|
if (!lockIsHeld) {
|
|
@@ -10879,7 +11431,7 @@ async function startListener() {
|
|
|
10879
11431
|
}
|
|
10880
11432
|
}
|
|
10881
11433
|
try {
|
|
10882
|
-
releaseLock = await
|
|
11434
|
+
releaseLock = await lockfile5.lock(lockPath, { stale: 3e4, realpath: false });
|
|
10883
11435
|
} catch {
|
|
10884
11436
|
console.error(`Listener already running. Stop it first with \`${true ? "repowisestage" : "repowise"} stop\`.`);
|
|
10885
11437
|
process.exitCode = 1;
|
|
@@ -10904,17 +11456,20 @@ async function startListener() {
|
|
|
10904
11456
|
return;
|
|
10905
11457
|
}
|
|
10906
11458
|
if (config2.repos.length === 0 && !config2.autoDiscoverRepos) {
|
|
10907
|
-
console.error(`No repos configured. Add repos to ${
|
|
11459
|
+
console.error(`No repos configured. Add repos to ${join42(configDir, "config.json")}`);
|
|
10908
11460
|
await releaseLockAndExit();
|
|
10909
11461
|
process.exitCode = 1;
|
|
10910
11462
|
return;
|
|
10911
11463
|
}
|
|
10912
|
-
|
|
11464
|
+
let credentials = await getValidCredentials();
|
|
10913
11465
|
if (!credentials) {
|
|
10914
|
-
console.
|
|
10915
|
-
await
|
|
10916
|
-
|
|
10917
|
-
|
|
11466
|
+
console.warn("Not logged in. Run `repowise login` \u2014 the listener will start automatically once you sign in.");
|
|
11467
|
+
credentials = await waitForCredentials(() => running);
|
|
11468
|
+
if (!credentials) {
|
|
11469
|
+
await releaseLockAndExit();
|
|
11470
|
+
return;
|
|
11471
|
+
}
|
|
11472
|
+
console.log("Logged in \u2014 starting RepoWise listener.");
|
|
10918
11473
|
}
|
|
10919
11474
|
let state;
|
|
10920
11475
|
try {
|
|
@@ -10972,8 +11527,8 @@ async function startListener() {
|
|
|
10972
11527
|
let currentVersion = "";
|
|
10973
11528
|
try {
|
|
10974
11529
|
const selfDir = dirname17(fileURLToPath3(import.meta.url));
|
|
10975
|
-
const pkgJsonPath =
|
|
10976
|
-
const pkgJson = JSON.parse(await
|
|
11530
|
+
const pkgJsonPath = join42(selfDir, "..", "..", "package.json");
|
|
11531
|
+
const pkgJson = JSON.parse(await readFile14(pkgJsonPath, "utf-8"));
|
|
10977
11532
|
currentVersion = pkgJson.version;
|
|
10978
11533
|
} catch (err) {
|
|
10979
11534
|
console.log(`[auto-update] Version detection failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -10981,6 +11536,7 @@ async function startListener() {
|
|
|
10981
11536
|
let pollIntervalMs = 5e3;
|
|
10982
11537
|
let pollCycleCount = 0;
|
|
10983
11538
|
const RECONCILE_EVERY_N_CYCLES = 60;
|
|
11539
|
+
let lastMcpAiToolsKey = null;
|
|
10984
11540
|
const origLog = console.log.bind(console);
|
|
10985
11541
|
const origError = console.error.bind(console);
|
|
10986
11542
|
const origWarn = console.warn.bind(console);
|
|
@@ -10994,6 +11550,8 @@ async function startListener() {
|
|
|
10994
11550
|
console.warn = (...args) => origWarn(`[${ts()}]`, ...args);
|
|
10995
11551
|
console.log(`RepoWise Listener started \u2014 watching ${allRepoIds.length} repo(s)`);
|
|
10996
11552
|
let mcpRuntime = null;
|
|
11553
|
+
let proactiveRefreshTimer = null;
|
|
11554
|
+
const PROACTIVE_REFRESH_MS = 10 * 60 * 1e3;
|
|
10997
11555
|
try {
|
|
10998
11556
|
mcpRuntime = await startMcp({
|
|
10999
11557
|
repos: config2.repos.map((r) => ({
|
|
@@ -11001,6 +11559,8 @@ async function startListener() {
|
|
|
11001
11559
|
apiUrl: r.apiUrl ?? config2.defaultApiUrl,
|
|
11002
11560
|
...r.localPath ? { localPath: r.localPath } : {}
|
|
11003
11561
|
})),
|
|
11562
|
+
// Rides the anonymous MCP-usage heartbeat for fleet version spread.
|
|
11563
|
+
listenerVersion: currentVersion,
|
|
11004
11564
|
getAuthToken: async () => {
|
|
11005
11565
|
const fresh = await getValidCredentials();
|
|
11006
11566
|
if (!fresh)
|
|
@@ -11134,6 +11694,10 @@ async function startListener() {
|
|
|
11134
11694
|
const shutdown = async () => {
|
|
11135
11695
|
console.log("Shutting down...");
|
|
11136
11696
|
stop();
|
|
11697
|
+
if (proactiveRefreshTimer) {
|
|
11698
|
+
clearInterval(proactiveRefreshTimer);
|
|
11699
|
+
proactiveRefreshTimer = null;
|
|
11700
|
+
}
|
|
11137
11701
|
if (mcpRuntime) {
|
|
11138
11702
|
try {
|
|
11139
11703
|
await mcpRuntime.close();
|
|
@@ -11164,7 +11728,22 @@ async function startListener() {
|
|
|
11164
11728
|
}).finally(() => process.exit(0));
|
|
11165
11729
|
});
|
|
11166
11730
|
}
|
|
11731
|
+
if (!proactiveRefreshTimer) {
|
|
11732
|
+
proactiveRefreshTimer = setInterval(() => {
|
|
11733
|
+
void getValidCredentials().catch(() => {
|
|
11734
|
+
});
|
|
11735
|
+
}, PROACTIVE_REFRESH_MS);
|
|
11736
|
+
proactiveRefreshTimer.unref();
|
|
11737
|
+
}
|
|
11738
|
+
let lastCycleAt = Date.now();
|
|
11167
11739
|
while (running) {
|
|
11740
|
+
const wakeDrift = Date.now() - lastCycleAt;
|
|
11741
|
+
if (wakeDrift > pollIntervalMs + 6e4) {
|
|
11742
|
+
console.log(`[wake] ~${Math.round(wakeDrift / 1e3).toString()}s gap detected (likely sleep) \u2014 refreshing credentials`);
|
|
11743
|
+
await getValidCredentials({ forceRefresh: true }).catch(() => {
|
|
11744
|
+
});
|
|
11745
|
+
}
|
|
11746
|
+
lastCycleAt = Date.now();
|
|
11168
11747
|
let anyAuthError = null;
|
|
11169
11748
|
let authErrorGroup = null;
|
|
11170
11749
|
let minPollInterval = pollIntervalMs;
|
|
@@ -11207,6 +11786,16 @@ async function startListener() {
|
|
|
11207
11786
|
console.warn("[mcp-config] front-load for newly-registered repo(s) failed:", err instanceof Error ? err.message : String(err));
|
|
11208
11787
|
});
|
|
11209
11788
|
}
|
|
11789
|
+
const aiToolsKey = mcpAiToolsKey((await readRawToolConfig()).aiTools);
|
|
11790
|
+
if (lastMcpAiToolsKey === null) {
|
|
11791
|
+
lastMcpAiToolsKey = aiToolsKey;
|
|
11792
|
+
} else if (aiToolsKey !== lastMcpAiToolsKey) {
|
|
11793
|
+
lastMcpAiToolsKey = aiToolsKey;
|
|
11794
|
+
console.log("[config-sync] AI-tool selection changed \u2014 front-loading MCP config");
|
|
11795
|
+
void reconcileMcpConfigs(freshRepos, packageName).catch((err) => {
|
|
11796
|
+
console.warn("[mcp-config] front-load for aiTools change failed:", err instanceof Error ? err.message : String(err));
|
|
11797
|
+
});
|
|
11798
|
+
}
|
|
11210
11799
|
} catch {
|
|
11211
11800
|
}
|
|
11212
11801
|
for (const group of groups) {
|
|
@@ -11371,10 +11960,10 @@ async function startListener() {
|
|
|
11371
11960
|
}
|
|
11372
11961
|
}
|
|
11373
11962
|
group.offline.attemptCount++;
|
|
11374
|
-
const
|
|
11375
|
-
group.offline.nextRetryAt = Date.now() +
|
|
11963
|
+
const delay3 = group.backoff.nextDelay();
|
|
11964
|
+
group.offline.nextRetryAt = Date.now() + delay3;
|
|
11376
11965
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
11377
|
-
console.error(`Poll failed for ${group.apiUrl} (attempt ${group.offline.attemptCount}): ${message}. Retrying in ${Math.round(
|
|
11966
|
+
console.error(`Poll failed for ${group.apiUrl} (attempt ${group.offline.attemptCount}): ${message}. Retrying in ${Math.round(delay3 / 1e3)}s`);
|
|
11378
11967
|
}
|
|
11379
11968
|
}
|
|
11380
11969
|
if (shouldReconcile) {
|
|
@@ -11451,13 +12040,14 @@ async function startListener() {
|
|
|
11451
12040
|
}
|
|
11452
12041
|
if (anyAuthError) {
|
|
11453
12042
|
let recovered = false;
|
|
11454
|
-
|
|
11455
|
-
|
|
11456
|
-
|
|
12043
|
+
const backoffs = [5e3, 1e4, 2e4];
|
|
12044
|
+
for (let attempt = 0; attempt < backoffs.length; attempt++) {
|
|
12045
|
+
console.warn(`Auth error \u2014 retrying credentials (attempt ${(attempt + 1).toString()}/${backoffs.length.toString()})...`);
|
|
12046
|
+
await sleep(backoffs[attempt]);
|
|
11457
12047
|
if (!running)
|
|
11458
12048
|
break;
|
|
11459
|
-
const
|
|
11460
|
-
if (
|
|
12049
|
+
const fresh2 = await getValidCredentials({ forceRefresh: true });
|
|
12050
|
+
if (fresh2) {
|
|
11461
12051
|
console.log("Credentials recovered \u2014 resuming listener");
|
|
11462
12052
|
recovered = true;
|
|
11463
12053
|
break;
|
|
@@ -11465,42 +12055,47 @@ async function startListener() {
|
|
|
11465
12055
|
}
|
|
11466
12056
|
if (recovered)
|
|
11467
12057
|
continue;
|
|
11468
|
-
|
|
12058
|
+
const outcome = getLastRefreshOutcome();
|
|
12059
|
+
let terminal = outcome === "terminal" || outcome === "no-creds";
|
|
12060
|
+
if (!terminal) {
|
|
12061
|
+
console.warn("[auth] Refresh still failing (transient/offline) \u2014 keeping MCP up, waiting for connectivity...");
|
|
12062
|
+
const waited = await waitForCredentialsOrTerminal(() => running);
|
|
12063
|
+
if (waited.kind === "stopped")
|
|
12064
|
+
continue;
|
|
12065
|
+
if (waited.kind === "recovered") {
|
|
12066
|
+
console.log("Reconnected \u2014 resuming listener");
|
|
12067
|
+
continue;
|
|
12068
|
+
}
|
|
12069
|
+
console.warn("[auth] Session became terminal during the offline wait \u2014 prompting re-login");
|
|
12070
|
+
terminal = true;
|
|
12071
|
+
}
|
|
12072
|
+
console.error("Session expired (refresh token rejected). Prompting re-login and waiting...");
|
|
12073
|
+
try {
|
|
12074
|
+
await mcpRuntime?.server?.suspendEndpoint();
|
|
12075
|
+
} catch (err) {
|
|
12076
|
+
console.warn("[mcp] suspendEndpoint failed:", err instanceof Error ? err.message : String(err));
|
|
12077
|
+
}
|
|
11469
12078
|
const creds = await getStoredCredentials();
|
|
11470
12079
|
const email = creds?.idToken ? decodeEmailFromIdToken(creds.idToken) : null;
|
|
11471
12080
|
if (email && authErrorGroup) {
|
|
11472
12081
|
try {
|
|
11473
|
-
await
|
|
12082
|
+
await fetchWithTimeout(`${authErrorGroup.apiUrl}/v1/public/notify-auth-expired`, {
|
|
11474
12083
|
method: "POST",
|
|
11475
12084
|
headers: { "Content-Type": "application/json" },
|
|
11476
12085
|
body: JSON.stringify({ email })
|
|
11477
|
-
});
|
|
12086
|
+
}, 5e3);
|
|
11478
12087
|
} catch {
|
|
11479
12088
|
}
|
|
11480
12089
|
}
|
|
11481
|
-
const
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
|
|
11488
|
-
});
|
|
11489
|
-
} catch {
|
|
11490
|
-
}
|
|
11491
|
-
while (running) {
|
|
11492
|
-
await sleep(credentialsChanged ? 0 : 1e4);
|
|
11493
|
-
credentialsChanged = false;
|
|
11494
|
-
if (!running)
|
|
11495
|
-
break;
|
|
11496
|
-
const fresh = await getValidCredentials({ forceRefresh: true });
|
|
11497
|
-
if (fresh) {
|
|
11498
|
-
console.log("Re-authenticated \u2014 resuming listener");
|
|
11499
|
-
break;
|
|
12090
|
+
const fresh = await waitForCredentials(() => running);
|
|
12091
|
+
if (fresh) {
|
|
12092
|
+
console.log("Re-authenticated \u2014 resuming listener");
|
|
12093
|
+
try {
|
|
12094
|
+
await mcpRuntime?.server?.resumeEndpoint();
|
|
12095
|
+
} catch (err) {
|
|
12096
|
+
console.warn("[mcp] resumeEndpoint failed:", err instanceof Error ? err.message : String(err));
|
|
11500
12097
|
}
|
|
11501
12098
|
}
|
|
11502
|
-
if (watcher)
|
|
11503
|
-
watcher.close();
|
|
11504
12099
|
continue;
|
|
11505
12100
|
}
|
|
11506
12101
|
pollIntervalMs = minPollInterval;
|
|
@@ -11524,7 +12119,7 @@ if (isDirectRun) {
|
|
|
11524
12119
|
|
|
11525
12120
|
// src/lib/env.ts
|
|
11526
12121
|
import { homedir as homedir7 } from "os";
|
|
11527
|
-
import { join as
|
|
12122
|
+
import { join as join43 } from "path";
|
|
11528
12123
|
var IS_STAGING2 = true ? true : false;
|
|
11529
12124
|
var PRODUCTION = {
|
|
11530
12125
|
apiUrl: "https://api.repowise.ai",
|
|
@@ -11544,7 +12139,7 @@ function getEnvConfig() {
|
|
|
11544
12139
|
return IS_STAGING2 ? STAGING : PRODUCTION;
|
|
11545
12140
|
}
|
|
11546
12141
|
function getConfigDir2() {
|
|
11547
|
-
return
|
|
12142
|
+
return join43(homedir7(), IS_STAGING2 ? ".repowise-staging" : ".repowise");
|
|
11548
12143
|
}
|
|
11549
12144
|
function getPackageName() {
|
|
11550
12145
|
return true ? "repowisestage" : "repowise";
|
|
@@ -11554,12 +12149,12 @@ function getPackageName() {
|
|
|
11554
12149
|
import chalk from "chalk";
|
|
11555
12150
|
|
|
11556
12151
|
// src/lib/config.ts
|
|
11557
|
-
import { readFile as
|
|
11558
|
-
import { join as
|
|
11559
|
-
import
|
|
12152
|
+
import { readFile as readFile15, writeFile as writeFile17, mkdir as mkdir17, rename as rename6, unlink as unlink12 } from "fs/promises";
|
|
12153
|
+
import { join as join44 } from "path";
|
|
12154
|
+
import lockfile6 from "proper-lockfile";
|
|
11560
12155
|
async function getConfig() {
|
|
11561
12156
|
try {
|
|
11562
|
-
const data = await
|
|
12157
|
+
const data = await readFile15(join44(getConfigDir2(), "config.json"), "utf-8");
|
|
11563
12158
|
return JSON.parse(data);
|
|
11564
12159
|
} catch {
|
|
11565
12160
|
return {};
|
|
@@ -11567,15 +12162,15 @@ async function getConfig() {
|
|
|
11567
12162
|
}
|
|
11568
12163
|
async function saveConfig(config2) {
|
|
11569
12164
|
const dir = getConfigDir2();
|
|
11570
|
-
const path =
|
|
12165
|
+
const path = join44(dir, "config.json");
|
|
11571
12166
|
await mkdir17(dir, { recursive: true });
|
|
11572
12167
|
const tmpPath = path + ".tmp";
|
|
11573
12168
|
try {
|
|
11574
|
-
await
|
|
11575
|
-
await
|
|
12169
|
+
await writeFile17(tmpPath, JSON.stringify(config2, null, 2));
|
|
12170
|
+
await rename6(tmpPath, path);
|
|
11576
12171
|
} catch (err) {
|
|
11577
12172
|
try {
|
|
11578
|
-
await
|
|
12173
|
+
await unlink12(tmpPath);
|
|
11579
12174
|
} catch {
|
|
11580
12175
|
}
|
|
11581
12176
|
throw err;
|
|
@@ -11583,18 +12178,18 @@ async function saveConfig(config2) {
|
|
|
11583
12178
|
}
|
|
11584
12179
|
async function mergeAndSaveConfig(updates) {
|
|
11585
12180
|
const dir = getConfigDir2();
|
|
11586
|
-
const path =
|
|
12181
|
+
const path = join44(dir, "config.json");
|
|
11587
12182
|
await mkdir17(dir, { recursive: true });
|
|
11588
12183
|
try {
|
|
11589
|
-
await
|
|
12184
|
+
await writeFile17(path, "", { flag: "a" });
|
|
11590
12185
|
} catch {
|
|
11591
12186
|
}
|
|
11592
12187
|
let release = null;
|
|
11593
12188
|
try {
|
|
11594
|
-
release = await
|
|
12189
|
+
release = await lockfile6.lock(path, { stale: 1e4, retries: 3, realpath: false });
|
|
11595
12190
|
let raw = {};
|
|
11596
12191
|
try {
|
|
11597
|
-
raw = JSON.parse(await
|
|
12192
|
+
raw = JSON.parse(await readFile15(path, "utf-8"));
|
|
11598
12193
|
} catch {
|
|
11599
12194
|
}
|
|
11600
12195
|
const merged = { ...raw, ...updates };
|
|
@@ -11654,18 +12249,33 @@ async function showWelcome(currentVersion) {
|
|
|
11654
12249
|
|
|
11655
12250
|
// src/commands/create.ts
|
|
11656
12251
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
11657
|
-
import { dirname as dirname19, join as
|
|
12252
|
+
import { dirname as dirname19, join as join51 } from "path";
|
|
11658
12253
|
init_src();
|
|
11659
12254
|
import chalk8 from "chalk";
|
|
11660
12255
|
import ora from "ora";
|
|
11661
12256
|
|
|
11662
12257
|
// src/lib/auth.ts
|
|
11663
12258
|
import { createHash as createHash6, randomBytes as randomBytes3 } from "crypto";
|
|
11664
|
-
import { readFile as
|
|
12259
|
+
import { readFile as readFile16, writeFile as writeFile18, mkdir as mkdir18, chmod as chmod4, unlink as unlink13, rename as rename7, open as open5 } from "fs/promises";
|
|
11665
12260
|
import http from "http";
|
|
11666
|
-
import { join as
|
|
12261
|
+
import { join as join45 } from "path";
|
|
12262
|
+
import lockfile7 from "proper-lockfile";
|
|
12263
|
+
|
|
12264
|
+
// src/lib/http.ts
|
|
12265
|
+
async function fetchWithTimeout2(url, init, timeoutMs) {
|
|
12266
|
+
const controller = new AbortController();
|
|
12267
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
12268
|
+
try {
|
|
12269
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
12270
|
+
} finally {
|
|
12271
|
+
clearTimeout(timer);
|
|
12272
|
+
}
|
|
12273
|
+
}
|
|
12274
|
+
|
|
12275
|
+
// src/lib/auth.ts
|
|
11667
12276
|
var CLI_CALLBACK_PORT = 19876;
|
|
11668
12277
|
var CALLBACK_TIMEOUT_MS = 12e4;
|
|
12278
|
+
var TOKEN_ENDPOINT_TIMEOUT_MS2 = 3e4;
|
|
11669
12279
|
function getCognitoConfigForStorage() {
|
|
11670
12280
|
const { domain, clientId, region, customDomain } = getCognitoConfig();
|
|
11671
12281
|
return { domain, clientId, region, customDomain };
|
|
@@ -11806,31 +12416,65 @@ async function exchangeCodeForTokens(code, codeVerifier) {
|
|
|
11806
12416
|
};
|
|
11807
12417
|
}
|
|
11808
12418
|
async function refreshTokens2(refreshToken) {
|
|
11809
|
-
|
|
11810
|
-
|
|
11811
|
-
|
|
11812
|
-
|
|
11813
|
-
|
|
11814
|
-
|
|
11815
|
-
|
|
11816
|
-
|
|
11817
|
-
|
|
12419
|
+
let response;
|
|
12420
|
+
try {
|
|
12421
|
+
response = await fetchWithTimeout2(
|
|
12422
|
+
getTokenUrl2(),
|
|
12423
|
+
{
|
|
12424
|
+
method: "POST",
|
|
12425
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
12426
|
+
body: new URLSearchParams({
|
|
12427
|
+
grant_type: "refresh_token",
|
|
12428
|
+
client_id: getCognitoConfig().clientId,
|
|
12429
|
+
refresh_token: refreshToken
|
|
12430
|
+
})
|
|
12431
|
+
},
|
|
12432
|
+
TOKEN_ENDPOINT_TIMEOUT_MS2
|
|
12433
|
+
);
|
|
12434
|
+
} catch (err) {
|
|
12435
|
+
throw new RefreshTokenError(
|
|
12436
|
+
"transient",
|
|
12437
|
+
`Token refresh network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
12438
|
+
null
|
|
12439
|
+
);
|
|
12440
|
+
}
|
|
11818
12441
|
if (!response.ok) {
|
|
11819
|
-
|
|
12442
|
+
const body = await response.text().catch(() => "");
|
|
12443
|
+
throw new RefreshTokenError(
|
|
12444
|
+
classifyRefreshFailure(response.status, body),
|
|
12445
|
+
`Token refresh failed: ${response.status}`,
|
|
12446
|
+
response.status
|
|
12447
|
+
);
|
|
11820
12448
|
}
|
|
11821
12449
|
const data = await response.json();
|
|
11822
12450
|
return {
|
|
11823
12451
|
accessToken: data.access_token,
|
|
11824
|
-
|
|
11825
|
-
//
|
|
12452
|
+
// Carry forward a rotated refresh token when Cognito returns one (rotation
|
|
12453
|
+
// enabled); otherwise reuse the current token (rotation disabled today).
|
|
12454
|
+
// Persisting the newest token is the hard prerequisite for enabling
|
|
12455
|
+
// refresh-token rotation server-side.
|
|
12456
|
+
refreshToken: data.refresh_token ?? refreshToken,
|
|
11826
12457
|
idToken: data.id_token,
|
|
11827
12458
|
expiresAt: Date.now() + data.expires_in * 1e3
|
|
11828
12459
|
};
|
|
11829
12460
|
}
|
|
12461
|
+
async function revokeRefreshToken(refreshToken) {
|
|
12462
|
+
const { clientId } = getCognitoConfig();
|
|
12463
|
+
if (!clientId || !refreshToken) return;
|
|
12464
|
+
await fetchWithTimeout2(
|
|
12465
|
+
`${getCognitoBaseUrl()}/oauth2/revoke`,
|
|
12466
|
+
{
|
|
12467
|
+
method: "POST",
|
|
12468
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
12469
|
+
body: new URLSearchParams({ token: refreshToken, client_id: clientId })
|
|
12470
|
+
},
|
|
12471
|
+
TOKEN_ENDPOINT_TIMEOUT_MS2
|
|
12472
|
+
);
|
|
12473
|
+
}
|
|
11830
12474
|
async function getStoredCredentials2() {
|
|
11831
12475
|
try {
|
|
11832
|
-
const credPath =
|
|
11833
|
-
const data = await
|
|
12476
|
+
const credPath = join45(getConfigDir2(), "credentials.json");
|
|
12477
|
+
const data = await readFile16(credPath, "utf-8");
|
|
11834
12478
|
return JSON.parse(data);
|
|
11835
12479
|
} catch (err) {
|
|
11836
12480
|
if (err.code === "ENOENT" || err instanceof SyntaxError) {
|
|
@@ -11841,37 +12485,78 @@ async function getStoredCredentials2() {
|
|
|
11841
12485
|
}
|
|
11842
12486
|
async function storeCredentials2(credentials) {
|
|
11843
12487
|
const dir = getConfigDir2();
|
|
11844
|
-
const credPath =
|
|
12488
|
+
const credPath = join45(dir, "credentials.json");
|
|
11845
12489
|
await mkdir18(dir, { recursive: true, mode: 448 });
|
|
11846
|
-
|
|
11847
|
-
|
|
12490
|
+
try {
|
|
12491
|
+
await writeFile18(credPath, "", { flag: "a", mode: 384 });
|
|
12492
|
+
} catch {
|
|
12493
|
+
}
|
|
12494
|
+
let release = null;
|
|
12495
|
+
try {
|
|
12496
|
+
release = await lockfile7.lock(credPath, { stale: 1e4, retries: 3, realpath: false });
|
|
12497
|
+
const tmpPath = credPath + ".tmp";
|
|
12498
|
+
try {
|
|
12499
|
+
await writeFile18(tmpPath, JSON.stringify(credentials, null, 2), { mode: 384 });
|
|
12500
|
+
let fh;
|
|
12501
|
+
try {
|
|
12502
|
+
fh = await open5(tmpPath, "r+");
|
|
12503
|
+
await fh.datasync();
|
|
12504
|
+
} finally {
|
|
12505
|
+
await fh?.close();
|
|
12506
|
+
}
|
|
12507
|
+
await rename7(tmpPath, credPath);
|
|
12508
|
+
await chmod4(credPath, 384);
|
|
12509
|
+
} catch (err) {
|
|
12510
|
+
try {
|
|
12511
|
+
await unlink13(tmpPath);
|
|
12512
|
+
} catch {
|
|
12513
|
+
}
|
|
12514
|
+
throw err;
|
|
12515
|
+
}
|
|
12516
|
+
} finally {
|
|
12517
|
+
if (release) {
|
|
12518
|
+
try {
|
|
12519
|
+
await release();
|
|
12520
|
+
} catch {
|
|
12521
|
+
}
|
|
12522
|
+
}
|
|
12523
|
+
}
|
|
11848
12524
|
}
|
|
11849
12525
|
async function clearCredentials() {
|
|
11850
12526
|
try {
|
|
11851
|
-
await
|
|
12527
|
+
await unlink13(join45(getConfigDir2(), "credentials.json"));
|
|
11852
12528
|
} catch (err) {
|
|
11853
12529
|
if (err.code !== "ENOENT") throw err;
|
|
11854
12530
|
}
|
|
11855
12531
|
}
|
|
12532
|
+
var refreshInFlight2 = null;
|
|
11856
12533
|
async function getValidCredentials2(opts) {
|
|
11857
12534
|
const creds = await getStoredCredentials2();
|
|
11858
12535
|
if (!creds) return null;
|
|
11859
12536
|
const needsRefresh = opts?.forceRefresh || Date.now() > creds.expiresAt - 5 * 60 * 1e3;
|
|
11860
|
-
if (needsRefresh)
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
|
|
11866
|
-
|
|
11867
|
-
|
|
11868
|
-
|
|
11869
|
-
|
|
12537
|
+
if (!needsRefresh) return creds;
|
|
12538
|
+
if (!refreshInFlight2) {
|
|
12539
|
+
refreshInFlight2 = doRefresh2(creds).finally(() => {
|
|
12540
|
+
refreshInFlight2 = null;
|
|
12541
|
+
});
|
|
12542
|
+
}
|
|
12543
|
+
return refreshInFlight2;
|
|
12544
|
+
}
|
|
12545
|
+
async function doRefresh2(creds) {
|
|
12546
|
+
try {
|
|
12547
|
+
const refreshed = await refreshTokens2(creds.refreshToken);
|
|
12548
|
+
refreshed.cognito = creds.cognito;
|
|
12549
|
+
await storeCredentials2(refreshed);
|
|
12550
|
+
return refreshed;
|
|
12551
|
+
} catch (err) {
|
|
12552
|
+
const kind = err instanceof RefreshTokenError ? err.kind : "transient";
|
|
12553
|
+
if (kind === "terminal") {
|
|
11870
12554
|
await clearCredentials();
|
|
11871
12555
|
return null;
|
|
11872
12556
|
}
|
|
12557
|
+
if (Date.now() < creds.expiresAt) return creds;
|
|
12558
|
+
return null;
|
|
11873
12559
|
}
|
|
11874
|
-
return creds;
|
|
11875
12560
|
}
|
|
11876
12561
|
async function performLogin() {
|
|
11877
12562
|
const codeVerifier = generateCodeVerifier();
|
|
@@ -11880,8 +12565,8 @@ async function performLogin() {
|
|
|
11880
12565
|
const authorizeUrl = getAuthorizeUrl(codeChallenge, state);
|
|
11881
12566
|
const callbackPromise = startCallbackServer();
|
|
11882
12567
|
try {
|
|
11883
|
-
const
|
|
11884
|
-
await
|
|
12568
|
+
const open6 = (await import("open")).default;
|
|
12569
|
+
await open6(authorizeUrl);
|
|
11885
12570
|
} catch {
|
|
11886
12571
|
console.log(`
|
|
11887
12572
|
Open this URL in your browser to authenticate:
|
|
@@ -11979,7 +12664,6 @@ async function apiRequest(path, options) {
|
|
|
11979
12664
|
});
|
|
11980
12665
|
}
|
|
11981
12666
|
if (!refreshed || response.status === 401) {
|
|
11982
|
-
await clearCredentials();
|
|
11983
12667
|
throw new Error("Session expired. Run `repowise login` again.");
|
|
11984
12668
|
}
|
|
11985
12669
|
}
|
|
@@ -12013,25 +12697,26 @@ async function apiRequest(path, options) {
|
|
|
12013
12697
|
// src/lib/prompts.ts
|
|
12014
12698
|
import { checkbox, confirm, Separator } from "@inquirer/prompts";
|
|
12015
12699
|
import chalk2 from "chalk";
|
|
12016
|
-
async function selectAiTools() {
|
|
12700
|
+
async function selectAiTools(opts) {
|
|
12701
|
+
const checkedSet = new Set(opts?.defaults ?? []);
|
|
12017
12702
|
const choices = [
|
|
12018
12703
|
new Separator(chalk2.dim("\u2500\u2500 Popular \u2500\u2500")),
|
|
12019
|
-
{ name: "Cursor", value: "cursor" },
|
|
12020
|
-
{ name: "Claude Code", value: "claude-code" },
|
|
12021
|
-
{ name: "GitHub Copilot", value: "copilot" },
|
|
12022
|
-
{ name: "Windsurf", value: "windsurf" },
|
|
12704
|
+
{ name: "Cursor", value: "cursor", checked: checkedSet.has("cursor") },
|
|
12705
|
+
{ name: "Claude Code", value: "claude-code", checked: checkedSet.has("claude-code") },
|
|
12706
|
+
{ name: "GitHub Copilot", value: "copilot", checked: checkedSet.has("copilot") },
|
|
12707
|
+
{ name: "Windsurf", value: "windsurf", checked: checkedSet.has("windsurf") },
|
|
12023
12708
|
new Separator(chalk2.dim("\u2500\u2500 More Tools \u2500\u2500")),
|
|
12024
|
-
{ name: "Cline", value: "cline" },
|
|
12025
|
-
{ name: "Codex", value: "codex" },
|
|
12709
|
+
{ name: "Cline", value: "cline", checked: checkedSet.has("cline") },
|
|
12710
|
+
{ name: "Codex", value: "codex", checked: checkedSet.has("codex") },
|
|
12026
12711
|
// Roo Code shut down 2026-05-15; Kilo Code is the community successor.
|
|
12027
|
-
{ name: "Kilo Code", value: "kilo" },
|
|
12028
|
-
{ name: "Gemini CLI", value: "gemini" },
|
|
12712
|
+
{ name: "Kilo Code", value: "kilo", checked: checkedSet.has("kilo") },
|
|
12713
|
+
{ name: "Gemini CLI", value: "gemini", checked: checkedSet.has("gemini") },
|
|
12029
12714
|
new Separator(chalk2.dim("\u2500\u2500 Cloud Agents \u2500\u2500")),
|
|
12030
|
-
{ name: "Warp", value: "warp" },
|
|
12031
|
-
{ name: "JetBrains Junie", value: "junie" },
|
|
12032
|
-
{ name: "Google Jules", value: "jules" },
|
|
12033
|
-
{ name: "Amp", value: "amp" },
|
|
12034
|
-
{ name: "Devin", value: "devin" },
|
|
12715
|
+
{ name: "Warp", value: "warp", checked: checkedSet.has("warp") },
|
|
12716
|
+
{ name: "JetBrains Junie", value: "junie", checked: checkedSet.has("junie") },
|
|
12717
|
+
{ name: "Google Jules", value: "jules", checked: checkedSet.has("jules") },
|
|
12718
|
+
{ name: "Amp", value: "amp", checked: checkedSet.has("amp") },
|
|
12719
|
+
{ name: "Devin", value: "devin", checked: checkedSet.has("devin") },
|
|
12035
12720
|
new Separator(chalk2.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),
|
|
12036
12721
|
{ name: "Other (manual setup)", value: "other" }
|
|
12037
12722
|
];
|
|
@@ -12054,11 +12739,126 @@ async function selectAiTools() {
|
|
|
12054
12739
|
}
|
|
12055
12740
|
}
|
|
12056
12741
|
|
|
12742
|
+
// src/lib/setup-tools.ts
|
|
12743
|
+
async function writeToolConfigsForRepo(opts) {
|
|
12744
|
+
const { repoRoot, tools, repoName, contextFolder, contextFiles, variant, migrateLegacy } = opts;
|
|
12745
|
+
const results = [];
|
|
12746
|
+
const written = /* @__PURE__ */ new Set();
|
|
12747
|
+
for (const tool of tools) {
|
|
12748
|
+
const config2 = AI_TOOL_CONFIG[tool];
|
|
12749
|
+
if (written.has(config2.filePath)) continue;
|
|
12750
|
+
written.add(config2.filePath);
|
|
12751
|
+
if (migrateLegacy && config2.legacyFilePath) {
|
|
12752
|
+
await migrateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles, variant);
|
|
12753
|
+
}
|
|
12754
|
+
const { created } = await updateToolConfig(
|
|
12755
|
+
repoRoot,
|
|
12756
|
+
tool,
|
|
12757
|
+
repoName,
|
|
12758
|
+
contextFolder,
|
|
12759
|
+
contextFiles,
|
|
12760
|
+
variant
|
|
12761
|
+
);
|
|
12762
|
+
results.push(`${created ? "Created" : "Updated"} ${config2.filePath}`);
|
|
12763
|
+
}
|
|
12764
|
+
if (!written.has("AGENTS.md")) {
|
|
12765
|
+
const { created } = await updateToolConfig(
|
|
12766
|
+
repoRoot,
|
|
12767
|
+
"codex",
|
|
12768
|
+
repoName,
|
|
12769
|
+
contextFolder,
|
|
12770
|
+
contextFiles,
|
|
12771
|
+
variant
|
|
12772
|
+
);
|
|
12773
|
+
results.push(`${created ? "Created" : "Updated"} AGENTS.md`);
|
|
12774
|
+
}
|
|
12775
|
+
if (tools.includes("claude-code")) {
|
|
12776
|
+
const { relPath } = await writeClaudeHooksToRepo(repoRoot, contextFolder, variant);
|
|
12777
|
+
results.push(`Configured ${relPath} (RepoWise-first + SubagentStart hooks, local-only)`);
|
|
12778
|
+
}
|
|
12779
|
+
return results;
|
|
12780
|
+
}
|
|
12781
|
+
|
|
12782
|
+
// src/lib/mcp-resolver.ts
|
|
12783
|
+
import { basename as basename4, join as join46 } from "path";
|
|
12784
|
+
import { promises as fs21 } from "fs";
|
|
12785
|
+
var MCP_WRITER_TOOLS = /* @__PURE__ */ new Set([
|
|
12786
|
+
"claude-code",
|
|
12787
|
+
"cursor",
|
|
12788
|
+
"copilot",
|
|
12789
|
+
"codex",
|
|
12790
|
+
"windsurf",
|
|
12791
|
+
"cline",
|
|
12792
|
+
"gemini"
|
|
12793
|
+
]);
|
|
12794
|
+
function mcpPathFor(tool, repoRoot, home) {
|
|
12795
|
+
switch (tool) {
|
|
12796
|
+
case "claude-code":
|
|
12797
|
+
return { path: join46(repoRoot, ".mcp.json"), scope: "repo" };
|
|
12798
|
+
case "cursor":
|
|
12799
|
+
return { path: join46(repoRoot, ".cursor", "mcp.json"), scope: "repo" };
|
|
12800
|
+
case "copilot":
|
|
12801
|
+
return { path: join46(repoRoot, ".vscode", "mcp.json"), scope: "repo" };
|
|
12802
|
+
case "codex":
|
|
12803
|
+
return { path: join46(home, ".codex", "mcp.json"), scope: "global" };
|
|
12804
|
+
case "windsurf":
|
|
12805
|
+
return { path: join46(home, ".codeium", "windsurf", "mcp_config.json"), scope: "global" };
|
|
12806
|
+
case "cline":
|
|
12807
|
+
return { path: join46(home, ".cline", "mcp.json"), scope: "global" };
|
|
12808
|
+
case "gemini":
|
|
12809
|
+
return { path: join46(home, ".gemini", "settings.json"), scope: "global" };
|
|
12810
|
+
default:
|
|
12811
|
+
return null;
|
|
12812
|
+
}
|
|
12813
|
+
}
|
|
12814
|
+
function mcpServerKeyFor(repoRoot) {
|
|
12815
|
+
return `RepoWise MCP for ${basename4(repoRoot)}`;
|
|
12816
|
+
}
|
|
12817
|
+
function mcpConfigTargetForTool(tool, opts) {
|
|
12818
|
+
const loc = mcpPathFor(tool, opts.repoRoot, opts.home);
|
|
12819
|
+
if (!loc) return null;
|
|
12820
|
+
return { tool, path: loc.path, scope: loc.scope, serverKey: mcpServerKeyFor(opts.repoRoot) };
|
|
12821
|
+
}
|
|
12822
|
+
async function mcpConfigState(target) {
|
|
12823
|
+
let raw;
|
|
12824
|
+
try {
|
|
12825
|
+
raw = await fs21.readFile(target.path, "utf-8");
|
|
12826
|
+
} catch {
|
|
12827
|
+
return "absent";
|
|
12828
|
+
}
|
|
12829
|
+
try {
|
|
12830
|
+
const parsed = JSON.parse(raw);
|
|
12831
|
+
return parsed.mcpServers && target.serverKey in parsed.mcpServers ? "configured" : "pending";
|
|
12832
|
+
} catch {
|
|
12833
|
+
return "absent";
|
|
12834
|
+
}
|
|
12835
|
+
}
|
|
12836
|
+
function mcpActivationHint(tool, repoName) {
|
|
12837
|
+
if (!MCP_WRITER_TOOLS.has(tool)) return null;
|
|
12838
|
+
if (tool === "claude-code") {
|
|
12839
|
+
return `In Claude Code, run \`/mcp\` and approve the "RepoWise MCP for ${repoName}" server.`;
|
|
12840
|
+
}
|
|
12841
|
+
return `Reload your AI tool and approve the "RepoWise MCP for ${repoName}" server when prompted.`;
|
|
12842
|
+
}
|
|
12843
|
+
async function mcpStatusForRepo(opts) {
|
|
12844
|
+
const repoName = basename4(opts.repoRoot);
|
|
12845
|
+
const out = [];
|
|
12846
|
+
for (const tool of opts.aiTools) {
|
|
12847
|
+
const target = mcpConfigTargetForTool(tool, { repoRoot: opts.repoRoot, home: opts.home });
|
|
12848
|
+
if (!target) continue;
|
|
12849
|
+
const state = await mcpConfigState(target);
|
|
12850
|
+
if (state === "absent") continue;
|
|
12851
|
+
const configured = state === "configured";
|
|
12852
|
+
out.push({ tool, configured, hint: configured ? null : mcpActivationHint(tool, repoName) });
|
|
12853
|
+
}
|
|
12854
|
+
return out;
|
|
12855
|
+
}
|
|
12856
|
+
|
|
12057
12857
|
// src/lib/gitignore.ts
|
|
12058
12858
|
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2 } from "fs";
|
|
12059
|
-
import { join as
|
|
12859
|
+
import { join as join47 } from "path";
|
|
12060
12860
|
function ensureGitignore(repoRoot, entry) {
|
|
12061
|
-
const gitignorePath =
|
|
12861
|
+
const gitignorePath = join47(repoRoot, ".gitignore");
|
|
12062
12862
|
if (existsSync2(gitignorePath)) {
|
|
12063
12863
|
const content = readFileSync2(gitignorePath, "utf-8");
|
|
12064
12864
|
const lines = content.split("\n").map((l) => l.trim());
|
|
@@ -12075,7 +12875,7 @@ function ensureGitignore(repoRoot, entry) {
|
|
|
12075
12875
|
// src/lib/graph-cache.ts
|
|
12076
12876
|
init_config_dir();
|
|
12077
12877
|
import { mkdirSync, writeFileSync as writeFileSync2, renameSync, unlinkSync } from "fs";
|
|
12078
|
-
import { dirname as dirname18, join as
|
|
12878
|
+
import { dirname as dirname18, join as join48 } from "path";
|
|
12079
12879
|
var SAFE_REPO_ID = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
12080
12880
|
function assertSafeRepoId2(repoId) {
|
|
12081
12881
|
if (!repoId || typeof repoId !== "string" || !SAFE_REPO_ID.test(repoId) || repoId === "." || repoId === ".." || repoId.startsWith(".")) {
|
|
@@ -12084,7 +12884,7 @@ function assertSafeRepoId2(repoId) {
|
|
|
12084
12884
|
}
|
|
12085
12885
|
function graphCachePath(repoId) {
|
|
12086
12886
|
assertSafeRepoId2(repoId);
|
|
12087
|
-
return
|
|
12887
|
+
return join48(getConfigDir(), "graphs", `${repoId}.json`);
|
|
12088
12888
|
}
|
|
12089
12889
|
function isUsableGraph(parsed) {
|
|
12090
12890
|
if (typeof parsed !== "object" || parsed === null) return false;
|
|
@@ -12513,19 +13313,17 @@ var ProgressRenderer = class {
|
|
|
12513
13313
|
});
|
|
12514
13314
|
}
|
|
12515
13315
|
/**
|
|
12516
|
-
*
|
|
12517
|
-
*
|
|
12518
|
-
*
|
|
13316
|
+
* Latch marking the end of the user-driven interview and the start of the
|
|
13317
|
+
* long machine-driven scan/generate/validate phases. The post-interview
|
|
13318
|
+
* "coffee" line now rides with the paid "RepoWise MCP is live" message
|
|
13319
|
+
* (create.ts), emitted once the knowledge graph is actually downloadable \u2014
|
|
13320
|
+
* so it no longer fires here. The stop/start keeps the spinner cadence
|
|
13321
|
+
* stable across the interview\u2192scan handoff.
|
|
12519
13322
|
*/
|
|
12520
13323
|
renderInterviewComplete(spinner) {
|
|
12521
13324
|
if (this.interviewCompleteShown) return;
|
|
12522
13325
|
this.interviewCompleteShown = true;
|
|
12523
13326
|
spinner.stop();
|
|
12524
|
-
console.log("");
|
|
12525
|
-
console.log(
|
|
12526
|
-
chalk4.cyan(" \u2615 Sit back and grab a coffee \u2014 we'll handle the rest from here.")
|
|
12527
|
-
);
|
|
12528
|
-
console.log("");
|
|
12529
13327
|
spinner.start();
|
|
12530
13328
|
}
|
|
12531
13329
|
renderScanProgress(progress, spinner) {
|
|
@@ -12700,17 +13498,27 @@ var ProgressRenderer = class {
|
|
|
12700
13498
|
if (generating) {
|
|
12701
13499
|
const genBaseName = generating.fileName.split("/").pop() ?? generating.fileName;
|
|
12702
13500
|
const isCore = CORE_FILES.has(genBaseName);
|
|
12703
|
-
const
|
|
13501
|
+
const sectionFiles2 = gp.fileStatuses.filter((f) => {
|
|
12704
13502
|
const bn = f.fileName.split("/").pop() ?? f.fileName;
|
|
12705
13503
|
return isCore ? CORE_FILES.has(bn) : !CORE_FILES.has(bn);
|
|
12706
13504
|
});
|
|
12707
|
-
const sectionCompleted =
|
|
12708
|
-
return `${generating.fileName} (${sectionCompleted}/${
|
|
13505
|
+
const sectionCompleted = sectionFiles2.filter((f) => f.status === "completed").length;
|
|
13506
|
+
return `${generating.fileName} (${sectionCompleted}/${sectionFiles2.length}) ${chalk4.dim(`(${overallPct}%)`)}`;
|
|
12709
13507
|
}
|
|
12710
13508
|
const allDone = gp.fileStatuses.every((f) => f.status === "completed");
|
|
12711
13509
|
if (allDone) {
|
|
12712
13510
|
return `${stepLabel}... ${chalk4.dim(`(${overallPct}%)`)}`;
|
|
12713
13511
|
}
|
|
13512
|
+
const baseNameOf = (f) => f.fileName.split("/").pop() ?? f.fileName;
|
|
13513
|
+
const coreFiles = gp.fileStatuses.filter((f) => CORE_FILES.has(baseNameOf(f)));
|
|
13514
|
+
const optionalFiles = gp.fileStatuses.filter((f) => !CORE_FILES.has(baseNameOf(f)));
|
|
13515
|
+
const coreDone = coreFiles.length > 0 && coreFiles.every((f) => f.status === "completed");
|
|
13516
|
+
const sectionFiles = coreDone && optionalFiles.length > 0 ? optionalFiles : coreFiles;
|
|
13517
|
+
if (sectionFiles.length > 0) {
|
|
13518
|
+
const sectionCompleted = sectionFiles.filter((f) => f.status === "completed").length;
|
|
13519
|
+
const name = gp.currentFileName || stepLabel;
|
|
13520
|
+
return `${name} (${sectionCompleted}/${sectionFiles.length}) ${chalk4.dim(`(${overallPct}%)`)}`;
|
|
13521
|
+
}
|
|
12714
13522
|
}
|
|
12715
13523
|
}
|
|
12716
13524
|
return `${stepLabel}... ${chalk4.dim(`(${overallPct}%)`)}`;
|
|
@@ -12801,8 +13609,8 @@ async function promptDepInstallConsent(missing, opts) {
|
|
|
12801
13609
|
import chalk6 from "chalk";
|
|
12802
13610
|
|
|
12803
13611
|
// src/lib/dep-installer.ts
|
|
12804
|
-
import { promises as
|
|
12805
|
-
import { join as
|
|
13612
|
+
import { promises as fs22 } from "fs";
|
|
13613
|
+
import { join as join49 } from "path";
|
|
12806
13614
|
var SUPPORTED_DEP_LANGUAGES = /* @__PURE__ */ new Set([
|
|
12807
13615
|
"typescript",
|
|
12808
13616
|
"javascript",
|
|
@@ -12812,14 +13620,14 @@ var SUPPORTED_DEP_LANGUAGES = /* @__PURE__ */ new Set([
|
|
|
12812
13620
|
]);
|
|
12813
13621
|
var exists = async (p) => {
|
|
12814
13622
|
try {
|
|
12815
|
-
await
|
|
13623
|
+
await fs22.access(p);
|
|
12816
13624
|
return true;
|
|
12817
13625
|
} catch {
|
|
12818
13626
|
return false;
|
|
12819
13627
|
}
|
|
12820
13628
|
};
|
|
12821
13629
|
async function fileExists16(repoRoot, name) {
|
|
12822
|
-
return exists(
|
|
13630
|
+
return exists(join49(repoRoot, name));
|
|
12823
13631
|
}
|
|
12824
13632
|
async function detectNodePackageManager(repoRoot) {
|
|
12825
13633
|
if (await fileExists16(repoRoot, "pnpm-lock.yaml")) {
|
|
@@ -12896,13 +13704,13 @@ async function detectMissingDeps(repoRoot, scopedLanguages) {
|
|
|
12896
13704
|
// src/lib/dep-installer-runner.ts
|
|
12897
13705
|
import { spawn as spawn11 } from "child_process";
|
|
12898
13706
|
import { createWriteStream as createWriteStream3 } from "fs";
|
|
12899
|
-
import { promises as
|
|
12900
|
-
import { join as
|
|
13707
|
+
import { promises as fs23 } from "fs";
|
|
13708
|
+
import { join as join50 } from "path";
|
|
12901
13709
|
var DEFAULT_INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
12902
13710
|
async function runMissingDepInstalls(opts) {
|
|
12903
13711
|
const safeRepoId = opts.repoId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
12904
|
-
const logPath2 =
|
|
12905
|
-
await
|
|
13712
|
+
const logPath2 = join50(getConfigDir2(), `install-log.${safeRepoId}.txt`);
|
|
13713
|
+
await fs23.mkdir(getConfigDir2(), { recursive: true });
|
|
12906
13714
|
const stream = opts.logStream ?? createWriteStream3(logPath2, { flags: "a" });
|
|
12907
13715
|
stream.on("error", () => {
|
|
12908
13716
|
});
|
|
@@ -12948,7 +13756,7 @@ async function runOne2(dep, repoRoot, stream, timeoutMs) {
|
|
|
12948
13756
|
}
|
|
12949
13757
|
async function runPipFlow(repoRoot, stream, timeoutMs) {
|
|
12950
13758
|
await runSimple(["python3", "-m", "venv", ".venv"], repoRoot, stream, timeoutMs);
|
|
12951
|
-
const venvPip = process.platform === "win32" ?
|
|
13759
|
+
const venvPip = process.platform === "win32" ? join50(".venv", "Scripts", "pip.exe") : join50(".venv", "bin", "pip");
|
|
12952
13760
|
await runSimple([venvPip, "install", "-r", "requirements.txt"], repoRoot, stream, timeoutMs);
|
|
12953
13761
|
}
|
|
12954
13762
|
function runSimple(cmd, repoRoot, stream, timeoutMs) {
|
|
@@ -13107,13 +13915,15 @@ function reportLspInstallOutcomes(results) {
|
|
|
13107
13915
|
// src/commands/create.ts
|
|
13108
13916
|
function formatElapsed(ms) {
|
|
13109
13917
|
const totalSeconds = Math.round(ms / 1e3);
|
|
13110
|
-
const
|
|
13918
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
13919
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
13111
13920
|
const seconds = totalSeconds % 60;
|
|
13921
|
+
if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
13112
13922
|
if (minutes === 0) return `${seconds}s`;
|
|
13113
13923
|
return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
13114
13924
|
}
|
|
13115
13925
|
var POLL_INTERVAL_MS = 3e3;
|
|
13116
|
-
var MAX_POLL_ATTEMPTS =
|
|
13926
|
+
var MAX_POLL_ATTEMPTS = 14600;
|
|
13117
13927
|
var DEFAULT_CONTEXT_FOLDER = "repowise-context";
|
|
13118
13928
|
function buildWatchedRepoEntry(args) {
|
|
13119
13929
|
const entry = {
|
|
@@ -13286,9 +14096,7 @@ async function create() {
|
|
|
13286
14096
|
}
|
|
13287
14097
|
if (tools.length === 0 && !hasOther) {
|
|
13288
14098
|
console.log(
|
|
13289
|
-
chalk8.yellow(
|
|
13290
|
-
"\nNo AI tools selected. You can configure them later with `repowise config`."
|
|
13291
|
-
)
|
|
14099
|
+
chalk8.yellow("\nNo AI tools selected. You can set them up later with `repowise tools`.")
|
|
13292
14100
|
);
|
|
13293
14101
|
}
|
|
13294
14102
|
if (repoRoot) {
|
|
@@ -13358,6 +14166,8 @@ async function create() {
|
|
|
13358
14166
|
}
|
|
13359
14167
|
let pollAttempts = 0;
|
|
13360
14168
|
let pollErrors = 0;
|
|
14169
|
+
let graphEarlyWriteDone = false;
|
|
14170
|
+
let nonAwaitingPolls = 0;
|
|
13361
14171
|
const MAX_POLL_ERRORS = 5;
|
|
13362
14172
|
const progressRenderer = new ProgressRenderer();
|
|
13363
14173
|
let depInstallShown = false;
|
|
@@ -13428,8 +14238,17 @@ async function create() {
|
|
|
13428
14238
|
if (!interviewComplete && graphOnly) {
|
|
13429
14239
|
interviewComplete = true;
|
|
13430
14240
|
progressRenderer.setInterviewComplete(spinner);
|
|
14241
|
+
spinner.stop();
|
|
14242
|
+
console.log(
|
|
14243
|
+
chalk8.cyan(
|
|
14244
|
+
" \u2615 Building your code graph \u2014 MCP will be live in a moment (Free plan: graph + MCP)."
|
|
14245
|
+
)
|
|
14246
|
+
);
|
|
14247
|
+
spinner.start();
|
|
13431
14248
|
}
|
|
13432
14249
|
progressRenderer.update(syncResult, spinner);
|
|
14250
|
+
if (syncResult.status === "awaiting_input") nonAwaitingPolls = 0;
|
|
14251
|
+
else nonAwaitingPolls += 1;
|
|
13433
14252
|
if (!graphOnly && syncResult.status === "in_progress") {
|
|
13434
14253
|
if (syncResult.lspWaitState === "awaiting" || syncResult.lspWaitState === "analyzing") {
|
|
13435
14254
|
lspWaitStartedAt ??= Date.now();
|
|
@@ -13443,6 +14262,45 @@ async function create() {
|
|
|
13443
14262
|
spinner.start();
|
|
13444
14263
|
}
|
|
13445
14264
|
}
|
|
14265
|
+
const interviewPhaseOver = interviewComplete || nonAwaitingPolls >= 3;
|
|
14266
|
+
if (!graphOnly && !graphEarlyWriteDone && syncResult.graphReady === true && syncResult.status !== "awaiting_input" && interviewPhaseOver && repoRoot) {
|
|
14267
|
+
const earlyCreds = await getValidCredentials2();
|
|
14268
|
+
if (earlyCreds) {
|
|
14269
|
+
graphEarlyWriteDone = true;
|
|
14270
|
+
if (!interviewComplete) {
|
|
14271
|
+
interviewComplete = true;
|
|
14272
|
+
progressRenderer.setInterviewComplete(spinner);
|
|
14273
|
+
}
|
|
14274
|
+
spinner.stop();
|
|
14275
|
+
await ensureGraphDownloaded({
|
|
14276
|
+
repoId,
|
|
14277
|
+
accessToken: earlyCreds.accessToken,
|
|
14278
|
+
apiUrl: getEnvConfig().apiUrl
|
|
14279
|
+
});
|
|
14280
|
+
await writeToolConfigsForRepo({
|
|
14281
|
+
repoRoot,
|
|
14282
|
+
tools,
|
|
14283
|
+
repoName,
|
|
14284
|
+
contextFolder: DEFAULT_CONTEXT_FOLDER,
|
|
14285
|
+
contextFiles: [],
|
|
14286
|
+
variant: "graph",
|
|
14287
|
+
migrateLegacy: true
|
|
14288
|
+
});
|
|
14289
|
+
if (!sigintBusy) {
|
|
14290
|
+
console.log("");
|
|
14291
|
+
console.log(
|
|
14292
|
+
chalk8.green(" \u2714 RepoWise MCP is live \u2014 start exploring your codebase now.")
|
|
14293
|
+
);
|
|
14294
|
+
console.log(
|
|
14295
|
+
chalk8.cyan(
|
|
14296
|
+
" \u2615 Full docs are still generating in the background \u2014 your AI tools auto-upgrade when they're ready (graph precision keeps improving too)."
|
|
14297
|
+
)
|
|
14298
|
+
);
|
|
14299
|
+
console.log("");
|
|
14300
|
+
spinner.start();
|
|
14301
|
+
}
|
|
14302
|
+
}
|
|
14303
|
+
}
|
|
13446
14304
|
if (syncResult.status === "awaiting_input") {
|
|
13447
14305
|
seenAwaitingInput = true;
|
|
13448
14306
|
}
|
|
@@ -13521,7 +14379,7 @@ async function create() {
|
|
|
13521
14379
|
const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
|
|
13522
14380
|
const files = listResult.data?.files ?? listResult.files ?? [];
|
|
13523
14381
|
if (files.length > 0) {
|
|
13524
|
-
const contextDir =
|
|
14382
|
+
const contextDir = join51(repoRoot, DEFAULT_CONTEXT_FOLDER);
|
|
13525
14383
|
mkdirSync2(contextDir, { recursive: true });
|
|
13526
14384
|
let downloadedCount = 0;
|
|
13527
14385
|
let failedCount = 0;
|
|
@@ -13535,7 +14393,7 @@ async function create() {
|
|
|
13535
14393
|
const response = await fetch(presignedUrl);
|
|
13536
14394
|
if (response.ok) {
|
|
13537
14395
|
const content = await response.text();
|
|
13538
|
-
const filePath =
|
|
14396
|
+
const filePath = join51(contextDir, file.fileName);
|
|
13539
14397
|
mkdirSync2(dirname19(filePath), { recursive: true });
|
|
13540
14398
|
writeFileSync3(filePath, content, "utf-8");
|
|
13541
14399
|
downloadedCount++;
|
|
@@ -13619,51 +14477,24 @@ Files are stored on our servers (not in git). Retry when online.`
|
|
|
13619
14477
|
}
|
|
13620
14478
|
if (repoRoot) {
|
|
13621
14479
|
spinner.start("Configuring AI tools...");
|
|
13622
|
-
const results =
|
|
13623
|
-
|
|
13624
|
-
|
|
13625
|
-
|
|
13626
|
-
|
|
13627
|
-
|
|
13628
|
-
|
|
13629
|
-
|
|
13630
|
-
|
|
13631
|
-
|
|
13632
|
-
|
|
13633
|
-
|
|
13634
|
-
|
|
13635
|
-
|
|
13636
|
-
|
|
13637
|
-
)
|
|
13638
|
-
}
|
|
13639
|
-
const { created: wasCreated } = await updateToolConfig(
|
|
13640
|
-
repoRoot,
|
|
13641
|
-
tool,
|
|
13642
|
-
repoName,
|
|
13643
|
-
contextFolder,
|
|
13644
|
-
contextFiles,
|
|
13645
|
-
toolVariant
|
|
13646
|
-
);
|
|
13647
|
-
const action = wasCreated ? "Created" : "Updated";
|
|
13648
|
-
results.push(` ${action} ${config2.filePath}`);
|
|
13649
|
-
}
|
|
13650
|
-
if (!written.has("AGENTS.md")) {
|
|
13651
|
-
const { created: wasCreated } = await updateToolConfig(
|
|
13652
|
-
repoRoot,
|
|
13653
|
-
"codex",
|
|
13654
|
-
repoName,
|
|
13655
|
-
contextFolder,
|
|
13656
|
-
contextFiles,
|
|
13657
|
-
toolVariant
|
|
14480
|
+
const results = await writeToolConfigsForRepo({
|
|
14481
|
+
repoRoot,
|
|
14482
|
+
tools,
|
|
14483
|
+
repoName,
|
|
14484
|
+
contextFolder,
|
|
14485
|
+
contextFiles,
|
|
14486
|
+
variant: graphOnly ? "graph" : "full",
|
|
14487
|
+
migrateLegacy: true
|
|
14488
|
+
});
|
|
14489
|
+
spinner.succeed("AI tools configured");
|
|
14490
|
+
console.log(chalk8.dim(results.map((r) => ` ${r}`).join("\n")));
|
|
14491
|
+
if (!graphOnly && contextFiles.length > 0) {
|
|
14492
|
+
console.log(
|
|
14493
|
+
chalk8.cyan(
|
|
14494
|
+
" \u2728 Full context docs are ready \u2014 start a new AI session (or restart your AI tool) to pick up the upgraded instructions."
|
|
14495
|
+
)
|
|
13658
14496
|
);
|
|
13659
|
-
results.push(` ${wasCreated ? "Created" : "Updated"} AGENTS.md`);
|
|
13660
|
-
}
|
|
13661
|
-
if (tools.includes("claude-code")) {
|
|
13662
|
-
const { relPath } = await writeClaudeHooksToRepo(repoRoot, contextFolder, toolVariant);
|
|
13663
|
-
results.push(` Configured ${relPath} (RepoWise-first + SubagentStart hooks, local-only)`);
|
|
13664
14497
|
}
|
|
13665
|
-
spinner.succeed("AI tools configured");
|
|
13666
|
-
console.log(chalk8.dim(results.join("\n")));
|
|
13667
14498
|
}
|
|
13668
14499
|
const priorAutoInstall = await getPriorConsent(repoId);
|
|
13669
14500
|
const updatedRepos = [];
|
|
@@ -13772,6 +14603,11 @@ Files are stored on our servers (not in git). Retry when online.`
|
|
|
13772
14603
|
console.log(
|
|
13773
14604
|
` ${chalk8.cyan("\u2022")} Open Claude Code / Cursor and ask: "What does this repo do?"`
|
|
13774
14605
|
);
|
|
14606
|
+
if (tools.some((t) => MCP_WRITER_TOOLS.has(t))) {
|
|
14607
|
+
console.log(
|
|
14608
|
+
` ${chalk8.cyan("\u2022")} If your AI tool asks to approve the "RepoWise MCP" server, approve it (in Claude Code, run ${chalk8.cyan("/mcp")}) \u2014 it can take a minute to appear.`
|
|
14609
|
+
);
|
|
14610
|
+
}
|
|
13775
14611
|
console.log(
|
|
13776
14612
|
` ${chalk8.cyan("\u2022")} Head to the dashboard \u2192 "Complete Onboarding" to explore quality scores and gaps.`
|
|
13777
14613
|
);
|
|
@@ -13786,7 +14622,7 @@ Files are stored on our servers (not in git). Retry when online.`
|
|
|
13786
14622
|
|
|
13787
14623
|
// src/commands/member.ts
|
|
13788
14624
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
13789
|
-
import { dirname as dirname20, join as
|
|
14625
|
+
import { dirname as dirname20, join as join52, resolve, sep } from "path";
|
|
13790
14626
|
import chalk9 from "chalk";
|
|
13791
14627
|
import ora2 from "ora";
|
|
13792
14628
|
var DEFAULT_CONTEXT_FOLDER2 = "repowise-context";
|
|
@@ -13940,7 +14776,7 @@ async function member() {
|
|
|
13940
14776
|
spinner.succeed(`Found ${chalk9.bold(files.length)} context files on server`);
|
|
13941
14777
|
const { tools } = await selectAiTools();
|
|
13942
14778
|
spinner.start("Downloading context files...");
|
|
13943
|
-
const contextDir =
|
|
14779
|
+
const contextDir = join52(repoRoot, DEFAULT_CONTEXT_FOLDER2);
|
|
13944
14780
|
mkdirSync3(contextDir, { recursive: true });
|
|
13945
14781
|
let downloadedCount = 0;
|
|
13946
14782
|
let failedCount = 0;
|
|
@@ -13985,35 +14821,14 @@ async function member() {
|
|
|
13985
14821
|
{
|
|
13986
14822
|
spinner.start("Configuring AI tools...");
|
|
13987
14823
|
const contextFiles = await scanLocalContextFiles(repoRoot, DEFAULT_CONTEXT_FOLDER2);
|
|
13988
|
-
const configured =
|
|
13989
|
-
|
|
13990
|
-
|
|
13991
|
-
|
|
13992
|
-
|
|
13993
|
-
|
|
13994
|
-
|
|
13995
|
-
|
|
13996
|
-
tool,
|
|
13997
|
-
repoName,
|
|
13998
|
-
DEFAULT_CONTEXT_FOLDER2,
|
|
13999
|
-
contextFiles
|
|
14000
|
-
);
|
|
14001
|
-
configured.push(`${created ? "Created" : "Updated"} ${config2.filePath}`);
|
|
14002
|
-
}
|
|
14003
|
-
if (!written.has("AGENTS.md")) {
|
|
14004
|
-
const { created } = await updateToolConfig(
|
|
14005
|
-
repoRoot,
|
|
14006
|
-
"codex",
|
|
14007
|
-
repoName,
|
|
14008
|
-
DEFAULT_CONTEXT_FOLDER2,
|
|
14009
|
-
contextFiles
|
|
14010
|
-
);
|
|
14011
|
-
configured.push(`${created ? "Created" : "Updated"} AGENTS.md`);
|
|
14012
|
-
}
|
|
14013
|
-
if (tools.includes("claude-code")) {
|
|
14014
|
-
const { relPath } = await writeClaudeHooksToRepo(repoRoot, DEFAULT_CONTEXT_FOLDER2);
|
|
14015
|
-
configured.push(`Configured ${relPath} (RepoWise-first + SubagentStart hooks, local-only)`);
|
|
14016
|
-
}
|
|
14824
|
+
const configured = await writeToolConfigsForRepo({
|
|
14825
|
+
repoRoot,
|
|
14826
|
+
tools,
|
|
14827
|
+
repoName,
|
|
14828
|
+
contextFolder: DEFAULT_CONTEXT_FOLDER2,
|
|
14829
|
+
contextFiles
|
|
14830
|
+
// member historically passed no variant and did not migrate legacy paths.
|
|
14831
|
+
});
|
|
14017
14832
|
spinner.succeed("AI tools configured");
|
|
14018
14833
|
for (const msg of configured) {
|
|
14019
14834
|
console.log(chalk9.dim(` ${msg}`));
|
|
@@ -14134,19 +14949,20 @@ async function member() {
|
|
|
14134
14949
|
}
|
|
14135
14950
|
|
|
14136
14951
|
// src/commands/login.ts
|
|
14952
|
+
import { homedir as homedir9 } from "os";
|
|
14137
14953
|
import chalk10 from "chalk";
|
|
14138
14954
|
import ora3 from "ora";
|
|
14139
14955
|
|
|
14140
14956
|
// src/lib/tenant-graph-purge.ts
|
|
14141
|
-
import { promises as
|
|
14957
|
+
import { promises as fs24 } from "fs";
|
|
14142
14958
|
import { homedir as homedir8 } from "os";
|
|
14143
|
-
import { join as
|
|
14959
|
+
import { join as join53 } from "path";
|
|
14144
14960
|
async function purgeForeignGraphs(validRepoIds, home = homedir8()) {
|
|
14145
|
-
const graphsDir =
|
|
14961
|
+
const graphsDir = join53(home, ".repowise", "graphs");
|
|
14146
14962
|
const result = { kept: [], removed: [] };
|
|
14147
14963
|
let entries;
|
|
14148
14964
|
try {
|
|
14149
|
-
entries = await
|
|
14965
|
+
entries = await fs24.readdir(graphsDir);
|
|
14150
14966
|
} catch (err) {
|
|
14151
14967
|
if (err.code === "ENOENT") return result;
|
|
14152
14968
|
throw err;
|
|
@@ -14160,15 +14976,15 @@ async function purgeForeignGraphs(validRepoIds, home = homedir8()) {
|
|
|
14160
14976
|
result.kept.push(entry);
|
|
14161
14977
|
continue;
|
|
14162
14978
|
}
|
|
14163
|
-
const path =
|
|
14979
|
+
const path = join53(graphsDir, entry);
|
|
14164
14980
|
try {
|
|
14165
|
-
const stat8 = await
|
|
14981
|
+
const stat8 = await fs24.lstat(path);
|
|
14166
14982
|
if (stat8.isSymbolicLink()) {
|
|
14167
|
-
await
|
|
14983
|
+
await fs24.unlink(path);
|
|
14168
14984
|
result.removed.push(entry);
|
|
14169
14985
|
continue;
|
|
14170
14986
|
}
|
|
14171
|
-
await
|
|
14987
|
+
await fs24.rm(path, { recursive: true, force: true });
|
|
14172
14988
|
result.removed.push(entry);
|
|
14173
14989
|
} catch {
|
|
14174
14990
|
}
|
|
@@ -14196,8 +15012,8 @@ Waiting for authentication...`);
|
|
|
14196
15012
|
} else {
|
|
14197
15013
|
spinner.text = "Opening browser for authentication...";
|
|
14198
15014
|
try {
|
|
14199
|
-
const
|
|
14200
|
-
await
|
|
15015
|
+
const open6 = (await import("open")).default;
|
|
15016
|
+
await open6(authorizeUrl);
|
|
14201
15017
|
spinner.text = "Waiting for authentication in browser...";
|
|
14202
15018
|
} catch {
|
|
14203
15019
|
spinner.stop();
|
|
@@ -14232,6 +15048,23 @@ Waiting for authentication...`);
|
|
|
14232
15048
|
}
|
|
14233
15049
|
} catch {
|
|
14234
15050
|
}
|
|
15051
|
+
try {
|
|
15052
|
+
const repoRoot = detectRepoRoot();
|
|
15053
|
+
const cfg = await getConfig();
|
|
15054
|
+
const registered = (cfg.repos ?? []).some((r) => r.localPath === repoRoot);
|
|
15055
|
+
const tools = cfg.aiTools ?? [];
|
|
15056
|
+
if (registered && tools.length > 0) {
|
|
15057
|
+
const pending = (await mcpStatusForRepo({ repoRoot, home: homedir9(), aiTools: tools })).filter((s) => !s.configured);
|
|
15058
|
+
if (pending.length > 0) {
|
|
15059
|
+
console.log("");
|
|
15060
|
+
console.log(chalk10.dim(" To finish enabling RepoWise in your AI tools:"));
|
|
15061
|
+
for (const p of pending) {
|
|
15062
|
+
if (p.hint) console.log(chalk10.dim(` \u2022 ${p.hint}`));
|
|
15063
|
+
}
|
|
15064
|
+
}
|
|
15065
|
+
}
|
|
15066
|
+
} catch {
|
|
15067
|
+
}
|
|
14235
15068
|
} catch (err) {
|
|
14236
15069
|
const message = err instanceof Error ? err.message : "Login failed";
|
|
14237
15070
|
spinner.fail(chalk10.red(message));
|
|
@@ -14247,20 +15080,25 @@ async function logout() {
|
|
|
14247
15080
|
console.log(chalk11.yellow("Not logged in."));
|
|
14248
15081
|
return;
|
|
14249
15082
|
}
|
|
15083
|
+
try {
|
|
15084
|
+
await revokeRefreshToken(creds.refreshToken);
|
|
15085
|
+
} catch {
|
|
15086
|
+
}
|
|
14250
15087
|
await clearCredentials();
|
|
14251
15088
|
console.log(chalk11.green("Logged out successfully."));
|
|
14252
15089
|
}
|
|
14253
15090
|
|
|
14254
15091
|
// src/commands/status.ts
|
|
14255
|
-
import { readFile as
|
|
14256
|
-
import { basename as
|
|
15092
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
15093
|
+
import { basename as basename5, join as join54 } from "path";
|
|
15094
|
+
import { homedir as homedir10 } from "os";
|
|
14257
15095
|
async function status() {
|
|
14258
15096
|
const configDir = getConfigDir2();
|
|
14259
|
-
const STATE_PATH =
|
|
14260
|
-
const CONFIG_PATH =
|
|
15097
|
+
const STATE_PATH = join54(configDir, "listener-state.json");
|
|
15098
|
+
const CONFIG_PATH = join54(configDir, "config.json");
|
|
14261
15099
|
let state = null;
|
|
14262
15100
|
try {
|
|
14263
|
-
const data = await
|
|
15101
|
+
const data = await readFile17(STATE_PATH, "utf-8");
|
|
14264
15102
|
state = JSON.parse(data);
|
|
14265
15103
|
} catch {
|
|
14266
15104
|
}
|
|
@@ -14279,42 +15117,85 @@ async function status() {
|
|
|
14279
15117
|
} else {
|
|
14280
15118
|
console.log("Auto-start: disabled");
|
|
14281
15119
|
}
|
|
15120
|
+
let creds = null;
|
|
15121
|
+
try {
|
|
15122
|
+
creds = await getStoredCredentials2();
|
|
15123
|
+
} catch {
|
|
15124
|
+
}
|
|
15125
|
+
if (isCredsHardExpired(creds)) {
|
|
15126
|
+
console.log("Authentication: not logged in \u2014 run `repowise login`");
|
|
15127
|
+
} else {
|
|
15128
|
+
let email = null;
|
|
15129
|
+
try {
|
|
15130
|
+
const decoded = creds?.idToken ? decodeIdToken(creds.idToken).email : null;
|
|
15131
|
+
email = decoded && decoded !== "unknown" ? decoded : null;
|
|
15132
|
+
} catch {
|
|
15133
|
+
}
|
|
15134
|
+
console.log(`Authentication: logged in${email ? ` as ${email}` : ""}`);
|
|
15135
|
+
}
|
|
14282
15136
|
console.log("");
|
|
14283
15137
|
if (!state || Object.keys(state.repos).length === 0) {
|
|
14284
15138
|
console.log("No sync history. Run `repowise listen` to start syncing.");
|
|
14285
15139
|
return;
|
|
14286
15140
|
}
|
|
14287
15141
|
const repoNames = /* @__PURE__ */ new Map();
|
|
15142
|
+
const repoPaths = /* @__PURE__ */ new Map();
|
|
15143
|
+
let aiTools = [];
|
|
14288
15144
|
try {
|
|
14289
|
-
const configData = await
|
|
15145
|
+
const configData = await readFile17(CONFIG_PATH, "utf-8");
|
|
14290
15146
|
const config2 = JSON.parse(configData);
|
|
14291
15147
|
for (const repo of config2.repos ?? []) {
|
|
14292
|
-
repoNames.set(repo.repoId,
|
|
15148
|
+
repoNames.set(repo.repoId, basename5(repo.localPath));
|
|
15149
|
+
repoPaths.set(repo.repoId, repo.localPath);
|
|
14293
15150
|
}
|
|
15151
|
+
aiTools = config2.aiTools ?? [];
|
|
14294
15152
|
} catch {
|
|
14295
15153
|
}
|
|
14296
15154
|
console.log("Watched Repos:");
|
|
15155
|
+
const home = homedir10();
|
|
14297
15156
|
for (const [repoId, repoState] of Object.entries(state.repos)) {
|
|
14298
15157
|
const name = repoNames.get(repoId);
|
|
14299
15158
|
const label = name ? `${name} (${repoId.slice(0, 8)})` : repoId;
|
|
14300
15159
|
const syncTime = repoState.lastSyncTimestamp ? new Date(repoState.lastSyncTimestamp).toLocaleString() : "never";
|
|
14301
15160
|
const commit = repoState.lastSyncCommitSha ? repoState.lastSyncCommitSha.slice(0, 7) : "none";
|
|
14302
15161
|
console.log(` ${label}: last sync ${syncTime} (commit: ${commit})`);
|
|
15162
|
+
const localPath = repoPaths.get(repoId);
|
|
15163
|
+
if (localPath && aiTools.length > 0) {
|
|
15164
|
+
try {
|
|
15165
|
+
const statuses = await mcpStatusForRepo({ repoRoot: localPath, home, aiTools });
|
|
15166
|
+
if (statuses.length > 0) {
|
|
15167
|
+
const pending = statuses.filter((s) => !s.configured);
|
|
15168
|
+
if (pending.length === 0) {
|
|
15169
|
+
console.log(` MCP: active in all ${statuses.length} tool(s)`);
|
|
15170
|
+
} else {
|
|
15171
|
+
console.log(
|
|
15172
|
+
` MCP: ${statuses.length - pending.length}/${statuses.length} tool(s) active \u2014 to finish:`
|
|
15173
|
+
);
|
|
15174
|
+
for (const p of pending) {
|
|
15175
|
+
if (p.hint) console.log(` \u2022 ${p.hint}`);
|
|
15176
|
+
}
|
|
15177
|
+
}
|
|
15178
|
+
}
|
|
15179
|
+
} catch {
|
|
15180
|
+
}
|
|
15181
|
+
}
|
|
14303
15182
|
}
|
|
14304
15183
|
}
|
|
14305
15184
|
|
|
14306
15185
|
// src/commands/sync.ts
|
|
14307
15186
|
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
14308
|
-
import { dirname as dirname21, join as
|
|
15187
|
+
import { dirname as dirname21, join as join55 } from "path";
|
|
14309
15188
|
import chalk12 from "chalk";
|
|
14310
15189
|
import ora4 from "ora";
|
|
14311
15190
|
var POLL_INTERVAL_MS2 = 3e3;
|
|
14312
|
-
var MAX_POLL_ATTEMPTS2 =
|
|
15191
|
+
var MAX_POLL_ATTEMPTS2 = 14600;
|
|
14313
15192
|
var DEFAULT_CONTEXT_FOLDER3 = "repowise-context";
|
|
14314
15193
|
function formatElapsed3(ms) {
|
|
14315
15194
|
const totalSeconds = Math.round(ms / 1e3);
|
|
14316
|
-
const
|
|
15195
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
15196
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
14317
15197
|
const seconds = totalSeconds % 60;
|
|
15198
|
+
if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
14318
15199
|
if (minutes === 0) return `${seconds}s`;
|
|
14319
15200
|
return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
14320
15201
|
}
|
|
@@ -14454,7 +15335,7 @@ async function sync() {
|
|
|
14454
15335
|
const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
|
|
14455
15336
|
const files = listResult.data?.files ?? listResult.files ?? [];
|
|
14456
15337
|
if (files.length > 0) {
|
|
14457
|
-
const contextDir =
|
|
15338
|
+
const contextDir = join55(repoRoot, DEFAULT_CONTEXT_FOLDER3);
|
|
14458
15339
|
mkdirSync4(contextDir, { recursive: true });
|
|
14459
15340
|
let downloadedCount = 0;
|
|
14460
15341
|
let failedCount = 0;
|
|
@@ -14468,7 +15349,7 @@ async function sync() {
|
|
|
14468
15349
|
const response = await fetch(presignedUrl);
|
|
14469
15350
|
if (response.ok) {
|
|
14470
15351
|
const content = await response.text();
|
|
14471
|
-
const filePath =
|
|
15352
|
+
const filePath = join55(contextDir, file.fileName);
|
|
14472
15353
|
mkdirSync4(dirname21(filePath), { recursive: true });
|
|
14473
15354
|
writeFileSync5(filePath, content, "utf-8");
|
|
14474
15355
|
downloadedCount++;
|
|
@@ -14721,10 +15602,158 @@ async function config() {
|
|
|
14721
15602
|
}
|
|
14722
15603
|
}
|
|
14723
15604
|
|
|
15605
|
+
// src/commands/tools.ts
|
|
15606
|
+
import { basename as basename6 } from "path";
|
|
15607
|
+
import chalk14 from "chalk";
|
|
15608
|
+
import ora6 from "ora";
|
|
15609
|
+
var DEFAULT_CONTEXT_FOLDER4 = "repowise-context";
|
|
15610
|
+
var VALID_TOOLS = Object.keys(AI_TOOL_CONFIG);
|
|
15611
|
+
async function resolveRepoOrExit() {
|
|
15612
|
+
let creds = null;
|
|
15613
|
+
try {
|
|
15614
|
+
creds = await getValidCredentials2();
|
|
15615
|
+
} catch {
|
|
15616
|
+
creds = null;
|
|
15617
|
+
}
|
|
15618
|
+
if (!creds) {
|
|
15619
|
+
console.error(chalk14.red("Not logged in. Run `repowise login` first."));
|
|
15620
|
+
process.exitCode = 1;
|
|
15621
|
+
return null;
|
|
15622
|
+
}
|
|
15623
|
+
let repoRoot;
|
|
15624
|
+
try {
|
|
15625
|
+
repoRoot = detectRepoRoot();
|
|
15626
|
+
} catch {
|
|
15627
|
+
console.error(chalk14.red("Not inside a git repository \u2014 `cd` into your repo first."));
|
|
15628
|
+
process.exitCode = 1;
|
|
15629
|
+
return null;
|
|
15630
|
+
}
|
|
15631
|
+
const config2 = await getConfig();
|
|
15632
|
+
const registered = (config2.repos ?? []).some((r) => r.localPath === repoRoot);
|
|
15633
|
+
if (!registered) {
|
|
15634
|
+
console.error(
|
|
15635
|
+
chalk14.red("This repo isn't set up with RepoWise yet. Run `repowise create` first.")
|
|
15636
|
+
);
|
|
15637
|
+
process.exitCode = 1;
|
|
15638
|
+
return null;
|
|
15639
|
+
}
|
|
15640
|
+
return {
|
|
15641
|
+
repoRoot,
|
|
15642
|
+
repoName: detectRepoName(repoRoot),
|
|
15643
|
+
current: config2.aiTools ?? [],
|
|
15644
|
+
contextFolder: config2.contextFolder ?? DEFAULT_CONTEXT_FOLDER4,
|
|
15645
|
+
variant: config2.graphOnly ? "graph" : "full"
|
|
15646
|
+
};
|
|
15647
|
+
}
|
|
15648
|
+
async function applyToolSelection(ctx, next) {
|
|
15649
|
+
const added = next.filter((t) => !ctx.current.includes(t));
|
|
15650
|
+
const spinner = ora6("Configuring AI tools...").start();
|
|
15651
|
+
const contextFiles = await scanLocalContextFiles(ctx.repoRoot, ctx.contextFolder);
|
|
15652
|
+
const results = await writeToolConfigsForRepo({
|
|
15653
|
+
repoRoot: ctx.repoRoot,
|
|
15654
|
+
tools: next,
|
|
15655
|
+
repoName: ctx.repoName,
|
|
15656
|
+
contextFolder: ctx.contextFolder,
|
|
15657
|
+
contextFiles,
|
|
15658
|
+
variant: ctx.variant,
|
|
15659
|
+
migrateLegacy: true
|
|
15660
|
+
});
|
|
15661
|
+
await mergeAndSaveConfig({ aiTools: next });
|
|
15662
|
+
spinner.succeed("AI tools configured");
|
|
15663
|
+
console.log(chalk14.dim(results.map((r) => ` ${r}`).join("\n")));
|
|
15664
|
+
try {
|
|
15665
|
+
await ensureListenerRunning();
|
|
15666
|
+
} catch {
|
|
15667
|
+
}
|
|
15668
|
+
const serverRepoName = basename6(ctx.repoRoot);
|
|
15669
|
+
const newMcp = added.filter((t) => MCP_WRITER_TOOLS.has(t));
|
|
15670
|
+
if (newMcp.length > 0) {
|
|
15671
|
+
console.log("");
|
|
15672
|
+
console.log(
|
|
15673
|
+
chalk14.dim(
|
|
15674
|
+
" The listener will wire these into your AI tools' MCP within a few seconds, then:"
|
|
15675
|
+
)
|
|
15676
|
+
);
|
|
15677
|
+
for (const t of newMcp) {
|
|
15678
|
+
const hint = mcpActivationHint(t, serverRepoName);
|
|
15679
|
+
if (hint) console.log(chalk14.dim(` \u2022 ${hint}`));
|
|
15680
|
+
}
|
|
15681
|
+
}
|
|
15682
|
+
}
|
|
15683
|
+
async function pickUnion(ctx) {
|
|
15684
|
+
const { tools: picked, hasOther } = await selectAiTools({ defaults: ctx.current });
|
|
15685
|
+
const removed = ctx.current.filter((t) => !picked.includes(t));
|
|
15686
|
+
if (removed.length > 0) {
|
|
15687
|
+
console.log(
|
|
15688
|
+
chalk14.yellow(`Note: removing tools isn't supported yet \u2014 keeping ${removed.join(", ")}.`)
|
|
15689
|
+
);
|
|
15690
|
+
}
|
|
15691
|
+
return { next: Array.from(/* @__PURE__ */ new Set([...ctx.current, ...picked])), hasOther };
|
|
15692
|
+
}
|
|
15693
|
+
function reportNothingNew(hasOther) {
|
|
15694
|
+
if (hasOther) {
|
|
15695
|
+
console.log(
|
|
15696
|
+
chalk14.dim(
|
|
15697
|
+
"For tools not listed, the instruction files work with any tool that reads the filesystem.\nTo request full support for a new AI tool, email support@repowise.ai"
|
|
15698
|
+
)
|
|
15699
|
+
);
|
|
15700
|
+
} else {
|
|
15701
|
+
console.log(chalk14.dim("No new tools added."));
|
|
15702
|
+
}
|
|
15703
|
+
}
|
|
15704
|
+
async function toolsPick() {
|
|
15705
|
+
const ctx = await resolveRepoOrExit();
|
|
15706
|
+
if (!ctx) return;
|
|
15707
|
+
const { next, hasOther } = await pickUnion(ctx);
|
|
15708
|
+
if (next.length === ctx.current.length) {
|
|
15709
|
+
reportNothingNew(hasOther);
|
|
15710
|
+
return;
|
|
15711
|
+
}
|
|
15712
|
+
await applyToolSelection(ctx, next);
|
|
15713
|
+
}
|
|
15714
|
+
async function toolsAdd(list) {
|
|
15715
|
+
const ctx = await resolveRepoOrExit();
|
|
15716
|
+
if (!ctx) return;
|
|
15717
|
+
let next;
|
|
15718
|
+
let hasOther = false;
|
|
15719
|
+
if (list.length === 0) {
|
|
15720
|
+
({ next, hasOther } = await pickUnion(ctx));
|
|
15721
|
+
} else {
|
|
15722
|
+
const invalid = list.filter((t) => !VALID_TOOLS.includes(t));
|
|
15723
|
+
if (invalid.length > 0) {
|
|
15724
|
+
console.error(chalk14.red(`Unknown tool(s): ${invalid.join(", ")}`));
|
|
15725
|
+
console.error(chalk14.dim(`Valid tools: ${VALID_TOOLS.join(", ")}`));
|
|
15726
|
+
process.exitCode = 1;
|
|
15727
|
+
return;
|
|
15728
|
+
}
|
|
15729
|
+
next = Array.from(/* @__PURE__ */ new Set([...ctx.current, ...list]));
|
|
15730
|
+
}
|
|
15731
|
+
if (next.length === ctx.current.length) {
|
|
15732
|
+
if (list.length === 0) reportNothingNew(hasOther);
|
|
15733
|
+
else console.log(chalk14.dim("Nothing new to add \u2014 those tools are already configured."));
|
|
15734
|
+
return;
|
|
15735
|
+
}
|
|
15736
|
+
await applyToolSelection(ctx, next);
|
|
15737
|
+
}
|
|
15738
|
+
async function toolsList() {
|
|
15739
|
+
const ctx = await resolveRepoOrExit();
|
|
15740
|
+
if (!ctx) return;
|
|
15741
|
+
if (ctx.current.length === 0) {
|
|
15742
|
+
console.log("No AI tools configured. Run `repowise tools` to add some.");
|
|
15743
|
+
return;
|
|
15744
|
+
}
|
|
15745
|
+
console.log(chalk14.bold("Configured AI tools:"));
|
|
15746
|
+
for (const t of ctx.current) {
|
|
15747
|
+
const label = AI_TOOL_CONFIG[t]?.label ?? t;
|
|
15748
|
+
const suffix = MCP_WRITER_TOOLS.has(t) ? "" : chalk14.dim(" (instruction files only)");
|
|
15749
|
+
console.log(` \u2022 ${label}${suffix}`);
|
|
15750
|
+
}
|
|
15751
|
+
}
|
|
15752
|
+
|
|
14724
15753
|
// src/commands/mcp-log.ts
|
|
14725
15754
|
import { createDecipheriv as createDecipheriv2 } from "crypto";
|
|
14726
|
-
import { mkdir as mkdir19, readFile as
|
|
14727
|
-
import { dirname as dirname22, join as
|
|
15755
|
+
import { mkdir as mkdir19, readFile as readFile18, stat as stat7, writeFile as writeFile19 } from "fs/promises";
|
|
15756
|
+
import { dirname as dirname22, join as join56 } from "path";
|
|
14728
15757
|
var FLAG_FILE = "mcp-log.flag";
|
|
14729
15758
|
var LOG_FILE = "mcp-log.jsonl.enc";
|
|
14730
15759
|
var KEY_FILE = "mcp-log.key";
|
|
@@ -14732,20 +15761,20 @@ var ENDPOINT_FILE = "listener.endpoint";
|
|
|
14732
15761
|
var IV_BYTES2 = 12;
|
|
14733
15762
|
var TAG_BYTES2 = 16;
|
|
14734
15763
|
function flagPath() {
|
|
14735
|
-
return
|
|
15764
|
+
return join56(getConfigDir2(), FLAG_FILE);
|
|
14736
15765
|
}
|
|
14737
15766
|
function logPath() {
|
|
14738
|
-
return
|
|
15767
|
+
return join56(getConfigDir2(), LOG_FILE);
|
|
14739
15768
|
}
|
|
14740
15769
|
async function writeFlag(flag) {
|
|
14741
15770
|
const path = flagPath();
|
|
14742
15771
|
await mkdir19(dirname22(path), { recursive: true });
|
|
14743
|
-
await
|
|
15772
|
+
await writeFile19(path, JSON.stringify(flag, null, 2), { encoding: "utf-8", mode: 384 });
|
|
14744
15773
|
}
|
|
14745
15774
|
async function mcpLogOn() {
|
|
14746
15775
|
let flagOnDisk = null;
|
|
14747
15776
|
try {
|
|
14748
|
-
flagOnDisk = JSON.parse(await
|
|
15777
|
+
flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
|
|
14749
15778
|
} catch {
|
|
14750
15779
|
flagOnDisk = null;
|
|
14751
15780
|
}
|
|
@@ -14755,11 +15784,10 @@ async function mcpLogOn() {
|
|
|
14755
15784
|
enabled: true
|
|
14756
15785
|
};
|
|
14757
15786
|
await writeFlag(next);
|
|
14758
|
-
process.stderr.write(
|
|
14759
|
-
`);
|
|
15787
|
+
process.stderr.write("MCP telemetry re-enabled. The anonymous usage heartbeat will resume.\n");
|
|
14760
15788
|
return;
|
|
14761
15789
|
}
|
|
14762
|
-
process.stderr.write("MCP
|
|
15790
|
+
process.stderr.write("MCP telemetry is already enabled.\n");
|
|
14763
15791
|
if (!flagOnDisk || !flagOnDisk.consentSentToServer) {
|
|
14764
15792
|
const synced = await trySendConsentToServer();
|
|
14765
15793
|
if (synced) {
|
|
@@ -14776,14 +15804,14 @@ async function trySendConsentToServer() {
|
|
|
14776
15804
|
let apiUrl = null;
|
|
14777
15805
|
let token = null;
|
|
14778
15806
|
try {
|
|
14779
|
-
const body = await
|
|
15807
|
+
const body = await readFile18(join56(getConfigDir2(), "config.json"), "utf-8");
|
|
14780
15808
|
const parsed = JSON.parse(body);
|
|
14781
15809
|
apiUrl = parsed.repos?.find((r) => Boolean(r.apiUrl))?.apiUrl ?? parsed.defaultApiUrl ?? null;
|
|
14782
15810
|
} catch {
|
|
14783
15811
|
return false;
|
|
14784
15812
|
}
|
|
14785
15813
|
try {
|
|
14786
|
-
const body = await
|
|
15814
|
+
const body = await readFile18(join56(getConfigDir2(), "credentials.json"), "utf-8");
|
|
14787
15815
|
const parsed = JSON.parse(body);
|
|
14788
15816
|
token = parsed.idToken ?? null;
|
|
14789
15817
|
} catch {
|
|
@@ -14806,12 +15834,12 @@ async function trySendConsentToServer() {
|
|
|
14806
15834
|
async function mcpLogOff() {
|
|
14807
15835
|
let flagOnDisk = null;
|
|
14808
15836
|
try {
|
|
14809
|
-
flagOnDisk = JSON.parse(await
|
|
15837
|
+
flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
|
|
14810
15838
|
} catch {
|
|
14811
15839
|
flagOnDisk = null;
|
|
14812
15840
|
}
|
|
14813
15841
|
if (flagOnDisk && flagOnDisk.enabled === false) {
|
|
14814
|
-
process.stderr.write("MCP
|
|
15842
|
+
process.stderr.write("MCP telemetry is already disabled.\n");
|
|
14815
15843
|
process.exitCode = 1;
|
|
14816
15844
|
return;
|
|
14817
15845
|
}
|
|
@@ -14822,38 +15850,38 @@ async function mcpLogOff() {
|
|
|
14822
15850
|
};
|
|
14823
15851
|
await writeFlag(next);
|
|
14824
15852
|
process.stderr.write(
|
|
14825
|
-
"MCP
|
|
15853
|
+
"MCP telemetry disabled. The anonymous usage heartbeat is now off; the server-side consent record is NOT revoked (GDPR Art. 7(1) audit trail).\n"
|
|
14826
15854
|
);
|
|
14827
15855
|
}
|
|
14828
15856
|
async function mcpLogStatus() {
|
|
14829
15857
|
let flagOnDisk = null;
|
|
14830
15858
|
try {
|
|
14831
|
-
flagOnDisk = JSON.parse(await
|
|
15859
|
+
flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
|
|
14832
15860
|
} catch {
|
|
14833
15861
|
flagOnDisk = null;
|
|
14834
15862
|
}
|
|
14835
15863
|
const enabled = !flagOnDisk || flagOnDisk.enabled !== false;
|
|
14836
15864
|
const enabledLabel = flagOnDisk == null ? "yes (default)" : flagOnDisk.enabled ? "yes" : "no";
|
|
14837
15865
|
void enabled;
|
|
14838
|
-
process.stderr.write("MCP
|
|
14839
|
-
process.stderr.write("
|
|
14840
|
-
process.stderr.write(`
|
|
15866
|
+
process.stderr.write("MCP Telemetry Status\n");
|
|
15867
|
+
process.stderr.write("====================\n");
|
|
15868
|
+
process.stderr.write(`Heartbeat enabled: ${enabledLabel}
|
|
14841
15869
|
`);
|
|
14842
|
-
process.stderr.write(`
|
|
14843
|
-
`);
|
|
14844
|
-
process.stderr.write(`Log file: ${logPath()}
|
|
15870
|
+
process.stderr.write(`Opt-in since: ${flagOnDisk?.consentGrantedAt ?? "never"}
|
|
14845
15871
|
`);
|
|
15872
|
+
process.stderr.write("Raw local logging: disabled (heartbeat only)\n");
|
|
14846
15873
|
try {
|
|
14847
15874
|
const s = await stat7(logPath());
|
|
14848
|
-
|
|
14849
|
-
|
|
15875
|
+
if (s.size > 0) {
|
|
15876
|
+
process.stderr.write(
|
|
15877
|
+
`Raw log file: ${logPath()} \u2014 ${formatBytes(s.size)} (escape hatch enabled)
|
|
14850
15878
|
`
|
|
14851
|
-
|
|
15879
|
+
);
|
|
15880
|
+
}
|
|
14852
15881
|
} catch {
|
|
14853
|
-
process.stderr.write("Log size: no file yet\n");
|
|
14854
15882
|
}
|
|
14855
15883
|
try {
|
|
14856
|
-
const endpointBody = await
|
|
15884
|
+
const endpointBody = await readFile18(join56(getConfigDir2(), ENDPOINT_FILE), "utf-8");
|
|
14857
15885
|
const match = /endpoint=([^\n]+)/.exec(endpointBody);
|
|
14858
15886
|
process.stderr.write(`MCP endpoint: ${match?.[1] ?? "(malformed endpoint file)"}
|
|
14859
15887
|
`);
|
|
@@ -14869,13 +15897,13 @@ function formatBytes(n) {
|
|
|
14869
15897
|
async function mcpLogViewingFlags(flags = {}) {
|
|
14870
15898
|
let flagOnDisk = null;
|
|
14871
15899
|
try {
|
|
14872
|
-
flagOnDisk = JSON.parse(await
|
|
15900
|
+
flagOnDisk = JSON.parse(await readFile18(flagPath(), "utf-8"));
|
|
14873
15901
|
} catch {
|
|
14874
15902
|
flagOnDisk = null;
|
|
14875
15903
|
}
|
|
14876
15904
|
if (flagOnDisk && flagOnDisk.enabled === false) {
|
|
14877
15905
|
process.stderr.write(
|
|
14878
|
-
`MCP
|
|
15906
|
+
`MCP telemetry is disabled. Run \`repowise mcp-log on\` to re-enable.
|
|
14879
15907
|
`
|
|
14880
15908
|
);
|
|
14881
15909
|
return;
|
|
@@ -14884,7 +15912,7 @@ async function mcpLogViewingFlags(flags = {}) {
|
|
|
14884
15912
|
const key = await readKey();
|
|
14885
15913
|
if (!key) {
|
|
14886
15914
|
process.stderr.write(
|
|
14887
|
-
`No encryption key at ${
|
|
15915
|
+
`No encryption key at ${join56(getConfigDir2(), KEY_FILE)} \u2014 listener may not have started yet.
|
|
14888
15916
|
`
|
|
14889
15917
|
);
|
|
14890
15918
|
return;
|
|
@@ -14912,7 +15940,7 @@ async function mcpLogViewingFlags(flags = {}) {
|
|
|
14912
15940
|
}
|
|
14913
15941
|
};
|
|
14914
15942
|
try {
|
|
14915
|
-
const raw = await
|
|
15943
|
+
const raw = await readFile18(path, "utf-8");
|
|
14916
15944
|
const decoded = [];
|
|
14917
15945
|
for (const line of raw.split("\n")) {
|
|
14918
15946
|
const r = filter(line);
|
|
@@ -14936,7 +15964,7 @@ async function mcpLogViewingFlags(flags = {}) {
|
|
|
14936
15964
|
try {
|
|
14937
15965
|
const s = await stat7(path);
|
|
14938
15966
|
if (s.size > lastSize) {
|
|
14939
|
-
const fresh = await
|
|
15967
|
+
const fresh = await readFile18(path, "utf-8");
|
|
14940
15968
|
const tail = fresh.slice(lastSize);
|
|
14941
15969
|
for (const line of tail.split("\n")) {
|
|
14942
15970
|
const r = filter(line);
|
|
@@ -14960,7 +15988,7 @@ async function mcpLogViewingFlags(flags = {}) {
|
|
|
14960
15988
|
}
|
|
14961
15989
|
async function readKey() {
|
|
14962
15990
|
try {
|
|
14963
|
-
const body = await
|
|
15991
|
+
const body = await readFile18(join56(getConfigDir2(), KEY_FILE), "utf-8");
|
|
14964
15992
|
const parsed = Buffer.from(body.trim(), "base64");
|
|
14965
15993
|
if (parsed.length !== 32) return null;
|
|
14966
15994
|
return parsed;
|
|
@@ -15000,11 +16028,11 @@ async function mcpLog(subcommand, flags = {}) {
|
|
|
15000
16028
|
}
|
|
15001
16029
|
|
|
15002
16030
|
// src/commands/query/_shared.ts
|
|
15003
|
-
import
|
|
16031
|
+
import chalk15 from "chalk";
|
|
15004
16032
|
|
|
15005
16033
|
// src/lib/graph-loader.ts
|
|
15006
|
-
import { promises as
|
|
15007
|
-
import { join as
|
|
16034
|
+
import { promises as fs25 } from "fs";
|
|
16035
|
+
import { join as join57, resolve as resolve2 } from "path";
|
|
15008
16036
|
import { gunzipSync } from "zlib";
|
|
15009
16037
|
var RELATIVE_GRAPH_PATH = "repowise-context/.meta/dependency-graph.json";
|
|
15010
16038
|
var GZIPPED_GRAPH_PATH = "repowise-context/.meta/dependency-graph.json.gz";
|
|
@@ -15021,8 +16049,8 @@ var GraphNotFoundError = class extends Error {
|
|
|
15021
16049
|
var cache = /* @__PURE__ */ new Map();
|
|
15022
16050
|
async function loadGraph(repoRoot = process.cwd()) {
|
|
15023
16051
|
const root = resolve2(repoRoot);
|
|
15024
|
-
const gzPath =
|
|
15025
|
-
const plainPath =
|
|
16052
|
+
const gzPath = join57(root, GZIPPED_GRAPH_PATH);
|
|
16053
|
+
const plainPath = join57(root, RELATIVE_GRAPH_PATH);
|
|
15026
16054
|
const cached = cache.get(root);
|
|
15027
16055
|
if (cached) {
|
|
15028
16056
|
return { graph: cached, path: plainPath, bytes: 0, parseMs: 0, fromCache: true };
|
|
@@ -15030,14 +16058,14 @@ async function loadGraph(repoRoot = process.cwd()) {
|
|
|
15030
16058
|
let graphPath = null;
|
|
15031
16059
|
let raw = null;
|
|
15032
16060
|
try {
|
|
15033
|
-
raw = await
|
|
16061
|
+
raw = await fs25.readFile(gzPath);
|
|
15034
16062
|
graphPath = gzPath;
|
|
15035
16063
|
} catch (err) {
|
|
15036
16064
|
if (err.code !== "ENOENT") throw err;
|
|
15037
16065
|
}
|
|
15038
16066
|
if (!raw) {
|
|
15039
16067
|
try {
|
|
15040
|
-
raw = await
|
|
16068
|
+
raw = await fs25.readFile(plainPath);
|
|
15041
16069
|
graphPath = plainPath;
|
|
15042
16070
|
} catch (err) {
|
|
15043
16071
|
if (err.code === "ENOENT") {
|
|
@@ -15240,7 +16268,7 @@ async function loadService() {
|
|
|
15240
16268
|
return createGraphQueryService(graph);
|
|
15241
16269
|
} catch (err) {
|
|
15242
16270
|
if (err instanceof GraphNotFoundError) {
|
|
15243
|
-
process.stderr.write(
|
|
16271
|
+
process.stderr.write(chalk15.red(`\u2717 ${err.message}
|
|
15244
16272
|
`));
|
|
15245
16273
|
process.exit(err.exitCode);
|
|
15246
16274
|
}
|
|
@@ -15436,14 +16464,14 @@ function registerQueryCommand(program2) {
|
|
|
15436
16464
|
}
|
|
15437
16465
|
|
|
15438
16466
|
// src/commands/uninstall.ts
|
|
15439
|
-
import { promises as
|
|
15440
|
-
import { homedir as
|
|
15441
|
-
import { join as
|
|
15442
|
-
import
|
|
16467
|
+
import { promises as fs29 } from "fs";
|
|
16468
|
+
import { homedir as homedir12 } from "os";
|
|
16469
|
+
import { join as join61 } from "path";
|
|
16470
|
+
import chalk16 from "chalk";
|
|
15443
16471
|
|
|
15444
16472
|
// src/lib/cleanup/marker-blocks.ts
|
|
15445
|
-
import { promises as
|
|
15446
|
-
import { join as
|
|
16473
|
+
import { promises as fs26 } from "fs";
|
|
16474
|
+
import { join as join58 } from "path";
|
|
15447
16475
|
var MARKER_START = "<!-- repowise-start -->";
|
|
15448
16476
|
var MARKER_END = "<!-- repowise-end -->";
|
|
15449
16477
|
var CONTEXT_FILES = [
|
|
@@ -15459,7 +16487,7 @@ var CONTEXT_FILES = [
|
|
|
15459
16487
|
async function stripMarkerBlock(filePath) {
|
|
15460
16488
|
let raw;
|
|
15461
16489
|
try {
|
|
15462
|
-
raw = await
|
|
16490
|
+
raw = await fs26.readFile(filePath, "utf-8");
|
|
15463
16491
|
} catch (err) {
|
|
15464
16492
|
if (err.code === "ENOENT")
|
|
15465
16493
|
return { path: filePath, status: "missing" };
|
|
@@ -15474,16 +16502,16 @@ async function stripMarkerBlock(filePath) {
|
|
|
15474
16502
|
const after = raw.slice(endIdx + MARKER_END.length).replace(/^\n+/, "");
|
|
15475
16503
|
const stripped = (before + (before && after ? "\n\n" : "") + after).trim();
|
|
15476
16504
|
if (stripped.length === 0) {
|
|
15477
|
-
await
|
|
16505
|
+
await fs26.unlink(filePath);
|
|
15478
16506
|
return { path: filePath, status: "deleted" };
|
|
15479
16507
|
}
|
|
15480
|
-
await
|
|
16508
|
+
await fs26.writeFile(filePath, stripped + "\n", "utf-8");
|
|
15481
16509
|
return { path: filePath, status: "stripped" };
|
|
15482
16510
|
}
|
|
15483
16511
|
async function stripAllMarkerBlocks(repoRoot) {
|
|
15484
16512
|
const out = [];
|
|
15485
16513
|
for (const relative of CONTEXT_FILES) {
|
|
15486
|
-
const full =
|
|
16514
|
+
const full = join58(repoRoot, relative);
|
|
15487
16515
|
const result = await stripMarkerBlock(full).catch((err) => ({
|
|
15488
16516
|
path: full,
|
|
15489
16517
|
status: "untouched",
|
|
@@ -15495,25 +16523,25 @@ async function stripAllMarkerBlocks(repoRoot) {
|
|
|
15495
16523
|
}
|
|
15496
16524
|
|
|
15497
16525
|
// src/lib/cleanup/mcp-configs.ts
|
|
15498
|
-
import { promises as
|
|
15499
|
-
import { join as
|
|
16526
|
+
import { promises as fs27 } from "fs";
|
|
16527
|
+
import { join as join59 } from "path";
|
|
15500
16528
|
function mcpConfigPaths(repoRoot, home) {
|
|
15501
16529
|
return [
|
|
15502
|
-
|
|
15503
|
-
|
|
15504
|
-
|
|
15505
|
-
|
|
15506
|
-
|
|
15507
|
-
|
|
15508
|
-
|
|
15509
|
-
|
|
15510
|
-
|
|
16530
|
+
join59(repoRoot, ".mcp.json"),
|
|
16531
|
+
join59(repoRoot, ".cursor", "mcp.json"),
|
|
16532
|
+
join59(repoRoot, ".vscode", "mcp.json"),
|
|
16533
|
+
join59(repoRoot, ".roo", "mcp.json"),
|
|
16534
|
+
join59(home, ".cline", "mcp.json"),
|
|
16535
|
+
join59(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
16536
|
+
join59(home, ".gemini", "settings.json"),
|
|
16537
|
+
join59(home, ".codex", "mcp.json"),
|
|
16538
|
+
join59(home, ".roo", "mcp.json")
|
|
15511
16539
|
];
|
|
15512
16540
|
}
|
|
15513
16541
|
async function removeRepowiseFromConfig(path, serverName) {
|
|
15514
16542
|
let raw;
|
|
15515
16543
|
try {
|
|
15516
|
-
raw = await
|
|
16544
|
+
raw = await fs27.readFile(path, "utf-8");
|
|
15517
16545
|
} catch (err) {
|
|
15518
16546
|
if (err.code === "ENOENT") return { path, status: "not-found" };
|
|
15519
16547
|
return { path, status: "error", error: err.message };
|
|
@@ -15533,7 +16561,7 @@ async function removeRepowiseFromConfig(path, serverName) {
|
|
|
15533
16561
|
} else {
|
|
15534
16562
|
next.mcpServers = servers;
|
|
15535
16563
|
}
|
|
15536
|
-
await
|
|
16564
|
+
await fs27.writeFile(path, JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
15537
16565
|
return { path, status: "removed" };
|
|
15538
16566
|
}
|
|
15539
16567
|
async function removeAllMcpEntries(repoRoot, home, repoId) {
|
|
@@ -15546,17 +16574,17 @@ async function removeAllMcpEntries(repoRoot, home, repoId) {
|
|
|
15546
16574
|
}
|
|
15547
16575
|
|
|
15548
16576
|
// src/lib/cleanup/local-state.ts
|
|
15549
|
-
import { promises as
|
|
15550
|
-
import { homedir as
|
|
15551
|
-
import { join as
|
|
16577
|
+
import { promises as fs28 } from "fs";
|
|
16578
|
+
import { homedir as homedir11 } from "os";
|
|
16579
|
+
import { join as join60, resolve as resolve3 } from "path";
|
|
15552
16580
|
async function clearLocalState(homeOverride) {
|
|
15553
|
-
const home = homeOverride ??
|
|
15554
|
-
const target = resolve3(
|
|
16581
|
+
const home = homeOverride ?? homedir11();
|
|
16582
|
+
const target = resolve3(join60(home, ".repowise"));
|
|
15555
16583
|
if (target === resolve3(home) || !target.startsWith(resolve3(home))) {
|
|
15556
16584
|
return { path: target, status: "error", error: "refused: not under home" };
|
|
15557
16585
|
}
|
|
15558
16586
|
try {
|
|
15559
|
-
await
|
|
16587
|
+
await fs28.rm(target, { recursive: true, force: false });
|
|
15560
16588
|
return { path: target, status: "removed" };
|
|
15561
16589
|
} catch (err) {
|
|
15562
16590
|
if (err.code === "ENOENT")
|
|
@@ -15586,7 +16614,7 @@ async function stopAndUninstallService(uninstaller) {
|
|
|
15586
16614
|
// src/commands/uninstall.ts
|
|
15587
16615
|
async function uninstall2(opts = {}) {
|
|
15588
16616
|
const tier = opts.tier ?? "uninstall";
|
|
15589
|
-
const home = opts.home ??
|
|
16617
|
+
const home = opts.home ?? homedir12();
|
|
15590
16618
|
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
15591
16619
|
const loadRepoIds = opts.loadRepoIds ?? defaultLoadRepoIds;
|
|
15592
16620
|
const report = { tier, removed: [], preserved: [], skipped: [] };
|
|
@@ -15595,7 +16623,7 @@ async function uninstall2(opts = {}) {
|
|
|
15595
16623
|
else if (svc.error) report.skipped.push({ path: "listener service", reason: svc.error });
|
|
15596
16624
|
if (tier === "stop") return report;
|
|
15597
16625
|
try {
|
|
15598
|
-
await
|
|
16626
|
+
await fs29.unlink(join61(home, ".repowise", "credentials.json"));
|
|
15599
16627
|
report.removed.push("credentials");
|
|
15600
16628
|
} catch (err) {
|
|
15601
16629
|
if (err.code !== "ENOENT") {
|
|
@@ -15622,7 +16650,7 @@ async function uninstall2(opts = {}) {
|
|
|
15622
16650
|
const allPaths = mcpConfigPaths(repoRoot, home);
|
|
15623
16651
|
for (const p of allPaths) {
|
|
15624
16652
|
try {
|
|
15625
|
-
await
|
|
16653
|
+
await fs29.access(p);
|
|
15626
16654
|
} catch {
|
|
15627
16655
|
}
|
|
15628
16656
|
}
|
|
@@ -15634,7 +16662,7 @@ async function uninstall2(opts = {}) {
|
|
|
15634
16662
|
}
|
|
15635
16663
|
async function defaultLoadRepoIds(home) {
|
|
15636
16664
|
try {
|
|
15637
|
-
const raw = await
|
|
16665
|
+
const raw = await fs29.readFile(join61(home, ".repowise", "config.json"), "utf-8");
|
|
15638
16666
|
const parsed = JSON.parse(raw);
|
|
15639
16667
|
return (parsed.repos ?? []).map((r) => r.repoId);
|
|
15640
16668
|
} catch {
|
|
@@ -15651,29 +16679,29 @@ async function uninstallCommand(opts = {}) {
|
|
|
15651
16679
|
default: false
|
|
15652
16680
|
});
|
|
15653
16681
|
if (!proceed) {
|
|
15654
|
-
process.stderr.write(
|
|
16682
|
+
process.stderr.write(chalk16.yellow("Uninstall cancelled.\n"));
|
|
15655
16683
|
process.exitCode = 1;
|
|
15656
16684
|
return;
|
|
15657
16685
|
}
|
|
15658
16686
|
}
|
|
15659
16687
|
}
|
|
15660
|
-
process.stderr.write(
|
|
16688
|
+
process.stderr.write(chalk16.bold(`RepoWise ${tier}
|
|
15661
16689
|
`));
|
|
15662
16690
|
const report = await uninstall2({ ...opts, tier });
|
|
15663
16691
|
for (const p of report.removed) {
|
|
15664
|
-
process.stderr.write(
|
|
16692
|
+
process.stderr.write(chalk16.green(` \u2713 removed: ${p}
|
|
15665
16693
|
`));
|
|
15666
16694
|
}
|
|
15667
16695
|
for (const p of report.preserved) {
|
|
15668
|
-
process.stderr.write(
|
|
16696
|
+
process.stderr.write(chalk16.gray(` \xB7 preserved: ${p}
|
|
15669
16697
|
`));
|
|
15670
16698
|
}
|
|
15671
16699
|
for (const s of report.skipped) {
|
|
15672
|
-
process.stderr.write(
|
|
16700
|
+
process.stderr.write(chalk16.yellow(` ! skipped: ${s.path} (${s.reason})
|
|
15673
16701
|
`));
|
|
15674
16702
|
}
|
|
15675
16703
|
process.stderr.write(
|
|
15676
|
-
|
|
16704
|
+
chalk16.bold(`
|
|
15677
16705
|
Done \u2014 ${report.removed.length} removed, ${report.skipped.length} skipped.
|
|
15678
16706
|
`)
|
|
15679
16707
|
);
|
|
@@ -15681,12 +16709,14 @@ Done \u2014 ${report.removed.length} removed, ${report.skipped.length} skipped.
|
|
|
15681
16709
|
|
|
15682
16710
|
// src/commands/mcp-shim.ts
|
|
15683
16711
|
init_config_dir();
|
|
15684
|
-
import { promises as
|
|
16712
|
+
import { promises as fs30 } from "fs";
|
|
15685
16713
|
import { createInterface as createInterface2 } from "readline";
|
|
15686
|
-
import { join as
|
|
16714
|
+
import { join as join62 } from "path";
|
|
15687
16715
|
var DEFAULT_MAX = 200 * 1024;
|
|
16716
|
+
var SHIM_REQUEST_TIMEOUT_MS = 45e3;
|
|
15688
16717
|
async function mcpShim(opts) {
|
|
15689
|
-
const endpointPath = opts.endpointFile ??
|
|
16718
|
+
const endpointPath = opts.endpointFile ?? join62(getConfigDir(), "listener.endpoint");
|
|
16719
|
+
const credsPath = opts.credentialsFile ?? join62(getConfigDir(), "credentials.json");
|
|
15690
16720
|
const stdin = opts.stdin ?? process.stdin;
|
|
15691
16721
|
const stdout = opts.stdout ?? process.stdout;
|
|
15692
16722
|
const stderr = opts.stderr ?? process.stderr;
|
|
@@ -15721,12 +16751,16 @@ async function mcpShim(opts) {
|
|
|
15721
16751
|
}
|
|
15722
16752
|
const info = await readEndpoint(endpointPath);
|
|
15723
16753
|
if (!info) {
|
|
16754
|
+
const authState = await readAuthState(credsPath);
|
|
16755
|
+
const message = authState === "logged-out" ? (
|
|
16756
|
+
// Agent-directed: the calling LLM can run this itself for a smoother UX
|
|
16757
|
+
// (it auto-opens the browser; the user still completes sign-in). The
|
|
16758
|
+
// command is a fixed, argument-free string (nothing injectable).
|
|
16759
|
+
"RepoWise isn't authenticated (this is rare \u2014 the session lasts about a year). To fix it, run the shell command `repowise login` on the user's behalf \u2014 it opens a browser for them to sign in and blocks until they finish. Run it once, wait for it to complete, then retry this request. Do not repeat the command."
|
|
16760
|
+
) : "listener endpoint unavailable \u2014 is the listener running?";
|
|
15724
16761
|
writeJson(stdout, {
|
|
15725
16762
|
jsonrpc: "2.0",
|
|
15726
|
-
error: {
|
|
15727
|
-
code: -32e3,
|
|
15728
|
-
message: "listener endpoint unavailable \u2014 is the listener running?"
|
|
15729
|
-
}
|
|
16763
|
+
error: { code: -32e3, message }
|
|
15730
16764
|
});
|
|
15731
16765
|
continue;
|
|
15732
16766
|
}
|
|
@@ -15746,12 +16780,19 @@ async function mcpShim(opts) {
|
|
|
15746
16780
|
} catch (err) {
|
|
15747
16781
|
stderr.write(`mcp-shim: ${err.message}
|
|
15748
16782
|
`);
|
|
16783
|
+
const name = err.name;
|
|
16784
|
+
const detail = err.message;
|
|
16785
|
+
const isTimeout = name === "TimeoutError" || name === "AbortError" || /timeout|aborted|abort/i.test(detail);
|
|
15749
16786
|
writeJson(stdout, {
|
|
15750
16787
|
jsonrpc: "2.0",
|
|
15751
|
-
error: {
|
|
16788
|
+
error: isTimeout ? {
|
|
16789
|
+
code: -32001,
|
|
16790
|
+
message: "RepoWise listener did not respond in time \u2014 it may be stuck. Restart it with `repowise restart`, then retry.",
|
|
16791
|
+
data: { detail }
|
|
16792
|
+
} : {
|
|
15752
16793
|
code: -32001,
|
|
15753
16794
|
message: "listener request failed",
|
|
15754
|
-
data: { detail
|
|
16795
|
+
data: { detail }
|
|
15755
16796
|
}
|
|
15756
16797
|
});
|
|
15757
16798
|
}
|
|
@@ -15759,7 +16800,7 @@ async function mcpShim(opts) {
|
|
|
15759
16800
|
}
|
|
15760
16801
|
async function readEndpoint(path) {
|
|
15761
16802
|
try {
|
|
15762
|
-
const raw = (await
|
|
16803
|
+
const raw = (await fs30.readFile(path, "utf-8")).trim();
|
|
15763
16804
|
if (!raw) return null;
|
|
15764
16805
|
if (raw.startsWith("http://") || raw.startsWith("https://")) {
|
|
15765
16806
|
return { endpoint: raw.split("\n")[0].trim(), secret: null };
|
|
@@ -15777,6 +16818,22 @@ async function readEndpoint(path) {
|
|
|
15777
16818
|
throw err;
|
|
15778
16819
|
}
|
|
15779
16820
|
}
|
|
16821
|
+
async function readAuthState(credsPath) {
|
|
16822
|
+
let raw;
|
|
16823
|
+
try {
|
|
16824
|
+
raw = await fs30.readFile(credsPath, "utf-8");
|
|
16825
|
+
} catch (err) {
|
|
16826
|
+
if (err.code === "ENOENT") return "logged-out";
|
|
16827
|
+
return "unknown";
|
|
16828
|
+
}
|
|
16829
|
+
let creds;
|
|
16830
|
+
try {
|
|
16831
|
+
creds = JSON.parse(raw);
|
|
16832
|
+
} catch {
|
|
16833
|
+
return "unknown";
|
|
16834
|
+
}
|
|
16835
|
+
return isCredsHardExpired(creds) ? "logged-out" : "authenticated";
|
|
16836
|
+
}
|
|
15780
16837
|
function writeJson(stream, value) {
|
|
15781
16838
|
stream.write(`${JSON.stringify(value)}
|
|
15782
16839
|
`);
|
|
@@ -15872,7 +16929,8 @@ async function defaultPostJson(url, body, headers = { "content-type": "applicati
|
|
|
15872
16929
|
const res = await fetch(url, {
|
|
15873
16930
|
method: "POST",
|
|
15874
16931
|
headers,
|
|
15875
|
-
body: JSON.stringify(body)
|
|
16932
|
+
body: JSON.stringify(body),
|
|
16933
|
+
signal: AbortSignal.timeout(SHIM_REQUEST_TIMEOUT_MS)
|
|
15876
16934
|
});
|
|
15877
16935
|
if (!res.ok) {
|
|
15878
16936
|
let detail = "";
|
|
@@ -15886,7 +16944,11 @@ async function defaultPostJson(url, body, headers = { "content-type": "applicati
|
|
|
15886
16944
|
return await res.json();
|
|
15887
16945
|
}
|
|
15888
16946
|
async function defaultGetJson(url, headers = {}) {
|
|
15889
|
-
const res = await fetch(url, {
|
|
16947
|
+
const res = await fetch(url, {
|
|
16948
|
+
method: "GET",
|
|
16949
|
+
headers,
|
|
16950
|
+
signal: AbortSignal.timeout(SHIM_REQUEST_TIMEOUT_MS)
|
|
16951
|
+
});
|
|
15890
16952
|
if (!res.ok) {
|
|
15891
16953
|
let detail = "";
|
|
15892
16954
|
try {
|
|
@@ -16001,7 +17063,7 @@ init_coursier_installer();
|
|
|
16001
17063
|
init_toolchain_installer();
|
|
16002
17064
|
import { spawn as spawn12 } from "child_process";
|
|
16003
17065
|
import { resolve as resolve4 } from "path";
|
|
16004
|
-
import
|
|
17066
|
+
import chalk17 from "chalk";
|
|
16005
17067
|
async function isOnPath(command) {
|
|
16006
17068
|
if (/[^\w./+-]/.test(command)) return false;
|
|
16007
17069
|
const isWin = process.platform === "win32";
|
|
@@ -16019,14 +17081,14 @@ async function isOnPath(command) {
|
|
|
16019
17081
|
async function lspDoctor() {
|
|
16020
17082
|
const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
|
|
16021
17083
|
if (noColor) {
|
|
16022
|
-
|
|
17084
|
+
chalk17.level = 0;
|
|
16023
17085
|
}
|
|
16024
|
-
const okGlyph = noColor ? "[OK]" :
|
|
16025
|
-
const missingGlyph = noColor ? "[MISSING]" :
|
|
16026
|
-
console.log(
|
|
16027
|
-
console.log(
|
|
17086
|
+
const okGlyph = noColor ? "[OK]" : chalk17.green("\u2713");
|
|
17087
|
+
const missingGlyph = noColor ? "[MISSING]" : chalk17.yellow("\u2717");
|
|
17088
|
+
console.log(chalk17.bold("RepoWise LSP doctor"));
|
|
17089
|
+
console.log(chalk17.dim("Probing PATH for language servers used by the listener."));
|
|
16028
17090
|
console.log(
|
|
16029
|
-
|
|
17091
|
+
chalk17.dim(
|
|
16030
17092
|
"Type-aware resolution covers 9 languages: Go, Python, Rust, Java, Ruby, Dart, Kotlin, PHP, Scala (Phase 7L + 7L++ + 7L+)."
|
|
16031
17093
|
)
|
|
16032
17094
|
);
|
|
@@ -16058,21 +17120,21 @@ async function lspDoctor() {
|
|
|
16058
17120
|
const missing = reports.filter((r) => r.status === "missing").length;
|
|
16059
17121
|
for (const r of reports) {
|
|
16060
17122
|
const head = r.status === "found" ? okGlyph : missingGlyph;
|
|
16061
|
-
const cmd = r.command ?
|
|
17123
|
+
const cmd = r.command ? chalk17.cyan(r.command) : chalk17.dim(r.candidates.join(" / "));
|
|
16062
17124
|
const effectiveConfigs = getEffectiveConfigList(r.language, process.env, lspOverrides);
|
|
16063
17125
|
const method = describeInstallMethod(effectiveConfigs);
|
|
16064
|
-
console.log(` ${head} ${r.language.padEnd(12)} ${cmd} ${
|
|
17126
|
+
console.log(` ${head} ${r.language.padEnd(12)} ${cmd} ${chalk17.dim(`[${method}]`)}`);
|
|
16065
17127
|
if (r.status === "missing" && r.hint) {
|
|
16066
|
-
console.log(` ${
|
|
17128
|
+
console.log(` ${chalk17.dim("install:")} ${r.hint}`);
|
|
16067
17129
|
}
|
|
16068
17130
|
}
|
|
16069
17131
|
console.log();
|
|
16070
17132
|
console.log(
|
|
16071
|
-
`${
|
|
17133
|
+
`${chalk17.green(found.toString())} found, ${chalk17.yellow(missing.toString())} missing of ${reports.length.toString()} languages.`
|
|
16072
17134
|
);
|
|
16073
17135
|
if (missing > 0) {
|
|
16074
17136
|
console.log(
|
|
16075
|
-
|
|
17137
|
+
chalk17.dim(
|
|
16076
17138
|
"Missing servers degrade LSP-backed MCP tools to AST-only fallback. Run `repowise lsp install` to auto-install."
|
|
16077
17139
|
)
|
|
16078
17140
|
);
|
|
@@ -16089,15 +17151,15 @@ function describeInstallMethod(configs) {
|
|
|
16089
17151
|
}
|
|
16090
17152
|
async function lspInstall(language) {
|
|
16091
17153
|
const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
|
|
16092
|
-
if (noColor)
|
|
16093
|
-
console.log(
|
|
17154
|
+
if (noColor) chalk17.level = 0;
|
|
17155
|
+
console.log(chalk17.bold("RepoWise LSP install"));
|
|
16094
17156
|
const cliConfig = await getConfig();
|
|
16095
17157
|
const lspOverrides = cliConfig.lspOverrides;
|
|
16096
17158
|
const targets = language ? [language] : Object.keys(LSP_REGISTRY);
|
|
16097
17159
|
for (const lang of targets) {
|
|
16098
17160
|
const configs = getEffectiveConfigList(lang, process.env, lspOverrides);
|
|
16099
17161
|
if (!configs || configs.length === 0) {
|
|
16100
|
-
console.log(` ${
|
|
17162
|
+
console.log(` ${chalk17.red("\u2717")} ${lang} ${chalk17.dim("(unknown language)")}`);
|
|
16101
17163
|
continue;
|
|
16102
17164
|
}
|
|
16103
17165
|
const result = await installOneLanguage(configs);
|
|
@@ -16165,11 +17227,11 @@ async function installOneLanguage(configs) {
|
|
|
16165
17227
|
};
|
|
16166
17228
|
}
|
|
16167
17229
|
function formatInstallLine(lang, outcome) {
|
|
16168
|
-
const glyph = outcome.ok ?
|
|
16169
|
-
const tag =
|
|
17230
|
+
const glyph = outcome.ok ? chalk17.green("\u2713") : chalk17.yellow("\u2717");
|
|
17231
|
+
const tag = chalk17.dim(`[${outcome.method}]`);
|
|
16170
17232
|
console.log(` ${glyph} ${lang.padEnd(12)} ${tag} ${outcome.detail}`);
|
|
16171
17233
|
if (!outcome.ok && outcome.hint) {
|
|
16172
|
-
console.log(` ${
|
|
17234
|
+
console.log(` ${chalk17.dim("install:")} ${outcome.hint}`);
|
|
16173
17235
|
}
|
|
16174
17236
|
}
|
|
16175
17237
|
var MAX_KEEP_WARM_MINUTES = 240;
|
|
@@ -16207,42 +17269,42 @@ async function autoDetectLanguages(cwd) {
|
|
|
16207
17269
|
async function lspWarm(opts = {}) {
|
|
16208
17270
|
const cwd = resolve4(opts.cwd ?? process.cwd());
|
|
16209
17271
|
const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
|
|
16210
|
-
if (noColor)
|
|
17272
|
+
if (noColor) chalk17.level = 0;
|
|
16211
17273
|
let languages;
|
|
16212
17274
|
if (opts.lang && opts.lang.length > 0) {
|
|
16213
17275
|
const valid = Object.keys(LSP_REGISTRY);
|
|
16214
17276
|
languages = opts.lang.filter((l) => valid.includes(l));
|
|
16215
17277
|
const invalid = opts.lang.filter((l) => !valid.includes(l));
|
|
16216
17278
|
if (invalid.length > 0) {
|
|
16217
|
-
console.log(
|
|
17279
|
+
console.log(chalk17.yellow(`[lsp warm] unknown languages skipped: ${invalid.join(", ")}`));
|
|
16218
17280
|
}
|
|
16219
17281
|
} else {
|
|
16220
17282
|
languages = await autoDetectLanguages(cwd);
|
|
16221
17283
|
if (languages.length === 0) {
|
|
16222
17284
|
console.log(
|
|
16223
|
-
|
|
17285
|
+
chalk17.yellow(
|
|
16224
17286
|
"[lsp warm] no recognised source files in this repo \u2014 pass --lang <id> to force a server."
|
|
16225
17287
|
)
|
|
16226
17288
|
);
|
|
16227
17289
|
return;
|
|
16228
17290
|
}
|
|
16229
|
-
console.log(
|
|
17291
|
+
console.log(chalk17.dim(`[lsp warm] auto-detected languages: ${languages.join(", ")}`));
|
|
16230
17292
|
}
|
|
16231
17293
|
let keepWarmMinutes = 0;
|
|
16232
17294
|
if (typeof opts.keepWarm === "number" && opts.keepWarm > 0) {
|
|
16233
17295
|
keepWarmMinutes = Math.min(opts.keepWarm, MAX_KEEP_WARM_MINUTES);
|
|
16234
17296
|
if (opts.keepWarm > MAX_KEEP_WARM_MINUTES) {
|
|
16235
17297
|
console.log(
|
|
16236
|
-
|
|
17298
|
+
chalk17.yellow(
|
|
16237
17299
|
`[lsp warm] --keep-warm clipped to ${MAX_KEEP_WARM_MINUTES.toString()} minutes (hard cap per Phase 7L plan).`
|
|
16238
17300
|
)
|
|
16239
17301
|
);
|
|
16240
17302
|
}
|
|
16241
17303
|
}
|
|
16242
|
-
console.log(
|
|
16243
|
-
console.log(
|
|
17304
|
+
console.log(chalk17.bold("RepoWise LSP warm \u2014 preview"));
|
|
17305
|
+
console.log(chalk17.dim(`Workspace: ${cwd}`));
|
|
16244
17306
|
console.log(
|
|
16245
|
-
|
|
17307
|
+
chalk17.dim(
|
|
16246
17308
|
"LSP sessions live inside the listener (lazy-by-default, idle-evict after 5 min). The CLI cannot currently trigger a remote spawn \u2014 this preview shows which servers would be used."
|
|
16247
17309
|
)
|
|
16248
17310
|
);
|
|
@@ -16258,23 +17320,23 @@ async function lspWarm(opts = {}) {
|
|
|
16258
17320
|
if (onPath) {
|
|
16259
17321
|
foundCount += 1;
|
|
16260
17322
|
console.log(
|
|
16261
|
-
|
|
17323
|
+
chalk17.green(` \u2713 ${lang.padEnd(12)} ${config2.displayName} (${config2.command}) on PATH`)
|
|
16262
17324
|
);
|
|
16263
17325
|
} else {
|
|
16264
17326
|
missingCount += 1;
|
|
16265
|
-
console.log(
|
|
17327
|
+
console.log(chalk17.yellow(` \u2717 ${lang.padEnd(12)} ${config2.displayName} not on PATH`));
|
|
16266
17328
|
if (config2.installHint) {
|
|
16267
|
-
console.log(
|
|
17329
|
+
console.log(chalk17.dim(` install: ${config2.installHint}`));
|
|
16268
17330
|
}
|
|
16269
17331
|
}
|
|
16270
17332
|
}
|
|
16271
17333
|
console.log();
|
|
16272
17334
|
console.log(
|
|
16273
|
-
`${
|
|
17335
|
+
`${chalk17.green(foundCount.toString())} ready, ${chalk17.yellow(missingCount.toString())} missing.`
|
|
16274
17336
|
);
|
|
16275
17337
|
if (keepWarmMinutes > 0) {
|
|
16276
17338
|
console.log(
|
|
16277
|
-
|
|
17339
|
+
chalk17.dim(
|
|
16278
17340
|
`Holding for ${keepWarmMinutes.toString()} minute(s) so the listener has time to spawn on its next sync.completed; Ctrl-C to exit early.`
|
|
16279
17341
|
)
|
|
16280
17342
|
);
|
|
@@ -16291,20 +17353,20 @@ async function lspWarm(opts = {}) {
|
|
|
16291
17353
|
}
|
|
16292
17354
|
function lspStatus() {
|
|
16293
17355
|
const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
|
|
16294
|
-
if (noColor)
|
|
16295
|
-
console.log(
|
|
17356
|
+
if (noColor) chalk17.level = 0;
|
|
17357
|
+
console.log(chalk17.bold("RepoWise LSP status"));
|
|
16296
17358
|
console.log(
|
|
16297
|
-
|
|
17359
|
+
chalk17.dim(
|
|
16298
17360
|
"Active LSP sessions live inside the listener (not the CLI). Sessions are lazy-by-default: spawn on first MCP tool / receiver query, idle-evict after 5 minutes. Scala sessions also evict their paired Bloop daemon (Phase 7L+)."
|
|
16299
17361
|
)
|
|
16300
17362
|
);
|
|
16301
17363
|
console.log();
|
|
16302
|
-
console.log(
|
|
17364
|
+
console.log(chalk17.dim("See `repowise lsp doctor` for PATH probe + install hints."));
|
|
16303
17365
|
}
|
|
16304
17366
|
function lspStop() {
|
|
16305
|
-
console.log(
|
|
17367
|
+
console.log(chalk17.bold("RepoWise LSP stop \u2014 preview"));
|
|
16306
17368
|
console.log(
|
|
16307
|
-
|
|
17369
|
+
chalk17.dim(
|
|
16308
17370
|
"No CLI-owned warm sessions exist. Listener-owned LSP sessions evict automatically after 5 minutes of idle (Phase 7L) and under the RSS soft-cap LRU when memory pressure rises. Explicit listener-side stop IPC is a future follow-up."
|
|
16309
17371
|
)
|
|
16310
17372
|
);
|
|
@@ -16327,29 +17389,29 @@ var ENABLE_TARGETS = {
|
|
|
16327
17389
|
};
|
|
16328
17390
|
async function lspEnable(target) {
|
|
16329
17391
|
const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
|
|
16330
|
-
if (noColor)
|
|
17392
|
+
if (noColor) chalk17.level = 0;
|
|
16331
17393
|
if (!target) {
|
|
16332
|
-
console.error(
|
|
16333
|
-
console.error(
|
|
17394
|
+
console.error(chalk17.red("Usage: repowise lsp enable <target>"));
|
|
17395
|
+
console.error(chalk17.dim(` Supported targets: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
|
|
16334
17396
|
process.exitCode = 1;
|
|
16335
17397
|
return;
|
|
16336
17398
|
}
|
|
16337
17399
|
const spec = ENABLE_TARGETS[target];
|
|
16338
17400
|
if (!spec) {
|
|
16339
|
-
console.error(
|
|
16340
|
-
console.error(
|
|
17401
|
+
console.error(chalk17.red(`\u2716 Unknown LSP override target: ${target}`));
|
|
17402
|
+
console.error(chalk17.dim(` Supported: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
|
|
16341
17403
|
process.exitCode = 1;
|
|
16342
17404
|
return;
|
|
16343
17405
|
}
|
|
16344
17406
|
const config2 = await getConfig();
|
|
16345
17407
|
const existing = config2.lspOverrides?.[spec.language];
|
|
16346
17408
|
if (existing === spec.name) {
|
|
16347
|
-
console.log(
|
|
16348
|
-
console.log(
|
|
17409
|
+
console.log(chalk17.green(`\u2714 ${spec.displayName} is already enabled.`));
|
|
17410
|
+
console.log(chalk17.dim(` ${spec.postAcceptTip}`));
|
|
16349
17411
|
return;
|
|
16350
17412
|
}
|
|
16351
17413
|
console.log();
|
|
16352
|
-
console.log(
|
|
17414
|
+
console.log(chalk17.cyan.bold(` \u2500\u2500 Enable ${spec.displayName} \u2500\u2500`));
|
|
16353
17415
|
for (const line of spec.consentLines) {
|
|
16354
17416
|
console.log(` ${line}`);
|
|
16355
17417
|
}
|
|
@@ -16361,7 +17423,7 @@ async function lspEnable(target) {
|
|
|
16361
17423
|
});
|
|
16362
17424
|
if (!proceed) {
|
|
16363
17425
|
console.log(
|
|
16364
|
-
|
|
17426
|
+
chalk17.dim(
|
|
16365
17427
|
` Cancelled. ${spec.language.toUpperCase()} will continue using ${spec.revertDisplay}.`
|
|
16366
17428
|
)
|
|
16367
17429
|
);
|
|
@@ -16370,32 +17432,32 @@ async function lspEnable(target) {
|
|
|
16370
17432
|
const nextOverrides = { ...config2.lspOverrides ?? {}, [spec.language]: spec.name };
|
|
16371
17433
|
await mergeAndSaveConfig({ lspOverrides: nextOverrides });
|
|
16372
17434
|
console.log(
|
|
16373
|
-
|
|
17435
|
+
chalk17.green(
|
|
16374
17436
|
` \u2714 ${spec.displayName} enabled \u2014 listener will use it within ~5 min (reconcile cycle) or on next session restart.`
|
|
16375
17437
|
)
|
|
16376
17438
|
);
|
|
16377
|
-
console.log(
|
|
17439
|
+
console.log(chalk17.dim(` \u2139 ${spec.postAcceptTip}`));
|
|
16378
17440
|
}
|
|
16379
17441
|
async function lspDisable(target) {
|
|
16380
17442
|
const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
|
|
16381
|
-
if (noColor)
|
|
17443
|
+
if (noColor) chalk17.level = 0;
|
|
16382
17444
|
if (!target) {
|
|
16383
|
-
console.error(
|
|
16384
|
-
console.error(
|
|
17445
|
+
console.error(chalk17.red("Usage: repowise lsp disable <target>"));
|
|
17446
|
+
console.error(chalk17.dim(` Supported targets: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
|
|
16385
17447
|
process.exitCode = 1;
|
|
16386
17448
|
return;
|
|
16387
17449
|
}
|
|
16388
17450
|
const spec = ENABLE_TARGETS[target];
|
|
16389
17451
|
if (!spec) {
|
|
16390
|
-
console.error(
|
|
16391
|
-
console.error(
|
|
17452
|
+
console.error(chalk17.red(`\u2716 Unknown LSP override target: ${target}`));
|
|
17453
|
+
console.error(chalk17.dim(` Supported: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
|
|
16392
17454
|
process.exitCode = 1;
|
|
16393
17455
|
return;
|
|
16394
17456
|
}
|
|
16395
17457
|
const config2 = await getConfig();
|
|
16396
17458
|
const current = config2.lspOverrides?.[spec.language];
|
|
16397
17459
|
if (current !== spec.name) {
|
|
16398
|
-
console.log(
|
|
17460
|
+
console.log(chalk17.dim(` ${spec.displayName} is not enabled \u2014 nothing to do.`));
|
|
16399
17461
|
return;
|
|
16400
17462
|
}
|
|
16401
17463
|
const { [spec.language]: _removed, ...rest } = config2.lspOverrides ?? {};
|
|
@@ -16405,28 +17467,28 @@ async function lspDisable(target) {
|
|
|
16405
17467
|
...nextOverrides ? { lspOverrides: nextOverrides } : { lspOverrides: void 0 }
|
|
16406
17468
|
});
|
|
16407
17469
|
console.log(
|
|
16408
|
-
|
|
17470
|
+
chalk17.green(
|
|
16409
17471
|
` \u2714 ${spec.displayName} disabled \u2014 listener will revert to ${spec.revertDisplay} within ~5 min or on next session restart.`
|
|
16410
17472
|
)
|
|
16411
17473
|
);
|
|
16412
17474
|
}
|
|
16413
17475
|
async function lspOff() {
|
|
16414
17476
|
const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
|
|
16415
|
-
if (noColor)
|
|
17477
|
+
if (noColor) chalk17.level = 0;
|
|
16416
17478
|
await mergeAndSaveConfig({ lspEnabled: false });
|
|
16417
17479
|
console.log(
|
|
16418
|
-
|
|
17480
|
+
chalk17.green(
|
|
16419
17481
|
" \u2714 LSP subsystem disabled. The listener will stop spawning language servers and `lsp_*` tools will report disabled within ~5 min (reconcile) or on next restart."
|
|
16420
17482
|
)
|
|
16421
17483
|
);
|
|
16422
|
-
console.log(
|
|
17484
|
+
console.log(chalk17.dim(" Re-enable with: repowise lsp on"));
|
|
16423
17485
|
}
|
|
16424
17486
|
async function lspOn() {
|
|
16425
17487
|
const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
|
|
16426
|
-
if (noColor)
|
|
17488
|
+
if (noColor) chalk17.level = 0;
|
|
16427
17489
|
await mergeAndSaveConfig({ lspEnabled: true });
|
|
16428
17490
|
console.log(
|
|
16429
|
-
|
|
17491
|
+
chalk17.green(
|
|
16430
17492
|
" \u2714 LSP subsystem enabled. The listener will install + warm language servers for your repos within ~5 min (reconcile) or on next restart."
|
|
16431
17493
|
)
|
|
16432
17494
|
);
|
|
@@ -16435,7 +17497,7 @@ async function lspOn() {
|
|
|
16435
17497
|
// bin/repowise.ts
|
|
16436
17498
|
var __filename = fileURLToPath4(import.meta.url);
|
|
16437
17499
|
var __dirname = dirname23(__filename);
|
|
16438
|
-
var pkg = JSON.parse(readFileSync3(
|
|
17500
|
+
var pkg = JSON.parse(readFileSync3(join63(__dirname, "..", "..", "package.json"), "utf-8"));
|
|
16439
17501
|
var program = new Command();
|
|
16440
17502
|
program.name(getPackageName()).description("AI-optimized codebase context generator").version(pkg.version).hook("preAction", async () => {
|
|
16441
17503
|
await showWelcome(pkg.version);
|
|
@@ -16511,6 +17573,15 @@ lspCommand.command("off").description("Disable the LSP subsystem entirely (kill-
|
|
|
16511
17573
|
lspCommand.command("on").description("Re-enable the LSP subsystem after `lsp off`").action(async () => {
|
|
16512
17574
|
await lspOn();
|
|
16513
17575
|
});
|
|
17576
|
+
var toolsCommand = program.command("tools").description("Add RepoWise to your AI tools (Claude Code, Cursor, \u2026) after install").action(async () => {
|
|
17577
|
+
await toolsPick();
|
|
17578
|
+
});
|
|
17579
|
+
toolsCommand.command("add [tools...]").description("Add named AI tools (or open the picker with no args)").action(async (list) => {
|
|
17580
|
+
await toolsAdd(list ?? []);
|
|
17581
|
+
});
|
|
17582
|
+
toolsCommand.command("list").description("List the AI tools RepoWise is configured for").action(async () => {
|
|
17583
|
+
await toolsList();
|
|
17584
|
+
});
|
|
16514
17585
|
program.command("uninstall").description("Remove RepoWise from this machine (3 tiers: stop | logout | uninstall)").option("--tier <tier>", "Cleanup tier: stop | logout | uninstall", "uninstall").option("--yes", "Skip confirm prompt", false).action(async (options) => {
|
|
16515
17586
|
const tier = options.tier ?? "uninstall";
|
|
16516
17587
|
await uninstallCommand({ tier, yes: options.yes });
|