# TUI

Terminal UI entrypoint, worker RPC, OpenTUI renderer, command registry, routes, event streams, and remote control wiring.

## Overview

The nikcli terminal UI is the default command registered by `packages/nikcli/src/cli/cmd/tui/thread.ts`. It is not only a renderer. The TUI is a split runtime: the main thread owns terminal input, OpenTUI rendering, dialogs, routes, and local process lifecycle, while a Bun worker owns the server app, project instances, SDK fetches, event streams, upgrades, and cleanup.

$0 \[project\]

### Default route

The bare `nikcli` command starts the TUI, resolves the working directory, validates any requested session, and passes initial args into the renderer.

Bun Worker

### Worker RPC

The worker exposes fetch, server startup, event subscription, reload, upgrade, and shutdown methods through `Rpc.client`.

Solid

### OpenTUI renderer

`app.tsx` builds the Solid provider tree, command registry, route switch, dialogs, plugin routes, and keyboard handling.

@nikcli-ai/sdk/httpapi

### SDK surface

The UI talks to the same Hono app as external clients, either through in-process fetch or through an HTTP server when network access is enabled.

## Visual tour

The captures on this page come from two sources: a recorded `bun run --cwd packages/nikcli dev` session run through a terminal-control PTY at 112×34 with the OpenTUI host profile, and live in-session dialogs opened during real workflows. Bundles, raw `.termctrl` recordings, accessibility trees, SVGs, PNGs and manifests live under `artifacts/tui/tui-check/`; the same skill powers the [terminal-control](/docs/terminal-control) docs.

### Core shell

Empty home, slash-command autocomplete, and command palette — the surfaces reached before submitting a prompt.

[![nikcli TUI home screen with empty prompt and placeholder text](/docs/tui/home.png)](/docs/tui/home.png)

Home Empty home with localized placeholder and selected agent.

[![Slash command autocomplete popover after typing /](/docs/tui/slash-menu.png)](/docs/tui/slash-menu.png)

Slash autocomplete Typing ‘/’ opens the slash-command autocomplete popover over the prompt.

[![Command palette dialog with suggested and system commands](/docs/tui/command-palette.png)](/docs/tui/command-palette.png)

Command palette Ctrl+P opens the full command palette with Suggested and System categories.

### In-app surfaces

Full-screen dialogs that the TUI renders on top of the shell: context-usage telemetry and analytics with KPIs, charts, gauges, and operational health badges.

[![Context usage dialog with prompt composition, top sources, tokens-per-turn sparkline and context health](/docs/tui/context-usage.png)](/docs/tui/context-usage.png)

Context usage Live context window pressure, cache efficiency, prompt composition, top sources and context health with fragmentation flag.

[![Analytics overview dialog with session, message, cost and token KPIs, last-30-days charts and operational health](/docs/tui/analytics-overview.png)](/docs/tui/analytics-overview.png)

Analytics — Overview Tabbed analytics dashboard: sessions, messages, cost, tokens KPIs; tokens/day histogram, cost sparkline, usage composition donut, efficiency gauges, operational health.

### Workspace view

The workspace route (`type: "workspace"`, defined in `context/route.tsx`) is a four-tab panel that overlays any session. Each tab is registered as a hidden command in `app.tsx` (lines 714–787) and reachable from the command palette or as a slash command.

| Tab | Slash | Purpose |
| --- | --- | --- |
| **Sessions** | `/tree` | Session tree with focus and timeline: shows every session for the active workspace, marks the focused session, and supports the delegated-task hierarchy (root, subagents, plan/decision points). |
| **Changes** | `/changes` | Git diff with stage/unstage, file and hunk navigation, commit, push, and PR publish. |
| **Graph** | `/graph` · `/gitgraph` · `/commits` | Commit graph with log search, branch/lane navigation, and one-key checkout or reset. |
| **GitHub** | `/github` · `/gh` | GitHub integration: PR list, create/merge PR, run workflow, branch protection status. |

[![Workspace view with Sessions tab active, focus on a session tree showing 1 root and 674 sessions, with a branch expanded into two delegated tasks](/docs/tui/workspace-sessions.png)](/docs/tui/workspace-sessions.png)

Workspace — Sessions Four-tab workspace view: Sessions (session tree with focus and timeline), Changes (git diff and stage/unstage), Graph (commit graph), GitHub (PR / branch / workflow). Cycle with tab or slash commands /tree, /changes, /graph, /github.

Reproducible captures

Re-run the recorded shell capture with `bun run --cwd packages/terminal-control control -- start tui-check --host opentui --cols 112 --rows 34 --record /tmp/tui-check.termctrl -- env NIKCLI_TERMINAL=1 bun run --cwd /Volumes/.../packages/nikcli dev`, then re-bundle per marker: `--at-marker home|slash-menu|palette --result passed ...`.

## Default command

`TuiThreadCommand` is registered in `packages/nikcli/src/index.ts` before the one-shot run command. The command name is `$0 [project]`, which makes it the default yargs route. It accepts project path, model, continue, session, prompt, and agent options, plus shared network flags from `withNetworkOptions`.

| Input | Runtime effect | Source |
| --- | --- | --- |
| `[project]` | Resolved by `resolveThreadDirectory`; relative paths are based on `PWD` so shell `--cwd` behavior stays intact. | `thread.ts` |
| `--model` | Forwarded into the TUI args context as the initial provider/model selection. | `thread.ts`, `context/args` |
| `--continue` | Signals that the UI should continue the latest session instead of starting from an empty home route. | `thread.ts`, session route code |
| `--session` | Validated with `Session.ID.safeParse` and `sdk.session.get` before the renderer starts. | `validateSession` |
| `--prompt` | Combined with piped stdin when stdin is not interactive, then passed into `ArgsProvider`. | `thread.ts` |
| `--agent` | Forwarded into the args context so session startup can select the requested agent. | `thread.ts` |
| `--port`, `--hostname`, `--mdns` | Switch the TUI from in-process RPC transport to a worker-hosted HTTP server. | `resolveNetworkOptions` |

PTY detection matters

Mobile and nikcli-managed PTYs set `NIKCLI_TERMINAL=1`. The TUI treats that as an interactive terminal even if Bun reports `stdin.isTTY` as false, avoiding the piped-stdin path that would otherwise block renderer startup.

## Worker model

The main process chooses `NIKCLI_WORKER_PATH`, the built worker file, or the local TypeScript worker source. It then starts a Bun `Worker` with a sanitized environment where `Process.ROLE_ENV` is `worker` and a run id is guaranteed. The worker calls `Process.ensureMetadata("worker")`, initializes logging, listens for global bus events, and exposes an RPC object.

| RPC method | Purpose | Operational detail |
| --- | --- | --- |
| `fetch` | Runs SDK requests against `Server.App().fetch`. | Adds Basic Auth from server flags when configured and returns status, headers, and text body. |
| `server` | Starts or restarts a Bun Hono server from the worker. | Stops the old server before replacing it; returns the concrete URL. |
| `subscribe` | Starts an event stream for a project directory. | Creates an SDK client against `http://nikcli.local` using worker-local fetch and emits each event back through RPC. |
| `unsubscribe` | Stops a worker event stream. | Aborts the matching stream controller and removes it from the stream map. |
| `checkUpgrade` | Runs passive upgrade checks after the renderer starts. | Executes inside `Instance.provide` for the current directory. |
| `upgradeNow` | Runs an explicit upgrade requested by the TUI. | Delegates to `upgradeNow(method, version)` inside the project instance. |
| `reload` | Disposes all project instances after `SIGUSR2`. | Used by development reload flows. |
| `shutdown` | Stops event streams, disposes instances, and stops the HTTP server. | The main thread waits up to five seconds before terminating the worker. |

Shutdown is explicit

The TUI does not rely on worker process exit for cleanup. `onExit` calls worker shutdown, `onBeforeExit` disposes TUI plugin runtime state, and the main thread terminates the worker only after the RPC shutdown path returns or times out.

## Transport modes

The TUI has two runtime transports. Both use the generated SDK surface, but they differ in how requests and events reach the server app.

| Mode | When used | Fetch path | Event path |
| --- | --- | --- | --- |
| Direct worker transport | No explicit port, hostname, mdns, or non-local configured server values. | `createWorkerFetch` forwards the request over RPC to `worker.fetch`. | `createEventSource` subscribes through worker RPC and receives `event` emissions. |
| Worker HTTP server | Network options request an externally reachable server. | The SDK uses normal `fetch` against the returned HTTP URL. | `SDKProvider` opens server SSE through `sdk.event.subscribe`. |

Server on demand

