@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.
- package/README.md +86 -0
- package/dist/index.js +1617 -571
- 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
|
|
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((
|
|
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
|
|
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
|
|
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
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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(
|
|
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(
|
|
844
|
+
console.log(chalk4.bold.cyan(` ${problem.questionFrontendId}. ${titlePrefix}${problem.title}`));
|
|
634
845
|
console.log(` ${colorDifficulty(problem.difficulty)}`);
|
|
635
|
-
console.log(
|
|
846
|
+
console.log(chalk4.gray(` https://leetcode.com/problems/${problem.titleSlug}/`));
|
|
636
847
|
console.log();
|
|
637
848
|
if (problem.isPaidOnly) {
|
|
638
|
-
console.log(
|
|
639
|
-
console.log(
|
|
640
|
-
console.log(
|
|
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) =>
|
|
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(
|
|
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(
|
|
653
|
-
console.log(
|
|
654
|
-
console.log(
|
|
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(/ /g, " ").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, '"').replace(/'/g, "'").replace(/≤/g, "\u2264").replace(/≥/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) =>
|
|
675
|
-
content = content.replace(/§INPUT§/g,
|
|
676
|
-
content = content.replace(/§OUTPUT§/g,
|
|
677
|
-
content = content.replace(/§EXPLAIN§/g,
|
|
678
|
-
content = content.replace(/§CONSTRAINTS§/g,
|
|
679
|
-
content = content.replace(/§FOLLOWUP§/g,
|
|
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(
|
|
687
|
-
console.log(
|
|
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(
|
|
692
|
-
console.log(
|
|
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(
|
|
907
|
+
console.log(chalk4.green.bold("\u2713 All test cases passed!"));
|
|
697
908
|
} else {
|
|
698
|
-
console.log(
|
|
909
|
+
console.log(chalk4.yellow.bold("\u2717 Some test cases failed"));
|
|
699
910
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
console.log(
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
-
|
|
948
|
+
const stdoutEntries = (result.std_output_list ?? []).filter((s) => s);
|
|
949
|
+
if (stdoutEntries.length > 0) {
|
|
711
950
|
console.log();
|
|
712
|
-
console.log(
|
|
713
|
-
for (const output of
|
|
714
|
-
|
|
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(
|
|
722
|
-
console.log(
|
|
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(
|
|
727
|
-
console.log(
|
|
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(
|
|
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(
|
|
973
|
+
console.log(chalk4.green.bold("\u2713 Accepted!"));
|
|
735
974
|
console.log();
|
|
736
975
|
console.log(
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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(
|
|
986
|
+
console.log(chalk4.red.bold(`\u274C ${result.status_msg}`));
|
|
748
987
|
console.log();
|
|
749
|
-
console.log(
|
|
988
|
+
console.log(chalk4.gray(`Passed ${result.total_correct}/${result.total_testcases} testcases`));
|
|
750
989
|
if (result.code_output) {
|
|
751
|
-
console.log(
|
|
990
|
+
console.log(chalk4.gray("Your Output:"), result.code_output);
|
|
752
991
|
}
|
|
753
992
|
if (result.expected_output) {
|
|
754
|
-
console.log(
|
|
993
|
+
console.log(chalk4.gray("Expected:"), result.expected_output);
|
|
755
994
|
}
|
|
756
995
|
if (result.last_testcase) {
|
|
757
|
-
console.log(
|
|
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(
|
|
764
|
-
console.log(
|
|
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: [
|
|
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([
|
|
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(
|
|
783
|
-
console.log(
|
|
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(
|
|
1026
|
+
console.log(chalk4.bold.yellow("\u{1F3AF} Daily Challenge"), chalk4.gray(`(${date})`));
|
|
788
1027
|
console.log();
|
|
789
|
-
console.log(
|
|
1028
|
+
console.log(chalk4.white(`${problem.questionFrontendId}. ${problem.title}`));
|
|
790
1029
|
console.log(colorDifficulty(problem.difficulty));
|
|
791
|
-
console.log(
|
|
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) =>
|
|
795
|
-
console.log(
|
|
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
|
|
1040
|
+
return chalk4.green(difficulty);
|
|
802
1041
|
case "medium":
|
|
803
|
-
return
|
|
1042
|
+
return chalk4.yellow(difficulty);
|
|
804
1043
|
case "hard":
|
|
805
|
-
return
|
|
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
|
|
1052
|
+
return chalk4.green("\u2713");
|
|
814
1053
|
case "notac":
|
|
815
|
-
return
|
|
1054
|
+
return chalk4.yellow("\u25CB");
|
|
816
1055
|
default:
|
|
817
|
-
return
|
|
1056
|
+
return chalk4.gray("-");
|
|
818
1057
|
}
|
|
819
1058
|
}
|
|
820
1059
|
function displaySubmissionsList(submissions) {
|
|
821
1060
|
const table = new Table({
|
|
822
1061
|
head: [
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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 ?
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
1174
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1175
|
+
import { join as join4 } from "path";
|
|
937
1176
|
import ora4 from "ora";
|
|
938
|
-
import
|
|
1177
|
+
import chalk8 from "chalk";
|
|
939
1178
|
|
|
940
1179
|
// src/storage/config.ts
|
|
941
|
-
import
|
|
942
|
-
|
|
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
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
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:
|
|
956
|
-
editor:
|
|
957
|
-
workDir:
|
|
958
|
-
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
|
-
|
|
1354
|
+
workspaceStorage.setConfig({ lang: language });
|
|
963
1355
|
},
|
|
964
1356
|
setEditor(editor) {
|
|
965
|
-
|
|
1357
|
+
workspaceStorage.setConfig({ editor });
|
|
966
1358
|
},
|
|
967
1359
|
setWorkDir(workDir) {
|
|
968
|
-
|
|
1360
|
+
workspaceStorage.setConfig({ workDir });
|
|
969
1361
|
},
|
|
970
1362
|
setRepo(repo) {
|
|
971
|
-
|
|
1363
|
+
workspaceStorage.setConfig({ syncRepo: repo });
|
|
972
1364
|
},
|
|
973
1365
|
deleteRepo() {
|
|
974
|
-
|
|
1366
|
+
const wsConfig = workspaceStorage.getConfig();
|
|
1367
|
+
delete wsConfig.syncRepo;
|
|
1368
|
+
workspaceStorage.setConfig(wsConfig);
|
|
975
1369
|
},
|
|
976
1370
|
getLanguage() {
|
|
977
|
-
return
|
|
1371
|
+
return workspaceStorage.getConfig().lang;
|
|
978
1372
|
},
|
|
979
1373
|
getEditor() {
|
|
980
|
-
return
|
|
1374
|
+
return workspaceStorage.getConfig().editor;
|
|
981
1375
|
},
|
|
982
1376
|
getWorkDir() {
|
|
983
|
-
return
|
|
1377
|
+
return workspaceStorage.getConfig().workDir;
|
|
984
1378
|
},
|
|
985
1379
|
getRepo() {
|
|
986
|
-
return
|
|
1380
|
+
return workspaceStorage.getConfig().syncRepo;
|
|
987
1381
|
},
|
|
988
1382
|
getPath() {
|
|
989
|
-
return
|
|
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
|
|
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(
|
|
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(
|
|
1164
|
-
console.log(
|
|
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(
|
|
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 =
|
|
1187
|
-
if (!
|
|
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 =
|
|
1192
|
-
if (
|
|
1589
|
+
const filePath = join4(targetDir, fileName);
|
|
1590
|
+
if (existsSync2(filePath)) {
|
|
1193
1591
|
spinner.warn(`File already exists: ${fileName}`);
|
|
1194
|
-
console.log(
|
|
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 ${
|
|
1202
|
-
console.log(
|
|
1599
|
+
spinner.succeed(`Created ${chalk8.green(fileName)}`);
|
|
1600
|
+
console.log(chalk8.gray(`Path: ${filePath}`));
|
|
1203
1601
|
console.log();
|
|
1204
|
-
console.log(
|
|
1205
|
-
console.log(
|
|
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(
|
|
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(
|
|
1617
|
+
console.log(chalk8.red("API Response Validation Failed"));
|
|
1220
1618
|
} else {
|
|
1221
|
-
console.log(
|
|
1619
|
+
console.log(chalk8.red(error.message));
|
|
1222
1620
|
}
|
|
1223
1621
|
} catch {
|
|
1224
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
1650
|
+
console.log(chalk8.gray("\u2500".repeat(50)));
|
|
1253
1651
|
console.log(
|
|
1254
|
-
|
|
1255
|
-
`Done! ${
|
|
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
|
|
1660
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1263
1661
|
import { basename } from "path";
|
|
1264
1662
|
import ora5 from "ora";
|
|
1265
|
-
import
|
|
1663
|
+
import chalk9 from "chalk";
|
|
1266
1664
|
|
|
1267
1665
|
// src/utils/fileUtils.ts
|
|
1268
1666
|
import { readdir } from "fs/promises";
|
|
1269
|
-
import { existsSync as
|
|
1270
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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(
|
|
1354
|
-
console.log(
|
|
1355
|
-
console.log(
|
|
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(
|
|
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(
|
|
1365
|
-
console.log(
|
|
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(
|
|
1773
|
+
console.log(chalk9.gray(`Found: ${filePath}`));
|
|
1370
1774
|
}
|
|
1371
|
-
if (!
|
|
1372
|
-
console.log(
|
|
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(
|
|
1382
|
-
console.log(
|
|
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(
|
|
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
|
|
1822
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1411
1823
|
import { basename as basename2 } from "path";
|
|
1412
1824
|
import ora6 from "ora";
|
|
1413
|
-
import
|
|
1825
|
+
import chalk10 from "chalk";
|
|
1414
1826
|
|
|
1415
1827
|
// src/storage/timer.ts
|
|
1416
|
-
import
|
|
1417
|
-
import {
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
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
|
-
|
|
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
|
|
1861
|
+
return loadTimer().activeTimer;
|
|
1439
1862
|
},
|
|
1440
1863
|
stopTimer() {
|
|
1441
|
-
const
|
|
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
|
-
|
|
1870
|
+
data.activeTimer = null;
|
|
1871
|
+
saveTimer(data);
|
|
1447
1872
|
return { durationSeconds };
|
|
1448
1873
|
},
|
|
1449
1874
|
recordSolveTime(problemId, title, difficulty, durationSeconds, timerMinutes) {
|
|
1450
|
-
const
|
|
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
|
-
|
|
1887
|
+
saveTimer(data);
|
|
1463
1888
|
},
|
|
1464
1889
|
getSolveTimes(problemId) {
|
|
1465
|
-
const
|
|
1466
|
-
return solveTimes[problemId] ?? [];
|
|
1890
|
+
const data = loadTimer();
|
|
1891
|
+
return data.solveTimes[problemId] ?? [];
|
|
1467
1892
|
},
|
|
1468
1893
|
getAllSolveTimes() {
|
|
1469
|
-
return
|
|
1894
|
+
return loadTimer().solveTimes ?? {};
|
|
1470
1895
|
},
|
|
1471
1896
|
getStats() {
|
|
1472
|
-
const 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(
|
|
1499
|
-
console.log(
|
|
1500
|
-
console.log(
|
|
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(
|
|
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(
|
|
1510
|
-
console.log(
|
|
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(
|
|
1938
|
+
console.log(chalk10.gray(`Found: ${filePath}`));
|
|
1515
1939
|
}
|
|
1516
|
-
if (!
|
|
1517
|
-
console.log(
|
|
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(
|
|
1527
|
-
console.log(
|
|
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(
|
|
1998
|
+
console.log(chalk10.bold("\u23F1\uFE0F Timer Result:"));
|
|
1567
1999
|
console.log(
|
|
1568
|
-
` Solved in ${withinLimit ?
|
|
2000
|
+
` Solved in ${withinLimit ? chalk10.green(timeStr) : chalk10.yellow(timeStr)} (limit: ${activeTimer.durationMinutes}m)`
|
|
1569
2001
|
);
|
|
1570
2002
|
if (withinLimit) {
|
|
1571
|
-
console.log(
|
|
2003
|
+
console.log(chalk10.green(" \u2713 Within time limit!"));
|
|
1572
2004
|
} else {
|
|
1573
|
-
console.log(
|
|
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(
|
|
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
|
|
2020
|
+
import chalk12 from "chalk";
|
|
1589
2021
|
|
|
1590
2022
|
// src/utils/stats-display.ts
|
|
1591
|
-
import
|
|
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(
|
|
1625
|
-
console.log(
|
|
1626
|
-
console.log(
|
|
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 ?
|
|
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(` ${
|
|
2065
|
+
console.log(` ${chalk11.white(weekLabel)} ${bar} ${chalk11.cyan(countStr)} ${chalk11.yellow(daysStr)}`);
|
|
1634
2066
|
}
|
|
1635
|
-
console.log(
|
|
1636
|
-
console.log(` ${
|
|
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(
|
|
1642
|
-
console.log(
|
|
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(` ${
|
|
2083
|
+
console.log(` ${chalk11.white(name)} ${bar} ${chalk11.white(stat.problemsSolved)}`);
|
|
1652
2084
|
}
|
|
1653
2085
|
};
|
|
1654
|
-
renderSection("Fundamental", fundamental,
|
|
1655
|
-
renderSection("Intermediate", intermediate,
|
|
1656
|
-
renderSection("Advanced", advanced,
|
|
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(
|
|
1678
|
-
console.log(
|
|
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 +=
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
1766
|
-
console.log(
|
|
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(
|
|
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
|
|
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(
|
|
1812
|
-
console.log(
|
|
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(
|
|
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
|
|
2256
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1825
2257
|
import { join as join6 } from "path";
|
|
1826
2258
|
import ora10 from "ora";
|
|
1827
|
-
import
|
|
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(
|
|
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(
|
|
2287
|
+
console.log(chalk15.bold("Last Accepted Submission:"));
|
|
1856
2288
|
displaySubmissionsList([lastAC]);
|
|
1857
2289
|
} else {
|
|
1858
|
-
console.log(
|
|
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 (!
|
|
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 ${
|
|
1890
|
-
console.log(
|
|
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(
|
|
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
|
|
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(
|
|
1925
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
2375
|
+
console.log(chalk16.green("\u2713 Repository URL cleared"));
|
|
1944
2376
|
} else {
|
|
1945
2377
|
config.setRepo(options.repo);
|
|
1946
|
-
console.log(
|
|
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(
|
|
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(
|
|
1996
|
-
console.log(
|
|
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(
|
|
2435
|
+
console.log(chalk16.gray("Config file:"), config.getPath());
|
|
1999
2436
|
console.log();
|
|
2000
|
-
console.log(
|
|
2001
|
-
console.log(
|
|
2002
|
-
console.log(
|
|
2003
|
-
console.log(
|
|
2004
|
-
console.log(
|
|
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
|
|
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
|
|
2014
|
-
var bookmarksStore = new
|
|
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(
|
|
2054
|
-
console.log(
|
|
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(
|
|
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(
|
|
2065
|
-
console.log(
|
|
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(
|
|
2506
|
+
console.log(chalk17.green(`\u2713 Bookmarked problem ${id}`));
|
|
2070
2507
|
} else {
|
|
2071
|
-
console.log(
|
|
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(
|
|
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(
|
|
2517
|
+
console.log(chalk17.green(`\u2713 Removed bookmark for problem ${id}`));
|
|
2081
2518
|
} else {
|
|
2082
|
-
console.log(
|
|
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(
|
|
2528
|
+
console.log(chalk17.yellow("No bookmarks to clear"));
|
|
2092
2529
|
} else {
|
|
2093
2530
|
bookmarks.clear();
|
|
2094
|
-
console.log(
|
|
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(
|
|
2103
|
-
console.log(
|
|
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(
|
|
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
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
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" ?
|
|
2568
|
+
problem.status === "ac" ? chalk17.green("\u2713") : chalk17.gray("-")
|
|
2132
2569
|
]);
|
|
2133
2570
|
} else {
|
|
2134
|
-
table.push([id,
|
|
2571
|
+
table.push([id, chalk17.gray("(not found)"), "-", "-"]);
|
|
2135
2572
|
}
|
|
2136
2573
|
} catch {
|
|
2137
|
-
table.push([id,
|
|
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(
|
|
2581
|
+
console.log(chalk17.gray("IDs: ") + bookmarkList.join(", "));
|
|
2145
2582
|
}
|
|
2146
2583
|
} else {
|
|
2147
|
-
console.log(
|
|
2584
|
+
console.log(chalk17.gray("IDs: ") + bookmarkList.join(", "));
|
|
2148
2585
|
console.log();
|
|
2149
|
-
console.log(
|
|
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
|
|
2592
|
+
return chalk17.green(difficulty);
|
|
2156
2593
|
case "medium":
|
|
2157
|
-
return
|
|
2594
|
+
return chalk17.yellow(difficulty);
|
|
2158
2595
|
case "hard":
|
|
2159
|
-
return
|
|
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
|
|
2169
|
-
import
|
|
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(
|
|
2173
|
-
console.log(
|
|
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 (!
|
|
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 (!
|
|
2190
|
-
console.log(
|
|
2191
|
-
console.log(
|
|
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(
|
|
2198
|
-
console.log(
|
|
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(
|
|
2639
|
+
console.log(chalk18.red("Failed to read notes"));
|
|
2203
2640
|
if (error instanceof Error) {
|
|
2204
|
-
console.log(
|
|
2641
|
+
console.log(chalk18.gray(error.message));
|
|
2205
2642
|
}
|
|
2206
2643
|
}
|
|
2207
2644
|
}
|
|
2208
2645
|
async function editNote(notePath, problemId) {
|
|
2209
|
-
if (!
|
|
2646
|
+
if (!existsSync8(notePath)) {
|
|
2210
2647
|
const template = await generateNoteTemplate(problemId);
|
|
2211
2648
|
await writeFile3(notePath, template, "utf-8");
|
|
2212
|
-
console.log(
|
|
2649
|
+
console.log(chalk18.green(`\u2713 Created notes file for problem ${problemId}`));
|
|
2213
2650
|
}
|
|
2214
|
-
console.log(
|
|
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
|
|
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(
|
|
2287
|
-
console.log(
|
|
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(
|
|
2290
|
-
console.log(
|
|
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(
|
|
2297
|
-
console.log(` ${
|
|
2298
|
-
console.log(` ${
|
|
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(
|
|
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(
|
|
2742
|
+
console.log(chalk19.green(" \u2713 Completed!"));
|
|
2306
2743
|
} else if (status === "notac") {
|
|
2307
|
-
console.log(
|
|
2744
|
+
console.log(chalk19.yellow(" \u25CB Attempted"));
|
|
2308
2745
|
} else {
|
|
2309
|
-
console.log(
|
|
2746
|
+
console.log(chalk19.gray(" - Not started"));
|
|
2310
2747
|
}
|
|
2311
2748
|
console.log();
|
|
2312
|
-
console.log(
|
|
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(
|
|
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
|
|
2760
|
+
return chalk19.green(difficulty);
|
|
2324
2761
|
case "medium":
|
|
2325
|
-
return
|
|
2762
|
+
return chalk19.yellow(difficulty);
|
|
2326
2763
|
case "hard":
|
|
2327
|
-
return
|
|
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
|
|
2336
|
-
import
|
|
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(
|
|
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
|
|
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(
|
|
2869
|
+
console.log(chalk20.red(error));
|
|
2421
2870
|
}
|
|
2422
2871
|
}
|
|
2423
2872
|
}
|
|
2424
2873
|
if (!repoUrl) {
|
|
2425
|
-
console.log(
|
|
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(
|
|
2898
|
+
console.log(chalk20.green("\u2713 Added remote origin"));
|
|
2445
2899
|
} catch (e) {
|
|
2446
|
-
console.log(
|
|
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 (!
|
|
2454
|
-
console.log(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
2529
|
-
console.log(
|
|
2530
|
-
console.log(
|
|
2531
|
-
console.log(
|
|
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(
|
|
2541
|
-
console.log(
|
|
2542
|
-
console.log(
|
|
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(
|
|
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(
|
|
2569
|
-
console.log(
|
|
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(
|
|
2572
|
-
console.log(
|
|
2573
|
-
console.log(
|
|
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(
|
|
2576
|
-
console.log(
|
|
2577
|
-
console.log(
|
|
2578
|
-
console.log(
|
|
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(
|
|
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(
|
|
3045
|
+
console.log(chalk21.yellow("No active timer to stop."));
|
|
2592
3046
|
return;
|
|
2593
3047
|
}
|
|
2594
|
-
console.log(
|
|
2595
|
-
console.log(
|
|
2596
|
-
console.log(
|
|
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(
|
|
3056
|
+
console.log(chalk21.yellow(`No solve times recorded for problem ${problemId}`));
|
|
2603
3057
|
return;
|
|
2604
3058
|
}
|
|
2605
3059
|
console.log();
|
|
2606
|
-
console.log(
|
|
2607
|
-
console.log(
|
|
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 ?
|
|
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(
|
|
2622
|
-
console.log(
|
|
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: ${
|
|
2625
|
-
console.log(` Total time: ${
|
|
2626
|
-
console.log(` Average time: ${
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
2666
|
-
import {
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
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
|
|
3141
|
+
return loadCollab().session;
|
|
2678
3142
|
},
|
|
2679
3143
|
setSession(session) {
|
|
2680
|
-
|
|
2681
|
-
collabStore.set("session", session);
|
|
2682
|
-
} else {
|
|
2683
|
-
collabStore.delete("session");
|
|
2684
|
-
}
|
|
3144
|
+
saveCollab({ session });
|
|
2685
3145
|
},
|
|
2686
3146
|
getPath() {
|
|
2687
|
-
return
|
|
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(
|
|
2816
|
-
console.log(
|
|
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(
|
|
2819
|
-
console.log(
|
|
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(
|
|
2822
|
-
console.log(
|
|
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(
|
|
2825
|
-
console.log(
|
|
2826
|
-
console.log(
|
|
2827
|
-
console.log(
|
|
2828
|
-
console.log(
|
|
2829
|
-
console.log(
|
|
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(
|
|
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(
|
|
2852
|
-
console.log(
|
|
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(
|
|
2855
|
-
console.log(
|
|
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(
|
|
2858
|
-
console.log(
|
|
2859
|
-
console.log(
|
|
2860
|
-
console.log(
|
|
2861
|
-
console.log(
|
|
2862
|
-
console.log(
|
|
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(
|
|
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(
|
|
2876
|
-
console.log(
|
|
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(
|
|
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(
|
|
2899
|
-
console.log(
|
|
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(
|
|
2918
|
-
console.log(
|
|
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(
|
|
2923
|
-
console.log(
|
|
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(
|
|
2926
|
-
console.log(
|
|
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(`${
|
|
3390
|
+
console.log(`${chalk22.gray(lineNum)} ${myLines[i]}`);
|
|
2931
3391
|
}
|
|
2932
3392
|
console.log();
|
|
2933
|
-
console.log(
|
|
2934
|
-
console.log(
|
|
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(`${
|
|
3398
|
+
console.log(`${chalk22.gray(lineNum)} ${partnerLines[i]}`);
|
|
2939
3399
|
}
|
|
2940
3400
|
console.log();
|
|
2941
|
-
console.log(
|
|
2942
|
-
console.log(
|
|
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(
|
|
3408
|
+
console.log(chalk22.yellow("No active collaboration session."));
|
|
2949
3409
|
return;
|
|
2950
3410
|
}
|
|
2951
3411
|
await collabService.leaveRoom();
|
|
2952
|
-
console.log(
|
|
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(
|
|
2958
|
-
console.log(
|
|
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(
|
|
3423
|
+
console.log(chalk22.red(status.error));
|
|
2964
3424
|
return;
|
|
2965
3425
|
}
|
|
2966
3426
|
console.log();
|
|
2967
|
-
console.log(
|
|
2968
|
-
console.log(
|
|
2969
|
-
console.log(` Room: ${
|
|
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(
|
|
2974
|
-
console.log(` Host: ${status.host} ${status.hasHostCode ?
|
|
2975
|
-
console.log(` Guest: ${status.guest ||
|
|
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) =>
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
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(
|
|
2989
|
-
${
|
|
2990
|
-
${
|
|
2991
|
-
${
|
|
2992
|
-
${
|
|
2993
|
-
${
|
|
2994
|
-
${
|
|
2995
|
-
${
|
|
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
|
-
${
|
|
2999
|
-
1. Open ${
|
|
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 ${
|
|
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
|
-
${
|
|
3009
|
-
${
|
|
3010
|
-
${
|
|
3011
|
-
${
|
|
3012
|
-
${
|
|
3013
|
-
${
|
|
3014
|
-
${
|
|
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
|
-
${
|
|
3018
|
-
${
|
|
3019
|
-
${
|
|
3020
|
-
${
|
|
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
|
-
${
|
|
3024
|
-
${
|
|
3025
|
-
${
|
|
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
|
-
${
|
|
3029
|
-
${
|
|
3030
|
-
${
|
|
3031
|
-
${
|
|
3032
|
-
${
|
|
3033
|
-
${
|
|
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
|
-
${
|
|
3037
|
-
${
|
|
3038
|
-
${
|
|
3039
|
-
${
|
|
3040
|
-
${
|
|
3041
|
-
${
|
|
3042
|
-
|
|
3043
|
-
${
|
|
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
|
-
${
|
|
3049
|
-
${
|
|
3050
|
-
${
|
|
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
|
-
${
|
|
3054
|
-
${
|
|
3055
|
-
${
|
|
3056
|
-
${
|
|
3057
|
-
${
|
|
3058
|
-
${
|
|
3059
|
-
|
|
3060
|
-
|
|
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
|
-
${
|
|
3064
|
-
${
|
|
3065
|
-
${
|
|
3066
|
-
${
|
|
3067
|
-
${
|
|
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
|
-
${
|
|
3071
|
-
${
|
|
3072
|
-
${
|
|
3073
|
-
${
|
|
3074
|
-
${
|
|
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
|
-
${
|
|
3078
|
-
${
|
|
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
|
-
${
|
|
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
|
-
${
|
|
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
|
-
${
|
|
3089
|
-
${
|
|
3090
|
-
${
|
|
3091
|
-
${
|
|
3092
|
-
${
|
|
3093
|
-
${
|
|
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
|
-
${
|
|
3097
|
-
${
|
|
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
|
-
${
|
|
3101
|
-
${
|
|
3102
|
-
${
|
|
3103
|
-
${
|
|
3104
|
-
${
|
|
3105
|
-
|
|
3106
|
-
${
|
|
3107
|
-
${
|
|
3108
|
-
${
|
|
3109
|
-
${
|
|
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
|
-
${
|
|
3113
|
-
${
|
|
3114
|
-
${
|
|
3115
|
-
|
|
3116
|
-
${
|
|
3117
|
-
${
|
|
3118
|
-
${
|
|
3119
|
-
${
|
|
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
|
-
${
|
|
3123
|
-
${
|
|
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
|
-
${
|
|
3127
|
-
${
|
|
3128
|
-
${
|
|
3129
|
-
${
|
|
3130
|
-
${
|
|
3131
|
-
${
|
|
3132
|
-
${
|
|
3133
|
-
|
|
3134
|
-
${
|
|
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
|
-
${
|
|
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
|
-
${
|
|
3149
|
-
${
|
|
3150
|
-
${
|
|
3151
|
-
${
|
|
3152
|
-
${
|
|
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
|
-
${
|
|
3156
|
-
${
|
|
3157
|
-
${
|
|
3158
|
-
${
|
|
3159
|
-
${
|
|
3160
|
-
${
|
|
3161
|
-
${
|
|
3162
|
-
|
|
3163
|
-
${
|
|
3164
|
-
${
|
|
3165
|
-
${
|
|
3166
|
-
${
|
|
3167
|
-
${
|
|
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(
|
|
3180
|
-
console.log(
|
|
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
|
}
|