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 +201 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)}`);
|