Skip to content

Add generic macOS launchd LaunchAgent starter for the dashboard (docs) #410

Description

@maxscheurer

Summary

The README documents a Linux systemd user unit for auto-restarting the dashboard, but for macOS it only says "macOS users can run the same command under a launchd user agent; a full plist is deferred." We should ship a complete, copy-pasteable launchd LaunchAgent example (a generic "starter" plist) plus the launchctl commands, so macOS users get parity with the Linux instructions.

Current Behavior

  • README (root) has a working systemd --user unit for Linux.
  • macOS is a one-line placeholder — no plist, no launchctl commands, no gotchas documented.

Proposed Behavior

Add a macOS launchd section next to the Linux systemd block in README.md (and cross-link from packages/coding-agent/docs/dashboard.md) containing:

  1. A generic ~/Library/LaunchAgents/com.dreb.dashboard.plist template with placeholders for the absolute paths.
  2. launchctl bootstrap / bootout commands (modern API) to load/unload it.
  3. The rationale + gotchas learned in practice (see Technical Notes).

Starter plist (validated on macOS, local mode)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.dreb.dashboard</string>
    <!-- Invoke node directly on the entry point (from `readlink $(which dreb-dashboard)`)
         so it does not depend on `/usr/bin/env node` resolving a PATH launchd lacks. -->
    <key>ProgramArguments</key>
    <array>
        <string>/opt/homebrew/bin/node</string>
        <string>/ABSOLUTE/PATH/TO/@dreb/dashboard/dist/index.js</string>
        <string>--port</string>
        <string>5343</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>ThrottleInterval</key>
    <integer>10</integer>
    <!-- HOME is set automatically for user agents, so ~/.dreb/agent/auth.json
         (OAuth creds) is found. PATH lets RPC children spawn bash/git/node. -->
    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    </dict>
    <key>StandardOutPath</key>
    <string>/Users/YOU/Library/Logs/dreb-dashboard.out.log</string>
    <key>StandardErrorPath</key>
    <string>/Users/YOU/Library/Logs/dreb-dashboard.err.log</string>
</dict>
</plist>
# load / reload
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.dreb.dashboard.plist
# stop / unload
launchctl bootout   gui/$(id -u)/com.dreb.dashboard
# status
launchctl print gui/$(id -u)/com.dreb.dashboard | grep -E 'state|pid'

Acceptance Criteria

  • Root README.md macOS placeholder is replaced with a full LaunchAgent example + launchctl load/unload/status commands.
  • packages/coding-agent/docs/dashboard.md gains (or links to) the same launchd walkthrough.
  • Docs explain LaunchAgent, not LaunchDaemon (must run as the user to read ~/.dreb/agent and spawn RPC children as that user).
  • Docs note how to obtain the two absolute paths (command -v node and readlink $(command -v dreb-dashboard)), since they vary by install method (Homebrew npm, ~/.npm-global, bun global, nvm).
  • Docs call out that no API-key env vars are needed when using OAuth subscription creds (stored in ~/.dreb/agent/auth.json); users on API-key providers via shell env must add those keys to EnvironmentVariables (LaunchAgents do not source shell profiles).
  • Docs mention the local-only default (binds 127.0.0.1) and point to the Tailscale --remote/--https path for phone access rather than exposing the port.

Technical Notes / learnings

These are the non-obvious points that made a real setup work — worth capturing in the docs:

  1. LaunchAgent, not LaunchDaemon. The dashboard must run as the logged-in user: it reads ~/.dreb/agent/sessions + auth.json and spawns dreb --mode rpc children under that user. A root LaunchDaemon would have the wrong HOME/creds.
  2. Invoke node directly, not the dreb-dashboard symlink. The bin is a #!/usr/bin/env node script; launchd's minimal environment can fail to resolve node via PATH. Point ProgramArguments[0] at the absolute node binary and [1] at the resolved dist/index.js.
  3. PATH matters for children. RPC agent children run bash/git/node; set a sane PATH in EnvironmentVariables (Homebrew + install-prefix bin dirs).
  4. Credentials: OAuth subscription tokens live in ~/.dreb/agent/auth.json and are found via the auto-set HOME, so no secrets in the plist. Only API-key-via-env users need to add keys to EnvironmentVariables.
  5. KeepAlive + ThrottleInterval gives crash auto-restart without a tight fail-loop. Verified: kill -9 the process and launchd respawns it in a few seconds, HTTP 200 restored.
  6. Prefer the modern launchctl bootstrap/bootout gui/$(id -u) API over the deprecated load/unload.

Context

  • README placeholder: root README.md, the line "macOS users can run the same command under a launchd user agent; a full plist is deferred." (right after the Linux systemd example).
  • Should mirror the structure/tone of the existing Linux systemd block.

Suggested labels: documentation, dashboard.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions