@night-slayer18/leetcode-cli 1.4.0 → 1.6.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.
Files changed (3) hide show
  1. package/README.md +43 -1
  2. package/dist/index.js +847 -214
  3. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import chalk20 from "chalk";
5
+ import chalk22 from "chalk";
6
6
 
7
7
  // src/commands/login.ts
8
8
  import inquirer from "inquirer";
@@ -308,12 +308,12 @@ var LeetCodeClient = class {
308
308
  retry: { limit: 2 }
309
309
  });
310
310
  }
311
- setCredentials(credentials) {
312
- this.credentials = credentials;
311
+ setCredentials(credentials2) {
312
+ this.credentials = credentials2;
313
313
  this.client = this.client.extend({
314
314
  headers: {
315
- "Cookie": `LEETCODE_SESSION=${credentials.session}; csrftoken=${credentials.csrfToken}`,
316
- "X-CSRFToken": credentials.csrfToken
315
+ "Cookie": `LEETCODE_SESSION=${credentials2.session}; csrftoken=${credentials2.csrfToken}`,
316
+ "X-CSRFToken": credentials2.csrfToken
317
317
  }
318
318
  });
319
319
  }
@@ -445,14 +445,14 @@ var LeetCodeClient = class {
445
445
  }).json();
446
446
  return this.pollSubmission(response.submission_id.toString(), "submission", SubmissionResultSchema);
447
447
  }
448
- async pollSubmission(id, _type, schema2) {
448
+ async pollSubmission(id, _type, schema) {
449
449
  const endpoint = `submissions/detail/${id}/check/`;
450
450
  const maxAttempts = 30;
451
451
  const delay = 1e3;
452
452
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
453
453
  const result = await this.client.get(endpoint).json();
454
454
  if (result.state === "SUCCESS" || result.state === "FAILURE") {
455
- return schema2.parse(result);
455
+ return schema.parse(result);
456
456
  }
457
457
  await new Promise((resolve) => setTimeout(resolve, delay));
458
458
  }
@@ -461,88 +461,31 @@ var LeetCodeClient = class {
461
461
  };
462
462
  var leetcodeClient = new LeetCodeClient();
463
463
 
464
- // src/storage/config.ts
464
+ // src/storage/credentials.ts
465
465
  import Conf from "conf";
466
466
  import { homedir } from "os";
467
467
  import { join } from "path";
468
- var schema = {
469
- credentials: {
470
- type: "object",
471
- nullable: true,
472
- properties: {
473
- csrfToken: { type: "string" },
474
- session: { type: "string" }
475
- }
476
- },
477
- config: {
478
- type: "object",
479
- properties: {
480
- language: { type: "string", default: "typescript" },
481
- editor: { type: "string" },
482
- workDir: { type: "string", default: join(homedir(), "leetcode") },
483
- repo: { type: "string" }
484
- },
485
- default: {
486
- language: "typescript",
487
- workDir: join(homedir(), "leetcode")
488
- }
489
- }
490
- };
491
- var configStore = new Conf({
492
- projectName: "leetcode-cli",
468
+ var credentialsStore = new Conf({
469
+ configName: "credentials",
493
470
  cwd: join(homedir(), ".leetcode"),
494
- schema
471
+ defaults: {}
495
472
  });
496
- var config = {
497
- // Credentials
498
- getCredentials() {
499
- return configStore.get("credentials") ?? null;
500
- },
501
- setCredentials(credentials) {
502
- configStore.set("credentials", credentials);
473
+ var credentials = {
474
+ get() {
475
+ const session = credentialsStore.get("session");
476
+ const csrfToken = credentialsStore.get("csrfToken");
477
+ if (!session || !csrfToken) return null;
478
+ return { session, csrfToken };
503
479
  },
504
- clearCredentials() {
505
- configStore.delete("credentials");
480
+ set(creds) {
481
+ credentialsStore.set("session", creds.session);
482
+ credentialsStore.set("csrfToken", creds.csrfToken);
506
483
  },
507
- // User Config
508
- getConfig() {
509
- return configStore.get("config");
510
- },
511
- setLanguage(language) {
512
- configStore.set("config.language", language);
513
- },
514
- setEditor(editor) {
515
- configStore.set("config.editor", editor);
516
- },
517
- setWorkDir(workDir) {
518
- configStore.set("config.workDir", workDir);
519
- },
520
- setRepo(repo) {
521
- configStore.set("config.repo", repo);
522
- },
523
- deleteRepo() {
524
- configStore.delete("config.repo");
525
- },
526
- // Get specific config values
527
- getLanguage() {
528
- return configStore.get("config.language");
529
- },
530
- getEditor() {
531
- return configStore.get("config.editor");
532
- },
533
- getWorkDir() {
534
- return configStore.get("config.workDir");
535
- },
536
- getRepo() {
537
- return configStore.get("config.repo");
538
- },
539
- // Clear all config
540
484
  clear() {
541
- configStore.clear();
485
+ credentialsStore.clear();
542
486
  },
543
- // Get config file path (for debugging)
544
487
  getPath() {
545
- return configStore.path;
488
+ return credentialsStore.path;
546
489
  }
547
490
  };
548
491
 
@@ -574,23 +517,23 @@ async function loginCommand() {
574
517
  validate: (input) => input.length > 0 || "CSRF token is required"
575
518
  }
576
519
  ]);
577
- const credentials = {
520
+ const creds = {
578
521
  session: answers.session.trim(),
579
522
  csrfToken: answers.csrfToken.trim()
580
523
  };
581
524
  const spinner = ora("Verifying credentials...").start();
582
525
  try {
583
- leetcodeClient.setCredentials(credentials);
526
+ leetcodeClient.setCredentials(creds);
584
527
  const { isSignedIn, username } = await leetcodeClient.checkAuth();
585
528
  if (!isSignedIn || !username) {
586
529
  spinner.fail("Invalid credentials");
587
530
  console.log(chalk.red("Please check your session cookies and try again."));
588
531
  return;
589
532
  }
590
- config.setCredentials(credentials);
533
+ credentials.set(creds);
591
534
  spinner.succeed(`Logged in as ${chalk.green(username)}`);
592
535
  console.log();
593
- console.log(chalk.gray(`Credentials saved to ${config.getPath()}`));
536
+ console.log(chalk.gray(`Credentials saved to ${credentials.getPath()}`));
594
537
  } catch (error) {
595
538
  spinner.fail("Authentication failed");
596
539
  if (error instanceof Error) {
@@ -599,18 +542,18 @@ async function loginCommand() {
599
542
  }
600
543
  }
601
544
  async function logoutCommand() {
602
- config.clearCredentials();
545
+ credentials.clear();
603
546
  console.log(chalk.green("\u2713 Logged out successfully"));
604
547
  }
605
548
  async function whoamiCommand() {
606
- const credentials = config.getCredentials();
607
- if (!credentials) {
549
+ const creds = credentials.get();
550
+ if (!creds) {
608
551
  console.log(chalk.yellow('Not logged in. Run "leetcode login" to authenticate.'));
609
552
  return;
610
553
  }
611
554
  const spinner = ora("Checking session...").start();
612
555
  try {
613
- leetcodeClient.setCredentials(credentials);
556
+ leetcodeClient.setCredentials(creds);
614
557
  const { isSignedIn, username } = await leetcodeClient.checkAuth();
615
558
  if (!isSignedIn || !username) {
616
559
  spinner.fail("Session expired");
@@ -633,13 +576,13 @@ import chalk4 from "chalk";
633
576
  // src/utils/auth.ts
634
577
  import chalk2 from "chalk";
635
578
  async function requireAuth() {
636
- const credentials = config.getCredentials();
637
- if (!credentials) {
579
+ const creds = credentials.get();
580
+ if (!creds) {
638
581
  console.log(chalk2.yellow("\u26A0\uFE0F Please login first: leetcode login"));
639
582
  return { authorized: false };
640
583
  }
641
584
  try {
642
- leetcodeClient.setCredentials(credentials);
585
+ leetcodeClient.setCredentials(creds);
643
586
  const { isSignedIn, username } = await leetcodeClient.checkAuth();
644
587
  if (!isSignedIn) {
645
588
  console.log(chalk2.yellow("\u26A0\uFE0F Session expired. Please run: leetcode login"));
@@ -990,10 +933,63 @@ async function showCommand(idOrSlug) {
990
933
  // src/commands/pick.ts
991
934
  import { writeFile, mkdir } from "fs/promises";
992
935
  import { existsSync } from "fs";
993
- import { join as join2 } from "path";
936
+ import { join as join3 } from "path";
994
937
  import ora4 from "ora";
995
938
  import chalk7 from "chalk";
996
939
 
940
+ // src/storage/config.ts
941
+ import Conf2 from "conf";
942
+ import { homedir as homedir2 } from "os";
943
+ import { join as join2 } from "path";
944
+ var configStore = new Conf2({
945
+ configName: "config",
946
+ cwd: join2(homedir2(), ".leetcode"),
947
+ defaults: {
948
+ language: "typescript",
949
+ workDir: join2(homedir2(), "leetcode")
950
+ }
951
+ });
952
+ var config = {
953
+ getConfig() {
954
+ return {
955
+ language: configStore.get("language"),
956
+ editor: configStore.get("editor"),
957
+ workDir: configStore.get("workDir"),
958
+ repo: configStore.get("repo")
959
+ };
960
+ },
961
+ setLanguage(language) {
962
+ configStore.set("language", language);
963
+ },
964
+ setEditor(editor) {
965
+ configStore.set("editor", editor);
966
+ },
967
+ setWorkDir(workDir) {
968
+ configStore.set("workDir", workDir);
969
+ },
970
+ setRepo(repo) {
971
+ configStore.set("repo", repo);
972
+ },
973
+ deleteRepo() {
974
+ configStore.delete("repo");
975
+ },
976
+ getLanguage() {
977
+ return configStore.get("language");
978
+ },
979
+ getEditor() {
980
+ return configStore.get("editor");
981
+ },
982
+ getWorkDir() {
983
+ return configStore.get("workDir");
984
+ },
985
+ getRepo() {
986
+ return configStore.get("repo");
987
+ },
988
+ getPath() {
989
+ return configStore.path;
990
+ }
991
+ };
992
+
997
993
  // src/utils/templates.ts
998
994
  var LANGUAGE_EXTENSIONS = {
999
995
  typescript: "ts",
@@ -1187,12 +1183,12 @@ async function pickCommand(idOrSlug, options) {
1187
1183
  const workDir = config.getWorkDir();
1188
1184
  const difficulty = problem.difficulty;
1189
1185
  const category = problem.topicTags.length > 0 ? problem.topicTags[0].name.replace(/[^\w\s-]/g, "").trim() : "Uncategorized";
1190
- const targetDir = join2(workDir, difficulty, category);
1186
+ const targetDir = join3(workDir, difficulty, category);
1191
1187
  if (!existsSync(targetDir)) {
1192
1188
  await mkdir(targetDir, { recursive: true });
1193
1189
  }
1194
1190
  const fileName = getSolutionFileName(problem.questionFrontendId, problem.titleSlug, language);
1195
- const filePath = join2(targetDir, fileName);
1191
+ const filePath = join3(targetDir, fileName);
1196
1192
  if (existsSync(filePath)) {
1197
1193
  spinner.warn(`File already exists: ${fileName}`);
1198
1194
  console.log(chalk7.gray(`Path: ${filePath}`));
@@ -1271,19 +1267,23 @@ import chalk8 from "chalk";
1271
1267
  // src/utils/fileUtils.ts
1272
1268
  import { readdir } from "fs/promises";
1273
1269
  import { existsSync as existsSync2 } from "fs";
1274
- import { join as join3 } from "path";
1270
+ import { join as join4 } from "path";
1275
1271
  var MAX_SEARCH_DEPTH = 5;
1276
1272
  async function findSolutionFile(dir, problemId, currentDepth = 0) {
1277
1273
  if (!existsSync2(dir)) return null;
1278
1274
  if (currentDepth >= MAX_SEARCH_DEPTH) return null;
1279
1275
  const entries = await readdir(dir, { withFileTypes: true });
1280
1276
  for (const entry of entries) {
1281
- const fullPath = join3(dir, entry.name);
1277
+ if (entry.name.startsWith(".")) continue;
1278
+ const fullPath = join4(dir, entry.name);
1282
1279
  if (entry.isDirectory()) {
1283
1280
  const found = await findSolutionFile(fullPath, problemId, currentDepth + 1);
1284
1281
  if (found) return found;
1285
1282
  } else if (entry.name.startsWith(`${problemId}.`)) {
1286
- return fullPath;
1283
+ const ext = entry.name.split(".").pop()?.toLowerCase();
1284
+ if (ext && ext in EXT_TO_LANG_MAP) {
1285
+ return fullPath;
1286
+ }
1287
1287
  }
1288
1288
  }
1289
1289
  return null;
@@ -1293,7 +1293,7 @@ async function findFileByName(dir, fileName, currentDepth = 0) {
1293
1293
  if (currentDepth >= MAX_SEARCH_DEPTH) return null;
1294
1294
  const entries = await readdir(dir, { withFileTypes: true });
1295
1295
  for (const entry of entries) {
1296
- const fullPath = join3(dir, entry.name);
1296
+ const fullPath = join4(dir, entry.name);
1297
1297
  if (entry.isDirectory()) {
1298
1298
  const found = await findFileByName(fullPath, fileName, currentDepth + 1);
1299
1299
  if (found) return found;
@@ -1303,6 +1303,19 @@ async function findFileByName(dir, fileName, currentDepth = 0) {
1303
1303
  }
1304
1304
  return null;
1305
1305
  }
1306
+ var EXT_TO_LANG_MAP = {
1307
+ ts: "typescript",
1308
+ js: "javascript",
1309
+ py: "python3",
1310
+ java: "java",
1311
+ cpp: "cpp",
1312
+ c: "c",
1313
+ cs: "csharp",
1314
+ go: "go",
1315
+ rs: "rust",
1316
+ kt: "kotlin",
1317
+ swift: "swift"
1318
+ };
1306
1319
  function getLangSlugFromExtension(ext) {
1307
1320
  const langMap = {
1308
1321
  ts: "typescript",
@@ -1398,6 +1411,82 @@ import { existsSync as existsSync4 } from "fs";
1398
1411
  import { basename as basename2 } from "path";
1399
1412
  import ora6 from "ora";
1400
1413
  import chalk9 from "chalk";
1414
+
1415
+ // src/storage/timer.ts
1416
+ import Conf3 from "conf";
1417
+ import { homedir as homedir3 } from "os";
1418
+ import { join as join5 } from "path";
1419
+ var timerStore = new Conf3({
1420
+ configName: "timer",
1421
+ cwd: join5(homedir3(), ".leetcode"),
1422
+ defaults: {
1423
+ solveTimes: {},
1424
+ activeTimer: null
1425
+ }
1426
+ });
1427
+ var timerStorage = {
1428
+ startTimer(problemId, title, difficulty, durationMinutes) {
1429
+ timerStore.set("activeTimer", {
1430
+ problemId,
1431
+ title,
1432
+ difficulty,
1433
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1434
+ durationMinutes
1435
+ });
1436
+ },
1437
+ getActiveTimer() {
1438
+ return timerStore.get("activeTimer");
1439
+ },
1440
+ stopTimer() {
1441
+ const active = timerStore.get("activeTimer");
1442
+ if (!active) return null;
1443
+ const startedAt = new Date(active.startedAt);
1444
+ const now = /* @__PURE__ */ new Date();
1445
+ const durationSeconds = Math.floor((now.getTime() - startedAt.getTime()) / 1e3);
1446
+ timerStore.set("activeTimer", null);
1447
+ return { durationSeconds };
1448
+ },
1449
+ recordSolveTime(problemId, title, difficulty, durationSeconds, timerMinutes) {
1450
+ const solveTimes = timerStore.get("solveTimes") ?? {};
1451
+ if (!solveTimes[problemId]) {
1452
+ solveTimes[problemId] = [];
1453
+ }
1454
+ solveTimes[problemId].push({
1455
+ problemId,
1456
+ title,
1457
+ difficulty,
1458
+ solvedAt: (/* @__PURE__ */ new Date()).toISOString(),
1459
+ durationSeconds,
1460
+ timerMinutes
1461
+ });
1462
+ timerStore.set("solveTimes", solveTimes);
1463
+ },
1464
+ getSolveTimes(problemId) {
1465
+ const solveTimes = timerStore.get("solveTimes") ?? {};
1466
+ return solveTimes[problemId] ?? [];
1467
+ },
1468
+ getAllSolveTimes() {
1469
+ return timerStore.get("solveTimes") ?? {};
1470
+ },
1471
+ getStats() {
1472
+ const solveTimes = timerStore.get("solveTimes") ?? {};
1473
+ let totalProblems = 0;
1474
+ let totalTime = 0;
1475
+ for (const times of Object.values(solveTimes)) {
1476
+ totalProblems += times.length;
1477
+ for (const t of times) {
1478
+ totalTime += t.durationSeconds;
1479
+ }
1480
+ }
1481
+ return {
1482
+ totalProblems,
1483
+ totalTime,
1484
+ avgTime: totalProblems > 0 ? Math.floor(totalTime / totalProblems) : 0
1485
+ };
1486
+ }
1487
+ };
1488
+
1489
+ // src/commands/submit.ts
1401
1490
  async function submitCommand(fileOrId) {
1402
1491
  const { authorized } = await requireAuth();
1403
1492
  if (!authorized) return;
@@ -1457,6 +1546,35 @@ async function submitCommand(fileOrId) {
1457
1546
  );
1458
1547
  spinner.stop();
1459
1548
  displaySubmissionResult(result);
1549
+ if (result.status_msg === "Accepted") {
1550
+ const activeTimer = timerStorage.getActiveTimer();
1551
+ if (activeTimer && activeTimer.problemId === problemId) {
1552
+ const timerResult = timerStorage.stopTimer();
1553
+ if (timerResult) {
1554
+ timerStorage.recordSolveTime(
1555
+ problemId,
1556
+ problem.title,
1557
+ problem.difficulty,
1558
+ timerResult.durationSeconds,
1559
+ activeTimer.durationMinutes
1560
+ );
1561
+ const mins = Math.floor(timerResult.durationSeconds / 60);
1562
+ const secs = timerResult.durationSeconds % 60;
1563
+ const timeStr = `${mins}m ${secs}s`;
1564
+ const withinLimit = timerResult.durationSeconds <= activeTimer.durationMinutes * 60;
1565
+ console.log();
1566
+ console.log(chalk9.bold("\u23F1\uFE0F Timer Result:"));
1567
+ console.log(
1568
+ ` Solved in ${withinLimit ? chalk9.green(timeStr) : chalk9.yellow(timeStr)} (limit: ${activeTimer.durationMinutes}m)`
1569
+ );
1570
+ if (withinLimit) {
1571
+ console.log(chalk9.green(" \u2713 Within time limit!"));
1572
+ } else {
1573
+ console.log(chalk9.yellow(" \u26A0 Exceeded time limit"));
1574
+ }
1575
+ }
1576
+ }
1577
+ }
1460
1578
  } catch (error) {
1461
1579
  spinner.fail("Submission failed");
1462
1580
  if (error instanceof Error) {
@@ -1704,7 +1822,7 @@ async function randomCommand(options) {
1704
1822
  // src/commands/submissions.ts
1705
1823
  import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1706
1824
  import { existsSync as existsSync5 } from "fs";
1707
- import { join as join4 } from "path";
1825
+ import { join as join6 } from "path";
1708
1826
  import ora10 from "ora";
1709
1827
  import chalk14 from "chalk";
1710
1828
  async function submissionsCommand(idOrSlug, options) {
@@ -1758,7 +1876,7 @@ async function submissionsCommand(idOrSlug, options) {
1758
1876
  const workDir = config.getWorkDir();
1759
1877
  const difficulty = problem.difficulty;
1760
1878
  const category = problem.topicTags.length > 0 ? problem.topicTags[0].name.replace(/[^\w\s-]/g, "").trim() : "Uncategorized";
1761
- const targetDir = join4(workDir, difficulty, category);
1879
+ const targetDir = join6(workDir, difficulty, category);
1762
1880
  if (!existsSync5(targetDir)) {
1763
1881
  await mkdir2(targetDir, { recursive: true });
1764
1882
  }
@@ -1766,7 +1884,7 @@ async function submissionsCommand(idOrSlug, options) {
1766
1884
  const supportedLang = LANG_SLUG_MAP[langSlug] ?? "txt";
1767
1885
  const ext = LANGUAGE_EXTENSIONS[supportedLang] ?? langSlug;
1768
1886
  const fileName = `${problem.questionFrontendId}.${problem.titleSlug}.submission-${lastAC.id}.${ext}`;
1769
- const filePath = join4(targetDir, fileName);
1887
+ const filePath = join6(targetDir, fileName);
1770
1888
  await writeFile2(filePath, details.code, "utf-8");
1771
1889
  downloadSpinner.succeed(`Downloaded to ${chalk14.green(fileName)}`);
1772
1890
  console.log(chalk14.gray(`Path: ${filePath}`));
@@ -1872,7 +1990,7 @@ async function configInteractiveCommand() {
1872
1990
  }
1873
1991
  function showCurrentConfig() {
1874
1992
  const currentConfig = config.getConfig();
1875
- const credentials = config.getCredentials();
1993
+ const creds = credentials.get();
1876
1994
  console.log();
1877
1995
  console.log(chalk15.bold("LeetCode CLI Configuration"));
1878
1996
  console.log(chalk15.gray("\u2500".repeat(40)));
@@ -1883,7 +2001,7 @@ function showCurrentConfig() {
1883
2001
  console.log(chalk15.gray("Editor: "), chalk15.white(currentConfig.editor ?? "(not set)"));
1884
2002
  console.log(chalk15.gray("Work Dir: "), chalk15.white(currentConfig.workDir));
1885
2003
  console.log(chalk15.gray("Repo URL: "), chalk15.white(currentConfig.repo ?? "(not set)"));
1886
- console.log(chalk15.gray("Logged in: "), credentials ? chalk15.green("Yes") : chalk15.yellow("No"));
2004
+ console.log(chalk15.gray("Logged in: "), creds ? chalk15.green("Yes") : chalk15.yellow("No"));
1887
2005
  }
1888
2006
 
1889
2007
  // src/commands/bookmark.ts
@@ -1892,8 +2010,8 @@ import Table2 from "cli-table3";
1892
2010
  import ora11 from "ora";
1893
2011
 
1894
2012
  // src/storage/bookmarks.ts
1895
- import Conf2 from "conf";
1896
- var bookmarksStore = new Conf2({
2013
+ import Conf4 from "conf";
2014
+ var bookmarksStore = new Conf4({
1897
2015
  projectName: "leetcode-cli-bookmarks",
1898
2016
  defaults: { bookmarks: [] }
1899
2017
  });
@@ -2046,7 +2164,7 @@ function colorDifficulty2(difficulty) {
2046
2164
 
2047
2165
  // src/commands/notes.ts
2048
2166
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
2049
- import { join as join5 } from "path";
2167
+ import { join as join7 } from "path";
2050
2168
  import { existsSync as existsSync6 } from "fs";
2051
2169
  import chalk17 from "chalk";
2052
2170
  async function notesCommand(problemId, action) {
@@ -2056,8 +2174,8 @@ async function notesCommand(problemId, action) {
2056
2174
  return;
2057
2175
  }
2058
2176
  const noteAction = action === "view" ? "view" : "edit";
2059
- const notesDir = join5(config.getWorkDir(), ".notes");
2060
- const notePath = join5(notesDir, `${problemId}.md`);
2177
+ const notesDir = join7(config.getWorkDir(), ".notes");
2178
+ const notePath = join7(notesDir, `${problemId}.md`);
2061
2179
  if (!existsSync6(notesDir)) {
2062
2180
  await mkdir3(notesDir, { recursive: true });
2063
2181
  }
@@ -2376,162 +2494,644 @@ async function syncCommand() {
2376
2494
  }
2377
2495
  }
2378
2496
 
2497
+ // src/commands/timer.ts
2498
+ import ora14 from "ora";
2499
+ import chalk20 from "chalk";
2500
+ var DEFAULT_TIMES = {
2501
+ Easy: 20,
2502
+ Medium: 40,
2503
+ Hard: 60
2504
+ };
2505
+ function formatDuration(seconds) {
2506
+ if (seconds < 60) {
2507
+ return `${seconds}s`;
2508
+ } else if (seconds < 3600) {
2509
+ const mins = Math.floor(seconds / 60);
2510
+ const secs = seconds % 60;
2511
+ return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;
2512
+ } else {
2513
+ const hours = Math.floor(seconds / 3600);
2514
+ const mins = Math.floor(seconds % 3600 / 60);
2515
+ return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
2516
+ }
2517
+ }
2518
+ async function timerCommand(idOrSlug, options) {
2519
+ if (options.stats) {
2520
+ await showTimerStats(idOrSlug);
2521
+ return;
2522
+ }
2523
+ if (options.stop) {
2524
+ await stopActiveTimer();
2525
+ return;
2526
+ }
2527
+ if (!idOrSlug) {
2528
+ console.log(chalk20.yellow("Please provide a problem ID to start the timer."));
2529
+ console.log(chalk20.gray("Usage: leetcode timer <id>"));
2530
+ console.log(chalk20.gray(" leetcode timer --stats"));
2531
+ console.log(chalk20.gray(" leetcode timer --stop"));
2532
+ return;
2533
+ }
2534
+ const { authorized } = await requireAuth();
2535
+ if (!authorized) return;
2536
+ const activeTimer = timerStorage.getActiveTimer();
2537
+ if (activeTimer) {
2538
+ const startedAt = new Date(activeTimer.startedAt);
2539
+ const elapsed = Math.floor((Date.now() - startedAt.getTime()) / 1e3);
2540
+ console.log(chalk20.yellow("\u26A0\uFE0F You have an active timer running:"));
2541
+ console.log(chalk20.white(` Problem: ${activeTimer.title}`));
2542
+ console.log(chalk20.white(` Elapsed: ${formatDuration(elapsed)}`));
2543
+ console.log();
2544
+ console.log(chalk20.gray("Use `leetcode timer --stop` to stop it first."));
2545
+ return;
2546
+ }
2547
+ const spinner = ora14("Fetching problem...").start();
2548
+ try {
2549
+ let problem;
2550
+ if (/^\d+$/.test(idOrSlug)) {
2551
+ problem = await leetcodeClient.getProblemById(idOrSlug);
2552
+ } else {
2553
+ problem = await leetcodeClient.getProblem(idOrSlug);
2554
+ }
2555
+ if (!problem) {
2556
+ spinner.fail(`Problem "${idOrSlug}" not found`);
2557
+ return;
2558
+ }
2559
+ spinner.stop();
2560
+ const durationMinutes = options.minutes ?? DEFAULT_TIMES[problem.difficulty] ?? 30;
2561
+ timerStorage.startTimer(
2562
+ problem.questionFrontendId,
2563
+ problem.title,
2564
+ problem.difficulty,
2565
+ durationMinutes
2566
+ );
2567
+ console.log();
2568
+ console.log(chalk20.bold.cyan("\u23F1\uFE0F Interview Mode Started!"));
2569
+ console.log(chalk20.gray("\u2500".repeat(50)));
2570
+ console.log();
2571
+ console.log(chalk20.white(`Problem: ${problem.questionFrontendId}. ${problem.title}`));
2572
+ console.log(chalk20.white(`Difficulty: ${chalk20.bold(problem.difficulty)}`));
2573
+ console.log(chalk20.white(`Time Limit: ${chalk20.bold.yellow(durationMinutes + " minutes")}`));
2574
+ console.log();
2575
+ console.log(chalk20.gray("\u2500".repeat(50)));
2576
+ console.log(chalk20.green("\u2713 Timer is running in background"));
2577
+ console.log(chalk20.gray(" When you submit successfully, your time will be recorded."));
2578
+ console.log(chalk20.gray(" Use `leetcode timer --stop` to cancel."));
2579
+ console.log();
2580
+ await pickCommand(idOrSlug, { open: true });
2581
+ } catch (error) {
2582
+ spinner.fail("Failed to start timer");
2583
+ if (error instanceof Error) {
2584
+ console.log(chalk20.red(error.message));
2585
+ }
2586
+ }
2587
+ }
2588
+ async function stopActiveTimer() {
2589
+ const result = timerStorage.stopTimer();
2590
+ if (!result) {
2591
+ console.log(chalk20.yellow("No active timer to stop."));
2592
+ return;
2593
+ }
2594
+ console.log(chalk20.green("\u23F1\uFE0F Timer stopped."));
2595
+ console.log(chalk20.gray(`Elapsed time: ${formatDuration(result.durationSeconds)}`));
2596
+ console.log(chalk20.gray("(Time not recorded since problem was not submitted)"));
2597
+ }
2598
+ async function showTimerStats(problemId) {
2599
+ if (problemId && /^\d+$/.test(problemId)) {
2600
+ const times = timerStorage.getSolveTimes(problemId);
2601
+ if (times.length === 0) {
2602
+ console.log(chalk20.yellow(`No solve times recorded for problem ${problemId}`));
2603
+ return;
2604
+ }
2605
+ console.log();
2606
+ console.log(chalk20.bold(`\u23F1\uFE0F Solve Times for Problem ${problemId}`));
2607
+ console.log(chalk20.gray("\u2500".repeat(40)));
2608
+ for (const entry of times) {
2609
+ const date = new Date(entry.solvedAt).toLocaleDateString();
2610
+ const duration = formatDuration(entry.durationSeconds);
2611
+ const limit = entry.timerMinutes;
2612
+ const withinLimit = entry.durationSeconds <= limit * 60;
2613
+ console.log(
2614
+ ` ${date} ${withinLimit ? chalk20.green(duration) : chalk20.red(duration)} (limit: ${limit}m)`
2615
+ );
2616
+ }
2617
+ } else {
2618
+ const stats = timerStorage.getStats();
2619
+ const allTimes = timerStorage.getAllSolveTimes();
2620
+ console.log();
2621
+ console.log(chalk20.bold("\u23F1\uFE0F Timer Statistics"));
2622
+ console.log(chalk20.gray("\u2500".repeat(40)));
2623
+ console.log();
2624
+ console.log(` Problems timed: ${chalk20.cyan(stats.totalProblems)}`);
2625
+ console.log(` Total time: ${chalk20.cyan(formatDuration(stats.totalTime))}`);
2626
+ console.log(` Average time: ${chalk20.cyan(formatDuration(stats.avgTime))}`);
2627
+ console.log();
2628
+ const recentSolves = [];
2629
+ for (const [id, times] of Object.entries(allTimes)) {
2630
+ for (const t of times) {
2631
+ recentSolves.push({
2632
+ problemId: id,
2633
+ title: t.title,
2634
+ duration: t.durationSeconds,
2635
+ date: t.solvedAt
2636
+ });
2637
+ }
2638
+ }
2639
+ if (recentSolves.length > 0) {
2640
+ recentSolves.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
2641
+ console.log(chalk20.bold(" Recent Solves:"));
2642
+ for (const solve of recentSolves.slice(0, 5)) {
2643
+ const date = new Date(solve.date).toLocaleDateString();
2644
+ console.log(
2645
+ chalk20.gray(` ${date} `) + chalk20.white(`${solve.problemId}. ${solve.title.substring(0, 25)}`) + chalk20.gray(" ") + chalk20.cyan(formatDuration(solve.duration))
2646
+ );
2647
+ }
2648
+ }
2649
+ console.log();
2650
+ }
2651
+ }
2652
+
2653
+ // src/commands/collab.ts
2654
+ import chalk21 from "chalk";
2655
+ import ora15 from "ora";
2656
+ import { readFile as readFile4 } from "fs/promises";
2657
+
2658
+ // src/services/supabase.ts
2659
+ import { createClient } from "@supabase/supabase-js";
2660
+ var SUPABASE_URL = "https://abagrmwdpvnfyuqizyym.supabase.co";
2661
+ var SUPABASE_ANON_KEY = "sb_publishable_indrKu8VJmASdyLp7w8Hog_OyqT17cV";
2662
+ var supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
2663
+
2664
+ // src/storage/collab.ts
2665
+ import Conf5 from "conf";
2666
+ import { homedir as homedir4 } from "os";
2667
+ import { join as join8 } from "path";
2668
+ var collabStore = new Conf5({
2669
+ configName: "collab",
2670
+ cwd: join8(homedir4(), ".leetcode"),
2671
+ defaults: {
2672
+ session: null
2673
+ }
2674
+ });
2675
+ var collabStorage = {
2676
+ getSession() {
2677
+ return collabStore.get("session");
2678
+ },
2679
+ setSession(session) {
2680
+ if (session) {
2681
+ collabStore.set("session", session);
2682
+ } else {
2683
+ collabStore.delete("session");
2684
+ }
2685
+ },
2686
+ getPath() {
2687
+ return collabStore.path;
2688
+ }
2689
+ };
2690
+
2691
+ // src/services/collab.ts
2692
+ function generateRoomCode() {
2693
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
2694
+ let code = "";
2695
+ for (let i = 0; i < 6; i++) {
2696
+ code += chars.charAt(Math.floor(Math.random() * chars.length));
2697
+ }
2698
+ return code;
2699
+ }
2700
+ var collabService = {
2701
+ getSession() {
2702
+ return collabStorage.getSession();
2703
+ },
2704
+ async createRoom(problemId, username) {
2705
+ const roomCode = generateRoomCode();
2706
+ const { error } = await supabase.from("collab_rooms").insert({
2707
+ room_code: roomCode,
2708
+ problem_id: problemId,
2709
+ host_username: username,
2710
+ host_code: "",
2711
+ guest_username: null,
2712
+ guest_code: null
2713
+ });
2714
+ if (error) {
2715
+ return { error: error.message };
2716
+ }
2717
+ collabStorage.setSession({
2718
+ roomCode,
2719
+ problemId,
2720
+ isHost: true,
2721
+ username
2722
+ });
2723
+ return { roomCode };
2724
+ },
2725
+ async joinRoom(roomCode, username) {
2726
+ const { data: room, error: fetchError } = await supabase.from("collab_rooms").select("*").eq("room_code", roomCode.toUpperCase()).single();
2727
+ if (fetchError || !room) {
2728
+ return { error: "Room not found" };
2729
+ }
2730
+ const { error: updateError } = await supabase.from("collab_rooms").update({ guest_username: username }).eq("room_code", roomCode.toUpperCase());
2731
+ if (updateError) {
2732
+ return { error: updateError.message };
2733
+ }
2734
+ collabStorage.setSession({
2735
+ roomCode: roomCode.toUpperCase(),
2736
+ problemId: room.problem_id,
2737
+ isHost: false,
2738
+ username
2739
+ });
2740
+ return { problemId: room.problem_id };
2741
+ },
2742
+ async syncCode(code) {
2743
+ const session = collabStorage.getSession();
2744
+ if (!session) {
2745
+ return { success: false, error: "No active session" };
2746
+ }
2747
+ const column = session.isHost ? "host_code" : "guest_code";
2748
+ const { error } = await supabase.from("collab_rooms").update({ [column]: code }).eq("room_code", session.roomCode);
2749
+ if (error) {
2750
+ return { success: false, error: error.message };
2751
+ }
2752
+ return { success: true };
2753
+ },
2754
+ async getPartnerCode() {
2755
+ const session = collabStorage.getSession();
2756
+ if (!session) {
2757
+ return { error: "No active session" };
2758
+ }
2759
+ const { data: room, error } = await supabase.from("collab_rooms").select("*").eq("room_code", session.roomCode).single();
2760
+ if (error || !room) {
2761
+ return { error: "Room not found" };
2762
+ }
2763
+ if (session.isHost) {
2764
+ return {
2765
+ code: room.guest_code || "",
2766
+ username: room.guest_username || "Partner"
2767
+ };
2768
+ } else {
2769
+ return {
2770
+ code: room.host_code || "",
2771
+ username: room.host_username || "Host"
2772
+ };
2773
+ }
2774
+ },
2775
+ async getRoomStatus() {
2776
+ const session = collabStorage.getSession();
2777
+ if (!session) {
2778
+ return { error: "No active session" };
2779
+ }
2780
+ const { data: room, error } = await supabase.from("collab_rooms").select("*").eq("room_code", session.roomCode).single();
2781
+ if (error || !room) {
2782
+ return { error: "Room not found" };
2783
+ }
2784
+ return {
2785
+ host: room.host_username,
2786
+ guest: room.guest_username,
2787
+ hasHostCode: !!room.host_code,
2788
+ hasGuestCode: !!room.guest_code
2789
+ };
2790
+ },
2791
+ async leaveRoom() {
2792
+ const session = collabStorage.getSession();
2793
+ if (session) {
2794
+ if (session.isHost) {
2795
+ await supabase.from("collab_rooms").delete().eq("room_code", session.roomCode);
2796
+ }
2797
+ }
2798
+ collabStorage.setSession(null);
2799
+ }
2800
+ };
2801
+
2802
+ // src/commands/collab.ts
2803
+ async function collabHostCommand(problemId) {
2804
+ const { authorized, username } = await requireAuth();
2805
+ if (!authorized || !username) return;
2806
+ const spinner = ora15("Creating collaboration room...").start();
2807
+ try {
2808
+ const result = await collabService.createRoom(problemId, username);
2809
+ if ("error" in result) {
2810
+ spinner.fail(result.error);
2811
+ return;
2812
+ }
2813
+ spinner.succeed("Room created!");
2814
+ console.log();
2815
+ console.log(chalk21.bold.cyan("\u{1F465} Collaborative Coding Session"));
2816
+ console.log(chalk21.gray("\u2500".repeat(50)));
2817
+ console.log();
2818
+ console.log(chalk21.white(`Room Code: ${chalk21.bold.green(result.roomCode)}`));
2819
+ console.log(chalk21.white(`Problem: ${problemId}`));
2820
+ console.log();
2821
+ console.log(chalk21.gray("Share this code with your partner:"));
2822
+ console.log(chalk21.yellow(` leetcode collab join ${result.roomCode}`));
2823
+ console.log();
2824
+ console.log(chalk21.gray("\u2500".repeat(50)));
2825
+ console.log(chalk21.gray("After solving, sync and compare:"));
2826
+ console.log(chalk21.gray(" leetcode collab sync - Upload your solution"));
2827
+ console.log(chalk21.gray(" leetcode collab compare - See both solutions"));
2828
+ console.log(chalk21.gray(" leetcode collab status - Check room status"));
2829
+ console.log(chalk21.gray(" leetcode collab leave - End session"));
2830
+ console.log();
2831
+ await pickCommand(problemId, { open: true });
2832
+ } catch (error) {
2833
+ spinner.fail("Failed to create room");
2834
+ if (error instanceof Error) {
2835
+ console.log(chalk21.red(error.message));
2836
+ }
2837
+ }
2838
+ }
2839
+ async function collabJoinCommand(roomCode) {
2840
+ const { authorized, username } = await requireAuth();
2841
+ if (!authorized || !username) return;
2842
+ const spinner = ora15(`Joining room ${roomCode}...`).start();
2843
+ try {
2844
+ const result = await collabService.joinRoom(roomCode.toUpperCase(), username);
2845
+ if ("error" in result) {
2846
+ spinner.fail(result.error);
2847
+ return;
2848
+ }
2849
+ spinner.succeed("Joined room!");
2850
+ console.log();
2851
+ console.log(chalk21.bold.cyan("\u{1F465} Collaborative Coding Session"));
2852
+ console.log(chalk21.gray("\u2500".repeat(50)));
2853
+ console.log();
2854
+ console.log(chalk21.white(`Room Code: ${chalk21.bold.green(roomCode.toUpperCase())}`));
2855
+ console.log(chalk21.white(`Problem: ${result.problemId}`));
2856
+ console.log();
2857
+ console.log(chalk21.gray("\u2500".repeat(50)));
2858
+ console.log(chalk21.gray("After solving, sync and compare:"));
2859
+ console.log(chalk21.gray(" leetcode collab sync - Upload your solution"));
2860
+ console.log(chalk21.gray(" leetcode collab compare - See both solutions"));
2861
+ console.log(chalk21.gray(" leetcode collab status - Check room status"));
2862
+ console.log(chalk21.gray(" leetcode collab leave - End session"));
2863
+ console.log();
2864
+ await pickCommand(result.problemId, { open: true });
2865
+ } catch (error) {
2866
+ spinner.fail("Failed to join room");
2867
+ if (error instanceof Error) {
2868
+ console.log(chalk21.red(error.message));
2869
+ }
2870
+ }
2871
+ }
2872
+ async function collabSyncCommand() {
2873
+ const session = collabService.getSession();
2874
+ if (!session) {
2875
+ console.log(chalk21.yellow("No active collaboration session."));
2876
+ console.log(chalk21.gray("Use `leetcode collab host <id>` or `leetcode collab join <code>` first."));
2877
+ return;
2878
+ }
2879
+ const spinner = ora15("Syncing your code...").start();
2880
+ const workDir = config.getWorkDir();
2881
+ const filePath = await findSolutionFile(workDir, session.problemId);
2882
+ if (!filePath) {
2883
+ spinner.fail(`No solution file found for problem ${session.problemId}`);
2884
+ return;
2885
+ }
2886
+ const code = await readFile4(filePath, "utf-8");
2887
+ const result = await collabService.syncCode(code);
2888
+ if (result.success) {
2889
+ spinner.succeed("Code synced successfully!");
2890
+ console.log(chalk21.gray(`Uploaded ${code.split("\n").length} lines from ${filePath}`));
2891
+ } else {
2892
+ spinner.fail(result.error || "Sync failed");
2893
+ }
2894
+ }
2895
+ async function collabCompareCommand() {
2896
+ const session = collabService.getSession();
2897
+ if (!session) {
2898
+ console.log(chalk21.yellow("No active collaboration session."));
2899
+ console.log(chalk21.gray("Use `leetcode collab host <id>` or `leetcode collab join <code>` first."));
2900
+ return;
2901
+ }
2902
+ const spinner = ora15("Fetching solutions...").start();
2903
+ const workDir = config.getWorkDir();
2904
+ const filePath = await findSolutionFile(workDir, session.problemId);
2905
+ if (!filePath) {
2906
+ spinner.fail(`No solution file found for problem ${session.problemId}`);
2907
+ return;
2908
+ }
2909
+ const myCode = await readFile4(filePath, "utf-8");
2910
+ const partnerResult = await collabService.getPartnerCode();
2911
+ if ("error" in partnerResult) {
2912
+ spinner.fail(partnerResult.error);
2913
+ return;
2914
+ }
2915
+ spinner.stop();
2916
+ if (!partnerResult.code) {
2917
+ console.log(chalk21.yellow("Partner has not synced their code yet."));
2918
+ console.log(chalk21.gray("Ask them to run `leetcode collab sync`."));
2919
+ return;
2920
+ }
2921
+ console.log();
2922
+ console.log(chalk21.bold.cyan("\u{1F4CA} Solution Comparison"));
2923
+ console.log(chalk21.gray("\u2500".repeat(60)));
2924
+ console.log();
2925
+ console.log(chalk21.bold.green(`\u25B8 Your Solution (${session.username})`));
2926
+ console.log(chalk21.gray("\u2500".repeat(60)));
2927
+ const myLines = myCode.split("\n");
2928
+ for (let i = 0; i < myLines.length; i++) {
2929
+ const lineNum = String(i + 1).padStart(3, " ");
2930
+ console.log(`${chalk21.gray(lineNum)} ${myLines[i]}`);
2931
+ }
2932
+ console.log();
2933
+ console.log(chalk21.bold.blue(`\u25B8 ${partnerResult.username}'s Solution`));
2934
+ console.log(chalk21.gray("\u2500".repeat(60)));
2935
+ const partnerLines = partnerResult.code.split("\n");
2936
+ for (let i = 0; i < partnerLines.length; i++) {
2937
+ const lineNum = String(i + 1).padStart(3, " ");
2938
+ console.log(`${chalk21.gray(lineNum)} ${partnerLines[i]}`);
2939
+ }
2940
+ console.log();
2941
+ console.log(chalk21.gray("\u2500".repeat(60)));
2942
+ console.log(chalk21.gray(`Your code: ${myLines.length} lines | Partner: ${partnerLines.length} lines`));
2943
+ console.log();
2944
+ }
2945
+ async function collabLeaveCommand() {
2946
+ const session = collabService.getSession();
2947
+ if (!session) {
2948
+ console.log(chalk21.yellow("No active collaboration session."));
2949
+ return;
2950
+ }
2951
+ await collabService.leaveRoom();
2952
+ console.log(chalk21.green("\u2713 Left the collaboration session."));
2953
+ }
2954
+ async function collabStatusCommand() {
2955
+ const session = collabService.getSession();
2956
+ if (!session) {
2957
+ console.log(chalk21.yellow("No active collaboration session."));
2958
+ console.log(chalk21.gray("Use `leetcode collab host <id>` or `leetcode collab join <code>` to start."));
2959
+ return;
2960
+ }
2961
+ const status = await collabService.getRoomStatus();
2962
+ if ("error" in status) {
2963
+ console.log(chalk21.red(status.error));
2964
+ return;
2965
+ }
2966
+ console.log();
2967
+ console.log(chalk21.bold.cyan("\u{1F465} Collaboration Status"));
2968
+ console.log(chalk21.gray("\u2500".repeat(40)));
2969
+ console.log(` Room: ${chalk21.green(session.roomCode)}`);
2970
+ console.log(` Problem: ${session.problemId}`);
2971
+ console.log(` Role: ${session.isHost ? "Host" : "Guest"}`);
2972
+ console.log();
2973
+ console.log(chalk21.bold(" Participants:"));
2974
+ console.log(` Host: ${status.host} ${status.hasHostCode ? chalk21.green("\u2713 synced") : chalk21.gray("pending")}`);
2975
+ console.log(` Guest: ${status.guest || chalk21.gray("(waiting...)")} ${status.hasGuestCode ? chalk21.green("\u2713 synced") : chalk21.gray("pending")}`);
2976
+ console.log();
2977
+ }
2978
+
2379
2979
  // src/index.ts
2380
2980
  var program = new Command();
2381
2981
  program.configureHelp({
2382
2982
  sortSubcommands: true,
2383
- subcommandTerm: (cmd) => chalk20.cyan(cmd.name()) + (cmd.alias() ? chalk20.gray(`|${cmd.alias()}`) : ""),
2384
- subcommandDescription: (cmd) => chalk20.white(cmd.description()),
2385
- optionTerm: (option) => chalk20.yellow(option.flags),
2386
- optionDescription: (option) => chalk20.white(option.description)
2983
+ subcommandTerm: (cmd) => chalk22.cyan(cmd.name()) + (cmd.alias() ? chalk22.gray(`|${cmd.alias()}`) : ""),
2984
+ subcommandDescription: (cmd) => chalk22.white(cmd.description()),
2985
+ optionTerm: (option) => chalk22.yellow(option.flags),
2986
+ optionDescription: (option) => chalk22.white(option.description)
2387
2987
  });
2388
- program.name("leetcode").usage("[command] [options]").description(chalk20.bold.cyan("\u{1F525} A modern LeetCode CLI built with TypeScript")).version("1.4.0", "-v, --version", "Output the version number").helpOption("-h, --help", "Display help for command").addHelpText("after", `
2389
- ${chalk20.yellow("Examples:")}
2390
- ${chalk20.cyan("$ leetcode login")} Login to LeetCode
2391
- ${chalk20.cyan("$ leetcode list -d easy")} List easy problems
2392
- ${chalk20.cyan("$ leetcode random -d medium")} Get random medium problem
2393
- ${chalk20.cyan("$ leetcode pick 1")} Start solving "Two Sum"
2394
- ${chalk20.cyan("$ leetcode test 1")} Test your solution
2395
- ${chalk20.cyan("$ leetcode submit 1")} Submit your solution
2988
+ program.name("leetcode").usage("[command] [options]").description(chalk22.bold.cyan("\u{1F525} A modern LeetCode CLI built with TypeScript")).version("1.6.0", "-v, --version", "Output the version number").helpOption("-h, --help", "Display help for command").addHelpText("after", `
2989
+ ${chalk22.yellow("Examples:")}
2990
+ ${chalk22.cyan("$ leetcode login")} Login to LeetCode
2991
+ ${chalk22.cyan("$ leetcode list -d easy")} List easy problems
2992
+ ${chalk22.cyan("$ leetcode random -d medium")} Get random medium problem
2993
+ ${chalk22.cyan("$ leetcode pick 1")} Start solving "Two Sum"
2994
+ ${chalk22.cyan("$ leetcode test 1")} Test your solution
2995
+ ${chalk22.cyan("$ leetcode submit 1")} Submit your solution
2396
2996
  `);
2397
2997
  program.command("login").description("Login to LeetCode with browser cookies").addHelpText("after", `
2398
- ${chalk20.yellow("How to login:")}
2399
- 1. Open ${chalk20.cyan("https://leetcode.com")} in your browser
2998
+ ${chalk22.yellow("How to login:")}
2999
+ 1. Open ${chalk22.cyan("https://leetcode.com")} in your browser
2400
3000
  2. Login to your account
2401
3001
  3. Open Developer Tools (F12) \u2192 Application \u2192 Cookies
2402
- 4. Copy values of ${chalk20.green("LEETCODE_SESSION")} and ${chalk20.green("csrftoken")}
3002
+ 4. Copy values of ${chalk22.green("LEETCODE_SESSION")} and ${chalk22.green("csrftoken")}
2403
3003
  5. Paste when prompted by this command
2404
3004
  `).action(loginCommand);
2405
3005
  program.command("logout").description("Clear stored credentials").action(logoutCommand);
2406
3006
  program.command("whoami").description("Check current login status").action(whoamiCommand);
2407
3007
  program.command("list").alias("l").description("List LeetCode problems").option("-d, --difficulty <level>", "Filter by difficulty (easy/medium/hard)").option("-s, --status <status>", "Filter by status (todo/solved/attempted)").option("-t, --tag <tags...>", "Filter by topic tags").option("-q, --search <keywords>", "Search by keywords").option("-n, --limit <number>", "Number of problems to show", "20").option("-p, --page <number>", "Page number", "1").addHelpText("after", `
2408
- ${chalk20.yellow("Examples:")}
2409
- ${chalk20.cyan("$ leetcode list")} List first 20 problems
2410
- ${chalk20.cyan("$ leetcode list -d easy")} List easy problems only
2411
- ${chalk20.cyan("$ leetcode list -s solved")} List your solved problems
2412
- ${chalk20.cyan("$ leetcode list -t array -t string")} Filter by multiple tags
2413
- ${chalk20.cyan('$ leetcode list -q "two sum"')} Search by keywords
2414
- ${chalk20.cyan("$ leetcode list -n 50 -p 2")} Show 50 problems, page 2
3008
+ ${chalk22.yellow("Examples:")}
3009
+ ${chalk22.cyan("$ leetcode list")} List first 20 problems
3010
+ ${chalk22.cyan("$ leetcode list -d easy")} List easy problems only
3011
+ ${chalk22.cyan("$ leetcode list -s solved")} List your solved problems
3012
+ ${chalk22.cyan("$ leetcode list -t array -t string")} Filter by multiple tags
3013
+ ${chalk22.cyan('$ leetcode list -q "two sum"')} Search by keywords
3014
+ ${chalk22.cyan("$ leetcode list -n 50 -p 2")} Show 50 problems, page 2
2415
3015
  `).action(listCommand);
2416
3016
  program.command("show <id>").alias("s").description("Show problem description").addHelpText("after", `
2417
- ${chalk20.yellow("Examples:")}
2418
- ${chalk20.cyan("$ leetcode show 1")} Show by problem ID
2419
- ${chalk20.cyan("$ leetcode show two-sum")} Show by problem slug
2420
- ${chalk20.cyan("$ leetcode s 412")} Short alias
3017
+ ${chalk22.yellow("Examples:")}
3018
+ ${chalk22.cyan("$ leetcode show 1")} Show by problem ID
3019
+ ${chalk22.cyan("$ leetcode show two-sum")} Show by problem slug
3020
+ ${chalk22.cyan("$ leetcode s 412")} Short alias
2421
3021
  `).action(showCommand);
2422
3022
  program.command("daily").alias("d").description("Show today's daily challenge").addHelpText("after", `
2423
- ${chalk20.yellow("Examples:")}
2424
- ${chalk20.cyan("$ leetcode daily")} Show today's challenge
2425
- ${chalk20.cyan("$ leetcode d")} Short alias
3023
+ ${chalk22.yellow("Examples:")}
3024
+ ${chalk22.cyan("$ leetcode daily")} Show today's challenge
3025
+ ${chalk22.cyan("$ leetcode d")} Short alias
2426
3026
  `).action(dailyCommand);
2427
3027
  program.command("random").alias("r").description("Get a random problem").option("-d, --difficulty <level>", "Filter by difficulty (easy/medium/hard)").option("-t, --tag <tag>", "Filter by topic tag").option("--pick", "Auto-generate solution file").option("--no-open", "Do not open file in editor").addHelpText("after", `
2428
- ${chalk20.yellow("Examples:")}
2429
- ${chalk20.cyan("$ leetcode random")} Get any random problem
2430
- ${chalk20.cyan("$ leetcode random -d medium")} Random medium problem
2431
- ${chalk20.cyan("$ leetcode random -t array")} Random array problem
2432
- ${chalk20.cyan("$ leetcode random --pick")} Random + create file
2433
- ${chalk20.cyan("$ leetcode r -d easy --pick")} Random easy + file
3028
+ ${chalk22.yellow("Examples:")}
3029
+ ${chalk22.cyan("$ leetcode random")} Get any random problem
3030
+ ${chalk22.cyan("$ leetcode random -d medium")} Random medium problem
3031
+ ${chalk22.cyan("$ leetcode random -t array")} Random array problem
3032
+ ${chalk22.cyan("$ leetcode random --pick")} Random + create file
3033
+ ${chalk22.cyan("$ leetcode r -d easy --pick")} Random easy + file
2434
3034
  `).action(randomCommand);
2435
3035
  program.command("pick <id>").alias("p").description("Generate solution file for a problem").option("-l, --lang <language>", "Programming language for the solution").option("--no-open", "Do not open file in editor").addHelpText("after", `
2436
- ${chalk20.yellow("Examples:")}
2437
- ${chalk20.cyan("$ leetcode pick 1")} Pick by problem ID
2438
- ${chalk20.cyan("$ leetcode pick two-sum")} Pick by problem slug
2439
- ${chalk20.cyan("$ leetcode pick 1 -l python3")} Pick with specific language
2440
- ${chalk20.cyan("$ leetcode pick 1 --no-open")} Create file without opening
2441
- ${chalk20.cyan("$ leetcode p 412")} Short alias
2442
-
2443
- ${chalk20.gray("Files are organized by: workDir/Difficulty/Category/")}
3036
+ ${chalk22.yellow("Examples:")}
3037
+ ${chalk22.cyan("$ leetcode pick 1")} Pick by problem ID
3038
+ ${chalk22.cyan("$ leetcode pick two-sum")} Pick by problem slug
3039
+ ${chalk22.cyan("$ leetcode pick 1 -l python3")} Pick with specific language
3040
+ ${chalk22.cyan("$ leetcode pick 1 --no-open")} Create file without opening
3041
+ ${chalk22.cyan("$ leetcode p 412")} Short alias
3042
+
3043
+ ${chalk22.gray("Files are organized by: workDir/Difficulty/Category/")}
2444
3044
  `).action(async (id, options) => {
2445
3045
  await pickCommand(id, options);
2446
3046
  });
2447
3047
  program.command("pick-batch <ids...>").description("Generate solution files for multiple problems").option("-l, --lang <language>", "Programming language for the solutions").addHelpText("after", `
2448
- ${chalk20.yellow("Examples:")}
2449
- ${chalk20.cyan("$ leetcode pick-batch 1 2 3")} Pick problems 1, 2, and 3
2450
- ${chalk20.cyan("$ leetcode pick-batch 1 2 3 -l py")} Pick with Python
3048
+ ${chalk22.yellow("Examples:")}
3049
+ ${chalk22.cyan("$ leetcode pick-batch 1 2 3")} Pick problems 1, 2, and 3
3050
+ ${chalk22.cyan("$ leetcode pick-batch 1 2 3 -l py")} Pick with Python
2451
3051
  `).action(batchPickCommand);
2452
3052
  program.command("test <file>").alias("t").description("Test solution against sample test cases").option("-c, --testcase <testcase>", "Custom test case").addHelpText("after", `
2453
- ${chalk20.yellow("Examples:")}
2454
- ${chalk20.cyan("$ leetcode test 1")} Test by problem ID
2455
- ${chalk20.cyan("$ leetcode test two-sum")} Test by problem slug
2456
- ${chalk20.cyan("$ leetcode test ./path/to/file.py")} Test by file path
2457
- ${chalk20.cyan('$ leetcode test 1 -c "[1,2]\\n3"')} Test with custom case
2458
- ${chalk20.cyan("$ leetcode t 412")} Short alias
2459
-
2460
- ${chalk20.gray("Testcases use \\n to separate multiple inputs.")}
3053
+ ${chalk22.yellow("Examples:")}
3054
+ ${chalk22.cyan("$ leetcode test 1")} Test by problem ID
3055
+ ${chalk22.cyan("$ leetcode test two-sum")} Test by problem slug
3056
+ ${chalk22.cyan("$ leetcode test ./path/to/file.py")} Test by file path
3057
+ ${chalk22.cyan('$ leetcode test 1 -c "[1,2]\\n3"')} Test with custom case
3058
+ ${chalk22.cyan("$ leetcode t 412")} Short alias
3059
+
3060
+ ${chalk22.gray("Testcases use \\n to separate multiple inputs.")}
2461
3061
  `).action(testCommand);
2462
3062
  program.command("submit <file>").alias("x").description("Submit solution to LeetCode").addHelpText("after", `
2463
- ${chalk20.yellow("Examples:")}
2464
- ${chalk20.cyan("$ leetcode submit 1")} Submit by problem ID
2465
- ${chalk20.cyan("$ leetcode submit two-sum")} Submit by problem slug
2466
- ${chalk20.cyan("$ leetcode submit ./path/to/file.py")} Submit by file path
2467
- ${chalk20.cyan("$ leetcode x 412")} Short alias
3063
+ ${chalk22.yellow("Examples:")}
3064
+ ${chalk22.cyan("$ leetcode submit 1")} Submit by problem ID
3065
+ ${chalk22.cyan("$ leetcode submit two-sum")} Submit by problem slug
3066
+ ${chalk22.cyan("$ leetcode submit ./path/to/file.py")} Submit by file path
3067
+ ${chalk22.cyan("$ leetcode x 412")} Short alias
2468
3068
  `).action(submitCommand);
2469
3069
  program.command("submissions <id>").description("View past submissions").option("-n, --limit <number>", "Number of submissions to show", "20").option("--last", "Show details of the last accepted submission").option("--download", "Download the last accepted submission code").addHelpText("after", `
2470
- ${chalk20.yellow("Examples:")}
2471
- ${chalk20.cyan("$ leetcode submissions 1")} View submissions for problem
2472
- ${chalk20.cyan("$ leetcode submissions 1 -n 5")} Show last 5 submissions
2473
- ${chalk20.cyan("$ leetcode submissions 1 --last")} Show last accepted submission
2474
- ${chalk20.cyan("$ leetcode submissions 1 --download")} Download last accepted code
3070
+ ${chalk22.yellow("Examples:")}
3071
+ ${chalk22.cyan("$ leetcode submissions 1")} View submissions for problem
3072
+ ${chalk22.cyan("$ leetcode submissions 1 -n 5")} Show last 5 submissions
3073
+ ${chalk22.cyan("$ leetcode submissions 1 --last")} Show last accepted submission
3074
+ ${chalk22.cyan("$ leetcode submissions 1 --download")} Download last accepted code
2475
3075
  `).action(submissionsCommand);
2476
3076
  program.command("stat [username]").description("Show user statistics and analytics").option("-c, --calendar", "Weekly activity summary (submissions & active days for last 12 weeks)").option("-s, --skills", "Skill breakdown (problems solved grouped by topic tags)").option("-t, --trend", "Daily trend chart (bar graph of submissions for last 7 days)").addHelpText("after", `
2477
- ${chalk20.yellow("Options Explained:")}
2478
- ${chalk20.cyan("-c, --calendar")} Shows a table of your weekly submissions and active days
3077
+ ${chalk22.yellow("Options Explained:")}
3078
+ ${chalk22.cyan("-c, --calendar")} Shows a table of your weekly submissions and active days
2479
3079
  for the past 12 weeks. Useful for tracking consistency.
2480
3080
 
2481
- ${chalk20.cyan("-s, --skills")} Shows how many problems you solved per topic tag,
3081
+ ${chalk22.cyan("-s, --skills")} Shows how many problems you solved per topic tag,
2482
3082
  grouped by difficulty (Fundamental/Intermediate/Advanced).
2483
3083
  Helps identify your strong and weak areas.
2484
3084
 
2485
- ${chalk20.cyan("-t, --trend")} Shows a bar chart of daily submissions for the past week.
3085
+ ${chalk22.cyan("-t, --trend")} Shows a bar chart of daily submissions for the past week.
2486
3086
  Visualizes your recent coding activity day by day.
2487
3087
 
2488
- ${chalk20.yellow("Examples:")}
2489
- ${chalk20.cyan("$ leetcode stat")} Show basic stats (solved count, rank)
2490
- ${chalk20.cyan("$ leetcode stat lee215")} Show another user's stats
2491
- ${chalk20.cyan("$ leetcode stat -c")} Weekly activity table
2492
- ${chalk20.cyan("$ leetcode stat -s")} Topic-wise breakdown
2493
- ${chalk20.cyan("$ leetcode stat -t")} 7-day trend chart
3088
+ ${chalk22.yellow("Examples:")}
3089
+ ${chalk22.cyan("$ leetcode stat")} Show basic stats (solved count, rank)
3090
+ ${chalk22.cyan("$ leetcode stat lee215")} Show another user's stats
3091
+ ${chalk22.cyan("$ leetcode stat -c")} Weekly activity table
3092
+ ${chalk22.cyan("$ leetcode stat -s")} Topic-wise breakdown
3093
+ ${chalk22.cyan("$ leetcode stat -t")} 7-day trend chart
2494
3094
  `).action((username, options) => statCommand(username, options));
2495
3095
  program.command("today").description("Show today's progress summary").addHelpText("after", `
2496
- ${chalk20.yellow("Examples:")}
2497
- ${chalk20.cyan("$ leetcode today")} Show streak, solved, and daily challenge
3096
+ ${chalk22.yellow("Examples:")}
3097
+ ${chalk22.cyan("$ leetcode today")} Show streak, solved, and daily challenge
2498
3098
  `).action(todayCommand);
2499
3099
  program.command("bookmark <action> [id]").description("Manage problem bookmarks").addHelpText("after", `
2500
- ${chalk20.yellow("Actions:")}
2501
- ${chalk20.cyan("add <id>")} Bookmark a problem
2502
- ${chalk20.cyan("remove <id>")} Remove a bookmark
2503
- ${chalk20.cyan("list")} List all bookmarks
2504
- ${chalk20.cyan("clear")} Clear all bookmarks
2505
-
2506
- ${chalk20.yellow("Examples:")}
2507
- ${chalk20.cyan("$ leetcode bookmark add 1")} Bookmark problem 1
2508
- ${chalk20.cyan("$ leetcode bookmark remove 1")} Remove bookmark
2509
- ${chalk20.cyan("$ leetcode bookmark list")} List all bookmarks
3100
+ ${chalk22.yellow("Actions:")}
3101
+ ${chalk22.cyan("add <id>")} Bookmark a problem
3102
+ ${chalk22.cyan("remove <id>")} Remove a bookmark
3103
+ ${chalk22.cyan("list")} List all bookmarks
3104
+ ${chalk22.cyan("clear")} Clear all bookmarks
3105
+
3106
+ ${chalk22.yellow("Examples:")}
3107
+ ${chalk22.cyan("$ leetcode bookmark add 1")} Bookmark problem 1
3108
+ ${chalk22.cyan("$ leetcode bookmark remove 1")} Remove bookmark
3109
+ ${chalk22.cyan("$ leetcode bookmark list")} List all bookmarks
2510
3110
  `).action(bookmarkCommand);
2511
3111
  program.command("note <id> [action]").description("View or edit notes for a problem").addHelpText("after", `
2512
- ${chalk20.yellow("Actions:")}
2513
- ${chalk20.cyan("edit")} Open notes in editor (default)
2514
- ${chalk20.cyan("view")} Display notes in terminal
2515
-
2516
- ${chalk20.yellow("Examples:")}
2517
- ${chalk20.cyan("$ leetcode note 1")} Edit notes for problem 1
2518
- ${chalk20.cyan("$ leetcode note 1 edit")} Edit notes (explicit)
2519
- ${chalk20.cyan("$ leetcode note 1 view")} View notes in terminal
3112
+ ${chalk22.yellow("Actions:")}
3113
+ ${chalk22.cyan("edit")} Open notes in editor (default)
3114
+ ${chalk22.cyan("view")} Display notes in terminal
3115
+
3116
+ ${chalk22.yellow("Examples:")}
3117
+ ${chalk22.cyan("$ leetcode note 1")} Edit notes for problem 1
3118
+ ${chalk22.cyan("$ leetcode note 1 edit")} Edit notes (explicit)
3119
+ ${chalk22.cyan("$ leetcode note 1 view")} View notes in terminal
2520
3120
  `).action(notesCommand);
2521
3121
  program.command("sync").description("Sync solutions to Git repository").addHelpText("after", `
2522
- ${chalk20.yellow("Examples:")}
2523
- ${chalk20.cyan("$ leetcode sync")} Sync all solutions to remote
3122
+ ${chalk22.yellow("Examples:")}
3123
+ ${chalk22.cyan("$ leetcode sync")} Sync all solutions to remote
2524
3124
  `).action(syncCommand);
2525
3125
  program.command("config").description("View or set configuration").option("-l, --lang <language>", "Set default programming language").option("-e, --editor <editor>", "Set editor command").option("-w, --workdir <path>", "Set working directory for solutions").option("-r, --repo <url>", "Set Git repository URL").option("-i, --interactive", "Interactive configuration").addHelpText("after", `
2526
- ${chalk20.yellow("Examples:")}
2527
- ${chalk20.cyan("$ leetcode config")} View current config
2528
- ${chalk20.cyan("$ leetcode config -l python3")} Set language to Python
2529
- ${chalk20.cyan('$ leetcode config -e "code"')} Set editor to VS Code
2530
- ${chalk20.cyan("$ leetcode config -w ~/leetcode")} Set solutions folder
2531
- ${chalk20.cyan("$ leetcode config -r https://...")} Set git repository
2532
- ${chalk20.cyan("$ leetcode config -i")} Interactive setup
2533
-
2534
- ${chalk20.gray("Supported languages: typescript, javascript, python3, java, cpp, c, csharp, go, rust, kotlin, swift")}
3126
+ ${chalk22.yellow("Examples:")}
3127
+ ${chalk22.cyan("$ leetcode config")} View current config
3128
+ ${chalk22.cyan("$ leetcode config -l python3")} Set language to Python
3129
+ ${chalk22.cyan('$ leetcode config -e "code"')} Set editor to VS Code
3130
+ ${chalk22.cyan("$ leetcode config -w ~/leetcode")} Set solutions folder
3131
+ ${chalk22.cyan("$ leetcode config -r https://...")} Set git repository
3132
+ ${chalk22.cyan("$ leetcode config -i")} Interactive setup
3133
+
3134
+ ${chalk22.gray("Supported languages: typescript, javascript, python3, java, cpp, c, csharp, go, rust, kotlin, swift")}
2535
3135
  `).action(async (options) => {
2536
3136
  if (options.interactive) {
2537
3137
  await configInteractiveCommand();
@@ -2539,12 +3139,45 @@ ${chalk20.gray("Supported languages: typescript, javascript, python3, java, cpp,
2539
3139
  await configCommand(options);
2540
3140
  }
2541
3141
  });
3142
+ program.command("timer [id]").description("Start interview mode with timer").option("-m, --minutes <minutes>", "Custom time limit in minutes").option("--stats", "Show solve time statistics").option("--stop", "Stop active timer").addHelpText("after", `
3143
+ ${chalk22.yellow("How it works:")}
3144
+ Start a problem with a countdown timer to simulate interview conditions.
3145
+ Default time limits: Easy (20 min), Medium (40 min), Hard (60 min).
3146
+ Your solve times are recorded when you submit successfully.
3147
+
3148
+ ${chalk22.yellow("Examples:")}
3149
+ ${chalk22.cyan("$ leetcode timer 1")} Start problem 1 with default time
3150
+ ${chalk22.cyan("$ leetcode timer 1 -m 30")} Start with 30 minute limit
3151
+ ${chalk22.cyan("$ leetcode timer --stats")} Show your solve time statistics
3152
+ ${chalk22.cyan("$ leetcode timer --stop")} Stop active timer
3153
+ `).action((id, options) => timerCommand(id, options));
3154
+ var collabCmd = program.command("collab").description("Collaborative coding with a partner").addHelpText("after", `
3155
+ ${chalk22.yellow("Subcommands:")}
3156
+ ${chalk22.cyan("host <id>")} Create a room and get a code to share
3157
+ ${chalk22.cyan("join <code>")} Join a room with the shared code
3158
+ ${chalk22.cyan("sync")} Upload your solution to the room
3159
+ ${chalk22.cyan("compare")} View both solutions side by side
3160
+ ${chalk22.cyan("status")} Check room and sync status
3161
+ ${chalk22.cyan("leave")} End the collaboration session
3162
+
3163
+ ${chalk22.yellow("Examples:")}
3164
+ ${chalk22.gray("$ leetcode collab host 1")} Start a session for Two Sum
3165
+ ${chalk22.gray("$ leetcode collab join ABC123")} Join your partner's session
3166
+ ${chalk22.gray("$ leetcode collab sync")} Upload your code after solving
3167
+ ${chalk22.gray("$ leetcode collab compare")} Compare solutions
3168
+ `);
3169
+ collabCmd.command("host <problemId>").description("Host a collaboration session").action(collabHostCommand);
3170
+ collabCmd.command("join <roomCode>").description("Join a collaboration session").action(collabJoinCommand);
3171
+ collabCmd.command("sync").description("Sync your code with partner").action(collabSyncCommand);
3172
+ collabCmd.command("compare").description("Compare your solution with partner").action(collabCompareCommand);
3173
+ collabCmd.command("leave").description("Leave the collaboration session").action(collabLeaveCommand);
3174
+ collabCmd.command("status").description("Show collaboration status").action(collabStatusCommand);
2542
3175
  program.showHelpAfterError("(add --help for additional information)");
2543
3176
  program.parse();
2544
3177
  if (!process.argv.slice(2).length) {
2545
3178
  console.log();
2546
- console.log(chalk20.bold.cyan(" \u{1F525} LeetCode CLI"));
2547
- console.log(chalk20.gray(" A modern command-line interface for LeetCode"));
3179
+ console.log(chalk22.bold.cyan(" \u{1F525} LeetCode CLI"));
3180
+ console.log(chalk22.gray(" A modern command-line interface for LeetCode"));
2548
3181
  console.log();
2549
3182
  program.outputHelp();
2550
3183
  }