akashcli 4.5.8__tar.gz → 4.6.0__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.5.8 → akashcli-4.6.0}/PKG-INFO +1 -1
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/_net.py +4 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/relay.py +120 -188
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli.egg-info/PKG-INFO +1 -1
- {akashcli-4.5.8 → akashcli-4.6.0}/pyproject.toml +1 -1
- {akashcli-4.5.8 → akashcli-4.6.0}/README.md +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/__init__.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/auth.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/cli.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/client.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/config.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/crypto.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/env_manager.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/exceptions.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/exec.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/integrity.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/logger.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/models.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/p2p.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/packages.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/restore.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/snapshot_crypto.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/storage.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/terminal.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/uploader.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli/utils.py +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli.egg-info/SOURCES.txt +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli.egg-info/dependency_links.txt +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli.egg-info/entry_points.txt +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli.egg-info/requires.txt +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/akashcli.egg-info/top_level.txt +0 -0
- {akashcli-4.5.8 → akashcli-4.6.0}/setup.cfg +0 -0
|
@@ -113,6 +113,10 @@ def ws_connect(url: str, **kwargs):
|
|
|
113
113
|
sock_timeout = kwargs.pop("_sock_timeout", 15)
|
|
114
114
|
sock = make_socket(host, port, timeout=sock_timeout)
|
|
115
115
|
|
|
116
|
+
# legacy=True: allow calling connect() outside of a `with` block.
|
|
117
|
+
# websockets ≥ 13 requires context manager usage by default;
|
|
118
|
+
# legacy mode silences the deprecation and preserves the old API.
|
|
119
|
+
kwargs.setdefault("legacy", True)
|
|
116
120
|
if use_tls:
|
|
117
121
|
return _wsc.connect(url, sock=sock, ssl=ssl_context(), **kwargs)
|
|
118
122
|
return _wsc.connect(url, sock=sock, **kwargs)
|
|
@@ -805,19 +805,19 @@ def _sh_history(history: list) -> None:
|
|
|
805
805
|
def guest_connect_shell(base_url: str, code: str) -> int:
|
|
806
806
|
"""
|
|
807
807
|
Connect to a guest host via pairing code.
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
Returns the
|
|
808
|
+
Opens a real PTY tunnel to the host's shell — identical to connect_shell()
|
|
809
|
+
but uses the guest relay (no API key). Full interactive terminal: nano,
|
|
810
|
+
vim, htop, etc. all work. DevOS banner is printed before entering raw mode.
|
|
811
|
+
Returns the remote exit code.
|
|
812
812
|
"""
|
|
813
|
-
import
|
|
814
|
-
import
|
|
813
|
+
import select as _sel
|
|
814
|
+
import signal
|
|
815
815
|
|
|
816
816
|
code = code.strip().upper()
|
|
817
817
|
ws_url = _guest_join_url(base_url, code)
|
|
818
818
|
|
|
819
|
-
# ── Connect & handle approval
|
|
820
|
-
print(f"\n {_C}◈{_RS} {_DM}connecting…{_RS}")
|
|
819
|
+
# ── Connect & handle approval (before entering raw mode) ─────────────────
|
|
820
|
+
print(f"\n {_C}◈{_RS} {_DM}connecting…{_RS}", flush=True)
|
|
821
821
|
try:
|
|
822
822
|
ws = _ws_connect(ws_url, open_timeout=15, close_timeout=10)
|
|
823
823
|
except Exception as e:
|
|
@@ -832,10 +832,10 @@ def guest_connect_shell(base_url: str, code: str) -> int:
|
|
|
832
832
|
|
|
833
833
|
if ftype == "waiting_approval":
|
|
834
834
|
fp = first.get("fingerprint", "")
|
|
835
|
-
print(f" {_C}◈{_RS} {_DM}waiting for host to approve…{_RS}")
|
|
835
|
+
print(f" {_C}◈{_RS} {_DM}waiting for host to approve…{_RS}", flush=True)
|
|
836
836
|
if fp:
|
|
837
|
-
print(f" {_DM}fingerprint {_C}{fp}{
|
|
838
|
-
print(f" {_DM}host has 30 s to respond{_RS}\n")
|
|
837
|
+
print(f" {_DM}fingerprint {_C}{fp}{_RS}", flush=True)
|
|
838
|
+
print(f" {_DM}host has 30 s to respond{_RS}\n", flush=True)
|
|
839
839
|
try:
|
|
840
840
|
second = json.loads(ws.recv(timeout=35))
|
|
841
841
|
except Exception:
|
|
@@ -851,213 +851,145 @@ def guest_connect_shell(base_url: str, code: str) -> int:
|
|
|
851
851
|
ws.close()
|
|
852
852
|
raise RuntimeError(first.get("message", "unexpected server response"))
|
|
853
853
|
|
|
854
|
-
# ──
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
identity = os.environ.get("USER") or os.environ.get("LOGNAME") or "akash"
|
|
854
|
+
# ── Print DevOS banner (before raw mode) ──────────────────────────────────
|
|
855
|
+
identity = os.environ.get("USER") or os.environ.get("LOGNAME") or "akash"
|
|
856
|
+
print(f"\n {_G}◉{_RS} {_C}akash shell{_RS} "
|
|
857
|
+
f"{_DM}code {code} · Ctrl-D or `exit` to quit{_RS}\n", flush=True)
|
|
859
858
|
|
|
860
|
-
#
|
|
861
|
-
hist_file = _AKASH_DIR / "shell_history"
|
|
862
|
-
_AKASH_DIR.mkdir(parents=True, exist_ok=True)
|
|
859
|
+
# ── Get terminal size ─────────────────────────────────────────────────────
|
|
863
860
|
try:
|
|
864
|
-
|
|
861
|
+
import shutil as _sh
|
|
862
|
+
cols, rows = _sh.get_terminal_size(fallback=(80, 24))
|
|
865
863
|
except Exception:
|
|
866
|
-
|
|
867
|
-
_rl.set_history_length(500)
|
|
864
|
+
rows, cols = 24, 80
|
|
868
865
|
|
|
869
|
-
# ──
|
|
870
|
-
|
|
871
|
-
|
|
866
|
+
# ── Enter raw mode ────────────────────────────────────────────────────────
|
|
867
|
+
_raw_ok = False
|
|
868
|
+
_old_settings = None
|
|
869
|
+
_stdin_fd = -1
|
|
870
|
+
try:
|
|
871
|
+
import tty as _tty, termios as _term
|
|
872
|
+
_stdin_fd = sys.stdin.fileno()
|
|
873
|
+
_old_settings = _term.tcgetattr(_stdin_fd)
|
|
874
|
+
_tty.setraw(_stdin_fd)
|
|
875
|
+
_raw_ok = True
|
|
876
|
+
except Exception:
|
|
877
|
+
pass
|
|
872
878
|
|
|
873
|
-
def
|
|
874
|
-
|
|
879
|
+
def _restore():
|
|
880
|
+
if _raw_ok and _old_settings is not None:
|
|
875
881
|
try:
|
|
876
|
-
|
|
877
|
-
|
|
882
|
+
import termios as _t
|
|
883
|
+
_t.tcsetattr(_stdin_fd, _t.TCSADRAIN, _old_settings)
|
|
878
884
|
except Exception:
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
885
|
+
pass
|
|
886
|
+
|
|
887
|
+
exit_code = 0
|
|
888
|
+
stop = threading.Event()
|
|
889
|
+
|
|
890
|
+
# ── Tell host to open a PTY shell ─────────────────────────────────────────
|
|
891
|
+
try:
|
|
892
|
+
ws.send(json.dumps({
|
|
893
|
+
"type": "pty_run",
|
|
894
|
+
"command": os.environ.get("SHELL", "/bin/bash"),
|
|
895
|
+
"rows": rows,
|
|
896
|
+
"cols": cols,
|
|
897
|
+
}))
|
|
898
|
+
except Exception as e:
|
|
899
|
+
_restore()
|
|
900
|
+
ws.close()
|
|
901
|
+
raise RuntimeError(f"Failed to start remote shell: {e}")
|
|
902
|
+
|
|
903
|
+
# ── Thread: local stdin → WS ──────────────────────────────────────────────
|
|
904
|
+
def _stdin_pump():
|
|
905
|
+
try:
|
|
906
|
+
while not stop.is_set():
|
|
907
|
+
if _stdin_fd >= 0:
|
|
908
|
+
r = _sel.select([_stdin_fd], [], [], 0.02)[0]
|
|
909
|
+
if not r:
|
|
910
|
+
continue
|
|
911
|
+
chunk = os.read(_stdin_fd, 4096)
|
|
912
|
+
else:
|
|
913
|
+
chunk = sys.stdin.buffer.read(1)
|
|
914
|
+
if not chunk:
|
|
915
|
+
break
|
|
916
|
+
ws.send(json.dumps({
|
|
917
|
+
"type": "pty_in",
|
|
918
|
+
"data": base64.b64encode(chunk).decode(),
|
|
919
|
+
}))
|
|
920
|
+
except Exception:
|
|
921
|
+
pass
|
|
922
|
+
|
|
923
|
+
# ── SIGWINCH → resize frame ───────────────────────────────────────────────
|
|
924
|
+
def _on_resize(signum, _frame):
|
|
925
|
+
try:
|
|
926
|
+
import shutil as _sh2
|
|
927
|
+
c2, r2 = _sh2.get_terminal_size(fallback=(80, 24))
|
|
928
|
+
ws.send(json.dumps({"type": "resize", "rows": r2, "cols": c2}))
|
|
929
|
+
except Exception:
|
|
930
|
+
pass
|
|
931
|
+
try:
|
|
932
|
+
signal.signal(signal.SIGWINCH, _on_resize)
|
|
933
|
+
except Exception:
|
|
934
|
+
pass
|
|
882
935
|
|
|
883
|
-
|
|
884
|
-
|
|
936
|
+
t = threading.Thread(target=_stdin_pump, daemon=True)
|
|
937
|
+
t.start()
|
|
885
938
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
ec = 0
|
|
890
|
-
new_cwd = cwd
|
|
891
|
-
deadline = time.time() + timeout
|
|
892
|
-
while time.time() < deadline:
|
|
939
|
+
# ── Main loop: WS → local stdout (raw PTY output) ────────────────────────
|
|
940
|
+
try:
|
|
941
|
+
while True:
|
|
893
942
|
try:
|
|
894
|
-
raw =
|
|
895
|
-
except
|
|
896
|
-
continue
|
|
897
|
-
if raw is None:
|
|
943
|
+
raw = ws.recv()
|
|
944
|
+
except Exception:
|
|
898
945
|
break
|
|
899
946
|
try:
|
|
900
947
|
frame = json.loads(raw)
|
|
901
948
|
except Exception:
|
|
902
949
|
continue
|
|
903
|
-
|
|
904
|
-
if
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
950
|
+
ftype = frame.get("type")
|
|
951
|
+
if ftype == "pty_out":
|
|
952
|
+
data = base64.b64decode(frame["data"])
|
|
953
|
+
if data:
|
|
954
|
+
sys.stdout.buffer.write(data)
|
|
955
|
+
sys.stdout.buffer.flush()
|
|
956
|
+
elif ftype == "exit":
|
|
957
|
+
exit_code = frame.get("code", 0)
|
|
958
|
+
stop.set()
|
|
959
|
+
break
|
|
960
|
+
elif ftype == "error":
|
|
961
|
+
stop.set()
|
|
962
|
+
_restore()
|
|
963
|
+
print(f"\r\n{_R}[error]{_RS} {frame.get('message','?')}\r\n",
|
|
964
|
+
file=sys.stderr, flush=True)
|
|
965
|
+
exit_code = 1
|
|
918
966
|
break
|
|
919
|
-
elif
|
|
920
|
-
|
|
921
|
-
ec = 1
|
|
967
|
+
elif ftype == "disconnected":
|
|
968
|
+
stop.set()
|
|
922
969
|
break
|
|
923
|
-
elif
|
|
970
|
+
elif ftype == "ping":
|
|
924
971
|
try:
|
|
925
972
|
ws.send(json.dumps({"type": "pong"}))
|
|
926
973
|
except Exception:
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
# ── Session banner ────────────────────────────────────────────────────────
|
|
931
|
-
print()
|
|
932
|
-
print(f" {_G}◉{_RS} {_C}akash shell{_RS} {_DM}code {code} · :help for commands · :exit to quit{_RS}")
|
|
933
|
-
print()
|
|
934
|
-
|
|
935
|
-
# ── REPL loop ─────────────────────────────────────────────────────────────
|
|
936
|
-
last_cmd = ""
|
|
937
|
-
try:
|
|
938
|
-
while True:
|
|
939
|
-
_sh_draw_header(identity, cwd)
|
|
940
|
-
try:
|
|
941
|
-
cmd = input(_sh_input_prompt()).strip()
|
|
942
|
-
except (EOFError, KeyboardInterrupt):
|
|
943
|
-
print()
|
|
944
|
-
break
|
|
945
|
-
|
|
946
|
-
if not cmd:
|
|
947
|
-
continue
|
|
948
|
-
|
|
949
|
-
# ── Built-in commands ─────────────────────────────────────────────
|
|
950
|
-
if cmd == ":exit" or cmd == "exit":
|
|
951
|
-
break
|
|
952
|
-
if cmd == ":clear" or cmd == "clear":
|
|
953
|
-
print("\033[2J\033[H", end="", flush=True)
|
|
954
|
-
continue
|
|
955
|
-
if cmd == ":help":
|
|
956
|
-
_sh_help()
|
|
957
|
-
continue
|
|
958
|
-
if cmd == ":h":
|
|
959
|
-
_sh_history(history)
|
|
960
|
-
continue
|
|
961
|
-
if cmd == ":status":
|
|
962
|
-
_sh_status(code, cwd, history)
|
|
963
|
-
continue
|
|
964
|
-
if cmd == ":r":
|
|
965
|
-
if not last_cmd:
|
|
966
|
-
print(f" {_Y}!{_RS} no previous command\n")
|
|
967
|
-
continue
|
|
968
|
-
cmd = last_cmd
|
|
969
|
-
print(f" {_DM}re-running:{_RS} {cmd}")
|
|
970
|
-
if cmd.startswith(":upload "):
|
|
971
|
-
fpath = cmd[8:].strip()
|
|
972
|
-
_p = print
|
|
973
|
-
_p(f" {_DM}uploading {fpath}…{_RS}")
|
|
974
|
-
try:
|
|
975
|
-
from .client import AkashClient
|
|
976
|
-
import os as _os
|
|
977
|
-
cfg = __import__("akashcli.config", fromlist=["ConfigManager"]).ConfigManager
|
|
978
|
-
_cfg = cfg.load()
|
|
979
|
-
api_key = _os.getenv("AKASH_API_KEY") or _cfg.get("api_key")
|
|
980
|
-
if not api_key:
|
|
981
|
-
print(f" {_R}✗{_RS} no API key — run akash login first\n")
|
|
982
|
-
continue
|
|
983
|
-
client = AkashClient(api_key=api_key)
|
|
984
|
-
result = client.upload_file(fpath)
|
|
985
|
-
print(f" {_G}✓{_RS} uploaded {_C}{result.file_code}{_RS} {_DM}{result.url}{_RS}\n")
|
|
986
|
-
except Exception as ue:
|
|
987
|
-
print(f" {_R}✗{_RS} upload failed: {ue}\n")
|
|
988
|
-
continue
|
|
989
|
-
|
|
990
|
-
# ── Remote command ────────────────────────────────────────────────
|
|
991
|
-
history.append(cmd)
|
|
992
|
-
last_cmd = cmd
|
|
993
|
-
try:
|
|
994
|
-
_rl.add_history(cmd)
|
|
995
|
-
except Exception:
|
|
996
|
-
pass
|
|
997
|
-
|
|
998
|
-
t0 = time.monotonic()
|
|
999
|
-
# Send as `run` — host device executes via bash and streams stdout back.
|
|
1000
|
-
# We always prefix with `cd <cwd>` so state persists across commands.
|
|
1001
|
-
# For `cd` commands we also capture $PWD to update our local cwd tracker.
|
|
1002
|
-
if cmd.startswith("cd ") or cmd == "cd":
|
|
1003
|
-
# Run cd inside current cwd, then print new $PWD as sentinel
|
|
1004
|
-
actual_cmd = (
|
|
1005
|
-
f'cd {json.dumps(cwd)} 2>/dev/null; '
|
|
1006
|
-
f'{cmd}; '
|
|
1007
|
-
f'echo "__AKASH_CWD__:$PWD"'
|
|
1008
|
-
)
|
|
1009
|
-
else:
|
|
1010
|
-
actual_cmd = (
|
|
1011
|
-
f'cd {json.dumps(cwd)} 2>/dev/null && '
|
|
1012
|
-
f'{{ {cmd}; }}; '
|
|
1013
|
-
f'echo "__AKASH_EXIT__:$?"'
|
|
1014
|
-
)
|
|
1015
|
-
|
|
1016
|
-
try:
|
|
1017
|
-
ws.send(json.dumps({"type": "run", "command": actual_cmd}))
|
|
1018
|
-
except Exception:
|
|
1019
|
-
print(f"\n {_R}✗{_RS} disconnected\n")
|
|
1020
|
-
break
|
|
1021
|
-
|
|
1022
|
-
lines, ec, _ = _flush_queue()
|
|
1023
|
-
elapsed_ms = (time.monotonic() - t0) * 1000
|
|
1024
|
-
|
|
1025
|
-
# Parse cwd tracking lines out of output
|
|
1026
|
-
clean_lines = []
|
|
1027
|
-
for ln in lines:
|
|
1028
|
-
if ln.startswith("__AKASH_CWD__:"):
|
|
1029
|
-
cwd = ln[len("__AKASH_CWD__:"):].strip() or cwd
|
|
1030
|
-
elif ln.startswith("__AKASH_EXIT__:"):
|
|
1031
|
-
try:
|
|
1032
|
-
ec = int(ln[len("__AKASH_EXIT__:"):].strip())
|
|
1033
|
-
except Exception:
|
|
1034
|
-
pass
|
|
1035
|
-
else:
|
|
1036
|
-
clean_lines.append(ln)
|
|
1037
|
-
|
|
1038
|
-
exit_code = ec
|
|
1039
|
-
|
|
1040
|
-
if clean_lines:
|
|
1041
|
-
_sh_inset(clean_lines)
|
|
1042
|
-
else:
|
|
1043
|
-
print()
|
|
1044
|
-
_sh_footer(ec, elapsed_ms)
|
|
1045
|
-
|
|
1046
|
-
except Exception:
|
|
974
|
+
break
|
|
975
|
+
except KeyboardInterrupt:
|
|
1047
976
|
pass
|
|
1048
977
|
finally:
|
|
1049
|
-
|
|
978
|
+
stop.set()
|
|
979
|
+
_restore()
|
|
1050
980
|
try:
|
|
1051
|
-
|
|
981
|
+
signal.signal(signal.SIGWINCH, signal.SIG_DFL)
|
|
1052
982
|
except Exception:
|
|
1053
983
|
pass
|
|
1054
984
|
try:
|
|
1055
|
-
ws.close
|
|
985
|
+
ws.send(json.dumps({"type": "close"}))
|
|
1056
986
|
except Exception:
|
|
1057
987
|
pass
|
|
1058
988
|
try:
|
|
1059
|
-
|
|
989
|
+
ws.close()
|
|
1060
990
|
except Exception:
|
|
1061
991
|
pass
|
|
1062
992
|
|
|
993
|
+
# Print a clean newline after raw mode ends
|
|
994
|
+
print(f"\r\n {_DM}session ended code {exit_code}{_RS}\r\n", flush=True)
|
|
1063
995
|
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
|
|
File without changes
|