akashcli 4.5.9__tar.gz → 4.6.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.5.9 → akashcli-4.6.1}/PKG-INFO +1 -1
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/relay.py +127 -193
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli.egg-info/PKG-INFO +1 -1
- {akashcli-4.5.9 → akashcli-4.6.1}/pyproject.toml +1 -1
- {akashcli-4.5.9 → akashcli-4.6.1}/README.md +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/__init__.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/_net.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/auth.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/cli.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/client.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/config.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/crypto.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/env_manager.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/exceptions.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/exec.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/integrity.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/logger.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/models.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/p2p.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/packages.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/restore.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/snapshot_crypto.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/storage.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/terminal.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/uploader.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli/utils.py +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli.egg-info/SOURCES.txt +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli.egg-info/dependency_links.txt +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli.egg-info/entry_points.txt +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli.egg-info/requires.txt +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/akashcli.egg-info/top_level.txt +0 -0
- {akashcli-4.5.9 → akashcli-4.6.1}/setup.cfg +0 -0
|
@@ -210,10 +210,9 @@ def _handle_pty(ws, frame: dict) -> None:
|
|
|
210
210
|
pass
|
|
211
211
|
|
|
212
212
|
def _child_setup():
|
|
213
|
-
"""Run inside the child process before exec
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
os.setsid()
|
|
213
|
+
"""Run inside the child process before exec.
|
|
214
|
+
start_new_session=True already calls setsid(); we just need to claim
|
|
215
|
+
the slave PTY as the controlling terminal via TIOCSCTTY."""
|
|
217
216
|
try:
|
|
218
217
|
fcntl.ioctl(0, termios.TIOCSCTTY, 0)
|
|
219
218
|
except Exception:
|
|
@@ -222,12 +221,15 @@ def _handle_pty(ws, frame: dict) -> None:
|
|
|
222
221
|
import subprocess as _sp
|
|
223
222
|
shell = command or os.environ.get("SHELL", "/bin/bash")
|
|
224
223
|
|
|
225
|
-
#
|
|
224
|
+
# Use start_new_session=True so the child is a proper session leader
|
|
225
|
+
# (equivalent to setsid()), then TIOCSCTTY in preexec_fn attaches the
|
|
226
|
+
# slave PTY as the controlling terminal → bash gets full job control.
|
|
226
227
|
try:
|
|
227
228
|
proc = _sp.Popen(
|
|
228
229
|
[shell],
|
|
229
230
|
stdin=slave_fd, stdout=slave_fd, stderr=slave_fd,
|
|
230
231
|
close_fds=True,
|
|
232
|
+
start_new_session=True,
|
|
231
233
|
preexec_fn=_child_setup,
|
|
232
234
|
cwd=os.path.expanduser("~"),
|
|
233
235
|
env={**os.environ,
|
|
@@ -805,19 +807,19 @@ def _sh_history(history: list) -> None:
|
|
|
805
807
|
def guest_connect_shell(base_url: str, code: str) -> int:
|
|
806
808
|
"""
|
|
807
809
|
Connect to a guest host via pairing code.
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
Returns the
|
|
810
|
+
Opens a real PTY tunnel to the host's shell — identical to connect_shell()
|
|
811
|
+
but uses the guest relay (no API key). Full interactive terminal: nano,
|
|
812
|
+
vim, htop, etc. all work. DevOS banner is printed before entering raw mode.
|
|
813
|
+
Returns the remote exit code.
|
|
812
814
|
"""
|
|
813
|
-
import
|
|
814
|
-
import
|
|
815
|
+
import select as _sel
|
|
816
|
+
import signal
|
|
815
817
|
|
|
816
818
|
code = code.strip().upper()
|
|
817
819
|
ws_url = _guest_join_url(base_url, code)
|
|
818
820
|
|
|
819
|
-
# ── Connect & handle approval
|
|
820
|
-
print(f"\n {_C}◈{_RS} {_DM}connecting…{_RS}")
|
|
821
|
+
# ── Connect & handle approval (before entering raw mode) ─────────────────
|
|
822
|
+
print(f"\n {_C}◈{_RS} {_DM}connecting…{_RS}", flush=True)
|
|
821
823
|
try:
|
|
822
824
|
ws = _ws_connect(ws_url, open_timeout=15, close_timeout=10)
|
|
823
825
|
except Exception as e:
|
|
@@ -832,10 +834,10 @@ def guest_connect_shell(base_url: str, code: str) -> int:
|
|
|
832
834
|
|
|
833
835
|
if ftype == "waiting_approval":
|
|
834
836
|
fp = first.get("fingerprint", "")
|
|
835
|
-
print(f" {_C}◈{_RS} {_DM}waiting for host to approve…{_RS}")
|
|
837
|
+
print(f" {_C}◈{_RS} {_DM}waiting for host to approve…{_RS}", flush=True)
|
|
836
838
|
if fp:
|
|
837
|
-
print(f" {_DM}fingerprint {_C}{fp}{
|
|
838
|
-
print(f" {_DM}host has 30 s to respond{_RS}\n")
|
|
839
|
+
print(f" {_DM}fingerprint {_C}{fp}{_RS}", flush=True)
|
|
840
|
+
print(f" {_DM}host has 30 s to respond{_RS}\n", flush=True)
|
|
839
841
|
try:
|
|
840
842
|
second = json.loads(ws.recv(timeout=35))
|
|
841
843
|
except Exception:
|
|
@@ -851,213 +853,145 @@ def guest_connect_shell(base_url: str, code: str) -> int:
|
|
|
851
853
|
ws.close()
|
|
852
854
|
raise RuntimeError(first.get("message", "unexpected server response"))
|
|
853
855
|
|
|
854
|
-
# ──
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
identity = os.environ.get("USER") or os.environ.get("LOGNAME") or "akash"
|
|
856
|
+
# ── Print DevOS banner (before raw mode) ──────────────────────────────────
|
|
857
|
+
identity = os.environ.get("USER") or os.environ.get("LOGNAME") or "akash"
|
|
858
|
+
print(f"\n {_G}◉{_RS} {_C}akash shell{_RS} "
|
|
859
|
+
f"{_DM}code {code} · Ctrl-D or `exit` to quit{_RS}\n", flush=True)
|
|
859
860
|
|
|
860
|
-
#
|
|
861
|
-
hist_file = _AKASH_DIR / "shell_history"
|
|
862
|
-
_AKASH_DIR.mkdir(parents=True, exist_ok=True)
|
|
861
|
+
# ── Get terminal size ─────────────────────────────────────────────────────
|
|
863
862
|
try:
|
|
864
|
-
|
|
863
|
+
import shutil as _sh
|
|
864
|
+
cols, rows = _sh.get_terminal_size(fallback=(80, 24))
|
|
865
865
|
except Exception:
|
|
866
|
-
|
|
867
|
-
_rl.set_history_length(500)
|
|
866
|
+
rows, cols = 24, 80
|
|
868
867
|
|
|
869
|
-
# ──
|
|
870
|
-
|
|
871
|
-
|
|
868
|
+
# ── Enter raw mode ────────────────────────────────────────────────────────
|
|
869
|
+
_raw_ok = False
|
|
870
|
+
_old_settings = None
|
|
871
|
+
_stdin_fd = -1
|
|
872
|
+
try:
|
|
873
|
+
import tty as _tty, termios as _term
|
|
874
|
+
_stdin_fd = sys.stdin.fileno()
|
|
875
|
+
_old_settings = _term.tcgetattr(_stdin_fd)
|
|
876
|
+
_tty.setraw(_stdin_fd)
|
|
877
|
+
_raw_ok = True
|
|
878
|
+
except Exception:
|
|
879
|
+
pass
|
|
872
880
|
|
|
873
|
-
def
|
|
874
|
-
|
|
881
|
+
def _restore():
|
|
882
|
+
if _raw_ok and _old_settings is not None:
|
|
875
883
|
try:
|
|
876
|
-
|
|
877
|
-
|
|
884
|
+
import termios as _t
|
|
885
|
+
_t.tcsetattr(_stdin_fd, _t.TCSADRAIN, _old_settings)
|
|
878
886
|
except Exception:
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
887
|
+
pass
|
|
888
|
+
|
|
889
|
+
exit_code = 0
|
|
890
|
+
stop = threading.Event()
|
|
891
|
+
|
|
892
|
+
# ── Tell host to open a PTY shell ─────────────────────────────────────────
|
|
893
|
+
try:
|
|
894
|
+
ws.send(json.dumps({
|
|
895
|
+
"type": "pty_run",
|
|
896
|
+
"command": os.environ.get("SHELL", "/bin/bash"),
|
|
897
|
+
"rows": rows,
|
|
898
|
+
"cols": cols,
|
|
899
|
+
}))
|
|
900
|
+
except Exception as e:
|
|
901
|
+
_restore()
|
|
902
|
+
ws.close()
|
|
903
|
+
raise RuntimeError(f"Failed to start remote shell: {e}")
|
|
904
|
+
|
|
905
|
+
# ── Thread: local stdin → WS ──────────────────────────────────────────────
|
|
906
|
+
def _stdin_pump():
|
|
907
|
+
try:
|
|
908
|
+
while not stop.is_set():
|
|
909
|
+
if _stdin_fd >= 0:
|
|
910
|
+
r = _sel.select([_stdin_fd], [], [], 0.02)[0]
|
|
911
|
+
if not r:
|
|
912
|
+
continue
|
|
913
|
+
chunk = os.read(_stdin_fd, 4096)
|
|
914
|
+
else:
|
|
915
|
+
chunk = sys.stdin.buffer.read(1)
|
|
916
|
+
if not chunk:
|
|
917
|
+
break
|
|
918
|
+
ws.send(json.dumps({
|
|
919
|
+
"type": "pty_in",
|
|
920
|
+
"data": base64.b64encode(chunk).decode(),
|
|
921
|
+
}))
|
|
922
|
+
except Exception:
|
|
923
|
+
pass
|
|
924
|
+
|
|
925
|
+
# ── SIGWINCH → resize frame ───────────────────────────────────────────────
|
|
926
|
+
def _on_resize(signum, _frame):
|
|
927
|
+
try:
|
|
928
|
+
import shutil as _sh2
|
|
929
|
+
c2, r2 = _sh2.get_terminal_size(fallback=(80, 24))
|
|
930
|
+
ws.send(json.dumps({"type": "resize", "rows": r2, "cols": c2}))
|
|
931
|
+
except Exception:
|
|
932
|
+
pass
|
|
933
|
+
try:
|
|
934
|
+
signal.signal(signal.SIGWINCH, _on_resize)
|
|
935
|
+
except Exception:
|
|
936
|
+
pass
|
|
882
937
|
|
|
883
|
-
|
|
884
|
-
|
|
938
|
+
t = threading.Thread(target=_stdin_pump, daemon=True)
|
|
939
|
+
t.start()
|
|
885
940
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
ec = 0
|
|
890
|
-
new_cwd = cwd
|
|
891
|
-
deadline = time.time() + timeout
|
|
892
|
-
while time.time() < deadline:
|
|
941
|
+
# ── Main loop: WS → local stdout (raw PTY output) ────────────────────────
|
|
942
|
+
try:
|
|
943
|
+
while True:
|
|
893
944
|
try:
|
|
894
|
-
raw =
|
|
895
|
-
except
|
|
896
|
-
continue
|
|
897
|
-
if raw is None:
|
|
945
|
+
raw = ws.recv()
|
|
946
|
+
except Exception:
|
|
898
947
|
break
|
|
899
948
|
try:
|
|
900
949
|
frame = json.loads(raw)
|
|
901
950
|
except Exception:
|
|
902
951
|
continue
|
|
903
|
-
|
|
904
|
-
if
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
952
|
+
ftype = frame.get("type")
|
|
953
|
+
if ftype == "pty_out":
|
|
954
|
+
data = base64.b64decode(frame["data"])
|
|
955
|
+
if data:
|
|
956
|
+
sys.stdout.buffer.write(data)
|
|
957
|
+
sys.stdout.buffer.flush()
|
|
958
|
+
elif ftype == "exit":
|
|
959
|
+
exit_code = frame.get("code", 0)
|
|
960
|
+
stop.set()
|
|
961
|
+
break
|
|
962
|
+
elif ftype == "error":
|
|
963
|
+
stop.set()
|
|
964
|
+
_restore()
|
|
965
|
+
print(f"\r\n{_R}[error]{_RS} {frame.get('message','?')}\r\n",
|
|
966
|
+
file=sys.stderr, flush=True)
|
|
967
|
+
exit_code = 1
|
|
918
968
|
break
|
|
919
|
-
elif
|
|
920
|
-
|
|
921
|
-
ec = 1
|
|
969
|
+
elif ftype == "disconnected":
|
|
970
|
+
stop.set()
|
|
922
971
|
break
|
|
923
|
-
elif
|
|
972
|
+
elif ftype == "ping":
|
|
924
973
|
try:
|
|
925
974
|
ws.send(json.dumps({"type": "pong"}))
|
|
926
975
|
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:
|
|
976
|
+
break
|
|
977
|
+
except KeyboardInterrupt:
|
|
1047
978
|
pass
|
|
1048
979
|
finally:
|
|
1049
|
-
|
|
980
|
+
stop.set()
|
|
981
|
+
_restore()
|
|
1050
982
|
try:
|
|
1051
|
-
|
|
983
|
+
signal.signal(signal.SIGWINCH, signal.SIG_DFL)
|
|
1052
984
|
except Exception:
|
|
1053
985
|
pass
|
|
1054
986
|
try:
|
|
1055
|
-
ws.close
|
|
987
|
+
ws.send(json.dumps({"type": "close"}))
|
|
1056
988
|
except Exception:
|
|
1057
989
|
pass
|
|
1058
990
|
try:
|
|
1059
|
-
|
|
991
|
+
ws.close()
|
|
1060
992
|
except Exception:
|
|
1061
993
|
pass
|
|
1062
994
|
|
|
995
|
+
# Print a clean newline after raw mode ends
|
|
996
|
+
print(f"\r\n {_DM}session ended code {exit_code}{_RS}\r\n", flush=True)
|
|
1063
997
|
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
|
|
File without changes
|