From be2eee63069d19091090b90ab5b68151aa8b81e7 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 2 May 2026 00:10:54 +0900 Subject: [PATCH 1/7] refactor(telemetry): drop install events from cli-installer --- src/cli/cli-installer.telemetry.test.ts | 74 ------------------------- src/cli/cli-installer.ts | 56 ------------------- 2 files changed, 130 deletions(-) delete mode 100644 src/cli/cli-installer.telemetry.test.ts diff --git a/src/cli/cli-installer.telemetry.test.ts b/src/cli/cli-installer.telemetry.test.ts deleted file mode 100644 index c1b8eb8ac..000000000 --- a/src/cli/cli-installer.telemetry.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { afterEach, describe, expect, it, mock, spyOn } from "bun:test" -import * as configManager from "./config-manager" -import type { InstallArgs } from "./types" - -describe("runCliInstaller telemetry isolation", () => { - afterEach(() => { - mock.restore() - }) - - it("does not crash CLI install when telemetry shutdown throws", async () => { - // given - const restoreSpies = [ - spyOn(configManager, "detectCurrentConfig").mockReturnValue({ - isInstalled: false, - installedVersion: null, - hasClaude: false, - isMax20: false, - hasOpenAI: false, - hasGemini: false, - hasCopilot: false, - hasOpencodeZen: false, - hasZaiCodingPlan: false, - hasKimiForCoding: false, - hasOpencodeGo: false, - hasVercelAiGateway: false, - }), - spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true), - spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.4.0"), - spyOn(configManager, "addPluginToOpenCodeConfig").mockResolvedValue({ - success: true, - configPath: "/tmp/opencode.jsonc", - }), - spyOn(configManager, "writeOmoConfig").mockReturnValue({ - success: true, - configPath: "/tmp/oh-my-opencode.jsonc", - }), - ] - - mock.module("../shared/posthog", () => ({ - createCliPostHog: mock(() => ({ - trackActive: mock(() => {}), - capture: mock(() => {}), - captureException: mock(() => {}), - shutdown: mock(async () => { - throw new Error("shutdown failed") - }), - })), - getPostHogDistinctId: mock(() => "install-distinct-id"), - })) - - const { runCliInstaller } = await import(`./cli-installer?telemetry=${Date.now()}-${Math.random()}`) - const args: InstallArgs = { - tui: false, - claude: "no", - openai: "yes", - gemini: "no", - copilot: "yes", - opencodeZen: "no", - zaiCodingPlan: "no", - kimiForCoding: "no", - opencodeGo: "no", - } - - // when - const result = await runCliInstaller(args, "3.4.0") - - // then - expect(result).toBe(0) - - for (const spy of restoreSpies) { - spy.mockRestore() - } - }) -}) diff --git a/src/cli/cli-installer.ts b/src/cli/cli-installer.ts index 72d5d8d7c..029215757 100644 --- a/src/cli/cli-installer.ts +++ b/src/cli/cli-installer.ts @@ -23,11 +23,8 @@ import { validateNonTuiArgs, } from "./install-validators" import { getUnsupportedOpenCodeVersionMessage } from "./minimum-opencode-version" -import { createCliPostHog, getPostHogDistinctId } from "../shared/posthog" export async function runCliInstaller(args: InstallArgs, version: string): Promise { - const posthog = createCliPostHog() - const distinctId = getPostHogDistinctId() const validation = validateNonTuiArgs(args) if (!validation.valid) { printHeader(false) @@ -65,16 +62,6 @@ export async function runCliInstaller(args: InstallArgs, version: string): Promi const unsupportedVersionMessage = getUnsupportedOpenCodeVersionMessage(openCodeVersion) if (unsupportedVersionMessage) { printWarning(unsupportedVersionMessage) - try { - posthog.capture({ distinctId, event: "install_failed", properties: { command: "install", reason: "unsupported_opencode_version", is_update: isUpdate } }) - } catch { - // telemetry failure is non-fatal, silently ignore - } - try { - await posthog.shutdown() - } catch { - // telemetry failure is non-fatal, silently ignore - } return 1 } } @@ -90,16 +77,6 @@ export async function runCliInstaller(args: InstallArgs, version: string): Promi const pluginResult = await addPluginToOpenCodeConfig(version) if (!pluginResult.success) { printError(`Failed: ${pluginResult.error}`) - try { - posthog.capture({ distinctId, event: "install_failed", properties: { command: "install", reason: "plugin_config_write_failed", is_update: isUpdate } }) - } catch { - // telemetry failure is non-fatal, silently ignore - } - try { - await posthog.shutdown() - } catch { - // telemetry failure is non-fatal, silently ignore - } return 1 } printSuccess( @@ -110,16 +87,6 @@ export async function runCliInstaller(args: InstallArgs, version: string): Promi const omoResult = writeOmoConfig(config) if (!omoResult.success) { printError(`Failed: ${omoResult.error}`) - try { - posthog.capture({ distinctId, event: "install_failed", properties: { command: "install", reason: "omo_config_write_failed", is_update: isUpdate } }) - } catch { - // telemetry failure is non-fatal, silently ignore - } - try { - await posthog.shutdown() - } catch { - // telemetry failure is non-fatal, silently ignore - } return 1 } printSuccess(`Config written ${SYMBOLS.arrow} ${color.dim(omoResult.configPath)}`) @@ -169,29 +136,6 @@ export async function runCliInstaller(args: InstallArgs, version: string): Promi console.log(color.dim("oMoMoMoMo... Enjoy!")) console.log() - try { - posthog.capture({ - distinctId, - event: "install_completed", - properties: { - command: "install", - is_update: isUpdate, - has_claude: config.hasClaude, - has_openai: config.hasOpenAI, - has_gemini: config.hasGemini, - has_copilot: config.hasCopilot, - has_opencode_zen: config.hasOpencodeZen, - }, - }) - } catch { - // telemetry failure is non-fatal, silently ignore - } - try { - await posthog.shutdown() - } catch { - // telemetry failure is non-fatal, silently ignore - } - if ((config.hasClaude || config.hasGemini || config.hasCopilot) && !args.skipAuth) { printBox( `Run ${color.cyan("opencode auth login")} and select your provider:\n` + From 14568db278d335f8ddca9b9d6ebf9fe359e22bc2 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 2 May 2026 00:10:57 +0900 Subject: [PATCH 2/7] refactor(telemetry): drop run lifecycle events and captureException from runner --- src/cli/run/runner.telemetry.test.ts | 2 - src/cli/run/runner.ts | 65 ---------------------------- 2 files changed, 67 deletions(-) diff --git a/src/cli/run/runner.telemetry.test.ts b/src/cli/run/runner.telemetry.test.ts index 3d4b5193b..26e700b10 100644 --- a/src/cli/run/runner.telemetry.test.ts +++ b/src/cli/run/runner.telemetry.test.ts @@ -64,8 +64,6 @@ describe("run telemetry isolation", () => { trackActive: () => { throw new Error("telemetry failed") }, - capture: mock(() => {}), - captureException: mock(() => {}), shutdown: mock(async () => { throw new Error("shutdown failed") }), diff --git a/src/cli/run/runner.ts b/src/cli/run/runner.ts index d6b52a299..75e6e49da 100644 --- a/src/cli/run/runner.ts +++ b/src/cli/run/runner.ts @@ -58,20 +58,6 @@ export async function run(options: RunOptions): Promise { } catch { // telemetry failure is non-fatal, silently ignore } - try { - posthog.capture({ - distinctId, - event: "run_started", - properties: { - command: "run", - agent: resolvedAgent, - has_model: !!options.model, - has_session_id: !!options.sessionId, - }, - }) - } catch { - // telemetry failure is non-fatal, silently ignore - } try { const resolvedModel = resolveRunModel(options.model) @@ -164,38 +150,6 @@ export async function run(options: RunOptions): Promise { }) } - if (exitCode === 0) { - try { - posthog.capture({ - distinctId, - event: "run_completed", - properties: { - command: "run", - agent: resolvedAgent, - duration_ms: durationMs, - message_count: eventState.messageCount, - }, - }) - } catch { - // telemetry failure is non-fatal, silently ignore - } - } else if (exitCode === 1) { - try { - posthog.capture({ - distinctId, - event: "run_failed", - properties: { - command: "run", - agent: resolvedAgent, - exit_code: exitCode, - duration_ms: durationMs, - }, - }) - } catch { - // telemetry failure is non-fatal, silently ignore - } - } - return exitCode } catch (err) { cleanup() @@ -210,25 +164,6 @@ export async function run(options: RunOptions): Promise { if (err instanceof Error && err.name === "AbortError") { return 130 } - try { - posthog.captureException(err, distinctId) - } catch { - // telemetry failure is non-fatal, silently ignore - } - try { - posthog.capture({ - distinctId, - event: "run_failed", - properties: { - command: "run", - agent: resolvedAgent, - error: serializeError(err), - duration_ms: Date.now() - startTime, - }, - }) - } catch { - // telemetry failure is non-fatal, silently ignore - } console.error(pc.red(`Error: ${serializeError(err)}`)) return 1 } finally { From b8a5f27deed3336dfd82c350c81077d90e749f83 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 2 May 2026 00:10:59 +0900 Subject: [PATCH 3/7] refactor(telemetry): drop plugin_loaded capture from plugin entry --- src/index.telemetry.test.ts | 8 -- src/index.ts | 22 ---- src/shared/posthog-activity-state.test.ts | 127 +--------------------- src/shared/posthog-activity-state.ts | 25 ----- 4 files changed, 1 insertion(+), 181 deletions(-) diff --git a/src/index.telemetry.test.ts b/src/index.telemetry.test.ts index ce427da49..99d9200b3 100644 --- a/src/index.telemetry.test.ts +++ b/src/index.telemetry.test.ts @@ -34,8 +34,6 @@ const mockCreatePluginPostHog = mock(() => ({ trackActive: () => { throw new Error("telemetry failed") }, - capture: mock(() => {}), - captureException: mock(() => {}), shutdown: mock(async () => {}), })) const mockGetPostHogDistinctId = mock(() => "plugin-distinct-id") @@ -104,12 +102,6 @@ function installModuleMocks(): void { createPluginPostHog: mockCreatePluginPostHog, getPostHogDistinctId: mockGetPostHogDistinctId, })) - mock.module("./shared/posthog-activity-state", () => ({ - getPluginLoadedCaptureState: () => ({ - dayUTC: "2026-04-18", - capturePluginLoaded: true, - }), - })) } describe("oh-my-openagent telemetry isolation", () => { diff --git a/src/index.ts b/src/index.ts index 7509ef651..a5f549d39 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,7 +18,6 @@ import { installAgentSortShim } from "./shared/agent-sort-shim" import { detectExternalSkillPlugin, getSkillPluginConflictWarning } from "./shared/external-plugin-detector" import { startBackgroundCheck as startTmuxCheck } from "./tools/interactive-bash" import { createPluginPostHog, getPostHogDistinctId } from "./shared/posthog" -import { getPluginLoadedCaptureState } from "./shared/posthog-activity-state" const serverPlugin: Plugin = async (input, _options): Promise => { installAgentSortShim() @@ -44,27 +43,6 @@ const serverPlugin: Plugin = async (input, _options): Promise => { } catch { // telemetry failure is non-fatal, silently ignore } - let pluginLoadedCaptureState: ReturnType | null = null - try { - pluginLoadedCaptureState = getPluginLoadedCaptureState() - } catch { - // telemetry failure is non-fatal, silently ignore - } - if (pluginLoadedCaptureState?.capturePluginLoaded) { - try { - posthog.capture({ - distinctId, - event: "plugin_loaded", - properties: { - entry_point: "plugin", - has_openclaw: !!pluginConfig.openclaw, - tmux_enabled: isTmuxIntegrationEnabled(pluginConfig), - }, - }) - } catch { - // telemetry failure is non-fatal, silently ignore - } - } if (pluginConfig.openclaw) { await initializeOpenClaw(pluginConfig.openclaw) } diff --git a/src/shared/posthog-activity-state.test.ts b/src/shared/posthog-activity-state.test.ts index 328f8de1a..7c5915c63 100644 --- a/src/shared/posthog-activity-state.test.ts +++ b/src/shared/posthog-activity-state.test.ts @@ -138,7 +138,7 @@ describe("getPostHogActivityCaptureState", () => { rmSync(dataHomePath, { recursive: true, force: true }) }) - it("preserves lastPluginLoadedDayUTC when writing lastActiveDayUTC", async () => { + it("preserves unrelated state fields when writing lastActiveDayUTC", async () => { // given const dataHomePath = createDataHomePath() const cachePath = join(dataHomePath, "oh-my-opencode") @@ -168,128 +168,3 @@ describe("getPostHogActivityCaptureState", () => { rmSync(dataHomePath, { recursive: true, force: true }) }) }) - -describe("getPluginLoadedCaptureState", () => { - it("returns capturePluginLoaded=true when activity file does not exist", async () => { - // given - const dataHomePath = createDataHomePath() - process.env.XDG_DATA_HOME = dataHomePath - const { getPluginLoadedCaptureState } = await importPostHogActivityStateModule() - - // when - const result = getPluginLoadedCaptureState(new Date("2026-04-11T10:15:00.000Z")) - - // then - expect(result).toEqual({ - dayUTC: "2026-04-11", - capturePluginLoaded: true, - }) - - rmSync(dataHomePath, { recursive: true, force: true }) - }) - - it("returns capturePluginLoaded=false when lastPluginLoadedDayUTC matches today", async () => { - // given - const dataHomePath = createDataHomePath() - const cachePath = join(dataHomePath, "oh-my-opencode") - mkdirSync(cachePath, { recursive: true }) - writeFileSync( - join(cachePath, "posthog-activity.json"), - `${JSON.stringify({ - lastPluginLoadedDayUTC: "2026-04-11", - })}\n`, - ) - process.env.XDG_DATA_HOME = dataHomePath - const { getPluginLoadedCaptureState } = await importPostHogActivityStateModule() - - // when - const result = getPluginLoadedCaptureState(new Date("2026-04-11T10:15:00.000Z")) - - // then - expect(result).toEqual({ - dayUTC: "2026-04-11", - capturePluginLoaded: false, - }) - - rmSync(dataHomePath, { recursive: true, force: true }) - }) - - it("returns capturePluginLoaded=true when lastPluginLoadedDayUTC is from a previous day", async () => { - // given - const dataHomePath = createDataHomePath() - const cachePath = join(dataHomePath, "oh-my-opencode") - mkdirSync(cachePath, { recursive: true }) - writeFileSync( - join(cachePath, "posthog-activity.json"), - `${JSON.stringify({ - lastPluginLoadedDayUTC: "2026-04-10", - })}\n`, - ) - process.env.XDG_DATA_HOME = dataHomePath - const { getPluginLoadedCaptureState } = await importPostHogActivityStateModule() - - // when - const result = getPluginLoadedCaptureState(new Date("2026-04-11T10:15:00.000Z")) - - // then - expect(result).toEqual({ - dayUTC: "2026-04-11", - capturePluginLoaded: true, - }) - - rmSync(dataHomePath, { recursive: true, force: true }) - }) - - it("preserves lastActiveDayUTC when writing lastPluginLoadedDayUTC", async () => { - // given - const dataHomePath = createDataHomePath() - const cachePath = join(dataHomePath, "oh-my-opencode") - mkdirSync(cachePath, { recursive: true }) - writeFileSync( - join(cachePath, "posthog-activity.json"), - `${JSON.stringify({ - lastActiveDayUTC: "2026-04-11", - lastPluginLoadedDayUTC: "2026-04-10", - })}\n`, - ) - process.env.XDG_DATA_HOME = dataHomePath - const { getPluginLoadedCaptureState } = await importPostHogActivityStateModule() - - // when - getPluginLoadedCaptureState(new Date("2026-04-11T10:15:00.000Z")) - - // then - const persistedState = JSON.parse( - readFileSync(join(cachePath, "posthog-activity.json"), "utf-8"), - ) - expect(persistedState).toEqual({ - lastActiveDayUTC: "2026-04-11", - lastPluginLoadedDayUTC: "2026-04-11", - }) - - rmSync(dataHomePath, { recursive: true, force: true }) - }) - - it("does not rewrite state when lastPluginLoadedDayUTC matches today", async () => { - // given - const dataHomePath = createDataHomePath() - const cachePath = join(dataHomePath, "oh-my-opencode") - mkdirSync(cachePath, { recursive: true }) - const initialPayload = `${JSON.stringify({ - lastActiveDayUTC: "2026-04-10", - lastPluginLoadedDayUTC: "2026-04-11", - })}\n` - writeFileSync(join(cachePath, "posthog-activity.json"), initialPayload) - process.env.XDG_DATA_HOME = dataHomePath - const { getPluginLoadedCaptureState } = await importPostHogActivityStateModule() - - // when - getPluginLoadedCaptureState(new Date("2026-04-11T10:15:00.000Z")) - - // then - const persistedPayload = readFileSync(join(cachePath, "posthog-activity.json"), "utf-8") - expect(persistedPayload).toBe(initialPayload) - - rmSync(dataHomePath, { recursive: true, force: true }) - }) -}) diff --git a/src/shared/posthog-activity-state.ts b/src/shared/posthog-activity-state.ts index e8f896679..ef266d86f 100644 --- a/src/shared/posthog-activity-state.ts +++ b/src/shared/posthog-activity-state.ts @@ -8,7 +8,6 @@ import { writeFileAtomically } from "./write-file-atomically" type PostHogActivityState = { lastActiveDayUTC?: string - lastPluginLoadedDayUTC?: string } type PostHogActivityCaptureState = { @@ -16,11 +15,6 @@ type PostHogActivityCaptureState = { captureDaily: boolean } -type PluginLoadedCaptureState = { - dayUTC: string - capturePluginLoaded: boolean -} - const POSTHOG_ACTIVITY_STATE_FILE = "posthog-activity.json" function getPostHogActivityStateFilePath(): string { @@ -89,22 +83,3 @@ export function getPostHogActivityCaptureState(now: Date = new Date()): PostHogA captureDaily, } } - -export function getPluginLoadedCaptureState(now: Date = new Date()): PluginLoadedCaptureState { - const state = readPostHogActivityState() - const dayUTC = getUtcDayString(now) - - const capturePluginLoaded = state.lastPluginLoadedDayUTC !== dayUTC - - if (capturePluginLoaded) { - writePostHogActivityState({ - ...state, - lastPluginLoadedDayUTC: dayUTC, - }) - } - - return { - dayUTC, - capturePluginLoaded, - } -} From 92dab9285ca641f42d92a62a1c2d517fb2553611 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 2 May 2026 00:11:01 +0900 Subject: [PATCH 4/7] refactor(telemetry): narrow PostHog client and mark omo_daily_active anonymous --- src/shared/posthog.test.ts | 24 +----------------------- src/shared/posthog.ts | 25 +------------------------ 2 files changed, 2 insertions(+), 47 deletions(-) diff --git a/src/shared/posthog.test.ts b/src/shared/posthog.test.ts index 8e65200e8..da87f69ac 100644 --- a/src/shared/posthog.test.ts +++ b/src/shared/posthog.test.ts @@ -29,7 +29,6 @@ function mockPostHogNode(capturedMessages: CapturedPostHogMessage[]): void { capture(message: CapturedPostHogMessage): void { capturedMessages.push(message) } - captureException(): void {} async shutdown(): Promise {} }, })) @@ -65,23 +64,9 @@ describe("posthog client creation", () => { const pluginPostHog = createPluginPostHog() // then - expect(() => - cliPostHog.capture({ - distinctId: "cli", - event: "run_started", - }), - ).not.toThrow() - expect(() => cliPostHog.captureException(new Error("cli failure"), "cli")).not.toThrow() expect(() => cliPostHog.trackActive("cli", "run_started")).not.toThrow() await expect(cliPostHog.shutdown()).resolves.toBeUndefined() - expect(() => - pluginPostHog.capture({ - distinctId: "plugin", - event: "plugin_loaded", - }), - ).not.toThrow() - expect(() => pluginPostHog.captureException(new Error("plugin failure"), "plugin")).not.toThrow() expect(() => pluginPostHog.trackActive("plugin", "plugin_loaded")).not.toThrow() await expect(pluginPostHog.shutdown()).resolves.toBeUndefined() }) @@ -109,7 +94,6 @@ describe("posthog client creation", () => { mock.module("posthog-node", () => ({ PostHog: class { capture() {} - captureException() {} async shutdown() {} }, })) @@ -120,13 +104,6 @@ describe("posthog client creation", () => { const pluginPostHog = createPluginPostHog() // then - expect(() => - pluginPostHog.capture({ - distinctId: "plugin", - event: "plugin_loaded", - }), - ).not.toThrow() - expect(() => pluginPostHog.captureException(new Error("plugin failure"), "plugin")).not.toThrow() expect(() => pluginPostHog.trackActive("plugin", "plugin_loaded")).not.toThrow() await expect(pluginPostHog.shutdown()).resolves.toBeUndefined() }) @@ -174,6 +151,7 @@ describe("posthog trackActive emission contract", () => { day_utc: "2026-04-18", reason: "run_started", source: "cli", + $process_person_profile: false, }) expect(dailyEvent?.properties).not.toHaveProperty("hour_utc") }) diff --git a/src/shared/posthog.ts b/src/shared/posthog.ts index 52a275fd7..b4c61a8f5 100644 --- a/src/shared/posthog.ts +++ b/src/shared/posthog.ts @@ -28,24 +28,15 @@ const DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com" const DEFAULT_POSTHOG_API_KEY = "phc_CFJhj5HyvA62QPhvyaUCtaq23aUfznnijg5VaaGkNk74" type PostHogCaptureEvent = Parameters[0] -type PostHogExceptionProperties = Parameters[2] type PostHogSource = "cli" | "plugin" type PostHogActivityReason = "run_started" | "plugin_loaded" type PostHogClient = { - capture: (message: PostHogCaptureEvent) => void - captureException: ( - error: unknown, - distinctId?: string, - additionalProperties?: PostHogExceptionProperties, - ) => void trackActive: (distinctId: string, reason: PostHogActivityReason) => void shutdown: () => Promise } const NO_OP_POSTHOG: PostHogClient = { - capture: () => undefined, - captureException: () => undefined, trackActive: () => undefined, shutdown: async () => undefined, } @@ -131,21 +122,6 @@ function createPostHogClient( const sharedProperties = getSharedProperties(source) return { - capture: (message) => { - configuredClient.capture({ - ...message, - properties: { - ...sharedProperties, - ...message.properties, - }, - }) - }, - captureException: (error, distinctId, additionalProperties) => { - configuredClient.captureException(error, distinctId, { - ...sharedProperties, - ...additionalProperties, - }) - }, trackActive: (distinctId, reason) => { const activityState = resolveActivityState() @@ -155,6 +131,7 @@ function createPostHogClient( event: "omo_daily_active", properties: { ...sharedProperties, + $process_person_profile: false, day_utc: activityState.dayUTC, reason, }, From d8d061c2b0ace77692dca2039d09787ee9fc0fd0 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 2 May 2026 00:11:03 +0900 Subject: [PATCH 5/7] docs(privacy): narrow telemetry scope to omo_daily_active only --- docs/legal/privacy-policy.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/legal/privacy-policy.md b/docs/legal/privacy-policy.md index b0167e85f..357c99566 100644 --- a/docs/legal/privacy-policy.md +++ b/docs/legal/privacy-policy.md @@ -1,6 +1,6 @@ # Privacy Policy -Last updated: April 11, 2026 +Last updated: May 2, 2026 This Privacy Policy explains how oh-my-opencode and oh-my-openagent collect, use, and protect information related to the published CLI package, the OpenCode plugin, and the project website or repository materials where they apply. @@ -14,14 +14,13 @@ We collect limited non-personal information needed to operate and improve the Se ### Automatically collected information -When anonymous telemetry is enabled, the Application may collect: +When anonymous telemetry is enabled, the Application may collect a single anonymous usage event: -- Anonymous usage events, including `run_started`, `run_completed`, `run_failed`, `install_completed`, `install_failed`, `plugin_loaded`, and `omo_daily_active` -- Application metadata such as package version, plugin name, runtime, and command or entry-point context -- Error diagnostics captured during failed CLI runs +- `omo_daily_active`, sent at most once per UTC day per machine when the plugin loads or when the `run` CLI is invoked, used to estimate daily, weekly, and monthly active installations +- Anonymous machine metadata bundled with that event, such as package version, plugin name, runtime, OS family, locale, and timezone - A pseudonymous installation identifier derived from a one-way hash of the local hostname -We do not intentionally collect prompt contents, source files, repository contents, access tokens, API keys, or raw hostnames through this telemetry path. +The Application does not create or update PostHog person profiles, and does not collect prompt contents, source files, repository contents, access tokens, API keys, raw hostnames, or runtime error diagnostics through this telemetry path. ### Configuration and local state From 0d89294a910014a8c784171049166af3087d36f2 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 2 May 2026 00:32:08 +0900 Subject: [PATCH 6/7] docs(telemetry): align README + installation guide + privacy policy with single-event scope --- README.ja.md | 2 +- README.ko.md | 2 +- README.md | 2 +- README.ru.md | 2 +- README.zh-cn.md | 2 +- docs/guide/installation.md | 2 +- docs/legal/privacy-policy.md | 7 +++---- 7 files changed, 9 insertions(+), 10 deletions(-) diff --git a/README.ja.md b/README.ja.md index 60957b7e3..e8a817abc 100644 --- a/README.ja.md +++ b/README.ja.md @@ -117,7 +117,7 @@ curl -s https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/head **注記**: 公開されているパッケージおよびバイナリ名は `oh-my-opencode` を使用してください。`opencode.json` 内では、互換性レイヤーがプラグインエントリ `oh-my-openagent` を優先しますが、従来の `oh-my-opencode` エントリも警告付きで読み込まれます。プラグイン設定ファイルは依然として `oh-my-opencode.json` または `oh-my-opencode.jsonc` を使用するのが一般的で、移行期間中は従来のファイル名と改名後のファイル名の両方が認識されます。 -匿名のテレメトリは、インストールとランタイムの信頼性向上のためにデフォルトで有効になっています。これは PostHog を使用し、生のホスト名ではなくハッシュ化されたインストール識別子を使用します。無効化するには `OMO_SEND_ANONYMOUS_TELEMETRY=0` または `OMO_DISABLE_POSTHOG=1` を設定してください。[プライバシーポリシー](docs/legal/privacy-policy.md)と[利用規約](docs/legal/terms-of-service.md)をご覧ください。 +匿名のテレメトリは、アクティブなインストール数(DAU/WAU/MAU)の集計のためにデフォルトで有効になっています。マシン1台につきUTC日あたり最大1回イベントが送信され、ハッシュ化されたインストール識別子を使用し、生のホスト名は使用せず、PostHog person profile も作成されません。無効化するには `OMO_SEND_ANONYMOUS_TELEMETRY=0` または `OMO_DISABLE_POSTHOG=1` を設定してください。[プライバシーポリシー](docs/legal/privacy-policy.md)と[利用規約](docs/legal/terms-of-service.md)をご覧ください。 --- diff --git a/README.ko.md b/README.ko.md index 96bfb731a..f53c7c1da 100644 --- a/README.ko.md +++ b/README.ko.md @@ -111,7 +111,7 @@ curl -s https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/head **참고**: 배포된 패키지와 바이너리 이름은 `oh-my-opencode`를 사용하세요. `opencode.json` 내부에서는 호환성 레이어가 이제 플러그인 엔트리 `oh-my-openagent`를 우선시하며, 레거시 `oh-my-opencode` 엔트리는 경고와 함께 여전히 로드됩니다. 플러그인 설정 파일은 여전히 일반적으로 `oh-my-opencode.json` 또는 `oh-my-opencode.jsonc`를 사용하며, 전환 기간 동안 레거시와 변경된 basename 모두 인식됩니다. -익명 텔레메트리는 설치 및 런타임 안정성 개선을 위해 기본적으로 활성화되어 있습니다. PostHog를 사용하며 해시된 설치 식별자를 사용하고 원시 호스트명은 절대 사용하지 않습니다. `OMO_SEND_ANONYMOUS_TELEMETRY=0` 또는 `OMO_DISABLE_POSTHOG=1`로 비활성화할 수 있습니다. [개인정보처리방침](docs/legal/privacy-policy.md)과 [서비스 이용약관](docs/legal/terms-of-service.md)을 참조하세요. +익명 텔레메트리는 활성 설치 수(DAU/WAU/MAU) 집계를 위해 기본적으로 활성화되어 있습니다. 머신당 UTC 하루에 최대 1회만 이벤트가 전송되며, 해시된 설치 식별자를 사용하고 원시 호스트명은 절대 사용하지 않으며 PostHog person profile은 생성되지 않습니다. `OMO_SEND_ANONYMOUS_TELEMETRY=0` 또는 `OMO_DISABLE_POSTHOG=1`로 비활성화할 수 있습니다. [개인정보처리방침](docs/legal/privacy-policy.md)과 [서비스 이용약관](docs/legal/terms-of-service.md)을 참조하세요. --- diff --git a/README.md b/README.md index 1c70d6f2a..74a04ea12 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ curl -s https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/head **Note**: Use the published package and binary name `oh-my-opencode`. Inside `opencode.json`, the compatibility layer now prefers the plugin entry `oh-my-openagent`, while legacy `oh-my-opencode` entries still load with a warning. Plugin config files still commonly use `oh-my-opencode.json` or `oh-my-opencode.jsonc`, and both legacy and renamed basenames are recognized during the transition. -Anonymous telemetry is enabled by default to help improve install and runtime reliability. It uses PostHog with a hashed installation identifier, never the raw hostname, and can be disabled with `OMO_SEND_ANONYMOUS_TELEMETRY=0` or `OMO_DISABLE_POSTHOG=1`. See [Privacy Policy](docs/legal/privacy-policy.md) and [Terms of Service](docs/legal/terms-of-service.md). +Anonymous telemetry is enabled by default to track active installations (DAU/WAU/MAU). A single event is sent at most once per UTC day per machine using a hashed installation identifier, never the raw hostname, and PostHog person profiles are not created. Disable with `OMO_SEND_ANONYMOUS_TELEMETRY=0` or `OMO_DISABLE_POSTHOG=1`. See [Privacy Policy](docs/legal/privacy-policy.md) and [Terms of Service](docs/legal/terms-of-service.md). --- diff --git a/README.ru.md b/README.ru.md index 85c43121e..8d5ce8ffc 100644 --- a/README.ru.md +++ b/README.ru.md @@ -103,7 +103,7 @@ curl -s https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/head **Примечание**: Используйте опубликованное имя пакета и бинарника `oh-my-opencode`. Внутри `opencode.json` слой совместимости теперь предпочитает точку входа плагина `oh-my-openagent`, в то время как устаревшие записи `oh-my-opencode` все еще загружаются с предупреждением. Файлы конфигурации плагина по-прежнему часто используют `oh-my-opencode.json` или `oh-my-opencode.jsonc`, и как устаревшие, так и переименованные базовые имена распознаются во время переходного периода. -Анонимная телеметрия включена по умолчанию для улучшения надежности установки и работы. Она использует PostHog с хешированным идентификатором установки, никогда не используя исходное имя хоста, и может быть отключена с помощью `OMO_SEND_ANONYMOUS_TELEMETRY=0` или `OMO_DISABLE_POSTHOG=1`. См. [Политику конфиденциальности](docs/legal/privacy-policy.md) и [Условия обслуживания](docs/legal/terms-of-service.md). +Анонимная телеметрия включена по умолчанию для подсчёта активных установок (DAU/WAU/MAU). Не более одного события на машину за UTC-сутки, использует хешированный идентификатор установки, никогда не использует исходное имя хоста, и не создаёт PostHog person profile. Можно отключить через `OMO_SEND_ANONYMOUS_TELEMETRY=0` или `OMO_DISABLE_POSTHOG=1`. См. [Политику конфиденциальности](docs/legal/privacy-policy.md) и [Условия обслуживания](docs/legal/terms-of-service.md). ------ diff --git a/README.zh-cn.md b/README.zh-cn.md index 1731cdc08..105c5b79c 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -118,7 +118,7 @@ curl -s https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/head **注意**:请使用已发布的包名和二进制名 `oh-my-opencode`。在 `opencode.json` 中,兼容性层现在优先使用插件入口 `oh-my-openagent`,而旧的 `oh-my-opencode` 条目仍会加载并显示警告。插件配置文件通常仍使用 `oh-my-opencode.json` 或 `oh-my-opencode.jsonc`,在过渡期间新旧两种文件名都会被识别。 -匿名遥测默认开启,用于帮助提升安装和运行时的可靠性。它使用 PostHog,并采用哈希化的安装标识符,绝不会使用原始主机名,可通过 `OMO_SEND_ANONYMOUS_TELEMETRY=0` 或 `OMO_DISABLE_POSTHOG=1` 禁用。详见 [隐私政策](docs/legal/privacy-policy.md) 和 [服务条款](docs/legal/terms-of-service.md)。 +匿名遥测默认开启,用于统计活跃安装数(DAU/WAU/MAU)。每台机器每个 UTC 日最多发送一次事件,使用哈希化的安装标识符,绝不会使用原始主机名,且不会创建 PostHog person profile。可通过 `OMO_SEND_ANONYMOUS_TELEMETRY=0` 或 `OMO_DISABLE_POSTHOG=1` 禁用。详见 [隐私政策](docs/legal/privacy-policy.md) 和 [服务条款](docs/legal/terms-of-service.md)。 --- diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 582b5d8ba..202cd5784 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -23,7 +23,7 @@ bunx oh-my-opencode install Follow the prompts to configure your Claude, ChatGPT, and Gemini subscriptions. After installation, authenticate your providers as instructed. -Anonymous telemetry is enabled by default to help improve install and runtime reliability. It uses PostHog with a hashed installation identifier and can be disabled with `OMO_SEND_ANONYMOUS_TELEMETRY=0` or `OMO_DISABLE_POSTHOG=1`. See [Privacy Policy](../legal/privacy-policy.md) and [Terms of Service](../legal/terms-of-service.md). +Anonymous telemetry is enabled by default to track active installations (DAU/WAU/MAU). A single event is sent at most once per UTC day per machine using a hashed installation identifier, and PostHog person profiles are not created. Disable with `OMO_SEND_ANONYMOUS_TELEMETRY=0` or `OMO_DISABLE_POSTHOG=1`. See [Privacy Policy](../legal/privacy-policy.md) and [Terms of Service](../legal/terms-of-service.md). After you install it, you can read this [overview guide](./overview.md) to understand more. diff --git a/docs/legal/privacy-policy.md b/docs/legal/privacy-policy.md index 357c99566..ab39c6f1d 100644 --- a/docs/legal/privacy-policy.md +++ b/docs/legal/privacy-policy.md @@ -28,7 +28,7 @@ The Application stores local configuration and telemetry deduplication state on ## 2. How Telemetry Works -The Application uses PostHog for anonymous product analytics. Telemetry is enabled by default, following the same opt-out posture used in cmux, and is intended to help us understand installation success, runtime reliability, and broad usage patterns. +The Application uses PostHog for anonymous product analytics. Telemetry is enabled by default, following the same opt-out posture used in cmux, and is intended only to estimate active installations (daily, weekly, and monthly) so we can understand broad adoption. Telemetry can be disabled at any time by setting one of these environment variables before running the CLI or plugin host: @@ -54,9 +54,8 @@ Each third-party service has its own terms and privacy practices. We use collected information to: -- Measure installation and runtime health -- Understand aggregate feature usage -- Diagnose failures and improve reliability +- Estimate daily, weekly, and monthly active installations +- Understand aggregate adoption across operating systems and package versions - Maintain and evolve the Service We do not sell personal information collected through this telemetry path. From 425d0300fcabb785fd52220ae214ab2b4ad87981 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 2 May 2026 00:37:07 +0900 Subject: [PATCH 7/7] docs(privacy): drop diagnostics retention reference (no diagnostics events collected) --- docs/legal/privacy-policy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/legal/privacy-policy.md b/docs/legal/privacy-policy.md index ab39c6f1d..3d5a20294 100644 --- a/docs/legal/privacy-policy.md +++ b/docs/legal/privacy-policy.md @@ -62,7 +62,7 @@ We do not sell personal information collected through this telemetry path. ## 5. Data Retention -Anonymous analytics and diagnostics are retained only as long as reasonably necessary for product, security, and operational analysis. Local telemetry state stored on your machine remains there until removed by you. +Anonymous analytics are retained only as long as reasonably necessary for understanding adoption. Local telemetry state stored on your machine remains there until removed by you. ## 6. Your Choices