@evlog/telemetry brings evlog's wide-event model to tools that run on other people's machines — CLIs, GitHub Actions, dev scripts, and CI jobs. Same philosophy as HTTP logging: one command execution → one structured event, not a stream of analytics calls.
pnpm add @evlog/telemetry
You get command name, sanitized flags, duration, and outcome automatically. Call telemetry.set() only when you have extra counters — numbers and booleans by default. Raw argv is never read; disclosure is generated from your runtime config so it cannot drift from what you actually collect.
Add anonymous telemetry to my CLI or script
From CLI to your backend
The disk outbox (~/.config/{toolName}/telemetry/outbox.ndjson) is a reliability buffer, not where telemetry lives long-term. Each run appends one event locally, then the HTTP drain POSTs the backlog to your ingestion endpoint. Short-lived CI jobs and offline machines drain on the next invocation.
- Run —
my-tool doctorexecutes on the user's machine - Capture —
@evlog/telemetrysanitizes flags, checks consent, and records oneRunEvent - Buffer — the event appends to
~/.config/{toolName}/telemetry/outbox.ndjson - Send — on flush, POST
{ events: RunEvent[] }to your ingest URL - Validate —
parseIngestBody()allowlists tools and custom keys (Ingest) - Store — dedupe on
idempotencyKey, then DB / warehouse / evlog drain - Ack — on
2xx, delivered keys are removed from the outbox
CLI → @evlog/telemetry → outbox.ndjson → POST /ingest → validate → store
Nothing is sent until you configure an endpoint and the server returns a success status. Until then, events stay in the outbox (or are purged on opt-out).
Why install it
You can wire anonymous usage telemetry yourself — outbox file, consent flags, flag sanitization, disclosure markdown, ingest validation, first-run notice. Or you add one dependency and get the evlog playbook baked in.
What you get for ~28 KB
| Published size | ~28 KB ESM (@evlog/telemetry), ~8.6 KB gzip — standalone, no dependency on evlog core |
| Server ingest only | @evlog/telemetry/ingest ~5 KB (~1.7 KB gzip) — parseIngestBody() without pulling citty wiring into your API |
| Runtime deps | citty + std-env only — both sideEffects: false, tree-shakeable ESM |
| Node | 18+ · ESM · sideEffects: false on the package |
Fits wherever your code runs
| Surface | Entry | Lines of integration |
|---|---|---|
| citty CLI | withTelemetry() on the root command | One wrapper + optional defineTelemetryCommands() |
| Script / migrator | createTelemetry() + t.run() | Wrap each logical run |
| GitHub Actions | createGitHubActionsTelemetry() | Same as script; ghaAction / ghaEvent auto-injected |
| Your API (ingest) | parseIngestBody() from @evlog/telemetry/ingest | One validator + one POST route |
No framework lock-in. No PostHog / Segment / custom analytics SDK. No second schema to maintain — disclosure is generated from the same collect config the CLI uses at runtime.
What you skip building
- Privacy-safe flag capture (never reads raw
argv) DO_NOT_TRACK/EVLOG_TELEMETRY/ persisted opt-out with outbox purge- Disk outbox with lockfile, stale-lock recovery, and backlog drain
- Auto-generated disclosure (
TELEMETRY.md+telemetry status) - First-run notice with opt-out instructions
- Typed
telemetry.set()with allowlisted string fields - Server-side ingest validation aligned with the CLI envelope
- Hard guarantee: telemetry never throws, never blocks exit
The payoff
Once ingest is live you can answer product questions that CLI authors usually guess at:
- Which commands are actually used (
doctorvssyncvstelemetry status) - Where runs fail (
outcome,errorCode, version breakdown) - How long operations take (
durationMsper command) - Which versions are still in the wild (
tool.versionon every event) - Whether CI or local dev dominates (
env.ci,env.agent,env.tty)
All from one wide event per run — the same mental model as evlog on the server, without bolting a browser analytics stack onto a terminal tool.
examples/telemetry-playground — pnpm run cli -- doctor, EVLOG_TELEMETRY_DEBUG=1 to inspect payloads, telemetry status for disclosure.See also
- Setup — citty, scripts, GitHub Actions, delivery config
- Ingest — server endpoint, validation, storage
- Reference — envelope, schema extensions, disclosure, consent
- Audit — wide events for security-sensitive actions
- Drain pipeline — forward ingested events into Axiom, Datadog, OTLP, or a custom store