Official agent skill

Tui Development

by NVIDIA in NVIDIA/OpenShell

Guide for developing the OpenShell TUI — a ratatui-based terminal UI for the OpenShell platform.

OfficialApache-2.0Auto-check passedFrontend & Design

Install Tui Development

skills CLI
$ npx skills add NVIDIA/OpenShell --skill tui-development -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install NVIDIA/OpenShell tui-development --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/NVIDIA/OpenShell.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/tui-development .claude/skills/tui-development && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
tui-development
GitHub stars
15k
Token cost
~8.5k tokens
SKILL.md length
3,392 words
Files
1
Skills in repo
23
Repo updated
First seen
Licence
Apache-2.0

At a glance

Guide for developing the OpenShell TUI — a ratatui-based terminal UI for the OpenShell platform.

  • Works in 9 steps: Overview → Domain Object Hierarchy → Navigation & Screen Architecture → …
  • Keywords - term
  • SKILL.md covers 1. Overview, 2. Domain Object Hierarchy, 3. Navigation & Screen… and 4. Data Fetching Pattern, plus 3 more sections
  • Calls mise, cargo and helm

What it does

Tui Development is an agent skill from NVIDIA/OpenShell, published by the product's own GitHub organization. Guide for developing the OpenShell TUI — a ratatui-based terminal UI for the OpenShell platform. Covers architecture, navigation, data fetching, theming, UX conventions, and development workflow. Trigger keywords - term, TUI, terminal UI, ratatui, openshell-tui, tui development, tui feature, tui bug.

Its SKILL.md is about 8.5k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Frontend & Design, covering Theming and dark mode. It works with NVIDIA AI Platform. The repository describes itself as: OpenShell is the safe, private runtime for autonomous AI agents. The licence is Apache-2.0.

When your agent uses it

  • Keywords - term
  • Tui development

Example prompts

  • “/tui-development”

Workflow steps

9 steps, taken from the step headings in SKILL.md.

  1. Overview
  2. Domain Object Hierarchy
  3. Navigation & Screen Architecture
  4. Data Fetching Pattern
  5. Style Guide & Colors
  6. UX Conventions
  7. Architecture & Key Files
  8. Technical Notes
  9. Development Workflow

What it can do on your machine

Read from SKILL.md and the folder at commit 834b79a. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • mise
    • cargo
    • helm
    • kubectl
    • sh

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md. Its commands use helm and kubectl, which can reach the network depending on how they are called.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Tui Development loads about 8.5k tokens when it runs. Until then it costs about 79 tokens; SKILL.md has 3,392 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~79
When it runs · the whole SKILL.md, loaded when a task matches
~8.5k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from NVIDIA/OpenShell at commit 834b79a, republished under its Apache-2.0 licence (© NVIDIA). 3,392 words, ~8,478 tokens.

Download SKILL.mdSave it as .claude/skills/tui-development/SKILL.md (or your agent's skills folder).
name
tui-development
description
Guide for developing the OpenShell TUI — a ratatui-based terminal UI for the OpenShell platform. Covers architecture, navigation, data fetching, theming, UX conventions, and development workflow. Trigger keywords - term, TUI, terminal UI, ratatui, openshell-tui, tui development, tui feature, tui bug.
metadata.internal
true

OpenShell TUI Development Guide

Comprehensive reference for any agent working on the OpenShell TUI.

1. Overview

The OpenShell TUI is a ratatui-based terminal UI for the OpenShell platform. It provides a keyboard-driven interface for managing gateways, sandboxes, and logs — the same operations available via the openshell CLI, but with a live, interactive dashboard.

  • Launched via: openshell term or mise run term
  • Crate: crates/openshell-tui/
  • Key dependencies:
    • ratatui (workspace version) — uses frame.area() for the drawable terminal area
    • crossterm (workspace version) — terminal backend and event polling
    • tonic with TLS — gRPC client for the OpenShell gateway
    • tokio — async runtime for event loop, spawned tasks, and mpsc channels
    • openshell-core — proto-generated types (OpenShellClient, request/response structs)
    • openshell-bootstrap — gateway discovery (list_gateways())
  • Theme: Adaptive dark/light via Theme struct — NVIDIA-branded green accents. Controlled by --theme flag, OPENSHELL_THEME env var, or auto-detection.

2. Domain Object Hierarchy

The data model follows a strict hierarchy: Gateway > Workspace > Sandboxes/Providers/Settings > Logs.

Gateway (discovered via openshell_bootstrap::list_gateways())
  ├── Global Settings (fetched via GetGatewayConfig)
  ├── Global Policy indicator (fetched via ListSandboxPolicies global=true)
  ├── Workspaces (fetched via ListWorkspaces)
  ├── Provider Profiles (fetched via ListProviderProfiles, workspace-scoped)
  ├── Providers (fetched via ListProviders, workspace-scoped)
  │     └── cached ProviderProfile (matched by type + workspace)
  └── Sandboxes (fetched via ListSandboxes, workspace-scoped)
        ├── Policy (fetched via GetSandboxConfig)
        ├── Settings (effective settings with scope, from GetSandboxConfig)
        ├── Draft recommendations (fetched via GetDraftPolicy)
        └── Logs (fetched via GetSandboxLogs + streamed via WatchSandbox)
  • Gateways are discovered from on-disk config via openshell_bootstrap::list_gateways(). Each gateway has a name, endpoint, local/remote flag, and source label.
  • Workspaces are fetched via ListWorkspaces. The user cycles through workspaces with [w], or views all workspaces at once. The current workspace scopes provider and sandbox lists.
  • Provider Profiles are fetched per-workspace via ListProviderProfiles. Profiles are cached in a ProviderProfileCache keyed by (workspace, profile_id) and matched to providers by type. They provide category, credential metadata, endpoint/binary counts, and inference capability.
  • Providers are fetched via ListProviders scoped to the current workspace. Each ProviderListEntry pairs a provider with its optional cached profile. The TUI supports profile-backed create, update, and delete operations.
  • Global Settings are fetched via GetGatewayConfig and displayed in a tabbed pane alongside providers on the dashboard. Each setting is a registered key with a typed value (bool/int/string). Platform-admin access is required; PermissionDenied disables the pane.
  • Sandboxes belong to the active gateway and workspace. Fetched via ListSandboxes with a periodic tick refresh.
  • Sandbox Settings are effective settings returned by GetSandboxConfig, each with a scope (sandbox, global, or unset). Globally-managed settings are blocked from sandbox-level edits.
  • Logs belong to a single sandbox. Initial batch fetched via GetSandboxLogs (500 lines), then live-tailed via WatchSandbox with follow_logs: true.

The title bar always reflects this hierarchy, reading left-to-right from general to specific:

 OpenShell v<version> │ Current Gateway: <name> [source] (<status>) │ Workspace: <name|all> │ <screen/context>

3. Navigation & Screen Architecture

Screens (Screen enum)

Top-level layouts that own the full content area. Each has its own nav bar hints.

ScreenDescriptionModule
SplashBoot screen shown on startup, auto-dismissed after 3 secondsui/splash.rs
DashboardGateway list (top) + providers/settings (middle) + sandbox table (bottom)ui/dashboard.rs
SandboxSingle-sandbox view — metadata (top) + policy/settings/logs/drafts (bottom)ui/sandbox_detail.rs, ui/sandbox_policy.rs, ui/sandbox_settings.rs, ui/sandbox_logs.rs, ui/sandbox_draft.rs
Focus (Focus enum)

Tracks which panel currently receives keyboard input.

FocusScreenDescription
GatewaysDashboardGateway list panel has input focus
ProvidersDashboardProvider list or global settings pane (depends on MiddlePaneTab)
SandboxesDashboardSandbox table panel has input focus
SandboxPolicySandboxPolicy viewer or settings table (depends on SandboxPolicyTab)
SandboxLogsSandboxLog viewer with structured rendering
SandboxDraftSandboxDraft policy recommendations list
Tab enums

Two tab enums control which sub-view renders within a focus area:

  • MiddlePaneTab (Providers | GlobalSettings): toggles the middle dashboard pane between the provider list and the global settings table. Switched with [h/l].
  • SandboxPolicyTab (Policy | Settings): toggles the sandbox bottom pane between the policy viewer and the sandbox settings table. Switched with [h].
Screen dispatch

The top-level ui::draw() function (ui/mod.rs) handles the chrome (title bar, nav bar, command bar) and dispatches to the correct screen module:

rust
match app.screen {
    Screen::Splash => unreachable!(),
    Screen::Dashboard => dashboard::draw(frame, app, chunks[1]),
    Screen::Sandbox => draw_sandbox_screen(frame, app, chunks[1]),
}

Within the Sandbox screen, sandbox_detail::required_height sizes the metadata and restart status pane to its contents. The remaining area dispatches based on focus and tab state:

rust
match app.focus {
    Focus::SandboxLogs => sandbox_logs::draw(frame, app, chunks[1]),
    Focus::SandboxDraft => sandbox_draft::draw(frame, app, chunks[1]),
    _ => match app.sandbox_policy_tab {
        SandboxPolicyTab::Settings => sandbox_settings::draw(frame, app, chunks[1]),
        SandboxPolicyTab::Policy => sandbox_policy::draw(frame, app, chunks[1]),
    },
}

On the dashboard, the middle pane dispatches by MiddlePaneTab:

rust
match app.middle_pane_tab {
    MiddlePaneTab::Providers => providers::draw(frame, app, chunks[1], mid_focused),
    MiddlePaneTab::GlobalSettings => global_settings::draw(frame, app, chunks[1], mid_focused),
}
Layout structure

Every frame renders four vertical regions:

┌─────────────────────────────────────────────┐
│ Title bar (1 row) — brand + gateway + context│
├─────────────────────────────────────────────┤
│                                             │
│ Main content (flexible)                     │
│                                             │
├─────────────────────────────────────────────┤
│ Nav bar (1 row) — context-sensitive key hints│
├─────────────────────────────────────────────┤
│ Command bar (1 row) — `:` command input      │
└─────────────────────────────────────────────┘
Title bar examples
  • Dashboard: >_ OpenShell v<version> | Current Gateway: openshell [local] (Healthy) | Workspace: default | Dashboard
  • Sandbox detail: >_ OpenShell v<version> | Current Gateway: openshell [local] (Healthy) | Workspace: team-a | Sandbox: my-sandbox
Adding a new screen
  1. Add a variant to Screen in app.rs.
  2. Create a new module under src/ui/ with a pub fn draw(frame, app, area).
  3. Add the module declaration in ui/mod.rs.
  4. Add a match arm in ui::draw() to dispatch to the new module.
  5. Add relevant Focus variants if the screen has multiple panels.
  6. Add key handling methods in App for the new focus states.
  7. Add nav bar hints in draw_nav_bar() for the new screen/focus combinations.

4. Data Fetching Pattern

Initial fetch first, then stream

Always grab a batch of initial data so the UI has content immediately, then attach streaming for live updates.

Logs example (spawn_log_stream in lib.rs):

Phase 1: GetSandboxLogs  →  500 initial lines  →  send via Event::LogLines
Phase 2: WatchSandbox(follow_logs: true)  →  live tail  →  send via Event::LogLines

Sandboxes: Fetched via ListSandboxes in a background collection-refresh task scheduled from the 2-second tick, scoped to the current workspace (or all workspaces). Follow next_page_token until empty so the dashboard reflects the complete collection. The NOTES column summarizes active ConfigurationInvalid readiness conditions as Invalid config before port forwards and clears the note on refresh after repair. Full diagnostics remain available through openshell sandbox get <name> -o json. Timed-out provisioning attempts show Provisioning timed out with cleanup pending or compute reclaimed, preserving port forwards. The sandbox detail pane wraps the full configuration error in its Notes field.

Providers: Fetched via ListProviders in the background collection-refresh task. Provider profiles are fetched per-workspace via ListProviderProfiles and cached in a ProviderProfileCache keyed by (workspace, profile_id). Follow each list RPC's next_page_token until empty.

Settings: Global settings are fetched via GetGatewayConfig on each tick. Sandbox settings are fetched alongside the sandbox policy via GetSandboxConfig and refreshed on each tick when viewing a sandbox.

Workspaces: The workspace list is fetched via ListWorkspaces in the background collection-refresh task, following next_page_token until empty.

Only one collection-refresh task may run at a time. Workspace and gateway changes abort the active task, and refresh results carry their gateway/workspace context so stale results are discarded.

Never block the event loop

All network calls must be spawned as async tasks via tokio::spawn. The event loop in lib.rs must remain responsive to keyboard input and rendering at all times.

Pattern:

rust
// Background task sends data back via mpsc channel
let handle = tokio::spawn(async move {
    let result = client.some_rpc(request).await;
    let _ = tx.send(Event::SomeData(result));
});
Loading states

Show "Loading..." while async data is in flight (see sandbox_logs.rs — renders a loading message when filtered is empty and sandbox_log_lines is also empty).

Event channel

Background tasks communicate with the event loop via mpsc::UnboundedSender<Event>. The EventHandler provides a sender() method to clone the transmit handle. There are many Event variants for different async results (log lines, create results, provider CRUD results, setting CRUD results, draft action results, forward warnings):

rust
// In lib.rs
spawn_log_stream(&mut app, events.sender());

// In the spawned task
let _ = tx.send(Event::LogLines(lines));
Access denial handling

Global settings and global policy queries may return PermissionDenied when the user lacks platform-admin access. The TUI sets global_settings_access_denied / global_policy_access_denied flags to stop retrying these calls on subsequent ticks, and clears the corresponding UI state.

gRPC timeouts

All gRPC calls use a 5-second timeout via tokio::time::timeout:

rust
tokio::time::timeout(Duration::from_secs(5), client.health(req)).await

5. Style Guide & Colors

Theme System (theme.rs)

Colors and styles are defined in crates/openshell-tui/src/theme.rs via the Theme struct. The TUI supports dark and light terminal backgrounds.

Theme selection

Theme mode is controlled by three mechanisms (highest priority first):

  1. --theme dark|light|auto CLI flag on openshell term
  2. OPENSHELL_THEME environment variable
  3. Auto-detection via COLORFGBG env var (falls back to dark)

The ThemeMode enum (Auto, Dark, Light) is resolved at startup via theme::detect() before entering raw mode.

Brand colors (theme::brand)
ConstantValueUsage
NVIDIA_GREENColor::Rgb(118, 185, 0)Primary accent (dark theme)
NVIDIA_GREEN_DARKColor::Rgb(80, 140, 0)Primary accent (light theme — darker for contrast)
EVERGLADEColor::Rgb(18, 49, 35)Dark green — borders, title bar bg (dark theme)
MAROONColor::Rgb(128, 0, 0)Pacman chase animation
Theme struct fields

The Theme struct has 16 Style fields, accessed at runtime via app.theme:

FieldDark valueLight valueUsage
textWhite fgNear-black fgDefault body text
mutedWhite + DIMGray fgSecondary info, separators
headingWhite + BOLDNear-black + BOLDPanel titles, names
accentNVIDIA_GREEN fgNVIDIA_GREEN_DARK fgSelected row marker, source labels
accent_boldNVIDIA_GREEN + BOLDNVIDIA_GREEN_DARK + BOLDBrand text, command prompt
selectedBOLD onlyBOLD onlySelected row emphasis
borderEVERGLADE fgLight sage fgUnfocused panel borders
border_focusedNVIDIA_GREEN fgNVIDIA_GREEN_DARK fgFocused panel borders
status_okNVIDIA_GREEN fgNVIDIA_GREEN_DARK fgHealthy, INFO, Ready
status_warnYellow fgDark yellow fgDegraded, WARN, Provisioning, Starting
status_errRed fgDark red fgUnhealthy, ERROR
key_hintNVIDIA_GREEN fgNVIDIA_GREEN_DARK fgKeyboard shortcut labels
log_cursorEVERGLADE bgLight green bgSelected log line highlight
clawMAROON + BOLDMAROON + BOLDPacman animation
title_barWhite on EVERGLADE + BOLDNear-black on light green + BOLDTitle bar strip
badgeBlack on NVIDIA_GREEN + BOLDWhite on NVIDIA_GREEN_DARK + BOLDNotification badges
Accessing the theme in draw functions

The Theme is stored on App and accessed via a local alias:

rust
fn draw_my_widget(frame: &mut Frame<'_>, app: &App, area: Rect) {
    let t = &app.theme;
    frame.render_widget(
        Paragraph::new(Span::styled("Hello", t.text)),
        area,
    );
}

For functions that don't take &App (e.g., detail popups, helpers), pass &Theme as a parameter:

rust
fn draw_detail_popup(frame: &mut Frame<'_>, data: &MyData, area: Rect, theme: &Theme) {
    let t = theme;
    // ...
}
Visual conventions
  • Selected row: Green ▌ left-border marker on the selected row. Active gateway also gets a green ● dot.
  • Focused panel: Border changes from border to border_focused style.
  • Status indicators: Green for healthy/ready/info, yellow for degraded/provisioning/starting/warn, red for unhealthy/error.
  • Separators: Muted │ characters between title bar segments and nav bar sections.
  • Log source labels: "sandbox" source renders in accent (green), "gateway" in muted.

6. UX Conventions

Destructive actions require confirmation

Always show a y/n confirm dialog before delete, stop, or other irreversible operations.

Delete sandbox 'my-sandbox'? [y] Confirm  [Esc] Cancel

The confirm_delete flag in App gates destructive key handling — while true, only y, n, and Esc are processed.

CLI parity

TUI actions should parallel openshell CLI commands so users have familiar mental models:

CLI CommandTUI Equivalent
openshell sandbox listSandbox table on Dashboard
openshell sandbox delete <name>[d] on sandbox detail, then [y] to confirm
openshell sandbox create[c] on sandbox panel to open create form
openshell sandbox connect[s] on sandbox policy view to launch SSH shell
openshell logs <name>[l] on sandbox detail to open log viewer
openshell provider listProvider table on Dashboard (middle pane)
openshell provider create[c] on provider panel
openshell statusStatus in title bar + gateway list

When adding new TUI features, check what the CLI offers and maintain consistency.

The create form parses the optional Command field as shell words before starting creation. Preserve the parsed argument vector through post-create execution and shell-escape each argument at the SSH boundary. Quoting groups arguments; expansions and operators remain literal unless the user explicitly invokes a shell such as sh -c. Invalid quoting must leave the form open with an error and must not queue sandbox creation.

Scrollable views follow k9s conventions

Any scrollable content (logs, future long lists) should follow the k9s autoscroll pattern:

  • Autoscroll on by default — when entering a scrollable view, it auto-follows new content
  • Scrolling up pauses — any upward scroll (keyboard or mouse) disables autoscroll
  • f or G re-enables — jump to bottom and resume following
  • Visual indicator — show ● FOLLOWING (green) or ○ PAUSED (yellow) in the panel footer
  • Mouse scroll supported — ScrollUp/ScrollDown events move by 3 lines and respect autoscroll state
  • Scroll position shown — [current/total] in the panel footer

State is tracked via log_autoscroll: bool on App. The scroll_logs(delta) method handles both keyboard and mouse input uniformly.

Long content: truncate + detail popup

When content can exceed the viewport width (log lines, field lists, etc.):

  • Truncate in the list view — hard-cut at the viewport's inner width and append …. This keeps density high and avoids wrapping that breaks the 1-line-per-entry model.
  • Enter opens a detail popup — a centered overlay showing the full untruncated content with word-wrap. Esc or Enter closes it. Track the open state via Option<usize> index.
  • Drop noise in the list view — omit empty fields, remove developer-internal info (like module paths / tracing targets) that the user doesn't need at a glance.
  • Smart field ordering — for known message types (e.g. CONNECT, L7_REQUEST), put the most important fields first and trail with process ancestry / noise. Unknown types sort alphabetically.
  • Show everything in the popup — the detail popup is where target, all fields (including empty ones if useful), and the full message are visible.

This pattern should be reused for any future view with potentially long entries.

Vim-style navigation
KeyAction
j / DownMove selection down
k / UpMove selection up
gJump to top (logs), disables autoscroll
GJump to bottom (logs), re-enables autoscroll
fFollow / re-enable autoscroll (logs)
Tab / BackTabSwitch between panels on Dashboard
EnterSelect / drill into item; open detail popup in logs
EscGo back one level
qQuit (from any screen)
Ctrl+CForce quit
Keyboard-first, mouse-augmented

All actions are accessible via keyboard shortcuts displayed in the nav bar. The nav bar is context-sensitive — it shows different hints depending on the current screen and focus state. Mouse scrolling is supported as a convenience but never required — every action must have a keyboard equivalent.

Show full SKILL.md (1,363 more words)Show less
Command mode

: enters command mode (like vim). The command bar renders at the bottom with a green : prompt and a block cursor. Currently supports:

  • :q / :quit — exit the application

Esc returns to normal mode. Enter executes the command.

Screen-specific key hints

Dashboard (Gateways focus): [Tab] Switch Panel [Enter] Select [j/k] Navigate │ [:] Command [q] Quit

Dashboard (Providers focus): [Tab] Switch Panel [h/l] Switch Tab [j/k] Navigate [Enter] Detail [c] Create [u] Update [d] Delete [w] Workspace │ [:] Command [q] Quit

Dashboard (Global Settings focus): [Tab] Switch Panel [h/l] Switch Tab [j/k] Navigate [Enter] Edit [d] Delete │ [:] Command [q] Quit

Dashboard (Sandboxes focus): [Tab] Switch Panel [j/k] Navigate [Enter] Select [c] Create Sandbox [w] Workspace │ [:] Command [q] Quit

Sandbox (Policy focus): [h] Switch Tab [j/k] Scroll [g/G] Top/Bottom [s] Shell [l] Logs [r] Rules [d] Delete │ [Esc] Back [q] Quit

Sandbox (Settings focus): [h/l] Switch Tab [j/k] Navigate [Enter] Edit [d] Delete │ [Esc] Back [q] Quit

Sandbox (Logs focus): [j/k] Navigate [Enter] Detail [g/G] Top/Bottom [f] Follow [s] Source: <filter> [y] Copy [Y] Copy All [v] Select [r] Rules │ [Esc] Policy [q] Quit

Sandbox (Draft focus): [j/k] Navigate [Enter] Detail [a] Approve [x] Reject [A] Approve All [p] Policy [l] Logs │ [Esc] Back [q] Quit

7. Architecture & Key Files

FilePurpose
crates/openshell-tui/Cargo.tomlCrate manifest — dependencies on openshell-core, openshell-bootstrap, ratatui, crossterm, tonic, tokio
crates/openshell-tui/src/lib.rsEntry point. Event loop, background collection refresh (spawn_list_refresh), gRPC calls (refresh_global_settings, spawn_log_stream, handle_sandbox_delete), gateway switching, mTLS channel building, provider CRUD spawners, settings CRUD spawners, draft approval spawners
crates/openshell-tui/src/app.rsApp state struct, Screen/Focus/InputMode/LogSourceFilter/MiddlePaneTab/SandboxPolicyTab enums, LogLine/GatewayEntry/GlobalSettingEntry/SandboxSettingEntry/ProviderListEntry/ProviderDetailView structs, create sandbox/provider form state, all key handling logic
crates/openshell-tui/src/event.rsEvent enum (Key, Mouse, Tick, Redraw, Resize, LogLines, ListRefreshCompleted, CreateResult, ProviderCreateResult, ProviderDetailFetched, ProviderUpdateResult, ProviderDeleteResult, DraftActionResult, GlobalSettingsFetched, GlobalSettingSetResult, GlobalSettingDeleteResult, SandboxSettingSetResult, SandboxSettingDeleteResult, ForwardWarnings), EventHandler with mpsc channels and crossterm polling
crates/openshell-tui/src/theme.rscolors module (NVIDIA_GREEN, EVERGLADE, BG, FG) and styles module (all Style constants)
crates/openshell-tui/src/clipboard.rsClipboard copy support for log lines
crates/openshell-tui/src/ui/mod.rsTop-level draw() dispatcher, draw_title_bar (with workspace display), draw_nav_bar, draw_command_bar, screen routing, shared setting-edit overlay, modal helpers
crates/openshell-tui/src/ui/dashboard.rsDashboard screen — 3-pane vertical layout: gateway list (25%) + provider/settings middle pane (25%) + sandbox table (50%)
crates/openshell-tui/src/ui/providers.rsProvider list table with profile-aware columns: Name, Category, Type, Credentials, Workspace
crates/openshell-tui/src/ui/global_settings.rsGlobal settings table: Key, Type, Value. Includes edit overlay, confirm-set, and confirm-delete popups
crates/openshell-tui/src/ui/sandboxes.rsReusable sandbox table widget with columns: Name, Status, Created, Age, Image, Workspace, Notes
crates/openshell-tui/src/ui/sandbox_detail.rsSandbox metadata view — name, status, image, created, age, restart policy/status, providers, policy version
crates/openshell-tui/src/ui/sandbox_policy.rsPolicy viewer — rendered policy lines with scroll support, tab title
crates/openshell-tui/src/ui/sandbox_settings.rsSandbox settings table: Key, Type, Value, Scope. Includes edit overlay and confirm popups
crates/openshell-tui/src/ui/sandbox_logs.rsStructured log viewer — timestamp, source, level, target, message, key=value fields, scroll position, source filter, visual selection mode, clipboard copy
crates/openshell-tui/src/ui/sandbox_draft.rsDraft policy recommendations — chunk list, detail popup, approve/reject/approve-all flows
crates/openshell-tui/src/ui/create_sandbox.rsCreate sandbox modal form with name, image, command, providers, ports
crates/openshell-tui/src/ui/create_provider.rsCreate provider modal, provider detail popup, update provider form
crates/openshell-tui/src/ui/splash.rsSplash/boot screen
Module dependency flow
lib.rs (event loop, gRPC, async tasks, capability fetch)
  ├── app.rs (state + key handling + tab/workspace logic)
  ├── event.rs (Event enum + EventHandler)
  ├── clipboard.rs (copy support)
  ├── theme.rs (colors + styles)
  └── ui/
        ├── mod.rs (draw dispatcher, chrome, shared overlays)
        ├── splash.rs (boot screen)
        ├── dashboard.rs (3-pane layout: gateways + middle + sandboxes)
        ├── providers.rs (provider list with profile awareness)
        ├── global_settings.rs (settings table + edit/confirm overlays)
        ├── sandboxes.rs (sandbox table widget)
        ├── sandbox_detail.rs (metadata view)
        ├── sandbox_policy.rs (policy viewer)
        ├── sandbox_settings.rs (sandbox settings table + overlays)
        ├── sandbox_logs.rs (log viewer + visual selection)
        ├── sandbox_draft.rs (draft recommendations)
        ├── create_sandbox.rs (create sandbox modal)
        └── create_provider.rs (create/detail/update provider modals)

8. Technical Notes

Dependency constraints
  • openshell-tui cannot depend on openshell-cli — this would create a circular dependency. TLS channel building for gateway switching is done directly in lib.rs using tonic::transport primitives (Certificate, Identity, ClientTlsConfig, Endpoint).
  • Gateway authentication supports both mTLS and OIDC. connect_to_gateway() reads gateway metadata to determine the auth mode, then builds an EdgeAuthInterceptor (bearer token for OIDC, noop for mTLS).
  • mTLS certs are read from ~/.config/openshell/gateways/<name>/mtls/ (ca.crt, tls.crt, tls.key).
  • OIDC tokens are loaded via openshell_bootstrap::oidc_token::load_oidc_token() and checked for expiry.
Proto generated code

Proto types come from openshell-core which generates them from OUT_DIR via include!. They are not checked into the repo. Import paths look like:

rust
use openshell_core::proto::openshell_client::OpenShellClient;
use openshell_core::proto::{
    all_workspaces_selector, workspace_selector, GetSandboxLogsRequest,
    ListSandboxesRequest, ...
};
Proto field gotchas
  • DeleteSandboxRequest uses name for the primary sandbox and an explicit workspace selector:
    rust
    let req = openshell_core::proto::DeleteSandboxRequest {
        name: sandbox_name,
        workspace_scope: Some(workspace_selector(workspace)),
        allow_missing: true,
        ..Default::default()
    };
  • Delete responses carry DeletionOutcome: distinguish Accepted (cleanup pending), Completed, and AlreadyAbsent. Treat unspecified or unknown outcomes as unconfirmed, not completed.
  • WatchSandboxRequest has extra fields beyond what you might need — always use ..Default::default():
    rust
    let req = openshell_core::proto::WatchSandboxRequest {
        sandbox: sandbox_name,
        follow_status: false,
        follow_logs: true,
        follow_events: false,
        log_tail_lines: 0,
        workspace_scope: Some(workspace_selector(workspace)),
        ..Default::default()
    };
  • SandboxLogLine proto fields: sandbox_id, event_time (Option<prost_types::Timestamp>), level, target, message, source, fields (HashMap<String, String>).
  • Workspace-scoped requests use workspace_scope: Option<WorkspaceSelector>. Select one workspace with Some(workspace_selector(name)). Collection list requests that explicitly support cross-workspace access also accept Some(all_workspaces_selector()); do not use that marker on other requests.
  • GetSandboxLogsRequest fields: sandbox, lines (u32), since_time (Option<prost_types::Timestamp>), sources (Vec<String>), min_level (String), workspace_scope.
  • ListSandboxesRequest fields: page_size (i32), page_token (String), label_selector (String), workspace_scope.
  • ListProvidersRequest fields: page_size (i32), page_token (String), workspace_scope.
  • ListWorkspacesRequest fields: page_size (i32), page_token (String), label_selector (String).
  • Paginated list responses return next_page_token. Continue with the same request parameters and that token until it is empty; changing filters or scope invalidates the token.
  • UpdateConfigRequest fields include sandbox (String, canonical sandbox name), setting_key, setting_value, delete_setting (bool), global (bool), and workspace_scope. Sandbox-scoped updates require canonical sandbox and a named selector; gateway-global updates leave sandbox empty and workspace_scope as None.
  • Most workspace-scoped requests require an explicit named selector, including the default workspace. An omitted selector is not an implicit default.
gRPC timeouts

All gRPC calls use a 5-second timeout:

rust
tokio::time::timeout(Duration::from_secs(5), client.health(req)).await

The connect timeout for gateway switching is 10 seconds with HTTP/2 keepalive at 10-second intervals.

Log streaming lifecycle
  1. User presses [l] on sandbox detail → pending_log_fetch = true
  2. Event loop sees the flag → calls spawn_log_stream()
  3. Previous stream handle is aborted via cancel_log_stream()
  4. New tokio::spawn task: fetches initial 500 lines, then streams via WatchSandbox
  5. Lines arrive as Event::LogLines and are appended to app.sandbox_log_lines
  6. Auto-scroll kicks in if the user is near the bottom (within 5 lines)
  7. Stream is cancelled when user presses Esc or navigates away (handle is .abort()ed)
Gateway switching lifecycle
  1. User selects a different gateway and presses Enter → pending_gateway_switch = Some(name)
  2. Event loop calls handle_gateway_switch()
  3. New channel is built via connect_to_gateway() (mTLS or OIDC depending on gateway metadata)
  4. On success:
    • app.client is replaced with a new intercepted client
    • reset_sandbox_state() clears all sandbox/log/draft/policy data
    • health and global settings are refreshed, then spawn_list_refresh() starts the cancellable workspace/provider/sandbox refresh task
  5. On failure: status_text shows the error
Initial startup lifecycle

On launch, before the event loop starts:

  1. refresh_gateway_list() — discover gateways from disk
  2. Refresh health and global settings, then start spawn_list_refresh() for workspaces, providers, and sandboxes
Workspace switching lifecycle
  1. User presses [w] on the providers or sandboxes panel → cycle_workspace() advances through discovered workspace names, then "all"
  2. pending_workspace_refresh = true is set, cursor indices are reset
  3. Event loop cancels any in-flight collection refresh and starts spawn_list_refresh() with the new workspace scope
Settings CRUD lifecycle (global and sandbox)
  1. User presses [Enter] on a setting → edit overlay opens (bool types toggle inline and jump to confirmation)
  2. Text input with validation (int, bool, string with allowed-values check)
  3. [Enter] opens a confirmation popup → [y] fires the pending flag
  4. Event loop spawns spawn_set_global_setting() or spawn_set_sandbox_setting() → UpdateConfig RPC
  5. On success: re-fetches settings to reflect the change
  6. [d] on a setting with a value → confirmation popup → spawn_delete_*_setting() → UpdateConfig with delete_setting: true

For sandbox settings, globally-managed entries (scope = global) are blocked from editing or deletion at the sandbox level.

9. Development Workflow

Build and run
bash
# Build the crate
cargo build -p openshell-tui

# Run the TUI against the active gateway
mise run term

# Run with cargo-watch for hot-reload during development
mise run term:dev

# Format
cargo fmt -p openshell-tui

# Lint
cargo clippy -p openshell-tui
Verification

Follow Choose Verification for the Change in CONTRIBUTING.md. Select format, lint, and tests for the affected TUI behavior and its dependencies. Use mise run pre-commit when its broader scope is warranted.

Gateway changes

If you change sandbox or server code that affects the backend, restart or redeploy the gateway for the compute platform you are using.

For Docker-backed local development:

bash
mise run gateway:docker

For Kubernetes Helm deployments:

bash
helm upgrade --install openshell deploy/helm/openshell --namespace openshell

For Kubernetes, pick up new sandbox images after changing sandbox code by deleting the pod manually so it gets recreated:

bash
kubectl delete pod <pod-name> -n <namespace>
Adding a new gRPC call
  1. Check the proto definitions in openshell-core for available RPCs and message types.
  2. Add the call in lib.rs following the existing pattern (timeout wrapper, error handling, state update).
  3. If the call is triggered by a key press, add a pending_* flag to App and handle it in the event loop.
  4. If the call returns streaming data, spawn it as a background task and send results via Event variants.
Adding a new Event variant
  1. Add the variant to Event in event.rs.
  2. Handle it in the match events.next().await block in lib.rs.
  3. Update App state as needed from the event data.

© NVIDIA, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .agents/skills/tui-development of NVIDIA/OpenShell.

Open the folder on GitHubat commit 834b79a

Compare with similar skills

Tui Development next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Tui Development compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Tui Development this skillNVIDIA/OpenShell15k—~8.5kAutomated safety check: PassApache-2.0
Impeccablebestofjs/bestofjs3.1k27 repos~2.6kAutomated safety check: PassMIT
Figma Design System Builderwarpdotdev/warp65k2 repos~4.4kAutomated safety check: PassAGPL-3.0
Tailwindcss Developmentanonaddy/anonaddy4.9k10 repos~865Automated safety check: PassMIT
UI StylingOhh-889/skyroc79513 repos~2.5kAutomated safety check: PassMIT
MCP Developmentcoollabsio/coolify63k1 repos~949Automated safety check: PassMIT

Similar skills

  • Impeccable

    bestofjs/bestofjs

    A skill your agent uses when the user wants to design, redesign, shape, critique, audit, polish, clarify, distill, harden, optimize, adapt, animate, colorize, extract, or otherwise improve a…

    3.1k GitHub starsUsed in 27 repos~2.6k tokens
    Frontend & DesignAuto-check passed
  • Builds or updates a design system in Figma from a codebase in ordered phases: discovery, variables and tokens, components, theming and documentation, with checkpoints.

    65k GitHub starsUsed in 2 repos~4.4k tokens
    Frontend & DesignAuto-check passed
  • Tailwindcss Development

    anonaddy/anonaddy

    Always invoke when the user's message includes 'tailwind' in any form.

    4.9k GitHub starsUsed in 10 repos~865 tokens
    Frontend & DesignAuto-check passed
  • UI Styling

    Ohh-889/skyroc

    Create beautiful, accessible user interfaces with shadcn/ui components (built on Radix UI + Tailwind), Tailwind CSS utility-first styling, and canvas-based visual designs.

    795 GitHub starsUsed in 13 repos~2.5k tokens
    Frontend & DesignAuto-check passed
  • MCP Development

    coollabsio/coolify

    A skill your agent uses for Laravel MCP development. An agent skill from coollabsio/coolify.

    63k GitHub starsUsed in 1 repo~949 tokens
    Frontend & DesignAuto-check passed
  • UI UX Pro Max

    saoudi-h/solar-icons

    UI/UX design intelligence for web and mobile. An agent skill from saoudi-h/solar-icons.

    182 GitHub starsUsed in 18 repos~11k tokens
    Frontend & DesignAuto-check: notes

More from NVIDIA/OpenShell

All 23 skills in this repo
  • Official

    Maintain and validate OpenShell's build-only Windows MSVC lane for x64 and ARM64.

    15k GitHub stars~4.9k tokensUpdated today
    Auto-check passed
  • Create GitHub Issue

    NVIDIA/OpenShell

    Official

    Create GitHub issues using the gh CLI. An agent skill from NVIDIA/OpenShell.

    15k GitHub stars~1.7k tokensUpdated today
    Auto-check passed
  • Create GitHub PR

    NVIDIA/OpenShell

    Official

    Create GitHub pull requests using the gh CLI. An agent skill from NVIDIA/OpenShell.

    15k GitHub stars~1.9k tokensUpdated today
    Auto-check passed
  • Debug Inference

    NVIDIA/OpenShell

    Official

    Debug inference clients that use an attached provider and its native endpoint, including hosted APIs and host-local Ollama, vLLM, SGLang, TRT-LLM, LM Studio, or NIM.

    15k GitHub stars~1.9k tokensUpdated today
    Auto-check passed
  • Debug Openshell Cluster

    NVIDIA/OpenShell

    Official

    Debug why an OpenShell gateway deployment is unhealthy, unreachable, or unable to create sandboxes.

    15k GitHub stars~19k tokensUpdated today
    Auto-check: notes
  • Gator Gate

    NVIDIA/OpenShell

    Official

    Validate and monitor OpenShell GitHub issues and PRs using the gator: state machine.

    15k GitHub stars~19k tokensUpdated today
    Auto-check passed

Questions about Tui Development

What does Tui Development do?

Guide for developing the OpenShell TUI — a ratatui-based terminal UI for the OpenShell platform. Tui Development is an agent skill from NVIDIA/OpenShell, published by the product's own GitHub organization. Guide for developing the OpenShell TUI — a ratatui-based terminal UI for the OpenShell platform.

When should I use Tui Development?

Tui Development fits situations like: keywords - term; tui development.

How do I install Tui Development in Claude Code?

Run `npx skills add NVIDIA/OpenShell --skill tui-development -a claude-code`. Or copy the skill folder (.agents/skills/tui-development in NVIDIA/OpenShell) into .claude/skills/tui-development in your project. Claude Code loads it when a task matches its description.

How do I install Tui Development in Codex?

Run `npx skills add NVIDIA/OpenShell --skill tui-development -a codex`. Or copy the skill folder (.agents/skills/tui-development in NVIDIA/OpenShell) into .agents/skills/tui-development in your project. Codex loads it when a task matches its description.

Can I use Tui Development in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add NVIDIA/OpenShell --skill tui-development -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/tui-development, .gemini/skills/tui-development, .github/skills/tui-development and .opencode/skills/tui-development in your project.

What does Tui Development need to run?

Going by SKILL.md and its folder, Tui Development needs the command-line tools its instructions call (mise, cargo, helm, kubectl and sh).

Does Tui Development access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Tui Development safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Tui Development use?

Tui Development is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Tui Development use?

About 8.5k tokens (SKILL.md is roughly 34k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Tui Development?

Skills that share tags, products or a category with Tui Development: Impeccable (bestofjs/bestofjs, 3.1k stars), Figma Design System Builder (warpdotdev/warp, 65k stars), Tailwindcss Development (anonaddy/anonaddy, 4.9k stars) and UI Styling (Ohh-889/skyroc, 795 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Tui Development?

NVIDIA (a GitHub organization, an official publisher) maintains it in NVIDIA/OpenShell, which has 15,188 GitHub stars. The repository holds 23 skills in this directory. The repository was last updated on October 7, 2026.

Source: NVIDIA/OpenShell on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.