@night-slayer18/leetcode-cli 1.5.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -0
- package/dist/index.js +1899 -555
- package/package.json +5 -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";
|
|
@@ -308,12 +308,12 @@ var LeetCodeClient = class {
|
|
|
308
308
|
retry: { limit: 2 }
|
|
309
309
|
});
|
|
310
310
|
}
|
|
311
|
-
setCredentials(
|
|
312
|
-
this.credentials =
|
|
311
|
+
setCredentials(credentials2) {
|
|
312
|
+
this.credentials = credentials2;
|
|
313
313
|
this.client = this.client.extend({
|
|
314
314
|
headers: {
|
|
315
|
-
"Cookie": `LEETCODE_SESSION=${
|
|
316
|
-
"X-CSRFToken":
|
|
315
|
+
"Cookie": `LEETCODE_SESSION=${credentials2.session}; csrftoken=${credentials2.csrfToken}`,
|
|
316
|
+
"X-CSRFToken": credentials2.csrfToken
|
|
317
317
|
}
|
|
318
318
|
});
|
|
319
319
|
}
|
|
@@ -445,14 +445,14 @@ var LeetCodeClient = class {
|
|
|
445
445
|
}).json();
|
|
446
446
|
return this.pollSubmission(response.submission_id.toString(), "submission", SubmissionResultSchema);
|
|
447
447
|
}
|
|
448
|
-
async pollSubmission(id, _type,
|
|
448
|
+
async pollSubmission(id, _type, schema) {
|
|
449
449
|
const endpoint = `submissions/detail/${id}/check/`;
|
|
450
450
|
const maxAttempts = 30;
|
|
451
451
|
const delay = 1e3;
|
|
452
452
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
453
453
|
const result = await this.client.get(endpoint).json();
|
|
454
454
|
if (result.state === "SUCCESS" || result.state === "FAILURE") {
|
|
455
|
-
return
|
|
455
|
+
return schema.parse(result);
|
|
456
456
|
}
|
|
457
457
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
458
458
|
}
|
|
@@ -461,88 +461,31 @@ var LeetCodeClient = class {
|
|
|
461
461
|
};
|
|
462
462
|
var leetcodeClient = new LeetCodeClient();
|
|
463
463
|
|
|
464
|
-
// src/storage/
|
|
464
|
+
// src/storage/credentials.ts
|
|
465
465
|
import Conf from "conf";
|
|
466
466
|
import { homedir } from "os";
|
|
467
467
|
import { join } from "path";
|
|
468
|
-
var
|
|
469
|
-
|
|
470
|
-
type: "object",
|
|
471
|
-
nullable: true,
|
|
472
|
-
properties: {
|
|
473
|
-
csrfToken: { type: "string" },
|
|
474
|
-
session: { type: "string" }
|
|
475
|
-
}
|
|
476
|
-
},
|
|
477
|
-
config: {
|
|
478
|
-
type: "object",
|
|
479
|
-
properties: {
|
|
480
|
-
language: { type: "string", default: "typescript" },
|
|
481
|
-
editor: { type: "string" },
|
|
482
|
-
workDir: { type: "string", default: join(homedir(), "leetcode") },
|
|
483
|
-
repo: { type: "string" }
|
|
484
|
-
},
|
|
485
|
-
default: {
|
|
486
|
-
language: "typescript",
|
|
487
|
-
workDir: join(homedir(), "leetcode")
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
};
|
|
491
|
-
var configStore = new Conf({
|
|
492
|
-
projectName: "leetcode-cli",
|
|
468
|
+
var credentialsStore = new Conf({
|
|
469
|
+
configName: "credentials",
|
|
493
470
|
cwd: join(homedir(), ".leetcode"),
|
|
494
|
-
|
|
471
|
+
defaults: {}
|
|
495
472
|
});
|
|
496
|
-
var
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
configStore.set("credentials", credentials);
|
|
503
|
-
},
|
|
504
|
-
clearCredentials() {
|
|
505
|
-
configStore.delete("credentials");
|
|
506
|
-
},
|
|
507
|
-
// User Config
|
|
508
|
-
getConfig() {
|
|
509
|
-
return configStore.get("config");
|
|
510
|
-
},
|
|
511
|
-
setLanguage(language) {
|
|
512
|
-
configStore.set("config.language", language);
|
|
473
|
+
var credentials = {
|
|
474
|
+
get() {
|
|
475
|
+
const session = credentialsStore.get("session");
|
|
476
|
+
const csrfToken = credentialsStore.get("csrfToken");
|
|
477
|
+
if (!session || !csrfToken) return null;
|
|
478
|
+
return { session, csrfToken };
|
|
513
479
|
},
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
setWorkDir(workDir) {
|
|
518
|
-
configStore.set("config.workDir", workDir);
|
|
519
|
-
},
|
|
520
|
-
setRepo(repo) {
|
|
521
|
-
configStore.set("config.repo", repo);
|
|
522
|
-
},
|
|
523
|
-
deleteRepo() {
|
|
524
|
-
configStore.delete("config.repo");
|
|
525
|
-
},
|
|
526
|
-
// Get specific config values
|
|
527
|
-
getLanguage() {
|
|
528
|
-
return configStore.get("config.language");
|
|
529
|
-
},
|
|
530
|
-
getEditor() {
|
|
531
|
-
return configStore.get("config.editor");
|
|
532
|
-
},
|
|
533
|
-
getWorkDir() {
|
|
534
|
-
return configStore.get("config.workDir");
|
|
535
|
-
},
|
|
536
|
-
getRepo() {
|
|
537
|
-
return configStore.get("config.repo");
|
|
480
|
+
set(creds) {
|
|
481
|
+
credentialsStore.set("session", creds.session);
|
|
482
|
+
credentialsStore.set("csrfToken", creds.csrfToken);
|
|
538
483
|
},
|
|
539
|
-
// Clear all config
|
|
540
484
|
clear() {
|
|
541
|
-
|
|
485
|
+
credentialsStore.clear();
|
|
542
486
|
},
|
|
543
|
-
// Get config file path (for debugging)
|
|
544
487
|
getPath() {
|
|
545
|
-
return
|
|
488
|
+
return credentialsStore.path;
|
|
546
489
|
}
|
|
547
490
|
};
|
|
548
491
|
|
|
@@ -574,23 +517,23 @@ async function loginCommand() {
|
|
|
574
517
|
validate: (input) => input.length > 0 || "CSRF token is required"
|
|
575
518
|
}
|
|
576
519
|
]);
|
|
577
|
-
const
|
|
520
|
+
const creds = {
|
|
578
521
|
session: answers.session.trim(),
|
|
579
522
|
csrfToken: answers.csrfToken.trim()
|
|
580
523
|
};
|
|
581
524
|
const spinner = ora("Verifying credentials...").start();
|
|
582
525
|
try {
|
|
583
|
-
leetcodeClient.setCredentials(
|
|
526
|
+
leetcodeClient.setCredentials(creds);
|
|
584
527
|
const { isSignedIn, username } = await leetcodeClient.checkAuth();
|
|
585
528
|
if (!isSignedIn || !username) {
|
|
586
529
|
spinner.fail("Invalid credentials");
|
|
587
530
|
console.log(chalk.red("Please check your session cookies and try again."));
|
|
588
531
|
return;
|
|
589
532
|
}
|
|
590
|
-
|
|
533
|
+
credentials.set(creds);
|
|
591
534
|
spinner.succeed(`Logged in as ${chalk.green(username)}`);
|
|
592
535
|
console.log();
|
|
593
|
-
console.log(chalk.gray(`Credentials saved to ${
|
|
536
|
+
console.log(chalk.gray(`Credentials saved to ${credentials.getPath()}`));
|
|
594
537
|
} catch (error) {
|
|
595
538
|
spinner.fail("Authentication failed");
|
|
596
539
|
if (error instanceof Error) {
|
|
@@ -599,18 +542,18 @@ async function loginCommand() {
|
|
|
599
542
|
}
|
|
600
543
|
}
|
|
601
544
|
async function logoutCommand() {
|
|
602
|
-
|
|
545
|
+
credentials.clear();
|
|
603
546
|
console.log(chalk.green("\u2713 Logged out successfully"));
|
|
604
547
|
}
|
|
605
548
|
async function whoamiCommand() {
|
|
606
|
-
const
|
|
607
|
-
if (!
|
|
549
|
+
const creds = credentials.get();
|
|
550
|
+
if (!creds) {
|
|
608
551
|
console.log(chalk.yellow('Not logged in. Run "leetcode login" to authenticate.'));
|
|
609
552
|
return;
|
|
610
553
|
}
|
|
611
554
|
const spinner = ora("Checking session...").start();
|
|
612
555
|
try {
|
|
613
|
-
leetcodeClient.setCredentials(
|
|
556
|
+
leetcodeClient.setCredentials(creds);
|
|
614
557
|
const { isSignedIn, username } = await leetcodeClient.checkAuth();
|
|
615
558
|
if (!isSignedIn || !username) {
|
|
616
559
|
spinner.fail("Session expired");
|
|
@@ -628,18 +571,18 @@ async function whoamiCommand() {
|
|
|
628
571
|
|
|
629
572
|
// src/commands/list.ts
|
|
630
573
|
import ora2 from "ora";
|
|
631
|
-
import
|
|
574
|
+
import chalk5 from "chalk";
|
|
632
575
|
|
|
633
576
|
// src/utils/auth.ts
|
|
634
577
|
import chalk2 from "chalk";
|
|
635
578
|
async function requireAuth() {
|
|
636
|
-
const
|
|
637
|
-
if (!
|
|
579
|
+
const creds = credentials.get();
|
|
580
|
+
if (!creds) {
|
|
638
581
|
console.log(chalk2.yellow("\u26A0\uFE0F Please login first: leetcode login"));
|
|
639
582
|
return { authorized: false };
|
|
640
583
|
}
|
|
641
584
|
try {
|
|
642
|
-
leetcodeClient.setCredentials(
|
|
585
|
+
leetcodeClient.setCredentials(creds);
|
|
643
586
|
const { isSignedIn, username } = await leetcodeClient.checkAuth();
|
|
644
587
|
if (!isSignedIn) {
|
|
645
588
|
console.log(chalk2.yellow("\u26A0\uFE0F Session expired. Please run: leetcode login"));
|
|
@@ -653,16 +596,227 @@ async function requireAuth() {
|
|
|
653
596
|
}
|
|
654
597
|
|
|
655
598
|
// src/utils/display.ts
|
|
656
|
-
import
|
|
599
|
+
import chalk4 from "chalk";
|
|
657
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
|
|
658
812
|
function displayProblemList(problems, total) {
|
|
659
813
|
const table = new Table({
|
|
660
814
|
head: [
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
815
|
+
chalk4.cyan("ID"),
|
|
816
|
+
chalk4.cyan("Title"),
|
|
817
|
+
chalk4.cyan("Difficulty"),
|
|
818
|
+
chalk4.cyan("Rate"),
|
|
819
|
+
chalk4.cyan("Status")
|
|
666
820
|
],
|
|
667
821
|
colWidths: [8, 45, 12, 10, 10],
|
|
668
822
|
style: { head: [], border: [] }
|
|
@@ -681,34 +835,34 @@ function displayProblemList(problems, total) {
|
|
|
681
835
|
]);
|
|
682
836
|
}
|
|
683
837
|
console.log(table.toString());
|
|
684
|
-
console.log(
|
|
838
|
+
console.log(chalk4.gray(`
|
|
685
839
|
Showing ${problems.length} of ${total} problems`));
|
|
686
840
|
}
|
|
687
841
|
function displayProblemDetail(problem) {
|
|
688
842
|
console.log();
|
|
689
843
|
const titlePrefix = problem.isPaidOnly ? "\u{1F512} " : "";
|
|
690
|
-
console.log(
|
|
844
|
+
console.log(chalk4.bold.cyan(` ${problem.questionFrontendId}. ${titlePrefix}${problem.title}`));
|
|
691
845
|
console.log(` ${colorDifficulty(problem.difficulty)}`);
|
|
692
|
-
console.log(
|
|
846
|
+
console.log(chalk4.gray(` https://leetcode.com/problems/${problem.titleSlug}/`));
|
|
693
847
|
console.log();
|
|
694
848
|
if (problem.isPaidOnly) {
|
|
695
|
-
console.log(
|
|
696
|
-
console.log(
|
|
697
|
-
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.`));
|
|
698
852
|
console.log();
|
|
699
853
|
}
|
|
700
854
|
if (problem.topicTags.length) {
|
|
701
|
-
const tags = problem.topicTags.map((t) =>
|
|
855
|
+
const tags = problem.topicTags.map((t) => chalk4.bgBlue.white(` ${t.name} `)).join(" ");
|
|
702
856
|
console.log(` ${tags}`);
|
|
703
857
|
console.log();
|
|
704
858
|
}
|
|
705
|
-
console.log(
|
|
859
|
+
console.log(chalk4.gray("\u2500".repeat(60)));
|
|
706
860
|
console.log();
|
|
707
861
|
let content = problem.content;
|
|
708
862
|
if (!content) {
|
|
709
|
-
console.log(
|
|
710
|
-
console.log(
|
|
711
|
-
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."));
|
|
712
866
|
console.log();
|
|
713
867
|
return;
|
|
714
868
|
}
|
|
@@ -728,100 +882,128 @@ function displayProblemDetail(problem) {
|
|
|
728
882
|
content = content.replace(/<[^>]+>/g, "");
|
|
729
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)));
|
|
730
884
|
content = content.replace(/\n{3,}/g, "\n\n").trim();
|
|
731
|
-
content = content.replace(/§EXAMPLE§(\d+)§/g, (_, num) =>
|
|
732
|
-
content = content.replace(/§INPUT§/g,
|
|
733
|
-
content = content.replace(/§OUTPUT§/g,
|
|
734
|
-
content = content.replace(/§EXPLAIN§/g,
|
|
735
|
-
content = content.replace(/§CONSTRAINTS§/g,
|
|
736
|
-
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:"));
|
|
737
891
|
console.log(content);
|
|
738
892
|
console.log();
|
|
739
893
|
}
|
|
740
|
-
function displayTestResult(result) {
|
|
894
|
+
function displayTestResult(result, topicTags) {
|
|
741
895
|
console.log();
|
|
742
896
|
if (result.compile_error) {
|
|
743
|
-
console.log(
|
|
744
|
-
console.log(
|
|
897
|
+
console.log(chalk4.red.bold("\u274C Compile Error"));
|
|
898
|
+
console.log(chalk4.red(result.compile_error));
|
|
745
899
|
return;
|
|
746
900
|
}
|
|
747
901
|
if (result.runtime_error) {
|
|
748
|
-
console.log(
|
|
749
|
-
console.log(
|
|
902
|
+
console.log(chalk4.red.bold("\u274C Runtime Error"));
|
|
903
|
+
console.log(chalk4.red(result.runtime_error));
|
|
750
904
|
return;
|
|
751
905
|
}
|
|
752
906
|
if (result.correct_answer) {
|
|
753
|
-
console.log(
|
|
907
|
+
console.log(chalk4.green.bold("\u2713 All test cases passed!"));
|
|
754
908
|
} else {
|
|
755
|
-
console.log(
|
|
756
|
-
}
|
|
757
|
-
console.log();
|
|
758
|
-
console.log(chalk3.gray("Your Output:"));
|
|
759
|
-
for (const output of result.code_answer ?? []) {
|
|
760
|
-
console.log(chalk3.white(` ${output}`));
|
|
909
|
+
console.log(chalk4.yellow.bold("\u2717 Some test cases failed"));
|
|
761
910
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
console.log(
|
|
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
|
+
}
|
|
766
947
|
}
|
|
767
|
-
|
|
948
|
+
const stdoutEntries = (result.std_output_list ?? []).filter((s) => s);
|
|
949
|
+
if (stdoutEntries.length > 0) {
|
|
768
950
|
console.log();
|
|
769
|
-
console.log(
|
|
770
|
-
for (const output of
|
|
771
|
-
|
|
951
|
+
console.log(chalk4.gray("Stdout:"));
|
|
952
|
+
for (const output of stdoutEntries) {
|
|
953
|
+
console.log(chalk4.gray(` ${output}`));
|
|
772
954
|
}
|
|
773
955
|
}
|
|
774
956
|
}
|
|
775
957
|
function displaySubmissionResult(result) {
|
|
776
958
|
console.log();
|
|
777
959
|
if (result.compile_error) {
|
|
778
|
-
console.log(
|
|
779
|
-
console.log(
|
|
960
|
+
console.log(chalk4.red.bold("\u274C Compile Error"));
|
|
961
|
+
console.log(chalk4.red(result.compile_error));
|
|
780
962
|
return;
|
|
781
963
|
}
|
|
782
964
|
if (result.runtime_error) {
|
|
783
|
-
console.log(
|
|
784
|
-
console.log(
|
|
965
|
+
console.log(chalk4.red.bold("\u274C Runtime Error"));
|
|
966
|
+
console.log(chalk4.red(result.runtime_error));
|
|
785
967
|
if (result.last_testcase) {
|
|
786
|
-
console.log(
|
|
968
|
+
console.log(chalk4.gray("Last testcase:"), result.last_testcase);
|
|
787
969
|
}
|
|
788
970
|
return;
|
|
789
971
|
}
|
|
790
972
|
if (result.status_msg === "Accepted") {
|
|
791
|
-
console.log(
|
|
973
|
+
console.log(chalk4.green.bold("\u2713 Accepted!"));
|
|
792
974
|
console.log();
|
|
793
975
|
console.log(
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
976
|
+
chalk4.gray("Runtime:"),
|
|
977
|
+
chalk4.white(result.status_runtime),
|
|
978
|
+
chalk4.gray(`(beats ${result.runtime_percentile?.toFixed(1) ?? "N/A"}%)`)
|
|
797
979
|
);
|
|
798
980
|
console.log(
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
981
|
+
chalk4.gray("Memory:"),
|
|
982
|
+
chalk4.white(result.status_memory),
|
|
983
|
+
chalk4.gray(`(beats ${result.memory_percentile?.toFixed(1) ?? "N/A"}%)`)
|
|
802
984
|
);
|
|
803
985
|
} else {
|
|
804
|
-
console.log(
|
|
986
|
+
console.log(chalk4.red.bold(`\u274C ${result.status_msg}`));
|
|
805
987
|
console.log();
|
|
806
|
-
console.log(
|
|
988
|
+
console.log(chalk4.gray(`Passed ${result.total_correct}/${result.total_testcases} testcases`));
|
|
807
989
|
if (result.code_output) {
|
|
808
|
-
console.log(
|
|
990
|
+
console.log(chalk4.gray("Your Output:"), result.code_output);
|
|
809
991
|
}
|
|
810
992
|
if (result.expected_output) {
|
|
811
|
-
console.log(
|
|
993
|
+
console.log(chalk4.gray("Expected:"), result.expected_output);
|
|
812
994
|
}
|
|
813
995
|
if (result.last_testcase) {
|
|
814
|
-
console.log(
|
|
996
|
+
console.log(chalk4.gray("Failed testcase:"), result.last_testcase);
|
|
815
997
|
}
|
|
816
998
|
}
|
|
817
999
|
}
|
|
818
1000
|
function displayUserStats(username, realName, ranking, acStats, streak, totalActiveDays) {
|
|
819
1001
|
console.log();
|
|
820
|
-
console.log(
|
|
821
|
-
console.log(
|
|
1002
|
+
console.log(chalk4.bold.white(`\u{1F464} ${username}`) + (realName ? chalk4.gray(` (${realName})`) : ""));
|
|
1003
|
+
console.log(chalk4.gray(`Ranking: #${ranking.toLocaleString()}`));
|
|
822
1004
|
console.log();
|
|
823
1005
|
const table = new Table({
|
|
824
|
-
head: [
|
|
1006
|
+
head: [chalk4.cyan("Difficulty"), chalk4.cyan("Solved")],
|
|
825
1007
|
style: { head: [], border: [] }
|
|
826
1008
|
});
|
|
827
1009
|
for (const stat of acStats) {
|
|
@@ -833,33 +1015,33 @@ function displayUserStats(username, realName, ranking, acStats, streak, totalAct
|
|
|
833
1015
|
}
|
|
834
1016
|
}
|
|
835
1017
|
const total = acStats.find((s) => s.difficulty === "All")?.count ?? 0;
|
|
836
|
-
table.push([
|
|
1018
|
+
table.push([chalk4.white.bold("Total"), chalk4.white.bold(total.toString())]);
|
|
837
1019
|
console.log(table.toString());
|
|
838
1020
|
console.log();
|
|
839
|
-
console.log(
|
|
840
|
-
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()));
|
|
841
1023
|
}
|
|
842
1024
|
function displayDailyChallenge(date, problem) {
|
|
843
1025
|
console.log();
|
|
844
|
-
console.log(
|
|
1026
|
+
console.log(chalk4.bold.yellow("\u{1F3AF} Daily Challenge"), chalk4.gray(`(${date})`));
|
|
845
1027
|
console.log();
|
|
846
|
-
console.log(
|
|
1028
|
+
console.log(chalk4.white(`${problem.questionFrontendId}. ${problem.title}`));
|
|
847
1029
|
console.log(colorDifficulty(problem.difficulty));
|
|
848
|
-
console.log(
|
|
1030
|
+
console.log(chalk4.gray(`https://leetcode.com/problems/${problem.titleSlug}/`));
|
|
849
1031
|
if (problem.topicTags.length) {
|
|
850
1032
|
console.log();
|
|
851
|
-
const tags = problem.topicTags.map((t) =>
|
|
852
|
-
console.log(
|
|
1033
|
+
const tags = problem.topicTags.map((t) => chalk4.blue(t.name)).join(" ");
|
|
1034
|
+
console.log(chalk4.gray("Tags:"), tags);
|
|
853
1035
|
}
|
|
854
1036
|
}
|
|
855
1037
|
function colorDifficulty(difficulty) {
|
|
856
1038
|
switch (difficulty.toLowerCase()) {
|
|
857
1039
|
case "easy":
|
|
858
|
-
return
|
|
1040
|
+
return chalk4.green(difficulty);
|
|
859
1041
|
case "medium":
|
|
860
|
-
return
|
|
1042
|
+
return chalk4.yellow(difficulty);
|
|
861
1043
|
case "hard":
|
|
862
|
-
return
|
|
1044
|
+
return chalk4.red(difficulty);
|
|
863
1045
|
default:
|
|
864
1046
|
return difficulty;
|
|
865
1047
|
}
|
|
@@ -867,22 +1049,22 @@ function colorDifficulty(difficulty) {
|
|
|
867
1049
|
function formatStatus(status) {
|
|
868
1050
|
switch (status) {
|
|
869
1051
|
case "ac":
|
|
870
|
-
return
|
|
1052
|
+
return chalk4.green("\u2713");
|
|
871
1053
|
case "notac":
|
|
872
|
-
return
|
|
1054
|
+
return chalk4.yellow("\u25CB");
|
|
873
1055
|
default:
|
|
874
|
-
return
|
|
1056
|
+
return chalk4.gray("-");
|
|
875
1057
|
}
|
|
876
1058
|
}
|
|
877
1059
|
function displaySubmissionsList(submissions) {
|
|
878
1060
|
const table = new Table({
|
|
879
1061
|
head: [
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1062
|
+
chalk4.cyan("ID"),
|
|
1063
|
+
chalk4.cyan("Status"),
|
|
1064
|
+
chalk4.cyan("Lang"),
|
|
1065
|
+
chalk4.cyan("Runtime"),
|
|
1066
|
+
chalk4.cyan("Memory"),
|
|
1067
|
+
chalk4.cyan("Date")
|
|
886
1068
|
],
|
|
887
1069
|
colWidths: [12, 18, 15, 12, 12, 25],
|
|
888
1070
|
style: { head: [], border: [] }
|
|
@@ -892,7 +1074,7 @@ function displaySubmissionsList(submissions) {
|
|
|
892
1074
|
const cleanTime = new Date(parseInt(s.timestamp) * 1e3).toLocaleString();
|
|
893
1075
|
table.push([
|
|
894
1076
|
s.id,
|
|
895
|
-
isAC ?
|
|
1077
|
+
isAC ? chalk4.green(s.statusDisplay) : chalk4.red(s.statusDisplay),
|
|
896
1078
|
s.lang,
|
|
897
1079
|
s.runtime,
|
|
898
1080
|
s.memory,
|
|
@@ -943,25 +1125,25 @@ async function listCommand(options) {
|
|
|
943
1125
|
const { total, problems } = await leetcodeClient.getProblems(filters);
|
|
944
1126
|
spinner.stop();
|
|
945
1127
|
if (problems.length === 0) {
|
|
946
|
-
console.log(
|
|
1128
|
+
console.log(chalk5.yellow("No problems found matching your criteria."));
|
|
947
1129
|
return;
|
|
948
1130
|
}
|
|
949
1131
|
displayProblemList(problems, total);
|
|
950
1132
|
if (page * limit < total) {
|
|
951
|
-
console.log(
|
|
1133
|
+
console.log(chalk5.gray(`
|
|
952
1134
|
Page ${page} of ${Math.ceil(total / limit)}. Use --page to navigate.`));
|
|
953
1135
|
}
|
|
954
1136
|
} catch (error) {
|
|
955
1137
|
spinner.fail("Failed to fetch problems");
|
|
956
1138
|
if (error instanceof Error) {
|
|
957
|
-
console.log(
|
|
1139
|
+
console.log(chalk5.red(error.message));
|
|
958
1140
|
}
|
|
959
1141
|
}
|
|
960
1142
|
}
|
|
961
1143
|
|
|
962
1144
|
// src/commands/show.ts
|
|
963
1145
|
import ora3 from "ora";
|
|
964
|
-
import
|
|
1146
|
+
import chalk6 from "chalk";
|
|
965
1147
|
async function showCommand(idOrSlug) {
|
|
966
1148
|
const { authorized } = await requireAuth();
|
|
967
1149
|
if (!authorized) return;
|
|
@@ -982,17 +1164,229 @@ async function showCommand(idOrSlug) {
|
|
|
982
1164
|
} catch (error) {
|
|
983
1165
|
spinner.fail("Failed to fetch problem");
|
|
984
1166
|
if (error instanceof Error) {
|
|
985
|
-
console.log(
|
|
1167
|
+
console.log(chalk6.red(error.message));
|
|
986
1168
|
}
|
|
987
1169
|
}
|
|
988
1170
|
}
|
|
989
1171
|
|
|
990
1172
|
// src/commands/pick.ts
|
|
991
1173
|
import { writeFile, mkdir } from "fs/promises";
|
|
992
|
-
import { existsSync } from "fs";
|
|
993
|
-
import { join as
|
|
1174
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1175
|
+
import { join as join4 } from "path";
|
|
994
1176
|
import ora4 from "ora";
|
|
995
|
-
import
|
|
1177
|
+
import chalk8 from "chalk";
|
|
1178
|
+
|
|
1179
|
+
// src/storage/config.ts
|
|
1180
|
+
import { join as join3 } from "path";
|
|
1181
|
+
|
|
1182
|
+
// src/storage/workspaces.ts
|
|
1183
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
1184
|
+
import { join as join2 } from "path";
|
|
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 });
|
|
1192
|
+
}
|
|
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
|
|
1343
|
+
var config = {
|
|
1344
|
+
getConfig() {
|
|
1345
|
+
const wsConfig = workspaceStorage.getConfig();
|
|
1346
|
+
return {
|
|
1347
|
+
language: wsConfig.lang,
|
|
1348
|
+
editor: wsConfig.editor,
|
|
1349
|
+
workDir: wsConfig.workDir,
|
|
1350
|
+
repo: wsConfig.syncRepo
|
|
1351
|
+
};
|
|
1352
|
+
},
|
|
1353
|
+
setLanguage(language) {
|
|
1354
|
+
workspaceStorage.setConfig({ lang: language });
|
|
1355
|
+
},
|
|
1356
|
+
setEditor(editor) {
|
|
1357
|
+
workspaceStorage.setConfig({ editor });
|
|
1358
|
+
},
|
|
1359
|
+
setWorkDir(workDir) {
|
|
1360
|
+
workspaceStorage.setConfig({ workDir });
|
|
1361
|
+
},
|
|
1362
|
+
setRepo(repo) {
|
|
1363
|
+
workspaceStorage.setConfig({ syncRepo: repo });
|
|
1364
|
+
},
|
|
1365
|
+
deleteRepo() {
|
|
1366
|
+
const wsConfig = workspaceStorage.getConfig();
|
|
1367
|
+
delete wsConfig.syncRepo;
|
|
1368
|
+
workspaceStorage.setConfig(wsConfig);
|
|
1369
|
+
},
|
|
1370
|
+
getLanguage() {
|
|
1371
|
+
return workspaceStorage.getConfig().lang;
|
|
1372
|
+
},
|
|
1373
|
+
getEditor() {
|
|
1374
|
+
return workspaceStorage.getConfig().editor;
|
|
1375
|
+
},
|
|
1376
|
+
getWorkDir() {
|
|
1377
|
+
return workspaceStorage.getConfig().workDir;
|
|
1378
|
+
},
|
|
1379
|
+
getRepo() {
|
|
1380
|
+
return workspaceStorage.getConfig().syncRepo;
|
|
1381
|
+
},
|
|
1382
|
+
getPath() {
|
|
1383
|
+
return join3(workspaceStorage.getWorkspaceDir(), "config.json");
|
|
1384
|
+
},
|
|
1385
|
+
// New workspace-aware methods
|
|
1386
|
+
getActiveWorkspace() {
|
|
1387
|
+
return workspaceStorage.getActive();
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
996
1390
|
|
|
997
1391
|
// src/utils/templates.ts
|
|
998
1392
|
var LANGUAGE_EXTENSIONS = {
|
|
@@ -1116,7 +1510,7 @@ function getSolutionFileName(problemId, titleSlug, language) {
|
|
|
1116
1510
|
// src/utils/editor.ts
|
|
1117
1511
|
import { spawn } from "child_process";
|
|
1118
1512
|
import open from "open";
|
|
1119
|
-
import
|
|
1513
|
+
import chalk7 from "chalk";
|
|
1120
1514
|
var TERMINAL_EDITORS = ["vim", "nvim", "vi", "nano", "emacs", "micro", "helix"];
|
|
1121
1515
|
var VSCODE_EDITORS = ["code", "code-insiders", "cursor", "codium", "vscodium"];
|
|
1122
1516
|
async function openInEditor(filePath, workDir) {
|
|
@@ -1124,7 +1518,7 @@ async function openInEditor(filePath, workDir) {
|
|
|
1124
1518
|
const workspace = workDir ?? config.getWorkDir();
|
|
1125
1519
|
if (TERMINAL_EDITORS.includes(editor)) {
|
|
1126
1520
|
console.log();
|
|
1127
|
-
console.log(
|
|
1521
|
+
console.log(chalk7.gray(`Open with: ${editor} ${filePath}`));
|
|
1128
1522
|
return;
|
|
1129
1523
|
}
|
|
1130
1524
|
try {
|
|
@@ -1164,13 +1558,13 @@ async function pickCommand(idOrSlug, options) {
|
|
|
1164
1558
|
const template = getCodeTemplate(snippets, language);
|
|
1165
1559
|
let code;
|
|
1166
1560
|
if (snippets.length === 0) {
|
|
1167
|
-
spinner.warn(
|
|
1168
|
-
console.log(
|
|
1561
|
+
spinner.warn(chalk8.yellow("Premium Problem (No code snippets available)"));
|
|
1562
|
+
console.log(chalk8.gray("Generating placeholder file with problem info..."));
|
|
1169
1563
|
code = `// \u{1F512} Premium Problem - ${problem.title}
|
|
1170
1564
|
// Solution stub not available - visit LeetCode to view`;
|
|
1171
1565
|
} else if (!template) {
|
|
1172
1566
|
spinner.fail(`No code template available for ${language}`);
|
|
1173
|
-
console.log(
|
|
1567
|
+
console.log(chalk8.gray(`Available languages: ${snippets.map((s) => s.langSlug).join(", ")}`));
|
|
1174
1568
|
return false;
|
|
1175
1569
|
} else {
|
|
1176
1570
|
code = template.code;
|
|
@@ -1187,26 +1581,26 @@ async function pickCommand(idOrSlug, options) {
|
|
|
1187
1581
|
const workDir = config.getWorkDir();
|
|
1188
1582
|
const difficulty = problem.difficulty;
|
|
1189
1583
|
const category = problem.topicTags.length > 0 ? problem.topicTags[0].name.replace(/[^\w\s-]/g, "").trim() : "Uncategorized";
|
|
1190
|
-
const targetDir =
|
|
1191
|
-
if (!
|
|
1584
|
+
const targetDir = join4(workDir, difficulty, category);
|
|
1585
|
+
if (!existsSync2(targetDir)) {
|
|
1192
1586
|
await mkdir(targetDir, { recursive: true });
|
|
1193
1587
|
}
|
|
1194
1588
|
const fileName = getSolutionFileName(problem.questionFrontendId, problem.titleSlug, language);
|
|
1195
|
-
const filePath =
|
|
1196
|
-
if (
|
|
1589
|
+
const filePath = join4(targetDir, fileName);
|
|
1590
|
+
if (existsSync2(filePath)) {
|
|
1197
1591
|
spinner.warn(`File already exists: ${fileName}`);
|
|
1198
|
-
console.log(
|
|
1592
|
+
console.log(chalk8.gray(`Path: ${filePath}`));
|
|
1199
1593
|
if (options.open !== false) {
|
|
1200
1594
|
await openInEditor(filePath);
|
|
1201
1595
|
}
|
|
1202
1596
|
return true;
|
|
1203
1597
|
}
|
|
1204
1598
|
await writeFile(filePath, content, "utf-8");
|
|
1205
|
-
spinner.succeed(`Created ${
|
|
1206
|
-
console.log(
|
|
1599
|
+
spinner.succeed(`Created ${chalk8.green(fileName)}`);
|
|
1600
|
+
console.log(chalk8.gray(`Path: ${filePath}`));
|
|
1207
1601
|
console.log();
|
|
1208
|
-
console.log(
|
|
1209
|
-
console.log(
|
|
1602
|
+
console.log(chalk8.cyan(`${problem.questionFrontendId}. ${problem.title}`));
|
|
1603
|
+
console.log(chalk8.gray(`Difficulty: ${problem.difficulty} | Category: ${category}`));
|
|
1210
1604
|
if (options.open !== false) {
|
|
1211
1605
|
await openInEditor(filePath);
|
|
1212
1606
|
}
|
|
@@ -1215,17 +1609,17 @@ async function pickCommand(idOrSlug, options) {
|
|
|
1215
1609
|
spinner.fail("Failed to fetch problem");
|
|
1216
1610
|
if (error instanceof Error) {
|
|
1217
1611
|
if (error.message.includes("expected object, received null")) {
|
|
1218
|
-
console.log(
|
|
1612
|
+
console.log(chalk8.red(`Problem "${idOrSlug}" not found`));
|
|
1219
1613
|
} else {
|
|
1220
1614
|
try {
|
|
1221
1615
|
const zodError = JSON.parse(error.message);
|
|
1222
1616
|
if (Array.isArray(zodError)) {
|
|
1223
|
-
console.log(
|
|
1617
|
+
console.log(chalk8.red("API Response Validation Failed"));
|
|
1224
1618
|
} else {
|
|
1225
|
-
console.log(
|
|
1619
|
+
console.log(chalk8.red(error.message));
|
|
1226
1620
|
}
|
|
1227
1621
|
} catch {
|
|
1228
|
-
console.log(
|
|
1622
|
+
console.log(chalk8.red(error.message));
|
|
1229
1623
|
}
|
|
1230
1624
|
}
|
|
1231
1625
|
}
|
|
@@ -1234,12 +1628,12 @@ async function pickCommand(idOrSlug, options) {
|
|
|
1234
1628
|
}
|
|
1235
1629
|
async function batchPickCommand(ids, options) {
|
|
1236
1630
|
if (ids.length === 0) {
|
|
1237
|
-
console.log(
|
|
1631
|
+
console.log(chalk8.yellow("Please provide at least one problem ID"));
|
|
1238
1632
|
return;
|
|
1239
1633
|
}
|
|
1240
1634
|
const { authorized } = await requireAuth();
|
|
1241
1635
|
if (!authorized) return;
|
|
1242
|
-
console.log(
|
|
1636
|
+
console.log(chalk8.cyan(`\u{1F4E6} Picking ${ids.length} problem${ids.length !== 1 ? "s" : ""}...`));
|
|
1243
1637
|
console.log();
|
|
1244
1638
|
console.log();
|
|
1245
1639
|
let succeeded = 0;
|
|
@@ -1253,33 +1647,33 @@ async function batchPickCommand(ids, options) {
|
|
|
1253
1647
|
}
|
|
1254
1648
|
console.log();
|
|
1255
1649
|
}
|
|
1256
|
-
console.log(
|
|
1650
|
+
console.log(chalk8.gray("\u2500".repeat(50)));
|
|
1257
1651
|
console.log(
|
|
1258
|
-
|
|
1259
|
-
`Done! ${
|
|
1652
|
+
chalk8.bold(
|
|
1653
|
+
`Done! ${chalk8.green(`${succeeded} succeeded`)}${failed > 0 ? `, ${chalk8.red(`${failed} failed`)}` : ""}`
|
|
1260
1654
|
)
|
|
1261
1655
|
);
|
|
1262
1656
|
}
|
|
1263
1657
|
|
|
1264
1658
|
// src/commands/test.ts
|
|
1265
1659
|
import { readFile } from "fs/promises";
|
|
1266
|
-
import { existsSync as
|
|
1660
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1267
1661
|
import { basename } from "path";
|
|
1268
1662
|
import ora5 from "ora";
|
|
1269
|
-
import
|
|
1663
|
+
import chalk9 from "chalk";
|
|
1270
1664
|
|
|
1271
1665
|
// src/utils/fileUtils.ts
|
|
1272
1666
|
import { readdir } from "fs/promises";
|
|
1273
|
-
import { existsSync as
|
|
1274
|
-
import { join as
|
|
1667
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1668
|
+
import { join as join5 } from "path";
|
|
1275
1669
|
var MAX_SEARCH_DEPTH = 5;
|
|
1276
1670
|
async function findSolutionFile(dir, problemId, currentDepth = 0) {
|
|
1277
|
-
if (!
|
|
1671
|
+
if (!existsSync3(dir)) return null;
|
|
1278
1672
|
if (currentDepth >= MAX_SEARCH_DEPTH) return null;
|
|
1279
1673
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
1280
1674
|
for (const entry of entries) {
|
|
1281
1675
|
if (entry.name.startsWith(".")) continue;
|
|
1282
|
-
const fullPath =
|
|
1676
|
+
const fullPath = join5(dir, entry.name);
|
|
1283
1677
|
if (entry.isDirectory()) {
|
|
1284
1678
|
const found = await findSolutionFile(fullPath, problemId, currentDepth + 1);
|
|
1285
1679
|
if (found) return found;
|
|
@@ -1293,11 +1687,11 @@ async function findSolutionFile(dir, problemId, currentDepth = 0) {
|
|
|
1293
1687
|
return null;
|
|
1294
1688
|
}
|
|
1295
1689
|
async function findFileByName(dir, fileName, currentDepth = 0) {
|
|
1296
|
-
if (!
|
|
1690
|
+
if (!existsSync3(dir)) return null;
|
|
1297
1691
|
if (currentDepth >= MAX_SEARCH_DEPTH) return null;
|
|
1298
1692
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
1299
1693
|
for (const entry of entries) {
|
|
1300
|
-
const fullPath =
|
|
1694
|
+
const fullPath = join5(dir, entry.name);
|
|
1301
1695
|
if (entry.isDirectory()) {
|
|
1302
1696
|
const found = await findFileByName(fullPath, fileName, currentDepth + 1);
|
|
1303
1697
|
if (found) return found;
|
|
@@ -1354,26 +1748,26 @@ async function testCommand(fileOrId, options) {
|
|
|
1354
1748
|
const workDir = config.getWorkDir();
|
|
1355
1749
|
const found = await findSolutionFile(workDir, fileOrId);
|
|
1356
1750
|
if (!found) {
|
|
1357
|
-
console.log(
|
|
1358
|
-
console.log(
|
|
1359
|
-
console.log(
|
|
1751
|
+
console.log(chalk9.red(`No solution file found for problem ${fileOrId}`));
|
|
1752
|
+
console.log(chalk9.gray(`Looking in: ${workDir}`));
|
|
1753
|
+
console.log(chalk9.gray(`Run "leetcode pick ${fileOrId}" first to create a solution file.`));
|
|
1360
1754
|
return;
|
|
1361
1755
|
}
|
|
1362
1756
|
filePath = found;
|
|
1363
|
-
console.log(
|
|
1757
|
+
console.log(chalk9.gray(`Found: ${filePath}`));
|
|
1364
1758
|
} else if (isFileName(fileOrId)) {
|
|
1365
1759
|
const workDir = config.getWorkDir();
|
|
1366
1760
|
const found = await findFileByName(workDir, fileOrId);
|
|
1367
1761
|
if (!found) {
|
|
1368
|
-
console.log(
|
|
1369
|
-
console.log(
|
|
1762
|
+
console.log(chalk9.red(`File not found: ${fileOrId}`));
|
|
1763
|
+
console.log(chalk9.gray(`Looking in: ${workDir}`));
|
|
1370
1764
|
return;
|
|
1371
1765
|
}
|
|
1372
1766
|
filePath = found;
|
|
1373
|
-
console.log(
|
|
1767
|
+
console.log(chalk9.gray(`Found: ${filePath}`));
|
|
1374
1768
|
}
|
|
1375
|
-
if (!
|
|
1376
|
-
console.log(
|
|
1769
|
+
if (!existsSync4(filePath)) {
|
|
1770
|
+
console.log(chalk9.red(`File not found: ${filePath}`));
|
|
1377
1771
|
return;
|
|
1378
1772
|
}
|
|
1379
1773
|
const spinner = ora5({ text: "Reading solution file...", spinner: "dots" }).start();
|
|
@@ -1382,8 +1776,8 @@ async function testCommand(fileOrId, options) {
|
|
|
1382
1776
|
const match = fileName.match(/^(\d+)\.([^.]+)\./);
|
|
1383
1777
|
if (!match) {
|
|
1384
1778
|
spinner.fail("Invalid filename format");
|
|
1385
|
-
console.log(
|
|
1386
|
-
console.log(
|
|
1779
|
+
console.log(chalk9.gray("Expected format: {id}.{title-slug}.{ext}"));
|
|
1780
|
+
console.log(chalk9.gray("Example: 1.two-sum.ts"));
|
|
1387
1781
|
return;
|
|
1388
1782
|
}
|
|
1389
1783
|
const [, problemId, titleSlug] = match;
|
|
@@ -1400,62 +1794,75 @@ async function testCommand(fileOrId, options) {
|
|
|
1400
1794
|
spinner.text = "Running tests...";
|
|
1401
1795
|
const result = await leetcodeClient.testSolution(titleSlug, code, lang, testcases, problem.questionId);
|
|
1402
1796
|
spinner.stop();
|
|
1403
|
-
displayTestResult(result);
|
|
1797
|
+
displayTestResult(result, options.visualize ? problem.topicTags : void 0);
|
|
1404
1798
|
} catch (error) {
|
|
1405
1799
|
spinner.fail("Test failed");
|
|
1406
1800
|
if (error instanceof Error) {
|
|
1407
|
-
console.log(
|
|
1801
|
+
console.log(chalk9.red(error.message));
|
|
1408
1802
|
}
|
|
1409
1803
|
}
|
|
1410
1804
|
}
|
|
1411
1805
|
|
|
1412
1806
|
// src/commands/submit.ts
|
|
1413
1807
|
import { readFile as readFile2 } from "fs/promises";
|
|
1414
|
-
import { existsSync as
|
|
1808
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1415
1809
|
import { basename as basename2 } from "path";
|
|
1416
1810
|
import ora6 from "ora";
|
|
1417
|
-
import
|
|
1811
|
+
import chalk10 from "chalk";
|
|
1418
1812
|
|
|
1419
1813
|
// src/storage/timer.ts
|
|
1420
|
-
import
|
|
1421
|
-
import {
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1814
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1815
|
+
import { dirname } from "path";
|
|
1816
|
+
function getTimerPath() {
|
|
1817
|
+
return workspaceStorage.getTimerPath();
|
|
1818
|
+
}
|
|
1819
|
+
function loadTimer() {
|
|
1820
|
+
const path = getTimerPath();
|
|
1821
|
+
if (existsSync5(path)) {
|
|
1822
|
+
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
1429
1823
|
}
|
|
1430
|
-
}
|
|
1824
|
+
return { solveTimes: {}, activeTimer: null };
|
|
1825
|
+
}
|
|
1826
|
+
function saveTimer(data) {
|
|
1827
|
+
const timerPath = getTimerPath();
|
|
1828
|
+
const dir = dirname(timerPath);
|
|
1829
|
+
if (!existsSync5(dir)) {
|
|
1830
|
+
mkdirSync2(dir, { recursive: true });
|
|
1831
|
+
}
|
|
1832
|
+
writeFileSync2(timerPath, JSON.stringify(data, null, 2));
|
|
1833
|
+
}
|
|
1431
1834
|
var timerStorage = {
|
|
1432
1835
|
startTimer(problemId, title, difficulty, durationMinutes) {
|
|
1433
|
-
|
|
1836
|
+
const data = loadTimer();
|
|
1837
|
+
data.activeTimer = {
|
|
1434
1838
|
problemId,
|
|
1435
1839
|
title,
|
|
1436
1840
|
difficulty,
|
|
1437
1841
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1438
1842
|
durationMinutes
|
|
1439
|
-
}
|
|
1843
|
+
};
|
|
1844
|
+
saveTimer(data);
|
|
1440
1845
|
},
|
|
1441
1846
|
getActiveTimer() {
|
|
1442
|
-
return
|
|
1847
|
+
return loadTimer().activeTimer;
|
|
1443
1848
|
},
|
|
1444
1849
|
stopTimer() {
|
|
1445
|
-
const
|
|
1850
|
+
const data = loadTimer();
|
|
1851
|
+
const active = data.activeTimer;
|
|
1446
1852
|
if (!active) return null;
|
|
1447
1853
|
const startedAt = new Date(active.startedAt);
|
|
1448
1854
|
const now = /* @__PURE__ */ new Date();
|
|
1449
1855
|
const durationSeconds = Math.floor((now.getTime() - startedAt.getTime()) / 1e3);
|
|
1450
|
-
|
|
1856
|
+
data.activeTimer = null;
|
|
1857
|
+
saveTimer(data);
|
|
1451
1858
|
return { durationSeconds };
|
|
1452
1859
|
},
|
|
1453
1860
|
recordSolveTime(problemId, title, difficulty, durationSeconds, timerMinutes) {
|
|
1454
|
-
const
|
|
1455
|
-
if (!solveTimes[problemId]) {
|
|
1456
|
-
solveTimes[problemId] = [];
|
|
1861
|
+
const data = loadTimer();
|
|
1862
|
+
if (!data.solveTimes[problemId]) {
|
|
1863
|
+
data.solveTimes[problemId] = [];
|
|
1457
1864
|
}
|
|
1458
|
-
solveTimes[problemId].push({
|
|
1865
|
+
data.solveTimes[problemId].push({
|
|
1459
1866
|
problemId,
|
|
1460
1867
|
title,
|
|
1461
1868
|
difficulty,
|
|
@@ -1463,17 +1870,17 @@ var timerStorage = {
|
|
|
1463
1870
|
durationSeconds,
|
|
1464
1871
|
timerMinutes
|
|
1465
1872
|
});
|
|
1466
|
-
|
|
1873
|
+
saveTimer(data);
|
|
1467
1874
|
},
|
|
1468
1875
|
getSolveTimes(problemId) {
|
|
1469
|
-
const
|
|
1470
|
-
return solveTimes[problemId] ?? [];
|
|
1876
|
+
const data = loadTimer();
|
|
1877
|
+
return data.solveTimes[problemId] ?? [];
|
|
1471
1878
|
},
|
|
1472
1879
|
getAllSolveTimes() {
|
|
1473
|
-
return
|
|
1880
|
+
return loadTimer().solveTimes ?? {};
|
|
1474
1881
|
},
|
|
1475
1882
|
getStats() {
|
|
1476
|
-
const solveTimes =
|
|
1883
|
+
const solveTimes = loadTimer().solveTimes ?? {};
|
|
1477
1884
|
let totalProblems = 0;
|
|
1478
1885
|
let totalTime = 0;
|
|
1479
1886
|
for (const times of Object.values(solveTimes)) {
|
|
@@ -1499,26 +1906,26 @@ async function submitCommand(fileOrId) {
|
|
|
1499
1906
|
const workDir = config.getWorkDir();
|
|
1500
1907
|
const found = await findSolutionFile(workDir, fileOrId);
|
|
1501
1908
|
if (!found) {
|
|
1502
|
-
console.log(
|
|
1503
|
-
console.log(
|
|
1504
|
-
console.log(
|
|
1909
|
+
console.log(chalk10.red(`No solution file found for problem ${fileOrId}`));
|
|
1910
|
+
console.log(chalk10.gray(`Looking in: ${workDir}`));
|
|
1911
|
+
console.log(chalk10.gray(`Run "leetcode pick ${fileOrId}" first to create a solution file.`));
|
|
1505
1912
|
return;
|
|
1506
1913
|
}
|
|
1507
1914
|
filePath = found;
|
|
1508
|
-
console.log(
|
|
1915
|
+
console.log(chalk10.gray(`Found: ${filePath}`));
|
|
1509
1916
|
} else if (isFileName(fileOrId)) {
|
|
1510
1917
|
const workDir = config.getWorkDir();
|
|
1511
1918
|
const found = await findFileByName(workDir, fileOrId);
|
|
1512
1919
|
if (!found) {
|
|
1513
|
-
console.log(
|
|
1514
|
-
console.log(
|
|
1920
|
+
console.log(chalk10.red(`File not found: ${fileOrId}`));
|
|
1921
|
+
console.log(chalk10.gray(`Looking in: ${workDir}`));
|
|
1515
1922
|
return;
|
|
1516
1923
|
}
|
|
1517
1924
|
filePath = found;
|
|
1518
|
-
console.log(
|
|
1925
|
+
console.log(chalk10.gray(`Found: ${filePath}`));
|
|
1519
1926
|
}
|
|
1520
|
-
if (!
|
|
1521
|
-
console.log(
|
|
1927
|
+
if (!existsSync6(filePath)) {
|
|
1928
|
+
console.log(chalk10.red(`File not found: ${filePath}`));
|
|
1522
1929
|
return;
|
|
1523
1930
|
}
|
|
1524
1931
|
const spinner = ora6({ text: "Reading solution file...", spinner: "dots" }).start();
|
|
@@ -1527,8 +1934,8 @@ async function submitCommand(fileOrId) {
|
|
|
1527
1934
|
const match = fileName.match(/^(\d+)\.([^.]+)\./);
|
|
1528
1935
|
if (!match) {
|
|
1529
1936
|
spinner.fail("Invalid filename format");
|
|
1530
|
-
console.log(
|
|
1531
|
-
console.log(
|
|
1937
|
+
console.log(chalk10.gray("Expected format: {id}.{title-slug}.{ext}"));
|
|
1938
|
+
console.log(chalk10.gray("Example: 1.two-sum.ts"));
|
|
1532
1939
|
return;
|
|
1533
1940
|
}
|
|
1534
1941
|
const [, problemId, titleSlug] = match;
|
|
@@ -1567,14 +1974,14 @@ async function submitCommand(fileOrId) {
|
|
|
1567
1974
|
const timeStr = `${mins}m ${secs}s`;
|
|
1568
1975
|
const withinLimit = timerResult.durationSeconds <= activeTimer.durationMinutes * 60;
|
|
1569
1976
|
console.log();
|
|
1570
|
-
console.log(
|
|
1977
|
+
console.log(chalk10.bold("\u23F1\uFE0F Timer Result:"));
|
|
1571
1978
|
console.log(
|
|
1572
|
-
` Solved in ${withinLimit ?
|
|
1979
|
+
` Solved in ${withinLimit ? chalk10.green(timeStr) : chalk10.yellow(timeStr)} (limit: ${activeTimer.durationMinutes}m)`
|
|
1573
1980
|
);
|
|
1574
1981
|
if (withinLimit) {
|
|
1575
|
-
console.log(
|
|
1982
|
+
console.log(chalk10.green(" \u2713 Within time limit!"));
|
|
1576
1983
|
} else {
|
|
1577
|
-
console.log(
|
|
1984
|
+
console.log(chalk10.yellow(" \u26A0 Exceeded time limit"));
|
|
1578
1985
|
}
|
|
1579
1986
|
}
|
|
1580
1987
|
}
|
|
@@ -1582,17 +1989,17 @@ async function submitCommand(fileOrId) {
|
|
|
1582
1989
|
} catch (error) {
|
|
1583
1990
|
spinner.fail("Submission failed");
|
|
1584
1991
|
if (error instanceof Error) {
|
|
1585
|
-
console.log(
|
|
1992
|
+
console.log(chalk10.red(error.message));
|
|
1586
1993
|
}
|
|
1587
1994
|
}
|
|
1588
1995
|
}
|
|
1589
1996
|
|
|
1590
1997
|
// src/commands/stat.ts
|
|
1591
1998
|
import ora7 from "ora";
|
|
1592
|
-
import
|
|
1999
|
+
import chalk12 from "chalk";
|
|
1593
2000
|
|
|
1594
2001
|
// src/utils/stats-display.ts
|
|
1595
|
-
import
|
|
2002
|
+
import chalk11 from "chalk";
|
|
1596
2003
|
var MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
1597
2004
|
function renderHeatmap(calendarJson) {
|
|
1598
2005
|
const data = JSON.parse(calendarJson);
|
|
@@ -1625,25 +2032,25 @@ function renderHeatmap(calendarJson) {
|
|
|
1625
2032
|
const totalSubmissions = weeks.reduce((sum, w) => sum + w.count, 0);
|
|
1626
2033
|
const totalActiveDays = weeks.reduce((sum, w) => sum + w.days, 0);
|
|
1627
2034
|
console.log();
|
|
1628
|
-
console.log(
|
|
1629
|
-
console.log(
|
|
1630
|
-
console.log(
|
|
2035
|
+
console.log(chalk11.bold("\u{1F4C5} Activity (Last 12 Weeks)"));
|
|
2036
|
+
console.log(chalk11.gray("How many problems you submitted and days you practiced."));
|
|
2037
|
+
console.log(chalk11.gray("\u2500".repeat(50)));
|
|
1631
2038
|
console.log();
|
|
1632
2039
|
for (const week of weeks) {
|
|
1633
2040
|
const weekLabel = `${week.start} - ${week.end}`.padEnd(18);
|
|
1634
|
-
const bar = week.count > 0 ?
|
|
2041
|
+
const bar = week.count > 0 ? chalk11.green("\u2588".repeat(Math.min(week.count, 10))).padEnd(10) : chalk11.gray("\xB7").padEnd(10);
|
|
1635
2042
|
const countStr = week.count > 0 ? `${week.count} subs`.padEnd(10) : "".padEnd(10);
|
|
1636
2043
|
const daysStr = week.days > 0 ? `${week.days}d active` : "";
|
|
1637
|
-
console.log(` ${
|
|
2044
|
+
console.log(` ${chalk11.white(weekLabel)} ${bar} ${chalk11.cyan(countStr)} ${chalk11.yellow(daysStr)}`);
|
|
1638
2045
|
}
|
|
1639
|
-
console.log(
|
|
1640
|
-
console.log(` ${
|
|
2046
|
+
console.log(chalk11.gray("\u2500".repeat(50)));
|
|
2047
|
+
console.log(` ${chalk11.bold.white("Total:")} ${chalk11.cyan.bold(totalSubmissions + " submissions")}, ${chalk11.yellow.bold(totalActiveDays + " days active")}`);
|
|
1641
2048
|
console.log();
|
|
1642
2049
|
}
|
|
1643
2050
|
function renderSkillStats(fundamental, intermediate, advanced) {
|
|
1644
2051
|
console.log();
|
|
1645
|
-
console.log(
|
|
1646
|
-
console.log(
|
|
2052
|
+
console.log(chalk11.bold("\u{1F3AF} Skill Breakdown"));
|
|
2053
|
+
console.log(chalk11.gray("\u2500".repeat(45)));
|
|
1647
2054
|
const renderSection = (title, stats, color) => {
|
|
1648
2055
|
if (stats.length === 0) return;
|
|
1649
2056
|
console.log();
|
|
@@ -1652,12 +2059,12 @@ function renderSkillStats(fundamental, intermediate, advanced) {
|
|
|
1652
2059
|
for (const stat of sorted.slice(0, 8)) {
|
|
1653
2060
|
const name = stat.tagName.padEnd(22);
|
|
1654
2061
|
const bar = color("\u2588".repeat(Math.min(stat.problemsSolved, 15)));
|
|
1655
|
-
console.log(` ${
|
|
2062
|
+
console.log(` ${chalk11.white(name)} ${bar} ${chalk11.white(stat.problemsSolved)}`);
|
|
1656
2063
|
}
|
|
1657
2064
|
};
|
|
1658
|
-
renderSection("Fundamental", fundamental,
|
|
1659
|
-
renderSection("Intermediate", intermediate,
|
|
1660
|
-
renderSection("Advanced", advanced,
|
|
2065
|
+
renderSection("Fundamental", fundamental, chalk11.green);
|
|
2066
|
+
renderSection("Intermediate", intermediate, chalk11.yellow);
|
|
2067
|
+
renderSection("Advanced", advanced, chalk11.red);
|
|
1661
2068
|
console.log();
|
|
1662
2069
|
}
|
|
1663
2070
|
function renderTrendChart(calendarJson) {
|
|
@@ -1678,15 +2085,15 @@ function renderTrendChart(calendarJson) {
|
|
|
1678
2085
|
const maxVal = Math.max(...days.map((d) => d.count), 1);
|
|
1679
2086
|
const chartHeight = 6;
|
|
1680
2087
|
console.log();
|
|
1681
|
-
console.log(
|
|
1682
|
-
console.log(
|
|
2088
|
+
console.log(chalk11.bold("\u{1F4C8} Submission Trend (Last 7 Days)"));
|
|
2089
|
+
console.log(chalk11.gray("\u2500".repeat(35)));
|
|
1683
2090
|
console.log();
|
|
1684
2091
|
for (let row = chartHeight; row >= 1; row--) {
|
|
1685
2092
|
let line = ` ${row === chartHeight ? maxVal.toString().padStart(2) : " "} \u2502`;
|
|
1686
2093
|
for (const day of days) {
|
|
1687
2094
|
const barHeight = Math.round(day.count / maxVal * chartHeight);
|
|
1688
2095
|
if (barHeight >= row) {
|
|
1689
|
-
line +=
|
|
2096
|
+
line += chalk11.green(" \u2588\u2588 ");
|
|
1690
2097
|
} else {
|
|
1691
2098
|
line += " ";
|
|
1692
2099
|
}
|
|
@@ -1695,10 +2102,10 @@ function renderTrendChart(calendarJson) {
|
|
|
1695
2102
|
}
|
|
1696
2103
|
console.log(` 0 \u2514${"\u2500\u2500\u2500\u2500".repeat(7)}`);
|
|
1697
2104
|
console.log(` ${days.map((d) => d.label.padEnd(4)).join("")}`);
|
|
1698
|
-
console.log(
|
|
2105
|
+
console.log(chalk11.gray(` ${days.map((d) => d.count.toString().padEnd(4)).join("")}`));
|
|
1699
2106
|
const total = days.reduce((a, b) => a + b.count, 0);
|
|
1700
2107
|
console.log();
|
|
1701
|
-
console.log(
|
|
2108
|
+
console.log(chalk11.white(` Total: ${total} submissions this week`));
|
|
1702
2109
|
console.log();
|
|
1703
2110
|
}
|
|
1704
2111
|
|
|
@@ -1730,14 +2137,14 @@ async function statCommand(username, options = {}) {
|
|
|
1730
2137
|
if (profile.submissionCalendar) {
|
|
1731
2138
|
renderHeatmap(profile.submissionCalendar);
|
|
1732
2139
|
} else {
|
|
1733
|
-
console.log(
|
|
2140
|
+
console.log(chalk12.yellow("Calendar data not available."));
|
|
1734
2141
|
}
|
|
1735
2142
|
}
|
|
1736
2143
|
if (options.trend) {
|
|
1737
2144
|
if (profile.submissionCalendar) {
|
|
1738
2145
|
renderTrendChart(profile.submissionCalendar);
|
|
1739
2146
|
} else {
|
|
1740
|
-
console.log(
|
|
2147
|
+
console.log(chalk12.yellow("Calendar data not available."));
|
|
1741
2148
|
}
|
|
1742
2149
|
}
|
|
1743
2150
|
if (options.skills) {
|
|
@@ -1749,14 +2156,14 @@ async function statCommand(username, options = {}) {
|
|
|
1749
2156
|
} catch (error) {
|
|
1750
2157
|
spinner.fail("Failed to fetch statistics");
|
|
1751
2158
|
if (error instanceof Error) {
|
|
1752
|
-
console.log(
|
|
2159
|
+
console.log(chalk12.red(error.message));
|
|
1753
2160
|
}
|
|
1754
2161
|
}
|
|
1755
2162
|
}
|
|
1756
2163
|
|
|
1757
2164
|
// src/commands/daily.ts
|
|
1758
2165
|
import ora8 from "ora";
|
|
1759
|
-
import
|
|
2166
|
+
import chalk13 from "chalk";
|
|
1760
2167
|
async function dailyCommand() {
|
|
1761
2168
|
const { authorized } = await requireAuth();
|
|
1762
2169
|
if (!authorized) return;
|
|
@@ -1766,19 +2173,19 @@ async function dailyCommand() {
|
|
|
1766
2173
|
spinner.stop();
|
|
1767
2174
|
displayDailyChallenge(daily.date, daily.question);
|
|
1768
2175
|
console.log();
|
|
1769
|
-
console.log(
|
|
1770
|
-
console.log(
|
|
2176
|
+
console.log(chalk13.gray("Run the following to start working on this problem:"));
|
|
2177
|
+
console.log(chalk13.cyan(` leetcode pick ${daily.question.titleSlug}`));
|
|
1771
2178
|
} catch (error) {
|
|
1772
2179
|
spinner.fail("Failed to fetch daily challenge");
|
|
1773
2180
|
if (error instanceof Error) {
|
|
1774
|
-
console.log(
|
|
2181
|
+
console.log(chalk13.red(error.message));
|
|
1775
2182
|
}
|
|
1776
2183
|
}
|
|
1777
2184
|
}
|
|
1778
2185
|
|
|
1779
2186
|
// src/commands/random.ts
|
|
1780
2187
|
import ora9 from "ora";
|
|
1781
|
-
import
|
|
2188
|
+
import chalk14 from "chalk";
|
|
1782
2189
|
async function randomCommand(options) {
|
|
1783
2190
|
const { authorized } = await requireAuth();
|
|
1784
2191
|
if (!authorized) return;
|
|
@@ -1812,23 +2219,23 @@ async function randomCommand(options) {
|
|
|
1812
2219
|
await pickCommand(titleSlug, { open: options.open ?? true });
|
|
1813
2220
|
} else {
|
|
1814
2221
|
await showCommand(titleSlug);
|
|
1815
|
-
console.log(
|
|
1816
|
-
console.log(
|
|
2222
|
+
console.log(chalk14.gray("Run following to start solving:"));
|
|
2223
|
+
console.log(chalk14.cyan(` leetcode pick ${titleSlug}`));
|
|
1817
2224
|
}
|
|
1818
2225
|
} catch (error) {
|
|
1819
2226
|
spinner.fail("Failed to fetch random problem");
|
|
1820
2227
|
if (error instanceof Error) {
|
|
1821
|
-
console.log(
|
|
2228
|
+
console.log(chalk14.red(error.message));
|
|
1822
2229
|
}
|
|
1823
2230
|
}
|
|
1824
2231
|
}
|
|
1825
2232
|
|
|
1826
2233
|
// src/commands/submissions.ts
|
|
1827
2234
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1828
|
-
import { existsSync as
|
|
1829
|
-
import { join as
|
|
2235
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2236
|
+
import { join as join6 } from "path";
|
|
1830
2237
|
import ora10 from "ora";
|
|
1831
|
-
import
|
|
2238
|
+
import chalk15 from "chalk";
|
|
1832
2239
|
async function submissionsCommand(idOrSlug, options) {
|
|
1833
2240
|
const { authorized } = await requireAuth();
|
|
1834
2241
|
if (!authorized) return;
|
|
@@ -1850,16 +2257,16 @@ async function submissionsCommand(idOrSlug, options) {
|
|
|
1850
2257
|
const submissions = await leetcodeClient.getSubmissionList(slug, limit);
|
|
1851
2258
|
spinner.stop();
|
|
1852
2259
|
if (submissions.length === 0) {
|
|
1853
|
-
console.log(
|
|
2260
|
+
console.log(chalk15.yellow("No submissions found."));
|
|
1854
2261
|
return;
|
|
1855
2262
|
}
|
|
1856
2263
|
if (options.last) {
|
|
1857
2264
|
const lastAC = submissions.find((s) => s.statusDisplay === "Accepted");
|
|
1858
2265
|
if (lastAC) {
|
|
1859
|
-
console.log(
|
|
2266
|
+
console.log(chalk15.bold("Last Accepted Submission:"));
|
|
1860
2267
|
displaySubmissionsList([lastAC]);
|
|
1861
2268
|
} else {
|
|
1862
|
-
console.log(
|
|
2269
|
+
console.log(chalk15.yellow("No accepted submissions found in recent history."));
|
|
1863
2270
|
}
|
|
1864
2271
|
} else {
|
|
1865
2272
|
displaySubmissionsList(submissions);
|
|
@@ -1880,30 +2287,30 @@ async function submissionsCommand(idOrSlug, options) {
|
|
|
1880
2287
|
const workDir = config.getWorkDir();
|
|
1881
2288
|
const difficulty = problem.difficulty;
|
|
1882
2289
|
const category = problem.topicTags.length > 0 ? problem.topicTags[0].name.replace(/[^\w\s-]/g, "").trim() : "Uncategorized";
|
|
1883
|
-
const targetDir =
|
|
1884
|
-
if (!
|
|
2290
|
+
const targetDir = join6(workDir, difficulty, category);
|
|
2291
|
+
if (!existsSync7(targetDir)) {
|
|
1885
2292
|
await mkdir2(targetDir, { recursive: true });
|
|
1886
2293
|
}
|
|
1887
2294
|
const langSlug = details.lang.name;
|
|
1888
2295
|
const supportedLang = LANG_SLUG_MAP[langSlug] ?? "txt";
|
|
1889
2296
|
const ext = LANGUAGE_EXTENSIONS[supportedLang] ?? langSlug;
|
|
1890
2297
|
const fileName = `${problem.questionFrontendId}.${problem.titleSlug}.submission-${lastAC.id}.${ext}`;
|
|
1891
|
-
const filePath =
|
|
2298
|
+
const filePath = join6(targetDir, fileName);
|
|
1892
2299
|
await writeFile2(filePath, details.code, "utf-8");
|
|
1893
|
-
downloadSpinner.succeed(`Downloaded to ${
|
|
1894
|
-
console.log(
|
|
2300
|
+
downloadSpinner.succeed(`Downloaded to ${chalk15.green(fileName)}`);
|
|
2301
|
+
console.log(chalk15.gray(`Path: ${filePath}`));
|
|
1895
2302
|
}
|
|
1896
2303
|
} catch (error) {
|
|
1897
2304
|
spinner.fail("Failed to fetch submissions");
|
|
1898
2305
|
if (error instanceof Error) {
|
|
1899
|
-
console.log(
|
|
2306
|
+
console.log(chalk15.red(error.message));
|
|
1900
2307
|
}
|
|
1901
2308
|
}
|
|
1902
2309
|
}
|
|
1903
2310
|
|
|
1904
2311
|
// src/commands/config.ts
|
|
1905
2312
|
import inquirer2 from "inquirer";
|
|
1906
|
-
import
|
|
2313
|
+
import chalk16 from "chalk";
|
|
1907
2314
|
var SUPPORTED_LANGUAGES = [
|
|
1908
2315
|
"typescript",
|
|
1909
2316
|
"javascript",
|
|
@@ -1925,34 +2332,38 @@ async function configCommand(options) {
|
|
|
1925
2332
|
if (options.lang) {
|
|
1926
2333
|
const langInput = options.lang.toLowerCase();
|
|
1927
2334
|
if (!SUPPORTED_LANGUAGES.includes(langInput)) {
|
|
1928
|
-
console.log(
|
|
1929
|
-
console.log(
|
|
2335
|
+
console.log(chalk16.red(`Unsupported language: ${options.lang}`));
|
|
2336
|
+
console.log(chalk16.gray(`Supported: ${SUPPORTED_LANGUAGES.join(", ")}`));
|
|
1930
2337
|
return;
|
|
1931
2338
|
}
|
|
1932
2339
|
const lang = langInput;
|
|
1933
2340
|
config.setLanguage(lang);
|
|
1934
|
-
console.log(
|
|
2341
|
+
console.log(chalk16.green(`\u2713 Default language set to ${lang}`));
|
|
1935
2342
|
}
|
|
1936
2343
|
if (options.editor) {
|
|
1937
2344
|
config.setEditor(options.editor);
|
|
1938
|
-
console.log(
|
|
2345
|
+
console.log(chalk16.green(`\u2713 Editor set to ${options.editor}`));
|
|
1939
2346
|
}
|
|
1940
2347
|
if (options.workdir) {
|
|
1941
2348
|
config.setWorkDir(options.workdir);
|
|
1942
|
-
console.log(
|
|
2349
|
+
console.log(chalk16.green(`\u2713 Work directory set to ${options.workdir}`));
|
|
1943
2350
|
}
|
|
1944
2351
|
if (options.repo !== void 0) {
|
|
1945
2352
|
if (options.repo.trim() === "") {
|
|
1946
2353
|
config.deleteRepo();
|
|
1947
|
-
console.log(
|
|
2354
|
+
console.log(chalk16.green("\u2713 Repository URL cleared"));
|
|
1948
2355
|
} else {
|
|
1949
2356
|
config.setRepo(options.repo);
|
|
1950
|
-
console.log(
|
|
2357
|
+
console.log(chalk16.green(`\u2713 Repository URL set to ${options.repo}`));
|
|
1951
2358
|
}
|
|
1952
2359
|
}
|
|
1953
2360
|
}
|
|
1954
2361
|
async function configInteractiveCommand() {
|
|
1955
2362
|
const currentConfig = config.getConfig();
|
|
2363
|
+
const workspace = config.getActiveWorkspace();
|
|
2364
|
+
console.log();
|
|
2365
|
+
console.log(chalk16.bold.cyan(`\u{1F4C1} Configuring workspace: ${workspace}`));
|
|
2366
|
+
console.log(chalk16.gray("\u2500".repeat(40)));
|
|
1956
2367
|
const answers = await inquirer2.prompt([
|
|
1957
2368
|
{
|
|
1958
2369
|
type: "list",
|
|
@@ -1989,33 +2400,34 @@ async function configInteractiveCommand() {
|
|
|
1989
2400
|
config.deleteRepo();
|
|
1990
2401
|
}
|
|
1991
2402
|
console.log();
|
|
1992
|
-
console.log(
|
|
2403
|
+
console.log(chalk16.green("\u2713 Configuration saved"));
|
|
1993
2404
|
showCurrentConfig();
|
|
1994
2405
|
}
|
|
1995
2406
|
function showCurrentConfig() {
|
|
1996
2407
|
const currentConfig = config.getConfig();
|
|
1997
|
-
const
|
|
2408
|
+
const creds = credentials.get();
|
|
2409
|
+
const workspace = config.getActiveWorkspace();
|
|
1998
2410
|
console.log();
|
|
1999
|
-
console.log(
|
|
2000
|
-
console.log(
|
|
2411
|
+
console.log(chalk16.bold.cyan(`\u{1F4C1} Workspace: ${workspace}`));
|
|
2412
|
+
console.log(chalk16.gray("\u2500".repeat(40)));
|
|
2001
2413
|
console.log();
|
|
2002
|
-
console.log(
|
|
2414
|
+
console.log(chalk16.gray("Config file:"), config.getPath());
|
|
2003
2415
|
console.log();
|
|
2004
|
-
console.log(
|
|
2005
|
-
console.log(
|
|
2006
|
-
console.log(
|
|
2007
|
-
console.log(
|
|
2008
|
-
console.log(
|
|
2416
|
+
console.log(chalk16.gray("Language: "), chalk16.white(currentConfig.language));
|
|
2417
|
+
console.log(chalk16.gray("Editor: "), chalk16.white(currentConfig.editor ?? "(not set)"));
|
|
2418
|
+
console.log(chalk16.gray("Work Dir: "), chalk16.white(currentConfig.workDir));
|
|
2419
|
+
console.log(chalk16.gray("Repo URL: "), chalk16.white(currentConfig.repo ?? "(not set)"));
|
|
2420
|
+
console.log(chalk16.gray("Logged in: "), creds ? chalk16.green("Yes") : chalk16.yellow("No"));
|
|
2009
2421
|
}
|
|
2010
2422
|
|
|
2011
2423
|
// src/commands/bookmark.ts
|
|
2012
|
-
import
|
|
2424
|
+
import chalk17 from "chalk";
|
|
2013
2425
|
import Table2 from "cli-table3";
|
|
2014
2426
|
import ora11 from "ora";
|
|
2015
2427
|
|
|
2016
2428
|
// src/storage/bookmarks.ts
|
|
2017
|
-
import
|
|
2018
|
-
var bookmarksStore = new
|
|
2429
|
+
import Conf2 from "conf";
|
|
2430
|
+
var bookmarksStore = new Conf2({
|
|
2019
2431
|
projectName: "leetcode-cli-bookmarks",
|
|
2020
2432
|
defaults: { bookmarks: [] }
|
|
2021
2433
|
});
|
|
@@ -2054,36 +2466,36 @@ var bookmarks = {
|
|
|
2054
2466
|
async function bookmarkCommand(action, id) {
|
|
2055
2467
|
const validActions = ["add", "remove", "list", "clear"];
|
|
2056
2468
|
if (!validActions.includes(action)) {
|
|
2057
|
-
console.log(
|
|
2058
|
-
console.log(
|
|
2469
|
+
console.log(chalk17.red(`Invalid action: ${action}`));
|
|
2470
|
+
console.log(chalk17.gray("Valid actions: add, remove, list, clear"));
|
|
2059
2471
|
return;
|
|
2060
2472
|
}
|
|
2061
2473
|
switch (action) {
|
|
2062
2474
|
case "add":
|
|
2063
2475
|
if (!id) {
|
|
2064
|
-
console.log(
|
|
2476
|
+
console.log(chalk17.red("Please provide a problem ID to bookmark"));
|
|
2065
2477
|
return;
|
|
2066
2478
|
}
|
|
2067
2479
|
if (!isProblemId(id)) {
|
|
2068
|
-
console.log(
|
|
2069
|
-
console.log(
|
|
2480
|
+
console.log(chalk17.red(`Invalid problem ID: ${id}`));
|
|
2481
|
+
console.log(chalk17.gray("Problem ID must be a positive integer"));
|
|
2070
2482
|
return;
|
|
2071
2483
|
}
|
|
2072
2484
|
if (bookmarks.add(id)) {
|
|
2073
|
-
console.log(
|
|
2485
|
+
console.log(chalk17.green(`\u2713 Bookmarked problem ${id}`));
|
|
2074
2486
|
} else {
|
|
2075
|
-
console.log(
|
|
2487
|
+
console.log(chalk17.yellow(`Problem ${id} is already bookmarked`));
|
|
2076
2488
|
}
|
|
2077
2489
|
break;
|
|
2078
2490
|
case "remove":
|
|
2079
2491
|
if (!id) {
|
|
2080
|
-
console.log(
|
|
2492
|
+
console.log(chalk17.red("Please provide a problem ID to remove"));
|
|
2081
2493
|
return;
|
|
2082
2494
|
}
|
|
2083
2495
|
if (bookmarks.remove(id)) {
|
|
2084
|
-
console.log(
|
|
2496
|
+
console.log(chalk17.green(`\u2713 Removed bookmark for problem ${id}`));
|
|
2085
2497
|
} else {
|
|
2086
|
-
console.log(
|
|
2498
|
+
console.log(chalk17.yellow(`Problem ${id} is not bookmarked`));
|
|
2087
2499
|
}
|
|
2088
2500
|
break;
|
|
2089
2501
|
case "list":
|
|
@@ -2092,10 +2504,10 @@ async function bookmarkCommand(action, id) {
|
|
|
2092
2504
|
case "clear":
|
|
2093
2505
|
const count = bookmarks.count();
|
|
2094
2506
|
if (count === 0) {
|
|
2095
|
-
console.log(
|
|
2507
|
+
console.log(chalk17.yellow("No bookmarks to clear"));
|
|
2096
2508
|
} else {
|
|
2097
2509
|
bookmarks.clear();
|
|
2098
|
-
console.log(
|
|
2510
|
+
console.log(chalk17.green(`\u2713 Cleared ${count} bookmark${count !== 1 ? "s" : ""}`));
|
|
2099
2511
|
}
|
|
2100
2512
|
break;
|
|
2101
2513
|
}
|
|
@@ -2103,12 +2515,12 @@ async function bookmarkCommand(action, id) {
|
|
|
2103
2515
|
async function listBookmarks() {
|
|
2104
2516
|
const bookmarkList = bookmarks.list();
|
|
2105
2517
|
if (bookmarkList.length === 0) {
|
|
2106
|
-
console.log(
|
|
2107
|
-
console.log(
|
|
2518
|
+
console.log(chalk17.yellow("\u{1F4CC} No bookmarked problems"));
|
|
2519
|
+
console.log(chalk17.gray('Use "leetcode bookmark add <id>" to bookmark a problem'));
|
|
2108
2520
|
return;
|
|
2109
2521
|
}
|
|
2110
2522
|
console.log();
|
|
2111
|
-
console.log(
|
|
2523
|
+
console.log(chalk17.bold.cyan(`\u{1F4CC} Bookmarked Problems (${bookmarkList.length})`));
|
|
2112
2524
|
console.log();
|
|
2113
2525
|
const { authorized } = await requireAuth();
|
|
2114
2526
|
if (authorized) {
|
|
@@ -2116,10 +2528,10 @@ async function listBookmarks() {
|
|
|
2116
2528
|
try {
|
|
2117
2529
|
const table = new Table2({
|
|
2118
2530
|
head: [
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2531
|
+
chalk17.cyan("ID"),
|
|
2532
|
+
chalk17.cyan("Title"),
|
|
2533
|
+
chalk17.cyan("Difficulty"),
|
|
2534
|
+
chalk17.cyan("Status")
|
|
2123
2535
|
],
|
|
2124
2536
|
colWidths: [8, 45, 12, 10],
|
|
2125
2537
|
style: { head: [], border: [] }
|
|
@@ -2132,35 +2544,35 @@ async function listBookmarks() {
|
|
|
2132
2544
|
problem.questionFrontendId,
|
|
2133
2545
|
problem.title.length > 42 ? problem.title.slice(0, 39) + "..." : problem.title,
|
|
2134
2546
|
colorDifficulty2(problem.difficulty),
|
|
2135
|
-
problem.status === "ac" ?
|
|
2547
|
+
problem.status === "ac" ? chalk17.green("\u2713") : chalk17.gray("-")
|
|
2136
2548
|
]);
|
|
2137
2549
|
} else {
|
|
2138
|
-
table.push([id,
|
|
2550
|
+
table.push([id, chalk17.gray("(not found)"), "-", "-"]);
|
|
2139
2551
|
}
|
|
2140
2552
|
} catch {
|
|
2141
|
-
table.push([id,
|
|
2553
|
+
table.push([id, chalk17.gray("(error fetching)"), "-", "-"]);
|
|
2142
2554
|
}
|
|
2143
2555
|
}
|
|
2144
2556
|
spinner.stop();
|
|
2145
2557
|
console.log(table.toString());
|
|
2146
2558
|
} catch {
|
|
2147
2559
|
spinner.stop();
|
|
2148
|
-
console.log(
|
|
2560
|
+
console.log(chalk17.gray("IDs: ") + bookmarkList.join(", "));
|
|
2149
2561
|
}
|
|
2150
2562
|
} else {
|
|
2151
|
-
console.log(
|
|
2563
|
+
console.log(chalk17.gray("IDs: ") + bookmarkList.join(", "));
|
|
2152
2564
|
console.log();
|
|
2153
|
-
console.log(
|
|
2565
|
+
console.log(chalk17.gray("Login to see problem details"));
|
|
2154
2566
|
}
|
|
2155
2567
|
}
|
|
2156
2568
|
function colorDifficulty2(difficulty) {
|
|
2157
2569
|
switch (difficulty.toLowerCase()) {
|
|
2158
2570
|
case "easy":
|
|
2159
|
-
return
|
|
2571
|
+
return chalk17.green(difficulty);
|
|
2160
2572
|
case "medium":
|
|
2161
|
-
return
|
|
2573
|
+
return chalk17.yellow(difficulty);
|
|
2162
2574
|
case "hard":
|
|
2163
|
-
return
|
|
2575
|
+
return chalk17.red(difficulty);
|
|
2164
2576
|
default:
|
|
2165
2577
|
return difficulty;
|
|
2166
2578
|
}
|
|
@@ -2168,19 +2580,19 @@ function colorDifficulty2(difficulty) {
|
|
|
2168
2580
|
|
|
2169
2581
|
// src/commands/notes.ts
|
|
2170
2582
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
2171
|
-
import { join as
|
|
2172
|
-
import { existsSync as
|
|
2173
|
-
import
|
|
2583
|
+
import { join as join7 } from "path";
|
|
2584
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2585
|
+
import chalk18 from "chalk";
|
|
2174
2586
|
async function notesCommand(problemId, action) {
|
|
2175
2587
|
if (!isProblemId(problemId)) {
|
|
2176
|
-
console.log(
|
|
2177
|
-
console.log(
|
|
2588
|
+
console.log(chalk18.red(`Invalid problem ID: ${problemId}`));
|
|
2589
|
+
console.log(chalk18.gray("Problem ID must be a positive integer"));
|
|
2178
2590
|
return;
|
|
2179
2591
|
}
|
|
2180
2592
|
const noteAction = action === "view" ? "view" : "edit";
|
|
2181
|
-
const notesDir =
|
|
2182
|
-
const notePath =
|
|
2183
|
-
if (!
|
|
2593
|
+
const notesDir = join7(config.getWorkDir(), ".notes");
|
|
2594
|
+
const notePath = join7(notesDir, `${problemId}.md`);
|
|
2595
|
+
if (!existsSync8(notesDir)) {
|
|
2184
2596
|
await mkdir3(notesDir, { recursive: true });
|
|
2185
2597
|
}
|
|
2186
2598
|
if (noteAction === "view") {
|
|
@@ -2190,32 +2602,32 @@ async function notesCommand(problemId, action) {
|
|
|
2190
2602
|
}
|
|
2191
2603
|
}
|
|
2192
2604
|
async function viewNote(notePath, problemId) {
|
|
2193
|
-
if (!
|
|
2194
|
-
console.log(
|
|
2195
|
-
console.log(
|
|
2605
|
+
if (!existsSync8(notePath)) {
|
|
2606
|
+
console.log(chalk18.yellow(`No notes found for problem ${problemId}`));
|
|
2607
|
+
console.log(chalk18.gray(`Use "leetcode note ${problemId} edit" to create notes`));
|
|
2196
2608
|
return;
|
|
2197
2609
|
}
|
|
2198
2610
|
try {
|
|
2199
2611
|
const content = await readFile3(notePath, "utf-8");
|
|
2200
2612
|
console.log();
|
|
2201
|
-
console.log(
|
|
2202
|
-
console.log(
|
|
2613
|
+
console.log(chalk18.bold.cyan(`\u{1F4DD} Notes for Problem ${problemId}`));
|
|
2614
|
+
console.log(chalk18.gray("\u2500".repeat(50)));
|
|
2203
2615
|
console.log();
|
|
2204
2616
|
console.log(content);
|
|
2205
2617
|
} catch (error) {
|
|
2206
|
-
console.log(
|
|
2618
|
+
console.log(chalk18.red("Failed to read notes"));
|
|
2207
2619
|
if (error instanceof Error) {
|
|
2208
|
-
console.log(
|
|
2620
|
+
console.log(chalk18.gray(error.message));
|
|
2209
2621
|
}
|
|
2210
2622
|
}
|
|
2211
2623
|
}
|
|
2212
2624
|
async function editNote(notePath, problemId) {
|
|
2213
|
-
if (!
|
|
2625
|
+
if (!existsSync8(notePath)) {
|
|
2214
2626
|
const template = await generateNoteTemplate(problemId);
|
|
2215
2627
|
await writeFile3(notePath, template, "utf-8");
|
|
2216
|
-
console.log(
|
|
2628
|
+
console.log(chalk18.green(`\u2713 Created notes file for problem ${problemId}`));
|
|
2217
2629
|
}
|
|
2218
|
-
console.log(
|
|
2630
|
+
console.log(chalk18.gray(`Opening: ${notePath}`));
|
|
2219
2631
|
await openInEditor(notePath);
|
|
2220
2632
|
}
|
|
2221
2633
|
async function generateNoteTemplate(problemId) {
|
|
@@ -2272,7 +2684,7 @@ async function generateNoteTemplate(problemId) {
|
|
|
2272
2684
|
}
|
|
2273
2685
|
|
|
2274
2686
|
// src/commands/today.ts
|
|
2275
|
-
import
|
|
2687
|
+
import chalk19 from "chalk";
|
|
2276
2688
|
import ora12 from "ora";
|
|
2277
2689
|
async function todayCommand() {
|
|
2278
2690
|
const { authorized, username } = await requireAuth();
|
|
@@ -2287,48 +2699,48 @@ async function todayCommand() {
|
|
|
2287
2699
|
]);
|
|
2288
2700
|
spinner.stop();
|
|
2289
2701
|
console.log();
|
|
2290
|
-
console.log(
|
|
2291
|
-
console.log(
|
|
2702
|
+
console.log(chalk19.bold.cyan(`\u{1F4CA} Today's Summary`), chalk19.gray(`- ${username}`));
|
|
2703
|
+
console.log(chalk19.gray("\u2500".repeat(50)));
|
|
2292
2704
|
console.log();
|
|
2293
|
-
console.log(
|
|
2294
|
-
console.log(
|
|
2705
|
+
console.log(chalk19.yellow(`\u{1F525} Current Streak: ${profile.streak} day${profile.streak !== 1 ? "s" : ""}`));
|
|
2706
|
+
console.log(chalk19.gray(` Total Active Days: ${profile.totalActiveDays}`));
|
|
2295
2707
|
console.log();
|
|
2296
2708
|
const total = profile.acSubmissionNum.find((s) => s.difficulty === "All");
|
|
2297
2709
|
const easy = profile.acSubmissionNum.find((s) => s.difficulty === "Easy");
|
|
2298
2710
|
const medium = profile.acSubmissionNum.find((s) => s.difficulty === "Medium");
|
|
2299
2711
|
const hard = profile.acSubmissionNum.find((s) => s.difficulty === "Hard");
|
|
2300
|
-
console.log(
|
|
2301
|
-
console.log(` ${
|
|
2302
|
-
console.log(` ${
|
|
2712
|
+
console.log(chalk19.white("\u{1F4C8} Problems Solved:"));
|
|
2713
|
+
console.log(` ${chalk19.green("Easy")}: ${easy?.count ?? 0} | ${chalk19.yellow("Medium")}: ${medium?.count ?? 0} | ${chalk19.red("Hard")}: ${hard?.count ?? 0}`);
|
|
2714
|
+
console.log(` ${chalk19.bold("Total")}: ${total?.count ?? 0}`);
|
|
2303
2715
|
console.log();
|
|
2304
|
-
console.log(
|
|
2716
|
+
console.log(chalk19.bold.yellow("\u{1F3AF} Today's Challenge:"));
|
|
2305
2717
|
console.log(` ${daily.question.questionFrontendId}. ${daily.question.title}`);
|
|
2306
2718
|
console.log(` ${colorDifficulty3(daily.question.difficulty)}`);
|
|
2307
2719
|
const status = daily.question.status;
|
|
2308
2720
|
if (status === "ac") {
|
|
2309
|
-
console.log(
|
|
2721
|
+
console.log(chalk19.green(" \u2713 Completed!"));
|
|
2310
2722
|
} else if (status === "notac") {
|
|
2311
|
-
console.log(
|
|
2723
|
+
console.log(chalk19.yellow(" \u25CB Attempted"));
|
|
2312
2724
|
} else {
|
|
2313
|
-
console.log(
|
|
2725
|
+
console.log(chalk19.gray(" - Not started"));
|
|
2314
2726
|
}
|
|
2315
2727
|
console.log();
|
|
2316
|
-
console.log(
|
|
2728
|
+
console.log(chalk19.gray(` leetcode pick ${daily.question.questionFrontendId} # Start working on it`));
|
|
2317
2729
|
} catch (error) {
|
|
2318
2730
|
spinner.fail("Failed to fetch progress");
|
|
2319
2731
|
if (error instanceof Error) {
|
|
2320
|
-
console.log(
|
|
2732
|
+
console.log(chalk19.red(error.message));
|
|
2321
2733
|
}
|
|
2322
2734
|
}
|
|
2323
2735
|
}
|
|
2324
2736
|
function colorDifficulty3(difficulty) {
|
|
2325
2737
|
switch (difficulty.toLowerCase()) {
|
|
2326
2738
|
case "easy":
|
|
2327
|
-
return
|
|
2739
|
+
return chalk19.green(difficulty);
|
|
2328
2740
|
case "medium":
|
|
2329
|
-
return
|
|
2741
|
+
return chalk19.yellow(difficulty);
|
|
2330
2742
|
case "hard":
|
|
2331
|
-
return
|
|
2743
|
+
return chalk19.red(difficulty);
|
|
2332
2744
|
default:
|
|
2333
2745
|
return difficulty;
|
|
2334
2746
|
}
|
|
@@ -2336,8 +2748,8 @@ function colorDifficulty3(difficulty) {
|
|
|
2336
2748
|
|
|
2337
2749
|
// src/commands/sync.ts
|
|
2338
2750
|
import { execSync } from "child_process";
|
|
2339
|
-
import { existsSync as
|
|
2340
|
-
import
|
|
2751
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2752
|
+
import chalk20 from "chalk";
|
|
2341
2753
|
import inquirer3 from "inquirer";
|
|
2342
2754
|
import ora13 from "ora";
|
|
2343
2755
|
function isGitInstalled() {
|
|
@@ -2382,7 +2794,7 @@ async function setupGitRepo(workDir) {
|
|
|
2382
2794
|
}
|
|
2383
2795
|
]);
|
|
2384
2796
|
if (!init) {
|
|
2385
|
-
console.log(
|
|
2797
|
+
console.log(chalk20.yellow("Skipping basic git initialization."));
|
|
2386
2798
|
return false;
|
|
2387
2799
|
}
|
|
2388
2800
|
const spinner = ora13("Initializing git repository...").start();
|
|
@@ -2421,12 +2833,12 @@ async function setupRemote(workDir) {
|
|
|
2421
2833
|
return repoUrl;
|
|
2422
2834
|
} catch (error) {
|
|
2423
2835
|
spinner.fail("Failed to create GitHub repository");
|
|
2424
|
-
console.log(
|
|
2836
|
+
console.log(chalk20.red(error));
|
|
2425
2837
|
}
|
|
2426
2838
|
}
|
|
2427
2839
|
}
|
|
2428
2840
|
if (!repoUrl) {
|
|
2429
|
-
console.log(
|
|
2841
|
+
console.log(chalk20.yellow("\nPlease create a new repository on your Git provider and copy the URL."));
|
|
2430
2842
|
const { url } = await inquirer3.prompt([
|
|
2431
2843
|
{
|
|
2432
2844
|
type: "input",
|
|
@@ -2445,21 +2857,21 @@ async function setupRemote(workDir) {
|
|
|
2445
2857
|
if (!currentRemote && repoUrl) {
|
|
2446
2858
|
try {
|
|
2447
2859
|
execSync(`git remote add origin ${repoUrl}`, { cwd: workDir });
|
|
2448
|
-
console.log(
|
|
2860
|
+
console.log(chalk20.green("\u2713 Added remote origin"));
|
|
2449
2861
|
} catch (e) {
|
|
2450
|
-
console.log(
|
|
2862
|
+
console.log(chalk20.red("Failed to add remote origin"));
|
|
2451
2863
|
}
|
|
2452
2864
|
}
|
|
2453
2865
|
return repoUrl || "";
|
|
2454
2866
|
}
|
|
2455
2867
|
async function syncCommand() {
|
|
2456
2868
|
const workDir = config.getWorkDir();
|
|
2457
|
-
if (!
|
|
2458
|
-
console.log(
|
|
2869
|
+
if (!existsSync9(workDir)) {
|
|
2870
|
+
console.log(chalk20.red(`Work directory does not exist: ${workDir}`));
|
|
2459
2871
|
return;
|
|
2460
2872
|
}
|
|
2461
2873
|
if (!isGitInstalled()) {
|
|
2462
|
-
console.log(
|
|
2874
|
+
console.log(chalk20.red("Git is not installed. Please install Git to use command."));
|
|
2463
2875
|
return;
|
|
2464
2876
|
}
|
|
2465
2877
|
if (!isMapRepo(workDir)) {
|
|
@@ -2493,14 +2905,14 @@ async function syncCommand() {
|
|
|
2493
2905
|
} catch (error) {
|
|
2494
2906
|
spinner.fail("Sync failed");
|
|
2495
2907
|
if (error.message) {
|
|
2496
|
-
console.log(
|
|
2908
|
+
console.log(chalk20.red(error.message));
|
|
2497
2909
|
}
|
|
2498
2910
|
}
|
|
2499
2911
|
}
|
|
2500
2912
|
|
|
2501
2913
|
// src/commands/timer.ts
|
|
2502
2914
|
import ora14 from "ora";
|
|
2503
|
-
import
|
|
2915
|
+
import chalk21 from "chalk";
|
|
2504
2916
|
var DEFAULT_TIMES = {
|
|
2505
2917
|
Easy: 20,
|
|
2506
2918
|
Medium: 40,
|
|
@@ -2529,10 +2941,10 @@ async function timerCommand(idOrSlug, options) {
|
|
|
2529
2941
|
return;
|
|
2530
2942
|
}
|
|
2531
2943
|
if (!idOrSlug) {
|
|
2532
|
-
console.log(
|
|
2533
|
-
console.log(
|
|
2534
|
-
console.log(
|
|
2535
|
-
console.log(
|
|
2944
|
+
console.log(chalk21.yellow("Please provide a problem ID to start the timer."));
|
|
2945
|
+
console.log(chalk21.gray("Usage: leetcode timer <id>"));
|
|
2946
|
+
console.log(chalk21.gray(" leetcode timer --stats"));
|
|
2947
|
+
console.log(chalk21.gray(" leetcode timer --stop"));
|
|
2536
2948
|
return;
|
|
2537
2949
|
}
|
|
2538
2950
|
const { authorized } = await requireAuth();
|
|
@@ -2541,11 +2953,11 @@ async function timerCommand(idOrSlug, options) {
|
|
|
2541
2953
|
if (activeTimer) {
|
|
2542
2954
|
const startedAt = new Date(activeTimer.startedAt);
|
|
2543
2955
|
const elapsed = Math.floor((Date.now() - startedAt.getTime()) / 1e3);
|
|
2544
|
-
console.log(
|
|
2545
|
-
console.log(
|
|
2546
|
-
console.log(
|
|
2956
|
+
console.log(chalk21.yellow("\u26A0\uFE0F You have an active timer running:"));
|
|
2957
|
+
console.log(chalk21.white(` Problem: ${activeTimer.title}`));
|
|
2958
|
+
console.log(chalk21.white(` Elapsed: ${formatDuration(elapsed)}`));
|
|
2547
2959
|
console.log();
|
|
2548
|
-
console.log(
|
|
2960
|
+
console.log(chalk21.gray("Use `leetcode timer --stop` to stop it first."));
|
|
2549
2961
|
return;
|
|
2550
2962
|
}
|
|
2551
2963
|
const spinner = ora14("Fetching problem...").start();
|
|
@@ -2569,65 +2981,65 @@ async function timerCommand(idOrSlug, options) {
|
|
|
2569
2981
|
durationMinutes
|
|
2570
2982
|
);
|
|
2571
2983
|
console.log();
|
|
2572
|
-
console.log(
|
|
2573
|
-
console.log(
|
|
2984
|
+
console.log(chalk21.bold.cyan("\u23F1\uFE0F Interview Mode Started!"));
|
|
2985
|
+
console.log(chalk21.gray("\u2500".repeat(50)));
|
|
2574
2986
|
console.log();
|
|
2575
|
-
console.log(
|
|
2576
|
-
console.log(
|
|
2577
|
-
console.log(
|
|
2987
|
+
console.log(chalk21.white(`Problem: ${problem.questionFrontendId}. ${problem.title}`));
|
|
2988
|
+
console.log(chalk21.white(`Difficulty: ${chalk21.bold(problem.difficulty)}`));
|
|
2989
|
+
console.log(chalk21.white(`Time Limit: ${chalk21.bold.yellow(durationMinutes + " minutes")}`));
|
|
2578
2990
|
console.log();
|
|
2579
|
-
console.log(
|
|
2580
|
-
console.log(
|
|
2581
|
-
console.log(
|
|
2582
|
-
console.log(
|
|
2991
|
+
console.log(chalk21.gray("\u2500".repeat(50)));
|
|
2992
|
+
console.log(chalk21.green("\u2713 Timer is running in background"));
|
|
2993
|
+
console.log(chalk21.gray(" When you submit successfully, your time will be recorded."));
|
|
2994
|
+
console.log(chalk21.gray(" Use `leetcode timer --stop` to cancel."));
|
|
2583
2995
|
console.log();
|
|
2584
2996
|
await pickCommand(idOrSlug, { open: true });
|
|
2585
2997
|
} catch (error) {
|
|
2586
2998
|
spinner.fail("Failed to start timer");
|
|
2587
2999
|
if (error instanceof Error) {
|
|
2588
|
-
console.log(
|
|
3000
|
+
console.log(chalk21.red(error.message));
|
|
2589
3001
|
}
|
|
2590
3002
|
}
|
|
2591
3003
|
}
|
|
2592
3004
|
async function stopActiveTimer() {
|
|
2593
3005
|
const result = timerStorage.stopTimer();
|
|
2594
3006
|
if (!result) {
|
|
2595
|
-
console.log(
|
|
3007
|
+
console.log(chalk21.yellow("No active timer to stop."));
|
|
2596
3008
|
return;
|
|
2597
3009
|
}
|
|
2598
|
-
console.log(
|
|
2599
|
-
console.log(
|
|
2600
|
-
console.log(
|
|
3010
|
+
console.log(chalk21.green("\u23F1\uFE0F Timer stopped."));
|
|
3011
|
+
console.log(chalk21.gray(`Elapsed time: ${formatDuration(result.durationSeconds)}`));
|
|
3012
|
+
console.log(chalk21.gray("(Time not recorded since problem was not submitted)"));
|
|
2601
3013
|
}
|
|
2602
3014
|
async function showTimerStats(problemId) {
|
|
2603
3015
|
if (problemId && /^\d+$/.test(problemId)) {
|
|
2604
3016
|
const times = timerStorage.getSolveTimes(problemId);
|
|
2605
3017
|
if (times.length === 0) {
|
|
2606
|
-
console.log(
|
|
3018
|
+
console.log(chalk21.yellow(`No solve times recorded for problem ${problemId}`));
|
|
2607
3019
|
return;
|
|
2608
3020
|
}
|
|
2609
3021
|
console.log();
|
|
2610
|
-
console.log(
|
|
2611
|
-
console.log(
|
|
3022
|
+
console.log(chalk21.bold(`\u23F1\uFE0F Solve Times for Problem ${problemId}`));
|
|
3023
|
+
console.log(chalk21.gray("\u2500".repeat(40)));
|
|
2612
3024
|
for (const entry of times) {
|
|
2613
3025
|
const date = new Date(entry.solvedAt).toLocaleDateString();
|
|
2614
3026
|
const duration = formatDuration(entry.durationSeconds);
|
|
2615
3027
|
const limit = entry.timerMinutes;
|
|
2616
3028
|
const withinLimit = entry.durationSeconds <= limit * 60;
|
|
2617
3029
|
console.log(
|
|
2618
|
-
` ${date} ${withinLimit ?
|
|
3030
|
+
` ${date} ${withinLimit ? chalk21.green(duration) : chalk21.red(duration)} (limit: ${limit}m)`
|
|
2619
3031
|
);
|
|
2620
3032
|
}
|
|
2621
3033
|
} else {
|
|
2622
3034
|
const stats = timerStorage.getStats();
|
|
2623
3035
|
const allTimes = timerStorage.getAllSolveTimes();
|
|
2624
3036
|
console.log();
|
|
2625
|
-
console.log(
|
|
2626
|
-
console.log(
|
|
3037
|
+
console.log(chalk21.bold("\u23F1\uFE0F Timer Statistics"));
|
|
3038
|
+
console.log(chalk21.gray("\u2500".repeat(40)));
|
|
2627
3039
|
console.log();
|
|
2628
|
-
console.log(` Problems timed: ${
|
|
2629
|
-
console.log(` Total time: ${
|
|
2630
|
-
console.log(` Average time: ${
|
|
3040
|
+
console.log(` Problems timed: ${chalk21.cyan(stats.totalProblems)}`);
|
|
3041
|
+
console.log(` Total time: ${chalk21.cyan(formatDuration(stats.totalTime))}`);
|
|
3042
|
+
console.log(` Average time: ${chalk21.cyan(formatDuration(stats.avgTime))}`);
|
|
2631
3043
|
console.log();
|
|
2632
3044
|
const recentSolves = [];
|
|
2633
3045
|
for (const [id, times] of Object.entries(allTimes)) {
|
|
@@ -2642,11 +3054,11 @@ async function showTimerStats(problemId) {
|
|
|
2642
3054
|
}
|
|
2643
3055
|
if (recentSolves.length > 0) {
|
|
2644
3056
|
recentSolves.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
2645
|
-
console.log(
|
|
3057
|
+
console.log(chalk21.bold(" Recent Solves:"));
|
|
2646
3058
|
for (const solve of recentSolves.slice(0, 5)) {
|
|
2647
3059
|
const date = new Date(solve.date).toLocaleDateString();
|
|
2648
3060
|
console.log(
|
|
2649
|
-
|
|
3061
|
+
chalk21.gray(` ${date} `) + chalk21.white(`${solve.problemId}. ${solve.title.substring(0, 25)}`) + chalk21.gray(" ") + chalk21.cyan(formatDuration(solve.duration))
|
|
2650
3062
|
);
|
|
2651
3063
|
}
|
|
2652
3064
|
}
|
|
@@ -2654,162 +3066,1048 @@ async function showTimerStats(problemId) {
|
|
|
2654
3066
|
}
|
|
2655
3067
|
}
|
|
2656
3068
|
|
|
3069
|
+
// src/commands/collab.ts
|
|
3070
|
+
import chalk22 from "chalk";
|
|
3071
|
+
import ora15 from "ora";
|
|
3072
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
3073
|
+
|
|
3074
|
+
// src/services/supabase.ts
|
|
3075
|
+
import { createClient } from "@supabase/supabase-js";
|
|
3076
|
+
var SUPABASE_URL = "https://abagrmwdpvnfyuqizyym.supabase.co";
|
|
3077
|
+
var SUPABASE_ANON_KEY = "sb_publishable_indrKu8VJmASdyLp7w8Hog_OyqT17cV";
|
|
3078
|
+
var supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
|
|
3079
|
+
|
|
3080
|
+
// src/storage/collab.ts
|
|
3081
|
+
import { existsSync as existsSync10, readFileSync as readFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
3082
|
+
import { dirname as dirname2 } from "path";
|
|
3083
|
+
function getCollabPath() {
|
|
3084
|
+
return workspaceStorage.getCollabPath();
|
|
3085
|
+
}
|
|
3086
|
+
function loadCollab() {
|
|
3087
|
+
const path = getCollabPath();
|
|
3088
|
+
if (existsSync10(path)) {
|
|
3089
|
+
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
3090
|
+
}
|
|
3091
|
+
return { session: null };
|
|
3092
|
+
}
|
|
3093
|
+
function saveCollab(data) {
|
|
3094
|
+
const collabPath = getCollabPath();
|
|
3095
|
+
const dir = dirname2(collabPath);
|
|
3096
|
+
if (!existsSync10(dir)) {
|
|
3097
|
+
mkdirSync3(dir, { recursive: true });
|
|
3098
|
+
}
|
|
3099
|
+
writeFileSync4(collabPath, JSON.stringify(data, null, 2));
|
|
3100
|
+
}
|
|
3101
|
+
var collabStorage = {
|
|
3102
|
+
getSession() {
|
|
3103
|
+
return loadCollab().session;
|
|
3104
|
+
},
|
|
3105
|
+
setSession(session) {
|
|
3106
|
+
saveCollab({ session });
|
|
3107
|
+
},
|
|
3108
|
+
getPath() {
|
|
3109
|
+
return getCollabPath();
|
|
3110
|
+
}
|
|
3111
|
+
};
|
|
3112
|
+
|
|
3113
|
+
// src/services/collab.ts
|
|
3114
|
+
function generateRoomCode() {
|
|
3115
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
3116
|
+
let code = "";
|
|
3117
|
+
for (let i = 0; i < 6; i++) {
|
|
3118
|
+
code += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
3119
|
+
}
|
|
3120
|
+
return code;
|
|
3121
|
+
}
|
|
3122
|
+
var collabService = {
|
|
3123
|
+
getSession() {
|
|
3124
|
+
return collabStorage.getSession();
|
|
3125
|
+
},
|
|
3126
|
+
async createRoom(problemId, username) {
|
|
3127
|
+
const roomCode = generateRoomCode();
|
|
3128
|
+
const { error } = await supabase.from("collab_rooms").insert({
|
|
3129
|
+
room_code: roomCode,
|
|
3130
|
+
problem_id: problemId,
|
|
3131
|
+
host_username: username,
|
|
3132
|
+
host_code: "",
|
|
3133
|
+
guest_username: null,
|
|
3134
|
+
guest_code: null
|
|
3135
|
+
});
|
|
3136
|
+
if (error) {
|
|
3137
|
+
return { error: error.message };
|
|
3138
|
+
}
|
|
3139
|
+
collabStorage.setSession({
|
|
3140
|
+
roomCode,
|
|
3141
|
+
problemId,
|
|
3142
|
+
isHost: true,
|
|
3143
|
+
username
|
|
3144
|
+
});
|
|
3145
|
+
return { roomCode };
|
|
3146
|
+
},
|
|
3147
|
+
async joinRoom(roomCode, username) {
|
|
3148
|
+
const { data: room, error: fetchError } = await supabase.from("collab_rooms").select("*").eq("room_code", roomCode.toUpperCase()).single();
|
|
3149
|
+
if (fetchError || !room) {
|
|
3150
|
+
return { error: "Room not found" };
|
|
3151
|
+
}
|
|
3152
|
+
const { error: updateError } = await supabase.from("collab_rooms").update({ guest_username: username }).eq("room_code", roomCode.toUpperCase());
|
|
3153
|
+
if (updateError) {
|
|
3154
|
+
return { error: updateError.message };
|
|
3155
|
+
}
|
|
3156
|
+
collabStorage.setSession({
|
|
3157
|
+
roomCode: roomCode.toUpperCase(),
|
|
3158
|
+
problemId: room.problem_id,
|
|
3159
|
+
isHost: false,
|
|
3160
|
+
username
|
|
3161
|
+
});
|
|
3162
|
+
return { problemId: room.problem_id };
|
|
3163
|
+
},
|
|
3164
|
+
async syncCode(code) {
|
|
3165
|
+
const session = collabStorage.getSession();
|
|
3166
|
+
if (!session) {
|
|
3167
|
+
return { success: false, error: "No active session" };
|
|
3168
|
+
}
|
|
3169
|
+
const column = session.isHost ? "host_code" : "guest_code";
|
|
3170
|
+
const { error } = await supabase.from("collab_rooms").update({ [column]: code }).eq("room_code", session.roomCode);
|
|
3171
|
+
if (error) {
|
|
3172
|
+
return { success: false, error: error.message };
|
|
3173
|
+
}
|
|
3174
|
+
return { success: true };
|
|
3175
|
+
},
|
|
3176
|
+
async getPartnerCode() {
|
|
3177
|
+
const session = collabStorage.getSession();
|
|
3178
|
+
if (!session) {
|
|
3179
|
+
return { error: "No active session" };
|
|
3180
|
+
}
|
|
3181
|
+
const { data: room, error } = await supabase.from("collab_rooms").select("*").eq("room_code", session.roomCode).single();
|
|
3182
|
+
if (error || !room) {
|
|
3183
|
+
return { error: "Room not found" };
|
|
3184
|
+
}
|
|
3185
|
+
if (session.isHost) {
|
|
3186
|
+
return {
|
|
3187
|
+
code: room.guest_code || "",
|
|
3188
|
+
username: room.guest_username || "Partner"
|
|
3189
|
+
};
|
|
3190
|
+
} else {
|
|
3191
|
+
return {
|
|
3192
|
+
code: room.host_code || "",
|
|
3193
|
+
username: room.host_username || "Host"
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
},
|
|
3197
|
+
async getRoomStatus() {
|
|
3198
|
+
const session = collabStorage.getSession();
|
|
3199
|
+
if (!session) {
|
|
3200
|
+
return { error: "No active session" };
|
|
3201
|
+
}
|
|
3202
|
+
const { data: room, error } = await supabase.from("collab_rooms").select("*").eq("room_code", session.roomCode).single();
|
|
3203
|
+
if (error || !room) {
|
|
3204
|
+
return { error: "Room not found" };
|
|
3205
|
+
}
|
|
3206
|
+
return {
|
|
3207
|
+
host: room.host_username,
|
|
3208
|
+
guest: room.guest_username,
|
|
3209
|
+
hasHostCode: !!room.host_code,
|
|
3210
|
+
hasGuestCode: !!room.guest_code
|
|
3211
|
+
};
|
|
3212
|
+
},
|
|
3213
|
+
async leaveRoom() {
|
|
3214
|
+
const session = collabStorage.getSession();
|
|
3215
|
+
if (session) {
|
|
3216
|
+
if (session.isHost) {
|
|
3217
|
+
await supabase.from("collab_rooms").delete().eq("room_code", session.roomCode);
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
collabStorage.setSession(null);
|
|
3221
|
+
}
|
|
3222
|
+
};
|
|
3223
|
+
|
|
3224
|
+
// src/commands/collab.ts
|
|
3225
|
+
async function collabHostCommand(problemId) {
|
|
3226
|
+
const { authorized, username } = await requireAuth();
|
|
3227
|
+
if (!authorized || !username) return;
|
|
3228
|
+
const spinner = ora15("Creating collaboration room...").start();
|
|
3229
|
+
try {
|
|
3230
|
+
const result = await collabService.createRoom(problemId, username);
|
|
3231
|
+
if ("error" in result) {
|
|
3232
|
+
spinner.fail(result.error);
|
|
3233
|
+
return;
|
|
3234
|
+
}
|
|
3235
|
+
spinner.succeed("Room created!");
|
|
3236
|
+
console.log();
|
|
3237
|
+
console.log(chalk22.bold.cyan("\u{1F465} Collaborative Coding Session"));
|
|
3238
|
+
console.log(chalk22.gray("\u2500".repeat(50)));
|
|
3239
|
+
console.log();
|
|
3240
|
+
console.log(chalk22.white(`Room Code: ${chalk22.bold.green(result.roomCode)}`));
|
|
3241
|
+
console.log(chalk22.white(`Problem: ${problemId}`));
|
|
3242
|
+
console.log();
|
|
3243
|
+
console.log(chalk22.gray("Share this code with your partner:"));
|
|
3244
|
+
console.log(chalk22.yellow(` leetcode collab join ${result.roomCode}`));
|
|
3245
|
+
console.log();
|
|
3246
|
+
console.log(chalk22.gray("\u2500".repeat(50)));
|
|
3247
|
+
console.log(chalk22.gray("After solving, sync and compare:"));
|
|
3248
|
+
console.log(chalk22.gray(" leetcode collab sync - Upload your solution"));
|
|
3249
|
+
console.log(chalk22.gray(" leetcode collab compare - See both solutions"));
|
|
3250
|
+
console.log(chalk22.gray(" leetcode collab status - Check room status"));
|
|
3251
|
+
console.log(chalk22.gray(" leetcode collab leave - End session"));
|
|
3252
|
+
console.log();
|
|
3253
|
+
await pickCommand(problemId, { open: true });
|
|
3254
|
+
} catch (error) {
|
|
3255
|
+
spinner.fail("Failed to create room");
|
|
3256
|
+
if (error instanceof Error) {
|
|
3257
|
+
console.log(chalk22.red(error.message));
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
async function collabJoinCommand(roomCode) {
|
|
3262
|
+
const { authorized, username } = await requireAuth();
|
|
3263
|
+
if (!authorized || !username) return;
|
|
3264
|
+
const spinner = ora15(`Joining room ${roomCode}...`).start();
|
|
3265
|
+
try {
|
|
3266
|
+
const result = await collabService.joinRoom(roomCode.toUpperCase(), username);
|
|
3267
|
+
if ("error" in result) {
|
|
3268
|
+
spinner.fail(result.error);
|
|
3269
|
+
return;
|
|
3270
|
+
}
|
|
3271
|
+
spinner.succeed("Joined room!");
|
|
3272
|
+
console.log();
|
|
3273
|
+
console.log(chalk22.bold.cyan("\u{1F465} Collaborative Coding Session"));
|
|
3274
|
+
console.log(chalk22.gray("\u2500".repeat(50)));
|
|
3275
|
+
console.log();
|
|
3276
|
+
console.log(chalk22.white(`Room Code: ${chalk22.bold.green(roomCode.toUpperCase())}`));
|
|
3277
|
+
console.log(chalk22.white(`Problem: ${result.problemId}`));
|
|
3278
|
+
console.log();
|
|
3279
|
+
console.log(chalk22.gray("\u2500".repeat(50)));
|
|
3280
|
+
console.log(chalk22.gray("After solving, sync and compare:"));
|
|
3281
|
+
console.log(chalk22.gray(" leetcode collab sync - Upload your solution"));
|
|
3282
|
+
console.log(chalk22.gray(" leetcode collab compare - See both solutions"));
|
|
3283
|
+
console.log(chalk22.gray(" leetcode collab status - Check room status"));
|
|
3284
|
+
console.log(chalk22.gray(" leetcode collab leave - End session"));
|
|
3285
|
+
console.log();
|
|
3286
|
+
await pickCommand(result.problemId, { open: true });
|
|
3287
|
+
} catch (error) {
|
|
3288
|
+
spinner.fail("Failed to join room");
|
|
3289
|
+
if (error instanceof Error) {
|
|
3290
|
+
console.log(chalk22.red(error.message));
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
async function collabSyncCommand() {
|
|
3295
|
+
const session = collabService.getSession();
|
|
3296
|
+
if (!session) {
|
|
3297
|
+
console.log(chalk22.yellow("No active collaboration session."));
|
|
3298
|
+
console.log(chalk22.gray("Use `leetcode collab host <id>` or `leetcode collab join <code>` first."));
|
|
3299
|
+
return;
|
|
3300
|
+
}
|
|
3301
|
+
const spinner = ora15("Syncing your code...").start();
|
|
3302
|
+
const workDir = config.getWorkDir();
|
|
3303
|
+
const filePath = await findSolutionFile(workDir, session.problemId);
|
|
3304
|
+
if (!filePath) {
|
|
3305
|
+
spinner.fail(`No solution file found for problem ${session.problemId}`);
|
|
3306
|
+
return;
|
|
3307
|
+
}
|
|
3308
|
+
const code = await readFile4(filePath, "utf-8");
|
|
3309
|
+
const result = await collabService.syncCode(code);
|
|
3310
|
+
if (result.success) {
|
|
3311
|
+
spinner.succeed("Code synced successfully!");
|
|
3312
|
+
console.log(chalk22.gray(`Uploaded ${code.split("\n").length} lines from ${filePath}`));
|
|
3313
|
+
} else {
|
|
3314
|
+
spinner.fail(result.error || "Sync failed");
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
async function collabCompareCommand() {
|
|
3318
|
+
const session = collabService.getSession();
|
|
3319
|
+
if (!session) {
|
|
3320
|
+
console.log(chalk22.yellow("No active collaboration session."));
|
|
3321
|
+
console.log(chalk22.gray("Use `leetcode collab host <id>` or `leetcode collab join <code>` first."));
|
|
3322
|
+
return;
|
|
3323
|
+
}
|
|
3324
|
+
const spinner = ora15("Fetching solutions...").start();
|
|
3325
|
+
const workDir = config.getWorkDir();
|
|
3326
|
+
const filePath = await findSolutionFile(workDir, session.problemId);
|
|
3327
|
+
if (!filePath) {
|
|
3328
|
+
spinner.fail(`No solution file found for problem ${session.problemId}`);
|
|
3329
|
+
return;
|
|
3330
|
+
}
|
|
3331
|
+
const myCode = await readFile4(filePath, "utf-8");
|
|
3332
|
+
const partnerResult = await collabService.getPartnerCode();
|
|
3333
|
+
if ("error" in partnerResult) {
|
|
3334
|
+
spinner.fail(partnerResult.error);
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
spinner.stop();
|
|
3338
|
+
if (!partnerResult.code) {
|
|
3339
|
+
console.log(chalk22.yellow("Partner has not synced their code yet."));
|
|
3340
|
+
console.log(chalk22.gray("Ask them to run `leetcode collab sync`."));
|
|
3341
|
+
return;
|
|
3342
|
+
}
|
|
3343
|
+
console.log();
|
|
3344
|
+
console.log(chalk22.bold.cyan("\u{1F4CA} Solution Comparison"));
|
|
3345
|
+
console.log(chalk22.gray("\u2500".repeat(60)));
|
|
3346
|
+
console.log();
|
|
3347
|
+
console.log(chalk22.bold.green(`\u25B8 Your Solution (${session.username})`));
|
|
3348
|
+
console.log(chalk22.gray("\u2500".repeat(60)));
|
|
3349
|
+
const myLines = myCode.split("\n");
|
|
3350
|
+
for (let i = 0; i < myLines.length; i++) {
|
|
3351
|
+
const lineNum = String(i + 1).padStart(3, " ");
|
|
3352
|
+
console.log(`${chalk22.gray(lineNum)} ${myLines[i]}`);
|
|
3353
|
+
}
|
|
3354
|
+
console.log();
|
|
3355
|
+
console.log(chalk22.bold.blue(`\u25B8 ${partnerResult.username}'s Solution`));
|
|
3356
|
+
console.log(chalk22.gray("\u2500".repeat(60)));
|
|
3357
|
+
const partnerLines = partnerResult.code.split("\n");
|
|
3358
|
+
for (let i = 0; i < partnerLines.length; i++) {
|
|
3359
|
+
const lineNum = String(i + 1).padStart(3, " ");
|
|
3360
|
+
console.log(`${chalk22.gray(lineNum)} ${partnerLines[i]}`);
|
|
3361
|
+
}
|
|
3362
|
+
console.log();
|
|
3363
|
+
console.log(chalk22.gray("\u2500".repeat(60)));
|
|
3364
|
+
console.log(chalk22.gray(`Your code: ${myLines.length} lines | Partner: ${partnerLines.length} lines`));
|
|
3365
|
+
console.log();
|
|
3366
|
+
}
|
|
3367
|
+
async function collabLeaveCommand() {
|
|
3368
|
+
const session = collabService.getSession();
|
|
3369
|
+
if (!session) {
|
|
3370
|
+
console.log(chalk22.yellow("No active collaboration session."));
|
|
3371
|
+
return;
|
|
3372
|
+
}
|
|
3373
|
+
await collabService.leaveRoom();
|
|
3374
|
+
console.log(chalk22.green("\u2713 Left the collaboration session."));
|
|
3375
|
+
}
|
|
3376
|
+
async function collabStatusCommand() {
|
|
3377
|
+
const session = collabService.getSession();
|
|
3378
|
+
if (!session) {
|
|
3379
|
+
console.log(chalk22.yellow("No active collaboration session."));
|
|
3380
|
+
console.log(chalk22.gray("Use `leetcode collab host <id>` or `leetcode collab join <code>` to start."));
|
|
3381
|
+
return;
|
|
3382
|
+
}
|
|
3383
|
+
const status = await collabService.getRoomStatus();
|
|
3384
|
+
if ("error" in status) {
|
|
3385
|
+
console.log(chalk22.red(status.error));
|
|
3386
|
+
return;
|
|
3387
|
+
}
|
|
3388
|
+
console.log();
|
|
3389
|
+
console.log(chalk22.bold.cyan("\u{1F465} Collaboration Status"));
|
|
3390
|
+
console.log(chalk22.gray("\u2500".repeat(40)));
|
|
3391
|
+
console.log(` Room: ${chalk22.green(session.roomCode)}`);
|
|
3392
|
+
console.log(` Problem: ${session.problemId}`);
|
|
3393
|
+
console.log(` Role: ${session.isHost ? "Host" : "Guest"}`);
|
|
3394
|
+
console.log();
|
|
3395
|
+
console.log(chalk22.bold(" Participants:"));
|
|
3396
|
+
console.log(` Host: ${status.host} ${status.hasHostCode ? chalk22.green("\u2713 synced") : chalk22.gray("pending")}`);
|
|
3397
|
+
console.log(` Guest: ${status.guest || chalk22.gray("(waiting...)")} ${status.hasGuestCode ? chalk22.green("\u2713 synced") : chalk22.gray("pending")}`);
|
|
3398
|
+
console.log();
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
// src/commands/snapshot.ts
|
|
3402
|
+
import chalk23 from "chalk";
|
|
3403
|
+
|
|
3404
|
+
// src/storage/snapshots.ts
|
|
3405
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5, unlinkSync } from "fs";
|
|
3406
|
+
import { join as join8 } from "path";
|
|
3407
|
+
function getSnapshotsDir() {
|
|
3408
|
+
return workspaceStorage.getSnapshotsDir();
|
|
3409
|
+
}
|
|
3410
|
+
function getSnapshotDir(problemId) {
|
|
3411
|
+
return join8(getSnapshotsDir(), problemId);
|
|
3412
|
+
}
|
|
3413
|
+
function getMetaPath(problemId) {
|
|
3414
|
+
return join8(getSnapshotDir(problemId), "meta.json");
|
|
3415
|
+
}
|
|
3416
|
+
function getFilesDir(problemId) {
|
|
3417
|
+
return join8(getSnapshotDir(problemId), "files");
|
|
3418
|
+
}
|
|
3419
|
+
function ensureSnapshotDir(problemId) {
|
|
3420
|
+
const dir = getFilesDir(problemId);
|
|
3421
|
+
if (!existsSync11(dir)) {
|
|
3422
|
+
mkdirSync4(dir, { recursive: true });
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
function loadMeta(problemId) {
|
|
3426
|
+
const metaPath = getMetaPath(problemId);
|
|
3427
|
+
if (existsSync11(metaPath)) {
|
|
3428
|
+
return JSON.parse(readFileSync4(metaPath, "utf-8"));
|
|
3429
|
+
}
|
|
3430
|
+
return {
|
|
3431
|
+
problemId,
|
|
3432
|
+
problemTitle: "",
|
|
3433
|
+
snapshots: []
|
|
3434
|
+
};
|
|
3435
|
+
}
|
|
3436
|
+
function saveMeta(problemId, meta) {
|
|
3437
|
+
ensureSnapshotDir(problemId);
|
|
3438
|
+
writeFileSync5(getMetaPath(problemId), JSON.stringify(meta, null, 2));
|
|
3439
|
+
}
|
|
3440
|
+
var snapshotStorage = {
|
|
3441
|
+
/**
|
|
3442
|
+
* Save a snapshot of the solution
|
|
3443
|
+
*/
|
|
3444
|
+
save(problemId, problemTitle, code, language, name) {
|
|
3445
|
+
ensureSnapshotDir(problemId);
|
|
3446
|
+
const meta = loadMeta(problemId);
|
|
3447
|
+
if (problemTitle) {
|
|
3448
|
+
meta.problemTitle = problemTitle;
|
|
3449
|
+
}
|
|
3450
|
+
const nextId = meta.snapshots.length > 0 ? Math.max(...meta.snapshots.map((s) => s.id)) + 1 : 1;
|
|
3451
|
+
const snapshotName = name || `snapshot-${nextId}`;
|
|
3452
|
+
const existing = meta.snapshots.find((s) => s.name === snapshotName);
|
|
3453
|
+
if (existing) {
|
|
3454
|
+
return { error: `Snapshot with name "${snapshotName}" already exists (ID: ${existing.id})` };
|
|
3455
|
+
}
|
|
3456
|
+
const ext = LANGUAGE_EXTENSIONS[language] || language;
|
|
3457
|
+
const fileName = `${nextId}_${snapshotName}.${ext}`;
|
|
3458
|
+
const filePath = join8(getFilesDir(problemId), fileName);
|
|
3459
|
+
writeFileSync5(filePath, code, "utf-8");
|
|
3460
|
+
const snapshot = {
|
|
3461
|
+
id: nextId,
|
|
3462
|
+
name: snapshotName,
|
|
3463
|
+
fileName,
|
|
3464
|
+
language,
|
|
3465
|
+
lines: code.split("\n").length,
|
|
3466
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3467
|
+
};
|
|
3468
|
+
meta.snapshots.push(snapshot);
|
|
3469
|
+
saveMeta(problemId, meta);
|
|
3470
|
+
return snapshot;
|
|
3471
|
+
},
|
|
3472
|
+
/**
|
|
3473
|
+
* Get all snapshots for a problem
|
|
3474
|
+
*/
|
|
3475
|
+
list(problemId) {
|
|
3476
|
+
const meta = loadMeta(problemId);
|
|
3477
|
+
return meta.snapshots;
|
|
3478
|
+
},
|
|
3479
|
+
/**
|
|
3480
|
+
* Get snapshot metadata
|
|
3481
|
+
*/
|
|
3482
|
+
getMeta(problemId) {
|
|
3483
|
+
return loadMeta(problemId);
|
|
3484
|
+
},
|
|
3485
|
+
/**
|
|
3486
|
+
* Get a specific snapshot by ID or name
|
|
3487
|
+
*/
|
|
3488
|
+
get(problemId, idOrName) {
|
|
3489
|
+
const meta = loadMeta(problemId);
|
|
3490
|
+
const byId = meta.snapshots.find((s) => s.id === parseInt(idOrName, 10));
|
|
3491
|
+
if (byId) return byId;
|
|
3492
|
+
const byName = meta.snapshots.find((s) => s.name === idOrName);
|
|
3493
|
+
return byName || null;
|
|
3494
|
+
},
|
|
3495
|
+
/**
|
|
3496
|
+
* Get snapshot code content
|
|
3497
|
+
*/
|
|
3498
|
+
getCode(problemId, snapshot) {
|
|
3499
|
+
const filePath = join8(getFilesDir(problemId), snapshot.fileName);
|
|
3500
|
+
if (!existsSync11(filePath)) {
|
|
3501
|
+
throw new Error(`Snapshot file not found: ${snapshot.fileName}`);
|
|
3502
|
+
}
|
|
3503
|
+
return readFileSync4(filePath, "utf-8");
|
|
3504
|
+
},
|
|
3505
|
+
/**
|
|
3506
|
+
* Delete a snapshot
|
|
3507
|
+
*/
|
|
3508
|
+
delete(problemId, idOrName) {
|
|
3509
|
+
const meta = loadMeta(problemId);
|
|
3510
|
+
const snapshot = this.get(problemId, idOrName);
|
|
3511
|
+
if (!snapshot) {
|
|
3512
|
+
return false;
|
|
3513
|
+
}
|
|
3514
|
+
const filePath = join8(getFilesDir(problemId), snapshot.fileName);
|
|
3515
|
+
if (existsSync11(filePath)) {
|
|
3516
|
+
unlinkSync(filePath);
|
|
3517
|
+
}
|
|
3518
|
+
meta.snapshots = meta.snapshots.filter((s) => s.id !== snapshot.id);
|
|
3519
|
+
saveMeta(problemId, meta);
|
|
3520
|
+
return true;
|
|
3521
|
+
},
|
|
3522
|
+
/**
|
|
3523
|
+
* Check if snapshots exist for a problem
|
|
3524
|
+
*/
|
|
3525
|
+
hasSnapshots(problemId) {
|
|
3526
|
+
return this.list(problemId).length > 0;
|
|
3527
|
+
}
|
|
3528
|
+
};
|
|
3529
|
+
|
|
3530
|
+
// src/commands/snapshot.ts
|
|
3531
|
+
import { readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
3532
|
+
import { extname, basename as basename3 } from "path";
|
|
3533
|
+
import { diffLines } from "diff";
|
|
3534
|
+
function formatTimeAgo(dateStr) {
|
|
3535
|
+
const date = new Date(dateStr);
|
|
3536
|
+
const now = /* @__PURE__ */ new Date();
|
|
3537
|
+
const diffMs = now.getTime() - date.getTime();
|
|
3538
|
+
const diffMins = Math.floor(diffMs / 6e4);
|
|
3539
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
3540
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
3541
|
+
if (diffMins < 1) return "just now";
|
|
3542
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
3543
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
3544
|
+
return `${diffDays}d ago`;
|
|
3545
|
+
}
|
|
3546
|
+
async function snapshotSaveCommand(problemId, name) {
|
|
3547
|
+
const workDir = config.getWorkDir();
|
|
3548
|
+
try {
|
|
3549
|
+
const filePath = await findSolutionFile(workDir, problemId);
|
|
3550
|
+
if (!filePath) {
|
|
3551
|
+
console.log(chalk23.red(`No solution file found for problem ${problemId}`));
|
|
3552
|
+
console.log(chalk23.gray("Run `leetcode pick " + problemId + "` first to create a solution file."));
|
|
3553
|
+
return;
|
|
3554
|
+
}
|
|
3555
|
+
const code = await readFile5(filePath, "utf-8");
|
|
3556
|
+
const ext = extname(filePath).slice(1);
|
|
3557
|
+
const lang = getLangSlugFromExtension(ext) || ext;
|
|
3558
|
+
const fileName = basename3(filePath);
|
|
3559
|
+
const titleMatch = fileName.match(/^\d+\.(.+)\.\w+$/);
|
|
3560
|
+
const title = titleMatch ? titleMatch[1] : "";
|
|
3561
|
+
const result = snapshotStorage.save(problemId, title, code, lang, name);
|
|
3562
|
+
if ("error" in result) {
|
|
3563
|
+
console.log(chalk23.red("\u2717 " + result.error));
|
|
3564
|
+
return;
|
|
3565
|
+
}
|
|
3566
|
+
const snapshot = result;
|
|
3567
|
+
console.log(chalk23.green("\u2713 Snapshot saved!"));
|
|
3568
|
+
console.log();
|
|
3569
|
+
console.log(` ID: ${chalk23.cyan(snapshot.id)}`);
|
|
3570
|
+
console.log(` Name: ${chalk23.white(snapshot.name)}`);
|
|
3571
|
+
console.log(` Lines: ${chalk23.gray(snapshot.lines)}`);
|
|
3572
|
+
console.log(` File: ${chalk23.gray(filePath)}`);
|
|
3573
|
+
} catch (error) {
|
|
3574
|
+
console.log(chalk23.red("Failed to save snapshot"));
|
|
3575
|
+
if (error instanceof Error) {
|
|
3576
|
+
console.log(chalk23.gray(error.message));
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
async function snapshotListCommand(problemId) {
|
|
3581
|
+
const snapshots = snapshotStorage.list(problemId);
|
|
3582
|
+
if (snapshots.length === 0) {
|
|
3583
|
+
console.log(chalk23.yellow(`No snapshots found for problem ${problemId}`));
|
|
3584
|
+
console.log(chalk23.gray("Use `leetcode snapshot save " + problemId + "` to create one."));
|
|
3585
|
+
return;
|
|
3586
|
+
}
|
|
3587
|
+
const meta = snapshotStorage.getMeta(problemId);
|
|
3588
|
+
console.log();
|
|
3589
|
+
console.log(chalk23.bold(`\u{1F4F8} Snapshots for Problem ${problemId}`));
|
|
3590
|
+
if (meta.problemTitle) {
|
|
3591
|
+
console.log(chalk23.gray(` ${meta.problemTitle}`));
|
|
3592
|
+
}
|
|
3593
|
+
console.log(chalk23.gray("\u2500".repeat(50)));
|
|
3594
|
+
console.log();
|
|
3595
|
+
for (const snap of snapshots) {
|
|
3596
|
+
const timeAgo = formatTimeAgo(snap.createdAt);
|
|
3597
|
+
console.log(
|
|
3598
|
+
` ${chalk23.cyan(snap.id.toString().padStart(2))}. ${chalk23.white(snap.name.padEnd(25))} ${chalk23.gray(snap.lines + " lines")} ${chalk23.gray("\xB7")} ${chalk23.gray(timeAgo)}`
|
|
3599
|
+
);
|
|
3600
|
+
}
|
|
3601
|
+
console.log();
|
|
3602
|
+
console.log(chalk23.gray("Commands:"));
|
|
3603
|
+
console.log(chalk23.gray(` restore: leetcode snapshot restore ${problemId} <id|name>`));
|
|
3604
|
+
console.log(chalk23.gray(` diff: leetcode snapshot diff ${problemId} <id1> <id2>`));
|
|
3605
|
+
console.log(chalk23.gray(` delete: leetcode snapshot delete ${problemId} <id|name>`));
|
|
3606
|
+
}
|
|
3607
|
+
async function snapshotRestoreCommand(problemId, idOrName) {
|
|
3608
|
+
const workDir = config.getWorkDir();
|
|
3609
|
+
try {
|
|
3610
|
+
const snapshot = snapshotStorage.get(problemId, idOrName);
|
|
3611
|
+
if (!snapshot) {
|
|
3612
|
+
console.log(chalk23.red(`Snapshot "${idOrName}" not found for problem ${problemId}`));
|
|
3613
|
+
console.log(chalk23.gray("Run `leetcode snapshot list " + problemId + "` to see available snapshots."));
|
|
3614
|
+
return;
|
|
3615
|
+
}
|
|
3616
|
+
const filePath = await findSolutionFile(workDir, problemId);
|
|
3617
|
+
if (!filePath) {
|
|
3618
|
+
console.log(chalk23.red(`No solution file found for problem ${problemId}`));
|
|
3619
|
+
return;
|
|
3620
|
+
}
|
|
3621
|
+
const currentCode = await readFile5(filePath, "utf-8");
|
|
3622
|
+
const backupName = `backup-before-restore-${Date.now()}`;
|
|
3623
|
+
const ext = extname(filePath).slice(1);
|
|
3624
|
+
const lang = getLangSlugFromExtension(ext) || ext;
|
|
3625
|
+
snapshotStorage.save(problemId, "", currentCode, lang, backupName);
|
|
3626
|
+
const snapshotCode = snapshotStorage.getCode(problemId, snapshot);
|
|
3627
|
+
await writeFile4(filePath, snapshotCode, "utf-8");
|
|
3628
|
+
console.log(chalk23.green("\u2713 Snapshot restored!"));
|
|
3629
|
+
console.log();
|
|
3630
|
+
console.log(` Restored: ${chalk23.cyan(snapshot.name)} (${snapshot.lines} lines)`);
|
|
3631
|
+
console.log(` File: ${chalk23.gray(filePath)}`);
|
|
3632
|
+
console.log(` Backup: ${chalk23.gray(backupName)}`);
|
|
3633
|
+
} catch (error) {
|
|
3634
|
+
console.log(chalk23.red("Failed to restore snapshot"));
|
|
3635
|
+
if (error instanceof Error) {
|
|
3636
|
+
console.log(chalk23.gray(error.message));
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
async function snapshotDiffCommand(problemId, idOrName1, idOrName2) {
|
|
3641
|
+
try {
|
|
3642
|
+
const snap1 = snapshotStorage.get(problemId, idOrName1);
|
|
3643
|
+
const snap2 = snapshotStorage.get(problemId, idOrName2);
|
|
3644
|
+
if (!snap1) {
|
|
3645
|
+
console.log(chalk23.red(`Snapshot "${idOrName1}" not found`));
|
|
3646
|
+
return;
|
|
3647
|
+
}
|
|
3648
|
+
if (!snap2) {
|
|
3649
|
+
console.log(chalk23.red(`Snapshot "${idOrName2}" not found`));
|
|
3650
|
+
return;
|
|
3651
|
+
}
|
|
3652
|
+
const code1 = snapshotStorage.getCode(problemId, snap1);
|
|
3653
|
+
const code2 = snapshotStorage.getCode(problemId, snap2);
|
|
3654
|
+
console.log();
|
|
3655
|
+
console.log(chalk23.bold(`\u{1F4CA} Diff: ${snap1.name} \u2192 ${snap2.name}`));
|
|
3656
|
+
console.log(chalk23.gray("\u2500".repeat(50)));
|
|
3657
|
+
console.log();
|
|
3658
|
+
const diff = diffLines(code1, code2);
|
|
3659
|
+
let added = 0;
|
|
3660
|
+
let removed = 0;
|
|
3661
|
+
for (const part of diff) {
|
|
3662
|
+
const lines = part.value.split("\n").filter((l) => l !== "");
|
|
3663
|
+
if (part.added) {
|
|
3664
|
+
added += lines.length;
|
|
3665
|
+
for (const line of lines) {
|
|
3666
|
+
console.log(chalk23.green("+ " + line));
|
|
3667
|
+
}
|
|
3668
|
+
} else if (part.removed) {
|
|
3669
|
+
removed += lines.length;
|
|
3670
|
+
for (const line of lines) {
|
|
3671
|
+
console.log(chalk23.red("- " + line));
|
|
3672
|
+
}
|
|
3673
|
+
} else {
|
|
3674
|
+
if (lines.length <= 4) {
|
|
3675
|
+
for (const line of lines) {
|
|
3676
|
+
console.log(chalk23.gray(" " + line));
|
|
3677
|
+
}
|
|
3678
|
+
} else {
|
|
3679
|
+
console.log(chalk23.gray(" " + lines[0]));
|
|
3680
|
+
console.log(chalk23.gray(" " + lines[1]));
|
|
3681
|
+
console.log(chalk23.gray(` ... (${lines.length - 4} more lines)`));
|
|
3682
|
+
console.log(chalk23.gray(" " + lines[lines.length - 2]));
|
|
3683
|
+
console.log(chalk23.gray(" " + lines[lines.length - 1]));
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
console.log();
|
|
3688
|
+
console.log(chalk23.gray("\u2500".repeat(50)));
|
|
3689
|
+
console.log(
|
|
3690
|
+
`${chalk23.green("+" + added + " added")} ${chalk23.gray("\xB7")} ${chalk23.red("-" + removed + " removed")} ${chalk23.gray("\xB7")} ${chalk23.gray(snap1.lines + " \u2192 " + snap2.lines + " lines")}`
|
|
3691
|
+
);
|
|
3692
|
+
} catch (error) {
|
|
3693
|
+
console.log(chalk23.red("Failed to diff snapshots"));
|
|
3694
|
+
if (error instanceof Error) {
|
|
3695
|
+
console.log(chalk23.gray(error.message));
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
async function snapshotDeleteCommand(problemId, idOrName) {
|
|
3700
|
+
const snapshot = snapshotStorage.get(problemId, idOrName);
|
|
3701
|
+
if (!snapshot) {
|
|
3702
|
+
console.log(chalk23.red(`Snapshot "${idOrName}" not found for problem ${problemId}`));
|
|
3703
|
+
return;
|
|
3704
|
+
}
|
|
3705
|
+
const deleted = snapshotStorage.delete(problemId, idOrName);
|
|
3706
|
+
if (deleted) {
|
|
3707
|
+
console.log(chalk23.green(`\u2713 Deleted snapshot: ${snapshot.name}`));
|
|
3708
|
+
} else {
|
|
3709
|
+
console.log(chalk23.red("Failed to delete snapshot"));
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3713
|
+
// src/commands/diff.ts
|
|
3714
|
+
import ora16 from "ora";
|
|
3715
|
+
import chalk24 from "chalk";
|
|
3716
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
3717
|
+
import { existsSync as existsSync12 } from "fs";
|
|
3718
|
+
import { diffLines as diffLines2 } from "diff";
|
|
3719
|
+
function showCodeBlock(code, label) {
|
|
3720
|
+
const lines = code.split("\n");
|
|
3721
|
+
const lineCount = lines.length;
|
|
3722
|
+
console.log();
|
|
3723
|
+
console.log(chalk24.bold.cyan(`=== ${label} (${lineCount} lines) ===`));
|
|
3724
|
+
console.log(chalk24.gray("\u2500".repeat(60)));
|
|
3725
|
+
lines.forEach((line, i) => {
|
|
3726
|
+
const lineNum = (i + 1).toString().padStart(3);
|
|
3727
|
+
console.log(chalk24.gray(lineNum + " \u2502 ") + line);
|
|
3728
|
+
});
|
|
3729
|
+
}
|
|
3730
|
+
function showUnifiedDiff(sourceCode, targetCode, sourceLabel, targetLabel) {
|
|
3731
|
+
const sourceLines = sourceCode.split("\n").length;
|
|
3732
|
+
const targetLines = targetCode.split("\n").length;
|
|
3733
|
+
console.log();
|
|
3734
|
+
console.log(chalk24.bold(`\u{1F4CA} Unified Diff: ${sourceLabel} \u2192 ${targetLabel}`));
|
|
3735
|
+
console.log(chalk24.gray("\u2500".repeat(60)));
|
|
3736
|
+
console.log();
|
|
3737
|
+
const diff = diffLines2(sourceCode, targetCode);
|
|
3738
|
+
let added = 0;
|
|
3739
|
+
let removed = 0;
|
|
3740
|
+
for (const part of diff) {
|
|
3741
|
+
const lines = part.value.split("\n").filter((l) => l !== "");
|
|
3742
|
+
if (part.added) {
|
|
3743
|
+
added += lines.length;
|
|
3744
|
+
for (const line of lines) {
|
|
3745
|
+
console.log(chalk24.green("+ " + line));
|
|
3746
|
+
}
|
|
3747
|
+
} else if (part.removed) {
|
|
3748
|
+
removed += lines.length;
|
|
3749
|
+
for (const line of lines) {
|
|
3750
|
+
console.log(chalk24.red("- " + line));
|
|
3751
|
+
}
|
|
3752
|
+
} else {
|
|
3753
|
+
if (lines.length <= 6) {
|
|
3754
|
+
for (const line of lines) {
|
|
3755
|
+
console.log(chalk24.gray(" " + line));
|
|
3756
|
+
}
|
|
3757
|
+
} else {
|
|
3758
|
+
console.log(chalk24.gray(" " + lines[0]));
|
|
3759
|
+
console.log(chalk24.gray(" " + lines[1]));
|
|
3760
|
+
console.log(chalk24.dim(` ... (${lines.length - 4} unchanged lines)`));
|
|
3761
|
+
console.log(chalk24.gray(" " + lines[lines.length - 2]));
|
|
3762
|
+
console.log(chalk24.gray(" " + lines[lines.length - 1]));
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
console.log();
|
|
3767
|
+
console.log(chalk24.gray("\u2500".repeat(60)));
|
|
3768
|
+
console.log(
|
|
3769
|
+
`${chalk24.green("+" + added + " added")} ${chalk24.gray("\xB7")} ${chalk24.red("-" + removed + " removed")} ${chalk24.gray("\xB7")} ${chalk24.gray(sourceLines + " \u2192 " + targetLines + " lines")}`
|
|
3770
|
+
);
|
|
3771
|
+
}
|
|
3772
|
+
function showComparison(sourceCode, targetCode, sourceLabel, targetLabel, unified) {
|
|
3773
|
+
if (unified) {
|
|
3774
|
+
showUnifiedDiff(sourceCode, targetCode, sourceLabel, targetLabel);
|
|
3775
|
+
} else {
|
|
3776
|
+
showCodeBlock(sourceCode, sourceLabel);
|
|
3777
|
+
showCodeBlock(targetCode, targetLabel);
|
|
3778
|
+
const diff = diffLines2(sourceCode, targetCode);
|
|
3779
|
+
let added = 0;
|
|
3780
|
+
let removed = 0;
|
|
3781
|
+
for (const part of diff) {
|
|
3782
|
+
const lines = part.value.split("\n").filter((l) => l !== "");
|
|
3783
|
+
if (part.added) added += lines.length;
|
|
3784
|
+
else if (part.removed) removed += lines.length;
|
|
3785
|
+
}
|
|
3786
|
+
console.log();
|
|
3787
|
+
console.log(chalk24.gray("\u2500".repeat(60)));
|
|
3788
|
+
console.log(
|
|
3789
|
+
`${chalk24.bold("Summary:")} ${chalk24.green("+" + added + " added")} ${chalk24.gray("\xB7")} ${chalk24.red("-" + removed + " removed")}`
|
|
3790
|
+
);
|
|
3791
|
+
console.log(chalk24.gray("Tip: Use --unified for line-by-line diff"));
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
async function diffCommand(problemId, options) {
|
|
3795
|
+
const { authorized } = await requireAuth();
|
|
3796
|
+
if (!authorized) return;
|
|
3797
|
+
const workDir = config.getWorkDir();
|
|
3798
|
+
const spinner = ora16("Finding solution file...").start();
|
|
3799
|
+
try {
|
|
3800
|
+
const filePath = await findSolutionFile(workDir, problemId);
|
|
3801
|
+
if (!filePath) {
|
|
3802
|
+
spinner.fail(`No solution file found for problem ${problemId}`);
|
|
3803
|
+
console.log(chalk24.gray("Run `leetcode pick " + problemId + "` first to create a solution file."));
|
|
3804
|
+
return;
|
|
3805
|
+
}
|
|
3806
|
+
const currentCode = await readFile6(filePath, "utf-8");
|
|
3807
|
+
spinner.text = "Fetching comparison target...";
|
|
3808
|
+
if (options.file) {
|
|
3809
|
+
spinner.stop();
|
|
3810
|
+
if (!existsSync12(options.file)) {
|
|
3811
|
+
console.log(chalk24.red(`File not found: ${options.file}`));
|
|
3812
|
+
return;
|
|
3813
|
+
}
|
|
3814
|
+
const otherCode = await readFile6(options.file, "utf-8");
|
|
3815
|
+
showComparison(currentCode, otherCode, "Your Solution", options.file, options.unified ?? false);
|
|
3816
|
+
return;
|
|
3817
|
+
}
|
|
3818
|
+
const problem = await leetcodeClient.getProblemById(problemId);
|
|
3819
|
+
if (!problem) {
|
|
3820
|
+
spinner.fail(`Problem ${problemId} not found`);
|
|
3821
|
+
return;
|
|
3822
|
+
}
|
|
3823
|
+
if (options.submission) {
|
|
3824
|
+
const submissionId = parseInt(options.submission, 10);
|
|
3825
|
+
const submission = await leetcodeClient.getSubmissionDetails(submissionId);
|
|
3826
|
+
spinner.stop();
|
|
3827
|
+
showComparison(currentCode, submission.code, "Your Solution", `Submission #${submissionId}`, options.unified ?? false);
|
|
3828
|
+
return;
|
|
3829
|
+
}
|
|
3830
|
+
const submissions = await leetcodeClient.getSubmissionList(problem.titleSlug, 50);
|
|
3831
|
+
const accepted = submissions.find((s) => s.statusDisplay === "Accepted");
|
|
3832
|
+
if (!accepted) {
|
|
3833
|
+
spinner.fail("No accepted submissions found for this problem");
|
|
3834
|
+
console.log(chalk24.gray("Tip: Use --file to compare with a local file instead"));
|
|
3835
|
+
return;
|
|
3836
|
+
}
|
|
3837
|
+
const acceptedDetails = await leetcodeClient.getSubmissionDetails(parseInt(accepted.id, 10));
|
|
3838
|
+
spinner.stop();
|
|
3839
|
+
showComparison(currentCode, acceptedDetails.code, "Your Solution", "Last Accepted", options.unified ?? false);
|
|
3840
|
+
} catch (error) {
|
|
3841
|
+
spinner.fail("Failed to diff");
|
|
3842
|
+
if (error instanceof Error) {
|
|
3843
|
+
console.log(chalk24.red(error.message));
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3848
|
+
// src/commands/workspace.ts
|
|
3849
|
+
import chalk25 from "chalk";
|
|
3850
|
+
import inquirer4 from "inquirer";
|
|
3851
|
+
import { homedir as homedir3 } from "os";
|
|
3852
|
+
import { join as join9 } from "path";
|
|
3853
|
+
async function workspaceCurrentCommand() {
|
|
3854
|
+
const active = workspaceStorage.getActive();
|
|
3855
|
+
const config2 = workspaceStorage.getConfig(active);
|
|
3856
|
+
console.log();
|
|
3857
|
+
console.log(chalk25.bold.cyan(`\u{1F4C1} Active Workspace: ${active}`));
|
|
3858
|
+
console.log(chalk25.gray("\u2500".repeat(40)));
|
|
3859
|
+
console.log(` workDir: ${chalk25.white(config2.workDir)}`);
|
|
3860
|
+
console.log(` lang: ${chalk25.white(config2.lang)}`);
|
|
3861
|
+
if (config2.editor) console.log(` editor: ${chalk25.white(config2.editor)}`);
|
|
3862
|
+
if (config2.syncRepo) console.log(` syncRepo: ${chalk25.white(config2.syncRepo)}`);
|
|
3863
|
+
console.log();
|
|
3864
|
+
}
|
|
3865
|
+
async function workspaceListCommand() {
|
|
3866
|
+
const workspaces = workspaceStorage.list();
|
|
3867
|
+
const active = workspaceStorage.getActive();
|
|
3868
|
+
console.log();
|
|
3869
|
+
console.log(chalk25.bold("Workspaces:"));
|
|
3870
|
+
console.log();
|
|
3871
|
+
for (const ws of workspaces) {
|
|
3872
|
+
const config2 = workspaceStorage.getConfig(ws);
|
|
3873
|
+
const marker = ws === active ? chalk25.green("\u25B8 ") : " ";
|
|
3874
|
+
const name = ws === active ? chalk25.green.bold(ws) : ws;
|
|
3875
|
+
console.log(`${marker}${name}`);
|
|
3876
|
+
console.log(` ${chalk25.gray(config2.workDir)}`);
|
|
3877
|
+
}
|
|
3878
|
+
console.log();
|
|
3879
|
+
}
|
|
3880
|
+
async function workspaceCreateCommand(name, options) {
|
|
3881
|
+
if (workspaceStorage.exists(name)) {
|
|
3882
|
+
console.log(chalk25.red(`Workspace "${name}" already exists`));
|
|
3883
|
+
return;
|
|
3884
|
+
}
|
|
3885
|
+
const workDir = options.workdir ?? join9(homedir3(), "leetcode", name);
|
|
3886
|
+
const config2 = {
|
|
3887
|
+
workDir,
|
|
3888
|
+
lang: "typescript"
|
|
3889
|
+
};
|
|
3890
|
+
const success = workspaceStorage.create(name, config2);
|
|
3891
|
+
if (success) {
|
|
3892
|
+
console.log(chalk25.green(`\u2713 Created workspace "${name}"`));
|
|
3893
|
+
console.log(` workDir: ${chalk25.gray(workDir)}`);
|
|
3894
|
+
console.log();
|
|
3895
|
+
console.log(chalk25.gray(`Switch to it: leetcode workspace use ${name}`));
|
|
3896
|
+
} else {
|
|
3897
|
+
console.log(chalk25.red("Failed to create workspace"));
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
async function workspaceUseCommand(name) {
|
|
3901
|
+
if (!workspaceStorage.exists(name)) {
|
|
3902
|
+
console.log(chalk25.red(`Workspace "${name}" not found`));
|
|
3903
|
+
console.log(chalk25.gray("Use `leetcode workspace list` to see available workspaces"));
|
|
3904
|
+
return;
|
|
3905
|
+
}
|
|
3906
|
+
const success = workspaceStorage.setActive(name);
|
|
3907
|
+
if (success) {
|
|
3908
|
+
const config2 = workspaceStorage.getConfig(name);
|
|
3909
|
+
console.log(chalk25.green(`\u2713 Switched to workspace "${name}"`));
|
|
3910
|
+
console.log(` workDir: ${chalk25.gray(config2.workDir)}`);
|
|
3911
|
+
} else {
|
|
3912
|
+
console.log(chalk25.red("Failed to switch workspace"));
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
async function workspaceDeleteCommand(name) {
|
|
3916
|
+
if (name === "default") {
|
|
3917
|
+
console.log(chalk25.red("Cannot delete the default workspace"));
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3920
|
+
if (!workspaceStorage.exists(name)) {
|
|
3921
|
+
console.log(chalk25.red(`Workspace "${name}" not found`));
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
const { confirmed } = await inquirer4.prompt([{
|
|
3925
|
+
type: "confirm",
|
|
3926
|
+
name: "confirmed",
|
|
3927
|
+
message: `Delete workspace "${name}"? (files in workDir will NOT be deleted)`,
|
|
3928
|
+
default: false
|
|
3929
|
+
}]);
|
|
3930
|
+
if (!confirmed) {
|
|
3931
|
+
console.log(chalk25.gray("Cancelled"));
|
|
3932
|
+
return;
|
|
3933
|
+
}
|
|
3934
|
+
const success = workspaceStorage.delete(name);
|
|
3935
|
+
if (success) {
|
|
3936
|
+
console.log(chalk25.green(`\u2713 Deleted workspace "${name}"`));
|
|
3937
|
+
} else {
|
|
3938
|
+
console.log(chalk25.red("Failed to delete workspace"));
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
|
|
2657
3942
|
// src/index.ts
|
|
2658
3943
|
var program = new Command();
|
|
2659
3944
|
program.configureHelp({
|
|
2660
3945
|
sortSubcommands: true,
|
|
2661
|
-
subcommandTerm: (cmd) =>
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
3946
|
+
subcommandTerm: (cmd) => {
|
|
3947
|
+
const name = cmd.name();
|
|
3948
|
+
const alias = cmd.alias();
|
|
3949
|
+
const term = alias ? `${name}|${alias}` : name;
|
|
3950
|
+
return chalk26.cyan(term.padEnd(16));
|
|
3951
|
+
},
|
|
3952
|
+
subcommandDescription: (cmd) => chalk26.white(cmd.description()),
|
|
3953
|
+
optionTerm: (option) => chalk26.yellow(option.flags),
|
|
3954
|
+
optionDescription: (option) => chalk26.white(option.description)
|
|
2665
3955
|
});
|
|
2666
|
-
program.name("leetcode").usage("[command] [options]").description(
|
|
2667
|
-
${
|
|
2668
|
-
${
|
|
2669
|
-
${
|
|
2670
|
-
${
|
|
2671
|
-
${
|
|
2672
|
-
${
|
|
2673
|
-
${
|
|
3956
|
+
program.name("leetcode").usage("[command] [options]").description(chalk26.bold.cyan("\u{1F525} A modern LeetCode CLI built with TypeScript")).version("2.0.0", "-v, --version", "Output the version number").helpOption("-h, --help", "Display help for command").addHelpText("after", `
|
|
3957
|
+
${chalk26.yellow("Examples:")}
|
|
3958
|
+
${chalk26.cyan("$ leetcode login")} Login to LeetCode
|
|
3959
|
+
${chalk26.cyan("$ leetcode list -d easy")} List easy problems
|
|
3960
|
+
${chalk26.cyan("$ leetcode random -d medium")} Get random medium problem
|
|
3961
|
+
${chalk26.cyan("$ leetcode pick 1")} Start solving "Two Sum"
|
|
3962
|
+
${chalk26.cyan("$ leetcode test 1")} Test your solution
|
|
3963
|
+
${chalk26.cyan("$ leetcode submit 1")} Submit your solution
|
|
2674
3964
|
`);
|
|
2675
3965
|
program.command("login").description("Login to LeetCode with browser cookies").addHelpText("after", `
|
|
2676
|
-
${
|
|
2677
|
-
1. Open ${
|
|
3966
|
+
${chalk26.yellow("How to login:")}
|
|
3967
|
+
1. Open ${chalk26.cyan("https://leetcode.com")} in your browser
|
|
2678
3968
|
2. Login to your account
|
|
2679
3969
|
3. Open Developer Tools (F12) \u2192 Application \u2192 Cookies
|
|
2680
|
-
4. Copy values of ${
|
|
3970
|
+
4. Copy values of ${chalk26.green("LEETCODE_SESSION")} and ${chalk26.green("csrftoken")}
|
|
2681
3971
|
5. Paste when prompted by this command
|
|
2682
3972
|
`).action(loginCommand);
|
|
2683
3973
|
program.command("logout").description("Clear stored credentials").action(logoutCommand);
|
|
2684
3974
|
program.command("whoami").description("Check current login status").action(whoamiCommand);
|
|
2685
3975
|
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", `
|
|
2686
|
-
${
|
|
2687
|
-
${
|
|
2688
|
-
${
|
|
2689
|
-
${
|
|
2690
|
-
${
|
|
2691
|
-
${
|
|
2692
|
-
${
|
|
3976
|
+
${chalk26.yellow("Examples:")}
|
|
3977
|
+
${chalk26.cyan("$ leetcode list")} List first 20 problems
|
|
3978
|
+
${chalk26.cyan("$ leetcode list -d easy")} List easy problems only
|
|
3979
|
+
${chalk26.cyan("$ leetcode list -s solved")} List your solved problems
|
|
3980
|
+
${chalk26.cyan("$ leetcode list -t array -t string")} Filter by multiple tags
|
|
3981
|
+
${chalk26.cyan('$ leetcode list -q "two sum"')} Search by keywords
|
|
3982
|
+
${chalk26.cyan("$ leetcode list -n 50 -p 2")} Show 50 problems, page 2
|
|
2693
3983
|
`).action(listCommand);
|
|
2694
3984
|
program.command("show <id>").alias("s").description("Show problem description").addHelpText("after", `
|
|
2695
|
-
${
|
|
2696
|
-
${
|
|
2697
|
-
${
|
|
2698
|
-
${
|
|
3985
|
+
${chalk26.yellow("Examples:")}
|
|
3986
|
+
${chalk26.cyan("$ leetcode show 1")} Show by problem ID
|
|
3987
|
+
${chalk26.cyan("$ leetcode show two-sum")} Show by problem slug
|
|
3988
|
+
${chalk26.cyan("$ leetcode s 412")} Short alias
|
|
2699
3989
|
`).action(showCommand);
|
|
2700
3990
|
program.command("daily").alias("d").description("Show today's daily challenge").addHelpText("after", `
|
|
2701
|
-
${
|
|
2702
|
-
${
|
|
2703
|
-
${
|
|
3991
|
+
${chalk26.yellow("Examples:")}
|
|
3992
|
+
${chalk26.cyan("$ leetcode daily")} Show today's challenge
|
|
3993
|
+
${chalk26.cyan("$ leetcode d")} Short alias
|
|
2704
3994
|
`).action(dailyCommand);
|
|
2705
3995
|
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", `
|
|
2706
|
-
${
|
|
2707
|
-
${
|
|
2708
|
-
${
|
|
2709
|
-
${
|
|
2710
|
-
${
|
|
2711
|
-
${
|
|
3996
|
+
${chalk26.yellow("Examples:")}
|
|
3997
|
+
${chalk26.cyan("$ leetcode random")} Get any random problem
|
|
3998
|
+
${chalk26.cyan("$ leetcode random -d medium")} Random medium problem
|
|
3999
|
+
${chalk26.cyan("$ leetcode random -t array")} Random array problem
|
|
4000
|
+
${chalk26.cyan("$ leetcode random --pick")} Random + create file
|
|
4001
|
+
${chalk26.cyan("$ leetcode r -d easy --pick")} Random easy + file
|
|
2712
4002
|
`).action(randomCommand);
|
|
2713
4003
|
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", `
|
|
2714
|
-
${
|
|
2715
|
-
${
|
|
2716
|
-
${
|
|
2717
|
-
${
|
|
2718
|
-
${
|
|
2719
|
-
${
|
|
2720
|
-
|
|
2721
|
-
${
|
|
4004
|
+
${chalk26.yellow("Examples:")}
|
|
4005
|
+
${chalk26.cyan("$ leetcode pick 1")} Pick by problem ID
|
|
4006
|
+
${chalk26.cyan("$ leetcode pick two-sum")} Pick by problem slug
|
|
4007
|
+
${chalk26.cyan("$ leetcode pick 1 -l python3")} Pick with specific language
|
|
4008
|
+
${chalk26.cyan("$ leetcode pick 1 --no-open")} Create file without opening
|
|
4009
|
+
${chalk26.cyan("$ leetcode p 412")} Short alias
|
|
4010
|
+
|
|
4011
|
+
${chalk26.gray("Files are organized by: workDir/Difficulty/Category/")}
|
|
2722
4012
|
`).action(async (id, options) => {
|
|
2723
4013
|
await pickCommand(id, options);
|
|
2724
4014
|
});
|
|
2725
4015
|
program.command("pick-batch <ids...>").description("Generate solution files for multiple problems").option("-l, --lang <language>", "Programming language for the solutions").addHelpText("after", `
|
|
2726
|
-
${
|
|
2727
|
-
${
|
|
2728
|
-
${
|
|
4016
|
+
${chalk26.yellow("Examples:")}
|
|
4017
|
+
${chalk26.cyan("$ leetcode pick-batch 1 2 3")} Pick problems 1, 2, and 3
|
|
4018
|
+
${chalk26.cyan("$ leetcode pick-batch 1 2 3 -l py")} Pick with Python
|
|
2729
4019
|
`).action(batchPickCommand);
|
|
2730
|
-
program.command("test <file>").alias("t").description("Test solution against sample test cases").option("-c, --testcase <testcase>", "Custom test case").addHelpText("after", `
|
|
2731
|
-
${
|
|
2732
|
-
${
|
|
2733
|
-
${
|
|
2734
|
-
${
|
|
2735
|
-
${
|
|
2736
|
-
${
|
|
2737
|
-
|
|
2738
|
-
|
|
4020
|
+
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", `
|
|
4021
|
+
${chalk26.yellow("Examples:")}
|
|
4022
|
+
${chalk26.cyan("$ leetcode test 1")} Test by problem ID
|
|
4023
|
+
${chalk26.cyan("$ leetcode test two-sum")} Test by problem slug
|
|
4024
|
+
${chalk26.cyan("$ leetcode test ./path/to/file.py")} Test by file path
|
|
4025
|
+
${chalk26.cyan('$ leetcode test 1 -c "[1,2]\\n3"')} Test with custom case
|
|
4026
|
+
${chalk26.cyan("$ leetcode test 1 --visualize")} Visual mode for debugging
|
|
4027
|
+
${chalk26.cyan("$ leetcode t 412")} Short alias
|
|
4028
|
+
|
|
4029
|
+
${chalk26.gray("Testcases use \\n to separate multiple inputs.")}
|
|
2739
4030
|
`).action(testCommand);
|
|
2740
4031
|
program.command("submit <file>").alias("x").description("Submit solution to LeetCode").addHelpText("after", `
|
|
2741
|
-
${
|
|
2742
|
-
${
|
|
2743
|
-
${
|
|
2744
|
-
${
|
|
2745
|
-
${
|
|
4032
|
+
${chalk26.yellow("Examples:")}
|
|
4033
|
+
${chalk26.cyan("$ leetcode submit 1")} Submit by problem ID
|
|
4034
|
+
${chalk26.cyan("$ leetcode submit two-sum")} Submit by problem slug
|
|
4035
|
+
${chalk26.cyan("$ leetcode submit ./path/to/file.py")} Submit by file path
|
|
4036
|
+
${chalk26.cyan("$ leetcode x 412")} Short alias
|
|
2746
4037
|
`).action(submitCommand);
|
|
4038
|
+
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", `
|
|
4039
|
+
${chalk26.yellow("Examples:")}
|
|
4040
|
+
${chalk26.cyan("$ leetcode diff 1")} Compare with last accepted
|
|
4041
|
+
${chalk26.cyan("$ leetcode diff 1 -u")} Show unified diff
|
|
4042
|
+
${chalk26.cyan("$ leetcode diff 1 -s 12345")} Compare with specific submission
|
|
4043
|
+
${chalk26.cyan("$ leetcode diff 1 -f other.py")} Compare with local file
|
|
4044
|
+
`).action(diffCommand);
|
|
2747
4045
|
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", `
|
|
2748
|
-
${
|
|
2749
|
-
${
|
|
2750
|
-
${
|
|
2751
|
-
${
|
|
2752
|
-
${
|
|
4046
|
+
${chalk26.yellow("Examples:")}
|
|
4047
|
+
${chalk26.cyan("$ leetcode submissions 1")} View submissions for problem
|
|
4048
|
+
${chalk26.cyan("$ leetcode submissions 1 -n 5")} Show last 5 submissions
|
|
4049
|
+
${chalk26.cyan("$ leetcode submissions 1 --last")} Show last accepted submission
|
|
4050
|
+
${chalk26.cyan("$ leetcode submissions 1 --download")} Download last accepted code
|
|
2753
4051
|
`).action(submissionsCommand);
|
|
2754
4052
|
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", `
|
|
2755
|
-
${
|
|
2756
|
-
${
|
|
4053
|
+
${chalk26.yellow("Options Explained:")}
|
|
4054
|
+
${chalk26.cyan("-c, --calendar")} Shows a table of your weekly submissions and active days
|
|
2757
4055
|
for the past 12 weeks. Useful for tracking consistency.
|
|
2758
4056
|
|
|
2759
|
-
${
|
|
4057
|
+
${chalk26.cyan("-s, --skills")} Shows how many problems you solved per topic tag,
|
|
2760
4058
|
grouped by difficulty (Fundamental/Intermediate/Advanced).
|
|
2761
4059
|
Helps identify your strong and weak areas.
|
|
2762
4060
|
|
|
2763
|
-
${
|
|
4061
|
+
${chalk26.cyan("-t, --trend")} Shows a bar chart of daily submissions for the past week.
|
|
2764
4062
|
Visualizes your recent coding activity day by day.
|
|
2765
4063
|
|
|
2766
|
-
${
|
|
2767
|
-
${
|
|
2768
|
-
${
|
|
2769
|
-
${
|
|
2770
|
-
${
|
|
2771
|
-
${
|
|
4064
|
+
${chalk26.yellow("Examples:")}
|
|
4065
|
+
${chalk26.cyan("$ leetcode stat")} Show basic stats (solved count, rank)
|
|
4066
|
+
${chalk26.cyan("$ leetcode stat lee215")} Show another user's stats
|
|
4067
|
+
${chalk26.cyan("$ leetcode stat -c")} Weekly activity table
|
|
4068
|
+
${chalk26.cyan("$ leetcode stat -s")} Topic-wise breakdown
|
|
4069
|
+
${chalk26.cyan("$ leetcode stat -t")} 7-day trend chart
|
|
2772
4070
|
`).action((username, options) => statCommand(username, options));
|
|
2773
4071
|
program.command("today").description("Show today's progress summary").addHelpText("after", `
|
|
2774
|
-
${
|
|
2775
|
-
${
|
|
4072
|
+
${chalk26.yellow("Examples:")}
|
|
4073
|
+
${chalk26.cyan("$ leetcode today")} Show streak, solved, and daily challenge
|
|
2776
4074
|
`).action(todayCommand);
|
|
2777
4075
|
program.command("bookmark <action> [id]").description("Manage problem bookmarks").addHelpText("after", `
|
|
2778
|
-
${
|
|
2779
|
-
${
|
|
2780
|
-
${
|
|
2781
|
-
${
|
|
2782
|
-
${
|
|
2783
|
-
|
|
2784
|
-
${
|
|
2785
|
-
${
|
|
2786
|
-
${
|
|
2787
|
-
${
|
|
4076
|
+
${chalk26.yellow("Actions:")}
|
|
4077
|
+
${chalk26.cyan("add <id>")} Bookmark a problem
|
|
4078
|
+
${chalk26.cyan("remove <id>")} Remove a bookmark
|
|
4079
|
+
${chalk26.cyan("list")} List all bookmarks
|
|
4080
|
+
${chalk26.cyan("clear")} Clear all bookmarks
|
|
4081
|
+
|
|
4082
|
+
${chalk26.yellow("Examples:")}
|
|
4083
|
+
${chalk26.cyan("$ leetcode bookmark add 1")} Bookmark problem 1
|
|
4084
|
+
${chalk26.cyan("$ leetcode bookmark remove 1")} Remove bookmark
|
|
4085
|
+
${chalk26.cyan("$ leetcode bookmark list")} List all bookmarks
|
|
2788
4086
|
`).action(bookmarkCommand);
|
|
2789
4087
|
program.command("note <id> [action]").description("View or edit notes for a problem").addHelpText("after", `
|
|
2790
|
-
${
|
|
2791
|
-
${
|
|
2792
|
-
${
|
|
2793
|
-
|
|
2794
|
-
${
|
|
2795
|
-
${
|
|
2796
|
-
${
|
|
2797
|
-
${
|
|
4088
|
+
${chalk26.yellow("Actions:")}
|
|
4089
|
+
${chalk26.cyan("edit")} Open notes in editor (default)
|
|
4090
|
+
${chalk26.cyan("view")} Display notes in terminal
|
|
4091
|
+
|
|
4092
|
+
${chalk26.yellow("Examples:")}
|
|
4093
|
+
${chalk26.cyan("$ leetcode note 1")} Edit notes for problem 1
|
|
4094
|
+
${chalk26.cyan("$ leetcode note 1 edit")} Edit notes (explicit)
|
|
4095
|
+
${chalk26.cyan("$ leetcode note 1 view")} View notes in terminal
|
|
2798
4096
|
`).action(notesCommand);
|
|
2799
4097
|
program.command("sync").description("Sync solutions to Git repository").addHelpText("after", `
|
|
2800
|
-
${
|
|
2801
|
-
${
|
|
4098
|
+
${chalk26.yellow("Examples:")}
|
|
4099
|
+
${chalk26.cyan("$ leetcode sync")} Sync all solutions to remote
|
|
2802
4100
|
`).action(syncCommand);
|
|
2803
4101
|
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", `
|
|
2804
|
-
${
|
|
2805
|
-
${
|
|
2806
|
-
${
|
|
2807
|
-
${
|
|
2808
|
-
${
|
|
2809
|
-
${
|
|
2810
|
-
${
|
|
2811
|
-
|
|
2812
|
-
${
|
|
4102
|
+
${chalk26.yellow("Examples:")}
|
|
4103
|
+
${chalk26.cyan("$ leetcode config")} View current config
|
|
4104
|
+
${chalk26.cyan("$ leetcode config -l python3")} Set language to Python
|
|
4105
|
+
${chalk26.cyan('$ leetcode config -e "code"')} Set editor to VS Code
|
|
4106
|
+
${chalk26.cyan("$ leetcode config -w ~/leetcode")} Set solutions folder
|
|
4107
|
+
${chalk26.cyan("$ leetcode config -r https://...")} Set git repository
|
|
4108
|
+
${chalk26.cyan("$ leetcode config -i")} Interactive setup
|
|
4109
|
+
|
|
4110
|
+
${chalk26.gray("Supported languages: typescript, javascript, python3, java, cpp, c, csharp, go, rust, kotlin, swift")}
|
|
2813
4111
|
`).action(async (options) => {
|
|
2814
4112
|
if (options.interactive) {
|
|
2815
4113
|
await configInteractiveCommand();
|
|
@@ -2818,23 +4116,69 @@ ${chalk21.gray("Supported languages: typescript, javascript, python3, java, cpp,
|
|
|
2818
4116
|
}
|
|
2819
4117
|
});
|
|
2820
4118
|
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", `
|
|
2821
|
-
${
|
|
4119
|
+
${chalk26.yellow("How it works:")}
|
|
2822
4120
|
Start a problem with a countdown timer to simulate interview conditions.
|
|
2823
4121
|
Default time limits: Easy (20 min), Medium (40 min), Hard (60 min).
|
|
2824
4122
|
Your solve times are recorded when you submit successfully.
|
|
2825
4123
|
|
|
2826
|
-
${
|
|
2827
|
-
${
|
|
2828
|
-
${
|
|
2829
|
-
${
|
|
2830
|
-
${
|
|
4124
|
+
${chalk26.yellow("Examples:")}
|
|
4125
|
+
${chalk26.cyan("$ leetcode timer 1")} Start problem 1 with default time
|
|
4126
|
+
${chalk26.cyan("$ leetcode timer 1 -m 30")} Start with 30 minute limit
|
|
4127
|
+
${chalk26.cyan("$ leetcode timer --stats")} Show your solve time statistics
|
|
4128
|
+
${chalk26.cyan("$ leetcode timer --stop")} Stop active timer
|
|
2831
4129
|
`).action((id, options) => timerCommand(id, options));
|
|
4130
|
+
var workspaceCmd = program.command("workspace").description("Manage workspaces for different contexts");
|
|
4131
|
+
workspaceCmd.command("current").description("Show current workspace").action(workspaceCurrentCommand);
|
|
4132
|
+
workspaceCmd.command("list").description("List all workspaces").action(workspaceListCommand);
|
|
4133
|
+
workspaceCmd.command("create <name>").description("Create a new workspace").option("-w, --workdir <path>", "Set working directory for this workspace").action(workspaceCreateCommand);
|
|
4134
|
+
workspaceCmd.command("use <name>").description("Switch to a workspace").action(workspaceUseCommand);
|
|
4135
|
+
workspaceCmd.command("delete <name>").description("Delete a workspace").action(workspaceDeleteCommand);
|
|
4136
|
+
var collabCmd = program.command("collab").description("Collaborative coding with a partner").addHelpText("after", `
|
|
4137
|
+
${chalk26.yellow("Subcommands:")}
|
|
4138
|
+
${chalk26.cyan("host <id>")} Create a room and get a code to share
|
|
4139
|
+
${chalk26.cyan("join <code>")} Join a room with the shared code
|
|
4140
|
+
${chalk26.cyan("sync")} Upload your solution to the room
|
|
4141
|
+
${chalk26.cyan("compare")} View both solutions side by side
|
|
4142
|
+
${chalk26.cyan("status")} Check room and sync status
|
|
4143
|
+
${chalk26.cyan("leave")} End the collaboration session
|
|
4144
|
+
|
|
4145
|
+
${chalk26.yellow("Examples:")}
|
|
4146
|
+
${chalk26.gray("$ leetcode collab host 1")} Start a session for Two Sum
|
|
4147
|
+
${chalk26.gray("$ leetcode collab join ABC123")} Join your partner's session
|
|
4148
|
+
${chalk26.gray("$ leetcode collab sync")} Upload your code after solving
|
|
4149
|
+
${chalk26.gray("$ leetcode collab compare")} Compare solutions
|
|
4150
|
+
`);
|
|
4151
|
+
collabCmd.command("host <problemId>").description("Host a collaboration session").action(collabHostCommand);
|
|
4152
|
+
collabCmd.command("join <roomCode>").description("Join a collaboration session").action(collabJoinCommand);
|
|
4153
|
+
collabCmd.command("sync").description("Sync your code with partner").action(collabSyncCommand);
|
|
4154
|
+
collabCmd.command("compare").description("Compare your solution with partner").action(collabCompareCommand);
|
|
4155
|
+
collabCmd.command("leave").description("Leave the collaboration session").action(collabLeaveCommand);
|
|
4156
|
+
collabCmd.command("status").description("Show collaboration status").action(collabStatusCommand);
|
|
4157
|
+
var snapshotCmd = program.command("snapshot").description("Save and restore solution versions").addHelpText("after", `
|
|
4158
|
+
${chalk26.yellow("Subcommands:")}
|
|
4159
|
+
${chalk26.cyan("save <id> [name]")} Save current solution as a snapshot
|
|
4160
|
+
${chalk26.cyan("list <id>")} List all snapshots for a problem
|
|
4161
|
+
${chalk26.cyan("restore <id> <snapshot>")} Restore a snapshot
|
|
4162
|
+
${chalk26.cyan("diff <id> <s1> <s2>")} Compare two snapshots
|
|
4163
|
+
${chalk26.cyan("delete <id> <snapshot>")} Delete a snapshot
|
|
4164
|
+
|
|
4165
|
+
${chalk26.yellow("Examples:")}
|
|
4166
|
+
${chalk26.gray('$ leetcode snapshot save 1 "brute-force"')} Save current solution
|
|
4167
|
+
${chalk26.gray("$ leetcode snapshot list 1")} List snapshots
|
|
4168
|
+
${chalk26.gray("$ leetcode snapshot restore 1 2")} Restore snapshot #2
|
|
4169
|
+
${chalk26.gray("$ leetcode snapshot diff 1 1 2")} Compare snapshots
|
|
4170
|
+
`);
|
|
4171
|
+
snapshotCmd.command("save <id> [name]").description("Save current solution as a snapshot").action(snapshotSaveCommand);
|
|
4172
|
+
snapshotCmd.command("list <id>").description("List all snapshots for a problem").action(snapshotListCommand);
|
|
4173
|
+
snapshotCmd.command("restore <id> <snapshot>").description("Restore a snapshot").action(snapshotRestoreCommand);
|
|
4174
|
+
snapshotCmd.command("diff <id> <snap1> <snap2>").description("Compare two snapshots").action(snapshotDiffCommand);
|
|
4175
|
+
snapshotCmd.command("delete <id> <snapshot>").description("Delete a snapshot").action(snapshotDeleteCommand);
|
|
2832
4176
|
program.showHelpAfterError("(add --help for additional information)");
|
|
2833
4177
|
program.parse();
|
|
2834
4178
|
if (!process.argv.slice(2).length) {
|
|
2835
4179
|
console.log();
|
|
2836
|
-
console.log(
|
|
2837
|
-
console.log(
|
|
4180
|
+
console.log(chalk26.bold.cyan(" \u{1F525} LeetCode CLI"));
|
|
4181
|
+
console.log(chalk26.gray(" A modern command-line interface for LeetCode"));
|
|
2838
4182
|
console.log();
|
|
2839
4183
|
program.outputHelp();
|
|
2840
4184
|
}
|