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 +266 -129
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
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
|
|
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, "")
|
|
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 =
|
|
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
|
|
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 =
|
|
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,
|
|
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 {
|
|
863
|
-
async function installSkillFiles(skillName, files,
|
|
864
|
-
const skillDir =
|
|
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 =
|
|
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 =
|
|
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,
|
|
876
|
-
const targetPath =
|
|
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(
|
|
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
|
|
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((
|
|
1001
|
-
resolvePort =
|
|
1026
|
+
const portPromise = new Promise((resolve3) => {
|
|
1027
|
+
resolvePort = resolve3;
|
|
1002
1028
|
});
|
|
1003
|
-
const resultPromise = new Promise((
|
|
1004
|
-
resolveResult =
|
|
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 =
|
|
1710
|
+
const previewDir = join4(homedir3(), ".context7", "previews");
|
|
1685
1711
|
await mkdir2(previewDir, { recursive: true });
|
|
1686
|
-
previewFile =
|
|
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((
|
|
1718
|
+
await new Promise((resolve3) => {
|
|
1693
1719
|
const child = spawn(editor, [previewFile], {
|
|
1694
1720
|
stdio: "inherit"
|
|
1695
1721
|
});
|
|
1696
|
-
child.on("close", () =>
|
|
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((
|
|
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 =
|
|
1763
|
-
const skillPath =
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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" ?
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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" ?
|
|
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" ?
|
|
2686
|
+
dir: (scope) => scope === "global" ? join7(claudeConfigDir(), "skills") : join7(".claude", "skills")
|
|
2633
2687
|
},
|
|
2634
2688
|
detect: {
|
|
2635
2689
|
projectPaths: [".mcp.json", ".claude"],
|
|
2636
|
-
globalPaths
|
|
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: [
|
|
2644
|
-
globalPaths: [
|
|
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" ?
|
|
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" ?
|
|
2711
|
+
dir: (scope) => scope === "global" ? join7(homedir5(), ".cursor", "skills") : join7(".cursor", "skills")
|
|
2656
2712
|
},
|
|
2657
2713
|
detect: {
|
|
2658
2714
|
projectPaths: [".cursor"],
|
|
2659
|
-
globalPaths: [
|
|
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
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
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" ?
|
|
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" ?
|
|
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: [
|
|
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: [
|
|
2695
|
-
globalPaths: [
|
|
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" ?
|
|
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" ?
|
|
2762
|
+
dir: (scope) => scope === "global" ? join7(homedir5(), ".agents", "skills") : join7(".agents", "skills")
|
|
2713
2763
|
},
|
|
2714
2764
|
detect: {
|
|
2715
2765
|
projectPaths: [".codex"],
|
|
2716
|
-
globalPaths: [
|
|
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: [
|
|
2724
|
-
globalPaths: [
|
|
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" ?
|
|
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" ?
|
|
2785
|
+
dir: (scope) => scope === "global" ? join7(homedir5(), ".gemini", "skills") : join7(".gemini", "skills")
|
|
2736
2786
|
},
|
|
2737
2787
|
detect: {
|
|
2738
2788
|
projectPaths: [".gemini"],
|
|
2739
|
-
globalPaths: [
|
|
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 :
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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") :
|
|
3172
|
-
const rulePath =
|
|
3173
|
-
await mkdir4(
|
|
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") :
|
|
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(
|
|
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
|
-
|
|
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) =>
|
|
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
|
|
3205
|
-
|
|
3206
|
-
|
|
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
|
-
|
|
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") :
|
|
3235
|
-
const skillPath =
|
|
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) ${
|
|
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") :
|
|
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 =
|
|
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) ${
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
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") :
|
|
3500
|
-
return pathExists2(
|
|
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") :
|
|
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") :
|
|
3513
|
-
return pathExists2(
|
|
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) =>
|
|
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") :
|
|
3587
|
-
const targetPath =
|
|
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") :
|
|
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") :
|
|
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 =
|
|
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
|
|
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 =
|
|
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(
|
|
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((
|
|
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) =>
|
|
4223
|
+
child.on("close", (code) => resolve3(code));
|
|
4087
4224
|
});
|
|
4088
4225
|
}
|
|
4089
4226
|
async function runUpgradePlan(plan) {
|