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: TheTokenRingAppinstance to manage agentsconfig: CLI configuration object matchingCLIConfigSchema
Methods:
| Method | Description | Parameters | Returns |
|---|---|---|---|
run | Starts the CLI interface and manages agent interactions | signal: AbortSignal | Promise<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.shutdownWhenDoneis 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: TheAgentinstance to interact withoptions: Configuration including available commands and CLI config
Methods:
| Method | Description | Parameters | Returns |
|---|---|---|---|
run | Starts the agent interaction loop | externalSignal: AbortSignal | Promise<void> |
Event Handling:
The AgentLoop processes the following agent events:
agent.created: Display agent creation messageagent.stopped: Shutdown the interaction loopoutput.chat: Stream chat output with formattingoutput.reasoning: Stream reasoning output with formattingoutput.info/warning/error: Display system messagesoutput.artifact: Display artifact informationinput.received: Display user inputquestion.request: Display agent question to userquestion.response: Display response to agent question
State Management:
- Tracks event cursor for incremental updates
- Subscribes to
AgentEventStatefor 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 messagestranscript: Array of transcript entries showing conversation historyfollowupEditors: Map of editors for follow-up interactionsquestionSessions: Map of inline question sessions
Methods:
| Method | Description | Parameters |
|---|---|---|
start | Attaches terminal and starts rendering | - |
stop | Detaches terminal and stops rendering | - |
suspend | Temporarily detaches terminal | - |
resume | Re-attaches terminal and replays UI | - |
renderEvent | Renders an agent event to the transcript | event: AgentEventEnvelope |
syncState | Synchronizes UI with agent state | state: AgentEventState |
flash | Shows a temporary flash message | text: string, tone?: FlashMessage["tone"], durationMs?: number |
Keyboard Shortcuts:
Ctrl+C: Exit the CLICtrl+L: Clear and replay the screenAlt+A/F1: Open agent selectionAlt+M/F3: Open model selectorAlt+T/F2: Open tools selectorAlt+V/F4: Toggle verbose modeAlt+Q/F6: Toggle optional questionsTab: Command completionEscape: Cancel current activityCtrl+O: Insert newlineCtrl+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 instancemessage: Prompt message to displayprefix: Optional prefix text (e.g., "user")history: Array of previous commands for history navigationautoCompletion: Array of completion suggestions or function to generate themsignal: Optional abort signal for cancellation
Returns:
- The trimmed input string if user submits
- Throws
PartialInputErrorif 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 spinnerhexColor: Hex color code for spinner (default: "#ffffff")
Methods:
| Method | Description | Parameters |
|---|---|---|
start | Starts the spinner animation | message?: string |
stop | Stops the spinner and shows cursor | - |
updateMessage | Updates the spinner message | message: 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:
| Element | Markdown | Terminal Styling |
|---|---|---|
| Code blocks | language ``` | Gray horizontal line with language |
| Horizontal rules | ---, ***, ___ | Gray line (60% of terminal width) |
| Unordered lists | *, -, + | Yellow bullet point |
| Ordered lists | 1., 2. | Yellow number |
| Headings | # Heading | Bold + Underlined |
| Blockquotes | > quote | Gray 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:
| Action | Format | Description |
|---|---|---|
| Spawn | spawn:agentType | Create a new agent of the specified type |
| Connect | connect:agentId | Connect to an existing running agent |
| Open | open:url | Open a web application in the system browser |
| Workflow | workflow:workflowKey | Execute 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 inputtreeSelect: Hierarchical selection from a treefileSelect: File and directory selectionform: 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 nodeSpace: Toggle selection (multiple) or expand (single)Enter: Submit selectionEsc/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 lineCtrl+D: Submit textEsc: Cancel inputBackspace: 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 directorySpace: Toggle selection (multiple) or expand (single)Enter: Submit selectionEsc/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
commandPromptfunction - 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 Type | Description |
|---|---|
agent.created | Display agent creation message |
agent.stopped | Shutdown the interaction loop |
agent.execution | Update execution state indicators |
output.artifact | Display artifact information |
output.chat | Stream chat output with formatting |
output.reasoning | Stream reasoning output with formatting |
output.info/warning/error | Display system messages |
input.received | Display user input |
input.handled | Display input handling status |
question.request | Display agent question to user |
question.response | Display response to agent question |
pause/resume/abort | Display 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 (
EDITORenvironment variable) - If no
EDITORis set, usesvion Unix/Linux,notepadon 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:
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
chatBanner | string | Yes | - | Banner message displayed during agent chat sessions |
loadingBannerNarrow | string | Yes | - | Banner for narrow terminal windows during loading |
loadingBannerWide | string | Yes | - | Banner for wide terminal windows during loading |
loadingBannerCompact | string | Yes | - | Banner for compact terminal layouts during loading |
screenBanner | string | Yes | - | Banner message displayed on all interactive screens |
uiFramework | 'ink' | 'opentui' | No | 'opentui' | UI rendering framework to use |
verbose | boolean | No | false | Enable verbose output including reasoning and artifacts |
startAgent | object | No | undefined | Optional agent to automatically spawn on startup |
startAgent.type | string | If startAgent | - | Agent type to spawn |
startAgent.prompt | string | If startAgent | undefined | Initial prompt to send to the agent |
startAgent.shutdownWhenDone | boolean | If startAgent | true | Whether 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
fgandbackgroundColorprops with theme values - Ink components: Use
colorandbackgroundColorprops with theme values - Terminal output: Uses
chalk.hex()with theme values
Integration
Integration with Agent System
The CLI integrates with the agent system through:
- Agent Selection: Presents available agents from
AgentManagerservice - Event Subscription: Subscribes to
AgentEventStatefor real-time updates - Input Handling: Sends user input via
agent.handleInput() - Question Responses: Sends responses to agent questions via
agent.sendInteractionResponse() - 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:
| Mode | Condition | Description |
|---|---|---|
minimalMode | height < 10 | |
isNarrow | width < 80 | Narrow terminal layout |
isShort | height < 20 | Short terminal layout |
isCompact | isNarrow |
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)
Related Components
@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.