@night-slayer18/leetcode-cli 1.3.2 → 1.4.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.
Files changed (3) hide show
  1. package/README.md +29 -1
  2. package/dist/index.js +575 -195
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -18,6 +18,7 @@ A modern, feature-rich LeetCode CLI built with TypeScript.
18
18
  - 🎯 **Daily challenge** - Get today's problem
19
19
  - ⚙️ **Configurable** - Set language, editor, and working directory
20
20
  - 📂 **Smart file discovery** - Use problem ID, filename, or full path
21
+ - 🔄 **Git Sync** - Auto-sync solutions to GitHub/GitLab
21
22
 
22
23
  ## 📚 Documentation
23
24
 
@@ -70,7 +71,9 @@ leetcode submit 1
70
71
  | `submit <id\|file>` | Submit solution to LeetCode |
71
72
  | `submissions <id>` | View past submissions |
72
73
  | `stat [username]` | Show user statistics |
74
+ | `stat [username]` | Show user statistics |
73
75
  | `config` | View or set configuration |
76
+ | `sync` | Sync solutions to Git repository |
74
77
 
75
78
  ## Usage Examples
76
79
 
@@ -181,6 +184,29 @@ leetcode submissions 1 --download
181
184
  # Keep personal notes
182
185
  leetcode note 1 edit
183
186
  ```
187
+
188
+ ### User Statistics
189
+
190
+ ```bash
191
+ # Basic stats (solved count, rank, streak)
192
+ leetcode stat
193
+
194
+ # Weekly activity table (last 12 weeks)
195
+ leetcode stat -c
196
+
197
+ # Skill breakdown by topic tags
198
+ leetcode stat -s
199
+
200
+ # 7-day trend chart
201
+ leetcode stat -t
202
+ ```
203
+
204
+ ### Git Integration
205
+
206
+ ```bash
207
+ # Sync all solutions to your configured git repo
208
+ leetcode sync
209
+ ```
184
210
 
185
211
  ### Configuration
186
212
 
@@ -195,6 +221,7 @@ leetcode config -i
195
221
  leetcode config --lang python3
196
222
  leetcode config --editor code
197
223
  leetcode config --workdir ~/leetcode
224
+ leetcode config --repo https://github.com/username/leetcode-solutions.git
198
225
  ```
199
226
 
200
227
  ## Folder Structure
@@ -254,7 +281,8 @@ Config is stored at `~/.leetcode/config.json`:
254
281
  "config": {
255
282
  "language": "java",
256
283
  "editor": "code",
257
- "workDir": "/path/to/leetcode"
284
+ "workDir": "/path/to/leetcode",
285
+ "repo": "https://github.com/username/leetcode-solutions.git"
258
286
  }
259
287
  }
