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/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import pc10 from "picocolors";
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. Answer using the fetched docs
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. Answer using the fetched documentation
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/docs.ts
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(`${pc9.dim(`${index + 1}.`)} ${pc9.bold(`Title: ${lib.title}`)}`);
3314
- lines.push(` ${pc9.cyan(`Context7-compatible library ID: ${lib.id}`)}`);
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(` ${pc9.dim(`Description: ${lib.description}`)}`);
3734
+ lines.push(` ${pc10.dim(`Description: ${lib.description}`)}`);
3317
3735
  }
3318
3736
  if (lib.totalSnippets) {
3319
- lines.push(` ${pc9.dim(`Code Snippets: ${lib.totalSnippets}`)}`);
3737
+ lines.push(` ${pc10.dim(`Code Snippets: ${lib.totalSnippets}`)}`);
3320
3738
  }
3321
3739
  if (lib.trustScore !== void 0) {
3322
- lines.push(` ${pc9.dim(`Source Reputation: ${getReputationLabel(lib.trustScore)}`)}`);
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(` ${pc9.dim(`Benchmark Score: ${lib.benchmarkScore}`)}`);
3743
+ lines.push(` ${pc10.dim(`Benchmark Score: ${lib.benchmarkScore}`)}`);
3326
3744
  }
3327
3745
  if (lib.versions && lib.versions.length > 0) {
3328
- lines.push(` ${pc9.dim(`Versions: ${lib.versions.join(", ")}`)}`);
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 ? ora5(`Searching for "${library}"...`).start() : null;
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
- `${pc9.bold("Quick command:")}
3377
- ${pc9.cyan(`ctx7 docs "${best.id}" "<your question>"`)}`
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 ? ora5(`Fetching docs for "${libraryId}"...`).start() : null;
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(libraryId, query, { type: outputType }, accessToken);
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: ${pc9.cyan(ctx.redirectUrl)}`);
3414
- log.info(`Run: ${pc9.cyan(`ctx7 docs "${ctx.redirectUrl}" "${query}"`)}`);
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(pc9.bold(snippet.codeTitle));
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(pc9.bold(snippet.breadcrumb));
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").description("Query documentation for a library").action(async (libraryId, query, options) => {
3461
- await queryCommand(libraryId, query, options);
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: pc10.green,
3468
- dim: pc10.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.parse();
4288
+ await program.parseAsync();
3524
4289
  //# sourceMappingURL=index.js.map