I Built a Desktop GUI for Claude Code in One Day — Here's How
Claude Code is amazing. But I got tired of the terminal.
So I built this:
TL;DR:
- 🚀 Run 3 AI tasks in parallel
- 📝 See live diffs before files change
- 💾 115MB portable — works offline
- 🖥️ Windows + Mac + Linux from one codebase
💡 What I Learned (The Short Version)
- Electron isn't dead — shipped to 3 platforms in 6 minutes
- Zustand > Redux — for apps this size, simplicity wins
- Ship broken, fix fast — users find bugs faster than you
Want the technical deep-dive? Keep reading. Want to try it? Download here.
🤔 The Problem: Terminal-Only AI
If you've used Claude Code, you know it's incredibly capable. It can read your codebase, write files, run commands, and reason about complex problems.
But it runs only in the terminal.
For quick tasks, that's fine. But when you're:
- Running multiple AI tasks simultaneously
- Reviewing file changes before approving them
- Context-switching between different projects
- Showing work to non-technical stakeholders
...the terminal becomes a bottleneck.
I wanted something that felt more like pair programming with a colleague than typing into a black box.
🛠️ The Solution: Claude Cowork
Claude Cowork is a desktop application that wraps the official Claude Agent SDK with:
| Feature | What It Does |
|---|---|
| Parallel Task Queue | Run up to 3 AI tasks simultaneously |
| Live Diff Visualization | See file changes before they happen |
| Session Management | Persist conversations in SQLite |
| Plugin System | Auto-discover MCP servers and skills |
| Cross-Platform | Single codebase → Win/Mac/Linux installers |
Let me walk you through how I built each piece.
🏗️ Architecture Overview
┌─────────────────────────────────────────────────────────┐
│ Electron Main Process │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ IPC Handler │ │ Session DB │ │ Settings Manager│ │
│ │ │ │ (SQLite) │ │ (MCP + Skills) │ │
│ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │
│ │ │ │ │
│ └────────────────┼───────────────────┘ │
│ │ │
│ ┌───────────▼───────────┐ │
│ │ Claude Agent SDK │ │
│ │ (Task Runner) │ │
│ └───────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
│ IPC Bridge
▼
┌─────────────────────────────────────────────────────────┐
│ React 19 Frontend │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ Sidebar │ │ Chat View│ │ Progress │ │Settings │ │
│ │ │ │ + Diffs │ │ Panel │ │ Modal │ │
│ └──────────┘ └──────────┘ └──────────┘ └─────────┘ │
│ │
│ Zustand Store │
└─────────────────────────────────────────────────────────┘
Tech Stack:
- Electron 39 — Cross-platform desktop framework
- React 19 — UI with latest features (Suspense, transitions)
- Zustand — Lightweight state management
- better-sqlite3 — Session persistence
- @anthropic-ai/claude-agent-sdk — The AI brain
⚡ Deep Dive #1: Parallel Task Queue
The killer feature. Most AI coding tools process one request at a time. Claude Cowork can handle three.
The Problem
Claude Agent SDK runs tasks synchronously. If you start a task, you wait until it completes.
The Solution
I implemented a task queue manager that:
- Accepts unlimited tasks into a queue
- Runs up to
MAX_CONCURRENT_TASKS(3) simultaneously - Broadcasts status updates via IPC
// src/electron/ipc-handlers.ts
const MAX_CONCURRENT_TASKS = 3;
const taskQueue: QueuedTask[] = [];
const runningTasks = new Map<string, { task: QueuedTask; sessionId: string }>();
async function processTaskQueue() {
while (runningTasks.size < MAX_CONCURRENT_TASKS) {
const nextTask = taskQueue.find(t => t.status === "queued");
if (!nextTask) break;
// Start task in background
nextTask.status = "running";
nextTask.startedAt = Date.now();
const session = await createSession(nextTask.cwd);
runningTasks.set(nextTask.id, { task: nextTask, sessionId: session.id });
// Run Claude (non-blocking)
runClaude({
sessionId: session.id,
prompt: nextTask.prompt,
cwd: nextTask.cwd,
onEvent: (event) => broadcast(event)
}).then(() => {
completeTask(nextTask.id);
processTaskQueue(); // Check for more tasks
});
}
}
The UI
Users see a collapsible panel showing:
- 🟢 Running tasks (with stop button)
- 🟡 Queued tasks (with cancel button)
- Toast notifications when tasks complete
// TaskQueuePanel.tsx
<div className="task-queue-panel">
{runningTasks.map(task => (
<div key={task.id} className="task-item running">
<span className="pulse-dot" />
<span>{task.prompt.slice(0, 40)}...</span>
<button onClick={() => cancelTask(task.id)}>Stop</button>
</div>
))}
</div>
Result: Queue "write tests for auth module", "update README", and "fix TypeScript errors" — they all run in parallel.
📝 Deep Dive #2: Live Diff Visualization
When Claude edits a file, you should see exactly what changed before it happens.
The Implementation
I intercept Edit and Write tool calls and render inline diffs:
// EventCard.tsx
function DiffView({ oldContent, newContent, filePath }: DiffProps) {
const changes = diffLines(oldContent, newContent);
return (
<div className="diff-container">
<div className="diff-header">
<span className="file-icon">📄</span>
<span className="file-path">{filePath}</span>
</div>
<pre className="diff-content">
{changes.map((change, i) => (
<span
key={i}
className={
change.added ? 'diff-added' :
change.removed ? 'diff-removed' :
'diff-unchanged'
}
>
{change.value}
</span>
))}
</pre>
</div>
);
}
Before vs After
Terminal (Claude Code):
Editing src/utils.ts...
Done.
Claude Cowork:
// src/utils.ts
export function formatDate(date: Date) {
- return date.toString();
+ return date.toISOString().split('T')[0];
}
You see the change, approve it, and move on. No guessing.
🔌 Deep Dive #3: Plugin System (MCP + Skills)
Claude Cowork auto-discovers capabilities from two sources:
1. MCP Servers
Model Context Protocol servers extend Claude's abilities. I built a settings manager that:
// settings-manager.ts
const BUILT_IN_MCP_SERVERS: MCPServer[] = [
{
id: "filesystem",
name: "Filesystem",
description: "Read and write files",
enabled: true,
builtIn: true,
config: { command: "npx", args: ["-y", "@anthropic-ai/mcp-filesystem"] }
},
{
id: "fetch",
name: "Web Fetch",
description: "Fetch URLs and web content",
enabled: false,
builtIn: true,
config: { command: "npx", args: ["-y", "@anthropic-ai/mcp-fetch"] }
}
];
// Also loads external servers from ~/.claude/settings.json
2. Skills Auto-Discovery
Skills are markdown files with instructions. Claude Cowork scans ~/.claude/skills/ and presents them in the settings modal:
function discoverSkills(): SkillInfo[] {
const skillsDir = join(homedir(), ".claude", "skills");
const skills: SkillInfo[] = [];
for (const folder of readdirSync(skillsDir)) {
const skillPath = join(skillsDir, folder, "SKILL.md");
if (existsSync(skillPath)) {
const content = readFileSync(skillPath, "utf-8");
const { name, description, triggers } = parseSkillFrontmatter(content);
skills.push({ id: folder, name, description, triggers, enabled: true });
}
}
return skills;
}
Result: Install any Claude Code skill → it automatically appears in Claude Cowork.
🚀 Deep Dive #4: Cross-Platform CI/CD
One codebase should produce installers for all platforms. Here's my GitHub Actions workflow:
# .github/workflows/build.yml
name: Build & Release
on:
push:
tags: ['v*']
jobs:
build:
strategy:
matrix:
include:
- os: windows-latest
platform: win
- os: macos-latest
platform: mac
- os: ubuntu-latest
platform: linux
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run dist:${{ matrix.platform }}
- uses: actions/upload-artifact@v4
with:
name: claude-cowork-${{ matrix.platform }}
path: |
release/*.exe
release/*.dmg
release/*.AppImage
release/*.deb
Push a tag → Get installers for 3 platforms in ~6 minutes.
Output Sizes
| Platform | Format | Size |
|---|---|---|
| Windows | NSIS Installer | 129 MB |
| Windows | Portable EXE | 115 MB |
| macOS | DMG | 147 MB |
| Linux | AppImage | 131 MB |
| Linux | .deb | 89 MB |
🐛 Challenges & Solutions
Challenge 1: Native Modules on Windows
better-sqlite3 requires native compilation. electron-builder handles this, but I hit symlink permission errors on Windows.
Solution: Add signAndEditExecutable: false to skip code signing during development.
Challenge 2: State Sync Between Processes
Electron has two processes (main + renderer). They need to stay in sync.
Solution: Zustand store in renderer + IPC event broadcasting from main:
// Main process
function broadcast(event: ServerEvent) {
BrowserWindow.getAllWindows().forEach(win => {
win.webContents.send("server-event", event);
});
}
// Renderer process
useEffect(() => {
window.electron.onServerEvent((event) => {
handleServerEvent(event); // Updates Zustand store
});
}, []);
Challenge 3: Stopping Running Tasks
Claude Agent SDK doesn't expose a clean abort mechanism.
Solution: AbortController pattern:
export async function runClaude(options: RunOptions): Promise<RunnerHandle> {
const abortController = new AbortController();
// Pass signal to SDK (if supported) or handle manually
const promise = claude.run({
...options,
signal: abortController.signal
});
return {
promise,
abort: () => abortController.abort()
};
}
🎓 What I Learned
Electron is still viable — Despite the "Electron bad" memes, it ships fast and works everywhere.
React 19 is nice — Suspense boundaries and transitions make async UI smoother.
Zustand > Redux — For apps this size, Zustand's simplicity wins.
CI/CD is table stakes — Automated builds save hours and catch platform-specific bugs early.
Ship early — The first version had bugs. Users found them faster than I would have.
🔮 What's Next
- [ ] GUI settings for API keys — Remove dependency on Claude Code CLI
- [ ] Multi-agent orchestration — Spawn specialized agents for different tasks
- [ ] Git integration — Auto-commit checkpoints during long tasks
- [ ] Project memory — Remember context across sessions
🎮 Try It Yourself
Download: GitHub Releases
Requirements:
- Claude Code installed and authenticated
- Node.js 18+ (for development)
Run from source:
git clone https://github.com/Skills03/Claude-Cowork.git
cd Claude-Cowork
npm install
npm run dev
💭 Final Thoughts
Claude Code changed how I write software. Claude Cowork makes that experience visual, parallel, and shareable.
If you're building AI-powered developer tools, the playbook is:
- Wrap existing SDKs (don't reinvent)
- Add the UX layer users actually need
- Ship cross-platform from day one
- Open source everything
The best tools feel like extensions of your brain. That's what I'm building toward.
Thanks for reading! If you found this useful:
Built with Claude Code + Claude Cowork (yes, recursively) 🔄


Top comments (0)