ctx7 0.3.13 → 0.4.1

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