Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:26:08 +08:00
commit 8f22ddf339
295 changed files with 59710 additions and 0 deletions

View File

@@ -0,0 +1,431 @@
# registry.query
**Version:** 0.1.0
**Status:** Active
**Tags:** registry, search, query, discovery, metadata, cli
## Overview
The `registry.query` skill enables programmatic searching of Betty registries (skills, agents, and commands) with flexible filtering capabilities. It's designed for dynamic discovery, workflow automation, and CLI autocompletion.
## Features
- **Multi-Registry Support**: Query skills, agents, commands, or hooks registries
- **Flexible Filtering**: Filter by name, version, status, tags, domain, and capability
- **Fuzzy Matching**: Optional fuzzy search for name and capability fields
- **Result Limiting**: Control the number of results returned
- **Rich Metadata**: Returns key metadata for each matching entry
- **Multiple Output Formats**: JSON, table, or compact format for different use cases
- **Table Formatting**: Aligned column display for easy CLI viewing
## Usage
### Command Line
```bash
# List all skills (compact format, default)
python3 skills/registry.query/registry_query.py skills
# Find skills with 'api' tag in table format
python3 skills/registry.query/registry_query.py skills --tag api --format table
# Find agents with 'design' capability
python3 skills/registry.query/registry_query.py agents --capability design
# Query hooks registry
python3 skills/registry.query/registry_query.py hooks --status active --format table
# Find active skills with name containing 'validate'
python3 skills/registry.query/registry_query.py skills --name validate --status active
# Fuzzy search for commands
python3 skills/registry.query/registry_query.py commands --name test --fuzzy
# Limit results to top 5
python3 skills/registry.query/registry_query.py skills --tag api --limit 5
# Get full JSON output
python3 skills/registry.query/registry_query.py skills --tag validation --format json
```
### Programmatic Use
```python
from skills.registry.query.registry_query import query_registry
# Query skills with API tag
result = query_registry(
registry="skills",
tag="api",
status="active"
)
if result["ok"]:
matching_entries = result["details"]["results"]
for entry in matching_entries:
print(f"{entry['name']}: {entry['description']}")
```
### Betty CLI
```bash
# Via Betty CLI (when registered)
betty registry query skills --tag api
betty registry query agents --capability "API design"
```
## Parameters
### Required
- **`registry`** (string): Registry to query
- Valid values: `skills`, `agents`, `commands`, `hooks`
### Optional Filters
- **`name`** (string): Filter by name (substring match, case-insensitive)
- **`version`** (string): Filter by exact version match
- **`status`** (string): Filter by status (e.g., `active`, `draft`, `deprecated`, `archived`)
- **`tag`** (string): Filter by single tag
- **`tags`** (array): Filter by multiple tags (matches any)
- **`capability`** (string): Filter by capability (agents only, substring match)
- **`domain`** (string): Filter by domain (alias for tag filter)
- **`fuzzy`** (boolean): Enable fuzzy matching for name and capability
- **`limit`** (integer): Maximum number of results to return
- **`format`** (string): Output format (`json`, `table`, `compact`)
- `json`: Full JSON response with all metadata
- `table`: Aligned column table for easy reading
- `compact`: Detailed list format (default)
## Output Format
### Success Response
```json
{
"ok": true,
"status": "success",
"errors": [],
"timestamp": "2025-10-23T10:30:00.000000Z",
"details": {
"registry": "skills",
"query": {
"name": "api",
"version": null,
"status": "active",
"tags": ["validation"],
"capability": null,
"domain": null,
"fuzzy": false,
"limit": null
},
"total_entries": 21,
"matching_entries": 3,
"results": [
{
"name": "api.validate",
"version": "0.1.0",
"description": "Validates OpenAPI or AsyncAPI specifications...",
"status": "active",
"tags": ["api", "validation", "openapi", "asyncapi"],
"dependencies": ["context.schema"],
"entrypoints": [
{
"command": "/api/validate",
"runtime": "python",
"description": "Validate API specification files"
}
],
"inputs": [...],
"outputs": [...]
}
]
}
}
```
### Error Response
```json
{
"ok": false,
"status": "failed",
"errors": ["Invalid registry: invalid_type"],
"timestamp": "2025-10-23T10:30:00.000000Z"
}
```
## Metadata Fields by Registry Type
### Skills
- `name`, `version`, `description`, `status`, `tags`
- `dependencies`: List of required skills
- `entrypoints`: Available commands and handlers
- `inputs`: Expected input parameters
- `outputs`: Generated outputs
### Agents
- `name`, `version`, `description`, `status`, `tags`
- `capabilities`: List of agent capabilities
- `skills_available`: Skills the agent can invoke
- `reasoning_mode`: `oneshot` or `iterative`
- `context_requirements`: Required context fields
### Commands
- `name`, `version`, `description`, `status`, `tags`
- `execution`: Execution configuration (type, target)
- `parameters`: Command parameters
### Hooks
- `name`, `version`, `description`, `status`, `tags`
- `event`: Hook event trigger (e.g., on_file_edit, on_commit)
- `command`: Command to execute
- `enabled`: Whether the hook is enabled
## Use Cases
### 1. Dynamic Discovery
Find skills related to a specific domain:
```bash
python3 skills/registry.query/registry_query.py skills --domain api
```
### 2. Workflow Automation
Programmatically find and invoke skills:
```python
# Find validation skills
result = query_registry(registry="skills", tag="validation", status="active")
for skill in result["details"]["results"]:
print(f"Found validation skill: {skill['name']}")
# Invoke skill programmatically
```
### 3. CLI Autocompletion
Generate autocompletion data:
```python
# Get all active skill names for tab completion
result = query_registry(registry="skills", status="active")
skill_names = [s["name"] for s in result["details"]["results"]]
```
### 4. Dependency Resolution
Find skills with specific dependencies:
```python
result = query_registry(registry="skills", status="active")
for skill in result["details"]["results"]:
if "context.schema" in skill.get("dependencies", []):
print(f"{skill['name']} depends on context.schema")
```
### 5. Capability Search
Find agents by capability:
```bash
python3 skills/registry.query/registry_query.py agents --capability "API design"
```
### 6. Hooks Management
Query and monitor hooks:
```bash
# List all hooks in table format
python3 skills/registry.query/registry_query.py hooks --format table
# Find hooks by event type
python3 skills/registry.query/registry_query.py hooks --tag commit
# Find enabled hooks
python3 skills/registry.query/registry_query.py hooks --status active
```
### 7. Status Monitoring
Find deprecated or draft entries:
```bash
python3 skills/registry.query/registry_query.py skills --status deprecated
python3 skills/registry.query/registry_query.py skills --status draft
```
## Future Extensions
The skill is designed with these future enhancements in mind:
1. **Advanced Fuzzy Matching**: Implement more sophisticated fuzzy matching algorithms (e.g., Levenshtein distance)
2. **Full-Text Search**: Search within descriptions and documentation
3. **Dependency Graph**: Query dependency relationships between skills
4. **Version Ranges**: Support semantic version range queries (e.g., `>=1.0.0,<2.0.0`)
5. **Sorting Options**: Sort results by name, version, or relevance
6. **Regular Expression Support**: Use regex patterns for advanced filtering
7. **Marketplace Integration**: Query marketplace catalogs with certification status
8. **Performance Caching**: Cache registry data for faster repeated queries
## Examples
### Example 1: Find all API-related skills in table format
```bash
$ python3 skills/registry.query/registry_query.py skills --tag api --format table
================================================================================
REGISTRY QUERY: SKILLS
================================================================================
Total entries: 21
Matching entries: 5
+-------------------+---------+--------+---------------------------+-------------------------+
| Name | Version | Status | Tags | Commands |
+-------------------+---------+--------+---------------------------+-------------------------+
| api.define | 0.1.0 | active | api, openapi, asyncapi | /api/define |
| api.validate | 0.1.0 | active | api, validation, openapi | /api/validate |
| api.compatibility | 0.1.0 | draft | api, compatibility | /api/compatibility |
| api.generate | 0.1.0 | active | api, codegen | /api/generate |
| api.test | 0.1.0 | draft | api, testing | /api/test |
+-------------------+---------+--------+---------------------------+-------------------------+
================================================================================
```
### Example 2: Find all API-related skills in compact format
```bash
$ python3 skills/registry.query/registry_query.py skills --tag api
================================================================================
REGISTRY QUERY: SKILLS
================================================================================
Total entries: 21
Matching entries: 5
--------------------------------------------------------------------------------
RESULTS:
--------------------------------------------------------------------------------
1. api.define (v0.1.0)
Status: active
Description: Generates OpenAPI 3.1 or AsyncAPI 2.6 specifications from natural language...
Tags: api, openapi, asyncapi, scaffolding
Commands: /api/define
2. api.validate (v0.1.0)
Status: active
Description: Validates OpenAPI or AsyncAPI specifications against their respective schemas...
Tags: api, validation, openapi, asyncapi
Commands: /api/validate
...
```
### Example 3: Query hooks registry
```bash
$ python3 skills/registry.query/registry_query.py hooks --format table
================================================================================
REGISTRY QUERY: HOOKS
================================================================================
Total entries: 3
Matching entries: 3
+------------------+---------+--------+------------------+----------------------------------------+---------+
| Name | Version | Status | Event | Command | Enabled |
+------------------+---------+--------+------------------+----------------------------------------+---------+
| pre-commit-lint | 0.1.0 | active | on_commit | python3 hooks/lint.py | True |
| auto-test | 0.1.0 | active | on_file_save | pytest tests/ | True |
| telemetry-log | 0.1.0 | active | on_workflow_end | python3 hooks/telemetry.py --capture | True |
+------------------+---------+--------+------------------+----------------------------------------+---------+
================================================================================
```
### Example 4: Find agents that can design APIs
```bash
$ python3 skills/registry.query/registry_query.py agents --capability design
================================================================================
REGISTRY QUERY: AGENTS
================================================================================
Total entries: 2
Matching entries: 1
--------------------------------------------------------------------------------
RESULTS:
--------------------------------------------------------------------------------
1. api.designer (v0.1.0)
Status: draft
Description: Design RESTful APIs following best practices and guidelines...
Tags: api, design, openapi
Capabilities: 7 capabilities
Reasoning: iterative
```
### Example 5: Fuzzy search with limit
```bash
$ python3 skills/registry.query/registry_query.py skills --name vld --fuzzy --limit 3
# Finds: api.validate, context.validate, etc.
```
## Error Handling
The skill handles various error conditions:
- **Invalid registry type**: Returns error with valid options
- **Missing registry file**: Returns empty results with warning
- **Invalid JSON**: Returns error with details
- **Invalid filter combinations**: Logs warnings and proceeds with valid filters
## Performance Considerations
- Registry files are loaded once per query
- Filtering is performed in memory (O(n) complexity)
- For large registries, use `--limit` to control result size
- Consider caching registry data for repeated queries in production
## Dependencies
- **None**: This skill has no dependencies on other Betty skills
- **Python Standard Library**: Uses `json`, `re`, `pathlib`
- **Betty Framework**: Requires `betty.config`, `betty.logging_utils`, `betty.errors`
## Permissions
- **`filesystem:read`**: Required to read registry JSON files
## Contributing
To extend this skill:
1. Add new filter types in `filter_entries()`
2. Enhance fuzzy matching in `matches_pattern()`
3. Add new metadata extractors in `extract_key_metadata()`
4. Update tests and documentation
## See Also
- **skill.define**: Define new skills
- **agent.define**: Define new agents
- **plugin.sync**: Sync registry files
- **marketplace**: Betty marketplace catalog

