Utility CLI for outbound calls, SMS/iMessage, Gmail, and per-channel history/search.
This repo is intentionally not a campaign manager. It does not own contacts, campaign files, async follow-up policy, reply watchers, callback agents, calendar/workspace management, or cross-channel context assembly. Calling agents/workflows pass explicit phone numbers, email addresses, Gmail IDs, and task text.
All command output is JSON.
outreach health
outreach call init
outreach call place --to <number> --objective <text> [--from <number>] [--persona <text>] [--hangup-when <text>] [--max-duration <seconds>] [--no-amd] [--wait-for-user] [--experimental-local-vad]
outreach call listen --id <callId>
outreach call status --id <callId>
outreach call latency (--id <callId> | --latest)
outreach call latency-test --to <number> [--from <number>] [--max-duration <seconds>] [--timeout <seconds>] [--hold-after-greeting <seconds>] [--experimental-local-vad] [--dry-run]
outreach call hangup --id <callId>
outreach call teardown
outreach sms send --to <number> --body <text> [--service iMessage|SMS]
outreach sms history --phone <number> [--limit <n>]
outreach email send --subject <text> --body <text> (--to <address> | --reply-to-id <messageId>) [--cc <addresses>] [--bcc <addresses>] [--no-reply-all] [--attach <paths...>]
outreach email history (--address <email> | --thread-id <threadId>) [--limit <n>]
outreach email search --query <gmail-query> [--limit <n>]There is no outreach setup command. Create config/workspace files outside this CLI, or point the CLI at an existing workspace.
Secrets live in .env next to the CLI checkout:
TWILIO_ACCOUNT_SID=...
TWILIO_AUTH_TOKEN=...
OUTREACH_DEFAULT_FROM=+15551234567
GOOGLE_GENERATIVE_AI_API_KEY=...
GMAIL_CLIENT_ID=...
GMAIL_CLIENT_SECRET=...Behavior config is loaded from <data_repo>/outreach/config.yaml; for dev-only use, outreach.config.dev.yaml next to the CLI may carry the same config plus data_repo_path.
Data/workspace path resolution order:
OUTREACH_DATA_REPOoutreach.config.dev.yamlnext to the CLI withdata_repo_path- Walk up from
cwdfor.agents/workspace.yaml
The config shape is documented in outreach.config.dev.yaml.example. Call transcripts are written to <data_repo>/outreach/transcripts/<callId>.jsonl; the writer creates that directory on demand.
Calls are stateful and use a local daemon:
CLI command -> Unix IPC -> daemon/server.ts
Twilio webhook tunnel -> Express + WebSocket
Twilio Media Streams <-> mediaStreamsBridge.ts <-> Gemini Live
Important call behavior:
call initstarts the daemon and ngrok tunnel.call placepre-connects Gemini while Twilio dials.- Normal calls proactively greet by default; greeting audio is pre-generated while ringing and flushed after a short post-stream delay.
--wait-for-useris for controlled turn-taking tests. Pair it with--experimental-local-vadto use bridge-side endpointing instead of Gemini automatic VAD.end_callis deferred until Twilio confirms the active outbound audio turn has played via mediamark; this prevents clipped goodbyes.latencyandlatency-testreport pickup-to-audible-greeting for proactive calls and user-speech-to-audible-response for wait-for-user calls.
SMS is stateless except for optional history reads from the local Messages database. Sending uses Messages.app AppleScript and does not read chat.db.
Email is stateless apart from Gmail OAuth tokens stored under the configured data repo. Replies with --reply-to-id derive Gmail threading headers and reply-all recipients.
npm install
npm run build
node dist/cli.js --help
node dist/cli.js healthnpm run build compiles TypeScript, marks dist/cli.js executable, and best-effort syncs skills/outreach/ into the configured agent workspace. If no data workspace is configured, the sync is skipped and the build still succeeds.
TypeScript is ESM. Local imports use .js extensions. All CLI output must go through outputJson() / outputError(). Exit codes: 0 success, 1 input error, 2 infrastructure error, 3 operation failed, 4 timeout.
| Path | Purpose |
|---|---|
src/cli.ts |
Commander.js entrypoint and command registration |
src/appConfig.ts, src/dataRepo.ts |
Config loading and data/workspace resolution |
src/commands/health.ts |
Readiness checks without scaffolding |
src/commands/call/*.ts |
Call lifecycle, monitoring, latency commands |
src/daemon/server.ts |
Call daemon, Twilio webhooks, IPC handling |
src/daemon/mediaStreamsBridge.ts |
Twilio Media Streams <-> Gemini Live bridge, local VAD, playback drain |
src/audio/geminiLive.ts |
Gemini Live API wrapper and tool/callback plumbing |
src/audio/transcode.ts |
μ-law/PCM conversion and resampling |
src/commands/sms/*.ts, src/providers/messages.ts |
SMS/iMessage send and history |
src/commands/email/*.ts, src/providers/gmail.ts |
Gmail send/history/search |
skills/outreach/*.md |
Agent-facing sharable utility docs |
Before cutting a major release:
npm run build
node dist/cli.js --help
node dist/cli.js health
node dist/cli.js call place --help
node dist/cli.js call latency-test --help
node dist/cli.js sms send --help
node dist/cli.js email send --help
npm pack --dry-runFor live call releases, also run:
node dist/cli.js call init
node dist/cli.js call latency-test --to <verified-test-number>
node dist/cli.js call place --to <verified-test-number> --objective 'Say a brief goodbye, then end the call.' --max-duration 30 --no-amd
node dist/cli.js call teardown