SLOPSHOPPER

telemetry

Plugin analytics: hooks on telemetry.log and telemetry.mark queue first-party rows over $.session, $.env, $.settings, $.fs, $.process, $.clock and $.http…

new
A shopper browsing a rack in a slop shop
Preview could not run: harness produced no result (98 | x-coderCodeAction: await beneath.env.get('X_CODER_ACTION'), ^ error: Expected "}" but found "-" at /Users/raymondxu/slopshopper-mods/.cache/s
README

telemetry

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 X-Coder 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: rtad, 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, X_CODER_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.

What it hooks

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.

What it calls on $

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.

Where it runs, whom it serves

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".

Source 91 files
hooks/register.ts 370 lines
1import type { EngineInterface, On } from 'x-coder'
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          'X_CODER_DISABLE_NONESSENTIAL_TRAFFIC',
69        ),
70        doNotTrack: await beneath.env.get('DO_NOT_TRACK'),
71        customOauthUrl: await beneath.env.get('X_CODER_CUSTOM_OAUTH_URL'),
72        useGateway: await beneath.env.get('X_CODER_USE_GATEWAY'),
73        providerManagedByHost: await beneath.env.get(
74          'X_CODER_PROVIDER_MANAGED_BY_HOST',
75        ),
76        useBedrock: await beneath.env.get('X_CODER_USE_BEDROCK'),
77        useVertex: await beneath.env.get('X_CODER_USE_VERTEX'),
78        useFoundry: await beneath.env.get('X_CODER_USE_FOUNDRY'),
79        useRTADAws: await beneath.env.get('X_CODER_USE_RTAD_AWS'),
80        useRTADGoogleCloud: await beneath.env.get(
81          'X_CODER_USE_RTAD_GOOGLE_CLOUD',
82        ),
83        useMantle: await beneath.env.get('X_CODER_USE_MANTLE'),
84      }),
85      policy: () => beneath.settings.read({ source: 'policy' }),
86      facts: async () => ({
87        entrypoint: await beneath.env.get('X_CODER_ENTRYPOINT'),
88        agentSdkVersion: await beneath.env.get('X_CODER_AGENT_SDK_VERSION'),
89        hostPlatform: await beneath.env.get('X_CODER_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        x-coderCodeAction: await beneath.env.get('X_CODER_ACTION'),
99        x-coderCodeRemote: await beneath.env.get('X_CODER_REMOTE'),
100        remoteEnvironmentType: await beneath.env.get(
101          'X_CODER_REMOTE_ENVIRONMENT_TYPE',
102        ),
103        containerId: await beneath.env.get('X_CODER_CONTAINER_ID'),
104        remoteSessionId: await beneath.env.get('X_CODER_REMOTE_SESSION_ID'),
105        tags: await beneath.env.get('X_CODER_TAGS'),
106        hasSessionAccessToken: IsAnalyticsOff.isEnvSet(
107          await beneath.env.get('X_CODER_SESSION_ACCESS_TOKEN'),
108        ),
109        hasSessionIngressTokenFile: IsAnalyticsOff.isEnvSet(
110          await beneath.env.get('X_CODER_SESSION_INGRESS_TOKEN_FILE'),
111        ),
112        hasWebsocketAuthFileDescriptor: IsAnalyticsOff.isEnvSet(
113          await beneath.env.get('X_CODER_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('X_CODER_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}
370
hooks/answer-of/index.ts 4 lines
1export * from './answer-of.js'
2
3export * as default from '.'
4
hooks/context/index.ts 22 lines
1export * 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 '.'
22
hooks/gate/index.ts 8 lines
1export * from './caught'
2export * from './gate-next'
3export * from './refused'
4export * from './served'
5export * from './served-tiers'
6
7export * as default from '.'
8
hooks/is-analytics-off/index.ts 6 lines
1export * from './is-analytics-off.js'
2export * from './is-env-set'
3export * from './is-env-truthy'
4
5export * as default from '.'
6
hooks/sender/index.ts 4 lines
1export type * from './sender.js'
2
3export * as default from '.'
4
hooks/telemetry-of/index.ts 4 lines
1export * from './telemetry-of.js'
2
3export * as default from '.'
4
hooks/answer-of/answer-of.ts 25 lines
1import 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  )
25
hooks/context/client-type-of/index.ts 5 lines
1export * from './client-type-by-entrypoint'
2export * from './client-type-of.js'
3
4export * as default from '.'
5
hooks/context/context/index.ts 4 lines
1export type * from './context.js'
2
3export * as default from '.'
4
hooks/context/context-of.ts 31 lines
1import 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}
31
hooks/context/entrypoint-of/index.ts 4 lines
1export * from './entrypoint-of.js'
2
3export * as default from '.'
4