aes-cli 0.6.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.
Files changed (88) hide show
  1. {aes_cli-0.6.0 → aes_cli-0.7.0}/PKG-INFO +1 -1
  2. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/__init__.py +1 -1
  3. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/commands/init.py +1 -0
  4. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/commands/status.py +6 -1
  5. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/commands/sync.py +30 -2
  6. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/domains.py +201 -0
  7. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/i18n/_messages.py +1 -0
  8. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/i18n/ja.py +1 -0
  9. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/agent.yaml.jinja +40 -0
  10. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/skill.yaml.jinja +27 -0
  11. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/schemas/agent.schema.json +111 -0
  12. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/schemas/permissions.schema.json +35 -0
  13. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/schemas/skill.schema.json +58 -0
  14. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/targets/__init__.py +2 -0
  15. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/targets/_composer.py +286 -0
  16. aes_cli-0.7.0/aes/targets/openclaw.py +507 -0
  17. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes_cli.egg-info/PKG-INFO +1 -1
  18. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes_cli.egg-info/SOURCES.txt +2 -0
  19. {aes_cli-0.6.0 → aes_cli-0.7.0}/pyproject.toml +1 -1
  20. {aes_cli-0.6.0 → aes_cli-0.7.0}/tests/test_init.py +2 -2
  21. aes_cli-0.7.0/tests/test_openclaw_target.py +262 -0
  22. {aes_cli-0.6.0 → aes_cli-0.7.0}/README.md +0 -0
  23. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/__main__.py +0 -0
  24. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/analyzer.py +0 -0
  25. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/commands/__init__.py +0 -0
  26. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/commands/bom.py +0 -0
  27. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/commands/inspect.py +0 -0
  28. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/commands/install.py +0 -0
  29. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/commands/publish.py +0 -0
  30. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/commands/search.py +0 -0
  31. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/commands/upgrade.py +0 -0
  32. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/commands/validate.py +0 -0
  33. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/config.py +0 -0
  34. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/frameworks.py +0 -0
  35. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/global_config.py +0 -0
  36. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/i18n/__init__.py +0 -0
  37. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/i18n/domains_ja.py +0 -0
  38. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/mcp_server.py +0 -0
  39. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/migrations.py +0 -0
  40. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/registry.py +0 -0
  41. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/agentignore.jinja +0 -0
  42. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/bom.yaml.jinja +0 -0
  43. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/instructions.md.jinja +0 -0
  44. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/ja/instructions.md.jinja +0 -0
  45. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/ja/memory_command.md.jinja +0 -0
  46. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/ja/operations.md.jinja +0 -0
  47. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/ja/orchestrator.md.jinja +0 -0
  48. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/ja/setup.md.jinja +0 -0
  49. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/ja/skill.md.jinja +0 -0
  50. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/ja/workflow_command.md.jinja +0 -0
  51. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/local.example.yaml.jinja +0 -0
  52. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/local.yaml.jinja +0 -0
  53. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/memory_command.md.jinja +0 -0
  54. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/operations.md.jinja +0 -0
  55. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/orchestrator.md.jinja +0 -0
  56. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/permissions.yaml.jinja +0 -0
  57. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/setup.md.jinja +0 -0
  58. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/skill.md.jinja +0 -0
  59. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/workflow.yaml.jinja +0 -0
  60. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/scaffold/workflow_command.md.jinja +0 -0
  61. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/schemas/bom.schema.json +0 -0
  62. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/schemas/decision-record.schema.json +0 -0
  63. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/schemas/registry.schema.json +0 -0
  64. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/schemas/workflow.schema.json +0 -0
  65. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/targets/_base.py +0 -0
  66. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/targets/claude.py +0 -0
  67. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/targets/copilot.py +0 -0
  68. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/targets/cursor.py +0 -0
  69. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/targets/windsurf.py +0 -0
  70. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes/validator.py +0 -0
  71. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes_cli.egg-info/dependency_links.txt +0 -0
  72. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes_cli.egg-info/entry_points.txt +0 -0
  73. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes_cli.egg-info/requires.txt +0 -0
  74. {aes_cli-0.6.0 → aes_cli-0.7.0}/aes_cli.egg-info/top_level.txt +0 -0
  75. {aes_cli-0.6.0 → aes_cli-0.7.0}/setup.cfg +0 -0
  76. {aes_cli-0.6.0 → aes_cli-0.7.0}/tests/test_analyzer.py +0 -0
  77. {aes_cli-0.6.0 → aes_cli-0.7.0}/tests/test_bom.py +0 -0
  78. {aes_cli-0.6.0 → aes_cli-0.7.0}/tests/test_frameworks.py +0 -0
  79. {aes_cli-0.6.0 → aes_cli-0.7.0}/tests/test_inspect.py +0 -0
  80. {aes_cli-0.6.0 → aes_cli-0.7.0}/tests/test_install.py +0 -0
  81. {aes_cli-0.6.0 → aes_cli-0.7.0}/tests/test_mcp_server.py +0 -0
  82. {aes_cli-0.6.0 → aes_cli-0.7.0}/tests/test_publish.py +0 -0
  83. {aes_cli-0.6.0 → aes_cli-0.7.0}/tests/test_registry.py +0 -0
  84. {aes_cli-0.6.0 → aes_cli-0.7.0}/tests/test_search.py +0 -0
  85. {aes_cli-0.6.0 → aes_cli-0.7.0}/tests/test_status.py +0 -0
  86. {aes_cli-0.6.0 → aes_cli-0.7.0}/tests/test_sync.py +0 -0
  87. {aes_cli-0.6.0 → aes_cli-0.7.0}/tests/test_upgrade.py +0 -0
  88. {aes_cli-0.6.0 → aes_cli-0.7.0}/tests/test_validate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aes-cli
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: CLI tool for the Agentic Engineering Standard
5
5
  Author: Hiro
6
6
  License: Apache-2.0
@@ -2,4 +2,4 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.6.0"
5
+ __version__ = "0.7.0"
@@ -254,6 +254,7 @@ def _get_agent_integrated_types() -> list:
254
254
  return [
255
255
  (t("init.type_ml"), "ml"),
256
256
  (t("init.type_research"), "research"),
257
+ (t("init.type_assistant"), "assistant"),
257
258
  (t("init.type_custom"), "other"),
258
259
  ]
259
260
 
@@ -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
- plan = adapter.plan(ctx, force=True)
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
- all_plans.append(adapter.plan(ctx, force))
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
- sync_plan = adapter.plan(ctx, force)
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
@@ -39,6 +39,14 @@ class SkillDef:
39
39
  negative_triggers: List[str] = field(default_factory=list)
40
40
  activation: str = "explicit" # "auto", "explicit", or "hybrid"
41
41
  allowed_tools: Optional[Dict[str, object]] = None
42
+ # OpenClaw-specific fields
43
+ emoji: str = ""
44
+ requires_bins: List[str] = field(default_factory=list)
45
+ requires_env: List[str] = field(default_factory=list)
46
+ primary_env: str = ""
47
+ user_invocable: bool = True
48
+ license_id: str = "MIT"
49
+ mcp_server: Optional[Dict[str, object]] = None
42
50
  # Runbook content sections
43
51
  runbook_purpose: str = ""
44
52
  runbook_when: str = ""
@@ -134,6 +142,18 @@ class DomainConfig:
134
142
  env_required: List[Dict[str, str]] = field(default_factory=list)
135
143
  env_optional: List[Dict[str, str]] = field(default_factory=list)
136
144
 
145
+ # OpenClaw / daemon-agent fields
146
+ identity_persona: str = ""
147
+ identity_name: str = ""
148
+ identity_emoji: str = ""
149
+ model_provider: str = ""
150
+ model_model: str = ""
151
+ sandbox_enabled: bool = False
152
+ sandbox_runtime: str = "docker"
153
+ heartbeat_interval: int = 30
154
+ heartbeat_checklist: str = ""
155
+ channels: Dict[str, Dict[str, str]] = field(default_factory=dict)
156
+
137
157
 
138
158
  # ---------------------------------------------------------------------------
139
159
  # ML domain config — drawn from examples/ml-pipeline
