ctx7 0.3.9 → 0.3.10

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
@@ -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,
@@ -1826,23 +1938,45 @@ async function installCommand(input2, skillName, options) {
1826
1938
  if (skillData.error === "prompt_injection_detected") {
1827
1939
  spinner.fail(pc7.red(`Prompt injection detected in skill: ${skillName}`));
1828
1940
  log.warn("This skill contains potentially malicious content and cannot be installed.");
1829
- } else {
1941
+ return;
1942
+ }
1943
+ spinner.text = `Fetching skill from GitHub: ${skillName}...`;
1944
+ const ghResult = await getSkillFromGitHub(repo, skillName);
1945
+ if (ghResult.status === "repo_not_found") {
1946
+ spinner.fail(pc7.red(`Repository not found: ${repo}`));
1947
+ return;
1948
+ }
1949
+ if (ghResult.status !== "ok" || !ghResult.skill) {
1830
1950
  spinner.fail(pc7.red(`Skill not found: ${skillName}`));
1951
+ return;
1831
1952
  }
1832
- return;
1953
+ spinner.succeed(`Found skill: ${skillName}`);
1954
+ selectedSkills = [ghResult.skill];
1955
+ } else {
1956
+ spinner.succeed(`Found skill: ${skillName}`);
1957
+ selectedSkills = [
1958
+ {
1959
+ name: skillData.name,
1960
+ description: skillData.description,
1961
+ url: skillData.url,
1962
+ project: repo
1963
+ }
1964
+ ];
1833
1965
  }
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
1966
  } else {
1844
- const data = await listProjectSkills(repo);
1845
- if (data.error) {
1967
+ let data = await listProjectSkills(repo);
1968
+ if ((data.error || !data.skills || data.skills.length === 0) && !data.blockedSkillsCount) {
1969
+ spinner.text = `Fetching skills from GitHub...`;
1970
+ const ghResult = await listSkillsFromGitHub(repo);
1971
+ if (ghResult.status === "repo_not_found") {
1972
+ spinner.fail(pc7.red(`Repository not found: ${repo}`));
1973
+ return;
1974
+ }
1975
+ if (ghResult.status === "ok" && ghResult.skills.length > 0) {
1976
+ data = { project: repo, skills: ghResult.skills };
1977
+ }
1978
+ }
1979
+ if (data.error && (!data.skills || data.skills.length === 0)) {
1846
1980
  spinner.fail(pc7.red(`Error: ${data.message || data.error}`));
1847
1981
  return;
1848
1982
  }
@@ -2438,7 +2572,8 @@ var SETUP_AGENT_NAMES = {
2438
2572
  claude: "Claude Code",
2439
2573
  cursor: "Cursor",
2440
2574
  opencode: "OpenCode",
2441
- codex: "Codex"
2575
+ codex: "Codex",
2576
+ gemini: "Gemini CLI"
2442
2577
  };
2443
2578
  var AUTH_MODE_LABELS = {
2444
2579
  oauth: "OAuth",
@@ -2557,6 +2692,29 @@ var agents = {
2557
2692
  projectPaths: [".codex"],
2558
2693
  globalPaths: [join8(homedir5(), ".codex")]
2559
2694
  }
2695
+ },
2696
+ gemini: {
2697
+ name: "gemini",
2698
+ displayName: "Gemini CLI",
2699
+ mcp: {
2700
+ projectPaths: [join8(".gemini", "settings.json")],
2701
+ globalPaths: [join8(homedir5(), ".gemini", "settings.json")],
2702
+ configKey: "mcpServers",
2703
+ buildEntry: (auth) => withHeaders({ httpUrl: mcpUrl(auth) }, auth)
2704
+ },
2705
+ rule: {
2706
+ kind: "append",
2707
+ file: (scope) => scope === "global" ? join8(homedir5(), ".gemini", "GEMINI.md") : "GEMINI.md",
2708
+ sectionMarker: "<!-- context7 -->"
2709
+ },
2710
+ skill: {
2711
+ name: "context7-mcp",
2712
+ dir: (scope) => scope === "global" ? join8(homedir5(), ".gemini", "skills") : join8(".gemini", "skills")
2713
+ },
2714
+ detect: {
2715
+ projectPaths: [".gemini"],
2716
+ globalPaths: [join8(homedir5(), ".gemini")]
2717
+ }
2560
2718
  }
2561
2719
  };
2562
2720
  function getAgent(name) {
@@ -2597,7 +2755,7 @@ Do not use for: refactoring, writing scripts from scratch, debugging business lo
2597
2755
 
2598
2756
  ## Steps
2599
2757
 
2600
- 1. \`resolve-library-id\` with the library name and the user's question
2758
+ 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
2759
  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
2760
  3. \`query-docs\` with the selected library ID and the user's full question (not single words)
2603
2761
  4. Answer using the fetched docs
@@ -2775,10 +2933,11 @@ function getSelectedAgents(options) {
2775
2933
  if (options.cursor) agents2.push("cursor");
2776
2934
  if (options.opencode) agents2.push("opencode");
2777
2935
  if (options.codex) agents2.push("codex");
2936
+ if (options.gemini) agents2.push("gemini");
2778
2937
  return agents2;
2779
2938
  }
2780
2939
  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) => {
2940
+ 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
2941
  await setupCommand(options);
2783
2942
  });
2784
2943
  }
@@ -3007,6 +3166,11 @@ async function setupMcp(agents2, options, scope) {
3007
3166
  const skillIcon = r.skillStatus === "installed" ? pc8.green("+") : pc8.dim("~");
3008
3167
  log.plain(` ${skillIcon} Skill ${r.skillStatus}`);
3009
3168
  log.plain(` ${pc8.dim(r.skillPath)}`);
3169
+ if (r.skillStatus.includes("EACCES")) {
3170
+ log.plain(
3171
+ ` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname5(dirname5(r.skillPath))}`)}`
3172
+ );
3173
+ }
3010
3174
  }
3011
3175
  log.blank();
3012
3176
  trackEvent("setup", { agents: agents2, scope, authMode: auth.mode });
@@ -3062,6 +3226,11 @@ async function setupCli(options) {
3062
3226
  const skillIcon = r.skillStatus === "installed" ? pc8.green("+") : pc8.dim("~");
3063
3227
  log.plain(` ${skillIcon} Skill ${r.skillStatus}`);
3064
3228
  log.plain(` ${pc8.dim(r.skillPath)}`);
3229
+ if (r.skillStatus.includes("EACCES")) {
3230
+ log.plain(
3231
+ ` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname5(dirname5(r.skillPath))}`)}`
3232
+ );
3233
+ }
3065
3234
  const ruleIcon = r.ruleStatus === "installed" || r.ruleStatus === "updated" ? pc8.green("+") : pc8.dim("~");
3066
3235
  log.plain(` ${ruleIcon} Rule ${r.ruleStatus}`);
3067
3236
  log.plain(` ${pc8.dim(r.rulePath)}`);