akashcli 4.8.3__tar.gz → 4.8.5__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.
Files changed (33) hide show
  1. {akashcli-4.8.3 → akashcli-4.8.5}/PKG-INFO +1 -1
  2. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/__init__.py +1 -1
  3. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/cli.py +258 -35
  4. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/client.py +1 -1
  5. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/storage.py +4 -0
  6. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/uploader.py +5 -1
  7. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli.egg-info/PKG-INFO +1 -1
  8. {akashcli-4.8.3 → akashcli-4.8.5}/pyproject.toml +1 -1
  9. {akashcli-4.8.3 → akashcli-4.8.5}/README.md +0 -0
  10. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/_net.py +0 -0
  11. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/auth.py +0 -0
  12. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/config.py +0 -0
  13. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/crypto.py +0 -0
  14. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/env_manager.py +0 -0
  15. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/exceptions.py +0 -0
  16. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/exec.py +0 -0
  17. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/integrity.py +0 -0
  18. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/logger.py +0 -0
  19. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/models.py +0 -0
  20. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/p2p.py +0 -0
  21. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/packages.py +0 -0
  22. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/relay.py +0 -0
  23. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/restore.py +0 -0
  24. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/snapshot_crypto.py +0 -0
  25. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/terminal.py +0 -0
  26. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/updater.py +0 -0
  27. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli/utils.py +0 -0
  28. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli.egg-info/SOURCES.txt +0 -0
  29. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli.egg-info/dependency_links.txt +0 -0
  30. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli.egg-info/entry_points.txt +0 -0
  31. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli.egg-info/requires.txt +0 -0
  32. {akashcli-4.8.3 → akashcli-4.8.5}/akashcli.egg-info/top_level.txt +0 -0
  33. {akashcli-4.8.3 → akashcli-4.8.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akashcli
3
- Version: 4.8.3
3
+ Version: 4.8.5
4
4
  Summary: Akash CLI — secure remote shell relay, file vault, and device management
5
5
  License-Expression: MIT
6
6
  Keywords: akash,cli,relay,remote-shell,vault
@@ -2,7 +2,7 @@ try:
2
2
  from importlib.metadata import version
3
3
  __version__ = version("akashcli")
4
4
  except Exception:
5
- __version__ = "4.7.4"
5
+ __version__ = "4.8.5"
6
6
 
7
7
  AKASH_VERSION = __version__
8
8
 
@@ -471,8 +471,14 @@ def cloud_upload(
471
471
  password: Optional[str] = typer.Option(None, "--pass", "-p", help="Password-protect"),
472
472
  ):
473
473
  """Upload a file or text snippet."""
474
+ api_key, base = _resolve_client(require_key=False, silent=True)
475
+ if not api_key:
476
+ _err("Not logged in. You must be logged in to upload files.")
477
+ print(f" {_DM}Run {_C}akash login{_RS}{_DM} to log in, or set {_C}AKASH_API_KEY{_RS}{_DM}.{_RS}")
478
+ raise typer.Exit(1)
479
+
474
480
  try:
475
- client = AkashClient()
481
+ client = AkashClient(api_key=api_key, api_url=base)
476
482
 
477
483
  def _cb(done, total):
478
484
  bar = _progress_bar(done, total)
@@ -500,6 +506,19 @@ def cloud_upload(
500
506
  raise typer.Exit(1)
501
507
 
502
508
 
509
+ @app.command("upload")
510
+ def app_upload(
511
+ path: Optional[Path] = typer.Argument(None, help="File to upload"),
512
+ text: Optional[str] = typer.Option(None, "--text", "-t", help="Upload raw text"),
513
+ name: Optional[str] = typer.Option(None, "--name", "-n", help="Custom filename"),
514
+ burn: bool = typer.Option(False, "--burn", help="Delete after first download"),
515
+ expiry: int = typer.Option(0, "--expiry","-e",help="Expire in N days (0 = never)"),
516
+ password: Optional[str] = typer.Option(None, "--pass", "-p", help="Password-protect"),
517
+ ):
518
+ """Upload a file or text snippet (requires login)."""
519
+ return cloud_upload(path=path, text=text, name=name, burn=burn, expiry=expiry, password=password)
520
+
521
+
503
522
  # ── akash cloud files ─────────────────────────────────────────────────────────
504
523
 
505
524
  @cloud_app.command("files")
@@ -604,8 +623,14 @@ def cloud_manage(
604
623
  @cloud_app.command("bulk")
605
624
  def cloud_bulk(path: Path):
606
625
  """Recursively upload every file in a folder."""
626
+ api_key, base = _resolve_client(require_key=False, silent=True)
627
+ if not api_key:
628
+ _err("Not logged in. You must be logged in to upload files.")
629
+ print(f" {_DM}Run {_C}akash login{_RS}{_DM} to log in, or set {_C}AKASH_API_KEY{_RS}{_DM}.{_RS}")
630
+ raise typer.Exit(1)
631
+
607
632
  try:
608
- client = AkashClient()
633
+ client = AkashClient(api_key=api_key, api_url=base)
609
634
  uploader = VaultUploader(client)
610
635
  all_files = [p for p in path.glob("**/*") if p.is_file() and not p.name.startswith(".")]
611
636
  for i, file in enumerate(all_files, 1):
@@ -2008,8 +2033,14 @@ def send(
2008
2033
  _ok(f"encrypted {_dim(f'{size_kb:.1f} KB → {enc_kb:.1f} KB')}")
2009
2034
 
2010
2035
  # ── 4. Upload envelope ────────────────────────────────────────────────────
2036
+ api_key, base = _resolve_client(require_key=False, silent=True)
2037
+ if not api_key:
2038
+ _err("Not logged in. You must be logged in to send files.")
2039
+ print(f" {_DM}Run {_C}akash login{_RS}{_DM} to log in, or set {_C}AKASH_API_KEY{_RS}{_DM}.{_RS}")
2040
+ raise typer.Exit(1)
2041
+
2011
2042
  try:
2012
- client = AkashClient()
2043
+ client = AkashClient(api_key=api_key, api_url=base)
2013
2044
  except Exception as e:
2014
2045
  _err(f"not logged in: {e}")
2015
2046
  raise typer.Exit(1)
@@ -2086,8 +2117,14 @@ def inbox(
2086
2117
  _err(f"failed to load identity: {e}")
2087
2118
  raise typer.Exit(1)
2088
2119
 
2120
+ api_key, base = _resolve_client(require_key=False, silent=True)
2121
+ if not api_key:
2122
+ _err("Not logged in. You must be logged in to view your inbox.")
2123
+ print(f" {_DM}Run {_C}akash login{_RS}{_DM} to log in, or set {_C}AKASH_API_KEY{_RS}{_DM}.{_RS}")
2124
+ raise typer.Exit(1)
2125
+
2089
2126
  try:
2090
- client = AkashClient()
2127
+ client = AkashClient(api_key=api_key, api_url=base)
2091
2128
  except Exception as e:
2092
2129
  _err(f"not logged in: {e}")
2093
2130
  raise typer.Exit(1)
@@ -2264,8 +2301,14 @@ def pipe(
2264
2301
  from .logger import log_op
2265
2302
  import io
2266
2303
 
2304
+ api_key, base = _resolve_client(require_key=False, silent=True)
2305
+ if not api_key:
2306
+ _err("Not logged in. You must be logged in to pipe data to the cloud.")
2307
+ print(f" {_DM}Run {_C}akash login{_RS}{_DM} to log in, or set {_C}AKASH_API_KEY{_RS}{_DM}.{_RS}")
2308
+ raise typer.Exit(1)
2309
+
2267
2310
  try:
2268
- client = AkashClient()
2311
+ client = AkashClient(api_key=api_key, api_url=base)
2269
2312
  except Exception as e:
2270
2313
  _err(f"not logged in: {e}")
2271
2314
  raise typer.Exit(1)
@@ -2644,8 +2687,14 @@ def env_push(
2644
2687
  from .logger import log_op
2645
2688
  import tempfile as _tf
2646
2689
 
2690
+ api_key, base = _resolve_client(require_key=False, silent=True)
2691
+ if not api_key:
2692
+ _err("Not logged in. You must be logged in to push environment variables.")
2693
+ print(f" {_DM}Run {_C}akash login{_RS}{_DM} to log in, or set {_C}AKASH_API_KEY{_RS}{_DM}.{_RS}")
2694
+ raise typer.Exit(1)
2695
+
2647
2696
  try:
2648
- client = AkashClient()
2697
+ client = AkashClient(api_key=api_key, api_url=base)
2649
2698
  except Exception as e:
2650
2699
  _err(f"not logged in: {e}")
2651
2700
  raise typer.Exit(1)
@@ -2708,8 +2757,14 @@ def env_pull(
2708
2757
  from .env_manager import env_save_blob
2709
2758
  from .logger import log_op
2710
2759
 
2760
+ api_key, base = _resolve_client(require_key=False, silent=True)
2761
+ if not api_key:
2762
+ _err("Not logged in. You must be logged in to pull environment variables.")
2763
+ print(f" {_DM}Run {_C}akash login{_RS}{_DM} to log in, or set {_C}AKASH_API_KEY{_RS}{_DM}.{_RS}")
2764
+ raise typer.Exit(1)
2765
+
2711
2766
  try:
2712
- client = AkashClient()
2767
+ client = AkashClient(api_key=api_key, api_url=base)
2713
2768
  except Exception as e:
2714
2769
  _err(f"not logged in: {e}")
2715
2770
  raise typer.Exit(1)
@@ -2819,11 +2874,35 @@ def agent_list(raw: bool = typer.Option(False, "--json", help="Raw JSON output")
2819
2874
  print(f" {_DM}{hdr_status}{hdr_id}{hdr_name}{hdr_cid}{hdr_scopes}{_RS}")
2820
2875
  print(f" {_GR}{'─'*76}{_RS}")
2821
2876
 
2877
+ import datetime
2878
+ now_utc = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
2879
+
2822
2880
  last_suffix = ""
2881
+ expired_count = 0
2823
2882
  for pid, data in agents.items():
2824
2883
  is_default = (pid == default_pid)
2825
- status_txt = "● active" if is_default else "○ idle"
2826
- status_color = _G if is_default else _DM
2884
+
2885
+ # Check expiration status
2886
+ is_expired = False
2887
+ exp_val = data.get("expires_at")
2888
+ if exp_val:
2889
+ try:
2890
+ exp_dt = datetime.datetime.fromisoformat(exp_val.replace("Z", "+00:00")).replace(tzinfo=None)
2891
+ if exp_dt < now_utc:
2892
+ is_expired = True
2893
+ except Exception:
2894
+ pass
2895
+
2896
+ if is_expired:
2897
+ expired_count += 1
2898
+ status_txt = "✗ expired"
2899
+ status_color = _R
2900
+ elif is_default:
2901
+ status_txt = "● active"
2902
+ status_color = _G
2903
+ else:
2904
+ status_txt = "○ idle"
2905
+ status_color = _DM
2827
2906
 
2828
2907
  name = data.get("name", "Agent")
2829
2908
  if len(name) > 14:
@@ -2856,12 +2935,16 @@ def agent_list(raw: bool = typer.Option(False, "--json", help="Raw JSON output")
2856
2935
  print(f" {c_status}{c_id}{c_name}{c_cid}{c_scopes}")
2857
2936
 
2858
2937
  print(f" {_GR}{'─'*76}{_RS}")
2859
- _p()
2938
+ if expired_count > 0:
2939
+ print(f" {_Y}⚡{_RS} {_W}{expired_count} profile(s) expired.{_RS} {_DM}Run {_cy('akash mcp renew <id>')} to extend or {_cy('akash mcp rm --expired')} to clean up.{_RS}\n")
2940
+ else:
2941
+ _p()
2860
2942
  print(f" {_W}Quick Launch:{_RS}")
2861
2943
  print(f" • Use default profile : {_C}akash mcp serve{_RS}")
2862
2944
  suffix_hint = f" {_dim('(or: --id ' + last_suffix + ')')}" if last_suffix else ""
2863
2945
  print(f" • Use specific ID : {_C}akash mcp serve --id {default_pid}{_RS}{suffix_hint}")
2864
- print(f" • Inspect permissions : {_C}akash mcp info {default_pid}{_RS}")
2946
+ print(f" • Clean idle profiles : {_C}akash mcp rm --all{_RS}")
2947
+ print(f" • Renew expiration : {_C}akash mcp renew {default_pid} --days 30{_RS}")
2865
2948
  print(f" • Create new profile : {_C}akash mcp create -n Dev -r @coder{_RS}")
2866
2949
  _p()
2867
2950
 
@@ -3001,6 +3084,12 @@ def agent_info(
3001
3084
  status = d.get("status", "active") + " (live server)"
3002
3085
  exp = d.get("expires_at", "unknown")[:19].replace("T", " ")
3003
3086
  is_live = True
3087
+ if pid in all_a and d.get("expires_at"):
3088
+ all_a[pid]["expires_at"] = d.get("expires_at")
3089
+ try:
3090
+ agents_file.write_text(json.dumps(all_a, indent=2))
3091
+ except Exception:
3092
+ pass
3004
3093
  elif r.status_code == 404:
3005
3094
  status = "not found on server"
3006
3095
  except Exception:
@@ -3075,42 +3164,175 @@ def agent_update(
3075
3164
  else:
3076
3165
  _err(f"Failed to update scopes: {res.text}")
3077
3166
 
3078
- @agent_app.command("revoke")
3079
- def agent_revoke(
3080
- target: Optional[str] = typer.Argument(None, help="Profile ID (e.g. fbf6) or Client ID"),
3081
- id: Optional[str] = typer.Option(None, "--id", help="Profile ID (e.g. fbf6)"),
3082
- url: Optional[str] = typer.Option(None, "--url", help="Override server URL"),
3167
+ @agent_app.command("rm")
3168
+ @agent_app.command("remove", hidden=True)
3169
+ @agent_app.command("delete", hidden=True)
3170
+ @agent_app.command("revoke", hidden=True)
3171
+ def agent_remove(
3172
+ target: Optional[str] = typer.Argument(None, help="Profile ID (e.g. 88d7), Client ID suffix (e.g. ddff), or name"),
3173
+ all_idle: bool = typer.Option(False, "--all", "-a", help="Remove all idle profiles, keeping active default"),
3174
+ expired_only: bool = typer.Option(False, "--expired", "-e", help="Remove all expired profiles"),
3175
+ yes: bool = typer.Option(False, "--yes", "-y", "--force", "-f", help="Skip confirmation prompt"),
3176
+ url: Optional[str] = typer.Option(None, "--url", help="Override server URL"),
3083
3177
  ):
3084
- """Revoke an agent's access in one command using its short Profile ID."""
3085
- target_id = id or target
3086
- if not target_id:
3087
- _err("Provide a Profile ID (e.g. 'akash agent revoke fbf6')")
3178
+ """Remove / clean up one or more MCP profile IDs locally and on gateway."""
3179
+ import json, datetime
3180
+ from pathlib import Path
3181
+ agents_file = Path.home() / ".akash" / "agents.json"
3182
+ if not agents_file.exists():
3183
+ _info("No saved profiles to remove.")
3184
+ return
3185
+
3186
+ try:
3187
+ agents = json.loads(agents_file.read_text(encoding="utf-8"))
3188
+ except Exception:
3189
+ agents = {}
3190
+
3191
+ if not agents:
3192
+ _info("No saved profiles to remove.")
3193
+ return
3194
+
3195
+ default_pid = next(reversed(list(agents.keys())))
3196
+ now_utc = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
3197
+
3198
+ targets_to_delete = []
3199
+
3200
+ if all_idle:
3201
+ for pid in list(agents.keys()):
3202
+ if pid != default_pid:
3203
+ targets_to_delete.append(pid)
3204
+ if not targets_to_delete:
3205
+ _info("No idle profiles to remove (only active profile exists).")
3206
+ return
3207
+ if not yes:
3208
+ try:
3209
+ sys.stdout.write(f" Remove {len(targets_to_delete)} idle profile(s)? [y/N]: ")
3210
+ sys.stdout.flush()
3211
+ ans = sys.stdin.readline().strip().lower()
3212
+ if ans not in ("y", "yes"):
3213
+ _info("Aborted.")
3214
+ return
3215
+ except (KeyboardInterrupt, EOFError):
3216
+ return
3217
+
3218
+ elif expired_only:
3219
+ for pid, d in list(agents.items()):
3220
+ exp_str = d.get("expires_at")
3221
+ if exp_str:
3222
+ try:
3223
+ exp_dt = datetime.datetime.fromisoformat(exp_str.replace("Z", "+00:00")).replace(tzinfo=None)
3224
+ if exp_dt < now_utc:
3225
+ targets_to_delete.append(pid)
3226
+ except Exception:
3227
+ pass
3228
+ if not targets_to_delete:
3229
+ _info("No expired profiles found to remove.")
3230
+ return
3231
+
3232
+ elif target:
3233
+ pid, cid, name = _resolve_agent_id(target)
3234
+ if pid not in agents:
3235
+ _err(f"Profile '{target}' not found in saved profiles.")
3236
+ raise typer.Exit(1)
3237
+ targets_to_delete.append(pid)
3238
+
3239
+ else:
3240
+ _err("Specify a Profile ID to remove (e.g. 'akash mcp rm 88d7'), or use '--all' / '--expired'.")
3241
+ _info(f"Run {_cy('akash mcp')} to view all IDs.")
3088
3242
  raise typer.Exit(1)
3089
3243
 
3090
- pid, cid, name = _resolve_agent_id(target_id)
3091
- api_key, base = _resolve_client(url, require_key=False, silent=True)
3092
- hdrs = {"Origin": "https://akash.cli", "x-device-id": _device_fingerprint()}
3093
- if api_key: hdrs["x-api-key"] = api_key
3244
+ _p()
3245
+ removed_count = 0
3246
+ for pid in targets_to_delete:
3247
+ d = agents.get(pid, {})
3248
+ name = d.get("name", "Agent")
3249
+ cid = d.get("client_id", "")
3250
+ suffix = cid[-4:] if len(cid) >= 4 else ""
3094
3251
 
3095
- with _Spin(f"revoking credentials for {pid} ({name})…"):
3096
- res = httpx.delete(f"{base}/api/agent/app/{cid}", headers=hdrs, timeout=10.0)
3252
+ # Best-effort remote revocation on gateway
3253
+ target_url = url or d.get("url")
3254
+ _, base = _resolve_client(target_url, require_key=False, silent=True)
3255
+ if cid and base:
3256
+ try:
3257
+ httpx.delete(f"{base}/api/agent/app/{cid}", timeout=3.0)
3258
+ except Exception:
3259
+ pass
3260
+
3261
+ if pid in agents:
3262
+ del agents[pid]
3263
+ removed_count += 1
3264
+ print(f" {_G}✓{_RS} Removed profile {_W}{name}{_RS} ({_ye(pid)}{_dim(' · ...' + suffix if suffix else '')})")
3097
3265
 
3098
- # Clean local profile if present
3266
+ try:
3267
+ agents_file.write_text(json.dumps(agents, indent=2))
3268
+ except Exception:
3269
+ pass
3270
+
3271
+ _p()
3272
+ _ok(f"Cleaned up {removed_count} MCP profile(s). {len(agents)} profile(s) remaining.")
3273
+ _p()
3274
+
3275
+ @agent_app.command("renew")
3276
+ def agent_renew(
3277
+ target: Optional[str] = typer.Argument(None, help="Profile ID, Client ID suffix, or name (defaults to active profile)"),
3278
+ days: int = typer.Option(7, "--days", "-d", help="Number of days to extend validity (default: 7)"),
3279
+ url: Optional[str] = typer.Option(None, "--url", help="Override server URL"),
3280
+ ):
3281
+ """Renew/extend the validity of an MCP profile / agent ID."""
3099
3282
  import json
3100
3283
  from pathlib import Path
3101
3284
  agents_file = Path.home() / ".akash" / "agents.json"
3102
- if agents_file.exists():
3285
+ if not agents_file.exists():
3286
+ _err("No saved profiles found.")
3287
+ raise typer.Exit(1)
3288
+
3289
+ try:
3290
+ agents = json.loads(agents_file.read_text(encoding="utf-8"))
3291
+ except Exception:
3292
+ agents = {}
3293
+
3294
+ if not agents:
3295
+ _err("No saved profiles found.")
3296
+ raise typer.Exit(1)
3297
+
3298
+ target_id = target
3299
+ if not target_id:
3300
+ target_id = next(reversed(list(agents.keys())))
3301
+
3302
+ pid, cid, name = _resolve_agent_id(target_id)
3303
+ cached = agents.get(pid, {})
3304
+ target_url = url or cached.get("url")
3305
+ _, base = _resolve_client(target_url, require_key=False, silent=True)
3306
+
3307
+ with _Spin(f"renewing MCP profile {pid} ({name}) for {days} days…"):
3103
3308
  try:
3104
- agents = json.loads(agents_file.read_text())
3105
- if pid in agents:
3106
- del agents[pid]
3107
- agents_file.write_text(json.dumps(agents, indent=2))
3108
- except Exception: pass
3309
+ res = httpx.patch(
3310
+ f"{base}/api/agent/app/{cid}",
3311
+ json={"renew": True, "ttl_days": days},
3312
+ timeout=8.0
3313
+ )
3314
+ except Exception as e:
3315
+ _err(f"Network error contacting gateway ({base}): {e}")
3316
+ raise typer.Exit(1)
3109
3317
 
3110
3318
  if res.status_code == 200:
3111
- _ok(f"Agent {_ye(pid)} ({name}) revoked successfully!")
3319
+ d = res.json()
3320
+ new_exp = d.get("expires_at", "")
3321
+ if pid in agents:
3322
+ agents[pid]["expires_at"] = new_exp
3323
+ try:
3324
+ agents_file.write_text(json.dumps(agents, indent=2))
3325
+ except Exception:
3326
+ pass
3327
+ _p()
3328
+ _ok(f"Successfully renewed profile {_ye(name)} ({_C}{pid}{_RS} · {_dim('...' + cid[-4:])}) for {_G}{days} days{_RS}!")
3329
+ if new_exp:
3330
+ exp_clean = new_exp[:19].replace("T", " ")
3331
+ print(f" {_DM}New Expiration : {_RS}{_cy(exp_clean)} UTC")
3332
+ _p()
3112
3333
  else:
3113
- _ok(f"Agent {_ye(pid)} removed locally.")
3334
+ _err(f"Failed to renew profile on gateway: HTTP {res.status_code} - {res.text}")
3335
+ raise typer.Exit(1)
3114
3336
 
3115
3337
 
3116
3338
  @agent_app.command("create")
@@ -3161,6 +3383,7 @@ def agent_create(
3161
3383
  "client_secret": d["client_secret"],
3162
3384
  "name": name,
3163
3385
  "scopes": d.get("scopes", effective_scopes),
3386
+ "expires_at": d.get("expires_at"),
3164
3387
  "url": base
3165
3388
  }
3166
3389
  agents_file.write_text(json.dumps(agents, indent=2))
@@ -20,7 +20,7 @@ class AkashClient:
20
20
  _patch_dns(_up(self.base_url).hostname or "")
21
21
 
22
22
  if not self.api_key:
23
- raise AuthenticationError("No API Key found. Run 'akash login' or set AKASH_API_KEY env var.")
23
+ raise AuthenticationError("User is not logged in. An API key is required. Run 'akash login' or set AKASH_API_KEY env var.")
24
24
 
25
25
  def _get_headers(self, custom: dict = None):
26
26
  h = {"x-api-key": self.api_key, "Origin": "https://akash.cli"}
@@ -379,6 +379,10 @@ def upload_large_file(
379
379
  progress_cb: Optional[Callable[[int, int], None]] = None,
380
380
  ) -> dict:
381
381
  from .uploader import VaultUploader
382
+ from .exceptions import AuthenticationError
383
+
384
+ if not getattr(client, "api_key", None):
385
+ raise AuthenticationError("User is not logged in. An API key is required to upload files. Run 'akash login'.")
382
386
 
383
387
  size = path.stat().st_size
384
388
  digest = sha256_file(path)
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  from typing import Optional, Callable, List
7
7
  from concurrent.futures import ThreadPoolExecutor
8
8
  from .models import UploadResult
9
- from .exceptions import UploadFailed, QuotaExceeded
9
+ from .exceptions import UploadFailed, QuotaExceeded, AuthenticationError
10
10
 
11
11
  class VaultUploader:
12
12
  def __init__(self, client):
@@ -26,6 +26,8 @@ class VaultUploader:
26
26
  Standardizes both File and Text uploads into a single stream.
27
27
  progress_callback signature: (bytes_read, total_bytes)
28
28
  """
29
+ if not getattr(self.client, "api_key", None):
30
+ raise AuthenticationError("User is not logged in. An API key is required to upload files. Run 'akash login'.")
29
31
 
30
32
  # 1. Prepare Headers based on V3 Power-Control Specs
31
33
  headers = {
@@ -220,6 +222,8 @@ class VaultUploader:
220
222
 
221
223
  def upload_directory(self, folder_path: str, max_workers: int = 3) -> List[UploadResult]:
222
224
  """Recursively uploads all files in a folder using a thread pool."""
225
+ if not getattr(self.client, "api_key", None):
226
+ raise AuthenticationError("User is not logged in. An API key is required to upload files. Run 'akash login'.")
223
227
  # Convert string path to Path object
224
228
  root = Path(folder_path)
225
229
  if not root.is_dir():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akashcli
3
- Version: 4.8.3
3
+ Version: 4.8.5
4
4
  Summary: Akash CLI — secure remote shell relay, file vault, and device management
5
5
  License-Expression: MIT
6
6
  Keywords: akash,cli,relay,remote-shell,vault
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "akashcli"
3
- version = "4.8.3"
3
+ version = "4.8.5"
4
4
  description = "Akash CLI — secure remote shell relay, file vault, and device management"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
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