@@ -1374,11 +1394,192 @@ AGENT_INTEGRATED_BASE_CONFIG = DomainConfig(
1374
1394
  # Public mapping
1375
1395
  # ---------------------------------------------------------------------------
1376
1396
 
1397
+ # ---------------------------------------------------------------------------
1398
+ # Assistant domain config — personal AI assistant (OpenClaw target)
1399
+ # ---------------------------------------------------------------------------
1400
+
1401
+ _ASSISTANT_SKILLS = [
1402
+ SkillDef(
1403
+ id="greeting",
1404
+ name="Greeting",
1405
+ version="1.0.0",
1406
+ description="Greet users warmly and contextually when they start a conversation or say hello. References time of day and user profile when available.",
1407
+ stage=1,
1408
+ phase="interaction",
1409
+ activation="auto",
1410
+ emoji="\U0001f44b",
1411
+ user_invocable=True,
1412
+ tags=["conversation", "onboarding"],
1413
+ negative_triggers=["Do NOT use mid-conversation or when the user is giving a command"],
1414
+ runbook_purpose="Greet the user warmly when they initiate a conversation or say hello.",
1415
+ runbook_when="- User sends a greeting (hello, hi, hey, good morning, etc.)\n- First message in a new session",
1416
+ runbook_how="1. Check time of day for contextual greeting\n2. Reference the user's name from USER.md if available\n3. Mention any pending heartbeat items if relevant\n4. Keep it brief — one or two sentences max",
1417
+ ),
1418
+ SkillDef(
1419
+ id="web-search",
1420
+ name="Web Search",
1421
+ version="1.0.0",
1422
+ description="Search the web using the Brave Search API when the user needs current information, news, or facts that may be beyond training data.",
1423
+ stage=1,
1424
+ phase="research",
1425
+ activation="explicit",
1426
+ emoji="\U0001f50d",
1427
+ user_invocable=True,
1428
+ requires_env=["BRAVE_API_KEY"],
1429
+ primary_env="BRAVE_API_KEY",
1430
+ mcp_server={
1431
+ "command": "npx",
1432
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
1433
+ "env": {"BRAVE_API_KEY": "${BRAVE_API_KEY}"},
1434
+ },
1435
+ tags=["search", "web", "research"],
1436
+ negative_triggers=["Do NOT use for questions answerable from memory or context"],
1437
+ allowed_tools={"network": True},
1438
+ runbook_purpose="Search the web using the Brave Search API when the user needs current information.",
1439
+ runbook_when="- User asks about current events or recent information\n- User explicitly asks to search the web\n- Question requires facts beyond training data",
1440
+ runbook_how="1. Call the `brave_search` tool with a concise query\n2. Format results as a bulleted list with title, URL, and snippet\n3. Summarize the key findings in 2-3 sentences\n4. Cite sources with URLs",
1441
+ runbook_error_handling="- **API timeout**: Retry once, then inform user\n- **No results**: Suggest alternative search terms\n- **Rate limit**: Wait and retry (max 5 searches per turn)",
1442
+ ),
1443
+ SkillDef(
1444
+ id="calendar",
1445
+ name="Calendar Manager",
1446
+ version="1.0.0",
1447
+ description="Manage calendar events — create, list, update, and cancel meetings and reminders. Integrates with Google Calendar or similar providers.",
1448
+ stage=1,
1449
+ phase="productivity",
1450
+ activation="explicit",
1451
+ emoji="\U0001f4c5",
1452
+ user_invocable=True,
1453
+ tags=["calendar", "scheduling", "productivity"],
1454
+ negative_triggers=["Do NOT use for general time questions or timezone conversions"],
1455
+ runbook_purpose="Manage the user's calendar events and reminders.",
1456
+ runbook_when="- User asks to schedule, reschedule, or cancel a meeting\n- User asks about upcoming events\n- User wants to set a reminder",
1457
+ runbook_how="1. Parse the user's request for event details (title, time, duration, attendees)\n2. Confirm details with the user before creating/modifying\n3. Execute the calendar operation\n4. Confirm the result with event details",
1458
+ ),
1459
+ SkillDef(
1460
+ id="code-review",
1461
+ name="Code Review",
1462
+ version="1.0.0",
1463
+ description="Review code changes from GitHub pull requests or local diffs. Analyze for bugs, style issues, security concerns, and suggest improvements.",
1464
+ stage=1,
1465
+ phase="development",
1466
+ activation="explicit",
1467
+ emoji="\U0001f50d",
1468
+ user_invocable=True,
1469
+ requires_bins=["git"],
1470
+ tags=["code-review", "github", "development"],
1471
+ negative_triggers=["Do NOT use for writing new code from scratch"],
1472
+ allowed_tools={"shell": True, "files": {"read": True}, "network": True},
1473
+ runbook_purpose="Review code changes and provide constructive feedback.",
1474
+ runbook_when="- User shares a PR link or asks for a code review\n- User pastes code and asks for feedback\n- Heartbeat detects open PRs assigned to the user",
1475
+ runbook_how="1. Fetch the diff (from GitHub PR or local git diff)\n2. Analyze for bugs, security issues, and style violations\n3. Provide specific, actionable feedback with line references\n4. Summarize overall assessment (approve / request changes)",
1476
+ ),
1477
+ ]
1478
+
1479
+ _ASSISTANT_CONVERSE_COMMAND = CommandDef(
1480
+ id="converse",
1481
+ trigger="/converse",
1482
+ description="Start or resume a conversation session with the assistant",
1483
+ runbook_purpose="Run the assistant's main conversational loop. The agent listens across configured channels, responds to messages, executes skills on demand, and proactively checks heartbeat tasks.",
1484
+ worker_specialty="Multi-platform conversational AI assistant",
1485
+ runbook_phases=[
1486
+ {"title": "Session Setup", "content": "Load user profile from USER.md. Check HEARTBEAT.md for pending tasks. Review recent conversation history for context continuity."},
1487
+ {"title": "Message Processing", "content": "Receive messages from connected channels. Determine intent — is this a greeting, a question, a skill invocation, or a general conversation? Route accordingly."},
1488
+ {"title": "Skill Execution", "content": "When a skill is triggered (explicitly via command or automatically via context match), execute it and return results to the user."},
1489
+ {"title": "Memory & Wrap-up", "content": "Persist important learnings to MEMORY.md. Update AGENTS.md if the user's preferences or context changed."},
1490
+ ],
1491
+ )
1492
+
1493
+ ASSISTANT_CONFIG = DomainConfig(
1494
+ mode="agent-integrated",
1495
+ workflow_commands=[_ASSISTANT_CONVERSE_COMMAND],
1496
+
1497
+ # Identity defaults for scaffold
1498
+ identity_persona=(
1499
+ "You are a helpful, security-conscious personal assistant. "
1500
+ "You are concise, accurate, and proactive. You ask for clarification "
1501
+ "when instructions are ambiguous. You never share credentials or "
1502
+ "sensitive information across channels."
1503
+ ),
1504
+ identity_name="Assistant",
1505
+ identity_emoji="\U0001f99e",
1506
+ model_provider="anthropic",
1507
+ model_model="claude-sonnet-4-20250514",
1508
+ sandbox_enabled=False,
1509
+ sandbox_runtime="docker",
1510
+ heartbeat_interval=30,
1511
+ heartbeat_checklist=(
1512
+ "- Check for unread messages across channels\n"
1513
+ "- Review calendar for upcoming meetings (next 2 hours)\n"
1514
+ "- Check GitHub for open PRs assigned to user\n"
1515
+ ),
1516
+ channels={
1517
+ "telegram": {"enabled": "true", "bot_token_env": "TELEGRAM_BOT_TOKEN"},
1518
+ "discord": {"enabled": "true", "bot_token_env": "DISCORD_BOT_TOKEN"},
1519
+ },
1520
+
1521
+ instructions_description="Personal AI assistant connected to messaging platforms via OpenClaw. Runs 24/7, responds across channels, and executes skills on demand.",
1522
+ instructions_quick_ref=(
1523
+ "```bash\n"
1524
+ "aes sync -t openclaw # generate .openclaw/ config\n"
1525
+ "openclaw gateway # start the agent daemon\n"
1526
+ "openclaw nemoclaw launch # start with sandbox (if configured)\n"
1527
+ "```"
1528
+ ),
1529
+ instructions_rules=[
1530
+ "Never share API keys, tokens, or credentials in any channel",
1531
+ "Always confirm destructive actions (deleting events, sending bulk messages) before executing",
1532
+ "Keep responses concise — messaging platforms have character limits",
1533
+ "Respect user's quiet hours (check HEARTBEAT.md schedule)",
1534
+ "Use the heartbeat to proactively surface important updates, not to spam",
1535
+ "When uncertain, ask for clarification rather than guessing",
1536
+ ],
1537
+ instructions_key_principle=(
1538
+ "This assistant treats every conversation as a service interaction — "
1539
+ "be helpful, be brief, be safe. Security comes first: never leak "
1540
+ "credentials, never execute unconfirmed destructive operations, "
1541
+ "and always respect the user's privacy across platforms."
1542
+ ),
1543
+ instructions_gotchas=[
1544
+ "Channel tokens are in environment variables, never in config files",
1545
+ "Heartbeat tasks run even when the user isn't actively chatting",
1546
+ "SKILL.md files in workspace/skills/ take precedence over managed skills",
1547
+ "OpenShell sandbox blocks all outbound traffic by default — add network policies for APIs you need",
1548
+ ],
1549
+
1550
+ skills=_ASSISTANT_SKILLS,
1551
+
1552
+ orchestrator_pipeline="greeting → (user message) → route to skill or general response",
1553
+ orchestrator_status_flow="idle → processing → responding → idle",
1554
+ orchestrator_decision_tree=(
1555
+ "On each message:\n"
1556
+ " |- Is greeting? → greeting skill\n"
1557
+ " |- Is skill command (e.g. /search)? → execute named skill\n"
1558
+ " |- Matches auto-skill context? → execute auto skill\n"
1559
+ " \\- General message → conversational response"
1560
+ ),
1561
+ orchestrator_when_to_stop="When the user explicitly ends the conversation or goes idle for >10 minutes",
1562
+
1563
+ permissions_confirm_shell=["rm *", "git push --force"],
1564
+ permissions_confirm_actions=["Send message to external channel", "Delete calendar event"],
1565
+
1566
+ env_required=[
1567
+ {"name": "ANTHROPIC_API_KEY", "description": "API key for the primary LLM provider"},
1568
+ ],
1569
+ env_optional=[
1570
+ {"name": "BRAVE_API_KEY", "description": "API key for web search (Brave Search)"},
1571
+ {"name": "TELEGRAM_BOT_TOKEN", "description": "Bot token for Telegram integration"},
1572
+ {"name": "DISCORD_BOT_TOKEN", "description": "Bot token for Discord integration"},
1573
+ ],
1574
+ )
1575
+
1576
+
1377
1577
  DOMAIN_CONFIGS: Dict[str, DomainConfig] = {
1378
1578
  "ml": ML_CONFIG,
1379
1579
  "web": WEB_CONFIG,
1380
1580
  "devops": DEVOPS_CONFIG,
1381
1581
  "research": RESEARCH_CONFIG,
1582
+ "assistant": ASSISTANT_CONFIG,
1382
1583
  }
1383
1584
 
1384
1585
 
@@ -178,6 +178,7 @@ MESSAGES: dict = {
178
178
  "init.type_skip": "Skip",
179
179
  "init.type_ml": "ML pipeline",
180
180
  "init.type_research": "Research / Content pipeline",
181
+ "init.type_assistant": "Assistant (24/7 AI on messaging platforms)",
181
182
  "init.type_custom": "Custom",
182
183
  "init.overwrite_warning": "{agent_dir}/ already exists at {path}",
183
184
  "init.overwrite_confirm": "Overwrite existing files?",
@@ -178,6 +178,7 @@ MESSAGES: dict = {
178
178
  "init.type_skip": "スキップ",
179
179
  "init.type_ml": "ML パイプライン",
180
180
  "init.type_research": "研究 / コンテンツパイプライン",
181
+ "init.type_assistant": "アシスタント(24時間メッセージングAI)",
181
182
  "init.type_custom": "カスタム",
182
183
  "init.overwrite_warning": "{path} に {agent_dir}/ が既に存在します",
183
184
  "init.overwrite_confirm": "既存のファイルを上書きしますか?",
@@ -54,6 +54,46 @@ commands:
54
54
  {% endfor %}
55
55
  {% endif %}
56
56
 
57
+ {% if domain_config.identity_name %}
58
+
59
+ identity:
60
+ persona: |
61
+ {{ domain_config.identity_persona | indent(4) }}
62
+ name: "{{ domain_config.identity_name }}"
63
+ emoji: "{{ domain_config.identity_emoji }}"
64
+ user_profile: ""
65
+ {% endif %}
66
+ {% if domain_config.model_provider %}
67
+
68
+ model:
69
+ provider: "{{ domain_config.model_provider }}"
70
+ model: "{{ domain_config.model_model }}"
71
+ api_key_env: "{{ domain_config.env_required[0].name if domain_config.env_required else 'ANTHROPIC_API_KEY' }}"
72
+ {% endif %}
73
+ {% if domain_config.sandbox_runtime %}
74
+
75
+ sandbox:
76
+ enabled: {{ 'true' if domain_config.sandbox_enabled else 'false' }}
77
+ runtime: "{{ domain_config.sandbox_runtime }}"
78
+ workspace_root: "/sandbox"
79
+ {% endif %}
80
+ {% if domain_config.heartbeat_checklist %}
81
+
82
+ heartbeat:
83
+ interval_minutes: {{ domain_config.heartbeat_interval }}
84
+ checklist: |
85
+ {{ domain_config.heartbeat_checklist | indent(4) }}
86
+ {% endif %}
87
+ {% if domain_config.channels %}
88
+
89
+ channels:
90
+ {% for platform, cfg in domain_config.channels.items() %}
91
+ {{ platform }}:
92
+ enabled: {{ cfg.enabled }}
93
+ bot_token_env: "{{ cfg.bot_token_env }}"
94
+ {% endfor %}
95
+ {% endif %}
96
+
57
97
  # models:
58
98
  # - name: "claude-sonnet-4-20250514"
59
99
  # provider: "anthropic"
@@ -13,6 +13,33 @@ negative_triggers:
13
13
  {% if skill.activation != "explicit" %}
14
14
  activation: "{{ skill.activation }}"
15
15
  {% endif %}
16
+ {% if skill.emoji %}
17
+ emoji: "{{ skill.emoji }}"
18
+ {% endif %}
19
+ {% if skill.license_id and skill.license_id != "MIT" %}
20
+ license: "{{ skill.license_id }}"
21
+ {% endif %}
22
+ {% if not skill.user_invocable %}
23
+ user_invocable: false
24
+ {% endif %}
25
+ {% if skill.requires_bins or skill.requires_env %}
26
+ requires:
27
+ {% if skill.requires_bins %}
28
+ bins:
29
+ {% for b in skill.requires_bins %}
30
+ - "{{ b }}"
31
+ {% endfor %}
32
+ {% endif %}
33
+ {% if skill.requires_env %}
34
+ env:
35
+ {% for e in skill.requires_env %}
36
+ - "{{ e }}"
37
+ {% endfor %}
38
+ {% endif %}
39
+ {% endif %}
40
+ {% if skill.primary_env %}
41
+ primary_env: "{{ skill.primary_env }}"
42
+ {% endif %}
16
43
 
17
44
  stage: {{ skill.stage }}
18
45
  phase: "{{ skill.phase }}"
@@ -153,6 +153,67 @@
153
153
  "items": { "$ref": "#/$defs/mcp_server_ref" }
154
154
  }
155
155
  }
156
+ },
157
+ "identity": {
158
+ "type": "object",
159
+ "description": "Agent identity and persona for persistent/daemon agents",
160
+ "properties": {
161
+ "persona": { "type": "string", "description": "Agent persona, behavioral rules, tone" },
162
+ "name": { "type": "string", "description": "Agent display name" },
163
+ "emoji": { "type": "string", "description": "Agent emoji identifier" },
164
+ "user_profile": { "type": "string", "description": "User profile and preferred form of address" }
165
+ }
166
+ },
167
+ "model": {
168
+ "type": "object",
169
+ "description": "Primary LLM provider configuration",
170
+ "properties": {
171
+ "provider": { "type": "string", "description": "Provider ID (anthropic, openai, google, ollama, etc.)" },
172
+ "model": { "type": "string", "description": "Model identifier" },
173
+ "api_key_env": { "type": "string", "description": "Environment variable name for API key (never a raw key)" },
174
+ "base_url": { "type": ["string", "null"], "description": "Custom endpoint URL" },
175
+ "fallback": {
176
+ "type": "array",
177
+ "description": "Failover model chain",
178
+ "items": { "$ref": "#/$defs/model_fallback" }
179
+ }
180
+ }
181
+ },
182
+ "agents": {
183
+ "type": "array",
184
+ "description": "Multi-agent declarations",
185
+ "items": { "$ref": "#/$defs/agent_decl" }
186
+ },
187
+ "sandbox": {
188
+ "type": "object",
189
+ "description": "Sandbox/container configuration for agent execution",
190
+ "properties": {
191
+ "enabled": { "type": "boolean" },
192
+ "runtime": {
193
+ "type": "string",
194
+ "enum": ["docker", "openshell"],
195
+ "description": "Sandbox runtime (docker built-in or openshell for NemoClaw)"
196
+ },
197
+ "workspace_root": { "type": "string", "description": "Workspace root inside container" }
198
+ }
199
+ },
200
+ "heartbeat": {
201
+ "type": "object",
202
+ "description": "Autonomous heartbeat scheduler configuration",
203
+ "properties": {
204
+ "interval_minutes": { "type": "integer", "minimum": 1, "description": "Minutes between heartbeat runs" },
205
+ "checklist": { "type": "string", "description": "Markdown checklist of tasks to perform each heartbeat" }
206
+ }
207
+ },
208
+ "channels": {
209
+ "type": "object",
210
+ "description": "Messaging platform integrations",
211
+ "additionalProperties": { "$ref": "#/$defs/channel_config" }
212
+ },
213
+ "mcp_servers": {
214
+ "type": "object",
215
+ "description": "Top-level MCP server declarations (keyed by server name)",
216
+ "additionalProperties": { "$ref": "#/$defs/mcp_server_decl" }
156
217
  }
