Add admin configuration system and M3 content automation
Admin Configuration System: - Add registry_settings table to database schema - Create settings.py module with 17 configurable settings across 5 categories (vetting, similarity, sync, moderation, rate_limits) - Add admin API endpoints for settings management (list/get/set/reset) - Create admin settings web UI page with tabbed categories - Add CLI commands: cmdforge registry config list/get/set - Wire settings page link into admin index M3 Content Automation Scripts: - scrutiny.py: Quality scoring with 5 weighted checks - similarity.py: TF-IDF based duplicate detection - vet_pipeline.py: Combined vetting with auto-decision logic - fabric_sync.py: Scheduled Fabric pattern synchronization - fabric-sync.service/timer: systemd units for scheduled sync Settings available: - Vetting thresholds (approve/reject) and check weights - Similarity detection thresholds (duplicate/similar/related) - Fabric sync configuration (enabled, interval, auto-approve) - Moderation settings (require review, auto-approve private) - Rate limiting configuration Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
@@ -190,6 +190,16 @@ def main():
|
||||
p_reg_browse = registry_sub.add_parser("browse", help="Browse tools (TUI)")
|
||||
p_reg_browse.set_defaults(func=cmd_registry)
|
||||
|
||||
# registry config (admin settings management)
|
||||
p_reg_config = registry_sub.add_parser("config", help="Manage registry settings (admin)")
|
||||
p_reg_config.add_argument("action", nargs="?", choices=["list", "get", "set"], default="list",
|
||||
help="Action to perform (default: list)")
|
||||
p_reg_config.add_argument("key", nargs="?", help="Setting key (for get/set)")
|
||||
p_reg_config.add_argument("value", nargs="?", help="Setting value (for set)")
|
||||
p_reg_config.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
p_reg_config.add_argument("--category", "-c", help="Filter by category (for list)")
|
||||
p_reg_config.set_defaults(func=cmd_registry)
|
||||
|
||||
# Default for registry with no subcommand
|
||||
p_registry.set_defaults(func=lambda args: cmd_registry(args) if args.registry_cmd else (setattr(args, 'registry_cmd', None) or cmd_registry(args)))
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ def cmd_registry(args):
|
||||
return _cmd_registry_my_tools(args)
|
||||
elif args.registry_cmd == "browse":
|
||||
return _cmd_registry_browse(args)
|
||||
elif args.registry_cmd == "config":
|
||||
return _cmd_registry_config(args)
|
||||
else:
|
||||
# Default: show registry help
|
||||
print("Registry commands:")
|
||||
@@ -44,6 +46,7 @@ def cmd_registry(args):
|
||||
print(" publish [path] Publish a tool")
|
||||
print(" my-tools List your published tools")
|
||||
print(" browse Browse tools (TUI)")
|
||||
print(" config [action] Manage registry settings (admin)")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -505,3 +508,158 @@ def _cmd_registry_browse(args):
|
||||
from ..gui import run_gui
|
||||
# Launch GUI - it will open to Registry page
|
||||
return run_gui()
|
||||
|
||||
|
||||
def _cmd_registry_config(args):
|
||||
"""Manage registry settings (admin only)."""
|
||||
from ..registry_client import RegistryError, get_client
|
||||
|
||||
action = getattr(args, 'action', 'list')
|
||||
key = getattr(args, 'key', None)
|
||||
value = getattr(args, 'value', None)
|
||||
as_json = getattr(args, 'json', False)
|
||||
category = getattr(args, 'category', None)
|
||||
|
||||
try:
|
||||
client = get_client()
|
||||
|
||||
if action == "list":
|
||||
return _config_list(client, as_json, category)
|
||||
elif action == "get":
|
||||
if not key:
|
||||
print("Error: key is required for 'get' action", file=sys.stderr)
|
||||
print("Usage: cmdforge registry config get <key>", file=sys.stderr)
|
||||
return 1
|
||||
return _config_get(client, key, as_json)
|
||||
elif action == "set":
|
||||
if not key or value is None:
|
||||
print("Error: key and value are required for 'set' action", file=sys.stderr)
|
||||
print("Usage: cmdforge registry config set <key> <value>", file=sys.stderr)
|
||||
return 1
|
||||
return _config_set(client, key, value)
|
||||
|
||||
except RegistryError as e:
|
||||
if e.code == "UNAUTHORIZED":
|
||||
print("Authentication failed.", file=sys.stderr)
|
||||
print("This command requires admin privileges.", file=sys.stderr)
|
||||
print("Set your admin token with: cmdforge config set-token <token>", file=sys.stderr)
|
||||
elif e.code == "FORBIDDEN":
|
||||
print("Access denied. Admin privileges required.", 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 _config_list(client, as_json, category=None):
|
||||
"""List all settings."""
|
||||
# Use the admin settings endpoint
|
||||
response = client._request("GET", "/admin/settings")
|
||||
settings = response.get("settings", [])
|
||||
|
||||
# Filter by category if specified
|
||||
if category:
|
||||
settings = [s for s in settings if s.get("category") == category]
|
||||
|
||||
if as_json:
|
||||
print(json.dumps({"settings": settings}, indent=2))
|
||||
return 0
|
||||
|
||||
if not settings:
|
||||
print("No settings found.")
|
||||
return 0
|
||||
|
||||
# Group by category
|
||||
by_category = {}
|
||||
for s in settings:
|
||||
cat = s.get("category", "general")
|
||||
if cat not in by_category:
|
||||
by_category[cat] = []
|
||||
by_category[cat].append(s)
|
||||
|
||||
print("Registry Settings")
|
||||
print("=" * 60)
|
||||
|
||||
for cat, cat_settings in sorted(by_category.items()):
|
||||
print(f"\n[{cat.upper()}]")
|
||||
for s in cat_settings:
|
||||
key = s.get("key", "")
|
||||
value = s.get("value")
|
||||
value_type = s.get("value_type", "string")
|
||||
desc = s.get("description", "")
|
||||
is_default = s.get("is_default", True)
|
||||
|
||||
# Format value display
|
||||
if value_type == "bool":
|
||||
value_str = "true" if value else "false"
|
||||
else:
|
||||
value_str = str(value)
|
||||
|
||||
status = "" if is_default else " (modified)"
|
||||
print(f" {key}")
|
||||
print(f" Value: {value_str}{status}")
|
||||
if desc:
|
||||
print(f" {desc}")
|
||||
|
||||
print()
|
||||
print("Use 'cmdforge registry config get <key>' to see a setting's value")
|
||||
print("Use 'cmdforge registry config set <key> <value>' to change a setting")
|
||||
return 0
|
||||
|
||||
|
||||
def _config_get(client, key, as_json):
|
||||
"""Get a specific setting."""
|
||||
response = client._request("GET", f"/admin/settings/{key}")
|
||||
|
||||
if as_json:
|
||||
print(json.dumps(response, indent=2))
|
||||
return 0
|
||||
|
||||
setting = response.get("setting", {})
|
||||
print(f"Key: {setting.get('key', key)}")
|
||||
print(f"Value: {setting.get('value')}")
|
||||
print(f"Type: {setting.get('value_type', 'string')}")
|
||||
print(f"Category: {setting.get('category', 'general')}")
|
||||
if setting.get('description'):
|
||||
print(f"Description: {setting['description']}")
|
||||
if setting.get('updated_at'):
|
||||
print(f"Last updated: {setting['updated_at'][:19]} by {setting.get('updated_by', 'system')}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def _config_set(client, key, value):
|
||||
"""Set a setting value."""
|
||||
# Try to parse value as appropriate type
|
||||
parsed_value = value
|
||||
|
||||
# Try to parse as bool
|
||||
if value.lower() in ("true", "false"):
|
||||
parsed_value = value.lower() == "true"
|
||||
# Try to parse as number
|
||||
else:
|
||||
try:
|
||||
if "." in value:
|
||||
parsed_value = float(value)
|
||||
else:
|
||||
parsed_value = int(value)
|
||||
except ValueError:
|
||||
# Keep as string
|
||||
pass
|
||||
|
||||
response = client._request("PUT", f"/admin/settings/{key}", json={"value": parsed_value})
|
||||
|
||||
if response.get("success"):
|
||||
print(f"Setting '{key}' updated successfully.")
|
||||
print(f"New value: {response.get('setting', {}).get('value', parsed_value)}")
|
||||
else:
|
||||
print(f"Failed to update setting: {response.get('error', 'Unknown error')}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user