autotel-terminal 2.0.0 → 3.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Jag Reehal 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,393 @@
1
+ # autotel-terminal
2
+
3
+ **Terminal dashboard for viewing OpenTelemetry traces in real-time** - Beautiful react-ink powered dashboard for live trace inspection during development.
4
+
5
+ [![npm version](https://badge.fury.io/js/autotel-terminal.svg)](https://www.npmjs.com/package/autotel-terminal)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Overview
9
+
10
+ `autotel-terminal` provides a beautiful, interactive terminal dashboard for viewing OpenTelemetry traces in real-time. Perfect for development and debugging, it displays spans as they're created with live statistics, filtering, and detailed span inspection.
11
+
12
+ ### Features
13
+
14
+ - ✅ **Real-time span streaming** - See traces as they happen
15
+ - ✅ **Interactive dashboard** - Navigate spans with keyboard controls
16
+ - ✅ **Error filtering** - Focus on failed operations
17
+ - ✅ **Live statistics** - Span count, error rate, P95 latency
18
+ - ✅ **Span details** - View attributes, trace IDs, parent relationships
19
+ - ✅ **Simple setup** - Just add a streaming processor to your config
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install autotel-terminal autotel
25
+ # or
26
+ pnpm add autotel-terminal autotel
27
+ # or
28
+ yarn add autotel-terminal autotel
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ### Recommended Usage
34
+
35
+ Create a `StreamingSpanProcessor` and pass it to `init()`, then use `renderTerminal()` with the stream:
36
+
37
+ ```typescript
38
+ import { init, trace } from 'autotel';
39
+ import {
40
+ renderTerminal,
41
+ StreamingSpanProcessor,
42
+ createTerminalSpanStream,
43
+ } from 'autotel-terminal';
44
+
45
+ // Create streaming processor for the terminal dashboard
46
+ const streamingProcessor = new StreamingSpanProcessor(null);
47
+
48
+ // Initialize autotel with the streaming processor
49
+ init({
50
+ service: 'my-app',
51
+ endpoint: 'http://localhost:4318',
52
+ spanProcessors: [streamingProcessor],
53
+ });
54
+
55
+ // Create the stream and launch the dashboard
56
+ const terminalStream = createTerminalSpanStream(streamingProcessor);
57
+ renderTerminal({ title: 'My App Traces' }, terminalStream);
58
+
59
+ // Your traced code will now appear in the dashboard
60
+ const myFunction = trace((ctx) => async () => {
61
+ ctx.setAttribute('example', 'value');
62
+ // ... your code
63
+ });
64
+
65
+ await myFunction();
66
+ ```
67
+
68
+ ### With Backend Export
69
+
70
+ To stream to the terminal AND export to a backend (e.g., Jaeger, OTLP collector):
71
+
72
+ ```typescript
73
+ import { init } from 'autotel';
74
+ import {
75
+ StreamingSpanProcessor,
76
+ createTerminalSpanStream,
77
+ renderTerminal,
78
+ } from 'autotel-terminal';
79
+ import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
80
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
81
+
82
+ // Create exporter and base processor for your backend
83
+ const exporter = new OTLPTraceExporter({ url: 'http://localhost:4318/v1/traces' });
84
+ const batchProcessor = new BatchSpanProcessor(exporter);
85
+
86
+ // Create streaming processor that wraps your batch processor
87
+ // Spans are forwarded to both the dashboard AND the backend
88
+ const streamingProcessor = new StreamingSpanProcessor(batchProcessor);
89
+
90
+ // Initialize autotel
91
+ init({
92
+ service: 'my-app',
93
+ spanProcessors: [streamingProcessor],
94
+ });
95
+
96
+ // Create stream and launch dashboard
97
+ const stream = createTerminalSpanStream(streamingProcessor);
98
+ renderTerminal({ title: 'My App Traces' }, stream);
99
+ ```
100
+
101
+ ### With Custom Options
102
+
103
+ ```typescript
104
+ renderTerminal(
105
+ {
106
+ title: 'My App Traces',
107
+ showStats: true,
108
+ maxSpans: 200,
109
+ colors: true, // Auto-detected if TTY
110
+ },
111
+ stream,
112
+ );
113
+ ```
114
+
115
+ ## Dashboard Controls
116
+
117
+ Once the dashboard is running, use these keyboard controls:
118
+
119
+ - **↑/↓** - Navigate through spans
120
+ - **`p`** - Pause/resume live updates
121
+ - **`e`** - Toggle error-only filter
122
+ - **`c`** - Clear all spans
123
+ - **Ctrl+C** - Exit dashboard
124
+
125
+ ## Dashboard Features
126
+
127
+ ### Span List
128
+
129
+ The left panel shows recent spans with:
130
+ - Span name (truncated to fit)
131
+ - Duration (color-coded: green < 500ms, yellow > 500ms, red = error)
132
+ - Trace ID (first 10 characters)
133
+ - Selection indicator (cyan `›`)
134
+
135
+ ### Span Details
136
+
137
+ The right panel shows detailed information for the selected span:
138
+ - Name, status, duration
139
+ - Trace ID, Span ID, Parent Span ID
140
+ - Span kind (INTERNAL, SERVER, CLIENT, etc.)
141
+ - Attributes (first 12 attributes)
142
+
143
+ ### Statistics Bar
144
+
145
+ When enabled, shows:
146
+ - Total spans
147
+ - Error count
148
+ - Average duration
149
+ - P95 latency
150
+
151
+ ## API Reference
152
+
153
+ ### `renderTerminal(options?, stream?)`
154
+
155
+ Render the terminal dashboard.
156
+
157
+ **Parameters:**
158
+
159
+ - `options` - Optional dashboard configuration
160
+ - `stream` - The terminal span stream (from `createTerminalSpanStream`)
161
+
162
+ **Options:**
163
+
164
+ ```typescript
165
+ interface TerminalOptions {
166
+ /** Dashboard title (default: 'Autotel Trace Inspector') */
167
+ title?: string;
168
+
169
+ /** Show statistics bar (default: true) */
170
+ showStats?: boolean;
171
+
172
+ /** Maximum number of spans to display (default: 100) */
173
+ maxSpans?: number;
174
+
175
+ /** Enable colors (default: true if TTY) */
176
+ colors?: boolean;
177
+ }
178
+ ```
179
+
180
+ **Example:**
181
+
182
+ ```typescript
183
+ renderTerminal(
184
+ {
185
+ title: 'API Server Traces',
186
+ showStats: true,
187
+ maxSpans: 200,
188
+ },
189
+ stream,
190
+ );
191
+ ```
192
+
193
+ ### `StreamingSpanProcessor`
194
+
195
+ A span processor that emits completed spans to subscribers. Can wrap another processor or work standalone.
196
+
197
+ ```typescript
198
+ import { StreamingSpanProcessor } from 'autotel-terminal';
199
+ import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
200
+
201
+ // Standalone (no forwarding)
202
+ const streamingProcessor = new StreamingSpanProcessor(null);
203
+
204
+ // Or wrap an existing processor (spans forwarded to both)
205
+ const batchProcessor = new BatchSpanProcessor(exporter);
206
+ const streamingProcessor = new StreamingSpanProcessor(batchProcessor);
207
+
208
+ // Subscribe to spans directly (alternative to using createTerminalSpanStream)
209
+ const unsubscribe = streamingProcessor.subscribe((span) => {
210
+ console.log('Span ended:', span.name);
211
+ });
212
+
213
+ // Later, unsubscribe
214
+ unsubscribe();
215
+ ```
216
+
217
+ **Constructor:**
218
+
219
+ ```typescript
220
+ new StreamingSpanProcessor(wrappedProcessor?: SpanProcessor | null)
221
+ ```
222
+
223
+ - `wrappedProcessor` - Optional processor to wrap. If provided, spans are forwarded to it. If `null`, spans are only emitted to subscribers.
224
+
225
+ **Methods:**
226
+
227
+ - `subscribe(callback: (span: ReadableSpan) => void): () => void` - Subscribe to span end events. Returns unsubscribe function.
228
+ - `forceFlush(): Promise<void>` - Flush wrapped processor (if any).
229
+ - `shutdown(): Promise<void>` - Shutdown processor and clear subscribers.
230
+
231
+ ### `createTerminalSpanStream(processor)`
232
+
233
+ Create a terminal-compatible stream from a `StreamingSpanProcessor`.
234
+
235
+ ```typescript
236
+ import { createTerminalSpanStream, StreamingSpanProcessor } from 'autotel-terminal';
237
+
238
+ const processor = new StreamingSpanProcessor(null);
239
+ const stream = createTerminalSpanStream(processor);
240
+
241
+ // Subscribe to span events
242
+ stream.onSpanEnd((event) => {
243
+ console.log('Span:', event.name, event.durationMs + 'ms');
244
+ });
245
+ ```
246
+
247
+ **Returns:** `TerminalSpanStream` with `onSpanEnd(callback)` method.
248
+
249
+ **Event Format:**
250
+
251
+ ```typescript
252
+ interface TerminalSpanEvent {
253
+ name: string;
254
+ spanId: string;
255
+ traceId: string;
256
+ parentSpanId?: string;
257
+ startTime: number;
258
+ endTime: number;
259
+ durationMs: number;
260
+ status: 'OK' | 'ERROR' | 'UNSET';
261
+ kind?: string;
262
+ attributes?: Record<string, unknown>;
263
+ }
264
+ ```
265
+
266
+ ## Use Cases
267
+
268
+ ### Development Debugging
269
+
270
+ Perfect for debugging during development - see traces in real-time without leaving your terminal:
271
+
272
+ ```typescript
273
+ import { init } from 'autotel';
274
+ import {
275
+ StreamingSpanProcessor,
276
+ createTerminalSpanStream,
277
+ renderTerminal,
278
+ } from 'autotel-terminal';
279
+
280
+ const streamingProcessor = new StreamingSpanProcessor(null);
281
+
282
+ init({
283
+ service: 'dev-server',
284
+ spanProcessors: [streamingProcessor],
285
+ });
286
+
287
+ const stream = createTerminalSpanStream(streamingProcessor);
288
+ renderTerminal({ title: 'Dev Server Traces' }, stream);
289
+ ```
290
+
291
+ ### Testing
292
+
293
+ Use the streaming processor to assert on spans in tests:
294
+
295
+ ```typescript
296
+ import { StreamingSpanProcessor, createTerminalSpanStream } from 'autotel-terminal';
297
+ import { init } from 'autotel';
298
+
299
+ const processor = new StreamingSpanProcessor(null);
300
+ const stream = createTerminalSpanStream(processor);
301
+
302
+ init({
303
+ service: 'test',
304
+ spanProcessors: [processor],
305
+ });
306
+
307
+ // Collect spans
308
+ const spans: TerminalSpanEvent[] = [];
309
+ stream.onSpanEnd((span) => spans.push(span));
310
+
311
+ // Run your code
312
+ await myFunction();
313
+
314
+ // Assert on spans
315
+ expect(spans).toHaveLength(1);
316
+ expect(spans[0].name).toBe('myFunction');
317
+ ```
318
+
319
+ ### Custom Dashboards
320
+
321
+ Build your own dashboard using the streaming processor:
322
+
323
+ ```typescript
324
+ import { StreamingSpanProcessor, createTerminalSpanStream } from 'autotel-terminal';
325
+
326
+ const processor = new StreamingSpanProcessor(null);
327
+ const stream = createTerminalSpanStream(processor);
328
+
329
+ stream.onSpanEnd((event) => {
330
+ // Send to your custom dashboard
331
+ myCustomDashboard.addSpan(event);
332
+ });
333
+ ```
334
+
335
+ ## Integration with autotel
336
+
337
+ `autotel-terminal` works seamlessly with `autotel`. Just include the `StreamingSpanProcessor` in your `spanProcessors` array:
338
+
339
+ ```typescript
340
+ import { init } from 'autotel';
341
+ import {
342
+ StreamingSpanProcessor,
343
+ createTerminalSpanStream,
344
+ renderTerminal,
345
+ } from 'autotel-terminal';
346
+
347
+ const streamingProcessor = new StreamingSpanProcessor(null);
348
+
349
+ init({
350
+ service: 'my-app',
351
+ endpoint: 'http://localhost:4318',
352
+ spanProcessors: [streamingProcessor],
353
+ });
354
+
355
+ const stream = createTerminalSpanStream(streamingProcessor);
356
+ renderTerminal({}, stream);
357
+ ```
358
+
359
+ ## Requirements
360
+
361
+ - Node.js 22+
362
+ - Terminal with TTY support (for colors and interactivity)
363
+ - `autotel` package (peer dependency)
364
+
365
+ ## Bundle Size
366
+
367
+ - **Core dashboard**: ~25KB minified
368
+ - **With dependencies** (ink, react): ~150KB total
369
+ - **Development tool** - not intended for production bundles
370
+
371
+ ## Limitations
372
+
373
+ - **Development only** - Not designed for production use
374
+ - **TTY required** - Colors and interactivity require a terminal
375
+ - **Memory** - Keeps spans in memory (limited by `maxSpans` option)
376
+ - **Single instance** - Only one dashboard can run at a time
377
+
378
+ ## Examples
379
+
380
+ See the [autotel examples](../../apps/) directory for complete working examples:
381
+
382
+ - `apps/example-terminal` - Terminal dashboard with live trace viewing
383
+
384
+ ## License
385
+
386
+ MIT © [Jag Reehal](https://github.com/jagreehal)
387
+
388
+ ## Links
389
+
390
+ - [GitHub Repository](https://github.com/jagreehal/autotel)
391
+ - [Documentation](https://github.com/jagreehal/autotel#readme)
392
+ - [Issues](https://github.com/jagreehal/autotel/issues)
393
+
package/dist/index.cjs CHANGED
@@ -164,17 +164,21 @@ function Dashboard({
164
164
  return { total, errors, avg, p95 };
165
165
  }, [spans]);
166
166
  const current = filtered[selected];
167
- ink.useInput((input, key) => {
168
- if (key.upArrow) setSelected((i) => Math.max(0, i - 1));
169
- if (key.downArrow)
170
- setSelected((i) => Math.min(filtered.length - 1, i + 1));
171
- if (input === "p") setPaused((p) => !p);
172
- if (input === "e") setFilterErrorsOnly((v) => !v);
173
- if (input === "c") {
174
- setSpans([]);
175
- setSelected(0);
176
- }
177
- });
167
+ const { isRawModeSupported } = ink.useStdin();
168
+ ink.useInput(
169
+ (input, key) => {
170
+ if (key.upArrow) setSelected((i) => Math.max(0, i - 1));
171
+ if (key.downArrow)
172
+ setSelected((i) => Math.min(filtered.length - 1, i + 1));
173
+ if (input === "p") setPaused((p) => !p);
174
+ if (input === "e") setFilterErrorsOnly((v) => !v);
175
+ if (input === "c") {
176
+ setSpans([]);
177
+ setSelected(0);
178
+ }
179
+ },
180
+ { isActive: isRawModeSupported }
181
+ );
178
182
  const headerRight = paused ? "[Paused]" : "[Live]";
179
183
  return /* @__PURE__ */ jsxRuntime.jsxs(
180
184
  ink.Box,
@@ -188,17 +192,17 @@ function Dashboard({
188
192
  /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { bold: true, children: [
189
193
  "\u{1F52D} ",
190
194
  title
191
- ] }),
192
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: paused ? "yellow" : "green", children: headerRight })
195
+ ] }, "title"),
196
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: paused ? "yellow" : "green", children: headerRight }, "status")
193
197
  ] }),
194
198
  /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginBottom: 1, flexDirection: "row", justifyContent: "space-between", children: [
195
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "\u2191/\u2193 select \u2022 p pause \u2022 e errors-only \u2022 c clear \u2022 Ctrl+C exit" }),
199
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "\u2191/\u2193 select \u2022 p pause \u2022 e errors-only \u2022 c clear \u2022 Ctrl+C exit" }, "controls"),
196
200
  /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
197
201
  "showing ",
198
202
  filtered.length,
199
203
  "/",
200
204
  spans.length
201
- ] })
205
+ ] }, "count")
202
206
  ] }),
203
207
  /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 2, children: [
204
208
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -212,12 +216,13 @@ function Dashboard({
212
216
  paddingY: 0,
213
217
  children: [
214
218
  /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginTop: 0, marginBottom: 1, children: [
215
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Recent spans" }),
216
- filterErrorsOnly && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "red", children: " (errors only)" })
219
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Recent spans" }, "recent-spans-title"),
220
+ filterErrorsOnly && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "red", children: " (errors only)" }, "errors-only-label")
217
221
  ] }),
218
222
  filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "No spans yet. Generate some traffic\u2026" }) : filtered.slice(0, 20).map((s, i) => {
219
223
  const isSel = i === selected;
220
224
  const statusColor = s.status === "ERROR" ? "red" : s.durationMs > 500 ? "yellow" : "green";
225
+ const uniqueKey = `${s.spanId}-${s.startTime}`;
221
226
  return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", children: [
222
227
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: isSel ? "cyan" : void 0, children: isSel ? "\u203A " : " " }),
223
228
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: colors ? statusColor : void 0, children: truncate(s.name, 26) }),
@@ -229,7 +234,7 @@ function Dashboard({
229
234
  " ",
230
235
  truncate(s.traceId, 10)
231
236
  ] })
232
- ] }, s.spanId);
237
+ ] }, uniqueKey);
233
238
  })
234
239
  ]
235
240
  }
@@ -311,6 +316,7 @@ function renderTerminal(options = {}, stream) {
311
316
  const showStats = options.showStats !== false;
312
317
  const maxSpans = options.maxSpans ?? 100;
313
318
  const colors = options.colors ?? Boolean(process.stdout.isTTY);
319
+ const stdinOption = process.stdin.isTTY ? process.stdin : void 0;
314
320
  if (stream) {
315
321
  try {
316
322
  ink.render(
@@ -323,7 +329,8 @@ function renderTerminal(options = {}, stream) {
323
329
  colors,
324
330
  stream
325
331
  }
326
- )
332
+ ),
333
+ { stdin: stdinOption }
327
334
  );
328
335
  } catch (error) {
329
336
  console.error("[autotel-terminal] Failed to render dashboard:", error);
@@ -366,7 +373,8 @@ function renderTerminal(options = {}, stream) {
366
373
  colors,
367
374
  stream: terminalStream
368
375
  }
369
- )
376
+ ),
377
+ { stdin: stdinOption }
370
378
  );
371
379
  } catch (error) {
372
380
  console.error("[autotel-terminal] Failed to render dashboard:", error);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/streaming-processor.ts","../src/span-stream.ts","../src/index.tsx"],"names":["SpanStatusCode","SpanKind","useState","useEffect","useMemo","useInput","jsxs","Box","Text","jsx","Fragment","render","getAutotelTracerProvider"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Streaming Span Processor\n *\n * A span processor that emits ReadableSpan objects to subscribers.\n * This allows real-time streaming of spans to terminal dashboards and other consumers.\n *\n * @example Basic usage\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { BatchSpanProcessor } from 'autotel/processors'\n * import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\n *\n * const exporter = new OTLPTraceExporter({ url: 'http://localhost:4318/v1/traces' })\n * const baseProcessor = new BatchSpanProcessor(exporter)\n * const streamingProcessor = new StreamingSpanProcessor(baseProcessor)\n *\n * // Subscribe to spans\n * const unsubscribe = streamingProcessor.subscribe((span) => {\n * console.log('Span ended:', span.name)\n * })\n *\n * // Later, unsubscribe\n * unsubscribe()\n * ```\n */\n\nimport type {\n SpanProcessor,\n ReadableSpan,\n} from '@opentelemetry/sdk-trace-base';\nimport type { Context } from '@opentelemetry/api';\nimport type { Span } from '@opentelemetry/sdk-trace-base';\n\n/**\n * Span processor that wraps another processor and emits spans to subscribers\n *\n * This processor forwards all span operations to a wrapped processor while\n * also emitting completed spans to registered subscribers. This enables\n * real-time streaming of spans to terminal dashboards, loggers, or other\n * consumers without interfering with normal span processing.\n */\nexport class StreamingSpanProcessor implements SpanProcessor {\n private wrappedProcessor: SpanProcessor | null;\n private subscribers = new Set<(span: ReadableSpan) => void>();\n\n /**\n * Create a new streaming span processor\n *\n * @param wrappedProcessor - The processor to wrap and forward spans to.\n * If null, spans are only emitted to subscribers (no forwarding).\n */\n constructor(wrappedProcessor: SpanProcessor | null = null) {\n this.wrappedProcessor = wrappedProcessor;\n }\n\n onStart(span: Span, parentContext: Context): void {\n if (this.wrappedProcessor) {\n this.wrappedProcessor.onStart(span, parentContext);\n }\n }\n\n onEnd(span: ReadableSpan): void {\n // Emit to all subscribers first\n for (const subscriber of this.subscribers) {\n try {\n subscriber(span);\n } catch (error) {\n // Don't let subscriber errors break span processing\n console.error('[autotel-terminal] Subscriber error:', error);\n }\n }\n\n // Forward to wrapped processor\n if (this.wrappedProcessor) {\n this.wrappedProcessor.onEnd(span);\n }\n }\n\n /**\n * Subscribe to span end events\n *\n * @param callback - Function called when a span ends\n * @returns Unsubscribe function\n *\n * @example\n * ```typescript\n * const unsubscribe = processor.subscribe((span) => {\n * console.log('Span ended:', span.name)\n * })\n *\n * // Later, unsubscribe\n * unsubscribe()\n * ```\n */\n subscribe(callback: (span: ReadableSpan) => void): () => void {\n this.subscribers.add(callback);\n return () => this.subscribers.delete(callback);\n }\n\n async forceFlush(): Promise<void> {\n if (this.wrappedProcessor) {\n return this.wrappedProcessor.forceFlush();\n }\n }\n\n async shutdown(): Promise<void> {\n // Clear subscribers\n this.subscribers.clear();\n if (this.wrappedProcessor) {\n return this.wrappedProcessor.shutdown();\n }\n }\n}\n","/**\n * Terminal Span Stream\n *\n * Converts OpenTelemetry ReadableSpan objects to TerminalSpanEvent format\n * for consumption by the terminal dashboard.\n */\n\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport { SpanStatusCode, SpanKind } from 'autotel';\nimport type { StreamingSpanProcessor } from './streaming-processor';\n\n/**\n * Span event format for terminal dashboard consumption\n */\nexport interface TerminalSpanEvent {\n /** Span name */\n name: string;\n /** Span ID (hex string) */\n spanId: string;\n /** Trace ID (hex string) */\n traceId: string;\n /** Parent span ID (hex string, optional) */\n parentSpanId?: string;\n /** Start time in milliseconds since epoch */\n startTime: number;\n /** End time in milliseconds since epoch */\n endTime: number;\n /** Duration in milliseconds */\n durationMs: number;\n /** Span status */\n status: 'OK' | 'ERROR' | 'UNSET';\n /** Span kind (INTERNAL, SERVER, CLIENT, etc.) */\n kind?: string;\n /** Span attributes */\n attributes?: Record<string, unknown>;\n}\n\n/**\n * Stream interface for terminal dashboard\n */\nexport interface TerminalSpanStream {\n /**\n * Subscribe to span end events\n *\n * @param callback - Called when a span ends\n * @returns Unsubscribe function\n */\n onSpanEnd(callback: (event: TerminalSpanEvent) => void): () => void;\n}\n\n/**\n * Convert OpenTelemetry time tuple to milliseconds\n */\nfunction timeToMs([seconds, nanoseconds]: [number, number]): number {\n return seconds * 1000 + nanoseconds / 1_000_000;\n}\n\n/**\n * Map SpanStatusCode to string\n */\nfunction mapStatus(code: SpanStatusCode): 'OK' | 'ERROR' | 'UNSET' {\n if (code === SpanStatusCode.OK) {\n return 'OK';\n }\n if (code === SpanStatusCode.ERROR) {\n return 'ERROR';\n }\n return 'UNSET';\n}\n\n/**\n * Map SpanKind number to string\n */\nfunction mapKind(kind: SpanKind): string {\n const kindMap: Record<number, string> = {\n [SpanKind.INTERNAL]: 'INTERNAL',\n [SpanKind.SERVER]: 'SERVER',\n [SpanKind.CLIENT]: 'CLIENT',\n [SpanKind.PRODUCER]: 'PRODUCER',\n [SpanKind.CONSUMER]: 'CONSUMER',\n };\n return kindMap[kind] ?? 'UNKNOWN';\n}\n\n/**\n * Create a terminal span stream from a streaming processor\n *\n * @param processor - The streaming span processor to subscribe to\n * @returns Terminal span stream interface\n *\n * @example\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { createTerminalSpanStream } from 'autotel-terminal'\n *\n * const processor = new StreamingSpanProcessor(baseProcessor)\n * const stream = createTerminalSpanStream(processor)\n *\n * stream.onSpanEnd((event) => {\n * console.log('Span:', event.name, event.durationMs + 'ms')\n * })\n * ```\n */\nexport function createTerminalSpanStream(\n processor: StreamingSpanProcessor,\n): TerminalSpanStream {\n return {\n onSpanEnd(callback) {\n return processor.subscribe((span: ReadableSpan) => {\n const spanContext = span.spanContext();\n // parentSpanId is not part of the standard ReadableSpan interface\n // but some implementations (like Node.js SDK) include it\n const parentSpanId =\n 'parentSpanId' in span && typeof span.parentSpanId === 'string'\n ? span.parentSpanId\n : undefined;\n\n // Convert time tuples to milliseconds\n const startTime = timeToMs(span.startTime);\n const endTime = timeToMs(span.endTime);\n const durationMs = endTime - startTime;\n\n // Create terminal event\n const event: TerminalSpanEvent = {\n name: span.name,\n spanId: spanContext.spanId,\n traceId: spanContext.traceId,\n parentSpanId,\n startTime,\n endTime,\n durationMs,\n status: mapStatus(span.status.code),\n kind: mapKind(span.kind),\n attributes: span.attributes as Record<string, unknown>,\n };\n\n callback(event);\n });\n },\n };\n}\n","/**\n * Terminal Dashboard for Autotel\n *\n * Beautiful react-ink powered dashboard for viewing traces in real-time.\n *\n * @example Basic usage (auto-wire)\n * ```typescript\n * import { init } from 'autotel'\n * import { renderTerminal } from 'autotel-terminal'\n *\n * init({ service: 'my-app' })\n * renderTerminal() // Automatically creates stream from current tracer provider\n * ```\n *\n * @example With options\n * ```typescript\n * renderTerminal({\n * title: 'My App Traces',\n * showStats: true,\n * maxSpans: 200,\n * })\n * ```\n *\n * @example Manual stream (advanced)\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { createTerminalSpanStream } from 'autotel-terminal'\n * import { BatchSpanProcessor } from 'autotel/processors'\n *\n * const processor = new StreamingSpanProcessor(new BatchSpanProcessor(exporter))\n * const stream = createTerminalSpanStream(processor)\n * renderTerminal({}, stream)\n * ```\n *\n * @module autotel-terminal\n */\n\nimport React, { useEffect, useMemo, useState } from 'react';\nimport { render, Box, Text, useInput } from 'ink';\nimport type { TerminalSpanEvent, TerminalSpanStream } from './span-stream';\nimport { StreamingSpanProcessor } from './streaming-processor';\nimport { createTerminalSpanStream } from './span-stream';\nimport { getAutotelTracerProvider } from 'autotel/tracer-provider';\nimport type { TracerProvider } from '@opentelemetry/api';\n\n/**\n * Terminal dashboard options\n */\nexport interface TerminalOptions {\n /**\n * Dashboard title\n * @default 'Autotel Trace Inspector'\n */\n title?: string;\n\n /**\n * Show statistics bar (span count, error rate, avg duration)\n * @default true\n */\n showStats?: boolean;\n\n /**\n * Maximum number of spans to display\n * @default 100\n */\n maxSpans?: number;\n\n /**\n * Enable colors (auto-detect by default)\n * @default true if TTY\n */\n colors?: boolean;\n}\n\n// Helper for rendering duration\nfunction ms(n: number): string {\n if (n < 1000) return `${n.toFixed(0)}ms`;\n return `${(n / 1000).toFixed(2)}s`;\n}\n\n// Helper for truncating strings\nfunction truncate(s: string, width: number): string {\n if (s.length <= width) return s;\n return s.slice(0, Math.max(0, width - 1)) + '…';\n}\n\ninterface DashboardProps {\n title: string;\n showStats: boolean;\n maxSpans: number;\n colors: boolean;\n stream: TerminalSpanStream;\n}\n\nfunction Dashboard({\n title,\n showStats,\n maxSpans,\n colors,\n stream,\n}: DashboardProps): React.ReactElement {\n const [paused, setPaused] = useState(false);\n const [spans, setSpans] = useState<TerminalSpanEvent[]>([]);\n const [selected, setSelected] = useState(0);\n const [filterErrorsOnly, setFilterErrorsOnly] = useState(false);\n\n useEffect(() => {\n const unsubscribe = stream.onSpanEnd((span) => {\n if (paused) return;\n setSpans((prev) => {\n const next = [span, ...prev];\n return next.slice(0, maxSpans);\n });\n setSelected(0);\n });\n return unsubscribe;\n }, [stream, paused, maxSpans]);\n\n const filtered = useMemo(() => {\n const list = filterErrorsOnly\n ? spans.filter((s) => s.status === 'ERROR')\n : spans;\n return list;\n }, [spans, filterErrorsOnly]);\n\n const stats = useMemo(() => {\n const total = spans.length;\n const errors = spans.filter((s) => s.status === 'ERROR').length;\n const avg = total\n ? spans.reduce((a, s) => a + s.durationMs, 0) / total\n : 0;\n const p95 = total\n ? (() => {\n const sorted = spans.map((s) => s.durationMs).toSorted((a, b) => a - b);\n return sorted[Math.floor(sorted.length * 0.95)] ?? 0;\n })()\n : 0;\n return { total, errors, avg, p95 };\n }, [spans]);\n\n const current = filtered[selected];\n\n useInput((input, key) => {\n if (key.upArrow) setSelected((i) => Math.max(0, i - 1));\n if (key.downArrow)\n setSelected((i) => Math.min(filtered.length - 1, i + 1));\n\n if (input === 'p') setPaused((p) => !p);\n if (input === 'e') setFilterErrorsOnly((v) => !v);\n\n if (input === 'c') {\n setSpans([]);\n setSelected(0);\n }\n });\n\n const headerRight = paused ? '[Paused]' : '[Live]';\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"round\"\n padding={1}\n borderColor={colors ? 'cyan' : undefined}\n >\n {/* Header */}\n <Box justifyContent=\"space-between\" marginBottom={1}>\n <Text bold>🔭 {title}</Text>\n <Text color={paused ? 'yellow' : 'green'}>{headerRight}</Text>\n </Box>\n\n {/* Help / controls */}\n <Box marginBottom={1} flexDirection=\"row\" justifyContent=\"space-between\">\n <Text dimColor>\n ↑/↓ select • p pause • e errors-only • c clear • Ctrl+C exit\n </Text>\n <Text dimColor>\n showing {filtered.length}/{spans.length}\n </Text>\n </Box>\n\n {/* Main content: list + details */}\n <Box flexDirection=\"row\" gap={2}>\n {/* List */}\n <Box\n flexDirection=\"column\"\n width=\"55%\"\n borderStyle=\"single\"\n borderColor=\"gray\"\n paddingX={1}\n paddingY={0}\n >\n <Box marginTop={0} marginBottom={1}>\n <Text bold>Recent spans</Text>\n {filterErrorsOnly && <Text color=\"red\"> (errors only)</Text>}\n </Box>\n\n {filtered.length === 0 ? (\n <Text dimColor>No spans yet. Generate some traffic…</Text>\n ) : (\n filtered.slice(0, 20).map((s, i) => {\n const isSel = i === selected;\n const statusColor =\n s.status === 'ERROR'\n ? 'red'\n : s.durationMs > 500\n ? 'yellow'\n : 'green';\n\n return (\n <Box key={s.spanId} flexDirection=\"row\">\n <Text color={isSel ? 'cyan' : undefined}>\n {isSel ? '› ' : ' '}\n </Text>\n <Text color={colors ? statusColor : undefined}>\n {truncate(s.name, 26)}\n </Text>\n <Text dimColor> {ms(s.durationMs)}</Text>\n <Text dimColor> {truncate(s.traceId, 10)}</Text>\n </Box>\n );\n })\n )}\n </Box>\n\n {/* Details */}\n <Box\n flexDirection=\"column\"\n width=\"45%\"\n borderStyle=\"single\"\n borderColor=\"gray\"\n paddingX={1}\n paddingY={0}\n >\n <Box marginBottom={1}>\n <Text bold>Details</Text>\n </Box>\n\n {current ? (\n <>\n <Text>\n <Text dimColor>Name: </Text>\n <Text>{current.name}</Text>\n </Text>\n <Text>\n <Text dimColor>Status: </Text>\n <Text color={current.status === 'ERROR' ? 'red' : 'green'}>\n {current.status}\n </Text>\n </Text>\n <Text>\n <Text dimColor>Duration: </Text>\n <Text>{ms(current.durationMs)}</Text>\n </Text>\n <Text dimColor>Trace: {current.traceId}</Text>\n <Text dimColor>Span: {current.spanId}</Text>\n {current.parentSpanId && (\n <Text dimColor>Parent: {current.parentSpanId}</Text>\n )}\n {current.kind && <Text dimColor>Kind: {current.kind}</Text>}\n\n <Box marginTop={1} flexDirection=\"column\">\n <Text bold>Attributes</Text>\n {current.attributes &&\n Object.keys(current.attributes).length > 0 ? (\n Object.entries(current.attributes)\n .slice(0, 12)\n .map(([k, v]) => (\n <Text key={k} dimColor>\n {truncate(k, 22)}: {truncate(String(v), 34)}\n </Text>\n ))\n ) : (\n <Text dimColor>(none)</Text>\n )}\n </Box>\n </>\n ) : (\n <Text dimColor>Select a span to view details.</Text>\n )}\n </Box>\n </Box>\n\n {/* Stats bar */}\n {showStats && (\n <Box marginTop={1} borderStyle=\"single\" borderColor=\"gray\" paddingX={1}>\n <Text dimColor>\n Spans: {stats.total} | Errors: {stats.errors} | Avg: {ms(stats.avg)}{' '}\n | P95: {ms(stats.p95)}\n </Text>\n </Box>\n )}\n </Box>\n );\n}\n\n// Global streaming processor for auto-wiring\nlet globalStreamingProcessor: StreamingSpanProcessor | null = null;\n\n/**\n * Check if a TracerProvider has an addSpanProcessor method\n * (Node.js SDK providers have this, but API-level providers don't)\n */\nfunction canAddSpanProcessor(\n provider: TracerProvider,\n): provider is TracerProvider & {\n addSpanProcessor: (processor: unknown) => void;\n} {\n return (\n typeof provider === 'object' &&\n provider !== null &&\n 'addSpanProcessor' in provider &&\n typeof (provider as { addSpanProcessor?: unknown }).addSpanProcessor ===\n 'function'\n );\n}\n\n/**\n * Render the terminal dashboard\n *\n * Automatically wires up a streaming processor from the current tracer provider\n * if no stream is provided. Otherwise uses the provided stream.\n *\n * @param options - Dashboard configuration options\n * @param stream - Optional manual stream (for advanced use cases)\n *\n * @example Auto-wire (recommended)\n * ```typescript\n * import { init } from 'autotel'\n * import { renderTerminal } from 'autotel-terminal'\n *\n * init({ service: 'my-app' })\n * renderTerminal() // Automatically creates stream from current tracer provider\n * ```\n *\n * @example Manual stream\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { createTerminalSpanStream } from 'autotel-terminal'\n *\n * const processor = new StreamingSpanProcessor(baseProcessor)\n * const stream = createTerminalSpanStream(processor)\n * renderTerminal({}, stream)\n * ```\n */\nexport function renderTerminal(\n options: TerminalOptions = {},\n stream?: TerminalSpanStream,\n): void {\n const title = options.title ?? 'Autotel Trace Inspector';\n const showStats = options.showStats !== false;\n const maxSpans = options.maxSpans ?? 100;\n const colors = options.colors ?? Boolean(process.stdout.isTTY);\n\n // If stream provided, use it directly\n if (stream) {\n try {\n render(\n <Dashboard\n title={title}\n showStats={showStats}\n maxSpans={maxSpans}\n colors={colors}\n stream={stream}\n />,\n );\n } catch (error) {\n console.error('[autotel-terminal] Failed to render dashboard:', error);\n }\n return;\n }\n\n // Otherwise, auto-wire from current tracer provider\n let provider: TracerProvider;\n try {\n provider = getAutotelTracerProvider();\n } catch (error) {\n console.error(\n '[autotel-terminal] Failed to get tracer provider. Call init() first or provide a stream.',\n error,\n );\n return;\n }\n\n if (!provider) {\n console.error(\n '[autotel-terminal] No tracer provider found. Call init() first or provide a stream.',\n );\n return;\n }\n\n // Check if provider supports addSpanProcessor (Node.js SDK providers do)\n if (!canAddSpanProcessor(provider)) {\n console.error(\n '[autotel-terminal] TracerProvider does not support addSpanProcessor. Provide a stream manually.',\n );\n return;\n }\n\n // Create streaming processor that doesn't wrap anything (just streams)\n // This way it doesn't interfere with existing processors\n globalStreamingProcessor = new StreamingSpanProcessor(null);\n const terminalStream = createTerminalSpanStream(globalStreamingProcessor);\n\n // Add streaming processor to provider\n provider.addSpanProcessor(globalStreamingProcessor);\n\n try {\n render(\n <Dashboard\n title={title}\n showStats={showStats}\n maxSpans={maxSpans}\n colors={colors}\n stream={terminalStream}\n />,\n );\n } catch (error) {\n console.error('[autotel-terminal] Failed to render dashboard:', error);\n }\n}\n\n// Re-export types and utilities\nexport type { TerminalSpanEvent, TerminalSpanStream } from './span-stream';\nexport { StreamingSpanProcessor } from './streaming-processor';\nexport { createTerminalSpanStream } from './span-stream';\n\n// Re-export OpenTelemetry types for advanced users\nexport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nexport { SpanStatusCode, SpanKind } from 'autotel';\n\n// Re-export PrettyConsoleExporter for convenience\nexport {\n PrettyConsoleExporter,\n type PrettyConsoleExporterOptions,\n} from 'autotel/exporters';\n"]}
