# CLI Development Guide This guide covers everything you need to build and run the Cline CLI locally after cloning the repository. It includes setup instructions, a tech stack overview, and a walkthrough of the TUI architecture. For CLI command reference and usage, see [DOC.md](./DOC.md) and [README.md](./README.md). ## Prerequisites Install these before starting: 1. [Bun](https://bun.sh) (v1.0.0+) - Package manager, runtime, and bundler 2. [Zig](https://ziglang.org/download/) - Required by OpenTUI's native core. The `@opentui/core` package includes a Zig-compiled native binary that builds from source on install. Without Zig, `bun install` will fail for OpenTUI packages. 3. Node.js 22+ - Required for some build tooling and test infrastructure Verify your setup: ```bash bun --version # should be >= 1.0.0 zig version # any recent stable release node --version # should be >= 22 ``` ## First-Time Setup From the repository root: ```bash # Install all workspace dependencies (including native OpenTUI build) bun install # Build the SDK packages and CLI bun run build # Run the CLI in dev mode (interactive) bun run cli ``` That last command is a shortcut for `cd apps/cli && bun run dev`, which runs: ```bash CLINE_BUILD_ENV=development bun --conditions=development ./src/index.ts ``` ### Linking for Global Access To use the CLI from anywhere on your system, first build the SDK packages, then link: ```bash # From the repo root -- build all workspace packages bun run build:sdk # Then link the CLI binary cd apps/cli bun link ``` The `build:sdk` step is required because `bun link` runs without the `--conditions=development` flag, so Bun resolves workspace packages (`@cline/llms`, `@cline/core`, etc.) via their `package.json` exports which point to `dist/`. Without the build, those dist files don't exist and you'll get "Cannot find module" errors. After linking, you can run `cline` from any directory: ```bash cline # interactive mode cline "prompt" # single-prompt mode cline auth # authenticate a provider ``` If you prefer to skip the build step, use `bun run dev` from `apps/cli/` instead -- it passes `--conditions=development` which resolves packages directly from source. ### Rebuilding After SDK Changes If you modify any package in `packages/` (shared, llms, agents, core, etc.), rebuild the SDK: ```bash bun run build:sdk ``` If you're using `bun run dev`, you don't need to rebuild after every SDK change -- dev mode resolves packages from source. But if you're using the linked `cline` binary, you do need to rebuild for changes to take effect. ## Monorepo Structure ``` cline-sdk/ packages/ # SDK packages (published to npm) shared/ # Contracts, schemas, path helpers, runtime utilities llms/ # Provider settings, model catalogs, AI SDK handlers agents/ # Stateless agent loop, tool orchestration, hooks scheduler/ # Scheduled execution, concurrency control core/ # Stateful orchestration, sessions, hub, storage, config enterprise/ # Internal enterprise integrations (not published) apps/ cli/ # This package - CLI host and TUI code/ # Tauri + Next.js desktop app vscode/ # VS Code extension desktop/ # Desktop application examples/ # Sample integrations biome.json # Linter and formatter config (Biome) ``` ## Tech Stack | Layer | Technology | Purpose | |-------|-----------|---------| | Runtime | Bun | Package management, script execution, bundling | | Language | TypeScript (strict) | All source code | | CLI Framework | Commander.js | Argument parsing, subcommands | | TUI Renderer | OpenTUI (`@opentui/core`) | Native terminal rendering engine (Zig + C ABI) | | TUI Components | OpenTUI React (`@opentui/react`) | React 19 reconciler for declarative terminal UI | | TUI Dialogs | `@opentui-ui/dialog` | Modal dialog system (model picker, tool approval, etc.) | | Linter/Formatter | Biome | Code quality and formatting | | Testing | Vitest | Unit and E2E tests | | Logging | Pino | Runtime file logging | ### Why OpenTUI? OpenTUI is a native terminal UI core written in Zig with TypeScript bindings. Compared to the previous terminal renderer, OpenTUI provides: - Native diff rendering with syntax highlighting - Streaming markdown rendering - Scrollable content areas - Mouse interaction (click, hover, drag-to-select, scroll) - Built-in clipboard support (OSC52) - Higher performance through native rendering OpenTUI exposes a C ABI from its Zig core. The `@opentui/core` package provides TypeScript bindings, and `@opentui/react` provides a React reconciler so you can write terminal UIs with JSX. ## CLI Source Structure ``` apps/cli/src/ index.ts # Entry point (shebang, signal handling) main.ts # CLI command definitions, argument parsing runtime/ run-interactive.ts # Interactive mode runtime (session lifecycle, event wiring) run-agent.ts # Single-prompt runtime session-events.ts # Event bridge types and pub/sub active-runtime.ts # Abort registry tool-policies.ts # Auto-approve toggle logic prompt.ts # System prompt and user input assembly defaults.ts # Default config values tui/ # Terminal UI (OpenTUI + React) index.tsx # Renderer entry point root.tsx # Provider tree, view routing, global keyboard types.ts # ChatEntry union, TuiProps, shared constants interactive-config.ts # Config data loading interactive-welcome.ts # Welcome line, slash command resolution components/ # Reusable UI components contexts/ # React context providers hooks/ # Custom React hooks views/ # Full-screen view components utils/ # TUI-specific utilities session/ # Session state management commands/ # CLI subcommands (auth, config, history, etc.) connectors/ # Chat adapter bridges (Telegram, Slack, etc.) utils/ # Shared utilities wizards/ # Interactive setup flows logging/ # Pino logger adapter ``` ## TUI Architecture The TUI lives at `src/tui/` and uses React with OpenTUI's reconciler. Every `.tsx` file in this directory uses a per-file JSX pragma: ```tsx // @jsxImportSource @opentui/react ``` This tells TypeScript to use OpenTUI's JSX runtime instead of React DOM. The `tsconfig.json` sets `jsxImportSource: "@opentui/react"` globally, but the per-file pragma makes the intent explicit and avoids conflicts with any non-TUI React code. ### Entry Point: `index.tsx` The TUI boots through `renderOpenTui()`: ```tsx const renderer = await createCliRenderer({ exitOnCtrlC: false, // We handle Ctrl+C ourselves autoFocus: false, // Prevents click-anywhere from stealing focus enableMouseMovement: true, }); const root = createRoot(renderer); root.render(); ``` The renderer returns `destroy()` and `waitUntilExit()` methods. The runtime calls `destroy()` on exit and awaits `waitUntilExit()` for cleanup. ### Runtime Bridge: `run-interactive.ts` This file is the bridge between the SDK and the TUI. It: 1. Creates a `SessionManager` via `createCliCore()` 2. Sets up event subscriptions (agent events, pending prompts, team events) 3. Passes callbacks to the TUI as props (`onSubmit`, `onAbort`, `onModelChange`, etc.) 4. Manages session lifecycle (start, stop, restart, resume, compact) The TUI never talks to the SDK directly. All communication flows through the callback props defined in `TuiProps` (see `types.ts`). ### Component Tree ``` Root (root.tsx) DialogProvider # Modal dialog system SessionProvider # Chat entries, running state, mode EventBridgeProvider # Subscribes to SDK events View Router HomeView # Welcome screen (before first prompt) ChatView # Message list + input bar + status OnboardingView # First-run provider setup ConfigView (dialog) # Settings browser HistoryView (dialog) # Session history ``` ### Context Providers Each context owns a slice of state. Components subscribe only to what they need. `SessionContext` - Core chat state: - `entries: ChatEntry[]` - All messages in the conversation - `isRunning` / `abortRequested` - Agent execution state - `mode` (plan/act), `autoApproveAll`, `hasSubmitted` - `lastTotalTokens`, `lastTotalCost`, `turnStartTime` `EventBridgeContext` - SDK event subscription: - Subscribes to `subscribeToEvents` prop once via useEffect - Forwards agent events to session context handlers via stable refs - Handles pending prompts, team events ### Event Flow ``` SDK (AgentLoop) --> AgentEvent emitted --> subscribeToAgentEvents() fires --> UIEventEmitter.emit("agent", event) --> EventBridgeProvider receives event --> useAgentEventHandlers processes event --> SessionContext.entries updated --> React re-renders affected components ``` ### ChatEntry Type All messages in the conversation are represented as a discriminated union: ```typescript type ChatEntry = | { kind: "user"; text: string } | { kind: "assistant_text"; text: string; streaming: boolean } | { kind: "reasoning"; text: string; streaming: boolean } | { kind: "tool_call"; toolName: string; inputSummary: string; ... } | { kind: "error"; text: string } | { kind: "status"; text: string } | { kind: "team"; text: string } | { kind: "user_submitted"; text: string; delivery?: "queue" | "steer" } | { kind: "done"; tokens: number; cost: number; elapsed: string; iterations: number } ``` ### Dialog System Dialogs use `@opentui-ui/dialog`. The pattern: ```tsx import { useDialog } from "@opentui-ui/dialog/react"; const dialog = useDialog(); const result = await dialog.choice({ style: { maxHeight: termHeight - 2 }, content: (ctx) => , }); ``` Dialog content components receive `resolve` and `dismiss` callbacks through the context. They use `useDialogKeyboard` for keyboard handling scoped to the dialog. Important gotcha: async data loading inside a dialog (via useEffect/useState) causes layout gaps between flex children in OpenTUI. Always fetch data before opening the dialog and pass it as props. ### Key Components `components/input-bar.tsx` - Text input with submit handling: - Uncontrolled `