@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/LICENSE +190 -0
- package/README.md +205 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1358 -0
- package/package.json +52 -0
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(/ /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)));
|
|
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(/ /g, " ").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, '"').replace(/'/g, "'").replace(/≤/g, "<=").replace(/≥/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
|
+
}
|