157
218
  },
158
219
  "additionalProperties": false,
@@ -239,6 +300,56 @@
239
300
  },
240
301
  "url": { "type": "string", "format": "uri" }
241
302
  }
303
+ },
304
+ "model_fallback": {
305
+ "type": "object",
306
+ "required": ["provider", "model"],
307
+ "properties": {
308
+ "provider": { "type": "string" },
309
+ "model": { "type": "string" }
310
+ }
311
+ },
312
+ "agent_decl": {
313
+ "type": "object",
314
+ "required": ["id"],
315
+ "properties": {
316
+ "id": { "type": "string", "pattern": "^[a-z][a-z0-9_-]*$" },
317
+ "workspace": { "type": "string" },
318
+ "model_override": {
319
+ "type": "object",
320
+ "properties": {
321
+ "provider": { "type": "string" },
322
+ "model": { "type": "string" }
323
+ }
324
+ },
325
+ "mcp_servers": {
326
+ "type": "array",
327
+ "items": { "type": "string" }
328
+ }
329
+ }
330
+ },
331
+ "channel_config": {
332
+ "type": "object",
333
+ "properties": {
334
+ "enabled": { "type": "boolean" },
335
+ "bot_token_env": { "type": "string", "description": "Environment variable name for the bot token" }
336
+ },
337
+ "additionalProperties": true
338
+ },
339
+ "mcp_server_decl": {
340
+ "type": "object",
341
+ "properties": {
342
+ "command": { "type": "string" },
343
+ "args": {
344
+ "type": "array",
345
+ "items": { "type": "string" }
346
+ },
347
+ "env": {
348
+ "type": "object",
349
+ "additionalProperties": { "type": "string" }
350
+ },
351
+ "disabled": { "type": "boolean" }
352
+ }
242
353
  }
