LogoRobo.js

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:

  • stdout and stdin are TTY (not piped or redirected)
  • Terminal is at least 10 rows by 40 columns
  • Not running in CI (no CI environment variable)
  • ROBO_NON_INTERACTIVE environment variable is not set

Built-in Commands

These commands are always available out of the box:

CommandDescription
/helpList all available commands
/clearClear the log output
/restartFull rebuild and restart (dev mode only)
/stateInspect and modify runtime state
/flashcoreInspect and modify Flashcore storage
/envShow loaded environment variables
/statusShow system status and info
/pluginsList 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 plugin

Features

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:

ping.ts/ping
greet.ts/greet
index.ts/db
status.ts/db status
clear.ts/db clear

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:

PropertyTypeDescription
argsstring[]Positional arguments
optionsobjectParsed option values
configConfigProject configuration
runtimeRuntimeProviderAccess to state and Flashcore
write(text: string) => voidWrite text to stdout
drawerobject | undefinedDrawer API (only available in interactive mode)

The runtime property provides methods for interacting with state and Flashcore:

MethodDescription
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.

MethodTypeDescription
show(lines: string[]) => voidDisplay content lines below the input prompt
hide() => voidCollapse the drawer and restore normal layout
isOpen() => booleanWhether 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.

Next Steps

On this page