Help us improve
Share bugs, ideas, or general feedback.
From loom
Use when building a native desktop application that needs Claude Code CLI (`claude -p`) as its runtime, using ElectroBun (Bun + system webview). The Bun process spawns Claude directly via typed RPC — no HTTP server, no auth, no SSE formatting. Triggers: "desktop app with Claude", "native Claude tool", "ElectroBun Claude app", "local AI agent", "Claude on the dock", or any desktop application needing Claude's agentic capabilities through a native interface. NOT for web apps (use loom), direct Anthropic API, or chat replicas.
npx claudepluginhub popmechanic/loomHow this skill is triggered — by the user, by Claude, or both
Slash command
/loom:loom-desktopThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You're helping someone build a desktop application where Claude Code is the
Guides Claude Desktop setup: macOS/Windows install, Git worktrees for parallel sessions, .worktreeinclude config, cloud launches, env vars, MCP extensions (.mcpb), vs CLI. Delegates to docs-management.
Guides desktop app development with Electron and Tauri — cross-platform apps for Windows, macOS, Linux using a single codebase.
Provides expertise in building secure, production-grade Electron apps: IPC patterns, preload scripts, packaging, code signing, auto-update, and native OS integration.
Share bugs, ideas, or general feedback.
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.
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:
Before building, you need:
claude --version should return a version stringbun add electrobun — github.com/blackboardsh/electrobunElectroBun is currently at v1.15.1 stable. It's actively maintained but still evolving — check the issue tracker for platform-specific concerns.
Every Loom desktop app has this shape:
┌─────────────────────────────────────────────────────┐
│ ElectroBun App │
│ │
│ ┌──────────────┐ Typed RPC ┌──────────────┐ │
│ │ Webview │◄───messages────►│ Bun Process │ │
│ │ (React/HTML) │ │ │ │
│ │ │ rpc.request │ Claude │ │
│ │ Renders UI │──startTask()──►│ Manager │ │
│ │ Shows tokens │ rpc.request │ │ │
│ │ Tool status │──abort()──────►│ Spawns │ │
│ │ │ │ claude -p │ │
│ │ │◄─rpc.sendProxy │ │ │
│ │ │ .token() │ Parses │ │
│ │ │ .toolUse() │ stdout │ │
│ │ │ .done() │ stream │ │
│ │ │ .error() │ │ │
│ │ │ .status() │ │ │
│ └──────────────┘ └──────┬───────┘ │
│ │ │
│ stdio │ │
│ ▼ │
│ ┌──────────┐ │
│ │ claude -p │ │
│ │ subprocess│ │
│ └──────────┘ │
└─────────────────────────────────────────────────────┘
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.
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.
Scaffold an ElectroBun project:
bunx electrobun init hello-world
cd myapp
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.
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.
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?"
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.
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.
This is where desktop Loom diverges from web Loom. Ask which native features the app needs:
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.
Desktop simplifies auth (no OAuth needed) but cost and permissions still matter:
--max-budget-usd caps spending per task. What's appropriate? $0.50 for quick analysis, $5 for deep codebase review.--fallback-model haiku for reliability.--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.--max-turns prevents runaway agent loops.--allowedTools "Read(/path/**)" patterns.Default to TypeScript for both the Bun process and the webview. Use React for the UI unless the person prefers vanilla HTML.
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-utilitiesfor the full implementations with explanatory prose.
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 |
tool_result | toolResult | Tool output |
result (subtype: success) | done | Session complete with cost |
| parse error / process crash | error | Process failure |
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.
| Pattern | When to Use | Reference |
|---|---|---|
| Synchronous | Quick extraction, classification | @references/desktop-patterns.md#pattern-1-synchronous |
| Streaming | Primary pattern — tokens flow to UI | @references/desktop-patterns.md#pattern-2-streaming |
| Conversational | Multi-turn with context retention | @references/desktop-patterns.md#pattern-3-conversational |
| Background | Long tasks, tray minimization | @references/desktop-patterns.md#pattern-4-background |
Read
@references/desktop-patterns.mdwhen implementing a specific interaction pattern — it has the complete Bun-side and webview-side code.
| Feature | Description | Reference |
|---|---|---|
| File Drag-and-Drop | Drop files for Claude to analyze (use FileReader, not File.path) | @references/desktop-features.md#file-drag-and-drop |
| Native File Dialogs | Open/save files via Utils.openFileDialog() | @references/desktop-features.md#native-file-dialogs |
| System Tray | Minimize during long tasks, show progress | @references/desktop-features.md#system-tray |
| Native Menus | App menu bar with keyboard shortcuts | @references/desktop-features.md#native-menus |
| File Access Config | Permission modes and tools lists for desktop | @references/desktop-features.md#local-file-access-configuration |
Read
@references/desktop-features.mdwhen adding native platform features to your app.
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.mdfor the complete build, signing, DMG icon, and auto-update setup.
Critical issues from real-world development. These 4 bite everyone:
CLAUDECODE and CLAUDE_CODE_ENTRYPOINT, but never CLAUDE_CODE_OAUTH_TOKEN. Use cleanEnv().createStreamParser, never raw split("\n").resolveClaudePath() at startup.FileReader.readAsText() via RPC instead.The remaining 12 gotchas (numbers match @references/gotchas.md):
{ stream: true } — prevents multi-byte UTF-8 corruptiondontAsk blocks reads outside project dir — use bypassPermissions for desktopassistant text blocks and stream_event deltastaskId immediately, before Claude finishes--setting-sources "" to skip settingsRead
@references/gotchas.mdfor full explanations, code samples, and fixes for all 16 issues.
When you build the app, produce:
electrobun.config.ts — App configuration (name, identifier, version, build options)src/bun/index.ts — Main process entry: startup CLI check, window creation, menu setupsrc/bun/claude-manager.ts — resolveClaudePath(), cleanEnv(), createStreamParser(), spawnClaude(), abort(), heartbeatsrc/bun/rpc.ts — RPC schema type definition (LoomRPC) and BrowserView.defineRPC<LoomRPC>() with handlerssrc/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 }) setupsrc/mainview/App.tsx — React UI (or vanilla HTML) with handlers.messages callbacks and task controlsclaude --version succeeds, show setup screen if missingproc.exited check, stderr capture, error RPC messageFor 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:
artifacts/xattr -cr /path/to/app.app or right-click > Openelectrobun.config.tsThe 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?"