Skip to main content

CLI Plugin

Overview

The @tokenring-ai/cli package provides a comprehensive command-line interface for interacting with TokenRing AI agents. This terminal-based interface enables users to manage agents, execute commands, and handle human interface requests with a rich, responsive UI. The package supports two UI frameworks: OpenTUI (default) and Ink, allowing you to choose the rendering engine that best fits your needs.

Key Features

  • Dual UI Framework Support: Choose between OpenTUI or Ink for rendering
  • Agent Management: Spawn, select, and interact with multiple agent types
  • Interactive Chat: Real-time streaming of agent output with syntax highlighting
  • Command History: Navigate previous inputs with arrow keys
  • Auto-completion: Command and input auto-completion support
  • Human Interface Handling: Interactive forms for agent questions and requests
  • Responsive Layout: Adapts to different terminal sizes (narrow, compact, wide)
  • Customizable Theme: Full theming support for colors and styling
  • Background Loading Screen: Optional loading screen while agents initialize
  • Graceful Shutdown: Proper signal handling and cleanup
  • Markdown Styling: Applied markdown formatting to terminal output
  • File Selection: Interactive file system browser for file selection questions
  • Tree Selection: Hierarchical tree-based selection for complex choices
  • Multi-field Forms: Support for multi-section forms with various field types
  • Bracketed Paste: Support for bracketed paste mode for efficient text input
  • Workspace File Search: File path completion using @ syntax

Core Components

AgentCLI Service

The main service that manages CLI operations, including user input, agent selection, and interaction handling.

Interface:

class AgentCLI implements TokenRingService {
readonly name = "AgentCLI";
description = "Command-line interface for interacting with agents";

constructor(
readonly app: TokenRingApp,
readonly config: z.infer<typeof CLIConfigSchema>
);

async run(signal: AbortSignal): Promise<void>;
}

Constructor Parameters:

  • app: The TokenRingApp instance to manage agents
  • config: CLI configuration object matching CLIConfigSchema

Methods:

MethodDescriptionParametersReturns
runStarts the CLI interface and manages agent interactionssignal: AbortSignalPromise<void>

Behavior:

  • Displays loading screen (if no auto-start agent configured)
  • Presents agent selection screen
  • Spawns selected agent and enters interaction loop
  • Handles SIGINT for graceful shutdown
  • Restarts agent selection after agent completion (unless startAgent.shutdownWhenDone is true)
  • Supports automatic agent spawning with optional initial prompt

AgentLoop Class

Handles the interactive loop for individual agents, managing input collection, event rendering, and human request handling.

Interface:

class AgentLoop {
constructor(
readonly agent: Agent,
readonly options: AgentLoopOptions
);

async run(externalSignal: AbortSignal): Promise<void>;
}

AgentLoopOptions Interface:

interface AgentLoopOptions {
availableCommands: CommandDefinition[];
config: z.infer<typeof CLIConfigSchema>;
}

Properties:

  • agent: The Agent instance to interact with
  • options: Configuration including available commands and CLI config

Methods:

MethodDescriptionParametersReturns
runStarts the agent interaction loopexternalSignal: AbortSignalPromise<void>

Event Handling: The AgentLoop processes the following agent events:

  • agent.created: Display agent creation message
  • agent.stopped: Shutdown the interaction loop
  • output.chat: Stream chat output with formatting
  • output.reasoning: Stream reasoning output with formatting
  • output.info/warning/error: Display system messages
  • output.artifact: Display artifact information
  • input.received: Display user input
  • question.request: Display agent question to user
  • question.response: Display response to agent question

State Management:

  • Tracks event cursor for incremental updates
  • Subscribes to AgentEventState for real-time updates
  • Handles abort signals for graceful cancellation

RawChatUI Class

The main chat UI component that handles terminal rendering, input editing, and interaction management. This is a raw terminal-based UI that works directly with ANSI escape codes.

Interface:

class RawChatUI {
constructor(options: RawChatUIOptions);

start(): void;
stop(): void;
suspend(): void;
resume(): void;
renderEvent(event: AgentEventEnvelope): void;
syncState(state: AgentEventState): void;
flash(text: string, tone?: FlashMessage["tone"], durationMs?: number): void;
}

RawChatUIOptions Interface:

interface RawChatUIOptions {
agent: Agent;
config: z.output<typeof CLIConfigSchema>;
commands: CommandDefinition[];
onSubmit: (message: string) => void;
onOpenAgentSelection: () => void;
onDeleteIdleAgent: () => void;
onAbortCurrentActivity: () => boolean;
}

