Skip to content

Commit 63cdc11

Browse files
jkyberneeesclaude
andcommitted
Codeviewer 1.0.0: read-only code viewer with a real terminal
A fast, read-only code viewer with a built-in terminal and live git indicators, for watching coding agents work. Bun + TypeScript, zero dependencies. - Terminal: native PTY + from-scratch xterm-compatible emulator (canvas) - Virtualized explorer and code view, incremental highlighter, quick open - Read-only git: explorer status, gutter vs HEAD with peek, inline blame - macOS app on the system WebKit (DMG); Linux packages with pinned ungoogled-chromium; slim CLI binaries - Hardened after adversarial security/performance/correctness reviews - Release workflow with SHA256SUMS and build-provenance attestations; landing page for codeviewer.21no.de Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0 parents  commit 63cdc11

69 files changed

Lines changed: 13169 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
test:
13+
strategy:
14+
matrix:
15+
os: [macos-15, ubuntu-24.04]
16+
runs-on: ${{ matrix.os }}
17+
steps:
18+
- uses: actions/checkout@v4
19+
- uses: oven-sh/setup-bun@v2
20+
with:
21+
bun-version: 1.4.4
22+
- run: bun run test

‎.github/workflows/release.yml‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Builds every package from a version tag on a clean GitHub-hosted runner, signs SLSA build
2+
# provenance for each file (Sigstore, verifiable with `gh attestation verify`), publishes the
3+
# release with SHA256SUMS, and deploys the landing page with the same checksums.
4+
name: release
5+
6+
on:
7+
push:
8+
tags: ["v*"]
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
build:
15+
runs-on: macos-15 # Apple silicon: builds the macOS apps (Swift + WebKit) and the Linux packages
16+
permissions:
17+
contents: write # create the GitHub release
18+
id-token: write # sign attestations
19+
attestations: write
20+
steps:
21+
- uses: actions/checkout@v4
22+
- uses: oven-sh/setup-bun@v2
23+
with:
24+
bun-version: 1.4.4
25+
26+
- name: Tag matches package.json
27+
run: |
28+
v=$(bun -e 'console.log(require("./package.json").version)')
29+
test "v$v" = "$GITHUB_REF_NAME" || { echo "tag $GITHUB_REF_NAME != package.json $v"; exit 1; }
30+
31+
- run: bun run test
32+
33+
# Pinned browser for the Linux packages is verified against packaging/browser-pins.json.
34+
- run: bun run build
35+
36+
- name: Attest build provenance
37+
uses: actions/attest-build-provenance@v2
38+
with:
39+
subject-path: dist/release/*
40+
41+
- name: Publish release
42+
env:
43+
GH_TOKEN: ${{ github.token }}
44+
run: |
45+
gh release create "$GITHUB_REF_NAME" dist/release/* \
46+
--verify-tag \
47+
--title "Codeviewer $GITHUB_REF_NAME" \
48+
--notes "Downloads, checksums and verification steps: https://codeviewer.21no.de/#verify"
49+
50+
- name: Build the landing page
51+
run: bun run site
52+
53+
- uses: actions/upload-pages-artifact@v3
54+
with:
55+
path: site/dist
56+
57+
pages:
58+
needs: build
59+
runs-on: ubuntu-24.04
60+
permissions:
61+
pages: write
62+
id-token: write
63+
environment:
64+
name: github-pages
65+
url: ${{ steps.deploy.outputs.page_url }}
66+
steps:
67+
- id: deploy
68+
uses: actions/deploy-pages@v4

‎.gitignore‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
dist/
2+
site/dist/
3+
node_modules/
4+
.cache/
5+
.DS_Store

‎DESIGN.md‎

Lines changed: 343 additions & 0 deletions
Large diffs are not rendered by default.

‎LICENSE‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 21no.de
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.

‎README.md‎

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Codeviewer
2+
3+
**Read the code. Run the agent. Same window.**
4+
5+
A fast, read-only code viewer with a real built-in terminal and live git indicators. It's for
6+
watching your code while an agent (Claude Code, Codex, aider…) or any CLI tool works on it.
7+
8+
**[Download at codeviewer.21no.de](https://codeviewer.21no.de)**: macOS (Apple silicon / Intel),
9+
Linux (x86-64 / ARM64), or a single command-line binary. Every download comes with a SHA-256 and
10+
signed build provenance; see [how to verify](https://codeviewer.21no.de/#verify).
11+
12+
![Codeviewer](site/assets/screenshot-dark.jpg)
13+
14+
- **Zero dependencies.** Bun + TypeScript only: the terminal emulator, syntax highlighter,
15+
fuzzy finder, file watcher and UI are all written from scratch.
16+
- **Standalone app.** On macOS it's a native app (AppKit + the system WebKit), installed from a
17+
40 MB `.dmg`, with no browser bundled. On Linux it bundles a verified
18+
[ungoogled-chromium](https://github.com/ungoogled-software). Either way, the window is locked down to its own
19+
loopback server and keeps no browser state.
20+
- **Typography:** JetBrains Mono for code and terminal, Inter for the UI, both embedded (OFL).
21+
Sizes adapt to pixel density: 13px on HiDPI screens, 14px on 1× screens.
22+
- **Real terminal:** a native PTY (`Bun.spawn({ terminal })`) plus a canvas-rendered xterm-compatible
23+
emulator with 256/truecolor, the alternate screen, mouse (SGR), bracketed paste, OSC 8 links,
24+
OSC 52 clipboard, synchronized output, wide characters and emoji, and pixel-exact box
25+
drawing. Sessions survive page reloads.
26+
- **Git (read-only):**
27+
- Explorer status letters and colors (M A U D R C), with folders rolled up and ignored files dimmed.
28+
- Colored tabs.
29+
- Gutter markers vs `HEAD`; click one to peek at the old lines.
30+
- Inline blame on the active line.
31+
- Status bar: branch, ahead/behind, merge/rebase state, stash count, and change counts that open
32+
a changed-files list (⌘⇧G).
33+
- Everything updates live as files, the index or refs change.
34+
- **Live:** files that change on disk reload in place, the changed lines flash, the gutter marks
35+
them, and the explorer shows dots on changed files.
36+
- **Fast:** a virtualized code view and tree, an incremental highlighter, and dirty-row
37+
terminal rendering. A 100k-line file opens instantly.
38+
39+
See [DESIGN.md](DESIGN.md) for the expert-panel design review and the architecture.
40+
41+
## Install
42+
43+
| Platform | Download | Contents |
44+
| --- | --- | --- |
45+
| macOS | `Codeviewer-<v>-macos-{arm64,x64}.dmg` | Drag `Codeviewer.app` to Applications |
46+
| Linux | `codeviewer-<v>-linux-{x64,arm64}.tar.gz` | `codeviewer/`, with `install.sh` (adds `~/.local/bin/codeviewer` and a desktop entry) |
47+
| Headless | `dist/bin/codeviewer-*` | Slim single binaries with no browser, for `--no-open` / SSH |
48+
49+
- **macOS:** launch from Finder (it asks for a folder), drop a folder on the Dock icon, or use
50+
**Codeviewer → Install 'codeviewer' Command…** and run `codeviewer <dir>` from any terminal.
51+
The app is ad-hoc signed, not notarized, so on first launch of a *downloaded* DMG,
52+
right-click → Open (or run `xattr -dr com.apple.quarantine /Applications/Codeviewer.app`).
53+
- **Linux:** Chromium's sandbox needs unprivileged user namespaces. Some distros (Ubuntu 24.04+) restrict
54+
them, and `install.sh` prints the one-time command for the setuid `chrome-sandbox` when that's the case.
55+
The sandbox is never disabled.
56+
57+
## Use
58+
59+
```sh
60+
codeviewer [dir] # opens the app window (macOS: Codeviewer.app; Linux: bundled Chromium)
61+
codeviewer . --no-open # headless/SSH: just print the URL
62+
codeviewer open src/app.ts:42 # from inside a codeviewer terminal: show a file in the viewer
63+
```
64+
65+
Options: `--port N`, `--host ADDR` (default `127.0.0.1`), `--shell PATH`, `--browser` (use your default browser).
66+
Closing the app window quits codeviewer. Settings live in `~/.config/codeviewer/settings.json`
67+
(on macOS, `~/Library/Application Support/codeviewer`).
68+
69+
| Keys | Action |
70+
| --- | --- |
71+
| ⌘P / Ctrl+P | Go to file (`path:line` works) |
72+
| ⌘⇧P / Ctrl+Shift+P | Command palette |
73+
| ⌃\` | Toggle / focus terminal · ⌃⇧\` new terminal |
74+
| ⌘F · ⌃G | Find in file · go to line |
75+
| ⌘B · ⌘⇧E | Toggle sidebar · focus explorer |
76+
| ⌘W · ⌘⇧[ ⌘⇧] | Close tab · switch tabs |
77+
| ⌘= ⌘- ⌘0 | Font size |
78+
| ⌘⇧G · ⌥F5 / ⌥⇧F5 | Changed files · next / previous change in file |
79+
| ⌘-click in terminal | Open `path:line` or URL |
80+
| Left ⌥ | Meta in the terminal (right ⌥ still types special characters) |
81+
82+
## Security
83+
84+
The server binds to localhost and serves a shell. It was hardened through an adversarial review
85+
(DESIGN.md §11):
86+
87+
- **Access:**
88+
- A random 256-bit token travels only in the URL *fragment*, then in a request header.
89+
- There are no cookies (they leak across localhost ports).
90+
- `Host` is validated (no DNS rebinding), WebSockets require a matching `Origin`, and writes must be JSON.
91+
- **No secrets in process arguments:** browsers start through a private bootstrap page.
92+
- **Files:** every path is `realpath`-contained in the root, and files are opened with
93+
`O_NOFOLLOW|O_NONBLOCK` (regular files only). Nothing writes to the viewed tree.
94+
- **Git runs untrusted repositories safely:**
95+
- No filters, textconv, diff drivers, signature checks, fsmonitor or submodule recursion.
96+
- It never writes the index.
97+
- **App windows:**
98+
- A throwaway profile, locked to the window's own server port.
99+
- External links open in your default browser.
100+
- Terminal clipboard writes always show a notice.
101+
- **Builds:** the Linux browser is pinned by SHA-256 in `packaging/browser-pins.json`.
102+
103+
## Verify a download
104+
105+
Releases are built by [GitHub Actions](.github/workflows/release.yml) from a version tag, on a
106+
clean runner. Each release has `SHA256SUMS` and a Sigstore-signed provenance attestation:
107+
108+
```sh
109+
shasum -a 256 -c SHA256SUMS --ignore-missing # Linux: sha256sum -c …
110+
gh attestation verify <file> --repo BackendStack21/codeviewer
111+
```
112+
113+
## Develop
114+
115+
```sh
116+
bun run dev # build the client and serve the current directory
117+
bun test # emulator, highlighter, path-safety and fuzzy tests
118+
bun run build # slim binaries (dist/bin) + .dmg / .tar.gz for every target (dist/release)
119+
bun run build:local # this machine only: dist/bin/codeviewer + its app (on a Mac: the .dmg)
120+
bun scripts/build.ts --slim # binaries only, no browser download
121+
bun scripts/build.ts --browser <tag|latest> # re-pin the Linux browser (packaging/browser-pins.json)
122+
bun run site # landing page → site/dist (reads dist/release for sizes + checksums)
123+
```
124+
125+
### Releasing
126+
127+
1. Bump `version` in `package.json` and commit.
128+
2. `git tag v<version> && git push --tags`
129+
3. The release workflow runs the tests, builds every package, attests provenance, publishes
130+
the GitHub release with `SHA256SUMS`, and deploys the landing page to GitHub Pages
131+
(custom domain `codeviewer.21no.de`, served from `site/dist/CNAME`).
132+
133+
## License
134+
135+
[MIT](LICENSE) © 2026 [21no.de](https://21no.de). Bundled fonts: JetBrains Mono and Inter (SIL OFL 1.1,
136+
see `vendor/fonts`). The Linux packages bundle ungoogled-chromium (BSD-3-Clause and others).

‎SECURITY.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Security policy
2+
3+
Codeviewer serves a real shell to a local window, so security issues are taken seriously.
4+
The threat model and the hardening are documented in [DESIGN.md](DESIGN.md) (§5, §8–§11).
5+
6+
## Reporting a vulnerability
7+
8+
Please **do not open a public issue**. Report privately through GitHub's
9+
[private vulnerability reporting](https://github.com/BackendStack21/codeviewer/security/advisories/new).
10+
Include the version, OS, and the steps to reproduce. Expect an acknowledgement within a few days.
11+
12+
## Verifying a download
13+
14+
Every release is built by GitHub Actions from a tagged commit and ships with `SHA256SUMS` and
15+
signed build-provenance attestations. See https://codeviewer.21no.de/#verify.

‎package.json‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "codeviewer",
3+
"version": "1.0.0",
4+
"description": "A fast, read-only code viewer with a real built-in terminal and live git indicators. Zero dependencies.",
5+
"license": "MIT",
6+
"author": "21no.de",
7+
"homepage": "https://codeviewer.21no.de",
8+
"repository": {
9+
"type": "git",
10+
"url": "git+https://github.com/BackendStack21/codeviewer.git"
11+
},
12+
"bugs": "https://github.com/BackendStack21/codeviewer/issues",
13+
"keywords": ["code-viewer", "terminal", "git", "bun", "read-only", "ide", "claude-code", "agents"],
14+
"type": "module",
15+
"private": true,
16+
"engines": { "bun": ">=1.3.5" },
17+
"bin": { "codeviewer": "src/server/main.ts" },
18+
"scripts": {
19+
"dev": "bun scripts/build.ts --client-only && bun src/server/main.ts",
20+
"build": "bun scripts/build.ts",
21+
"build:local": "bun scripts/build.ts --local",
22+
"site": "bun scripts/site.ts",
23+
"test": "bun scripts/build.ts --client-only && bun test"
24+
}
25+
}

‎packaging/browser-pins.json‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"repo": "ungoogled-software/ungoogled-chromium-portablelinux",
3+
"tag": "156.0.8078.47-1",
4+
"sha256": {
5+
"ungoogled-chromium-156.0.8078.47-1-x86_64_linux.tar.xz": "3fd4ff6bc398a622f77c728ef80f00df41a10846fb60a647392a7983dc6ad28e",
6+
"ungoogled-chromium-156.0.8078.47-1-arm64_linux.tar.xz": "1973127a5ae83b0227bdd74b0194983e2412fb478d6ba81cd338912eb87d7ab8"
7+
}
8+
}

‎packaging/codeviewer.desktop‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[Desktop Entry]
2+
Type=Application
3+
Name=Codeviewer
4+
GenericName=Code Viewer
5+
Comment=Fast, read-only code viewer with a built-in terminal
6+
Exec=codeviewer %f
7+
Icon=codeviewer
8+
Terminal=false
9+
Categories=Development;Viewer;
10+
StartupWMClass=codeviewer

0 commit comments

Comments
 (0)