@night-slayer18/leetcode-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1358 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import chalk11 from "chalk";
6
+
7
+ // src/commands/login.ts
8
+ import inquirer from "inquirer";
9
+ import ora from "ora";
10
+ import chalk from "chalk";
11
+
12
+ // src/api/client.ts
13
+ import got from "got";
14
+
15
+ // src/api/queries.ts
16
+ var PROBLEM_LIST_QUERY = `
17
+ query problemsetQuestionList($categorySlug: String, $limit: Int, $skip: Int, $filters: QuestionListFilterInput) {
18
+ problemsetQuestionList: questionList(
19
+ categorySlug: $categorySlug
20
+ limit: $limit
21
+ skip: $skip
22
+ filters: $filters
23
+ ) {
24
+ total: totalNum
25
+ questions: data {
26
+ questionId
27
+ questionFrontendId
28
+ title
29
+ titleSlug
30
+ difficulty
31
+ isPaidOnly
32
+ acRate
33
+ topicTags {
34
+ name
35
+ slug
36
+ }
37
+ status
38
+ }
39
+ }
40
+ }
41
+ `;
42
+ var PROBLEM_DETAIL_QUERY = `
43
+ query questionData($titleSlug: String!) {
44
+ question(titleSlug: $titleSlug) {
45
+ questionId
46
+ questionFrontendId
47
+ title
48
+ titleSlug
49
+ content
50
+ difficulty
51
+ isPaidOnly
52
+ topicTags {
53
+ name
54
+ slug
55
+ }
56
+ codeSnippets {
57
+ lang
58
+ langSlug
59
+ code
60
+ }
61
+ sampleTestCase
62
+ exampleTestcases
63
+ hints
64
+ companyTags {
65
+ name
66
+ slug
67
+ }
68
+ stats
69
+ status
70
+ }
71
+ }
72
+ `;
73
+ var USER_STATUS_QUERY = `
74
+ query globalData {
75
+ userStatus {
76
+ isSignedIn
77
+ username
78
+ }
79
+ }
80
+ `;
81
+ var USER_PROFILE_QUERY = `
82
+ query userPublicProfile($username: String!) {
83
+ matchedUser(username: $username) {
84
+ username
85
+ profile {
86
+ realName
87
+ ranking
88
+ }
89
+ submitStatsGlobal {
90
+ acSubmissionNum {
91
+ difficulty
92
+ count
93
+ }
94
+ }
95
+ userCalendar {
96
+ streak
97
+ totalActiveDays
98
+ }
99
+ }
100
+ }
101
+ `;
102
+ var DAILY_CHALLENGE_QUERY = `
103
+ query questionOfToday {
104
+ activeDailyCodingChallengeQuestion {
105
+ date
106
+ link
107
+ question {
108
+ questionId
109
+ questionFrontendId
110
+ title
111
+ titleSlug
112
+ difficulty
113
+ isPaidOnly
114
+ acRate
115
+ topicTags {
116
+ name
117
+ slug
118
+ }
119
+ status
120
+ }
121
+ }
122
+ }
123
+ `;
124
+
125
+ // src/api/client.ts
126
+ var LEETCODE_BASE_URL = "https://leetcode.com";
127
+ var LEETCODE_GRAPHQL_URL = `${LEETCODE_BASE_URL}/graphql`;
128
+ var LeetCodeClient = class {
129
+ client;
130
+ credentials = null;
131
+ constructor() {
132
+ this.client = got.extend({
133
+ prefixUrl: LEETCODE_BASE_URL,
134
+ headers: {
135
+ "Content-Type": "application/json",
136
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
137
+ "Origin": LEETCODE_BASE_URL,
138
+ "Referer": `${LEETCODE_BASE_URL}/`
139
+ },
140
+ timeout: { request: 3e4 },
141
+ retry: { limit: 2 }
142
+ });
143
+ }
144
+ setCredentials(credentials) {
145
+ this.credentials = credentials;
146
+ this.client = this.client.extend({
147
+ headers: {
148
+ "Cookie": `LEETCODE_SESSION=${credentials.session}; csrftoken=${credentials.csrfToken}`,
149
+ "X-CSRFToken": credentials.csrfToken
150
+ }
151
+ });
152
+ }
153
+ getCredentials() {
154
+ return this.credentials;
155
+ }
156
+ async graphql(query, variables = {}) {
157
+ const response = await this.client.post("graphql", {
158
+ json: { query, variables }
159
+ }).json();
160
+ if (response.errors?.length) {
161
+ throw new Error(`GraphQL Error: ${response.errors[0].message}`);
162
+ }
163
+ return response.data;
164
+ }
165
+ async checkAuth() {
166
+ const data = await this.graphql(USER_STATUS_QUERY);
167
+ return data.userStatus;
168
+ }
169
+ async getProblems(filters = {}) {
170
+ const variables = {
171
+ categorySlug: "",
172
+ limit: filters.limit ?? 50,
173
+ skip: filters.skip ?? 0,
174
+ filters: {}
175
+ };
176
+ if (filters.difficulty) {
177
+ variables.filters.difficulty = filters.difficulty;
178
+ }
179
+ if (filters.status) {
180
+ variables.filters.status = filters.status;
181
+ }
182
+ if (filters.tags?.length) {
183
+ variables.filters.tags = filters.tags;
184
+ }
185
+ if (filters.searchKeywords) {
186
+ variables.filters.searchKeywords = filters.searchKeywords;
187
+ }
188
+ const data = await this.graphql(PROBLEM_LIST_QUERY, variables);
189
+ return {
190
+ total: data.problemsetQuestionList.total,
191
+ problems: data.problemsetQuestionList.questions
192
+ };
193
+ }
194
+ async getProblem(titleSlug) {
195
+ const data = await this.graphql(
196
+ PROBLEM_DETAIL_QUERY,
197
+ { titleSlug }
198
+ );
199
+ return data.question;
200
+ }
201
+ async getProblemById(id) {
202
+ const { problems } = await this.getProblems({ searchKeywords: id, limit: 10 });
203
+ const problem = problems.find((p) => p.questionFrontendId === id);
204
+ if (!problem) {
205
+ throw new Error(`Problem #${id} not found`);
206
+ }
207
+ return this.getProblem(problem.titleSlug);
208
+ }
209
+ async getDailyChallenge() {
210
+ const data = await this.graphql(DAILY_CHALLENGE_QUERY);
211
+ return data.activeDailyCodingChallengeQuestion;
212
+ }
213
+ async getUserProfile(username) {
214
+ const data = await this.graphql(USER_PROFILE_QUERY, { username });
215
+ const user = data.matchedUser;
216
+ return {
217
+ username: user.username,
218
+ realName: user.profile.realName,
219
+ ranking: user.profile.ranking,
220
+ acSubmissionNum: user.submitStatsGlobal.acSubmissionNum,
221
+ streak: user.userCalendar.streak,
222
+ totalActiveDays: user.userCalendar.totalActiveDays
223
+ };
224
+ }
225
+ async testSolution(titleSlug, code, lang, testcases, questionId) {
226
+ const response = await this.client.post(`problems/${titleSlug}/interpret_solution/`, {
227
+ json: {
228
+ data_input: testcases,
229
+ lang,
230
+ typed_code: code,
231
+ question_id: questionId
232
+ }
233
+ }).json();
234
+ return this.pollSubmission(response.interpret_id, "interpret");
235
+ }
236
+ async submitSolution(titleSlug, code, lang, questionId) {
237
+ const response = await this.client.post(`problems/${titleSlug}/submit/`, {
238
+ json: {
239
+ lang,
240
+ typed_code: code,
241
+ question_id: questionId
242
+ }
243
+ }).json();
244
+ return this.pollSubmission(response.submission_id.toString(), "submission");
245
+ }
246
+ async pollSubmission(id, type) {
247
+ const endpoint = type === "interpret" ? `submissions/detail/${id}/check/` : `submissions/detail/${id}/check/`;
248
+ const maxAttempts = 30;
249
+ const delay = 1e3;
250
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
251
+ const result = await this.client.get(endpoint).json();
252
+ if (result.state === "SUCCESS" || result.state === "FAILURE") {
253
+ return result;
254
+ }
255
+ await new Promise((resolve) => setTimeout(resolve, delay));
256
+ }
257
+ throw new Error("Submission timeout: Result not available after 30 seconds");
258
+ }
259
+ };
260
+ var leetcodeClient = new LeetCodeClient();
261
+
262
+ // src/storage/config.ts
263
+ import Conf from "conf";
264
+ import { homedir } from "os";
265
+ import { join } from "path";
266
+ var schema = {
267
+ credentials: {
268
+ type: "object",
269
+ nullable: true,
270
+ properties: {
271
+ csrfToken: { type: "string" },
272
+ session: { type: "string" }
273
+ }
274
+ },
275
+ config: {
276
+ type: "object",
277
+ properties: {
278
+ language: { type: "string", default: "typescript" },
279
+ editor: { type: "string" },
280
+ workDir: { type: "string", default: join(homedir(), "leetcode") }
281
+ },
282
+ default: {
283
+ language: "typescript",
284
+ workDir: join(homedir(), "leetcode")
285
+ }
286
+ }
287
+ };
288
+ var configStore = new Conf({
289
+ projectName: "leetcode-cli",
290
+ cwd: join(homedir(), ".leetcode"),
291
+ schema
292
+ });
293
+ var config = {
294
+ // Credentials
295
+ getCredentials() {
296
+ return configStore.get("credentials") ?? null;
297
+ },
298
+ setCredentials(credentials) {
299
+ configStore.set("credentials", credentials);
300
+ },
301
+ clearCredentials() {
302
+ configStore.delete("credentials");
303
+ },
304
+ // User Config
305
+ getConfig() {
306
+ return configStore.get("config");
307
+ },
308
+ setLanguage(language) {
309
+ configStore.set("config.language", language);
310
+ },
311
+ setEditor(editor) {
312
+ configStore.set("config.editor", editor);
313
+ },
314
+ setWorkDir(workDir) {
315
+ configStore.set("config.workDir", workDir);
316
+ },
317
+ // Get specific config values
318
+ getLanguage() {
319
+ return configStore.get("config.language");
320
+ },
321
+ getEditor() {
322
+ return configStore.get("config.editor");
323
+ },
324
+ getWorkDir() {
325
+ return configStore.get("config.workDir");
326
+ },
327
+ // Clear all config
328
+ clear() {
329
+ configStore.clear();
330
+ },
331
+ // Get config file path (for debugging)
332
+ getPath() {
333
+ return configStore.path;
334
+ }
335
+ };
336
+
337
+ // src/commands/login.ts
338
+ async function loginCommand() {
339
+ console.log();
340
+ console.log(chalk.cyan("LeetCode CLI Login"));
341
+ console.log(chalk.gray("\u2500".repeat(40)));
342
+ console.log();
343
+ console.log(chalk.yellow("To login, you need to provide your LeetCode session cookies."));
344
+ console.log(chalk.gray("1. Open https://leetcode.com in your browser"));
345
+ console.log(chalk.gray("2. Login to your account"));
346
+ console.log(chalk.gray("3. Open DevTools (F12) \u2192 Application \u2192 Cookies \u2192 leetcode.com"));
347
+ console.log(chalk.gray("4. Copy the values of LEETCODE_SESSION and csrftoken"));
348
+ console.log();
349
+ const answers = await inquirer.prompt([
350
+ {
351
+ type: "password",
352
+ name: "session",
353
+ message: "LEETCODE_SESSION:",
354
+ mask: "*",
355
+ validate: (input) => input.length > 0 || "Session token is required"
356
+ },
357
+ {
358
+ type: "password",
359
+ name: "csrfToken",
360
+ message: "csrftoken:",
361
+ mask: "*",
362
+ validate: (input) => input.length > 0 || "CSRF token is required"
363
+ }
364
+ ]);
365
+ const credentials = {
366
+ session: answers.session.trim(),
367
+ csrfToken: answers.csrfToken.trim()
368
+ };
369
+ const spinner = ora("Verifying credentials...").start();
370
+ try {
371
+ leetcodeClient.setCredentials(credentials);
372
+ const { isSignedIn, username } = await leetcodeClient.checkAuth();
373
+ if (!isSignedIn || !username) {
374
+ spinner.fail("Invalid credentials");
375
+ console.log(chalk.red("Please check your session cookies and try again."));
376
+ return;
377
+ }
378
+ config.setCredentials(credentials);
379
+ spinner.succeed(`Logged in as ${chalk.green(username)}`);
380
+ console.log();
381
+ console.log(chalk.gray(`Credentials saved to ${config.getPath()}`));
382
+ } catch (error) {
383
+ spinner.fail("Authentication failed");
384
+ if (error instanceof Error) {
385
+ console.log(chalk.red(error.message));
386
+ }
387
+ }
388
+ }
389
+ async function logoutCommand() {
390
+ config.clearCredentials();
391
+ console.log(chalk.green("\u2713 Logged out successfully"));
392
+ }
393
+ async function whoamiCommand() {
394
+ const credentials = config.getCredentials();
395
+ if (!credentials) {
396
+ console.log(chalk.yellow('Not logged in. Run "leetcode login" to authenticate.'));
397
+ return;
398
+ }
399
+ const spinner = ora("Checking session...").start();
400
+ try {
401
+ leetcodeClient.setCredentials(credentials);
402
+ const { isSignedIn, username } = await leetcodeClient.checkAuth();
403
+ if (!isSignedIn || !username) {
404
+ spinner.fail("Session expired");
405
+ console.log(chalk.yellow('Please run "leetcode login" to re-authenticate.'));
406
+ return;
407
+ }
408
+ spinner.succeed(`Logged in as ${chalk.green(username)}`);
409
+ } catch (error) {
410
+ spinner.fail("Failed to check session");
411
+ if (error instanceof Error) {
412
+ console.log(chalk.red(error.message));
413
+ }
414
+ }
415
+ }
416
+
417
+ // src/commands/list.ts
418
+ import ora2 from "ora";
419
+ import chalk3 from "chalk";
420
+
421
+ // src/utils/display.ts
422
+ import chalk2 from "chalk";
423
+ import Table from "cli-table3";
424
+ function displayProblemList(problems, total) {
425
+ const table = new Table({
426
+ head: [
427
+ chalk2.cyan("ID"),
428
+ chalk2.cyan("Title"),
429
+ chalk2.cyan("Difficulty"),
430
+ chalk2.cyan("Rate"),
431
+ chalk2.cyan("Status")
432
+ ],
433
+ colWidths: [8, 45, 12, 10, 10],
434
+ style: { head: [], border: [] }
435
+ });
436
+ for (const problem of problems) {
437
+ table.push([
438
+ problem.questionFrontendId,
439
+ problem.title.length > 42 ? problem.title.slice(0, 39) + "..." : problem.title,
440
+ colorDifficulty(problem.difficulty),
441
+ `${problem.acRate.toFixed(1)}%`,
442
+ formatStatus(problem.status)
443
+ ]);
444
+ }
445
+ console.log(table.toString());
446
+ console.log(chalk2.gray(`
447
+ Showing ${problems.length} of ${total} problems`));
448
+ }
449
+ function displayProblemDetail(problem) {
450
+ console.log();
451
+ console.log(chalk2.bold.cyan(` ${problem.questionFrontendId}. ${problem.title}`));
452
+ console.log(` ${colorDifficulty(problem.difficulty)}`);
453
+ console.log(chalk2.gray(` https://leetcode.com/problems/${problem.titleSlug}/`));
454
+ console.log();
455
+ if (problem.topicTags.length) {
456
+ const tags = problem.topicTags.map((t) => chalk2.bgBlue.white(` ${t.name} `)).join(" ");
457
+ console.log(` ${tags}`);
458
+ console.log();
459
+ }
460
+ console.log(chalk2.gray("\u2500".repeat(60)));
461
+ console.log();
462
+ let content = problem.content;
463
+ content = content.replace(/<sup>(.*?)<\/sup>/gi, "^$1");
464
+ content = content.replace(/<strong class="example">Example (\d+):<\/strong>/gi, "\xA7EXAMPLE\xA7$1\xA7");
465
+ content = content.replace(/Input:/gi, "\xA7INPUT\xA7");
466
+ content = content.replace(/Output:/gi, "\xA7OUTPUT\xA7");
467
+ content = content.replace(/Explanation:/gi, "\xA7EXPLAIN\xA7");
468
+ content = content.replace(/<strong>Constraints:<\/strong>/gi, "\xA7CONSTRAINTS\xA7");
469
+ content = content.replace(/Constraints:/gi, "\xA7CONSTRAINTS\xA7");
470
+ content = content.replace(/<strong>Follow-up:/gi, "\xA7FOLLOWUP\xA7");
471
+ content = content.replace(/Follow-up:/gi, "\xA7FOLLOWUP\xA7");
472
+ content = content.replace(/<li>/gi, " \u2022 ");
473
+ content = content.replace(/<\/li>/gi, "\n");
474
+ content = content.replace(/<\/p>/gi, "\n\n");
475
+ content = content.replace(/<br\s*\/?>/gi, "\n");
476
+ content = content.replace(/<[^>]+>/g, "");
477
+ content = content.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&le;/g, "\u2264").replace(/&ge;/g, "\u2265").replace(/&#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code, 10)));
478
+ content = content.replace(/\n{3,}/g, "\n\n").trim();
479
+ content = content.replace(/§EXAMPLE§(\d+)§/g, (_, num) => chalk2.green.bold(`\u{1F4CC} Example ${num}:`));
480
+ content = content.replace(/§INPUT§/g, chalk2.yellow("Input:"));
481
+ content = content.replace(/§OUTPUT§/g, chalk2.yellow("Output:"));
482
+ content = content.replace(/§EXPLAIN§/g, chalk2.gray("Explanation:"));
483
+ content = content.replace(/§CONSTRAINTS§/g, chalk2.cyan.bold("\n\u{1F4CB} Constraints:"));
484
+ content = content.replace(/§FOLLOWUP§/g, chalk2.magenta.bold("\n\u{1F4A1} Follow-up:"));
485
+ console.log(content);
486
+ console.log();
487
+ }
488
+ function displayTestResult(result) {
489
+ console.log();
490
+ if (result.compile_error) {
491
+ console.log(chalk2.red.bold("\u274C Compile Error"));
492
+ console.log(chalk2.red(result.compile_error));
493
+ return;
494
+ }
495
+ if (result.runtime_error) {
496
+ console.log(chalk2.red.bold("\u274C Runtime Error"));
497
+ console.log(chalk2.red(result.runtime_error));
498
+ return;
499
+ }
500
+ if (result.correct_answer) {
501
+ console.log(chalk2.green.bold("\u2713 All test cases passed!"));
502
+ } else {
503
+ console.log(chalk2.yellow.bold("\u2717 Some test cases failed"));
504
+ }
505
+ console.log();
506
+ console.log(chalk2.gray("Your Output:"));
507
+ for (const output of result.code_answer) {
508
+ console.log(chalk2.white(` ${output}`));
509
+ }
510
+ console.log();
511
+ console.log(chalk2.gray("Expected Output:"));
512
+ for (const output of result.expected_code_answer) {
513
+ console.log(chalk2.white(` ${output}`));
514
+ }
515
+ if (result.std_output_list?.length) {
516
+ console.log();
517
+ console.log(chalk2.gray("Stdout:"));
518
+ for (const output of result.std_output_list) {
519
+ if (output) console.log(chalk2.gray(` ${output}`));
520
+ }
521
+ }
522
+ }
523
+ function displaySubmissionResult(result) {
524
+ console.log();
525
+ if (result.compile_error) {
526
+ console.log(chalk2.red.bold("\u274C Compile Error"));
527
+ console.log(chalk2.red(result.compile_error));
528
+ return;
529
+ }
530
+ if (result.runtime_error) {
531
+ console.log(chalk2.red.bold("\u274C Runtime Error"));
532
+ console.log(chalk2.red(result.runtime_error));
533
+ if (result.last_testcase) {
534
+ console.log(chalk2.gray("Last testcase:"), result.last_testcase);
535
+ }
536
+ return;
537
+ }
538
+ if (result.status_msg === "Accepted") {
539
+ console.log(chalk2.green.bold("\u2713 Accepted!"));
540
+ console.log();
541
+ console.log(
542
+ chalk2.gray("Runtime:"),
543
+ chalk2.white(result.status_runtime),
544
+ chalk2.gray(`(beats ${result.runtime_percentile?.toFixed(1) ?? "N/A"}%)`)
545
+ );
546
+ console.log(
547
+ chalk2.gray("Memory:"),
548
+ chalk2.white(result.status_memory),
549
+ chalk2.gray(`(beats ${result.memory_percentile?.toFixed(1) ?? "N/A"}%)`)
550
+ );
551
+ } else {
552
+ console.log(chalk2.red.bold(`\u274C ${result.status_msg}`));
553
+ console.log();
554
+ console.log(chalk2.gray(`Passed ${result.total_correct}/${result.total_testcases} testcases`));
555
+ if (result.code_output) {
556
+ console.log(chalk2.gray("Your Output:"), result.code_output);
557
+ }
558
+ if (result.expected_output) {
559
+ console.log(chalk2.gray("Expected:"), result.expected_output);
560
+ }
561
+ if (result.last_testcase) {
562
+ console.log(chalk2.gray("Failed testcase:"), result.last_testcase);
563
+ }
564
+ }
565
+ }
566
+ function displayUserStats(username, realName, ranking, acStats, streak, totalActiveDays) {
567
+ console.log();
568
+ console.log(chalk2.bold.white(`\u{1F464} ${username}`) + (realName ? chalk2.gray(` (${realName})`) : ""));
569
+ console.log(chalk2.gray(`Ranking: #${ranking.toLocaleString()}`));
570
+ console.log();
571
+ const table = new Table({
572
+ head: [chalk2.cyan("Difficulty"), chalk2.cyan("Solved")],
573
+ style: { head: [], border: [] }
574
+ });
575
+ for (const stat of acStats) {
576
+ if (stat.difficulty !== "All") {
577
+ table.push([
578
+ colorDifficulty(stat.difficulty),
579
+ stat.count.toString()
580
+ ]);
581
+ }
582
+ }
583
+ const total = acStats.find((s) => s.difficulty === "All")?.count ?? 0;
584
+ table.push([chalk2.white.bold("Total"), chalk2.white.bold(total.toString())]);
585
+ console.log(table.toString());
586
+ console.log();
587
+ console.log(chalk2.gray("\u{1F525} Current streak:"), chalk2.hex("#FFA500")(streak.toString()), chalk2.gray("days"));
588
+ console.log(chalk2.gray("\u{1F4C5} Total active days:"), chalk2.white(totalActiveDays.toString()));
589
+ }
590
+ function displayDailyChallenge(date, problem) {
591
+ console.log();
592
+ console.log(chalk2.bold.yellow("\u{1F3AF} Daily Challenge"), chalk2.gray(`(${date})`));
593
+ console.log();
594
+ console.log(chalk2.white(`${problem.questionFrontendId}. ${problem.title}`));
595
+ console.log(colorDifficulty(problem.difficulty));
596
+ console.log(chalk2.gray(`https://leetcode.com/problems/${problem.titleSlug}/`));
597
+ if (problem.topicTags.length) {
598
+ console.log();
599
+ const tags = problem.topicTags.map((t) => chalk2.blue(t.name)).join(" ");
600
+ console.log(chalk2.gray("Tags:"), tags);
601
+ }
602
+ }
603
+ function colorDifficulty(difficulty) {
604
+ switch (difficulty.toLowerCase()) {
605
+ case "easy":
606
+ return chalk2.green(difficulty);
607
+ case "medium":
608
+ return chalk2.yellow(difficulty);
609
+ case "hard":
610
+ return chalk2.red(difficulty);
611
+ default:
612
+ return difficulty;
613
+ }
614
+ }
615
+ function formatStatus(status) {
616
+ switch (status) {
617
+ case "ac":
618
+ return chalk2.green("\u2713");
619
+ case "notac":
620
+ return chalk2.yellow("\u25CB");
621
+ default:
622
+ return chalk2.gray("-");
623
+ }
624
+ }
625
+
626
+ // src/commands/list.ts
627
+ async function listCommand(options) {
628
+ const credentials = config.getCredentials();
629
+ if (credentials) {
630
+ leetcodeClient.setCredentials(credentials);
631
+ }
632
+ const spinner = ora2("Fetching problems...").start();
633
+ try {
634
+ const filters = {};
635
+ const limit = parseInt(options.limit ?? "20", 10);
636
+ const page = parseInt(options.page ?? "1", 10);
637
+ filters.limit = limit;
638
+ filters.skip = (page - 1) * limit;
639
+ if (options.difficulty) {
640
+ const diffMap = {
641
+ easy: "EASY",
642
+ e: "EASY",
643
+ medium: "MEDIUM",
644
+ m: "MEDIUM",
645
+ hard: "HARD",
646
+ h: "HARD"
647
+ };
648
+ filters.difficulty = diffMap[options.difficulty.toLowerCase()];
649
+ }
650
+ if (options.status) {
651
+ const statusMap = {
652
+ todo: "NOT_STARTED",
653
+ solved: "AC",
654
+ ac: "AC",
655
+ attempted: "TRIED",
656
+ tried: "TRIED"
657
+ };
658
+ filters.status = statusMap[options.status.toLowerCase()];
659
+ }
660
+ if (options.tag?.length) {
661
+ filters.tags = options.tag;
662
+ }
663
+ if (options.search) {
664
+ filters.searchKeywords = options.search;
665
+ }
666
+ const { total, problems } = await leetcodeClient.getProblems(filters);
667
+ spinner.stop();
668
+ if (problems.length === 0) {
669
+ console.log(chalk3.yellow("No problems found matching your criteria."));
670
+ return;
671
+ }
672
+ displayProblemList(problems, total);
673
+ if (page * limit < total) {
674
+ console.log(chalk3.gray(`
675
+ Page ${page} of ${Math.ceil(total / limit)}. Use --page to navigate.`));
676
+ }
677
+ } catch (error) {
678
+ spinner.fail("Failed to fetch problems");
679
+ if (error instanceof Error) {
680
+ console.log(chalk3.red(error.message));
681
+ }
682
+ }
683
+ }
684
+
685
+ // src/commands/show.ts
686
+ import ora3 from "ora";
687
+ import chalk4 from "chalk";
688
+ async function showCommand(idOrSlug) {
689
+ const credentials = config.getCredentials();
690
+ if (credentials) {
691
+ leetcodeClient.setCredentials(credentials);
692
+ }
693
+ const spinner = ora3("Fetching problem...").start();
694
+ try {
695
+ let problem;
696
+ if (/^\d+$/.test(idOrSlug)) {
697
+ problem = await leetcodeClient.getProblemById(idOrSlug);
698
+ } else {
699
+ problem = await leetcodeClient.getProblem(idOrSlug);
700
+ }
701
+ spinner.stop();
702
+ displayProblemDetail(problem);
703
+ } catch (error) {
704
+ spinner.fail("Failed to fetch problem");
705
+ if (error instanceof Error) {
706
+ console.log(chalk4.red(error.message));
707
+ }
708
+ }
709
+ }
710
+
711
+ // src/commands/pick.ts
712
+ import { writeFile, mkdir } from "fs/promises";
713
+ import { existsSync } from "fs";
714
+ import { join as join2 } from "path";
715
+ import { spawn } from "child_process";
716
+ import ora4 from "ora";
717
+ import chalk5 from "chalk";
718
+
719
+ // src/utils/templates.ts
720
+ var LANGUAGE_EXTENSIONS = {
721
+ typescript: "ts",
722
+ javascript: "js",
723
+ python3: "py",
724
+ java: "java",
725
+ cpp: "cpp",
726
+ c: "c",
727
+ csharp: "cs",
728
+ go: "go",
729
+ rust: "rs",
730
+ kotlin: "kt",
731
+ swift: "swift"
732
+ };
733
+ var LANG_SLUG_MAP = {
734
+ typescript: "typescript",
735
+ javascript: "javascript",
736
+ python3: "python3",
737
+ python: "python3",
738
+ java: "java",
739
+ "c++": "cpp",
740
+ cpp: "cpp",
741
+ c: "c",
742
+ "c#": "csharp",
743
+ csharp: "csharp",
744
+ go: "go",
745
+ golang: "go",
746
+ rust: "rust",
747
+ kotlin: "kotlin",
748
+ swift: "swift"
749
+ };
750
+ function getCodeTemplate(snippets, preferredLanguage) {
751
+ const preferred = snippets.find(
752
+ (s) => LANG_SLUG_MAP[s.langSlug.toLowerCase()] === preferredLanguage
753
+ );
754
+ if (preferred) return preferred;
755
+ return snippets[0] ?? null;
756
+ }
757
+ function generateSolutionFile(problemId, titleSlug, title, difficulty, codeSnippet, language, problemContent) {
758
+ const commentStyle = getCommentStyle(language);
759
+ const header = generateHeader(commentStyle, problemId, title, difficulty, titleSlug, problemContent);
760
+ return `${header}
761
+
762
+ ${codeSnippet}
763
+ `;
764
+ }
765
+ function getCommentStyle(language) {
766
+ switch (language) {
767
+ case "python3":
768
+ return { single: "#", blockStart: '"""', blockEnd: '"""', linePrefix: "" };
769
+ case "c":
770
+ case "cpp":
771
+ case "java":
772
+ case "typescript":
773
+ case "javascript":
774
+ case "go":
775
+ case "rust":
776
+ case "kotlin":
777
+ case "swift":
778
+ case "csharp":
779
+ default:
780
+ return { single: "//", blockStart: "/*", blockEnd: "*/", linePrefix: " * " };
781
+ }
782
+ }
783
+ function formatProblemContent(html) {
784
+ let content = html;
785
+ content = content.replace(/<sup>(.*?)<\/sup>/gi, "^$1");
786
+ content = content.replace(/<strong class="example">Example (\d+):<\/strong>/gi, "\nExample $1:");
787
+ content = content.replace(/<li>/gi, "\u2022 ");
788
+ content = content.replace(/<\/li>/gi, "\n");
789
+ content = content.replace(/<\/p>/gi, "\n\n");
790
+ content = content.replace(/<br\s*\/?>/gi, "\n");
791
+ content = content.replace(/<[^>]+>/g, "");
792
+ content = content.replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&le;/g, "<=").replace(/&ge;/g, ">=").replace(/&#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code, 10)));
793
+ content = content.replace(/\n{3,}/g, "\n\n").trim();
794
+ return content;
795
+ }
796
+ function generateHeader(style, problemId, title, difficulty, titleSlug, problemContent) {
797
+ const prefix = style.linePrefix;
798
+ const lines = [
799
+ style.blockStart,
800
+ `${prefix}${problemId}. ${title}`,
801
+ `${prefix}Difficulty: ${difficulty}`,
802
+ `${prefix}https://leetcode.com/problems/${titleSlug}/`
803
+ ];
804
+ if (problemContent) {
805
+ lines.push(`${prefix}`);
806
+ lines.push(`${prefix}${"\u2500".repeat(50)}`);
807
+ lines.push(`${prefix}`);
808
+ const formattedContent = formatProblemContent(problemContent);
809
+ const contentLines = formattedContent.split("\n");
810
+ for (const line of contentLines) {
811
+ if (line.length > 70) {
812
+ const words = line.split(" ");
813
+ let currentLine = "";
814
+ for (const word of words) {
815
+ if ((currentLine + " " + word).length > 70) {
816
+ lines.push(`${prefix}${currentLine.trim()}`);
817
+ currentLine = word;
818
+ } else {
819
+ currentLine += " " + word;
820
+ }
821
+ }
822
+ if (currentLine.trim()) {
823
+ lines.push(`${prefix}${currentLine.trim()}`);
824
+ }
825
+ } else {
826
+ lines.push(`${prefix}${line}`);
827
+ }
828
+ }
829
+ }
830
+ lines.push(style.blockEnd);
831
+ return lines.join("\n");
832
+ }
833
+ function getSolutionFileName(problemId, titleSlug, language) {
834
+ const ext = LANGUAGE_EXTENSIONS[language];
835
+ return `${problemId}.${titleSlug}.${ext}`;
836
+ }
837
+
838
+ // src/commands/pick.ts
839
+ async function pickCommand(idOrSlug, options) {
840
+ const credentials = config.getCredentials();
841
+ if (credentials) {
842
+ leetcodeClient.setCredentials(credentials);
843
+ }
844
+ const spinner = ora4("Fetching problem...").start();
845
+ try {
846
+ let problem;
847
+ if (/^\d+$/.test(idOrSlug)) {
848
+ problem = await leetcodeClient.getProblemById(idOrSlug);
849
+ } else {
850
+ problem = await leetcodeClient.getProblem(idOrSlug);
851
+ }
852
+ spinner.text = "Generating solution file...";
853
+ const langInput = options.lang?.toLowerCase() ?? config.getLanguage();
854
+ const language = LANG_SLUG_MAP[langInput] ?? langInput;
855
+ const template = getCodeTemplate(problem.codeSnippets, language);
856
+ if (!template) {
857
+ spinner.fail(`No code template available for ${language}`);
858
+ return;
859
+ }
860
+ const content = generateSolutionFile(
861
+ problem.questionFrontendId,
862
+ problem.titleSlug,
863
+ problem.title,
864
+ problem.difficulty,
865
+ template.code,
866
+ language,
867
+ problem.content
868
+ );
869
+ const workDir = config.getWorkDir();
870
+ const difficulty = problem.difficulty;
871
+ const category = problem.topicTags.length > 0 ? problem.topicTags[0].name.replace(/[^\w\s-]/g, "").trim() : "Uncategorized";
872
+ const targetDir = join2(workDir, difficulty, category);
873
+ if (!existsSync(targetDir)) {
874
+ await mkdir(targetDir, { recursive: true });
875
+ }
876
+ const fileName = getSolutionFileName(problem.questionFrontendId, problem.titleSlug, language);
877
+ const filePath = join2(targetDir, fileName);
878
+ if (existsSync(filePath)) {
879
+ spinner.warn(`File already exists: ${fileName}`);
880
+ console.log(chalk5.gray(`Path: ${filePath}`));
881
+ if (options.open !== false) {
882
+ openInEditor(filePath);
883
+ }
884
+ return;
885
+ }
886
+ await writeFile(filePath, content, "utf-8");
887
+ spinner.succeed(`Created ${chalk5.green(fileName)}`);
888
+ console.log(chalk5.gray(`Path: ${filePath}`));
889
+ console.log();
890
+ console.log(chalk5.cyan(`${problem.questionFrontendId}. ${problem.title}`));
891
+ console.log(chalk5.gray(`Difficulty: ${problem.difficulty} | Category: ${category}`));
892
+ if (options.open !== false) {
893
+ openInEditor(filePath);
894
+ }
895
+ } catch (error) {
896
+ spinner.fail("Failed to create solution file");
897
+ if (error instanceof Error) {
898
+ console.log(chalk5.red(error.message));
899
+ }
900
+ }
901
+ }
902
+ function openInEditor(filePath) {
903
+ const editor = config.getEditor() ?? process.env.EDITOR ?? "code";
904
+ const workDir = config.getWorkDir();
905
+ const terminalEditors = ["vim", "nvim", "vi", "nano", "emacs", "micro"];
906
+ if (terminalEditors.includes(editor)) {
907
+ console.log();
908
+ console.log(chalk5.gray(`Open with: ${editor} ${filePath}`));
909
+ return;
910
+ }
911
+ try {
912
+ if (editor === "code" || editor === "code-insiders" || editor === "cursor") {
913
+ const child = spawn(editor, ["-r", workDir, "-g", filePath], {
914
+ detached: true,
915
+ stdio: "ignore"
916
+ });
917
+ child.unref();
918
+ } else {
919
+ const child = spawn(editor, [filePath], {
920
+ detached: true,
921
+ stdio: "ignore"
922
+ });
923
+ child.unref();
924
+ }
925
+ } catch {
926
+ }
927
+ }
928
+
929
+ // src/commands/test.ts
930
+ import { readFile, readdir } from "fs/promises";
931
+ import { existsSync as existsSync2 } from "fs";
932
+ import { basename, join as join3 } from "path";
933
+ import ora5 from "ora";
934
+ import chalk6 from "chalk";
935
+ async function findSolutionFile(dir, problemId) {
936
+ if (!existsSync2(dir)) return null;
937
+ const entries = await readdir(dir, { withFileTypes: true });
938
+ for (const entry of entries) {
939
+ const fullPath = join3(dir, entry.name);
940
+ if (entry.isDirectory()) {
941
+ const found = await findSolutionFile(fullPath, problemId);
942
+ if (found) return found;
943
+ } else if (entry.name.startsWith(`${problemId}.`)) {
944
+ return fullPath;
945
+ }
946
+ }
947
+ return null;
948
+ }
949
+ async function findFileByName(dir, fileName) {
950
+ if (!existsSync2(dir)) return null;
951
+ const entries = await readdir(dir, { withFileTypes: true });
952
+ for (const entry of entries) {
953
+ const fullPath = join3(dir, entry.name);
954
+ if (entry.isDirectory()) {
955
+ const found = await findFileByName(fullPath, fileName);
956
+ if (found) return found;
957
+ } else if (entry.name === fileName) {
958
+ return fullPath;
959
+ }
960
+ }
961
+ return null;
962
+ }
963
+ async function testCommand(fileOrId, options) {
964
+ const credentials = config.getCredentials();
965
+ if (!credentials) {
966
+ console.log(chalk6.yellow("Please login first: leetcode login"));
967
+ return;
968
+ }
969
+ leetcodeClient.setCredentials(credentials);
970
+ let filePath = fileOrId;
971
+ if (/^\d+$/.test(fileOrId)) {
972
+ const workDir = config.getWorkDir();
973
+ const found = await findSolutionFile(workDir, fileOrId);
974
+ if (!found) {
975
+ console.log(chalk6.red(`No solution file found for problem ${fileOrId}`));
976
+ console.log(chalk6.gray(`Looking in: ${workDir}`));
977
+ console.log(chalk6.gray('Run "leetcode pick ' + fileOrId + '" first to create a solution file.'));
978
+ return;
979
+ }
980
+ filePath = found;
981
+ console.log(chalk6.gray(`Found: ${filePath}`));
982
+ } else if (!fileOrId.includes("/") && !fileOrId.includes("\\") && fileOrId.includes(".")) {
983
+ const workDir = config.getWorkDir();
984
+ const found = await findFileByName(workDir, fileOrId);
985
+ if (!found) {
986
+ console.log(chalk6.red(`File not found: ${fileOrId}`));
987
+ console.log(chalk6.gray(`Looking in: ${workDir}`));
988
+ return;
989
+ }
990
+ filePath = found;
991
+ console.log(chalk6.gray(`Found: ${filePath}`));
992
+ }
993
+ if (!existsSync2(filePath)) {
994
+ console.log(chalk6.red(`File not found: ${filePath}`));
995
+ return;
996
+ }
997
+ const spinner = ora5("Reading solution file...").start();
998
+ try {
999
+ const fileName = basename(filePath);
1000
+ const match = fileName.match(/^(\d+)\.([^.]+)\./);
1001
+ if (!match) {
1002
+ spinner.fail("Invalid filename format");
1003
+ console.log(chalk6.gray("Expected format: {id}.{title-slug}.{ext}"));
1004
+ console.log(chalk6.gray("Example: 1.two-sum.ts"));
1005
+ return;
1006
+ }
1007
+ const [, problemId, titleSlug] = match;
1008
+ const ext = fileName.split(".").pop();
1009
+ const langMap = {
1010
+ ts: "typescript",
1011
+ js: "javascript",
1012
+ py: "python3",
1013
+ java: "java",
1014
+ cpp: "cpp",
1015
+ c: "c",
1016
+ cs: "csharp",
1017
+ go: "golang",
1018
+ rs: "rust",
1019
+ kt: "kotlin",
1020
+ swift: "swift"
1021
+ };
1022
+ const lang = langMap[ext];
1023
+ if (!lang) {
1024
+ spinner.fail(`Unsupported file extension: .${ext}`);
1025
+ return;
1026
+ }
1027
+ const code = await readFile(filePath, "utf-8");
1028
+ spinner.text = "Fetching problem details...";
1029
+ const problem = await leetcodeClient.getProblem(titleSlug);
1030
+ const testcases = options.testcase ?? problem.exampleTestcases ?? problem.sampleTestCase;
1031
+ spinner.text = "Running tests...";
1032
+ const result = await leetcodeClient.testSolution(titleSlug, code, lang, testcases, problem.questionId);
1033
+ spinner.stop();
1034
+ displayTestResult(result);
1035
+ } catch (error) {
1036
+ spinner.fail("Test failed");
1037
+ if (error instanceof Error) {
1038
+ console.log(chalk6.red(error.message));
1039
+ }
1040
+ }
1041
+ }
1042
+
1043
+ // src/commands/submit.ts
1044
+ import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
1045
+ import { existsSync as existsSync3 } from "fs";
1046
+ import { basename as basename2, join as join4 } from "path";
1047
+ import ora6 from "ora";
1048
+ import chalk7 from "chalk";
1049
+ async function findSolutionFile2(dir, problemId) {
1050
+ if (!existsSync3(dir)) return null;
1051
+ const entries = await readdir2(dir, { withFileTypes: true });
1052
+ for (const entry of entries) {
1053
+ const fullPath = join4(dir, entry.name);
1054
+ if (entry.isDirectory()) {
1055
+ const found = await findSolutionFile2(fullPath, problemId);
1056
+ if (found) return found;
1057
+ } else if (entry.name.startsWith(`${problemId}.`)) {
1058
+ return fullPath;
1059
+ }
1060
+ }
1061
+ return null;
1062
+ }
1063
+ async function findFileByName2(dir, fileName) {
1064
+ if (!existsSync3(dir)) return null;
1065
+ const entries = await readdir2(dir, { withFileTypes: true });
1066
+ for (const entry of entries) {
1067
+ const fullPath = join4(dir, entry.name);
1068
+ if (entry.isDirectory()) {
1069
+ const found = await findFileByName2(fullPath, fileName);
1070
+ if (found) return found;
1071
+ } else if (entry.name === fileName) {
1072
+ return fullPath;
1073
+ }
1074
+ }
1075
+ return null;
1076
+ }
1077
+ async function submitCommand(fileOrId) {
1078
+ const credentials = config.getCredentials();
1079
+ if (!credentials) {
1080
+ console.log(chalk7.yellow("Please login first: leetcode login"));
1081
+ return;
1082
+ }
1083
+ leetcodeClient.setCredentials(credentials);
1084
+ let filePath = fileOrId;
1085
+ if (/^\d+$/.test(fileOrId)) {
1086
+ const workDir = config.getWorkDir();
1087
+ const found = await findSolutionFile2(workDir, fileOrId);
1088
+ if (!found) {
1089
+ console.log(chalk7.red(`No solution file found for problem ${fileOrId}`));
1090
+ console.log(chalk7.gray(`Looking in: ${workDir}`));
1091
+ console.log(chalk7.gray('Run "leetcode pick ' + fileOrId + '" first to create a solution file.'));
1092
+ return;
1093
+ }
1094
+ filePath = found;
1095
+ console.log(chalk7.gray(`Found: ${filePath}`));
1096
+ } else if (!fileOrId.includes("/") && !fileOrId.includes("\\") && fileOrId.includes(".")) {
1097
+ const workDir = config.getWorkDir();
1098
+ const found = await findFileByName2(workDir, fileOrId);
1099
+ if (!found) {
1100
+ console.log(chalk7.red(`File not found: ${fileOrId}`));
1101
+ console.log(chalk7.gray(`Looking in: ${workDir}`));
1102
+ return;
1103
+ }
1104
+ filePath = found;
1105
+ console.log(chalk7.gray(`Found: ${filePath}`));
1106
+ }
1107
+ if (!existsSync3(filePath)) {
1108
+ console.log(chalk7.red(`File not found: ${filePath}`));
1109
+ return;
1110
+ }
1111
+ const spinner = ora6("Reading solution file...").start();
1112
+ try {
1113
+ const fileName = basename2(filePath);
1114
+ const match = fileName.match(/^(\d+)\.([^.]+)\./);
1115
+ if (!match) {
1116
+ spinner.fail("Invalid filename format");
1117
+ console.log(chalk7.gray("Expected format: {id}.{title-slug}.{ext}"));
1118
+ console.log(chalk7.gray("Example: 1.two-sum.ts"));
1119
+ return;
1120
+ }
1121
+ const [, problemId, titleSlug] = match;
1122
+ const ext = fileName.split(".").pop();
1123
+ const langMap = {
1124
+ ts: "typescript",
1125
+ js: "javascript",
1126
+ py: "python3",
1127
+ java: "java",
1128
+ cpp: "cpp",
1129
+ c: "c",
1130
+ cs: "csharp",
1131
+ go: "golang",
1132
+ rs: "rust",
1133
+ kt: "kotlin",
1134
+ swift: "swift"
1135
+ };
1136
+ const lang = langMap[ext];
1137
+ if (!lang) {
1138
+ spinner.fail(`Unsupported file extension: .${ext}`);
1139
+ return;
1140
+ }
1141
+ const code = await readFile2(filePath, "utf-8");
1142
+ spinner.text = "Fetching problem details...";
1143
+ const problem = await leetcodeClient.getProblem(titleSlug);
1144
+ spinner.text = "Submitting solution...";
1145
+ const result = await leetcodeClient.submitSolution(
1146
+ titleSlug,
1147
+ code,
1148
+ lang,
1149
+ problem.questionId
1150
+ );
1151
+ spinner.stop();
1152
+ displaySubmissionResult(result);
1153
+ } catch (error) {
1154
+ spinner.fail("Submission failed");
1155
+ if (error instanceof Error) {
1156
+ console.log(chalk7.red(error.message));
1157
+ }
1158
+ }
1159
+ }
1160
+
1161
+ // src/commands/stat.ts
1162
+ import ora7 from "ora";
1163
+ import chalk8 from "chalk";
1164
+ async function statCommand(username) {
1165
+ const credentials = config.getCredentials();
1166
+ if (!credentials && !username) {
1167
+ console.log(chalk8.yellow("Please login first or provide a username: leetcode stat [username]"));
1168
+ return;
1169
+ }
1170
+ if (credentials) {
1171
+ leetcodeClient.setCredentials(credentials);
1172
+ }
1173
+ const spinner = ora7("Fetching statistics...").start();
1174
+ try {
1175
+ let targetUsername = username;
1176
+ if (!targetUsername && credentials) {
1177
+ const { isSignedIn, username: currentUser } = await leetcodeClient.checkAuth();
1178
+ if (!isSignedIn || !currentUser) {
1179
+ spinner.fail("Session expired");
1180
+ console.log(chalk8.yellow('Please run "leetcode login" to re-authenticate.'));
1181
+ return;
1182
+ }
1183
+ targetUsername = currentUser;
1184
+ }
1185
+ if (!targetUsername) {
1186
+ spinner.fail("No username available");
1187
+ return;
1188
+ }
1189
+ const profile = await leetcodeClient.getUserProfile(targetUsername);
1190
+ spinner.stop();
1191
+ displayUserStats(
1192
+ profile.username,
1193
+ profile.realName,
1194
+ profile.ranking,
1195
+ profile.acSubmissionNum,
1196
+ profile.streak,
1197
+ profile.totalActiveDays
1198
+ );
1199
+ } catch (error) {
1200
+ spinner.fail("Failed to fetch statistics");
1201
+ if (error instanceof Error) {
1202
+ console.log(chalk8.red(error.message));
1203
+ }
1204
+ }
1205
+ }
1206
+
1207
+ // src/commands/daily.ts
1208
+ import ora8 from "ora";
1209
+ import chalk9 from "chalk";
1210
+ async function dailyCommand(options) {
1211
+ const credentials = config.getCredentials();
1212
+ if (credentials) {
1213
+ leetcodeClient.setCredentials(credentials);
1214
+ }
1215
+ const spinner = ora8("Fetching daily challenge...").start();
1216
+ try {
1217
+ const daily = await leetcodeClient.getDailyChallenge();
1218
+ spinner.stop();
1219
+ displayDailyChallenge(daily.date, daily.question);
1220
+ console.log();
1221
+ console.log(chalk9.gray("Run the following to start working on this problem:"));
1222
+ console.log(chalk9.cyan(` leetcode pick ${daily.question.titleSlug}`));
1223
+ } catch (error) {
1224
+ spinner.fail("Failed to fetch daily challenge");
1225
+ if (error instanceof Error) {
1226
+ console.log(chalk9.red(error.message));
1227
+ }
1228
+ }
1229
+ }
1230
+
1231
+ // src/commands/config.ts
1232
+ import inquirer2 from "inquirer";
1233
+ import chalk10 from "chalk";
1234
+ var SUPPORTED_LANGUAGES = [
1235
+ "typescript",
1236
+ "javascript",
1237
+ "python3",
1238
+ "java",
1239
+ "cpp",
1240
+ "c",
1241
+ "csharp",
1242
+ "go",
1243
+ "rust",
1244
+ "kotlin",
1245
+ "swift"
1246
+ ];
1247
+ async function configCommand(options) {
1248
+ if (!options.lang && !options.editor && !options.workdir) {
1249
+ showCurrentConfig();
1250
+ return;
1251
+ }
1252
+ if (options.lang) {
1253
+ const lang = options.lang.toLowerCase();
1254
+ if (!SUPPORTED_LANGUAGES.includes(lang)) {
1255
+ console.log(chalk10.red(`Unsupported language: ${options.lang}`));
1256
+ console.log(chalk10.gray(`Supported: ${SUPPORTED_LANGUAGES.join(", ")}`));
1257
+ return;
1258
+ }
1259
+ config.setLanguage(lang);
1260
+ console.log(chalk10.green(`\u2713 Default language set to ${lang}`));
1261
+ }
1262
+ if (options.editor) {
1263
+ config.setEditor(options.editor);
1264
+ console.log(chalk10.green(`\u2713 Editor set to ${options.editor}`));
1265
+ }
1266
+ if (options.workdir) {
1267
+ config.setWorkDir(options.workdir);
1268
+ console.log(chalk10.green(`\u2713 Work directory set to ${options.workdir}`));
1269
+ }
1270
+ }
1271
+ async function configInteractiveCommand() {
1272
+ const currentConfig = config.getConfig();
1273
+ const answers = await inquirer2.prompt([
1274
+ {
1275
+ type: "list",
1276
+ name: "language",
1277
+ message: "Default programming language:",
1278
+ choices: SUPPORTED_LANGUAGES,
1279
+ default: currentConfig.language
1280
+ },
1281
+ {
1282
+ type: "input",
1283
+ name: "editor",
1284
+ message: "Editor command (e.g., code, vim, nvim):",
1285
+ default: currentConfig.editor ?? "code"
1286
+ },
1287
+ {
1288
+ type: "input",
1289
+ name: "workDir",
1290
+ message: "Working directory for solution files:",
1291
+ default: currentConfig.workDir
1292
+ }
1293
+ ]);
1294
+ config.setLanguage(answers.language);
1295
+ config.setEditor(answers.editor);
1296
+ config.setWorkDir(answers.workDir);
1297
+ console.log();
1298
+ console.log(chalk10.green("\u2713 Configuration saved"));
1299
+ showCurrentConfig();
1300
+ }
1301
+ function showCurrentConfig() {
1302
+ const currentConfig = config.getConfig();
1303
+ const credentials = config.getCredentials();
1304
+ console.log();
1305
+ console.log(chalk10.bold("LeetCode CLI Configuration"));
1306
+ console.log(chalk10.gray("\u2500".repeat(40)));
1307
+ console.log();
1308
+ console.log(chalk10.gray("Config file:"), config.getPath());
1309
+ console.log();
1310
+ console.log(chalk10.gray("Language: "), chalk10.white(currentConfig.language));
1311
+ console.log(chalk10.gray("Editor: "), chalk10.white(currentConfig.editor ?? "(not set)"));
1312
+ console.log(chalk10.gray("Work Dir: "), chalk10.white(currentConfig.workDir));
1313
+ console.log(chalk10.gray("Logged in: "), credentials ? chalk10.green("Yes") : chalk10.yellow("No"));
1314
+ }
1315
+
1316
+ // src/index.ts
1317
+ var program = new Command();
1318
+ program.configureHelp({
1319
+ sortSubcommands: true,
1320
+ subcommandTerm: (cmd) => chalk11.cyan(cmd.name()) + (cmd.alias() ? chalk11.gray(`|${cmd.alias()}`) : ""),
1321
+ subcommandDescription: (cmd) => chalk11.white(cmd.description()),
1322
+ optionTerm: (option) => chalk11.yellow(option.flags),
1323
+ optionDescription: (option) => chalk11.white(option.description)
1324
+ });
1325
+ program.name("leetcode").usage("[command] [options]").description(chalk11.bold.cyan("\u{1F525} A modern LeetCode CLI built with TypeScript")).version("1.0.0", "-v, --version", "Output the version number").helpOption("-h, --help", "Display help for command").addHelpText("after", `
1326
+ ${chalk11.yellow("Examples:")}
1327
+ ${chalk11.cyan("$ leetcode login")} Login to LeetCode
1328
+ ${chalk11.cyan("$ leetcode list -d easy")} List easy problems
1329
+ ${chalk11.cyan("$ leetcode pick 1")} Start solving "Two Sum"
1330
+ ${chalk11.cyan("$ leetcode test 1")} Test your solution
1331
+ ${chalk11.cyan("$ leetcode submit 1")} Submit your solution
1332
+ `);
1333
+ program.command("login").description("Login to LeetCode with browser cookies").action(loginCommand);
1334
+ program.command("logout").description("Clear stored credentials").action(logoutCommand);
1335
+ program.command("whoami").description("Check current login status").action(whoamiCommand);
1336
+ program.command("list").alias("l").description("List LeetCode problems").option("-d, --difficulty <level>", "Filter by difficulty (easy/medium/hard)").option("-s, --status <status>", "Filter by status (todo/solved/attempted)").option("-t, --tag <tags...>", "Filter by topic tags").option("-q, --search <keywords>", "Search by keywords").option("-n, --limit <number>", "Number of problems to show", "20").option("-p, --page <number>", "Page number", "1").action(listCommand);
1337
+ program.command("show <id>").alias("s").description("Show problem description").action(showCommand);
1338
+ program.command("daily").alias("d").description("Show today's daily challenge").action(dailyCommand);
1339
+ program.command("pick <id>").alias("p").description("Generate solution file for a problem").option("-l, --lang <language>", "Programming language for the solution").option("--no-open", "Do not open file in editor").action(pickCommand);
1340
+ program.command("test <file>").alias("t").description("Test solution against sample test cases").option("-c, --testcase <testcase>", "Custom test case").action(testCommand);
1341
+ program.command("submit <file>").alias("x").description("Submit solution to LeetCode").action(submitCommand);
1342
+ program.command("stat [username]").description("Show user statistics").action(statCommand);
1343
+ program.command("config").description("View or set configuration").option("-l, --lang <language>", "Set default programming language").option("-e, --editor <editor>", "Set editor command").option("-w, --workdir <path>", "Set working directory for solutions").option("-i, --interactive", "Interactive configuration").action(async (options) => {
1344
+ if (options.interactive) {
1345
+ await configInteractiveCommand();
1346
+ } else {
1347
+ await configCommand(options);
1348
+ }
1349
+ });
1350
+ program.showHelpAfterError("(add --help for additional information)");
1351
+ program.parse();
1352
+ if (!process.argv.slice(2).length) {
1353
+ console.log();
1354
+ console.log(chalk11.bold.cyan(" \u{1F525} LeetCode CLI"));
1355
+ console.log(chalk11.gray(" A modern command-line interface for LeetCode"));
1356
+ console.log();
1357
+ program.outputHelp();
1358
+ }