akashcli 4.8.0__tar.gz → 4.8.2__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.0 → akashcli-4.8.2}/PKG-INFO +1 -1
  2. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/cli.py +276 -18
  3. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli.egg-info/PKG-INFO +1 -1
  4. {akashcli-4.8.0 → akashcli-4.8.2}/pyproject.toml +1 -1
  5. {akashcli-4.8.0 → akashcli-4.8.2}/README.md +0 -0
  6. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/__init__.py +0 -0
  7. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/_net.py +0 -0
  8. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/auth.py +0 -0
  9. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/client.py +0 -0
  10. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/config.py +0 -0
  11. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/crypto.py +0 -0
  12. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/env_manager.py +0 -0
  13. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/exceptions.py +0 -0
  14. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/exec.py +0 -0
  15. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/integrity.py +0 -0
  16. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/logger.py +0 -0
  17. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/models.py +0 -0
  18. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/p2p.py +0 -0
  19. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/packages.py +0 -0
  20. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/relay.py +0 -0
  21. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/restore.py +0 -0
  22. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/snapshot_crypto.py +0 -0
  23. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/storage.py +0 -0
  24. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/terminal.py +0 -0
  25. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/updater.py +0 -0
  26. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/uploader.py +0 -0
  27. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/utils.py +0 -0
  28. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli.egg-info/SOURCES.txt +0 -0
  29. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli.egg-info/dependency_links.txt +0 -0
  30. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli.egg-info/entry_points.txt +0 -0
  31. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli.egg-info/requires.txt +0 -0
  32. {akashcli-4.8.0 → akashcli-4.8.2}/akashcli.egg-info/top_level.txt +0 -0
  33. {akashcli-4.8.0 → akashcli-4.8.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akashcli
3
- Version: 4.8.0
3
+ Version: 4.8.2
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
@@ -51,7 +51,7 @@ class _Spin:
51
51
  _FRAMES = ("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏")
52
52
 
53
53
  def __init__(self, label: str, min_seconds: float = 0.35, disabled: bool = False, prefix: str = ""):
54
- self._label = label
54
+ self._label = re.sub(r'[\r\n\t]+', ' ', str(label)).strip()
55
55
  self._prefix = prefix
56
56
  self._disabled = disabled or (not sys.stdout.isatty())
57
57
  self._min_seconds = min_seconds
@@ -90,7 +90,7 @@ class _Spin:
90
90
  return self
91
91
 
92
92
  def update(self, label: str):
93
- self._label = label
93
+ self._label = re.sub(r'[\r\n\t]+', ' ', str(label)).strip()
94
94
 
95
95
  def __exit__(self, *_):
96
96
  if self._disabled:
@@ -2854,33 +2854,50 @@ def _resolve_agent_id(target_id: str) -> tuple[str, str, str]:
2854
2854
 
2855
2855
  @agent_app.command("scopes")
2856
2856
  def agent_scopes():
2857
- """View directory of all available roles, scopes, and permission rules with animation."""
2857
+ """View directory of all available roles, presets, scopes, and tools."""
2858
2858
  with _Spin("loading scope directory…"):
2859
2859
  pass
2860
2860
  _p()
2861
- print(f" {_C}◈{_RS} {_W}Akash Agent Scopes & Roles Directory{_RS}")
2861
+ print(f" {_C}◈{_RS} {_W}Akash Agent Scopes & Role Presets Directory{_RS}")
2862
+ print(f" {_GR}────────────────────────────────────────────────────────────{_RS}")
2862
2863
  _p()
2863
- print(f" {_DM}ROLE PRESETS (Recommended):{_RS}")
2864
- print(f" {_cy('@viewer')} {_DM}: Read-Only Inspector (Cannot edit code, delete files, or install packages){_RS}")
2865
- print(f" {_dim('Scopes: workspace:read, exec:read_only, fs:cwd_only')}")
2866
- print(f" {_cy('@coder')} {_DM}: Safe Developer (Can edit code and test; file deletions blocked){_RS}")
2864
+ print(f" {_W}ROLE PRESETS:{_RS}")
2865
+ print(f" {_cy('@viewer')} {_DM}: Read-Only Inspector (Cannot edit code, delete files, or run mutating commands){_RS}")
2866
+ print(f" {_dim('Scopes: workspace:read, exec:read_only, fs:cwd_only, limits:max_commands=50')}")
2867
+ print(f" {_cy('@editor')} {_DM}: Safe Editor (Can create and edit files; cannot run shell or delete){_RS}")
2868
+ print(f" {_dim('Scopes: workspace:read, workspace:write, fs:cwd_only, vault:checkpoint')}")
2869
+ print(f" {_cy('@coder')} {_DM}: Safe Developer (Can edit code, build, and test; file deletions blocked){_RS}")
2867
2870
  print(f" {_dim('Scopes: workspace:read, workspace:write, exec:build, fs:cwd_only, fs:no_delete, vault:checkpoint')}")
2868
2871
  print(f" {_cy('@tester')} {_DM}: QA Runner (Can build and test; cannot edit source files){_RS}")
2869
2872
  print(f" {_dim('Scopes: workspace:read, exec:build, fs:cwd_only, limits:timeout=30')}")
2873
+ print(f" {_cy('@admin')} {_DM}: Full DevOps & Package Manager (Can edit, install packages, network calls){_RS}")
2874
+ print(f" {_dim('Scopes: workspace:read, workspace:write, exec:build, exec:package_install, exec:network, vault:checkpoint')}")
2870
2875
  print(f" {_cy('@full')} {_DM}: Unrestricted Master Access (*){_RS}")
2871
2876
  _p()
2872
- print(f" {_DM}GRANULAR PERMISSION SCOPES:{_RS}")
2873
- print(f" {_ye('exec:read_only')} {_DM}Blocks all mutating commands (touch, mkdir, git commit, sed){_RS}")
2874
- print(f" {_ye('fs:no_delete')} {_DM}Blocks destructive deletions (rm, unlink, git rm){_RS}")
2875
- print(f" {_ye('exec:package_install')} {_DM}Enables installing packages (pip install, npm install){_RS}")
2877
+ print(f" {_W}GRANULAR PERMISSION SCOPES:{_RS}")
2878
+ print(f" {_ye('workspace:read')} {_DM}Enables inspecting files via akash_read_file and akash_list_dir{_RS}")
2879
+ print(f" {_ye('workspace:write')} {_DM}Enables writing files via akash_write_file{_RS}")
2880
+ print(f" {_ye('exec:read_only')} {_DM}Blocks mutating terminal commands (touch, mkdir, sed, git commit){_RS}")
2881
+ print(f" {_ye('fs:no_delete')} {_DM}Blocks destructive deletions (rm, unlink, git rm, rmdir){_RS}")
2882
+ print(f" {_ye('exec:package_install')} {_DM}Enables package managers (pip install, npm install, apt){_RS}")
2876
2883
  print(f" {_ye('exec:network')} {_DM}Enables outbound network pipes (curl, wget){_RS}")
2877
- print(f" {_ye('fs:cwd_only')} {_DM}Restricts operations strictly to current project root{_RS}")
2878
- print(f" {_ye('vault:checkpoint')} {_DM}Enables time-machine undo snapshots before AI edits{_RS}")
2884
+ print(f" {_ye('fs:cwd_only')} {_DM}Restricts operations strictly to current working directory{_RS}")
2885
+ print(f" {_ye('vault:checkpoint')} {_DM}Enables automatic undo rollback snapshots before AI edits{_RS}")
2879
2886
  _p()
2880
- print(f" {_DM}HOW TO ASSIGN OR UPDATE:{_RS}")
2881
- print(f" Create with role : {_C}akash agent create --name 'Agent' --role @coder{_RS}")
2887
+ print(f" {_W}AVAILABLE MCP TOOLS:{_RS}")
2888
+ print(f" • {_C}akash_exec_command{_RS} {_DM}: Safe terminal runner with secret redaction and process timeouts{_RS}")
2889
+ print(f" • {_C}akash_read_file{_RS} {_DM}: Read files with line-range pagination directly into AI context{_RS}")
2890
+ print(f" • {_C}akash_write_file{_RS} {_DM}: Write or update files safely on device{_RS}")
2891
+ print(f" • {_C}akash_list_dir{_RS} {_DM}: Browse directory trees with sizes, types, and depth control{_RS}")
2892
+ print(f" • {_C}akash_checkpoint_create{_RS} {_DM}: Create an encrypted rollback snapshot before major changes{_RS}")
2893
+ print(f" • {_C}akash_undo{_RS} {_DM}: Revert workspace to pre-action state{_RS}")
2894
+ print(f" • {_C}akash_scope_details{_RS} {_DM}: Introspect active permissions and security rules{_RS}")
2895
+ _p()
2896
+ print(f" {_W}HOW TO ASSIGN OR UPDATE:{_RS}")
2897
+ print(f" Create with role : {_C}akash agent create -n 'Dev' -r @coder{_RS}")
2898
+ print(f" Switch role : {_C}akash agent update --id <ID> --role @admin{_RS}")
2882
2899
  print(f" Add a scope : {_C}akash agent update --id <ID> --scopes +exec:package_install{_RS}")
2883
- print(f" Switch role : {_C}akash agent update --id <ID> --role @viewer{_RS}")
2900
+ print(f" Inspect agent : {_C}akash agent info <ID>{_RS}")
2884
2901
  _p()
2885
2902
 
2886
2903
  @agent_app.command("info")
@@ -3363,6 +3380,185 @@ def _parse_temp_duration(val: str) -> int:
3363
3380
  elif unit in ('d', 'day', 'days'): return num * 1440
3364
3381
  return num
3365
3382
 
3383
+ def _handle_tool_read_file(args: dict, scopes: str) -> tuple[str, bool]:
3384
+ path_str = args.get("path", "").strip()
3385
+ if not path_str:
3386
+ return "Error: 'path' argument is required.", True
3387
+
3388
+ target_path = Path(path_str).expanduser()
3389
+ if not target_path.is_absolute():
3390
+ target_path = (Path.cwd() / target_path).resolve()
3391
+ else:
3392
+ target_path = target_path.resolve()
3393
+
3394
+ scopes_list = [s.strip().lower() for s in scopes.split(",") if s.strip()]
3395
+ if "*" not in scopes_list and "@full" not in scopes_list:
3396
+ if "fs:cwd_only" in scopes_list:
3397
+ cwd = Path.cwd().resolve()
3398
+ try:
3399
+ target_path.relative_to(cwd)
3400
+ except ValueError:
3401
+ return f"⛔ Permission denied by scope 'fs:cwd_only': Path '{path_str}' is outside working directory ({cwd}).", True
3402
+
3403
+ if not target_path.exists():
3404
+ return f"Error: File '{path_str}' not found.", True
3405
+ if target_path.is_dir():
3406
+ return f"Error: '{path_str}' is a directory, not a file. Use akash_list_dir to view directory contents.", True
3407
+
3408
+ start_line = int(args.get("start_line", 1) or 1)
3409
+ end_line = args.get("end_line")
3410
+ end_line = int(end_line) if end_line is not None else None
3411
+ max_bytes = int(args.get("max_bytes", 65536) or 65536)
3412
+
3413
+ try:
3414
+ with open(target_path, "rb") as f:
3415
+ chunk = f.read(1024)
3416
+ if b"\x00" in chunk:
3417
+ return f"Binary file detected ({target_path.stat().st_size} bytes). Raw binary cannot be rendered in text context.", True
3418
+
3419
+ with open(target_path, "r", encoding="utf-8", errors="replace") as f:
3420
+ lines = f.readlines()
3421
+
3422
+ total_lines = len(lines)
3423
+ s_idx = max(0, start_line - 1)
3424
+ e_idx = end_line if end_line is not None else total_lines
3425
+ selected_lines = lines[s_idx:e_idx]
3426
+
3427
+ content = "".join(selected_lines)
3428
+ if len(content.encode("utf-8")) > max_bytes:
3429
+ content = content[:max_bytes] + f"\n... [Output truncated at {max_bytes} bytes]"
3430
+
3431
+ rel_name = target_path.name
3432
+ header = f"=== File: {rel_name} (Lines {s_idx + 1}-{min(e_idx, total_lines)} of {total_lines}) ===\n"
3433
+ return header + content, False
3434
+ except Exception as e:
3435
+ return f"Error reading file '{path_str}': {e}", True
3436
+
3437
+ def _handle_tool_write_file(args: dict, scopes: str) -> tuple[str, bool]:
3438
+ path_str = args.get("path", "").strip()
3439
+ content = args.get("content", "")
3440
+ overwrite = args.get("overwrite", True)
3441
+
3442
+ if not path_str:
3443
+ return "Error: 'path' argument is required.", True
3444
+
3445
+ target_path = Path(path_str).expanduser()
3446
+ if not target_path.is_absolute():
3447
+ target_path = (Path.cwd() / target_path).resolve()
3448
+ else:
3449
+ target_path = target_path.resolve()
3450
+
3451
+ scopes_list = [s.strip().lower() for s in scopes.split(",") if s.strip()]
3452
+ if "*" not in scopes_list and "@full" not in scopes_list:
3453
+ if "exec:read_only" in scopes_list or "@viewer" in scopes_list:
3454
+ return "⛔ Permission denied: Agent is in read-only mode ('exec:read_only'). File writing is blocked.", True
3455
+ if "fs:cwd_only" in scopes_list:
3456
+ cwd = Path.cwd().resolve()
3457
+ try:
3458
+ target_path.relative_to(cwd)
3459
+ except ValueError:
3460
+ return f"⛔ Permission denied by scope 'fs:cwd_only': Path '{path_str}' is outside working directory ({cwd}).", True
3461
+
3462
+ if target_path.exists() and not overwrite:
3463
+ return f"Error: File '{path_str}' already exists and overwrite is set to False.", True
3464
+
3465
+ try:
3466
+ target_path.parent.mkdir(parents=True, exist_ok=True)
3467
+ target_path.write_text(content, encoding="utf-8")
3468
+ byte_count = len(content.encode("utf-8"))
3469
+ line_count = len(content.splitlines())
3470
+ return f"✓ File '{path_str}' successfully written ({byte_count} bytes, {line_count} lines).", False
3471
+ except Exception as e:
3472
+ return f"Error writing file '{path_str}': {e}", True
3473
+
3474
+ def _handle_tool_list_dir(args: dict, scopes: str) -> tuple[str, bool]:
3475
+ path_str = args.get("path", ".").strip() or "."
3476
+ depth = min(max(int(args.get("depth", 1) or 1), 1), 3)
3477
+
3478
+ target_path = Path(path_str).expanduser()
3479
+ if not target_path.is_absolute():
3480
+ target_path = (Path.cwd() / target_path).resolve()
3481
+ else:
3482
+ target_path = target_path.resolve()
3483
+
3484
+ scopes_list = [s.strip().lower() for s in scopes.split(",") if s.strip()]
3485
+ if "*" not in scopes_list and "@full" not in scopes_list:
3486
+ if "fs:cwd_only" in scopes_list:
3487
+ cwd = Path.cwd().resolve()
3488
+ try:
3489
+ target_path.relative_to(cwd)
3490
+ except ValueError:
3491
+ return f"⛔ Permission denied by scope 'fs:cwd_only': Path '{path_str}' is outside working directory ({cwd}).", True
3492
+
3493
+ if not target_path.exists():
3494
+ return f"Error: Path '{path_str}' not found.", True
3495
+ if not target_path.is_dir():
3496
+ return f"Error: '{path_str}' is not a directory. Use akash_read_file for files.", True
3497
+
3498
+ try:
3499
+ output_lines = [f"Directory listing of: {target_path}"]
3500
+
3501
+ def _walk(curr_p: Path, cur_depth: int, prefix: str = ""):
3502
+ if cur_depth > depth:
3503
+ return
3504
+ try:
3505
+ entries = sorted(list(curr_p.iterdir()), key=lambda p: (not p.is_dir(), p.name.lower()))
3506
+ except PermissionError:
3507
+ output_lines.append(f"{prefix}└── [Permission Denied]")
3508
+ return
3509
+
3510
+ excluded = {".git", "__pycache__", "venv", ".akash", "node_modules", "storage", ".cache"}
3511
+ filtered = [e for e in entries if e.name not in excluded and not (e.is_symlink() and e.name == "storage")]
3512
+
3513
+ for idx, entry in enumerate(filtered[:100]):
3514
+ is_last = (idx == len(filtered) - 1)
3515
+ ptr = "└── " if is_last else "├── "
3516
+ sub_pfx = " " if is_last else "│ "
3517
+ if entry.is_dir():
3518
+ output_lines.append(f"{prefix}{ptr}📁 {entry.name}/")
3519
+ if cur_depth < depth and not entry.is_symlink():
3520
+ _walk(entry, cur_depth + 1, prefix + sub_pfx)
3521
+ elif entry.is_symlink():
3522
+ output_lines.append(f"{prefix}{ptr}🔗 {entry.name} -> {os.readlink(entry)}")
3523
+ else:
3524
+ try:
3525
+ sz = entry.stat().st_size
3526
+ sz_str = f"{sz / 1024:.1f}KB" if sz >= 1024 else f"{sz}B"
3527
+ except Exception:
3528
+ sz_str = "?"
3529
+ output_lines.append(f"{prefix}{ptr}📄 {entry.name} ({sz_str})")
3530
+ if len(filtered) > 100:
3531
+ output_lines.append(f"{prefix}... [{len(filtered) - 100} more items omitted]")
3532
+
3533
+ _walk(target_path, 1, "")
3534
+ return "\n".join(output_lines), False
3535
+ except Exception as e:
3536
+ return f"Error listing directory '{path_str}': {e}", True
3537
+
3538
+ def _handle_tool_scope_details(scopes: str, profile_name: str, profile_id: str) -> tuple[str, bool]:
3539
+ scopes_list = [s.strip() for s in scopes.split(",") if s.strip()]
3540
+ lines = [
3541
+ f"=== Akash Agent Security Scope Profile ===",
3542
+ f"Profile: {profile_name} (ID: {profile_id})",
3543
+ f"Active Scopes ({len(scopes_list)}): {', '.join(scopes_list)}",
3544
+ "",
3545
+ "Enforced Policies:"
3546
+ ]
3547
+ if "*" in scopes_list or "@full" in scopes_list:
3548
+ lines.append(" • Full Access: No restrictions applied.")
3549
+ else:
3550
+ if "fs:cwd_only" in scopes_list:
3551
+ lines.append(" • Workspace Jailing (fs:cwd_only): Access restricted strictly to current project root; path traversal ('..') is blocked.")
3552
+ if "fs:no_delete" in scopes_list:
3553
+ lines.append(" • Delete Prevention (fs:no_delete): Destructive deletions (rm, rmdir, unlink) are blocked.")
3554
+ if "exec:read_only" in scopes_list or "@viewer" in scopes_list:
3555
+ lines.append(" • Read-Only Mode (exec:read_only): File mutations, file writes, and state changes are blocked.")
3556
+ if "exec:package_install" not in scopes_list:
3557
+ lines.append(" • Package Lock: pip/npm/apt installations are blocked (requires 'exec:package_install').")
3558
+ if "vault:checkpoint" in scopes_list:
3559
+ lines.append(" • Safety Checkpoints (vault:checkpoint): AI modifications create rollback snapshots.")
3560
+ return "\n".join(lines), False
3561
+
3366
3562
  @agent_app.command("serve")
3367
3563
  def mcp_serve(
3368
3564
  url: Optional[str] = typer.Option(None, "--url", help="Override server URL"),
@@ -3411,8 +3607,12 @@ def mcp_serve(
3411
3607
  print(" " + _GR + "──────────────────────────────────────────────────" + _RS)
3412
3608
  print(" " + _DM + "Active Tools:" + _RS)
3413
3609
  print(" • akash_exec_command " + _dim("(Safe terminal runner)"))
3610
+ print(" • akash_read_file " + _dim("(Inspect & import files into AI)"))
3611
+ print(" • akash_write_file " + _dim("(Create or edit workspace files)"))
3612
+ print(" • akash_list_dir " + _dim("(Explore directory trees)"))
3414
3613
  print(" • akash_checkpoint_create " + _dim("(Rollback snapshot)"))
3415
3614
  print(" • akash_undo " + _dim("(Revert last AI edit)"))
3615
+ print(" • akash_scope_details " + _dim("(Inspect active session permissions)"))
3416
3616
  print(" " + _GR + "──────────────────────────────────────────────────" + _RS)
3417
3617
  print(" " + _DM + "[LIVE ACTIVITY]" + _RS)
3418
3618
 
@@ -3479,7 +3679,8 @@ def mcp_serve(
3479
3679
 
3480
3680
  if t_name == "akash_exec_command":
3481
3681
  cmd = args.get("command", "")
3482
- disp_cmd = cmd if len(cmd) <= 32 else cmd[:29] + "…"
3682
+ one_line_cmd = re.sub(r'[\r\n\t]+', ' ', cmd).strip()
3683
+ disp_cmd = one_line_cmd if len(one_line_cmd) <= 36 else one_line_cmd[:33] + "…"
3483
3684
 
3484
3685
  is_safe, sec_reason = _sanitize_agent_command(cmd, scopes=cached.get("scopes", "*"))
3485
3686
  if not is_safe:
@@ -3585,6 +3786,63 @@ def mcp_serve(
3585
3786
  "id": call_id,
3586
3787
  "result": {"content": [{"type": "text", "text": f"Checkpoint '{label}' created."}]}
3587
3788
  }))
3789
+
3790
+ elif t_name == "akash_read_file":
3791
+ f_path = args.get("path", "")
3792
+ c_f = re.sub(r'[\r\n\t]+', ' ', f_path).strip()
3793
+ disp_f = c_f if len(c_f) <= 36 else c_f[:33] + "…"
3794
+ with _Spin(f"read: {disp_f}", min_seconds=0.15, prefix=ts_pfx):
3795
+ res_text, is_err = _handle_tool_read_file(args, cached.get("scopes", "*"))
3796
+ elapsed_ms = (time.monotonic() - t0) * 1000
3797
+ icon = f"{_G}✓{_RS}" if not is_err else f"{_R}✗{_RS}"
3798
+ print(f"{ts_pfx} {icon} {(_G if not is_err else _R)}read:{_RS} {disp_f} {_dim(f'{elapsed_ms:.1f}ms')}")
3799
+ ws.send(json.dumps({
3800
+ "type": "tool_response",
3801
+ "id": call_id,
3802
+ "result": {"content": [{"type": "text", "text": res_text}], "isError": is_err}
3803
+ }))
3804
+
3805
+ elif t_name == "akash_write_file":
3806
+ f_path = args.get("path", "")
3807
+ c_f = re.sub(r'[\r\n\t]+', ' ', f_path).strip()
3808
+ disp_f = c_f if len(c_f) <= 36 else c_f[:33] + "…"
3809
+ _save_undo_checkpoint()
3810
+ with _Spin(f"write: {disp_f}", min_seconds=0.15, prefix=ts_pfx):
3811
+ res_text, is_err = _handle_tool_write_file(args, cached.get("scopes", "*"))
3812
+ elapsed_ms = (time.monotonic() - t0) * 1000
3813
+ icon = f"{_G}✓{_RS}" if not is_err else f"{_R}✗{_RS}"
3814
+ print(f"{ts_pfx} {icon} {(_G if not is_err else _R)}write:{_RS} {disp_f} {_dim(f'{elapsed_ms:.1f}ms')}")
3815
+ ws.send(json.dumps({
3816
+ "type": "tool_response",
3817
+ "id": call_id,
3818
+ "result": {"content": [{"type": "text", "text": res_text}], "isError": is_err}
3819
+ }))
3820
+
3821
+ elif t_name == "akash_list_dir":
3822
+ d_path = args.get("path", ".")
3823
+ c_d = re.sub(r'[\r\n\t]+', ' ', d_path).strip()
3824
+ disp_d = c_d if len(c_d) <= 36 else c_d[:33] + "…"
3825
+ with _Spin(f"list: {disp_d}", min_seconds=0.15, prefix=ts_pfx):
3826
+ res_text, is_err = _handle_tool_list_dir(args, cached.get("scopes", "*"))
3827
+ elapsed_ms = (time.monotonic() - t0) * 1000
3828
+ icon = f"{_G}✓{_RS}" if not is_err else f"{_R}✗{_RS}"
3829
+ print(f"{ts_pfx} {icon} {(_G if not is_err else _R)}list:{_RS} {disp_d} {_dim(f'{elapsed_ms:.1f}ms')}")
3830
+ ws.send(json.dumps({
3831
+ "type": "tool_response",
3832
+ "id": call_id,
3833
+ "result": {"content": [{"type": "text", "text": res_text}], "isError": is_err}
3834
+ }))
3835
+
3836
+ elif t_name == "akash_scope_details":
3837
+ with _Spin("inspecting scopes…", min_seconds=0.15, prefix=ts_pfx):
3838
+ res_text, is_err = _handle_tool_scope_details(cached.get("scopes", "*"), pname, profile_id)
3839
+ elapsed_ms = (time.monotonic() - t0) * 1000
3840
+ print(f"{ts_pfx} {_C}🔒 scopes:{_RS} {_dim(pname)} {_dim(f'{elapsed_ms:.1f}ms')}")
3841
+ ws.send(json.dumps({
3842
+ "type": "tool_response",
3843
+ "id": call_id,
3844
+ "result": {"content": [{"type": "text", "text": res_text}], "isError": is_err}
3845
+ }))
3588
3846
  except KeyboardInterrupt:
3589
3847
  print("\n " + _DM + "Worker stopped cleanly." + _RS + "\n")
3590
3848
  break
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akashcli
3
- Version: 4.8.0
3
+ Version: 4.8.2
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.0"
3
+ version = "4.8.2"
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
File without changes
File without changes
File without changes
File without changes