243
354
  }
244
355
  }
@@ -107,6 +107,41 @@
107
107
  "overrides": {
108
108
  "type": "object",
109
109
  "additionalProperties": true
110
+ },
111
+ "filesystem": {
112
+ "type": "object",
113
+ "description": "Filesystem access policy (maps to OpenShell filesystem_policy)",
114
+ "properties": {
115
+ "enforcement": {
116
+ "type": "string",
117
+ "enum": ["best_effort", "enforce"]
118
+ },
119
+ "read_only": {
120
+ "type": "array",
121
+ "items": { "type": "string" },
122
+ "description": "Paths agent can read but not write"
123
+ },
124
+ "read_write": {
125
+ "type": "array",
126
+ "items": { "type": "string" },
127
+ "description": "Paths agent can read and write"
128
+ }
129
+ }
130
+ },
131
+ "tools": {
132
+ "type": "object",
133
+ "description": "Tool execution approval policies",
134
+ "properties": {
135
+ "approval_mode": {
136
+ "type": "string",
137
+ "description": "Tool approval mode (ask, a2h, record, ignore)"
138
+ },
139
+ "assurance_levels": {
140
+ "type": "object",
141
+ "description": "Per-command assurance levels (HIGH, MEDIUM, LOW)",
142
+ "additionalProperties": { "type": "string" }
143
+ }
144
+ }
110
145
  }
