Skip to content
MrArcher23Public

About

Lightweight desktop audit viewer for Claude Code — which files it touched, in which repo, with what diff and session. No AI chat, no git/hooks dependency.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

arrow

License: MIT Built with Rust Tauri Svelte CI Latest release Platform: Linux Platform: macOS

An audit viewer for Claude Code. It answers a single question, reliably: which files did Claude touch, in which repo, with what diff, and in which session? — without opening an IDE, without AI chat, and without depending on git or hooks.

Status: Phase 2 complete + polish — released on Linux (v0.3.0). A desktop app (Tauri 2.x) with the Rust parser as its native backend, already usable day to day on Linux (.deb + AppImage). Phases 0 (parser/CLI), 1 (web UI) and 2 (packaging) are complete; since then: 38 parser tests, a resilient watcher, zoom, a custom titlebar, active-session focus, live diff-panel refresh, open-in-editor (jump from a diff straight into your editor at the first change), files ordered by most-recent edit (with relative times that age in place via a clock tick), macOS adaptation, a robust diff-"before" reconstruction (shows a real diff even on Claude Code's originalFile: null edits, instead of mislabeling an edited file as new), and — new in v0.2.0 — a Sessions tab: browse every Claude Code session across your repos, copy its id or resume command, and clean sessions up to the system trash. Phases 3 (honesty + git) and 4 (editing) are pending — details in ROADMAP.md. Newest: a light/dark appearance for the app itself, separate from the editor's syntax theme.

Install (Linux)

Linux (x86-64) has pre-built bundles. macOS (Apple Silicon + Intel) bundles are built and published by CI for each tagged release — see Install (macOS). Windows is not built yet; build from source there (see Desktop app below). The macOS .dmg is unsigned (no Apple notarization yet — see MACOS.md and the roadmap), so the first launch needs one extra step (covered in Install (macOS)).

Grab the latest from the Releases page:

# Debian / Ubuntu / Pop!_OS — the .deb
sudo dpkg -i arrow_*_amd64.deb
sudo apt-get install -f      # only if dpkg reports missing dependencies

# Any distro — the AppImage (no install)
chmod +x arrow_*_amd64.AppImage
./arrow_*_amd64.AppImage

The bundles are built on Ubuntu 22.04, so they run on glibc ≥ 2.35 (Ubuntu/Pop!_OS 22.04+, Debian 12+, Fedora 37+, and newer). On older systems, build from source.

Install (macOS)

Each tagged release publishes an unsigned .dmg for both Apple Silicon and Intel, built by CI (no Apple notarization yet), so macOS Gatekeeper would normally block the first launch — the one-liner below handles that for you. If no macOS release has been published yet, the installer says so and exits cleanly; meanwhile, build from source (see Desktop app below).

One-liner (recommended): downloads the right .dmg for your chip from the latest release, installs arrow.app into /Applications, and clears the Gatekeeper quarantine flag:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/MrArcher23/arrow/main/install.sh)"

The installer strips the quarantine attribute of an unsigned binary so it opens without friction — only run it if you trust this source. To update, re-run the one-liner; to uninstall, rm -rf /Applications/arrow.app. arrow tells you when a newer release exists (a dot on the version badge, or arrow --check-update from the CLI) but does not auto-install it yet — being unsigned, a silent in-app updater would fight Gatekeeper. A Homebrew Cask and a signed auto-updater are on the roadmap.

Manual: grab the .dmg for your chip (_aarch64 = Apple Silicon, _x64 = Intel) from the Releases page, open it, and drag arrow to Applications. Because the app is unsigned, the first launch needs right-click → Open (once), or clear the flag manually:

xattr -dr com.apple.quarantine /Applications/arrow.app

Why it exists

When Claude Code becomes the one doing most of the changes, your job shifts to steering and reviewing: the IDE stops being where you write and is relegated to "show me what it touched." But keeping an IDE — or a heavy Electron-style Git client — open just to review diffs wastes anywhere from hundreds of MB to several GB of RAM. arrow does exactly that one part — seeing what Claude changed, repo by repo and session by session — in a lightweight, focused app.

