Push a LeetCode solution to GitHub without leaving your terminal.
No clone. No working tree. No cd. Type lcpush from wherever you happen to
be, answer three prompts, and your solution is committed.
Solving the problem was never the annoying part. The annoying part was
everything after: find the solutions repo, cd into it, remember whether it's
0001-two-sum.py or 1-two-sum.py, paste, git add, write a commit message,
push. Thirty seconds of ceremony per problem, every time - enough friction that
I'd batch it up "for later" and then never do it.
So lcpush does the ceremony. It keeps the whole LeetCode problem set cached
locally so the question picker is instant, figures out the language from the
code itself, names the file the way it should be named, and pushes through the
GitHub API. There's no repo on disk to keep in sync, because there's no repo on
disk at all.
$ lcpush
? Question: two su
1. Two Sum [Easy]
167. Two Sum II - Input Array Is Sorted [Medium]
653. Two Sum IV - Input is a BST [Easy]
1099. Two Sum Less Than K [Easy] 🔒
↑/↓ to move, Enter to select
✓ 1. Two Sum
? Solution source: [Clipboard] Editor Stdin
✓ Read 24 lines from clipboard
┌ Clipboard - 24 lines, 612 bytes, detected Python3
│ class Solution:
│ def twoSum(self, nums: List[int], target: int) -> List[int]:
│ seen = {}
│ … 18 lines hidden …
│ return []
└ ? Use this? [Y/n]
┌ Ready to push
│ File 0001-two-sum.py (24 lines)
│ Repo user/leetcode-solutions (main)
│ Message Add 1. Two Sum (Python3)
└ ? [Enter] push [m] edit message [M] edit in $EDITOR [n] cancel
→ Pushing 0001-two-sum.py to user/leetcode-solutions (main)
✓ https://github.com/user/leetcode-solutions/blob/main/0001-two-sum.py
Build from source with CMake (3.24+), a C++20 compiler, and libcurl
(preinstalled on macOS, libcurl4-openssl-dev or similar on Linux). All other
dependencies are pinned and fetched at configure time.
cmake -S . -B build -G Ninja
cmake --build build
cmake --install build # puts `lcpush` on your PATH (may need sudo)? GitHub repo to push to: user/leetcode-solutions
? GitHub token: **** # auto-detected from `gh auth token` if available
✓ Verified write access to user/leetcode-solutions
✓ Saved config to ~/.config/lcpush/config.toml
That's the only time you'll be asked. Every run after this goes straight to the question picker.
The token needs the repo scope (classic) or contents: read & write
(fine-grained). It's stored in the macOS
Keychain and never written to config.toml. On other platforms (no keyring
backend yet) it falls back to $GITHUB_TOKEN, gh auth token, or a 0600
file, and tells you which.
lcpush # the full interactive session
lcpush --refresh # force a re-fetch of the problem listThat's the whole surface. lcpush is deliberately interactive-only: one
command, three prompts, done. The problem list is cached locally, so the
picker opens instantly - when the cache goes stale it is refreshed in the
background while you type, never while you wait. Only the very first run
(no cache yet) fetches in the foreground.
lcpush config show # token is described, never printed
lcpush config set repo owner/name
lcpush config set branch main
lcpush config set path solutions/
lcpush config set commit.message_template "Add {id}. {title} ({language})"
lcpush config set commit.prompt confirm # confirm | always | never
lcpush config set cache.problems_ttl_days 7
lcpush config reset-token
lcpush config path # where config and cache liveConfig lives at $XDG_CONFIG_HOME/lcpush/config.toml by default at
~/.config/lcpush/config.toml with mode 0600. The problem-set cache lives at
$XDG_CACHE_HOME/lcpush/problems.json.
Template variables: {id} {padded_id} {title} {slug} {language}
{ext} {difficulty} {filename} {path} {lines}. Misspell one and it
warns once and falls back to the built-in default - a typo in your config
shouldn't take the tool down.
The GraphQL endpoint happily accepts limit: 500 and returns 200 OK - and
then gives you 100 results. If you paginate by advancing skip by the limit
you requested, you silently skip 400 problems per page and end up with 803
of the 4003 that exist, with no error anywhere to tell you. lcpush advances
by the length of the page it actually got back. There's a regression test
pinned to that behaviour, because I'd rather not rediscover it.
| Module | Responsibility |
|---|---|
cli.cpp |
CLI11 flags, config subcommands, top-level error handling |
session.cpp |
The run flow, question to push |
onboarding.cpp |
First-run setup, token resolution |
config.cpp paths.cpp tokens.cpp keyring_*.cpp |
Persistence, XDG paths, keyring |
problems.cpp search.cpp picker.cpp |
Problem set, offline fuzzy match, picker UI |
clipboard.cpp editor.cpp |
Solution sources |
detect.cpp plausibility.cpp solution.cpp |
Language detection, scoring, validation |
render.cpp github.cpp ui.cpp |
Paths, commit messages, API, terminal output |
term/ http/ util/ |
Raw mode + key decoding, libcurl transport, helpers |
cmake --preset strict
cmake --build --preset strict
ctest --preset strict # 200+ testsUse the sanitizers preset for AddressSanitizer and UndefinedBehaviorSanitizer.
The prompt widgets are tested for real, driven through scripted key bytes
rather than mocked out - so "typing two su filters to Two Sum" and
"Ctrl-U clears the pre-filled commit message" are assertions, not hopes.
tests/data/parity.json holds vectors generated from the original Python
implementation, and a replay suite pins the C++ fuzzy-search ranking,
language-detection scores, and clipboard plausibility to them exactly.
Deliberately out of scope for v1, in rough order of how likely I am to change my mind:
- An auto-generated
README.mdindex table in the solutions repo - Grouping by difficulty or topic tag (the
pathconfig field is the seam) lcpush log- local history of what's been pushed- Complexity/approach notes appended as a header comment
- Multiple languages for one question in a single invocation
Not planned: LeetCode account auth, scraping your accepted submissions, or any host that isn't GitHub.