@night-slayer18/leetcode-cli 1.3.2 → 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 +45 -1
- package/dist/index.js +874 -204
- package/package.json +1 -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 chalk21 from "chalk";
|
|
6
6
|
|
|
7
7
|
// src/commands/login.ts
|
|
8
8
|
import inquirer from "inquirer";
|
|
@@ -111,7 +111,8 @@ var UserProfileSchema = z.object({
|
|
|
111
111
|
}),
|
|
112
112
|
userCalendar: z.object({
|
|
113
113
|
streak: z.number(),
|
|
114
|
-
totalActiveDays: z.number()
|
|
114
|
+
totalActiveDays: z.number(),
|
|
115
|
+
submissionCalendar: z.string().optional()
|
|
115
116
|
})
|
|
116
117
|
});
|
|
117
118
|
var UserStatusSchema = z.object({
|
|
@@ -202,6 +203,30 @@ var USER_PROFILE_QUERY = `
|
|
|
202
203
|
userCalendar {
|
|
203
204
|
streak
|
|
204
205
|
totalActiveDays
|
|
206
|
+
submissionCalendar
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
`;
|
|
211
|
+
var SKILL_STATS_QUERY = `
|
|
212
|
+
query skillStats($username: String!) {
|
|
213
|
+
matchedUser(username: $username) {
|
|
214
|
+
tagProblemCounts {
|
|
215
|
+
fundamental {
|
|
216
|
+
tagName
|
|
217
|
+
tagSlug
|
|
218
|
+
problemsSolved
|
|
219
|
+
}
|
|
220
|
+
intermediate {
|
|
221
|
+
tagName
|
|
222
|
+
tagSlug
|
|
223
|
+
problemsSolved
|
|
224
|
+
}
|
|
225
|
+
advanced {
|
|
226
|
+
tagName
|
|
227
|
+
tagSlug
|
|
228
|
+
problemsSolved
|
|
229
|
+
}
|
|
205
230
|
}
|
|
206
231
|
}
|
|
207
232
|
}
|
|
@@ -381,9 +406,14 @@ var LeetCodeClient = class {
|
|
|
381
406
|
ranking: validated.profile.ranking,
|
|
382
407
|
acSubmissionNum: validated.submitStatsGlobal.acSubmissionNum,
|
|
383
408
|
streak: validated.userCalendar.streak,
|
|
384
|
-
totalActiveDays: validated.userCalendar.totalActiveDays
|
|
409
|
+
totalActiveDays: validated.userCalendar.totalActiveDays,
|
|
410
|
+
submissionCalendar: user.userCalendar.submissionCalendar
|
|
385
411
|
};
|
|
386
412
|
}
|
|
413
|
+
async getSkillStats(username) {
|
|
414
|
+
const data = await this.graphql(SKILL_STATS_QUERY, { username });
|
|
415
|
+
return data.matchedUser.tagProblemCounts;
|
|
416
|
+
}
|
|
387
417
|
async getSubmissionList(slug, limit = 20, offset = 0) {
|
|
388
418
|
const data = await this.graphql(SUBMISSION_LIST_QUERY, { questionSlug: slug, limit, offset });
|
|
389
419
|
const validated = z2.array(SubmissionSchema).parse(data.questionSubmissionList.submissions);
|
|
@@ -449,7 +479,8 @@ var schema = {
|
|
|
449
479
|
properties: {
|
|
450
480
|
language: { type: "string", default: "typescript" },
|
|
451
481
|
editor: { type: "string" },
|
|
452
|
-
workDir: { type: "string", default: join(homedir(), "leetcode") }
|
|
482
|
+
workDir: { type: "string", default: join(homedir(), "leetcode") },
|
|
483
|
+
repo: { type: "string" }
|
|
453
484
|
},
|
|
454
485
|
default: {
|
|
455
486
|
language: "typescript",
|
|
@@ -486,6 +517,12 @@ var config = {
|
|
|
486
517
|
setWorkDir(workDir) {
|
|
487
518
|
configStore.set("config.workDir", workDir);
|
|
488
519
|
},
|
|
520
|
+
setRepo(repo) {
|
|
521
|
+
configStore.set("config.repo", repo);
|
|
522
|
+
},
|
|
523
|
+
deleteRepo() {
|
|
524
|
+
configStore.delete("config.repo");
|
|
525
|
+
},
|
|
489
526
|
// Get specific config values
|
|
490
527
|
getLanguage() {
|
|
491
528
|
return configStore.get("config.language");
|
|
@@ -496,6 +533,9 @@ var config = {
|
|
|
496
533
|
getWorkDir() {
|
|
497
534
|
return configStore.get("config.workDir");
|
|
498
535
|
},
|
|
536
|
+
getRepo() {
|
|
537
|
+
return configStore.get("config.repo");
|
|
538
|
+
},
|
|
499
539
|
// Clear all config
|
|
500
540
|
clear() {
|
|
501
541
|
configStore.clear();
|
|
@@ -1238,12 +1278,16 @@ async function findSolutionFile(dir, problemId, currentDepth = 0) {
|
|
|
1238
1278
|
if (currentDepth >= MAX_SEARCH_DEPTH) return null;
|
|
1239
1279
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
1240
1280
|
for (const entry of entries) {
|
|
1281
|
+
if (entry.name.startsWith(".")) continue;
|
|
1241
1282
|
const fullPath = join3(dir, entry.name);
|
|
1242
1283
|
if (entry.isDirectory()) {
|
|
1243
1284
|
const found = await findSolutionFile(fullPath, problemId, currentDepth + 1);
|
|
1244
1285
|
if (found) return found;
|
|
1245
1286
|
} else if (entry.name.startsWith(`${problemId}.`)) {
|
|
1246
|
-
|
|
1287
|
+
const ext = entry.name.split(".").pop()?.toLowerCase();
|
|
1288
|
+
if (ext && ext in EXT_TO_LANG_MAP) {
|
|
1289
|
+
return fullPath;
|
|
1290
|
+
}
|
|
1247
1291
|
}
|
|
1248
1292
|
}
|
|
1249
1293
|
return null;
|
|
@@ -1263,6 +1307,19 @@ async function findFileByName(dir, fileName, currentDepth = 0) {
|
|
|
1263
1307
|
}
|
|
1264
1308
|
return null;
|
|
1265
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
|
+
};
|
|
1266
1323
|
function getLangSlugFromExtension(ext) {
|
|
1267
1324
|
const langMap = {
|
|
1268
1325
|
ts: "typescript",
|
|
@@ -1358,6 +1415,82 @@ import { existsSync as existsSync4 } from "fs";
|
|
|
1358
1415
|
import { basename as basename2 } from "path";
|
|
1359
1416
|
import ora6 from "ora";
|
|
1360
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
|
|
1361
1494
|
async function submitCommand(fileOrId) {
|
|
1362
1495
|
const { authorized } = await requireAuth();
|
|
1363
1496
|
if (!authorized) return;
|
|
@@ -1417,6 +1550,35 @@ async function submitCommand(fileOrId) {
|
|
|
1417
1550
|
);
|
|
1418
1551
|
spinner.stop();
|
|
1419
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
|
+
}
|
|
1420
1582
|
} catch (error) {
|
|
1421
1583
|
spinner.fail("Submission failed");
|
|
1422
1584
|
if (error instanceof Error) {
|
|
@@ -1427,8 +1589,121 @@ async function submitCommand(fileOrId) {
|
|
|
1427
1589
|
|
|
1428
1590
|
// src/commands/stat.ts
|
|
1429
1591
|
import ora7 from "ora";
|
|
1592
|
+
import chalk11 from "chalk";
|
|
1593
|
+
|
|
1594
|
+
// src/utils/stats-display.ts
|
|
1430
1595
|
import chalk10 from "chalk";
|
|
1431
|
-
|
|
1596
|
+
var MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
1597
|
+
function renderHeatmap(calendarJson) {
|
|
1598
|
+
const data = JSON.parse(calendarJson);
|
|
1599
|
+
const now = /* @__PURE__ */ new Date();
|
|
1600
|
+
const weeks = [];
|
|
1601
|
+
for (let w = 11; w >= 0; w--) {
|
|
1602
|
+
const weekStart = new Date(now);
|
|
1603
|
+
weekStart.setDate(weekStart.getDate() - w * 7 - weekStart.getDay());
|
|
1604
|
+
let weekCount = 0;
|
|
1605
|
+
let activeDays = 0;
|
|
1606
|
+
for (let d = 0; d < 7; d++) {
|
|
1607
|
+
const day = new Date(weekStart);
|
|
1608
|
+
day.setDate(day.getDate() + d);
|
|
1609
|
+
if (day > now) break;
|
|
1610
|
+
const midnight = new Date(Date.UTC(day.getFullYear(), day.getMonth(), day.getDate()));
|
|
1611
|
+
const timestamp = Math.floor(midnight.getTime() / 1e3).toString();
|
|
1612
|
+
const count = data[timestamp] || 0;
|
|
1613
|
+
weekCount += count;
|
|
1614
|
+
if (count > 0) activeDays++;
|
|
1615
|
+
}
|
|
1616
|
+
const weekEnd = new Date(weekStart);
|
|
1617
|
+
weekEnd.setDate(weekEnd.getDate() + 6);
|
|
1618
|
+
weeks.push({
|
|
1619
|
+
start: `${MONTH_NAMES[weekStart.getMonth()]} ${weekStart.getDate()}`,
|
|
1620
|
+
end: `${MONTH_NAMES[weekEnd.getMonth()]} ${weekEnd.getDate()}`,
|
|
1621
|
+
count: weekCount,
|
|
1622
|
+
days: activeDays
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
const totalSubmissions = weeks.reduce((sum, w) => sum + w.count, 0);
|
|
1626
|
+
const totalActiveDays = weeks.reduce((sum, w) => sum + w.days, 0);
|
|
1627
|
+
console.log();
|
|
1628
|
+
console.log(chalk10.bold("\u{1F4C5} Activity (Last 12 Weeks)"));
|
|
1629
|
+
console.log(chalk10.gray("How many problems you submitted and days you practiced."));
|
|
1630
|
+
console.log(chalk10.gray("\u2500".repeat(50)));
|
|
1631
|
+
console.log();
|
|
1632
|
+
for (const week of weeks) {
|
|
1633
|
+
const weekLabel = `${week.start} - ${week.end}`.padEnd(18);
|
|
1634
|
+
const bar = week.count > 0 ? chalk10.green("\u2588".repeat(Math.min(week.count, 10))).padEnd(10) : chalk10.gray("\xB7").padEnd(10);
|
|
1635
|
+
const countStr = week.count > 0 ? `${week.count} subs`.padEnd(10) : "".padEnd(10);
|
|
1636
|
+
const daysStr = week.days > 0 ? `${week.days}d active` : "";
|
|
1637
|
+
console.log(` ${chalk10.white(weekLabel)} ${bar} ${chalk10.cyan(countStr)} ${chalk10.yellow(daysStr)}`);
|
|
1638
|
+
}
|
|
1639
|
+
console.log(chalk10.gray("\u2500".repeat(50)));
|
|
1640
|
+
console.log(` ${chalk10.bold.white("Total:")} ${chalk10.cyan.bold(totalSubmissions + " submissions")}, ${chalk10.yellow.bold(totalActiveDays + " days active")}`);
|
|
1641
|
+
console.log();
|
|
1642
|
+
}
|
|
1643
|
+
function renderSkillStats(fundamental, intermediate, advanced) {
|
|
1644
|
+
console.log();
|
|
1645
|
+
console.log(chalk10.bold("\u{1F3AF} Skill Breakdown"));
|
|
1646
|
+
console.log(chalk10.gray("\u2500".repeat(45)));
|
|
1647
|
+
const renderSection = (title, stats, color) => {
|
|
1648
|
+
if (stats.length === 0) return;
|
|
1649
|
+
console.log();
|
|
1650
|
+
console.log(color.bold(` ${title}`));
|
|
1651
|
+
const sorted = [...stats].sort((a, b) => b.problemsSolved - a.problemsSolved);
|
|
1652
|
+
for (const stat of sorted.slice(0, 8)) {
|
|
1653
|
+
const name = stat.tagName.padEnd(22);
|
|
1654
|
+
const bar = color("\u2588".repeat(Math.min(stat.problemsSolved, 15)));
|
|
1655
|
+
console.log(` ${chalk10.white(name)} ${bar} ${chalk10.white(stat.problemsSolved)}`);
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
renderSection("Fundamental", fundamental, chalk10.green);
|
|
1659
|
+
renderSection("Intermediate", intermediate, chalk10.yellow);
|
|
1660
|
+
renderSection("Advanced", advanced, chalk10.red);
|
|
1661
|
+
console.log();
|
|
1662
|
+
}
|
|
1663
|
+
function renderTrendChart(calendarJson) {
|
|
1664
|
+
const data = JSON.parse(calendarJson);
|
|
1665
|
+
const now = /* @__PURE__ */ new Date();
|
|
1666
|
+
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
1667
|
+
const days = [];
|
|
1668
|
+
for (let d = 6; d >= 0; d--) {
|
|
1669
|
+
const day = new Date(now);
|
|
1670
|
+
day.setDate(day.getDate() - d);
|
|
1671
|
+
const midnight = new Date(Date.UTC(day.getFullYear(), day.getMonth(), day.getDate()));
|
|
1672
|
+
const timestamp = Math.floor(midnight.getTime() / 1e3).toString();
|
|
1673
|
+
days.push({
|
|
1674
|
+
label: dayNames[day.getDay()],
|
|
1675
|
+
count: data[timestamp] || 0
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
const maxVal = Math.max(...days.map((d) => d.count), 1);
|
|
1679
|
+
const chartHeight = 6;
|
|
1680
|
+
console.log();
|
|
1681
|
+
console.log(chalk10.bold("\u{1F4C8} Submission Trend (Last 7 Days)"));
|
|
1682
|
+
console.log(chalk10.gray("\u2500".repeat(35)));
|
|
1683
|
+
console.log();
|
|
1684
|
+
for (let row = chartHeight; row >= 1; row--) {
|
|
1685
|
+
let line = ` ${row === chartHeight ? maxVal.toString().padStart(2) : " "} \u2502`;
|
|
1686
|
+
for (const day of days) {
|
|
1687
|
+
const barHeight = Math.round(day.count / maxVal * chartHeight);
|
|
1688
|
+
if (barHeight >= row) {
|
|
1689
|
+
line += chalk10.green(" \u2588\u2588 ");
|
|
1690
|
+
} else {
|
|
1691
|
+
line += " ";
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
console.log(line);
|
|
1695
|
+
}
|
|
1696
|
+
console.log(` 0 \u2514${"\u2500\u2500\u2500\u2500".repeat(7)}`);
|
|
1697
|
+
console.log(` ${days.map((d) => d.label.padEnd(4)).join("")}`);
|
|
1698
|
+
console.log(chalk10.gray(` ${days.map((d) => d.count.toString().padEnd(4)).join("")}`));
|
|
1699
|
+
const total = days.reduce((a, b) => a + b.count, 0);
|
|
1700
|
+
console.log();
|
|
1701
|
+
console.log(chalk10.white(` Total: ${total} submissions this week`));
|
|
1702
|
+
console.log();
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
// src/commands/stat.ts
|
|
1706
|
+
async function statCommand(username, options = {}) {
|
|
1432
1707
|
const { authorized, username: currentUser } = await requireAuth();
|
|
1433
1708
|
if (!authorized) return;
|
|
1434
1709
|
const spinner = ora7("Fetching statistics...").start();
|
|
@@ -1440,25 +1715,48 @@ async function statCommand(username) {
|
|
|
1440
1715
|
}
|
|
1441
1716
|
const profile = await leetcodeClient.getUserProfile(targetUsername);
|
|
1442
1717
|
spinner.stop();
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1718
|
+
if (!options.calendar && !options.skills && !options.trend) {
|
|
1719
|
+
displayUserStats(
|
|
1720
|
+
profile.username,
|
|
1721
|
+
profile.realName,
|
|
1722
|
+
profile.ranking,
|
|
1723
|
+
profile.acSubmissionNum,
|
|
1724
|
+
profile.streak,
|
|
1725
|
+
profile.totalActiveDays
|
|
1726
|
+
);
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
if (options.calendar) {
|
|
1730
|
+
if (profile.submissionCalendar) {
|
|
1731
|
+
renderHeatmap(profile.submissionCalendar);
|
|
1732
|
+
} else {
|
|
1733
|
+
console.log(chalk11.yellow("Calendar data not available."));
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
if (options.trend) {
|
|
1737
|
+
if (profile.submissionCalendar) {
|
|
1738
|
+
renderTrendChart(profile.submissionCalendar);
|
|
1739
|
+
} else {
|
|
1740
|
+
console.log(chalk11.yellow("Calendar data not available."));
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
if (options.skills) {
|
|
1744
|
+
spinner.start("Fetching skill stats...");
|
|
1745
|
+
const skills = await leetcodeClient.getSkillStats(targetUsername);
|
|
1746
|
+
spinner.stop();
|
|
1747
|
+
renderSkillStats(skills.fundamental, skills.intermediate, skills.advanced);
|
|
1748
|
+
}
|
|
1451
1749
|
} catch (error) {
|
|
1452
1750
|
spinner.fail("Failed to fetch statistics");
|
|
1453
1751
|
if (error instanceof Error) {
|
|
1454
|
-
console.log(
|
|
1752
|
+
console.log(chalk11.red(error.message));
|
|
1455
1753
|
}
|
|
1456
1754
|
}
|
|
1457
1755
|
}
|
|
1458
1756
|
|
|
1459
1757
|
// src/commands/daily.ts
|
|
1460
1758
|
import ora8 from "ora";
|
|
1461
|
-
import
|
|
1759
|
+
import chalk12 from "chalk";
|
|
1462
1760
|
async function dailyCommand() {
|
|
1463
1761
|
const { authorized } = await requireAuth();
|
|
1464
1762
|
if (!authorized) return;
|
|
@@ -1468,19 +1766,19 @@ async function dailyCommand() {
|
|
|
1468
1766
|
spinner.stop();
|
|
1469
1767
|
displayDailyChallenge(daily.date, daily.question);
|
|
1470
1768
|
console.log();
|
|
1471
|
-
console.log(
|
|
1472
|
-
console.log(
|
|
1769
|
+
console.log(chalk12.gray("Run the following to start working on this problem:"));
|
|
1770
|
+
console.log(chalk12.cyan(` leetcode pick ${daily.question.titleSlug}`));
|
|
1473
1771
|
} catch (error) {
|
|
1474
1772
|
spinner.fail("Failed to fetch daily challenge");
|
|
1475
1773
|
if (error instanceof Error) {
|
|
1476
|
-
console.log(
|
|
1774
|
+
console.log(chalk12.red(error.message));
|
|
1477
1775
|
}
|
|
1478
1776
|
}
|
|
1479
1777
|
}
|
|
1480
1778
|
|
|
1481
1779
|
// src/commands/random.ts
|
|
1482
1780
|
import ora9 from "ora";
|
|
1483
|
-
import
|
|
1781
|
+
import chalk13 from "chalk";
|
|
1484
1782
|
async function randomCommand(options) {
|
|
1485
1783
|
const { authorized } = await requireAuth();
|
|
1486
1784
|
if (!authorized) return;
|
|
@@ -1514,13 +1812,13 @@ async function randomCommand(options) {
|
|
|
1514
1812
|
await pickCommand(titleSlug, { open: options.open ?? true });
|
|
1515
1813
|
} else {
|
|
1516
1814
|
await showCommand(titleSlug);
|
|
1517
|
-
console.log(
|
|
1518
|
-
console.log(
|
|
1815
|
+
console.log(chalk13.gray("Run following to start solving:"));
|
|
1816
|
+
console.log(chalk13.cyan(` leetcode pick ${titleSlug}`));
|
|
1519
1817
|
}
|
|
1520
1818
|
} catch (error) {
|
|
1521
1819
|
spinner.fail("Failed to fetch random problem");
|
|
1522
1820
|
if (error instanceof Error) {
|
|
1523
|
-
console.log(
|
|
1821
|
+
console.log(chalk13.red(error.message));
|
|
1524
1822
|
}
|
|
1525
1823
|
}
|
|
1526
1824
|
}
|
|
@@ -1528,9 +1826,9 @@ async function randomCommand(options) {
|
|
|
1528
1826
|
// src/commands/submissions.ts
|
|
1529
1827
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1530
1828
|
import { existsSync as existsSync5 } from "fs";
|
|
1531
|
-
import { join as
|
|
1829
|
+
import { join as join5 } from "path";
|
|
1532
1830
|
import ora10 from "ora";
|
|
1533
|
-
import
|
|
1831
|
+
import chalk14 from "chalk";
|
|
1534
1832
|
async function submissionsCommand(idOrSlug, options) {
|
|
1535
1833
|
const { authorized } = await requireAuth();
|
|
1536
1834
|
if (!authorized) return;
|
|
@@ -1552,16 +1850,16 @@ async function submissionsCommand(idOrSlug, options) {
|
|
|
1552
1850
|
const submissions = await leetcodeClient.getSubmissionList(slug, limit);
|
|
1553
1851
|
spinner.stop();
|
|
1554
1852
|
if (submissions.length === 0) {
|
|
1555
|
-
console.log(
|
|
1853
|
+
console.log(chalk14.yellow("No submissions found."));
|
|
1556
1854
|
return;
|
|
1557
1855
|
}
|
|
1558
1856
|
if (options.last) {
|
|
1559
1857
|
const lastAC = submissions.find((s) => s.statusDisplay === "Accepted");
|
|
1560
1858
|
if (lastAC) {
|
|
1561
|
-
console.log(
|
|
1859
|
+
console.log(chalk14.bold("Last Accepted Submission:"));
|
|
1562
1860
|
displaySubmissionsList([lastAC]);
|
|
1563
1861
|
} else {
|
|
1564
|
-
console.log(
|
|
1862
|
+
console.log(chalk14.yellow("No accepted submissions found in recent history."));
|
|
1565
1863
|
}
|
|
1566
1864
|
} else {
|
|
1567
1865
|
displaySubmissionsList(submissions);
|
|
@@ -1582,7 +1880,7 @@ async function submissionsCommand(idOrSlug, options) {
|
|
|
1582
1880
|
const workDir = config.getWorkDir();
|
|
1583
1881
|
const difficulty = problem.difficulty;
|
|
1584
1882
|
const category = problem.topicTags.length > 0 ? problem.topicTags[0].name.replace(/[^\w\s-]/g, "").trim() : "Uncategorized";
|
|
1585
|
-
const targetDir =
|
|
1883
|
+
const targetDir = join5(workDir, difficulty, category);
|
|
1586
1884
|
if (!existsSync5(targetDir)) {
|
|
1587
1885
|
await mkdir2(targetDir, { recursive: true });
|
|
1588
1886
|
}
|
|
@@ -1590,22 +1888,22 @@ async function submissionsCommand(idOrSlug, options) {
|
|
|
1590
1888
|
const supportedLang = LANG_SLUG_MAP[langSlug] ?? "txt";
|
|
1591
1889
|
const ext = LANGUAGE_EXTENSIONS[supportedLang] ?? langSlug;
|
|
1592
1890
|
const fileName = `${problem.questionFrontendId}.${problem.titleSlug}.submission-${lastAC.id}.${ext}`;
|
|
1593
|
-
const filePath =
|
|
1891
|
+
const filePath = join5(targetDir, fileName);
|
|
1594
1892
|
await writeFile2(filePath, details.code, "utf-8");
|
|
1595
|
-
downloadSpinner.succeed(`Downloaded to ${
|
|
1596
|
-
console.log(
|
|
1893
|
+
downloadSpinner.succeed(`Downloaded to ${chalk14.green(fileName)}`);
|
|
1894
|
+
console.log(chalk14.gray(`Path: ${filePath}`));
|
|
1597
1895
|
}
|
|
1598
1896
|
} catch (error) {
|
|
1599
1897
|
spinner.fail("Failed to fetch submissions");
|
|
1600
1898
|
if (error instanceof Error) {
|
|
1601
|
-
console.log(
|
|
1899
|
+
console.log(chalk14.red(error.message));
|
|
1602
1900
|
}
|
|
1603
1901
|
}
|
|
1604
1902
|
}
|
|
1605
1903
|
|
|
1606
1904
|
// src/commands/config.ts
|
|
1607
1905
|
import inquirer2 from "inquirer";
|
|
1608
|
-
import
|
|
1906
|
+
import chalk15 from "chalk";
|
|
1609
1907
|
var SUPPORTED_LANGUAGES = [
|
|
1610
1908
|
"typescript",
|
|
1611
1909
|
"javascript",
|
|
@@ -1627,21 +1925,30 @@ async function configCommand(options) {
|
|
|
1627
1925
|
if (options.lang) {
|
|
1628
1926
|
const langInput = options.lang.toLowerCase();
|
|
1629
1927
|
if (!SUPPORTED_LANGUAGES.includes(langInput)) {
|
|
1630
|
-
console.log(
|
|
1631
|
-
console.log(
|
|
1928
|
+
console.log(chalk15.red(`Unsupported language: ${options.lang}`));
|
|
1929
|
+
console.log(chalk15.gray(`Supported: ${SUPPORTED_LANGUAGES.join(", ")}`));
|
|
1632
1930
|
return;
|
|
1633
1931
|
}
|
|
1634
1932
|
const lang = langInput;
|
|
1635
1933
|
config.setLanguage(lang);
|
|
1636
|
-
console.log(
|
|
1934
|
+
console.log(chalk15.green(`\u2713 Default language set to ${lang}`));
|
|
1637
1935
|
}
|
|
1638
1936
|
if (options.editor) {
|
|
1639
1937
|
config.setEditor(options.editor);
|
|
1640
|
-
console.log(
|
|
1938
|
+
console.log(chalk15.green(`\u2713 Editor set to ${options.editor}`));
|
|
1641
1939
|
}
|
|
1642
1940
|
if (options.workdir) {
|
|
1643
1941
|
config.setWorkDir(options.workdir);
|
|
1644
|
-
console.log(
|
|
1942
|
+
console.log(chalk15.green(`\u2713 Work directory set to ${options.workdir}`));
|
|
1943
|
+
}
|
|
1944
|
+
if (options.repo !== void 0) {
|
|
1945
|
+
if (options.repo.trim() === "") {
|
|
1946
|
+
config.deleteRepo();
|
|
1947
|
+
console.log(chalk15.green("\u2713 Repository URL cleared"));
|
|
1948
|
+
} else {
|
|
1949
|
+
config.setRepo(options.repo);
|
|
1950
|
+
console.log(chalk15.green(`\u2713 Repository URL set to ${options.repo}`));
|
|
1951
|
+
}
|
|
1645
1952
|
}
|
|
1646
1953
|
}
|
|
1647
1954
|
async function configInteractiveCommand() {
|
|
@@ -1665,38 +1972,50 @@ async function configInteractiveCommand() {
|
|
|
1665
1972
|
name: "workDir",
|
|
1666
1973
|
message: "Working directory for solution files:",
|
|
1667
1974
|
default: currentConfig.workDir
|
|
1975
|
+
},
|
|
1976
|
+
{
|
|
1977
|
+
type: "input",
|
|
1978
|
+
name: "repo",
|
|
1979
|
+
message: "Git repository URL (optional):",
|
|
1980
|
+
default: currentConfig.repo
|
|
1668
1981
|
}
|
|
1669
1982
|
]);
|
|
1670
1983
|
config.setLanguage(answers.language);
|
|
1671
1984
|
config.setEditor(answers.editor);
|
|
1672
1985
|
config.setWorkDir(answers.workDir);
|
|
1986
|
+
if (answers.repo) {
|
|
1987
|
+
config.setRepo(answers.repo);
|
|
1988
|
+
} else {
|
|
1989
|
+
config.deleteRepo();
|
|
1990
|
+
}
|
|
1673
1991
|
console.log();
|
|
1674
|
-
console.log(
|
|
1992
|
+
console.log(chalk15.green("\u2713 Configuration saved"));
|
|
1675
1993
|
showCurrentConfig();
|
|
1676
1994
|
}
|
|
1677
1995
|
function showCurrentConfig() {
|
|
1678
1996
|
const currentConfig = config.getConfig();
|
|
1679
1997
|
const credentials = config.getCredentials();
|
|
1680
1998
|
console.log();
|
|
1681
|
-
console.log(
|
|
1682
|
-
console.log(
|
|
1999
|
+
console.log(chalk15.bold("LeetCode CLI Configuration"));
|
|
2000
|
+
console.log(chalk15.gray("\u2500".repeat(40)));
|
|
1683
2001
|
console.log();
|
|
1684
|
-
console.log(
|
|
2002
|
+
console.log(chalk15.gray("Config file:"), config.getPath());
|
|
1685
2003
|
console.log();
|
|
1686
|
-
console.log(
|
|
1687
|
-
console.log(
|
|
1688
|
-
console.log(
|
|
1689
|
-
console.log(
|
|
2004
|
+
console.log(chalk15.gray("Language: "), chalk15.white(currentConfig.language));
|
|
2005
|
+
console.log(chalk15.gray("Editor: "), chalk15.white(currentConfig.editor ?? "(not set)"));
|
|
2006
|
+
console.log(chalk15.gray("Work Dir: "), chalk15.white(currentConfig.workDir));
|
|
2007
|
+
console.log(chalk15.gray("Repo URL: "), chalk15.white(currentConfig.repo ?? "(not set)"));
|
|
2008
|
+
console.log(chalk15.gray("Logged in: "), credentials ? chalk15.green("Yes") : chalk15.yellow("No"));
|
|
1690
2009
|
}
|
|
1691
2010
|
|
|
1692
2011
|
// src/commands/bookmark.ts
|
|
1693
|
-
import
|
|
2012
|
+
import chalk16 from "chalk";
|
|
1694
2013
|
import Table2 from "cli-table3";
|
|
1695
2014
|
import ora11 from "ora";
|
|
1696
2015
|
|
|
1697
2016
|
// src/storage/bookmarks.ts
|
|
1698
|
-
import
|
|
1699
|
-
var bookmarksStore = new
|
|
2017
|
+
import Conf3 from "conf";
|
|
2018
|
+
var bookmarksStore = new Conf3({
|
|
1700
2019
|
projectName: "leetcode-cli-bookmarks",
|
|
1701
2020
|
defaults: { bookmarks: [] }
|
|
1702
2021
|
});
|
|
@@ -1735,36 +2054,36 @@ var bookmarks = {
|
|
|
1735
2054
|
async function bookmarkCommand(action, id) {
|
|
1736
2055
|
const validActions = ["add", "remove", "list", "clear"];
|
|
1737
2056
|
if (!validActions.includes(action)) {
|
|
1738
|
-
console.log(
|
|
1739
|
-
console.log(
|
|
2057
|
+
console.log(chalk16.red(`Invalid action: ${action}`));
|
|
2058
|
+
console.log(chalk16.gray("Valid actions: add, remove, list, clear"));
|
|
1740
2059
|
return;
|
|
1741
2060
|
}
|
|
1742
2061
|
switch (action) {
|
|
1743
2062
|
case "add":
|
|
1744
2063
|
if (!id) {
|
|
1745
|
-
console.log(
|
|
2064
|
+
console.log(chalk16.red("Please provide a problem ID to bookmark"));
|
|
1746
2065
|
return;
|
|
1747
2066
|
}
|
|
1748
2067
|
if (!isProblemId(id)) {
|
|
1749
|
-
console.log(
|
|
1750
|
-
console.log(
|
|
2068
|
+
console.log(chalk16.red(`Invalid problem ID: ${id}`));
|
|
2069
|
+
console.log(chalk16.gray("Problem ID must be a positive integer"));
|
|
1751
2070
|
return;
|
|
1752
2071
|
}
|
|
1753
2072
|
if (bookmarks.add(id)) {
|
|
1754
|
-
console.log(
|
|
2073
|
+
console.log(chalk16.green(`\u2713 Bookmarked problem ${id}`));
|
|
1755
2074
|
} else {
|
|
1756
|
-
console.log(
|
|
2075
|
+
console.log(chalk16.yellow(`Problem ${id} is already bookmarked`));
|
|
1757
2076
|
}
|
|
1758
2077
|
break;
|
|
1759
2078
|
case "remove":
|
|
1760
2079
|
if (!id) {
|
|
1761
|
-
console.log(
|
|
2080
|
+
console.log(chalk16.red("Please provide a problem ID to remove"));
|
|
1762
2081
|
return;
|
|
1763
2082
|
}
|
|
1764
2083
|
if (bookmarks.remove(id)) {
|
|
1765
|
-
console.log(
|
|
2084
|
+
console.log(chalk16.green(`\u2713 Removed bookmark for problem ${id}`));
|
|
1766
2085
|
} else {
|
|
1767
|
-
console.log(
|
|
2086
|
+
console.log(chalk16.yellow(`Problem ${id} is not bookmarked`));
|
|
1768
2087
|
}
|
|
1769
2088
|
break;
|
|
1770
2089
|
case "list":
|
|
@@ -1773,10 +2092,10 @@ async function bookmarkCommand(action, id) {
|
|
|
1773
2092
|
case "clear":
|
|
1774
2093
|
const count = bookmarks.count();
|
|
1775
2094
|
if (count === 0) {
|
|
1776
|
-
console.log(
|
|
2095
|
+
console.log(chalk16.yellow("No bookmarks to clear"));
|
|
1777
2096
|
} else {
|
|
1778
2097
|
bookmarks.clear();
|
|
1779
|
-
console.log(
|
|
2098
|
+
console.log(chalk16.green(`\u2713 Cleared ${count} bookmark${count !== 1 ? "s" : ""}`));
|
|
1780
2099
|
}
|
|
1781
2100
|
break;
|
|
1782
2101
|
}
|
|
@@ -1784,12 +2103,12 @@ async function bookmarkCommand(action, id) {
|
|
|
1784
2103
|
async function listBookmarks() {
|
|
1785
2104
|
const bookmarkList = bookmarks.list();
|
|
1786
2105
|
if (bookmarkList.length === 0) {
|
|
1787
|
-
console.log(
|
|
1788
|
-
console.log(
|
|
2106
|
+
console.log(chalk16.yellow("\u{1F4CC} No bookmarked problems"));
|
|
2107
|
+
console.log(chalk16.gray('Use "leetcode bookmark add <id>" to bookmark a problem'));
|
|
1789
2108
|
return;
|
|
1790
2109
|
}
|
|
1791
2110
|
console.log();
|
|
1792
|
-
console.log(
|
|
2111
|
+
console.log(chalk16.bold.cyan(`\u{1F4CC} Bookmarked Problems (${bookmarkList.length})`));
|
|
1793
2112
|
console.log();
|
|
1794
2113
|
const { authorized } = await requireAuth();
|
|
1795
2114
|
if (authorized) {
|
|
@@ -1797,10 +2116,10 @@ async function listBookmarks() {
|
|
|
1797
2116
|
try {
|
|
1798
2117
|
const table = new Table2({
|
|
1799
2118
|
head: [
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
2119
|
+
chalk16.cyan("ID"),
|
|
2120
|
+
chalk16.cyan("Title"),
|
|
2121
|
+
chalk16.cyan("Difficulty"),
|
|
2122
|
+
chalk16.cyan("Status")
|
|
1804
2123
|
],
|
|
1805
2124
|
colWidths: [8, 45, 12, 10],
|
|
1806
2125
|
style: { head: [], border: [] }
|
|
@@ -1813,35 +2132,35 @@ async function listBookmarks() {
|
|
|
1813
2132
|
problem.questionFrontendId,
|
|
1814
2133
|
problem.title.length > 42 ? problem.title.slice(0, 39) + "..." : problem.title,
|
|
1815
2134
|
colorDifficulty2(problem.difficulty),
|
|
1816
|
-
problem.status === "ac" ?
|
|
2135
|
+
problem.status === "ac" ? chalk16.green("\u2713") : chalk16.gray("-")
|
|
1817
2136
|
]);
|
|
1818
2137
|
} else {
|
|
1819
|
-
table.push([id,
|
|
2138
|
+
table.push([id, chalk16.gray("(not found)"), "-", "-"]);
|
|
1820
2139
|
}
|
|
1821
2140
|
} catch {
|
|
1822
|
-
table.push([id,
|
|
2141
|
+
table.push([id, chalk16.gray("(error fetching)"), "-", "-"]);
|
|
1823
2142
|
}
|
|
1824
2143
|
}
|
|
1825
2144
|
spinner.stop();
|
|
1826
2145
|
console.log(table.toString());
|
|
1827
2146
|
} catch {
|
|
1828
2147
|
spinner.stop();
|
|
1829
|
-
console.log(
|
|
2148
|
+
console.log(chalk16.gray("IDs: ") + bookmarkList.join(", "));
|
|
1830
2149
|
}
|
|
1831
2150
|
} else {
|
|
1832
|
-
console.log(
|
|
2151
|
+
console.log(chalk16.gray("IDs: ") + bookmarkList.join(", "));
|
|
1833
2152
|
console.log();
|
|
1834
|
-
console.log(
|
|
2153
|
+
console.log(chalk16.gray("Login to see problem details"));
|
|
1835
2154
|
}
|
|
1836
2155
|
}
|
|
1837
2156
|
function colorDifficulty2(difficulty) {
|
|
1838
2157
|
switch (difficulty.toLowerCase()) {
|
|
1839
2158
|
case "easy":
|
|
1840
|
-
return
|
|
2159
|
+
return chalk16.green(difficulty);
|
|
1841
2160
|
case "medium":
|
|
1842
|
-
return
|
|
2161
|
+
return chalk16.yellow(difficulty);
|
|
1843
2162
|
case "hard":
|
|
1844
|
-
return
|
|
2163
|
+
return chalk16.red(difficulty);
|
|
1845
2164
|
default:
|
|
1846
2165
|
return difficulty;
|
|
1847
2166
|
}
|
|
@@ -1849,18 +2168,18 @@ function colorDifficulty2(difficulty) {
|
|
|
1849
2168
|
|
|
1850
2169
|
// src/commands/notes.ts
|
|
1851
2170
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
1852
|
-
import { join as
|
|
2171
|
+
import { join as join6 } from "path";
|
|
1853
2172
|
import { existsSync as existsSync6 } from "fs";
|
|
1854
|
-
import
|
|
2173
|
+
import chalk17 from "chalk";
|
|
1855
2174
|
async function notesCommand(problemId, action) {
|
|
1856
2175
|
if (!isProblemId(problemId)) {
|
|
1857
|
-
console.log(
|
|
1858
|
-
console.log(
|
|
2176
|
+
console.log(chalk17.red(`Invalid problem ID: ${problemId}`));
|
|
2177
|
+
console.log(chalk17.gray("Problem ID must be a positive integer"));
|
|
1859
2178
|
return;
|
|
1860
2179
|
}
|
|
1861
2180
|
const noteAction = action === "view" ? "view" : "edit";
|
|
1862
|
-
const notesDir =
|
|
1863
|
-
const notePath =
|
|
2181
|
+
const notesDir = join6(config.getWorkDir(), ".notes");
|
|
2182
|
+
const notePath = join6(notesDir, `${problemId}.md`);
|
|
1864
2183
|
if (!existsSync6(notesDir)) {
|
|
1865
2184
|
await mkdir3(notesDir, { recursive: true });
|
|
1866
2185
|
}
|
|
@@ -1872,21 +2191,21 @@ async function notesCommand(problemId, action) {
|
|
|
1872
2191
|
}
|
|
1873
2192
|
async function viewNote(notePath, problemId) {
|
|
1874
2193
|
if (!existsSync6(notePath)) {
|
|
1875
|
-
console.log(
|
|
1876
|
-
console.log(
|
|
2194
|
+
console.log(chalk17.yellow(`No notes found for problem ${problemId}`));
|
|
2195
|
+
console.log(chalk17.gray(`Use "leetcode note ${problemId} edit" to create notes`));
|
|
1877
2196
|
return;
|
|
1878
2197
|
}
|
|
1879
2198
|
try {
|
|
1880
2199
|
const content = await readFile3(notePath, "utf-8");
|
|
1881
2200
|
console.log();
|
|
1882
|
-
console.log(
|
|
1883
|
-
console.log(
|
|
2201
|
+
console.log(chalk17.bold.cyan(`\u{1F4DD} Notes for Problem ${problemId}`));
|
|
2202
|
+
console.log(chalk17.gray("\u2500".repeat(50)));
|
|
1884
2203
|
console.log();
|
|
1885
2204
|
console.log(content);
|
|
1886
2205
|
} catch (error) {
|
|
1887
|
-
console.log(
|
|
2206
|
+
console.log(chalk17.red("Failed to read notes"));
|
|
1888
2207
|
if (error instanceof Error) {
|
|
1889
|
-
console.log(
|
|
2208
|
+
console.log(chalk17.gray(error.message));
|
|
1890
2209
|
}
|
|
1891
2210
|
}
|
|
1892
2211
|
}
|
|
@@ -1894,9 +2213,9 @@ async function editNote(notePath, problemId) {
|
|
|
1894
2213
|
if (!existsSync6(notePath)) {
|
|
1895
2214
|
const template = await generateNoteTemplate(problemId);
|
|
1896
2215
|
await writeFile3(notePath, template, "utf-8");
|
|
1897
|
-
console.log(
|
|
2216
|
+
console.log(chalk17.green(`\u2713 Created notes file for problem ${problemId}`));
|
|
1898
2217
|
}
|
|
1899
|
-
console.log(
|
|
2218
|
+
console.log(chalk17.gray(`Opening: ${notePath}`));
|
|
1900
2219
|
await openInEditor(notePath);
|
|
1901
2220
|
}
|
|
1902
2221
|
async function generateNoteTemplate(problemId) {
|
|
@@ -1953,7 +2272,7 @@ async function generateNoteTemplate(problemId) {
|
|
|
1953
2272
|
}
|
|
1954
2273
|
|
|
1955
2274
|
// src/commands/today.ts
|
|
1956
|
-
import
|
|
2275
|
+
import chalk18 from "chalk";
|
|
1957
2276
|
import ora12 from "ora";
|
|
1958
2277
|
async function todayCommand() {
|
|
1959
2278
|
const { authorized, username } = await requireAuth();
|
|
@@ -1968,190 +2287,529 @@ async function todayCommand() {
|
|
|
1968
2287
|
]);
|
|
1969
2288
|
spinner.stop();
|
|
1970
2289
|
console.log();
|
|
1971
|
-
console.log(
|
|
1972
|
-
console.log(
|
|
2290
|
+
console.log(chalk18.bold.cyan(`\u{1F4CA} Today's Summary`), chalk18.gray(`- ${username}`));
|
|
2291
|
+
console.log(chalk18.gray("\u2500".repeat(50)));
|
|
1973
2292
|
console.log();
|
|
1974
|
-
console.log(
|
|
1975
|
-
console.log(
|
|
2293
|
+
console.log(chalk18.yellow(`\u{1F525} Current Streak: ${profile.streak} day${profile.streak !== 1 ? "s" : ""}`));
|
|
2294
|
+
console.log(chalk18.gray(` Total Active Days: ${profile.totalActiveDays}`));
|
|
1976
2295
|
console.log();
|
|
1977
2296
|
const total = profile.acSubmissionNum.find((s) => s.difficulty === "All");
|
|
1978
2297
|
const easy = profile.acSubmissionNum.find((s) => s.difficulty === "Easy");
|
|
1979
2298
|
const medium = profile.acSubmissionNum.find((s) => s.difficulty === "Medium");
|
|
1980
2299
|
const hard = profile.acSubmissionNum.find((s) => s.difficulty === "Hard");
|
|
1981
|
-
console.log(
|
|
1982
|
-
console.log(` ${
|
|
1983
|
-
console.log(` ${
|
|
2300
|
+
console.log(chalk18.white("\u{1F4C8} Problems Solved:"));
|
|
2301
|
+
console.log(` ${chalk18.green("Easy")}: ${easy?.count ?? 0} | ${chalk18.yellow("Medium")}: ${medium?.count ?? 0} | ${chalk18.red("Hard")}: ${hard?.count ?? 0}`);
|
|
2302
|
+
console.log(` ${chalk18.bold("Total")}: ${total?.count ?? 0}`);
|
|
1984
2303
|
console.log();
|
|
1985
|
-
console.log(
|
|
2304
|
+
console.log(chalk18.bold.yellow("\u{1F3AF} Today's Challenge:"));
|
|
1986
2305
|
console.log(` ${daily.question.questionFrontendId}. ${daily.question.title}`);
|
|
1987
2306
|
console.log(` ${colorDifficulty3(daily.question.difficulty)}`);
|
|
1988
2307
|
const status = daily.question.status;
|
|
1989
2308
|
if (status === "ac") {
|
|
1990
|
-
console.log(
|
|
2309
|
+
console.log(chalk18.green(" \u2713 Completed!"));
|
|
1991
2310
|
} else if (status === "notac") {
|
|
1992
|
-
console.log(
|
|
2311
|
+
console.log(chalk18.yellow(" \u25CB Attempted"));
|
|
1993
2312
|
} else {
|
|
1994
|
-
console.log(
|
|
2313
|
+
console.log(chalk18.gray(" - Not started"));
|
|
1995
2314
|
}
|
|
1996
2315
|
console.log();
|
|
1997
|
-
console.log(
|
|
2316
|
+
console.log(chalk18.gray(` leetcode pick ${daily.question.questionFrontendId} # Start working on it`));
|
|
1998
2317
|
} catch (error) {
|
|
1999
2318
|
spinner.fail("Failed to fetch progress");
|
|
2000
2319
|
if (error instanceof Error) {
|
|
2001
|
-
console.log(
|
|
2320
|
+
console.log(chalk18.red(error.message));
|
|
2002
2321
|
}
|
|
2003
2322
|
}
|
|
2004
2323
|
}
|
|
2005
2324
|
function colorDifficulty3(difficulty) {
|
|
2006
2325
|
switch (difficulty.toLowerCase()) {
|
|
2007
2326
|
case "easy":
|
|
2008
|
-
return
|
|
2327
|
+
return chalk18.green(difficulty);
|
|
2009
2328
|
case "medium":
|
|
2010
|
-
return
|
|
2329
|
+
return chalk18.yellow(difficulty);
|
|
2011
2330
|
case "hard":
|
|
2012
|
-
return
|
|
2331
|
+
return chalk18.red(difficulty);
|
|
2013
2332
|
default:
|
|
2014
2333
|
return difficulty;
|
|
2015
2334
|
}
|
|
2016
2335
|
}
|
|
2017
2336
|
|
|
2337
|
+
// src/commands/sync.ts
|
|
2338
|
+
import { execSync } from "child_process";
|
|
2339
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2340
|
+
import chalk19 from "chalk";
|
|
2341
|
+
import inquirer3 from "inquirer";
|
|
2342
|
+
import ora13 from "ora";
|
|
2343
|
+
function isGitInstalled() {
|
|
2344
|
+
try {
|
|
2345
|
+
execSync("git --version", { stdio: "ignore" });
|
|
2346
|
+
return true;
|
|
2347
|
+
} catch (error) {
|
|
2348
|
+
return false;
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
function isMapRepo(workDir) {
|
|
2352
|
+
try {
|
|
2353
|
+
execSync("git rev-parse --is-inside-work-tree", { cwd: workDir, stdio: "ignore" });
|
|
2354
|
+
return true;
|
|
2355
|
+
} catch (error) {
|
|
2356
|
+
return false;
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
function isGhInstalled() {
|
|
2360
|
+
try {
|
|
2361
|
+
execSync("gh --version", { stdio: "ignore" });
|
|
2362
|
+
return true;
|
|
2363
|
+
} catch (error) {
|
|
2364
|
+
return false;
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
function getRemoteUrl(workDir) {
|
|
2368
|
+
try {
|
|
2369
|
+
const url = execSync("git config --get remote.origin.url", { cwd: workDir, encoding: "utf-8" });
|
|
2370
|
+
return url.trim();
|
|
2371
|
+
} catch (error) {
|
|
2372
|
+
return null;
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
async function setupGitRepo(workDir) {
|
|
2376
|
+
const { init } = await inquirer3.prompt([
|
|
2377
|
+
{
|
|
2378
|
+
type: "confirm",
|
|
2379
|
+
name: "init",
|
|
2380
|
+
message: "Work directory is not a git repository. Initialize?",
|
|
2381
|
+
default: true
|
|
2382
|
+
}
|
|
2383
|
+
]);
|
|
2384
|
+
if (!init) {
|
|
2385
|
+
console.log(chalk19.yellow("Skipping basic git initialization."));
|
|
2386
|
+
return false;
|
|
2387
|
+
}
|
|
2388
|
+
const spinner = ora13("Initializing git repository...").start();
|
|
2389
|
+
try {
|
|
2390
|
+
execSync("git init", { cwd: workDir });
|
|
2391
|
+
spinner.succeed("Initialized git repository");
|
|
2392
|
+
return true;
|
|
2393
|
+
} catch (error) {
|
|
2394
|
+
spinner.fail("Failed to initialize git repository");
|
|
2395
|
+
throw error;
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
async function setupRemote(workDir) {
|
|
2399
|
+
const spinner = ora13();
|
|
2400
|
+
let repoUrl = config.getRepo();
|
|
2401
|
+
if (!repoUrl) {
|
|
2402
|
+
if (isGhInstalled()) {
|
|
2403
|
+
const { createGh } = await inquirer3.prompt([
|
|
2404
|
+
{
|
|
2405
|
+
type: "confirm",
|
|
2406
|
+
name: "createGh",
|
|
2407
|
+
message: "Create a new private GitHub repository?",
|
|
2408
|
+
default: true
|
|
2409
|
+
}
|
|
2410
|
+
]);
|
|
2411
|
+
if (createGh) {
|
|
2412
|
+
spinner.start("Creating GitHub repository...");
|
|
2413
|
+
try {
|
|
2414
|
+
const repoName = workDir.split("/").pop() || "leetcode-solutions";
|
|
2415
|
+
execSync(`gh repo create ${repoName} --private --source=. --remote=origin`, { cwd: workDir });
|
|
2416
|
+
spinner.succeed("Created and linked GitHub repository");
|
|
2417
|
+
repoUrl = getRemoteUrl(workDir) || "";
|
|
2418
|
+
if (repoUrl) {
|
|
2419
|
+
config.setRepo(repoUrl);
|
|
2420
|
+
}
|
|
2421
|
+
return repoUrl;
|
|
2422
|
+
} catch (error) {
|
|
2423
|
+
spinner.fail("Failed to create GitHub repository");
|
|
2424
|
+
console.log(chalk19.red(error));
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
if (!repoUrl) {
|
|
2429
|
+
console.log(chalk19.yellow("\nPlease create a new repository on your Git provider and copy the URL."));
|
|
2430
|
+
const { url } = await inquirer3.prompt([
|
|
2431
|
+
{
|
|
2432
|
+
type: "input",
|
|
2433
|
+
name: "url",
|
|
2434
|
+
message: "Enter remote repository URL:",
|
|
2435
|
+
validate: (input) => input.length > 0 ? true : "URL cannot be empty"
|
|
2436
|
+
}
|
|
2437
|
+
]);
|
|
2438
|
+
repoUrl = url;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
if (repoUrl) {
|
|
2442
|
+
config.setRepo(repoUrl);
|
|
2443
|
+
}
|
|
2444
|
+
const currentRemote = getRemoteUrl(workDir);
|
|
2445
|
+
if (!currentRemote && repoUrl) {
|
|
2446
|
+
try {
|
|
2447
|
+
execSync(`git remote add origin ${repoUrl}`, { cwd: workDir });
|
|
2448
|
+
console.log(chalk19.green("\u2713 Added remote origin"));
|
|
2449
|
+
} catch (e) {
|
|
2450
|
+
console.log(chalk19.red("Failed to add remote origin"));
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
return repoUrl || "";
|
|
2454
|
+
}
|
|
2455
|
+
async function syncCommand() {
|
|
2456
|
+
const workDir = config.getWorkDir();
|
|
2457
|
+
if (!existsSync7(workDir)) {
|
|
2458
|
+
console.log(chalk19.red(`Work directory does not exist: ${workDir}`));
|
|
2459
|
+
return;
|
|
2460
|
+
}
|
|
2461
|
+
if (!isGitInstalled()) {
|
|
2462
|
+
console.log(chalk19.red("Git is not installed. Please install Git to use command."));
|
|
2463
|
+
return;
|
|
2464
|
+
}
|
|
2465
|
+
if (!isMapRepo(workDir)) {
|
|
2466
|
+
const initialized = await setupGitRepo(workDir);
|
|
2467
|
+
if (!initialized) return;
|
|
2468
|
+
}
|
|
2469
|
+
await setupRemote(workDir);
|
|
2470
|
+
const spinner = ora13("Syncing solutions...").start();
|
|
2471
|
+
try {
|
|
2472
|
+
const status = execSync("git status --porcelain", { cwd: workDir, encoding: "utf-8" });
|
|
2473
|
+
if (!status) {
|
|
2474
|
+
spinner.info("No changes to sync");
|
|
2475
|
+
return;
|
|
2476
|
+
}
|
|
2477
|
+
execSync("git add .", { cwd: workDir });
|
|
2478
|
+
const lines = status.trim().split("\n");
|
|
2479
|
+
const count = lines.length;
|
|
2480
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
|
|
2481
|
+
const message = `Sync: ${count} solutions - ${timestamp}`;
|
|
2482
|
+
execSync(`git commit -m "${message}"`, { cwd: workDir });
|
|
2483
|
+
try {
|
|
2484
|
+
execSync("git push -u origin main", { cwd: workDir, stdio: "ignore" });
|
|
2485
|
+
} catch {
|
|
2486
|
+
try {
|
|
2487
|
+
execSync("git push -u origin master", { cwd: workDir, stdio: "ignore" });
|
|
2488
|
+
} catch (e) {
|
|
2489
|
+
throw new Error("Failed to push to remote. Please check your git credentials and branch status.");
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
spinner.succeed("Successfully synced solutions to remote");
|
|
2493
|
+
} catch (error) {
|
|
2494
|
+
spinner.fail("Sync failed");
|
|
2495
|
+
if (error.message) {
|
|
2496
|
+
console.log(chalk19.red(error.message));
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
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
|
+
|
|
2018
2657
|
// src/index.ts
|
|
2019
2658
|
var program = new Command();
|
|
2020
2659
|
program.configureHelp({
|
|
2021
2660
|
sortSubcommands: true,
|
|
2022
|
-
subcommandTerm: (cmd) =>
|
|
2023
|
-
subcommandDescription: (cmd) =>
|
|
2024
|
-
optionTerm: (option) =>
|
|
2025
|
-
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)
|
|
2026
2665
|
});
|
|
2027
|
-
program.name("leetcode").usage("[command] [options]").description(
|
|
2028
|
-
${
|
|
2029
|
-
${
|
|
2030
|
-
${
|
|
2031
|
-
${
|
|
2032
|
-
${
|
|
2033
|
-
${
|
|
2034
|
-
${
|
|
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
|
|
2035
2674
|
`);
|
|
2036
2675
|
program.command("login").description("Login to LeetCode with browser cookies").addHelpText("after", `
|
|
2037
|
-
${
|
|
2038
|
-
1. Open ${
|
|
2676
|
+
${chalk21.yellow("How to login:")}
|
|
2677
|
+
1. Open ${chalk21.cyan("https://leetcode.com")} in your browser
|
|
2039
2678
|
2. Login to your account
|
|
2040
2679
|
3. Open Developer Tools (F12) \u2192 Application \u2192 Cookies
|
|
2041
|
-
4. Copy values of ${
|
|
2680
|
+
4. Copy values of ${chalk21.green("LEETCODE_SESSION")} and ${chalk21.green("csrftoken")}
|
|
2042
2681
|
5. Paste when prompted by this command
|
|
2043
2682
|
`).action(loginCommand);
|
|
2044
2683
|
program.command("logout").description("Clear stored credentials").action(logoutCommand);
|
|
2045
2684
|
program.command("whoami").description("Check current login status").action(whoamiCommand);
|
|
2046
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", `
|
|
2047
|
-
${
|
|
2048
|
-
${
|
|
2049
|
-
${
|
|
2050
|
-
${
|
|
2051
|
-
${
|
|
2052
|
-
${
|
|
2053
|
-
${
|
|
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
|
|
2054
2693
|
`).action(listCommand);
|
|
2055
2694
|
program.command("show <id>").alias("s").description("Show problem description").addHelpText("after", `
|
|
2056
|
-
${
|
|
2057
|
-
${
|
|
2058
|
-
${
|
|
2059
|
-
${
|
|
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
|
|
2060
2699
|
`).action(showCommand);
|
|
2061
2700
|
program.command("daily").alias("d").description("Show today's daily challenge").addHelpText("after", `
|
|
2062
|
-
${
|
|
2063
|
-
${
|
|
2064
|
-
${
|
|
2701
|
+
${chalk21.yellow("Examples:")}
|
|
2702
|
+
${chalk21.cyan("$ leetcode daily")} Show today's challenge
|
|
2703
|
+
${chalk21.cyan("$ leetcode d")} Short alias
|
|
2065
2704
|
`).action(dailyCommand);
|
|
2066
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", `
|
|
2067
|
-
${
|
|
2068
|
-
${
|
|
2069
|
-
${
|
|
2070
|
-
${
|
|
2071
|
-
${
|
|
2072
|
-
${
|
|
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
|
|
2073
2712
|
`).action(randomCommand);
|
|
2074
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", `
|
|
2075
|
-
${
|
|
2076
|
-
${
|
|
2077
|
-
${
|
|
2078
|
-
${
|
|
2079
|
-
${
|
|
2080
|
-
${
|
|
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
|
|
2081
2720
|
|
|
2082
|
-
${
|
|
2721
|
+
${chalk21.gray("Files are organized by: workDir/Difficulty/Category/")}
|
|
2083
2722
|
`).action(async (id, options) => {
|
|
2084
2723
|
await pickCommand(id, options);
|
|
2085
2724
|
});
|
|
2086
2725
|
program.command("pick-batch <ids...>").description("Generate solution files for multiple problems").option("-l, --lang <language>", "Programming language for the solutions").addHelpText("after", `
|
|
2087
|
-
${
|
|
2088
|
-
${
|
|
2089
|
-
${
|
|
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
|
|
2090
2729
|
`).action(batchPickCommand);
|
|
2091
2730
|
program.command("test <file>").alias("t").description("Test solution against sample test cases").option("-c, --testcase <testcase>", "Custom test case").addHelpText("after", `
|
|
2092
|
-
${
|
|
2093
|
-
${
|
|
2094
|
-
${
|
|
2095
|
-
${
|
|
2096
|
-
${
|
|
2097
|
-
${
|
|
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
|
|
2098
2737
|
|
|
2099
|
-
${
|
|
2738
|
+
${chalk21.gray("Testcases use \\n to separate multiple inputs.")}
|
|
2100
2739
|
`).action(testCommand);
|
|
2101
2740
|
program.command("submit <file>").alias("x").description("Submit solution to LeetCode").addHelpText("after", `
|
|
2102
|
-
${
|
|
2103
|
-
${
|
|
2104
|
-
${
|
|
2105
|
-
${
|
|
2106
|
-
${
|
|
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
|
|
2107
2746
|
`).action(submitCommand);
|
|
2108
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", `
|
|
2109
|
-
${
|
|
2110
|
-
${
|
|
2111
|
-
${
|
|
2112
|
-
${
|
|
2113
|
-
${
|
|
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
|
|
2114
2753
|
`).action(submissionsCommand);
|
|
2115
|
-
program.command("stat [username]").description("Show user statistics").addHelpText("after", `
|
|
2116
|
-
${
|
|
2117
|
-
${
|
|
2118
|
-
|
|
2119
|
-
|
|
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", `
|
|
2755
|
+
${chalk21.yellow("Options Explained:")}
|
|
2756
|
+
${chalk21.cyan("-c, --calendar")} Shows a table of your weekly submissions and active days
|
|
2757
|
+
for the past 12 weeks. Useful for tracking consistency.
|
|
2758
|
+
|
|
2759
|
+
${chalk21.cyan("-s, --skills")} Shows how many problems you solved per topic tag,
|
|
2760
|
+
grouped by difficulty (Fundamental/Intermediate/Advanced).
|
|
2761
|
+
Helps identify your strong and weak areas.
|
|
2762
|
+
|
|
2763
|
+
${chalk21.cyan("-t, --trend")} Shows a bar chart of daily submissions for the past week.
|
|
2764
|
+
Visualizes your recent coding activity day by day.
|
|
2765
|
+
|
|
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
|
|
2772
|
+
`).action((username, options) => statCommand(username, options));
|
|
2120
2773
|
program.command("today").description("Show today's progress summary").addHelpText("after", `
|
|
2121
|
-
${
|
|
2122
|
-
${
|
|
2774
|
+
${chalk21.yellow("Examples:")}
|
|
2775
|
+
${chalk21.cyan("$ leetcode today")} Show streak, solved, and daily challenge
|
|
2123
2776
|
`).action(todayCommand);
|
|
2124
2777
|
program.command("bookmark <action> [id]").description("Manage problem bookmarks").addHelpText("after", `
|
|
2125
|
-
${
|
|
2126
|
-
${
|
|
2127
|
-
${
|
|
2128
|
-
${
|
|
2129
|
-
${
|
|
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
|
|
2130
2783
|
|
|
2131
|
-
${
|
|
2132
|
-
${
|
|
2133
|
-
${
|
|
2134
|
-
${
|
|
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
|
|
2135
2788
|
`).action(bookmarkCommand);
|
|
2136
2789
|
program.command("note <id> [action]").description("View or edit notes for a problem").addHelpText("after", `
|
|
2137
|
-
${
|
|
2138
|
-
${
|
|
2139
|
-
${
|
|
2790
|
+
${chalk21.yellow("Actions:")}
|
|
2791
|
+
${chalk21.cyan("edit")} Open notes in editor (default)
|
|
2792
|
+
${chalk21.cyan("view")} Display notes in terminal
|
|
2140
2793
|
|
|
2141
|
-
${
|
|
2142
|
-
${
|
|
2143
|
-
${
|
|
2144
|
-
${
|
|
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
|
|
2145
2798
|
`).action(notesCommand);
|
|
2146
|
-
program.command("
|
|
2147
|
-
${
|
|
2148
|
-
${
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
${
|
|
2799
|
+
program.command("sync").description("Sync solutions to Git repository").addHelpText("after", `
|
|
2800
|
+
${chalk21.yellow("Examples:")}
|
|
2801
|
+
${chalk21.cyan("$ leetcode sync")} Sync all solutions to remote
|
|
2802
|
+
`).action(syncCommand);
|
|
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", `
|
|
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
|
|
2153
2811
|
|
|
2154
|
-
${
|
|
2812
|
+
${chalk21.gray("Supported languages: typescript, javascript, python3, java, cpp, c, csharp, go, rust, kotlin, swift")}
|
|
2155
2813
|
`).action(async (options) => {
|
|
2156
2814
|
if (options.interactive) {
|
|
2157
2815
|
await configInteractiveCommand();
|
|
@@ -2159,12 +2817,24 @@ ${chalk18.gray("Supported languages: typescript, javascript, python3, java, cpp,
|
|
|
2159
2817
|
await configCommand(options);
|
|
2160
2818
|
}
|
|
2161
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));
|
|
2162
2832
|
program.showHelpAfterError("(add --help for additional information)");
|
|
2163
2833
|
program.parse();
|
|
2164
2834
|
if (!process.argv.slice(2).length) {
|
|
2165
2835
|
console.log();
|
|
2166
|
-
console.log(
|
|
2167
|
-
console.log(
|
|
2836
|
+
console.log(chalk21.bold.cyan(" \u{1F525} LeetCode CLI"));
|
|
2837
|
+
console.log(chalk21.gray(" A modern command-line interface for LeetCode"));
|
|
2168
2838
|
console.log();
|
|
2169
2839
|
program.outputHelp();
|
|
2170
2840
|
}
|