akashcli 4.8.3__tar.gz → 4.8.4__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.3 → akashcli-4.8.4}/PKG-INFO +1 -1
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/cli.py +196 -28
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli.egg-info/PKG-INFO +1 -1
- {akashcli-4.8.3 → akashcli-4.8.4}/pyproject.toml +1 -1
- {akashcli-4.8.3 → akashcli-4.8.4}/README.md +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/__init__.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/_net.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/auth.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/client.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/config.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/crypto.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/env_manager.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/exceptions.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/exec.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/integrity.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/logger.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/models.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/p2p.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/packages.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/relay.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/restore.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/snapshot_crypto.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/storage.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/terminal.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/updater.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/uploader.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli/utils.py +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli.egg-info/SOURCES.txt +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli.egg-info/dependency_links.txt +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli.egg-info/entry_points.txt +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli.egg-info/requires.txt +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/akashcli.egg-info/top_level.txt +0 -0
- {akashcli-4.8.3 → akashcli-4.8.4}/setup.cfg +0 -0
|
@@ -2819,11 +2819,35 @@ def agent_list(raw: bool = typer.Option(False, "--json", help="Raw JSON output")
|
|
|
2819
2819
|
print(f" {_DM}{hdr_status}{hdr_id}{hdr_name}{hdr_cid}{hdr_scopes}{_RS}")
|
|
2820
2820
|
print(f" {_GR}{'─'*76}{_RS}")
|
|
2821
2821
|
|
|
2822
|
+
import datetime
|
|
2823
|
+
now_utc = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
|
|
2824
|
+
|
|
2822
2825
|
last_suffix = ""
|
|
2826
|
+
expired_count = 0
|
|
2823
2827
|
for pid, data in agents.items():
|
|
2824
2828
|
is_default = (pid == default_pid)
|
|
2825
|
-
|
|
2826
|
-
|
|
2829
|
+
|
|
2830
|
+
# Check expiration status
|
|
2831
|
+
is_expired = False
|
|
2832
|
+
exp_val = data.get("expires_at")
|
|
2833
|
+
if exp_val:
|
|
2834
|
+
try:
|
|
2835
|
+
exp_dt = datetime.datetime.fromisoformat(exp_val.replace("Z", "+00:00")).replace(tzinfo=None)
|
|
2836
|
+
if exp_dt < now_utc:
|
|
2837
|
+
is_expired = True
|
|
2838
|
+
except Exception:
|
|
2839
|
+
pass
|
|
2840
|
+
|
|
2841
|
+
if is_expired:
|
|
2842
|
+
expired_count += 1
|
|
2843
|
+
status_txt = "✗ expired"
|
|
2844
|
+
status_color = _R
|
|
2845
|
+
elif is_default:
|
|
2846
|
+
status_txt = "● active"
|
|
2847
|
+
status_color = _G
|
|
2848
|
+
else:
|
|
2849
|
+
status_txt = "○ idle"
|
|
2850
|
+
status_color = _DM
|
|
2827
2851
|
|
|
2828
2852
|
name = data.get("name", "Agent")
|
|
2829
2853
|
if len(name) > 14:
|
|
@@ -2856,12 +2880,16 @@ def agent_list(raw: bool = typer.Option(False, "--json", help="Raw JSON output")
|
|
|
2856
2880
|
print(f" {c_status}{c_id}{c_name}{c_cid}{c_scopes}")
|
|
2857
2881
|
|
|
2858
2882
|
print(f" {_GR}{'─'*76}{_RS}")
|
|
2859
|
-
|
|
2883
|
+
if expired_count > 0:
|
|
2884
|
+
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")
|
|
2885
|
+
else:
|
|
2886
|
+
_p()
|
|
2860
2887
|
print(f" {_W}Quick Launch:{_RS}")
|
|
2861
2888
|
print(f" • Use default profile : {_C}akash mcp serve{_RS}")
|
|
2862
2889
|
suffix_hint = f" {_dim('(or: --id ' + last_suffix + ')')}" if last_suffix else ""
|
|
2863
2890
|
print(f" • Use specific ID : {_C}akash mcp serve --id {default_pid}{_RS}{suffix_hint}")
|
|
2864
|
-
print(f" •
|
|
2891
|
+
print(f" • Clean idle profiles : {_C}akash mcp rm --all{_RS}")
|
|
2892
|
+
print(f" • Renew expiration : {_C}akash mcp renew {default_pid} --days 30{_RS}")
|
|
2865
2893
|
print(f" • Create new profile : {_C}akash mcp create -n Dev -r @coder{_RS}")
|
|
2866
2894
|
_p()
|
|
2867
2895
|
|
|
@@ -3001,6 +3029,12 @@ def agent_info(
|
|
|
3001
3029
|
status = d.get("status", "active") + " (live server)"
|
|
3002
3030
|
exp = d.get("expires_at", "unknown")[:19].replace("T", " ")
|
|
3003
3031
|
is_live = True
|
|
3032
|
+
if pid in all_a and d.get("expires_at"):
|
|
3033
|
+
all_a[pid]["expires_at"] = d.get("expires_at")
|
|
3034
|
+
try:
|
|
3035
|
+
agents_file.write_text(json.dumps(all_a, indent=2))
|
|
3036
|
+
except Exception:
|
|
3037
|
+
pass
|
|
3004
3038
|
elif r.status_code == 404:
|
|
3005
3039
|
status = "not found on server"
|
|
3006
3040
|
except Exception:
|
|
@@ -3075,42 +3109,175 @@ def agent_update(
|
|
|
3075
3109
|
else:
|
|
3076
3110
|
_err(f"Failed to update scopes: {res.text}")
|
|
3077
3111
|
|
|
3078
|
-
@agent_app.command("
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3112
|
+
@agent_app.command("rm")
|
|
3113
|
+
@agent_app.command("remove", hidden=True)
|
|
3114
|
+
@agent_app.command("delete", hidden=True)
|
|
3115
|
+
@agent_app.command("revoke", hidden=True)
|
|
3116
|
+
def agent_remove(
|
|
3117
|
+
target: Optional[str] = typer.Argument(None, help="Profile ID (e.g. 88d7), Client ID suffix (e.g. ddff), or name"),
|
|
3118
|
+
all_idle: bool = typer.Option(False, "--all", "-a", help="Remove all idle profiles, keeping active default"),
|
|
3119
|
+
expired_only: bool = typer.Option(False, "--expired", "-e", help="Remove all expired profiles"),
|
|
3120
|
+
yes: bool = typer.Option(False, "--yes", "-y", "--force", "-f", help="Skip confirmation prompt"),
|
|
3121
|
+
url: Optional[str] = typer.Option(None, "--url", help="Override server URL"),
|
|
3083
3122
|
):
|
|
3084
|
-
"""
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3123
|
+
"""Remove / clean up one or more MCP profile IDs locally and on gateway."""
|
|
3124
|
+
import json, datetime
|
|
3125
|
+
from pathlib import Path
|
|
3126
|
+
agents_file = Path.home() / ".akash" / "agents.json"
|
|
3127
|
+
if not agents_file.exists():
|
|
3128
|
+
_info("No saved profiles to remove.")
|
|
3129
|
+
return
|
|
3130
|
+
|
|
3131
|
+
try:
|
|
3132
|
+
agents = json.loads(agents_file.read_text(encoding="utf-8"))
|
|
3133
|
+
except Exception:
|
|
3134
|
+
agents = {}
|
|
3135
|
+
|
|
3136
|
+
if not agents:
|
|
3137
|
+
_info("No saved profiles to remove.")
|
|
3138
|
+
return
|
|
3139
|
+
|
|
3140
|
+
default_pid = next(reversed(list(agents.keys())))
|
|
3141
|
+
now_utc = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
|
|
3142
|
+
|
|
3143
|
+
targets_to_delete = []
|
|
3144
|
+
|
|
3145
|
+
if all_idle:
|
|
3146
|
+
for pid in list(agents.keys()):
|
|
3147
|
+
if pid != default_pid:
|
|
3148
|
+
targets_to_delete.append(pid)
|
|
3149
|
+
if not targets_to_delete:
|
|
3150
|
+
_info("No idle profiles to remove (only active profile exists).")
|
|
3151
|
+
return
|
|
3152
|
+
if not yes:
|
|
3153
|
+
try:
|
|
3154
|
+
sys.stdout.write(f" Remove {len(targets_to_delete)} idle profile(s)? [y/N]: ")
|
|
3155
|
+
sys.stdout.flush()
|
|
3156
|
+
ans = sys.stdin.readline().strip().lower()
|
|
3157
|
+
if ans not in ("y", "yes"):
|
|
3158
|
+
_info("Aborted.")
|
|
3159
|
+
return
|
|
3160
|
+
except (KeyboardInterrupt, EOFError):
|
|
3161
|
+
return
|
|
3162
|
+
|
|
3163
|
+
elif expired_only:
|
|
3164
|
+
for pid, d in list(agents.items()):
|
|
3165
|
+
exp_str = d.get("expires_at")
|
|
3166
|
+
if exp_str:
|
|
3167
|
+
try:
|
|
3168
|
+
exp_dt = datetime.datetime.fromisoformat(exp_str.replace("Z", "+00:00")).replace(tzinfo=None)
|
|
3169
|
+
if exp_dt < now_utc:
|
|
3170
|
+
targets_to_delete.append(pid)
|
|
3171
|
+
except Exception:
|
|
3172
|
+
pass
|
|
3173
|
+
if not targets_to_delete:
|
|
3174
|
+
_info("No expired profiles found to remove.")
|
|
3175
|
+
return
|
|
3176
|
+
|
|
3177
|
+
elif target:
|
|
3178
|
+
pid, cid, name = _resolve_agent_id(target)
|
|
3179
|
+
if pid not in agents:
|
|
3180
|
+
_err(f"Profile '{target}' not found in saved profiles.")
|
|
3181
|
+
raise typer.Exit(1)
|
|
3182
|
+
targets_to_delete.append(pid)
|
|
3183
|
+
|
|
3184
|
+
else:
|
|
3185
|
+
_err("Specify a Profile ID to remove (e.g. 'akash mcp rm 88d7'), or use '--all' / '--expired'.")
|
|
3186
|
+
_info(f"Run {_cy('akash mcp')} to view all IDs.")
|
|
3088
3187
|
raise typer.Exit(1)
|
|
3089
3188
|
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3189
|
+
_p()
|
|
3190
|
+
removed_count = 0
|
|
3191
|
+
for pid in targets_to_delete:
|
|
3192
|
+
d = agents.get(pid, {})
|
|
3193
|
+
name = d.get("name", "Agent")
|
|
3194
|
+
cid = d.get("client_id", "")
|
|
3195
|
+
suffix = cid[-4:] if len(cid) >= 4 else ""
|
|
3196
|
+
|
|
3197
|
+
# Best-effort remote revocation on gateway
|
|
3198
|
+
target_url = url or d.get("url")
|
|
3199
|
+
_, base = _resolve_client(target_url, require_key=False, silent=True)
|
|
3200
|
+
if cid and base:
|
|
3201
|
+
try:
|
|
3202
|
+
httpx.delete(f"{base}/api/agent/app/{cid}", timeout=3.0)
|
|
3203
|
+
except Exception:
|
|
3204
|
+
pass
|
|
3205
|
+
|
|
3206
|
+
if pid in agents:
|
|
3207
|
+
del agents[pid]
|
|
3208
|
+
removed_count += 1
|
|
3209
|
+
print(f" {_G}✓{_RS} Removed profile {_W}{name}{_RS} ({_ye(pid)}{_dim(' · ...' + suffix if suffix else '')})")
|
|
3210
|
+
|
|
3211
|
+
try:
|
|
3212
|
+
agents_file.write_text(json.dumps(agents, indent=2))
|
|
3213
|
+
except Exception:
|
|
3214
|
+
pass
|
|
3094
3215
|
|
|
3095
|
-
|
|
3096
|
-
|
|
3216
|
+
_p()
|
|
3217
|
+
_ok(f"Cleaned up {removed_count} MCP profile(s). {len(agents)} profile(s) remaining.")
|
|
3218
|
+
_p()
|
|
3097
3219
|
|
|
3098
|
-
|
|
3220
|
+
@agent_app.command("renew")
|
|
3221
|
+
def agent_renew(
|
|
3222
|
+
target: Optional[str] = typer.Argument(None, help="Profile ID, Client ID suffix, or name (defaults to active profile)"),
|
|
3223
|
+
days: int = typer.Option(7, "--days", "-d", help="Number of days to extend validity (default: 7)"),
|
|
3224
|
+
url: Optional[str] = typer.Option(None, "--url", help="Override server URL"),
|
|
3225
|
+
):
|
|
3226
|
+
"""Renew/extend the validity of an MCP profile / agent ID."""
|
|
3099
3227
|
import json
|
|
3100
3228
|
from pathlib import Path
|
|
3101
3229
|
agents_file = Path.home() / ".akash" / "agents.json"
|
|
3102
|
-
if agents_file.exists():
|
|
3230
|
+
if not agents_file.exists():
|
|
3231
|
+
_err("No saved profiles found.")
|
|
3232
|
+
raise typer.Exit(1)
|
|
3233
|
+
|
|
3234
|
+
try:
|
|
3235
|
+
agents = json.loads(agents_file.read_text(encoding="utf-8"))
|
|
3236
|
+
except Exception:
|
|
3237
|
+
agents = {}
|
|
3238
|
+
|
|
3239
|
+
if not agents:
|
|
3240
|
+
_err("No saved profiles found.")
|
|
3241
|
+
raise typer.Exit(1)
|
|
3242
|
+
|
|
3243
|
+
target_id = target
|
|
3244
|
+
if not target_id:
|
|
3245
|
+
target_id = next(reversed(list(agents.keys())))
|
|
3246
|
+
|
|
3247
|
+
pid, cid, name = _resolve_agent_id(target_id)
|
|
3248
|
+
cached = agents.get(pid, {})
|
|
3249
|
+
target_url = url or cached.get("url")
|
|
3250
|
+
_, base = _resolve_client(target_url, require_key=False, silent=True)
|
|
3251
|
+
|
|
3252
|
+
with _Spin(f"renewing MCP profile {pid} ({name}) for {days} days…"):
|
|
3103
3253
|
try:
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3254
|
+
res = httpx.patch(
|
|
3255
|
+
f"{base}/api/agent/app/{cid}",
|
|
3256
|
+
json={"renew": True, "ttl_days": days},
|
|
3257
|
+
timeout=8.0
|
|
3258
|
+
)
|
|
3259
|
+
except Exception as e:
|
|
3260
|
+
_err(f"Network error contacting gateway ({base}): {e}")
|
|
3261
|
+
raise typer.Exit(1)
|
|
3109
3262
|
|
|
3110
3263
|
if res.status_code == 200:
|
|
3111
|
-
|
|
3264
|
+
d = res.json()
|
|
3265
|
+
new_exp = d.get("expires_at", "")
|
|
3266
|
+
if pid in agents:
|
|
3267
|
+
agents[pid]["expires_at"] = new_exp
|
|
3268
|
+
try:
|
|
3269
|
+
agents_file.write_text(json.dumps(agents, indent=2))
|
|
3270
|
+
except Exception:
|
|
3271
|
+
pass
|
|
3272
|
+
_p()
|
|
3273
|
+
_ok(f"Successfully renewed profile {_ye(name)} ({_C}{pid}{_RS} · {_dim('...' + cid[-4:])}) for {_G}{days} days{_RS}!")
|
|
3274
|
+
if new_exp:
|
|
3275
|
+
exp_clean = new_exp[:19].replace("T", " ")
|
|
3276
|
+
print(f" {_DM}New Expiration : {_RS}{_cy(exp_clean)} UTC")
|
|
3277
|
+
_p()
|
|
3112
3278
|
else:
|
|
3113
|
-
|
|
3279
|
+
_err(f"Failed to renew profile on gateway: HTTP {res.status_code} - {res.text}")
|
|
3280
|
+
raise typer.Exit(1)
|
|
3114
3281
|
|
|
3115
3282
|
|
|
3116
3283
|
@agent_app.command("create")
|
|
@@ -3161,6 +3328,7 @@ def agent_create(
|
|
|
3161
3328
|
"client_secret": d["client_secret"],
|
|
3162
3329
|
"name": name,
|
|
3163
3330
|
"scopes": d.get("scopes", effective_scopes),
|
|
3331
|
+
"expires_at": d.get("expires_at"),
|
|
3164
3332
|
"url": base
|
|
3165
3333
|
}
|
|
3166
3334
|
agents_file.write_text(json.dumps(agents, indent=2))
|
|
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
|