find watch instead. Run one or the other for a workspace, never both: each polls the same triggers.
The scheduler polls finders and runs the learning jobs. It does not send. A batch goes out when you press Batch send… or run find drain.
The dashboard as a service
ui --install-service generates a service file that starts the dashboard at login and restarts it after a crash: a launchd agent on macOS, a systemd user unit on Linux. It prints the file; --write puts it in place. The workspace’s name, home and port are read from the workspace the command runs in, so run it once per workspace.
com.oneshot-gtm.workspace.<name> (default without named workspaces). Logs go to <home>/dashboard.log.
oneshot-gtm-workspace-<name>.service. Logs go to the user journal: journalctl --user -u oneshot-gtm-workspace-acme.
- The service needs the web build (
bun run --cwd apps/web build) and will not generate without it. - A stop you ask for is respected. Only a crash restarts it.
- It starts at login, not at power-on. On Linux,
loginctl enable-linger $USERchanges that. - After moving bun or the checkout, or changing the port, run
--writeagain and reload. A restart alone keeps the old values. - It needs a clone.
bunx oneshot-gtm-serverhas no service file.
The watch daemon as a service
find watch --install-service does the same for the headless daemon. Paths are written absolute, including the active workspace home, so --workspace acme find watch --install-service pins the service to that workspace.
macOS (launchd). Logs go to <home>/find-watch.log.
journalctl --user -u oneshot-gtm-find-watch.
loginctl enable-linger $USER once so the unit starts at boot.
Windows (Task Scheduler). There is no service template. Schedule find watch --once, which runs every due trigger and exits:
*/15 * * * * ONESHOT_GTM_HOME=$HOME/.oneshot-gtm /path/to/bun /path/to/apps/cli/src/main.ts find watch --once --quiet.
Exit codes for scheduled runs
A cron wrapper needs to tell idle from broken.--fail-on-empty gives it the third answer.
An error beats emptiness: a run that errored and produced nothing exits
1. “Queued nothing” counts candidates that reached the queue, so a poll whose every hit was a duplicate reads as empty. The flag needs --once; the daemon rejects it.
Spend ceiling
Per-run caps bound one run. They do not stop several finders from overspending across a day.config spend-ceiling <amount>, or the Wallet card on Setup, sets a daily limit in USD for the whole workspace.
- Checked before every finder run and every batch send. Spend is reserved first, so two runs at once cannot both slip under it.
- Once reached, finders and batches stop with a named reason,
daily spend ceiling reached, shown on the trigger cards and indoctor. Approved rows stay approved. - A single send you make by hand is never blocked. That is a decision, and the ceiling is for what runs without one.
- It resets at local midnight. Unset means no limit.