autotel-devtools 13.1.1 → 15.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +24 -23
  2. package/dist/cli.cjs.map +1 -1
  3. package/dist/cli.d.cts +1 -1
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.js.map +1 -1
  6. package/dist/{error-aggregator-C8X-RQRi.d.ts → error-aggregator-BrgYNNoC.d.ts} +2 -3
  7. package/dist/{error-aggregator-7Q2H7htn.d.cts → error-aggregator-bZ-prq97.d.cts} +2 -3
  8. package/dist/{exporter-yqXiHw1Q.d.ts → exporter-BLqTs00O.d.ts} +1 -2
  9. package/dist/{exporter-kXTGDFoH.d.cts → exporter-BSIS3Qgy.d.cts} +1 -2
  10. package/dist/genai/index.cjs.map +1 -1
  11. package/dist/genai/index.d.cts +0 -1
  12. package/dist/genai/index.d.ts +0 -1
  13. package/dist/genai/index.js.map +1 -1
  14. package/dist/http-BUHnkhrC.cjs.map +1 -1
  15. package/dist/http-yIS667nX.js.map +1 -1
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +2 -3
  18. package/dist/index.d.ts +2 -3
  19. package/dist/index.js.map +1 -1
  20. package/dist/listen-DGzVi7DE.cjs.map +1 -1
  21. package/dist/listen-NVMbBWHw.js.map +1 -1
  22. package/dist/resource-utils-B4UVvfnH.js.map +1 -1
  23. package/dist/resource-utils-DjHJB6uc.cjs.map +1 -1
  24. package/dist/server/exporter.cjs.map +1 -1
  25. package/dist/server/exporter.d.cts +1 -1
  26. package/dist/server/exporter.d.ts +1 -1
  27. package/dist/server/exporter.js.map +1 -1
  28. package/dist/server/index.d.cts +2 -3
  29. package/dist/server/index.d.ts +2 -3
  30. package/dist/server/log-exporter.cjs.map +1 -1
  31. package/dist/server/log-exporter.d.cts +0 -1
  32. package/dist/server/log-exporter.d.ts +0 -1
  33. package/dist/server/log-exporter.js.map +1 -1
  34. package/dist/server/remote-exporter.d.cts +0 -1
  35. package/dist/server/remote-exporter.d.ts +0 -1
  36. package/dist/widget.global.js +14 -13
  37. package/package.json +28 -29
  38. package/skills/autotel-devtools/SKILL.md +0 -123
package/README.md CHANGED
@@ -43,8 +43,8 @@ node app.js
43
43
 
44
44
  The endpoints accept **both OTLP/JSON and OTLP/protobuf** (`application/x-protobuf`),
45
45
  selected automatically from the request `Content-Type`. That means SDKs that default
46
- to protobuf over OTLP HTTP — including the Python, Java, and Go OpenTelemetry SDKs —
47
- work without any extra configuration:
46
+ to protobuf over OTLP HTTP, including the Python, Java, and Go OpenTelemetry SDKs.
47
+ Work without any extra configuration:
48
48
 
49
49
  ```bash
50
50
  # Python / Java / Go SDKs default to http/protobuf — just point them at the receiver
@@ -84,7 +84,7 @@ Or programmatically:
84
84
  Use in Node.js with autotel:
85
85
 
86
86
  ```typescript
87
- import { init, trace } from 'autotel';
87
+ import { init, withTracing } from 'autotel';
88
88
  import { createDevtools } from 'autotel-devtools';
89
89
 
