mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 23:00:03 +00:00
feat(cli): add opt-out anonymous telemetry via PostHog
Add anonymous usage telemetry to help improve the CLI. Uses PostHog's HTTP batch API directly (zero new dependencies) with a 5-second timeout and fail-silent behavior — telemetry never breaks the CLI. What's collected: command names, render performance (duration, fps, quality), template choices, OS/arch/Node version/CLI version. What's NOT collected: file paths, project names, video content, or any personally identifiable information. Telemetry is: - Disabled in dev mode (running via tsx) - Disabled in CI (CI=true) or via HYPERFRAMES_NO_TELEMETRY=1 - Disabled when API key is placeholder (safe to merge before key is set) - Controllable via `hyperframes telemetry [enable|disable|status]` - Disclosed on first run with clear opt-out instructions Config stored at ~/.hyperframes/config.json (0600 permissions). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { homedir } from "node:os";
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Config directory: ~/.hyperframes/
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const CONFIG_DIR = join(homedir(), ".hyperframes");
|
||||
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
||||
|
||||
export interface HyperframesConfig {
|
||||
/** Whether anonymous telemetry is enabled (default: true in production) */
|
||||
telemetryEnabled: boolean;
|
||||
/** Stable anonymous identifier — no PII, just a random UUID */
|
||||
anonymousId: string;
|
||||
/** Whether the first-run telemetry notice has been shown */
|
||||
telemetryNoticeShown: boolean;
|
||||
/** Total CLI command invocations (for engagement prompts) */
|
||||
commandCount: number;
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: HyperframesConfig = {
|
||||
telemetryEnabled: true,
|
||||
anonymousId: "",
|
||||
telemetryNoticeShown: false,
|
||||
commandCount: 0,
|
||||
};
|
||||
|
||||
let cachedConfig: HyperframesConfig | null = null;
|
||||
|
||||
/**
|
||||
* Read the config file, creating it with defaults if it doesn't exist.
|
||||
* Returns a mutable copy — call `writeConfig()` to persist changes.
|
||||
*/
|
||||
export function readConfig(): HyperframesConfig {
|
||||
if (cachedConfig) return { ...cachedConfig };
|
||||
|
||||
if (!existsSync(CONFIG_FILE)) {
|
||||
const config = { ...DEFAULT_CONFIG, anonymousId: randomUUID() };
|
||||
writeConfig(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
||||
const parsed = JSON.parse(raw) as Partial<HyperframesConfig>;
|
||||
|
||||
const config: HyperframesConfig = {
|
||||
telemetryEnabled: parsed.telemetryEnabled ?? DEFAULT_CONFIG.telemetryEnabled,
|
||||
anonymousId: parsed.anonymousId || randomUUID(),
|
||||
telemetryNoticeShown: parsed.telemetryNoticeShown ?? DEFAULT_CONFIG.telemetryNoticeShown,
|
||||
commandCount: parsed.commandCount ?? DEFAULT_CONFIG.commandCount,
|
||||
};
|
||||
|
||||
cachedConfig = config;
|
||||
return { ...config };
|
||||
} catch {
|
||||
// Corrupted config — reset
|
||||
const config = { ...DEFAULT_CONFIG, anonymousId: randomUUID() };
|
||||
writeConfig(config);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist config to disk. Updates the in-memory cache.
|
||||
*/
|
||||
export function writeConfig(config: HyperframesConfig): void {
|
||||
try {
|
||||
mkdirSync(CONFIG_DIR, { recursive: true });
|
||||
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
|
||||
cachedConfig = { ...config };
|
||||
} catch {
|
||||
// Non-fatal — telemetry should never break the CLI
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the command counter and persist.
|
||||
*/
|
||||
export function incrementCommandCount(): number {
|
||||
const config = readConfig();
|
||||
config.commandCount++;
|
||||
writeConfig(config);
|
||||
return config.commandCount;
|
||||
}
|
||||
|
||||
/** Expose the config directory path for the telemetry command output */
|
||||
export const CONFIG_PATH = CONFIG_FILE;
|
||||
Reference in New Issue
Block a user