megit-app 0.1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,88 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this
6
+ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). While the
7
+ version is below 1.0.0, the config file format (`~/.config/megit/config.json`), the CLI
8
+ surface, and the HTTP API may change in any minor release.
9
+
10
+ ## [Unreleased]
11
+
12
+ ## [0.1.0] - 2026-07-28
13
+
14
+ First public release.
15
+
16
+ ### Graph
17
+
18
+ - Commit graph with per-branch lanes and colours, greedy top-down lane assignment, and reserved leftmost lanes so WIP and stash connectors get straight runs into HEAD
19
+ - The checked-out branch's path is drawn thicker and highlighted
20
+ - Merge edges bulge around the lanes they cross rather than cutting through them
21
+ - Commits page in 200 at a time, loading more on demand
22
+ - Stash entries render as their own rows, dot-connected to the commit they were taken from
23
+ - Sticky WIP row at the top of the list whenever the worktree is dirty
24
+ - Resizable ref / graph / message columns, with widths persisted
25
+ - Gravatar avatars with initials fallback
26
+
27
+ ### Diffs
28
+
29
+ - Unified and split (side-by-side) views
30
+ - Syntax highlighting and word-level intra-line highlighting
31
+ - Collapsed context expandable a hunk at a time
32
+ - Per-file "Viewed" checkbox
33
+ - Merge commits diff against their first parent
34
+ - Images diff visually instead of as binary noise
35
+ - Untracked files diff via `git diff --no-index`
36
+
37
+ ### Search
38
+
39
+ - <kbd>⌘F</kbd> find bar filtering the loaded rows as you type, matching commit message, author name, email, hash prefix, or ref name — no request, and immune to going stale across a background refresh
40
+ - <kbd>Enter</kbd> / <kbd>Shift+Enter</kbd> step through matches, wrapping at both ends
41
+ - Full-history search on demand: three `git log` passes (message, author, hash) unioned into one date-ordered list, capped at 500 results, with the query treated as a literal so a typed `(` or `.` is not a regex
42
+ - A match below the loaded window pulls the graph down to it
43
+
44
+ ### Working tree
45
+
46
+ - Stage, unstage, and discard individual files
47
+ - Commit message composer with staged / unstaged sections and counts
48
+ - Amend the last commit's message
49
+ - Non-ASCII paths handled correctly (`core.quotePath=false`, NUL-delimited output)
50
+
51
+ ### Git operations
52
+
53
+ - Toolbar: pull (fast-forward only), push, create branch, stash all, pop latest stash, undo last commit (soft reset)
54
+ - Ref chips: checkout, create branch here, rename, delete, set upstream, merge, rebase, delete tag, copy name, copy GitHub link
55
+ - Commit rows: checkout, cherry-pick, revert, reset (soft / mixed / hard), copy hash, copy GitHub link
56
+ - Checkout auto-stashes a dirty worktree first
57
+ - Destructive actions are marked, and hidden where they would be meaningless
58
+
59
+ ### Shell
60
+
61
+ - Full PTY terminal (xterm.js + node-pty) opened in the active repository, lazy-loaded so it costs nothing until used
62
+ - Sessions survive panel hides and tab switches, with a replay buffer on reattach
63
+
64
+ ### App
65
+
66
+ - Multiple repositories in draggable tabs
67
+ - Light and dark themes
68
+ - Auto-refresh: the server watches each open repo (`fs.watch`, filtered and debounced) and pushes updates over SSE
69
+ - Keyboard navigation through rows, plus shortcuts for refresh, terminal, theme, and commit
70
+ - Toast notifications for errors
71
+
72
+ ### Security
73
+
74
+ - Server binds `127.0.0.1` only
75
+ - `Host` header pinned to loopback names, closing DNS-rebinding access
76
+ - WebSocket `Origin` checked before handing out a PTY
77
+ - Repositories are only reachable once registered in `~/.config/megit/config.json`
78
+ - Client-supplied revs whitelisted rather than escaped, so a leading-dash rev cannot smuggle in a git option
79
+ - Git invocations cannot block on credential prompts
80
+
81
+ ### Platforms
82
+
83
+ - macOS (arm64, x64): full support, and the only platform verified by hand
84
+ - Windows (arm64, x64): builds and runs, but untested on real hardware — recursive `fs.watch` crashes the test worker there, so auto-refresh is not covered by CI
85
+ - Linux: everything except the terminal — `node-pty` is an `optionalDependency` with no Linux prebuild, and the terminal button is hidden when it is unavailable
86
+
87
+ [Unreleased]: https://github.com/vuongvu1/megit/compare/v0.1.0...HEAD
88
+ [0.1.0]: https://github.com/vuongvu1/megit/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hoang Vuong Vu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # <img src="public/logo.svg" width="28"/> megit
2
+
3
+ A git repository viewer that runs in your browser. Commit graph with real branch lanes, diffs including your uncommitted work, stashes, staging, search, and a shell — pointed at as many local repos as you like, each in its own tab.
4
+
5
+ It writes, too: stage/unstage/discard, commit and amend, branch and tag create/delete, stash push/pop/drop, checkout (with auto-stash when the worktree is dirty), plus revert, reset, cherry-pick, merge, rebase, pull and push.
6
+
7
+ Nothing leaves your machine: the server binds `127.0.0.1`, shells out to your own `git`, and has no account, telemetry, or network dependency beyond optional Gravatar avatars.
8
+
9
+ ![megit — commit graph with branch lanes, stash rows and the sticky WIP row](docs/graph-dark.png)
10
+
11
+ ## Install
12
+
13
+ Requires Node ≥ 24.
14
+
15
+ ```bash
16
+ npx megit-app # reopen your last session
17
+ ```
18
+
19
+ Installed globally (`npm i -g megit-app`), the command is just `megit`.
20
+
21
+ `PORT` picks the port (default 3411). The server opens your browser at it.
22
+
23
+ ## Features
24
+
25
+ ### Commit graph with lanes
26
+
27
+ Branches get their own colour and lane. The checked-out branch's path is drawn thicker so you can follow it at a glance, and merges bulge around the lanes they cross instead of cutting through them. Commits page in 200 at a time and load more on demand, so opening a repository never waits on the full history.
28
+
29
+ Ref chips sit in their own resizable column to the left — local branches, remotes, and tags — and the graph column and message column can be dragged to whatever widths suit the repo.
30
+
31
+ ### Light and dark themes
32
+
33
+ Toggle with the switch in the toolbar or <kbd>⌘</kbd><kbd>⇧</kbd><kbd>0</kbd>. The choice persists.
34
+
35
+ ![megit in the light theme](docs/graph-light.png)
36
+
37
+ ### Uncommitted work is part of the graph
38
+
39
+ A sticky WIP row sits at the top of the list whenever the worktree is dirty, connected into HEAD like any other node. Click it to stage, unstage, or discard individual files, write a message, and commit — staged and unstaged changes are separate collapsible sections with counts.
40
+
41
+ ![the WIP row expanded into staged and unstaged sections with a commit composer](docs/wip-staging.png)
42
+
43
+ Stashes appear as their own rows, attached to the commit they were taken from with a dotted connector, and can be popped, deleted, or retitled in place.
44
+
45
+ ### Diffs
46
+
47
+ Click a commit to see its changed files, then a file to see the diff. Syntax highlighting, word-level intra-line highlighting, collapsed context you can expand a hunk at a time, and a per-file "Viewed" checkbox.
48
+
49
+ **Unified:**
50
+
51
+ ![unified diff with an expandable hunk gap](docs/diff-unified.png)
52
+
53
+ **Split**, side by side:
54
+
55
+ ![split diff with word-level highlighting](docs/diff-side-by-side.png)
56
+
57
+ Merge commits diff against their first parent. Images diff visually rather than as binary noise. Untracked files diff too, via `git diff --no-index`.
58
+
59
+ ### Commit detail
60
+
61
+ Author and committer are shown separately when they differ — including the dates, which is the bit most tools hide. The changed-file list toggles between a flat path list and a directory tree.
62
+
63
+ ![commit detail panel showing author, committer and changed files](docs/commit-detail.png)
64
+
65
+ ### Search
66
+
67
+ <kbd>⌘</kbd><kbd>F</kbd> opens a find bar that filters the rows already loaded, as you type — matching commit message, author name, email, hash prefix, or ref name. That costs no request and can't go stale when the graph refreshes underneath you. <kbd>↵</kbd> and <kbd>⇧</kbd><kbd>↵</kbd> walk the matches, wrapping at the ends the way a find bar should.
68
+
69
+ If what you want is further back than the rows you've loaded, the globe button re-runs the same query as a full-history `git log` search. Because git ANDs its commit-limiting options, "message OR author OR hash" is three searches unioned into one date-ordered list, capped at 500 results — the counter shows `12 of 340 · all`, with a `+` when the cap truncated it. A match below the loaded window pulls the graph down to it.
70
+
71
+ ### A real shell, in the repo
72
+
73
+ <kbd>⌘</kbd><kbd>J</kbd> opens a full PTY already `cd`'d into the active repository — your shell, your prompt, your aliases. It survives panel hides and tab switches, and <kbd>⌘</kbd><kbd>K</kbd> clears it. xterm.js is lazy-loaded, so it costs nothing until you open it.
74
+
75
+ ![terminal panel open below the graph, running git log](docs/terminal.png)
76
+
77
+ ### Git operations
78
+
79
+ megit started as a viewer, but the common operations are here:
80
+
81
+ - **Toolbar** — pull (fast-forward only), push, create branch, stash all, pop latest stash, undo last commit (soft reset, keeps changes staged)
82
+ - **Ref chips** — checkout, create branch here, rename, delete, set upstream, merge, rebase, delete tag, copy name, copy GitHub link
83
+ - **Commit rows** — checkout, cherry-pick, revert, reset (soft / mixed / hard), copy hash, copy GitHub link
84
+ - **Files** — stage, unstage, discard, amend the last commit's message
85
+
86
+ Checkout auto-stashes a dirty worktree first. Destructive items are marked as such and are hidden where they'd be meaningless.
87
+
88
+ ### Auto-refresh
89
+
90
+ The server watches each open repository (`fs.watch`, filtered and debounced) and pushes changes to the browser over SSE. Commit in your terminal and the graph updates within about a second. <kbd>r</kbd> forces a refresh if you want one.
91
+
92
+ ### Keyboard
93
+
94
+ | Key | Action |
95
+ | -------------------------------------------------------- | ---------------------------------- |
96
+ | <kbd>↑</kbd> <kbd>↓</kbd> <kbd>Home</kbd> <kbd>End</kbd> | move through rows |
97
+ | <kbd>⌘</kbd><kbd>F</kbd> | search commits |
98
+ | <kbd>↵</kbd> / <kbd>⇧</kbd><kbd>↵</kbd> | next / previous match |
99
+ | <kbd>r</kbd> | refresh |
100
+ | <kbd>⌘</kbd><kbd>J</kbd> | toggle terminal |
101
+ | <kbd>⌘</kbd><kbd>K</kbd> | clear terminal |
102
+ | <kbd>⌘</kbd><kbd>⇧</kbd><kbd>0</kbd> | toggle theme |
103
+ | <kbd>⌘</kbd><kbd>↵</kbd> | commit |
104
+ | <kbd>Esc</kbd> | close search or menu / cancel edit |
105
+
106
+ ## Platform support
107
+
108
+ | Platform | Status |
109
+ | -------------------- | ----------------------------------------- |
110
+ | macOS (arm64, x64) | full |
111
+ | Windows (arm64, x64) | untested on real hardware; auto-refresh unverified |
112
+ | Linux | everything except the built-in terminal |
113
+
114
+ The terminal needs [node-pty](https://github.com/microsoft/node-pty), which ships prebuilt binaries for macOS and Windows only. It is an `optionalDependency`: on Linux the install either compiles it from source (needs python3 and a C++ toolchain) or skips it, and megit hides the terminal button. Nothing else is affected.
115
+
116
+ On Windows, the watcher integration tests crash the test worker outright, so they are skipped there and auto-refresh is not exercised by CI. Everything else in the suite runs. If you use megit on Windows, please report whether the graph updates on its own after a commit — that is the part we cannot currently verify.
117
+
118
+ ## Configuration
119
+
120
+ The list of open repositories lives in `~/.config/megit/config.json`. Repositories are only reachable through the API if they are registered there, so pointing megit at a repo is always an explicit act.
121
+
122
+ ## Development
123
+
124
+ Requires pnpm.
125
+
126
+ ```bash
127
+ pnpm install
128
+ pnpm dev # Express API on :4500 + Vite dev server on :4000
129
+ ```
130
+
131
+ Production build:
132
+
133
+ ```bash
134
+ pnpm build # vite → dist/
135
+ pnpm build:server # tsc → dist-server/ (only needed for publishing)
136
+ pnpm start # serves dist/ + API on http://127.0.0.1:4500
137
+ ```
138
+
139
+ Ports come from `PORT` (API, default 3411) and `UI_PORT` (Vite dev server, default 5173); the dev/start scripts pin 4500/4000.
140
+
141
+ In development the server runs its TypeScript directly via Node's native type-stripping — no build step. That does not work for a published package, because Node refuses to strip types under `node_modules`, so `pnpm build:server` compiles `server/` to `dist-server/` at publish time.
142
+
143
+ `scripts/make-test-repo.sh` generates `test-repo/` — a throwaway fixture with interleaved branches, merges, stashes and a dirty worktree, used for manual testing and for the screenshots above.
144
+
145
+ [`docs/architecture.md`](docs/architecture.md) explains how the two halves fit together and why the graph layout lives in a pure module. [CONTRIBUTING.md](CONTRIBUTING.md) has the setup and the house rules; [SECURITY.md](SECURITY.md) has the threat model.
146
+
147
+ ```bash
148
+ pnpm test # vitest — parsers, lane layout, watcher, menus
149
+ npx tsc --noEmit # typecheck
150
+ ```
151
+
152
+ ## License
153
+
154
+ MIT
package/bin/megit.js ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ // megit CLI — start the local server, open the browser at it, and register the
3
+ // repo path given as an argument (if any) by POSTing to our own /api/repos, so
4
+ // path validation stays in one place.
5
+ import { execFile } from 'node:child_process'
6
+ import { createRequire } from 'node:module'
7
+ import { resolve } from 'node:path'
8
+
9
+ const arg = process.argv[2]
10
+
11
+ // handled before importing the server, so --help never binds a port
12
+ if (arg === '-h' || arg === '--help') {
13
+ console.log(`megit — git repository viewer in the browser
14
+
15
+ Usage:
16
+ megit [repo-path]
17
+
18
+ Arguments:
19
+ repo-path open this repository in a tab (default: reopen last session)
20
+
21
+ Options:
22
+ -h, --help show this message
23
+ -v, --version print the version
24
+
25
+ Environment:
26
+ PORT port to listen on (default 3411)`)
27
+ process.exit(0)
28
+ }
29
+
30
+ if (arg === '-v' || arg === '--version') {
31
+ console.log(createRequire(import.meta.url)('../package.json').version)
32
+ process.exit(0)
33
+ }
34
+
35
+ const port = Number(process.env.PORT) || 3411
36
+ const url = `http://127.0.0.1:${port}`
37
+
38
+ const { server } = await import('../dist-server/index.js')
39
+ if (!server.listening) await new Promise(r => server.once('listening', r))
40
+
41
+ if (arg) {
42
+ await fetch(`${url}/api/repos`, {
43
+ method: 'POST',
44
+ headers: { 'content-type': 'application/json' },
45
+ body: JSON.stringify({ path: resolve(arg) }),
46
+ })
47
+ .then(async r => { if (!r.ok) console.error(`megit: ${(await r.json()).error}`) })
48
+ .catch(() => {})
49
+ }
50
+
51
+ // ponytail: no `open` dependency — three platform names cover what it does.
52
+ const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'explorer' : 'xdg-open'
53
+ execFile(opener, [url], () => {}) // exit code ignored: explorer.exe returns 1 on success
54
+ console.log(`megit → ${url}`)