@night-slayer18/leetcode-cli 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/index.js +568 -225
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ A modern, feature-rich LeetCode CLI built with TypeScript.
|
|
|
16
16
|
- 📤 **Submit solutions** - Submit directly to LeetCode
|
|
17
17
|
- 📊 **View statistics** - Track your progress
|
|
18
18
|
- 🎯 **Daily challenge** - Get today's problem
|
|
19
|
+
- ⏱️ **Interview timer** - Timed practice with solve time tracking
|
|
20
|
+
- 👥 **Collaborative coding** - Solve problems with a partner
|
|
19
21
|
- ⚙️ **Configurable** - Set language, editor, and working directory
|
|
20
22
|
- 📂 **Smart file discovery** - Use problem ID, filename, or full path
|
|
21
23
|
- 🔄 **Git Sync** - Auto-sync solutions to GitHub/GitLab
|
|
@@ -72,6 +74,7 @@ leetcode submit 1
|
|
|
72
74
|
| `submissions <id>` | View past submissions |
|
|
73
75
|
| `stat [username]` | Show user statistics |
|
|
74
76
|
| `timer <id>` | Interview mode with timer |
|
|
77
|
+
| `collab <cmd>` | Collaborative coding with a partner |
|
|
75
78
|
| `config` | View or set configuration |
|
|
76
79
|
| `sync` | Sync solutions to Git repository |
|
|
77
80
|
|
|
@@ -222,6 +225,29 @@ leetcode timer --stats
|
|
|
222
225
|
|
|
223
226
|
# Stop active timer
|
|
224
227
|
leetcode timer --stop
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Collaborative Coding
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
# Host a collaboration session
|
|
234
|
+
leetcode collab host 1
|
|
235
|
+
|
|
236
|
+
# Share the room code with your partner
|
|
237
|
+
# Partner joins with:
|
|
238
|
+
leetcode collab join ABC123
|
|
239
|
+
|
|
240
|
+
# Both solve the problem, then sync
|
|
241
|
+
leetcode collab sync
|
|
242
|
+
|
|
243
|
+
# Compare solutions
|
|
244
|
+
leetcode collab compare
|
|
245
|
+
|
|
246
|
+
# Check session status
|
|
247
|
+
leetcode collab status
|
|
248
|
+
|
|
249
|
+
# Leave session
|
|
250
|
+
leetcode collab leave
|
|
225
251
|
```
|
|
226
252
|
|
|
227
253
|
### Configuration
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import chalk22 from "chalk";
|
|
6
6
|
|
|
7
7
|
// src/commands/login.ts
|
|
8
8
|
import inquirer from "inquirer";
|
|
@@ -308,12 +308,12 @@ var LeetCodeClient = class {
|
|
|
308
308
|
retry: { limit: 2 }
|
|
309
309
|
});
|
|
310
310
|
}
|
|
311
|
-
setCredentials(
|
|
312
|
-
this.credentials =
|
|
311
|
+
setCredentials(credentials2) {
|
|
312
|
+
this.credentials = credentials2;
|
|
313
313
|
this.client = this.client.extend({
|
|
314
314
|
headers: {
|
|
315
|
-
"Cookie": `LEETCODE_SESSION=${
|
|
316
|
-
"X-CSRFToken":
|
|
315
|
+
"Cookie": `LEETCODE_SESSION=${credentials2.session}; csrftoken=${credentials2.csrfToken}`,
|
|
316
|
+
"X-CSRFToken": credentials2.csrfToken
|
|
317
317
|
}
|
|
318
318
|
});
|
|
319
319
|
}
|
|
@@ -445,14 +445,14 @@ var LeetCodeClient = class {
|
|
|
445
445
|
}).json();
|
|
446
446
|
return this.pollSubmission(response.submission_id.toString(), "submission", SubmissionResultSchema);
|
|
447
447
|
}
|
|
448
|
-
async pollSubmission(id, _type,
|
|
448
|
+
async pollSubmission(id, _type, schema) {
|
|
449
449
|
const endpoint = `submissions/detail/${id}/check/`;
|
|
450
450
|
const maxAttempts = 30;
|
|
451
451
|
const delay = 1e3;
|
|
452
452
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
453
453
|
const result = await this.client.get(endpoint).json();
|
|
454
454
|
if (result.state === "SUCCESS" || result.state === "FAILURE") {
|
|
455
|
-
return
|
|
455
|
+
return schema.parse(result);
|
|
456
456
|
}
|
|
457
457
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
458
458
|
}
|
|
@@ -461,88 +461,31 @@ var LeetCodeClient = class {
|
|
|
461
461
|
};
|
|
462
462
|
var leetcodeClient = new LeetCodeClient();
|
|
463
463
|
|
|
464
|
-
// src/storage/
|
|
464
|
+
// src/storage/credentials.ts
|
|
465
465
|
import Conf from "conf";
|
|
466
466
|
import { homedir } from "os";
|
|
467
467
|
import { join } from "path";
|
|
468
|
-
var
|
|
469
|
-
|
|
470
|
-
type: "object",
|
|
471
|
-
nullable: true,
|
|
472
|
-
properties: {
|
|
473
|
-
csrfToken: { type: "string" },
|
|
474
|
-
session: { type: "string" }
|
|
475
|
-
}
|
|
476
|
-
},
|
|
477
|
-
config: {
|
|
478
|
-
type: "object",
|
|
479
|
-
properties: {
|
|
480
|
-
language: { type: "string", default: "typescript" },
|
|
481
|
-
editor: { type: "string" },
|
|
482
|
-
workDir: { type: "string", default: join(homedir(), "leetcode") },
|
|
483
|
-
repo: { type: "string" }
|
|
484
|
-
},
|
|
485
|
-
default: {
|
|
486
|
-
language: "typescript",
|
|
487
|
-
workDir: join(homedir(), "leetcode")
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
};
|
|
491
|
-
var configStore = new Conf({
|
|
492
|
-
projectName: "leetcode-cli",
|
|
468
|
+
var credentialsStore = new Conf({
|
|
469
|
+
configName: "credentials",
|
|
493
470
|
cwd: join(homedir(), ".leetcode"),
|
|
494
|
-
|
|
471
|
+
defaults: {}
|
|
495
472
|
});
|
|
496
|
-
var
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
473
|
+
var credentials = {
|
|
474
|
+
get() {
|
|
475
|
+
const session = credentialsStore.get("session");
|
|
476
|
+
const csrfToken = credentialsStore.get("csrfToken");
|
|
477
|
+
if (!session || !csrfToken) return null;
|
|
478
|
+
return { session, csrfToken };
|
|
500
479
|
},
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
clearCredentials() {
|
|
505
|
-
configStore.delete("credentials");
|
|
506
|
-
},
|
|
507
|
-
// User Config
|
|
508
|
-
getConfig() {
|
|
509
|
-
return configStore.get("config");
|
|
510
|
-
},
|
|
511
|
-
setLanguage(language) {
|
|
512
|
-
configStore.set("config.language", language);
|
|
513
|
-
},
|
|
514
|
-
setEditor(editor) {
|
|
515
|
-
configStore.set("config.editor", editor);
|
|
516
|
-
},
|
|
517
|
-
setWorkDir(workDir) {
|
|
518
|
-
configStore.set("config.workDir", workDir);
|
|
519
|
-
},
|
|
520
|
-
setRepo(repo) {
|
|
521
|
-
configStore.set("config.repo", repo);
|
|
522
|
-
},
|
|
523
|
-
deleteRepo() {
|
|
524
|
-
configStore.delete("config.repo");
|
|
525
|
-
},
|
|
526
|
-
// Get specific config values
|
|
527
|
-
getLanguage() {
|
|
528
|
-
return configStore.get("config.language");
|
|
480
|
+
set(creds) {
|
|
481
|
+
credentialsStore.set("session", creds.session);
|
|
482
|
+
credentialsStore.set("csrfToken", creds.csrfToken);
|
|
529
483
|
},
|
|
530
|
-
getEditor() {
|
|
531
|
-
return configStore.get("config.editor");
|
|
532
|
-
},
|
|
533
|
-
getWorkDir() {
|
|
534
|
-
return configStore.get("config.workDir");
|
|
535
|
-
},
|
|
536
|
-
getRepo() {
|
|
537
|
-
return configStore.get("config.repo");
|
|
538
|
-
},
|
|
539
|
-
// Clear all config
|
|
540
484
|
clear() {
|
|
541
|
-
|
|
485
|
+
credentialsStore.clear();
|
|
542
486
|
},
|
|
543
|
-
// Get config file path (for debugging)
|
|
544
487
|
getPath() {
|
|
545
|
-
return
|
|
488
|
+
return credentialsStore.path;
|
|
546
489
|
}
|
|
547
490
|
};
|
|
548
491
|
|
|
@@ -574,23 +517,23 @@ async function loginCommand() {
|
|
|
574
517
|
validate: (input) => input.length > 0 || "CSRF token is required"
|
|
575
518
|
}
|
|
576
519
|
]);
|
|
577
|
-
const
|
|
520
|
+
const creds = {
|
|
578
521
|
session: answers.session.trim(),
|
|
579
522
|
csrfToken: answers.csrfToken.trim()
|
|
580
523
|
};
|
|
581
524
|
const spinner = ora("Verifying credentials...").start();
|
|
582
525
|
try {
|
|
583
|
-
leetcodeClient.setCredentials(
|
|
526
|
+
leetcodeClient.setCredentials(creds);
|
|
584
527
|
const { isSignedIn, username } = await leetcodeClient.checkAuth();
|
|
585
528
|
if (!isSignedIn || !username) {
|
|
586
529
|
spinner.fail("Invalid credentials");
|
|
587
530
|
console.log(chalk.red("Please check your session cookies and try again."));
|
|
588
531
|
return;
|
|
589
532
|
}
|
|
590
|
-
|
|
533
|
+
credentials.set(creds);
|
|
591
534
|
spinner.succeed(`Logged in as ${chalk.green(username)}`);
|
|
592
535
|
console.log();
|
|
593
|
-
console.log(chalk.gray(`Credentials saved to ${
|
|
536
|
+
console.log(chalk.gray(`Credentials saved to ${credentials.getPath()}`));
|
|
594
537
|
} catch (error) {
|
|
595
538
|
spinner.fail("Authentication failed");
|
|
596
539
|
if (error instanceof Error) {
|
|
@@ -599,18 +542,18 @@ async function loginCommand() {
|
|
|
599
542
|
}
|
|
600
543
|
}
|
|
601
544
|
async function logoutCommand() {
|
|
602
|
-
|
|
545
|
+
credentials.clear();
|
|
603
546
|
console.log(chalk.green("\u2713 Logged out successfully"));
|
|
604
547
|
}
|
|
605
548
|
async function whoamiCommand() {
|
|
606
|
-
const
|
|
607
|
-
if (!
|
|
549
|
+
const creds = credentials.get();
|
|
550
|
+
if (!creds) {
|
|
608
551
|
console.log(chalk.yellow('Not logged in. Run "leetcode login" to authenticate.'));
|
|
609
552
|
return;
|
|
610
553
|
}
|
|
611
554
|
const spinner = ora("Checking session...").start();
|
|
612
555
|
try {
|
|
613
|
-
leetcodeClient.setCredentials(
|
|
556
|
+
leetcodeClient.setCredentials(creds);
|
|
614
557
|
const { isSignedIn, username } = await leetcodeClient.checkAuth();
|
|
615
558
|
if (!isSignedIn || !username) {
|
|
616
559
|
spinner.fail("Session expired");
|
|
@@ -633,13 +576,13 @@ import chalk4 from "chalk";
|
|
|
633
576
|
// src/utils/auth.ts
|
|
634
577
|
import chalk2 from "chalk";
|
|
635
578
|
async function requireAuth() {
|
|
636
|
-
const
|
|
637
|
-
if (!
|
|
579
|
+
const creds = credentials.get();
|
|
580
|
+
if (!creds) {
|
|
638
581
|
console.log(chalk2.yellow("\u26A0\uFE0F Please login first: leetcode login"));
|
|
639
582
|
return { authorized: false };
|
|
640
583
|
}
|
|
641
584
|
try {
|
|
642
|
-
leetcodeClient.setCredentials(
|
|
585
|
+
leetcodeClient.setCredentials(creds);
|
|
643
586
|
const { isSignedIn, username } = await leetcodeClient.checkAuth();
|
|
644
587
|
if (!isSignedIn) {
|
|
645
588
|
console.log(chalk2.yellow("\u26A0\uFE0F Session expired. Please run: leetcode login"));
|
|
@@ -990,10 +933,63 @@ async function showCommand(idOrSlug) {
|
|
|
990
933
|
// src/commands/pick.ts
|
|
991
934
|
import { writeFile, mkdir } from "fs/promises";
|
|
992
935
|
import { existsSync } from "fs";
|
|
993
|
-
import { join as
|
|
936
|
+
import { join as join3 } from "path";
|
|
994
937
|
import ora4 from "ora";
|
|
995
938
|
import chalk7 from "chalk";
|
|
996
939
|
|
|
940
|
+
// src/storage/config.ts
|
|
941
|
+
import Conf2 from "conf";
|
|
942
|
+
import { homedir as homedir2 } from "os";
|
|
943
|
+
import { join as join2 } from "path";
|
|
944
|
+
var configStore = new Conf2({
|
|
945
|
+
configName: "config",
|
|
946
|
+
cwd: join2(homedir2(), ".leetcode"),
|
|
947
|
+
defaults: {
|
|
948
|
+
language: "typescript",
|
|
949
|
+
workDir: join2(homedir2(), "leetcode")
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
var config = {
|
|
953
|
+
getConfig() {
|
|
954
|
+
return {
|
|
955
|
+
language: configStore.get("language"),
|
|
956
|
+
editor: configStore.get("editor"),
|
|
957
|
+
workDir: configStore.get("workDir"),
|
|
958
|
+
repo: configStore.get("repo")
|
|
959
|
+
};
|
|
960
|
+
},
|
|
961
|
+
setLanguage(language) {
|
|
962
|
+
configStore.set("language", language);
|
|
963
|
+
},
|
|
964
|
+
setEditor(editor) {
|
|
965
|
+
configStore.set("editor", editor);
|
|
966
|
+
},
|
|
967
|
+
setWorkDir(workDir) {
|
|
968
|
+
configStore.set("workDir", workDir);
|
|
969
|
+
},
|
|
970
|
+
setRepo(repo) {
|
|
971
|
+
configStore.set("repo", repo);
|
|
972
|
+
},
|
|
973
|
+
deleteRepo() {
|
|
974
|
+
configStore.delete("repo");
|
|
975
|
+
},
|
|
976
|
+
getLanguage() {
|
|
977
|
+
return configStore.get("language");
|
|
978
|
+
},
|
|
979
|
+
getEditor() {
|
|
980
|
+
return configStore.get("editor");
|
|
981
|
+
},
|
|
982
|
+
getWorkDir() {
|
|
983
|
+
return configStore.get("workDir");
|
|
984
|
+
},
|
|
985
|
+
getRepo() {
|
|
986
|
+
return configStore.get("repo");
|
|
987
|
+
},
|
|
988
|
+
getPath() {
|
|
989
|
+
return configStore.path;
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
|
|
997
993
|
// src/utils/templates.ts
|
|
998
994
|
var LANGUAGE_EXTENSIONS = {
|
|
999
995
|
typescript: "ts",
|
|
@@ -1187,12 +1183,12 @@ async function pickCommand(idOrSlug, options) {
|
|
|
1187
1183
|
const workDir = config.getWorkDir();
|
|
1188
1184
|
const difficulty = problem.difficulty;
|
|
1189
1185
|
const category = problem.topicTags.length > 0 ? problem.topicTags[0].name.replace(/[^\w\s-]/g, "").trim() : "Uncategorized";
|
|
1190
|
-
const targetDir =
|
|
1186
|
+
const targetDir = join3(workDir, difficulty, category);
|
|
1191
1187
|
if (!existsSync(targetDir)) {
|
|
1192
1188
|
await mkdir(targetDir, { recursive: true });
|
|
1193
1189
|
}
|
|
1194
1190
|
const fileName = getSolutionFileName(problem.questionFrontendId, problem.titleSlug, language);
|
|
1195
|
-
const filePath =
|
|
1191
|
+
const filePath = join3(targetDir, fileName);
|
|
1196
1192
|
if (existsSync(filePath)) {
|
|
1197
1193
|
spinner.warn(`File already exists: ${fileName}`);
|
|
1198
1194
|
console.log(chalk7.gray(`Path: ${filePath}`));
|
|
@@ -1271,7 +1267,7 @@ import chalk8 from "chalk";
|
|
|
1271
1267
|
// src/utils/fileUtils.ts
|
|
1272
1268
|
import { readdir } from "fs/promises";
|
|
1273
1269
|
import { existsSync as existsSync2 } from "fs";
|
|
1274
|
-
import { join as
|
|
1270
|
+
import { join as join4 } from "path";
|
|
1275
1271
|
var MAX_SEARCH_DEPTH = 5;
|
|
1276
1272
|
async function findSolutionFile(dir, problemId, currentDepth = 0) {
|
|
1277
1273
|
if (!existsSync2(dir)) return null;
|
|
@@ -1279,7 +1275,7 @@ async function findSolutionFile(dir, problemId, currentDepth = 0) {
|
|
|
1279
1275
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
1280
1276
|
for (const entry of entries) {
|
|
1281
1277
|
if (entry.name.startsWith(".")) continue;
|
|
1282
|
-
const fullPath =
|
|
1278
|
+
const fullPath = join4(dir, entry.name);
|
|
1283
1279
|
if (entry.isDirectory()) {
|
|
1284
1280
|
const found = await findSolutionFile(fullPath, problemId, currentDepth + 1);
|
|
1285
1281
|
if (found) return found;
|
|
@@ -1297,7 +1293,7 @@ async function findFileByName(dir, fileName, currentDepth = 0) {
|
|
|
1297
1293
|
if (currentDepth >= MAX_SEARCH_DEPTH) return null;
|
|
1298
1294
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
1299
1295
|
for (const entry of entries) {
|
|
1300
|
-
const fullPath =
|
|
1296
|
+
const fullPath = join4(dir, entry.name);
|
|
1301
1297
|
if (entry.isDirectory()) {
|
|
1302
1298
|
const found = await findFileByName(fullPath, fileName, currentDepth + 1);
|
|
1303
1299
|
if (found) return found;
|
|
@@ -1417,12 +1413,12 @@ import ora6 from "ora";
|
|
|
1417
1413
|
import chalk9 from "chalk";
|
|
1418
1414
|
|
|
1419
1415
|
// src/storage/timer.ts
|
|
1420
|
-
import
|
|
1421
|
-
import { homedir as
|
|
1422
|
-
import { join as
|
|
1423
|
-
var timerStore = new
|
|
1424
|
-
|
|
1425
|
-
cwd:
|
|
1416
|
+
import Conf3 from "conf";
|
|
1417
|
+
import { homedir as homedir3 } from "os";
|
|
1418
|
+
import { join as join5 } from "path";
|
|
1419
|
+
var timerStore = new Conf3({
|
|
1420
|
+
configName: "timer",
|
|
1421
|
+
cwd: join5(homedir3(), ".leetcode"),
|
|
1426
1422
|
defaults: {
|
|
1427
1423
|
solveTimes: {},
|
|
1428
1424
|
activeTimer: null
|
|
@@ -1826,7 +1822,7 @@ async function randomCommand(options) {
|
|
|
1826
1822
|
// src/commands/submissions.ts
|
|
1827
1823
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1828
1824
|
import { existsSync as existsSync5 } from "fs";
|
|
1829
|
-
import { join as
|
|
1825
|
+
import { join as join6 } from "path";
|
|
1830
1826
|
import ora10 from "ora";
|
|
1831
1827
|
import chalk14 from "chalk";
|
|
1832
1828
|
async function submissionsCommand(idOrSlug, options) {
|
|
@@ -1880,7 +1876,7 @@ async function submissionsCommand(idOrSlug, options) {
|
|
|
1880
1876
|
const workDir = config.getWorkDir();
|
|
1881
1877
|
const difficulty = problem.difficulty;
|
|
1882
1878
|
const category = problem.topicTags.length > 0 ? problem.topicTags[0].name.replace(/[^\w\s-]/g, "").trim() : "Uncategorized";
|
|
1883
|
-
const targetDir =
|
|
1879
|
+
const targetDir = join6(workDir, difficulty, category);
|
|
1884
1880
|
if (!existsSync5(targetDir)) {
|
|
1885
1881
|
await mkdir2(targetDir, { recursive: true });
|
|
1886
1882
|
}
|
|
@@ -1888,7 +1884,7 @@ async function submissionsCommand(idOrSlug, options) {
|
|
|
1888
1884
|
const supportedLang = LANG_SLUG_MAP[langSlug] ?? "txt";
|
|
1889
1885
|
const ext = LANGUAGE_EXTENSIONS[supportedLang] ?? langSlug;
|
|
1890
1886
|
const fileName = `${problem.questionFrontendId}.${problem.titleSlug}.submission-${lastAC.id}.${ext}`;
|
|
1891
|
-
const filePath =
|
|
1887
|
+
const filePath = join6(targetDir, fileName);
|
|
1892
1888
|
await writeFile2(filePath, details.code, "utf-8");
|
|
1893
1889
|
downloadSpinner.succeed(`Downloaded to ${chalk14.green(fileName)}`);
|
|
1894
1890
|
console.log(chalk14.gray(`Path: ${filePath}`));
|
|
@@ -1994,7 +1990,7 @@ async function configInteractiveCommand() {
|
|
|
1994
1990
|
}
|
|
1995
1991
|
function showCurrentConfig() {
|
|
1996
1992
|
const currentConfig = config.getConfig();
|
|
1997
|
-
const
|
|
1993
|
+
const creds = credentials.get();
|
|
1998
1994
|
console.log();
|
|
1999
1995
|
console.log(chalk15.bold("LeetCode CLI Configuration"));
|
|
2000
1996
|
console.log(chalk15.gray("\u2500".repeat(40)));
|
|
@@ -2005,7 +2001,7 @@ function showCurrentConfig() {
|
|
|
2005
2001
|
console.log(chalk15.gray("Editor: "), chalk15.white(currentConfig.editor ?? "(not set)"));
|
|
2006
2002
|
console.log(chalk15.gray("Work Dir: "), chalk15.white(currentConfig.workDir));
|
|
2007
2003
|
console.log(chalk15.gray("Repo URL: "), chalk15.white(currentConfig.repo ?? "(not set)"));
|
|
2008
|
-
console.log(chalk15.gray("Logged in: "),
|
|
2004
|
+
console.log(chalk15.gray("Logged in: "), creds ? chalk15.green("Yes") : chalk15.yellow("No"));
|
|
2009
2005
|
}
|
|
2010
2006
|
|
|
2011
2007
|
// src/commands/bookmark.ts
|
|
@@ -2014,8 +2010,8 @@ import Table2 from "cli-table3";
|
|
|
2014
2010
|
import ora11 from "ora";
|
|
2015
2011
|
|
|
2016
2012
|
// src/storage/bookmarks.ts
|
|
2017
|
-
import
|
|
2018
|
-
var bookmarksStore = new
|
|
2013
|
+
import Conf4 from "conf";
|
|
2014
|
+
var bookmarksStore = new Conf4({
|
|
2019
2015
|
projectName: "leetcode-cli-bookmarks",
|
|
2020
2016
|
defaults: { bookmarks: [] }
|
|
2021
2017
|
});
|
|
@@ -2168,7 +2164,7 @@ function colorDifficulty2(difficulty) {
|
|
|
2168
2164
|
|
|
2169
2165
|
// src/commands/notes.ts
|
|
2170
2166
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
2171
|
-
import { join as
|
|
2167
|
+
import { join as join7 } from "path";
|
|
2172
2168
|
import { existsSync as existsSync6 } from "fs";
|
|
2173
2169
|
import chalk17 from "chalk";
|
|
2174
2170
|
async function notesCommand(problemId, action) {
|
|
@@ -2178,8 +2174,8 @@ async function notesCommand(problemId, action) {
|
|
|
2178
2174
|
return;
|
|
2179
2175
|
}
|
|
2180
2176
|
const noteAction = action === "view" ? "view" : "edit";
|
|
2181
|
-
const notesDir =
|
|
2182
|
-
const notePath =
|
|
2177
|
+
const notesDir = join7(config.getWorkDir(), ".notes");
|
|
2178
|
+
const notePath = join7(notesDir, `${problemId}.md`);
|
|
2183
2179
|
if (!existsSync6(notesDir)) {
|
|
2184
2180
|
await mkdir3(notesDir, { recursive: true });
|
|
2185
2181
|
}
|
|
@@ -2654,162 +2650,488 @@ async function showTimerStats(problemId) {
|
|
|
2654
2650
|
}
|
|
2655
2651
|
}
|
|
2656
2652
|
|
|
2653
|
+
// src/commands/collab.ts
|
|
2654
|
+
import chalk21 from "chalk";
|
|
2655
|
+
import ora15 from "ora";
|
|
2656
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
2657
|
+
|
|
2658
|
+
// src/services/supabase.ts
|
|
2659
|
+
import { createClient } from "@supabase/supabase-js";
|
|
2660
|
+
var SUPABASE_URL = "https://abagrmwdpvnfyuqizyym.supabase.co";
|
|
2661
|
+
var SUPABASE_ANON_KEY = "sb_publishable_indrKu8VJmASdyLp7w8Hog_OyqT17cV";
|
|
2662
|
+
var supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
|
|
2663
|
+
|
|
2664
|
+
// src/storage/collab.ts
|
|
2665
|
+
import Conf5 from "conf";
|
|
2666
|
+
import { homedir as homedir4 } from "os";
|
|
2667
|
+
import { join as join8 } from "path";
|
|
2668
|
+
var collabStore = new Conf5({
|
|
2669
|
+
configName: "collab",
|
|
2670
|
+
cwd: join8(homedir4(), ".leetcode"),
|
|
2671
|
+
defaults: {
|
|
2672
|
+
session: null
|
|
2673
|
+
}
|
|
2674
|
+
});
|
|
2675
|
+
var collabStorage = {
|
|
2676
|
+
getSession() {
|
|
2677
|
+
return collabStore.get("session");
|
|
2678
|
+
},
|
|
2679
|
+
setSession(session) {
|
|
2680
|
+
if (session) {
|
|
2681
|
+
collabStore.set("session", session);
|
|
2682
|
+
} else {
|
|
2683
|
+
collabStore.delete("session");
|
|
2684
|
+
}
|
|
2685
|
+
},
|
|
2686
|
+
getPath() {
|
|
2687
|
+
return collabStore.path;
|
|
2688
|
+
}
|
|
2689
|
+
};
|
|
2690
|
+
|
|
2691
|
+
// src/services/collab.ts
|
|
2692
|
+
function generateRoomCode() {
|
|
2693
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
2694
|
+
let code = "";
|
|
2695
|
+
for (let i = 0; i < 6; i++) {
|
|
2696
|
+
code += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
2697
|
+
}
|
|
2698
|
+
return code;
|
|
2699
|
+
}
|
|
2700
|
+
var collabService = {
|
|
2701
|
+
getSession() {
|
|
2702
|
+
return collabStorage.getSession();
|
|
2703
|
+
},
|
|
2704
|
+
async createRoom(problemId, username) {
|
|
2705
|
+
const roomCode = generateRoomCode();
|
|
2706
|
+
const { error } = await supabase.from("collab_rooms").insert({
|
|
2707
|
+
room_code: roomCode,
|
|
2708
|
+
problem_id: problemId,
|
|
2709
|
+
host_username: username,
|
|
2710
|
+
host_code: "",
|
|
2711
|
+
guest_username: null,
|
|
2712
|
+
guest_code: null
|
|
2713
|
+
});
|
|
2714
|
+
if (error) {
|
|
2715
|
+
return { error: error.message };
|
|
2716
|
+
}
|
|
2717
|
+
collabStorage.setSession({
|
|
2718
|
+
roomCode,
|
|
2719
|
+
problemId,
|
|
2720
|
+
isHost: true,
|
|
2721
|
+
username
|
|
2722
|
+
});
|
|
2723
|
+
return { roomCode };
|
|
2724
|
+
},
|
|
2725
|
+
async joinRoom(roomCode, username) {
|
|
2726
|
+
const { data: room, error: fetchError } = await supabase.from("collab_rooms").select("*").eq("room_code", roomCode.toUpperCase()).single();
|
|
2727
|
+
if (fetchError || !room) {
|
|
2728
|
+
return { error: "Room not found" };
|
|
2729
|
+
}
|
|
2730
|
+
const { error: updateError } = await supabase.from("collab_rooms").update({ guest_username: username }).eq("room_code", roomCode.toUpperCase());
|
|
2731
|
+
if (updateError) {
|
|
2732
|
+
return { error: updateError.message };
|
|
2733
|
+
}
|
|
2734
|
+
collabStorage.setSession({
|
|
2735
|
+
roomCode: roomCode.toUpperCase(),
|
|
2736
|
+
problemId: room.problem_id,
|
|
2737
|
+
isHost: false,
|
|
2738
|
+
username
|
|
2739
|
+
});
|
|
2740
|
+
return { problemId: room.problem_id };
|
|
2741
|
+
},
|
|
2742
|
+
async syncCode(code) {
|
|
2743
|
+
const session = collabStorage.getSession();
|
|
2744
|
+
if (!session) {
|
|
2745
|
+
return { success: false, error: "No active session" };
|
|
2746
|
+
}
|
|
2747
|
+
const column = session.isHost ? "host_code" : "guest_code";
|
|
2748
|
+
const { error } = await supabase.from("collab_rooms").update({ [column]: code }).eq("room_code", session.roomCode);
|
|
2749
|
+
if (error) {
|
|
2750
|
+
return { success: false, error: error.message };
|
|
2751
|
+
}
|
|
2752
|
+
return { success: true };
|
|
2753
|
+
},
|
|
2754
|
+
async getPartnerCode() {
|
|
2755
|
+
const session = collabStorage.getSession();
|
|
2756
|
+
if (!session) {
|
|
2757
|
+
return { error: "No active session" };
|
|
2758
|
+
}
|
|
2759
|
+
const { data: room, error } = await supabase.from("collab_rooms").select("*").eq("room_code", session.roomCode).single();
|
|
2760
|
+
if (error || !room) {
|
|
2761
|
+
return { error: "Room not found" };
|
|
2762
|
+
}
|
|
2763
|
+
if (session.isHost) {
|
|
2764
|
+
return {
|
|
2765
|
+
code: room.guest_code || "",
|
|
2766
|
+
username: room.guest_username || "Partner"
|
|
2767
|
+
};
|
|
2768
|
+
} else {
|
|
2769
|
+
return {
|
|
2770
|
+
code: room.host_code || "",
|
|
2771
|
+
username: room.host_username || "Host"
|
|
2772
|
+
};
|
|
2773
|
+
}
|
|
2774
|
+
},
|
|
2775
|
+
async getRoomStatus() {
|
|
2776
|
+
const session = collabStorage.getSession();
|
|
2777
|
+
if (!session) {
|
|
2778
|
+
return { error: "No active session" };
|
|
2779
|
+
}
|
|
2780
|
+
const { data: room, error } = await supabase.from("collab_rooms").select("*").eq("room_code", session.roomCode).single();
|
|
2781
|
+
if (error || !room) {
|
|
2782
|
+
return { error: "Room not found" };
|
|
2783
|
+
}
|
|
2784
|
+
return {
|
|
2785
|
+
host: room.host_username,
|
|
2786
|
+
guest: room.guest_username,
|
|
2787
|
+
hasHostCode: !!room.host_code,
|
|
2788
|
+
hasGuestCode: !!room.guest_code
|
|
2789
|
+
};
|
|
2790
|
+
},
|
|
2791
|
+
async leaveRoom() {
|
|
2792
|
+
const session = collabStorage.getSession();
|
|
2793
|
+
if (session) {
|
|
2794
|
+
if (session.isHost) {
|
|
2795
|
+
await supabase.from("collab_rooms").delete().eq("room_code", session.roomCode);
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
collabStorage.setSession(null);
|
|
2799
|
+
}
|
|
2800
|
+
};
|
|
2801
|
+
|
|
2802
|
+
// src/commands/collab.ts
|
|
2803
|
+
async function collabHostCommand(problemId) {
|
|
2804
|
+
const { authorized, username } = await requireAuth();
|
|
2805
|
+
if (!authorized || !username) return;
|
|
2806
|
+
const spinner = ora15("Creating collaboration room...").start();
|
|
2807
|
+
try {
|
|
2808
|
+
const result = await collabService.createRoom(problemId, username);
|
|
2809
|
+
if ("error" in result) {
|
|
2810
|
+
spinner.fail(result.error);
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
spinner.succeed("Room created!");
|
|
2814
|
+
console.log();
|
|
2815
|
+
console.log(chalk21.bold.cyan("\u{1F465} Collaborative Coding Session"));
|
|
2816
|
+
console.log(chalk21.gray("\u2500".repeat(50)));
|
|
2817
|
+
console.log();
|
|
2818
|
+
console.log(chalk21.white(`Room Code: ${chalk21.bold.green(result.roomCode)}`));
|
|
2819
|
+
console.log(chalk21.white(`Problem: ${problemId}`));
|
|
2820
|
+
console.log();
|
|
2821
|
+
console.log(chalk21.gray("Share this code with your partner:"));
|
|
2822
|
+
console.log(chalk21.yellow(` leetcode collab join ${result.roomCode}`));
|
|
2823
|
+
console.log();
|
|
2824
|
+
console.log(chalk21.gray("\u2500".repeat(50)));
|
|
2825
|
+
console.log(chalk21.gray("After solving, sync and compare:"));
|
|
2826
|
+
console.log(chalk21.gray(" leetcode collab sync - Upload your solution"));
|
|
2827
|
+
console.log(chalk21.gray(" leetcode collab compare - See both solutions"));
|
|
2828
|
+
console.log(chalk21.gray(" leetcode collab status - Check room status"));
|
|
2829
|
+
console.log(chalk21.gray(" leetcode collab leave - End session"));
|
|
2830
|
+
console.log();
|
|
2831
|
+
await pickCommand(problemId, { open: true });
|
|
2832
|
+
} catch (error) {
|
|
2833
|
+
spinner.fail("Failed to create room");
|
|
2834
|
+
if (error instanceof Error) {
|
|
2835
|
+
console.log(chalk21.red(error.message));
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
async function collabJoinCommand(roomCode) {
|
|
2840
|
+
const { authorized, username } = await requireAuth();
|
|
2841
|
+
if (!authorized || !username) return;
|
|
2842
|
+
const spinner = ora15(`Joining room ${roomCode}...`).start();
|
|
2843
|
+
try {
|
|
2844
|
+
const result = await collabService.joinRoom(roomCode.toUpperCase(), username);
|
|
2845
|
+
if ("error" in result) {
|
|
2846
|
+
spinner.fail(result.error);
|
|
2847
|
+
return;
|
|
2848
|
+
}
|
|
2849
|
+
spinner.succeed("Joined room!");
|
|
2850
|
+
console.log();
|
|
2851
|
+
console.log(chalk21.bold.cyan("\u{1F465} Collaborative Coding Session"));
|
|
2852
|
+
console.log(chalk21.gray("\u2500".repeat(50)));
|
|
2853
|
+
console.log();
|
|
2854
|
+
console.log(chalk21.white(`Room Code: ${chalk21.bold.green(roomCode.toUpperCase())}`));
|
|
2855
|
+
console.log(chalk21.white(`Problem: ${result.problemId}`));
|
|
2856
|
+
console.log();
|
|
2857
|
+
console.log(chalk21.gray("\u2500".repeat(50)));
|
|
2858
|
+
console.log(chalk21.gray("After solving, sync and compare:"));
|
|
2859
|
+
console.log(chalk21.gray(" leetcode collab sync - Upload your solution"));
|
|
2860
|
+
console.log(chalk21.gray(" leetcode collab compare - See both solutions"));
|
|
2861
|
+
console.log(chalk21.gray(" leetcode collab status - Check room status"));
|
|
2862
|
+
console.log(chalk21.gray(" leetcode collab leave - End session"));
|
|
2863
|
+
console.log();
|
|
2864
|
+
await pickCommand(result.problemId, { open: true });
|
|
2865
|
+
} catch (error) {
|
|
2866
|
+
spinner.fail("Failed to join room");
|
|
2867
|
+
if (error instanceof Error) {
|
|
2868
|
+
console.log(chalk21.red(error.message));
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
async function collabSyncCommand() {
|
|
2873
|
+
const session = collabService.getSession();
|
|
2874
|
+
if (!session) {
|
|
2875
|
+
console.log(chalk21.yellow("No active collaboration session."));
|
|
2876
|
+
console.log(chalk21.gray("Use `leetcode collab host <id>` or `leetcode collab join <code>` first."));
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2879
|
+
const spinner = ora15("Syncing your code...").start();
|
|
2880
|
+
const workDir = config.getWorkDir();
|
|
2881
|
+
const filePath = await findSolutionFile(workDir, session.problemId);
|
|
2882
|
+
if (!filePath) {
|
|
2883
|
+
spinner.fail(`No solution file found for problem ${session.problemId}`);
|
|
2884
|
+
return;
|
|
2885
|
+
}
|
|
2886
|
+
const code = await readFile4(filePath, "utf-8");
|
|
2887
|
+
const result = await collabService.syncCode(code);
|
|
2888
|
+
if (result.success) {
|
|
2889
|
+
spinner.succeed("Code synced successfully!");
|
|
2890
|
+
console.log(chalk21.gray(`Uploaded ${code.split("\n").length} lines from ${filePath}`));
|
|
2891
|
+
} else {
|
|
2892
|
+
spinner.fail(result.error || "Sync failed");
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
async function collabCompareCommand() {
|
|
2896
|
+
const session = collabService.getSession();
|
|
2897
|
+
if (!session) {
|
|
2898
|
+
console.log(chalk21.yellow("No active collaboration session."));
|
|
2899
|
+
console.log(chalk21.gray("Use `leetcode collab host <id>` or `leetcode collab join <code>` first."));
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
const spinner = ora15("Fetching solutions...").start();
|
|
2903
|
+
const workDir = config.getWorkDir();
|
|
2904
|
+
const filePath = await findSolutionFile(workDir, session.problemId);
|
|
2905
|
+
if (!filePath) {
|
|
2906
|
+
spinner.fail(`No solution file found for problem ${session.problemId}`);
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
const myCode = await readFile4(filePath, "utf-8");
|
|
2910
|
+
const partnerResult = await collabService.getPartnerCode();
|
|
2911
|
+
if ("error" in partnerResult) {
|
|
2912
|
+
spinner.fail(partnerResult.error);
|
|
2913
|
+
return;
|
|
2914
|
+
}
|
|
2915
|
+
spinner.stop();
|
|
2916
|
+
if (!partnerResult.code) {
|
|
2917
|
+
console.log(chalk21.yellow("Partner has not synced their code yet."));
|
|
2918
|
+
console.log(chalk21.gray("Ask them to run `leetcode collab sync`."));
|
|
2919
|
+
return;
|
|
2920
|
+
}
|
|
2921
|
+
console.log();
|
|
2922
|
+
console.log(chalk21.bold.cyan("\u{1F4CA} Solution Comparison"));
|
|
2923
|
+
console.log(chalk21.gray("\u2500".repeat(60)));
|
|
2924
|
+
console.log();
|
|
2925
|
+
console.log(chalk21.bold.green(`\u25B8 Your Solution (${session.username})`));
|
|
2926
|
+
console.log(chalk21.gray("\u2500".repeat(60)));
|
|
2927
|
+
const myLines = myCode.split("\n");
|
|
2928
|
+
for (let i = 0; i < myLines.length; i++) {
|
|
2929
|
+
const lineNum = String(i + 1).padStart(3, " ");
|
|
2930
|
+
console.log(`${chalk21.gray(lineNum)} ${myLines[i]}`);
|
|
2931
|
+
}
|
|
2932
|
+
console.log();
|
|
2933
|
+
console.log(chalk21.bold.blue(`\u25B8 ${partnerResult.username}'s Solution`));
|
|
2934
|
+
console.log(chalk21.gray("\u2500".repeat(60)));
|
|
2935
|
+
const partnerLines = partnerResult.code.split("\n");
|
|
2936
|
+
for (let i = 0; i < partnerLines.length; i++) {
|
|
2937
|
+
const lineNum = String(i + 1).padStart(3, " ");
|
|
2938
|
+
console.log(`${chalk21.gray(lineNum)} ${partnerLines[i]}`);
|
|
2939
|
+
}
|
|
2940
|
+
console.log();
|
|
2941
|
+
console.log(chalk21.gray("\u2500".repeat(60)));
|
|
2942
|
+
console.log(chalk21.gray(`Your code: ${myLines.length} lines | Partner: ${partnerLines.length} lines`));
|
|
2943
|
+
console.log();
|
|
2944
|
+
}
|
|
2945
|
+
async function collabLeaveCommand() {
|
|
2946
|
+
const session = collabService.getSession();
|
|
2947
|
+
if (!session) {
|
|
2948
|
+
console.log(chalk21.yellow("No active collaboration session."));
|
|
2949
|
+
return;
|
|
2950
|
+
}
|
|
2951
|
+
await collabService.leaveRoom();
|
|
2952
|
+
console.log(chalk21.green("\u2713 Left the collaboration session."));
|
|
2953
|
+
}
|
|
2954
|
+
async function collabStatusCommand() {
|
|
2955
|
+
const session = collabService.getSession();
|
|
2956
|
+
if (!session) {
|
|
2957
|
+
console.log(chalk21.yellow("No active collaboration session."));
|
|
2958
|
+
console.log(chalk21.gray("Use `leetcode collab host <id>` or `leetcode collab join <code>` to start."));
|
|
2959
|
+
return;
|
|
2960
|
+
}
|
|
2961
|
+
const status = await collabService.getRoomStatus();
|
|
2962
|
+
if ("error" in status) {
|
|
2963
|
+
console.log(chalk21.red(status.error));
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
console.log();
|
|
2967
|
+
console.log(chalk21.bold.cyan("\u{1F465} Collaboration Status"));
|
|
2968
|
+
console.log(chalk21.gray("\u2500".repeat(40)));
|
|
2969
|
+
console.log(` Room: ${chalk21.green(session.roomCode)}`);
|
|
2970
|
+
console.log(` Problem: ${session.problemId}`);
|
|
2971
|
+
console.log(` Role: ${session.isHost ? "Host" : "Guest"}`);
|
|
2972
|
+
console.log();
|
|
2973
|
+
console.log(chalk21.bold(" Participants:"));
|
|
2974
|
+
console.log(` Host: ${status.host} ${status.hasHostCode ? chalk21.green("\u2713 synced") : chalk21.gray("pending")}`);
|
|
2975
|
+
console.log(` Guest: ${status.guest || chalk21.gray("(waiting...)")} ${status.hasGuestCode ? chalk21.green("\u2713 synced") : chalk21.gray("pending")}`);
|
|
2976
|
+
console.log();
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2657
2979
|
// src/index.ts
|
|
2658
2980
|
var program = new Command();
|
|
2659
2981
|
program.configureHelp({
|
|
2660
2982
|
sortSubcommands: true,
|
|
2661
|
-
subcommandTerm: (cmd) =>
|
|
2662
|
-
subcommandDescription: (cmd) =>
|
|
2663
|
-
optionTerm: (option) =>
|
|
2664
|
-
optionDescription: (option) =>
|
|
2983
|
+
subcommandTerm: (cmd) => chalk22.cyan(cmd.name()) + (cmd.alias() ? chalk22.gray(`|${cmd.alias()}`) : ""),
|
|
2984
|
+
subcommandDescription: (cmd) => chalk22.white(cmd.description()),
|
|
2985
|
+
optionTerm: (option) => chalk22.yellow(option.flags),
|
|
2986
|
+
optionDescription: (option) => chalk22.white(option.description)
|
|
2665
2987
|
});
|
|
2666
|
-
program.name("leetcode").usage("[command] [options]").description(
|
|
2667
|
-
${
|
|
2668
|
-
${
|
|
2669
|
-
${
|
|
2670
|
-
${
|
|
2671
|
-
${
|
|
2672
|
-
${
|
|
2673
|
-
${
|
|
2988
|
+
program.name("leetcode").usage("[command] [options]").description(chalk22.bold.cyan("\u{1F525} A modern LeetCode CLI built with TypeScript")).version("1.6.0", "-v, --version", "Output the version number").helpOption("-h, --help", "Display help for command").addHelpText("after", `
|
|
2989
|
+
${chalk22.yellow("Examples:")}
|
|
2990
|
+
${chalk22.cyan("$ leetcode login")} Login to LeetCode
|
|
2991
|
+
${chalk22.cyan("$ leetcode list -d easy")} List easy problems
|
|
2992
|
+
${chalk22.cyan("$ leetcode random -d medium")} Get random medium problem
|
|
2993
|
+
${chalk22.cyan("$ leetcode pick 1")} Start solving "Two Sum"
|
|
2994
|
+
${chalk22.cyan("$ leetcode test 1")} Test your solution
|
|
2995
|
+
${chalk22.cyan("$ leetcode submit 1")} Submit your solution
|
|
2674
2996
|
`);
|
|
2675
2997
|
program.command("login").description("Login to LeetCode with browser cookies").addHelpText("after", `
|
|
2676
|
-
${
|
|
2677
|
-
1. Open ${
|
|
2998
|
+
${chalk22.yellow("How to login:")}
|
|
2999
|
+
1. Open ${chalk22.cyan("https://leetcode.com")} in your browser
|
|
2678
3000
|
2. Login to your account
|
|
2679
3001
|
3. Open Developer Tools (F12) \u2192 Application \u2192 Cookies
|
|
2680
|
-
4. Copy values of ${
|
|
3002
|
+
4. Copy values of ${chalk22.green("LEETCODE_SESSION")} and ${chalk22.green("csrftoken")}
|
|
2681
3003
|
5. Paste when prompted by this command
|
|
2682
3004
|
`).action(loginCommand);
|
|
2683
3005
|
program.command("logout").description("Clear stored credentials").action(logoutCommand);
|
|
2684
3006
|
program.command("whoami").description("Check current login status").action(whoamiCommand);
|
|
2685
3007
|
program.command("list").alias("l").description("List LeetCode problems").option("-d, --difficulty <level>", "Filter by difficulty (easy/medium/hard)").option("-s, --status <status>", "Filter by status (todo/solved/attempted)").option("-t, --tag <tags...>", "Filter by topic tags").option("-q, --search <keywords>", "Search by keywords").option("-n, --limit <number>", "Number of problems to show", "20").option("-p, --page <number>", "Page number", "1").addHelpText("after", `
|
|
2686
|
-
${
|
|
2687
|
-
${
|
|
2688
|
-
${
|
|
2689
|
-
${
|
|
2690
|
-
${
|
|
2691
|
-
${
|
|
2692
|
-
${
|
|
3008
|
+
${chalk22.yellow("Examples:")}
|
|
3009
|
+
${chalk22.cyan("$ leetcode list")} List first 20 problems
|
|
3010
|
+
${chalk22.cyan("$ leetcode list -d easy")} List easy problems only
|
|
3011
|
+
${chalk22.cyan("$ leetcode list -s solved")} List your solved problems
|
|
3012
|
+
${chalk22.cyan("$ leetcode list -t array -t string")} Filter by multiple tags
|
|
3013
|
+
${chalk22.cyan('$ leetcode list -q "two sum"')} Search by keywords
|
|
3014
|
+
${chalk22.cyan("$ leetcode list -n 50 -p 2")} Show 50 problems, page 2
|
|
2693
3015
|
`).action(listCommand);
|
|
2694
3016
|
program.command("show <id>").alias("s").description("Show problem description").addHelpText("after", `
|
|
2695
|
-
${
|
|
2696
|
-
${
|
|
2697
|
-
${
|
|
2698
|
-
${
|
|
3017
|
+
${chalk22.yellow("Examples:")}
|
|
3018
|
+
${chalk22.cyan("$ leetcode show 1")} Show by problem ID
|
|
3019
|
+
${chalk22.cyan("$ leetcode show two-sum")} Show by problem slug
|
|
3020
|
+
${chalk22.cyan("$ leetcode s 412")} Short alias
|
|
2699
3021
|
`).action(showCommand);
|
|
2700
3022
|
program.command("daily").alias("d").description("Show today's daily challenge").addHelpText("after", `
|
|
2701
|
-
${
|
|
2702
|
-
${
|
|
2703
|
-
${
|
|
3023
|
+
${chalk22.yellow("Examples:")}
|
|
3024
|
+
${chalk22.cyan("$ leetcode daily")} Show today's challenge
|
|
3025
|
+
${chalk22.cyan("$ leetcode d")} Short alias
|
|
2704
3026
|
`).action(dailyCommand);
|
|
2705
3027
|
program.command("random").alias("r").description("Get a random problem").option("-d, --difficulty <level>", "Filter by difficulty (easy/medium/hard)").option("-t, --tag <tag>", "Filter by topic tag").option("--pick", "Auto-generate solution file").option("--no-open", "Do not open file in editor").addHelpText("after", `
|
|
2706
|
-
${
|
|
2707
|
-
${
|
|
2708
|
-
${
|
|
2709
|
-
${
|
|
2710
|
-
${
|
|
2711
|
-
${
|
|
3028
|
+
${chalk22.yellow("Examples:")}
|
|
3029
|
+
${chalk22.cyan("$ leetcode random")} Get any random problem
|
|
3030
|
+
${chalk22.cyan("$ leetcode random -d medium")} Random medium problem
|
|
3031
|
+
${chalk22.cyan("$ leetcode random -t array")} Random array problem
|
|
3032
|
+
${chalk22.cyan("$ leetcode random --pick")} Random + create file
|
|
3033
|
+
${chalk22.cyan("$ leetcode r -d easy --pick")} Random easy + file
|
|
2712
3034
|
`).action(randomCommand);
|
|
2713
3035
|
program.command("pick <id>").alias("p").description("Generate solution file for a problem").option("-l, --lang <language>", "Programming language for the solution").option("--no-open", "Do not open file in editor").addHelpText("after", `
|
|
2714
|
-
${
|
|
2715
|
-
${
|
|
2716
|
-
${
|
|
2717
|
-
${
|
|
2718
|
-
${
|
|
2719
|
-
${
|
|
2720
|
-
|
|
2721
|
-
${
|
|
3036
|
+
${chalk22.yellow("Examples:")}
|
|
3037
|
+
${chalk22.cyan("$ leetcode pick 1")} Pick by problem ID
|
|
3038
|
+
${chalk22.cyan("$ leetcode pick two-sum")} Pick by problem slug
|
|
3039
|
+
${chalk22.cyan("$ leetcode pick 1 -l python3")} Pick with specific language
|
|
3040
|
+
${chalk22.cyan("$ leetcode pick 1 --no-open")} Create file without opening
|
|
3041
|
+
${chalk22.cyan("$ leetcode p 412")} Short alias
|
|
3042
|
+
|
|
3043
|
+
${chalk22.gray("Files are organized by: workDir/Difficulty/Category/")}
|
|
2722
3044
|
`).action(async (id, options) => {
|
|
2723
3045
|
await pickCommand(id, options);
|
|
2724
3046
|
});
|
|
2725
3047
|
program.command("pick-batch <ids...>").description("Generate solution files for multiple problems").option("-l, --lang <language>", "Programming language for the solutions").addHelpText("after", `
|
|
2726
|
-
${
|
|
2727
|
-
${
|
|
2728
|
-
${
|
|
3048
|
+
${chalk22.yellow("Examples:")}
|
|
3049
|
+
${chalk22.cyan("$ leetcode pick-batch 1 2 3")} Pick problems 1, 2, and 3
|
|
3050
|
+
${chalk22.cyan("$ leetcode pick-batch 1 2 3 -l py")} Pick with Python
|
|
2729
3051
|
`).action(batchPickCommand);
|
|
2730
3052
|
program.command("test <file>").alias("t").description("Test solution against sample test cases").option("-c, --testcase <testcase>", "Custom test case").addHelpText("after", `
|
|
2731
|
-
${
|
|
2732
|
-
${
|
|
2733
|
-
${
|
|
2734
|
-
${
|
|
2735
|
-
${
|
|
2736
|
-
${
|
|
2737
|
-
|
|
2738
|
-
${
|
|
3053
|
+
${chalk22.yellow("Examples:")}
|
|
3054
|
+
${chalk22.cyan("$ leetcode test 1")} Test by problem ID
|
|
3055
|
+
${chalk22.cyan("$ leetcode test two-sum")} Test by problem slug
|
|
3056
|
+
${chalk22.cyan("$ leetcode test ./path/to/file.py")} Test by file path
|
|
3057
|
+
${chalk22.cyan('$ leetcode test 1 -c "[1,2]\\n3"')} Test with custom case
|
|
3058
|
+
${chalk22.cyan("$ leetcode t 412")} Short alias
|
|
3059
|
+
|
|
3060
|
+
${chalk22.gray("Testcases use \\n to separate multiple inputs.")}
|
|
2739
3061
|
`).action(testCommand);
|
|
2740
3062
|
program.command("submit <file>").alias("x").description("Submit solution to LeetCode").addHelpText("after", `
|
|
2741
|
-
${
|
|
2742
|
-
${
|
|
2743
|
-
${
|
|
2744
|
-
${
|
|
2745
|
-
${
|
|
3063
|
+
${chalk22.yellow("Examples:")}
|
|
3064
|
+
${chalk22.cyan("$ leetcode submit 1")} Submit by problem ID
|
|
3065
|
+
${chalk22.cyan("$ leetcode submit two-sum")} Submit by problem slug
|
|
3066
|
+
${chalk22.cyan("$ leetcode submit ./path/to/file.py")} Submit by file path
|
|
3067
|
+
${chalk22.cyan("$ leetcode x 412")} Short alias
|
|
2746
3068
|
`).action(submitCommand);
|
|
2747
3069
|
program.command("submissions <id>").description("View past submissions").option("-n, --limit <number>", "Number of submissions to show", "20").option("--last", "Show details of the last accepted submission").option("--download", "Download the last accepted submission code").addHelpText("after", `
|
|
2748
|
-
${
|
|
2749
|
-
${
|
|
2750
|
-
${
|
|
2751
|
-
${
|
|
2752
|
-
${
|
|
3070
|
+
${chalk22.yellow("Examples:")}
|
|
3071
|
+
${chalk22.cyan("$ leetcode submissions 1")} View submissions for problem
|
|
3072
|
+
${chalk22.cyan("$ leetcode submissions 1 -n 5")} Show last 5 submissions
|
|
3073
|
+
${chalk22.cyan("$ leetcode submissions 1 --last")} Show last accepted submission
|
|
3074
|
+
${chalk22.cyan("$ leetcode submissions 1 --download")} Download last accepted code
|
|
2753
3075
|
`).action(submissionsCommand);
|
|
2754
3076
|
program.command("stat [username]").description("Show user statistics and analytics").option("-c, --calendar", "Weekly activity summary (submissions & active days for last 12 weeks)").option("-s, --skills", "Skill breakdown (problems solved grouped by topic tags)").option("-t, --trend", "Daily trend chart (bar graph of submissions for last 7 days)").addHelpText("after", `
|
|
2755
|
-
${
|
|
2756
|
-
${
|
|
3077
|
+
${chalk22.yellow("Options Explained:")}
|
|
3078
|
+
${chalk22.cyan("-c, --calendar")} Shows a table of your weekly submissions and active days
|
|
2757
3079
|
for the past 12 weeks. Useful for tracking consistency.
|
|
2758
3080
|
|
|
2759
|
-
${
|
|
3081
|
+
${chalk22.cyan("-s, --skills")} Shows how many problems you solved per topic tag,
|
|
2760
3082
|
grouped by difficulty (Fundamental/Intermediate/Advanced).
|
|
2761
3083
|
Helps identify your strong and weak areas.
|
|
2762
3084
|
|
|
2763
|
-
${
|
|
3085
|
+
${chalk22.cyan("-t, --trend")} Shows a bar chart of daily submissions for the past week.
|
|
2764
3086
|
Visualizes your recent coding activity day by day.
|
|
2765
3087
|
|
|
2766
|
-
${
|
|
2767
|
-
${
|
|
2768
|
-
${
|
|
2769
|
-
${
|
|
2770
|
-
${
|
|
2771
|
-
${
|
|
3088
|
+
${chalk22.yellow("Examples:")}
|
|
3089
|
+
${chalk22.cyan("$ leetcode stat")} Show basic stats (solved count, rank)
|
|
3090
|
+
${chalk22.cyan("$ leetcode stat lee215")} Show another user's stats
|
|
3091
|
+
${chalk22.cyan("$ leetcode stat -c")} Weekly activity table
|
|
3092
|
+
${chalk22.cyan("$ leetcode stat -s")} Topic-wise breakdown
|
|
3093
|
+
${chalk22.cyan("$ leetcode stat -t")} 7-day trend chart
|
|
2772
3094
|
`).action((username, options) => statCommand(username, options));
|
|
2773
3095
|
program.command("today").description("Show today's progress summary").addHelpText("after", `
|
|
2774
|
-
${
|
|
2775
|
-
${
|
|
3096
|
+
${chalk22.yellow("Examples:")}
|
|
3097
|
+
${chalk22.cyan("$ leetcode today")} Show streak, solved, and daily challenge
|
|
2776
3098
|
`).action(todayCommand);
|
|
2777
3099
|
program.command("bookmark <action> [id]").description("Manage problem bookmarks").addHelpText("after", `
|
|
2778
|
-
${
|
|
2779
|
-
${
|
|
2780
|
-
${
|
|
2781
|
-
${
|
|
2782
|
-
${
|
|
2783
|
-
|
|
2784
|
-
${
|
|
2785
|
-
${
|
|
2786
|
-
${
|
|
2787
|
-
${
|
|
3100
|
+
${chalk22.yellow("Actions:")}
|
|
3101
|
+
${chalk22.cyan("add <id>")} Bookmark a problem
|
|
3102
|
+
${chalk22.cyan("remove <id>")} Remove a bookmark
|
|
3103
|
+
${chalk22.cyan("list")} List all bookmarks
|
|
3104
|
+
${chalk22.cyan("clear")} Clear all bookmarks
|
|
3105
|
+
|
|
3106
|
+
${chalk22.yellow("Examples:")}
|
|
3107
|
+
${chalk22.cyan("$ leetcode bookmark add 1")} Bookmark problem 1
|
|
3108
|
+
${chalk22.cyan("$ leetcode bookmark remove 1")} Remove bookmark
|
|
3109
|
+
${chalk22.cyan("$ leetcode bookmark list")} List all bookmarks
|
|
2788
3110
|
`).action(bookmarkCommand);
|
|
2789
3111
|
program.command("note <id> [action]").description("View or edit notes for a problem").addHelpText("after", `
|
|
2790
|
-
${
|
|
2791
|
-
${
|
|
2792
|
-
${
|
|
2793
|
-
|
|
2794
|
-
${
|
|
2795
|
-
${
|
|
2796
|
-
${
|
|
2797
|
-
${
|
|
3112
|
+
${chalk22.yellow("Actions:")}
|
|
3113
|
+
${chalk22.cyan("edit")} Open notes in editor (default)
|
|
3114
|
+
${chalk22.cyan("view")} Display notes in terminal
|
|
3115
|
+
|
|
3116
|
+
${chalk22.yellow("Examples:")}
|
|
3117
|
+
${chalk22.cyan("$ leetcode note 1")} Edit notes for problem 1
|
|
3118
|
+
${chalk22.cyan("$ leetcode note 1 edit")} Edit notes (explicit)
|
|
3119
|
+
${chalk22.cyan("$ leetcode note 1 view")} View notes in terminal
|
|
2798
3120
|
`).action(notesCommand);
|
|
2799
3121
|
program.command("sync").description("Sync solutions to Git repository").addHelpText("after", `
|
|
2800
|
-
${
|
|
2801
|
-
${
|
|
3122
|
+
${chalk22.yellow("Examples:")}
|
|
3123
|
+
${chalk22.cyan("$ leetcode sync")} Sync all solutions to remote
|
|
2802
3124
|
`).action(syncCommand);
|
|
2803
3125
|
program.command("config").description("View or set configuration").option("-l, --lang <language>", "Set default programming language").option("-e, --editor <editor>", "Set editor command").option("-w, --workdir <path>", "Set working directory for solutions").option("-r, --repo <url>", "Set Git repository URL").option("-i, --interactive", "Interactive configuration").addHelpText("after", `
|
|
2804
|
-
${
|
|
2805
|
-
${
|
|
2806
|
-
${
|
|
2807
|
-
${
|
|
2808
|
-
${
|
|
2809
|
-
${
|
|
2810
|
-
${
|
|
2811
|
-
|
|
2812
|
-
${
|
|
3126
|
+
${chalk22.yellow("Examples:")}
|
|
3127
|
+
${chalk22.cyan("$ leetcode config")} View current config
|
|
3128
|
+
${chalk22.cyan("$ leetcode config -l python3")} Set language to Python
|
|
3129
|
+
${chalk22.cyan('$ leetcode config -e "code"')} Set editor to VS Code
|
|
3130
|
+
${chalk22.cyan("$ leetcode config -w ~/leetcode")} Set solutions folder
|
|
3131
|
+
${chalk22.cyan("$ leetcode config -r https://...")} Set git repository
|
|
3132
|
+
${chalk22.cyan("$ leetcode config -i")} Interactive setup
|
|
3133
|
+
|
|
3134
|
+
${chalk22.gray("Supported languages: typescript, javascript, python3, java, cpp, c, csharp, go, rust, kotlin, swift")}
|
|
2813
3135
|
`).action(async (options) => {
|
|
2814
3136
|
if (options.interactive) {
|
|
2815
3137
|
await configInteractiveCommand();
|
|
@@ -2818,23 +3140,44 @@ ${chalk21.gray("Supported languages: typescript, javascript, python3, java, cpp,
|
|
|
2818
3140
|
}
|
|
2819
3141
|
});
|
|
2820
3142
|
program.command("timer [id]").description("Start interview mode with timer").option("-m, --minutes <minutes>", "Custom time limit in minutes").option("--stats", "Show solve time statistics").option("--stop", "Stop active timer").addHelpText("after", `
|
|
2821
|
-
${
|
|
3143
|
+
${chalk22.yellow("How it works:")}
|
|
2822
3144
|
Start a problem with a countdown timer to simulate interview conditions.
|
|
2823
3145
|
Default time limits: Easy (20 min), Medium (40 min), Hard (60 min).
|
|
2824
3146
|
Your solve times are recorded when you submit successfully.
|
|
2825
3147
|
|
|
2826
|
-
${
|
|
2827
|
-
${
|
|
2828
|
-
${
|
|
2829
|
-
${
|
|
2830
|
-
${
|
|
3148
|
+
${chalk22.yellow("Examples:")}
|
|
3149
|
+
${chalk22.cyan("$ leetcode timer 1")} Start problem 1 with default time
|
|
3150
|
+
${chalk22.cyan("$ leetcode timer 1 -m 30")} Start with 30 minute limit
|
|
3151
|
+
${chalk22.cyan("$ leetcode timer --stats")} Show your solve time statistics
|
|
3152
|
+
${chalk22.cyan("$ leetcode timer --stop")} Stop active timer
|
|
2831
3153
|
`).action((id, options) => timerCommand(id, options));
|
|
3154
|
+
var collabCmd = program.command("collab").description("Collaborative coding with a partner").addHelpText("after", `
|
|
3155
|
+
${chalk22.yellow("Subcommands:")}
|
|
3156
|
+
${chalk22.cyan("host <id>")} Create a room and get a code to share
|
|
3157
|
+
${chalk22.cyan("join <code>")} Join a room with the shared code
|
|
3158
|
+
${chalk22.cyan("sync")} Upload your solution to the room
|
|
3159
|
+
${chalk22.cyan("compare")} View both solutions side by side
|
|
3160
|
+
${chalk22.cyan("status")} Check room and sync status
|
|
3161
|
+
${chalk22.cyan("leave")} End the collaboration session
|
|
3162
|
+
|
|
3163
|
+
${chalk22.yellow("Examples:")}
|
|
3164
|
+
${chalk22.gray("$ leetcode collab host 1")} Start a session for Two Sum
|
|
3165
|
+
${chalk22.gray("$ leetcode collab join ABC123")} Join your partner's session
|
|
3166
|
+
${chalk22.gray("$ leetcode collab sync")} Upload your code after solving
|
|
3167
|
+
${chalk22.gray("$ leetcode collab compare")} Compare solutions
|
|
3168
|
+
`);
|
|
3169
|
+
collabCmd.command("host <problemId>").description("Host a collaboration session").action(collabHostCommand);
|
|
3170
|
+
collabCmd.command("join <roomCode>").description("Join a collaboration session").action(collabJoinCommand);
|
|
3171
|
+
collabCmd.command("sync").description("Sync your code with partner").action(collabSyncCommand);
|
|
3172
|
+
collabCmd.command("compare").description("Compare your solution with partner").action(collabCompareCommand);
|
|
3173
|
+
collabCmd.command("leave").description("Leave the collaboration session").action(collabLeaveCommand);
|
|
3174
|
+
collabCmd.command("status").description("Show collaboration status").action(collabStatusCommand);
|
|
2832
3175
|
program.showHelpAfterError("(add --help for additional information)");
|
|
2833
3176
|
program.parse();
|
|
2834
3177
|
if (!process.argv.slice(2).length) {
|
|
2835
3178
|
console.log();
|
|
2836
|
-
console.log(
|
|
2837
|
-
console.log(
|
|
3179
|
+
console.log(chalk22.bold.cyan(" \u{1F525} LeetCode CLI"));
|
|
3180
|
+
console.log(chalk22.gray(" A modern command-line interface for LeetCode"));
|
|
2838
3181
|
console.log();
|
|
2839
3182
|
program.outputHelp();
|
|
2840
3183
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@night-slayer18/leetcode-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "A modern LeetCode CLI built with TypeScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"author": "night-slayer18",
|
|
30
30
|
"license": "Apache-2.0",
|
|
31
31
|
"dependencies": {
|
|
32
|
+
"@supabase/supabase-js": "^2.90.1",
|
|
32
33
|
"chalk": "^5.3.0",
|
|
33
34
|
"cli-table3": "^0.6.5",
|
|
34
35
|
"commander": "^12.1.0",
|