@night-slayer18/leetcode-cli 1.4.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 +43 -1
- package/dist/index.js +847 -214
- package/package.json +2 -1
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
configStore.set("credentials", credentials);
|
|
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 };
|
|
503
479
|
},
|
|
504
|
-
|
|
505
|
-
|
|
480
|
+
set(creds) {
|
|
481
|
+
credentialsStore.set("session", creds.session);
|
|
482
|
+
credentialsStore.set("csrfToken", creds.csrfToken);
|
|
506
483
|
},
|
|
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");
|
|
529
|
-
},
|
|
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,19 +1267,23 @@ 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;
|
|
1278
1274
|
if (currentDepth >= MAX_SEARCH_DEPTH) return null;
|
|
1279
1275
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
1280
1276
|
for (const entry of entries) {
|
|
1281
|
-
|
|
1277
|
+
if (entry.name.startsWith(".")) continue;
|
|
1278
|
+
const fullPath = join4(dir, entry.name);
|
|
1282
1279
|
if (entry.isDirectory()) {
|
|
1283
1280
|
const found = await findSolutionFile(fullPath, problemId, currentDepth + 1);
|
|
1284
1281
|
if (found) return found;
|
|
1285
1282
|
} else if (entry.name.startsWith(`${problemId}.`)) {
|
|
1286
|
-
|
|
1283
|
+
const ext = entry.name.split(".").pop()?.toLowerCase();
|
|
1284
|
+
if (ext && ext in EXT_TO_LANG_MAP) {
|
|
1285
|
+
return fullPath;
|
|
1286
|
+
}
|
|
1287
1287
|
}
|
|
1288
1288
|
}
|
|
1289
1289
|
return null;
|
|
@@ -1293,7 +1293,7 @@ async function findFileByName(dir, fileName, currentDepth = 0) {
|
|
|
1293
1293
|
if (currentDepth >= MAX_SEARCH_DEPTH) return null;
|
|
1294
1294
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
1295
1295
|
for (const entry of entries) {
|
|
1296
|
-
const fullPath =
|
|
1296
|
+
const fullPath = join4(dir, entry.name);
|
|
1297
1297
|
if (entry.isDirectory()) {
|
|
1298
1298
|
const found = await findFileByName(fullPath, fileName, currentDepth + 1);
|
|
1299
1299
|
if (found) return found;
|
|
@@ -1303,6 +1303,19 @@ async function findFileByName(dir, fileName, currentDepth = 0) {
|
|
|
1303
1303
|
}
|
|
1304
1304
|
return null;
|
|
1305
1305
|
}
|
|
1306
|
+
var EXT_TO_LANG_MAP = {
|
|
1307
|
+
ts: "typescript",
|
|
1308
|
+
js: "javascript",
|
|
1309
|
+
py: "python3",
|
|
1310
|
+
java: "java",
|
|
1311
|
+
cpp: "cpp",
|
|
1312
|
+
c: "c",
|
|
1313
|
+
cs: "csharp",
|
|
1314
|
+
go: "go",
|
|
1315
|
+
rs: "rust",
|
|
1316
|
+
kt: "kotlin",
|
|
1317
|
+
swift: "swift"
|
|
1318
|
+
};
|
|
1306
1319
|
function getLangSlugFromExtension(ext) {
|
|
1307
1320
|
const langMap = {
|
|
1308
1321
|
ts: "typescript",
|
|
@@ -1398,6 +1411,82 @@ import { existsSync as existsSync4 } from "fs";
|
|
|
1398
1411
|
import { basename as basename2 } from "path";
|
|
1399
1412
|
import ora6 from "ora";
|
|
1400
1413
|
import chalk9 from "chalk";
|
|
1414
|
+
|
|
1415
|
+
// src/storage/timer.ts
|
|
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"),
|
|
1422
|
+
defaults: {
|
|
1423
|
+
solveTimes: {},
|
|
1424
|
+
activeTimer: null
|
|
1425
|
+
}
|
|
1426
|
+
});
|
|
1427
|
+
var timerStorage = {
|
|
1428
|
+
startTimer(problemId, title, difficulty, durationMinutes) {
|
|
1429
|
+
timerStore.set("activeTimer", {
|
|
1430
|
+
problemId,
|
|
1431
|
+
title,
|
|
1432
|
+
difficulty,
|
|
1433
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1434
|
+
durationMinutes
|
|
1435
|
+
});
|
|
1436
|
+
},
|
|
1437
|
+
getActiveTimer() {
|
|
1438
|
+
return timerStore.get("activeTimer");
|
|
1439
|
+
},
|
|
1440
|
+
stopTimer() {
|
|
1441
|
+
const active = timerStore.get("activeTimer");
|
|
1442
|
+
if (!active) return null;
|
|
1443
|
+
const startedAt = new Date(active.startedAt);
|
|
1444
|
+
const now = /* @__PURE__ */ new Date();
|
|
1445
|
+
const durationSeconds = Math.floor((now.getTime() - startedAt.getTime()) / 1e3);
|
|
1446
|
+
timerStore.set("activeTimer", null);
|
|
1447
|
+
return { durationSeconds };
|
|
1448
|
+
},
|
|
1449
|
+
recordSolveTime(problemId, title, difficulty, durationSeconds, timerMinutes) {
|
|
1450
|
+
const solveTimes = timerStore.get("solveTimes") ?? {};
|
|
1451
|
+
if (!solveTimes[problemId]) {
|
|
1452
|
+
solveTimes[problemId] = [];
|
|
1453
|
+
}
|
|
1454
|
+
solveTimes[problemId].push({
|
|
1455
|
+
problemId,
|
|
1456
|
+
title,
|
|
1457
|
+
difficulty,
|
|
1458
|
+
solvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1459
|
+
durationSeconds,
|
|
1460
|
+
timerMinutes
|
|
1461
|
+
});
|
|
1462
|
+
timerStore.set("solveTimes", solveTimes);
|
|
1463
|
+
},
|
|
1464
|
+
getSolveTimes(problemId) {
|
|
1465
|
+
const solveTimes = timerStore.get("solveTimes") ?? {};
|
|
1466
|
+
return solveTimes[problemId] ?? [];
|
|
1467
|
+
},
|
|
1468
|
+
getAllSolveTimes() {
|
|
1469
|
+
return timerStore.get("solveTimes") ?? {};
|
|
1470
|
+
},
|
|
1471
|
+
getStats() {
|
|
1472
|
+
const solveTimes = timerStore.get("solveTimes") ?? {};
|
|
1473
|
+
let totalProblems = 0;
|
|
1474
|
+
let totalTime = 0;
|
|
1475
|
+
for (const times of Object.values(solveTimes)) {
|
|
1476
|
+
totalProblems += times.length;
|
|
1477
|
+
for (const t of times) {
|
|
1478
|
+
totalTime += t.durationSeconds;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
return {
|
|
1482
|
+
totalProblems,
|
|
1483
|
+
totalTime,
|
|
1484
|
+
avgTime: totalProblems > 0 ? Math.floor(totalTime / totalProblems) : 0
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
|
|
1489
|
+
// src/commands/submit.ts
|
|
1401
1490
|
async function submitCommand(fileOrId) {
|
|
1402
1491
|
const { authorized } = await requireAuth();
|
|
1403
1492
|
if (!authorized) return;
|
|
@@ -1457,6 +1546,35 @@ async function submitCommand(fileOrId) {
|
|
|
1457
1546
|
);
|
|
1458
1547
|
spinner.stop();
|
|
1459
1548
|
displaySubmissionResult(result);
|
|
1549
|
+
if (result.status_msg === "Accepted") {
|
|
1550
|
+
const activeTimer = timerStorage.getActiveTimer();
|
|
1551
|
+
if (activeTimer && activeTimer.problemId === problemId) {
|
|
1552
|
+
const timerResult = timerStorage.stopTimer();
|
|
1553
|
+
if (timerResult) {
|
|
1554
|
+
timerStorage.recordSolveTime(
|
|
1555
|
+
problemId,
|
|
1556
|
+
problem.title,
|
|
1557
|
+
problem.difficulty,
|
|
1558
|
+
timerResult.durationSeconds,
|
|
1559
|
+
activeTimer.durationMinutes
|
|
1560
|
+
);
|
|
1561
|
+
const mins = Math.floor(timerResult.durationSeconds / 60);
|
|
1562
|
+
const secs = timerResult.durationSeconds % 60;
|
|
1563
|
+
const timeStr = `${mins}m ${secs}s`;
|
|
1564
|
+
const withinLimit = timerResult.durationSeconds <= activeTimer.durationMinutes * 60;
|
|
1565
|
+
console.log();
|
|
1566
|
+
console.log(chalk9.bold("\u23F1\uFE0F Timer Result:"));
|
|
1567
|
+
console.log(
|
|
1568
|
+
` Solved in ${withinLimit ? chalk9.green(timeStr) : chalk9.yellow(timeStr)} (limit: ${activeTimer.durationMinutes}m)`
|
|
1569
|
+
);
|
|
1570
|
+
if (withinLimit) {
|
|
1571
|
+
console.log(chalk9.green(" \u2713 Within time limit!"));
|
|
1572
|
+
} else {
|
|
1573
|
+
console.log(chalk9.yellow(" \u26A0 Exceeded time limit"));
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1460
1578
|
} catch (error) {
|
|
1461
1579
|
spinner.fail("Submission failed");
|
|
1462
1580
|
if (error instanceof Error) {
|
|
@@ -1704,7 +1822,7 @@ async function randomCommand(options) {
|
|
|
1704
1822
|
// src/commands/submissions.ts
|
|
1705
1823
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1706
1824
|
import { existsSync as existsSync5 } from "fs";
|
|
1707
|
-
import { join as
|
|
1825
|
+
import { join as join6 } from "path";
|
|
1708
1826
|
import ora10 from "ora";
|
|
1709
1827
|
import chalk14 from "chalk";
|
|
1710
1828
|
async function submissionsCommand(idOrSlug, options) {
|
|
@@ -1758,7 +1876,7 @@ async function submissionsCommand(idOrSlug, options) {
|
|
|
1758
1876
|
const workDir = config.getWorkDir();
|
|
1759
1877
|
const difficulty = problem.difficulty;
|
|
1760
1878
|
const category = problem.topicTags.length > 0 ? problem.topicTags[0].name.replace(/[^\w\s-]/g, "").trim() : "Uncategorized";
|
|
1761
|
-
const targetDir =
|
|
1879
|
+
const targetDir = join6(workDir, difficulty, category);
|
|
1762
1880
|
if (!existsSync5(targetDir)) {
|
|
1763
1881
|
await mkdir2(targetDir, { recursive: true });
|
|
1764
1882
|
}
|
|
@@ -1766,7 +1884,7 @@ async function submissionsCommand(idOrSlug, options) {
|
|
|
1766
1884
|
const supportedLang = LANG_SLUG_MAP[langSlug] ?? "txt";
|
|
1767
1885
|
const ext = LANGUAGE_EXTENSIONS[supportedLang] ?? langSlug;
|
|
1768
1886
|
const fileName = `${problem.questionFrontendId}.${problem.titleSlug}.submission-${lastAC.id}.${ext}`;
|
|
1769
|
-
const filePath =
|
|
1887
|
+
const filePath = join6(targetDir, fileName);
|
|
1770
1888
|
await writeFile2(filePath, details.code, "utf-8");
|
|
1771
1889
|
downloadSpinner.succeed(`Downloaded to ${chalk14.green(fileName)}`);
|
|
1772
1890
|
console.log(chalk14.gray(`Path: ${filePath}`));
|
|
@@ -1872,7 +1990,7 @@ async function configInteractiveCommand() {
|
|
|
1872
1990
|
}
|
|
1873
1991
|
function showCurrentConfig() {
|
|
1874
1992
|
const currentConfig = config.getConfig();
|
|
1875
|
-
const
|
|
1993
|
+
const creds = credentials.get();
|
|
1876
1994
|
console.log();
|
|
1877
1995
|
console.log(chalk15.bold("LeetCode CLI Configuration"));
|
|
1878
1996
|
console.log(chalk15.gray("\u2500".repeat(40)));
|
|
@@ -1883,7 +2001,7 @@ function showCurrentConfig() {
|
|
|
1883
2001
|
console.log(chalk15.gray("Editor: "), chalk15.white(currentConfig.editor ?? "(not set)"));
|
|
1884
2002
|
console.log(chalk15.gray("Work Dir: "), chalk15.white(currentConfig.workDir));
|
|
1885
2003
|
console.log(chalk15.gray("Repo URL: "), chalk15.white(currentConfig.repo ?? "(not set)"));
|
|
1886
|
-
console.log(chalk15.gray("Logged in: "),
|
|
2004
|
+
console.log(chalk15.gray("Logged in: "), creds ? chalk15.green("Yes") : chalk15.yellow("No"));
|
|
1887
2005
|
}
|
|
1888
2006
|
|
|
1889
2007
|
// src/commands/bookmark.ts
|
|
@@ -1892,8 +2010,8 @@ import Table2 from "cli-table3";
|
|
|
1892
2010
|
import ora11 from "ora";
|
|
1893
2011
|
|
|
1894
2012
|
// src/storage/bookmarks.ts
|
|
1895
|
-
import
|
|
1896
|
-
var bookmarksStore = new
|
|
2013
|
+
import Conf4 from "conf";
|
|
2014
|
+
var bookmarksStore = new Conf4({
|
|
1897
2015
|
projectName: "leetcode-cli-bookmarks",
|
|
1898
2016
|
defaults: { bookmarks: [] }
|
|
1899
2017
|
});
|
|
@@ -2046,7 +2164,7 @@ function colorDifficulty2(difficulty) {
|
|
|
2046
2164
|
|
|
2047
2165
|
// src/commands/notes.ts
|
|
2048
2166
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
2049
|
-
import { join as
|
|
2167
|
+
import { join as join7 } from "path";
|
|
2050
2168
|
import { existsSync as existsSync6 } from "fs";
|
|
2051
2169
|
import chalk17 from "chalk";
|
|
2052
2170
|
async function notesCommand(problemId, action) {
|
|
@@ -2056,8 +2174,8 @@ async function notesCommand(problemId, action) {
|
|
|
2056
2174
|
return;
|
|
2057
2175
|
}
|
|
2058
2176
|
const noteAction = action === "view" ? "view" : "edit";
|
|
2059
|
-
const notesDir =
|
|
2060
|
-
const notePath =
|
|
2177
|
+
const notesDir = join7(config.getWorkDir(), ".notes");
|
|
2178
|
+
const notePath = join7(notesDir, `${problemId}.md`);
|
|
2061
2179
|
if (!existsSync6(notesDir)) {
|
|
2062
2180
|
await mkdir3(notesDir, { recursive: true });
|
|
2063
2181
|
}
|
|
@@ -2376,162 +2494,644 @@ async function syncCommand() {
|
|
|
2376
2494
|
}
|
|
2377
2495
|
}
|
|
2378
2496
|
|
|
2497
|
+
// src/commands/timer.ts
|
|
2498
|
+
import ora14 from "ora";
|
|
2499
|
+
import chalk20 from "chalk";
|
|
2500
|
+
var DEFAULT_TIMES = {
|
|
2501
|
+
Easy: 20,
|
|
2502
|
+
Medium: 40,
|
|
2503
|
+
Hard: 60
|
|
2504
|
+
};
|
|
2505
|
+
function formatDuration(seconds) {
|
|
2506
|
+
if (seconds < 60) {
|
|
2507
|
+
return `${seconds}s`;
|
|
2508
|
+
} else if (seconds < 3600) {
|
|
2509
|
+
const mins = Math.floor(seconds / 60);
|
|
2510
|
+
const secs = seconds % 60;
|
|
2511
|
+
return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;
|
|
2512
|
+
} else {
|
|
2513
|
+
const hours = Math.floor(seconds / 3600);
|
|
2514
|
+
const mins = Math.floor(seconds % 3600 / 60);
|
|
2515
|
+
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
async function timerCommand(idOrSlug, options) {
|
|
2519
|
+
if (options.stats) {
|
|
2520
|
+
await showTimerStats(idOrSlug);
|
|
2521
|
+
return;
|
|
2522
|
+
}
|
|
2523
|
+
if (options.stop) {
|
|
2524
|
+
await stopActiveTimer();
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2527
|
+
if (!idOrSlug) {
|
|
2528
|
+
console.log(chalk20.yellow("Please provide a problem ID to start the timer."));
|
|
2529
|
+
console.log(chalk20.gray("Usage: leetcode timer <id>"));
|
|
2530
|
+
console.log(chalk20.gray(" leetcode timer --stats"));
|
|
2531
|
+
console.log(chalk20.gray(" leetcode timer --stop"));
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
const { authorized } = await requireAuth();
|
|
2535
|
+
if (!authorized) return;
|
|
2536
|
+
const activeTimer = timerStorage.getActiveTimer();
|
|
2537
|
+
if (activeTimer) {
|
|
2538
|
+
const startedAt = new Date(activeTimer.startedAt);
|
|
2539
|
+
const elapsed = Math.floor((Date.now() - startedAt.getTime()) / 1e3);
|
|
2540
|
+
console.log(chalk20.yellow("\u26A0\uFE0F You have an active timer running:"));
|
|
2541
|
+
console.log(chalk20.white(` Problem: ${activeTimer.title}`));
|
|
2542
|
+
console.log(chalk20.white(` Elapsed: ${formatDuration(elapsed)}`));
|
|
2543
|
+
console.log();
|
|
2544
|
+
console.log(chalk20.gray("Use `leetcode timer --stop` to stop it first."));
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
const spinner = ora14("Fetching problem...").start();
|
|
2548
|
+
try {
|
|
2549
|
+
let problem;
|
|
2550
|
+
if (/^\d+$/.test(idOrSlug)) {
|
|
2551
|
+
problem = await leetcodeClient.getProblemById(idOrSlug);
|
|
2552
|
+
} else {
|
|
2553
|
+
problem = await leetcodeClient.getProblem(idOrSlug);
|
|
2554
|
+
}
|
|
2555
|
+
if (!problem) {
|
|
2556
|
+
spinner.fail(`Problem "${idOrSlug}" not found`);
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
spinner.stop();
|
|
2560
|
+
const durationMinutes = options.minutes ?? DEFAULT_TIMES[problem.difficulty] ?? 30;
|
|
2561
|
+
timerStorage.startTimer(
|
|
2562
|
+
problem.questionFrontendId,
|
|
2563
|
+
problem.title,
|
|
2564
|
+
problem.difficulty,
|
|
2565
|
+
durationMinutes
|
|
2566
|
+
);
|
|
2567
|
+
console.log();
|
|
2568
|
+
console.log(chalk20.bold.cyan("\u23F1\uFE0F Interview Mode Started!"));
|
|
2569
|
+
console.log(chalk20.gray("\u2500".repeat(50)));
|
|
2570
|
+
console.log();
|
|
2571
|
+
console.log(chalk20.white(`Problem: ${problem.questionFrontendId}. ${problem.title}`));
|
|
2572
|
+
console.log(chalk20.white(`Difficulty: ${chalk20.bold(problem.difficulty)}`));
|
|
2573
|
+
console.log(chalk20.white(`Time Limit: ${chalk20.bold.yellow(durationMinutes + " minutes")}`));
|
|
2574
|
+
console.log();
|
|
2575
|
+
console.log(chalk20.gray("\u2500".repeat(50)));
|
|
2576
|
+
console.log(chalk20.green("\u2713 Timer is running in background"));
|
|
2577
|
+
console.log(chalk20.gray(" When you submit successfully, your time will be recorded."));
|
|
2578
|
+
console.log(chalk20.gray(" Use `leetcode timer --stop` to cancel."));
|
|
2579
|
+
console.log();
|
|
2580
|
+
await pickCommand(idOrSlug, { open: true });
|
|
2581
|
+
} catch (error) {
|
|
2582
|
+
spinner.fail("Failed to start timer");
|
|
2583
|
+
if (error instanceof Error) {
|
|
2584
|
+
console.log(chalk20.red(error.message));
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
async function stopActiveTimer() {
|
|
2589
|
+
const result = timerStorage.stopTimer();
|
|
2590
|
+
if (!result) {
|
|
2591
|
+
console.log(chalk20.yellow("No active timer to stop."));
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
console.log(chalk20.green("\u23F1\uFE0F Timer stopped."));
|
|
2595
|
+
console.log(chalk20.gray(`Elapsed time: ${formatDuration(result.durationSeconds)}`));
|
|
2596
|
+
console.log(chalk20.gray("(Time not recorded since problem was not submitted)"));
|
|
2597
|
+
}
|
|
2598
|
+
async function showTimerStats(problemId) {
|
|
2599
|
+
if (problemId && /^\d+$/.test(problemId)) {
|
|
2600
|
+
const times = timerStorage.getSolveTimes(problemId);
|
|
2601
|
+
if (times.length === 0) {
|
|
2602
|
+
console.log(chalk20.yellow(`No solve times recorded for problem ${problemId}`));
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
console.log();
|
|
2606
|
+
console.log(chalk20.bold(`\u23F1\uFE0F Solve Times for Problem ${problemId}`));
|
|
2607
|
+
console.log(chalk20.gray("\u2500".repeat(40)));
|
|
2608
|
+
for (const entry of times) {
|
|
2609
|
+
const date = new Date(entry.solvedAt).toLocaleDateString();
|
|
2610
|
+
const duration = formatDuration(entry.durationSeconds);
|
|
2611
|
+
const limit = entry.timerMinutes;
|
|
2612
|
+
const withinLimit = entry.durationSeconds <= limit * 60;
|
|
2613
|
+
console.log(
|
|
2614
|
+
` ${date} ${withinLimit ? chalk20.green(duration) : chalk20.red(duration)} (limit: ${limit}m)`
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
} else {
|
|
2618
|
+
const stats = timerStorage.getStats();
|
|
2619
|
+
const allTimes = timerStorage.getAllSolveTimes();
|
|
2620
|
+
console.log();
|
|
2621
|
+
console.log(chalk20.bold("\u23F1\uFE0F Timer Statistics"));
|
|
2622
|
+
console.log(chalk20.gray("\u2500".repeat(40)));
|
|
2623
|
+
console.log();
|
|
2624
|
+
console.log(` Problems timed: ${chalk20.cyan(stats.totalProblems)}`);
|
|
2625
|
+
console.log(` Total time: ${chalk20.cyan(formatDuration(stats.totalTime))}`);
|
|
2626
|
+
console.log(` Average time: ${chalk20.cyan(formatDuration(stats.avgTime))}`);
|
|
2627
|
+
console.log();
|
|
2628
|
+
const recentSolves = [];
|
|
2629
|
+
for (const [id, times] of Object.entries(allTimes)) {
|
|
2630
|
+
for (const t of times) {
|
|
2631
|
+
recentSolves.push({
|
|
2632
|
+
problemId: id,
|
|
2633
|
+
title: t.title,
|
|
2634
|
+
duration: t.durationSeconds,
|
|
2635
|
+
date: t.solvedAt
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
if (recentSolves.length > 0) {
|
|
2640
|
+
recentSolves.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
2641
|
+
console.log(chalk20.bold(" Recent Solves:"));
|
|
2642
|
+
for (const solve of recentSolves.slice(0, 5)) {
|
|
2643
|
+
const date = new Date(solve.date).toLocaleDateString();
|
|
2644
|
+
console.log(
|
|
2645
|
+
chalk20.gray(` ${date} `) + chalk20.white(`${solve.problemId}. ${solve.title.substring(0, 25)}`) + chalk20.gray(" ") + chalk20.cyan(formatDuration(solve.duration))
|
|
2646
|
+
);
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
console.log();
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
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
|
+
|
|
2379
2979
|
// src/index.ts
|
|
2380
2980
|
var program = new Command();
|
|
2381
2981
|
program.configureHelp({
|
|
2382
2982
|
sortSubcommands: true,
|
|
2383
|
-
subcommandTerm: (cmd) =>
|
|
2384
|
-
subcommandDescription: (cmd) =>
|
|
2385
|
-
optionTerm: (option) =>
|
|
2386
|
-
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)
|
|
2387
2987
|
});
|
|
2388
|
-
program.name("leetcode").usage("[command] [options]").description(
|
|
2389
|
-
${
|
|
2390
|
-
${
|
|
2391
|
-
${
|
|
2392
|
-
${
|
|
2393
|
-
${
|
|
2394
|
-
${
|
|
2395
|
-
${
|
|
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
|
|
2396
2996
|
`);
|
|
2397
2997
|
program.command("login").description("Login to LeetCode with browser cookies").addHelpText("after", `
|
|
2398
|
-
${
|
|
2399
|
-
1. Open ${
|
|
2998
|
+
${chalk22.yellow("How to login:")}
|
|
2999
|
+
1. Open ${chalk22.cyan("https://leetcode.com")} in your browser
|
|
2400
3000
|
2. Login to your account
|
|
2401
3001
|
3. Open Developer Tools (F12) \u2192 Application \u2192 Cookies
|
|
2402
|
-
4. Copy values of ${
|
|
3002
|
+
4. Copy values of ${chalk22.green("LEETCODE_SESSION")} and ${chalk22.green("csrftoken")}
|
|
2403
3003
|
5. Paste when prompted by this command
|
|
2404
3004
|
`).action(loginCommand);
|
|
2405
3005
|
program.command("logout").description("Clear stored credentials").action(logoutCommand);
|
|
2406
3006
|
program.command("whoami").description("Check current login status").action(whoamiCommand);
|
|
2407
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", `
|
|
2408
|
-
${
|
|
2409
|
-
${
|
|
2410
|
-
${
|
|
2411
|
-
${
|
|
2412
|
-
${
|
|
2413
|
-
${
|
|
2414
|
-
${
|
|
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
|
|
2415
3015
|
`).action(listCommand);
|
|
2416
3016
|
program.command("show <id>").alias("s").description("Show problem description").addHelpText("after", `
|
|
2417
|
-
${
|
|
2418
|
-
${
|
|
2419
|
-
${
|
|
2420
|
-
${
|
|
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
|
|
2421
3021
|
`).action(showCommand);
|
|
2422
3022
|
program.command("daily").alias("d").description("Show today's daily challenge").addHelpText("after", `
|
|
2423
|
-
${
|
|
2424
|
-
${
|
|
2425
|
-
${
|
|
3023
|
+
${chalk22.yellow("Examples:")}
|
|
3024
|
+
${chalk22.cyan("$ leetcode daily")} Show today's challenge
|
|
3025
|
+
${chalk22.cyan("$ leetcode d")} Short alias
|
|
2426
3026
|
`).action(dailyCommand);
|
|
2427
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", `
|
|
2428
|
-
${
|
|
2429
|
-
${
|
|
2430
|
-
${
|
|
2431
|
-
${
|
|
2432
|
-
${
|
|
2433
|
-
${
|
|
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
|
|
2434
3034
|
`).action(randomCommand);
|
|
2435
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", `
|
|
2436
|
-
${
|
|
2437
|
-
${
|
|
2438
|
-
${
|
|
2439
|
-
${
|
|
2440
|
-
${
|
|
2441
|
-
${
|
|
2442
|
-
|
|
2443
|
-
${
|
|
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/")}
|
|
2444
3044
|
`).action(async (id, options) => {
|
|
2445
3045
|
await pickCommand(id, options);
|
|
2446
3046
|
});
|
|
2447
3047
|
program.command("pick-batch <ids...>").description("Generate solution files for multiple problems").option("-l, --lang <language>", "Programming language for the solutions").addHelpText("after", `
|
|
2448
|
-
${
|
|
2449
|
-
${
|
|
2450
|
-
${
|
|
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
|
|
2451
3051
|
`).action(batchPickCommand);
|
|
2452
3052
|
program.command("test <file>").alias("t").description("Test solution against sample test cases").option("-c, --testcase <testcase>", "Custom test case").addHelpText("after", `
|
|
2453
|
-
${
|
|
2454
|
-
${
|
|
2455
|
-
${
|
|
2456
|
-
${
|
|
2457
|
-
${
|
|
2458
|
-
${
|
|
2459
|
-
|
|
2460
|
-
${
|
|
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.")}
|
|
2461
3061
|
`).action(testCommand);
|
|
2462
3062
|
program.command("submit <file>").alias("x").description("Submit solution to LeetCode").addHelpText("after", `
|
|
2463
|
-
${
|
|
2464
|
-
${
|
|
2465
|
-
${
|
|
2466
|
-
${
|
|
2467
|
-
${
|
|
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
|
|
2468
3068
|
`).action(submitCommand);
|
|
2469
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", `
|
|
2470
|
-
${
|
|
2471
|
-
${
|
|
2472
|
-
${
|
|
2473
|
-
${
|
|
2474
|
-
${
|
|
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
|
|
2475
3075
|
`).action(submissionsCommand);
|
|
2476
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", `
|
|
2477
|
-
${
|
|
2478
|
-
${
|
|
3077
|
+
${chalk22.yellow("Options Explained:")}
|
|
3078
|
+
${chalk22.cyan("-c, --calendar")} Shows a table of your weekly submissions and active days
|
|
2479
3079
|
for the past 12 weeks. Useful for tracking consistency.
|
|
2480
3080
|
|
|
2481
|
-
${
|
|
3081
|
+
${chalk22.cyan("-s, --skills")} Shows how many problems you solved per topic tag,
|
|
2482
3082
|
grouped by difficulty (Fundamental/Intermediate/Advanced).
|
|
2483
3083
|
Helps identify your strong and weak areas.
|
|
2484
3084
|
|
|
2485
|
-
${
|
|
3085
|
+
${chalk22.cyan("-t, --trend")} Shows a bar chart of daily submissions for the past week.
|
|
2486
3086
|
Visualizes your recent coding activity day by day.
|
|
2487
3087
|
|
|
2488
|
-
${
|
|
2489
|
-
${
|
|
2490
|
-
${
|
|
2491
|
-
${
|
|
2492
|
-
${
|
|
2493
|
-
${
|
|
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
|
|
2494
3094
|
`).action((username, options) => statCommand(username, options));
|
|
2495
3095
|
program.command("today").description("Show today's progress summary").addHelpText("after", `
|
|
2496
|
-
${
|
|
2497
|
-
${
|
|
3096
|
+
${chalk22.yellow("Examples:")}
|
|
3097
|
+
${chalk22.cyan("$ leetcode today")} Show streak, solved, and daily challenge
|
|
2498
3098
|
`).action(todayCommand);
|
|
2499
3099
|
program.command("bookmark <action> [id]").description("Manage problem bookmarks").addHelpText("after", `
|
|
2500
|
-
${
|
|
2501
|
-
${
|
|
2502
|
-
${
|
|
2503
|
-
${
|
|
2504
|
-
${
|
|
2505
|
-
|
|
2506
|
-
${
|
|
2507
|
-
${
|
|
2508
|
-
${
|
|
2509
|
-
${
|
|
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
|
|
2510
3110
|
`).action(bookmarkCommand);
|
|
2511
3111
|
program.command("note <id> [action]").description("View or edit notes for a problem").addHelpText("after", `
|
|
2512
|
-
${
|
|
2513
|
-
${
|
|
2514
|
-
${
|
|
2515
|
-
|
|
2516
|
-
${
|
|
2517
|
-
${
|
|
2518
|
-
${
|
|
2519
|
-
${
|
|
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
|
|
2520
3120
|
`).action(notesCommand);
|
|
2521
3121
|
program.command("sync").description("Sync solutions to Git repository").addHelpText("after", `
|
|
2522
|
-
${
|
|
2523
|
-
${
|
|
3122
|
+
${chalk22.yellow("Examples:")}
|
|
3123
|
+
${chalk22.cyan("$ leetcode sync")} Sync all solutions to remote
|
|
2524
3124
|
`).action(syncCommand);
|
|
2525
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", `
|
|
2526
|
-
${
|
|
2527
|
-
${
|
|
2528
|
-
${
|
|
2529
|
-
${
|
|
2530
|
-
${
|
|
2531
|
-
${
|
|
2532
|
-
${
|
|
2533
|
-
|
|
2534
|
-
${
|
|
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")}
|
|
2535
3135
|
`).action(async (options) => {
|
|
2536
3136
|
if (options.interactive) {
|
|
2537
3137
|
await configInteractiveCommand();
|
|
@@ -2539,12 +3139,45 @@ ${chalk20.gray("Supported languages: typescript, javascript, python3, java, cpp,
|
|
|
2539
3139
|
await configCommand(options);
|
|
2540
3140
|
}
|
|
2541
3141
|
});
|
|
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", `
|
|
3143
|
+
${chalk22.yellow("How it works:")}
|
|
3144
|
+
Start a problem with a countdown timer to simulate interview conditions.
|
|
3145
|
+
Default time limits: Easy (20 min), Medium (40 min), Hard (60 min).
|
|
3146
|
+
Your solve times are recorded when you submit successfully.
|
|
3147
|
+
|
|
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
|
|
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);
|
|
2542
3175
|
program.showHelpAfterError("(add --help for additional information)");
|
|
2543
3176
|
program.parse();
|
|
2544
3177
|
if (!process.argv.slice(2).length) {
|
|
2545
3178
|
console.log();
|
|
2546
|
-
console.log(
|
|
2547
|
-
console.log(
|
|
3179
|
+
console.log(chalk22.bold.cyan(" \u{1F525} LeetCode CLI"));
|
|
3180
|
+
console.log(chalk22.gray(" A modern command-line interface for LeetCode"));
|
|
2548
3181
|
console.log();
|
|
2549
3182
|
program.outputHelp();
|
|
2550
3183
|
}
|