refactor(omo-codex): sync telemetry component sources

This commit is contained in:
YeonGyu-Kim
2026-05-26 17:40:46 +09:00
parent b164cb94be
commit f77338b4a2
9 changed files with 307 additions and 106 deletions
@@ -1,21 +1,22 @@
import { renameSync, unlinkSync, writeFileSync } from "node:fs";
import { renameSync, unlinkSync, writeFileSync } from "node:fs"
export function writeFileAtomically(filePath: string, content: string): void {
const tempPath = `${filePath}.tmp`;
writeFileSync(tempPath, content, "utf-8");
const tempPath = `${filePath}.tmp`
writeFileSync(tempPath, content, "utf-8")
try {
renameSync(tempPath, filePath);
} catch (error) {
const isPermissionError =
error instanceof Error && (error.message.includes("EPERM") || error.message.includes("EACCES"));
try {
renameSync(tempPath, filePath)
} catch (error) {
const isPermissionError =
error instanceof Error &&
(error.message.includes("EPERM") || error.message.includes("EACCES"))
if (process.platform === "win32" && isPermissionError) {
unlinkSync(filePath);
renameSync(tempPath, filePath);
return;
}
if (process.platform === "win32" && isPermissionError) {
unlinkSync(filePath)
renameSync(tempPath, filePath)
return
}
throw error;
}
throw error
}
}
@@ -1,44 +1,45 @@
import { accessSync, constants, mkdirSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { accessSync, constants, mkdirSync } from "node:fs"
import os from "node:os"
import path from "node:path"
import { CACHE_DIR_NAME } from "./product-identity.js";
import { CACHE_DIR_NAME } from "./product-identity.js"
type OsProvider = Pick<typeof os, "homedir" | "tmpdir">;
type OsProvider = Pick<typeof os, "homedir" | "tmpdir">
let osProviderOverride: OsProvider | null = null;
let osProviderOverride: OsProvider | null = null
export function getOsProvider(): OsProvider {
return osProviderOverride ?? os;
return osProviderOverride ?? os
}
/** @internal test-only */
export function __setOsProviderForTesting(provider: OsProvider): void {
osProviderOverride = provider;
osProviderOverride = provider
}
/** @internal test-only */
export function __resetOsProviderForTesting(): void {
osProviderOverride = null;
osProviderOverride = null
}
function resolveWritableDirectory(preferredDir: string, fallbackSuffix: string): string {
try {
mkdirSync(preferredDir, { recursive: true });
accessSync(preferredDir, constants.W_OK);
return preferredDir;
} catch {
const fallbackDir = path.join(getOsProvider().tmpdir(), fallbackSuffix);
mkdirSync(fallbackDir, { recursive: true });
return fallbackDir;
}
try {
mkdirSync(preferredDir, { recursive: true })
accessSync(preferredDir, constants.W_OK)
return preferredDir
} catch {
const fallbackDir = path.join(getOsProvider().tmpdir(), fallbackSuffix)
mkdirSync(fallbackDir, { recursive: true })
return fallbackDir
}
}
export function getDataDir(): string {
const preferredDataDir = process.env["XDG_DATA_HOME"] ?? path.join(getOsProvider().homedir(), ".local", "share");
return resolveWritableDirectory(preferredDataDir, "omo-codex-data");
const preferredDataDir =
process.env["XDG_DATA_HOME"] ?? path.join(getOsProvider().homedir(), ".local", "share")
return resolveWritableDirectory(preferredDataDir, "omo-codex-data")
}
export function getActivityStateDir(): string {
return path.join(getDataDir(), CACHE_DIR_NAME);
return path.join(getDataDir(), CACHE_DIR_NAME)
}
@@ -1,40 +1,43 @@
import { DEFAULT_POSTHOG_API_KEY, DEFAULT_POSTHOG_HOST } from "./product-identity.js";
import {
DEFAULT_POSTHOG_API_KEY,
DEFAULT_POSTHOG_HOST,
} from "./product-identity.js"
function normalizeEnvValue(value: string | undefined): string | undefined {
return value?.trim().toLowerCase();
return value?.trim().toLowerCase()
}
function isDisableFlag(value: string | undefined): boolean {
const normalized = normalizeEnvValue(value);
return normalized === "1" || normalized === "true";
const normalized = normalizeEnvValue(value)
return normalized === "1" || normalized === "true"
}
function isTelemetryOptOutFlag(value: string | undefined): boolean {
const normalized = normalizeEnvValue(value);
return normalized === "0" || normalized === "false" || normalized === "no";
const normalized = normalizeEnvValue(value)
return normalized === "0" || normalized === "false" || normalized === "no"
}
export function shouldDisablePostHog(): boolean {
return (
isDisableFlag(process.env["OMO_DISABLE_POSTHOG"]) ||
isTelemetryOptOutFlag(process.env["OMO_SEND_ANONYMOUS_TELEMETRY"]) ||
isDisableFlag(process.env["OMO_CODEX_DISABLE_POSTHOG"]) ||
isTelemetryOptOutFlag(process.env["OMO_CODEX_SEND_ANONYMOUS_TELEMETRY"])
);
return (
isDisableFlag(process.env["OMO_DISABLE_POSTHOG"]) ||
isTelemetryOptOutFlag(process.env["OMO_SEND_ANONYMOUS_TELEMETRY"]) ||
isDisableFlag(process.env["OMO_CODEX_DISABLE_POSTHOG"]) ||
isTelemetryOptOutFlag(process.env["OMO_CODEX_SEND_ANONYMOUS_TELEMETRY"])
)
}
export function getPostHogApiKey(): string {
const explicit = process.env["POSTHOG_API_KEY"];
if (explicit === undefined) {
return DEFAULT_POSTHOG_API_KEY;
}
return explicit.trim();
const explicit = process.env["POSTHOG_API_KEY"]
if (explicit === undefined) {
return DEFAULT_POSTHOG_API_KEY
}
return explicit.trim()
}
export function hasPostHogApiKey(): boolean {
return getPostHogApiKey().length > 0;
return getPostHogApiKey().length > 0
}
export function getPostHogHost(): string {
return process.env["POSTHOG_HOST"]?.trim() || DEFAULT_POSTHOG_HOST;
return process.env["POSTHOG_HOST"]?.trim() || DEFAULT_POSTHOG_HOST
}
@@ -1,79 +1,81 @@
import { existsSync, mkdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { existsSync, mkdirSync, readFileSync } from "node:fs"
import { join } from "node:path"
import { writeFileAtomically } from "./atomic-write.js";
import { getActivityStateDir } from "./data-path.js";
import { writeFileAtomically } from "./atomic-write.js"
import { getActivityStateDir } from "./data-path.js"
export type PostHogActivityState = {
readonly lastActiveDayUTC?: string;
};
readonly lastActiveDayUTC?: string
}
export type PostHogActivityCaptureState = {
readonly dayUTC: string;
readonly captureDaily: boolean;
};
readonly dayUTC: string
readonly captureDaily: boolean
}
const POSTHOG_ACTIVITY_STATE_FILE = "posthog-activity.json";
const POSTHOG_ACTIVITY_STATE_FILE = "posthog-activity.json"
function getPostHogActivityStateFilePath(): string {
return join(getActivityStateDir(), POSTHOG_ACTIVITY_STATE_FILE);
return join(getActivityStateDir(), POSTHOG_ACTIVITY_STATE_FILE)
}
function getUtcDayString(date: Date): string {
return date.toISOString().slice(0, 10);
return date.toISOString().slice(0, 10)
}
function isPostHogActivityState(value: unknown): value is PostHogActivityState {
return value !== null && typeof value === "object" && !Array.isArray(value);
return value !== null && typeof value === "object" && !Array.isArray(value)
}
function readPostHogActivityState(): PostHogActivityState {
const stateFilePath = getPostHogActivityStateFilePath();
const stateFilePath = getPostHogActivityStateFilePath()
if (!existsSync(stateFilePath)) {
return {};
}
if (!existsSync(stateFilePath)) {
return {}
}
try {
const stateContent = readFileSync(stateFilePath, "utf-8");
const stateJson: unknown = JSON.parse(stateContent);
try {
const stateContent = readFileSync(stateFilePath, "utf-8")
const stateJson: unknown = JSON.parse(stateContent)
if (!isPostHogActivityState(stateJson)) {
return {};
}
if (!isPostHogActivityState(stateJson)) {
return {}
}
return stateJson;
} catch {
return {};
}
return stateJson
} catch {
return {}
}
}
function writePostHogActivityState(nextState: PostHogActivityState): void {
const stateDir = getActivityStateDir();
const stateFilePath = getPostHogActivityStateFilePath();
const stateDir = getActivityStateDir()
const stateFilePath = getPostHogActivityStateFilePath()
try {
mkdirSync(stateDir, { recursive: true });
writeFileAtomically(stateFilePath, `${JSON.stringify(nextState, null, 2)}\n`);
} catch {
return;
}
try {
mkdirSync(stateDir, { recursive: true })
writeFileAtomically(stateFilePath, `${JSON.stringify(nextState, null, 2)}\n`)
} catch {
return
}
}
export function getPostHogActivityCaptureState(now: Date = new Date()): PostHogActivityCaptureState {
const state = readPostHogActivityState();
const dayUTC = getUtcDayString(now);
const captureDaily = state.lastActiveDayUTC !== dayUTC;
export function getPostHogActivityCaptureState(
now: Date = new Date(),
): PostHogActivityCaptureState {
const state = readPostHogActivityState()
const dayUTC = getUtcDayString(now)
const captureDaily = state.lastActiveDayUTC !== dayUTC
if (captureDaily) {
writePostHogActivityState({
...state,
lastActiveDayUTC: dayUTC,
});
}
if (captureDaily) {
writePostHogActivityState({
...state,
lastActiveDayUTC: dayUTC,
})
}
return {
dayUTC,
captureDaily,
};
return {
dayUTC,
captureDaily,
}
}