Add collections CLI commands and admin management
CLI Commands: - cmdforge collections list - List available collections - cmdforge collections info <name> - Show collection details - cmdforge collections install <name> - Install all tools in a collection Admin API: - GET /api/v1/admin/collections - List all collections (admin) - POST /api/v1/admin/collections - Create collection - PUT /api/v1/admin/collections/<name> - Update collection - DELETE /api/v1/admin/collections/<name> - Delete collection Admin Web UI: - /dashboard/admin/collections - List and manage collections - /dashboard/admin/collections/new - Create new collection form - /dashboard/admin/collections/<name>/edit - Edit collection form - Added "Manage collections" link to admin dashboard Registry Client: - Added get_collections() method - Added get_collection(name) method Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
"""Collections commands for CmdForge CLI."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
from ..resolver import install_from_registry
|
||||
|
||||
|
||||
def cmd_collections(args):
|
||||
"""Handle collections subcommands."""
|
||||
if args.collections_cmd == "list":
|
||||
return _cmd_collections_list(args)
|
||||
elif args.collections_cmd == "info":
|
||||
return _cmd_collections_info(args)
|
||||
elif args.collections_cmd == "install":
|
||||
return _cmd_collections_install(args)
|
||||
else:
|
||||
# Default: show help
|
||||
print("Collections commands:")
|
||||
print(" list List available collections")
|
||||
print(" info <name> Show collection details")
|
||||
print(" install <name> Install all tools in a collection")
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_collections_list(args):
|
||||
"""List available collections."""
|
||||
from ..registry_client import RegistryError, get_client
|
||||
|
||||
try:
|
||||
client = get_client()
|
||||
collections = client.get_collections()
|
||||
|
||||
# JSON output
|
||||
if getattr(args, 'json', False):
|
||||
print(json.dumps({"collections": collections}, indent=2))
|
||||
return 0
|
||||
|
||||
if not collections:
|
||||
print("No collections available.")
|
||||
return 0
|
||||
|
||||
print(f"Available collections ({len(collections)}):\n")
|
||||
for coll in collections:
|
||||
name = coll.get("name", "")
|
||||
display_name = coll.get("display_name", name)
|
||||
description = coll.get("description", "")
|
||||
tools = coll.get("tools", [])
|
||||
tool_count = len(tools) if isinstance(tools, list) else 0
|
||||
|
||||
print(f" {name}")
|
||||
print(f" {display_name}")
|
||||
if description:
|
||||
desc_short = description[:60] + ('...' if len(description) > 60 else '')
|
||||
print(f" {desc_short}")
|
||||
print(f" Tools: {tool_count}")
|
||||
print()
|
||||
|
||||
print("Install a collection with: cmdforge collections install <name>")
|
||||
|
||||
except RegistryError as e:
|
||||
if e.code == "CONNECTION_ERROR":
|
||||
print("Could not connect to the registry.", file=sys.stderr)
|
||||
else:
|
||||
print(f"Error: {e.message}", file=sys.stderr)
|
||||
return 1
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_collections_info(args):
|
||||
"""Show collection details."""
|
||||
from ..registry_client import RegistryError, get_client
|
||||
|
||||
name = args.name
|
||||
|
||||
try:
|
||||
client = get_client()
|
||||
coll = client.get_collection(name)
|
||||
|
||||
# JSON output
|
||||
if getattr(args, 'json', False):
|
||||
print(json.dumps(coll, indent=2))
|
||||
return 0
|
||||
|
||||
display_name = coll.get("display_name", name)
|
||||
description = coll.get("description", "")
|
||||
maintainer = coll.get("maintainer", "")
|
||||
tools = coll.get("tools", [])
|
||||
pinned = coll.get("pinned", {})
|
||||
tags = coll.get("tags", [])
|
||||
|
||||
print(f"{display_name}")
|
||||
print("=" * 50)
|
||||
if description:
|
||||
print(f"\n{description}\n")
|
||||
if maintainer:
|
||||
print(f"Maintainer: {maintainer}")
|
||||
if tags:
|
||||
print(f"Tags: {', '.join(tags)}")
|
||||
|
||||
print(f"\nTools ({len(tools)}):")
|
||||
for tool_ref in tools:
|
||||
version_constraint = pinned.get(tool_ref, "")
|
||||
if version_constraint:
|
||||
print(f" - {tool_ref} @ {version_constraint}")
|
||||
else:
|
||||
print(f" - {tool_ref}")
|
||||
|
||||
print(f"\nInstall all: cmdforge collections install {name}")
|
||||
|
||||
except RegistryError as e:
|
||||
if e.code == "COLLECTION_NOT_FOUND":
|
||||
print(f"Collection '{name}' not found.", file=sys.stderr)
|
||||
print("List available collections: cmdforge collections list", file=sys.stderr)
|
||||
elif e.code == "CONNECTION_ERROR":
|
||||
print("Could not connect to the registry.", file=sys.stderr)
|
||||
else:
|
||||
print(f"Error: {e.message}", file=sys.stderr)
|
||||
return 1
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_collections_install(args):
|
||||
"""Install all tools in a collection."""
|
||||
from ..registry_client import RegistryError, get_client
|
||||
|
||||
name = args.name
|
||||
use_pinned = getattr(args, 'pinned', False)
|
||||
|
||||
try:
|
||||
client = get_client()
|
||||
coll = client.get_collection(name)
|
||||
|
||||
tools = coll.get("tools", [])
|
||||
pinned = coll.get("pinned", {}) if use_pinned else {}
|
||||
display_name = coll.get("display_name", name)
|
||||
|
||||
if not tools:
|
||||
print(f"Collection '{name}' has no tools.")
|
||||
return 0
|
||||
|
||||
print(f"Installing collection: {display_name}")
|
||||
print(f"Tools to install: {len(tools)}")
|
||||
if use_pinned and pinned:
|
||||
print(f"Using pinned versions: {len(pinned)} tools")
|
||||
print()
|
||||
|
||||
installed = 0
|
||||
failed = 0
|
||||
|
||||
for tool_ref in tools:
|
||||
version = pinned.get(tool_ref)
|
||||
try:
|
||||
print(f" Installing {tool_ref}...", end=" ", flush=True)
|
||||
resolved = install_from_registry(tool_ref, version)
|
||||
print(f"v{resolved.version}")
|
||||
installed += 1
|
||||
except RegistryError as e:
|
||||
print(f"FAILED: {e.message}")
|
||||
failed += 1
|
||||
except Exception as e:
|
||||
print(f"FAILED: {e}")
|
||||
failed += 1
|
||||
|
||||
print()
|
||||
print(f"Installed: {installed}/{len(tools)}")
|
||||
if failed:
|
||||
print(f"Failed: {failed}")
|
||||
return 1
|
||||
|
||||
print(f"\nCollection '{name}' installed successfully!")
|
||||
|
||||
except RegistryError as e:
|
||||
if e.code == "COLLECTION_NOT_FOUND":
|
||||
print(f"Collection '{name}' not found.", file=sys.stderr)
|
||||
print("List available collections: cmdforge collections list", file=sys.stderr)
|
||||
elif e.code == "CONNECTION_ERROR":
|
||||
print("Could not connect to the registry.", file=sys.stderr)
|
||||
else:
|
||||
print(f"Error: {e.message}", file=sys.stderr)
|
||||
return 1
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
Reference in New Issue
Block a user