Properties:

  • chatEditor: Multi-line input editor for chat messages
  • transcript: Array of transcript entries showing conversation history
  • followupEditors: Map of editors for follow-up interactions
  • questionSessions: Map of inline question sessions

Methods:

MethodDescriptionParameters
startAttaches terminal and starts rendering-
stopDetaches terminal and stops rendering-
suspendTemporarily detaches terminal-
resumeRe-attaches terminal and replays UI-
renderEventRenders an agent event to the transcriptevent: AgentEventEnvelope
syncStateSynchronizes UI with agent statestate: AgentEventState
flashShows a temporary flash messagetext: string, tone?: FlashMessage["tone"], durationMs?: number

Keyboard Shortcuts:

  • Ctrl+C: Exit the CLI
  • Ctrl+L: Clear and replay the screen
  • Alt+A / F1: Open agent selection
  • Alt+M / F3: Open model selector
  • Alt+T / F2: Open tools selector
  • Alt+V / F4: Toggle verbose mode
  • Alt+Q / F6: Toggle optional questions
  • Tab: Command completion
  • Escape: Cancel current activity
  • Ctrl+O: Insert newline
  • Ctrl+P / Up: Browse command history (previous)
  • Ctrl+N / Down: Browse command history (next)

Input Editor Features:

  • Multi-line text editing
  • Word navigation (Alt+B/F or Ctrl+Left/Right)
  • Line navigation (Home/End)
  • Delete operations (Ctrl+U/K/W/D)
  • Bracketed paste support

commandPrompt Function

Provides a prompt implementation using a shared Node.js readline interface with history and auto-completion support.

Interface:

interface CommandPromptOptions {
rl: readline.Interface;
message: string;
prefix?: string;
history?: string[];
autoCompletion?: string[] | ((line: string) => Promise<string[]> | string[]);
signal?: AbortSignal;
}

async function commandPrompt(options: CommandPromptOptions): Promise<string>

Parameters:

  • rl: Shared readline interface instance
  • message: Prompt message to display
  • prefix: Optional prefix text (e.g., "user")
  • history: Array of previous commands for history navigation
  • autoCompletion: Array of completion suggestions or function to generate them
  • signal: Optional abort signal for cancellation

Returns:

  • The trimmed input string if user submits
  • Throws PartialInputError if aborted with non-empty buffer

Usage:

import readline from 'node:readline';
import { commandPrompt } from '@tokenring-ai/cli';

const rl = readline.createInterface(process.stdin, process.stdout);

const answer = await commandPrompt({
rl,
message: '>',
prefix: chalk.yellowBright('user'),
history: ['help', 'status', 'config'],
autoCompletion: ['help', 'status', 'config', 'shutdown'],
});

console.log('User entered:', answer);

PartialInputError Class

Error class thrown when input is interrupted but contains non-empty buffer.

Interface:

class PartialInputError extends Error {
constructor(public buffer: string);
}

Usage:

try {
const input = await commandPrompt({ rl, message: '>', signal });
} catch (err) {
if (err instanceof PartialInputError) {
console.log('Input interrupted with buffer:', err.buffer);
}
}

SimpleSpinner Class

Custom spinner class that renders a simple animation in the terminal. Designed to work with abort signals without conflicting with Ctrl-C handling.

Interface:

class SimpleSpinner {
constructor(message?: string, hexColor?: string);

start(message?: string): void;
stop(): void;
updateMessage(message: string): void;
}

Constructor Parameters:

  • message: Initial message to display next to spinner
  • hexColor: Hex color code for spinner (default: "#ffffff")

Methods:

MethodDescriptionParameters
startStarts the spinner animationmessage?: string
stopStops the spinner and shows cursor-
updateMessageUpdates the spinner messagemessage: string

Usage:

import { SimpleSpinner } from '@tokenring-ai/cli';

const spinner = new SimpleSpinner('Loading...', '#FFEB3BFF');
spinner.start();

// Perform async operation
await someAsyncOperation();

spinner.stop();

Frames: The spinner uses 10 frames: , , , , , , , , ,

applyMarkdownStyles Utility

Utility function that applies terminal-friendly styling to markdown text. Converts markdown syntax into ANSI-colored terminal output.

Interface:

function applyMarkdownStyles(text: string): string

Parameters:

  • text: Markdown-formatted text to style

Returns:

  • Styled text with ANSI color codes

Supported Markdown Elements:

