akashcli 4.3.2__tar.gz → 4.4.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.3.2 → akashcli-4.4.1}/PKG-INFO +1 -1
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/cli.py +216 -140
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli.egg-info/PKG-INFO +1 -1
- {akashcli-4.3.2 → akashcli-4.4.1}/pyproject.toml +1 -1
- {akashcli-4.3.2 → akashcli-4.4.1}/README.md +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/__init__.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/auth.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/client.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/config.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/crypto.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/env_manager.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/exceptions.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/exec.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/integrity.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/logger.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/models.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/p2p.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/packages.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/relay.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/restore.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/snapshot_crypto.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/storage.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/terminal.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/uploader.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli/utils.py +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli.egg-info/SOURCES.txt +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli.egg-info/dependency_links.txt +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli.egg-info/entry_points.txt +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli.egg-info/requires.txt +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/akashcli.egg-info/top_level.txt +0 -0
- {akashcli-4.3.2 → akashcli-4.4.1}/setup.cfg +0 -0
|
@@ -142,22 +142,72 @@ def _upload_with_progress(client: AkashClient, tmp_path: str, password=None) ->
|
|
|
142
142
|
# `cloud_app` — file management (akash cloud upload/files/stream/delete/manage/bulk)
|
|
143
143
|
|
|
144
144
|
app = typer.Typer(
|
|
145
|
-
|
|
146
|
-
"akash — encrypted cloud backup for your terminal.\n\n"
|
|
147
|
-
"Quick start:\n"
|
|
148
|
-
" akash login connect your API key\n"
|
|
149
|
-
" akash backup save & sync your terminal\n"
|
|
150
|
-
" akash pull <CODE> restore on a fresh machine\n\n"
|
|
151
|
-
"Run akash cloud --help for file sync commands."
|
|
152
|
-
),
|
|
145
|
+
invoke_without_command=True,
|
|
153
146
|
rich_markup_mode=None,
|
|
154
147
|
add_completion=True,
|
|
148
|
+
no_args_is_help=False,
|
|
155
149
|
)
|
|
150
|
+
|
|
151
|
+
@app.callback(invoke_without_command=True)
|
|
152
|
+
def _main(ctx: typer.Context,
|
|
153
|
+
help: bool = typer.Option(False, "--help", "-h", is_eager=True, expose_value=False,
|
|
154
|
+
callback=lambda v: (_print_help(), raise_exit()) if v else None,
|
|
155
|
+
help="Show this message and exit.")):
|
|
156
|
+
"""akash — your encrypted terminal cloud."""
|
|
157
|
+
if ctx.invoked_subcommand is None:
|
|
158
|
+
_print_help()
|
|
159
|
+
raise typer.Exit(0)
|
|
160
|
+
|
|
161
|
+
def raise_exit():
|
|
162
|
+
raise typer.Exit(0)
|
|
163
|
+
|
|
164
|
+
def _print_help():
|
|
165
|
+
C, G, Y, DM, RS = _C, _G, _Y, _DM, _RS
|
|
166
|
+
lines = [
|
|
167
|
+
f"",
|
|
168
|
+
f" {C}akash{RS} {DM}— encrypted cloud backup · remote shell · file vault{RS}",
|
|
169
|
+
f"",
|
|
170
|
+
f" {DM}ACCOUNT{RS}",
|
|
171
|
+
f" {C}login{RS} {DM}[--label NAME]{RS} save your API key + name this device",
|
|
172
|
+
f" {C}whoami{RS} show account info and storage quota",
|
|
173
|
+
f" {C}logout{RS} clear saved credentials",
|
|
174
|
+
f"",
|
|
175
|
+
f" {DM}TERMINAL BACKUP{RS}",
|
|
176
|
+
f" {C}backup{RS} save this terminal environment to the cloud",
|
|
177
|
+
f" {C}restore{RS} restore environment from the cloud",
|
|
178
|
+
f" {C}saves{RS} list your saved environments",
|
|
179
|
+
f" {C}pull{RS} {DM}<CODE>{RS} restore on any machine — no login needed",
|
|
180
|
+
f"",
|
|
181
|
+
f" {DM}REMOTE SHELL{RS}",
|
|
182
|
+
f" {C}shell{RS} {DM}[--device NAME]{RS} open a shell here, or on another device",
|
|
183
|
+
f" {C}run{RS} {DM}<CMD> [--device NAME]{RS} run a command locally or on a device",
|
|
184
|
+
f" {C}serve{RS} {DM}[--label NAME]{RS} make this device reachable for shell/run",
|
|
185
|
+
f" {C}devices{RS} {DM}[--revoke NAME]{RS} list or revoke enrolled devices",
|
|
186
|
+
f"",
|
|
187
|
+
f" {DM}FILES{RS}",
|
|
188
|
+
f" {C}pipe{RS} {DM}[--run CMD]{RS} upload stdin or a command's output",
|
|
189
|
+
f" {C}cloud{RS} {DM}<upload|files|stream…>{RS} manage individual cloud files",
|
|
190
|
+
f"",
|
|
191
|
+
f" {DM}SECURE TRANSFER{RS}",
|
|
192
|
+
f" {C}send{RS} {DM}<USER> <FILE>{RS} encrypt and send a file to another user",
|
|
193
|
+
f" {C}inbox{RS} receive and decrypt files sent to you",
|
|
194
|
+
f" {C}id{RS} show your P2P identity / manage contacts",
|
|
195
|
+
f"",
|
|
196
|
+
f" {DM}OTHER{RS}",
|
|
197
|
+
f" {C}env{RS} {DM}<set|get|list…>{RS} encrypted local .env manager",
|
|
198
|
+
f" {C}log{RS} {DM}[--stats]{RS} operation history (--stats for summary)",
|
|
199
|
+
f" {C}check{RS} {DM}[--seal]{RS} verify file integrity (--seal to re-seal)",
|
|
200
|
+
f"",
|
|
201
|
+
f" {DM}Run {RS}{C}akash <command> --help{RS}{DM} for full options on any command.{RS}",
|
|
202
|
+
f"",
|
|
203
|
+
]
|
|
204
|
+
print("\n".join(lines))
|
|
205
|
+
|
|
156
206
|
cloud_app = typer.Typer(
|
|
157
|
-
help="
|
|
207
|
+
help="Manage individual cloud files.",
|
|
158
208
|
rich_markup_mode=None,
|
|
159
209
|
)
|
|
160
|
-
app.add_typer(cloud_app, name="cloud", help="
|
|
210
|
+
app.add_typer(cloud_app, name="cloud", help="upload · stream · delete individual files")
|
|
161
211
|
|
|
162
212
|
|
|
163
213
|
# ── Integrity check ───────────────────────────────────────────────────────────
|
|
@@ -865,44 +915,37 @@ def pull(
|
|
|
865
915
|
_print_reload_hint(meta)
|
|
866
916
|
|
|
867
917
|
|
|
868
|
-
# ── akash seal
|
|
918
|
+
# ── akash check (seal + integrity merged) ────────────────────────────────────
|
|
869
919
|
|
|
870
920
|
@app.command()
|
|
871
|
-
def
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
hashes = _seal()
|
|
876
|
-
_ok(f"hashed {len(hashes)} source files")
|
|
877
|
-
_ok(f"manifest → {_dim(str(_manifest_path()))}")
|
|
878
|
-
_info("tamper detection active for backup, restore, and pull")
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
# ── akash integrity ───────────────────────────────────────────────────────────
|
|
921
|
+
def check(
|
|
922
|
+
seal: bool = typer.Option(False, "--seal", help="Re-seal the integrity manifest"),
|
|
923
|
+
):
|
|
924
|
+
"""Verify file integrity — or re-seal with --seal.
|
|
882
925
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
926
|
+
Examples:
|
|
927
|
+
akash check verify all source files are untampered
|
|
928
|
+
akash check --seal hash source files and write a new manifest
|
|
929
|
+
"""
|
|
930
|
+
if seal:
|
|
931
|
+
from .integrity import seal as _seal, _manifest_path
|
|
932
|
+
_info("sealing source integrity…")
|
|
933
|
+
hashes = _seal()
|
|
934
|
+
_ok(f"hashed {len(hashes)} source files")
|
|
935
|
+
_ok(f"manifest → {_dim(str(_manifest_path()))}")
|
|
936
|
+
_info("tamper detection active for backup, restore, and pull")
|
|
894
937
|
else:
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
938
|
+
from .integrity import status as _status
|
|
939
|
+
s = _status()
|
|
940
|
+
if not s["sealed"]:
|
|
941
|
+
_warn(f"not sealed yet — run {_cy('akash check --seal')} to enable tamper detection")
|
|
942
|
+
return
|
|
943
|
+
if s["ok"]:
|
|
944
|
+
_ok(f"integrity OK — {s['files']} files verified")
|
|
945
|
+
else:
|
|
946
|
+
_err("tamper detected!")
|
|
947
|
+
print(s["message"], file=sys.stderr)
|
|
948
|
+
raise typer.Exit(2)
|
|
906
949
|
|
|
907
950
|
|
|
908
951
|
# ── akash logout ─────────────────────────────────────────────────────────────
|
|
@@ -923,27 +966,37 @@ def run(
|
|
|
923
966
|
reauth: bool = typer.Option(False, "--reauth", help="Force re-authentication"),
|
|
924
967
|
save: bool = typer.Option(False, "--save", help="Upload command output to cloud"),
|
|
925
968
|
save_name: Optional[str] = typer.Option(None, "--save-name", help="Filename for saved output"),
|
|
926
|
-
device: Optional[str] = typer.Option(None, "--device","-d",
|
|
969
|
+
device: Optional[str] = typer.Option(None, "--device", "-d",
|
|
970
|
+
help="Run on a device (set up with: akash serve --label NAME)"),
|
|
927
971
|
):
|
|
928
|
-
"""Run a command — on the server or on
|
|
972
|
+
"""Run a command — locally, on the server, or on an enrolled device.
|
|
929
973
|
|
|
930
|
-
|
|
974
|
+
No login needed:
|
|
975
|
+
akash run "df -h" runs locally
|
|
976
|
+
akash run "ls" --save runs locally, uploads output
|
|
931
977
|
|
|
932
|
-
|
|
933
|
-
akash run "
|
|
934
|
-
akash run "df -h" --device termux
|
|
935
|
-
akash run "
|
|
936
|
-
|
|
978
|
+
With login:
|
|
979
|
+
akash run "df -h" runs on server
|
|
980
|
+
akash run "df -h" --device termux runs on your phone
|
|
981
|
+
akash run "pip list" --save runs on server, saves output
|
|
982
|
+
|
|
983
|
+
To make a device reachable:
|
|
984
|
+
1. On that device: akash login --label termux && akash serve
|
|
985
|
+
2. From anywhere: akash run "cmd" --device termux
|
|
937
986
|
"""
|
|
938
987
|
from .exec import acquire_exec_token, run_command, _clear_cached_token
|
|
939
988
|
|
|
940
989
|
try:
|
|
941
990
|
api_key, base = _resolve_client(url)
|
|
942
991
|
|
|
943
|
-
# ──
|
|
992
|
+
# ── --device but no key ───────────────────────────────────────────
|
|
944
993
|
if device is not None and not api_key:
|
|
945
|
-
|
|
946
|
-
|
|
994
|
+
_warn("relay requires an API key.")
|
|
995
|
+
_p()
|
|
996
|
+
_info(f"To run on {_cy(device.strip() or 'a device')} you need to log in first:")
|
|
997
|
+
_info(f" {_cy('akash login')} save your API key on this machine")
|
|
998
|
+
_info(f" {_cy('akash serve')} run on the target device to make it reachable")
|
|
999
|
+
_p()
|
|
947
1000
|
raise typer.Exit(1)
|
|
948
1001
|
|
|
949
1002
|
# ── Device relay path ─────────────────────────────────────────────
|
|
@@ -1087,39 +1140,52 @@ def _build_ws_url_for_run(token: str, base_url: str) -> str:
|
|
|
1087
1140
|
def shell(
|
|
1088
1141
|
url: Optional[str] = typer.Option(None, "--url", help="Override server URL"),
|
|
1089
1142
|
reauth: bool = typer.Option(False, "--reauth", help="Force re-authentication"),
|
|
1090
|
-
device: Optional[str] = typer.Option(None, "--device","-d",
|
|
1143
|
+
device: Optional[str] = typer.Option(None, "--device", "-d",
|
|
1144
|
+
help="Device label to connect to (set up with: akash serve --label NAME)"),
|
|
1091
1145
|
):
|
|
1092
|
-
"""Open
|
|
1146
|
+
"""Open a shell — local, server, or on any enrolled device.
|
|
1093
1147
|
|
|
1094
|
-
|
|
1095
|
-
|
|
1148
|
+
No login needed:
|
|
1149
|
+
akash shell open a local bash shell
|
|
1096
1150
|
|
|
1097
|
-
|
|
1098
|
-
akash shell
|
|
1099
|
-
akash shell --device termux
|
|
1100
|
-
akash shell --device laptop
|
|
1101
|
-
akash shell --device
|
|
1151
|
+
With login (akash login):
|
|
1152
|
+
akash shell full server shell (persistent, coloured)
|
|
1153
|
+
akash shell --device termux relay shell on your phone
|
|
1154
|
+
akash shell --device laptop relay shell on your laptop
|
|
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
|
|
1102
1160
|
"""
|
|
1103
1161
|
from .exec import acquire_exec_token, run_shell, _clear_cached_token
|
|
1162
|
+
import shutil as _sh, subprocess as _sp
|
|
1104
1163
|
|
|
1105
1164
|
try:
|
|
1106
1165
|
api_key, base = _resolve_client(url)
|
|
1107
1166
|
|
|
1108
|
-
# ──
|
|
1167
|
+
# ── --device flag but no key ──────────────────────────────────────
|
|
1109
1168
|
if device is not None and not api_key:
|
|
1110
|
-
|
|
1111
|
-
|
|
1169
|
+
_warn("relay requires an API key.")
|
|
1170
|
+
_p()
|
|
1171
|
+
_info(f"To connect to {_cy(device.strip() or 'a device')} you need to log in first:")
|
|
1172
|
+
_info(f" {_cy('akash login')} save your API key on this machine")
|
|
1173
|
+
_info(f" {_cy('akash serve')} run on the target device to make it reachable")
|
|
1174
|
+
_p()
|
|
1112
1175
|
raise typer.Exit(1)
|
|
1113
1176
|
|
|
1114
1177
|
# ── Device relay path ─────────────────────────────────────────────
|
|
1115
1178
|
if device is not None:
|
|
1116
1179
|
from .relay import connect_shell
|
|
1117
1180
|
label = device.strip() if device.strip() else None
|
|
1118
|
-
|
|
1181
|
+
_p()
|
|
1182
|
+
_info(f"connecting to {_cy(label or 'auto')}…")
|
|
1119
1183
|
try:
|
|
1120
1184
|
exit_code = connect_shell(api_key, base, target=label)
|
|
1121
1185
|
except RuntimeError as e:
|
|
1122
1186
|
_err(str(e))
|
|
1187
|
+
_p()
|
|
1188
|
+
_info(f"Make sure the device is running: {_cy('akash serve')}")
|
|
1123
1189
|
raise typer.Exit(1)
|
|
1124
1190
|
_p()
|
|
1125
1191
|
_info("session closed")
|
|
@@ -1127,20 +1193,17 @@ def shell(
|
|
|
1127
1193
|
raise typer.Exit(exit_code)
|
|
1128
1194
|
return
|
|
1129
1195
|
|
|
1130
|
-
# ── No key → local
|
|
1196
|
+
# ── No key → silent local shell ───────────────────────────────────
|
|
1131
1197
|
if not api_key:
|
|
1132
|
-
import shutil as _sh
|
|
1133
|
-
print(_GUEST_WARNING)
|
|
1134
|
-
_p()
|
|
1135
1198
|
shell_bin = os.environ.get("SHELL") or _sh.which("bash") or _sh.which("sh") or "/bin/sh"
|
|
1136
|
-
_info(f"
|
|
1199
|
+
_info(f"local shell {_dim(shell_bin)} {_dim('· tip: run akash login to unlock remote shells')}")
|
|
1137
1200
|
_p()
|
|
1138
|
-
import subprocess as _sp
|
|
1139
1201
|
result = _sp.run([shell_bin])
|
|
1140
1202
|
_p()
|
|
1141
1203
|
_info("session closed")
|
|
1142
1204
|
raise typer.Exit(result.returncode)
|
|
1143
1205
|
|
|
1206
|
+
# ── Server shell ──────────────────────────────────────────────────
|
|
1144
1207
|
if reauth:
|
|
1145
1208
|
_clear_cached_token()
|
|
1146
1209
|
_info("cleared cached token — re-authenticating…")
|
|
@@ -1172,23 +1235,43 @@ def devices(
|
|
|
1172
1235
|
revoke: Optional[str] = typer.Option(None, "--revoke", "-r", help="Revoke (unenroll) a device by label"),
|
|
1173
1236
|
raw: bool = typer.Option(False, "--json", help="Raw JSON output"),
|
|
1174
1237
|
):
|
|
1175
|
-
"""List enrolled devices or revoke one.
|
|
1238
|
+
"""List enrolled devices — or revoke one.
|
|
1176
1239
|
|
|
1177
|
-
Each device you
|
|
1178
|
-
|
|
1240
|
+
Each device you run `akash serve` on appears here.
|
|
1241
|
+
Connect from anywhere with: akash shell --device NAME
|
|
1179
1242
|
|
|
1180
1243
|
Examples:
|
|
1181
1244
|
akash devices list all enrolled devices
|
|
1182
1245
|
akash devices --revoke laptop remove a device
|
|
1246
|
+
|
|
1247
|
+
Setup:
|
|
1248
|
+
akash login --label phone name this device
|
|
1249
|
+
akash serve register it as reachable
|
|
1183
1250
|
"""
|
|
1184
1251
|
try:
|
|
1185
|
-
api_key, base = _resolve_client(require_key=
|
|
1252
|
+
api_key, base = _resolve_client(require_key=False)
|
|
1186
1253
|
except typer.Exit:
|
|
1187
1254
|
raise
|
|
1188
1255
|
except Exception as e:
|
|
1189
1256
|
_err(str(e))
|
|
1190
1257
|
raise typer.Exit(1)
|
|
1191
1258
|
|
|
1259
|
+
if not api_key:
|
|
1260
|
+
_p()
|
|
1261
|
+
_info("no devices yet — here's how to set up remote access:")
|
|
1262
|
+
_p()
|
|
1263
|
+
print(f" {_DM}Step 1 — on each device you want to reach:{_RS}")
|
|
1264
|
+
print(f" {_cy('akash login --label termux')} {_DM}(name this device){_RS}")
|
|
1265
|
+
print(f" {_cy('akash serve')} {_DM}(keep running — makes it reachable){_RS}")
|
|
1266
|
+
_p()
|
|
1267
|
+
print(f" {_DM}Step 2 — from anywhere:{_RS}")
|
|
1268
|
+
print(f" {_cy('akash shell --device termux')} {_DM}(open a shell on it){_RS}")
|
|
1269
|
+
print(f" {_cy('akash run --device termux \"cmd\"')} {_DM}(run a command on it){_RS}")
|
|
1270
|
+
_p()
|
|
1271
|
+
_info(f"Run {_cy('akash login')} first to save your API key.")
|
|
1272
|
+
_p()
|
|
1273
|
+
raise typer.Exit(0)
|
|
1274
|
+
|
|
1192
1275
|
_hdrs = {"x-api-key": api_key, "Origin": "https://akash.cli"}
|
|
1193
1276
|
|
|
1194
1277
|
if revoke:
|
|
@@ -1255,18 +1338,17 @@ def serve(
|
|
|
1255
1338
|
status: bool = typer.Option(False, "--status", help="Show serve daemon status"),
|
|
1256
1339
|
stop: bool = typer.Option(False, "--stop", help="Stop background serve daemon"),
|
|
1257
1340
|
):
|
|
1258
|
-
"""Make this device reachable
|
|
1341
|
+
"""Make this device reachable for remote shell and run.
|
|
1259
1342
|
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
no public IP required.
|
|
1343
|
+
Run this once on a device to register it. Then from any other
|
|
1344
|
+
machine: akash shell --device NAME or akash run --device NAME
|
|
1263
1345
|
|
|
1264
1346
|
Examples:
|
|
1265
|
-
akash serve
|
|
1266
|
-
akash serve --daemon
|
|
1267
|
-
akash serve --status
|
|
1268
|
-
akash serve --stop
|
|
1269
|
-
akash serve --label
|
|
1347
|
+
akash serve start in foreground (Ctrl-C to stop)
|
|
1348
|
+
akash serve --daemon run as background daemon
|
|
1349
|
+
akash serve --status check if daemon is running
|
|
1350
|
+
akash serve --stop stop the daemon
|
|
1351
|
+
akash serve --label myphone use a custom label
|
|
1270
1352
|
"""
|
|
1271
1353
|
from .relay import serve as _serve, serve_status, serve_stop
|
|
1272
1354
|
|
|
@@ -1277,7 +1359,8 @@ def serve(
|
|
|
1277
1359
|
_ok(f"running {_dim('pid ' + pid_str)}")
|
|
1278
1360
|
_info(f"logs {st['log']}")
|
|
1279
1361
|
else:
|
|
1280
|
-
_info("not running
|
|
1362
|
+
_info("not running")
|
|
1363
|
+
_info(f"start with: {_cy('akash serve')} or {_cy('akash serve --daemon')}")
|
|
1281
1364
|
return
|
|
1282
1365
|
|
|
1283
1366
|
if stop:
|
|
@@ -1288,13 +1371,23 @@ def serve(
|
|
|
1288
1371
|
return
|
|
1289
1372
|
|
|
1290
1373
|
try:
|
|
1291
|
-
api_key, base = _resolve_client(url, require_key=
|
|
1374
|
+
api_key, base = _resolve_client(url, require_key=False)
|
|
1292
1375
|
except typer.Exit:
|
|
1293
1376
|
raise
|
|
1294
1377
|
except Exception as e:
|
|
1295
1378
|
_err(str(e))
|
|
1296
1379
|
raise typer.Exit(1)
|
|
1297
1380
|
|
|
1381
|
+
if not api_key:
|
|
1382
|
+
_p()
|
|
1383
|
+
_warn("not logged in — can't register with relay server.")
|
|
1384
|
+
_p()
|
|
1385
|
+
_info("Run this first:")
|
|
1386
|
+
print(f" {_cy('akash login --label myphone')} {_DM}# name this device{_RS}")
|
|
1387
|
+
print(f" {_cy('akash serve')} {_DM}# then run this{_RS}")
|
|
1388
|
+
_p()
|
|
1389
|
+
raise typer.Exit(1)
|
|
1390
|
+
|
|
1298
1391
|
# Resolve label: flag > config > "default"
|
|
1299
1392
|
from .exec import _device_label as _cfg_label
|
|
1300
1393
|
resolved_label = (label or _cfg_label() or "default").strip()
|
|
@@ -1764,26 +1857,51 @@ def pipe(
|
|
|
1764
1857
|
_p()
|
|
1765
1858
|
|
|
1766
1859
|
|
|
1767
|
-
# ── akash log
|
|
1860
|
+
# ── akash log (+ --stats) ─────────────────────────────────────────────────────
|
|
1768
1861
|
|
|
1769
1862
|
@app.command(name="log")
|
|
1770
1863
|
def cmd_log(
|
|
1771
|
-
last: int = typer.Option(20,
|
|
1772
|
-
op: Optional[str] = typer.Option(None,"--op", "-o", help="Filter by operation type"),
|
|
1773
|
-
raw: bool = typer.Option(False,"--json",
|
|
1864
|
+
last: int = typer.Option(20, "--last", "-n", help="Show last N entries"),
|
|
1865
|
+
op: Optional[str] = typer.Option(None, "--op", "-o", help="Filter by operation type"),
|
|
1866
|
+
raw: bool = typer.Option(False, "--json", help="Raw JSON output"),
|
|
1867
|
+
stats: bool = typer.Option(False, "--stats", "-s", help="Show summary instead of list"),
|
|
1774
1868
|
):
|
|
1775
|
-
"""Show operation history
|
|
1869
|
+
"""Show operation history — or a summary with --stats.
|
|
1776
1870
|
|
|
1777
1871
|
Examples:
|
|
1778
1872
|
akash log last 20 operations
|
|
1873
|
+
akash log --stats summary with counts per operation
|
|
1779
1874
|
akash log --last 50 last 50 operations
|
|
1780
1875
|
akash log --op backup only backup operations
|
|
1781
1876
|
akash log --json raw JSON (pipe to jq)
|
|
1782
1877
|
"""
|
|
1783
|
-
from .logger import read_log
|
|
1878
|
+
from .logger import read_log, log_summary
|
|
1784
1879
|
|
|
1785
|
-
|
|
1880
|
+
if stats:
|
|
1881
|
+
s = log_summary()
|
|
1882
|
+
if s["total"] == 0:
|
|
1883
|
+
_info("no log entries yet — run some commands first")
|
|
1884
|
+
return
|
|
1885
|
+
_p()
|
|
1886
|
+
print(f" {_C}◈{_RS} operation history")
|
|
1887
|
+
_p()
|
|
1888
|
+
_info(f"total {_ye(str(s['total']))} "
|
|
1889
|
+
f"({_G}{s['ok']} ok{_RS}" +
|
|
1890
|
+
(f" {_R}{s['fail']} failed{_RS}" if s["fail"] else "") + ")")
|
|
1891
|
+
_p()
|
|
1892
|
+
for oper, count in sorted(s["by_op"].items(), key=lambda x: -x[1]):
|
|
1893
|
+
bar = "█" * min(count * 2, 24)
|
|
1894
|
+
print(f" {_C}{oper:<14}{_RS} {_DM}{bar}{_RS} {count}")
|
|
1895
|
+
_p()
|
|
1896
|
+
uploads = read_log(last_n=100, op_filter=None)
|
|
1897
|
+
recent = [e for e in uploads if e.get("op") in ("backup", "pipe", "send", "cloud_upload") and e.get("code")]
|
|
1898
|
+
if recent:
|
|
1899
|
+
lu = recent[-1]
|
|
1900
|
+
print(f" {_DM}last upload {_RS}{_ye(str(lu.get('code','?')))} {_dim(str(lu.get('ts',''))[:16].replace('T',' '))}")
|
|
1901
|
+
_p()
|
|
1902
|
+
return
|
|
1786
1903
|
|
|
1904
|
+
entries = read_log(last_n=last, op_filter=op)
|
|
1787
1905
|
if not entries:
|
|
1788
1906
|
_info("no log entries yet")
|
|
1789
1907
|
return
|
|
@@ -1798,11 +1916,7 @@ def cmd_log(
|
|
|
1798
1916
|
oper = e.get("op", "?")
|
|
1799
1917
|
ok = e.get("ok", True)
|
|
1800
1918
|
icon = f"{_G}✓{_RS}" if ok else f"{_R}✗{_RS}"
|
|
1801
|
-
|
|
1802
|
-
# Build a short detail string from the payload
|
|
1803
1919
|
detail_parts = []
|
|
1804
|
-
for skip in ("ts", "op", "ok", "error"):
|
|
1805
|
-
pass
|
|
1806
1920
|
for k, v in e.items():
|
|
1807
1921
|
if k in ("ts", "op", "ok"):
|
|
1808
1922
|
continue
|
|
@@ -1814,56 +1928,18 @@ def cmd_log(
|
|
|
1814
1928
|
detail_parts.append(f"{k}={_dim(str(v))}")
|
|
1815
1929
|
else:
|
|
1816
1930
|
detail_parts.append(f"{k}={_dim(str(v)[:40])}")
|
|
1817
|
-
|
|
1818
1931
|
detail = " ".join(detail_parts[:4])
|
|
1819
1932
|
print(f" {icon} {_GR}{ts}{_RS} {_C}{oper:<12}{_RS} {detail}")
|
|
1820
1933
|
_p()
|
|
1821
1934
|
|
|
1822
1935
|
|
|
1823
|
-
# ── akash stats ───────────────────────────────────────────────────────────────
|
|
1824
|
-
|
|
1825
|
-
@app.command()
|
|
1826
|
-
def stats():
|
|
1827
|
-
"""Show a summary of your akash operation history."""
|
|
1828
|
-
from .logger import log_summary, read_log
|
|
1829
|
-
|
|
1830
|
-
s = log_summary()
|
|
1831
|
-
if s["total"] == 0:
|
|
1832
|
-
_info("no log entries yet — run some commands first")
|
|
1833
|
-
return
|
|
1834
|
-
|
|
1835
|
-
_p()
|
|
1836
|
-
print(f" {_C}◈{_RS} operation history")
|
|
1837
|
-
_p()
|
|
1838
|
-
_info(f"total ops {_ye(str(s['total']))}")
|
|
1839
|
-
_info(f"succeeded {_G}{s['ok']}{_RS}")
|
|
1840
|
-
if s["fail"]:
|
|
1841
|
-
_info(f"failed {_R}{s['fail']}{_RS}")
|
|
1842
|
-
_p()
|
|
1843
|
-
print(f" {_DM}by operation:{_RS}")
|
|
1844
|
-
for op, count in sorted(s["by_op"].items(), key=lambda x: -x[1]):
|
|
1845
|
-
bar_w = min(count * 2, 20)
|
|
1846
|
-
bar = "█" * bar_w
|
|
1847
|
-
print(f" {_C}{op:<14}{_RS} {_dim(bar)} {count}")
|
|
1848
|
-
_p()
|
|
1849
|
-
|
|
1850
|
-
# Last upload code
|
|
1851
|
-
uploads = read_log(last_n=100, op_filter=None)
|
|
1852
|
-
recent = [e for e in uploads if e.get("op") in ("backup", "pipe", "send", "cloud_upload") and e.get("code")]
|
|
1853
|
-
if recent:
|
|
1854
|
-
last_up = recent[-1]
|
|
1855
|
-
print(f" {_DM}last upload {_RS}{_ye(str(last_up.get('code', '?')))} "
|
|
1856
|
-
f"{_dim(str(last_up.get('ts', ''))[:16].replace('T',' '))}")
|
|
1857
|
-
_p()
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
1936
|
# ── akash env ─────────────────────────────────────────────────────────────────
|
|
1861
1937
|
|
|
1862
1938
|
env_app = typer.Typer(
|
|
1863
|
-
help="
|
|
1939
|
+
help="Encrypted local .env store — set, get, push, pull.",
|
|
1864
1940
|
rich_markup_mode=None,
|
|
1865
1941
|
)
|
|
1866
|
-
app.add_typer(env_app, name="env", help="
|
|
1942
|
+
app.add_typer(env_app, name="env", help="set · get · push · pull encrypted .env variables")
|
|
1867
1943
|
|
|
1868
1944
|
|
|
1869
1945
|
@env_app.command("list")
|
|
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
|