ctx7 0.3.12 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -5
- package/dist/index.js +792 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import pc12 from "picocolors";
|
|
6
6
|
import figlet from "figlet";
|
|
7
7
|
|
|
8
8
|
// src/commands/skill.ts
|
|
@@ -453,6 +453,9 @@ async function getLibraryContext(libraryId, query, options, accessToken) {
|
|
|
453
453
|
if (options?.type) {
|
|
454
454
|
params.set("type", options.type);
|
|
455
455
|
}
|
|
456
|
+
if (options?.researchMode) {
|
|
457
|
+
params.set("researchMode", "true");
|
|
458
|
+
}
|
|
456
459
|
const response = await fetch(`${baseUrl}/api/v2/context?${params}`, {
|
|
457
460
|
headers: getAuthHeaders(accessToken)
|
|
458
461
|
});
|
|
@@ -482,6 +485,24 @@ async function getLibraryContext(libraryId, query, options, accessToken) {
|
|
|
482
485
|
|
|
483
486
|
// src/utils/logger.ts
|
|
484
487
|
import pc from "picocolors";
|
|
488
|
+
var ANSI_PATTERN = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
489
|
+
function visibleLength(text) {
|
|
490
|
+
return text.replace(ANSI_PATTERN, "").length;
|
|
491
|
+
}
|
|
492
|
+
function padVisible(text, width) {
|
|
493
|
+
const padding = Math.max(0, width - visibleLength(text));
|
|
494
|
+
return text + " ".repeat(padding);
|
|
495
|
+
}
|
|
496
|
+
function box(lines, color = pc.green) {
|
|
497
|
+
const contentWidth = Math.max(...lines.map((line) => visibleLength(line)), 0);
|
|
498
|
+
const top = color(`\u250C${"\u2500".repeat(contentWidth + 2)}\u2510`);
|
|
499
|
+
const bottom = color(`\u2514${"\u2500".repeat(contentWidth + 2)}\u2518`);
|
|
500
|
+
console.log(top);
|
|
501
|
+
for (const line of lines) {
|
|
502
|
+
console.log(color("\u2502 ") + padVisible(line, contentWidth) + color(" \u2502"));
|
|
503
|
+
}
|
|
504
|
+
console.log(bottom);
|
|
505
|
+
}
|
|
485
506
|
var log = {
|
|
486
507
|
info: (message) => console.log(pc.cyan(message)),
|
|
487
508
|
success: (message) => console.log(pc.green(`\u2714 ${message}`)),
|
|
@@ -491,7 +512,8 @@ var log = {
|
|
|
491
512
|
item: (message) => console.log(pc.green(` ${message}`)),
|
|
492
513
|
itemAdd: (message) => console.log(` ${pc.green("+")} ${message}`),
|
|
493
514
|
plain: (message) => console.log(message),
|
|
494
|
-
blank: () => console.log("")
|
|
515
|
+
blank: () => console.log(""),
|
|
516
|
+
box
|
|
495
517
|
};
|
|
496
518
|
|
|
497
519
|
// src/utils/ide.ts
|
|
@@ -844,7 +866,7 @@ async function installSkillFiles(skillName, files, targetDir) {
|
|
|
844
866
|
const skillDir = resolve(targetDir, skillName);
|
|
845
867
|
for (const file of files) {
|
|
846
868
|
const filePath = resolve(skillDir, file.path);
|
|
847
|
-
if (!filePath.startsWith(skillDir + "/") && filePath !== skillDir) {
|
|
869
|
+
if (!filePath.startsWith(skillDir + "/") && !filePath.startsWith(skillDir + "\\") && filePath !== skillDir) {
|
|
848
870
|
throw new Error(`Skill file path "${file.path}" resolves outside the target directory`);
|
|
849
871
|
}
|
|
850
872
|
const fileDir = dirname3(filePath);
|
|
@@ -2761,7 +2783,8 @@ Do not use for: refactoring, writing scripts from scratch, debugging business lo
|
|
|
2761
2783
|
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")
|
|
2762
2784
|
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
|
|
2763
2785
|
3. \`query-docs\` with the selected library ID and the user's full question (not single words)
|
|
2764
|
-
4.
|
|
2786
|
+
4. If you weren't satisfied with the answer, call \`query-docs\` again for the same library with \`researchMode: true\`. This retries with sandboxed agents that git-pull the actual source repos plus a live web search, then synthesizes a fresh answer. More costly than the default
|
|
2787
|
+
5. Answer using the fetched docs
|
|
2765
2788
|
`;
|
|
2766
2789
|
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.
|
|
2767
2790
|
|
|
@@ -2772,7 +2795,8 @@ Do not use for: refactoring, writing scripts from scratch, debugging business lo
|
|
|
2772
2795
|
1. Resolve library: \`npx ctx7@latest library <name> "<user's question>"\`
|
|
2773
2796
|
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")
|
|
2774
2797
|
3. Fetch docs: \`npx ctx7@latest docs <libraryId> "<user's question>"\`
|
|
2775
|
-
4.
|
|
2798
|
+
4. If you weren't satisfied with the answer, re-run the same command with \`--research\`. This retries with sandboxed agents that git-pull the actual source repos plus a live web search, then synthesizes a fresh answer. More costly than the default
|
|
2799
|
+
5. Answer using the fetched documentation
|
|
2776
2800
|
|
|
2777
2801
|
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.
|
|
2778
2802
|
|
|
@@ -2886,6 +2910,24 @@ function mergeServerEntry(existing, configKey, serverName, entry) {
|
|
|
2886
2910
|
alreadyExists
|
|
2887
2911
|
};
|
|
2888
2912
|
}
|
|
2913
|
+
function removeServerEntry(existing, configKey, serverName) {
|
|
2914
|
+
const section = existing[configKey];
|
|
2915
|
+
if (!section || typeof section !== "object" || Array.isArray(section)) {
|
|
2916
|
+
return { config: existing, removed: false };
|
|
2917
|
+
}
|
|
2918
|
+
const current = section;
|
|
2919
|
+
if (!(serverName in current)) {
|
|
2920
|
+
return { config: existing, removed: false };
|
|
2921
|
+
}
|
|
2922
|
+
const rest = Object.fromEntries(Object.entries(current).filter(([key]) => key !== serverName));
|
|
2923
|
+
const next = { ...existing };
|
|
2924
|
+
if (Object.keys(rest).length === 0) {
|
|
2925
|
+
delete next[configKey];
|
|
2926
|
+
} else {
|
|
2927
|
+
next[configKey] = rest;
|
|
2928
|
+
}
|
|
2929
|
+
return { config: next, removed: true };
|
|
2930
|
+
}
|
|
2889
2931
|
async function resolveMcpPath(candidates) {
|
|
2890
2932
|
for (const candidate of candidates) {
|
|
2891
2933
|
try {
|
|
@@ -2900,6 +2942,14 @@ async function writeJsonConfig(filePath, config) {
|
|
|
2900
2942
|
await mkdir3(dirname4(filePath), { recursive: true });
|
|
2901
2943
|
await writeFile3(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
2902
2944
|
}
|
|
2945
|
+
async function readTomlServerExists(filePath, serverName) {
|
|
2946
|
+
try {
|
|
2947
|
+
const raw = await readFile3(filePath, "utf-8");
|
|
2948
|
+
return raw.includes(`[mcp_servers.${serverName}]`);
|
|
2949
|
+
} catch {
|
|
2950
|
+
return false;
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2903
2953
|
function buildTomlServerBlock(serverName, entry) {
|
|
2904
2954
|
const lines = [`[mcp_servers.${serverName}]`];
|
|
2905
2955
|
const headers = entry.headers;
|
|
@@ -2954,6 +3004,39 @@ async function appendTomlServer(filePath, serverName, entry) {
|
|
|
2954
3004
|
}
|
|
2955
3005
|
return { alreadyExists };
|
|
2956
3006
|
}
|
|
3007
|
+
async function removeTomlServer(filePath, serverName) {
|
|
3008
|
+
let existing = "";
|
|
3009
|
+
try {
|
|
3010
|
+
existing = await readFile3(filePath, "utf-8");
|
|
3011
|
+
} catch {
|
|
3012
|
+
return { removed: false };
|
|
3013
|
+
}
|
|
3014
|
+
const sectionHeader = `[mcp_servers.${serverName}]`;
|
|
3015
|
+
const startIdx = existing.indexOf(sectionHeader);
|
|
3016
|
+
if (startIdx === -1) {
|
|
3017
|
+
return { removed: false };
|
|
3018
|
+
}
|
|
3019
|
+
const subPrefix = `[mcp_servers.${serverName}.`;
|
|
3020
|
+
const rest = existing.slice(startIdx + sectionHeader.length);
|
|
3021
|
+
let endOffset = rest.length;
|
|
3022
|
+
const re = /^\[/gm;
|
|
3023
|
+
let match;
|
|
3024
|
+
while ((match = re.exec(rest)) !== null) {
|
|
3025
|
+
const lineEnd = rest.indexOf("\n", match.index);
|
|
3026
|
+
const line = rest.slice(match.index, lineEnd === -1 ? void 0 : lineEnd);
|
|
3027
|
+
if (!line.startsWith(subPrefix)) {
|
|
3028
|
+
endOffset = match.index;
|
|
3029
|
+
break;
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
const rawBefore = existing.slice(0, startIdx).replace(/\n+$/, "");
|
|
3033
|
+
const rawAfter = existing.slice(startIdx + sectionHeader.length + endOffset).replace(/^\n+/, "");
|
|
3034
|
+
const content = [rawBefore, rawAfter].filter(Boolean).join("\n\n");
|
|
3035
|
+
await mkdir3(dirname4(filePath), { recursive: true });
|
|
3036
|
+
await writeFile3(filePath, content.length > 0 ? `${content}
|
|
3037
|
+
` : "", "utf-8");
|
|
3038
|
+
return { removed: true };
|
|
3039
|
+
}
|
|
2957
3040
|
|
|
2958
3041
|
// src/commands/setup.ts
|
|
2959
3042
|
var CHECKBOX_THEME = {
|
|
@@ -3293,9 +3376,344 @@ async function setupCommand(options) {
|
|
|
3293
3376
|
}
|
|
3294
3377
|
}
|
|
3295
3378
|
|
|
3296
|
-
// src/commands/
|
|
3379
|
+
// src/commands/remove.ts
|
|
3297
3380
|
import pc9 from "picocolors";
|
|
3298
3381
|
import ora5 from "ora";
|
|
3382
|
+
import { join as join10 } from "path";
|
|
3383
|
+
import { access as access4, readFile as readFile5, rm as rm3, writeFile as writeFile5 } from "fs/promises";
|
|
3384
|
+
var CHECKBOX_THEME2 = {
|
|
3385
|
+
style: {
|
|
3386
|
+
highlight: (text) => pc9.green(text),
|
|
3387
|
+
disabledChoice: (text) => ` ${pc9.dim("\u25EF")} ${pc9.dim(text)}`
|
|
3388
|
+
}
|
|
3389
|
+
};
|
|
3390
|
+
var CONTEXT7_SECTION_MARKER = "<!-- context7 -->";
|
|
3391
|
+
var MODE_SKILLS = {
|
|
3392
|
+
mcp: ["context7-mcp"],
|
|
3393
|
+
cli: ["find-docs"]
|
|
3394
|
+
};
|
|
3395
|
+
var MODE_LABELS = {
|
|
3396
|
+
mcp: "MCP",
|
|
3397
|
+
cli: "CLI + Skills"
|
|
3398
|
+
};
|
|
3399
|
+
function registerRemoveCommand(program2) {
|
|
3400
|
+
program2.command("remove").alias("uninstall").description("Remove Context7 setup from your AI coding agent").option("--claude", "Remove from Claude Code").option("--cursor", "Remove from Cursor").option("--opencode", "Remove from OpenCode").option("--codex", "Remove from Codex").option("--gemini", "Remove from Gemini CLI").option("--all", "Remove both MCP setup and CLI + Skills setup").option("--mcp", "Remove MCP setup").option("--cli", "Remove CLI + Skills setup").option("-p, --project", "Remove from the current project instead of global config").option("-y, --yes", "Skip confirmation prompts").action(async (options) => {
|
|
3401
|
+
await removeCommand2(options);
|
|
3402
|
+
});
|
|
3403
|
+
}
|
|
3404
|
+
function getSelectedAgents2(options) {
|
|
3405
|
+
const agents2 = [];
|
|
3406
|
+
if (options.claude) agents2.push("claude");
|
|
3407
|
+
if (options.cursor) agents2.push("cursor");
|
|
3408
|
+
if (options.opencode) agents2.push("opencode");
|
|
3409
|
+
if (options.codex) agents2.push("codex");
|
|
3410
|
+
if (options.gemini) agents2.push("gemini");
|
|
3411
|
+
return agents2;
|
|
3412
|
+
}
|
|
3413
|
+
async function promptAgents2(detected) {
|
|
3414
|
+
const choices = detected.map((name) => ({
|
|
3415
|
+
name: SETUP_AGENT_NAMES[name],
|
|
3416
|
+
value: name
|
|
3417
|
+
}));
|
|
3418
|
+
if (detected.length > 0) {
|
|
3419
|
+
log.dim(`Detected: ${detected.map((agent) => SETUP_AGENT_NAMES[agent]).join(", ")}`);
|
|
3420
|
+
}
|
|
3421
|
+
try {
|
|
3422
|
+
return await checkboxWithHover(
|
|
3423
|
+
{
|
|
3424
|
+
message: "Which agents do you want to remove Context7 setup from?",
|
|
3425
|
+
choices,
|
|
3426
|
+
loop: false,
|
|
3427
|
+
theme: CHECKBOX_THEME2
|
|
3428
|
+
},
|
|
3429
|
+
{ getName: (agent) => SETUP_AGENT_NAMES[agent] }
|
|
3430
|
+
);
|
|
3431
|
+
} catch {
|
|
3432
|
+
return null;
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
async function promptModes(modes) {
|
|
3436
|
+
const choices = modes.map((mode) => ({
|
|
3437
|
+
name: MODE_LABELS[mode],
|
|
3438
|
+
value: mode
|
|
3439
|
+
}));
|
|
3440
|
+
try {
|
|
3441
|
+
return await checkboxWithHover(
|
|
3442
|
+
{
|
|
3443
|
+
message: "Which Context7 setup modes do you want to remove?",
|
|
3444
|
+
choices,
|
|
3445
|
+
loop: false,
|
|
3446
|
+
theme: CHECKBOX_THEME2
|
|
3447
|
+
},
|
|
3448
|
+
{ getName: (mode) => MODE_LABELS[mode] }
|
|
3449
|
+
);
|
|
3450
|
+
} catch {
|
|
3451
|
+
return null;
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
async function resolveAgents2(options, scope) {
|
|
3455
|
+
const explicit = getSelectedAgents2(options);
|
|
3456
|
+
if (explicit.length > 0) return explicit;
|
|
3457
|
+
const detected = await detectConfiguredAgents(scope);
|
|
3458
|
+
if (detected.length > 0 && options.yes) return detected;
|
|
3459
|
+
if (detected.length === 0) {
|
|
3460
|
+
log.warn(
|
|
3461
|
+
"No Context7 setup detected. Pass --claude, --cursor, --opencode, --codex, or --gemini."
|
|
3462
|
+
);
|
|
3463
|
+
return [];
|
|
3464
|
+
}
|
|
3465
|
+
log.blank();
|
|
3466
|
+
const selected = await promptAgents2(detected);
|
|
3467
|
+
if (!selected) {
|
|
3468
|
+
log.warn("Remove cancelled");
|
|
3469
|
+
return [];
|
|
3470
|
+
}
|
|
3471
|
+
return selected;
|
|
3472
|
+
}
|
|
3473
|
+
function resolveFlagModes(options) {
|
|
3474
|
+
if (options.all) return ["mcp", "cli"];
|
|
3475
|
+
const selected = [];
|
|
3476
|
+
if (options.mcp) selected.push("mcp");
|
|
3477
|
+
if (options.cli) selected.push("cli");
|
|
3478
|
+
return selected.length > 0 ? selected : ["mcp", "cli"];
|
|
3479
|
+
}
|
|
3480
|
+
async function pathExists2(path2) {
|
|
3481
|
+
try {
|
|
3482
|
+
await access4(path2);
|
|
3483
|
+
return true;
|
|
3484
|
+
} catch {
|
|
3485
|
+
return false;
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
async function hasMcpConfig(agentName, scope) {
|
|
3489
|
+
const agent = getAgent(agentName);
|
|
3490
|
+
const candidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((path2) => join10(process.cwd(), path2));
|
|
3491
|
+
const mcpPath = await resolveMcpPath(candidates);
|
|
3492
|
+
if (mcpPath.endsWith(".toml")) {
|
|
3493
|
+
return readTomlServerExists(mcpPath, "context7");
|
|
3494
|
+
}
|
|
3495
|
+
const existing = await readJsonConfig(mcpPath);
|
|
3496
|
+
const section = existing[agent.mcp.configKey];
|
|
3497
|
+
return !!section && typeof section === "object" && !Array.isArray(section) && "context7" in section;
|
|
3498
|
+
}
|
|
3499
|
+
async function hasRule(agentName, scope) {
|
|
3500
|
+
const agent = getAgent(agentName);
|
|
3501
|
+
const rule = agent.rule;
|
|
3502
|
+
if (rule.kind === "file") {
|
|
3503
|
+
const ruleDir = scope === "global" ? rule.dir("global") : join10(process.cwd(), rule.dir("project"));
|
|
3504
|
+
return pathExists2(join10(ruleDir, rule.filename));
|
|
3505
|
+
}
|
|
3506
|
+
const filePath = scope === "global" ? rule.file("global") : join10(process.cwd(), rule.file("project"));
|
|
3507
|
+
try {
|
|
3508
|
+
const existing = await readFile5(filePath, "utf-8");
|
|
3509
|
+
return existing.includes(CONTEXT7_SECTION_MARKER);
|
|
3510
|
+
} catch {
|
|
3511
|
+
return false;
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
async function hasSkill(agentName, scope, skillName) {
|
|
3515
|
+
const agent = getAgent(agentName);
|
|
3516
|
+
const skillsDir = scope === "global" ? agent.skill.dir("global") : join10(process.cwd(), agent.skill.dir("project"));
|
|
3517
|
+
return pathExists2(join10(skillsDir, skillName));
|
|
3518
|
+
}
|
|
3519
|
+
async function detectAvailableModes(agents2, scope) {
|
|
3520
|
+
let hasMcpArtifacts = false;
|
|
3521
|
+
let hasCliArtifacts = false;
|
|
3522
|
+
let hasRuleArtifacts = false;
|
|
3523
|
+
for (const agent of agents2) {
|
|
3524
|
+
hasMcpArtifacts = hasMcpArtifacts || await hasMcpConfig(agent, scope) || await hasSkill(agent, scope, MODE_SKILLS.mcp[0]);
|
|
3525
|
+
hasCliArtifacts = hasCliArtifacts || await hasSkill(agent, scope, MODE_SKILLS.cli[0]);
|
|
3526
|
+
hasRuleArtifacts = hasRuleArtifacts || await hasRule(agent, scope);
|
|
3527
|
+
}
|
|
3528
|
+
const modes = [];
|
|
3529
|
+
if (hasMcpArtifacts) modes.push("mcp");
|
|
3530
|
+
if (hasCliArtifacts) modes.push("cli");
|
|
3531
|
+
if (modes.length === 0 && hasRuleArtifacts) {
|
|
3532
|
+
return ["mcp", "cli"];
|
|
3533
|
+
}
|
|
3534
|
+
return modes;
|
|
3535
|
+
}
|
|
3536
|
+
async function hasAnyContext7Artifacts(agent, scope) {
|
|
3537
|
+
return await hasMcpConfig(agent, scope) || await hasRule(agent, scope) || await hasSkill(agent, scope, MODE_SKILLS.mcp[0]) || await hasSkill(agent, scope, MODE_SKILLS.cli[0]);
|
|
3538
|
+
}
|
|
3539
|
+
async function detectConfiguredAgents(scope) {
|
|
3540
|
+
const detected = [];
|
|
3541
|
+
for (const agent of ALL_AGENT_NAMES) {
|
|
3542
|
+
if (await hasAnyContext7Artifacts(agent, scope)) {
|
|
3543
|
+
detected.push(agent);
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
return detected;
|
|
3547
|
+
}
|
|
3548
|
+
async function resolveModes(options, agents2, scope) {
|
|
3549
|
+
if (options.all || options.mcp || options.cli) {
|
|
3550
|
+
return resolveFlagModes(options);
|
|
3551
|
+
}
|
|
3552
|
+
const detectedModes = await detectAvailableModes(agents2, scope);
|
|
3553
|
+
if (detectedModes.length <= 1) {
|
|
3554
|
+
return detectedModes.length === 1 ? detectedModes : ["mcp", "cli"];
|
|
3555
|
+
}
|
|
3556
|
+
if (options.yes) {
|
|
3557
|
+
return detectedModes;
|
|
3558
|
+
}
|
|
3559
|
+
log.blank();
|
|
3560
|
+
const selected = await promptModes(detectedModes);
|
|
3561
|
+
if (!selected) {
|
|
3562
|
+
log.warn("Remove cancelled");
|
|
3563
|
+
return [];
|
|
3564
|
+
}
|
|
3565
|
+
return selected;
|
|
3566
|
+
}
|
|
3567
|
+
async function uninstallMcp(agentName, scope) {
|
|
3568
|
+
const agent = getAgent(agentName);
|
|
3569
|
+
const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((path2) => join10(process.cwd(), path2));
|
|
3570
|
+
const mcpPath = await resolveMcpPath(mcpCandidates);
|
|
3571
|
+
try {
|
|
3572
|
+
if (mcpPath.endsWith(".toml")) {
|
|
3573
|
+
const { removed: removed2 } = await removeTomlServer(mcpPath, "context7");
|
|
3574
|
+
return { status: removed2 ? "removed" : "not found", path: mcpPath };
|
|
3575
|
+
}
|
|
3576
|
+
const existing = await readJsonConfig(mcpPath);
|
|
3577
|
+
const { config, removed } = removeServerEntry(existing, agent.mcp.configKey, "context7");
|
|
3578
|
+
if (removed) {
|
|
3579
|
+
await writeJsonConfig(mcpPath, config);
|
|
3580
|
+
}
|
|
3581
|
+
return { status: removed ? "removed" : "not found", path: mcpPath };
|
|
3582
|
+
} catch (err) {
|
|
3583
|
+
return { status: `failed: ${err instanceof Error ? err.message : String(err)}`, path: mcpPath };
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
async function uninstallRule(agentName, scope) {
|
|
3587
|
+
const agent = getAgent(agentName);
|
|
3588
|
+
const rule = agent.rule;
|
|
3589
|
+
if (rule.kind === "file") {
|
|
3590
|
+
const rulePath = scope === "global" ? rule.dir("global") : join10(process.cwd(), rule.dir("project"));
|
|
3591
|
+
const targetPath = join10(rulePath, rule.filename);
|
|
3592
|
+
try {
|
|
3593
|
+
await rm3(targetPath);
|
|
3594
|
+
return { status: "removed", path: targetPath };
|
|
3595
|
+
} catch (err) {
|
|
3596
|
+
const error = err;
|
|
3597
|
+
if (error.code === "ENOENT") return { status: "not found", path: targetPath };
|
|
3598
|
+
return { status: `failed: ${error.message}`, path: targetPath };
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
const filePath = scope === "global" ? rule.file("global") : join10(process.cwd(), rule.file("project"));
|
|
3602
|
+
try {
|
|
3603
|
+
const existing = await readFile5(filePath, "utf-8");
|
|
3604
|
+
if (!existing.includes(CONTEXT7_SECTION_MARKER)) {
|
|
3605
|
+
return { status: "not found", path: filePath };
|
|
3606
|
+
}
|
|
3607
|
+
const escapedMarker = CONTEXT7_SECTION_MARKER.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3608
|
+
const updated = existing.replace(new RegExp(`\\n?${escapedMarker}\\n[\\s\\S]*?${escapedMarker}\\n?`, "m"), "").replace(/\n{3,}/g, "\n\n").replace(/^\n+/, "").trimEnd();
|
|
3609
|
+
if (updated.length === 0) {
|
|
3610
|
+
await rm3(filePath);
|
|
3611
|
+
} else {
|
|
3612
|
+
await writeFile5(filePath, `${updated}
|
|
3613
|
+
`, "utf-8");
|
|
3614
|
+
}
|
|
3615
|
+
return { status: "removed", path: filePath };
|
|
3616
|
+
} catch (err) {
|
|
3617
|
+
const error = err;
|
|
3618
|
+
if (error.code === "ENOENT") return { status: "not found", path: filePath };
|
|
3619
|
+
return { status: `failed: ${error.message}`, path: filePath };
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
async function uninstallSkills(agentName, scope, skillNames) {
|
|
3623
|
+
const agent = getAgent(agentName);
|
|
3624
|
+
const skillsDir = scope === "global" ? agent.skill.dir("global") : join10(process.cwd(), agent.skill.dir("project"));
|
|
3625
|
+
const results = [];
|
|
3626
|
+
for (const skillName of skillNames) {
|
|
3627
|
+
const skillPath = join10(skillsDir, skillName);
|
|
3628
|
+
try {
|
|
3629
|
+
await rm3(skillPath, { recursive: true });
|
|
3630
|
+
results.push({ name: skillName, status: "removed", path: skillPath });
|
|
3631
|
+
} catch (err) {
|
|
3632
|
+
const error = err;
|
|
3633
|
+
if (error.code === "ENOENT") {
|
|
3634
|
+
results.push({ name: skillName, status: "not found", path: skillPath });
|
|
3635
|
+
} else {
|
|
3636
|
+
results.push({ name: skillName, status: `failed: ${error.message}`, path: skillPath });
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
return results;
|
|
3641
|
+
}
|
|
3642
|
+
async function uninstallAgent(agentName, scope, modes) {
|
|
3643
|
+
const result = { agent: getAgent(agentName).displayName };
|
|
3644
|
+
const skillNames = Array.from(new Set(modes.flatMap((mode) => [...MODE_SKILLS[mode]])));
|
|
3645
|
+
const shouldRemoveRule = modes.includes("mcp") || modes.includes("cli");
|
|
3646
|
+
if (modes.includes("mcp")) {
|
|
3647
|
+
result.mcp = await uninstallMcp(agentName, scope);
|
|
3648
|
+
}
|
|
3649
|
+
if (shouldRemoveRule) {
|
|
3650
|
+
result.rule = await uninstallRule(agentName, scope);
|
|
3651
|
+
}
|
|
3652
|
+
if (skillNames.length > 0) {
|
|
3653
|
+
result.skills = await uninstallSkills(agentName, scope, skillNames);
|
|
3654
|
+
}
|
|
3655
|
+
return result;
|
|
3656
|
+
}
|
|
3657
|
+
function iconForStatus(status) {
|
|
3658
|
+
if (status === "removed") return pc9.green("-");
|
|
3659
|
+
if (status === "not found") return pc9.dim("~");
|
|
3660
|
+
return pc9.red("!");
|
|
3661
|
+
}
|
|
3662
|
+
function printResults(results, modes) {
|
|
3663
|
+
log.blank();
|
|
3664
|
+
const shouldPrintRule = modes.includes("mcp") || modes.includes("cli");
|
|
3665
|
+
let hasVisibleResults = false;
|
|
3666
|
+
for (const result of results) {
|
|
3667
|
+
const visibleSkills = result.skills?.filter((skill) => skill.status !== "not found") ?? [];
|
|
3668
|
+
const showMcp = modes.includes("mcp") && result.mcp && result.mcp.status !== "not found";
|
|
3669
|
+
const showRule = shouldPrintRule && result.rule && result.rule.status !== "not found";
|
|
3670
|
+
if (!showMcp && !showRule && visibleSkills.length === 0) {
|
|
3671
|
+
continue;
|
|
3672
|
+
}
|
|
3673
|
+
hasVisibleResults = true;
|
|
3674
|
+
log.plain(` ${pc9.bold(result.agent)}`);
|
|
3675
|
+
if (showMcp && result.mcp) {
|
|
3676
|
+
log.plain(` ${iconForStatus(result.mcp.status)} MCP config ${result.mcp.status}`);
|
|
3677
|
+
log.plain(` ${pc9.dim(result.mcp.path)}`);
|
|
3678
|
+
}
|
|
3679
|
+
if (showRule && result.rule) {
|
|
3680
|
+
log.plain(` ${iconForStatus(result.rule.status)} Rule ${result.rule.status}`);
|
|
3681
|
+
log.plain(` ${pc9.dim(result.rule.path)}`);
|
|
3682
|
+
}
|
|
3683
|
+
for (const skill of visibleSkills) {
|
|
3684
|
+
log.plain(` ${iconForStatus(skill.status)} Skill ${skill.name} ${skill.status}`);
|
|
3685
|
+
log.plain(` ${pc9.dim(skill.path)}`);
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
if (hasVisibleResults) {
|
|
3689
|
+
log.blank();
|
|
3690
|
+
} else {
|
|
3691
|
+
log.plain(` ${pc9.dim("No matching Context7 setup was found to remove.")}`);
|
|
3692
|
+
log.blank();
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
async function removeCommand2(options) {
|
|
3696
|
+
trackEvent("command", { name: "remove" });
|
|
3697
|
+
const scope = options.project ? "project" : "global";
|
|
3698
|
+
const agents2 = await resolveAgents2(options, scope);
|
|
3699
|
+
if (agents2.length === 0) return;
|
|
3700
|
+
const modes = await resolveModes(options, agents2, scope);
|
|
3701
|
+
if (modes.length === 0) return;
|
|
3702
|
+
log.blank();
|
|
3703
|
+
const spinner = ora5("Removing Context7 setup...").start();
|
|
3704
|
+
const results = [];
|
|
3705
|
+
for (const agentName of agents2) {
|
|
3706
|
+
spinner.text = `Cleaning up ${getAgent(agentName).displayName}...`;
|
|
3707
|
+
results.push(await uninstallAgent(agentName, scope, modes));
|
|
3708
|
+
}
|
|
3709
|
+
spinner.succeed("Context7 cleanup complete");
|
|
3710
|
+
printResults(results, modes);
|
|
3711
|
+
trackEvent("remove", { agents: agents2, scope, modes });
|
|
3712
|
+
}
|
|
3713
|
+
|
|
3714
|
+
// src/commands/docs.ts
|
|
3715
|
+
import pc10 from "picocolors";
|
|
3716
|
+
import ora6 from "ora";
|
|
3299
3717
|
var isTTY = process.stdout.isTTY;
|
|
3300
3718
|
function getReputationLabel(score) {
|
|
3301
3719
|
if (score === void 0 || score < 0) return "Unknown";
|
|
@@ -3310,28 +3728,28 @@ function getAccessToken() {
|
|
|
3310
3728
|
}
|
|
3311
3729
|
function formatLibraryResult(lib, index) {
|
|
3312
3730
|
const lines = [];
|
|
3313
|
-
lines.push(`${
|
|
3314
|
-
lines.push(` ${
|
|
3731
|
+
lines.push(`${pc10.dim(`${index + 1}.`)} ${pc10.bold(`Title: ${lib.title}`)}`);
|
|
3732
|
+
lines.push(` ${pc10.cyan(`Context7-compatible library ID: ${lib.id}`)}`);
|
|
3315
3733
|
if (lib.description) {
|
|
3316
|
-
lines.push(` ${
|
|
3734
|
+
lines.push(` ${pc10.dim(`Description: ${lib.description}`)}`);
|
|
3317
3735
|
}
|
|
3318
3736
|
if (lib.totalSnippets) {
|
|
3319
|
-
lines.push(` ${
|
|
3737
|
+
lines.push(` ${pc10.dim(`Code Snippets: ${lib.totalSnippets}`)}`);
|
|
3320
3738
|
}
|
|
3321
3739
|
if (lib.trustScore !== void 0) {
|
|
3322
|
-
lines.push(` ${
|
|
3740
|
+
lines.push(` ${pc10.dim(`Source Reputation: ${getReputationLabel(lib.trustScore)}`)}`);
|
|
3323
3741
|
}
|
|
3324
3742
|
if (lib.benchmarkScore !== void 0 && lib.benchmarkScore > 0) {
|
|
3325
|
-
lines.push(` ${
|
|
3743
|
+
lines.push(` ${pc10.dim(`Benchmark Score: ${lib.benchmarkScore}`)}`);
|
|
3326
3744
|
}
|
|
3327
3745
|
if (lib.versions && lib.versions.length > 0) {
|
|
3328
|
-
lines.push(` ${
|
|
3746
|
+
lines.push(` ${pc10.dim(`Versions: ${lib.versions.join(", ")}`)}`);
|
|
3329
3747
|
}
|
|
3330
3748
|
return lines.join("\n");
|
|
3331
3749
|
}
|
|
3332
3750
|
async function resolveCommand(library, query, options) {
|
|
3333
3751
|
trackEvent("command", { name: "library" });
|
|
3334
|
-
const spinner = isTTY ?
|
|
3752
|
+
const spinner = isTTY ? ora6(`Searching for "${library}"...`).start() : null;
|
|
3335
3753
|
const accessToken = getAccessToken();
|
|
3336
3754
|
let data;
|
|
3337
3755
|
try {
|
|
@@ -3373,8 +3791,8 @@ async function resolveCommand(library, query, options) {
|
|
|
3373
3791
|
if (isTTY && results.length > 0) {
|
|
3374
3792
|
const best = results[0];
|
|
3375
3793
|
log.plain(
|
|
3376
|
-
`${
|
|
3377
|
-
${
|
|
3794
|
+
`${pc10.bold("Quick command:")}
|
|
3795
|
+
${pc10.cyan(`ctx7 docs "${best.id}" "<your question>"`)}`
|
|
3378
3796
|
);
|
|
3379
3797
|
log.blank();
|
|
3380
3798
|
}
|
|
@@ -3388,12 +3806,19 @@ async function queryCommand(libraryId, query, options) {
|
|
|
3388
3806
|
process.exitCode = 1;
|
|
3389
3807
|
return;
|
|
3390
3808
|
}
|
|
3391
|
-
const spinner = isTTY ?
|
|
3809
|
+
const spinner = isTTY ? ora6(
|
|
3810
|
+
options.research ? `Researching "${libraryId}"...` : `Fetching docs for "${libraryId}"...`
|
|
3811
|
+
).start() : null;
|
|
3392
3812
|
const accessToken = getAccessToken();
|
|
3393
3813
|
const outputType = options.json ? "json" : "txt";
|
|
3394
3814
|
let result;
|
|
3395
3815
|
try {
|
|
3396
|
-
result = await getLibraryContext(
|
|
3816
|
+
result = await getLibraryContext(
|
|
3817
|
+
libraryId,
|
|
3818
|
+
query,
|
|
3819
|
+
{ type: outputType, researchMode: options.research },
|
|
3820
|
+
accessToken
|
|
3821
|
+
);
|
|
3397
3822
|
} catch (err) {
|
|
3398
3823
|
spinner?.fail(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3399
3824
|
if (!spinner) log.error(err instanceof Error ? err.message : String(err));
|
|
@@ -3410,8 +3835,8 @@ async function queryCommand(libraryId, query, options) {
|
|
|
3410
3835
|
if (ctx.redirectUrl) {
|
|
3411
3836
|
spinner?.warn("Library has been redirected");
|
|
3412
3837
|
if (!spinner) log.warn("Library has been redirected");
|
|
3413
|
-
log.info(`New ID: ${
|
|
3414
|
-
log.info(`Run: ${
|
|
3838
|
+
log.info(`New ID: ${pc10.cyan(ctx.redirectUrl)}`);
|
|
3839
|
+
log.info(`Run: ${pc10.cyan(`ctx7 docs "${ctx.redirectUrl}" "${query}"`)}`);
|
|
3415
3840
|
process.exitCode = 1;
|
|
3416
3841
|
return;
|
|
3417
3842
|
}
|
|
@@ -3434,7 +3859,7 @@ async function queryCommand(libraryId, query, options) {
|
|
|
3434
3859
|
log.blank();
|
|
3435
3860
|
if (ctx.codeSnippets) {
|
|
3436
3861
|
for (const snippet of ctx.codeSnippets) {
|
|
3437
|
-
log.plain(
|
|
3862
|
+
log.plain(pc10.bold(snippet.codeTitle));
|
|
3438
3863
|
if (snippet.codeDescription) log.dim(snippet.codeDescription);
|
|
3439
3864
|
log.blank();
|
|
3440
3865
|
for (const code of snippet.codeList) {
|
|
@@ -3447,7 +3872,7 @@ async function queryCommand(libraryId, query, options) {
|
|
|
3447
3872
|
}
|
|
3448
3873
|
if (ctx.infoSnippets) {
|
|
3449
3874
|
for (const snippet of ctx.infoSnippets) {
|
|
3450
|
-
if (snippet.breadcrumb) log.plain(
|
|
3875
|
+
if (snippet.breadcrumb) log.plain(pc10.bold(snippet.breadcrumb));
|
|
3451
3876
|
log.plain(snippet.content);
|
|
3452
3877
|
log.blank();
|
|
3453
3878
|
}
|
|
@@ -3457,15 +3882,342 @@ function registerDocsCommands(program2) {
|
|
|
3457
3882
|
program2.command("library").argument("<name>", "Library name to search for").argument("[query]", "Question or task for relevance ranking").option("--json", "Output as JSON").description("Resolve a library name to a Context7 library ID").action(async (name, query, options) => {
|
|
3458
3883
|
await resolveCommand(name, query, options);
|
|
3459
3884
|
});
|
|
3460
|
-
program2.command("docs").argument("<libraryId>", "Context7 library ID (e.g., /facebook/react)").argument("<query>", "Question or task to get docs for").option("--json", "Output as JSON").
|
|
3461
|
-
|
|
3885
|
+
program2.command("docs").argument("<libraryId>", "Context7 library ID (e.g., /facebook/react)").argument("<query>", "Question or task to get docs for").option("--json", "Output as JSON").option(
|
|
3886
|
+
"--research",
|
|
3887
|
+
"Retry the query with deep research: spins up sandboxed agents that read the actual source repos (git pull + inspect) and runs a live web search, then synthesizes a fresh answer. Use on retry if you weren't satisfied with the first answer. More costly than the default."
|
|
3888
|
+
).description("Query documentation for a library").action(
|
|
3889
|
+
async (libraryId, query, options) => {
|
|
3890
|
+
await queryCommand(libraryId, query, options);
|
|
3891
|
+
}
|
|
3892
|
+
);
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3895
|
+
// src/commands/upgrade.ts
|
|
3896
|
+
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
3897
|
+
import { spawn as spawn2 } from "child_process";
|
|
3898
|
+
import pc11 from "picocolors";
|
|
3899
|
+
|
|
3900
|
+
// src/utils/update-check.ts
|
|
3901
|
+
import { homedir as homedir6 } from "os";
|
|
3902
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
3903
|
+
import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
3904
|
+
var DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3905
|
+
var UPDATE_STATE_FILE = join11(homedir6(), ".context7", "cli-state.json");
|
|
3906
|
+
function getStateFilePath(stateFile) {
|
|
3907
|
+
return stateFile ?? UPDATE_STATE_FILE;
|
|
3908
|
+
}
|
|
3909
|
+
async function readUpdateState(stateFile) {
|
|
3910
|
+
try {
|
|
3911
|
+
const raw = await readFile6(getStateFilePath(stateFile), "utf-8");
|
|
3912
|
+
return JSON.parse(raw);
|
|
3913
|
+
} catch {
|
|
3914
|
+
return {};
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
async function writeUpdateState(state, stateFile) {
|
|
3918
|
+
const path2 = getStateFilePath(stateFile);
|
|
3919
|
+
await mkdir5(dirname6(path2), { recursive: true });
|
|
3920
|
+
await writeFile6(path2, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
3921
|
+
}
|
|
3922
|
+
function compareVersions(a, b) {
|
|
3923
|
+
const normalize = (version) => version.split("-", 1)[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
3924
|
+
const left = normalize(a);
|
|
3925
|
+
const right = normalize(b);
|
|
3926
|
+
const max = Math.max(left.length, right.length);
|
|
3927
|
+
for (let i = 0; i < max; i++) {
|
|
3928
|
+
const diff = (left[i] ?? 0) - (right[i] ?? 0);
|
|
3929
|
+
if (diff !== 0) return diff;
|
|
3930
|
+
}
|
|
3931
|
+
return 0;
|
|
3932
|
+
}
|
|
3933
|
+
function detectInstallMethod(env = process.env) {
|
|
3934
|
+
const execPath = env.npm_execpath?.toLowerCase() ?? "";
|
|
3935
|
+
const npmCommand = env.npm_command?.toLowerCase() ?? "";
|
|
3936
|
+
const userAgent = env.npm_config_user_agent?.toLowerCase() ?? "";
|
|
3937
|
+
if (execPath.includes("pnpm") && npmCommand === "dlx") return "pnpm-dlx";
|
|
3938
|
+
if (execPath.includes("pnpm")) return "pnpm-global";
|
|
3939
|
+
if (execPath.includes("bun") && npmCommand === "x") return "bunx";
|
|
3940
|
+
if (execPath.includes("bun")) return "bun-global";
|
|
3941
|
+
if (execPath.includes("npm") && npmCommand === "exec") return "npx";
|
|
3942
|
+
if (execPath.includes("npm")) return "npm-global";
|
|
3943
|
+
if (userAgent.startsWith("pnpm/")) return "pnpm-global";
|
|
3944
|
+
if (userAgent.startsWith("bun/")) return "bun-global";
|
|
3945
|
+
if (userAgent.startsWith("npm/")) return "npm-global";
|
|
3946
|
+
return "unknown";
|
|
3947
|
+
}
|
|
3948
|
+
function getUpgradePlan(installMethod = detectInstallMethod(), packageName = NAME) {
|
|
3949
|
+
switch (installMethod) {
|
|
3950
|
+
case "pnpm-global":
|
|
3951
|
+
return {
|
|
3952
|
+
installMethod,
|
|
3953
|
+
command: "pnpm",
|
|
3954
|
+
args: ["add", "-g", `${packageName}@latest`],
|
|
3955
|
+
displayCommand: `pnpm add -g ${packageName}@latest`,
|
|
3956
|
+
canRun: true,
|
|
3957
|
+
needsExplicitVersion: false
|
|
3958
|
+
};
|
|
3959
|
+
case "bun-global":
|
|
3960
|
+
return {
|
|
3961
|
+
installMethod,
|
|
3962
|
+
command: "bun",
|
|
3963
|
+
args: ["add", "-g", `${packageName}@latest`],
|
|
3964
|
+
displayCommand: `bun add -g ${packageName}@latest`,
|
|
3965
|
+
canRun: true,
|
|
3966
|
+
needsExplicitVersion: false
|
|
3967
|
+
};
|
|
3968
|
+
case "npx":
|
|
3969
|
+
return {
|
|
3970
|
+
installMethod,
|
|
3971
|
+
command: "npx",
|
|
3972
|
+
args: [`${packageName}@latest`],
|
|
3973
|
+
displayCommand: `npx ${packageName}@latest <command>`,
|
|
3974
|
+
canRun: false,
|
|
3975
|
+
needsExplicitVersion: true
|
|
3976
|
+
};
|
|
3977
|
+
case "pnpm-dlx":
|
|
3978
|
+
return {
|
|
3979
|
+
installMethod,
|
|
3980
|
+
command: "pnpm",
|
|
3981
|
+
args: ["dlx", `${packageName}@latest`],
|
|
3982
|
+
displayCommand: `pnpm dlx ${packageName}@latest <command>`,
|
|
3983
|
+
canRun: false,
|
|
3984
|
+
needsExplicitVersion: true
|
|
3985
|
+
};
|
|
3986
|
+
case "bunx":
|
|
3987
|
+
return {
|
|
3988
|
+
installMethod,
|
|
3989
|
+
command: "bunx",
|
|
3990
|
+
args: [`${packageName}@latest`],
|
|
3991
|
+
displayCommand: `bunx ${packageName}@latest <command>`,
|
|
3992
|
+
canRun: false,
|
|
3993
|
+
needsExplicitVersion: true
|
|
3994
|
+
};
|
|
3995
|
+
case "unknown":
|
|
3996
|
+
return {
|
|
3997
|
+
installMethod,
|
|
3998
|
+
command: "npm",
|
|
3999
|
+
args: ["install", "-g", `${packageName}@latest`],
|
|
4000
|
+
displayCommand: `npm install -g ${packageName}@latest`,
|
|
4001
|
+
canRun: false,
|
|
4002
|
+
needsExplicitVersion: false
|
|
4003
|
+
};
|
|
4004
|
+
case "npm-global":
|
|
4005
|
+
default:
|
|
4006
|
+
return {
|
|
4007
|
+
installMethod: "npm-global",
|
|
4008
|
+
command: "npm",
|
|
4009
|
+
args: ["install", "-g", `${packageName}@latest`],
|
|
4010
|
+
displayCommand: `npm install -g ${packageName}@latest`,
|
|
4011
|
+
canRun: true,
|
|
4012
|
+
needsExplicitVersion: false
|
|
4013
|
+
};
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
async function fetchLatestVersion(packageName = NAME) {
|
|
4017
|
+
try {
|
|
4018
|
+
const response = await fetch(
|
|
4019
|
+
`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`,
|
|
4020
|
+
{
|
|
4021
|
+
headers: { Accept: "application/json" },
|
|
4022
|
+
signal: AbortSignal.timeout(1500)
|
|
4023
|
+
}
|
|
4024
|
+
);
|
|
4025
|
+
if (!response.ok) return null;
|
|
4026
|
+
const data = await response.json();
|
|
4027
|
+
return typeof data.version === "string" ? data.version : null;
|
|
4028
|
+
} catch {
|
|
4029
|
+
return null;
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
async function checkForUpdates(options = {}) {
|
|
4033
|
+
const now = options.now ?? Date.now();
|
|
4034
|
+
const cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
4035
|
+
const stateFile = options.stateFile;
|
|
4036
|
+
const state = await readUpdateState(stateFile);
|
|
4037
|
+
const isStale = options.force || !state.lastCheckedAt || now - state.lastCheckedAt >= cacheTtlMs || !state.latestVersion;
|
|
4038
|
+
let latestVersion = state.latestVersion ?? null;
|
|
4039
|
+
if (isStale) {
|
|
4040
|
+
const fetchedVersion = await fetchLatestVersion();
|
|
4041
|
+
if (fetchedVersion) {
|
|
4042
|
+
latestVersion = fetchedVersion;
|
|
4043
|
+
await writeUpdateState(
|
|
4044
|
+
{
|
|
4045
|
+
...state,
|
|
4046
|
+
latestVersion: fetchedVersion,
|
|
4047
|
+
lastCheckedAt: now
|
|
4048
|
+
},
|
|
4049
|
+
stateFile
|
|
4050
|
+
);
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
if (!latestVersion) return null;
|
|
4054
|
+
const installMethod = detectInstallMethod();
|
|
4055
|
+
return {
|
|
4056
|
+
currentVersion: VERSION,
|
|
4057
|
+
latestVersion,
|
|
4058
|
+
updateAvailable: compareVersions(latestVersion, VERSION) > 0,
|
|
4059
|
+
installMethod,
|
|
4060
|
+
upgradePlan: getUpgradePlan(installMethod)
|
|
4061
|
+
};
|
|
4062
|
+
}
|
|
4063
|
+
async function shouldShowUpdateNotification(info, options = {}) {
|
|
4064
|
+
if (!info.updateAvailable) return false;
|
|
4065
|
+
const now = options.now ?? Date.now();
|
|
4066
|
+
const cooldownMs = options.cooldownMs ?? DEFAULT_CACHE_TTL_MS;
|
|
4067
|
+
const state = await readUpdateState(options.stateFile);
|
|
4068
|
+
if (state.notifiedVersion === info.latestVersion && state.lastNotifiedAt && now - state.lastNotifiedAt < cooldownMs) {
|
|
4069
|
+
return false;
|
|
4070
|
+
}
|
|
4071
|
+
return true;
|
|
4072
|
+
}
|
|
4073
|
+
async function markUpdateNotificationShown(latestVersion, options = {}) {
|
|
4074
|
+
const now = options.now ?? Date.now();
|
|
4075
|
+
const state = await readUpdateState(options.stateFile);
|
|
4076
|
+
await writeUpdateState(
|
|
4077
|
+
{
|
|
4078
|
+
...state,
|
|
4079
|
+
notifiedVersion: latestVersion,
|
|
4080
|
+
lastNotifiedAt: now
|
|
4081
|
+
},
|
|
4082
|
+
options.stateFile
|
|
4083
|
+
);
|
|
4084
|
+
}
|
|
4085
|
+
function shouldSkipUpdateNotifier(argv = process.argv) {
|
|
4086
|
+
return argv.includes("--json") || argv.includes("-v") || argv.includes("--version");
|
|
4087
|
+
}
|
|
4088
|
+
|
|
4089
|
+
// src/commands/upgrade.ts
|
|
4090
|
+
function registerUpgradeCommand(program2) {
|
|
4091
|
+
program2.command("upgrade").description("Check for a newer ctx7 version and upgrade when possible").option("-y, --yes", "Run the suggested upgrade command without prompting").option("--check", "Only check for updates without running the upgrade command").action(async (options) => {
|
|
4092
|
+
await upgradeCommand(options);
|
|
4093
|
+
});
|
|
4094
|
+
}
|
|
4095
|
+
function runCommand(command, args) {
|
|
4096
|
+
return new Promise((resolve2, reject) => {
|
|
4097
|
+
const child = spawn2(command, args, {
|
|
4098
|
+
stdio: "inherit",
|
|
4099
|
+
shell: process.platform === "win32"
|
|
4100
|
+
});
|
|
4101
|
+
child.on("error", reject);
|
|
4102
|
+
child.on("close", (code) => resolve2(code));
|
|
3462
4103
|
});
|
|
3463
4104
|
}
|
|
4105
|
+
async function runUpgradePlan(plan) {
|
|
4106
|
+
return runCommand(plan.command, plan.args);
|
|
4107
|
+
}
|
|
4108
|
+
function showUpgradeFailureHelp(plan) {
|
|
4109
|
+
log.info(`Try rerunning: ${pc11.cyan(plan.displayCommand)}`);
|
|
4110
|
+
const isGlobalNpmInstall = (plan.installMethod === "npm-global" || plan.installMethod === "unknown") && plan.command === "npm" && plan.args.includes("-g");
|
|
4111
|
+
const isGlobalAltInstall = (plan.installMethod === "pnpm-global" || plan.installMethod === "bun-global") && plan.args.includes("-g");
|
|
4112
|
+
if (isGlobalNpmInstall) {
|
|
4113
|
+
log.dim(
|
|
4114
|
+
"If this failed due to permissions, your global npm directory may require elevated privileges on this machine."
|
|
4115
|
+
);
|
|
4116
|
+
} else if (isGlobalAltInstall) {
|
|
4117
|
+
log.dim(
|
|
4118
|
+
"If this failed due to permissions, your global package manager install location may require additional privileges on this machine."
|
|
4119
|
+
);
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
async function maybeShowUpgradeNotice(options = {}) {
|
|
4123
|
+
const actionName = options.actionName ?? "";
|
|
4124
|
+
const argv = options.argv ?? process.argv;
|
|
4125
|
+
const isInteractive = options.isInteractive ?? Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
4126
|
+
if (!isInteractive || shouldSkipUpdateNotifier(argv) || actionName === "upgrade") {
|
|
4127
|
+
return;
|
|
4128
|
+
}
|
|
4129
|
+
const info = await checkForUpdates();
|
|
4130
|
+
if (!info || !info.updateAvailable || !await shouldShowUpdateNotification(info)) {
|
|
4131
|
+
return;
|
|
4132
|
+
}
|
|
4133
|
+
log.blank();
|
|
4134
|
+
if (info.upgradePlan.needsExplicitVersion) {
|
|
4135
|
+
log.box([
|
|
4136
|
+
`${pc11.white(pc11.bold("Update available:"))} ${pc11.green(pc11.bold(`v${info.currentVersion}`))} ${pc11.dim("->")} ${pc11.green(pc11.bold(`v${info.latestVersion}`))}`,
|
|
4137
|
+
`${pc11.white("Use")} ${pc11.yellow(pc11.bold(info.upgradePlan.displayCommand))} ${pc11.white("to run the latest version")}`
|
|
4138
|
+
]);
|
|
4139
|
+
await markUpdateNotificationShown(info.latestVersion);
|
|
4140
|
+
log.blank();
|
|
4141
|
+
return;
|
|
4142
|
+
}
|
|
4143
|
+
if (!info.upgradePlan.canRun) {
|
|
4144
|
+
log.box([
|
|
4145
|
+
`${pc11.white(pc11.bold("Update available:"))} ${pc11.green(pc11.bold(`v${info.currentVersion}`))} ${pc11.dim("->")} ${pc11.green(pc11.bold(`v${info.latestVersion}`))}`,
|
|
4146
|
+
`${pc11.white("Run")} ${pc11.yellow(pc11.bold("ctx7 upgrade"))} ${pc11.white("for update steps")}`,
|
|
4147
|
+
`${pc11.white("Or run")} ${pc11.yellow(info.upgradePlan.displayCommand)}`
|
|
4148
|
+
]);
|
|
4149
|
+
await markUpdateNotificationShown(info.latestVersion);
|
|
4150
|
+
log.blank();
|
|
4151
|
+
return;
|
|
4152
|
+
}
|
|
4153
|
+
log.box([
|
|
4154
|
+
`${pc11.white(pc11.bold("Update available:"))} ${pc11.green(pc11.bold(`v${info.currentVersion}`))} ${pc11.dim("->")} ${pc11.green(pc11.bold(`v${info.latestVersion}`))}`,
|
|
4155
|
+
`${pc11.white("Run")} ${pc11.yellow(pc11.bold("ctx7 upgrade"))} ${pc11.white("to update now")}`,
|
|
4156
|
+
`${pc11.white("Or run")} ${pc11.yellow(info.upgradePlan.displayCommand)}`
|
|
4157
|
+
]);
|
|
4158
|
+
await markUpdateNotificationShown(info.latestVersion);
|
|
4159
|
+
log.blank();
|
|
4160
|
+
}
|
|
4161
|
+
async function upgradeCommand(options) {
|
|
4162
|
+
trackEvent("command", { name: "upgrade" });
|
|
4163
|
+
const info = await checkForUpdates({ force: true });
|
|
4164
|
+
const plan = info?.upgradePlan ?? getUpgradePlan();
|
|
4165
|
+
if (!info) {
|
|
4166
|
+
log.warn("Couldn't check for updates right now.");
|
|
4167
|
+
log.info(`Try again later or run ${pc11.cyan(plan.displayCommand)} manually.`);
|
|
4168
|
+
return;
|
|
4169
|
+
}
|
|
4170
|
+
if (!info.updateAvailable) {
|
|
4171
|
+
log.success(`ctx7 is up to date (${pc11.bold(`v${VERSION}`)})`);
|
|
4172
|
+
return;
|
|
4173
|
+
}
|
|
4174
|
+
log.blank();
|
|
4175
|
+
log.info(
|
|
4176
|
+
`Update available: ${pc11.bold(`v${info.currentVersion}`)} ${pc11.dim("->")} ${pc11.bold(`v${info.latestVersion}`)}`
|
|
4177
|
+
);
|
|
4178
|
+
if (plan.needsExplicitVersion) {
|
|
4179
|
+
log.info(`You're using an ephemeral runner (${plan.installMethod}).`);
|
|
4180
|
+
log.info(`Use ${pc11.cyan(plan.displayCommand)} to run the latest version immediately.`);
|
|
4181
|
+
log.info(`Or install globally with ${pc11.cyan("npm install -g ctx7@latest")}.`);
|
|
4182
|
+
return;
|
|
4183
|
+
}
|
|
4184
|
+
if (!plan.canRun) {
|
|
4185
|
+
log.info(`Run ${pc11.cyan(plan.displayCommand)} to update your installed version.`);
|
|
4186
|
+
return;
|
|
4187
|
+
}
|
|
4188
|
+
log.info(`Upgrade command: ${pc11.cyan(plan.displayCommand)}`);
|
|
4189
|
+
if (options.check) {
|
|
4190
|
+
return;
|
|
4191
|
+
}
|
|
4192
|
+
let shouldRun = options.yes ?? false;
|
|
4193
|
+
if (!shouldRun && process.stdout.isTTY) {
|
|
4194
|
+
shouldRun = await confirm2({
|
|
4195
|
+
message: `Run ${plan.displayCommand} now?`,
|
|
4196
|
+
default: true
|
|
4197
|
+
});
|
|
4198
|
+
}
|
|
4199
|
+
if (!shouldRun) {
|
|
4200
|
+
log.dim("Upgrade skipped.");
|
|
4201
|
+
return;
|
|
4202
|
+
}
|
|
4203
|
+
log.blank();
|
|
4204
|
+
const exitCode = await runUpgradePlan(plan);
|
|
4205
|
+
if (exitCode === 0) {
|
|
4206
|
+
log.blank();
|
|
4207
|
+
log.success("Upgrade complete.");
|
|
4208
|
+
log.info(`Run ${pc11.cyan("ctx7 --version")} to verify the installed version.`);
|
|
4209
|
+
return;
|
|
4210
|
+
}
|
|
4211
|
+
log.blank();
|
|
4212
|
+
log.error(`Upgrade command exited with code ${exitCode ?? "unknown"}.`);
|
|
4213
|
+
showUpgradeFailureHelp(plan);
|
|
4214
|
+
process.exitCode = 1;
|
|
4215
|
+
}
|
|
3464
4216
|
|
|
3465
4217
|
// src/index.ts
|
|
3466
4218
|
var brand = {
|
|
3467
|
-
primary:
|
|
3468
|
-
dim:
|
|
4219
|
+
primary: pc12.green,
|
|
4220
|
+
dim: pc12.dim
|
|
3469
4221
|
};
|
|
3470
4222
|
var program = new Command();
|
|
3471
4223
|
program.name("ctx7").description("Context7 CLI - Manage AI coding skills and documentation context").version(VERSION).option("--base-url <url>").hook("preAction", (thisCommand) => {
|
|
@@ -3474,6 +4226,11 @@ program.name("ctx7").description("Context7 CLI - Manage AI coding skills and doc
|
|
|
3474
4226
|
setBaseUrl(opts.baseUrl);
|
|
3475
4227
|
setAuthBaseUrl(opts.baseUrl);
|
|
3476
4228
|
}
|
|
4229
|
+
}).hook("preAction", async (_thisCommand, actionCommand) => {
|
|
4230
|
+
await maybeShowUpgradeNotice({
|
|
4231
|
+
actionName: actionCommand.name(),
|
|
4232
|
+
argv: process.argv
|
|
4233
|
+
});
|
|
3477
4234
|
}).addHelpText(
|
|
3478
4235
|
"after",
|
|
3479
4236
|
`
|
|
@@ -3494,6 +4251,12 @@ Examples:
|
|
|
3494
4251
|
${brand.primary("npx ctx7 skills list --claude")}
|
|
3495
4252
|
${brand.primary("npx ctx7 skills remove pdf")}
|
|
3496
4253
|
|
|
4254
|
+
${brand.dim("# Remove Context7 setup")}
|
|
4255
|
+
${brand.primary("npx ctx7 remove --cursor")}
|
|
4256
|
+
${brand.primary("npx ctx7 remove --cursor --all")}
|
|
4257
|
+
${brand.primary("npx ctx7 remove --cursor --cli")}
|
|
4258
|
+
${brand.primary("npx ctx7 remove --claude --mcp")}
|
|
4259
|
+
|
|
3497
4260
|
${brand.dim("# Query library documentation")}
|
|
3498
4261
|
${brand.primary('npx ctx7 library react "how to use hooks"')}
|
|
3499
4262
|
${brand.primary('npx ctx7 docs /facebook/react "useEffect examples"')}
|
|
@@ -3505,7 +4268,9 @@ registerSkillCommands(program);
|
|
|
3505
4268
|
registerSkillAliases(program);
|
|
3506
4269
|
registerAuthCommands(program);
|
|
3507
4270
|
registerSetupCommand(program);
|
|
4271
|
+
registerRemoveCommand(program);
|
|
3508
4272
|
registerDocsCommands(program);
|
|
4273
|
+
registerUpgradeCommand(program);
|
|
3509
4274
|
program.action(() => {
|
|
3510
4275
|
console.log("");
|
|
3511
4276
|
const banner = figlet.textSync("Context7", { font: "ANSI Shadow" });
|
|
@@ -3520,5 +4285,5 @@ program.action(() => {
|
|
|
3520
4285
|
console.log(` Visit ${brand.primary("https://context7.com")} to browse skills`);
|
|
3521
4286
|
console.log("");
|
|
3522
4287
|
});
|
|
3523
|
-
program.
|
|
4288
|
+
await program.parseAsync();
|
|
3524
4289
|
//# sourceMappingURL=index.js.map
|