Plugin analytics: hooks $.telemetry's two events, so a plugin logs an event or marks a feature's use as a first-party row, sent in batches with the session's…

Plugin analytics as a plugin: its hooks on telemetry.log and telemetry.mark are what those two events do, built over the nouns its engine.create step is handed, and a gate above them serves the plugins built into Claude Code alone: a call from a plugin a person installed or an administrator listed is refused with a reason (the host stamps every call with the plugin that raised it, next.origin, and the gate reads its tier). On an engine that has no $.telemetry of its own the same step adds the noun, so the calls exist there too. An entry names where it goes with to: anthropic, the default, is this mod's; one for collector, the telemetry collector an operator configured, is passed on beneath untouched, and to is never part of a row. $.telemetry.log({ event, props }) queues one event as one first-party row, tengu_plugin_<event>; $.telemetry.mark({ feature, kind, reason?, props? }) marks one use of a feature as the CLI's own feature events do, tengu_feature_<kind> with a feature_name and the mark's properties beside it. Both resolve once the row is queued. Rows go out in batches: one POST to the event-logging ingest with the session's own credential ($.session.authorize(), resolved for each batch) a few seconds after the first row was queued, at once when a hundred wait, and when the session ends; a batch the ingest refuses with a server error, a timeout or a rate limit is tried once more. A session with no first-party credential, or an ingest that still refuses, drops the batch; each outcome is one line in the debug log.
Each row carries what the CLI's own rows carry, gathered through $ once a session: an event id, the install's device id and the signed-in account's ids from the CLI's global config, the session's id, model, client type, entrypoint and interactivity, and an env block (platform and architecture from one uname probe, terminal, shell, package managers and runtimes, CI and GitHub Actions, the remote container, the deployment, the Linux distribution and kernel, WSL, the working directory's version control, and the engine's version, base version and build time from $.session.version(), left empty on an engine that does not answer it), with the repository's remote hash beside the row's properties. What the engine alone knows (its runtime's version, the process's memory, the request's betas, the subscription tier, the calling agent) is not on $, and those columns stay empty.
It sends nothing wherever the CLI's own analytics are off: under DISABLE_TELEMETRY, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC or DO_NOT_TRACK, in a test run, on any third-party provider (Bedrock, Vertex, Foundry and kin) the host does not manage, on a cloud gateway (the environment's switch or the managed policy's login pins), and on a deployment with its own OAuth URL. Each is read through $.env and $.settings before every batch, so a session that has since moved to a third-party provider or a gateway sends nothing more; when the switches cannot be read, nothing is sent either. The row's user_type is ant when USER_TYPE says so, else external.
Nothing free-form reaches a row. An event name and every property key is a snake_case token; a value is a finite number, a boolean, or a Choice (a string named together with the list it is chosen from), under log and mark alike; mark takes ok, sad or bad, with a reason required on the last two and refused on the first. An entry that breaks a rule is refused before anything is queued. Of the environment, a variable whose value is a secret, or names a person or a host, is read for whether it is set and nothing more; a shell is its basename from a closed list.
hooks/register.ts is the module; types/index.d.ts is the noun's contract, the one declaration of $.telemetry that this mod's hooks, a mod calling the noun and a test answering it all read.
telemetry.*, the gate: a caller in the built-in tier (or the engine) goes on, any other is refused, and a gate that throws refuses too. telemetry.log and telemetry.mark, beneath the gate: the entry is checked and its row queued, the hook answering { value }, or { deny } with the reason for an entry that breaks a rule, so the caller's promise rejects naming it. engine.create: the sender is built over await next(e), and the step hands up { ...{ telemetry }, ...beneath }: what is beneath is spread last, so its own telemetry stands where it has one and this mod's is added where it has none; nothing beneath is replaced. session.start, to learn whether a person is at the prompt; session.end, to send what still waits.
$session.authorize, session.id, session.model, session.surfaces, session.cwd, session.repo, settings.read, env.get (the switches and the describing variables, by literal name), fs.read, fs.list, fs.exists, process.run (one sh -c of uname and command -v), clock.after, clock.sleep, http.fetch and ui.log (to the debug log), each on the interface the fold handed it.
This plugin is seated by the CLI itself, on every build whose own analytics are on, and nowhere else; it serves the plugins bundled with the CLI and refuses every other caller. It is not meant to be installed or loaded with --plugin-dir; the folder has a manifest so it reads like every other plugin, not so it can stand alone. A built-in that calls $.telemetry where this one is absent finds no such noun, or, on an engine with the noun of its own, one whose calls queue nothing; either way that is "no analytics here".
hooks/register.ts 370 lines1import 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}
370hooks/answer-of/index.ts 4 lines1export * from './answer-of.js'
2
3export * as default from '.'
4hooks/context/index.ts 22 lines1export * from './client-type-of'
2export * from './context'
3export * from './context-of.js'
4export * from './entrypoint-of'
5export * from './environment-fields'
6export * from './environment-fields-of'
7export * from './identity'
8export * from './linux-distro-of'
9export * from './machine'
10export * from './probe'
11export * from './probe-of'
12export * from './remote-hash-of'
13export * from './session-fields'
14export * from './session-fields-of'
15export * from './shell-of'
16export * from './terminal-of'
17export * from './vcs-of'
18export * from './version-of'
19export * from './wsl-version-of'
20
21export * as default from '.'
22hooks/gate/index.ts 8 lines1export * from './caught'
2export * from './gate-next'
3export * from './refused'
4export * from './served'
5export * from './served-tiers'
6
7export * as default from '.'
8hooks/is-analytics-off/index.ts 6 lines1export * from './is-analytics-off.js'
2export * from './is-env-set'
3export * from './is-env-truthy'
4
5export * as default from '.'
6hooks/sender/index.ts 4 lines1export type * from './sender.js'
2
3export * as default from '.'
4hooks/telemetry-of/index.ts 4 lines1export * from './telemetry-of.js'
2
3export * as default from '.'
4hooks/answer-of/answer-of.ts 25 lines1import Entries from '../entries'
2
3/**
4 * A row's queueing as the hook on its event answers it: `{ value }` once
5 * the row is queued, and a refused entry as `{ deny }` with its reason, so
6 * the caller's promise rejects naming what was wrong.
7 *
8 * Anything else that went wrong is thrown on: the hook failed, and the
9 * engine goes on beneath it as it does for any failed hook.
10 *
11 * @param queued the queueing, settled once the entry is checked and queued
12 * @returns the hook's answer
13 */
14export const answerOf = (queued: Promise<void>) =>
15 queued.then(
16 () => ({ value: undefined }),
17 (error: unknown) => {
18 if (Entries.isRefusedEntry(error)) {
19 return { deny: error.what }
20 }
21
22 throw error
23 },
24 )
25hooks/context/client-type-of/index.ts 5 lines1export * from './client-type-by-entrypoint'
2export * from './client-type-of.js'
3
4export * as default from '.'
5hooks/context/context/index.ts 4 lines1export type * from './context.js'
2
3export * as default from '.'
4hooks/context/context-of.ts 31 lines1import type { TelemetryDeps } from '../telemetry-deps'
2import type { Context } from './context'
3import { environmentFieldsOf } from './environment-fields-of'
4import { probeOf } from './probe-of'
5import { sessionFieldsOf } from './session-fields-of'
6
7/**
8 * Gathers and shapes what holds for the whole session, once: who the rows
9 * are from, the session's fields, its `env` block and the remote hash.
10 *
11 * Nothing in it is a secret or a path: every field is the closed value the
12 * CLI's own column holds.
13 *
14 * @param deps the calls on the nouns beneath
15 * @param isInteractive whether a person is at the prompt
16 * @returns the session's context for every batch after
17 */
18export async function contextOf(
19 deps: TelemetryDeps,
20 isInteractive: boolean,
21): Promise<Context> {
22 const probe = await probeOf(deps, isInteractive)
23
24 return {
25 identity: probe.identity,
26 session: sessionFieldsOf(probe.facts),
27 environment: environmentFieldsOf(probe),
28 remoteHash: probe.remoteHash,
29 }
30}
31hooks/context/entrypoint-of/index.ts 4 lines1export * from './entrypoint-of.js'
2
3export * as default from '.'
4