11import type { PermissionV1 } from "@opencode-ai/core/v1/permission"
22import { FSUtil } from "@opencode-ai/core/fs-util"
3- // CLI entry point for `opencode run`.
3+ // CLI entry point for `opencode run` and `opencode --mini` .
44//
55// Handles three modes:
66// 1. Non-interactive (default): sends a single prompt, streams events to
77// stdout, and exits when the session goes idle.
8- // 2. Interactive local (`--interactive `): boots the split-footer direct mode
8+ // 2. Interactive local (`opencode --mini `): boots the split-footer direct mode
99// with an in-process server (no external HTTP).
10- // 3. Interactive attach (`--interactive --attach`): connects to a running
10+ // 3. Interactive attach (`opencode --mini --attach`): connects to a running
1111// opencode server and runs interactive mode against it.
1212//
1313// Also supports `--command` for slash-command execution, `--format json` for
@@ -217,21 +217,22 @@ export const RunCommand = effectCmd({
217217 type : "boolean" ,
218218 describe : "show thinking blocks" ,
219219 } )
220+ . option ( "mini" , {
221+ type : "boolean" ,
222+ hidden : true ,
223+ default : false ,
224+ } )
220225 . option ( "replay" , {
221226 type : "boolean" ,
222227 default : true ,
228+ hidden : true ,
223229 describe : "replay interactive session history on resume and after resize (use --no-replay to disable)" ,
224230 } )
225231 . option ( "replay-limit" , {
226232 type : "number" ,
233+ hidden : true ,
227234 describe : "cap visible interactive replay to the newest N messages" ,
228235 } )
229- . option ( "interactive" , {
230- alias : [ "i" ] ,
231- type : "boolean" ,
232- describe : "run in direct interactive split-footer mode" ,
233- default : false ,
234- } )
235236 . option ( "dangerously-skip-permissions" , {
236237 type : "boolean" ,
237238 describe : "auto-approve permissions that are not explicitly denied (dangerous!)" ,
@@ -240,6 +241,7 @@ export const RunCommand = effectCmd({
240241 . option ( "demo" , {
241242 type : "boolean" ,
242243 default : false ,
244+ hidden : true ,
243245 describe : "enable direct interactive demo slash commands; pass one as the message to run it immediately" ,
244246 } ) ,
245247 handler : Effect . fn ( "Cli.run" ) ( function * ( args ) {
@@ -252,7 +254,8 @@ export const RunCommand = effectCmd({
252254 const localInstance = yield * InstanceRef
253255 yield * Effect . promise ( async ( ) => {
254256 const rawMessage = [ ...args . message , ...( args [ "--" ] || [ ] ) ] . join ( " " )
255- const thinking = args . interactive ? ( args . thinking ?? true ) : ( args . thinking ?? false )
257+ const interactive = args . mini
258+ const thinking = interactive ? ( args . thinking ?? true ) : ( args . thinking ?? false )
256259 const die = ( message : string ) : never => {
257260 UI . error ( message )
258261 process . exit ( 1 )
@@ -269,20 +272,24 @@ export const RunCommand = effectCmd({
269272 . map ( ( arg ) => ( arg . includes ( " " ) ? `"${ arg . replace ( / " / g, '\\"' ) } "` : arg ) )
270273 . join ( " " )
271274
272- if ( args . interactive && args . command ) {
273- die ( "--interactive cannot be used with --command" )
275+ if ( interactive && args . command ) {
276+ die ( "--mini cannot be used with --command" )
274277 }
275278
276- if ( args . demo && ! args . interactive ) {
277- die ( "--demo requires --interactive " )
279+ if ( interactive && args . _ ?. [ 0 ] !== "mini" ) {
280+ die ( "--mini must be used without the run subcommand " )
278281 }
279282
280- if ( args . interactive && args . format === "json" ) {
281- die ( "--interactive cannot be used with --format json " )
283+ if ( args . demo && ! interactive ) {
284+ die ( "--demo requires --mini " )
282285 }
283286
284- if ( args [ "replay-limit" ] !== undefined && ! args . interactive ) {
285- die ( "--replay-limit requires --interactive" )
287+ if ( interactive && args . format === "json" ) {
288+ die ( "--mini cannot be used with --format json" )
289+ }
290+
291+ if ( args [ "replay-limit" ] !== undefined && ! interactive ) {
292+ die ( "--replay-limit requires --mini" )
286293 }
287294
288295 if (
@@ -292,19 +299,19 @@ export const RunCommand = effectCmd({
292299 die ( "--replay-limit must be a positive integer" )
293300 }
294301
295- if ( args . interactive && ! process . stdout . isTTY ) {
296- die ( "--interactive requires a TTY stdout" )
302+ if ( interactive && ! process . stdout . isTTY ) {
303+ die ( "--mini requires a TTY stdout" )
297304 }
298305
299- if ( args . interactive ) {
306+ if ( interactive ) {
300307 try {
301308 resolveInteractiveStdin ( ) . cleanup ?.( )
302309 } catch ( error ) {
303310 dieInteractive ( error )
304311 }
305312 }
306313
307- const replay = args . replay || args [ "replay-limit" ] !== undefined
314+ const replay = args . replay === false ? false : args . replay || args [ "replay-limit" ] !== undefined
308315
309316 const root = Filesystem . resolve ( process . env . PWD ?? process . cwd ( ) )
310317 const directory = ( ( ) => {
@@ -393,7 +400,7 @@ export const RunCommand = effectCmd({
393400 message = resolveRunInput ( message , piped ) ?? ""
394401 const initialInput = resolveRunInput ( rawMessage , piped )
395402
396- if ( message . trim ( ) . length === 0 && ! args . command && ! args . interactive ) {
403+ if ( message . trim ( ) . length === 0 && ! args . command && ! interactive ) {
397404 UI . error ( "You must provide a message or a command" )
398405 process . exit ( 1 )
399406 }
@@ -403,7 +410,7 @@ export const RunCommand = effectCmd({
403410 process . exit ( 1 )
404411 }
405412
406- const rules : PermissionV1 . Ruleset = args . interactive
413+ const rules : PermissionV1 . Ruleset = interactive
407414 ? [ ]
408415 : [
409416 {
@@ -801,7 +808,7 @@ export const RunCommand = effectCmd({
801808
802809 await share ( client , sessionID )
803810
804- if ( ! args . interactive ) {
811+ if ( ! interactive ) {
805812 const events = await client . event . subscribe ( )
806813 const completed = loop ( client , events ) . catch ( ( e ) => {
807814 console . error ( e )
@@ -875,7 +882,7 @@ export const RunCommand = effectCmd({
875882 return
876883 }
877884
878- if ( args . interactive && ! args . attach && ! args . session && ! args . continue ) {
885+ if ( interactive && ! args . attach && ! args . session && ! args . continue ) {
879886 const model = pick ( args . model )
880887 const { runInteractiveLocalMode } = await import ( "./run/runtime" )
881888 const fetchFn = ( async ( input : RequestInfo | URL , init ?: RequestInit ) => {
@@ -933,3 +940,52 @@ export const RunCommand = effectCmd({
933940 } )
934941 } ) ,
935942} )
943+
944+ type MiniCommandInput = {
945+ directory ?: string
946+ attach ?: string
947+ password ?: string
948+ username ?: string
949+ continue ?: boolean
950+ session ?: string
951+ fork ?: boolean
952+ model ?: string
953+ agent ?: string
954+ prompt ?: string
955+ replay ?: boolean
956+ replayLimit ?: number
957+ demo ?: boolean
958+ }
959+
960+ export async function runMini ( input : MiniCommandInput ) {
961+ if ( ! RunCommand . handler ) throw new Error ( "Mini command handler is unavailable" )
962+ await RunCommand . handler ( {
963+ $0 : "opencode" ,
964+ _ : [ "mini" ] ,
965+ message : input . prompt ? [ input . prompt ] : [ ] ,
966+ command : undefined ,
967+ continue : input . continue ,
968+ session : input . session ,
969+ fork : input . fork ,
970+ share : undefined ,
971+ model : input . model ,
972+ agent : input . agent ,
973+ format : "default" ,
974+ file : undefined ,
975+ title : undefined ,
976+ attach : input . attach ,
977+ password : input . password ,
978+ username : input . username ,
979+ dir : input . directory ,
980+ port : undefined ,
981+ variant : undefined ,
982+ thinking : undefined ,
983+ mini : true ,
984+ replay : input . replay ?? true ,
985+ "replay-limit" : input . replayLimit ,
986+ replayLimit : input . replayLimit ,
987+ "dangerously-skip-permissions" : false ,
988+ dangerouslySkipPermissions : false ,
989+ demo : input . demo ?? false ,
990+ } )
991+ }
0 commit comments