ctx7 0.3.3 → 0.3.5

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 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 join6 } from "path";
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 formatInstallCount(count, placeholder = "") {
382
- if (count === void 0 || count === 0) return placeholder;
383
- return pc2.yellow(String(count));
384
- }
385
- function formatTrustScore(score) {
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 < 3) return pc2.red(score.toFixed(1));
388
- return pc2.yellow(score.toFixed(1));
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 = dirname(pathMap[ide]);
519
+ const parentDir = dirname2(pathMap[ide]);
480
520
  try {
481
- await access(join(baseDir, parentDir));
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 join(homedir(), UNIVERSAL_SKILLS_GLOBAL_PATH);
530
+ return join2(homedir(), UNIVERSAL_SKILLS_GLOBAL_PATH);
491
531
  }
492
- return join(process.cwd(), UNIVERSAL_SKILLS_PATH);
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(join(baseDir, dirname(universalPath)));
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(join(baseDir, universalPath));
561
+ pathLines.push(join2(baseDir, universalPath));
522
562
  }
523
563
  for (const ide of detectedVendor) {
524
- pathLines.push(join(baseDir, pathMap[ide]));
564
+ pathLines.push(join2(baseDir, pathMap[ide]));
525
565
  }
526
566
  log.blank();
527
567
  let confirmed;
528
- try {
529
- confirmed = await confirm({
530
- message: `Install to detected location(s)?
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
- default: true
533
- });
534
- } catch {
535
- return null;
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 pc3.dim(text.split("\n")[0]);
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 = [{ name: universalLabel, value: "universal" }];
591
- for (const ide of VENDOR_SPECIFIC_AGENTS) {
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(join(baseDir, uniPath));
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(join(baseDir, pathMap[ide]));
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 join(homedir(), IDE_GLOBAL_PATHS[ide]);
710
+ return join2(homedir(), IDE_GLOBAL_PATHS[ide]);
659
711
  }
660
- return join(process.cwd(), IDE_PATHS[ide]);
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 join2 } from "path";
717
+ import { join as join3 } from "path";
666
718
  async function installSkillFiles(skillName, files, targetDir) {
667
- const skillDir = join2(targetDir, skillName);
719
+ const skillDir = join3(targetDir, skillName);
668
720
  for (const file of files) {
669
- const filePath = join2(skillDir, file.path);
670
- const fileDir = join2(filePath, "..");
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 = join2(targetDir, skillName);
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 join4 } from "path";
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 = join4(homedir3(), ".context7", "previews");
1508
+ const previewDir = join5(homedir3(), ".context7", "previews");
1457
1509
  await mkdir2(previewDir, { recursive: true });
1458
- previewFile = join4(previewDir, `${skillName}.md`);
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 = join4(finalDir, skillName);
1536
- const skillPath = join4(skillDir, "SKILL.md");
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 = join4(dir, "..");
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 join5 } from "path";
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(join5(cwd, "package.json"));
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(join5(cwd, "requirements.txt"));
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(join5(cwd, "pyproject.toml"));
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 installsColWidth = 10;
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 installsRaw = s.installCount ? String(s.installCount) : "-";
1780
- const paddedInstalls = formatInstallCount(s.installCount, pc7.dim("-")) + " ".repeat(installsColWidth - installsRaw.length);
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} ${paddedInstalls}${trust}`,
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("Installs".padEnd(installsColWidth)) + pc7.dim("Trust(0-10)");
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 = join6(primaryDir, skill.name);
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 = join6(dir, "..");
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 installsColWidth = 10;
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 installsRaw = s.installCount ? String(s.installCount) : "-";
1930
- const paddedInstalls = formatInstallCount(s.installCount, pc7.dim("-")) + " ".repeat(installsColWidth - installsRaw.length);
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} ${paddedInstalls}${trust}`,
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("Installs".padEnd(installsColWidth)) + pc7.dim("Trust(0-10)");
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 = join6(primaryDir, skill.name);
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 = join6(dir, "..");
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" ? join6(baseDir, scope === "global" ? UNIVERSAL_SKILLS_GLOBAL_PATH : UNIVERSAL_SKILLS_PATH) : join6(baseDir, (scope === "global" ? IDE_GLOBAL_PATHS : IDE_PATHS)[ide]);
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 = join6(baseDir, universalPath);
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 = join6(baseDir, pathMap[ide]);
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 = join6(skillsDir, name);
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 installsColWidth = 10;
2199
- const trustColWidth = 12;
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 installsRaw = s.installCount ? String(s.installCount) : "-";
2207
- const paddedInstalls = formatInstallCount(s.installCount, pc7.dim("-")) + " ".repeat(installsColWidth - installsRaw.length);
2208
- const trustRaw = s.trustScore !== void 0 && s.trustScore >= 0 ? s.trustScore.toFixed(1) : "-";
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} ${paddedInstalls}${trust}${matched}`,
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("Installs".padEnd(installsColWidth)) + pc7.dim("Trust(0-10)".padEnd(trustColWidth)) + pc7.dim("Relevant");
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 = join6(primaryDir, skill.name);
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 = join6(dir, "..");
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 dirname3, join as join8 } from "path";
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 join7 } from "path";
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: join7(homedir5(), ".claude.json"),
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" ? join7(homedir5(), ".claude", "rules") : join7(".claude", "rules"),
2427
+ dir: (scope) => scope === "global" ? join8(homedir5(), ".claude", "rules") : join8(".claude", "rules"),
2372
2428
  filename: "context7.md"
