Skip to content

feat(api-client): emit X-DevHelm-Surface telemetry headers#25

Merged
caballeto merged 1 commit into
mainfrom
feat/surface-telemetry
May 1, 2026
Merged

feat(api-client): emit X-DevHelm-Surface telemetry headers#25
caballeto merged 1 commit into
mainfrom
feat/surface-telemetry

Conversation

@caballeto
Copy link
Copy Markdown
Member

Summary

Reports the CLI's identity to the DevHelm API on every authenticated request so the GTM rollup can attribute usage to the CLI vs. SDKs / MCP / Terraform. Wire contract documented at https://devhelm.io/telemetry; the matching API-side handler shipped in `devhelmhq/mono#332`.

What lands on the wire (defaults)

Header Value
`X-DevHelm-Surface` `cli`
`X-DevHelm-Surface-Version` `package.json` `version`
`X-DevHelm-Cli-Os` `-` (e.g. `darwin-arm64`, `linux-x64`)
`X-DevHelm-Cli-Install-Source` `npm` | `brew` | `other`

Install source detection (in order):

  1. `DEVHELM_INSTALL_SOURCE` env var — set this in the brew formula or any other distribution channel that knows better than the heuristics
  2. realpath of `process.argv[1]`:
    • `/Cellar/`, `/opt/homebrew/`, `/home/linuxbrew/` → `brew`
    • `/node_modules/` → `npm`
  3. Otherwise `other` (deliberately doesn't guess wrong)

Opt-out

`DEVHELM_TELEMETRY=0` returns an empty headers object — the API receives no surface signal at all. Single env var, not per-command flag. Auth + tenant headers are unaffected. Strict equality on `"0"` (so `DEVHELM_TELEMETRY=on` / `true` / `yes` don't accidentally read as opt-out).

Zero callsite changes

`createApiClient` is the single bottleneck for every CLI command, so spreading the headers there covers all 73 commands without touching one of them. The telemetry module (`lib/surface-telemetry.ts`) is a pure function of `process.*` with no external state.

Tests

  • 6 new `test/lib/surface-telemetry.test.ts` tests: defaults, OS detection, install-source override, env opt-out, strict-on-`"0"` parsing
  • Full suite (899) green
  • ESLint + `tsc` clean

Version bumped to `0.6.3` (additive feature; patch bump because no public CLI surface changed).

Test plan

  • `npm run lint` — clean
  • `npm run typecheck` — clean
  • `npm test` — 899 / 899 green
  • Post-release: smoke a CLI call against stage; verify the `org_surface_usage` row + the `cli_first_run` PostHog event

Made with Cursor

Adds CLI surface identification to every authenticated API call so the
GTM rollup can attribute usage to the CLI vs. SDKs / MCP / Terraform.
Wire contract docs at https://devhelm.io/telemetry; matching API-side
handler in mono#332.

Headers emitted by default (every request):
- X-DevHelm-Surface: cli
- X-DevHelm-Surface-Version: package.json version (read at module load
  via createRequire — no release-script edit needed)
- X-DevHelm-Cli-Os: <platform>-<arch>  (e.g. darwin-arm64)
- X-DevHelm-Cli-Install-Source: npm | brew | other
  - DEVHELM_INSTALL_SOURCE env var overrides (set this in the brew
    formula or any other distribution channel that knows better than
    the heuristics)
  - Heuristic checks the realpath of process.argv[1] for /Cellar/,
    /opt/homebrew/, /home/linuxbrew/ → "brew"; /node_modules/ → "npm";
    falls back to "other" rather than guessing wrong

Opt-out is a single env var, not per-command flag: DEVHELM_TELEMETRY=0
returns an empty headers object and the API receives no surface signal
at all. Auth + tenant headers are unaffected. Strict equality on "0" so
DEVHELM_TELEMETRY=on / true / yes don't accidentally read as opt-out.

Zero callsite changes — `createApiClient` is the single bottleneck for
every CLI command, so spreading the headers in there covers all 73
commands without touching one of them. The telemetry module
(lib/surface-telemetry.ts) is a pure function of process.* with no
external state.

Tests: 6 new tests in test/lib/surface-telemetry.test.ts covering
defaults, OS detection, install-source override, env opt-out, and the
strict-on-"0" parsing. Full suite (899) green; eslint + tsc clean.

Bumped to 0.6.3 (additive feature; patch bump because no public CLI
surface changed).

Co-authored-by: Cursor <cursoragent@cursor.com>
@caballeto caballeto merged commit 9a1cc5c into main May 1, 2026
3 checks passed
@caballeto caballeto deleted the feat/surface-telemetry branch May 1, 2026 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant