ctx7 0.3.3 → 0.3.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 +273 -186
- package/dist/index.js.map +1 -1
- package/package.json +1 -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 join7 } from "path";
|
|
13
13
|
|
|
14
14
|
// src/utils/parse-input.ts
|
|
15
15
|
function parseSkillInput(input2) {
|
|
@@ -115,6 +115,15 @@ async function downloadSkillFromGitHub(skill) {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// src/constants.ts
|
|
119
|
+
import { readFileSync } from "fs";
|
|
120
|
+
import { fileURLToPath } from "url";
|
|
121
|
+
import { dirname, join } from "path";
|
|
122
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
123
|
+
var pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
|
124
|
+
var VERSION = pkg.version;
|
|
125
|
+
var NAME = pkg.name;
|
|
126
|
+
|
|
118
127
|
// src/utils/api.ts
|
|
119
128
|
var baseUrl = "https://context7.com";
|
|
120
129
|
function getBaseUrl() {
|
|
@@ -290,7 +299,12 @@ async function handleGenerateResponse(response, libraryName, onEvent) {
|
|
|
290
299
|
return { content, libraryName: finalLibraryName, error };
|
|
291
300
|
}
|
|
292
301
|
function getAuthHeaders(accessToken) {
|
|
293
|
-
const headers = {
|
|
302
|
+
const headers = {
|
|
303
|
+
"X-Context7-Source": "cli",
|
|
304
|
+
"X-Context7-Client-IDE": "ctx7-cli",
|
|
305
|
+
"X-Context7-Client-Version": VERSION,
|
|
306
|
+
"X-Context7-Transport": "cli"
|
|
307
|
+
};
|
|
294
308
|
const apiKey = process.env.CONTEXT7_API_KEY;
|
|
295
309
|
if (apiKey) {
|
|
296
310
|
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
@@ -367,7 +381,7 @@ var log = {
|
|
|
367
381
|
import pc3 from "picocolors";
|
|
368
382
|
import { select, confirm } from "@inquirer/prompts";
|
|
369
383
|
import { access } from "fs/promises";
|
|
370
|
-
import { join, dirname } from "path";
|
|
384
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
371
385
|
import { homedir } from "os";
|
|
372
386
|
|
|
373
387
|
// src/utils/prompts.ts
|
|
@@ -378,14 +392,39 @@ function terminalLink(text, url, color) {
|
|
|
378
392
|
const colorFn = color ?? ((s) => s);
|
|
379
393
|
return `\x1B]8;;${url}\x07${colorFn(text)}\x1B]8;;\x07 ${pc2.white("\u2197")}`;
|
|
380
394
|
}
|
|
381
|
-
function
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
395
|
+
function formatPopularity(count) {
|
|
396
|
+
const filled = "\u2605";
|
|
397
|
+
const empty = "\u2606";
|
|
398
|
+
const max = 4;
|
|
399
|
+
let stars;
|
|
400
|
+
if (count === void 0 || count === 0) stars = 0;
|
|
401
|
+
else if (count < 100) stars = 1;
|
|
402
|
+
else if (count < 500) stars = 2;
|
|
403
|
+
else if (count < 1e3) stars = 3;
|
|
404
|
+
else stars = 4;
|
|
405
|
+
const filledPart = filled.repeat(stars);
|
|
406
|
+
const emptyPart = empty.repeat(max - stars);
|
|
407
|
+
if (stars === 0) return pc2.dim(emptyPart);
|
|
408
|
+
return pc2.yellow(filledPart) + pc2.dim(emptyPart);
|
|
409
|
+
}
|
|
410
|
+
function formatInstallRange(count) {
|
|
411
|
+
if (count === void 0 || count === 0) return "Unknown";
|
|
412
|
+
if (count < 100) return "<100";
|
|
413
|
+
if (count < 500) return "<500";
|
|
414
|
+
if (count < 1e3) return "<1,000";
|
|
415
|
+
return "1,000+";
|
|
416
|
+
}
|
|
417
|
+
function formatTrust(score) {
|
|
386
418
|
if (score === void 0 || score < 0) return pc2.dim("-");
|
|
387
|
-
if (score
|
|
388
|
-
return pc2.yellow(
|
|
419
|
+
if (score >= 7) return pc2.green("High");
|
|
420
|
+
if (score >= 4) return pc2.yellow("Medium");
|
|
421
|
+
return pc2.red("Low");
|
|
422
|
+
}
|
|
423
|
+
function getTrustLabel(score) {
|
|
424
|
+
if (score === void 0 || score < 0) return "-";
|
|
425
|
+
if (score >= 7) return "High";
|
|
426
|
+
if (score >= 4) return "Medium";
|
|
427
|
+
return "Low";
|
|
389
428
|
}
|
|
390
429
|
async function checkboxWithHover(config, options) {
|
|
391
430
|
const choices = config.choices.filter(
|
|
@@ -409,6 +448,7 @@ async function checkboxWithHover(config, options) {
|
|
|
409
448
|
theme: {
|
|
410
449
|
...config.theme,
|
|
411
450
|
style: {
|
|
451
|
+
answer: (text) => pc2.green(text),
|
|
412
452
|
...config.theme?.style,
|
|
413
453
|
highlight: (text) => pc2.green(text),
|
|
414
454
|
renderSelectedChoices: (selected, _allChoices) => {
|
|
@@ -476,9 +516,9 @@ async function detectVendorSpecificAgents(scope) {
|
|
|
476
516
|
const pathMap = scope === "global" ? IDE_GLOBAL_PATHS : IDE_PATHS;
|
|
477
517
|
const detected = [];
|
|
478
518
|
for (const ide of VENDOR_SPECIFIC_AGENTS) {
|
|
479
|
-
const parentDir =
|
|
519
|
+
const parentDir = dirname2(pathMap[ide]);
|
|
480
520
|
try {
|
|
481
|
-
await access(
|
|
521
|
+
await access(join2(baseDir, parentDir));
|
|
482
522
|
detected.push(ide);
|
|
483
523
|
} catch {
|
|
484
524
|
}
|
|
@@ -487,11 +527,11 @@ async function detectVendorSpecificAgents(scope) {
|
|
|
487
527
|
}
|
|
488
528
|
function getUniversalDir(scope) {
|
|
489
529
|
if (scope === "global") {
|
|
490
|
-
return
|
|
530
|
+
return join2(homedir(), UNIVERSAL_SKILLS_GLOBAL_PATH);
|
|
491
531
|
}
|
|
492
|
-
return
|
|
532
|
+
return join2(process.cwd(), UNIVERSAL_SKILLS_PATH);
|
|
493
533
|
}
|
|
494
|
-
async function promptForInstallTargets(options) {
|
|
534
|
+
async function promptForInstallTargets(options, forceUniversal = true) {
|
|
495
535
|
if (hasExplicitIdeOption(options)) {
|
|
496
536
|
const ides2 = getSelectedIdes(options);
|
|
497
537
|
const scope2 = options.global ? "global" : "project";
|
|
@@ -507,7 +547,7 @@ async function promptForInstallTargets(options) {
|
|
|
507
547
|
const detectedVendor = await detectVendorSpecificAgents(scope);
|
|
508
548
|
let hasUniversalDir = false;
|
|
509
549
|
try {
|
|
510
|
-
await access(
|
|
550
|
+
await access(join2(baseDir, dirname2(universalPath)));
|
|
511
551
|
hasUniversalDir = true;
|
|
512
552
|
} catch {
|
|
513
553
|
}
|
|
@@ -518,21 +558,25 @@ async function promptForInstallTargets(options) {
|
|
|
518
558
|
if (detectedIdes.length > 0) {
|
|
519
559
|
const pathLines = [];
|
|
520
560
|
if (hasUniversalDir) {
|
|
521
|
-
pathLines.push(
|
|
561
|
+
pathLines.push(join2(baseDir, universalPath));
|
|
522
562
|
}
|
|
523
563
|
for (const ide of detectedVendor) {
|
|
524
|
-
pathLines.push(
|
|
564
|
+
pathLines.push(join2(baseDir, pathMap[ide]));
|
|
525
565
|
}
|
|
526
566
|
log.blank();
|
|
527
567
|
let confirmed;
|
|
528
|
-
|
|
529
|
-
confirmed =
|
|
530
|
-
|
|
568
|
+
if (options.yes) {
|
|
569
|
+
confirmed = true;
|
|
570
|
+
} else {
|
|
571
|
+
try {
|
|
572
|
+
confirmed = await confirm({
|
|
573
|
+
message: `Install to detected location(s)?
|
|
531
574
|
${pc3.dim(pathLines.join("\n"))}`,
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
575
|
+
default: true
|
|
576
|
+
});
|
|
577
|
+
} catch {
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
536
580
|
}
|
|
537
581
|
if (!confirmed) {
|
|
538
582
|
log.warn("Installation cancelled");
|
|
@@ -542,9 +586,14 @@ ${pc3.dim(pathLines.join("\n"))}`,
|
|
|
542
586
|
}
|
|
543
587
|
const universalLabel = `Universal \u2014 ${UNIVERSAL_AGENTS_LABEL} ${pc3.dim(`(${universalPath})`)}`;
|
|
544
588
|
const choices = [
|
|
589
|
+
{
|
|
590
|
+
name: `${IDE_NAMES["claude"]} ${pc3.dim(`(${pathMap["claude"]})`)}`,
|
|
591
|
+
value: "claude",
|
|
592
|
+
checked: false
|
|
593
|
+
},
|
|
545
594
|
{ name: universalLabel, value: "universal", checked: false }
|
|
546
595
|
];
|
|
547
|
-
for (const ide of VENDOR_SPECIFIC_AGENTS) {
|
|
596
|
+
for (const ide of VENDOR_SPECIFIC_AGENTS.filter((ide2) => ide2 !== "claude")) {
|
|
548
597
|
choices.push({
|
|
549
598
|
name: `${IDE_NAMES[ide]} ${pc3.dim(`(${pathMap[ide]})`)}`,
|
|
550
599
|
value: ide,
|
|
@@ -564,7 +613,7 @@ ${pc3.dim(` ${baseDir}`)}`,
|
|
|
564
613
|
style: {
|
|
565
614
|
highlight: (text) => pc3.green(text),
|
|
566
615
|
message: (text, status) => {
|
|
567
|
-
if (status === "done") return
|
|
616
|
+
if (status === "done") return text.split("\n")[0];
|
|
568
617
|
return pc3.bold(text);
|
|
569
618
|
}
|
|
570
619
|
}
|
|
@@ -575,7 +624,7 @@ ${pc3.dim(` ${baseDir}`)}`,
|
|
|
575
624
|
} catch {
|
|
576
625
|
return null;
|
|
577
626
|
}
|
|
578
|
-
const ides = ["universal", ...selectedIdes.filter((ide) => ide !== "universal")];
|
|
627
|
+
const ides = forceUniversal ? ["universal", ...selectedIdes.filter((ide) => ide !== "universal")] : selectedIdes;
|
|
579
628
|
return { ides, scopes: [scope] };
|
|
580
629
|
}
|
|
581
630
|
async function promptForSingleTarget(options) {
|
|
@@ -587,8 +636,11 @@ async function promptForSingleTarget(options) {
|
|
|
587
636
|
}
|
|
588
637
|
log.blank();
|
|
589
638
|
const universalLabel = `Universal ${pc3.dim(`(${UNIVERSAL_SKILLS_PATH})`)}`;
|
|
590
|
-
const choices = [
|
|
591
|
-
|
|
639
|
+
const choices = [
|
|
640
|
+
{ name: `${IDE_NAMES["claude"]} ${pc3.dim(`(${IDE_PATHS["claude"]})`)}`, value: "claude" },
|
|
641
|
+
{ name: universalLabel, value: "universal" }
|
|
642
|
+
];
|
|
643
|
+
for (const ide of VENDOR_SPECIFIC_AGENTS.filter((ide2) => ide2 !== "claude")) {
|
|
592
644
|
choices.push({
|
|
593
645
|
name: `${IDE_NAMES[ide]} ${pc3.dim(`(${IDE_PATHS[ide]})`)}`,
|
|
594
646
|
value: ide
|
|
@@ -640,12 +692,12 @@ function getTargetDirs(targets) {
|
|
|
640
692
|
const baseDir = scope === "global" ? homedir() : process.cwd();
|
|
641
693
|
if (hasUniversal) {
|
|
642
694
|
const uniPath = scope === "global" ? UNIVERSAL_SKILLS_GLOBAL_PATH : UNIVERSAL_SKILLS_PATH;
|
|
643
|
-
dirs.push(
|
|
695
|
+
dirs.push(join2(baseDir, uniPath));
|
|
644
696
|
}
|
|
645
697
|
for (const ide of targets.ides) {
|
|
646
698
|
if (ide === "universal") continue;
|
|
647
699
|
const pathMap = scope === "global" ? IDE_GLOBAL_PATHS : IDE_PATHS;
|
|
648
|
-
dirs.push(
|
|
700
|
+
dirs.push(join2(baseDir, pathMap[ide]));
|
|
649
701
|
}
|
|
650
702
|
}
|
|
651
703
|
return dirs;
|
|
@@ -655,25 +707,25 @@ function getTargetDirFromSelection(ide, scope) {
|
|
|
655
707
|
return getUniversalDir(scope);
|
|
656
708
|
}
|
|
657
709
|
if (scope === "global") {
|
|
658
|
-
return
|
|
710
|
+
return join2(homedir(), IDE_GLOBAL_PATHS[ide]);
|
|
659
711
|
}
|
|
660
|
-
return
|
|
712
|
+
return join2(process.cwd(), IDE_PATHS[ide]);
|
|
661
713
|
}
|
|
662
714
|
|
|
663
715
|
// src/utils/installer.ts
|
|
664
716
|
import { mkdir, writeFile, rm, symlink, lstat } from "fs/promises";
|
|
665
|
-
import { join as
|
|
717
|
+
import { join as join3 } from "path";
|
|
666
718
|
async function installSkillFiles(skillName, files, targetDir) {
|
|
667
|
-
const skillDir =
|
|
719
|
+
const skillDir = join3(targetDir, skillName);
|
|
668
720
|
for (const file of files) {
|
|
669
|
-
const filePath =
|
|
670
|
-
const fileDir =
|
|
721
|
+
const filePath = join3(skillDir, file.path);
|
|
722
|
+
const fileDir = join3(filePath, "..");
|
|
671
723
|
await mkdir(fileDir, { recursive: true });
|
|
672
724
|
await writeFile(filePath, file.content);
|
|
673
725
|
}
|
|
674
726
|
}
|
|
675
727
|
async function symlinkSkill(skillName, sourcePath, targetDir) {
|
|
676
|
-
const targetPath =
|
|
728
|
+
const targetPath = join3(targetDir, skillName);
|
|
677
729
|
try {
|
|
678
730
|
const stats = await lstat(targetPath);
|
|
679
731
|
if (stats.isSymbolicLink() || stats.isDirectory()) {
|
|
@@ -700,7 +752,7 @@ function trackEvent(event, data) {
|
|
|
700
752
|
import pc6 from "picocolors";
|
|
701
753
|
import ora2 from "ora";
|
|
702
754
|
import { mkdir as mkdir2, writeFile as writeFile2, readFile, unlink } from "fs/promises";
|
|
703
|
-
import { join as
|
|
755
|
+
import { join as join5 } from "path";
|
|
704
756
|
import { homedir as homedir3 } from "os";
|
|
705
757
|
import { spawn } from "child_process";
|
|
706
758
|
import { input, select as select2 } from "@inquirer/prompts";
|
|
@@ -1453,9 +1505,9 @@ async function generateCommand(options) {
|
|
|
1453
1505
|
log.blank();
|
|
1454
1506
|
};
|
|
1455
1507
|
const openInEditor = async () => {
|
|
1456
|
-
const previewDir =
|
|
1508
|
+
const previewDir = join5(homedir3(), ".context7", "previews");
|
|
1457
1509
|
await mkdir2(previewDir, { recursive: true });
|
|
1458
|
-
previewFile =
|
|
1510
|
+
previewFile = join5(previewDir, `${skillName}.md`);
|
|
1459
1511
|
if (!previewFileWritten) {
|
|
1460
1512
|
await writeFile2(previewFile, generatedContent, "utf-8");
|
|
1461
1513
|
previewFileWritten = true;
|
|
@@ -1532,8 +1584,8 @@ async function generateCommand(options) {
|
|
|
1532
1584
|
if (options.output && !targetDir.includes("/.config/") && !targetDir.startsWith(homedir3())) {
|
|
1533
1585
|
finalDir = targetDir.replace(process.cwd(), options.output);
|
|
1534
1586
|
}
|
|
1535
|
-
const skillDir =
|
|
1536
|
-
const skillPath =
|
|
1587
|
+
const skillDir = join5(finalDir, skillName);
|
|
1588
|
+
const skillPath = join5(skillDir, "SKILL.md");
|
|
1537
1589
|
try {
|
|
1538
1590
|
await mkdir2(skillDir, { recursive: true });
|
|
1539
1591
|
await writeFile2(skillPath, generatedContent, "utf-8");
|
|
@@ -1552,7 +1604,7 @@ async function generateCommand(options) {
|
|
|
1552
1604
|
log.blank();
|
|
1553
1605
|
console.log(pc6.yellow("Fix permissions with:"));
|
|
1554
1606
|
for (const dir of failedDirs) {
|
|
1555
|
-
const parentDir =
|
|
1607
|
+
const parentDir = join5(dir, "..");
|
|
1556
1608
|
console.log(pc6.dim(` sudo chown -R $(whoami) "${parentDir}"`));
|
|
1557
1609
|
}
|
|
1558
1610
|
log.blank();
|
|
@@ -1574,7 +1626,7 @@ import { homedir as homedir4 } from "os";
|
|
|
1574
1626
|
|
|
1575
1627
|
// src/utils/deps.ts
|
|
1576
1628
|
import { readFile as readFile2 } from "fs/promises";
|
|
1577
|
-
import { join as
|
|
1629
|
+
import { join as join6 } from "path";
|
|
1578
1630
|
async function readFileOrNull(path2) {
|
|
1579
1631
|
try {
|
|
1580
1632
|
return await readFile2(path2, "utf-8");
|
|
@@ -1586,7 +1638,7 @@ function isSkippedLocally(name) {
|
|
|
1586
1638
|
return name.startsWith("@types/");
|
|
1587
1639
|
}
|
|
1588
1640
|
async function parsePackageJson(cwd) {
|
|
1589
|
-
const content = await readFileOrNull(
|
|
1641
|
+
const content = await readFileOrNull(join6(cwd, "package.json"));
|
|
1590
1642
|
if (!content) return [];
|
|
1591
1643
|
try {
|
|
1592
1644
|
const pkg2 = JSON.parse(content);
|
|
@@ -1603,7 +1655,7 @@ async function parsePackageJson(cwd) {
|
|
|
1603
1655
|
}
|
|
1604
1656
|
}
|
|
1605
1657
|
async function parseRequirementsTxt(cwd) {
|
|
1606
|
-
const content = await readFileOrNull(
|
|
1658
|
+
const content = await readFileOrNull(join6(cwd, "requirements.txt"));
|
|
1607
1659
|
if (!content) return [];
|
|
1608
1660
|
const deps = [];
|
|
1609
1661
|
for (const line of content.split("\n")) {
|
|
@@ -1617,7 +1669,7 @@ async function parseRequirementsTxt(cwd) {
|
|
|
1617
1669
|
return deps;
|
|
1618
1670
|
}
|
|
1619
1671
|
async function parsePyprojectToml(cwd) {
|
|
1620
|
-
const content = await readFileOrNull(
|
|
1672
|
+
const content = await readFileOrNull(join6(cwd, "pyproject.toml"));
|
|
1621
1673
|
if (!content) return [];
|
|
1622
1674
|
const deps = [];
|
|
1623
1675
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1772,13 +1824,12 @@ async function installCommand(input2, skillName, options) {
|
|
|
1772
1824
|
} else {
|
|
1773
1825
|
const indexWidth = data.skills.length.toString().length;
|
|
1774
1826
|
const maxNameLen = Math.max(...data.skills.map((s) => s.name.length));
|
|
1775
|
-
const
|
|
1827
|
+
const popularityColWidth = 13;
|
|
1776
1828
|
const choices = skillsWithRepo.map((s, index) => {
|
|
1777
1829
|
const indexStr = pc7.dim(`${(index + 1).toString().padStart(indexWidth)}.`);
|
|
1778
1830
|
const paddedName = s.name.padEnd(maxNameLen);
|
|
1779
|
-
const
|
|
1780
|
-
const
|
|
1781
|
-
const trust = formatTrustScore(s.trustScore);
|
|
1831
|
+
const popularity = formatPopularity(s.installCount) + " ".repeat(popularityColWidth - 4);
|
|
1832
|
+
const trust = formatTrust(s.trustScore);
|
|
1782
1833
|
const skillUrl = `https://context7.com/skills${s.project}/${s.name}`;
|
|
1783
1834
|
const skillLink = terminalLink(s.name, skillUrl, pc7.white);
|
|
1784
1835
|
const repoLink = terminalLink(s.project, `https://github.com${s.project}`, pc7.white);
|
|
@@ -1787,11 +1838,13 @@ async function installCommand(input2, skillName, options) {
|
|
|
1787
1838
|
"",
|
|
1788
1839
|
`${pc7.yellow("Skill:")} ${skillLink}`,
|
|
1789
1840
|
`${pc7.yellow("Repo:")} ${repoLink}`,
|
|
1841
|
+
`${pc7.yellow("Installs:")} ${pc7.white(formatInstallRange(s.installCount))}`,
|
|
1842
|
+
`${pc7.yellow("Trust:")} ${s.trustScore !== void 0 && s.trustScore >= 0 ? pc7.white(s.trustScore.toFixed(1)) : pc7.dim("-")}`,
|
|
1790
1843
|
`${pc7.yellow("Description:")}`,
|
|
1791
1844
|
pc7.white(s.description || "No description")
|
|
1792
1845
|
];
|
|
1793
1846
|
return {
|
|
1794
|
-
name: `${indexStr} ${paddedName} ${
|
|
1847
|
+
name: `${indexStr} ${paddedName} ${popularity}${trust}`,
|
|
1795
1848
|
value: s,
|
|
1796
1849
|
description: metadataLines.join("\n")
|
|
1797
1850
|
};
|
|
@@ -1799,7 +1852,7 @@ async function installCommand(input2, skillName, options) {
|
|
|
1799
1852
|
log.blank();
|
|
1800
1853
|
const checkboxPrefixWidth = 3;
|
|
1801
1854
|
const headerPad = " ".repeat(checkboxPrefixWidth + indexWidth + 1 + 1 + maxNameLen + 1);
|
|
1802
|
-
const headerLine = headerPad + pc7.dim("
|
|
1855
|
+
const headerLine = headerPad + pc7.dim("Popularity".padEnd(popularityColWidth)) + pc7.dim("Trust");
|
|
1803
1856
|
try {
|
|
1804
1857
|
selectedSkills = await checkboxWithHover({
|
|
1805
1858
|
message: `Select skills to install:
|
|
@@ -1856,7 +1909,7 @@ ${headerLine}`,
|
|
|
1856
1909
|
}
|
|
1857
1910
|
throw dirErr;
|
|
1858
1911
|
}
|
|
1859
|
-
const primarySkillDir =
|
|
1912
|
+
const primarySkillDir = join7(primaryDir, skill.name);
|
|
1860
1913
|
for (const targetDir of symlinkDirs) {
|
|
1861
1914
|
try {
|
|
1862
1915
|
await symlinkSkill(skill.name, primarySkillDir, targetDir);
|
|
@@ -1884,7 +1937,7 @@ ${headerLine}`,
|
|
|
1884
1937
|
log.blank();
|
|
1885
1938
|
log.warn("Fix permissions with:");
|
|
1886
1939
|
for (const dir of failedDirs) {
|
|
1887
|
-
const parentDir =
|
|
1940
|
+
const parentDir = join7(dir, "..");
|
|
1888
1941
|
log.dim(` sudo chown -R $(whoami) "${parentDir}"`);
|
|
1889
1942
|
}
|
|
1890
1943
|
log.blank();
|
|
@@ -1921,14 +1974,13 @@ async function searchCommand(query) {
|
|
|
1921
1974
|
const nameWithRepo = (s) => `${s.name} ${pc7.dim(`(${s.project})`)}`;
|
|
1922
1975
|
const nameWithRepoLen = (s) => `${s.name} (${s.project})`.length;
|
|
1923
1976
|
const maxNameLen = Math.max(...data.results.map(nameWithRepoLen));
|
|
1924
|
-
const
|
|
1977
|
+
const popularityColWidth = 13;
|
|
1925
1978
|
const choices = data.results.map((s, index) => {
|
|
1926
1979
|
const indexStr = pc7.dim(`${(index + 1).toString().padStart(indexWidth)}.`);
|
|
1927
1980
|
const rawLen = nameWithRepoLen(s);
|
|
1928
1981
|
const displayName = nameWithRepo(s) + " ".repeat(maxNameLen - rawLen);
|
|
1929
|
-
const
|
|
1930
|
-
const
|
|
1931
|
-
const trust = formatTrustScore(s.trustScore);
|
|
1982
|
+
const popularity = formatPopularity(s.installCount) + " ".repeat(popularityColWidth - 4);
|
|
1983
|
+
const trust = formatTrust(s.trustScore);
|
|
1932
1984
|
const skillLink = terminalLink(
|
|
1933
1985
|
s.name,
|
|
1934
1986
|
`https://context7.com/skills${s.project}/${s.name}`,
|
|
@@ -1940,18 +1992,20 @@ async function searchCommand(query) {
|
|
|
1940
1992
|
"",
|
|
1941
1993
|
`${pc7.yellow("Skill:")} ${skillLink}`,
|
|
1942
1994
|
`${pc7.yellow("Repo:")} ${repoLink}`,
|
|
1995
|
+
`${pc7.yellow("Installs:")} ${pc7.white(formatInstallRange(s.installCount))}`,
|
|
1996
|
+
`${pc7.yellow("Trust:")} ${s.trustScore !== void 0 && s.trustScore >= 0 ? pc7.white(s.trustScore.toFixed(1)) : pc7.dim("-")}`,
|
|
1943
1997
|
`${pc7.yellow("Description:")}`,
|
|
1944
1998
|
pc7.white(s.description || "No description")
|
|
1945
1999
|
];
|
|
1946
2000
|
return {
|
|
1947
|
-
name: `${indexStr} ${displayName} ${
|
|
2001
|
+
name: `${indexStr} ${displayName} ${popularity}${trust}`,
|
|
1948
2002
|
value: s,
|
|
1949
2003
|
description: metadataLines.join("\n")
|
|
1950
2004
|
};
|
|
1951
2005
|
});
|
|
1952
2006
|
const checkboxPrefixWidth = 3;
|
|
1953
2007
|
const headerPad = " ".repeat(checkboxPrefixWidth + indexWidth + 1 + 1 + maxNameLen + 1);
|
|
1954
|
-
const headerLine = headerPad + pc7.dim("
|
|
2008
|
+
const headerLine = headerPad + pc7.dim("Popularity".padEnd(popularityColWidth)) + pc7.dim("Trust");
|
|
1955
2009
|
let selectedSkills;
|
|
1956
2010
|
try {
|
|
1957
2011
|
selectedSkills = await checkboxWithHover({
|
|
@@ -2008,7 +2062,7 @@ ${headerLine}`,
|
|
|
2008
2062
|
}
|
|
2009
2063
|
throw dirErr;
|
|
2010
2064
|
}
|
|
2011
|
-
const primarySkillDir =
|
|
2065
|
+
const primarySkillDir = join7(primaryDir, skill.name);
|
|
2012
2066
|
for (const targetDir of symlinkDirs) {
|
|
2013
2067
|
try {
|
|
2014
2068
|
await symlinkSkill(skill.name, primarySkillDir, targetDir);
|
|
@@ -2036,7 +2090,7 @@ ${headerLine}`,
|
|
|
2036
2090
|
log.blank();
|
|
2037
2091
|
log.warn("Fix permissions with:");
|
|
2038
2092
|
for (const dir of failedDirs) {
|
|
2039
|
-
const parentDir =
|
|
2093
|
+
const parentDir = join7(dir, "..");
|
|
2040
2094
|
log.dim(` sudo chown -R $(whoami) "${parentDir}"`);
|
|
2041
2095
|
}
|
|
2042
2096
|
log.blank();
|
|
@@ -2063,7 +2117,7 @@ async function listCommand(options) {
|
|
|
2063
2117
|
if (hasExplicitIdeOption(options)) {
|
|
2064
2118
|
const ides = getSelectedIdes(options);
|
|
2065
2119
|
for (const ide of ides) {
|
|
2066
|
-
const dir = ide === "universal" ?
|
|
2120
|
+
const dir = ide === "universal" ? join7(baseDir, scope === "global" ? UNIVERSAL_SKILLS_GLOBAL_PATH : UNIVERSAL_SKILLS_PATH) : join7(baseDir, (scope === "global" ? IDE_GLOBAL_PATHS : IDE_PATHS)[ide]);
|
|
2067
2121
|
const label = ide === "universal" ? UNIVERSAL_AGENTS_LABEL : IDE_NAMES[ide];
|
|
2068
2122
|
const skills = await scanDir(dir);
|
|
2069
2123
|
if (skills.length > 0) {
|
|
@@ -2072,14 +2126,14 @@ async function listCommand(options) {
|
|
|
2072
2126
|
}
|
|
2073
2127
|
} else {
|
|
2074
2128
|
const universalPath = scope === "global" ? UNIVERSAL_SKILLS_GLOBAL_PATH : UNIVERSAL_SKILLS_PATH;
|
|
2075
|
-
const universalDir =
|
|
2129
|
+
const universalDir = join7(baseDir, universalPath);
|
|
2076
2130
|
const universalSkills = await scanDir(universalDir);
|
|
2077
2131
|
if (universalSkills.length > 0) {
|
|
2078
2132
|
results.push({ label: UNIVERSAL_AGENTS_LABEL, path: universalPath, skills: universalSkills });
|
|
2079
2133
|
}
|
|
2080
2134
|
for (const ide of VENDOR_SPECIFIC_AGENTS) {
|
|
2081
2135
|
const pathMap = scope === "global" ? IDE_GLOBAL_PATHS : IDE_PATHS;
|
|
2082
|
-
const dir =
|
|
2136
|
+
const dir = join7(baseDir, pathMap[ide]);
|
|
2083
2137
|
const skills = await scanDir(dir);
|
|
2084
2138
|
if (skills.length > 0) {
|
|
2085
2139
|
results.push({ label: IDE_NAMES[ide], path: pathMap[ide], skills });
|
|
@@ -2107,7 +2161,7 @@ async function removeCommand(name, options) {
|
|
|
2107
2161
|
return;
|
|
2108
2162
|
}
|
|
2109
2163
|
const skillsDir = getTargetDirFromSelection(target.ide, target.scope);
|
|
2110
|
-
const skillPath =
|
|
2164
|
+
const skillPath = join7(skillsDir, name);
|
|
2111
2165
|
try {
|
|
2112
2166
|
await rm2(skillPath, { recursive: true });
|
|
2113
2167
|
log.success(`Removed skill: ${name}`);
|
|
@@ -2195,18 +2249,17 @@ async function suggestCommand(options) {
|
|
|
2195
2249
|
const nameWithRepo = (s) => `${s.name} ${pc7.dim(`(${s.project})`)}`;
|
|
2196
2250
|
const nameWithRepoLen = (s) => `${s.name} (${s.project})`.length;
|
|
2197
2251
|
const maxNameLen = Math.max(...skills.map(nameWithRepoLen));
|
|
2198
|
-
const
|
|
2199
|
-
const trustColWidth =
|
|
2252
|
+
const popularityColWidth = 13;
|
|
2253
|
+
const trustColWidth = 8;
|
|
2200
2254
|
const maxMatchedLen = Math.max(...skills.map((s) => s.matchedDep.length));
|
|
2201
2255
|
const indexWidth = skills.length.toString().length;
|
|
2202
2256
|
const choices = skills.map((s, index) => {
|
|
2203
2257
|
const indexStr = pc7.dim(`${(index + 1).toString().padStart(indexWidth)}.`);
|
|
2204
2258
|
const rawLen = nameWithRepoLen(s);
|
|
2205
2259
|
const displayName = nameWithRepo(s) + " ".repeat(maxNameLen - rawLen);
|
|
2206
|
-
const
|
|
2207
|
-
const
|
|
2208
|
-
const
|
|
2209
|
-
const trust = formatTrustScore(s.trustScore) + " ".repeat(trustColWidth - trustRaw.length);
|
|
2260
|
+
const popularity = formatPopularity(s.installCount) + " ".repeat(popularityColWidth - 4);
|
|
2261
|
+
const trustLabel = getTrustLabel(s.trustScore);
|
|
2262
|
+
const trust = formatTrust(s.trustScore) + " ".repeat(trustColWidth - trustLabel.length);
|
|
2210
2263
|
const matched = pc7.yellow(s.matchedDep.padEnd(maxMatchedLen));
|
|
2211
2264
|
const skillLink = terminalLink(
|
|
2212
2265
|
s.name,
|
|
@@ -2219,19 +2272,21 @@ async function suggestCommand(options) {
|
|
|
2219
2272
|
"",
|
|
2220
2273
|
`${pc7.yellow("Skill:")} ${skillLink}`,
|
|
2221
2274
|
`${pc7.yellow("Repo:")} ${repoLink}`,
|
|
2275
|
+
`${pc7.yellow("Installs:")} ${pc7.white(formatInstallRange(s.installCount))}`,
|
|
2276
|
+
`${pc7.yellow("Trust:")} ${s.trustScore !== void 0 && s.trustScore >= 0 ? pc7.white(s.trustScore.toFixed(1)) : pc7.dim("-")}`,
|
|
2222
2277
|
`${pc7.yellow("Relevant:")} ${pc7.white(s.matchedDep)}`,
|
|
2223
2278
|
`${pc7.yellow("Description:")}`,
|
|
2224
2279
|
pc7.white(s.description || "No description")
|
|
2225
2280
|
];
|
|
2226
2281
|
return {
|
|
2227
|
-
name: `${indexStr} ${displayName} ${
|
|
2282
|
+
name: `${indexStr} ${displayName} ${popularity}${trust}${matched}`,
|
|
2228
2283
|
value: s,
|
|
2229
2284
|
description: metadataLines.join("\n")
|
|
2230
2285
|
};
|
|
2231
2286
|
});
|
|
2232
2287
|
const checkboxPrefixWidth = 3;
|
|
2233
2288
|
const headerPad = " ".repeat(checkboxPrefixWidth + indexWidth + 1 + 1 + maxNameLen + 1);
|
|
2234
|
-
const headerLine = headerPad + pc7.dim("
|
|
2289
|
+
const headerLine = headerPad + pc7.dim("Popularity".padEnd(popularityColWidth)) + pc7.dim("Trust".padEnd(trustColWidth)) + pc7.dim("Relevant");
|
|
2235
2290
|
let selectedSkills;
|
|
2236
2291
|
try {
|
|
2237
2292
|
selectedSkills = await checkboxWithHover({
|
|
@@ -2287,7 +2342,7 @@ ${headerLine}`,
|
|
|
2287
2342
|
}
|
|
2288
2343
|
throw dirErr;
|
|
2289
2344
|
}
|
|
2290
|
-
const primarySkillDir =
|
|
2345
|
+
const primarySkillDir = join7(primaryDir, skill.name);
|
|
2291
2346
|
for (const targetDir of symlinkDirs) {
|
|
2292
2347
|
try {
|
|
2293
2348
|
await symlinkSkill(skill.name, primarySkillDir, targetDir);
|
|
@@ -2315,7 +2370,7 @@ ${headerLine}`,
|
|
|
2315
2370
|
log.blank();
|
|
2316
2371
|
log.warn("Fix permissions with:");
|
|
2317
2372
|
for (const dir of failedDirs) {
|
|
2318
|
-
const parentDir =
|
|
2373
|
+
const parentDir = join7(dir, "..");
|
|
2319
2374
|
log.dim(` sudo chown -R $(whoami) "${parentDir}"`);
|
|
2320
2375
|
}
|
|
2321
2376
|
log.blank();
|
|
@@ -2330,13 +2385,14 @@ ${headerLine}`,
|
|
|
2330
2385
|
// src/commands/setup.ts
|
|
2331
2386
|
import pc8 from "picocolors";
|
|
2332
2387
|
import ora4 from "ora";
|
|
2388
|
+
import { select as select3 } from "@inquirer/prompts";
|
|
2333
2389
|
import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
|
|
2334
|
-
import { dirname as
|
|
2390
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
2335
2391
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2336
2392
|
|
|
2337
2393
|
// src/setup/agents.ts
|
|
2338
2394
|
import { access as access2 } from "fs/promises";
|
|
2339
|
-
import { join as
|
|
2395
|
+
import { join as join8 } from "path";
|
|
2340
2396
|
import { homedir as homedir5 } from "os";
|
|
2341
2397
|
var SETUP_AGENT_NAMES = {
|
|
2342
2398
|
claude: "Claude Code",
|
|
@@ -2363,43 +2419,43 @@ var agents = {
|
|
|
2363
2419
|
displayName: "Claude Code",
|
|
2364
2420
|
mcp: {
|
|
2365
2421
|
projectPath: ".mcp.json",
|
|
2366
|
-
globalPath:
|
|
2422
|
+
globalPath: join8(homedir5(), ".claude.json"),
|
|
2367
2423
|
configKey: "mcpServers",
|
|
2368
2424
|
buildEntry: (auth) => withHeaders({ type: "http", url: mcpUrl(auth) }, auth)
|
|
2369
2425
|
},
|
|
2370
2426
|
rule: {
|
|
2371
|
-
dir: (scope) => scope === "global" ?
|
|
2427
|
+
dir: (scope) => scope === "global" ? join8(homedir5(), ".claude", "rules") : join8(".claude", "rules"),
|
|
2372
2428
|
filename: "context7.md"
|
|
2373
2429
|
},
|
|
2374
2430
|
skill: {
|
|
2375
|
-
name: "
|
|
2376
|
-
dir: (scope) => scope === "global" ?
|
|
2431
|
+
name: "context7-mcp",
|
|
2432
|
+
dir: (scope) => scope === "global" ? join8(homedir5(), ".claude", "skills") : join8(".claude", "skills")
|
|
2377
2433
|
},
|
|
2378
2434
|
detect: {
|
|
2379
2435
|
projectPaths: [".mcp.json", ".claude"],
|
|
2380
|
-
globalPaths: [
|
|
2436
|
+
globalPaths: [join8(homedir5(), ".claude")]
|
|
2381
2437
|
}
|
|
2382
2438
|
},
|
|
2383
2439
|
cursor: {
|
|
2384
2440
|
name: "cursor",
|
|
2385
2441
|
displayName: "Cursor",
|
|
2386
2442
|
mcp: {
|
|
2387
|
-
projectPath:
|
|
2388
|
-
globalPath:
|
|
2443
|
+
projectPath: join8(".cursor", "mcp.json"),
|
|
2444
|
+
globalPath: join8(homedir5(), ".cursor", "mcp.json"),
|
|
2389
2445
|
configKey: "mcpServers",
|
|
2390
2446
|
buildEntry: (auth) => withHeaders({ url: mcpUrl(auth) }, auth)
|
|
2391
2447
|
},
|
|
2392
2448
|
rule: {
|
|
2393
|
-
dir: (scope) => scope === "global" ?
|
|
2449
|
+
dir: (scope) => scope === "global" ? join8(homedir5(), ".cursor", "rules") : join8(".cursor", "rules"),
|
|
2394
2450
|
filename: "context7.mdc"
|
|
2395
2451
|
},
|
|
2396
2452
|
skill: {
|
|
2397
|
-
name: "
|
|
2398
|
-
dir: (scope) => scope === "global" ?
|
|
2453
|
+
name: "context7-mcp",
|
|
2454
|
+
dir: (scope) => scope === "global" ? join8(homedir5(), ".cursor", "skills") : join8(".cursor", "skills")
|
|
2399
2455
|
},
|
|
2400
2456
|
detect: {
|
|
2401
2457
|
projectPaths: [".cursor"],
|
|
2402
|
-
globalPaths: [
|
|
2458
|
+
globalPaths: [join8(homedir5(), ".cursor")]
|
|
2403
2459
|
}
|
|
2404
2460
|
},
|
|
2405
2461
|
opencode: {
|
|
@@ -2407,22 +2463,22 @@ var agents = {
|
|
|
2407
2463
|
displayName: "OpenCode",
|
|
2408
2464
|
mcp: {
|
|
2409
2465
|
projectPath: ".opencode.json",
|
|
2410
|
-
globalPath:
|
|
2466
|
+
globalPath: join8(homedir5(), ".config", "opencode", "opencode.json"),
|
|
2411
2467
|
configKey: "mcp",
|
|
2412
2468
|
buildEntry: (auth) => withHeaders({ type: "remote", url: mcpUrl(auth), enabled: true }, auth)
|
|
2413
2469
|
},
|
|
2414
2470
|
rule: {
|
|
2415
|
-
dir: (scope) => scope === "global" ?
|
|
2471
|
+
dir: (scope) => scope === "global" ? join8(homedir5(), ".config", "opencode", "rules") : join8(".opencode", "rules"),
|
|
2416
2472
|
filename: "context7.md",
|
|
2417
|
-
instructionsGlob: (scope) => scope === "global" ?
|
|
2473
|
+
instructionsGlob: (scope) => scope === "global" ? join8(homedir5(), ".config", "opencode", "rules", "*.md") : ".opencode/rules/*.md"
|
|
2418
2474
|
},
|
|
2419
2475
|
skill: {
|
|
2420
|
-
name: "
|
|
2421
|
-
dir: (scope) => scope === "global" ?
|
|
2476
|
+
name: "context7-mcp",
|
|
2477
|
+
dir: (scope) => scope === "global" ? join8(homedir5(), ".agents", "skills") : join8(".agents", "skills")
|
|
2422
2478
|
},
|
|
2423
2479
|
detect: {
|
|
2424
2480
|
projectPaths: [".opencode.json"],
|
|
2425
|
-
globalPaths: [
|
|
2481
|
+
globalPaths: [join8(homedir5(), ".config", "opencode")]
|
|
2426
2482
|
}
|
|
2427
2483
|
}
|
|
2428
2484
|
};
|
|
@@ -2443,7 +2499,7 @@ async function detectAgents(scope) {
|
|
|
2443
2499
|
for (const agent of Object.values(agents)) {
|
|
2444
2500
|
const paths = scope === "global" ? agent.detect.globalPaths : agent.detect.projectPaths;
|
|
2445
2501
|
for (const p of paths) {
|
|
2446
|
-
const fullPath = scope === "global" ? p :
|
|
2502
|
+
const fullPath = scope === "global" ? p : join8(process.cwd(), p);
|
|
2447
2503
|
if (await pathExists(fullPath)) {
|
|
2448
2504
|
detected.push(agent.name);
|
|
2449
2505
|
break;
|
|
@@ -2454,60 +2510,6 @@ async function detectAgents(scope) {
|
|
|
2454
2510
|
}
|
|
2455
2511
|
|
|
2456
2512
|
// src/setup/templates.ts
|
|
2457
|
-
var SKILL_CONTENT = `---
|
|
2458
|
-
name: documentation-lookup
|
|
2459
|
-
description: This skill should be used when the user asks about libraries, frameworks, API references, or needs code examples. Activates for setup questions, code generation involving libraries, or mentions of specific frameworks like React, Vue, Next.js, Prisma, Supabase, etc.
|
|
2460
|
-
---
|
|
2461
|
-
|
|
2462
|
-
When the user asks about libraries, frameworks, or needs code examples, use Context7 to fetch current documentation instead of relying on training data.
|
|
2463
|
-
|
|
2464
|
-
## When to Use This Skill
|
|
2465
|
-
|
|
2466
|
-
Activate this skill when the user:
|
|
2467
|
-
|
|
2468
|
-
- Asks setup or configuration questions ("How do I configure Next.js middleware?")
|
|
2469
|
-
- Requests code involving libraries ("Write a Prisma query for...")
|
|
2470
|
-
- Needs API references ("What are the Supabase auth methods?")
|
|
2471
|
-
- Mentions specific frameworks (React, Vue, Svelte, Express, Tailwind, etc.)
|
|
2472
|
-
|
|
2473
|
-
## How to Fetch Documentation
|
|
2474
|
-
|
|
2475
|
-
### Step 1: Resolve the Library ID
|
|
2476
|
-
|
|
2477
|
-
Call \`resolve-library-id\` with:
|
|
2478
|
-
|
|
2479
|
-
- \`libraryName\`: The library name extracted from the user's question
|
|
2480
|
-
- \`query\`: The user's full question (improves relevance ranking)
|
|
2481
|
-
|
|
2482
|
-
### Step 2: Select the Best Match
|
|
2483
|
-
|
|
2484
|
-
From the resolution results, choose based on:
|
|
2485
|
-
|
|
2486
|
-
- Exact or closest name match to what the user asked for
|
|
2487
|
-
- Higher benchmark scores indicate better documentation quality
|
|
2488
|
-
- If the user mentioned a version (e.g., "React 19"), prefer version-specific IDs
|
|
2489
|
-
|
|
2490
|
-
### Step 3: Fetch the Documentation
|
|
2491
|
-
|
|
2492
|
-
Call \`query-docs\` with:
|
|
2493
|
-
|
|
2494
|
-
- \`libraryId\`: The selected Context7 library ID (e.g., \`/vercel/next.js\`)
|
|
2495
|
-
- \`query\`: The user's specific question
|
|
2496
|
-
|
|
2497
|
-
### Step 4: Use the Documentation
|
|
2498
|
-
|
|
2499
|
-
Incorporate the fetched documentation into your response:
|
|
2500
|
-
|
|
2501
|
-
- Answer the user's question using current, accurate information
|
|
2502
|
-
- Include relevant code examples from the docs
|
|
2503
|
-
- Cite the library version when relevant
|
|
2504
|
-
|
|
2505
|
-
## Guidelines
|
|
2506
|
-
|
|
2507
|
-
- **Be specific**: Pass the user's full question as the query for better results
|
|
2508
|
-
- **Version awareness**: When users mention versions ("Next.js 15", "React 19"), use version-specific library IDs if available from the resolution step
|
|
2509
|
-
- **Prefer official sources**: When multiple matches exist, prefer official/primary packages over community forks
|
|
2510
|
-
`;
|
|
2511
2513
|
var RULE_CONTENT = `---
|
|
2512
2514
|
alwaysApply: true
|
|
2513
2515
|
---
|
|
@@ -2524,7 +2526,7 @@ When working with libraries, frameworks, or APIs \u2014 use Context7 MCP to fetc
|
|
|
2524
2526
|
|
|
2525
2527
|
// src/setup/mcp-writer.ts
|
|
2526
2528
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
2527
|
-
import { dirname as
|
|
2529
|
+
import { dirname as dirname3 } from "path";
|
|
2528
2530
|
async function readJsonConfig(filePath) {
|
|
2529
2531
|
let raw;
|
|
2530
2532
|
try {
|
|
@@ -2558,7 +2560,7 @@ function mergeInstructions(config, glob) {
|
|
|
2558
2560
|
return { ...config, instructions: [...instructions, glob] };
|
|
2559
2561
|
}
|
|
2560
2562
|
async function writeJsonConfig(filePath, config) {
|
|
2561
|
-
await mkdir3(
|
|
2563
|
+
await mkdir3(dirname3(filePath), { recursive: true });
|
|
2562
2564
|
await writeFile3(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
2563
2565
|
}
|
|
2564
2566
|
|
|
@@ -2577,7 +2579,7 @@ function getSelectedAgents(options) {
|
|
|
2577
2579
|
return agents2;
|
|
2578
2580
|
}
|
|
2579
2581
|
function registerSetupCommand(program2) {
|
|
2580
|
-
program2.command("setup").description("Set up Context7
|
|
2582
|
+
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("--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) => {
|
|
2581
2583
|
await setupCommand(options);
|
|
2582
2584
|
});
|
|
2583
2585
|
}
|
|
@@ -2617,9 +2619,49 @@ async function resolveAuth(options) {
|
|
|
2617
2619
|
if (!apiKey) return null;
|
|
2618
2620
|
return { mode: "api-key", apiKey };
|
|
2619
2621
|
}
|
|
2622
|
+
async function resolveMode(options) {
|
|
2623
|
+
if (options.cli) return "cli";
|
|
2624
|
+
if (options.mcp || options.yes || options.oauth || options.apiKey) return "mcp";
|
|
2625
|
+
return select3({
|
|
2626
|
+
message: "How should your agent access Context7?",
|
|
2627
|
+
choices: [
|
|
2628
|
+
{
|
|
2629
|
+
name: `CLI + Skills
|
|
2630
|
+
${pc8.dim("Installs a find-docs skill that guides your agent to fetch up-to-date library docs using ")}${pc8.dim(pc8.bold("ctx7"))}${pc8.dim(" CLI commands")}`,
|
|
2631
|
+
value: "cli"
|
|
2632
|
+
},
|
|
2633
|
+
{
|
|
2634
|
+
name: `MCP server
|
|
2635
|
+
${pc8.dim("Agent calls Context7 tools via MCP protocol to retrieve up-to-date library docs")}`,
|
|
2636
|
+
value: "mcp"
|
|
2637
|
+
}
|
|
2638
|
+
],
|
|
2639
|
+
theme: {
|
|
2640
|
+
style: {
|
|
2641
|
+
highlight: (text) => pc8.green(text),
|
|
2642
|
+
answer: (text) => pc8.green(text.split("\n")[0].trim())
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
async function resolveCliAuth(apiKey) {
|
|
2648
|
+
if (apiKey) {
|
|
2649
|
+
saveTokens({ access_token: apiKey, token_type: "bearer" });
|
|
2650
|
+
log.blank();
|
|
2651
|
+
log.plain(`${pc8.green("\u2714")} Authenticated`);
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2654
|
+
const existingTokens = loadTokens();
|
|
2655
|
+
if (existingTokens && !isTokenExpired(existingTokens)) {
|
|
2656
|
+
log.blank();
|
|
2657
|
+
log.plain(`${pc8.green("\u2714")} Authenticated`);
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
await performLogin();
|
|
2661
|
+
}
|
|
2620
2662
|
async function isAlreadyConfigured(agentName, scope) {
|
|
2621
2663
|
const agent = getAgent(agentName);
|
|
2622
|
-
const mcpPath = scope === "global" ? agent.mcp.globalPath :
|
|
2664
|
+
const mcpPath = scope === "global" ? agent.mcp.globalPath : join9(process.cwd(), agent.mcp.projectPath);
|
|
2623
2665
|
try {
|
|
2624
2666
|
const existing = await readJsonConfig(mcpPath);
|
|
2625
2667
|
const section = existing[agent.mcp.configKey] ?? {};
|
|
@@ -2628,10 +2670,10 @@ async function isAlreadyConfigured(agentName, scope) {
|
|
|
2628
2670
|
return false;
|
|
2629
2671
|
}
|
|
2630
2672
|
}
|
|
2631
|
-
async function promptAgents(scope) {
|
|
2673
|
+
async function promptAgents(scope, mode) {
|
|
2632
2674
|
const choices = await Promise.all(
|
|
2633
2675
|
ALL_AGENT_NAMES.map(async (name) => {
|
|
2634
|
-
const configured = await isAlreadyConfigured(name, scope);
|
|
2676
|
+
const configured = mode === "mcp" ? await isAlreadyConfigured(name, scope) : false;
|
|
2635
2677
|
return {
|
|
2636
2678
|
name: SETUP_AGENT_NAMES[name],
|
|
2637
2679
|
value: name,
|
|
@@ -2643,10 +2685,11 @@ async function promptAgents(scope) {
|
|
|
2643
2685
|
log.info("Context7 is already configured for all detected agents.");
|
|
2644
2686
|
return null;
|
|
2645
2687
|
}
|
|
2688
|
+
const message = mode === "cli" ? "Install find-docs skill for which agents?" : "Which agents do you want to set up?";
|
|
2646
2689
|
try {
|
|
2647
2690
|
return await checkboxWithHover(
|
|
2648
2691
|
{
|
|
2649
|
-
message
|
|
2692
|
+
message,
|
|
2650
2693
|
choices,
|
|
2651
2694
|
loop: false,
|
|
2652
2695
|
theme: CHECKBOX_THEME
|
|
@@ -2657,13 +2700,13 @@ async function promptAgents(scope) {
|
|
|
2657
2700
|
return null;
|
|
2658
2701
|
}
|
|
2659
2702
|
}
|
|
2660
|
-
async function resolveAgents(options, scope) {
|
|
2703
|
+
async function resolveAgents(options, scope, mode = "mcp") {
|
|
2661
2704
|
const explicit = getSelectedAgents(options);
|
|
2662
2705
|
if (explicit.length > 0) return explicit;
|
|
2663
2706
|
const detected = await detectAgents(scope);
|
|
2664
2707
|
if (detected.length > 0 && options.yes) return detected;
|
|
2665
2708
|
log.blank();
|
|
2666
|
-
const selected = await promptAgents(scope);
|
|
2709
|
+
const selected = await promptAgents(scope, mode);
|
|
2667
2710
|
if (!selected) {
|
|
2668
2711
|
log.warn("Setup cancelled");
|
|
2669
2712
|
return [];
|
|
@@ -2672,7 +2715,7 @@ async function resolveAgents(options, scope) {
|
|
|
2672
2715
|
}
|
|
2673
2716
|
async function setupAgent(agentName, auth, scope) {
|
|
2674
2717
|
const agent = getAgent(agentName);
|
|
2675
|
-
const mcpPath = scope === "global" ? agent.mcp.globalPath :
|
|
2718
|
+
const mcpPath = scope === "global" ? agent.mcp.globalPath : join9(process.cwd(), agent.mcp.projectPath);
|
|
2676
2719
|
let mcpStatus;
|
|
2677
2720
|
try {
|
|
2678
2721
|
const existing = await readJsonConfig(mcpPath);
|
|
@@ -2694,21 +2737,24 @@ async function setupAgent(agentName, auth, scope) {
|
|
|
2694
2737
|
} catch (err) {
|
|
2695
2738
|
mcpStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
2696
2739
|
}
|
|
2697
|
-
const rulePath = scope === "global" ?
|
|
2740
|
+
const rulePath = scope === "global" ? join9(agent.rule.dir("global"), agent.rule.filename) : join9(process.cwd(), agent.rule.dir("project"), agent.rule.filename);
|
|
2698
2741
|
let ruleStatus;
|
|
2699
2742
|
try {
|
|
2700
|
-
await mkdir4(
|
|
2743
|
+
await mkdir4(dirname4(rulePath), { recursive: true });
|
|
2701
2744
|
await writeFile4(rulePath, RULE_CONTENT, "utf-8");
|
|
2702
2745
|
ruleStatus = "installed";
|
|
2703
2746
|
} catch (err) {
|
|
2704
2747
|
ruleStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
2705
2748
|
}
|
|
2706
|
-
const skillDir = scope === "global" ? agent.skill.dir("global") :
|
|
2707
|
-
const skillPath =
|
|
2749
|
+
const skillDir = scope === "global" ? agent.skill.dir("global") : join9(process.cwd(), agent.skill.dir("project"));
|
|
2750
|
+
const skillPath = join9(skillDir, agent.skill.name, "SKILL.md");
|
|
2708
2751
|
let skillStatus;
|
|
2709
2752
|
try {
|
|
2710
|
-
await
|
|
2711
|
-
|
|
2753
|
+
const downloadData = await downloadSkill("/upstash/context7", agent.skill.name);
|
|
2754
|
+
if (downloadData.error || downloadData.files.length === 0) {
|
|
2755
|
+
throw new Error(downloadData.error || "no files");
|
|
2756
|
+
}
|
|
2757
|
+
await installSkillFiles(agent.skill.name, downloadData.files, skillDir);
|
|
2712
2758
|
skillStatus = "installed";
|
|
2713
2759
|
} catch (err) {
|
|
2714
2760
|
skillStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -2723,11 +2769,7 @@ async function setupAgent(agentName, auth, scope) {
|
|
|
2723
2769
|
skillPath
|
|
2724
2770
|
};
|
|
2725
2771
|
}
|
|
2726
|
-
async function
|
|
2727
|
-
trackEvent("command", { name: "setup" });
|
|
2728
|
-
const scope = options.project ? "project" : "global";
|
|
2729
|
-
const agents2 = await resolveAgents(options, scope);
|
|
2730
|
-
if (agents2.length === 0) return;
|
|
2772
|
+
async function setupMcp(agents2, options, scope) {
|
|
2731
2773
|
const auth = await resolveAuth(options);
|
|
2732
2774
|
if (!auth) {
|
|
2733
2775
|
log.warn("Setup cancelled");
|
|
@@ -2757,6 +2799,60 @@ async function setupCommand(options) {
|
|
|
2757
2799
|
log.blank();
|
|
2758
2800
|
trackEvent("setup", { agents: agents2, scope, authMode: auth.mode });
|
|
2759
2801
|
}
|
|
2802
|
+
async function setupCli(options) {
|
|
2803
|
+
await resolveCliAuth(options.apiKey);
|
|
2804
|
+
const targets = await promptForInstallTargets({ ...options, global: !options.project }, false);
|
|
2805
|
+
if (!targets) {
|
|
2806
|
+
log.warn("Setup cancelled");
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
log.blank();
|
|
2810
|
+
const spinner = ora4("Downloading find-docs skill...").start();
|
|
2811
|
+
const downloadData = await downloadSkill("/upstash/context7", "find-docs");
|
|
2812
|
+
if (downloadData.error || downloadData.files.length === 0) {
|
|
2813
|
+
spinner.fail(`Failed to download find-docs skill: ${downloadData.error || "no files"}`);
|
|
2814
|
+
return;
|
|
2815
|
+
}
|
|
2816
|
+
spinner.succeed("Downloaded find-docs skill");
|
|
2817
|
+
const targetDirs = getTargetDirs(targets);
|
|
2818
|
+
const installSpinner = ora4("Installing find-docs skill...").start();
|
|
2819
|
+
for (const dir of targetDirs) {
|
|
2820
|
+
installSpinner.text = `Installing to ${dir}...`;
|
|
2821
|
+
await installSkillFiles("find-docs", downloadData.files, dir);
|
|
2822
|
+
}
|
|
2823
|
+
installSpinner.stop();
|
|
2824
|
+
log.blank();
|
|
2825
|
+
log.plain(`${pc8.green("\u2714")} Context7 CLI setup complete`);
|
|
2826
|
+
log.blank();
|
|
2827
|
+
for (const dir of targetDirs) {
|
|
2828
|
+
log.itemAdd(
|
|
2829
|
+
`find-docs ${pc8.dim("Guides your agent to fetch up-to-date library docs on demand using ctx7 CLI commands")}`
|
|
2830
|
+
);
|
|
2831
|
+
log.plain(` ${pc8.dim(dir)}`);
|
|
2832
|
+
}
|
|
2833
|
+
log.blank();
|
|
2834
|
+
log.plain(` ${pc8.bold("Next steps")}`);
|
|
2835
|
+
log.plain(` Ask your agent: ${pc8.cyan(`"Use ctx7 CLI to look up React hooks"`)}`);
|
|
2836
|
+
log.blank();
|
|
2837
|
+
trackEvent("setup", { mode: "cli" });
|
|
2838
|
+
}
|
|
2839
|
+
async function setupCommand(options) {
|
|
2840
|
+
trackEvent("command", { name: "setup" });
|
|
2841
|
+
try {
|
|
2842
|
+
const mode = await resolveMode(options);
|
|
2843
|
+
if (mode === "mcp") {
|
|
2844
|
+
const scope = options.project ? "project" : "global";
|
|
2845
|
+
const agents2 = await resolveAgents(options, scope, mode);
|
|
2846
|
+
if (agents2.length === 0) return;
|
|
2847
|
+
await setupMcp(agents2, options, scope);
|
|
2848
|
+
} else {
|
|
2849
|
+
await setupCli(options);
|
|
2850
|
+
}
|
|
2851
|
+
} catch (err) {
|
|
2852
|
+
if (err instanceof Error && err.name === "ExitPromptError") process.exit(0);
|
|
2853
|
+
throw err;
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2760
2856
|
|
|
2761
2857
|
// src/commands/docs.ts
|
|
2762
2858
|
import pc9 from "picocolors";
|
|
@@ -2846,9 +2942,9 @@ async function resolveCommand(library, query, options) {
|
|
|
2846
2942
|
}
|
|
2847
2943
|
async function queryCommand(libraryId, query, options) {
|
|
2848
2944
|
trackEvent("command", { name: "docs" });
|
|
2849
|
-
if (!libraryId.startsWith("/")) {
|
|
2850
|
-
log.error(`Invalid library ID: ${libraryId}`);
|
|
2851
|
-
log.info(`
|
|
2945
|
+
if (!libraryId.startsWith("/") || !/^\/[^/]+\/[^/]/.test(libraryId)) {
|
|
2946
|
+
log.error(`Invalid library ID: "${libraryId}"`);
|
|
2947
|
+
log.info(`Expected format: /owner/repo or /owner/repo/version (e.g., /facebook/react)`);
|
|
2852
2948
|
log.info(`Run "ctx7 library <name>" to find the correct ID`);
|
|
2853
2949
|
process.exitCode = 1;
|
|
2854
2950
|
return;
|
|
@@ -2927,15 +3023,6 @@ function registerDocsCommands(program2) {
|
|
|
2927
3023
|
});
|
|
2928
3024
|
}
|
|
2929
3025
|
|
|
2930
|
-
// src/constants.ts
|
|
2931
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
2932
|
-
import { fileURLToPath } from "url";
|
|
2933
|
-
import { dirname as dirname4, join as join9 } from "path";
|
|
2934
|
-
var __dirname = dirname4(fileURLToPath(import.meta.url));
|
|
2935
|
-
var pkg = JSON.parse(readFileSync2(join9(__dirname, "../package.json"), "utf-8"));
|
|
2936
|
-
var VERSION = pkg.version;
|
|
2937
|
-
var NAME = pkg.name;
|
|
2938
|
-
|
|
2939
3026
|
// src/index.ts
|
|
2940
3027
|
var brand = {
|
|
2941
3028
|
primary: pc10.green,
|