260
288
  ```
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import chalk18 from "chalk";
5
+ import chalk20 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();
@@ -1427,8 +1467,121 @@ async function submitCommand(fileOrId) {
1427
1467
 
1428
1468
  // src/commands/stat.ts
1429
1469
  import ora7 from "ora";
1470
+ import chalk11 from "chalk";
1471
+
1472
+ // src/utils/stats-display.ts
1430
1473
  import chalk10 from "chalk";
1431
- async function statCommand(username) {
1474
+ var MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1475
+ function renderHeatmap(calendarJson) {
1476
+ const data = JSON.parse(calendarJson);
1477
+ const now = /* @__PURE__ */ new Date();
1478
+ const weeks = [];
1479
+ for (let w = 11; w >= 0; w--) {
1480
+ const weekStart = new Date(now);
1481
+ weekStart.setDate(weekStart.getDate() - w * 7 - weekStart.getDay());
1482
+ let weekCount = 0;
1483
+ let activeDays = 0;
1484
+ for (let d = 0; d < 7; d++) {
1485
+ const day = new Date(weekStart);
1486
+ day.setDate(day.getDate() + d);
1487
+ if (day > now) break;
1488
+ const midnight = new Date(Date.UTC(day.getFullYear(), day.getMonth(), day.getDate()));
1489
+ const timestamp = Math.floor(midnight.getTime() / 1e3).toString();
1490
+ const count = data[timestamp] || 0;
1491
+ weekCount += count;
1492
+ if (count > 0) activeDays++;
1493
+ }
1494
+ const weekEnd = new Date(weekStart);
1495
+ weekEnd.setDate(weekEnd.getDate() + 6);
1496
+ weeks.push({
1497
+ start: `${MONTH_NAMES[weekStart.getMonth()]} ${weekStart.getDate()}`,
1498
+ end: `${MONTH_NAMES[weekEnd.getMonth()]} ${weekEnd.getDate()}`,
1499
+ count: weekCount,
1500
+ days: activeDays
1501
+ });
1502
+ }
1503
+ const totalSubmissions = weeks.reduce((sum, w) => sum + w.count, 0);
1504
+ const totalActiveDays = weeks.reduce((sum, w) => sum + w.days, 0);
1505
+ console.log();
1506
+ console.log(chalk10.bold("\u{1F4C5} Activity (Last 12 Weeks)"));
1507
+ console.log(chalk10.gray("How many problems you submitted and days you practiced."));
1508
+ console.log(chalk10.gray("\u2500".repeat(50)));
1509
+ console.log();
1510
+ for (const week of weeks) {
1511
+ const weekLabel = `${week.start} - ${week.end}`.padEnd(18);
1512
+ const bar = week.count > 0 ? chalk10.green("\u2588".repeat(Math.min(week.count, 10))).padEnd(10) : chalk10.gray("\xB7").padEnd(10);
1513
+ const countStr = week.count > 0 ? `${week.count} subs`.padEnd(10) : "".padEnd(10);
1514
+ const daysStr = week.days > 0 ? `${week.days}d active` : "";
1515
+ console.log(` ${chalk10.white(weekLabel)} ${bar} ${chalk10.cyan(countStr)} ${chalk10.yellow(daysStr)}`);
1516
+ }
1517
+ console.log(chalk10.gray("\u2500".repeat(50)));
1518
+ console.log(` ${chalk10.bold.white("Total:")} ${chalk10.cyan.bold(totalSubmissions + " submissions")}, ${chalk10.yellow.bold(totalActiveDays + " days active")}`);
1519
+ console.log();
1520
+ }
1521
+ function renderSkillStats(fundamental, intermediate, advanced) {
1522
+ console.log();
1523
+ console.log(chalk10.bold("\u{1F3AF} Skill Breakdown"));
1524
+ console.log(chalk10.gray("\u2500".repeat(45)));
1525
+ const renderSection = (title, stats, color) => {
1526
+ if (stats.length === 0) return;
1527
+ console.log();
1528
+ console.log(color.bold(` ${title}`));
1529
+ const sorted = [...stats].sort((a, b) => b.problemsSolved - a.problemsSolved);
1530
+ for (const stat of sorted.slice(0, 8)) {
1531
+ const name = stat.tagName.padEnd(22);
1532
+ const bar = color("\u2588".repeat(Math.min(stat.problemsSolved, 15)));
1533
+ console.log(` ${chalk10.white(name)} ${bar} ${chalk10.white(stat.problemsSolved)}`);
1534
+ }
1535
+ };
1536
+ renderSection("Fundamental", fundamental, chalk10.green);
1537
+ renderSection("Intermediate", intermediate, chalk10.yellow);
1538
+ renderSection("Advanced", advanced, chalk10.red);
1539
+ console.log();
1540
+ }
1541
+ function renderTrendChart(calendarJson) {
1542
+ const data = JSON.parse(calendarJson);
1543
+ const now = /* @__PURE__ */ new Date();
1544
+ const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1545
+ const days = [];
1546
+ for (let d = 6; d >= 0; d--) {
1547
+ const day = new Date(now);
1548
+ day.setDate(day.getDate() - d);
1549
+ const midnight = new Date(Date.UTC(day.getFullYear(), day.getMonth(), day.getDate()));
1550
+ const timestamp = Math.floor(midnight.getTime() / 1e3).toString();
1551
+ days.push({
1552
+ label: dayNames[day.getDay()],
1553
+ count: data[timestamp] || 0
1554
+ });
1555
+ }
1556
+ const maxVal = Math.max(...days.map((d) => d.count), 1);
1557
+ const chartHeight = 6;
1558
+ console.log();
1559
+ console.log(chalk10.bold("\u{1F4C8} Submission Trend (Last 7 Days)"));
1560
+ console.log(chalk10.gray("\u2500".repeat(35)));
1561
+ console.log();
1562
+ for (let row = chartHeight; row >= 1; row--) {
1563
+ let line = ` ${row === chartHeight ? maxVal.toString().padStart(2) : " "} \u2502`;
1564
+ for (const day of days) {
1565
+ const barHeight = Math.round(day.count / maxVal * chartHeight);
1566
+ if (barHeight >= row) {
1567
+ line += chalk10.green(" \u2588\u2588 ");
1568
+ } else {
1569
+ line += " ";
1570
+ }
1571
+ }
1572
+ console.log(line);
1573
+ }
1574
+ console.log(` 0 \u2514${"\u2500\u2500\u2500\u2500".repeat(7)}`);
1575
+ console.log(` ${days.map((d) => d.label.padEnd(4)).join("")}`);
1576
+ console.log(chalk10.gray(` ${days.map((d) => d.count.toString().padEnd(4)).join("")}`));
1577
+ const total = days.reduce((a, b) => a + b.count, 0);
1578
+ console.log();
1579
+ console.log(chalk10.white(` Total: ${total} submissions this week`));
1580
+ console.log();
1581
+ }
1582
+
1583
+ // src/commands/stat.ts
1584
+ async function statCommand(username, options = {}) {
1432
1585
  const { authorized, username: currentUser } = await requireAuth();
1433
1586
  if (!authorized) return;
1434
1587
  const spinner = ora7("Fetching statistics...").start();
@@ -1440,25 +1593,48 @@ async function statCommand(username) {
1440
1593
  }
1441
1594
  const profile = await leetcodeClient.getUserProfile(targetUsername);
1442
1595
  spinner.stop();
1443
- displayUserStats(
1444
- profile.username,
1445
- profile.realName,
1446
- profile.ranking,
1447
- profile.acSubmissionNum,
1448
- profile.streak,
1449
- profile.totalActiveDays
1450
- );
1596
+ if (!options.calendar && !options.skills && !options.trend) {
1597
+ displayUserStats(
1598
+ profile.username,
1599
+ profile.realName,
1600
+ profile.ranking,
1601
+ profile.acSubmissionNum,
1602
+ profile.streak,
1603
+ profile.totalActiveDays
1604
+ );
1605
+ return;
1606
+ }
1607
+ if (options.calendar) {
1608
+ if (profile.submissionCalendar) {
1609
+ renderHeatmap(profile.submissionCalendar);
1610
+ } else {
1611
+ console.log(chalk11.yellow("Calendar data not available."));
1612
+ }
1613
+ }
1614
+ if (options.trend) {
1615
+ if (profile.submissionCalendar) {
1616
+ renderTrendChart(profile.submissionCalendar);
1617
+ } else {
1618
+ console.log(chalk11.yellow("Calendar data not available."));
1619
+ }
1620
+ }
1621
+ if (options.skills) {
1622
+ spinner.start("Fetching skill stats...");
1623
+ const skills = await leetcodeClient.getSkillStats(targetUsername);
1624
+ spinner.stop();
1625
+ renderSkillStats(skills.fundamental, skills.intermediate, skills.advanced);
1626
+ }
1451
1627
  } catch (error) {
1452
1628
  spinner.fail("Failed to fetch statistics");
1453
1629
  if (error instanceof Error) {
1454
- console.log(chalk10.red(error.message));
1630
+ console.log(chalk11.red(error.message));
1455
1631
  }
1456
1632
  }
1457
1633
  }
1458
1634
 
1459
1635
  // src/commands/daily.ts
1460
1636
  import ora8 from "ora";
1461
- import chalk11 from "chalk";
1637
+ import chalk12 from "chalk";
1462
1638
  async function dailyCommand() {
1463
1639
  const { authorized } = await requireAuth();
1464
1640
  if (!authorized) return;
@@ -1468,19 +1644,19 @@ async function dailyCommand() {
1468
1644
  spinner.stop();
1469
1645
  displayDailyChallenge(daily.date, daily.question);
1470
1646
  console.log();
1471
- console.log(chalk11.gray("Run the following to start working on this problem:"));
1472
- console.log(chalk11.cyan(` leetcode pick ${daily.question.titleSlug}`));
1647
+ console.log(chalk12.gray("Run the following to start working on this problem:"));
1648
+ console.log(chalk12.cyan(` leetcode pick ${daily.question.titleSlug}`));
1473
1649
  } catch (error) {
1474
1650
  spinner.fail("Failed to fetch daily challenge");
1475
1651
  if (error instanceof Error) {
1476
- console.log(chalk11.red(error.message));
1652
+ console.log(chalk12.red(error.message));
1477
1653
  }
1478
1654
  }
1479
1655
  }
1480
1656
 
1481
1657
  // src/commands/random.ts
1482
1658
  import ora9 from "ora";
1483
- import chalk12 from "chalk";
1659
+ import chalk13 from "chalk";
1484
1660
  async function randomCommand(options) {
1485
1661
  const { authorized } = await requireAuth();
1486
1662
  if (!authorized) return;
@@ -1514,13 +1690,13 @@ async function randomCommand(options) {
1514
1690
  await pickCommand(titleSlug, { open: options.open ?? true });
1515
1691
  } else {
1516
1692
  await showCommand(titleSlug);
1517
- console.log(chalk12.gray("Run following to start solving:"));
1518
- console.log(chalk12.cyan(` leetcode pick ${titleSlug}`));
1693
+ console.log(chalk13.gray("Run following to start solving:"));
1694
+ console.log(chalk13.cyan(` leetcode pick ${titleSlug}`));
1519
1695
  }
1520
1696
  } catch (error) {
1521
1697
  spinner.fail("Failed to fetch random problem");
1522
1698
  if (error instanceof Error) {
1523
- console.log(chalk12.red(error.message));
1699
+ console.log(chalk13.red(error.message));
1524
1700
  }
1525
1701
  }
1526
1702
  }
@@ -1530,7 +1706,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1530
1706
  import { existsSync as existsSync5 } from "fs";
1531
1707
  import { join as join4 } from "path";
1532
1708
  import ora10 from "ora";
1533
- import chalk13 from "chalk";
1709
+ import chalk14 from "chalk";
1534
1710
  async function submissionsCommand(idOrSlug, options) {
1535
1711
  const { authorized } = await requireAuth();
1536
1712
  if (!authorized) return;
@@ -1552,16 +1728,16 @@ async function submissionsCommand(idOrSlug, options) {
1552
1728
  const submissions = await leetcodeClient.getSubmissionList(slug, limit);
1553
1729
  spinner.stop();
1554
1730
  if (submissions.length === 0) {
1555
- console.log(chalk13.yellow("No submissions found."));
1731
+ console.log(chalk14.yellow("No submissions found."));
1556
1732
  return;
1557
1733
  }
1558
1734
  if (options.last) {
1559
1735
  const lastAC = submissions.find((s) => s.statusDisplay === "Accepted");
1560
1736
  if (lastAC) {
1561
- console.log(chalk13.bold("Last Accepted Submission:"));
1737
+ console.log(chalk14.bold("Last Accepted Submission:"));
1562
1738
  displaySubmissionsList([lastAC]);
1563
1739
  } else {
1564
- console.log(chalk13.yellow("No accepted submissions found in recent history."));
1740
+ console.log(chalk14.yellow("No accepted submissions found in recent history."));
1565
1741
  }
1566
1742
  } else {
1567
1743
  displaySubmissionsList(submissions);
@@ -1592,20 +1768,20 @@ async function submissionsCommand(idOrSlug, options) {
1592
1768
  const fileName = `${problem.questionFrontendId}.${problem.titleSlug}.submission-${lastAC.id}.${ext}`;
1593
1769
  const filePath = join4(targetDir, fileName);
1594
1770
  await writeFile2(filePath, details.code, "utf-8");
1595
- downloadSpinner.succeed(`Downloaded to ${chalk13.green(fileName)}`);
1596
- console.log(chalk13.gray(`Path: ${filePath}`));
1771
+ downloadSpinner.succeed(`Downloaded to ${chalk14.green(fileName)}`);
1772
+ console.log(chalk14.gray(`Path: ${filePath}`));
1597
1773
  }
1598
1774
  } catch (error) {
1599
1775
  spinner.fail("Failed to fetch submissions");
1600
1776
  if (error instanceof Error) {
1601
- console.log(chalk13.red(error.message));
1777
+ console.log(chalk14.red(error.message));
1602
1778
  }
1603
1779
  }
1604
1780
  }
1605
1781
 
1606
1782
  // src/commands/config.ts
1607
1783
  import inquirer2 from "inquirer";
1608
- import chalk14 from "chalk";
1784
+ import chalk15 from "chalk";
1609
1785
  var SUPPORTED_LANGUAGES = [
1610
1786
  "typescript",
1611
1787
  "javascript",
@@ -1627,21 +1803,30 @@ async function configCommand(options) {
1627
1803
  if (options.lang) {
1628
1804
  const langInput = options.lang.toLowerCase();
1629
1805
  if (!SUPPORTED_LANGUAGES.includes(langInput)) {
1630
- console.log(chalk14.red(`Unsupported language: ${options.lang}`));
1631
- console.log(chalk14.gray(`Supported: ${SUPPORTED_LANGUAGES.join(", ")}`));
1806
+ console.log(chalk15.red(`Unsupported language: ${options.lang}`));
1807
+ console.log(chalk15.gray(`Supported: ${SUPPORTED_LANGUAGES.join(", ")}`));
1632
1808
  return;
1633
1809
  }
1634
1810
  const lang = langInput;
1635
1811
  config.setLanguage(lang);
1636
- console.log(chalk14.green(`\u2713 Default language set to ${lang}`));
1812
+ console.log(chalk15.green(`\u2713 Default language set to ${lang}`));
1637
1813
  }
1638
1814
  if (options.editor) {
1639
1815
  config.setEditor(options.editor);
1640
- console.log(chalk14.green(`\u2713 Editor set to ${options.editor}`));
1816
+ console.log(chalk15.green(`\u2713 Editor set to ${options.editor}`));
1641
1817
  }
1642
1818
  if (options.workdir) {
1643
1819
  config.setWorkDir(options.workdir);
1644
- console.log(chalk14.green(`\u2713 Work directory set to ${options.workdir}`));
1820
+ console.log(chalk15.green(`\u2713 Work directory set to ${options.workdir}`));
1821
+ }
1822
+ if (options.repo !== void 0) {
1823
+ if (options.repo.trim() === "") {
1824
+ config.deleteRepo();
1825
+ console.log(chalk15.green("\u2713 Repository URL cleared"));
1826
+ } else {
1827
+ config.setRepo(options.repo);
1828
+ console.log(chalk15.green(`\u2713 Repository URL set to ${options.repo}`));
1829
+ }
1645
1830
  }
1646
1831
  }
1647
1832
  async function configInteractiveCommand() {
@@ -1665,32 +1850,44 @@ async function configInteractiveCommand() {
1665
1850
  name: "workDir",
1666
1851
  message: "Working directory for solution files:",
1667
1852
  default: currentConfig.workDir
1853
+ },
1854
+ {
1855
+ type: "input",
1856
+ name: "repo",
1857
+ message: "Git repository URL (optional):",
1858
+ default: currentConfig.repo
1668
1859
  }
1669
1860
  ]);
1670
1861
  config.setLanguage(answers.language);
1671
1862
  config.setEditor(answers.editor);
1672
1863
  config.setWorkDir(answers.workDir);
1864
+ if (answers.repo) {
1865
+ config.setRepo(answers.repo);
1866
+ } else {
1867
+ config.deleteRepo();
1868
+ }
1673
1869
  console.log();
1674
- console.log(chalk14.green("\u2713 Configuration saved"));
1870
+ console.log(chalk15.green("\u2713 Configuration saved"));
1675
1871
  showCurrentConfig();
1676
1872
  }
1677
1873
  function showCurrentConfig() {
1678
1874
  const currentConfig = config.getConfig();
1679
1875
  const credentials = config.getCredentials();
1680
1876
  console.log();
1681
- console.log(chalk14.bold("LeetCode CLI Configuration"));
1682
- console.log(chalk14.gray("\u2500".repeat(40)));
1877
+ console.log(chalk15.bold("LeetCode CLI Configuration"));
1878
+ console.log(chalk15.gray("\u2500".repeat(40)));
1683
1879
  console.log();
1684
- console.log(chalk14.gray("Config file:"), config.getPath());
1880
+ console.log(chalk15.gray("Config file:"), config.getPath());
1685
1881
  console.log();
1686
- console.log(chalk14.gray("Language: "), chalk14.white(currentConfig.language));
1687
- console.log(chalk14.gray("Editor: "), chalk14.white(currentConfig.editor ?? "(not set)"));
1688
- console.log(chalk14.gray("Work Dir: "), chalk14.white(currentConfig.workDir));
1689
- console.log(chalk14.gray("Logged in: "), credentials ? chalk14.green("Yes") : chalk14.yellow("No"));
1882
+ console.log(chalk15.gray("Language: "), chalk15.white(currentConfig.language));
1883
+ console.log(chalk15.gray("Editor: "), chalk15.white(currentConfig.editor ?? "(not set)"));
1884
+ console.log(chalk15.gray("Work Dir: "), chalk15.white(currentConfig.workDir));
1885
+ console.log(chalk15.gray("Repo URL: "), chalk15.white(currentConfig.repo ?? "(not set)"));
1886
+ console.log(chalk15.gray("Logged in: "), credentials ? chalk15.green("Yes") : chalk15.yellow("No"));
1690
1887
  }
1691
1888
 
1692
1889
  // src/commands/bookmark.ts
1693
- import chalk15 from "chalk";
1890
+ import chalk16 from "chalk";
1694
1891
  import Table2 from "cli-table3";
1695
1892
  import ora11 from "ora";
1696
1893
 
@@ -1735,36 +1932,36 @@ var bookmarks = {
1735
1932
  async function bookmarkCommand(action, id) {
1736
1933
  const validActions = ["add", "remove", "list", "clear"];
1737
1934
  if (!validActions.includes(action)) {
1738
- console.log(chalk15.red(`Invalid action: ${action}`));
1739
- console.log(chalk15.gray("Valid actions: add, remove, list, clear"));
1935
+ console.log(chalk16.red(`Invalid action: ${action}`));
1936
+ console.log(chalk16.gray("Valid actions: add, remove, list, clear"));
1740
1937
  return;
1741
1938
  }
1742
1939
  switch (action) {
1743
1940
  case "add":
1744
1941
  if (!id) {
1745
- console.log(chalk15.red("Please provide a problem ID to bookmark"));
1942
+ console.log(chalk16.red("Please provide a problem ID to bookmark"));
1746
1943
  return;
1747
1944
  }
1748
1945
  if (!isProblemId(id)) {
1749
- console.log(chalk15.red(`Invalid problem ID: ${id}`));
1750
- console.log(chalk15.gray("Problem ID must be a positive integer"));
1946
+ console.log(chalk16.red(`Invalid problem ID: ${id}`));
1947
+ console.log(chalk16.gray("Problem ID must be a positive integer"));
1751
1948
  return;
1752
1949
  }
1753
1950
  if (bookmarks.add(id)) {
1754
- console.log(chalk15.green(`\u2713 Bookmarked problem ${id}`));
1951
+ console.log(chalk16.green(`\u2713 Bookmarked problem ${id}`));
1755
1952
  } else {
1756
- console.log(chalk15.yellow(`Problem ${id} is already bookmarked`));
1953
+ console.log(chalk16.yellow(`Problem ${id} is already bookmarked`));
1757
1954
  }
1758
1955
  break;
1759
1956
  case "remove":
1760
1957
  if (!id) {
1761
- console.log(chalk15.red("Please provide a problem ID to remove"));
1958
+ console.log(chalk16.red("Please provide a problem ID to remove"));
1762
1959
  return;
1763
1960
  }
1764
1961
  if (bookmarks.remove(id)) {
1765
- console.log(chalk15.green(`\u2713 Removed bookmark for problem ${id}`));
1962
+ console.log(chalk16.green(`\u2713 Removed bookmark for problem ${id}`));
1766
1963
  } else {
1767
- console.log(chalk15.yellow(`Problem ${id} is not bookmarked`));
1964
+ console.log(chalk16.yellow(`Problem ${id} is not bookmarked`));
1768
1965
  }
1769
1966
  break;
1770
1967
  case "list":
@@ -1773,10 +1970,10 @@ async function bookmarkCommand(action, id) {
1773
1970
  case "clear":
1774
1971
  const count = bookmarks.count();
1775
1972
  if (count === 0) {
1776
- console.log(chalk15.yellow("No bookmarks to clear"));
1973
+ console.log(chalk16.yellow("No bookmarks to clear"));
1777
1974
  } else {
1778
1975
  bookmarks.clear();
1779
- console.log(chalk15.green(`\u2713 Cleared ${count} bookmark${count !== 1 ? "s" : ""}`));
1976
+ console.log(chalk16.green(`\u2713 Cleared ${count} bookmark${count !== 1 ? "s" : ""}`));
1780
1977
  }
1781
1978
  break;
1782
1979
  }
@@ -1784,12 +1981,12 @@ async function bookmarkCommand(action, id) {
1784
1981
  async function listBookmarks() {
1785
1982
  const bookmarkList = bookmarks.list();
1786
1983
  if (bookmarkList.length === 0) {
1787
- console.log(chalk15.yellow("\u{1F4CC} No bookmarked problems"));
1788
- console.log(chalk15.gray('Use "leetcode bookmark add <id>" to bookmark a problem'));
1984
+ console.log(chalk16.yellow("\u{1F4CC} No bookmarked problems"));
1985
+ console.log(chalk16.gray('Use "leetcode bookmark add <id>" to bookmark a problem'));
1789
1986
  return;
1790
1987
  }
1791
1988
  console.log();
1792
- console.log(chalk15.bold.cyan(`\u{1F4CC} Bookmarked Problems (${bookmarkList.length})`));
1989
+ console.log(chalk16.bold.cyan(`\u{1F4CC} Bookmarked Problems (${bookmarkList.length})`));
1793
1990
  console.log();
1794
1991
  const { authorized } = await requireAuth();
1795
1992
  if (authorized) {
@@ -1797,10 +1994,10 @@ async function listBookmarks() {
1797
1994
  try {
1798
1995
  const table = new Table2({
1799
1996
  head: [
1800
- chalk15.cyan("ID"),
1801
- chalk15.cyan("Title"),
1802
- chalk15.cyan("Difficulty"),
1803
- chalk15.cyan("Status")
1997
+ chalk16.cyan("ID"),
1998
+ chalk16.cyan("Title"),
1999
+ chalk16.cyan("Difficulty"),
2000
+ chalk16.cyan("Status")
1804
2001
  ],
1805
2002
  colWidths: [8, 45, 12, 10],
1806
2003
  style: { head: [], border: [] }
@@ -1813,35 +2010,35 @@ async function listBookmarks() {
1813
2010
  problem.questionFrontendId,
1814
2011
  problem.title.length > 42 ? problem.title.slice(0, 39) + "..." : problem.title,
1815
2012
  colorDifficulty2(problem.difficulty),
1816
- problem.status === "ac" ? chalk15.green("\u2713") : chalk15.gray("-")
2013
+ problem.status === "ac" ? chalk16.green("\u2713") : chalk16.gray("-")
1817
2014
  ]);
1818
2015
  } else {
1819
- table.push([id, chalk15.gray("(not found)"), "-", "-"]);
2016
+ table.push([id, chalk16.gray("(not found)"), "-", "-"]);
1820
2017
  }
1821
2018
  } catch {
1822
- table.push([id, chalk15.gray("(error fetching)"), "-", "-"]);
2019
+ table.push([id, chalk16.gray("(error fetching)"), "-", "-"]);
1823
2020
  }
1824
2021
  }
1825
2022
  spinner.stop();
1826
2023
  console.log(table.toString());
1827
2024
  } catch {
1828
2025
  spinner.stop();
1829
- console.log(chalk15.gray("IDs: ") + bookmarkList.join(", "));
2026
+ console.log(chalk16.gray("IDs: ") + bookmarkList.join(", "));
1830
2027
  }
1831
2028
  } else {
1832
- console.log(chalk15.gray("IDs: ") + bookmarkList.join(", "));
2029
+ console.log(chalk16.gray("IDs: ") + bookmarkList.join(", "));
1833
2030
  console.log();
1834
- console.log(chalk15.gray("Login to see problem details"));
2031
+ console.log(chalk16.gray("Login to see problem details"));
1835
2032
  }
1836
2033
  }
1837
2034
  function colorDifficulty2(difficulty) {
1838
2035
  switch (difficulty.toLowerCase()) {
1839
2036
  case "easy":
1840
- return chalk15.green(difficulty);
2037
+ return chalk16.green(difficulty);
1841
2038
  case "medium":
1842
- return chalk15.yellow(difficulty);
2039
+ return chalk16.yellow(difficulty);
1843
2040
  case "hard":
1844
- return chalk15.red(difficulty);
2041
+ return chalk16.red(difficulty);
1845
2042
  default:
1846
2043
  return difficulty;
1847
2044
  }
@@ -1851,11 +2048,11 @@ function colorDifficulty2(difficulty) {
1851
2048
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1852
2049
  import { join as join5 } from "path";
1853
2050
  import { existsSync as existsSync6 } from "fs";
1854
- import chalk16 from "chalk";
2051
+ import chalk17 from "chalk";
1855
2052
  async function notesCommand(problemId, action) {
1856
2053
  if (!isProblemId(problemId)) {
1857
- console.log(chalk16.red(`Invalid problem ID: ${problemId}`));
1858
- console.log(chalk16.gray("Problem ID must be a positive integer"));
2054
+ console.log(chalk17.red(`Invalid problem ID: ${problemId}`));
2055
+ console.log(chalk17.gray("Problem ID must be a positive integer"));
1859
2056
  return;
1860
2057
  }
1861
2058
  const noteAction = action === "view" ? "view" : "edit";
@@ -1872,21 +2069,21 @@ async function notesCommand(problemId, action) {
1872
2069
  }
1873
2070
  async function viewNote(notePath, problemId) {
1874
2071
  if (!existsSync6(notePath)) {
1875
- console.log(chalk16.yellow(`No notes found for problem ${problemId}`));
1876
- console.log(chalk16.gray(`Use "leetcode note ${problemId} edit" to create notes`));
2072
+ console.log(chalk17.yellow(`No notes found for problem ${problemId}`));
2073
+ console.log(chalk17.gray(`Use "leetcode note ${problemId} edit" to create notes`));
1877
2074
  return;
1878
2075
  }
1879
2076
  try {
1880
2077
  const content = await readFile3(notePath, "utf-8");
1881
2078
  console.log();
1882
- console.log(chalk16.bold.cyan(`\u{1F4DD} Notes for Problem ${problemId}`));
1883
- console.log(chalk16.gray("\u2500".repeat(50)));
2079
+ console.log(chalk17.bold.cyan(`\u{1F4DD} Notes for Problem ${problemId}`));
2080
+ console.log(chalk17.gray("\u2500".repeat(50)));
1884
2081
  console.log();
1885
2082
  console.log(content);
1886
2083
  } catch (error) {
1887
- console.log(chalk16.red("Failed to read notes"));
2084
+ console.log(chalk17.red("Failed to read notes"));
1888
2085
  if (error instanceof Error) {
1889
- console.log(chalk16.gray(error.message));
2086
+ console.log(chalk17.gray(error.message));
1890
2087
  }
1891
2088
  }
1892
2089
  }
@@ -1894,9 +2091,9 @@ async function editNote(notePath, problemId) {
1894
2091
  if (!existsSync6(notePath)) {
1895
2092
  const template = await generateNoteTemplate(problemId);
1896
2093
  await writeFile3(notePath, template, "utf-8");
1897
- console.log(chalk16.green(`\u2713 Created notes file for problem ${problemId}`));
2094
+ console.log(chalk17.green(`\u2713 Created notes file for problem ${problemId}`));
1898
2095
  }
1899
- console.log(chalk16.gray(`Opening: ${notePath}`));
2096
+ console.log(chalk17.gray(`Opening: ${notePath}`));
1900
2097
  await openInEditor(notePath);
1901
2098
  }
1902
2099
  async function generateNoteTemplate(problemId) {
@@ -1953,7 +2150,7 @@ async function generateNoteTemplate(problemId) {
1953
2150
  }
1954
2151
 
1955
2152
  // src/commands/today.ts
1956
- import chalk17 from "chalk";
2153
+ import chalk18 from "chalk";
1957
2154
  import ora12 from "ora";
1958
2155
  async function todayCommand() {
1959
2156
  const { authorized, username } = await requireAuth();
@@ -1968,190 +2165,373 @@ async function todayCommand() {
1968
2165
  ]);
1969
2166
  spinner.stop();
1970
2167
  console.log();
1971
- console.log(chalk17.bold.cyan(`\u{1F4CA} Today's Summary`), chalk17.gray(`- ${username}`));
1972
- console.log(chalk17.gray("\u2500".repeat(50)));
2168
+ console.log(chalk18.bold.cyan(`\u{1F4CA} Today's Summary`), chalk18.gray(`- ${username}`));
2169
+ console.log(chalk18.gray("\u2500".repeat(50)));
1973
2170
  console.log();