ElementMarkdownTerminal Styling
Code blocks language ```Gray horizontal line with language
Horizontal rules---, ***, ___Gray line (60% of terminal width)
Unordered lists*, -, +Yellow bullet point
Ordered lists1., 2.Yellow number
Headings# HeadingBold + Underlined
Blockquotes> quoteGray vertical bar + italic
Bold**text** or __text__Bold
Italic*text* or _text_Italic
Strikethrough~~text~~Strikethrough
Inline code`code`White background, black text
Links[text](url)Cyan underlined text + gray URL

Usage:

import applyMarkdownStyles from '@tokenring-ai/cli/utility/applyMarkdownStyles';

const markdown = `
# Agent Status

## Overview
- Agent is running
- 5 tools enabled

### Code Example
\`\`\`typescript
const result = await agent.execute();
\`\`\`

> Note: This is a blockquote

**Important**: Always check the logs
`;

const styled = applyMarkdownStyles(markdown);
console.log(styled);

Screen Components

AgentSelectionScreen

The main screen for selecting, spawning, or connecting to agents. Supports multiple action types and provides a preview of selected items.

Features:

  • Spawn Agents: Create new agent instances from available configurations
  • Connect to Agents: Connect to already running agents
  • Open Web Applications: Launch web browsers connected to SPA resources
  • Run Workflows: Execute predefined workflows

Action Types:

ActionFormatDescription
Spawnspawn:agentTypeCreate a new agent of the specified type
Connectconnect:agentIdConnect to an existing running agent
Openopen:urlOpen a web application in the system browser
Workflowworkflow:workflowKeyExecute a workflow and spawn an agent

Preview Panel:

  • Shows agent description and enabled tools when hovering over spawn options
  • Displays agent status (idle/running) for connected agents
  • Shows workflow description for workflow options
  • Provides visual feedback for web application links

Categories:

  • Web Application: SPA resources from WebHostService
  • Current Agents: Already running agents
  • Agent Categories: Organized by agent configuration category
  • Workflows: Available workflows

Usage:

import AgentSelectionScreen from '@tokenring-ai/cli/opentui/screens/AgentSelectionScreen';
import { renderScreen } from '@tokenring-ai/cli/opentui/renderScreen';

const agent = await renderScreen(AgentSelectionScreen, {
app,
config: {
chatBanner: 'TokenRing CLI',
screenBanner: 'Select an Agent',
uiFramework: 'opentui',
loadingBannerNarrow: 'Loading...',
loadingBannerWide: 'Loading TokenRing CLI...',
loadingBannerCompact: 'Loading',
},
}, signal);

LoadingScreen

Displays a loading screen with animated spinner and configurable banner while the application initializes.

Features:

  • Animated spinner with progress
  • Responsive banner selection based on terminal width
  • Random loading messages from a curated list
  • Automatic width detection for banner selection

Banner Selection:

  • Wide Banner: Used when terminal width > wide banner width
  • Narrow Banner: Used when terminal width > narrow banner width but < wide banner width
  • Compact Banner: Used when terminal is very narrow

Usage:

import LoadingScreen from '@tokenring-ai/cli/opentui/screens/LoadingScreen';
import { renderScreen } from '@tokenring-ai/cli/opentui/renderScreen';

await renderScreen(LoadingScreen, {
config: {
chatBanner: 'TokenRing CLI',
screenBanner: 'Loading...',
uiFramework: 'opentui',
loadingBannerNarrow: 'Loading...',
loadingBannerWide: 'Loading TokenRing CLI...',
loadingBannerCompact: 'Loading',
},
}, signal);

Loading Messages: The screen displays random messages from @tokenring-ai/utility/string/ridiculousMessages that progress based on loading time.

QuestionInputScreen

Handles various types of agent questions and human interface requests.

Supported Question Types:

  • text: Multi-line text input
  • treeSelect: Hierarchical selection from a tree
  • fileSelect: File and directory selection
  • form: Multi-field form with sections

Usage:

import QuestionInputScreen from '@tokenring-ai/cli/opentui/screens/QuestionInputScreen';
import { renderScreen } from '@tokenring-ai/cli/opentui/renderScreen';

const response = await renderScreen(QuestionInputScreen, {
agent,
request: {
question: { type: 'text', label: 'Enter your message' },
message: 'Please provide additional information'
},
config: {
screenBanner: 'TokenRing CLI',
uiFramework: 'opentui',
loadingBannerNarrow: 'Loading...',
loadingBannerWide: 'Loading TokenRing CLI...',
loadingBannerCompact: 'Loading',
},
}, signal);

Input Components

TreeSelect

Hierarchical selection component for choosing from nested options.

Features:

  • Expandable/collapsible tree structure
  • Single or multiple selection modes
  • Minimum and maximum selection constraints
  • Keyboard navigation (arrow keys, space, enter)
  • Visual feedback for selected items
  • Preview panel integration

Props:

interface TreeSelectProps {
question: Omit<z.output<typeof TreeSelectQuestionSchema>, "type">;
onResponse: (response: string[] | null) => void;
onHighlight?: (value: string) => void;
signal?: AbortSignal;
}

Question Schema:

{
type: 'treeSelect';
label: string;
tree: TreeLeaf[];
defaultValue?: string[];
minimumSelections?: number;
maximumSelections?: number;
allowFreeform?: boolean;
}

Keyboard Controls:

  • ↑/↓: Navigate up/down
  • : Expand selected node
  • : Collapse selected node
  • Space: Toggle selection (multiple) or expand (single)
  • Enter: Submit selection
  • Esc/q: Cancel

Usage:

import TreeSelect from '@tokenring-ai/cli/opentui/components/inputs/TreeSelect';

function MyComponent() {
const question = {
label: 'Select Agents',
tree: [
{
name: 'Development',
children: [
{ name: 'Coder Agent', value: 'spawn:coder' },
{ name: 'Reviewer Agent', value: 'spawn:reviewer' }
]
},
{
name: 'Operations',
children: [
{ name: 'Deploy Agent', value: 'spawn:deployer' }
]
}
],
minimumSelections: 1,
maximumSelections: 3,
};

return (
<TreeSelect
question={question}
onResponse={(selected) => console.log('Selected:', selected)}
onHighlight={(value) => console.log('Highlighted:', value)}
/>
);
}

TextInput

Multi-line text input component for agent questions.

Features:

  • Multi-line support with Enter
  • Ctrl+D to submit
  • Esc to cancel
  • Visual cursor indicator

Keyboard Controls:

  • Enter: Add new line
  • Ctrl+D: Submit text
  • Esc: Cancel input
  • Backspace: Delete character/line

Usage:

import TextInput from '@tokenring-ai/cli/opentui/components/inputs/TextInput';

<TextInput
question={{ label: 'Enter your message' }}
onResponse={(text) => console.log('Input:', text)}
/>

FileSelect

File and directory selection component with lazy-loaded directory tree.

Features:

  • Lazy-loaded directory tree
  • File and directory selection
  • Single or multiple selection modes
  • Visual indicators for loading state
  • Parent directory selection tracking

Props:

interface FileSelectProps {
agent: Agent;
question: Omit<z.output<typeof FileSelectQuestionSchema>, "type">;
onResponse: (response: string[] | null) => void;
signal?: AbortSignal;
}

Question Schema:

{
type: 'fileSelect';
label?: string;
allowFiles?: boolean;
allowDirectories?: boolean;
defaultValue?: string[];
minimumSelections?: number;
maximumSelections?: number;
}

Keyboard Controls:

  • ↑/↓: Navigate up/down
  • : Expand directory
  • : Collapse directory
  • Space: Toggle selection (multiple) or expand (single)
  • Enter: Submit selection
  • Esc/q: Cancel

Usage:

import FileSelect from '@tokenring-ai/cli/opentui/components/inputs/FileSelect';

<FileSelect
agent={agent}
question={{
label: 'Select Files',
allowFiles: true,
allowDirectories: false,
maximumSelections: 5,
}}
onResponse={(files) => console.log('Selected files:', files)}
/>

FormInput

Multi-field form component that sequences through multiple question types.

Features:

  • Section-based organization
  • Mixed question type support
  • Auto-advance between fields
  • Progress indicator
  • Response aggregation by section

Props:

interface FormInputProps {
agent: Agent;
question: Omit<z.output<typeof FormQuestionSchema>, "type">;
onResponse: (response: Record<string, Record<string, any>> | null) => void;
signal?: AbortSignal;
}

Question Schema:

{
type: 'form';
sections: {
name: string;
fields: {
[fieldName: string]: {
type: 'text' | 'treeSelect' | 'fileSelect';
label: string;
// Type-specific options
};
};
}[];
}

Usage:

import FormInput from '@tokenring-ai/cli/opentui/components/inputs/FormInput';

<FormInput
agent={agent}
question={{
sections: [
{
name: 'Project Info',
fields: {
name: { type: 'text', label: 'Project Name' },
type: {
type: 'treeSelect',
label: 'Project Type',
tree: [
{ name: 'Web App', value: 'web' },
{ name: 'CLI Tool', value: 'cli' }
]
}
}
},
{
name: 'Files',
fields: {
config: {
type: 'fileSelect',
label: 'Config File',
allowDirectories: false
}
}
}
]
}}
onResponse={(responses) => console.log('Form responses:', responses)}
/>

Services

AgentCLI

The CLI package implements the TokenRingService interface through the AgentCLI class.

Service Registration:

import TokenRingApp from '@tokenring-ai/app';
import AgentCLI from '@tokenring-ai/cli';

const app = new TokenRingApp();

app.addServices(new AgentCLI(app, {
chatBanner: 'TokenRing CLI',
loadingBannerNarrow: 'Loading...',
loadingBannerWide: 'Loading TokenRing CLI...',
loadingBannerCompact: 'Loading',
screenBanner: 'TokenRing CLI',
uiFramework: 'opentui',
}));

await app.start();

Agent Loop Operations:

The AgentCLI service uses an AgentLoop instance to handle individual agent interactions:

  • Event Processing: Consumes agent events and renders them to the terminal
  • Execution State Sync: Syncs with agent execution state to show appropriate UI indicators
  • Input Collection: Gathers user input via the commandPrompt function
  • Human Request Handling: Processes human interface requests from agents
  • Spinner Management: Displays loading spinners during agent activity
  • Signal Handling: Responds to abort signals and handles graceful shutdown

Event Types Handled:

Event TypeDescription
agent.createdDisplay agent creation message
agent.stoppedShutdown the interaction loop
agent.executionUpdate execution state indicators
output.artifactDisplay artifact information
output.chatStream chat output with formatting
output.reasoningStream reasoning output with formatting
output.info/warning/errorDisplay system messages
input.receivedDisplay user input
input.handledDisplay input handling status
question.requestDisplay agent question to user
question.responseDisplay response to agent question
pause/resume/abortDisplay control messages

Providers

The CLI package does not define any providers that register with a KeyedRegistry.

RPC Endpoints

The CLI package does not define any RPC endpoints.

Chat Commands

Available commands in the agent CLI interface:

/multi - Open an editor for multiline input

The /multi command opens your default text editor where you can write and edit multi-line text. This is useful for complex prompts, code examples, or detailed instructions that would be difficult to type line by line.

Usage:

/multi

Behavior:

  • Opens your system's default text editor (EDITOR environment variable)
  • If no EDITOR is set, uses vi on Unix/Linux, notepad on Windows
  • Start with a blank editor or continue from previous input
  • Save and close the editor to submit your text as input
  • If you cancel or provide empty input, nothing will be sent

Examples:

/multi                    # Open editor with blank content
/multi Write a story... # Open editor with initial text
/multi #include <stdio.h> # Start with code snippet

Configuration

The CLI plugin supports configuration options that define the user interface behavior and appearance.

Configuration Schema:

const CLIConfigSchema = z.object({
chatBanner: z.string(),
loadingBannerNarrow: z.string(),
loadingBannerWide: z.string(),
loadingBannerCompact: z.string(),
screenBanner: z.string(),
uiFramework: z.enum(['ink', 'opentui']).default('opentui'),
verbose: z.boolean().default(false),
startAgent: z.object({
type: z.string(),
prompt: z.string().optional(),
shutdownWhenDone: z.boolean().default(true),
}).optional(),
});

Configuration Options:

OptionTypeRequiredDefaultDescription
chatBannerstringYes-Banner message displayed during agent chat sessions
loadingBannerNarrowstringYes-Banner for narrow terminal windows during loading
loadingBannerWidestringYes-Banner for wide terminal windows during loading
loadingBannerCompactstringYes-Banner for compact terminal layouts during loading
screenBannerstringYes-Banner message displayed on all interactive screens
uiFramework'ink' | 'opentui'No'opentui'UI rendering framework to use
verbosebooleanNofalseEnable verbose output including reasoning and artifacts
startAgentobjectNoundefinedOptional agent to automatically spawn on startup
startAgent.typestringIf startAgent-Agent type to spawn
startAgent.promptstringIf startAgentundefinedInitial prompt to send to the agent
startAgent.shutdownWhenDonebooleanIf startAgenttrueWhether to shutdown after agent completes

Configuration Example:

const config = {
cli: {
chatBanner: 'TokenRing CLI',
loadingBannerNarrow: 'Loading...',
loadingBannerWide: 'Loading TokenRing CLI...',
loadingBannerCompact: 'Loading',
screenBanner: 'TokenRing CLI',
uiFramework: 'opentui',
verbose: true,
startAgent: {
type: 'coder',
prompt: 'Write a function to calculate Fibonacci',
shutdownWhenDone: true,
},
},
};

Theme Configuration

The CLI uses a color theme defined in theme.ts that controls the appearance of all UI elements.

Theme Properties:

