agent-catalog-new 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_catalog_new-0.2.0/LICENSE +21 -0
- agent_catalog_new-0.2.0/PKG-INFO +201 -0
- agent_catalog_new-0.2.0/README.md +173 -0
- agent_catalog_new-0.2.0/agent_catalog/__init__.py +33 -0
- agent_catalog_new-0.2.0/agent_catalog/cli/__init__.py +108 -0
- agent_catalog_new-0.2.0/agent_catalog/cli/__main__.py +3 -0
- agent_catalog_new-0.2.0/agent_catalog/cli/aux_commands.py +139 -0
- agent_catalog_new-0.2.0/agent_catalog/cli/crud.py +192 -0
- agent_catalog_new-0.2.0/agent_catalog/cli/diff_commands.py +165 -0
- agent_catalog_new-0.2.0/agent_catalog/cli/discover_commands.py +190 -0
- agent_catalog_new-0.2.0/agent_catalog/client.py +340 -0
- agent_catalog_new-0.2.0/agent_catalog/config.py +120 -0
- agent_catalog_new-0.2.0/agent_catalog/decorators.py +549 -0
- agent_catalog_new-0.2.0/agent_catalog/diff.py +156 -0
- agent_catalog_new-0.2.0/agent_catalog/discovery.py +177 -0
- agent_catalog_new-0.2.0/agent_catalog/graph.py +87 -0
- agent_catalog_new-0.2.0/agent_catalog/loader.py +161 -0
- agent_catalog_new-0.2.0/agent_catalog/mcp_server.py +279 -0
- agent_catalog_new-0.2.0/agent_catalog/py.typed +0 -0
- agent_catalog_new-0.2.0/agent_catalog/schema.py +252 -0
- agent_catalog_new-0.2.0/agent_catalog/security.py +101 -0
- agent_catalog_new-0.2.0/agent_catalog/serve.py +397 -0
- agent_catalog_new-0.2.0/agent_catalog/storage.py +280 -0
- agent_catalog_new-0.2.0/agent_catalog_new.egg-info/PKG-INFO +201 -0
- agent_catalog_new-0.2.0/agent_catalog_new.egg-info/SOURCES.txt +41 -0
- agent_catalog_new-0.2.0/agent_catalog_new.egg-info/dependency_links.txt +1 -0
- agent_catalog_new-0.2.0/agent_catalog_new.egg-info/entry_points.txt +2 -0
- agent_catalog_new-0.2.0/agent_catalog_new.egg-info/requires.txt +10 -0
- agent_catalog_new-0.2.0/agent_catalog_new.egg-info/top_level.txt +1 -0
- agent_catalog_new-0.2.0/pyproject.toml +83 -0
- agent_catalog_new-0.2.0/setup.cfg +4 -0
- agent_catalog_new-0.2.0/tests/test_cli.py +500 -0
- agent_catalog_new-0.2.0/tests/test_client.py +30 -0
- agent_catalog_new-0.2.0/tests/test_coverage_gaps.py +456 -0
- agent_catalog_new-0.2.0/tests/test_decorators.py +404 -0
- agent_catalog_new-0.2.0/tests/test_diff.py +69 -0
- agent_catalog_new-0.2.0/tests/test_discovery.py +205 -0
- agent_catalog_new-0.2.0/tests/test_features.py +186 -0
- agent_catalog_new-0.2.0/tests/test_loader.py +216 -0
- agent_catalog_new-0.2.0/tests/test_mcp_server.py +143 -0
- agent_catalog_new-0.2.0/tests/test_schema.py +102 -0
- agent_catalog_new-0.2.0/tests/test_schema_full.py +521 -0
- agent_catalog_new-0.2.0/tests/test_storage.py +85 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ervin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-catalog-new
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Declarative agent registry — catalog, discover, and diff agent capabilities across environments
|
|
5
|
+
Author-email: Ervin <ervin@nexusgate.tech>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: agents,catalog,registry,ai-agents,agent-management,mcp
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest<10,>=9.0; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-cov<8,>=7.0; extra == "dev"
|
|
22
|
+
Requires-Dist: ruff<1,>=0.15; extra == "dev"
|
|
23
|
+
Requires-Dist: coverage[toml]<8,>=7.6; extra == "dev"
|
|
24
|
+
Requires-Dist: mypy>=1.15; extra == "dev"
|
|
25
|
+
Provides-Extra: mcp
|
|
26
|
+
Requires-Dist: mcp<2,>=1.0; extra == "mcp"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# Agent Catalog
|
|
30
|
+
|
|
31
|
+
[](https://github.com/ewardGPT/agent-catalog/actions/workflows/ci.yml)
|
|
32
|
+
[](https://pypi.org/project/agent-catalog/)
|
|
33
|
+
[](https://pypi.org/project/agent-catalog/)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
|
|
36
|
+
A registry for AI agents. Track every agent you own — what it does, what tools it uses, what model it runs, where it's deployed. YAML manifests stored on disk. No database. Git-ops friendly.
|
|
37
|
+
|
|
38
|
+
Think `npm` for agents. One tool to list, search, diff, and invoke them all.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install agent-catalog
|
|
42
|
+
agent-catalog register ./my-agent.yaml
|
|
43
|
+
agent-catalog list
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Why
|
|
47
|
+
|
|
48
|
+
You have agents scattered across repos, environments, and projects. Prod vs staging drift goes unnoticed. A dependency changes and nothing tells you. You grep through directories to find which agent has the capability you need.
|
|
49
|
+
|
|
50
|
+
Agent Catalog fixes that. One directory is the source of truth. Every command queries it.
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
- **Registry.** Register agents from YAML manifests. List, search, get details. Filter by environment, capability, tool, surface.
|
|
55
|
+
- **Diff.** Compare manifests across environments (`--left-env staging --right-env production`). Catch drift before it hits prod.
|
|
56
|
+
- **Consistency.** `agent-catalog doctor` finds orphaned files and missing manifests.
|
|
57
|
+
- **Security audit.** Flags MCP endpoints without auth, write capabilities without confirmation gates, idempotent tools that should not be.
|
|
58
|
+
- **MCP server.** Expose the catalog as MCP tools — any MCP client can discover agents and invoke their capabilities.
|
|
59
|
+
- **HTTP dashboard.** `agent-catalog serve` starts a web UI on `:8420`.
|
|
60
|
+
- **Python SDK.** `from agent_catalog.client import CatalogClient` — sync or async.
|
|
61
|
+
- **Decorator API.** Define agents inline with `@agent`, `@capability`, `@tool`. Manifests auto-generated from Python classes.
|
|
62
|
+
- **Runtime loader.** Load an agent class from the catalog and invoke a capability from the command line or API.
|
|
63
|
+
|
|
64
|
+
## Quick start
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install agent-catalog
|
|
68
|
+
|
|
69
|
+
# Register an agent
|
|
70
|
+
agent-catalog register ./examples/agentic-inbox.yaml
|
|
71
|
+
|
|
72
|
+
# See everything you have
|
|
73
|
+
agent-catalog list
|
|
74
|
+
|
|
75
|
+
# Get details
|
|
76
|
+
agent-catalog get agentic-inbox
|
|
77
|
+
|
|
78
|
+
# Search
|
|
79
|
+
agent-catalog search --capability send_email
|
|
80
|
+
|
|
81
|
+
# Validate a manifest without registering
|
|
82
|
+
agent-catalog validate ./my-agent.yaml
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Walkthrough: managing agents across environments
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Register your staging agent
|
|
89
|
+
agent-catalog register ./staging/agent.yaml --env staging
|
|
90
|
+
|
|
91
|
+
# Later, register production
|
|
92
|
+
agent-catalog register ./prod/agent.yaml
|
|
93
|
+
|
|
94
|
+
# See the drift
|
|
95
|
+
agent-catalog diff agentic-inbox --left-env staging --right-env production
|
|
96
|
+
|
|
97
|
+
# Validate your changes
|
|
98
|
+
agent-catalog validate ./prod/agent.yaml
|
|
99
|
+
|
|
100
|
+
# Check everything is consistent
|
|
101
|
+
agent-catalog doctor
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Walkthrough: exposing agents via MCP
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Start the MCP server
|
|
108
|
+
agent-catalog serve --mcp
|
|
109
|
+
|
|
110
|
+
# Any MCP client can now call:
|
|
111
|
+
# - catalog_list_agents
|
|
112
|
+
# - catalog_get_agent
|
|
113
|
+
# - catalog_search
|
|
114
|
+
# - catalog_invoke
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Walkthrough: Python SDK
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from agent_catalog.client import CatalogClient
|
|
121
|
+
|
|
122
|
+
client = CatalogClient()
|
|
123
|
+
|
|
124
|
+
# List all production agents
|
|
125
|
+
for agent in client.agents.list(env="production"):
|
|
126
|
+
print(agent.slug, agent.capability_ids())
|
|
127
|
+
|
|
128
|
+
# Async
|
|
129
|
+
from agent_catalog.client import AsyncCatalogClient
|
|
130
|
+
client = AsyncCatalogClient()
|
|
131
|
+
agents = await client.agents.list()
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Walkthrough: decorator API
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from agent_catalog import agent, capability, tool, build_manifest
|
|
138
|
+
|
|
139
|
+
@agent(name="My Agent", version="1.0.0", environment="production")
|
|
140
|
+
class MyAgent:
|
|
141
|
+
@capability(id="greet", description="Greets the user")
|
|
142
|
+
@tool(name="greet", description="Say hello")
|
|
143
|
+
def greet(self, name: str) -> str:
|
|
144
|
+
return f"Hello, {name}!"
|
|
145
|
+
|
|
146
|
+
manifest = build_manifest(MyAgent)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Commands
|
|
150
|
+
|
|
151
|
+
| Command | Action |
|
|
152
|
+
|---------|--------|
|
|
153
|
+
| `register <path>` | Register agent from YAML |
|
|
154
|
+
| `list [--env] [--output json]` | List agents |
|
|
155
|
+
| `get <slug> [--output json]` | Show agent details |
|
|
156
|
+
| `search [--capability] [--tool] [--surface] [--env]` | Find agents |
|
|
157
|
+
| `diff <slug> [--right] [--slug2] [--right-env]` | Compare manifests |
|
|
158
|
+
| `validate <path>` | Validate YAML |
|
|
159
|
+
| `unregister <slug>` | Remove agent |
|
|
160
|
+
| `update <slug> <path>` | Update manifest |
|
|
161
|
+
| `sync <dir> [--pattern]` | Bulk register YAML files |
|
|
162
|
+
| `scan <dir>` | Discover @agent-decorated Python classes |
|
|
163
|
+
| `inspect <file>` | Show manifest from Python file |
|
|
164
|
+
| `export-contract <slug>` | Export eval contract |
|
|
165
|
+
| `security-audit [--format json]` | Security audit |
|
|
166
|
+
| `graph [--format json]` | Dependency graph |
|
|
167
|
+
| `serve [--port] [--mcp]` | HTTP dashboard or MCP server |
|
|
168
|
+
| `run <slug> <capability>` | Invoke a capability |
|
|
169
|
+
| `doctor` | Consistency check |
|
|
170
|
+
|
|
171
|
+
## Storage
|
|
172
|
+
|
|
173
|
+
Manifests are YAML files in `~/.config/agent-catalog/agents/`. `index.yaml` maps slugs to filenames. Set `AGENT_CATALOG_DIR` to point at a git repo — changes show up in `git diff`.
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
~/.config/agent-catalog/agents/
|
|
177
|
+
├── index.yaml # slug → filename mapping
|
|
178
|
+
├── agentic-inbox.yaml # agent manifests
|
|
179
|
+
├── nexusgate.yaml
|
|
180
|
+
└── trading-agent.yaml
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Production features
|
|
184
|
+
|
|
185
|
+
- **Atomic writes.** Every write goes to a temp file then `os.replace()` — no partial writes on crash.
|
|
186
|
+
- **Slug validation.** Rejects path traversal characters (/, \\, .., null).
|
|
187
|
+
- **Index cache.** Repeated `index()` calls within one invocation skip re-parsing.
|
|
188
|
+
- **Config validation.** Warns on type errors and unknown keys in config.yaml.
|
|
189
|
+
- **Consistency check.** `doctor` command finds orphaned files and missing manifests.
|
|
190
|
+
- **XSS-safe dashboard.** All agent values HTML-escaped.
|
|
191
|
+
- **CORS headers.** JSON API supports cross-origin requests.
|
|
192
|
+
- **Graceful shutdown.** SIGINT/SIGTERM signal handlers on HTTP server.
|
|
193
|
+
- **MCP token budget.** Tool responses hard-truncated to 1500 chars.
|
|
194
|
+
|
|
195
|
+
## Contributing
|
|
196
|
+
|
|
197
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Agent Catalog
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ewardGPT/agent-catalog/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/agent-catalog/)
|
|
5
|
+
[](https://pypi.org/project/agent-catalog/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
A registry for AI agents. Track every agent you own — what it does, what tools it uses, what model it runs, where it's deployed. YAML manifests stored on disk. No database. Git-ops friendly.
|
|
9
|
+
|
|
10
|
+
Think `npm` for agents. One tool to list, search, diff, and invoke them all.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install agent-catalog
|
|
14
|
+
agent-catalog register ./my-agent.yaml
|
|
15
|
+
agent-catalog list
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Why
|
|
19
|
+
|
|
20
|
+
You have agents scattered across repos, environments, and projects. Prod vs staging drift goes unnoticed. A dependency changes and nothing tells you. You grep through directories to find which agent has the capability you need.
|
|
21
|
+
|
|
22
|
+
Agent Catalog fixes that. One directory is the source of truth. Every command queries it.
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- **Registry.** Register agents from YAML manifests. List, search, get details. Filter by environment, capability, tool, surface.
|
|
27
|
+
- **Diff.** Compare manifests across environments (`--left-env staging --right-env production`). Catch drift before it hits prod.
|
|
28
|
+
- **Consistency.** `agent-catalog doctor` finds orphaned files and missing manifests.
|
|
29
|
+
- **Security audit.** Flags MCP endpoints without auth, write capabilities without confirmation gates, idempotent tools that should not be.
|
|
30
|
+
- **MCP server.** Expose the catalog as MCP tools — any MCP client can discover agents and invoke their capabilities.
|
|
31
|
+
- **HTTP dashboard.** `agent-catalog serve` starts a web UI on `:8420`.
|
|
32
|
+
- **Python SDK.** `from agent_catalog.client import CatalogClient` — sync or async.
|
|
33
|
+
- **Decorator API.** Define agents inline with `@agent`, `@capability`, `@tool`. Manifests auto-generated from Python classes.
|
|
34
|
+
- **Runtime loader.** Load an agent class from the catalog and invoke a capability from the command line or API.
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install agent-catalog
|
|
40
|
+
|
|
41
|
+
# Register an agent
|
|
42
|
+
agent-catalog register ./examples/agentic-inbox.yaml
|
|
43
|
+
|
|
44
|
+
# See everything you have
|
|
45
|
+
agent-catalog list
|
|
46
|
+
|
|
47
|
+
# Get details
|
|
48
|
+
agent-catalog get agentic-inbox
|
|
49
|
+
|
|
50
|
+
# Search
|
|
51
|
+
agent-catalog search --capability send_email
|
|
52
|
+
|
|
53
|
+
# Validate a manifest without registering
|
|
54
|
+
agent-catalog validate ./my-agent.yaml
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Walkthrough: managing agents across environments
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Register your staging agent
|
|
61
|
+
agent-catalog register ./staging/agent.yaml --env staging
|
|
62
|
+
|
|
63
|
+
# Later, register production
|
|
64
|
+
agent-catalog register ./prod/agent.yaml
|
|
65
|
+
|
|
66
|
+
# See the drift
|
|
67
|
+
agent-catalog diff agentic-inbox --left-env staging --right-env production
|
|
68
|
+
|
|
69
|
+
# Validate your changes
|
|
70
|
+
agent-catalog validate ./prod/agent.yaml
|
|
71
|
+
|
|
72
|
+
# Check everything is consistent
|
|
73
|
+
agent-catalog doctor
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Walkthrough: exposing agents via MCP
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Start the MCP server
|
|
80
|
+
agent-catalog serve --mcp
|
|
81
|
+
|
|
82
|
+
# Any MCP client can now call:
|
|
83
|
+
# - catalog_list_agents
|
|
84
|
+
# - catalog_get_agent
|
|
85
|
+
# - catalog_search
|
|
86
|
+
# - catalog_invoke
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Walkthrough: Python SDK
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from agent_catalog.client import CatalogClient
|
|
93
|
+
|
|
94
|
+
client = CatalogClient()
|
|
95
|
+
|
|
96
|
+
# List all production agents
|
|
97
|
+
for agent in client.agents.list(env="production"):
|
|
98
|
+
print(agent.slug, agent.capability_ids())
|
|
99
|
+
|
|
100
|
+
# Async
|
|
101
|
+
from agent_catalog.client import AsyncCatalogClient
|
|
102
|
+
client = AsyncCatalogClient()
|
|
103
|
+
agents = await client.agents.list()
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Walkthrough: decorator API
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from agent_catalog import agent, capability, tool, build_manifest
|
|
110
|
+
|
|
111
|
+
@agent(name="My Agent", version="1.0.0", environment="production")
|
|
112
|
+
class MyAgent:
|
|
113
|
+
@capability(id="greet", description="Greets the user")
|
|
114
|
+
@tool(name="greet", description="Say hello")
|
|
115
|
+
def greet(self, name: str) -> str:
|
|
116
|
+
return f"Hello, {name}!"
|
|
117
|
+
|
|
118
|
+
manifest = build_manifest(MyAgent)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Commands
|
|
122
|
+
|
|
123
|
+
| Command | Action |
|
|
124
|
+
|---------|--------|
|
|
125
|
+
| `register <path>` | Register agent from YAML |
|
|
126
|
+
| `list [--env] [--output json]` | List agents |
|
|
127
|
+
| `get <slug> [--output json]` | Show agent details |
|
|
128
|
+
| `search [--capability] [--tool] [--surface] [--env]` | Find agents |
|
|
129
|
+
| `diff <slug> [--right] [--slug2] [--right-env]` | Compare manifests |
|
|
130
|
+
| `validate <path>` | Validate YAML |
|
|
131
|
+
| `unregister <slug>` | Remove agent |
|
|
132
|
+
| `update <slug> <path>` | Update manifest |
|
|
133
|
+
| `sync <dir> [--pattern]` | Bulk register YAML files |
|
|
134
|
+
| `scan <dir>` | Discover @agent-decorated Python classes |
|
|
135
|
+
| `inspect <file>` | Show manifest from Python file |
|
|
136
|
+
| `export-contract <slug>` | Export eval contract |
|
|
137
|
+
| `security-audit [--format json]` | Security audit |
|
|
138
|
+
| `graph [--format json]` | Dependency graph |
|
|
139
|
+
| `serve [--port] [--mcp]` | HTTP dashboard or MCP server |
|
|
140
|
+
| `run <slug> <capability>` | Invoke a capability |
|
|
141
|
+
| `doctor` | Consistency check |
|
|
142
|
+
|
|
143
|
+
## Storage
|
|
144
|
+
|
|
145
|
+
Manifests are YAML files in `~/.config/agent-catalog/agents/`. `index.yaml` maps slugs to filenames. Set `AGENT_CATALOG_DIR` to point at a git repo — changes show up in `git diff`.
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
~/.config/agent-catalog/agents/
|
|
149
|
+
├── index.yaml # slug → filename mapping
|
|
150
|
+
├── agentic-inbox.yaml # agent manifests
|
|
151
|
+
├── nexusgate.yaml
|
|
152
|
+
└── trading-agent.yaml
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Production features
|
|
156
|
+
|
|
157
|
+
- **Atomic writes.** Every write goes to a temp file then `os.replace()` — no partial writes on crash.
|
|
158
|
+
- **Slug validation.** Rejects path traversal characters (/, \\, .., null).
|
|
159
|
+
- **Index cache.** Repeated `index()` calls within one invocation skip re-parsing.
|
|
160
|
+
- **Config validation.** Warns on type errors and unknown keys in config.yaml.
|
|
161
|
+
- **Consistency check.** `doctor` command finds orphaned files and missing manifests.
|
|
162
|
+
- **XSS-safe dashboard.** All agent values HTML-escaped.
|
|
163
|
+
- **CORS headers.** JSON API supports cross-origin requests.
|
|
164
|
+
- **Graceful shutdown.** SIGINT/SIGTERM signal handlers on HTTP server.
|
|
165
|
+
- **MCP token budget.** Tool responses hard-truncated to 1500 chars.
|
|
166
|
+
|
|
167
|
+
## Contributing
|
|
168
|
+
|
|
169
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
MIT
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Agent Catalog — Declarative agent registry.
|
|
2
|
+
|
|
3
|
+
Discover, diff, and manage agent capabilities across environments.
|
|
4
|
+
|
|
5
|
+
Decorator API (new in 0.2.0):
|
|
6
|
+
|
|
7
|
+
from agent_catalog import agent, capability, tool, interface, dependency
|
|
8
|
+
from agent_catalog.decorators import build_manifest
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from agent_catalog.decorators import (
|
|
14
|
+
agent,
|
|
15
|
+
build_manifest,
|
|
16
|
+
capability,
|
|
17
|
+
dependency,
|
|
18
|
+
interface,
|
|
19
|
+
prompt_ref,
|
|
20
|
+
tool,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"agent",
|
|
25
|
+
"build_manifest",
|
|
26
|
+
"capability",
|
|
27
|
+
"dependency",
|
|
28
|
+
"interface",
|
|
29
|
+
"prompt_ref",
|
|
30
|
+
"tool",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Agent Catalog CLI — declarative agent registry.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
agent-catalog register ./agent.yaml
|
|
5
|
+
agent-catalog list
|
|
6
|
+
agent-catalog get agentic-inbox
|
|
7
|
+
agent-catalog search --capability send_email
|
|
8
|
+
agent-catalog diff agentic-inbox --right staging
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
import typer
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.panel import Panel
|
|
18
|
+
from rich.text import Text
|
|
19
|
+
|
|
20
|
+
from agent_catalog import __version__
|
|
21
|
+
from agent_catalog.storage import CatalogStore
|
|
22
|
+
|
|
23
|
+
# ── App ────────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
app = typer.Typer(
|
|
26
|
+
name="agent-catalog",
|
|
27
|
+
help="Declarative agent registry — catalog, discover, and diff agent capabilities.",
|
|
28
|
+
no_args_is_help=True,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Typer adds --install-completion and --show-completion automatically.
|
|
32
|
+
|
|
33
|
+
console = Console()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ── Version flag ────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _version_callback(value: bool) -> None:
|
|
40
|
+
if value:
|
|
41
|
+
console.print(f"agent-catalog {__version__}")
|
|
42
|
+
raise typer.Exit()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.callback()
|
|
46
|
+
def _main_options(
|
|
47
|
+
version: bool = typer.Option(
|
|
48
|
+
False,
|
|
49
|
+
"--version",
|
|
50
|
+
help="Show version and exit.",
|
|
51
|
+
callback=_version_callback,
|
|
52
|
+
is_eager=True,
|
|
53
|
+
),
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Agent Catalog — declarative agent registry."""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ── Shared helpers ─────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _get_store() -> CatalogStore:
|
|
62
|
+
"""Resolve the catalog store, respecting AGENT_CATALOG_DIR env var."""
|
|
63
|
+
root = os.environ.get("AGENT_CATALOG_DIR")
|
|
64
|
+
return CatalogStore(root=root) if root else CatalogStore()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _render_manifest(m) -> Panel:
|
|
68
|
+
"""Pretty-print an agent manifest as a Rich Panel."""
|
|
69
|
+
rows: list[str] = []
|
|
70
|
+
rows.append(f"[bold]Name:[/] {m.name}")
|
|
71
|
+
rows.append(f"[bold]Slug:[/] {m.slug}")
|
|
72
|
+
rows.append(f"[bold]Version:[/] {m.version}")
|
|
73
|
+
rows.append(f"[bold]Environment:[/] {m.environment}")
|
|
74
|
+
rows.append(f"[bold]Status:[/] {m.status}")
|
|
75
|
+
rows.append(f"[bold]Description:[/] {m.description}")
|
|
76
|
+
if m.model:
|
|
77
|
+
rows.append(f"[bold]Model:[/] {m.model.provider}/{m.model.name}")
|
|
78
|
+
if m.capabilities:
|
|
79
|
+
rows.append(f"[bold]Capabilities:[/] {', '.join(c.id for c in m.capabilities)}")
|
|
80
|
+
if m.tools:
|
|
81
|
+
rows.append(f"[bold]Tools:[/] {', '.join(t.name for t in m.tools)}")
|
|
82
|
+
if m.prompt:
|
|
83
|
+
versions = ", ".join(p.version for p in m.prompt)
|
|
84
|
+
rows.append(f"[bold]Prompts:[/] {versions}")
|
|
85
|
+
if m.dependencies:
|
|
86
|
+
deps = ", ".join(d.name for d in m.dependencies)
|
|
87
|
+
rows.append(f"[bold]Dependencies:[/] {deps}")
|
|
88
|
+
if m.eval_contract:
|
|
89
|
+
suites = ", ".join(m.eval_contract.suites)
|
|
90
|
+
rows.append(f"[bold]Eval Suites:[/] {suites}")
|
|
91
|
+
text = "\n".join(rows)
|
|
92
|
+
return Panel(Text.from_markup(text), title=m.environment_tag())
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ── Entry point ────────────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def main() -> None:
|
|
99
|
+
"""Entry point for the 'agent-catalog' command."""
|
|
100
|
+
app()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
main()
|
|
105
|
+
|
|
106
|
+
# ── Register subcommand modules ────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
from . import aux_commands, crud, diff_commands, discover_commands # noqa: E402,F401
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Auxiliary commands: security-audit, graph, serve, run, doctor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from agent_catalog.cli import _get_store, app, console
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@app.command()
|
|
13
|
+
def security_audit(
|
|
14
|
+
output_format: str = typer.Option("table", "--format", "-f", help="Output: table, json"),
|
|
15
|
+
):
|
|
16
|
+
"""Audit all registered agents for security gaps."""
|
|
17
|
+
from agent_catalog.security import audit_catalog
|
|
18
|
+
|
|
19
|
+
findings = audit_catalog(_get_store())
|
|
20
|
+
if not findings:
|
|
21
|
+
console.print("[green]No security issues found[/]")
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
if output_format == "json":
|
|
25
|
+
console.print(
|
|
26
|
+
json.dumps(
|
|
27
|
+
[
|
|
28
|
+
{"severity": f.severity, "agent": f.agent, "title": f.title, "detail": f.detail}
|
|
29
|
+
for f in findings
|
|
30
|
+
],
|
|
31
|
+
indent=2,
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
from rich.table import Table
|
|
37
|
+
|
|
38
|
+
table = Table(title="Security Audit")
|
|
39
|
+
table.add_column("Severity", style="bold")
|
|
40
|
+
table.add_column("Agent")
|
|
41
|
+
table.add_column("Issue")
|
|
42
|
+
table.add_column("Detail", style="dim")
|
|
43
|
+
|
|
44
|
+
for f in findings:
|
|
45
|
+
color = {"critical": "red", "high": "yellow", "medium": "dim", "low": "dim"}[f.severity]
|
|
46
|
+
table.add_row(f"[{color}]{f.severity}[/]", f.agent, f.title, f.detail)
|
|
47
|
+
|
|
48
|
+
console.print(table)
|
|
49
|
+
crit = sum(1 for f in findings if f.severity == "critical")
|
|
50
|
+
high = sum(1 for f in findings if f.severity == "high")
|
|
51
|
+
console.print(f"[bold]Summary:[/] {len(findings)} findings ({crit} critical, {high} high)")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@app.command()
|
|
55
|
+
def graph(
|
|
56
|
+
output_format: str = typer.Option("mermaid", "--format", "-f", help="Output: mermaid, json"),
|
|
57
|
+
):
|
|
58
|
+
"""Show agent dependency graph."""
|
|
59
|
+
from agent_catalog.graph import build_graph, to_mermaid
|
|
60
|
+
|
|
61
|
+
store = _get_store()
|
|
62
|
+
if output_format == "json":
|
|
63
|
+
console.print_json(json.dumps(build_graph(store), default=str))
|
|
64
|
+
else:
|
|
65
|
+
console.print(to_mermaid(store))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@app.command()
|
|
69
|
+
def serve(
|
|
70
|
+
port: int = typer.Option(8420, "--port", "-p", help="HTTP port"),
|
|
71
|
+
mcp: bool = typer.Option(False, "--mcp", help="Run MCP server instead of HTTP (stdio transport)"),
|
|
72
|
+
):
|
|
73
|
+
"""Start the Agent Marketplace web dashboard.
|
|
74
|
+
|
|
75
|
+
By default starts an HTTP server. Pass --mcp to run as an MCP
|
|
76
|
+
server over stdio (for use with MCP clients like Claude Code).
|
|
77
|
+
"""
|
|
78
|
+
if mcp:
|
|
79
|
+
from agent_catalog.mcp_server import run_server
|
|
80
|
+
|
|
81
|
+
run_server(store=_get_store())
|
|
82
|
+
else:
|
|
83
|
+
from agent_catalog.serve import serve as run_serve
|
|
84
|
+
|
|
85
|
+
run_serve(port=port, store=_get_store())
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@app.command()
|
|
89
|
+
def run(
|
|
90
|
+
slug: str = typer.Argument(..., help="Agent slug"),
|
|
91
|
+
capability: str = typer.Argument(..., help="Capability ID to invoke"),
|
|
92
|
+
params: str | None = typer.Option(None, "--params", "-p", help="JSON string of parameters"),
|
|
93
|
+
):
|
|
94
|
+
"""Load an agent and invoke a capability at runtime.
|
|
95
|
+
|
|
96
|
+
Uses metadata.python_module / metadata.python_class to find and
|
|
97
|
+
import the agent class, then calls the capability method.
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
agent-catalog run my-agent greet
|
|
101
|
+
agent-catalog run my-agent greet --params '{"name": "World"}'
|
|
102
|
+
"""
|
|
103
|
+
from agent_catalog.loader import invoke_capability
|
|
104
|
+
|
|
105
|
+
kwargs: dict = {}
|
|
106
|
+
if params:
|
|
107
|
+
try:
|
|
108
|
+
kwargs = json.loads(params)
|
|
109
|
+
except json.JSONDecodeError as e:
|
|
110
|
+
console.print(f"[red]\u2717[/] Invalid JSON in --params: {e}")
|
|
111
|
+
raise typer.Exit(1) from e
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
result = invoke_capability(slug, capability, store=_get_store(), **kwargs)
|
|
115
|
+
console.print(result)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
console.print(f"[red]\u2717[/] {e}")
|
|
118
|
+
raise typer.Exit(1) from e
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@app.command()
|
|
122
|
+
def doctor():
|
|
123
|
+
"""Check catalog consistency (orphaned files, missing manifests).
|
|
124
|
+
|
|
125
|
+
Scans the catalog directory and verifies every indexed agent
|
|
126
|
+
has a manifest file on disk, and every YAML file is indexed.
|
|
127
|
+
"""
|
|
128
|
+
store = _get_store()
|
|
129
|
+
issues = store.check_consistency()
|
|
130
|
+
|
|
131
|
+
if not issues:
|
|
132
|
+
console.print("[green]\u2713[/] Catalog is consistent")
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
console.print(f"[yellow]Found {len(issues)} issue(s):[/]")
|
|
136
|
+
for issue in issues:
|
|
137
|
+
prefix = "[red]MISSING[/]" if issue.startswith("MISSING") else "[yellow]ORPHAN[/]"
|
|
138
|
+
console.print(f" {prefix} {issue}")
|
|
139
|
+
raise typer.Exit(1)
|