1974
- console.log(chalk17.yellow(`\u{1F525} Current Streak: ${profile.streak} day${profile.streak !== 1 ? "s" : ""}`));
1975
- console.log(chalk17.gray(` Total Active Days: ${profile.totalActiveDays}`));
2171
+ console.log(chalk18.yellow(`\u{1F525} Current Streak: ${profile.streak} day${profile.streak !== 1 ? "s" : ""}`));
2172
+ console.log(chalk18.gray(` Total Active Days: ${profile.totalActiveDays}`));
1976
2173
  console.log();
1977
2174
  const total = profile.acSubmissionNum.find((s) => s.difficulty === "All");
1978
2175
  const easy = profile.acSubmissionNum.find((s) => s.difficulty === "Easy");
1979
2176
  const medium = profile.acSubmissionNum.find((s) => s.difficulty === "Medium");
1980
2177
  const hard = profile.acSubmissionNum.find((s) => s.difficulty === "Hard");
1981
- console.log(chalk17.white("\u{1F4C8} Problems Solved:"));
1982
- console.log(` ${chalk17.green("Easy")}: ${easy?.count ?? 0} | ${chalk17.yellow("Medium")}: ${medium?.count ?? 0} | ${chalk17.red("Hard")}: ${hard?.count ?? 0}`);
1983
- console.log(` ${chalk17.bold("Total")}: ${total?.count ?? 0}`);
2178
+ console.log(chalk18.white("\u{1F4C8} Problems Solved:"));
2179
+ console.log(` ${chalk18.green("Easy")}: ${easy?.count ?? 0} | ${chalk18.yellow("Medium")}: ${medium?.count ?? 0} | ${chalk18.red("Hard")}: ${hard?.count ?? 0}`);
2180
+ console.log(` ${chalk18.bold("Total")}: ${total?.count ?? 0}`);
1984
2181
  console.log();
