@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/README.md +94 -8
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1326 -370
- package/package.json +4 -2
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
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
265
|
-
realName:
|
|
266
|
-
ranking:
|
|
267
|
-
acSubmissionNum:
|
|
268
|
-
streak:
|
|
269
|
-
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
|
-
|
|
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
|
-
|
|
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,
|
|
302
|
-
const endpoint =
|
|
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
|
|
631
|
+
import chalk4 from "chalk";
|
|
475
632
|
|
|
476
|
-
// src/utils/
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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(
|
|
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(
|
|
690
|
+
console.log(chalk3.bold.cyan(` ${problem.questionFrontendId}. ${titlePrefix}${problem.title}`));
|
|
512
691
|
console.log(` ${colorDifficulty(problem.difficulty)}`);
|
|
513
|
-
console.log(
|
|
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) =>
|
|
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(
|
|
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(
|
|
525
|
-
console.log(
|
|
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(/ /g, " ").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, '"').replace(/'/g, "'").replace(/≤/g, "\u2264").replace(/≥/g, "\u2265").replace(/&#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code, 10)));
|
|
544
730
|
content = content.replace(/\n{3,}/g, "\n\n").trim();
|
|
545
|
-
content = content.replace(/§EXAMPLE§(\d+)§/g, (_, num) =>
|
|
546
|
-
content = content.replace(/§INPUT§/g,
|
|
547
|
-
content = content.replace(/§OUTPUT§/g,
|
|
548
|
-
content = content.replace(/§EXPLAIN§/g,
|
|
549
|
-
content = content.replace(/§CONSTRAINTS§/g,
|
|
550
|
-
content = content.replace(/§FOLLOWUP§/g,
|
|
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(
|
|
558
|
-
console.log(
|
|
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(
|
|
563
|
-
console.log(
|
|
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(
|
|
753
|
+
console.log(chalk3.green.bold("\u2713 All test cases passed!"));
|
|
568
754
|
} else {
|
|
569
|
-
console.log(
|
|
755
|
+
console.log(chalk3.yellow.bold("\u2717 Some test cases failed"));
|
|
570
756
|
}
|
|
571
757
|
console.log();
|
|
572
|
-
console.log(
|
|
573
|
-
for (const output of result.code_answer) {
|
|
574
|
-
console.log(
|
|
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(
|
|
578
|
-
for (const output of result.expected_code_answer) {
|
|
579
|
-
console.log(
|
|
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(
|
|
769
|
+
console.log(chalk3.gray("Stdout:"));
|
|
584
770
|
for (const output of result.std_output_list) {
|
|
585
|
-
if (output) console.log(
|
|
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(
|
|
593
|
-
console.log(
|
|
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(
|
|
598
|
-
console.log(
|
|
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(
|
|
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(
|
|
791
|
+
console.log(chalk3.green.bold("\u2713 Accepted!"));
|
|
606
792
|
console.log();
|
|
607
793
|
console.log(
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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(
|
|
804
|
+
console.log(chalk3.red.bold(`\u274C ${result.status_msg}`));
|
|
619
805
|
console.log();
|
|
620
|
-
console.log(
|
|
806
|
+
console.log(chalk3.gray(`Passed ${result.total_correct}/${result.total_testcases} testcases`));
|
|
621
807
|
if (result.code_output) {
|
|
622
|
-
console.log(
|
|
808
|
+
console.log(chalk3.gray("Your Output:"), result.code_output);
|
|
623
809
|
}
|
|
624
810
|
if (result.expected_output) {
|
|
625
|
-
console.log(
|
|
811
|
+
console.log(chalk3.gray("Expected:"), result.expected_output);
|
|
626
812
|
}
|
|
627
813
|
if (result.last_testcase) {
|
|
628
|
-
console.log(
|
|
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(
|
|
635
|
-
console.log(
|
|
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: [
|
|
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([
|
|
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(
|
|
654
|
-
console.log(
|
|
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(
|
|
844
|
+
console.log(chalk3.bold.yellow("\u{1F3AF} Daily Challenge"), chalk3.gray(`(${date})`));
|
|
659
845
|
console.log();
|
|
660
|
-
console.log(
|
|
846
|
+
console.log(chalk3.white(`${problem.questionFrontendId}. ${problem.title}`));
|
|
661
847
|
console.log(colorDifficulty(problem.difficulty));
|
|
662
|
-
console.log(
|
|
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) =>
|
|
666
|
-
console.log(
|
|
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
|
|
858
|
+
return chalk3.green(difficulty);
|
|
673
859
|
case "medium":
|
|
674
|
-
return
|
|
860
|
+
return chalk3.yellow(difficulty);
|
|
675
861
|
case "hard":
|
|
676
|
-
return
|
|
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
|
|
870
|
+
return chalk3.green("\u2713");
|
|
685
871
|
case "notac":
|
|
686
|
-
return
|
|
872
|
+
return chalk3.yellow("\u25CB");
|
|
687
873
|
default:
|
|
688
|
-
return
|
|
874
|
+
return chalk3.gray("-");
|
|
689
875
|
}
|
|
690
876
|
}
|
|
691
877
|
function displaySubmissionsList(submissions) {
|
|
692
878
|
const table = new Table({
|
|
693
879
|
head: [
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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 ?
|
|
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
|
|
722
|
-
if (
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
964
|
+
import chalk5 from "chalk";
|
|
781
965
|
async function showCommand(idOrSlug) {
|
|
782
|
-
const
|
|
783
|
-
if (
|
|
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(
|
|
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
|
|
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
|
|
938
|
-
if (
|
|
939
|
-
|
|
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
|
-
|
|
959
|
-
|
|
960
|
-
|
|
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
|
-
|
|
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(
|
|
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 ${
|
|
995
|
-
console.log(
|
|
1205
|
+
spinner.succeed(`Created ${chalk7.green(fileName)}`);
|
|
1206
|
+
console.log(chalk7.gray(`Path: ${filePath}`));
|
|
996
1207
|
console.log();
|
|
997
|
-
console.log(
|
|
998
|
-
console.log(
|
|
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
|
|
1215
|
+
spinner.fail("Failed to fetch problem");
|
|
1004
1216
|
if (error instanceof Error) {
|
|
1005
|
-
|
|
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
|
|
1010
|
-
|
|
1011
|
-
|
|
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
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
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
|
-
|
|
1027
|
-
detached: true,
|
|
1028
|
-
stdio: "ignore"
|
|
1029
|
-
});
|
|
1030
|
-
child.unref();
|
|
1252
|
+
failed++;
|
|
1031
1253
|
}
|
|
1032
|
-
|
|
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
|
|
1038
|
-
import { existsSync as
|
|
1039
|
-
import { basename
|
|
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
|
|
1042
|
-
|
|
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
|
|
1072
|
-
if (!
|
|
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 (
|
|
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(
|
|
1083
|
-
console.log(
|
|
1084
|
-
console.log(
|
|
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(
|
|
1089
|
-
} else if (
|
|
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(
|
|
1094
|
-
console.log(
|
|
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(
|
|
1356
|
+
console.log(chalk8.gray(`Found: ${filePath}`));
|
|
1099
1357
|
}
|
|
1100
|
-
if (!
|
|
1101
|
-
console.log(
|
|
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(
|
|
1111
|
-
console.log(
|
|
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
|
|
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(
|
|
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
|
|
1152
|
-
import { existsSync as
|
|
1153
|
-
import { basename as basename2
|
|
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
|
|
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
|
|
1186
|
-
if (!
|
|
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 (
|
|
1405
|
+
if (isProblemId(fileOrId)) {
|
|
1193
1406
|
const workDir = config.getWorkDir();
|
|
1194
|
-
const found = await
|
|
1407
|
+
const found = await findSolutionFile(workDir, fileOrId);
|
|
1195
1408
|
if (!found) {
|
|
1196
|
-
console.log(
|
|
1197
|
-
console.log(
|
|
1198
|
-
console.log(
|
|
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(
|
|
1203
|
-
} else if (
|
|
1415
|
+
console.log(chalk9.gray(`Found: ${filePath}`));
|
|
1416
|
+
} else if (isFileName(fileOrId)) {
|
|
1204
1417
|
const workDir = config.getWorkDir();
|
|
1205
|
-
const found = await
|
|
1418
|
+
const found = await findFileByName(workDir, fileOrId);
|
|
1206
1419
|
if (!found) {
|
|
1207
|
-
console.log(
|
|
1208
|
-
console.log(
|
|
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(
|
|
1425
|
+
console.log(chalk9.gray(`Found: ${filePath}`));
|
|
1213
1426
|
}
|
|
1214
|
-
if (!
|
|
1215
|
-
console.log(
|
|
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(
|
|
1225
|
-
console.log(
|
|
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
|
|
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(
|
|
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
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
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
|
-
|
|
1278
|
-
|
|
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
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
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
|
|
1591
|
+
spinner.fail("No username found");
|
|
1294
1592
|
return;
|
|
1295
1593
|
}
|
|
1296
1594
|
const profile = await leetcodeClient.getUserProfile(targetUsername);
|
|
1297
1595
|
spinner.stop();
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
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(
|
|
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
|
|
1317
|
-
async function dailyCommand(
|
|
1318
|
-
const
|
|
1319
|
-
if (
|
|
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(
|
|
1329
|
-
console.log(
|
|
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(
|
|
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
|
|
1659
|
+
import chalk13 from "chalk";
|
|
1341
1660
|
async function randomCommand(options) {
|
|
1342
|
-
const
|
|
1343
|
-
if (
|
|
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(
|
|
1377
|
-
console.log(
|
|
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(
|
|
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
|
|
1390
|
-
import { join as
|
|
1706
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1707
|
+
import { join as join4 } from "path";
|
|
1391
1708
|
import ora10 from "ora";
|
|
1392
|
-
import
|
|
1709
|
+
import chalk14 from "chalk";
|
|
1393
1710
|
async function submissionsCommand(idOrSlug, options) {
|
|
1394
|
-
const
|
|
1395
|
-
if (!
|
|
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(
|
|
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(
|
|
1737
|
+
console.log(chalk14.bold("Last Accepted Submission:"));
|
|
1425
1738
|
displaySubmissionsList([lastAC]);
|
|
1426
1739
|
} else {
|
|
1427
|
-
console.log(
|
|
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
|
|
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 =
|
|
1444
|
-
if (!
|
|
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 =
|
|
1769
|
+
const filePath = join4(targetDir, fileName);
|
|
1452
1770
|
await writeFile2(filePath, details.code, "utf-8");
|
|
1453
|
-
downloadSpinner.succeed(`Downloaded to ${
|
|
1454
|
-
console.log(
|
|
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(
|
|
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
|
|
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
|
|
1487
|
-
if (!SUPPORTED_LANGUAGES.includes(
|
|
1488
|
-
console.log(
|
|
1489
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1539
|
-
console.log(
|
|
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(
|
|
1989
|
+
console.log(chalk16.bold.cyan(`\u{1F4CC} Bookmarked Problems (${bookmarkList.length})`));
|
|
1542
1990
|
console.log();
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
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) =>
|
|
1554
|
-
subcommandDescription: (cmd) =>
|
|
1555
|
-
optionTerm: (option) =>
|
|
1556
|
-
optionDescription: (option) =>
|
|
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(
|
|
1559
|
-
${
|
|
1560
|
-
${
|
|
1561
|
-
${
|
|
1562
|
-
${
|
|
1563
|
-
${
|
|
1564
|
-
${
|
|
1565
|
-
${
|
|
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").
|
|
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").
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
program.command("
|
|
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(
|
|
1591
|
-
console.log(
|
|
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
|
}
|