A desktop tool for pushing local projects to GitHub — cleanly, without the junk.
v2.0.0 — the push engine now talks to GitHub directly from your machine via git2 (libgit2). No files are ever uploaded through an intermediary. The backend only prepares the push (one-time token + repo facts) and records history; all your code travels straight from your computer to GitHub.
Built by Lidprex Labs. Parent Company Lidprex .
You pick a local folder, you pick a GitHub repo from your account, you click push. LidPush handles the rest: it strips node_modules, build artifacts, caches, and any detected secrets before anything hits GitHub. Your original folder stays untouched — the cleaning happens on a temporary copy.
Two upload modes:
- Update (safe, default) — the new commit is built on top of the remote branch's latest commit (clean fast-forward). Files that exist remotely but not locally are kept; only same-name files are overwritten. No force push is ever needed for normal updates.
- Replace (destructive) — resets the branch to a fresh root commit and force-pushes your project over the remote branch. Use it when you want the remote to mirror your local folder exactly.
The problem it solves is real. A project that starts at 80 files can quietly balloon to 150+ after a few installs and builds. GitHub's file limits become a problem, and more importantly, you end up pushing gigabytes of dependency folders that have no business being in version control. LidPush catches all of that before the push happens — a live scanner previews LOC, sizes, skipped files, and detected secrets before you commit to the push.
| v1.1.0 (old) | v2.0.0 (this) | |
|---|---|---|
| Push engine | Direct git2 push from your machine | Rebuilt: fetches the remote branch head first and bases the commit on it → clean fast-forward, no force dialog on normal updates; force is honored correctly in replace mode |
| Upload modes | Force push only | Update (safe fast-forward, keeps remote-only files) + Replace (reset branch + force push) |
| Secrets protection | Warning only | Blocked before upload + live pre-push preview (LOC, clean size, skipped dirs/files, flagged secrets) |
| Auth & sessions | Local-only | Hosted backend: GitHub OAuth, encrypted token storage, push history and activity stored in a database |
| Force Push dialog | Recurring loop | Fixed — normal pushes are clean fast-forwards |
| Deployment | Single desktop app | Desktop app + hosted API + independent public website |
Key additions in v2.0.0:
- Update / Replace mode toggle with an explicit warning before destructive pushes.
- Pre-push scanner preview — see lines of code, clean project size, skipped folders, and blocked secrets before you commit to the push.
- Push history persisted server-side and displayed inside the app.
- Smarter cleaning — secrets are blocked, not just flagged.
| Layer | What |
|---|---|
| Desktop | Tauri v2 |
| Frontend | React 18 + TypeScript |
| Local relay | Axum (Rust, embedded inside the app, random port) |
| Remote backend | Fastify (Node.js/TypeScript) — hosted API service (private companion, not published) |
| Git engine | git2-rs (libgit2 — no Git install required) |
| Database | Neon PostgreSQL (users, tokens, push history, activity) |
| Auth | GitHub OAuth 2.0, negotiated through the backend |
The embedded Axum server starts on a random port every launch and requires a one-time secret header on every request. Nothing is accessible from outside the machine. The remote backend is the only other service the app talks to, and only over the endpoints it needs.
This repository contains only the desktop application and the public landing website:
src/+src-tauri/— the Tauri + React desktop appindex.html+*.html— the public site pages
The app talks to a hosted backend API service that is private and not published here. That's intentional: the backend owns all secrets (OAuth client secret, token encryption keys, database). Everything a normal user needs comes packaged in the app — install, sign in with GitHub, and push.
The desktop app connects to the official LidPush service by default — no configuration required. The public website lives at https://lidpush.onrender.com.
You'll need Rust (1.77+), Node.js (20+), and the Tauri CLI. Nothing else.
# Install Tauri CLI if you don't have it
cargo install tauri-cli --version "^2"
# Install frontend deps
npm install
# Dev mode (hot reload on both React and Rust changes)
npm run tauri dev
# Production build → outputs an NSIS installer under src-tauri/target/release/bundle/
npm run tauri buildFirst compile takes a while — Rust is building ~150 crates. After that, incremental builds on the Rust side take 5–15 seconds, and React changes reload instantly without recompiling Rust at all.
None required for you. LidPush is a client for a hosted service:
- The app talks to a backend API for GitHub OAuth, sessions, and push history. That backend is managed by LidPush and is not open-sourced — you never install or configure it.
- The release build connects to the official service automatically. All secrets (OAuth client, token encryption keys, database credentials) live server-side — never on your machine, never in this repository.
- Install the app, sign in with GitHub, push. That is the whole setup.
Icons go in src-tauri/icons/. Tauri expects specific filenames:
src-tauri/icons/
├── 32x32.png
├── 128x128.png
├── 128x128@2x.png
├── icon.ico ← Windows taskbar + titlebar
├── icon.icns ← macOS (ignore if Windows-only)
└── icon.png ← 512×512 source
The fastest way to generate all sizes from a single PNG:
# Install the Tauri icon generator
cargo install tauri-cli --version "^2" # already installed if you followed above
# Drop your 512×512 source image as icon.png in src-tauri/icons/
# then run:
npm run tauri icon src-tauri/icons/icon.pngThat command generates every required size and format automatically. If you skip this step, Tauri uses its own placeholder icon and the taskbar will show the default Tauri logo.
For the .ico file specifically (Windows titlebar), make sure it contains both 16×16 and 32×32 layers. Most icon converters online do this by default when you give them a PNG.
React UI → POST /push/upload (local relay, random port)
│
├─ verify local session → GET backend /api/push/prepare
│ backend checks the token against GitHub and returns:
│ { token, default_branch, repo_url, is_private, user_name, user_email }
│
├─ git2, entirely on this machine: temp copy → git init → add → commit
│ • Update mode: fetch remote HEAD, base the commit on it (fast-forward),
│ checkout remote files, then overlay the clean local copy
│ • Replace mode: fresh root commit + force refspec (+refs/heads/<branch>)
│
├─ push straight to github.com over HTTPS (x-access-token credential)
│
└─ POST backend /api/push/record (best-effort) → stored in push history DB
Because update mode bases the commit on the remote HEAD, normal pushes are always a clean fast-forward — no force push required. The "Force Push Required" dialog only appears in the rare case of divergent branches.
1. App opens the backend's GitHub authorize URL (desktop flow)
2. User authorizes → GitHub redirects to the public site (github-callback.html)
3. The page forwards the raw code to the backend /api/auth/github/exchange
4. Backend exchanges the code (secret stays server-side), creates a session,
returns the app via the local callback: lidpush://auth/callback?code=...&username=...
5. The app exchanges the one-time login code locally and stores its app token
in session.dat (local app data dir)
The real GitHub token stays encrypted on the backend (TOKEN_ENCRYPTION_KEY) and is handed to the app only as a short-lived push credential. The React side never sees GitHub tokens.
LidPush.exe
│
├── Tauri WebView
│ └── React app (the UI you see)
│ └── calls internal API via fetch to 127.0.0.1:RANDOM_PORT
│
└── Axum HTTP server (spawned at startup, random port)
├── /auth/url opens browser for GitHub OAuth
├── /auth/callback internal callback after desktop OAuth
├── /auth/status checks if the app is signed in
├── /auth/logout clears the local session
├── /github/repos lists your GitHub repos
├── /github/orgs lists your orgs
├── /push/upload full push pipeline (prepare → git2 → record)
├── /push/scan scans a folder (LOC, sizes, secrets) before pushing
├── /push/files lists files that would be pushed
├── /push/history reads push history from the backend DB
└── /health local health probe
The port is randomized on every launch. The React frontend learns the port at startup via a Tauri IPC call (invoke("get_server_port")). Every request also requires an x-lidpush-secret header containing a UUID generated fresh each launch. This means nothing on the machine can accidentally or maliciously call the internal API without knowing both values.
Before any push, LidPush scans the project and removes:
- Dependency folders:
node_modules,venv,.venv,vendor,Pods - Build output:
dist,build,target,out,bin,.next,.nuxt - Caches:
__pycache__,.pytest_cache,.gradle,.m2,.npm - Lock files:
package-lock.json,yarn.lock,pnpm-lock.yaml - OS junk:
.DS_Store,Thumbs.db,desktop.ini - Temp/compiled:
*.pyc,*.log,*.tmp,*.class,*.o
It also scans text files for secret patterns before the push: GitHub tokens, AWS keys, private key blocks, Stripe keys, Discord tokens, and generic API_KEY=... patterns. If anything matches, it's blocked from the upload and flagged in the results. In replace mode you can review the exact preview — LOC, clean file count, clean size, and secrets — before confirming.
This is the same logic as RepoPrep, rewritten in Rust. It's considerably faster on large repos.
The UI ships in five languages, switchable from the titlebar dropdown without restarting:
- English
- Arabic (العربية)
- Russian (Русский)
- Hindi (हिन्दी)
- Chinese (中文)
Translations live in src/i18n/index.ts. Adding a new language means adding one object to that file.
Apache-2.0 license © 2026 Lidprex Labs
LidPush is completely free and open-source. If it saves you time or helps your workflow, consider supporting the project:
Your support covers infrastructure costs and helps us build more privacy-first tools.