Files
oh-my-opencode/packages/omo-claude/src/telemetry/posthog.ts
T
YeonGyu-Kim 024788c823 feat(omo-claude): fork telemetry with distinct identity and derived literals
omo_claude_daily_active event + omo-claude: distinct-id salt; PostHog key/host/
LEGACY_PARENT_PACKAGE kept identical (shared project). platform + tmp-fallback
derived from constants (no literals). 4-flag opt-out incl inherited OMO_*.
cross-package-equivalence + cross-product distinctness tests (RED->GREEN).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 13:40:10 +09:00

189 lines
5.1 KiB
TypeScript

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<void>
}
type OsProvider = Pick<typeof os, "arch" | "cpus" | "hostname" | "platform" | "release" | "totalmem" | "type">
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<PostHog["capture"]>[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<PostHogCaptureEvent["properties"]> {
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<typeof PostHog>[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
}