autotel-terminal 17.0.3 → 17.0.5

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/cli.cjs CHANGED
@@ -111,6 +111,25 @@ function createTerminalSpanStream(processor) {
111
111
  const startTime = timeToMs(span.startTime);
112
112
  const endTime = timeToMs(span.endTime);
113
113
  const durationMs = endTime - startTime;
114
+ const resourceAttrs = span.resource?.attributes ?? {};
115
+ const spanAttrs = span.attributes;
116
+ const mergedAttrs = {};
117
+ for (const [k, v] of Object.entries(resourceAttrs)) {
118
+ mergedAttrs[k] = v;
119
+ }
120
+ for (const [k, v] of Object.entries(spanAttrs)) {
121
+ mergedAttrs[k] = v;
122
+ }
123
+ const spanEvents = span.events?.length ? span.events.map((e) => ({
124
+ name: e.name,
125
+ timeMs: timeToMs(e.time),
126
+ attributes: e.attributes
127
+ })) : void 0;
128
+ const spanLinks = span.links?.length ? span.links.map((l) => ({
129
+ traceId: l.context.traceId,
130
+ spanId: l.context.spanId,
131
+ attributes: l.attributes
132
+ })) : void 0;
114
133
  const event = {
115
134
  name: span.name,
116
135
  spanId: spanContext.spanId,
@@ -121,7 +140,9 @@ function createTerminalSpanStream(processor) {
121
140
  durationMs,
122
141
  status: mapStatus(span.status.code),
123
142
  kind: mapKind(span.kind),
124
- attributes: span.attributes
143
+ attributes: mergedAttrs,
144
+ ...spanEvents ? { events: spanEvents } : {},
145
+ ...spanLinks ? { links: spanLinks } : {}
125
146
  };
126
147
  callback(event);
127
148
  });
@@ -130,6 +151,10 @@ function createTerminalSpanStream(processor) {
130
151
  }
131
152
 
132
153
  // src/lib/trace-model.ts
154
+ function spanServiceName(span) {
155
+ const svc = span.attributes?.["service.name"];
156
+ return typeof svc === "string" ? svc : "unknown";
157
+ }
133
158
  function buildTraceMap(spans, maxTraces = 50) {
134
159
  const byTrace = /* @__PURE__ */ new Map();
135
160
  for (const s of spans) {
@@ -153,6 +178,7 @@ function buildTraceSummaries(traceMap) {
153
178
  const durationMs = root ? root.durationMs : 0;
154
179
  const hasError = traceSpans.some((s) => s.status === "ERROR");
155
180
  const lastEndTime = Math.max(...traceSpans.map((s) => s.endTime));
181
+ const services = [...new Set(traceSpans.map((s) => spanServiceName(s)))];
156
182
  summaries.push({
157
183
  traceId,
158
184
  rootName: root?.name ?? "unknown",
@@ -160,7 +186,8 @@ function buildTraceSummaries(traceMap) {
160
186
  hasError,
161
187
  spanCount: traceSpans.length,
162
188
  lastEndTime,
163
- spans: traceSpans
189
+ spans: traceSpans,
190
+ services
164
191
  });
165
192
  }
166
193
  return summaries;
@@ -276,6 +303,45 @@ function buildWaterfallBar(spanStart, spanDuration, traceStart, traceDuration, w
276
303
  const trailing = Math.max(0, width - clampedStart - clampedLen);
277
304
  return " ".repeat(clampedStart) + "\u2588".repeat(clampedLen) + " ".repeat(trailing);
278
305
  }
306
+ function buildTimeRuler(totalMs, width) {
307
+ const left = "0ms";
308
+ const right = formatDurationMs(totalMs);
309
+ const mid = formatDurationMs(totalMs / 2);
310
+ const midPos = Math.floor(width / 2) - Math.floor(mid.length / 2);
311
+ const rightPos = width - right.length;
312
+ const chars = Array.from({ length: width }).fill(" ");
313
+ for (let i = 0; i < left.length && i < width; i++) chars[i] = left[i];
314
+ if (midPos > left.length + 1 && midPos + mid.length < rightPos - 1) {
315
+ for (let i = 0; i < mid.length; i++) chars[midPos + i] = mid[i];
316
+ }
317
+ for (let i = 0; i < right.length; i++) chars[rightPos + i] = right[i];
318
+ return chars.join("");
319
+ }
320
+
321
+ // src/lib/service-colors.ts
322
+ var SERVICE_COLORS = [
323
+ "cyan",
324
+ "magenta",
325
+ "blue",
326
+ "yellow",
327
+ "green",
328
+ "redBright",
329
+ "cyanBright",
330
+ "magentaBright",
331
+ "blueBright",
332
+ "yellowBright",
333
+ "greenBright"
334
+ ];
335
+ function hashString(s) {
336
+ let hash = 0;
337
+ for (let i = 0; i < s.length; i++) {
338
+ hash = Math.trunc((hash << 5) - hash + s.codePointAt(i));
339
+ }
340
+ return Math.abs(hash);
341
+ }
342
+ function getServiceColor(serviceName) {
343
+ return SERVICE_COLORS[hashString(serviceName) % SERVICE_COLORS.length];
344
+ }
279
345
 
280
346
  // src/lib/log-model.ts
281
347
  function filterLogsBySearch(logs, searchQuery, minLevel) {
@@ -516,6 +582,123 @@ function buildErrorSummaries(traceSummaries) {
516
582
  return out;
517
583
  }
518
584
 
585
+ // src/lib/topology-model.ts
586
+ function getServiceName2(span) {
587
+ const attrs = span.attributes ?? {};
588
+ const serviceName = attrs["service.name"] ?? attrs["resource.service.name"];
589
+ return serviceName || "unknown";
590
+ }
591
+ function getPeerService(span) {
592
+ const attrs = span.attributes ?? {};
593
+ const peerService = attrs["peer.service"] ?? attrs["db.system"] ?? attrs["messaging.system"] ?? attrs["http.host"];
594
+ return peerService ?? null;
595
+ }
596
+ function buildServiceGraph(spans) {
597
+ const byService = /* @__PURE__ */ new Map();
598
+ for (const span of spans) {
599
+ const svc = getServiceName2(span);
600
+ const entry = byService.get(svc) ?? {
601
+ durations: [],
602
+ spanCount: 0,
603
+ errorCount: 0
604
+ };
605
+ entry.spanCount += 1;
606
+ entry.durations.push(span.durationMs);
607
+ if (span.status === "ERROR") entry.errorCount += 1;
608
+ byService.set(svc, entry);
609
+ }
610
+ const services = [];
611
+ for (const [serviceName, { durations, spanCount, errorCount }] of byService) {
612
+ if (spanCount === 0) continue;
613
+ const avgDurationMs = durations.reduce((acc, d) => acc + d, 0) / durations.length;
614
+ const sorted = durations.toSorted((a, b) => a - b);
615
+ const p95Index = Math.floor(sorted.length * 0.95);
616
+ const p95DurationMs = sorted[p95Index] ?? sorted.at(-1) ?? 0;
617
+ services.push({
618
+ serviceName,
619
+ spanCount,
620
+ errorCount,
621
+ avgDurationMs,
622
+ p95DurationMs
623
+ });
624
+ }
625
+ const byEdge = /* @__PURE__ */ new Map();
626
+ for (const span of spans) {
627
+ const to = getPeerService(span);
628
+ if (!to) continue;
629
+ const from = getServiceName2(span);
630
+ const key = `${from}\u2192${to}`;
631
+ const entry = byEdge.get(key) ?? { spanCount: 0, errorCount: 0 };
632
+ entry.spanCount += 1;
633
+ if (span.status === "ERROR") entry.errorCount += 1;
634
+ byEdge.set(key, entry);
635
+ }
636
+ const edges = [];
637
+ for (const [key, { spanCount, errorCount }] of byEdge) {
638
+ const [fromService, toService] = key.split("\u2192");
639
+ edges.push({ fromService, toService, spanCount, errorCount });
640
+ }
641
+ services.sort((a, b) => b.spanCount - a.spanCount);
642
+ edges.sort((a, b) => b.spanCount - a.spanCount);
643
+ return { services, edges };
644
+ }
645
+
646
+ // src/lib/topology-render.ts
647
+ function renderTopologyAscii(graph) {
648
+ if (graph.services.length === 0) {
649
+ return [" No services detected yet."];
650
+ }
651
+ const targetServices = new Set(graph.edges.map((e) => e.toService));
652
+ const roots = graph.services.filter(
653
+ (s) => !targetServices.has(s.serviceName)
654
+ );
655
+ const rootList = roots.length > 0 ? roots : [...graph.services];
656
+ const lines = [];
657
+ for (const root of rootList) {
658
+ renderNode(graph, root.serviceName, "", true, lines, /* @__PURE__ */ new Set());
659
+ }
660
+ return lines;
661
+ }
662
+ function renderNode(graph, serviceName, prefix, isRoot, lines, ancestors) {
663
+ const svc = graph.services.find((s) => s.serviceName === serviceName);
664
+ const label = svc ? formatServiceLine(svc) : `[${serviceName}]`;
665
+ if (isRoot) {
666
+ lines.push(label);
667
+ }
668
+ if (ancestors.has(serviceName)) return;
669
+ const pathAncestors = new Set(ancestors);
670
+ pathAncestors.add(serviceName);
671
+ const outgoing = graph.edges.filter((e) => e.fromService === serviceName);
672
+ for (let i = 0; i < outgoing.length; i++) {
673
+ const edge = outgoing[i];
674
+ const isLast = i === outgoing.length - 1;
675
+ const connector = isLast ? "\u2514" : "\u251C";
676
+ const childPrefix = isLast ? " " : "\u2502 ";
677
+ const downstream = graph.services.find(
678
+ (s) => s.serviceName === edge.toService
679
+ );
680
+ const edgeLabel = formatEdgeLabel(edge);
681
+ const downstreamLabel = downstream ? formatServiceLine(downstream) : `[${edge.toService}]`;
682
+ lines.push(`${prefix} ${connector}\u2500\u2500${edgeLabel}\u2500\u2500\u2192 ${downstreamLabel}`);
683
+ renderNode(
684
+ graph,
685
+ edge.toService,
686
+ prefix + " " + childPrefix,
687
+ false,
688
+ lines,
689
+ pathAncestors
690
+ );
691
+ }
692
+ }
693
+ function formatServiceLine(svc) {
694
+ const errPart = svc.errorCount > 0 ? ` \xB7 ${svc.errorCount} err` : "";
695
+ return `[${svc.serviceName}] ${svc.spanCount} spans${errPart} \xB7 p95 ${formatDurationMs(svc.p95DurationMs)}`;
696
+ }
697
+ function formatEdgeLabel(edge) {
698
+ const errPart = edge.errorCount > 0 ? `, ${edge.errorCount} err` : "";
699
+ return `(${edge.spanCount}${errPart})`;
700
+ }
701
+
519
702
  // src/lib/export-model.ts
520
703
  function exportTraceToJson(trace, logs) {
521
704
  const exported = {
@@ -1033,6 +1216,8 @@ function Dashboard({
1033
1216
  [spansForSelectedService]
1034
1217
  );
1035
1218
  const selectedTraceSummary = drilldownTraceId == null ? filteredSummaries[selected] ?? null : filteredSummaries.find((t2) => t2.traceId === drilldownTraceId) ?? null;
1219
+ const serviceGraph = react.useMemo(() => buildServiceGraph(spans), [spans]);
1220
+ const topologyLines = react.useMemo(() => renderTopologyAscii(serviceGraph), [serviceGraph]);
1036
1221
  const errorSummaries = react.useMemo(
1037
1222
  () => buildErrorSummaries(traceSummaries),
1038
1223
  [traceSummaries]
@@ -1439,6 +1624,13 @@ Currently viewing trace ${drilldownTraceId}. This trace has ${drilldownSpans.len
1439
1624
  setDrilldownSelectedIndex(0);
1440
1625
  setDrilldownScrollOffset(0);
1441
1626
  }
1627
+ if (input === "G") {
1628
+ setViewMode((m) => m === "topology" ? "trace" : "topology");
1629
+ setSelected(0);
1630
+ setDrilldownTraceId(null);
1631
+ setDrilldownSelectedIndex(0);
1632
+ setDrilldownScrollOffset(0);
1633
+ }
1442
1634
  if (input === "c") {
1443
1635
  setSpans([]);
1444
1636
  setLogs([]);
@@ -1580,20 +1772,31 @@ ${json}
1580
1772
  { isActive: isRawModeSupported }
1581
1773
  );
1582
1774
  const headerRight = recording ? "[Recording]" : paused ? "[Paused]" : "[Live]";
1583
- const headerModeLabel = viewMode === "trace" ? "traces" : viewMode === "span" ? "spans" : viewMode === "log" ? "logs" : viewMode === "service-summary" ? "services" : "errors";
1775
+ const headerModeLabel = viewMode === "trace" ? "traces" : viewMode === "span" ? "spans" : viewMode === "log" ? "logs" : viewMode === "service-summary" ? "services" : viewMode === "topology" ? "topology" : "errors";
1584
1776
  const showNewError = newErrorCount > 0;
1585
1777
  function renderTreeRow(node, index) {
1586
1778
  const isSel = drilldownTraceId != null && index === drilldownSelectedIndex;
1587
1779
  const prefix = node.depth === 0 ? "" : " ".repeat(node.depth) + (node.children.length > 0 ? "\u251C\u2500\u2500 " : "\u2514\u2500\u2500 ");
1780
+ const svcName = spanServiceName(node.span);
1781
+ const svcColor = getServiceColor(svcName);
1588
1782
  const statusColor = node.span.status === "ERROR" ? "red" : node.span.durationMs > 500 ? "yellow" : "green";
1589
1783
  return /* @__PURE__ */ jsxRuntime.jsxs(
1590
1784
  ink.Box,
1591
1785
  {
1592
1786
  flexDirection: "row",
1593
1787
  children: [
1594
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: isSel ? "cyan" : void 0, children: isSel ? "\u203A " : " " }),
1788
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { backgroundColor: isSel ? "blue" : void 0, color: isSel ? "white" : void 0, children: isSel ? "\u25B8 " : " " }),
1789
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: node.span.status === "ERROR" ? "red" : void 0, children: node.span.status === "ERROR" ? "\u2717" : " " }),
1595
1790
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: prefix }),
1596
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: colors ? statusColor : void 0, children: truncate(node.span.name, 24) }),
1791
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: colors ? statusColor : void 0, children: truncate(node.span.name, 23) }),
1792
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: svcColor, children: [
1793
+ " ",
1794
+ truncate(svcName, 10)
1795
+ ] }),
1796
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
1797
+ " ",
1798
+ node.span.kind ?? ""
1799
+ ] }),
1597
1800
  /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
1598
1801
  " ",
1599
1802
  formatDurationMs(node.span.durationMs)
@@ -1651,18 +1854,323 @@ ${json}
1651
1854
  ] })
1652
1855
  ] }) : /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", justifyContent: "space-between", children: [
1653
1856
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: drilldownTraceId == null ? "\u2191/\u2193 select \u2022 Enter open \u2022 Tab cycle tabs \u2022 Esc back \u2022 T trace \u2022 L logs \u2022 a AI \u2022 ? help" : "\u2191/\u2193 select \u2022 Tab cycle tabs \u2022 Esc back \u2022 T trace \u2022 L logs \u2022 a AI \u2022 ? help" }),
1654
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: viewMode === "trace" ? `traces ${filteredSummaries.length}/${traceSummaries.length}` : viewMode === "span" ? `spans ${filteredSpans.length}/${spans.length}` : viewMode === "service-summary" ? `services ${serviceStats.length}` : viewMode === "errors" ? `errors ${filteredErrorSummaries.length}/${errorSummaries.length}` : `logs ${filteredLogs.length}/${logs.length}` })
1857
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: viewMode === "trace" ? `traces ${filteredSummaries.length}/${traceSummaries.length}` : viewMode === "span" ? `spans ${filteredSpans.length}/${spans.length}` : viewMode === "service-summary" ? `services ${serviceStats.length}` : viewMode === "errors" ? `errors ${filteredErrorSummaries.length}/${errorSummaries.length}` : viewMode === "topology" ? `services ${serviceGraph.services.length} \xB7 edges ${serviceGraph.edges.length}` : `logs ${filteredLogs.length}/${logs.length}` })
1655
1858
  ] }),
1656
- showHelp && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Views: t/l/v/E \u2022 Search: / \u2022 Filters: e/S/R/H/f/x \u2022 Capture: p/r/J \u2022 AI: a \u2022 Clear: c" })
1859
+ showHelp && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Views: t/l/v/E/G \u2022 Search: / \u2022 Filters: e/S/R/H/f/x \u2022 Capture: p/r/J \u2022 AI: a \u2022 Clear: c" })
1657
1860
  ] }),
1658
1861
  /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginBottom: 0, children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: spanFilters.serviceName || spanFilters.route || spanFilters.statusGroup !== "all" || spanFilters.traceId ? `filters:${spanFilters.serviceName ? ` service=${spanFilters.serviceName}` : ""}${spanFilters.route ? ` route=${spanFilters.route}` : ""}${spanFilters.statusGroup && spanFilters.statusGroup !== "all" ? ` status=${spanFilters.statusGroup}` : ""}${spanFilters.traceId ? ` trace=${spanFilters.traceId.slice(0, 8)}\u2026` : ""}` : "filters: none" }) }),