1985
- console.log(chalk17.bold.yellow("\u{1F3AF} Today's Challenge:"));
2182
+ console.log(chalk18.bold.yellow("\u{1F3AF} Today's Challenge:"));
1986
2183
  console.log(` ${daily.question.questionFrontendId}. ${daily.question.title}`);
1987
2184
  console.log(` ${colorDifficulty3(daily.question.difficulty)}`);
1988
2185
  const status = daily.question.status;
1989
2186
  if (status === "ac") {
1990
- console.log(chalk17.green(" \u2713 Completed!"));
2187
+ console.log(chalk18.green(" \u2713 Completed!"));
1991
2188
  } else if (status === "notac") {
1992
- console.log(chalk17.yellow(" \u25CB Attempted"));
2189
+ console.log(chalk18.yellow(" \u25CB Attempted"));
1993
2190
  } else {
1994
- console.log(chalk17.gray(" - Not started"));
2191
+ console.log(chalk18.gray(" - Not started"));
1995
2192
  }
1996
2193
  console.log();
1997
- console.log(chalk17.gray(` leetcode pick ${daily.question.questionFrontendId} # Start working on it`));
2194
+ console.log(chalk18.gray(` leetcode pick ${daily.question.questionFrontendId} # Start working on it`));
1998
2195
  } catch (error) {
1999
2196
  spinner.fail("Failed to fetch progress");
2000
2197
  if (error instanceof Error) {
2001
- console.log(chalk17.red(error.message));
2198
+ console.log(chalk18.red(error.message));
2002
2199
  }
2003
2200
  }