const theme = {
// Agent selection
agentSelectionBanner: '#ffffff',
agentSelectionBannerBackground: '#2c2c2c',

// Question screen
questionScreenBanner: '#ffffff',
questionScreenBannerBackground: '#cf6e32',

// General panel background style
panelBackground: '#1e1e1e',
screenBackground: '#1e1e1e',

// Ask screen
askMessage: '#00BCD4FF',

// Confirmation screen
confirmYes: '#66BB6AFF',
confirmNo: '#EF5350FF',
confirmInactive: '#9E9E9EFF',
confirmTimeout: '#FFEB3BFF',

// Chat styles
chatOutputText: '#66BB6AFF',
chatReasoningText: '#FFEB3BFF',
chatPreviousInput: '#8c6ac6',
chatSystemInfoMessage: '#64B5F6FF',
chatSystemWarningMessage: '#FFEB3BFF',
chatSystemErrorMessage: '#EF5350FF',
chatDivider: '#9E9E9EFF',
chatSpinner: '#FFEB3BFF',
chatInputReceived: '#6699CCFF',
chatInputHandledSuccess: '#99CC99FF',
chatQuestionRequest: '#00BCD4FF',
chatQuestionResponse: '#00BCD4FF',
chatReset: '#AB47BCFF',
chatAbort: '#EF5350FF',

// Box styles
boxTitle: '#FFF176FF',

// Tree Selection screen
treeMessage: '#00BCD4FF',
treePartiallySelectedItem: '#FFF176FF',
treeFullySelectedItem: '#66BB6AFF',
treeNotSelectedItem: '#9E9E9EFF',
treeHighlightedItem: '#FFEB3BFF',
treeTimeout: '#FFEB3BFF',

// Loading screen
loadingScreenBackground: '#27292c',
loadingScreenBannerBackground: '#2c2e32',
loadingScreenText: '#f0f9ff',
} as const;

Theme Usage:

The theme is automatically applied to all UI components:

  • OpenTUI components: Use fg and backgroundColor props with theme values
  • Ink components: Use color and backgroundColor props with theme values
  • Terminal output: Uses chalk.hex() with theme values

Integration

Integration with Agent System

The CLI integrates with the agent system through:

  1. Agent Selection: Presents available agents from AgentManager service
  2. Event Subscription: Subscribes to AgentEventState for real-time updates
  3. Input Handling: Sends user input via agent.handleInput()
  4. Question Responses: Sends responses to agent questions via agent.sendInteractionResponse()
  5. Command Registration: Registers chat commands via AgentCommandService

Integration with WebHostService

The CLI integrates with WebHostService to display and launch web applications:

// WebHostService provides SPA resources that appear in the agent selection screen
// Selecting a web application option opens it in the system browser

Integration with WorkflowService

The CLI integrates with WorkflowService to execute workflows:

// Workflows appear in the agent selection screen under "Workflows" category
// Selecting a workflow spawns an agent running that workflow

Plugin Registration

import cliPlugin from '@tokenring-ai/cli';

app.install(cliPlugin, {
cli: {
chatBanner: 'TokenRing CLI',
loadingBannerNarrow: 'Loading...',
loadingBannerWide: 'Loading TokenRing CLI...',
loadingBannerCompact: 'Loading',
screenBanner: 'TokenRing CLI',
uiFramework: 'opentui',
},
});

Service Registration

import AgentCLI from '@tokenring-ai/cli';

app.addServices(new AgentCLI(app, {
chatBanner: 'TokenRing CLI',
loadingBannerNarrow: 'Loading...',
loadingBannerWide: 'Loading TokenRing CLI...',
loadingBannerCompact: 'Loading',
screenBanner: 'TokenRing CLI',
uiFramework: 'opentui',
}));

Command Registration

The plugin automatically registers commands with AgentCommandService:

// In plugin.ts
app.waitForService(AgentCommandService, agentCommandService =>
agentCommandService.addAgentCommands(agentCommands)
);

State Management

The CLI package manages the following state components through the agent system:

  • AgentEventCursor: Tracks current position in the event stream
  • AgentEventState: Manages agent event history and rendering state
  • AgentExecutionState: Tracks agent execution status and active operations
  • CommandHistoryState: Manages input history for command completion

State Integration:

// Access event state
const eventState = agent.getState(AgentEventState);

// Get events since last cursor position
const events = eventState.yieldEventsByCursor(cursor);

// Update cursor after processing
cursor = eventState.getEventCursorFromCurrentPosition();

Hooks

useAbortSignal

React hook that listens for abort signals and calls a callback when aborted.

Interface:

function useAbortSignal(signal: AbortSignal | undefined, onAbort: () => void): void

Usage:

import { useAbortSignal } from '@tokenring-ai/cli/hooks/useAbortSignal';

function MyComponent({ signal, onResponse }) {
useAbortSignal(signal, () => onResponse(null));

// Component logic...
}

useResponsiveLayout

React hook that provides layout information based on terminal dimensions.

Interface:

interface ResponsiveLayout {
maxVisibleItems: number;
showBreadcrumbs: boolean;
showHelp: boolean;
truncateAt: number;
isCompact: boolean;
isNarrow: boolean;
isShort: boolean;
minimalMode: boolean;
width: number;
height: number;
}

function useResponsiveLayout(): ResponsiveLayout

Layout Modes:

ModeConditionDescription
minimalModeheight < 10
isNarrowwidth < 80Narrow terminal layout
isShortheight < 20Short terminal layout
isCompactisNarrow

Usage:

import { useResponsiveLayout } from '@tokenring-ai/cli/hooks/useResponsiveLayout';

function MyComponent() {
const { isNarrow, isCompact, maxVisibleItems, width, height } = useResponsiveLayout();

if (layout.minimalMode) {
return <text>Terminal too small. Minimum: 40x10</text>;
}

return (
<box>
{isNarrow ? <CompactView /> : <FullView />}
</box>
);
}

Usage Examples

Basic CLI Usage with Plugin

import TokenRingApp from '@tokenring-ai/app';
import cliPlugin from '@tokenring-ai/cli';

const app = new TokenRingApp();

const config = {
cli: {
chatBanner: 'TokenRing CLI',
loadingBannerNarrow: 'Loading...',
loadingBannerWide: 'Loading TokenRing CLI...',
loadingBannerCompact: 'Loading',
screenBanner: 'TokenRing CLI',
uiFramework: 'opentui',
},
};

app.install(cliPlugin, config);
await app.start();

Manual CLI Usage (without plugin)

import TokenRingApp from '@tokenring-ai/app';
import AgentCLI from '@tokenring-ai/cli';

const app = new TokenRingApp();

app.addServices(new AgentCLI(app, {
chatBanner: 'TokenRing CLI',
loadingBannerNarrow: 'Loading...',
loadingBannerWide: 'Loading TokenRing CLI...',
loadingBannerCompact: 'Loading',
screenBanner: 'TokenRing CLI',
uiFramework: 'opentui',
}));

await app.start();

Starting a Specific Agent

const config = {
cli: {
chatBanner: 'TokenRing CLI',
uiFramework: 'opentui',
startAgent: {
type: 'coder',
prompt: 'Help me debug this issue...',
shutdownWhenDone: false, // Keep agent running after completion
},
},
};

Using Ink Framework

const config = {
cli: {
chatBanner: 'TokenRing CLI',
uiFramework: 'ink', // Use Ink instead of OpenTUI
loadingBannerNarrow: 'Loading...',
loadingBannerWide: 'Loading TokenRing CLI...',
loadingBannerCompact: 'Loading',
screenBanner: 'TokenRing CLI',
},
};

Enable Verbose Mode

const config = {
cli: {
chatBanner: 'TokenRing CLI',
uiFramework: 'opentui',
verbose: true, // Show reasoning and artifacts
loadingBannerNarrow: 'Loading...',
loadingBannerWide: 'Loading TokenRing CLI...',
loadingBannerCompact: 'Loading',
screenBanner: 'TokenRing CLI',
},
};

Custom Theme Usage

import { theme } from '@tokenring-ai/cli/theme';

// Access theme colors for custom components
const successColor = theme.chatOutputText;
const warningColor = theme.chatSystemWarningMessage;
const errorColor = theme.chatSystemErrorMessage;

Using Abort Signals

const abortController = new AbortController();

try {
const input = await commandPrompt({
rl,
message: 'Enter input:',
signal: abortController.signal,
});

console.log('Input:', input);
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') {
console.log('Input was aborted');
}
}

Rendering Custom Screens

import { renderScreen } from '@tokenring-ai/cli/opentui/renderScreen';
import React from 'react';

const MyScreen = ({ onResponse }) => {
return (
<box>
<text>Hello, World!</text>
</box>
);
};

const result = await renderScreen(MyScreen, {}, signal);

Best Practices

Signal Handling

Always pass abort signals to long-running operations:

async function handleUserInput(signal: AbortSignal) {
try {
const input = await commandPrompt({
rl,
message: '>',
signal,
});
// Process input
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') {
// Handle abort gracefully
}
}
}

Responsive Layout

Use the responsive layout hook to adapt UI to terminal size:

import { useResponsiveLayout } from '@tokenring-ai/cli/hooks/useResponsiveLayout';

function MyComponent() {
const { isNarrow, isCompact, maxVisibleItems, width, height } = useResponsiveLayout();

return (
<Container>
{isNarrow ? <CompactView /> : <FullView />}
</Container>
);
}

Error Handling

Handle errors gracefully in the agent loop:

try {
await agentLoop.run(signal);
} catch (error) {
process.stderr.write(formatLogMessages(['Error while running agent loop', error as Error]));
await setTimeout(1000);
}

Theme Consistency

Use theme colors consistently across components:

import { theme } from '@tokenring-ai/cli/theme';
import chalk from 'chalk';

const errorText = chalk.hex(theme.chatSystemErrorMessage)('Error occurred');

Markdown Styling

The CLI applies markdown styling to terminal output using applyMarkdownStyles:

import applyMarkdownStyles from '@tokenring-ai/cli/utility/applyMarkdownStyles';

const styledText = applyMarkdownStyles('# Heading\n- Item 1\n- Item 2');
console.log(styledText);

File Search Syntax

Use the @ syntax for file path completion in the chat input:

# Type @ followed by a search query to find files
Write code for @utils/helper.ts

The file search will:

  • Index all files in the workspace
  • Show matches as you type
  • Allow navigation with arrow keys
  • Insert the selected path with Tab or Enter

Loading Screen Usage

Use the loading screen during initialization:

import LoadingScreen from '@tokenring-ai/cli/opentui/screens/LoadingScreen';
import { renderScreen } from '@tokenring-ai/cli/opentui/renderScreen';

// Show loading screen while initializing
await renderScreen(LoadingScreen, {
config: {
loadingBannerNarrow: 'Loading...',
loadingBannerWide: 'Loading TokenRing CLI...',
loadingBannerCompact: 'Loading',
screenBanner: 'TokenRing CLI',
uiFramework: 'opentui',
chatBanner: 'TokenRing CLI',
},
}, abortSignal);

Testing and Development

Running Tests

# Run tests
bun test

# Run tests in watch mode
bun run test:watch

# Run with coverage
bun run test:coverage

Building

# Type check
bun run build

Package Structure

The package uses TypeScript with ES modules:

{
"type": "module",
"exports": {
".": "./index.ts",
"./*": "./*.ts"
},
"types": "./dist-types/index.d.ts"
}

Directory Structure:

pkg/cli/
├── commands/ # Agent commands
│ └── multi.ts # /multi command
├── components/ # UI components (both frameworks)
│ ├── ink/ # Ink-specific components
│ │ ├── components/
│ │ │ └── inputs/
│ │ ├── hooks/
│ │ └── screens/
│ └── opentui/ # OpenTUI-specific components
│ ├── components/
│ │ └── inputs/
│ ├── hooks/
│ └── screens/
├── hooks/ # Shared hooks
├── utility/ # Utility functions
│ └── applyMarkdownStyles.ts
├── AgentCLI.ts # Main CLI service
├── AgentLoop.ts # Agent interaction loop
├── AgentSelection.ts # Agent selection parsing
├── commandPrompt.ts # Command prompt implementation
├── commands.ts # Command registry
├── index.ts # Package exports
├── plugin.ts # Plugin definition
├── schema.ts # Configuration schema
├── SimpleSpinner.ts # Spinner implementation
└── theme.ts # Theme configuration

Dependencies

Runtime Dependencies

  • @tokenring-ai/app (0.2.0)
  • @tokenring-ai/ai-client (0.2.0)
  • @tokenring-ai/chat (0.2.0)
  • @tokenring-ai/agent (0.2.0)
  • @tokenring-ai/utility (0.2.0)
  • @tokenring-ai/web-host (0.2.0)
  • @tokenring-ai/workflow (0.2.0)
  • @tokenring-ai/filesystem (0.2.0)
  • zod (^4.3.6)
  • @inquirer/prompts (^8.3.0)
  • @mishieck/ink-titled-box (^0.4.2)
  • execa (^9.6.1)
  • chalk (^5.6.2)
  • open (^11.0.0)
  • @opentui/core (^0.1.87)
  • @opentui/react (^0.1.87)
  • react (^19.2.4)
  • ink (^6.8.0)
  • fullscreen-ink (^0.1.0)

Development Dependencies

  • vitest (^4.1.0)
  • typescript (^5.9.3)
  • @types/react (^19.2.14)
  • @tokenring-ai/agent: Agent framework for AI interactions
  • @tokenring-ai/app: Application framework and plugin system
  • @tokenring-ai/chat: Chat service and tool definitions
  • @tokenring-ai/utility: Utility functions for formatting and string manipulation
  • @tokenring-ai/web-host: Web server for serving resources and APIs
  • @tokenring-ai/workflow: Workflow definition and execution service
  • @tokenring-ai/filesystem: File system operations and directory tree navigation

License

MIT License - see LICENSE file for details.