akashcli 4.8.8__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.8 → akashcli-4.8.9}/PKG-INFO +1 -1
  2. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/__init__.py +1 -1
  3. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/cli.py +307 -17
  4. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli.egg-info/PKG-INFO +1 -1
  5. {akashcli-4.8.8 → akashcli-4.8.9}/pyproject.toml +1 -1
  6. {akashcli-4.8.8 → akashcli-4.8.9}/README.md +0 -0
  7. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/_net.py +0 -0
  8. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/auth.py +0 -0
  9. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/client.py +0 -0
  10. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/config.py +0 -0
  11. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/crypto.py +0 -0
  12. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/env_manager.py +0 -0
  13. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/exceptions.py +0 -0
  14. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/exec.py +0 -0
  15. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/integrity.py +0 -0
  16. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/logger.py +0 -0
  17. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/models.py +0 -0
  18. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/p2p.py +0 -0
  19. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/packages.py +0 -0
  20. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/relay.py +0 -0
  21. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/restore.py +0 -0
  22. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/snapshot_crypto.py +0 -0
  23. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/storage.py +0 -0
  24. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/terminal.py +0 -0
  25. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/updater.py +0 -0
  26. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/uploader.py +0 -0
  27. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli/utils.py +0 -0
  28. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli.egg-info/SOURCES.txt +0 -0
  29. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli.egg-info/dependency_links.txt +0 -0
  30. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli.egg-info/entry_points.txt +0 -0
  31. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli.egg-info/requires.txt +0 -0
  32. {akashcli-4.8.8 → akashcli-4.8.9}/akashcli.egg-info/top_level.txt +0 -0
  33. {akashcli-4.8.8 → 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.8
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.8"
5
+ __version__ = "4.8.9"
6
6
 
7
7
  AKASH_VERSION = __version__
8
8
 
@@ -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.8
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.8"
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