ctx7 0.4.2 → 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 +245 -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,14 +2635,25 @@ 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
|
+
}
|
|
2606
2649
|
function claudeConfigDir() {
|
|
2607
|
-
return process.env.CLAUDE_CONFIG_DIR ||
|
|
2650
|
+
return process.env.CLAUDE_CONFIG_DIR || join7(homedir5(), ".claude");
|
|
2608
2651
|
}
|
|
2609
2652
|
function claudeGlobalMcpPath() {
|
|
2610
2653
|
if (process.env.CLAUDE_CONFIG_DIR) {
|
|
2611
|
-
return
|
|
2654
|
+
return join7(claudeConfigDir(), ".claude.json");
|
|
2612
2655
|
}
|
|
2613
|
-
return
|
|
2656
|
+
return join7(homedir5(), ".claude.json");
|
|
2614
2657
|
}
|
|
2615
2658
|
function mcpUrl(auth) {
|
|
2616
2659
|
return auth.mode === "oauth" ? `${MCP_BASE_URL}/mcp/oauth` : `${MCP_BASE_URL}/mcp`;
|
|
@@ -2631,16 +2674,16 @@ var agents = {
|
|
|
2631
2674
|
return [claudeGlobalMcpPath()];
|
|
2632
2675
|
},
|
|
2633
2676
|
configKey: "mcpServers",
|
|
2634
|
-
buildEntry: (auth) => withHeaders({ type: "http", url: mcpUrl(auth) }, auth)
|
|
2677
|
+
buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ type: "http", url: mcpUrl(auth) }, auth)
|
|
2635
2678
|
},
|
|
2636
2679
|
rule: {
|
|
2637
2680
|
kind: "file",
|
|
2638
|
-
dir: (scope) => scope === "global" ?
|
|
2681
|
+
dir: (scope) => scope === "global" ? join7(claudeConfigDir(), "rules") : join7(".claude", "rules"),
|
|
2639
2682
|
filename: "context7.md"
|
|
2640
2683
|
},
|
|
2641
2684
|
skill: {
|
|
2642
2685
|
name: "context7-mcp",
|
|
2643
|
-
dir: (scope) => scope === "global" ?
|
|
2686
|
+
dir: (scope) => scope === "global" ? join7(claudeConfigDir(), "skills") : join7(".claude", "skills")
|
|
2644
2687
|
},
|
|
2645
2688
|
detect: {
|
|
2646
2689
|
projectPaths: [".mcp.json", ".claude"],
|
|
@@ -2653,23 +2696,23 @@ var agents = {
|
|
|
2653
2696
|
name: "cursor",
|
|
2654
2697
|
displayName: "Cursor",
|
|
2655
2698
|
mcp: {
|
|
2656
|
-
projectPaths: [
|
|
2657
|
-
globalPaths: [
|
|
2699
|
+
projectPaths: [join7(".cursor", "mcp.json")],
|
|
2700
|
+
globalPaths: [join7(homedir5(), ".cursor", "mcp.json")],
|
|
2658
2701
|
configKey: "mcpServers",
|
|
2659
|
-
buildEntry: (auth) => withHeaders({ url: mcpUrl(auth) }, auth)
|
|
2702
|
+
buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ url: mcpUrl(auth) }, auth)
|
|
2660
2703
|
},
|
|
2661
2704
|
rule: {
|
|
2662
2705
|
kind: "file",
|
|
2663
|
-
dir: (scope) => scope === "global" ?
|
|
2706
|
+
dir: (scope) => scope === "global" ? join7(homedir5(), ".cursor", "rules") : join7(".cursor", "rules"),
|
|
2664
2707
|
filename: "context7.mdc"
|
|
2665
2708
|
},
|
|
2666
2709
|
skill: {
|
|
2667
2710
|
name: "context7-mcp",
|
|
2668
|
-
dir: (scope) => scope === "global" ?
|
|
2711
|
+
dir: (scope) => scope === "global" ? join7(homedir5(), ".cursor", "skills") : join7(".cursor", "skills")
|
|
2669
2712
|
},
|
|
2670
2713
|
detect: {
|
|
2671
2714
|
projectPaths: [".cursor"],
|
|
2672
|
-
globalPaths: [
|
|
2715
|
+
globalPaths: [join7(homedir5(), ".cursor")]
|
|
2673
2716
|
}
|
|
2674
2717
|
},
|
|
2675
2718
|
opencode: {
|
|
@@ -2678,78 +2721,72 @@ var agents = {
|
|
|
2678
2721
|
mcp: {
|
|
2679
2722
|
projectPaths: ["opencode.json", "opencode.jsonc", ".opencode.json", ".opencode.jsonc"],
|
|
2680
2723
|
globalPaths: [
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
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")
|
|
2685
2728
|
],
|
|
2686
2729
|
configKey: "mcp",
|
|
2687
|
-
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)
|
|
2688
2731
|
},
|
|
2689
2732
|
rule: {
|
|
2690
2733
|
kind: "append",
|
|
2691
|
-
file: (scope) => scope === "global" ?
|
|
2734
|
+
file: (scope) => scope === "global" ? join7(homedir5(), ".config", "opencode", "AGENTS.md") : "AGENTS.md",
|
|
2692
2735
|
sectionMarker: "<!-- context7 -->"
|
|
2693
2736
|
},
|
|
2694
2737
|
skill: {
|
|
2695
2738
|
name: "context7-mcp",
|
|
2696
|
-
dir: (scope) => scope === "global" ?
|
|
2739
|
+
dir: (scope) => scope === "global" ? join7(homedir5(), ".agents", "skills") : join7(".agents", "skills")
|
|
2697
2740
|
},
|
|
2698
2741
|
detect: {
|
|
2699
2742
|
projectPaths: ["opencode.json", "opencode.jsonc", ".opencode.json", ".opencode.jsonc"],
|
|
2700
|
-
globalPaths: [
|
|
2743
|
+
globalPaths: [join7(homedir5(), ".config", "opencode")]
|
|
2701
2744
|
}
|
|
2702
2745
|
},
|
|
2703
2746
|
codex: {
|
|
2704
2747
|
name: "codex",
|
|
2705
2748
|
displayName: "Codex",
|
|
2706
2749
|
mcp: {
|
|
2707
|
-
projectPaths: [
|
|
2708
|
-
globalPaths: [
|
|
2750
|
+
projectPaths: [join7(".codex", "config.toml")],
|
|
2751
|
+
globalPaths: [join7(homedir5(), ".codex", "config.toml")],
|
|
2709
2752
|
configKey: "mcp_servers",
|
|
2710
|
-
buildEntry: (auth) => {
|
|
2711
|
-
const entry = { type: "http", url: mcpUrl(auth) };
|
|
2712
|
-
if (auth.mode === "api-key" && auth.apiKey) {
|
|
2713
|
-
entry.headers = { CONTEXT7_API_KEY: auth.apiKey };
|
|
2714
|
-
}
|
|
2715
|
-
return entry;
|
|
2716
|
-
}
|
|
2753
|
+
buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ type: "http", url: mcpUrl(auth) }, auth)
|
|
2717
2754
|
},
|
|
2718
2755
|
rule: {
|
|
2719
2756
|
kind: "append",
|
|
2720
|
-
file: (scope) => scope === "global" ?
|
|
2757
|
+
file: (scope) => scope === "global" ? join7(homedir5(), ".codex", "AGENTS.md") : "AGENTS.md",
|
|
2721
2758
|
sectionMarker: "<!-- context7 -->"
|
|
2722
2759
|
},
|
|
2723
2760
|
skill: {
|
|
2724
2761
|
name: "context7-mcp",
|
|
2725
|
-
dir: (scope) => scope === "global" ?
|
|
2762
|
+
dir: (scope) => scope === "global" ? join7(homedir5(), ".agents", "skills") : join7(".agents", "skills")
|
|
2726
2763
|
},
|
|
2727
2764
|
detect: {
|
|
2728
2765
|
projectPaths: [".codex"],
|
|
2729
|
-
globalPaths: [
|
|
2766
|
+
globalPaths: [join7(homedir5(), ".codex")]
|
|
2730
2767
|
}
|
|
2731
2768
|
},
|
|
2732
2769
|
gemini: {
|
|
2733
2770
|
name: "gemini",
|
|
2734
2771
|
displayName: "Gemini CLI",
|
|
2735
2772
|
mcp: {
|
|
2736
|
-
projectPaths: [
|
|
2737
|
-
globalPaths: [
|
|
2773
|
+
projectPaths: [join7(".gemini", "settings.json")],
|
|
2774
|
+
globalPaths: [join7(homedir5(), ".gemini", "settings.json")],
|
|
2738
2775
|
configKey: "mcpServers",
|
|
2739
|
-
buildEntry: (auth) => withHeaders({ httpUrl: mcpUrl(auth) }, auth)
|
|
2776
|
+
buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ httpUrl: mcpUrl(auth) }, auth)
|
|
2740
2777
|
},
|
|
2741
2778
|
rule: {
|
|
2742
2779
|
kind: "append",
|
|
2743
|
-
file: (scope) => scope === "global" ?
|
|
2780
|
+
file: (scope) => scope === "global" ? join7(homedir5(), ".gemini", "GEMINI.md") : "GEMINI.md",
|
|
2744
2781
|
sectionMarker: "<!-- context7 -->"
|
|
2745
2782
|
},
|
|
2746
2783
|
skill: {
|
|
2747
2784
|
name: "context7-mcp",
|
|
2748
|
-
dir: (scope) => scope === "global" ?
|
|
2785
|
+
dir: (scope) => scope === "global" ? join7(homedir5(), ".gemini", "skills") : join7(".gemini", "skills")
|
|
2749
2786
|
},
|
|
2750
2787
|
detect: {
|
|
2751
2788
|
projectPaths: [".gemini"],
|
|
2752
|
-
globalPaths: [
|
|
2789
|
+
globalPaths: [join7(homedir5(), ".gemini")]
|
|
2753
2790
|
}
|
|
2754
2791
|
}
|
|
2755
2792
|
};
|
|
@@ -2770,7 +2807,7 @@ async function detectAgents(scope) {
|
|
|
2770
2807
|
for (const agent of Object.values(agents)) {
|
|
2771
2808
|
const paths = scope === "global" ? agent.detect.globalPaths : agent.detect.projectPaths;
|
|
2772
2809
|
for (const p of paths) {
|
|
2773
|
-
const fullPath = scope === "global" ? p :
|
|
2810
|
+
const fullPath = scope === "global" ? p : join7(process.cwd(), p);
|
|
2774
2811
|
if (await pathExists(fullPath)) {
|
|
2775
2812
|
detected.push(agent.name);
|
|
2776
2813
|
break;
|
|
@@ -2869,7 +2906,7 @@ function customizeSkillFilesForAgent(agent, skillName, files) {
|
|
|
2869
2906
|
|
|
2870
2907
|
// src/setup/mcp-writer.ts
|
|
2871
2908
|
import { access as access3, readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
2872
|
-
import { dirname as
|
|
2909
|
+
import { dirname as dirname5 } from "path";
|
|
2873
2910
|
function stripJsonComments(text) {
|
|
2874
2911
|
let result = "";
|
|
2875
2912
|
let i = 0;
|
|
@@ -2948,7 +2985,7 @@ async function resolveMcpPath(candidates) {
|
|
|
2948
2985
|
return candidates[0];
|
|
2949
2986
|
}
|
|
2950
2987
|
async function writeJsonConfig(filePath, config) {
|
|
2951
|
-
await mkdir3(
|
|
2988
|
+
await mkdir3(dirname5(filePath), { recursive: true });
|
|
2952
2989
|
await writeFile3(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
2953
2990
|
}
|
|
2954
2991
|
async function readTomlServerExists(filePath, serverName) {
|
|
@@ -2959,6 +2996,70 @@ async function readTomlServerExists(filePath, serverName) {
|
|
|
2959
2996
|
return false;
|
|
2960
2997
|
}
|
|
2961
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
|
+
}
|
|
2962
3063
|
function buildTomlServerBlock(serverName, entry) {
|
|
2963
3064
|
const lines = [`[mcp_servers.${serverName}]`];
|
|
2964
3065
|
const headers = entry.headers;
|
|
@@ -3004,11 +3105,11 @@ async function appendTomlServer(filePath, serverName, entry) {
|
|
|
3004
3105
|
const before = rawBefore.length > 0 ? rawBefore + "\n\n" : "";
|
|
3005
3106
|
const after = rawAfter.length > 0 ? "\n" + rawAfter : "";
|
|
3006
3107
|
const content = before + block + after;
|
|
3007
|
-
await mkdir3(
|
|
3108
|
+
await mkdir3(dirname5(filePath), { recursive: true });
|
|
3008
3109
|
await writeFile3(filePath, content, "utf-8");
|
|
3009
3110
|
} else {
|
|
3010
3111
|
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
|
|
3011
|
-
await mkdir3(
|
|
3112
|
+
await mkdir3(dirname5(filePath), { recursive: true });
|
|
3012
3113
|
await writeFile3(filePath, existing + separator + block, "utf-8");
|
|
3013
3114
|
}
|
|
3014
3115
|
return { alreadyExists };
|
|
@@ -3041,13 +3142,16 @@ async function removeTomlServer(filePath, serverName) {
|
|
|
3041
3142
|
const rawBefore = existing.slice(0, startIdx).replace(/\n+$/, "");
|
|
3042
3143
|
const rawAfter = existing.slice(startIdx + sectionHeader.length + endOffset).replace(/^\n+/, "");
|
|
3043
3144
|
const content = [rawBefore, rawAfter].filter(Boolean).join("\n\n");
|
|
3044
|
-
await mkdir3(
|
|
3145
|
+
await mkdir3(dirname5(filePath), { recursive: true });
|
|
3045
3146
|
await writeFile3(filePath, content.length > 0 ? `${content}
|
|
3046
3147
|
` : "", "utf-8");
|
|
3047
3148
|
return { removed: true };
|
|
3048
3149
|
}
|
|
3049
3150
|
|
|
3050
3151
|
// src/commands/setup.ts
|
|
3152
|
+
function resolveTransport(options) {
|
|
3153
|
+
return options.stdio ? "stdio" : "http";
|
|
3154
|
+
}
|
|
3051
3155
|
var CHECKBOX_THEME = {
|
|
3052
3156
|
style: {
|
|
3053
3157
|
highlight: (text) => pc8.green(text),
|
|
@@ -3064,7 +3168,7 @@ function getSelectedAgents(options) {
|
|
|
3064
3168
|
return agents2;
|
|
3065
3169
|
}
|
|
3066
3170
|
function registerSetupCommand(program2) {
|
|
3067
|
-
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) => {
|
|
3068
3172
|
await setupCommand(options);
|
|
3069
3173
|
});
|
|
3070
3174
|
}
|
|
@@ -3105,7 +3209,7 @@ async function resolveAuth(options) {
|
|
|
3105
3209
|
}
|
|
3106
3210
|
async function resolveMode(options) {
|
|
3107
3211
|
if (options.cli) return "cli";
|
|
3108
|
-
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";
|
|
3109
3213
|
return select3({
|
|
3110
3214
|
message: "How should your agent access Context7?",
|
|
3111
3215
|
choices: [
|
|
@@ -3181,13 +3285,13 @@ async function installRule(agentName, mode, scope) {
|
|
|
3181
3285
|
const rule = agent.rule;
|
|
3182
3286
|
const content = await getRuleContent(mode, agentName);
|
|
3183
3287
|
if (rule.kind === "file") {
|
|
3184
|
-
const ruleDir = scope === "global" ? rule.dir("global") :
|
|
3185
|
-
const rulePath =
|
|
3186
|
-
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 });
|
|
3187
3291
|
await writeFile4(rulePath, content, "utf-8");
|
|
3188
3292
|
return { status: "installed", path: rulePath };
|
|
3189
3293
|
}
|
|
3190
|
-
const filePath = scope === "global" ? rule.file("global") :
|
|
3294
|
+
const filePath = scope === "global" ? rule.file("global") : join8(process.cwd(), rule.file("project"));
|
|
3191
3295
|
const escapedMarker = rule.sectionMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3192
3296
|
const section = `${rule.sectionMarker}
|
|
3193
3297
|
${content}${rule.sectionMarker}`;
|
|
@@ -3203,30 +3307,37 @@ ${content}${rule.sectionMarker}`;
|
|
|
3203
3307
|
return { status: "updated", path: filePath };
|
|
3204
3308
|
}
|
|
3205
3309
|
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
|
|
3206
|
-
await mkdir4(
|
|
3310
|
+
await mkdir4(dirname6(filePath), { recursive: true });
|
|
3207
3311
|
await writeFile4(filePath, existing + separator + section + "\n", "utf-8");
|
|
3208
3312
|
return { status: "installed", path: filePath };
|
|
3209
3313
|
}
|
|
3210
|
-
|
|
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) {
|
|
3211
3322
|
const agent = getAgent(agentName);
|
|
3212
|
-
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));
|
|
3213
3324
|
const mcpPath = await resolveMcpPath(mcpCandidates);
|
|
3214
3325
|
let mcpStatus;
|
|
3215
3326
|
try {
|
|
3216
3327
|
if (mcpPath.endsWith(".toml")) {
|
|
3217
|
-
const
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
agent.mcp.buildEntry(auth)
|
|
3221
|
-
);
|
|
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);
|
|
3222
3331
|
mcpStatus = alreadyExists ? `reconfigured with ${AUTH_MODE_LABELS[auth.mode]}` : `configured with ${AUTH_MODE_LABELS[auth.mode]}`;
|
|
3223
3332
|
} else {
|
|
3224
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);
|
|
3225
3336
|
const { config, alreadyExists } = mergeServerEntry(
|
|
3226
3337
|
existing,
|
|
3227
3338
|
agent.mcp.configKey,
|
|
3228
3339
|
"context7",
|
|
3229
|
-
|
|
3340
|
+
entry
|
|
3230
3341
|
);
|
|
3231
3342
|
mcpStatus = alreadyExists ? `reconfigured with ${AUTH_MODE_LABELS[auth.mode]}` : `configured with ${AUTH_MODE_LABELS[auth.mode]}`;
|
|
3232
3343
|
await writeJsonConfig(mcpPath, config);
|
|
@@ -3244,8 +3355,8 @@ async function setupAgent(agentName, auth, scope) {
|
|
|
3244
3355
|
ruleStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
3245
3356
|
rulePath = "";
|
|
3246
3357
|
}
|
|
3247
|
-
const skillDir = scope === "global" ? agent.skill.dir("global") :
|
|
3248
|
-
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");
|
|
3249
3360
|
let skillStatus;
|
|
3250
3361
|
try {
|
|
3251
3362
|
const downloadData = await downloadSkill("/upstash/context7", agent.skill.name);
|
|
@@ -3268,6 +3379,11 @@ async function setupAgent(agentName, auth, scope) {
|
|
|
3268
3379
|
};
|
|
3269
3380
|
}
|
|
3270
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
|
+
}
|
|
3271
3387
|
const auth = await resolveAuth(options);
|
|
3272
3388
|
if (!auth) {
|
|
3273
3389
|
log.warn("Setup cancelled");
|
|
@@ -3278,7 +3394,7 @@ async function setupMcp(agents2, options, scope) {
|
|
|
3278
3394
|
const results = [];
|
|
3279
3395
|
for (const agentName of agents2) {
|
|
3280
3396
|
spinner.text = `Setting up ${getAgent(agentName).displayName}...`;
|
|
3281
|
-
results.push(await setupAgent(agentName, auth, scope));
|
|
3397
|
+
results.push(await setupAgent(agentName, auth, transport, scope));
|
|
3282
3398
|
}
|
|
3283
3399
|
spinner.succeed("Context7 setup complete");
|
|
3284
3400
|
log.blank();
|
|
@@ -3295,7 +3411,7 @@ async function setupMcp(agents2, options, scope) {
|
|
|
3295
3411
|
log.plain(` ${pc8.dim(r.skillPath)}`);
|
|
3296
3412
|
if (r.skillStatus.includes("EACCES")) {
|
|
3297
3413
|
log.plain(
|
|
3298
|
-
` ${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))}`)}`
|
|
3299
3415
|
);
|
|
3300
3416
|
}
|
|
3301
3417
|
}
|
|
@@ -3305,7 +3421,7 @@ async function setupMcp(agents2, options, scope) {
|
|
|
3305
3421
|
}
|
|
3306
3422
|
async function setupCliAgent(agentName, scope, downloadData) {
|
|
3307
3423
|
const agent = getAgent(agentName);
|
|
3308
|
-
const skillDir = scope === "global" ? agent.skill.dir("global") :
|
|
3424
|
+
const skillDir = scope === "global" ? agent.skill.dir("global") : join8(process.cwd(), agent.skill.dir("project"));
|
|
3309
3425
|
let skillStatus;
|
|
3310
3426
|
try {
|
|
3311
3427
|
const files = customizeSkillFilesForAgent(agentName, "find-docs", downloadData.files);
|
|
@@ -3314,7 +3430,7 @@ async function setupCliAgent(agentName, scope, downloadData) {
|
|
|
3314
3430
|
} catch (err) {
|
|
3315
3431
|
skillStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
3316
3432
|
}
|
|
3317
|
-
const skillPath =
|
|
3433
|
+
const skillPath = join8(skillDir, "find-docs");
|
|
3318
3434
|
let ruleStatus;
|
|
3319
3435
|
let rulePath;
|
|
3320
3436
|
try {
|
|
@@ -3356,7 +3472,7 @@ async function setupCli(options) {
|
|
|
3356
3472
|
log.plain(` ${pc8.dim(r.skillPath)}`);
|
|
3357
3473
|
if (r.skillStatus.includes("EACCES")) {
|
|
3358
3474
|
log.plain(
|
|
3359
|
-
` ${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))}`)}`
|
|
3360
3476
|
);
|
|
3361
3477
|
}
|
|
3362
3478
|
const ruleIcon = r.ruleStatus === "installed" || r.ruleStatus === "updated" ? pc8.green("+") : pc8.dim("~");
|
|
@@ -3388,7 +3504,7 @@ async function setupCommand(options) {
|
|
|
3388
3504
|
// src/commands/remove.ts
|
|
3389
3505
|
import pc9 from "picocolors";
|
|
3390
3506
|
import ora5 from "ora";
|
|
3391
|
-
import { join as
|
|
3507
|
+
import { join as join9 } from "path";
|
|
3392
3508
|
import { access as access4, readFile as readFile5, rm as rm3, writeFile as writeFile5 } from "fs/promises";
|
|
3393
3509
|
var CHECKBOX_THEME2 = {
|
|
3394
3510
|
style: {
|
|
@@ -3496,7 +3612,7 @@ async function pathExists2(path2) {
|
|
|
3496
3612
|
}
|
|
3497
3613
|
async function hasMcpConfig(agentName, scope) {
|
|
3498
3614
|
const agent = getAgent(agentName);
|
|
3499
|
-
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));
|
|
3500
3616
|
const mcpPath = await resolveMcpPath(candidates);
|
|
3501
3617
|
if (mcpPath.endsWith(".toml")) {
|
|
3502
3618
|
return readTomlServerExists(mcpPath, "context7");
|
|
@@ -3517,10 +3633,10 @@ async function hasRule(agentName, scope) {
|
|
|
3517
3633
|
const agent = getAgent(agentName);
|
|
3518
3634
|
const rule = agent.rule;
|
|
3519
3635
|
if (rule.kind === "file") {
|
|
3520
|
-
const ruleDir = scope === "global" ? rule.dir("global") :
|
|
3521
|
-
return pathExists2(
|
|
3636
|
+
const ruleDir = scope === "global" ? rule.dir("global") : join9(process.cwd(), rule.dir("project"));
|
|
3637
|
+
return pathExists2(join9(ruleDir, rule.filename));
|
|
3522
3638
|
}
|
|
3523
|
-
const filePath = scope === "global" ? rule.file("global") :
|
|
3639
|
+
const filePath = scope === "global" ? rule.file("global") : join9(process.cwd(), rule.file("project"));
|
|
3524
3640
|
try {
|
|
3525
3641
|
const existing = await readFile5(filePath, "utf-8");
|
|
3526
3642
|
return existing.includes(CONTEXT7_SECTION_MARKER);
|
|
@@ -3530,8 +3646,8 @@ async function hasRule(agentName, scope) {
|
|
|
3530
3646
|
}
|
|
3531
3647
|
async function hasSkill(agentName, scope, skillName) {
|
|
3532
3648
|
const agent = getAgent(agentName);
|
|
3533
|
-
const skillsDir = scope === "global" ? agent.skill.dir("global") :
|
|
3534
|
-
return pathExists2(
|
|
3649
|
+
const skillsDir = scope === "global" ? agent.skill.dir("global") : join9(process.cwd(), agent.skill.dir("project"));
|
|
3650
|
+
return pathExists2(join9(skillsDir, skillName));
|
|
3535
3651
|
}
|
|
3536
3652
|
async function detectAvailableModes(agents2, scope) {
|
|
3537
3653
|
let hasMcpArtifacts = false;
|
|
@@ -3583,7 +3699,7 @@ async function resolveModes(options, agents2, scope) {
|
|
|
3583
3699
|
}
|
|
3584
3700
|
async function uninstallMcp(agentName, scope) {
|
|
3585
3701
|
const agent = getAgent(agentName);
|
|
3586
|
-
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));
|
|
3587
3703
|
const mcpPath = await resolveMcpPath(mcpCandidates);
|
|
3588
3704
|
try {
|
|
3589
3705
|
if (mcpPath.endsWith(".toml")) {
|
|
@@ -3604,8 +3720,8 @@ async function uninstallRule(agentName, scope) {
|
|
|
3604
3720
|
const agent = getAgent(agentName);
|
|
3605
3721
|
const rule = agent.rule;
|
|
3606
3722
|
if (rule.kind === "file") {
|
|
3607
|
-
const rulePath = scope === "global" ? rule.dir("global") :
|
|
3608
|
-
const targetPath =
|
|
3723
|
+
const rulePath = scope === "global" ? rule.dir("global") : join9(process.cwd(), rule.dir("project"));
|
|
3724
|
+
const targetPath = join9(rulePath, rule.filename);
|
|
3609
3725
|
try {
|
|
3610
3726
|
await rm3(targetPath);
|
|
3611
3727
|
return { status: "removed", path: targetPath };
|
|
@@ -3615,7 +3731,7 @@ async function uninstallRule(agentName, scope) {
|
|
|
3615
3731
|
return { status: `failed: ${error.message}`, path: targetPath };
|
|
3616
3732
|
}
|
|
3617
3733
|
}
|
|
3618
|
-
const filePath = scope === "global" ? rule.file("global") :
|
|
3734
|
+
const filePath = scope === "global" ? rule.file("global") : join9(process.cwd(), rule.file("project"));
|
|
3619
3735
|
try {
|
|
3620
3736
|
const existing = await readFile5(filePath, "utf-8");
|
|
3621
3737
|
if (!existing.includes(CONTEXT7_SECTION_MARKER)) {
|
|
@@ -3638,10 +3754,10 @@ async function uninstallRule(agentName, scope) {
|
|
|
3638
3754
|
}
|
|
3639
3755
|
async function uninstallSkills(agentName, scope, skillNames) {
|
|
3640
3756
|
const agent = getAgent(agentName);
|
|
3641
|
-
const skillsDir = scope === "global" ? agent.skill.dir("global") :
|
|
3757
|
+
const skillsDir = scope === "global" ? agent.skill.dir("global") : join9(process.cwd(), agent.skill.dir("project"));
|
|
3642
3758
|
const results = [];
|
|
3643
3759
|
for (const skillName of skillNames) {
|
|
3644
|
-
const skillPath =
|
|
3760
|
+
const skillPath = join9(skillsDir, skillName);
|
|
3645
3761
|
try {
|
|
3646
3762
|
await rm3(skillPath, { recursive: true });
|
|
3647
3763
|
results.push({ name: skillName, status: "removed", path: skillPath });
|
|
@@ -3904,10 +4020,10 @@ import pc11 from "picocolors";
|
|
|
3904
4020
|
|
|
3905
4021
|
// src/utils/update-check.ts
|
|
3906
4022
|
import { homedir as homedir6 } from "os";
|
|
3907
|
-
import { dirname as
|
|
4023
|
+
import { dirname as dirname7, join as join10 } from "path";
|
|
3908
4024
|
import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
3909
4025
|
var DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3910
|
-
var UPDATE_STATE_FILE =
|
|
4026
|
+
var UPDATE_STATE_FILE = join10(homedir6(), ".context7", "cli-state.json");
|
|
3911
4027
|
function getStateFilePath(stateFile) {
|
|
3912
4028
|
return stateFile ?? UPDATE_STATE_FILE;
|
|
3913
4029
|
}
|
|
@@ -3921,7 +4037,7 @@ async function readUpdateState(stateFile) {
|
|
|
3921
4037
|
}
|
|
3922
4038
|
async function writeUpdateState(state, stateFile) {
|
|
3923
4039
|
const path2 = getStateFilePath(stateFile);
|
|
3924
|
-
await mkdir5(
|
|
4040
|
+
await mkdir5(dirname7(path2), { recursive: true });
|
|
3925
4041
|
await writeFile6(path2, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
3926
4042
|
}
|
|
3927
4043
|
function compareVersions(a, b) {
|
|
@@ -4098,13 +4214,13 @@ function registerUpgradeCommand(program2) {
|
|
|
4098
4214
|
});
|
|
4099
4215
|
}
|
|
4100
4216
|
function runCommand(command, args) {
|
|
4101
|
-
return new Promise((
|
|
4217
|
+
return new Promise((resolve3, reject) => {
|
|
4102
4218
|
const child = spawn2(command, args, {
|
|
4103
4219
|
stdio: "inherit",
|
|
4104
4220
|
shell: process.platform === "win32"
|
|
4105
4221
|
});
|
|
4106
4222
|
child.on("error", reject);
|
|
4107
|
-
child.on("close", (code) =>
|
|
4223
|
+
child.on("close", (code) => resolve3(code));
|
|
4108
4224
|
});
|
|
4109
4225
|
}
|
|
4110
4226
|
async function runUpgradePlan(plan) {
|