- Install deps:
bun install - Run the TUI:
bun run devorbun run start(auto-ensures a managed OTLP daemon is running in the background so traces ingest while the TUI is up) - Start the background daemon only:
bun run daemon(same asmotel start) - Stop the managed daemon:
bun run stop - Daemon status JSON:
bun run status - Restart daemon + relaunch TUI:
bun run restart - Run the local server in the foreground (no daemon, no TUI):
bun run server - Run tests:
bun run test - Query services via CLI:
bun run cli services - Query traces via CLI:
bun run cli traces <service> [limit] - Query a span via CLI:
bun run cli span <span-id> - Query spans for one trace:
bun run cli trace-spans <trace-id> - Search spans via CLI:
bun run cli search-spans [service] [operation] [parent=<operation>] [attr.key=value ...] - Search traces via CLI:
bun run cli search-traces <service> [operation] [attr.key=value ...] - Query trace stats via CLI:
bun run cli trace-stats <groupBy> <agg> [service] [attr.key=value ...] - Query logs via CLI:
bun run cli logs <service> - Search logs via CLI:
bun run cli search-logs <service> [body] [attr.key=value ...] - Query log stats via CLI:
bun run cli log-stats <groupBy> [service] [attr.key=value ...] - Query logs for one trace:
bun run cli trace-logs <trace-id> - Query logs for one span:
bun run cli span-logs <span-id> - Query facets via CLI:
bun run cli facets <traces|logs> <field> - Print Effect setup instructions:
bun run instructions - Build the web UI:
bun run web:build - Dev the web UI (with hot reload):
bun run web:dev - Typecheck:
bun run typecheck - Effect LSP diagnostics over the whole project:
bunx effect-language-service diagnostics --project tsconfig.json --format text - Effect LSP interactive setup wizard:
bunx effect-language-service setup
- npm package:
@kitlangton/motel - Current published npm
latest:0.2.4(npm view @kitlangton/motel dist-tags --json) - Tags are versioned as
vX.Y.Z(git tag --sort=-version:refnameshowsv0.2.4,v0.2.3, ...) - Publishing is handled by GitHub Actions in
.github/workflows/publish.yml, not by local manualnpm publish - The publish workflow triggers on
git pushof tags matchingv*or via manualworkflow_dispatch - The workflow runs
bun install --frozen-lockfile,bun run typecheck,bun run test, thennpm publish --provenance npm publishrunsprepublishOnly, which builds the web UI viabun run web:build- Before tagging a release, make sure the committed
package.jsonversion matches the intended git tag exactly - Preferred release flow: update
package.jsonversion, commit the release changes, create tagv<package.json version>, push the commit and tag, then verify the GitHub Actions publish and npm dist-tags - Do not create or push release tags from a dirty worktree with unrelated uncommitted changes; ask before including unrelated edits in a release
The repo is wired up with @effect/language-service as a tsconfig.json plugins entry. Editors that pick up the TypeScript workspace plugin (Zed, VSCode, Cursor, NVim via vtsls) will surface Effect-specific diagnostics, quick fixes, and refactors inline. In Zed this requires selecting the workspace TypeScript version — it does so automatically when node_modules/typescript is present.
- The built-in verification step is
bun run typecheck. - For runtime verification, start the TUI or server once, then query
http://127.0.0.1:27686/api/services,http://127.0.0.1:27686/api/spans/<span-id>,http://127.0.0.1:27686/openapi.json, andbun run cli logs motel-otel-tui. - For span-centric debugging, use
http://127.0.0.1:27686/api/spans/search?...,http://127.0.0.1:27686/api/spans/<span-id>/logs, andhttp://127.0.0.1:27686/api/traces/<trace-id>/spans.
- List and search endpoints return a
metaobject withlimit,lookback,returned,truncated, andnextCursor. /api/tracesand/api/traces/searchreturn summaries by default. Use/api/traces/<trace-id>for the full trace tree./api/logsand/api/logs/searchsupportseverity(e.g.?severity=ERROR), case-insensitive body search, andattrContains.<key>=<substring>for substring search inside attribute values./api/spans/searchsupportstraceIdto scope to one trace,attr.<key>=<value>for exact match, andattrContains.<key>=<substring>for case-insensitive substring search inside attribute values./api/ai/callssearches AI SDK calls (streamText, generateText, etc.) with first-class filters formodel,provider,sessionId,functionId,operation,status,text(cross-field search), and returns compact summaries with previews and token usage./api/ai/calls/<span-id>returns the full detail of a single AI call including complete prompt messages, response text, tool calls, timing, and correlated logs./api/ai/statsaggregates AI call statistics byprovider,model,functionId,sessionId, orstatuswith aggregations:count,avg_duration,p95_duration,total_input_tokens,total_output_tokens./api/facets?type=traces&field=attribute_keys&service=<svc>lists span-attribute keys for a service, ranked by discriminating power (keys with many distinct values first). Pair withfield=attribute_values&key=<key>to list values for a specific key. Used by the TUIfattribute filter./api/docslists available documentation;/api/docs/debugand/api/docs/effectreturn the full skill content.
src/index.tsxcreates the OpenTUI renderer and mounts the app.src/App.tsxcomposes the top-level screen: header, footer, and the drill-in workspace. Heavy logic is delegated to the modules below.src/ui/app/useTraceScreenData.tsowns the atoms and data-loading effects for traces, logs, services, and cache warming.src/ui/app/useAppLayout.tsis the single source for layout math (pane widths, body lines, viewport rows, drill-in level).src/ui/app/TraceWorkspace.tsxrenders the drill-in state machine: L0 (trace list), L1 (waterfall), L2 (span detail), plus the service logs side mode. When drilled in the list is hidden entirely and the detail pane(s) expand to fill.src/ui/app/TraceListPane.tsxhosts the trace list: header + optional filter bar + virtual-windowed body (no opentui scrollbox — that had a race with Yoga layout timing).src/ui/TraceList.tsxexportsTraceListHeader(theTRACES 100 · ...strip) andTraceListBody(virtual-windowed rows with mouse-wheel scrolling). The body owns its own scrollOffset state, preserves the selected row's visual position across auto-refresh shifts, and snaps the window to follow selection that moves off-screen.src/ui/Waterfall.tsxrenders the waterfall timeline with a virtualised scroll viewport;src/ui/waterfallNav.tsis the pure collapse/expand/walk resolver (unit-tested).src/ui/TraceDetailsPane.tsxis the L1 body: header + waterfall.src/ui/SpanDetailPane.tsxis the L2 body; renderssrc/ui/SpanDetail.tsxbelow a header that owns the span identity.src/ui/useKeyboardNav.tscentralises the keyboard handlers and cross-pane navigation state transitions.src/cli.tsexposes trace and log queries through a small local CLI wrapper.src/runtime.tswires the Effect beta runtime and OTEL trace + log exporters.src/localServer.tsstarts the local Bun OTLP/query server.src/httpApi.tsdefines the typed Effect HttpApi surface and OpenAPI spec for the local server.src/httpListPolicy.tsowns pure HTTP list/search parameter decoding, bounds, cursors, and pagination metadata shaping.src/server.tsruns the local server without the TUI.src/instructions.tscontains the copied setup instructions for other Effect apps.src/services/TelemetryStore.tspersists traces and logs in SQLite and exposes indexed queries through writer and read-only service identifiers.src/services/TelemetryQuery.tsproxies read-only store calls tosrc/services/telemetryQueryWorker.ts, keeping synchronous Bun SQLite queries off the HTTP event loop.src/config.tsis the source of truth for ports and env-driven OTEL settings.web/is a Vite + React SPA for the browser-based UI (Tailwind CSS,@effect/atom-react,AtomHttpApi).web/src/api.tscreates the typedAtomHttpApi.Serviceclient fromsrc/httpApi.ts.web/src/pages/contains route pages: TracesPage, TraceDetailPage, LogsPage, AiCallsPage.web/src/components/contains Waterfall and SpanDetail components.- The server in
src/localServer.tsservesweb/dist/as static files with SPA fallback for non-API routes.
bun run testruns the suite. Three kinds of tests live in the repo:src/telemetry.test.tsexercises the SQLite TelemetryStore with OTLP payloads end-to-end.src/ui/waterfallNav.test.tsunit-tests the pure collapse/expand resolver (no UI).src/ui/*.repro.test.tsdrive the real TUI undertuistoryto reproduce regressions, using deterministic traces seeded into SQLite by child processes. They become no-op passes whentuistoryis absent.
- Inspect the target repo’s existing Effect runtime and observability wiring before adding anything new.
- Prefer the repo’s existing Effect-native observability APIs if available.
- If
effect/unstable/observabilityis already the best fit, prefer it over adding@effect/opentelemetry. - Only add new OpenTelemetry SDK packages when the repo already uses them or they are clearly required.
- Merge telemetry into the main runtime once, not per-feature.
- Prefer structured log annotations so fields like
sessionID,modelID,providerID, andtoolare queryable.
- Local API / UI base:
http://127.0.0.1:27686 - OTLP HTTP traces:
http://127.0.0.1:27686/v1/traces - OTLP HTTP logs:
http://127.0.0.1:27686/v1/logs - Health:
http://127.0.0.1:27686/api/health
MOTEL_OTEL_ENABLED: defaults tofalse(set totrueto emit self-traces for debugging motel itself)MOTEL_OTEL_SERVICE_NAME: defaults tomotel-otel-tuiMOTEL_OTEL_BASE_URL: defaults tohttp://127.0.0.1:27686MOTEL_OTEL_HOST: defaults to127.0.0.1MOTEL_OTEL_PORT: defaults to27686MOTEL_OTEL_EXPORTER_URL: defaults tohttp://127.0.0.1:27686/v1/tracesMOTEL_OTEL_LOGS_EXPORTER_URL: defaults tohttp://127.0.0.1:27686/v1/logsMOTEL_OTEL_QUERY_URL: defaults tohttp://127.0.0.1:27686MOTEL_OTEL_DB_PATH: defaults to${XDG_STATE_HOME:-~/.local/state}/motel/telemetry.sqlite(one shared DB per machine; daemon log + lock + instance registry live in the same directory)MOTEL_RUNTIME_DIR: overrides the daemon log, lock, and instance-registry directory (primarily for isolated tests and custom managed instances)MOTEL_OTEL_TRACE_LOOKBACK_MINUTES: defaults to1440(24h)MOTEL_OTEL_TRACE_LIMIT: defaults to100MOTEL_OTEL_LOG_LIMIT: defaults to80MOTEL_OTEL_RETENTION_HOURS: defaults to168(7d)MOTEL_OTEL_MAX_DB_SIZE_MB: defaults to1024(size-based retention cap)MOTEL_OTEL_RETENTION_TRACE_BATCH: defaults to100completed traces per cleanup passMOTEL_OTEL_RETENTION_LOG_BATCH: defaults to5000logs per cleanup passMOTEL_OTEL_RETENTION_INTERVAL_SECONDS: defaults to10
?: toggle shortcut helpj/korup/down: move trace or span selectionh/left: collapse current span, or step to parentl/right: expand current span, or step to first childctrl-n/ctrl-p: switch traces while staying in the details areagg/home: jump to the first trace or spanG/end: jump to the last trace or spanctrl-u/pageup: page upctrl-d/pagedown: page downenter: drill in one level (list → waterfall → span detail)esc: back out one leveltab: toggle service logs view[/]: switch servicess: cycle sort mode (recent → slowest → errors)t: cycle theme (motel-default → tokyo-night → catppuccin)/: enter filter mode.- In the trace list (L0) the input matches against the root operation name. Composable modifiers:
:error— restrict to traces with at least one failed span (client-side):ai <query>— FTS5-backed search against LLM prompt/response/tool content (AI_FTS_KEYS) across every span in the trace. Tokens are prefix-matched and implicitly AND'd. Debounced 250ms.- Modifiers compose:
/ :ai rate limit :error
- In the waterfall (L1/L2) the input runs a client-side substring match against each span's operation name and tag values. Non-matching spans are dimmed; the filter bar shows the live match count.
entercommits (dim persists while you navigate);escclears.
- In the trace list (L0) the input matches against the root operation name. Composable modifiers:
f: open attribute filter picker (browse span-attribute keys → values for the current service;backspacewalks back to keys;escin the trace list clears the active filter)a: pause or resume auto-refreshr: refresh nowc: copy setup instructions for another Effect appo: open selected trace in the browsery: copy selected trace or span idq: quit