View File

@@ -0,0 +1 @@
# Auto-generated package initializer for skills.

View File

@@ -0,0 +1,735 @@
#!/usr/bin/env python3
"""
registry_query.py - Implementation of the registry.query Skill
Search Betty registries programmatically by filtering skills, agents, and commands.
Supports filtering by tags, domain, status, name, version, and capability.
"""
import os
import sys
import json
import re
from typing import Dict, Any, List, Optional, Set
from datetime import datetime, timezone
from pathlib import Path
from betty.config import (
REGISTRY_FILE,
AGENTS_REGISTRY_FILE,
COMMANDS_REGISTRY_FILE,
HOOKS_REGISTRY_FILE,
BASE_DIR
)
from betty.logging_utils import setup_logger
from betty.errors import BettyError
logger = setup_logger(__name__)
def build_response(
ok: bool,
errors: Optional[List[str]] = None,
details: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Build standardized response.
Args:
ok: Whether the operation was successful
errors: List of error messages
details: Additional details to include
Returns:
Standardized response dictionary
"""
response: Dict[str, Any] = {
"ok": ok,
"status": "success" if ok else "failed",
"errors": errors or [],
"timestamp": datetime.now(timezone.utc).isoformat()
}
if details is not None:
response["details"] = details
return response
def load_registry(registry_type: str) -> Dict[str, Any]:
"""
Load a registry file.
Args:
registry_type: Type of registry ('skills', 'agents', 'commands')
Returns:
Registry data dictionary
Raises:
BettyError: If registry cannot be loaded
"""
registry_paths = {
'skills': REGISTRY_FILE,
'agents': AGENTS_REGISTRY_FILE,
'commands': COMMANDS_REGISTRY_FILE,
'hooks': HOOKS_REGISTRY_FILE
}
if registry_type not in registry_paths:
raise BettyError(
f"Invalid registry type: {registry_type}",
details={
"valid_types": list(registry_paths.keys()),
"provided": registry_type
}
)
registry_path = registry_paths[registry_type]
if not os.path.exists(registry_path):
logger.warning(f"Registry not found: {registry_path}")
return {
"version": "1.0.0",
"generated_at": datetime.now(timezone.utc).isoformat(),
registry_type: []
}
try:
with open(registry_path) as f:
data = json.load(f)
logger.debug(f"Loaded {registry_type} registry: {len(data.get(registry_type, []))} entries")
return data
except json.JSONDecodeError as e:
raise BettyError(f"Invalid JSON in {registry_type} registry: {e}")
except Exception as e:
raise BettyError(f"Failed to load {registry_type} registry: {e}")
def normalize_filter_value(value: Any) -> str:
"""
Normalize a filter value for case-insensitive comparison.
Args:
value: Value to normalize
Returns:
Normalized string value
"""
if value is None:
return ""
return str(value).lower().strip()
def matches_pattern(text: str, pattern: str, fuzzy: bool = False) -> bool:
"""
Check if text matches a pattern.
Args:
text: Text to match
pattern: Pattern to match against
fuzzy: Whether to use fuzzy matching
Returns:
True if text matches pattern
"""
text = normalize_filter_value(text)
pattern = normalize_filter_value(pattern)
if not pattern:
return True
if fuzzy:
# Fuzzy match: all characters of pattern appear in order in text
pattern_idx = 0
for char in text:
if pattern_idx < len(pattern) and char == pattern[pattern_idx]:
pattern_idx += 1
return pattern_idx == len(pattern)
else:
# Exact substring match
return pattern in text
def matches_tags(entry_tags: List[str], filter_tags: List[str]) -> bool:
"""
Check if entry tags match any of the filter tags.
Args:
entry_tags: Tags from the entry
filter_tags: Tags to filter by
Returns:
True if any filter tag is in entry tags
"""
if not filter_tags:
return True
if not entry_tags:
return False
entry_tags_normalized = [normalize_filter_value(tag) for tag in entry_tags]
filter_tags_normalized = [normalize_filter_value(tag) for tag in filter_tags]
return any(filter_tag in entry_tags_normalized for filter_tag in filter_tags_normalized)
def matches_capabilities(entry_capabilities: List[str], filter_capabilities: List[str], fuzzy: bool = False) -> bool:
"""
Check if entry capabilities match any of the filter capabilities.
Args:
entry_capabilities: Capabilities from the entry
filter_capabilities: Capabilities to filter by
fuzzy: Whether to use fuzzy matching
Returns:
True if any capability matches
"""
if not filter_capabilities:
return True
if not entry_capabilities:
return False
for filter_cap in filter_capabilities:
for entry_cap in entry_capabilities:
if matches_pattern(entry_cap, filter_cap, fuzzy):
return True
return False
def extract_key_metadata(entry: Dict[str, Any], registry_type: str) -> Dict[str, Any]:
"""
Extract key metadata from an entry based on registry type.
Args:
entry: Registry entry
registry_type: Type of registry
Returns:
Dictionary with key metadata
"""
metadata = {
"name": entry.get("name"),
"version": entry.get("version"),
"description": entry.get("description"),
"status": entry.get("status"),
"tags": entry.get("tags", [])
}
# Add registry-specific metadata
if registry_type == "skills":
# Handle inputs - can be strings or objects
inputs = entry.get("inputs", [])
formatted_inputs = []
for inp in inputs:
if isinstance(inp, str):
formatted_inputs.append({"name": inp, "type": "string", "required": False})
elif isinstance(inp, dict):
formatted_inputs.append({
"name": inp.get("name"),
"type": inp.get("type"),
"required": inp.get("required", False)
})
# Handle outputs - can be strings or objects
outputs = entry.get("outputs", [])
formatted_outputs = []
for out in outputs:
if isinstance(out, str):
formatted_outputs.append({"name": out, "type": "string"})
elif isinstance(out, dict):
formatted_outputs.append({
"name": out.get("name"),
"type": out.get("type")
})
metadata.update({
"dependencies": entry.get("dependencies", []),
"entrypoints": [
{
"command": ep.get("command"),
"runtime": ep.get("runtime"),
"description": ep.get("description")
}
for ep in entry.get("entrypoints", [])
],
"inputs": formatted_inputs,
"outputs": formatted_outputs
})
elif registry_type == "agents":
metadata.update({
"capabilities": entry.get("capabilities", []),
"skills_available": entry.get("skills_available", []),
"reasoning_mode": entry.get("reasoning_mode"),
"context_requirements": entry.get("context_requirements", {})
})
elif registry_type == "commands":
metadata.update({
"execution": entry.get("execution", {}),
"parameters": entry.get("parameters", [])
})
elif registry_type == "hooks":
metadata.update({
"event": entry.get("event"),
"command": entry.get("command"),
"enabled": entry.get("enabled", True)
})
return metadata
def filter_entries(
entries: List[Dict[str, Any]],
registry_type: str,
name: Optional[str] = None,
version: Optional[str] = None,
status: Optional[str] = None,
tags: Optional[List[str]] = None,
capability: Optional[str] = None,
domain: Optional[str] = None,
fuzzy: bool = False
) -> List[Dict[str, Any]]:
"""
Filter entries based on criteria.
Args:
entries: List of registry entries
registry_type: Type of registry
name: Filter by name (substring match)
version: Filter by version (exact match)
status: Filter by status (exact match)
tags: Filter by tags (any match)
capability: Filter by capability (for agents, substring match)
domain: Filter by domain/tag (alias for tags filter)
fuzzy: Use fuzzy matching for name and capability
Returns:
List of matching entries with key metadata
"""
results = []
# Convert domain to tags if provided
if domain:
tags = tags or []
if domain not in tags:
tags.append(domain)
logger.debug(f"Filtering {len(entries)} entries with criteria:")
logger.debug(f" name={name}, version={version}, status={status}")
logger.debug(f" tags={tags}, capability={capability}, fuzzy={fuzzy}")
for entry in entries:
# Filter by name
if name and not matches_pattern(entry.get("name", ""), name, fuzzy):
continue
# Filter by version (exact match)
if version and normalize_filter_value(entry.get("version")) != normalize_filter_value(version):
continue
# Filter by status (exact match)
if status and normalize_filter_value(entry.get("status")) != normalize_filter_value(status):
continue
# Filter by tags
if tags and not matches_tags(entry.get("tags", []), tags):
continue
# Filter by capability (agents only)
if capability:
if registry_type == "agents":
capabilities = entry.get("capabilities", [])
if not matches_capabilities(capabilities, [capability], fuzzy):
continue
else:
# For non-agents, skip capability filter
logger.debug(f"Capability filter only applies to agents, skipping for {registry_type}")
# Entry matches all criteria
metadata = extract_key_metadata(entry, registry_type)
results.append(metadata)
logger.info(f"Found {len(results)} matching entries")
return results
def query_registry(
registry: str,
name: Optional[str] = None,
version: Optional[str] = None,
status: Optional[str] = None,
tag: Optional[str] = None,
tags: Optional[List[str]] = None,
capability: Optional[str] = None,
domain: Optional[str] = None,
fuzzy: bool = False,
limit: Optional[int] = None
) -> Dict[str, Any]:
"""
Query a Betty registry with filters.
Args:
registry: Registry to query ('skills', 'agents', 'commands')
name: Filter by name
version: Filter by version
status: Filter by status
tag: Single tag filter (convenience parameter)
tags: List of tags to filter by
capability: Filter by capability (agents only)
domain: Domain/tag filter (alias for tags)
fuzzy: Use fuzzy matching
limit: Maximum number of results to return
Returns:
Query result with matching entries
"""
logger.info(f"Querying {registry} registry")
# Normalize registry type
registry = registry.lower()
if registry not in ['skills', 'agents', 'commands', 'hooks']:
raise BettyError(
f"Invalid registry: {registry}",
details={
"valid_registries": ["skills", "agents", "commands", "hooks"],
"provided": registry
}
)
# Load registry
registry_data = load_registry(registry)
entries = registry_data.get(registry, [])
# Merge tag and tags parameters
if tag:
tags = tags or []
if tag not in tags:
tags.append(tag)
# Filter entries
results = filter_entries(
entries,
registry,
name=name,
version=version,
status=status,
tags=tags,
capability=capability,
domain=domain,
fuzzy=fuzzy
)
# Apply limit
if limit and limit > 0:
results = results[:limit]
# Build response
return build_response(
ok=True,
details={
"registry": registry,
"query": {
"name": name,
"version": version,
"status": status,
"tags": tags,
"capability": capability,
"domain": domain,
"fuzzy": fuzzy,
"limit": limit
},
"total_entries": len(entries),
"matching_entries": len(results),
"results": results
}
)
def format_table(results: List[Dict[str, Any]], registry_type: str) -> str:
"""
Format results as an aligned table.
Args:
results: List of matching entries
registry_type: Type of registry
Returns:
Formatted table string
"""
if not results:
return "No matching entries found."
# Define columns based on registry type
if registry_type == "skills":
columns = ["Name", "Version", "Status", "Tags", "Commands"]
elif registry_type == "agents":
columns = ["Name", "Version", "Status", "Tags", "Reasoning", "Skills"]
elif registry_type == "commands":
columns = ["Name", "Version", "Status", "Tags", "Execution Type"]
elif registry_type == "hooks":
columns = ["Name", "Version", "Status", "Event", "Command", "Enabled"]
else:
columns = ["Name", "Version", "Status", "Description"]
# Extract data for each column
rows = []
for entry in results:
if registry_type == "skills":
commands = [ep.get('command', '') for ep in entry.get('entrypoints', [])]
row = [
entry.get('name', ''),
entry.get('version', ''),
entry.get('status', ''),
', '.join(entry.get('tags', [])[:3]), # Limit to 3 tags
', '.join(commands[:2]) # Limit to 2 commands
]
elif registry_type == "agents":
row = [
entry.get('name', ''),
entry.get('version', ''),
entry.get('status', ''),
', '.join(entry.get('tags', [])[:3]),
entry.get('reasoning_mode', ''),
str(len(entry.get('skills_available', [])))
]
elif registry_type == "commands":
exec_type = entry.get('execution', {}).get('type', '')
row = [
entry.get('name', ''),
entry.get('version', ''),
entry.get('status', ''),
', '.join(entry.get('tags', [])[:3]),
exec_type
]
elif registry_type == "hooks":
row = [
entry.get('name', ''),
entry.get('version', ''),
entry.get('status', ''),
entry.get('event', ''),
entry.get('command', '')[:40], # Truncate long commands
str(entry.get('enabled', True))
]
else:
row = [
entry.get('name', ''),
entry.get('version', ''),
entry.get('status', ''),
entry.get('description', '')[:50]
]
rows.append(row)
# Calculate column widths
col_widths = [len(col) for col in columns]
for row in rows:
for i, cell in enumerate(row):
col_widths[i] = max(col_widths[i], len(str(cell)))
# Build table
lines = []
separator = "+" + "+".join("-" * (w + 2) for w in col_widths) + "+"
# Header
lines.append(separator)
header = "|" + "|".join(f" {col:<{col_widths[i]}} " for i, col in enumerate(columns)) + "|"
lines.append(header)
lines.append(separator)
# Rows
for row in rows:
row_str = "|" + "|".join(f" {str(cell):<{col_widths[i]}} " for i, cell in enumerate(row)) + "|"
lines.append(row_str)
lines.append(separator)
return "\n".join(lines)
def main():
"""Main CLI entry point."""
import argparse
parser = argparse.ArgumentParser(
description="Query Betty registries programmatically",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# List all skills (compact format)
registry_query.py skills
# Find skills with 'api' tag in table format
registry_query.py skills --tag api --format table
# Find agents with 'design' capability
registry_query.py agents --capability design
# Find active skills with name containing 'validate'
registry_query.py skills --name validate --status active
# Query hooks registry
registry_query.py hooks --status active --format table
# Fuzzy search for commands
registry_query.py commands --name test --fuzzy
# Limit results with JSON output
registry_query.py skills --tag api --limit 5 --format json
"""
)
parser.add_argument(
"registry",
choices=["skills", "agents", "commands", "hooks"],
help="Registry to query"
)
parser.add_argument(
"--name",
help="Filter by name (substring match)"
)
parser.add_argument(
"--version",
help="Filter by version (exact match)"
)
parser.add_argument(
"--status",
help="Filter by status (e.g., active, draft, deprecated)"
)
parser.add_argument(
"--tag",
help="Filter by single tag"
)
parser.add_argument(
"--tags",
nargs="+",
help="Filter by multiple tags (any match)"
)
parser.add_argument(
"--capability",
help="Filter by capability (agents only)"
)
parser.add_argument(
"--domain",
help="Filter by domain (alias for tag)"
)
parser.add_argument(
"--fuzzy",
action="store_true",
help="Use fuzzy matching for name and capability"
)
parser.add_argument(
"--limit",
type=int,
help="Maximum number of results to return"
)
parser.add_argument(
"--format",
choices=["json", "table", "compact"],
default="compact",
help="Output format (default: compact)"
)
args = parser.parse_args()
try:
result = query_registry(
registry=args.registry,
name=args.name,
version=args.version,
status=args.status,
tag=args.tag,
tags=args.tags,
capability=args.capability,
domain=args.domain,
fuzzy=args.fuzzy,
limit=args.limit
)
details = result["details"]
if args.format == "json":
# Output full JSON
print(json.dumps(result, indent=2))
elif args.format == "table":
# Table format
print(f"\n{'='*80}")
print(f"REGISTRY QUERY: {details['registry'].upper()}")
print(f"{'='*80}")
print(f"\nTotal entries: {details['total_entries']}")
print(f"Matching entries: {details['matching_entries']}\n")
if details['results']:
print(format_table(details['results'], details['registry']))
else:
print("No matching entries found.")
print(f"\n{'='*80}\n")
else:
# Compact format (original pretty print)
print(f"\n{'='*80}")
print(f"REGISTRY QUERY: {details['registry'].upper()}")
print(f"{'='*80}")
print(f"\nTotal entries: {details['total_entries']}")
print(f"Matching entries: {details['matching_entries']}")
if details['results']:
print(f"\n{'-'*80}")
print("RESULTS:")
print(f"{'-'*80}\n")
for i, entry in enumerate(details['results'], 1):
print(f"{i}. {entry['name']} (v{entry['version']})")
print(f" Status: {entry['status']}")
print(f" Description: {entry['description'][:80]}...")
if entry.get('tags'):
print(f" Tags: {', '.join(entry['tags'])}")
# Registry-specific details
if details['registry'] == 'skills':
if entry.get('entrypoints'):
commands = [ep['command'] for ep in entry['entrypoints']]
print(f" Commands: {', '.join(commands)}")
elif details['registry'] == 'agents':
if entry.get('capabilities'):
print(f" Capabilities: {len(entry['capabilities'])} capabilities")
print(f" Reasoning: {entry.get('reasoning_mode', 'unknown')}")
elif details['registry'] == 'hooks':
print(f" Event: {entry.get('event', 'unknown')}")
print(f" Command: {entry.get('command', 'unknown')}")
print(f" Enabled: {entry.get('enabled', True)}")
print()
print(f"{'-'*80}")
else:
print("\nNo matching entries found.")
print(f"\n{'='*80}\n")
sys.exit(0 if result['ok'] else 1)
except BettyError as e:
logger.error(f"Query failed: {e}")
result = build_response(
ok=False,
errors=[str(e)]
)
print(json.dumps(result, indent=2))
sys.exit(1)
except Exception as e:
logger.error(f"Unexpected error: {e}", exc_info=True)
result = build_response(
ok=False,
errors=[f"Unexpected error: {str(e)}"]
)
print(json.dumps(result, indent=2))
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,131 @@
name: registry.query
version: 0.1.0
description: >
Search Betty registries programmatically by filtering skills, agents, commands, and hooks.
Supports filtering by tags, domain, status, name, version, and capability with optional
fuzzy matching for dynamic discovery and CLI autocompletion. Includes table formatting
for easy viewing in CLI.
inputs:
- name: registry
type: string
required: true
description: Registry to query (skills, agents, commands, or hooks)
- name: name
type: string
required: false
description: Filter by name (substring match, supports fuzzy matching)
- name: version
type: string
required: false
description: Filter by version (exact match)
- name: status
type: string
required: false
description: Filter by status (e.g., active, draft, deprecated, archived)
- name: tag
type: string
required: false
description: Filter by single tag
- name: tags
type: array
required: false
description: Filter by multiple tags (any match)
- name: capability
type: string
required: false
description: Filter by capability (agents only, substring match)
- name: domain
type: string
required: false
description: Filter by domain (alias for tag filtering)
- name: fuzzy
type: boolean
required: false
description: Enable fuzzy matching for name and capability filters
- name: limit
type: integer
required: false
description: Maximum number of results to return
- name: format
type: string
required: false
description: Output format (json, table, compact)
outputs:
- name: results
type: array
description: List of matching registry entries with key metadata
- name: query_metadata
type: object
description: Query statistics including total entries and matching count
- name: registry_info
type: object
description: Information about the queried registry
dependencies: []
status: active
entrypoints:
- command: /registry/query
handler: registry_query.py
runtime: python
description: >
Query Betty registries with flexible filtering options. Returns matching entries
with key metadata for programmatic use or CLI exploration.
parameters:
- name: registry
type: string
required: true
description: Registry to query (skills, agents, commands, or hooks)
- name: name
type: string
required: false
description: Filter by name (substring match)
- name: version
type: string
required: false
description: Filter by version (exact match)
- name: status
type: string
required: false
description: Filter by status
- name: tag
type: string
required: false
description: Filter by single tag
- name: tags
type: array
required: false
description: Filter by multiple tags
- name: capability
type: string
required: false
description: Filter by capability (agents only)
- name: domain
type: string
required: false
description: Filter by domain
- name: fuzzy
type: boolean
required: false
description: Enable fuzzy matching
- name: limit
type: integer
required: false
description: Maximum results to return
- name: format
type: string
required: false
description: Output format (json, table, compact)
permissions:
- filesystem:read
tags:
- registry
- search
- query
- discovery
- metadata
- cli