ctx7 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import figlet from "figlet";
9
9
  import pc7 from "picocolors";
10
10
  import ora3 from "ora";
11
11
  import { readdir, rm as rm2 } from "fs/promises";
12
- import { join as join7 } from "path";
12
+ import { join as join6 } from "path";
13
13
 
14
14
  // src/utils/parse-input.ts
15
15
  function parseSkillInput(input2) {
@@ -30,6 +30,31 @@ function parseSkillInput(input2) {
30
30
 
31
31
  // src/utils/github.ts
32
32
  import { execSync } from "child_process";
33
+
34
+ // src/utils/skill-name.ts
35
+ import { resolve, dirname, basename } from "path";
36
+ var SAFE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
37
+ function isSafeSkillName(name) {
38
+ if (typeof name !== "string") return false;
39
+ if (name.length === 0 || name.length > 128) return false;
40
+ if (name === "." || name === "..") return false;
41
+ if (name.includes("\0")) return false;
42
+ if (!SAFE_NAME.test(name)) return false;
43
+ return true;
44
+ }
45
+ function assertSkillNameInRoot(skillsRoot, skillName) {
46
+ if (!isSafeSkillName(skillName)) {
47
+ throw new Error(`Unsafe skill name: ${JSON.stringify(skillName)}`);
48
+ }
49
+ const root = resolve(skillsRoot);
50
+ const target = resolve(root, skillName);
51
+ if (dirname(target) !== root || basename(target) !== skillName) {
52
+ throw new Error(`Skill name "${skillName}" escapes the skills root`);
53
+ }
54
+ return target;
55
+ }
56
+
57
+ // src/utils/github.ts
33
58
  var GITHUB_API = "https://api.github.com";
34
59
  var GITHUB_RAW = "https://raw.githubusercontent.com";
35
60
  function parseGitHubUrl(url) {
@@ -85,7 +110,8 @@ function parseSkillFrontmatter(content) {
85
110
  const frontmatter = frontmatterMatch[1];
86
111
  const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
87
112
  if (!nameMatch) return null;
88
- const name = nameMatch[1].trim().replace(/^["']|["']$/g, "").toLowerCase();
113
+ const name = nameMatch[1].trim().replace(/^["']|["']$/g, "");
114
+ if (!isSafeSkillName(name)) return null;
89
115
  let description = "";
90
116
  const multiLineMatch = frontmatter.match(/^description:\s*([|>])-?\s*$/m);
91
117
  if (multiLineMatch) {
@@ -176,7 +202,7 @@ async function listSkillsFromGitHub(project) {
176
202
  async function getSkillFromGitHub(project, skillName) {
177
203
  const result = await listSkillsFromGitHub(project);
178
204
  if (result.status !== "ok") return result;
179
- const skill = result.skills.find((s) => s.name === skillName.toLowerCase());
205
+ const skill = result.skills.find((s) => s.name.toLowerCase() === skillName.toLowerCase());
180
206
  return { ...result, skill };
181
207
  }
182
208
  async function downloadSkillFromGitHub(skill) {
@@ -226,8 +252,8 @@ async function downloadSkillFromGitHub(skill) {
226
252
  // src/constants.ts
227
253
  import { readFileSync } from "fs";
228
254
  import { fileURLToPath } from "url";
229
- import { dirname, join } from "path";
230
- var __dirname = dirname(fileURLToPath(import.meta.url));
255
+ import { dirname as dirname2, join } from "path";
256
+ var __dirname = dirname2(fileURLToPath(import.meta.url));
231
257
  var pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
232
258
  var VERSION = pkg.version;
233
259
  var NAME = pkg.name;
@@ -518,7 +544,7 @@ var log = {
518
544
  import pc3 from "picocolors";
519
545
  import { select, confirm } from "@inquirer/prompts";
520
546
  import { access } from "fs/promises";
521
- import { join as join2, dirname as dirname2 } from "path";
547
+ import { join as join2, dirname as dirname3 } from "path";
522
548
  import { homedir } from "os";
523
549
 
524
550
  // src/utils/prompts.ts
@@ -661,7 +687,7 @@ async function detectVendorSpecificAgents(scope) {
661
687
  const pathMap = scope === "global" ? IDE_GLOBAL_PATHS : IDE_PATHS;
662
688
  const detected = [];
663
689
  for (const ide of VENDOR_SPECIFIC_AGENTS) {
664
- const parentDir = dirname2(pathMap[ide]);
690
+ const parentDir = dirname3(pathMap[ide]);
665
691
  try {
666
692
  await access(join2(baseDir, parentDir));
667
693
  detected.push(ide);
@@ -692,7 +718,7 @@ async function promptForInstallTargets(options, forceUniversal = true) {
692
718
  const detectedVendor = await detectVendorSpecificAgents(scope);
693
719
  let hasUniversalDir = false;
694
720
  try {
695
- await access(join2(baseDir, dirname2(universalPath)));
721
+ await access(join2(baseDir, dirname3(universalPath)));
696
722
  hasUniversalDir = true;
697
723
  } catch {
698
724
  }
@@ -859,21 +885,21 @@ function getTargetDirFromSelection(ide, scope) {
859
885
 
860
886
  // src/utils/installer.ts
861
887
  import { mkdir, writeFile, rm, symlink, lstat } from "fs/promises";
862
- import { join as join3, resolve, dirname as dirname3 } from "path";
863
- async function installSkillFiles(skillName, files, targetDir) {
864
- const skillDir = resolve(targetDir, skillName);
888
+ import { resolve as resolve2, dirname as dirname4 } from "path";
889
+ async function installSkillFiles(skillName, files, skillsRoot) {
890
+ const skillDir = assertSkillNameInRoot(skillsRoot, skillName);
865
891
  for (const file of files) {
866
- const filePath = resolve(skillDir, file.path);
892
+ const filePath = resolve2(skillDir, file.path);
867
893
  if (!filePath.startsWith(skillDir + "/") && !filePath.startsWith(skillDir + "\\") && filePath !== skillDir) {
868
894
  throw new Error(`Skill file path "${file.path}" resolves outside the target directory`);
869
895
  }
870
- const fileDir = dirname3(filePath);
896
+ const fileDir = dirname4(filePath);
871
897
  await mkdir(fileDir, { recursive: true });
872
898
  await writeFile(filePath, file.content);
873
899
  }
874
900
  }
875
- async function symlinkSkill(skillName, sourcePath, targetDir) {
876
- const targetPath = join3(targetDir, skillName);
901
+ async function symlinkSkill(skillName, sourcePath, skillsRoot) {
902
+ const targetPath = assertSkillNameInRoot(skillsRoot, skillName);
877
903
  try {
878
904
  const stats = await lstat(targetPath);
879
905
  if (stats.isSymbolicLink() || stats.isDirectory()) {
@@ -881,7 +907,7 @@ async function symlinkSkill(skillName, sourcePath, targetDir) {
881
907
  }
882
908
  } catch {
883
909
  }
884
- await mkdir(targetDir, { recursive: true });
910
+ await mkdir(skillsRoot, { recursive: true });
885
911
  await symlink(sourcePath, targetPath);
886
912
  }
887
913
 
@@ -900,7 +926,7 @@ function trackEvent(event, data) {
900
926
  import pc6 from "picocolors";
901
927
  import ora2 from "ora";
902
928
  import { mkdir as mkdir2, writeFile as writeFile2, readFile, unlink } from "fs/promises";
903
- import { join as join5 } from "path";
929
+ import { join as join4 } from "path";
904
930
  import { homedir as homedir3 } from "os";
905
931
  import { spawn } from "child_process";
906
932
  import { input, select as select2 } from "@inquirer/prompts";
@@ -997,11 +1023,11 @@ function createCallbackServer(expectedState) {
997
1023
  let resolveResult;
998
1024
  let rejectResult;
999
1025
  let serverInstance = null;
1000
- const portPromise = new Promise((resolve2) => {
1001
- resolvePort = resolve2;
1026
+ const portPromise = new Promise((resolve3) => {
1027
+ resolvePort = resolve3;
1002
1028
  });
1003
- const resultPromise = new Promise((resolve2, reject) => {
1004
- resolveResult = resolve2;
1029
+ const resultPromise = new Promise((resolve3, reject) => {
1030
+ resolveResult = resolve3;
1005
1031
  rejectResult = reject;
1006
1032
  });
1007
1033
  const server = http.createServer((req, res) => {
@@ -1681,19 +1707,19 @@ async function generateCommand(options) {
1681
1707
  log.blank();
1682
1708
  };
1683
1709
  const openInEditor = async () => {
1684
- const previewDir = join5(homedir3(), ".context7", "previews");
1710
+ const previewDir = join4(homedir3(), ".context7", "previews");
1685
1711
  await mkdir2(previewDir, { recursive: true });
1686
- previewFile = join5(previewDir, `${skillName}.md`);
1712
+ previewFile = join4(previewDir, `${skillName}.md`);
1687
1713
  if (!previewFileWritten) {
1688
1714
  await writeFile2(previewFile, generatedContent, "utf-8");
1689
1715
  previewFileWritten = true;
1690
1716
  }
1691
1717
  const editor = process.env.EDITOR || "open";
1692
- await new Promise((resolve2) => {
1718
+ await new Promise((resolve3) => {
1693
1719
  const child = spawn(editor, [previewFile], {
1694
1720
  stdio: "inherit"
1695
1721
  });
1696
- child.on("close", () => resolve2());
1722
+ child.on("close", () => resolve3());
1697
1723
  });
1698
1724
  };
1699
1725
  const syncFromPreviewFile = async () => {
@@ -1702,7 +1728,7 @@ async function generateCommand(options) {
1702
1728
  }
1703
1729
  };
1704
1730
  showPreview();
1705
- await new Promise((resolve2) => setTimeout(resolve2, 100));
1731
+ await new Promise((resolve3) => setTimeout(resolve3, 100));
1706
1732
  try {
1707
1733
  let action;
1708
1734
  while (true) {
@@ -1759,8 +1785,8 @@ async function generateCommand(options) {
1759
1785
  if (options.output && !targetDir.includes("/.config/") && !targetDir.startsWith(homedir3())) {
1760
1786
  finalDir = targetDir.replace(process.cwd(), options.output);
1761
1787
  }
1762
- const skillDir = join5(finalDir, skillName);
1763
- const skillPath = join5(skillDir, "SKILL.md");
1788
+ const skillDir = join4(finalDir, skillName);
1789
+ const skillPath = join4(skillDir, "SKILL.md");
1764
1790
  try {
1765
1791
  await mkdir2(skillDir, { recursive: true });
1766
1792
  await writeFile2(skillPath, generatedContent, "utf-8");
@@ -1779,7 +1805,7 @@ async function generateCommand(options) {
1779
1805
  log.blank();
1780
1806
  console.log(pc6.yellow("Fix permissions with:"));
1781
1807
  for (const dir of failedDirs) {
1782
- const parentDir = join5(dir, "..");
1808
+ const parentDir = join4(dir, "..");
1783
1809
  console.log(pc6.dim(` sudo chown -R $(whoami) "${parentDir}"`));
1784
1810
  }
1785
1811
  log.blank();
@@ -1801,7 +1827,7 @@ import { homedir as homedir4 } from "os";
1801
1827
 
1802
1828
  // src/utils/deps.ts
1803
1829
  import { readFile as readFile2 } from "fs/promises";
1804
- import { join as join6 } from "path";
1830
+ import { join as join5 } from "path";
1805
1831
  async function readFileOrNull(path2) {
1806
1832
  try {
1807
1833
  return await readFile2(path2, "utf-8");
@@ -1813,7 +1839,7 @@ function isSkippedLocally(name) {
1813
1839
  return name.startsWith("@types/");
1814
1840
  }
1815
1841
  async function parsePackageJson(cwd) {
1816
- const content = await readFileOrNull(join6(cwd, "package.json"));
1842
+ const content = await readFileOrNull(join5(cwd, "package.json"));
1817
1843
  if (!content) return [];
1818
1844
  try {
1819
1845
  const pkg2 = JSON.parse(content);
@@ -1830,7 +1856,7 @@ async function parsePackageJson(cwd) {
1830
1856
  }
1831
1857
  }
1832
1858
  async function parseRequirementsTxt(cwd) {
1833
- const content = await readFileOrNull(join6(cwd, "requirements.txt"));
1859
+ const content = await readFileOrNull(join5(cwd, "requirements.txt"));
1834
1860
  if (!content) return [];
1835
1861
  const deps = [];
1836
1862
  for (const line of content.split("\n")) {
@@ -1844,7 +1870,7 @@ async function parseRequirementsTxt(cwd) {
1844
1870
  return deps;
1845
1871
  }
1846
1872
  async function parsePyprojectToml(cwd) {
1847
- const content = await readFileOrNull(join6(cwd, "pyproject.toml"));
1873
+ const content = await readFileOrNull(join5(cwd, "pyproject.toml"));
1848
1874
  if (!content) return [];
1849
1875
  const deps = [];
1850
1876
  const seen = /* @__PURE__ */ new Set();
@@ -2106,7 +2132,7 @@ ${headerLine}`,
2106
2132
  }
2107
2133
  throw dirErr;
2108
2134
  }
2109
- const primarySkillDir = join7(primaryDir, skill.name);
2135
+ const primarySkillDir = join6(primaryDir, skill.name);
2110
2136
  for (const targetDir of symlinkDirs) {
2111
2137
  try {
2112
2138
  await symlinkSkill(skill.name, primarySkillDir, targetDir);
@@ -2134,7 +2160,7 @@ ${headerLine}`,
2134
2160
  log.blank();
2135
2161
  log.warn("Fix permissions with:");
2136
2162
  for (const dir of failedDirs) {
2137
- const parentDir = join7(dir, "..");
2163
+ const parentDir = join6(dir, "..");
2138
2164
  log.dim(` sudo chown -R $(whoami) "${parentDir}"`);
2139
2165
  }
2140
2166
  log.blank();
@@ -2259,7 +2285,7 @@ ${headerLine}`,
2259
2285
  }
2260
2286
  throw dirErr;
2261
2287
  }
2262
- const primarySkillDir = join7(primaryDir, skill.name);
2288
+ const primarySkillDir = join6(primaryDir, skill.name);
2263
2289
  for (const targetDir of symlinkDirs) {
2264
2290
  try {
2265
2291
  await symlinkSkill(skill.name, primarySkillDir, targetDir);
@@ -2287,7 +2313,7 @@ ${headerLine}`,
2287
2313
  log.blank();
2288
2314
  log.warn("Fix permissions with:");
2289
2315
  for (const dir of failedDirs) {
2290
- const parentDir = join7(dir, "..");
2316
+ const parentDir = join6(dir, "..");
2291
2317
  log.dim(` sudo chown -R $(whoami) "${parentDir}"`);
2292
2318
  }
2293
2319
  log.blank();
@@ -2314,7 +2340,7 @@ async function listCommand(options) {
2314
2340
  if (hasExplicitIdeOption(options)) {
2315
2341
  const ides = getSelectedIdes(options);
2316
2342
  for (const ide of ides) {
2317
- const dir = ide === "universal" ? join7(baseDir, scope === "global" ? UNIVERSAL_SKILLS_GLOBAL_PATH : UNIVERSAL_SKILLS_PATH) : join7(baseDir, (scope === "global" ? IDE_GLOBAL_PATHS : IDE_PATHS)[ide]);
2343
+ const dir = ide === "universal" ? join6(baseDir, scope === "global" ? UNIVERSAL_SKILLS_GLOBAL_PATH : UNIVERSAL_SKILLS_PATH) : join6(baseDir, (scope === "global" ? IDE_GLOBAL_PATHS : IDE_PATHS)[ide]);
2318
2344
  const label = ide === "universal" ? UNIVERSAL_AGENTS_LABEL : IDE_NAMES[ide];
2319
2345
  const skills = await scanDir(dir);
2320
2346
  if (skills.length > 0) {
@@ -2323,14 +2349,14 @@ async function listCommand(options) {
2323
2349
  }
2324
2350
  } else {
2325
2351
  const universalPath = scope === "global" ? UNIVERSAL_SKILLS_GLOBAL_PATH : UNIVERSAL_SKILLS_PATH;
2326
- const universalDir = join7(baseDir, universalPath);
2352
+ const universalDir = join6(baseDir, universalPath);
2327
2353
  const universalSkills = await scanDir(universalDir);
2328
2354
  if (universalSkills.length > 0) {
2329
2355
  results.push({ label: UNIVERSAL_AGENTS_LABEL, path: universalPath, skills: universalSkills });
2330
2356
  }
2331
2357
  for (const ide of VENDOR_SPECIFIC_AGENTS) {
2332
2358
  const pathMap = scope === "global" ? IDE_GLOBAL_PATHS : IDE_PATHS;
2333
- const dir = join7(baseDir, pathMap[ide]);
2359
+ const dir = join6(baseDir, pathMap[ide]);
2334
2360
  const skills = await scanDir(dir);
2335
2361
  if (skills.length > 0) {
2336
2362
  results.push({ label: IDE_NAMES[ide], path: pathMap[ide], skills });
@@ -2358,7 +2384,13 @@ async function removeCommand(name, options) {
2358
2384
  return;
2359
2385
  }
2360
2386
  const skillsDir = getTargetDirFromSelection(target.ide, target.scope);
2361
- const skillPath = join7(skillsDir, name);
2387
+ let skillPath;
2388
+ try {
2389
+ skillPath = assertSkillNameInRoot(skillsDir, name);
2390
+ } catch {
2391
+ log.error(`Invalid skill name: ${name}`);
2392
+ return;
2393
+ }
2362
2394
  try {
2363
2395
  await rm2(skillPath, { recursive: true });
2364
2396
  log.success(`Removed skill: ${name}`);
@@ -2539,7 +2571,7 @@ ${headerLine}`,
2539
2571
  }
2540
2572
  throw dirErr;
2541
2573
  }
2542
- const primarySkillDir = join7(primaryDir, skill.name);
2574
+ const primarySkillDir = join6(primaryDir, skill.name);
2543
2575
  for (const targetDir of symlinkDirs) {
2544
2576
  try {
2545
2577
  await symlinkSkill(skill.name, primarySkillDir, targetDir);
@@ -2567,7 +2599,7 @@ ${headerLine}`,
2567
2599
  log.blank();
2568
2600
  log.warn("Fix permissions with:");
2569
2601
  for (const dir of failedDirs) {
2570
- const parentDir = join7(dir, "..");
2602
+ const parentDir = join6(dir, "..");
2571
2603
  log.dim(` sudo chown -R $(whoami) "${parentDir}"`);
2572
2604
  }
2573
2605
  log.blank();
@@ -2584,12 +2616,12 @@ import pc8 from "picocolors";
2584
2616
  import ora4 from "ora";
2585
2617
  import { select as select3 } from "@inquirer/prompts";
2586
2618
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
2587
- import { dirname as dirname5, join as join9 } from "path";
2619
+ import { dirname as dirname6, join as join8 } from "path";
2588
2620
  import { randomBytes as randomBytes2 } from "crypto";
2589
2621
 
2590
2622
  // src/setup/agents.ts
2591
2623
  import { access as access2 } from "fs/promises";
2592
- import { join as join8 } from "path";
2624
+ import { join as join7 } from "path";
2593
2625
  import { homedir as homedir5 } from "os";
2594
2626
  var SETUP_AGENT_NAMES = {
2595
2627
  claude: "Claude Code",
@@ -2603,6 +2635,26 @@ var AUTH_MODE_LABELS = {
2603
2635
  "api-key": "API Key"
2604
2636
  };
2605
2637
  var MCP_BASE_URL = "https://mcp.context7.com";
2638
+ var STDIO_PACKAGE = "@upstash/context7-mcp";
2639
+ function stdioArgs(auth) {
2640
+ const args = ["-y", STDIO_PACKAGE];
2641
+ if (auth.mode === "api-key" && auth.apiKey) {
2642
+ args.push("--api-key", auth.apiKey);
2643
+ }
2644
+ return args;
2645
+ }
2646
+ function stdioEntry(auth) {
2647
+ return { command: "npx", args: stdioArgs(auth) };
2648
+ }
2649
+ function claudeConfigDir() {
2650
+ return process.env.CLAUDE_CONFIG_DIR || join7(homedir5(), ".claude");
2651
+ }
2652
+ function claudeGlobalMcpPath() {
2653
+ if (process.env.CLAUDE_CONFIG_DIR) {
2654
+ return join7(claudeConfigDir(), ".claude.json");
2655
+ }
2656
+ return join7(homedir5(), ".claude.json");
2657
+ }
2606
2658
  function mcpUrl(auth) {
2607
2659
  return auth.mode === "oauth" ? `${MCP_BASE_URL}/mcp/oauth` : `${MCP_BASE_URL}/mcp`;
2608
2660
  }
@@ -2618,45 +2670,49 @@ var agents = {
2618
2670
  displayName: "Claude Code",
2619
2671
  mcp: {
2620
2672
  projectPaths: [".mcp.json"],
2621
- globalPaths: [join8(homedir5(), ".claude.json")],
2673
+ get globalPaths() {
2674
+ return [claudeGlobalMcpPath()];
2675
+ },
2622
2676
  configKey: "mcpServers",
2623
- buildEntry: (auth) => withHeaders({ type: "http", url: mcpUrl(auth) }, auth)
2677
+ buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ type: "http", url: mcpUrl(auth) }, auth)
2624
2678
  },
2625
2679
  rule: {
2626
2680
  kind: "file",
2627
- dir: (scope) => scope === "global" ? join8(homedir5(), ".claude", "rules") : join8(".claude", "rules"),
2681
+ dir: (scope) => scope === "global" ? join7(claudeConfigDir(), "rules") : join7(".claude", "rules"),
2628
2682
  filename: "context7.md"
2629
2683
  },
2630
2684
  skill: {
2631
2685
  name: "context7-mcp",
2632
- dir: (scope) => scope === "global" ? join8(homedir5(), ".claude", "skills") : join8(".claude", "skills")
2686
+ dir: (scope) => scope === "global" ? join7(claudeConfigDir(), "skills") : join7(".claude", "skills")
2633
2687
  },
2634
2688
  detect: {
2635
2689
  projectPaths: [".mcp.json", ".claude"],
2636
- globalPaths: [join8(homedir5(), ".claude")]
2690
+ get globalPaths() {
2691
+ return [claudeConfigDir()];
2692
+ }
2637
2693
  }
2638
2694
  },
2639
2695
  cursor: {
2640
2696
  name: "cursor",
2641
2697
  displayName: "Cursor",
2642
2698
  mcp: {
2643
- projectPaths: [join8(".cursor", "mcp.json")],
2644
- globalPaths: [join8(homedir5(), ".cursor", "mcp.json")],
2699
+ projectPaths: [join7(".cursor", "mcp.json")],
2700
+ globalPaths: [join7(homedir5(), ".cursor", "mcp.json")],
2645
2701
  configKey: "mcpServers",
2646
- buildEntry: (auth) => withHeaders({ url: mcpUrl(auth) }, auth)
2702
+ buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ url: mcpUrl(auth) }, auth)
2647
2703
  },
2648
2704
  rule: {
2649
2705
  kind: "file",
2650
- dir: (scope) => scope === "global" ? join8(homedir5(), ".cursor", "rules") : join8(".cursor", "rules"),
2706
+ dir: (scope) => scope === "global" ? join7(homedir5(), ".cursor", "rules") : join7(".cursor", "rules"),
2651
2707
  filename: "context7.mdc"
2652
2708
  },
2653
2709
  skill: {
2654
2710
  name: "context7-mcp",
2655
- dir: (scope) => scope === "global" ? join8(homedir5(), ".cursor", "skills") : join8(".cursor", "skills")
2711
+ dir: (scope) => scope === "global" ? join7(homedir5(), ".cursor", "skills") : join7(".cursor", "skills")
2656
2712
  },
2657
2713
  detect: {
2658
2714
  projectPaths: [".cursor"],
2659
- globalPaths: [join8(homedir5(), ".cursor")]
2715
+ globalPaths: [join7(homedir5(), ".cursor")]
2660
2716
  }
2661
2717
  },
2662
2718
  opencode: {
@@ -2665,78 +2721,72 @@ var agents = {
2665
2721
  mcp: {
2666
2722
  projectPaths: ["opencode.json", "opencode.jsonc", ".opencode.json", ".opencode.jsonc"],
2667
2723
  globalPaths: [
2668
- join8(homedir5(), ".config", "opencode", "opencode.json"),
2669
- join8(homedir5(), ".config", "opencode", "opencode.jsonc"),
2670
- join8(homedir5(), ".config", "opencode", ".opencode.json"),
2671
- join8(homedir5(), ".config", "opencode", ".opencode.jsonc")
2724
+ join7(homedir5(), ".config", "opencode", "opencode.json"),
2725
+ join7(homedir5(), ".config", "opencode", "opencode.jsonc"),
2726
+ join7(homedir5(), ".config", "opencode", ".opencode.json"),
2727
+ join7(homedir5(), ".config", "opencode", ".opencode.jsonc")
2672
2728
  ],
2673
2729
  configKey: "mcp",
2674
- buildEntry: (auth) => withHeaders({ type: "remote", url: mcpUrl(auth), enabled: true }, auth)
2730
+ buildEntry: (auth, transport) => transport === "stdio" ? { type: "local", command: ["npx", ...stdioArgs(auth)], enabled: true } : withHeaders({ type: "remote", url: mcpUrl(auth), enabled: true }, auth)
2675
2731
  },
2676
2732
  rule: {
2677
2733
  kind: "append",
2678
- file: (scope) => scope === "global" ? join8(homedir5(), ".config", "opencode", "AGENTS.md") : "AGENTS.md",
2734
+ file: (scope) => scope === "global" ? join7(homedir5(), ".config", "opencode", "AGENTS.md") : "AGENTS.md",
2679
2735
  sectionMarker: "<!-- context7 -->"
2680
2736
  },
2681
2737
  skill: {
2682
2738
  name: "context7-mcp",
2683
- dir: (scope) => scope === "global" ? join8(homedir5(), ".agents", "skills") : join8(".agents", "skills")
2739
+ dir: (scope) => scope === "global" ? join7(homedir5(), ".agents", "skills") : join7(".agents", "skills")
2684
2740
  },
2685
2741
  detect: {
2686
2742
  projectPaths: ["opencode.json", "opencode.jsonc", ".opencode.json", ".opencode.jsonc"],
2687
- globalPaths: [join8(homedir5(), ".config", "opencode")]
2743
+ globalPaths: [join7(homedir5(), ".config", "opencode")]
2688
2744
  }
2689
2745
  },
2690
2746
  codex: {
2691
2747
  name: "codex",
2692
2748
  displayName: "Codex",
2693
2749
  mcp: {
2694
- projectPaths: [join8(".codex", "config.toml")],
2695
- globalPaths: [join8(homedir5(), ".codex", "config.toml")],
2750
+ projectPaths: [join7(".codex", "config.toml")],
2751
+ globalPaths: [join7(homedir5(), ".codex", "config.toml")],
2696
2752
  configKey: "mcp_servers",
2697
- buildEntry: (auth) => {
2698
- const entry = { type: "http", url: mcpUrl(auth) };
2699
- if (auth.mode === "api-key" && auth.apiKey) {
2700
- entry.headers = { CONTEXT7_API_KEY: auth.apiKey };
2701
- }
2702
- return entry;
2703
- }
2753
+ buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ type: "http", url: mcpUrl(auth) }, auth)
2704
2754
  },
2705
2755
  rule: {
2706
2756
  kind: "append",
2707
- file: (scope) => scope === "global" ? join8(homedir5(), ".codex", "AGENTS.md") : "AGENTS.md",
2757
+ file: (scope) => scope === "global" ? join7(homedir5(), ".codex", "AGENTS.md") : "AGENTS.md",
2708
2758
  sectionMarker: "<!-- context7 -->"
2709
2759
  },
2710
2760
  skill: {
2711
2761
  name: "context7-mcp",
2712
- dir: (scope) => scope === "global" ? join8(homedir5(), ".agents", "skills") : join8(".agents", "skills")
2762
+ dir: (scope) => scope === "global" ? join7(homedir5(), ".agents", "skills") : join7(".agents", "skills")
2713
2763
  },
2714
2764
  detect: {
2715
2765
  projectPaths: [".codex"],
2716
- globalPaths: [join8(homedir5(), ".codex")]
2766
+ globalPaths: [join7(homedir5(), ".codex")]
2717
2767
  }
2718
2768
  },
2719
2769
  gemini: {
2720
2770
  name: "gemini",
2721
2771
  displayName: "Gemini CLI",
2722
2772
  mcp: {
2723
- projectPaths: [join8(".gemini", "settings.json")],
2724
- globalPaths: [join8(homedir5(), ".gemini", "settings.json")],
2773
+ projectPaths: [join7(".gemini", "settings.json")],
2774
+ globalPaths: [join7(homedir5(), ".gemini", "settings.json")],
2725
2775
  configKey: "mcpServers",
2726
- buildEntry: (auth) => withHeaders({ httpUrl: mcpUrl(auth) }, auth)
2776
+ buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ httpUrl: mcpUrl(auth) }, auth)
2727
2777
  },
2728
2778
  rule: {
2729
2779
  kind: "append",
2730
- file: (scope) => scope === "global" ? join8(homedir5(), ".gemini", "GEMINI.md") : "GEMINI.md",
2780
+ file: (scope) => scope === "global" ? join7(homedir5(), ".gemini", "GEMINI.md") : "GEMINI.md",
2731
2781
  sectionMarker: "<!-- context7 -->"
2732
2782
  },
2733
2783
  skill: {
2734
2784
  name: "context7-mcp",
2735
- dir: (scope) => scope === "global" ? join8(homedir5(), ".gemini", "skills") : join8(".gemini", "skills")
2785
+ dir: (scope) => scope === "global" ? join7(homedir5(), ".gemini", "skills") : join7(".gemini", "skills")
2736
2786
  },
2737
2787
  detect: {
2738
2788
  projectPaths: [".gemini"],
2739
- globalPaths: [join8(homedir5(), ".gemini")]
2789
+ globalPaths: [join7(homedir5(), ".gemini")]
2740
2790
  }
2741
2791
  }
2742
2792
  };
@@ -2757,7 +2807,7 @@ async function detectAgents(scope) {
2757
2807
  for (const agent of Object.values(agents)) {
2758
2808
  const paths = scope === "global" ? agent.detect.globalPaths : agent.detect.projectPaths;
2759
2809
  for (const p of paths) {
2760
- const fullPath = scope === "global" ? p : join8(process.cwd(), p);
2810
+ const fullPath = scope === "global" ? p : join7(process.cwd(), p);
2761
2811
  if (await pathExists(fullPath)) {
2762
2812
  detected.push(agent.name);
2763
2813
  break;
@@ -2856,7 +2906,7 @@ function customizeSkillFilesForAgent(agent, skillName, files) {
2856
2906
 
2857
2907
  // src/setup/mcp-writer.ts
2858
2908
  import { access as access3, readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
2859
- import { dirname as dirname4 } from "path";
2909
+ import { dirname as dirname5 } from "path";
2860
2910
  function stripJsonComments(text) {
2861
2911
  let result = "";
2862
2912
  let i = 0;
@@ -2935,7 +2985,7 @@ async function resolveMcpPath(candidates) {
2935
2985
  return candidates[0];
2936
2986
  }
2937
2987
  async function writeJsonConfig(filePath, config) {
2938
- await mkdir3(dirname4(filePath), { recursive: true });
2988
+ await mkdir3(dirname5(filePath), { recursive: true });
2939
2989
  await writeFile3(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
2940
2990
  }
2941
2991
  async function readTomlServerExists(filePath, serverName) {
@@ -2946,6 +2996,70 @@ async function readTomlServerExists(filePath, serverName) {
2946
2996
  return false;
2947
2997
  }
2948
2998
  }
2999
+ async function readTomlServerEntry(filePath, serverName) {
3000
+ let raw;
3001
+ try {
3002
+ raw = await readFile3(filePath, "utf-8");
3003
+ } catch {
3004
+ return void 0;
3005
+ }
3006
+ const sectionHeader = `[mcp_servers.${serverName}]`;
3007
+ const startIdx = raw.indexOf(sectionHeader);
3008
+ if (startIdx === -1) return void 0;
3009
+ const rest = raw.slice(startIdx + sectionHeader.length);
3010
+ const nextHeader = /^\[/m.exec(rest);
3011
+ const block = nextHeader ? rest.slice(0, nextHeader.index) : rest;
3012
+ const entry = {};
3013
+ const lineRe = /^([A-Za-z_][\w-]*)\s*=\s*(.+?)\s*$/gm;
3014
+ let lineMatch;
3015
+ while ((lineMatch = lineRe.exec(block)) !== null) {
3016
+ const [, key, valueText] = lineMatch;
3017
+ try {
3018
+ entry[key] = JSON.parse(valueText);
3019
+ } catch {
3020
+ }
3021
+ }
3022
+ return Object.keys(entry).length > 0 ? entry : void 0;
3023
+ }
3024
+ function isStdioContext7Entry(entry) {
3025
+ if (!entry || typeof entry !== "object") return false;
3026
+ const e = entry;
3027
+ const refs = (s) => typeof s === "string" && s.includes(STDIO_PACKAGE);
3028
+ if (Array.isArray(e.command)) {
3029
+ return e.command.some(refs);
3030
+ }
3031
+ if (typeof e.command === "string" && Array.isArray(e.args)) {
3032
+ return e.args.some(refs);
3033
+ }
3034
+ return false;
3035
+ }
3036
+ function getJsonServerEntry(config, configKey, serverName) {
3037
+ const section = config[configKey];
3038
+ if (!section || typeof section !== "object") return void 0;
3039
+ const entry = section[serverName];
3040
+ return entry && typeof entry === "object" ? entry : void 0;
3041
+ }
3042
+ function stripApiKeyPair(args) {
3043
+ const result = [];
3044
+ for (let i = 0; i < args.length; i++) {
3045
+ if (args[i] === "--api-key") {
3046
+ i++;
3047
+ continue;
3048
+ }
3049
+ result.push(args[i]);
3050
+ }
3051
+ return result;
3052
+ }
3053
+ function patchStdioApiKey(entry, apiKey) {
3054
+ if (Array.isArray(entry.command)) {
3055
+ const cmd = stripApiKeyPair(entry.command);
3056
+ if (apiKey) cmd.push("--api-key", apiKey);
3057
+ return { ...entry, command: cmd };
3058
+ }
3059
+ const args = Array.isArray(entry.args) ? stripApiKeyPair(entry.args) : [];
3060
+ if (apiKey) args.push("--api-key", apiKey);
3061
+ return { ...entry, args };
3062
+ }
2949
3063
  function buildTomlServerBlock(serverName, entry) {
2950
3064
  const lines = [`[mcp_servers.${serverName}]`];
2951
3065
  const headers = entry.headers;
@@ -2991,11 +3105,11 @@ async function appendTomlServer(filePath, serverName, entry) {
2991
3105
  const before = rawBefore.length > 0 ? rawBefore + "\n\n" : "";
2992
3106
  const after = rawAfter.length > 0 ? "\n" + rawAfter : "";
2993
3107
  const content = before + block + after;
2994
- await mkdir3(dirname4(filePath), { recursive: true });
3108
+ await mkdir3(dirname5(filePath), { recursive: true });
2995
3109
  await writeFile3(filePath, content, "utf-8");
2996
3110
  } else {
2997
3111
  const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
2998
- await mkdir3(dirname4(filePath), { recursive: true });
3112
+ await mkdir3(dirname5(filePath), { recursive: true });
2999
3113
  await writeFile3(filePath, existing + separator + block, "utf-8");
3000
3114
  }
3001
3115
  return { alreadyExists };
@@ -3028,13 +3142,16 @@ async function removeTomlServer(filePath, serverName) {
3028
3142
  const rawBefore = existing.slice(0, startIdx).replace(/\n+$/, "");
3029
3143
  const rawAfter = existing.slice(startIdx + sectionHeader.length + endOffset).replace(/^\n+/, "");
3030
3144
  const content = [rawBefore, rawAfter].filter(Boolean).join("\n\n");
3031
- await mkdir3(dirname4(filePath), { recursive: true });
3145
+ await mkdir3(dirname5(filePath), { recursive: true });
3032
3146
  await writeFile3(filePath, content.length > 0 ? `${content}
3033
3147
  ` : "", "utf-8");
3034
3148
  return { removed: true };
3035
3149
  }
3036
3150
 
3037
3151
  // src/commands/setup.ts
3152
+ function resolveTransport(options) {
3153
+ return options.stdio ? "stdio" : "http";
3154
+ }
3038
3155
  var CHECKBOX_THEME = {
3039
3156
  style: {
3040
3157
  highlight: (text) => pc8.green(text),
@@ -3051,7 +3168,7 @@ function getSelectedAgents(options) {
3051
3168
  return agents2;
3052
3169
  }
3053
3170
  function registerSetupCommand(program2) {
3054
- program2.command("setup").description("Set up Context7 for your AI coding agent").option("--claude", "Set up for Claude Code").option("--cursor", "Set up for Cursor").option("--universal", "Set up for Universal (.agents/skills)").option("--antigravity", "Set up for Antigravity (.agent/skills)").option("--opencode", "Set up for OpenCode").option("--codex", "Set up for Codex").option("--gemini", "Set up for Gemini CLI").option("--mcp", "Set up MCP server mode").option("--cli", "Set up CLI + Skills mode (no MCP server)").option("-p, --project", "Configure for current project instead of globally").option("-y, --yes", "Skip confirmation prompts").option("--api-key <key>", "Use API key authentication").option("--oauth", "Use OAuth endpoint (IDE handles auth flow)").action(async (options) => {
3171
+ program2.command("setup").description("Set up Context7 for your AI coding agent").option("--claude", "Set up for Claude Code").option("--cursor", "Set up for Cursor").option("--universal", "Set up for Universal (.agents/skills)").option("--antigravity", "Set up for Antigravity (.agent/skills)").option("--opencode", "Set up for OpenCode").option("--codex", "Set up for Codex").option("--gemini", "Set up for Gemini CLI").option("--mcp", "Set up MCP server mode").option("--cli", "Set up CLI + Skills mode (no MCP server)").option("-p, --project", "Configure for current project instead of globally").option("-y, --yes", "Skip confirmation prompts").option("--api-key <key>", "Use API key authentication").option("--oauth", "Use OAuth endpoint (IDE handles auth flow)").option("--stdio", "Configure the MCP server as a local stdio process (default: HTTP)").action(async (options) => {
3055
3172
  await setupCommand(options);
3056
3173
  });
3057
3174
  }
@@ -3092,7 +3209,7 @@ async function resolveAuth(options) {
3092
3209
  }
3093
3210
  async function resolveMode(options) {
3094
3211
  if (options.cli) return "cli";
3095
- if (options.mcp || options.yes || options.oauth || options.apiKey) return "mcp";
3212
+ if (options.mcp || options.yes || options.oauth || options.apiKey || options.stdio) return "mcp";
3096
3213
  return select3({
3097
3214
  message: "How should your agent access Context7?",
3098
3215
  choices: [
@@ -3168,13 +3285,13 @@ async function installRule(agentName, mode, scope) {
3168
3285
  const rule = agent.rule;
3169
3286
  const content = await getRuleContent(mode, agentName);
3170
3287
  if (rule.kind === "file") {
3171
- const ruleDir = scope === "global" ? rule.dir("global") : join9(process.cwd(), rule.dir("project"));
3172
- const rulePath = join9(ruleDir, rule.filename);
3173
- await mkdir4(dirname5(rulePath), { recursive: true });
3288
+ const ruleDir = scope === "global" ? rule.dir("global") : join8(process.cwd(), rule.dir("project"));
3289
+ const rulePath = join8(ruleDir, rule.filename);
3290
+ await mkdir4(dirname6(rulePath), { recursive: true });
3174
3291
  await writeFile4(rulePath, content, "utf-8");
3175
3292
  return { status: "installed", path: rulePath };
3176
3293
  }
3177
- const filePath = scope === "global" ? rule.file("global") : join9(process.cwd(), rule.file("project"));
3294
+ const filePath = scope === "global" ? rule.file("global") : join8(process.cwd(), rule.file("project"));
3178
3295
  const escapedMarker = rule.sectionMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3179
3296
  const section = `${rule.sectionMarker}
3180
3297
  ${content}${rule.sectionMarker}`;
@@ -3190,30 +3307,37 @@ ${content}${rule.sectionMarker}`;
3190
3307
  return { status: "updated", path: filePath };
3191
3308
  }
3192
3309
  const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
3193
- await mkdir4(dirname5(filePath), { recursive: true });
3310
+ await mkdir4(dirname6(filePath), { recursive: true });
3194
3311
  await writeFile4(filePath, existing + separator + section + "\n", "utf-8");
3195
3312
  return { status: "installed", path: filePath };
3196
3313
  }
3197
- async function setupAgent(agentName, auth, scope) {
3314
+ function resolveEntryToWrite(agent, auth, transport, existingEntry) {
3315
+ if (transport === "stdio" && existingEntry && isStdioContext7Entry(existingEntry)) {
3316
+ const apiKey = auth.mode === "api-key" ? auth.apiKey : void 0;
3317
+ return patchStdioApiKey(existingEntry, apiKey);
3318
+ }
3319
+ return agent.mcp.buildEntry(auth, transport);
3320
+ }
3321
+ async function setupAgent(agentName, auth, transport, scope) {
3198
3322
  const agent = getAgent(agentName);
3199
- const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((p) => join9(process.cwd(), p));
3323
+ const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((p) => join8(process.cwd(), p));
3200
3324
  const mcpPath = await resolveMcpPath(mcpCandidates);
3201
3325
  let mcpStatus;
3202
3326
  try {
3203
3327
  if (mcpPath.endsWith(".toml")) {
3204
- const { alreadyExists } = await appendTomlServer(
3205
- mcpPath,
3206
- "context7",
3207
- agent.mcp.buildEntry(auth)
3208
- );
3328
+ const existingTomlEntry = transport === "stdio" ? await readTomlServerEntry(mcpPath, "context7") : void 0;
3329
+ const entry = resolveEntryToWrite(agent, auth, transport, existingTomlEntry);
3330
+ const { alreadyExists } = await appendTomlServer(mcpPath, "context7", entry);
3209
3331
  mcpStatus = alreadyExists ? `reconfigured with ${AUTH_MODE_LABELS[auth.mode]}` : `configured with ${AUTH_MODE_LABELS[auth.mode]}`;
3210
3332
  } else {
3211
3333
  const existing = await readJsonConfig(mcpPath);
3334
+ const existingJsonEntry = transport === "stdio" ? getJsonServerEntry(existing, agent.mcp.configKey, "context7") : void 0;
3335
+ const entry = resolveEntryToWrite(agent, auth, transport, existingJsonEntry);
3212
3336
  const { config, alreadyExists } = mergeServerEntry(
3213
3337
  existing,
3214
3338
  agent.mcp.configKey,
3215
3339
  "context7",
3216
- agent.mcp.buildEntry(auth)
3340
+ entry
3217
3341
  );
3218
3342
  mcpStatus = alreadyExists ? `reconfigured with ${AUTH_MODE_LABELS[auth.mode]}` : `configured with ${AUTH_MODE_LABELS[auth.mode]}`;
3219
3343
  await writeJsonConfig(mcpPath, config);
@@ -3231,8 +3355,8 @@ async function setupAgent(agentName, auth, scope) {
3231
3355
  ruleStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
3232
3356
  rulePath = "";
3233
3357
  }
3234
- const skillDir = scope === "global" ? agent.skill.dir("global") : join9(process.cwd(), agent.skill.dir("project"));
3235
- const skillPath = join9(skillDir, agent.skill.name, "SKILL.md");
3358
+ const skillDir = scope === "global" ? agent.skill.dir("global") : join8(process.cwd(), agent.skill.dir("project"));
3359
+ const skillPath = join8(skillDir, agent.skill.name, "SKILL.md");
3236
3360
  let skillStatus;
3237
3361
  try {
3238
3362
  const downloadData = await downloadSkill("/upstash/context7", agent.skill.name);
@@ -3255,6 +3379,11 @@ async function setupAgent(agentName, auth, scope) {
3255
3379
  };
3256
3380
  }
3257
3381
  async function setupMcp(agents2, options, scope) {
3382
+ const transport = resolveTransport(options);
3383
+ if (transport === "stdio" && options.oauth) {
3384
+ log.error("--stdio is incompatible with --oauth (OAuth uses the hosted HTTP endpoint).");
3385
+ return;
3386
+ }
3258
3387
  const auth = await resolveAuth(options);
3259
3388
  if (!auth) {
3260
3389
  log.warn("Setup cancelled");
@@ -3265,7 +3394,7 @@ async function setupMcp(agents2, options, scope) {
3265
3394
  const results = [];
3266
3395
  for (const agentName of agents2) {
3267
3396
  spinner.text = `Setting up ${getAgent(agentName).displayName}...`;
3268
- results.push(await setupAgent(agentName, auth, scope));
3397
+ results.push(await setupAgent(agentName, auth, transport, scope));
3269
3398
  }
3270
3399
  spinner.succeed("Context7 setup complete");
3271
3400
  log.blank();
@@ -3282,7 +3411,7 @@ async function setupMcp(agents2, options, scope) {
3282
3411
  log.plain(` ${pc8.dim(r.skillPath)}`);
3283
3412
  if (r.skillStatus.includes("EACCES")) {
3284
3413
  log.plain(
3285
- ` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname5(dirname5(r.skillPath))}`)}`
3414
+ ` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname6(dirname6(r.skillPath))}`)}`
3286
3415
  );
3287
3416
  }
3288
3417
  }
@@ -3292,7 +3421,7 @@ async function setupMcp(agents2, options, scope) {
3292
3421
  }
3293
3422
  async function setupCliAgent(agentName, scope, downloadData) {
3294
3423
  const agent = getAgent(agentName);
3295
- const skillDir = scope === "global" ? agent.skill.dir("global") : join9(process.cwd(), agent.skill.dir("project"));
3424
+ const skillDir = scope === "global" ? agent.skill.dir("global") : join8(process.cwd(), agent.skill.dir("project"));
3296
3425
  let skillStatus;
3297
3426
  try {
3298
3427
  const files = customizeSkillFilesForAgent(agentName, "find-docs", downloadData.files);
@@ -3301,7 +3430,7 @@ async function setupCliAgent(agentName, scope, downloadData) {
3301
3430
  } catch (err) {
3302
3431
  skillStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
3303
3432
  }
3304
- const skillPath = join9(skillDir, "find-docs");
3433
+ const skillPath = join8(skillDir, "find-docs");
3305
3434
  let ruleStatus;
3306
3435
  let rulePath;
3307
3436
  try {
@@ -3343,7 +3472,7 @@ async function setupCli(options) {
3343
3472
  log.plain(` ${pc8.dim(r.skillPath)}`);
3344
3473
  if (r.skillStatus.includes("EACCES")) {
3345
3474
  log.plain(
3346
- ` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname5(dirname5(r.skillPath))}`)}`
3475
+ ` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname6(dirname6(r.skillPath))}`)}`
3347
3476
  );
3348
3477
  }
3349
3478
  const ruleIcon = r.ruleStatus === "installed" || r.ruleStatus === "updated" ? pc8.green("+") : pc8.dim("~");
@@ -3375,7 +3504,7 @@ async function setupCommand(options) {
3375
3504
  // src/commands/remove.ts
3376
3505
  import pc9 from "picocolors";
3377
3506
  import ora5 from "ora";
3378
- import { join as join10 } from "path";
3507
+ import { join as join9 } from "path";
3379
3508
  import { access as access4, readFile as readFile5, rm as rm3, writeFile as writeFile5 } from "fs/promises";
3380
3509
  var CHECKBOX_THEME2 = {
3381
3510
  style: {
@@ -3483,12 +3612,20 @@ async function pathExists2(path2) {
3483
3612
  }
3484
3613
  async function hasMcpConfig(agentName, scope) {
3485
3614
  const agent = getAgent(agentName);
3486
- const candidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((path2) => join10(process.cwd(), path2));
3615
+ const candidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((path2) => join9(process.cwd(), path2));
3487
3616
  const mcpPath = await resolveMcpPath(candidates);
3488
3617
  if (mcpPath.endsWith(".toml")) {
3489
3618
  return readTomlServerExists(mcpPath, "context7");
3490
3619
  }
3491
- const existing = await readJsonConfig(mcpPath);
3620
+ let existing;
3621
+ try {
3622
+ existing = await readJsonConfig(mcpPath);
3623
+ } catch (err) {
3624
+ log.warn(
3625
+ `Skipped ${mcpPath}: could not parse (${err instanceof Error ? err.message : String(err)})`
3626
+ );
3627
+ return false;
3628
+ }
3492
3629
  const section = existing[agent.mcp.configKey];
3493
3630
  return !!section && typeof section === "object" && !Array.isArray(section) && "context7" in section;
3494
3631
  }
@@ -3496,10 +3633,10 @@ async function hasRule(agentName, scope) {
3496
3633
  const agent = getAgent(agentName);
3497
3634
  const rule = agent.rule;
3498
3635
  if (rule.kind === "file") {
3499
- const ruleDir = scope === "global" ? rule.dir("global") : join10(process.cwd(), rule.dir("project"));
3500
- return pathExists2(join10(ruleDir, rule.filename));
3636
+ const ruleDir = scope === "global" ? rule.dir("global") : join9(process.cwd(), rule.dir("project"));
3637
+ return pathExists2(join9(ruleDir, rule.filename));
3501
3638
  }
3502
- const filePath = scope === "global" ? rule.file("global") : join10(process.cwd(), rule.file("project"));
3639
+ const filePath = scope === "global" ? rule.file("global") : join9(process.cwd(), rule.file("project"));
3503
3640
  try {
3504
3641
  const existing = await readFile5(filePath, "utf-8");
3505
3642
  return existing.includes(CONTEXT7_SECTION_MARKER);
@@ -3509,8 +3646,8 @@ async function hasRule(agentName, scope) {
3509
3646
  }
3510
3647
  async function hasSkill(agentName, scope, skillName) {
3511
3648
  const agent = getAgent(agentName);
3512
- const skillsDir = scope === "global" ? agent.skill.dir("global") : join10(process.cwd(), agent.skill.dir("project"));
3513
- return pathExists2(join10(skillsDir, skillName));
3649
+ const skillsDir = scope === "global" ? agent.skill.dir("global") : join9(process.cwd(), agent.skill.dir("project"));
3650
+ return pathExists2(join9(skillsDir, skillName));
3514
3651
  }
3515
3652
  async function detectAvailableModes(agents2, scope) {
3516
3653
  let hasMcpArtifacts = false;
@@ -3562,7 +3699,7 @@ async function resolveModes(options, agents2, scope) {
3562
3699
  }
3563
3700
  async function uninstallMcp(agentName, scope) {
3564
3701
  const agent = getAgent(agentName);
3565
- const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((path2) => join10(process.cwd(), path2));
3702
+ const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((path2) => join9(process.cwd(), path2));
3566
3703
  const mcpPath = await resolveMcpPath(mcpCandidates);
3567
3704
  try {
3568
3705
  if (mcpPath.endsWith(".toml")) {
@@ -3583,8 +3720,8 @@ async function uninstallRule(agentName, scope) {
3583
3720
  const agent = getAgent(agentName);
3584
3721
  const rule = agent.rule;
3585
3722
  if (rule.kind === "file") {
3586
- const rulePath = scope === "global" ? rule.dir("global") : join10(process.cwd(), rule.dir("project"));
3587
- const targetPath = join10(rulePath, rule.filename);
3723
+ const rulePath = scope === "global" ? rule.dir("global") : join9(process.cwd(), rule.dir("project"));
3724
+ const targetPath = join9(rulePath, rule.filename);
3588
3725
  try {
3589
3726
  await rm3(targetPath);
3590
3727
  return { status: "removed", path: targetPath };
@@ -3594,7 +3731,7 @@ async function uninstallRule(agentName, scope) {
3594
3731
  return { status: `failed: ${error.message}`, path: targetPath };
3595
3732
  }
3596
3733
  }
3597
- const filePath = scope === "global" ? rule.file("global") : join10(process.cwd(), rule.file("project"));
3734
+ const filePath = scope === "global" ? rule.file("global") : join9(process.cwd(), rule.file("project"));
3598
3735
  try {
3599
3736
  const existing = await readFile5(filePath, "utf-8");
3600
3737
  if (!existing.includes(CONTEXT7_SECTION_MARKER)) {
@@ -3617,10 +3754,10 @@ async function uninstallRule(agentName, scope) {
3617
3754
  }
3618
3755
  async function uninstallSkills(agentName, scope, skillNames) {
3619
3756
  const agent = getAgent(agentName);
3620
- const skillsDir = scope === "global" ? agent.skill.dir("global") : join10(process.cwd(), agent.skill.dir("project"));
3757
+ const skillsDir = scope === "global" ? agent.skill.dir("global") : join9(process.cwd(), agent.skill.dir("project"));
3621
3758
  const results = [];
3622
3759
  for (const skillName of skillNames) {
3623
- const skillPath = join10(skillsDir, skillName);
3760
+ const skillPath = join9(skillsDir, skillName);
3624
3761
  try {
3625
3762
  await rm3(skillPath, { recursive: true });
3626
3763
  results.push({ name: skillName, status: "removed", path: skillPath });
@@ -3883,10 +4020,10 @@ import pc11 from "picocolors";
3883
4020
 
3884
4021
  // src/utils/update-check.ts
3885
4022
  import { homedir as homedir6 } from "os";
3886
- import { dirname as dirname6, join as join11 } from "path";
4023
+ import { dirname as dirname7, join as join10 } from "path";
3887
4024
  import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
3888
4025
  var DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
3889
- var UPDATE_STATE_FILE = join11(homedir6(), ".context7", "cli-state.json");
4026
+ var UPDATE_STATE_FILE = join10(homedir6(), ".context7", "cli-state.json");
3890
4027
  function getStateFilePath(stateFile) {
3891
4028
  return stateFile ?? UPDATE_STATE_FILE;
3892
4029
  }
@@ -3900,7 +4037,7 @@ async function readUpdateState(stateFile) {
3900
4037
  }
3901
4038
  async function writeUpdateState(state, stateFile) {
3902
4039
  const path2 = getStateFilePath(stateFile);
3903
- await mkdir5(dirname6(path2), { recursive: true });
4040
+ await mkdir5(dirname7(path2), { recursive: true });
3904
4041
  await writeFile6(path2, JSON.stringify(state, null, 2) + "\n", "utf-8");
3905
4042
  }
3906
4043
  function compareVersions(a, b) {
@@ -4077,13 +4214,13 @@ function registerUpgradeCommand(program2) {
4077
4214
  });
4078
4215
  }
4079
4216
  function runCommand(command, args) {
4080
- return new Promise((resolve2, reject) => {
4217
+ return new Promise((resolve3, reject) => {
4081
4218
  const child = spawn2(command, args, {
4082
4219
  stdio: "inherit",
4083
4220
  shell: process.platform === "win32"
4084
4221
  });
4085
4222
  child.on("error", reject);
4086
- child.on("close", (code) => resolve2(code));
4223
+ child.on("close", (code) => resolve3(code));
4087
4224
  });
4088
4225
  }
4089
4226
  async function runUpgradePlan(plan) {