2373
2429
  },
2374
2430
  skill: {
2375
- name: "documentation-lookup",
2376
- dir: (scope) => scope === "global" ? join7(homedir5(), ".claude", "skills") : join7(".claude", "skills")
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: [join7(homedir5(), ".claude")]
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: join7(".cursor", "mcp.json"),
2388
- globalPath: join7(homedir5(), ".cursor", "mcp.json"),
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" ? join7(homedir5(), ".cursor", "rules") : join7(".cursor", "rules"),
2449
+ dir: (scope) => scope === "global" ? join8(homedir5(), ".cursor", "rules") : join8(".cursor", "rules"),
2394
2450
  filename: "context7.mdc"
2395
2451
  },
2396
2452
  skill: {
2397
- name: "documentation-lookup",
2398
- dir: (scope) => scope === "global" ? join7(homedir5(), ".cursor", "skills") : join7(".cursor", "skills")
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: [join7(homedir5(), ".cursor")]
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: join7(homedir5(), ".config", "opencode", "opencode.json"),
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" ? join7(homedir5(), ".config", "opencode", "rules") : join7(".opencode", "rules"),
2471
+ dir: (scope) => scope === "global" ? join8(homedir5(), ".config", "opencode", "rules") : join8(".opencode", "rules"),
2416
2472
  filename: "context7.md",
2417
- instructionsGlob: (scope) => scope === "global" ? join7(homedir5(), ".config", "opencode", "rules", "*.md") : ".opencode/rules/*.md"
2473
+ instructionsGlob: (scope) => scope === "global" ? join8(homedir5(), ".config", "opencode", "rules", "*.md") : ".opencode/rules/*.md"
2418
2474
  },
