Skip to content

Repository files navigation

Dream OS

A transparent, nestable desktop element layer for Windows — that Claude can build on.

Rainmeter-style widgets rendered in web tech, sitting on your desktop under your real windows. The difference: Dream OS ships an MCP server, so Claude can read the desktop, add gadgets, update them live, and capture your ideas — through a proper tool interface instead of you hand-editing config files.

Styled after the Freelancer (2003) HUD, because that game's UI is nested translucent frames — which is exactly what this is.

┌─ Claude ──┐   stdio    ┌─ MCP server ─┐   HTTP    ┌─ Dream OS ─┐
│  tools    │ ────────►  │  8 tools     │ ────────► │  desktop   │
└───────────┘            └──────────────┘  :8787    └────────────┘

What it does

  • Transparent full-desktop overlay with dynamic click-through — clicks pass to whatever is underneath, and the layer only becomes solid while your cursor is over a gadget.
  • Sits under your windows. It's a desktop layer, not an always-on-top nag.
  • Nestable elements. Drop a gadget into a group and it becomes a DOM child, so dragging the parent moves everything inside it with zero extra code.
  • Drag, resize, persist. Layout survives restarts.
  • Claude-drivable. Everything above is reachable over MCP.

The toolbag

Every gadget is one self-registering file in renderer/elementals/:

defineElemental('clock', {
  label: 'Clock',
  size: { w: 210, h: 120 },
  render(node, ctx) { /* return an HTMLElement */ },
});

Shipped elementals:

type what it is
capture zero-friction idea box; Enter files a timestamped line
note free text that persists
clock time + date readout
group pure container — drag it, children follow
text static label / paragraph
markdown rendered markdown (safe subset, everything escaped)
list bulleted or checkable list
meter labelled progress bars with tone colouring
status named lamps: ok / warn / bad / idle
table simple data table with sticky headers
log scrolling line feed
link clickable shortcuts (URL or file path)

A gadget reads everything from node.props, which is exactly what Claude writes over the API — so anything you can build by hand, Claude can build too.

MCP tools

tool purpose
dreamos_state list what's on the desktop
dreamos_elementals list the toolbag and each type's props
dreamos_add add an element (optionally nested)
dreamos_update move / resize / retitle / patch props
dreamos_remove delete an element and its children
dreamos_move re-parent an element
dreamos_capture append to the capture log
dreamos_read_captures read the capture log back

The MCP server has zero dependencies — plain Node, newline-delimited JSON-RPC.

Install

npm install
npm start

Quick launch

Dream OS.bat brings up the overlay and both local voice services in one go, and Stop Dream OS.bat shuts them all down. Running it twice is safe — anything already listening is skipped, so it doubles as a "make sure everything is up" command.

.\launch.ps1            # start whatever isn't already running
.\launch.ps1 -Stop      # shut everything down
.\launch.ps1 -NoVoice   # overlay only, skip the speech services

Cold start is about 20 seconds, most of it Kokoro loading its 325 MB model.

Register the MCP server with Claude Code (.mcp.json in your project, or your user config):

{
  "mcpServers": {
    "dream-os": {
      "command": "node",
      "args": ["C:/path/to/dream-os/mcp/server.js"]
    }
  }
}

Dream OS must be running — the MCP server bridges to its control API and will say so plainly if it can't reach it.

Shortcuts

There's a tray icon too — left-click toggles the layer, right-click for the full menu (show/hide, edit, float on top, capture box, data folder, quit).

key action
Ctrl+Alt+H hide / show the layer
Ctrl+Alt+D toggle edit mode (headers, drag handles, toolbar)
Ctrl+Alt+N jump to the capture box
Ctrl+Alt+T float above everything (toggle)
Ctrl+Alt+Q quit — the escape hatch

Talking to Claude from the desktop

The claude elemental is a message box wired to the Claude desktop app. Type, press Enter: the text goes on the clipboard, Claude is focused, pasted and sent. The reply comes back by tailing Claude's own session transcript at ~/.claude/projects/<project>/<session-id>.jsonl.

It costs no extra tokens — it's the same conversation, just a different surface. Records in that transcript are typed, so filtering thinking / tool_use / tool_result from the visible text is the entire "cleaner". No OCR, no screen scraping.

Tag routing

A line beginning #dream:<id> in Claude's prose updates that element — no tool call at all:

#dream:console
All systems nominal.

A JSON object payload is merged into the element's props; anything else becomes props.text. Guards: the element must already exist, the tag must start a line (so inline mentions are inert), and the element flashes gold when touched so the magic is at least visible.

Icon

build/gen-icon.js generates the icon as a real PNG with zero image dependencies — hand-rolled CRC32 and zlib chunks — so it can be re-themed from the same colour values as the CSS rather than living as a binary blob.

node build/gen-icon.js

Where it sits (Wallpaper Engine, Fences, Rainmeter)

Windows has no arbitrary "layers". There is one z-order for normal windows, plus a wallpaper layer (WorkerW) beneath it where Wallpaper Engine and the Fences background live. Fences popups — and every application — are ordinary top-level windows.

So "above the wallpaper, below everything else" is exactly one call:

SetWindowPos(hwnd, HWND_BOTTOM, 0,0,0,0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE)

Electron doesn't expose it, so zorder.js reaches it through koffi (prebuilt, no node-gyp). Dream OS parks at the bottom on startup and re-sinks on blur, so interacting with a gadget raises it briefly and it drops back afterwards. Ctrl+Alt+T pins it above everything when you want the HUD in front.

If koffi is unavailable the module degrades to no-ops and the app still runs — it just won't stay underneath.

Theming

One block at the top of renderer/style.css drives everything:

--h:    258;   /* base hue — 210 blue · 288 purple · 160 green · 20 amber */
--lum:   27%;  /* master darkness. lower = darker */

Panels, insets, borders, text and glow all derive from those via hsl().

Control API

Loopback only, port 8787.

GET    /health          GET    /state[?flat=1]      POST /state
GET    /elementals      POST   /elements            GET  /elements/:id
PATCH  /elements/:id    DELETE /elements/:id        POST /elements/:id/move
GET    /captures        POST   /captures

seed-desktop.js is a worked example that builds a whole desktop through it.

Development

renderer/app.js includes a shim: with no Electron preload it stubs the API and, if Dream OS is running, mirrors the live desktop over HTTP. So you can serve renderer/ as a static site and iterate on styling without relaunching the overlay.

python -m http.server 8757 --directory renderer

Notes from the build

A few things worth knowing, found the hard way:

  • Elements are DOM children, not separate OS windows. One window per widget is the obvious Rainmeter-style approach, but then nesting means manually syncing window positions on every drag. As DOM nodes, nesting is free.
  • .dp-body needs min-height: 0. Flex items default to min-height: auto, which lets content push the inset box out past the panel border.
  • Don't use setPointerCapture for dragging. It routes events to the element and silently kills the whole drag if it throws. Window-level capture-phase listeners are robust, and survive a fast drag outrunning the cursor.
  • Atomic save can fail on EFS-encrypted directories. Where %APPDATA% carries the EFS attribute, Windows treats the encryption boundary as a device boundary: Node's fs.renameSync fails with EXDEV within a single folder, and .NET's File.Replace throws access-denied. The store attempts the atomic path and falls back to a direct write plus a .bak.

License

MIT © minidraco711

About

A transparent, nestable desktop element layer for Windows that Claude can build on - Rainmeter-style widgets with an MCP toolbag. Freelancer-inspired HUD.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages