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.
- {akashcli-4.8.0 → akashcli-4.8.2}/PKG-INFO +1 -1
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/cli.py +276 -18
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli.egg-info/PKG-INFO +1 -1
- {akashcli-4.8.0 → akashcli-4.8.2}/pyproject.toml +1 -1
- {akashcli-4.8.0 → akashcli-4.8.2}/README.md +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/__init__.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/_net.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/auth.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/client.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/config.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/crypto.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/env_manager.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/exceptions.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/exec.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/integrity.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/logger.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/models.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/p2p.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/packages.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/relay.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/restore.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/snapshot_crypto.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/storage.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/terminal.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/updater.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/uploader.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli/utils.py +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli.egg-info/SOURCES.txt +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli.egg-info/dependency_links.txt +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli.egg-info/entry_points.txt +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli.egg-info/requires.txt +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/akashcli.egg-info/top_level.txt +0 -0
- {akashcli-4.8.0 → akashcli-4.8.2}/setup.cfg +0 -0
|
@@ -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
|
|
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 &
|
|
2861
|
+
print(f" {_C}◈{_RS} {_W}Akash Agent Scopes & Role Presets Directory{_RS}")
|
|
2862
|
+
print(f" {_GR}────────────────────────────────────────────────────────────{_RS}")
|
|
2862
2863
|
_p()
|
|
2863
|
-
print(f" {
|
|
2864
|
-
print(f" {_cy('@viewer')} {_DM}: Read-Only Inspector (Cannot edit code, delete files, or
|
|
2865
|
-
print(f" {_dim('Scopes: workspace:read, exec:read_only, fs:cwd_only')}")
|
|
2866
|
-
print(f" {_cy('@
|
|
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" {
|
|
2873
|
-
print(f" {_ye('
|
|
2874
|
-
print(f" {_ye('
|
|
2875
|
-
print(f" {_ye('exec:
|
|
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
|
|
2878
|
-
print(f" {_ye('vault:checkpoint')} {_DM}Enables
|
|
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" {
|
|
2881
|
-
print(f"
|
|
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"
|
|
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
|
-
|
|
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
|
|
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
|
|
File without changes
|