import os from 'os'; import { detectAgent, isCI } from '../agent.js'; import { isTelemetryEnabled } from '../config.js'; import { TELEMETRY_ASYNC_TIMEOUT_MS, TELEMETRY_IDENTITY_TIMEOUT_MS } from '../constants.js'; import { getVersions } from '../helpers.js'; import { getAccessToken, getUserEmail, storeUserEmail } from '../keyring.js'; import { fetchCliStatus } from '../status.js'; import { deferShutdownUntil, getPostHogClient } from './client.js'; import { getDistinctId, isFirstRun } from './distinctId.js'; export interface TrackCommandOptions { command: string; cliVersion?: string; subdomain?: string; } function getEnvironmentProperties(): Record { const agent = detectAgent(); return { os: os.platform(), arch: os.arch(), node_version: process.version, is_ai_agent: agent !== undefined, ai_agent: agent, is_ci: isCI(), is_tty: Boolean(process.stdin.isTTY), is_first_run: isFirstRun(), }; } async function captureWithTimeout( event: string, properties: Record ): Promise { await Promise.race([ getPostHogClient() .captureImmediate({ distinctId: getDistinctId(), event, properties, }) .catch(() => {}), new Promise((resolve) => { const t = setTimeout(resolve, TELEMETRY_ASYNC_TIMEOUT_MS); t.unref(); }), ]); } async function getIdentity(): Promise<{ loggedIn: boolean; email: string | null }> { try { const [accessToken, storedEmail] = await Promise.all([ getAccessToken(), getUserEmail().catch(() => null), ]); if (!accessToken) return { loggedIn: false, email: null }; if (storedEmail) return { loggedIn: true, email: storedEmail }; const status = await fetchCliStatus(AbortSignal.timeout(TELEMETRY_IDENTITY_TIMEOUT_MS)); if (!status.ok) return { loggedIn: true, email: null }; const email = status.status.user.email; await storeUserEmail(email).catch(() => {}); return { loggedIn: true, email }; } catch { return { loggedIn: false, email: null }; } } export async function trackCommand({ command, cliVersion, subdomain, }: TrackCommandOptions): Promise { if (!isTelemetryEnabled()) return; const work = (async () => { try { const { loggedIn, email } = await getIdentity(); await captureWithTimeout('cli.command.executed', { command, cli_version: cliVersion, ...getEnvironmentProperties(), is_logged_in: loggedIn, subdomain, ...(email ? { email, $set: { email } } : {}), }); } catch {} })(); deferShutdownUntil(work); await work; } async function trackLoginEvent(event: string, extra?: Record): Promise { if (!isTelemetryEnabled()) return; try { const { cli: cliVersion } = getVersions(); await captureWithTimeout(event, { ...extra, cli_version: cliVersion, ...getEnvironmentProperties(), }); } catch {} } export async function trackLoginAttempt(): Promise { return trackLoginEvent('cli.login.attempted'); } export async function trackLoginSuccess(): Promise { return trackLoginEvent('cli.login.succeeded'); } export async function trackLoginFailed(reason: string): Promise { return trackLoginEvent('cli.login.failed', { reason }); } export async function trackEvent( event: string, properties?: Record ): Promise { if (!isTelemetryEnabled()) return; try { const { cli: cliVersion } = getVersions(); await captureWithTimeout(event, { ...properties, cli_version: cliVersion, ...getEnvironmentProperties(), }); } catch {} } export async function trackTelemetryPreferenceChange(options: { enabled: boolean }): Promise { if (process.env.CLI_TEST_MODE === 'true') return; try { const { cli: cliVersion } = getVersions(); await captureWithTimeout('cli.telemetry.preference_changed', { enabled: options.enabled, cli_version: cliVersion, ...getEnvironmentProperties(), }); } catch {} }