1
+ {"version":3,"sources":["../src/streaming-processor.ts","../src/span-stream.ts","../src/index.tsx"],"names":["SpanStatusCode","SpanKind","useState","useEffect","useMemo","useStdin","useInput","jsxs","Box","Text","jsx","Fragment","render","getAutotelTracerProvider"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Streaming Span Processor\n *\n * A span processor that emits ReadableSpan objects to subscribers.\n * This allows real-time streaming of spans to terminal dashboards and other consumers.\n *\n * @example Basic usage\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { BatchSpanProcessor } from 'autotel/processors'\n * import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\n *\n * const exporter = new OTLPTraceExporter({ url: 'http://localhost:4318/v1/traces' })\n * const baseProcessor = new BatchSpanProcessor(exporter)\n * const streamingProcessor = new StreamingSpanProcessor(baseProcessor)\n *\n * // Subscribe to spans\n * const unsubscribe = streamingProcessor.subscribe((span) => {\n * console.log('Span ended:', span.name)\n * })\n *\n * // Later, unsubscribe\n * unsubscribe()\n * ```\n */\n\nimport type {\n SpanProcessor,\n ReadableSpan,\n} from '@opentelemetry/sdk-trace-base';\nimport type { Context } from '@opentelemetry/api';\nimport type { Span } from '@opentelemetry/sdk-trace-base';\n\n/**\n * Span processor that wraps another processor and emits spans to subscribers\n *\n * This processor forwards all span operations to a wrapped processor while\n * also emitting completed spans to registered subscribers. This enables\n * real-time streaming of spans to terminal dashboards, loggers, or other\n * consumers without interfering with normal span processing.\n */\nexport class StreamingSpanProcessor implements SpanProcessor {\n private wrappedProcessor: SpanProcessor | null;\n private subscribers = new Set<(span: ReadableSpan) => void>();\n\n /**\n * Create a new streaming span processor\n *\n * @param wrappedProcessor - The processor to wrap and forward spans to.\n * If null, spans are only emitted to subscribers (no forwarding).\n */\n constructor(wrappedProcessor: SpanProcessor | null = null) {\n this.wrappedProcessor = wrappedProcessor;\n }\n\n onStart(span: Span, parentContext: Context): void {\n if (this.wrappedProcessor) {\n this.wrappedProcessor.onStart(span, parentContext);\n }\n }\n\n onEnd(span: ReadableSpan): void {\n // Emit to all subscribers first\n for (const subscriber of this.subscribers) {\n try {\n subscriber(span);\n } catch (error) {\n // Don't let subscriber errors break span processing\n console.error('[autotel-terminal] Subscriber error:', error);\n }\n }\n\n // Forward to wrapped processor\n if (this.wrappedProcessor) {\n this.wrappedProcessor.onEnd(span);\n }\n }\n\n /**\n * Subscribe to span end events\n *\n * @param callback - Function called when a span ends\n * @returns Unsubscribe function\n *\n * @example\n * ```typescript\n * const unsubscribe = processor.subscribe((span) => {\n * console.log('Span ended:', span.name)\n * })\n *\n * // Later, unsubscribe\n * unsubscribe()\n * ```\n */\n subscribe(callback: (span: ReadableSpan) => void): () => void {\n this.subscribers.add(callback);\n return () => this.subscribers.delete(callback);\n }\n\n async forceFlush(): Promise<void> {\n if (this.wrappedProcessor) {\n return this.wrappedProcessor.forceFlush();\n }\n }\n\n async shutdown(): Promise<void> {\n // Clear subscribers\n this.subscribers.clear();\n if (this.wrappedProcessor) {\n return this.wrappedProcessor.shutdown();\n }\n }\n}\n","/**\n * Terminal Span Stream\n *\n * Converts OpenTelemetry ReadableSpan objects to TerminalSpanEvent format\n * for consumption by the terminal dashboard.\n */\n\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport { SpanStatusCode, SpanKind } from 'autotel';\nimport type { StreamingSpanProcessor } from './streaming-processor';\n\n/**\n * Span event format for terminal dashboard consumption\n */\nexport interface TerminalSpanEvent {\n /** Span name */\n name: string;\n /** Span ID (hex string) */\n spanId: string;\n /** Trace ID (hex string) */\n traceId: string;\n /** Parent span ID (hex string, optional) */\n parentSpanId?: string;\n /** Start time in milliseconds since epoch */\n startTime: number;\n /** End time in milliseconds since epoch */\n endTime: number;\n /** Duration in milliseconds */\n durationMs: number;\n /** Span status */\n status: 'OK' | 'ERROR' | 'UNSET';\n /** Span kind (INTERNAL, SERVER, CLIENT, etc.) */\n kind?: string;\n /** Span attributes */\n attributes?: Record<string, unknown>;\n}\n\n/**\n * Stream interface for terminal dashboard\n */\nexport interface TerminalSpanStream {\n /**\n * Subscribe to span end events\n *\n * @param callback - Called when a span ends\n * @returns Unsubscribe function\n */\n onSpanEnd(callback: (event: TerminalSpanEvent) => void): () => void;\n}\n\n/**\n * Convert OpenTelemetry time tuple to milliseconds\n */\nfunction timeToMs([seconds, nanoseconds]: [number, number]): number {\n return seconds * 1000 + nanoseconds / 1_000_000;\n}\n\n/**\n * Map SpanStatusCode to string\n */\nfunction mapStatus(code: SpanStatusCode): 'OK' | 'ERROR' | 'UNSET' {\n if (code === SpanStatusCode.OK) {\n return 'OK';\n }\n if (code === SpanStatusCode.ERROR) {\n return 'ERROR';\n }\n return 'UNSET';\n}\n\n/**\n * Map SpanKind number to string\n */\nfunction mapKind(kind: SpanKind): string {\n const kindMap: Record<number, string> = {\n [SpanKind.INTERNAL]: 'INTERNAL',\n [SpanKind.SERVER]: 'SERVER',\n [SpanKind.CLIENT]: 'CLIENT',\n [SpanKind.PRODUCER]: 'PRODUCER',\n [SpanKind.CONSUMER]: 'CONSUMER',\n };\n return kindMap[kind] ?? 'UNKNOWN';\n}\n\n/**\n * Create a terminal span stream from a streaming processor\n *\n * @param processor - The streaming span processor to subscribe to\n * @returns Terminal span stream interface\n *\n * @example\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { createTerminalSpanStream } from 'autotel-terminal'\n *\n * const processor = new StreamingSpanProcessor(baseProcessor)\n * const stream = createTerminalSpanStream(processor)\n *\n * stream.onSpanEnd((event) => {\n * console.log('Span:', event.name, event.durationMs + 'ms')\n * })\n * ```\n */\nexport function createTerminalSpanStream(\n processor: StreamingSpanProcessor,\n): TerminalSpanStream {\n return {\n onSpanEnd(callback) {\n return processor.subscribe((span: ReadableSpan) => {\n const spanContext = span.spanContext();\n // parentSpanId is not part of the standard ReadableSpan interface\n // but some implementations (like Node.js SDK) include it\n const parentSpanId =\n 'parentSpanId' in span && typeof span.parentSpanId === 'string'\n ? span.parentSpanId\n : undefined;\n\n // Convert time tuples to milliseconds\n const startTime = timeToMs(span.startTime);\n const endTime = timeToMs(span.endTime);\n const durationMs = endTime - startTime;\n\n // Create terminal event\n const event: TerminalSpanEvent = {\n name: span.name,\n spanId: spanContext.spanId,\n traceId: spanContext.traceId,\n parentSpanId,\n startTime,\n endTime,\n durationMs,\n status: mapStatus(span.status.code),\n kind: mapKind(span.kind),\n attributes: span.attributes as Record<string, unknown>,\n };\n\n callback(event);\n });\n },\n };\n}\n","/**\n * Terminal Dashboard for Autotel\n *\n * Beautiful react-ink powered dashboard for viewing traces in real-time.\n *\n * @example Basic usage (auto-wire)\n * ```typescript\n * import { init } from 'autotel'\n * import { renderTerminal } from 'autotel-terminal'\n *\n * init({ service: 'my-app' })\n * renderTerminal() // Automatically creates stream from current tracer provider\n * ```\n *\n * @example With options\n * ```typescript\n * renderTerminal({\n * title: 'My App Traces',\n * showStats: true,\n * maxSpans: 200,\n * })\n * ```\n *\n * @example Manual stream (advanced)\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { createTerminalSpanStream } from 'autotel-terminal'\n * import { BatchSpanProcessor } from 'autotel/processors'\n *\n * const processor = new StreamingSpanProcessor(new BatchSpanProcessor(exporter))\n * const stream = createTerminalSpanStream(processor)\n * renderTerminal({}, stream)\n * ```\n *\n * @module autotel-terminal\n */\n\nimport React, { useEffect, useMemo, useState } from 'react';\nimport { render, Box, Text, useInput, useStdin } from 'ink';\nimport type { TerminalSpanEvent, TerminalSpanStream } from './span-stream';\nimport { StreamingSpanProcessor } from './streaming-processor';\nimport { createTerminalSpanStream } from './span-stream';\nimport { getAutotelTracerProvider } from 'autotel/tracer-provider';\nimport type { TracerProvider } from '@opentelemetry/api';\n\n/**\n * Terminal dashboard options\n */\nexport interface TerminalOptions {\n /**\n * Dashboard title\n * @default 'Autotel Trace Inspector'\n */\n title?: string;\n\n /**\n * Show statistics bar (span count, error rate, avg duration)\n * @default true\n */\n showStats?: boolean;\n\n /**\n * Maximum number of spans to display\n * @default 100\n */\n maxSpans?: number;\n\n /**\n * Enable colors (auto-detect by default)\n * @default true if TTY\n */\n colors?: boolean;\n}\n\n// Helper for rendering duration\nfunction ms(n: number): string {\n if (n < 1000) return `${n.toFixed(0)}ms`;\n return `${(n / 1000).toFixed(2)}s`;\n}\n\n// Helper for truncating strings\nfunction truncate(s: string, width: number): string {\n if (s.length <= width) return s;\n return s.slice(0, Math.max(0, width - 1)) + '…';\n}\n\ninterface DashboardProps {\n title: string;\n showStats: boolean;\n maxSpans: number;\n colors: boolean;\n stream: TerminalSpanStream;\n}\n\nfunction Dashboard({\n title,\n showStats,\n maxSpans,\n colors,\n stream,\n}: DashboardProps): React.ReactElement {\n const [paused, setPaused] = useState(false);\n const [spans, setSpans] = useState<TerminalSpanEvent[]>([]);\n const [selected, setSelected] = useState(0);\n const [filterErrorsOnly, setFilterErrorsOnly] = useState(false);\n\n useEffect(() => {\n const unsubscribe = stream.onSpanEnd((span) => {\n if (paused) return;\n setSpans((prev) => {\n const next = [span, ...prev];\n return next.slice(0, maxSpans);\n });\n setSelected(0);\n });\n return unsubscribe;\n }, [stream, paused, maxSpans]);\n\n const filtered = useMemo(() => {\n const list = filterErrorsOnly\n ? spans.filter((s) => s.status === 'ERROR')\n : spans;\n return list;\n }, [spans, filterErrorsOnly]);\n\n const stats = useMemo(() => {\n const total = spans.length;\n const errors = spans.filter((s) => s.status === 'ERROR').length;\n const avg = total\n ? spans.reduce((a, s) => a + s.durationMs, 0) / total\n : 0;\n const p95 = total\n ? (() => {\n const sorted = spans.map((s) => s.durationMs).toSorted((a, b) => a - b);\n return sorted[Math.floor(sorted.length * 0.95)] ?? 0;\n })()\n : 0;\n return { total, errors, avg, p95 };\n }, [spans]);\n\n const current = filtered[selected];\n\n // Check if raw mode is supported (needed for keyboard input)\n const { isRawModeSupported } = useStdin();\n\n useInput(\n (input, key) => {\n if (key.upArrow) setSelected((i) => Math.max(0, i - 1));\n if (key.downArrow)\n setSelected((i) => Math.min(filtered.length - 1, i + 1));\n\n if (input === 'p') setPaused((p) => !p);\n if (input === 'e') setFilterErrorsOnly((v) => !v);\n\n if (input === 'c') {\n setSpans([]);\n setSelected(0);\n }\n },\n { isActive: isRawModeSupported },\n );\n\n const headerRight = paused ? '[Paused]' : '[Live]';\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"round\"\n padding={1}\n borderColor={colors ? 'cyan' : undefined}\n >\n {/* Header */}\n <Box justifyContent=\"space-between\" marginBottom={1}>\n <Text key=\"title\" bold>🔭 {title}</Text>\n <Text key=\"status\" color={paused ? 'yellow' : 'green'}>{headerRight}</Text>\n </Box>\n\n {/* Help / controls */}\n <Box marginBottom={1} flexDirection=\"row\" justifyContent=\"space-between\">\n <Text key=\"controls\" dimColor>\n ↑/↓ select • p pause • e errors-only • c clear • Ctrl+C exit\n </Text>\n <Text key=\"count\" dimColor>\n showing {filtered.length}/{spans.length}\n </Text>\n </Box>\n\n {/* Main content: list + details */}\n <Box flexDirection=\"row\" gap={2}>\n {/* List */}\n <Box\n flexDirection=\"column\"\n width=\"55%\"\n borderStyle=\"single\"\n borderColor=\"gray\"\n paddingX={1}\n paddingY={0}\n >\n <Box marginTop={0} marginBottom={1}>\n <Text key=\"recent-spans-title\" bold>Recent spans</Text>\n {filterErrorsOnly && <Text key=\"errors-only-label\" color=\"red\"> (errors only)</Text>}\n </Box>\n\n {filtered.length === 0 ? (\n <Text dimColor>No spans yet. Generate some traffic…</Text>\n ) : (\n filtered.slice(0, 20).map((s, i) => {\n const isSel = i === selected;\n const statusColor =\n s.status === 'ERROR'\n ? 'red'\n : s.durationMs > 500\n ? 'yellow'\n : 'green';\n\n // Use compound key to ensure uniqueness (spanId + startTime)\n const uniqueKey = `${s.spanId}-${s.startTime}`;\n\n return (\n <Box key={uniqueKey} flexDirection=\"row\">\n <Text color={isSel ? 'cyan' : undefined}>\n {isSel ? '› ' : ' '}\n </Text>\n <Text color={colors ? statusColor : undefined}>\n {truncate(s.name, 26)}\n </Text>\n <Text dimColor> {ms(s.durationMs)}</Text>\n <Text dimColor> {truncate(s.traceId, 10)}</Text>\n </Box>\n );\n })\n )}\n </Box>\n\n {/* Details */}\n <Box\n flexDirection=\"column\"\n width=\"45%\"\n borderStyle=\"single\"\n borderColor=\"gray\"\n paddingX={1}\n paddingY={0}\n >\n <Box marginBottom={1}>\n <Text bold>Details</Text>\n </Box>\n\n {current ? (\n <>\n <Text>\n <Text dimColor>Name: </Text>\n <Text>{current.name}</Text>\n </Text>\n <Text>\n <Text dimColor>Status: </Text>\n <Text color={current.status === 'ERROR' ? 'red' : 'green'}>\n {current.status}\n </Text>\n </Text>\n <Text>\n <Text dimColor>Duration: </Text>\n <Text>{ms(current.durationMs)}</Text>\n </Text>\n <Text dimColor>Trace: {current.traceId}</Text>\n <Text dimColor>Span: {current.spanId}</Text>\n {current.parentSpanId && (\n <Text dimColor>Parent: {current.parentSpanId}</Text>\n )}\n {current.kind && <Text dimColor>Kind: {current.kind}</Text>}\n\n <Box marginTop={1} flexDirection=\"column\">\n <Text bold>Attributes</Text>\n {current.attributes &&\n Object.keys(current.attributes).length > 0 ? (\n Object.entries(current.attributes)\n .slice(0, 12)\n .map(([k, v]) => (\n <Text key={k} dimColor>\n {truncate(k, 22)}: {truncate(String(v), 34)}\n </Text>\n ))\n ) : (\n <Text dimColor>(none)</Text>\n )}\n </Box>\n </>\n ) : (\n <Text dimColor>Select a span to view details.</Text>\n )}\n </Box>\n </Box>\n\n {/* Stats bar */}\n {showStats && (\n <Box marginTop={1} borderStyle=\"single\" borderColor=\"gray\" paddingX={1}>\n <Text dimColor>\n Spans: {stats.total} | Errors: {stats.errors} | Avg: {ms(stats.avg)}{' '}\n | P95: {ms(stats.p95)}\n </Text>\n </Box>\n )}\n </Box>\n );\n}\n\n// Global streaming processor for auto-wiring\nlet globalStreamingProcessor: StreamingSpanProcessor | null = null;\n\n/**\n * Check if a TracerProvider has an addSpanProcessor method\n * (Node.js SDK providers have this, but API-level providers don't)\n */\nfunction canAddSpanProcessor(\n provider: TracerProvider,\n): provider is TracerProvider & {\n addSpanProcessor: (processor: unknown) => void;\n} {\n return (\n typeof provider === 'object' &&\n provider !== null &&\n 'addSpanProcessor' in provider &&\n typeof (provider as { addSpanProcessor?: unknown }).addSpanProcessor ===\n 'function'\n );\n}\n\n/**\n * Render the terminal dashboard\n *\n * Automatically wires up a streaming processor from the current tracer provider\n * if no stream is provided. Otherwise uses the provided stream.\n *\n * @param options - Dashboard configuration options\n * @param stream - Optional manual stream (for advanced use cases)\n *\n * @example Auto-wire (recommended)\n * ```typescript\n * import { init } from 'autotel'\n * import { renderTerminal } from 'autotel-terminal'\n *\n * init({ service: 'my-app' })\n * renderTerminal() // Automatically creates stream from current tracer provider\n * ```\n *\n * @example Manual stream\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { createTerminalSpanStream } from 'autotel-terminal'\n *\n * const processor = new StreamingSpanProcessor(baseProcessor)\n * const stream = createTerminalSpanStream(processor)\n * renderTerminal({}, stream)\n * ```\n */\nexport function renderTerminal(\n options: TerminalOptions = {},\n stream?: TerminalSpanStream,\n): void {\n const title = options.title ?? 'Autotel Trace Inspector';\n const showStats = options.showStats !== false;\n const maxSpans = options.maxSpans ?? 100;\n const colors = options.colors ?? Boolean(process.stdout.isTTY);\n\n // Check if stdin supports raw mode (needed for keyboard input)\n // If not, we disable stdin to prevent Ink from throwing an error\n const stdinOption = process.stdin.isTTY ? process.stdin : undefined;\n\n // If stream provided, use it directly\n if (stream) {\n try {\n render(\n <Dashboard\n title={title}\n showStats={showStats}\n maxSpans={maxSpans}\n colors={colors}\n stream={stream}\n />,\n { stdin: stdinOption },\n );\n } catch (error) {\n console.error('[autotel-terminal] Failed to render dashboard:', error);\n }\n return;\n }\n\n // Otherwise, auto-wire from current tracer provider\n let provider: TracerProvider;\n try {\n provider = getAutotelTracerProvider();\n } catch (error) {\n console.error(\n '[autotel-terminal] Failed to get tracer provider. Call init() first or provide a stream.',\n error,\n );\n return;\n }\n\n if (!provider) {\n console.error(\n '[autotel-terminal] No tracer provider found. Call init() first or provide a stream.',\n );\n return;\n }\n\n // Check if provider supports addSpanProcessor (Node.js SDK providers do)\n if (!canAddSpanProcessor(provider)) {\n console.error(\n '[autotel-terminal] TracerProvider does not support addSpanProcessor. Provide a stream manually.',\n );\n return;\n }\n\n // Create streaming processor that doesn't wrap anything (just streams)\n // This way it doesn't interfere with existing processors\n globalStreamingProcessor = new StreamingSpanProcessor(null);\n const terminalStream = createTerminalSpanStream(globalStreamingProcessor);\n\n // Add streaming processor to provider\n provider.addSpanProcessor(globalStreamingProcessor);\n\n try {\n render(\n <Dashboard\n title={title}\n showStats={showStats}\n maxSpans={maxSpans}\n colors={colors}\n stream={terminalStream}\n />,\n { stdin: stdinOption },\n );\n } catch (error) {\n console.error('[autotel-terminal] Failed to render dashboard:', error);\n }\n}\n\n// Re-export types and utilities\nexport type { TerminalSpanEvent, TerminalSpanStream } from './span-stream';\nexport { StreamingSpanProcessor } from './streaming-processor';\nexport { createTerminalSpanStream } from './span-stream';\n\n// Re-export OpenTelemetry types for advanced users\nexport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nexport { SpanStatusCode, SpanKind } from 'autotel';\n\n// Re-export PrettyConsoleExporter for convenience\nexport {\n PrettyConsoleExporter,\n type PrettyConsoleExporterOptions,\n} from 'autotel/exporters';\n"]}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useState, useEffect, useMemo } from 'react';
2
- import { render, useInput, Box, Text } from 'ink';
2
+ import { render, useStdin, useInput, Box, Text } from 'ink';
3
3
  import { SpanKind, SpanStatusCode } from 'autotel';
4
4
  export { SpanKind, SpanStatusCode } from 'autotel';
5
5
  import { getAutotelTracerProvider } from 'autotel/tracer-provider';
@@ -163,17 +163,21 @@ function Dashboard({
163
163
  return { total, errors, avg, p95 };
164
164
  }, [spans]);
165
165
  const current = filtered[selected];
166
- useInput((input, key) => {
167
- if (key.upArrow) setSelected((i) => Math.max(0, i - 1));
168
- if (key.downArrow)
169
- setSelected((i) => Math.min(filtered.length - 1, i + 1));
170
- if (input === "p") setPaused((p) => !p);
171
- if (input === "e") setFilterErrorsOnly((v) => !v);
172
- if (input === "c") {
173
- setSpans([]);
174
- setSelected(0);
175
- }
176
- });
166
+ const { isRawModeSupported } = useStdin();
167
+ useInput(
168
+ (input, key) => {
169
+ if (key.upArrow) setSelected((i) => Math.max(0, i - 1));
170
+ if (key.downArrow)
171
+ setSelected((i) => Math.min(filtered.length - 1, i + 1));
172
+ if (input === "p") setPaused((p) => !p);
173
+ if (input === "e") setFilterErrorsOnly((v) => !v);
174
+ if (input === "c") {
175
+ setSpans([]);
176
+ setSelected(0);
177
+ }
178
+ },
179
+ { isActive: isRawModeSupported }
180
+ );
177
181
  const headerRight = paused ? "[Paused]" : "[Live]";
178
182
  return /* @__PURE__ */ jsxs(
179
183
  Box,
@@ -187,17 +191,17 @@ function Dashboard({
187
191
  /* @__PURE__ */ jsxs(Text, { bold: true, children: [
188
192
  "\u{1F52D} ",
189
193
  title
190
- ] }),
191
- /* @__PURE__ */ jsx(Text, { color: paused ? "yellow" : "green", children: headerRight })
194
+ ] }, "title"),
195
+ /* @__PURE__ */ jsx(Text, { color: paused ? "yellow" : "green", children: headerRight }, "status")
192
196
  ] }),
