feat(cli): associate signed-in HeyGen account with telemetry (#2020)

* feat(cli): associate signed-in HeyGen account with telemetry

Sign-in telemetry currently attributes everything to the anonymous
install id, so the sign-in funnel can be counted but a completed sign-in
can't be tied to the account it produced. This associates the two.

- On a completed sign-in, emit a PostHog `$identify` alias whose
  `$anon_distinct_id` is the install's anonymousId, so events recorded
  before sign-in stitch to the same person, and tag `auth_login_completed`
  with the account identity (the pre-plumbed `distinctId`).
- `/v3/users/me` exposes no opaque user_id, so the identity key is the
  account email, falling back to username (single `identityKey` helper).
- Both no-op under the `telemetry disable` opt-out and only fire after
  the user chooses to sign in.

Privacy disclosure updated in lockstep, since this is the first PII the
CLI attaches: the first-run telemetry notice and the telemetry section
of docs/packages/cli.mdx now state that signing in links your account
email to your usage.

Tests: identifyUser payload + no-op, completion attribution incl.
username fallback and no-identity-on-reject/empty. Verified end-to-end
against the built CLI: pre-auth events anonymous, $identify carries
$anon_distinct_id, completion carries the account email.

* docs(cli): disclose the username identity fallback

Review gating item: identityKey is `email ?? username`, but the
first-run notice and cli.mdx said only "email", so an emailless
account's username would reach PostHog undisclosed. `/v3/users/me`
treats email as optional (pickString), so the fallback is live code,
not dead — disclose it rather than assert an unverifiable email
guarantee. Both surfaces now say "email, or username if the account
has no email".

Also soften the identityKey comment: it implied username is "less
identifying", but HeyGen usernames are often email-shaped, so the note
now states username is a fallback, not a privacy win.
This commit is contained in:
Miguel Ángel
2026-07-07 02:23:54 -04:00
committed by GitHub
parent 229e88eac5
commit 3d8372f880
7 changed files with 121 additions and 20 deletions
+4 -1
View File
@@ -191,7 +191,10 @@ export function showTelemetryNotice(): boolean {
console.log();
console.log(` ${c.dim("Hyperframes collects anonymous usage data to improve the tool.")}`);
console.log(` ${c.dim("No personal info, file paths, or content is collected.")}`);
console.log(` ${c.dim("File paths and composition content are never collected.")}`);
console.log(
` ${c.dim("If you sign in to HeyGen, your account (email, or username) is linked to your usage.")}`,
);
console.log();
console.log(` ${c.dim("Disable anytime:")} ${c.accent("hyperframes telemetry disable")}`);
console.log();
+24 -3
View File
@@ -5,6 +5,12 @@ vi.mock("./client.js", () => ({
trackEvent: (...args: unknown[]) => trackEvent(...args),
}));
// identifyUser reads the install anonymousId; pin it so the $identify alias is
// deterministic and the test never touches disk.
vi.mock("./config.js", () => ({
readConfig: () => ({ anonymousId: "anon-test-123", telemetryEnabled: true }),
}));
const {
trackRenderComplete,
trackRenderError,
@@ -17,6 +23,7 @@ const {
trackAuthLoginStarted,
trackAuthLoginCompleted,
trackAuthLoginFailed,
identifyUser,
} = await import("./events.js");
describe("render telemetry events", () => {
@@ -285,12 +292,26 @@ describe("auth login telemetry events", () => {
);
});
it("forwards an explicit distinctId to trackEvent for future user-level attribution", () => {
trackAuthLoginCompleted("oauth", "heygen-user-123");
it("forwards an explicit distinctId to trackEvent for user-level attribution", () => {
trackAuthLoginCompleted("oauth", "alice@example.com");
expect(trackEvent).toHaveBeenCalledWith(
"auth_login_completed",
{ method: "oauth" },
"heygen-user-123",
"alice@example.com",
);
});
it("identifyUser emits a $identify alias linking the anon install to the identity", () => {
identifyUser("alice@example.com");
expect(trackEvent).toHaveBeenCalledWith(
"$identify",
{ $anon_distinct_id: "anon-test-123" },
"alice@example.com",
);
});
it("identifyUser is a no-op when there is no identity to attach", () => {
identifyUser("");
expect(trackEvent).not.toHaveBeenCalled();
});
});
+13
View File
@@ -1,5 +1,6 @@
import { redactTelemetryString, type OutputResolutionIssueKind } from "@hyperframes/core";
import { trackEvent } from "./client.js";
import { readConfig } from "./config.js";
export interface RenderObservabilityTelemetryPayload {
observabilityRenderJobId?: string;
@@ -387,6 +388,18 @@ export function trackAuthLoginFailed(
trackEvent("auth_login_failed", { method, reason }, distinctId);
}
// Associate this install with the signed-in HeyGen account after a completed
// sign-in. Emits a PostHog `$identify` alias whose `$anon_distinct_id` is the
// install's anonymousId, so events recorded before sign-in stitch to the same
// person instead of stranding as a separate anonymous profile. Routed through
// trackEvent so it shares the opt-out gate and flush path — a no-op when
// telemetry is disabled. `distinctId` is the account email (else username);
// see the privacy notice in showTelemetryNotice and docs/packages/cli.mdx.
export function identifyUser(distinctId: string): void {
if (!distinctId) return;
trackEvent("$identify", { $anon_distinct_id: readConfig().anonymousId }, distinctId);
}
// A render was rejected by the output-resolution/alpha/HDR pre-flight (P1-3)
// before any browser/ffmpeg work. Counts the "caught early" saves on dashboard
// 1783183, distinct from deep render failures. `kind` is the low-cardinality
+1
View File
@@ -12,5 +12,6 @@ export {
trackAuthLoginStarted,
trackAuthLoginCompleted,
trackAuthLoginFailed,
identifyUser,
} from "./events.js";
export { getSystemMeta, getShmSizeMb, getFreeDiskMb, bytesToMb } from "./system.js";