Skip to main content

Daemon

The WhatCode daemon (@whatcode-ai/whatcode) is a lightweight Node.js process that runs on your machine alongside OpenCode and connects it to the iOS app.

Why do I need a daemon?​

The daemon gives you three things that plain OpenCode cannot provide out of the box.

Reachable from your phone. By default, OpenCode binds to 127.0.0.1 only. The daemon exposes it on your local network (and optionally over Tailscale) so your iPhone can connect.

A stable address. OpenCode picks a random port on every restart. The daemon always listens on a fixed port (8192), so the app can reconnect automatically without any reconfiguration.

Push notifications. The daemon watches the OpenCode event stream and sends a push to your iPhone the moment an agent goes idle, hits an error, or needs your approval. The app does not need to be open.

Under the hood, the daemon connects to the local OpenCode service (starting it with opencode serve --service if it isn't already running) and layers these capabilities on top. You can still use OpenCode on its own. The daemon adds what the mobile app needs.

Installation​

No installation required. Run it directly with npx:

npx @whatcode-ai/whatcode start

Or install it globally:

npm install -g @whatcode-ai/whatcode
whatcode start

Or use it as a library in your own Node.js project:

npm install @whatcode-ai/sdk
import { createWhatcodeServer } from '@whatcode-ai/sdk';

await createWhatcodeServer({
port: 8192,
tailscale: true,
password: 'secret',
logLevel: 'none',
});

How it works​

When you run the daemon:

  1. It starts OpenCode on localhost (or reuses an existing instance).
  2. It starts a WhatCode server on port 8192 that sits in front of OpenCode, patching responses to improve the mobile experience.
  3. It prints your local network URL and a QR code. Scan it with the app to connect.
  4. If --tailscale is passed, it also prints a secure HTTPS URL on your tailnet.

The WhatCode server layer:

  • Patches project sorting: the /api/project endpoint enriches each project with the timestamp of its most recent message and its latest session, so the app can sort projects by actual activity rather than creation date.
  • Disables caching: forces cache-control: no-cache and x-accel-buffering: no on all proxied responses, which the SSE event stream the app subscribes to depends on.
  • Exposes /info: returns the daemon version and the minimum supported app version.
  • Handles device pairing: the /user/pair endpoint registers the app and returns an access token, /user returns the identity of the host (used by the app to deduplicate connections across multiple machines), and /user/logout removes the device.

Push notifications​

The daemon subscribes to the OpenCode event stream and watches for three events:

  • session.execution.succeeded: the agent finished its turn and is waiting for your input.
  • session.execution.failed: the agent hit an unrecoverable error.
  • permission.asked: the agent needs your approval before it can proceed.

Events from subagent sessions are ignored, so you only get notified for top-level sessions.

When any of these events fire, the daemon sends a push notification to your iPhone via a relay server using APNs. You receive it within seconds, even when the app is in the background. If the event stream drops, the daemon reconnects automatically with exponential backoff.

Password protection​

You can protect your daemon with a password. When set, every request must include the password via HTTP Basic Auth.

Via CLI, set the WHATCODE_PASSWORD environment variable:

WHATCODE_PASSWORD=your-password npx @whatcode-ai/whatcode start

Or add it to a .env file in the directory where you run the daemon:

WHATCODE_PASSWORD = your-password

Via library, pass it as a parameter:

await createWhatcodeServer({ password: 'your-password' });

In the app, enter the password when adding a connection manually. The QR code does not embed the password.

HTTP Basic Auth sends credentials unencrypted, so on a plain HTTP local network URL the password can be read by anyone on that network. Use --tailscale if that matters for your setup.

Log level​

Control how much the daemon logs. The CLI defaults to info (shows info, warnings, and errors). The SDK defaults to none (silent). Use debug to see everything including internal events, notification dispatches, and Tailscale state.

Via CLI:

npx @whatcode-ai/whatcode start --log-level debug

Via library:

await createWhatcodeServer({ logLevel: 'debug' });
LevelDescription
noneSilent, no output (SDK default)
infoInfo, warnings and errors (CLI default)
debugEverything, including internal events and notifications

Tailscale​

Passing --tailscale exposes the daemon over a secure HTTPS tunnel on your Tailscale network:

npx @whatcode-ai/whatcode start --tailscale

This runs tailscale serve --bg <port> in the background, which proxies your local port over HTTPS using your Tailscale hostname (e.g. https://my-mac.tail1234.ts.net). The QR code printed in the terminal includes this URL so the app connects over Tailscale automatically. When you stop the daemon (Ctrl+C), it cleans up the Tailscale serve rule automatically.

Requirements​

  • Tailscale installed and authenticated (tailscale up)
  • The same Tailscale account logged in on your iPhone

Resetting the daemon​

If notifications stop working or device pairings get into a bad state, you can reset the daemon's stored data. This clears all paired devices and the stored notification state. After resetting, open the app and reconnect to pair your device again.

Via CLI:

npx @whatcode-ai/whatcode reset

Via library:

import { resetWhatcodeServer } from '@whatcode-ai/sdk';

await resetWhatcodeServer();

API reference​

createWhatcodeServer(config)​

OptionTypeDefaultDescription
portnumber8192Port the WhatCode server listens on. Change this if 8192 conflicts with another process on your machine.
tailscalebooleanundefinedWhen true, exposes the daemon over HTTPS via Tailscale serve. Requires Tailscale installed and authenticated.
passwordstringundefinedProtects all daemon endpoints with HTTP Basic Auth. The app will prompt for this password when connecting manually.
logLevel'none' | 'info' | 'debug''none'Controls log verbosity. none = silent, info = info/warn/error, debug = everything.

resetWhatcodeServer()​

Clears all paired devices and the stored notification state. Use this if notifications stop working or if you need to unlink all devices from this machine.

CLI reference​

npx @whatcode-ai/whatcode start runs the start command by default, so whatcode and whatcode start are equivalent. The reset command must be called explicitly.

Run npx @whatcode-ai/whatcode --version to print the installed version without starting the daemon.

Flags

FlagTypeDefaultDescription
--portnumber8192Port for the WhatCode server.
--tailscaleboolean-Expose via Tailscale HTTPS.
--log-level'none' | 'info' | 'debug'infoControls log verbosity.
--versionboolean-Print the installed version of @whatcode-ai/whatcode and exit.

Environment variables

VariableDefaultDescription
WHATCODE_PASSWORD-Protect servers with HTTP Basic Auth. Set via environment variable or .env file.

Commands​

CommandDescription
startStart the daemon. This is the default command, so it also runs with no subcommand.
resetReset stored daemon data (paired devices and notification state). Use this if notifications stop working.