@night-slayer18/leetcode-cli 1.6.0 → 2.0.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.
Files changed (3) hide show
  1. package/README.md +86 -0
  2. package/dist/index.js +1617 -571
  3. package/package.json +4 -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 chalk22 from "chalk";
5
+ import chalk26 from "chalk";
6
6
 
7
7
  // src/commands/login.ts
8
8
  import inquirer from "inquirer";
@@ -454,7 +454,7 @@ var LeetCodeClient = class {
454
454
  if (result.state === "SUCCESS" || result.state === "FAILURE") {
455
455
  return schema.parse(result);
456
456
  }
457
- await new Promise((resolve) => setTimeout(resolve, delay));
457
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
458
458
  }
459
459
  throw new Error("Submission timeout: Result not available after 30 seconds");
460
460
  }
@@ -571,7 +571,7 @@ async function whoamiCommand() {
571
571
 
572
572
  // src/commands/list.ts
573
573
  import ora2 from "ora";
574
- import chalk4 from "chalk";
574
+ import chalk5 from "chalk";
575
575
 
576
576
  // src/utils/auth.ts
577
577
  import chalk2 from "chalk";
@@ -596,16 +596,227 @@ async function requireAuth() {
596
596
  }
597
597
 
598
598
  // src/utils/display.ts
599
- import chalk3 from "chalk";
599
+ import chalk4 from "chalk";
600
600
  import Table from "cli-table3";
601
+
602
+ // src/utils/visualize.ts
603
+ import chalk3 from "chalk";
604
+ var TAG_VISUALIZATION = {
605
+ "Linked List": "linkedlist",
606
+ "Doubly-Linked List": "linkedlist",
607
+ "Tree": "tree",
608
+ "Binary Tree": "tree",
609
+ "Binary Search Tree": "tree",
610
+ "Trie": "tree",
611
+ "Segment Tree": "tree",
612
+ "Binary Indexed Tree": "tree",
613
+ "Graph": "graph",
614
+ "Matrix": "matrix",
615
+ "Array": "array",
616
+ "Hash Table": "array",
617
+ "Stack": "array",
618
+ "Queue": "array",
619
+ "Monotonic Stack": "array",
620
+ "Monotonic Queue": "array",
621
+ "Heap (Priority Queue)": "array",
622
+ "String": "string"
623
+ };
624
+ function detectVisualizationType(tags) {
625
+ for (const tag of tags) {
626
+ const vizType = TAG_VISUALIZATION[tag.name];
627
+ if (vizType) return vizType;
628
+ }
629
+ return null;
630
+ }
631
+ function parseValue(value) {
632
+ try {
633
+ return JSON.parse(value);
634
+ } catch {
635
+ return value;
636
+ }
637
+ }
638
+ function isMatrix(value) {
639
+ return Array.isArray(value) && value.length > 0 && Array.isArray(value[0]);
640
+ }
641
+ function visualizeArray(arr, expected) {
642
+ if (!Array.isArray(arr) || arr.length === 0) {
643
+ return String(arr);
644
+ }
645
+ const maxLen = Math.max(...arr.map((v) => String(v).length), 1);
646
+ const cellWidth = Math.max(maxLen, 3);
647
+ const indices = arr.map((_, i) => `[${i}]`.padStart(cellWidth).padEnd(cellWidth)).join(" ");
648
+ const values = arr.map((v, i) => {
649
+ const valStr = String(v).padStart(cellWidth).padEnd(cellWidth);
650
+ if (expected && Array.isArray(expected) && expected[i] !== v) {
651
+ return chalk3.red.bold(valStr);
652
+ }
653
+ return valStr;
654
+ }).join(" ");
655
+ return `${chalk3.gray(indices)}
656
+ ${values}`;
657
+ }
658
+ function visualizeLinkedList(arr, expected) {
659
+ if (!Array.isArray(arr)) {
660
+ return String(arr);
661
+ }
662
+ if (arr.length === 0) {
663
+ return chalk3.gray("(empty)");
664
+ }
665
+ const parts = arr.map((v, i) => {
666
+ const valStr = String(v);
667
+ if (expected && Array.isArray(expected)) {
668
+ if (i >= expected.length || expected[i] !== v) {
669
+ return chalk3.red.bold(valStr);
670
+ }
671
+ }
672
+ return valStr;
673
+ });
674
+ return parts.join(chalk3.gray(" \u2192 "));
675
+ }
676
+ function visualizeTree(arr) {
677
+ if (!Array.isArray(arr) || arr.length === 0) {
678
+ return chalk3.gray("(empty tree)");
679
+ }
680
+ const lines = [];
681
+ const height = Math.floor(Math.log2(arr.length)) + 1;
682
+ const maxWidth = Math.pow(2, height) * 3;
683
+ function renderLevel(level, startIdx, endIdx, indent) {
684
+ if (startIdx > arr.length - 1) return;
685
+ const levelNodes = [];
686
+ const levelBranches = [];
687
+ const spacing = Math.floor(maxWidth / Math.pow(2, level + 1));
688
+ for (let i = startIdx; i <= endIdx && i < arr.length; i++) {
689
+ const val = arr[i];
690
+ const nodeStr = val === null ? " " : String(val);
691
+ levelNodes.push(nodeStr.padStart(spacing).padEnd(spacing));
692
+ const leftChild = 2 * i + 1;
693
+ const rightChild = 2 * i + 2;
694
+ const hasLeft = leftChild < arr.length && arr[leftChild] !== null;
695
+ const hasRight = rightChild < arr.length && arr[rightChild] !== null;
696
+ if (hasLeft || hasRight) {
697
+ let branch = "";
698
+ if (hasLeft) branch += "/";
699
+ else branch += " ";
700
+ branch += " ";
701
+ if (hasRight) branch += "\\";
702
+ else branch += " ";
703
+ levelBranches.push(branch.padStart(spacing).padEnd(spacing));
704
+ }
705
+ }
706
+ if (levelNodes.length > 0) {
707
+ lines.push(levelNodes.join(""));
708
+ if (levelBranches.length > 0 && level < height - 1) {
709
+ lines.push(levelBranches.join(""));
710
+ }
711
+ }
712
+ }
713
+ for (let level = 0; level < height; level++) {
714
+ const startIdx = Math.pow(2, level) - 1;
715
+ const endIdx = Math.pow(2, level + 1) - 2;
716
+ renderLevel(level, startIdx, endIdx, 0);
717
+ }
718
+ return lines.join("\n");
719
+ }
720
+ function visualizeMatrix(matrix, expected) {
721
+ if (!isMatrix(matrix) || matrix.length === 0) {
722
+ return String(matrix);
723
+ }
724
+ const rows = matrix.length;
725
+ const cols = matrix[0].length;
726
+ const cellWidth = 3;
727
+ const lines = [];
728
+ const colHeaders = " " + matrix[0].map((_, i) => String(i).padStart(cellWidth).padEnd(cellWidth)).join(" ");
729
+ lines.push(chalk3.gray(colHeaders));
730
+ lines.push(" \u250C" + Array(cols).fill("\u2500\u2500\u2500").join("\u252C") + "\u2510");
731
+ for (let r = 0; r < rows; r++) {
732
+ const rowContent = matrix[r].map((v, c) => {
733
+ const valStr = String(v).padStart(2);
734
+ if (expected && isMatrix(expected) && expected[r] && expected[r][c] !== v) {
735
+ return chalk3.red.bold(valStr);
736
+ }
737
+ return valStr;
738
+ }).join(" \u2502 ");
739
+ lines.push(chalk3.gray(` ${r} `) + `\u2502 ${rowContent} \u2502`);
740
+ if (r < rows - 1) {
741
+ lines.push(" \u251C" + Array(cols).fill("\u2500\u2500\u2500").join("\u253C") + "\u2524");
742
+ } else {
743
+ lines.push(" \u2514" + Array(cols).fill("\u2500\u2500\u2500").join("\u2534") + "\u2518");
744
+ }
745
+ }
746
+ return lines.join("\n");
747
+ }
748
+ function visualizeGraph(adjList) {
749
+ if (!isMatrix(adjList)) {
750
+ return String(adjList);
751
+ }
752
+ const lines = adjList.map((neighbors, node) => {
753
+ const neighborStr = Array.isArray(neighbors) ? neighbors.join(", ") : String(neighbors);
754
+ return ` ${chalk3.cyan(String(node))} \u2192 [${neighborStr}]`;
755
+ });
756
+ return lines.join("\n");
757
+ }
758
+ function visualizeTestOutput(output, expected, tags) {
759
+ const outputVal = parseValue(output);
760
+ const expectedVal = parseValue(expected);
761
+ const matches = output === expected;
762
+ const vizType = detectVisualizationType(tags);
763
+ let outputVis;
764
+ let expectedVis;
765
+ if (isMatrix(outputVal)) {
766
+ const expectedMatrix = isMatrix(expectedVal) ? expectedVal : void 0;
767
+ outputVis = visualizeMatrix(outputVal, expectedMatrix);
768
+ expectedVis = isMatrix(expectedVal) ? visualizeMatrix(expectedVal) : String(expected);
769
+ return { outputVis, expectedVis, matches };
770
+ }
771
+ if (vizType === null) {
772
+ return {
773
+ outputVis: String(output),
774
+ expectedVis: String(expected),
775
+ matches,
776
+ unsupported: true
777
+ };
778
+ }
779
+ switch (vizType) {
780
+ case "linkedlist":
781
+ outputVis = visualizeLinkedList(outputVal, expectedVal);
782
+ expectedVis = visualizeLinkedList(expectedVal);
783
+ break;
784
+ case "tree":
785
+ outputVis = visualizeTree(outputVal);
786
+ expectedVis = visualizeTree(expectedVal);
787
+ break;
788
+ case "graph":
789
+ outputVis = visualizeGraph(outputVal);
790
+ expectedVis = visualizeGraph(expectedVal);
791
+ break;
792
+ case "matrix":
793
+ const expMatrix = isMatrix(expectedVal) ? expectedVal : void 0;
794
+ outputVis = visualizeMatrix(outputVal, expMatrix);
795
+ expectedVis = isMatrix(expectedVal) ? visualizeMatrix(expectedVal) : String(expected);
796
+ break;
797
+ case "array":
798
+ case "string":
799
+ if (Array.isArray(outputVal)) {
800
+ outputVis = visualizeArray(outputVal, expectedVal);
801
+ expectedVis = Array.isArray(expectedVal) ? visualizeArray(expectedVal) : String(expected);
802
+ } else {
803
+ outputVis = matches ? String(output) : chalk3.red.bold(String(output));
804
+ expectedVis = String(expected);
805
+ }
806
+ break;
807
+ }
808
+ return { outputVis, expectedVis, matches };
809
+ }
810
+
811
+ // src/utils/display.ts
601
812
  function displayProblemList(problems, total) {
602
813
  const table = new Table({
603
814
  head: [
604
- chalk3.cyan("ID"),
605
- chalk3.cyan("Title"),
606
- chalk3.cyan("Difficulty"),
607
- chalk3.cyan("Rate"),
608
- chalk3.cyan("Status")
815
+ chalk4.cyan("ID"),
816
+ chalk4.cyan("Title"),
817
+ chalk4.cyan("Difficulty"),
818
+ chalk4.cyan("Rate"),
819
+ chalk4.cyan("Status")
609
820
  ],
610
821
  colWidths: [8, 45, 12, 10, 10],
611
822
  style: { head: [], border: [] }
@@ -624,34 +835,34 @@ function displayProblemList(problems, total) {
624
835
  ]);
625
836
  }
626
837
  console.log(table.toString());
627
- console.log(chalk3.gray(`
838
+ console.log(chalk4.gray(`
628
839
  Showing ${problems.length} of ${total} problems`));
629
840
  }
630
841
  function displayProblemDetail(problem) {
631
842
  console.log();
632
843
  const titlePrefix = problem.isPaidOnly ? "\u{1F512} " : "";
633
- console.log(chalk3.bold.cyan(` ${problem.questionFrontendId}. ${titlePrefix}${problem.title}`));
844
+ console.log(chalk4.bold.cyan(` ${problem.questionFrontendId}. ${titlePrefix}${problem.title}`));
634
845
  console.log(` ${colorDifficulty(problem.difficulty)}`);
635
- console.log(chalk3.gray(` https://leetcode.com/problems/${problem.titleSlug}/`));
846
+ console.log(chalk4.gray(` https://leetcode.com/problems/${problem.titleSlug}/`));
636
847
  console.log();
637
848
  if (problem.isPaidOnly) {
638
- console.log(chalk3.yellow(" \u26A0\uFE0F Premium Problem"));
639
- console.log(chalk3.gray(" This problem requires a LeetCode Premium subscription."));
640
- console.log(chalk3.gray(` Visit the URL above to view on LeetCode.`));
849
+ console.log(chalk4.yellow(" \u26A0\uFE0F Premium Problem"));
850
+ console.log(chalk4.gray(" This problem requires a LeetCode Premium subscription."));
851
+ console.log(chalk4.gray(` Visit the URL above to view on LeetCode.`));
641
852
  console.log();
642
853
  }
643
854
  if (problem.topicTags.length) {
644
- const tags = problem.topicTags.map((t) => chalk3.bgBlue.white(` ${t.name} `)).join(" ");
855
+ const tags = problem.topicTags.map((t) => chalk4.bgBlue.white(` ${t.name} `)).join(" ");
645
856
  console.log(` ${tags}`);
646
857
  console.log();
647
858
  }
648
- console.log(chalk3.gray("\u2500".repeat(60)));
859
+ console.log(chalk4.gray("\u2500".repeat(60)));
649
860
  console.log();
650
861
  let content = problem.content;
651
862
  if (!content) {
652
- console.log(chalk3.yellow(" \u{1F512} Premium Content"));
653
- console.log(chalk3.gray(" Problem description is not available directly."));
654
- console.log(chalk3.gray(" Please visit the URL above to view on LeetCode."));
863
+ console.log(chalk4.yellow(" \u{1F512} Premium Content"));
864
+ console.log(chalk4.gray(" Problem description is not available directly."));
865
+ console.log(chalk4.gray(" Please visit the URL above to view on LeetCode."));
655
866
  console.log();
656
867
  return;
657
868
  }
@@ -671,100 +882,128 @@ function displayProblemDetail(problem) {
671
882
  content = content.replace(/<[^>]+>/g, "");
672
883
  content = content.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&le;/g, "\u2264").replace(/&ge;/g, "\u2265").replace(/&#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code, 10)));
673
884
  content = content.replace(/\n{3,}/g, "\n\n").trim();
674
- content = content.replace(/§EXAMPLE§(\d+)§/g, (_, num) => chalk3.green.bold(`\u{1F4CC} Example ${num}:`));
675
- content = content.replace(/§INPUT§/g, chalk3.yellow("Input:"));
676
- content = content.replace(/§OUTPUT§/g, chalk3.yellow("Output:"));
677
- content = content.replace(/§EXPLAIN§/g, chalk3.gray("Explanation:"));
678
- content = content.replace(/§CONSTRAINTS§/g, chalk3.cyan.bold("\n\u{1F4CB} Constraints:"));
679
- content = content.replace(/§FOLLOWUP§/g, chalk3.magenta.bold("\n\u{1F4A1} Follow-up:"));
885
+ content = content.replace(/§EXAMPLE§(\d+)§/g, (_, num) => chalk4.green.bold(`\u{1F4CC} Example ${num}:`));
886
+ content = content.replace(/§INPUT§/g, chalk4.yellow("Input:"));
887
+ content = content.replace(/§OUTPUT§/g, chalk4.yellow("Output:"));
888
+ content = content.replace(/§EXPLAIN§/g, chalk4.gray("Explanation:"));
889
+ content = content.replace(/§CONSTRAINTS§/g, chalk4.cyan.bold("\n\u{1F4CB} Constraints:"));
890
+ content = content.replace(/§FOLLOWUP§/g, chalk4.magenta.bold("\n\u{1F4A1} Follow-up:"));
680
891
  console.log(content);
681
892
  console.log();
682
893
  }
683
- function displayTestResult(result) {
894
+ function displayTestResult(result, topicTags) {
684
895
  console.log();
685
896
  if (result.compile_error) {
686
- console.log(chalk3.red.bold("\u274C Compile Error"));
687
- console.log(chalk3.red(result.compile_error));
897
+ console.log(chalk4.red.bold("\u274C Compile Error"));
898
+ console.log(chalk4.red(result.compile_error));
688
899
  return;
689
900
  }
690
901
  if (result.runtime_error) {
691
- console.log(chalk3.red.bold("\u274C Runtime Error"));
692
- console.log(chalk3.red(result.runtime_error));
902
+ console.log(chalk4.red.bold("\u274C Runtime Error"));
903
+ console.log(chalk4.red(result.runtime_error));
693
904
  return;
694
905
  }
695
906
  if (result.correct_answer) {
696
- console.log(chalk3.green.bold("\u2713 All test cases passed!"));
907
+ console.log(chalk4.green.bold("\u2713 All test cases passed!"));
697
908
  } else {
698
- console.log(chalk3.yellow.bold("\u2717 Some test cases failed"));
909
+ console.log(chalk4.yellow.bold("\u2717 Some test cases failed"));
699
910
  }
700
- console.log();
701
- console.log(chalk3.gray("Your Output:"));
702
- for (const output of result.code_answer ?? []) {
703
- console.log(chalk3.white(` ${output}`));
704
- }
705
- console.log();
706
- console.log(chalk3.gray("Expected Output:"));
707
- for (const output of result.expected_code_answer ?? []) {
708
- console.log(chalk3.white(` ${output}`));
911
+ const outputs = result.code_answer ?? [];
912
+ const expected = result.expected_code_answer ?? [];
913
+ if (topicTags && outputs.length > 0) {
914
+ console.log();
915
+ console.log(chalk4.gray.bold("\u2500".repeat(50)));
916
+ const validCases = outputs.map((out, i) => ({ out, exp: expected[i] ?? "" })).filter(({ out, exp }) => out !== "" || exp !== "");
917
+ for (let i = 0; i < validCases.length; i++) {
918
+ const { out, exp } = validCases[i];
919
+ const { outputVis, expectedVis, matches, unsupported } = visualizeTestOutput(out, exp, topicTags);
920
+ console.log();
921
+ console.log(chalk4.gray(`Test Case ${i + 1}:`));
922
+ if (unsupported) {
923
+ console.log(chalk4.yellow(" \u26A0 No visualization available for this problem type"));
924
+ console.log(chalk4.gray(` Tags: ${topicTags.map((t) => t.name).join(", ")}`));
925
+ }
926
+ console.log();
927
+ console.log(chalk4.cyan(" Your Output:"));
928
+ outputVis.split("\n").forEach((line) => console.log(` ${line}`));
929
+ console.log();
930
+ console.log(chalk4.cyan(" Expected:"));
931
+ expectedVis.split("\n").forEach((line) => console.log(` ${line}`));
932
+ console.log();
933
+ console.log(matches ? chalk4.green(" \u2713 Match") : chalk4.red(" \u2717 Mismatch"));
934
+ }
935
+ console.log(chalk4.gray.bold("\u2500".repeat(50)));
936
+ } else {
937
+ console.log();
938
+ console.log(chalk4.gray("Your Output:"));
939
+ for (const output of outputs) {
940
+ console.log(chalk4.white(` ${output}`));
941
+ }
942
+ console.log();
943
+ console.log(chalk4.gray("Expected Output:"));
944
+ for (const output of expected) {
945
+ console.log(chalk4.white(` ${output}`));
946
+ }
709
947
  }
710
- if (result.std_output_list?.length) {
948
+ const stdoutEntries = (result.std_output_list ?? []).filter((s) => s);
949
+ if (stdoutEntries.length > 0) {
711
950
  console.log();
712
- console.log(chalk3.gray("Stdout:"));
713
- for (const output of result.std_output_list) {
714
- if (output) console.log(chalk3.gray(` ${output}`));
951
+ console.log(chalk4.gray("Stdout:"));
952
+ for (const output of stdoutEntries) {
953
+ console.log(chalk4.gray(` ${output}`));
715
954
  }
716
955
  }
717
956
  }
718
957
  function displaySubmissionResult(result) {
719
958
  console.log();
720
959
  if (result.compile_error) {
721
- console.log(chalk3.red.bold("\u274C Compile Error"));
722
- console.log(chalk3.red(result.compile_error));
960
+ console.log(chalk4.red.bold("\u274C Compile Error"));
961
+ console.log(chalk4.red(result.compile_error));
723
962
  return;
724
963
  }
725
964
  if (result.runtime_error) {
726
- console.log(chalk3.red.bold("\u274C Runtime Error"));
727
- console.log(chalk3.red(result.runtime_error));
965
+ console.log(chalk4.red.bold("\u274C Runtime Error"));
966
+ console.log(chalk4.red(result.runtime_error));
728
967
  if (result.last_testcase) {
729
- console.log(chalk3.gray("Last testcase:"), result.last_testcase);
968
+ console.log(chalk4.gray("Last testcase:"), result.last_testcase);
730
969
  }
731
970
  return;
732
971
  }
733
972
  if (result.status_msg === "Accepted") {
734
- console.log(chalk3.green.bold("\u2713 Accepted!"));
973
+ console.log(chalk4.green.bold("\u2713 Accepted!"));
735
974
  console.log();
736
975
  console.log(
737
- chalk3.gray("Runtime:"),
738
- chalk3.white(result.status_runtime),
739
- chalk3.gray(`(beats ${result.runtime_percentile?.toFixed(1) ?? "N/A"}%)`)
976
+ chalk4.gray("Runtime:"),
977
+ chalk4.white(result.status_runtime),
978
+ chalk4.gray(`(beats ${result.runtime_percentile?.toFixed(1) ?? "N/A"}%)`)
740
979
  );
741
980
  console.log(
742
- chalk3.gray("Memory:"),
743
- chalk3.white(result.status_memory),
744
- chalk3.gray(`(beats ${result.memory_percentile?.toFixed(1) ?? "N/A"}%)`)
981
+ chalk4.gray("Memory:"),
982
+ chalk4.white(result.status_memory),
983
+ chalk4.gray(`(beats ${result.memory_percentile?.toFixed(1) ?? "N/A"}%)`)
745
984
  );
746
985
  } else {
747
- console.log(chalk3.red.bold(`\u274C ${result.status_msg}`));
986
+ console.log(chalk4.red.bold(`\u274C ${result.status_msg}`));
748
987
  console.log();
749
- console.log(chalk3.gray(`Passed ${result.total_correct}/${result.total_testcases} testcases`));
988
+ console.log(chalk4.gray(`Passed ${result.total_correct}/${result.total_testcases} testcases`));
750
989
  if (result.code_output) {
751
- console.log(chalk3.gray("Your Output:"), result.code_output);
990
+ console.log(chalk4.gray("Your Output:"), result.code_output);
752
991
  }
753
992
  if (result.expected_output) {
754
- console.log(chalk3.gray("Expected:"), result.expected_output);
993
+ console.log(chalk4.gray("Expected:"), result.expected_output);
755
994
  }
756
995
  if (result.last_testcase) {
757
- console.log(chalk3.gray("Failed testcase:"), result.last_testcase);
996
+ console.log(chalk4.gray("Failed testcase:"), result.last_testcase);
758
997
  }
759
998
  }
760
999
  }
761
1000
  function displayUserStats(username, realName, ranking, acStats, streak, totalActiveDays) {
762
1001
  console.log();
763
- console.log(chalk3.bold.white(`\u{1F464} ${username}`) + (realName ? chalk3.gray(` (${realName})`) : ""));
764
- console.log(chalk3.gray(`Ranking: #${ranking.toLocaleString()}`));
1002
+ console.log(chalk4.bold.white(`\u{1F464} ${username}`) + (realName ? chalk4.gray(` (${realName})`) : ""));
1003
+ console.log(chalk4.gray(`Ranking: #${ranking.toLocaleString()}`));
765
1004
  console.log();
766
1005
  const table = new Table({
767
- head: [chalk3.cyan("Difficulty"), chalk3.cyan("Solved")],
1006
+ head: [chalk4.cyan("Difficulty"), chalk4.cyan("Solved")],
768
1007
  style: { head: [], border: [] }
769
1008
  });
770
1009
  for (const stat of acStats) {
@@ -776,33 +1015,33 @@ function displayUserStats(username, realName, ranking, acStats, streak, totalAct
776
1015
  }
777
1016
  }
778
1017
  const total = acStats.find((s) => s.difficulty === "All")?.count ?? 0;
779
- table.push([chalk3.white.bold("Total"), chalk3.white.bold(total.toString())]);
1018
+ table.push([chalk4.white.bold("Total"), chalk4.white.bold(total.toString())]);
780
1019
  console.log(table.toString());
781
1020
  console.log();
782
- console.log(chalk3.gray("\u{1F525} Current streak:"), chalk3.hex("#FFA500")(streak.toString()), chalk3.gray("days"));
783
- console.log(chalk3.gray("\u{1F4C5} Total active days:"), chalk3.white(totalActiveDays.toString()));
1021
+ console.log(chalk4.gray("\u{1F525} Current streak:"), chalk4.hex("#FFA500")(streak.toString()), chalk4.gray("days"));
1022
+ console.log(chalk4.gray("\u{1F4C5} Total active days:"), chalk4.white(totalActiveDays.toString()));
784
1023
  }
785
1024
  function displayDailyChallenge(date, problem) {
786
1025
  console.log();
787
- console.log(chalk3.bold.yellow("\u{1F3AF} Daily Challenge"), chalk3.gray(`(${date})`));
1026
+ console.log(chalk4.bold.yellow("\u{1F3AF} Daily Challenge"), chalk4.gray(`(${date})`));
788
1027
  console.log();
789
- console.log(chalk3.white(`${problem.questionFrontendId}. ${problem.title}`));
1028
+ console.log(chalk4.white(`${problem.questionFrontendId}. ${problem.title}`));
790
1029
  console.log(colorDifficulty(problem.difficulty));
791
- console.log(chalk3.gray(`https://leetcode.com/problems/${problem.titleSlug}/`));
1030
+ console.log(chalk4.gray(`https://leetcode.com/problems/${problem.titleSlug}/`));
792
1031
  if (problem.topicTags.length) {
793
1032
  console.log();
794
- const tags = problem.topicTags.map((t) => chalk3.blue(t.name)).join(" ");
795
- console.log(chalk3.gray("Tags:"), tags);
1033
+ const tags = problem.topicTags.map((t) => chalk4.blue(t.name)).join(" ");
1034
+ console.log(chalk4.gray("Tags:"), tags);
796
1035
  }
797
1036
  }
798
1037
  function colorDifficulty(difficulty) {
799
1038
  switch (difficulty.toLowerCase()) {
800
1039
  case "easy":
801
- return chalk3.green(difficulty);
1040
+ return chalk4.green(difficulty);
802
1041
  case "medium":
803
- return chalk3.yellow(difficulty);
1042
+ return chalk4.yellow(difficulty);
804
1043
  case "hard":
805
- return chalk3.red(difficulty);
1044
+ return chalk4.red(difficulty);
806
1045
  default:
807
1046
  return difficulty;
808
1047
  }
@@ -810,22 +1049,22 @@ function colorDifficulty(difficulty) {
810
1049
  function formatStatus(status) {
811
1050
  switch (status) {
812
1051
  case "ac":
813
- return chalk3.green("\u2713");
1052
+ return chalk4.green("\u2713");
814
1053
  case "notac":
815
- return chalk3.yellow("\u25CB");
1054
+ return chalk4.yellow("\u25CB");
816
1055
  default:
817
- return chalk3.gray("-");
1056
+ return chalk4.gray("-");
818
1057
  }
819
1058
  }
820
1059
  function displaySubmissionsList(submissions) {
821
1060
  const table = new Table({
822
1061
  head: [
823
- chalk3.cyan("ID"),
824
- chalk3.cyan("Status"),
825
- chalk3.cyan("Lang"),
826
- chalk3.cyan("Runtime"),
827
- chalk3.cyan("Memory"),
828
- chalk3.cyan("Date")
1062
+ chalk4.cyan("ID"),
1063
+ chalk4.cyan("Status"),
1064
+ chalk4.cyan("Lang"),
1065
+ chalk4.cyan("Runtime"),
1066
+ chalk4.cyan("Memory"),
1067
+ chalk4.cyan("Date")
829
1068
  ],
830
1069
  colWidths: [12, 18, 15, 12, 12, 25],
831
1070
  style: { head: [], border: [] }
@@ -835,7 +1074,7 @@ function displaySubmissionsList(submissions) {
835
1074
  const cleanTime = new Date(parseInt(s.timestamp) * 1e3).toLocaleString();
836
1075
  table.push([
837
1076
  s.id,
838
- isAC ? chalk3.green(s.statusDisplay) : chalk3.red(s.statusDisplay),
1077
+ isAC ? chalk4.green(s.statusDisplay) : chalk4.red(s.statusDisplay),
839
1078
  s.lang,
840
1079
  s.runtime,
841
1080
  s.memory,
@@ -886,25 +1125,25 @@ async function listCommand(options) {
886
1125
  const { total, problems } = await leetcodeClient.getProblems(filters);
887
1126
  spinner.stop();
888
1127
  if (problems.length === 0) {
889
- console.log(chalk4.yellow("No problems found matching your criteria."));
1128
+ console.log(chalk5.yellow("No problems found matching your criteria."));
890
1129
  return;
891
1130
  }
892
1131
  displayProblemList(problems, total);
893
1132
  if (page * limit < total) {
894
- console.log(chalk4.gray(`
1133
+ console.log(chalk5.gray(`
895
1134
  Page ${page} of ${Math.ceil(total / limit)}. Use --page to navigate.`));
896
1135
  }
897
1136
  } catch (error) {
898
1137
  spinner.fail("Failed to fetch problems");
899
1138
  if (error instanceof Error) {
900
- console.log(chalk4.red(error.message));
1139
+ console.log(chalk5.red(error.message));
901
1140
  }
902
1141
  }
903
1142
  }
904
1143
 
905
1144
  // src/commands/show.ts
906
1145
  import ora3 from "ora";
907
- import chalk5 from "chalk";
1146
+ import chalk6 from "chalk";
908
1147
  async function showCommand(idOrSlug) {
909
1148
  const { authorized } = await requireAuth();
910
1149
  if (!authorized) return;
@@ -925,68 +1164,227 @@ async function showCommand(idOrSlug) {
925
1164
  } catch (error) {
926
1165
  spinner.fail("Failed to fetch problem");
927
1166
  if (error instanceof Error) {
928
- console.log(chalk5.red(error.message));
1167
+ console.log(chalk6.red(error.message));
929
1168
  }
930
1169
  }
931
1170
  }
932
1171
 
933
1172
  // src/commands/pick.ts
934
1173
  import { writeFile, mkdir } from "fs/promises";
935
- import { existsSync } from "fs";
936
- import { join as join3 } from "path";
1174
+ import { existsSync as existsSync2 } from "fs";
1175
+ import { join as join4 } from "path";
937
1176
  import ora4 from "ora";
938
- import chalk7 from "chalk";
1177
+ import chalk8 from "chalk";
939
1178
 
940
1179
  // src/storage/config.ts
941
- import Conf2 from "conf";
942
- import { homedir as homedir2 } from "os";
1180
+ import { join as join3 } from "path";
1181
+
1182
+ // src/storage/workspaces.ts
1183
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
943
1184
  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")
1185
+ import { homedir as homedir2 } from "os";
1186
+ var LEETCODE_DIR = join2(homedir2(), ".leetcode");
1187
+ var WORKSPACES_FILE = join2(LEETCODE_DIR, "workspaces.json");
1188
+ var WORKSPACES_DIR = join2(LEETCODE_DIR, "workspaces");
1189
+ function ensureDir(dir) {
1190
+ if (!existsSync(dir)) {
1191
+ mkdirSync(dir, { recursive: true });
950
1192
  }
951
- });
1193
+ }
1194
+ function loadRegistry() {
1195
+ if (existsSync(WORKSPACES_FILE)) {
1196
+ return JSON.parse(readFileSync(WORKSPACES_FILE, "utf-8"));
1197
+ }
1198
+ return { active: "default", workspaces: [] };
1199
+ }
1200
+ function saveRegistry(registry) {
1201
+ ensureDir(LEETCODE_DIR);
1202
+ writeFileSync(WORKSPACES_FILE, JSON.stringify(registry, null, 2));
1203
+ }
1204
+ var workspaceStorage = {
1205
+ /**
1206
+ * Initialize workspaces on first run - migrate existing config to "default" workspace
1207
+ */
1208
+ ensureInitialized() {
1209
+ const registry = loadRegistry();
1210
+ if (registry.workspaces.length === 0) {
1211
+ this.create("default", {
1212
+ workDir: join2(homedir2(), "leetcode"),
1213
+ lang: "typescript"
1214
+ });
1215
+ registry.workspaces = ["default"];
1216
+ registry.active = "default";
1217
+ saveRegistry(registry);
1218
+ }
1219
+ },
1220
+ /**
1221
+ * Get the currently active workspace name
1222
+ */
1223
+ getActive() {
1224
+ this.ensureInitialized();
1225
+ return loadRegistry().active;
1226
+ },
1227
+ /**
1228
+ * Set the active workspace
1229
+ */
1230
+ setActive(name) {
1231
+ const registry = loadRegistry();
1232
+ if (!registry.workspaces.includes(name)) {
1233
+ return false;
1234
+ }
1235
+ registry.active = name;
1236
+ saveRegistry(registry);
1237
+ return true;
1238
+ },
1239
+ /**
1240
+ * List all workspace names
1241
+ */
1242
+ list() {
1243
+ this.ensureInitialized();
1244
+ return loadRegistry().workspaces;
1245
+ },
1246
+ /**
1247
+ * Check if a workspace exists
1248
+ */
1249
+ exists(name) {
1250
+ this.ensureInitialized();
1251
+ return loadRegistry().workspaces.includes(name);
1252
+ },
1253
+ /**
1254
+ * Create a new workspace
1255
+ */
1256
+ create(name, config2) {
1257
+ const registry = loadRegistry();
1258
+ if (registry.workspaces.includes(name)) {
1259
+ return false;
1260
+ }
1261
+ const wsDir = join2(WORKSPACES_DIR, name);
1262
+ ensureDir(wsDir);
1263
+ ensureDir(join2(wsDir, "snapshots"));
1264
+ const configPath = join2(wsDir, "config.json");
1265
+ writeFileSync(configPath, JSON.stringify(config2, null, 2));
1266
+ writeFileSync(join2(wsDir, "timer.json"), JSON.stringify({ solveTimes: {}, activeTimer: null }, null, 2));
1267
+ writeFileSync(join2(wsDir, "collab.json"), JSON.stringify({ session: null }, null, 2));
1268
+ registry.workspaces.push(name);
1269
+ saveRegistry(registry);
1270
+ return true;
1271
+ },
1272
+ /**
1273
+ * Delete a workspace
1274
+ */
1275
+ delete(name) {
1276
+ if (name === "default") {
1277
+ return false;
1278
+ }
1279
+ const registry = loadRegistry();
1280
+ if (!registry.workspaces.includes(name)) {
1281
+ return false;
1282
+ }
1283
+ registry.workspaces = registry.workspaces.filter((w) => w !== name);
1284
+ if (registry.active === name) {
1285
+ registry.active = "default";
1286
+ }
1287
+ saveRegistry(registry);
1288
+ return true;
1289
+ },
1290
+ /**
1291
+ * Get the directory path for a workspace
1292
+ */
1293
+ getWorkspaceDir(name) {
1294
+ const wsName = name ?? this.getActive();
1295
+ return join2(WORKSPACES_DIR, wsName);
1296
+ },
1297
+ /**
1298
+ * Get config for a workspace
1299
+ */
1300
+ getConfig(name) {
1301
+ const wsName = name ?? this.getActive();
1302
+ const configPath = join2(WORKSPACES_DIR, wsName, "config.json");
1303
+ if (existsSync(configPath)) {
1304
+ return JSON.parse(readFileSync(configPath, "utf-8"));
1305
+ }
1306
+ return {
1307
+ workDir: join2(homedir2(), "leetcode"),
1308
+ lang: "typescript"
1309
+ };
1310
+ },
1311
+ /**
1312
+ * Update config for a workspace
1313
+ */
1314
+ setConfig(config2, name) {
1315
+ const wsName = name ?? this.getActive();
1316
+ const wsDir = join2(WORKSPACES_DIR, wsName);
1317
+ ensureDir(wsDir);
1318
+ const currentConfig = this.getConfig(wsName);
1319
+ const newConfig = { ...currentConfig, ...config2 };
1320
+ writeFileSync(join2(wsDir, "config.json"), JSON.stringify(newConfig, null, 2));
1321
+ },
1322
+ /**
1323
+ * Get snapshots directory for active workspace
1324
+ */
1325
+ getSnapshotsDir() {
1326
+ return join2(this.getWorkspaceDir(), "snapshots");
1327
+ },
1328
+ /**
1329
+ * Get timer file path for active workspace
1330
+ */
1331
+ getTimerPath() {
1332
+ return join2(this.getWorkspaceDir(), "timer.json");
1333
+ },
1334
+ /**
1335
+ * Get collab file path for active workspace
1336
+ */
1337
+ getCollabPath() {
1338
+ return join2(this.getWorkspaceDir(), "collab.json");
1339
+ }
1340
+ };
1341
+
1342
+ // src/storage/config.ts
952
1343
  var config = {
953
1344
  getConfig() {
1345
+ const wsConfig = workspaceStorage.getConfig();
954
1346
  return {
955
- language: configStore.get("language"),
956
- editor: configStore.get("editor"),
957
- workDir: configStore.get("workDir"),
958
- repo: configStore.get("repo")
1347
+ language: wsConfig.lang,
1348
+ editor: wsConfig.editor,
1349
+ workDir: wsConfig.workDir,
1350
+ repo: wsConfig.syncRepo
959
1351
  };
960
1352
  },
961
1353
  setLanguage(language) {
962
- configStore.set("language", language);
1354
+ workspaceStorage.setConfig({ lang: language });
963
1355
  },
964
1356
  setEditor(editor) {
965
- configStore.set("editor", editor);
1357
+ workspaceStorage.setConfig({ editor });
966
1358
  },
967
1359
  setWorkDir(workDir) {
968
- configStore.set("workDir", workDir);
1360
+ workspaceStorage.setConfig({ workDir });
969
1361
  },
970
1362
  setRepo(repo) {
971
- configStore.set("repo", repo);
1363
+ workspaceStorage.setConfig({ syncRepo: repo });
972
1364
  },
973
1365
  deleteRepo() {
974
- configStore.delete("repo");
1366
+ const wsConfig = workspaceStorage.getConfig();
1367
+ delete wsConfig.syncRepo;
1368
+ workspaceStorage.setConfig(wsConfig);
975
1369
  },
976
1370
  getLanguage() {
977
- return configStore.get("language");
1371
+ return workspaceStorage.getConfig().lang;
978
1372
  },
979
1373
  getEditor() {
980
- return configStore.get("editor");
1374
+ return workspaceStorage.getConfig().editor;
981
1375
  },
982
1376
  getWorkDir() {
983
- return configStore.get("workDir");
1377
+ return workspaceStorage.getConfig().workDir;
984
1378
  },
985
1379
  getRepo() {
986
- return configStore.get("repo");
1380
+ return workspaceStorage.getConfig().syncRepo;
987
1381
  },
988
1382
  getPath() {
989
- return configStore.path;
1383
+ return join3(workspaceStorage.getWorkspaceDir(), "config.json");
1384
+ },
1385
+ // New workspace-aware methods
1386
+ getActiveWorkspace() {
1387
+ return workspaceStorage.getActive();
990
1388
  }
991
1389
  };
992
1390
 
@@ -1112,7 +1510,7 @@ function getSolutionFileName(problemId, titleSlug, language) {
1112
1510
  // src/utils/editor.ts
1113
1511
  import { spawn } from "child_process";
1114
1512
  import open from "open";
1115
- import chalk6 from "chalk";
1513
+ import chalk7 from "chalk";
1116
1514
  var TERMINAL_EDITORS = ["vim", "nvim", "vi", "nano", "emacs", "micro", "helix"];
1117
1515
  var VSCODE_EDITORS = ["code", "code-insiders", "cursor", "codium", "vscodium"];
1118
1516
  async function openInEditor(filePath, workDir) {
@@ -1120,7 +1518,7 @@ async function openInEditor(filePath, workDir) {
1120
1518
  const workspace = workDir ?? config.getWorkDir();
1121
1519
  if (TERMINAL_EDITORS.includes(editor)) {
1122
1520
  console.log();
1123
- console.log(chalk6.gray(`Open with: ${editor} ${filePath}`));
1521
+ console.log(chalk7.gray(`Open with: ${editor} ${filePath}`));
1124
1522
  return;
1125
1523
  }
1126
1524
  try {
@@ -1160,13 +1558,13 @@ async function pickCommand(idOrSlug, options) {
1160
1558
  const template = getCodeTemplate(snippets, language);
1161
1559
  let code;
1162
1560
  if (snippets.length === 0) {
1163
- spinner.warn(chalk7.yellow("Premium Problem (No code snippets available)"));
1164
- console.log(chalk7.gray("Generating placeholder file with problem info..."));
1561
+ spinner.warn(chalk8.yellow("Premium Problem (No code snippets available)"));
1562
+ console.log(chalk8.gray("Generating placeholder file with problem info..."));
1165
1563
  code = `// \u{1F512} Premium Problem - ${problem.title}
1166
1564
  // Solution stub not available - visit LeetCode to view`;
1167
1565
  } else if (!template) {
1168
1566
  spinner.fail(`No code template available for ${language}`);
1169
- console.log(chalk7.gray(`Available languages: ${snippets.map((s) => s.langSlug).join(", ")}`));
1567
+ console.log(chalk8.gray(`Available languages: ${snippets.map((s) => s.langSlug).join(", ")}`));
1170
1568
  return false;
1171
1569
  } else {
1172
1570
  code = template.code;
@@ -1183,26 +1581,26 @@ async function pickCommand(idOrSlug, options) {
1183
1581
  const workDir = config.getWorkDir();
1184
1582
  const difficulty = problem.difficulty;
1185
1583
  const category = problem.topicTags.length > 0 ? problem.topicTags[0].name.replace(/[^\w\s-]/g, "").trim() : "Uncategorized";
1186
- const targetDir = join3(workDir, difficulty, category);
1187
- if (!existsSync(targetDir)) {
1584
+ const targetDir = join4(workDir, difficulty, category);
1585
+ if (!existsSync2(targetDir)) {
1188
1586
  await mkdir(targetDir, { recursive: true });
1189
1587
  }
1190
1588
  const fileName = getSolutionFileName(problem.questionFrontendId, problem.titleSlug, language);
1191
- const filePath = join3(targetDir, fileName);
1192
- if (existsSync(filePath)) {
1589
+ const filePath = join4(targetDir, fileName);
1590
+ if (existsSync2(filePath)) {
1193
1591
  spinner.warn(`File already exists: ${fileName}`);
1194
- console.log(chalk7.gray(`Path: ${filePath}`));
1592
+ console.log(chalk8.gray(`Path: ${filePath}`));
1195
1593
  if (options.open !== false) {
1196
1594
  await openInEditor(filePath);
1197
1595
  }
1198
1596
  return true;
1199
1597
  }
1200
1598
  await writeFile(filePath, content, "utf-8");
1201
- spinner.succeed(`Created ${chalk7.green(fileName)}`);
1202
- console.log(chalk7.gray(`Path: ${filePath}`));
1599
+ spinner.succeed(`Created ${chalk8.green(fileName)}`);
1600
+ console.log(chalk8.gray(`Path: ${filePath}`));
1203
1601
  console.log();
1204
- console.log(chalk7.cyan(`${problem.questionFrontendId}. ${problem.title}`));
1205
- console.log(chalk7.gray(`Difficulty: ${problem.difficulty} | Category: ${category}`));
1602
+ console.log(chalk8.cyan(`${problem.questionFrontendId}. ${problem.title}`));
1603
+ console.log(chalk8.gray(`Difficulty: ${problem.difficulty} | Category: ${category}`));
1206
1604
  if (options.open !== false) {
1207
1605
  await openInEditor(filePath);
1208
1606
  }
@@ -1211,17 +1609,17 @@ async function pickCommand(idOrSlug, options) {
1211
1609
  spinner.fail("Failed to fetch problem");
1212
1610
  if (error instanceof Error) {
1213
1611
  if (error.message.includes("expected object, received null")) {
1214
- console.log(chalk7.red(`Problem "${idOrSlug}" not found`));
1612
+ console.log(chalk8.red(`Problem "${idOrSlug}" not found`));
1215
1613
  } else {
1216
1614
  try {
1217
1615
  const zodError = JSON.parse(error.message);
1218
1616
  if (Array.isArray(zodError)) {
1219
- console.log(chalk7.red("API Response Validation Failed"));
1617
+ console.log(chalk8.red("API Response Validation Failed"));
1220
1618
  } else {
1221
- console.log(chalk7.red(error.message));
1619
+ console.log(chalk8.red(error.message));
1222
1620
  }
1223
1621
  } catch {
1224
- console.log(chalk7.red(error.message));
1622
+ console.log(chalk8.red(error.message));
1225
1623
  }
1226
1624
  }
1227
1625
  }
@@ -1230,12 +1628,12 @@ async function pickCommand(idOrSlug, options) {
1230
1628
  }
1231
1629
  async function batchPickCommand(ids, options) {
1232
1630
  if (ids.length === 0) {
1233
- console.log(chalk7.yellow("Please provide at least one problem ID"));
1631
+ console.log(chalk8.yellow("Please provide at least one problem ID"));
1234
1632
  return;
1235
1633
  }
1236
1634
  const { authorized } = await requireAuth();
1237
1635
  if (!authorized) return;
1238
- console.log(chalk7.cyan(`\u{1F4E6} Picking ${ids.length} problem${ids.length !== 1 ? "s" : ""}...`));
1636
+ console.log(chalk8.cyan(`\u{1F4E6} Picking ${ids.length} problem${ids.length !== 1 ? "s" : ""}...`));
1239
1637
  console.log();
1240
1638
  console.log();
1241
1639
  let succeeded = 0;
@@ -1249,33 +1647,33 @@ async function batchPickCommand(ids, options) {
1249
1647
  }
1250
1648
  console.log();
1251
1649
  }
1252
- console.log(chalk7.gray("\u2500".repeat(50)));
1650
+ console.log(chalk8.gray("\u2500".repeat(50)));
1253
1651
  console.log(
1254
- chalk7.bold(
1255
- `Done! ${chalk7.green(`${succeeded} succeeded`)}${failed > 0 ? `, ${chalk7.red(`${failed} failed`)}` : ""}`
1652
+ chalk8.bold(
1653
+ `Done! ${chalk8.green(`${succeeded} succeeded`)}${failed > 0 ? `, ${chalk8.red(`${failed} failed`)}` : ""}`
1256
1654
  )
1257
1655
  );
1258
1656
  }
1259
1657
 
1260
1658
  // src/commands/test.ts
1261
1659
  import { readFile } from "fs/promises";
1262
- import { existsSync as existsSync3 } from "fs";
1660
+ import { existsSync as existsSync4 } from "fs";
1263
1661
  import { basename } from "path";
1264
1662
  import ora5 from "ora";
1265
- import chalk8 from "chalk";
1663
+ import chalk9 from "chalk";
1266
1664
 
1267
1665
  // src/utils/fileUtils.ts
1268
1666
  import { readdir } from "fs/promises";
1269
- import { existsSync as existsSync2 } from "fs";
1270
- import { join as join4 } from "path";
1667
+ import { existsSync as existsSync3 } from "fs";
1668
+ import { join as join5 } from "path";
1271
1669
  var MAX_SEARCH_DEPTH = 5;
1272
1670
  async function findSolutionFile(dir, problemId, currentDepth = 0) {
1273
- if (!existsSync2(dir)) return null;
1671
+ if (!existsSync3(dir)) return null;
1274
1672
  if (currentDepth >= MAX_SEARCH_DEPTH) return null;
1275
1673
  const entries = await readdir(dir, { withFileTypes: true });
1276
1674
  for (const entry of entries) {
1277
1675
  if (entry.name.startsWith(".")) continue;
1278
- const fullPath = join4(dir, entry.name);
1676
+ const fullPath = join5(dir, entry.name);
1279
1677
  if (entry.isDirectory()) {
1280
1678
  const found = await findSolutionFile(fullPath, problemId, currentDepth + 1);
1281
1679
  if (found) return found;
@@ -1289,11 +1687,11 @@ async function findSolutionFile(dir, problemId, currentDepth = 0) {
1289
1687
  return null;
1290
1688
  }
1291
1689
  async function findFileByName(dir, fileName, currentDepth = 0) {
1292
- if (!existsSync2(dir)) return null;
1690
+ if (!existsSync3(dir)) return null;
1293
1691
  if (currentDepth >= MAX_SEARCH_DEPTH) return null;
1294
1692
  const entries = await readdir(dir, { withFileTypes: true });
1295
1693
  for (const entry of entries) {
1296
- const fullPath = join4(dir, entry.name);
1694
+ const fullPath = join5(dir, entry.name);
1297
1695
  if (entry.isDirectory()) {
1298
1696
  const found = await findFileByName(fullPath, fileName, currentDepth + 1);
1299
1697
  if (found) return found;
@@ -1334,42 +1732,56 @@ function getLangSlugFromExtension(ext) {
1334
1732
  }
1335
1733
 
1336
1734
  // src/utils/validation.ts
1735
+ import { resolve, sep } from "path";
1337
1736
  function isProblemId(input) {
1338
1737
  return /^\d+$/.test(input);
1339
1738
  }
1340
1739
  function isFileName(input) {
1341
1740
  return !input.includes("/") && !input.includes("\\") && input.includes(".");
1342
1741
  }
1742
+ function isPathInsideWorkDir(filePath, workDir) {
1743
+ const resolvedFilePath = resolve(filePath);
1744
+ const resolvedWorkDir = resolve(workDir);
1745
+ const workDirWithSep = resolvedWorkDir.endsWith(sep) ? resolvedWorkDir : resolvedWorkDir + sep;
1746
+ return resolvedFilePath === resolvedWorkDir || resolvedFilePath.startsWith(workDirWithSep);
1747
+ }
1343
1748
 
1344
1749
  // src/commands/test.ts
1345
1750
  async function testCommand(fileOrId, options) {
1346
1751
  const { authorized } = await requireAuth();
1347
1752
  if (!authorized) return;
1348
1753
  let filePath = fileOrId;
1754
+ const workDir = config.getWorkDir();
1349
1755
  if (isProblemId(fileOrId)) {
1350
- const workDir = config.getWorkDir();
1351
1756
  const found = await findSolutionFile(workDir, fileOrId);
1352
1757
  if (!found) {
1353
- console.log(chalk8.red(`No solution file found for problem ${fileOrId}`));
1354
- console.log(chalk8.gray(`Looking in: ${workDir}`));
1355
- console.log(chalk8.gray(`Run "leetcode pick ${fileOrId}" first to create a solution file.`));
1758
+ console.log(chalk9.red(`No solution file found for problem ${fileOrId}`));
1759
+ console.log(chalk9.gray(`Looking in: ${workDir}`));
1760
+ console.log(chalk9.gray(`Run "leetcode pick ${fileOrId}" first to create a solution file.`));
1356
1761
  return;
1357
1762
  }
1358
1763
  filePath = found;
1359
- console.log(chalk8.gray(`Found: ${filePath}`));
1764
+ console.log(chalk9.gray(`Found: ${filePath}`));
1360
1765
  } else if (isFileName(fileOrId)) {
1361
- const workDir = config.getWorkDir();
1362
1766
  const found = await findFileByName(workDir, fileOrId);
1363
1767
  if (!found) {
1364
- console.log(chalk8.red(`File not found: ${fileOrId}`));
1365
- console.log(chalk8.gray(`Looking in: ${workDir}`));
1768
+ console.log(chalk9.red(`File not found: ${fileOrId}`));
1769
+ console.log(chalk9.gray(`Looking in: ${workDir}`));
1366
1770
  return;
1367
1771
  }
1368
1772
  filePath = found;
1369
- console.log(chalk8.gray(`Found: ${filePath}`));
1773
+ console.log(chalk9.gray(`Found: ${filePath}`));
1370
1774
  }
1371
- if (!existsSync3(filePath)) {
1372
- console.log(chalk8.red(`File not found: ${filePath}`));
1775
+ if (!existsSync4(filePath)) {
1776
+ console.log(chalk9.red(`File not found: ${filePath}`));
1777
+ return;
1778
+ }
1779
+ if (!isPathInsideWorkDir(filePath, workDir)) {
1780
+ console.log(chalk9.red("\u26A0\uFE0F Security Error: File path is outside the configured workspace"));
1781
+ console.log(chalk9.gray(`File: ${filePath}`));
1782
+ console.log(chalk9.gray(`Workspace: ${workDir}`));
1783
+ console.log(chalk9.yellow("\nFor security reasons, you can only test files from within your workspace."));
1784
+ console.log(chalk9.gray('Use "leetcode config workdir <path>" to change your workspace.'));
1373
1785
  return;
1374
1786
  }
1375
1787
  const spinner = ora5({ text: "Reading solution file...", spinner: "dots" }).start();
@@ -1378,8 +1790,8 @@ async function testCommand(fileOrId, options) {
1378
1790
  const match = fileName.match(/^(\d+)\.([^.]+)\./);
1379
1791
  if (!match) {
1380
1792
  spinner.fail("Invalid filename format");
1381
- console.log(chalk8.gray("Expected format: {id}.{title-slug}.{ext}"));
1382
- console.log(chalk8.gray("Example: 1.two-sum.ts"));
1793
+ console.log(chalk9.gray("Expected format: {id}.{title-slug}.{ext}"));
1794
+ console.log(chalk9.gray("Example: 1.two-sum.ts"));
1383
1795
  return;
1384
1796
  }
1385
1797
  const [, problemId, titleSlug] = match;
@@ -1396,62 +1808,75 @@ async function testCommand(fileOrId, options) {
1396
1808
  spinner.text = "Running tests...";
1397
1809
  const result = await leetcodeClient.testSolution(titleSlug, code, lang, testcases, problem.questionId);
1398
1810
  spinner.stop();
1399
- displayTestResult(result);
1811
+ displayTestResult(result, options.visualize ? problem.topicTags : void 0);
1400
1812
  } catch (error) {
1401
1813
  spinner.fail("Test failed");
1402
1814
  if (error instanceof Error) {
1403
- console.log(chalk8.red(error.message));
1815
+ console.log(chalk9.red(error.message));
1404
1816
  }
1405
1817
  }
1406
1818
  }
1407
1819
 
1408
1820
  // src/commands/submit.ts
1409
1821
  import { readFile as readFile2 } from "fs/promises";
1410
- import { existsSync as existsSync4 } from "fs";
1822
+ import { existsSync as existsSync6 } from "fs";
1411
1823
  import { basename as basename2 } from "path";
1412
1824
  import ora6 from "ora";
1413
- import chalk9 from "chalk";
1825
+ import chalk10 from "chalk";
1414
1826
 
1415
1827
  // 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
1828
+ import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1829
+ import { dirname } from "path";
1830
+ function getTimerPath() {
1831
+ return workspaceStorage.getTimerPath();
1832
+ }
1833
+ function loadTimer() {
1834
+ const path = getTimerPath();
1835
+ if (existsSync5(path)) {
1836
+ return JSON.parse(readFileSync2(path, "utf-8"));
1425
1837
  }
1426
- });
1838
+ return { solveTimes: {}, activeTimer: null };
1839
+ }
1840
+ function saveTimer(data) {
1841
+ const timerPath = getTimerPath();
1842
+ const dir = dirname(timerPath);
1843
+ if (!existsSync5(dir)) {
1844
+ mkdirSync2(dir, { recursive: true });
1845
+ }
1846
+ writeFileSync2(timerPath, JSON.stringify(data, null, 2));
1847
+ }
1427
1848
  var timerStorage = {
1428
1849
  startTimer(problemId, title, difficulty, durationMinutes) {
1429
- timerStore.set("activeTimer", {
1850
+ const data = loadTimer();
1851
+ data.activeTimer = {
1430
1852
  problemId,
1431
1853
  title,
1432
1854
  difficulty,
1433
1855
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1434
1856
  durationMinutes
1435
- });
1857
+ };
1858
+ saveTimer(data);
1436
1859
  },
1437
1860
  getActiveTimer() {
1438
- return timerStore.get("activeTimer");
1861
+ return loadTimer().activeTimer;
1439
1862
  },
1440
1863
  stopTimer() {
1441
- const active = timerStore.get("activeTimer");
1864
+ const data = loadTimer();
1865
+ const active = data.activeTimer;
1442
1866
  if (!active) return null;
1443
1867
  const startedAt = new Date(active.startedAt);
1444
1868
  const now = /* @__PURE__ */ new Date();
1445
1869
  const durationSeconds = Math.floor((now.getTime() - startedAt.getTime()) / 1e3);
1446
- timerStore.set("activeTimer", null);
1870
+ data.activeTimer = null;
1871
+ saveTimer(data);
1447
1872
  return { durationSeconds };
1448
1873
  },
1449
1874
  recordSolveTime(problemId, title, difficulty, durationSeconds, timerMinutes) {
1450
- const solveTimes = timerStore.get("solveTimes") ?? {};
1451
- if (!solveTimes[problemId]) {
1452
- solveTimes[problemId] = [];
1875
+ const data = loadTimer();
1876
+ if (!data.solveTimes[problemId]) {
1877
+ data.solveTimes[problemId] = [];
1453
1878
  }
1454
- solveTimes[problemId].push({
1879
+ data.solveTimes[problemId].push({
1455
1880
  problemId,
1456
1881
  title,
1457
1882
  difficulty,
@@ -1459,17 +1884,17 @@ var timerStorage = {
1459
1884
  durationSeconds,
1460
1885
  timerMinutes
1461
1886
  });
1462
- timerStore.set("solveTimes", solveTimes);
1887
+ saveTimer(data);
1463
1888
  },
1464
1889
  getSolveTimes(problemId) {
1465
- const solveTimes = timerStore.get("solveTimes") ?? {};
1466
- return solveTimes[problemId] ?? [];
1890
+ const data = loadTimer();
1891
+ return data.solveTimes[problemId] ?? [];
1467
1892
  },
1468
1893
  getAllSolveTimes() {
1469
- return timerStore.get("solveTimes") ?? {};
1894
+ return loadTimer().solveTimes ?? {};
1470
1895
  },
1471
1896
  getStats() {
1472
- const solveTimes = timerStore.get("solveTimes") ?? {};
1897
+ const solveTimes = loadTimer().solveTimes ?? {};
1473
1898
  let totalProblems = 0;
1474
1899
  let totalTime = 0;
1475
1900
  for (const times of Object.values(solveTimes)) {
@@ -1491,30 +1916,37 @@ async function submitCommand(fileOrId) {
1491
1916
  const { authorized } = await requireAuth();
1492
1917
  if (!authorized) return;
1493
1918
  let filePath = fileOrId;
1919
+ const workDir = config.getWorkDir();
1494
1920
  if (isProblemId(fileOrId)) {
1495
- const workDir = config.getWorkDir();
1496
1921
  const found = await findSolutionFile(workDir, fileOrId);
1497
1922
  if (!found) {
1498
- console.log(chalk9.red(`No solution file found for problem ${fileOrId}`));
1499
- console.log(chalk9.gray(`Looking in: ${workDir}`));
1500
- console.log(chalk9.gray(`Run "leetcode pick ${fileOrId}" first to create a solution file.`));
1923
+ console.log(chalk10.red(`No solution file found for problem ${fileOrId}`));
1924
+ console.log(chalk10.gray(`Looking in: ${workDir}`));
1925
+ console.log(chalk10.gray(`Run "leetcode pick ${fileOrId}" first to create a solution file.`));
1501
1926
  return;
1502
1927
  }
1503
1928
  filePath = found;
1504
- console.log(chalk9.gray(`Found: ${filePath}`));
1929
+ console.log(chalk10.gray(`Found: ${filePath}`));
1505
1930
  } else if (isFileName(fileOrId)) {
1506
- const workDir = config.getWorkDir();
1507
1931
  const found = await findFileByName(workDir, fileOrId);
1508
1932
  if (!found) {
1509
- console.log(chalk9.red(`File not found: ${fileOrId}`));
1510
- console.log(chalk9.gray(`Looking in: ${workDir}`));
1933
+ console.log(chalk10.red(`File not found: ${fileOrId}`));
1934
+ console.log(chalk10.gray(`Looking in: ${workDir}`));
1511
1935
  return;
1512
1936
  }
1513
1937
  filePath = found;
1514
- console.log(chalk9.gray(`Found: ${filePath}`));
1938
+ console.log(chalk10.gray(`Found: ${filePath}`));
1515
1939
  }
1516
- if (!existsSync4(filePath)) {
1517
- console.log(chalk9.red(`File not found: ${filePath}`));
1940
+ if (!existsSync6(filePath)) {
1941
+ console.log(chalk10.red(`File not found: ${filePath}`));
1942
+ return;
1943
+ }
1944
+ if (!isPathInsideWorkDir(filePath, workDir)) {
1945
+ console.log(chalk10.red("\u26A0\uFE0F Security Error: File path is outside the configured workspace"));
1946
+ console.log(chalk10.gray(`File: ${filePath}`));
1947
+ console.log(chalk10.gray(`Workspace: ${workDir}`));
1948
+ console.log(chalk10.yellow("\nFor security reasons, you can only submit files from within your workspace."));
1949
+ console.log(chalk10.gray('Use "leetcode config workdir <path>" to change your workspace.'));
1518
1950
  return;
1519
1951
  }
1520
1952
  const spinner = ora6({ text: "Reading solution file...", spinner: "dots" }).start();
@@ -1523,8 +1955,8 @@ async function submitCommand(fileOrId) {
1523
1955
  const match = fileName.match(/^(\d+)\.([^.]+)\./);
1524
1956
  if (!match) {
1525
1957
  spinner.fail("Invalid filename format");
1526
- console.log(chalk9.gray("Expected format: {id}.{title-slug}.{ext}"));
1527
- console.log(chalk9.gray("Example: 1.two-sum.ts"));
1958
+ console.log(chalk10.gray("Expected format: {id}.{title-slug}.{ext}"));
1959
+ console.log(chalk10.gray("Example: 1.two-sum.ts"));
1528
1960
  return;
1529
1961
  }
1530
1962
  const [, problemId, titleSlug] = match;
@@ -1563,14 +1995,14 @@ async function submitCommand(fileOrId) {
1563
1995
  const timeStr = `${mins}m ${secs}s`;
1564
1996
  const withinLimit = timerResult.durationSeconds <= activeTimer.durationMinutes * 60;
1565
1997
  console.log();
1566
- console.log(chalk9.bold("\u23F1\uFE0F Timer Result:"));
1998
+ console.log(chalk10.bold("\u23F1\uFE0F Timer Result:"));
1567
1999
  console.log(
1568
- ` Solved in ${withinLimit ? chalk9.green(timeStr) : chalk9.yellow(timeStr)} (limit: ${activeTimer.durationMinutes}m)`
2000
+ ` Solved in ${withinLimit ? chalk10.green(timeStr) : chalk10.yellow(timeStr)} (limit: ${activeTimer.durationMinutes}m)`
1569
2001
  );
1570
2002
  if (withinLimit) {
1571
- console.log(chalk9.green(" \u2713 Within time limit!"));
2003
+ console.log(chalk10.green(" \u2713 Within time limit!"));
1572
2004
  } else {
1573
- console.log(chalk9.yellow(" \u26A0 Exceeded time limit"));
2005
+ console.log(chalk10.yellow(" \u26A0 Exceeded time limit"));
1574
2006
  }
1575
2007
  }
1576
2008
  }
@@ -1578,17 +2010,17 @@ async function submitCommand(fileOrId) {
1578
2010
  } catch (error) {
1579
2011
  spinner.fail("Submission failed");
1580
2012
  if (error instanceof Error) {
1581
- console.log(chalk9.red(error.message));
2013
+ console.log(chalk10.red(error.message));
1582
2014
  }
1583
2015
  }
1584
2016
  }
1585
2017
 
1586
2018
  // src/commands/stat.ts
1587
2019
  import ora7 from "ora";
1588
- import chalk11 from "chalk";
2020
+ import chalk12 from "chalk";
1589
2021
 
1590
2022
  // src/utils/stats-display.ts
1591
- import chalk10 from "chalk";
2023
+ import chalk11 from "chalk";
1592
2024
  var MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1593
2025
  function renderHeatmap(calendarJson) {
1594
2026
  const data = JSON.parse(calendarJson);
@@ -1621,25 +2053,25 @@ function renderHeatmap(calendarJson) {
1621
2053
  const totalSubmissions = weeks.reduce((sum, w) => sum + w.count, 0);
1622
2054
  const totalActiveDays = weeks.reduce((sum, w) => sum + w.days, 0);
1623
2055
  console.log();
1624
- console.log(chalk10.bold("\u{1F4C5} Activity (Last 12 Weeks)"));
1625
- console.log(chalk10.gray("How many problems you submitted and days you practiced."));
1626
- console.log(chalk10.gray("\u2500".repeat(50)));
2056
+ console.log(chalk11.bold("\u{1F4C5} Activity (Last 12 Weeks)"));
2057
+ console.log(chalk11.gray("How many problems you submitted and days you practiced."));
2058
+ console.log(chalk11.gray("\u2500".repeat(50)));
1627
2059
  console.log();
1628
2060
  for (const week of weeks) {
1629
2061
  const weekLabel = `${week.start} - ${week.end}`.padEnd(18);
1630
- const bar = week.count > 0 ? chalk10.green("\u2588".repeat(Math.min(week.count, 10))).padEnd(10) : chalk10.gray("\xB7").padEnd(10);
2062
+ const bar = week.count > 0 ? chalk11.green("\u2588".repeat(Math.min(week.count, 10))).padEnd(10) : chalk11.gray("\xB7").padEnd(10);
1631
2063
  const countStr = week.count > 0 ? `${week.count} subs`.padEnd(10) : "".padEnd(10);
1632
2064
  const daysStr = week.days > 0 ? `${week.days}d active` : "";
1633
- console.log(` ${chalk10.white(weekLabel)} ${bar} ${chalk10.cyan(countStr)} ${chalk10.yellow(daysStr)}`);
2065
+ console.log(` ${chalk11.white(weekLabel)} ${bar} ${chalk11.cyan(countStr)} ${chalk11.yellow(daysStr)}`);
1634
2066
  }
1635
- console.log(chalk10.gray("\u2500".repeat(50)));
1636
- console.log(` ${chalk10.bold.white("Total:")} ${chalk10.cyan.bold(totalSubmissions + " submissions")}, ${chalk10.yellow.bold(totalActiveDays + " days active")}`);
2067
+ console.log(chalk11.gray("\u2500".repeat(50)));
2068
+ console.log(` ${chalk11.bold.white("Total:")} ${chalk11.cyan.bold(totalSubmissions + " submissions")}, ${chalk11.yellow.bold(totalActiveDays + " days active")}`);
1637
2069
  console.log();
1638
2070
  }
1639
2071
  function renderSkillStats(fundamental, intermediate, advanced) {
1640
2072
  console.log();
1641
- console.log(chalk10.bold("\u{1F3AF} Skill Breakdown"));
1642
- console.log(chalk10.gray("\u2500".repeat(45)));
2073
+ console.log(chalk11.bold("\u{1F3AF} Skill Breakdown"));
2074
+ console.log(chalk11.gray("\u2500".repeat(45)));
1643
2075
  const renderSection = (title, stats, color) => {
1644
2076
  if (stats.length === 0) return;
1645
2077
  console.log();
@@ -1648,12 +2080,12 @@ function renderSkillStats(fundamental, intermediate, advanced) {
1648
2080
  for (const stat of sorted.slice(0, 8)) {
1649
2081
  const name = stat.tagName.padEnd(22);
1650
2082
  const bar = color("\u2588".repeat(Math.min(stat.problemsSolved, 15)));
1651
- console.log(` ${chalk10.white(name)} ${bar} ${chalk10.white(stat.problemsSolved)}`);
2083
+ console.log(` ${chalk11.white(name)} ${bar} ${chalk11.white(stat.problemsSolved)}`);
1652
2084
  }
1653
2085
  };
1654
- renderSection("Fundamental", fundamental, chalk10.green);
1655
- renderSection("Intermediate", intermediate, chalk10.yellow);
1656
- renderSection("Advanced", advanced, chalk10.red);
2086
+ renderSection("Fundamental", fundamental, chalk11.green);
2087
+ renderSection("Intermediate", intermediate, chalk11.yellow);
2088
+ renderSection("Advanced", advanced, chalk11.red);
1657
2089
  console.log();
1658
2090
  }
1659
2091
  function renderTrendChart(calendarJson) {
@@ -1674,15 +2106,15 @@ function renderTrendChart(calendarJson) {
1674
2106
  const maxVal = Math.max(...days.map((d) => d.count), 1);
1675
2107
  const chartHeight = 6;
1676
2108
  console.log();
1677
- console.log(chalk10.bold("\u{1F4C8} Submission Trend (Last 7 Days)"));
1678
- console.log(chalk10.gray("\u2500".repeat(35)));
2109
+ console.log(chalk11.bold("\u{1F4C8} Submission Trend (Last 7 Days)"));
2110
+ console.log(chalk11.gray("\u2500".repeat(35)));
1679
2111
  console.log();
1680
2112
  for (let row = chartHeight; row >= 1; row--) {
1681
2113
  let line = ` ${row === chartHeight ? maxVal.toString().padStart(2) : " "} \u2502`;
1682
2114
  for (const day of days) {
1683
2115
  const barHeight = Math.round(day.count / maxVal * chartHeight);
1684
2116
  if (barHeight >= row) {
1685
- line += chalk10.green(" \u2588\u2588 ");
2117
+ line += chalk11.green(" \u2588\u2588 ");
1686
2118
  } else {
1687
2119
  line += " ";
1688
2120
  }
@@ -1691,10 +2123,10 @@ function renderTrendChart(calendarJson) {
1691
2123
  }
1692
2124
  console.log(` 0 \u2514${"\u2500\u2500\u2500\u2500".repeat(7)}`);
1693
2125
  console.log(` ${days.map((d) => d.label.padEnd(4)).join("")}`);
1694
- console.log(chalk10.gray(` ${days.map((d) => d.count.toString().padEnd(4)).join("")}`));
2126
+ console.log(chalk11.gray(` ${days.map((d) => d.count.toString().padEnd(4)).join("")}`));
1695
2127
  const total = days.reduce((a, b) => a + b.count, 0);
1696
2128
  console.log();
1697
- console.log(chalk10.white(` Total: ${total} submissions this week`));
2129
+ console.log(chalk11.white(` Total: ${total} submissions this week`));
1698
2130
  console.log();
1699
2131
  }
1700
2132
 
@@ -1726,14 +2158,14 @@ async function statCommand(username, options = {}) {
1726
2158
  if (profile.submissionCalendar) {
1727
2159
  renderHeatmap(profile.submissionCalendar);
1728
2160
  } else {
1729
- console.log(chalk11.yellow("Calendar data not available."));
2161
+ console.log(chalk12.yellow("Calendar data not available."));
1730
2162
  }
1731
2163
  }
1732
2164
  if (options.trend) {
1733
2165
  if (profile.submissionCalendar) {
1734
2166
  renderTrendChart(profile.submissionCalendar);
1735
2167
  } else {
1736
- console.log(chalk11.yellow("Calendar data not available."));
2168
+ console.log(chalk12.yellow("Calendar data not available."));
1737
2169
  }
1738
2170
  }
1739
2171
  if (options.skills) {
@@ -1745,14 +2177,14 @@ async function statCommand(username, options = {}) {
1745
2177
  } catch (error) {
1746
2178
  spinner.fail("Failed to fetch statistics");
1747
2179
  if (error instanceof Error) {
1748
- console.log(chalk11.red(error.message));
2180
+ console.log(chalk12.red(error.message));
1749
2181
  }
1750
2182
  }
1751
2183
  }
1752
2184
 
1753
2185
  // src/commands/daily.ts
1754
2186
  import ora8 from "ora";
1755
- import chalk12 from "chalk";
2187
+ import chalk13 from "chalk";
1756
2188
  async function dailyCommand() {
1757
2189
  const { authorized } = await requireAuth();
1758
2190
  if (!authorized) return;
@@ -1762,19 +2194,19 @@ async function dailyCommand() {
1762
2194
  spinner.stop();
1763
2195
  displayDailyChallenge(daily.date, daily.question);
1764
2196
  console.log();
1765
- console.log(chalk12.gray("Run the following to start working on this problem:"));
1766
- console.log(chalk12.cyan(` leetcode pick ${daily.question.titleSlug}`));
2197
+ console.log(chalk13.gray("Run the following to start working on this problem:"));
2198
+ console.log(chalk13.cyan(` leetcode pick ${daily.question.titleSlug}`));
1767
2199
  } catch (error) {
1768
2200
  spinner.fail("Failed to fetch daily challenge");
1769
2201
  if (error instanceof Error) {
1770
- console.log(chalk12.red(error.message));
2202
+ console.log(chalk13.red(error.message));
1771
2203
  }
1772
2204
  }
1773
2205
  }
1774
2206
 
1775
2207
  // src/commands/random.ts
1776
2208
  import ora9 from "ora";
1777
- import chalk13 from "chalk";
2209
+ import chalk14 from "chalk";
1778
2210
  async function randomCommand(options) {
1779
2211
  const { authorized } = await requireAuth();
1780
2212
  if (!authorized) return;
@@ -1808,23 +2240,23 @@ async function randomCommand(options) {
1808
2240
  await pickCommand(titleSlug, { open: options.open ?? true });
1809
2241
  } else {
1810
2242
  await showCommand(titleSlug);
1811
- console.log(chalk13.gray("Run following to start solving:"));
1812
- console.log(chalk13.cyan(` leetcode pick ${titleSlug}`));
2243
+ console.log(chalk14.gray("Run following to start solving:"));
2244
+ console.log(chalk14.cyan(` leetcode pick ${titleSlug}`));
1813
2245
  }
1814
2246
  } catch (error) {
1815
2247
  spinner.fail("Failed to fetch random problem");
1816
2248
  if (error instanceof Error) {
1817
- console.log(chalk13.red(error.message));
2249
+ console.log(chalk14.red(error.message));
1818
2250
  }
1819
2251
  }
1820
2252
  }
1821
2253
 
1822
2254
  // src/commands/submissions.ts
1823
2255
  import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1824
- import { existsSync as existsSync5 } from "fs";
2256
+ import { existsSync as existsSync7 } from "fs";
1825
2257
  import { join as join6 } from "path";
1826
2258
  import ora10 from "ora";
1827
- import chalk14 from "chalk";
2259
+ import chalk15 from "chalk";
1828
2260
  async function submissionsCommand(idOrSlug, options) {
1829
2261
  const { authorized } = await requireAuth();
1830
2262
  if (!authorized) return;
@@ -1846,16 +2278,16 @@ async function submissionsCommand(idOrSlug, options) {
1846
2278
  const submissions = await leetcodeClient.getSubmissionList(slug, limit);
1847
2279
  spinner.stop();
1848
2280
  if (submissions.length === 0) {
1849
- console.log(chalk14.yellow("No submissions found."));
2281
+ console.log(chalk15.yellow("No submissions found."));
1850
2282
  return;
1851
2283
  }
1852
2284
  if (options.last) {
1853
2285
  const lastAC = submissions.find((s) => s.statusDisplay === "Accepted");
1854
2286
  if (lastAC) {
1855
- console.log(chalk14.bold("Last Accepted Submission:"));
2287
+ console.log(chalk15.bold("Last Accepted Submission:"));
1856
2288
  displaySubmissionsList([lastAC]);
1857
2289
  } else {
1858
- console.log(chalk14.yellow("No accepted submissions found in recent history."));
2290
+ console.log(chalk15.yellow("No accepted submissions found in recent history."));
1859
2291
  }
1860
2292
  } else {
1861
2293
  displaySubmissionsList(submissions);
@@ -1877,7 +2309,7 @@ async function submissionsCommand(idOrSlug, options) {
1877
2309
  const difficulty = problem.difficulty;
1878
2310
  const category = problem.topicTags.length > 0 ? problem.topicTags[0].name.replace(/[^\w\s-]/g, "").trim() : "Uncategorized";
1879
2311
  const targetDir = join6(workDir, difficulty, category);
1880
- if (!existsSync5(targetDir)) {
2312
+ if (!existsSync7(targetDir)) {
1881
2313
  await mkdir2(targetDir, { recursive: true });
1882
2314
  }
1883
2315
  const langSlug = details.lang.name;
@@ -1886,20 +2318,20 @@ async function submissionsCommand(idOrSlug, options) {
1886
2318
  const fileName = `${problem.questionFrontendId}.${problem.titleSlug}.submission-${lastAC.id}.${ext}`;
1887
2319
  const filePath = join6(targetDir, fileName);
1888
2320
  await writeFile2(filePath, details.code, "utf-8");
1889
- downloadSpinner.succeed(`Downloaded to ${chalk14.green(fileName)}`);
1890
- console.log(chalk14.gray(`Path: ${filePath}`));
2321
+ downloadSpinner.succeed(`Downloaded to ${chalk15.green(fileName)}`);
2322
+ console.log(chalk15.gray(`Path: ${filePath}`));
1891
2323
  }
1892
2324
  } catch (error) {
1893
2325
  spinner.fail("Failed to fetch submissions");
1894
2326
  if (error instanceof Error) {
1895
- console.log(chalk14.red(error.message));
2327
+ console.log(chalk15.red(error.message));
1896
2328
  }
1897
2329
  }
1898
2330
  }
1899
2331
 
1900
2332
  // src/commands/config.ts
1901
2333
  import inquirer2 from "inquirer";
1902
- import chalk15 from "chalk";
2334
+ import chalk16 from "chalk";
1903
2335
  var SUPPORTED_LANGUAGES = [
1904
2336
  "typescript",
1905
2337
  "javascript",
@@ -1921,34 +2353,38 @@ async function configCommand(options) {
1921
2353
  if (options.lang) {
1922
2354
  const langInput = options.lang.toLowerCase();
1923
2355
  if (!SUPPORTED_LANGUAGES.includes(langInput)) {
1924
- console.log(chalk15.red(`Unsupported language: ${options.lang}`));
1925
- console.log(chalk15.gray(`Supported: ${SUPPORTED_LANGUAGES.join(", ")}`));
2356
+ console.log(chalk16.red(`Unsupported language: ${options.lang}`));
2357
+ console.log(chalk16.gray(`Supported: ${SUPPORTED_LANGUAGES.join(", ")}`));
1926
2358
  return;
1927
2359
  }
1928
2360
  const lang = langInput;
1929
2361
  config.setLanguage(lang);
1930
- console.log(chalk15.green(`\u2713 Default language set to ${lang}`));
2362
+ console.log(chalk16.green(`\u2713 Default language set to ${lang}`));
1931
2363
  }
1932
2364
  if (options.editor) {
1933
2365
  config.setEditor(options.editor);
1934
- console.log(chalk15.green(`\u2713 Editor set to ${options.editor}`));
2366
+ console.log(chalk16.green(`\u2713 Editor set to ${options.editor}`));
1935
2367
  }
1936
2368
  if (options.workdir) {
1937
2369
  config.setWorkDir(options.workdir);
1938
- console.log(chalk15.green(`\u2713 Work directory set to ${options.workdir}`));
2370
+ console.log(chalk16.green(`\u2713 Work directory set to ${options.workdir}`));
1939
2371
  }
1940
2372
  if (options.repo !== void 0) {
1941
2373
  if (options.repo.trim() === "") {
1942
2374
  config.deleteRepo();
1943
- console.log(chalk15.green("\u2713 Repository URL cleared"));
2375
+ console.log(chalk16.green("\u2713 Repository URL cleared"));
1944
2376
  } else {
1945
2377
  config.setRepo(options.repo);
1946
- console.log(chalk15.green(`\u2713 Repository URL set to ${options.repo}`));
2378
+ console.log(chalk16.green(`\u2713 Repository URL set to ${options.repo}`));
1947
2379
  }
1948
2380
  }
1949
2381
  }
1950
2382
  async function configInteractiveCommand() {
1951
2383
  const currentConfig = config.getConfig();
2384
+ const workspace = config.getActiveWorkspace();
2385
+ console.log();
2386
+ console.log(chalk16.bold.cyan(`\u{1F4C1} Configuring workspace: ${workspace}`));
2387
+ console.log(chalk16.gray("\u2500".repeat(40)));
1952
2388
  const answers = await inquirer2.prompt([
1953
2389
  {
1954
2390
  type: "list",
@@ -1985,33 +2421,34 @@ async function configInteractiveCommand() {
1985
2421
  config.deleteRepo();
1986
2422
  }
1987
2423
  console.log();
1988
- console.log(chalk15.green("\u2713 Configuration saved"));
2424
+ console.log(chalk16.green("\u2713 Configuration saved"));
1989
2425
  showCurrentConfig();
1990
2426
  }
1991
2427
  function showCurrentConfig() {
1992
2428
  const currentConfig = config.getConfig();
1993
2429
  const creds = credentials.get();
2430
+ const workspace = config.getActiveWorkspace();
1994
2431
  console.log();
1995
- console.log(chalk15.bold("LeetCode CLI Configuration"));
1996
- console.log(chalk15.gray("\u2500".repeat(40)));
2432
+ console.log(chalk16.bold.cyan(`\u{1F4C1} Workspace: ${workspace}`));
2433
+ console.log(chalk16.gray("\u2500".repeat(40)));
1997
2434
  console.log();
1998
- console.log(chalk15.gray("Config file:"), config.getPath());
2435
+ console.log(chalk16.gray("Config file:"), config.getPath());
1999
2436
  console.log();
2000
- console.log(chalk15.gray("Language: "), chalk15.white(currentConfig.language));
2001
- console.log(chalk15.gray("Editor: "), chalk15.white(currentConfig.editor ?? "(not set)"));
2002
- console.log(chalk15.gray("Work Dir: "), chalk15.white(currentConfig.workDir));
2003
- console.log(chalk15.gray("Repo URL: "), chalk15.white(currentConfig.repo ?? "(not set)"));
2004
- console.log(chalk15.gray("Logged in: "), creds ? chalk15.green("Yes") : chalk15.yellow("No"));
2437
+ console.log(chalk16.gray("Language: "), chalk16.white(currentConfig.language));
2438
+ console.log(chalk16.gray("Editor: "), chalk16.white(currentConfig.editor ?? "(not set)"));
2439
+ console.log(chalk16.gray("Work Dir: "), chalk16.white(currentConfig.workDir));
2440
+ console.log(chalk16.gray("Repo URL: "), chalk16.white(currentConfig.repo ?? "(not set)"));
2441
+ console.log(chalk16.gray("Logged in: "), creds ? chalk16.green("Yes") : chalk16.yellow("No"));
2005
2442
  }
2006
2443
 
2007
2444
  // src/commands/bookmark.ts
2008
- import chalk16 from "chalk";
2445
+ import chalk17 from "chalk";
2009
2446
  import Table2 from "cli-table3";
2010
2447
  import ora11 from "ora";
2011
2448
 
2012
2449
  // src/storage/bookmarks.ts
2013
- import Conf4 from "conf";
2014
- var bookmarksStore = new Conf4({
2450
+ import Conf2 from "conf";
2451
+ var bookmarksStore = new Conf2({
2015
2452
  projectName: "leetcode-cli-bookmarks",
2016
2453
  defaults: { bookmarks: [] }
2017
2454
  });
@@ -2050,36 +2487,36 @@ var bookmarks = {
2050
2487
  async function bookmarkCommand(action, id) {
2051
2488
  const validActions = ["add", "remove", "list", "clear"];
2052
2489
  if (!validActions.includes(action)) {
2053
- console.log(chalk16.red(`Invalid action: ${action}`));
2054
- console.log(chalk16.gray("Valid actions: add, remove, list, clear"));
2490
+ console.log(chalk17.red(`Invalid action: ${action}`));
2491
+ console.log(chalk17.gray("Valid actions: add, remove, list, clear"));
2055
2492
  return;
2056
2493
  }
2057
2494
  switch (action) {
2058
2495
  case "add":
2059
2496
  if (!id) {
2060
- console.log(chalk16.red("Please provide a problem ID to bookmark"));
2497
+ console.log(chalk17.red("Please provide a problem ID to bookmark"));
2061
2498
  return;
2062
2499
  }
2063
2500
  if (!isProblemId(id)) {
2064
- console.log(chalk16.red(`Invalid problem ID: ${id}`));
2065
- console.log(chalk16.gray("Problem ID must be a positive integer"));
2501
+ console.log(chalk17.red(`Invalid problem ID: ${id}`));
2502
+ console.log(chalk17.gray("Problem ID must be a positive integer"));
2066
2503
  return;
2067
2504
  }
2068
2505
  if (bookmarks.add(id)) {
2069
- console.log(chalk16.green(`\u2713 Bookmarked problem ${id}`));
2506
+ console.log(chalk17.green(`\u2713 Bookmarked problem ${id}`));
2070
2507
  } else {
2071
- console.log(chalk16.yellow(`Problem ${id} is already bookmarked`));
2508
+ console.log(chalk17.yellow(`Problem ${id} is already bookmarked`));
2072
2509
  }
2073
2510
  break;
2074
2511
  case "remove":
2075
2512
  if (!id) {
2076
- console.log(chalk16.red("Please provide a problem ID to remove"));
2513
+ console.log(chalk17.red("Please provide a problem ID to remove"));
2077
2514
  return;
2078
2515
  }
2079
2516
  if (bookmarks.remove(id)) {
2080
- console.log(chalk16.green(`\u2713 Removed bookmark for problem ${id}`));
2517
+ console.log(chalk17.green(`\u2713 Removed bookmark for problem ${id}`));
2081
2518
  } else {
2082
- console.log(chalk16.yellow(`Problem ${id} is not bookmarked`));
2519
+ console.log(chalk17.yellow(`Problem ${id} is not bookmarked`));
2083
2520
  }
2084
2521
  break;
2085
2522
  case "list":
@@ -2088,10 +2525,10 @@ async function bookmarkCommand(action, id) {
2088
2525
  case "clear":
2089
2526
  const count = bookmarks.count();
2090
2527
  if (count === 0) {
2091
- console.log(chalk16.yellow("No bookmarks to clear"));
2528
+ console.log(chalk17.yellow("No bookmarks to clear"));
2092
2529
  } else {
2093
2530
  bookmarks.clear();
2094
- console.log(chalk16.green(`\u2713 Cleared ${count} bookmark${count !== 1 ? "s" : ""}`));
2531
+ console.log(chalk17.green(`\u2713 Cleared ${count} bookmark${count !== 1 ? "s" : ""}`));
2095
2532
  }
2096
2533
  break;
2097
2534
  }
@@ -2099,12 +2536,12 @@ async function bookmarkCommand(action, id) {
2099
2536
  async function listBookmarks() {
2100
2537
  const bookmarkList = bookmarks.list();
2101
2538
  if (bookmarkList.length === 0) {
2102
- console.log(chalk16.yellow("\u{1F4CC} No bookmarked problems"));
2103
- console.log(chalk16.gray('Use "leetcode bookmark add <id>" to bookmark a problem'));
2539
+ console.log(chalk17.yellow("\u{1F4CC} No bookmarked problems"));
2540
+ console.log(chalk17.gray('Use "leetcode bookmark add <id>" to bookmark a problem'));
2104
2541
  return;
2105
2542
  }
2106
2543
  console.log();
2107
- console.log(chalk16.bold.cyan(`\u{1F4CC} Bookmarked Problems (${bookmarkList.length})`));
2544
+ console.log(chalk17.bold.cyan(`\u{1F4CC} Bookmarked Problems (${bookmarkList.length})`));
2108
2545
  console.log();
2109
2546
  const { authorized } = await requireAuth();
2110
2547
  if (authorized) {
@@ -2112,10 +2549,10 @@ async function listBookmarks() {
2112
2549
  try {
2113
2550
  const table = new Table2({
2114
2551
  head: [
2115
- chalk16.cyan("ID"),
2116
- chalk16.cyan("Title"),
2117
- chalk16.cyan("Difficulty"),
2118
- chalk16.cyan("Status")
2552
+ chalk17.cyan("ID"),
2553
+ chalk17.cyan("Title"),
2554
+ chalk17.cyan("Difficulty"),
2555
+ chalk17.cyan("Status")
2119
2556
  ],
2120
2557
  colWidths: [8, 45, 12, 10],
2121
2558
  style: { head: [], border: [] }
@@ -2128,35 +2565,35 @@ async function listBookmarks() {
2128
2565
  problem.questionFrontendId,
2129
2566
  problem.title.length > 42 ? problem.title.slice(0, 39) + "..." : problem.title,
2130
2567
  colorDifficulty2(problem.difficulty),
2131
- problem.status === "ac" ? chalk16.green("\u2713") : chalk16.gray("-")
2568
+ problem.status === "ac" ? chalk17.green("\u2713") : chalk17.gray("-")
2132
2569
  ]);
2133
2570
  } else {
2134
- table.push([id, chalk16.gray("(not found)"), "-", "-"]);
2571
+ table.push([id, chalk17.gray("(not found)"), "-", "-"]);
2135
2572
  }
2136
2573
  } catch {
2137
- table.push([id, chalk16.gray("(error fetching)"), "-", "-"]);
2574
+ table.push([id, chalk17.gray("(error fetching)"), "-", "-"]);
2138
2575
  }
2139
2576
  }
2140
2577
  spinner.stop();
2141
2578
  console.log(table.toString());
2142
2579
  } catch {
2143
2580
  spinner.stop();
2144
- console.log(chalk16.gray("IDs: ") + bookmarkList.join(", "));
2581
+ console.log(chalk17.gray("IDs: ") + bookmarkList.join(", "));
2145
2582
  }
2146
2583
  } else {
2147
- console.log(chalk16.gray("IDs: ") + bookmarkList.join(", "));
2584
+ console.log(chalk17.gray("IDs: ") + bookmarkList.join(", "));
2148
2585
  console.log();
2149
- console.log(chalk16.gray("Login to see problem details"));
2586
+ console.log(chalk17.gray("Login to see problem details"));
2150
2587
  }
2151
2588
  }
2152
2589
  function colorDifficulty2(difficulty) {
2153
2590
  switch (difficulty.toLowerCase()) {
2154
2591
  case "easy":
2155
- return chalk16.green(difficulty);
2592
+ return chalk17.green(difficulty);
2156
2593
  case "medium":
2157
- return chalk16.yellow(difficulty);
2594
+ return chalk17.yellow(difficulty);
2158
2595
  case "hard":
2159
- return chalk16.red(difficulty);
2596
+ return chalk17.red(difficulty);
2160
2597
  default:
2161
2598
  return difficulty;
2162
2599
  }
@@ -2165,18 +2602,18 @@ function colorDifficulty2(difficulty) {
2165
2602
  // src/commands/notes.ts
2166
2603
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
2167
2604
  import { join as join7 } from "path";
2168
- import { existsSync as existsSync6 } from "fs";
2169
- import chalk17 from "chalk";
2605
+ import { existsSync as existsSync8 } from "fs";
2606
+ import chalk18 from "chalk";
2170
2607
  async function notesCommand(problemId, action) {
2171
2608
  if (!isProblemId(problemId)) {
2172
- console.log(chalk17.red(`Invalid problem ID: ${problemId}`));
2173
- console.log(chalk17.gray("Problem ID must be a positive integer"));
2609
+ console.log(chalk18.red(`Invalid problem ID: ${problemId}`));
2610
+ console.log(chalk18.gray("Problem ID must be a positive integer"));
2174
2611
  return;
2175
2612
  }
2176
2613
  const noteAction = action === "view" ? "view" : "edit";
2177
2614
  const notesDir = join7(config.getWorkDir(), ".notes");
2178
2615
  const notePath = join7(notesDir, `${problemId}.md`);
2179
- if (!existsSync6(notesDir)) {
2616
+ if (!existsSync8(notesDir)) {
2180
2617
  await mkdir3(notesDir, { recursive: true });
2181
2618
  }
2182
2619
  if (noteAction === "view") {
@@ -2186,32 +2623,32 @@ async function notesCommand(problemId, action) {
2186
2623
  }
2187
2624
  }
2188
2625
  async function viewNote(notePath, problemId) {
2189
- if (!existsSync6(notePath)) {
2190
- console.log(chalk17.yellow(`No notes found for problem ${problemId}`));
2191
- console.log(chalk17.gray(`Use "leetcode note ${problemId} edit" to create notes`));
2626
+ if (!existsSync8(notePath)) {
2627
+ console.log(chalk18.yellow(`No notes found for problem ${problemId}`));
2628
+ console.log(chalk18.gray(`Use "leetcode note ${problemId} edit" to create notes`));
2192
2629
  return;
2193
2630
  }
2194
2631
  try {
2195
2632
  const content = await readFile3(notePath, "utf-8");
2196
2633
  console.log();
2197
- console.log(chalk17.bold.cyan(`\u{1F4DD} Notes for Problem ${problemId}`));
2198
- console.log(chalk17.gray("\u2500".repeat(50)));
2634
+ console.log(chalk18.bold.cyan(`\u{1F4DD} Notes for Problem ${problemId}`));
2635
+ console.log(chalk18.gray("\u2500".repeat(50)));
2199
2636
  console.log();
2200
2637
  console.log(content);
2201
2638
  } catch (error) {
2202
- console.log(chalk17.red("Failed to read notes"));
2639
+ console.log(chalk18.red("Failed to read notes"));
2203
2640
  if (error instanceof Error) {
2204
- console.log(chalk17.gray(error.message));
2641
+ console.log(chalk18.gray(error.message));
2205
2642
  }
2206
2643
  }
2207
2644
  }
2208
2645
  async function editNote(notePath, problemId) {
2209
- if (!existsSync6(notePath)) {
2646
+ if (!existsSync8(notePath)) {
2210
2647
  const template = await generateNoteTemplate(problemId);
2211
2648
  await writeFile3(notePath, template, "utf-8");
2212
- console.log(chalk17.green(`\u2713 Created notes file for problem ${problemId}`));
2649
+ console.log(chalk18.green(`\u2713 Created notes file for problem ${problemId}`));
2213
2650
  }
2214
- console.log(chalk17.gray(`Opening: ${notePath}`));
2651
+ console.log(chalk18.gray(`Opening: ${notePath}`));
2215
2652
  await openInEditor(notePath);
2216
2653
  }
2217
2654
  async function generateNoteTemplate(problemId) {
@@ -2268,7 +2705,7 @@ async function generateNoteTemplate(problemId) {
2268
2705
  }
2269
2706
 
2270
2707
  // src/commands/today.ts
2271
- import chalk18 from "chalk";
2708
+ import chalk19 from "chalk";
2272
2709
  import ora12 from "ora";
2273
2710
  async function todayCommand() {
2274
2711
  const { authorized, username } = await requireAuth();
@@ -2283,48 +2720,48 @@ async function todayCommand() {
2283
2720
  ]);
2284
2721
  spinner.stop();
2285
2722
  console.log();
2286
- console.log(chalk18.bold.cyan(`\u{1F4CA} Today's Summary`), chalk18.gray(`- ${username}`));
2287
- console.log(chalk18.gray("\u2500".repeat(50)));
2723
+ console.log(chalk19.bold.cyan(`\u{1F4CA} Today's Summary`), chalk19.gray(`- ${username}`));
2724
+ console.log(chalk19.gray("\u2500".repeat(50)));
2288
2725
  console.log();
2289
- console.log(chalk18.yellow(`\u{1F525} Current Streak: ${profile.streak} day${profile.streak !== 1 ? "s" : ""}`));
2290
- console.log(chalk18.gray(` Total Active Days: ${profile.totalActiveDays}`));
2726
+ console.log(chalk19.yellow(`\u{1F525} Current Streak: ${profile.streak} day${profile.streak !== 1 ? "s" : ""}`));
2727
+ console.log(chalk19.gray(` Total Active Days: ${profile.totalActiveDays}`));
2291
2728
  console.log();
2292
2729
  const total = profile.acSubmissionNum.find((s) => s.difficulty === "All");
2293
2730
  const easy = profile.acSubmissionNum.find((s) => s.difficulty === "Easy");
2294
2731
  const medium = profile.acSubmissionNum.find((s) => s.difficulty === "Medium");
2295
2732
  const hard = profile.acSubmissionNum.find((s) => s.difficulty === "Hard");
2296
- console.log(chalk18.white("\u{1F4C8} Problems Solved:"));
2297
- console.log(` ${chalk18.green("Easy")}: ${easy?.count ?? 0} | ${chalk18.yellow("Medium")}: ${medium?.count ?? 0} | ${chalk18.red("Hard")}: ${hard?.count ?? 0}`);
2298
- console.log(` ${chalk18.bold("Total")}: ${total?.count ?? 0}`);
2733
+ console.log(chalk19.white("\u{1F4C8} Problems Solved:"));
2734
+ console.log(` ${chalk19.green("Easy")}: ${easy?.count ?? 0} | ${chalk19.yellow("Medium")}: ${medium?.count ?? 0} | ${chalk19.red("Hard")}: ${hard?.count ?? 0}`);
2735
+ console.log(` ${chalk19.bold("Total")}: ${total?.count ?? 0}`);
2299
2736
  console.log();
2300
- console.log(chalk18.bold.yellow("\u{1F3AF} Today's Challenge:"));
2737
+ console.log(chalk19.bold.yellow("\u{1F3AF} Today's Challenge:"));
2301
2738
  console.log(` ${daily.question.questionFrontendId}. ${daily.question.title}`);
2302
2739
  console.log(` ${colorDifficulty3(daily.question.difficulty)}`);
2303
2740
  const status = daily.question.status;
2304
2741
  if (status === "ac") {
2305
- console.log(chalk18.green(" \u2713 Completed!"));
2742
+ console.log(chalk19.green(" \u2713 Completed!"));
2306
2743
  } else if (status === "notac") {
2307
- console.log(chalk18.yellow(" \u25CB Attempted"));
2744
+ console.log(chalk19.yellow(" \u25CB Attempted"));
2308
2745
  } else {
2309
- console.log(chalk18.gray(" - Not started"));
2746
+ console.log(chalk19.gray(" - Not started"));
2310
2747
  }
2311
2748
  console.log();
2312
- console.log(chalk18.gray(` leetcode pick ${daily.question.questionFrontendId} # Start working on it`));
2749
+ console.log(chalk19.gray(` leetcode pick ${daily.question.questionFrontendId} # Start working on it`));
2313
2750
  } catch (error) {
2314
2751
  spinner.fail("Failed to fetch progress");
2315
2752
  if (error instanceof Error) {
2316
- console.log(chalk18.red(error.message));
2753
+ console.log(chalk19.red(error.message));
2317
2754
  }
2318
2755
  }
2319
2756
  }
2320
2757
  function colorDifficulty3(difficulty) {
2321
2758
  switch (difficulty.toLowerCase()) {
2322
2759
  case "easy":
2323
- return chalk18.green(difficulty);
2760
+ return chalk19.green(difficulty);
2324
2761
  case "medium":
2325
- return chalk18.yellow(difficulty);
2762
+ return chalk19.yellow(difficulty);
2326
2763
  case "hard":
2327
- return chalk18.red(difficulty);
2764
+ return chalk19.red(difficulty);
2328
2765
  default:
2329
2766
  return difficulty;
2330
2767
  }
@@ -2332,10 +2769,21 @@ function colorDifficulty3(difficulty) {
2332
2769
 
2333
2770
  // src/commands/sync.ts
2334
2771
  import { execSync } from "child_process";
2335
- import { existsSync as existsSync7 } from "fs";
2336
- import chalk19 from "chalk";
2772
+ import { existsSync as existsSync9 } from "fs";
2773
+ import chalk20 from "chalk";
2337
2774
  import inquirer3 from "inquirer";
2338
2775
  import ora13 from "ora";
2776
+ function sanitizeRepoName(name) {
2777
+ return name.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/--+/g, "-");
2778
+ }
2779
+ function isValidGitUrl(url) {
2780
+ const httpsPattern = /^https:\/\/[\w.-]+\/[\w./-]+$/;
2781
+ const sshPattern = /^git@[\w.-]+:[\w./-]+$/;
2782
+ return httpsPattern.test(url) || sshPattern.test(url);
2783
+ }
2784
+ function escapeShellArg(arg) {
2785
+ return `'${arg.replace(/'/g, "'\\''")}'`;
2786
+ }
2339
2787
  function isGitInstalled() {
2340
2788
  try {
2341
2789
  execSync("git --version", { stdio: "ignore" });
@@ -2378,7 +2826,7 @@ async function setupGitRepo(workDir) {
2378
2826
  }
2379
2827
  ]);
2380
2828
  if (!init) {
2381
- console.log(chalk19.yellow("Skipping basic git initialization."));
2829
+ console.log(chalk20.yellow("Skipping basic git initialization."));
2382
2830
  return false;
2383
2831
  }
2384
2832
  const spinner = ora13("Initializing git repository...").start();
@@ -2407,7 +2855,8 @@ async function setupRemote(workDir) {
2407
2855
  if (createGh) {
2408
2856
  spinner.start("Creating GitHub repository...");
2409
2857
  try {
2410
- const repoName = workDir.split("/").pop() || "leetcode-solutions";
2858
+ const rawRepoName = workDir.split("/").pop() || "leetcode-solutions";
2859
+ const repoName = sanitizeRepoName(rawRepoName);
2411
2860
  execSync(`gh repo create ${repoName} --private --source=. --remote=origin`, { cwd: workDir });
2412
2861
  spinner.succeed("Created and linked GitHub repository");
2413
2862
  repoUrl = getRemoteUrl(workDir) || "";
@@ -2417,12 +2866,12 @@ async function setupRemote(workDir) {
2417
2866
  return repoUrl;
2418
2867
  } catch (error) {
2419
2868
  spinner.fail("Failed to create GitHub repository");
2420
- console.log(chalk19.red(error));
2869
+ console.log(chalk20.red(error));
2421
2870
  }
2422
2871
  }
2423
2872
  }
2424
2873
  if (!repoUrl) {
2425
- console.log(chalk19.yellow("\nPlease create a new repository on your Git provider and copy the URL."));
2874
+ console.log(chalk20.yellow("\nPlease create a new repository on your Git provider and copy the URL."));
2426
2875
  const { url } = await inquirer3.prompt([
2427
2876
  {
2428
2877
  type: "input",
@@ -2434,6 +2883,11 @@ async function setupRemote(workDir) {
2434
2883
  repoUrl = url;
2435
2884
  }
2436
2885
  }
2886
+ if (repoUrl && !isValidGitUrl(repoUrl)) {
2887
+ console.log(chalk20.red("Invalid repository URL format."));
2888
+ console.log(chalk20.gray("Expected: https://github.com/user/repo or git@github.com:user/repo"));
2889
+ return "";
2890
+ }
2437
2891
  if (repoUrl) {
2438
2892
  config.setRepo(repoUrl);
2439
2893
  }
@@ -2441,21 +2895,21 @@ async function setupRemote(workDir) {
2441
2895
  if (!currentRemote && repoUrl) {
2442
2896
  try {
2443
2897
  execSync(`git remote add origin ${repoUrl}`, { cwd: workDir });
2444
- console.log(chalk19.green("\u2713 Added remote origin"));
2898
+ console.log(chalk20.green("\u2713 Added remote origin"));
2445
2899
  } catch (e) {
2446
- console.log(chalk19.red("Failed to add remote origin"));
2900
+ console.log(chalk20.red("Failed to add remote origin"));
2447
2901
  }
2448
2902
  }
2449
2903
  return repoUrl || "";
2450
2904
  }
2451
2905
  async function syncCommand() {
2452
2906
  const workDir = config.getWorkDir();
2453
- if (!existsSync7(workDir)) {
2454
- console.log(chalk19.red(`Work directory does not exist: ${workDir}`));
2907
+ if (!existsSync9(workDir)) {
2908
+ console.log(chalk20.red(`Work directory does not exist: ${workDir}`));
2455
2909
  return;
2456
2910
  }
2457
2911
  if (!isGitInstalled()) {
2458
- console.log(chalk19.red("Git is not installed. Please install Git to use command."));
2912
+ console.log(chalk20.red("Git is not installed. Please install Git to use command."));
2459
2913
  return;
2460
2914
  }
2461
2915
  if (!isMapRepo(workDir)) {
@@ -2475,7 +2929,7 @@ async function syncCommand() {
2475
2929
  const count = lines.length;
2476
2930
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
2477
2931
  const message = `Sync: ${count} solutions - ${timestamp}`;
2478
- execSync(`git commit -m "${message}"`, { cwd: workDir });
2932
+ execSync(`git commit -m ${escapeShellArg(message)}`, { cwd: workDir });
2479
2933
  try {
2480
2934
  execSync("git push -u origin main", { cwd: workDir, stdio: "ignore" });
2481
2935
  } catch {
@@ -2489,14 +2943,14 @@ async function syncCommand() {
2489
2943
  } catch (error) {
2490
2944
  spinner.fail("Sync failed");
2491
2945
  if (error.message) {
2492
- console.log(chalk19.red(error.message));
2946
+ console.log(chalk20.red(error.message));
2493
2947
  }
2494
2948
  }
2495
2949
  }
2496
2950
 
2497
2951
  // src/commands/timer.ts
2498
2952
  import ora14 from "ora";
2499
- import chalk20 from "chalk";
2953
+ import chalk21 from "chalk";
2500
2954
  var DEFAULT_TIMES = {
2501
2955
  Easy: 20,
2502
2956
  Medium: 40,
@@ -2525,10 +2979,10 @@ async function timerCommand(idOrSlug, options) {
2525
2979
  return;
2526
2980
  }
2527
2981
  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"));
2982
+ console.log(chalk21.yellow("Please provide a problem ID to start the timer."));
2983
+ console.log(chalk21.gray("Usage: leetcode timer <id>"));
2984
+ console.log(chalk21.gray(" leetcode timer --stats"));
2985
+ console.log(chalk21.gray(" leetcode timer --stop"));
2532
2986
  return;
2533
2987
  }
2534
2988
  const { authorized } = await requireAuth();
@@ -2537,11 +2991,11 @@ async function timerCommand(idOrSlug, options) {
2537
2991
  if (activeTimer) {
2538
2992
  const startedAt = new Date(activeTimer.startedAt);
2539
2993
  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)}`));
2994
+ console.log(chalk21.yellow("\u26A0\uFE0F You have an active timer running:"));
2995
+ console.log(chalk21.white(` Problem: ${activeTimer.title}`));
2996
+ console.log(chalk21.white(` Elapsed: ${formatDuration(elapsed)}`));
2543
2997
  console.log();
2544
- console.log(chalk20.gray("Use `leetcode timer --stop` to stop it first."));
2998
+ console.log(chalk21.gray("Use `leetcode timer --stop` to stop it first."));
2545
2999
  return;
2546
3000
  }
2547
3001
  const spinner = ora14("Fetching problem...").start();
@@ -2565,65 +3019,65 @@ async function timerCommand(idOrSlug, options) {
2565
3019
  durationMinutes
2566
3020
  );
2567
3021
  console.log();
2568
- console.log(chalk20.bold.cyan("\u23F1\uFE0F Interview Mode Started!"));
2569
- console.log(chalk20.gray("\u2500".repeat(50)));
3022
+ console.log(chalk21.bold.cyan("\u23F1\uFE0F Interview Mode Started!"));
3023
+ console.log(chalk21.gray("\u2500".repeat(50)));
2570
3024
  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")}`));
3025
+ console.log(chalk21.white(`Problem: ${problem.questionFrontendId}. ${problem.title}`));
3026
+ console.log(chalk21.white(`Difficulty: ${chalk21.bold(problem.difficulty)}`));
3027
+ console.log(chalk21.white(`Time Limit: ${chalk21.bold.yellow(durationMinutes + " minutes")}`));
2574
3028
  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."));
3029
+ console.log(chalk21.gray("\u2500".repeat(50)));
3030
+ console.log(chalk21.green("\u2713 Timer is running in background"));
3031
+ console.log(chalk21.gray(" When you submit successfully, your time will be recorded."));
3032
+ console.log(chalk21.gray(" Use `leetcode timer --stop` to cancel."));
2579
3033
  console.log();
2580
3034
  await pickCommand(idOrSlug, { open: true });
2581
3035
  } catch (error) {
2582
3036
  spinner.fail("Failed to start timer");
2583
3037
  if (error instanceof Error) {
2584
- console.log(chalk20.red(error.message));
3038
+ console.log(chalk21.red(error.message));
2585
3039
  }
2586
3040
  }
2587
3041
  }
2588
3042
  async function stopActiveTimer() {
2589
3043
  const result = timerStorage.stopTimer();
2590
3044
  if (!result) {
2591
- console.log(chalk20.yellow("No active timer to stop."));
3045
+ console.log(chalk21.yellow("No active timer to stop."));
2592
3046
  return;
2593
3047
  }
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)"));
3048
+ console.log(chalk21.green("\u23F1\uFE0F Timer stopped."));
3049
+ console.log(chalk21.gray(`Elapsed time: ${formatDuration(result.durationSeconds)}`));
3050
+ console.log(chalk21.gray("(Time not recorded since problem was not submitted)"));
2597
3051
  }
2598
3052
  async function showTimerStats(problemId) {
2599
3053
  if (problemId && /^\d+$/.test(problemId)) {
2600
3054
  const times = timerStorage.getSolveTimes(problemId);
2601
3055
  if (times.length === 0) {
2602
- console.log(chalk20.yellow(`No solve times recorded for problem ${problemId}`));
3056
+ console.log(chalk21.yellow(`No solve times recorded for problem ${problemId}`));
2603
3057
  return;
2604
3058
  }
2605
3059
  console.log();
2606
- console.log(chalk20.bold(`\u23F1\uFE0F Solve Times for Problem ${problemId}`));
2607
- console.log(chalk20.gray("\u2500".repeat(40)));
3060
+ console.log(chalk21.bold(`\u23F1\uFE0F Solve Times for Problem ${problemId}`));
3061
+ console.log(chalk21.gray("\u2500".repeat(40)));
2608
3062
  for (const entry of times) {
2609
3063
  const date = new Date(entry.solvedAt).toLocaleDateString();
2610
3064
  const duration = formatDuration(entry.durationSeconds);
2611
3065
  const limit = entry.timerMinutes;
2612
3066
  const withinLimit = entry.durationSeconds <= limit * 60;
2613
3067
  console.log(
2614
- ` ${date} ${withinLimit ? chalk20.green(duration) : chalk20.red(duration)} (limit: ${limit}m)`
3068
+ ` ${date} ${withinLimit ? chalk21.green(duration) : chalk21.red(duration)} (limit: ${limit}m)`
2615
3069
  );
2616
3070
  }
2617
3071
  } else {
2618
3072
  const stats = timerStorage.getStats();
2619
3073
  const allTimes = timerStorage.getAllSolveTimes();
2620
3074
  console.log();
2621
- console.log(chalk20.bold("\u23F1\uFE0F Timer Statistics"));
2622
- console.log(chalk20.gray("\u2500".repeat(40)));
3075
+ console.log(chalk21.bold("\u23F1\uFE0F Timer Statistics"));
3076
+ console.log(chalk21.gray("\u2500".repeat(40)));
2623
3077
  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))}`);
3078
+ console.log(` Problems timed: ${chalk21.cyan(stats.totalProblems)}`);
3079
+ console.log(` Total time: ${chalk21.cyan(formatDuration(stats.totalTime))}`);
3080
+ console.log(` Average time: ${chalk21.cyan(formatDuration(stats.avgTime))}`);
2627
3081
  console.log();
2628
3082
  const recentSolves = [];
2629
3083
  for (const [id, times] of Object.entries(allTimes)) {
@@ -2638,11 +3092,11 @@ async function showTimerStats(problemId) {
2638
3092
  }
2639
3093
  if (recentSolves.length > 0) {
2640
3094
  recentSolves.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
2641
- console.log(chalk20.bold(" Recent Solves:"));
3095
+ console.log(chalk21.bold(" Recent Solves:"));
2642
3096
  for (const solve of recentSolves.slice(0, 5)) {
2643
3097
  const date = new Date(solve.date).toLocaleDateString();
2644
3098
  console.log(
2645
- chalk20.gray(` ${date} `) + chalk20.white(`${solve.problemId}. ${solve.title.substring(0, 25)}`) + chalk20.gray(" ") + chalk20.cyan(formatDuration(solve.duration))
3099
+ chalk21.gray(` ${date} `) + chalk21.white(`${solve.problemId}. ${solve.title.substring(0, 25)}`) + chalk21.gray(" ") + chalk21.cyan(formatDuration(solve.duration))
2646
3100
  );
2647
3101
  }
2648
3102
  }
@@ -2651,7 +3105,7 @@ async function showTimerStats(problemId) {
2651
3105
  }
2652
3106
 
2653
3107
  // src/commands/collab.ts
2654
- import chalk21 from "chalk";
3108
+ import chalk22 from "chalk";
2655
3109
  import ora15 from "ora";
2656
3110
  import { readFile as readFile4 } from "fs/promises";
2657
3111
 
@@ -2662,29 +3116,35 @@ var SUPABASE_ANON_KEY = "sb_publishable_indrKu8VJmASdyLp7w8Hog_OyqT17cV";
2662
3116
  var supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
2663
3117
 
2664
3118
  // 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
3119
+ import { existsSync as existsSync10, readFileSync as readFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
3120
+ import { dirname as dirname2 } from "path";
3121
+ function getCollabPath() {
3122
+ return workspaceStorage.getCollabPath();
3123
+ }
3124
+ function loadCollab() {
3125
+ const path = getCollabPath();
3126
+ if (existsSync10(path)) {
3127
+ return JSON.parse(readFileSync3(path, "utf-8"));
2673
3128
  }
2674
- });
3129
+ return { session: null };
3130
+ }
3131
+ function saveCollab(data) {
3132
+ const collabPath = getCollabPath();
3133
+ const dir = dirname2(collabPath);
3134
+ if (!existsSync10(dir)) {
3135
+ mkdirSync3(dir, { recursive: true });
3136
+ }
3137
+ writeFileSync4(collabPath, JSON.stringify(data, null, 2));
3138
+ }
2675
3139
  var collabStorage = {
2676
3140
  getSession() {
2677
- return collabStore.get("session");
3141
+ return loadCollab().session;
2678
3142
  },
2679
3143
  setSession(session) {
2680
- if (session) {
2681
- collabStore.set("session", session);
2682
- } else {
2683
- collabStore.delete("session");
2684
- }
3144
+ saveCollab({ session });
2685
3145
  },
2686
3146
  getPath() {
2687
- return collabStore.path;
3147
+ return getCollabPath();
2688
3148
  }
2689
3149
  };
2690
3150
 
@@ -2812,27 +3272,27 @@ async function collabHostCommand(problemId) {
2812
3272
  }
2813
3273
  spinner.succeed("Room created!");
2814
3274
  console.log();
2815
- console.log(chalk21.bold.cyan("\u{1F465} Collaborative Coding Session"));
2816
- console.log(chalk21.gray("\u2500".repeat(50)));
3275
+ console.log(chalk22.bold.cyan("\u{1F465} Collaborative Coding Session"));
3276
+ console.log(chalk22.gray("\u2500".repeat(50)));
2817
3277
  console.log();
2818
- console.log(chalk21.white(`Room Code: ${chalk21.bold.green(result.roomCode)}`));
2819
- console.log(chalk21.white(`Problem: ${problemId}`));
3278
+ console.log(chalk22.white(`Room Code: ${chalk22.bold.green(result.roomCode)}`));
3279
+ console.log(chalk22.white(`Problem: ${problemId}`));
2820
3280
  console.log();
2821
- console.log(chalk21.gray("Share this code with your partner:"));
2822
- console.log(chalk21.yellow(` leetcode collab join ${result.roomCode}`));
3281
+ console.log(chalk22.gray("Share this code with your partner:"));
3282
+ console.log(chalk22.yellow(` leetcode collab join ${result.roomCode}`));
2823
3283
  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"));
3284
+ console.log(chalk22.gray("\u2500".repeat(50)));
3285
+ console.log(chalk22.gray("After solving, sync and compare:"));
3286
+ console.log(chalk22.gray(" leetcode collab sync - Upload your solution"));
3287
+ console.log(chalk22.gray(" leetcode collab compare - See both solutions"));
3288
+ console.log(chalk22.gray(" leetcode collab status - Check room status"));
3289
+ console.log(chalk22.gray(" leetcode collab leave - End session"));
2830
3290
  console.log();
2831
3291
  await pickCommand(problemId, { open: true });
2832
3292
  } catch (error) {
2833
3293
  spinner.fail("Failed to create room");
2834
3294
  if (error instanceof Error) {
2835
- console.log(chalk21.red(error.message));
3295
+ console.log(chalk22.red(error.message));
2836
3296
  }
2837
3297
  }
2838
3298
  }
@@ -2848,32 +3308,32 @@ async function collabJoinCommand(roomCode) {
2848
3308
  }
2849
3309
  spinner.succeed("Joined room!");
2850
3310
  console.log();
2851
- console.log(chalk21.bold.cyan("\u{1F465} Collaborative Coding Session"));
2852
- console.log(chalk21.gray("\u2500".repeat(50)));
3311
+ console.log(chalk22.bold.cyan("\u{1F465} Collaborative Coding Session"));
3312
+ console.log(chalk22.gray("\u2500".repeat(50)));
2853
3313
  console.log();
2854
- console.log(chalk21.white(`Room Code: ${chalk21.bold.green(roomCode.toUpperCase())}`));
2855
- console.log(chalk21.white(`Problem: ${result.problemId}`));
3314
+ console.log(chalk22.white(`Room Code: ${chalk22.bold.green(roomCode.toUpperCase())}`));
3315
+ console.log(chalk22.white(`Problem: ${result.problemId}`));
2856
3316
  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"));
3317
+ console.log(chalk22.gray("\u2500".repeat(50)));
3318
+ console.log(chalk22.gray("After solving, sync and compare:"));
3319
+ console.log(chalk22.gray(" leetcode collab sync - Upload your solution"));
3320
+ console.log(chalk22.gray(" leetcode collab compare - See both solutions"));
3321
+ console.log(chalk22.gray(" leetcode collab status - Check room status"));
3322
+ console.log(chalk22.gray(" leetcode collab leave - End session"));
2863
3323
  console.log();
2864
3324
  await pickCommand(result.problemId, { open: true });
2865
3325
  } catch (error) {
2866
3326
  spinner.fail("Failed to join room");
2867
3327
  if (error instanceof Error) {
2868
- console.log(chalk21.red(error.message));
3328
+ console.log(chalk22.red(error.message));
2869
3329
  }
2870
3330
  }
2871
3331
  }
2872
3332
  async function collabSyncCommand() {
2873
3333
  const session = collabService.getSession();
2874
3334
  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."));
3335
+ console.log(chalk22.yellow("No active collaboration session."));
3336
+ console.log(chalk22.gray("Use `leetcode collab host <id>` or `leetcode collab join <code>` first."));
2877
3337
  return;
2878
3338
  }
2879
3339
  const spinner = ora15("Syncing your code...").start();
@@ -2887,7 +3347,7 @@ async function collabSyncCommand() {
2887
3347
  const result = await collabService.syncCode(code);
2888
3348
  if (result.success) {
2889
3349
  spinner.succeed("Code synced successfully!");
2890
- console.log(chalk21.gray(`Uploaded ${code.split("\n").length} lines from ${filePath}`));
3350
+ console.log(chalk22.gray(`Uploaded ${code.split("\n").length} lines from ${filePath}`));
2891
3351
  } else {
2892
3352
  spinner.fail(result.error || "Sync failed");
2893
3353
  }
@@ -2895,8 +3355,8 @@ async function collabSyncCommand() {
2895
3355
  async function collabCompareCommand() {
2896
3356
  const session = collabService.getSession();
2897
3357
  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."));
3358
+ console.log(chalk22.yellow("No active collaboration session."));
3359
+ console.log(chalk22.gray("Use `leetcode collab host <id>` or `leetcode collab join <code>` first."));
2900
3360
  return;
2901
3361
  }
2902
3362
  const spinner = ora15("Fetching solutions...").start();
@@ -2914,224 +3374,785 @@ async function collabCompareCommand() {
2914
3374
  }
2915
3375
  spinner.stop();
2916
3376
  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`."));
3377
+ console.log(chalk22.yellow("Partner has not synced their code yet."));
3378
+ console.log(chalk22.gray("Ask them to run `leetcode collab sync`."));
2919
3379
  return;
2920
3380
  }
2921
3381
  console.log();
2922
- console.log(chalk21.bold.cyan("\u{1F4CA} Solution Comparison"));
2923
- console.log(chalk21.gray("\u2500".repeat(60)));
3382
+ console.log(chalk22.bold.cyan("\u{1F4CA} Solution Comparison"));
3383
+ console.log(chalk22.gray("\u2500".repeat(60)));
2924
3384
  console.log();
2925
- console.log(chalk21.bold.green(`\u25B8 Your Solution (${session.username})`));
2926
- console.log(chalk21.gray("\u2500".repeat(60)));
3385
+ console.log(chalk22.bold.green(`\u25B8 Your Solution (${session.username})`));
3386
+ console.log(chalk22.gray("\u2500".repeat(60)));
2927
3387
  const myLines = myCode.split("\n");
2928
3388
  for (let i = 0; i < myLines.length; i++) {
2929
3389
  const lineNum = String(i + 1).padStart(3, " ");
2930
- console.log(`${chalk21.gray(lineNum)} ${myLines[i]}`);
3390
+ console.log(`${chalk22.gray(lineNum)} ${myLines[i]}`);
2931
3391
  }
2932
3392
  console.log();
2933
- console.log(chalk21.bold.blue(`\u25B8 ${partnerResult.username}'s Solution`));
2934
- console.log(chalk21.gray("\u2500".repeat(60)));
3393
+ console.log(chalk22.bold.blue(`\u25B8 ${partnerResult.username}'s Solution`));
3394
+ console.log(chalk22.gray("\u2500".repeat(60)));
2935
3395
  const partnerLines = partnerResult.code.split("\n");
2936
3396
  for (let i = 0; i < partnerLines.length; i++) {
2937
3397
  const lineNum = String(i + 1).padStart(3, " ");
2938
- console.log(`${chalk21.gray(lineNum)} ${partnerLines[i]}`);
3398
+ console.log(`${chalk22.gray(lineNum)} ${partnerLines[i]}`);
2939
3399
  }
2940
3400
  console.log();
2941
- console.log(chalk21.gray("\u2500".repeat(60)));
2942
- console.log(chalk21.gray(`Your code: ${myLines.length} lines | Partner: ${partnerLines.length} lines`));
3401
+ console.log(chalk22.gray("\u2500".repeat(60)));
3402
+ console.log(chalk22.gray(`Your code: ${myLines.length} lines | Partner: ${partnerLines.length} lines`));
2943
3403
  console.log();
2944
3404
  }
2945
3405
  async function collabLeaveCommand() {
2946
3406
  const session = collabService.getSession();
2947
3407
  if (!session) {
2948
- console.log(chalk21.yellow("No active collaboration session."));
3408
+ console.log(chalk22.yellow("No active collaboration session."));
2949
3409
  return;
2950
3410
  }
2951
3411
  await collabService.leaveRoom();
2952
- console.log(chalk21.green("\u2713 Left the collaboration session."));
3412
+ console.log(chalk22.green("\u2713 Left the collaboration session."));
2953
3413
  }
2954
3414
  async function collabStatusCommand() {
2955
3415
  const session = collabService.getSession();
2956
3416
  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."));
3417
+ console.log(chalk22.yellow("No active collaboration session."));
3418
+ console.log(chalk22.gray("Use `leetcode collab host <id>` or `leetcode collab join <code>` to start."));
2959
3419
  return;
2960
3420
  }
2961
3421
  const status = await collabService.getRoomStatus();
2962
3422
  if ("error" in status) {
2963
- console.log(chalk21.red(status.error));
3423
+ console.log(chalk22.red(status.error));
2964
3424
  return;
2965
3425
  }
2966
3426
  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)}`);
3427
+ console.log(chalk22.bold.cyan("\u{1F465} Collaboration Status"));
3428
+ console.log(chalk22.gray("\u2500".repeat(40)));
3429
+ console.log(` Room: ${chalk22.green(session.roomCode)}`);
2970
3430
  console.log(` Problem: ${session.problemId}`);
2971
3431
  console.log(` Role: ${session.isHost ? "Host" : "Guest"}`);
2972
3432
  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")}`);
3433
+ console.log(chalk22.bold(" Participants:"));
3434
+ console.log(` Host: ${status.host} ${status.hasHostCode ? chalk22.green("\u2713 synced") : chalk22.gray("pending")}`);
3435
+ console.log(` Guest: ${status.guest || chalk22.gray("(waiting...)")} ${status.hasGuestCode ? chalk22.green("\u2713 synced") : chalk22.gray("pending")}`);
2976
3436
  console.log();
2977
3437
  }
2978
3438
 
3439
+ // src/commands/snapshot.ts
3440
+ import chalk23 from "chalk";
3441
+
3442
+ // src/storage/snapshots.ts
3443
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5, unlinkSync } from "fs";
3444
+ import { join as join8 } from "path";
3445
+ function getSnapshotsDir() {
3446
+ return workspaceStorage.getSnapshotsDir();
3447
+ }
3448
+ function getSnapshotDir(problemId) {
3449
+ return join8(getSnapshotsDir(), problemId);
3450
+ }
3451
+ function getMetaPath(problemId) {
3452
+ return join8(getSnapshotDir(problemId), "meta.json");
3453
+ }
3454
+ function getFilesDir(problemId) {
3455
+ return join8(getSnapshotDir(problemId), "files");
3456
+ }
3457
+ function ensureSnapshotDir(problemId) {
3458
+ const dir = getFilesDir(problemId);
3459
+ if (!existsSync11(dir)) {
3460
+ mkdirSync4(dir, { recursive: true });
3461
+ }
3462
+ }
3463
+ function loadMeta(problemId) {
3464
+ const metaPath = getMetaPath(problemId);
3465
+ if (existsSync11(metaPath)) {
3466
+ return JSON.parse(readFileSync4(metaPath, "utf-8"));
3467
+ }
3468
+ return {
3469
+ problemId,
3470
+ problemTitle: "",
3471
+ snapshots: []
3472
+ };
3473
+ }
3474
+ function saveMeta(problemId, meta) {
3475
+ ensureSnapshotDir(problemId);
3476
+ writeFileSync5(getMetaPath(problemId), JSON.stringify(meta, null, 2));
3477
+ }
3478
+ var snapshotStorage = {
3479
+ /**
3480
+ * Save a snapshot of the solution
3481
+ */
3482
+ save(problemId, problemTitle, code, language, name) {
3483
+ ensureSnapshotDir(problemId);
3484
+ const meta = loadMeta(problemId);
3485
+ if (problemTitle) {
3486
+ meta.problemTitle = problemTitle;
3487
+ }
3488
+ const nextId = meta.snapshots.length > 0 ? Math.max(...meta.snapshots.map((s) => s.id)) + 1 : 1;
3489
+ const snapshotName = name || `snapshot-${nextId}`;
3490
+ const existing = meta.snapshots.find((s) => s.name === snapshotName);
3491
+ if (existing) {
3492
+ return { error: `Snapshot with name "${snapshotName}" already exists (ID: ${existing.id})` };
3493
+ }
3494
+ const ext = LANGUAGE_EXTENSIONS[language] || language;
3495
+ const fileName = `${nextId}_${snapshotName}.${ext}`;
3496
+ const filePath = join8(getFilesDir(problemId), fileName);
3497
+ writeFileSync5(filePath, code, "utf-8");
3498
+ const snapshot = {
3499
+ id: nextId,
3500
+ name: snapshotName,
3501
+ fileName,
3502
+ language,
3503
+ lines: code.split("\n").length,
3504
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
3505
+ };
3506
+ meta.snapshots.push(snapshot);
3507
+ saveMeta(problemId, meta);
3508
+ return snapshot;
3509
+ },
3510
+ /**
3511
+ * Get all snapshots for a problem
3512
+ */
3513
+ list(problemId) {
3514
+ const meta = loadMeta(problemId);
3515
+ return meta.snapshots;
3516
+ },
3517
+ /**
3518
+ * Get snapshot metadata
3519
+ */
3520
+ getMeta(problemId) {
3521
+ return loadMeta(problemId);
3522
+ },
3523
+ /**
3524
+ * Get a specific snapshot by ID or name
3525
+ */
3526
+ get(problemId, idOrName) {
3527
+ const meta = loadMeta(problemId);
3528
+ const byId = meta.snapshots.find((s) => s.id === parseInt(idOrName, 10));
3529
+ if (byId) return byId;
3530
+ const byName = meta.snapshots.find((s) => s.name === idOrName);
3531
+ return byName || null;
3532
+ },
3533
+ /**
3534
+ * Get snapshot code content
3535
+ */
3536
+ getCode(problemId, snapshot) {
3537
+ const filePath = join8(getFilesDir(problemId), snapshot.fileName);
3538
+ if (!existsSync11(filePath)) {
3539
+ throw new Error(`Snapshot file not found: ${snapshot.fileName}`);
3540
+ }
3541
+ return readFileSync4(filePath, "utf-8");
3542
+ },
3543
+ /**
3544
+ * Delete a snapshot
3545
+ */
3546
+ delete(problemId, idOrName) {
3547
+ const meta = loadMeta(problemId);
3548
+ const snapshot = this.get(problemId, idOrName);
3549
+ if (!snapshot) {
3550
+ return false;
3551
+ }
3552
+ const filePath = join8(getFilesDir(problemId), snapshot.fileName);
3553
+ if (existsSync11(filePath)) {
3554
+ unlinkSync(filePath);
3555
+ }
3556
+ meta.snapshots = meta.snapshots.filter((s) => s.id !== snapshot.id);
3557
+ saveMeta(problemId, meta);
3558
+ return true;
3559
+ },
3560
+ /**
3561
+ * Check if snapshots exist for a problem
3562
+ */
3563
+ hasSnapshots(problemId) {
3564
+ return this.list(problemId).length > 0;
3565
+ }
3566
+ };
3567
+
3568
+ // src/commands/snapshot.ts
3569
+ import { readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
3570
+ import { extname, basename as basename3 } from "path";
3571
+ import { diffLines } from "diff";
3572
+ function formatTimeAgo(dateStr) {
3573
+ const date = new Date(dateStr);
3574
+ const now = /* @__PURE__ */ new Date();
3575
+ const diffMs = now.getTime() - date.getTime();
3576
+ const diffMins = Math.floor(diffMs / 6e4);
3577
+ const diffHours = Math.floor(diffMins / 60);
3578
+ const diffDays = Math.floor(diffHours / 24);
3579
+ if (diffMins < 1) return "just now";
3580
+ if (diffMins < 60) return `${diffMins}m ago`;
3581
+ if (diffHours < 24) return `${diffHours}h ago`;
3582
+ return `${diffDays}d ago`;
3583
+ }
3584
+ async function snapshotSaveCommand(problemId, name) {
3585
+ const workDir = config.getWorkDir();
3586
+ try {
3587
+ const filePath = await findSolutionFile(workDir, problemId);
3588
+ if (!filePath) {
3589
+ console.log(chalk23.red(`No solution file found for problem ${problemId}`));
3590
+ console.log(chalk23.gray("Run `leetcode pick " + problemId + "` first to create a solution file."));
3591
+ return;
3592
+ }
3593
+ const code = await readFile5(filePath, "utf-8");
3594
+ const ext = extname(filePath).slice(1);
3595
+ const lang = getLangSlugFromExtension(ext) || ext;
3596
+ const fileName = basename3(filePath);
3597
+ const titleMatch = fileName.match(/^\d+\.(.+)\.\w+$/);
3598
+ const title = titleMatch ? titleMatch[1] : "";
3599
+ const result = snapshotStorage.save(problemId, title, code, lang, name);
3600
+ if ("error" in result) {
3601
+ console.log(chalk23.red("\u2717 " + result.error));
3602
+ return;
3603
+ }
3604
+ const snapshot = result;
3605
+ console.log(chalk23.green("\u2713 Snapshot saved!"));
3606
+ console.log();
3607
+ console.log(` ID: ${chalk23.cyan(snapshot.id)}`);
3608
+ console.log(` Name: ${chalk23.white(snapshot.name)}`);
3609
+ console.log(` Lines: ${chalk23.gray(snapshot.lines)}`);
3610
+ console.log(` File: ${chalk23.gray(filePath)}`);
3611
+ } catch (error) {
3612
+ console.log(chalk23.red("Failed to save snapshot"));
3613
+ if (error instanceof Error) {
3614
+ console.log(chalk23.gray(error.message));
3615
+ }
3616
+ }
3617
+ }
3618
+ async function snapshotListCommand(problemId) {
3619
+ const snapshots = snapshotStorage.list(problemId);
3620
+ if (snapshots.length === 0) {
3621
+ console.log(chalk23.yellow(`No snapshots found for problem ${problemId}`));
3622
+ console.log(chalk23.gray("Use `leetcode snapshot save " + problemId + "` to create one."));
3623
+ return;
3624
+ }
3625
+ const meta = snapshotStorage.getMeta(problemId);
3626
+ console.log();
3627
+ console.log(chalk23.bold(`\u{1F4F8} Snapshots for Problem ${problemId}`));
3628
+ if (meta.problemTitle) {
3629
+ console.log(chalk23.gray(` ${meta.problemTitle}`));
3630
+ }
3631
+ console.log(chalk23.gray("\u2500".repeat(50)));
3632
+ console.log();
3633
+ for (const snap of snapshots) {
3634
+ const timeAgo = formatTimeAgo(snap.createdAt);
3635
+ console.log(
3636
+ ` ${chalk23.cyan(snap.id.toString().padStart(2))}. ${chalk23.white(snap.name.padEnd(25))} ${chalk23.gray(snap.lines + " lines")} ${chalk23.gray("\xB7")} ${chalk23.gray(timeAgo)}`
3637
+ );
3638
+ }
3639
+ console.log();
3640
+ console.log(chalk23.gray("Commands:"));
3641
+ console.log(chalk23.gray(` restore: leetcode snapshot restore ${problemId} <id|name>`));
3642
+ console.log(chalk23.gray(` diff: leetcode snapshot diff ${problemId} <id1> <id2>`));
3643
+ console.log(chalk23.gray(` delete: leetcode snapshot delete ${problemId} <id|name>`));
3644
+ }
3645
+ async function snapshotRestoreCommand(problemId, idOrName) {
3646
+ const workDir = config.getWorkDir();
3647
+ try {
3648
+ const snapshot = snapshotStorage.get(problemId, idOrName);
3649
+ if (!snapshot) {
3650
+ console.log(chalk23.red(`Snapshot "${idOrName}" not found for problem ${problemId}`));
3651
+ console.log(chalk23.gray("Run `leetcode snapshot list " + problemId + "` to see available snapshots."));
3652
+ return;
3653
+ }
3654
+ const filePath = await findSolutionFile(workDir, problemId);
3655
+ if (!filePath) {
3656
+ console.log(chalk23.red(`No solution file found for problem ${problemId}`));
3657
+ return;
3658
+ }
3659
+ const currentCode = await readFile5(filePath, "utf-8");
3660
+ const backupName = `backup-before-restore-${Date.now()}`;
3661
+ const ext = extname(filePath).slice(1);
3662
+ const lang = getLangSlugFromExtension(ext) || ext;
3663
+ snapshotStorage.save(problemId, "", currentCode, lang, backupName);
3664
+ const snapshotCode = snapshotStorage.getCode(problemId, snapshot);
3665
+ await writeFile4(filePath, snapshotCode, "utf-8");
3666
+ console.log(chalk23.green("\u2713 Snapshot restored!"));
3667
+ console.log();
3668
+ console.log(` Restored: ${chalk23.cyan(snapshot.name)} (${snapshot.lines} lines)`);
3669
+ console.log(` File: ${chalk23.gray(filePath)}`);
3670
+ console.log(` Backup: ${chalk23.gray(backupName)}`);
3671
+ } catch (error) {
3672
+ console.log(chalk23.red("Failed to restore snapshot"));
3673
+ if (error instanceof Error) {
3674
+ console.log(chalk23.gray(error.message));
3675
+ }
3676
+ }
3677
+ }
3678
+ async function snapshotDiffCommand(problemId, idOrName1, idOrName2) {
3679
+ try {
3680
+ const snap1 = snapshotStorage.get(problemId, idOrName1);
3681
+ const snap2 = snapshotStorage.get(problemId, idOrName2);
3682
+ if (!snap1) {
3683
+ console.log(chalk23.red(`Snapshot "${idOrName1}" not found`));
3684
+ return;
3685
+ }
3686
+ if (!snap2) {
3687
+ console.log(chalk23.red(`Snapshot "${idOrName2}" not found`));
3688
+ return;
3689
+ }
3690
+ const code1 = snapshotStorage.getCode(problemId, snap1);
3691
+ const code2 = snapshotStorage.getCode(problemId, snap2);
3692
+ console.log();
3693
+ console.log(chalk23.bold(`\u{1F4CA} Diff: ${snap1.name} \u2192 ${snap2.name}`));
3694
+ console.log(chalk23.gray("\u2500".repeat(50)));
3695
+ console.log();
3696
+ const diff = diffLines(code1, code2);
3697
+ let added = 0;
3698
+ let removed = 0;
3699
+ for (const part of diff) {
3700
+ const lines = part.value.split("\n").filter((l) => l !== "");
3701
+ if (part.added) {
3702
+ added += lines.length;
3703
+ for (const line of lines) {
3704
+ console.log(chalk23.green("+ " + line));
3705
+ }
3706
+ } else if (part.removed) {
3707
+ removed += lines.length;
3708
+ for (const line of lines) {
3709
+ console.log(chalk23.red("- " + line));
3710
+ }
3711
+ } else {
3712
+ if (lines.length <= 4) {
3713
+ for (const line of lines) {
3714
+ console.log(chalk23.gray(" " + line));
3715
+ }
3716
+ } else {
3717
+ console.log(chalk23.gray(" " + lines[0]));
3718
+ console.log(chalk23.gray(" " + lines[1]));
3719
+ console.log(chalk23.gray(` ... (${lines.length - 4} more lines)`));
3720
+ console.log(chalk23.gray(" " + lines[lines.length - 2]));
3721
+ console.log(chalk23.gray(" " + lines[lines.length - 1]));
3722
+ }
3723
+ }
3724
+ }
3725
+ console.log();
3726
+ console.log(chalk23.gray("\u2500".repeat(50)));
3727
+ console.log(
3728
+ `${chalk23.green("+" + added + " added")} ${chalk23.gray("\xB7")} ${chalk23.red("-" + removed + " removed")} ${chalk23.gray("\xB7")} ${chalk23.gray(snap1.lines + " \u2192 " + snap2.lines + " lines")}`
3729
+ );
3730
+ } catch (error) {
3731
+ console.log(chalk23.red("Failed to diff snapshots"));
3732
+ if (error instanceof Error) {
3733
+ console.log(chalk23.gray(error.message));
3734
+ }
3735
+ }
3736
+ }
3737
+ async function snapshotDeleteCommand(problemId, idOrName) {
3738
+ const snapshot = snapshotStorage.get(problemId, idOrName);
3739
+ if (!snapshot) {
3740
+ console.log(chalk23.red(`Snapshot "${idOrName}" not found for problem ${problemId}`));
3741
+ return;
3742
+ }
3743
+ const deleted = snapshotStorage.delete(problemId, idOrName);
3744
+ if (deleted) {
3745
+ console.log(chalk23.green(`\u2713 Deleted snapshot: ${snapshot.name}`));
3746
+ } else {
3747
+ console.log(chalk23.red("Failed to delete snapshot"));
3748
+ }
3749
+ }
3750
+
3751
+ // src/commands/diff.ts
3752
+ import ora16 from "ora";
3753
+ import chalk24 from "chalk";
3754
+ import { readFile as readFile6 } from "fs/promises";
3755
+ import { existsSync as existsSync12 } from "fs";
3756
+ import { diffLines as diffLines2 } from "diff";
3757
+ function showCodeBlock(code, label) {
3758
+ const lines = code.split("\n");
3759
+ const lineCount = lines.length;
3760
+ console.log();
3761
+ console.log(chalk24.bold.cyan(`=== ${label} (${lineCount} lines) ===`));
3762
+ console.log(chalk24.gray("\u2500".repeat(60)));
3763
+ lines.forEach((line, i) => {
3764
+ const lineNum = (i + 1).toString().padStart(3);
3765
+ console.log(chalk24.gray(lineNum + " \u2502 ") + line);
3766
+ });
3767
+ }
3768
+ function showUnifiedDiff(sourceCode, targetCode, sourceLabel, targetLabel) {
3769
+ const sourceLines = sourceCode.split("\n").length;
3770
+ const targetLines = targetCode.split("\n").length;
3771
+ console.log();
3772
+ console.log(chalk24.bold(`\u{1F4CA} Unified Diff: ${sourceLabel} \u2192 ${targetLabel}`));
3773
+ console.log(chalk24.gray("\u2500".repeat(60)));
3774
+ console.log();
3775
+ const diff = diffLines2(sourceCode, targetCode);
3776
+ let added = 0;
3777
+ let removed = 0;
3778
+ for (const part of diff) {
3779
+ const lines = part.value.split("\n").filter((l) => l !== "");
3780
+ if (part.added) {
3781
+ added += lines.length;
3782
+ for (const line of lines) {
3783
+ console.log(chalk24.green("+ " + line));
3784
+ }
3785
+ } else if (part.removed) {
3786
+ removed += lines.length;
3787
+ for (const line of lines) {
3788
+ console.log(chalk24.red("- " + line));
3789
+ }
3790
+ } else {
3791
+ if (lines.length <= 6) {
3792
+ for (const line of lines) {
3793
+ console.log(chalk24.gray(" " + line));
3794
+ }
3795
+ } else {
3796
+ console.log(chalk24.gray(" " + lines[0]));
3797
+ console.log(chalk24.gray(" " + lines[1]));
3798
+ console.log(chalk24.dim(` ... (${lines.length - 4} unchanged lines)`));
3799
+ console.log(chalk24.gray(" " + lines[lines.length - 2]));
3800
+ console.log(chalk24.gray(" " + lines[lines.length - 1]));
3801
+ }
3802
+ }
3803
+ }
3804
+ console.log();
3805
+ console.log(chalk24.gray("\u2500".repeat(60)));
3806
+ console.log(
3807
+ `${chalk24.green("+" + added + " added")} ${chalk24.gray("\xB7")} ${chalk24.red("-" + removed + " removed")} ${chalk24.gray("\xB7")} ${chalk24.gray(sourceLines + " \u2192 " + targetLines + " lines")}`
3808
+ );
3809
+ }
3810
+ function showComparison(sourceCode, targetCode, sourceLabel, targetLabel, unified) {
3811
+ if (unified) {
3812
+ showUnifiedDiff(sourceCode, targetCode, sourceLabel, targetLabel);
3813
+ } else {
3814
+ showCodeBlock(sourceCode, sourceLabel);
3815
+ showCodeBlock(targetCode, targetLabel);
3816
+ const diff = diffLines2(sourceCode, targetCode);
3817
+ let added = 0;
3818
+ let removed = 0;
3819
+ for (const part of diff) {
3820
+ const lines = part.value.split("\n").filter((l) => l !== "");
3821
+ if (part.added) added += lines.length;
3822
+ else if (part.removed) removed += lines.length;
3823
+ }
3824
+ console.log();
3825
+ console.log(chalk24.gray("\u2500".repeat(60)));
3826
+ console.log(
3827
+ `${chalk24.bold("Summary:")} ${chalk24.green("+" + added + " added")} ${chalk24.gray("\xB7")} ${chalk24.red("-" + removed + " removed")}`
3828
+ );
3829
+ console.log(chalk24.gray("Tip: Use --unified for line-by-line diff"));
3830
+ }
3831
+ }
3832
+ async function diffCommand(problemId, options) {
3833
+ const { authorized } = await requireAuth();
3834
+ if (!authorized) return;
3835
+ const workDir = config.getWorkDir();
3836
+ const spinner = ora16("Finding solution file...").start();
3837
+ try {
3838
+ const filePath = await findSolutionFile(workDir, problemId);
3839
+ if (!filePath) {
3840
+ spinner.fail(`No solution file found for problem ${problemId}`);
3841
+ console.log(chalk24.gray("Run `leetcode pick " + problemId + "` first to create a solution file."));
3842
+ return;
3843
+ }
3844
+ const currentCode = await readFile6(filePath, "utf-8");
3845
+ spinner.text = "Fetching comparison target...";
3846
+ if (options.file) {
3847
+ spinner.stop();
3848
+ if (!existsSync12(options.file)) {
3849
+ console.log(chalk24.red(`File not found: ${options.file}`));
3850
+ return;
3851
+ }
3852
+ if (!isPathInsideWorkDir(options.file, workDir)) {
3853
+ console.log(chalk24.red("\u26A0\uFE0F Security Error: File path is outside the configured workspace"));
3854
+ console.log(chalk24.gray(`File: ${options.file}`));
3855
+ console.log(chalk24.gray(`Workspace: ${workDir}`));
3856
+ console.log(chalk24.yellow("\nFor security reasons, you can only diff files from within your workspace."));
3857
+ return;
3858
+ }
3859
+ const otherCode = await readFile6(options.file, "utf-8");
3860
+ showComparison(currentCode, otherCode, "Your Solution", options.file, options.unified ?? false);
3861
+ return;
3862
+ }
3863
+ const problem = await leetcodeClient.getProblemById(problemId);
3864
+ if (!problem) {
3865
+ spinner.fail(`Problem ${problemId} not found`);
3866
+ return;
3867
+ }
3868
+ if (options.submission) {
3869
+ const submissionId = parseInt(options.submission, 10);
3870
+ const submission = await leetcodeClient.getSubmissionDetails(submissionId);
3871
+ spinner.stop();
3872
+ showComparison(currentCode, submission.code, "Your Solution", `Submission #${submissionId}`, options.unified ?? false);
3873
+ return;
3874
+ }
3875
+ const submissions = await leetcodeClient.getSubmissionList(problem.titleSlug, 50);
3876
+ const accepted = submissions.find((s) => s.statusDisplay === "Accepted");
3877
+ if (!accepted) {
3878
+ spinner.fail("No accepted submissions found for this problem");
3879
+ console.log(chalk24.gray("Tip: Use --file to compare with a local file instead"));
3880
+ return;
3881
+ }
3882
+ const acceptedDetails = await leetcodeClient.getSubmissionDetails(parseInt(accepted.id, 10));
3883
+ spinner.stop();
3884
+ showComparison(currentCode, acceptedDetails.code, "Your Solution", "Last Accepted", options.unified ?? false);
3885
+ } catch (error) {
3886
+ spinner.fail("Failed to diff");
3887
+ if (error instanceof Error) {
3888
+ console.log(chalk24.red(error.message));
3889
+ }
3890
+ }
3891
+ }
3892
+
3893
+ // src/commands/workspace.ts
3894
+ import chalk25 from "chalk";
3895
+ import inquirer4 from "inquirer";
3896
+ import { homedir as homedir3 } from "os";
3897
+ import { join as join9 } from "path";
3898
+ async function workspaceCurrentCommand() {
3899
+ const active = workspaceStorage.getActive();
3900
+ const config2 = workspaceStorage.getConfig(active);
3901
+ console.log();
3902
+ console.log(chalk25.bold.cyan(`\u{1F4C1} Active Workspace: ${active}`));
3903
+ console.log(chalk25.gray("\u2500".repeat(40)));
3904
+ console.log(` workDir: ${chalk25.white(config2.workDir)}`);
3905
+ console.log(` lang: ${chalk25.white(config2.lang)}`);
3906
+ if (config2.editor) console.log(` editor: ${chalk25.white(config2.editor)}`);
3907
+ if (config2.syncRepo) console.log(` syncRepo: ${chalk25.white(config2.syncRepo)}`);
3908
+ console.log();
3909
+ }
3910
+ async function workspaceListCommand() {
3911
+ const workspaces = workspaceStorage.list();
3912
+ const active = workspaceStorage.getActive();
3913
+ console.log();
3914
+ console.log(chalk25.bold("Workspaces:"));
3915
+ console.log();
3916
+ for (const ws of workspaces) {
3917
+ const config2 = workspaceStorage.getConfig(ws);
3918
+ const marker = ws === active ? chalk25.green("\u25B8 ") : " ";
3919
+ const name = ws === active ? chalk25.green.bold(ws) : ws;
3920
+ console.log(`${marker}${name}`);
3921
+ console.log(` ${chalk25.gray(config2.workDir)}`);
3922
+ }
3923
+ console.log();
3924
+ }
3925
+ async function workspaceCreateCommand(name, options) {
3926
+ if (workspaceStorage.exists(name)) {
3927
+ console.log(chalk25.red(`Workspace "${name}" already exists`));
3928
+ return;
3929
+ }
3930
+ const workDir = options.workdir ?? join9(homedir3(), "leetcode", name);
3931
+ const config2 = {
3932
+ workDir,
3933
+ lang: "typescript"
3934
+ };
3935
+ const success = workspaceStorage.create(name, config2);
3936
+ if (success) {
3937
+ console.log(chalk25.green(`\u2713 Created workspace "${name}"`));
3938
+ console.log(` workDir: ${chalk25.gray(workDir)}`);
3939
+ console.log();
3940
+ console.log(chalk25.gray(`Switch to it: leetcode workspace use ${name}`));
3941
+ } else {
3942
+ console.log(chalk25.red("Failed to create workspace"));
3943
+ }
3944
+ }
3945
+ async function workspaceUseCommand(name) {
3946
+ if (!workspaceStorage.exists(name)) {
3947
+ console.log(chalk25.red(`Workspace "${name}" not found`));
3948
+ console.log(chalk25.gray("Use `leetcode workspace list` to see available workspaces"));
3949
+ return;
3950
+ }
3951
+ const success = workspaceStorage.setActive(name);
3952
+ if (success) {
3953
+ const config2 = workspaceStorage.getConfig(name);
3954
+ console.log(chalk25.green(`\u2713 Switched to workspace "${name}"`));
3955
+ console.log(` workDir: ${chalk25.gray(config2.workDir)}`);
3956
+ } else {
3957
+ console.log(chalk25.red("Failed to switch workspace"));
3958
+ }
3959
+ }
3960
+ async function workspaceDeleteCommand(name) {
3961
+ if (name === "default") {
3962
+ console.log(chalk25.red("Cannot delete the default workspace"));
3963
+ return;
3964
+ }
3965
+ if (!workspaceStorage.exists(name)) {
3966
+ console.log(chalk25.red(`Workspace "${name}" not found`));
3967
+ return;
3968
+ }
3969
+ const { confirmed } = await inquirer4.prompt([{
3970
+ type: "confirm",
3971
+ name: "confirmed",
3972
+ message: `Delete workspace "${name}"? (files in workDir will NOT be deleted)`,
3973
+ default: false
3974
+ }]);
3975
+ if (!confirmed) {
3976
+ console.log(chalk25.gray("Cancelled"));
3977
+ return;
3978
+ }
3979
+ const success = workspaceStorage.delete(name);
3980
+ if (success) {
3981
+ console.log(chalk25.green(`\u2713 Deleted workspace "${name}"`));
3982
+ } else {
3983
+ console.log(chalk25.red("Failed to delete workspace"));
3984
+ }
3985
+ }
3986
+
2979
3987
  // src/index.ts
2980
3988
  var program = new Command();
2981
3989
  program.configureHelp({
2982
3990
  sortSubcommands: true,
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)
3991
+ subcommandTerm: (cmd) => {
3992
+ const name = cmd.name();
3993
+ const alias = cmd.alias();
3994
+ const term = alias ? `${name}|${alias}` : name;
3995
+ return chalk26.cyan(term.padEnd(16));
3996
+ },
3997
+ subcommandDescription: (cmd) => chalk26.white(cmd.description()),
3998
+ optionTerm: (option) => chalk26.yellow(option.flags),
3999
+ optionDescription: (option) => chalk26.white(option.description)
2987
4000
  });
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
4001
+ program.name("leetcode").usage("[command] [options]").description(chalk26.bold.cyan("\u{1F525} A modern LeetCode CLI built with TypeScript")).version("2.0.1", "-v, --version", "Output the version number").helpOption("-h, --help", "Display help for command").addHelpText("after", `
4002
+ ${chalk26.yellow("Examples:")}
4003
+ ${chalk26.cyan("$ leetcode login")} Login to LeetCode
4004
+ ${chalk26.cyan("$ leetcode list -d easy")} List easy problems
4005
+ ${chalk26.cyan("$ leetcode random -d medium")} Get random medium problem
4006
+ ${chalk26.cyan("$ leetcode pick 1")} Start solving "Two Sum"
4007
+ ${chalk26.cyan("$ leetcode test 1")} Test your solution
4008
+ ${chalk26.cyan("$ leetcode submit 1")} Submit your solution
2996
4009
  `);
2997
4010
  program.command("login").description("Login to LeetCode with browser cookies").addHelpText("after", `
2998
- ${chalk22.yellow("How to login:")}
2999
- 1. Open ${chalk22.cyan("https://leetcode.com")} in your browser
4011
+ ${chalk26.yellow("How to login:")}
4012
+ 1. Open ${chalk26.cyan("https://leetcode.com")} in your browser
3000
4013
  2. Login to your account
3001
4014
  3. Open Developer Tools (F12) \u2192 Application \u2192 Cookies
3002
- 4. Copy values of ${chalk22.green("LEETCODE_SESSION")} and ${chalk22.green("csrftoken")}
4015
+ 4. Copy values of ${chalk26.green("LEETCODE_SESSION")} and ${chalk26.green("csrftoken")}
3003
4016
  5. Paste when prompted by this command
3004
4017
  `).action(loginCommand);
3005
4018
  program.command("logout").description("Clear stored credentials").action(logoutCommand);
3006
4019
  program.command("whoami").description("Check current login status").action(whoamiCommand);
3007
4020
  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", `
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
4021
+ ${chalk26.yellow("Examples:")}
4022
+ ${chalk26.cyan("$ leetcode list")} List first 20 problems
4023
+ ${chalk26.cyan("$ leetcode list -d easy")} List easy problems only
4024
+ ${chalk26.cyan("$ leetcode list -s solved")} List your solved problems
4025
+ ${chalk26.cyan("$ leetcode list -t array -t string")} Filter by multiple tags
4026
+ ${chalk26.cyan('$ leetcode list -q "two sum"')} Search by keywords
4027
+ ${chalk26.cyan("$ leetcode list -n 50 -p 2")} Show 50 problems, page 2
3015
4028
  `).action(listCommand);
3016
4029
  program.command("show <id>").alias("s").description("Show problem description").addHelpText("after", `
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
4030
+ ${chalk26.yellow("Examples:")}
4031
+ ${chalk26.cyan("$ leetcode show 1")} Show by problem ID
4032
+ ${chalk26.cyan("$ leetcode show two-sum")} Show by problem slug
4033
+ ${chalk26.cyan("$ leetcode s 412")} Short alias
3021
4034
  `).action(showCommand);
3022
4035
  program.command("daily").alias("d").description("Show today's daily challenge").addHelpText("after", `
3023
- ${chalk22.yellow("Examples:")}
3024
- ${chalk22.cyan("$ leetcode daily")} Show today's challenge
3025
- ${chalk22.cyan("$ leetcode d")} Short alias
4036
+ ${chalk26.yellow("Examples:")}
4037
+ ${chalk26.cyan("$ leetcode daily")} Show today's challenge
4038
+ ${chalk26.cyan("$ leetcode d")} Short alias
3026
4039
  `).action(dailyCommand);
3027
4040
  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", `
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
4041
+ ${chalk26.yellow("Examples:")}
4042
+ ${chalk26.cyan("$ leetcode random")} Get any random problem
4043
+ ${chalk26.cyan("$ leetcode random -d medium")} Random medium problem
4044
+ ${chalk26.cyan("$ leetcode random -t array")} Random array problem
4045
+ ${chalk26.cyan("$ leetcode random --pick")} Random + create file
4046
+ ${chalk26.cyan("$ leetcode r -d easy --pick")} Random easy + file
3034
4047
  `).action(randomCommand);
3035
4048
  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", `
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/")}
4049
+ ${chalk26.yellow("Examples:")}
4050
+ ${chalk26.cyan("$ leetcode pick 1")} Pick by problem ID
4051
+ ${chalk26.cyan("$ leetcode pick two-sum")} Pick by problem slug
4052
+ ${chalk26.cyan("$ leetcode pick 1 -l python3")} Pick with specific language
4053
+ ${chalk26.cyan("$ leetcode pick 1 --no-open")} Create file without opening
4054
+ ${chalk26.cyan("$ leetcode p 412")} Short alias
4055
+
4056
+ ${chalk26.gray("Files are organized by: workDir/Difficulty/Category/")}
3044
4057
  `).action(async (id, options) => {
3045
4058
  await pickCommand(id, options);
3046
4059
  });
3047
4060
  program.command("pick-batch <ids...>").description("Generate solution files for multiple problems").option("-l, --lang <language>", "Programming language for the solutions").addHelpText("after", `
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
4061
+ ${chalk26.yellow("Examples:")}
4062
+ ${chalk26.cyan("$ leetcode pick-batch 1 2 3")} Pick problems 1, 2, and 3
4063
+ ${chalk26.cyan("$ leetcode pick-batch 1 2 3 -l py")} Pick with Python
3051
4064
  `).action(batchPickCommand);
3052
- program.command("test <file>").alias("t").description("Test solution against sample test cases").option("-c, --testcase <testcase>", "Custom test case").addHelpText("after", `
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.")}
4065
+ program.command("test <file>").alias("t").description("Test solution against sample test cases").option("-c, --testcase <testcase>", "Custom test case").option("-V, --visualize", "Visual output for data structures (arrays, trees, etc.)").addHelpText("after", `
4066
+ ${chalk26.yellow("Examples:")}
4067
+ ${chalk26.cyan("$ leetcode test 1")} Test by problem ID
4068
+ ${chalk26.cyan("$ leetcode test two-sum")} Test by problem slug
4069
+ ${chalk26.cyan("$ leetcode test ./path/to/file.py")} Test by file path
4070
+ ${chalk26.cyan('$ leetcode test 1 -c "[1,2]\\n3"')} Test with custom case
4071
+ ${chalk26.cyan("$ leetcode test 1 --visualize")} Visual mode for debugging
4072
+ ${chalk26.cyan("$ leetcode t 412")} Short alias
4073
+
4074
+ ${chalk26.gray("Testcases use \\n to separate multiple inputs.")}
3061
4075
  `).action(testCommand);
3062
4076
  program.command("submit <file>").alias("x").description("Submit solution to LeetCode").addHelpText("after", `
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
4077
+ ${chalk26.yellow("Examples:")}
4078
+ ${chalk26.cyan("$ leetcode submit 1")} Submit by problem ID
4079
+ ${chalk26.cyan("$ leetcode submit two-sum")} Submit by problem slug
4080
+ ${chalk26.cyan("$ leetcode submit ./path/to/file.py")} Submit by file path
4081
+ ${chalk26.cyan("$ leetcode x 412")} Short alias
3068
4082
  `).action(submitCommand);
4083
+ program.command("diff <id>").description("Compare solution with past submissions").option("-s, --submission <id>", "Compare with specific submission ID").option("-f, --file <path>", "Compare with a local file").option("-u, --unified", "Show unified diff (line-by-line changes)").addHelpText("after", `
4084
+ ${chalk26.yellow("Examples:")}
4085
+ ${chalk26.cyan("$ leetcode diff 1")} Compare with last accepted
4086
+ ${chalk26.cyan("$ leetcode diff 1 -u")} Show unified diff
4087
+ ${chalk26.cyan("$ leetcode diff 1 -s 12345")} Compare with specific submission
4088
+ ${chalk26.cyan("$ leetcode diff 1 -f other.py")} Compare with local file
4089
+ `).action(diffCommand);
3069
4090
  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", `
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
4091
+ ${chalk26.yellow("Examples:")}
4092
+ ${chalk26.cyan("$ leetcode submissions 1")} View submissions for problem
4093
+ ${chalk26.cyan("$ leetcode submissions 1 -n 5")} Show last 5 submissions
4094
+ ${chalk26.cyan("$ leetcode submissions 1 --last")} Show last accepted submission
4095
+ ${chalk26.cyan("$ leetcode submissions 1 --download")} Download last accepted code
3075
4096
  `).action(submissionsCommand);
3076
4097
  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", `
3077
- ${chalk22.yellow("Options Explained:")}
3078
- ${chalk22.cyan("-c, --calendar")} Shows a table of your weekly submissions and active days
4098
+ ${chalk26.yellow("Options Explained:")}
4099
+ ${chalk26.cyan("-c, --calendar")} Shows a table of your weekly submissions and active days
3079
4100
  for the past 12 weeks. Useful for tracking consistency.
3080
4101
 
3081
- ${chalk22.cyan("-s, --skills")} Shows how many problems you solved per topic tag,
4102
+ ${chalk26.cyan("-s, --skills")} Shows how many problems you solved per topic tag,
3082
4103
  grouped by difficulty (Fundamental/Intermediate/Advanced).
3083
4104
  Helps identify your strong and weak areas.
3084
4105
 
3085
- ${chalk22.cyan("-t, --trend")} Shows a bar chart of daily submissions for the past week.
4106
+ ${chalk26.cyan("-t, --trend")} Shows a bar chart of daily submissions for the past week.
3086
4107
  Visualizes your recent coding activity day by day.
3087
4108
 
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
4109
+ ${chalk26.yellow("Examples:")}
4110
+ ${chalk26.cyan("$ leetcode stat")} Show basic stats (solved count, rank)
4111
+ ${chalk26.cyan("$ leetcode stat lee215")} Show another user's stats
4112
+ ${chalk26.cyan("$ leetcode stat -c")} Weekly activity table
4113
+ ${chalk26.cyan("$ leetcode stat -s")} Topic-wise breakdown
4114
+ ${chalk26.cyan("$ leetcode stat -t")} 7-day trend chart
3094
4115
  `).action((username, options) => statCommand(username, options));
3095
4116
  program.command("today").description("Show today's progress summary").addHelpText("after", `
3096
- ${chalk22.yellow("Examples:")}
3097
- ${chalk22.cyan("$ leetcode today")} Show streak, solved, and daily challenge
4117
+ ${chalk26.yellow("Examples:")}
4118
+ ${chalk26.cyan("$ leetcode today")} Show streak, solved, and daily challenge
3098
4119
  `).action(todayCommand);
3099
4120
  program.command("bookmark <action> [id]").description("Manage problem bookmarks").addHelpText("after", `
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
4121
+ ${chalk26.yellow("Actions:")}
4122
+ ${chalk26.cyan("add <id>")} Bookmark a problem
4123
+ ${chalk26.cyan("remove <id>")} Remove a bookmark
4124
+ ${chalk26.cyan("list")} List all bookmarks
4125
+ ${chalk26.cyan("clear")} Clear all bookmarks
4126
+
4127
+ ${chalk26.yellow("Examples:")}
4128
+ ${chalk26.cyan("$ leetcode bookmark add 1")} Bookmark problem 1
4129
+ ${chalk26.cyan("$ leetcode bookmark remove 1")} Remove bookmark
4130
+ ${chalk26.cyan("$ leetcode bookmark list")} List all bookmarks
3110
4131
  `).action(bookmarkCommand);
3111
4132
  program.command("note <id> [action]").description("View or edit notes for a problem").addHelpText("after", `
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
4133
+ ${chalk26.yellow("Actions:")}
4134
+ ${chalk26.cyan("edit")} Open notes in editor (default)
4135
+ ${chalk26.cyan("view")} Display notes in terminal
4136
+
4137
+ ${chalk26.yellow("Examples:")}
4138
+ ${chalk26.cyan("$ leetcode note 1")} Edit notes for problem 1
4139
+ ${chalk26.cyan("$ leetcode note 1 edit")} Edit notes (explicit)
4140
+ ${chalk26.cyan("$ leetcode note 1 view")} View notes in terminal
3120
4141
  `).action(notesCommand);
3121
4142
  program.command("sync").description("Sync solutions to Git repository").addHelpText("after", `
3122
- ${chalk22.yellow("Examples:")}
3123
- ${chalk22.cyan("$ leetcode sync")} Sync all solutions to remote
4143
+ ${chalk26.yellow("Examples:")}
4144
+ ${chalk26.cyan("$ leetcode sync")} Sync all solutions to remote
3124
4145
  `).action(syncCommand);
3125
4146
  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", `
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")}
4147
+ ${chalk26.yellow("Examples:")}
4148
+ ${chalk26.cyan("$ leetcode config")} View current config
4149
+ ${chalk26.cyan("$ leetcode config -l python3")} Set language to Python
4150
+ ${chalk26.cyan('$ leetcode config -e "code"')} Set editor to VS Code
4151
+ ${chalk26.cyan("$ leetcode config -w ~/leetcode")} Set solutions folder
4152
+ ${chalk26.cyan("$ leetcode config -r https://...")} Set git repository
4153
+ ${chalk26.cyan("$ leetcode config -i")} Interactive setup
4154
+
4155
+ ${chalk26.gray("Supported languages: typescript, javascript, python3, java, cpp, c, csharp, go, rust, kotlin, swift")}
3135
4156
  `).action(async (options) => {
3136
4157
  if (options.interactive) {
3137
4158
  await configInteractiveCommand();
@@ -3140,31 +4161,37 @@ ${chalk22.gray("Supported languages: typescript, javascript, python3, java, cpp,
3140
4161
  }
3141
4162
  });
3142
4163
  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:")}
4164
+ ${chalk26.yellow("How it works:")}
3144
4165
  Start a problem with a countdown timer to simulate interview conditions.
3145
4166
  Default time limits: Easy (20 min), Medium (40 min), Hard (60 min).
3146
4167
  Your solve times are recorded when you submit successfully.
3147
4168
 
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
4169
+ ${chalk26.yellow("Examples:")}
4170
+ ${chalk26.cyan("$ leetcode timer 1")} Start problem 1 with default time
4171
+ ${chalk26.cyan("$ leetcode timer 1 -m 30")} Start with 30 minute limit
4172
+ ${chalk26.cyan("$ leetcode timer --stats")} Show your solve time statistics
4173
+ ${chalk26.cyan("$ leetcode timer --stop")} Stop active timer
3153
4174
  `).action((id, options) => timerCommand(id, options));
4175
+ var workspaceCmd = program.command("workspace").description("Manage workspaces for different contexts");
4176
+ workspaceCmd.command("current").description("Show current workspace").action(workspaceCurrentCommand);
4177
+ workspaceCmd.command("list").description("List all workspaces").action(workspaceListCommand);
4178
+ workspaceCmd.command("create <name>").description("Create a new workspace").option("-w, --workdir <path>", "Set working directory for this workspace").action(workspaceCreateCommand);
4179
+ workspaceCmd.command("use <name>").description("Switch to a workspace").action(workspaceUseCommand);
4180
+ workspaceCmd.command("delete <name>").description("Delete a workspace").action(workspaceDeleteCommand);
3154
4181
  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
4182
+ ${chalk26.yellow("Subcommands:")}
4183
+ ${chalk26.cyan("host <id>")} Create a room and get a code to share
4184
+ ${chalk26.cyan("join <code>")} Join a room with the shared code
4185
+ ${chalk26.cyan("sync")} Upload your solution to the room
4186
+ ${chalk26.cyan("compare")} View both solutions side by side
4187
+ ${chalk26.cyan("status")} Check room and sync status
4188
+ ${chalk26.cyan("leave")} End the collaboration session
4189
+
4190
+ ${chalk26.yellow("Examples:")}
4191
+ ${chalk26.gray("$ leetcode collab host 1")} Start a session for Two Sum
4192
+ ${chalk26.gray("$ leetcode collab join ABC123")} Join your partner's session
4193
+ ${chalk26.gray("$ leetcode collab sync")} Upload your code after solving
4194
+ ${chalk26.gray("$ leetcode collab compare")} Compare solutions
3168
4195
  `);
3169
4196
  collabCmd.command("host <problemId>").description("Host a collaboration session").action(collabHostCommand);
3170
4197
  collabCmd.command("join <roomCode>").description("Join a collaboration session").action(collabJoinCommand);
@@ -3172,12 +4199,31 @@ collabCmd.command("sync").description("Sync your code with partner").action(coll
3172
4199
  collabCmd.command("compare").description("Compare your solution with partner").action(collabCompareCommand);
3173
4200
  collabCmd.command("leave").description("Leave the collaboration session").action(collabLeaveCommand);
3174
4201
  collabCmd.command("status").description("Show collaboration status").action(collabStatusCommand);
4202
+ var snapshotCmd = program.command("snapshot").description("Save and restore solution versions").addHelpText("after", `
4203
+ ${chalk26.yellow("Subcommands:")}
4204
+ ${chalk26.cyan("save <id> [name]")} Save current solution as a snapshot
4205
+ ${chalk26.cyan("list <id>")} List all snapshots for a problem
4206
+ ${chalk26.cyan("restore <id> <snapshot>")} Restore a snapshot
4207
+ ${chalk26.cyan("diff <id> <s1> <s2>")} Compare two snapshots
4208
+ ${chalk26.cyan("delete <id> <snapshot>")} Delete a snapshot
4209
+
4210
+ ${chalk26.yellow("Examples:")}
4211
+ ${chalk26.gray('$ leetcode snapshot save 1 "brute-force"')} Save current solution
4212
+ ${chalk26.gray("$ leetcode snapshot list 1")} List snapshots
4213
+ ${chalk26.gray("$ leetcode snapshot restore 1 2")} Restore snapshot #2
4214
+ ${chalk26.gray("$ leetcode snapshot diff 1 1 2")} Compare snapshots
4215
+ `);
4216
+ snapshotCmd.command("save <id> [name]").description("Save current solution as a snapshot").action(snapshotSaveCommand);
4217
+ snapshotCmd.command("list <id>").description("List all snapshots for a problem").action(snapshotListCommand);
4218
+ snapshotCmd.command("restore <id> <snapshot>").description("Restore a snapshot").action(snapshotRestoreCommand);
4219
+ snapshotCmd.command("diff <id> <snap1> <snap2>").description("Compare two snapshots").action(snapshotDiffCommand);
4220
+ snapshotCmd.command("delete <id> <snapshot>").description("Delete a snapshot").action(snapshotDeleteCommand);
3175
4221
  program.showHelpAfterError("(add --help for additional information)");
3176
4222
  program.parse();
3177
4223
  if (!process.argv.slice(2).length) {
3178
4224
  console.log();
3179
- console.log(chalk22.bold.cyan(" \u{1F525} LeetCode CLI"));
3180
- console.log(chalk22.gray(" A modern command-line interface for LeetCode"));
4225
+ console.log(chalk26.bold.cyan(" \u{1F525} LeetCode CLI"));
4226
+ console.log(chalk26.gray(" A modern command-line interface for LeetCode"));
3181
4227
  console.log();
3182
4228
  program.outputHelp();
3183
4229
  }