@@ -32,26 +32,61 @@ export function optedOut() {
3232 ) ;
3333}
3434
35- // Stable per-machine anonymous id, read from the shared CLI/studio contract.
36- function anonymousId ( ) {
35+ // CLI + studio share one install identity in ~/.hyperframes/config.json
36+ // (packages/cli/src/telemetry/config.ts — same path, same `anonymousId` /
37+ // `telemetryNoticeShown` fields). Read and write that same file so media-use is
38+ // the same PostHog person and shows the notice once per person, not per tool.
39+ // Computed per call (not a module const) so it honors HOME at runtime — tests
40+ // sandbox HOME, and os.homedir() re-reads it each call.
41+ function sharedConfigPath ( ) {
42+ return join ( homedir ( ) , ".hyperframes" , "config.json" ) ;
43+ }
44+
45+ function readSharedConfig ( ) {
46+ try {
47+ const file = sharedConfigPath ( ) ;
48+ if ( existsSync ( file ) ) {
49+ const parsed = JSON . parse ( readFileSync ( file , "utf8" ) ) ;
50+ if ( parsed && typeof parsed === "object" && ! Array . isArray ( parsed ) ) return parsed ;
51+ }
52+ } catch {
53+ // unreadable config → treat as empty; never throw
54+ }
55+ return { } ;
56+ }
57+
58+ function writeSharedConfig ( config ) {
3759 const dir = join ( homedir ( ) , ".hyperframes" ) ;
38- const file = join ( dir , "config.json" ) ;
60+ if ( ! existsSync ( dir ) ) mkdirSync ( dir , { recursive : true } ) ;
61+ writeFileSync ( join ( dir , "config.json" ) , JSON . stringify ( config , null , 2 ) + "\n" ) ;
62+ }
63+
64+ // Adopt a pre-existing media-use-only id (~/.media/anon-id from before this
65+ // change) so upgraders keep their PostHog persona instead of resetting to a new
66+ // one — otherwise cross-surface continuity would start over on upgrade.
67+ function legacyMediaAnonId ( ) {
3968 try {
40- let config = { } ;
69+ const file = join ( homedir ( ) , ".media" , "anon-id" ) ;
4170 if ( existsSync ( file ) ) {
42- try {
43- const parsed = JSON . parse ( readFileSync ( file , "utf8" ) ) ;
44- if ( parsed && typeof parsed === "object" && ! Array . isArray ( parsed ) ) config = parsed ;
45- } catch {
46- config = { } ;
47- }
48- if ( typeof config . anonymousId === "string" && config . anonymousId . trim ( ) ) {
49- return config . anonymousId . trim ( ) ;
50- }
71+ const id = readFileSync ( file , "utf8" ) . trim ( ) ;
72+ if ( id ) return id ;
5173 }
52- if ( ! existsSync ( dir ) ) mkdirSync ( dir , { recursive : true } ) ;
53- const id = randomUUID ( ) ;
54- writeFileSync ( file , JSON . stringify ( { ...config , anonymousId : id } , null , 2 ) + "\n" ) ;
74+ } catch {
75+ // ignore
76+ }
77+ return null ;
78+ }
79+
80+ // Stable per-machine id from the shared config; seeds it (adopting a legacy
81+ // media-use id when present) if absent.
82+ function anonymousId ( ) {
83+ try {
84+ const config = readSharedConfig ( ) ;
85+ if ( typeof config . anonymousId === "string" && config . anonymousId . trim ( ) ) {
86+ return config . anonymousId . trim ( ) ;
87+ }
88+ const id = legacyMediaAnonId ( ) || randomUUID ( ) ;
89+ writeSharedConfig ( { ...config , anonymousId : id } ) ;
5590 return id ;
5691 } catch {
5792 return "anon" ; // best-effort; a shared bucket is fine if the fs is read-only
@@ -77,24 +112,20 @@ function heygenAccountDistinctId() {
77112
78113function showTelemetryNotice ( ) {
79114 if ( optedOut ( ) ) return ;
80- const dir = join ( homedir ( ) , ".media" ) ;
81- const file = join ( dir , "telemetry-notice-shown" ) ;
82- try {
83- if ( existsSync ( file ) ) return ;
84- } catch {
85- return ;
86- }
87- console . error (
88- [
89- "media-use sends usage telemetry: media type, resolution source, and provider; never intent text, file names, or paths." ,
90- "If you sign in to HeyGen, usage links to your account email or username. Opt out with HYPERFRAMES_NO_TELEMETRY=1 or DO_NOT_TRACK=1." ,
91- ] . join ( "\n" ) ,
92- ) ;
93115 try {
94- if ( ! existsSync ( dir ) ) mkdirSync ( dir , { recursive : true } ) ;
95- writeFileSync ( file , new Date ( ) . toISOString ( ) + "\n" ) ;
116+ const config = readSharedConfig ( ) ;
117+ // Shared with the CLI (config.telemetryNoticeShown): shown once per person
118+ // across surfaces, not once per tool.
119+ if ( config . telemetryNoticeShown === true ) return ;
120+ console . error (
121+ [
122+ "media-use sends usage telemetry: media type, resolution source, and provider; never intent text, file names, or paths." ,
123+ "If you sign in to HeyGen, usage links to your account email or username. Opt out with HYPERFRAMES_NO_TELEMETRY=1 or DO_NOT_TRACK=1." ,
124+ ] . join ( "\n" ) ,
125+ ) ;
126+ writeSharedConfig ( { ...config , telemetryNoticeShown : true } ) ;
96127 } catch {
97- // notice marker is best-effort; never surface into the command
128+ // notice is best-effort; never surface into the command
98129 }
99130}
100131
0 commit comments