@@ -9,12 +9,10 @@ export interface ShellOptions {
99 discoConfig : DiscoConfig
1010 service ?: string
1111 command ?: string
12- interactive ?: boolean
1312}
1413
1514export interface ShellResult {
1615 exitCode : number
17- output : string
1816}
1917
2018export async function checkShellSupport ( discoConfig : DiscoConfig ) : Promise < { supported : boolean ; version : string } > {
@@ -27,85 +25,6 @@ export async function checkShellSupport(discoConfig: DiscoConfig): Promise<{ sup
2725 }
2826}
2927
30- export function runCommandViaShell ( options : ShellOptions ) : Promise < ShellResult > {
31- const { project, discoConfig, service, command } = options
32-
33- return new Promise ( ( resolve , reject ) => {
34- const wsUrl = `wss://${ discoConfig . host } /api/projects/${ project } /shell`
35- const ws = new WS ( wsUrl )
36- let output = ''
37- let exitCode = 0
38-
39- // Handler for forwarding stdin - defined here so we can remove it on close
40- const stdinHandler = ( chunk : Buffer ) => {
41- if ( ws . readyState === WS . OPEN ) {
42- ws . send ( chunk )
43- }
44- }
45-
46- const cleanup = ( ) => {
47- process . stdin . removeListener ( 'data' , stdinHandler )
48- process . stdin . pause ( )
49- }
50-
51- ws . on ( 'open' , ( ) => {
52- const authMessage : { token : string ; service ?: string ; command ?: string } = { token : discoConfig . apiKey }
53-
54- if ( service ) {
55- authMessage . service = service
56- }
57-
58- if ( command ) {
59- authMessage . command = command
60- }
61-
62- ws . send ( JSON . stringify ( authMessage ) )
63- } )
64-
65- ws . on ( 'message' , ( data : WS . RawData , isBinary : boolean ) => {
66- if ( isBinary ) {
67- const chunk = ( data as Buffer ) . toString ( )
68- output += chunk
69- process . stdout . write ( chunk )
70- } else {
71- try {
72- const message = JSON . parse ( data . toString ( ) )
73- if ( message . type === 'connected' ) {
74- // Forward stdin in case the command needs input (e.g. accidentally ran python REPL)
75- // This allows user to type 'exit' or Ctrl+C to escape
76- process . stdin . resume ( )
77- process . stdin . on ( 'data' , stdinHandler )
78- } else if ( message . type === 'ping' && ws . readyState === WS . OPEN ) {
79- ws . send ( JSON . stringify ( { type : 'pong' } ) )
80- } else if ( message . type === 'exit' ) {
81- exitCode = message . code ?? 0
82- }
83- } catch {
84- // Not JSON, treat as text output
85- const text = data . toString ( )
86- output += text
87- process . stdout . write ( text )
88- }
89- }
90- } )
91-
92- ws . on ( 'close' , ( code , reason ) => {
93- cleanup ( )
94-
95- if ( code === 1000 ) {
96- resolve ( { exitCode, output } )
97- } else {
98- reject ( new Error ( `Connection closed: ${ code } ${ reason . toString ( ) } ` ) )
99- }
100- } )
101-
102- ws . on ( 'error' , ( err ) => {
103- cleanup ( )
104- reject ( new Error ( `WebSocket error: ${ err . message } ` ) )
105- } )
106- } )
107- }
108-
10928function restoreTerminal ( ) : void {
11029 if ( process . stdin . isTTY ) {
11130 process . stdin . setRawMode ( false )
@@ -114,20 +33,25 @@ function restoreTerminal(): void {
11433 process . stdin . pause ( )
11534}
11635
117- export function runInteractiveShell ( options : ShellOptions ) : Promise < void > {
118- const { project, discoConfig, service } = options
36+ export function runShell ( options : ShellOptions ) : Promise < ShellResult > {
37+ const { project, discoConfig, service, command } = options
11938
12039 return new Promise ( ( resolve , reject ) => {
12140 const wsUrl = `wss://${ discoConfig . host } /api/projects/${ project } /shell`
12241 const ws = new WS ( wsUrl )
42+ let exitCode = 0
12343
12444 ws . on ( 'open' , ( ) => {
125- const authMessage : { token : string ; service ?: string } = { token : discoConfig . apiKey }
45+ const authMessage : { token : string ; service ?: string ; command ?: string } = { token : discoConfig . apiKey }
12646
12747 if ( service ) {
12848 authMessage . service = service
12949 }
13050
51+ if ( command ) {
52+ authMessage . command = command
53+ }
54+
13155 ws . send ( JSON . stringify ( authMessage ) )
13256 } )
13357
@@ -169,6 +93,8 @@ export function runInteractiveShell(options: ShellOptions): Promise<void> {
16993 } )
17094 } else if ( message . type === 'ping' && ws . readyState === WS . OPEN ) {
17195 ws . send ( JSON . stringify ( { type : 'pong' } ) )
96+ } else if ( message . type === 'exit' ) {
97+ exitCode = message . code ?? 0
17298 }
17399 } catch {
174100 process . stdout . write ( data . toString ( ) )
@@ -180,7 +106,7 @@ export function runInteractiveShell(options: ShellOptions): Promise<void> {
180106 restoreTerminal ( )
181107
182108 if ( code === 1000 ) {
183- resolve ( )
109+ resolve ( { exitCode } )
184110 } else {
185111 reject ( new Error ( `Connection closed: ${ code } ${ reason . toString ( ) } ` ) )
186112 }
0 commit comments