Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ dotnet test
they may be talking to you through it.
- **Verification is tests, `--smoke` and targeted live checks.** No UI automation: no screenshots, synthetic keystrokes or
WebView2 remote debugging. Antivirus behavior shields flag those and quarantine the build.
- **Live checks against real accounts:** scratch sessions only, and never a prompt that spends the user's plan usage.
- **Live checks against real accounts:** scratch sessions only, and never a prompt that spends the user's plan usage. A check
that needs the real account (rare) deletes its own sessions afterwards.
- **A scratch `claude` never gets the user's config folder.** Start it with `CLAUDE_CONFIG_DIR` = a fresh temp dir,
`ANTHROPIC_BASE_URL=http://127.0.0.1:9`, a dummy `ANTHROPIC_API_KEY` (answer Yes at "use this API key?" and at "Trust this folder?") and
`ClaudeLaunch.ParentSessionVars` stripped; under Git Bash set `MSYS_NO_PATHCONV=1` (else "/help" becomes a Windows path); delete the temp dir and its cwd when the check ends. Otherwise its sessions
pile up in the session list and `.claude.json`.
- **New non-trivial logic leaves a test.** `ThemeLoadTests` builds every view with `App.xaml` loaded, so a missing theme
key fails the tests instead of the app.

Expand All @@ -24,8 +29,8 @@ dotnet test
| Folder | What |
|---|---|
| `src/CCWrapper` (root) | `Program.cs`: the Velopack hook runs before WPF starts |
| `src/CCWrapper/Core` | Accounts and paths, `AppState`, the `LiveSession` contract the views bind to; `AppUpdate` (auto-update), `AppPrefs` (`updateCheck`, `browserTool`) and `CompactPrefs` (`app.json`: each rewrites only its own key) |
| `src/CCWrapper/Data` | Read-only parsers for Claude Code's files (registry, transcripts, file history, `.claude.json`) and for what `claude plugin`/`mcp` print; `SettingsData`, the Settings window's load-edit-save of `settings.json` and `CLAUDE.md`; `ClaudeJson`, locked updates of `.claude.json` |
| `src/CCWrapper/Core` | Accounts and paths, `AppState`, the `LiveSession` contract the views bind to; `Housekeeping` (start-up deletes by age: pasted images, orphaned status line files); `DraftStore` (compose-box drafts, `drafts-<letter>.json`, saved on exit, before an update restart and when the app is deactivated with an update it knows of; another window's update restart can still lose the drafts of a window that has not seen the update); `AppUpdate` (auto-update), `AppPrefs` (`updateCheck`, `browserTool`) and `CompactPrefs` (`app.json`: each rewrites only its own key) |
| `src/CCWrapper/Data` | Read-only parsers for Claude Code's files (registry, transcripts, file history, `.claude.json`) and for what `claude plugin`/`mcp` print; `SlashCommands` (the `/` list, with enabled plugins' commands and skills), `UsageStats` (the usage popup's token counts); `SettingsData`, the Settings window's load-edit-save of `settings.json` and `CLAUDE.md`; `ClaudeJson`, locked updates of `.claude.json` |
| `src/CCWrapper/Terminal` | ConPTY (`PtySession`), WebView2 + xterm.js (`TerminalHost`, `wwwroot/`), launch, paste, `UsageProbe`, `ProcessTree`, `ClaudeCli` (hidden `claude plugin`/`mcp` runs), `BrowserMcp`/`BrowserPipe` (the Browser tool: MCP handler and the window's named pipe) |
| `src/CCWrapper/Views` | WPF windows and controls; pure logic lives in static helpers (`ShellModel`, `BarFormat`, `TuiInput`) |
| `src/CCWrapper/Theme.xaml` | Every color and custom style; `App.xaml` recolors Fluent's own brushes |
Expand Down Expand Up @@ -74,7 +79,8 @@ dotnet test
`cachedUsageUtilization` are internal and change with Claude Code releases. Parse them only in `Data/`, treat every
field as optional, and show "—" when one is missing.
- **Never type into a permission prompt.** Anything that sends text or keys to the TUI goes through `TuiInput`. It
re-reads the registry right before sending and fails closed.
re-reads the registry right before sending and fails closed. A slash command goes in as pastes
of at most 500 characters and one line break each: Claude Code folds a paste over 800 characters or of 4 lines or more into `[Pasted text]` instead of running it.
- **WPF cannot draw over WebView2.** Never overlay WPF on the terminal. Collapse it and show a WPF panel in its cell, or
put the UI in the terminal page (`wwwroot`).
- **Child `claude` processes must not inherit a parent Claude Code session.** `ClaudeLaunch` strips its environment
Expand Down
11 changes: 6 additions & 5 deletions PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,18 @@ If the runtime is missing on your PC, the installer (`Setup.exe`) downloads it f
| Where | What |
|---|---|
| `%LOCALAPPDATA%\PieMH.CCWrapper` | The installed program. |
| `%LOCALAPPDATA%\CCWrapper\paste` | Images you pasted into the terminal. |
| `%LOCALAPPDATA%\CCWrapper\WebView2` | Browser profiles of the terminal and of the Browser panel, one per account: cookies, cache, site data. |
| `%LOCALAPPDATA%\CCWrapper\paste` | Images you pasted into the terminal (`paste-*.png`). Deleted when they are 7 days old, checked once when the app starts. |
| `%LOCALAPPDATA%\CCWrapper\WebView2` | Browser profiles of the terminal and of the Browser panel, one per account: cookies, cache, site data. Cookies and site data stay until you clear them (or delete the folder). A Browser panel cache over 100 MB is cleared when the panel starts. A leftover `smoke` folder of the developer self-test is deleted when the self-test next runs. |
| `%LOCALAPPDATA%\CCWrapper\backups` | Earlier versions of files you saved from the app. |
| `%LOCALAPPDATA%\CCWrapper\live` | Live status data (model, context, cost, usage) from the status-line relay, one small file per tab. |
| `%LOCALAPPDATA%\CCWrapper\live` | Live status data (model, context, cost, usage) from the status-line relay, one small file per tab. Deleted when the tab closes; one a crash left behind is deleted at the next start once it was not written for a day. |
| `%LOCALAPPDATA%\CCWrapper\drafts-<letter>.json` | The unsent text of each session's compose box, one file per account: session id, text and cursor position, at most 100 drafts. Written only when the app exits, when it restarts to update, and whenever the app loses focus while it knows an update is ready to install. A restart to update started from another window can still end a window that has not noticed the update yet, and its unsaved drafts are lost. Read when a session is reopened. Not encrypted. A draft is dropped after 14 days without a change, and when you delete its session. If two windows of one account are open, the one that exits last wins. |
| `%LOCALAPPDATA%\CCWrapper\app.json` | The app's own settings: `updateCheck`, `browserTool` (whether Claude may use the Browser panel) and the bottom bar's `compact` hint (on/off, threshold, prompt). |

These files stay on your computer. The publisher cannot see them.

### Claude Code's files

- The app **reads** files that Claude Code already writes: each account's `.claude.json` (email, plan, cached usage), `settings.json`, transcripts, session status files, file-history backups, and plugin and skill lists. It also asks `git` (and `gh`) about the folder you work in: branch, upstream, remote, your git user name and email, and your GitHub account name. It shows them in the top bar and sends them nowhere.
- The app **reads** files that Claude Code already writes: each account's `.claude.json` (email, plan, cached usage), `settings.json`, transcripts, session status files, file-history backups, and plugin and skill lists. The usage popup (click the usage meters) adds up token counts from the transcripts, and the `/` list in the compose box reads the installed plugins' commands and skills. Both stay on your computer. It also asks `git` (and `gh`) about the folder you work in: branch, upstream, remote, your git user name and email, and your GitHub account name. It shows them in the top bar and sends them nowhere.
- The app **writes** to Claude Code's files only when you act:
- **Save** in the Settings window writes the `settings.json` and `CLAUDE.md` files you changed.
- A server's folder switch in Plugins & connectors changes the list of turned-off servers for that folder in `.claude.json`.
Expand All @@ -94,7 +95,7 @@ These files stay on your computer. The publisher cannot see them.
## Delete your data

1. Uninstall from **Windows Settings > Apps > Installed apps**. This removes the program.
2. Uninstalling does **not** remove `%LOCALAPPDATA%\CCWrapper`. To delete it, close the app, type `%LOCALAPPDATA%\CCWrapper` in File Explorer's address bar, and delete the folder. Browser cookies, pasted images and backups go with it.
2. Uninstalling does **not** remove `%LOCALAPPDATA%\CCWrapper`. To delete it, close the app, type `%LOCALAPPDATA%\CCWrapper` in File Explorer's address bar, and delete the folder. Browser cookies, pasted images, drafts and backups go with it.
3. Claude Code's own folders (for example `%USERPROFILE%\.claude`) belong to Claude Code. The app does not remove them.

## Children
Expand Down
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
![Platform](https://img.shields.io/badge/platform-Windows%2010%20%7C%2011-0078D6)
![.NET](https://img.shields.io/badge/.NET-10-512BD4)
![License](https://img.shields.io/badge/license-GPL--3.0-blue)
![Tests](https://img.shields.io/badge/tests-445%20passing-brightgreen)
![Tests](https://img.shields.io/badge/tests-497%20passing-brightgreen)

Claude Code in a bare PowerShell window works, but it's clumsy. There's no search, you can't paste screenshots or files with Ctrl+V, you can't see your other sessions, and context, usage, file changes and subagents are hard to keep an eye on. If you also juggle **several Claude Code accounts** (work and personal, for example), it gets worse.

Expand Down Expand Up @@ -82,8 +82,9 @@ It stays light: a native WPF app (no Electron), Windows' own icon font, a dark s
**Compose box** (optional, under the terminal)
- A real multi-line editor with a round send button: Enter sends, Shift+Enter adds a line, and Up recalls earlier prompts.
- When Claude Code suggests your next prompt, the empty box shows it greyed too, as the terminal does: Tab takes it to edit, Enter sends it, typing replaces it.
- Pasted screenshots become image files.
- The **+** button adds files or folders (as a drop would) and opens the slash commands. Typing `/` lists Claude Code's commands, your custom commands and skills, narrowing as you type: Up/Down choose, Enter or Tab complete it without sending.
- Pasted screenshots become image files in `%LOCALAPPDATA%\CCWrapper\paste`; those older than 7 days are deleted when the app starts.
- The **+** button adds files or folders (as a drop would) and opens the slash commands. Typing `/` (or a `/` word after a space or line break) lists Claude Code's commands, your custom commands and skills, and those of your enabled plugins and claude.ai synced plugins (not MCP prompts), narrowing as you type: Up/Down choose, Enter runs it, Tab completes it without sending (after a space or line break, Enter only completes). Text that starts with a known command shows in blue. A slash command goes in as short pastes (at most 500 characters and one line break each), so Claude Code runs it instead of folding it into `[Pasted text]`.
- Each session keeps its own draft: switching tabs leaves it as it is, and drafts are saved when the app exits, restarts to update, or loses focus once it knows an update is ready to install, and come back when the session is reopened; a draft nobody changed for 14 days, or whose session you delete, is dropped. An update restart started from another window can still lose the drafts of a window that has not noticed the update.
- It never types into a permission prompt or a dialog. Those you always answer in the terminal.

Confirmations, warnings and errors use the app's dark theme too.
Expand All @@ -95,7 +96,7 @@ Confirmations, warnings and errors use the app's dark theme too.

**Bottom bar**
- Model, effort and permission-mode switches that drive Claude Code's own `/model`, `/effort` and Shift+Tab.
- Meters with small bars for the context window and the 5-hour and weekly plan usage (amber from 80%), plus session tokens and cost. Usage shows even with no session open, is checked when the window opens and every 30 minutes, and the ↻ button checks it now. Hover the usage meters for when the 5-hour and weekly windows reset.
- Meters with small bars for the context window and the 5-hour and weekly plan usage (amber from 80%), plus session tokens and cost. Usage shows even with no session open, is checked when the window opens and every 30 minutes, and the ↻ button checks it now. Hover the usage meters for when the 5-hour and weekly windows reset; click them for a popup with both windows' bars and resets, and the tokens (in, out, cache) by model and by hour of day, counted from the account's transcripts.
- **Compact hint:** once the context passes a threshold (50% by default; set it in Settings), a *Compact* button appears left of the context meter. It puts `/compact` and your keep/drop prompt in the message box and focuses it, but never sends it, and leaves text you already typed alone.

## Requirements
Expand Down Expand Up @@ -126,7 +127,7 @@ Get-FileHash .\PieMH.CCWrapper-win-Setup.exe -Algorithm SHA256 # compare with
gh attestation verify .\PieMH.CCWrapper-win-Setup.exe -R PieMH/CCWrapper
```

**Uninstall** in Windows **Settings → Apps → Installed apps → CCWrapper**. That leaves your data in `%LOCALAPPDATA%\CCWrapper` (settings backups, pasted images, browser profiles, `app.json`); delete that folder to remove it. Claude Code's own files are never touched.
**Uninstall** in Windows **Settings → Apps → Installed apps → CCWrapper**. That leaves your data in `%LOCALAPPDATA%\CCWrapper` (settings backups, recent pasted images, browser profiles, `app.json`); delete that folder to remove it. Claude Code's own files are never touched.

### Build from source

Expand Down Expand Up @@ -179,13 +180,13 @@ A few tips:
CCWrapper is a viewer and a launcher. It doesn't touch your logins.

- **It ends a `claude` process only when you confirm it:** Resume here's "Close it there and resume it here?" ends that terminal's `claude`, as closing the terminal would. Nothing else is ever stopped for you.
- **It reads only files Claude Code already writes:** each account's `.claude.json` (email, plan, cached usage), `settings.json`, transcripts, session status files, file-history backups, and plugin and skill lists.
- **It reads only files Claude Code already writes:** each account's `.claude.json` (email, plan, cached usage), `settings.json`, transcripts (also for the usage popup), session status files, file-history backups, and plugin and skill lists (also for the `/` list).
- **It never reads** `.credentials.json` and never calls Anthropic APIs. Plugin and MCP changes you make in Plugins & connectors are done by Claude Code's own `claude plugin` and `claude mcp` commands.
- **It edits Claude Code's files only from the Settings window, when you click Save:** the `settings.json` and `CLAUDE.md` files you changed there. A file that changed on disk since the window read it is not overwritten, and the previous version of each saved file is kept in `%LOCALAPPDATA%\CCWrapper\backups`.
- **It deletes a session's files only when you pick Delete on its right-click menu and confirm:** the transcript `projects\<folder>\<id>.jsonl` and its `<id>\` folder, for good. A session that is open anywhere is refused, and nothing outside those two paths is touched.
- **It edits `.claude.json` only when you flip a server's folder switch** in Plugins & connectors, the one change there that no `claude` command makes. Like Claude Code itself, it takes the file's lock, reads the file as it is at that moment, changes that folder's list of servers turned off and writes it back; the previous version goes to the same backups folder.
- **Check health** in Plugins & connectors runs `claude mcp list`, which connects to every MCP server (and starts the local ones) to see if it answers, as `/mcp` does.
- **Otherwise it writes only to** `%LOCALAPPDATA%\CCWrapper`: pasted images, the browser profiles, settings backups, `app.json` (CCWrapper's own settings: your update choice, the compact hint and the browser choice), and the live status data described next.
- **Otherwise it writes only to** `%LOCALAPPDATA%\CCWrapper`: pasted images (deleted after 7 days), the browser profiles (a Browser cache over 100 MB is cleared at start; cookies stay until you clear them), settings backups, `app.json` (CCWrapper's own settings: your update choice, the compact hint and the browser choice), and the live status data described next.
- **The status line:** if an account has no `statusLine` of its own, each session gets a tiny silent relay (`CCWrapper.StatusRelay.exe`) through `--settings`, which feeds the bottom bar. If you have your own status line, it's left alone.
- **The Browser tool:** each session also gets that relay as a local MCP server (`ccwrapper-browser`, through `--mcp-config`), and a line in its system prompt (`--append-system-prompt`) to prefer it. The server talks only to its own CCWrapper window, through a local pipe that only your Windows user can open. Pages Claude opens load in the Browser panel's profile for the account (all tabs share it), with your logins, and Claude Code asks permission per tool as it does for any MCP tool. Its scripts and DevTools act with those logins, but DevTools refuses cookie access, downloads, local files, all simulated input (keyboard, mouse, touch and drag), the page history and scripts that persist across pages. Claude opens only http, https, blob, about:blank and data addresses, not local files, and its tools refuse to work on a page that is not a web address. The same holds for every tab. Turn it off in Settings → CCWrapper.
- **Its only network call is the update check,** and only in an installed copy, only after you said yes on first start; the switch is in **About**. It asks GitHub for the latest release and downloads it; GitHub sees your IP address and the app version, nothing else is sent. The Browser panel loads only what you or Claude open in it. Details in [PRIVACY.md](PRIVACY.md).
Expand Down Expand Up @@ -222,7 +223,7 @@ Every step is in the history: `git log --first-parent` shows the merge of each t
- [xterm.js](https://xtermjs.org/) 6 (the VS Code terminal) in WebView2;
- [Porta.Pty](https://www.nuget.org/packages/Porta.Pty) for ConPTY;
- [DiffPlex](https://github.com/mmanela/diffplex) for diffs;
- xUnit, with 445 tests on synthetic fixtures.
- xUnit, with 497 tests on synthetic fixtures.

## Development

Expand All @@ -234,7 +235,7 @@ dotnet test

```
src/CCWrapper/ WPF app (Theme.xaml: colors and styles, App.xaml: Fluent recolored)
Core/ accounts, paths, the LiveSession contract
Core/ accounts, paths, the LiveSession contract, compose-box drafts
Data/ read-only parsers for Claude Code's files (all tolerant of unknown fields)
Terminal/ ConPTY session, WebView2 + xterm.js host, paste, hidden /usage, process helpers
Views/ picker, main window, tab bar, sidebar, panels, bottom bar, compose box
Expand Down
4 changes: 4 additions & 0 deletions src/CCWrapper/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ protected override async void OnStartup(StartupEventArgs e)
Shutdown(await Smoke.RunAsync());
return;
}
_ = Task.Run(Housekeeping.Run); // old pasted images and status-line files, off the UI thread
// One stray exception must not take every session down with the process (job objects).
DispatcherUnhandledException += OnUnhandled;
// Another window's "Restart now" force-stops this process without OnExit, and the user must activate that window first.
Deactivated += (_, _) => { if (AppUpdate.ReadyVersion is not null) DraftStore.SaveAll(); };
TaskScheduler.UnobservedTaskException += (_, t) => t.SetObserved();
var i = Array.FindIndex(e.Args, a => a.Equals("--account", StringComparison.OrdinalIgnoreCase));
var account = i >= 0 && i + 1 < e.Args.Length ? AccountStore.Find(e.Args[i + 1]) : null;
Expand All @@ -35,6 +38,7 @@ bool AskUpdateCheck() => Dialog.Show(Windows.OfType<Window>().FirstOrDefault(),

protected override void OnExit(ExitEventArgs e)
{
DraftStore.SaveAll();
AppUpdate.ApplyOnExit();
base.OnExit(e);
}
Expand Down
Loading