1659
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 2, children: [
1862
+ viewMode === "topology" && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [
1863
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Service Topology" }),
1864
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Press G to toggle \xB7 Shows service dependencies from span data" }),
1865
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", marginTop: 1, children: topologyLines.map((line, i) => {
1866
+ const hasErr = line.includes(" err");
1867
+ return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: hasErr ? "red" : void 0, children: line }, `topo-${i}`);
1868
+ }) })
1869
+ ] }),
1870
+ viewMode !== "topology" && (drilldownTraceId != null ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, paddingY: 0, children: [
1871
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginBottom: 0, flexDirection: "column", children: [
1872
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { bold: true, children: [
1873
+ "Trace ",
1874
+ drilldownTraceId.slice(0, 16),
1875
+ "\u2026"
1876
+ ] }),
1877
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 1, children: [
1878
+ drilldownSummary?.services?.map((svc) => /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: getServiceColor(svc), children: svc }, svc)),
1879
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "\xB7" }),
1880
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: drilldownSummary?.rootName ?? "unknown" }),
1881
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "\xB7" }),
1882
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
1883
+ drilldownSpans.length,
1884
+ " spans"
1885
+ ] }),
1886
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "\xB7" }),
1887
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
1888
+ drilldownSummary?.services?.length ?? 0,
1889
+ " services"
1890
+ ] }),
1891
+ drilldownSummary?.hasError && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "red", children: [
1892
+ " ",
1893
+ "ERROR"
1894
+ ] })
1895
+ ] }),
1896
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 2, children: [
1897
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Duration: " }),
1898
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "green", children: formatDurationMs(drilldownSummary?.durationMs ?? 0) })
1899
+ ] })
1900
+ ] }),
1901
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginBottom: 0, flexDirection: "row", gap: 2, children: [
1902
+ /* @__PURE__ */ jsxRuntime.jsx(
1903
+ ink.Text,
1904
+ {
1905
+ color: drilldownTab === "timeline" ? "blue" : void 0,
1906
+ dimColor: drilldownTab !== "timeline",
1907
+ bold: drilldownTab === "timeline",
1908
+ children: "Timeline"
1909
+ }
1910
+ ),
1911
+ /* @__PURE__ */ jsxRuntime.jsxs(
1912
+ ink.Text,
1913
+ {
1914
+ color: drilldownTab === "spans" ? "blue" : void 0,
1915
+ dimColor: drilldownTab !== "spans",
1916
+ bold: drilldownTab === "spans",
1917
+ children: [
1918
+ "Spans (",
1919
+ drilldownSpans.length,
1920
+ ")"
1921
+ ]
1922
+ }
1923
+ ),
1924
+ /* @__PURE__ */ jsxRuntime.jsxs(
1925
+ ink.Text,
1926
+ {
1927
+ color: drilldownTab === "logs" ? "blue" : void 0,
1928
+ dimColor: drilldownTab !== "logs",
1929
+ bold: drilldownTab === "logs",
1930
+ children: [
1931
+ "Logs (",
1932
+ drilldownLogs.length,
1933
+ ")"
1934
+ ]
1935
+ }
1936
+ )
1937
+ ] }),
1938
+ drilldownTab === "timeline" && (() => {
1939
+ const NAME_COL = 28;
1940
+ const SERVICE_COL = 12;
1941
+ const KIND_COL = 10;
1942
+ let traceStartMs = Infinity;
1943
+ for (const s of drilldownSummary?.spans ?? []) {
1944
+ if (s.startTime < traceStartMs) traceStartMs = s.startTime;
1945
+ }
1946
+ if (traceStartMs === Infinity) traceStartMs = 0;
1947
+ const traceDurMs = drilldownSummary?.durationMs ?? 1;
1948
+ const WATERFALL_WIDTH = 44;
1949
+ const items = drilldownTimeline.slice(drilldownScrollOffset, drilldownScrollOffset + LIST_HEIGHT);
1950
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1951
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", children: [
1952
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "".padEnd(NAME_COL + SERVICE_COL + KIND_COL + 2) }),
1953
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: buildTimeRuler(traceDurMs, WATERFALL_WIDTH) })
1954
+ ] }),
1955
+ items.map((item, i) => {
1956
+ const isSel = i + drilldownScrollOffset === drilldownSelectedIndex;
1957
+ if (item.type === "span" && item.span) {
1958
+ const s = item.span;
1959
+ const node = drilldownTree.find(
1960
+ (n) => n.span.spanId === s.spanId
1961
+ );
1962
+ const depth = node?.depth ?? 0;
1963
+ const indent = " ".repeat(Math.min(depth, 4));
1964
+ const nameWidth = NAME_COL - Math.min(depth, 4) * 2 - 1;
1965
+ const svcName = spanServiceName(s);
1966
+ const svcColor = getServiceColor(svcName);
1967
+ const kindStr = (s.kind ?? "").padEnd(KIND_COL);
1968
+ const svcStr = truncate(svcName, SERVICE_COL - 2).padEnd(SERVICE_COL);
1969
+ const errorMark = s.status === "ERROR" ? "\u2717" : " ";
1970
+ const namePart = `${indent}${truncate(s.name, nameWidth - 1)}`.padEnd(NAME_COL - 1);
1971
+ const bar = buildWaterfallBar(
1972
+ s.startTime,
1973
+ s.durationMs,
1974
+ traceStartMs,
1975
+ traceDurMs,
1976
+ WATERFALL_WIDTH
1977
+ );
1978
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", children: [
1979
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { backgroundColor: isSel ? "blue" : void 0, color: isSel ? "white" : void 0, children: [
1980
+ isSel ? "\u25B8" : " ",
1981
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: s.status === "ERROR" ? "red" : void 0, children: errorMark }),
1982
+ namePart
1983
+ ] }),
1984
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: svcColor, children: [
1985
+ " ",
1986
+ svcStr
1987
+ ] }),
1988
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: kindStr }),
1989
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: s.status === "ERROR" ? "red" : svcColor, children: bar }),
1990
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: s.status === "ERROR" ? "red" : svcColor, children: [
1991
+ " ",
1992
+ formatDurationMs(s.durationMs)
1993
+ ] })
1994
+ ] }, `${s.spanId}-${i}`);
1995
+ } else if (item.type === "log" && item.log) {
1996
+ const l = item.log;
1997
+ const levelColor = l.level === "error" ? "red" : l.level === "warn" ? "yellow" : "blue";
1998
+ const relTime = drilldownSummary ? `+${formatDurationMs(l.time - traceStartMs)}` : "";
1999
+ const logName = ` ${l.level.toUpperCase()} ${truncate(l.message, NAME_COL - 8)}`.padEnd(NAME_COL);
2000
+ const logOffset = drilldownSummary ? Math.floor((l.time - traceStartMs) / traceDurMs * WATERFALL_WIDTH) : 0;
2001
+ const clampedOffset = Math.max(0, Math.min(logOffset, WATERFALL_WIDTH - 1));
2002
+ const logBar = " ".repeat(clampedOffset) + "\xB7" + " ".repeat(WATERFALL_WIDTH - clampedOffset - 1);
2003
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", children: [
2004
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { backgroundColor: isSel ? "blue" : void 0, color: isSel ? "white" : void 0, children: [
2005
+ isSel ? "\u25B8" : " ",
2006
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: levelColor, children: logName })
2007
+ ] }),
2008
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: " ".padEnd(SERVICE_COL + KIND_COL + 1) }),
2009
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: logBar }),
2010
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2011
+ " ",
2012
+ relTime
2013
+ ] })
2014
+ ] }, `log-${i}`);
2015
+ }
2016
+ return null;
2017
+ }),
2018
+ Array.from({ length: Math.max(0, LIST_HEIGHT - items.length) }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: " " }) }, `pad-${i}`))
2019
+ ] });
2020
+ })(),
2021
+ drilldownTab === "spans" && drilldownTree.slice(drilldownScrollOffset, drilldownScrollOffset + LIST_HEIGHT).map((node, i) => renderTreeRow(node, i + drilldownScrollOffset)),
2022
+ drilldownTab === "spans" && Array.from({ length: Math.max(0, LIST_HEIGHT - Math.min(drilldownTree.length, LIST_HEIGHT)) }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: " " }) }, `pad-${i}`)),
2023
+ drilldownTab === "logs" && drilldownLogs.slice(drilldownScrollOffset, drilldownScrollOffset + LIST_HEIGHT).map((log, i) => {
2024
+ const isSel = i + drilldownScrollOffset === drilldownSelectedIndex;
2025
+ const levelColor = log.level === "error" ? "red" : log.level === "warn" ? "yellow" : log.level === "info" ? "green" : void 0;
2026
+ return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { children: /* @__PURE__ */ jsxRuntime.jsxs(
2027
+ ink.Text,
2028
+ {
2029
+ backgroundColor: isSel ? "blue" : void 0,
2030
+ color: isSel ? "white" : void 0,
2031
+ children: [
2032
+ isSel ? "\u25B8" : " ",
2033
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: levelColor, children: [
2034
+ " ",
2035
+ log.level.toUpperCase()
2036
+ ] }),
2037
+ " ",
2038
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2039
+ "[",
2040
+ truncate(log.message, 50),
2041
+ "]"
2042
+ ] })
2043
+ ]
2044
+ }
2045
+ ) }, `log-${i}`);
2046
+ }),
2047
+ drilldownTab === "logs" && Array.from({ length: Math.max(0, LIST_HEIGHT - Math.min(drilldownLogs.length, LIST_HEIGHT)) }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: " " }) }, `pad-${i}`)),
2048
+ drilldownSelectedItem?.type === "span" && drilldownSelectedItem.span && (() => {
2049
+ const span = drilldownSelectedItem.span;
2050
+ const { key: keyAttrs, rest: restAttrs } = keyAttrsAndRest(span.attributes);
2051
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginTop: 1, flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
2052
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 2, children: [
2053
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: span.name }),
2054
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: getServiceColor(spanServiceName(span)), children: spanServiceName(span) }),
2055
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: span.kind ?? "" }),
2056
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: span.status === "ERROR" ? "red" : "green", children: span.status }),
2057
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: formatDurationMs(span.durationMs) })
2058
+ ] }),
2059
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 2, children: [
2060
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2061
+ "Trace: ",
2062
+ span.traceId
2063
+ ] }),
2064
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2065
+ "Span: ",
2066
+ span.spanId
2067
+ ] }),
2068
+ span.parentSpanId && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2069
+ "Parent: ",
2070
+ span.parentSpanId
2071
+ ] })
2072
+ ] }),
2073
+ keyAttrs.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginTop: 1, flexDirection: "column", children: [
2074
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Key attributes" }),
2075
+ keyAttrs.slice(0, 6).map(([k, v]) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2076
+ truncate(k, 18),
2077
+ ": ",
2078
+ truncate(String(v), 28)
2079
+ ] }, k))
2080
+ ] }),
2081
+ restAttrs.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginTop: 1, flexDirection: "column", children: [
2082
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Attributes" }),
2083
+ restAttrs.slice(0, 8).map(([k, v]) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2084
+ truncate(k, 18),
2085
+ ": ",
2086
+ truncate(String(v), 28)
2087
+ ] }, k))
2088
+ ] }),
2089
+ span.events && span.events.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", marginTop: 0, children: [
2090
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { bold: true, dimColor: true, children: [
2091
+ "Events (",
2092
+ span.events.length,
2093
+ ")"
2094
+ ] }),
2095
+ span.events.slice(0, 5).map((ev, i) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", children: [
2096
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: ev.name === "exception" ? "red" : "yellow", children: [
2097
+ " ",
2098
+ "\u25C6",
2099
+ " ",
2100
+ truncate(ev.name, 20)
2101
+ ] }),
2102
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2103
+ " ",
2104
+ "+",
2105
+ formatDurationMs(ev.timeMs - span.startTime)
2106
+ ] }),
2107
+ ev.attributes && Object.keys(ev.attributes).length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2108
+ " ",
2109
+ Object.entries(ev.attributes).slice(0, 2).map(([k, v]) => `${k}=${String(v)}`).join(" ")
2110
+ ] })
2111
+ ] }, `ev-${i}`))
2112
+ ] }),
2113
+ span.links && span.links.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", marginTop: 0, children: [
2114
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { bold: true, dimColor: true, children: [
2115
+ "Links (",
2116
+ span.links.length,
2117
+ ")"
2118
+ ] }),
2119
+ span.links.slice(0, 5).map((lnk, i) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", children: [
2120
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "cyan", children: [
2121
+ " ",
2122
+ "\u2192",
2123
+ " trace:",
2124
+ lnk.traceId.slice(0, 8),
2125
+ "\u2026",
2126
+ " span:",
2127
+ lnk.spanId.slice(0, 8),
2128
+ "\u2026"
2129
+ ] }),
2130
+ lnk.attributes && Object.keys(lnk.attributes).length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2131
+ " ",
2132
+ Object.entries(lnk.attributes).slice(0, 2).map(([k, v]) => `${k}=${String(v)}`).join(" ")
2133
+ ] })
2134
+ ] }, `lnk-${i}`))
2135
+ ] })
2136
+ ] });
2137
+ })(),
2138
+ drilldownSelectedItem?.type === "log" && drilldownSelectedItem.log && (() => {
2139
+ const log = drilldownSelectedItem.log;
2140
+ return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginTop: 1, flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
2141
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 2, children: [
2142
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: log.level.toUpperCase() }),
2143
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: log.message })
2144
+ ] }),
2145
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { children: [
2146
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Time: " }),
2147
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: new Date(log.time).toISOString() })
2148
+ ] }),
2149
+ log.traceId && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2150
+ "Trace: ",
2151
+ log.traceId
2152
+ ] }),
2153
+ log.spanId && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2154
+ "Span: ",
2155
+ log.spanId
2156
+ ] }),
2157
+ log.attributes && Object.keys(log.attributes).length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginTop: 1, flexDirection: "column", children: [
2158
+ /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Attributes" }),
2159
+ Object.entries(log.attributes).slice(0, 10).map(([k, v]) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2160
+ truncate(k, 18),
2161
+ ": ",
2162
+ truncate(String(v), 40)
2163
+ ] }, k))
2164
+ ] })
2165
+ ] });
2166
+ })()
2167
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", gap: 2, children: [
1660
2168
  /* @__PURE__ */ jsxRuntime.jsxs(
1661
2169
  ink.Box,
1662
2170
  {
1663
2171
  flexDirection: "column",
1664
2172
  width: "55%",
1665
- borderStyle: "single",
2173
+ borderStyle: "round",
1666
2174
  borderColor: "gray",
1667
2175
  paddingX: 1,
1668
2176
  paddingY: 0,
@@ -1679,156 +2187,7 @@ ${json}
1679
2187
  searchQuery
1680
2188
  ] }, "search-label")
