aes-cli 0.5.0__tar.gz → 0.7.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.
- {aes_cli-0.5.0 → aes_cli-0.7.0}/PKG-INFO +1 -1
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/__init__.py +1 -1
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/__main__.py +4 -0
- aes_cli-0.7.0/aes/commands/bom.py +131 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/commands/init.py +8 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/commands/inspect.py +42 -1
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/commands/publish.py +77 -9
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/commands/status.py +6 -1
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/commands/sync.py +30 -2
- aes_cli-0.7.0/aes/commands/upgrade.py +282 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/config.py +4 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/domains.py +201 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/i18n/_messages.py +49 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/i18n/ja.py +49 -0
- aes_cli-0.7.0/aes/migrations.py +72 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/agent.yaml.jinja +52 -2
- aes_cli-0.7.0/aes/scaffold/bom.yaml.jinja +27 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/instructions.md.jinja +4 -4
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/permissions.yaml.jinja +30 -2
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/skill.yaml.jinja +29 -2
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/workflow.yaml.jinja +1 -1
- aes_cli-0.7.0/aes/schemas/agent.schema.json +355 -0
- aes_cli-0.7.0/aes/schemas/bom.schema.json +77 -0
- aes_cli-0.7.0/aes/schemas/decision-record.schema.json +78 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/schemas/permissions.schema.json +86 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/schemas/skill.schema.json +58 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/targets/__init__.py +2 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/targets/_composer.py +286 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/targets/claude.py +11 -7
- aes_cli-0.7.0/aes/targets/openclaw.py +507 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/validator.py +12 -1
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes_cli.egg-info/PKG-INFO +1 -1
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes_cli.egg-info/SOURCES.txt +10 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/pyproject.toml +1 -1
- aes_cli-0.7.0/tests/test_bom.py +132 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/tests/test_init.py +3 -3
- aes_cli-0.7.0/tests/test_openclaw_target.py +262 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/tests/test_sync.py +34 -0
- aes_cli-0.7.0/tests/test_upgrade.py +430 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/tests/test_validate.py +326 -0
- aes_cli-0.5.0/aes/schemas/agent.schema.json +0 -188
- {aes_cli-0.5.0 → aes_cli-0.7.0}/README.md +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/analyzer.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/commands/__init__.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/commands/install.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/commands/search.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/commands/validate.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/frameworks.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/global_config.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/i18n/__init__.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/i18n/domains_ja.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/mcp_server.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/registry.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/agentignore.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/ja/instructions.md.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/ja/memory_command.md.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/ja/operations.md.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/ja/orchestrator.md.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/ja/setup.md.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/ja/skill.md.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/ja/workflow_command.md.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/local.example.yaml.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/local.yaml.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/memory_command.md.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/operations.md.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/orchestrator.md.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/setup.md.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/skill.md.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/scaffold/workflow_command.md.jinja +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/schemas/registry.schema.json +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/schemas/workflow.schema.json +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/targets/_base.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/targets/copilot.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/targets/cursor.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes/targets/windsurf.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes_cli.egg-info/dependency_links.txt +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes_cli.egg-info/entry_points.txt +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes_cli.egg-info/requires.txt +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/aes_cli.egg-info/top_level.txt +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/setup.cfg +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/tests/test_analyzer.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/tests/test_frameworks.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/tests/test_inspect.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/tests/test_install.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/tests/test_mcp_server.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/tests/test_publish.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/tests/test_registry.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/tests/test_search.py +0 -0
- {aes_cli-0.5.0 → aes_cli-0.7.0}/tests/test_status.py +0 -0
|
@@ -16,6 +16,8 @@ from aes.commands.install import install_cmd
|
|
|
16
16
|
from aes.commands.search import search_cmd
|
|
17
17
|
from aes.commands.status import status_cmd
|
|
18
18
|
from aes.commands.sync import sync_cmd
|
|
19
|
+
from aes.commands.upgrade import upgrade_cmd
|
|
20
|
+
from aes.commands.bom import bom_cmd
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
def _prompt_language() -> None:
|
|
@@ -77,6 +79,8 @@ cli.add_command(install_cmd, "install")
|
|
|
77
79
|
cli.add_command(sync_cmd, "sync")
|
|
78
80
|
cli.add_command(status_cmd, "status")
|
|
79
81
|
cli.add_command(search_cmd, "search")
|
|
82
|
+
cli.add_command(upgrade_cmd, "upgrade")
|
|
83
|
+
cli.add_command(bom_cmd, "bom")
|
|
80
84
|
|
|
81
85
|
|
|
82
86
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""aes bom — Display the Agent Bill of Materials."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import yaml
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from aes.config import AGENT_DIR, BOM_FILE
|
|
13
|
+
from aes.i18n import t
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _load_bom(agent_dir: Path) -> dict:
|
|
19
|
+
"""Load and return bom.yaml, or empty dict."""
|
|
20
|
+
bom_path = agent_dir / BOM_FILE
|
|
21
|
+
if not bom_path.exists():
|
|
22
|
+
return {}
|
|
23
|
+
with open(bom_path) as f:
|
|
24
|
+
data = yaml.safe_load(f)
|
|
25
|
+
return data if isinstance(data, dict) else {}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@click.command("bom")
|
|
29
|
+
@click.argument("path", default=".", type=click.Path(exists=True))
|
|
30
|
+
def bom_cmd(path: str) -> None:
|
|
31
|
+
"""Display the Agent Bill of Materials (AI-BOM).
|
|
32
|
+
|
|
33
|
+
Shows all models, frameworks, tools, and data sources
|
|
34
|
+
declared in .agent/bom.yaml.
|
|
35
|
+
|
|
36
|
+
\b
|
|
37
|
+
Examples:
|
|
38
|
+
aes bom # current project
|
|
39
|
+
aes bom ./my-project # specific project
|
|
40
|
+
"""
|
|
41
|
+
project_root = Path(path).resolve()
|
|
42
|
+
agent_dir = project_root / AGENT_DIR
|
|
43
|
+
|
|
44
|
+
if not agent_dir.exists():
|
|
45
|
+
console.print(f"[red]{t('common.error')}:[/] {t('common.no_agent_dir', agent_dir=AGENT_DIR, path=project_root)}")
|
|
46
|
+
raise SystemExit(1)
|
|
47
|
+
|
|
48
|
+
bom = _load_bom(agent_dir)
|
|
49
|
+
if not bom:
|
|
50
|
+
console.print(f"\n [dim]{t('bom.not_found')}[/]\n")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
console.print(f"\n[bold]{t('bom.title')}[/] [dim](AES {bom.get('aes_bom', '?')})[/]\n")
|
|
54
|
+
|
|
55
|
+
# Models
|
|
56
|
+
models = bom.get("models", [])
|
|
57
|
+
if models:
|
|
58
|
+
table = Table(title=t("bom.models_table"), show_header=True, header_style="bold")
|
|
59
|
+
table.add_column(t("bom.col_name"), style="cyan")
|
|
60
|
+
table.add_column(t("bom.col_provider"))
|
|
61
|
+
table.add_column(t("bom.col_purpose"))
|
|
62
|
+
table.add_column(t("bom.col_license"))
|
|
63
|
+
for m in models:
|
|
64
|
+
table.add_row(
|
|
65
|
+
m.get("name", "?"),
|
|
66
|
+
m.get("provider", "?"),
|
|
67
|
+
m.get("purpose", "-"),
|
|
68
|
+
m.get("license", "-"),
|
|
69
|
+
)
|
|
70
|
+
console.print(table)
|
|
71
|
+
console.print()
|
|
72
|
+
|
|
73
|
+
# Frameworks
|
|
74
|
+
frameworks = bom.get("frameworks", [])
|
|
75
|
+
if frameworks:
|
|
76
|
+
table = Table(title=t("bom.frameworks_table"), show_header=True, header_style="bold")
|
|
77
|
+
table.add_column(t("bom.col_name"), style="cyan")
|
|
78
|
+
table.add_column(t("bom.col_version"))
|
|
79
|
+
table.add_column(t("bom.col_license"))
|
|
80
|
+
for fw in frameworks:
|
|
81
|
+
table.add_row(
|
|
82
|
+
fw.get("name", "?"),
|
|
83
|
+
fw.get("version", "-"),
|
|
84
|
+
fw.get("license", "-"),
|
|
85
|
+
)
|
|
86
|
+
console.print(table)
|
|
87
|
+
console.print()
|
|
88
|
+
|
|
89
|
+
# Tools
|
|
90
|
+
tools = bom.get("tools", [])
|
|
91
|
+
if tools:
|
|
92
|
+
table = Table(title=t("bom.tools_table"), show_header=True, header_style="bold")
|
|
93
|
+
table.add_column(t("bom.col_name"), style="cyan")
|
|
94
|
+
table.add_column(t("bom.col_type"))
|
|
95
|
+
table.add_column(t("bom.col_version"))
|
|
96
|
+
table.add_column(t("bom.col_source"))
|
|
97
|
+
for tool in tools:
|
|
98
|
+
table.add_row(
|
|
99
|
+
tool.get("name", "?"),
|
|
100
|
+
tool.get("type", "?"),
|
|
101
|
+
tool.get("version", "-"),
|
|
102
|
+
tool.get("source", "-"),
|
|
103
|
+
)
|
|
104
|
+
console.print(table)
|
|
105
|
+
console.print()
|
|
106
|
+
|
|
107
|
+
# Data Sources
|
|
108
|
+
data_sources = bom.get("data_sources", [])
|
|
109
|
+
if data_sources:
|
|
110
|
+
table = Table(title=t("bom.data_sources_table"), show_header=True, header_style="bold")
|
|
111
|
+
table.add_column(t("bom.col_name"), style="cyan")
|
|
112
|
+
table.add_column(t("bom.col_type"))
|
|
113
|
+
table.add_column(t("bom.col_uri"))
|
|
114
|
+
table.add_column(t("bom.col_license"))
|
|
115
|
+
for ds in data_sources:
|
|
116
|
+
table.add_row(
|
|
117
|
+
ds.get("name", "?"),
|
|
118
|
+
ds.get("type", "?"),
|
|
119
|
+
ds.get("uri", "-"),
|
|
120
|
+
ds.get("license", "-"),
|
|
121
|
+
)
|
|
122
|
+
console.print(table)
|
|
123
|
+
console.print()
|
|
124
|
+
|
|
125
|
+
# Summary
|
|
126
|
+
console.print(f"[bold]{t('bom.summary')}[/]")
|
|
127
|
+
console.print(f" {t('bom.models_count', count=len(models))}")
|
|
128
|
+
console.print(f" {t('bom.frameworks_count', count=len(frameworks))}")
|
|
129
|
+
console.print(f" {t('bom.tools_count', count=len(tools))}")
|
|
130
|
+
console.print(f" {t('bom.data_sources_count', count=len(data_sources))}")
|
|
131
|
+
console.print()
|
|
@@ -20,6 +20,8 @@ from rich.tree import Tree
|
|
|
20
20
|
from aes.config import (
|
|
21
21
|
AGENT_DIR,
|
|
22
22
|
AGENTIGNORE_FILE,
|
|
23
|
+
BOM_FILE,
|
|
24
|
+
DECISIONS_DIR,
|
|
23
25
|
LOCAL_EXAMPLE_FILE,
|
|
24
26
|
LOCAL_FILE,
|
|
25
27
|
SCAFFOLD_DIR,
|
|
@@ -252,6 +254,7 @@ def _get_agent_integrated_types() -> list:
|
|
|
252
254
|
return [
|
|
253
255
|
(t("init.type_ml"), "ml"),
|
|
254
256
|
(t("init.type_research"), "research"),
|
|
257
|
+
(t("init.type_assistant"), "assistant"),
|
|
255
258
|
(t("init.type_custom"), "other"),
|
|
256
259
|
]
|
|
257
260
|
|
|
@@ -620,6 +623,7 @@ def init_cmd(
|
|
|
620
623
|
agent_dir.mkdir(exist_ok=True)
|
|
621
624
|
(agent_dir / MEMORY_DIR).mkdir(exist_ok=True)
|
|
622
625
|
(agent_dir / MEMORY_DIR / "sessions").mkdir(exist_ok=True)
|
|
626
|
+
(agent_dir / DECISIONS_DIR).mkdir(parents=True, exist_ok=True)
|
|
623
627
|
(agent_dir / OVERRIDES_DIR).mkdir(exist_ok=True)
|
|
624
628
|
|
|
625
629
|
if skills:
|
|
@@ -664,6 +668,10 @@ def init_cmd(
|
|
|
664
668
|
content = _render_template(env, "permissions.yaml.jinja", context)
|
|
665
669
|
(agent_dir / "permissions.yaml").write_text(content)
|
|
666
670
|
|
|
671
|
+
# bom.yaml
|
|
672
|
+
content = _render_template(env, "bom.yaml.jinja", context)
|
|
673
|
+
(agent_dir / BOM_FILE).write_text(content)
|
|
674
|
+
|
|
667
675
|
# .agentignore
|
|
668
676
|
agentignore_path = project_root / AGENTIGNORE_FILE
|
|
669
677
|
if not agentignore_path.exists():
|
|
@@ -12,7 +12,7 @@ import yaml
|
|
|
12
12
|
from rich.console import Console
|
|
13
13
|
from rich.table import Table
|
|
14
14
|
|
|
15
|
-
from aes.config import AGENT_DIR
|
|
15
|
+
from aes.config import AGENT_DIR, BOM_FILE, DECISIONS_DIR
|
|
16
16
|
from aes.i18n import t
|
|
17
17
|
from aes.registry import (
|
|
18
18
|
fetch_index,
|
|
@@ -205,6 +205,47 @@ def _inspect_local(path: str) -> None:
|
|
|
205
205
|
console.print(table)
|
|
206
206
|
console.print()
|
|
207
207
|
|
|
208
|
+
# Models
|
|
209
|
+
models = manifest.get("models", [])
|
|
210
|
+
if models:
|
|
211
|
+
console.print(f"[bold]{t('inspect.models_section')}[/]")
|
|
212
|
+
for m in models:
|
|
213
|
+
purpose = m.get("purpose", "")
|
|
214
|
+
purpose_str = f" [dim]({purpose})[/]" if purpose else ""
|
|
215
|
+
console.print(f" {m.get('name', '?')} — {m.get('provider', '?')}{purpose_str}")
|
|
216
|
+
console.print()
|
|
217
|
+
|
|
218
|
+
# Provenance
|
|
219
|
+
provenance = manifest.get("provenance", {})
|
|
220
|
+
if provenance:
|
|
221
|
+
console.print(f"[bold]{t('inspect.provenance_section')}[/]")
|
|
222
|
+
if provenance.get("created_by"):
|
|
223
|
+
console.print(f" {t('inspect.provenance_created_by', value=provenance['created_by'])}")
|
|
224
|
+
if provenance.get("source"):
|
|
225
|
+
console.print(f" {t('inspect.provenance_source', value=provenance['source'])}")
|
|
226
|
+
console.print()
|
|
227
|
+
|
|
228
|
+
# BOM summary
|
|
229
|
+
bom_path = agent_dir / BOM_FILE
|
|
230
|
+
if bom_path.exists():
|
|
231
|
+
bom = _load_yaml(bom_path)
|
|
232
|
+
n_models = len(bom.get("models", []))
|
|
233
|
+
n_frameworks = len(bom.get("frameworks", []))
|
|
234
|
+
n_tools = len(bom.get("tools", []))
|
|
235
|
+
n_data = len(bom.get("data_sources", []))
|
|
236
|
+
console.print(f"[bold]{t('inspect.bom_section')}[/]")
|
|
237
|
+
console.print(f" {t('inspect.bom_summary', models=n_models, frameworks=n_frameworks, tools=n_tools, data=n_data)}")
|
|
238
|
+
console.print()
|
|
239
|
+
|
|
240
|
+
# Decision records count
|
|
241
|
+
decisions_dir = agent_dir / DECISIONS_DIR
|
|
242
|
+
if decisions_dir.exists() and decisions_dir.is_dir():
|
|
243
|
+
dr_count = len(list(decisions_dir.glob("*.yaml")))
|
|
244
|
+
if dr_count > 0:
|
|
245
|
+
console.print(f"[bold]{t('inspect.decisions_section')}[/]")
|
|
246
|
+
console.print(f" {t('inspect.decisions_count', count=dr_count)}")
|
|
247
|
+
console.print()
|
|
248
|
+
|
|
208
249
|
# Summary
|
|
209
250
|
console.print(f"[bold]{t('inspect.summary')}[/]")
|
|
210
251
|
console.print(f" {t('inspect.skills_count', count=len(skills))}")
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import fnmatch
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
6
8
|
import shutil
|
|
7
9
|
import sys
|
|
8
10
|
import tarfile
|
|
@@ -14,7 +16,7 @@ import click
|
|
|
14
16
|
import yaml
|
|
15
17
|
from rich.console import Console
|
|
16
18
|
|
|
17
|
-
from aes.config import AGENT_DIR, MANIFEST_FILE
|
|
19
|
+
from aes.config import AGENT_DIR, BOM_FILE, MANIFEST_FILE
|
|
18
20
|
from aes.i18n import t
|
|
19
21
|
|
|
20
22
|
console = Console()
|
|
@@ -171,6 +173,53 @@ def _validate_before_publish(project_root: Path) -> bool:
|
|
|
171
173
|
return True
|
|
172
174
|
|
|
173
175
|
|
|
176
|
+
def _media_type_for_path(path: str) -> str:
|
|
177
|
+
"""Return a media type string for a file path."""
|
|
178
|
+
if path.endswith(".yaml") or path.endswith(".yml"):
|
|
179
|
+
return "application/vnd.aes.agent-config.v1+yaml"
|
|
180
|
+
if path.endswith(".md"):
|
|
181
|
+
return "text/markdown"
|
|
182
|
+
if path.endswith(".json"):
|
|
183
|
+
return "application/json"
|
|
184
|
+
return "application/octet-stream"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _build_package_manifest(
|
|
188
|
+
name: str,
|
|
189
|
+
version: str,
|
|
190
|
+
aes_version: str,
|
|
191
|
+
pkg_type: str,
|
|
192
|
+
files: List[tuple],
|
|
193
|
+
) -> dict:
|
|
194
|
+
"""Build an aes-manifest.json dict.
|
|
195
|
+
|
|
196
|
+
*files* is a list of ``(arcname, file_path)`` tuples.
|
|
197
|
+
"""
|
|
198
|
+
layers = []
|
|
199
|
+
for arcname, file_path in files:
|
|
200
|
+
data = file_path.read_bytes()
|
|
201
|
+
digest = hashlib.sha256(data).hexdigest()
|
|
202
|
+
layers.append({
|
|
203
|
+
"mediaType": _media_type_for_path(arcname),
|
|
204
|
+
"digest": f"sha256:{digest}",
|
|
205
|
+
"size": len(data),
|
|
206
|
+
"path": arcname,
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
"schemaVersion": 1,
|
|
211
|
+
"mediaType": "application/vnd.aes.package.v1+tar+gzip",
|
|
212
|
+
"config": {
|
|
213
|
+
"name": name,
|
|
214
|
+
"version": version,
|
|
215
|
+
"type": pkg_type,
|
|
216
|
+
"aes": aes_version,
|
|
217
|
+
},
|
|
218
|
+
"layers": layers,
|
|
219
|
+
"signature": None,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
174
223
|
def _publish_template_dir(
|
|
175
224
|
project_root: Path,
|
|
176
225
|
output_dir: Path,
|
|
@@ -194,6 +243,7 @@ def _publish_template_dir(
|
|
|
194
243
|
|
|
195
244
|
name = manifest.get("name", "unknown")
|
|
196
245
|
version = manifest.get("version", "0.0.0")
|
|
246
|
+
aes_version = manifest.get("aes", "1.0")
|
|
197
247
|
|
|
198
248
|
# Build exclusion list
|
|
199
249
|
if include_all:
|
|
@@ -208,17 +258,35 @@ def _publish_template_dir(
|
|
|
208
258
|
tarball_name = f"{name}-{version}.tar.gz"
|
|
209
259
|
tarball_path = output_dir / tarball_name
|
|
210
260
|
|
|
261
|
+
# Collect files and build package manifest
|
|
262
|
+
included_files: List[tuple] = [] # (rel_path_in_agent, absolute_path)
|
|
263
|
+
|
|
264
|
+
for file_path in sorted(agent_dir.rglob("*")):
|
|
265
|
+
if not file_path.is_file():
|
|
266
|
+
continue
|
|
267
|
+
rel = file_path.relative_to(agent_dir)
|
|
268
|
+
rel_str = str(rel)
|
|
269
|
+
if _is_excluded(rel_str, excludes):
|
|
270
|
+
continue
|
|
271
|
+
included_files.append((f"{AGENT_DIR}/{rel_str}", file_path))
|
|
272
|
+
|
|
273
|
+
# Generate aes-manifest.json
|
|
274
|
+
pkg_manifest = _build_package_manifest(
|
|
275
|
+
name, version, aes_version, "template", included_files,
|
|
276
|
+
)
|
|
277
|
+
|
|
211
278
|
with tarfile.open(tarball_path, "w:gz") as tar:
|
|
212
|
-
for file_path in
|
|
213
|
-
|
|
214
|
-
continue
|
|
215
|
-
rel = file_path.relative_to(agent_dir)
|
|
216
|
-
rel_str = str(rel)
|
|
217
|
-
if _is_excluded(rel_str, excludes):
|
|
218
|
-
continue
|
|
219
|
-
arcname = f"{name}/{AGENT_DIR}/{rel_str}"
|
|
279
|
+
for rel_path, file_path in included_files:
|
|
280
|
+
arcname = f"{name}/{rel_path}"
|
|
220
281
|
tar.add(file_path, arcname=arcname)
|
|
221
282
|
|
|
283
|
+
# Add aes-manifest.json at the package root
|
|
284
|
+
manifest_json = json.dumps(pkg_manifest, indent=2).encode()
|
|
285
|
+
import io
|
|
286
|
+
info = tarfile.TarInfo(name=f"{name}/aes-manifest.json")
|
|
287
|
+
info.size = len(manifest_json)
|
|
288
|
+
tar.addfile(info, io.BytesIO(manifest_json))
|
|
289
|
+
|
|
222
290
|
return tarball_path
|
|
223
291
|
|
|
224
292
|
|
|
@@ -70,7 +70,12 @@ def status_cmd(path: str) -> None:
|
|
|
70
70
|
|
|
71
71
|
for name in TARGET_NAMES:
|
|
72
72
|
adapter = TARGETS[name]()
|
|
73
|
-
|
|
73
|
+
try:
|
|
74
|
+
plan = adapter.plan(ctx, force=True)
|
|
75
|
+
except Exception:
|
|
76
|
+
# Target-specific validation failure (e.g. openclaw requires
|
|
77
|
+
# identity/model) — skip incompatible targets silently in status.
|
|
78
|
+
continue
|
|
74
79
|
for gf in plan.files:
|
|
75
80
|
would_generate[gf.relative_path] = gf.content
|
|
76
81
|
|
|
@@ -64,7 +64,15 @@ def run_sync(
|
|
|
64
64
|
all_plans: List[SyncPlan] = []
|
|
65
65
|
for name in selected:
|
|
66
66
|
adapter = TARGETS[name]()
|
|
67
|
-
|
|
67
|
+
try:
|
|
68
|
+
all_plans.append(adapter.plan(ctx, force))
|
|
69
|
+
except click.ClickException:
|
|
70
|
+
# Target-specific validation failure — skip when syncing
|
|
71
|
+
# multiple targets (e.g. openclaw requires identity/model
|
|
72
|
+
# which non-assistant projects won't have).
|
|
73
|
+
if len(selected) == 1:
|
|
74
|
+
raise
|
|
75
|
+
continue
|
|
68
76
|
|
|
69
77
|
sync_manifest = _load_sync_manifest(project_root)
|
|
70
78
|
written = 0
|
|
@@ -158,6 +166,14 @@ def _load_agent_context(project_root: Path) -> AgentContext:
|
|
|
158
166
|
"negative_triggers": skill_data.get("negative_triggers", []),
|
|
159
167
|
"activation": skill_data.get("activation", "explicit"),
|
|
160
168
|
"allowed_tools": skill_data.get("allowed_tools"),
|
|
169
|
+
"version": skill_data.get("version", "0.1.0"),
|
|
170
|
+
"emoji": skill_data.get("emoji", ""),
|
|
171
|
+
"license": skill_data.get("license", "MIT"),
|
|
172
|
+
"user_invocable": skill_data.get("user_invocable", True),
|
|
173
|
+
"primary_env": skill_data.get("primary_env", ""),
|
|
174
|
+
"requires_bins": (skill_data.get("requires") or {}).get("bins", []),
|
|
175
|
+
"requires_env": (skill_data.get("requires") or {}).get("env", []),
|
|
176
|
+
"mcp_server": skill_data.get("mcp_server"),
|
|
161
177
|
}
|
|
162
178
|
if skill_id not in skill_metadata:
|
|
163
179
|
skill_metadata[skill_id] = {
|
|
@@ -305,7 +321,19 @@ def sync_cmd(
|
|
|
305
321
|
all_plans: List[SyncPlan] = []
|
|
306
322
|
for target_name in selected:
|
|
307
323
|
adapter = TARGETS[target_name]()
|
|
308
|
-
|
|
324
|
+
try:
|
|
325
|
+
sync_plan = adapter.plan(ctx, force)
|
|
326
|
+
except click.ClickException as exc:
|
|
327
|
+
# Target-specific validation failure (e.g. openclaw requires
|
|
328
|
+
# identity/model). When syncing a single explicit target, re-raise
|
|
329
|
+
# so the user sees the error. When syncing all targets, skip and
|
|
330
|
+
# warn — not every project is compatible with every target.
|
|
331
|
+
if len(selected) == 1:
|
|
332
|
+
raise
|
|
333
|
+
console.print(
|
|
334
|
+
f" [yellow]⚠ {target_name}:[/] {exc.format_message()}"
|
|
335
|
+
)
|
|
336
|
+
continue
|
|
309
337
|
all_plans.append(sync_plan)
|
|
310
338
|
|
|
311
339
|
# Execute plans
|