akashcli 4.5.7__tar.gz → 4.5.8__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.7 → akashcli-4.5.8}/PKG-INFO +1 -1
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/relay.py +333 -156
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli.egg-info/PKG-INFO +1 -1
- {akashcli-4.5.7 → akashcli-4.5.8}/pyproject.toml +1 -1
- {akashcli-4.5.7 → akashcli-4.5.8}/README.md +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/__init__.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/_net.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/auth.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/cli.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/client.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/config.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/crypto.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/env_manager.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/exceptions.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/exec.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/integrity.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/logger.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/models.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/p2p.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/packages.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/restore.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/snapshot_crypto.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/storage.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/terminal.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/uploader.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli/utils.py +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli.egg-info/SOURCES.txt +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli.egg-info/dependency_links.txt +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli.egg-info/entry_points.txt +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli.egg-info/requires.txt +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/akashcli.egg-info/top_level.txt +0 -0
- {akashcli-4.5.7 → akashcli-4.5.8}/setup.cfg +0 -0
|
@@ -222,21 +222,7 @@ def _handle_pty(ws, frame: dict) -> None:
|
|
|
222
222
|
import subprocess as _sp
|
|
223
223
|
shell = command or os.environ.get("SHELL", "/bin/bash")
|
|
224
224
|
|
|
225
|
-
#
|
|
226
|
-
# Uses only bash builtins + git (optional). No external deps.
|
|
227
|
-
_AKASH_PS1 = (
|
|
228
|
-
r'__akash_ps1() { '
|
|
229
|
-
r' local b; b=$(git branch --show-current 2>/dev/null); '
|
|
230
|
-
r' [ -n "$b" ] && echo -e " \033[90m(\033[1;35m$b\033[90m)\033[0m"; '
|
|
231
|
-
r'}; '
|
|
232
|
-
r'PS1="\n\033[1;36m╭─\033[0m \033[1;32m\u\033[90m@\033[1;32m\h\033[0m'
|
|
233
|
-
r' \033[1;34m\w\033[0m\$(__akash_ps1)\n'
|
|
234
|
-
r'\033[1;36m╰─❯\033[0m "; '
|
|
235
|
-
r'export PS1'
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
# Run the shell directly (not wrapped in bash -c) so it IS the login shell
|
|
239
|
-
# and gets a proper controlling terminal from slave_fd.
|
|
225
|
+
# Run the shell directly so it gets a proper controlling terminal.
|
|
240
226
|
try:
|
|
241
227
|
proc = _sp.Popen(
|
|
242
228
|
[shell],
|
|
@@ -258,14 +244,6 @@ def _handle_pty(ws, frame: dict) -> None:
|
|
|
258
244
|
|
|
259
245
|
os.close(slave_fd)
|
|
260
246
|
|
|
261
|
-
# Give bash ~120 ms to finish sourcing .bashrc, then inject our PS1.
|
|
262
|
-
import time as _time
|
|
263
|
-
_time.sleep(0.12)
|
|
264
|
-
try:
|
|
265
|
-
os.write(master_fd, (_AKASH_PS1 + "\n").encode())
|
|
266
|
-
except Exception:
|
|
267
|
-
pass
|
|
268
|
-
|
|
269
247
|
stop = threading.Event()
|
|
270
248
|
|
|
271
249
|
# Thread: master fd → WebSocket (drain as fast as possible)
|
|
@@ -711,175 +689,374 @@ def guest_serve(base_url: str) -> None:
|
|
|
711
689
|
time.sleep(3)
|
|
712
690
|
|
|
713
691
|
|
|
692
|
+
# ── DevOS-style shell UI ──────────────────────────────────────────────────────
|
|
693
|
+
|
|
694
|
+
_SH_C = "\033[1;36m" # cyan bold (border, ╭╰)
|
|
695
|
+
_SH_W = "\033[1;37m" # white bold (identity)
|
|
696
|
+
_SH_G = "\033[1;32m" # green bold (ok exit)
|
|
697
|
+
_SH_R = "\033[1;31m" # red bold (error exit)
|
|
698
|
+
_SH_Y = "\033[1;33m" # yellow bold (git branch)
|
|
699
|
+
_SH_DM = "\033[2m" # dim (timing, path)
|
|
700
|
+
_SH_GR = "\033[90m" # gray (│ border, separators)
|
|
701
|
+
_SH_RS = "\033[0m" # reset
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def _sh_sanitize_path(path: str, maxlen: int = 34) -> str:
|
|
705
|
+
home = os.path.expanduser("~")
|
|
706
|
+
if path == home:
|
|
707
|
+
return "~"
|
|
708
|
+
if path.startswith(home + "/"):
|
|
709
|
+
path = "~" + path[len(home):]
|
|
710
|
+
if len(path) <= maxlen:
|
|
711
|
+
return path
|
|
712
|
+
parts = path.split("/")
|
|
713
|
+
tail = parts[-1]
|
|
714
|
+
mid = parts[-2] if len(parts) >= 2 else ""
|
|
715
|
+
return f".../{mid}/{tail}" if mid else f".../{tail}"
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def _sh_git_branch(cwd: str) -> str:
|
|
719
|
+
"""Read git branch from .git/HEAD — no subprocess, pure Python."""
|
|
720
|
+
d = cwd
|
|
721
|
+
while d and d != "/":
|
|
722
|
+
head = os.path.join(d, ".git", "HEAD")
|
|
723
|
+
if os.path.isfile(head):
|
|
724
|
+
try:
|
|
725
|
+
line = open(head).readline().strip()
|
|
726
|
+
if line.startswith("ref: refs/heads/"):
|
|
727
|
+
return line[len("ref: refs/heads/"):]
|
|
728
|
+
return line[:7]
|
|
729
|
+
except Exception:
|
|
730
|
+
return ""
|
|
731
|
+
d = os.path.dirname(d)
|
|
732
|
+
return ""
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
def _sh_draw_header(identity: str, cwd: str) -> None:
|
|
736
|
+
path = _sh_sanitize_path(cwd)
|
|
737
|
+
branch = _sh_git_branch(cwd)
|
|
738
|
+
branch_part = (
|
|
739
|
+
f" {_SH_GR}({_SH_Y}{branch}{_SH_GR}){_SH_RS}" if branch else ""
|
|
740
|
+
)
|
|
741
|
+
print(
|
|
742
|
+
f"{_SH_C}╭─{_SH_RS} {_SH_W}{identity}{_SH_RS} "
|
|
743
|
+
f"{_SH_GR}•{_SH_RS} {_SH_DM}{path}{_SH_RS}"
|
|
744
|
+
f"{branch_part}"
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
def _sh_input_prompt() -> str:
|
|
749
|
+
return f"{_SH_C}╰─{_SH_RS} {_SH_W}${_SH_RS} "
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
def _sh_footer(ec: int, elapsed_ms: float) -> None:
|
|
753
|
+
color = _SH_G if ec == 0 else _SH_R
|
|
754
|
+
t = f" {_SH_GR}{elapsed_ms/1000:.2f}s{_SH_RS}" if elapsed_ms >= 100 else ""
|
|
755
|
+
print(f" {_SH_GR}•{_SH_RS} {color}[{ec}]{_SH_RS}{t}")
|
|
756
|
+
print()
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
def _sh_inset(lines: list[str]) -> None:
|
|
760
|
+
print()
|
|
761
|
+
for line in lines:
|
|
762
|
+
print(f" {_SH_GR}│{_SH_RS} {line}")
|
|
763
|
+
print(f" {_SH_GR}│{_SH_RS}")
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
def _sh_help() -> None:
|
|
767
|
+
print()
|
|
768
|
+
print(f" {_SH_GR}╭─ AKASH SHELL COMMANDS{_SH_RS}")
|
|
769
|
+
cmds = [
|
|
770
|
+
(":h", "session history"),
|
|
771
|
+
(":r", "re-run last command"),
|
|
772
|
+
(":clear", "clear screen"),
|
|
773
|
+
(":status", "show session info"),
|
|
774
|
+
(":upload F", "upload local file F to vault"),
|
|
775
|
+
(":exit", "disconnect"),
|
|
776
|
+
]
|
|
777
|
+
for cmd, desc in cmds:
|
|
778
|
+
print(f" {_SH_GR}│{_SH_RS} {_SH_C}{cmd:<12}{_SH_RS} {_SH_DM}{desc}{_SH_RS}")
|
|
779
|
+
print(f" {_SH_GR}╰─{_SH_RS}")
|
|
780
|
+
print()
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
def _sh_status(code: str, cwd: str, history: list) -> None:
|
|
784
|
+
print()
|
|
785
|
+
print(f" {_SH_GR}╭─ SESSION{_SH_RS}")
|
|
786
|
+
print(f" {_SH_GR}│{_SH_RS} code {_SH_C}{code}{_SH_RS}")
|
|
787
|
+
print(f" {_SH_GR}│{_SH_RS} cwd {_SH_DM}{cwd}{_SH_RS}")
|
|
788
|
+
print(f" {_SH_GR}│{_SH_RS} history {len(history)} commands")
|
|
789
|
+
print(f" {_SH_GR}╰─{_SH_RS}")
|
|
790
|
+
print()
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
def _sh_history(history: list) -> None:
|
|
794
|
+
print()
|
|
795
|
+
if not history:
|
|
796
|
+
print(f" {_SH_GR}│{_SH_RS} (no commands yet)")
|
|
797
|
+
else:
|
|
798
|
+
start = max(0, len(history) - 15)
|
|
799
|
+
for i, cmd in enumerate(history[start:], start + 1):
|
|
800
|
+
print(f" {_SH_GR}│{_SH_RS} {_SH_DM}{i:>3}{_SH_RS} {cmd}")
|
|
801
|
+
print(f" {_SH_GR}│{_SH_RS}")
|
|
802
|
+
print()
|
|
803
|
+
|
|
804
|
+
|
|
714
805
|
def guest_connect_shell(base_url: str, code: str) -> int:
|
|
715
806
|
"""
|
|
716
|
-
Connect to a guest host
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
807
|
+
Connect to a guest host via pairing code.
|
|
808
|
+
Uses a Python REPL loop with DevOS-style UI — no raw-mode PTY needed on
|
|
809
|
+
the client side. The remote side runs a persistent bash that we drive with
|
|
810
|
+
`run` frames; output is shown with inset │ borders.
|
|
811
|
+
Returns the last remote exit code.
|
|
720
812
|
"""
|
|
721
|
-
import
|
|
813
|
+
import readline as _rl
|
|
814
|
+
import queue as _q
|
|
722
815
|
|
|
723
816
|
code = code.strip().upper()
|
|
724
817
|
ws_url = _guest_join_url(base_url, code)
|
|
725
818
|
|
|
726
|
-
|
|
727
|
-
|
|
819
|
+
# ── Connect & handle approval ─────────────────────────────────────────────
|
|
820
|
+
print(f"\n {_C}◈{_RS} {_DM}connecting…{_RS}")
|
|
821
|
+
try:
|
|
822
|
+
ws = _ws_connect(ws_url, open_timeout=15, close_timeout=10)
|
|
823
|
+
except Exception as e:
|
|
824
|
+
raise RuntimeError(str(e))
|
|
728
825
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
826
|
+
first = json.loads(ws.recv())
|
|
827
|
+
ftype = first.get("type")
|
|
828
|
+
|
|
829
|
+
if ftype == "error":
|
|
830
|
+
ws.close()
|
|
831
|
+
raise RuntimeError(first.get("message", "relay error"))
|
|
832
|
+
|
|
833
|
+
if ftype == "waiting_approval":
|
|
834
|
+
fp = first.get("fingerprint", "")
|
|
835
|
+
print(f" {_C}◈{_RS} {_DM}waiting for host to approve…{_RS}")
|
|
836
|
+
if fp:
|
|
837
|
+
print(f" {_DM}fingerprint {_C}{fp}{_SH_RS}")
|
|
838
|
+
print(f" {_DM}host has 30 s to respond{_RS}\n")
|
|
839
|
+
try:
|
|
840
|
+
second = json.loads(ws.recv(timeout=35))
|
|
841
|
+
except Exception:
|
|
842
|
+
ws.close()
|
|
843
|
+
raise RuntimeError("Timed out waiting for host approval.")
|
|
844
|
+
if second.get("type") == "denied":
|
|
845
|
+
ws.close()
|
|
846
|
+
raise RuntimeError("Host declined the connection.")
|
|
847
|
+
if second.get("type") != "connected":
|
|
848
|
+
ws.close()
|
|
849
|
+
raise RuntimeError(second.get("message", "unexpected response from host"))
|
|
850
|
+
elif ftype != "connected":
|
|
851
|
+
ws.close()
|
|
852
|
+
raise RuntimeError(first.get("message", "unexpected server response"))
|
|
853
|
+
|
|
854
|
+
# ── Session state ─────────────────────────────────────────────────────────
|
|
855
|
+
cwd = os.path.expanduser("~")
|
|
856
|
+
history : list[str] = []
|
|
857
|
+
exit_code = 0
|
|
858
|
+
identity = os.environ.get("USER") or os.environ.get("LOGNAME") or "akash"
|
|
859
|
+
|
|
860
|
+
# readline history setup
|
|
861
|
+
hist_file = _AKASH_DIR / "shell_history"
|
|
862
|
+
_AKASH_DIR.mkdir(parents=True, exist_ok=True)
|
|
732
863
|
try:
|
|
733
|
-
|
|
734
|
-
_stdin_fd = sys.stdin.fileno()
|
|
735
|
-
_old_settings = _term.tcgetattr(_stdin_fd)
|
|
736
|
-
tty.setraw(_stdin_fd)
|
|
737
|
-
_raw_ok = True
|
|
864
|
+
_rl.read_history_file(str(hist_file))
|
|
738
865
|
except Exception:
|
|
739
866
|
pass
|
|
867
|
+
_rl.set_history_length(500)
|
|
740
868
|
|
|
741
|
-
|
|
742
|
-
|
|
869
|
+
# ── Output queue — background thread feeds frames ─────────────────────────
|
|
870
|
+
q: _q.Queue = _q.Queue()
|
|
871
|
+
stop_recv = threading.Event()
|
|
872
|
+
|
|
873
|
+
def _recv_loop():
|
|
874
|
+
while not stop_recv.is_set():
|
|
743
875
|
try:
|
|
744
|
-
|
|
745
|
-
|
|
876
|
+
raw = ws.recv(timeout=0.1)
|
|
877
|
+
q.put(raw)
|
|
746
878
|
except Exception:
|
|
747
|
-
|
|
879
|
+
if not stop_recv.is_set():
|
|
880
|
+
q.put(None) # signals disconnection
|
|
881
|
+
break
|
|
748
882
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
with _ws_connect(ws_url, open_timeout=15, close_timeout=10) as ws:
|
|
752
|
-
# First frame: could be error, waiting_approval, or connected
|
|
753
|
-
first = json.loads(ws.recv())
|
|
754
|
-
ftype = first.get("type")
|
|
883
|
+
recv_thread = threading.Thread(target=_recv_loop, daemon=True)
|
|
884
|
+
recv_thread.start()
|
|
755
885
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
#
|
|
886
|
+
def _flush_queue(timeout: float = 30.0) -> tuple[list[str], int, str]:
|
|
887
|
+
"""Collect stdout/stderr until exit frame. Returns (lines, code, new_cwd)."""
|
|
888
|
+
lines: list[str] = []
|
|
889
|
+
ec = 0
|
|
890
|
+
new_cwd = cwd
|
|
891
|
+
deadline = time.time() + timeout
|
|
892
|
+
while time.time() < deadline:
|
|
893
|
+
try:
|
|
894
|
+
raw = q.get(timeout=0.05)
|
|
895
|
+
except _q.Empty:
|
|
896
|
+
continue
|
|
897
|
+
if raw is None:
|
|
898
|
+
break
|
|
899
|
+
try:
|
|
900
|
+
frame = json.loads(raw)
|
|
901
|
+
except Exception:
|
|
902
|
+
continue
|
|
903
|
+
ft = frame.get("type")
|
|
904
|
+
if ft in ("stdout", "pty_out"):
|
|
905
|
+
chunk = base64.b64decode(frame.get("data", ""))
|
|
906
|
+
# Strip ANSI, split into lines for inset display
|
|
907
|
+
text = chunk.decode("utf-8", errors="replace")
|
|
908
|
+
# Remove ANSI escape codes for clean inset display
|
|
909
|
+
import re as _re
|
|
910
|
+
text = _re.sub(r'\x1b\[[0-9;]*[mGKHF]', '', text)
|
|
911
|
+
text = _re.sub(r'\x1b\][^\x07]*\x07', '', text)
|
|
912
|
+
for ln in text.splitlines():
|
|
913
|
+
if ln.strip():
|
|
914
|
+
lines.append(ln)
|
|
915
|
+
elif ft == "exit":
|
|
916
|
+
ec = frame.get("code", 0)
|
|
917
|
+
new_cwd = frame.get("cwd", cwd)
|
|
918
|
+
break
|
|
919
|
+
elif ft == "error":
|
|
920
|
+
lines.append(f"{_R}error:{_RS} {frame.get('message','?')}")
|
|
921
|
+
ec = 1
|
|
922
|
+
break
|
|
923
|
+
elif ft == "ping":
|
|
777
924
|
try:
|
|
778
|
-
|
|
779
|
-
_old_settings = _term2.tcgetattr(_stdin_fd)
|
|
780
|
-
_tty2.setraw(_stdin_fd)
|
|
781
|
-
_raw_ok = True
|
|
925
|
+
ws.send(json.dumps({"type": "pong"}))
|
|
782
926
|
except Exception:
|
|
783
927
|
pass
|
|
784
|
-
|
|
785
|
-
_restore()
|
|
786
|
-
raise RuntimeError(first.get("message", "unexpected server response"))
|
|
928
|
+
return lines, ec, new_cwd
|
|
787
929
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
try:
|
|
793
|
-
import tty as _tty3, termios as _term3
|
|
794
|
-
_old_settings = _term3.tcgetattr(_stdin_fd)
|
|
795
|
-
_tty3.setraw(_stdin_fd)
|
|
796
|
-
_raw_ok = True
|
|
797
|
-
except Exception:
|
|
798
|
-
pass
|
|
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()
|
|
799
934
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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
|
|
805
945
|
|
|
806
|
-
|
|
946
|
+
if not cmd:
|
|
947
|
+
continue
|
|
807
948
|
|
|
808
|
-
|
|
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}")
|
|
809
974
|
try:
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
}))
|
|
825
|
-
except Exception:
|
|
826
|
-
pass
|
|
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
|
|
827
989
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
ws.send(json.dumps({"type": "resize", "rows": r, "cols": c}))
|
|
832
|
-
except Exception:
|
|
833
|
-
pass
|
|
990
|
+
# ── Remote command ────────────────────────────────────────────────
|
|
991
|
+
history.append(cmd)
|
|
992
|
+
last_cmd = cmd
|
|
834
993
|
try:
|
|
835
|
-
|
|
994
|
+
_rl.add_history(cmd)
|
|
836
995
|
except Exception:
|
|
837
996
|
pass
|
|
838
997
|
|
|
839
|
-
|
|
840
|
-
|
|
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
|
+
)
|
|
841
1015
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
elif ftype == "exit":
|
|
858
|
-
exit_code = frame.get("code", 0)
|
|
859
|
-
stop.set()
|
|
860
|
-
break
|
|
861
|
-
elif ftype == "error":
|
|
862
|
-
stop.set()
|
|
863
|
-
_restore()
|
|
864
|
-
print(f"\r\n{_R}[error]{_RS} {frame.get('message','?')}", file=sys.stderr)
|
|
865
|
-
exit_code = 1
|
|
866
|
-
break
|
|
867
|
-
elif ftype == "disconnected":
|
|
868
|
-
stop.set()
|
|
869
|
-
break
|
|
870
|
-
elif ftype == "ping":
|
|
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__:"):
|
|
871
1031
|
try:
|
|
872
|
-
|
|
1032
|
+
ec = int(ln[len("__AKASH_EXIT__:"):].strip())
|
|
873
1033
|
except Exception:
|
|
874
|
-
|
|
1034
|
+
pass
|
|
1035
|
+
else:
|
|
1036
|
+
clean_lines.append(ln)
|
|
875
1037
|
|
|
876
|
-
|
|
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:
|
|
877
1047
|
pass
|
|
878
1048
|
finally:
|
|
879
|
-
|
|
880
|
-
_restore()
|
|
1049
|
+
stop_recv.set()
|
|
881
1050
|
try:
|
|
882
|
-
|
|
1051
|
+
ws.send(json.dumps({"type": "close"}))
|
|
1052
|
+
except Exception:
|
|
1053
|
+
pass
|
|
1054
|
+
try:
|
|
1055
|
+
ws.close()
|
|
1056
|
+
except Exception:
|
|
1057
|
+
pass
|
|
1058
|
+
try:
|
|
1059
|
+
_rl.write_history_file(str(hist_file))
|
|
883
1060
|
except Exception:
|
|
884
1061
|
pass
|
|
885
1062
|
|
|
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
|