ctx7 0.5.1 → 0.5.2

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
@@ -925,48 +925,153 @@ function trackEvent(event, data) {
925
925
  // src/commands/generate.ts
926
926
  import pc6 from "picocolors";
927
927
  import ora2 from "ora";
928
- import { mkdir as mkdir2, writeFile as writeFile2, readFile, unlink } from "fs/promises";
928
+ import { mkdir as mkdir3, writeFile as writeFile2, readFile, unlink } from "fs/promises";
929
929
  import { join as join4 } from "path";
930
930
  import { homedir as homedir3 } from "os";
931
931
  import { spawn } from "child_process";
932
932
  import { input, select as select2 } from "@inquirer/prompts";
933
933
 
934
934
  // src/utils/auth.ts
935
+ import * as fs2 from "fs";
936
+ import * as os2 from "os";
937
+
938
+ // src/utils/storage-paths.ts
935
939
  import * as fs from "fs";
936
- import * as path from "path";
940
+ import { access as access2, chmod, mkdir as mkdir2, rename } from "fs/promises";
937
941
  import * as os from "os";
938
- var CONFIG_DIR = path.join(os.homedir(), ".context7");
939
- var CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
942
+ import * as path from "path";
943
+ var APP_DIR = "context7";
944
+ var LEGACY_DIR = ".context7";
945
+ var CREDENTIALS_FILE_NAME = "credentials.json";
946
+ var UPDATE_STATE_FILE_NAME = "cli-state.json";
947
+ var PREVIEWS_DIR_NAME = "previews";
948
+ function xdgBase(envVar, ...defaultSegments) {
949
+ const value = process.env[envVar];
950
+ const base = value && path.isAbsolute(value) ? value : path.join(os.homedir(), ...defaultSegments);
951
+ return path.join(base, APP_DIR);
952
+ }
953
+ function getConfigDir() {
954
+ return xdgBase("XDG_CONFIG_HOME", ".config");
955
+ }
956
+ function getStateDir() {
957
+ return xdgBase("XDG_STATE_HOME", ".local", "state");
958
+ }
959
+ function getCacheDir() {
960
+ return xdgBase("XDG_CACHE_HOME", ".cache");
961
+ }
962
+ function getCredentialsFilePath() {
963
+ return path.join(getConfigDir(), CREDENTIALS_FILE_NAME);
964
+ }
965
+ function getUpdateStateFilePath() {
966
+ return path.join(getStateDir(), UPDATE_STATE_FILE_NAME);
967
+ }
968
+ function getPreviewsDir() {
969
+ return path.join(getCacheDir(), PREVIEWS_DIR_NAME);
970
+ }
971
+ function getLegacyFilePath(fileName) {
972
+ return path.join(os.homedir(), LEGACY_DIR, fileName);
973
+ }
974
+ function migrateLegacyFileSync(fileName, targetPath, mode) {
975
+ const legacyPath = getLegacyFilePath(fileName);
976
+ if (legacyPath === targetPath || fs.existsSync(targetPath) || !fs.existsSync(legacyPath)) {
977
+ return;
978
+ }
979
+ try {
980
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true, mode: 448 });
981
+ fs.renameSync(legacyPath, targetPath);
982
+ if (mode !== void 0) {
983
+ fs.chmodSync(targetPath, mode);
984
+ }
985
+ } catch {
986
+ }
987
+ }
988
+ async function migrateLegacyFile(fileName, targetPath, mode) {
989
+ const legacyPath = getLegacyFilePath(fileName);
990
+ if (legacyPath === targetPath || await exists(targetPath) || !await exists(legacyPath)) {
991
+ return;
992
+ }
993
+ try {
994
+ await mkdir2(path.dirname(targetPath), { recursive: true, mode: 448 });
995
+ await rename(legacyPath, targetPath);
996
+ if (mode !== void 0) {
997
+ await chmod(targetPath, mode);
998
+ }
999
+ } catch {
1000
+ }
1001
+ }
1002
+ function resolveReadPathSync(fileName, targetPath, mode) {
1003
+ migrateLegacyFileSync(fileName, targetPath, mode);
1004
+ if (fs.existsSync(targetPath)) {
1005
+ return targetPath;
1006
+ }
1007
+ const legacyPath = getLegacyFilePath(fileName);
1008
+ return fs.existsSync(legacyPath) ? legacyPath : targetPath;
1009
+ }
1010
+ async function resolveReadPath(fileName, targetPath, mode) {
1011
+ await migrateLegacyFile(fileName, targetPath, mode);
1012
+ if (await exists(targetPath)) {
1013
+ return targetPath;
1014
+ }
1015
+ const legacyPath = getLegacyFilePath(fileName);
1016
+ return await exists(legacyPath) ? legacyPath : targetPath;
1017
+ }
1018
+ async function exists(filePath) {
1019
+ try {
1020
+ await access2(filePath);
1021
+ return true;
1022
+ } catch {
1023
+ return false;
1024
+ }
1025
+ }
1026
+
1027
+ // src/utils/auth.ts
940
1028
  function ensureConfigDir() {
941
- if (!fs.existsSync(CONFIG_DIR)) {
942
- fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
1029
+ const configDir = getConfigDir();
1030
+ if (!fs2.existsSync(configDir)) {
1031
+ fs2.mkdirSync(configDir, { recursive: true, mode: 448 });
943
1032
  }
944
1033
  }
1034
+ var CREDENTIALS_MODE = 384;
945
1035
  function saveTokens(tokens) {
1036
+ const credentialsFile = getCredentialsFilePath();
1037
+ migrateLegacyFileSync(CREDENTIALS_FILE_NAME, credentialsFile, CREDENTIALS_MODE);
946
1038
  ensureConfigDir();
947
1039
  const data = {
948
1040
  ...tokens,
949
1041
  expires_at: tokens.expires_at ?? (tokens.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0)
950
1042
  };
951
- fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(data, null, 2), { mode: 384 });
1043
+ fs2.writeFileSync(credentialsFile, JSON.stringify(data, null, 2), { mode: CREDENTIALS_MODE });
1044
+ fs2.chmodSync(credentialsFile, CREDENTIALS_MODE);
952
1045
  }
