ctx7 0.4.2 → 0.4.4
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 +274 -135
- 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();
|
|
@@ -1916,7 +1942,7 @@ function registerSkillCommands(program2) {
|
|
|
1916
1942
|
skill.command("search").alias("s").argument("<keywords...>", "Search keywords").description("Search for skills across all indexed repositories").action(async (keywords) => {
|
|
1917
1943
|
await searchCommand(keywords.join(" "));
|
|
1918
1944
|
});
|
|
1919
|
-
skill.command("list").alias("ls").option("--global", "List global skills").option("--claude", "Claude Code (.claude/skills/)").option("--cursor", "Cursor (.cursor/skills/)").option("--universal", "Universal (.agents/skills/)").option("--antigravity", "Antigravity (.agent/skills/)").description("List installed skills").action(async (options) => {
|
|
1945
|
+
skill.command("list").alias("ls").option("--json", "Output as JSON").option("--global", "List global skills").option("--claude", "Claude Code (.claude/skills/)").option("--cursor", "Cursor (.cursor/skills/)").option("--universal", "Universal (.agents/skills/)").option("--antigravity", "Antigravity (.agent/skills/)").description("List installed skills").action(async (options) => {
|
|
1920
1946
|
await listCommand(options);
|
|
1921
1947
|
});
|
|
1922
1948
|
skill.command("remove").alias("rm").alias("delete").argument("<name>", "Skill name to remove").option("--global", "Remove from global skills").option("--claude", "Claude Code (.claude/skills/)").option("--cursor", "Cursor (.cursor/skills/)").option("--universal", "Universal (.agents/skills/)").option("--antigravity", "Antigravity (.agent/skills/)").description("Remove an installed skill").action(async (name, options) => {
|
|
@@ -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,36 +2340,59 @@ 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) {
|
|
2321
|
-
results.push({ label,
|
|
2347
|
+
results.push({ label, displayPath: dir, dir, source: ide, skills });
|
|
2322
2348
|
}
|
|
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
|
-
results.push({
|
|
2355
|
+
results.push({
|
|
2356
|
+
label: UNIVERSAL_AGENTS_LABEL,
|
|
2357
|
+
displayPath: universalPath,
|
|
2358
|
+
dir: universalDir,
|
|
2359
|
+
source: "universal",
|
|
2360
|
+
skills: universalSkills
|
|
2361
|
+
});
|
|
2330
2362
|
}
|
|
2331
2363
|
for (const ide of VENDOR_SPECIFIC_AGENTS) {
|
|
2332
2364
|
const pathMap = scope === "global" ? IDE_GLOBAL_PATHS : IDE_PATHS;
|
|
2333
|
-
const dir =
|
|
2365
|
+
const dir = join6(baseDir, pathMap[ide]);
|
|
2334
2366
|
const skills = await scanDir(dir);
|
|
2335
2367
|
if (skills.length > 0) {
|
|
2336
|
-
results.push({
|
|
2368
|
+
results.push({
|
|
2369
|
+
label: IDE_NAMES[ide],
|
|
2370
|
+
displayPath: pathMap[ide],
|
|
2371
|
+
dir,
|
|
2372
|
+
source: ide,
|
|
2373
|
+
skills
|
|
2374
|
+
});
|
|
2337
2375
|
}
|
|
2338
2376
|
}
|
|
2339
2377
|
}
|
|
2378
|
+
if (options.json) {
|
|
2379
|
+
const skills = results.flatMap(
|
|
2380
|
+
(result) => result.skills.map((name) => ({
|
|
2381
|
+
name,
|
|
2382
|
+
path: join6(result.dir, name),
|
|
2383
|
+
source: result.source
|
|
2384
|
+
}))
|
|
2385
|
+
);
|
|
2386
|
+
console.log(JSON.stringify({ skills }, null, 2));
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2340
2389
|
if (results.length === 0) {
|
|
2341
2390
|
log.warn("No skills installed");
|
|
2342
2391
|
return;
|
|
2343
2392
|
}
|
|
2344
2393
|
log.blank();
|
|
2345
|
-
for (const { label,
|
|
2346
|
-
log.plain(`${pc7.bold(label)} ${pc7.dim(
|
|
2394
|
+
for (const { label, displayPath, skills } of results) {
|
|
2395
|
+
log.plain(`${pc7.bold(label)} ${pc7.dim(displayPath)}`);
|
|
2347
2396
|
for (const skill of skills) {
|
|
2348
2397
|
log.plain(` ${pc7.green(skill)}`);
|
|
2349
2398
|
}
|
|
@@ -2358,7 +2407,13 @@ async function removeCommand(name, options) {
|
|
|
2358
2407
|
return;
|
|
2359
2408
|
}
|
|
2360
2409
|
const skillsDir = getTargetDirFromSelection(target.ide, target.scope);
|
|
2361
|
-
|
|
2410
|
+
let skillPath;
|
|
2411
|
+
try {
|
|
2412
|
+
skillPath = assertSkillNameInRoot(skillsDir, name);
|
|
2413
|
+
} catch {
|
|
2414
|
+
log.error(`Invalid skill name: ${name}`);
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2362
2417
|
try {
|
|
2363
2418
|
await rm2(skillPath, { recursive: true });
|
|
2364
2419
|
log.success(`Removed skill: ${name}`);
|
|
@@ -2539,7 +2594,7 @@ ${headerLine}`,
|
|
|
2539
2594
|
}
|
|
2540
2595
|
throw dirErr;
|
|
2541
2596
|
}
|
|
2542
|
-
const primarySkillDir =
|
|
2597
|
+
const primarySkillDir = join6(primaryDir, skill.name);
|
|
2543
2598
|
for (const targetDir of symlinkDirs) {
|
|
2544
2599
|
try {
|
|
2545
2600
|
await symlinkSkill(skill.name, primarySkillDir, targetDir);
|
|
@@ -2567,7 +2622,7 @@ ${headerLine}`,
|
|
|
2567
2622
|
log.blank();
|
|
2568
2623
|
log.warn("Fix permissions with:");
|
|
2569
2624
|
for (const dir of failedDirs) {
|
|
2570
|
-
const parentDir =
|
|
2625
|
+
const parentDir = join6(dir, "..");
|
|
2571
2626
|
log.dim(` sudo chown -R $(whoami) "${parentDir}"`);
|
|
2572
2627
|
}
|
|
2573
2628
|
log.blank();
|
|
@@ -2584,12 +2639,12 @@ import pc8 from "picocolors";
|
|
|
2584
2639
|
import ora4 from "ora";
|
|
2585
2640
|
import { select as select3 } from "@inquirer/prompts";
|
|
2586
2641
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
2587
|
-
import { dirname as
|
|
2642
|
+
import { dirname as dirname6, join as join8 } from "path";
|
|
2588
2643
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2589
2644
|
|
|
2590
2645
|
// src/setup/agents.ts
|
|
2591
2646
|
import { access as access2 } from "fs/promises";
|
|
2592
|
-
import { join as
|
|
2647
|
+
import { join as join7 } from "path";
|
|
2593
2648
|
import { homedir as homedir5 } from "os";
|
|
2594
2649
|
var SETUP_AGENT_NAMES = {
|
|
2595
2650
|
claude: "Claude Code",
|
|
@@ -2603,14 +2658,25 @@ var AUTH_MODE_LABELS = {
|
|
|
2603
2658
|
"api-key": "API Key"
|
|
2604
2659
|
};
|
|
2605
2660
|
var MCP_BASE_URL = "https://mcp.context7.com";
|
|
2661
|
+
var STDIO_PACKAGE = "@upstash/context7-mcp";
|
|
2662
|
+
function stdioArgs(auth) {
|
|
2663
|
+
const args = ["-y", STDIO_PACKAGE];
|
|
2664
|
+
if (auth.mode === "api-key" && auth.apiKey) {
|
|
2665
|
+
args.push("--api-key", auth.apiKey);
|
|
2666
|
+
}
|
|
2667
|
+
return args;
|
|
2668
|
+
}
|
|
2669
|
+
function stdioEntry(auth) {
|
|
2670
|
+
return { command: "npx", args: stdioArgs(auth) };
|
|
2671
|
+
}
|
|
2606
2672
|
function claudeConfigDir() {
|
|
2607
|
-
return process.env.CLAUDE_CONFIG_DIR ||
|
|
2673
|
+
return process.env.CLAUDE_CONFIG_DIR || join7(homedir5(), ".claude");
|
|
2608
2674
|
}
|
|
2609
2675
|
function claudeGlobalMcpPath() {
|
|
2610
2676
|
if (process.env.CLAUDE_CONFIG_DIR) {
|
|
2611
|
-
return
|
|
2677
|
+
return join7(claudeConfigDir(), ".claude.json");
|
|
2612
2678
|
}
|
|
2613
|
-
return
|
|
2679
|
+
return join7(homedir5(), ".claude.json");
|
|
2614
2680
|
}
|
|
2615
2681
|
function mcpUrl(auth) {
|
|
2616
2682
|
return auth.mode === "oauth" ? `${MCP_BASE_URL}/mcp/oauth` : `${MCP_BASE_URL}/mcp`;
|
|
@@ -2631,16 +2697,16 @@ var agents = {
|
|
|
2631
2697
|
return [claudeGlobalMcpPath()];
|
|
2632
2698
|
},
|
|
2633
2699
|
configKey: "mcpServers",
|
|
2634
|
-
buildEntry: (auth) => withHeaders({ type: "http", url: mcpUrl(auth) }, auth)
|
|
2700
|
+
buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ type: "http", url: mcpUrl(auth) }, auth)
|
|
2635
2701
|
},
|
|
2636
2702
|
rule: {
|
|
2637
2703
|
kind: "file",
|
|
2638
|
-
dir: (scope) => scope === "global" ?
|
|
2704
|
+
dir: (scope) => scope === "global" ? join7(claudeConfigDir(), "rules") : join7(".claude", "rules"),
|
|
2639
2705
|
filename: "context7.md"
|
|
2640
2706
|
},
|
|
2641
2707
|
skill: {
|
|
2642
2708
|
name: "context7-mcp",
|
|
2643
|
-
dir: (scope) => scope === "global" ?
|
|
2709
|
+
dir: (scope) => scope === "global" ? join7(claudeConfigDir(), "skills") : join7(".claude", "skills")
|
|
2644
2710
|
},
|
|
2645
2711
|
detect: {
|
|
2646
2712
|
projectPaths: [".mcp.json", ".claude"],
|
|
@@ -2653,23 +2719,23 @@ var agents = {
|
|
|
2653
2719
|
name: "cursor",
|
|
2654
2720
|
displayName: "Cursor",
|
|
2655
2721
|
mcp: {
|
|
2656
|
-
projectPaths: [
|
|
2657
|
-
globalPaths: [
|
|
2722
|
+
projectPaths: [join7(".cursor", "mcp.json")],
|
|
2723
|
+
globalPaths: [join7(homedir5(), ".cursor", "mcp.json")],
|
|
2658
2724
|
configKey: "mcpServers",
|
|
2659
|
-
buildEntry: (auth) => withHeaders({ url: mcpUrl(auth) }, auth)
|
|
2725
|
+
buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ url: mcpUrl(auth) }, auth)
|
|
2660
2726
|
},
|
|
2661
2727
|
rule: {
|
|
2662
2728
|
kind: "file",
|
|
2663
|
-
dir: (scope) => scope === "global" ?
|
|
2729
|
+
dir: (scope) => scope === "global" ? join7(homedir5(), ".cursor", "rules") : join7(".cursor", "rules"),
|
|
2664
2730
|
filename: "context7.mdc"
|
|
2665
2731
|
},
|
|
2666
2732
|
skill: {
|
|
2667
2733
|
name: "context7-mcp",
|
|
2668
|
-
dir: (scope) => scope === "global" ?
|
|
2734
|
+
dir: (scope) => scope === "global" ? join7(homedir5(), ".cursor", "skills") : join7(".cursor", "skills")
|
|
2669
2735
|
},
|
|
2670
2736
|
detect: {
|
|
2671
2737
|
projectPaths: [".cursor"],
|
|
2672
|
-
globalPaths: [
|
|
2738
|
+
globalPaths: [join7(homedir5(), ".cursor")]
|
|
2673
2739
|
}
|
|
2674
2740
|
},
|
|
2675
2741
|
opencode: {
|
|
@@ -2678,78 +2744,72 @@ var agents = {
|
|
|
2678
2744
|
mcp: {
|
|
2679
2745
|
projectPaths: ["opencode.json", "opencode.jsonc", ".opencode.json", ".opencode.jsonc"],
|
|
2680
2746
|
globalPaths: [
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2747
|
+
join7(homedir5(), ".config", "opencode", "opencode.json"),
|
|
2748
|
+
join7(homedir5(), ".config", "opencode", "opencode.jsonc"),
|
|
2749
|
+
join7(homedir5(), ".config", "opencode", ".opencode.json"),
|
|
2750
|
+
join7(homedir5(), ".config", "opencode", ".opencode.jsonc")
|
|
2685
2751
|
],
|
|
2686
2752
|
configKey: "mcp",
|
|
2687
|
-
buildEntry: (auth) => withHeaders({ type: "remote", url: mcpUrl(auth), enabled: true }, auth)
|
|
2753
|
+
buildEntry: (auth, transport) => transport === "stdio" ? { type: "local", command: ["npx", ...stdioArgs(auth)], enabled: true } : withHeaders({ type: "remote", url: mcpUrl(auth), enabled: true }, auth)
|
|
2688
2754
|
},
|
|
2689
2755
|
rule: {
|
|
2690
2756
|
kind: "append",
|
|
2691
|
-
file: (scope) => scope === "global" ?
|
|
2757
|
+
file: (scope) => scope === "global" ? join7(homedir5(), ".config", "opencode", "AGENTS.md") : "AGENTS.md",
|
|
2692
2758
|
sectionMarker: "<!-- context7 -->"
|
|
2693
2759
|
},
|
|
2694
2760
|
skill: {
|
|
2695
2761
|
name: "context7-mcp",
|
|
2696
|
-
dir: (scope) => scope === "global" ?
|
|
2762
|
+
dir: (scope) => scope === "global" ? join7(homedir5(), ".agents", "skills") : join7(".agents", "skills")
|
|
2697
2763
|
},
|
|
2698
2764
|
detect: {
|
|
2699
2765
|
projectPaths: ["opencode.json", "opencode.jsonc", ".opencode.json", ".opencode.jsonc"],
|
|
2700
|
-
globalPaths: [
|
|
2766
|
+
globalPaths: [join7(homedir5(), ".config", "opencode")]
|
|
2701
2767
|
}
|
|
2702
2768
|
},
|
|
2703
2769
|
codex: {
|
|
2704
2770
|
name: "codex",
|
|
2705
2771
|
displayName: "Codex",
|
|
2706
2772
|
mcp: {
|
|
2707
|
-
projectPaths: [
|
|
2708
|
-
globalPaths: [
|
|
2773
|
+
projectPaths: [join7(".codex", "config.toml")],
|
|
2774
|
+
globalPaths: [join7(homedir5(), ".codex", "config.toml")],
|
|
2709
2775
|
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
|
-
}
|
|
2776
|
+
buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ type: "http", url: mcpUrl(auth) }, auth)
|
|
2717
2777
|
},
|
|
2718
2778
|
rule: {
|
|
2719
2779
|
kind: "append",
|
|
2720
|
-
file: (scope) => scope === "global" ?
|
|
2780
|
+
file: (scope) => scope === "global" ? join7(homedir5(), ".codex", "AGENTS.md") : "AGENTS.md",
|
|
2721
2781
|
sectionMarker: "<!-- context7 -->"
|
|
2722
2782
|
},
|
|
2723
2783
|
skill: {
|
|
2724
2784
|
name: "context7-mcp",
|
|
2725
|
-
dir: (scope) => scope === "global" ?
|
|
2785
|
+
dir: (scope) => scope === "global" ? join7(homedir5(), ".agents", "skills") : join7(".agents", "skills")
|
|
2726
2786
|
},
|
|
2727
2787
|
detect: {
|
|
2728
2788
|
projectPaths: [".codex"],
|
|
2729
|
-
globalPaths: [
|
|
2789
|
+
globalPaths: [join7(homedir5(), ".codex")]
|
|
2730
2790
|
}
|
|
2731
2791
|
},
|
|
2732
2792
|
gemini: {
|
|
2733
2793
|
name: "gemini",
|
|
2734
2794
|
displayName: "Gemini CLI",
|
|
2735
2795
|
mcp: {
|
|
2736
|
-
projectPaths: [
|
|
2737
|
-
globalPaths: [
|
|
2796
|
+
projectPaths: [join7(".gemini", "settings.json")],
|
|
2797
|
+
globalPaths: [join7(homedir5(), ".gemini", "settings.json")],
|
|
2738
2798
|
configKey: "mcpServers",
|
|
2739
|
-
buildEntry: (auth) => withHeaders({ httpUrl: mcpUrl(auth) }, auth)
|
|
2799
|
+
buildEntry: (auth, transport) => transport === "stdio" ? stdioEntry(auth) : withHeaders({ httpUrl: mcpUrl(auth) }, auth)
|
|
2740
2800
|
},
|
|
2741
2801
|
rule: {
|
|
2742
2802
|
kind: "append",
|
|
2743
|
-
file: (scope) => scope === "global" ?
|
|
2803
|
+
file: (scope) => scope === "global" ? join7(homedir5(), ".gemini", "GEMINI.md") : "GEMINI.md",
|
|
2744
2804
|
sectionMarker: "<!-- context7 -->"
|
|
2745
2805
|
},
|
|
2746
2806
|
skill: {
|
|
2747
2807
|
name: "context7-mcp",
|
|
2748
|
-
dir: (scope) => scope === "global" ?
|
|
2808
|
+
dir: (scope) => scope === "global" ? join7(homedir5(), ".gemini", "skills") : join7(".gemini", "skills")
|
|
2749
2809
|
},
|
|
2750
2810
|
detect: {
|
|
2751
2811
|
projectPaths: [".gemini"],
|
|
2752
|
-
globalPaths: [
|
|
2812
|
+
globalPaths: [join7(homedir5(), ".gemini")]
|
|
2753
2813
|
}
|
|
2754
2814
|
}
|
|
2755
2815
|
};
|
|
@@ -2770,7 +2830,7 @@ async function detectAgents(scope) {
|
|
|
2770
2830
|
for (const agent of Object.values(agents)) {
|
|
2771
2831
|
const paths = scope === "global" ? agent.detect.globalPaths : agent.detect.projectPaths;
|
|
2772
2832
|
for (const p of paths) {
|
|
2773
|
-
const fullPath = scope === "global" ? p :
|
|
2833
|
+
const fullPath = scope === "global" ? p : join7(process.cwd(), p);
|
|
2774
2834
|
if (await pathExists(fullPath)) {
|
|
2775
2835
|
detected.push(agent.name);
|
|
2776
2836
|
break;
|
|
@@ -2869,7 +2929,7 @@ function customizeSkillFilesForAgent(agent, skillName, files) {
|
|
|
2869
2929
|
|
|
2870
2930
|
// src/setup/mcp-writer.ts
|
|
2871
2931
|
import { access as access3, readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
2872
|
-
import { dirname as
|
|
2932
|
+
import { dirname as dirname5 } from "path";
|
|
2873
2933
|
function stripJsonComments(text) {
|
|
2874
2934
|
let result = "";
|
|
2875
2935
|
let i = 0;
|
|
@@ -2948,7 +3008,7 @@ async function resolveMcpPath(candidates) {
|
|
|
2948
3008
|
return candidates[0];
|
|
2949
3009
|
}
|
|
2950
3010
|
async function writeJsonConfig(filePath, config) {
|
|
2951
|
-
await mkdir3(
|
|
3011
|
+
await mkdir3(dirname5(filePath), { recursive: true });
|
|
2952
3012
|
await writeFile3(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
2953
3013
|
}
|
|
2954
3014
|
async function readTomlServerExists(filePath, serverName) {
|
|
@@ -2959,6 +3019,70 @@ async function readTomlServerExists(filePath, serverName) {
|
|
|
2959
3019
|
return false;
|
|
2960
3020
|
}
|
|
2961
3021
|
}
|
|
3022
|
+
async function readTomlServerEntry(filePath, serverName) {
|
|
3023
|
+
let raw;
|
|
3024
|
+
try {
|
|
3025
|
+
raw = await readFile3(filePath, "utf-8");
|
|
3026
|
+
} catch {
|
|
3027
|
+
return void 0;
|
|
3028
|
+
}
|
|
3029
|
+
const sectionHeader = `[mcp_servers.${serverName}]`;
|
|
3030
|
+
const startIdx = raw.indexOf(sectionHeader);
|
|
3031
|
+
if (startIdx === -1) return void 0;
|
|
3032
|
+
const rest = raw.slice(startIdx + sectionHeader.length);
|
|
3033
|
+
const nextHeader = /^\[/m.exec(rest);
|
|
3034
|
+
const block = nextHeader ? rest.slice(0, nextHeader.index) : rest;
|
|
3035
|
+
const entry = {};
|
|
3036
|
+
const lineRe = /^([A-Za-z_][\w-]*)\s*=\s*(.+?)\s*$/gm;
|
|
3037
|
+
let lineMatch;
|
|
3038
|
+
while ((lineMatch = lineRe.exec(block)) !== null) {
|
|
3039
|
+
const [, key, valueText] = lineMatch;
|
|
3040
|
+
try {
|
|
3041
|
+
entry[key] = JSON.parse(valueText);
|
|
3042
|
+
} catch {
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
return Object.keys(entry).length > 0 ? entry : void 0;
|
|
3046
|
+
}
|
|
3047
|
+
function isStdioContext7Entry(entry) {
|
|
3048
|
+
if (!entry || typeof entry !== "object") return false;
|
|
3049
|
+
const e = entry;
|
|
3050
|
+
const refs = (s) => typeof s === "string" && s.includes(STDIO_PACKAGE);
|
|
3051
|
+
if (Array.isArray(e.command)) {
|
|
3052
|
+
return e.command.some(refs);
|
|
3053
|
+
}
|
|
3054
|
+
if (typeof e.command === "string" && Array.isArray(e.args)) {
|
|
3055
|
+
return e.args.some(refs);
|
|
3056
|
+
}
|
|
3057
|
+
return false;
|
|
3058
|
+
}
|
|
3059
|
+
function getJsonServerEntry(config, configKey, serverName) {
|
|
3060
|
+
const section = config[configKey];
|
|
3061
|
+
if (!section || typeof section !== "object") return void 0;
|
|
3062
|
+
const entry = section[serverName];
|
|
3063
|
+
return entry && typeof entry === "object" ? entry : void 0;
|
|
3064
|
+
}
|
|
3065
|
+
function stripApiKeyPair(args) {
|
|
3066
|
+
const result = [];
|
|
3067
|
+
for (let i = 0; i < args.length; i++) {
|
|
3068
|
+
if (args[i] === "--api-key") {
|
|
3069
|
+
i++;
|
|
3070
|
+
continue;
|
|
3071
|
+
}
|
|
3072
|
+
result.push(args[i]);
|
|
3073
|
+
}
|
|
3074
|
+
return result;
|
|
3075
|
+
}
|
|
3076
|
+
function patchStdioApiKey(entry, apiKey) {
|
|
3077
|
+
if (Array.isArray(entry.command)) {
|
|
3078
|
+
const cmd = stripApiKeyPair(entry.command);
|
|
3079
|
+
if (apiKey) cmd.push("--api-key", apiKey);
|
|
3080
|
+
return { ...entry, command: cmd };
|
|
3081
|
+
}
|
|
3082
|
+
const args = Array.isArray(entry.args) ? stripApiKeyPair(entry.args) : [];
|
|
3083
|
+
if (apiKey) args.push("--api-key", apiKey);
|
|
3084
|
+
return { ...entry, args };
|
|
3085
|
+
}
|
|
2962
3086
|
function buildTomlServerBlock(serverName, entry) {
|
|
2963
3087
|
const lines = [`[mcp_servers.${serverName}]`];
|
|
2964
3088
|
const headers = entry.headers;
|
|
@@ -3004,11 +3128,11 @@ async function appendTomlServer(filePath, serverName, entry) {
|
|
|
3004
3128
|
const before = rawBefore.length > 0 ? rawBefore + "\n\n" : "";
|
|
3005
3129
|
const after = rawAfter.length > 0 ? "\n" + rawAfter : "";
|
|
3006
3130
|
const content = before + block + after;
|
|
3007
|
-
await mkdir3(
|
|
3131
|
+
await mkdir3(dirname5(filePath), { recursive: true });
|
|
3008
3132
|
await writeFile3(filePath, content, "utf-8");
|
|
3009
3133
|
} else {
|
|
3010
3134
|
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
|
|
3011
|
-
await mkdir3(
|
|
3135
|
+
await mkdir3(dirname5(filePath), { recursive: true });
|
|
3012
3136
|
await writeFile3(filePath, existing + separator + block, "utf-8");
|
|
3013
3137
|
}
|
|
3014
3138
|
return { alreadyExists };
|
|
@@ -3041,13 +3165,16 @@ async function removeTomlServer(filePath, serverName) {
|
|
|
3041
3165
|
const rawBefore = existing.slice(0, startIdx).replace(/\n+$/, "");
|
|
3042
3166
|
const rawAfter = existing.slice(startIdx + sectionHeader.length + endOffset).replace(/^\n+/, "");
|
|
3043
3167
|
const content = [rawBefore, rawAfter].filter(Boolean).join("\n\n");
|
|
3044
|
-
await mkdir3(
|
|
3168
|
+
await mkdir3(dirname5(filePath), { recursive: true });
|
|
3045
3169
|
await writeFile3(filePath, content.length > 0 ? `${content}
|
|
3046
3170
|
` : "", "utf-8");
|
|
3047
3171
|
return { removed: true };
|
|
3048
3172
|
}
|
|
3049
3173
|
|
|
3050
3174
|
// src/commands/setup.ts
|
|
3175
|
+
function resolveTransport(options) {
|
|
3176
|
+
return options.stdio ? "stdio" : "http";
|
|
3177
|
+
}
|
|
3051
3178
|
var CHECKBOX_THEME = {
|
|
3052
3179
|
style: {
|
|
3053
3180
|
highlight: (text) => pc8.green(text),
|
|
@@ -3064,7 +3191,7 @@ function getSelectedAgents(options) {
|
|
|
3064
3191
|
return agents2;
|
|
3065
3192
|
}
|
|
3066
3193
|
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) => {
|
|
3194
|
+
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
3195
|
await setupCommand(options);
|
|
3069
3196
|
});
|
|
3070
3197
|
}
|
|
@@ -3105,7 +3232,7 @@ async function resolveAuth(options) {
|
|
|
3105
3232
|
}
|
|
3106
3233
|
async function resolveMode(options) {
|
|
3107
3234
|
if (options.cli) return "cli";
|
|
3108
|
-
if (options.mcp || options.yes || options.oauth || options.apiKey) return "mcp";
|
|
3235
|
+
if (options.mcp || options.yes || options.oauth || options.apiKey || options.stdio) return "mcp";
|
|
3109
3236
|
return select3({
|
|
3110
3237
|
message: "How should your agent access Context7?",
|
|
3111
3238
|
choices: [
|
|
@@ -3181,13 +3308,13 @@ async function installRule(agentName, mode, scope) {
|
|
|
3181
3308
|
const rule = agent.rule;
|
|
3182
3309
|
const content = await getRuleContent(mode, agentName);
|
|
3183
3310
|
if (rule.kind === "file") {
|
|
3184
|
-
const ruleDir = scope === "global" ? rule.dir("global") :
|
|
3185
|
-
const rulePath =
|
|
3186
|
-
await mkdir4(
|
|
3311
|
+
const ruleDir = scope === "global" ? rule.dir("global") : join8(process.cwd(), rule.dir("project"));
|
|
3312
|
+
const rulePath = join8(ruleDir, rule.filename);
|
|
3313
|
+
await mkdir4(dirname6(rulePath), { recursive: true });
|
|
3187
3314
|
await writeFile4(rulePath, content, "utf-8");
|
|
3188
3315
|
return { status: "installed", path: rulePath };
|
|
3189
3316
|
}
|
|
3190
|
-
const filePath = scope === "global" ? rule.file("global") :
|
|
3317
|
+
const filePath = scope === "global" ? rule.file("global") : join8(process.cwd(), rule.file("project"));
|
|
3191
3318
|
const escapedMarker = rule.sectionMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3192
3319
|
const section = `${rule.sectionMarker}
|
|
3193
3320
|
${content}${rule.sectionMarker}`;
|
|
@@ -3203,30 +3330,37 @@ ${content}${rule.sectionMarker}`;
|
|
|
3203
3330
|
return { status: "updated", path: filePath };
|
|
3204
3331
|
}
|
|
3205
3332
|
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
|
|
3206
|
-
await mkdir4(
|
|
3333
|
+
await mkdir4(dirname6(filePath), { recursive: true });
|
|
3207
3334
|
await writeFile4(filePath, existing + separator + section + "\n", "utf-8");
|
|
3208
3335
|
return { status: "installed", path: filePath };
|
|
3209
3336
|
}
|
|
3210
|
-
|
|
3337
|
+
function resolveEntryToWrite(agent, auth, transport, existingEntry) {
|
|
3338
|
+
if (transport === "stdio" && existingEntry && isStdioContext7Entry(existingEntry)) {
|
|
3339
|
+
const apiKey = auth.mode === "api-key" ? auth.apiKey : void 0;
|
|
3340
|
+
return patchStdioApiKey(existingEntry, apiKey);
|
|
3341
|
+
}
|
|
3342
|
+
return agent.mcp.buildEntry(auth, transport);
|
|
3343
|
+
}
|
|
3344
|
+
async function setupAgent(agentName, auth, transport, scope) {
|
|
3211
3345
|
const agent = getAgent(agentName);
|
|
3212
|
-
const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((p) =>
|
|
3346
|
+
const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((p) => join8(process.cwd(), p));
|
|
3213
3347
|
const mcpPath = await resolveMcpPath(mcpCandidates);
|
|
3214
3348
|
let mcpStatus;
|
|
3215
3349
|
try {
|
|
3216
3350
|
if (mcpPath.endsWith(".toml")) {
|
|
3217
|
-
const
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
agent.mcp.buildEntry(auth)
|
|
3221
|
-
);
|
|
3351
|
+
const existingTomlEntry = transport === "stdio" ? await readTomlServerEntry(mcpPath, "context7") : void 0;
|
|
3352
|
+
const entry = resolveEntryToWrite(agent, auth, transport, existingTomlEntry);
|
|
3353
|
+
const { alreadyExists } = await appendTomlServer(mcpPath, "context7", entry);
|
|
3222
3354
|
mcpStatus = alreadyExists ? `reconfigured with ${AUTH_MODE_LABELS[auth.mode]}` : `configured with ${AUTH_MODE_LABELS[auth.mode]}`;
|
|
3223
3355
|
} else {
|
|
3224
3356
|
const existing = await readJsonConfig(mcpPath);
|
|
3357
|
+
const existingJsonEntry = transport === "stdio" ? getJsonServerEntry(existing, agent.mcp.configKey, "context7") : void 0;
|
|
3358
|
+
const entry = resolveEntryToWrite(agent, auth, transport, existingJsonEntry);
|
|
3225
3359
|
const { config, alreadyExists } = mergeServerEntry(
|
|
3226
3360
|
existing,
|
|
3227
3361
|
agent.mcp.configKey,
|
|
3228
3362
|
"context7",
|
|
3229
|
-
|
|
3363
|
+
entry
|
|
3230
3364
|
);
|
|
3231
3365
|
mcpStatus = alreadyExists ? `reconfigured with ${AUTH_MODE_LABELS[auth.mode]}` : `configured with ${AUTH_MODE_LABELS[auth.mode]}`;
|
|
3232
3366
|
await writeJsonConfig(mcpPath, config);
|
|
@@ -3244,8 +3378,8 @@ async function setupAgent(agentName, auth, scope) {
|
|
|
3244
3378
|
ruleStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
3245
3379
|
rulePath = "";
|
|
3246
3380
|
}
|
|
3247
|
-
const skillDir = scope === "global" ? agent.skill.dir("global") :
|
|
3248
|
-
const skillPath =
|
|
3381
|
+
const skillDir = scope === "global" ? agent.skill.dir("global") : join8(process.cwd(), agent.skill.dir("project"));
|
|
3382
|
+
const skillPath = join8(skillDir, agent.skill.name, "SKILL.md");
|
|
3249
3383
|
let skillStatus;
|
|
3250
3384
|
try {
|
|
3251
3385
|
const downloadData = await downloadSkill("/upstash/context7", agent.skill.name);
|
|
@@ -3268,6 +3402,11 @@ async function setupAgent(agentName, auth, scope) {
|
|
|
3268
3402
|
};
|
|
3269
3403
|
}
|
|
3270
3404
|
async function setupMcp(agents2, options, scope) {
|
|
3405
|
+
const transport = resolveTransport(options);
|
|
3406
|
+
if (transport === "stdio" && options.oauth) {
|
|
3407
|
+
log.error("--stdio is incompatible with --oauth (OAuth uses the hosted HTTP endpoint).");
|
|
3408
|
+
return;
|
|
3409
|
+
}
|
|
3271
3410
|
const auth = await resolveAuth(options);
|
|
3272
3411
|
if (!auth) {
|
|
3273
3412
|
log.warn("Setup cancelled");
|
|
@@ -3278,7 +3417,7 @@ async function setupMcp(agents2, options, scope) {
|
|
|
3278
3417
|
const results = [];
|
|
3279
3418
|
for (const agentName of agents2) {
|
|
3280
3419
|
spinner.text = `Setting up ${getAgent(agentName).displayName}...`;
|
|
3281
|
-
results.push(await setupAgent(agentName, auth, scope));
|
|
3420
|
+
results.push(await setupAgent(agentName, auth, transport, scope));
|
|
3282
3421
|
}
|
|
3283
3422
|
spinner.succeed("Context7 setup complete");
|
|
3284
3423
|
log.blank();
|
|
@@ -3295,7 +3434,7 @@ async function setupMcp(agents2, options, scope) {
|
|
|
3295
3434
|
log.plain(` ${pc8.dim(r.skillPath)}`);
|
|
3296
3435
|
if (r.skillStatus.includes("EACCES")) {
|
|
3297
3436
|
log.plain(
|
|
3298
|
-
` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${
|
|
3437
|
+
` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname6(dirname6(r.skillPath))}`)}`
|
|
3299
3438
|
);
|
|
3300
3439
|
}
|
|
3301
3440
|
}
|
|
@@ -3305,7 +3444,7 @@ async function setupMcp(agents2, options, scope) {
|
|
|
3305
3444
|
}
|
|
3306
3445
|
async function setupCliAgent(agentName, scope, downloadData) {
|
|
3307
3446
|
const agent = getAgent(agentName);
|
|
3308
|
-
const skillDir = scope === "global" ? agent.skill.dir("global") :
|
|
3447
|
+
const skillDir = scope === "global" ? agent.skill.dir("global") : join8(process.cwd(), agent.skill.dir("project"));
|
|
3309
3448
|
let skillStatus;
|
|
3310
3449
|
try {
|
|
3311
3450
|
const files = customizeSkillFilesForAgent(agentName, "find-docs", downloadData.files);
|
|
@@ -3314,7 +3453,7 @@ async function setupCliAgent(agentName, scope, downloadData) {
|
|
|
3314
3453
|
} catch (err) {
|
|
3315
3454
|
skillStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
3316
3455
|
}
|
|
3317
|
-
const skillPath =
|
|
3456
|
+
const skillPath = join8(skillDir, "find-docs");
|
|
3318
3457
|
let ruleStatus;
|
|
3319
3458
|
let rulePath;
|
|
3320
3459
|
try {
|
|
@@ -3356,7 +3495,7 @@ async function setupCli(options) {
|
|
|
3356
3495
|
log.plain(` ${pc8.dim(r.skillPath)}`);
|
|
3357
3496
|
if (r.skillStatus.includes("EACCES")) {
|
|
3358
3497
|
log.plain(
|
|
3359
|
-
` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${
|
|
3498
|
+
` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname6(dirname6(r.skillPath))}`)}`
|
|
3360
3499
|
);
|
|
3361
3500
|
}
|
|
3362
3501
|
const ruleIcon = r.ruleStatus === "installed" || r.ruleStatus === "updated" ? pc8.green("+") : pc8.dim("~");
|
|
@@ -3388,7 +3527,7 @@ async function setupCommand(options) {
|
|
|
3388
3527
|
// src/commands/remove.ts
|
|
3389
3528
|
import pc9 from "picocolors";
|
|
3390
3529
|
import ora5 from "ora";
|
|
3391
|
-
import { join as
|
|
3530
|
+
import { join as join9 } from "path";
|
|
3392
3531
|
import { access as access4, readFile as readFile5, rm as rm3, writeFile as writeFile5 } from "fs/promises";
|
|
3393
3532
|
var CHECKBOX_THEME2 = {
|
|
3394
3533
|
style: {
|
|
@@ -3496,7 +3635,7 @@ async function pathExists2(path2) {
|
|
|
3496
3635
|
}
|
|
3497
3636
|
async function hasMcpConfig(agentName, scope) {
|
|
3498
3637
|
const agent = getAgent(agentName);
|
|
3499
|
-
const candidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((path2) =>
|
|
3638
|
+
const candidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((path2) => join9(process.cwd(), path2));
|
|
3500
3639
|
const mcpPath = await resolveMcpPath(candidates);
|
|
3501
3640
|
if (mcpPath.endsWith(".toml")) {
|
|
3502
3641
|
return readTomlServerExists(mcpPath, "context7");
|
|
@@ -3517,10 +3656,10 @@ async function hasRule(agentName, scope) {
|
|
|
3517
3656
|
const agent = getAgent(agentName);
|
|
3518
3657
|
const rule = agent.rule;
|
|
3519
3658
|
if (rule.kind === "file") {
|
|
3520
|
-
const ruleDir = scope === "global" ? rule.dir("global") :
|
|
3521
|
-
return pathExists2(
|
|
3659
|
+
const ruleDir = scope === "global" ? rule.dir("global") : join9(process.cwd(), rule.dir("project"));
|
|
3660
|
+
return pathExists2(join9(ruleDir, rule.filename));
|
|
3522
3661
|
}
|
|
3523
|
-
const filePath = scope === "global" ? rule.file("global") :
|
|
3662
|
+
const filePath = scope === "global" ? rule.file("global") : join9(process.cwd(), rule.file("project"));
|
|
3524
3663
|
try {
|
|
3525
3664
|
const existing = await readFile5(filePath, "utf-8");
|
|
3526
3665
|
return existing.includes(CONTEXT7_SECTION_MARKER);
|
|
@@ -3530,8 +3669,8 @@ async function hasRule(agentName, scope) {
|
|
|
3530
3669
|
}
|
|
3531
3670
|
async function hasSkill(agentName, scope, skillName) {
|
|
3532
3671
|
const agent = getAgent(agentName);
|
|
3533
|
-
const skillsDir = scope === "global" ? agent.skill.dir("global") :
|
|
3534
|
-
return pathExists2(
|
|
3672
|
+
const skillsDir = scope === "global" ? agent.skill.dir("global") : join9(process.cwd(), agent.skill.dir("project"));
|
|
3673
|
+
return pathExists2(join9(skillsDir, skillName));
|
|
3535
3674
|
}
|
|
3536
3675
|
async function detectAvailableModes(agents2, scope) {
|
|
3537
3676
|
let hasMcpArtifacts = false;
|
|
@@ -3583,7 +3722,7 @@ async function resolveModes(options, agents2, scope) {
|
|
|
3583
3722
|
}
|
|
3584
3723
|
async function uninstallMcp(agentName, scope) {
|
|
3585
3724
|
const agent = getAgent(agentName);
|
|
3586
|
-
const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((path2) =>
|
|
3725
|
+
const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((path2) => join9(process.cwd(), path2));
|
|
3587
3726
|
const mcpPath = await resolveMcpPath(mcpCandidates);
|
|
3588
3727
|
try {
|
|
3589
3728
|
if (mcpPath.endsWith(".toml")) {
|
|
@@ -3604,8 +3743,8 @@ async function uninstallRule(agentName, scope) {
|
|
|
3604
3743
|
const agent = getAgent(agentName);
|
|
3605
3744
|
const rule = agent.rule;
|
|
3606
3745
|
if (rule.kind === "file") {
|
|
3607
|
-
const rulePath = scope === "global" ? rule.dir("global") :
|
|
3608
|
-
const targetPath =
|
|
3746
|
+
const rulePath = scope === "global" ? rule.dir("global") : join9(process.cwd(), rule.dir("project"));
|
|
3747
|
+
const targetPath = join9(rulePath, rule.filename);
|
|
3609
3748
|
try {
|
|
3610
3749
|
await rm3(targetPath);
|
|
3611
3750
|
return { status: "removed", path: targetPath };
|
|
@@ -3615,7 +3754,7 @@ async function uninstallRule(agentName, scope) {
|
|
|
3615
3754
|
return { status: `failed: ${error.message}`, path: targetPath };
|
|
3616
3755
|
}
|
|
3617
3756
|
}
|
|
3618
|
-
const filePath = scope === "global" ? rule.file("global") :
|
|
3757
|
+
const filePath = scope === "global" ? rule.file("global") : join9(process.cwd(), rule.file("project"));
|
|
3619
3758
|
try {
|
|
3620
3759
|
const existing = await readFile5(filePath, "utf-8");
|
|
3621
3760
|
if (!existing.includes(CONTEXT7_SECTION_MARKER)) {
|
|
@@ -3638,10 +3777,10 @@ async function uninstallRule(agentName, scope) {
|
|
|
3638
3777
|
}
|
|
3639
3778
|
async function uninstallSkills(agentName, scope, skillNames) {
|
|
3640
3779
|
const agent = getAgent(agentName);
|
|
3641
|
-
const skillsDir = scope === "global" ? agent.skill.dir("global") :
|
|
3780
|
+
const skillsDir = scope === "global" ? agent.skill.dir("global") : join9(process.cwd(), agent.skill.dir("project"));
|
|
3642
3781
|
const results = [];
|
|
3643
3782
|
for (const skillName of skillNames) {
|
|
3644
|
-
const skillPath =
|
|
3783
|
+
const skillPath = join9(skillsDir, skillName);
|
|
3645
3784
|
try {
|
|
3646
3785
|
await rm3(skillPath, { recursive: true });
|
|
3647
3786
|
results.push({ name: skillName, status: "removed", path: skillPath });
|
|
@@ -3904,10 +4043,10 @@ import pc11 from "picocolors";
|
|
|
3904
4043
|
|
|
3905
4044
|
// src/utils/update-check.ts
|
|
3906
4045
|
import { homedir as homedir6 } from "os";
|
|
3907
|
-
import { dirname as
|
|
4046
|
+
import { dirname as dirname7, join as join10 } from "path";
|
|
3908
4047
|
import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
3909
4048
|
var DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3910
|
-
var UPDATE_STATE_FILE =
|
|
4049
|
+
var UPDATE_STATE_FILE = join10(homedir6(), ".context7", "cli-state.json");
|
|
3911
4050
|
function getStateFilePath(stateFile) {
|
|
3912
4051
|
return stateFile ?? UPDATE_STATE_FILE;
|
|
3913
4052
|
}
|
|
@@ -3921,7 +4060,7 @@ async function readUpdateState(stateFile) {
|
|
|
3921
4060
|
}
|
|
3922
4061
|
async function writeUpdateState(state, stateFile) {
|
|
3923
4062
|
const path2 = getStateFilePath(stateFile);
|
|
3924
|
-
await mkdir5(
|
|
4063
|
+
await mkdir5(dirname7(path2), { recursive: true });
|
|
3925
4064
|
await writeFile6(path2, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
3926
4065
|
}
|
|
3927
4066
|
function compareVersions(a, b) {
|
|
@@ -4098,13 +4237,13 @@ function registerUpgradeCommand(program2) {
|
|
|
4098
4237
|
});
|
|
4099
4238
|
}
|
|
4100
4239
|
function runCommand(command, args) {
|
|
4101
|
-
return new Promise((
|
|
4240
|
+
return new Promise((resolve3, reject) => {
|
|
4102
4241
|
const child = spawn2(command, args, {
|
|
4103
4242
|
stdio: "inherit",
|
|
4104
4243
|
shell: process.platform === "win32"
|
|
4105
4244
|
});
|
|
4106
4245
|
child.on("error", reject);
|
|
4107
|
-
child.on("close", (code) =>
|
|
4246
|
+
child.on("close", (code) => resolve3(code));
|
|
4108
4247
|
});
|
|
4109
4248
|
}
|
|
4110
4249
|
async function runUpgradePlan(plan) {
|