A minimal desktop shell for Claude: a Blazor web UI plus a small Node sidecar that
drives Claude through the Claude Agent SDK. No system prompt, no governance, no
integrations — plain Claude with all of its native tools, where anything that writes,
runs a command, or reaches the network pauses for your Allow/Deny before it runs.
Read-only inspection (Read/Grep/Glob) is allowed without a prompt, matching the
real Claude Code client; that line is one edit away if you want it
stricter or looser.
Use it as-is for a local Claude console, or fork it as the starting shell for your own workflow: the sidecar is the single place where prompt, tools, and permissions are decided.
If you're looking for a working example of browser-based tool approval with the
Agent SDK — a canUseTool hook that forwards each approval request to the browser
over SSE and blocks until the user answers, plus streaming responses and session
resume behind an HTTP API — that is exactly what this repo implements
(single-operator, local; see sidecar/basic/index.ts).
Blazor host (:5000) ── spawns ──► BasicSidecar (:6110, Node + Claude Agent SDK)
chat UI, permission dialog, drives Claude (empty prompt, all tools)
questions dialog, usage meters gates writes/commands/egress on the operator
IOperatorConsole seam streams events back over SSE
- Assistant tab — transcript with markdown, composer with file attachments, per-message copy, pop-out chat history, activity log modal.
- Permission gate — writes, commands (
Bash/PowerShell), and network calls pop an Allow/Deny dialog with the actual command/URL/skill shown. Read-only tools (Read/Grep/Glob, plusTodoWrite/ToolSearch) run without a prompt; change theAUTO_ALLOWEDset insidecar/basic/index.tsto gate everything (or less). - Inline images (moderately tested) — when the agent writes a file and references
it as
— or justReads a local image — it renders inline in chat, served through a workspace-scoped/local-fileendpoint. The endpoint's security (only files under the workspace or ones the agent read/wrote this thread) is unit-verified; the rendering works in normal use but hasn't been hammered. Known gaps: base64 image blocks straight off the tool stream aren't rendered, and git-bash/tmp/...paths that Windows can't resolve fall back to a plain tool line. - Questions dialog — when Claude asks a clarifying question
(
AskUserQuestion), you answer in a card UI with an always-available free-text. - Usage meters — live context fill, weekly/5-hour subscription utilization, read straight off the Agent SDK query handle.
- Model & reasoning settings — pick model and effort per thread.
- Session continuity — threads resume across restarts; New Thread starts clean.
- Diagrams & code (moderately tested) — a ```mermaid fence renders inline as an
SVG (mermaid vendored locally, no CDN,
securityLevel:'strict'); other code fences get lightweight in-house syntax highlighting (no CDN). The agent must actually emit the fence — there's no auto-diagram fallback the way there is for images. - Security — model output is untrusted (it can launder file/web content via prompt
injection), so the renderer escapes raw HTML (
<script>/<iframe>/<img onerror>become text) and downgrades external<img>URLs to click-through links; a CDN-free Content-Security-Policy backs it up. - Almost nothing injected —
settingSources: [](no CLAUDE.md, no personal settings, no coding persona). The only injection is a short display hint (DISPLAY_NUDGEinsidecar/basic/index.ts) telling Claude it's in a chat UI that renders images and mermaid diagrams inline — nothing about tools, workflow, or persona. Set it to""for a truly empty prompt.
| Requirement | Why |
|---|---|
| .NET 10 SDK | the Blazor host |
| Node.js (LTS) | the sidecar runs the Claude Agent SDK (Node-only); npm ships with it and installs the sidecar's dependencies |
| A Claude login | a subscription login cached by the claude CLI (Claude Code) is enough — the CLI ships inside the Agent SDK package, no API key needed. Not signed in anywhere yet? The Launcher's Claude sign-in button is one login path: it opens the CLI's interactive sign-in (using the bundled CLI if none is installed). Installing the Claude Code extension for VS Code and signing in there works too — the login is cached per machine in ~\.claude and shared by every session. Skip both if Claude Code or the claude CLI is already signed in on this machine. |
cd sidecar/basic
npm install
npm run build # -> dist/
dotnet run --project src/ClaudeWorkbench.Host # UI on http://localhost:5000The host launches and supervises the sidecar itself (skips if one is already on the
port, kills it on shutdown). The agent works in %LOCALAPPDATA%\ClaudeShell\workspace by default —
override with the WORKSPACE environment variable. Composer attachments land in a
files/ subfolder there.
The repo ships a .vscode/ config so you can build and debug with F5. You need the
C# Dev Kit
extension (it pulls in the .NET debugger); Node just needs to be on PATH.
- Open the repo folder in VS Code (
code .from the repo root). - Press F5 (or Run ▸ Run ClaudeShell (host + sidecar)).
The launch runs a build all task first — it does npm install + npm run build
in sidecar/basic, then dotnet build on the host — so a fresh clone works on the first
F5 with no manual setup. When the host reports "Now listening on…", VS Code opens
http://localhost:5000 in your browser; the host spawns and supervises the sidecar as usual.
What the config provides:
.vscode/launch.json— the Run ClaudeShell (host + sidecar) launch profile (ASPNETCORE_URLS=http://localhost:5000; editenvthere to change the port)..vscode/tasks.json—sidecar: npm install,sidecar: build,host: build, and the compositebuild all. Run any of them standalone via Terminal ▸ Run Task….
Subsequent F5s reuse the installed node_modules, so they only rebuild what changed.
Not signed in to Claude yet? If you install the
Claude Code extension for VS Code
and sign in, that caches a machine-wide login in ~\.claude that ClaudeShell picks up — no
separate sign-in needed.
Each session is one host process with its own port pair and workspace:
$env:ASPNETCORE_URLS = "http://localhost:5001"
$env:Sidecar__Port = "6111"
$env:WORKSPACE = "C:\somewhere\else"
dotnet run --project src/ClaudeWorkbench.HostOr use the WinForms launcher sample:
dotnet run --project samples/launcherCreate/start/stop named sessions with auto-assigned ports; a session started on a launcher-created temp workspace has its folder deleted when it stops, so scratch sessions leave nothing on disk. The Launcher's Claude sign-in button drops a menu (sign in / check status / sign out) that runs the CLI's interactive auth in its own console — the login is cached per machine and shared by every session.
dotnet run is for development. To get an installed, double-clickable ClaudeShell —
the host, sidecar, and Launcher side by side with a desktop shortcut — run the publish
script from the repo root:
.\scripts\publish-live.ps1 # -> C:\ClaudeShellLive
.\scripts\publish-live.ps1 -Destination D:\ClaudeShell -CleanIt publishes the host and Launcher (dotnet publish -c Release), builds the sidecar with
npm, and mirrors the sidecar's node_modules into the output.
The result is a self-contained install root that works wherever you move the folder:
<Destination>\ (default C:\ClaudeShellLive)
host\ ClaudeWorkbench.Host.exe — the Blazor app
sidecar\ dist\index.js + node_modules — the Claude Agent SDK driver
launcher\ ClaudeShell.Launcher.exe — the multi-session manager
scripts\ launch-shell.ps1 — single session, no Launcher
ClaudeShell Launcher.lnk — shortcut (also placed on the Desktop)
The Launcher finds the host at <root>\host and the sidecar at <root>\sidecar.
Quick start after publishing: double-click ClaudeShell Launcher (on the Desktop) →
New Session → Open. For a single session without the Launcher, run
scripts\launch-shell.ps1. Sessions started on a Launcher-created temp workspace have their
folder deleted when they stop.
Useful flags: -Configuration Debug, -NoShortcut (skip the Desktop shortcut; one is still
written into the install folder), -Clean (remove host\/sidecar\/launcher\ first).
Target-machine requirements (no SDK needed to run a published install):
- .NET 10 runtime (or SDK)
- Node.js on
PATH— theclaudeCLI ships inside the sidecar'snode_modules, so no separate CLI install is needed - A Claude login in
~\.claude(use the Launcher's Claude sign-in button if not signed in yet)
src/ClaudeWorkbench.Host/ Blazor host — assistant tab, gate + questions dialogs, settings
sidecar/basic/ BasicSidecar — Node driver on the Claude Agent SDK
samples/launcher/ WinForms multi-session launcher sample
scripts/ publish-live.ps1 (install + Launcher shortcut) · launch-shell.ps1
This repo is deliberately small, and so is its documentation surface:
- This README — setup, running, multi-session, fork points.
sidecar/README.md— the sidecar's HTTP/SSE contract and env vars.- In-app Help (the Help button in the UI) — the user guide: the permission gate, the questions dialog, composer controls, workspace and sessions.
- The About page (
/about) shows the live install's version, ports, and paths.
There are no automated tests here — the governed test suites belonged to the
engine this shell was factored out of and left with it. The sidecar has
npm run typecheck; the solution builds with dotnet build ClaudeWorkbench.slnx.
sidecar/basic/index.ts is the whole policy surface:
DISPLAY_NUDGE/systemPrompt— the injected prompt. Ships as a display-only hint (images/diagrams inline); replace it with your role card, or set""for none.AUTO_ALLOWED— the set of tools that skip the gate. It ships with the read-only and bookkeeping tools (Read/Grep/Glob/TodoWrite/ToolSearch); empty it to gate literally everything, or add tools (e.g.Write) to prompt less.canUseTool— the gate itself: decide what pauses, auto-allows, or is denied.- SDK options — register MCP servers, change
settingSources, restrict tools.
The host UI binds only to IOperatorConsole/IApprovalQueue
(src/ClaudeWorkbench.Host/Console), so the whole backend can be swapped without
touching the UI.
For a more detailed, fully-worked example of building a real workflow on this shell — governed edits, staging, review gates, a Roslyn code index — see the ClaudeWorkbench repository.