1681
2189
  ] }),
1682
- drilldownTraceId != null && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1683
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginBottom: 0, flexDirection: "row", gap: 2, children: [
1684
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "yellow", children: drilldownSummary?.rootName ?? "unknown" }),
1685
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
1686
- drilldownTraceId.slice(0, 16),
1687
- "\u2026"
1688
- ] }),
1689
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "green", children: drilldownSummary ? formatDurationMs(drilldownSummary.durationMs) : "?" }),
1690
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
1691
- drilldownSpans.length,
1692
- " spans \u2022 ",
1693
- drilldownLogs.length,
1694
- " logs"
1695
- ] })
1696
- ] }),
1697
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginBottom: 0, flexDirection: "row", gap: 2, children: [
1698
- /* @__PURE__ */ jsxRuntime.jsx(
1699
- ink.Text,
1700
- {
1701
- color: drilldownTab === "timeline" ? "yellow" : void 0,
1702
- dimColor: drilldownTab !== "timeline",
1703
- underline: drilldownTab === "timeline",
1704
- children: "Timeline"
1705
- }
1706
- ),
1707
- /* @__PURE__ */ jsxRuntime.jsxs(
1708
- ink.Text,
1709
- {
1710
- color: drilldownTab === "spans" ? "yellow" : void 0,
1711
- dimColor: drilldownTab !== "spans",
1712
- underline: drilldownTab === "spans",
1713
- children: [
1714
- "Spans (",
1715
- drilldownSpans.length,
1716
- ")"
1717
- ]
1718
- }
1719
- ),
1720
- /* @__PURE__ */ jsxRuntime.jsxs(
1721
- ink.Text,
1722
- {
1723
- color: drilldownTab === "logs" ? "yellow" : void 0,
1724
- dimColor: drilldownTab !== "logs",
1725
- underline: drilldownTab === "logs",
1726
- children: [
1727
- "Logs (",
1728
- drilldownLogs.length,
1729
- ")"
1730
- ]
1731
- }
1732
- )
1733
- ] })
1734
- ] }),
1735
- drilldownTraceId != null && drilldownTab === "timeline" && (() => {
1736
- let traceStartMs = Infinity;
1737
- for (const s of drilldownSummary?.spans ?? []) {
1738
- if (s.startTime < traceStartMs) traceStartMs = s.startTime;
1739
- }
1740
- if (traceStartMs === Infinity) traceStartMs = 0;
1741
- const traceDurMs = drilldownSummary?.durationMs ?? 1;
1742
- const WATERFALL_WIDTH = 24;
1743
- const items = drilldownTimeline.slice(drilldownScrollOffset, drilldownScrollOffset + LIST_HEIGHT);
1744
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1745
- items.map((item, i) => {
1746
- const isSel = i + drilldownScrollOffset === drilldownSelectedIndex;
1747
- if (item.type === "span" && item.span) {
1748
- const s = item.span;
1749
- const node = drilldownTree.find(
1750
- (n) => n.span.spanId === s.spanId
1751
- );
1752
- const depth = node?.depth ?? 0;
1753
- const indent = " ".repeat(Math.min(depth, 4));
1754
- const nameWidth = 24 - Math.min(depth, 4) * 2;
1755
- const bar = buildWaterfallBar(
1756
- s.startTime,
1757
- s.durationMs,
1758
- traceStartMs,
1759
- traceDurMs,
1760
- WATERFALL_WIDTH
1761
- );
1762
- return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", children: [
1763
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { backgroundColor: isSel ? "gray" : void 0, color: isSel ? "white" : void 0, children: [
1764
- isSel ? "\u25B8" : " ",
1765
- indent,
1766
- truncate(s.name, nameWidth)
1767
- ] }),
1768
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: " " }),
1769
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: s.status === "ERROR" ? "red" : "green", children: bar }),
1770
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
1771
- " ",
1772
- formatDurationMs(s.durationMs),
1773
- s.kind ? ` ${s.kind}` : ""
1774
- ] })
1775
- ] }, `${s.spanId}-${i}`);
1776
- } else if (item.type === "log" && item.log) {
1777
- const l = item.log;
1778
- const levelColor = l.level === "error" ? "red" : l.level === "warn" ? "yellow" : "blue";
1779
- const relTime = drilldownSummary ? `+${formatDurationMs(l.time - traceStartMs)}` : "";
1780
- const logOffset = drilldownSummary ? Math.floor((l.time - traceStartMs) / traceDurMs * WATERFALL_WIDTH) : 0;
1781
- const clampedOffset = Math.max(0, Math.min(logOffset, WATERFALL_WIDTH - 1));
1782
- const logBar = " ".repeat(clampedOffset) + "\xB7" + " ".repeat(WATERFALL_WIDTH - clampedOffset - 1);
1783
- return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "row", children: [
1784
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { backgroundColor: isSel ? "gray" : void 0, color: isSel ? "white" : void 0, children: [
1785
- isSel ? "\u25B8" : " ",
1786
- " ",
1787
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: levelColor, children: l.level.toUpperCase() }),
1788
- " ",
1789
- truncate(l.message, 18)
1790
- ] }),
1791
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: " " }),
1792
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: logBar }),
1793
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
1794
- " ",
1795
- relTime
1796
- ] })
1797
- ] }, `log-${i}`);
1798
- }
1799
- return null;
1800
- }),
1801
- Array.from({ length: Math.max(0, LIST_HEIGHT - items.length) }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: " " }) }, `pad-${i}`))
1802
- ] });
1803
- })(),
1804
- drilldownTraceId != null && drilldownTab === "spans" && drilldownTree.slice(drilldownScrollOffset, drilldownScrollOffset + LIST_HEIGHT).map((node, i) => renderTreeRow(node, i + drilldownScrollOffset)),
1805
- drilldownTraceId != null && drilldownTab === "spans" && Array.from({ length: Math.max(0, LIST_HEIGHT - Math.min(drilldownTree.length, LIST_HEIGHT)) }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: " " }) }, `pad-${i}`)),
1806
- drilldownTraceId != null && drilldownTab === "logs" && drilldownLogs.slice(drilldownScrollOffset, drilldownScrollOffset + LIST_HEIGHT).map((log, i) => {
1807
- const isSel = i + drilldownScrollOffset === drilldownSelectedIndex;
1808
- const levelColor = log.level === "error" ? "red" : log.level === "warn" ? "yellow" : log.level === "info" ? "green" : void 0;
1809
- return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { children: /* @__PURE__ */ jsxRuntime.jsxs(
1810
- ink.Text,
1811
- {
1812
- backgroundColor: isSel ? "gray" : void 0,
1813
- color: isSel ? "white" : void 0,
1814
- children: [
1815
- isSel ? "\u25B8" : " ",
1816
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: levelColor, children: [
1817
- " ",
1818
- log.level.toUpperCase()
1819
- ] }),
1820
- " ",
1821
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
1822
- "[",
1823
- truncate(log.message, 50),
1824
- "]"
1825
- ] })
1826
- ]
1827
- }
1828
- ) }, `log-${i}`);
1829
- }),
1830
- drilldownTraceId != null && drilldownTab === "logs" && Array.from({ length: Math.max(0, LIST_HEIGHT - Math.min(drilldownLogs.length, LIST_HEIGHT)) }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: " " }) }, `pad-${i}`)),
1831
- drilldownTraceId == null && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: viewMode === "trace" ? filteredSummaries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2190
+ /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: viewMode === "trace" ? filteredSummaries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1832
2191
  /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
