1111import type { AgentCoreConfig } from "./core/agentcore.js" ;
1212import type { LogLevel } from "./core/observability.js" ;
1313import { startCoreRuntime } from "./core/core-runtime.js" ;
14+ import { CoreServerTui } from "./tui/core-server-tui.js" ;
1415import { ExecutionBudget } from "./core/execution-budget.js" ;
1516import { BackpressureController } from "./core/backpressure.js" ;
1617import { CircuitBreakerRegistry } from "./core/circuit-breaker.js" ;
@@ -29,6 +30,7 @@ import type { WorkerTask, Job } from "./types/index.js";
2930interface SharedOptions {
3031 help : boolean ;
3132 version : boolean ;
33+ tui : boolean | undefined ;
3234 concurrency : number | undefined ;
3335 rps : number | undefined ;
3436 maxCost : number | undefined ;
@@ -56,14 +58,17 @@ const HELP_TEXT = `
5658roboppi — execution-control runtime for agentic workers
5759
5860USAGE
59- roboppi [options] Start Core IPC server mode
61+ roboppi Show help (interactive)
62+ roboppi server [options] Start Core IPC server mode (default: TUI when interactive)
63+ roboppi [options] Start Core IPC server mode (when non-interactive)
6064 roboppi run [options] <instructions...> Run a one-shot worker task
6165 roboppi workflow <workflow.yaml> [options] Run a workflow YAML
6266 roboppi daemon <daemon.yaml> [options] Run daemon mode (resident workflows)
6367 roboppi agent [options] <instructions...> Alias for 'roboppi run'
6468
6569IPC SERVER MODE
6670 Reads JSON Lines from stdin, writes responses to stdout. Logs go to stderr.
71+ TUI is enabled by default when stderr is a TTY; disable with --no-tui.
6772
6873RUN MODE OPTIONS
6974 --worker <kind> Worker to use: opencode, claude-code, codex (required)
@@ -88,6 +93,9 @@ SHARED OPTIONS
8893
8994 --log-level <level> Log level: debug|info|warn|error|fatal (default: info)
9095
96+ --tui Force TUI logs UI (stderr)
97+ --no-tui, --plain Disable TUI; emit JSON logs to stderr
98+
9199 --help, -h Show this help message
92100 --version, -v Show version
93101
@@ -139,6 +147,7 @@ function parseCliArgs(argv: string[]): CliMode {
139147 const shared : SharedOptions = {
140148 help : false ,
141149 version : false ,
150+ tui : undefined ,
142151 concurrency : undefined ,
143152 rps : undefined ,
144153 maxCost : undefined ,
@@ -224,6 +233,8 @@ function parseCliArgs(argv: string[]): CliMode {
224233 switch ( arg ) {
225234 case "--help" : case "-h" : shared . help = true ; break ;
226235 case "--version" : case "-v" : shared . version = true ; break ;
236+ case "--tui" : shared . tui = true ; break ;
237+ case "--no-tui" : case "--plain" : shared . tui = false ; break ;
227238 case "--concurrency" : shared . concurrency = nextInt ( ) ; break ;
228239 case "--rps" : shared . rps = nextInt ( ) ; break ;
229240 case "--max-cost" : shared . maxCost = nextInt ( ) ; break ;
@@ -246,6 +257,21 @@ function parseCliArgs(argv: string[]): CliMode {
246257 return { mode : "server" , opts : shared } ;
247258}
248259
260+ function parseEnvBool ( value : string | undefined ) : boolean | undefined {
261+ if ( value === undefined ) return undefined ;
262+ const v = value . trim ( ) . toLowerCase ( ) ;
263+ if ( v === "1" || v === "true" || v === "yes" || v === "on" ) return true ;
264+ if ( v === "0" || v === "false" || v === "no" || v === "off" ) return false ;
265+ return undefined ;
266+ }
267+
268+ function resolveTuiMode ( cliTui : boolean | undefined , envTui : string | undefined ) : boolean {
269+ if ( cliTui !== undefined ) return cliTui ;
270+ const envVal = parseEnvBool ( envTui ) ;
271+ if ( envVal !== undefined ) return envVal ;
272+ return ! ! process . stderr . isTTY ;
273+ }
274+
249275function buildConfig ( opts : SharedOptions ) : AgentCoreConfig {
250276 const config : AgentCoreConfig = { } ;
251277 if ( opts . concurrency !== undefined || opts . rps !== undefined || opts . maxCost !== undefined ) {
@@ -444,17 +470,37 @@ async function executeRun(opts: SharedOptions, run: RunOptions): Promise<void> {
444470
445471function startServer ( opts : SharedOptions ) : void {
446472 const config = buildConfig ( opts ) ;
447- startCoreRuntime ( {
448- config,
449- loggerComponent : "cli" ,
450- } ) ;
473+ const useTui = resolveTuiMode ( opts . tui , process . env . ROBOPPI_TUI ) ;
474+ if ( useTui && process . stderr . isTTY ) {
475+ // In Core stdio IPC mode, stdin is reserved for JSONL; use a passive renderer.
476+ const tui = new CoreServerTui ( { title : "roboppi core (IPC server)" } ) ;
477+ tui . start ( ) ;
478+ process . on ( "exit" , ( ) => {
479+ try { tui . stop ( ) ; } catch { /* ignore */ }
480+ } ) ;
481+ startCoreRuntime ( {
482+ config,
483+ loggerComponent : "cli" ,
484+ logSink : tui . logSink ,
485+ } ) ;
486+ return ;
487+ }
488+
489+ startCoreRuntime ( { config, loggerComponent : "cli" } ) ;
451490}
452491
453492// ── Main ──────────────────────────────────────────────────────────
454493
455494async function main ( ) : Promise < void > {
456495 const argv = process . argv . slice ( 2 ) ;
457496
497+ // Default: show help in interactive TTY.
498+ // Starting the Core IPC server is explicit via `roboppi server` (or by passing options).
499+ if ( argv . length === 0 && process . stdin . isTTY && process . stderr . isTTY ) {
500+ console . log ( HELP_TEXT ) ;
501+ process . exit ( 0 ) ;
502+ }
503+
458504 if ( argv [ 0 ] === "workflow" ) {
459505 const { runWorkflowCli } = await import ( "./workflow/run.js" ) ;
460506 await runWorkflowCli ( argv . slice ( 1 ) ) ;
@@ -467,6 +513,21 @@ async function main(): Promise<void> {
467513 return ;
468514 }
469515
516+ // Explicit server subcommand.
517+ if ( argv [ 0 ] === "server" ) {
518+ const parsed = parseCliArgs ( argv . slice ( 1 ) ) ;
519+ if ( parsed . opts . help ) {
520+ console . log ( HELP_TEXT ) ;
521+ process . exit ( 0 ) ;
522+ }
523+ if ( parsed . opts . version ) {
524+ console . log ( `roboppi ${ VERSION } ` ) ;
525+ process . exit ( 0 ) ;
526+ }
527+ startServer ( parsed . opts ) ;
528+ return ;
529+ }
530+
470531 // Alias: `roboppi agent ...` == `roboppi run ...`
471532 const parsed = argv [ 0 ] === "agent"
472533 ? parseCliArgs ( [ "run" , ...argv . slice ( 1 ) ] )
0 commit comments