Skip to main content

The devotel CLI model

The devotel CLI exists for one loop the SDKs and curl cannot close: building a webhook handler against live events without deploying anything. An SDK types request/response calls; curl runs them; neither can push a signed message.delivered event into localhost:3000 while you edit the handler. This page explains the model behind that loop — where the credential lives, how the forwarder subscribes to temporary events, and how the four subcommand families divide the surface. For install and first-run usage, see the devotel CLI guide.

Why a dedicated CLI exists

curl and the SDKs cover the request/response plane well. The gap is the inbound plane: webhooks arrive server-to-server, so a local handler under development is unreachable. The standard workaround — a public tunnel such as ngrok — puts your work-in-progress code on a public URL and adds a credential-managed dependency. A first-party CLI closes that loop client-side: it reads the event history over the API (the same endpoint the dashboard reads) and POSTs each event to your localhost, signed with the exact X-Devotel-Signature scheme the production dispatcher emits. Your handler code runs unchanged — the same verifier call, the same envelope shape — so “works locally” and “works in production” are the same claim.

The four subcommand families

Each family owns a plane; none crosses into another’s job. Two read commands (numbers list, sandbox numbers) and the migration surface (migrate twilio|telnyx|status) round out the surface, but the model is the four families above: auth decides who you are, send/numbers decide what you transmit, and listen/tail decide what you watch.

The auth resolution chain

Every command resolves its credential through one chain, first match wins:
  1. An explicit flag — --token <t> at auth login time.
  2. An environment variable — DEVOTEL_TOKEN as the login-time fallback (used in CI where flags would put the key in the argv list).
  3. The workspace config file — ~/.devotel/config.json, written by auth login with 0600 permissions (owner-read-only). Move it with DEVOTEL_CONFIG_HOME; pick a named profile with --profile or DEVOTEL_PROFILE.
The token format decides the auth scheme the client sends: dv_… keys go as X-API-Key, anything else as Authorization: Bearer. auth whoami prints the resolved profile with the token redacted, so it is safe to paste into a chat.

The webhook-forwarding loop

devotel listen is the harness the CLI exists for. A session runs four steps, and all of them are client-side polling plus local POSTs — no new server surface, no public URL:
  1. Open the session. Mint an ephemeral signing secret (whsec_…) unless you pinned one with --secret or DEVOTEL_WEBHOOK_SECRET. The CLI prints it once, on startup.
  2. Subscribe. Poll the first-party GET /events history endpoint — the same Redis replay buffer the dashboard’s event stream reads — tailing forward from a monotonic stream-id cursor. A --types allowlist narrows the subscription (mirroring the server’s filter semantics: no filter delivers everything).
  3. Forward. Re-shape each event into the canonical webhook envelope { id, type, created_at, data } and POST it to your local URL with X-Devotel-Signature: t=<ts>,v1=<hmac> — byte-for-byte the header the production dispatcher emits. Your handler verifies it with the same SDK verifier call it uses in production.
  4. Terminate on exit. Ctrl-C ends the poll loop; the ephemeral secret and the cursor die with the process. Nothing was registered server-side, so nothing needs cleanup. --replay backfills the buffered events first, then tails.

One session, end to end

Start the forwarder while your handler runs on the machine:
The first POST hit a bug in your handler and got a 500. The CLI prints the non-2xx line and keeps the session alive; the platform’s own retry schedule (attempt one of ten, on the delivery-semantics backoff) re-delivers the event, the forwarder sees it again, and this attempt resolves locally once you fix the handler. That is the loop: the retried webhook resolves on your machine, before anything deploys.

CLI planes vs. the dashboard event pages

Use listen/tail when you are developing — you want events pushed into local code, or a raw stream piped into jq. Use the dashboard’s event log and webhook debug pages when you are operating — historical search across the team, dead-letter inspection and replay, endpoint health. The CLI is the inner loop; the dashboard is the shared record. Neither replaces the other.

Map to the guide

Install, login, first send, and the flag-by-flag walkthrough live in the devotel CLI guide. This page is the model behind it: read the guide to run the commands, read this page when you need to explain to a teammate why the credential file sits at ~/.devotel, why the forwarded signature matches production, or why the listen loop was worth a first-party binary.