Even when the TUI starts in direct mode, the session UI can later call the `startServer` callback. That asks the worker to bind a local server on port `0` and returns the URL for local share or preview flows.

## Renderer tree

`packages/tui/src/app.tsx` creates an OpenTUI renderer with a 45 FPS target, Kitty keyboard support, mouse support from TUI config, copy selection handling, and explicit Ctrl-C behavior. The Solid app is mounted inside an error boundary and a deep provider stack.

| Provider group | Purpose |
| --- | --- |
| `ArgsProvider`, `ExitProvider`, `ServerProvider` | Expose command args, restart/exit hooks, server startup, and plugin disposal on exit. |
| `SDKProvider`, `ProjectProvider`, `SyncProvider` | Create the SDK client, subscribe to project/server events, and keep synchronized runtime state. |
| `AnalyticsProvider`, `KVProvider`, `LocalProvider` | Expose usage, local preferences, model favorites, and small local state to routes and dialogs. |
| `ThemeProvider`, `KeybindProvider`, `AttentionProvider` | Apply terminal palette mode, user keybinds, and attention/notification handling. |
| `DialogProvider`, `CommandProvider`, `ToastProvider` | Manage dialogs, slash commands, keyboard command triggers, and toast rendering. |
| `PromptHistoryProvider`, `PromptStashProvider`, `PromptRefProvider` | Power prompt history, frecency, stash, and imperative prompt operations. |
| `EditorContextProvider`, `UpgradeProvider` | Expose editor integration state and interactive upgrade actions. |

## Feature plugins

Almost every surface the TUI shows — home tips, sidebar panels, the plugin manager, the wallpaper, the loop and mission managers — is a self-contained feature plugin under `packages/tui/src/feature-plugins/`, loaded through the same runtime that loads third-party plugins from npm. The larger ones follow the same layout: `store.ts` for reactive state, `sdk.ts` for typed SDK bindings, `runner.ts` for the local control plane, and `dialogs.tsx` for the Solid components.

