https://github.com/pekinlcc/salmonapp
Unofficial Linux + macOS desktop client wrapping the Claude Code CLI and Codex CLI. Three-pane chat UI, your existing CLI login, no API key required.
https://github.com/pekinlcc/salmonapp
ai-coding anthropic claude-code codex-cli desktop-client linux macos openai tauri
Last synced: about 1 month ago
JSON representation
Unofficial Linux + macOS desktop client wrapping the Claude Code CLI and Codex CLI. Three-pane chat UI, your existing CLI login, no API key required.
- Host: GitHub
- URL: https://github.com/pekinlcc/salmonapp
- Owner: pekinlcc
- Created: 2026-05-01T02:19:51.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-05-11T05:01:27.000Z (about 1 month ago)
- Last Synced: 2026-05-11T08:37:55.155Z (about 1 month ago)
- Topics: ai-coding, anthropic, claude-code, codex-cli, desktop-client, linux, macos, openai, tauri
- Language: TypeScript
- Homepage: https://pekinlcc.github.io/SalmonApp/
- Size: 1.77 MB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# SalmonApp
> A three-pane desktop client for the **Claude Code CLI** and **Codex CLI** — Linux + macOS.
>
> SalmonApp wraps a locally-logged-in `claude` or `codex` and reuses its credentials — no second account to manage. See [Releases](https://github.com/pekinlcc/SalmonApp/releases) for the changelog.
>
> Note: deliberately named **SalmonApp** (one word) to avoid colliding with the bioinformatics `salmon` package on Linux distros.
[中文 PRD](PRD.md) · [Mockup](mockup.html) · [Icon candidates](icon-candidates.html)
---
## Why
If you already use `claude` (Claude Code) or `codex` (OpenAI Codex CLI) in a terminal, you've hit the same speed bumps:
- Long transcripts are painful to read in a scroll-back buffer
- Multiple projects all share one shell history
- Tool-call diffs need a second `cat`/`ls` to inspect
- Switching between Claude and Codex means switching terminals
SalmonApp wraps the CLI you're already running and gives it a chat UI:
| Pane | What it shows |
|---|---|
| **Left** | Topic list, grouped by recency, with engine + workdir badges |
| **Middle** | Markdown-rendered chat, tool-call cards, permission prompts, code blocks with `highlight.js` |
| **Right** | Tabs: Files (workdir tree) · Diff · Preview (MD / HTML / pptx / docx / xlsx) · Logs · ⛶ fullscreen toggle |
A **Topic** is mentally a *terminal tab pinned to a workdir* — open many at once, each with its own engine + persistent CLI session. Closing a Topic SIGTERMs its child PTY but keeps the CLI's transcript in `~/.claude/...` or `~/.codex/...` exactly as the CLI itself stores it. Re-opening lazily re-spawns via `claude --resume ` (or the Codex equivalent). Detach / attach, basically.
SalmonApp **does not** speak to Anthropic or OpenAI directly. It owns no API key. Credentials and session storage live entirely with the CLI.
## Install
Grab the latest from [Releases](https://github.com/pekinlcc/SalmonApp/releases/latest).
### Ubuntu / Debian
```bash
# .deb — installs to /usr/bin/salmonapp and adds an application entry
sudo apt install ./SalmonApp_*.deb
# OR AppImage — no install, double-click or chmod +x then run
chmod +x SalmonApp_*.AppImage
./SalmonApp_*.AppImage
```
The `.deb` declares its WebKit / GTK runtime deps; `apt` resolves them. The AppImage bundles them.
### macOS (Apple Silicon + Intel, universal `.dmg`)
> ⚠ The Mac build is **not notarized** — this project has no Apple Developer account. The `.dmg` is signed ad-hoc, which is enough to launch but not enough to satisfy Gatekeeper out of the box. You'll see "Apple could not verify SalmonApp is free of malware" on first launch.
```bash
# 1. Open the .dmg, drag SalmonApp.app into /Applications
# 2. Tell Gatekeeper to trust it. EITHER:
# (a) Right-click SalmonApp.app → Open → click "Open" in the dialog. macOS
# remembers the choice; subsequent launches are normal.
# OR (b) clear the quarantine bit from a terminal:
xattr -dr com.apple.quarantine /Applications/SalmonApp.app
```
`(b)` is the smoother path if you trust this repo's release pipeline. The `.dmg` is universal (`arm64` + `x86_64`), so the same file works on M-series and Intel Macs.
SalmonApp needs `claude` or `codex` discoverable on PATH. On macOS, GUI apps don't inherit your shell's PATH — SalmonApp walks `$SHELL -ilc 'echo $PATH'` at startup to import it, plus probes `/opt/homebrew/bin`, `/usr/local/bin`, `~/.npm-global/bin`, `~/.bun/bin`, etc. If `npm i -g @anthropic-ai/claude-code` worked in your terminal, it'll be found.
### Prerequisites
You need at least one of the CLIs already installed and logged in:
```bash
# Claude Code CLI
npm i -g @anthropic-ai/claude-code
claude # follow the auth flow once
# OR Codex CLI
npm i -g @openai/codex-cli
codex # auth flow
```
SalmonApp detects whichever is on `PATH` and offers to use them per-Topic.
### Optional: Office document preview
The Preview pane renders `.pptx` / `.docx` / `.xlsx` / `.odp` / `.odt` / `.ods` by shelling out to LibreOffice headless and slicing the resulting PDF with `pdftoppm`. Install once:
```bash
# Linux
sudo apt install libreoffice-impress libreoffice-writer libreoffice-calc poppler-utils
# macOS (either of these)
brew install --cask libreoffice && brew install poppler
# or download LibreOffice.app from libreoffice.org/download/ — SalmonApp
# probes /Applications/LibreOffice.app/Contents/MacOS/soffice automatically.
```
Without these, Office files fall back to a friendly "binary file" placeholder instead of crashing the preview.
## Build from source
Common: Rust toolchain (`rustup` 1.77+) and Node 20+.
### Ubuntu 22.04 / 24.04
```bash
sudo apt install \
libwebkit2gtk-4.1-dev libssl-dev libayatana-appindicator3-dev \
librsvg2-dev build-essential curl wget file pkg-config
cd salmon
npm install
npm run tauri:build # → src-tauri/target/release/bundle/{deb,appimage}/
```
### macOS
```bash
xcode-select --install # if you don't have command-line tools yet
rustup target add aarch64-apple-darwin x86_64-apple-darwin
cd salmon
npm install
npm run tauri:build -- --target universal-apple-darwin
# → src-tauri/target/universal-apple-darwin/release/bundle/{macos,dmg}/
```
For native-arch only (faster build, won't run on the other Mac arch):
```bash
npm run tauri:build # → src-tauri/target/release/bundle/{macos,dmg}/
```
### Development (hot-reload UI + auto-restart Tauri, all platforms)
```bash
npm run tauri:dev
```
## Architecture
```
salmon/
├── src/ React + TypeScript UI (Vite)
│ ├── App.tsx top-level layout, routing between Topics
│ ├── components/
│ │ ├── LeftSidebar.tsx Topic list, search, grouping
│ │ ├── ChatStream.tsx Markdown / tool-call rendering
│ │ ├── Composer.tsx Input box, /-command pass-through
│ │ ├── ToolCallCard.tsx Per-tool result rendering
│ │ ├── PermissionCard.tsx Approval prompts (allow / deny)
│ │ ├── RightPane.tsx Files / Diff / Preview / Logs tabs
│ │ ├── NewTopicDialog.tsx Create-topic flow
│ │ └── Onboarding.tsx First-run CLI detection
│ └── lib/ invoke() wrappers + types
└── src-tauri/ Rust backend
└── src/
├── lib.rs Tauri builder, plugin wiring
├── commands.rs Tauri commands invoked from React
├── engine.rs PTY child management, JSONL parse loop
├── db.rs SQLite schema + topic / message CRUD
└── types.rs Shared Rust ↔ TS types
```
Key choices:
- **Tauri 2** — native window, system WebKit, ~3 MB app vs. an Electron equivalent
- **Per-Topic PTY** — each Topic owns one `tokio::process::Child` running `claude` (or `codex`) in JSONL streaming mode. Stream events flow through an unbounded mpsc channel and out to the UI as Tauri events.
- **SQLite** in `~/.local/share/app.salmonapp.desktop/salmon.db` — Topics, messages, tool calls, permission decisions, token counts. Plain text. Export / clear available from the UI.
- **No API calls from SalmonApp itself** — every model interaction is a child process invocation.
## Limitations
- Single window, single profile — no multi-account
- No cloud sync, no team workspace (out of scope per [PRD](PRD.md))
- Windows build not yet wired
- macOS build is unsigned / unnotarized — first launch needs the `xattr` workaround above
- Token-usage display only counts what the CLI emits in stream events
- Office preview blocks the UI thread for ~2-3 s on first render (LibreOffice cold-start); cached after
See [PRD.md](PRD.md) for the full design rationale and roadmap.