1833
2192
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "No traces yet. Call a traced function or hit an endpoint to see them here." }),
1834
2193
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Tip: trace() your handlers with autotel to get spans." })
@@ -1853,6 +2212,11 @@ ${json}
1853
2212
  " ",
1854
2213
  formatRelative(t2.lastEndTime)
1855
2214
  ] }),
2215
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2216
+ " ",
2217
+ t2.traceId.slice(0, 12),
2218
+ "\u2026"
2219
+ ] }),
1856
2220
  t2.hasError && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "red", children: " \u25CF" })
1857
2221
  ] }, t2.traceId);
1858
2222
  }),
@@ -1866,6 +2230,8 @@ ${json}
1866
2230
  ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1867
2231
  filteredSpans.slice(0, 20).map((s, i) => {
1868
2232
  const isSel = i === selected;
2233
+ const svcName = spanServiceName(s);
2234
+ const svcColor = getServiceColor(svcName);
1869
2235
  const statusColor = s.status === "ERROR" ? "red" : s.durationMs > 500 ? "yellow" : "green";
1870
2236
  return /* @__PURE__ */ jsxRuntime.jsxs(
1871
2237
  ink.Box,
@@ -1874,6 +2240,10 @@ ${json}
1874
2240
  children: [
1875
2241
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: isSel ? "cyan" : void 0, children: isSel ? "\u203A " : " " }),
1876
2242
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: colors ? statusColor : void 0, children: truncate(s.name, 26) }),
2243
+ /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: svcColor, children: [
2244
+ " ",
2245
+ truncate(svcName, 10)
2246
+ ] }),
1877
2247
  /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
