agents-tool 0.1.0__py3-none-any.whl
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.
- agents_tool/__init__.py +3 -0
- agents_tool/cli.py +36 -0
- agents_tool/config.py +59 -0
- agents_tool/git.py +95 -0
- agents_tool/registry.py +229 -0
- agents_tool/validator.py +86 -0
- agents_tool-0.1.0.dist-info/METADATA +64 -0
- agents_tool-0.1.0.dist-info/RECORD +11 -0
- agents_tool-0.1.0.dist-info/WHEEL +4 -0
- agents_tool-0.1.0.dist-info/entry_points.txt +2 -0
- agents_tool-0.1.0.dist-info/licenses/LICENSE +21 -0
agents_tool/__init__.py
ADDED
agents_tool/cli.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""CLI entry point for agents-tool."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from . import __version__
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.group()
|
|
9
|
+
@click.version_option(version=__version__)
|
|
10
|
+
def main():
|
|
11
|
+
"""Agents Tool - Manage AI agents, skills and harness."""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@main.command()
|
|
16
|
+
def init():
|
|
17
|
+
"""Initialize agents-tool configuration."""
|
|
18
|
+
from .config import init_config
|
|
19
|
+
|
|
20
|
+
init_config()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@main.command()
|
|
24
|
+
def gou():
|
|
25
|
+
"""Get or update agents from configured repos."""
|
|
26
|
+
from .git import process_repos
|
|
27
|
+
|
|
28
|
+
process_repos()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@main.command()
|
|
32
|
+
def list():
|
|
33
|
+
"""List installed agents, skills and harness."""
|
|
34
|
+
from .registry import list_installed
|
|
35
|
+
|
|
36
|
+
list_installed()
|
agents_tool/config.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Config management for agents-tool."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
# Local config (in current directory)
|
|
10
|
+
CONFIG_DIR = Path.cwd() / ".agents"
|
|
11
|
+
CONFIG_FILE = CONFIG_DIR / "config.yaml"
|
|
12
|
+
LOCAL_DIR = Path.cwd() / ".agents"
|
|
13
|
+
REPOS_DIR = LOCAL_DIR / "repos"
|
|
14
|
+
INSTALLED_DIR = LOCAL_DIR / "installed"
|
|
15
|
+
|
|
16
|
+
CONFIG_TEMPLATE = """\
|
|
17
|
+
# Agents Tool Configuration
|
|
18
|
+
# Edit this file with your repos and preferences.
|
|
19
|
+
|
|
20
|
+
repos:
|
|
21
|
+
# Example:
|
|
22
|
+
# - url: git@github.com:your-username/your-agents.git
|
|
23
|
+
# user: your-username
|
|
24
|
+
# branch: main
|
|
25
|
+
#
|
|
26
|
+
# - url: git@github.com:team/shared-agents.git
|
|
27
|
+
# user: your-username
|
|
28
|
+
# branch: develop
|
|
29
|
+
|
|
30
|
+
defaults:
|
|
31
|
+
branch: main
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def init_config():
|
|
36
|
+
"""Initialize configuration and directories."""
|
|
37
|
+
if CONFIG_FILE.exists():
|
|
38
|
+
click.echo(f"Config already exists: {CONFIG_FILE}")
|
|
39
|
+
click.echo("Edit it manually to add your repos.")
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
REPOS_DIR.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
INSTALLED_DIR.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
|
|
46
|
+
CONFIG_FILE.write_text(CONFIG_TEMPLATE)
|
|
47
|
+
|
|
48
|
+
click.echo("Agents tool initialized!")
|
|
49
|
+
click.echo(f"Config: {CONFIG_FILE}")
|
|
50
|
+
click.echo("Edit it to add your repos, then run: agents gou")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load_config():
|
|
54
|
+
"""Load configuration from YAML file."""
|
|
55
|
+
if not CONFIG_FILE.exists():
|
|
56
|
+
raise FileNotFoundError(f"Config not found: {CONFIG_FILE}")
|
|
57
|
+
|
|
58
|
+
with open(CONFIG_FILE) as f:
|
|
59
|
+
return yaml.safe_load(f)
|
agents_tool/git.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Git operations for agents-tool."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from git import Repo
|
|
8
|
+
|
|
9
|
+
from .config import load_config, REPOS_DIR, INSTALLED_DIR
|
|
10
|
+
from .registry import sync_installed, update_registry
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def process_repos():
|
|
14
|
+
"""Process all repos from config (clone or update)."""
|
|
15
|
+
config = load_config()
|
|
16
|
+
repos = config.get("repos", [])
|
|
17
|
+
|
|
18
|
+
if not repos:
|
|
19
|
+
click.echo("No repos configured.")
|
|
20
|
+
click.echo("Edit .agents/config.yaml to add repos.")
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
for repo_config in repos:
|
|
24
|
+
process_repo(repo_config)
|
|
25
|
+
|
|
26
|
+
# Sync and update registry after all repos are processed
|
|
27
|
+
click.echo()
|
|
28
|
+
click.echo("Syncing installed...")
|
|
29
|
+
sync_installed()
|
|
30
|
+
|
|
31
|
+
click.echo("Updating registry...")
|
|
32
|
+
update_registry()
|
|
33
|
+
|
|
34
|
+
click.echo()
|
|
35
|
+
click.echo("Done!")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def process_repo(repo_config):
|
|
39
|
+
"""Process a single repo (clone or update)."""
|
|
40
|
+
url = repo_config.get("url")
|
|
41
|
+
user = repo_config.get("user", "")
|
|
42
|
+
branch = repo_config.get("branch", "main")
|
|
43
|
+
|
|
44
|
+
if not url:
|
|
45
|
+
click.echo("Warning: Repo without URL, skipping.")
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
repo_name = extract_repo_name(url)
|
|
49
|
+
repo_path = REPOS_DIR / repo_name
|
|
50
|
+
|
|
51
|
+
if repo_path.exists():
|
|
52
|
+
update_repo(repo_path, branch)
|
|
53
|
+
else:
|
|
54
|
+
clone_repo(url, repo_path, branch)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def extract_repo_name(url):
|
|
58
|
+
"""Extract repo name from git URL."""
|
|
59
|
+
# Handle SSH URLs
|
|
60
|
+
if url.endswith(".git"):
|
|
61
|
+
url = url[:-4]
|
|
62
|
+
# Get last part after /
|
|
63
|
+
return url.split("/")[-1]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def clone_repo(url, path, branch):
|
|
67
|
+
"""Clone a repo with authentication."""
|
|
68
|
+
click.echo(f"Cloning: {url}")
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
# Try cloning without auth first (public repos)
|
|
72
|
+
Repo.clone_from(url, str(path), branch=branch)
|
|
73
|
+
click.echo(f" Cloned to: {path}")
|
|
74
|
+
except Exception as e:
|
|
75
|
+
if "Authentication" in str(e) or "public" in str(e).lower():
|
|
76
|
+
# Need auth - ask for token
|
|
77
|
+
token = click.prompt("Token", hide_input=True)
|
|
78
|
+
auth_url = url.replace("git@", f"https://{token}@")
|
|
79
|
+
Repo.clone_from(auth_url, str(path), branch=branch)
|
|
80
|
+
click.echo(f" Cloned to: {path}")
|
|
81
|
+
else:
|
|
82
|
+
click.echo(f" Error: {e}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def update_repo(path, branch):
|
|
86
|
+
"""Pull updates for an existing repo."""
|
|
87
|
+
click.echo(f"Updating: {path.name}")
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
repo = Repo(str(path))
|
|
91
|
+
origin = repo.remotes.origin
|
|
92
|
+
origin.pull(branch)
|
|
93
|
+
click.echo(" Updated")
|
|
94
|
+
except Exception as e:
|
|
95
|
+
click.echo(f" Error: {e}")
|
agents_tool/registry.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Registry for installed agents, skills and harness."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import shutil
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
from .config import REPOS_DIR, INSTALLED_DIR
|
|
11
|
+
from .validator import validate_agent_yaml, validate_skill_md
|
|
12
|
+
|
|
13
|
+
REGISTRY_FILE = INSTALLED_DIR / "registry.json"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def update_registry():
|
|
17
|
+
"""Update registry.json with current installed content."""
|
|
18
|
+
INSTALLED_DIR.mkdir(parents=True, exist_ok=True)
|
|
19
|
+
|
|
20
|
+
registry = {
|
|
21
|
+
"agents": find_agents(),
|
|
22
|
+
"skills": find_skills(),
|
|
23
|
+
"harness": find_harness(),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
with open(REGISTRY_FILE, "w") as f:
|
|
27
|
+
json.dump(registry, f, indent=2)
|
|
28
|
+
|
|
29
|
+
return registry
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_registry():
|
|
33
|
+
"""Load registry from file."""
|
|
34
|
+
if not REGISTRY_FILE.exists():
|
|
35
|
+
return update_registry()
|
|
36
|
+
|
|
37
|
+
with open(REGISTRY_FILE) as f:
|
|
38
|
+
return json.load(f)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def sync_installed():
|
|
42
|
+
"""Sync repos content to installed directory."""
|
|
43
|
+
INSTALLED_DIR.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
|
|
45
|
+
# Clean installed directory
|
|
46
|
+
for item in INSTALLED_DIR.iterdir():
|
|
47
|
+
if item.name == "registry.json":
|
|
48
|
+
continue
|
|
49
|
+
if item.is_dir():
|
|
50
|
+
shutil.rmtree(item)
|
|
51
|
+
else:
|
|
52
|
+
item.unlink()
|
|
53
|
+
|
|
54
|
+
agents_dir = INSTALLED_DIR / "agents"
|
|
55
|
+
skills_dir = INSTALLED_DIR / "skills"
|
|
56
|
+
harness_dir = INSTALLED_DIR / "harness"
|
|
57
|
+
|
|
58
|
+
agents_dir.mkdir(exist_ok=True)
|
|
59
|
+
skills_dir.mkdir(exist_ok=True)
|
|
60
|
+
harness_dir.mkdir(exist_ok=True)
|
|
61
|
+
|
|
62
|
+
if not REPOS_DIR.exists():
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
for repo_dir in REPOS_DIR.iterdir():
|
|
66
|
+
if not repo_dir.is_dir():
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
# Sync agents
|
|
70
|
+
repo_agents = repo_dir / "agents"
|
|
71
|
+
if repo_agents.exists():
|
|
72
|
+
for agent_dir in repo_agents.iterdir():
|
|
73
|
+
if not agent_dir.is_dir():
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
is_valid, errors, config = validate_agent_yaml(agent_dir)
|
|
77
|
+
if is_valid:
|
|
78
|
+
dest = agents_dir / agent_dir.name
|
|
79
|
+
if not dest.exists():
|
|
80
|
+
shutil.copytree(agent_dir, dest)
|
|
81
|
+
else:
|
|
82
|
+
click.echo(f"Warning: Invalid agent {agent_dir.name}: {errors}")
|
|
83
|
+
|
|
84
|
+
# Sync skills
|
|
85
|
+
repo_skills = repo_dir / "skills"
|
|
86
|
+
if repo_skills.exists():
|
|
87
|
+
for skill_dir in repo_skills.iterdir():
|
|
88
|
+
if not skill_dir.is_dir():
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
is_valid, errors, content = validate_skill_md(skill_dir)
|
|
92
|
+
if is_valid:
|
|
93
|
+
dest = skills_dir / skill_dir.name
|
|
94
|
+
if not dest.exists():
|
|
95
|
+
shutil.copytree(skill_dir, dest)
|
|
96
|
+
else:
|
|
97
|
+
click.echo(f"Warning: Invalid skill {skill_dir.name}: {errors}")
|
|
98
|
+
|
|
99
|
+
# Sync harness
|
|
100
|
+
repo_harness = repo_dir / "harness"
|
|
101
|
+
if repo_harness.exists():
|
|
102
|
+
for h_dir in repo_harness.iterdir():
|
|
103
|
+
if not h_dir.is_dir():
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
dest = harness_dir / h_dir.name
|
|
107
|
+
if not dest.exists():
|
|
108
|
+
shutil.copytree(h_dir, dest)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def list_installed():
|
|
112
|
+
"""List all installed agents, skills and harness."""
|
|
113
|
+
registry = load_registry()
|
|
114
|
+
|
|
115
|
+
agents = registry.get("agents", [])
|
|
116
|
+
skills = registry.get("skills", [])
|
|
117
|
+
harness = registry.get("harness", [])
|
|
118
|
+
|
|
119
|
+
if not agents and not skills and not harness:
|
|
120
|
+
click.echo("No agents/skills/harness installed.")
|
|
121
|
+
click.echo("Run: agents gou")
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
if agents:
|
|
125
|
+
click.echo("Agents:")
|
|
126
|
+
for agent in agents:
|
|
127
|
+
version = agent.get('version', 'unknown')
|
|
128
|
+
source = agent.get('source', 'unknown')
|
|
129
|
+
click.echo(f" - {agent['name']} v{version} ({source})")
|
|
130
|
+
click.echo()
|
|
131
|
+
|
|
132
|
+
if skills:
|
|
133
|
+
click.echo("Skills:")
|
|
134
|
+
for skill in skills:
|
|
135
|
+
source = skill.get('source', 'unknown')
|
|
136
|
+
click.echo(f" - {skill['name']} ({source})")
|
|
137
|
+
click.echo()
|
|
138
|
+
|
|
139
|
+
if harness:
|
|
140
|
+
click.echo("Harness:")
|
|
141
|
+
for h in harness:
|
|
142
|
+
source = h.get('source', 'unknown')
|
|
143
|
+
click.echo(f" - {h['name']} ({source})")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def find_agents():
|
|
147
|
+
"""Find all agents in repos."""
|
|
148
|
+
agents = []
|
|
149
|
+
if not REPOS_DIR.exists():
|
|
150
|
+
return agents
|
|
151
|
+
|
|
152
|
+
for repo_dir in REPOS_DIR.iterdir():
|
|
153
|
+
if not repo_dir.is_dir():
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
agents_dir = repo_dir / "agents"
|
|
157
|
+
if not agents_dir.exists():
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
for agent_dir in agents_dir.iterdir():
|
|
161
|
+
if not agent_dir.is_dir():
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
config_file = agent_dir / "agent.yaml"
|
|
165
|
+
if config_file.exists():
|
|
166
|
+
with open(config_file) as f:
|
|
167
|
+
config = yaml.safe_load(f) or {}
|
|
168
|
+
|
|
169
|
+
agents.append({
|
|
170
|
+
"name": config.get("name", agent_dir.name),
|
|
171
|
+
"version": config.get("version", "unknown"),
|
|
172
|
+
"source": repo_dir.name,
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
return agents
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def find_skills():
|
|
179
|
+
"""Find all skills in repos."""
|
|
180
|
+
skills = []
|
|
181
|
+
if not REPOS_DIR.exists():
|
|
182
|
+
return skills
|
|
183
|
+
|
|
184
|
+
for repo_dir in REPOS_DIR.iterdir():
|
|
185
|
+
if not repo_dir.is_dir():
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
skills_dir = repo_dir / "skills"
|
|
189
|
+
if not skills_dir.exists():
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
for skill_dir in skills_dir.iterdir():
|
|
193
|
+
if not skill_dir.is_dir():
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
skill_file = skill_dir / "SKILL.md"
|
|
197
|
+
if skill_file.exists():
|
|
198
|
+
skills.append({
|
|
199
|
+
"name": skill_dir.name,
|
|
200
|
+
"source": repo_dir.name,
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
return skills
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def find_harness():
|
|
207
|
+
"""Find all harness configurations in repos."""
|
|
208
|
+
harness = []
|
|
209
|
+
if not REPOS_DIR.exists():
|
|
210
|
+
return harness
|
|
211
|
+
|
|
212
|
+
for repo_dir in REPOS_DIR.iterdir():
|
|
213
|
+
if not repo_dir.is_dir():
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
harness_dir = repo_dir / "harness"
|
|
217
|
+
if not harness_dir.exists():
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
for h_dir in harness_dir.iterdir():
|
|
221
|
+
if not h_dir.is_dir():
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
harness.append({
|
|
225
|
+
"name": h_dir.name,
|
|
226
|
+
"source": repo_dir.name,
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
return harness
|
agents_tool/validator.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Validation for agent/skill/harness definitions."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
REQUIRED_AGENT_FIELDS = ["name"]
|
|
9
|
+
VALID_TOOL_TYPES = ["read", "write", "edit", "bash", "grep", "glob"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def validate_agent_yaml(agent_dir):
|
|
13
|
+
"""Validate agent.yaml in a directory.
|
|
14
|
+
|
|
15
|
+
Returns (is_valid, errors, config).
|
|
16
|
+
"""
|
|
17
|
+
config_file = agent_dir / "agent.yaml"
|
|
18
|
+
|
|
19
|
+
if not config_file.exists():
|
|
20
|
+
return False, ["agent.yaml not found"], None
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
with open(config_file) as f:
|
|
24
|
+
config = yaml.safe_load(f)
|
|
25
|
+
except yaml.YAMLError as e:
|
|
26
|
+
return False, [f"Invalid YAML: {e}"], None
|
|
27
|
+
|
|
28
|
+
if config is None:
|
|
29
|
+
return False, ["agent.yaml is empty"], None
|
|
30
|
+
|
|
31
|
+
errors = []
|
|
32
|
+
|
|
33
|
+
# Check required fields
|
|
34
|
+
for field in REQUIRED_AGENT_FIELDS:
|
|
35
|
+
if field not in config:
|
|
36
|
+
errors.append(f"Missing required field: {field}")
|
|
37
|
+
|
|
38
|
+
# Validate tools if present
|
|
39
|
+
if "tools" in config:
|
|
40
|
+
tools = config["tools"]
|
|
41
|
+
if not isinstance(tools, list):
|
|
42
|
+
errors.append("'tools' must be a list")
|
|
43
|
+
else:
|
|
44
|
+
for tool in tools:
|
|
45
|
+
if tool not in VALID_TOOL_TYPES:
|
|
46
|
+
errors.append(f"Unknown tool type: {tool}")
|
|
47
|
+
|
|
48
|
+
# Validate name format
|
|
49
|
+
if "name" in config:
|
|
50
|
+
name = config["name"]
|
|
51
|
+
if not isinstance(name, str) or not name.strip():
|
|
52
|
+
errors.append("'name' must be a non-empty string")
|
|
53
|
+
|
|
54
|
+
return len(errors) == 0, errors, config
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def validate_skill_md(skill_dir):
|
|
58
|
+
"""Validate SKILL.md in a directory.
|
|
59
|
+
|
|
60
|
+
Returns (is_valid, errors, content).
|
|
61
|
+
"""
|
|
62
|
+
skill_file = skill_dir / "SKILL.md"
|
|
63
|
+
|
|
64
|
+
if not skill_file.exists():
|
|
65
|
+
return False, ["SKILL.md not found"], None
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
content = skill_file.read_text()
|
|
69
|
+
except Exception as e:
|
|
70
|
+
return False, [f"Cannot read SKILL.md: {e}"], None
|
|
71
|
+
|
|
72
|
+
if not content.strip():
|
|
73
|
+
return False, ["SKILL.md is empty"], None
|
|
74
|
+
|
|
75
|
+
# Check for frontmatter
|
|
76
|
+
if content.startswith("---"):
|
|
77
|
+
try:
|
|
78
|
+
parts = content.split("---", 2)
|
|
79
|
+
if len(parts) >= 3:
|
|
80
|
+
frontmatter = yaml.safe_load(parts[1])
|
|
81
|
+
if frontmatter and "name" not in frontmatter:
|
|
82
|
+
return False, ["Frontmatter missing 'name' field"], content
|
|
83
|
+
except yaml.YAMLError:
|
|
84
|
+
return False, ["Invalid YAML in frontmatter"], content
|
|
85
|
+
|
|
86
|
+
return True, [], content
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: agents-tool
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI tool for managing AI agents, skills and harness
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: click>=8.1.0
|
|
9
|
+
Requires-Dist: gitpython>=3.1.0
|
|
10
|
+
Requires-Dist: pyyaml>=6.0
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# Agents Tool
|
|
14
|
+
|
|
15
|
+
CLI tool for managing AI agents, skills and harness.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install agents-tool
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Initialize configuration
|
|
27
|
+
agents init
|
|
28
|
+
|
|
29
|
+
# Edit config to add your repos
|
|
30
|
+
vim ~/.config/agents/config.yaml
|
|
31
|
+
|
|
32
|
+
# Import/update all agents
|
|
33
|
+
agents gou
|
|
34
|
+
|
|
35
|
+
# List installed agents/skills/harness
|
|
36
|
+
agents list
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Configuration
|
|
40
|
+
|
|
41
|
+
Edit `~/.config/agents/config.yaml`:
|
|
42
|
+
|
|
43
|
+
```yaml
|
|
44
|
+
repos:
|
|
45
|
+
- url: git@github.com:your-username/your-agents.git
|
|
46
|
+
user: your-username
|
|
47
|
+
branch: main
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Agent Repo Structure
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
your-agents/
|
|
54
|
+
├── agents/
|
|
55
|
+
│ └── reviewer/
|
|
56
|
+
│ ├── agent.yaml
|
|
57
|
+
│ └── prompt.md
|
|
58
|
+
├── skills/
|
|
59
|
+
│ └── code-review/
|
|
60
|
+
│ └── SKILL.md
|
|
61
|
+
└── harness/
|
|
62
|
+
└── opencode/
|
|
63
|
+
└── config.json
|
|
64
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
agents_tool/__init__.py,sha256=a6BznFb0FAS2EI_TXOqZLiuRdBRtdO1Jc2HdmFDHiQE,91
|
|
2
|
+
agents_tool/cli.py,sha256=x8bTS-QhT5tAMKjL8ptHH8LF8bGc7rzPfdps9gpRGl0,641
|
|
3
|
+
agents_tool/config.py,sha256=M0rTii7TusHlrK7Glx5i_IhCnA0Kt-cQ5VwWa99DsJU,1507
|
|
4
|
+
agents_tool/git.py,sha256=mo7RBHaGes4BjrnY93FlQuHHj26JI2vNvdBYOFuxZzU,2532
|
|
5
|
+
agents_tool/registry.py,sha256=mqAUxqDCuVjtXw9O4gNkLCZkKWe2tTSJa6_J_sHOjyk,6311
|
|
6
|
+
agents_tool/validator.py,sha256=x48PHYrtQgmU3zD0OenNU9uWHuk9uTwW044gpJr7zdw,2416
|
|
7
|
+
agents_tool-0.1.0.dist-info/METADATA,sha256=QMxjtVwidmVL4NBaRFxEJienW7T0AsRMulrqYv7yePU,1127
|
|
8
|
+
agents_tool-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
9
|
+
agents_tool-0.1.0.dist-info/entry_points.txt,sha256=pGdW_2B-utlHR6EtcBglUVGHd_QHnrMjz6DkR2tJ3UQ,48
|
|
10
|
+
agents_tool-0.1.0.dist-info/licenses/LICENSE,sha256=ESYyLizI0WWtxMeS7rGVcX3ivMezm-HOd5WdeOh-9oU,1056
|
|
11
|
+
agents_tool-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
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.
|