193
197
  /* @__PURE__ */ jsxs(Box, { marginBottom: 1, flexDirection: "row", justifyContent: "space-between", children: [
194
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 select \u2022 p pause \u2022 e errors-only \u2022 c clear \u2022 Ctrl+C exit" }),
198
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 select \u2022 p pause \u2022 e errors-only \u2022 c clear \u2022 Ctrl+C exit" }, "controls"),
195
199
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
196
200
  "showing ",
197
201
  filtered.length,
198
202
  "/",
199
203
  spans.length
200
- ] })
204
+ ] }, "count")
201
205
  ] }),
202
206
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
203
207
  /* @__PURE__ */ jsxs(
@@ -211,12 +215,13 @@ function Dashboard({
211
215
  paddingY: 0,
212
216
  children: [
213
217
  /* @__PURE__ */ jsxs(Box, { marginTop: 0, marginBottom: 1, children: [
214
- /* @__PURE__ */ jsx(Text, { bold: true, children: "Recent spans" }),
215
- filterErrorsOnly && /* @__PURE__ */ jsx(Text, { color: "red", children: " (errors only)" })
218
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Recent spans" }, "recent-spans-title"),
219
+ filterErrorsOnly && /* @__PURE__ */ jsx(Text, { color: "red", children: " (errors only)" }, "errors-only-label")
216
220
  ] }),
217
221
  filtered.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No spans yet. Generate some traffic\u2026" }) : filtered.slice(0, 20).map((s, i) => {
218
222
  const isSel = i === selected;
219
223
  const statusColor = s.status === "ERROR" ? "red" : s.durationMs > 500 ? "yellow" : "green";
224
+ const uniqueKey = `${s.spanId}-${s.startTime}`;
220
225
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
221
226
  /* @__PURE__ */ jsx(Text, { color: isSel ? "cyan" : void 0, children: isSel ? "\u203A " : " " }),
222
227
  /* @__PURE__ */ jsx(Text, { color: colors ? statusColor : void 0, children: truncate(s.name, 26) }),
@@ -228,7 +233,7 @@ function Dashboard({
228
233
  " ",
229
234
  truncate(s.traceId, 10)
230
235
  ] })
231
- ] }, s.spanId);
236
+ ] }, uniqueKey);
232
237
  })
233
238
  ]
234
239
  }
@@ -310,6 +315,7 @@ function renderTerminal(options = {}, stream) {
310
315
  const showStats = options.showStats !== false;
311
316
  const maxSpans = options.maxSpans ?? 100;
312
317
  const colors = options.colors ?? Boolean(process.stdout.isTTY);
318
+ const stdinOption = process.stdin.isTTY ? process.stdin : void 0;
313
319
  if (stream) {
314
320
  try {
315
321
  render(
@@ -322,7 +328,8 @@ function renderTerminal(options = {}, stream) {
322
328
  colors,
323
329
  stream
324
330
  }
325
- )
331
+ ),
332
+ { stdin: stdinOption }
326
333
  );
327
334
  } catch (error) {
328
335
  console.error("[autotel-terminal] Failed to render dashboard:", error);
@@ -365,7 +372,8 @@ function renderTerminal(options = {}, stream) {
365
372
  colors,
366
373
  stream: terminalStream
367
374
  }
368
- )
375
+ ),
376
+ { stdin: stdinOption }
369
377
  );