| Plugin | Purpose |
| --- | --- |
| `home` | Rotating tip line in the `home.bottom` slot, plus the `tips.toggle` command. |
| `sidebar` | Six independent slot plugins: context/cost, MCP, LSP, todo, modified files, and the footer. |
| `system` | The plugin manager (list, toggle, install) and the OpenRouter Fusion preset manager. |
| `background` | Paints a real image behind the TUI via OpenTUI's native half-block super-sampler; owns `/background`. |
| `loops` | [Loops](/docs/loops) manager, creation wizard and live sidebar. Mirrors the loop SDK contract. |
| `mission` | [Missions](/docs/missions) manager, planning wizard and live sidebar. Mirrors the mission SDK contract. |
| `brain` | Arms the Brain consolidation scheduler and registers `/brain` for an on-demand pass. |
| `browser` | Browser-use control surface for the `browser` tool (page navigation, snapshots, action log). |
| `chatbot` | Chat SDK bots — start/stop per platform and show the webhook path each one must be pointed at. |
| `computer` | Probes the host and reports what the `computer` tool can do on this machine. |
| `connectors` | Live connector health, cache invalidation, and stored-credential removal inside the TUI. |
| `observability` | Live OpenTelemetry spans and OTLP endpoint dialogs (see [Observability](/docs/observability)). |
| `island` | Toggles the macOS Island notch integration for the current TUI session. Registered only under `--island`. See [Island plugin](#island) below. |

Each plugin's commands, slash aliases, KV keys and internals are documented in [TUI feature plugins](/docs/tui-plugins).

TUI plugin runtime

Plugins register commands, routes, dialogs, keybinds, and sidebar / status slots through `packages/tui/src/plugin/api.tsx`. Runtime cleanup runs on exit through `TuiPluginRuntime.dispose()` — plugins should not rely on process exit for teardown.

## Dialogs

All dialogs live in `packages/tui/src/component/` and are registered through the `DialogProvider` and the command palette. The full list (in addition to those in the top-level README):

| Dialog | Source | Purpose |
| --- | --- | --- |
| `dialog-telemetry-live` | `dialog-telemetry-live.tsx` | Scrolling, filterable list of live OpenTelemetry spans (see [Observability](/docs/observability)). |
| `dialog-opentelemetry` | `dialog-opentelemetry.tsx` | OTLP configuration dialog: shows the resolved endpoint, the resource block, and a "Copy as env" action. |
| `dialog-advisor-model` | `dialog-advisor-model.tsx` | Pick the model used by the `advisor` tool. |
| `dialog-browser-use` | `dialog-browser-use.tsx` | Browser-use control surface: live snapshot, navigation, action log for the `browser` tool. |
| `dialog-routine` | `dialog-routine.tsx` | Routine create/edit dialog backed by the routine SDK. |
| `dialog-session-warp` | `dialog-session-warp.tsx` | Quick-jump to another session by fuzzy match. |
| `dialog-session-rename` | `dialog-session-rename.tsx` | Rename the active session. |
| `dialog-session-delete-failed` | `dialog-session-delete-failed.tsx` | Confirmation when a session delete failed and the local cache is stale. |
| `dialog-mobile-connect` | `dialog-mobile-connect.tsx` | Show a pairing QR code and bearer token, plus copy-to-clipboard, from inside the TUI. |
| `dialog-sync` | `dialog-sync.tsx` | [Sync](/docs/sync) status, hub URL/token config, autostart toggle, and reconnect. |
| `dialog-teleport` | `dialog-teleport.tsx` | Pick a session and remote target, then ship the session through `nikcli teleport`. |
| `dialog-workspace-create` | `dialog-workspace-create.tsx` | Create a new multi-project workspace. |
| `dialog-workspace-list` | `dialog-workspace-list.tsx` | Switch between workspaces. |
| `dialog-workspace-unavailable` | `dialog-workspace-unavailable.tsx` | Show the error that prevents entering a workspace. |
| `dialog-workspace-file-changes` | `dialog-workspace-file-changes.tsx` | Aggregate file changes across the workspace's projects. |
| `plugin-route-missing` | `plugin-route-missing.tsx` | Rendered when a TUI plugin navigates to a route id that is not registered. Offers a "go home" action instead of crashing. |

## Routes

The TUI route state lives in `context/route.tsx`. It is a Solid store initialized from `NIKCLI_ROUTE` when present, otherwise from the home route. Route changes are in-memory UI navigation, not browser history.

| Route type | Fields | Used for |
| --- | --- | --- |
| `home` | `initialPrompt`, `workspaceID` | Default landing route and empty prompt state. |
| `session` | `sessionID`, `initialPrompt`, `workspaceID` | Main transcript and prompt view for one session. |
| `workspace` | `tab`, `sessionID`, `workspaceID` | Unified workspace panel with tree, changes, graph, and GitHub tabs. |
| `tree`, `changes`, `git-graph`, `github` | `sessionID`, `workspaceID` | Legacy route shapes redirected into the workspace view when needed. |
| `plugin` | `id`, `data`, `workspaceID` | TUI plugin routes registered through the plugin runtime. |

The app keeps a mutable plugin route map and a reactive route key. When plugin routes change, the key is bumped so the route switch re-renders. Missing plugin routes render `PluginRouteMissing` with a home navigation action instead of crashing the TUI.

## Commands and keybinds

`CommandProvider` owns the command registry. Components register command options with titles, descriptions, optional keybind names, optional slash aliases, categories, visibility, and an `onSelect` callback. The registry deduplicates by category plus value, merges local and global registrations, and exposes command palette, slash, and trigger APIs.

| Mechanism | Behavior |
| --- | --- |
| Command palette | The configured `command_list` keybind opens a `DialogSelect` of visible commands. |
| Direct keybinds | The provider listens to keyboard events, skips disabled commands and active dialogs, and calls the matching command callback. |
| Slash commands | Visible command options with `slash` metadata become prompt slash entries with aliases. |
| Programmatic trigger | `command.trigger(name)` runs the matching command; TUI HTTP events use this path. |
| Suspension | `keybinds(false)` increments a suspend counter so dialogs or text inputs can temporarily suppress command shortcuts. |
| Permission mode keybind | The `permission_mode` command opens `DialogPermissionMode` — see [Permission modes](/docs/permissions#modes). It applies the selected preset (`require_approval` / `approve_for_me` / `full_access`) to the current primary agent. |
| Session dialogs | `Context usage` (`/usage` or `/context`) opens `DialogUsage`; `Analytics` (`/analytics` or `/stats`) opens `DialogAnalytics`. See [Dialogs](#dialogs) for screenshots. |

## Dialogs

The TUI ships a few session-scoped dialogs that are useful to keep open while running long work. They are reachable from the command palette and as slash commands.

### Context usage

`DialogUsage` (`app.tsx` line 1074) shows how the active session is spending its 1.0 M-token context window. The dialog breaks the window into used / free / reserved slices, plots a tokens-per-turn sparkline over the last 77 turns, surfaces the top token sources (system prompt, tools, skills, conversation, plus per-tool contributions from `opentui`, `terminal-control`, `generate_image`, `browser`, etc.), and ends with a four-card _Context health_ strip (Usage, Cache, Headroom, Fragmentation).

[![Context usage dialog](/docs/tui/context-usage.png)](/docs/tui/context-usage.png)

### Analytics

`DialogAnalytics` (`app.tsx` line 1086) is a 6-tab dashboard loaded through `AnalyticsProvider` against the host's `/analytics/*` HTTP endpoints. The Overview tab shows sessions, messages, cost, tokens with deltas vs. the prior window, the last-30-day token and cost histograms, the usage-composition stack (input / output / cache / reasoning), and the four-card Operational health strip (Activity, Tools, Background, Workspaces). Tabs `2 Tokens`, `3 Models`, `4 Tools`, `5 Projects`, `6 Sessions` drill into each slice; cycle with `tab` or `1`–`6`.

[![Analytics overview tab](/docs/tui/analytics-overview.png)](/docs/tui/analytics-overview.png)

## Event flow

`SDKProvider` creates a generated SDK client and an event emitter keyed by event type. It batches event delivery inside Solid `batch` calls, flushing immediately unless another flush happened in the last 16 milliseconds.

1.  **Direct mode** passes a custom `EventSource` from `thread.ts`; the worker opens the SDK event stream and emits events over RPC.
2.  **HTTP mode** calls `sdk.event.subscribe` directly and retries with exponential backoff from 250 ms to 5 seconds.
3.  **Queueing** batches received events to keep bursty server updates from forcing one render per event.
4.  **TUI commands** listen for `tui.command.execute`, `tui.toast.show`, and `tui.session.select` events and map them to local UI actions.
5.  **Cleanup** aborts the SDK signal, active SSE controller, and pending flush timer on provider unmount.

| TUI event | Payload | Local action |
| --- | --- | --- |
| `tui.prompt.append` | `text` | Append text to the active prompt. |
| `tui.command.execute` | `command` | Trigger a registered command such as session list, submit prompt, page up, compact, or agent cycle. |
| `tui.toast.show` | `title`, `message`, `variant`, `duration` | Render an info, success, warning, or error toast. |
| `tui.session.select` | `sessionID` | Navigate to the requested session after the server validates it exists. |

Workspace event catch-up

The session sync layer replays missed events for every workspace when the TUI starts or reconnects, so per-project state is identical to the server at render time.

## Island plugin

The Island plugin is the CLI-side mirror of the macOS `NikcliIsland.app` notch UI. It writes a per-session JSON snapshot under `~/Library/Application Support/NikcliIsland/state.d/` on every relevant event, and the native app polls that directory to render live session state without keeping a separate IPC channel.

The bridge lives in `@nikcli-ai/util/island-bridge` and is self-activating: `Bus.publish` itself calls `start()` on first publish (idempotent, no-op after the first real call). That matters because the TUI's real session / bus activity runs inside a `Worker` thread with isolated module state — a listener registered only in the parent process would never see those events.

| State | Source | Display |
| --- | --- | --- |
| `idle` | `session.status` → `idle` | Empty pill on the notch. |
| `thinking` | `session.status` → `busy` / `retry` | "Thinking…" or "Retrying (N)…". |
| `tool` | `message.part.updated` tool status | Friendly label plus file basename, lingers for ~1.9 s after completion. |
| `permission` | `permission.asked` | "Awaiting permission" + the request id for one-tap approval. |
| `error` | `session.error` | "Error" pill. |
| `done` | (reserved) | Used by the native app to mark a completed row before archival. |

Each snapshot also carries the parent session id, agent title, project, cwd, the process pid, the local server port (when one is running), and a server URL — enough for the native app to call `POST /permission/:requestID/reply` directly when you approve a pending request from the notch.

Terminal

bash 

```
# Inspect Island state
cat ~/Library/Application\ Support/NikcliIsland/state.d/nikcli-ses_abc.json

# Disable Island for this run without uninstalling the app
NIKCLI_ISLAND_DISABLE=1 nikcli
```

$ shell · paste into your terminal

macOS only

Island is a no-op on Windows and Linux: the native app is a macOS-only notch UI. The `NIKCLI_ISLAND_TEST_FORCE_DARWIN=1` flag exists only for the bridge's own test suite — never set it in production.

Toggling from inside the TUI

The Island feature plugin's activate / deactivate calls `IslandBridge.setEnabled()` — toggling off clears the current snapshots and stops writing, toggling back on resumes through the already-registered listener without any race window.

## HTTP control routes

`packages/nikcli/src/server/httpapi/tui.ts` exposes routes that remote clients, companion surfaces, and automation can use to nudge the running TUI. Most routes publish a typed `TuiEvent` onto the bus. `/tui/control` is a queue bridge for request/response handoff.

| Route | Publishes or returns | Use |
| --- | --- | --- |
| `POST /tui/append-prompt` | `TuiEvent.PromptAppend` | Append text into the active prompt input. |
| `POST /tui/open-help` | `help.show` | Open the help dialog. |
| `POST /tui/open-sessions` | `session.list` | Open session selection. |
| `POST /tui/open-themes` | `theme.switch` | Open theme selection. |
| `POST /tui/open-models` | `model.list` | Open model selection. |
| `POST /tui/submit-prompt` | `prompt.submit` | Submit the current prompt. |
| `POST /tui/clear-prompt` | `prompt.clear` | Clear prompt input. |
| `POST /tui/execute-command` | Mapped command name | Translate legacy remote names such as `agent_cycle` into current command ids. |
| `POST /tui/show-toast` | `TuiEvent.ToastShow` | Show a notification in the terminal. |
| `POST /tui/publish` | Any typed `TuiEvent` | Generic event publish endpoint with zod validation. |
| `POST /tui/select-session` | `TuiEvent.SessionSelect` | Validate a session through `Session.Service`, then navigate the TUI. |
| `GET /tui/control/next` | Queued TUI request | Pull the next queued control request. |
| `POST /tui/control/response` | `true` | Push the response for a pending queued request. |

## Config and plugins

TUI-specific configuration is loaded by `packages/nikcli/src/config/tui.ts`. The loader reads global config directory files, `NIKCLI_TUI_CONFIG`, project TUI files, `.nikcli` config directories, and managed config. It migrates old TUI config, flattens nested `tui` objects, parses keybinds, deduplicates plugin specs, and records plugin metadata with local or global scope.

| Config area | Runtime use |
| --- | --- |
| `mouse` | Controls OpenTUI mouse support; defaults to enabled. |
| `keybinds` | Parsed through shared config keybind parsing and consumed by `KeybindProvider`. |
| `scroll_speed`, `scroll_acceleration` | Consumed by session route scroll logic. |
| `diff_style` | Used by session permission and diff rendering components. |
| `plugin` | Feeds `TuiPluginRuntime.init` through `createTuiApi`. |
| `plugin_meta` | Records whether each plugin came from local or global config and the source file. |

The TUI plugin API can register commands, plugin routes, and UI contributions. Runtime cleanup is tied to exit through `TuiPluginRuntime.dispose()`, so plugins should not depend on process exit for teardown.

## Remote and attach

The TUI can either host its own local server for external access or attach to an existing server. `nikcli attach <url>` calls the same `tui` renderer with the provided URL instead of starting a local worker server. Remote Control uses TUI commands and server routes to drive the active prompt, sessions, scrolling, compacting, sharing, and interruption flows.

Terminal

bash 

```
nikcli --port 4096 --hostname 127.0.0.1
nikcli attach http://127.0.0.1:4096
curl -X POST http://127.0.0.1:4096/tui/open-sessions
curl -X POST http://127.0.0.1:4096/tui/execute-command -H 'content-type: application/json' -d '{"command":"agent_cycle"}'
```

$ shell · paste into your terminal

Companion is separate

`nikcli companion serve` and `nikcli companion open` start the desktop browser companion surface. It connects to the same server/event model, but it is not the terminal renderer and it is distinct from the mobile host.

## Operational checks

Use these checks when debugging TUI startup, remote control, or event delivery.

Terminal

bash 

```
NIKCLI_TERMINAL=1 bun run dev
nikcli --print-logs --log-level DEBUG
nikcli --port 0 --hostname 127.0.0.1
nikcli attach http://127.0.0.1:4096
curl -i http://127.0.0.1:4096/global
curl -i -X POST http://127.0.0.1:4096/tui/open-help
```

$ shell · paste into your terminal

| Check | What it proves |
| --- | --- |
| Start without network flags | Direct worker fetch and worker event subscription work. |
| Start with `--port` | Worker server startup, HTTP fetch, and server-side SSE work. |
| Call `/tui/open-help` | HTTP route can publish a TUI command event and the renderer is subscribed. |
| Call `/global` | Server auth, request middleware, and basic SDK reachability are healthy. |
| Use `SIGUSR2` | Worker reload disposes project instances without restarting the renderer process. |

## Failure modes

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| TUI hangs before drawing | Bun reports non-TTY stdin and no `NIKCLI_TERMINAL=1`, so the command is waiting for piped stdin. | Run from an interactive terminal, set `NIKCLI_TERMINAL=1` for managed PTYs, or provide stdin intentionally. |
| Session id rejected before UI starts | `--session` failed schema parsing or `sdk.session.get` returned an error. | Use a valid `ses...` id from `nikcli session list` and confirm the data root/project matches. |
| Remote button does nothing | The TUI is in direct mode and no local HTTP server has been started yet, or the server callback failed. | Start with `--port` or trigger the on-demand local server path and inspect worker logs. |
| HTTP TUI route returns true but UI does not change | The server published the event, but the renderer SDK event subscription is disconnected. | Check SSE reconnect logs, server auth flags, and whether the TUI is attached to the same server URL. |
| Keyboard shortcut ignored | A dialog or text input has suspended command keybinds, or the command is disabled for the current route. | Close the dialog, check route-specific command registration, and inspect TUI keybind config. |
| Plugin route missing | A plugin navigated to a route id that is not registered in the current plugin route map. | Verify the TUI plugin was loaded from config and registered its route before navigation. |
| Worker does not exit cleanly | A server, event stream, or instance finalizer is still shutting down after the five-second timeout. | Inspect worker logs and confirm server/event streams stop through `shutdown`. |

## Source anchors

| File | What to inspect |
| --- | --- |
| `packages/nikcli/src/cli/cmd/tui/thread.ts` | Default command, worker startup, network mode selection, session validation, prompt input, restart, and shutdown. |
| `packages/nikcli/src/cli/cmd/tui/worker.ts` | RPC server, worker fetch, event stream bridge, server startup, reload, upgrade, cleanup, and `IslandBridge.stop()`. |
| `packages/tui/src/app.tsx` | OpenTUI renderer config, provider tree, route rendering, dialogs, command registration, plugin runtime, and event handlers. |
| `packages/tui/src/context/sdk.tsx` | SDK client creation, direct event source support, SSE retry loop, event batching, and cleanup. |
| `packages/tui/src/context/route.tsx` | Route shapes, route store initialization from `NIKCLI_ROUTE`, navigation, and override provider. |
| `packages/tui/src/context/remote-sync.tsx` | Remote sync state and stats used by the sync dialog. |
| `packages/tui/src/component/dialog-command.tsx` | Command registry, slash command projection, keybind handling, suspension, and palette dialog. |
| `packages/tui/src/component/dialog-sync.tsx` | Sync dialog: URL / token config, autostart toggle, reconnect. |
| `packages/tui/src/component/dialog-teleport.tsx` | Teleport dialog: pick a session and remote target, then ship it. |
| `packages/tui/src/component/dialog-mobile-connect.tsx` | Pairing QR + bearer token from inside the TUI. |
| `packages/tui/src/component/dialog-browser-use.tsx` | Browser-use control surface for the `browser` tool. |
| `packages/tui/src/event.ts` | Typed TUI bus events and zod payload schemas. |
| `packages/nikcli/src/server/httpapi/tui.ts` | Remote TUI control HTTP routes and queue bridge. |
| `packages/nikcli/src/config/tui.ts` | TUI config loading, migration, keybind parsing, plugin deduplication, and plugin metadata. |
| `packages/nikcli/src/cli/cmd/tui/attach.ts` | Attach command path for connecting the renderer to an existing server URL. |
| `@nikcli-ai/util/island-bridge` | Island file-contract writer, `setEnabled`, and shutdown semantics. |
| `packages/tui/src/feature-plugins/island/index.tsx` | TUI-side Island plugin (activate / deactivate the bridge). |

---

Source: https://nikcli-ai.dev/docs/tui
