@night-slayer18/leetcode-cli 1.1.0 → 1.4.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/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
+ #!/usr/bin/env node
2
+
1
3
  // src/index.ts
2
4
  import { Command } from "commander";
3
- import chalk13 from "chalk";
5
+ import chalk20 from "chalk";
4
6
 
5
7
  // src/commands/login.ts
6
8
  import inquirer from "inquirer";
@@ -9,6 +11,114 @@ import chalk from "chalk";
9
11
 
10
12
  // src/api/client.ts
11
13
  import got from "got";
14
+ import { z as z2 } from "zod";
15
+
16
+ // src/schemas/api.ts
17
+ import { z } from "zod";
18
+ var TopicTagSchema = z.object({
19
+ name: z.string(),
20
+ slug: z.string()
21
+ });
22
+ var CompanyTagSchema = z.object({
23
+ name: z.string(),
24
+ slug: z.string()
25
+ });
26
+ var CodeSnippetSchema = z.object({
27
+ lang: z.string(),
28
+ langSlug: z.string(),
29
+ code: z.string()
30
+ });
31
+ var ProblemSchema = z.object({
32
+ questionId: z.string(),
33
+ questionFrontendId: z.string(),
34
+ title: z.string(),
35
+ titleSlug: z.string(),
36
+ difficulty: z.enum(["Easy", "Medium", "Hard"]),
37
+ isPaidOnly: z.boolean(),
38
+ acRate: z.number().optional().default(0),
39
+ topicTags: z.array(TopicTagSchema),
40
+ status: z.enum(["ac", "notac"]).nullable()
41
+ });
42
+ var ProblemDetailSchema = ProblemSchema.extend({
43
+ content: z.string().nullable(),
44
+ codeSnippets: z.array(CodeSnippetSchema).nullable(),
45
+ sampleTestCase: z.string(),
46
+ exampleTestcases: z.string(),
47
+ hints: z.array(z.string()),
48
+ companyTags: z.array(CompanyTagSchema).nullable(),
49
+ stats: z.string()
50
+ });
51
+ var DailyChallengeSchema = z.object({
52
+ date: z.string(),
53
+ link: z.string(),
54
+ question: ProblemSchema
55
+ });
56
+ var SubmissionSchema = z.object({
57
+ id: z.string(),
58
+ statusDisplay: z.string(),
59
+ lang: z.string(),
60
+ runtime: z.string(),
61
+ timestamp: z.string(),
62
+ memory: z.string()
63
+ });
64
+ var SubmissionDetailsSchema = z.object({
65
+ code: z.string(),
66
+ lang: z.object({
67
+ name: z.string()
68
+ })
69
+ });
70
+ var TestResultSchema = z.object({
71
+ status_code: z.number(),
72
+ status_msg: z.string(),
73
+ state: z.string(),
74
+ run_success: z.boolean(),
75
+ code_answer: z.array(z.string()).optional(),
76
+ expected_code_answer: z.array(z.string()).optional(),
77
+ correct_answer: z.boolean().optional(),
78
+ std_output_list: z.array(z.string()).optional(),
79
+ compile_error: z.string().optional(),
80
+ runtime_error: z.string().optional()
81
+ });
82
+ var SubmissionResultSchema = z.object({
83
+ status_code: z.number(),
84
+ status_msg: z.string(),
85
+ state: z.string(),
86
+ run_success: z.boolean(),
87
+ total_correct: z.number(),
88
+ total_testcases: z.number(),
89
+ status_runtime: z.string(),
90
+ status_memory: z.string(),
91
+ runtime_percentile: z.number(),
92
+ memory_percentile: z.number(),
93
+ code_output: z.string().optional(),
94
+ std_output: z.string().optional(),
95
+ expected_output: z.string().optional(),
96
+ compile_error: z.string().optional(),
97
+ runtime_error: z.string().optional(),
98
+ last_testcase: z.string().optional()
99
+ });
100
+ var UserProfileSchema = z.object({
101
+ username: z.string(),
102
+ profile: z.object({
103
+ realName: z.string(),
104
+ ranking: z.number()
105
+ }),
106
+ submitStatsGlobal: z.object({
107
+ acSubmissionNum: z.array(z.object({
108
+ difficulty: z.string(),
109
+ count: z.number()
110
+ }))
111
+ }),
112
+ userCalendar: z.object({
113
+ streak: z.number(),
114
+ totalActiveDays: z.number(),
115
+ submissionCalendar: z.string().optional()
116
+ })
117
+ });
118
+ var UserStatusSchema = z.object({
119
+ isSignedIn: z.boolean(),
120
+ username: z.string().nullable()
121
+ });
12
122
 
13
123
  // src/api/queries.ts
14
124
  var PROBLEM_LIST_QUERY = `
@@ -93,6 +203,30 @@ var USER_PROFILE_QUERY = `
93
203
  userCalendar {
94
204
  streak
95
205
  totalActiveDays
206
+ submissionCalendar
207
+ }
208
+ }
209
+ }
210
+ `;
211
+ var SKILL_STATS_QUERY = `
212
+ query skillStats($username: String!) {
213
+ matchedUser(username: $username) {
214
+ tagProblemCounts {
215
+ fundamental {
216
+ tagName
217
+ tagSlug
218
+ problemsSolved
219
+ }
220
+ intermediate {
221
+ tagName
222
+ tagSlug
223
+ problemsSolved
224
+ }
225
+ advanced {
226
+ tagName
227
+ tagSlug
228
+ problemsSolved
229
+ }
96
230
  }
97
231
  }
98
232
  }
@@ -197,7 +331,8 @@ var LeetCodeClient = class {
197
331
  }
198
332
  async checkAuth() {
199
333
  const data = await this.graphql(USER_STATUS_QUERY);
200
- return data.userStatus;
334
+ const validated = UserStatusSchema.parse(data.userStatus);
335
+ return validated;
201
336
  }