2004
2201
  }
2005
2202
  function colorDifficulty3(difficulty) {
2006
2203
  switch (difficulty.toLowerCase()) {
2007
2204
  case "easy":
2008
- return chalk17.green(difficulty);
2205
+ return chalk18.green(difficulty);
2009
2206
  case "medium":
2010
- return chalk17.yellow(difficulty);
2207
+ return chalk18.yellow(difficulty);
2011
2208
  case "hard":
2012
- return chalk17.red(difficulty);
2209
+ return chalk18.red(difficulty);
2013
2210
  default:
2014
2211
  return difficulty;
2015
2212
  }
2016
2213
  }
2017
2214
 
2215
+ // src/commands/sync.ts
2216
+ import { execSync } from "child_process";
2217
+ import { existsSync as existsSync7 } from "fs";
2218
+ import chalk19 from "chalk";
2219
+ import inquirer3 from "inquirer";
2220
+ import ora13 from "ora";
2221
+ function isGitInstalled() {
2222
+ try {
2223
+ execSync("git --version", { stdio: "ignore" });
2224
+ return true;
2225
+ } catch (error) {
2226
+ return false;
2227
+ }
2228
+ }
2229
+ function isMapRepo(workDir) {
2230
+ try {
2231
+ execSync("git rev-parse --is-inside-work-tree", { cwd: workDir, stdio: "ignore" });
2232
+ return true;
2233
+ } catch (error) {
2234
+ return false;
2235
+ }
2236
+ }
2237
+ function isGhInstalled() {
2238
+ try {
2239
+ execSync("gh --version", { stdio: "ignore" });
2240
+ return true;
2241
+ } catch (error) {
2242
+ return false;
2243
+ }
2244
+ }
2245
+ function getRemoteUrl(workDir) {
2246
+ try {
2247
+ const url = execSync("git config --get remote.origin.url", { cwd: workDir, encoding: "utf-8" });
2248
+ return url.trim();
2249
+ } catch (error) {
2250
+ return null;
2251
+ }
2252
+ }
2253
+ async function setupGitRepo(workDir) {
2254
+ const { init } = await inquirer3.prompt([
2255
+ {
2256
+ type: "confirm",
2257
+ name: "init",
2258
+ message: "Work directory is not a git repository. Initialize?",
2259
+ default: true
2260
+ }
2261
+ ]);
2262
+ if (!init) {
2263
+ console.log(chalk19.yellow("Skipping basic git initialization."));
2264
+ return false;
2265
+ }
2266
+ const spinner = ora13("Initializing git repository...").start();
2267
+ try {
2268
+ execSync("git init", { cwd: workDir });
2269
+ spinner.succeed("Initialized git repository");
2270
+ return true;
2271
+ } catch (error) {
2272
+ spinner.fail("Failed to initialize git repository");
2273
+ throw error;
2274
+ }
2275
+ }
2276
+ async function setupRemote(workDir) {
2277
+ const spinner = ora13();
2278
+ let repoUrl = config.getRepo();
2279
+ if (!repoUrl) {
2280
+ if (isGhInstalled()) {
2281
+ const { createGh } = await inquirer3.prompt([
2282
+ {
2283
+ type: "confirm",
2284
+ name: "createGh",
2285
+ message: "Create a new private GitHub repository?",
2286
+ default: true
2287
+ }
2288
+ ]);
2289
+ if (createGh) {
2290
+ spinner.start("Creating GitHub repository...");
2291
+ try {
2292
+ const repoName = workDir.split("/").pop() || "leetcode-solutions";
2293
+ execSync(`gh repo create ${repoName} --private --source=. --remote=origin`, { cwd: workDir });
2294
+ spinner.succeed("Created and linked GitHub repository");
2295
+ repoUrl = getRemoteUrl(workDir) || "";
2296
+ if (repoUrl) {
2297
+ config.setRepo(repoUrl);
2298
+ }
2299
+ return repoUrl;
2300
+ } catch (error) {
2301
+ spinner.fail("Failed to create GitHub repository");
2302
+ console.log(chalk19.red(error));
2303
+ }
2304
+ }
2305
+ }
2306
+ if (!repoUrl) {
2307
+ console.log(chalk19.yellow("\nPlease create a new repository on your Git provider and copy the URL."));
2308
+ const { url } = await inquirer3.prompt([
2309
+ {
2310
+ type: "input",
2311
+ name: "url",
2312
+ message: "Enter remote repository URL:",
2313
+ validate: (input) => input.length > 0 ? true : "URL cannot be empty"
2314
+ }
2315
+ ]);
2316
+ repoUrl = url;
2317
+ }
2318
+ }
2319
+ if (repoUrl) {
2320
+ config.setRepo(repoUrl);
2321
+ }
2322
+ const currentRemote = getRemoteUrl(workDir);
2323
+ if (!currentRemote && repoUrl) {
2324
+ try {
2325
+ execSync(`git remote add origin ${repoUrl}`, { cwd: workDir });
2326
+ console.log(chalk19.green("\u2713 Added remote origin"));
2327
+ } catch (e) {
2328
+ console.log(chalk19.red("Failed to add remote origin"));
2329
+ }
2330
+ }
2331
+ return repoUrl || "";
2332
+ }
2333
+ async function syncCommand() {
2334
+ const workDir = config.getWorkDir();
2335
+ if (!existsSync7(workDir)) {
2336
+ console.log(chalk19.red(`Work directory does not exist: ${workDir}`));
2337
+ return;
2338
+ }
2339
+ if (!isGitInstalled()) {
2340
+ console.log(chalk19.red("Git is not installed. Please install Git to use command."));
2341
+ return;
2342
+ }
2343
+ if (!isMapRepo(workDir)) {
2344
+ const initialized = await setupGitRepo(workDir);
2345
+ if (!initialized) return;
2346
+ }
2347
+ await setupRemote(workDir);
2348
+ const spinner = ora13("Syncing solutions...").start();
2349
+ try {
2350
+ const status = execSync("git status --porcelain", { cwd: workDir, encoding: "utf-8" });
2351
+ if (!status) {
2352
+ spinner.info("No changes to sync");
2353
+ return;
2354
+ }
2355
+ execSync("git add .", { cwd: workDir });
2356
+ const lines = status.trim().split("\n");
2357
+ const count = lines.length;
2358
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
2359
+ const message = `Sync: ${count} solutions - ${timestamp}`;
2360
+ execSync(`git commit -m "${message}"`, { cwd: workDir });
2361
+ try {
2362
+ execSync("git push -u origin main", { cwd: workDir, stdio: "ignore" });
2363
+ } catch {
2364
+ try {
2365
+ execSync("git push -u origin master", { cwd: workDir, stdio: "ignore" });
2366
+ } catch (e) {
2367
+ throw new Error("Failed to push to remote. Please check your git credentials and branch status.");
2368
+ }
2369
+ }
2370
+ spinner.succeed("Successfully synced solutions to remote");
2371
+ } catch (error) {
2372
+ spinner.fail("Sync failed");
2373
+ if (error.message) {
2374
+ console.log(chalk19.red(error.message));
2375
+ }
2376
+ }
2377
+ }
2378
+
2018
2379
  // src/index.ts
