import os from "os" import { createHash } from "node:crypto" import { PostHog } from "posthog-node" import packageJson from "../../package.json" with { type: "json" } import { PLUGIN_NAME, PUBLISHED_PACKAGE_NAME } from "./plugin-identity" import { getPostHogActivityCaptureState } from "./posthog-activity-state" /** @internal test-only seam: keep null in production to use the real implementation. */ let activityStateProviderOverride: typeof getPostHogActivityCaptureState | null = null type OsProvider = Pick let osProviderOverride: OsProvider | null = null function resolveActivityState(): ReturnType { return (activityStateProviderOverride ?? getPostHogActivityCaptureState)() } function resolveOsProvider(): OsProvider { return osProviderOverride ?? os } /** @internal test-only */ export function __setActivityStateProviderForTesting( provider: typeof getPostHogActivityCaptureState, ): void { activityStateProviderOverride = provider } /** @internal test-only */ export function __resetActivityStateProviderForTesting(): void { activityStateProviderOverride = null } /** @internal test-only */ export function __setOsProviderForTesting(provider: OsProvider): void { osProviderOverride = provider } /** @internal test-only */ export function __resetOsProviderForTesting(): void { osProviderOverride = null } const DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com" const DEFAULT_POSTHOG_API_KEY = "phc_CFJhj5HyvA62QPhvyaUCtaq23aUfznnijg5VaaGkNk74" type PostHogCaptureEvent = Parameters[0] type PostHogSource = "cli" | "plugin" type PostHogActivityReason = "run_started" type PostHogClient = { trackActive: (distinctId: string, reason: PostHogActivityReason) => void shutdown: () => Promise } const NO_OP_POSTHOG: PostHogClient = { trackActive: () => undefined, shutdown: async () => undefined, } function isFalsy(value: string | undefined): boolean { return value === "0" || value === "false" || value === "no" } function shouldDisablePostHog(): boolean { if (process.env.OMO_DISABLE_POSTHOG === "true" || process.env.OMO_DISABLE_POSTHOG === "1") { return true } return isFalsy(process.env.OMO_SEND_ANONYMOUS_TELEMETRY?.trim().toLowerCase()) } function hasPostHogApiKey(): boolean { return getPostHogApiKey().length > 0 } function getPostHogApiKey(): string { return process.env.POSTHOG_API_KEY?.trim() || DEFAULT_POSTHOG_API_KEY } function getPostHogHost(): string { return process.env.POSTHOG_HOST?.trim() || DEFAULT_POSTHOG_HOST } function safeCpus(): { length: number; model: string | undefined } { try { const cpus = resolveOsProvider().cpus() return { length: cpus.length, model: cpus[0]?.model } } catch { return { length: 0, model: undefined } } } function getSharedProperties(source: PostHogSource): NonNullable { const cpus = safeCpus() const osProvider = resolveOsProvider() return { platform: "oh-my-opencode", package_name: PUBLISHED_PACKAGE_NAME, plugin_name: PLUGIN_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: cpus.length, cpu_model: cpus.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 configuredClient: PostHog try { configuredClient = new PostHog(getPostHogApiKey(), { ...options, host: getPostHogHost(), disableGeoip: false, }) } catch { return NO_OP_POSTHOG } const sharedProperties = getSharedProperties(source) return { trackActive: (distinctId, reason) => { const activityState = resolveActivityState() if (activityState.captureDaily) { configuredClient.capture({ distinctId, event: "omo_daily_active", properties: { ...sharedProperties, $process_person_profile: false, day_utc: activityState.dayUTC, reason, }, }) } }, shutdown: async () => configuredClient.shutdown(), } } export function getPostHogDistinctId(): string { return createHash("sha256") .update(`${PUBLISHED_PACKAGE_NAME}:${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 createPluginPostHog(): PostHogClient { return createPostHogClient("plugin", { enableExceptionAutocapture: false, enableLocalEvaluation: false, strictLocalEvaluation: true, disableRemoteConfig: true, flushAt: 1, flushInterval: 0, }) }