- Node.js >= 22.16.0
- Yarn 4 (workspace managed from monorepo root)
- Bun (optional, for standalone binary compilation)
- A running HyperDX instance (API server or Next.js frontend with proxy)
# From the monorepo root
yarn install
# Navigate to the CLI package
cd packages/cliBefore using the TUI, authenticate with your HyperDX instance:
# Interactive login (opens email/password prompts)
yarn dev auth login -s http://localhost:8000
# Non-interactive login (for scripting/CI)
yarn dev auth login -s http://localhost:8000 -e user@example.com -p password
# Verify auth status
yarn dev auth status
# Session is saved to ~/.config/hyperdx/cli/session.jsonOnce authenticated, the -s flag is optional — the CLI reads the server URL
from the saved session.
yarn dev uses tsx for direct TypeScript execution — no compile step needed.
# Interactive TUI
yarn dev tui
# With explicit server URL
yarn dev tui -s http://localhost:8000
# Skip source picker
yarn dev tui --source "Logs"
# Start with a search query + follow mode
yarn dev tui -q "level:error" -f
# Non-interactive streaming
yarn dev stream --source "Logs"
# List available sources
yarn dev sources
# Type check (no output on success)
npx tsc --noEmit
# Bundle with tsup (outputs to dist/cli.js)
yarn build
# Compile standalone binary for current platform
yarn compile
# Cross-compile
yarn compile:macos # macOS ARM64
yarn compile:macos-x64 # macOS x64
yarn compile:linux # Linux x64The compiled binary is a single file at dist/hdx (or dist/hdx-<platform>).
src/
├── cli.tsx # Entry point — Commander CLI commands
│ # (tui, stream, sources, auth login/logout/status)
│ # Also contains LoginPrompt component
├── App.tsx # Ink app shell — state machine:
│ # loading → login → pick-source → EventViewer
├── api/
│ ├── client.ts # ApiClient (REST + session cookies)
│ │ # ProxyClickhouseClient (ClickHouse via /clickhouse-proxy)
│ └── eventQuery.ts # Query builders:
│ # buildEventSearchQuery — table view (uses renderChartConfig)
│ # buildTraceSpansSql — waterfall trace spans
│ # buildTraceLogsSql — waterfall correlated logs
│ # buildFullRowQuery — SELECT * for row detail (uses renderChartConfig)
├── components/
│ ├── EventViewer.tsx # Main TUI view (~1275 lines)
│ │ # Table, search, detail panel with 3 tabs
│ ├── TraceWaterfall.tsx # Trace waterfall chart with j/k navigation
│ ├── RowOverview.tsx # Structured overview (Top Level, Attributes, Resources)
│ ├── ColumnValues.tsx # Shared key-value renderer with scroll support
│ ├── LoginForm.tsx # Email/password login form (used inside TUI App)
│ ├── SourcePicker.tsx # j/k source selector
│ └── Spotlight.tsx # Ctrl+K spotlight overlay for quick navigation
├── shared/ # Logic ported from packages/app (@source annotated)
│ ├── useRowWhere.ts # processRowToWhereClause, buildColumnMap, getRowWhere
│ ├── source.ts # getDisplayedTimestampValueExpression, getEventBody, etc.
│ └── rowDataPanel.ts # ROW_DATA_ALIASES, buildRowDataSelectList
└── utils/
├── config.ts # Session persistence (~/.config/hyperdx/cli/session.json)
├── editor.ts # $EDITOR integration for time range and SELECT editing
└── silenceLogs.ts # Suppresses console.debug/warn/error, verbose file logging
User types search → buildEventSearchQuery()
→ renderChartConfig() from @hyperdx/common-utils
→ ProxyClickhouseClient.query()
→ API /clickhouse-proxy → ClickHouse
→ JSON response with { data, meta }
→ Store chSql in lastTableChSqlRef, meta in lastTableMetaRef
→ Render dynamic table columns from row keys
User expands row → buildFullRowQuery()
→ chSqlToAliasMap(lastTableChSql) for alias resolution
→ buildColumnMap(lastTableMeta, aliasMap) for type-aware WHERE
→ processRowToWhereClause() with proper type handling
→ renderChartConfig() with SELECT *, __hdx_* aliases
→ Results include LogAttributes, ResourceAttributes, etc.
User switches to Trace tab
→ buildTraceSpansSql() — fetch spans by TraceId
→ buildTraceLogsSql() — fetch correlated logs by TraceId (if logSource exists)
→ buildTree() — single-pass DAG builder (port of DBTraceWaterfallChart)
→ Render waterfall with timing bars
→ j/k navigation highlights spans, fetches SELECT * for Event Details
ProxyClickhouseClient extends BaseClickhouseClient from common-utils:
- Derives proxy pathname from API URL (e.g.
/api/clickhouse-proxyfor Next.js proxy,/clickhouse-proxyfor direct API) - Passes
originonly tocreateClient(not the path, which ClickHouse client would interpret as a database name) - Injects session cookies +
x-hyperdx-connection-idheader - Forces
content-type: text/plainto prevent Express body parser issues
The -s flag is optional on most commands. Resolution order:
- Explicit
-s <url>flag - Saved session's
apiUrlfrom~/.config/hyperdx/cli/session.json - Error: "No server specified"
The useInput callback in EventViewer has a strict priority order. Do not
reorder these checks:
?toggles help (except when search focused)- Any key closes help when showing
- SQL preview — D/Esc close, Ctrl+D/U scroll
Ctrl+K— opens spotlight (quick navigation)focusDetailSearch— consumes all keys except Esc/EnterfocusSearch— consumes all keys except Tab/Esc- Trace tab j/k + Ctrl+D/U — when detail panel open and Trace tab active
- Column Values / Overview Ctrl+D/U — scroll detail view
- General j/k, G/g, Enter/Esc, Tab, etc.
- Single-key shortcuts:
w,f,/,s,t,q
- Enabled by default on startup
- Slides
timeRangeforward every 2s viasetInterval, triggering a replace fetch - Paused when detail panel opens (
wasFollowingRefsaves previous state) - Restored when detail panel closes
customSelectMap: Record<string, string> stores custom SELECT overrides keyed
by source.id. Each source remembers its own custom select independently. Press
s to open $EDITOR with the current SELECT clause.
All detail tabs have fixed-height viewports with Ctrl+D/U scrolling:
- Overview / Column Values — Uses
fullDetailMaxRows(full screen minus overhead) - Trace Event Details — Uses
detailMaxRows(1/3 of terminal, since waterfall takes the rest)
This package ports several web frontend components. Always check the corresponding web component before making changes:
| 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 |
| Row WHERE clause | useRowWhere.tsx |
processRowToWhereClause |
| Row data fetch | DBRowDataPanel |
ROW_DATA_ALIASES, SELECT list |
| Source helpers | source.ts |
Expression getters |
Files in src/shared/ are copied from packages/app with @source annotations
at the top of each file. These are candidates for future extraction to
@hyperdx/common-utils:
/**
* Row WHERE clause builder.
*
* @source packages/app/src/hooks/useRowWhere.tsx
*/When updating these files, check the original source in packages/app first.
- Add the key handler in
EventViewer.tsx'suseInputcallback at the correct priority level - Update the
HelpScreencomponent's key list - Update
AGENTS.mdkeybindings table - Update
README.mdkeybindings table
- Add the tab key to the
detailTabunion type inEventViewer.tsx - Add the tab to the
tabsarray in the Tab key handler - Add the tab to the tab bar rendering
- Add the tab content rendering block
- Handle any tab-specific Ctrl+D/U scrolling
- Add the command in
cli.tsxusing Commander - Use
resolveServer(opts.server)for server URL resolution - Use
withVerbose()if the command needs--verbosesupport - Update
README.mdandAGENTS.mdwith the new command
- Create the file in
src/shared/(if pure logic) orsrc/components/(if UI) - Add
@source packages/app/src/...annotation at the top - Use
SourceResponsetype from@/api/clientinstead ofTSourcetypes - Replace React hooks with plain functions where possible (for non-React usage)
- Add the mapping to the "Web Frontend Alignment" table in
AGENTS.md
Run yarn dev auth login -s <url> to authenticate. The session may have expired
or the server URL may have changed.
The API URL may be pointing to the Next.js frontend instead of the Express API
server. Both work — the CLI auto-detects the /api prefix in the URL and
adjusts the ClickHouse proxy path accordingly.
The ProxyClickhouseClient was sending the URL path as a database name. This
was fixed by passing origin only (without path) to createClient.
The SELECT * row detail fetch requires:
lastTableChSqlRef— the rendered SQL from the last table query (forchSqlToAliasMap)lastTableMetaRef— column metadata from the query response (for type-aware WHERE clause)
If these are null (e.g. first render before any query), the fetch may fail silently and fall back to the partial table row data.