← All projects
Internal tool · Electron + local daemon + Claude Code

VibeDesk

Developer tooling

A local AI-assisted development environment: task board, a Fastify daemon that drives Claude Code over stream-json, and a spec-driven flow that blocks implementation until the investigation is approved.

React 19TypeScriptFastifyElectronWebSocketClaude CodeSpec-driven development
Role
Product and engineering, personal project
Timeline
2026
Tech
React 19, Vite 6, Zustand, Fastify 5, WebSocket, Electron 33, Vitest
Status
—
0 reads
Monorepo
3 apps, ~16k lines
Tests
20 files, ~2k lines
Daemon
Fastify on 127.0.0.1
How it's built

Implementation

01

A local daemon that operates the agent

Fastify 5 bound to 127.0.0.1 spawns the Claude Code CLI with stream-json on both ends, no shell, arguments as an array. The prompt goes in over stdin, never argv, and the stream parser turns events, tool-use blocks and control requests into a live log on the WebSocket.

02

Permission as configuration, not as trust

The permission level becomes CLI arguments. The default is acceptEdits plus an allowlist of 19 commands, with git push deliberately left out. The question channel uses permission-prompt-tool and the answer goes back through the process stdin, which turns every agent request into a toast I approve or deny in the UI.

03

Spec-driven development with an execution gate

Each task or project runs in vibe or spec mode. In spec mode the first run is a read-only investigation that returns JSON with refutations, hypothesis, evidence, files, acceptance criteria, tests and out-of-scope. With no approved spec, implementation cannot start. An approved spec can be versioned in the repository as specs/id-slug.md.

Overview

VibeDesk is the tool I use every day to run development with Claude Code. It puts the task board, a project's repositories, the rules the agent must respect and the execution itself in one place, with the log streaming while a task runs. It is not published: it is an internal tool, and this page exists because it holds engineering decisions worth more than the product.

The repository is an npm-workspaces monorepo with three applications:

  • web: React 19, Vite 6, Tailwind 4, Zustand 5 and dnd-kit. State lives in IndexedDB, with JSON export and import for backup. Eight views, from projects and board to execution, rules and stats.
  • daemon: Fastify 5 with WebSocket, listening only on 127.0.0.1. It is what talks to the Claude Code CLI, to git and to gh.
  • desktop: Electron 33 as the shell, with tray, mini window and daemon supervision.

Roughly 16k lines of code and 2k lines of Vitest tests across 20 files in web and daemon.

Why a separate daemon

Browsers do not spawn processes, read the disk or run git. Instead of pushing everything into Electron, the system work went into a local service with its own API. The frontend talks to it over HTTP and WebSocket, and behaves the same in a browser tab or inside the desktop app.

That made the security surface explicit, and it is deliberately small:

  • Loopback only.
  • CORS restricted to localhost, 127.0.0.1, [::1] and Electron's internal protocol.
  • Spawn without a shell, arguments as an array.
  • Prompt delivered over stdin.
  • Disk writes limited by a file allowlist and a path regex for specs.
  • The agent auth token lives in ~/.vibedesk/daemon.json with 0600 permissions, and the API never returns the value, only a hint that one exists.

The spec flow

Spec mode exists because an agent that starts writing too early produces plausible, wrong code. The cycle is:

  1. The task fires a read-only investigation whose prompt opens by saying not to implement anything.
  2. The answer has a fixed shape: refutations first, then hypothesis, evidence, files, acceptance criteria, tests and out-of-scope.
  3. I read it and approve. Without approval, the execution view refuses to start implementation.
  4. The approved spec is injected into the implementation prompt as that task's contract, and optionally becomes a markdown file versioned in the repository.

Asking for refutations before the hypothesis is intentional: it forces a list of what the code contradicts, not only what confirms the first idea.

Details that only show up in use

  • Completion judge: exit code 0 does not mean done. The tail of the log is scanned for signs of a pending question, partial delivery or a blocker before a task moves itself to review.
  • A browser for the agent to check its work: when a project has a dev server, the daemon starts one per folder, reuses anything already running and injects a browser MCP into the run so the agent can open the URL and look at the result.
  • Resuming conversations: runs keep the session id and continue where they stopped, and the daemon also reads Claude Code's own history from ~/.claude/projects.
  • History on disk: the last 60 runs are stored as JSON with atomic rename writes, each keeping up to 500 log lines.
  • Attachments: images pasted into a task are written to disk and their paths enter the prompt, which is how the agent sees a bug screenshot.

Status

Daily use, no public release. Known rough edges: the README is stale in two places, the repository is not under version control yet, and there are manual test files sitting in the root that need to go before anything is published.