Devtools
autotel-devtools is a local OpenTelemetry receiver with a Svelte 5 web UI.
Think TanStack Devtools for OTLP. Run it as a CLI to inspect traces, logs,
metrics, and errors in the browser, or embed the widget directly in your app
for in-page diagnostics.
Any OTLP-compatible exporter sends into it, so autotel and vanilla OpenTelemetry both work.
Installation
Section titled “Installation”npm install autotel-devtoolsThe package ships a CLI binary, a Node.js library, and a browser widget bundle.
Standalone Mode
Section titled “Standalone Mode”Run the receiver, then point any OTLP exporter at it:
npx autotel-devtoolsOTEL_EXPORTER_OTLP_PROTOCOL=http/json \OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \node app.jsOpen http://localhost:4318 for the dashboard. It
includes a traces waterfall, flame graph, log filtering by severity and
resource, error aggregation, service map, resources view, a GenAI view for
LLM and agent runs (see below), and a Security lens that
surfaces spans carrying the security.* schema (see
Security Observability). Search debounces at
300ms.
CLI options:
-p, --port <port>— Port (default:4318). If the port is taken, falls forward to the next free port and prints a warning. When the original port is held by a foreign process (not another autotel-devtools), the warning explains that any OTLP exporter still pointed at the busy port is reaching that process — point the exporter at the bound port, or free the original and restart.-H, --host <host>— Host (default:127.0.0.1)-t, --title <title>— Dashboard title
Embedded Widget
Section titled “Embedded Widget”Add the widget to any web page. It mounts as a custom element with Shadow DOM isolation, so its CSS never leaks into your app:
<script src="http://localhost:4318/widget.js"></script><autotel-devtools></autotel-devtools>The widget connects to the receiver over WebSocket, replays history on connect,
and persists position via localStorage.
Programmatic API
Section titled “Programmatic API”For tighter integration, wire the exporter directly into init() so spans flow
to devtools without going over HTTP:
import { init, trace } from 'autotel';import { createDevtools } from 'autotel-devtools';
const { exporter, close } = createDevtools({ port: 4318, verbose: true,});
init({ service: 'my-app', endpoint: 'http://localhost:4318', spanProcessors: [exporter],});
const checkout = trace((ctx) => async (req, res) => { // spans appear in the devtools UI in real time});Call close() on shutdown to release the port and WebSocket clients.
GenAI runs
Section titled “GenAI runs”When your app emits OpenTelemetry GenAI spans — for example spans with
gen_ai.provider.name and gen_ai.operation.name — the GenAI tab turns
them into a readable view of each LLM and agent run. It works with anything that
follows the spec, including the Vercel AI SDK via
registerTelemetry(autotelTelemetry()) or subscribeAiTelemetry(),
Pydantic AI + Logfire, OpenAI Agents, Anthropic, Google GenAI, LangChain and
OpenLLMetry.
Per span you get the model, provider, parameters, token usage (with the cached and reasoning share called out), estimated cost, and the full conversation transcript — messages, tool calls and their results.
On top of the per-span detail, three things make a multi-span run readable at a glance:
- Run summary strip — a KPI row above the detail showing the run’s total
cost, input→output tokens, reasoning tokens, model calls, tool executions,
sub-agents, duration and errors. Cost is summed only from priced calls and
marked with a trailing
+when some calls are unpriced (a lower bound, never a fabricated total). Wrapper/aggregate spans and tool calls replayed across turns are de-duplicated, so counts and totals don’t double-count. - Explain run — a guided, narrated walkthrough for demos. It steps through the run in order with plain-language narration of each step (“the model decides what to do”, “a tool is real code the agent ran”, “the model writes the answer”). Auto-play, step with the arrow keys / Space, Esc to exit; clicking a span jumps the tour to that step.
- Trace — decomposes the selected run into a depth-indented tree of what happened inside it: each model call broken into its reasoning, the tools it called (with arguments and results) and the text it wrote, with nested sub-agents underneath. A Timeline view shows the same run as time-aligned lanes.
Configuration
Section titled “Configuration”| Variable | Default | Description |
|---|---|---|
AUTOTEL_DEVTOOLS_PORT | 4318 | Server port |
AUTOTEL_DEVTOOLS_HOST | 127.0.0.1 | Bind host |
AUTOTEL_DEVTOOLS_TITLE | unset | Dashboard title |
AUTOTEL_MAX_TRACE_COUNT | 100 | Max traces kept in memory |
AUTOTEL_MAX_LOG_COUNT | 100 | Max log records kept |
AUTOTEL_MAX_METRIC_COUNT | 100 | Max metric data points kept |
Bump the limits when you need a longer scrollback during a development session.
Endpoints
Section titled “Endpoints”The standalone server exposes:
| Path | Method | Purpose |
|---|---|---|
/v1/traces | POST | OTLP/HTTP traces ingest (JSON or protobuf) |
/v1/traces | GET | Read back received traces |
/v1/traces | DELETE | Clear all captured telemetry |
/v1/logs | POST | OTLP/HTTP logs ingest (JSON or protobuf) |
/v1/metrics | POST | OTLP/HTTP metrics ingest (JSON or protobuf) |
/ | GET | Full-page dashboard UI |
/widget.js | GET | Embeddable widget bundle |
/healthz | GET | Health + identity ({ ok, service, version, clients }) |
/ws | WS | WebSocket stream (live updates + replay) |
Every response carries an x-autotel-devtools: <version> header. Use it to
confirm you are talking to autotel-devtools rather than another OTLP collector
that happens to share the port.
Read surface is origin-guarded. Ingestion (POST), /widget.js and
/healthz are open to any origin so apps on arbitrary dev origins can send
telemetry and load the widget. The read/clear endpoints (GET/DELETE /v1/traces) and the /ws stream reject cross-origin browser requests (a
non-loopback Origin) with 403, so a page you happen to be visiting can’t read
your captured prompts and responses. When bound to a loopback host (the default),
a non-loopback Host is rejected too (DNS-rebinding defense); --host 0.0.0.0
opts into network exposure and applies only the Origin check. Server-side reads
with no Origin (curl, Node fetch in tests) are unaffected.
Identity Detection
Section titled “Identity Detection”When an IDE or other tool is already listening on port 4318, the CLI warns you and falls forward to the next free port. To detect what owns a port programmatically:
import { probePortHolder } from 'autotel-devtools/server';
// Returns 'autotel-devtools' | 'foreign' | 'none'const holder = await probePortHolder('127.0.0.1', 4318);probePortHolder checks for the x-autotel-devtools header and the
/healthz service field. Use it in test setup scripts to verify the
receiver is ready, or in tooling that needs to decide whether to start a new
instance.
Devtools vs. Terminal Viewer
Section titled “Devtools vs. Terminal Viewer”Both ship as local OTLP receivers. Pick by surface:
- Devtools. Browser UI, embeddable widget, service map, flame graph, longer scrollback. Best when you want a real dashboard alongside the running app.
- Terminal viewer. Ink-powered TUI, no browser needed. Best when you live in the terminal or are running over SSH.
To skip the standalone process entirely, enable devtools directly from
init():
init({ service: 'my-app', devtools: true });This wires the exporter automatically. No separate CLI process required.
License
Section titled “License”Apache-2.0.