spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / mods / telemetry / hooks / register.ts
369 lines · 13.5 KBRaw
1import type { EngineInterface, On } from 'claude-code'
2
3import { answerOf } from './answer-of'
4import Context from './context'
5import Gate from './gate'
6import IsAnalyticsOff from './is-analytics-off'
7import type { Sender } from './sender'
8import { telemetryOf } from './telemetry-of'
9
10/**
11 * Registers the plugin's hooks: the gate on `telemetry.*`, the two hooks
12 * that are what `telemetry.log` and `telemetry.mark` do, and its
13 * engine.create step, which builds the sender over the nouns beneath.
14 *
15 * A row is queued for a built-in caller and refused to any other; a
16 * malformed entry is denied with its reason; an entry for `collector` is
17 * not this plugin's and goes on beneath. On an engine with no `telemetry`
18 * of its own the step adds the noun, its methods the same queueing. A batch
19 * goes out on a timer, when full, and when the session ends.
20 *
21 * @param on the engine's registrar
22 */
23export function register(on: On) {
24 let sender: Sender | undefined
25 let isInteractive: boolean | undefined
26
27 on('telemetry.*', (_$, e, next) => Gate.served(e, next)).catch(
28 (_$, e, next) => Gate.caught(e, next),
29 )
30
31 on('telemetry.log', (_$, e, next) =>
32 sender === undefined || e.to === 'collector'
33 ? next(e)
34 : answerOf(sender.telemetry.log(e)),
35 )
36
37 on('telemetry.mark', (_$, e, next) =>
38 sender === undefined ? next(e) : answerOf(sender.telemetry.mark(e)),
39 )
40
41 on('session.start', (_$, e, next) => {
42 isInteractive = e.isInteractive
43
44 return next(e)
45 })
46
47 on('session.end', async (_$, e, next) => {
48 await sender?.flush()
49
50 return next(e)
51 })
52
53 on('engine.create', async (_$, e, next) => {
54 const beneath = await next(e)
55
56 sender = telemetryOf({
57 authorize: () => beneath.session.authorize(),
58 id: () => beneath.session.id(),
59 model: () => beneath.session.model(),
60 isInteractive: async () =>
61 isInteractive ??
62 (await beneath.session.surfaces()).includes('terminal'),
63 environment: async () => ({
64 userType: await beneath.env.get('USER_TYPE'),
65 nodeEnv: await beneath.env.get('NODE_ENV'),
66 disableTelemetry: await beneath.env.get('DISABLE_TELEMETRY'),
67 disableNonessentialTraffic: await beneath.env.get(
68 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC',
69 ),
70 doNotTrack: await beneath.env.get('DO_NOT_TRACK'),
71 customOauthUrl: await beneath.env.get('CLAUDE_CODE_CUSTOM_OAUTH_URL'),
72 useGateway: await beneath.env.get('CLAUDE_CODE_USE_GATEWAY'),
73 providerManagedByHost: await beneath.env.get(
74 'CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST',
75 ),
76 useBedrock: await beneath.env.get('CLAUDE_CODE_USE_BEDROCK'),
77 useVertex: await beneath.env.get('CLAUDE_CODE_USE_VERTEX'),
78 useFoundry: await beneath.env.get('CLAUDE_CODE_USE_FOUNDRY'),
79 useAnthropicAws: await beneath.env.get('CLAUDE_CODE_USE_ANTHROPIC_AWS'),
80 useAnthropicGoogleCloud: await beneath.env.get(
81 'CLAUDE_CODE_USE_ANTHROPIC_GOOGLE_CLOUD',
82 ),
83 useMantle: await beneath.env.get('CLAUDE_CODE_USE_MANTLE'),
84 }),
85 policy: () => beneath.settings.read({ source: 'policy' }),
86 facts: async () => ({
87 entrypoint: await beneath.env.get('CLAUDE_CODE_ENTRYPOINT'),
88 agentSdkVersion: await beneath.env.get('CLAUDE_AGENT_SDK_VERSION'),
89 hostPlatform: await beneath.env.get('CLAUDE_CODE_HOST_PLATFORM'),
90 os: await beneath.env.get('OS'),
91 processorArchitecture: await beneath.env.get('PROCESSOR_ARCHITECTURE'),
92 shellPath:
93 (await beneath.env.get('SHELL')) ??
94 (await beneath.env.get('COMSPEC')),
95 ci: await beneath.env.get('CI'),
96 claubbit: await beneath.env.get('CLAUBBIT'),
97 githubActions: await beneath.env.get('GITHUB_ACTIONS'),
98 claudeCodeAction: await beneath.env.get('CLAUDE_CODE_ACTION'),
99 claudeCodeRemote: await beneath.env.get('CLAUDE_CODE_REMOTE'),
100 remoteEnvironmentType: await beneath.env.get(
101 'CLAUDE_CODE_REMOTE_ENVIRONMENT_TYPE',
102 ),
103 containerId: await beneath.env.get('CLAUDE_CODE_CONTAINER_ID'),
104 remoteSessionId: await beneath.env.get('CLAUDE_CODE_REMOTE_SESSION_ID'),
105 tags: await beneath.env.get('CLAUDE_CODE_TAGS'),
106 hasSessionAccessToken: IsAnalyticsOff.isEnvSet(
107 await beneath.env.get('CLAUDE_CODE_SESSION_ACCESS_TOKEN'),
108 ),
109 hasSessionIngressTokenFile: IsAnalyticsOff.isEnvSet(
110 await beneath.env.get('CLAUDE_SESSION_INGRESS_TOKEN_FILE'),
111 ),
112 hasWebsocketAuthFileDescriptor: IsAnalyticsOff.isEnvSet(
113 await beneath.env.get('CLAUDE_CODE_WEBSOCKET_AUTH_FILE_DESCRIPTOR'),
114 ),
115 githubEventName: await beneath.env.get('GITHUB_EVENT_NAME'),
116 runnerEnvironment: await beneath.env.get('RUNNER_ENVIRONMENT'),
117 runnerOs: await beneath.env.get('RUNNER_OS'),
118 githubActionPath: await beneath.env.get('GITHUB_ACTION_PATH'),
119 githubActorId: await beneath.env.get('GITHUB_ACTOR_ID'),
120 githubRepositoryId: await beneath.env.get('GITHUB_REPOSITORY_ID'),
121 githubRepositoryOwnerId: await beneath.env.get(
122 'GITHUB_REPOSITORY_OWNER_ID',
123 ),
124 sweBenchRunId: await beneath.env.get('SWE_BENCH_RUN_ID'),
125 sweBenchInstanceId: await beneath.env.get('SWE_BENCH_INSTANCE_ID'),
126 sweBenchTaskId: await beneath.env.get('SWE_BENCH_TASK_ID'),
127 hasP4Port: IsAnalyticsOff.isEnvSet(await beneath.env.get('P4PORT')),
128 wslDistroName: await beneath.env.get('WSL_DISTRO_NAME'),
129 hasCursorTraceId: IsAnalyticsOff.isEnvSet(
130 await beneath.env.get('CURSOR_TRACE_ID'),
131 ),
132 vscodeGitAskpassMain: await beneath.env.get('VSCODE_GIT_ASKPASS_MAIN'),
133 bundleIdentifier: await beneath.env.get('__CFBundleIdentifier'),
134 hasVisualStudioVersion: IsAnalyticsOff.isEnvSet(
135 await beneath.env.get('VisualStudioVersion'),
136 ),
137 terminalEmulator: await beneath.env.get('TERMINAL_EMULATOR'),
138 term: await beneath.env.get('TERM'),
139 termProgram: await beneath.env.get('TERM_PROGRAM'),
140 hasTmux: IsAnalyticsOff.isEnvSet(await beneath.env.get('TMUX')),
141 hasSty: IsAnalyticsOff.isEnvSet(await beneath.env.get('STY')),
142 hasKonsoleVersion: IsAnalyticsOff.isEnvSet(
143 await beneath.env.get('KONSOLE_VERSION'),
144 ),
145 hasGnomeTerminalService: IsAnalyticsOff.isEnvSet(
146 await beneath.env.get('GNOME_TERMINAL_SERVICE'),
147 ),
148 hasXtermVersion: IsAnalyticsOff.isEnvSet(
149 await beneath.env.get('XTERM_VERSION'),
150 ),
151 hasVteVersion: IsAnalyticsOff.isEnvSet(
152 await beneath.env.get('VTE_VERSION'),
153 ),
154 hasTerminatorUuid: IsAnalyticsOff.isEnvSet(
155 await beneath.env.get('TERMINATOR_UUID'),
156 ),
157 hasKittyWindowId: IsAnalyticsOff.isEnvSet(
158 await beneath.env.get('KITTY_WINDOW_ID'),
159 ),
160 hasAlacrittyLog: IsAnalyticsOff.isEnvSet(
161 await beneath.env.get('ALACRITTY_LOG'),
162 ),
163 hasTilixId: IsAnalyticsOff.isEnvSet(await beneath.env.get('TILIX_ID')),
164 hasWtSession: IsAnalyticsOff.isEnvSet(
165 await beneath.env.get('WT_SESSION'),
166 ),
167 hasSessionName: IsAnalyticsOff.isEnvSet(
168 await beneath.env.get('SESSIONNAME'),
169 ),
170 msystem: await beneath.env.get('MSYSTEM'),
171 hasConEmuAnsi: IsAnalyticsOff.isEnvSet(
172 await beneath.env.get('ConEmuANSI'),
173 ),
174 hasConEmuPid: IsAnalyticsOff.isEnvSet(
175 await beneath.env.get('ConEmuPID'),
176 ),
177 hasConEmuTask: IsAnalyticsOff.isEnvSet(
178 await beneath.env.get('ConEmuTask'),
179 ),
180 hasSshConnection: IsAnalyticsOff.isEnvSet(
181 await beneath.env.get('SSH_CONNECTION'),
182 ),
183 hasSshClient: IsAnalyticsOff.isEnvSet(
184 await beneath.env.get('SSH_CLIENT'),
185 ),
186 hasSshTty: IsAnalyticsOff.isEnvSet(await beneath.env.get('SSH_TTY')),
187 }),
188 deployment: async () => [
189 [
190 'codespaces',
191 IsAnalyticsOff.isEnvTruthy(await beneath.env.get('CODESPACES')),
192 ],
193 [
194 'gitpod',
195 IsAnalyticsOff.isEnvSet(await beneath.env.get('GITPOD_WORKSPACE_ID')),
196 ],
197 ['coder', IsAnalyticsOff.isEnvTruthy(await beneath.env.get('CODER'))],
198 [
199 'coder',
200 IsAnalyticsOff.isEnvSet(
201 await beneath.env.get('CODER_WORKSPACE_NAME'),
202 ),
203 ],
204 ['devpod', IsAnalyticsOff.isEnvTruthy(await beneath.env.get('DEVPOD'))],
205 [
206 'devpod',
207 IsAnalyticsOff.isEnvSet(
208 await beneath.env.get('DEVPOD_WORKSPACE_UID'),
209 ),
210 ],
211 [
212 'daytona',
213 IsAnalyticsOff.isEnvSet(await beneath.env.get('DAYTONA_WS_ID')),
214 ],
215 [
216 'gcp-cloud-workstations',
217 IsAnalyticsOff.isEnvTruthy(
218 await beneath.env.get('GOOGLE_CLOUD_WORKSTATIONS'),
219 ),
220 ],
221 [
222 'aws-cloud9',
223 IsAnalyticsOff.isEnvSet(await beneath.env.get('C9_PID')),
224 ],
225 [
226 'aws-cloud9',
227 IsAnalyticsOff.isEnvSet(await beneath.env.get('C9_USER')),
228 ],
229 ['replit', IsAnalyticsOff.isEnvSet(await beneath.env.get('REPL_ID'))],
230 ['replit', IsAnalyticsOff.isEnvSet(await beneath.env.get('REPL_SLUG'))],
231 [
232 'glitch',
233 IsAnalyticsOff.isEnvSet(await beneath.env.get('PROJECT_DOMAIN')),
234 ],
235 ['vercel', IsAnalyticsOff.isEnvTruthy(await beneath.env.get('VERCEL'))],
236 [
237 'railway',
238 IsAnalyticsOff.isEnvSet(
239 await beneath.env.get('RAILWAY_ENVIRONMENT_NAME'),
240 ),
241 ],
242 [
243 'railway',
244 IsAnalyticsOff.isEnvSet(
245 await beneath.env.get('RAILWAY_SERVICE_NAME'),
246 ),
247 ],
248 ['render', IsAnalyticsOff.isEnvTruthy(await beneath.env.get('RENDER'))],
249 [
250 'netlify',
251 IsAnalyticsOff.isEnvTruthy(await beneath.env.get('NETLIFY')),
252 ],
253 ['heroku', IsAnalyticsOff.isEnvSet(await beneath.env.get('DYNO'))],
254 [
255 'fly.io',
256 IsAnalyticsOff.isEnvSet(await beneath.env.get('FLY_APP_NAME')),
257 ],
258 [
259 'fly.io',
260 IsAnalyticsOff.isEnvSet(await beneath.env.get('FLY_MACHINE_ID')),
261 ],
262 [
263 'cloudflare-pages',
264 IsAnalyticsOff.isEnvTruthy(await beneath.env.get('CF_PAGES')),
265 ],
266 [
267 'deno-deploy',
268 IsAnalyticsOff.isEnvSet(await beneath.env.get('DENO_DEPLOYMENT_ID')),
269 ],
270 [
271 'aws-lambda',
272 IsAnalyticsOff.isEnvSet(
273 await beneath.env.get('AWS_LAMBDA_FUNCTION_NAME'),
274 ),
275 ],
276 [
277 'aws-fargate',
278 (await beneath.env.get('AWS_EXECUTION_ENV')) === 'AWS_ECS_FARGATE',
279 ],
280 [
281 'aws-ecs',
282 (await beneath.env.get('AWS_EXECUTION_ENV')) === 'AWS_ECS_EC2',
283 ],
284 [
285 'gcp-cloud-run',
286 IsAnalyticsOff.isEnvSet(await beneath.env.get('K_SERVICE')),
287 ],
288 [
289 'gcp',
290 IsAnalyticsOff.isEnvSet(
291 await beneath.env.get('GOOGLE_CLOUD_PROJECT'),
292 ),
293 ],
294 [
295 'azure-app-service',
296 IsAnalyticsOff.isEnvSet(await beneath.env.get('WEBSITE_SITE_NAME')),
297 ],
298 [
299 'azure-app-service',
300 IsAnalyticsOff.isEnvSet(await beneath.env.get('WEBSITE_SKU')),
301 ],
302 [
303 'azure-functions',
304 IsAnalyticsOff.isEnvSet(
305 await beneath.env.get('AZURE_FUNCTIONS_ENVIRONMENT'),
306 ),
307 ],
308 [
309 'digitalocean-app-platform',
310 IsAnalyticsOff.isEnvSet(await beneath.env.get('APP_URL')) &&
311 String(await beneath.env.get('APP_URL')).includes(
312 'ondigitalocean.app',
313 ),
314 ],
315 [
316 'huggingface-spaces',
317 IsAnalyticsOff.isEnvSet(
318 await beneath.env.get('SPACE_CREATOR_USER_ID'),
319 ),
320 ],
321 [
322 'github-actions',
323 IsAnalyticsOff.isEnvTruthy(await beneath.env.get('GITHUB_ACTIONS')),
324 ],
325 [
326 'gitlab-ci',
327 IsAnalyticsOff.isEnvTruthy(await beneath.env.get('GITLAB_CI')),
328 ],
329 [
330 'circleci',
331 IsAnalyticsOff.isEnvSet(await beneath.env.get('CIRCLECI')),
332 ],
333 [
334 'buildkite',
335 IsAnalyticsOff.isEnvSet(await beneath.env.get('BUILDKITE')),
336 ],
337 ['ci', IsAnalyticsOff.isEnvTruthy(await beneath.env.get('CI'))],
338 [
339 'kubernetes',
340 IsAnalyticsOff.isEnvSet(
341 await beneath.env.get('KUBERNETES_SERVICE_HOST'),
342 ),
343 ],
344 ],
345 configLocation: async () => ({
346 configDir: await beneath.env.get('CLAUDE_CONFIG_DIR'),
347 home: await beneath.env.get('HOME'),
348 userProfile: await beneath.env.get('USERPROFILE'),
349 }),
350 cwd: () => beneath.session.cwd(),
351 repo: () => beneath.session.repo(),
352 version: async () => beneath.session.version(),
353 read: path => beneath.fs.read(path),
354 list: path => beneath.fs.list(path),
355 exists: path => beneath.fs.exists(path),
356 run: argv =>
357 beneath.process.run(argv, { timeoutMs: Context.PROBE_TIMEOUT_MS }),
358 fetch: (url, init) => beneath.http.fetch(url, init),
359 after: (ms, fn) => beneath.clock.after(ms, fn),
360 sleep: ms => beneath.clock.sleep(ms),
361 debug: text => beneath.ui.log(text, { to: 'debug' }),
362 })
363
364 const telemetry: EngineInterface['telemetry'] = sender.telemetry
365 const added = { telemetry }
366
367 return { ...added, ...beneath }
368 })
369}