import { createHash } from "node:crypto" import os from "node:os" import { PostHog } from "posthog-node" import packageJson from "../../package.json" with { type: "json" } import { getPostHogApiKey, getPostHogHost, hasPostHogApiKey, shouldDisablePostHog } from "./env-flags" import { getPostHogActivityCaptureState } from "./posthog-activity-state" import { DEFAULT_POSTHOG_API_KEY, DEFAULT_POSTHOG_HOST, EVENT_NAME, PACKAGE_NAME, PRODUCT_NAME, } from "./product-identity" export { DEFAULT_POSTHOG_API_KEY, DEFAULT_POSTHOG_HOST } export type PostHogSource = "cli" | "plugin" | "install" export type PostHogActivityReason = "install_started" | "install_completed" | "cli_run" | "session_start" export type PostHogClient = { trackActive: (distinctId: string, reason: PostHogActivityReason) => void shutdown: () => Promise } type OsProvider = Pick type ActivityStateProvider = typeof getPostHogActivityCaptureState let osProviderOverride: OsProvider | null = null let activityStateProviderOverride: ActivityStateProvider | null = null const NO_OP_POSTHOG: PostHogClient = { trackActive: () => undefined, shutdown: async () => undefined, } type PostHogCaptureEvent = Parameters[0] function resolveOsProvider(): OsProvider { return osProviderOverride ?? os } function resolveActivityStateProvider(): ActivityStateProvider { return activityStateProviderOverride ?? getPostHogActivityCaptureState } function getSafeCpuInfo(): { readonly count: number; readonly model: string | undefined } { try { const cpuInfo = resolveOsProvider().cpus() return { count: cpuInfo.length, model: cpuInfo[0]?.model, } } catch { return { count: 0, model: undefined, } } } function getSharedProperties(source: PostHogSource): NonNullable { const osProvider = resolveOsProvider() const cpuInfo = getSafeCpuInfo() return { platform: PRODUCT_NAME, product_name: PRODUCT_NAME, package_name: PACKAGE_NAME, package_version: packageJson.version, runtime: "bun", runtime_version: process.versions.bun ?? process.version, source, $os: osProvider.platform(), $os_version: osProvider.release(), os_arch: osProvider.arch(), os_type: osProvider.type(), cpu_count: cpuInfo.count, cpu_model: cpuInfo.model, total_memory_gb: Math.round(osProvider.totalmem() / 1024 / 1024 / 1024), locale: Intl.DateTimeFormat().resolvedOptions().locale, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, shell: process.env.SHELL, ci: Boolean(process.env.CI), terminal: process.env.TERM_PROGRAM, } } function createPostHogClient( source: PostHogSource, options: ConstructorParameters[1], ): PostHogClient { if (shouldDisablePostHog() || !hasPostHogApiKey()) { return NO_OP_POSTHOG } let client: PostHog try { client = new PostHog(getPostHogApiKey(), { ...options, host: getPostHogHost(), disableGeoip: false, }) } catch { return NO_OP_POSTHOG } const sharedProperties = getSharedProperties(source) return { trackActive: (distinctId, reason) => { const activityState = resolveActivityStateProvider()() if (!activityState.captureDaily) { return } client.capture({ distinctId, event: EVENT_NAME, properties: { ...sharedProperties, $process_person_profile: false, day_utc: activityState.dayUTC, reason, }, }) }, shutdown: async () => client.shutdown(), } } export function getPostHogDistinctId(): string { return createHash("sha256").update(`omo-claude:${resolveOsProvider().hostname()}`).digest("hex") } export function createCliPostHog(): PostHogClient { return createPostHogClient("cli", { enableExceptionAutocapture: false, enableLocalEvaluation: false, strictLocalEvaluation: true, disableRemoteConfig: true, flushAt: 1, flushInterval: 0, }) } export function createInstallPostHog(): PostHogClient { return createPostHogClient("install", { enableExceptionAutocapture: false, enableLocalEvaluation: false, strictLocalEvaluation: true, disableRemoteConfig: true, flushAt: 1, flushInterval: 0, }) } export function createPluginPostHog(): PostHogClient { return createPostHogClient("plugin", { enableExceptionAutocapture: false, enableLocalEvaluation: false, strictLocalEvaluation: true, disableRemoteConfig: true, flushAt: 1, flushInterval: 0, }) } /** @internal test-only */ export function __setOsProviderForTesting(provider: OsProvider): void { osProviderOverride = provider } /** @internal test-only */ export function __resetOsProviderForTesting(): void { osProviderOverride = null } /** @internal test-only */ export function __setActivityStateProviderForTesting(provider: ActivityStateProvider): void { activityStateProviderOverride = provider } /** @internal test-only */ export function __resetActivityStateProviderForTesting(): void { activityStateProviderOverride = null }