| ... | @@ -9,6 +9,10 @@ const CodexAuthJsonSchema = Schema.Struct({ | ... | @@ -9,6 +9,10 @@ const CodexAuthJsonSchema = Schema.Struct({ |
| 9 | }), | 9 | }), |
| 10 | }); | 10 | }); |
| 11 | | 11 | |
| | 12 | const ClaudeJsonSchema = Schema.Struct({ |
| | 13 | userID: Schema.String, |
| | 14 | }); |
| | 15 | |
| 12 | class IdentifyUserError extends Schema.TaggedErrorClass<IdentifyUserError>()("IdentifyUserError", { | 16 | class IdentifyUserError extends Schema.TaggedErrorClass<IdentifyUserError>()("IdentifyUserError", { |
| 13 | message: Schema.String, | 17 | message: Schema.String, |
| 14 | cause: Schema.optional(Schema.Defect), | 18 | cause: Schema.optional(Schema.Defect), |
| ... | @@ -37,6 +41,19 @@ const getCodexAccountId = Effect.gen(function* () { | ... | @@ -37,6 +41,19 @@ const getCodexAccountId = Effect.gen(function* () { |
| 37 | return authJson.tokens.account_id; | 41 | return authJson.tokens.account_id; |
| 38 | }); | 42 | }); |
| 39 | | 43 | |
| | 44 | const getClaudeUserId = Effect.gen(function* () { |
| | 45 | const fileSystem = yield* FileSystem.FileSystem; |
| | 46 | const path = yield* Path.Path; |
| | 47 | |
| | 48 | const claudeJsonPath = path.join(homedir(), ".claude.json"); |
| | 49 | const claudeJson = yield* Effect.flatMap( |
| | 50 | fileSystem.readFileString(claudeJsonPath), |
| | 51 | Schema.decodeEffect(Schema.fromJsonString(ClaudeJsonSchema)), |
| | 52 | ); |
| | 53 | |
| | 54 | return claudeJson.userID; |
| | 55 | }); |
| | 56 | |
| 40 | const upsertAnonymousId = Effect.gen(function* () { | 57 | const upsertAnonymousId = Effect.gen(function* () { |
| 41 | const fileSystem = yield* FileSystem.FileSystem; | 58 | const fileSystem = yield* FileSystem.FileSystem; |
| 42 | const path = yield* Path.Path; | 59 | const path = yield* Path.Path; |
| ... | @@ -59,7 +76,8 @@ const upsertAnonymousId = Effect.gen(function* () { | ... | @@ -59,7 +76,8 @@ const upsertAnonymousId = Effect.gen(function* () { |
| 59 | /** | 76 | /** |
| 60 | * getTelemetryIdentifier - Users are "identified" by finding the first match of the following, then hashing the value. | 77 | * getTelemetryIdentifier - Users are "identified" by finding the first match of the following, then hashing the value. |
| 61 | * 1. ~/.codex/auth.json tokens.account_id | 78 | * 1. ~/.codex/auth.json tokens.account_id |
| 62 | * 2. ~/.t3/telemetry/anonymous-id | 79 | * 2. ~/.claude.json userID |
| | 80 | * 3. ~/.t3/telemetry/anonymous-id |
| 63 | */ | 81 | */ |
| 64 | export const getTelemetryIdentifier = Effect.gen(function* () { | 82 | export const getTelemetryIdentifier = Effect.gen(function* () { |
| 65 | const codexAccountId = yield* Effect.result(getCodexAccountId); | 83 | const codexAccountId = yield* Effect.result(getCodexAccountId); |
| ... | @@ -67,6 +85,11 @@ export const getTelemetryIdentifier = Effect.gen(function* () { | ... | @@ -67,6 +85,11 @@ export const getTelemetryIdentifier = Effect.gen(function* () { |
| 67 | return yield* hash(codexAccountId.success); | 85 | return yield* hash(codexAccountId.success); |
| 68 | } | 86 | } |
| 69 | | 87 | |
| | 88 | const claudeUserId = yield* Effect.result(getClaudeUserId); |
| | 89 | if (claudeUserId._tag === "Success") { |
| | 90 | return yield* hash(claudeUserId.success); |
| | 91 | } |
| | 92 | |
| 70 | const anonymousId = yield* Effect.result(upsertAnonymousId); | 93 | const anonymousId = yield* Effect.result(upsertAnonymousId); |
| 71 | if (anonymousId._tag === "Success") { | 94 | if (anonymousId._tag === "Success") { |
| 72 | return yield* hash(anonymousId.success); | 95 | return yield* hash(anonymousId.success); |