ctx7 0.3.5 → 0.3.8
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/chunk-WKOIWR6Y.js +120 -0
- package/dist/chunk-WKOIWR6Y.js.map +1 -0
- package/dist/index.js +305 -155
- package/dist/index.js.map +1 -1
- package/dist/mcp-writer-IYBCUACD.js +20 -0
- package/dist/mcp-writer-IYBCUACD.js.map +1 -0
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
appendTomlServer,
|
|
4
|
+
mergeServerEntry,
|
|
5
|
+
readJsonConfig,
|
|
6
|
+
resolveMcpPath,
|
|
7
|
+
writeJsonConfig
|
|
8
|
+
} from "./chunk-WKOIWR6Y.js";
|
|
2
9
|
|
|
3
10
|
// src/index.ts
|
|
4
11
|
import { Command } from "commander";
|
|
@@ -103,6 +110,10 @@ async function downloadSkillFromGitHub(skill) {
|
|
|
103
110
|
}
|
|
104
111
|
const content = await fileResponse.text();
|
|
105
112
|
const relativePath = item.path.slice(skillPath.length + 1);
|
|
113
|
+
if (relativePath.includes("..")) {
|
|
114
|
+
console.warn(`Skipping file with unsafe path: ${item.path}`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
106
117
|
files.push({
|
|
107
118
|
path: relativePath,
|
|
108
119
|
content
|
|
@@ -123,6 +134,7 @@ var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
123
134
|
var pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
|
124
135
|
var VERSION = pkg.version;
|
|
125
136
|
var NAME = pkg.name;
|
|
137
|
+
var CLI_CLIENT_ID = "2veBSofhicRBguUT";
|
|
126
138
|
|
|
127
139
|
// src/utils/api.ts
|
|
128
140
|
var baseUrl = "https://context7.com";
|
|
@@ -432,13 +444,18 @@ async function checkboxWithHover(config, options) {
|
|
|
432
444
|
);
|
|
433
445
|
const values = choices.map((c) => c.value);
|
|
434
446
|
const totalItems = values.length;
|
|
435
|
-
let cursorPosition =
|
|
447
|
+
let cursorPosition = choices.findIndex((c) => !c.disabled);
|
|
448
|
+
if (cursorPosition < 0) cursorPosition = 0;
|
|
436
449
|
const getName = options?.getName ?? ((v) => v.name);
|
|
437
450
|
const keypressHandler = (_str, key) => {
|
|
438
|
-
if (key.name === "up"
|
|
439
|
-
cursorPosition
|
|
440
|
-
|
|
441
|
-
cursorPosition
|
|
451
|
+
if (key.name === "up") {
|
|
452
|
+
let next = cursorPosition - 1;
|
|
453
|
+
while (next >= 0 && choices[next].disabled) next--;
|
|
454
|
+
if (next >= 0) cursorPosition = next;
|
|
455
|
+
} else if (key.name === "down") {
|
|
456
|
+
let next = cursorPosition + 1;
|
|
457
|
+
while (next < totalItems && choices[next].disabled) next++;
|
|
458
|
+
if (next < totalItems) cursorPosition = next;
|
|
442
459
|
}
|
|
443
460
|
};
|
|
444
461
|
readline.emitKeypressEvents(process.stdin);
|
|
@@ -482,7 +499,7 @@ var IDE_GLOBAL_PATHS = {
|
|
|
482
499
|
claude: ".claude/skills",
|
|
483
500
|
cursor: ".cursor/skills",
|
|
484
501
|
antigravity: ".agent/skills",
|
|
485
|
-
universal: ".
|
|
502
|
+
universal: ".agents/skills"
|
|
486
503
|
};
|
|
487
504
|
var IDE_NAMES = {
|
|
488
505
|
claude: "Claude Code",
|
|
@@ -491,7 +508,7 @@ var IDE_NAMES = {
|
|
|
491
508
|
universal: "Universal"
|
|
492
509
|
};
|
|
493
510
|
var UNIVERSAL_SKILLS_PATH = ".agents/skills";
|
|
494
|
-
var UNIVERSAL_SKILLS_GLOBAL_PATH = ".
|
|
511
|
+
var UNIVERSAL_SKILLS_GLOBAL_PATH = ".agents/skills";
|
|
495
512
|
var UNIVERSAL_AGENTS_LABEL = "Amp, Codex, Gemini CLI, GitHub Copilot, OpenCode + more";
|
|
496
513
|
var VENDOR_SPECIFIC_AGENTS = ["claude", "cursor", "antigravity"];
|
|
497
514
|
var DEFAULT_CONFIG = {
|
|
@@ -714,12 +731,15 @@ function getTargetDirFromSelection(ide, scope) {
|
|
|
714
731
|
|
|
715
732
|
// src/utils/installer.ts
|
|
716
733
|
import { mkdir, writeFile, rm, symlink, lstat } from "fs/promises";
|
|
717
|
-
import { join as join3 } from "path";
|
|
734
|
+
import { join as join3, resolve, dirname as dirname3 } from "path";
|
|
718
735
|
async function installSkillFiles(skillName, files, targetDir) {
|
|
719
|
-
const skillDir =
|
|
736
|
+
const skillDir = resolve(targetDir, skillName);
|
|
720
737
|
for (const file of files) {
|
|
721
|
-
const filePath =
|
|
722
|
-
|
|
738
|
+
const filePath = resolve(skillDir, file.path);
|
|
739
|
+
if (!filePath.startsWith(skillDir + "/") && filePath !== skillDir) {
|
|
740
|
+
throw new Error(`Skill file path "${file.path}" resolves outside the target directory`);
|
|
741
|
+
}
|
|
742
|
+
const fileDir = dirname3(filePath);
|
|
723
743
|
await mkdir(fileDir, { recursive: true });
|
|
724
744
|
await writeFile(filePath, file.content);
|
|
725
745
|
}
|
|
@@ -810,17 +830,50 @@ function isTokenExpired(tokens) {
|
|
|
810
830
|
}
|
|
811
831
|
return Date.now() > tokens.expires_at - 6e4;
|
|
812
832
|
}
|
|
833
|
+
async function refreshAccessToken(refreshToken) {
|
|
834
|
+
const response = await fetch(`${getBaseUrl()}/api/oauth/token`, {
|
|
835
|
+
method: "POST",
|
|
836
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
837
|
+
body: new URLSearchParams({
|
|
838
|
+
grant_type: "refresh_token",
|
|
839
|
+
client_id: CLI_CLIENT_ID,
|
|
840
|
+
refresh_token: refreshToken
|
|
841
|
+
}).toString()
|
|
842
|
+
});
|
|
843
|
+
if (!response.ok) {
|
|
844
|
+
const err = await response.json().catch(() => ({}));
|
|
845
|
+
throw new Error(err.error_description || err.error || "Failed to refresh token");
|
|
846
|
+
}
|
|
847
|
+
return await response.json();
|
|
848
|
+
}
|
|
849
|
+
async function getValidAccessToken() {
|
|
850
|
+
const tokens = loadTokens();
|
|
851
|
+
if (!tokens) return null;
|
|
852
|
+
if (!isTokenExpired(tokens)) {
|
|
853
|
+
return tokens.access_token;
|
|
854
|
+
}
|
|
855
|
+
if (!tokens.refresh_token) {
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
try {
|
|
859
|
+
const newTokens = await refreshAccessToken(tokens.refresh_token);
|
|
860
|
+
saveTokens(newTokens);
|
|
861
|
+
return newTokens.access_token;
|
|
862
|
+
} catch {
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
813
866
|
var CALLBACK_PORT = 52417;
|
|
814
867
|
function createCallbackServer(expectedState) {
|
|
815
868
|
let resolvePort;
|
|
816
869
|
let resolveResult;
|
|
817
870
|
let rejectResult;
|
|
818
871
|
let serverInstance = null;
|
|
819
|
-
const portPromise = new Promise((
|
|
820
|
-
resolvePort =
|
|
872
|
+
const portPromise = new Promise((resolve2) => {
|
|
873
|
+
resolvePort = resolve2;
|
|
821
874
|
});
|
|
822
|
-
const resultPromise = new Promise((
|
|
823
|
-
resolveResult =
|
|
875
|
+
const resultPromise = new Promise((resolve2, reject) => {
|
|
876
|
+
resolveResult = resolve2;
|
|
824
877
|
rejectResult = reject;
|
|
825
878
|
});
|
|
826
879
|
const server = http.createServer((req, res) => {
|
|
@@ -953,7 +1006,6 @@ function buildAuthorizationUrl(baseUrl3, clientId, redirectUri, codeChallenge, s
|
|
|
953
1006
|
import pc4 from "picocolors";
|
|
954
1007
|
import ora from "ora";
|
|
955
1008
|
import open from "open";
|
|
956
|
-
var CLI_CLIENT_ID = "2veBSofhicRBguUT";
|
|
957
1009
|
var baseUrl2 = "https://context7.com";
|
|
958
1010
|
function setAuthBaseUrl(url) {
|
|
959
1011
|
baseUrl2 = url;
|
|
@@ -1029,18 +1081,13 @@ async function performLogin(openBrowser = true) {
|
|
|
1029
1081
|
}
|
|
1030
1082
|
async function loginCommand(options) {
|
|
1031
1083
|
trackEvent("command", { name: "login" });
|
|
1032
|
-
const
|
|
1033
|
-
if (
|
|
1034
|
-
|
|
1035
|
-
if
|
|
1036
|
-
|
|
1037
|
-
console.log(
|
|
1038
|
-
pc4.dim("Run 'ctx7 logout' first if you want to log in with a different account.")
|
|
1039
|
-
);
|
|
1040
|
-
return;
|
|
1041
|
-
}
|
|
1042
|
-
clearTokens();
|
|
1084
|
+
const existingToken = await getValidAccessToken();
|
|
1085
|
+
if (existingToken) {
|
|
1086
|
+
console.log(pc4.yellow("You are already logged in."));
|
|
1087
|
+
console.log(pc4.dim("Run 'ctx7 logout' first if you want to log in with a different account."));
|
|
1088
|
+
return;
|
|
1043
1089
|
}
|
|
1090
|
+
clearTokens();
|
|
1044
1091
|
const token = await performLogin(options.browser);
|
|
1045
1092
|
if (!token) {
|
|
1046
1093
|
process.exit(1);
|
|
@@ -1058,29 +1105,30 @@ function logoutCommand() {
|
|
|
1058
1105
|
}
|
|
1059
1106
|
async function whoamiCommand() {
|
|
1060
1107
|
trackEvent("command", { name: "whoami" });
|
|
1061
|
-
const
|
|
1062
|
-
if (!
|
|
1108
|
+
const accessToken = await getValidAccessToken();
|
|
1109
|
+
if (!accessToken) {
|
|
1063
1110
|
console.log(pc4.yellow("Not logged in."));
|
|
1064
1111
|
console.log(pc4.dim("Run 'ctx7 login' to authenticate."));
|
|
1065
1112
|
return;
|
|
1066
1113
|
}
|
|
1067
1114
|
console.log(pc4.green("Logged in"));
|
|
1068
1115
|
try {
|
|
1069
|
-
const
|
|
1070
|
-
if (
|
|
1071
|
-
console.log(`${pc4.dim("Name:".padEnd(
|
|
1116
|
+
const whoami = await fetchWhoami(accessToken);
|
|
1117
|
+
if (whoami.name) {
|
|
1118
|
+
console.log(`${pc4.dim("Name:".padEnd(13))}${whoami.name}`);
|
|
1072
1119
|
}
|
|
1073
|
-
if (
|
|
1074
|
-
console.log(`${pc4.dim("Email:".padEnd(
|
|
1120
|
+
if (whoami.email) {
|
|
1121
|
+
console.log(`${pc4.dim("Email:".padEnd(13))}${whoami.email}`);
|
|
1075
1122
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
console.log(pc4.dim("(Session may be expired - run 'ctx7 login' to refresh)"));
|
|
1123
|
+
if (whoami.teamspace) {
|
|
1124
|
+
console.log(`${pc4.dim("Teamspace:".padEnd(13))}${whoami.teamspace.name}`);
|
|
1079
1125
|
}
|
|
1126
|
+
} catch {
|
|
1127
|
+
console.log(pc4.dim("(Session may be expired - run 'ctx7 login' to refresh)"));
|
|
1080
1128
|
}
|
|
1081
1129
|
}
|
|
1082
|
-
async function
|
|
1083
|
-
const response = await fetch(
|
|
1130
|
+
async function fetchWhoami(accessToken) {
|
|
1131
|
+
const response = await fetch(`${getBaseUrl()}/api/dashboard/whoami`, {
|
|
1084
1132
|
headers: {
|
|
1085
1133
|
Authorization: `Bearer ${accessToken}`
|
|
1086
1134
|
}
|
|
@@ -1288,7 +1336,7 @@ async function generateCommand(options) {
|
|
|
1288
1336
|
log.blank();
|
|
1289
1337
|
if (searchResult.searchFilterApplied) {
|
|
1290
1338
|
log.warn(
|
|
1291
|
-
"Your results only include libraries matching your
|
|
1339
|
+
"Your results only include libraries matching your teamspace's library filters. To adjust quality thresholds or blocked libraries, update your filters at https://context7.com/dashboard?tab=policies"
|
|
1292
1340
|
);
|
|
1293
1341
|
log.blank();
|
|
1294
1342
|
}
|
|
@@ -1513,12 +1561,11 @@ async function generateCommand(options) {
|
|
|
1513
1561
|
previewFileWritten = true;
|
|
1514
1562
|
}
|
|
1515
1563
|
const editor = process.env.EDITOR || "open";
|
|
1516
|
-
await new Promise((
|
|
1564
|
+
await new Promise((resolve2) => {
|
|
1517
1565
|
const child = spawn(editor, [previewFile], {
|
|
1518
|
-
stdio: "inherit"
|
|
1519
|
-
shell: true
|
|
1566
|
+
stdio: "inherit"
|
|
1520
1567
|
});
|
|
1521
|
-
child.on("close", () =>
|
|
1568
|
+
child.on("close", () => resolve2());
|
|
1522
1569
|
});
|
|
1523
1570
|
};
|
|
1524
1571
|
const syncFromPreviewFile = async () => {
|
|
@@ -1527,7 +1574,7 @@ async function generateCommand(options) {
|
|
|
1527
1574
|
}
|
|
1528
1575
|
};
|
|
1529
1576
|
showPreview();
|
|
1530
|
-
await new Promise((
|
|
1577
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
1531
1578
|
try {
|
|
1532
1579
|
let action;
|
|
1533
1580
|
while (true) {
|
|
@@ -2386,7 +2433,7 @@ ${headerLine}`,
|
|
|
2386
2433
|
import pc8 from "picocolors";
|
|
2387
2434
|
import ora4 from "ora";
|
|
2388
2435
|
import { select as select3 } from "@inquirer/prompts";
|
|
2389
|
-
import { mkdir as
|
|
2436
|
+
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
2390
2437
|
import { dirname as dirname4, join as join9 } from "path";
|
|
2391
2438
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2392
2439
|
|
|
@@ -2397,7 +2444,8 @@ import { homedir as homedir5 } from "os";
|
|
|
2397
2444
|
var SETUP_AGENT_NAMES = {
|
|
2398
2445
|
claude: "Claude Code",
|
|
2399
2446
|
cursor: "Cursor",
|
|
2400
|
-
opencode: "OpenCode"
|
|
2447
|
+
opencode: "OpenCode",
|
|
2448
|
+
codex: "Codex"
|
|
2401
2449
|
};
|
|
2402
2450
|
var AUTH_MODE_LABELS = {
|
|
2403
2451
|
oauth: "OAuth",
|
|
@@ -2418,12 +2466,13 @@ var agents = {
|
|
|
2418
2466
|
name: "claude",
|
|
2419
2467
|
displayName: "Claude Code",
|
|
2420
2468
|
mcp: {
|
|
2421
|
-
|
|
2422
|
-
|
|
2469
|
+
projectPaths: [".mcp.json"],
|
|
2470
|
+
globalPaths: [join8(homedir5(), ".claude.json")],
|
|
2423
2471
|
configKey: "mcpServers",
|
|
2424
2472
|
buildEntry: (auth) => withHeaders({ type: "http", url: mcpUrl(auth) }, auth)
|
|
2425
2473
|
},
|
|
2426
2474
|
rule: {
|
|
2475
|
+
kind: "file",
|
|
2427
2476
|
dir: (scope) => scope === "global" ? join8(homedir5(), ".claude", "rules") : join8(".claude", "rules"),
|
|
2428
2477
|
filename: "context7.md"
|
|
2429
2478
|
},
|
|
@@ -2440,12 +2489,13 @@ var agents = {
|
|
|
2440
2489
|
name: "cursor",
|
|
2441
2490
|
displayName: "Cursor",
|
|
2442
2491
|
mcp: {
|
|
2443
|
-
|
|
2444
|
-
|
|
2492
|
+
projectPaths: [join8(".cursor", "mcp.json")],
|
|
2493
|
+
globalPaths: [join8(homedir5(), ".cursor", "mcp.json")],
|
|
2445
2494
|
configKey: "mcpServers",
|
|
2446
2495
|
buildEntry: (auth) => withHeaders({ url: mcpUrl(auth) }, auth)
|
|
2447
2496
|
},
|
|
2448
2497
|
rule: {
|
|
2498
|
+
kind: "file",
|
|
2449
2499
|
dir: (scope) => scope === "global" ? join8(homedir5(), ".cursor", "rules") : join8(".cursor", "rules"),
|
|
2450
2500
|
filename: "context7.mdc"
|
|
2451
2501
|
},
|
|
@@ -2462,24 +2512,58 @@ var agents = {
|
|
|
2462
2512
|
name: "opencode",
|
|
2463
2513
|
displayName: "OpenCode",
|
|
2464
2514
|
mcp: {
|
|
2465
|
-
|
|
2466
|
-
|
|
2515
|
+
projectPaths: ["opencode.json", "opencode.jsonc", ".opencode.json", ".opencode.jsonc"],
|
|
2516
|
+
globalPaths: [
|
|
2517
|
+
join8(homedir5(), ".config", "opencode", "opencode.json"),
|
|
2518
|
+
join8(homedir5(), ".config", "opencode", "opencode.jsonc"),
|
|
2519
|
+
join8(homedir5(), ".config", "opencode", ".opencode.json"),
|
|
2520
|
+
join8(homedir5(), ".config", "opencode", ".opencode.jsonc")
|
|
2521
|
+
],
|
|
2467
2522
|
configKey: "mcp",
|
|
2468
2523
|
buildEntry: (auth) => withHeaders({ type: "remote", url: mcpUrl(auth), enabled: true }, auth)
|
|
2469
2524
|
},
|
|
2470
2525
|
rule: {
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2526
|
+
kind: "append",
|
|
2527
|
+
file: (scope) => scope === "global" ? join8(homedir5(), ".config", "opencode", "AGENTS.md") : "AGENTS.md",
|
|
2528
|
+
sectionMarker: "<!-- context7 -->"
|
|
2474
2529
|
},
|
|
2475
2530
|
skill: {
|
|
2476
2531
|
name: "context7-mcp",
|
|
2477
2532
|
dir: (scope) => scope === "global" ? join8(homedir5(), ".agents", "skills") : join8(".agents", "skills")
|
|
2478
2533
|
},
|
|
2479
2534
|
detect: {
|
|
2480
|
-
projectPaths: [".opencode.json"],
|
|
2535
|
+
projectPaths: ["opencode.json", "opencode.jsonc", ".opencode.json", ".opencode.jsonc"],
|
|
2481
2536
|
globalPaths: [join8(homedir5(), ".config", "opencode")]
|
|
2482
2537
|
}
|
|
2538
|
+
},
|
|
2539
|
+
codex: {
|
|
2540
|
+
name: "codex",
|
|
2541
|
+
displayName: "Codex",
|
|
2542
|
+
mcp: {
|
|
2543
|
+
projectPaths: [join8(".codex", "config.toml")],
|
|
2544
|
+
globalPaths: [join8(homedir5(), ".codex", "config.toml")],
|
|
2545
|
+
configKey: "mcp_servers",
|
|
2546
|
+
buildEntry: (auth) => {
|
|
2547
|
+
const entry = { type: "http", url: mcpUrl(auth) };
|
|
2548
|
+
if (auth.mode === "api-key" && auth.apiKey) {
|
|
2549
|
+
entry.headers = { CONTEXT7_API_KEY: auth.apiKey };
|
|
2550
|
+
}
|
|
2551
|
+
return entry;
|
|
2552
|
+
}
|
|
2553
|
+
},
|
|
2554
|
+
rule: {
|
|
2555
|
+
kind: "append",
|
|
2556
|
+
file: (scope) => scope === "global" ? join8(homedir5(), ".codex", "AGENTS.md") : "AGENTS.md",
|
|
2557
|
+
sectionMarker: "<!-- context7 -->"
|
|
2558
|
+
},
|
|
2559
|
+
skill: {
|
|
2560
|
+
name: "context7-mcp",
|
|
2561
|
+
dir: (scope) => scope === "global" ? join8(homedir5(), ".agents", "skills") : join8(".agents", "skills")
|
|
2562
|
+
},
|
|
2563
|
+
detect: {
|
|
2564
|
+
projectPaths: [".codex"],
|
|
2565
|
+
globalPaths: [join8(homedir5(), ".codex")]
|
|
2566
|
+
}
|
|
2483
2567
|
}
|
|
2484
2568
|
};
|
|
2485
2569
|
function getAgent(name) {
|
|
@@ -2510,58 +2594,58 @@ async function detectAgents(scope) {
|
|
|
2510
2594
|
}
|
|
2511
2595
|
|
|
2512
2596
|
// src/setup/templates.ts
|
|
2513
|
-
var
|
|
2514
|
-
|
|
2515
|
-
|
|
2597
|
+
var GITHUB_RAW_URLS = [
|
|
2598
|
+
"https://raw.githubusercontent.com/upstash/context7/master/rules",
|
|
2599
|
+
"https://raw.githubusercontent.com/upstash/context7/main/rules"
|
|
2600
|
+
];
|
|
2601
|
+
var FALLBACK_MCP = `Use Context7 MCP to fetch current documentation whenever the user asks about a library, framework, SDK, API, CLI tool, or cloud service -- even well-known ones like React, Next.js, Prisma, Express, Tailwind, Django, or Spring Boot. This includes API syntax, configuration, version migration, library-specific debugging, setup instructions, and CLI tool usage. Use even when you think you know the answer -- your training data may not reflect recent changes. Prefer this over web search for library docs.
|
|
2516
2602
|
|
|
2517
|
-
|
|
2603
|
+
Do not use for: refactoring, writing scripts from scratch, debugging business logic, code review, or general programming concepts.
|
|
2518
2604
|
|
|
2519
2605
|
## Steps
|
|
2520
2606
|
|
|
2521
|
-
1.
|
|
2522
|
-
2. Pick the best match
|
|
2523
|
-
3.
|
|
2524
|
-
4. Answer using the fetched docs
|
|
2607
|
+
1. \`resolve-library-id\` with the library name and the user's question
|
|
2608
|
+
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
|
|
2609
|
+
3. \`query-docs\` with the selected library ID and the user's full question (not single words)
|
|
2610
|
+
4. Answer using the fetched docs
|
|
2525
2611
|
`;
|
|
2612
|
+
var FALLBACK_CLI = `Use the \`ctx7\` CLI to fetch current documentation whenever the user asks about a library, framework, SDK, API, CLI tool, or cloud service -- even well-known ones like React, Next.js, Prisma, Express, Tailwind, Django, or Spring Boot. This includes API syntax, configuration, version migration, library-specific debugging, setup instructions, and CLI tool usage. Use even when you think you know the answer -- your training data may not reflect recent changes. Prefer this over web search for library docs.
|
|
2526
2613
|
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2614
|
+
Do not use for: refactoring, writing scripts from scratch, debugging business logic, code review, or general programming concepts.
|
|
2615
|
+
|
|
2616
|
+
## Steps
|
|
2617
|
+
|
|
2618
|
+
1. Resolve library: \`npx ctx7@latest library <name> "<user's question>"\`
|
|
2619
|
+
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). If results don't look right, try the full name with punctuation (e.g., "next.js" not "nextjs")
|
|
2620
|
+
3. Fetch docs: \`npx ctx7@latest docs <libraryId> "<user's question>"\`
|
|
2621
|
+
4. Answer using the fetched documentation
|
|
2622
|
+
|
|
2623
|
+
You MUST call \`library\` first to get a valid ID (format: \`/org/project\`) unless the user provides one directly. Use the user's full question as the query -- specific and detailed queries return better results than vague single words. Do not run more than 3 commands per question. Do not include sensitive information (API keys, passwords, credentials) in queries.
|
|
2624
|
+
|
|
2625
|
+
For version-specific docs, use \`/org/project/version\` from the \`library\` output (e.g., \`/vercel/next.js/v14.3.0\`).
|
|
2626
|
+
|
|
2627
|
+
If a command fails with a quota error, inform the user and suggest \`npx ctx7@latest login\` or setting \`CONTEXT7_API_KEY\` env var for higher limits. Do not silently fall back to training data.
|
|
2628
|
+
`;
|
|
2629
|
+
var CURSOR_FRONTMATTER = `---
|
|
2630
|
+
alwaysApply: true
|
|
2631
|
+
---
|
|
2632
|
+
|
|
2633
|
+
`;
|
|
2634
|
+
async function fetchRule(filename, fallback) {
|
|
2635
|
+
for (const base of GITHUB_RAW_URLS) {
|
|
2636
|
+
try {
|
|
2637
|
+
const res = await fetch(`${base}/${filename}`);
|
|
2638
|
+
if (res.ok) return await res.text();
|
|
2639
|
+
} catch {
|
|
2640
|
+
continue;
|
|
2641
|
+
}
|
|
2545
2642
|
}
|
|
2546
|
-
return
|
|
2547
|
-
config: {
|
|
2548
|
-
...existing,
|
|
2549
|
-
[configKey]: {
|
|
2550
|
-
...section,
|
|
2551
|
-
[serverName]: entry
|
|
2552
|
-
}
|
|
2553
|
-
},
|
|
2554
|
-
alreadyExists: false
|
|
2555
|
-
};
|
|
2556
|
-
}
|
|
2557
|
-
function mergeInstructions(config, glob) {
|
|
2558
|
-
const instructions = config.instructions ?? [];
|
|
2559
|
-
if (instructions.includes(glob)) return config;
|
|
2560
|
-
return { ...config, instructions: [...instructions, glob] };
|
|
2643
|
+
return fallback;
|
|
2561
2644
|
}
|
|
2562
|
-
async function
|
|
2563
|
-
|
|
2564
|
-
|
|
2645
|
+
async function getRuleContent(mode, agent) {
|
|
2646
|
+
const [filename, fallback] = mode === "mcp" ? ["context7-mcp.md", FALLBACK_MCP] : ["context7-cli.md", FALLBACK_CLI];
|
|
2647
|
+
const body = await fetchRule(filename, fallback);
|
|
2648
|
+
return agent === "cursor" ? `${CURSOR_FRONTMATTER}${body}` : body;
|
|
2565
2649
|
}
|
|
2566
2650
|
|
|
2567
2651
|
// src/commands/setup.ts
|
|
@@ -2576,16 +2660,16 @@ function getSelectedAgents(options) {
|
|
|
2576
2660
|
if (options.claude) agents2.push("claude");
|
|
2577
2661
|
if (options.cursor) agents2.push("cursor");
|
|
2578
2662
|
if (options.opencode) agents2.push("opencode");
|
|
2663
|
+
if (options.codex) agents2.push("codex");
|
|
2579
2664
|
return agents2;
|
|
2580
2665
|
}
|
|
2581
2666
|
function registerSetupCommand(program2) {
|
|
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) => {
|
|
2667
|
+
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) => {
|
|
2583
2668
|
await setupCommand(options);
|
|
2584
2669
|
});
|
|
2585
2670
|
}
|
|
2586
2671
|
async function authenticateAndGenerateKey() {
|
|
2587
|
-
const
|
|
2588
|
-
const accessToken = existingTokens && !isTokenExpired(existingTokens) ? existingTokens.access_token : await performLogin();
|
|
2672
|
+
const accessToken = await getValidAccessToken() ?? await performLogin();
|
|
2589
2673
|
if (!accessToken) return null;
|
|
2590
2674
|
const spinner = ora4("Configuring authentication...").start();
|
|
2591
2675
|
try {
|
|
@@ -2625,15 +2709,15 @@ async function resolveMode(options) {
|
|
|
2625
2709
|
return select3({
|
|
2626
2710
|
message: "How should your agent access Context7?",
|
|
2627
2711
|
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
2712
|
{
|
|
2634
2713
|
name: `MCP server
|
|
2635
2714
|
${pc8.dim("Agent calls Context7 tools via MCP protocol to retrieve up-to-date library docs")}`,
|
|
2636
2715
|
value: "mcp"
|
|
2716
|
+
},
|
|
2717
|
+
{
|
|
2718
|
+
name: `CLI + Skills
|
|
2719
|
+
${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")}`,
|
|
2720
|
+
value: "cli"
|
|
2637
2721
|
}
|
|
2638
2722
|
],
|
|
2639
2723
|
theme: {
|
|
@@ -2651,8 +2735,8 @@ async function resolveCliAuth(apiKey) {
|
|
|
2651
2735
|
log.plain(`${pc8.green("\u2714")} Authenticated`);
|
|
2652
2736
|
return;
|
|
2653
2737
|
}
|
|
2654
|
-
const
|
|
2655
|
-
if (
|
|
2738
|
+
const validToken = await getValidAccessToken();
|
|
2739
|
+
if (validToken) {
|
|
2656
2740
|
log.blank();
|
|
2657
2741
|
log.plain(`${pc8.green("\u2714")} Authenticated`);
|
|
2658
2742
|
return;
|
|
@@ -2661,8 +2745,13 @@ async function resolveCliAuth(apiKey) {
|
|
|
2661
2745
|
}
|
|
2662
2746
|
async function isAlreadyConfigured(agentName, scope) {
|
|
2663
2747
|
const agent = getAgent(agentName);
|
|
2664
|
-
const
|
|
2748
|
+
const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((p) => join9(process.cwd(), p));
|
|
2749
|
+
const mcpPath = await resolveMcpPath(mcpCandidates);
|
|
2665
2750
|
try {
|
|
2751
|
+
if (mcpPath.endsWith(".toml")) {
|
|
2752
|
+
const { readTomlServerExists } = await import("./mcp-writer-IYBCUACD.js");
|
|
2753
|
+
return readTomlServerExists(mcpPath, "context7");
|
|
2754
|
+
}
|
|
2666
2755
|
const existing = await readJsonConfig(mcpPath);
|
|
2667
2756
|
const section = existing[agent.mcp.configKey] ?? {};
|
|
2668
2757
|
return "context7" in section;
|
|
@@ -2685,7 +2774,7 @@ async function promptAgents(scope, mode) {
|
|
|
2685
2774
|
log.info("Context7 is already configured for all detected agents.");
|
|
2686
2775
|
return null;
|
|
2687
2776
|
}
|
|
2688
|
-
const message =
|
|
2777
|
+
const message = "Which agents do you want to set up?";
|
|
2689
2778
|
try {
|
|
2690
2779
|
return await checkboxWithHover(
|
|
2691
2780
|
{
|
|
@@ -2713,38 +2802,79 @@ async function resolveAgents(options, scope, mode = "mcp") {
|
|
|
2713
2802
|
}
|
|
2714
2803
|
return selected;
|
|
2715
2804
|
}
|
|
2805
|
+
async function installRule(agentName, mode, scope) {
|
|
2806
|
+
const agent = getAgent(agentName);
|
|
2807
|
+
const rule = agent.rule;
|
|
2808
|
+
const content = await getRuleContent(mode, agentName);
|
|
2809
|
+
if (rule.kind === "file") {
|
|
2810
|
+
const ruleDir = scope === "global" ? rule.dir("global") : join9(process.cwd(), rule.dir("project"));
|
|
2811
|
+
const rulePath = join9(ruleDir, rule.filename);
|
|
2812
|
+
await mkdir3(dirname4(rulePath), { recursive: true });
|
|
2813
|
+
await writeFile3(rulePath, content, "utf-8");
|
|
2814
|
+
return { status: "installed", path: rulePath };
|
|
2815
|
+
}
|
|
2816
|
+
const filePath = scope === "global" ? rule.file("global") : join9(process.cwd(), rule.file("project"));
|
|
2817
|
+
const escapedMarker = rule.sectionMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2818
|
+
const section = `${rule.sectionMarker}
|
|
2819
|
+
${content}${rule.sectionMarker}`;
|
|
2820
|
+
let existing = "";
|
|
2821
|
+
try {
|
|
2822
|
+
existing = await readFile3(filePath, "utf-8");
|
|
2823
|
+
} catch {
|
|
2824
|
+
}
|
|
2825
|
+
if (existing.includes(rule.sectionMarker)) {
|
|
2826
|
+
const regex = new RegExp(`${escapedMarker}\\n[\\s\\S]*?${escapedMarker}`);
|
|
2827
|
+
const updated = existing.replace(regex, section);
|
|
2828
|
+
await writeFile3(filePath, updated, "utf-8");
|
|
2829
|
+
return { status: "updated", path: filePath };
|
|
2830
|
+
}
|
|
2831
|
+
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
|
|
2832
|
+
await mkdir3(dirname4(filePath), { recursive: true });
|
|
2833
|
+
await writeFile3(filePath, existing + separator + section + "\n", "utf-8");
|
|
2834
|
+
return { status: "installed", path: filePath };
|
|
2835
|
+
}
|
|
2716
2836
|
async function setupAgent(agentName, auth, scope) {
|
|
2717
2837
|
const agent = getAgent(agentName);
|
|
2718
|
-
const
|
|
2838
|
+
const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((p) => join9(process.cwd(), p));
|
|
2839
|
+
const mcpPath = await resolveMcpPath(mcpCandidates);
|
|
2719
2840
|
let mcpStatus;
|
|
2720
2841
|
try {
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
if (alreadyExists) {
|
|
2729
|
-
mcpStatus = "already configured";
|
|
2842
|
+
if (mcpPath.endsWith(".toml")) {
|
|
2843
|
+
const { alreadyExists } = await appendTomlServer(
|
|
2844
|
+
mcpPath,
|
|
2845
|
+
"context7",
|
|
2846
|
+
agent.mcp.buildEntry(auth)
|
|
2847
|
+
);
|
|
2848
|
+
mcpStatus = alreadyExists ? "already configured" : `configured with ${AUTH_MODE_LABELS[auth.mode]}`;
|
|
2730
2849
|
} else {
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2850
|
+
const existing = await readJsonConfig(mcpPath);
|
|
2851
|
+
const { config, alreadyExists } = mergeServerEntry(
|
|
2852
|
+
existing,
|
|
2853
|
+
agent.mcp.configKey,
|
|
2854
|
+
"context7",
|
|
2855
|
+
agent.mcp.buildEntry(auth)
|
|
2856
|
+
);
|
|
2857
|
+
if (alreadyExists) {
|
|
2858
|
+
mcpStatus = "already configured";
|
|
2859
|
+
} else {
|
|
2860
|
+
mcpStatus = `configured with ${AUTH_MODE_LABELS[auth.mode]}`;
|
|
2861
|
+
}
|
|
2862
|
+
if (config !== existing) {
|
|
2863
|
+
await writeJsonConfig(mcpPath, config);
|
|
2864
|
+
}
|
|
2736
2865
|
}
|
|
2737
2866
|
} catch (err) {
|
|
2738
2867
|
mcpStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
2739
2868
|
}
|
|
2740
|
-
const rulePath = scope === "global" ? join9(agent.rule.dir("global"), agent.rule.filename) : join9(process.cwd(), agent.rule.dir("project"), agent.rule.filename);
|
|
2741
2869
|
let ruleStatus;
|
|
2870
|
+
let rulePath;
|
|
2742
2871
|
try {
|
|
2743
|
-
await
|
|
2744
|
-
|
|
2745
|
-
|
|
2872
|
+
const result = await installRule(agentName, "mcp", scope);
|
|
2873
|
+
ruleStatus = result.status;
|
|
2874
|
+
rulePath = result.path;
|
|
2746
2875
|
} catch (err) {
|
|
2747
2876
|
ruleStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
2877
|
+
rulePath = "";
|
|
2748
2878
|
}
|
|
2749
2879
|
const skillDir = scope === "global" ? agent.skill.dir("global") : join9(process.cwd(), agent.skill.dir("project"));
|
|
2750
2880
|
const skillPath = join9(skillDir, agent.skill.name, "SKILL.md");
|
|
@@ -2800,13 +2930,34 @@ async function setupMcp(agents2, options, scope) {
|
|
|
2800
2930
|
trackEvent("setup", { agents: agents2, scope, authMode: auth.mode });
|
|
2801
2931
|
trackEvent("install", { skills: ["/upstash/context7/context7-mcp"], ides: agents2 });
|
|
2802
2932
|
}
|
|
2933
|
+
async function setupCliAgent(agentName, scope, downloadData) {
|
|
2934
|
+
const agent = getAgent(agentName);
|
|
2935
|
+
const skillDir = scope === "global" ? agent.skill.dir("global") : join9(process.cwd(), agent.skill.dir("project"));
|
|
2936
|
+
let skillStatus;
|
|
2937
|
+
try {
|
|
2938
|
+
await installSkillFiles("find-docs", downloadData.files, skillDir);
|
|
2939
|
+
skillStatus = "installed";
|
|
2940
|
+
} catch (err) {
|
|
2941
|
+
skillStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
2942
|
+
}
|
|
2943
|
+
const skillPath = join9(skillDir, "find-docs");
|
|
2944
|
+
let ruleStatus;
|
|
2945
|
+
let rulePath;
|
|
2946
|
+
try {
|
|
2947
|
+
const result = await installRule(agentName, "cli", scope);
|
|
2948
|
+
ruleStatus = result.status;
|
|
2949
|
+
rulePath = result.path;
|
|
2950
|
+
} catch (err) {
|
|
2951
|
+
ruleStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
2952
|
+
rulePath = "";
|
|
2953
|
+
}
|
|
2954
|
+
return { skillPath, skillStatus, rulePath, ruleStatus };
|
|
2955
|
+
}
|
|
2803
2956
|
async function setupCli(options) {
|
|
2804
2957
|
await resolveCliAuth(options.apiKey);
|
|
2805
|
-
const
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
return;
|
|
2809
|
-
}
|
|
2958
|
+
const scope = options.project ? "project" : "global";
|
|
2959
|
+
const agents2 = await resolveAgents(options, scope, "cli");
|
|
2960
|
+
if (agents2.length === 0) return;
|
|
2810
2961
|
log.blank();
|
|
2811
2962
|
const spinner = ora4("Downloading find-docs skill...").start();
|
|
2812
2963
|
const downloadData = await downloadSkill("/upstash/context7", "find-docs");
|
|
@@ -2815,28 +2966,27 @@ async function setupCli(options) {
|
|
|
2815
2966
|
return;
|
|
2816
2967
|
}
|
|
2817
2968
|
spinner.succeed("Downloaded find-docs skill");
|
|
2818
|
-
const
|
|
2819
|
-
const
|
|
2820
|
-
for (const
|
|
2821
|
-
installSpinner.text = `
|
|
2822
|
-
await
|
|
2969
|
+
const installSpinner = ora4("Installing...").start();
|
|
2970
|
+
const results = [];
|
|
2971
|
+
for (const agentName of agents2) {
|
|
2972
|
+
installSpinner.text = `Setting up ${getAgent(agentName).displayName}...`;
|
|
2973
|
+
const r = await setupCliAgent(agentName, scope, downloadData);
|
|
2974
|
+
results.push({ agent: getAgent(agentName).displayName, ...r });
|
|
2823
2975
|
}
|
|
2824
|
-
installSpinner.
|
|
2976
|
+
installSpinner.succeed("Context7 CLI setup complete");
|
|
2825
2977
|
log.blank();
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
log.
|
|
2830
|
-
|
|
2831
|
-
);
|
|
2832
|
-
log.plain(` ${
|
|
2978
|
+
for (const r of results) {
|
|
2979
|
+
log.plain(` ${pc8.bold(r.agent)}`);
|
|
2980
|
+
const skillIcon = r.skillStatus === "installed" ? pc8.green("+") : pc8.dim("~");
|
|
2981
|
+
log.plain(` ${skillIcon} Skill ${r.skillStatus}`);
|
|
2982
|
+
log.plain(` ${pc8.dim(r.skillPath)}`);
|
|
2983
|
+
const ruleIcon = r.ruleStatus === "installed" || r.ruleStatus === "updated" ? pc8.green("+") : pc8.dim("~");
|
|
2984
|
+
log.plain(` ${ruleIcon} Rule ${r.ruleStatus}`);
|
|
2985
|
+
log.plain(` ${pc8.dim(r.rulePath)}`);
|
|
2833
2986
|
}
|
|
2834
2987
|
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
2988
|
trackEvent("setup", { mode: "cli" });
|
|
2839
|
-
trackEvent("install", { skills: ["/upstash/context7/find-docs"], ides:
|
|
2989
|
+
trackEvent("install", { skills: ["/upstash/context7/find-docs"], ides: agents2 });
|
|
2840
2990
|
}
|
|
2841
2991
|
async function setupCommand(options) {
|
|
2842
2992
|
trackEvent("command", { name: "setup" });
|
|
@@ -2925,7 +3075,7 @@ async function resolveCommand(library, query, options) {
|
|
|
2925
3075
|
log.blank();
|
|
2926
3076
|
if (data.searchFilterApplied) {
|
|
2927
3077
|
log.warn(
|
|
2928
|
-
"Your results only include libraries matching your
|
|
3078
|
+
"Your results only include libraries matching your teamspace's library filters. To adjust quality thresholds or blocked libraries, update your filters at https://context7.com/dashboard?tab=policies"
|
|
2929
3079
|
);
|
|
2930
3080
|
log.blank();
|
|
2931
3081
|
}
|