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.
Files changed (48) hide show
  1. aes/__init__.py +5 -0
  2. aes/__main__.py +37 -0
  3. aes/analyzer.py +487 -0
  4. aes/commands/__init__.py +0 -0
  5. aes/commands/init.py +727 -0
  6. aes/commands/inspect.py +204 -0
  7. aes/commands/install.py +379 -0
  8. aes/commands/publish.py +432 -0
  9. aes/commands/search.py +65 -0
  10. aes/commands/status.py +153 -0
  11. aes/commands/sync.py +413 -0
  12. aes/commands/validate.py +77 -0
  13. aes/config.py +43 -0
  14. aes/domains.py +1382 -0
  15. aes/frameworks.py +522 -0
  16. aes/mcp_server.py +213 -0
  17. aes/registry.py +294 -0
  18. aes/scaffold/agent.yaml.jinja +135 -0
  19. aes/scaffold/agentignore.jinja +61 -0
  20. aes/scaffold/instructions.md.jinja +311 -0
  21. aes/scaffold/local.example.yaml.jinja +35 -0
  22. aes/scaffold/local.yaml.jinja +29 -0
  23. aes/scaffold/operations.md.jinja +33 -0
  24. aes/scaffold/orchestrator.md.jinja +95 -0
  25. aes/scaffold/permissions.yaml.jinja +151 -0
  26. aes/scaffold/setup.md.jinja +244 -0
  27. aes/scaffold/skill.md.jinja +27 -0
  28. aes/scaffold/skill.yaml.jinja +175 -0
  29. aes/scaffold/workflow.yaml.jinja +44 -0
  30. aes/scaffold/workflow_command.md.jinja +48 -0
  31. aes/schemas/agent.schema.json +188 -0
  32. aes/schemas/permissions.schema.json +100 -0
  33. aes/schemas/registry.schema.json +72 -0
  34. aes/schemas/skill.schema.json +209 -0
  35. aes/schemas/workflow.schema.json +92 -0
  36. aes/targets/__init__.py +29 -0
  37. aes/targets/_base.py +77 -0
  38. aes/targets/_composer.py +338 -0
  39. aes/targets/claude.py +153 -0
  40. aes/targets/copilot.py +48 -0
  41. aes/targets/cursor.py +46 -0
  42. aes/targets/windsurf.py +46 -0
  43. aes/validator.py +394 -0
  44. aes_cli-0.2.0.dist-info/METADATA +110 -0
  45. aes_cli-0.2.0.dist-info/RECORD +48 -0
  46. aes_cli-0.2.0.dist-info/WHEEL +5 -0
  47. aes_cli-0.2.0.dist-info/entry_points.txt +3 -0
  48. aes_cli-0.2.0.dist-info/top_level.txt +1 -0