2019
2380
  var program = new Command();
2020
2381
  program.configureHelp({
2021
2382
  sortSubcommands: true,
2022
- subcommandTerm: (cmd) => chalk18.cyan(cmd.name()) + (cmd.alias() ? chalk18.gray(`|${cmd.alias()}`) : ""),
2023
- subcommandDescription: (cmd) => chalk18.white(cmd.description()),
2024
- optionTerm: (option) => chalk18.yellow(option.flags),
2025
- optionDescription: (option) => chalk18.white(option.description)
2383
+ subcommandTerm: (cmd) => chalk20.cyan(cmd.name()) + (cmd.alias() ? chalk20.gray(`|${cmd.alias()}`) : ""),
2384
+ subcommandDescription: (cmd) => chalk20.white(cmd.description()),
2385
+ optionTerm: (option) => chalk20.yellow(option.flags),
2386
+ optionDescription: (option) => chalk20.white(option.description)
2026
2387
  });
2027
- program.name("leetcode").usage("[command] [options]").description(chalk18.bold.cyan("\u{1F525} A modern LeetCode CLI built with TypeScript")).version("1.3.2", "-v, --version", "Output the version number").helpOption("-h, --help", "Display help for command").addHelpText("after", `
2028
- ${chalk18.yellow("Examples:")}
2029
- ${chalk18.cyan("$ leetcode login")} Login to LeetCode
2030
- ${chalk18.cyan("$ leetcode list -d easy")} List easy problems
2031
- ${chalk18.cyan("$ leetcode random -d medium")} Get random medium problem
2032
- ${chalk18.cyan("$ leetcode pick 1")} Start solving "Two Sum"
2033
- ${chalk18.cyan("$ leetcode test 1")} Test your solution
2034
- ${chalk18.cyan("$ leetcode submit 1")} Submit your solution
2388
+ program.name("leetcode").usage("[command] [options]").description(chalk20.bold.cyan("\u{1F525} A modern LeetCode CLI built with TypeScript")).version("1.4.0", "-v, --version", "Output the version number").helpOption("-h, --help", "Display help for command").addHelpText("after", `
2389
+ ${chalk20.yellow("Examples:")}
2390
+ ${chalk20.cyan("$ leetcode login")} Login to LeetCode
2391
+ ${chalk20.cyan("$ leetcode list -d easy")} List easy problems
2392
+ ${chalk20.cyan("$ leetcode random -d medium")} Get random medium problem
2393
+ ${chalk20.cyan("$ leetcode pick 1")} Start solving "Two Sum"
2394
+ ${chalk20.cyan("$ leetcode test 1")} Test your solution
2395
+ ${chalk20.cyan("$ leetcode submit 1")} Submit your solution
2035
2396
  `);
2036
2397
  program.command("login").description("Login to LeetCode with browser cookies").addHelpText("after", `
2037
- ${chalk18.yellow("How to login:")}
2038
- 1. Open ${chalk18.cyan("https://leetcode.com")} in your browser
2398
+ ${chalk20.yellow("How to login:")}
2399
+ 1. Open ${chalk20.cyan("https://leetcode.com")} in your browser
2039
2400
  2. Login to your account
2040
2401
  3. Open Developer Tools (F12) \u2192 Application \u2192 Cookies
2041
- 4. Copy values of ${chalk18.green("LEETCODE_SESSION")} and ${chalk18.green("csrftoken")}
2402
+ 4. Copy values of ${chalk20.green("LEETCODE_SESSION")} and ${chalk20.green("csrftoken")}
2042
2403
  5. Paste when prompted by this command
2043
2404
  `).action(loginCommand);
2044
2405
  program.command("logout").description("Clear stored credentials").action(logoutCommand);
2045
2406
  program.command("whoami").description("Check current login status").action(whoamiCommand);
2046
2407
  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
- ${chalk18.yellow("Examples:")}
2048
- ${chalk18.cyan("$ leetcode list")} List first 20 problems
2049
- ${chalk18.cyan("$ leetcode list -d easy")} List easy problems only
2050
- ${chalk18.cyan("$ leetcode list -s solved")} List your solved problems
2051
- ${chalk18.cyan("$ leetcode list -t array -t string")} Filter by multiple tags
2052
- ${chalk18.cyan('$ leetcode list -q "two sum"')} Search by keywords
2053
- ${chalk18.cyan("$ leetcode list -n 50 -p 2")} Show 50 problems, page 2
2408
+ ${chalk20.yellow("Examples:")}
2409
+ ${chalk20.cyan("$ leetcode list")} List first 20 problems
2410
+ ${chalk20.cyan("$ leetcode list -d easy")} List easy problems only
2411
+ ${chalk20.cyan("$ leetcode list -s solved")} List your solved problems
2412
+ ${chalk20.cyan("$ leetcode list -t array -t string")} Filter by multiple tags
2413
+ ${chalk20.cyan('$ leetcode list -q "two sum"')} Search by keywords
2414
+ ${chalk20.cyan("$ leetcode list -n 50 -p 2")} Show 50 problems, page 2
2054
2415
  `).action(listCommand);
2055
2416
  program.command("show <id>").alias("s").description("Show problem description").addHelpText("after", `
2056
- ${chalk18.yellow("Examples:")}
2057
- ${chalk18.cyan("$ leetcode show 1")} Show by problem ID
2058
- ${chalk18.cyan("$ leetcode show two-sum")} Show by problem slug
2059
- ${chalk18.cyan("$ leetcode s 412")} Short alias
2417
+ ${chalk20.yellow("Examples:")}
2418
+ ${chalk20.cyan("$ leetcode show 1")} Show by problem ID
2419
+ ${chalk20.cyan("$ leetcode show two-sum")} Show by problem slug
2420
+ ${chalk20.cyan("$ leetcode s 412")} Short alias
2060
2421
  `).action(showCommand);
2061
2422
  program.command("daily").alias("d").description("Show today's daily challenge").addHelpText("after", `
2062
- ${chalk18.yellow("Examples:")}
2063
- ${chalk18.cyan("$ leetcode daily")} Show today's challenge
2064
- ${chalk18.cyan("$ leetcode d")} Short alias
2423
+ ${chalk20.yellow("Examples:")}
2424
+ ${chalk20.cyan("$ leetcode daily")} Show today's challenge
2425
+ ${chalk20.cyan("$ leetcode d")} Short alias
2065
2426
  `).action(dailyCommand);
2066
2427
  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
- ${chalk18.yellow("Examples:")}
2068
- ${chalk18.cyan("$ leetcode random")} Get any random problem
2069
- ${chalk18.cyan("$ leetcode random -d medium")} Random medium problem
2070
- ${chalk18.cyan("$ leetcode random -t array")} Random array problem
2071
- ${chalk18.cyan("$ leetcode random --pick")} Random + create file
2072
- ${chalk18.cyan("$ leetcode r -d easy --pick")} Random easy + file
2428
+ ${chalk20.yellow("Examples:")}
2429
+ ${chalk20.cyan("$ leetcode random")} Get any random problem
2430
+ ${chalk20.cyan("$ leetcode random -d medium")} Random medium problem
2431
+ ${chalk20.cyan("$ leetcode random -t array")} Random array problem
2432
+ ${chalk20.cyan("$ leetcode random --pick")} Random + create file
2433
+ ${chalk20.cyan("$ leetcode r -d easy --pick")} Random easy + file
2073
2434
  `).action(randomCommand);
2074
2435
  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
- ${chalk18.yellow("Examples:")}
2076
- ${chalk18.cyan("$ leetcode pick 1")} Pick by problem ID
2077
- ${chalk18.cyan("$ leetcode pick two-sum")} Pick by problem slug
2078
- ${chalk18.cyan("$ leetcode pick 1 -l python3")} Pick with specific language
2079
- ${chalk18.cyan("$ leetcode pick 1 --no-open")} Create file without opening
2080
- ${chalk18.cyan("$ leetcode p 412")} Short alias
2436
+ ${chalk20.yellow("Examples:")}
2437
+ ${chalk20.cyan("$ leetcode pick 1")} Pick by problem ID
2438
+ ${chalk20.cyan("$ leetcode pick two-sum")} Pick by problem slug
2439
+ ${chalk20.cyan("$ leetcode pick 1 -l python3")} Pick with specific language
2440
+ ${chalk20.cyan("$ leetcode pick 1 --no-open")} Create file without opening
2441
+ ${chalk20.cyan("$ leetcode p 412")} Short alias
2081
2442
 
2082
- ${chalk18.gray("Files are organized by: workDir/Difficulty/Category/")}
2443
+ ${chalk20.gray("Files are organized by: workDir/Difficulty/Category/")}
2083
2444
  `).action(async (id, options) => {
2084
2445
  await pickCommand(id, options);
2085
2446
  });
2086
2447
  program.command("pick-batch <ids...>").description("Generate solution files for multiple problems").option("-l, --lang <language>", "Programming language for the solutions").addHelpText("after", `
2087
- ${chalk18.yellow("Examples:")}
2088
- ${chalk18.cyan("$ leetcode pick-batch 1 2 3")} Pick problems 1, 2, and 3
2089
- ${chalk18.cyan("$ leetcode pick-batch 1 2 3 -l py")} Pick with Python
2448
+ ${chalk20.yellow("Examples:")}
2449
+ ${chalk20.cyan("$ leetcode pick-batch 1 2 3")} Pick problems 1, 2, and 3
2450
+ ${chalk20.cyan("$ leetcode pick-batch 1 2 3 -l py")} Pick with Python
2090
2451
  `).action(batchPickCommand);
2091
2452
  program.command("test <file>").alias("t").description("Test solution against sample test cases").option("-c, --testcase <testcase>", "Custom test case").addHelpText("after", `
2092
- ${chalk18.yellow("Examples:")}
2093
- ${chalk18.cyan("$ leetcode test 1")} Test by problem ID
2094
- ${chalk18.cyan("$ leetcode test two-sum")} Test by problem slug
2095
- ${chalk18.cyan("$ leetcode test ./path/to/file.py")} Test by file path
2096
- ${chalk18.cyan('$ leetcode test 1 -c "[1,2]\\n3"')} Test with custom case
2097
- ${chalk18.cyan("$ leetcode t 412")} Short alias
2453
+ ${chalk20.yellow("Examples:")}
2454
+ ${chalk20.cyan("$ leetcode test 1")} Test by problem ID
2455
+ ${chalk20.cyan("$ leetcode test two-sum")} Test by problem slug
2456
+ ${chalk20.cyan("$ leetcode test ./path/to/file.py")} Test by file path
2457
+ ${chalk20.cyan('$ leetcode test 1 -c "[1,2]\\n3"')} Test with custom case
2458
+ ${chalk20.cyan("$ leetcode t 412")} Short alias
2098
2459
 
2099
- ${chalk18.gray("Testcases use \\n to separate multiple inputs.")}
2460
+ ${chalk20.gray("Testcases use \\n to separate multiple inputs.")}
2100
2461
  `).action(testCommand);
2101
2462
  program.command("submit <file>").alias("x").description("Submit solution to LeetCode").addHelpText("after", `
2102
- ${chalk18.yellow("Examples:")}
2103
- ${chalk18.cyan("$ leetcode submit 1")} Submit by problem ID
2104
- ${chalk18.cyan("$ leetcode submit two-sum")} Submit by problem slug
2105
- ${chalk18.cyan("$ leetcode submit ./path/to/file.py")} Submit by file path
2106
- ${chalk18.cyan("$ leetcode x 412")} Short alias
2463
+ ${chalk20.yellow("Examples:")}
2464
+ ${chalk20.cyan("$ leetcode submit 1")} Submit by problem ID
2465
+ ${chalk20.cyan("$ leetcode submit two-sum")} Submit by problem slug
2466
+ ${chalk20.cyan("$ leetcode submit ./path/to/file.py")} Submit by file path
2467
+ ${chalk20.cyan("$ leetcode x 412")} Short alias
2107
2468
  `).action(submitCommand);
2108
2469
  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
