@@ -5,7 +5,8 @@ import { resolve, dirname, join } from 'path';
55import { fileURLToPath } from 'url' ;
66import { tmpdir } from 'os' ;
77
8- const pm2lib = require ( 'pm2' ) ;
8+ let pm2lib = null ;
9+ try { pm2lib = require ( 'pm2' ) ; } catch { /* pm2 not installed — fallback to direct spawn */ }
910
1011const __dirname = dirname ( fileURLToPath ( import . meta. url ) ) ;
1112const RUNNER_SCRIPT = resolve ( __dirname , 'task-runner.js' ) ;
@@ -33,12 +34,14 @@ function pm2describe(name) {
3334}
3435
3536async function withPm2 ( fn ) {
37+ if ( ! pm2lib ) throw new Error ( 'pm2 unavailable' ) ;
3638 await pm2connect ( ) ;
3739 try { return await fn ( ) ; }
3840 finally { await pm2disconnect ( ) ; }
3941}
4042
4143async function printRunningTools ( ) {
44+ if ( ! pm2lib ) return ;
4245 try {
4346 await pm2connect ( ) ;
4447 const list = await pm2list ( ) ;
@@ -77,10 +80,16 @@ async function healthCheck() {
7780async function ensureRunner ( ) {
7881 if ( await healthCheck ( ) ) return false ;
7982 process . stderr . write ( 'Auto-starting runner...\n' ) ;
80- await withPm2 ( async ( ) => {
81- await pm2delete ( PM2_NAME ) . catch ( ( ) => { } ) ;
82- await pm2start ( { script : 'bun' , args : RUNNER_SCRIPT , name : PM2_NAME , autorestart : false , watch : false } ) ;
83- } ) ;
83+ if ( pm2lib ) {
84+ await withPm2 ( async ( ) => {
85+ await pm2delete ( PM2_NAME ) . catch ( ( ) => { } ) ;
86+ await pm2start ( { script : 'bun' , args : RUNNER_SCRIPT , name : PM2_NAME , autorestart : false , watch : false } ) ;
87+ } ) ;
88+ } else {
89+ const { spawn } = await import ( 'child_process' ) ;
90+ const child = spawn ( 'bun' , [ RUNNER_SCRIPT ] , { detached : true , stdio : 'ignore' } ) ;
91+ child . unref ( ) ;
92+ }
8493 for ( let i = 0 ; i < 20 ; i ++ ) {
8594 await new Promise ( r => setTimeout ( r , 500 ) ) ;
8695 if ( await healthCheck ( ) ) return true ;
@@ -89,6 +98,7 @@ async function ensureRunner() {
8998}
9099
91100async function stopRunner ( ) {
101+ if ( ! pm2lib ) return ;
92102 await withPm2 ( ( ) => pm2delete ( PM2_NAME ) . catch ( ( ) => { } ) ) ;
93103}
94104
@@ -123,7 +133,7 @@ function rpcCall(method, params, timeoutMs = 10000) {
123133}
124134
125135async function runCode ( code , runtime , workingDirectory ) {
126- const autoStarted = await ensureRunner ( ) ;
136+ await ensureRunner ( ) ;
127137 const taskId = await rpcCall ( 'createTask' , { code, runtime, workingDirectory } ) . then ( r => r ?. taskId ?? r ) ;
128138
129139 const safetyTimeout = new Promise ( r => {
@@ -151,6 +161,7 @@ async function runCode(code, runtime, workingDirectory) {
151161 console . log ( `Task ID: ${ id } \n` ) ;
152162 console . log ( ` gm-exec sleep ${ id } # wait for completion (up to 30s) — recommended` ) ;
153163 console . log ( ` gm-exec status ${ id } # drain output buffer (snapshot)` ) ;
164+ console . log ( ` gm-exec type ${ id } <input> # send stdin to running task` ) ;
154165 console . log ( ` gm-exec close ${ id } # delete task when done` ) ;
155166 console . log ( ` gm-exec runner stop # stop runner when all tasks done` ) ;
156167 console . log ( `\nRunner kept alive: ${ PM2_NAME } (PM2)` ) ;
@@ -176,10 +187,16 @@ async function cmdRunnerStart() {
176187 console . log ( `Runner already healthy on port ${ readFileSync ( PORT_FILE , 'utf8' ) . trim ( ) } ` ) ;
177188 return ;
178189 }
179- await withPm2 ( async ( ) => {
180- await pm2delete ( PM2_NAME ) . catch ( ( ) => { } ) ;
181- await pm2start ( { script : 'bun' , args : RUNNER_SCRIPT , name : PM2_NAME , autorestart : false , watch : false } ) ;
182- } ) ;
190+ if ( pm2lib ) {
191+ await withPm2 ( async ( ) => {
192+ await pm2delete ( PM2_NAME ) . catch ( ( ) => { } ) ;
193+ await pm2start ( { script : 'bun' , args : RUNNER_SCRIPT , name : PM2_NAME , autorestart : false , watch : false } ) ;
194+ } ) ;
195+ } else {
196+ const { spawn } = await import ( 'child_process' ) ;
197+ const child = spawn ( 'bun' , [ RUNNER_SCRIPT ] , { detached : true , stdio : 'ignore' } ) ;
198+ child . unref ( ) ;
199+ }
183200 for ( let i = 0 ; i < 20 ; i ++ ) {
184201 await new Promise ( r => setTimeout ( r , 500 ) ) ;
185202 if ( await healthCheck ( ) ) { console . log ( `Runner started on port ${ readFileSync ( PORT_FILE , 'utf8' ) . trim ( ) } ` ) ; return ; }
@@ -193,6 +210,12 @@ async function cmdRunnerStop() {
193210}
194211
195212async function cmdRunnerStatus ( ) {
213+ if ( ! pm2lib ) {
214+ const alive = await healthCheck ( ) ;
215+ console . log ( `${ PM2_NAME } : ${ alive ? 'online (no pm2 — direct spawn)' : 'not running' } ` ) ;
216+ if ( alive && existsSync ( PORT_FILE ) ) console . log ( `port: ${ readFileSync ( PORT_FILE , 'utf8' ) . trim ( ) } ` ) ;
217+ return ;
218+ }
196219 const desc = await withPm2 ( ( ) => pm2describe ( PM2_NAME ) . catch ( ( ) => [ ] ) ) ;
197220 if ( ! desc || desc . length === 0 ) { console . log ( `${ PM2_NAME } : not found` ) ; return ; }
198221 const p = desc [ 0 ] ;
@@ -208,6 +231,7 @@ async function cmdRunnerStatus() {
208231 console . log ( `\nRunner is active. If you have background tasks:` ) ;
209232 console . log ( ` gm-exec sleep <task_id> # wait for task completion (up to 30s)` ) ;
210233 console . log ( ` gm-exec status <task_id> # check task status` ) ;
234+ console . log ( ` gm-exec type <task_id> <input> # send stdin to running task` ) ;
211235 console . log ( ` gm-exec runner stop # stop runner when all tasks done` ) ;
212236 }
213237}
@@ -230,11 +254,10 @@ async function cmdBash(cmdArgs, positional) {
230254}
231255
232256async function cmdStatus ( taskId ) {
233- const autoStarted = await ensureRunner ( ) ;
257+ await ensureRunner ( ) ;
234258 const rawId = parseInt ( taskId . replace ( / ^ t a s k _ / , '' ) , 10 ) ;
235259 const task = await rpcCall ( 'getTask' , { taskId : rawId } ) . then ( r => r ?. task ?? r ) ;
236260 if ( ! task ) {
237- if ( autoStarted ) await stopRunner ( ) ;
238261 throw Object . assign ( new Error ( 'Task not found' ) , { exitCode : 1 , silent : true } ) ;
239262 }
240263 console . log ( `Status: ${ task . status } ` ) ;
@@ -254,17 +277,17 @@ async function cmdStatus(taskId) {
254277 if ( task . status === 'running' ) {
255278 console . log ( `\nTask still running. Options:` ) ;
256279 console . log ( ` gm-exec sleep ${ taskId } # wait for completion (up to 30s) — recommended` ) ;
280+ console . log ( ` gm-exec type ${ taskId } <input> # send stdin to running task` ) ;
257281 console . log ( ` gm-exec status ${ taskId } # check status again (snapshot)` ) ;
258282 } else if ( task . status === 'completed' || task . status === 'failed' ) {
259283 console . log ( `\nTask finished. Clean up:` ) ;
260284 console . log ( ` gm-exec close ${ taskId } # delete task` ) ;
261285 console . log ( ` gm-exec runner stop # stop runner if no more tasks` ) ;
262286 }
263- if ( autoStarted ) await stopRunner ( ) ;
264287}
265288
266289async function cmdClose ( taskId ) {
267- const autoStarted = await ensureRunner ( ) ;
290+ await ensureRunner ( ) ;
268291 const rawId = parseInt ( taskId . replace ( / ^ t a s k _ / , '' ) , 10 ) ;
269292 await rpcCall ( 'deleteTask' , { taskId : rawId } ) ;
270293 const res = await rpcCall ( 'listTasks' , { } ) . catch ( ( ) => ( { tasks : [ ] } ) ) ;
@@ -277,12 +300,11 @@ async function cmdClose(taskId) {
277300 }
278301 } else {
279302 console . log ( ` gm-exec runner stop # no more tasks — stop runner` ) ;
280- if ( autoStarted ) await stopRunner ( ) ;
281303 }
282304}
283305
284306async function cmdSleep ( taskId , timeoutSeconds , nextOutputMode ) {
285- const autoStarted = await ensureRunner ( ) ;
307+ await ensureRunner ( ) ;
286308 const rawId = parseInt ( taskId . replace ( / ^ t a s k _ / , '' ) , 10 ) ;
287309 const timeout = ( parseInt ( timeoutSeconds , 10 ) || 30 ) * 1000 ;
288310 const startTime = Date . now ( ) ;
@@ -309,7 +331,6 @@ async function cmdSleep(taskId, timeoutSeconds, nextOutputMode) {
309331 console . log ( `\nTask finished (${ task . status } ). Clean up:` ) ;
310332 console . log ( ` gm-exec close ${ taskId } # delete task` ) ;
311333 console . log ( ` gm-exec runner stop # stop runner if no more tasks` ) ;
312- if ( autoStarted ) await stopRunner ( ) ;
313334 return ;
314335 }
315336 if ( nextOutputMode ) {
@@ -323,7 +344,19 @@ async function cmdSleep(taskId, timeoutSeconds, nextOutputMode) {
323344 console . log ( `\nTimeout after ${ timeout / 1000 } s. Task still running.` ) ;
324345 console . log ( ` gm-exec sleep ${ taskId } # wait again (up to 30s) — recommended` ) ;
325346 console . log ( ` gm-exec status ${ taskId } # check current status (snapshot)` ) ;
326- if ( autoStarted ) await stopRunner ( ) ;
347+ }
348+
349+ async function cmdType ( taskId , inputData ) {
350+ await ensureRunner ( ) ;
351+ const rawId = parseInt ( taskId . replace ( / ^ t a s k _ / , '' ) , 10 ) ;
352+ const data = inputData + '\n' ;
353+ const result = await rpcCall ( 'sendStdin' , { taskId : rawId , data } ) . then ( r => r ?. ok ?? r ) . catch ( ( ) => false ) ;
354+ if ( result ) {
355+ console . log ( `Sent to task ${ taskId } ` ) ;
356+ } else {
357+ process . stderr . write ( `Task ${ taskId } not found or not running\n` ) ;
358+ return 1 ;
359+ }
327360}
328361
329362function parseArgs ( argv ) {
@@ -362,6 +395,7 @@ Commands:
362395 status <task_id> Poll status + drain output of a background task
363396 sleep <task_id> [seconds]
364397 Wait for task completion (default 30s timeout)
398+ type <task_id> <input> Send input to stdin of a running background task
365399 close <task_id> Delete a background task
366400 runner start|stop|status
367401 Manage the task runner process (PM2)
@@ -401,6 +435,10 @@ try {
401435 } else if ( cmd === 'close' ) {
402436 if ( ! rest [ 0 ] ) { process . stderr . write ( 'Task ID required\n' ) ; exitCode = 1 ; }
403437 else await cmdClose ( rest [ 0 ] ) ;
438+ } else if ( cmd === 'type' ) {
439+ if ( ! rest [ 0 ] ) { process . stderr . write ( 'Task ID required\n' ) ; exitCode = 1 ; }
440+ else if ( ! rest [ 1 ] ) { process . stderr . write ( 'Input required\n' ) ; exitCode = 1 ; }
441+ else exitCode = ( await cmdType ( rest [ 0 ] , rest . slice ( 1 ) . join ( ' ' ) ) ) ?? 0 ;
404442 } else {
405443 process . stderr . write ( `Unknown command: ${ cmd } \n` ) ;
406444 usage ( ) ;
0 commit comments