370
378
  } catch (error) {
371
379
  console.error("[autotel-terminal] Failed to render dashboard:", error);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/streaming-processor.ts","../src/span-stream.ts","../src/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Streaming Span Processor\n *\n * A span processor that emits ReadableSpan objects to subscribers.\n * This allows real-time streaming of spans to terminal dashboards and other consumers.\n *\n * @example Basic usage\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { BatchSpanProcessor } from 'autotel/processors'\n * import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\n *\n * const exporter = new OTLPTraceExporter({ url: 'http://localhost:4318/v1/traces' })\n * const baseProcessor = new BatchSpanProcessor(exporter)\n * const streamingProcessor = new StreamingSpanProcessor(baseProcessor)\n *\n * // Subscribe to spans\n * const unsubscribe = streamingProcessor.subscribe((span) => {\n * console.log('Span ended:', span.name)\n * })\n *\n * // Later, unsubscribe\n * unsubscribe()\n * ```\n */\n\nimport type {\n SpanProcessor,\n ReadableSpan,\n} from '@opentelemetry/sdk-trace-base';\nimport type { Context } from '@opentelemetry/api';\nimport type { Span } from '@opentelemetry/sdk-trace-base';\n\n/**\n * Span processor that wraps another processor and emits spans to subscribers\n *\n * This processor forwards all span operations to a wrapped processor while\n * also emitting completed spans to registered subscribers. This enables\n * real-time streaming of spans to terminal dashboards, loggers, or other\n * consumers without interfering with normal span processing.\n */\nexport class StreamingSpanProcessor implements SpanProcessor {\n private wrappedProcessor: SpanProcessor | null;\n private subscribers = new Set<(span: ReadableSpan) => void>();\n\n /**\n * Create a new streaming span processor\n *\n * @param wrappedProcessor - The processor to wrap and forward spans to.\n * If null, spans are only emitted to subscribers (no forwarding).\n */\n constructor(wrappedProcessor: SpanProcessor | null = null) {\n this.wrappedProcessor = wrappedProcessor;\n }\n\n onStart(span: Span, parentContext: Context): void {\n if (this.wrappedProcessor) {\n this.wrappedProcessor.onStart(span, parentContext);\n }\n }\n\n onEnd(span: ReadableSpan): void {\n // Emit to all subscribers first\n for (const subscriber of this.subscribers) {\n try {\n subscriber(span);\n } catch (error) {\n // Don't let subscriber errors break span processing\n console.error('[autotel-terminal] Subscriber error:', error);\n }\n }\n\n // Forward to wrapped processor\n if (this.wrappedProcessor) {\n this.wrappedProcessor.onEnd(span);\n }\n }\n\n /**\n * Subscribe to span end events\n *\n * @param callback - Function called when a span ends\n * @returns Unsubscribe function\n *\n * @example\n * ```typescript\n * const unsubscribe = processor.subscribe((span) => {\n * console.log('Span ended:', span.name)\n * })\n *\n * // Later, unsubscribe\n * unsubscribe()\n * ```\n */\n subscribe(callback: (span: ReadableSpan) => void): () => void {\n this.subscribers.add(callback);\n return () => this.subscribers.delete(callback);\n }\n\n async forceFlush(): Promise<void> {\n if (this.wrappedProcessor) {\n return this.wrappedProcessor.forceFlush();\n }\n }\n\n async shutdown(): Promise<void> {\n // Clear subscribers\n this.subscribers.clear();\n if (this.wrappedProcessor) {\n return this.wrappedProcessor.shutdown();\n }\n }\n}\n","/**\n * Terminal Span Stream\n *\n * Converts OpenTelemetry ReadableSpan objects to TerminalSpanEvent format\n * for consumption by the terminal dashboard.\n */\n\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport { SpanStatusCode, SpanKind } from 'autotel';\nimport type { StreamingSpanProcessor } from './streaming-processor';\n\n/**\n * Span event format for terminal dashboard consumption\n */\nexport interface TerminalSpanEvent {\n /** Span name */\n name: string;\n /** Span ID (hex string) */\n spanId: string;\n /** Trace ID (hex string) */\n traceId: string;\n /** Parent span ID (hex string, optional) */\n parentSpanId?: string;\n /** Start time in milliseconds since epoch */\n startTime: number;\n /** End time in milliseconds since epoch */\n endTime: number;\n /** Duration in milliseconds */\n durationMs: number;\n /** Span status */\n status: 'OK' | 'ERROR' | 'UNSET';\n /** Span kind (INTERNAL, SERVER, CLIENT, etc.) */\n kind?: string;\n /** Span attributes */\n attributes?: Record<string, unknown>;\n}\n\n/**\n * Stream interface for terminal dashboard\n */\nexport interface TerminalSpanStream {\n /**\n * Subscribe to span end events\n *\n * @param callback - Called when a span ends\n * @returns Unsubscribe function\n */\n onSpanEnd(callback: (event: TerminalSpanEvent) => void): () => void;\n}\n\n/**\n * Convert OpenTelemetry time tuple to milliseconds\n */\nfunction timeToMs([seconds, nanoseconds]: [number, number]): number {\n return seconds * 1000 + nanoseconds / 1_000_000;\n}\n\n/**\n * Map SpanStatusCode to string\n */\nfunction mapStatus(code: SpanStatusCode): 'OK' | 'ERROR' | 'UNSET' {\n if (code === SpanStatusCode.OK) {\n return 'OK';\n }\n if (code === SpanStatusCode.ERROR) {\n return 'ERROR';\n }\n return 'UNSET';\n}\n\n/**\n * Map SpanKind number to string\n */\nfunction mapKind(kind: SpanKind): string {\n const kindMap: Record<number, string> = {\n [SpanKind.INTERNAL]: 'INTERNAL',\n [SpanKind.SERVER]: 'SERVER',\n [SpanKind.CLIENT]: 'CLIENT',\n [SpanKind.PRODUCER]: 'PRODUCER',\n [SpanKind.CONSUMER]: 'CONSUMER',\n };\n return kindMap[kind] ?? 'UNKNOWN';\n}\n\n/**\n * Create a terminal span stream from a streaming processor\n *\n * @param processor - The streaming span processor to subscribe to\n * @returns Terminal span stream interface\n *\n * @example\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { createTerminalSpanStream } from 'autotel-terminal'\n *\n * const processor = new StreamingSpanProcessor(baseProcessor)\n * const stream = createTerminalSpanStream(processor)\n *\n * stream.onSpanEnd((event) => {\n * console.log('Span:', event.name, event.durationMs + 'ms')\n * })\n * ```\n */\nexport function createTerminalSpanStream(\n processor: StreamingSpanProcessor,\n): TerminalSpanStream {\n return {\n onSpanEnd(callback) {\n return processor.subscribe((span: ReadableSpan) => {\n const spanContext = span.spanContext();\n // parentSpanId is not part of the standard ReadableSpan interface\n // but some implementations (like Node.js SDK) include it\n const parentSpanId =\n 'parentSpanId' in span && typeof span.parentSpanId === 'string'\n ? span.parentSpanId\n : undefined;\n\n // Convert time tuples to milliseconds\n const startTime = timeToMs(span.startTime);\n const endTime = timeToMs(span.endTime);\n const durationMs = endTime - startTime;\n\n // Create terminal event\n const event: TerminalSpanEvent = {\n name: span.name,\n spanId: spanContext.spanId,\n traceId: spanContext.traceId,\n parentSpanId,\n startTime,\n endTime,\n durationMs,\n status: mapStatus(span.status.code),\n kind: mapKind(span.kind),\n attributes: span.attributes as Record<string, unknown>,\n };\n\n callback(event);\n });\n },\n };\n}\n","/**\n * Terminal Dashboard for Autotel\n *\n * Beautiful react-ink powered dashboard for viewing traces in real-time.\n *\n * @example Basic usage (auto-wire)\n * ```typescript\n * import { init } from 'autotel'\n * import { renderTerminal } from 'autotel-terminal'\n *\n * init({ service: 'my-app' })\n * renderTerminal() // Automatically creates stream from current tracer provider\n * ```\n *\n * @example With options\n * ```typescript\n * renderTerminal({\n * title: 'My App Traces',\n * showStats: true,\n * maxSpans: 200,\n * })\n * ```\n *\n * @example Manual stream (advanced)\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { createTerminalSpanStream } from 'autotel-terminal'\n * import { BatchSpanProcessor } from 'autotel/processors'\n *\n * const processor = new StreamingSpanProcessor(new BatchSpanProcessor(exporter))\n * const stream = createTerminalSpanStream(processor)\n * renderTerminal({}, stream)\n * ```\n *\n * @module autotel-terminal\n */\n\nimport React, { useEffect, useMemo, useState } from 'react';\nimport { render, Box, Text, useInput } from 'ink';\nimport type { TerminalSpanEvent, TerminalSpanStream } from './span-stream';\nimport { StreamingSpanProcessor } from './streaming-processor';\nimport { createTerminalSpanStream } from './span-stream';\nimport { getAutotelTracerProvider } from 'autotel/tracer-provider';\nimport type { TracerProvider } from '@opentelemetry/api';\n\n/**\n * Terminal dashboard options\n */\nexport interface TerminalOptions {\n /**\n * Dashboard title\n * @default 'Autotel Trace Inspector'\n */\n title?: string;\n\n /**\n * Show statistics bar (span count, error rate, avg duration)\n * @default true\n */\n showStats?: boolean;\n\n /**\n * Maximum number of spans to display\n * @default 100\n */\n maxSpans?: number;\n\n /**\n * Enable colors (auto-detect by default)\n * @default true if TTY\n */\n colors?: boolean;\n}\n\n// Helper for rendering duration\nfunction ms(n: number): string {\n if (n < 1000) return `${n.toFixed(0)}ms`;\n return `${(n / 1000).toFixed(2)}s`;\n}\n\n// Helper for truncating strings\nfunction truncate(s: string, width: number): string {\n if (s.length <= width) return s;\n return s.slice(0, Math.max(0, width - 1)) + '…';\n}\n\ninterface DashboardProps {\n title: string;\n showStats: boolean;\n maxSpans: number;\n colors: boolean;\n stream: TerminalSpanStream;\n}\n\nfunction Dashboard({\n title,\n showStats,\n maxSpans,\n colors,\n stream,\n}: DashboardProps): React.ReactElement {\n const [paused, setPaused] = useState(false);\n const [spans, setSpans] = useState<TerminalSpanEvent[]>([]);\n const [selected, setSelected] = useState(0);\n const [filterErrorsOnly, setFilterErrorsOnly] = useState(false);\n\n useEffect(() => {\n const unsubscribe = stream.onSpanEnd((span) => {\n if (paused) return;\n setSpans((prev) => {\n const next = [span, ...prev];\n return next.slice(0, maxSpans);\n });\n setSelected(0);\n });\n return unsubscribe;\n }, [stream, paused, maxSpans]);\n\n const filtered = useMemo(() => {\n const list = filterErrorsOnly\n ? spans.filter((s) => s.status === 'ERROR')\n : spans;\n return list;\n }, [spans, filterErrorsOnly]);\n\n const stats = useMemo(() => {\n const total = spans.length;\n const errors = spans.filter((s) => s.status === 'ERROR').length;\n const avg = total\n ? spans.reduce((a, s) => a + s.durationMs, 0) / total\n : 0;\n const p95 = total\n ? (() => {\n const sorted = spans.map((s) => s.durationMs).toSorted((a, b) => a - b);\n return sorted[Math.floor(sorted.length * 0.95)] ?? 0;\n })()\n : 0;\n return { total, errors, avg, p95 };\n }, [spans]);\n\n const current = filtered[selected];\n\n useInput((input, key) => {\n if (key.upArrow) setSelected((i) => Math.max(0, i - 1));\n if (key.downArrow)\n setSelected((i) => Math.min(filtered.length - 1, i + 1));\n\n if (input === 'p') setPaused((p) => !p);\n if (input === 'e') setFilterErrorsOnly((v) => !v);\n\n if (input === 'c') {\n setSpans([]);\n setSelected(0);\n }\n });\n\n const headerRight = paused ? '[Paused]' : '[Live]';\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"round\"\n padding={1}\n borderColor={colors ? 'cyan' : undefined}\n >\n {/* Header */}\n <Box justifyContent=\"space-between\" marginBottom={1}>\n <Text bold>🔭 {title}</Text>\n <Text color={paused ? 'yellow' : 'green'}>{headerRight}</Text>\n </Box>\n\n {/* Help / controls */}\n <Box marginBottom={1} flexDirection=\"row\" justifyContent=\"space-between\">\n <Text dimColor>\n ↑/↓ select • p pause • e errors-only • c clear • Ctrl+C exit\n </Text>\n <Text dimColor>\n showing {filtered.length}/{spans.length}\n </Text>\n </Box>\n\n {/* Main content: list + details */}\n <Box flexDirection=\"row\" gap={2}>\n {/* List */}\n <Box\n flexDirection=\"column\"\n width=\"55%\"\n borderStyle=\"single\"\n borderColor=\"gray\"\n paddingX={1}\n paddingY={0}\n >\n <Box marginTop={0} marginBottom={1}>\n <Text bold>Recent spans</Text>\n {filterErrorsOnly && <Text color=\"red\"> (errors only)</Text>}\n </Box>\n\n {filtered.length === 0 ? (\n <Text dimColor>No spans yet. Generate some traffic…</Text>\n ) : (\n filtered.slice(0, 20).map((s, i) => {\n const isSel = i === selected;\n const statusColor =\n s.status === 'ERROR'\n ? 'red'\n : s.durationMs > 500\n ? 'yellow'\n : 'green';\n\n return (\n <Box key={s.spanId} flexDirection=\"row\">\n <Text color={isSel ? 'cyan' : undefined}>\n {isSel ? '› ' : ' '}\n </Text>\n <Text color={colors ? statusColor : undefined}>\n {truncate(s.name, 26)}\n </Text>\n <Text dimColor> {ms(s.durationMs)}</Text>\n <Text dimColor> {truncate(s.traceId, 10)}</Text>\n </Box>\n );\n })\n )}\n </Box>\n\n {/* Details */}\n <Box\n flexDirection=\"column\"\n width=\"45%\"\n borderStyle=\"single\"\n borderColor=\"gray\"\n paddingX={1}\n paddingY={0}\n >\n <Box marginBottom={1}>\n <Text bold>Details</Text>\n </Box>\n\n {current ? (\n <>\n <Text>\n <Text dimColor>Name: </Text>\n <Text>{current.name}</Text>\n </Text>\n <Text>\n <Text dimColor>Status: </Text>\n <Text color={current.status === 'ERROR' ? 'red' : 'green'}>\n {current.status}\n </Text>\n </Text>\n <Text>\n <Text dimColor>Duration: </Text>\n <Text>{ms(current.durationMs)}</Text>\n </Text>\n <Text dimColor>Trace: {current.traceId}</Text>\n <Text dimColor>Span: {current.spanId}</Text>\n {current.parentSpanId && (\n <Text dimColor>Parent: {current.parentSpanId}</Text>\n )}\n {current.kind && <Text dimColor>Kind: {current.kind}</Text>}\n\n <Box marginTop={1} flexDirection=\"column\">\n <Text bold>Attributes</Text>\n {current.attributes &&\n Object.keys(current.attributes).length > 0 ? (\n Object.entries(current.attributes)\n .slice(0, 12)\n .map(([k, v]) => (\n <Text key={k} dimColor>\n {truncate(k, 22)}: {truncate(String(v), 34)}\n </Text>\n ))\n ) : (\n <Text dimColor>(none)</Text>\n )}\n </Box>\n </>\n ) : (\n <Text dimColor>Select a span to view details.</Text>\n )}\n </Box>\n </Box>\n\n {/* Stats bar */}\n {showStats && (\n <Box marginTop={1} borderStyle=\"single\" borderColor=\"gray\" paddingX={1}>\n <Text dimColor>\n Spans: {stats.total} | Errors: {stats.errors} | Avg: {ms(stats.avg)}{' '}\n | P95: {ms(stats.p95)}\n </Text>\n </Box>\n )}\n </Box>\n );\n}\n\n// Global streaming processor for auto-wiring\nlet globalStreamingProcessor: StreamingSpanProcessor | null = null;\n\n/**\n * Check if a TracerProvider has an addSpanProcessor method\n * (Node.js SDK providers have this, but API-level providers don't)\n */\nfunction canAddSpanProcessor(\n provider: TracerProvider,\n): provider is TracerProvider & {\n addSpanProcessor: (processor: unknown) => void;\n} {\n return (\n typeof provider === 'object' &&\n provider !== null &&\n 'addSpanProcessor' in provider &&\n typeof (provider as { addSpanProcessor?: unknown }).addSpanProcessor ===\n 'function'\n );\n}\n\n/**\n * Render the terminal dashboard\n *\n * Automatically wires up a streaming processor from the current tracer provider\n * if no stream is provided. Otherwise uses the provided stream.\n *\n * @param options - Dashboard configuration options\n * @param stream - Optional manual stream (for advanced use cases)\n *\n * @example Auto-wire (recommended)\n * ```typescript\n * import { init } from 'autotel'\n * import { renderTerminal } from 'autotel-terminal'\n *\n * init({ service: 'my-app' })\n * renderTerminal() // Automatically creates stream from current tracer provider\n * ```\n *\n * @example Manual stream\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { createTerminalSpanStream } from 'autotel-terminal'\n *\n * const processor = new StreamingSpanProcessor(baseProcessor)\n * const stream = createTerminalSpanStream(processor)\n * renderTerminal({}, stream)\n * ```\n */\nexport function renderTerminal(\n options: TerminalOptions = {},\n stream?: TerminalSpanStream,\n): void {\n const title = options.title ?? 'Autotel Trace Inspector';\n const showStats = options.showStats !== false;\n const maxSpans = options.maxSpans ?? 100;\n const colors = options.colors ?? Boolean(process.stdout.isTTY);\n\n // If stream provided, use it directly\n if (stream) {\n try {\n render(\n <Dashboard\n title={title}\n showStats={showStats}\n maxSpans={maxSpans}\n colors={colors}\n stream={stream}\n />,\n );\n } catch (error) {\n console.error('[autotel-terminal] Failed to render dashboard:', error);\n }\n return;\n }\n\n // Otherwise, auto-wire from current tracer provider\n let provider: TracerProvider;\n try {\n provider = getAutotelTracerProvider();\n } catch (error) {\n console.error(\n '[autotel-terminal] Failed to get tracer provider. Call init() first or provide a stream.',\n error,\n );\n return;\n }\n\n if (!provider) {\n console.error(\n '[autotel-terminal] No tracer provider found. Call init() first or provide a stream.',\n );\n return;\n }\n\n // Check if provider supports addSpanProcessor (Node.js SDK providers do)\n if (!canAddSpanProcessor(provider)) {\n console.error(\n '[autotel-terminal] TracerProvider does not support addSpanProcessor. Provide a stream manually.',\n );\n return;\n }\n\n // Create streaming processor that doesn't wrap anything (just streams)\n // This way it doesn't interfere with existing processors\n globalStreamingProcessor = new StreamingSpanProcessor(null);\n const terminalStream = createTerminalSpanStream(globalStreamingProcessor);\n\n // Add streaming processor to provider\n provider.addSpanProcessor(globalStreamingProcessor);\n\n try {\n render(\n <Dashboard\n title={title}\n showStats={showStats}\n maxSpans={maxSpans}\n colors={colors}\n stream={terminalStream}\n />,\n );\n } catch (error) {\n console.error('[autotel-terminal] Failed to render dashboard:', error);\n }\n}\n\n// Re-export types and utilities\nexport type { TerminalSpanEvent, TerminalSpanStream } from './span-stream';\nexport { StreamingSpanProcessor } from './streaming-processor';\nexport { createTerminalSpanStream } from './span-stream';\n\n// Re-export OpenTelemetry types for advanced users\nexport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nexport { SpanStatusCode, SpanKind } from 'autotel';\n\n// Re-export PrettyConsoleExporter for convenience\nexport {\n PrettyConsoleExporter,\n type PrettyConsoleExporterOptions,\n} from 'autotel/exporters';\n"]}
1
+ {"version":3,"sources":["../src/streaming-processor.ts","../src/span-stream.ts","../src/index.tsx"],"names":[],"mappings":";;;;;;;;;;;AAyCO,IAAM,yBAAN,MAAsD;AAAA,EACnD,gBAAA;AAAA,EACA,WAAA,uBAAkB,GAAA,EAAkC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ5D,WAAA,CAAY,mBAAyC,IAAA,EAAM;AACzD,IAAA,IAAA,CAAK,gBAAA,GAAmB,gBAAA;AAAA,EAC1B;AAAA,EAEA,OAAA,CAAQ,MAAY,aAAA,EAA8B;AAChD,IAAA,IAAI,KAAK,gBAAA,EAAkB;AACzB,MAAA,IAAA,CAAK,gBAAA,CAAiB,OAAA,CAAQ,IAAA,EAAM,aAAa,CAAA;AAAA,IACnD;AAAA,EACF;AAAA,EAEA,MAAM,IAAA,EAA0B;AAE9B,IAAA,KAAA,MAAW,UAAA,IAAc,KAAK,WAAA,EAAa;AACzC,MAAA,IAAI;AACF,QAAA,UAAA,CAAW,IAAI,CAAA;AAAA,MACjB,SAAS,KAAA,EAAO;AAEd,QAAA,OAAA,CAAQ,KAAA,CAAM,wCAAwC,KAAK,CAAA;AAAA,MAC7D;AAAA,IACF;AAGA,IAAA,IAAI,KAAK,gBAAA,EAAkB;AACzB,MAAA,IAAA,CAAK,gBAAA,CAAiB,MAAM,IAAI,CAAA;AAAA,IAClC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,UAAU,QAAA,EAAoD;AAC5D,IAAA,IAAA,CAAK,WAAA,CAAY,IAAI,QAAQ,CAAA;AAC7B,IAAA,OAAO,MAAM,IAAA,CAAK,WAAA,CAAY,MAAA,CAAO,QAAQ,CAAA;AAAA,EAC/C;AAAA,EAEA,MAAM,UAAA,GAA4B;AAChC,IAAA,IAAI,KAAK,gBAAA,EAAkB;AACzB,MAAA,OAAO,IAAA,CAAK,iBAAiB,UAAA,EAAW;AAAA,IAC1C;AAAA,EACF;AAAA,EAEA,MAAM,QAAA,GAA0B;AAE9B,IAAA,IAAA,CAAK,YAAY,KAAA,EAAM;AACvB,IAAA,IAAI,KAAK,gBAAA,EAAkB;AACzB,MAAA,OAAO,IAAA,CAAK,iBAAiB,QAAA,EAAS;AAAA,IACxC;AAAA,EACF;AACF;AC3DA,SAAS,QAAA,CAAS,CAAC,OAAA,EAAS,WAAW,CAAA,EAA6B;AAClE,EAAA,OAAO,OAAA,GAAU,MAAO,WAAA,GAAc,GAAA;AACxC;AAKA,SAAS,UAAU,IAAA,EAAgD;AACjE,EAAA,IAAI,IAAA,KAAS,eAAe,EAAA,EAAI;AAC9B,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,IAAA,KAAS,eAAe,KAAA,EAAO;AACjC,IAAA,OAAO,OAAA;AAAA,EACT;AACA,EAAA,OAAO,OAAA;AACT;AAKA,SAAS,QAAQ,IAAA,EAAwB;AACvC,EAAA,MAAM,OAAA,GAAkC;AAAA,IACtC,CAAC,QAAA,CAAS,QAAQ,GAAG,UAAA;AAAA,IACrB,CAAC,QAAA,CAAS,MAAM,GAAG,QAAA;AAAA,IACnB,CAAC,QAAA,CAAS,MAAM,GAAG,QAAA;AAAA,IACnB,CAAC,QAAA,CAAS,QAAQ,GAAG,UAAA;AAAA,IACrB,CAAC,QAAA,CAAS,QAAQ,GAAG;AAAA,GACvB;AACA,EAAA,OAAO,OAAA,CAAQ,IAAI,CAAA,IAAK,SAAA;AAC1B;AAqBO,SAAS,yBACd,SAAA,EACoB;AACpB,EAAA,OAAO;AAAA,IACL,UAAU,QAAA,EAAU;AAClB,MAAA,OAAO,SAAA,CAAU,SAAA,CAAU,CAAC,IAAA,KAAuB;AACjD,QAAA,MAAM,WAAA,GAAc,KAAK,WAAA,EAAY;AAGrC,QAAA,MAAM,YAAA,GACJ,kBAAkB,IAAA,IAAQ,OAAO,KAAK,YAAA,KAAiB,QAAA,GACnD,KAAK,YAAA,GACL,MAAA;AAGN,QAAA,MAAM,SAAA,GAAY,QAAA,CAAS,IAAA,CAAK,SAAS,CAAA;AACzC,QAAA,MAAM,OAAA,GAAU,QAAA,CAAS,IAAA,CAAK,OAAO,CAAA;AACrC,QAAA,MAAM,aAAa,OAAA,GAAU,SAAA;AAG7B,QAAA,MAAM,KAAA,GAA2B;AAAA,UAC/B,MAAM,IAAA,CAAK,IAAA;AAAA,UACX,QAAQ,WAAA,CAAY,MAAA;AAAA,UACpB,SAAS,WAAA,CAAY,OAAA;AAAA,UACrB,YAAA;AAAA,UACA,SAAA;AAAA,UACA,OAAA;AAAA,UACA,UAAA;AAAA,UACA,MAAA,EAAQ,SAAA,CAAU,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA;AAAA,UAClC,IAAA,EAAM,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAA;AAAA,UACvB,YAAY,IAAA,CAAK;AAAA,SACnB;AAEA,QAAA,QAAA,CAAS,KAAK,CAAA;AAAA,MAChB,CAAC,CAAA;AAAA,IACH;AAAA,GACF;AACF;ACjEA,SAAS,GAAG,CAAA,EAAmB;AAC7B,EAAA,IAAI,IAAI,GAAA,EAAM,OAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAC,CAAA,EAAA,CAAA;AACpC,EAAA,OAAO,CAAA,EAAA,CAAI,CAAA,GAAI,GAAA,EAAM,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA,CAAA;AACjC;AAGA,SAAS,QAAA,CAAS,GAAW,KAAA,EAAuB;AAClD,EAAA,IAAI,CAAA,CAAE,MAAA,IAAU,KAAA,EAAO,OAAO,CAAA;AAC9B,EAAA,OAAO,CAAA,CAAE,MAAM,CAAA,EAAG,IAAA,CAAK,IAAI,CAAA,EAAG,KAAA,GAAQ,CAAC,CAAC,CAAA,GAAI,QAAA;AAC9C;AAUA,SAAS,SAAA,CAAU;AAAA,EACjB,KAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAAuC;AACrC,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAS,KAAK,CAAA;AAC1C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,QAAA,CAA8B,EAAE,CAAA;AAC1D,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,CAAC,CAAA;AAC1C,EAAA,MAAM,CAAC,gBAAA,EAAkB,mBAAmB,CAAA,GAAI,SAAS,KAAK,CAAA;AAE9D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,WAAA,GAAc,MAAA,CAAO,SAAA,CAAU,CAAC,IAAA,KAAS;AAC7C,MAAA,IAAI,MAAA,EAAQ;AACZ,MAAA,QAAA,CAAS,CAAC,IAAA,KAAS;AACjB,QAAA,MAAM,IAAA,GAAO,CAAC,IAAA,EAAM,GAAG,IAAI,CAAA;AAC3B,QAAA,OAAO,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,QAAQ,CAAA;AAAA,MAC/B,CAAC,CAAA;AACD,MAAA,WAAA,CAAY,CAAC,CAAA;AAAA,IACf,CAAC,CAAA;AACD,IAAA,OAAO,WAAA;AAAA,EACT,CAAA,EAAG,CAAC,MAAA,EAAQ,MAAA,EAAQ,QAAQ,CAAC,CAAA;AAE7B,EAAA,MAAM,QAAA,GAAW,QAAQ,MAAM;AAC7B,IAAA,MAAM,IAAA,GAAO,mBACT,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,OAAO,CAAA,GACxC,KAAA;AACJ,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,EAAG,CAAC,KAAA,EAAO,gBAAgB,CAAC,CAAA;AAE5B,EAAA,MAAM,KAAA,GAAQ,QAAQ,MAAM;AAC1B,IAAA,MAAM,QAAQ,KAAA,CAAM,MAAA;AACpB,IAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,OAAO,CAAA,CAAE,MAAA;AACzD,IAAA,MAAM,GAAA,GAAM,KAAA,GACR,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,GAAI,CAAA,CAAE,UAAA,EAAY,CAAC,CAAA,GAAI,KAAA,GAC9C,CAAA;AACJ,IAAA,MAAM,GAAA,GAAM,SACP,MAAM;AACL,MAAA,MAAM,MAAA,GAAS,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,CAAA,CAAE,QAAA,CAAS,CAAC,CAAA,EAAG,CAAA,KAAM,IAAI,CAAC,CAAA;AACtE,MAAA,OAAO,OAAO,IAAA,CAAK,KAAA,CAAM,OAAO,MAAA,GAAS,IAAI,CAAC,CAAA,IAAK,CAAA;AAAA,IACrD,IAAG,GACH,CAAA;AACJ,IAAA,OAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,GAAA,EAAI;AAAA,EACnC,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,MAAM,OAAA,GAAU,SAAS,QAAQ,CAAA;AAGjC,EAAA,MAAM,EAAE,kBAAA,EAAmB,GAAI,QAAA,EAAS;AAExC,EAAA,QAAA;AAAA,IACE,CAAC,OAAO,GAAA,KAAQ;AACd,MAAA,IAAI,GAAA,CAAI,OAAA,EAAS,WAAA,CAAY,CAAC,CAAA,KAAM,KAAK,GAAA,CAAI,CAAA,EAAG,CAAA,GAAI,CAAC,CAAC,CAAA;AACtD,MAAA,IAAI,GAAA,CAAI,SAAA;AACN,QAAA,WAAA,CAAY,CAAC,MAAM,IAAA,CAAK,GAAA,CAAI,SAAS,MAAA,GAAS,CAAA,EAAG,CAAA,GAAI,CAAC,CAAC,CAAA;AAEzD,MAAA,IAAI,UAAU,GAAA,EAAK,SAAA,CAAU,CAAC,CAAA,KAAM,CAAC,CAAC,CAAA;AACtC,MAAA,IAAI,UAAU,GAAA,EAAK,mBAAA,CAAoB,CAAC,CAAA,KAAM,CAAC,CAAC,CAAA;AAEhD,MAAA,IAAI,UAAU,GAAA,EAAK;AACjB,QAAA,QAAA,CAAS,EAAE,CAAA;AACX,QAAA,WAAA,CAAY,CAAC,CAAA;AAAA,MACf;AAAA,IACF,CAAA;AAAA,IACA,EAAE,UAAU,kBAAA;AAAmB,GACjC;AAEA,EAAA,MAAM,WAAA,GAAc,SAAS,UAAA,GAAa,QAAA;AAE1C,EAAA,uBACE,IAAA;AAAA,IAAC,GAAA;AAAA,IAAA;AAAA,MACC,aAAA,EAAc,QAAA;AAAA,MACd,WAAA,EAAY,OAAA;AAAA,MACZ,OAAA,EAAS,CAAA;AAAA,MACT,WAAA,EAAa,SAAS,MAAA,GAAS,MAAA;AAAA,MAG/B,QAAA,EAAA;AAAA,wBAAA,IAAA,CAAC,GAAA,EAAA,EAAI,cAAA,EAAe,eAAA,EAAgB,YAAA,EAAc,CAAA,EAChD,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,IAAA,EAAA,EAAiB,MAAI,IAAA,EAAC,QAAA,EAAA;AAAA,YAAA,YAAA;AAAA,YAAI;AAAA,WAAA,EAAA,EAAjB,OAAuB,CAAA;AAAA,8BAChC,IAAA,EAAA,EAAkB,KAAA,EAAO,SAAS,QAAA,GAAW,OAAA,EAAU,yBAA9C,QAA0D;AAAA,SAAA,EACtE,CAAA;AAAA,6BAGC,GAAA,EAAA,EAAI,YAAA,EAAc,GAAG,aAAA,EAAc,KAAA,EAAM,gBAAe,eAAA,EACvD,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,IAAA,EAAA,EAAoB,QAAA,EAAQ,IAAA,EAAC,QAAA,EAAA,4FAAA,EAAA,EAApB,UAEV,CAAA;AAAA,0BACA,IAAA,CAAC,IAAA,EAAA,EAAiB,QAAA,EAAQ,IAAA,EAAC,QAAA,EAAA;AAAA,YAAA,UAAA;AAAA,YAChB,QAAA,CAAS,MAAA;AAAA,YAAO,GAAA;AAAA,YAAE,KAAA,CAAM;AAAA,WAAA,EAAA,EADzB,OAEV;AAAA,SAAA,EACF,CAAA;AAAA,wBAGA,IAAA,CAAC,GAAA,EAAA,EAAI,aAAA,EAAc,KAAA,EAAM,KAAK,CAAA,EAE5B,QAAA,EAAA;AAAA,0BAAA,IAAA;AAAA,YAAC,GAAA;AAAA,YAAA;AAAA,cACC,aAAA,EAAc,QAAA;AAAA,cACd,KAAA,EAAM,KAAA;AAAA,cACN,WAAA,EAAY,QAAA;AAAA,cACZ,WAAA,EAAY,MAAA;AAAA,cACZ,QAAA,EAAU,CAAA;AAAA,cACV,QAAA,EAAU,CAAA;AAAA,cAEV,QAAA,EAAA;AAAA,gCAAA,IAAA,CAAC,GAAA,EAAA,EAAI,SAAA,EAAW,CAAA,EAAG,YAAA,EAAc,CAAA,EAC/B,QAAA,EAAA;AAAA,kCAAA,GAAA,CAAC,IAAA,EAAA,EAA8B,IAAA,EAAI,IAAA,EAAC,QAAA,EAAA,cAAA,EAAA,EAA1B,oBAAsC,CAAA;AAAA,kBAC/C,oCAAoB,GAAA,CAAC,IAAA,EAAA,EAA6B,KAAA,EAAM,KAAA,EAAM,8BAAhC,mBAA8C;AAAA,iBAAA,EAC/E,CAAA;AAAA,gBAEC,SAAS,MAAA,KAAW,CAAA,mBACnB,GAAA,CAAC,IAAA,EAAA,EAAK,UAAQ,IAAA,EAAC,QAAA,EAAA,2CAAA,EAAoC,CAAA,GAEnD,QAAA,CAAS,MAAM,CAAA,EAAG,EAAE,EAAE,GAAA,CAAI,CAAC,GAAG,CAAA,KAAM;AAClC,kBAAA,MAAM,QAAQ,CAAA,KAAM,QAAA;AACpB,kBAAA,MAAM,WAAA,GACJ,EAAE,MAAA,KAAW,OAAA,GACT,QACA,CAAA,CAAE,UAAA,GAAa,MACb,QAAA,GACA,OAAA;AAGR,kBAAA,MAAM,YAAY,CAAA,EAAG,CAAA,CAAE,MAAM,CAAA,CAAA,EAAI,EAAE,SAAS,CAAA,CAAA;AAE5C,kBAAA,uBACE,IAAA,CAAC,GAAA,EAAA,EAAoB,aAAA,EAAc,KAAA,EACjC,QAAA,EAAA;AAAA,oCAAA,GAAA,CAAC,QAAK,KAAA,EAAO,KAAA,GAAQ,SAAS,MAAA,EAC3B,QAAA,EAAA,KAAA,GAAQ,YAAO,IAAA,EAClB,CAAA;AAAA,oCACA,GAAA,CAAC,IAAA,EAAA,EAAK,KAAA,EAAO,MAAA,GAAS,WAAA,GAAc,QACjC,QAAA,EAAA,QAAA,CAAS,CAAA,CAAE,IAAA,EAAM,EAAE,CAAA,EACtB,CAAA;AAAA,oCACA,IAAA,CAAC,IAAA,EAAA,EAAK,QAAA,EAAQ,IAAA,EAAC,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,sBAAE,EAAA,CAAG,EAAE,UAAU;AAAA,qBAAA,EAAE,CAAA;AAAA,oCAClC,IAAA,CAAC,IAAA,EAAA,EAAK,QAAA,EAAQ,IAAA,EAAC,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,sBAAE,QAAA,CAAS,CAAA,CAAE,OAAA,EAAS,EAAE;AAAA,qBAAA,EAAE;AAAA,mBAAA,EAAA,EARjC,SASV,CAAA;AAAA,gBAEJ,CAAC;AAAA;AAAA;AAAA,WAEL;AAAA,0BAGA,IAAA;AAAA,YAAC,GAAA;AAAA,YAAA;AAAA,cACC,aAAA,EAAc,QAAA;AAAA,cACd,KAAA,EAAM,KAAA;AAAA,cACN,WAAA,EAAY,QAAA;AAAA,cACZ,WAAA,EAAY,MAAA;AAAA,cACZ,QAAA,EAAU,CAAA;AAAA,cACV,QAAA,EAAU,CAAA;AAAA,cAEV,QAAA,EAAA;AAAA,gCAAA,GAAA,CAAC,GAAA,EAAA,EAAI,cAAc,CAAA,EACjB,QAAA,kBAAA,GAAA,CAAC,QAAK,IAAA,EAAI,IAAA,EAAC,qBAAO,CAAA,EACpB,CAAA;AAAA,gBAEC,0BACC,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,kCAAA,IAAA,CAAC,IAAA,EAAA,EACC,QAAA,EAAA;AAAA,oCAAA,GAAA,CAAC,IAAA,EAAA,EAAK,QAAA,EAAQ,IAAA,EAAC,QAAA,EAAA,QAAA,EAAM,CAAA;AAAA,oCACrB,GAAA,CAAC,IAAA,EAAA,EAAM,QAAA,EAAA,OAAA,CAAQ,IAAA,EAAK;AAAA,mBAAA,EACtB,CAAA;AAAA,uCACC,IAAA,EAAA,EACC,QAAA,EAAA;AAAA,oCAAA,GAAA,CAAC,IAAA,EAAA,EAAK,QAAA,EAAQ,IAAA,EAAC,QAAA,EAAA,UAAA,EAAQ,CAAA;AAAA,oCACvB,GAAA,CAAC,QAAK,KAAA,EAAO,OAAA,CAAQ,WAAW,OAAA,GAAU,KAAA,GAAQ,OAAA,EAC/C,QAAA,EAAA,OAAA,CAAQ,MAAA,EACX;AAAA,mBAAA,EACF,CAAA;AAAA,uCACC,IAAA,EAAA,EACC,QAAA,EAAA;AAAA,oCAAA,GAAA,CAAC,IAAA,EAAA,EAAK,QAAA,EAAQ,IAAA,EAAC,QAAA,EAAA,YAAA,EAAU,CAAA;AAAA,oCACzB,GAAA,CAAC,IAAA,EAAA,EAAM,QAAA,EAAA,EAAA,CAAG,OAAA,CAAQ,UAAU,CAAA,EAAE;AAAA,mBAAA,EAChC,CAAA;AAAA,kCACA,IAAA,CAAC,IAAA,EAAA,EAAK,QAAA,EAAQ,IAAA,EAAC,QAAA,EAAA;AAAA,oBAAA,SAAA;AAAA,oBAAQ,OAAA,CAAQ;AAAA,mBAAA,EAAQ,CAAA;AAAA,kCACvC,IAAA,CAAC,IAAA,EAAA,EAAK,QAAA,EAAQ,IAAA,EAAC,QAAA,EAAA;AAAA,oBAAA,QAAA;AAAA,oBAAO,OAAA,CAAQ;AAAA,mBAAA,EAAO,CAAA;AAAA,kBACpC,OAAA,CAAQ,YAAA,oBACP,IAAA,CAAC,IAAA,EAAA,EAAK,UAAQ,IAAA,EAAC,QAAA,EAAA;AAAA,oBAAA,UAAA;AAAA,oBAAS,OAAA,CAAQ;AAAA,mBAAA,EAAa,CAAA;AAAA,kBAE9C,OAAA,CAAQ,IAAA,oBAAQ,IAAA,CAAC,IAAA,EAAA,EAAK,UAAQ,IAAA,EAAC,QAAA,EAAA;AAAA,oBAAA,QAAA;AAAA,oBAAO,OAAA,CAAQ;AAAA,mBAAA,EAAK,CAAA;AAAA,kCAEpD,IAAA,CAAC,GAAA,EAAA,EAAI,SAAA,EAAW,CAAA,EAAG,eAAc,QAAA,EAC/B,QAAA,EAAA;AAAA,oCAAA,GAAA,CAAC,IAAA,EAAA,EAAK,IAAA,EAAI,IAAA,EAAC,QAAA,EAAA,YAAA,EAAU,CAAA;AAAA,oBACpB,OAAA,CAAQ,UAAA,IACT,MAAA,CAAO,IAAA,CAAK,OAAA,CAAQ,UAAU,CAAA,CAAE,MAAA,GAAS,CAAA,GACvC,MAAA,CAAO,OAAA,CAAQ,OAAA,CAAQ,UAAU,CAAA,CAC9B,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,CACX,GAAA,CAAI,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,qBACT,IAAA,CAAC,IAAA,EAAA,EAAa,QAAA,EAAQ,IAAA,EACnB,QAAA,EAAA;AAAA,sBAAA,QAAA,CAAS,GAAG,EAAE,CAAA;AAAA,sBAAE,IAAA;AAAA,sBAAG,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,EAAG,EAAE;AAAA,qBAAA,EAAA,EADjC,CAEX,CACD,CAAA,uBAEF,IAAA,EAAA,EAAK,QAAA,EAAQ,MAAC,QAAA,EAAA,QAAA,EAAM;AAAA,mBAAA,EAEzB;AAAA,iBAAA,EACF,CAAA,mBAEA,GAAA,CAAC,IAAA,EAAA,EAAK,QAAA,EAAQ,MAAC,QAAA,EAAA,gCAAA,EAA8B;AAAA;AAAA;AAAA;AAEjD,SAAA,EACF,CAAA;AAAA,QAGC,SAAA,oBACC,GAAA,CAAC,GAAA,EAAA,EAAI,SAAA,EAAW,GAAG,WAAA,EAAY,QAAA,EAAS,WAAA,EAAY,MAAA,EAAO,QAAA,EAAU,CAAA,EACnE,QAAA,kBAAA,IAAA,CAAC,IAAA,EAAA,EAAK,UAAQ,IAAA,EAAC,QAAA,EAAA;AAAA,UAAA,SAAA;AAAA,UACL,KAAA,CAAM,KAAA;AAAA,UAAM,aAAA;AAAA,UAAY,KAAA,CAAM,MAAA;AAAA,UAAO,UAAA;AAAA,UAAS,EAAA,CAAG,MAAM,GAAG,CAAA;AAAA,UAAG,GAAA;AAAA,UAAI,SAAA;AAAA,UACjE,EAAA,CAAG,MAAM,GAAG;AAAA,SAAA,EACtB,CAAA,EACF;AAAA;AAAA;AAAA,GAEJ;AAEJ;AAGA,IAAI,wBAAA,GAA0D,IAAA;AAM9D,SAAS,oBACP,QAAA,EAGA;AACA,EAAA,OACE,OAAO,aAAa,QAAA,IACpB,QAAA,KAAa,QACb,kBAAA,IAAsB,QAAA,IACtB,OAAQ,QAAA,CAA4C,gBAAA,KAClD,UAAA;AAEN;AA8BO,SAAS,cAAA,CACd,OAAA,GAA2B,EAAC,EAC5B,MAAA,EACM;AACN,EAAA,MAAM,KAAA,GAAQ,QAAQ,KAAA,IAAS,yBAAA;AAC/B,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,KAAc,KAAA;AACxC,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,GAAA;AACrC,EAAA,MAAM,SAAS,OAAA,CAAQ,MAAA,IAAU,OAAA,CAAQ,OAAA,CAAQ,OAAO,KAAK,CAAA;AAI7D,EAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,KAAA,CAAM,KAAA,GAAQ,QAAQ,KAAA,GAAQ,MAAA;AAG1D,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,IAAI;AACF,MAAA,MAAA;AAAA,wBACE,GAAA;AAAA,UAAC,SAAA;AAAA,UAAA;AAAA,YACC,KAAA;AAAA,YACA,SAAA;AAAA,YACA,QAAA;AAAA,YACA,MAAA;AAAA,YACA;AAAA;AAAA,SACF;AAAA,QACA,EAAE,OAAO,WAAA;AAAY,OACvB;AAAA,IACF,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,kDAAkD,KAAK,CAAA;AAAA,IACvE;AACA,IAAA;AAAA,EACF;AAGA,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACF,IAAA,QAAA,GAAW,wBAAA,EAAyB;AAAA,EACtC,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,KAAA;AAAA,MACN,0FAAA;AAAA,MACA;AAAA,KACF;AACA,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,OAAA,CAAQ,KAAA;AAAA,MACN;AAAA,KACF;AACA,IAAA;AAAA,EACF;AAGA,EAAA,IAAI,CAAC,mBAAA,CAAoB,QAAQ,CAAA,EAAG;AAClC,IAAA,OAAA,CAAQ,KAAA;AAAA,MACN;AAAA,KACF;AACA,IAAA;AAAA,EACF;AAIA,EAAA,wBAAA,GAA2B,IAAI,uBAAuB,IAAI,CAAA;AAC1D,EAAA,MAAM,cAAA,GAAiB,yBAAyB,wBAAwB,CAAA;AAGxE,EAAA,QAAA,CAAS,iBAAiB,wBAAwB,CAAA;AAElD,EAAA,IAAI;AACF,IAAA,MAAA;AAAA,sBACE,GAAA;AAAA,QAAC,SAAA;AAAA,QAAA;AAAA,UACC,KAAA;AAAA,UACA,SAAA;AAAA,UACA,QAAA;AAAA,UACA,MAAA;AAAA,UACA,MAAA,EAAQ;AAAA;AAAA,OACV;AAAA,MACA,EAAE,OAAO,WAAA;AAAY,KACvB;AAAA,EACF,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,KAAA,CAAM,kDAAkD,KAAK,CAAA;AAAA,EACvE;AACF","file":"index.js","sourcesContent":["/**\n * Streaming Span Processor\n *\n * A span processor that emits ReadableSpan objects to subscribers.\n * This allows real-time streaming of spans to terminal dashboards and other consumers.\n *\n * @example Basic usage\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { BatchSpanProcessor } from 'autotel/processors'\n * import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\n *\n * const exporter = new OTLPTraceExporter({ url: 'http://localhost:4318/v1/traces' })\n * const baseProcessor = new BatchSpanProcessor(exporter)\n * const streamingProcessor = new StreamingSpanProcessor(baseProcessor)\n *\n * // Subscribe to spans\n * const unsubscribe = streamingProcessor.subscribe((span) => {\n * console.log('Span ended:', span.name)\n * })\n *\n * // Later, unsubscribe\n * unsubscribe()\n * ```\n */\n\nimport type {\n SpanProcessor,\n ReadableSpan,\n} from '@opentelemetry/sdk-trace-base';\nimport type { Context } from '@opentelemetry/api';\nimport type { Span } from '@opentelemetry/sdk-trace-base';\n\n/**\n * Span processor that wraps another processor and emits spans to subscribers\n *\n * This processor forwards all span operations to a wrapped processor while\n * also emitting completed spans to registered subscribers. This enables\n * real-time streaming of spans to terminal dashboards, loggers, or other\n * consumers without interfering with normal span processing.\n */\nexport class StreamingSpanProcessor implements SpanProcessor {\n private wrappedProcessor: SpanProcessor | null;\n private subscribers = new Set<(span: ReadableSpan) => void>();\n\n /**\n * Create a new streaming span processor\n *\n * @param wrappedProcessor - The processor to wrap and forward spans to.\n * If null, spans are only emitted to subscribers (no forwarding).\n */\n constructor(wrappedProcessor: SpanProcessor | null = null) {\n this.wrappedProcessor = wrappedProcessor;\n }\n\n onStart(span: Span, parentContext: Context): void {\n if (this.wrappedProcessor) {\n this.wrappedProcessor.onStart(span, parentContext);\n }\n }\n\n onEnd(span: ReadableSpan): void {\n // Emit to all subscribers first\n for (const subscriber of this.subscribers) {\n try {\n subscriber(span);\n } catch (error) {\n // Don't let subscriber errors break span processing\n console.error('[autotel-terminal] Subscriber error:', error);\n }\n }\n\n // Forward to wrapped processor\n if (this.wrappedProcessor) {\n this.wrappedProcessor.onEnd(span);\n }\n }\n\n /**\n * Subscribe to span end events\n *\n * @param callback - Function called when a span ends\n * @returns Unsubscribe function\n *\n * @example\n * ```typescript\n * const unsubscribe = processor.subscribe((span) => {\n * console.log('Span ended:', span.name)\n * })\n *\n * // Later, unsubscribe\n * unsubscribe()\n * ```\n */\n subscribe(callback: (span: ReadableSpan) => void): () => void {\n this.subscribers.add(callback);\n return () => this.subscribers.delete(callback);\n }\n\n async forceFlush(): Promise<void> {\n if (this.wrappedProcessor) {\n return this.wrappedProcessor.forceFlush();\n }\n }\n\n async shutdown(): Promise<void> {\n // Clear subscribers\n this.subscribers.clear();\n if (this.wrappedProcessor) {\n return this.wrappedProcessor.shutdown();\n }\n }\n}\n","/**\n * Terminal Span Stream\n *\n * Converts OpenTelemetry ReadableSpan objects to TerminalSpanEvent format\n * for consumption by the terminal dashboard.\n */\n\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport { SpanStatusCode, SpanKind } from 'autotel';\nimport type { StreamingSpanProcessor } from './streaming-processor';\n\n/**\n * Span event format for terminal dashboard consumption\n */\nexport interface TerminalSpanEvent {\n /** Span name */\n name: string;\n /** Span ID (hex string) */\n spanId: string;\n /** Trace ID (hex string) */\n traceId: string;\n /** Parent span ID (hex string, optional) */\n parentSpanId?: string;\n /** Start time in milliseconds since epoch */\n startTime: number;\n /** End time in milliseconds since epoch */\n endTime: number;\n /** Duration in milliseconds */\n durationMs: number;\n /** Span status */\n status: 'OK' | 'ERROR' | 'UNSET';\n /** Span kind (INTERNAL, SERVER, CLIENT, etc.) */\n kind?: string;\n /** Span attributes */\n attributes?: Record<string, unknown>;\n}\n\n/**\n * Stream interface for terminal dashboard\n */\nexport interface TerminalSpanStream {\n /**\n * Subscribe to span end events\n *\n * @param callback - Called when a span ends\n * @returns Unsubscribe function\n */\n onSpanEnd(callback: (event: TerminalSpanEvent) => void): () => void;\n}\n\n/**\n * Convert OpenTelemetry time tuple to milliseconds\n */\nfunction timeToMs([seconds, nanoseconds]: [number, number]): number {\n return seconds * 1000 + nanoseconds / 1_000_000;\n}\n\n/**\n * Map SpanStatusCode to string\n */\nfunction mapStatus(code: SpanStatusCode): 'OK' | 'ERROR' | 'UNSET' {\n if (code === SpanStatusCode.OK) {\n return 'OK';\n }\n if (code === SpanStatusCode.ERROR) {\n return 'ERROR';\n }\n return 'UNSET';\n}\n\n/**\n * Map SpanKind number to string\n */\nfunction mapKind(kind: SpanKind): string {\n const kindMap: Record<number, string> = {\n [SpanKind.INTERNAL]: 'INTERNAL',\n [SpanKind.SERVER]: 'SERVER',\n [SpanKind.CLIENT]: 'CLIENT',\n [SpanKind.PRODUCER]: 'PRODUCER',\n [SpanKind.CONSUMER]: 'CONSUMER',\n };\n return kindMap[kind] ?? 'UNKNOWN';\n}\n\n/**\n * Create a terminal span stream from a streaming processor\n *\n * @param processor - The streaming span processor to subscribe to\n * @returns Terminal span stream interface\n *\n * @example\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { createTerminalSpanStream } from 'autotel-terminal'\n *\n * const processor = new StreamingSpanProcessor(baseProcessor)\n * const stream = createTerminalSpanStream(processor)\n *\n * stream.onSpanEnd((event) => {\n * console.log('Span:', event.name, event.durationMs + 'ms')\n * })\n * ```\n */\nexport function createTerminalSpanStream(\n processor: StreamingSpanProcessor,\n): TerminalSpanStream {\n return {\n onSpanEnd(callback) {\n return processor.subscribe((span: ReadableSpan) => {\n const spanContext = span.spanContext();\n // parentSpanId is not part of the standard ReadableSpan interface\n // but some implementations (like Node.js SDK) include it\n const parentSpanId =\n 'parentSpanId' in span && typeof span.parentSpanId === 'string'\n ? span.parentSpanId\n : undefined;\n\n // Convert time tuples to milliseconds\n const startTime = timeToMs(span.startTime);\n const endTime = timeToMs(span.endTime);\n const durationMs = endTime - startTime;\n\n // Create terminal event\n const event: TerminalSpanEvent = {\n name: span.name,\n spanId: spanContext.spanId,\n traceId: spanContext.traceId,\n parentSpanId,\n startTime,\n endTime,\n durationMs,\n status: mapStatus(span.status.code),\n kind: mapKind(span.kind),\n attributes: span.attributes as Record<string, unknown>,\n };\n\n callback(event);\n });\n },\n };\n}\n","/**\n * Terminal Dashboard for Autotel\n *\n * Beautiful react-ink powered dashboard for viewing traces in real-time.\n *\n * @example Basic usage (auto-wire)\n * ```typescript\n * import { init } from 'autotel'\n * import { renderTerminal } from 'autotel-terminal'\n *\n * init({ service: 'my-app' })\n * renderTerminal() // Automatically creates stream from current tracer provider\n * ```\n *\n * @example With options\n * ```typescript\n * renderTerminal({\n * title: 'My App Traces',\n * showStats: true,\n * maxSpans: 200,\n * })\n * ```\n *\n * @example Manual stream (advanced)\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { createTerminalSpanStream } from 'autotel-terminal'\n * import { BatchSpanProcessor } from 'autotel/processors'\n *\n * const processor = new StreamingSpanProcessor(new BatchSpanProcessor(exporter))\n * const stream = createTerminalSpanStream(processor)\n * renderTerminal({}, stream)\n * ```\n *\n * @module autotel-terminal\n */\n\nimport React, { useEffect, useMemo, useState } from 'react';\nimport { render, Box, Text, useInput, useStdin } from 'ink';\nimport type { TerminalSpanEvent, TerminalSpanStream } from './span-stream';\nimport { StreamingSpanProcessor } from './streaming-processor';\nimport { createTerminalSpanStream } from './span-stream';\nimport { getAutotelTracerProvider } from 'autotel/tracer-provider';\nimport type { TracerProvider } from '@opentelemetry/api';\n\n/**\n * Terminal dashboard options\n */\nexport interface TerminalOptions {\n /**\n * Dashboard title\n * @default 'Autotel Trace Inspector'\n */\n title?: string;\n\n /**\n * Show statistics bar (span count, error rate, avg duration)\n * @default true\n */\n showStats?: boolean;\n\n /**\n * Maximum number of spans to display\n * @default 100\n */\n maxSpans?: number;\n\n /**\n * Enable colors (auto-detect by default)\n * @default true if TTY\n */\n colors?: boolean;\n}\n\n// Helper for rendering duration\nfunction ms(n: number): string {\n if (n < 1000) return `${n.toFixed(0)}ms`;\n return `${(n / 1000).toFixed(2)}s`;\n}\n\n// Helper for truncating strings\nfunction truncate(s: string, width: number): string {\n if (s.length <= width) return s;\n return s.slice(0, Math.max(0, width - 1)) + '…';\n}\n\ninterface DashboardProps {\n title: string;\n showStats: boolean;\n maxSpans: number;\n colors: boolean;\n stream: TerminalSpanStream;\n}\n\nfunction Dashboard({\n title,\n showStats,\n maxSpans,\n colors,\n stream,\n}: DashboardProps): React.ReactElement {\n const [paused, setPaused] = useState(false);\n const [spans, setSpans] = useState<TerminalSpanEvent[]>([]);\n const [selected, setSelected] = useState(0);\n const [filterErrorsOnly, setFilterErrorsOnly] = useState(false);\n\n useEffect(() => {\n const unsubscribe = stream.onSpanEnd((span) => {\n if (paused) return;\n setSpans((prev) => {\n const next = [span, ...prev];\n return next.slice(0, maxSpans);\n });\n setSelected(0);\n });\n return unsubscribe;\n }, [stream, paused, maxSpans]);\n\n const filtered = useMemo(() => {\n const list = filterErrorsOnly\n ? spans.filter((s) => s.status === 'ERROR')\n : spans;\n return list;\n }, [spans, filterErrorsOnly]);\n\n const stats = useMemo(() => {\n const total = spans.length;\n const errors = spans.filter((s) => s.status === 'ERROR').length;\n const avg = total\n ? spans.reduce((a, s) => a + s.durationMs, 0) / total\n : 0;\n const p95 = total\n ? (() => {\n const sorted = spans.map((s) => s.durationMs).toSorted((a, b) => a - b);\n return sorted[Math.floor(sorted.length * 0.95)] ?? 0;\n })()\n : 0;\n return { total, errors, avg, p95 };\n }, [spans]);\n\n const current = filtered[selected];\n\n // Check if raw mode is supported (needed for keyboard input)\n const { isRawModeSupported } = useStdin();\n\n useInput(\n (input, key) => {\n if (key.upArrow) setSelected((i) => Math.max(0, i - 1));\n if (key.downArrow)\n setSelected((i) => Math.min(filtered.length - 1, i + 1));\n\n if (input === 'p') setPaused((p) => !p);\n if (input === 'e') setFilterErrorsOnly((v) => !v);\n\n if (input === 'c') {\n setSpans([]);\n setSelected(0);\n }\n },\n { isActive: isRawModeSupported },\n );\n\n const headerRight = paused ? '[Paused]' : '[Live]';\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"round\"\n padding={1}\n borderColor={colors ? 'cyan' : undefined}\n >\n {/* Header */}\n <Box justifyContent=\"space-between\" marginBottom={1}>\n <Text key=\"title\" bold>🔭 {title}</Text>\n <Text key=\"status\" color={paused ? 'yellow' : 'green'}>{headerRight}</Text>\n </Box>\n\n {/* Help / controls */}\n <Box marginBottom={1} flexDirection=\"row\" justifyContent=\"space-between\">\n <Text key=\"controls\" dimColor>\n ↑/↓ select • p pause • e errors-only • c clear • Ctrl+C exit\n </Text>\n <Text key=\"count\" dimColor>\n showing {filtered.length}/{spans.length}\n </Text>\n </Box>\n\n {/* Main content: list + details */}\n <Box flexDirection=\"row\" gap={2}>\n {/* List */}\n <Box\n flexDirection=\"column\"\n width=\"55%\"\n borderStyle=\"single\"\n borderColor=\"gray\"\n paddingX={1}\n paddingY={0}\n >\n <Box marginTop={0} marginBottom={1}>\n <Text key=\"recent-spans-title\" bold>Recent spans</Text>\n {filterErrorsOnly && <Text key=\"errors-only-label\" color=\"red\"> (errors only)</Text>}\n </Box>\n\n {filtered.length === 0 ? (\n <Text dimColor>No spans yet. Generate some traffic…</Text>\n ) : (\n filtered.slice(0, 20).map((s, i) => {\n const isSel = i === selected;\n const statusColor =\n s.status === 'ERROR'\n ? 'red'\n : s.durationMs > 500\n ? 'yellow'\n : 'green';\n\n // Use compound key to ensure uniqueness (spanId + startTime)\n const uniqueKey = `${s.spanId}-${s.startTime}`;\n\n return (\n <Box key={uniqueKey} flexDirection=\"row\">\n <Text color={isSel ? 'cyan' : undefined}>\n {isSel ? '› ' : ' '}\n </Text>\n <Text color={colors ? statusColor : undefined}>\n {truncate(s.name, 26)}\n </Text>\n <Text dimColor> {ms(s.durationMs)}</Text>\n <Text dimColor> {truncate(s.traceId, 10)}</Text>\n </Box>\n );\n })\n )}\n </Box>\n\n {/* Details */}\n <Box\n flexDirection=\"column\"\n width=\"45%\"\n borderStyle=\"single\"\n borderColor=\"gray\"\n paddingX={1}\n paddingY={0}\n >\n <Box marginBottom={1}>\n <Text bold>Details</Text>\n </Box>\n\n {current ? (\n <>\n <Text>\n <Text dimColor>Name: </Text>\n <Text>{current.name}</Text>\n </Text>\n <Text>\n <Text dimColor>Status: </Text>\n <Text color={current.status === 'ERROR' ? 'red' : 'green'}>\n {current.status}\n </Text>\n </Text>\n <Text>\n <Text dimColor>Duration: </Text>\n <Text>{ms(current.durationMs)}</Text>\n </Text>\n <Text dimColor>Trace: {current.traceId}</Text>\n <Text dimColor>Span: {current.spanId}</Text>\n {current.parentSpanId && (\n <Text dimColor>Parent: {current.parentSpanId}</Text>\n )}\n {current.kind && <Text dimColor>Kind: {current.kind}</Text>}\n\n <Box marginTop={1} flexDirection=\"column\">\n <Text bold>Attributes</Text>\n {current.attributes &&\n Object.keys(current.attributes).length > 0 ? (\n Object.entries(current.attributes)\n .slice(0, 12)\n .map(([k, v]) => (\n <Text key={k} dimColor>\n {truncate(k, 22)}: {truncate(String(v), 34)}\n </Text>\n ))\n ) : (\n <Text dimColor>(none)</Text>\n )}\n </Box>\n </>\n ) : (\n <Text dimColor>Select a span to view details.</Text>\n )}\n </Box>\n </Box>\n\n {/* Stats bar */}\n {showStats && (\n <Box marginTop={1} borderStyle=\"single\" borderColor=\"gray\" paddingX={1}>\n <Text dimColor>\n Spans: {stats.total} | Errors: {stats.errors} | Avg: {ms(stats.avg)}{' '}\n | P95: {ms(stats.p95)}\n </Text>\n </Box>\n )}\n </Box>\n );\n}\n\n// Global streaming processor for auto-wiring\nlet globalStreamingProcessor: StreamingSpanProcessor | null = null;\n\n/**\n * Check if a TracerProvider has an addSpanProcessor method\n * (Node.js SDK providers have this, but API-level providers don't)\n */\nfunction canAddSpanProcessor(\n provider: TracerProvider,\n): provider is TracerProvider & {\n addSpanProcessor: (processor: unknown) => void;\n} {\n return (\n typeof provider === 'object' &&\n provider !== null &&\n 'addSpanProcessor' in provider &&\n typeof (provider as { addSpanProcessor?: unknown }).addSpanProcessor ===\n 'function'\n );\n}\n\n/**\n * Render the terminal dashboard\n *\n * Automatically wires up a streaming processor from the current tracer provider\n * if no stream is provided. Otherwise uses the provided stream.\n *\n * @param options - Dashboard configuration options\n * @param stream - Optional manual stream (for advanced use cases)\n *\n * @example Auto-wire (recommended)\n * ```typescript\n * import { init } from 'autotel'\n * import { renderTerminal } from 'autotel-terminal'\n *\n * init({ service: 'my-app' })\n * renderTerminal() // Automatically creates stream from current tracer provider\n * ```\n *\n * @example Manual stream\n * ```typescript\n * import { StreamingSpanProcessor } from 'autotel-terminal'\n * import { createTerminalSpanStream } from 'autotel-terminal'\n *\n * const processor = new StreamingSpanProcessor(baseProcessor)\n * const stream = createTerminalSpanStream(processor)\n * renderTerminal({}, stream)\n * ```\n */\nexport function renderTerminal(\n options: TerminalOptions = {},\n stream?: TerminalSpanStream,\n): void {\n const title = options.title ?? 'Autotel Trace Inspector';\n const showStats = options.showStats !== false;\n const maxSpans = options.maxSpans ?? 100;\n const colors = options.colors ?? Boolean(process.stdout.isTTY);\n\n // Check if stdin supports raw mode (needed for keyboard input)\n // If not, we disable stdin to prevent Ink from throwing an error\n const stdinOption = process.stdin.isTTY ? process.stdin : undefined;\n\n // If stream provided, use it directly\n if (stream) {\n try {\n render(\n <Dashboard\n title={title}\n showStats={showStats}\n maxSpans={maxSpans}\n colors={colors}\n stream={stream}\n />,\n { stdin: stdinOption },\n );\n } catch (error) {\n console.error('[autotel-terminal] Failed to render dashboard:', error);\n }\n return;\n }\n\n // Otherwise, auto-wire from current tracer provider\n let provider: TracerProvider;\n try {\n provider = getAutotelTracerProvider();\n } catch (error) {\n console.error(\n '[autotel-terminal] Failed to get tracer provider. Call init() first or provide a stream.',\n error,\n );\n return;\n }\n\n if (!provider) {\n console.error(\n '[autotel-terminal] No tracer provider found. Call init() first or provide a stream.',\n );\n return;\n }\n\n // Check if provider supports addSpanProcessor (Node.js SDK providers do)\n if (!canAddSpanProcessor(provider)) {\n console.error(\n '[autotel-terminal] TracerProvider does not support addSpanProcessor. Provide a stream manually.',\n );\n return;\n }\n\n // Create streaming processor that doesn't wrap anything (just streams)\n // This way it doesn't interfere with existing processors\n globalStreamingProcessor = new StreamingSpanProcessor(null);\n const terminalStream = createTerminalSpanStream(globalStreamingProcessor);\n\n // Add streaming processor to provider\n provider.addSpanProcessor(globalStreamingProcessor);\n\n try {\n render(\n <Dashboard\n title={title}\n showStats={showStats}\n maxSpans={maxSpans}\n colors={colors}\n stream={terminalStream}\n />,\n { stdin: stdinOption },\n );\n } catch (error) {\n console.error('[autotel-terminal] Failed to render dashboard:', error);\n }\n}\n\n// Re-export types and utilities\nexport type { TerminalSpanEvent, TerminalSpanStream } from './span-stream';\nexport { StreamingSpanProcessor } from './streaming-processor';\nexport { createTerminalSpanStream } from './span-stream';\n\n// Re-export OpenTelemetry types for advanced users\nexport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nexport { SpanStatusCode, SpanKind } from 'autotel';\n\n// Re-export PrettyConsoleExporter for convenience\nexport {\n PrettyConsoleExporter,\n type PrettyConsoleExporterOptions,\n} from 'autotel/exporters';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autotel-terminal",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "Terminal dashboard for autotel traces using react-ink",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -18,16 +18,6 @@
18
18
  "src",
19
19
  "README.md"
20
20
  ],
21
- "scripts": {
22
- "build": "tsup",
23
- "dev": "tsup --watch",
24
- "lint": "npx eslint src",
25
- "lint:fix": "npx eslint src --fix",
26
- "type-check": "tsc --noEmit",
27
- "test": "vitest run --passWithNoTests",
28
- "test:watch": "vitest --passWithNoTests",
29
- "quality": "pnpm type-check && pnpm test && pnpm lint && pnpm format:check && pnpm build && pnpm check-exports"
30
- },
31
21
  "keywords": [
32
22
  "autotel",
33
23
  "opentelemetry",
@@ -47,9 +37,9 @@
47
37
  "react": "^19.2.3"
48
38
  },
49
39
  "peerDependencies": {
50
- "autotel": "workspace:*",
51
40
  "@opentelemetry/sdk-trace-base": "^2.2.0",
52
- "@opentelemetry/api": "^1.0.0"
41
+ "@opentelemetry/api": "^1.0.0",
42
+ "autotel": "2.11.0"
53
43
  },
54
44
  "devDependencies": {
55
45
  "@arethetypeswrong/cli": "^0.18.2",
@@ -58,7 +48,6 @@
58
48
  "@types/react": "^19.2.7",
59
49
  "@typescript-eslint/eslint-plugin": "^8.51.0",
60
50
  "@typescript-eslint/parser": "^8.51.0",
61
- "autotel": "workspace:*",
62
51
  "eslint-config-prettier": "^10.1.8",
63
52
  "eslint-plugin-unicorn": "^62.0.0",
64
53
  "prettier": "^3.7.4",
@@ -66,7 +55,8 @@
66
55
  "tsup": "^8.5.1",
67
56
  "typescript": "^5.9.3",
68
57
  "typescript-eslint": "^8.51.0",
69
- "vitest": "^4.0.16"
58
+ "vitest": "^4.0.16",
59
+ "autotel": "2.11.0"
70
60
  },
71
61
  "repository": {
72
62
  "type": "git",
@@ -76,5 +66,15 @@
76
66
  "bugs": {
77
67
  "url": "https://github.com/jagreehal/autotel/issues"
78
68
  },
79
- "homepage": "https://github.com/jagreehal/autotel#readme"
69
+ "homepage": "https://github.com/jagreehal/autotel#readme",
70
+ "scripts": {
71
+ "build": "tsup",
72
+ "dev": "tsup --watch",
73
+ "lint": "npx eslint src",
74
+ "lint:fix": "npx eslint src --fix",
75
+ "type-check": "tsc --noEmit",
76
+ "test": "vitest run --passWithNoTests",
77
+ "test:watch": "vitest --passWithNoTests",
78
+ "quality": "pnpm type-check && pnpm test && pnpm lint && pnpm format:check && pnpm build && pnpm check-exports"
79
+ }
80
80
  }
package/src/index.tsx CHANGED
@@ -36,7 +36,7 @@
36
36
  */
37
37
 
38
38
  import React, { useEffect, useMemo, useState } from 'react';
39
- import { render, Box, Text, useInput } from 'ink';
39
+ import { render, Box, Text, useInput, useStdin } from 'ink';
40
40
  import type { TerminalSpanEvent, TerminalSpanStream } from './span-stream';
41
41
  import { StreamingSpanProcessor } from './streaming-processor';
42
42
  import { createTerminalSpanStream } from './span-stream';
@@ -140,19 +140,25 @@ function Dashboard({
140
140
 
141
141
  const current = filtered[selected];
142
142
 
143
- useInput((input, key) => {
144
- if (key.upArrow) setSelected((i) => Math.max(0, i - 1));
145
- if (key.downArrow)
146
- setSelected((i) => Math.min(filtered.length - 1, i + 1));
143
+ // Check if raw mode is supported (needed for keyboard input)
144
+ const { isRawModeSupported } = useStdin();
147
145
 
148
- if (input === 'p') setPaused((p) => !p);
149
- if (input === 'e') setFilterErrorsOnly((v) => !v);
146
+ useInput(
147
+ (input, key) => {
148
+ if (key.upArrow) setSelected((i) => Math.max(0, i - 1));
149
+ if (key.downArrow)
150
+ setSelected((i) => Math.min(filtered.length - 1, i + 1));
150
151
 
151
- if (input === 'c') {
152
- setSpans([]);
153
- setSelected(0);
154
- }
155
- });
152
+ if (input === 'p') setPaused((p) => !p);
153
+ if (input === 'e') setFilterErrorsOnly((v) => !v);
154
+
155
+ if (input === 'c') {
156
+ setSpans([]);
157
+ setSelected(0);
158
+ }
159
+ },
160
+ { isActive: isRawModeSupported },
161
+ );
156
162
 
157
163
  const headerRight = paused ? '[Paused]' : '[Live]';
158
164
 
@@ -165,16 +171,16 @@ function Dashboard({
165
171
  >
166
172
  {/* Header */}
167
173
  <Box justifyContent="space-between" marginBottom={1}>
168
- <Text bold>🔭 {title}</Text>
169
- <Text color={paused ? 'yellow' : 'green'}>{headerRight}</Text>
174
+ <Text key="title" bold>🔭 {title}</Text>
175
+ <Text key="status" color={paused ? 'yellow' : 'green'}>{headerRight}</Text>
170
176
  </Box>
171
177
 
172
178
  {/* Help / controls */}
173
179
  <Box marginBottom={1} flexDirection="row" justifyContent="space-between">
174
- <Text dimColor>
180
+ <Text key="controls" dimColor>
175
181
  ↑/↓ select • p pause • e errors-only • c clear • Ctrl+C exit
176
182
  </Text>
177
- <Text dimColor>
183
+ <Text key="count" dimColor>
178
184
  showing {filtered.length}/{spans.length}
179
185
  </Text>
180
186
  </Box>
@@ -191,8 +197,8 @@ function Dashboard({
191
197
  paddingY={0}
192
198
  >
193
199
  <Box marginTop={0} marginBottom={1}>
194
- <Text bold>Recent spans</Text>
195
- {filterErrorsOnly && <Text color="red"> (errors only)</Text>}
200
+ <Text key="recent-spans-title" bold>Recent spans</Text>
201
+ {filterErrorsOnly && <Text key="errors-only-label" color="red"> (errors only)</Text>}
196
202
  </Box>
197
203
 
198
204
  {filtered.length === 0 ? (
@@ -207,8 +213,11 @@ function Dashboard({
207
213
  ? 'yellow'
208
214
  : 'green';
209
215
 
216
+ // Use compound key to ensure uniqueness (spanId + startTime)
217
+ const uniqueKey = `${s.spanId}-${s.startTime}`;
218
+
210
219
  return (
211
- <Box key={s.spanId} flexDirection="row">
220
+ <Box key={uniqueKey} flexDirection="row">
212
221
  <Text color={isSel ? 'cyan' : undefined}>
213
222
  {isSel ? '› ' : ' '}
214
223
  </Text>
@@ -352,6 +361,10 @@ export function renderTerminal(
352
361
  const maxSpans = options.maxSpans ?? 100;
353
362
  const colors = options.colors ?? Boolean(process.stdout.isTTY);
354
363
 
364
+ // Check if stdin supports raw mode (needed for keyboard input)
365
+ // If not, we disable stdin to prevent Ink from throwing an error
366
+ const stdinOption = process.stdin.isTTY ? process.stdin : undefined;
367
+
355
368
  // If stream provided, use it directly
356
369
  if (stream) {
357
370
  try {
@@ -363,6 +376,7 @@ export function renderTerminal(
363
376
  colors={colors}
364
377
  stream={stream}
365
378
  />,
379
+ { stdin: stdinOption },
366
380
  );
367
381
  } catch (error) {
368
382
  console.error('[autotel-terminal] Failed to render dashboard:', error);
@@ -414,6 +428,7 @@ export function renderTerminal(
414
428
  colors={colors}
415
429
  stream={terminalStream}
416
430
  />,
431
+ { stdin: stdinOption },
417
432
  );
418
433
  } catch (error) {
419
434
  console.error('[autotel-terminal] Failed to render dashboard:', error);