- ${chalk18.yellow("Examples:")}
2110
- ${chalk18.cyan("$ leetcode submissions 1")} View submissions for problem
2111
- ${chalk18.cyan("$ leetcode submissions 1 -n 5")} Show last 5 submissions
2112
- ${chalk18.cyan("$ leetcode submissions 1 --last")} Show last accepted submission
2113
- ${chalk18.cyan("$ leetcode submissions 1 --download")} Download last accepted code
2470
+ ${chalk20.yellow("Examples:")}
2471
+ ${chalk20.cyan("$ leetcode submissions 1")} View submissions for problem
2472
+ ${chalk20.cyan("$ leetcode submissions 1 -n 5")} Show last 5 submissions
2473
+ ${chalk20.cyan("$ leetcode submissions 1 --last")} Show last accepted submission
2474
+ ${chalk20.cyan("$ leetcode submissions 1 --download")} Download last accepted code
2114
2475
  `).action(submissionsCommand);
2115
- program.command("stat [username]").description("Show user statistics").addHelpText("after", `
2116
- ${chalk18.yellow("Examples:")}
2117
- ${chalk18.cyan("$ leetcode stat")} Show your statistics
2118
- ${chalk18.cyan("$ leetcode stat lee215")} Show another user's stats
2119
- `).action(statCommand);
2476
+ 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
+ ${chalk20.yellow("Options Explained:")}
2478
+ ${chalk20.cyan("-c, --calendar")} Shows a table of your weekly submissions and active days
2479
+ for the past 12 weeks. Useful for tracking consistency.
2480
+
2481
+ ${chalk20.cyan("-s, --skills")} Shows how many problems you solved per topic tag,
2482
+ grouped by difficulty (Fundamental/Intermediate/Advanced).
2483
+ Helps identify your strong and weak areas.
2484
+
2485
+ ${chalk20.cyan("-t, --trend")} Shows a bar chart of daily submissions for the past week.
2486
+ Visualizes your recent coding activity day by day.
2487
+
2488
+ ${chalk20.yellow("Examples:")}
2489
+ ${chalk20.cyan("$ leetcode stat")} Show basic stats (solved count, rank)
2490
+ ${chalk20.cyan("$ leetcode stat lee215")} Show another user's stats
2491
+ ${chalk20.cyan("$ leetcode stat -c")} Weekly activity table
2492
+ ${chalk20.cyan("$ leetcode stat -s")} Topic-wise breakdown
2493
+ ${chalk20.cyan("$ leetcode stat -t")} 7-day trend chart
2494
+ `).action((username, options) => statCommand(username, options));
2120
2495
  program.command("today").description("Show today's progress summary").addHelpText("after", `
2121
- ${chalk18.yellow("Examples:")}
2122
- ${chalk18.cyan("$ leetcode today")} Show streak, solved, and daily challenge
2496
+ ${chalk20.yellow("Examples:")}
2497
+ ${chalk20.cyan("$ leetcode today")} Show streak, solved, and daily challenge
2123
2498
  `).action(todayCommand);
2124
2499
  program.command("bookmark <action> [id]").description("Manage problem bookmarks").addHelpText("after", `
2125
- ${chalk18.yellow("Actions:")}
2126
- ${chalk18.cyan("add <id>")} Bookmark a problem
2127
- ${chalk18.cyan("remove <id>")} Remove a bookmark
2128
- ${chalk18.cyan("list")} List all bookmarks
2129
- ${chalk18.cyan("clear")} Clear all bookmarks
2500
+ ${chalk20.yellow("Actions:")}
2501
+ ${chalk20.cyan("add <id>")} Bookmark a problem
2502
+ ${chalk20.cyan("remove <id>")} Remove a bookmark
2503
+ ${chalk20.cyan("list")} List all bookmarks
2504
+ ${chalk20.cyan("clear")} Clear all bookmarks
2130
2505
 
2131
- ${chalk18.yellow("Examples:")}
2132
- ${chalk18.cyan("$ leetcode bookmark add 1")} Bookmark problem 1
2133
- ${chalk18.cyan("$ leetcode bookmark remove 1")} Remove bookmark
2134
- ${chalk18.cyan("$ leetcode bookmark list")} List all bookmarks
2506
+ ${chalk20.yellow("Examples:")}
2507
+ ${chalk20.cyan("$ leetcode bookmark add 1")} Bookmark problem 1
2508
+ ${chalk20.cyan("$ leetcode bookmark remove 1")} Remove bookmark
2509
+ ${chalk20.cyan("$ leetcode bookmark list")} List all bookmarks
2135
2510
  `).action(bookmarkCommand);
2136
2511
  program.command("note <id> [action]").description("View or edit notes for a problem").addHelpText("after", `
2137
- ${chalk18.yellow("Actions:")}
2138
- ${chalk18.cyan("edit")} Open notes in editor (default)
2139
- ${chalk18.cyan("view")} Display notes in terminal
2512
+ ${chalk20.yellow("Actions:")}
2513
+ ${chalk20.cyan("edit")} Open notes in editor (default)
2514
+ ${chalk20.cyan("view")} Display notes in terminal
2140
2515
 
2141
- ${chalk18.yellow("Examples:")}
2142
- ${chalk18.cyan("$ leetcode note 1")} Edit notes for problem 1
2143
- ${chalk18.cyan("$ leetcode note 1 edit")} Edit notes (explicit)
2144
- ${chalk18.cyan("$ leetcode note 1 view")} View notes in terminal
2516
+ ${chalk20.yellow("Examples:")}
2517
+ ${chalk20.cyan("$ leetcode note 1")} Edit notes for problem 1
2518
+ ${chalk20.cyan("$ leetcode note 1 edit")} Edit notes (explicit)
2519
+ ${chalk20.cyan("$ leetcode note 1 view")} View notes in terminal
2145
2520
  `).action(notesCommand);
2146
- program.command("config").description("View or set configuration").option("-l, --lang <language>", "Set default programming language").option("-e, --editor <editor>", "Set editor command").option("-w, --workdir <path>", "Set working directory for solutions").option("-i, --interactive", "Interactive configuration").addHelpText("after", `
2147
- ${chalk18.yellow("Examples:")}
2148
- ${chalk18.cyan("$ leetcode config")} View current config
2149
- ${chalk18.cyan("$ leetcode config -l python3")} Set language to Python
2150
- ${chalk18.cyan('$ leetcode config -e "code"')} Set editor to VS Code
2151
- ${chalk18.cyan("$ leetcode config -w ~/leetcode")} Set solutions folder
2152
- ${chalk18.cyan("$ leetcode config -i")} Interactive setup
2521
+ program.command("sync").description("Sync solutions to Git repository").addHelpText("after", `
2522
+ ${chalk20.yellow("Examples:")}
2523
+ ${chalk20.cyan("$ leetcode sync")} Sync all solutions to remote
2524
+ `).action(syncCommand);
2525
+ 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
+ ${chalk20.yellow("Examples:")}
2527
+ ${chalk20.cyan("$ leetcode config")} View current config
2528
+ ${chalk20.cyan("$ leetcode config -l python3")} Set language to Python
2529
+ ${chalk20.cyan('$ leetcode config -e "code"')} Set editor to VS Code
2530
+ ${chalk20.cyan("$ leetcode config -w ~/leetcode")} Set solutions folder
2531
+ ${chalk20.cyan("$ leetcode config -r https://...")} Set git repository
2532
+ ${chalk20.cyan("$ leetcode config -i")} Interactive setup
2153
2533
 
2154
- ${chalk18.gray("Supported languages: typescript, javascript, python3, java, cpp, c, csharp, go, rust, kotlin, swift")}
2534
+ ${chalk20.gray("Supported languages: typescript, javascript, python3, java, cpp, c, csharp, go, rust, kotlin, swift")}
2155
2535
  `).action(async (options) => {
2156
2536
  if (options.interactive) {
2157
2537
  await configInteractiveCommand();
@@ -2163,8 +2543,8 @@ program.showHelpAfterError("(add --help for additional information)");
2163
2543
  program.parse();
2164
2544
  if (!process.argv.slice(2).length) {
2165
2545
  console.log();
2166
- console.log(chalk18.bold.cyan(" \u{1F525} LeetCode CLI"));
2167
- console.log(chalk18.gray(" A modern command-line interface for LeetCode"));
2546
+ console.log(chalk20.bold.cyan(" \u{1F525} LeetCode CLI"));
2547
+ console.log(chalk20.gray(" A modern command-line interface for LeetCode"));
2168
2548
  console.log();
2169
2549
  program.outputHelp();
2170
2550
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@night-slayer18/leetcode-cli",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "A modern LeetCode CLI built with TypeScript",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",