Interactive Terminal
Built-in and custom slash commands for the interactive terminal.
When running robo dev or robo start in a TTY environment, Robo.js launches an interactive terminal. Type / followed by a command name to inspect state, manage Flashcore, view environment variables, and more.
For an overview of how terminal commands fit into the AI-native development experience, see AI Native.
The terminal activates automatically when all of these conditions are met:
stdoutandstdinare TTY (not piped or redirected)- Terminal is at least 10 rows by 40 columns
- Not running in CI (no
CIenvironment variable) ROBO_NON_INTERACTIVEenvironment variable is not set
Built-in Commands
These commands are always available out of the box:
| Command | Description |
|---|---|
/help | List all available commands |
/clear | Clear the log output |
/restart | Full rebuild and restart (dev mode only) |
/state | Inspect and modify runtime state |
/flashcore | Inspect and modify Flashcore storage |
/env | Show loaded environment variables |
/status | Show system status and info |
/plugins | List installed plugins |
/state
Inspect and modify your Robo's runtime state.
/state List all state keys and values
/state get <key> Show a specific value
/state set <key> <value> Set a value (JSON or plain string)
/state delete <key> Remove a key
/state forks List registered state forks/flashcore
Interact with Flashcore key-value storage directly.
/flashcore get <key> Get a stored value
/flashcore set <key> <value> Set a value (JSON or plain string)
/flashcore delete <key> Delete a key
/flashcore has <key> Check if a key exists/env
View loaded environment variables. Values with sensitive names (containing TOKEN, SECRET, KEY, etc.) are automatically masked.
/env List all .env variables (masked)
/env get <key> Show a specific variable (masked)
/env get <key> --raw Show the unmasked value/status
Display system information including project name, Robo.js version, current mode, Node.js version, PID, uptime, plugin count, and Flashcore adapter type.
/plugins
List installed plugins and their configuration status.
/plugins List all installed plugins
/plugins info <name> Show details for a specific pluginFeatures
The interactive terminal includes several quality-of-life features:
- Command history — Use the up/down arrow keys to cycle through previously entered commands.
- Autocomplete hints — As you type a
/command, matching command names and descriptions appear as suggestions. - Status indicator — The input line shows the current Robo status (
building,restarting,starting X/Y,ready,error,stopping,stopped). During startup, the[starting X/Y]badge tracks plugin initialization progress automatically. - Hint line — A contextual line below the input prompt cycles through layers: startup progress, plugin status items (reported via
Robo.status), and tips. - Scroll regions — Log output and the input line occupy separate regions, so your typing is never interrupted by log messages.
Custom Terminal Commands
You can create your own / commands by placing files in src/robo/terminal/commands/. The file path determines the command name:
Basic Example
// src/robo/terminal/commands/ping.ts
import { createTerminalCommandConfig } from 'robo.js'
import type { TerminalContext } from 'robo.js'
export const config = createTerminalCommandConfig({
description: 'Check if Robo is responding',
options: [
{ alias: '-c', name: '--count', description: 'Number of pings', type: 'number', default: 1 }
]
} as const)
export default async function (ctx: TerminalContext<typeof config>) {
for (let i = 0; i < ctx.options.count; i++) {
ctx.write(`pong! (${i + 1}/${ctx.options.count})\n`)
}
}// src/robo/terminal/commands/ping.mjs
import { createTerminalCommandConfig } from 'robo.js'
export const config = createTerminalCommandConfig({
description: 'Check if Robo is responding',
options: [
{ alias: '-c', name: '--count', description: 'Number of pings', type: 'number', default: 1 }
]
})
export default async function (ctx) {
for (let i = 0; i < ctx.options.count; i++) {
ctx.write(`pong! (${i + 1}/${ctx.options.count})\n`)
}
}Use as const in TypeScript for full type inference on ctx.options.
Terminal Context
Handlers receive a TerminalContext object with:
| Property | Type | Description |
|---|---|---|
args | string[] | Positional arguments |
options | object | Parsed option values |
config | Config | Project configuration |
runtime | RuntimeProvider | Access to state and Flashcore |
write | (text: string) => void | Write text to stdout |
drawer | object | undefined | Drawer API (only available in interactive mode) |
The runtime property provides methods for interacting with state and Flashcore:
| Method | Description |
|---|---|
getState() | Get the full state object |
getStateValue(key) | Get a specific state value |
setStateValue(key, value) | Set a state value |
deleteStateKey(key) | Delete a state key |
flashcoreGet(key) | Get a Flashcore value |
flashcoreSet(key, value) | Set a Flashcore value |
flashcoreDelete(key) | Delete a Flashcore key |
flashcoreHas(key) | Check if a Flashcore key exists |
isRunning() | Check if the Robo process is running |
Subcommands
Nested folders create subcommand hierarchies. For example, src/robo/terminal/commands/db/status.ts becomes /db status:
// src/robo/terminal/commands/db/status.ts
import { createTerminalCommandConfig } from 'robo.js'
import type { TerminalContext } from 'robo.js'
export const config = createTerminalCommandConfig({
description: 'Show database status'
})
export default async function (ctx: TerminalContext<typeof config>) {
const isRunning = ctx.runtime?.isRunning() ?? false
ctx.write(`Database: ${isRunning ? 'connected' : 'disconnected'}\n`)
}// src/robo/terminal/commands/db/status.mjs
import { createTerminalCommandConfig } from 'robo.js'
export const config = createTerminalCommandConfig({
description: 'Show database status'
})
export default async function (ctx) {
const isRunning = ctx.runtime?.isRunning() ?? false
ctx.write(`Database: ${isRunning ? 'connected' : 'disconnected'}\n`)
}Plugin-Provided Commands
Plugins can provide terminal commands the same way — by placing files in src/robo/terminal/commands/ within their package. When a plugin is installed, its terminal commands become available automatically.
Project-level commands receive a priority boost of +100 over plugin commands, so your local commands always take precedence. If you need finer control, set the priority field in your config:
export const config = createTerminalCommandConfig({
description: 'My high-priority command',
priority: 200
})export const config = createTerminalCommandConfig({
description: 'My high-priority command',
priority: 200
})Custom terminal commands cannot override built-in commands like /help, /clear, /restart, /state, /flashcore, /env, /status, or /plugins.
Drawer API
Terminal commands can display persistent content below the input prompt using the drawer. This is useful for showing multi-line output like system status, plugin info, or formatted tables without cluttering the log stream.
The drawer is accessed via ctx.drawer, which is only available when the interactive terminal is active. Always check for its existence before using it.
| Method | Type | Description |
|---|---|---|
show | (lines: string[]) => void | Display content lines below the input prompt |
hide | () => void | Collapse the drawer and restore normal layout |
isOpen | () => boolean | Whether the drawer is currently visible |
Any keypress automatically dismisses the drawer.
// src/robo/terminal/commands/info.ts
import { createTerminalCommandConfig } from 'robo.js'
import type { TerminalContext } from 'robo.js'
export const config = createTerminalCommandConfig({
description: 'Show plugin status'
})
export default async function (ctx: TerminalContext<typeof config>) {
const lines = [
'╭───────────────────────╮',
'│ Plugin Status │',
'│ Database: connected │',
'│ Cache: warm │',
'│ Uptime: 2h 15m │',
'╰───────────────────────╯'
]
if (ctx.drawer) {
ctx.drawer.show(lines)
} else {
ctx.write(lines.join('\n') + '\n')
}
}// src/robo/terminal/commands/info.mjs
import { createTerminalCommandConfig } from 'robo.js'
export const config = createTerminalCommandConfig({
description: 'Show plugin status'
})
export default async function (ctx) {
const lines = [
'╭───────────────────────╮',
'│ Plugin Status │',
'│ Database: connected │',
'│ Cache: warm │',
'│ Uptime: 2h 15m │',
'╰───────────────────────╯'
]
if (ctx.drawer) {
ctx.drawer.show(lines)
} else {
ctx.write(lines.join('\n') + '\n')
}
}When ctx.drawer is undefined (non-interactive environments like CI or piped output), fall back to ctx.write() so your command still works everywhere.
