# How RTK Manages JavaScript/TypeScript Tooling Output: tsc and eslint Handling Explained

> Discover how RTK handles tsc and eslint output. Learn how it processes tooling logs with streaming, filtering, and concise summaries for efficient development.

- Repository: [rtk-ai/rtk](https://github.com/rtk-ai/rtk)
- Tags: deep-dive
- Published: 2026-04-24

---

**RTK executes TypeScript and ESLint as subprocesses through a streaming runner that detects local binaries, filters noisy output via `BlockStreamFilter`, and prints concise, token-efficient summaries grouped by error codes and files.**

The RTK CLI (rtk-ai/rtk) provides a unified interface for JavaScript and TypeScript development workflows by wrapping external tooling like the TypeScript compiler (`tsc`) and ESLint. Instead of executing these commands directly, RTK intercepts their output streams, parses diagnostic messages in real-time, and presents condensed summaries that highlight critical issues while reducing token noise for AI-assisted development.

## How RTK Wraps External JavaScript/TypeScript Tools

RTK treats every external tool as a **subprocess** executed through its core runner. For JavaScript and TypeScript workflows, the system handles two primary commands: the TypeScript compiler and the ESLint linter. Both follow a consistent four-step pipeline:

1. **Binary Detection** – Prefer locally-installed binaries (`tsc`, `eslint`) and fall back to package-manager wrappers (`npx tsc`, `npx eslint`) when binaries are missing.
2. **Stream Processing** – Pipe raw output line-by-line through a `BlockStreamFilter` that implements the `BlockHandler` trait.
3. **Message Parsing** – Group messages by file, error code, or rule to eliminate redundancy.
4. **Summarized Output** – Pass filtered results to an optional tee subsystem for telemetry, then print a compact summary to the terminal.

## TypeScript Compiler (tsc) Handling

RTK’s TypeScript integration lives in [`src/cmds/js/tsc_cmd.rs`](https://github.com/rtk-ai/rtk/blob/main/src/cmds/js/tsc_cmd.rs). The module wraps the compiler in a streaming handler that reduces verbose diagnostic output into actionable summaries.

### Binary Detection and Command Construction

RTK first checks for a local TypeScript installation before invoking the compiler. According to the source in [`src/cmds/js/tsc_cmd.rs`](https://github.com/rtk-ai/rtk/blob/main/src/cmds/js/tsc_cmd.rs) (lines 18-26), the system uses `tool_exists("tsc")` to verify availability:

- If `tsc` exists locally, RTK calls `resolved_command("tsc")` to obtain the absolute path.
- If missing, RTK constructs a package-manager fallback using `npx tsc`.

```rust
let tsc_exists = tool_exists("tsc");
let mut cmd = if tsc_exists { 
    resolved_command("tsc") 
} else { 
    // Fallback to npx tsc
};

```

The system then forwards all user arguments verbatim (lines 28-30) and logs the exact command when verbose mode (`-v`) is enabled (lines 32-35).

### Streaming Output Processing

Rather than buffering the entire compiler output, RTK uses **incremental stream processing**. The `run_streamed` function in [`src/core/runner.rs`](https://github.com/rtk-ai/rtk/blob/main/src/core/runner.rs) executes the command while piping stdout through a `BlockStreamFilter` configured with a `TscHandler` (lines 37-42 in [`tsc_cmd.rs`](https://github.com/rtk-ai/rtk/blob/main/tsc_cmd.rs)):

```rust
runner::run_streamed(
    ..., 
    Box::new(BlockStreamFilter::new(TscHandler::new())), 
    ...
)

```

The `TscHandler` implements the `BlockHandler` trait defined in [`src/core/stream.rs`](https://github.com/rtk-ai/rtk/blob/main/src/core/stream.rs). It recognizes error block boundaries using the `TSC_ERROR` regex (lines 11-15), enabling real-time filtering without waiting for process completion.

### Error Aggregation and Summary Generation

As the stream processes, `TscHandler` maintains counters for error frequencies and affected files. Once the subprocess exits, the handler formats a compact summary (lines 62-86) that includes:

- Total error count and affected file count.
- Top error codes by frequency (e.g., TS2322, TS2345).
- Grouped messages to prevent repetitive file listings.

The final output displays only the essential diagnostic information, with full details optionally tee'd to telemetry files.

## ESLint Handling

ESLint integration resides in [`src/cmds/js/lint_cmd.rs`](https://github.com/rtk-ai/rtk/blob/main/src/cmds/js/lint_cmd.rs), which handles multiple linters but contains specific logic for ESLint’s JSON output format.

### Linter Detection and Command Execution

RTK automatically detects whether the user intends to run ESLint through the `detect_linter` function (lines 74-87). The system strips package-manager prefixes like `npx` or `pnpm exec`, defaulting to `eslint` when the first non-flag argument lacks path-like characteristics.

Command construction uses `package_manager_exec("eslint")` (lines 99-103), which expands to the appropriate package-manager invocation (`npx eslint`, `pnpm exec eslint`, etc.). RTK automatically forces JSON output by appending `-f json` when the user hasn't specified a custom format (lines 106-109):

```rust
cmd.arg("-f").arg("json");

```

Unlike the streaming approach used for `tsc`, ESLint execution uses `exec_capture` (lines 70-74) to capture the complete JSON payload before processing.

### JSON Parsing and Result Summarization

The `filter_eslint_json` function (lines 25-78) deserializes the ESLint JSON output and computes:

- Total error and warning counts.
- Frequency tables for violated rules (e.g., `no-unused-vars`, `prefer-const`).
- Issue counts per file.

The results render as a token-efficient summary showing top rules and affected files, followed by telemetry recording.

## Core Runner and Streaming Infrastructure

Both `tsc` and `eslint` handlers rely on infrastructure defined in [`src/core/runner.rs`](https://github.com/rtk-ai/rtk/blob/main/src/core/runner.rs) and [`src/core/stream.rs`](https://github.com/rtk-ai/rtk/blob/main/src/core/stream.rs):

- **[`runner.rs`](https://github.com/rtk-ai/rtk/blob/main/runner.rs)** – Orchestrates subprocess execution, sets up tee channels for raw output capture, and manages the streaming lifecycle.
- **[`stream.rs`](https://github.com/rtk-ai/rtk/blob/main/stream.rs)** – Defines the `BlockHandler` trait and `BlockStreamFilter` struct used to process output incrementally without buffering the entire stream in memory.

The `BlockStreamFilter` works by scanning for block start patterns (like TypeScript error headers), accumulating lines until the block ends, then passing the complete block to the handler's `format_summary` method.

## Practical Usage Examples

Run TypeScript compilation with automatic binary detection and filtered output:

```bash

# Compiles TypeScript and shows only the summary

rtk tsc src/**/*.ts

```

Example output:

```

───────────────────────────────────────
TypeScript: 12 errors in 4 files
Top codes: TS2322 (5x), TS2345 (4x), TS2339 (3x)

```

Execute ESLint with automatic JSON formatting and package-manager handling:

```bash

# Lints the src/ directory with concise output

rtk lint src/

# Explicitly using pnpm (RTK strips the prefix automatically)

rtk lint pnpm exec eslint .

```

Example output:

```

───────────────────────────────────────
ESLint: 3 errors, 7 warnings in 2 files
Top rules:
  no-unused-vars (4x)
  prefer-const (2x)
  eqeqeq (1x)

Top files:
  src/utils.ts (5 issues)
  src/api.ts (5 issues)

```

## Summary

- **RTK wraps JavaScript/TypeScript tools as subprocesses** through a unified runner in [`src/core/runner.rs`](https://github.com/rtk-ai/rtk/blob/main/src/core/runner.rs), providing consistent behavior across `tsc` and `eslint`.
- **Binary detection prefers local installations** (`tsc`, `eslint`) and falls back to `npx` or `pnpm exec` wrappers when needed, implemented in [`src/cmds/js/tsc_cmd.rs`](https://github.com/rtk-ai/rtk/blob/main/src/cmds/js/tsc_cmd.rs) (lines 18-26) and [`src/cmds/js/lint_cmd.rs`](https://github.com/rtk-ai/rtk/blob/main/src/cmds/js/lint_cmd.rs) (lines 99-103).
- **TypeScript uses streaming filters** via `BlockStreamFilter` and `TscHandler` to process compiler output incrementally, recognizing error blocks with regex patterns and aggregating statistics in real-time.
- **ESLint uses JSON capture**; RTK forces `-f json` format (lines 106-109 in [`src/cmds/js/lint_cmd.rs`](https://github.com/rtk-ai/rtk/blob/main/src/cmds/js/lint_cmd.rs)), then parses the output through `filter_eslint_json` (lines 25-78) to produce grouped summaries by rule and file.
- **Both tools support telemetry** through the `timer.track()` subsystem, enabling performance monitoring and output logging without cluttering the terminal.

## Frequently Asked Questions

### How does RTK choose between a local binary and npx for TypeScript?

RTK checks for the existence of a local `tsc` binary using `tool_exists("tsc")` before executing. If found, it resolves the absolute path via `resolved_command("tsc")`; otherwise, it constructs a command using `npx tsc` as a fallback. This logic appears in [`src/cmds/js/tsc_cmd.rs`](https://github.com/rtk-ai/rtk/blob/main/src/cmds/js/tsc_cmd.rs) at lines 18-26.

### Why does RTK force JSON output for ESLint but not for TypeScript?

ESLint provides structured JSON output via the `-f json` flag, which RTK automatically appends (lines 106-109 in [`src/cmds/js/lint_cmd.rs`](https://github.com/rtk-ai/rtk/blob/main/src/cmds/js/lint_cmd.rs)) to enable reliable parsing through `filter_eslint_json`. TypeScript's `tsc` does not offer JSON streaming; instead, RTK parses human-readable error blocks using the `TscHandler` regex (`TSC_ERROR`) and `BlockStreamFilter` to handle the streaming text output.

### Can I see the full raw output from tsc or eslint instead of the summary?

Yes. RTK includes a tee subsystem (implemented in [`src/core/tee.rs`](https://github.com/rtk-ai/rtk/blob/main/src/core/tee.rs)) that writes raw output to files for later analysis. While the terminal displays the filtered summary, the complete diagnostic stream can be captured for debugging or detailed review without interfering with the concise display.

### Does RTK support package managers other than npm for running these tools?

Yes. RTK abstracts package-manager commands through `package_manager_exec()` in [`src/core/utils.rs`](https://github.com/rtk-ai/rtk/blob/main/src/core/utils.rs). When detecting linter commands, it strips prefixes like `npx`, `pnpm exec`, or `bunx` via `detect_linter` (lines 74-87 in [`src/cmds/js/lint_cmd.rs`](https://github.com/rtk-ai/rtk/blob/main/src/cmds/js/lint_cmd.rs)), allowing seamless usage across npm, pnpm, Yarn, and Bun ecosystems.