@night-slayer18/leetcode-cli 1.4.0 → 1.5.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 +17 -1
- package/dist/index.js +402 -112
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,7 +71,7 @@ leetcode submit 1
|
|
|
71
71
|
| `submit <id\|file>` | Submit solution to LeetCode |
|
|
72
72
|
| `submissions <id>` | View past submissions |
|
|
73
73
|
| `stat [username]` | Show user statistics |
|
|
74
|
-
| `
|
|
74
|
+
| `timer <id>` | Interview mode with timer |
|
|
75
75
|
| `config` | View or set configuration |
|
|
76
76
|
| `sync` | Sync solutions to Git repository |
|
|
77
77
|
|
|
@@ -207,6 +207,22 @@ leetcode stat -t
|
|
|
207
207
|
# Sync all solutions to your configured git repo
|
|
208
208
|
leetcode sync
|
|
209
209
|
```
|
|
210
|
+
|
|
211
|
+
### Interview Timer
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# Start timer for a problem (default: Easy=20m, Medium=40m, Hard=60m)
|
|
215
|
+
leetcode timer 1
|
|
216
|
+
|
|
217
|
+
# Custom time limit
|
|
218
|
+
leetcode timer 1 -m 30
|
|
219
|
+
|
|
220
|
+
# View your solve time stats
|
|
221
|
+
leetcode timer --stats
|
|
222
|
+
|
|
223
|
+
# Stop active timer
|
|
224
|
+
leetcode timer --stop
|
|
225
|
+
```
|
|
210
226
|
|
|
211
227
|
### Configuration
|
|
212
228
|
|
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 chalk21 from "chalk";
|
|
6
6
|
|
|
7
7
|
// src/commands/login.ts
|
|
8
8
|
import inquirer from "inquirer";
|
|
@@ -1278,12 +1278,16 @@ async function findSolutionFile(dir, problemId, currentDepth = 0) {
|
|
|
1278
1278
|
if (currentDepth >= MAX_SEARCH_DEPTH) return null;
|
|
1279
1279
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
1280
1280
|
for (const entry of entries) {
|
|
1281
|
+
if (entry.name.startsWith(".")) continue;
|
|
1281
1282
|
const fullPath = join3(dir, entry.name);
|
|
1282
1283
|
if (entry.isDirectory()) {
|
|
1283
1284
|
const found = await findSolutionFile(fullPath, problemId, currentDepth + 1);
|
|
1284
1285
|
if (found) return found;
|
|
1285
1286
|
} else if (entry.name.startsWith(`${problemId}.`)) {
|
|
1286
|
-
|
|
1287
|
+
const ext = entry.name.split(".").pop()?.toLowerCase();
|
|
1288
|
+
if (ext && ext in EXT_TO_LANG_MAP) {
|
|
1289
|
+
return fullPath;
|
|
1290
|
+
}
|
|
1287
1291
|
}
|
|
1288
1292
|
}
|
|
1289
1293
|
return null;
|
|
@@ -1303,6 +1307,19 @@ async function findFileByName(dir, fileName, currentDepth = 0) {
|
|
|
1303
1307
|
}
|
|
1304
1308
|
return null;
|
|
1305
1309
|
}
|
|
1310
|
+
var EXT_TO_LANG_MAP = {
|
|
1311
|
+
ts: "typescript",
|
|
1312
|
+
js: "javascript",
|
|
1313
|
+
py: "python3",
|
|
1314
|
+
java: "java",
|
|
1315
|
+
cpp: "cpp",
|
|
1316
|
+
c: "c",
|
|
1317
|
+
cs: "csharp",
|
|
1318
|
+
go: "go",
|
|
1319
|
+
rs: "rust",
|
|
1320
|
+
kt: "kotlin",
|
|
1321
|
+
swift: "swift"
|
|
1322
|
+
};
|
|
1306
1323
|
function getLangSlugFromExtension(ext) {
|
|
1307
1324
|
const langMap = {
|
|
1308
1325
|
ts: "typescript",
|
|
@@ -1398,6 +1415,82 @@ import { existsSync as existsSync4 } from "fs";
|
|
|
1398
1415
|
import { basename as basename2 } from "path";
|
|
1399
1416
|
import ora6 from "ora";
|
|
1400
1417
|
import chalk9 from "chalk";
|
|
1418
|
+
|
|
1419
|
+
// src/storage/timer.ts
|
|
1420
|
+
import Conf2 from "conf";
|
|
1421
|
+
import { homedir as homedir2 } from "os";
|
|
1422
|
+
import { join as join4 } from "path";
|
|
1423
|
+
var timerStore = new Conf2({
|
|
1424
|
+
projectName: "leetcode-cli-timer",
|
|
1425
|
+
cwd: join4(homedir2(), ".leetcode"),
|
|
1426
|
+
defaults: {
|
|
1427
|
+
solveTimes: {},
|
|
1428
|
+
activeTimer: null
|
|
1429
|
+
}
|
|
1430
|
+
});
|
|
1431
|
+
var timerStorage = {
|
|
1432
|
+
startTimer(problemId, title, difficulty, durationMinutes) {
|
|
1433
|
+
timerStore.set("activeTimer", {
|
|
1434
|
+
problemId,
|
|
1435
|
+
title,
|
|
1436
|
+
difficulty,
|
|
1437
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1438
|
+
durationMinutes
|
|
1439
|
+
});
|
|
1440
|
+
},
|
|
1441
|
+
getActiveTimer() {
|
|
1442
|
+
return timerStore.get("activeTimer");
|
|
1443
|
+
},
|
|
1444
|
+
stopTimer() {
|
|
1445
|
+
const active = timerStore.get("activeTimer");
|
|
1446
|
+
if (!active) return null;
|
|
1447
|
+
const startedAt = new Date(active.startedAt);
|
|
1448
|
+
const now = /* @__PURE__ */ new Date();
|
|
1449
|
+
const durationSeconds = Math.floor((now.getTime() - startedAt.getTime()) / 1e3);
|
|
1450
|
+
timerStore.set("activeTimer", null);
|
|
1451
|
+
return { durationSeconds };
|
|
1452
|
+
},
|
|
1453
|
+
recordSolveTime(problemId, title, difficulty, durationSeconds, timerMinutes) {
|
|
1454
|
+
const solveTimes = timerStore.get("solveTimes") ?? {};
|
|
1455
|
+
if (!solveTimes[problemId]) {
|
|
1456
|
+
solveTimes[problemId] = [];
|
|
1457
|
+
}
|
|
1458
|
+
solveTimes[problemId].push({
|
|
1459
|
+
problemId,
|
|
1460
|
+
title,
|
|
1461
|
+
difficulty,
|
|
1462
|
+
solvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1463
|
+
durationSeconds,
|
|
1464
|
+
timerMinutes
|
|
1465
|
+
});
|
|
1466
|
+
timerStore.set("solveTimes", solveTimes);
|
|
1467
|
+
},
|
|
1468
|
+
getSolveTimes(problemId) {
|
|
1469
|
+
const solveTimes = timerStore.get("solveTimes") ?? {};
|
|
1470
|
+
return solveTimes[problemId] ?? [];
|
|
1471
|
+
},
|
|
1472
|
+
getAllSolveTimes() {
|
|
1473
|
+
return timerStore.get("solveTimes") ?? {};
|
|
1474
|
+
},
|
|
1475
|
+
getStats() {
|
|
1476
|
+
const solveTimes = timerStore.get("solveTimes") ?? {};
|
|
1477
|
+
let totalProblems = 0;
|
|
1478
|
+
let totalTime = 0;
|
|
1479
|
+
for (const times of Object.values(solveTimes)) {
|
|
1480
|
+
totalProblems += times.length;
|
|
1481
|
+
for (const t of times) {
|
|
1482
|
+
totalTime += t.durationSeconds;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
return {
|
|
1486
|
+
totalProblems,
|
|
1487
|
+
totalTime,
|
|
1488
|
+
avgTime: totalProblems > 0 ? Math.floor(totalTime / totalProblems) : 0
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
|
|
1493
|
+
// src/commands/submit.ts
|
|
1401
1494
|
async function submitCommand(fileOrId) {
|
|
1402
1495
|
const { authorized } = await requireAuth();
|
|
1403
1496
|
if (!authorized) return;
|
|
@@ -1457,6 +1550,35 @@ async function submitCommand(fileOrId) {
|
|
|
1457
1550
|
);
|
|
1458
1551
|
spinner.stop();
|
|
1459
1552
|
displaySubmissionResult(result);
|
|
1553
|
+
if (result.status_msg === "Accepted") {
|
|
1554
|
+
const activeTimer = timerStorage.getActiveTimer();
|
|
1555
|
+
if (activeTimer && activeTimer.problemId === problemId) {
|
|
1556
|
+
const timerResult = timerStorage.stopTimer();
|
|
1557
|
+
if (timerResult) {
|
|
1558
|
+
timerStorage.recordSolveTime(
|
|
1559
|
+
problemId,
|
|
1560
|
+
problem.title,
|
|
1561
|
+
problem.difficulty,
|
|
1562
|
+
timerResult.durationSeconds,
|
|
1563
|
+
activeTimer.durationMinutes
|
|
1564
|
+
);
|
|
1565
|
+
const mins = Math.floor(timerResult.durationSeconds / 60);
|
|
1566
|
+
const secs = timerResult.durationSeconds % 60;
|
|
1567
|
+
const timeStr = `${mins}m ${secs}s`;
|
|
1568
|
+
const withinLimit = timerResult.durationSeconds <= activeTimer.durationMinutes * 60;
|
|
1569
|
+
console.log();
|
|
1570
|
+
console.log(chalk9.bold("\u23F1\uFE0F Timer Result:"));
|
|
1571
|
+
console.log(
|
|
1572
|
+
` Solved in ${withinLimit ? chalk9.green(timeStr) : chalk9.yellow(timeStr)} (limit: ${activeTimer.durationMinutes}m)`
|
|
1573
|
+
);
|
|
1574
|
+
if (withinLimit) {
|
|
1575
|
+
console.log(chalk9.green(" \u2713 Within time limit!"));
|
|
1576
|
+
} else {
|
|
1577
|
+
console.log(chalk9.yellow(" \u26A0 Exceeded time limit"));
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1460
1582
|
} catch (error) {
|
|
1461
1583
|
spinner.fail("Submission failed");
|
|
1462
1584
|
if (error instanceof Error) {
|
|
@@ -1704,7 +1826,7 @@ async function randomCommand(options) {
|
|
|
1704
1826
|
// src/commands/submissions.ts
|
|
1705
1827
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1706
1828
|
import { existsSync as existsSync5 } from "fs";
|
|
1707
|
-
import { join as
|
|
1829
|
+
import { join as join5 } from "path";
|
|
1708
1830
|
import ora10 from "ora";
|
|
1709
1831
|
import chalk14 from "chalk";
|
|
1710
1832
|
async function submissionsCommand(idOrSlug, options) {
|
|
@@ -1758,7 +1880,7 @@ async function submissionsCommand(idOrSlug, options) {
|
|
|
1758
1880
|
const workDir = config.getWorkDir();
|
|
1759
1881
|
const difficulty = problem.difficulty;
|
|
1760
1882
|
const category = problem.topicTags.length > 0 ? problem.topicTags[0].name.replace(/[^\w\s-]/g, "").trim() : "Uncategorized";
|
|
1761
|
-
const targetDir =
|
|
1883
|
+
const targetDir = join5(workDir, difficulty, category);
|
|
1762
1884
|
if (!existsSync5(targetDir)) {
|
|
1763
1885
|
await mkdir2(targetDir, { recursive: true });
|
|
1764
1886
|
}
|
|
@@ -1766,7 +1888,7 @@ async function submissionsCommand(idOrSlug, options) {
|
|
|
1766
1888
|
const supportedLang = LANG_SLUG_MAP[langSlug] ?? "txt";
|
|
1767
1889
|
const ext = LANGUAGE_EXTENSIONS[supportedLang] ?? langSlug;
|
|
1768
1890
|
const fileName = `${problem.questionFrontendId}.${problem.titleSlug}.submission-${lastAC.id}.${ext}`;
|
|
1769
|
-
const filePath =
|
|
1891
|
+
const filePath = join5(targetDir, fileName);
|
|
1770
1892
|
await writeFile2(filePath, details.code, "utf-8");
|
|
1771
1893
|
downloadSpinner.succeed(`Downloaded to ${chalk14.green(fileName)}`);
|
|
1772
1894
|
console.log(chalk14.gray(`Path: ${filePath}`));
|
|
@@ -1892,8 +2014,8 @@ import Table2 from "cli-table3";
|
|
|
1892
2014
|
import ora11 from "ora";
|
|
1893
2015
|
|
|
1894
2016
|
// src/storage/bookmarks.ts
|
|
1895
|
-
import
|
|
1896
|
-
var bookmarksStore = new
|
|
2017
|
+
import Conf3 from "conf";
|
|
2018
|
+
var bookmarksStore = new Conf3({
|
|
1897
2019
|
projectName: "leetcode-cli-bookmarks",
|
|
1898
2020
|
defaults: { bookmarks: [] }
|
|
1899
2021
|
});
|
|
@@ -2046,7 +2168,7 @@ function colorDifficulty2(difficulty) {
|
|
|
2046
2168
|
|
|
2047
2169
|
// src/commands/notes.ts
|
|
2048
2170
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
2049
|
-
import { join as
|
|
2171
|
+
import { join as join6 } from "path";
|
|
2050
2172
|
import { existsSync as existsSync6 } from "fs";
|
|
2051
2173
|
import chalk17 from "chalk";
|
|
2052
2174
|
async function notesCommand(problemId, action) {
|
|
@@ -2056,8 +2178,8 @@ async function notesCommand(problemId, action) {
|
|
|
2056
2178
|
return;
|
|
2057
2179
|
}
|
|
2058
2180
|
const noteAction = action === "view" ? "view" : "edit";
|
|
2059
|
-
const notesDir =
|
|
2060
|
-
const notePath =
|
|
2181
|
+
const notesDir = join6(config.getWorkDir(), ".notes");
|
|
2182
|
+
const notePath = join6(notesDir, `${problemId}.md`);
|
|
2061
2183
|
if (!existsSync6(notesDir)) {
|
|
2062
2184
|
await mkdir3(notesDir, { recursive: true });
|
|
2063
2185
|
}
|
|
@@ -2376,162 +2498,318 @@ async function syncCommand() {
|
|
|
2376
2498
|
}
|
|
2377
2499
|
}
|
|
2378
2500
|
|
|
2501
|
+
// src/commands/timer.ts
|
|
2502
|
+
import ora14 from "ora";
|
|
2503
|
+
import chalk20 from "chalk";
|
|
2504
|
+
var DEFAULT_TIMES = {
|
|
2505
|
+
Easy: 20,
|
|
2506
|
+
Medium: 40,
|
|
2507
|
+
Hard: 60
|
|
2508
|
+
};
|
|
2509
|
+
function formatDuration(seconds) {
|
|
2510
|
+
if (seconds < 60) {
|
|
2511
|
+
return `${seconds}s`;
|
|
2512
|
+
} else if (seconds < 3600) {
|
|
2513
|
+
const mins = Math.floor(seconds / 60);
|
|
2514
|
+
const secs = seconds % 60;
|
|
2515
|
+
return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;
|
|
2516
|
+
} else {
|
|
2517
|
+
const hours = Math.floor(seconds / 3600);
|
|
2518
|
+
const mins = Math.floor(seconds % 3600 / 60);
|
|
2519
|
+
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
async function timerCommand(idOrSlug, options) {
|
|
2523
|
+
if (options.stats) {
|
|
2524
|
+
await showTimerStats(idOrSlug);
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2527
|
+
if (options.stop) {
|
|
2528
|
+
await stopActiveTimer();
|
|
2529
|
+
return;
|
|
2530
|
+
}
|
|
2531
|
+
if (!idOrSlug) {
|
|
2532
|
+
console.log(chalk20.yellow("Please provide a problem ID to start the timer."));
|
|
2533
|
+
console.log(chalk20.gray("Usage: leetcode timer <id>"));
|
|
2534
|
+
console.log(chalk20.gray(" leetcode timer --stats"));
|
|
2535
|
+
console.log(chalk20.gray(" leetcode timer --stop"));
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
const { authorized } = await requireAuth();
|
|
2539
|
+
if (!authorized) return;
|
|
2540
|
+
const activeTimer = timerStorage.getActiveTimer();
|
|
2541
|
+
if (activeTimer) {
|
|
2542
|
+
const startedAt = new Date(activeTimer.startedAt);
|
|
2543
|
+
const elapsed = Math.floor((Date.now() - startedAt.getTime()) / 1e3);
|
|
2544
|
+
console.log(chalk20.yellow("\u26A0\uFE0F You have an active timer running:"));
|
|
2545
|
+
console.log(chalk20.white(` Problem: ${activeTimer.title}`));
|
|
2546
|
+
console.log(chalk20.white(` Elapsed: ${formatDuration(elapsed)}`));
|
|
2547
|
+
console.log();
|
|
2548
|
+
console.log(chalk20.gray("Use `leetcode timer --stop` to stop it first."));
|
|
2549
|
+
return;
|
|
2550
|
+
}
|
|
2551
|
+
const spinner = ora14("Fetching problem...").start();
|
|
2552
|
+
try {
|
|
2553
|
+
let problem;
|
|
2554
|
+
if (/^\d+$/.test(idOrSlug)) {
|
|
2555
|
+
problem = await leetcodeClient.getProblemById(idOrSlug);
|
|
2556
|
+
} else {
|
|
2557
|
+
problem = await leetcodeClient.getProblem(idOrSlug);
|
|
2558
|
+
}
|
|
2559
|
+
if (!problem) {
|
|
2560
|
+
spinner.fail(`Problem "${idOrSlug}" not found`);
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
2563
|
+
spinner.stop();
|
|
2564
|
+
const durationMinutes = options.minutes ?? DEFAULT_TIMES[problem.difficulty] ?? 30;
|
|
2565
|
+
timerStorage.startTimer(
|
|
2566
|
+
problem.questionFrontendId,
|
|
2567
|
+
problem.title,
|
|
2568
|
+
problem.difficulty,
|
|
2569
|
+
durationMinutes
|
|
2570
|
+
);
|
|
2571
|
+
console.log();
|
|
2572
|
+
console.log(chalk20.bold.cyan("\u23F1\uFE0F Interview Mode Started!"));
|
|
2573
|
+
console.log(chalk20.gray("\u2500".repeat(50)));
|
|
2574
|
+
console.log();
|
|
2575
|
+
console.log(chalk20.white(`Problem: ${problem.questionFrontendId}. ${problem.title}`));
|
|
2576
|
+
console.log(chalk20.white(`Difficulty: ${chalk20.bold(problem.difficulty)}`));
|
|
2577
|
+
console.log(chalk20.white(`Time Limit: ${chalk20.bold.yellow(durationMinutes + " minutes")}`));
|
|
2578
|
+
console.log();
|
|
2579
|
+
console.log(chalk20.gray("\u2500".repeat(50)));
|
|
2580
|
+
console.log(chalk20.green("\u2713 Timer is running in background"));
|
|
2581
|
+
console.log(chalk20.gray(" When you submit successfully, your time will be recorded."));
|
|
2582
|
+
console.log(chalk20.gray(" Use `leetcode timer --stop` to cancel."));
|
|
2583
|
+
console.log();
|
|
2584
|
+
await pickCommand(idOrSlug, { open: true });
|
|
2585
|
+
} catch (error) {
|
|
2586
|
+
spinner.fail("Failed to start timer");
|
|
2587
|
+
if (error instanceof Error) {
|
|
2588
|
+
console.log(chalk20.red(error.message));
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
async function stopActiveTimer() {
|
|
2593
|
+
const result = timerStorage.stopTimer();
|
|
2594
|
+
if (!result) {
|
|
2595
|
+
console.log(chalk20.yellow("No active timer to stop."));
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
console.log(chalk20.green("\u23F1\uFE0F Timer stopped."));
|
|
2599
|
+
console.log(chalk20.gray(`Elapsed time: ${formatDuration(result.durationSeconds)}`));
|
|
2600
|
+
console.log(chalk20.gray("(Time not recorded since problem was not submitted)"));
|
|
2601
|
+
}
|
|
2602
|
+
async function showTimerStats(problemId) {
|
|
2603
|
+
if (problemId && /^\d+$/.test(problemId)) {
|
|
2604
|
+
const times = timerStorage.getSolveTimes(problemId);
|
|
2605
|
+
if (times.length === 0) {
|
|
2606
|
+
console.log(chalk20.yellow(`No solve times recorded for problem ${problemId}`));
|
|
2607
|
+
return;
|
|
2608
|
+
}
|
|
2609
|
+
console.log();
|
|
2610
|
+
console.log(chalk20.bold(`\u23F1\uFE0F Solve Times for Problem ${problemId}`));
|
|
2611
|
+
console.log(chalk20.gray("\u2500".repeat(40)));
|
|
2612
|
+
for (const entry of times) {
|
|
2613
|
+
const date = new Date(entry.solvedAt).toLocaleDateString();
|
|
2614
|
+
const duration = formatDuration(entry.durationSeconds);
|
|
2615
|
+
const limit = entry.timerMinutes;
|
|
2616
|
+
const withinLimit = entry.durationSeconds <= limit * 60;
|
|
2617
|
+
console.log(
|
|
2618
|
+
` ${date} ${withinLimit ? chalk20.green(duration) : chalk20.red(duration)} (limit: ${limit}m)`
|
|
2619
|
+
);
|
|
2620
|
+
}
|
|
2621
|
+
} else {
|
|
2622
|
+
const stats = timerStorage.getStats();
|
|
2623
|
+
const allTimes = timerStorage.getAllSolveTimes();
|
|
2624
|
+
console.log();
|
|
2625
|
+
console.log(chalk20.bold("\u23F1\uFE0F Timer Statistics"));
|
|
2626
|
+
console.log(chalk20.gray("\u2500".repeat(40)));
|
|
2627
|
+
console.log();
|
|
2628
|
+
console.log(` Problems timed: ${chalk20.cyan(stats.totalProblems)}`);
|
|
2629
|
+
console.log(` Total time: ${chalk20.cyan(formatDuration(stats.totalTime))}`);
|
|
2630
|
+
console.log(` Average time: ${chalk20.cyan(formatDuration(stats.avgTime))}`);
|
|
2631
|
+
console.log();
|
|
2632
|
+
const recentSolves = [];
|
|
2633
|
+
for (const [id, times] of Object.entries(allTimes)) {
|
|
2634
|
+
for (const t of times) {
|
|
2635
|
+
recentSolves.push({
|
|
2636
|
+
problemId: id,
|
|
2637
|
+
title: t.title,
|
|
2638
|
+
duration: t.durationSeconds,
|
|
2639
|
+
date: t.solvedAt
|
|
2640
|
+
});
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
if (recentSolves.length > 0) {
|
|
2644
|
+
recentSolves.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
2645
|
+
console.log(chalk20.bold(" Recent Solves:"));
|
|
2646
|
+
for (const solve of recentSolves.slice(0, 5)) {
|
|
2647
|
+
const date = new Date(solve.date).toLocaleDateString();
|
|
2648
|
+
console.log(
|
|
2649
|
+
chalk20.gray(` ${date} `) + chalk20.white(`${solve.problemId}. ${solve.title.substring(0, 25)}`) + chalk20.gray(" ") + chalk20.cyan(formatDuration(solve.duration))
|
|
2650
|
+
);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
console.log();
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2379
2657
|
// src/index.ts
|
|
2380
2658
|
var program = new Command();
|
|
2381
2659
|
program.configureHelp({
|
|
2382
2660
|
sortSubcommands: true,
|
|
2383
|
-
subcommandTerm: (cmd) =>
|
|
2384
|
-
subcommandDescription: (cmd) =>
|
|
2385
|
-
optionTerm: (option) =>
|
|
2386
|
-
optionDescription: (option) =>
|
|
2661
|
+
subcommandTerm: (cmd) => chalk21.cyan(cmd.name()) + (cmd.alias() ? chalk21.gray(`|${cmd.alias()}`) : ""),
|
|
2662
|
+
subcommandDescription: (cmd) => chalk21.white(cmd.description()),
|
|
2663
|
+
optionTerm: (option) => chalk21.yellow(option.flags),
|
|
2664
|
+
optionDescription: (option) => chalk21.white(option.description)
|
|
2387
2665
|
});
|
|
2388
|
-
program.name("leetcode").usage("[command] [options]").description(
|
|
2389
|
-
${
|
|
2390
|
-
${
|
|
2391
|
-
${
|
|
2392
|
-
${
|
|
2393
|
-
${
|
|
2394
|
-
${
|
|
2395
|
-
${
|
|
2666
|
+
program.name("leetcode").usage("[command] [options]").description(chalk21.bold.cyan("\u{1F525} A modern LeetCode CLI built with TypeScript")).version("1.5.0", "-v, --version", "Output the version number").helpOption("-h, --help", "Display help for command").addHelpText("after", `
|
|
2667
|
+
${chalk21.yellow("Examples:")}
|
|
2668
|
+
${chalk21.cyan("$ leetcode login")} Login to LeetCode
|
|
2669
|
+
${chalk21.cyan("$ leetcode list -d easy")} List easy problems
|
|
2670
|
+
${chalk21.cyan("$ leetcode random -d medium")} Get random medium problem
|
|
2671
|
+
${chalk21.cyan("$ leetcode pick 1")} Start solving "Two Sum"
|
|
2672
|
+
${chalk21.cyan("$ leetcode test 1")} Test your solution
|
|
2673
|
+
${chalk21.cyan("$ leetcode submit 1")} Submit your solution
|
|
2396
2674
|
`);
|
|
2397
2675
|
program.command("login").description("Login to LeetCode with browser cookies").addHelpText("after", `
|
|
2398
|
-
${
|
|
2399
|
-
1. Open ${
|
|
2676
|
+
${chalk21.yellow("How to login:")}
|
|
2677
|
+
1. Open ${chalk21.cyan("https://leetcode.com")} in your browser
|
|
2400
2678
|
2. Login to your account
|
|
2401
2679
|
3. Open Developer Tools (F12) \u2192 Application \u2192 Cookies
|
|
2402
|
-
4. Copy values of ${
|
|
2680
|
+
4. Copy values of ${chalk21.green("LEETCODE_SESSION")} and ${chalk21.green("csrftoken")}
|
|
2403
2681
|
5. Paste when prompted by this command
|
|
2404
2682
|
`).action(loginCommand);
|
|
2405
2683
|
program.command("logout").description("Clear stored credentials").action(logoutCommand);
|
|
2406
2684
|
program.command("whoami").description("Check current login status").action(whoamiCommand);
|
|
2407
2685
|
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
|
-
${
|
|
2686
|
+
${chalk21.yellow("Examples:")}
|
|
2687
|
+
${chalk21.cyan("$ leetcode list")} List first 20 problems
|
|
2688
|
+
${chalk21.cyan("$ leetcode list -d easy")} List easy problems only
|
|
2689
|
+
${chalk21.cyan("$ leetcode list -s solved")} List your solved problems
|
|
2690
|
+
${chalk21.cyan("$ leetcode list -t array -t string")} Filter by multiple tags
|
|
2691
|
+
${chalk21.cyan('$ leetcode list -q "two sum"')} Search by keywords
|
|
2692
|
+
${chalk21.cyan("$ leetcode list -n 50 -p 2")} Show 50 problems, page 2
|
|
2415
2693
|
`).action(listCommand);
|
|
2416
2694
|
program.command("show <id>").alias("s").description("Show problem description").addHelpText("after", `
|
|
2417
|
-
${
|
|
2418
|
-
${
|
|
2419
|
-
${
|
|
2420
|
-
${
|
|
2695
|
+
${chalk21.yellow("Examples:")}
|
|
2696
|
+
${chalk21.cyan("$ leetcode show 1")} Show by problem ID
|
|
2697
|
+
${chalk21.cyan("$ leetcode show two-sum")} Show by problem slug
|
|
2698
|
+
${chalk21.cyan("$ leetcode s 412")} Short alias
|
|
2421
2699
|
`).action(showCommand);
|
|
2422
2700
|
program.command("daily").alias("d").description("Show today's daily challenge").addHelpText("after", `
|
|
2423
|
-
${
|
|
2424
|
-
${
|
|
2425
|
-
${
|
|
2701
|
+
${chalk21.yellow("Examples:")}
|
|
2702
|
+
${chalk21.cyan("$ leetcode daily")} Show today's challenge
|
|
2703
|
+
${chalk21.cyan("$ leetcode d")} Short alias
|
|
2426
2704
|
`).action(dailyCommand);
|
|
2427
2705
|
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
|
-
${
|
|
2706
|
+
${chalk21.yellow("Examples:")}
|
|
2707
|
+
${chalk21.cyan("$ leetcode random")} Get any random problem
|
|
2708
|
+
${chalk21.cyan("$ leetcode random -d medium")} Random medium problem
|
|
2709
|
+
${chalk21.cyan("$ leetcode random -t array")} Random array problem
|
|
2710
|
+
${chalk21.cyan("$ leetcode random --pick")} Random + create file
|
|
2711
|
+
${chalk21.cyan("$ leetcode r -d easy --pick")} Random easy + file
|
|
2434
2712
|
`).action(randomCommand);
|
|
2435
2713
|
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
|
-
${
|
|
2714
|
+
${chalk21.yellow("Examples:")}
|
|
2715
|
+
${chalk21.cyan("$ leetcode pick 1")} Pick by problem ID
|
|
2716
|
+
${chalk21.cyan("$ leetcode pick two-sum")} Pick by problem slug
|
|
2717
|
+
${chalk21.cyan("$ leetcode pick 1 -l python3")} Pick with specific language
|
|
2718
|
+
${chalk21.cyan("$ leetcode pick 1 --no-open")} Create file without opening
|
|
2719
|
+
${chalk21.cyan("$ leetcode p 412")} Short alias
|
|
2442
2720
|
|
|
2443
|
-
${
|
|
2721
|
+
${chalk21.gray("Files are organized by: workDir/Difficulty/Category/")}
|
|
2444
2722
|
`).action(async (id, options) => {
|
|
2445
2723
|
await pickCommand(id, options);
|
|
2446
2724
|
});
|
|
2447
2725
|
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
|
-
${
|
|
2726
|
+
${chalk21.yellow("Examples:")}
|
|
2727
|
+
${chalk21.cyan("$ leetcode pick-batch 1 2 3")} Pick problems 1, 2, and 3
|
|
2728
|
+
${chalk21.cyan("$ leetcode pick-batch 1 2 3 -l py")} Pick with Python
|
|
2451
2729
|
`).action(batchPickCommand);
|
|
2452
2730
|
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
|
-
${
|
|
2731
|
+
${chalk21.yellow("Examples:")}
|
|
2732
|
+
${chalk21.cyan("$ leetcode test 1")} Test by problem ID
|
|
2733
|
+
${chalk21.cyan("$ leetcode test two-sum")} Test by problem slug
|
|
2734
|
+
${chalk21.cyan("$ leetcode test ./path/to/file.py")} Test by file path
|
|
2735
|
+
${chalk21.cyan('$ leetcode test 1 -c "[1,2]\\n3"')} Test with custom case
|
|
2736
|
+
${chalk21.cyan("$ leetcode t 412")} Short alias
|
|
2459
2737
|
|
|
2460
|
-
${
|
|
2738
|
+
${chalk21.gray("Testcases use \\n to separate multiple inputs.")}
|
|
2461
2739
|
`).action(testCommand);
|
|
2462
2740
|
program.command("submit <file>").alias("x").description("Submit solution to LeetCode").addHelpText("after", `
|
|
2463
|
-
${
|
|
2464
|
-
${
|
|
2465
|
-
${
|
|
2466
|
-
${
|
|
2467
|
-
${
|
|
2741
|
+
${chalk21.yellow("Examples:")}
|
|
2742
|
+
${chalk21.cyan("$ leetcode submit 1")} Submit by problem ID
|
|
2743
|
+
${chalk21.cyan("$ leetcode submit two-sum")} Submit by problem slug
|
|
2744
|
+
${chalk21.cyan("$ leetcode submit ./path/to/file.py")} Submit by file path
|
|
2745
|
+
${chalk21.cyan("$ leetcode x 412")} Short alias
|
|
2468
2746
|
`).action(submitCommand);
|
|
2469
2747
|
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
|
-
${
|
|
2748
|
+
${chalk21.yellow("Examples:")}
|
|
2749
|
+
${chalk21.cyan("$ leetcode submissions 1")} View submissions for problem
|
|
2750
|
+
${chalk21.cyan("$ leetcode submissions 1 -n 5")} Show last 5 submissions
|
|
2751
|
+
${chalk21.cyan("$ leetcode submissions 1 --last")} Show last accepted submission
|
|
2752
|
+
${chalk21.cyan("$ leetcode submissions 1 --download")} Download last accepted code
|
|
2475
2753
|
`).action(submissionsCommand);
|
|
2476
2754
|
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
|
-
${
|
|
2755
|
+
${chalk21.yellow("Options Explained:")}
|
|
2756
|
+
${chalk21.cyan("-c, --calendar")} Shows a table of your weekly submissions and active days
|
|
2479
2757
|
for the past 12 weeks. Useful for tracking consistency.
|
|
2480
2758
|
|
|
2481
|
-
${
|
|
2759
|
+
${chalk21.cyan("-s, --skills")} Shows how many problems you solved per topic tag,
|
|
2482
2760
|
grouped by difficulty (Fundamental/Intermediate/Advanced).
|
|
2483
2761
|
Helps identify your strong and weak areas.
|
|
2484
2762
|
|
|
2485
|
-
${
|
|
2763
|
+
${chalk21.cyan("-t, --trend")} Shows a bar chart of daily submissions for the past week.
|
|
2486
2764
|
Visualizes your recent coding activity day by day.
|
|
2487
2765
|
|
|
2488
|
-
${
|
|
2489
|
-
${
|
|
2490
|
-
${
|
|
2491
|
-
${
|
|
2492
|
-
${
|
|
2493
|
-
${
|
|
2766
|
+
${chalk21.yellow("Examples:")}
|
|
2767
|
+
${chalk21.cyan("$ leetcode stat")} Show basic stats (solved count, rank)
|
|
2768
|
+
${chalk21.cyan("$ leetcode stat lee215")} Show another user's stats
|
|
2769
|
+
${chalk21.cyan("$ leetcode stat -c")} Weekly activity table
|
|
2770
|
+
${chalk21.cyan("$ leetcode stat -s")} Topic-wise breakdown
|
|
2771
|
+
${chalk21.cyan("$ leetcode stat -t")} 7-day trend chart
|
|
2494
2772
|
`).action((username, options) => statCommand(username, options));
|
|
2495
2773
|
program.command("today").description("Show today's progress summary").addHelpText("after", `
|
|
2496
|
-
${
|
|
2497
|
-
${
|
|
2774
|
+
${chalk21.yellow("Examples:")}
|
|
2775
|
+
${chalk21.cyan("$ leetcode today")} Show streak, solved, and daily challenge
|
|
2498
2776
|
`).action(todayCommand);
|
|
2499
2777
|
program.command("bookmark <action> [id]").description("Manage problem bookmarks").addHelpText("after", `
|
|
2500
|
-
${
|
|
2501
|
-
${
|
|
2502
|
-
${
|
|
2503
|
-
${
|
|
2504
|
-
${
|
|
2778
|
+
${chalk21.yellow("Actions:")}
|
|
2779
|
+
${chalk21.cyan("add <id>")} Bookmark a problem
|
|
2780
|
+
${chalk21.cyan("remove <id>")} Remove a bookmark
|
|
2781
|
+
${chalk21.cyan("list")} List all bookmarks
|
|
2782
|
+
${chalk21.cyan("clear")} Clear all bookmarks
|
|
2505
2783
|
|
|
2506
|
-
${
|
|
2507
|
-
${
|
|
2508
|
-
${
|
|
2509
|
-
${
|
|
2784
|
+
${chalk21.yellow("Examples:")}
|
|
2785
|
+
${chalk21.cyan("$ leetcode bookmark add 1")} Bookmark problem 1
|
|
2786
|
+
${chalk21.cyan("$ leetcode bookmark remove 1")} Remove bookmark
|
|
2787
|
+
${chalk21.cyan("$ leetcode bookmark list")} List all bookmarks
|
|
2510
2788
|
`).action(bookmarkCommand);
|
|
2511
2789
|
program.command("note <id> [action]").description("View or edit notes for a problem").addHelpText("after", `
|
|
2512
|
-
${
|
|
2513
|
-
${
|
|
2514
|
-
${
|
|
2790
|
+
${chalk21.yellow("Actions:")}
|
|
2791
|
+
${chalk21.cyan("edit")} Open notes in editor (default)
|
|
2792
|
+
${chalk21.cyan("view")} Display notes in terminal
|
|
2515
2793
|
|
|
2516
|
-
${
|
|
2517
|
-
${
|
|
2518
|
-
${
|
|
2519
|
-
${
|
|
2794
|
+
${chalk21.yellow("Examples:")}
|
|
2795
|
+
${chalk21.cyan("$ leetcode note 1")} Edit notes for problem 1
|
|
2796
|
+
${chalk21.cyan("$ leetcode note 1 edit")} Edit notes (explicit)
|
|
2797
|
+
${chalk21.cyan("$ leetcode note 1 view")} View notes in terminal
|
|
2520
2798
|
`).action(notesCommand);
|
|
2521
2799
|
program.command("sync").description("Sync solutions to Git repository").addHelpText("after", `
|
|
2522
|
-
${
|
|
2523
|
-
${
|
|
2800
|
+
${chalk21.yellow("Examples:")}
|
|
2801
|
+
${chalk21.cyan("$ leetcode sync")} Sync all solutions to remote
|
|
2524
2802
|
`).action(syncCommand);
|
|
2525
2803
|
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
|
-
${
|
|
2804
|
+
${chalk21.yellow("Examples:")}
|
|
2805
|
+
${chalk21.cyan("$ leetcode config")} View current config
|
|
2806
|
+
${chalk21.cyan("$ leetcode config -l python3")} Set language to Python
|
|
2807
|
+
${chalk21.cyan('$ leetcode config -e "code"')} Set editor to VS Code
|
|
2808
|
+
${chalk21.cyan("$ leetcode config -w ~/leetcode")} Set solutions folder
|
|
2809
|
+
${chalk21.cyan("$ leetcode config -r https://...")} Set git repository
|
|
2810
|
+
${chalk21.cyan("$ leetcode config -i")} Interactive setup
|
|
2533
2811
|
|
|
2534
|
-
${
|
|
2812
|
+
${chalk21.gray("Supported languages: typescript, javascript, python3, java, cpp, c, csharp, go, rust, kotlin, swift")}
|
|
2535
2813
|
`).action(async (options) => {
|
|
2536
2814
|
if (options.interactive) {
|
|
2537
2815
|
await configInteractiveCommand();
|
|
@@ -2539,12 +2817,24 @@ ${chalk20.gray("Supported languages: typescript, javascript, python3, java, cpp,
|
|
|
2539
2817
|
await configCommand(options);
|
|
2540
2818
|
}
|
|
2541
2819
|
});
|
|
2820
|
+
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
|
+
${chalk21.yellow("How it works:")}
|
|
2822
|
+
Start a problem with a countdown timer to simulate interview conditions.
|
|
2823
|
+
Default time limits: Easy (20 min), Medium (40 min), Hard (60 min).
|
|
2824
|
+
Your solve times are recorded when you submit successfully.
|
|
2825
|
+
|
|
2826
|
+
${chalk21.yellow("Examples:")}
|
|
2827
|
+
${chalk21.cyan("$ leetcode timer 1")} Start problem 1 with default time
|
|
2828
|
+
${chalk21.cyan("$ leetcode timer 1 -m 30")} Start with 30 minute limit
|
|
2829
|
+
${chalk21.cyan("$ leetcode timer --stats")} Show your solve time statistics
|
|
2830
|
+
${chalk21.cyan("$ leetcode timer --stop")} Stop active timer
|
|
2831
|
+
`).action((id, options) => timerCommand(id, options));
|
|
2542
2832
|
program.showHelpAfterError("(add --help for additional information)");
|
|
2543
2833
|
program.parse();
|
|
2544
2834
|
if (!process.argv.slice(2).length) {
|
|
2545
2835
|
console.log();
|
|
2546
|
-
console.log(
|
|
2547
|
-
console.log(
|
|
2836
|
+
console.log(chalk21.bold.cyan(" \u{1F525} LeetCode CLI"));
|
|
2837
|
+
console.log(chalk21.gray(" A modern command-line interface for LeetCode"));
|
|
2548
2838
|
console.log();
|
|
2549
2839
|
program.outputHelp();
|
|
2550
2840
|
}
|