@gtc6244/immediacy-daemon
v0.2.9
Published
Mac daemon — Conductor core: worktrees + headless Claude Code sessions behind a multiplexed WSS protocol.
Readme
@gtc6244/immediacy-daemon
The Conductor core for Immediacy — a daemon that runs wherever your code lives (your Mac, a VPS, or a spare box at home), owns git worktrees, and drives headless coding agents — Claude, Codex, OpenRouter (just about any model), and local Ollama models — behind a single multiplexed WebSocket protocol.
- 🌐 Landing page: https://GetImmediacy.com
- 📦 npm: https://www.npmjs.com/package/@gtc6244/immediacy-daemon
- 🛠 Source / issues: https://github.com/GTC6244/Immediacy (this package lives in
/daemon)
Overview
Immediacy lets you run parallel AI agents on the machine that holds your code and drive them from anywhere — an Android/iOS phone or tablet, a native macOS app, or a browser. Pick any model per workspace: Claude, Codex, OpenRouter (just about any hosted model), or a local Ollama model running on your own hardware — and run it in a coding workspace (git worktree + diff + terminal) or a plain chat (repo-less, free-form). So you can use any model at any time, including your own local models driven remotely from your phone. This package is the daemon that makes that possible.
It runs as a lightweight background service, installed with one CLI command on macOS, Linux, or a VPS (see Install below). Each task gets one git worktree + one headless agent session, isolated from the others, and every client speaks to the daemon over one WebSocket wire protocol. The work is device-fluid: because the daemon is the source of truth, you can start on one device and pick up on another, and a client can connect to several daemons at once to fan work across every machine you own. Remote access needs no inbound internet port — the daemon binds loopback locally and remote clients reach it through a Cloudflare edge that it dials out to.
Flutter / iOS app ⇄ (WSS) ⇄ Cloudflare edge ⇄ (cloudflared outbound) ⇄ daemon (Mac/VPS) → N × (git worktree + agent)
macOS app ⇄ (WS, loopback) ─────────────────────────────────────────────────┘The phone and the daemon never connect directly. The phone hits a public
Cloudflare URL; the daemon only holds an outbound tunnel open. On the same
machine, the macOS app and the bundled browser client talk to the daemon over
loopback (ws://127.0.0.1:8787) — no tunnel needed.
Features
- One worktree + one agent per task. The daemon forks a fresh
git worktreefor every workspace and runs an isolated agent session against it. Deleting a workspace removes the worktree — never the branch or the underlying repo. - Four model providers behind one interface:
- Claude (
claude-opus-4-8,claude-fable-5,claude-sonnet-5, …) via the Anthropic Agent SDK. - Codex (
gpt-5-codex,gpt-5, orCodex (account default)) via the OpenAIcodexCLI in headless, sandboxedexec --jsonmode. - OpenRouter — just about any model (Gemini, DeepSeek, Grok, Qwen, and any
slug from https://openrouter.ai/models). It rides the same Agent SDK
backend as Claude via OpenRouter's Anthropic-compatible endpoint, so all the
same guarantees hold — only billing changes. Needs
OPENROUTER_API_KEY. - Ollama — your own models, running locally. Serves whatever you've
ollama pulled from a local (or remote) Ollama — no API key, no billing, code never leaves the machine. Installed models are auto-discovered into the picker; setOLLAMA_BASE_URLfor a remote box. - A mock backend (
AGENT_MODE=mock) does real worktree writes and diffs with no API credits — ideal for testing.
- Claude (
- Coding workspaces and plain chats. A workspace is either a git worktree (coding: diff + terminal + PR) or a repo-less free-form chat (like the Claude/ChatGPT desktop apps). Every provider runs in both — use any model at any time, for shipping code or just conversation.
- Permission gating by default. Every tool call is deferred to the client
(
event.permission_request→permission.respond) and default-denies on timeout. The daemon never runs--dangerously-skip-permissions; Codex runs sandboxed (-s workspace-write,approval_policy=never) so edits stay inside the worktree. - Live streaming. Assistant messages, session events, tool-use events, git
diffs (
diff.get), and a real PTY terminal (node-pty) all stream over the socket. - End-to-end crypto + multi-device pairing. An optional NaCl box layer (
E2E=1) seals each socket to that device's own key, so a phone, tablet, and desktop pair independently. Pairing is trust-on-first-use behind Cloudflare Access, via a QR code — no shared secret beyond the Access token. - GitHub integration without leaking tokens. Workspace creation can pick from
your GitHub repos using the
ghCLI already authenticated on the daemon's machine — the token never touches the phone, the wire, or this source. - Bundled browser client. The daemon's HTTP server serves a dependency-free
web UI on the same port
cloudflareddials, so the Access login cookie authorizes the same-origin WebSocket. - One CLI, every target. Run in the foreground or install as a supervised
service — launchd on macOS, systemd (
--user) on Linux. Native deps ship prebuilds for macOS + Linux (glibc, arm64/x64), so no compiler toolchain is needed.
Install
npm i -g @gtc6244/immediacy-daemon # public package — no auth neededRequires Node.js ≥ 20, plus git and (for GitHub integration) the gh CLI on
PATH. On musl/Alpine there are no native prebuilds — use Docker (see below).
Run it
immediacy-daemon doctor # verify node / git / gh are present
immediacy-daemon install # write + start the background service
immediacy-daemon status # service state + /healthz probeOr run it in the foreground (Docker, ad-hoc):
immediacy-daemon startCLI commands
| Command | What it does |
|---|---|
| start | Run the daemon in the foreground (Ctrl-C to stop). |
| install [--print] | Install + start the supervised service (launchd/systemd). --print renders the unit without installing. |
| uninstall | Stop and remove the service. |
| restart | Restart the running service. |
| status | Show service state and probe http://127.0.0.1:<port>/healthz. |
| logs [-f] | Print (or -f to tail) the daemon's stdout/stderr logs. |
| doctor | Check node/git/gh on PATH and whether the daemon responds. |
| update [--check] | Update to the latest npm release and restart the service (--check only reports if a newer version exists). |
| help / version | Show help or version. |
The same self-update is reachable over the wire so a connected client can trigger
it: daemon.update.check reports the current/latest version and whether an update
is available, and daemon.update installs it (optionally a pinned version) via
npm install -g and restarts the supervised service to load the new code. Both
answer with daemon.update.result.
Options (also read from the environment):
| Flag | Env | Default | Meaning |
|---|---|---|---|
| --port <n> | DAEMON_PORT | 8787 | Local port to bind (the port cloudflared dials). |
| --host <addr> | DAEMON_HOST | 127.0.0.1 | Bind address (0.0.0.0 to expose on a LAN/emulator). |
| --agent-mode <m> | AGENT_MODE | claude | claude | mock. |
| --e2e | E2E=1 | off | Enable the NaCl box E2E layer (needs a paired peer). |
Cloudflare connection
Remote clients never talk to the daemon's machine directly. cloudflared dials
out from that machine (Mac or VPS) to the Cloudflare edge and holds the
connection open, so there's no inbound port, no port forwarding, and no VPN.
The daemon just binds a local WebSocket on 127.0.0.1:8787; Cloudflare Access does
authentication/authorization at the edge.
app ⇄ (WSS, public URL) ⇄ Cloudflare edge ⇄ (cloudflared outbound) ⇄ daemon (ws://127.0.0.1:8787)End state: the daemon is reachable at wss://your-subdomain.your-domain, gated by
Cloudflare Access, with a service token the app presents. The optional NaCl box
codec (E2E=1) keeps the message payload private even from the edge.
Quick setup:
brew install cloudflared
cloudflared tunnel login # pick your Cloudflare domain
cloudflared tunnel create immediacy # prints a tunnel UUID + creds path
cloudflared tunnel route dns immediacy conductor.example.com~/.cloudflared/config.yml:
tunnel: immediacy
credentials-file: /Users/YOU/.cloudflared/<UUID>.json
ingress:
- hostname: conductor.example.com
service: ws://localhost:8787 # WebSockets are on by default
- service: http_status:404 # required catch-allFull walkthrough — tunnel + Access + service tokens:
docs/cloudflare-setup.md.
Configuration & secrets
The daemon reads config from flags/environment and from ~/.immediacy/:
| What | Where | Notes |
|---|---|---|
| Port / host / agent mode | --port / --host / --agent-mode, or DAEMON_PORT / DAEMON_HOST / AGENT_MODE | Baked into the service unit at install time. |
| E2E crypto | --e2e / E2E=1 | NaCl box layer; needs a paired peer. |
| Provider API keys | ~/.immediacy/credentials.json (0600), or ANTHROPIC_API_KEY / OPENAI_API_KEY / OPENROUTER_API_KEY | Stored key wins over env. Anthropic/OpenAI fall back to a cached CLI/SSO login if unset. |
| Ollama endpoint | OLLAMA_BASE_URL (default http://localhost:11434), optional OLLAMA_API_KEY | Local Ollama is keyless; the base URL points at a remote box if you don't run it on the daemon's machine. Installed models are auto-discovered. |
| NaCl keypair / paired peers | ~/.immediacy/keys.json, peers.json | Auto-created on first run. |
The npm package is public, so its source is world-readable. Runtime secrets are never in the package — they live in
~/.immediacy/on each host. Never commit or bundle API keys, and don't bake them into a Docker image.
Docker (Linux fleet / Alpine)
For reproducible VPS fleets or musl/Alpine (no native prebuilds), a container is cleaner than global npm — run the daemon in the foreground:
FROM node:20-bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN npm i -g @gtc6244/immediacy-daemon
EXPOSE 8787
# Mount ~/.immediacy for persistent keys/credentials; pass keys via env or the mount.
CMD ["immediacy-daemon", "start", "--host", "0.0.0.0"]Keep Macs on native launchd (real PTYs, local filesystem access) rather than Docker.
Day-2 operations
immediacy-daemon status # is it up? is /healthz responding?
immediacy-daemon logs -f # tail stdout/err
immediacy-daemon restart # restart the service
immediacy-daemon uninstall # stop + remove the service
npm i -g @gtc6244/immediacy-daemon@latest # update to the latest published version
immediacy-daemon restartLogs: ~/Library/Logs/immediacy-daemon.{out,err}.log (macOS) or
~/.immediacy/daemon.{out,err}.log (Linux).
Full deploy & publish guide:
daemon/DEPLOY.md.
Links
- Landing page: https://GetImmediacy.com
- GitHub repo: https://github.com/GTC6244/Immediacy
- npm package: https://www.npmjs.com/package/@gtc6244/immediacy-daemon
- Architecture reference:
docs/ARCHITECTURE.md - Cloudflare setup:
docs/cloudflare-setup.md
