leetcli
A command-line interface for LeetCode. Submit and solve problems locally.
view project →Motivation
I wanted to solve LeetCode problems without leaving my terminal. The web editor is fine, but I'm faster in my own editor with my own keybindings and tools. The goal was a CLI that could fetch problems, let me write solutions locally, and submit them directly — all without opening a browser.
Architecture
The CLI is built in Go and communicates with LeetCode's GraphQL API. It handles session authentication, problem fetching, code submission, and result polling.
type Client struct {
session string
csrfToken string
httpClient *http.Client
}
type Problem struct {
ID int
Title string
Slug string
Difficulty string
Content string
}The workflow is straightforward: leetcli fetch <problem> pulls the problem description and generates a solution template in your preferred language. You write your solution locally, then leetcli submit sends it off and streams the result back.
func (c *Client) Submit(slug string, lang string, code string) (*Result, error) {
payload := map[string]string{
"lang": lang,
"question_id": slug,
"typed_code": code,
}
resp, err := c.post("/problems/"+slug+"/submit/", payload)
if err != nil {
return nil, err
}
return c.pollResult(resp.SubmissionID)
}Challenges
The trickiest part was handling LeetCode's authentication flow. Sessions expire, CSRF tokens rotate, and rate limiting kicks in if you're not careful. I ended up implementing automatic session refresh and request retrying with exponential backoff.
Parsing problem descriptions from HTML into clean terminal output also required some work — stripping tags while preserving code blocks, examples, and constraints in a readable format.
Takeaways
This was a fun exercise in building a developer tool that scratches your own itch. The GraphQL API exploration taught me a lot about reverse-engineering undocumented APIs, and the project became part of my daily workflow.