111
146
  },
112
147
  "additionalProperties": false,
@@ -152,6 +152,64 @@
152
152
  "tags": {
153
153
  "type": "array",
154
154
  "items": { "type": "string" }
155
+ },
156
+ "requires": {
157
+ "type": "object",
158
+ "description": "Runtime requirements for skill activation",
159
+ "properties": {
160
+ "bins": {
161
+ "type": "array",
162
+ "items": { "type": "string" },
163
+ "description": "Binaries that must be on PATH"
164
+ },
165
+ "env": {
166
+ "type": "array",
167
+ "items": { "type": "string" },
168
+ "description": "Environment variables that must be set"
169
+ },
170
+ "config": {
171
+ "type": "array",
172
+ "items": { "type": "string" },
173
+ "description": "Config paths that must be truthy"
174
+ },
175
+ "os": {
176
+ "type": "array",
177
+ "items": { "type": "string" },
178
+ "description": "Supported operating systems (platform filter)"
179
+ }
180
+ }
181
+ },
182
+ "primary_env": {
183
+ "type": "string",
184
+ "description": "Primary environment variable for skill configuration"
185
+ },
186
+ "emoji": {
187
+ "type": "string",
188
+ "description": "Emoji identifier for the skill"
189
+ },
190
+ "license": {
191
+ "type": "string",
192
+ "description": "SPDX license identifier"
193
+ },
194
+ "user_invocable": {
195
+ "type": "boolean",
196
+ "default": true,
197
+ "description": "Whether the skill can be invoked directly by users"
198
+ },
199
+ "mcp_server": {
200
+ "type": "object",
201
+ "description": "MCP server bundled with this skill",
202
+ "properties": {
203
+ "command": { "type": "string" },
204
+ "args": {
205
+ "type": "array",
206
+ "items": { "type": "string" }
207
+ },
208
+ "env": {
209
+ "type": "object",
210
+ "additionalProperties": { "type": "string" }
211
+ }
212
+ }
155
213
  }
156
214
  },
157
215
  "additionalProperties": false,
@@ -8,6 +8,7 @@ from aes.targets._base import AgentContext, GeneratedFile, SyncPlan, SyncTarget
8
8
  from aes.targets.claude import ClaudeTarget
9
9
  from aes.targets.copilot import CopilotTarget
10
10
  from aes.targets.cursor import CursorTarget
11
+ from aes.targets.openclaw import OpenClawTarget
11
12
  from aes.targets.windsurf import WindsurfTarget
12
13
 
13
14
  TARGETS: Dict[str, Type[SyncTarget]] = {
@@ -15,6 +16,7 @@ TARGETS: Dict[str, Type[SyncTarget]] = {
15
16
  "cursor": CursorTarget,
16
17
  "copilot": CopilotTarget,
17
18
  "windsurf": WindsurfTarget,
19
+ "openclaw": OpenClawTarget,
18
20
  }
19
21
 
20
22
  TARGET_NAMES = list(TARGETS.keys())