ctx7 0.5.0 → 0.5.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
@@ -932,21 +932,11 @@ 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 crypto from "crypto";
936
- import * as http from "http";
937
935
  import * as fs from "fs";
938
936
  import * as path from "path";
939
937
  import * as os from "os";
940
938
  var CONFIG_DIR = path.join(os.homedir(), ".context7");
941
939
  var CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
942
- function generatePKCE() {
943
- const codeVerifier = crypto.randomBytes(32).toString("base64url");
944
- const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
945
- return { codeVerifier, codeChallenge };
946
- }
947
- function generateState() {
948
- return crypto.randomBytes(16).toString("base64url");
949
- }
950
940
  function ensureConfigDir() {
951
941
  if (!fs.existsSync(CONFIG_DIR)) {
952
942
  fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
@@ -1017,153 +1007,8 @@ async function getValidAccessToken() {
1017
1007
  return null;
1018
1008
  }
1019
1009
  }
1020
- var CALLBACK_PORT = 52417;
1021
- function createCallbackServer(expectedState) {
1022
- let resolvePort;
1023
- let resolveResult;
1024
- let rejectResult;
1025
- let serverInstance = null;
1026
- const portPromise = new Promise((resolve3) => {
1027
- resolvePort = resolve3;
1028
- });
1029
- const resultPromise = new Promise((resolve3, reject) => {
1030
- resolveResult = resolve3;
1031
- rejectResult = reject;
1032
- });
1033
- const server = http.createServer((req, res) => {
1034
- const url = new URL(req.url || "/", `http://localhost`);
1035
- if (url.pathname === "/callback") {
1036
- const code = url.searchParams.get("code");
1037
- const state = url.searchParams.get("state");
1038
- const error = url.searchParams.get("error");
1039
- const errorDescription = url.searchParams.get("error_description");
1040
- res.writeHead(200, { "Content-Type": "text/html" });
1041
- if (error) {
1042
- res.end(errorPage(errorDescription || error));
1043
- serverInstance?.close();
1044
- rejectResult(new Error(errorDescription || error));
1045
- return;
1046
- }
1047
- if (!code || !state) {
1048
- res.end(errorPage("Missing authorization code or state"));
1049
- serverInstance?.close();
1050
- rejectResult(new Error("Missing authorization code or state"));
1051
- return;
1052
- }
1053
- if (state !== expectedState) {
1054
- res.end(errorPage("State mismatch - possible CSRF attack"));
1055
- serverInstance?.close();
1056
- rejectResult(new Error("State mismatch"));
1057
- return;
1058
- }
1059
- res.end(successPage());
1060
- serverInstance?.close();
1061
- resolveResult({ code, state });
1062
- } else {
1063
- res.writeHead(404);
1064
- res.end("Not found");
1065
- }
1066
- });
1067
- serverInstance = server;
1068
- server.on("error", (err) => {
1069
- rejectResult(err);
1070
- });
1071
- server.listen(CALLBACK_PORT, "127.0.0.1", () => {
1072
- resolvePort(CALLBACK_PORT);
1073
- });
1074
- const timeout = setTimeout(
1075
- () => {
1076
- server.close();
1077
- rejectResult(new Error("Login timed out after 5 minutes"));
1078
- },
1079
- 5 * 60 * 1e3
1080
- );
1081
- return {
1082
- port: portPromise,
1083
- result: resultPromise,
1084
- close: () => {
1085
- clearTimeout(timeout);
1086
- server.close();
1087
- }
1088
- };
1089
- }
1090
- function successPage() {
1091
- return `<!DOCTYPE html>
1092
- <html>
1093
- <head><title>Login Successful</title></head>
1094
- <body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #f9fafb;">
1095
- <div style="text-align: center; padding: 2rem;">
1096
- <div style="width: 64px; height: 64px; background: #16a34a; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 1rem;">
1097
- <svg width="32" height="32" fill="none" stroke="white" stroke-width="3" viewBox="0 0 24 24">
1098
- <path d="M5 13l4 4L19 7" stroke-linecap="round" stroke-linejoin="round"/>
1099
- </svg>
1100
- </div>
1101
- <h1 style="color: #16a34a; margin: 0 0 0.5rem;">Login Successful!</h1>
1102
- <p style="color: #6b7280; margin: 0;">You can close this window and return to the terminal.</p>
1103
- </div>
1104
- </body>
1105
- </html>`;
1106
- }
1107
- function escapeHtml(text) {
1108
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
1109
- }
1110
- function errorPage(message) {
1111
- const safeMessage = escapeHtml(message);
1112
- return `<!DOCTYPE html>
1113
- <html>
1114
- <head><title>Login Failed</title></head>
1115
- <body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #f9fafb;">
1116
- <div style="text-align: center; padding: 2rem;">
1117
- <div style="width: 64px; height: 64px; background: #dc2626; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 1rem;">
1118
- <svg width="32" height="32" fill="none" stroke="white" stroke-width="3" viewBox="0 0 24 24">
1119
- <path d="M6 18L18 6M6 6l12 12" stroke-linecap="round" stroke-linejoin="round"/>
1120
- </svg>
1121
- </div>
1122
- <h1 style="color: #dc2626; margin: 0 0 0.5rem;">Login Failed</h1>
1123
- <p style="color: #6b7280; margin: 0;">${safeMessage}</p>
1124
- <p style="color: #9ca3af; margin: 1rem 0 0; font-size: 0.875rem;">You can close this window.</p>
1125
- </div>
1126
- </body>
1127
- </html>`;
1128
- }
1129
- async function exchangeCodeForTokens(baseUrl3, code, codeVerifier, redirectUri, clientId) {
1130
- const response = await fetch(`${baseUrl3}/api/oauth/token`, {
1131
- method: "POST",
1132
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
1133
- body: new URLSearchParams({
1134
- grant_type: "authorization_code",
1135
- client_id: clientId,
1136
- code,
1137
- code_verifier: codeVerifier,
1138
- redirect_uri: redirectUri
1139
- }).toString()
1140
- });
1141
- if (!response.ok) {
1142
- const err = await response.json().catch(() => ({}));
1143
- throw new Error(err.error_description || err.error || "Failed to exchange code for tokens");
1144
- }
1145
- return await response.json();
1146
- }
1147
- function buildAuthorizationUrl(baseUrl3, clientId, redirectUri, codeChallenge, state) {
1148
- const url = new URL(`${baseUrl3}/api/oauth/authorize`);
1149
- url.searchParams.set("client_id", clientId);
1150
- url.searchParams.set("redirect_uri", redirectUri);
1151
- url.searchParams.set("code_challenge", codeChallenge);
1152
- url.searchParams.set("code_challenge_method", "S256");
1153
- url.searchParams.set("state", state);
1154
- url.searchParams.set("scope", "profile email");
1155
- url.searchParams.set("response_type", "code");
1156
- return url.toString();
1157
- }
1158
1010
  var DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
1159
1011
  var DEFAULT_DEVICE_POLL_INTERVAL_SECONDS = 5;
1160
- function shouldUseDeviceFlow() {
1161
- if (process.env.SSH_CONNECTION || process.env.SSH_CLIENT || process.env.SSH_TTY) return true;
1162
- if (process.platform === "linux" && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
1163
- return true;
1164
- }
1165
- return false;
1166
- }
1167
1012
  async function startDeviceAuthorization(baseUrl3, clientId) {
1168
1013
  const params = new URLSearchParams({ client_id: clientId });
1169
1014
  try {
@@ -1236,7 +1081,7 @@ function setAuthBaseUrl(url) {
1236
1081
  baseUrl2 = url;
1237
1082
  }
1238
1083
  function registerAuthCommands(program2) {
1239
- program2.command("login").description("Log in to Context7").option("--no-browser", "Don't open browser automatically").option("--device", "Force device-code flow (use on SSH / headless hosts)").action(async (options) => {
1084
+ program2.command("login").description("Log in to Context7").option("--no-browser", "Don't open browser automatically").action(async (options) => {
1240
1085
  await loginCommand(options);
1241
1086
  });
1242
1087
  program2.command("logout").description("Log out of Context7").action(() => {
@@ -1299,7 +1144,7 @@ async function announceIdentity(accessToken) {
1299
1144
  return "Login successful!";
1300
1145
  }
1301
1146
  }
1302
- async function performDeviceLogin(openBrowser = true) {
1147
+ async function performLogin(openBrowser = true) {
1303
1148
  const spinner = ora("Preparing login...").start();
1304
1149
  let authorization;
1305
1150
  try {
@@ -1367,67 +1212,6 @@ async function performDeviceLogin(openBrowser = true) {
1367
1212
  waitingSpinner.fail(pc4.red("Code expired without approval."));
1368
1213
  return null;
1369
1214
  }
1370
- async function performLogin(openBrowser = true, forceDevice = false) {
1371
- if (forceDevice || shouldUseDeviceFlow()) {
1372
- return performDeviceLogin(openBrowser);
1373
- }
1374
- const spinner = ora("Preparing login...").start();
1375
- try {
1376
- const { codeVerifier, codeChallenge } = generatePKCE();
1377
- const state = generateState();
1378
- const callbackServer = createCallbackServer(state);
1379
- const port = await callbackServer.port;
1380
- const redirectUri = `http://localhost:${port}/callback`;
1381
- const authUrl = buildAuthorizationUrl(
1382
- baseUrl2,
1383
- CLI_CLIENT_ID,
1384
- redirectUri,
1385
- codeChallenge,
1386
- state
1387
- );
1388
- spinner.stop();
1389
- console.log("");
1390
- console.log(pc4.bold("Opening browser to log in..."));
1391
- console.log("");
1392
- if (openBrowser) {
1393
- await open(authUrl);
1394
- console.log(pc4.dim("If the browser didn't open, visit this URL:"));
1395
- } else {
1396
- console.log(pc4.dim("Open this URL in your browser:"));
1397
- }
1398
- console.log(pc4.cyan(authUrl));
1399
- console.log("");
1400
- const waitingSpinner = ora("Waiting for login...").start();
1401
- try {
1402
- const { code } = await callbackServer.result;
1403
- waitingSpinner.text = "Exchanging code for tokens...";
1404
- const tokens = await exchangeCodeForTokens(
1405
- baseUrl2,
1406
- code,
1407
- codeVerifier,
1408
- redirectUri,
1409
- CLI_CLIENT_ID
1410
- );
1411
- saveTokens(tokens);
1412
- callbackServer.close();
1413
- waitingSpinner.succeed(pc4.green("Login successful!"));
1414
- return tokens.access_token;
1415
- } catch (error) {
1416
- callbackServer.close();
1417
- waitingSpinner.fail(pc4.red("Login failed"));
1418
- if (error instanceof Error) {
1419
- console.error(pc4.red(error.message));
1420
- }
1421
- return null;
1422
- }
1423
- } catch (error) {
1424
- spinner.fail(pc4.red("Login failed"));
1425
- if (error instanceof Error) {
1426
- console.error(pc4.red(error.message));
1427
- }
1428
- return null;
1429
- }
1430
- }
1431
1215
  async function loginCommand(options) {
1432
1216
  trackEvent("command", { name: "login" });
1433
1217
  const existingToken = await getValidAccessToken();
@@ -1437,7 +1221,7 @@ async function loginCommand(options) {
1437
1221
  return;
1438
1222
  }
1439
1223
  clearTokens();
1440
- const token = await performLogin(options.browser, options.device ?? false);
1224
+ const token = await performLogin(options.browser);
1441
1225
  if (!token) {
1442
1226
  process.exit(1);
1443
1227
  }
@@ -2837,7 +2621,7 @@ import ora4 from "ora";
2837
2621
  import { select as select3 } from "@inquirer/prompts";
2838
2622
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
2839
2623
  import { dirname as dirname6, join as join8 } from "path";
2840
- import { randomBytes as randomBytes2 } from "crypto";
2624
+ import { randomBytes } from "crypto";
2841
2625
 
2842
2626
  // src/setup/agents.ts
2843
2627
  import { access as access2 } from "fs/promises";
@@ -3432,7 +3216,7 @@ async function authenticateAndGenerateKey() {
3432
3216
  Authorization: `Bearer ${accessToken}`,
3433
3217
  "Content-Type": "application/json"
3434
3218
  },
3435
- body: JSON.stringify({ name: `ctx7-cli-${randomBytes2(3).toString("hex")}` })
3219
+ body: JSON.stringify({ name: `ctx7-cli-${randomBytes(3).toString("hex")}` })
3436
3220
  });
3437
3221
  if (!response.ok) {
3438
3222
  const err = await response.json().catch(() => ({}));