ctx7 0.3.2 → 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 +309 -204
- 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 = [
|
|
545
|
-
{
|
|
589
|
+
{
|
|
590
|
+
name: `${IDE_NAMES["claude"]} ${pc3.dim(`(${pathMap["claude"]})`)}`,
|
|
591
|
+
value: "claude",
|
|
592
|
+
checked: false
|
|
593
|
+
},
|
|
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";
|
|
@@ -1234,6 +1286,12 @@ async function generateCommand(options) {
|
|
|
1234
1286
|
}
|
|
1235
1287
|
searchSpinner.succeed(pc6.green(`Found ${searchResult.results.length} relevant sources`));
|
|
1236
1288
|
log.blank();
|
|
1289
|
+
if (searchResult.searchFilterApplied) {
|
|
1290
|
+
log.warn(
|
|
1291
|
+
"Your results only include libraries matching your access settings. To search across all public libraries, update your settings at https://context7.com/dashboard?tab=libraries"
|
|
1292
|
+
);
|
|
1293
|
+
log.blank();
|
|
1294
|
+
}
|
|
1237
1295
|
let selectedLibraries;
|
|
1238
1296
|
try {
|
|
1239
1297
|
const formatProjectId = (id) => {
|
|
@@ -1447,9 +1505,9 @@ async function generateCommand(options) {
|
|
|
1447
1505
|
log.blank();
|
|
1448
1506
|
};
|
|
1449
1507
|
const openInEditor = async () => {
|
|
1450
|
-
const previewDir =
|
|
1508
|
+
const previewDir = join5(homedir3(), ".context7", "previews");
|
|
1451
1509
|
await mkdir2(previewDir, { recursive: true });
|
|
1452
|
-
previewFile =
|
|
1510
|
+
previewFile = join5(previewDir, `${skillName}.md`);
|
|
1453
1511
|
if (!previewFileWritten) {
|
|
1454
1512
|
await writeFile2(previewFile, generatedContent, "utf-8");
|
|
1455
1513
|
previewFileWritten = true;
|
|
@@ -1526,8 +1584,8 @@ async function generateCommand(options) {
|
|
|
1526
1584
|
if (options.output && !targetDir.includes("/.config/") && !targetDir.startsWith(homedir3())) {
|
|
1527
1585
|
finalDir = targetDir.replace(process.cwd(), options.output);
|
|
1528
1586
|
}
|
|
1529
|
-
const skillDir =
|
|
1530
|
-
const skillPath =
|
|
1587
|
+
const skillDir = join5(finalDir, skillName);
|
|
1588
|
+
const skillPath = join5(skillDir, "SKILL.md");
|
|
1531
1589
|
try {
|
|
1532
1590
|
await mkdir2(skillDir, { recursive: true });
|
|
1533
1591
|
await writeFile2(skillPath, generatedContent, "utf-8");
|
|
@@ -1546,7 +1604,7 @@ async function generateCommand(options) {
|
|
|
1546
1604
|
log.blank();
|
|
1547
1605
|
console.log(pc6.yellow("Fix permissions with:"));
|
|
1548
1606
|
for (const dir of failedDirs) {
|
|
1549
|
-
const parentDir =
|
|
1607
|
+
const parentDir = join5(dir, "..");
|
|
1550
1608
|
console.log(pc6.dim(` sudo chown -R $(whoami) "${parentDir}"`));
|
|
1551
1609
|
}
|
|
1552
1610
|
log.blank();
|
|
@@ -1568,7 +1626,7 @@ import { homedir as homedir4 } from "os";
|
|
|
1568
1626
|
|
|
1569
1627
|
// src/utils/deps.ts
|
|
1570
1628
|
import { readFile as readFile2 } from "fs/promises";
|
|
1571
|
-
import { join as
|
|
1629
|
+
import { join as join6 } from "path";
|
|
1572
1630
|
async function readFileOrNull(path2) {
|
|
1573
1631
|
try {
|
|
1574
1632
|
return await readFile2(path2, "utf-8");
|
|
@@ -1580,7 +1638,7 @@ function isSkippedLocally(name) {
|
|
|
1580
1638
|
return name.startsWith("@types/");
|
|
1581
1639
|
}
|
|
1582
1640
|
async function parsePackageJson(cwd) {
|
|
1583
|
-
const content = await readFileOrNull(
|
|
1641
|
+
const content = await readFileOrNull(join6(cwd, "package.json"));
|
|
1584
1642
|
if (!content) return [];
|
|
1585
1643
|
try {
|
|
1586
1644
|
const pkg2 = JSON.parse(content);
|
|
@@ -1597,7 +1655,7 @@ async function parsePackageJson(cwd) {
|
|
|
1597
1655
|
}
|
|
1598
1656
|
}
|
|
1599
1657
|
async function parseRequirementsTxt(cwd) {
|
|
1600
|
-
const content = await readFileOrNull(
|
|
1658
|
+
const content = await readFileOrNull(join6(cwd, "requirements.txt"));
|
|
1601
1659
|
if (!content) return [];
|
|
1602
1660
|
const deps = [];
|
|
1603
1661
|
for (const line of content.split("\n")) {
|
|
@@ -1611,7 +1669,7 @@ async function parseRequirementsTxt(cwd) {
|
|
|
1611
1669
|
return deps;
|
|
1612
1670
|
}
|
|
1613
1671
|
async function parsePyprojectToml(cwd) {
|
|
1614
|
-
const content = await readFileOrNull(
|
|
1672
|
+
const content = await readFileOrNull(join6(cwd, "pyproject.toml"));
|
|
1615
1673
|
if (!content) return [];
|
|
1616
1674
|
const deps = [];
|
|
1617
1675
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1766,13 +1824,12 @@ async function installCommand(input2, skillName, options) {
|
|
|
1766
1824
|
} else {
|
|
1767
1825
|
const indexWidth = data.skills.length.toString().length;
|
|
1768
1826
|
const maxNameLen = Math.max(...data.skills.map((s) => s.name.length));
|
|
1769
|
-
const
|
|
1827
|
+
const popularityColWidth = 13;
|
|
1770
1828
|
const choices = skillsWithRepo.map((s, index) => {
|
|
1771
1829
|
const indexStr = pc7.dim(`${(index + 1).toString().padStart(indexWidth)}.`);
|
|
1772
1830
|
const paddedName = s.name.padEnd(maxNameLen);
|
|
1773
|
-
const
|
|
1774
|
-
const
|
|
1775
|
-
const trust = formatTrustScore(s.trustScore);
|
|
1831
|
+
const popularity = formatPopularity(s.installCount) + " ".repeat(popularityColWidth - 4);
|
|
1832
|
+
const trust = formatTrust(s.trustScore);
|
|
1776
1833
|
const skillUrl = `https://context7.com/skills${s.project}/${s.name}`;
|
|
1777
1834
|
const skillLink = terminalLink(s.name, skillUrl, pc7.white);
|
|
1778
1835
|
const repoLink = terminalLink(s.project, `https://github.com${s.project}`, pc7.white);
|
|
@@ -1781,11 +1838,13 @@ async function installCommand(input2, skillName, options) {
|
|
|
1781
1838
|
"",
|
|
1782
1839
|
`${pc7.yellow("Skill:")} ${skillLink}`,
|
|
1783
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("-")}`,
|
|
1784
1843
|
`${pc7.yellow("Description:")}`,
|
|
1785
1844
|
pc7.white(s.description || "No description")
|
|
1786
1845
|
];
|
|
1787
1846
|
return {
|
|
1788
|
-
name: `${indexStr} ${paddedName} ${
|
|
1847
|
+
name: `${indexStr} ${paddedName} ${popularity}${trust}`,
|
|
1789
1848
|
value: s,
|
|
1790
1849
|
description: metadataLines.join("\n")
|
|
1791
1850
|
};
|
|
@@ -1793,7 +1852,7 @@ async function installCommand(input2, skillName, options) {
|
|
|
1793
1852
|
log.blank();
|
|
1794
1853
|
const checkboxPrefixWidth = 3;
|
|
1795
1854
|
const headerPad = " ".repeat(checkboxPrefixWidth + indexWidth + 1 + 1 + maxNameLen + 1);
|
|
1796
|
-
const headerLine = headerPad + pc7.dim("
|
|
1855
|
+
const headerLine = headerPad + pc7.dim("Popularity".padEnd(popularityColWidth)) + pc7.dim("Trust");
|
|
1797
1856
|
try {
|
|
1798
1857
|
selectedSkills = await checkboxWithHover({
|
|
1799
1858
|
message: `Select skills to install:
|
|
@@ -1850,7 +1909,7 @@ ${headerLine}`,
|
|
|
1850
1909
|
}
|
|
1851
1910
|
throw dirErr;
|
|
1852
1911
|
}
|
|
1853
|
-
const primarySkillDir =
|
|
1912
|
+
const primarySkillDir = join7(primaryDir, skill.name);
|
|
1854
1913
|
for (const targetDir of symlinkDirs) {
|
|
1855
1914
|
try {
|
|
1856
1915
|
await symlinkSkill(skill.name, primarySkillDir, targetDir);
|
|
@@ -1878,7 +1937,7 @@ ${headerLine}`,
|
|
|
1878
1937
|
log.blank();
|
|
1879
1938
|
log.warn("Fix permissions with:");
|
|
1880
1939
|
for (const dir of failedDirs) {
|
|
1881
|
-
const parentDir =
|
|
1940
|
+
const parentDir = join7(dir, "..");
|
|
1882
1941
|
log.dim(` sudo chown -R $(whoami) "${parentDir}"`);
|
|
1883
1942
|
}
|
|
1884
1943
|
log.blank();
|
|
@@ -1912,14 +1971,16 @@ async function searchCommand(query) {
|
|
|
1912
1971
|
trackEvent("search_query", { query, resultCount: data.results.length });
|
|
1913
1972
|
log.blank();
|
|
1914
1973
|
const indexWidth = data.results.length.toString().length;
|
|
1915
|
-
const
|
|
1916
|
-
const
|
|
1974
|
+
const nameWithRepo = (s) => `${s.name} ${pc7.dim(`(${s.project})`)}`;
|
|
1975
|
+
const nameWithRepoLen = (s) => `${s.name} (${s.project})`.length;
|
|
1976
|
+
const maxNameLen = Math.max(...data.results.map(nameWithRepoLen));
|
|
1977
|
+
const popularityColWidth = 13;
|
|
1917
1978
|
const choices = data.results.map((s, index) => {
|
|
1918
1979
|
const indexStr = pc7.dim(`${(index + 1).toString().padStart(indexWidth)}.`);
|
|
1919
|
-
const
|
|
1920
|
-
const
|
|
1921
|
-
const
|
|
1922
|
-
const trust =
|
|
1980
|
+
const rawLen = nameWithRepoLen(s);
|
|
1981
|
+
const displayName = nameWithRepo(s) + " ".repeat(maxNameLen - rawLen);
|
|
1982
|
+
const popularity = formatPopularity(s.installCount) + " ".repeat(popularityColWidth - 4);
|
|
1983
|
+
const trust = formatTrust(s.trustScore);
|
|
1923
1984
|
const skillLink = terminalLink(
|
|
1924
1985
|
s.name,
|
|
1925
1986
|
`https://context7.com/skills${s.project}/${s.name}`,
|
|
@@ -1931,18 +1992,20 @@ async function searchCommand(query) {
|
|
|
1931
1992
|
"",
|
|
1932
1993
|
`${pc7.yellow("Skill:")} ${skillLink}`,
|
|
1933
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("-")}`,
|
|
1934
1997
|
`${pc7.yellow("Description:")}`,
|
|
1935
1998
|
pc7.white(s.description || "No description")
|
|
1936
1999
|
];
|
|
1937
2000
|
return {
|
|
1938
|
-
name: `${indexStr} ${
|
|
2001
|
+
name: `${indexStr} ${displayName} ${popularity}${trust}`,
|
|
1939
2002
|
value: s,
|
|
1940
2003
|
description: metadataLines.join("\n")
|
|
1941
2004
|
};
|
|
1942
2005
|
});
|
|
1943
2006
|
const checkboxPrefixWidth = 3;
|
|
1944
2007
|
const headerPad = " ".repeat(checkboxPrefixWidth + indexWidth + 1 + 1 + maxNameLen + 1);
|
|
1945
|
-
const headerLine = headerPad + pc7.dim("
|
|
2008
|
+
const headerLine = headerPad + pc7.dim("Popularity".padEnd(popularityColWidth)) + pc7.dim("Trust");
|
|
1946
2009
|
let selectedSkills;
|
|
1947
2010
|
try {
|
|
1948
2011
|
selectedSkills = await checkboxWithHover({
|
|
@@ -1999,7 +2062,7 @@ ${headerLine}`,
|
|
|
1999
2062
|
}
|
|
2000
2063
|
throw dirErr;
|
|
2001
2064
|
}
|
|
2002
|
-
const primarySkillDir =
|
|
2065
|
+
const primarySkillDir = join7(primaryDir, skill.name);
|
|
2003
2066
|
for (const targetDir of symlinkDirs) {
|
|
2004
2067
|
try {
|
|
2005
2068
|
await symlinkSkill(skill.name, primarySkillDir, targetDir);
|
|
@@ -2027,7 +2090,7 @@ ${headerLine}`,
|
|
|
2027
2090
|
log.blank();
|
|
2028
2091
|
log.warn("Fix permissions with:");
|
|
2029
2092
|
for (const dir of failedDirs) {
|
|
2030
|
-
const parentDir =
|
|
2093
|
+
const parentDir = join7(dir, "..");
|
|
2031
2094
|
log.dim(` sudo chown -R $(whoami) "${parentDir}"`);
|
|
2032
2095
|
}
|
|
2033
2096
|
log.blank();
|
|
@@ -2054,7 +2117,7 @@ async function listCommand(options) {
|
|
|
2054
2117
|
if (hasExplicitIdeOption(options)) {
|
|
2055
2118
|
const ides = getSelectedIdes(options);
|
|
2056
2119
|
for (const ide of ides) {
|
|
2057
|
-
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]);
|
|
2058
2121
|
const label = ide === "universal" ? UNIVERSAL_AGENTS_LABEL : IDE_NAMES[ide];
|
|
2059
2122
|
const skills = await scanDir(dir);
|
|
2060
2123
|
if (skills.length > 0) {
|
|
@@ -2063,14 +2126,14 @@ async function listCommand(options) {
|
|
|
2063
2126
|
}
|
|
2064
2127
|
} else {
|
|
2065
2128
|
const universalPath = scope === "global" ? UNIVERSAL_SKILLS_GLOBAL_PATH : UNIVERSAL_SKILLS_PATH;
|
|
2066
|
-
const universalDir =
|
|
2129
|
+
const universalDir = join7(baseDir, universalPath);
|
|
2067
2130
|
const universalSkills = await scanDir(universalDir);
|
|
2068
2131
|
if (universalSkills.length > 0) {
|
|
2069
2132
|
results.push({ label: UNIVERSAL_AGENTS_LABEL, path: universalPath, skills: universalSkills });
|
|
2070
2133
|
}
|
|
2071
2134
|
for (const ide of VENDOR_SPECIFIC_AGENTS) {
|
|
2072
2135
|
const pathMap = scope === "global" ? IDE_GLOBAL_PATHS : IDE_PATHS;
|
|
2073
|
-
const dir =
|
|
2136
|
+
const dir = join7(baseDir, pathMap[ide]);
|
|
2074
2137
|
const skills = await scanDir(dir);
|
|
2075
2138
|
if (skills.length > 0) {
|
|
2076
2139
|
results.push({ label: IDE_NAMES[ide], path: pathMap[ide], skills });
|
|
@@ -2098,7 +2161,7 @@ async function removeCommand(name, options) {
|
|
|
2098
2161
|
return;
|
|
2099
2162
|
}
|
|
2100
2163
|
const skillsDir = getTargetDirFromSelection(target.ide, target.scope);
|
|
2101
|
-
const skillPath =
|
|
2164
|
+
const skillPath = join7(skillsDir, name);
|
|
2102
2165
|
try {
|
|
2103
2166
|
await rm2(skillPath, { recursive: true });
|
|
2104
2167
|
log.success(`Removed skill: ${name}`);
|
|
@@ -2183,18 +2246,20 @@ async function suggestCommand(options) {
|
|
|
2183
2246
|
searchSpinner.succeed(`Found ${skills.length} relevant skill(s)`);
|
|
2184
2247
|
trackEvent("suggest_results", { depCount: deps.length, skillCount: skills.length });
|
|
2185
2248
|
log.blank();
|
|
2186
|
-
const
|
|
2187
|
-
const
|
|
2188
|
-
const
|
|
2249
|
+
const nameWithRepo = (s) => `${s.name} ${pc7.dim(`(${s.project})`)}`;
|
|
2250
|
+
const nameWithRepoLen = (s) => `${s.name} (${s.project})`.length;
|
|
2251
|
+
const maxNameLen = Math.max(...skills.map(nameWithRepoLen));
|
|
2252
|
+
const popularityColWidth = 13;
|
|
2253
|
+
const trustColWidth = 8;
|
|
2189
2254
|
const maxMatchedLen = Math.max(...skills.map((s) => s.matchedDep.length));
|
|
2190
2255
|
const indexWidth = skills.length.toString().length;
|
|
2191
2256
|
const choices = skills.map((s, index) => {
|
|
2192
2257
|
const indexStr = pc7.dim(`${(index + 1).toString().padStart(indexWidth)}.`);
|
|
2193
|
-
const
|
|
2194
|
-
const
|
|
2195
|
-
const
|
|
2196
|
-
const
|
|
2197
|
-
const trust =
|
|
2258
|
+
const rawLen = nameWithRepoLen(s);
|
|
2259
|
+
const displayName = nameWithRepo(s) + " ".repeat(maxNameLen - rawLen);
|
|
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);
|
|
2198
2263
|
const matched = pc7.yellow(s.matchedDep.padEnd(maxMatchedLen));
|
|
2199
2264
|
const skillLink = terminalLink(
|
|
2200
2265
|
s.name,
|
|
@@ -2207,19 +2272,21 @@ async function suggestCommand(options) {
|
|
|
2207
2272
|
"",
|
|
2208
2273
|
`${pc7.yellow("Skill:")} ${skillLink}`,
|
|
2209
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("-")}`,
|
|
2210
2277
|
`${pc7.yellow("Relevant:")} ${pc7.white(s.matchedDep)}`,
|
|
2211
2278
|
`${pc7.yellow("Description:")}`,
|
|
2212
2279
|
pc7.white(s.description || "No description")
|
|
2213
2280
|
];
|
|
2214
2281
|
return {
|
|
2215
|
-
name: `${indexStr} ${
|
|
2282
|
+
name: `${indexStr} ${displayName} ${popularity}${trust}${matched}`,
|
|
2216
2283
|
value: s,
|
|
2217
2284
|
description: metadataLines.join("\n")
|
|
2218
2285
|
};
|
|
2219
2286
|
});
|
|
2220
2287
|
const checkboxPrefixWidth = 3;
|
|
2221
2288
|
const headerPad = " ".repeat(checkboxPrefixWidth + indexWidth + 1 + 1 + maxNameLen + 1);
|
|
2222
|
-
const headerLine = headerPad + pc7.dim("
|
|
2289
|
+
const headerLine = headerPad + pc7.dim("Popularity".padEnd(popularityColWidth)) + pc7.dim("Trust".padEnd(trustColWidth)) + pc7.dim("Relevant");
|
|
2223
2290
|
let selectedSkills;
|
|
2224
2291
|
try {
|
|
2225
2292
|
selectedSkills = await checkboxWithHover({
|
|
@@ -2275,7 +2342,7 @@ ${headerLine}`,
|
|
|
2275
2342
|
}
|
|
2276
2343
|
throw dirErr;
|
|
2277
2344
|
}
|
|
2278
|
-
const primarySkillDir =
|
|
2345
|
+
const primarySkillDir = join7(primaryDir, skill.name);
|
|
2279
2346
|
for (const targetDir of symlinkDirs) {
|
|
2280
2347
|
try {
|
|
2281
2348
|
await symlinkSkill(skill.name, primarySkillDir, targetDir);
|
|
@@ -2303,7 +2370,7 @@ ${headerLine}`,
|
|
|
2303
2370
|
log.blank();
|
|
2304
2371
|
log.warn("Fix permissions with:");
|
|
2305
2372
|
for (const dir of failedDirs) {
|
|
2306
|
-
const parentDir =
|
|
2373
|
+
const parentDir = join7(dir, "..");
|
|
2307
2374
|
log.dim(` sudo chown -R $(whoami) "${parentDir}"`);
|
|
2308
2375
|
}
|
|
2309
2376
|
log.blank();
|
|
@@ -2318,13 +2385,14 @@ ${headerLine}`,
|
|
|
2318
2385
|
// src/commands/setup.ts
|
|
2319
2386
|
import pc8 from "picocolors";
|
|
2320
2387
|
import ora4 from "ora";
|
|
2388
|
+
import { select as select3 } from "@inquirer/prompts";
|
|
2321
2389
|
import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
|
|
2322
|
-
import { dirname as
|
|
2390
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
2323
2391
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2324
2392
|
|
|
2325
2393
|
// src/setup/agents.ts
|
|
2326
2394
|
import { access as access2 } from "fs/promises";
|
|
2327
|
-
import { join as
|
|
2395
|
+
import { join as join8 } from "path";
|
|
2328
2396
|
import { homedir as homedir5 } from "os";
|
|
2329
2397
|
var SETUP_AGENT_NAMES = {
|
|
2330
2398
|
claude: "Claude Code",
|
|
@@ -2351,43 +2419,43 @@ var agents = {
|
|
|
2351
2419
|
displayName: "Claude Code",
|
|
2352
2420
|
mcp: {
|
|
2353
2421
|
projectPath: ".mcp.json",
|
|
2354
|
-
globalPath:
|
|
2422
|
+
globalPath: join8(homedir5(), ".claude.json"),
|
|
2355
2423
|
configKey: "mcpServers",
|
|
2356
2424
|
buildEntry: (auth) => withHeaders({ type: "http", url: mcpUrl(auth) }, auth)
|
|
2357
2425
|
},
|
|
2358
2426
|
rule: {
|
|
2359
|
-
dir: (scope) => scope === "global" ?
|
|
2427
|
+
dir: (scope) => scope === "global" ? join8(homedir5(), ".claude", "rules") : join8(".claude", "rules"),
|
|
2360
2428
|
filename: "context7.md"
|
|
2361
2429
|
},
|
|
2362
2430
|
skill: {
|
|
2363
|
-
name: "
|
|
2364
|
-
dir: (scope) => scope === "global" ?
|
|
2431
|
+
name: "context7-mcp",
|
|
2432
|
+
dir: (scope) => scope === "global" ? join8(homedir5(), ".claude", "skills") : join8(".claude", "skills")
|
|
2365
2433
|
},
|
|
2366
2434
|
detect: {
|
|
2367
2435
|
projectPaths: [".mcp.json", ".claude"],
|
|
2368
|
-
globalPaths: [
|
|
2436
|
+
globalPaths: [join8(homedir5(), ".claude")]
|
|
2369
2437
|
}
|
|
2370
2438
|
},
|
|
2371
2439
|
cursor: {
|
|
2372
2440
|
name: "cursor",
|
|
2373
2441
|
displayName: "Cursor",
|
|
2374
2442
|
mcp: {
|
|
2375
|
-
projectPath:
|
|
2376
|
-
globalPath:
|
|
2443
|
+
projectPath: join8(".cursor", "mcp.json"),
|
|
2444
|
+
globalPath: join8(homedir5(), ".cursor", "mcp.json"),
|
|
2377
2445
|
configKey: "mcpServers",
|
|
2378
2446
|
buildEntry: (auth) => withHeaders({ url: mcpUrl(auth) }, auth)
|
|
2379
2447
|
},
|
|
2380
2448
|
rule: {
|
|
2381
|
-
dir: (scope) => scope === "global" ?
|
|
2449
|
+
dir: (scope) => scope === "global" ? join8(homedir5(), ".cursor", "rules") : join8(".cursor", "rules"),
|
|
2382
2450
|
filename: "context7.mdc"
|
|
2383
2451
|
},
|
|
2384
2452
|
skill: {
|
|
2385
|
-
name: "
|
|
2386
|
-
dir: (scope) => scope === "global" ?
|
|
2453
|
+
name: "context7-mcp",
|
|
2454
|
+
dir: (scope) => scope === "global" ? join8(homedir5(), ".cursor", "skills") : join8(".cursor", "skills")
|
|
2387
2455
|
},
|
|
2388
2456
|
detect: {
|
|
2389
2457
|
projectPaths: [".cursor"],
|
|
2390
|
-
globalPaths: [
|
|
2458
|
+
globalPaths: [join8(homedir5(), ".cursor")]
|
|
2391
2459
|
}
|
|
2392
2460
|
},
|
|
2393
2461
|
opencode: {
|
|
@@ -2395,22 +2463,22 @@ var agents = {
|
|
|
2395
2463
|
displayName: "OpenCode",
|
|
2396
2464
|
mcp: {
|
|
2397
2465
|
projectPath: ".opencode.json",
|
|
2398
|
-
globalPath:
|
|
2466
|
+
globalPath: join8(homedir5(), ".config", "opencode", "opencode.json"),
|
|
2399
2467
|
configKey: "mcp",
|
|
2400
2468
|
buildEntry: (auth) => withHeaders({ type: "remote", url: mcpUrl(auth), enabled: true }, auth)
|
|
2401
2469
|
},
|
|
2402
2470
|
rule: {
|
|
2403
|
-
dir: (scope) => scope === "global" ?
|
|
2471
|
+
dir: (scope) => scope === "global" ? join8(homedir5(), ".config", "opencode", "rules") : join8(".opencode", "rules"),
|
|
2404
2472
|
filename: "context7.md",
|
|
2405
|
-
instructionsGlob: (scope) => scope === "global" ?
|
|
2473
|
+
instructionsGlob: (scope) => scope === "global" ? join8(homedir5(), ".config", "opencode", "rules", "*.md") : ".opencode/rules/*.md"
|
|
2406
2474
|
},
|
|
2407
2475
|
skill: {
|
|
2408
|
-
name: "
|
|
2409
|
-
dir: (scope) => scope === "global" ?
|
|
2476
|
+
name: "context7-mcp",
|
|
2477
|
+
dir: (scope) => scope === "global" ? join8(homedir5(), ".agents", "skills") : join8(".agents", "skills")
|
|
2410
2478
|
},
|
|
2411
2479
|
detect: {
|
|
2412
2480
|
projectPaths: [".opencode.json"],
|
|
2413
|
-
globalPaths: [
|
|
2481
|
+
globalPaths: [join8(homedir5(), ".config", "opencode")]
|
|
2414
2482
|
}
|
|
2415
2483
|
}
|
|
2416
2484
|
};
|
|
@@ -2431,7 +2499,7 @@ async function detectAgents(scope) {
|
|
|
2431
2499
|
for (const agent of Object.values(agents)) {
|
|
2432
2500
|
const paths = scope === "global" ? agent.detect.globalPaths : agent.detect.projectPaths;
|
|
2433
2501
|
for (const p of paths) {
|
|
2434
|
-
const fullPath = scope === "global" ? p :
|
|
2502
|
+
const fullPath = scope === "global" ? p : join8(process.cwd(), p);
|
|
2435
2503
|
if (await pathExists(fullPath)) {
|
|
2436
2504
|
detected.push(agent.name);
|
|
2437
2505
|
break;
|
|
@@ -2442,60 +2510,6 @@ async function detectAgents(scope) {
|
|
|
2442
2510
|
}
|
|
2443
2511
|
|
|
2444
2512
|
// src/setup/templates.ts
|
|
2445
|
-
var SKILL_CONTENT = `---
|
|
2446
|
-
name: documentation-lookup
|
|
2447
|
-
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.
|
|
2448
|
-
---
|
|
2449
|
-
|
|
2450
|
-
When the user asks about libraries, frameworks, or needs code examples, use Context7 to fetch current documentation instead of relying on training data.
|
|
2451
|
-
|
|
2452
|
-
## When to Use This Skill
|
|
2453
|
-
|
|
2454
|
-
Activate this skill when the user:
|
|
2455
|
-
|
|
2456
|
-
- Asks setup or configuration questions ("How do I configure Next.js middleware?")
|
|
2457
|
-
- Requests code involving libraries ("Write a Prisma query for...")
|
|
2458
|
-
- Needs API references ("What are the Supabase auth methods?")
|
|
2459
|
-
- Mentions specific frameworks (React, Vue, Svelte, Express, Tailwind, etc.)
|
|
2460
|
-
|
|
2461
|
-
## How to Fetch Documentation
|
|
2462
|
-
|
|
2463
|
-
### Step 1: Resolve the Library ID
|
|
2464
|
-
|
|
2465
|
-
Call \`resolve-library-id\` with:
|
|
2466
|
-
|
|
2467
|
-
- \`libraryName\`: The library name extracted from the user's question
|
|
2468
|
-
- \`query\`: The user's full question (improves relevance ranking)
|
|
2469
|
-
|
|
2470
|
-
### Step 2: Select the Best Match
|
|
2471
|
-
|
|
2472
|
-
From the resolution results, choose based on:
|
|
2473
|
-
|
|
2474
|
-
- Exact or closest name match to what the user asked for
|
|
2475
|
-
- Higher benchmark scores indicate better documentation quality
|
|
2476
|
-
- If the user mentioned a version (e.g., "React 19"), prefer version-specific IDs
|
|
2477
|
-
|
|
2478
|
-
### Step 3: Fetch the Documentation
|
|
2479
|
-
|
|
2480
|
-
Call \`query-docs\` with:
|
|
2481
|
-
|
|
2482
|
-
- \`libraryId\`: The selected Context7 library ID (e.g., \`/vercel/next.js\`)
|
|
2483
|
-
- \`query\`: The user's specific question
|
|
2484
|
-
|
|
2485
|
-
### Step 4: Use the Documentation
|
|
2486
|
-
|
|
2487
|
-
Incorporate the fetched documentation into your response:
|
|
2488
|
-
|
|
2489
|
-
- Answer the user's question using current, accurate information
|
|
2490
|
-
- Include relevant code examples from the docs
|
|
2491
|
-
- Cite the library version when relevant
|
|
2492
|
-
|
|
2493
|
-
## Guidelines
|
|
2494
|
-
|
|
2495
|
-
- **Be specific**: Pass the user's full question as the query for better results
|
|
2496
|
-
- **Version awareness**: When users mention versions ("Next.js 15", "React 19"), use version-specific library IDs if available from the resolution step
|
|
2497
|
-
- **Prefer official sources**: When multiple matches exist, prefer official/primary packages over community forks
|
|
2498
|
-
`;
|
|
2499
2513
|
var RULE_CONTENT = `---
|
|
2500
2514
|
alwaysApply: true
|
|
2501
2515
|
---
|
|
@@ -2512,7 +2526,7 @@ When working with libraries, frameworks, or APIs \u2014 use Context7 MCP to fetc
|
|
|
2512
2526
|
|
|
2513
2527
|
// src/setup/mcp-writer.ts
|
|
2514
2528
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
2515
|
-
import { dirname as
|
|
2529
|
+
import { dirname as dirname3 } from "path";
|
|
2516
2530
|
async function readJsonConfig(filePath) {
|
|
2517
2531
|
let raw;
|
|
2518
2532
|
try {
|
|
@@ -2546,7 +2560,7 @@ function mergeInstructions(config, glob) {
|
|
|
2546
2560
|
return { ...config, instructions: [...instructions, glob] };
|
|
2547
2561
|
}
|
|
2548
2562
|
async function writeJsonConfig(filePath, config) {
|
|
2549
|
-
await mkdir3(
|
|
2563
|
+
await mkdir3(dirname3(filePath), { recursive: true });
|
|
2550
2564
|
await writeFile3(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
2551
2565
|
}
|
|
2552
2566
|
|
|
@@ -2565,7 +2579,7 @@ function getSelectedAgents(options) {
|
|
|
2565
2579
|
return agents2;
|
|
2566
2580
|
}
|
|
2567
2581
|
function registerSetupCommand(program2) {
|
|
2568
|
-
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) => {
|
|
2569
2583
|
await setupCommand(options);
|
|
2570
2584
|
});
|
|
2571
2585
|
}
|
|
@@ -2605,9 +2619,49 @@ async function resolveAuth(options) {
|
|
|
2605
2619
|
if (!apiKey) return null;
|
|
2606
2620
|
return { mode: "api-key", apiKey };
|
|
2607
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
|
+
}
|
|
2608
2662
|
async function isAlreadyConfigured(agentName, scope) {
|
|
2609
2663
|
const agent = getAgent(agentName);
|
|
2610
|
-
const mcpPath = scope === "global" ? agent.mcp.globalPath :
|
|
2664
|
+
const mcpPath = scope === "global" ? agent.mcp.globalPath : join9(process.cwd(), agent.mcp.projectPath);
|
|
2611
2665
|
try {
|
|
2612
2666
|
const existing = await readJsonConfig(mcpPath);
|
|
2613
2667
|
const section = existing[agent.mcp.configKey] ?? {};
|
|
@@ -2616,10 +2670,10 @@ async function isAlreadyConfigured(agentName, scope) {
|
|
|
2616
2670
|
return false;
|
|
2617
2671
|
}
|
|
2618
2672
|
}
|
|
2619
|
-
async function promptAgents(scope) {
|
|
2673
|
+
async function promptAgents(scope, mode) {
|
|
2620
2674
|
const choices = await Promise.all(
|
|
2621
2675
|
ALL_AGENT_NAMES.map(async (name) => {
|
|
2622
|
-
const configured = await isAlreadyConfigured(name, scope);
|
|
2676
|
+
const configured = mode === "mcp" ? await isAlreadyConfigured(name, scope) : false;
|
|
2623
2677
|
return {
|
|
2624
2678
|
name: SETUP_AGENT_NAMES[name],
|
|
2625
2679
|
value: name,
|
|
@@ -2631,10 +2685,11 @@ async function promptAgents(scope) {
|
|
|
2631
2685
|
log.info("Context7 is already configured for all detected agents.");
|
|
2632
2686
|
return null;
|
|
2633
2687
|
}
|
|
2688
|
+
const message = mode === "cli" ? "Install find-docs skill for which agents?" : "Which agents do you want to set up?";
|
|
2634
2689
|
try {
|
|
2635
2690
|
return await checkboxWithHover(
|
|
2636
2691
|
{
|
|
2637
|
-
message
|
|
2692
|
+
message,
|
|
2638
2693
|
choices,
|
|
2639
2694
|
loop: false,
|
|
2640
2695
|
theme: CHECKBOX_THEME
|
|
@@ -2645,13 +2700,13 @@ async function promptAgents(scope) {
|
|
|
2645
2700
|
return null;
|
|
2646
2701
|
}
|
|
2647
2702
|
}
|
|
2648
|
-
async function resolveAgents(options, scope) {
|
|
2703
|
+
async function resolveAgents(options, scope, mode = "mcp") {
|
|
2649
2704
|
const explicit = getSelectedAgents(options);
|
|
2650
2705
|
if (explicit.length > 0) return explicit;
|
|
2651
2706
|
const detected = await detectAgents(scope);
|
|
2652
2707
|
if (detected.length > 0 && options.yes) return detected;
|
|
2653
2708
|
log.blank();
|
|
2654
|
-
const selected = await promptAgents(scope);
|
|
2709
|
+
const selected = await promptAgents(scope, mode);
|
|
2655
2710
|
if (!selected) {
|
|
2656
2711
|
log.warn("Setup cancelled");
|
|
2657
2712
|
return [];
|
|
@@ -2660,7 +2715,7 @@ async function resolveAgents(options, scope) {
|
|
|
2660
2715
|
}
|
|
2661
2716
|
async function setupAgent(agentName, auth, scope) {
|
|
2662
2717
|
const agent = getAgent(agentName);
|
|
2663
|
-
const mcpPath = scope === "global" ? agent.mcp.globalPath :
|
|
2718
|
+
const mcpPath = scope === "global" ? agent.mcp.globalPath : join9(process.cwd(), agent.mcp.projectPath);
|
|
2664
2719
|
let mcpStatus;
|
|
2665
2720
|
try {
|
|
2666
2721
|
const existing = await readJsonConfig(mcpPath);
|
|
@@ -2682,21 +2737,24 @@ async function setupAgent(agentName, auth, scope) {
|
|
|
2682
2737
|
} catch (err) {
|
|
2683
2738
|
mcpStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
2684
2739
|
}
|
|
2685
|
-
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);
|
|
2686
2741
|
let ruleStatus;
|
|
2687
2742
|
try {
|
|
2688
|
-
await mkdir4(
|
|
2743
|
+
await mkdir4(dirname4(rulePath), { recursive: true });
|
|
2689
2744
|
await writeFile4(rulePath, RULE_CONTENT, "utf-8");
|
|
2690
2745
|
ruleStatus = "installed";
|
|
2691
2746
|
} catch (err) {
|
|
2692
2747
|
ruleStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
2693
2748
|
}
|
|
2694
|
-
const skillDir = scope === "global" ? agent.skill.dir("global") :
|
|
2695
|
-
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");
|
|
2696
2751
|
let skillStatus;
|
|
2697
2752
|
try {
|
|
2698
|
-
await
|
|
2699
|
-
|
|
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);
|
|
2700
2758
|
skillStatus = "installed";
|
|
2701
2759
|
} catch (err) {
|
|
2702
2760
|
skillStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -2711,11 +2769,7 @@ async function setupAgent(agentName, auth, scope) {
|
|
|
2711
2769
|
skillPath
|
|
2712
2770
|
};
|
|
2713
2771
|
}
|
|
2714
|
-
async function
|
|
2715
|
-
trackEvent("command", { name: "setup" });
|
|
2716
|
-
const scope = options.project ? "project" : "global";
|
|
2717
|
-
const agents2 = await resolveAgents(options, scope);
|
|
2718
|
-
if (agents2.length === 0) return;
|
|
2772
|
+
async function setupMcp(agents2, options, scope) {
|
|
2719
2773
|
const auth = await resolveAuth(options);
|
|
2720
2774
|
if (!auth) {
|
|
2721
2775
|
log.warn("Setup cancelled");
|
|
@@ -2745,11 +2799,71 @@ async function setupCommand(options) {
|
|
|
2745
2799
|
log.blank();
|
|
2746
2800
|
trackEvent("setup", { agents: agents2, scope, authMode: auth.mode });
|
|
2747
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
|
+
}
|
|
2748
2856
|
|
|
2749
2857
|
// src/commands/docs.ts
|
|
2750
2858
|
import pc9 from "picocolors";
|
|
2751
2859
|
import ora5 from "ora";
|
|
2752
2860
|
var isTTY = process.stdout.isTTY;
|
|
2861
|
+
function getReputationLabel(score) {
|
|
2862
|
+
if (score === void 0 || score < 0) return "Unknown";
|
|
2863
|
+
if (score >= 7) return "High";
|
|
2864
|
+
if (score >= 4) return "Medium";
|
|
2865
|
+
return "Low";
|
|
2866
|
+
}
|
|
2753
2867
|
function getAccessToken() {
|
|
2754
2868
|
const tokens = loadTokens();
|
|
2755
2869
|
if (!tokens || isTokenExpired(tokens)) return void 0;
|
|
@@ -2757,28 +2871,22 @@ function getAccessToken() {
|
|
|
2757
2871
|
}
|
|
2758
2872
|
function formatLibraryResult(lib, index) {
|
|
2759
2873
|
const lines = [];
|
|
2760
|
-
lines.push(`${pc9.dim(`${index + 1}.`)} ${pc9.bold(
|
|
2874
|
+
lines.push(`${pc9.dim(`${index + 1}.`)} ${pc9.bold(`Title: ${lib.title}`)}`);
|
|
2875
|
+
lines.push(` ${pc9.cyan(`Context7-compatible library ID: ${lib.id}`)}`);
|
|
2761
2876
|
if (lib.description) {
|
|
2762
|
-
lines.push(` ${pc9.dim(lib.description)}`);
|
|
2877
|
+
lines.push(` ${pc9.dim(`Description: ${lib.description}`)}`);
|
|
2763
2878
|
}
|
|
2764
|
-
const meta = [];
|
|
2765
2879
|
if (lib.totalSnippets) {
|
|
2766
|
-
|
|
2767
|
-
}
|
|
2768
|
-
if (lib.stars && lib.stars > 0) {
|
|
2769
|
-
meta.push(`${lib.stars.toLocaleString()} stars`);
|
|
2880
|
+
lines.push(` ${pc9.dim(`Code Snippets: ${lib.totalSnippets}`)}`);
|
|
2770
2881
|
}
|
|
2771
2882
|
if (lib.trustScore !== void 0) {
|
|
2772
|
-
|
|
2883
|
+
lines.push(` ${pc9.dim(`Source Reputation: ${getReputationLabel(lib.trustScore)}`)}`);
|
|
2773
2884
|
}
|
|
2774
2885
|
if (lib.benchmarkScore !== void 0 && lib.benchmarkScore > 0) {
|
|
2775
|
-
|
|
2776
|
-
}
|
|
2777
|
-
if (meta.length > 0) {
|
|
2778
|
-
lines.push(` ${pc9.dim(meta.join(" \xB7 "))}`);
|
|
2886
|
+
lines.push(` ${pc9.dim(`Benchmark Score: ${lib.benchmarkScore}`)}`);
|
|
2779
2887
|
}
|
|
2780
2888
|
if (lib.versions && lib.versions.length > 0) {
|
|
2781
|
-
lines.push(` ${pc9.dim(`
|
|
2889
|
+
lines.push(` ${pc9.dim(`Versions: ${lib.versions.join(", ")}`)}`);
|
|
2782
2890
|
}
|
|
2783
2891
|
return lines.join("\n");
|
|
2784
2892
|
}
|
|
@@ -2813,6 +2921,12 @@ async function resolveCommand(library, query, options) {
|
|
|
2813
2921
|
return;
|
|
2814
2922
|
}
|
|
2815
2923
|
log.blank();
|
|
2924
|
+
if (data.searchFilterApplied) {
|
|
2925
|
+
log.warn(
|
|
2926
|
+
"Your results only include libraries matching your access settings. To search across all public libraries, update your settings at https://context7.com/dashboard?tab=libraries"
|
|
2927
|
+
);
|
|
2928
|
+
log.blank();
|
|
2929
|
+
}
|
|
2816
2930
|
for (let i = 0; i < results.length; i++) {
|
|
2817
2931
|
log.plain(formatLibraryResult(results[i], i));
|
|
2818
2932
|
log.blank();
|
|
@@ -2828,9 +2942,9 @@ async function resolveCommand(library, query, options) {
|
|
|
2828
2942
|
}
|
|
2829
2943
|
async function queryCommand(libraryId, query, options) {
|
|
2830
2944
|
trackEvent("command", { name: "docs" });
|
|
2831
|
-
if (!libraryId.startsWith("/")) {
|
|
2832
|
-
log.error(`Invalid library ID: ${libraryId}`);
|
|
2833
|
-
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)`);
|
|
2834
2948
|
log.info(`Run "ctx7 library <name>" to find the correct ID`);
|
|
2835
2949
|
process.exitCode = 1;
|
|
2836
2950
|
return;
|
|
@@ -2909,15 +3023,6 @@ function registerDocsCommands(program2) {
|
|
|
2909
3023
|
});
|
|
2910
3024
|
}
|
|
2911
3025
|
|
|
2912
|
-
// src/constants.ts
|
|
2913
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
2914
|
-
import { fileURLToPath } from "url";
|
|
2915
|
-
import { dirname as dirname4, join as join9 } from "path";
|
|
2916
|
-
var __dirname = dirname4(fileURLToPath(import.meta.url));
|
|
2917
|
-
var pkg = JSON.parse(readFileSync2(join9(__dirname, "../package.json"), "utf-8"));
|
|
2918
|
-
var VERSION = pkg.version;
|
|
2919
|
-
var NAME = pkg.name;
|
|
2920
|
-
|
|
2921
3026
|
// src/index.ts
|
|
2922
3027
|
var brand = {
|
|
2923
3028
|
primary: pc10.green,
|