1878
2248
  " ",
1879
2249
  formatDurationMs(s.durationMs)
@@ -1972,7 +2342,7 @@ ${json}
1972
2342
  {
1973
2343
  flexDirection: "column",
1974
2344
  width: "45%",
1975
- borderStyle: "single",
2345
+ borderStyle: "round",
1976
2346
  borderColor: "gray",
1977
2347
  paddingX: 1,
1978
2348
  paddingY: 0,
@@ -2020,92 +2390,7 @@ ${json}
2020
2390
  ] })
2021
2391
  ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2022
2392
  /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginBottom: 1, children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Details" }) }),
2023
- drilldownTraceId != null && drilldownSelectedItem?.type === "span" && drilldownSelectedItem.span ? (() => {
2024
- const span = drilldownSelectedItem.span;
2025
- const { key: keyAttrs, rest: restAttrs } = keyAttrsAndRest(
2026
- span.attributes
2027
- );
2028
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2029
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { children: [
2030
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Name: " }),
2031
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: span.name })
2032
- ] }),
2033
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { children: [
2034
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Status: " }),
2035
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: span.status === "ERROR" ? "red" : "green", children: span.status })
2036
- ] }),
2037
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { children: [
2038
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Duration: " }),
2039
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: formatDurationMs(span.durationMs) })
2040
- ] }),
2041
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2042
- "Trace: ",
2043
- span.traceId
2044
- ] }),
2045
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2046
- "Span: ",
2047
- span.spanId
2048
- ] }),
2049
- span.parentSpanId && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2050
- "Parent: ",
2051
- span.parentSpanId
2052
- ] }),
2053
- span.kind && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2054
- "Kind: ",
2055
- span.kind
2056
- ] }),
2057
- keyAttrs.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginTop: 1, flexDirection: "column", children: [
2058
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Key attributes" }),
2059
- keyAttrs.slice(0, 6).map(([k, v]) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2060
- truncate(k, 18),
2061
- ": ",
2062
- truncate(String(v), 28)
2063
- ] }, k))
2064
- ] }),
2065
- restAttrs.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginTop: 1, flexDirection: "column", children: [
2066
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Attributes" }),
2067
- restAttrs.slice(0, 8).map(([k, v]) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2068
- truncate(k, 18),
2069
- ": ",
2070
- truncate(String(v), 28)
2071
- ] }, k))
2072
- ] }),
2073
- keyAttrs.length === 0 && restAttrs.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "(no attributes)" })
2074
- ] });
2075
- })() : drilldownTraceId != null && drilldownSelectedItem?.type === "log" && drilldownSelectedItem.log ? (() => {
2076
- const log = drilldownSelectedItem.log;
2077
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2078
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { children: [
2079
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Level: " }),
2080
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: log.level.toUpperCase() })
2081
- ] }),
2082
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { children: [
2083
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Time: " }),
2084
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: new Date(log.time).toISOString() })
2085
- ] }),
2086
- /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { children: [
2087
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Message: " }),
2088
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: log.message })
2089
- ] }),
2090
- log.traceId && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2091
- "Trace: ",
2092
- log.traceId
2093
- ] }),
2094
- log.spanId && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2095
- "Span: ",
2096
- log.spanId
2097
- ] }),
2098
- log.attributes && Object.keys(log.attributes).length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { marginTop: 1, flexDirection: "column", children: [
2099
- /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, children: "Attributes" }),
2100
- Object.entries(log.attributes).slice(0, 10).map(([k, v]) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2101
- truncate(k, 18),
2102
- ": ",
2103
- truncate(String(v), 40)
2104
- ] }, k))
2105
- ] })
2106
- ] });
2107
- })() : drilldownTraceId == null ? null : /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Select an item to view details." }),
2108
- drilldownTraceId == null && (viewMode === "errors" ? (() => {
2393
+ viewMode === "errors" ? (() => {
2109
2394
  const e = filteredErrorSummaries[selected] ?? null;
2110
2395
  if (!e)
2111
2396
  return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Select an error to view details." });
@@ -2323,12 +2608,12 @@ ${json}
2323
2608
  ] }, w.span.spanId);
2324
2609
  })
