ctx7 0.3.9 → 0.3.11

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/README.md CHANGED
@@ -177,6 +177,12 @@ ctx7 skills install /anthropics/skills pdf --universal
177
177
 
178
178
  # Install globally (home directory instead of current project)
179
179
  ctx7 skills install /anthropics/skills pdf --global
180
+
181
+ # Install non-interactively
182
+ ctx7 skills install /anthropics/skills pdf --global --universal --yes
183
+
184
+ # Install to all supported agent locations
185
+ ctx7 skills install /anthropics/skills pdf --all-agents
180
186
  ```
181
187
 
182
188
  ### Search for skills
package/dist/index.js CHANGED
@@ -29,6 +29,7 @@ function parseSkillInput(input2) {
29
29
  }
30
30
 
31
31
  // src/utils/github.ts
32
+ import { execSync } from "child_process";
32
33
  var GITHUB_API = "https://api.github.com";
33
34
  var GITHUB_RAW = "https://raw.githubusercontent.com";
34
35
  function parseGitHubUrl(url) {
@@ -69,6 +70,115 @@ function parseGitHubUrl(url) {
69
70
  return null;
70
71
  }
71
72
  }
73
+ function getGitHubToken() {
74
+ const envToken = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
75
+ if (envToken) return envToken;
76
+ try {
77
+ return execSync("gh auth token", { stdio: ["pipe", "pipe", "ignore"] }).toString().trim();
78
+ } catch {
79
+ return void 0;
80
+ }
81
+ }
82
+ function parseSkillFrontmatter(content) {
83
+ const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
84
+ if (!frontmatterMatch) return null;
85
+ const frontmatter = frontmatterMatch[1];
86
+ const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
87
+ if (!nameMatch) return null;
88
+ const name = nameMatch[1].trim().replace(/^["']|["']$/g, "").toLowerCase();
89
+ let description = "";
90
+ const multiLineMatch = frontmatter.match(/^description:\s*([|>])-?\s*$/m);
91
+ if (multiLineMatch) {
92
+ const descLineIndex = frontmatter.indexOf("description:");
93
+ const lines = frontmatter.slice(descLineIndex).split("\n").slice(1);
94
+ const indentedLines = [];
95
+ for (const line of lines) {
96
+ if (line.trim() === "") {
97
+ indentedLines.push("");
98
+ continue;
99
+ }
100
+ if (/^\s+/.test(line)) {
101
+ indentedLines.push(line);
102
+ } else {
103
+ break;
104
+ }
105
+ }
106
+ const firstNonEmpty = indentedLines.find((l) => l.trim().length > 0);
107
+ const indent = firstNonEmpty?.match(/^(\s+)/)?.[1].length ?? 0;
108
+ description = indentedLines.map((line) => line.slice(indent)).join(" ").replace(/\s+/g, " ").trim();
109
+ } else {
110
+ const singleMatch = frontmatter.match(/^description:\s*(.+)$/m);
111
+ if (singleMatch) {
112
+ const value = singleMatch[1].trim();
113
+ if (!["|", ">", "|-", ">-"].includes(value)) {
114
+ description = value.replace(/^["']|["']$/g, "");
115
+ }
116
+ }
117
+ }
118
+ if (!description) return null;
119
+ return { name, description };
120
+ }
121
+ function getGitHubHeaders() {
122
+ const ghToken = getGitHubToken();
123
+ return {
124
+ Accept: "application/vnd.github.v3+json",
125
+ "User-Agent": "context7-cli",
126
+ ...ghToken && { Authorization: `token ${ghToken}` }
127
+ };
128
+ }
129
+ async function fetchRepoTree(owner, repo, branch, headers) {
130
+ const treeUrl = `${GITHUB_API}/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`;
131
+ const response = await fetch(treeUrl, { headers });
132
+ if (!response.ok) return null;
133
+ return await response.json();
134
+ }
135
+ async function fetchDefaultBranch(owner, repo, headers) {
136
+ const response = await fetch(`${GITHUB_API}/repos/${owner}/${repo}`, { headers });
137
+ if (!response.ok) return { status: response.status };
138
+ const data = await response.json();
139
+ return { branch: data.default_branch };
140
+ }
141
+ async function listSkillsFromGitHub(project) {
142
+ try {
143
+ const parts = project.split("/").filter(Boolean);
144
+ if (parts.length < 2) return { status: "error", error: "Invalid project format" };
145
+ const [owner, repo] = parts;
146
+ const headers = getGitHubHeaders();
147
+ const branchResult = await fetchDefaultBranch(owner, repo, headers);
148
+ if ("status" in branchResult) return { status: "repo_not_found" };
149
+ const treeData = await fetchRepoTree(owner, repo, branchResult.branch, headers);
150
+ if (!treeData) return { status: "error", error: "Could not fetch repository tree" };
151
+ const skillMdFiles = treeData.tree.filter(
152
+ (item) => item.type === "blob" && item.path.toLowerCase().endsWith("skill.md")
153
+ );
154
+ const skills = [];
155
+ for (const item of skillMdFiles) {
156
+ const rawUrl = `${GITHUB_RAW}/${owner}/${repo}/${branchResult.branch}/${item.path}`;
157
+ const response = await fetch(rawUrl, { headers });
158
+ if (!response.ok) continue;
159
+ const content = await response.text();
160
+ const meta = parseSkillFrontmatter(content);
161
+ if (!meta) continue;
162
+ const skillDir = item.path.split("/").slice(0, -1).join("/");
163
+ skills.push({
164
+ name: meta.name,
165
+ description: meta.description,
166
+ url: `https://github.com/${owner}/${repo}/tree/${branchResult.branch}/${skillDir}`,
167
+ project
168
+ });
169
+ }
170
+ return { status: "ok", skills };
171
+ } catch (err) {
172
+ const message = err instanceof Error ? err.message : String(err);
173
+ return { status: "error", error: message };
174
+ }
175
+ }
176
+ async function getSkillFromGitHub(project, skillName) {
177
+ const result = await listSkillsFromGitHub(project);
178
+ if (result.status !== "ok") return result;
179
+ const skill = result.skills.find((s) => s.name === skillName.toLowerCase());
180
+ return { ...result, skill };
181
+ }
72
182
  async function downloadSkillFromGitHub(skill) {
73
183
  try {
74
184
  const parsed = parseGitHubUrl(skill.url);
@@ -76,17 +186,11 @@ async function downloadSkillFromGitHub(skill) {
76
186
  return { files: [], error: `Invalid GitHub URL: ${skill.url}` };
77
187
  }
78
188
  const { owner, repo, branch, path: skillPath } = parsed;
79
- const treeUrl = `${GITHUB_API}/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`;
80
- const treeResponse = await fetch(treeUrl, {
81
- headers: {
82
- Accept: "application/vnd.github.v3+json",
83
- "User-Agent": "context7-cli"
84
- }
85
- });
86
- if (!treeResponse.ok) {
87
- return { files: [], error: `GitHub API error: ${treeResponse.status}` };
189
+ const ghHeaders = getGitHubHeaders();
190
+ const treeData = await fetchRepoTree(owner, repo, branch, ghHeaders);
191
+ if (!treeData) {
192
+ return { files: [], error: `GitHub API error` };
88
193
  }
89
- const treeData = await treeResponse.json();
90
194
  const skillFiles = treeData.tree.filter(
91
195
  (item) => item.type === "blob" && item.path.startsWith(skillPath + "/")
92
196
  );
@@ -96,7 +200,7 @@ async function downloadSkillFromGitHub(skill) {
96
200
  const files = [];
97
201
  for (const item of skillFiles) {
98
202
  const rawUrl = `${GITHUB_RAW}/${owner}/${repo}/${branch}/${item.path}`;
99
- const fileResponse = await fetch(rawUrl);
203
+ const fileResponse = await fetch(rawUrl, { headers: ghHeaders });
100
204
  if (!fileResponse.ok) {
101
205
  console.warn(`Failed to fetch ${item.path}: ${fileResponse.status}`);
102
206
  continue;
@@ -167,11 +271,19 @@ async function suggestSkills(dependencies, accessToken) {
167
271
  async function downloadSkill(project, skillName) {
168
272
  const skillData = await getSkill(project, skillName);
169
273
  if (skillData.error) {
170
- return {
171
- skill: { name: skillName, description: "", url: "", project },
172
- files: [],
173
- error: skillData.message || skillData.error
174
- };
274
+ const ghResult = await getSkillFromGitHub(project, skillName);
275
+ if (ghResult.status !== "ok" || !ghResult.skill) {
276
+ return {
277
+ skill: { name: skillName, description: "", url: "", project },
278
+ files: [],
279
+ error: skillData.message || skillData.error
280
+ };
281
+ }
282
+ const { files: files2, error: error2 } = await downloadSkillFromGitHub(ghResult.skill);
283
+ if (error2) {
284
+ return { skill: ghResult.skill, files: [], error: error2 };
285
+ }
286
+ return { skill: ghResult.skill, files: files2 };
175
287
  }
176
288
  const skill = {
177
289
  name: skillData.name,
@@ -511,6 +623,9 @@ var DEFAULT_CONFIG = {
511
623
 
512
624
  // src/utils/ide.ts
513
625
  function getSelectedIdes(options) {
626
+ if (options.allAgents) {
627
+ return ["universal", ...VENDOR_SPECIFIC_AGENTS];
628
+ }
514
629
  const ides = [];
515
630
  if (options.claude) ides.push("claude");
516
631
  if (options.cursor) ides.push("cursor");
@@ -519,7 +634,7 @@ function getSelectedIdes(options) {
519
634
  return ides;
520
635
  }
521
636
  function hasExplicitIdeOption(options) {
522
- return !!(options.claude || options.cursor || options.universal || options.antigravity);
637
+ return !!(options.allAgents || options.claude || options.cursor || options.universal || options.antigravity);
523
638
  }
524
639
  async function detectVendorSpecificAgents(scope) {
525
640
  const baseDir = scope === "global" ? homedir() : process.cwd();
@@ -1775,7 +1890,7 @@ function logInstallSummary(targets, targetDirs, skillNames) {
1775
1890
  function registerSkillCommands(program2) {
1776
1891
  const skill = program2.command("skills").alias("skill").description("Manage AI coding skills");
1777
1892
  registerGenerateCommand(skill);
1778
- skill.command("install").alias("i").alias("add").argument("<repository>", "GitHub repository (/owner/repo)").argument("[skill]", "Specific skill name to install").option("--all", "Install all skills without prompting").option("--global", "Install globally instead of current directory").option("--claude", "Claude Code (.claude/skills/)").option("--cursor", "Cursor (.cursor/skills/)").option("--universal", "Universal (.agents/skills/)").option("--antigravity", "Antigravity (.agent/skills/)").description("Install skills from a repository").action(async (project, skillName, options) => {
1893
+ skill.command("install").alias("i").alias("add").argument("<repository>", "GitHub repository (/owner/repo)").argument("[skill]", "Specific skill name to install").option("--all", "Install all skills without prompting").option("--all-agents", "Install to all supported agent locations").option("-y, --yes", "Skip confirmation prompts").option("--global", "Install globally instead of current directory").option("--claude", "Claude Code (.claude/skills/)").option("--cursor", "Cursor (.cursor/skills/)").option("--universal", "Universal (.agents/skills/)").option("--antigravity", "Antigravity (.agent/skills/)").description("Install skills from a repository").action(async (project, skillName, options) => {
1779
1894
  await installCommand(project, skillName, options);
1780
1895
  });
1781
1896
  skill.command("search").alias("s").argument("<keywords...>", "Search keywords").description("Search for skills across all indexed repositories").action(async (keywords) => {
@@ -1795,7 +1910,7 @@ function registerSkillCommands(program2) {
1795
1910
  });
1796
1911
  }
1797
1912
  function registerSkillAliases(program2) {
1798
- program2.command("si", { hidden: true }).argument("<repository>", "GitHub repository (/owner/repo)").argument("[skill]", "Specific skill name to install").option("--all", "Install all skills without prompting").option("--global", "Install globally instead of current directory").option("--claude", "Claude Code (.claude/skills/)").option("--cursor", "Cursor (.cursor/skills/)").option("--universal", "Universal (.agents/skills/)").option("--antigravity", "Antigravity (.agent/skills/)").description("Install skills (alias for: skills install)").action(async (project, skillName, options) => {
1913
+ program2.command("si", { hidden: true }).argument("<repository>", "GitHub repository (/owner/repo)").argument("[skill]", "Specific skill name to install").option("--all", "Install all skills without prompting").option("--all-agents", "Install to all supported agent locations").option("-y, --yes", "Skip confirmation prompts").option("--global", "Install globally instead of current directory").option("--claude", "Claude Code (.claude/skills/)").option("--cursor", "Cursor (.cursor/skills/)").option("--universal", "Universal (.agents/skills/)").option("--antigravity", "Antigravity (.agent/skills/)").description("Install skills (alias for: skills install)").action(async (project, skillName, options) => {
1799
1914
  await installCommand(project, skillName, options);
1800
1915
  });
1801
1916
  program2.command("ss", { hidden: true }).argument("<keywords...>", "Search keywords").description("Search for skills (alias for: skills search)").action(async (keywords) => {
@@ -1826,23 +1941,45 @@ async function installCommand(input2, skillName, options) {
1826
1941
  if (skillData.error === "prompt_injection_detected") {
1827
1942
  spinner.fail(pc7.red(`Prompt injection detected in skill: ${skillName}`));
1828
1943
  log.warn("This skill contains potentially malicious content and cannot be installed.");
1829
- } else {
1944
+ return;
1945
+ }
1946
+ spinner.text = `Fetching skill from GitHub: ${skillName}...`;
1947
+ const ghResult = await getSkillFromGitHub(repo, skillName);
1948
+ if (ghResult.status === "repo_not_found") {
1949
+ spinner.fail(pc7.red(`Repository not found: ${repo}`));
1950
+ return;
1951
+ }
1952
+ if (ghResult.status !== "ok" || !ghResult.skill) {
1830
1953
  spinner.fail(pc7.red(`Skill not found: ${skillName}`));
1954
+ return;
1831
1955
  }
1832
- return;
1956
+ spinner.succeed(`Found skill: ${skillName}`);
1957
+ selectedSkills = [ghResult.skill];
1958
+ } else {
1959
+ spinner.succeed(`Found skill: ${skillName}`);
1960
+ selectedSkills = [
1961
+ {
1962
+ name: skillData.name,
1963
+ description: skillData.description,
1964
+ url: skillData.url,
1965
+ project: repo
1966
+ }
1967
+ ];
1833
1968
  }
1834
- spinner.succeed(`Found skill: ${skillName}`);
1835
- selectedSkills = [
1836
- {
1837
- name: skillData.name,
1838
- description: skillData.description,
1839
- url: skillData.url,
1840
- project: repo
1841
- }
1842
- ];
1843
1969
  } else {
1844
- const data = await listProjectSkills(repo);
1845
- if (data.error) {
1970
+ let data = await listProjectSkills(repo);
1971
+ if ((data.error || !data.skills || data.skills.length === 0) && !data.blockedSkillsCount) {
1972
+ spinner.text = `Fetching skills from GitHub...`;
1973
+ const ghResult = await listSkillsFromGitHub(repo);
1974
+ if (ghResult.status === "repo_not_found") {
1975
+ spinner.fail(pc7.red(`Repository not found: ${repo}`));
1976
+ return;
1977
+ }
1978
+ if (ghResult.status === "ok" && ghResult.skills.length > 0) {
1979
+ data = { project: repo, skills: ghResult.skills };
1980
+ }
1981
+ }
1982
+ if (data.error && (!data.skills || data.skills.length === 0)) {
1846
1983
  spinner.fail(pc7.red(`Error: ${data.message || data.error}`));
1847
1984
  return;
1848
1985
  }
@@ -2438,7 +2575,8 @@ var SETUP_AGENT_NAMES = {
2438
2575
  claude: "Claude Code",
2439
2576
  cursor: "Cursor",
2440
2577
  opencode: "OpenCode",
2441
- codex: "Codex"
2578
+ codex: "Codex",
2579
+ gemini: "Gemini CLI"
2442
2580
  };
2443
2581
  var AUTH_MODE_LABELS = {
2444
2582
  oauth: "OAuth",
@@ -2557,6 +2695,29 @@ var agents = {
2557
2695
  projectPaths: [".codex"],
2558
2696
  globalPaths: [join8(homedir5(), ".codex")]
2559
2697
  }
2698
+ },
2699
+ gemini: {
2700
+ name: "gemini",
2701
+ displayName: "Gemini CLI",
2702
+ mcp: {
2703
+ projectPaths: [join8(".gemini", "settings.json")],
2704
+ globalPaths: [join8(homedir5(), ".gemini", "settings.json")],
2705
+ configKey: "mcpServers",
2706
+ buildEntry: (auth) => withHeaders({ httpUrl: mcpUrl(auth) }, auth)
2707
+ },
2708
+ rule: {
2709
+ kind: "append",
2710
+ file: (scope) => scope === "global" ? join8(homedir5(), ".gemini", "GEMINI.md") : "GEMINI.md",
2711
+ sectionMarker: "<!-- context7 -->"
2712
+ },
2713
+ skill: {
2714
+ name: "context7-mcp",
2715
+ dir: (scope) => scope === "global" ? join8(homedir5(), ".gemini", "skills") : join8(".gemini", "skills")
2716
+ },
2717
+ detect: {
2718
+ projectPaths: [".gemini"],
2719
+ globalPaths: [join8(homedir5(), ".gemini")]
2720
+ }
2560
2721
  }
2561
2722
  };
2562
2723
  function getAgent(name) {
@@ -2597,7 +2758,7 @@ Do not use for: refactoring, writing scripts from scratch, debugging business lo
2597
2758
 
2598
2759
  ## Steps
2599
2760
 
2600
- 1. \`resolve-library-id\` with the library name and the user's question
2761
+ 1. \`resolve-library-id\` with the library name and the user's question. Use the official library name with proper punctuation (e.g., "Next.js" not "nextjs", "Customer.io" not "customerio", "Three.js" not "threejs")
2601
2762
  2. Pick the best match by: exact name match, description relevance, code snippet count, source reputation (High/Medium preferred), and benchmark score (higher is better). Use version-specific IDs when the user mentions a version
2602
2763
  3. \`query-docs\` with the selected library ID and the user's full question (not single words)
2603
2764
  4. Answer using the fetched docs
@@ -2775,10 +2936,11 @@ function getSelectedAgents(options) {
2775
2936
  if (options.cursor) agents2.push("cursor");
2776
2937
  if (options.opencode) agents2.push("opencode");
2777
2938
  if (options.codex) agents2.push("codex");
2939
+ if (options.gemini) agents2.push("gemini");
2778
2940
  return agents2;
2779
2941
  }
2780
2942
  function registerSetupCommand(program2) {
2781
- program2.command("setup").description("Set up Context7 for your AI coding agent").option("--claude", "Set up for Claude Code").option("--cursor", "Set up for Cursor").option("--universal", "Set up for Universal (.agents/skills)").option("--antigravity", "Set up for Antigravity (.agent/skills)").option("--opencode", "Set up for OpenCode").option("--codex", "Set up for Codex").option("--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) => {
2943
+ program2.command("setup").description("Set up Context7 for your AI coding agent").option("--claude", "Set up for Claude Code").option("--cursor", "Set up for Cursor").option("--universal", "Set up for Universal (.agents/skills)").option("--antigravity", "Set up for Antigravity (.agent/skills)").option("--opencode", "Set up for OpenCode").option("--codex", "Set up for Codex").option("--gemini", "Set up for Gemini CLI").option("--mcp", "Set up MCP server mode").option("--cli", "Set up CLI + Skills mode (no MCP server)").option("-p, --project", "Configure for current project instead of globally").option("-y, --yes", "Skip confirmation prompts").option("--api-key <key>", "Use API key authentication").option("--oauth", "Use OAuth endpoint (IDE handles auth flow)").action(async (options) => {
2782
2944
  await setupCommand(options);
2783
2945
  });
2784
2946
  }
@@ -3007,6 +3169,11 @@ async function setupMcp(agents2, options, scope) {
3007
3169
  const skillIcon = r.skillStatus === "installed" ? pc8.green("+") : pc8.dim("~");
3008
3170
  log.plain(` ${skillIcon} Skill ${r.skillStatus}`);
3009
3171
  log.plain(` ${pc8.dim(r.skillPath)}`);
3172
+ if (r.skillStatus.includes("EACCES")) {
3173
+ log.plain(
3174
+ ` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname5(dirname5(r.skillPath))}`)}`
3175
+ );
3176
+ }
3010
3177
  }
3011
3178
  log.blank();
3012
3179
  trackEvent("setup", { agents: agents2, scope, authMode: auth.mode });
@@ -3062,6 +3229,11 @@ async function setupCli(options) {
3062
3229
  const skillIcon = r.skillStatus === "installed" ? pc8.green("+") : pc8.dim("~");
3063
3230
  log.plain(` ${skillIcon} Skill ${r.skillStatus}`);
3064
3231
  log.plain(` ${pc8.dim(r.skillPath)}`);
3232
+ if (r.skillStatus.includes("EACCES")) {
3233
+ log.plain(
3234
+ ` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname5(dirname5(r.skillPath))}`)}`
3235
+ );
3236
+ }
3065
3237
  const ruleIcon = r.ruleStatus === "installed" || r.ruleStatus === "updated" ? pc8.green("+") : pc8.dim("~");
3066
3238
  log.plain(` ${ruleIcon} Rule ${r.ruleStatus}`);
3067
3239
  log.plain(` ${pc8.dim(r.rulePath)}`);