Build native desktop applications where Claude Code CLI (`claude -p`) is the runtime, using ElectroBun (Bun + system webview) with typed RPC — no HTTP server, no auth.
설치
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
Build native desktop applications where Claude Code CLI (`claude -p`) is the runtime, using ElectroBun (Bun + system webview) with typed RPC — no HTTP server, no auth.
when_to_use
Use when building a native macOS/Windows/Linux tool with drag-and-drop, file dialogs, system tray, or desktop distribution. Triggers: "desktop app with Claude", "native Claude tool", "ElectroBun Claude app", "Claude on the dock". NOT for web apps (use loom / loom-local), direct Anthropic API, or chat replicas.
Loom Desktop: Native Apps on the Claude Code Runtime
You're helping someone build a desktop application where Claude Code is the
runtime — not a chat wrapper, but a native tool where Claude reads files, runs
commands, and streams structured output to a local UI.
This is the desktop counterpart to web Loom. Where web Loom puts an Express
server between the browser and Claude, desktop Loom removes that layer entirely.
The Bun process spawns claude -p directly and pushes output to the webview via
typed RPC. No HTTP. No SSE formatting. No proxy timeouts. No auth.
Desktop means one user on their own machine. That simplifies everything: no
sessions to manage, no credentials to pass per-request, no rate limiting across
users. The architecture collapses to a thin bridge between subprocess and UI.
Why Desktop
The question to help users explore is: what would you build if Claude had full
access to your local filesystem, ran as a native app on your dock, and could
stream its reasoning to a custom interface — with zero server infrastructure?
A desktop Loom app has things web apps don't:
Full local file access — Claude reads and writes files on the user's machine directly. No upload step.
Native UX — System tray, native menus, file dialogs, drag-and-drop. Feels like a real tool, not a browser tab.
Offline-first — The app launches instantly. Claude needs network for inference, but the interface itself has no server dependency.
No infrastructure — No VM to provision, no server to keep running, no auth to maintain. Ship a binary.
Privacy — Files never leave the machine. Claude processes them locally via the CLI.
Prerequisites
Before building, you need:
Claude CLI installed and on PATH — claude --version should return a version string
Webview: The custom UI. React, vanilla HTML, whatever fits the app. Runs in
a system webview (WKWebView on macOS, WebView2 on Windows, WebKit2GTK on Linux).
Bun Process: The bridge. Receives requests from the webview via typed RPC,
spawns claude -p subprocesses, parses their NDJSON stdout, and pushes results
back as typed RPC messages. This is the Claude Manager — typically 80-100 lines
of TypeScript.
Claude Subprocess: The intelligence. claude -p with the right flags. This
is where the agentic work happens — reading files, running commands, generating
structured output.
Why RPC Instead of HTTP
Web Loom uses Express + SSE because browsers need HTTP. Desktop Loom skips that:
Concern
Web Loom (HTTP/SSE)
Desktop Loom (RPC)
Transport
HTTP + SSE streams
WebSocket (encrypted)
Type safety
Manual JSON parsing
Compile-time typed schema
Proxy timeouts
Heartbeat hacks needed
Not applicable
Auth
OAuth per-request
Not needed (single user)
Complexity
~200 lines of plumbing
~20 lines of plumbing
The typed RPC contract is defined once and shared between Bun and webview. See
references/rpc-schema-reference.md for the full schema.
Project Setup
Scaffold an ElectroBun project:
bunx electrobun init hello-world
cd hello-world
bun install
bunx electrobun dev
This gives you a running app with a Bun process and webview. From here, you'll
add the Claude Manager to the Bun side and the task UI to the webview.
For the full setup guide — project structure, configuration, dev workflow — see
references/electrobun-setup.md.
For Claude CLI flags and output formats, see references/cli-runtime-reference.md.
The Conversation
When someone comes to you with a desktop app idea, walk through these design
questions. Don't dump them all at once — have a natural conversation. But cover
this ground before you start building.
1. What does this app do?
Get concrete about the interface. What does someone see when they open it? What
do they click? What appears when Claude is working? What does the output look
like?
Desktop apps have a stronger "tool" identity than web apps. A web app might be a
dashboard someone visits. A desktop app is something someone installs and
reaches for. What's the tool? What problem does it solve?
Ask: "Walk me through opening this app. What do you see? What do you do first?"
2. What Claude capabilities does it need?
Map the app's features to what Claude does behind the scenes:
Need
Claude Configuration
Read and analyze files
--tools "Read,Glob,Grep"
Modify code or files
--tools "Read,Edit,Write,Glob,Grep,Bash"
Pure reasoning, no file access
Omit --tools and use --permission-mode bypassPermissions
Web research
--tools "WebSearch,WebFetch"
Structured data extraction
--json-schema '{...}'
Custom persona
--system-prompt "You are..."
The tools list determines what Claude can do. Tighter is safer — only grant what
the app actually needs.
Note:--tools "" (empty string) is fragile and causes ambiguous CLI
behavior. For pure reasoning tasks, prefer omitting --tools entirely and
relying on bypassPermissions with no tool invocations, or pass file content
directly in the prompt so Claude doesn't need tools.
3. What's the interaction model?
How does the person's action translate to a Claude invocation?
Interaction
Pattern
Click a button, get a result
Synchronous — spawn, collect, return
Watch progress in real-time
Streaming — tokens flow as RPC messages
Multi-step conversation with context
Conversational — --session-id first turn, --resume <id> after
Long task, minimize to tray
Background — task registry + tray status
Most desktop apps start with Streaming. Add Conversational if the user needs
follow-up questions. Add Background for tasks that take minutes.
4. What desktop features matter?
This is where desktop Loom diverges from web Loom. Ask which native features
the app needs:
File drag-and-drop — Drop files onto the window to feed them to Claude
Native file dialogs — "Open File..." to select input, save results to disk
System tray — Minimize during long tasks, show progress in tooltip
Native menus — App menu bar with New Task, Abort, Model Selection, Settings
Notifications — Native OS notification when a background task completes
Not every app needs all of these. A simple analysis tool might only need
drag-and-drop. A code assistant might need the full set.
5. What are the cost and safety boundaries?
Desktop simplifies auth (no OAuth needed) but cost and permissions still matter:
Budget: --max-budget-usd caps spending per task (print mode only). What's appropriate? $0.50 for quick analysis, $5 for deep codebase review. Pair with --max-turns to bound cost and loop count.
Model: haiku for fast/cheap, sonnet for balanced, opus for best quality; fable is the most capable alias — enable it for the hardest agentic work once it's available on your plan. Aliases resolve to the latest generation of each tier. --fallback-model sonnet,haiku (comma-separated, tried in order) for reliability.
Reasoning depth: --effort low|medium|high|xhigh|max is a separate axis from the model. xhigh is Claude Code's own default for coding/agentic work; use low/medium for quick extraction.
Permissions: Use --permission-mode bypassPermissions with explicit --tools list. dontAsk auto-denies anything not whitelisted, but also blocks reads outside the project directory — meaning dropped files from ~/Desktop won't be accessible. bypassPermissions is safer for desktop apps where you control the prompt.
Filesystem scope: Consider constraining Claude to specific directories via --allowedTools "Read(/path/**)" patterns.
Building It
Default to TypeScript for both the Bun process and the webview. Use
React for the UI unless the person prefers vanilla HTML.
Shared Utilities
Four helpers used by every pattern: resolveClaudePath() (find claude binary),
cleanEnv() (remove nesting guards), createStreamParser() (buffer stdout
into JSON lines), and deriveAndSendRPC() (map stream events to RPC messages).
See references/desktop-patterns.md#shared-utilities for the full
implementations with explanatory prose.
Event Type Mapping
claude -p --output-format stream-json --verbose emits newline-delimited JSON.
Each event maps to an RPC message:
Stream Event
RPC Message
Notes
system (subtype: init)
status (state: "running")
Session started
assistant (text content block)
token
Complete text for thinking models
stream_event (content_block_delta)
token
Incremental for non-thinking models
assistant (tool_use content block)
toolUse
Tool invocation
user (tool_result block)
toolResult
Tool output
result (subtype: success)
done
Session complete with cost
parse error / process crash
error
Process failure
Patterns at a Glance
Most desktop apps start with Streaming — it's the primary pattern. Tokens flow
from Claude's stdout through Bun to the webview as typed RPC messages, with
status heartbeats every 2 seconds.
Read references/desktop-features.md when adding native platform features
to your app.
Distribution
Build with bunx electrobun build --env=stable. Always distribute the DMG,
never a ZIP (zipping strips executable permissions). Claude CLI is an external
dependency — don't bundle it. For clean distribution without Gatekeeper warnings,
set up code signing with an Apple Developer account.
Read references/distribution.md for the complete build, signing, DMG icon,
and auto-update setup.
Patterns at a Glance (extended)
Two additional patterns beyond the four core ones:
The duplex bridge pattern (one long-lived claude -p --input-format stream-json process fed turns on stdin) is covered in
references/desktop-patterns.md#persistent-duplex-bridge-for-multi-turn as a lower-latency alternative to per-turn --resume.
Gotchas
Critical issues from real-world development. These 4 bite everyone:
#1 Env cleaning — Remove CLAUDECODE and CLAUDE_CODE_ENTRYPOINT, but never CLAUDE_CODE_OAUTH_TOKEN. Use cleanEnv().
#2 Stream buffering — TCP chunks split JSON lines. Use createStreamParser, never raw split("\n").
#13 macOS PATH — GUI apps don't inherit shell PATH. Use resolveClaudePath() at startup.
#12 File.path — Doesn't exist in system webviews. Use FileReader.readAsText() via RPC instead.
Bare kill orphans grandchildren — proc.kill() only signals the top-level process. Spawn with detached: true and signal the process group to kill Bash subtrees too. See references/desktop-patterns.md#interrupt-done-right.
The remaining 12 gotchas (numbers match references/gotchas.md):
src/bun/rpc.ts — RPC schema type definition (LoomRPC) and BrowserView.defineRPC<LoomRPC>() with handlers
src/mainview/index.html — HTML shell (ElectroBun does NOT auto-generate this)
src/mainview/index.ts — Webview entry point with Electroview.defineRPC<LoomRPC>() + new Electroview({ rpc }) setup
src/mainview/App.tsx — React UI (or vanilla HTML) with handlers.messages callbacks and task controls
Startup CLI check — verify claude --version succeeds, show setup screen if missing
Error handling for process crashes — proc.exited check, stderr capture, error RPC message
For simple apps, the Bun-side code fits in two files (index.ts + claude-manager.ts).
For complex apps, split RPC definitions, session management, and tray logic into
separate modules.
After generating, run bunx electrobun dev and verify the app launches. Then iterate
based on what the person sees.
After the app is working, ask the user if they'd like help building the
executable for distribution. If yes, run bunx electrobun build --env=stable
and explain:
The build artifacts land in artifacts/
Recipients need Claude CLI installed and authenticated on their machine
Without an Apple Developer certificate, macOS Gatekeeper will block the app —
the recipient can bypass with xattr -cr /path/to/app.app or right-click > Open
For proper distribution without warnings, configure code signing and
notarization in electrobun.config.ts
The Possibility Space
The most interesting desktop Loom apps are not chat interfaces in a window.
They're tools that couldn't exist without an agentic runtime on the local
machine — applications where Claude has direct access to the filesystem, runs
as a native process, and streams its reasoning to a purpose-built interface.
A code review tool that you drag a project folder onto. A document processor
that watches a directory and summarizes new files. A research assistant that
reads your notes and finds connections. A local AI agent with a system tray
icon that's always available.
Help people think about what they actually want to make. The question isn't
"how do I put a chat box in a native window?" but "what would this tool look
like if there were an intelligence behind it — one that could read any file on
your machine and stream its thinking to a custom interface?"