akashcli 4.4.1__tar.gz → 4.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {akashcli-4.4.1 → akashcli-4.5.1}/PKG-INFO +1 -1
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/cli.py +54 -24
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/relay.py +277 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli.egg-info/PKG-INFO +1 -1
- {akashcli-4.4.1 → akashcli-4.5.1}/pyproject.toml +1 -1
- {akashcli-4.4.1 → akashcli-4.5.1}/README.md +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/__init__.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/auth.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/client.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/config.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/crypto.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/env_manager.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/exceptions.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/exec.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/integrity.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/logger.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/models.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/p2p.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/packages.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/restore.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/snapshot_crypto.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/storage.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/terminal.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/uploader.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli/utils.py +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli.egg-info/SOURCES.txt +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli.egg-info/dependency_links.txt +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli.egg-info/entry_points.txt +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli.egg-info/requires.txt +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/akashcli.egg-info/top_level.txt +0 -0
- {akashcli-4.4.1 → akashcli-4.5.1}/setup.cfg +0 -0
|
@@ -179,9 +179,11 @@ def _print_help():
|
|
|
179
179
|
f" {C}pull{RS} {DM}<CODE>{RS} restore on any machine — no login needed",
|
|
180
180
|
f"",
|
|
181
181
|
f" {DM}REMOTE SHELL{RS}",
|
|
182
|
-
f" {C}shell{RS} {DM}[--device NAME]{RS} open a shell here,
|
|
182
|
+
f" {C}shell{RS} {DM}[--device NAME]{RS} open a shell here, on a device, or local",
|
|
183
|
+
f" {C}shell{RS} {DM}--code A3F7B2{RS} connect via one-time pairing code (no login)",
|
|
183
184
|
f" {C}run{RS} {DM}<CMD> [--device NAME]{RS} run a command locally or on a device",
|
|
184
|
-
f" {C}serve{RS} {DM}[--
|
|
185
|
+
f" {C}serve{RS} {DM}[--share]{RS} share this shell (prints a pairing code)",
|
|
186
|
+
f" {C}serve{RS} {DM}[--label NAME]{RS} register device permanently (needs login)",
|
|
185
187
|
f" {C}devices{RS} {DM}[--revoke NAME]{RS} list or revoke enrolled devices",
|
|
186
188
|
f"",
|
|
187
189
|
f" {DM}FILES{RS}",
|
|
@@ -1142,21 +1144,20 @@ def shell(
|
|
|
1142
1144
|
reauth: bool = typer.Option(False, "--reauth", help="Force re-authentication"),
|
|
1143
1145
|
device: Optional[str] = typer.Option(None, "--device", "-d",
|
|
1144
1146
|
help="Device label to connect to (set up with: akash serve --label NAME)"),
|
|
1147
|
+
code: Optional[str] = typer.Option(None, "--code", "-c",
|
|
1148
|
+
help="Pairing code from `akash serve` — no login needed"),
|
|
1145
1149
|
):
|
|
1146
|
-
"""Open a shell — local,
|
|
1150
|
+
"""Open a shell — local, on a device, or via a one-time pairing code.
|
|
1147
1151
|
|
|
1148
1152
|
No login needed:
|
|
1149
1153
|
akash shell open a local bash shell
|
|
1154
|
+
akash serve get a pairing code, share it
|
|
1155
|
+
akash shell --code A3F7B2 connect using that code
|
|
1150
1156
|
|
|
1151
|
-
With login
|
|
1152
|
-
akash shell full server shell
|
|
1153
|
-
akash shell --device termux relay shell on
|
|
1154
|
-
akash shell --device
|
|
1155
|
-
akash shell --device auto-pick (works if only 1 device enrolled)
|
|
1156
|
-
|
|
1157
|
-
To make a device reachable:
|
|
1158
|
-
1. On that device: akash login --label termux && akash serve
|
|
1159
|
-
2. From anywhere: akash shell --device termux
|
|
1157
|
+
With login:
|
|
1158
|
+
akash shell full server shell
|
|
1159
|
+
akash shell --device termux relay shell on enrolled device
|
|
1160
|
+
akash shell --device auto-pick enrolled device
|
|
1160
1161
|
"""
|
|
1161
1162
|
from .exec import acquire_exec_token, run_shell, _clear_cached_token
|
|
1162
1163
|
import shutil as _sh, subprocess as _sp
|
|
@@ -1164,13 +1165,30 @@ def shell(
|
|
|
1164
1165
|
try:
|
|
1165
1166
|
api_key, base = _resolve_client(url)
|
|
1166
1167
|
|
|
1168
|
+
# ── Pairing code path (no login needed) ───────────────────────────
|
|
1169
|
+
if code is not None:
|
|
1170
|
+
from .relay import guest_connect_shell
|
|
1171
|
+
c = code.strip().upper()
|
|
1172
|
+
_p()
|
|
1173
|
+
_info(f"connecting with code {_cy(c)}…")
|
|
1174
|
+
try:
|
|
1175
|
+
exit_code = guest_connect_shell(base, c)
|
|
1176
|
+
except RuntimeError as e:
|
|
1177
|
+
_err(str(e))
|
|
1178
|
+
raise typer.Exit(1)
|
|
1179
|
+
_p()
|
|
1180
|
+
_info("session closed")
|
|
1181
|
+
if exit_code != 0:
|
|
1182
|
+
raise typer.Exit(exit_code)
|
|
1183
|
+
return
|
|
1184
|
+
|
|
1167
1185
|
# ── --device flag but no key ──────────────────────────────────────
|
|
1168
1186
|
if device is not None and not api_key:
|
|
1169
1187
|
_warn("relay requires an API key.")
|
|
1170
1188
|
_p()
|
|
1171
1189
|
_info(f"To connect to {_cy(device.strip() or 'a device')} you need to log in first:")
|
|
1172
1190
|
_info(f" {_cy('akash login')} save your API key on this machine")
|
|
1173
|
-
_info(f" {_cy('akash serve')}
|
|
1191
|
+
_info(f" Or use a pairing code: {_cy('akash serve')} → {_cy('akash shell --code <CODE>')}")
|
|
1174
1192
|
_p()
|
|
1175
1193
|
raise typer.Exit(1)
|
|
1176
1194
|
|
|
@@ -1337,21 +1355,33 @@ def serve(
|
|
|
1337
1355
|
daemon: bool = typer.Option(False, "--daemon", help="Run in background"),
|
|
1338
1356
|
status: bool = typer.Option(False, "--status", help="Show serve daemon status"),
|
|
1339
1357
|
stop: bool = typer.Option(False, "--stop", help="Stop background serve daemon"),
|
|
1358
|
+
share: bool = typer.Option(False, "--share", help="Guest mode: get a one-time code, no login needed"),
|
|
1340
1359
|
):
|
|
1341
|
-
"""Make this device reachable for remote shell
|
|
1360
|
+
"""Make this device reachable for remote shell — with or without login.
|
|
1342
1361
|
|
|
1343
|
-
|
|
1344
|
-
|
|
1362
|
+
No login needed (one-time pairing code):
|
|
1363
|
+
akash serve --share prints a code, share it with anyone
|
|
1364
|
+
akash shell --code A3F7B2 they connect using that code
|
|
1345
1365
|
|
|
1346
|
-
|
|
1347
|
-
akash serve
|
|
1366
|
+
With login (permanent, device enrolled):
|
|
1367
|
+
akash serve register this device permanently
|
|
1348
1368
|
akash serve --daemon run as background daemon
|
|
1349
|
-
akash serve --status check if daemon is running
|
|
1350
|
-
akash serve --stop stop the daemon
|
|
1351
1369
|
akash serve --label myphone use a custom label
|
|
1370
|
+
akash serve --status check daemon status
|
|
1371
|
+
akash serve --stop stop daemon
|
|
1352
1372
|
"""
|
|
1353
1373
|
from .relay import serve as _serve, serve_status, serve_stop
|
|
1354
1374
|
|
|
1375
|
+
# ── Guest / no-login mode ─────────────────────────────────────────────
|
|
1376
|
+
if share:
|
|
1377
|
+
from .relay import guest_serve
|
|
1378
|
+
_, base = _resolve_client(url)
|
|
1379
|
+
try:
|
|
1380
|
+
guest_serve(base)
|
|
1381
|
+
except KeyboardInterrupt:
|
|
1382
|
+
pass
|
|
1383
|
+
return
|
|
1384
|
+
|
|
1355
1385
|
if status:
|
|
1356
1386
|
st = serve_status()
|
|
1357
1387
|
if st["running"]:
|
|
@@ -1380,11 +1410,11 @@ def serve(
|
|
|
1380
1410
|
|
|
1381
1411
|
if not api_key:
|
|
1382
1412
|
_p()
|
|
1383
|
-
_warn("not logged in
|
|
1413
|
+
_warn("not logged in.")
|
|
1414
|
+
_p()
|
|
1415
|
+
_info(f"For a quick one-time session (no login): {_cy('akash serve --share')}")
|
|
1384
1416
|
_p()
|
|
1385
|
-
_info("
|
|
1386
|
-
print(f" {_cy('akash login --label myphone')} {_DM}# name this device{_RS}")
|
|
1387
|
-
print(f" {_cy('akash serve')} {_DM}# then run this{_RS}")
|
|
1417
|
+
_info(f"To set up permanently: {_cy('akash login --label myphone')} then {_cy('akash serve')}")
|
|
1388
1418
|
_p()
|
|
1389
1419
|
raise typer.Exit(1)
|
|
1390
1420
|
|
|
@@ -535,3 +535,280 @@ def serve_stop() -> bool:
|
|
|
535
535
|
return True
|
|
536
536
|
except Exception:
|
|
537
537
|
return False
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
# ── Guest relay (no API key — pairing code) ───────────────────────────────────
|
|
541
|
+
|
|
542
|
+
def _guest_code_url(base: str) -> str:
|
|
543
|
+
return f"{base.rstrip('/')}/api/guest/relay/code"
|
|
544
|
+
|
|
545
|
+
def _guest_host_url(base: str, code: str) -> str:
|
|
546
|
+
return f"{_ws_base(base)}/api/guest/relay/host?code={code}"
|
|
547
|
+
|
|
548
|
+
def _guest_join_url(base: str, code: str) -> str:
|
|
549
|
+
return f"{_ws_base(base)}/api/guest/relay/join?code={code}"
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def guest_serve(base_url: str) -> None:
|
|
553
|
+
"""
|
|
554
|
+
Host a shell session with no API key.
|
|
555
|
+
Requests a pairing code from the server, prints it, then waits for
|
|
556
|
+
someone to connect with: akash shell --code <CODE>
|
|
557
|
+
|
|
558
|
+
Security: every incoming connection shows the guest IP + fingerprint
|
|
559
|
+
and asks the host to approve before the shell is bridged.
|
|
560
|
+
"""
|
|
561
|
+
import httpx
|
|
562
|
+
import websockets.sync.client as _ws
|
|
563
|
+
|
|
564
|
+
# 1. Get a pairing code
|
|
565
|
+
try:
|
|
566
|
+
r = httpx.post(_guest_code_url(base_url), timeout=10)
|
|
567
|
+
r.raise_for_status()
|
|
568
|
+
code = r.json()["code"]
|
|
569
|
+
ttl = r.json().get("expires_in", 600)
|
|
570
|
+
except Exception as e:
|
|
571
|
+
print(f" {_R}✗{_RS} could not get pairing code: {e}", file=sys.stderr)
|
|
572
|
+
return
|
|
573
|
+
|
|
574
|
+
print()
|
|
575
|
+
print(f" {_G}◉{_RS} guest relay ready")
|
|
576
|
+
print()
|
|
577
|
+
print(f" {_DM}Pairing code:{_RS} {_C}{code}{_RS}")
|
|
578
|
+
print()
|
|
579
|
+
print(f" {_DM}On another machine:{_RS} {_C}akash shell --code {code}{_RS}")
|
|
580
|
+
print()
|
|
581
|
+
print(f" {_DM}Code expires in {ttl // 60} min · Ctrl-C to stop{_RS}")
|
|
582
|
+
print(f" {_DM}You will be asked to approve each connection before it gets shell access.{_RS}")
|
|
583
|
+
print()
|
|
584
|
+
|
|
585
|
+
# 2. Connect to host endpoint — handle knock/allow/deny then serve shell
|
|
586
|
+
while True:
|
|
587
|
+
try:
|
|
588
|
+
ws_url = _guest_host_url(base_url, code)
|
|
589
|
+
with _ws.connect(ws_url, open_timeout=15, close_timeout=5) as ws:
|
|
590
|
+
ack = json.loads(ws.recv())
|
|
591
|
+
if ack.get("type") == "error":
|
|
592
|
+
print(f" {_R}✗{_RS} {ack.get('message')}", file=sys.stderr)
|
|
593
|
+
return
|
|
594
|
+
|
|
595
|
+
print(f" {_DM}waiting for connection…{_RS}", flush=True)
|
|
596
|
+
|
|
597
|
+
# Wait for either a knock (someone wants in) or a ping
|
|
598
|
+
while True:
|
|
599
|
+
try:
|
|
600
|
+
raw = ws.recv(timeout=35)
|
|
601
|
+
except Exception:
|
|
602
|
+
# Timeout or disconnect — break to reconnect loop
|
|
603
|
+
break
|
|
604
|
+
frame = json.loads(raw)
|
|
605
|
+
ftype = frame.get("type")
|
|
606
|
+
|
|
607
|
+
if ftype == "ping":
|
|
608
|
+
ws.send(json.dumps({"type": "pong"}))
|
|
609
|
+
continue
|
|
610
|
+
|
|
611
|
+
if ftype == "knock":
|
|
612
|
+
guest_ip = frame.get("ip", "unknown")
|
|
613
|
+
fp = frame.get("fingerprint", "????????")
|
|
614
|
+
print()
|
|
615
|
+
print(f" {_Y}⚡{_RS} Incoming connection request")
|
|
616
|
+
print(f" {_DM}From:{_RS} {_C}{guest_ip}{_RS}")
|
|
617
|
+
print(f" {_DM}Fingerprint:{_RS} {_C}{fp}{_RS}")
|
|
618
|
+
print()
|
|
619
|
+
|
|
620
|
+
# Prompt host — default is N (deny)
|
|
621
|
+
try:
|
|
622
|
+
sys.stdout.write(f" Allow? [y/N]: ")
|
|
623
|
+
sys.stdout.flush()
|
|
624
|
+
answer = sys.stdin.readline().strip().lower()
|
|
625
|
+
except (EOFError, KeyboardInterrupt):
|
|
626
|
+
answer = "n"
|
|
627
|
+
|
|
628
|
+
if answer == "y":
|
|
629
|
+
ws.send(json.dumps({"type": "allow"}))
|
|
630
|
+
print(f" {_G}✓{_RS} connection allowed — starting shell…")
|
|
631
|
+
print()
|
|
632
|
+
# Now serve shell frames
|
|
633
|
+
_run_relay_device(ws, f"guest:{code}")
|
|
634
|
+
print(f"\n {_DM}session ended — waiting for next connection…{_RS}\n")
|
|
635
|
+
else:
|
|
636
|
+
ws.send(json.dumps({"type": "deny"}))
|
|
637
|
+
print(f" {_R}✗{_RS} connection denied")
|
|
638
|
+
print()
|
|
639
|
+
# Loop back — still waiting on same code
|
|
640
|
+
continue
|
|
641
|
+
|
|
642
|
+
# Any other frame — ignore and keep waiting
|
|
643
|
+
continue
|
|
644
|
+
|
|
645
|
+
except KeyboardInterrupt:
|
|
646
|
+
print(f"\n {_DM}guest serve stopped{_RS}")
|
|
647
|
+
break
|
|
648
|
+
except Exception as exc:
|
|
649
|
+
print(f" {_Y}!{_RS} relay disconnected ({exc}) — reconnecting in 3s…")
|
|
650
|
+
time.sleep(3)
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def guest_connect_shell(base_url: str, code: str) -> int:
|
|
654
|
+
"""
|
|
655
|
+
Connect to a guest host by pairing code — no API key needed.
|
|
656
|
+
Handles the approval handshake: shows a spinner while the host decides,
|
|
657
|
+
then opens a full PTY shell if approved.
|
|
658
|
+
Returns the remote exit code.
|
|
659
|
+
"""
|
|
660
|
+
import websockets.sync.client as _ws
|
|
661
|
+
import select as _sel, signal
|
|
662
|
+
|
|
663
|
+
code = code.strip().upper()
|
|
664
|
+
ws_url = _guest_join_url(base_url, code)
|
|
665
|
+
|
|
666
|
+
from .exec import _get_terminal_size
|
|
667
|
+
rows, cols = _get_terminal_size()
|
|
668
|
+
|
|
669
|
+
_raw_ok = False
|
|
670
|
+
_old_settings = None
|
|
671
|
+
_stdin_fd = -1
|
|
672
|
+
try:
|
|
673
|
+
import tty, termios as _term
|
|
674
|
+
_stdin_fd = sys.stdin.fileno()
|
|
675
|
+
_old_settings = _term.tcgetattr(_stdin_fd)
|
|
676
|
+
tty.setraw(_stdin_fd)
|
|
677
|
+
_raw_ok = True
|
|
678
|
+
except Exception:
|
|
679
|
+
pass
|
|
680
|
+
|
|
681
|
+
def _restore():
|
|
682
|
+
if _raw_ok and _old_settings is not None:
|
|
683
|
+
try:
|
|
684
|
+
import termios as _t
|
|
685
|
+
_t.tcsetattr(_stdin_fd, _t.TCSADRAIN, _old_settings)
|
|
686
|
+
except Exception:
|
|
687
|
+
pass
|
|
688
|
+
|
|
689
|
+
exit_code = 1
|
|
690
|
+
try:
|
|
691
|
+
with _ws.connect(ws_url, open_timeout=15, close_timeout=10) as ws:
|
|
692
|
+
# First frame: could be error, waiting_approval, or connected
|
|
693
|
+
first = json.loads(ws.recv())
|
|
694
|
+
ftype = first.get("type")
|
|
695
|
+
|
|
696
|
+
if ftype == "error":
|
|
697
|
+
_restore()
|
|
698
|
+
raise RuntimeError(first.get("message", "relay error"))
|
|
699
|
+
|
|
700
|
+
if ftype == "waiting_approval":
|
|
701
|
+
fp = first.get("fingerprint", "")
|
|
702
|
+
_restore() # restore terminal so we can print cleanly
|
|
703
|
+
print(f" {_Y}…{_RS} waiting for host to approve this connection…")
|
|
704
|
+
if fp:
|
|
705
|
+
print(f" {_DM}Your fingerprint: {_C}{fp}{_RS}")
|
|
706
|
+
print(f" {_DM}(host has 30 seconds to respond){_RS}")
|
|
707
|
+
print()
|
|
708
|
+
# Wait for next frame: connected or denied
|
|
709
|
+
try:
|
|
710
|
+
second = json.loads(ws.recv(timeout=35))
|
|
711
|
+
except Exception:
|
|
712
|
+
raise RuntimeError("Timed out waiting for host approval.")
|
|
713
|
+
if second.get("type") == "denied":
|
|
714
|
+
raise RuntimeError("Host declined the connection.")
|
|
715
|
+
if second.get("type") != "connected":
|
|
716
|
+
raise RuntimeError(second.get("message", "unexpected response from host"))
|
|
717
|
+
# Re-enter raw mode for the PTY session
|
|
718
|
+
try:
|
|
719
|
+
import tty as _tty2, termios as _term2
|
|
720
|
+
_old_settings = _term2.tcgetattr(_stdin_fd)
|
|
721
|
+
_tty2.setraw(_stdin_fd)
|
|
722
|
+
_raw_ok = True
|
|
723
|
+
except Exception:
|
|
724
|
+
pass
|
|
725
|
+
elif ftype != "connected":
|
|
726
|
+
_restore()
|
|
727
|
+
raise RuntimeError(first.get("message", "unexpected server response"))
|
|
728
|
+
|
|
729
|
+
ws.send(json.dumps({
|
|
730
|
+
"type": "pty_run",
|
|
731
|
+
"command": os.environ.get("SHELL", "/bin/bash"),
|
|
732
|
+
"rows": rows, "cols": cols,
|
|
733
|
+
}))
|
|
734
|
+
|
|
735
|
+
stop = threading.Event()
|
|
736
|
+
|
|
737
|
+
def _stdin_pump():
|
|
738
|
+
try:
|
|
739
|
+
while not stop.is_set():
|
|
740
|
+
if _stdin_fd >= 0:
|
|
741
|
+
r = _sel.select([_stdin_fd], [], [], 0.05)[0]
|
|
742
|
+
if not r:
|
|
743
|
+
continue
|
|
744
|
+
chunk = os.read(_stdin_fd, 256)
|
|
745
|
+
else:
|
|
746
|
+
chunk = sys.stdin.buffer.read(1)
|
|
747
|
+
if not chunk:
|
|
748
|
+
break
|
|
749
|
+
ws.send(json.dumps({
|
|
750
|
+
"type": "pty_in",
|
|
751
|
+
"data": base64.b64encode(chunk).decode(),
|
|
752
|
+
}))
|
|
753
|
+
except Exception:
|
|
754
|
+
pass
|
|
755
|
+
|
|
756
|
+
def _on_resize(signum, _frame):
|
|
757
|
+
r, c = _get_terminal_size()
|
|
758
|
+
try:
|
|
759
|
+
ws.send(json.dumps({"type": "resize", "rows": r, "cols": c}))
|
|
760
|
+
except Exception:
|
|
761
|
+
pass
|
|
762
|
+
try:
|
|
763
|
+
signal.signal(signal.SIGWINCH, _on_resize)
|
|
764
|
+
except Exception:
|
|
765
|
+
pass
|
|
766
|
+
|
|
767
|
+
t = threading.Thread(target=_stdin_pump, daemon=True)
|
|
768
|
+
t.start()
|
|
769
|
+
|
|
770
|
+
while True:
|
|
771
|
+
try:
|
|
772
|
+
raw = ws.recv()
|
|
773
|
+
except Exception:
|
|
774
|
+
break
|
|
775
|
+
try:
|
|
776
|
+
frame = json.loads(raw)
|
|
777
|
+
except Exception:
|
|
778
|
+
continue
|
|
779
|
+
ftype = frame.get("type")
|
|
780
|
+
if ftype == "pty_out":
|
|
781
|
+
data = base64.b64decode(frame["data"])
|
|
782
|
+
if data:
|
|
783
|
+
sys.stdout.buffer.write(data)
|
|
784
|
+
sys.stdout.buffer.flush()
|
|
785
|
+
elif ftype == "exit":
|
|
786
|
+
exit_code = frame.get("code", 0)
|
|
787
|
+
stop.set()
|
|
788
|
+
break
|
|
789
|
+
elif ftype == "error":
|
|
790
|
+
stop.set()
|
|
791
|
+
_restore()
|
|
792
|
+
print(f"\r\n{_R}[error]{_RS} {frame.get('message','?')}", file=sys.stderr)
|
|
793
|
+
exit_code = 1
|
|
794
|
+
break
|
|
795
|
+
elif ftype == "disconnected":
|
|
796
|
+
stop.set()
|
|
797
|
+
break
|
|
798
|
+
elif ftype == "ping":
|
|
799
|
+
try:
|
|
800
|
+
ws.send(json.dumps({"type": "pong"}))
|
|
801
|
+
except Exception:
|
|
802
|
+
break
|
|
803
|
+
|
|
804
|
+
except KeyboardInterrupt:
|
|
805
|
+
pass
|
|
806
|
+
finally:
|
|
807
|
+
stop.set() if 'stop' in dir() else None
|
|
808
|
+
_restore()
|
|
809
|
+
try:
|
|
810
|
+
signal.signal(signal.SIGWINCH, signal.SIG_DFL)
|
|
811
|
+
except Exception:
|
|
812
|
+
pass
|
|
813
|
+
|
|
814
|
+
return exit_code
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|