@bunsdev/cvnty
v2.0.0
Published
A modern and sleek CLI git management tool
Readme
git-cvnty
A modern, sleek terminal git client built with Ink 4 (React 18 + ESM). Fuzzy-filterable lists, inline diff previews, hunk-level staging, an interactive-rebase helper, a reflog safety net, and a built-in changelog viewer — all keyboard-first, all in your terminal.
█▀▀ █ ▀█▀ ▄▄ █▀▀ █ █ █▄ █ ▀█▀ █▄█
█▄█ █ █ █▄▄ ▀▄▀ █ ▀█ █ █
⎇ main ↑1 · Stage Changes
↑↓ navigate / filter Space stage/unstage h hunks x discard Esc backFeatures
Dashboard
- Working-tree panels — color-coded unstaged / staged / untracked / conflicted files
- Live commit graph —
git log --all --graphvisualization - Status header — branch, ahead/behind arrows (
↑2 ↓1), detached-HEAD badge,MERGING/REBASINGindicators, conflict count - One-key actions — push (
P), pull (p), fetch (f) run async with toast feedback; push auto-offers--set-upstreamwhen missing
Staging & committing
- Stage screen — toggle files with
Space/Enter, stage/unstage all witha, discard withx(confirmed), live diff preview of the highlighted file - Hunk-level staging — press
hon a file for agit add -pequivalent: step through hunks, stage or unstage each - Commit screen — staged-file summary, amend toggle (
Tab), warns when nothing is staged
Branches, history & more
- Branches — fuzzy-filterable list with tracking info; checkout (
Enter), create (n), delete (d, confirmed + force-delete flow for unmerged), merge (M), rename (R) - History browser — commit list → per-commit files with
+adds -dels→ full file diff, drill-down withEnter, back withEsc - Stashes — save (with message), pop, apply, drop (confirmed), patch preview
- Interactive rebase helper — pick a range, assign
pick/squash/fixup/dropper commit, execute without ever opening$EDITOR - Reflog / undo view — browse every HEAD move; recover with soft or hard reset (both confirmed)
- Remotes — list, add, remove, edit URLs
- Tags — list, create (annotated or lightweight), delete, push
- 📋 Changelog viewer — browse the local
CHANGELOG.mdor fetch one for any npm package / GitHub repo (lazy-loaded, keeps startup fast)
UX
- Unified interaction model —
Enterconfirms,Escgoes back,/filters, everywhere - Context-sensitive hint bar — visible keybindings that update per screen
- Fuzzy filtering — press
/in any list and type; matches are ranked and highlighted - Confirmation modals for every destructive action (delete, discard, drop, force ops, resets)
- Non-blocking toasts for success/error — no silent failures, no frozen UI
- Event-driven refresh — data reloads after each operation (no background polling)
- Graceful edge cases — non-git directories, empty repos, detached HEAD, merge conflicts, missing remotes/upstreams
- Responsive layout — adapts to terminal resize, degrades gracefully under 80 columns
Scripting (non-interactive subcommands)
cvnty status # colored working-tree summary (alias: st)
cvnty branch # branches with tracking info (alias: br)
cvnty log -n 5 --all # recent commits
cvnty commit -m "fix: message"
cvnty push · pull · stash · tag · remoteSubcommands never load React/Ink, print plain text (respects NO_COLOR), and exit non-zero on failure — safe for scripts and CI.
Requirements
- Node.js ≥ 18
- Git ≥ 2.23 (uses
git restore, porcelain v2)
Installation
npm install --global git-cvntyThis exposes the cvnty command (with a git-cvnty alias). Also published under the @bunsdev scope: @bunsdev/cvnty, @bunsdev/cvnt.
Or run without installing:
npx git-cvntyUsage
cvnty # interactive TUI (run inside a git repo)
cvnty --no-logo # hide the ASCII logo
cvnty <command> # non-interactive subcommand (see above)
cvnty --helpKeybindings
| Key | Action |
|-----|--------|
| ↑ ↓ / PgUp PgDn | Navigate lists, scroll diffs |
| / | Fuzzy-filter the focused list (Esc clears) |
| Enter | Select / confirm |
| Esc | Back / cancel |
| ? | Help screen (full reference) |
| q / Ctrl+C | Quit |
Dashboard shortcuts: s stage · c commit · b branches · l history · t stashes · m remotes · g tags · u reflog · i rebase · v changelog · P push · p pull · f fetch · r refresh.
Each screen shows its own hint bar — you never have to memorize these.
Configuration
git-cvnty reads ~/.config/cvnty/config.json (honours $XDG_CONFIG_HOME). Everything is optional:
{
"theme": "solar",
"themeOverrides": { "accent": "#ff8800" },
"settings": {
"showLogo": false,
"graphLines": 30,
"logLines": 100,
"includeUntrackedInStash": true
},
"keybinds": { "quit": "x", "filter": "f" }
}theme— preset name:cyan(default),magenta,mono,solarthemeOverrides— override any color (Ink color names or hex):accent,highlight,success,error,warning,added,removed,modified,untracked,conflicted,tree,border, …settings—showLogo,graphLines,logLines,includeUntrackedInStashkeybinds— remap the single-char dashboard shortcuts (quit,help,filter,refresh,stage,commit,branches,history,stashes,remotes,tags,reflog,push,pull,fetch)
Invalid config files are ignored safely (defaults apply).
Changelog Viewer
Two sources:
- Local — reads the first of
CHANGELOG.md/HISTORY.md/CHANGES.md/ … in your repo root and parses Keep a Changelog sections into a per-version sidebar. - Remote — enter
react,[email protected],facebook/react, orowner/[email protected]; resolves installed packages vianode_modules, then fetchesCHANGELOG.mdor GitHub Releases.
Building from source
git clone https://github.com/BunsDev/git-cvnty
cd git-cvnty
npm install
npm run build # esbuild → dist/cli.js
npm test # jest (unit + ink-testing-library interaction tests)
npm run dev # rebuild on changeArchitecture
git-cvnty/
├── cli.js # Entry: subcommand routing + lazy TUI bootstrap
├── build.js # esbuild config (ESM, JSX-in-.js, node18)
├── jest.config.js # ESM-native jest via esbuild transformer
├── src/
│ ├── app.js # Screen registry + router + store provider
│ ├── config.js # ~/.config/cvnty/config.json loader, theme presets
│ ├── cli-run.js # Non-interactive subcommands (no React/Ink)
│ ├── changelog.js # Changelog parsing + GitHub/npm fetching
│ ├── commands/ # Async git ops (execFile, no shell) with typed GitError
│ │ ├── exec.js # runGit + GitError kinds (NO_REMOTE, MERGE_CONFLICT, …)
│ │ ├── repo.js status.js branches.js commits.js diff.js
│ │ ├── remote.js stash.js tags.js rebase.js
│ │ └── index.js # barrel
│ ├── utils/
│ │ ├── parsers.js # Pure parsers: porcelain v2, refs, diffs/hunks, reflog …
│ │ └── fuzzy.js # Scored fuzzy matching for lists
│ ├── state/store.js # Reducer store: screen · modals · toasts · refresh tick
│ ├── hooks/
│ │ ├── useKeymap.js # Declarative bindings → input handling + hint bar
│ │ ├── useGitData.js # Event-driven queries (useGitStatus, useBranches, …)
│ │ ├── useAsyncTask.js # Async op lifecycle for the UI
│ │ └── useTerminalSize.js
│ ├── components/ # Pure presentational: FilterList, DiffView, Modals,
│ │ └── … # Toasts, HintBar, Frame, Panel, ChangelogDialog
│ └── screens/ # Composed views: Dashboard, Stage, Commit, Branches,
│ └── … # Stash, History, Remotes, Tags, Reflog, Rebase, Help
├── test/ # 100+ tests: parsers, commands (real temp repos),
│ └── … # config, CLI e2e, full-screen interaction tests
└── dist/cli.js # Bundled output (npm run build)Design principles
src/commands/are pure async utilities — every failure is aGitErrorwith a machine-readablekind, so screens can offer the right fix (e.g. push with no upstream → auto--set-upstreamretry).- Screens declare keybindings with
useKeymap; the same declaration renders the hint bar, so docs and behavior can't drift. - All git mutations bump a store-level refresh tick; data hooks refetch — no
setIntervalpolling. - Destructive operations always go through the promise-based
confirm()modal.
License
MIT © BunsDev
