mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
* feat(media-use): usage visibility — shared telemetry identity, miss log, resolve --stats - U6: join the CLI/studio telemetry identity — read the shared install id from ~/.hyperframes/config.json (seed if absent) instead of a media-use-only ~/.media/anon-id, and $identify to the HeyGen account (email/username) once per run on sign-in. One PostHog person across surfaces; pseudonymous before sign-in, account-linked after. Event properties stay coarse (no intent/paths). - U1: one-time first-run disclosure to stderr + Privacy section in SKILL.md; honors DO_NOT_TRACK / HYPERFRAMES_NO_TELEMETRY. - U2: persist resolve misses to ~/.media/misses.jsonl (local → intent kept; the media_use_resolve_miss telemetry event stays intent-free). - U3: `resolve --stats` (+ --days) — local usage report over .media/ + ~/.media (volume by type, source/provider/via split, hit-rate, top missed intents, global-cache size/reuse); human + --json. - U4: reproducible PostHog dashboard definition (references/telemetry-dashboard.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H5k87mPZ4d6yiFwcWSb8Vv * fix(media-use): address #2113 review — shared notice state, legacy id migration, stats robustness - Notice-shown state now lives in the shared ~/.hyperframes/config.json (config.telemetryNoticeShown, the CLI's own field) instead of a media-use-only ~/.media marker — so shared-identity users see the first-run notice once per person, not once per tool. - Migrate a pre-existing ~/.media/anon-id into the shared config on upgrade, so media-use-only users keep their PostHog persona instead of resetting. - buildStats: --days only windows on a positive finite value (negative/NaN → all time, not an empty report); dropped the top-level catch that masked a real error as an all-zero "no usage" report (sub-reads are individually guarded). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H5k87mPZ4d6yiFwcWSb8Vv --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
50 lines
1.2 KiB
JavaScript
50 lines
1.2 KiB
JavaScript
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
import { homedir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
const MISSES_FILE = "misses.jsonl";
|
|
|
|
function missesPath() {
|
|
return join(homedir(), ".media", MISSES_FILE);
|
|
}
|
|
|
|
export function recordMiss({ type, intent, provider_override, local_only }) {
|
|
try {
|
|
const dir = join(homedir(), ".media");
|
|
mkdirSync(dir, { recursive: true });
|
|
appendFileSync(
|
|
join(dir, MISSES_FILE),
|
|
JSON.stringify({
|
|
ts: new Date().toISOString(),
|
|
type,
|
|
intent,
|
|
provider_override: !!provider_override,
|
|
local_only: !!local_only,
|
|
}) + "\n",
|
|
);
|
|
} catch {
|
|
// local miss logging is best-effort; never surface into resolve
|
|
}
|
|
}
|
|
|
|
export function readMisses() {
|
|
const p = missesPath();
|
|
try {
|
|
if (!existsSync(p)) return [];
|
|
const raw = readFileSync(p, "utf8");
|
|
const records = [];
|
|
for (const line of raw.split(/\r?\n/)) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed) continue;
|
|
try {
|
|
records.push(JSON.parse(trimmed));
|
|
} catch {
|
|
// skip malformed local lines, don't crash stats
|
|
}
|
|
}
|
|
return records;
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|