And while the tooling space around Claude Code is crowded, nobody covers exactly this: a graphical UI, no chat, with the repository → touched files → diff/editor hierarchy as a navigable audit trail. The closest options are a terminal TUI (claude-file-recovery), a web chat client (claude-code-viewer), or large GUIs oriented toward running agents (opcode, AGPL). Anthropic closed the "recoverable edit history" feature request (#36542) as not planned — so the gap is real, even if the margin is narrow.

The key idea: the right data source

There is no need to install a session-log hook. Claude Code already persists everything needed, natively and in a structured form (verified on Claude Code v2.1.x):

Data Native source
Repos The cwd field of each record (the real path; we don't decode the directory name, which is ambiguous).
Sessions Each ~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl. The file name is the sessionId.
Touched files type:"user" records with toolUseResult.filePath (only Edit/Write/MultiEdit).
Diff (before/after) toolUseResult.structuredPatch — the exact hunks {oldStart, oldLines, newStart, newLines, lines}. This is already the per-session diff.
"Before" / point-in-time ~/.claude/file-history/<sessionId>/<hash>@v<n> — snapshots of the prior content.

git diff is kept as an optional secondary view (the full working tree), and only when the repo is a git repo: it does not attribute by author or by session, and many repos aren't git at all.

Honest limits (the product must NOT lie)

  • Only what goes through Edit/Write/MultiEdit is captured. Changes made via the session's Bash commands (sed, prettier, build, mv, rm) do not appear. The correct label is always "edits via Claude's tools", never "everything Claude did".
  • The userModified flag signals drift (the user edited between the read and the write): it's marked with ⚠.
  • The JSONL format is internal, undocumented, changes between versions, and auto-deletes at ~30 days (cleanupPeriodDays). Hence the defensive parsing: an invalid line is skipped, never crashing. The Sessions tab surfaces that expiry honestly ("expires in Nd").
  • Deleting a session is reversible by construction: artifacts go to the system trash, never rm, and a session a running claude process has registered is refused outright.

Usage

cargo build --release

# Summary: all repos -> sessions -> files (+/-)
./target/release/arrow --list

# Full diff of a repo (filters by a substring of the cwd)
./target/release/arrow --repo my-project

# A specific session (prefix of the sessionId)
./target/release/arrow --session 14385fed

# Normalized JSON (the contract the UI consumes)
./target/release/arrow --repo my-project --json

# Is there a newer arrow release? (network; read-only, never installs)
./target/release/arrow --check-update

# What would trashing a session remove? (dry-run; add --yes to actually trash)
./target/release/arrow --trash-session 14385fed

Options: --projects-dir <path> (defaults to ~/.claude/projects), --repo, --session, --list, --json, --check-update, --content --file <path> [--session <id>] (emits {before, after} for a file, for the UI's diff view), and --trash-session <id> [--yes] (send a session's artifacts to the system trash — dry-run without --yes, never rm, refuses a session a running claude process has registered).

Web UI (Phase 1)

A Vite + Svelte 5 app that consumes the parser through a local dev-server which runs the arrow binary (in web/vite.config.ts). The diff view uses CodeMirror 6 + @codemirror/merge.

cargo build --release          # the dev-server runs target/release/arrow
cd web && npm install
npm run dev                     # http://localhost:5173

Layout: a sidebar repo → session → touched files (with +/- and ⚠ userModified), and a central panel with the before/after diff of the selected file. Total frontend weight: ~93 KB gzip.

Desktop app (Phase 2)

The same UI, packaged in Tauri 2.x: the Rust parser is the native backend (no sidecar, no HTTP server). The Svelte UI is reused as-is; only its transport layer (web/src/lib/api.ts) detects the environment and uses Tauri invoke() inside the app or fetch in the browser — so npm run dev keeps working for fast iteration.

# system requirements (Linux/Debian/Ubuntu/Pop!_OS), once:
sudo apt install -y libwebkit2gtk-4.1-dev libxdo-dev libayatana-appindicator3-dev librsvg2-dev
cargo install tauri-cli --version "^2"     # Tauri CLI

# development: native window with frontend hot-reload
cargo tauri dev      # (from the repo root)

# installer: .deb + AppImage in src-tauri/target/release/bundle/
cargo tauri build

macOS: the app adapts to the OS on its own (native titlebar with traffic lights + correct font weight). To build the .app/.dmg — which can only be done on a Mac — see MACOS.md.

  • Native backend (src-tauri/): two invoke commands — report() and content(file, session) — that wrap the parser library (arrow = { path = ".." }, see Architecture). The AppImage runs standalone, reading ~/.claude/projects directly from Rust.
  • Lightweight footprint: Tauri uses the system's native webview (WebKitGTK on Linux), it does not bundle a Chromium like Electron — so the app lives on the order of ~200 MB of RAM (PSS) in use, a fraction of an equivalent Electron desktop client (which usually runs into several GB). The installer weighs ~77 MB (AppImage) / ~2 MB (.deb).
  • Native live refresh: a notify watcher over ~/.claude/projects emits a report-changed event (debounced) and the UI refreshes instantly; a slow polling fallback is kept as a backstop.
  • Active-session focus: the repo(s) of the active session (the one with the most recent activity) are shown at the top, plus any repo touched in the same burst (~10 min, BURST_WINDOW in web/src/lib/time.ts); the rest sink into Other repos as they age relative to the active one. The green dot marks those focused repos with recent activity (the redundant live text badge was removed). Honest: "active session" = most recent activity on disk, not a running process (arrow cannot know the latter).
  • Open in editor: a button in the file bar opens the selected file in your own editor at the first changed line. It detects installed editors (the VS Code family — incl. Cursor, Windsurf, Kiro, Antigravity — plus Zed, JetBrains, Sublime, Kate) and delegates to the editor's CLI; arrow never embeds an editor or a language server (the mirror image of Claude Code's /ide). Honest: it opens the current on-disk file (and is disabled when that file is gone), never the reconstructed snapshot.
  • Worktrees inventory + cleanup: a Worktrees button in the topbar opens an inventory of the git worktrees Claude Code creates per session (under <repo>/.claude/worktrees/), grouped by repo and flagged active / stale (≥10 min no edits) / merged → safe to remove, with on-demand disk sizes. Each row offers copy cmd (the exact git worktree remove for you to run) and, in the desktop app, a Clean button that runs it for you — git worktree remove without --force (so git refuses a locked or dirty worktree), gated behind a dry-run and a confirmation. It's one of only two actions in arrow that mutate disk (the other: sending a session to the system trash from the Sessions tab), and it's offered only on rows git would actually clean (merged+clean, or a phantom to prune) — never the main, dirty, locked, or active worktree. Honest by construction: "merged" is shown only when the branch is provably an ancestor of the repo's (dynamically resolved) default branch — a squash/rebase merge reads as can't confirm, never a false green; "active" means recent edits on disk, not a running process; sizes are approximate. All git shelling stays in the Tauri backend, so the parser library remains git-free.
  • Window and zoom: a custom titlebar (decorations:false) with minimize/maximize/close buttons, drag, and double-click to maximize — guaranteeing cross-distro controls (on GNOME/Pop!_OS the WM doesn't paint them reliably). VSCode-style UI zoom with Ctrl +/−/0: native to the webview (setZoom, so it doesn't throw off CodeMirror), persistent across sessions.
  • Linux/WebKitGTK: the app sets WEBKIT_DISABLE_DMABUF_RENDERER=1 in its main() (avoids the white screen caused by DMABUF/NVIDIA); the font-weight (+100) bug is already compensated in web/src/app.css (font-weight: 350).

Architecture (shared parser, no duplication)

The parser lives in a library (src/lib.rs): pure functions build_report(projects_dir) and file_content(projects_dir, file, session) + the serializable structs. Two frontends consume it: src/main.rs (the CLI, with flags intact) and src-tauri/ (the desktop backend). Zero duplicated logic; the same source of truth for terminal, web, and native app. The parser ships 38 unit tests (cargo test) over fixture transcripts in a tempdir, covering the non-obvious parts: defensive parsing, top-level transcripts only, grouping by git root, +/− counting, filtering of ~/.claude/, recency ordering (repos, sessions, and files within a session), the diff-"before" reconstruction cascade (inline originalFile, reverse-applied patches, full-Read snapshot, create → new file, and drift → honestly unavailable), and the Sessions metadata + trash safety (chat-only listing, resumeCwd anchoring, retention, charset-validated ids that can't escape the claude root, and the live-session guard).

Sessions tab (v0.2.0)

A second tab next to the audit view. Audit answers "what did Claude touch?"; Sessions answers "where was I, and how do I pick that session back up?" — every repo with Claude Code history, each session with its AI-generated title, its last two prompts, linked PRs (from the transcript's pr-link records), size, a retention countdown (from cleanupPeriodDays, default ~30 days), and a live dot for sessions a running claude process has registered — checked against the real process (/proc, or ps -p where procfs doesn't exist), not just a timestamp.

  • Resume anywhere: copy the session id, or a ready-to-paste cd '<cwd>' && claude --resume <id> — anchored to the directory Claude Code registered the session under (the session cwd that encodes to its transcript's project-dir name), which is the only place claude --resume will actually find it. A cwd that drifts mid-session — say, into a nested repo — no longer sends you somewhere the session is invisible; if no cwd matches, the last record's cwd is the honest fallback. The desktop app can also open your terminal straight into the session (resume — $TERMINAL, gnome-terminal, kitty, and friends on Linux; Terminal.app on macOS).
  • Clean up: filters for junk (untitled or < 20 KB) and expiring sessions, search across titles and prompts, pins that protect a session from bulk cleaning, and per-row or bulk delete — always to the system trash (gio trash on Linux, ~/.Trash on macOS), never rm, always behind an explicit confirmation, and never for a live session. Deleting removes the transcript, its subagent logs, and its file-history / session-env artifacts — all recoverable from the trash.
  • Mutation stays in the desktop app (same rule as the worktree Clean): deleting is never exposed over HTTP, so in the browser build the UI hands you the exact arrow --trash-session <id> --yes command to run yourself.

Appearance (light/dark)

The app chrome — topbar, sidebar, Sessions, modals — follows its own light/dark setting, toggled from the topbar and remembered across launches. It is deliberately separate from the Themes menu, which picks the editor's syntax theme: pairing a light chrome with a dark editor (or the reverse) is a valid choice, not a mistake, so arrow does not couple them. On first run the appearance is seeded from the polarity of the editor theme you already picked, so nobody upgrades into a mismatch.

There is no "System" option, and that is on purpose. Inside a Tauri window on Linux, prefers-color-scheme tracks whether your GTK theme has a light variant rather than the color scheme you actually chose — under a dark-only theme it stays pinned to dark and never fires a change event. Shipping a "System" toggle on top of that would be a switch that silently does nothing, which is exactly the kind of thing arrow refuses to do. Following the OS honestly needs a polled read of the XDG portal; it is written up in ROADMAP.md.

Both palettes are gated on contrast: npm --prefix web run check:contrast parses the two palette blocks straight out of web/src/app.css and asserts WCAG 2 ratios on every pair the components actually render, so the live dot, the ⚠ userModified marker and the expiry countdown cannot go quietly unreadable on either side.

Roadmap

  • Phase 0 — native parser JSONL → repo/session/file/diff, terminal output and --json.
  • Phase 1 — local web UI (Vite + Svelte 5) consuming the parser: sidebar repo → session → files (Antigravity-style) + per-file diff with CodeMirror 6 + @codemirror/merge. (Read-only; editing is Phase 4.)
  • Phase 2 — packaging in Tauri 2.x (.deb + AppImage). The Rust parser was extracted into a library (src/lib.rs) and is the native backend (no sidecar); the CLI and the app share it. Dual-mode frontend (invoke/fetch), a notify watcher → report-changed event for live refresh, and WebKitGTK mitigation. (The sidebar focus was later refined to "active session + ~10 min burst" with the green dot as the sole indicator; see ROADMAP.)
  • Phase 3 — honesty + git: a "git diff working tree" toggle, userModified marking, a point-in-time timeline reusing file-history.
  • Phase 4 (deferred) — real editing with save-to-disk, GitHub integration (PRs/commits).

Operational tracking, technical debt, and a backlog of ideas (search, stats, export, shortcuts): see ROADMAP.md. The data contract lives in SPEC.md.

Stack

Rust (parser/backend) · CodeMirror 6 (UI, Phase 1+) · Tauri 2.x (packaging, Phase 2+). On Linux/WebKitGTK you'll need to apply WEBKIT_DISABLE_DMABUF_RENDERER=1 and compensate for the font-weight (+100) bug — both documented for Phase 2.

Contributing

Contributions are welcome — bug reports, ideas, and pull requests alike. Please read CONTRIBUTING.md for how to build, test, and submit changes, and our Code of Conduct. To report a security issue, see our Security Policy. When in doubt, keep the product honest: arrow shows edits via Claude's tools, never everything Claude did.

License

MIT.

About

Lightweight desktop audit viewer for Claude Code — which files it touched, in which repo, with what diff and session. No AI chat, no git/hooks dependency.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages