aes-cli 0.2.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.
- aes/__init__.py +5 -0
- aes/__main__.py +37 -0
- aes/analyzer.py +487 -0
- aes/commands/__init__.py +0 -0
- aes/commands/init.py +727 -0
- aes/commands/inspect.py +204 -0
- aes/commands/install.py +379 -0
- aes/commands/publish.py +432 -0
- aes/commands/search.py +65 -0
- aes/commands/status.py +153 -0
- aes/commands/sync.py +413 -0
- aes/commands/validate.py +77 -0
- aes/config.py +43 -0
- aes/domains.py +1382 -0
- aes/frameworks.py +522 -0
- aes/mcp_server.py +213 -0
- aes/registry.py +294 -0
- aes/scaffold/agent.yaml.jinja +135 -0
- aes/scaffold/agentignore.jinja +61 -0
- aes/scaffold/instructions.md.jinja +311 -0
- aes/scaffold/local.example.yaml.jinja +35 -0
- aes/scaffold/local.yaml.jinja +29 -0
- aes/scaffold/operations.md.jinja +33 -0
- aes/scaffold/orchestrator.md.jinja +95 -0
- aes/scaffold/permissions.yaml.jinja +151 -0
- aes/scaffold/setup.md.jinja +244 -0
- aes/scaffold/skill.md.jinja +27 -0
- aes/scaffold/skill.yaml.jinja +175 -0
- aes/scaffold/workflow.yaml.jinja +44 -0
- aes/scaffold/workflow_command.md.jinja +48 -0
- aes/schemas/agent.schema.json +188 -0
- aes/schemas/permissions.schema.json +100 -0
- aes/schemas/registry.schema.json +72 -0
- aes/schemas/skill.schema.json +209 -0
- aes/schemas/workflow.schema.json +92 -0
- aes/targets/__init__.py +29 -0
- aes/targets/_base.py +77 -0
- aes/targets/_composer.py +338 -0
- aes/targets/claude.py +153 -0
- aes/targets/copilot.py +48 -0
- aes/targets/cursor.py +46 -0
- aes/targets/windsurf.py +46 -0
- aes/validator.py +394 -0
- aes_cli-0.2.0.dist-info/METADATA +110 -0
- aes_cli-0.2.0.dist-info/RECORD +48 -0
- aes_cli-0.2.0.dist-info/WHEEL +5 -0
- aes_cli-0.2.0.dist-info/entry_points.txt +3 -0
- aes_cli-0.2.0.dist-info/top_level.txt +1 -0
aes/targets/windsurf.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Windsurf sync target."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from aes.targets._base import AES_SENTINEL_MD, AgentContext, GeneratedFile, SyncPlan, SyncTarget
|
|
6
|
+
from aes.targets._composer import compose_instructions, translate_permissions_to_markdown
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WindsurfTarget(SyncTarget):
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def name(self) -> str:
|
|
13
|
+
return "windsurf"
|
|
14
|
+
|
|
15
|
+
def plan(self, ctx: AgentContext, force: bool) -> SyncPlan:
|
|
16
|
+
plan = SyncPlan(target_name=self.name)
|
|
17
|
+
|
|
18
|
+
header = (
|
|
19
|
+
AES_SENTINEL_MD
|
|
20
|
+
+ "\n# "
|
|
21
|
+
+ ctx.manifest.get("name", "Project")
|
|
22
|
+
+ " \u2014 Windsurf Rules"
|
|
23
|
+
)
|
|
24
|
+
content = compose_instructions(
|
|
25
|
+
project_name=ctx.manifest.get("name", "Project"),
|
|
26
|
+
instructions=ctx.instructions,
|
|
27
|
+
orchestrator=ctx.orchestrator,
|
|
28
|
+
skill_runbooks=ctx.skill_runbooks,
|
|
29
|
+
memory_project=ctx.memory_project,
|
|
30
|
+
header=header,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if ctx.permissions:
|
|
34
|
+
perms_md = translate_permissions_to_markdown(ctx.permissions)
|
|
35
|
+
if perms_md:
|
|
36
|
+
content += "\n" + perms_md
|
|
37
|
+
|
|
38
|
+
action = self._check_conflict(ctx.project_root, ".windsurfrules", force)
|
|
39
|
+
plan.files.append(GeneratedFile(
|
|
40
|
+
relative_path=".windsurfrules",
|
|
41
|
+
content=content,
|
|
42
|
+
description="Windsurf rules file",
|
|
43
|
+
action=action,
|
|
44
|
+
))
|
|
45
|
+
|
|
46
|
+
return plan
|
aes/validator.py
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"""Schema validation engine for AES files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
from jsonschema import Draft202012Validator, ValidationError
|
|
12
|
+
|
|
13
|
+
from aes.config import SCHEMAS_DIR, SCHEMA_MAP
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class ValidationResult:
|
|
18
|
+
"""Result of validating a single file."""
|
|
19
|
+
|
|
20
|
+
file_path: Path
|
|
21
|
+
schema_type: str
|
|
22
|
+
valid: bool
|
|
23
|
+
errors: List[str] = field(default_factory=list)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_schema(schema_type: str) -> dict:
|
|
27
|
+
"""Load a JSON Schema by type name."""
|
|
28
|
+
filename = SCHEMA_MAP.get(schema_type)
|
|
29
|
+
if filename is None:
|
|
30
|
+
raise ValueError(f"Unknown schema type: {schema_type}. Known: {list(SCHEMA_MAP.keys())}")
|
|
31
|
+
schema_path = SCHEMAS_DIR / filename
|
|
32
|
+
if not schema_path.exists():
|
|
33
|
+
raise FileNotFoundError(f"Schema not found: {schema_path}")
|
|
34
|
+
with open(schema_path) as f:
|
|
35
|
+
return json.load(f)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load_yaml(file_path: Path) -> dict:
|
|
39
|
+
"""Load and parse a YAML file."""
|
|
40
|
+
with open(file_path) as f:
|
|
41
|
+
data = yaml.safe_load(f)
|
|
42
|
+
if data is None:
|
|
43
|
+
return {}
|
|
44
|
+
if not isinstance(data, dict):
|
|
45
|
+
raise ValueError(f"Expected YAML mapping in {file_path}, got {type(data).__name__}")
|
|
46
|
+
return data
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def validate_file(file_path: Path, schema_type: str) -> ValidationResult:
|
|
50
|
+
"""Validate a YAML file against a JSON Schema."""
|
|
51
|
+
result = ValidationResult(file_path=file_path, schema_type=schema_type, valid=True)
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
data = load_yaml(file_path)
|
|
55
|
+
except Exception as e:
|
|
56
|
+
result.valid = False
|
|
57
|
+
result.errors.append(f"Failed to parse YAML: {e}")
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
schema = load_schema(schema_type)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
result.valid = False
|
|
64
|
+
result.errors.append(f"Failed to load schema: {e}")
|
|
65
|
+
return result
|
|
66
|
+
|
|
67
|
+
validator = Draft202012Validator(schema)
|
|
68
|
+
errors = sorted(validator.iter_errors(data), key=lambda e: list(e.path))
|
|
69
|
+
|
|
70
|
+
if errors:
|
|
71
|
+
result.valid = False
|
|
72
|
+
for error in errors:
|
|
73
|
+
path = ".".join(str(p) for p in error.absolute_path) or "(root)"
|
|
74
|
+
result.errors.append(f" {path}: {error.message}")
|
|
75
|
+
|
|
76
|
+
return result
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def validate_agent_dir(agent_dir: Path) -> List[ValidationResult]:
|
|
80
|
+
"""Validate all files in a .agent/ directory."""
|
|
81
|
+
results = []
|
|
82
|
+
|
|
83
|
+
# Validate agent.yaml
|
|
84
|
+
manifest_path = agent_dir / "agent.yaml"
|
|
85
|
+
if manifest_path.exists():
|
|
86
|
+
results.append(validate_file(manifest_path, "agent"))
|
|
87
|
+
else:
|
|
88
|
+
results.append(ValidationResult(
|
|
89
|
+
file_path=manifest_path,
|
|
90
|
+
schema_type="agent",
|
|
91
|
+
valid=False,
|
|
92
|
+
errors=["File not found: agent.yaml is required"],
|
|
93
|
+
))
|
|
94
|
+
return results # Can't continue without manifest
|
|
95
|
+
|
|
96
|
+
# Load manifest to find referenced files
|
|
97
|
+
try:
|
|
98
|
+
manifest = load_yaml(manifest_path)
|
|
99
|
+
except Exception:
|
|
100
|
+
return results
|
|
101
|
+
|
|
102
|
+
# Validate permissions
|
|
103
|
+
agent_section = manifest.get("agent", {})
|
|
104
|
+
permissions_path_str = agent_section.get("permissions")
|
|
105
|
+
if permissions_path_str:
|
|
106
|
+
permissions_path = agent_dir / permissions_path_str
|
|
107
|
+
if permissions_path.exists():
|
|
108
|
+
results.append(validate_file(permissions_path, "permissions"))
|
|
109
|
+
else:
|
|
110
|
+
results.append(ValidationResult(
|
|
111
|
+
file_path=permissions_path,
|
|
112
|
+
schema_type="permissions",
|
|
113
|
+
valid=False,
|
|
114
|
+
errors=[f"Referenced file not found: {permissions_path_str}"],
|
|
115
|
+
))
|
|
116
|
+
|
|
117
|
+
# Validate skills
|
|
118
|
+
for skill_ref in manifest.get("skills", []):
|
|
119
|
+
manifest_rel = skill_ref.get("manifest")
|
|
120
|
+
if manifest_rel:
|
|
121
|
+
skill_path = agent_dir / manifest_rel
|
|
122
|
+
if skill_path.exists():
|
|
123
|
+
results.append(validate_file(skill_path, "skill"))
|
|
124
|
+
else:
|
|
125
|
+
results.append(ValidationResult(
|
|
126
|
+
file_path=skill_path,
|
|
127
|
+
schema_type="skill",
|
|
128
|
+
valid=False,
|
|
129
|
+
errors=[f"Referenced file not found: {manifest_rel}"],
|
|
130
|
+
))
|
|
131
|
+
|
|
132
|
+
runbook_rel = skill_ref.get("runbook")
|
|
133
|
+
if runbook_rel:
|
|
134
|
+
runbook_path = agent_dir / runbook_rel
|
|
135
|
+
if not runbook_path.exists():
|
|
136
|
+
results.append(ValidationResult(
|
|
137
|
+
file_path=runbook_path,
|
|
138
|
+
schema_type="skill",
|
|
139
|
+
valid=False,
|
|
140
|
+
errors=[f"Referenced runbook not found: {runbook_rel}"],
|
|
141
|
+
))
|
|
142
|
+
|
|
143
|
+
# Validate workflows
|
|
144
|
+
for wf_ref in manifest.get("workflows", []):
|
|
145
|
+
wf_path = agent_dir / wf_ref["path"]
|
|
146
|
+
if wf_path.exists():
|
|
147
|
+
results.append(validate_file(wf_path, "workflow"))
|
|
148
|
+
else:
|
|
149
|
+
results.append(ValidationResult(
|
|
150
|
+
file_path=wf_path,
|
|
151
|
+
schema_type="workflow",
|
|
152
|
+
valid=False,
|
|
153
|
+
errors=[f"Referenced file not found: {wf_ref['path']}"],
|
|
154
|
+
))
|
|
155
|
+
|
|
156
|
+
# Validate registries
|
|
157
|
+
for reg_ref in manifest.get("registries", []):
|
|
158
|
+
reg_path = agent_dir / reg_ref["path"]
|
|
159
|
+
if reg_path.exists():
|
|
160
|
+
results.append(validate_file(reg_path, "registry"))
|
|
161
|
+
else:
|
|
162
|
+
results.append(ValidationResult(
|
|
163
|
+
file_path=reg_path,
|
|
164
|
+
schema_type="registry",
|
|
165
|
+
valid=False,
|
|
166
|
+
errors=[f"Referenced file not found: {reg_ref['path']}"],
|
|
167
|
+
))
|
|
168
|
+
|
|
169
|
+
# Check command files exist
|
|
170
|
+
for cmd_ref in manifest.get("commands", []):
|
|
171
|
+
cmd_path = agent_dir / cmd_ref["path"]
|
|
172
|
+
if not cmd_path.exists():
|
|
173
|
+
results.append(ValidationResult(
|
|
174
|
+
file_path=cmd_path,
|
|
175
|
+
schema_type="command",
|
|
176
|
+
valid=False,
|
|
177
|
+
errors=[f"Referenced command file not found: {cmd_ref['path']}"],
|
|
178
|
+
))
|
|
179
|
+
|
|
180
|
+
# Check instructions file exists
|
|
181
|
+
instructions_rel = agent_section.get("instructions")
|
|
182
|
+
if instructions_rel:
|
|
183
|
+
instructions_path = agent_dir / instructions_rel
|
|
184
|
+
if not instructions_path.exists():
|
|
185
|
+
results.append(ValidationResult(
|
|
186
|
+
file_path=instructions_path,
|
|
187
|
+
schema_type="instructions",
|
|
188
|
+
valid=False,
|
|
189
|
+
errors=[f"Instructions file not found: {instructions_rel}"],
|
|
190
|
+
))
|
|
191
|
+
|
|
192
|
+
# Validate skill dependency graph
|
|
193
|
+
results.extend(_validate_skill_graph(agent_dir, manifest))
|
|
194
|
+
|
|
195
|
+
# Quality checks (warnings)
|
|
196
|
+
results.extend(_validate_skill_quality(agent_dir, manifest))
|
|
197
|
+
|
|
198
|
+
return results
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _validate_skill_graph(
|
|
202
|
+
agent_dir: Path,
|
|
203
|
+
manifest: dict,
|
|
204
|
+
) -> List[ValidationResult]:
|
|
205
|
+
"""Validate depends_on/blocks references and detect cycles."""
|
|
206
|
+
results: List[ValidationResult] = []
|
|
207
|
+
|
|
208
|
+
# Collect all declared skill IDs from the manifest
|
|
209
|
+
declared_ids = {s["id"] for s in manifest.get("skills", []) if "id" in s}
|
|
210
|
+
if not declared_ids:
|
|
211
|
+
return results
|
|
212
|
+
|
|
213
|
+
# Build adjacency list for cycle detection (depends_on edges)
|
|
214
|
+
adjacency: dict = {sid: [] for sid in declared_ids}
|
|
215
|
+
|
|
216
|
+
for skill_ref in manifest.get("skills", []):
|
|
217
|
+
manifest_rel = skill_ref.get("manifest")
|
|
218
|
+
if not manifest_rel:
|
|
219
|
+
continue
|
|
220
|
+
skill_path = agent_dir / manifest_rel
|
|
221
|
+
if not skill_path.exists():
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
skill_data = load_yaml(skill_path)
|
|
226
|
+
except Exception:
|
|
227
|
+
continue
|
|
228
|
+
|
|
229
|
+
skill_id = skill_data.get("id", skill_ref.get("id", "unknown"))
|
|
230
|
+
|
|
231
|
+
# Check depends_on references
|
|
232
|
+
for dep in skill_data.get("depends_on", []):
|
|
233
|
+
dep_id = dep if isinstance(dep, str) else dep.get("skill", "")
|
|
234
|
+
if not dep_id:
|
|
235
|
+
continue
|
|
236
|
+
if dep_id not in declared_ids:
|
|
237
|
+
# Warning only — vendored skills may reference deps not
|
|
238
|
+
# yet installed in this project.
|
|
239
|
+
results.append(ValidationResult(
|
|
240
|
+
file_path=skill_path,
|
|
241
|
+
schema_type="skill",
|
|
242
|
+
valid=True,
|
|
243
|
+
errors=[f"depends_on references skill not in this project: '{dep_id}' (warning)"],
|
|
244
|
+
))
|
|
245
|
+
else:
|
|
246
|
+
adjacency.setdefault(skill_id, []).append(dep_id)
|
|
247
|
+
|
|
248
|
+
# Check blocks references (warnings only — blocked skills may
|
|
249
|
+
# exist in a larger system outside this project)
|
|
250
|
+
for blocked in skill_data.get("blocks", []):
|
|
251
|
+
if blocked not in declared_ids:
|
|
252
|
+
results.append(ValidationResult(
|
|
253
|
+
file_path=skill_path,
|
|
254
|
+
schema_type="skill",
|
|
255
|
+
valid=True,
|
|
256
|
+
errors=[f"blocks references skill not in this project: '{blocked}' (warning)"],
|
|
257
|
+
))
|
|
258
|
+
|
|
259
|
+
# Cycle detection via topological sort (Kahn's algorithm)
|
|
260
|
+
in_degree: dict = {sid: 0 for sid in declared_ids}
|
|
261
|
+
for sid, deps in adjacency.items():
|
|
262
|
+
for dep in deps:
|
|
263
|
+
if dep in in_degree:
|
|
264
|
+
in_degree[dep] += 1
|
|
265
|
+
|
|
266
|
+
queue = [sid for sid, deg in in_degree.items() if deg == 0]
|
|
267
|
+
visited = 0
|
|
268
|
+
while queue:
|
|
269
|
+
node = queue.pop(0)
|
|
270
|
+
visited += 1
|
|
271
|
+
for dep in adjacency.get(node, []):
|
|
272
|
+
if dep in in_degree:
|
|
273
|
+
in_degree[dep] -= 1
|
|
274
|
+
if in_degree[dep] == 0:
|
|
275
|
+
queue.append(dep)
|
|
276
|
+
|
|
277
|
+
if visited < len(declared_ids):
|
|
278
|
+
cycle_members = [sid for sid, deg in in_degree.items() if deg > 0]
|
|
279
|
+
results.append(ValidationResult(
|
|
280
|
+
file_path=agent_dir / "agent.yaml",
|
|
281
|
+
schema_type="skill",
|
|
282
|
+
valid=False,
|
|
283
|
+
errors=[
|
|
284
|
+
f"Circular dependency detected among skills: {', '.join(sorted(cycle_members))}"
|
|
285
|
+
],
|
|
286
|
+
))
|
|
287
|
+
|
|
288
|
+
return results
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _validate_skill_quality(
|
|
292
|
+
agent_dir: Path,
|
|
293
|
+
manifest: dict,
|
|
294
|
+
) -> List[ValidationResult]:
|
|
295
|
+
"""Quality checks for skills (warnings only, valid=True).
|
|
296
|
+
|
|
297
|
+
These catch common issues: bad descriptions, oversized runbooks,
|
|
298
|
+
empty tags, and excessive skill counts.
|
|
299
|
+
"""
|
|
300
|
+
results: List[ValidationResult] = []
|
|
301
|
+
|
|
302
|
+
skills = manifest.get("skills", [])
|
|
303
|
+
skill_count = len(skills)
|
|
304
|
+
if skill_count > 50:
|
|
305
|
+
results.append(ValidationResult(
|
|
306
|
+
file_path=agent_dir / "agent.yaml",
|
|
307
|
+
schema_type="skill",
|
|
308
|
+
valid=True,
|
|
309
|
+
errors=[
|
|
310
|
+
f"Project has {skill_count} skills; recommended maximum is 50 (warning)"
|
|
311
|
+
],
|
|
312
|
+
))
|
|
313
|
+
|
|
314
|
+
for skill_ref in skills:
|
|
315
|
+
manifest_rel = skill_ref.get("manifest")
|
|
316
|
+
if not manifest_rel:
|
|
317
|
+
continue
|
|
318
|
+
skill_path = agent_dir / manifest_rel
|
|
319
|
+
if not skill_path.exists():
|
|
320
|
+
continue
|
|
321
|
+
|
|
322
|
+
try:
|
|
323
|
+
skill_data = load_yaml(skill_path)
|
|
324
|
+
except Exception:
|
|
325
|
+
continue
|
|
326
|
+
|
|
327
|
+
skill_id = skill_data.get("id", skill_ref.get("id", "unknown"))
|
|
328
|
+
desc = skill_data.get("description", "")
|
|
329
|
+
|
|
330
|
+
# Description contains TODO
|
|
331
|
+
if "TODO" in desc:
|
|
332
|
+
results.append(ValidationResult(
|
|
333
|
+
file_path=skill_path,
|
|
334
|
+
schema_type="skill",
|
|
335
|
+
valid=True,
|
|
336
|
+
errors=[
|
|
337
|
+
f"Skill '{skill_id}' description contains TODO (warning)"
|
|
338
|
+
],
|
|
339
|
+
))
|
|
340
|
+
elif len(desc) < 20:
|
|
341
|
+
results.append(ValidationResult(
|
|
342
|
+
file_path=skill_path,
|
|
343
|
+
schema_type="skill",
|
|
344
|
+
valid=True,
|
|
345
|
+
errors=[
|
|
346
|
+
f"Skill '{skill_id}' description is only {len(desc)} chars; "
|
|
347
|
+
f"aim for 20+ chars (warning)"
|
|
348
|
+
],
|
|
349
|
+
))
|
|
350
|
+
|
|
351
|
+
if len(desc) > 1024:
|
|
352
|
+
results.append(ValidationResult(
|
|
353
|
+
file_path=skill_path,
|
|
354
|
+
schema_type="skill",
|
|
355
|
+
valid=True,
|
|
356
|
+
errors=[
|
|
357
|
+
f"Skill '{skill_id}' description is {len(desc)} chars; "
|
|
358
|
+
f"maximum recommended is 1024 (warning)"
|
|
359
|
+
],
|
|
360
|
+
))
|
|
361
|
+
|
|
362
|
+
# Empty tags check
|
|
363
|
+
tags = skill_data.get("tags", [])
|
|
364
|
+
if isinstance(tags, list) and any(
|
|
365
|
+
isinstance(t, str) and not t.strip() for t in tags
|
|
366
|
+
):
|
|
367
|
+
results.append(ValidationResult(
|
|
368
|
+
file_path=skill_path,
|
|
369
|
+
schema_type="skill",
|
|
370
|
+
valid=True,
|
|
371
|
+
errors=[
|
|
372
|
+
f"Skill '{skill_id}' has empty tag values (warning)"
|
|
373
|
+
],
|
|
374
|
+
))
|
|
375
|
+
|
|
376
|
+
# Runbook size check
|
|
377
|
+
runbook_rel = skill_ref.get("runbook")
|
|
378
|
+
if runbook_rel:
|
|
379
|
+
runbook_path = agent_dir / runbook_rel
|
|
380
|
+
if runbook_path.exists():
|
|
381
|
+
runbook_text = runbook_path.read_text()
|
|
382
|
+
word_count = len(runbook_text.split())
|
|
383
|
+
if word_count > 5000:
|
|
384
|
+
results.append(ValidationResult(
|
|
385
|
+
file_path=runbook_path,
|
|
386
|
+
schema_type="skill",
|
|
387
|
+
valid=True,
|
|
388
|
+
errors=[
|
|
389
|
+
f"Skill '{skill_id}' runbook is {word_count} words; "
|
|
390
|
+
f"recommended maximum is 5000 (warning)"
|
|
391
|
+
],
|
|
392
|
+
))
|
|
393
|
+
|
|
394
|
+
return results
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aes-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: CLI tool for the Agentic Engineering Standard
|
|
5
|
+
Author: Hiro
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://aes-official.com
|
|
8
|
+
Project-URL: Repository, https://github.com/user-1221/agentic-engineering-standard
|
|
9
|
+
Project-URL: Documentation, https://github.com/user-1221/agentic-engineering-standard/tree/main/spec
|
|
10
|
+
Keywords: agentic,ai,engineering,standard,cli
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: click>=8.0
|
|
23
|
+
Requires-Dist: pyyaml>=6.0
|
|
24
|
+
Requires-Dist: jsonschema>=4.17
|
|
25
|
+
Requires-Dist: jinja2>=3.1
|
|
26
|
+
Requires-Dist: rich>=13.0
|
|
27
|
+
Requires-Dist: mcp>=1.2.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-tmp-files>=0.0.2; extra == "dev"
|
|
31
|
+
|
|
32
|
+
# aes-cli
|
|
33
|
+
|
|
34
|
+
CLI tool for the [Agentic Engineering Standard](https://github.com/user-1221/agentic-engineering-standard) (AES) — an open standard for structuring, sharing, and discovering agentic engineering projects.
|
|
35
|
+
|
|
36
|
+
AES treats agent instructions, skills, permissions, and memory as **first-class engineering artifacts**, making them portable, composable, and shareable across AI coding tools.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install aes-cli
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Requires Python 3.10+.
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
### Initialize a new project
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
aes init
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Interactive wizard that scaffolds a `.agent/` directory with agent config, skills, permissions, and memory. Supports multiple domains (web, ML, DevOps, research) and modes (dev-assist, agent-integrated).
|
|
55
|
+
|
|
56
|
+
### Validate a project
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
aes validate .
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Checks `.agent/` files against the AES JSON Schema, validates dependency graphs, and reports errors/warnings.
|
|
63
|
+
|
|
64
|
+
### Sync to your AI tool
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
aes sync -t claude # or: cursor, copilot, windsurf
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Generates tool-specific config from your `.agent/` directory. Write once, use with any supported AI coding tool.
|
|
71
|
+
|
|
72
|
+
### Publish & install skills
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
aes publish ./my-skill # share a skill or template to the AES registry
|
|
76
|
+
aes install user/skill # install a skill into your project
|
|
77
|
+
aes search "deploy" # search the registry
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Inspect a project
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
aes inspect .
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Displays a summary of the project's agent configuration: skills, workflows, permissions, and dependencies.
|
|
87
|
+
|
|
88
|
+
## Commands
|
|
89
|
+
|
|
90
|
+
| Command | Description |
|
|
91
|
+
|-------------|------------------------------------------------|
|
|
92
|
+
| `init` | Scaffold a new `.agent/` directory |
|
|
93
|
+
| `validate` | Validate against the AES spec |
|
|
94
|
+
| `sync` | Generate tool-specific config |
|
|
95
|
+
| `publish` | Publish a skill or template to the registry |
|
|
96
|
+
| `install` | Install a skill or template from the registry |
|
|
97
|
+
| `search` | Search the AES registry |
|
|
98
|
+
| `inspect` | Inspect project agent configuration |
|
|
99
|
+
| `status` | Show sync status and drift |
|
|
100
|
+
|
|
101
|
+
## Links
|
|
102
|
+
|
|
103
|
+
- [Specification](https://github.com/user-1221/agentic-engineering-standard/tree/main/spec)
|
|
104
|
+
- [Examples](https://github.com/user-1221/agentic-engineering-standard/tree/main/examples)
|
|
105
|
+
- [Templates](https://github.com/user-1221/agentic-engineering-standard/tree/main/templates)
|
|
106
|
+
- [Registry](https://registry.aes-official.com)
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
Apache 2.0
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
aes/__init__.py,sha256=T-5H_3v2SiQQKJO-URTTN28kOLV_d9nw3bQzhCKSxTw,133
|
|
2
|
+
aes/__main__.py,sha256=SzAOz9p2crOaL1Rt12p8_zyM9nJ1ipd8w4SyeUu2z7w,984
|
|
3
|
+
aes/analyzer.py,sha256=N2xjQHA0iW4jV-5pw4w9fuKlwErcti0sroXXWlaONe4,15633
|
|
4
|
+
aes/config.py,sha256=gKH2WllUcEzp_igURXcqNUzrYmT5KLBfsT1SQkcdW4k,1281
|
|
5
|
+
aes/domains.py,sha256=p8ChOwAkdUnMv1KOoPJ0szmHMcnOB28nOU4tuGWXtbY,92031
|
|
6
|
+
aes/frameworks.py,sha256=ajLBIplnh8yCISEdWsvsHyGJxcLh0OjxpAzB4eJ_wdc,26450
|
|
7
|
+
aes/mcp_server.py,sha256=8eYdf7w2Nia7y8alaOvnXr8RnTpQguOn_6Jz3o-NFVA,6267
|
|
8
|
+
aes/registry.py,sha256=DxX3vxYuzJiQWDa6N1_cA9f1IzkQZ9JHgzmrmdhI1ak,8910
|
|
9
|
+
aes/validator.py,sha256=5ulJUCSmrgOYoubVrasjTUgOru0nTbQOh7wTrx6UtzM,13339
|
|
10
|
+
aes/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
aes/commands/init.py,sha256=KR2600c-D83yc7GtUVq7-c1iBblv57MBla3Ljd0IDNY,26648
|
|
12
|
+
aes/commands/inspect.py,sha256=cgl1VwPAda5M5RCCX3wO2R12MzSH3VOVul8441pogpU,7295
|
|
13
|
+
aes/commands/install.py,sha256=M3r_sJg4nji-_Hmh30AUxzrgcwWtompgh-T0N2beZXU,13093
|
|
14
|
+
aes/commands/publish.py,sha256=wSvAWEhVqIl7YqPr5v7BL0SSq5uLr8VqwjEBIMnrCgI,15517
|
|
15
|
+
aes/commands/search.py,sha256=FznTjCUDRsb9W9Eu9DhpV2myJAGUliORqPOiiroXCRI,2240
|
|
16
|
+
aes/commands/status.py,sha256=EdeGyh5QbFdm_WiflkfrwmaxSBApD4pAsgAP7vbVsr0,5146
|
|
17
|
+
aes/commands/sync.py,sha256=G_N33r73yP25-F3k7DB_kxFKkujetDIMSSePkVav6x4,13644
|
|
18
|
+
aes/commands/validate.py,sha256=bZPca3F5R2PlQCQw2i-nBtzMCDgwCx5ppGI_T-JZ0-U,2431
|
|
19
|
+
aes/scaffold/agent.yaml.jinja,sha256=BFHyj4O73ZQeq4l8ZLsPHe7Xeth74uI9VwYa0va7-fk,3371
|
|
20
|
+
aes/scaffold/agentignore.jinja,sha256=HM_sqvAyfuDrpivAAISIpVRDGKAqLo-nZUITTovMQkM,818
|
|
21
|
+
aes/scaffold/instructions.md.jinja,sha256=2oJWZnO-2VMPJ92enSVql4wqtGGbYiqbMFSOcv7vBUM,7969
|
|
22
|
+
aes/scaffold/local.example.yaml.jinja,sha256=sSLREMH9c3-aKNB5QWL4RVr_7lBNQskdCQeqavR-Eiw,1122
|
|
23
|
+
aes/scaffold/local.yaml.jinja,sha256=pt_qXV3a5J3wqEs9G8wglcR0EeUK_efw0PL1BCC4esk,863
|
|
24
|
+
aes/scaffold/operations.md.jinja,sha256=eed8bKgCyjJFBwMi0rUXnwRUjwK91YLwrPXq8F9v0N0,1118
|
|
25
|
+
aes/scaffold/orchestrator.md.jinja,sha256=lL6oYIHMItWSUSjoGe3-VBDpL7V_opZgZWEse9zWL5Y,2615
|
|
26
|
+
aes/scaffold/permissions.yaml.jinja,sha256=saS0tijYjIsNhPI_aBQZhMPPfyWhIGuhR3HaU70WQBI,3915
|
|
27
|
+
aes/scaffold/setup.md.jinja,sha256=Ta759rfuj1f-MTL2q5fXc5y6Rz1OK037zRvyG3cSsAI,10399
|
|
28
|
+
aes/scaffold/skill.md.jinja,sha256=No84nmEVxtS3wmvXt4FZ-la_5ko64SQ7boXQCIvcxSE,324
|
|
29
|
+
aes/scaffold/skill.yaml.jinja,sha256=D5APq2fJjLOOwCXdoN2tggOc6H_5bv4cGZycGa9X71w,5421
|
|
30
|
+
aes/scaffold/workflow.yaml.jinja,sha256=98wJxr_V7LgH2OsXEROvn6FvR97KaATQa5cPNLmnE_Y,867
|
|
31
|
+
aes/scaffold/workflow_command.md.jinja,sha256=CJWNgS--XhzE4jc9b3Hl65pufxO8upn5GlWc0hElmQY,1714
|
|
32
|
+
aes/schemas/agent.schema.json,sha256=8tnW1gGV8ZGzOKrX937ttoO3GrahLIg_TVYrdUQPE3k,4904
|
|
33
|
+
aes/schemas/permissions.schema.json,sha256=gw9kiW4KFP8QryQ2ox0mNvay9QqltTDGrOKfI3YUTWs,2822
|
|
34
|
+
aes/schemas/registry.schema.json,sha256=MNW3hMcl9zMHjOzZxKio4s6PoYcltAf5G1adZSP9tr0,1793
|
|
35
|
+
aes/schemas/skill.schema.json,sha256=l0LiVaM5QTSbMdzK0o88U8iYbU_a2G4KffX-BCIZNKE,5417
|
|
36
|
+
aes/schemas/workflow.schema.json,sha256=hhjvF316AjwkUDzhpuexS53g180QxlZyPjy1snIX9Ko,2425
|
|
37
|
+
aes/targets/__init__.py,sha256=OtBpr0bHIe7JcqO0aGUdmkwkK34ILX_duvpEd6Vbs0g,702
|
|
38
|
+
aes/targets/_base.py,sha256=nROseIPfBwFjwAQsL67vhjRaxtpk4ldj7xr5SGzqxTM,2142
|
|
39
|
+
aes/targets/_composer.py,sha256=LN5qBGHtYDt4XjCRA6Yy6YDRSz1tgYMffoXG9XwUHIw,11932
|
|
40
|
+
aes/targets/claude.py,sha256=_JqHTtgkS6iTMmzY054iL9n4FhN0LcF0j6Aq0pgKhgk,5131
|
|
41
|
+
aes/targets/copilot.py,sha256=uWTo2W3x_nI8fhJm4pnafj8kwvTt1m_FhQiS7WZCuIc,1502
|
|
42
|
+
aes/targets/cursor.py,sha256=6vscINmBY2Lr4uKRflPKbVZ2kNvcll8S8d6usCT7LGE,1414
|
|
43
|
+
aes/targets/windsurf.py,sha256=gAiYSx_qLyJkCfIaqomeOFRO97FeOZc3BwjvVoOB8E8,1428
|
|
44
|
+
aes_cli-0.2.0.dist-info/METADATA,sha256=XNgCsem5a8kBPuVnm0I6TzptdRg6zOj0IZGNXXDWVf0,3731
|
|
45
|
+
aes_cli-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
46
|
+
aes_cli-0.2.0.dist-info/entry_points.txt,sha256=gEFtzc7_YoR8A5Wp_5L5boCDce5LEfv9wGrddZ8TaFU,71
|
|
47
|
+
aes_cli-0.2.0.dist-info/top_level.txt,sha256=Meqs3utRZP-ectIZQrPjj4UOpME-5QJ4fiOCM6ebUos,4
|
|
48
|
+
aes_cli-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aes
|