akashcli 4.8.7__tar.gz → 4.8.9__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.7 → akashcli-4.8.9}/PKG-INFO +1 -1
  2. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/__init__.py +1 -1
  3. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/cli.py +333 -43
  4. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli.egg-info/PKG-INFO +1 -1
  5. {akashcli-4.8.7 → akashcli-4.8.9}/pyproject.toml +1 -1
  6. {akashcli-4.8.7 → akashcli-4.8.9}/README.md +0 -0
  7. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/_net.py +0 -0
  8. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/auth.py +0 -0
  9. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/client.py +0 -0
  10. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/config.py +0 -0
  11. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/crypto.py +0 -0
  12. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/env_manager.py +0 -0
  13. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/exceptions.py +0 -0
  14. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/exec.py +0 -0
  15. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/integrity.py +0 -0
  16. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/logger.py +0 -0
  17. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/models.py +0 -0
  18. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/p2p.py +0 -0
  19. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/packages.py +0 -0
  20. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/relay.py +0 -0
  21. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/restore.py +0 -0
  22. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/snapshot_crypto.py +0 -0
  23. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/storage.py +0 -0
  24. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/terminal.py +0 -0
  25. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/updater.py +0 -0
  26. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/uploader.py +0 -0
  27. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli/utils.py +0 -0
  28. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli.egg-info/SOURCES.txt +0 -0
  29. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli.egg-info/dependency_links.txt +0 -0
  30. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli.egg-info/entry_points.txt +0 -0
  31. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli.egg-info/requires.txt +0 -0
  32. {akashcli-4.8.7 → akashcli-4.8.9}/akashcli.egg-info/top_level.txt +0 -0
  33. {akashcli-4.8.7 → akashcli-4.8.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akashcli
3
- Version: 4.8.7
3
+ Version: 4.8.9
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.8.7"
5
+ __version__ = "4.8.9"
6
6
 
7
7
  AKASH_VERSION = __version__
8
8
 
@@ -136,7 +136,7 @@ def _resolve_client(url_override: str = None, require_key: bool = False, silent:
136
136
 
137
137
 
138
138
  _GUEST_WARNING = (
139
- f" {_Y}⚠{_RS} {_Y}Running as guest{_RS} - not logged in.\n"
139
+ f" {_Y}!{_RS} {_Y}Running as guest{_RS} - not logged in.\n"
140
140
  f" {_DM}Features are limited and your data won't be saved to the cloud.\n"
141
141
  f" Run {_RS}{_C}akash login{_RS}{_DM} to unlock full access.{_RS}"
142
142
  )
@@ -243,7 +243,7 @@ def _print_help():
243
243
  f" {C}devices{RS} {DM}[--revoke NAME]{RS} list or manage enrolled devices",
244
244
  "",
245
245
  f" {DM}AI AGENTS & MCP{RS}",
246
- f" {C}mcp{RS} {DM}<serve|share|create|list…>{RS} AI agent & MCP tool bridge (Codex, Claude, Cursor)",
246
+ f" {C}mcp{RS} {DM}<serve|share|create|list…>{RS} AI agent & MCP tool bridge (Cursor, Claude, ChatGPT)",
247
247
  "",
248
248
  f" {DM}FILES & CLOUD VAULT{RS}",
249
249
  f" {C}cloud{RS} {DM}<upload|files|stream|delete…>{RS} cloud file vault & media streaming engine",
@@ -359,7 +359,7 @@ def update():
359
359
  _p()
360
360
  return
361
361
 
362
- print(f" {_Y}⚡{_RS} New version available: {_DM}{__version__}{_RS} {_C}→{_RS} {_G}{latest}{_RS}")
362
+ print(f" {_Y}◈{_RS} New version available: {_DM}{__version__}{_RS} {_C}→{_RS} {_G}{latest}{_RS}")
363
363
  with _Spin(f"updating akashcli to v{latest}…"):
364
364
  cmd = [sys.executable, "-m", "pip", "install", "-U", "--no-cache-dir", f"akashcli=={latest}", "--break-system-packages"]
365
365
  res = subprocess.run(cmd, capture_output=True, text=True)
@@ -464,10 +464,10 @@ def cmd_url(
464
464
  _info("To reset URL : akash url --reset")
465
465
  _p()
466
466
  def _storage_discontinued_notice():
467
- print(f"\n{_R}⚠️ [SERVICE DISCONTINUED] Cloud storage & upload services are disabled.{_RS}")
467
+ print(f"\n{_R}!️ [SERVICE DISCONTINUED] Cloud storage & upload services are disabled.{_RS}")
468
468
  print(f" {_DM}Public cloud uploading has been discontinued due to lack of funds and hosting costs.{_RS}")
469
469
  print(f" {_DM}We are actively seeking sponsors to help restore community storage infrastructure.{_RS}\n")
470
- print(f" {_C}💖 Sponsoring the project:{_RS}")
470
+ print(f" {_C}Sponsoring the project:{_RS}")
471
471
  print(f" Run {_YE}akash sponsor{_RS} or visit {_CY}https://github.com/juniorsir/akashcli#sponsorship{_RS}\n")
472
472
  raise typer.Exit(1)
473
473
 
@@ -476,7 +476,7 @@ def _storage_discontinued_notice():
476
476
  def cmd_sponsor():
477
477
  """Information on sponsoring Akash CLI to restore storage services."""
478
478
  print(f"\n{_C}╔═══════════════════════════════════════════════════════════════╗{_RS}")
479
- print(f"{_C}║ 💖 SPONSOR AKASH CLI INFRASTRUCTURE ║{_RS}")
479
+ print(f"{_C}║ SPONSOR AKASH CLI INFRASTRUCTURE ║{_RS}")
480
480
  print(f"{_C}╚═══════════════════════════════════════════════════════════════╝{_RS}\n")
481
481
  print(f" {_B}Status Update:{_RS}")
482
482
  print(f" Public cloud storage and file uploading services have been")
@@ -692,7 +692,7 @@ def _interactive_explorer(vfs, root_name="/"):
692
692
  sys.stdout.write(f" {_GR}────────────────────────────────────────────────────────────{_RS}\r\n")
693
693
 
694
694
  full_path = "".join([n[1] + ("/" if n[1] != "/" and not n[1].endswith("/") else "") for n in path_stack])
695
- sys.stdout.write(f" 📂 {_C}{full_path}{_RS}\r\n")
695
+ sys.stdout.write(f" [DIR] {_C}{full_path}{_RS}\r\n")
696
696
  sys.stdout.write(f" {_GR}────────────────────────────────────────────────────────────{_RS}\r\n")
697
697
 
698
698
  if not keys:
@@ -704,7 +704,7 @@ def _interactive_explorer(vfs, root_name="/"):
704
704
  child = children[k]
705
705
  is_sel = (actual_idx == selected_idx)
706
706
  cursor = f"{_G}> {_RS}" if is_sel else " "
707
- icon = "📁" if child["type"] == "dir" else "📄"
707
+ icon = "[DIR]" if child["type"] == "dir" else "[FILE]"
708
708
 
709
709
  display_name = k[:35] + ("…" if len(k) > 35 else "")
710
710
  if child["type"] == "dir":
@@ -2704,11 +2704,11 @@ def env_pull(
2704
2704
 
2705
2705
  mcp_app = typer.Typer(help="Model Context Protocol (MCP) server & AI agent tool bridge.")
2706
2706
  agent_app = mcp_app
2707
- app.add_typer(mcp_app, name="mcp", help="MCP server · AI agent tools · Claude/Cursor/Codex bridge")
2707
+ app.add_typer(mcp_app, name="mcp", help="MCP server · AI agent tools · Claude/Cursor/ChatGPT bridge")
2708
2708
  app.add_typer(mcp_app, name="agent", hidden=True, help="Alias for akash mcp")
2709
2709
 
2710
2710
  def _render_inspection_card(r: dict) -> None:
2711
- print(f"\n {_C}╭─ ⚡ INCOMING AGENT DEVICE REGISTRATION ────────────────────────────{_RS}")
2711
+ print(f"\n {_C}╭─ ◈ INCOMING AGENT DEVICE REGISTRATION ────────────────────────────{_RS}")
2712
2712
  print(f" {_GR}│{_RS} {_DM}Request ID :{_RS} {_ye(r['request_id'])}")
2713
2713
  print(f" {_GR}│{_RS} {_DM}Client Name :{_RS} {_W}{r['client_name']}{_RS}")
2714
2714
  print(f" {_GR}│{_RS} {_DM}Client ID :{_RS} {_dim(r['client_id'])}")
@@ -2868,7 +2868,7 @@ def agent_list(raw: bool = typer.Option(False, "--json", help="Raw JSON output")
2868
2868
 
2869
2869
  print(f" {_GR}{'─'*76}{_RS}")
2870
2870
  if expired_count > 0:
2871
- 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")
2871
+ 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")
2872
2872
  else:
2873
2873
  _p()
2874
2874
  print(f" {_W}Quick Launch:{_RS}")
@@ -3036,7 +3036,7 @@ def agent_info(
3036
3036
  mcp_url = f"{base}/mcp?{params}"
3037
3037
 
3038
3038
  _p()
3039
- print(f" {_C}╭─ 🤖 AGENT PERMISSION DETAILS: {_W}{name}{_RS} {_dim('(' + pid + ')')}")
3039
+ print(f" {_C}╭─ AGENT PERMISSION DETAILS: {_W}{name}{_RS} {_dim('(' + pid + ')')}")
3040
3040
  print(f" {_GR}│{_RS} {_DM}Profile ID :{_RS} {_ye(pid)}")
3041
3041
  print(f" {_GR}│{_RS} {_DM}Client ID :{_RS} {_dim(cid)}")
3042
3042
  st_col = _G if "active" in status.lower() else (_Y if "pending" in status.lower() else _R)
@@ -3614,7 +3614,7 @@ def _handle_tool_read_file(args: dict, scopes: str) -> tuple[str, bool]:
3614
3614
  try:
3615
3615
  target_path.relative_to(cwd)
3616
3616
  except ValueError:
3617
- return f"⛔ Permission denied by scope 'fs:cwd_only': Path '{path_str}' is outside working directory ({cwd}).", True
3617
+ return f"[BLOCKED] Permission denied by scope 'fs:cwd_only': Path '{path_str}' is outside working directory ({cwd}).", True
3618
3618
 
3619
3619
  if not target_path.exists():
3620
3620
  return f"Error: File '{path_str}' not found.", True
@@ -3667,13 +3667,13 @@ def _handle_tool_write_file(args: dict, scopes: str) -> tuple[str, bool]:
3667
3667
  scopes_list = [s.strip().lower() for s in scopes.split(",") if s.strip()]
3668
3668
  if "*" not in scopes_list and "@full" not in scopes_list:
3669
3669
  if "exec:read_only" in scopes_list or "@viewer" in scopes_list:
3670
- return "⛔ Permission denied: Agent is in read-only mode ('exec:read_only'). File writing is blocked.", True
3670
+ return "[BLOCKED] Permission denied: Agent is in read-only mode ('exec:read_only'). File writing is blocked.", True
3671
3671
  if "fs:cwd_only" in scopes_list:
3672
3672
  cwd = Path.cwd().resolve()
3673
3673
  try:
3674
3674
  target_path.relative_to(cwd)
3675
3675
  except ValueError:
3676
- return f"⛔ Permission denied by scope 'fs:cwd_only': Path '{path_str}' is outside working directory ({cwd}).", True
3676
+ return f"[BLOCKED] Permission denied by scope 'fs:cwd_only': Path '{path_str}' is outside working directory ({cwd}).", True
3677
3677
 
3678
3678
  if target_path.exists() and not overwrite:
3679
3679
  return f"Error: File '{path_str}' already exists and overwrite is set to False.", True
@@ -3704,7 +3704,7 @@ def _handle_tool_list_dir(args: dict, scopes: str) -> tuple[str, bool]:
3704
3704
  try:
3705
3705
  target_path.relative_to(cwd)
3706
3706
  except ValueError:
3707
- return f"⛔ Permission denied by scope 'fs:cwd_only': Path '{path_str}' is outside working directory ({cwd}).", True
3707
+ return f"[BLOCKED] Permission denied by scope 'fs:cwd_only': Path '{path_str}' is outside working directory ({cwd}).", True
3708
3708
 
3709
3709
  if not target_path.exists():
3710
3710
  return f"Error: Path '{path_str}' not found.", True
@@ -3731,18 +3731,18 @@ def _handle_tool_list_dir(args: dict, scopes: str) -> tuple[str, bool]:
3731
3731
  ptr = "└── " if is_last else "├── "
3732
3732
  sub_pfx = " " if is_last else "│ "
3733
3733
  if entry.is_dir():
3734
- output_lines.append(f"{prefix}{ptr}📁 {entry.name}/")
3734
+ output_lines.append(f"{prefix}{ptr}[DIR] {entry.name}/")
3735
3735
  if cur_depth < depth and not entry.is_symlink():
3736
3736
  _walk(entry, cur_depth + 1, prefix + sub_pfx)
3737
3737
  elif entry.is_symlink():
3738
- output_lines.append(f"{prefix}{ptr}🔗 {entry.name} -> {os.readlink(entry)}")
3738
+ output_lines.append(f"{prefix}{ptr}[LINK] {entry.name} -> {os.readlink(entry)}")
3739
3739
  else:
3740
3740
  try:
3741
3741
  sz = entry.stat().st_size
3742
3742
  sz_str = f"{sz / 1024:.1f}KB" if sz >= 1024 else f"{sz}B"
3743
3743
  except Exception:
3744
3744
  sz_str = "?"
3745
- output_lines.append(f"{prefix}{ptr}📄 {entry.name} ({sz_str})")
3745
+ output_lines.append(f"{prefix}{ptr}[FILE] {entry.name} ({sz_str})")
3746
3746
  if len(filtered) > 100:
3747
3747
  output_lines.append(f"{prefix}... [{len(filtered) - 100} more items omitted]")
3748
3748
 
@@ -3852,7 +3852,7 @@ def mcp_serve(
3852
3852
  if ack.get("type") == "pending_approval":
3853
3853
  req_id = ack.get("request_id", "?")
3854
3854
  sys.stdout.write(chr(13) + chr(27) + "[2K"); sys.stdout.flush()
3855
- print("\n " + _Y + "⚡" + _RS + " " + _W + "New device registration pending:" + _RS)
3855
+ print("\n " + _Y + "◈" + _RS + " " + _W + "New device registration pending:" + _RS)
3856
3856
  print(" " + _DM + "Request ID : " + _RS + _ye(req_id))
3857
3857
  print(" " + _DM + "Device FP : " + _RS + _dim(dev_fp))
3858
3858
  print()
@@ -3908,7 +3908,7 @@ def mcp_serve(
3908
3908
  "type": "tool_response",
3909
3909
  "id": call_id,
3910
3910
  "result": {
3911
- "content": [{"type": "text", "text": f"⛔ Security Policy Block: {sec_reason}\nCommand: {cmd}"}],
3911
+ "content": [{"type": "text", "text": f"[BLOCKED] Security Policy Block: {sec_reason}\nCommand: {cmd}"}],
3912
3912
  "isError": True
3913
3913
  }
3914
3914
  }))
@@ -3957,11 +3957,11 @@ def mcp_serve(
3957
3957
  except Exception:
3958
3958
  raw_out = ""
3959
3959
  elapsed_ms = 25000.0
3960
- raw_out = raw_out or "⛔ Command timed out after 25 seconds."
3960
+ raw_out = raw_out or "[BLOCKED] Command timed out after 25 seconds."
3961
3961
  returncode = 124
3962
3962
  except Exception as cmd_err:
3963
3963
  elapsed_ms = (time.monotonic() - t0) * 1000
3964
- raw_out = f"⛔ Command execution error: {cmd_err}"
3964
+ raw_out = f"[BLOCKED] Command execution error: {cmd_err}"
3965
3965
  returncode = 1
3966
3966
 
3967
3967
  bounded_out = _smart_truncate_output(raw_out)
@@ -3973,8 +3973,8 @@ def mcp_serve(
3973
3973
 
3974
3974
  if returncode != 0:
3975
3975
  res_content = bounded_out or f"Command exited with status code {returncode} (no output)"
3976
- if not res_content.startswith("⛔"):
3977
- res_content = f"⛔ Command execution failed (exit code {returncode}):\n{res_content}"
3976
+ if not res_content.startswith("[BLOCKED]"):
3977
+ res_content = f"[BLOCKED] Command execution failed (exit code {returncode}):\n{res_content}"
3978
3978
  else:
3979
3979
  res_content = bounded_out or "(command completed successfully with no output)"
3980
3980
 
@@ -4081,7 +4081,7 @@ def mcp_serve(
4081
4081
  reconnect_attempts += 1
4082
4082
  backoff = min(1.2 * (1.5 ** min(reconnect_attempts, 6)), 30.0) + random.uniform(0.1, 0.4)
4083
4083
  now_t = datetime.datetime.now().strftime("%H:%M:%S")
4084
- print(f" {_GR}{now_t}{_RS} {_Y}⚠{_RS} {_dim(f'Worker disconnected ({e}). Reconnecting in {backoff:.1f}s (attempt {reconnect_attempts})…')}")
4084
+ print(f" {_GR}{now_t}{_RS} {_Y}!{_RS} {_dim(f'Worker disconnected ({e}). Reconnecting in {backoff:.1f}s (attempt {reconnect_attempts})…')}")
4085
4085
  time.sleep(backoff)
4086
4086
 
4087
4087
  @agent_app.command("share")
@@ -4090,6 +4090,7 @@ def mcp_share(
4090
4090
  target: Optional[str] = typer.Argument(None, help="Profile ID (optional if only 1 profile exists)"),
4091
4091
  id: Optional[str] = typer.Option(None, "--id", "-i", help="Profile ID"),
4092
4092
  temp: Optional[str] = typer.Option(None, "--temp", "-t", help="Temporary short URL duration (e.g. '30m', '30 min', '1h')"),
4093
+ local: bool = typer.Option(False, "--local", "-l", help="Show snippet only for local-first zero-network stdio MCP"),
4093
4094
  codex: bool = typer.Option(False, "--codex", "-c", help="Show snippet only for Codex CLI/Desktop"),
4094
4095
  claude: bool = typer.Option(False, "--claude", help="Show snippet only for Claude Desktop / Cursor"),
4095
4096
  chatgpt: bool = typer.Option(False, "--chatgpt", "-g", help="Show snippet only for ChatGPT Custom GPT Actions"),
@@ -4250,16 +4251,48 @@ def mcp_share(
4250
4251
  print(" " + _GR + "──────────────────────────────────────────────────" + _RS)
4251
4252
  _p()
4252
4253
 
4253
- show_all = not codex and not claude and not chatgpt
4254
+ show_all = not local and not codex and not claude and not chatgpt
4255
+
4256
+ # ── 1. LOCAL-FIRST STDIO MODE (ZERO NETWORK) ──
4257
+ if show_all or local:
4258
+ print(" " + _C + "1. Local Mode (Offline · Zero Network · Direct Stdio · Recommended)" + _RS + ":")
4259
+ print(" " + _DM + "For Claude Desktop, Cursor & local AI agents on this device." + _RS)
4260
+ print(" " + _dim(" (Zero network exposure, no open ports, direct local pipe)"))
4261
+ stdio_cfg = json.dumps({
4262
+ "mcpServers": {
4263
+ "akash": {
4264
+ "command": "akash",
4265
+ "args": ["mcp", "stdio"]
4266
+ }
4267
+ }
4268
+ }, indent=2)
4269
+ for c_line in stdio_cfg.splitlines():
4270
+ print(" " + c_line)
4271
+ print(" " + _GR + "──────────────────────────────────────────────────" + _RS)
4272
+ _p()
4254
4273
 
4255
- if show_all:
4256
- gemini_url = base + "/mcp"
4257
- print(" " + _C + "For Google Gemini (Web & Extensions)" + _RS + ":")
4258
- print(" " + _DM + "MCP Server URL : " + _RS + _cy(gemini_url))
4259
- print(" " + _dim(" (Standard OAuth 2.0 with instant one-click approval)"))
4274
+ # ── 2. NETWORK / RELAY MODE (AUTHENTICATED OVER DOMAIN / CLOUDFLARE / AKASHBOX) ──
4275
+ if show_all or claude:
4276
+ is_custom = bool(base != DEFAULT_STORAGE_URL)
4277
+ domain_tag = "Custom Domain / Cloudflare" if is_custom else "Akashbox Network Relay"
4278
+ print(" " + _C + "2. Network / Internet Mode (" + domain_tag + ")" + _RS + ":")
4279
+ print(" " + _DM + "Active Domain : " + _RS + _cy(base))
4280
+ print(" " + _DM + "Hardware Auth : " + _RS + _G + "Strict (Client ID + Secret + Device Fingerprint required)" + _RS)
4281
+ print(" " + _DM + "Authenticated URL : " + _RS)
4282
+ print(" " + _ye(local_url))
4283
+ _p()
4284
+ print(" " + _DM + "Config snippet for remote Claude Desktop, Cursor & Cline:" + _RS)
4285
+ claude_cfg = json.dumps({"mcpServers": {"akash": {"url": local_url}}}, indent=2)
4286
+ for c_line in claude_cfg.splitlines():
4287
+ print(" " + c_line)
4288
+ print(" " + _DM + " Logo / Icon: Clean Cloud Icon delivered automatically via MCP handshake" + _RS)
4289
+ _p()
4290
+ print(" " + _dim(" • To use a custom domain or Cloudflare tunnel: akash url https://your-domain.com"))
4291
+ print(" " + _dim(" • To reset back to default Akashbox relay : akash url --reset"))
4260
4292
  print(" " + _GR + "──────────────────────────────────────────────────" + _RS)
4261
4293
  _p()
4262
4294
 
4295
+ # ── 3. CHATGPT CUSTOM GPT ACTIONS (OPENAPI REST) ──
4263
4296
  if show_all or chatgpt:
4264
4297
  app_token = None
4265
4298
  for tok_path in [Path.home() / ".akash_app_tokens.json", Path.home() / ".akash" / "app_tokens.json"]:
@@ -4282,7 +4315,7 @@ def mcp_share(
4282
4315
  token_val = app_token or csec or "akash_sec_client_03e804cabbbcface799a4522ca673dd166b8e76813f84389"
4283
4316
  openapi_url = base + "/openapi.json"
4284
4317
  privacy_url = base + "/privacy"
4285
- print(" " + _C + "For ChatGPT Custom GPT (OpenAPI Actions)" + _RS + ":")
4318
+ print(" " + _C + "3. For ChatGPT Custom GPT (OpenAPI Actions)" + _RS + ":")
4286
4319
  print(" " + _DM + "OpenAPI Schema URL : " + _RS + _cy(openapi_url))
4287
4320
  print(" " + _DM + "Authentication : " + _RS + _W + "API Key" + _RS + " " + _dim("->") + " " + _W + "Custom" + _RS)
4288
4321
  print(" " + _DM + "Custom Header Name : " + _RS + _ye("X-App-Token"))
@@ -4292,7 +4325,7 @@ def mcp_share(
4292
4325
  print(" " + _GR + "──────────────────────────────────────────────────" + _RS)
4293
4326
  _p()
4294
4327
 
4295
- if show_all or codex:
4328
+ if codex:
4296
4329
  print(" " + _C + "For Codex CLI / Desktop" + _RS + " " + _dim("(~/.codex/config.toml)") + ":")
4297
4330
  print(" " + _DM + "Option A: Same Device (Termux / Localhost)" + _RS)
4298
4331
  print(" [mcp_servers.akash]")
@@ -4305,15 +4338,6 @@ def mcp_share(
4305
4338
  print(" " + _GR + "──────────────────────────────────────────────────" + _RS)
4306
4339
  _p()
4307
4340
 
4308
- if show_all or claude:
4309
- print(" " + _C + "For Claude Desktop, Cursor & Cline" + _RS + " " + _dim("(config.json / settings.json)") + ":")
4310
- claude_cfg = json.dumps({"mcpServers": {"akash": {"url": local_url}}}, indent=2)
4311
- for c_line in claude_cfg.splitlines():
4312
- print(" " + c_line)
4313
- print(" " + _DM + " Logo / Icon: Clean Cloud Icon delivered automatically via MCP handshake" + _RS)
4314
- print(" " + _GR + "──────────────────────────────────────────────────" + _RS)
4315
- _p()
4316
-
4317
4341
  _info("Paste snippet into AI client, then keep running:")
4318
4342
  _info(" " + _cy("akash mcp serve"))
4319
4343
  _p()
@@ -4360,5 +4384,271 @@ def mcp_status(
4360
4384
  print(" " + _GR + "──────────────────────────────────────────────────" + _RS)
4361
4385
  _p()
4362
4386
 
4387
+
4388
+ @agent_app.command("stdio")
4389
+ def mcp_stdio(
4390
+ profile_id: Optional[str] = typer.Option(None, "--id", "-i", help="Local Agent Profile ID"),
4391
+ ):
4392
+ """Runs a zero-network, local-first Model Context Protocol stdio server over stdin/stdout."""
4393
+ import sys, json, re, subprocess, time
4394
+ from pathlib import Path
4395
+ from . import __version__
4396
+
4397
+ agents_file = Path.home() / ".akash" / "agents.json"
4398
+ agents = {}
4399
+ if agents_file.exists():
4400
+ try:
4401
+ agents = json.loads(agents_file.read_text(encoding="utf-8"))
4402
+ except Exception:
4403
+ agents = {}
4404
+
4405
+ target_pid = profile_id or (next(reversed(list(agents.keys()))) if agents else "default")
4406
+ profile = agents.get(target_pid, {})
4407
+ scopes = profile.get("scopes", "*")
4408
+ pname = profile.get("name", "LocalAgent")
4409
+
4410
+ tools_definitions = [
4411
+ {
4412
+ "name": "akash_exec_command",
4413
+ "description": "Executes shell commands in the workspace with safety policies, secret redaction, and automatic undo snapshotting.",
4414
+ "inputSchema": {
4415
+ "type": "object",
4416
+ "properties": {
4417
+ "command": {"type": "string", "description": "The shell command to execute."}
4418
+ },
4419
+ "required": ["command"]
4420
+ }
4421
+ },
4422
+ {
4423
+ "name": "akash_read_file",
4424
+ "description": "Reads a file from the workspace with line-range pagination directly into context.",
4425
+ "inputSchema": {
4426
+ "type": "object",
4427
+ "properties": {
4428
+ "path": {"type": "string", "description": "Path to the file to read."},
4429
+ "offset": {"type": "integer", "description": "Starting line number (1-indexed).", "default": 1},
4430
+ "limit": {"type": "integer", "description": "Maximum number of lines to return.", "default": 200}
4431
+ },
4432
+ "required": ["path"]
4433
+ }
4434
+ },
4435
+ {
4436
+ "name": "akash_write_file",
4437
+ "description": "Writes or edits a file in the workspace safely with auto-undo checkpoints.",
4438
+ "inputSchema": {
4439
+ "type": "object",
4440
+ "properties": {
4441
+ "path": {"type": "string", "description": "Path to the file to write."},
4442
+ "content": {"type": "string", "description": "The content to write into the file."},
4443
+ "append": {"type": "boolean", "description": "Whether to append to the file instead of overwriting.", "default": False}
4444
+ },
4445
+ "required": ["path", "content"]
4446
+ }
4447
+ },
4448
+ {
4449
+ "name": "akash_list_dir",
4450
+ "description": "Lists directory entries with file sizes, types, and depth control.",
4451
+ "inputSchema": {
4452
+ "type": "object",
4453
+ "properties": {
4454
+ "path": {"type": "string", "description": "Directory path to list.", "default": "."},
4455
+ "depth": {"type": "integer", "description": "Depth of subdirectory recursion.", "default": 1}
4456
+ }
4457
+ }
4458
+ },
4459
+ {
4460
+ "name": "akash_checkpoint_create",
4461
+ "description": "Creates an instant rollback snapshot before executing large refactors.",
4462
+ "inputSchema": {
4463
+ "type": "object",
4464
+ "properties": {
4465
+ "label": {"type": "string", "description": "Name or tag for this checkpoint.", "default": "manual_checkpoint"}
4466
+ }
4467
+ }
4468
+ },
4469
+ {
4470
+ "name": "akash_undo",
4471
+ "description": "Reverts workspace files to the last pre-command checkpoint.",
4472
+ "inputSchema": {
4473
+ "type": "object",
4474
+ "properties": {}
4475
+ }
4476
+ },
4477
+ {
4478
+ "name": "akash_scope_details",
4479
+ "description": "Inspects active session permissions, allowed file boundaries, and security rules.",
4480
+ "inputSchema": {
4481
+ "type": "object",
4482
+ "properties": {}
4483
+ }
4484
+ }
4485
+ ]
4486
+
4487
+ while True:
4488
+ line = sys.stdin.readline()
4489
+ if not line:
4490
+ break
4491
+ line = line.strip()
4492
+ if not line:
4493
+ continue
4494
+ try:
4495
+ req = json.loads(line)
4496
+ except Exception:
4497
+ continue
4498
+
4499
+ msg_id = req.get("id")
4500
+ method = req.get("method")
4501
+ params = req.get("params", {})
4502
+
4503
+ if method == "initialize":
4504
+ res = {
4505
+ "jsonrpc": "2.0",
4506
+ "id": msg_id,
4507
+ "result": {
4508
+ "protocolVersion": "2024-11-05",
4509
+ "capabilities": {"tools": {}},
4510
+ "serverInfo": {"name": "akash", "version": __version__}
4511
+ }
4512
+ }
4513
+ sys.stdout.write(json.dumps(res) + "\n")
4514
+ sys.stdout.flush()
4515
+ elif method == "notifications/initialized":
4516
+ pass
4517
+ elif method == "ping":
4518
+ if msg_id is not None:
4519
+ sys.stdout.write(json.dumps({"jsonrpc": "2.0", "id": msg_id, "result": {}}) + "\n")
4520
+ sys.stdout.flush()
4521
+ elif method == "tools/list":
4522
+ res = {
4523
+ "jsonrpc": "2.0",
4524
+ "id": msg_id,
4525
+ "result": {"tools": tools_definitions}
4526
+ }
4527
+ sys.stdout.write(json.dumps(res) + "\n")
4528
+ sys.stdout.flush()
4529
+ elif method == "tools/call":
4530
+ t_name = params.get("name")
4531
+ t_args = params.get("arguments", {})
4532
+ res_content = ""
4533
+ is_err = False
4534
+
4535
+ if t_name == "akash_exec_command":
4536
+ cmd = t_args.get("command", "")
4537
+ is_safe, sec_reason = _sanitize_agent_command(cmd, scopes=scopes)
4538
+ if not is_safe:
4539
+ res_content = f"[BLOCKED] Security Policy Block: {sec_reason}\nCommand: {cmd}"
4540
+ is_err = True
4541
+ else:
4542
+ if any(k in cmd for k in ["touch", "mkdir", "mv", "cp", "sed", ">", ">>", "python", "npm", "pip"]):
4543
+ _save_undo_checkpoint()
4544
+ import signal
4545
+ try:
4546
+ proc = subprocess.Popen(
4547
+ cmd,
4548
+ shell=True,
4549
+ stdout=subprocess.PIPE,
4550
+ stderr=subprocess.PIPE,
4551
+ stdin=subprocess.DEVNULL,
4552
+ text=True,
4553
+ start_new_session=True,
4554
+ errors="replace"
4555
+ )
4556
+ try:
4557
+ out, err = proc.communicate(timeout=25)
4558
+ returncode = proc.returncode
4559
+ raw_out = (out or "") + (err or "")
4560
+ except subprocess.TimeoutExpired:
4561
+ try:
4562
+ os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
4563
+ except Exception:
4564
+ pass
4565
+ raw_out = "[BLOCKED] Command timed out after 25 seconds."
4566
+ returncode = 124
4567
+ except Exception as cmd_err:
4568
+ raw_out = f"[BLOCKED] Command execution error: {cmd_err}"
4569
+ returncode = 1
4570
+ bounded_out = _smart_truncate_output(raw_out)
4571
+ if returncode != 0:
4572
+ res_content = bounded_out or f"Command exited with status code {returncode}"
4573
+ is_err = True
4574
+ else:
4575
+ res_content = bounded_out or "(command completed successfully with no output)"
4576
+ elif t_name == "akash_read_file":
4577
+ res_content, is_err = _handle_tool_read_file(t_args, scopes)
4578
+ elif t_name == "akash_write_file":
4579
+ _save_undo_checkpoint()
4580
+ res_content, is_err = _handle_tool_write_file(t_args, scopes)
4581
+ elif t_name == "akash_list_dir":
4582
+ res_content, is_err = _handle_tool_list_dir(t_args, scopes)
4583
+ elif t_name == "akash_checkpoint_create":
4584
+ label = t_args.get("label", "agent_checkpoint")
4585
+ from .terminal import build_snapshot
4586
+ _, manifest, _ = build_snapshot(label=label)
4587
+ res_content = f"Checkpoint '{label}' created ({len(manifest.get('files', []))} files tracked)"
4588
+ elif t_name == "akash_undo":
4589
+ ok, msg_txt = _restore_undo_checkpoint()
4590
+ res_content = msg_txt
4591
+ is_err = not ok
4592
+ elif t_name == "akash_scope_details":
4593
+ res_content, is_err = _handle_tool_scope_details(scopes, pname, target_pid)
4594
+ else:
4595
+ res_content = f"Tool '{t_name}' not found."
4596
+ is_err = True
4597
+
4598
+ sys.stdout.write(json.dumps({
4599
+ "jsonrpc": "2.0",
4600
+ "id": msg_id,
4601
+ "result": {
4602
+ "content": [{"type": "text", "text": res_content}],
4603
+ "isError": is_err
4604
+ }
4605
+ }) + "\n")
4606
+ sys.stdout.flush()
4607
+ else:
4608
+ if msg_id is not None:
4609
+ sys.stdout.write(json.dumps({
4610
+ "jsonrpc": "2.0",
4611
+ "id": msg_id,
4612
+ "error": {"code": -32601, "message": f"Method '{method}' not found"}
4613
+ }) + "\n")
4614
+ sys.stdout.flush()
4615
+
4616
+
4617
+ @app.command("tunnel")
4618
+ @agent_app.command("tunnel")
4619
+ def cmd_tunnel(
4620
+ url: Optional[str] = typer.Option(None, "--url", "-u", help="Set custom domain or Cloudflare tunnel URL"),
4621
+ reset: bool = typer.Option(False, "--reset", "-r", help="Reset domain back to default akashbox.duckdns.org"),
4622
+ ):
4623
+ """Configure or view custom domain / Cloudflare tunnel for internet mode."""
4624
+ if reset:
4625
+ ConfigManager.delete("api_url")
4626
+ _ok(f"Reset storage and relay URL to default: {_cy(DEFAULT_STORAGE_URL)}")
4627
+ return
4628
+
4629
+ if url:
4630
+ clean = url.strip().rstrip("/")
4631
+ if not clean.startswith(("http://", "https://")):
4632
+ clean = f"https://{clean}"
4633
+ ConfigManager.set("api_url", clean)
4634
+ _ok(f"Configured active gateway domain to: {_cy(clean)}")
4635
+ _info("All remote MCP and API operations will now route through this domain.")
4636
+ return
4637
+
4638
+ cfg = ConfigManager.load()
4639
+ current_url = cfg.get("api_url", DEFAULT_STORAGE_URL)
4640
+ _p()
4641
+ print(f" {_C}◈{_RS} {_W}Akash Gateway Domain Configuration{_RS}")
4642
+ print(f" {_GR}──────────────────────────────────────────────────{_RS}")
4643
+ print(f" {_DM}Active Domain :{_RS} {_cy(current_url)}")
4644
+ print(f" {_DM}Default Relay :{_RS} {_dim(DEFAULT_STORAGE_URL)}")
4645
+ print(f" {_GR}──────────────────────────────────────────────────{_RS}")
4646
+ _p()
4647
+ _info("How to connect your own domain or Cloudflare Tunnel:")
4648
+ print(f" 1. Start Cloudflare Tunnel locally : {_cy('cloudflared tunnel --url http://127.0.0.1:8000')}")
4649
+ print(f" 2. Configure tunnel in akashcli : {_cy('akash tunnel --url https://<your-subdomain>.trycloudflare.com')}")
4650
+ print(f" 3. Reset back to Akashbox relay : {_cy('akash tunnel --reset')}")
4651
+ _p()
4652
+
4363
4653
  if __name__ == "__main__":
4364
4654
  app()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akashcli
3
- Version: 4.8.7
3
+ Version: 4.8.9
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.7"
3
+ version = "4.8.9"
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