@kitlangton/motel 0.1.0 → 0.1.2

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.
package/AGENTS.md CHANGED
@@ -2,8 +2,14 @@
2
2
 
3
3
  ## Commands
4
4
  - Install deps: `bun install`
5
- - Run the TUI: `bun run dev` or `bun run start`
6
- - Run the local server only: `bun run server`
5
+ - Run the TUI: `bun run dev` or `bun run start` (auto-ensures a managed
6
+ OTLP daemon is running in the background so traces ingest while the TUI
7
+ is up)
8
+ - Start the background daemon only: `bun run daemon` (same as `motel start`)
9
+ - Stop the managed daemon: `bun run stop`
10
+ - Daemon status JSON: `bun run status`
11
+ - Restart daemon + relaunch TUI: `bun run restart`
12
+ - Run the local server in the foreground (no daemon, no TUI): `bun run server`
7
13
  - Run tests: `bun run test`
8
14
  - Query services via CLI: `bun run cli services`
9
15
  - Query traces via CLI: `bun run cli traces <service> [limit]`
@@ -36,6 +42,7 @@
36
42
  - `/api/ai/calls` searches AI SDK calls (streamText, generateText, etc.) with first-class filters for `model`, `provider`, `sessionId`, `functionId`, `operation`, `status`, `text` (cross-field search), and returns compact summaries with previews and token usage.
37
43
  - `/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.
38
44
  - `/api/ai/stats` aggregates AI call statistics by `provider`, `model`, `functionId`, `sessionId`, or `status` with aggregations: `count`, `avg_duration`, `p95_duration`, `total_input_tokens`, `total_output_tokens`.
45
+ - `/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 with `field=attribute_values&key=<key>` to list values for a specific key. Used by the TUI `f` attribute filter.
39
46
  - `/api/docs` lists available documentation; `/api/docs/debug` and `/api/docs/effect` return the full skill content.
40
47
 
41
48
  ## Architecture
@@ -48,11 +55,16 @@
48
55
  (pane widths, body lines, viewport rows, drill-in level).
49
56
  - `src/ui/app/TraceWorkspace.tsx` renders the drill-in state machine:
50
57
  L0 (trace list), L1 (waterfall), L2 (span detail), plus the service
51
- logs side mode.
52
- - `src/ui/app/TraceListPane.tsx` wraps `TraceList` in a scrollbox with
53
- the filter bar and list header.
54
- - `src/ui/TraceList.tsx` renders trace rows (trace id, duration, span
55
- count, relative age).
58
+ logs side mode. When drilled in the list is hidden entirely and the
59
+ detail pane(s) expand to fill.
60
+ - `src/ui/app/TraceListPane.tsx` hosts the trace list: header + optional
61
+ filter bar + virtual-windowed body (no opentui scrollbox — that had a
62
+ race with Yoga layout timing).
63
+ - `src/ui/TraceList.tsx` exports `TraceListHeader` (the `TRACES 100 · ...`
64
+ strip) and `TraceListBody` (virtual-windowed rows with mouse-wheel
65
+ scrolling). The body owns its own scrollOffset state, preserves the
66
+ selected row's visual position across auto-refresh shifts, and snaps
67
+ the window to follow selection that moves off-screen.
56
68
  - `src/ui/Waterfall.tsx` renders the waterfall timeline with a
57
69
  virtualised scroll viewport; `src/ui/waterfallNav.ts` is the pure
58
70
  collapse/expand/walk resolver (unit-tested).
@@ -116,7 +128,7 @@
116
128
  - `MOTEL_OTEL_TRACE_LIMIT`: defaults to `100`
117
129
  - `MOTEL_OTEL_LOG_LIMIT`: defaults to `80`
118
130
  - `MOTEL_OTEL_RETENTION_HOURS`: defaults to `168` (7d)
119
- - `MOTEL_OTEL_MAX_DB_SIZE_MB`: defaults to `256` (size-based retention cap)
131
+ - `MOTEL_OTEL_MAX_DB_SIZE_MB`: defaults to `1024` (size-based retention cap)
120
132
 
121
133
  ## TUI Keys
122
134
  - `?`: toggle shortcut help
@@ -133,7 +145,9 @@
133
145
  - `tab`: toggle service logs view
134
146
  - `[` / `]`: switch services
135
147
  - `s`: cycle sort mode (recent → slowest → errors)
