@@ -6,14 +6,14 @@ import { printBanner } from '../banner.js';
66import { assembleInstructions } from '../agent/context.js' ;
77import { interactiveSession } from '../agent/loop.js' ;
88import { allCapabilities , createSubAgentCapability } from '../tools/index.js' ;
9- import { TerminalUI } from '../ui/terminal .js' ;
9+ import { launchInkUI } from '../ui/app .js' ;
1010import { pickModel , resolveModel } from '../ui/model-picker.js' ;
1111export async function startCommand ( options ) {
1212 const version = options . version ?? '1.0.0' ;
1313 const chain = loadChain ( ) ;
1414 const apiUrl = API_URLS [ chain ] ;
1515 const config = loadConfig ( ) ;
16- // Resolve model — show picker if interactive and no model specified
16+ // Resolve model
1717 let model ;
1818 let bannerShown = false ;
1919 const configModel = config [ 'default-model' ] ;
@@ -24,7 +24,6 @@ export async function startCommand(options) {
2424 model = configModel ;
2525 }
2626 else if ( process . stdin . isTTY ) {
27- // Interactive — show model picker
2827 printBanner ( version ) ;
2928 bannerShown = true ;
3029 const picked = await pickModel ( ) ;
@@ -57,15 +56,12 @@ export async function startCommand(options) {
5756 return ;
5857 }
5958 }
60- // Print banner (skip if already shown during model pick)
6159 if ( ! bannerShown )
6260 printBanner ( version ) ;
6361 const workDir = process . cwd ( ) ;
64- const ui = new TerminalUI ( ) ;
65- ui . printWelcome ( model , workDir ) ;
6662 // Assemble system instructions
6763 const systemInstructions = assembleInstructions ( workDir ) ;
68- // Build capabilities including sub-agent
64+ // Build capabilities
6965 const subAgent = createSubAgentCapability ( apiUrl , chain , allCapabilities ) ;
7066 const capabilities = [ ...allCapabilities , subAgent ] ;
7167 // Agent config
@@ -80,38 +76,69 @@ export async function startCommand(options) {
8076 permissionMode : options . trust ? 'trust' : 'default' ,
8177 debug : options . debug ,
8278 } ;
83- // Run interactive session
79+ // Use ink UI if TTY, fallback to basic readline for piped input
80+ if ( process . stdin . isTTY ) {
81+ await runWithInkUI ( agentConfig , model , workDir , version ) ;
82+ }
83+ else {
84+ await runWithBasicUI ( agentConfig , model , workDir ) ;
85+ }
86+ }
87+ // ─── Ink UI (interactive terminal) ─────────────────────────────────────────
88+ async function runWithInkUI ( agentConfig , model , workDir , version ) {
89+ const ui = launchInkUI ( { model, workDir, version } ) ;
90+ try {
91+ await interactiveSession ( agentConfig , async ( ) => {
92+ const input = await ui . waitForInput ( ) ;
93+ if ( input === null )
94+ return null ;
95+ if ( input === '' )
96+ return '' ;
97+ // Handle slash commands
98+ if ( input . startsWith ( '/' ) ) {
99+ const result = await handleSlashCommand ( input , agentConfig , ui ) ;
100+ if ( result === 'exit' )
101+ return null ;
102+ if ( result === null )
103+ return '' ; // re-prompt
104+ return result ;
105+ }
106+ return input ;
107+ } , ( event ) => ui . handleEvent ( event ) ) ;
108+ }
109+ catch ( err ) {
110+ if ( err . name !== 'AbortError' ) {
111+ console . error ( chalk . red ( `\nError: ${ err . message } ` ) ) ;
112+ }
113+ }
114+ ui . cleanup ( ) ;
115+ console . log ( chalk . dim ( '\nGoodbye.\n' ) ) ;
116+ }
117+ // ─── Basic readline UI (piped input) ───────────────────────────────────────
118+ async function runWithBasicUI ( agentConfig , model , workDir ) {
119+ const { TerminalUI } = await import ( '../ui/terminal.js' ) ;
120+ const ui = new TerminalUI ( ) ;
121+ ui . printWelcome ( model , workDir ) ;
84122 try {
85123 await interactiveSession ( agentConfig , async ( ) => {
86124 while ( true ) {
87125 const input = await ui . promptUser ( ) ;
88126 if ( input === null )
89- return null ; // EOF
127+ return null ;
90128 if ( input === '' )
91- continue ; // empty → re-prompt
92- if ( input . startsWith ( '/' ) ) {
93- const result = await handleSlashCommand ( input , agentConfig ) ;
94- if ( result === 'exit' )
95- return null ;
96- if ( result === null )
97- continue ; // command handled → re-prompt
98- return result ; // string → send to agent
99- }
129+ continue ;
100130 return input ;
101131 }
102132 } , ( event ) => ui . handleEvent ( event ) ) ;
103133 }
104134 catch ( err ) {
105- if ( err . name === 'AbortError' ) {
106- // User interrupted
107- }
108- else {
135+ if ( err . name !== 'AbortError' ) {
109136 console . error ( chalk . red ( `\nError: ${ err . message } ` ) ) ;
110137 }
111138 }
112139 ui . printGoodbye ( ) ;
113140}
114- async function handleSlashCommand ( cmd , config ) {
141+ async function handleSlashCommand ( cmd , config , ui ) {
115142 const parts = cmd . trim ( ) . split ( / \s + / ) ;
116143 const command = parts [ 0 ] . toLowerCase ( ) ;
117144 switch ( command ) {
@@ -121,39 +148,37 @@ async function handleSlashCommand(cmd, config) {
121148 case '/model' : {
122149 const newModel = parts [ 1 ] ;
123150 if ( newModel ) {
124- // Direct switch via shortcut or full ID
125151 config . model = resolveModel ( newModel ) ;
126152 console . error ( chalk . green ( ` Model → ${ config . model } ` ) ) ;
127153 return null ;
128154 }
129- // No arg — show interactive picker
130155 const picked = await pickModel ( config . model ) ;
131156 if ( picked ) {
132157 config . model = picked ;
133158 console . error ( chalk . green ( ` Model → ${ config . model } ` ) ) ;
134159 }
135160 return null ;
136161 }
137- case '/models' :
138- // Shortcut: same as /model with no args
162+ case '/models' : {
139163 const picked = await pickModel ( config . model ) ;
140164 if ( picked ) {
141165 config . model = picked ;
142166 console . error ( chalk . green ( ` Model → ${ config . model } ` ) ) ;
143167 }
144168 return null ;
169+ }
145170 case '/cost' :
146171 case '/usage' : {
147172 const { getStatsSummary } = await import ( '../stats/tracker.js' ) ;
148- const { stats, opusCost , saved } = getStatsSummary ( ) ;
173+ const { stats, saved } = getStatsSummary ( ) ;
149174 console . error ( chalk . dim ( `\n Requests: ${ stats . totalRequests } | Cost: $${ stats . totalCostUsd . toFixed ( 4 ) } | Saved: $${ saved . toFixed ( 2 ) } vs Opus\n` ) ) ;
150175 return null ;
151176 }
152177 case '/help' :
153178 console . error ( chalk . bold ( '\n Commands:' ) ) ;
154- console . error ( ' /model [name] — switch model (interactive picker if no name)' ) ;
155- console . error ( ' /models — browse all available models' ) ;
156- console . error ( ' /cost — show session cost and savings' ) ;
179+ console . error ( ' /model [name] — switch model (picker if no name)' ) ;
180+ console . error ( ' /models — browse available models' ) ;
181+ console . error ( ' /cost — session cost and savings' ) ;
157182 console . error ( ' /exit — quit' ) ;
158183 console . error ( ' /help — this help\n' ) ;
159184 console . error ( chalk . dim ( ' Shortcuts: sonnet, opus, gpt, gemini, deepseek, flash, free, r1, o4\n' ) ) ;
0 commit comments