~/.oneshot-gtm/ledger.sqlite is the source of truth for all three surfaces: receipts, prospects, sequence events, cadence state, deal outcomes, interviews, target queue, triggers, bounces, and sender assignments. ONESHOT_GTM_HOME relocates the whole directory.
Secrets — ~/.oneshot-gtm/.env, chmod 600, auto-loaded on first import.
Server — single-user, local-first, binds 127.0.0.1 only. Dashboard routes rely on that local boundary. Keep unsigned intake endpoints private or protect them at your reverse proxy; the LinkedIn reply endpoint has its own bearer authentication.
Trigger webhooks
Two JSON endpoints feed warm product signals through the normal ICP filter and review queue:POST /api/triggers/signuprequiresname,email, andphone; optional fields aresignupContext,callWindow, andlinkedinUrl. Accepted ICP matches enqueue theconciergeplay.POST /api/triggers/cal-no-showrequiresname,email,company,missedAt, andrescheduleLink; optional fields arephone,whatTheyWanted, andlinkedinUrl. Accepted ICP matches enqueuedemo-no-show.
202; ICP rejections return 200 with accepted: false; malformed JSON or fields return 400. Signup deliveries deduplicate by lowercase email, while no-shows deduplicate by lowercase email + missedAt.
Set WEBHOOK_SECRET to authenticate both endpoints. Send X-Webhook-Signature: t=<unix-seconds>,v1=<hex> where the hex value is an HMAC-SHA256 of <timestamp>.<raw JSON body>. Signed deliveries are accepted for five minutes and may be used only once. When the secret is unset, intake remains unsigned for backwards-compatible local use.
LinkedIn reply webhook
Public LinkedIn inbox APIs require partner approval, so OneShot exposes a provider-neutral intake that Expandi, Zapier, Make, n8n, or another automation can map into. Set a random bearer secret in/setup or ~/.oneshot-gtm/.env:
linkedinUrl or email is required; when both match different prospects the request is rejected.
duplicate, prospectId, cadencesStopped, and inFlightSends. Retries of the same source + eventId succeed without applying the event twice. The endpoint stops OneShot email; the source tool remains responsible for stopping its own LinkedIn automation.
Stack
Bun 1.3+ · Turborepo with a Bun catalog · Vitest 4 · oxlint + oxfmt · TypeScript 6 (verbatimModuleSyntax, noUncheckedIndexedAccess, noImplicitOverride) · Vite 8 + React 19 + TanStack Router/Query + Base UI + Tailwind 4 · tsdown for the server bundle · bun:sqlite · BYO LLM via OpenRouter, OpenAI or Anthropic.
Plain async/await throughout — no monadic abstractions to learn before reading the code. Keeps it forkable.