148
+ - `t`: cycle theme (motel-default → tokyo-night → catppuccin)
136
149
  - `/`: enter filter mode (type to match on root operation name; `:error` restricts to failing traces)
150
+ - `f`: open attribute filter picker (browse span-attribute keys → values for the current service; `backspace` walks back to keys; `esc` in the trace list clears the active filter)
137
151
  - `a`: pause or resume auto-refresh
138
152
  - `r`: refresh now
139
153
  - `c`: copy setup instructions for another Effect app
package/README.md CHANGED
@@ -1,199 +1,106 @@
1
1
  # motel
2
2
 
3
- A local OpenTelemetry ingest + TUI viewer for development, backed by SQLite.
4
- Point your app's OTLP/HTTP exporters at the local motel server and browse
5
- traces, spans, and logs from a terminal or the built-in web UI.
3
+ A local OpenTelemetry ingest + TUI viewer for development, backed by
4
+ SQLite. Point your app's OTLP/HTTP exporters at the local motel server
5
+ and debug with real runtime evidence — from a terminal, the built-in web
6
+ UI, or directly from an AI coding agent.
6
7
 
7
- ## Install
8
+ ## For agents: install the motel-debug skill
8
9
 
9
- ```bash
10
- bun add -g @kitlangton/motel
11
- motel
12
- ```
13
-
14
- (or `bunx @kitlangton/motel` for a one-off run without installing.)
15
-
16
- ## Requirements
17
-
18
- - [Bun](https://bun.sh/) — v1.1 or newer
19
-
20
- ## Install the motel-debug skill
21
-
22
- `motel` ships a companion skill that teaches agents (Claude Code, OpenCode,
23
- Cursor, Codex, and 40+ others) how to debug with runtime evidence by
24
- querying motel's local OTLP store. Install it with a one-liner via
25
- [`npx skills`](https://github.com/vercel-labs/skills):
10
+ `motel` ships a companion skill that teaches Claude Code, OpenCode,
11
+ Cursor, Codex, and 40+ other agents how to debug with runtime evidence
12
+ by querying motel's local OTLP store. Install it once and any future
13
+ agent session in the project will know how to use it.
26
14
 
27
15
  ```bash
28
16
  # Project-local (adds to .claude/skills, .agents/skills, etc.)
29
17
  npx skills add kitlangton/motel --skill motel-debug
30
18
 
31
- # Globally, available in every project
19
+ # Or globally, for every project
32
20
  npx skills add kitlangton/motel --skill motel-debug -g
33
-
34
- # Target a specific agent only
35
- npx skills add kitlangton/motel --skill motel-debug -a claude-code
36
21
  ```
37
22
 
38
- The skill lives at [`skills/motel-debug/`](skills/motel-debug/) in this repo.
39
-
40
- ## Quick start
23
+ See the full skill at [`skills/motel-debug/SKILL.md`](skills/motel-debug/SKILL.md).
41
24
 
42
- ```bash
43
- bun install
44
- bun run dev
45
- ```
46
-
47
- `bun run dev` starts the local OTLP ingest server (on `http://127.0.0.1:27686`)
48
- and launches the TUI. Press `?` once inside for the keyboard cheat sheet, or
49
- `c` to copy paste-ready setup instructions for another Effect/OTEL app.
25
+ ## For humans: install and run the TUI
50
26
 
51
- If you just want the server without the TUI (for example, to run it in the
52
- background and browse the web UI):
27
+ motel is distributed on npm as `@kitlangton/motel`. The binary is a Bun
28
+ script, so Bun must be on your `PATH` at runtime:
53
29
 
54
30
  ```bash
55
- bun run server
56
- # then in another terminal
57
- bun run web:dev
58
- ```
59
-
60
- ## Commands
61
-
62
- - `bun install`
63
- - `bun run server`
64
- - `bun run dev`
65
- - `bun run test`
66
- - `bun run cli services`
67
- - `bun run cli traces <service>`
68
- - `bun run cli span <span-id>`
69
- - `bun run cli search-traces <service> [operation]`
70
- - `bun run cli trace-stats <groupBy> <agg> [service]`
71
- - `bun run cli logs <service>`
72
- - `bun run cli search-logs <service> [body]`
73
- - `bun run cli log-stats <groupBy> [service]`
74
- - `bun run cli trace-logs <trace-id>`
75
- - `bun run cli facets <traces|logs> <field>`
76
- - `bun run instructions`
77
- - `bun run typecheck`
31
+ # one-off (no install)
32
+ bunx @kitlangton/motel
78
33
 
79
- ## Local ports
80
-
81
- This repo uses one local Bun server with SQLite storage. No Docker is required.
82
-
83
- - motel local API / UI base: `http://127.0.0.1:27686`
84
- - OTLP HTTP traces: `http://127.0.0.1:27686/v1/traces`
85
- - OTLP HTTP logs: `http://127.0.0.1:27686/v1/logs`
86
- - health: `http://127.0.0.1:27686/api/health`
87
-
88
- Other local apps can send telemetry to:
34
+ # or install globally
35
+ bun add -g @kitlangton/motel
36
+ motel
89
37
 
90
- ```bash
91
- http://127.0.0.1:27686/v1/traces
92
- http://127.0.0.1:27686/v1/logs
38
+ # npm also works (Bun still required to run it)
39
+ npm install -g @kitlangton/motel
93
40
  ```
94
41
 
95
- Agents and scripts can query traces and logs from the local API:
42
+ Don't have Bun?
96
43
 
97
44
  ```bash
98
- http://127.0.0.1:27686/api/services
99
- http://127.0.0.1:27686/api/traces?service=<service>&limit=20&lookback=1h
100
- http://127.0.0.1:27686/api/traces/search?service=<service>&operation=proxy&status=error&attr.sessionID=<session-id>
101
- http://127.0.0.1:27686/api/traces/stats?groupBy=operation&agg=p95_duration&service=<service>
102
- http://127.0.0.1:27686/api/spans/<span-id>
103
- http://127.0.0.1:27686/api/spans/<span-id>/logs
104
- http://127.0.0.1:27686/api/spans/search?service=<service>&operation=Format.file&parentOperation=Tool.write&attr.sessionID=<session-id>
105
- http://127.0.0.1:27686/api/traces/<trace-id>/spans
106
- http://127.0.0.1:27686/api/logs?service=<service>&body=proxy_request
107
- http://127.0.0.1:27686/api/logs?service=<service>&attr.service.name=<service>
108
- http://127.0.0.1:27686/api/logs/stats?groupBy=severity&agg=count&service=<service>
109
- http://127.0.0.1:27686/api/facets?type=logs&field=severity
110
- http://127.0.0.1:27686/openapi.json
111
- http://127.0.0.1:27686/docs
45
+ curl -fsSL https://bun.sh/install | bash
112
46
  ```
113
47
 
114
- ## TUI keys
115
-
116
- - `?`: show or hide keyboard shortcut help
117
- - `j` / `k` or `up` / `down`: move selection
118
- - `ctrl-n` / `ctrl-p`: switch traces even while in trace details
119
- - `gg` or `home`: jump to the first trace or first span
120
- - `G` or `end`: jump to the last trace or last span
121
- - `ctrl-u` / `pageup`: move up by one page
122
- - `ctrl-d` / `pagedown`: move down by one page
123
- - `l`: toggle service logs mode
124
- - `[` / `]`: switch service
125
- - `enter`: enter span navigation or open selected span detail
126
- - `esc`: leave span detail or span navigation
127
- - `r`: refresh
128
- - `c`: copy a paste-ready Effect setup prompt for another app
129
- - `o`: open selected trace in browser
130
- - `q`: quit
131
-
132
- ## How It Works
133
-
134
- `motel` now has one local service process:
135
-
136
- - the local Bun server receives OTLP traces and logs on `http://127.0.0.1:27686`
137
- - it stores telemetry in SQLite at `.motel-data/telemetry.sqlite`
138
- - it exposes query endpoints on the same base URL
139
-
140
- So yes: another service has to point its OTEL exporters at this local motel instance.
141
-
142
- ## Privacy Note
143
-
144
- `motel` is a local observability tool, and it can store sensitive telemetry content if the upstream app emits it.
48
+ `motel` starts the local OTLP ingest server on
49
+ `http://127.0.0.1:27686` and launches the TUI. Press `?` once inside for
50
+ the keyboard cheat sheet, or `c` to copy paste-ready setup instructions
51
+ for any Effect/OTEL app you want to trace.
145
52
 
146
- - correlated logs may include secrets, tokens, or PII if your app logs them
147
- - AI call data may include prompt previews, response previews, full prompt content, response text, tool metadata, and provider metadata
148
- - treat the local SQLite store as sensitive development data when using motel against real workloads
53
+ Requirements: [Bun](https://bun.sh/) v1.1 or newer.
149
54
 
150
- The easiest flow is:
55
+ ## How your app connects
151
56
 
152
- 1. Run `bun run dev` here. That starts the local server if needed and then launches the TUI.
153
- 2. In `motel`, press `c`.
154
- 3. Paste the copied instructions into an agent working in the other service.
155
- 4. Have that service export OTEL traces to `http://127.0.0.1:27686/v1/traces` and OTEL logs to `http://127.0.0.1:27686/v1/logs`.
156
- 5. Refresh `motel`, switch to that service with `[` / `]`, and use `l` or `enter` to inspect logs under a trace or span.
57
+ Once motel is running, point your app's OTLP/HTTP exporters at these
58
+ local endpoints:
157
59
 
158
- ## For Agents
159
-
160
- An agent does not need to talk to the TUI.
60
+ ```
61
+ http://127.0.0.1:27686/v1/traces
62
+ http://127.0.0.1:27686/v1/logs
63
+ ```
161
64
 
162
- List and search endpoints now return a `meta` object with `limit`, `lookback`, `returned`, `truncated`, and `nextCursor` so callers can page safely instead of assuming they received all results.
65
+ Motel keeps everything in a local SQLite database at
66
+ `.motel-data/telemetry.sqlite`. No Docker, no cloud account.
163
67
 
164
- Use one of these:
68
+ ## How agents connect
165
69
 
166
- 1. motel HTTP API directly
70
+ Agents with the `motel-debug` skill installed will automatically use
71
+ motel's HTTP API. The full OpenAPI spec is at
72
+ `http://127.0.0.1:27686/openapi.json` — the key endpoints are:
167
73
 
168
- ```bash
169
- curl http://127.0.0.1:27686/api/services
170
- curl "http://127.0.0.1:27686/api/traces?service=my-service&limit=20&lookback=1h"
171
- curl http://127.0.0.1:27686/api/traces/<trace-id>
172
74
  ```
173
-
174
- 2. The local CLI wrapper in this repo
175
-
176
- ```bash
177
- bun run cli services
178
- bun run cli traces my-service 20
179
- bun run cli span <span-id>
180
- bun run cli trace-spans <trace-id>
181
- bun run cli search-spans my-service Format.file parent=Tool.write attr.sessionID=sess_123
182
- bun run cli search-traces my-service proxy attr.sessionID=sess_123
183
- bun run cli trace-stats operation p95_duration my-service attr.modelID=gpt-5.4
184
- bun run cli trace <trace-id>
185
- bun run cli logs my-service
186
- bun run cli search-logs my-service timeout attr.tool=search
187
- bun run cli log-stats severity my-service attr.tool=search
188
- bun run cli trace-logs <trace-id>
189
- bun run cli span-logs <span-id>
190
- bun run cli facets logs severity
191
- bun run instructions
75
+ GET /api/health liveness check
76
+ GET /api/services services reporting telemetry
77
+ GET /api/traces?service=<service> recent traces for a service
78
+ GET /api/traces/<trace-id> full trace tree
79
+ GET /api/spans/<span-id> single span + logs
80
+ GET /api/logs?service=<service> recent logs
81
+ GET /api/traces/search?... structured trace search
82
+ GET /api/logs/search?... structured log search
83
+ GET /api/ai/calls AI SDK call inspector
192
84
  ```
193
85
 
194
- Recommended shape going forward:
86
+ ## TUI keys
195
87
 
196
- 1. Keep motel as the single ingest point for apps.
197
- 2. Keep SQLite as the local source of truth.
198
- 3. Keep `motel` as the interactive viewer.
199
- 4. Keep the CLI and HTTP API as the agent/script interfaces.
88
+ - `?` keyboard cheat sheet
89
+ - `j` / `k` or `↑` / `↓` — move selection
90
+ - `enter` / `esc` drill in / back out (trace → waterfall → span detail)
91
+ - `[` / `]` switch service
92
+ - `tab` — toggle service logs
93
+ - `/` — filter traces
94
+ - `s` — cycle sort (recent → slowest → errors)
95
+ - `t` — cycle theme
96
+ - `c` — copy paste-ready setup instructions for another app
97
+ - `o` — open selected trace in the browser
98
+ - `q` — quit
99
+
100
+ ## Privacy note
101
+
102
+ motel is a local development tool, but your app can emit sensitive
103
+ telemetry. Correlated logs may include secrets, tokens, or PII if your
104
+ app logs them; AI call traces may include full prompt content and
105
+ response text. Treat the local SQLite store as sensitive development
106
+ data when pointing motel at real workloads.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitlangton/motel",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "A local OpenTelemetry ingest + TUI viewer for development, backed by SQLite.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,7 +23,9 @@
23
23
  "effect",
24
24
  "opentui"
25
25
  ],
26
- "workspaces": ["web"],
26
+ "workspaces": [
27
+ "web"
28
+ ],
27
29
  "engines": {
28
30
  "bun": ">=1.1.0"
29
31
  },
@@ -48,6 +50,7 @@
48
50
  "daemon": "bun run src/motel.ts daemon",
49
51
  "status": "bun run src/motel.ts status",
50
52
  "stop": "bun run src/motel.ts stop",
53
+ "restart": "bun run src/motel.ts restart",
51
54
  "server": "bun run src/motel.ts server",
52
55
  "mcp": "bun run src/mcp.ts",
53
56
  "test": "bun test",
package/src/App.tsx CHANGED
@@ -1,16 +1,26 @@
1
- import { RGBA, TextAttributes, type ScrollBoxRenderable } from "@opentui/core"
1
+ import { RGBA, TextAttributes } from "@opentui/core"
2
2
  import { useAtom } from "@effect/atom-react"
3
3
  import { useTerminalDimensions } from "@opentui/react"
4
- import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from "react"
5
- import { formatTimestamp, traceRowId } from "./ui/format.ts"
4
+ import { useCallback, useEffect, useMemo, useRef } from "react"
5
+ import { formatTimestamp } from "./ui/format.ts"
6
6
  import { Divider, FooterHints, HelpModal, PlainLine, SplitDivider, TextLine } from "./ui/primitives.tsx"
7
7
  import { useAppLayout } from "./ui/app/useAppLayout.ts"
8
8
  import { useTraceScreenData } from "./ui/app/useTraceScreenData.ts"
9
9
  import { TraceWorkspace } from "./ui/app/TraceWorkspace.tsx"
10
- import { noticeAtom, persistSelectedTheme, selectedThemeAtom } from "./ui/state.ts"
10
+ import {
11
+ attrPickerIndexAtom,
12
+ attrPickerInputAtom,
13
+ attrPickerModeAtom,
14
+ attrFacetStateAtom,
15
+ noticeAtom,
16
+ persistSelectedTheme,
17
+ selectedThemeAtom,
18
+ } from "./ui/state.ts"
11
19
  import { applyTheme, colors, SEPARATOR, themeLabel } from "./ui/theme.ts"
12
20
  import { getVisibleSpans } from "./ui/Waterfall.tsx"
13
21
  import { useKeyboardNav } from "./ui/useKeyboardNav.ts"
22
+ import { AttrFilterModal } from "./ui/AttrFilterModal.tsx"
23
+ import { useAttrFilterPicker } from "./ui/useAttrFilterPicker.ts"
14
24
 
15
25
  export const App = () => {
16
26
  const { width, height } = useTerminalDimensions()
@@ -36,11 +46,18 @@ export const App = () => {
36
46
  autoRefresh,
37
47
  filterMode,
38
48
  filterText,
49
+ activeAttrKey,
50
+ activeAttrValue,
39
51
  traceSort,
40
52
  selectedTraceSummary,
41
53
  selectedTrace,
42
54
  filteredTraces,
43
55
  } = useTraceScreenData()
56
+ const [pickerMode] = useAtom(attrPickerModeAtom)
57
+ const [pickerInput] = useAtom(attrPickerInputAtom)
58
+ const [pickerIndex] = useAtom(attrPickerIndexAtom)
59
+ const [attrFacets] = useAtom(attrFacetStateAtom)
60
+ useAttrFilterPicker(activeAttrKey)
44
61
 
45
62
  const layout = useAppLayout({ width, height, notice, detailView, selectedSpanIndex })
46
63
  const {
@@ -61,7 +78,6 @@ export const App = () => {
61
78
  } = layout
62
79
 
63
80
  const noticeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
64
- const traceListScrollRef = useRef<ScrollBoxRenderable | null>(null)
65
81
 
66
82
  const flashNotice = (message: string) => {
67
83
  if (noticeTimeoutRef.current !== null) {
@@ -83,27 +99,6 @@ export const App = () => {
83
99
  persistSelectedTheme(selectedTheme)
84
100
  }, [selectedTheme])
85
101
 
86
- useLayoutEffect(() => {
87
- const box = traceListScrollRef.current
88
- const traceId = selectedTraceSummary?.traceId
89
- if (!box || !traceId) return
90
- const indexInList = filteredTraces.findIndex((trace) => trace.traceId === traceId)
91
- if (indexInList < 0) return
92
- const currentTop = box.scrollTop
93
- const viewportRows = Math.max(1, traceViewportRows)
94
- let nextTop = currentTop
95
- if (indexInList < currentTop) {
96
- nextTop = indexInList
97
- } else if (indexInList >= currentTop + viewportRows) {
98
- nextTop = indexInList - viewportRows + 1
99
- }
100
- const maxTop = Math.max(0, filteredTraces.length - viewportRows)
101
- nextTop = Math.max(0, Math.min(nextTop, maxTop))
102
- if (nextTop !== currentTop) {
103
- box.scrollTop = nextTop
104
- }
105
- }, [filteredTraces, selectedTraceIndex, selectedTraceSummary?.traceId, traceSort, traceViewportRows])
106
-
107
102
  const { spanNavActive } = useKeyboardNav({
108
103
  selectedTrace,
109
104
  filteredTraces,
@@ -117,12 +112,15 @@ export const App = () => {
117
112
 
118
113
  const headerServiceLabel = selectedTraceService ?? "none"
119
114
  const autoLabel = autoRefresh ? "● live" : "○ paused"
115
+ const attrFilterLabel = activeAttrKey && activeAttrValue
116
+ ? ` [${activeAttrKey}=${activeAttrValue.length > 20 ? `${activeAttrValue.slice(0, 19)}…` : activeAttrValue}]`
117
+ : ""
120
118
  const headerRight = traceState.fetchedAt
121
119
  ? `${autoLabel} ${formatTimestamp(traceState.fetchedAt)}`
122
120
  : traceState.status === "loading"
123
121
  ? "loading traces..."
124
122
  : ""
125
- const headerLeftLen = "MOTEL".length + SEPARATOR.length + headerServiceLabel.length
123
+ const headerLeftLen = "MOTEL".length + SEPARATOR.length + headerServiceLabel.length + attrFilterLabel.length
126
124
  const headerGap = Math.max(2, headerFooterWidth - headerLeftLen - headerRight.length)
127
125
  const visibleFooterNotice = footerNotice
128
126
 
@@ -168,6 +166,7 @@ export const App = () => {
168
166
  <span fg={colors.muted} attributes={TextAttributes.BOLD}>MOTEL</span>
169
167
  <span fg={colors.separator}>{SEPARATOR}</span>
170
168
  <span fg={colors.muted}>{headerServiceLabel}</span>
169
+ {attrFilterLabel ? <span fg={colors.accent} attributes={TextAttributes.BOLD}>{attrFilterLabel}</span> : null}
171
170
  <span fg={colors.muted}>{" ".repeat(headerGap)}</span>
172
171
  <span fg={colors.muted} attributes={TextAttributes.BOLD}>{headerRight}</span>
173
172
  </TextLine>
@@ -181,7 +180,6 @@ export const App = () => {
181
180
  filterMode={filterMode}
182
181
  filterText={filterText}
183
182
  traceListProps={traceListProps}
184
- traceListScrollRef={traceListScrollRef}
185
183
  selectedTraceService={selectedTraceService}
186
184
  serviceLogState={serviceLogState}
187
185
  selectedServiceLogIndex={selectedServiceLogIndex}
@@ -212,6 +210,18 @@ export const App = () => {
212
210
  </>
213
211
  ) : null}
214
212
  {showHelp ? <HelpModal width={width ?? 100} height={height ?? 24} autoRefresh={autoRefresh} themeLabel={themeLabel(selectedTheme)} onClose={() => setShowHelp(false)} /> : null}
213
+ {pickerMode !== "off" ? (
214
+ <AttrFilterModal
215
+ width={width ?? 100}
216
+ height={height ?? 24}
217
+ mode={pickerMode}
218
+ input={pickerInput}
219
+ selectedIndex={pickerIndex}
220
+ selectedKey={activeAttrKey}
221
+ state={attrFacets}
222
+ onClose={() => { /* handled via keyboard */ }}
223
+ />
224
+ ) : null}
215
225
  </box>
216
226
  )
217
227
  }
package/src/config.ts CHANGED
@@ -34,6 +34,6 @@ export const config = {
34
34
  traceFetchLimit: parsePositiveInt(process.env.MOTEL_OTEL_TRACE_LIMIT, 100),
35
35
  logFetchLimit: parsePositiveInt(process.env.MOTEL_OTEL_LOG_LIMIT, 80),
36
36
  retentionHours: parsePositiveInt(process.env.MOTEL_OTEL_RETENTION_HOURS, 168),
37
- maxDbSizeMb: parsePositiveInt(process.env.MOTEL_OTEL_MAX_DB_SIZE_MB, 256),
37
+ maxDbSizeMb: parsePositiveInt(process.env.MOTEL_OTEL_MAX_DB_SIZE_MB, 1024),
38
38
  },
39
39
  } as const
package/src/httpApi.ts CHANGED
@@ -310,7 +310,10 @@ export const MotelHttpApi = HttpApi.make("MotelTelemetry")
310
310
  Schema.annotateKey({ description: "Data source to facet: 'traces' facets span columns, 'logs' facets log columns" }),
311
311
  ),
312
312
  field: Schema.String.pipe(
313
- Schema.annotateKey({ description: "Column to facet. Traces: service, operation, status. Logs: service, severity, scope" }),
313
+ Schema.annotateKey({ description: "Column to facet. Traces: service, operation, status, attribute_keys, attribute_values. Logs: service, severity, scope. For attribute_values, also pass key=<attribute-name>." }),
314
+ ),
315
+ key: Schema.optionalKey(Schema.String).pipe(
316
+ Schema.annotateKey({ description: "Attribute key to get values for (required when field=attribute_values)." }),
314
317
  ),
315
318
  service: ServiceParam,
316
319
  lookback: LookbackParam,
@@ -320,7 +323,7 @@ export const MotelHttpApi = HttpApi.make("MotelTelemetry")
320
323
  error: ErrorResponse,
321
324
  })
322
325
  .annotate(OpenApi.Summary, "Get facet value counts")
323
- .annotate(OpenApi.Description, "Returns distinct values and their counts for a given field, useful for discovering what data exists before querying. For example: ?type=logs&field=severity returns the distribution of log levels."),
326
+ .annotate(OpenApi.Description, "Returns distinct values and their counts for a given field, useful for discovering what data exists before querying. Examples: ?type=logs&field=severity returns log level distribution; ?type=traces&field=attribute_keys&service=opencode lists top span attribute keys; ?type=traces&field=attribute_values&key=ai.model.id lists values seen for that key."),
324
327
 
325
328
  // AI Call endpoints
326
329
  HttpApiEndpoint.get("aiCalls", "/api/ai/calls", {
@@ -504,6 +504,7 @@ const TelemetryGroupLive = HttpApiBuilder.group(
504
504
  type,
505
505
  field,
506
506
  serviceName: url.searchParams.get("service"),
507
+ key: url.searchParams.get("key"),
507
508
  lookbackMinutes: parseLookbackMinutes(url.searchParams.get("lookback"), config.otel.traceLookbackMinutes),
508
509
  limit: parseLimit(url.searchParams.get("limit"), 20),
509
510
  }),
package/src/motel.ts CHANGED
@@ -36,6 +36,17 @@ case "stop": {
36
36
  break
37
37
  }
38
38
 
39
+ case "restart": {
40
+ // Stop any running managed daemon, then start a fresh one + launch the
41
+ // TUI. Handy during local development when you've rebuilt the server
42
+ // and want the TUI to reconnect to the new binary in one command.
43
+ await run(stopManagedDaemon)
44
+ await run(applyManagedDaemonEnv)
45
+ await run(ensureManagedDaemon)
46
+ await import("./index.js")
47
+ break
48
+ }
49
+
39
50
  case "server": {
40
51
  await run(applyManagedDaemonEnv)
41
52
  await import("./server.js")
@@ -56,6 +67,7 @@ case "-h": {
56
67
  motel daemon
57
68
  motel status
58
69
  motel stop
70
+ motel restart
59
71
  motel server
60
72
  motel mcp
61
73
  motel services