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