202
337
  async getProblems(filters = {}) {
203
338
  const variables = {
@@ -219,9 +354,10 @@ var LeetCodeClient = class {
219
354
  variables.filters.searchKeywords = filters.searchKeywords;
220
355
  }
221
356
  const data = await this.graphql(PROBLEM_LIST_QUERY, variables);
357
+ const validatedProblems = z2.array(ProblemSchema).parse(data.problemsetQuestionList.questions);
222
358
  return {
223
359
  total: data.problemsetQuestionList.total,
224
- problems: data.problemsetQuestionList.questions
360
+ problems: validatedProblems
225
361
  };
226
362
  }
227
363
  async getProblem(titleSlug) {
@@ -229,7 +365,8 @@ var LeetCodeClient = class {
229
365
  PROBLEM_DETAIL_QUERY,
230
366
  { titleSlug }
231
367
  );
232
- return data.question;
368
+ const validated = ProblemDetailSchema.parse(data.question);
369
+ return validated;
233
370
  }
234
371
  async getProblemById(id) {
235
372
  const { problems } = await this.getProblems({ searchKeywords: id, limit: 10 });
@@ -241,7 +378,8 @@ var LeetCodeClient = class {
241
378
  }
242
379
  async getDailyChallenge() {
243
380
  const data = await this.graphql(DAILY_CHALLENGE_QUERY);
244
- return data.activeDailyCodingChallengeQuestion;
381
+ const validated = DailyChallengeSchema.parse(data.activeDailyCodingChallengeQuestion);
382
+ return validated;
245
383
  }
246
384
  async getRandomProblem(filters = {}) {
247
385
  const variables = {
@@ -255,27 +393,36 @@ var LeetCodeClient = class {
255
393
  variables.filters.tags = filters.tags;
256
394
  }
257
395
  const data = await this.graphql(RANDOM_PROBLEM_QUERY, variables);
258
- return data.randomQuestion.titleSlug;
396
+ const validated = z2.object({ titleSlug: z2.string() }).parse(data.randomQuestion);
397
+ return validated.titleSlug;
259
398
  }
260
399
  async getUserProfile(username) {
261
400
  const data = await this.graphql(USER_PROFILE_QUERY, { username });
262
401
  const user = data.matchedUser;
402
+ const validated = UserProfileSchema.parse(user);
263
403
  return {
264
- username: user.username,
265
- realName: user.profile.realName,
266
- ranking: user.profile.ranking,
267
- acSubmissionNum: user.submitStatsGlobal.acSubmissionNum,
268
- streak: user.userCalendar.streak,
269
- totalActiveDays: user.userCalendar.totalActiveDays
404
+ username: validated.username,
405
+ realName: validated.profile.realName,
406
+ ranking: validated.profile.ranking,
407
+ acSubmissionNum: validated.submitStatsGlobal.acSubmissionNum,
408
+ streak: validated.userCalendar.streak,
409
+ totalActiveDays: validated.userCalendar.totalActiveDays,
410
+ submissionCalendar: user.userCalendar.submissionCalendar
270
411
  };
271
412
  }
413
+ async getSkillStats(username) {
414
+ const data = await this.graphql(SKILL_STATS_QUERY, { username });
415
+ return data.matchedUser.tagProblemCounts;
416
+ }
272
417
  async getSubmissionList(slug, limit = 20, offset = 0) {
273
418
  const data = await this.graphql(SUBMISSION_LIST_QUERY, { questionSlug: slug, limit, offset });
274
- return data.questionSubmissionList.submissions;
419
+ const validated = z2.array(SubmissionSchema).parse(data.questionSubmissionList.submissions);
420
+ return validated;
275
421
  }
276
422
  async getSubmissionDetails(submissionId) {
277
423
  const data = await this.graphql(SUBMISSION_DETAILS_QUERY, { submissionId });
278
- return data.submissionDetails;
424
+ const validated = SubmissionDetailsSchema.parse(data.submissionDetails);
425
+ return validated;
279
426
  }
280
427
  async testSolution(titleSlug, code, lang, testcases, questionId) {
281
428
  const response = await this.client.post(`problems/${titleSlug}/interpret_solution/`, {
@@ -286,7 +433,7 @@ var LeetCodeClient = class {
286
433
  question_id: questionId
287
434
  }
288
435
  }).json();
289
- return this.pollSubmission(response.interpret_id, "interpret");
436
+ return this.pollSubmission(response.interpret_id, "interpret", TestResultSchema);
290
437
  }
291
438
  async submitSolution(titleSlug, code, lang, questionId) {
292
439
  const response = await this.client.post(`problems/${titleSlug}/submit/`, {
@@ -296,16 +443,16 @@ var LeetCodeClient = class {
296
443
  question_id: questionId
297
444
  }
298
445
  }).json();
299
- return this.pollSubmission(response.submission_id.toString(), "submission");
446
+ return this.pollSubmission(response.submission_id.toString(), "submission", SubmissionResultSchema);
300
447
  }
301
- async pollSubmission(id, type) {
302
- const endpoint = type === "interpret" ? `submissions/detail/${id}/check/` : `submissions/detail/${id}/check/`;
448
+ async pollSubmission(id, _type, schema2) {
449
+ const endpoint = `submissions/detail/${id}/check/`;
303
450
  const maxAttempts = 30;
304
451
  const delay = 1e3;
305
452
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
306
453
  const result = await this.client.get(endpoint).json();
307
454
  if (result.state === "SUCCESS" || result.state === "FAILURE") {
308
- return result;
455
+ return schema2.parse(result);
309
456
  }
310
457
  await new Promise((resolve) => setTimeout(resolve, delay));
311
458
  }
@@ -332,7 +479,8 @@ var schema = {
332
479
  properties: {
333
480
  language: { type: "string", default: "typescript" },
334
481
  editor: { type: "string" },
335
- workDir: { type: "string", default: join(homedir(), "leetcode") }
482
+ workDir: { type: "string", default: join(homedir(), "leetcode") },
483
+ repo: { type: "string" }
336
484
  },
337
485
  default: {
338
486
  language: "typescript",
@@ -369,6 +517,12 @@ var config = {
369
517
  setWorkDir(workDir) {
370
518
  configStore.set("config.workDir", workDir);
371
519
  },
520
+ setRepo(repo) {
521
+ configStore.set("config.repo", repo);
522
+ },
523
+ deleteRepo() {
524
+ configStore.delete("config.repo");
525
+ },
372
526
  // Get specific config values
373
527
  getLanguage() {
374
528
  return configStore.get("config.language");
@@ -379,6 +533,9 @@ var config = {
379
533
  getWorkDir() {
380
534
  return configStore.get("config.workDir");
381
535
  },
536
+ getRepo() {
537
+ return configStore.get("config.repo");
538
+ },
382
539
  // Clear all config
383
540
  clear() {
384
541
  configStore.clear();
@@ -471,19 +628,41 @@ async function whoamiCommand() {
471
628
 
472
629
  // src/commands/list.ts
473
630
  import ora2 from "ora";
474
- import chalk3 from "chalk";
631
+ import chalk4 from "chalk";
475
632
 
476
- // src/utils/display.ts
633
+ // src/utils/auth.ts
477
634
  import chalk2 from "chalk";
635
+ async function requireAuth() {
636
+ const credentials = config.getCredentials();
637
+ if (!credentials) {
638
+ console.log(chalk2.yellow("\u26A0\uFE0F Please login first: leetcode login"));
639
+ return { authorized: false };
640
+ }
641
+ try {
642
+ leetcodeClient.setCredentials(credentials);
643
+ const { isSignedIn, username } = await leetcodeClient.checkAuth();
644
+ if (!isSignedIn) {
645
+ console.log(chalk2.yellow("\u26A0\uFE0F Session expired. Please run: leetcode login"));
646
+ return { authorized: false };
647
+ }
648
+ return { authorized: true, username: username ?? void 0 };
649
+ } catch {
650
+ console.log(chalk2.yellow("\u26A0\uFE0F Session validation failed. Please run: leetcode login"));
651
+ return { authorized: false };
652
+ }
653
+ }
654
+
655
+ // src/utils/display.ts
656
+ import chalk3 from "chalk";
478
657
  import Table from "cli-table3";
479
658
  function displayProblemList(problems, total) {
480
659
  const table = new Table({
481
660
  head: [
482
- chalk2.cyan("ID"),
483
- chalk2.cyan("Title"),
484
- chalk2.cyan("Difficulty"),
485
- chalk2.cyan("Rate"),
486
- chalk2.cyan("Status")
661
+ chalk3.cyan("ID"),
662
+ chalk3.cyan("Title"),
663
+ chalk3.cyan("Difficulty"),
664
+ chalk3.cyan("Rate"),
665
+ chalk3.cyan("Status")
487
666
  ],
488
667
  colWidths: [8, 45, 12, 10, 10],
489
668
  style: { head: [], border: [] }
@@ -502,27 +681,34 @@ function displayProblemList(problems, total) {
502
681
  ]);
503
682
  }
504
683
  console.log(table.toString());
505
- console.log(chalk2.gray(`
684
+ console.log(chalk3.gray(`
506
685
  Showing ${problems.length} of ${total} problems`));
507
686
  }
508
687
  function displayProblemDetail(problem) {
509
688
  console.log();
510
689
  const titlePrefix = problem.isPaidOnly ? "\u{1F512} " : "";
511
- console.log(chalk2.bold.cyan(` ${problem.questionFrontendId}. ${titlePrefix}${problem.title}`));
690
+ console.log(chalk3.bold.cyan(` ${problem.questionFrontendId}. ${titlePrefix}${problem.title}`));
512
691
  console.log(` ${colorDifficulty(problem.difficulty)}`);
513
- console.log(chalk2.gray(` https://leetcode.com/problems/${problem.titleSlug}/`));
692
+ console.log(chalk3.gray(` https://leetcode.com/problems/${problem.titleSlug}/`));
514
693
  console.log();
694
+ if (problem.isPaidOnly) {
695
+ console.log(chalk3.yellow(" \u26A0\uFE0F Premium Problem"));
696
+ console.log(chalk3.gray(" This problem requires a LeetCode Premium subscription."));
697
+ console.log(chalk3.gray(` Visit the URL above to view on LeetCode.`));
698
+ console.log();
699
+ }
515
700
  if (problem.topicTags.length) {
516
- const tags = problem.topicTags.map((t) => chalk2.bgBlue.white(` ${t.name} `)).join(" ");
701
+ const tags = problem.topicTags.map((t) => chalk3.bgBlue.white(` ${t.name} `)).join(" ");
517
702
  console.log(` ${tags}`);
518
703
  console.log();
519
704
  }
520
- console.log(chalk2.gray("\u2500".repeat(60)));
705
+ console.log(chalk3.gray("\u2500".repeat(60)));
521
706
  console.log();
522
707
  let content = problem.content;
523
708
  if (!content) {
524
- console.log(chalk2.yellow("\u{1F512} Premium Problem"));
525
- console.log(chalk2.gray("Content is not available directly. Please visit the URL below."));
709
+ console.log(chalk3.yellow(" \u{1F512} Premium Content"));
710
+ console.log(chalk3.gray(" Problem description is not available directly."));
711
+ console.log(chalk3.gray(" Please visit the URL above to view on LeetCode."));
526
712
  console.log();
527
713
  return;
528
714
  }
@@ -542,100 +728,100 @@ function displayProblemDetail(problem) {
542
728
  content = content.replace(/<[^>]+>/g, "");
543
729
  content = content.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&le;/g, "\u2264").replace(/&ge;/g, "\u2265").replace(/&#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code, 10)));
544
730
  content = content.replace(/\n{3,}/g, "\n\n").trim();
545
- content = content.replace(/§EXAMPLE§(\d+)§/g, (_, num) => chalk2.green.bold(`\u{1F4CC} Example ${num}:`));
546
- content = content.replace(/§INPUT§/g, chalk2.yellow("Input:"));
547
- content = content.replace(/§OUTPUT§/g, chalk2.yellow("Output:"));
548
- content = content.replace(/§EXPLAIN§/g, chalk2.gray("Explanation:"));
549
- content = content.replace(/§CONSTRAINTS§/g, chalk2.cyan.bold("\n\u{1F4CB} Constraints:"));
550
- content = content.replace(/§FOLLOWUP§/g, chalk2.magenta.bold("\n\u{1F4A1} Follow-up:"));
731
+ content = content.replace(/§EXAMPLE§(\d+)§/g, (_, num) => chalk3.green.bold(`\u{1F4CC} Example ${num}:`));
732
+ content = content.replace(/§INPUT§/g, chalk3.yellow("Input:"));
733
+ content = content.replace(/§OUTPUT§/g, chalk3.yellow("Output:"));
734
+ content = content.replace(/§EXPLAIN§/g, chalk3.gray("Explanation:"));
735
+ content = content.replace(/§CONSTRAINTS§/g, chalk3.cyan.bold("\n\u{1F4CB} Constraints:"));
736
+ content = content.replace(/§FOLLOWUP§/g, chalk3.magenta.bold("\n\u{1F4A1} Follow-up:"));
551
737
  console.log(content);
552
738
  console.log();
553
739
  }
554
740
  function displayTestResult(result) {
555
741
  console.log();
556
742
  if (result.compile_error) {
557
- console.log(chalk2.red.bold("\u274C Compile Error"));
558
- console.log(chalk2.red(result.compile_error));
743
+ console.log(chalk3.red.bold("\u274C Compile Error"));
744
+ console.log(chalk3.red(result.compile_error));
559
745
  return;
560
746
  }
561
747
  if (result.runtime_error) {
562
- console.log(chalk2.red.bold("\u274C Runtime Error"));
563
- console.log(chalk2.red(result.runtime_error));
748
+ console.log(chalk3.red.bold("\u274C Runtime Error"));
749
+ console.log(chalk3.red(result.runtime_error));
564
750
  return;
565
751
  }
566
752
  if (result.correct_answer) {
567
- console.log(chalk2.green.bold("\u2713 All test cases passed!"));
753
+ console.log(chalk3.green.bold("\u2713 All test cases passed!"));
568
754
  } else {
569
- console.log(chalk2.yellow.bold("\u2717 Some test cases failed"));
755
+ console.log(chalk3.yellow.bold("\u2717 Some test cases failed"));
570
756
  }
571
757
  console.log();
572
- console.log(chalk2.gray("Your Output:"));
573
- for (const output of result.code_answer) {
574
- console.log(chalk2.white(` ${output}`));
758
+ console.log(chalk3.gray("Your Output:"));
759
+ for (const output of result.code_answer ?? []) {
760
+ console.log(chalk3.white(` ${output}`));
575
761
  }
576
762
  console.log();
577
- console.log(chalk2.gray("Expected Output:"));
578
- for (const output of result.expected_code_answer) {
579
- console.log(chalk2.white(` ${output}`));
763
+ console.log(chalk3.gray("Expected Output:"));
764
+ for (const output of result.expected_code_answer ?? []) {
765
+ console.log(chalk3.white(` ${output}`));
580
766
  }
581
767
  if (result.std_output_list?.length) {
582
768
  console.log();
583
- console.log(chalk2.gray("Stdout:"));
769
+ console.log(chalk3.gray("Stdout:"));
584
770
  for (const output of result.std_output_list) {
585
- if (output) console.log(chalk2.gray(` ${output}`));
771
+ if (output) console.log(chalk3.gray(` ${output}`));
586
772
  }
587
773
  }
588
774
  }
589
775
  function displaySubmissionResult(result) {
590
776
  console.log();
591
777
  if (result.compile_error) {
592
- console.log(chalk2.red.bold("\u274C Compile Error"));
593
- console.log(chalk2.red(result.compile_error));
778
+ console.log(chalk3.red.bold("\u274C Compile Error"));
779
+ console.log(chalk3.red(result.compile_error));
594
780
  return;
595
781
  }
596
782
  if (result.runtime_error) {
597
- console.log(chalk2.red.bold("\u274C Runtime Error"));
598
- console.log(chalk2.red(result.runtime_error));
783
+ console.log(chalk3.red.bold("\u274C Runtime Error"));
784
+ console.log(chalk3.red(result.runtime_error));
599
785
  if (result.last_testcase) {
600
- console.log(chalk2.gray("Last testcase:"), result.last_testcase);
786
+ console.log(chalk3.gray("Last testcase:"), result.last_testcase);
601
787
  }
602
788
  return;
603
789
  }
604
790
  if (result.status_msg === "Accepted") {
605
- console.log(chalk2.green.bold("\u2713 Accepted!"));
791
+ console.log(chalk3.green.bold("\u2713 Accepted!"));
606
792
  console.log();
607
793
  console.log(
608
- chalk2.gray("Runtime:"),
609
- chalk2.white(result.status_runtime),
610
- chalk2.gray(`(beats ${result.runtime_percentile?.toFixed(1) ?? "N/A"}%)`)
794
+ chalk3.gray("Runtime:"),
795
+ chalk3.white(result.status_runtime),
796
+ chalk3.gray(`(beats ${result.runtime_percentile?.toFixed(1) ?? "N/A"}%)`)
611
797
  );
612
798
  console.log(
613
- chalk2.gray("Memory:"),
614
- chalk2.white(result.status_memory),
615
- chalk2.gray(`(beats ${result.memory_percentile?.toFixed(1) ?? "N/A"}%)`)
799
+ chalk3.gray("Memory:"),
800
+ chalk3.white(result.status_memory),
801
+ chalk3.gray(`(beats ${result.memory_percentile?.toFixed(1) ?? "N/A"}%)`)
616
802
  );
617
803
  } else {
618
- console.log(chalk2.red.bold(`\u274C ${result.status_msg}`));
804
+ console.log(chalk3.red.bold(`\u274C ${result.status_msg}`));
619
805
  console.log();
620
- console.log(chalk2.gray(`Passed ${result.total_correct}/${result.total_testcases} testcases`));
806
+ console.log(chalk3.gray(`Passed ${result.total_correct}/${result.total_testcases} testcases`));
621
807
  if (result.code_output) {
622
- console.log(chalk2.gray("Your Output:"), result.code_output);
808
+ console.log(chalk3.gray("Your Output:"), result.code_output);
623
809
  }
624
810
  if (result.expected_output) {
625
- console.log(chalk2.gray("Expected:"), result.expected_output);
811
+ console.log(chalk3.gray("Expected:"), result.expected_output);
626
812
  }
627
813
  if (result.last_testcase) {
628
- console.log(chalk2.gray("Failed testcase:"), result.last_testcase);
814
+ console.log(chalk3.gray("Failed testcase:"), result.last_testcase);
629
815
  }
630
816
  }
631
817
  }
632
818
  function displayUserStats(username, realName, ranking, acStats, streak, totalActiveDays) {
633
819
  console.log();
634
- console.log(chalk2.bold.white(`\u{1F464} ${username}`) + (realName ? chalk2.gray(` (${realName})`) : ""));
635
- console.log(chalk2.gray(`Ranking: #${ranking.toLocaleString()}`));
820
+ console.log(chalk3.bold.white(`\u{1F464} ${username}`) + (realName ? chalk3.gray(` (${realName})`) : ""));
821
+ console.log(chalk3.gray(`Ranking: #${ranking.toLocaleString()}`));
636
822
  console.log();
637
823
  const table = new Table({
638
- head: [chalk2.cyan("Difficulty"), chalk2.cyan("Solved")],
824
+ head: [chalk3.cyan("Difficulty"), chalk3.cyan("Solved")],
639
825
  style: { head: [], border: [] }
640
826
  });
641
827
  for (const stat of acStats) {
@@ -647,33 +833,33 @@ function displayUserStats(username, realName, ranking, acStats, streak, totalAct
647
833
  }
648
834
  }
649
835
  const total = acStats.find((s) => s.difficulty === "All")?.count ?? 0;
650
- table.push([chalk2.white.bold("Total"), chalk2.white.bold(total.toString())]);
836
+ table.push([chalk3.white.bold("Total"), chalk3.white.bold(total.toString())]);
651
837
  console.log(table.toString());
652
838
  console.log();
653
- console.log(chalk2.gray("\u{1F525} Current streak:"), chalk2.hex("#FFA500")(streak.toString()), chalk2.gray("days"));
654
- console.log(chalk2.gray("\u{1F4C5} Total active days:"), chalk2.white(totalActiveDays.toString()));
839
+ console.log(chalk3.gray("\u{1F525} Current streak:"), chalk3.hex("#FFA500")(streak.toString()), chalk3.gray("days"));
840
+ console.log(chalk3.gray("\u{1F4C5} Total active days:"), chalk3.white(totalActiveDays.toString()));
655
841
  }
656
842
  function displayDailyChallenge(date, problem) {
657
843
  console.log();
658
- console.log(chalk2.bold.yellow("\u{1F3AF} Daily Challenge"), chalk2.gray(`(${date})`));
844
+ console.log(chalk3.bold.yellow("\u{1F3AF} Daily Challenge"), chalk3.gray(`(${date})`));
659
845
  console.log();
660
- console.log(chalk2.white(`${problem.questionFrontendId}. ${problem.title}`));
846
+ console.log(chalk3.white(`${problem.questionFrontendId}. ${problem.title}`));
661
847
  console.log(colorDifficulty(problem.difficulty));
662
- console.log(chalk2.gray(`https://leetcode.com/problems/${problem.titleSlug}/`));
848
+ console.log(chalk3.gray(`https://leetcode.com/problems/${problem.titleSlug}/`));
663
849
  if (problem.topicTags.length) {
664
850
  console.log();
665
- const tags = problem.topicTags.map((t) => chalk2.blue(t.name)).join(" ");
666
- console.log(chalk2.gray("Tags:"), tags);
851
+ const tags = problem.topicTags.map((t) => chalk3.blue(t.name)).join(" ");
852
+ console.log(chalk3.gray("Tags:"), tags);
667
853
  }
668
854
  }
669
855
  function colorDifficulty(difficulty) {
670
856
  switch (difficulty.toLowerCase()) {
671
857
  case "easy":
672
- return chalk2.green(difficulty);
858
+ return chalk3.green(difficulty);
673
859
  case "medium":
674
- return chalk2.yellow(difficulty);
860
+ return chalk3.yellow(difficulty);
675
861
  case "hard":
676
- return chalk2.red(difficulty);
862
+ return chalk3.red(difficulty);
677
863
  default:
678
864
  return difficulty;
679
865
  }
@@ -681,22 +867,22 @@ function colorDifficulty(difficulty) {
681
867
  function formatStatus(status) {
682
868
  switch (status) {
683
869
  case "ac":
684
- return chalk2.green("\u2713");
870
+ return chalk3.green("\u2713");
685
871
  case "notac":
686
- return chalk2.yellow("\u25CB");
872
+ return chalk3.yellow("\u25CB");
687
873
  default:
688
- return chalk2.gray("-");
874
+ return chalk3.gray("-");
689
875
  }
690
876
  }
691
877
  function displaySubmissionsList(submissions) {
692
878
  const table = new Table({
693
879
  head: [
694
- chalk2.cyan("ID"),
695
- chalk2.cyan("Status"),
696
- chalk2.cyan("Lang"),
697
- chalk2.cyan("Runtime"),
698
- chalk2.cyan("Memory"),
699
- chalk2.cyan("Date")
880
+ chalk3.cyan("ID"),
881
+ chalk3.cyan("Status"),
882
+ chalk3.cyan("Lang"),
883
+ chalk3.cyan("Runtime"),
884
+ chalk3.cyan("Memory"),
885
+ chalk3.cyan("Date")
700
886
  ],
701
887
  colWidths: [12, 18, 15, 12, 12, 25],
702
888
  style: { head: [], border: [] }
@@ -706,7 +892,7 @@ function displaySubmissionsList(submissions) {
706
892
  const cleanTime = new Date(parseInt(s.timestamp) * 1e3).toLocaleString();
707
893
  table.push([
708
894
  s.id,
709
- isAC ? chalk2.green(s.statusDisplay) : chalk2.red(s.statusDisplay),
895
+ isAC ? chalk3.green(s.statusDisplay) : chalk3.red(s.statusDisplay),
710
896
  s.lang,
711
897
  s.runtime,
712
898
  s.memory,
@@ -718,10 +904,8 @@ function displaySubmissionsList(submissions) {
718
904
 
719
905
  // src/commands/list.ts
720
906
  async function listCommand(options) {
721
- const credentials = config.getCredentials();
722
- if (credentials) {
723
- leetcodeClient.setCredentials(credentials);
724
- }
907
+ const { authorized } = await requireAuth();
908
+ if (!authorized) return;
725
909
  const spinner = ora2("Fetching problems...").start();
726
910
  try {
727
911
  const filters = {};
@@ -759,30 +943,28 @@ async function listCommand(options) {
759
943
  const { total, problems } = await leetcodeClient.getProblems(filters);
760
944
  spinner.stop();
761
945
  if (problems.length === 0) {
762
- console.log(chalk3.yellow("No problems found matching your criteria."));
946
+ console.log(chalk4.yellow("No problems found matching your criteria."));
763
947
  return;
764
948
  }
765
949
  displayProblemList(problems, total);
766
950
  if (page * limit < total) {
767
- console.log(chalk3.gray(`
951
+ console.log(chalk4.gray(`
768
952
  Page ${page} of ${Math.ceil(total / limit)}. Use --page to navigate.`));
769
953
  }
770
954
  } catch (error) {
771
955
  spinner.fail("Failed to fetch problems");
772
956
  if (error instanceof Error) {
773
- console.log(chalk3.red(error.message));
957
+ console.log(chalk4.red(error.message));
774
958
  }
775
959
  }
776
960
  }
777
961
 
778
962
  // src/commands/show.ts
779
963
  import ora3 from "ora";
780
- import chalk4 from "chalk";
964
+ import chalk5 from "chalk";
781
965
  async function showCommand(idOrSlug) {
782
- const credentials = config.getCredentials();
783
- if (credentials) {
784
- leetcodeClient.setCredentials(credentials);
785
- }
966
+ const { authorized } = await requireAuth();
967
+ if (!authorized) return;
786
968
  const spinner = ora3("Fetching problem...").start();
787
969
  try {
788
970
  let problem;
@@ -800,7 +982,7 @@ async function showCommand(idOrSlug) {
800
982
  } catch (error) {
801
983
  spinner.fail("Failed to fetch problem");
802
984
  if (error instanceof Error) {
803
- console.log(chalk4.red(error.message));
985
+ console.log(chalk5.red(error.message));
804
986
  }
805
987
  }
806
988
  }
@@ -809,9 +991,8 @@ async function showCommand(idOrSlug) {
809
991
  import { writeFile, mkdir } from "fs/promises";
810
992
  import { existsSync } from "fs";
811
993
  import { join as join2 } from "path";
812
- import { spawn } from "child_process";
813
994
  import ora4 from "ora";
814
- import chalk5 from "chalk";
995
+ import chalk7 from "chalk";
815
996
 
816
997
  // src/utils/templates.ts
817
998
  var LANGUAGE_EXTENSIONS = {
@@ -932,13 +1113,39 @@ function getSolutionFileName(problemId, titleSlug, language) {
932
1113
  return `${problemId}.${titleSlug}.${ext}`;
933
1114
  }
934
1115
 
1116
+ // src/utils/editor.ts
1117
+ import { spawn } from "child_process";
1118
+ import open from "open";
1119
+ import chalk6 from "chalk";
1120
+ var TERMINAL_EDITORS = ["vim", "nvim", "vi", "nano", "emacs", "micro", "helix"];
1121
+ var VSCODE_EDITORS = ["code", "code-insiders", "cursor", "codium", "vscodium"];
1122
+ async function openInEditor(filePath, workDir) {
1123
+ const editor = config.getEditor() ?? process.env.EDITOR ?? "code";
1124
+ const workspace = workDir ?? config.getWorkDir();
1125
+ if (TERMINAL_EDITORS.includes(editor)) {
1126
+ console.log();
1127
+ console.log(chalk6.gray(`Open with: ${editor} ${filePath}`));
1128
+ return;
1129
+ }
1130
+ try {
1131
+ if (VSCODE_EDITORS.includes(editor)) {
1132
+ const child = spawn(editor, ["-r", workspace, "-g", filePath], {
1133
+ detached: true,
1134
+ stdio: "ignore"
1135
+ });
1136
+ child.unref();
1137
+ return;
1138
+ }
1139
+ await open(filePath, { app: { name: editor } });
1140
+ } catch {
1141
+ }
1142
+ }
1143
+
935
1144
  // src/commands/pick.ts
936
1145
  async function pickCommand(idOrSlug, options) {
937
- const credentials = config.getCredentials();
938
- if (credentials) {
939
- leetcodeClient.setCredentials(credentials);
940
- }
941
- const spinner = ora4("Fetching problem...").start();
1146
+ const { authorized } = await requireAuth();
1147
+ if (!authorized) return false;
1148
+ const spinner = ora4({ text: "Fetching problem details...", spinner: "dots" }).start();
942
1149
  try {
943
1150
  let problem;
944
1151
  if (/^\d+$/.test(idOrSlug)) {
@@ -948,22 +1155,26 @@ async function pickCommand(idOrSlug, options) {
948
1155
  }
949
1156
  if (!problem) {
950
1157
  spinner.fail(`Problem "${idOrSlug}" not found`);
951
- return;
1158
+ return false;
952
1159
  }
953
1160
  spinner.text = "Generating solution file...";
954
1161
  const langInput = options.lang?.toLowerCase() ?? config.getLanguage();
955
1162
  const language = LANG_SLUG_MAP[langInput] ?? langInput;
956
1163
  const snippets = problem.codeSnippets ?? [];
957
1164
  const template = getCodeTemplate(snippets, language);
958
- if (!template && snippets.length === 0) {
959
- spinner.warn(chalk5.yellow("Premium Problem (No code snippets available)"));
960
- console.log(chalk5.gray("Generating plain file with problem info..."));
1165
+ let code;
1166
+ if (snippets.length === 0) {
1167
+ spinner.warn(chalk7.yellow("Premium Problem (No code snippets available)"));
1168
+ console.log(chalk7.gray("Generating placeholder file with problem info..."));
1169
+ code = `// \u{1F512} Premium Problem - ${problem.title}
1170
+ // Solution stub not available - visit LeetCode to view`;
961
1171
  } else if (!template) {
962
1172
  spinner.fail(`No code template available for ${language}`);
963
- return;
1173
+ console.log(chalk7.gray(`Available languages: ${snippets.map((s) => s.langSlug).join(", ")}`));
1174
+ return false;
1175
+ } else {
1176
+ code = template.code;
964
1177
  }
965
- const code = template?.code ?? `// \u{1F512} Premium Problem - ${problem.title}
966
- // Solution stub not available`;
967
1178
  const content = generateSolutionFile(
968
1179
  problem.questionFrontendId,
969
1180
  problem.titleSlug,
@@ -971,7 +1182,7 @@ async function pickCommand(idOrSlug, options) {
971
1182
  problem.difficulty,
972
1183
  code,
973
1184
  language,
974
- problem.content
1185
+ problem.content ?? void 0
975
1186
  );
976
1187
  const workDir = config.getWorkDir();
977
1188
  const difficulty = problem.difficulty;
@@ -984,68 +1195,92 @@ async function pickCommand(idOrSlug, options) {
984
1195
  const filePath = join2(targetDir, fileName);
985
1196
  if (existsSync(filePath)) {
986
1197
  spinner.warn(`File already exists: ${fileName}`);
987
- console.log(chalk5.gray(`Path: ${filePath}`));
1198
+ console.log(chalk7.gray(`Path: ${filePath}`));
988
1199
  if (options.open !== false) {
989
- openInEditor(filePath);
1200
+ await openInEditor(filePath);
990
1201
  }
991
- return;
1202
+ return true;
992
1203
  }
993
1204
  await writeFile(filePath, content, "utf-8");
994
- spinner.succeed(`Created ${chalk5.green(fileName)}`);
995
- console.log(chalk5.gray(`Path: ${filePath}`));
1205
+ spinner.succeed(`Created ${chalk7.green(fileName)}`);
1206
+ console.log(chalk7.gray(`Path: ${filePath}`));
996
1207
  console.log();
997
- console.log(chalk5.cyan(`${problem.questionFrontendId}. ${problem.title}`));
998
- console.log(chalk5.gray(`Difficulty: ${problem.difficulty} | Category: ${category}`));
1208
+ console.log(chalk7.cyan(`${problem.questionFrontendId}. ${problem.title}`));
1209
+ console.log(chalk7.gray(`Difficulty: ${problem.difficulty} | Category: ${category}`));
999
1210
  if (options.open !== false) {
1000
- openInEditor(filePath);
1211
+ await openInEditor(filePath);
1001
1212
  }
1213
+ return true;
1002
1214
  } catch (error) {
1003
- spinner.fail("Failed to create solution file");
1215
+ spinner.fail("Failed to fetch problem");
1004
1216
  if (error instanceof Error) {
1005
- console.log(chalk5.red(error.message));
1217
+ if (error.message.includes("expected object, received null")) {
1218
+ console.log(chalk7.red(`Problem "${idOrSlug}" not found`));
1219
+ } else {
1220
+ try {
1221
+ const zodError = JSON.parse(error.message);
1222
+ if (Array.isArray(zodError)) {
1223
+ console.log(chalk7.red("API Response Validation Failed"));
1224
+ } else {
1225
+ console.log(chalk7.red(error.message));
1226
+ }
1227
+ } catch {
1228
+ console.log(chalk7.red(error.message));
1229
+ }
1230
+ }
1006
1231
  }
1232
+ return false;
1007
1233
  }
1008
1234
  }
1009
- function openInEditor(filePath) {
1010
- const editor = config.getEditor() ?? process.env.EDITOR ?? "code";
1011
- const workDir = config.getWorkDir();
1012
- const terminalEditors = ["vim", "nvim", "vi", "nano", "emacs", "micro"];
1013
- if (terminalEditors.includes(editor)) {
1014
- console.log();
1015
- console.log(chalk5.gray(`Open with: ${editor} ${filePath}`));
1235
+ async function batchPickCommand(ids, options) {
1236
+ if (ids.length === 0) {
1237
+ console.log(chalk7.yellow("Please provide at least one problem ID"));
1016
1238
  return;
1017
1239
  }
1018
- try {
1019
- if (editor === "code" || editor === "code-insiders" || editor === "cursor") {
1020
- const child = spawn(editor, ["-r", workDir, "-g", filePath], {
1021
- detached: true,
1022
- stdio: "ignore"
1023
- });
1024
- child.unref();
1240
+ const { authorized } = await requireAuth();
1241
+ if (!authorized) return;
1242
+ console.log(chalk7.cyan(`\u{1F4E6} Picking ${ids.length} problem${ids.length !== 1 ? "s" : ""}...`));
1243
+ console.log();
1244
+ console.log();
1245
+ let succeeded = 0;
1246
+ let failed = 0;
1247
+ for (const id of ids) {
1248
+ const success = await pickCommand(id, { ...options, open: false });
1249
+ if (success) {
1250
+ succeeded++;
1025
1251
  } else {
1026
- const child = spawn(editor, [filePath], {
1027
- detached: true,
1028
- stdio: "ignore"
1029
- });
1030
- child.unref();
1252
+ failed++;
1031
1253
  }
1032
- } catch {
1254
+ console.log();
1033
1255
  }
1256
+ console.log(chalk7.gray("\u2500".repeat(50)));
1257
+ console.log(
1258
+ chalk7.bold(
1259
+ `Done! ${chalk7.green(`${succeeded} succeeded`)}${failed > 0 ? `, ${chalk7.red(`${failed} failed`)}` : ""}`
1260
+ )
1261
+ );
1034
1262
  }
1035
1263
 
1036
1264
  // src/commands/test.ts
1037
- import { readFile, readdir } from "fs/promises";
1038
- import { existsSync as existsSync2 } from "fs";
1039
- import { basename, join as join3 } from "path";
1265
+ import { readFile } from "fs/promises";
1266
+ import { existsSync as existsSync3 } from "fs";
1267
+ import { basename } from "path";
1040
1268
  import ora5 from "ora";
1041
- import chalk6 from "chalk";
1042
- async function findSolutionFile(dir, problemId) {
1269
+ import chalk8 from "chalk";
1270
+
1271
+ // src/utils/fileUtils.ts
1272
+ import { readdir } from "fs/promises";
1273
+ import { existsSync as existsSync2 } from "fs";
1274
+ import { join as join3 } from "path";
1275
+ var MAX_SEARCH_DEPTH = 5;
1276
+ async function findSolutionFile(dir, problemId, currentDepth = 0) {
1043
1277
  if (!existsSync2(dir)) return null;
1278
+ if (currentDepth >= MAX_SEARCH_DEPTH) return null;
1044
1279
  const entries = await readdir(dir, { withFileTypes: true });
1045
1280
  for (const entry of entries) {
1046
1281
  const fullPath = join3(dir, entry.name);
1047
1282
  if (entry.isDirectory()) {
1048
- const found = await findSolutionFile(fullPath, problemId);
1283
+ const found = await findSolutionFile(fullPath, problemId, currentDepth + 1);
1049
1284
  if (found) return found;
1050
1285
  } else if (entry.name.startsWith(`${problemId}.`)) {
1051
1286
  return fullPath;
@@ -1053,13 +1288,14 @@ async function findSolutionFile(dir, problemId) {
1053
1288
  }
1054
1289
  return null;
1055
1290
  }
1056
- async function findFileByName(dir, fileName) {
1291
+ async function findFileByName(dir, fileName, currentDepth = 0) {
1057
1292
  if (!existsSync2(dir)) return null;
1293
+ if (currentDepth >= MAX_SEARCH_DEPTH) return null;
1058
1294
  const entries = await readdir(dir, { withFileTypes: true });
1059
1295
  for (const entry of entries) {
1060
1296
  const fullPath = join3(dir, entry.name);
1061
1297
  if (entry.isDirectory()) {
1062
- const found = await findFileByName(fullPath, fileName);
1298
+ const found = await findFileByName(fullPath, fileName, currentDepth + 1);
1063
1299
  if (found) return found;
1064
1300
  } else if (entry.name === fileName) {
1065
1301
  return fullPath;
@@ -1067,66 +1303,75 @@ async function findFileByName(dir, fileName) {
1067
1303
  }
1068
1304
  return null;
1069
1305
  }
1306
+ function getLangSlugFromExtension(ext) {
1307
+ const langMap = {
1308
+ ts: "typescript",
1309
+ js: "javascript",
1310
+ py: "python3",
1311
+ java: "java",
1312
+ cpp: "cpp",
1313
+ c: "c",
1314
+ cs: "csharp",
1315
+ go: "golang",
1316
+ rs: "rust",
1317
+ kt: "kotlin",
1318
+ swift: "swift"
1319
+ };
1320
+ return langMap[ext.toLowerCase()] ?? null;
1321
+ }
1322
+
1323
+ // src/utils/validation.ts
1324
+ function isProblemId(input) {
1325
+ return /^\d+$/.test(input);
1326
+ }
1327
+ function isFileName(input) {
1328
+ return !input.includes("/") && !input.includes("\\") && input.includes(".");
1329
+ }
1330
+
1331
+ // src/commands/test.ts
1070
1332
  async function testCommand(fileOrId, options) {
1071
- const credentials = config.getCredentials();
1072
- if (!credentials) {
1073
- console.log(chalk6.yellow("Please login first: leetcode login"));
1074
- return;
1075
- }
1076
- leetcodeClient.setCredentials(credentials);
1333
+ const { authorized } = await requireAuth();
1334
+ if (!authorized) return;
1077
1335
  let filePath = fileOrId;
1078
- if (/^\d+$/.test(fileOrId)) {
1336
+ if (isProblemId(fileOrId)) {
1079
1337
  const workDir = config.getWorkDir();
1080
1338
  const found = await findSolutionFile(workDir, fileOrId);
1081
1339
  if (!found) {
1082
- console.log(chalk6.red(`No solution file found for problem ${fileOrId}`));
1083
- console.log(chalk6.gray(`Looking in: ${workDir}`));
1084
- console.log(chalk6.gray('Run "leetcode pick ' + fileOrId + '" first to create a solution file.'));
1340
+ console.log(chalk8.red(`No solution file found for problem ${fileOrId}`));
1341
+ console.log(chalk8.gray(`Looking in: ${workDir}`));
1342
+ console.log(chalk8.gray(`Run "leetcode pick ${fileOrId}" first to create a solution file.`));
1085
1343
  return;
1086
1344
  }
1087
1345
  filePath = found;
1088
- console.log(chalk6.gray(`Found: ${filePath}`));
1089
- } else if (!fileOrId.includes("/") && !fileOrId.includes("\\") && fileOrId.includes(".")) {
1346
+ console.log(chalk8.gray(`Found: ${filePath}`));
1347
+ } else if (isFileName(fileOrId)) {
1090
1348
  const workDir = config.getWorkDir();
1091
1349
  const found = await findFileByName(workDir, fileOrId);
1092
1350
  if (!found) {
1093
- console.log(chalk6.red(`File not found: ${fileOrId}`));
1094
- console.log(chalk6.gray(`Looking in: ${workDir}`));
1351
+ console.log(chalk8.red(`File not found: ${fileOrId}`));
1352
+ console.log(chalk8.gray(`Looking in: ${workDir}`));
1095
1353
  return;
1096
1354
  }
1097
1355
  filePath = found;
1098
- console.log(chalk6.gray(`Found: ${filePath}`));
1356
+ console.log(chalk8.gray(`Found: ${filePath}`));
1099
1357
  }
1100
- if (!existsSync2(filePath)) {
1101
- console.log(chalk6.red(`File not found: ${filePath}`));
1358
+ if (!existsSync3(filePath)) {
1359
+ console.log(chalk8.red(`File not found: ${filePath}`));
1102
1360
  return;
1103
1361
  }
1104
- const spinner = ora5("Reading solution file...").start();
1362
+ const spinner = ora5({ text: "Reading solution file...", spinner: "dots" }).start();
1105
1363
  try {
1106
1364
  const fileName = basename(filePath);
1107
1365
  const match = fileName.match(/^(\d+)\.([^.]+)\./);
1108
1366
  if (!match) {
1109
1367
  spinner.fail("Invalid filename format");
1110
- console.log(chalk6.gray("Expected format: {id}.{title-slug}.{ext}"));
1111
- console.log(chalk6.gray("Example: 1.two-sum.ts"));
1368
+ console.log(chalk8.gray("Expected format: {id}.{title-slug}.{ext}"));
1369
+ console.log(chalk8.gray("Example: 1.two-sum.ts"));
1112
1370
  return;
1113
1371
  }
1114
1372
  const [, problemId, titleSlug] = match;
1115
1373
  const ext = fileName.split(".").pop();
1116
- const langMap = {
1117
- ts: "typescript",
1118
- js: "javascript",
1119
- py: "python3",
1120
- java: "java",
1121
- cpp: "cpp",
1122
- c: "c",
1123
- cs: "csharp",
1124
- go: "golang",
1125
- rs: "rust",
1126
- kt: "kotlin",
1127
- swift: "swift"
1128
- };
1129
- const lang = langMap[ext];
1374
+ const lang = getLangSlugFromExtension(ext);
1130
1375
  if (!lang) {
1131
1376
  spinner.fail(`Unsupported file extension: .${ext}`);
1132
1377
  return;
@@ -1142,105 +1387,60 @@ async function testCommand(fileOrId, options) {
1142
1387
  } catch (error) {
1143
1388
  spinner.fail("Test failed");
1144
1389
  if (error instanceof Error) {
1145
- console.log(chalk6.red(error.message));
1390
+ console.log(chalk8.red(error.message));
1146
1391
  }
1147
1392
  }
1148
1393
  }
1149
1394
 
1150
1395
  // src/commands/submit.ts
1151
- import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
1152
- import { existsSync as existsSync3 } from "fs";
1153
- import { basename as basename2, join as join4 } from "path";
1396
+ import { readFile as readFile2 } from "fs/promises";
1397
+ import { existsSync as existsSync4 } from "fs";
1398
+ import { basename as basename2 } from "path";
1154
1399
  import ora6 from "ora";
1155
- import chalk7 from "chalk";
1156
- async function findSolutionFile2(dir, problemId) {
1157
- if (!existsSync3(dir)) return null;
1158
- const entries = await readdir2(dir, { withFileTypes: true });
1159
- for (const entry of entries) {
1160
- const fullPath = join4(dir, entry.name);
1161
- if (entry.isDirectory()) {
1162
- const found = await findSolutionFile2(fullPath, problemId);
1163
- if (found) return found;
1164
- } else if (entry.name.startsWith(`${problemId}.`)) {
1165
- return fullPath;
1166
- }
1167
- }
1168
- return null;
1169
- }
1170
- async function findFileByName2(dir, fileName) {
1171
- if (!existsSync3(dir)) return null;
1172
- const entries = await readdir2(dir, { withFileTypes: true });
1173
- for (const entry of entries) {
1174
- const fullPath = join4(dir, entry.name);
1175
- if (entry.isDirectory()) {
1176
- const found = await findFileByName2(fullPath, fileName);
1177
- if (found) return found;
1178
- } else if (entry.name === fileName) {
1179
- return fullPath;
1180
- }
1181
- }
1182
- return null;
1183
- }
1400
+ import chalk9 from "chalk";
1184
1401
  async function submitCommand(fileOrId) {
1185
- const credentials = config.getCredentials();
1186
- if (!credentials) {
1187
- console.log(chalk7.yellow("Please login first: leetcode login"));
1188
- return;
1189
- }
1190
- leetcodeClient.setCredentials(credentials);
1402
+ const { authorized } = await requireAuth();
1403
+ if (!authorized) return;
1191
1404
  let filePath = fileOrId;
1192
- if (/^\d+$/.test(fileOrId)) {
1405
+ if (isProblemId(fileOrId)) {
1193
1406
  const workDir = config.getWorkDir();
1194
- const found = await findSolutionFile2(workDir, fileOrId);
1407
+ const found = await findSolutionFile(workDir, fileOrId);
1195
1408
  if (!found) {
1196
- console.log(chalk7.red(`No solution file found for problem ${fileOrId}`));
1197
- console.log(chalk7.gray(`Looking in: ${workDir}`));
1198
- console.log(chalk7.gray('Run "leetcode pick ' + fileOrId + '" first to create a solution file.'));
1409
+ console.log(chalk9.red(`No solution file found for problem ${fileOrId}`));
1410
+ console.log(chalk9.gray(`Looking in: ${workDir}`));
1411
+ console.log(chalk9.gray(`Run "leetcode pick ${fileOrId}" first to create a solution file.`));
1199
1412
  return;
1200
1413
  }
1201
1414
  filePath = found;
1202
- console.log(chalk7.gray(`Found: ${filePath}`));
1203
- } else if (!fileOrId.includes("/") && !fileOrId.includes("\\") && fileOrId.includes(".")) {
1415
+ console.log(chalk9.gray(`Found: ${filePath}`));
1416
+ } else if (isFileName(fileOrId)) {
1204
1417
  const workDir = config.getWorkDir();
1205
- const found = await findFileByName2(workDir, fileOrId);
1418
+ const found = await findFileByName(workDir, fileOrId);
1206
1419
  if (!found) {
1207
- console.log(chalk7.red(`File not found: ${fileOrId}`));
1208
- console.log(chalk7.gray(`Looking in: ${workDir}`));
1420
+ console.log(chalk9.red(`File not found: ${fileOrId}`));
1421
+ console.log(chalk9.gray(`Looking in: ${workDir}`));
1209
1422
  return;
1210
1423
  }
1211
1424
  filePath = found;
1212
- console.log(chalk7.gray(`Found: ${filePath}`));
1425
+ console.log(chalk9.gray(`Found: ${filePath}`));
1213
1426
  }
1214
- if (!existsSync3(filePath)) {
1215
- console.log(chalk7.red(`File not found: ${filePath}`));
1427
+ if (!existsSync4(filePath)) {
1428
+ console.log(chalk9.red(`File not found: ${filePath}`));
1216
1429
  return;
1217
1430
  }
1218
- const spinner = ora6("Reading solution file...").start();
1431
+ const spinner = ora6({ text: "Reading solution file...", spinner: "dots" }).start();
1219
1432
  try {
1220
1433
  const fileName = basename2(filePath);
1221
1434
  const match = fileName.match(/^(\d+)\.([^.]+)\./);
1222
1435
  if (!match) {
1223
1436
  spinner.fail("Invalid filename format");
1224
- console.log(chalk7.gray("Expected format: {id}.{title-slug}.{ext}"));
1225
- console.log(chalk7.gray("Example: 1.two-sum.ts"));
1437
+ console.log(chalk9.gray("Expected format: {id}.{title-slug}.{ext}"));
1438
+ console.log(chalk9.gray("Example: 1.two-sum.ts"));
1226
1439
  return;
1227
1440
  }
1228
1441
  const [, problemId, titleSlug] = match;
1229
1442
  const ext = fileName.split(".").pop();
1230
- const langMap = {
1231
- ts: "typescript",
1232
- js: "javascript",
1233
- py: "python3",
1234
- java: "java",
1235
- cpp: "cpp",
1236
- c: "c",
1237
- cs: "csharp",
1238
- go: "golang",
1239
- rs: "rust",
1240
- kt: "kotlin",
1241
- swift: "swift"
1242
- };
1243
- const lang = langMap[ext];
1443
+ const lang = getLangSlugFromExtension(ext);
1244
1444
  if (!lang) {
1245
1445
  spinner.fail(`Unsupported file extension: .${ext}`);
1246
1446
  return;
@@ -1260,89 +1460,206 @@ async function submitCommand(fileOrId) {
1260
1460
  } catch (error) {
1261
1461
  spinner.fail("Submission failed");
1262
1462
  if (error instanceof Error) {
1263
- console.log(chalk7.red(error.message));
1463
+ console.log(chalk9.red(error.message));
1264
1464
  }
1265
1465
  }
1266
1466
  }
1267
1467
 
1268
1468
  // src/commands/stat.ts
1269
1469
  import ora7 from "ora";
1270
- import chalk8 from "chalk";
1271
- async function statCommand(username) {
1272
- const credentials = config.getCredentials();
1273
- if (!credentials && !username) {
1274
- console.log(chalk8.yellow("Please login first or provide a username: leetcode stat [username]"));
1275
- return;
1470
+ import chalk11 from "chalk";
1471
+
1472
+ // src/utils/stats-display.ts
1473
+ import chalk10 from "chalk";
1474
+ var MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1475
+ function renderHeatmap(calendarJson) {
1476
+ const data = JSON.parse(calendarJson);
1477
+ const now = /* @__PURE__ */ new Date();
1478
+ const weeks = [];
1479
+ for (let w = 11; w >= 0; w--) {
1480
+ const weekStart = new Date(now);
1481
+ weekStart.setDate(weekStart.getDate() - w * 7 - weekStart.getDay());
1482
+ let weekCount = 0;
1483
+ let activeDays = 0;
1484
+ for (let d = 0; d < 7; d++) {
1485
+ const day = new Date(weekStart);
1486
+ day.setDate(day.getDate() + d);
1487
+ if (day > now) break;
1488
+ const midnight = new Date(Date.UTC(day.getFullYear(), day.getMonth(), day.getDate()));
1489
+ const timestamp = Math.floor(midnight.getTime() / 1e3).toString();
1490
+ const count = data[timestamp] || 0;
1491
+ weekCount += count;
1492
+ if (count > 0) activeDays++;
1493
+ }
1494
+ const weekEnd = new Date(weekStart);
1495
+ weekEnd.setDate(weekEnd.getDate() + 6);
1496
+ weeks.push({
1497
+ start: `${MONTH_NAMES[weekStart.getMonth()]} ${weekStart.getDate()}`,
1498
+ end: `${MONTH_NAMES[weekEnd.getMonth()]} ${weekEnd.getDate()}`,
1499
+ count: weekCount,
1500
+ days: activeDays
1501
+ });
1276
1502
  }
1277
- if (credentials) {
1278
- leetcodeClient.setCredentials(credentials);
1503
+ const totalSubmissions = weeks.reduce((sum, w) => sum + w.count, 0);
1504
+ const totalActiveDays = weeks.reduce((sum, w) => sum + w.days, 0);
1505
+ console.log();
1506
+ console.log(chalk10.bold("\u{1F4C5} Activity (Last 12 Weeks)"));
1507
+ console.log(chalk10.gray("How many problems you submitted and days you practiced."));
1508
+ console.log(chalk10.gray("\u2500".repeat(50)));
1509
+ console.log();
1510
+ for (const week of weeks) {
1511
+ const weekLabel = `${week.start} - ${week.end}`.padEnd(18);
1512
+ const bar = week.count > 0 ? chalk10.green("\u2588".repeat(Math.min(week.count, 10))).padEnd(10) : chalk10.gray("\xB7").padEnd(10);
1513
+ const countStr = week.count > 0 ? `${week.count} subs`.padEnd(10) : "".padEnd(10);
1514
+ const daysStr = week.days > 0 ? `${week.days}d active` : "";
1515
+ console.log(` ${chalk10.white(weekLabel)} ${bar} ${chalk10.cyan(countStr)} ${chalk10.yellow(daysStr)}`);
1516
+ }
1517
+ console.log(chalk10.gray("\u2500".repeat(50)));
1518
+ console.log(` ${chalk10.bold.white("Total:")} ${chalk10.cyan.bold(totalSubmissions + " submissions")}, ${chalk10.yellow.bold(totalActiveDays + " days active")}`);
1519
+ console.log();
1520
+ }
1521
+ function renderSkillStats(fundamental, intermediate, advanced) {
1522
+ console.log();
1523
+ console.log(chalk10.bold("\u{1F3AF} Skill Breakdown"));
1524
+ console.log(chalk10.gray("\u2500".repeat(45)));
1525
+ const renderSection = (title, stats, color) => {
1526
+ if (stats.length === 0) return;
1527
+ console.log();
1528
+ console.log(color.bold(` ${title}`));
1529
+ const sorted = [...stats].sort((a, b) => b.problemsSolved - a.problemsSolved);
1530
+ for (const stat of sorted.slice(0, 8)) {
1531
+ const name = stat.tagName.padEnd(22);
1532
+ const bar = color("\u2588".repeat(Math.min(stat.problemsSolved, 15)));
1533
+ console.log(` ${chalk10.white(name)} ${bar} ${chalk10.white(stat.problemsSolved)}`);
1534
+ }
1535
+ };
1536
+ renderSection("Fundamental", fundamental, chalk10.green);
1537
+ renderSection("Intermediate", intermediate, chalk10.yellow);
1538
+ renderSection("Advanced", advanced, chalk10.red);
1539
+ console.log();
1540
+ }
1541
+ function renderTrendChart(calendarJson) {
1542
+ const data = JSON.parse(calendarJson);
1543
+ const now = /* @__PURE__ */ new Date();
1544
+ const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1545
+ const days = [];
1546
+ for (let d = 6; d >= 0; d--) {
1547
+ const day = new Date(now);
1548
+ day.setDate(day.getDate() - d);
1549
+ const midnight = new Date(Date.UTC(day.getFullYear(), day.getMonth(), day.getDate()));
1550
+ const timestamp = Math.floor(midnight.getTime() / 1e3).toString();
1551
+ days.push({
1552
+ label: dayNames[day.getDay()],
1553
+ count: data[timestamp] || 0
1554
+ });
1279
1555
  }
1280
- const spinner = ora7("Fetching statistics...").start();
1281
- try {
1282
- let targetUsername = username;
1283
- if (!targetUsername && credentials) {
1284
- const { isSignedIn, username: currentUser } = await leetcodeClient.checkAuth();
1285
- if (!isSignedIn || !currentUser) {
1286
- spinner.fail("Session expired");
1287
- console.log(chalk8.yellow('Please run "leetcode login" to re-authenticate.'));
1288
- return;
1556
+ const maxVal = Math.max(...days.map((d) => d.count), 1);
1557
+ const chartHeight = 6;
1558
+ console.log();
1559
+ console.log(chalk10.bold("\u{1F4C8} Submission Trend (Last 7 Days)"));
1560
+ console.log(chalk10.gray("\u2500".repeat(35)));
1561
+ console.log();
1562
+ for (let row = chartHeight; row >= 1; row--) {
1563
+ let line = ` ${row === chartHeight ? maxVal.toString().padStart(2) : " "} \u2502`;
1564
+ for (const day of days) {
1565
+ const barHeight = Math.round(day.count / maxVal * chartHeight);
1566
+ if (barHeight >= row) {
1567
+ line += chalk10.green(" \u2588\u2588 ");
1568
+ } else {
1569
+ line += " ";
1289
1570
  }
1290
- targetUsername = currentUser;
1291
1571
  }
1572
+ console.log(line);
1573
+ }
1574
+ console.log(` 0 \u2514${"\u2500\u2500\u2500\u2500".repeat(7)}`);
1575
+ console.log(` ${days.map((d) => d.label.padEnd(4)).join("")}`);
1576
+ console.log(chalk10.gray(` ${days.map((d) => d.count.toString().padEnd(4)).join("")}`));
1577
+ const total = days.reduce((a, b) => a + b.count, 0);
1578
+ console.log();
1579
+ console.log(chalk10.white(` Total: ${total} submissions this week`));
1580
+ console.log();
1581
+ }
1582
+
1583
+ // src/commands/stat.ts
1584
+ async function statCommand(username, options = {}) {
1585
+ const { authorized, username: currentUser } = await requireAuth();
1586
+ if (!authorized) return;
1587
+ const spinner = ora7("Fetching statistics...").start();
1588
+ try {
1589
+ const targetUsername = username || currentUser;
1292
1590
  if (!targetUsername) {
1293
- spinner.fail("No username available");
1591
+ spinner.fail("No username found");
1294
1592
  return;
1295
1593
  }
1296
1594
  const profile = await leetcodeClient.getUserProfile(targetUsername);
1297
1595
  spinner.stop();
1298
- displayUserStats(
1299
- profile.username,
1300
- profile.realName,
1301
- profile.ranking,
1302
- profile.acSubmissionNum,
1303
- profile.streak,
1304
- profile.totalActiveDays
1305
- );
1596
+ if (!options.calendar && !options.skills && !options.trend) {
1597
+ displayUserStats(
1598
+ profile.username,
1599
+ profile.realName,
1600
+ profile.ranking,
1601
+ profile.acSubmissionNum,
1602
+ profile.streak,
1603
+ profile.totalActiveDays
1604
+ );
1605
+ return;
1606
+ }
1607
+ if (options.calendar) {
1608
+ if (profile.submissionCalendar) {
1609
+ renderHeatmap(profile.submissionCalendar);
1610
+ } else {
1611
+ console.log(chalk11.yellow("Calendar data not available."));
1612
+ }
1613
+ }
1614
+ if (options.trend) {
1615
+ if (profile.submissionCalendar) {
1616
+ renderTrendChart(profile.submissionCalendar);
1617
+ } else {
1618
+ console.log(chalk11.yellow("Calendar data not available."));
1619
+ }
1620
+ }
1621
+ if (options.skills) {
1622
+ spinner.start("Fetching skill stats...");
1623
+ const skills = await leetcodeClient.getSkillStats(targetUsername);
1624
+ spinner.stop();
1625
+ renderSkillStats(skills.fundamental, skills.intermediate, skills.advanced);
1626
+ }
1306
1627
  } catch (error) {
1307
1628
  spinner.fail("Failed to fetch statistics");
1308
1629
  if (error instanceof Error) {
1309
- console.log(chalk8.red(error.message));
1630
+ console.log(chalk11.red(error.message));
1310
1631
  }
1311
1632
  }
1312
1633
  }
1313
1634
 
1314
1635
  // src/commands/daily.ts
1315
1636
  import ora8 from "ora";
1316
- import chalk9 from "chalk";
1317
- async function dailyCommand(options) {
1318
- const credentials = config.getCredentials();
1319
- if (credentials) {
1320
- leetcodeClient.setCredentials(credentials);
1321
- }
1637
+ import chalk12 from "chalk";
1638
+ async function dailyCommand() {
1639
+ const { authorized } = await requireAuth();
1640
+ if (!authorized) return;
1322
1641
  const spinner = ora8("Fetching daily challenge...").start();
1323
1642
  try {
1324
1643
  const daily = await leetcodeClient.getDailyChallenge();
1325
1644
  spinner.stop();
1326
1645
  displayDailyChallenge(daily.date, daily.question);
1327
1646
  console.log();
1328
- console.log(chalk9.gray("Run the following to start working on this problem:"));
1329
- console.log(chalk9.cyan(` leetcode pick ${daily.question.titleSlug}`));
1647
+ console.log(chalk12.gray("Run the following to start working on this problem:"));
1648
+ console.log(chalk12.cyan(` leetcode pick ${daily.question.titleSlug}`));
1330
1649
  } catch (error) {
1331
1650
  spinner.fail("Failed to fetch daily challenge");
1332
1651
  if (error instanceof Error) {
1333
- console.log(chalk9.red(error.message));
1652
+ console.log(chalk12.red(error.message));
1334
1653
  }
1335
1654
  }
1336
1655
  }
1337
1656
 
1338
1657
  // src/commands/random.ts
1339
1658
  import ora9 from "ora";
1340
- import chalk10 from "chalk";
1659
+ import chalk13 from "chalk";
1341
1660
  async function randomCommand(options) {
1342
- const credentials = config.getCredentials();
1343
- if (credentials) {
1344
- leetcodeClient.setCredentials(credentials);
1345
- }
1661
+ const { authorized } = await requireAuth();
1662
+ if (!authorized) return;
1346
1663
  const spinner = ora9("Fetching random problem...").start();
1347
1664
  try {
1348
1665
  const filters = {};
@@ -1373,30 +1690,26 @@ async function randomCommand(options) {
1373
1690
  await pickCommand(titleSlug, { open: options.open ?? true });
1374
1691
  } else {
1375
1692
  await showCommand(titleSlug);
1376
- console.log(chalk10.gray("Run following to start solving:"));
1377
- console.log(chalk10.cyan(` leetcode pick ${titleSlug}`));
1693
+ console.log(chalk13.gray("Run following to start solving:"));
1694
+ console.log(chalk13.cyan(` leetcode pick ${titleSlug}`));
1378
1695
  }
1379
1696
  } catch (error) {
1380
1697
  spinner.fail("Failed to fetch random problem");
1381
1698
  if (error instanceof Error) {
1382
- console.log(chalk10.red(error.message));
1699
+ console.log(chalk13.red(error.message));
1383
1700
  }
1384
1701
  }
1385
1702
  }
1386
1703
 
1387
1704
  // src/commands/submissions.ts
1388
1705
  import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1389
- import { existsSync as existsSync4 } from "fs";
1390
- import { join as join5 } from "path";
1706
+ import { existsSync as existsSync5 } from "fs";
1707
+ import { join as join4 } from "path";
1391
1708
  import ora10 from "ora";
1392
- import chalk11 from "chalk";
1709
+ import chalk14 from "chalk";
1393
1710
  async function submissionsCommand(idOrSlug, options) {
1394
- const credentials = config.getCredentials();
1395
- if (!credentials) {
1396
- console.log(chalk11.red("Please login first to view submissions."));
1397
- return;
1398
- }
1399
- leetcodeClient.setCredentials(credentials);
1711
+ const { authorized } = await requireAuth();
1712
+ if (!authorized) return;
1400
1713
  const spinner = ora10("Fetching problem info...").start();
1401
1714
  try {
1402
1715
  let problem;
@@ -1415,16 +1728,16 @@ async function submissionsCommand(idOrSlug, options) {
1415
1728
  const submissions = await leetcodeClient.getSubmissionList(slug, limit);
1416
1729
  spinner.stop();
1417
1730
  if (submissions.length === 0) {
1418
- console.log(chalk11.yellow("No submissions found."));
1731
+ console.log(chalk14.yellow("No submissions found."));
1419
1732
  return;
1420
1733
  }
1421
1734
  if (options.last) {
1422
1735
  const lastAC = submissions.find((s) => s.statusDisplay === "Accepted");
1423
1736
  if (lastAC) {
1424
- console.log(chalk11.bold("Last Accepted Submission:"));
1737
+ console.log(chalk14.bold("Last Accepted Submission:"));
1425
1738
  displaySubmissionsList([lastAC]);
1426
1739
  } else {
1427
- console.log(chalk11.yellow("No accepted submissions found in recent history."));
1740
+ console.log(chalk14.yellow("No accepted submissions found in recent history."));
1428
1741
  }
1429
1742
  } else {
1430
1743
  displaySubmissionsList(submissions);
@@ -1436,34 +1749,39 @@ async function submissionsCommand(idOrSlug, options) {
1436
1749
  downloadSpinner.fail("No accepted submission found to download.");
1437
1750
  return;
1438
1751
  }
1439
- const details = await leetcodeClient.getSubmissionDetails(parseInt(lastAC.id, 10));
1752
+ const submissionId = parseInt(lastAC.id, 10);
1753
+ if (isNaN(submissionId)) {
1754
+ downloadSpinner.fail("Invalid submission ID format");
1755
+ return;
1756
+ }
1757
+ const details = await leetcodeClient.getSubmissionDetails(submissionId);
1440
1758
  const workDir = config.getWorkDir();
1441
1759
  const difficulty = problem.difficulty;
1442
1760
  const category = problem.topicTags.length > 0 ? problem.topicTags[0].name.replace(/[^\w\s-]/g, "").trim() : "Uncategorized";
1443
- const targetDir = join5(workDir, difficulty, category);
1444
- if (!existsSync4(targetDir)) {
1761
+ const targetDir = join4(workDir, difficulty, category);
1762
+ if (!existsSync5(targetDir)) {
1445
1763
  await mkdir2(targetDir, { recursive: true });
1446
1764
  }
1447
1765
  const langSlug = details.lang.name;
1448
1766
  const supportedLang = LANG_SLUG_MAP[langSlug] ?? "txt";
1449
1767
  const ext = LANGUAGE_EXTENSIONS[supportedLang] ?? langSlug;
1450
1768
  const fileName = `${problem.questionFrontendId}.${problem.titleSlug}.submission-${lastAC.id}.${ext}`;
1451
- const filePath = join5(targetDir, fileName);
1769
+ const filePath = join4(targetDir, fileName);
1452
1770
  await writeFile2(filePath, details.code, "utf-8");
1453
- downloadSpinner.succeed(`Downloaded to ${chalk11.green(fileName)}`);
1454
- console.log(chalk11.gray(`Path: ${filePath}`));
1771
+ downloadSpinner.succeed(`Downloaded to ${chalk14.green(fileName)}`);
1772
+ console.log(chalk14.gray(`Path: ${filePath}`));
1455
1773
  }
1456
1774
  } catch (error) {
1457
1775
  spinner.fail("Failed to fetch submissions");
1458
1776
  if (error instanceof Error) {
1459
- console.log(chalk11.red(error.message));
1777
+ console.log(chalk14.red(error.message));
1460
1778
  }
1461
1779
  }
1462
1780
  }
1463
1781
 
1464
1782
  // src/commands/config.ts
1465
1783
  import inquirer2 from "inquirer";
1466
- import chalk12 from "chalk";
1784
+ import chalk15 from "chalk";
1467
1785
  var SUPPORTED_LANGUAGES = [
1468
1786
  "typescript",
1469
1787
  "javascript",
@@ -1483,22 +1801,32 @@ async function configCommand(options) {
1483
1801
  return;
1484
1802
  }
1485
1803
  if (options.lang) {
1486
- const lang = options.lang.toLowerCase();
1487
- if (!SUPPORTED_LANGUAGES.includes(lang)) {
1488
- console.log(chalk12.red(`Unsupported language: ${options.lang}`));
1489
- console.log(chalk12.gray(`Supported: ${SUPPORTED_LANGUAGES.join(", ")}`));
1804
+ const langInput = options.lang.toLowerCase();
1805
+ if (!SUPPORTED_LANGUAGES.includes(langInput)) {
1806
+ console.log(chalk15.red(`Unsupported language: ${options.lang}`));
1807
+ console.log(chalk15.gray(`Supported: ${SUPPORTED_LANGUAGES.join(", ")}`));
1490
1808
  return;
1491
1809
  }
1810
+ const lang = langInput;
1492
1811
  config.setLanguage(lang);
1493
- console.log(chalk12.green(`\u2713 Default language set to ${lang}`));
1812
+ console.log(chalk15.green(`\u2713 Default language set to ${lang}`));
1494
1813
  }
1495
1814
  if (options.editor) {
1496
1815
  config.setEditor(options.editor);
1497
- console.log(chalk12.green(`\u2713 Editor set to ${options.editor}`));
1816
+ console.log(chalk15.green(`\u2713 Editor set to ${options.editor}`));
1498
1817
  }
1499
1818
  if (options.workdir) {
1500
1819
  config.setWorkDir(options.workdir);
1501
- console.log(chalk12.green(`\u2713 Work directory set to ${options.workdir}`));
1820
+ console.log(chalk15.green(`\u2713 Work directory set to ${options.workdir}`));
1821
+ }
1822
+ if (options.repo !== void 0) {
1823
+ if (options.repo.trim() === "") {
1824
+ config.deleteRepo();
1825
+ console.log(chalk15.green("\u2713 Repository URL cleared"));
1826
+ } else {
1827
+ config.setRepo(options.repo);
1828
+ console.log(chalk15.green(`\u2713 Repository URL set to ${options.repo}`));
1829
+ }
1502
1830
  }
1503
1831
  }
1504
1832
  async function configInteractiveCommand() {
@@ -1522,61 +1850,689 @@ async function configInteractiveCommand() {
1522
1850
  name: "workDir",
1523
1851
  message: "Working directory for solution files:",
1524
1852
  default: currentConfig.workDir
1853
+ },
1854
+ {
1855
+ type: "input",
1856
+ name: "repo",
1857
+ message: "Git repository URL (optional):",
1858
+ default: currentConfig.repo
1525
1859
  }
1526
1860
  ]);
1527
1861
  config.setLanguage(answers.language);
1528
1862
  config.setEditor(answers.editor);
1529
1863
  config.setWorkDir(answers.workDir);
1864
+ if (answers.repo) {
1865
+ config.setRepo(answers.repo);
1866
+ } else {
1867
+ config.deleteRepo();
1868
+ }
1530
1869
  console.log();
1531
- console.log(chalk12.green("\u2713 Configuration saved"));
1870
+ console.log(chalk15.green("\u2713 Configuration saved"));
1532
1871
  showCurrentConfig();
1533
1872
  }
1534
1873
  function showCurrentConfig() {
1535
1874
  const currentConfig = config.getConfig();
1536
1875
  const credentials = config.getCredentials();
1537
1876
  console.log();
1538
- console.log(chalk12.bold("LeetCode CLI Configuration"));
1539
- console.log(chalk12.gray("\u2500".repeat(40)));
1877
+ console.log(chalk15.bold("LeetCode CLI Configuration"));
1878
+ console.log(chalk15.gray("\u2500".repeat(40)));
1879
+ console.log();
1880
+ console.log(chalk15.gray("Config file:"), config.getPath());
1881
+ console.log();
1882
+ console.log(chalk15.gray("Language: "), chalk15.white(currentConfig.language));
1883
+ console.log(chalk15.gray("Editor: "), chalk15.white(currentConfig.editor ?? "(not set)"));
1884
+ console.log(chalk15.gray("Work Dir: "), chalk15.white(currentConfig.workDir));
1885
+ console.log(chalk15.gray("Repo URL: "), chalk15.white(currentConfig.repo ?? "(not set)"));
1886
+ console.log(chalk15.gray("Logged in: "), credentials ? chalk15.green("Yes") : chalk15.yellow("No"));
1887
+ }
1888
+
1889
+ // src/commands/bookmark.ts
1890
+ import chalk16 from "chalk";
1891
+ import Table2 from "cli-table3";
1892
+ import ora11 from "ora";
1893
+
1894
+ // src/storage/bookmarks.ts
1895
+ import Conf2 from "conf";
1896
+ var bookmarksStore = new Conf2({
1897
+ projectName: "leetcode-cli-bookmarks",
1898
+ defaults: { bookmarks: [] }
1899
+ });
1900
+ var bookmarks = {
1901
+ add(problemId) {
1902
+ const current = bookmarksStore.get("bookmarks");
1903
+ if (current.includes(problemId)) {
1904
+ return false;
1905
+ }
1906
+ bookmarksStore.set("bookmarks", [...current, problemId]);
1907
+ return true;
1908
+ },
1909
+ remove(problemId) {
1910
+ const current = bookmarksStore.get("bookmarks");
1911
+ if (!current.includes(problemId)) {
1912
+ return false;
1913
+ }
1914
+ bookmarksStore.set("bookmarks", current.filter((id) => id !== problemId));
1915
+ return true;
1916
+ },
1917
+ list() {
1918
+ return bookmarksStore.get("bookmarks");
1919
+ },
1920
+ has(problemId) {
1921
+ return bookmarksStore.get("bookmarks").includes(problemId);
1922
+ },
1923
+ count() {
1924
+ return bookmarksStore.get("bookmarks").length;
1925
+ },
1926
+ clear() {
1927
+ bookmarksStore.set("bookmarks", []);
1928
+ }
1929
+ };
1930
+
1931
+ // src/commands/bookmark.ts
1932
+ async function bookmarkCommand(action, id) {
1933
+ const validActions = ["add", "remove", "list", "clear"];
1934
+ if (!validActions.includes(action)) {
1935
+ console.log(chalk16.red(`Invalid action: ${action}`));
1936
+ console.log(chalk16.gray("Valid actions: add, remove, list, clear"));
1937
+ return;
1938
+ }
1939
+ switch (action) {
1940
+ case "add":
1941
+ if (!id) {
1942
+ console.log(chalk16.red("Please provide a problem ID to bookmark"));
1943
+ return;
1944
+ }
1945
+ if (!isProblemId(id)) {
1946
+ console.log(chalk16.red(`Invalid problem ID: ${id}`));
1947
+ console.log(chalk16.gray("Problem ID must be a positive integer"));
1948
+ return;
1949
+ }
1950
+ if (bookmarks.add(id)) {
1951
+ console.log(chalk16.green(`\u2713 Bookmarked problem ${id}`));
1952
+ } else {
1953
+ console.log(chalk16.yellow(`Problem ${id} is already bookmarked`));
1954
+ }
1955
+ break;
1956
+ case "remove":
1957
+ if (!id) {
1958
+ console.log(chalk16.red("Please provide a problem ID to remove"));
1959
+ return;
1960
+ }
1961
+ if (bookmarks.remove(id)) {
1962
+ console.log(chalk16.green(`\u2713 Removed bookmark for problem ${id}`));
1963
+ } else {
1964
+ console.log(chalk16.yellow(`Problem ${id} is not bookmarked`));
1965
+ }
1966
+ break;
1967
+ case "list":
1968
+ await listBookmarks();
1969
+ break;
1970
+ case "clear":
1971
+ const count = bookmarks.count();
1972
+ if (count === 0) {
1973
+ console.log(chalk16.yellow("No bookmarks to clear"));
1974
+ } else {
1975
+ bookmarks.clear();
1976
+ console.log(chalk16.green(`\u2713 Cleared ${count} bookmark${count !== 1 ? "s" : ""}`));
1977
+ }
1978
+ break;
1979
+ }
1980
+ }
1981
+ async function listBookmarks() {
1982
+ const bookmarkList = bookmarks.list();
1983
+ if (bookmarkList.length === 0) {
1984
+ console.log(chalk16.yellow("\u{1F4CC} No bookmarked problems"));
1985
+ console.log(chalk16.gray('Use "leetcode bookmark add <id>" to bookmark a problem'));
1986
+ return;
1987
+ }
1540
1988
  console.log();
1541
- console.log(chalk12.gray("Config file:"), config.getPath());
1989
+ console.log(chalk16.bold.cyan(`\u{1F4CC} Bookmarked Problems (${bookmarkList.length})`));
1542
1990
  console.log();
1543
- console.log(chalk12.gray("Language: "), chalk12.white(currentConfig.language));
1544
- console.log(chalk12.gray("Editor: "), chalk12.white(currentConfig.editor ?? "(not set)"));
1545
- console.log(chalk12.gray("Work Dir: "), chalk12.white(currentConfig.workDir));
1546
- console.log(chalk12.gray("Logged in: "), credentials ? chalk12.green("Yes") : chalk12.yellow("No"));
1991
+ const { authorized } = await requireAuth();
1992
+ if (authorized) {
1993
+ const spinner = ora11({ text: "Fetching problem details...", spinner: "dots" }).start();
1994
+ try {
1995
+ const table = new Table2({
1996
+ head: [
1997
+ chalk16.cyan("ID"),
1998
+ chalk16.cyan("Title"),
1999
+ chalk16.cyan("Difficulty"),
2000
+ chalk16.cyan("Status")
2001
+ ],
2002
+ colWidths: [8, 45, 12, 10],
2003
+ style: { head: [], border: [] }
2004
+ });
2005
+ for (const id of bookmarkList) {
2006
+ try {
2007
+ const problem = await leetcodeClient.getProblemById(id);
2008
+ if (problem) {
2009
+ table.push([
2010
+ problem.questionFrontendId,
2011
+ problem.title.length > 42 ? problem.title.slice(0, 39) + "..." : problem.title,
2012
+ colorDifficulty2(problem.difficulty),
2013
+ problem.status === "ac" ? chalk16.green("\u2713") : chalk16.gray("-")
2014
+ ]);
2015
+ } else {
2016
+ table.push([id, chalk16.gray("(not found)"), "-", "-"]);
2017
+ }
2018
+ } catch {
2019
+ table.push([id, chalk16.gray("(error fetching)"), "-", "-"]);
2020
+ }
2021
+ }
2022
+ spinner.stop();
2023
+ console.log(table.toString());
2024
+ } catch {
2025
+ spinner.stop();
2026
+ console.log(chalk16.gray("IDs: ") + bookmarkList.join(", "));
2027
+ }
2028
+ } else {
2029
+ console.log(chalk16.gray("IDs: ") + bookmarkList.join(", "));
2030
+ console.log();
2031
+ console.log(chalk16.gray("Login to see problem details"));
2032
+ }
2033
+ }
2034
+ function colorDifficulty2(difficulty) {
2035
+ switch (difficulty.toLowerCase()) {
2036
+ case "easy":
2037
+ return chalk16.green(difficulty);
2038
+ case "medium":
2039
+ return chalk16.yellow(difficulty);
2040
+ case "hard":
2041
+ return chalk16.red(difficulty);
2042
+ default:
2043
+ return difficulty;
2044
+ }
2045
+ }
2046
+
2047
+ // src/commands/notes.ts
2048
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
2049
+ import { join as join5 } from "path";
2050
+ import { existsSync as existsSync6 } from "fs";
2051
+ import chalk17 from "chalk";
2052
+ async function notesCommand(problemId, action) {
2053
+ if (!isProblemId(problemId)) {
2054
+ console.log(chalk17.red(`Invalid problem ID: ${problemId}`));
2055
+ console.log(chalk17.gray("Problem ID must be a positive integer"));
2056
+ return;
2057
+ }
2058
+ const noteAction = action === "view" ? "view" : "edit";
2059
+ const notesDir = join5(config.getWorkDir(), ".notes");
2060
+ const notePath = join5(notesDir, `${problemId}.md`);
2061
+ if (!existsSync6(notesDir)) {
2062
+ await mkdir3(notesDir, { recursive: true });
2063
+ }
2064
+ if (noteAction === "view") {
2065
+ await viewNote(notePath, problemId);
2066
+ } else {
2067
+ await editNote(notePath, problemId);
2068
+ }
2069
+ }
2070
+ async function viewNote(notePath, problemId) {
2071
+ if (!existsSync6(notePath)) {
2072
+ console.log(chalk17.yellow(`No notes found for problem ${problemId}`));
2073
+ console.log(chalk17.gray(`Use "leetcode note ${problemId} edit" to create notes`));
2074
+ return;
2075
+ }
2076
+ try {
2077
+ const content = await readFile3(notePath, "utf-8");
2078
+ console.log();
2079
+ console.log(chalk17.bold.cyan(`\u{1F4DD} Notes for Problem ${problemId}`));
2080
+ console.log(chalk17.gray("\u2500".repeat(50)));
2081
+ console.log();
2082
+ console.log(content);
2083
+ } catch (error) {
2084
+ console.log(chalk17.red("Failed to read notes"));
2085
+ if (error instanceof Error) {
2086
+ console.log(chalk17.gray(error.message));
2087
+ }
2088
+ }
2089
+ }
2090
+ async function editNote(notePath, problemId) {
2091
+ if (!existsSync6(notePath)) {
2092
+ const template = await generateNoteTemplate(problemId);
2093
+ await writeFile3(notePath, template, "utf-8");
2094
+ console.log(chalk17.green(`\u2713 Created notes file for problem ${problemId}`));
2095
+ }
2096
+ console.log(chalk17.gray(`Opening: ${notePath}`));
2097
+ await openInEditor(notePath);
2098
+ }
2099
+ async function generateNoteTemplate(problemId) {
2100
+ let header = `# Problem ${problemId} Notes
2101
+
2102
+ `;
2103
+ const { authorized } = await requireAuth();
2104
+ if (authorized) {
2105
+ try {
2106
+ const problem = await leetcodeClient.getProblemById(problemId);
2107
+ if (problem) {
2108
+ header = `# ${problemId}. ${problem.title}
2109
+
2110
+ `;
2111
+ header += `**Difficulty:** ${problem.difficulty}
2112
+ `;
2113
+ header += `**URL:** https://leetcode.com/problems/${problem.titleSlug}/
2114
+ `;
2115
+ if (problem.topicTags.length > 0) {
2116
+ header += `**Topics:** ${problem.topicTags.map((t) => t.name).join(", ")}
2117
+ `;
2118
+ }
2119
+ header += "\n---\n\n";
2120
+ }
2121
+ } catch {
2122
+ }
2123
+ }
2124
+ return `${header}## Approach
2125
+
2126
+ <!-- Describe your approach to solving this problem -->
2127
+
2128
+
2129
+ ## Key Insights
2130
+
2131
+ <!-- What patterns or techniques did you use? -->
2132
+
2133
+
2134
+ ## Complexity
2135
+
2136
+ - **Time:** O(?)
2137
+ - **Space:** O(?)
2138
+
2139
+
2140
+ ## Code Notes
2141
+
2142
+ <!-- Any notes about the implementation -->
2143
+
2144
+
2145
+ ## Mistakes / Learnings
2146
+
2147
+ <!-- What did you learn from this problem? -->
2148
+
2149
+ `;
2150
+ }
2151
+
2152
+ // src/commands/today.ts
2153
+ import chalk18 from "chalk";
2154
+ import ora12 from "ora";
2155
+ async function todayCommand() {
2156
+ const { authorized, username } = await requireAuth();
2157
+ if (!authorized || !username) {
2158
+ return;
2159
+ }
2160
+ const spinner = ora12({ text: "Fetching your progress...", spinner: "dots" }).start();
2161
+ try {
2162
+ const [profile, daily] = await Promise.all([
2163
+ leetcodeClient.getUserProfile(username),
2164
+ leetcodeClient.getDailyChallenge()
2165
+ ]);
2166
+ spinner.stop();
2167
+ console.log();
2168
+ console.log(chalk18.bold.cyan(`\u{1F4CA} Today's Summary`), chalk18.gray(`- ${username}`));
2169
+ console.log(chalk18.gray("\u2500".repeat(50)));
2170
+ console.log();
2171
+ console.log(chalk18.yellow(`\u{1F525} Current Streak: ${profile.streak} day${profile.streak !== 1 ? "s" : ""}`));
2172
+ console.log(chalk18.gray(` Total Active Days: ${profile.totalActiveDays}`));
2173
+ console.log();
2174
+ const total = profile.acSubmissionNum.find((s) => s.difficulty === "All");
2175
+ const easy = profile.acSubmissionNum.find((s) => s.difficulty === "Easy");
2176
+ const medium = profile.acSubmissionNum.find((s) => s.difficulty === "Medium");
2177
+ const hard = profile.acSubmissionNum.find((s) => s.difficulty === "Hard");
2178
+ console.log(chalk18.white("\u{1F4C8} Problems Solved:"));
2179
+ console.log(` ${chalk18.green("Easy")}: ${easy?.count ?? 0} | ${chalk18.yellow("Medium")}: ${medium?.count ?? 0} | ${chalk18.red("Hard")}: ${hard?.count ?? 0}`);
2180
+ console.log(` ${chalk18.bold("Total")}: ${total?.count ?? 0}`);
2181
+ console.log();
2182
+ console.log(chalk18.bold.yellow("\u{1F3AF} Today's Challenge:"));
2183
+ console.log(` ${daily.question.questionFrontendId}. ${daily.question.title}`);
2184
+ console.log(` ${colorDifficulty3(daily.question.difficulty)}`);
2185
+ const status = daily.question.status;
2186
+ if (status === "ac") {
2187
+ console.log(chalk18.green(" \u2713 Completed!"));
2188
+ } else if (status === "notac") {
2189
+ console.log(chalk18.yellow(" \u25CB Attempted"));
2190
+ } else {
2191
+ console.log(chalk18.gray(" - Not started"));
2192
+ }
2193
+ console.log();
2194
+ console.log(chalk18.gray(` leetcode pick ${daily.question.questionFrontendId} # Start working on it`));
2195
+ } catch (error) {
2196
+ spinner.fail("Failed to fetch progress");
2197
+ if (error instanceof Error) {
2198
+ console.log(chalk18.red(error.message));
2199
+ }
2200
+ }
2201
+ }
2202
+ function colorDifficulty3(difficulty) {
2203
+ switch (difficulty.toLowerCase()) {
2204
+ case "easy":
2205
+ return chalk18.green(difficulty);
2206
+ case "medium":
2207
+ return chalk18.yellow(difficulty);
2208
+ case "hard":
2209
+ return chalk18.red(difficulty);
2210
+ default:
2211
+ return difficulty;
2212
+ }
2213
+ }
2214
+
2215
+ // src/commands/sync.ts
2216
+ import { execSync } from "child_process";
2217
+ import { existsSync as existsSync7 } from "fs";
2218
+ import chalk19 from "chalk";
2219
+ import inquirer3 from "inquirer";
2220
+ import ora13 from "ora";
2221
+ function isGitInstalled() {
2222
+ try {
2223
+ execSync("git --version", { stdio: "ignore" });
2224
+ return true;
2225
+ } catch (error) {
2226
+ return false;
2227
+ }
2228
+ }
2229
+ function isMapRepo(workDir) {
2230
+ try {
2231
+ execSync("git rev-parse --is-inside-work-tree", { cwd: workDir, stdio: "ignore" });
2232
+ return true;
2233
+ } catch (error) {
2234
+ return false;
2235
+ }
2236
+ }
2237
+ function isGhInstalled() {
2238
+ try {
2239
+ execSync("gh --version", { stdio: "ignore" });
2240
+ return true;
2241
+ } catch (error) {
2242
+ return false;
2243
+ }
2244
+ }
2245
+ function getRemoteUrl(workDir) {
2246
+ try {
2247
+ const url = execSync("git config --get remote.origin.url", { cwd: workDir, encoding: "utf-8" });
2248
+ return url.trim();
2249
+ } catch (error) {
2250
+ return null;
2251
+ }
2252
+ }
2253
+ async function setupGitRepo(workDir) {
2254
+ const { init } = await inquirer3.prompt([
2255
+ {
2256
+ type: "confirm",
2257
+ name: "init",
2258
+ message: "Work directory is not a git repository. Initialize?",
2259
+ default: true
2260
+ }
2261
+ ]);
2262
+ if (!init) {
2263
+ console.log(chalk19.yellow("Skipping basic git initialization."));
2264
+ return false;
2265
+ }
2266
+ const spinner = ora13("Initializing git repository...").start();
2267
+ try {
2268
+ execSync("git init", { cwd: workDir });
2269
+ spinner.succeed("Initialized git repository");
2270
+ return true;
2271
+ } catch (error) {
2272
+ spinner.fail("Failed to initialize git repository");
2273
+ throw error;
2274
+ }
2275
+ }
2276
+ async function setupRemote(workDir) {
2277
+ const spinner = ora13();
2278
+ let repoUrl = config.getRepo();
2279
+ if (!repoUrl) {
2280
+ if (isGhInstalled()) {
2281
+ const { createGh } = await inquirer3.prompt([
2282
+ {
2283
+ type: "confirm",
2284
+ name: "createGh",
2285
+ message: "Create a new private GitHub repository?",
2286
+ default: true
2287
+ }
2288
+ ]);
2289
+ if (createGh) {
2290
+ spinner.start("Creating GitHub repository...");
2291
+ try {
2292
+ const repoName = workDir.split("/").pop() || "leetcode-solutions";
2293
+ execSync(`gh repo create ${repoName} --private --source=. --remote=origin`, { cwd: workDir });
2294
+ spinner.succeed("Created and linked GitHub repository");
2295
+ repoUrl = getRemoteUrl(workDir) || "";
2296
+ if (repoUrl) {
2297
+ config.setRepo(repoUrl);
2298
+ }
2299
+ return repoUrl;
2300
+ } catch (error) {
2301
+ spinner.fail("Failed to create GitHub repository");
2302
+ console.log(chalk19.red(error));
2303
+ }
2304
+ }
2305
+ }
2306
+ if (!repoUrl) {
2307
+ console.log(chalk19.yellow("\nPlease create a new repository on your Git provider and copy the URL."));
2308
+ const { url } = await inquirer3.prompt([
2309
+ {
2310
+ type: "input",
2311
+ name: "url",
2312
+ message: "Enter remote repository URL:",
2313
+ validate: (input) => input.length > 0 ? true : "URL cannot be empty"
2314
+ }
2315
+ ]);
2316
+ repoUrl = url;
2317
+ }
2318
+ }
2319
+ if (repoUrl) {
2320
+ config.setRepo(repoUrl);
2321
+ }
2322
+ const currentRemote = getRemoteUrl(workDir);
2323
+ if (!currentRemote && repoUrl) {
2324
+ try {
2325
+ execSync(`git remote add origin ${repoUrl}`, { cwd: workDir });
2326
+ console.log(chalk19.green("\u2713 Added remote origin"));
2327
+ } catch (e) {
2328
+ console.log(chalk19.red("Failed to add remote origin"));
2329
+ }
2330
+ }
2331
+ return repoUrl || "";
2332
+ }
2333
+ async function syncCommand() {
2334
+ const workDir = config.getWorkDir();
2335
+ if (!existsSync7(workDir)) {
2336
+ console.log(chalk19.red(`Work directory does not exist: ${workDir}`));
2337
+ return;
2338
+ }
2339
+ if (!isGitInstalled()) {
2340
+ console.log(chalk19.red("Git is not installed. Please install Git to use command."));
2341
+ return;
2342
+ }
2343
+ if (!isMapRepo(workDir)) {
2344
+ const initialized = await setupGitRepo(workDir);
2345
+ if (!initialized) return;
2346
+ }
2347
+ await setupRemote(workDir);
2348
+ const spinner = ora13("Syncing solutions...").start();
2349
+ try {
2350
+ const status = execSync("git status --porcelain", { cwd: workDir, encoding: "utf-8" });
2351
+ if (!status) {
2352
+ spinner.info("No changes to sync");
2353
+ return;
2354
+ }
2355
+ execSync("git add .", { cwd: workDir });
2356
+ const lines = status.trim().split("\n");
2357
+ const count = lines.length;
2358
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
2359
+ const message = `Sync: ${count} solutions - ${timestamp}`;
2360
+ execSync(`git commit -m "${message}"`, { cwd: workDir });
2361
+ try {
2362
+ execSync("git push -u origin main", { cwd: workDir, stdio: "ignore" });
2363
+ } catch {
2364
+ try {
2365
+ execSync("git push -u origin master", { cwd: workDir, stdio: "ignore" });
2366
+ } catch (e) {
2367
+ throw new Error("Failed to push to remote. Please check your git credentials and branch status.");
2368
+ }
2369
+ }
2370
+ spinner.succeed("Successfully synced solutions to remote");
2371
+ } catch (error) {
2372
+ spinner.fail("Sync failed");
2373
+ if (error.message) {
2374
+ console.log(chalk19.red(error.message));
2375
+ }
2376
+ }
1547
2377
  }
1548
2378
 
1549
2379
  // src/index.ts
1550
2380
  var program = new Command();
1551
2381
  program.configureHelp({
1552
2382
  sortSubcommands: true,
1553
- subcommandTerm: (cmd) => chalk13.cyan(cmd.name()) + (cmd.alias() ? chalk13.gray(`|${cmd.alias()}`) : ""),
1554
- subcommandDescription: (cmd) => chalk13.white(cmd.description()),
1555
- optionTerm: (option) => chalk13.yellow(option.flags),
1556
- optionDescription: (option) => chalk13.white(option.description)
2383
+ subcommandTerm: (cmd) => chalk20.cyan(cmd.name()) + (cmd.alias() ? chalk20.gray(`|${cmd.alias()}`) : ""),
2384
+ subcommandDescription: (cmd) => chalk20.white(cmd.description()),
2385
+ optionTerm: (option) => chalk20.yellow(option.flags),
2386
+ optionDescription: (option) => chalk20.white(option.description)
1557
2387
  });
1558
- program.name("leetcode").usage("[command] [options]").description(chalk13.bold.cyan("\u{1F525} A modern LeetCode CLI built with TypeScript")).version("1.1.0", "-v, --version", "Output the version number").helpOption("-h, --help", "Display help for command").addHelpText("after", `
1559
- ${chalk13.yellow("Examples:")}
1560
- ${chalk13.cyan("$ leetcode login")} Login to LeetCode
1561
- ${chalk13.cyan("$ leetcode list -d easy")} List easy problems
1562
- ${chalk13.cyan("$ leetcode random -d medium")} Get random medium problem
1563
- ${chalk13.cyan("$ leetcode pick 1")} Start solving "Two Sum"
1564
- ${chalk13.cyan("$ leetcode test 1")} Test your solution
1565
- ${chalk13.cyan("$ leetcode submit 1")} Submit your solution
2388
+ program.name("leetcode").usage("[command] [options]").description(chalk20.bold.cyan("\u{1F525} A modern LeetCode CLI built with TypeScript")).version("1.4.0", "-v, --version", "Output the version number").helpOption("-h, --help", "Display help for command").addHelpText("after", `
2389
+ ${chalk20.yellow("Examples:")}
2390
+ ${chalk20.cyan("$ leetcode login")} Login to LeetCode
2391
+ ${chalk20.cyan("$ leetcode list -d easy")} List easy problems
2392
+ ${chalk20.cyan("$ leetcode random -d medium")} Get random medium problem
2393
+ ${chalk20.cyan("$ leetcode pick 1")} Start solving "Two Sum"
2394
+ ${chalk20.cyan("$ leetcode test 1")} Test your solution
2395
+ ${chalk20.cyan("$ leetcode submit 1")} Submit your solution
1566
2396
  `);
1567
- program.command("login").description("Login to LeetCode with browser cookies").action(loginCommand);
2397
+ program.command("login").description("Login to LeetCode with browser cookies").addHelpText("after", `
2398
+ ${chalk20.yellow("How to login:")}
2399
+ 1. Open ${chalk20.cyan("https://leetcode.com")} in your browser
2400
+ 2. Login to your account
2401
+ 3. Open Developer Tools (F12) \u2192 Application \u2192 Cookies
2402
+ 4. Copy values of ${chalk20.green("LEETCODE_SESSION")} and ${chalk20.green("csrftoken")}
2403
+ 5. Paste when prompted by this command
2404
+ `).action(loginCommand);
1568
2405
  program.command("logout").description("Clear stored credentials").action(logoutCommand);
1569
2406
  program.command("whoami").description("Check current login status").action(whoamiCommand);
1570
- 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").action(listCommand);
1571
- program.command("show <id>").alias("s").description("Show problem description").action(showCommand);
1572
- program.command("daily").alias("d").description("Show today's daily challenge").action(dailyCommand);
1573
- 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").action(randomCommand);
1574
- 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").action(pickCommand);
1575
- program.command("test <file>").alias("t").description("Test solution against sample test cases").option("-c, --testcase <testcase>", "Custom test case").action(testCommand);
1576
- program.command("submit <file>").alias("x").description("Submit solution to LeetCode").action(submitCommand);
1577
- 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").action(submissionsCommand);
1578
- program.command("stat [username]").description("Show user statistics").action(statCommand);
1579
- 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("-i, --interactive", "Interactive configuration").action(async (options) => {
2407
+ program.command("list").alias("l").description("List LeetCode problems").option("-d, --difficulty <level>", "Filter by difficulty (easy/medium/hard)").option("-s, --status <status>", "Filter by status (todo/solved/attempted)").option("-t, --tag <tags...>", "Filter by topic tags").option("-q, --search <keywords>", "Search by keywords").option("-n, --limit <number>", "Number of problems to show", "20").option("-p, --page <number>", "Page number", "1").addHelpText("after", `
2408
+ ${chalk20.yellow("Examples:")}
2409
+ ${chalk20.cyan("$ leetcode list")} List first 20 problems
2410
+ ${chalk20.cyan("$ leetcode list -d easy")} List easy problems only
2411
+ ${chalk20.cyan("$ leetcode list -s solved")} List your solved problems
2412
+ ${chalk20.cyan("$ leetcode list -t array -t string")} Filter by multiple tags
2413
+ ${chalk20.cyan('$ leetcode list -q "two sum"')} Search by keywords
2414
+ ${chalk20.cyan("$ leetcode list -n 50 -p 2")} Show 50 problems, page 2
2415
+ `).action(listCommand);
2416
+ program.command("show <id>").alias("s").description("Show problem description").addHelpText("after", `
2417
+ ${chalk20.yellow("Examples:")}
2418
+ ${chalk20.cyan("$ leetcode show 1")} Show by problem ID
2419
+ ${chalk20.cyan("$ leetcode show two-sum")} Show by problem slug
2420
+ ${chalk20.cyan("$ leetcode s 412")} Short alias
2421
+ `).action(showCommand);
2422
+ program.command("daily").alias("d").description("Show today's daily challenge").addHelpText("after", `
2423
+ ${chalk20.yellow("Examples:")}
2424
+ ${chalk20.cyan("$ leetcode daily")} Show today's challenge
2425
+ ${chalk20.cyan("$ leetcode d")} Short alias
2426
+ `).action(dailyCommand);
2427
+ program.command("random").alias("r").description("Get a random problem").option("-d, --difficulty <level>", "Filter by difficulty (easy/medium/hard)").option("-t, --tag <tag>", "Filter by topic tag").option("--pick", "Auto-generate solution file").option("--no-open", "Do not open file in editor").addHelpText("after", `
2428
+ ${chalk20.yellow("Examples:")}
2429
+ ${chalk20.cyan("$ leetcode random")} Get any random problem
2430
+ ${chalk20.cyan("$ leetcode random -d medium")} Random medium problem
2431
+ ${chalk20.cyan("$ leetcode random -t array")} Random array problem
2432
+ ${chalk20.cyan("$ leetcode random --pick")} Random + create file
2433
+ ${chalk20.cyan("$ leetcode r -d easy --pick")} Random easy + file
2434
+ `).action(randomCommand);
2435
+ program.command("pick <id>").alias("p").description("Generate solution file for a problem").option("-l, --lang <language>", "Programming language for the solution").option("--no-open", "Do not open file in editor").addHelpText("after", `
2436
+ ${chalk20.yellow("Examples:")}
2437
+ ${chalk20.cyan("$ leetcode pick 1")} Pick by problem ID
2438
+ ${chalk20.cyan("$ leetcode pick two-sum")} Pick by problem slug
2439
+ ${chalk20.cyan("$ leetcode pick 1 -l python3")} Pick with specific language
2440
+ ${chalk20.cyan("$ leetcode pick 1 --no-open")} Create file without opening
2441
+ ${chalk20.cyan("$ leetcode p 412")} Short alias
2442
+
2443
+ ${chalk20.gray("Files are organized by: workDir/Difficulty/Category/")}
2444
+ `).action(async (id, options) => {
2445
+ await pickCommand(id, options);
2446
+ });
2447
+ program.command("pick-batch <ids...>").description("Generate solution files for multiple problems").option("-l, --lang <language>", "Programming language for the solutions").addHelpText("after", `
2448
+ ${chalk20.yellow("Examples:")}
2449
+ ${chalk20.cyan("$ leetcode pick-batch 1 2 3")} Pick problems 1, 2, and 3
2450
+ ${chalk20.cyan("$ leetcode pick-batch 1 2 3 -l py")} Pick with Python
2451
+ `).action(batchPickCommand);
2452
+ program.command("test <file>").alias("t").description("Test solution against sample test cases").option("-c, --testcase <testcase>", "Custom test case").addHelpText("after", `
2453
+ ${chalk20.yellow("Examples:")}
2454
+ ${chalk20.cyan("$ leetcode test 1")} Test by problem ID
2455
+ ${chalk20.cyan("$ leetcode test two-sum")} Test by problem slug
2456
+ ${chalk20.cyan("$ leetcode test ./path/to/file.py")} Test by file path
2457
+ ${chalk20.cyan('$ leetcode test 1 -c "[1,2]\\n3"')} Test with custom case
2458
+ ${chalk20.cyan("$ leetcode t 412")} Short alias
2459
+
2460
+ ${chalk20.gray("Testcases use \\n to separate multiple inputs.")}
2461
+ `).action(testCommand);
2462
+ program.command("submit <file>").alias("x").description("Submit solution to LeetCode").addHelpText("after", `
2463
+ ${chalk20.yellow("Examples:")}
2464
+ ${chalk20.cyan("$ leetcode submit 1")} Submit by problem ID
2465
+ ${chalk20.cyan("$ leetcode submit two-sum")} Submit by problem slug
2466
+ ${chalk20.cyan("$ leetcode submit ./path/to/file.py")} Submit by file path
2467
+ ${chalk20.cyan("$ leetcode x 412")} Short alias
2468
+ `).action(submitCommand);
2469
+ program.command("submissions <id>").description("View past submissions").option("-n, --limit <number>", "Number of submissions to show", "20").option("--last", "Show details of the last accepted submission").option("--download", "Download the last accepted submission code").addHelpText("after", `
2470
+ ${chalk20.yellow("Examples:")}
2471
+ ${chalk20.cyan("$ leetcode submissions 1")} View submissions for problem
2472
+ ${chalk20.cyan("$ leetcode submissions 1 -n 5")} Show last 5 submissions
2473
+ ${chalk20.cyan("$ leetcode submissions 1 --last")} Show last accepted submission
2474
+ ${chalk20.cyan("$ leetcode submissions 1 --download")} Download last accepted code
2475
+ `).action(submissionsCommand);
2476
+ program.command("stat [username]").description("Show user statistics and analytics").option("-c, --calendar", "Weekly activity summary (submissions & active days for last 12 weeks)").option("-s, --skills", "Skill breakdown (problems solved grouped by topic tags)").option("-t, --trend", "Daily trend chart (bar graph of submissions for last 7 days)").addHelpText("after", `
2477
+ ${chalk20.yellow("Options Explained:")}
2478
+ ${chalk20.cyan("-c, --calendar")} Shows a table of your weekly submissions and active days
2479
+ for the past 12 weeks. Useful for tracking consistency.
2480
+
2481
+ ${chalk20.cyan("-s, --skills")} Shows how many problems you solved per topic tag,
2482
+ grouped by difficulty (Fundamental/Intermediate/Advanced).
2483
+ Helps identify your strong and weak areas.
2484
+
2485
+ ${chalk20.cyan("-t, --trend")} Shows a bar chart of daily submissions for the past week.
2486
+ Visualizes your recent coding activity day by day.
2487
+
2488
+ ${chalk20.yellow("Examples:")}
2489
+ ${chalk20.cyan("$ leetcode stat")} Show basic stats (solved count, rank)
2490
+ ${chalk20.cyan("$ leetcode stat lee215")} Show another user's stats
2491
+ ${chalk20.cyan("$ leetcode stat -c")} Weekly activity table
2492
+ ${chalk20.cyan("$ leetcode stat -s")} Topic-wise breakdown
2493
+ ${chalk20.cyan("$ leetcode stat -t")} 7-day trend chart
2494
+ `).action((username, options) => statCommand(username, options));
2495
+ program.command("today").description("Show today's progress summary").addHelpText("after", `
2496
+ ${chalk20.yellow("Examples:")}
2497
+ ${chalk20.cyan("$ leetcode today")} Show streak, solved, and daily challenge
2498
+ `).action(todayCommand);
2499
+ program.command("bookmark <action> [id]").description("Manage problem bookmarks").addHelpText("after", `
2500
+ ${chalk20.yellow("Actions:")}
2501
+ ${chalk20.cyan("add <id>")} Bookmark a problem
2502
+ ${chalk20.cyan("remove <id>")} Remove a bookmark
2503
+ ${chalk20.cyan("list")} List all bookmarks
2504
+ ${chalk20.cyan("clear")} Clear all bookmarks
2505
+
2506
+ ${chalk20.yellow("Examples:")}
2507
+ ${chalk20.cyan("$ leetcode bookmark add 1")} Bookmark problem 1
2508
+ ${chalk20.cyan("$ leetcode bookmark remove 1")} Remove bookmark
2509
+ ${chalk20.cyan("$ leetcode bookmark list")} List all bookmarks
2510
+ `).action(bookmarkCommand);
2511
+ program.command("note <id> [action]").description("View or edit notes for a problem").addHelpText("after", `
2512
+ ${chalk20.yellow("Actions:")}
2513
+ ${chalk20.cyan("edit")} Open notes in editor (default)
2514
+ ${chalk20.cyan("view")} Display notes in terminal
2515
+
2516
+ ${chalk20.yellow("Examples:")}
2517
+ ${chalk20.cyan("$ leetcode note 1")} Edit notes for problem 1
2518
+ ${chalk20.cyan("$ leetcode note 1 edit")} Edit notes (explicit)
2519
+ ${chalk20.cyan("$ leetcode note 1 view")} View notes in terminal
2520
+ `).action(notesCommand);
2521
+ program.command("sync").description("Sync solutions to Git repository").addHelpText("after", `
2522
+ ${chalk20.yellow("Examples:")}
2523
+ ${chalk20.cyan("$ leetcode sync")} Sync all solutions to remote
2524
+ `).action(syncCommand);
2525
+ program.command("config").description("View or set configuration").option("-l, --lang <language>", "Set default programming language").option("-e, --editor <editor>", "Set editor command").option("-w, --workdir <path>", "Set working directory for solutions").option("-r, --repo <url>", "Set Git repository URL").option("-i, --interactive", "Interactive configuration").addHelpText("after", `
2526
+ ${chalk20.yellow("Examples:")}
2527
+ ${chalk20.cyan("$ leetcode config")} View current config
2528
+ ${chalk20.cyan("$ leetcode config -l python3")} Set language to Python
2529
+ ${chalk20.cyan('$ leetcode config -e "code"')} Set editor to VS Code
2530
+ ${chalk20.cyan("$ leetcode config -w ~/leetcode")} Set solutions folder
2531
+ ${chalk20.cyan("$ leetcode config -r https://...")} Set git repository
2532
+ ${chalk20.cyan("$ leetcode config -i")} Interactive setup
2533
+
2534
+ ${chalk20.gray("Supported languages: typescript, javascript, python3, java, cpp, c, csharp, go, rust, kotlin, swift")}
2535
+ `).action(async (options) => {
1580
2536
  if (options.interactive) {
1581
2537
  await configInteractiveCommand();
1582
2538
  } else {
@@ -1587,8 +2543,8 @@ program.showHelpAfterError("(add --help for additional information)");
1587
2543
  program.parse();
1588
2544
  if (!process.argv.slice(2).length) {
1589
2545
  console.log();
1590
- console.log(chalk13.bold.cyan(" \u{1F525} LeetCode CLI"));
1591
- console.log(chalk13.gray(" A modern command-line interface for LeetCode"));
2546
+ console.log(chalk20.bold.cyan(" \u{1F525} LeetCode CLI"));
2547
+ console.log(chalk20.gray(" A modern command-line interface for LeetCode"));
1592
2548
  console.log();
1593
2549
  program.outputHelp();
1594
2550
  }