Skip to content

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.

Terminal window
npm install autotel-devtools

The package ships a CLI binary, a Node.js library, and a browser widget bundle.

Run the receiver, then point any OTLP exporter at it:

Terminal window
npx autotel-devtools
Terminal window
OTEL_EXPORTER_OTLP_PROTOCOL=http/json \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
node app.js

Open 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

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.

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.

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.
VariableDefaultDescription
AUTOTEL_DEVTOOLS_PORT4318Server port
AUTOTEL_DEVTOOLS_HOST127.0.0.1Bind host
AUTOTEL_DEVTOOLS_TITLEunsetDashboard title
AUTOTEL_MAX_TRACE_COUNT100Max traces kept in memory
AUTOTEL_MAX_LOG_COUNT100Max log records kept
AUTOTEL_MAX_METRIC_COUNT100Max metric data points kept

Bump the limits when you need a longer scrollback during a development session.

The standalone server exposes:

PathMethodPurpose
/v1/tracesPOSTOTLP/HTTP traces ingest (JSON or protobuf)
/v1/tracesGETRead back received traces
/v1/tracesDELETEClear all captured telemetry
/v1/logsPOSTOTLP/HTTP logs ingest (JSON or protobuf)
/v1/metricsPOSTOTLP/HTTP metrics ingest (JSON or protobuf)
/GETFull-page dashboard UI
/widget.jsGETEmbeddable widget bundle
/healthzGETHealth + identity ({ ok, service, version, clients })
/wsWSWebSocket 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.

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.

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.

Apache-2.0.