autotel-terminal 2.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/dist/index.cjs +392 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +231 -0
- package/dist/index.d.ts +231 -0
- package/dist/index.js +377 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -0
- package/src/index.tsx +436 -0
- package/src/span-stream.ts +141 -0
- package/src/streaming-processor.ts +113 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var ink = require('ink');
|
|
5
|
+
var autotel = require('autotel');
|
|
6
|
+
var tracerProvider = require('autotel/tracer-provider');
|
|
7
|
+
var exporters = require('autotel/exporters');
|
|
8
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
+
|
|
10
|
+
// src/index.tsx
|
|
11
|
+
|
|
12
|
+
// src/streaming-processor.ts
|
|
13
|
+
var StreamingSpanProcessor = class {
|
|
14
|
+
wrappedProcessor;
|
|
15
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
16
|
+
/**
|
|
17
|
+
* Create a new streaming span processor
|
|
18
|
+
*
|
|
19
|
+
* @param wrappedProcessor - The processor to wrap and forward spans to.
|
|
20
|
+
* If null, spans are only emitted to subscribers (no forwarding).
|
|
21
|
+
*/
|
|
22
|
+
constructor(wrappedProcessor = null) {
|
|
23
|
+
this.wrappedProcessor = wrappedProcessor;
|
|
24
|
+
}
|
|
25
|
+
onStart(span, parentContext) {
|
|
26
|
+
if (this.wrappedProcessor) {
|
|
27
|
+
this.wrappedProcessor.onStart(span, parentContext);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
onEnd(span) {
|
|
31
|
+
for (const subscriber of this.subscribers) {
|
|
32
|
+
try {
|
|
33
|
+
subscriber(span);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error("[autotel-terminal] Subscriber error:", error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (this.wrappedProcessor) {
|
|
39
|
+
this.wrappedProcessor.onEnd(span);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Subscribe to span end events
|
|
44
|
+
*
|
|
45
|
+
* @param callback - Function called when a span ends
|
|
46
|
+
* @returns Unsubscribe function
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const unsubscribe = processor.subscribe((span) => {
|
|
51
|
+
* console.log('Span ended:', span.name)
|
|
52
|
+
* })
|
|
53
|
+
*
|
|
54
|
+
* // Later, unsubscribe
|
|
55
|
+
* unsubscribe()
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
subscribe(callback) {
|
|
59
|
+
this.subscribers.add(callback);
|
|
60
|
+
return () => this.subscribers.delete(callback);
|
|
61
|
+
}
|
|
62
|
+
async forceFlush() {
|
|
63
|
+
if (this.wrappedProcessor) {
|
|
64
|
+
return this.wrappedProcessor.forceFlush();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async shutdown() {
|
|
68
|
+
this.subscribers.clear();
|
|
69
|
+
if (this.wrappedProcessor) {
|
|
70
|
+
return this.wrappedProcessor.shutdown();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
function timeToMs([seconds, nanoseconds]) {
|
|
75
|
+
return seconds * 1e3 + nanoseconds / 1e6;
|
|
76
|
+
}
|
|
77
|
+
function mapStatus(code) {
|
|
78
|
+
if (code === autotel.SpanStatusCode.OK) {
|
|
79
|
+
return "OK";
|
|
80
|
+
}
|
|
81
|
+
if (code === autotel.SpanStatusCode.ERROR) {
|
|
82
|
+
return "ERROR";
|
|
83
|
+
}
|
|
84
|
+
return "UNSET";
|
|
85
|
+
}
|
|
86
|
+
function mapKind(kind) {
|
|
87
|
+
const kindMap = {
|
|
88
|
+
[autotel.SpanKind.INTERNAL]: "INTERNAL",
|
|
89
|
+
[autotel.SpanKind.SERVER]: "SERVER",
|
|
90
|
+
[autotel.SpanKind.CLIENT]: "CLIENT",
|
|
91
|
+
[autotel.SpanKind.PRODUCER]: "PRODUCER",
|
|
92
|
+
[autotel.SpanKind.CONSUMER]: "CONSUMER"
|
|
93
|
+
};
|
|
94
|
+
return kindMap[kind] ?? "UNKNOWN";
|
|
95
|
+
}
|
|
96
|
+
function createTerminalSpanStream(processor) {
|
|
97
|
+
return {
|
|
98
|
+
onSpanEnd(callback) {
|
|
99
|
+
return processor.subscribe((span) => {
|
|
100
|
+
const spanContext = span.spanContext();
|
|
101
|
+
const parentSpanId = "parentSpanId" in span && typeof span.parentSpanId === "string" ? span.parentSpanId : void 0;
|
|
102
|
+
const startTime = timeToMs(span.startTime);
|
|
103
|
+
const endTime = timeToMs(span.endTime);
|
|
104
|
+
const durationMs = endTime - startTime;
|
|
105
|
+
const event = {
|
|
106
|
+
name: span.name,
|
|
107
|
+
spanId: spanContext.spanId,
|
|
108
|
+
traceId: spanContext.traceId,
|
|
109
|
+
parentSpanId,
|
|
110
|
+
startTime,
|
|
111
|
+
endTime,
|
|
112
|
+
durationMs,
|
|
113
|
+
status: mapStatus(span.status.code),
|
|
114
|
+
kind: mapKind(span.kind),
|
|
115
|
+
attributes: span.attributes
|
|
116
|
+
};
|
|
117
|
+
callback(event);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function ms(n) {
|
|
123
|
+
if (n < 1e3) return `${n.toFixed(0)}ms`;
|
|
124
|
+
return `${(n / 1e3).toFixed(2)}s`;
|
|
125
|
+
}
|
|
126
|
+
function truncate(s, width) {
|
|
127
|
+
if (s.length <= width) return s;
|
|
128
|
+
return s.slice(0, Math.max(0, width - 1)) + "\u2026";
|
|
129
|
+
}
|
|
130
|
+
function Dashboard({
|
|
131
|
+
title,
|
|
132
|
+
showStats,
|
|
133
|
+
maxSpans,
|
|
134
|
+
colors,
|
|
135
|
+
stream
|
|
136
|
+
}) {
|
|
137
|
+
const [paused, setPaused] = react.useState(false);
|
|
138
|
+
const [spans, setSpans] = react.useState([]);
|
|
139
|
+
const [selected, setSelected] = react.useState(0);
|
|
140
|
+
const [filterErrorsOnly, setFilterErrorsOnly] = react.useState(false);
|
|
141
|
+
react.useEffect(() => {
|
|
142
|
+
const unsubscribe = stream.onSpanEnd((span) => {
|
|
143
|
+
if (paused) return;
|
|
144
|
+
setSpans((prev) => {
|
|
145
|
+
const next = [span, ...prev];
|
|
146
|
+
return next.slice(0, maxSpans);
|
|
147
|
+
});
|
|
148
|
+
setSelected(0);
|
|
149
|
+
});
|
|
150
|
+
return unsubscribe;
|
|
151
|
+
}, [stream, paused, maxSpans]);
|
|
152
|
+
const filtered = react.useMemo(() => {
|
|
153
|
+
const list = filterErrorsOnly ? spans.filter((s) => s.status === "ERROR") : spans;
|
|
154
|
+
return list;
|
|
155
|
+
}, [spans, filterErrorsOnly]);
|
|
156
|
+
const stats = react.useMemo(() => {
|
|
157
|
+
const total = spans.length;
|
|
158
|
+
const errors = spans.filter((s) => s.status === "ERROR").length;
|
|
159
|
+
const avg = total ? spans.reduce((a, s) => a + s.durationMs, 0) / total : 0;
|
|
160
|
+
const p95 = total ? (() => {
|
|
161
|
+
const sorted = spans.map((s) => s.durationMs).toSorted((a, b) => a - b);
|
|
162
|
+
return sorted[Math.floor(sorted.length * 0.95)] ?? 0;
|
|
163
|
+
})() : 0;
|
|
164
|
+
return { total, errors, avg, p95 };
|
|
165
|
+
}, [spans]);
|
|
166
|
+
const current = filtered[selected];
|
|
167
|
+
ink.useInput((input, key) => {
|
|
168
|
+
if (key.upArrow) setSelected((i) => Math.max(0, i - 1));
|
|
169
|
+
if (key.downArrow)
|
|
170
|
+
setSelected((i) => Math.min(filtered.length - 1, i + 1));
|
|
171
|
+
if (input === "p") setPaused((p) => !p);
|
|
172
|
+
if (input === "e") setFilterErrorsOnly((v) => !v);
|
|
173
|
+
if (input === "c") {
|
|
174
|
+
setSpans([]);
|
|
175
|
+
setSelected(0);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
const headerRight = paused ? "[Paused]" : "[Live]";
|
|
179
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
180
|
+
ink.Box,
|
|
181
|
+
{
|
|
182
|
+
flexDirection: "column",
|
|
183
|
+
borderStyle: "round",
|
|
184
|
+
padding: 1,
|
|
185
|
+
borderColor: colors ? "cyan" : void 0,
|
|
186
|
+
children: [
|
|
187
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { justifyContent: "space-between", marginBottom: 1, children: [
|
|
188
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { bold: true, children: [
|
|
189
|
+
"\u{1F52D} ",
|
|
190
|
+
title
|
|
191
|
+
] }),
|
|
192
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: paused ? "yellow" : "green", children: headerRight })
|
|
193
|
+
] }),
|
|
194
|
+
/* @__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" }),
|
|
196
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
197
|
+
"showing ",
|
|
198
|
+
filtered.length,
|
|
199
|
+
"/",
|
|
200
|
+
spans.length
|
|
201
|
+
] })
|
|
202
|
+
] }),
|
|
203
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 2, children: [
|
|
204
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
205
|
+
ink.Box,
|
|
206
|
+
{
|
|
207
|
+
flexDirection: "column",
|
|
208
|
+
width: "55%",
|
|
209
|
+
borderStyle: "single",
|
|
210
|
+
borderColor: "gray",
|
|
211
|
+
paddingX: 1,
|
|
212
|
+
paddingY: 0,
|
|
213
|
+
children: [
|
|
214
|
+
/* @__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)" })
|
|
217
|
+
] }),
|
|
218
|
+
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
|
+
const isSel = i === selected;
|
|
220
|
+
const statusColor = s.status === "ERROR" ? "red" : s.durationMs > 500 ? "yellow" : "green";
|
|
221
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", children: [
|
|
222
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: isSel ? "cyan" : void 0, children: isSel ? "\u203A " : " " }),
|
|
223
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: colors ? statusColor : void 0, children: truncate(s.name, 26) }),
|
|
224
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
225
|
+
" ",
|
|
226
|
+
ms(s.durationMs)
|
|
227
|
+
] }),
|
|
228
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
229
|
+
" ",
|
|
230
|
+
truncate(s.traceId, 10)
|
|
231
|
+
] })
|
|
232
|
+
] }, s.spanId);
|
|
233
|
+
})
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
),
|
|
237
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
238
|
+
ink.Box,
|
|
239
|
+
{
|
|
240
|
+
flexDirection: "column",
|
|
241
|
+
width: "45%",
|
|
242
|
+
borderStyle: "single",
|
|
243
|
+
borderColor: "gray",
|
|
244
|
+
paddingX: 1,
|
|
245
|
+
paddingY: 0,
|
|
246
|
+
children: [
|
|
247
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginBottom: 1, children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Details" }) }),
|
|
248
|
+
current ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
249
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { children: [
|
|
250
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Name: " }),
|
|
251
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: current.name })
|
|
252
|
+
] }),
|
|
253
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { children: [
|
|
254
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Status: " }),
|
|
255
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: current.status === "ERROR" ? "red" : "green", children: current.status })
|
|
256
|
+
] }),
|
|
257
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { children: [
|
|
258
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Duration: " }),
|
|
259
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: ms(current.durationMs) })
|
|
260
|
+
] }),
|
|
261
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
262
|
+
"Trace: ",
|
|
263
|
+
current.traceId
|
|
264
|
+
] }),
|
|
265
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
266
|
+
"Span: ",
|
|
267
|
+
current.spanId
|
|
268
|
+
] }),
|
|
269
|
+
current.parentSpanId && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
270
|
+
"Parent: ",
|
|
271
|
+
current.parentSpanId
|
|
272
|
+
] }),
|
|
273
|
+
current.kind && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
274
|
+
"Kind: ",
|
|
275
|
+
current.kind
|
|
276
|
+
] }),
|
|
277
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginTop: 1, flexDirection: "column", children: [
|
|
278
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Attributes" }),
|
|
279
|
+
current.attributes && Object.keys(current.attributes).length > 0 ? Object.entries(current.attributes).slice(0, 12).map(([k, v]) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
280
|
+
truncate(k, 22),
|
|
281
|
+
": ",
|
|
282
|
+
truncate(String(v), 34)
|
|
283
|
+
] }, k)) : /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "(none)" })
|
|
284
|
+
] })
|
|
285
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Select a span to view details." })
|
|
286
|
+
]
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
] }),
|
|
290
|
+
showStats && /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
291
|
+
"Spans: ",
|
|
292
|
+
stats.total,
|
|
293
|
+
" | Errors: ",
|
|
294
|
+
stats.errors,
|
|
295
|
+
" | Avg: ",
|
|
296
|
+
ms(stats.avg),
|
|
297
|
+
" ",
|
|
298
|
+
"| P95: ",
|
|
299
|
+
ms(stats.p95)
|
|
300
|
+
] }) })
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
var globalStreamingProcessor = null;
|
|
306
|
+
function canAddSpanProcessor(provider) {
|
|
307
|
+
return typeof provider === "object" && provider !== null && "addSpanProcessor" in provider && typeof provider.addSpanProcessor === "function";
|
|
308
|
+
}
|
|
309
|
+
function renderTerminal(options = {}, stream) {
|
|
310
|
+
const title = options.title ?? "Autotel Trace Inspector";
|
|
311
|
+
const showStats = options.showStats !== false;
|
|
312
|
+
const maxSpans = options.maxSpans ?? 100;
|
|
313
|
+
const colors = options.colors ?? Boolean(process.stdout.isTTY);
|
|
314
|
+
if (stream) {
|
|
315
|
+
try {
|
|
316
|
+
ink.render(
|
|
317
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
318
|
+
Dashboard,
|
|
319
|
+
{
|
|
320
|
+
title,
|
|
321
|
+
showStats,
|
|
322
|
+
maxSpans,
|
|
323
|
+
colors,
|
|
324
|
+
stream
|
|
325
|
+
}
|
|
326
|
+
)
|
|
327
|
+
);
|
|
328
|
+
} catch (error) {
|
|
329
|
+
console.error("[autotel-terminal] Failed to render dashboard:", error);
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
let provider;
|
|
334
|
+
try {
|
|
335
|
+
provider = tracerProvider.getAutotelTracerProvider();
|
|
336
|
+
} catch (error) {
|
|
337
|
+
console.error(
|
|
338
|
+
"[autotel-terminal] Failed to get tracer provider. Call init() first or provide a stream.",
|
|
339
|
+
error
|
|
340
|
+
);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (!provider) {
|
|
344
|
+
console.error(
|
|
345
|
+
"[autotel-terminal] No tracer provider found. Call init() first or provide a stream."
|
|
346
|
+
);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (!canAddSpanProcessor(provider)) {
|
|
350
|
+
console.error(
|
|
351
|
+
"[autotel-terminal] TracerProvider does not support addSpanProcessor. Provide a stream manually."
|
|
352
|
+
);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
globalStreamingProcessor = new StreamingSpanProcessor(null);
|
|
356
|
+
const terminalStream = createTerminalSpanStream(globalStreamingProcessor);
|
|
357
|
+
provider.addSpanProcessor(globalStreamingProcessor);
|
|
358
|
+
try {
|
|
359
|
+
ink.render(
|
|
360
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
361
|
+
Dashboard,
|
|
362
|
+
{
|
|
363
|
+
title,
|
|
364
|
+
showStats,
|
|
365
|
+
maxSpans,
|
|
366
|
+
colors,
|
|
367
|
+
stream: terminalStream
|
|
368
|
+
}
|
|
369
|
+
)
|
|
370
|
+
);
|
|
371
|
+
} catch (error) {
|
|
372
|
+
console.error("[autotel-terminal] Failed to render dashboard:", error);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
Object.defineProperty(exports, "SpanKind", {
|
|
377
|
+
enumerable: true,
|
|
378
|
+
get: function () { return autotel.SpanKind; }
|
|
379
|
+
});
|
|
380
|
+
Object.defineProperty(exports, "SpanStatusCode", {
|
|
381
|
+
enumerable: true,
|
|
382
|
+
get: function () { return autotel.SpanStatusCode; }
|
|
383
|
+
});
|
|
384
|
+
Object.defineProperty(exports, "PrettyConsoleExporter", {
|
|
385
|
+
enumerable: true,
|
|
386
|
+
get: function () { return exporters.PrettyConsoleExporter; }
|
|
387
|
+
});
|
|
388
|
+
exports.StreamingSpanProcessor = StreamingSpanProcessor;
|
|
389
|
+
exports.createTerminalSpanStream = createTerminalSpanStream;
|
|
390
|
+
exports.renderTerminal = renderTerminal;
|
|
391
|
+
//# sourceMappingURL=index.cjs.map
|
|
392
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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"]}
|