2419
2475
  skill: {
2420
- name: "documentation-lookup",
2421
- dir: (scope) => scope === "global" ? join7(homedir5(), ".agents", "skills") : join7(".agents", "skills")
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: [join7(homedir5(), ".config", "opencode")]
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 : join7(process.cwd(), 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 dirname2 } from "path";
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(dirname2(filePath), { recursive: true });
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 MCP and rule for your AI coding agent").option("--claude", "Set up for Claude Code").option("--cursor", "Set up for Cursor").option("--opencode", "Set up for OpenCode").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) => {
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 : join8(process.cwd(), agent.mcp.projectPath);
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: "Which agents do you want to set up?",
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 : join8(process.cwd(), agent.mcp.projectPath);
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" ? join8(agent.rule.dir("global"), agent.rule.filename) : join8(process.cwd(), agent.rule.dir("project"), agent.rule.filename);
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(dirname3(rulePath), { recursive: true });
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") : join8(process.cwd(), agent.skill.dir("project"));
2707
- const skillPath = join8(skillDir, agent.skill.name, "SKILL.md");
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 mkdir4(dirname3(skillPath), { recursive: true });
2711
- await writeFile4(skillPath, SKILL_CONTENT, "utf-8");
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 setupCommand(options) {
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");
@@ -2756,6 +2798,62 @@ async function setupCommand(options) {
2756
2798
  }
2757
2799
  log.blank();
2758
2800
  trackEvent("setup", { agents: agents2, scope, authMode: auth.mode });
2801
+ trackEvent("install", { skills: ["/upstash/context7/context7-mcp"], ides: agents2 });
2802
+ }
2803
+ async function setupCli(options) {
2804
+ await resolveCliAuth(options.apiKey);
2805
+ const targets = await promptForInstallTargets({ ...options, global: !options.project }, false);
2806
+ if (!targets) {
2807
+ log.warn("Setup cancelled");
2808
+ return;
2809
+ }
2810
+ log.blank();
2811
+ const spinner = ora4("Downloading find-docs skill...").start();
2812
+ const downloadData = await downloadSkill("/upstash/context7", "find-docs");
2813
+ if (downloadData.error || downloadData.files.length === 0) {
2814
+ spinner.fail(`Failed to download find-docs skill: ${downloadData.error || "no files"}`);
2815
+ return;
2816
+ }
2817
+ spinner.succeed("Downloaded find-docs skill");
2818
+ const targetDirs = getTargetDirs(targets);
2819
+ const installSpinner = ora4("Installing find-docs skill...").start();
2820
+ for (const dir of targetDirs) {
2821
+ installSpinner.text = `Installing to ${dir}...`;
2822
+ await installSkillFiles("find-docs", downloadData.files, dir);
2823
+ }
2824
+ installSpinner.stop();
2825
+ log.blank();
2826
+ log.plain(`${pc8.green("\u2714")} Context7 CLI setup complete`);
2827
+ log.blank();
2828
+ for (const dir of targetDirs) {
2829
+ log.itemAdd(
2830
+ `find-docs ${pc8.dim("Guides your agent to fetch up-to-date library docs on demand using ctx7 CLI commands")}`
2831
+ );
2832
+ log.plain(` ${pc8.dim(dir)}`);
2833
+ }
2834
+ log.blank();
2835
+ log.plain(` ${pc8.bold("Next steps")}`);
2836
+ log.plain(` Ask your agent: ${pc8.cyan(`"Use ctx7 CLI to look up React hooks"`)}`);
2837
+ log.blank();
2838
+ trackEvent("setup", { mode: "cli" });
2839
+ trackEvent("install", { skills: ["/upstash/context7/find-docs"], ides: targets.ides });
2840
+ }
2841
+ async function setupCommand(options) {
2842
+ trackEvent("command", { name: "setup" });
2843
+ try {
2844
+ const mode = await resolveMode(options);
2845
+ if (mode === "mcp") {
2846
+ const scope = options.project ? "project" : "global";
2847
+ const agents2 = await resolveAgents(options, scope, mode);
2848
+ if (agents2.length === 0) return;
2849
+ await setupMcp(agents2, options, scope);
2850
+ } else {
2851
+ await setupCli(options);
2852
+ }
2853
+ } catch (err) {
2854
+ if (err instanceof Error && err.name === "ExitPromptError") process.exit(0);
2855
+ throw err;
2856
+ }
2759
2857
  }
2760
2858
 
2761
2859
  // src/commands/docs.ts
@@ -2846,9 +2944,9 @@ async function resolveCommand(library, query, options) {
2846
2944
  }
2847
2945
  async function queryCommand(libraryId, query, options) {
2848
2946
  trackEvent("command", { name: "docs" });
2849
- if (!libraryId.startsWith("/")) {
2850
- log.error(`Invalid library ID: ${libraryId}`);
2851
- log.info(`Library IDs start with "/" (e.g., /facebook/react)`);
2947
+ if (!libraryId.startsWith("/") || !/^\/[^/]+\/[^/]/.test(libraryId)) {
2948
+ log.error(`Invalid library ID: "${libraryId}"`);
2949
+ log.info(`Expected format: /owner/repo or /owner/repo/version (e.g., /facebook/react)`);
2852
2950
  log.info(`Run "ctx7 library <name>" to find the correct ID`);
2853
2951
  process.exitCode = 1;
2854
2952
  return;
@@ -2927,15 +3025,6 @@ function registerDocsCommands(program2) {
2927
3025
  });
2928
3026
  }
2929
3027
 
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
3028
  // src/index.ts
2940
3029
  var brand = {
2941
3030
  primary: pc10.green,