Skip to main content
Finders discover prospects, ICP-filter them, and enqueue into /queue for one-click approve or reject. Each runs as a trigger with its own interval and spend cap. Only show-hn and post-funding-auto are on by default; enable the rest from /queue. A trigger missing required config reads as not ready — the toggle and Run button disable with the reason, and the API returns 409, so scripted callers can’t bypass the gate either. Per-run caps (maxCostUsd on a finder, maxSpendPerRun on x-reposters) bound one call; they don’t stop independently-scheduled finders and automatic drains from collectively overspending across a day. config spend-ceiling <amount> (or the Wallet card on /setup) sets an install-wide daily USD ceiling, checked before every automated finder run and drain — a reservation held for the call’s duration closes the race between two concurrent automated paths, so they can’t both slip under the ceiling before either one’s spend has posted. Once reached, scheduled/run-now finders and drains halt with a named reason (daily spend ceiling reached ($X.XX/$Y.YY spent today)) visible on the trigger cards and in doctor; manual /queue sends (approve, reject, mark-sent, send-draft) are never gated by it — a founder reviewing and sending one email by hand is a deliberate decision the ceiling should never block. The counter resets at local midnight, same boundary the per-identity send caps use. Unset (the default) is unlimited, matching the historical behavior. Qualified first-touch rows receive a product dossier before the trigger completes: up to two known first-party pages plus quick external research covering the product, ecosystem, architecture, and business model. Set productResearch: false on a trigger to disable it. Research counts toward that trigger’s maxCostUsd; a failure or exhausted cap leaves the row reviewable with an explicit warning. find research-products backfills the same context onto active/replied prospects and pending queue rows (--dry-run, --limit, and --refresh are supported). Use --first-party-only for a resilient bulk backfill when the external research provider is unavailable. Before any paid findEmail, a prescreen skips dud domains (*.vercel.app, social hosts, link aggregators, personal email providers) and inputs whose “name” is obviously a username. LinkedIn URLs are captured on every finder path and verified to belong to the person before they’re stored. Two ICP gates run per candidate, not one. The topic gate judges the source — the repo, event, or announcement — and keeps whole categories of noise out before any spend. The person gate judges the human’s role, staged by cost: free role text the finder already holds (an event bio, an extracted title), then the job title off the enrichment every verified email already pays for, then — only when still ambiguous and a LinkedIn URL exists — one extra ~$0.005 lookup. It judges capability to build and self-adopt, not job-title seniority: students shipping hackathon projects and consultants building agent systems for clients pass; a Marketing Manager at a brilliant AI company doesn’t. Only a positive reject drops a candidate — ambiguity escalates or proceeds, never silently discards. Rejections land in /queue as auditable auto: role — <reason> rows you can override, count as role-drop on trigger cards, and a prospect judged off-ICP after contact stops receiving cadence follow-ups (terminal status off-icp). The dashboard server runs an in-process scheduler, so enabling a trigger is enough — no separate daemon. find watch stays useful for cron and headless boxes. Approved rows ship via the Drain button or find drain <play>.

Background monitoring as a service

find watch --install-service generates a service file that keeps the watch daemon running in the background — a launchd user agent on macOS, a systemd user unit on Linux. It prints to stdout (redirect-friendly); add --write to drop it at the platform-conventional path. Every path is embedded absolute at generation time — the bun binary, the CLI entry, and the active ONESHOT_GTM_HOME (so --workspace acme find watch --install-service pins the service to that workspace) — because service managers don’t source your shell profile. Regenerate with --write after moving bun or the checkout. macOS (launchd). Logs go to <home>/find-watch.log; the agent restarts on crash and survives reboots.
Linux (systemd user unit). Logs go to the user journal: journalctl --user -u oneshot-gtm-find-watch.
On a headless box, also run loginctl enable-linger $USER once so the user unit starts at boot rather than at first login. Windows (Task Scheduler). There’s no user-service template; schedule the cron-style find watch --once instead, which runs all due triggers and exits:
The classic cron route works on POSIX hosts; Windows users should use Task Scheduler: */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. find watch --once exits 1 when a due trigger errored. Add --fail-on-empty and a run that worked but produced nothing exits 2 instead of 0, with one line on stderr naming the triggers and the zero count — enough for a cron wrapper to tell a dry run from a productive one without reading the ledger. find drain <play> takes the same flag. Both are opt-in: without it, exit codes are exactly what they were. Errors win over emptiness: a run that both errored and produced nothing exits 1, not 2. “Queued nothing” counts candidates that reached the queue, not raw hits scanned — a poll whose every hit was a duplicate or off-ICP left the ledger untouched and reads as empty. --fail-on-empty needs --once; on the daemon (which never ends on its own) it’s rejected rather than ignored.