Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 27 additions & 53 deletions .github/workflows/build_registry.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Build aggregated registry.json from individual agent and extension directories."""
"""Build aggregated registry.json from individual agent directories."""

import json
import os
Expand Down Expand Up @@ -268,11 +268,9 @@ def validate_icon_monochrome(content: str) -> list[str]:
reported_colors.add(style_stroke.group(1).strip())

# Check that currentColor is actually used (icons without fill default to black)
has_current_color = bool(re.search(r'currentColor', content, re.IGNORECASE))
has_current_color = bool(re.search(r"currentColor", content, re.IGNORECASE))
if not has_current_color:
errors.append(
'Icon must use currentColor for fills/strokes to support theming'
)
errors.append("Icon must use currentColor for fills/strokes to support theming")

# Deduplicate errors
return list(dict.fromkeys(errors))
Expand Down Expand Up @@ -475,7 +473,7 @@ def process_entry(
base_url: str,
seen_ids: dict,
) -> tuple[dict | None, list[str]]:
"""Process a single agent or extension entry. Returns (entry, errors)."""
"""Process a single registry entry. Returns (entry, errors)."""
entry_path = entry_dir / entry_file

# Parse JSON with error handling
Expand All @@ -485,7 +483,7 @@ def process_entry(
except json.JSONDecodeError as e:
return None, [f"{entry_dir.name}/{entry_file} is invalid JSON: {e}"]

# Validate entry (uses same schema for both agents and extensions)
# Validate entry
validation_errors = validate_agent(entry, entry_dir.name, schema)
if validation_errors:
return None, [f"{entry_dir.name}/{entry_file} validation failed:"] + [
Expand All @@ -502,7 +500,7 @@ def process_entry(
f" - {e}" for e in version_errors
]

# Check for duplicate IDs (across both agents and extensions)
# Check for duplicate IDs
entry_id = entry["id"]
if entry_id in seen_ids:
return None, [
Expand Down Expand Up @@ -532,15 +530,14 @@ def process_entry(


def build_registry():
"""Build registry.json from agent and extension directories."""
"""Build registry.json from agent directories."""
registry_dir = Path(__file__).parent.parent.parent
base_url = get_base_url()
agents = []
extensions = []
seen_ids = {}
has_errors = False

# Load schema for validation (used for both agents and extensions)
# Load schema for validation
schema = load_schema(registry_dir)
if schema and not HAS_JSONSCHEMA:
print("Warning: jsonschema not installed, skipping schema validation")
Expand All @@ -551,56 +548,32 @@ def build_registry():
continue

agent_json_path = entry_dir / "agent.json"
extension_json_path = entry_dir / "extension.json"

has_agent = agent_json_path.exists()
has_extension = extension_json_path.exists()

if has_agent and has_extension:
print(f"Error: {entry_dir.name}/ has both agent.json and extension.json")
has_errors = True
if not agent_json_path.exists():
print(f"Warning: {entry_dir.name}/ has no agent.json, skipping")
continue

if not has_agent and not has_extension:
print(
f"Warning: {entry_dir.name}/ has no agent.json or extension.json, skipping"
)
entry, errors = process_entry(
entry_dir, "agent.json", "agent", schema, base_url, seen_ids
)
if errors:
for error in errors:
print(f"Error: {error}")
has_errors = True
continue

if has_agent:
entry, errors = process_entry(
entry_dir, "agent.json", "agent", schema, base_url, seen_ids
)
if errors:
for error in errors:
print(f"Error: {error}")
has_errors = True
continue
agents.append(entry)
print(f"Added agent: {entry['id']} v{entry['version']}")
else:
entry, errors = process_entry(
entry_dir, "extension.json", "extension", schema, base_url, seen_ids
)
if errors:
for error in errors:
print(f"Error: {error}")
has_errors = True
continue
extensions.append(entry)
print(f"Added extension: {entry['id']} v{entry['version']}")
agents.append(entry)
print(f"Added agent: {entry['id']} v{entry['version']}")

if has_errors:
print("\nBuild failed due to validation errors")
sys.exit(1)

if not agents and not extensions:
print("\nWarning: No agents or extensions found")
if not agents:
print("\nWarning: No agents found")

registry = {
"version": REGISTRY_VERSION,
"agents": agents,
"extensions": extensions,
}

# Create dist directory
Expand All @@ -618,15 +591,14 @@ def build_registry():
jetbrains_registry = {
"version": REGISTRY_VERSION,
"agents": [a for a in agents if a["id"] not in JETBRAINS_EXCLUDE_IDS],
"extensions": extensions,
}
jetbrains_output_path = dist_dir / "registry-for-jetbrains.json"
with open(jetbrains_output_path, "w") as f:
json.dump(jetbrains_registry, f, indent=2)
f.write("\n")

# Copy icons to dist (for both agents and extensions)
for entry in agents + extensions:
# Copy icons to dist
for entry in agents:
entry_id = entry["id"]
icon_src = registry_dir / entry_id / "icon.svg"
if icon_src.exists():
Expand All @@ -641,9 +613,11 @@ def build_registry():
schema_dst.write_bytes(schema_src.read_bytes())

jetbrains_agent_count = len(jetbrains_registry["agents"])
print(f"\nBuilt dist/ with {len(agents)} agents and {len(extensions)} extensions")
print(f"\nBuilt dist/ with {len(agents)} agents")
print(f" registry.json: {len(agents)} agents")
print(f" registry-for-jetbrains.json: {jetbrains_agent_count} agents (excluded: {', '.join(JETBRAINS_EXCLUDE_IDS)})")
print(
f" registry-for-jetbrains.json: {jetbrains_agent_count} agents (excluded: {', '.join(JETBRAINS_EXCLUDE_IDS)})"
)


if __name__ == "__main__":
Expand Down
17 changes: 11 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,24 @@ python .github/workflows/build_registry.py

## Architecture

This is a registry of ACP (Agent Client Protocol) agents and extensions. The structure is:
This is a registry of ACP (Agent Client Protocol) agents. The structure is:

```
<id>/
├── agent.json # Agent metadata and distribution info
├── extension.json # OR extension metadata (same schema as agent.json)
└── icon.svg # Icon: 16x16 SVG, monochrome with currentColor (optional)
```

Each directory contains either `agent.json` (for agents) or `extension.json` (for extensions), but not both. Extensions use the same schema as agents (`agent.schema.json`).

**Build process** (`.github/workflows/build_registry.py`):
1. Scans directories for `agent.json` or `extension.json` files

1. Scans directories for `agent.json` files
2. Validates against `agent.schema.json` (JSON Schema)
3. Validates icons (16x16 SVG, monochrome with `currentColor`)
4. Aggregates into `dist/registry.json` with separate `agents` and `extensions` arrays
4. Aggregates into `dist/registry.json`
5. Copies icons to `dist/<id>.svg`

**CI/CD** (`.github/workflows/build-registry.yml`):

- PRs: Runs validation only
- Push to main: Validates, then publishes versioned + `latest` GitHub releases

Expand All @@ -53,6 +52,7 @@ Set `SKIP_URL_VALIDATION=1` to bypass URL checks during local development.
### Automated Updates

Agent versions are automatically updated via `.github/workflows/update-versions.yml`:

- **Schedule:** Runs hourly (cron: `0 * * * *`)
- **Scope:** Checks all agents in root and `_not_yet_unsupported/`
- **Supported distributions:** `npx` (npm), `uvx` (PyPI), `binary` (GitHub releases)
Expand All @@ -78,6 +78,7 @@ To update agents manually:
2. **For GitHub binaries** (`binary` distribution): Check latest release at `https://api.github.com/repos/<owner>/<repo>/releases/latest`

Update `agent.json`:

- Update the `version` field
- Update version in all distribution URLs (use replace-all for consistency)
- For npm: update `package` field (e.g., `@google/gemini-cli@0.22.5`)
Expand All @@ -96,20 +97,23 @@ Run build to validate: `uv run --with jsonschema .github/workflows/build_registr
## Icon Requirements

Icons must be:

- **SVG format** (only `.svg` files accepted)
- **16x16 dimensions** (via width/height attributes or viewBox)
- **Monochrome using `currentColor`** - all fills and strokes must use `currentColor` or `none`

Using `currentColor` enables icons to adapt to different themes (light/dark mode) automatically.

**Valid example:**

```svg
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="currentColor" d="M..."/>
</svg>
```

**Invalid patterns:**

- Hardcoded colors: `fill="#FF5500"`, `fill="red"`, `stroke="rgb(0,0,0)"`
- Missing currentColor: `fill` or `stroke` without `currentColor`

Expand All @@ -118,6 +122,7 @@ Using `currentColor` enables icons to adapt to different themes (light/dark mode
Agents must support ACP authentication. The CI verifies auth via `.github/workflows/verify_agents.py --auth-check`.

**Requirements:**

- Return `authMethods` array in `initialize` response
- At least one method must have type `"agent"` or `"terminal"`

Expand Down
42 changes: 23 additions & 19 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Contributing to the ACP Registry

## Adding a New Agent or Extension
## Adding a New Agent

1. **Fork this repository**

Expand All @@ -12,9 +12,7 @@

The directory name must match your entry's `id` field.

3. **Create `agent.json` or `extension.json`**

Use `agent.json` for agents, `extension.json` for extensions. Both use the same schema.
3. **Create `agent.json`**

```json
{
Expand All @@ -40,6 +38,7 @@
- **Monochrome using `currentColor`** - enables theme support (light/dark mode)

Example:

```svg
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="currentColor" d="M..."/>
Expand Down Expand Up @@ -126,21 +125,21 @@ Supported platforms: `darwin-aarch64`, `darwin-x86_64`, `linux-aarch64`, `linux-

## Required Fields

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Unique identifier (lowercase, hyphens allowed) |
| `name` | string | Display name |
| `version` | string | Semantic version |
| `description` | string | Brief description |
| `distribution` | object | At least one distribution method |
| Field | Type | Description |
| -------------- | ------ | ---------------------------------------------- |
| `id` | string | Unique identifier (lowercase, hyphens allowed) |
| `name` | string | Display name |
| `version` | string | Semantic version |
| `description` | string | Brief description |
| `distribution` | object | At least one distribution method |

## Optional Fields

| Field | Type | Description |
|-------|------|-------------|
| `repository` | string | Source code URL |
| `authors` | array | List of author names/emails |
| `license` | string | SPDX license identifier |
| Field | Type | Description |
| ------------ | ------ | --------------------------- |
| `repository` | string | Source code URL |
| `authors` | array | List of author names/emails |
| `license` | string | SPDX license identifier |

## Automatic Version Updates

Expand All @@ -154,9 +153,9 @@ You don't need to submit a PR for version bumps.

## Manual Updates

To manually update your agent or extension (e.g., changing description, adding platforms):
To manually update your agent (e.g., changing description, adding platforms):

1. Fork and update the `agent.json` or `extension.json` file
1. Fork and update the `agent.json` file
2. Submit a Pull Request
3. CI will validate and merge will trigger a new registry release

Expand All @@ -173,7 +172,7 @@ Entries are validated against the [JSON Schema](agent.schema.json).
- Must be lowercase letters, digits, and hyphens only
- Must start with a letter
- Must match the directory name
- Must be unique across all agents and extensions
- Must be unique across all agents

### Version Validation

Expand All @@ -183,11 +182,13 @@ Entries are validated against the [JSON Schema](agent.schema.json).
### Distribution Validation

**Structure:**

- At least one distribution method required (`binary`, `npx`, or `uvx`)
- Binary distributions require `archive` and `cmd` fields per platform
- Package distributions (`npx`, `uvx`) require `package` field

**Platforms** (for binary):

- `darwin-aarch64`, `darwin-x86_64`
- `linux-aarch64`, `linux-x86_64`
- `windows-aarch64`, `windows-x86_64`
Expand All @@ -201,12 +202,14 @@ Entries are validated against the [JSON Schema](agent.schema.json).
- Missing OS families will produce a warning but will not fail validation

**Version matching:**

- Distribution versions must match the entry's `version` field
- Binary URLs containing version (e.g., `/download/v1.0.0/`) are checked
- npm package versions (`@scope/pkg@1.0.0`) are checked
- PyPI package versions (`pkg==1.0.0` or `pkg@1.0.0`) are checked

**No `latest` allowed:**

- Binary URLs must not contain `/latest/`
- npm packages must not use `@latest`
- PyPI packages must not use `@latest`
Expand Down Expand Up @@ -239,6 +242,7 @@ python3 .github/workflows/verify_agents.py --auth-check
```

**What gets checked:**

- Agent must return `authMethods` in the `initialize` response
- At least one method must have `type: "agent"` or `type: "terminal"`
- See [AUTHENTICATION.md](AUTHENTICATION.md) for implementation details
Expand Down
9 changes: 4 additions & 5 deletions FORMAT.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
# Registry Format

The registry contains both agents and extensions:
The registry contains agents:

```json
{
"version": "1.0.0",
"agents": [...],
"extensions": [...]
"agents": [...]
}
```

Each entry (agent or extension) has the same structure:
Each agent has the following structure:

```json
{
Expand Down Expand Up @@ -46,7 +45,7 @@ Each entry (agent or extension) has the same structure:
## Distribution Types

| Type | Description | Command |
|----------|-------------------------------|------------------------|
| -------- | ----------------------------- | ---------------------- |
| `binary` | Platform-specific executables | Download, extract, run |
| `npx` | npm packages | `npx <package> [args]` |
| `uvx` | PyPI packages via uv | `uvx <package> [args]` |
Expand Down
Loading
Loading