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 +21 -0
- package/README.md +393 -0
- package/dist/index.cjs +28 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +29 -21
- package/dist/index.js.map +1 -1
- package/package.json +16 -16
- package/src/index.tsx +34 -19
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
|
+
[](https://www.npmjs.com/package/autotel-terminal)
|
|
6
|
+
[](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.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
setSelected((i) => Math.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
] },
|
|
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);
|
package/dist/index.cjs.map
CHANGED
|
@@ -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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
setSelected((i) => Math.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
] },
|
|
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":"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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, 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": "
|
|
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
|
-
|
|
144
|
-
|
|
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
|
-
|
|
149
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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={
|
|
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);
|