Add registry endpoint and CLI command for updating tool READMEs
- PATCH /api/v1/tools/<owner>/<name>/readme updates README without affecting config hash or requiring version bump - Add RegistryClient.update_readme() method - Add `cmdforge registry update-readme` CLI command with --all flag for batch updating all published tools Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f2cb9c0057
commit
e83c9c15f3
|
|
@ -190,6 +190,12 @@ def main():
|
|||
p_reg_publish.add_argument("--owner", default="", help="Owner override (admin only, e.g. 'official')")
|
||||
p_reg_publish.set_defaults(func=cmd_registry)
|
||||
|
||||
# registry update-readme
|
||||
p_reg_update_readme = registry_sub.add_parser("update-readme", help="Update README for a published tool")
|
||||
p_reg_update_readme.add_argument("tool", help="Tool name (local name, will resolve owner)")
|
||||
p_reg_update_readme.add_argument("--all", action="store_true", dest="update_all", help="Update README for all published tools that have a local README.md")
|
||||
p_reg_update_readme.set_defaults(func=cmd_registry)
|
||||
|
||||
# registry my-tools
|
||||
p_reg_mytools = registry_sub.add_parser("my-tools", help="List your published tools")
|
||||
p_reg_mytools.set_defaults(func=cmd_registry)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ def cmd_registry(args):
|
|||
return _cmd_registry_update(args)
|
||||
elif args.registry_cmd == "publish":
|
||||
return _cmd_registry_publish(args)
|
||||
elif args.registry_cmd == "update-readme":
|
||||
return _cmd_registry_update_readme(args)
|
||||
elif args.registry_cmd == "my-tools":
|
||||
return _cmd_registry_my_tools(args)
|
||||
elif args.registry_cmd == "status":
|
||||
|
|
@ -47,6 +49,7 @@ def cmd_registry(args):
|
|||
print(" info <tool> Show tool information")
|
||||
print(" update Update local index cache")
|
||||
print(" publish [path] Publish a tool")
|
||||
print(" update-readme Update README for published tool(s)")
|
||||
print(" my-tools List your published tools")
|
||||
print(" status <tool> Check moderation status of a tool")
|
||||
print(" browse Browse tools (GUI)")
|
||||
|
|
@ -635,6 +638,86 @@ def _cmd_registry_publish(args):
|
|||
return 0
|
||||
|
||||
|
||||
def _cmd_registry_update_readme(args):
|
||||
"""Update README for published tool(s) on the registry."""
|
||||
from ..registry_client import RegistryError, get_client
|
||||
from ..tool import TOOLS_DIR, list_tools
|
||||
|
||||
try:
|
||||
client = get_client()
|
||||
except RegistryError as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if getattr(args, "update_all", False):
|
||||
# Update all published tools that have a local README
|
||||
tools = list_tools()
|
||||
updated = 0
|
||||
skipped = 0
|
||||
failed = 0
|
||||
for tool_name in tools:
|
||||
tool_dir = TOOLS_DIR / tool_name
|
||||
config_path = tool_dir / "config.yaml"
|
||||
readme_path = tool_dir / "README.md"
|
||||
|
||||
if not readme_path.exists():
|
||||
continue
|
||||
|
||||
# Check if published
|
||||
try:
|
||||
config = yaml.safe_load(config_path.read_text()) or {}
|
||||
except Exception:
|
||||
continue
|
||||
owner = config.get("registry_owner", "")
|
||||
if not owner:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
readme_content = readme_path.read_text()
|
||||
try:
|
||||
result = client.update_readme(owner, tool_name, readme_content)
|
||||
count = result.get("versions_updated", 0)
|
||||
print(f" {owner}/{tool_name}: updated ({count} version(s))")
|
||||
updated += 1
|
||||
except RegistryError as e:
|
||||
print(f" {owner}/{tool_name}: FAILED - {e}", file=sys.stderr)
|
||||
failed += 1
|
||||
|
||||
print(f"\nDone: {updated} updated, {failed} failed, {skipped} skipped (not published)")
|
||||
return 0
|
||||
|
||||
# Single tool mode
|
||||
tool_name = args.tool
|
||||
tool_dir = TOOLS_DIR / tool_name
|
||||
config_path = tool_dir / "config.yaml"
|
||||
readme_path = tool_dir / "README.md"
|
||||
|
||||
if not config_path.exists():
|
||||
print(f"Error: Tool '{tool_name}' not found", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if not readme_path.exists():
|
||||
print(f"Error: No README.md found for '{tool_name}'", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
config = yaml.safe_load(config_path.read_text()) or {}
|
||||
owner = config.get("registry_owner", "")
|
||||
if not owner:
|
||||
print(f"Error: Tool '{tool_name}' has no registry_owner — not published?", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
readme_content = readme_path.read_text()
|
||||
try:
|
||||
result = client.update_readme(owner, tool_name, readme_content)
|
||||
count = result.get("versions_updated", 0)
|
||||
print(f"Updated README for {owner}/{tool_name} ({count} version(s))")
|
||||
except RegistryError as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_registry_my_tools(args):
|
||||
"""List your published tools."""
|
||||
from ..registry_client import RegistryError, get_client
|
||||
|
|
|
|||
|
|
@ -2815,6 +2815,36 @@ def create_app() -> Flask:
|
|||
|
||||
return jsonify({"data": {"status": "active", "owner": owner, "name": name}})
|
||||
|
||||
@app.route("/api/v1/tools/<owner>/<name>/readme", methods=["PATCH"])
|
||||
@require_token
|
||||
def update_tool_readme(owner: str, name: str) -> Response:
|
||||
"""Update the README for an existing tool without affecting config or hash."""
|
||||
if g.current_publisher["slug"] != owner:
|
||||
return error_response("FORBIDDEN", "You can only update your own tools", 403)
|
||||
|
||||
data = request.get_json() or {}
|
||||
readme = data.get("readme")
|
||||
if readme is None:
|
||||
return error_response("VALIDATION_ERROR", "Missing 'readme' field", 400)
|
||||
|
||||
version = data.get("version")
|
||||
if version:
|
||||
result = g.db.execute(
|
||||
"UPDATE tools SET readme = ? WHERE owner = ? AND name = ? AND version = ?",
|
||||
[readme, owner, name, version],
|
||||
)
|
||||
else:
|
||||
# Update all versions
|
||||
result = g.db.execute(
|
||||
"UPDATE tools SET readme = ? WHERE owner = ? AND name = ?",
|
||||
[readme, owner, name],
|
||||
)
|
||||
if result.rowcount == 0:
|
||||
return error_response("TOOL_NOT_FOUND", f"Tool {owner}/{name} not found", 404)
|
||||
g.db.commit()
|
||||
|
||||
return jsonify({"data": {"status": "updated", "owner": owner, "name": name, "versions_updated": result.rowcount}})
|
||||
|
||||
@app.route("/api/v1/me/settings", methods=["PUT"])
|
||||
@require_token
|
||||
def update_settings() -> Response:
|
||||
|
|
|
|||
|
|
@ -666,6 +666,35 @@ class RegistryClient:
|
|||
|
||||
return response.json().get("data", {})
|
||||
|
||||
def update_readme(self, owner: str, name: str, readme: str, version: str = "") -> Dict[str, Any]:
|
||||
"""
|
||||
Update the README for an existing published tool.
|
||||
|
||||
Args:
|
||||
owner: Tool owner slug
|
||||
name: Tool name
|
||||
readme: New README content
|
||||
version: Optional specific version to update (default: all versions)
|
||||
|
||||
Returns:
|
||||
Dict with update status
|
||||
"""
|
||||
payload: Dict[str, Any] = {"readme": readme}
|
||||
if version:
|
||||
payload["version"] = version
|
||||
|
||||
response = self._request(
|
||||
"PATCH",
|
||||
f"/tools/{owner}/{name}/readme",
|
||||
json_data=payload,
|
||||
require_auth=True,
|
||||
)
|
||||
|
||||
if response.status_code not in (200, 201):
|
||||
self._handle_error_response(response)
|
||||
|
||||
return response.json().get("data", {})
|
||||
|
||||
def validate_token(self) -> tuple[bool, str]:
|
||||
"""
|
||||
Validate that the current token is valid.
|
||||
|
|
|
|||
Loading…
Reference in New Issue