repowise 0.1.95 → 0.1.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin/repowise.js +629 -284
  2. package/package.json +1 -1
@@ -3133,7 +3133,7 @@ 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 join61 } from "path";
3136
+ import { dirname as dirname23, join as join62 } from "path";
3137
3137
  import { Command } from "commander";
3138
3138
 
3139
3139
  // ../listener/dist/main.js
@@ -8007,10 +8007,10 @@ async function ensureServerConsent(opts) {
8007
8007
  });
8008
8008
  if (!res.ok)
8009
8009
  return;
8010
- const fs30 = await import("fs/promises");
8010
+ const fs31 = await import("fs/promises");
8011
8011
  const path = await import("path");
8012
- await fs30.mkdir(path.dirname(opts.markerPath), { recursive: true });
8013
- await fs30.writeFile(opts.markerPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", {
8012
+ await fs31.mkdir(path.dirname(opts.markerPath), { recursive: true });
8013
+ await fs31.writeFile(opts.markerPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", {
8014
8014
  encoding: "utf-8",
8015
8015
  mode: 384
8016
8016
  });
@@ -10510,6 +10510,33 @@ function sleep(ms) {
10510
10510
  }, ms);
10511
10511
  });
10512
10512
  }
10513
+ async function waitForCredentials(isRunning2) {
10514
+ let changed = false;
10515
+ let watcher = null;
10516
+ try {
10517
+ const fs31 = await import("fs");
10518
+ watcher = fs31.watch(getConfigDir(), (_event, filename) => {
10519
+ if (!filename || filename === "credentials.json")
10520
+ changed = true;
10521
+ });
10522
+ } catch {
10523
+ }
10524
+ try {
10525
+ while (isRunning2()) {
10526
+ await sleep(changed ? 0 : 1e4);
10527
+ changed = false;
10528
+ if (!isRunning2())
10529
+ break;
10530
+ const fresh = await getValidCredentials({ forceRefresh: true });
10531
+ if (fresh)
10532
+ return fresh;
10533
+ }
10534
+ return null;
10535
+ } finally {
10536
+ if (watcher)
10537
+ watcher.close();
10538
+ }
10539
+ }
10513
10540
  function decodeEmailFromIdToken(idToken) {
10514
10541
  try {
10515
10542
  const payload = JSON.parse(Buffer.from(idToken.split(".")[1], "base64url").toString());
@@ -10741,6 +10768,9 @@ async function checkStaleContext(repos, state, groups) {
10741
10768
  }
10742
10769
  return dirty;
10743
10770
  }
10771
+ function mcpAiToolsKey(aiTools) {
10772
+ return JSON.stringify([...aiTools ?? []].sort());
10773
+ }
10744
10774
  async function reconcileMcpConfigs(repos, packageName) {
10745
10775
  const shimCmd = packageName;
10746
10776
  const { contextFolder, aiTools, graphOnly } = await readRawToolConfig();
@@ -10909,12 +10939,15 @@ async function startListener() {
10909
10939
  process.exitCode = 1;
10910
10940
  return;
10911
10941
  }
10912
- const credentials = await getValidCredentials();
10942
+ let credentials = await getValidCredentials();
10913
10943
  if (!credentials) {
10914
- console.error("Not logged in. Run `repowise login` first.");
10915
- await releaseLockAndExit();
10916
- process.exitCode = 1;
10917
- return;
10944
+ console.warn("Not logged in. Run `repowise login` \u2014 the listener will start automatically once you sign in.");
10945
+ credentials = await waitForCredentials(() => running);
10946
+ if (!credentials) {
10947
+ await releaseLockAndExit();
10948
+ return;
10949
+ }
10950
+ console.log("Logged in \u2014 starting RepoWise listener.");
10918
10951
  }
10919
10952
  let state;
10920
10953
  try {
@@ -10981,6 +11014,7 @@ async function startListener() {
10981
11014
  let pollIntervalMs = 5e3;
10982
11015
  let pollCycleCount = 0;
10983
11016
  const RECONCILE_EVERY_N_CYCLES = 60;
11017
+ let lastMcpAiToolsKey = null;
10984
11018
  const origLog = console.log.bind(console);
10985
11019
  const origError = console.error.bind(console);
10986
11020
  const origWarn = console.warn.bind(console);
@@ -11207,6 +11241,16 @@ async function startListener() {
11207
11241
  console.warn("[mcp-config] front-load for newly-registered repo(s) failed:", err instanceof Error ? err.message : String(err));
11208
11242
  });
11209
11243
  }
11244
+ const aiToolsKey = mcpAiToolsKey((await readRawToolConfig()).aiTools);
11245
+ if (lastMcpAiToolsKey === null) {
11246
+ lastMcpAiToolsKey = aiToolsKey;
11247
+ } else if (aiToolsKey !== lastMcpAiToolsKey) {
11248
+ lastMcpAiToolsKey = aiToolsKey;
11249
+ console.log("[config-sync] AI-tool selection changed \u2014 front-loading MCP config");
11250
+ void reconcileMcpConfigs(freshRepos, packageName).catch((err) => {
11251
+ console.warn("[mcp-config] front-load for aiTools change failed:", err instanceof Error ? err.message : String(err));
11252
+ });
11253
+ }
11210
11254
  } catch {
11211
11255
  }
11212
11256
  for (const group of groups) {
@@ -11456,8 +11500,8 @@ async function startListener() {
11456
11500
  await sleep(5e3);
11457
11501
  if (!running)
11458
11502
  break;
11459
- const fresh = await getValidCredentials({ forceRefresh: true });
11460
- if (fresh) {
11503
+ const fresh2 = await getValidCredentials({ forceRefresh: true });
11504
+ if (fresh2) {
11461
11505
  console.log("Credentials recovered \u2014 resuming listener");
11462
11506
  recovered = true;
11463
11507
  break;
@@ -11478,29 +11522,9 @@ async function startListener() {
11478
11522
  } catch {
11479
11523
  }
11480
11524
  }
11481
- const credentialsPath = join41(getConfigDir(), "credentials.json");
11482
- let credentialsChanged = false;
11483
- let watcher = null;
11484
- try {
11485
- const fs30 = await import("fs");
11486
- watcher = fs30.watch(credentialsPath, () => {
11487
- credentialsChanged = true;
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;
11500
- }
11501
- }
11502
- if (watcher)
11503
- watcher.close();
11525
+ const fresh = await waitForCredentials(() => running);
11526
+ if (fresh)
11527
+ console.log("Re-authenticated \u2014 resuming listener");
11504
11528
  continue;
11505
11529
  }
11506
11530
  pollIntervalMs = minPollInterval;
@@ -11654,7 +11678,7 @@ async function showWelcome(currentVersion) {
11654
11678
 
11655
11679
  // src/commands/create.ts
11656
11680
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
11657
- import { dirname as dirname19, join as join49 } from "path";
11681
+ import { dirname as dirname19, join as join50 } from "path";
11658
11682
  init_src();
11659
11683
  import chalk8 from "chalk";
11660
11684
  import ora from "ora";
@@ -12013,25 +12037,26 @@ async function apiRequest(path, options) {
12013
12037
  // src/lib/prompts.ts
12014
12038
  import { checkbox, confirm, Separator } from "@inquirer/prompts";
12015
12039
  import chalk2 from "chalk";
12016
- async function selectAiTools() {
12040
+ async function selectAiTools(opts) {
12041
+ const checkedSet = new Set(opts?.defaults ?? []);
12017
12042
  const choices = [
12018
12043
  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" },
12044
+ { name: "Cursor", value: "cursor", checked: checkedSet.has("cursor") },
12045
+ { name: "Claude Code", value: "claude-code", checked: checkedSet.has("claude-code") },
12046
+ { name: "GitHub Copilot", value: "copilot", checked: checkedSet.has("copilot") },
12047
+ { name: "Windsurf", value: "windsurf", checked: checkedSet.has("windsurf") },
12023
12048
  new Separator(chalk2.dim("\u2500\u2500 More Tools \u2500\u2500")),
12024
- { name: "Cline", value: "cline" },
12025
- { name: "Codex", value: "codex" },
12049
+ { name: "Cline", value: "cline", checked: checkedSet.has("cline") },
12050
+ { name: "Codex", value: "codex", checked: checkedSet.has("codex") },
12026
12051
  // 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" },
12052
+ { name: "Kilo Code", value: "kilo", checked: checkedSet.has("kilo") },
12053
+ { name: "Gemini CLI", value: "gemini", checked: checkedSet.has("gemini") },
12029
12054
  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" },
12055
+ { name: "Warp", value: "warp", checked: checkedSet.has("warp") },
12056
+ { name: "JetBrains Junie", value: "junie", checked: checkedSet.has("junie") },
12057
+ { name: "Google Jules", value: "jules", checked: checkedSet.has("jules") },
12058
+ { name: "Amp", value: "amp", checked: checkedSet.has("amp") },
12059
+ { name: "Devin", value: "devin", checked: checkedSet.has("devin") },
12035
12060
  new Separator(chalk2.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),
12036
12061
  { name: "Other (manual setup)", value: "other" }
12037
12062
  ];
@@ -12054,11 +12079,126 @@ async function selectAiTools() {
12054
12079
  }
12055
12080
  }
12056
12081
 
12082
+ // src/lib/setup-tools.ts
12083
+ async function writeToolConfigsForRepo(opts) {
12084
+ const { repoRoot, tools, repoName, contextFolder, contextFiles, variant, migrateLegacy } = opts;
12085
+ const results = [];
12086
+ const written = /* @__PURE__ */ new Set();
12087
+ for (const tool of tools) {
12088
+ const config2 = AI_TOOL_CONFIG[tool];
12089
+ if (written.has(config2.filePath)) continue;
12090
+ written.add(config2.filePath);
12091
+ if (migrateLegacy && config2.legacyFilePath) {
12092
+ await migrateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles, variant);
12093
+ }
12094
+ const { created } = await updateToolConfig(
12095
+ repoRoot,
12096
+ tool,
12097
+ repoName,
12098
+ contextFolder,
12099
+ contextFiles,
12100
+ variant
12101
+ );
12102
+ results.push(`${created ? "Created" : "Updated"} ${config2.filePath}`);
12103
+ }
12104
+ if (!written.has("AGENTS.md")) {
12105
+ const { created } = await updateToolConfig(
12106
+ repoRoot,
12107
+ "codex",
12108
+ repoName,
12109
+ contextFolder,
12110
+ contextFiles,
12111
+ variant
12112
+ );
12113
+ results.push(`${created ? "Created" : "Updated"} AGENTS.md`);
12114
+ }
12115
+ if (tools.includes("claude-code")) {
12116
+ const { relPath } = await writeClaudeHooksToRepo(repoRoot, contextFolder, variant);
12117
+ results.push(`Configured ${relPath} (RepoWise-first + SubagentStart hooks, local-only)`);
12118
+ }
12119
+ return results;
12120
+ }
12121
+
12122
+ // src/lib/mcp-resolver.ts
12123
+ import { basename as basename4, join as join45 } from "path";
12124
+ import { promises as fs21 } from "fs";
12125
+ var MCP_WRITER_TOOLS = /* @__PURE__ */ new Set([
12126
+ "claude-code",
12127
+ "cursor",
12128
+ "copilot",
12129
+ "codex",
12130
+ "windsurf",
12131
+ "cline",
12132
+ "gemini"
12133
+ ]);
12134
+ function mcpPathFor(tool, repoRoot, home) {
12135
+ switch (tool) {
12136
+ case "claude-code":
12137
+ return { path: join45(repoRoot, ".mcp.json"), scope: "repo" };
12138
+ case "cursor":
12139
+ return { path: join45(repoRoot, ".cursor", "mcp.json"), scope: "repo" };
12140
+ case "copilot":
12141
+ return { path: join45(repoRoot, ".vscode", "mcp.json"), scope: "repo" };
12142
+ case "codex":
12143
+ return { path: join45(home, ".codex", "mcp.json"), scope: "global" };
12144
+ case "windsurf":
12145
+ return { path: join45(home, ".codeium", "windsurf", "mcp_config.json"), scope: "global" };
12146
+ case "cline":
12147
+ return { path: join45(home, ".cline", "mcp.json"), scope: "global" };
12148
+ case "gemini":
12149
+ return { path: join45(home, ".gemini", "settings.json"), scope: "global" };
12150
+ default:
12151
+ return null;
12152
+ }
12153
+ }
12154
+ function mcpServerKeyFor(repoRoot) {
12155
+ return `RepoWise MCP for ${basename4(repoRoot)}`;
12156
+ }
12157
+ function mcpConfigTargetForTool(tool, opts) {
12158
+ const loc = mcpPathFor(tool, opts.repoRoot, opts.home);
12159
+ if (!loc) return null;
12160
+ return { tool, path: loc.path, scope: loc.scope, serverKey: mcpServerKeyFor(opts.repoRoot) };
12161
+ }
12162
+ async function mcpConfigState(target) {
12163
+ let raw;
12164
+ try {
12165
+ raw = await fs21.readFile(target.path, "utf-8");
12166
+ } catch {
12167
+ return "absent";
12168
+ }
12169
+ try {
12170
+ const parsed = JSON.parse(raw);
12171
+ return parsed.mcpServers && target.serverKey in parsed.mcpServers ? "configured" : "pending";
12172
+ } catch {
12173
+ return "absent";
12174
+ }
12175
+ }
12176
+ function mcpActivationHint(tool, repoName) {
12177
+ if (!MCP_WRITER_TOOLS.has(tool)) return null;
12178
+ if (tool === "claude-code") {
12179
+ return `In Claude Code, run \`/mcp\` and approve the "RepoWise MCP for ${repoName}" server.`;
12180
+ }
12181
+ return `Reload your AI tool and approve the "RepoWise MCP for ${repoName}" server when prompted.`;
12182
+ }
12183
+ async function mcpStatusForRepo(opts) {
12184
+ const repoName = basename4(opts.repoRoot);
12185
+ const out = [];
12186
+ for (const tool of opts.aiTools) {
12187
+ const target = mcpConfigTargetForTool(tool, { repoRoot: opts.repoRoot, home: opts.home });
12188
+ if (!target) continue;
12189
+ const state = await mcpConfigState(target);
12190
+ if (state === "absent") continue;
12191
+ const configured = state === "configured";
12192
+ out.push({ tool, configured, hint: configured ? null : mcpActivationHint(tool, repoName) });
12193
+ }
12194
+ return out;
12195
+ }
12196
+
12057
12197
  // src/lib/gitignore.ts
12058
12198
  import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2 } from "fs";
12059
- import { join as join45 } from "path";
12199
+ import { join as join46 } from "path";
12060
12200
  function ensureGitignore(repoRoot, entry) {
12061
- const gitignorePath = join45(repoRoot, ".gitignore");
12201
+ const gitignorePath = join46(repoRoot, ".gitignore");
12062
12202
  if (existsSync2(gitignorePath)) {
12063
12203
  const content = readFileSync2(gitignorePath, "utf-8");
12064
12204
  const lines = content.split("\n").map((l) => l.trim());
@@ -12075,7 +12215,7 @@ function ensureGitignore(repoRoot, entry) {
12075
12215
  // src/lib/graph-cache.ts
12076
12216
  init_config_dir();
12077
12217
  import { mkdirSync, writeFileSync as writeFileSync2, renameSync, unlinkSync } from "fs";
12078
- import { dirname as dirname18, join as join46 } from "path";
12218
+ import { dirname as dirname18, join as join47 } from "path";
12079
12219
  var SAFE_REPO_ID = /^[A-Za-z0-9_.-]{1,128}$/;
12080
12220
  function assertSafeRepoId2(repoId) {
12081
12221
  if (!repoId || typeof repoId !== "string" || !SAFE_REPO_ID.test(repoId) || repoId === "." || repoId === ".." || repoId.startsWith(".")) {
@@ -12084,7 +12224,7 @@ function assertSafeRepoId2(repoId) {
12084
12224
  }
12085
12225
  function graphCachePath(repoId) {
12086
12226
  assertSafeRepoId2(repoId);
12087
- return join46(getConfigDir(), "graphs", `${repoId}.json`);
12227
+ return join47(getConfigDir(), "graphs", `${repoId}.json`);
12088
12228
  }
12089
12229
  function isUsableGraph(parsed) {
12090
12230
  if (typeof parsed !== "object" || parsed === null) return false;
@@ -12700,17 +12840,27 @@ var ProgressRenderer = class {
12700
12840
  if (generating) {
12701
12841
  const genBaseName = generating.fileName.split("/").pop() ?? generating.fileName;
12702
12842
  const isCore = CORE_FILES.has(genBaseName);
12703
- const sectionFiles = gp.fileStatuses.filter((f) => {
12843
+ const sectionFiles2 = gp.fileStatuses.filter((f) => {
12704
12844
  const bn = f.fileName.split("/").pop() ?? f.fileName;
12705
12845
  return isCore ? CORE_FILES.has(bn) : !CORE_FILES.has(bn);
12706
12846
  });
12707
- const sectionCompleted = sectionFiles.filter((f) => f.status === "completed").length;
12708
- return `${generating.fileName} (${sectionCompleted}/${sectionFiles.length}) ${chalk4.dim(`(${overallPct}%)`)}`;
12847
+ const sectionCompleted = sectionFiles2.filter((f) => f.status === "completed").length;
12848
+ return `${generating.fileName} (${sectionCompleted}/${sectionFiles2.length}) ${chalk4.dim(`(${overallPct}%)`)}`;
12709
12849
  }
12710
12850
  const allDone = gp.fileStatuses.every((f) => f.status === "completed");
12711
12851
  if (allDone) {
12712
12852
  return `${stepLabel}... ${chalk4.dim(`(${overallPct}%)`)}`;
12713
12853
  }
12854
+ const baseNameOf = (f) => f.fileName.split("/").pop() ?? f.fileName;
12855
+ const coreFiles = gp.fileStatuses.filter((f) => CORE_FILES.has(baseNameOf(f)));
12856
+ const optionalFiles = gp.fileStatuses.filter((f) => !CORE_FILES.has(baseNameOf(f)));
12857
+ const coreDone = coreFiles.length > 0 && coreFiles.every((f) => f.status === "completed");
12858
+ const sectionFiles = coreDone && optionalFiles.length > 0 ? optionalFiles : coreFiles;
12859
+ if (sectionFiles.length > 0) {
12860
+ const sectionCompleted = sectionFiles.filter((f) => f.status === "completed").length;
12861
+ const name = gp.currentFileName || stepLabel;
12862
+ return `${name} (${sectionCompleted}/${sectionFiles.length}) ${chalk4.dim(`(${overallPct}%)`)}`;
12863
+ }
12714
12864
  }
12715
12865
  }
12716
12866
  return `${stepLabel}... ${chalk4.dim(`(${overallPct}%)`)}`;
@@ -12801,8 +12951,8 @@ async function promptDepInstallConsent(missing, opts) {
12801
12951
  import chalk6 from "chalk";
12802
12952
 
12803
12953
  // src/lib/dep-installer.ts
12804
- import { promises as fs21 } from "fs";
12805
- import { join as join47 } from "path";
12954
+ import { promises as fs22 } from "fs";
12955
+ import { join as join48 } from "path";
12806
12956
  var SUPPORTED_DEP_LANGUAGES = /* @__PURE__ */ new Set([
12807
12957
  "typescript",
12808
12958
  "javascript",
@@ -12812,14 +12962,14 @@ var SUPPORTED_DEP_LANGUAGES = /* @__PURE__ */ new Set([
12812
12962
  ]);
12813
12963
  var exists = async (p) => {
12814
12964
  try {
12815
- await fs21.access(p);
12965
+ await fs22.access(p);
12816
12966
  return true;
12817
12967
  } catch {
12818
12968
  return false;
12819
12969
  }
12820
12970
  };
12821
12971
  async function fileExists16(repoRoot, name) {
12822
- return exists(join47(repoRoot, name));
12972
+ return exists(join48(repoRoot, name));
12823
12973
  }
12824
12974
  async function detectNodePackageManager(repoRoot) {
12825
12975
  if (await fileExists16(repoRoot, "pnpm-lock.yaml")) {
@@ -12896,13 +13046,13 @@ async function detectMissingDeps(repoRoot, scopedLanguages) {
12896
13046
  // src/lib/dep-installer-runner.ts
12897
13047
  import { spawn as spawn11 } from "child_process";
12898
13048
  import { createWriteStream as createWriteStream3 } from "fs";
12899
- import { promises as fs22 } from "fs";
12900
- import { join as join48 } from "path";
13049
+ import { promises as fs23 } from "fs";
13050
+ import { join as join49 } from "path";
12901
13051
  var DEFAULT_INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
12902
13052
  async function runMissingDepInstalls(opts) {
12903
13053
  const safeRepoId = opts.repoId.replace(/[^a-zA-Z0-9_-]/g, "_");
12904
- const logPath2 = join48(getConfigDir2(), `install-log.${safeRepoId}.txt`);
12905
- await fs22.mkdir(getConfigDir2(), { recursive: true });
13054
+ const logPath2 = join49(getConfigDir2(), `install-log.${safeRepoId}.txt`);
13055
+ await fs23.mkdir(getConfigDir2(), { recursive: true });
12906
13056
  const stream = opts.logStream ?? createWriteStream3(logPath2, { flags: "a" });
12907
13057
  stream.on("error", () => {
12908
13058
  });
@@ -12948,7 +13098,7 @@ async function runOne2(dep, repoRoot, stream, timeoutMs) {
12948
13098
  }
12949
13099
  async function runPipFlow(repoRoot, stream, timeoutMs) {
12950
13100
  await runSimple(["python3", "-m", "venv", ".venv"], repoRoot, stream, timeoutMs);
12951
- const venvPip = process.platform === "win32" ? join48(".venv", "Scripts", "pip.exe") : join48(".venv", "bin", "pip");
13101
+ const venvPip = process.platform === "win32" ? join49(".venv", "Scripts", "pip.exe") : join49(".venv", "bin", "pip");
12952
13102
  await runSimple([venvPip, "install", "-r", "requirements.txt"], repoRoot, stream, timeoutMs);
12953
13103
  }
12954
13104
  function runSimple(cmd, repoRoot, stream, timeoutMs) {
@@ -13286,9 +13436,7 @@ async function create() {
13286
13436
  }
13287
13437
  if (tools.length === 0 && !hasOther) {
13288
13438
  console.log(
13289
- chalk8.yellow(
13290
- "\nNo AI tools selected. You can configure them later with `repowise config`."
13291
- )
13439
+ chalk8.yellow("\nNo AI tools selected. You can set them up later with `repowise tools`.")
13292
13440
  );
13293
13441
  }
13294
13442
  if (repoRoot) {
@@ -13521,7 +13669,7 @@ async function create() {
13521
13669
  const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
13522
13670
  const files = listResult.data?.files ?? listResult.files ?? [];
13523
13671
  if (files.length > 0) {
13524
- const contextDir = join49(repoRoot, DEFAULT_CONTEXT_FOLDER);
13672
+ const contextDir = join50(repoRoot, DEFAULT_CONTEXT_FOLDER);
13525
13673
  mkdirSync2(contextDir, { recursive: true });
13526
13674
  let downloadedCount = 0;
13527
13675
  let failedCount = 0;
@@ -13535,7 +13683,7 @@ async function create() {
13535
13683
  const response = await fetch(presignedUrl);
13536
13684
  if (response.ok) {
13537
13685
  const content = await response.text();
13538
- const filePath = join49(contextDir, file.fileName);
13686
+ const filePath = join50(contextDir, file.fileName);
13539
13687
  mkdirSync2(dirname19(filePath), { recursive: true });
13540
13688
  writeFileSync3(filePath, content, "utf-8");
13541
13689
  downloadedCount++;
@@ -13619,51 +13767,17 @@ Files are stored on our servers (not in git). Retry when online.`
13619
13767
  }
13620
13768
  if (repoRoot) {
13621
13769
  spinner.start("Configuring AI tools...");
13622
- const results = [];
13623
- const written = /* @__PURE__ */ new Set();
13624
- const toolVariant = graphOnly ? "graph" : "full";
13625
- for (const tool of tools) {
13626
- const config2 = AI_TOOL_CONFIG[tool];
13627
- if (written.has(config2.filePath)) continue;
13628
- written.add(config2.filePath);
13629
- if (config2.legacyFilePath) {
13630
- await migrateToolConfig(
13631
- repoRoot,
13632
- tool,
13633
- repoName,
13634
- contextFolder,
13635
- contextFiles,
13636
- toolVariant
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
13658
- );
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
- }
13770
+ const results = await writeToolConfigsForRepo({
13771
+ repoRoot,
13772
+ tools,
13773
+ repoName,
13774
+ contextFolder,
13775
+ contextFiles,
13776
+ variant: graphOnly ? "graph" : "full",
13777
+ migrateLegacy: true
13778
+ });
13665
13779
  spinner.succeed("AI tools configured");
13666
- console.log(chalk8.dim(results.join("\n")));
13780
+ console.log(chalk8.dim(results.map((r) => ` ${r}`).join("\n")));
13667
13781
  }
13668
13782
  const priorAutoInstall = await getPriorConsent(repoId);
13669
13783
  const updatedRepos = [];
@@ -13772,6 +13886,11 @@ Files are stored on our servers (not in git). Retry when online.`
13772
13886
  console.log(
13773
13887
  ` ${chalk8.cyan("\u2022")} Open Claude Code / Cursor and ask: "What does this repo do?"`
13774
13888
  );
13889
+ if (tools.some((t) => MCP_WRITER_TOOLS.has(t))) {
13890
+ console.log(
13891
+ ` ${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.`
13892
+ );
13893
+ }
13775
13894
  console.log(
13776
13895
  ` ${chalk8.cyan("\u2022")} Head to the dashboard \u2192 "Complete Onboarding" to explore quality scores and gaps.`
13777
13896
  );
@@ -13786,7 +13905,7 @@ Files are stored on our servers (not in git). Retry when online.`
13786
13905
 
13787
13906
  // src/commands/member.ts
13788
13907
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
13789
- import { dirname as dirname20, join as join50, resolve, sep } from "path";
13908
+ import { dirname as dirname20, join as join51, resolve, sep } from "path";
13790
13909
  import chalk9 from "chalk";
13791
13910
  import ora2 from "ora";
13792
13911
  var DEFAULT_CONTEXT_FOLDER2 = "repowise-context";
@@ -13940,7 +14059,7 @@ async function member() {
13940
14059
  spinner.succeed(`Found ${chalk9.bold(files.length)} context files on server`);
13941
14060
  const { tools } = await selectAiTools();
13942
14061
  spinner.start("Downloading context files...");
13943
- const contextDir = join50(repoRoot, DEFAULT_CONTEXT_FOLDER2);
14062
+ const contextDir = join51(repoRoot, DEFAULT_CONTEXT_FOLDER2);
13944
14063
  mkdirSync3(contextDir, { recursive: true });
13945
14064
  let downloadedCount = 0;
13946
14065
  let failedCount = 0;
@@ -13985,35 +14104,14 @@ async function member() {
13985
14104
  {
13986
14105
  spinner.start("Configuring AI tools...");
13987
14106
  const contextFiles = await scanLocalContextFiles(repoRoot, DEFAULT_CONTEXT_FOLDER2);
13988
- const configured = [];
13989
- const written = /* @__PURE__ */ new Set();
13990
- for (const tool of tools) {
13991
- const config2 = AI_TOOL_CONFIG[tool];
13992
- if (written.has(config2.filePath)) continue;
13993
- written.add(config2.filePath);
13994
- const { created } = await updateToolConfig(
13995
- repoRoot,
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
- }
14107
+ const configured = await writeToolConfigsForRepo({
14108
+ repoRoot,
14109
+ tools,
14110
+ repoName,
14111
+ contextFolder: DEFAULT_CONTEXT_FOLDER2,
14112
+ contextFiles
14113
+ // member historically passed no variant and did not migrate legacy paths.
14114
+ });
14017
14115
  spinner.succeed("AI tools configured");
14018
14116
  for (const msg of configured) {
14019
14117
  console.log(chalk9.dim(` ${msg}`));
@@ -14134,19 +14232,20 @@ async function member() {
14134
14232
  }
14135
14233
 
14136
14234
  // src/commands/login.ts
14235
+ import { homedir as homedir9 } from "os";
14137
14236
  import chalk10 from "chalk";
14138
14237
  import ora3 from "ora";
14139
14238
 
14140
14239
  // src/lib/tenant-graph-purge.ts
14141
- import { promises as fs23 } from "fs";
14240
+ import { promises as fs24 } from "fs";
14142
14241
  import { homedir as homedir8 } from "os";
14143
- import { join as join51 } from "path";
14242
+ import { join as join52 } from "path";
14144
14243
  async function purgeForeignGraphs(validRepoIds, home = homedir8()) {
14145
- const graphsDir = join51(home, ".repowise", "graphs");
14244
+ const graphsDir = join52(home, ".repowise", "graphs");
14146
14245
  const result = { kept: [], removed: [] };
14147
14246
  let entries;
14148
14247
  try {
14149
- entries = await fs23.readdir(graphsDir);
14248
+ entries = await fs24.readdir(graphsDir);
14150
14249
  } catch (err) {
14151
14250
  if (err.code === "ENOENT") return result;
14152
14251
  throw err;
@@ -14160,15 +14259,15 @@ async function purgeForeignGraphs(validRepoIds, home = homedir8()) {
14160
14259
  result.kept.push(entry);
14161
14260
  continue;
14162
14261
  }
14163
- const path = join51(graphsDir, entry);
14262
+ const path = join52(graphsDir, entry);
14164
14263
  try {
14165
- const stat8 = await fs23.lstat(path);
14264
+ const stat8 = await fs24.lstat(path);
14166
14265
  if (stat8.isSymbolicLink()) {
14167
- await fs23.unlink(path);
14266
+ await fs24.unlink(path);
14168
14267
  result.removed.push(entry);
14169
14268
  continue;
14170
14269
  }
14171
- await fs23.rm(path, { recursive: true, force: true });
14270
+ await fs24.rm(path, { recursive: true, force: true });
14172
14271
  result.removed.push(entry);
14173
14272
  } catch {
14174
14273
  }
@@ -14232,6 +14331,23 @@ Waiting for authentication...`);
14232
14331
  }
14233
14332
  } catch {
14234
14333
  }
14334
+ try {
14335
+ const repoRoot = detectRepoRoot();
14336
+ const cfg = await getConfig();
14337
+ const registered = (cfg.repos ?? []).some((r) => r.localPath === repoRoot);
14338
+ const tools = cfg.aiTools ?? [];
14339
+ if (registered && tools.length > 0) {
14340
+ const pending = (await mcpStatusForRepo({ repoRoot, home: homedir9(), aiTools: tools })).filter((s) => !s.configured);
14341
+ if (pending.length > 0) {
14342
+ console.log("");
14343
+ console.log(chalk10.dim(" To finish enabling RepoWise in your AI tools:"));
14344
+ for (const p of pending) {
14345
+ if (p.hint) console.log(chalk10.dim(` \u2022 ${p.hint}`));
14346
+ }
14347
+ }
14348
+ }
14349
+ } catch {
14350
+ }
14235
14351
  } catch (err) {
14236
14352
  const message = err instanceof Error ? err.message : "Login failed";
14237
14353
  spinner.fail(chalk10.red(message));
@@ -14253,11 +14369,21 @@ async function logout() {
14253
14369
 
14254
14370
  // src/commands/status.ts
14255
14371
  import { readFile as readFile16 } from "fs/promises";
14256
- import { basename as basename4, join as join52 } from "path";
14372
+ import { basename as basename5, join as join53 } from "path";
14373
+ import { homedir as homedir10 } from "os";
14374
+
14375
+ // ../../packages/shared/dist/lib/creds.js
14376
+ function isCredsHardExpired(creds) {
14377
+ if (!creds || typeof creds.expiresAt !== "number")
14378
+ return true;
14379
+ return Date.now() >= creds.expiresAt;
14380
+ }
14381
+
14382
+ // src/commands/status.ts
14257
14383
  async function status() {
14258
14384
  const configDir = getConfigDir2();
14259
- const STATE_PATH = join52(configDir, "listener-state.json");
14260
- const CONFIG_PATH = join52(configDir, "config.json");
14385
+ const STATE_PATH = join53(configDir, "listener-state.json");
14386
+ const CONFIG_PATH = join53(configDir, "config.json");
14261
14387
  let state = null;
14262
14388
  try {
14263
14389
  const data = await readFile16(STATE_PATH, "utf-8");
@@ -14279,33 +14405,74 @@ async function status() {
14279
14405
  } else {
14280
14406
  console.log("Auto-start: disabled");
14281
14407
  }
14408
+ let creds = null;
14409
+ try {
14410
+ creds = await getStoredCredentials2();
14411
+ } catch {
14412
+ }
14413
+ if (isCredsHardExpired(creds)) {
14414
+ console.log("Authentication: not logged in \u2014 run `repowise login`");
14415
+ } else {
14416
+ let email = null;
14417
+ try {
14418
+ const decoded = creds?.idToken ? decodeIdToken(creds.idToken).email : null;
14419
+ email = decoded && decoded !== "unknown" ? decoded : null;
14420
+ } catch {
14421
+ }
14422
+ console.log(`Authentication: logged in${email ? ` as ${email}` : ""}`);
14423
+ }
14282
14424
  console.log("");
14283
14425
  if (!state || Object.keys(state.repos).length === 0) {
14284
14426
  console.log("No sync history. Run `repowise listen` to start syncing.");
14285
14427
  return;
14286
14428
  }
14287
14429
  const repoNames = /* @__PURE__ */ new Map();
14430
+ const repoPaths = /* @__PURE__ */ new Map();
14431
+ let aiTools = [];
14288
14432
  try {
14289
14433
  const configData = await readFile16(CONFIG_PATH, "utf-8");
14290
14434
  const config2 = JSON.parse(configData);
14291
14435
  for (const repo of config2.repos ?? []) {
14292
- repoNames.set(repo.repoId, basename4(repo.localPath));
14436
+ repoNames.set(repo.repoId, basename5(repo.localPath));
14437
+ repoPaths.set(repo.repoId, repo.localPath);
14293
14438
  }
14439
+ aiTools = config2.aiTools ?? [];
14294
14440
  } catch {
14295
14441
  }
14296
14442
  console.log("Watched Repos:");
14443
+ const home = homedir10();
14297
14444
  for (const [repoId, repoState] of Object.entries(state.repos)) {
14298
14445
  const name = repoNames.get(repoId);
14299
14446
  const label = name ? `${name} (${repoId.slice(0, 8)})` : repoId;
14300
14447
  const syncTime = repoState.lastSyncTimestamp ? new Date(repoState.lastSyncTimestamp).toLocaleString() : "never";
14301
14448
  const commit = repoState.lastSyncCommitSha ? repoState.lastSyncCommitSha.slice(0, 7) : "none";
14302
14449
  console.log(` ${label}: last sync ${syncTime} (commit: ${commit})`);
14450
+ const localPath = repoPaths.get(repoId);
14451
+ if (localPath && aiTools.length > 0) {
14452
+ try {
14453
+ const statuses = await mcpStatusForRepo({ repoRoot: localPath, home, aiTools });
14454
+ if (statuses.length > 0) {
14455
+ const pending = statuses.filter((s) => !s.configured);
14456
+ if (pending.length === 0) {
14457
+ console.log(` MCP: active in all ${statuses.length} tool(s)`);
14458
+ } else {
14459
+ console.log(
14460
+ ` MCP: ${statuses.length - pending.length}/${statuses.length} tool(s) active \u2014 to finish:`
14461
+ );
14462
+ for (const p of pending) {
14463
+ if (p.hint) console.log(` \u2022 ${p.hint}`);
14464
+ }
14465
+ }
14466
+ }
14467
+ } catch {
14468
+ }
14469
+ }
14303
14470
  }
14304
14471
  }
14305
14472
 
14306
14473
  // src/commands/sync.ts
14307
14474
  import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
14308
- import { dirname as dirname21, join as join53 } from "path";
14475
+ import { dirname as dirname21, join as join54 } from "path";
14309
14476
  import chalk12 from "chalk";
14310
14477
  import ora4 from "ora";
14311
14478
  var POLL_INTERVAL_MS2 = 3e3;
@@ -14454,7 +14621,7 @@ async function sync() {
14454
14621
  const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
14455
14622
  const files = listResult.data?.files ?? listResult.files ?? [];
14456
14623
  if (files.length > 0) {
14457
- const contextDir = join53(repoRoot, DEFAULT_CONTEXT_FOLDER3);
14624
+ const contextDir = join54(repoRoot, DEFAULT_CONTEXT_FOLDER3);
14458
14625
  mkdirSync4(contextDir, { recursive: true });
14459
14626
  let downloadedCount = 0;
14460
14627
  let failedCount = 0;
@@ -14468,7 +14635,7 @@ async function sync() {
14468
14635
  const response = await fetch(presignedUrl);
14469
14636
  if (response.ok) {
14470
14637
  const content = await response.text();
14471
- const filePath = join53(contextDir, file.fileName);
14638
+ const filePath = join54(contextDir, file.fileName);
14472
14639
  mkdirSync4(dirname21(filePath), { recursive: true });
14473
14640
  writeFileSync5(filePath, content, "utf-8");
14474
14641
  downloadedCount++;
@@ -14721,10 +14888,158 @@ async function config() {
14721
14888
  }
14722
14889
  }
14723
14890
 
14891
+ // src/commands/tools.ts
14892
+ import { basename as basename6 } from "path";
14893
+ import chalk14 from "chalk";
14894
+ import ora6 from "ora";
14895
+ var DEFAULT_CONTEXT_FOLDER4 = "repowise-context";
14896
+ var VALID_TOOLS = Object.keys(AI_TOOL_CONFIG);
14897
+ async function resolveRepoOrExit() {
14898
+ let creds = null;
14899
+ try {
14900
+ creds = await getValidCredentials2();
14901
+ } catch {
14902
+ creds = null;
14903
+ }
14904
+ if (!creds) {
14905
+ console.error(chalk14.red("Not logged in. Run `repowise login` first."));
14906
+ process.exitCode = 1;
14907
+ return null;
14908
+ }
14909
+ let repoRoot;
14910
+ try {
14911
+ repoRoot = detectRepoRoot();
14912
+ } catch {
14913
+ console.error(chalk14.red("Not inside a git repository \u2014 `cd` into your repo first."));
14914
+ process.exitCode = 1;
14915
+ return null;
14916
+ }
14917
+ const config2 = await getConfig();
14918
+ const registered = (config2.repos ?? []).some((r) => r.localPath === repoRoot);
14919
+ if (!registered) {
14920
+ console.error(
14921
+ chalk14.red("This repo isn't set up with RepoWise yet. Run `repowise create` first.")
14922
+ );
14923
+ process.exitCode = 1;
14924
+ return null;
14925
+ }
14926
+ return {
14927
+ repoRoot,
14928
+ repoName: detectRepoName(repoRoot),
14929
+ current: config2.aiTools ?? [],
14930
+ contextFolder: config2.contextFolder ?? DEFAULT_CONTEXT_FOLDER4,
14931
+ variant: config2.graphOnly ? "graph" : "full"
14932
+ };
14933
+ }
14934
+ async function applyToolSelection(ctx, next) {
14935
+ const added = next.filter((t) => !ctx.current.includes(t));
14936
+ const spinner = ora6("Configuring AI tools...").start();
14937
+ const contextFiles = await scanLocalContextFiles(ctx.repoRoot, ctx.contextFolder);
14938
+ const results = await writeToolConfigsForRepo({
14939
+ repoRoot: ctx.repoRoot,
14940
+ tools: next,
14941
+ repoName: ctx.repoName,
14942
+ contextFolder: ctx.contextFolder,
14943
+ contextFiles,
14944
+ variant: ctx.variant,
14945
+ migrateLegacy: true
14946
+ });
14947
+ await mergeAndSaveConfig({ aiTools: next });
14948
+ spinner.succeed("AI tools configured");
14949
+ console.log(chalk14.dim(results.map((r) => ` ${r}`).join("\n")));
14950
+ try {
14951
+ await ensureListenerRunning();
14952
+ } catch {
14953
+ }
14954
+ const serverRepoName = basename6(ctx.repoRoot);
14955
+ const newMcp = added.filter((t) => MCP_WRITER_TOOLS.has(t));
14956
+ if (newMcp.length > 0) {
14957
+ console.log("");
14958
+ console.log(
14959
+ chalk14.dim(
14960
+ " The listener will wire these into your AI tools' MCP within a few seconds, then:"
14961
+ )
14962
+ );
14963
+ for (const t of newMcp) {
14964
+ const hint = mcpActivationHint(t, serverRepoName);
14965
+ if (hint) console.log(chalk14.dim(` \u2022 ${hint}`));
14966
+ }
14967
+ }
14968
+ }
14969
+ async function pickUnion(ctx) {
14970
+ const { tools: picked, hasOther } = await selectAiTools({ defaults: ctx.current });
14971
+ const removed = ctx.current.filter((t) => !picked.includes(t));
14972
+ if (removed.length > 0) {
14973
+ console.log(
14974
+ chalk14.yellow(`Note: removing tools isn't supported yet \u2014 keeping ${removed.join(", ")}.`)
14975
+ );
14976
+ }
14977
+ return { next: Array.from(/* @__PURE__ */ new Set([...ctx.current, ...picked])), hasOther };
14978
+ }
14979
+ function reportNothingNew(hasOther) {
14980
+ if (hasOther) {
14981
+ console.log(
14982
+ chalk14.dim(
14983
+ "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"
14984
+ )
14985
+ );
14986
+ } else {
14987
+ console.log(chalk14.dim("No new tools added."));
14988
+ }
14989
+ }
14990
+ async function toolsPick() {
14991
+ const ctx = await resolveRepoOrExit();
14992
+ if (!ctx) return;
14993
+ const { next, hasOther } = await pickUnion(ctx);
14994
+ if (next.length === ctx.current.length) {
14995
+ reportNothingNew(hasOther);
14996
+ return;
14997
+ }
14998
+ await applyToolSelection(ctx, next);
14999
+ }
15000
+ async function toolsAdd(list) {
15001
+ const ctx = await resolveRepoOrExit();
15002
+ if (!ctx) return;
15003
+ let next;
15004
+ let hasOther = false;
15005
+ if (list.length === 0) {
15006
+ ({ next, hasOther } = await pickUnion(ctx));
15007
+ } else {
15008
+ const invalid = list.filter((t) => !VALID_TOOLS.includes(t));
15009
+ if (invalid.length > 0) {
15010
+ console.error(chalk14.red(`Unknown tool(s): ${invalid.join(", ")}`));
15011
+ console.error(chalk14.dim(`Valid tools: ${VALID_TOOLS.join(", ")}`));
15012
+ process.exitCode = 1;
15013
+ return;
15014
+ }
15015
+ next = Array.from(/* @__PURE__ */ new Set([...ctx.current, ...list]));
15016
+ }
15017
+ if (next.length === ctx.current.length) {
15018
+ if (list.length === 0) reportNothingNew(hasOther);
15019
+ else console.log(chalk14.dim("Nothing new to add \u2014 those tools are already configured."));
15020
+ return;
15021
+ }
15022
+ await applyToolSelection(ctx, next);
15023
+ }
15024
+ async function toolsList() {
15025
+ const ctx = await resolveRepoOrExit();
15026
+ if (!ctx) return;
15027
+ if (ctx.current.length === 0) {
15028
+ console.log("No AI tools configured. Run `repowise tools` to add some.");
15029
+ return;
15030
+ }
15031
+ console.log(chalk14.bold("Configured AI tools:"));
15032
+ for (const t of ctx.current) {
15033
+ const label = AI_TOOL_CONFIG[t]?.label ?? t;
15034
+ const suffix = MCP_WRITER_TOOLS.has(t) ? "" : chalk14.dim(" (instruction files only)");
15035
+ console.log(` \u2022 ${label}${suffix}`);
15036
+ }
15037
+ }
15038
+
14724
15039
  // src/commands/mcp-log.ts
14725
15040
  import { createDecipheriv as createDecipheriv2 } from "crypto";
14726
15041
  import { mkdir as mkdir19, readFile as readFile17, stat as stat7, writeFile as writeFile18 } from "fs/promises";
14727
- import { dirname as dirname22, join as join54 } from "path";
15042
+ import { dirname as dirname22, join as join55 } from "path";
14728
15043
  var FLAG_FILE = "mcp-log.flag";
14729
15044
  var LOG_FILE = "mcp-log.jsonl.enc";
14730
15045
  var KEY_FILE = "mcp-log.key";
@@ -14732,10 +15047,10 @@ var ENDPOINT_FILE = "listener.endpoint";
14732
15047
  var IV_BYTES2 = 12;
14733
15048
  var TAG_BYTES2 = 16;
14734
15049
  function flagPath() {
14735
- return join54(getConfigDir2(), FLAG_FILE);
15050
+ return join55(getConfigDir2(), FLAG_FILE);
14736
15051
  }
14737
15052
  function logPath() {
14738
- return join54(getConfigDir2(), LOG_FILE);
15053
+ return join55(getConfigDir2(), LOG_FILE);
14739
15054
  }
14740
15055
  async function writeFlag(flag) {
14741
15056
  const path = flagPath();
@@ -14776,14 +15091,14 @@ async function trySendConsentToServer() {
14776
15091
  let apiUrl = null;
14777
15092
  let token = null;
14778
15093
  try {
14779
- const body = await readFile17(join54(getConfigDir2(), "config.json"), "utf-8");
15094
+ const body = await readFile17(join55(getConfigDir2(), "config.json"), "utf-8");
14780
15095
  const parsed = JSON.parse(body);
14781
15096
  apiUrl = parsed.repos?.find((r) => Boolean(r.apiUrl))?.apiUrl ?? parsed.defaultApiUrl ?? null;
14782
15097
  } catch {
14783
15098
  return false;
14784
15099
  }
14785
15100
  try {
14786
- const body = await readFile17(join54(getConfigDir2(), "credentials.json"), "utf-8");
15101
+ const body = await readFile17(join55(getConfigDir2(), "credentials.json"), "utf-8");
14787
15102
  const parsed = JSON.parse(body);
14788
15103
  token = parsed.idToken ?? null;
14789
15104
  } catch {
@@ -14853,7 +15168,7 @@ async function mcpLogStatus() {
14853
15168
  process.stderr.write("Log size: no file yet\n");
14854
15169
  }
14855
15170
  try {
14856
- const endpointBody = await readFile17(join54(getConfigDir2(), ENDPOINT_FILE), "utf-8");
15171
+ const endpointBody = await readFile17(join55(getConfigDir2(), ENDPOINT_FILE), "utf-8");
14857
15172
  const match = /endpoint=([^\n]+)/.exec(endpointBody);
14858
15173
  process.stderr.write(`MCP endpoint: ${match?.[1] ?? "(malformed endpoint file)"}
14859
15174
  `);
@@ -14884,7 +15199,7 @@ async function mcpLogViewingFlags(flags = {}) {
14884
15199
  const key = await readKey();
14885
15200
  if (!key) {
14886
15201
  process.stderr.write(
14887
- `No encryption key at ${join54(getConfigDir2(), KEY_FILE)} \u2014 listener may not have started yet.
15202
+ `No encryption key at ${join55(getConfigDir2(), KEY_FILE)} \u2014 listener may not have started yet.
14888
15203
  `
14889
15204
  );
14890
15205
  return;
@@ -14960,7 +15275,7 @@ async function mcpLogViewingFlags(flags = {}) {
14960
15275
  }
14961
15276
  async function readKey() {
14962
15277
  try {
14963
- const body = await readFile17(join54(getConfigDir2(), KEY_FILE), "utf-8");
15278
+ const body = await readFile17(join55(getConfigDir2(), KEY_FILE), "utf-8");
14964
15279
  const parsed = Buffer.from(body.trim(), "base64");
14965
15280
  if (parsed.length !== 32) return null;
14966
15281
  return parsed;
@@ -15000,11 +15315,11 @@ async function mcpLog(subcommand, flags = {}) {
15000
15315
  }
15001
15316
 
15002
15317
  // src/commands/query/_shared.ts
15003
- import chalk14 from "chalk";
15318
+ import chalk15 from "chalk";
15004
15319
 
15005
15320
  // src/lib/graph-loader.ts
15006
- import { promises as fs24 } from "fs";
15007
- import { join as join55, resolve as resolve2 } from "path";
15321
+ import { promises as fs25 } from "fs";
15322
+ import { join as join56, resolve as resolve2 } from "path";
15008
15323
  import { gunzipSync } from "zlib";
15009
15324
  var RELATIVE_GRAPH_PATH = "repowise-context/.meta/dependency-graph.json";
15010
15325
  var GZIPPED_GRAPH_PATH = "repowise-context/.meta/dependency-graph.json.gz";
@@ -15021,8 +15336,8 @@ var GraphNotFoundError = class extends Error {
15021
15336
  var cache = /* @__PURE__ */ new Map();
15022
15337
  async function loadGraph(repoRoot = process.cwd()) {
15023
15338
  const root = resolve2(repoRoot);
15024
- const gzPath = join55(root, GZIPPED_GRAPH_PATH);
15025
- const plainPath = join55(root, RELATIVE_GRAPH_PATH);
15339
+ const gzPath = join56(root, GZIPPED_GRAPH_PATH);
15340
+ const plainPath = join56(root, RELATIVE_GRAPH_PATH);
15026
15341
  const cached = cache.get(root);
15027
15342
  if (cached) {
15028
15343
  return { graph: cached, path: plainPath, bytes: 0, parseMs: 0, fromCache: true };
@@ -15030,14 +15345,14 @@ async function loadGraph(repoRoot = process.cwd()) {
15030
15345
  let graphPath = null;
15031
15346
  let raw = null;
15032
15347
  try {
15033
- raw = await fs24.readFile(gzPath);
15348
+ raw = await fs25.readFile(gzPath);
15034
15349
  graphPath = gzPath;
15035
15350
  } catch (err) {
15036
15351
  if (err.code !== "ENOENT") throw err;
15037
15352
  }
15038
15353
  if (!raw) {
15039
15354
  try {
15040
- raw = await fs24.readFile(plainPath);
15355
+ raw = await fs25.readFile(plainPath);
15041
15356
  graphPath = plainPath;
15042
15357
  } catch (err) {
15043
15358
  if (err.code === "ENOENT") {
@@ -15240,7 +15555,7 @@ async function loadService() {
15240
15555
  return createGraphQueryService(graph);
15241
15556
  } catch (err) {
15242
15557
  if (err instanceof GraphNotFoundError) {
15243
- process.stderr.write(chalk14.red(`\u2717 ${err.message}
15558
+ process.stderr.write(chalk15.red(`\u2717 ${err.message}
15244
15559
  `));
15245
15560
  process.exit(err.exitCode);
15246
15561
  }
@@ -15436,14 +15751,14 @@ function registerQueryCommand(program2) {
15436
15751
  }
15437
15752
 
15438
15753
  // src/commands/uninstall.ts
15439
- import { promises as fs28 } from "fs";
15440
- import { homedir as homedir10 } from "os";
15441
- import { join as join59 } from "path";
15442
- import chalk15 from "chalk";
15754
+ import { promises as fs29 } from "fs";
15755
+ import { homedir as homedir12 } from "os";
15756
+ import { join as join60 } from "path";
15757
+ import chalk16 from "chalk";
15443
15758
 
15444
15759
  // src/lib/cleanup/marker-blocks.ts
15445
- import { promises as fs25 } from "fs";
15446
- import { join as join56 } from "path";
15760
+ import { promises as fs26 } from "fs";
15761
+ import { join as join57 } from "path";
15447
15762
  var MARKER_START = "<!-- repowise-start -->";
15448
15763
  var MARKER_END = "<!-- repowise-end -->";
15449
15764
  var CONTEXT_FILES = [
@@ -15459,7 +15774,7 @@ var CONTEXT_FILES = [
15459
15774
  async function stripMarkerBlock(filePath) {
15460
15775
  let raw;
15461
15776
  try {
15462
- raw = await fs25.readFile(filePath, "utf-8");
15777
+ raw = await fs26.readFile(filePath, "utf-8");
15463
15778
  } catch (err) {
15464
15779
  if (err.code === "ENOENT")
15465
15780
  return { path: filePath, status: "missing" };
@@ -15474,16 +15789,16 @@ async function stripMarkerBlock(filePath) {
15474
15789
  const after = raw.slice(endIdx + MARKER_END.length).replace(/^\n+/, "");
15475
15790
  const stripped = (before + (before && after ? "\n\n" : "") + after).trim();
15476
15791
  if (stripped.length === 0) {
15477
- await fs25.unlink(filePath);
15792
+ await fs26.unlink(filePath);
15478
15793
  return { path: filePath, status: "deleted" };
15479
15794
  }
15480
- await fs25.writeFile(filePath, stripped + "\n", "utf-8");
15795
+ await fs26.writeFile(filePath, stripped + "\n", "utf-8");
15481
15796
  return { path: filePath, status: "stripped" };
15482
15797
  }
15483
15798
  async function stripAllMarkerBlocks(repoRoot) {
15484
15799
  const out = [];
15485
15800
  for (const relative of CONTEXT_FILES) {
15486
- const full = join56(repoRoot, relative);
15801
+ const full = join57(repoRoot, relative);
15487
15802
  const result = await stripMarkerBlock(full).catch((err) => ({
15488
15803
  path: full,
15489
15804
  status: "untouched",
@@ -15495,25 +15810,25 @@ async function stripAllMarkerBlocks(repoRoot) {
15495
15810
  }
15496
15811
 
15497
15812
  // src/lib/cleanup/mcp-configs.ts
15498
- import { promises as fs26 } from "fs";
15499
- import { join as join57 } from "path";
15813
+ import { promises as fs27 } from "fs";
15814
+ import { join as join58 } from "path";
15500
15815
  function mcpConfigPaths(repoRoot, home) {
15501
15816
  return [
15502
- join57(repoRoot, ".mcp.json"),
15503
- join57(repoRoot, ".cursor", "mcp.json"),
15504
- join57(repoRoot, ".vscode", "mcp.json"),
15505
- join57(repoRoot, ".roo", "mcp.json"),
15506
- join57(home, ".cline", "mcp.json"),
15507
- join57(home, ".codeium", "windsurf", "mcp_config.json"),
15508
- join57(home, ".gemini", "settings.json"),
15509
- join57(home, ".codex", "mcp.json"),
15510
- join57(home, ".roo", "mcp.json")
15817
+ join58(repoRoot, ".mcp.json"),
15818
+ join58(repoRoot, ".cursor", "mcp.json"),
15819
+ join58(repoRoot, ".vscode", "mcp.json"),
15820
+ join58(repoRoot, ".roo", "mcp.json"),
15821
+ join58(home, ".cline", "mcp.json"),
15822
+ join58(home, ".codeium", "windsurf", "mcp_config.json"),
15823
+ join58(home, ".gemini", "settings.json"),
15824
+ join58(home, ".codex", "mcp.json"),
15825
+ join58(home, ".roo", "mcp.json")
15511
15826
  ];
15512
15827
  }
15513
15828
  async function removeRepowiseFromConfig(path, serverName) {
15514
15829
  let raw;
15515
15830
  try {
15516
- raw = await fs26.readFile(path, "utf-8");
15831
+ raw = await fs27.readFile(path, "utf-8");
15517
15832
  } catch (err) {
15518
15833
  if (err.code === "ENOENT") return { path, status: "not-found" };
15519
15834
  return { path, status: "error", error: err.message };
@@ -15533,7 +15848,7 @@ async function removeRepowiseFromConfig(path, serverName) {
15533
15848
  } else {
15534
15849
  next.mcpServers = servers;
15535
15850
  }
15536
- await fs26.writeFile(path, JSON.stringify(next, null, 2) + "\n", "utf-8");
15851
+ await fs27.writeFile(path, JSON.stringify(next, null, 2) + "\n", "utf-8");
15537
15852
  return { path, status: "removed" };
15538
15853
  }
15539
15854
  async function removeAllMcpEntries(repoRoot, home, repoId) {
@@ -15546,17 +15861,17 @@ async function removeAllMcpEntries(repoRoot, home, repoId) {
15546
15861
  }
15547
15862
 
15548
15863
  // src/lib/cleanup/local-state.ts
15549
- import { promises as fs27 } from "fs";
15550
- import { homedir as homedir9 } from "os";
15551
- import { join as join58, resolve as resolve3 } from "path";
15864
+ import { promises as fs28 } from "fs";
15865
+ import { homedir as homedir11 } from "os";
15866
+ import { join as join59, resolve as resolve3 } from "path";
15552
15867
  async function clearLocalState(homeOverride) {
15553
- const home = homeOverride ?? homedir9();
15554
- const target = resolve3(join58(home, ".repowise"));
15868
+ const home = homeOverride ?? homedir11();
15869
+ const target = resolve3(join59(home, ".repowise"));
15555
15870
  if (target === resolve3(home) || !target.startsWith(resolve3(home))) {
15556
15871
  return { path: target, status: "error", error: "refused: not under home" };
15557
15872
  }
15558
15873
  try {
15559
- await fs27.rm(target, { recursive: true, force: false });
15874
+ await fs28.rm(target, { recursive: true, force: false });
15560
15875
  return { path: target, status: "removed" };
15561
15876
  } catch (err) {
15562
15877
  if (err.code === "ENOENT")
@@ -15586,7 +15901,7 @@ async function stopAndUninstallService(uninstaller) {
15586
15901
  // src/commands/uninstall.ts
15587
15902
  async function uninstall2(opts = {}) {
15588
15903
  const tier = opts.tier ?? "uninstall";
15589
- const home = opts.home ?? homedir10();
15904
+ const home = opts.home ?? homedir12();
15590
15905
  const repoRoot = opts.repoRoot ?? process.cwd();
15591
15906
  const loadRepoIds = opts.loadRepoIds ?? defaultLoadRepoIds;
15592
15907
  const report = { tier, removed: [], preserved: [], skipped: [] };
@@ -15595,7 +15910,7 @@ async function uninstall2(opts = {}) {
15595
15910
  else if (svc.error) report.skipped.push({ path: "listener service", reason: svc.error });
15596
15911
  if (tier === "stop") return report;
15597
15912
  try {
15598
- await fs28.unlink(join59(home, ".repowise", "credentials.json"));
15913
+ await fs29.unlink(join60(home, ".repowise", "credentials.json"));
15599
15914
  report.removed.push("credentials");
15600
15915
  } catch (err) {
15601
15916
  if (err.code !== "ENOENT") {
@@ -15622,7 +15937,7 @@ async function uninstall2(opts = {}) {
15622
15937
  const allPaths = mcpConfigPaths(repoRoot, home);
15623
15938
  for (const p of allPaths) {
15624
15939
  try {
15625
- await fs28.access(p);
15940
+ await fs29.access(p);
15626
15941
  } catch {
15627
15942
  }
15628
15943
  }
@@ -15634,7 +15949,7 @@ async function uninstall2(opts = {}) {
15634
15949
  }
15635
15950
  async function defaultLoadRepoIds(home) {
15636
15951
  try {
15637
- const raw = await fs28.readFile(join59(home, ".repowise", "config.json"), "utf-8");
15952
+ const raw = await fs29.readFile(join60(home, ".repowise", "config.json"), "utf-8");
15638
15953
  const parsed = JSON.parse(raw);
15639
15954
  return (parsed.repos ?? []).map((r) => r.repoId);
15640
15955
  } catch {
@@ -15651,29 +15966,29 @@ async function uninstallCommand(opts = {}) {
15651
15966
  default: false
15652
15967
  });
15653
15968
  if (!proceed) {
15654
- process.stderr.write(chalk15.yellow("Uninstall cancelled.\n"));
15969
+ process.stderr.write(chalk16.yellow("Uninstall cancelled.\n"));
15655
15970
  process.exitCode = 1;
15656
15971
  return;
15657
15972
  }
15658
15973
  }
15659
15974
  }
15660
- process.stderr.write(chalk15.bold(`RepoWise ${tier}
15975
+ process.stderr.write(chalk16.bold(`RepoWise ${tier}
15661
15976
  `));
15662
15977
  const report = await uninstall2({ ...opts, tier });
15663
15978
  for (const p of report.removed) {
15664
- process.stderr.write(chalk15.green(` \u2713 removed: ${p}
15979
+ process.stderr.write(chalk16.green(` \u2713 removed: ${p}
15665
15980
  `));
15666
15981
  }
15667
15982
  for (const p of report.preserved) {
15668
- process.stderr.write(chalk15.gray(` \xB7 preserved: ${p}
15983
+ process.stderr.write(chalk16.gray(` \xB7 preserved: ${p}
15669
15984
  `));
15670
15985
  }
15671
15986
  for (const s of report.skipped) {
15672
- process.stderr.write(chalk15.yellow(` ! skipped: ${s.path} (${s.reason})
15987
+ process.stderr.write(chalk16.yellow(` ! skipped: ${s.path} (${s.reason})
15673
15988
  `));
15674
15989
  }
15675
15990
  process.stderr.write(
15676
- chalk15.bold(`
15991
+ chalk16.bold(`
15677
15992
  Done \u2014 ${report.removed.length} removed, ${report.skipped.length} skipped.
15678
15993
  `)
15679
15994
  );
@@ -15681,12 +15996,13 @@ Done \u2014 ${report.removed.length} removed, ${report.skipped.length} skipped.
15681
15996
 
15682
15997
  // src/commands/mcp-shim.ts
15683
15998
  init_config_dir();
15684
- import { promises as fs29 } from "fs";
15999
+ import { promises as fs30 } from "fs";
15685
16000
  import { createInterface as createInterface2 } from "readline";
15686
- import { join as join60 } from "path";
16001
+ import { join as join61 } from "path";
15687
16002
  var DEFAULT_MAX = 200 * 1024;
15688
16003
  async function mcpShim(opts) {
15689
- const endpointPath = opts.endpointFile ?? join60(getConfigDir(), "listener.endpoint");
16004
+ const endpointPath = opts.endpointFile ?? join61(getConfigDir(), "listener.endpoint");
16005
+ const credsPath = opts.credentialsFile ?? join61(getConfigDir(), "credentials.json");
15690
16006
  const stdin = opts.stdin ?? process.stdin;
15691
16007
  const stdout = opts.stdout ?? process.stdout;
15692
16008
  const stderr = opts.stderr ?? process.stderr;
@@ -15721,12 +16037,16 @@ async function mcpShim(opts) {
15721
16037
  }
15722
16038
  const info = await readEndpoint(endpointPath);
15723
16039
  if (!info) {
16040
+ const authState = await readAuthState(credsPath);
16041
+ const message = authState === "logged-out" ? (
16042
+ // Agent-directed: the calling LLM can run this itself for a smoother UX
16043
+ // (it auto-opens the browser; the user still completes sign-in). The
16044
+ // command is a fixed, argument-free string (nothing injectable).
16045
+ "RepoWise isn't authenticated (the session expires ~monthly). 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."
16046
+ ) : "listener endpoint unavailable \u2014 is the listener running?";
15724
16047
  writeJson(stdout, {
15725
16048
  jsonrpc: "2.0",
15726
- error: {
15727
- code: -32e3,
15728
- message: "listener endpoint unavailable \u2014 is the listener running?"
15729
- }
16049
+ error: { code: -32e3, message }
15730
16050
  });
15731
16051
  continue;
15732
16052
  }
@@ -15759,7 +16079,7 @@ async function mcpShim(opts) {
15759
16079
  }
15760
16080
  async function readEndpoint(path) {
15761
16081
  try {
15762
- const raw = (await fs29.readFile(path, "utf-8")).trim();
16082
+ const raw = (await fs30.readFile(path, "utf-8")).trim();
15763
16083
  if (!raw) return null;
15764
16084
  if (raw.startsWith("http://") || raw.startsWith("https://")) {
15765
16085
  return { endpoint: raw.split("\n")[0].trim(), secret: null };
@@ -15777,6 +16097,22 @@ async function readEndpoint(path) {
15777
16097
  throw err;
15778
16098
  }
15779
16099
  }
16100
+ async function readAuthState(credsPath) {
16101
+ let raw;
16102
+ try {
16103
+ raw = await fs30.readFile(credsPath, "utf-8");
16104
+ } catch (err) {
16105
+ if (err.code === "ENOENT") return "logged-out";
16106
+ return "unknown";
16107
+ }
16108
+ let creds;
16109
+ try {
16110
+ creds = JSON.parse(raw);
16111
+ } catch {
16112
+ return "unknown";
16113
+ }
16114
+ return isCredsHardExpired(creds) ? "logged-out" : "authenticated";
16115
+ }
15780
16116
  function writeJson(stream, value) {
15781
16117
  stream.write(`${JSON.stringify(value)}
15782
16118
  `);
@@ -16001,7 +16337,7 @@ init_coursier_installer();
16001
16337
  init_toolchain_installer();
16002
16338
  import { spawn as spawn12 } from "child_process";
16003
16339
  import { resolve as resolve4 } from "path";
16004
- import chalk16 from "chalk";
16340
+ import chalk17 from "chalk";
16005
16341
  async function isOnPath(command) {
16006
16342
  if (/[^\w./+-]/.test(command)) return false;
16007
16343
  const isWin = process.platform === "win32";
@@ -16019,14 +16355,14 @@ async function isOnPath(command) {
16019
16355
  async function lspDoctor() {
16020
16356
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16021
16357
  if (noColor) {
16022
- chalk16.level = 0;
16358
+ chalk17.level = 0;
16023
16359
  }
16024
- const okGlyph = noColor ? "[OK]" : chalk16.green("\u2713");
16025
- const missingGlyph = noColor ? "[MISSING]" : chalk16.yellow("\u2717");
16026
- console.log(chalk16.bold("RepoWise LSP doctor"));
16027
- console.log(chalk16.dim("Probing PATH for language servers used by the listener."));
16360
+ const okGlyph = noColor ? "[OK]" : chalk17.green("\u2713");
16361
+ const missingGlyph = noColor ? "[MISSING]" : chalk17.yellow("\u2717");
16362
+ console.log(chalk17.bold("RepoWise LSP doctor"));
16363
+ console.log(chalk17.dim("Probing PATH for language servers used by the listener."));
16028
16364
  console.log(
16029
- chalk16.dim(
16365
+ chalk17.dim(
16030
16366
  "Type-aware resolution covers 9 languages: Go, Python, Rust, Java, Ruby, Dart, Kotlin, PHP, Scala (Phase 7L + 7L++ + 7L+)."
16031
16367
  )
16032
16368
  );
@@ -16058,21 +16394,21 @@ async function lspDoctor() {
16058
16394
  const missing = reports.filter((r) => r.status === "missing").length;
16059
16395
  for (const r of reports) {
16060
16396
  const head = r.status === "found" ? okGlyph : missingGlyph;
16061
- const cmd = r.command ? chalk16.cyan(r.command) : chalk16.dim(r.candidates.join(" / "));
16397
+ const cmd = r.command ? chalk17.cyan(r.command) : chalk17.dim(r.candidates.join(" / "));
16062
16398
  const effectiveConfigs = getEffectiveConfigList(r.language, process.env, lspOverrides);
16063
16399
  const method = describeInstallMethod(effectiveConfigs);
16064
- console.log(` ${head} ${r.language.padEnd(12)} ${cmd} ${chalk16.dim(`[${method}]`)}`);
16400
+ console.log(` ${head} ${r.language.padEnd(12)} ${cmd} ${chalk17.dim(`[${method}]`)}`);
16065
16401
  if (r.status === "missing" && r.hint) {
16066
- console.log(` ${chalk16.dim("install:")} ${r.hint}`);
16402
+ console.log(` ${chalk17.dim("install:")} ${r.hint}`);
16067
16403
  }
16068
16404
  }
16069
16405
  console.log();
16070
16406
  console.log(
16071
- `${chalk16.green(found.toString())} found, ${chalk16.yellow(missing.toString())} missing of ${reports.length.toString()} languages.`
16407
+ `${chalk17.green(found.toString())} found, ${chalk17.yellow(missing.toString())} missing of ${reports.length.toString()} languages.`
16072
16408
  );
16073
16409
  if (missing > 0) {
16074
16410
  console.log(
16075
- chalk16.dim(
16411
+ chalk17.dim(
16076
16412
  "Missing servers degrade LSP-backed MCP tools to AST-only fallback. Run `repowise lsp install` to auto-install."
16077
16413
  )
16078
16414
  );
@@ -16089,15 +16425,15 @@ function describeInstallMethod(configs) {
16089
16425
  }
16090
16426
  async function lspInstall(language) {
16091
16427
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16092
- if (noColor) chalk16.level = 0;
16093
- console.log(chalk16.bold("RepoWise LSP install"));
16428
+ if (noColor) chalk17.level = 0;
16429
+ console.log(chalk17.bold("RepoWise LSP install"));
16094
16430
  const cliConfig = await getConfig();
16095
16431
  const lspOverrides = cliConfig.lspOverrides;
16096
16432
  const targets = language ? [language] : Object.keys(LSP_REGISTRY);
16097
16433
  for (const lang of targets) {
16098
16434
  const configs = getEffectiveConfigList(lang, process.env, lspOverrides);
16099
16435
  if (!configs || configs.length === 0) {
16100
- console.log(` ${chalk16.red("\u2717")} ${lang} ${chalk16.dim("(unknown language)")}`);
16436
+ console.log(` ${chalk17.red("\u2717")} ${lang} ${chalk17.dim("(unknown language)")}`);
16101
16437
  continue;
16102
16438
  }
16103
16439
  const result = await installOneLanguage(configs);
@@ -16165,11 +16501,11 @@ async function installOneLanguage(configs) {
16165
16501
  };
16166
16502
  }
16167
16503
  function formatInstallLine(lang, outcome) {
16168
- const glyph = outcome.ok ? chalk16.green("\u2713") : chalk16.yellow("\u2717");
16169
- const tag = chalk16.dim(`[${outcome.method}]`);
16504
+ const glyph = outcome.ok ? chalk17.green("\u2713") : chalk17.yellow("\u2717");
16505
+ const tag = chalk17.dim(`[${outcome.method}]`);
16170
16506
  console.log(` ${glyph} ${lang.padEnd(12)} ${tag} ${outcome.detail}`);
16171
16507
  if (!outcome.ok && outcome.hint) {
16172
- console.log(` ${chalk16.dim("install:")} ${outcome.hint}`);
16508
+ console.log(` ${chalk17.dim("install:")} ${outcome.hint}`);
16173
16509
  }
16174
16510
  }
16175
16511
  var MAX_KEEP_WARM_MINUTES = 240;
@@ -16207,42 +16543,42 @@ async function autoDetectLanguages(cwd) {
16207
16543
  async function lspWarm(opts = {}) {
16208
16544
  const cwd = resolve4(opts.cwd ?? process.cwd());
16209
16545
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16210
- if (noColor) chalk16.level = 0;
16546
+ if (noColor) chalk17.level = 0;
16211
16547
  let languages;
16212
16548
  if (opts.lang && opts.lang.length > 0) {
16213
16549
  const valid = Object.keys(LSP_REGISTRY);
16214
16550
  languages = opts.lang.filter((l) => valid.includes(l));
16215
16551
  const invalid = opts.lang.filter((l) => !valid.includes(l));
16216
16552
  if (invalid.length > 0) {
16217
- console.log(chalk16.yellow(`[lsp warm] unknown languages skipped: ${invalid.join(", ")}`));
16553
+ console.log(chalk17.yellow(`[lsp warm] unknown languages skipped: ${invalid.join(", ")}`));
16218
16554
  }
16219
16555
  } else {
16220
16556
  languages = await autoDetectLanguages(cwd);
16221
16557
  if (languages.length === 0) {
16222
16558
  console.log(
16223
- chalk16.yellow(
16559
+ chalk17.yellow(
16224
16560
  "[lsp warm] no recognised source files in this repo \u2014 pass --lang <id> to force a server."
16225
16561
  )
16226
16562
  );
16227
16563
  return;
16228
16564
  }
16229
- console.log(chalk16.dim(`[lsp warm] auto-detected languages: ${languages.join(", ")}`));
16565
+ console.log(chalk17.dim(`[lsp warm] auto-detected languages: ${languages.join(", ")}`));
16230
16566
  }
16231
16567
  let keepWarmMinutes = 0;
16232
16568
  if (typeof opts.keepWarm === "number" && opts.keepWarm > 0) {
16233
16569
  keepWarmMinutes = Math.min(opts.keepWarm, MAX_KEEP_WARM_MINUTES);
16234
16570
  if (opts.keepWarm > MAX_KEEP_WARM_MINUTES) {
16235
16571
  console.log(
16236
- chalk16.yellow(
16572
+ chalk17.yellow(
16237
16573
  `[lsp warm] --keep-warm clipped to ${MAX_KEEP_WARM_MINUTES.toString()} minutes (hard cap per Phase 7L plan).`
16238
16574
  )
16239
16575
  );
16240
16576
  }
16241
16577
  }
16242
- console.log(chalk16.bold("RepoWise LSP warm \u2014 preview"));
16243
- console.log(chalk16.dim(`Workspace: ${cwd}`));
16578
+ console.log(chalk17.bold("RepoWise LSP warm \u2014 preview"));
16579
+ console.log(chalk17.dim(`Workspace: ${cwd}`));
16244
16580
  console.log(
16245
- chalk16.dim(
16581
+ chalk17.dim(
16246
16582
  "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
16583
  )
16248
16584
  );
@@ -16258,23 +16594,23 @@ async function lspWarm(opts = {}) {
16258
16594
  if (onPath) {
16259
16595
  foundCount += 1;
16260
16596
  console.log(
16261
- chalk16.green(` \u2713 ${lang.padEnd(12)} ${config2.displayName} (${config2.command}) on PATH`)
16597
+ chalk17.green(` \u2713 ${lang.padEnd(12)} ${config2.displayName} (${config2.command}) on PATH`)
16262
16598
  );
16263
16599
  } else {
16264
16600
  missingCount += 1;
16265
- console.log(chalk16.yellow(` \u2717 ${lang.padEnd(12)} ${config2.displayName} not on PATH`));
16601
+ console.log(chalk17.yellow(` \u2717 ${lang.padEnd(12)} ${config2.displayName} not on PATH`));
16266
16602
  if (config2.installHint) {
16267
- console.log(chalk16.dim(` install: ${config2.installHint}`));
16603
+ console.log(chalk17.dim(` install: ${config2.installHint}`));
16268
16604
  }
16269
16605
  }
16270
16606
  }
16271
16607
  console.log();
16272
16608
  console.log(
16273
- `${chalk16.green(foundCount.toString())} ready, ${chalk16.yellow(missingCount.toString())} missing.`
16609
+ `${chalk17.green(foundCount.toString())} ready, ${chalk17.yellow(missingCount.toString())} missing.`
16274
16610
  );
16275
16611
  if (keepWarmMinutes > 0) {
16276
16612
  console.log(
16277
- chalk16.dim(
16613
+ chalk17.dim(
16278
16614
  `Holding for ${keepWarmMinutes.toString()} minute(s) so the listener has time to spawn on its next sync.completed; Ctrl-C to exit early.`
16279
16615
  )
16280
16616
  );
@@ -16291,20 +16627,20 @@ async function lspWarm(opts = {}) {
16291
16627
  }
16292
16628
  function lspStatus() {
16293
16629
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16294
- if (noColor) chalk16.level = 0;
16295
- console.log(chalk16.bold("RepoWise LSP status"));
16630
+ if (noColor) chalk17.level = 0;
16631
+ console.log(chalk17.bold("RepoWise LSP status"));
16296
16632
  console.log(
16297
- chalk16.dim(
16633
+ chalk17.dim(
16298
16634
  "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
16635
  )
16300
16636
  );
16301
16637
  console.log();
16302
- console.log(chalk16.dim("See `repowise lsp doctor` for PATH probe + install hints."));
16638
+ console.log(chalk17.dim("See `repowise lsp doctor` for PATH probe + install hints."));
16303
16639
  }
16304
16640
  function lspStop() {
16305
- console.log(chalk16.bold("RepoWise LSP stop \u2014 preview"));
16641
+ console.log(chalk17.bold("RepoWise LSP stop \u2014 preview"));
16306
16642
  console.log(
16307
- chalk16.dim(
16643
+ chalk17.dim(
16308
16644
  "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
16645
  )
16310
16646
  );
@@ -16327,29 +16663,29 @@ var ENABLE_TARGETS = {
16327
16663
  };
16328
16664
  async function lspEnable(target) {
16329
16665
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16330
- if (noColor) chalk16.level = 0;
16666
+ if (noColor) chalk17.level = 0;
16331
16667
  if (!target) {
16332
- console.error(chalk16.red("Usage: repowise lsp enable <target>"));
16333
- console.error(chalk16.dim(` Supported targets: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
16668
+ console.error(chalk17.red("Usage: repowise lsp enable <target>"));
16669
+ console.error(chalk17.dim(` Supported targets: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
16334
16670
  process.exitCode = 1;
16335
16671
  return;
16336
16672
  }
16337
16673
  const spec = ENABLE_TARGETS[target];
16338
16674
  if (!spec) {
16339
- console.error(chalk16.red(`\u2716 Unknown LSP override target: ${target}`));
16340
- console.error(chalk16.dim(` Supported: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
16675
+ console.error(chalk17.red(`\u2716 Unknown LSP override target: ${target}`));
16676
+ console.error(chalk17.dim(` Supported: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
16341
16677
  process.exitCode = 1;
16342
16678
  return;
16343
16679
  }
16344
16680
  const config2 = await getConfig();
16345
16681
  const existing = config2.lspOverrides?.[spec.language];
16346
16682
  if (existing === spec.name) {
16347
- console.log(chalk16.green(`\u2714 ${spec.displayName} is already enabled.`));
16348
- console.log(chalk16.dim(` ${spec.postAcceptTip}`));
16683
+ console.log(chalk17.green(`\u2714 ${spec.displayName} is already enabled.`));
16684
+ console.log(chalk17.dim(` ${spec.postAcceptTip}`));
16349
16685
  return;
16350
16686
  }
16351
16687
  console.log();
16352
- console.log(chalk16.cyan.bold(` \u2500\u2500 Enable ${spec.displayName} \u2500\u2500`));
16688
+ console.log(chalk17.cyan.bold(` \u2500\u2500 Enable ${spec.displayName} \u2500\u2500`));
16353
16689
  for (const line of spec.consentLines) {
16354
16690
  console.log(` ${line}`);
16355
16691
  }
@@ -16361,7 +16697,7 @@ async function lspEnable(target) {
16361
16697
  });
16362
16698
  if (!proceed) {
16363
16699
  console.log(
16364
- chalk16.dim(
16700
+ chalk17.dim(
16365
16701
  ` Cancelled. ${spec.language.toUpperCase()} will continue using ${spec.revertDisplay}.`
16366
16702
  )
16367
16703
  );
@@ -16370,32 +16706,32 @@ async function lspEnable(target) {
16370
16706
  const nextOverrides = { ...config2.lspOverrides ?? {}, [spec.language]: spec.name };
16371
16707
  await mergeAndSaveConfig({ lspOverrides: nextOverrides });
16372
16708
  console.log(
16373
- chalk16.green(
16709
+ chalk17.green(
16374
16710
  ` \u2714 ${spec.displayName} enabled \u2014 listener will use it within ~5 min (reconcile cycle) or on next session restart.`
16375
16711
  )
16376
16712
  );
16377
- console.log(chalk16.dim(` \u2139 ${spec.postAcceptTip}`));
16713
+ console.log(chalk17.dim(` \u2139 ${spec.postAcceptTip}`));
16378
16714
  }
16379
16715
  async function lspDisable(target) {
16380
16716
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16381
- if (noColor) chalk16.level = 0;
16717
+ if (noColor) chalk17.level = 0;
16382
16718
  if (!target) {
16383
- console.error(chalk16.red("Usage: repowise lsp disable <target>"));
16384
- console.error(chalk16.dim(` Supported targets: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
16719
+ console.error(chalk17.red("Usage: repowise lsp disable <target>"));
16720
+ console.error(chalk17.dim(` Supported targets: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
16385
16721
  process.exitCode = 1;
16386
16722
  return;
16387
16723
  }
16388
16724
  const spec = ENABLE_TARGETS[target];
16389
16725
  if (!spec) {
16390
- console.error(chalk16.red(`\u2716 Unknown LSP override target: ${target}`));
16391
- console.error(chalk16.dim(` Supported: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
16726
+ console.error(chalk17.red(`\u2716 Unknown LSP override target: ${target}`));
16727
+ console.error(chalk17.dim(` Supported: ${Object.keys(ENABLE_TARGETS).join(", ")}`));
16392
16728
  process.exitCode = 1;
16393
16729
  return;
16394
16730
  }
16395
16731
  const config2 = await getConfig();
16396
16732
  const current = config2.lspOverrides?.[spec.language];
16397
16733
  if (current !== spec.name) {
16398
- console.log(chalk16.dim(` ${spec.displayName} is not enabled \u2014 nothing to do.`));
16734
+ console.log(chalk17.dim(` ${spec.displayName} is not enabled \u2014 nothing to do.`));
16399
16735
  return;
16400
16736
  }
16401
16737
  const { [spec.language]: _removed, ...rest } = config2.lspOverrides ?? {};
@@ -16405,28 +16741,28 @@ async function lspDisable(target) {
16405
16741
  ...nextOverrides ? { lspOverrides: nextOverrides } : { lspOverrides: void 0 }
16406
16742
  });
16407
16743
  console.log(
16408
- chalk16.green(
16744
+ chalk17.green(
16409
16745
  ` \u2714 ${spec.displayName} disabled \u2014 listener will revert to ${spec.revertDisplay} within ~5 min or on next session restart.`
16410
16746
  )
16411
16747
  );
16412
16748
  }
16413
16749
  async function lspOff() {
16414
16750
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16415
- if (noColor) chalk16.level = 0;
16751
+ if (noColor) chalk17.level = 0;
16416
16752
  await mergeAndSaveConfig({ lspEnabled: false });
16417
16753
  console.log(
16418
- chalk16.green(
16754
+ chalk17.green(
16419
16755
  " \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
16756
  )
16421
16757
  );
16422
- console.log(chalk16.dim(" Re-enable with: repowise lsp on"));
16758
+ console.log(chalk17.dim(" Re-enable with: repowise lsp on"));
16423
16759
  }
16424
16760
  async function lspOn() {
16425
16761
  const noColor = Boolean(process.env.NO_COLOR) || !process.stdout.isTTY;
16426
- if (noColor) chalk16.level = 0;
16762
+ if (noColor) chalk17.level = 0;
16427
16763
  await mergeAndSaveConfig({ lspEnabled: true });
16428
16764
  console.log(
16429
- chalk16.green(
16765
+ chalk17.green(
16430
16766
  " \u2714 LSP subsystem enabled. The listener will install + warm language servers for your repos within ~5 min (reconcile) or on next restart."
16431
16767
  )
16432
16768
  );
@@ -16435,7 +16771,7 @@ async function lspOn() {
16435
16771
  // bin/repowise.ts
16436
16772
  var __filename = fileURLToPath4(import.meta.url);
16437
16773
  var __dirname = dirname23(__filename);
16438
- var pkg = JSON.parse(readFileSync3(join61(__dirname, "..", "..", "package.json"), "utf-8"));
16774
+ var pkg = JSON.parse(readFileSync3(join62(__dirname, "..", "..", "package.json"), "utf-8"));
16439
16775
  var program = new Command();
16440
16776
  program.name(getPackageName()).description("AI-optimized codebase context generator").version(pkg.version).hook("preAction", async () => {
16441
16777
  await showWelcome(pkg.version);
@@ -16511,6 +16847,15 @@ lspCommand.command("off").description("Disable the LSP subsystem entirely (kill-
16511
16847
  lspCommand.command("on").description("Re-enable the LSP subsystem after `lsp off`").action(async () => {
16512
16848
  await lspOn();
16513
16849
  });
16850
+ var toolsCommand = program.command("tools").description("Add RepoWise to your AI tools (Claude Code, Cursor, \u2026) after install").action(async () => {
16851
+ await toolsPick();
16852
+ });
16853
+ toolsCommand.command("add [tools...]").description("Add named AI tools (or open the picker with no args)").action(async (list) => {
16854
+ await toolsAdd(list ?? []);
16855
+ });
16856
+ toolsCommand.command("list").description("List the AI tools RepoWise is configured for").action(async () => {
16857
+ await toolsList();
16858
+ });
16514
16859
  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
16860
  const tier = options.tier ?? "uninstall";
16516
16861
  await uninstallCommand({ tier, yes: options.yes });