2325
2610
  ] })
2326
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Select a trace or span to view details." }))
2611
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { dimColor: true, children: "Select a trace or span to view details." })
2327
2612
  ] })
2328
2613
  }
2329
2614
  )
2330
- ] }),
2331
- showStats && /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2615
+ ] })),
2616
+ showStats && /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
2332
2617
  "Spans: ",
2333
2618
  stats.total,
2334
2619
  " | Span errors: ",
@@ -2523,6 +2808,8 @@ function* extractSpans(payload) {
2523
2808
  if (!Array.isArray(resourceSpans)) return;
2524
2809
  for (const resourceSpan of resourceSpans) {
2525
2810
  if (!resourceSpan || typeof resourceSpan !== "object") continue;
2811
+ const resource = resourceSpan.resource;
2812
+ const resourceAttrs = attrsToRecord(resource?.attributes);
2526
2813
  const scopeSpans = resourceSpan.scopeSpans;
2527
2814
  if (!Array.isArray(scopeSpans)) continue;
2528
2815
  for (const scopeSpan of scopeSpans) {
@@ -2531,15 +2818,33 @@ function* extractSpans(payload) {
2531
2818
  if (!Array.isArray(spans)) continue;
2532
2819
  for (const span of spans) {
2533
2820
  if (span && typeof span === "object") {
2534
- yield span;
2821
+ yield { span, resourceAttrs };
2535
2822
  }
2536
2823
  }
2537
2824
  }
2538
2825
  }
2539
2826
  }
2540
- function otlpSpanToTerminalEvent(span) {
2827
+ function otlpSpanToTerminalEvent(span, resourceAttrs = {}) {
2541
2828
  const startTime = toMs(span.startTimeUnixNano);
2542
2829
  const endTime = toMs(span.endTimeUnixNano);
2830
+ const spanAttrs = attrsToRecord(span.attributes);
2831
+ const mergedAttrs = {};
2832
+ for (const [k, v] of Object.entries(resourceAttrs)) {
2833
+ mergedAttrs[k] = v;
2834
+ }
2835
+ for (const [k, v] of Object.entries(spanAttrs)) {
2836
+ mergedAttrs[k] = v;
2837
+ }
2838
+ const parsedEvents = span.events?.length ? span.events.map((e) => ({
2839
+ name: e.name || "",
2840
+ timeMs: toMs(e.timeUnixNano),
2841
+ attributes: attrsToRecord(e.attributes)
2842
+ })) : void 0;
2843
+ const parsedLinks = span.links?.length ? span.links.map((l) => ({
2844
+ traceId: normalizeHexId(l.traceId, 32),
2845
+ spanId: normalizeHexId(l.spanId, 16),
2846
+ attributes: attrsToRecord(l.attributes)
2847
+ })) : void 0;
2543
2848
  return {
2544
2849
  name: span.name || "unnamed",
2545
2850
  spanId: normalizeHexId(span.spanId, 16),
@@ -2550,7 +2855,9 @@ function otlpSpanToTerminalEvent(span) {
2550
2855
  durationMs: Math.max(0, endTime - startTime),
2551
2856
  status: mapStatus2(span.status?.code),
2552
2857
  kind: mapKind2(span.kind),
2553
- attributes: attrsToRecord(span.attributes)
2858
+ attributes: mergedAttrs,
2859
+ ...parsedEvents ? { events: parsedEvents } : {},
2860
+ ...parsedLinks ? { links: parsedLinks } : {}
2554
2861
  };
2555
2862
  }
2556
2863
  async function readJsonBody(req) {
@@ -2575,8 +2882,8 @@ function sendJson(res, status, data) {
2575
2882
  }
2576
2883
  function parseOtlpEvents(payload) {
2577
2884
  const events = [];
2578
- for (const span of extractSpans(payload)) {
2579
- events.push(otlpSpanToTerminalEvent(span));
2885
+ for (const { span, resourceAttrs } of extractSpans(payload)) {
2886
+ events.push(otlpSpanToTerminalEvent(span, resourceAttrs));
2580
2887
  }
2581
2888
  return events;
2582
2889
  }