hyperdx/packages/cli/AGENTS.md
Warren Lee d995b78c66
[HDX-3919] Add @hyperdx/cli package — terminal TUI, source map upload, and agent-friendly commands (#2043)
## Summary

Adds `packages/cli` (`@hyperdx/cli`) — a unified CLI for HyperDX that provides an interactive TUI for searching/tailing logs and traces, source map upload (migrated from `hyperdx-js`), and agent-friendly commands for programmatic access.

Ref: HDX-3919
Ref: HDX-3920

### CLI Commands

```
hdx tui -s <url>                    # Interactive TUI (main command)
hdx sources -s <url>                # List sources with ClickHouse schemas
hdx sources -s <url> --json         # JSON output for agents / scripts
hdx dashboards -s <url>             # List dashboards with tile summaries
hdx dashboards -s <url> --json      # JSON output for agents / scripts
hdx query --source "Logs" --sql "SELECT count() FROM default.otel_logs"
hdx upload-sourcemaps -k <key>      # Upload source maps (ported from hyperdx-js)
hdx auth login -s <url>             # Sign in (interactive or -e/-p flags)
hdx auth status                     # Show auth status
hdx auth logout                     # Clear saved session
```

### Key Features

**Interactive TUI (`hdx tui`)**
- Table view with dynamic columns derived from query results (percentage-based widths)
- Follow mode (live tail) enabled by default, auto-pauses when detail panel is open
- Vim-like keybindings: j/k navigation, G/g jump, Ctrl+D/U half-page scroll
- `/` for Lucene search, `s` to edit SELECT clause in `$EDITOR`, `t` to edit time range
- Tab to cycle between sources and saved searches
- `w` to toggle line wrap

**Detail Panel (3 tabs, full-screen with Ctrl+D/U scrolling)**
- **Overview** — Structured view: Top Level Attributes, Log/Span Attributes, Resource Attributes
- **Column Values** — Full `SELECT *` row data with `__hdx_*` aliased columns
- **Trace** — Waterfall chart (port of `DBTraceWaterfallChart` DAG builder) with correlated log events, j/k span navigation, inverse highlight, Event Details section

**Agent-friendly commands**
- `hdx sources --json` — Full source metadata with ClickHouse `CREATE TABLE` DDL, expression mappings, and correlated source IDs. Detailed `--help` describes the JSON schema for LLM consumption. Schema queries run in parallel.
- `hdx dashboards --json` — Dashboard metadata with simplified tile summaries (name, type, source, sql). Resolves source names for human-readable output.
- `hdx query --source <name> --sql <query>` — Raw SQL execution against any source's ClickHouse connection. Supports `--format` for ClickHouse output formats (JSON, JSONEachRow, CSV, etc.).

**Source map upload (`hdx upload-sourcemaps`)**
- Ported from `hyperdx-js/packages/cli` to consolidate on a single `@hyperdx/cli` package
- Authenticates via service account API key (`-k` / `HYPERDX_SERVICE_KEY` env var)
- Globs `.js` and `.js.map` files, handles Next.js route groups
- Uploads to presigned URLs in parallel with retry (3 attempts) and progress
- Modernized: native `fetch` (Node 22+), ESM-compatible, proper TypeScript types

### Architecture

```
packages/cli/
├── src/
│   ├── cli.tsx              # Commander CLI: tui, sources, dashboards, query,
│   │                        #   upload-sourcemaps, auth
│   ├── App.tsx              # Ink app shell (login → source picker → EventViewer)
│   ├── sourcemaps.ts        # Source map upload logic (ported from hyperdx-js)
│   ├── api/
│   │   ├── client.ts        # ApiClient + ProxyClickhouseClient
│   │   └── eventQuery.ts    # Query builders (renderChartConfig, raw SQL)
│   ├── components/
│   │   ├── EventViewer/     # Main TUI (9 files)
│   │   │   ├── EventViewer.tsx    # Orchestrator (state, hooks, render shell)
│   │   │   ├── types.ts           # Shared types & constants
│   │   │   ├── utils.ts           # Row formatting functions
│   │   │   ├── SubComponents.tsx  # Header, TabBar, SearchBar, Footer, HelpScreen, TableHeader
│   │   │   ├── TableView.tsx      # Table rows rendering
│   │   │   ├── DetailPanel.tsx    # Detail panel (overview/columns/trace tabs)
│   │   │   ├── useEventData.ts    # Data fetching hook
│   │   │   └── useKeybindings.ts  # Input handler hook
│   │   ├── TraceWaterfall/  # Trace chart (6 files)
│   │   │   ├── TraceWaterfall.tsx  # Orchestrator + render
│   │   │   ├── types.ts           # SpanRow, SpanNode, props
│   │   │   ├── utils.ts           # Duration/status/bar helpers
│   │   │   ├── buildTree.ts       # DAG builder (port of DBTraceWaterfallChart)
│   │   │   └── useTraceData.ts    # Data fetching hook
│   │   ├── RowOverview.tsx
│   │   ├── ColumnValues.tsx
│   │   ├── LoginForm.tsx
│   │   └── SourcePicker.tsx
│   ├── shared/              # Ported from packages/app (@source annotated)
│   └── utils/               # Config, editor, log silencing
├── AGENTS.md
├── CONTRIBUTING.md
└── README.md
```

### Tech Stack
- **Ink v6.8.0** (React 19 for terminals) + Commander.js
- **@clickhouse/client** via ProxyClickhouseClient (routes through `/clickhouse-proxy`)
- **@hyperdx/common-utils** for query generation (`renderChartConfig`, `chSqlToAliasMap`)
- **glob v13** for source map file discovery
- **tsup** for bundling (all deps bundled via `noExternal: [/.*/]`, zero runtime deps)
- **Bun 1.3.11** for standalone binary compilation
- Session stored at `~/.config/hyperdx/cli/session.json`

### CI/CD (`release.yml`)
- CLI binaries compiled for macOS ARM64, macOS x64, and Linux x64
- GitHub Release created with download instructions
- Version-change gate: skips release if `cli-v{version}` tag already exists
- `softprops/action-gh-release` pinned to full SHA (v2.6.1) for supply chain safety
- Bun pinned to `1.3.11` for reproducible builds
- npm publishing handled by changesets

### Keybindings

| Key | Action |
|---|---|
| `j/k` | Navigate rows (or spans in Trace tab) |
| `l/Enter` | Expand row detail |
| `h/Esc` | Close detail / blur search |
| `G/g` | Jump to newest/oldest |
| `Ctrl+D/U` | Scroll half-page (table, detail panels, Event Details) |
| `/` | Search (global or detail filter) |
| `Tab` | Cycle sources/searches or detail tabs |
| `s` | Edit SELECT clause in $EDITOR |
| `t` | Edit time range in $EDITOR |
| `f` | Toggle follow mode (live tail) |
| `w` | Toggle line wrap |
| `?` | Help screen |

### Demo
#### Main Search View
<img width="1004" height="1014" alt="image" src="https://github.com/user-attachments/assets/bb6a7f00-38c9-4281-9915-c71b65d852f8" />

#### Event Details Overview
<img width="1003" height="1024" alt="image" src="https://github.com/user-attachments/assets/57025fa5-fddb-452a-9320-93465538d5b2" />

#### Trace Waterfall
<img width="1004" height="1029" alt="image" src="https://github.com/user-attachments/assets/3443c898-ea0d-47f3-acc5-edb7cdd31946" />
2026-04-09 20:21:34 +00:00

11 KiB

@hyperdx/cli Development Guide

What is @hyperdx/cli?

A terminal CLI for searching, tailing, and inspecting logs and traces from HyperDX. It provides both an interactive TUI (built with Ink — React for terminals) and a non-interactive streaming mode for piping.

The CLI connects to the HyperDX API server and queries ClickHouse directly through the API's /clickhouse-proxy endpoint, using the same query generation logic (@hyperdx/common-utils) as the web frontend.

CLI Commands

hdx tui -s <url>                    # Interactive TUI (main command)
hdx stream -s <url> --source "Logs" # Non-interactive streaming to stdout
hdx sources -s <url>                # List available sources
hdx auth login -s <url>             # Sign in (interactive or -e/-p flags)
hdx auth status                     # Show auth status (reads saved session)
hdx auth logout                     # Clear saved session

The -s, --server <url> flag is required for commands that talk to the API. If omitted, the CLI falls back to the server URL saved in the session file from a previous hdx auth login.

Architecture

src/
├── cli.tsx              # Entry point — Commander CLI with commands:
│                        #   tui, stream, sources, auth (login/logout/status)
│                        #   Also contains the standalone LoginPrompt component
├── App.tsx              # App shell — state machine:
│                        #   loading → login → pick-source → EventViewer
├── api/
│   ├── client.ts        # ApiClient (REST + session cookies)
│   │                    # ProxyClickhouseClient (routes through /clickhouse-proxy)
│   └── eventQuery.ts    # Query builders:
│                        #   buildEventSearchQuery (table view, uses renderChartConfig)
│                        #   buildTraceSpansSql (waterfall trace spans)
│                        #   buildTraceLogsSql (waterfall correlated logs)
│                        #   buildFullRowSql (SELECT * for row detail)
├── components/
│   ├── EventViewer.tsx  # Main TUI view — table, search, detail panel with tabs
│   ├── TraceWaterfall.tsx # Trace waterfall chart with j/k navigation + event details
│   ├── RowOverview.tsx  # Structured overview (top-level attrs, event attrs, resource attrs)
│   ├── ColumnValues.tsx # Shared key-value renderer (used by Column Values tab + Event Details)
│   ├── LoginForm.tsx    # Email/password login form (used inside TUI App)
│   └── SourcePicker.tsx # Arrow-key source selector
└── utils/
    ├── config.ts        # Session persistence (~/.config/hyperdx/cli/session.json)
    ├── editor.ts        # $EDITOR integration for time range and select clause editing
    └── silenceLogs.ts   # Suppresses console.debug/warn/error from common-utils

Key Components

EventViewer (components/EventViewer.tsx)

The main TUI component (~1100 lines). Handles:

  • Table view: Dynamic columns derived from query results, percentage-based widths, overflowX="hidden" for truncation
  • Search: Lucene query via / key, submits on Enter
  • Follow mode: Slides time range forward every 2s, pauses when detail panel is open, restores on close
  • Detail panel: Three tabs (Overview / Column Values / Trace), cycled via Tab key. Detail search via / filters content within the active tab.
  • Select editor: s key opens $EDITOR with the current SELECT clause. Custom selects are stored per source ID.
  • State management: ~20 useState hooks. Key states include events, expandedRow, detailTab, isFollowing, customSelectMap, traceSelectedIndex.

TraceWaterfall (components/TraceWaterfall.tsx)

Port of the web frontend's DBTraceWaterfallChart. Key details:

  • Tree building: Single-pass DAG builder over time-sorted rows. Direct port of the web frontend's logic — do NOT modify without checking DBTraceWaterfallChart first.
  • Correlated logs: Fetches log events via buildTraceLogsSql and merges them into the span tree (logs attach as children of the span with matching SpanId, using SpanId-log suffix to avoid key collisions).
  • j/k navigation: selectedIndex + onSelectedIndexChange props controlled by EventViewer. effectiveIndex falls back to highlightHint when no j/k navigation has occurred.
  • Event Details: SELECT * fetch for the selected span/log, rendered via the shared ColumnValues component. Uses stable scalar deps (selectedNodeSpanId, selectedNodeTimestamp, selectedNodeKind) to avoid infinite re-fetch loops.
  • Duration formatting: Dynamic units — 1.2s, 3.5ms, 45.2μs, 123ns.
  • Highlight: inverse on label and duration text for the selected row. Bar color unchanged.

RowOverview (components/RowOverview.tsx)

Port of the web frontend's DBRowOverviewPanel. Three sections:

  1. Top Level Attributes: Standard OTel fields (TraceId, SpanId, SpanName, ServiceName, Duration, StatusCode, etc.)
  2. Span/Log Attributes: Flattened from source.eventAttributesExpression, shown with key count header
  3. Resource Attributes: Flattened from source.resourceAttributesExpression, rendered as chips with backgroundColor="#3a3a3a" and cyan key / white value

ColumnValues (components/ColumnValues.tsx)

Shared component for rendering key-value pairs from a row data object. Used by:

  • Column Values tab in the detail panel
  • Event Details section in the Trace tab's waterfall

Supports searchQuery filtering and wrapLines toggle.

Web Frontend Alignment

This package mirrors several web frontend components. Always check the corresponding web component before making changes to ensure behavior stays consistent:

CLI Component Web Component Notes
TraceWaterfall DBTraceWaterfallChart Tree builder is a direct port
RowOverview DBRowOverviewPanel Same sections and field list
Trace tab logic DBTracePanel Source resolution (trace/log)
Detail panel DBRowSidePanel Tab structure, highlight hint
Event query DBTraceWaterfallChart getConfig()buildTrace*Sql

Key expression mappings from the web frontend's getConfig():

  • TimestampdisplayedTimestampValueExpression (NOT timestampValueExpression)
  • DurationdurationExpression (raw, not seconds like web frontend)
  • BodybodyExpression (logs) or spanNameExpression (traces)
  • SpanIdspanIdExpression
  • ParentSpanIdparentSpanIdExpression (traces only)

Keybindings (TUI mode)

Key Action
j / Move selection down
k / Move selection up
l / Enter Expand row detail
h / Esc Close detail / blur search
G Jump to newest
g Jump to oldest
/ Search (global in table, filter in detail)
Tab Cycle sources/searches or detail tabs
Shift+Tab Cycle backwards
s Edit SELECT clause in $EDITOR
t Edit time range in $EDITOR
f Toggle follow mode (live tail)
w Toggle line wrap
? Toggle help screen
q Quit

In the Trace tab, j/k navigate spans/logs in the waterfall instead of the main table.

Development

# Run in dev mode (tsx, no compile step)
cd packages/cli
yarn dev tui -s http://localhost:8000

# Type check
npx tsc --noEmit

# Bundle with tsup
yarn build

# Compile standalone binary (current platform)
yarn compile

# Cross-compile
yarn compile:macos        # macOS ARM64
yarn compile:macos-x64    # macOS x64
yarn compile:linux        # Linux x64

Key Patterns

Session Management

Session is stored at ~/.config/hyperdx/cli/session.json with mode 0o600. Contains apiUrl and cookies[]. The ApiClient constructor loads the saved session and checks if the stored apiUrl matches the requested one.

ClickHouse Proxy Client

ProxyClickhouseClient extends BaseClickhouseClient from common-utils. It:

  • Routes queries through /clickhouse-proxy (sets pathname)
  • Injects session cookies for auth
  • Passes x-hyperdx-connection-id header
  • Disables basic auth (set_basic_auth_header: false)
  • Forces content-type: text/plain to prevent Express body parser issues

Source Expressions

Sources have many expression fields that map to ClickHouse column names. Key ones used in the CLI:

  • timestampValueExpression — Primary timestamp (often TimestampTime, DateTime)
  • displayedTimestampValueExpression — High-precision timestamp (often Timestamp, DateTime64 with nanoseconds). Use this for waterfall queries.
  • traceIdExpression, spanIdExpression, parentSpanIdExpression
  • bodyExpression, spanNameExpression, serviceNameExpression
  • durationExpression + durationPrecision (3=ms, 6=μs, 9=ns)
  • eventAttributesExpression, resourceAttributesExpression
  • logSourceId, traceSourceId — Correlated source IDs

useInput Handler Ordering

The useInput callback in EventViewer has a specific priority order. Do not reorder these checks:

  1. ? toggles help (except when search focused)
  2. Any key closes help when showing
  3. focusDetailSearch — consumes all keys except Esc/Enter
  4. focusSearch — consumes all keys except Tab/Esc
  5. Trace tab j/k — when detail panel open and Trace tab active
  6. General j/k, G/g, Enter/Esc, Tab, etc.
  7. Single-key shortcuts: w, f, /, s, t, q

Dynamic Table Columns

When customSelect is set (via s key), columns are derived from the query result keys. Otherwise, hardcoded percentage-based columns are used per source kind. The getDynamicColumns function distributes 60% evenly among non-last columns, with the last column getting the remainder.

Follow Mode

  • Enabled by default on startup
  • Slides timeRange forward every 2s, triggering a replace fetch
  • Paused when detail panel opens (wasFollowingRef saves previous state)
  • Restored when detail panel closes

Custom Select Per Source

customSelectMap: Record<string, string> stores custom SELECT overrides keyed by source.id. Each source remembers its own custom select independently.