ctx7 0.3.6 → 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 +244 -122
- 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 +1 -1
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
|
|
@@ -433,13 +444,18 @@ async function checkboxWithHover(config, options) {
|
|
|
433
444
|
);
|
|
434
445
|
const values = choices.map((c) => c.value);
|
|
435
446
|
const totalItems = values.length;
|
|
436
|
-
let cursorPosition =
|
|
447
|
+
let cursorPosition = choices.findIndex((c) => !c.disabled);
|
|
448
|
+
if (cursorPosition < 0) cursorPosition = 0;
|
|
437
449
|
const getName = options?.getName ?? ((v) => v.name);
|
|
438
450
|
const keypressHandler = (_str, key) => {
|
|
439
|
-
if (key.name === "up"
|
|
440
|
-
cursorPosition
|
|
441
|
-
|
|
442
|
-
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;
|
|
443
459
|
}
|
|
444
460
|
};
|
|
445
461
|
readline.emitKeypressEvents(process.stdin);
|
|
@@ -483,7 +499,7 @@ var IDE_GLOBAL_PATHS = {
|
|
|
483
499
|
claude: ".claude/skills",
|
|
484
500
|
cursor: ".cursor/skills",
|
|
485
501
|
antigravity: ".agent/skills",
|
|
486
|
-
universal: ".
|
|
502
|
+
universal: ".agents/skills"
|
|
487
503
|
};
|
|
488
504
|
var IDE_NAMES = {
|
|
489
505
|
claude: "Claude Code",
|
|
@@ -492,7 +508,7 @@ var IDE_NAMES = {
|
|
|
492
508
|
universal: "Universal"
|
|
493
509
|
};
|
|
494
510
|
var UNIVERSAL_SKILLS_PATH = ".agents/skills";
|
|
495
|
-
var UNIVERSAL_SKILLS_GLOBAL_PATH = ".
|
|
511
|
+
var UNIVERSAL_SKILLS_GLOBAL_PATH = ".agents/skills";
|
|
496
512
|
var UNIVERSAL_AGENTS_LABEL = "Amp, Codex, Gemini CLI, GitHub Copilot, OpenCode + more";
|
|
497
513
|
var VENDOR_SPECIFIC_AGENTS = ["claude", "cursor", "antigravity"];
|
|
498
514
|
var DEFAULT_CONFIG = {
|
|
@@ -715,12 +731,15 @@ function getTargetDirFromSelection(ide, scope) {
|
|
|
715
731
|
|
|
716
732
|
// src/utils/installer.ts
|
|
717
733
|
import { mkdir, writeFile, rm, symlink, lstat } from "fs/promises";
|
|
718
|
-
import { join as join3 } from "path";
|
|
734
|
+
import { join as join3, resolve, dirname as dirname3 } from "path";
|
|
719
735
|
async function installSkillFiles(skillName, files, targetDir) {
|
|
720
|
-
const skillDir =
|
|
736
|
+
const skillDir = resolve(targetDir, skillName);
|
|
721
737
|
for (const file of files) {
|
|
722
|
-
const filePath =
|
|
723
|
-
|
|
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);
|
|
724
743
|
await mkdir(fileDir, { recursive: true });
|
|
725
744
|
await writeFile(filePath, file.content);
|
|
726
745
|
}
|
|
@@ -850,11 +869,11 @@ function createCallbackServer(expectedState) {
|
|
|
850
869
|
let resolveResult;
|
|
851
870
|
let rejectResult;
|
|
852
871
|
let serverInstance = null;
|
|
853
|
-
const portPromise = new Promise((
|
|
854
|
-
resolvePort =
|
|
872
|
+
const portPromise = new Promise((resolve2) => {
|
|
873
|
+
resolvePort = resolve2;
|
|
855
874
|
});
|
|
856
|
-
const resultPromise = new Promise((
|
|
857
|
-
resolveResult =
|
|
875
|
+
const resultPromise = new Promise((resolve2, reject) => {
|
|
876
|
+
resolveResult = resolve2;
|
|
858
877
|
rejectResult = reject;
|
|
859
878
|
});
|
|
860
879
|
const server = http.createServer((req, res) => {
|
|
@@ -1317,7 +1336,7 @@ async function generateCommand(options) {
|
|
|
1317
1336
|
log.blank();
|
|
1318
1337
|
if (searchResult.searchFilterApplied) {
|
|
1319
1338
|
log.warn(
|
|
1320
|
-
"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"
|
|
1321
1340
|
);
|
|
1322
1341
|
log.blank();
|
|
1323
1342
|
}
|
|
@@ -1542,12 +1561,11 @@ async function generateCommand(options) {
|
|
|
1542
1561
|
previewFileWritten = true;
|
|
1543
1562
|
}
|
|
1544
1563
|
const editor = process.env.EDITOR || "open";
|
|
1545
|
-
await new Promise((
|
|
1564
|
+
await new Promise((resolve2) => {
|
|
1546
1565
|
const child = spawn(editor, [previewFile], {
|
|
1547
|
-
stdio: "inherit"
|
|
1548
|
-
shell: true
|
|
1566
|
+
stdio: "inherit"
|
|
1549
1567
|
});
|
|
1550
|
-
child.on("close", () =>
|
|
1568
|
+
child.on("close", () => resolve2());
|
|
1551
1569
|
});
|
|
1552
1570
|
};
|
|
1553
1571
|
const syncFromPreviewFile = async () => {
|
|
@@ -1556,7 +1574,7 @@ async function generateCommand(options) {
|
|
|
1556
1574
|
}
|
|
1557
1575
|
};
|
|
1558
1576
|
showPreview();
|
|
1559
|
-
await new Promise((
|
|
1577
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
1560
1578
|
try {
|
|
1561
1579
|
let action;
|
|
1562
1580
|
while (true) {
|
|
@@ -2415,7 +2433,7 @@ ${headerLine}`,
|
|
|
2415
2433
|
import pc8 from "picocolors";
|
|
2416
2434
|
import ora4 from "ora";
|
|
2417
2435
|
import { select as select3 } from "@inquirer/prompts";
|
|
2418
|
-
import { mkdir as
|
|
2436
|
+
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
2419
2437
|
import { dirname as dirname4, join as join9 } from "path";
|
|
2420
2438
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2421
2439
|
|
|
@@ -2426,7 +2444,8 @@ import { homedir as homedir5 } from "os";
|
|
|
2426
2444
|
var SETUP_AGENT_NAMES = {
|
|
2427
2445
|
claude: "Claude Code",
|
|
2428
2446
|
cursor: "Cursor",
|
|
2429
|
-
opencode: "OpenCode"
|
|
2447
|
+
opencode: "OpenCode",
|
|
2448
|
+
codex: "Codex"
|
|
2430
2449
|
};
|
|
2431
2450
|
var AUTH_MODE_LABELS = {
|
|
2432
2451
|
oauth: "OAuth",
|
|
@@ -2447,12 +2466,13 @@ var agents = {
|
|
|
2447
2466
|
name: "claude",
|
|
2448
2467
|
displayName: "Claude Code",
|
|
2449
2468
|
mcp: {
|
|
2450
|
-
|
|
2451
|
-
|
|
2469
|
+
projectPaths: [".mcp.json"],
|
|
2470
|
+
globalPaths: [join8(homedir5(), ".claude.json")],
|
|
2452
2471
|
configKey: "mcpServers",
|
|
2453
2472
|
buildEntry: (auth) => withHeaders({ type: "http", url: mcpUrl(auth) }, auth)
|
|
2454
2473
|
},
|
|
2455
2474
|
rule: {
|
|
2475
|
+
kind: "file",
|
|
2456
2476
|
dir: (scope) => scope === "global" ? join8(homedir5(), ".claude", "rules") : join8(".claude", "rules"),
|
|
2457
2477
|
filename: "context7.md"
|
|
2458
2478
|
},
|
|
@@ -2469,12 +2489,13 @@ var agents = {
|
|
|
2469
2489
|
name: "cursor",
|
|
2470
2490
|
displayName: "Cursor",
|
|
2471
2491
|
mcp: {
|
|
2472
|
-
|
|
2473
|
-
|
|
2492
|
+
projectPaths: [join8(".cursor", "mcp.json")],
|
|
2493
|
+
globalPaths: [join8(homedir5(), ".cursor", "mcp.json")],
|
|
2474
2494
|
configKey: "mcpServers",
|
|
2475
2495
|
buildEntry: (auth) => withHeaders({ url: mcpUrl(auth) }, auth)
|
|
2476
2496
|
},
|
|
2477
2497
|
rule: {
|
|
2498
|
+
kind: "file",
|
|
2478
2499
|
dir: (scope) => scope === "global" ? join8(homedir5(), ".cursor", "rules") : join8(".cursor", "rules"),
|
|
2479
2500
|
filename: "context7.mdc"
|
|
2480
2501
|
},
|
|
@@ -2491,24 +2512,58 @@ var agents = {
|
|
|
2491
2512
|
name: "opencode",
|
|
2492
2513
|
displayName: "OpenCode",
|
|
2493
2514
|
mcp: {
|
|
2494
|
-
|
|
2495
|
-
|
|
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
|
+
],
|
|
2496
2522
|
configKey: "mcp",
|
|
2497
2523
|
buildEntry: (auth) => withHeaders({ type: "remote", url: mcpUrl(auth), enabled: true }, auth)
|
|
2498
2524
|
},
|
|
2499
2525
|
rule: {
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2526
|
+
kind: "append",
|
|
2527
|
+
file: (scope) => scope === "global" ? join8(homedir5(), ".config", "opencode", "AGENTS.md") : "AGENTS.md",
|
|
2528
|
+
sectionMarker: "<!-- context7 -->"
|
|
2503
2529
|
},
|
|
2504
2530
|
skill: {
|
|
2505
2531
|
name: "context7-mcp",
|
|
2506
2532
|
dir: (scope) => scope === "global" ? join8(homedir5(), ".agents", "skills") : join8(".agents", "skills")
|
|
2507
2533
|
},
|
|
2508
2534
|
detect: {
|
|
2509
|
-
projectPaths: [".opencode.json"],
|
|
2535
|
+
projectPaths: ["opencode.json", "opencode.jsonc", ".opencode.json", ".opencode.jsonc"],
|
|
2510
2536
|
globalPaths: [join8(homedir5(), ".config", "opencode")]
|
|
2511
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
|
+
}
|
|
2512
2567
|
}
|
|
2513
2568
|
};
|
|
2514
2569
|
function getAgent(name) {
|
|
@@ -2539,58 +2594,58 @@ async function detectAgents(scope) {
|
|
|
2539
2594
|
}
|
|
2540
2595
|
|
|
2541
2596
|
// src/setup/templates.ts
|
|
2542
|
-
var
|
|
2543
|
-
|
|
2544
|
-
|
|
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.
|
|
2545
2602
|
|
|
2546
|
-
|
|
2603
|
+
Do not use for: refactoring, writing scripts from scratch, debugging business logic, code review, or general programming concepts.
|
|
2547
2604
|
|
|
2548
2605
|
## Steps
|
|
2549
2606
|
|
|
2550
|
-
1.
|
|
2551
|
-
2. Pick the best match
|
|
2552
|
-
3.
|
|
2553
|
-
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
|
|
2554
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.
|
|
2555
2613
|
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
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
|
+
}
|
|
2574
2642
|
}
|
|
2575
|
-
return
|
|
2576
|
-
config: {
|
|
2577
|
-
...existing,
|
|
2578
|
-
[configKey]: {
|
|
2579
|
-
...section,
|
|
2580
|
-
[serverName]: entry
|
|
2581
|
-
}
|
|
2582
|
-
},
|
|
2583
|
-
alreadyExists: false
|
|
2584
|
-
};
|
|
2585
|
-
}
|
|
2586
|
-
function mergeInstructions(config, glob) {
|
|
2587
|
-
const instructions = config.instructions ?? [];
|
|
2588
|
-
if (instructions.includes(glob)) return config;
|
|
2589
|
-
return { ...config, instructions: [...instructions, glob] };
|
|
2643
|
+
return fallback;
|
|
2590
2644
|
}
|
|
2591
|
-
async function
|
|
2592
|
-
|
|
2593
|
-
|
|
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;
|
|
2594
2649
|
}
|
|
2595
2650
|
|
|
2596
2651
|
// src/commands/setup.ts
|
|
@@ -2605,10 +2660,11 @@ function getSelectedAgents(options) {
|
|
|
2605
2660
|
if (options.claude) agents2.push("claude");
|
|
2606
2661
|
if (options.cursor) agents2.push("cursor");
|
|
2607
2662
|
if (options.opencode) agents2.push("opencode");
|
|
2663
|
+
if (options.codex) agents2.push("codex");
|
|
2608
2664
|
return agents2;
|
|
2609
2665
|
}
|
|
2610
2666
|
function registerSetupCommand(program2) {
|
|
2611
|
-
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) => {
|
|
2612
2668
|
await setupCommand(options);
|
|
2613
2669
|
});
|
|
2614
2670
|
}
|
|
@@ -2689,8 +2745,13 @@ async function resolveCliAuth(apiKey) {
|
|
|
2689
2745
|
}
|
|
2690
2746
|
async function isAlreadyConfigured(agentName, scope) {
|
|
2691
2747
|
const agent = getAgent(agentName);
|
|
2692
|
-
const
|
|
2748
|
+
const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((p) => join9(process.cwd(), p));
|
|
2749
|
+
const mcpPath = await resolveMcpPath(mcpCandidates);
|
|
2693
2750
|
try {
|
|
2751
|
+
if (mcpPath.endsWith(".toml")) {
|
|
2752
|
+
const { readTomlServerExists } = await import("./mcp-writer-IYBCUACD.js");
|
|
2753
|
+
return readTomlServerExists(mcpPath, "context7");
|
|
2754
|
+
}
|
|
2694
2755
|
const existing = await readJsonConfig(mcpPath);
|
|
2695
2756
|
const section = existing[agent.mcp.configKey] ?? {};
|
|
2696
2757
|
return "context7" in section;
|
|
@@ -2713,7 +2774,7 @@ async function promptAgents(scope, mode) {
|
|
|
2713
2774
|
log.info("Context7 is already configured for all detected agents.");
|
|
2714
2775
|
return null;
|
|
2715
2776
|
}
|
|
2716
|
-
const message =
|
|
2777
|
+
const message = "Which agents do you want to set up?";
|
|
2717
2778
|
try {
|
|
2718
2779
|
return await checkboxWithHover(
|
|
2719
2780
|
{
|
|
@@ -2741,38 +2802,79 @@ async function resolveAgents(options, scope, mode = "mcp") {
|
|
|
2741
2802
|
}
|
|
2742
2803
|
return selected;
|
|
2743
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
|
+
}
|
|
2744
2836
|
async function setupAgent(agentName, auth, scope) {
|
|
2745
2837
|
const agent = getAgent(agentName);
|
|
2746
|
-
const
|
|
2838
|
+
const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((p) => join9(process.cwd(), p));
|
|
2839
|
+
const mcpPath = await resolveMcpPath(mcpCandidates);
|
|
2747
2840
|
let mcpStatus;
|
|
2748
2841
|
try {
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
if (alreadyExists) {
|
|
2757
|
-
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]}`;
|
|
2758
2849
|
} else {
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
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
|
+
}
|
|
2764
2865
|
}
|
|
2765
2866
|
} catch (err) {
|
|
2766
2867
|
mcpStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
2767
2868
|
}
|
|
2768
|
-
const rulePath = scope === "global" ? join9(agent.rule.dir("global"), agent.rule.filename) : join9(process.cwd(), agent.rule.dir("project"), agent.rule.filename);
|
|
2769
2869
|
let ruleStatus;
|
|
2870
|
+
let rulePath;
|
|
2770
2871
|
try {
|
|
2771
|
-
await
|
|
2772
|
-
|
|
2773
|
-
|
|
2872
|
+
const result = await installRule(agentName, "mcp", scope);
|
|
2873
|
+
ruleStatus = result.status;
|
|
2874
|
+
rulePath = result.path;
|
|
2774
2875
|
} catch (err) {
|
|
2775
2876
|
ruleStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
2877
|
+
rulePath = "";
|
|
2776
2878
|
}
|
|
2777
2879
|
const skillDir = scope === "global" ? agent.skill.dir("global") : join9(process.cwd(), agent.skill.dir("project"));
|
|
2778
2880
|
const skillPath = join9(skillDir, agent.skill.name, "SKILL.md");
|
|
@@ -2828,13 +2930,34 @@ async function setupMcp(agents2, options, scope) {
|
|
|
2828
2930
|
trackEvent("setup", { agents: agents2, scope, authMode: auth.mode });
|
|
2829
2931
|
trackEvent("install", { skills: ["/upstash/context7/context7-mcp"], ides: agents2 });
|
|
2830
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
|
+
}
|
|
2831
2956
|
async function setupCli(options) {
|
|
2832
2957
|
await resolveCliAuth(options.apiKey);
|
|
2833
|
-
const
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
return;
|
|
2837
|
-
}
|
|
2958
|
+
const scope = options.project ? "project" : "global";
|
|
2959
|
+
const agents2 = await resolveAgents(options, scope, "cli");
|
|
2960
|
+
if (agents2.length === 0) return;
|
|
2838
2961
|
log.blank();
|
|
2839
2962
|
const spinner = ora4("Downloading find-docs skill...").start();
|
|
2840
2963
|
const downloadData = await downloadSkill("/upstash/context7", "find-docs");
|
|
@@ -2843,28 +2966,27 @@ async function setupCli(options) {
|
|
|
2843
2966
|
return;
|
|
2844
2967
|
}
|
|
2845
2968
|
spinner.succeed("Downloaded find-docs skill");
|
|
2846
|
-
const
|
|
2847
|
-
const
|
|
2848
|
-
for (const
|
|
2849
|
-
installSpinner.text = `
|
|
2850
|
-
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 });
|
|
2851
2975
|
}
|
|
2852
|
-
installSpinner.
|
|
2853
|
-
log.blank();
|
|
2854
|
-
log.plain(`${pc8.green("\u2714")} Context7 CLI setup complete`);
|
|
2976
|
+
installSpinner.succeed("Context7 CLI setup complete");
|
|
2855
2977
|
log.blank();
|
|
2856
|
-
for (const
|
|
2857
|
-
log.
|
|
2858
|
-
|
|
2859
|
-
);
|
|
2860
|
-
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)}`);
|
|
2861
2986
|
}
|
|
2862
2987
|
log.blank();
|
|
2863
|
-
log.plain(` ${pc8.bold("Next steps")}`);
|
|
2864
|
-
log.plain(` Ask your agent: ${pc8.cyan(`"Use ctx7 CLI to look up React hooks"`)}`);
|
|
2865
|
-
log.blank();
|
|
2866
2988
|
trackEvent("setup", { mode: "cli" });
|
|
2867
|
-
trackEvent("install", { skills: ["/upstash/context7/find-docs"], ides:
|
|
2989
|
+
trackEvent("install", { skills: ["/upstash/context7/find-docs"], ides: agents2 });
|
|
2868
2990
|
}
|
|
2869
2991
|
async function setupCommand(options) {
|
|
2870
2992
|
trackEvent("command", { name: "setup" });
|
|
@@ -2953,7 +3075,7 @@ async function resolveCommand(library, query, options) {
|
|
|
2953
3075
|
log.blank();
|
|
2954
3076
|
if (data.searchFilterApplied) {
|
|
2955
3077
|
log.warn(
|
|
2956
|
-
"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"
|
|
2957
3079
|
);
|
|
2958
3080
|
log.blank();
|
|
2959
3081
|
}
|