953
1046
  function loadTokens() {
954
- if (!fs.existsSync(CREDENTIALS_FILE)) {
1047
+ const credentialsFile = resolveReadPathSync(
1048
+ CREDENTIALS_FILE_NAME,
1049
+ getCredentialsFilePath(),
1050
+ CREDENTIALS_MODE
1051
+ );
1052
+ if (!fs2.existsSync(credentialsFile)) {
955
1053
  return null;
956
1054
  }
957
1055
  try {
958
- const data = JSON.parse(fs.readFileSync(CREDENTIALS_FILE, "utf-8"));
1056
+ const data = JSON.parse(fs2.readFileSync(credentialsFile, "utf-8"));
959
1057
  return data;
960
1058
  } catch {
961
1059
  return null;
962
1060
  }
963
1061
  }
964
1062
  function clearTokens() {
965
- if (fs.existsSync(CREDENTIALS_FILE)) {
966
- fs.unlinkSync(CREDENTIALS_FILE);
967
- return true;
1063
+ const credentialsFile = getCredentialsFilePath();
1064
+ let removed = false;
1065
+ if (fs2.existsSync(credentialsFile)) {
1066
+ fs2.unlinkSync(credentialsFile);
1067
+ removed = true;
968
1068
  }
969
- return false;
1069
+ const legacyCredentialsFile = getLegacyFilePath(CREDENTIALS_FILE_NAME);
1070
+ if (fs2.existsSync(legacyCredentialsFile)) {
1071
+ fs2.unlinkSync(legacyCredentialsFile);
1072
+ removed = true;
1073
+ }
1074
+ return removed;
970
1075
  }
971
1076
  function isTokenExpired(tokens) {
972
1077
  if (!tokens.expires_at) {
@@ -1012,7 +1117,7 @@ var DEFAULT_DEVICE_POLL_INTERVAL_SECONDS = 5;
1012
1117
  async function startDeviceAuthorization(baseUrl3, clientId) {
1013
1118
  const params = new URLSearchParams({ client_id: clientId });
1014
1119
  try {
1015
- const hostname2 = os.hostname();
1120
+ const hostname2 = os2.hostname();
1016
1121
  if (hostname2) params.set("hostname", hostname2);
1017
1122
  } catch {
1018
1123
  }
@@ -1686,8 +1791,8 @@ async function generateCommand(options) {
1686
1791
  log.blank();
1687
1792
  };
1688
1793
  const openInEditor = async () => {
1689
- const previewDir = join4(homedir3(), ".context7", "previews");
1690
- await mkdir2(previewDir, { recursive: true });
1794
+ const previewDir = getPreviewsDir();
1795
+ await mkdir3(previewDir, { recursive: true });
1691
1796
  previewFile = join4(previewDir, `${skillName}.md`);
1692
1797
  if (!previewFileWritten) {
1693
1798
  await writeFile2(previewFile, generatedContent, "utf-8");
@@ -1767,7 +1872,7 @@ async function generateCommand(options) {
1767
1872
  const skillDir = join4(finalDir, skillName);
1768
1873
  const skillPath = join4(skillDir, "SKILL.md");
1769
1874
  try {
1770
- await mkdir2(skillDir, { recursive: true });
1875
+ await mkdir3(skillDir, { recursive: true });
1771
1876
  await writeFile2(skillPath, generatedContent, "utf-8");
1772
1877
  } catch (err) {
1773
1878
  const error = err;
@@ -2619,12 +2724,12 @@ ${headerLine}`,
2619
2724
  import pc8 from "picocolors";
2620
2725
  import ora4 from "ora";
2621
2726
  import { select as select3 } from "@inquirer/prompts";
2622
- import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
2623
- import { dirname as dirname6, join as join8 } from "path";
2727
+ import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
2728
+ import { dirname as dirname7, join as join8 } from "path";
2624
2729
  import { randomBytes } from "crypto";
2625
2730
 
2626
2731
  // src/setup/agents.ts
2627
- import { access as access2 } from "fs/promises";
2732
+ import { access as access3 } from "fs/promises";
2628
2733
  import { join as join7 } from "path";
2629
2734
  import { homedir as homedir5 } from "os";
2630
2735
  var SETUP_AGENT_NAMES = {
@@ -2828,7 +2933,7 @@ function getAgent(name) {
2828
2933
  var ALL_AGENT_NAMES = Object.keys(agents);
2829
2934
  async function pathExists(p) {
2830
2935
  try {
2831
- await access2(p);
2936
+ await access3(p);
2832
2937
  return true;
2833
2938
  } catch {
2834
2939
  return false;
@@ -2937,8 +3042,8 @@ function customizeSkillFilesForAgent(agent, skillName, files) {
2937
3042
  }
2938
3043
 
2939
3044
  // src/setup/mcp-writer.ts
2940
- import { access as access3, readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
2941
- import { dirname as dirname5 } from "path";
3045
+ import { access as access4, readFile as readFile3, writeFile as writeFile3, mkdir as mkdir4 } from "fs/promises";
3046
+ import { dirname as dirname6 } from "path";
2942
3047
  function stripJsonComments(text) {
2943
3048
  let result = "";
2944
3049
  let i = 0;
@@ -3009,7 +3114,7 @@ function removeServerEntry(existing, configKey, serverName) {
3009
3114
  async function resolveMcpPath(candidates) {
3010
3115
  for (const candidate of candidates) {
3011
3116
  try {
3012
- await access3(candidate);
3117
+ await access4(candidate);
3013
3118
  return candidate;
3014
3119
  } catch {
3015
3120
  }
@@ -3017,7 +3122,7 @@ async function resolveMcpPath(candidates) {
3017
3122
  return candidates[0];
3018
3123
  }
3019
3124
  async function writeJsonConfig(filePath, config) {
3020
- await mkdir3(dirname5(filePath), { recursive: true });
3125
+ await mkdir4(dirname6(filePath), { recursive: true });
3021
3126
  await writeFile3(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3022
3127
  }
3023
3128
  async function readTomlServerExists(filePath, serverName) {
@@ -3137,11 +3242,11 @@ async function appendTomlServer(filePath, serverName, entry) {
3137
3242
  const before = rawBefore.length > 0 ? rawBefore + "\n\n" : "";
3138
3243
  const after = rawAfter.length > 0 ? "\n" + rawAfter : "";
3139
3244
  const content = before + block + after;
3140
- await mkdir3(dirname5(filePath), { recursive: true });
3245
+ await mkdir4(dirname6(filePath), { recursive: true });
3141
3246
  await writeFile3(filePath, content, "utf-8");
3142
3247
  } else {
3143
3248
  const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
3144
- await mkdir3(dirname5(filePath), { recursive: true });
3249
+ await mkdir4(dirname6(filePath), { recursive: true });
3145
3250
  await writeFile3(filePath, existing + separator + block, "utf-8");
3146
3251
  }
3147
3252
  return { alreadyExists };
@@ -3174,7 +3279,7 @@ async function removeTomlServer(filePath, serverName) {
3174
3279
  const rawBefore = existing.slice(0, startIdx).replace(/\n+$/, "");
3175
3280
  const rawAfter = existing.slice(startIdx + sectionHeader.length + endOffset).replace(/^\n+/, "");
3176
3281
  const content = [rawBefore, rawAfter].filter(Boolean).join("\n\n");
3177
- await mkdir3(dirname5(filePath), { recursive: true });
3282
+ await mkdir4(dirname6(filePath), { recursive: true });
3178
3283
  await writeFile3(filePath, content.length > 0 ? `${content}
3179
3284
  ` : "", "utf-8");
3180
3285
  return { removed: true };
@@ -3320,7 +3425,7 @@ async function installRule(agentName, mode, scope) {
3320
3425
  if (rule.kind === "file") {
3321
3426
  const ruleDir = scope === "global" ? rule.dir("global") : join8(process.cwd(), rule.dir("project"));
3322
3427
  const rulePath = join8(ruleDir, rule.filename);
3323
- await mkdir4(dirname6(rulePath), { recursive: true });
3428
+ await mkdir5(dirname7(rulePath), { recursive: true });
3324
3429
  await writeFile4(rulePath, content, "utf-8");
3325
3430
  return { status: "installed", path: rulePath };
3326
3431
  }
@@ -3340,7 +3445,7 @@ ${content}${rule.sectionMarker}`;
3340
3445
  return { status: "updated", path: filePath };
3341
3446
  }
3342
3447
  const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
3343
- await mkdir4(dirname6(filePath), { recursive: true });
3448
+ await mkdir5(dirname7(filePath), { recursive: true });
3344
3449
  await writeFile4(filePath, existing + separator + section + "\n", "utf-8");
3345
3450
  return { status: "installed", path: filePath };
3346
3451
  }
@@ -3444,7 +3549,7 @@ async function setupMcp(agents2, options, scope) {
3444
3549
  log.plain(` ${pc8.dim(r.skillPath)}`);
3445
3550
  if (r.skillStatus.includes("EACCES")) {
3446
3551
  log.plain(
3447
- ` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname6(dirname6(r.skillPath))}`)}`
3552
+ ` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname7(dirname7(r.skillPath))}`)}`
3448
3553
  );
3449
3554
  }
3450
3555
  }
@@ -3505,7 +3610,7 @@ async function setupCli(options) {
3505
3610
  log.plain(` ${pc8.dim(r.skillPath)}`);
3506
3611
  if (r.skillStatus.includes("EACCES")) {
3507
3612
  log.plain(
3508
- ` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname6(dirname6(r.skillPath))}`)}`
3613
+ ` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname7(dirname7(r.skillPath))}`)}`
3509
3614
  );
3510
3615
  }
3511
3616
  const ruleIcon = r.ruleStatus === "installed" || r.ruleStatus === "updated" ? pc8.green("+") : pc8.dim("~");
@@ -3538,7 +3643,7 @@ async function setupCommand(options) {
3538
3643
  import pc9 from "picocolors";
3539
3644
  import ora5 from "ora";
3540
3645
  import { join as join9 } from "path";
3541
- import { access as access4, readFile as readFile5, rm as rm3, writeFile as writeFile5 } from "fs/promises";
3646
+ import { access as access5, readFile as readFile5, rm as rm3, writeFile as writeFile5 } from "fs/promises";
3542
3647
  var CHECKBOX_THEME2 = {
3543
3648
  style: {
3544
3649
  highlight: (text) => pc9.green(text),
@@ -3638,7 +3743,7 @@ function resolveFlagModes(options) {
3638
3743
  }
3639
3744
  async function pathExists2(path2) {
3640
3745
  try {
3641
- await access4(path2);
3746
+ await access5(path2);
3642
3747
  return true;
3643
3748
  } catch {
3644
3749
  return false;
@@ -3885,6 +3990,18 @@ async function removeCommand2(options) {
3885
3990
  // src/commands/docs.ts
3886
3991
  import pc10 from "picocolors";
3887
3992
  import ora6 from "ora";
3993
+
3994
+ // src/utils/library-id.ts
3995
+ function recoverLibraryId(input2) {
3996
+ if (input2.startsWith("//")) return input2.replace(/^\/+/, "/");
3997
+ if (input2.startsWith("/")) return input2;
3998
+ if (!/^[A-Za-z]:[\\/]/.test(input2)) return input2;
3999
+ const normalized = input2.replace(/\\/g, "/");
4000
+ const match = normalized.match(/^[A-Za-z]:\/.*?\/(?:Git|PortableGit|git-bash)\/(.+)$/i);
4001
+ return match ? `/${match[1]}` : input2;
4002
+ }
4003
+
4004
+ // src/commands/docs.ts
3888
4005
  var isTTY = process.stdout.isTTY;
3889
4006
  function getReputationLabel(score) {
3890
4007
  if (score === void 0 || score < 0) return "Unknown";
@@ -3970,10 +4087,16 @@ async function resolveCommand(library, query, options) {
3970
4087
  }
3971
4088
  async function queryCommand(libraryId, query, options) {
3972
4089
  trackEvent("command", { name: "docs" });
4090
+ libraryId = recoverLibraryId(libraryId);
3973
4091
  if (!libraryId.startsWith("/") || !/^\/[^/]+\/[^/]/.test(libraryId)) {
3974
4092
  log.error(`Invalid library ID: "${libraryId}"`);
3975
4093
  log.info(`Expected format: /owner/repo or /owner/repo/version (e.g., /facebook/react)`);
3976
4094
  log.info(`Run "ctx7 library <name>" to find the correct ID`);
4095
+ if (process.platform === "win32") {
4096
+ log.info(
4097
+ `On Git Bash, prefix the ID with an extra slash to avoid path conversion: ctx7 docs "//facebook/react" "<your question>"`
4098
+ );
4099
+ }
3977
4100
  process.exitCode = 1;
3978
4101
  return;
3979
4102
  }
@@ -4057,25 +4180,36 @@ import { spawn as spawn2 } from "child_process";
4057
4180
  import pc11 from "picocolors";
4058
4181
 
4059
4182
  // src/utils/update-check.ts
4060
- import { homedir as homedir6 } from "os";
4061
- import { dirname as dirname7, join as join10 } from "path";
4062
- import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
4183
+ import { dirname as dirname8 } from "path";
4184
+ import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
4063
4185
  var DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
4064
- var UPDATE_STATE_FILE = join10(homedir6(), ".context7", "cli-state.json");
4065
4186
  function getStateFilePath(stateFile) {
4066
- return stateFile ?? UPDATE_STATE_FILE;
4187
+ return stateFile ?? getUpdateStateFilePath();
4188
+ }
4189
+ async function readStateFilePath(stateFile) {
4190
+ if (stateFile) {
4191
+ return stateFile;
4192
+ }
4193
+ return resolveReadPath(UPDATE_STATE_FILE_NAME, getUpdateStateFilePath());
4194
+ }
4195
+ async function writeStateFilePath(stateFile) {
4196
+ const path2 = getStateFilePath(stateFile);
4197
+ if (!stateFile) {
4198
+ await migrateLegacyFile(UPDATE_STATE_FILE_NAME, path2);
4199
+ }
4200
+ return path2;
4067
4201
  }
4068
4202
  async function readUpdateState(stateFile) {
4069
4203
  try {
4070
- const raw = await readFile6(getStateFilePath(stateFile), "utf-8");
4204
+ const raw = await readFile6(await readStateFilePath(stateFile), "utf-8");
4071
4205
  return JSON.parse(raw);
4072
4206
  } catch {
4073
4207
  return {};
4074
4208
  }
4075
4209
  }
4076
4210
  async function writeUpdateState(state, stateFile) {
4077
- const path2 = getStateFilePath(stateFile);
4078
- await mkdir5(dirname7(path2), { recursive: true });
4211
+ const path2 = await writeStateFilePath(stateFile);
4212
+ await mkdir6(dirname8(path2), { recursive: true });
4079
4213
  await writeFile6(path2, JSON.stringify(state, null, 2) + "\n", "utf-8");
4080
4214
  }
4081
4215
  function compareVersions(a, b) {
@@ -4379,7 +4513,7 @@ var brand = {
4379
4513
  dim: pc12.dim
4380
4514
  };
4381
4515
  var program = new Command();
4382
- program.name("ctx7").description("Context7 CLI - Fetch documentation context and configure Context7").version(VERSION).option("--base-url <url>").hook("preAction", (thisCommand) => {
4516
+ program.name("ctx7").description("Context7 CLI - Fetch documentation context and configure Context7").version(VERSION, "-v, --version").option("--base-url <url>").hook("preAction", (thisCommand) => {
4383
4517
  const opts = thisCommand.opts();
4384
4518
  if (opts.baseUrl) {
4385
4519
  setBaseUrl(opts.baseUrl);