@@ -52,17 +52,22 @@ def _resolve_pod(cclient, deployment_id, pod_name=None):
5252 running_pods .append (pod .name )
5353
5454 if not running_pods :
55- raise click .ClickException (f"No running pods found for deployment { deployment_id } " )
55+ raise click .ClickException (
56+ f"No running pods found for deployment { deployment_id } "
57+ )
5658
5759 if pod_name is not None :
5860 if pod_name not in running_pods :
5961 pods_list = ", " .join (running_pods )
60- raise click .ClickException (f"Pod '{ pod_name } ' not found. Available running pods: { pods_list } " )
62+ raise click .ClickException (
63+ f"Pod '{ pod_name } ' not found. Available running pods: { pods_list } "
64+ )
6165 return pod_name
6266
6367 if len (running_pods ) > 1 :
6468 click .echo (
65- f"Multiple running pods found, connecting to { running_pods [0 ]} . " f"Use --pod to specify a different pod." ,
69+ f"Multiple running pods found, connecting to { running_pods [0 ]} . "
70+ f"Use --pod to specify a different pod." ,
6671 err = True ,
6772 )
6873 return running_pods [0 ]
@@ -110,8 +115,16 @@ def _on_stdin_ready():
110115 data = await asyncio .wait_for (read_queue .get (), timeout = 0.5 )
111116 except asyncio .TimeoutError :
112117 continue
118+ r , c = shutil .get_terminal_size ()
113119 await ws .send (
114- json .dumps ({"operation" : "stdin" , "data" : data .decode ("utf-8" , errors = "replace" )})
120+ json .dumps (
121+ {
122+ "operation" : "stdin" ,
123+ "data" : data .decode ("utf-8" , errors = "replace" ),
124+ "rows" : r ,
125+ "cols" : c ,
126+ }
127+ )
115128 )
116129 finally :
117130 loop .remove_reader (stdin_fd )
@@ -144,14 +157,20 @@ async def _interactive_session(ws_url, token):
144157 rows , cols = shutil .get_terminal_size ()
145158
146159 headers = {"Authorization" : f"Bearer { token } " }
147- async with websockets .connect (ws_url , additional_headers = headers , close_timeout = 2 ) as ws :
148- await ws .send (json .dumps ({"operation" : "resize" , "rows" : rows , "cols" : cols }))
160+ async with websockets .connect (
161+ ws_url , additional_headers = headers , close_timeout = 2
162+ ) as ws :
163+ await ws .send (
164+ json .dumps ({"operation" : "resize" , "rows" : rows , "cols" : cols })
165+ )
149166
150167 loop = asyncio .get_running_loop ()
151168
152169 def _send_resize ():
153170 r , c = shutil .get_terminal_size ()
154- asyncio .ensure_future (ws .send (json .dumps ({"operation" : "resize" , "rows" : r , "cols" : c })))
171+ asyncio .ensure_future (
172+ ws .send (json .dumps ({"operation" : "resize" , "rows" : r , "cols" : c }))
173+ )
155174
156175 async def _force_initial_redraw ():
157176 """Force SIGWINCH on the remote by toggling the PTY width.
@@ -164,9 +183,17 @@ async def _force_initial_redraw():
164183 try :
165184 await asyncio .sleep (0.5 )
166185 r , c = shutil .get_terminal_size ()
167- await ws .send (json .dumps ({"operation" : "resize" , "rows" : r , "cols" : c + 1 }))
186+ await ws .send (
187+ json .dumps ({"operation" : "resize" , "rows" : r , "cols" : c + 1 })
188+ )
168189 await asyncio .sleep (0.05 )
169- await ws .send (json .dumps ({"operation" : "resize" , "rows" : r , "cols" : c }))
190+ await ws .send (
191+ json .dumps ({"operation" : "resize" , "rows" : r , "cols" : c })
192+ )
193+ await asyncio .sleep (0.1 )
194+ # Ctrl+L clears screen and forces the shell to redraw
195+ # its prompt at the now-correct terminal width.
196+ await ws .send (json .dumps ({"operation" : "stdin" , "data" : "\x0c " }))
170197 except Exception :
171198 pass
172199
@@ -208,7 +235,9 @@ async def _exec_session(ws_url, token, command):
208235 rows , cols = shutil .get_terminal_size (fallback = (80 , 24 ))
209236 headers = {"Authorization" : f"Bearer { token } " }
210237
211- async with websockets .connect (ws_url , additional_headers = headers , close_timeout = 2 ) as ws :
238+ async with websockets .connect (
239+ ws_url , additional_headers = headers , close_timeout = 2
240+ ) as ws :
212241 await ws .send (json .dumps ({"operation" : "resize" , "rows" : rows , "cols" : cols }))
213242
214243 # Suppress echo/bracketed-paste, emit begin marker, run command,
@@ -264,8 +293,16 @@ async def _exec_session(ws_url, token, command):
264293
265294@click .command (help = "Open an interactive shell to a deployment pod" )
266295@click .argument ("deployment_id" , type = int )
267- @click .option ("--pod" , default = None , help = "Specific pod name (auto-selects first running pod)" )
268- @click .option ("--shell" , "shell_type" , default = None , type = click .Choice (["bash" , "sh" , "zsh" ]), help = "Shell type" )
296+ @click .option (
297+ "--pod" , default = None , help = "Specific pod name (auto-selects first running pod)"
298+ )
299+ @click .option (
300+ "--shell" ,
301+ "shell_type" ,
302+ default = None ,
303+ type = click .Choice (["bash" , "sh" , "zsh" ]),
304+ help = "Shell type" ,
305+ )
269306@handle_exception
270307def shell (deployment_id , pod , shell_type ):
271308 if not sys .stdin .isatty ():
@@ -274,23 +311,36 @@ def shell(deployment_id, pod, shell_type):
274311 with get_centml_client () as cclient :
275312 pod_name = _resolve_pod (cclient , deployment_id , pod )
276313
277- ws_url = _build_ws_url (settings .CENTML_PLATFORM_API_URL , deployment_id , pod_name , shell_type )
314+ ws_url = _build_ws_url (
315+ settings .CENTML_PLATFORM_API_URL , deployment_id , pod_name , shell_type
316+ )
278317 token = auth .get_centml_token ()
279318 exit_code = asyncio .run (_interactive_session (ws_url , token ))
280319 sys .exit (exit_code )
281320
282321
283- @click .command (help = "Execute a command in a deployment pod" , context_settings = {"ignore_unknown_options" : True })
322+ @click .command (
323+ help = "Execute a command in a deployment pod" ,
324+ context_settings = {"ignore_unknown_options" : True },
325+ )
284326@click .argument ("deployment_id" , type = int )
285327@click .argument ("command" , nargs = - 1 , required = True , type = click .UNPROCESSED )
286328@click .option ("--pod" , default = None , help = "Specific pod name" )
287- @click .option ("--shell" , "shell_type" , default = None , type = click .Choice (["bash" , "sh" , "zsh" ]), help = "Shell type" )
329+ @click .option (
330+ "--shell" ,
331+ "shell_type" ,
332+ default = None ,
333+ type = click .Choice (["bash" , "sh" , "zsh" ]),
334+ help = "Shell type" ,
335+ )
288336@handle_exception
289337def exec_cmd (deployment_id , command , pod , shell_type ):
290338 with get_centml_client () as cclient :
291339 pod_name = _resolve_pod (cclient , deployment_id , pod )
292340
293- ws_url = _build_ws_url (settings .CENTML_PLATFORM_API_URL , deployment_id , pod_name , shell_type )
341+ ws_url = _build_ws_url (
342+ settings .CENTML_PLATFORM_API_URL , deployment_id , pod_name , shell_type
343+ )
294344 token = auth .get_centml_token ()
295345 cmd_str = " " .join (command )
296346 exit_code = asyncio .run (_exec_session (ws_url , token , cmd_str ))
0 commit comments