aes/mcp_server.py ADDED
@@ -0,0 +1,213 @@
1
+ """AES MCP Server — Model Context Protocol server for the AES registry."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ import json
7
+ import logging
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Optional
11
+
12
+ from mcp.server.fastmcp import FastMCP
13
+
14
+ from aes.config import AGENT_DIR
15
+ from aes.registry import fetch_index, search_packages
16
+ from aes.validator import validate_agent_dir
17
+
18
+ # Logging must go to stderr — stdout is the JSON-RPC channel
19
+ logging.basicConfig(level=logging.INFO, stream=sys.stderr)
20
+ logger = logging.getLogger("aes-mcp")
21
+
22
+ mcp = FastMCP("aes-registry")
23
+
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Tools
27
+ # ---------------------------------------------------------------------------
28
+
29
+
30
+ @mcp.tool()
31
+ def aes_search(
32
+ query: str = "",
33
+ tag: str = "",
34
+ domain: str = "",
35
+ pkg_type: str = "",
36
+ ) -> str:
37
+ """Search the AES package registry for skills and templates.
38
+
39
+ Args:
40
+ query: Keyword to search package names and descriptions.
41
+ tag: Filter by tag (e.g. "ml", "devops").
42
+ domain: Filter by domain (convention: domain as tag).
43
+ pkg_type: Filter by type: "skill" or "template".
44
+
45
+ Returns a JSON array of matching packages.
46
+ """
47
+ try:
48
+ results = search_packages(
49
+ query=query or "",
50
+ tag=tag or None,
51
+ domain=domain or None,
52
+ pkg_type=pkg_type or None,
53
+ )
54
+ return json.dumps(results, indent=2)
55
+ except Exception as exc:
56
+ return json.dumps({"error": f"Registry search failed: {exc}"})
57
+
58
+
59
+ @mcp.tool()
60
+ def aes_inspect(name: str) -> str:
61
+ """Show details about a specific package in the AES registry.
62
+
63
+ Returns all versions, description, tags, and metadata for the package.
64
+
65
+ Args:
66
+ name: Package name to inspect.
67
+ """
68
+ try:
69
+ index = fetch_index()
70
+ except Exception as exc:
71
+ return json.dumps({"error": f"Failed to fetch registry index: {exc}"})
72
+
73
+ packages = index.get("packages", {})
74
+ if name not in packages:
75
+ return json.dumps({"error": f"Package '{name}' not found in registry."})
76
+
77
+ pkg = packages[name]
78
+ return json.dumps(
79
+ {
80
+ "name": name,
81
+ "type": pkg.get("type", "skill"),
82
+ "description": pkg.get("description", ""),
83
+ "tags": pkg.get("tags", []),
84
+ "latest": pkg.get("latest", ""),
85
+ "versions": pkg.get("versions", {}),
86
+ },
87
+ indent=2,
88
+ )
89
+
90
+
91
+ @mcp.tool()
92
+ def aes_install(
93
+ source: str,
94
+ project_path: str = ".",
95
+ force: bool = False,
96
+ ) -> str:
97
+ """Install a skill or template from the AES registry into the project.
98
+
99
+ Installs into the .agent/skills/vendor/ directory and registers in agent.yaml.
100
+
101
+ Args:
102
+ source: Package source — registry ref (e.g. "aes-hub/deploy@^1.0"),
103
+ local path, or tarball path.
104
+ project_path: Project root directory (defaults to current directory).
105
+ force: Overwrite existing vendor skills if True.
106
+ """
107
+ import click
108
+
109
+ from aes.commands.install import (
110
+ _detect_source_type,
111
+ _install_local,
112
+ _install_registry,
113
+ _install_tarball,
114
+ )
115
+
116
+ project_root = Path(project_path).resolve()
117
+ agent_dir = project_root / AGENT_DIR
118
+
119
+ if not agent_dir.exists():
120
+ return json.dumps(
121
+ {"error": f"No {AGENT_DIR}/ directory found at {project_root}"}
122
+ )
123
+
124
+ source_type = _detect_source_type(source)
125
+
126
+ if source_type == "git":
127
+ return json.dumps({"error": "Git sources are not yet supported."})
128
+ if source_type == "unknown":
129
+ return json.dumps(
130
+ {"error": f"Cannot determine source type for '{source}'."}
131
+ )
132
+
133
+ try:
134
+ # Redirect stdout to stderr so rich.console.print doesn't corrupt
135
+ # the stdio JSON-RPC channel.
136
+ with contextlib.redirect_stdout(sys.stderr):
137
+ if source_type == "registry":
138
+ skill_id = _install_registry(source, project_root, force)
139
+ elif source_type == "tarball":
140
+ skill_id = _install_tarball(
141
+ Path(source).resolve(), project_root, force
142
+ )
143
+ elif source_type == "local":
144
+ skill_id = _install_local(source, project_root, force)
145
+ else:
146
+ return json.dumps({"error": f"Unsupported source type: {source_type}"})
147
+
148
+ return json.dumps(
149
+ {
150
+ "installed": skill_id,
151
+ "vendor_path": f".agent/skills/vendor/{skill_id}",
152
+ }
153
+ )
154
+ except click.ClickException as exc:
155
+ return json.dumps({"error": exc.format_message()})
156
+ except Exception as exc:
157
+ return json.dumps({"error": str(exc)})
158
+
159
+
160
+ @mcp.tool()
161
+ def aes_validate(project_path: str = ".") -> str:
162
+ """Validate the .agent/ directory against AES schemas.
163
+
164
+ Reports pass/fail for each file and any validation errors.
165
+
166
+ Args:
167
+ project_path: Project root directory (defaults to current directory).
168
+ """
169
+ project_root = Path(project_path).resolve()
170
+ agent_dir = project_root / AGENT_DIR
171
+
172
+ if not agent_dir.exists():
173
+ return json.dumps(
174
+ {"error": f"No {AGENT_DIR}/ directory found at {project_root}"}
175
+ )
176
+
177
+ try:
178
+ results = validate_agent_dir(agent_dir)
179
+ except Exception as exc:
180
+ return json.dumps({"error": f"Validation failed: {exc}"})
181
+
182
+ passed = sum(1 for r in results if r.valid)
183
+ failed = sum(1 for r in results if not r.valid)
184
+
185
+ return json.dumps(
186
+ {
187
+ "summary": {"passed": passed, "failed": failed, "total": len(results)},
188
+ "results": [
189
+ {
190
+ "file": str(r.file_path),
191
+ "schema": r.schema_type,
192
+ "valid": r.valid,
193
+ "errors": r.errors,
194
+ }
195
+ for r in results
196
+ ],
197
+ },
198
+ indent=2,
199
+ )
200
+
201
+
202
+ # ---------------------------------------------------------------------------
203
+ # Entry point
204
+ # ---------------------------------------------------------------------------
205
+
206
+
207
+ def main() -> None:
208
+ """Run the AES MCP server over stdio."""
209
+ mcp.run(transport="stdio")
210
+
211
+
212
+ if __name__ == "__main__":
213
+ main()
aes/registry.py ADDED
@@ -0,0 +1,294 @@
1
+ """Registry client — fetch index, resolve versions, download packages."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import os
8
+ import re
9
+ import urllib.request
10
+ import urllib.error
11
+ from pathlib import Path
12
+ from typing import Dict, List, Optional, Tuple
13
+
14
+ # Default registry URL (R2/S3 public bucket)
15
+ REGISTRY_URL = os.environ.get("AES_REGISTRY_URL", "https://registry.aes-official.com")
16
+ INDEX_PATH = "index.json"
17
+ PACKAGES_PATH = "packages"
18
+
19
+
20
+ def _parse_version(v: str) -> Tuple[int, int, int]:
21
+ """Parse a semver string into (major, minor, patch)."""
22
+ match = re.match(r"^(\d+)\.(\d+)\.(\d+)", v)
23
+ if not match:
24
+ raise ValueError(f"Invalid semver: {v}")
25
+ return int(match.group(1)), int(match.group(2)), int(match.group(3))
26
+
27
+
28
+ def _version_matches(version: str, spec: str) -> bool:
29
+ """Check if *version* satisfies *spec*.
30
+
31
+ Supports: exact "1.2.3", caret "^1.2.0", tilde "~1.2.0",
32
+ minimum ">=1.0.0", wildcard "*".
33
+ """
34
+ if spec == "*":
35
+ return True
36
+
37
+ ver = _parse_version(version)
38
+
39
+ if spec.startswith("^"):
40
+ base = _parse_version(spec[1:])
41
+ # >=base, <next_major (or <next_minor if major==0)
42
+ if base[0] == 0:
43
+ ceiling = (0, base[1] + 1, 0)
44
+ else:
45
+ ceiling = (base[0] + 1, 0, 0)
46
+ return base <= ver < ceiling
47
+
48
+ if spec.startswith("~"):
49
+ base = _parse_version(spec[1:])
50
+ ceiling = (base[0], base[1] + 1, 0)
51
+ return base <= ver < ceiling
52
+
53
+ if spec.startswith(">="):
54
+ base = _parse_version(spec[2:])
55
+ return ver >= base
56
+
57
+ if spec.startswith(">"):
58
+ base = _parse_version(spec[1:])
59
+ return ver > base
60
+
61
+ if spec.startswith("<="):
62
+ base = _parse_version(spec[2:])
63
+ return ver <= base
64
+
65
+ if spec.startswith("<"):
66
+ base = _parse_version(spec[1:])
67
+ return ver < base
68
+
69
+ # Exact match
70
+ return ver == _parse_version(spec)
71
+
72
+
73
+ def resolve_version(spec: str, available: List[str]) -> Optional[str]:
74
+ """Find the best (highest) version matching *spec*."""
75
+ matches = [v for v in available if _version_matches(v, spec)]
76
+ if not matches:
77
+ return None
78
+ matches.sort(key=_parse_version)
79
+ return matches[-1]
80
+
81
+
82
+ _NAME_RE = re.compile(r"^[a-z][a-z0-9_-]{0,63}$")
83
+
84
+
85
+ def parse_registry_source(source: str) -> Tuple[str, str]:
86
+ """Parse ``aes-hub/deploy@^1.2.0`` into (name, version_spec).
87
+
88
+ Returns (name, "*") if no version constraint given.
89
+ Raises ValueError if name is invalid.
90
+ """
91
+ # Strip prefix
92
+ name_ver = source
93
+ if "/" in name_ver:
94
+ name_ver = name_ver.split("/", 1)[1]
95
+
96
+ if "@" in name_ver:
97
+ name, version_spec = name_ver.split("@", 1)
98
+ else:
99
+ name = name_ver
100
+ version_spec = "*"
101
+
102
+ if not _NAME_RE.match(name):
103
+ raise ValueError(
104
+ f"Invalid package name {name!r}: must be 1-64 lowercase chars "
105
+ "starting with a letter (a-z, 0-9, _, -)"
106
+ )
107
+
108
+ return name, version_spec
109
+
110
+
111
+ def fetch_index(registry_url: Optional[str] = None) -> dict:
112
+ """Fetch and parse the registry ``index.json``."""
113
+ base = registry_url or os.environ.get("AES_REGISTRY_URL", REGISTRY_URL)
114
+ url = f"{base.rstrip('/')}/{INDEX_PATH}"
115
+
116
+ req = urllib.request.Request(url)
117
+ token = os.environ.get("AES_REGISTRY_KEY")
118
+ if token:
119
+ req.add_header("Authorization", f"Bearer {token}")
120
+
121
+ with urllib.request.urlopen(req, timeout=30) as resp:
122
+ return json.loads(resp.read().decode())
123
+
124
+
125
+ def download_package(
126
+ name: str,
127
+ version: str,
128
+ sha256_expected: str,
129
+ dest: Path,
130
+ registry_url: Optional[str] = None,
131
+ ) -> Path:
132
+ """Download a tarball from the registry and verify its sha256.
133
+
134
+ Returns the local path to the downloaded file.
135
+ """
136
+ base = registry_url or os.environ.get("AES_REGISTRY_URL", REGISTRY_URL)
137
+ url = f"{base.rstrip('/')}/{PACKAGES_PATH}/{name}/{version}.tar.gz"
138
+
139
+ tarball_path = dest / f"{name}-{version}.tar.gz"
140
+
141
+ req = urllib.request.Request(url)
142
+ token = os.environ.get("AES_REGISTRY_KEY")
143
+ if token:
144
+ req.add_header("Authorization", f"Bearer {token}")
145
+
146
+ with urllib.request.urlopen(req, timeout=120) as resp:
147
+ data = resp.read()
148
+
149
+ actual_sha = hashlib.sha256(data).hexdigest()
150
+ if actual_sha != sha256_expected:
151
+ raise ValueError(
152
+ f"SHA256 mismatch for {name}@{version}: "
153
+ f"expected {sha256_expected[:16]}..., got {actual_sha[:16]}..."
154
+ )
155
+
156
+ tarball_path.write_bytes(data)
157
+ return tarball_path
158
+
159
+
160
+ def upload_package(
161
+ tarball_path: Path,
162
+ name: str,
163
+ version: str,
164
+ description: str,
165
+ tags: Optional[List[str]] = None,
166
+ registry_url: Optional[str] = None,
167
+ pkg_type: str = "skill",
168
+ visibility: str = "public",
169
+ ) -> dict:
170
+ """Upload a tarball and update the registry index.
171
+
172
+ Returns the updated index entry for this package.
173
+ """
174
+ base = registry_url or os.environ.get("AES_REGISTRY_URL", REGISTRY_URL)
175
+ token = os.environ.get("AES_REGISTRY_KEY")
176
+ if not token:
177
+ raise RuntimeError(
178
+ "AES_REGISTRY_KEY environment variable is required for publishing. "
179
+ "Set it to your registry API token."
180
+ )
181
+
182
+ tarball_data = tarball_path.read_bytes()
183
+ sha = hashlib.sha256(tarball_data).hexdigest()
184
+
185
+ # Upload tarball
186
+ upload_url = f"{base.rstrip('/')}/{PACKAGES_PATH}/{name}/{version}.tar.gz"
187
+ req = urllib.request.Request(upload_url, data=tarball_data, method="PUT")
188
+ req.add_header("Authorization", f"Bearer {token}")
189
+ req.add_header("Content-Type", "application/gzip")
190
+ try:
191
+ urllib.request.urlopen(req, timeout=120)
192
+ except urllib.error.HTTPError as exc:
193
+ if exc.code == 409:
194
+ raise RuntimeError(
195
+ f"Version {version} of '{name}' already exists in the registry. "
196
+ "Bump the version in your skill manifest and try again."
197
+ ) from None
198
+ raise
199
+
200
+ # Fetch current index
201
+ try:
202
+ index = fetch_index(registry_url)
203
+ except (urllib.error.URLError, urllib.error.HTTPError):
204
+ index = {"packages": {}}
205
+
206
+ # Update index
207
+ packages = index.setdefault("packages", {})
208
+ pkg = packages.setdefault(name, {
209
+ "description": description,
210
+ "latest": version,
211
+ "versions": {},
212
+ })
213
+ pkg["description"] = description
214
+ pkg["type"] = pkg_type
215
+ pkg["visibility"] = visibility
216
+ if tags:
217
+ pkg["tags"] = tags
218
+
219
+ from datetime import datetime, timezone
220
+ pkg["versions"][version] = {
221
+ "url": f"{PACKAGES_PATH}/{name}/{version}.tar.gz",
222
+ "sha256": sha,
223
+ "published_at": datetime.now(timezone.utc).isoformat(),
224
+ }
225
+
226
+ # Update "latest" if this version is higher
227
+ try:
228
+ if _parse_version(version) >= _parse_version(pkg.get("latest", "0.0.0")):
229
+ pkg["latest"] = version
230
+ except ValueError:
231
+ pkg["latest"] = version
232
+
233
+ # Upload updated index
234
+ index_data = json.dumps(index, indent=2).encode()
235
+ index_url = f"{base.rstrip('/')}/{INDEX_PATH}"
236
+ req = urllib.request.Request(index_url, data=index_data, method="PUT")
237
+ req.add_header("Authorization", f"Bearer {token}")
238
+ req.add_header("Content-Type", "application/json")
239
+ urllib.request.urlopen(req, timeout=30)
240
+
241
+ return pkg
242
+
243
+
244
+ def search_packages(
245
+ query: str = "",
246
+ tag: Optional[str] = None,
247
+ domain: Optional[str] = None,
248
+ index: Optional[dict] = None,
249
+ registry_url: Optional[str] = None,
250
+ pkg_type: Optional[str] = None,
251
+ ) -> List[Dict[str, object]]:
252
+ """Search the registry index for matching packages.
253
+
254
+ Returns list of dicts with: name, description, latest, tags, type.
255
+ """
256
+ if index is None:
257
+ index = fetch_index(registry_url)
258
+
259
+ results = []
260
+ for name, pkg in index.get("packages", {}).items():
261
+ # Type filter (packages without type default to "skill")
262
+ if pkg_type:
263
+ if pkg.get("type", "skill") != pkg_type:
264
+ continue
265
+
266
+ # Keyword filter
267
+ if query:
268
+ q = query.lower()
269
+ haystack = f"{name} {pkg.get('description', '')}".lower()
270
+ if q not in haystack:
271
+ continue
272
+
273
+ # Tag filter
274
+ if tag:
275
+ pkg_tags = pkg.get("tags", [])
276
+ if tag.lower() not in [t.lower() for t in pkg_tags]:
277
+ continue
278
+
279
+ # Domain filter (convention: domain is a tag)
280
+ if domain:
281
+ pkg_tags = pkg.get("tags", [])
282
+ if domain.lower() not in [t.lower() for t in pkg_tags]:
283
+ continue
284
+
285
+ results.append({
286
+ "name": name,
287
+ "description": pkg.get("description", ""),
288
+ "latest": pkg.get("latest", "?"),
289
+ "tags": pkg.get("tags", []),
290
+ "type": pkg.get("type", "skill"),
291
+ "versions": list(pkg.get("versions", {}).keys()),
292
+ })
293
+
294
+ return results
@@ -0,0 +1,135 @@
1
+ {% if domain_config %}
2
+ # .agent/agent.yaml — AES Manifest
3
+ aes: "1.0"
4
+
5
+ name: "{{ name }}"
6
+ version: "0.1.0"
7
+ description: "{{ domain_config.instructions_description or 'TODO: describe what this agentic system does' }}"
8
+ domain: "{{ domain }}"
9
+ license: "MIT"
10
+
11
+ runtime:
12
+ language: "{{ language }}"
13
+
14
+ agent:
15
+ instructions: "instructions.md"
16
+ permissions: "permissions.yaml"
17
+ {% if has_skills %}
18
+ orchestrator: "skills/ORCHESTRATOR.md"
19
+ {% if domain_config.skills %}
20
+
21
+ skills:
22
+ {% for skill in domain_config.skills %}
23
+ - id: "{{ skill.id }}"
24
+ manifest: "skills/{{ skill.id }}.skill.yaml"
25
+ runbook: "skills/{{ skill.id }}.md"
26
+ {% endfor %}
27
+ {% else %}
28
+
29
+ skills: []
30
+ {% endif %}
31
+ {% endif %}
32
+ {% if has_workflows and domain_config.workflow %}
33
+
34
+ workflows:
35
+ - id: "{{ domain_config.workflow.id }}"
36
+ path: "workflows/{{ domain_config.workflow.id }}.yaml"
37
+ {% endif %}
38
+
39
+ commands:
40
+ - id: "setup"
41
+ path: "commands/setup.md"
42
+ trigger: "/setup"
43
+ description: "Review and customize .agent/ files for your project"
44
+ {% if domain_config.workflow_commands %}
45
+ {% for cmd in domain_config.workflow_commands %}
46
+ - id: "{{ cmd.id }}"
47
+ path: "commands/{{ cmd.id }}.md"
48
+ trigger: "{{ cmd.trigger }}"
49
+ description: "{{ cmd.description }}"
50
+ {% endfor %}
51
+ {% endif %}
52
+
53
+ environment:
54
+ {% if domain_config.env_required %}
55
+ required:
56
+ {% for env in domain_config.env_required %}
57
+ - name: "{{ env.name }}"
58
+ description: "{{ env.description }}"
59
+ {% endfor %}
60
+ {% else %}
61
+ required: []
62
+ {% endif %}
63
+ {% if domain_config.env_optional %}
64
+ optional:
65
+ {% for env in domain_config.env_optional %}
66
+ - name: "{{ env.name }}"
67
+ default: "{{ env.default }}"
68
+ description: "{{ env.description }}"
69
+ {% endfor %}
70
+ {% else %}
71
+ optional: []
72
+ {% endif %}
73
+ {% else %}
74
+ # .agent/agent.yaml — AES Manifest
75
+ aes: "1.0"
76
+
77
+ name: "{{ name }}"
78
+ version: "0.1.0"
79
+ # <!-- AGENT: Describe what this agentic system does in one sentence.
80
+ # If code exists: read README + entry point + package config.
81
+ # If greenfield: ask the user what they're building. -->
82
+ description: "TODO: describe what this agentic system does"
83
+ domain: "{{ domain }}"
84
+ license: "MIT"
85
+
86
+ runtime:
87
+ language: "{{ language }}"
88
+
89
+ agent:
90
+ instructions: "instructions.md"
91
+ permissions: "permissions.yaml"
92
+ {% if has_skills %}
93
+ orchestrator: "skills/ORCHESTRATOR.md"
94
+
95
+ skills: []
96
+ # - id: "example"
97
+ # manifest: "skills/example.skill.yaml"
98
+ # runbook: "skills/example.md"
99
+ {% endif %}
100
+ {% if has_registry %}
101
+
102
+ registries: []
103
+ # - id: "components"
104
+ # path: "registry/components.yaml"
105
+ # description: "Component definitions"
106
+ {% endif %}
107
+ {% if has_workflows %}
108
+
109
+ workflows: []
110
+ # - id: "pipeline"
111
+ # path: "workflows/pipeline.yaml"
112
+ {% endif %}
113
+
114
+ commands:
115
+ - id: "setup"
116
+ path: "commands/setup.md"
117
+ trigger: "/setup"
118
+ description: "Populate .agent/ files by analyzing the codebase"
119
+ # - id: "run"
120
+ # path: "commands/run.md"
121
+ # trigger: "/run"
122
+ # description: "Run the main workflow"
123
+
124
+ environment:
125
+ # <!-- AGENT: List required and optional environment variables.
126
+ # If code exists: scan for os.environ, process.env, .env, .env.example.
127
+ # If greenfield: ask "any API keys or config vars needed?" -->
128
+ required: []
129
+ # - name: "API_KEY"
130
+ # description: "Main API key"
131
+ optional: []
132
+ # - name: "TIMEOUT"
133
+ # default: "300"
134
+ # description: "Operation timeout"
135
+ {% endif %}
@@ -0,0 +1,61 @@
1
+ # .agentignore — Files agents should never read or modify
2
+
3
+ # Secrets
4
+ .env
5
+ .env.*
6
+ *.pem
7
+ *.key
8
+ *.p12
9
+ credentials.json
10
+
11
+ # Databases
12
+ *.sqlite3
13
+ *.db
14
+
15
+ # Binary artifacts
16
+ {% if language == "python" %}
17
+ *.joblib
18
+ *.pkl
19
+ *.h5
20
+ *.pt
21
+ {% elif language == "javascript" or language == "typescript" %}
22
+ *.wasm
23
+ {% endif %}
24
+
25
+ # Large data
26
+ data/**/*.parquet
27
+ data/**/*.csv
28
+
29
+ # Build artifacts
30
+ {% if language == "python" %}
31
+ dist/
32
+ build/
33
+ *.egg-info/
34
+ __pycache__/
35
+ *.pyc
36
+ .venv/
37
+ {% elif language == "javascript" or language == "typescript" %}
38
+ node_modules/
39
+ dist/
40
+ .next/
41
+ {% elif language == "go" %}
42
+ vendor/
43
+ {% elif language == "rust" %}
44
+ target/
45
+ {% elif language == "java" %}
46
+ target/
47
+ build/
48
+ *.class
49
+ *.jar
50
+ {% endif %}
51
+
52
+ # System
53
+ .git/
54
+ .DS_Store
55
+ Thumbs.db
56
+
57
+ # Agent session data
58
+ .agent/memory/sessions/
59
+
60
+ # Local config (not committed)
61
+ .agent/local.yaml