90
90
  const { server, httpServer, exporter, port, close } = createDevtools({
@@ -100,7 +100,7 @@ init({
100
100
  });
101
101
 
102
102
  // Your traced code
103
- const myFunction = trace((ctx) => async () => {
103
+ const myFunction = withTracing({ name: 'example.run' })((ctx) => async () => {
104
104
  // ... this span appears in devtools
105
105
  });
106
106
  ```
@@ -117,19 +117,20 @@ const myFunction = trace((ctx) => async () => {
117
117
  Every response carries an `x-autotel-devtools: <version>` header, and `GET /healthz`
118
118
  returns `{ ok, service: "autotel-devtools", version, clients }`. Use either to confirm
119
119
  you are talking to autotel-devtools rather than another OTLP collector that happens to
120
- share the port — for example before pointing an exporter at `:4318`:
120
+ share the port. For example before pointing an exporter at `:4318`:
121
121
 
122
122
  ```ts
123
- import { probePortHolder } from 'autotel-devtools/server'
123
+ import { probePortHolder } from 'autotel-devtools/server';
124
124
 
125
125
  // 'autotel-devtools' | 'foreign' | 'none'
126
- const holder = await probePortHolder('127.0.0.1', 4318)
126
+ const holder = await probePortHolder('127.0.0.1', 4318);
127
127
  ```
128
128
 
129
- If you start the receiver and the requested port is held by a *foreign* process (some
129
+ If you start the receiver and the requested port is held by a _foreign_ process (some
130
130
  IDEs run their own OTLP collector on `:4318`), the CLI falls forward to the next free
131
131
  port and warns that exporters still aimed at the busy port are reaching that other
132
- process — point them at the bound port, or free the original.
132
+ process. Point them at the bound port, or free the original.
133
+
133
134
  - **Exporters** - OpenTelemetry span/log exporters
134
135
 
135
136
  ### Widget (Svelte 5)
@@ -150,7 +151,7 @@ process — point them at the bound port, or free the original.
150
151
  - ✅ Service map visualization
151
152
  - ✅ Resources view (derived from telemetry)
152
153
  - ✅ GenAI run summaries + narrated walkthrough
153
- - ✅ **Agents view** — observe coding agents (Claude Code, opencode) from their OTel metrics + log events
154
+ - ✅ **Agents view**: observe coding agents (Claude Code, opencode) from their OTel metrics + log events
154
155
  - ✅ Search with debounce (300ms)
155
156
  - ✅ Configurable telemetry limits (env vars)
156
157
  - ✅ Widget position persistence (localStorage)
@@ -163,7 +164,7 @@ When your app emits OpenTelemetry GenAI spans (Vercel AI SDK, Pydantic AI, OpenA
163
164
  Agents, Anthropic, Google GenAI, LangChain, …), the **GenAI** tab gives two extras
164
165
  on top of the per-span detail:
165
166
 
166
- - A **run summary strip** sits above the detail for any multi-span run — total
167
+ - A **run summary strip** sits above the detail for any multi-span run: total
167
168
  cost (table-priced; a trailing `+` marks a lower bound when some calls are
168
169
  unpriced), input→output tokens, reasoning tokens, model calls, tool
169
170
  executions, sub-agents, duration and errors.
@@ -176,8 +177,8 @@ on top of the per-span detail:
176
177
  ### Agents: observe Claude Code (and other coding agents)
177
178
 
178
179
  Coding agents like **Claude Code** emit OpenTelemetry **metrics and log events**
179
- (no traces). The **Agents** tab reconstructs them into a session-centric view —
180
- powered by the [`autotel-agents`](../autotel-agents) package, which also handles
180
+ (no traces). The **Agents** tab reconstructs them into a session-centric view.
181
+ Powered by the [`autotel-agents`](../autotel-agents) package, which also handles
181
182
  opencode and is one adapter away from Codex.
182
183
 
183
184
  One command starts the receiver _and_ launches Claude Code wired to it:
@@ -186,21 +187,21 @@ One command starts the receiver _and_ launches Claude Code wired to it:
186
187
  npx autotel-devtools claude
187
188
  ```
188
189
 
189
- This sets the telemetry env for a live local view — OTLP **`http/protobuf`** to
190
+ This sets the telemetry env for a live local view. OTLP **`http/protobuf`** to
190
191
  this receiver (not the gRPC setup in most guides, which this receiver doesn't
191
192
  speak), 1s export intervals, and `session.id` kept on metrics. Then open the UI
192
193
  and switch to **Agents**.
193
194
 
194
- - `--print-env` — print the env block instead of launching (for managed-settings
195
+ - `--print-env`: print the env block instead of launching (for managed-settings
195
196
  / MDM / VS Code), e.g. `npx autotel-devtools claude --print-env`.
196
- - `--log-prompts` — capture prompt _text_ (default is private: length only).
197
+ - `--log-prompts`: capture prompt _text_ (default is private: length only).
197
198
 
198
199
  What you get per session: a **timeline** (prompts → tool calls → API requests →
199
200
  decisions), a **rollup** (cost, tokens, requests, lines changed), and breakdowns
200
201
  by **tool category**, **MCP server** (`mcp__server__tool`), **sub-agent** (`Task`)
201
- and **skill** (`Skill`) — plus an aggregate strip across all sessions. Cost uses
202
+ and **skill** (`Skill`). Plus an aggregate strip across all sessions. Cost uses
202
203
  the agent's reported `cost_usd`, falling back to a token estimate (badged). MCP
203
- protocol internals are out of scope here — that's `autotel-mcp-instrumentation`.
204
+ protocol internals are out of scope here. That's `autotel-mcp-instrumentation`.
204
205
 
205
206
  ## Configuration
206
207
 
@@ -247,7 +248,7 @@ not a silent black hole.
247
248
  ## Behind a dev-server proxy
248
249
 
249
250
  If your app's dev server proxies `/v1/traces` to the receiver, two classic
250
- bugs make spans vanish with **no error** — both worth knowing:
251
+ bugs make spans vanish with **no error**. Both worth knowing:
251
252
 
252
253
  ```ts
253
254
  // Express / http-proxy-middleware
@@ -274,7 +275,7 @@ app.use(
274
275
  ## Verifying ingestion in tests
275
276
 
276
277
  The receiver exposes an HTTP read-back so a test can assert the collector
277
- **actually received** spans — instead of asserting "the client tried to send",
278
+ **actually received** spans. Instead of asserting "the client tried to send",
278
279
  which a browser-level route intercept can fake (it fulfils the request before
279
280
  it reaches any server):
280
281
 
@@ -287,7 +288,7 @@ DELETE /v1/traces # clear captured telemetry (reset between tests)
287
288
  // Playwright / integration test — bypass any page.route() intercept and ask
288
289
  // the collector directly.
289
290
  await fetch(`${RECEIVER}/v1/traces`, { method: 'DELETE' }); // reset
290
- await runTheUserFlow(); // app emits spans
291
+ await runTheUserFlow(); // app emits spans
291
292
  await expect
292
293
  .poll(async () => (await (await fetch(`${RECEIVER}/v1/traces`)).json()).count)
293
294
  .toBeGreaterThan(0);
@@ -299,7 +300,7 @@ by the origin guard below.
299
300
  ## Read-surface origin guard
300
301
 
301
302
  OTLP **ingestion** (`POST /v1/{traces,logs,metrics}`), `GET /widget.js` and
302
- `GET /healthz` are open to any origin — browser apps on arbitrary dev origins
303
+ `GET /healthz` are open to any origin. Browser apps on arbitrary dev origins
303
304
  must be able to send telemetry and load the embeddable widget. The **read**
304
305
  surface is not: `GET /v1/traces`, `DELETE /v1/traces` and the `/ws` WebSocket are
305
306
  origin-checked so a web page you happen to be visiting can't `fetch()` or stream
@@ -310,7 +311,7 @@ your locally captured prompts, responses and tokens.
310
311
  rebinding) is also rejected. `--host 0.0.0.0` opts into network exposure and
311
312
  applies only the `Origin` check.
312
313
 
313
- The embedded widget keeps working — it connects from a loopback origin
314
+ The embedded widget keeps working. It connects from a loopback origin
314
315
  (`http://localhost:<your-app-port>`). Server-side reads with no `Origin` pass.
315
316
 
316
317
  ## License
package/dist/cli.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.cjs","names":["hostHeaderIsLoopback","DevtoolsServer","listenLoopbackDualStack","attachDevtoolsRoutes","probePortHolder"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n// src/cli.ts\nimport { createServer } from 'node:http'\nimport { spawn } from 'node:child_process'\nimport { readFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { DevtoolsServer } from './server/server'\nimport { attachDevtoolsRoutes } from './server/http'\nimport { hostHeaderIsLoopback } from './server/origin-guard'\nimport { listenLoopbackDualStack } from './server/listen'\nimport { probePortHolder } from './server/identity'\n\ninterface CliOptions {\n port: number\n host: string\n title?: string\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n `autotel-devtools - Standalone OTLP receiver with web devtools UI\n\nUsage: autotel-devtools [port] [options]\n autotel-devtools claude [claude args] [--print-env] [--log-prompts]\n\nSubcommands:\n claude Start the receiver AND launch Claude Code wired to it\n (HTTP/protobuf → this receiver, 1s intervals, session id on).\n Open the \"Agents\" tab to watch sessions, tokens, cost,\n tool / MCP / sub-agent / skill usage live.\n --print-env Print the telemetry env block and exit (don't launch)\n --log-prompts Capture prompt text (default: length only / private)\n\nArguments:\n port Port to listen on (shorthand for --port; must be a positive integer)\n\nOptions:\n -p, --port <port> Port to listen on (default: 4318, env: AUTOTEL_DEVTOOLS_PORT).\n If the port is taken, the next free port is used and a warning is shown.\n -H, --host <host> Host to bind to (default: 127.0.0.1, env: AUTOTEL_DEVTOOLS_HOST)\n -t, --title <title> Dashboard title (env: AUTOTEL_DEVTOOLS_TITLE)\n Env limits: AUTOTEL_MAX_TRACE_COUNT, AUTOTEL_MAX_LOG_COUNT, AUTOTEL_MAX_METRIC_COUNT\n -h, --help Show this help message\n -v, --version Show version number\n\nEndpoints:\n GET / Web devtools UI (fullpage)\n GET /widget.js Widget bundle (embed in your app)\n POST /v1/traces Receive OTLP JSON trace data\n GET /v1/traces Read back received traces (verify ingestion in tests)\n DELETE /v1/traces Clear captured telemetry (test reset)\n POST /v1/logs Receive OTLP JSON log data\n POST /v1/metrics Receive OTLP JSON metric data\n WS /ws WebSocket stream for real-time updates\n GET /healthz Health check\n\nExamples:\n npx autotel-devtools\n npx autotel-devtools 4319\n npx autotel-devtools -p 4319 -H 0.0.0.0\n npx autotel-devtools claude # watch Claude Code in the Agents tab\n npx autotel-devtools claude --print-env # just print the env (for MDM / VS Code)\n\nThen point your app:\n OTEL_EXPORTER_OTLP_PROTOCOL=http/json OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 node app.js\n\nView in browser:\n http://localhost:4318\n\nOr embed widget in your app:\n <script src=\"http://localhost:4318/widget.js\"></script>\n` + '\\n',\n )\n}\n\nfunction printVersion(): void {\n try {\n const dir = dirname(fileURLToPath(import.meta.url))\n const pkgPath = resolve(dir, '..', 'package.json')\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))\n process.stdout.write(`${pkg.version}\\n`)\n } catch {\n process.stdout.write('unknown\\n')\n }\n}\n\nfunction parseArgs(argv: string[]): CliOptions | null {\n const options: CliOptions = {\n port: parsePort(process.env.AUTOTEL_DEVTOOLS_PORT || '4318'),\n host: process.env.AUTOTEL_DEVTOOLS_HOST || '127.0.0.1',\n title: process.env.AUTOTEL_DEVTOOLS_TITLE,\n }\n\n // An explicit `--port`/`-p` always wins, regardless of where it sits in\n // argv. A bare numeric positional is shorthand for `--port`, but only when\n // no explicit flag was given — and only the first positional counts, so a\n // stray \"4318\" can't override an earlier positional either.\n let portWasExplicit = false\n let positionalPortConsumed = false\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]\n const next = argv[i + 1]\n if (arg === '--help' || arg === '-h') { printHelp(); return null }\n if (arg === '--version' || arg === '-v') { printVersion(); return null }\n if ((arg === '--port' || arg === '-p') && next) { options.port = parsePort(next); portWasExplicit = true; i++; continue }\n if ((arg === '--host' || arg === '-H') && next) { options.host = next; i++; continue }\n if ((arg === '--title' || arg === '-t') && next) { options.title = next; i++; continue }\n if (/^\\d+$/.test(arg) && !positionalPortConsumed) {\n if (!portWasExplicit) options.port = parsePort(arg)\n positionalPortConsumed = true\n continue\n }\n }\n\n return options\n}\n\nfunction parsePort(value: string): number {\n const n = Number(value)\n if (!Number.isInteger(n) || n < 0 || n > 65535) {\n process.stderr.write(`[autotel-devtools] invalid port: ${value}\\n`)\n process.exit(2)\n }\n return n\n}\n\ninterface RunningReceiver {\n wsServer: DevtoolsServer\n closeAll: () => Promise<void>\n addresses: string[]\n warnings: string[]\n boundPort: number\n uiBase: string\n}\n\nasync function startReceiver(options: CliOptions): Promise<RunningReceiver> {\n const httpServer = createServer()\n const loopbackOnly = hostHeaderIsLoopback(options.host)\n const wsServer = new DevtoolsServer({ server: httpServer, host: options.host, verbose: true })\n attachDevtoolsRoutes(httpServer, wsServer, { loopbackOnly })\n\n const listeners = listenLoopbackDualStack({\n primary: httpServer,\n port: options.port,\n host: options.host,\n attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer, { loopbackOnly }),\n })\n\n const { addresses, warnings, port: boundPort } = await listeners.ready\n\n // Falling forward to a different port means the one we wanted is held by\n // someone else. Classify them: another autotel-devtools (benign) versus a\n // foreign process. The foreign case is the silent footgun — apps keep\n // exporting OTLP to the requested port and reach that process, not us, so\n // this UI stays empty while they see export errors. Say so, with the fix.\n if (boundPort !== options.port) {\n const holder = await probePortHolder(options.host, options.port)\n if (holder === 'autotel-devtools') {\n warnings.push(\n `another autotel-devtools is already running on port ${options.port}; ` +\n `this instance is on ${boundPort}. Use the existing one, or stop it and restart here.`,\n )\n } else {\n warnings.push(\n `port ${options.port} is held by another process that is NOT autotel-devtools. ` +\n `Anything exporting OTLP to :${options.port} is reaching that process, not this devtools. ` +\n `Point your exporter at :${boundPort}, or free :${options.port} and restart.`,\n )\n }\n }\n\n const uiBase = `http://${options.host === 'localhost' ? '127.0.0.1' : options.host}:${boundPort}`\n return {\n wsServer,\n closeAll: () => Promise.all([wsServer.close(), listeners.closeSibling()]).then(() => undefined),\n addresses,\n warnings,\n boundPort,\n uiBase,\n }\n}\n\n// Telemetry env that wires Claude Code (and any OTel-via-env CLI) to this\n// receiver for a *live* local view: HTTP/protobuf (NOT gRPC — this receiver\n// speaks HTTP only), 1s export intervals so events show up promptly, and\n// session.id kept on metrics so metric-only signals join their session.\nfunction buildAgentEnv(uiBase: string, logPrompts: boolean): Record<string, string> {\n const env: Record<string, string> = {\n CLAUDE_CODE_ENABLE_TELEMETRY: '1',\n OTEL_METRICS_EXPORTER: 'otlp',\n OTEL_LOGS_EXPORTER: 'otlp',\n OTEL_EXPORTER_OTLP_PROTOCOL: 'http/protobuf',\n OTEL_EXPORTER_OTLP_ENDPOINT: uiBase,\n OTEL_METRIC_EXPORT_INTERVAL: '1000',\n OTEL_LOGS_EXPORT_INTERVAL: '1000',\n OTEL_METRICS_INCLUDE_SESSION_ID: 'true',\n }\n // Private by default: prompt *text* only flows when explicitly opted in.\n if (logPrompts) env.OTEL_LOG_USER_PROMPTS = '1'\n return env\n}\n\nfunction printEnvBlock(env: Record<string, string>): void {\n for (const [key, value] of Object.entries(env)) {\n process.stdout.write(`export ${key}=${value}\\n`)\n }\n}\n\ninterface ClaudeOptions {\n port: number\n host: string\n printEnv: boolean\n logPrompts: boolean\n claudeArgs: string[]\n}\n\nfunction parseClaudeArgs(argv: string[]): ClaudeOptions {\n const opts: ClaudeOptions = {\n port: parsePort(process.env.AUTOTEL_DEVTOOLS_PORT || '4318'),\n host: process.env.AUTOTEL_DEVTOOLS_HOST || '127.0.0.1',\n printEnv: false,\n logPrompts: false,\n claudeArgs: [],\n }\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]\n const next = argv[i + 1]\n if (arg === '--') { opts.claudeArgs.push(...argv.slice(i + 1)); break }\n if (arg === '--print-env') { opts.printEnv = true; continue }\n if (arg === '--log-prompts') { opts.logPrompts = true; continue }\n if ((arg === '--port' || arg === '-p') && next) { opts.port = parsePort(next); i++; continue }\n if ((arg === '--host' || arg === '-H') && next) { opts.host = next; i++; continue }\n // Anything else is passed straight through to `claude`.\n opts.claudeArgs.push(arg)\n }\n return opts\n}\n\nasync function runClaudeSubcommand(argv: string[]): Promise<void> {\n const opts = parseClaudeArgs(argv)\n\n // --print-env needs no running server: emit the block for the configured\n // port so users can paste it into a shell / managed-settings file.\n if (opts.printEnv) {\n const uiBase = `http://${opts.host === 'localhost' ? '127.0.0.1' : opts.host}:${opts.port}`\n printEnvBlock(buildAgentEnv(uiBase, opts.logPrompts))\n return\n }\n\n const receiver = await startReceiver({ port: opts.port, host: opts.host })\n const env = buildAgentEnv(receiver.uiBase, opts.logPrompts)\n\n process.stdout.write(`\\n autotel-devtools — Claude Code\\n\\n`)\n process.stdout.write(` Receiver: ${receiver.uiBase}\\n`)\n process.stdout.write(` UI: ${receiver.uiBase} → open the \"Agents\" tab\\n`)\n if (!opts.logPrompts) {\n process.stdout.write(` Prompts: private (length only) — add --log-prompts to capture text\\n`)\n }\n for (const w of receiver.warnings) process.stdout.write(` ⚠ ${w}\\n`)\n process.stdout.write(`\\n Launching claude…\\n\\n`)\n\n const child = spawn('claude', opts.claudeArgs, {\n stdio: 'inherit',\n env: { ...process.env, ...env },\n })\n\n const shutdown = () => { receiver.closeAll().then(() => process.exit(0)) }\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n\n child.on('error', (err) => {\n const reason = (err as NodeJS.ErrnoException).code === 'ENOENT'\n ? `'claude' not found on PATH. Install Claude Code, or run the receiver alone with 'npx autotel-devtools' and point claude at ${receiver.uiBase} using --print-env.`\n : err.message\n process.stderr.write(`[autotel-devtools] ${reason}\\n`)\n receiver.closeAll().then(() => process.exit(1))\n })\n child.on('exit', (code) => {\n receiver.closeAll().then(() => process.exit(code ?? 0))\n })\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2)\n\n // Subcommand: `autotel-devtools claude [claude args] [--print-env] [--log-prompts]`\n if (argv[0] === 'claude') {\n await runClaudeSubcommand(argv.slice(1))\n return\n }\n\n const options = parseArgs(argv)\n if (!options) { process.exit(0) }\n\n const receiver = await startReceiver(options)\n const { addresses, warnings, uiBase } = receiver\n const title = options.title || 'autotel-devtools'\n process.stdout.write(`\\n ${title}\\n\\n`)\n process.stdout.write(` Listening: ${addresses.join(' + ')}\\n`)\n process.stdout.write(` UI: ${uiBase} (open in a browser)\\n`)\n process.stdout.write(` OTLP: ${uiBase}/v1/traces\\n`)\n process.stdout.write(` WebSocket: ${uiBase.replace('http', 'ws')}/ws\\n\\n`)\n // The widget bundle auto-mounts on load, so the bare <script> tag is all the\n // user needs — spell that out, plus the two opt-in variations, so nobody\n // wonders whether they also have to add an element by hand.\n process.stdout.write(\n ` Embed in your app — paste into your HTML; a floating panel appears automatically:\\n`,\n )\n process.stdout.write(` <script src=\"${uiBase}/widget.js\"></script>\\n\\n`)\n process.stdout.write(\n ` full screen instead: <script src=\"${uiBase}/widget.js?mode=fullpage\"></script>\\n`,\n )\n process.stdout.write(\n ` choose where it goes: add <autotel-devtools></autotel-devtools> to your markup\\n\\n`,\n )\n process.stdout.write(` Or point any OTLP exporter at this receiver:\\n`)\n process.stdout.write(` OTEL_EXPORTER_OTLP_PROTOCOL=http/json\\n`)\n process.stdout.write(` OTEL_EXPORTER_OTLP_ENDPOINT=${uiBase}\\n\\n`)\n // Self-check: confirm the collector is reachable AND that ingestion works,\n // not just that something is listening. Reading /v1/traces back is the same\n // check a test should make instead of trusting \"the client tried to send\".\n process.stdout.write(` Verify ingestion: curl -s ${uiBase}/v1/traces\\n\\n`)\n for (const w of warnings) {\n process.stdout.write(` ⚠ ${w}\\n`)\n }\n if (warnings.length > 0) process.stdout.write('\\n')\n\n const shutdown = () => {\n receiver.closeAll().then(() => process.exit(0))\n }\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n}\n\nmain().catch((error) => {\n process.stderr.write(`[autotel-devtools] failed to start: ${error instanceof Error ? error.message : String(error)}\\n`)\n process.exit(1)\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.cjs","names":["hostHeaderIsLoopback","DevtoolsServer","listenLoopbackDualStack","attachDevtoolsRoutes","probePortHolder"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n// src/cli.ts\nimport { createServer } from 'node:http';\nimport { spawn } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { DevtoolsServer } from './server/server';\nimport { attachDevtoolsRoutes } from './server/http';\nimport { hostHeaderIsLoopback } from './server/origin-guard';\nimport { listenLoopbackDualStack } from './server/listen';\nimport { probePortHolder } from './server/identity';\n\ninterface CliOptions {\n port: number;\n host: string;\n title?: string;\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n `autotel-devtools - Standalone OTLP receiver with web devtools UI\n\nUsage: autotel-devtools [port] [options]\n autotel-devtools claude [claude args] [--print-env] [--log-prompts]\n\nSubcommands:\n claude Start the receiver AND launch Claude Code wired to it\n (HTTP/protobuf → this receiver, 1s intervals, session id on).\n Open the \"Agents\" tab to watch sessions, tokens, cost,\n tool / MCP / sub-agent / skill usage live.\n --print-env Print the telemetry env block and exit (don't launch)\n --log-prompts Capture prompt text (default: length only / private)\n\nArguments:\n port Port to listen on (shorthand for --port; must be a positive integer)\n\nOptions:\n -p, --port <port> Port to listen on (default: 4318, env: AUTOTEL_DEVTOOLS_PORT).\n If the port is taken, the next free port is used and a warning is shown.\n -H, --host <host> Host to bind to (default: 127.0.0.1, env: AUTOTEL_DEVTOOLS_HOST)\n -t, --title <title> Dashboard title (env: AUTOTEL_DEVTOOLS_TITLE)\n Env limits: AUTOTEL_MAX_TRACE_COUNT, AUTOTEL_MAX_LOG_COUNT, AUTOTEL_MAX_METRIC_COUNT\n -h, --help Show this help message\n -v, --version Show version number\n\nEndpoints:\n GET / Web devtools UI (fullpage)\n GET /widget.js Widget bundle (embed in your app)\n POST /v1/traces Receive OTLP JSON trace data\n GET /v1/traces Read back received traces (verify ingestion in tests)\n DELETE /v1/traces Clear captured telemetry (test reset)\n POST /v1/logs Receive OTLP JSON log data\n POST /v1/metrics Receive OTLP JSON metric data\n WS /ws WebSocket stream for real-time updates\n GET /healthz Health check\n\nExamples:\n npx autotel-devtools\n npx autotel-devtools 4319\n npx autotel-devtools -p 4319 -H 0.0.0.0\n npx autotel-devtools claude # watch Claude Code in the Agents tab\n npx autotel-devtools claude --print-env # just print the env (for MDM / VS Code)\n\nThen point your app:\n OTEL_EXPORTER_OTLP_PROTOCOL=http/json OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 node app.js\n\nView in browser:\n http://localhost:4318\n\nOr embed widget in your app:\n <script src=\"http://localhost:4318/widget.js\"></script>\n` + '\\n',\n );\n}\n\nfunction printVersion(): void {\n try {\n const dir = dirname(fileURLToPath(import.meta.url));\n const pkgPath = resolve(dir, '..', 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));\n process.stdout.write(`${pkg.version}\\n`);\n } catch {\n process.stdout.write('unknown\\n');\n }\n}\n\nfunction parseArgs(argv: string[]): CliOptions | null {\n const options: CliOptions = {\n port: parsePort(process.env.AUTOTEL_DEVTOOLS_PORT || '4318'),\n host: process.env.AUTOTEL_DEVTOOLS_HOST || '127.0.0.1',\n title: process.env.AUTOTEL_DEVTOOLS_TITLE,\n };\n\n // An explicit `--port`/`-p` always wins, regardless of where it sits in\n // argv. A bare numeric positional is shorthand for `--port`, but only when\n // no explicit flag was given — and only the first positional counts, so a\n // stray \"4318\" can't override an earlier positional either.\n let portWasExplicit = false;\n let positionalPortConsumed = false;\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = argv[i + 1];\n if (arg === '--help' || arg === '-h') {\n printHelp();\n return null;\n }\n if (arg === '--version' || arg === '-v') {\n printVersion();\n return null;\n }\n if ((arg === '--port' || arg === '-p') && next) {\n options.port = parsePort(next);\n portWasExplicit = true;\n i++;\n continue;\n }\n if ((arg === '--host' || arg === '-H') && next) {\n options.host = next;\n i++;\n continue;\n }\n if ((arg === '--title' || arg === '-t') && next) {\n options.title = next;\n i++;\n continue;\n }\n if (/^\\d+$/.test(arg) && !positionalPortConsumed) {\n if (!portWasExplicit) options.port = parsePort(arg);\n positionalPortConsumed = true;\n continue;\n }\n }\n\n return options;\n}\n\nfunction parsePort(value: string): number {\n const n = Number(value);\n if (!Number.isInteger(n) || n < 0 || n > 65535) {\n process.stderr.write(`[autotel-devtools] invalid port: ${value}\\n`);\n process.exit(2);\n }\n return n;\n}\n\ninterface RunningReceiver {\n wsServer: DevtoolsServer;\n closeAll: () => Promise<void>;\n addresses: string[];\n warnings: string[];\n boundPort: number;\n uiBase: string;\n}\n\nasync function startReceiver(options: CliOptions): Promise<RunningReceiver> {\n const httpServer = createServer();\n const loopbackOnly = hostHeaderIsLoopback(options.host);\n const wsServer = new DevtoolsServer({\n server: httpServer,\n host: options.host,\n verbose: true,\n });\n attachDevtoolsRoutes(httpServer, wsServer, { loopbackOnly });\n\n const listeners = listenLoopbackDualStack({\n primary: httpServer,\n port: options.port,\n host: options.host,\n attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer, { loopbackOnly }),\n });\n\n const { addresses, warnings, port: boundPort } = await listeners.ready;\n\n // Falling forward to a different port means the one we wanted is held by\n // someone else. Classify them: another autotel-devtools (benign) versus a\n // foreign process. The foreign case is the silent footgun — apps keep\n // exporting OTLP to the requested port and reach that process, not us, so\n // this UI stays empty while they see export errors. Say so, with the fix.\n if (boundPort !== options.port) {\n const holder = await probePortHolder(options.host, options.port);\n if (holder === 'autotel-devtools') {\n warnings.push(\n `another autotel-devtools is already running on port ${options.port}; ` +\n `this instance is on ${boundPort}. Use the existing one, or stop it and restart here.`,\n );\n } else {\n warnings.push(\n `port ${options.port} is held by another process that is NOT autotel-devtools. ` +\n `Anything exporting OTLP to :${options.port} is reaching that process, not this devtools. ` +\n `Point your exporter at :${boundPort}, or free :${options.port} and restart.`,\n );\n }\n }\n\n const uiBase = `http://${options.host === 'localhost' ? '127.0.0.1' : options.host}:${boundPort}`;\n return {\n wsServer,\n closeAll: () =>\n Promise.all([wsServer.close(), listeners.closeSibling()]).then(\n () => undefined,\n ),\n addresses,\n warnings,\n boundPort,\n uiBase,\n };\n}\n\n// Telemetry env that wires Claude Code (and any OTel-via-env CLI) to this\n// receiver for a *live* local view: HTTP/protobuf (NOT gRPC — this receiver\n// speaks HTTP only), 1s export intervals so events show up promptly, and\n// session.id kept on metrics so metric-only signals join their session.\nfunction buildAgentEnv(\n uiBase: string,\n logPrompts: boolean,\n): Record<string, string> {\n const env: Record<string, string> = {\n CLAUDE_CODE_ENABLE_TELEMETRY: '1',\n OTEL_METRICS_EXPORTER: 'otlp',\n OTEL_LOGS_EXPORTER: 'otlp',\n OTEL_EXPORTER_OTLP_PROTOCOL: 'http/protobuf',\n OTEL_EXPORTER_OTLP_ENDPOINT: uiBase,\n OTEL_METRIC_EXPORT_INTERVAL: '1000',\n OTEL_LOGS_EXPORT_INTERVAL: '1000',\n OTEL_METRICS_INCLUDE_SESSION_ID: 'true',\n };\n // Private by default: prompt *text* only flows when explicitly opted in.\n if (logPrompts) env.OTEL_LOG_USER_PROMPTS = '1';\n return env;\n}\n\nfunction printEnvBlock(env: Record<string, string>): void {\n for (const [key, value] of Object.entries(env)) {\n process.stdout.write(`export ${key}=${value}\\n`);\n }\n}\n\ninterface ClaudeOptions {\n port: number;\n host: string;\n printEnv: boolean;\n logPrompts: boolean;\n claudeArgs: string[];\n}\n\nfunction parseClaudeArgs(argv: string[]): ClaudeOptions {\n const opts: ClaudeOptions = {\n port: parsePort(process.env.AUTOTEL_DEVTOOLS_PORT || '4318'),\n host: process.env.AUTOTEL_DEVTOOLS_HOST || '127.0.0.1',\n printEnv: false,\n logPrompts: false,\n claudeArgs: [],\n };\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = argv[i + 1];\n if (arg === '--') {\n opts.claudeArgs.push(...argv.slice(i + 1));\n break;\n }\n if (arg === '--print-env') {\n opts.printEnv = true;\n continue;\n }\n if (arg === '--log-prompts') {\n opts.logPrompts = true;\n continue;\n }\n if ((arg === '--port' || arg === '-p') && next) {\n opts.port = parsePort(next);\n i++;\n continue;\n }\n if ((arg === '--host' || arg === '-H') && next) {\n opts.host = next;\n i++;\n continue;\n }\n // Anything else is passed straight through to `claude`.\n opts.claudeArgs.push(arg);\n }\n return opts;\n}\n\nasync function runClaudeSubcommand(argv: string[]): Promise<void> {\n const opts = parseClaudeArgs(argv);\n\n // --print-env needs no running server: emit the block for the configured\n // port so users can paste it into a shell / managed-settings file.\n if (opts.printEnv) {\n const uiBase = `http://${opts.host === 'localhost' ? '127.0.0.1' : opts.host}:${opts.port}`;\n printEnvBlock(buildAgentEnv(uiBase, opts.logPrompts));\n return;\n }\n\n const receiver = await startReceiver({ port: opts.port, host: opts.host });\n const env = buildAgentEnv(receiver.uiBase, opts.logPrompts);\n\n process.stdout.write(`\\n autotel-devtools — Claude Code\\n\\n`);\n process.stdout.write(` Receiver: ${receiver.uiBase}\\n`);\n process.stdout.write(\n ` UI: ${receiver.uiBase} → open the \"Agents\" tab\\n`,\n );\n if (!opts.logPrompts) {\n process.stdout.write(\n ` Prompts: private (length only) — add --log-prompts to capture text\\n`,\n );\n }\n for (const w of receiver.warnings) process.stdout.write(` ⚠ ${w}\\n`);\n process.stdout.write(`\\n Launching claude…\\n\\n`);\n\n const child = spawn('claude', opts.claudeArgs, {\n stdio: 'inherit',\n env: { ...process.env, ...env },\n });\n\n const shutdown = () => {\n receiver.closeAll().then(() => process.exit(0));\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n child.on('error', (err) => {\n const reason =\n (err as NodeJS.ErrnoException).code === 'ENOENT'\n ? `'claude' not found on PATH. Install Claude Code, or run the receiver alone with 'npx autotel-devtools' and point claude at ${receiver.uiBase} using --print-env.`\n : err.message;\n process.stderr.write(`[autotel-devtools] ${reason}\\n`);\n receiver.closeAll().then(() => process.exit(1));\n });\n child.on('exit', (code) => {\n receiver.closeAll().then(() => process.exit(code ?? 0));\n });\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n\n // Subcommand: `autotel-devtools claude [claude args] [--print-env] [--log-prompts]`\n if (argv[0] === 'claude') {\n await runClaudeSubcommand(argv.slice(1));\n return;\n }\n\n const options = parseArgs(argv);\n if (!options) {\n process.exit(0);\n }\n\n const receiver = await startReceiver(options);\n const { addresses, warnings, uiBase } = receiver;\n const title = options.title || 'autotel-devtools';\n process.stdout.write(`\\n ${title}\\n\\n`);\n process.stdout.write(` Listening: ${addresses.join(' + ')}\\n`);\n process.stdout.write(` UI: ${uiBase} (open in a browser)\\n`);\n process.stdout.write(` OTLP: ${uiBase}/v1/traces\\n`);\n process.stdout.write(` WebSocket: ${uiBase.replace('http', 'ws')}/ws\\n\\n`);\n // The widget bundle auto-mounts on load, so the bare <script> tag is all the\n // user needs — spell that out, plus the two opt-in variations, so nobody\n // wonders whether they also have to add an element by hand.\n process.stdout.write(\n ` Embed in your app — paste into your HTML; a floating panel appears automatically:\\n`,\n );\n process.stdout.write(` <script src=\"${uiBase}/widget.js\"></script>\\n\\n`);\n process.stdout.write(\n ` full screen instead: <script src=\"${uiBase}/widget.js?mode=fullpage\"></script>\\n`,\n );\n process.stdout.write(\n ` choose where it goes: add <autotel-devtools></autotel-devtools> to your markup\\n\\n`,\n );\n process.stdout.write(` Or point any OTLP exporter at this receiver:\\n`);\n process.stdout.write(` OTEL_EXPORTER_OTLP_PROTOCOL=http/json\\n`);\n process.stdout.write(` OTEL_EXPORTER_OTLP_ENDPOINT=${uiBase}\\n\\n`);\n // Self-check: confirm the collector is reachable AND that ingestion works,\n // not just that something is listening. Reading /v1/traces back is the same\n // check a test should make instead of trusting \"the client tried to send\".\n process.stdout.write(` Verify ingestion: curl -s ${uiBase}/v1/traces\\n\\n`);\n for (const w of warnings) {\n process.stdout.write(` ⚠ ${w}\\n`);\n }\n if (warnings.length > 0) process.stdout.write('\\n');\n\n const shutdown = () => {\n receiver.closeAll().then(() => process.exit(0));\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n\nmain().catch((error) => {\n process.stderr.write(\n `[autotel-devtools] failed to start: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n process.exit(1);\n});\n"],"mappings":"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"}
package/dist/cli.d.cts CHANGED
@@ -1 +1 @@
1
- export { };
1
+ export {}
package/dist/cli.d.ts CHANGED
@@ -1 +1 @@
1
- export { };
1
+ export {}
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n// src/cli.ts\nimport { createServer } from 'node:http'\nimport { spawn } from 'node:child_process'\nimport { readFileSync } from 'node:fs'\nimport { resolve, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { DevtoolsServer } from './server/server'\nimport { attachDevtoolsRoutes } from './server/http'\nimport { hostHeaderIsLoopback } from './server/origin-guard'\nimport { listenLoopbackDualStack } from './server/listen'\nimport { probePortHolder } from './server/identity'\n\ninterface CliOptions {\n port: number\n host: string\n title?: string\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n `autotel-devtools - Standalone OTLP receiver with web devtools UI\n\nUsage: autotel-devtools [port] [options]\n autotel-devtools claude [claude args] [--print-env] [--log-prompts]\n\nSubcommands:\n claude Start the receiver AND launch Claude Code wired to it\n (HTTP/protobuf → this receiver, 1s intervals, session id on).\n Open the \"Agents\" tab to watch sessions, tokens, cost,\n tool / MCP / sub-agent / skill usage live.\n --print-env Print the telemetry env block and exit (don't launch)\n --log-prompts Capture prompt text (default: length only / private)\n\nArguments:\n port Port to listen on (shorthand for --port; must be a positive integer)\n\nOptions:\n -p, --port <port> Port to listen on (default: 4318, env: AUTOTEL_DEVTOOLS_PORT).\n If the port is taken, the next free port is used and a warning is shown.\n -H, --host <host> Host to bind to (default: 127.0.0.1, env: AUTOTEL_DEVTOOLS_HOST)\n -t, --title <title> Dashboard title (env: AUTOTEL_DEVTOOLS_TITLE)\n Env limits: AUTOTEL_MAX_TRACE_COUNT, AUTOTEL_MAX_LOG_COUNT, AUTOTEL_MAX_METRIC_COUNT\n -h, --help Show this help message\n -v, --version Show version number\n\nEndpoints:\n GET / Web devtools UI (fullpage)\n GET /widget.js Widget bundle (embed in your app)\n POST /v1/traces Receive OTLP JSON trace data\n GET /v1/traces Read back received traces (verify ingestion in tests)\n DELETE /v1/traces Clear captured telemetry (test reset)\n POST /v1/logs Receive OTLP JSON log data\n POST /v1/metrics Receive OTLP JSON metric data\n WS /ws WebSocket stream for real-time updates\n GET /healthz Health check\n\nExamples:\n npx autotel-devtools\n npx autotel-devtools 4319\n npx autotel-devtools -p 4319 -H 0.0.0.0\n npx autotel-devtools claude # watch Claude Code in the Agents tab\n npx autotel-devtools claude --print-env # just print the env (for MDM / VS Code)\n\nThen point your app:\n OTEL_EXPORTER_OTLP_PROTOCOL=http/json OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 node app.js\n\nView in browser:\n http://localhost:4318\n\nOr embed widget in your app:\n <script src=\"http://localhost:4318/widget.js\"></script>\n` + '\\n',\n )\n}\n\nfunction printVersion(): void {\n try {\n const dir = dirname(fileURLToPath(import.meta.url))\n const pkgPath = resolve(dir, '..', 'package.json')\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))\n process.stdout.write(`${pkg.version}\\n`)\n } catch {\n process.stdout.write('unknown\\n')\n }\n}\n\nfunction parseArgs(argv: string[]): CliOptions | null {\n const options: CliOptions = {\n port: parsePort(process.env.AUTOTEL_DEVTOOLS_PORT || '4318'),\n host: process.env.AUTOTEL_DEVTOOLS_HOST || '127.0.0.1',\n title: process.env.AUTOTEL_DEVTOOLS_TITLE,\n }\n\n // An explicit `--port`/`-p` always wins, regardless of where it sits in\n // argv. A bare numeric positional is shorthand for `--port`, but only when\n // no explicit flag was given — and only the first positional counts, so a\n // stray \"4318\" can't override an earlier positional either.\n let portWasExplicit = false\n let positionalPortConsumed = false\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]\n const next = argv[i + 1]\n if (arg === '--help' || arg === '-h') { printHelp(); return null }\n if (arg === '--version' || arg === '-v') { printVersion(); return null }\n if ((arg === '--port' || arg === '-p') && next) { options.port = parsePort(next); portWasExplicit = true; i++; continue }\n if ((arg === '--host' || arg === '-H') && next) { options.host = next; i++; continue }\n if ((arg === '--title' || arg === '-t') && next) { options.title = next; i++; continue }\n if (/^\\d+$/.test(arg) && !positionalPortConsumed) {\n if (!portWasExplicit) options.port = parsePort(arg)\n positionalPortConsumed = true\n continue\n }\n }\n\n return options\n}\n\nfunction parsePort(value: string): number {\n const n = Number(value)\n if (!Number.isInteger(n) || n < 0 || n > 65535) {\n process.stderr.write(`[autotel-devtools] invalid port: ${value}\\n`)\n process.exit(2)\n }\n return n\n}\n\ninterface RunningReceiver {\n wsServer: DevtoolsServer\n closeAll: () => Promise<void>\n addresses: string[]\n warnings: string[]\n boundPort: number\n uiBase: string\n}\n\nasync function startReceiver(options: CliOptions): Promise<RunningReceiver> {\n const httpServer = createServer()\n const loopbackOnly = hostHeaderIsLoopback(options.host)\n const wsServer = new DevtoolsServer({ server: httpServer, host: options.host, verbose: true })\n attachDevtoolsRoutes(httpServer, wsServer, { loopbackOnly })\n\n const listeners = listenLoopbackDualStack({\n primary: httpServer,\n port: options.port,\n host: options.host,\n attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer, { loopbackOnly }),\n })\n\n const { addresses, warnings, port: boundPort } = await listeners.ready\n\n // Falling forward to a different port means the one we wanted is held by\n // someone else. Classify them: another autotel-devtools (benign) versus a\n // foreign process. The foreign case is the silent footgun — apps keep\n // exporting OTLP to the requested port and reach that process, not us, so\n // this UI stays empty while they see export errors. Say so, with the fix.\n if (boundPort !== options.port) {\n const holder = await probePortHolder(options.host, options.port)\n if (holder === 'autotel-devtools') {\n warnings.push(\n `another autotel-devtools is already running on port ${options.port}; ` +\n `this instance is on ${boundPort}. Use the existing one, or stop it and restart here.`,\n )\n } else {\n warnings.push(\n `port ${options.port} is held by another process that is NOT autotel-devtools. ` +\n `Anything exporting OTLP to :${options.port} is reaching that process, not this devtools. ` +\n `Point your exporter at :${boundPort}, or free :${options.port} and restart.`,\n )\n }\n }\n\n const uiBase = `http://${options.host === 'localhost' ? '127.0.0.1' : options.host}:${boundPort}`\n return {\n wsServer,\n closeAll: () => Promise.all([wsServer.close(), listeners.closeSibling()]).then(() => undefined),\n addresses,\n warnings,\n boundPort,\n uiBase,\n }\n}\n\n// Telemetry env that wires Claude Code (and any OTel-via-env CLI) to this\n// receiver for a *live* local view: HTTP/protobuf (NOT gRPC — this receiver\n// speaks HTTP only), 1s export intervals so events show up promptly, and\n// session.id kept on metrics so metric-only signals join their session.\nfunction buildAgentEnv(uiBase: string, logPrompts: boolean): Record<string, string> {\n const env: Record<string, string> = {\n CLAUDE_CODE_ENABLE_TELEMETRY: '1',\n OTEL_METRICS_EXPORTER: 'otlp',\n OTEL_LOGS_EXPORTER: 'otlp',\n OTEL_EXPORTER_OTLP_PROTOCOL: 'http/protobuf',\n OTEL_EXPORTER_OTLP_ENDPOINT: uiBase,\n OTEL_METRIC_EXPORT_INTERVAL: '1000',\n OTEL_LOGS_EXPORT_INTERVAL: '1000',\n OTEL_METRICS_INCLUDE_SESSION_ID: 'true',\n }\n // Private by default: prompt *text* only flows when explicitly opted in.\n if (logPrompts) env.OTEL_LOG_USER_PROMPTS = '1'\n return env\n}\n\nfunction printEnvBlock(env: Record<string, string>): void {\n for (const [key, value] of Object.entries(env)) {\n process.stdout.write(`export ${key}=${value}\\n`)\n }\n}\n\ninterface ClaudeOptions {\n port: number\n host: string\n printEnv: boolean\n logPrompts: boolean\n claudeArgs: string[]\n}\n\nfunction parseClaudeArgs(argv: string[]): ClaudeOptions {\n const opts: ClaudeOptions = {\n port: parsePort(process.env.AUTOTEL_DEVTOOLS_PORT || '4318'),\n host: process.env.AUTOTEL_DEVTOOLS_HOST || '127.0.0.1',\n printEnv: false,\n logPrompts: false,\n claudeArgs: [],\n }\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]\n const next = argv[i + 1]\n if (arg === '--') { opts.claudeArgs.push(...argv.slice(i + 1)); break }\n if (arg === '--print-env') { opts.printEnv = true; continue }\n if (arg === '--log-prompts') { opts.logPrompts = true; continue }\n if ((arg === '--port' || arg === '-p') && next) { opts.port = parsePort(next); i++; continue }\n if ((arg === '--host' || arg === '-H') && next) { opts.host = next; i++; continue }\n // Anything else is passed straight through to `claude`.\n opts.claudeArgs.push(arg)\n }\n return opts\n}\n\nasync function runClaudeSubcommand(argv: string[]): Promise<void> {\n const opts = parseClaudeArgs(argv)\n\n // --print-env needs no running server: emit the block for the configured\n // port so users can paste it into a shell / managed-settings file.\n if (opts.printEnv) {\n const uiBase = `http://${opts.host === 'localhost' ? '127.0.0.1' : opts.host}:${opts.port}`\n printEnvBlock(buildAgentEnv(uiBase, opts.logPrompts))\n return\n }\n\n const receiver = await startReceiver({ port: opts.port, host: opts.host })\n const env = buildAgentEnv(receiver.uiBase, opts.logPrompts)\n\n process.stdout.write(`\\n autotel-devtools — Claude Code\\n\\n`)\n process.stdout.write(` Receiver: ${receiver.uiBase}\\n`)\n process.stdout.write(` UI: ${receiver.uiBase} → open the \"Agents\" tab\\n`)\n if (!opts.logPrompts) {\n process.stdout.write(` Prompts: private (length only) — add --log-prompts to capture text\\n`)\n }\n for (const w of receiver.warnings) process.stdout.write(` ⚠ ${w}\\n`)\n process.stdout.write(`\\n Launching claude…\\n\\n`)\n\n const child = spawn('claude', opts.claudeArgs, {\n stdio: 'inherit',\n env: { ...process.env, ...env },\n })\n\n const shutdown = () => { receiver.closeAll().then(() => process.exit(0)) }\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n\n child.on('error', (err) => {\n const reason = (err as NodeJS.ErrnoException).code === 'ENOENT'\n ? `'claude' not found on PATH. Install Claude Code, or run the receiver alone with 'npx autotel-devtools' and point claude at ${receiver.uiBase} using --print-env.`\n : err.message\n process.stderr.write(`[autotel-devtools] ${reason}\\n`)\n receiver.closeAll().then(() => process.exit(1))\n })\n child.on('exit', (code) => {\n receiver.closeAll().then(() => process.exit(code ?? 0))\n })\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2)\n\n // Subcommand: `autotel-devtools claude [claude args] [--print-env] [--log-prompts]`\n if (argv[0] === 'claude') {\n await runClaudeSubcommand(argv.slice(1))\n return\n }\n\n const options = parseArgs(argv)\n if (!options) { process.exit(0) }\n\n const receiver = await startReceiver(options)\n const { addresses, warnings, uiBase } = receiver\n const title = options.title || 'autotel-devtools'\n process.stdout.write(`\\n ${title}\\n\\n`)\n process.stdout.write(` Listening: ${addresses.join(' + ')}\\n`)\n process.stdout.write(` UI: ${uiBase} (open in a browser)\\n`)\n process.stdout.write(` OTLP: ${uiBase}/v1/traces\\n`)\n process.stdout.write(` WebSocket: ${uiBase.replace('http', 'ws')}/ws\\n\\n`)\n // The widget bundle auto-mounts on load, so the bare <script> tag is all the\n // user needs — spell that out, plus the two opt-in variations, so nobody\n // wonders whether they also have to add an element by hand.\n process.stdout.write(\n ` Embed in your app — paste into your HTML; a floating panel appears automatically:\\n`,\n )\n process.stdout.write(` <script src=\"${uiBase}/widget.js\"></script>\\n\\n`)\n process.stdout.write(\n ` full screen instead: <script src=\"${uiBase}/widget.js?mode=fullpage\"></script>\\n`,\n )\n process.stdout.write(\n ` choose where it goes: add <autotel-devtools></autotel-devtools> to your markup\\n\\n`,\n )\n process.stdout.write(` Or point any OTLP exporter at this receiver:\\n`)\n process.stdout.write(` OTEL_EXPORTER_OTLP_PROTOCOL=http/json\\n`)\n process.stdout.write(` OTEL_EXPORTER_OTLP_ENDPOINT=${uiBase}\\n\\n`)\n // Self-check: confirm the collector is reachable AND that ingestion works,\n // not just that something is listening. Reading /v1/traces back is the same\n // check a test should make instead of trusting \"the client tried to send\".\n process.stdout.write(` Verify ingestion: curl -s ${uiBase}/v1/traces\\n\\n`)\n for (const w of warnings) {\n process.stdout.write(` ⚠ ${w}\\n`)\n }\n if (warnings.length > 0) process.stdout.write('\\n')\n\n const shutdown = () => {\n receiver.closeAll().then(() => process.exit(0))\n }\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n}\n\nmain().catch((error) => {\n process.stderr.write(`[autotel-devtools] failed to start: ${error instanceof Error ? error.message : String(error)}\\n`)\n process.exit(1)\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n// src/cli.ts\nimport { createServer } from 'node:http';\nimport { spawn } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { DevtoolsServer } from './server/server';\nimport { attachDevtoolsRoutes } from './server/http';\nimport { hostHeaderIsLoopback } from './server/origin-guard';\nimport { listenLoopbackDualStack } from './server/listen';\nimport { probePortHolder } from './server/identity';\n\ninterface CliOptions {\n port: number;\n host: string;\n title?: string;\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n `autotel-devtools - Standalone OTLP receiver with web devtools UI\n\nUsage: autotel-devtools [port] [options]\n autotel-devtools claude [claude args] [--print-env] [--log-prompts]\n\nSubcommands:\n claude Start the receiver AND launch Claude Code wired to it\n (HTTP/protobuf → this receiver, 1s intervals, session id on).\n Open the \"Agents\" tab to watch sessions, tokens, cost,\n tool / MCP / sub-agent / skill usage live.\n --print-env Print the telemetry env block and exit (don't launch)\n --log-prompts Capture prompt text (default: length only / private)\n\nArguments:\n port Port to listen on (shorthand for --port; must be a positive integer)\n\nOptions:\n -p, --port <port> Port to listen on (default: 4318, env: AUTOTEL_DEVTOOLS_PORT).\n If the port is taken, the next free port is used and a warning is shown.\n -H, --host <host> Host to bind to (default: 127.0.0.1, env: AUTOTEL_DEVTOOLS_HOST)\n -t, --title <title> Dashboard title (env: AUTOTEL_DEVTOOLS_TITLE)\n Env limits: AUTOTEL_MAX_TRACE_COUNT, AUTOTEL_MAX_LOG_COUNT, AUTOTEL_MAX_METRIC_COUNT\n -h, --help Show this help message\n -v, --version Show version number\n\nEndpoints:\n GET / Web devtools UI (fullpage)\n GET /widget.js Widget bundle (embed in your app)\n POST /v1/traces Receive OTLP JSON trace data\n GET /v1/traces Read back received traces (verify ingestion in tests)\n DELETE /v1/traces Clear captured telemetry (test reset)\n POST /v1/logs Receive OTLP JSON log data\n POST /v1/metrics Receive OTLP JSON metric data\n WS /ws WebSocket stream for real-time updates\n GET /healthz Health check\n\nExamples:\n npx autotel-devtools\n npx autotel-devtools 4319\n npx autotel-devtools -p 4319 -H 0.0.0.0\n npx autotel-devtools claude # watch Claude Code in the Agents tab\n npx autotel-devtools claude --print-env # just print the env (for MDM / VS Code)\n\nThen point your app:\n OTEL_EXPORTER_OTLP_PROTOCOL=http/json OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 node app.js\n\nView in browser:\n http://localhost:4318\n\nOr embed widget in your app:\n <script src=\"http://localhost:4318/widget.js\"></script>\n` + '\\n',\n );\n}\n\nfunction printVersion(): void {\n try {\n const dir = dirname(fileURLToPath(import.meta.url));\n const pkgPath = resolve(dir, '..', 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));\n process.stdout.write(`${pkg.version}\\n`);\n } catch {\n process.stdout.write('unknown\\n');\n }\n}\n\nfunction parseArgs(argv: string[]): CliOptions | null {\n const options: CliOptions = {\n port: parsePort(process.env.AUTOTEL_DEVTOOLS_PORT || '4318'),\n host: process.env.AUTOTEL_DEVTOOLS_HOST || '127.0.0.1',\n title: process.env.AUTOTEL_DEVTOOLS_TITLE,\n };\n\n // An explicit `--port`/`-p` always wins, regardless of where it sits in\n // argv. A bare numeric positional is shorthand for `--port`, but only when\n // no explicit flag was given — and only the first positional counts, so a\n // stray \"4318\" can't override an earlier positional either.\n let portWasExplicit = false;\n let positionalPortConsumed = false;\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = argv[i + 1];\n if (arg === '--help' || arg === '-h') {\n printHelp();\n return null;\n }\n if (arg === '--version' || arg === '-v') {\n printVersion();\n return null;\n }\n if ((arg === '--port' || arg === '-p') && next) {\n options.port = parsePort(next);\n portWasExplicit = true;\n i++;\n continue;\n }\n if ((arg === '--host' || arg === '-H') && next) {\n options.host = next;\n i++;\n continue;\n }\n if ((arg === '--title' || arg === '-t') && next) {\n options.title = next;\n i++;\n continue;\n }\n if (/^\\d+$/.test(arg) && !positionalPortConsumed) {\n if (!portWasExplicit) options.port = parsePort(arg);\n positionalPortConsumed = true;\n continue;\n }\n }\n\n return options;\n}\n\nfunction parsePort(value: string): number {\n const n = Number(value);\n if (!Number.isInteger(n) || n < 0 || n > 65535) {\n process.stderr.write(`[autotel-devtools] invalid port: ${value}\\n`);\n process.exit(2);\n }\n return n;\n}\n\ninterface RunningReceiver {\n wsServer: DevtoolsServer;\n closeAll: () => Promise<void>;\n addresses: string[];\n warnings: string[];\n boundPort: number;\n uiBase: string;\n}\n\nasync function startReceiver(options: CliOptions): Promise<RunningReceiver> {\n const httpServer = createServer();\n const loopbackOnly = hostHeaderIsLoopback(options.host);\n const wsServer = new DevtoolsServer({\n server: httpServer,\n host: options.host,\n verbose: true,\n });\n attachDevtoolsRoutes(httpServer, wsServer, { loopbackOnly });\n\n const listeners = listenLoopbackDualStack({\n primary: httpServer,\n port: options.port,\n host: options.host,\n attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer, { loopbackOnly }),\n });\n\n const { addresses, warnings, port: boundPort } = await listeners.ready;\n\n // Falling forward to a different port means the one we wanted is held by\n // someone else. Classify them: another autotel-devtools (benign) versus a\n // foreign process. The foreign case is the silent footgun — apps keep\n // exporting OTLP to the requested port and reach that process, not us, so\n // this UI stays empty while they see export errors. Say so, with the fix.\n if (boundPort !== options.port) {\n const holder = await probePortHolder(options.host, options.port);\n if (holder === 'autotel-devtools') {\n warnings.push(\n `another autotel-devtools is already running on port ${options.port}; ` +\n `this instance is on ${boundPort}. Use the existing one, or stop it and restart here.`,\n );\n } else {\n warnings.push(\n `port ${options.port} is held by another process that is NOT autotel-devtools. ` +\n `Anything exporting OTLP to :${options.port} is reaching that process, not this devtools. ` +\n `Point your exporter at :${boundPort}, or free :${options.port} and restart.`,\n );\n }\n }\n\n const uiBase = `http://${options.host === 'localhost' ? '127.0.0.1' : options.host}:${boundPort}`;\n return {\n wsServer,\n closeAll: () =>\n Promise.all([wsServer.close(), listeners.closeSibling()]).then(\n () => undefined,\n ),\n addresses,\n warnings,\n boundPort,\n uiBase,\n };\n}\n\n// Telemetry env that wires Claude Code (and any OTel-via-env CLI) to this\n// receiver for a *live* local view: HTTP/protobuf (NOT gRPC — this receiver\n// speaks HTTP only), 1s export intervals so events show up promptly, and\n// session.id kept on metrics so metric-only signals join their session.\nfunction buildAgentEnv(\n uiBase: string,\n logPrompts: boolean,\n): Record<string, string> {\n const env: Record<string, string> = {\n CLAUDE_CODE_ENABLE_TELEMETRY: '1',\n OTEL_METRICS_EXPORTER: 'otlp',\n OTEL_LOGS_EXPORTER: 'otlp',\n OTEL_EXPORTER_OTLP_PROTOCOL: 'http/protobuf',\n OTEL_EXPORTER_OTLP_ENDPOINT: uiBase,\n OTEL_METRIC_EXPORT_INTERVAL: '1000',\n OTEL_LOGS_EXPORT_INTERVAL: '1000',\n OTEL_METRICS_INCLUDE_SESSION_ID: 'true',\n };\n // Private by default: prompt *text* only flows when explicitly opted in.\n if (logPrompts) env.OTEL_LOG_USER_PROMPTS = '1';\n return env;\n}\n\nfunction printEnvBlock(env: Record<string, string>): void {\n for (const [key, value] of Object.entries(env)) {\n process.stdout.write(`export ${key}=${value}\\n`);\n }\n}\n\ninterface ClaudeOptions {\n port: number;\n host: string;\n printEnv: boolean;\n logPrompts: boolean;\n claudeArgs: string[];\n}\n\nfunction parseClaudeArgs(argv: string[]): ClaudeOptions {\n const opts: ClaudeOptions = {\n port: parsePort(process.env.AUTOTEL_DEVTOOLS_PORT || '4318'),\n host: process.env.AUTOTEL_DEVTOOLS_HOST || '127.0.0.1',\n printEnv: false,\n logPrompts: false,\n claudeArgs: [],\n };\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const next = argv[i + 1];\n if (arg === '--') {\n opts.claudeArgs.push(...argv.slice(i + 1));\n break;\n }\n if (arg === '--print-env') {\n opts.printEnv = true;\n continue;\n }\n if (arg === '--log-prompts') {\n opts.logPrompts = true;\n continue;\n }\n if ((arg === '--port' || arg === '-p') && next) {\n opts.port = parsePort(next);\n i++;\n continue;\n }\n if ((arg === '--host' || arg === '-H') && next) {\n opts.host = next;\n i++;\n continue;\n }\n // Anything else is passed straight through to `claude`.\n opts.claudeArgs.push(arg);\n }\n return opts;\n}\n\nasync function runClaudeSubcommand(argv: string[]): Promise<void> {\n const opts = parseClaudeArgs(argv);\n\n // --print-env needs no running server: emit the block for the configured\n // port so users can paste it into a shell / managed-settings file.\n if (opts.printEnv) {\n const uiBase = `http://${opts.host === 'localhost' ? '127.0.0.1' : opts.host}:${opts.port}`;\n printEnvBlock(buildAgentEnv(uiBase, opts.logPrompts));\n return;\n }\n\n const receiver = await startReceiver({ port: opts.port, host: opts.host });\n const env = buildAgentEnv(receiver.uiBase, opts.logPrompts);\n\n process.stdout.write(`\\n autotel-devtools — Claude Code\\n\\n`);\n process.stdout.write(` Receiver: ${receiver.uiBase}\\n`);\n process.stdout.write(\n ` UI: ${receiver.uiBase} → open the \"Agents\" tab\\n`,\n );\n if (!opts.logPrompts) {\n process.stdout.write(\n ` Prompts: private (length only) — add --log-prompts to capture text\\n`,\n );\n }\n for (const w of receiver.warnings) process.stdout.write(` ⚠ ${w}\\n`);\n process.stdout.write(`\\n Launching claude…\\n\\n`);\n\n const child = spawn('claude', opts.claudeArgs, {\n stdio: 'inherit',\n env: { ...process.env, ...env },\n });\n\n const shutdown = () => {\n receiver.closeAll().then(() => process.exit(0));\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n\n child.on('error', (err) => {\n const reason =\n (err as NodeJS.ErrnoException).code === 'ENOENT'\n ? `'claude' not found on PATH. Install Claude Code, or run the receiver alone with 'npx autotel-devtools' and point claude at ${receiver.uiBase} using --print-env.`\n : err.message;\n process.stderr.write(`[autotel-devtools] ${reason}\\n`);\n receiver.closeAll().then(() => process.exit(1));\n });\n child.on('exit', (code) => {\n receiver.closeAll().then(() => process.exit(code ?? 0));\n });\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n\n // Subcommand: `autotel-devtools claude [claude args] [--print-env] [--log-prompts]`\n if (argv[0] === 'claude') {\n await runClaudeSubcommand(argv.slice(1));\n return;\n }\n\n const options = parseArgs(argv);\n if (!options) {\n process.exit(0);\n }\n\n const receiver = await startReceiver(options);\n const { addresses, warnings, uiBase } = receiver;\n const title = options.title || 'autotel-devtools';\n process.stdout.write(`\\n ${title}\\n\\n`);\n process.stdout.write(` Listening: ${addresses.join(' + ')}\\n`);\n process.stdout.write(` UI: ${uiBase} (open in a browser)\\n`);\n process.stdout.write(` OTLP: ${uiBase}/v1/traces\\n`);\n process.stdout.write(` WebSocket: ${uiBase.replace('http', 'ws')}/ws\\n\\n`);\n // The widget bundle auto-mounts on load, so the bare <script> tag is all the\n // user needs — spell that out, plus the two opt-in variations, so nobody\n // wonders whether they also have to add an element by hand.\n process.stdout.write(\n ` Embed in your app — paste into your HTML; a floating panel appears automatically:\\n`,\n );\n process.stdout.write(` <script src=\"${uiBase}/widget.js\"></script>\\n\\n`);\n process.stdout.write(\n ` full screen instead: <script src=\"${uiBase}/widget.js?mode=fullpage\"></script>\\n`,\n );\n process.stdout.write(\n ` choose where it goes: add <autotel-devtools></autotel-devtools> to your markup\\n\\n`,\n );\n process.stdout.write(` Or point any OTLP exporter at this receiver:\\n`);\n process.stdout.write(` OTEL_EXPORTER_OTLP_PROTOCOL=http/json\\n`);\n process.stdout.write(` OTEL_EXPORTER_OTLP_ENDPOINT=${uiBase}\\n\\n`);\n // Self-check: confirm the collector is reachable AND that ingestion works,\n // not just that something is listening. Reading /v1/traces back is the same\n // check a test should make instead of trusting \"the client tried to send\".\n process.stdout.write(` Verify ingestion: curl -s ${uiBase}/v1/traces\\n\\n`);\n for (const w of warnings) {\n process.stdout.write(` ⚠ ${w}\\n`);\n }\n if (warnings.length > 0) process.stdout.write('\\n');\n\n const shutdown = () => {\n receiver.closeAll().then(() => process.exit(0));\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n\nmain().catch((error) => {\n process.stderr.write(\n `[autotel-devtools] failed to start: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n process.exit(1);\n});\n"],"mappings":"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"}
@@ -1,5 +1,4 @@
1
- import { a as ErrorGroup, o as ErrorOccurrence, u as TraceData } from "./exporter-yqXiHw1Q.js";
2
-
1
+ import { a as ErrorGroup, o as ErrorOccurrence, u as TraceData } from "./exporter-BLqTs00O.js";
3
2
  //#region src/server/error-aggregator.d.ts
4
3
  interface ErrorAggregatorOptions {
5
4
  /**
@@ -117,4 +116,4 @@ declare class ErrorAggregator {
117
116
  }
118
117
  //#endregion
119
118
  export { ErrorAggregator as t };
120
- //# sourceMappingURL=error-aggregator-C8X-RQRi.d.ts.map
119
+ //# sourceMappingURL=error-aggregator-BrgYNNoC.d.ts.map
@@ -1,5 +1,4 @@
1
- import { a as ErrorGroup, o as ErrorOccurrence, u as TraceData } from "./exporter-kXTGDFoH.cjs";
2
-
1
+ import { a as ErrorGroup, o as ErrorOccurrence, u as TraceData } from "./exporter-BSIS3Qgy.cjs";
3
2
  //#region src/server/error-aggregator.d.ts
4
3
  interface ErrorAggregatorOptions {
5
4
  /**
@@ -117,4 +116,4 @@ declare class ErrorAggregator {
117
116
  }
118
117
  //#endregion
119
118
  export { ErrorAggregator as t };
120
- //# sourceMappingURL=error-aggregator-7Q2H7htn.d.cts.map
119
+ //# sourceMappingURL=error-aggregator-bZ-prq97.d.cts.map
@@ -2,7 +2,6 @@ import { Server } from "node:http";
2
2
  import { AgentRawEvent, AgentSession, OtelMetricRecord } from "autotel-agents";
3
3
  import { ExportResult } from "@opentelemetry/core";
4
4
  import { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
5
-
6
5
  //#region src/server/types.d.ts
7
6
  interface SpanData {
8
7
  traceId: string;
@@ -189,4 +188,4 @@ declare class DevtoolsSpanExporter implements SpanExporter {
189
188
  }
190
189
  //#endregion
191
190
  export { ErrorGroup as a, MetricData as c, DevtoolsData as i, SpanData as l, DevtoolsServer as n, ErrorOccurrence as o, DevtoolsServerOptions as r, LogData as s, DevtoolsSpanExporter as t, TraceData as u };
192
- //# sourceMappingURL=exporter-yqXiHw1Q.d.ts.map
191
+ //# sourceMappingURL=exporter-BLqTs00O.d.ts.map
@@ -2,7 +2,6 @@ import { Server } from "node:http";
2
2
  import { AgentRawEvent, AgentSession, OtelMetricRecord } from "autotel-agents";
3
3
  import { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
4
4
  import { ExportResult } from "@opentelemetry/core";
5
-
6
5
  //#region src/server/types.d.ts
7
6
  interface SpanData {
8
7
  traceId: string;
@@ -189,4 +188,4 @@ declare class DevtoolsSpanExporter implements SpanExporter {
189
188
  }
190
189
  //#endregion
191
190
  export { ErrorGroup as a, MetricData as c, DevtoolsData as i, SpanData as l, DevtoolsServer as n, ErrorOccurrence as o, DevtoolsServerOptions as r, LogData as s, DevtoolsSpanExporter as t, TraceData as u };
192
- //# sourceMappingURL=exporter-kXTGDFoH.d.cts.map
191
+ //# sourceMappingURL=exporter-BSIS3Qgy.d.cts.map