> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oneshotagent.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture and webhooks

> Local components, storage and event intake contracts.

```
   ┌──────────────────────────────────────────────┐
   │  apps/cli    apps/server      apps/web       │   ← surfaces
   │  commander   Bun.serve+SSE    Vite+React     │
   └───────────────────────┬──────────────────────┘
                           │
   ┌───────────────────────┴──────────────────────┐
   │  packages/*  — the brains, shared by all 3   │
   │  core · intel · plays · find · prompts ·     │
   │  doctor · shared-types                       │
   └───────────────────────┬──────────────────────┘
                           ▼
   ┌──────────────────────────────────────────────┐
   │  @oneshot-agent/sdk — OneShot primitives     │
   │  email · SMS · voice · research · enrichment │
   │  browser · build · signed receipts           │
   └──────────────────────────────────────────────┘
```

**State** — one `~/.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/signup` requires `name`, `email`, and `phone`; optional fields are `signupContext`, `callWindow`, and `linkedinUrl`. Accepted ICP matches enqueue the `concierge` play.
* `POST /api/triggers/cal-no-show` requires `name`, `email`, `company`, `missedAt`, and `rescheduleLink`; optional fields are `phone`, `whatTheyWanted`, and `linkedinUrl`. Accepted ICP matches enqueue `demo-no-show`.

Valid matches return `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`:

```bash theme={null}
LINKEDIN_REPLY_WEBHOOK_SECRET=<random-32+-character-secret>
```

Then send one stable event ID per actual reply. At least one of `linkedinUrl` or `email` is required; when both match different prospects the request is rejected.

```bash theme={null}
curl -X POST http://127.0.0.1:3030/api/triggers/linkedin-reply \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <secret>' \
  -d '{
    "source": "expandi",
    "eventId": "provider-event-123",
    "occurredAt": "2026-09-01T12:00:00Z",
    "linkedinUrl": "https://www.linkedin.com/in/example-person"
  }'
```

Successful responses include `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.

```
apps/
  cli/        CLI (commander); src/demo/ seeds the demo install, src/main.ts picks the workspace
  server/     Bun.serve + SSE; tsdown bundle published as `oneshot-gtm-server`
  web/        Vite + React 19 + TanStack + Base UI — dashboard, run form, strategist dock, privacy mode
packages/
  core/       SDK wrapper, SQLite ledger, config + secrets, Gmail transport, JSONL events
  intel/      LLM client, advise, personalize, triage, weekly-review
  plays/      Outreach plays + handoff/icp/pmf modules + cadence engine
  find/       Finders + shared pipeline (manifest scan, dedupe, ICP filter, drain, registry)
  prompts/    Markdown prompts — humanizer canon, per-play, per-extract
  doctor/     Wallet, ledger, key and deliverability health checks
  shared-types/  Wire types shared across CLI / server / web
examples/     Sample target files
launch/       Draft launch posts (unpublished)
docs/         The built-with badge
```

### 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.
