@datalayer/agent-runtimes 1.3.15 → 1.3.17

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 (81) hide show
  1. package/README.md +1 -1
  2. package/lib/chat/base/ChatBase.js +3 -2
  3. package/lib/config/AgentConfiguration.d.ts +1 -1
  4. package/lib/loop/apps/AppRenderer.d.ts +24 -0
  5. package/lib/loop/apps/AppRenderer.js +101 -0
  6. package/lib/loop/apps/appspec.d.ts +69 -0
  7. package/lib/loop/apps/appspec.js +566 -0
  8. package/lib/loop/apps/checks.d.ts +21 -0
  9. package/lib/loop/apps/checks.js +556 -0
  10. package/lib/loop/apps/index.d.ts +10 -0
  11. package/lib/loop/apps/index.js +14 -0
  12. package/lib/loop/apps/rules.d.ts +64 -0
  13. package/lib/loop/apps/rules.js +289 -0
  14. package/lib/loop/apps/yaml.d.ts +17 -0
  15. package/lib/loop/apps/yaml.js +185 -0
  16. package/lib/loop/embed/LoopEmbed.d.ts +6 -1
  17. package/lib/loop/embed/LoopEmbed.js +3 -2
  18. package/lib/loop/index.d.ts +1 -0
  19. package/lib/loop/index.js +3 -0
  20. package/lib/loop/plugins/chat/ChatView.js +9 -4
  21. package/lib/loop/plugins/chat/index.d.ts +10 -0
  22. package/lib/loop/plugins/chat/index.js +1 -0
  23. package/lib/loop/presets.d.ts +6 -0
  24. package/lib/loop/presets.js +2 -1
  25. package/lib/loop/shell/LoopWorkspace.d.ts +14 -0
  26. package/lib/loop/shell/LoopWorkspace.js +20 -6
  27. package/lib/loop/shell/index.d.ts +1 -1
  28. package/lib/loop/shell/index.js +1 -1
  29. package/lib/loop/shell/useWorkspaceFullScreen.d.ts +17 -4
  30. package/lib/loop/shell/useWorkspaceFullScreen.js +23 -6
  31. package/lib/specs/actions.d.ts +30 -0
  32. package/lib/specs/actions.js +616 -0
  33. package/lib/specs/agents/agents.d.ts +1 -0
  34. package/lib/specs/agents/agents.js +338 -284
  35. package/lib/specs/apps.d.ts +25 -0
  36. package/lib/specs/apps.js +1064 -0
  37. package/lib/specs/cogs.d.ts +19 -0
  38. package/lib/specs/cogs.js +736 -0
  39. package/lib/specs/events.d.ts +6 -1
  40. package/lib/specs/events.js +144 -1
  41. package/lib/specs/frames.d.ts +19 -0
  42. package/lib/specs/frames.js +444 -0
  43. package/lib/specs/gates.d.ts +22 -0
  44. package/lib/specs/gates.js +177 -0
  45. package/lib/specs/guards.d.ts +26 -0
  46. package/lib/specs/guards.js +930 -0
  47. package/lib/specs/index.d.ts +10 -0
  48. package/lib/specs/index.js +10 -0
  49. package/lib/specs/mcpServers.js +2 -2
  50. package/lib/specs/memory.js +4 -0
  51. package/lib/specs/modelProviders.d.ts +22 -0
  52. package/lib/specs/modelProviders.js +102 -0
  53. package/lib/specs/models.d.ts +31 -0
  54. package/lib/specs/models.js +215 -0
  55. package/lib/specs/ops.d.ts +15 -0
  56. package/lib/specs/ops.js +1272 -0
  57. package/lib/specs/outputs.js +2 -2
  58. package/lib/specs/skills.js +1 -1
  59. package/lib/specs/teams/teams.js +18 -18
  60. package/lib/specs/tracks.d.ts +15 -0
  61. package/lib/specs/tracks.js +87 -0
  62. package/lib/specs/uiPlugins.d.ts +16 -0
  63. package/lib/specs/uiPlugins.js +40 -0
  64. package/lib/types/agentspecs.d.ts +453 -2
  65. package/lib/types/memory.d.ts +2 -0
  66. package/lib/types/models.d.ts +52 -0
  67. package/package.json +2 -1
  68. package/scripts/check-cloudflare-models.py +122 -0
  69. package/scripts/codegen/agentspecs_clone.py +53 -0
  70. package/scripts/codegen/compose.py +65 -0
  71. package/scripts/codegen/generate_agents.py +14 -4
  72. package/scripts/codegen/generate_apps.py +454 -0
  73. package/scripts/codegen/generate_cogs.py +308 -0
  74. package/scripts/codegen/generate_frames.py +278 -0
  75. package/scripts/codegen/generate_memory.py +2 -0
  76. package/scripts/codegen/generate_model_providers.py +246 -0
  77. package/scripts/codegen/generate_models.py +135 -4
  78. package/scripts/codegen/generate_ops.py +388 -0
  79. package/scripts/codegen/generate_sandbox_agents.py +1 -1
  80. package/scripts/codegen/generate_ui_plugins.py +226 -0
  81. package/scripts/gen_workers.py +2 -2
@@ -0,0 +1,308 @@
1
+ #!/usr/bin/env python3
2
+ # Copyright (c) 2025-2026 Datalayer, Inc.
3
+ # Distributed under the terms of the Modified BSD License.
4
+
5
+ """
6
+ Generate Python and TypeScript code from YAML Cog specifications.
7
+
8
+ A Cog is an AI worker you can hold to account: it extends an agent spec and is
9
+ equipped with Frames. One YAML per Cog under ``agentspecs/cogs``.
10
+
11
+ A Cog is resolved here, by ``agentspecs`` itself: the agent it extends, the
12
+ Cog's own changes, then its Frames — their skills, tools and MCP servers added
13
+ to the agent's and their context rendered onto its system prompt. What is
14
+ generated is flat: each Cog carries a complete agent spec, written by the
15
+ agent generator, with the Frames it works under and the Guards it answers to.
16
+
17
+ Usage:
18
+ python generate_cogs.py \\
19
+ --specs-dir agentspecs/agentspecs/cogs \\
20
+ --python-output agent_runtimes/specs/cogs.py \\
21
+ --typescript-output src/specs/cogs.ts
22
+ """
23
+
24
+ import argparse
25
+ import json
26
+ import re
27
+ import sys
28
+ from pathlib import Path
29
+ from typing import Any
30
+
31
+ import generate_agents
32
+ from agentspecs_clone import import_from_clone
33
+ from versioning import ensure_spec_version, version_suffix
34
+
35
+
36
+ def load_cog_specs(specs_dir: Path) -> list[dict[str, Any]]:
37
+ """Every Cog of a directory, resolved to a flat agent spec with its Frames."""
38
+ cogs = import_from_clone(specs_dir, "cogs")
39
+ raw = cogs.load_raw_cogs(specs_dir)
40
+ specs = []
41
+ for identity in sorted(raw):
42
+ ensure_spec_version(raw[identity])
43
+ flat = cogs.resolve_cog(raw[identity])
44
+ ensure_spec_version(flat)
45
+ specs.append(flat)
46
+ return specs
47
+
48
+
49
+ def _agent_const(spec: dict[str, Any]) -> str:
50
+ """The constant the agent generator gives a spec."""
51
+ base = spec["id"].upper().replace("-", "_")
52
+ suffix = "_SPEC" if spec["id"].endswith("-agent") else "_AGENTSPEC"
53
+ return base + suffix + version_suffix(spec["version"])
54
+
55
+
56
+ def _cog_const(spec: dict[str, Any]) -> str:
57
+ """The constant of a Cog: `cog-crawler` 0.0.1 → `COG_CRAWLER_0_0_1`."""
58
+ base = spec["id"].upper().replace("-", "_")
59
+ if not base.startswith("COG_"):
60
+ base += "_COG"
61
+ return base + version_suffix(spec["version"])
62
+
63
+
64
+ def _cog_fields(spec: dict[str, Any]) -> dict[str, Any]:
65
+ context = spec["frame_context"]
66
+ return {
67
+ "id": spec["id"],
68
+ "version": spec["version"],
69
+ "name": spec["name"],
70
+ "description": " ".join(str(spec.get("description") or "").split()),
71
+ "agent": spec["agent"],
72
+ "frames": list(spec["frames"]),
73
+ "lineage": list(context["lineage"]),
74
+ "kind": spec["kind"],
75
+ "enabled": bool(spec["enabled"]),
76
+ "guards": list(context["guards"]),
77
+ }
78
+
79
+
80
+ def generate_python_code(specs: list[dict[str, Any]]) -> str:
81
+ """Generate Python code from resolved Cog specifications."""
82
+ code = generate_agents.generate_python_code([("", spec) for spec in specs])
83
+ # The agent generator writes an agent library; this is the same specs,
84
+ # under the names of a Cog catalogue.
85
+ head, registry = code.split(
86
+ "# ============================================================================\n# Agent Specs Registry",
87
+ 1,
88
+ )
89
+ head = head.replace(
90
+ '"""\nAgent Library.\n\nPredefined agent specifications that can be instantiated as Agent Runtimes.\nTHIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY.\nGenerated from YAML specifications in specs/agents/\n"""',
91
+ '"""\nCog Catalog.\n\nAI workers you can hold to account: a Cog extends an agent spec and is\nequipped with Frames. Every Cog is resolved — its `spec` is a complete agent\nspec, with its Frames\' capability and context already in it.\n\nTHIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY.\nGenerated from YAML specifications in specs/cogs/\n"""',
92
+ )
93
+ if "Cog Catalog." not in head:
94
+ raise SystemExit(
95
+ "Error: the agent generator's header changed; update generate_cogs.py"
96
+ )
97
+ head = head.replace(
98
+ "from agent_runtimes.types import (\n",
99
+ "from agent_runtimes.types import (\n CogSpec,\n FrameGuardSpec,\n",
100
+ ).replace(
101
+ "# Agent Specs\n",
102
+ "# Cog Agent Specs: each Cog, resolved to the agent spec a runtime launches\n",
103
+ )
104
+ del registry
105
+
106
+ lines = [
107
+ head.rstrip("\n"),
108
+ "",
109
+ "",
110
+ "# " + "=" * 76,
111
+ "# Cog Catalog",
112
+ "# " + "=" * 76,
113
+ "",
114
+ ]
115
+ for spec in specs:
116
+ fields = _cog_fields(spec)
117
+ const = _cog_const(spec)
118
+ lines.append(f"{const} = CogSpec(")
119
+ for name, value in fields.items():
120
+ if name == "guards":
121
+ rendered = (
122
+ "["
123
+ + ", ".join(f"FrameGuardSpec(**{guard!r})" for guard in value)
124
+ + "]"
125
+ )
126
+ else:
127
+ rendered = repr(value)
128
+ lines.append(f" {name}={rendered},")
129
+ lines.extend([f" spec={_agent_const(spec)},", ")", ""])
130
+ lines.extend(["", "COG_CATALOGUE: Dict[str, CogSpec] = {"])
131
+ for spec in specs:
132
+ const = _cog_const(spec)
133
+ lines.append(f" {spec['id']!r}: {const},")
134
+ lines.extend(
135
+ [
136
+ "}",
137
+ "",
138
+ "",
139
+ "def get_cog(cog_id: str) -> CogSpec | None:",
140
+ ' """A Cog, by `id` or `id:version`, or None."""',
141
+ " cog = COG_CATALOGUE.get(cog_id)",
142
+ " if cog is not None:",
143
+ " return cog",
144
+ ' base, _, version = cog_id.rpartition(":")',
145
+ ' return COG_CATALOGUE.get(base) if base and "." in version else None',
146
+ "",
147
+ "",
148
+ "def list_cogs() -> list[CogSpec]:",
149
+ ' """Every Cog of the catalogue, resolved."""',
150
+ " return list(COG_CATALOGUE.values())",
151
+ "",
152
+ "",
153
+ "def cogs_using(frame_id: str) -> list[CogSpec]:",
154
+ ' """The Cogs that work under a Frame, directly or through one that extends it."""',
155
+ ' base, _, version = frame_id.rpartition(":")',
156
+ ' wanted = base if base and "." in version else frame_id',
157
+ " return [cog for cog in COG_CATALOGUE.values() if wanted in cog.lineage]",
158
+ "",
159
+ ]
160
+ )
161
+ return "\n".join(lines)
162
+
163
+
164
+ def generate_typescript_code(specs: list[dict[str, Any]], specs_dir: Path) -> str:
165
+ """Generate TypeScript code from resolved Cog specifications."""
166
+ root = specs_dir.resolve().parent
167
+ code = generate_agents.generate_typescript_code(
168
+ [("", spec) for spec in specs],
169
+ str(root / "mcp-servers"),
170
+ str(root / "skills"),
171
+ str(root / "tools"),
172
+ )
173
+ marker = "// ============================================================================\n// Agent Specs Registry"
174
+ if marker not in code:
175
+ raise SystemExit(
176
+ "Error: the agent generator's registry changed; update generate_cogs.py"
177
+ )
178
+ head = code.split(marker, 1)[0]
179
+ if "import type { Agentspec }" in head:
180
+ head = head.replace(
181
+ "import type { Agentspec }", "import type { Agentspec, CogSpec }", 1
182
+ )
183
+ elif "import type {\n Agentspec," in head:
184
+ head = head.replace(
185
+ "import type {\n Agentspec,", "import type {\n Agentspec,\n CogSpec,", 1
186
+ )
187
+ else:
188
+ raise SystemExit(
189
+ "Error: the agent generator's type import changed; update generate_cogs.py"
190
+ )
191
+ # The resolved agent specs are this module's own: a Cog's `spec` exposes them.
192
+ head = head.replace("export const ", "const ").replace(
193
+ "Generated from YAML specifications in specs/agents/",
194
+ "Generated from YAML specifications in specs/cogs/",
195
+ )
196
+ described = (
197
+ " * Cog Catalog.\n *\n * AI workers you can hold to account: a Cog extends an agent spec and is\n"
198
+ " * equipped with Frames. Every Cog is resolved — its `spec` is a complete\n"
199
+ " * agent spec, with its Frames' capability and context already in it.\n"
200
+ )
201
+ library = " * Agent Library.\n *\n * Predefined agent specifications that can be instantiated as Agent Runtimes.\n"
202
+ if library not in head:
203
+ raise SystemExit(
204
+ "Error: the agent generator's header changed; update generate_cogs.py"
205
+ )
206
+ head = head.replace(library, described)
207
+ # The agent generator writes for `src/specs/agents/agents.ts`; this file
208
+ # is one directory up, beside the catalogues it imports.
209
+ head = head.replace("from '../../types'", "from '../types'")
210
+ head = re.sub(
211
+ r"from '\.\./(mcpServers|skills|tools|frontendTools)'", r"from './\1'", head
212
+ )
213
+
214
+ lines = [
215
+ head.rstrip("\n"),
216
+ "",
217
+ "// " + "=" * 76,
218
+ "// Cog Catalog",
219
+ "// " + "=" * 76,
220
+ "",
221
+ ]
222
+ for spec in specs:
223
+ fields = _cog_fields(spec)
224
+ const = _cog_const(spec)
225
+ body = json.dumps(fields, indent=2, ensure_ascii=False)
226
+ body = (
227
+ body.rstrip().rstrip("}").rstrip()
228
+ + f',\n "spec": {_agent_const(spec)}\n}}'
229
+ )
230
+ lines.extend([f"export const {const}: CogSpec = {body};", ""])
231
+ lines.append("export const COG_CATALOGUE: Record<string, CogSpec> = {")
232
+ for spec in specs:
233
+ const = _cog_const(spec)
234
+ lines.append(f" {json.dumps(spec['id'])}: {const},")
235
+ lines.extend(
236
+ [
237
+ "};",
238
+ "",
239
+ "function cogId(ref: string): string {",
240
+ " const at = ref.lastIndexOf(':');",
241
+ " return at > 0 && ref.slice(at + 1).includes('.') ? ref.slice(0, at) : ref;",
242
+ "}",
243
+ "",
244
+ "/** A Cog, by `id` or `id:version`, or undefined. */",
245
+ "export function getCog(ref: string): CogSpec | undefined {",
246
+ " // Own entries only: `constructor` and `toString` are not Cogs.",
247
+ " const id = cogId(ref);",
248
+ " return Object.prototype.hasOwnProperty.call(COG_CATALOGUE, id)",
249
+ " ? COG_CATALOGUE[id]",
250
+ " : undefined;",
251
+ "}",
252
+ "",
253
+ "export function listCogs(): CogSpec[] {",
254
+ " return Object.values(COG_CATALOGUE);",
255
+ "}",
256
+ "",
257
+ "/** The Cogs that work under a Frame, directly or through one that extends it. */",
258
+ "export function cogsUsing(frameId: string): CogSpec[] {",
259
+ " const wanted = cogId(frameId);",
260
+ " return listCogs().filter(cog => cog.lineage.includes(wanted));",
261
+ "}",
262
+ "",
263
+ ]
264
+ )
265
+ return "\n".join(lines)
266
+
267
+
268
+ def main():
269
+ parser = argparse.ArgumentParser(
270
+ description="Generate Python and TypeScript code from YAML Cog specifications"
271
+ )
272
+ parser.add_argument(
273
+ "--specs-dir",
274
+ type=Path,
275
+ required=True,
276
+ help="Directory containing Cog YAML files",
277
+ )
278
+ parser.add_argument(
279
+ "--python-output", type=Path, required=True, help="Output path for Python file"
280
+ )
281
+ parser.add_argument(
282
+ "--typescript-output",
283
+ type=Path,
284
+ required=True,
285
+ help="Output path for TypeScript file",
286
+ )
287
+ args = parser.parse_args()
288
+
289
+ if not args.specs_dir.exists():
290
+ print(f"Error: Specs directory not found: {args.specs_dir}", file=sys.stderr)
291
+ sys.exit(1)
292
+ specs = load_cog_specs(args.specs_dir)
293
+ if not specs:
294
+ print(
295
+ f"Warning: No Cog specifications found in {args.specs_dir}", file=sys.stderr
296
+ )
297
+ return
298
+ args.python_output.parent.mkdir(parents=True, exist_ok=True)
299
+ args.python_output.write_text(generate_python_code(specs))
300
+ print(f"Generated Python code: {args.python_output}")
301
+ args.typescript_output.parent.mkdir(parents=True, exist_ok=True)
302
+ args.typescript_output.write_text(generate_typescript_code(specs, args.specs_dir))
303
+ print(f"Generated TypeScript code: {args.typescript_output}")
304
+ print(f"\n✓ Successfully generated code from {len(specs)} Cog specs")
305
+
306
+
307
+ if __name__ == "__main__":
308
+ main()
@@ -0,0 +1,278 @@
1
+ #!/usr/bin/env python3
2
+ # Copyright (c) 2025-2026 Datalayer, Inc.
3
+ # Distributed under the terms of the Modified BSD License.
4
+
5
+ """
6
+ Generate Python and TypeScript code from YAML Frame specifications.
7
+
8
+ A Frame is the context work happens in, written down — rules, terminology,
9
+ goals, style, norms, process — owned, scoped, versioned and inherited, with
10
+ the Guards an output has to pass. One YAML per Frame under
11
+ ``agentspecs/frames``; a Cog names the Frames it works under.
12
+
13
+ A Frame inherits with ``extends``. It is resolved here, by ``agentspecs``
14
+ itself, so the generated catalogue is flat: every Frame carries what it
15
+ inherits, and ``lineage`` says from which Frames.
16
+
17
+ Usage:
18
+ python generate_frames.py \\
19
+ --specs-dir agentspecs/agentspecs/frames \\
20
+ --python-output agent_runtimes/specs/frames.py \\
21
+ --typescript-output src/specs/frames.ts
22
+ """
23
+
24
+ import argparse
25
+ import json
26
+ import sys
27
+ from pathlib import Path
28
+ from typing import Any
29
+
30
+ from agentspecs_clone import import_from_clone
31
+ from versioning import ensure_spec_version, version_suffix
32
+
33
+ #: The fields of a generated Frame, in the order they are written.
34
+ FIELDS = (
35
+ "id",
36
+ "version",
37
+ "name",
38
+ "description",
39
+ "scope",
40
+ "owner",
41
+ "extends",
42
+ "lineage",
43
+ "tags",
44
+ "enabled",
45
+ "icon",
46
+ "emoji",
47
+ "rules",
48
+ "terminology",
49
+ "goals",
50
+ "style",
51
+ "norms",
52
+ "process",
53
+ "architecture",
54
+ "prompts",
55
+ "skills",
56
+ "tools",
57
+ "mcp_servers",
58
+ "guards",
59
+ )
60
+
61
+ #: Python field → TypeScript property, where they differ.
62
+ CAMEL = {"mcp_servers": "mcpServers"}
63
+
64
+
65
+ def load_frame_specs(specs_dir: Path) -> list[dict[str, Any]]:
66
+ """Every Frame of a directory, resolved and validated, as plain data."""
67
+ frames = import_from_clone(specs_dir, "frames")
68
+ raw = frames.load_raw_frames(specs_dir)
69
+ specs = []
70
+ for identity in sorted(raw):
71
+ ensure_spec_version(raw[identity])
72
+ resolved = frames.resolve_frame(raw[identity], raw)
73
+ lineage = list(resolved["lineage"])
74
+ spec = frames.FrameSpec(
75
+ **{key: value for key, value in resolved.items() if key != "lineage"}
76
+ ).model_dump(mode="json")
77
+ spec["description"] = " ".join(str(spec["description"]).split())
78
+ spec["extends"] = raw[identity].get("extends") or None
79
+ # Tags describe the Frame itself; its parent's are the parent's.
80
+ spec["tags"] = list(raw[identity].get("tags") or [])
81
+ spec["lineage"] = lineage
82
+ specs.append({field: spec.get(field) for field in FIELDS})
83
+ return specs
84
+
85
+
86
+ def _const_name(spec: dict[str, Any]) -> str:
87
+ base = spec["id"].replace("-", "_").replace(".", "_").upper()
88
+ return f"{base}_FRAME{version_suffix(spec['version'])}"
89
+
90
+
91
+ def generate_python_code(specs: list[dict[str, Any]]) -> str:
92
+ """Generate Python code from Frame specifications."""
93
+ lines = [
94
+ "# Copyright (c) 2025-2026 Datalayer, Inc.",
95
+ "# Distributed under the terms of the Modified BSD License.",
96
+ '"""',
97
+ "Frame Catalog.",
98
+ "",
99
+ "The context work happens in, written down: owned, scoped and inherited.",
100
+ "Every Frame is resolved — what it inherits is already in it.",
101
+ "",
102
+ "This file is AUTO-GENERATED from YAML specifications.",
103
+ "DO NOT EDIT MANUALLY - run 'make specs' to regenerate.",
104
+ '"""',
105
+ "",
106
+ "from typing import Dict",
107
+ "",
108
+ "from agent_runtimes.types import FrameGuardSpec, FramePromptSpec, FrameSpec",
109
+ "",
110
+ "",
111
+ "# " + "=" * 76,
112
+ "# Frame Definitions",
113
+ "# " + "=" * 76,
114
+ "",
115
+ ]
116
+ for spec in specs:
117
+ lines.append(f"{_const_name(spec)} = FrameSpec(")
118
+ for field in FIELDS:
119
+ value = spec[field]
120
+ if field == "guards":
121
+ rendered = (
122
+ "["
123
+ + ", ".join(f"FrameGuardSpec(**{guard!r})" for guard in value)
124
+ + "]"
125
+ )
126
+ elif field == "prompts":
127
+ rendered = (
128
+ "["
129
+ + ", ".join(f"FramePromptSpec(**{prompt!r})" for prompt in value)
130
+ + "]"
131
+ )
132
+ else:
133
+ rendered = repr(value)
134
+ lines.append(f" {field}={rendered},")
135
+ lines.extend([")", ""])
136
+ lines.extend(
137
+ [
138
+ "",
139
+ "# " + "=" * 76,
140
+ "# Frame Catalog",
141
+ "# " + "=" * 76,
142
+ "",
143
+ "FRAME_CATALOGUE: Dict[str, FrameSpec] = {",
144
+ ]
145
+ )
146
+ for spec in specs:
147
+ lines.append(f" {spec['id']!r}: {_const_name(spec)},")
148
+ lines.extend(
149
+ [
150
+ "}",
151
+ "",
152
+ "",
153
+ "def get_frame(frame_id: str) -> FrameSpec | None:",
154
+ ' """The Frame a Cog names, by `id` or `id:version`, or None."""',
155
+ " frame = FRAME_CATALOGUE.get(frame_id)",
156
+ " if frame is not None:",
157
+ " return frame",
158
+ ' base, _, version = frame_id.rpartition(":")',
159
+ ' return FRAME_CATALOGUE.get(base) if base and "." in version else None',
160
+ "",
161
+ "",
162
+ "def list_frames() -> list[FrameSpec]:",
163
+ ' """Every Frame of the catalogue, resolved."""',
164
+ " return list(FRAME_CATALOGUE.values())",
165
+ "",
166
+ ]
167
+ )
168
+ return "\n".join(lines)
169
+
170
+
171
+ def _ts_object(spec: dict[str, Any]) -> str:
172
+ camel = {
173
+ CAMEL.get(field, field): spec[field]
174
+ for field in FIELDS
175
+ if spec[field] is not None
176
+ }
177
+ return json.dumps(camel, indent=2, ensure_ascii=False)
178
+
179
+
180
+ def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
181
+ """Generate TypeScript code from Frame specifications."""
182
+ lines = [
183
+ "/*",
184
+ " * Copyright (c) 2025-2026 Datalayer, Inc.",
185
+ " * Distributed under the terms of the Modified BSD License.",
186
+ " */",
187
+ "",
188
+ "/**",
189
+ " * Frame Catalog.",
190
+ " *",
191
+ " * The context work happens in, written down: owned, scoped and inherited.",
192
+ " * Every Frame is resolved — what it inherits is already in it.",
193
+ " *",
194
+ " * This file is AUTO-GENERATED from YAML specifications.",
195
+ " * DO NOT EDIT MANUALLY - run 'make specs' to regenerate.",
196
+ " */",
197
+ "",
198
+ "import type { FrameSpec } from '../types/agentspecs';",
199
+ "",
200
+ ]
201
+ for spec in specs:
202
+ lines.extend(
203
+ [f"export const {_const_name(spec)}: FrameSpec = {_ts_object(spec)};", ""]
204
+ )
205
+ lines.append("export const FRAME_CATALOGUE: Record<string, FrameSpec> = {")
206
+ for spec in specs:
207
+ lines.append(f" {json.dumps(spec['id'])}: {_const_name(spec)},")
208
+ lines.extend(
209
+ [
210
+ "};",
211
+ "",
212
+ "/** The Frame a Cog names, by `id` or `id:version`, or undefined. */",
213
+ "export function getFrame(frameId: string): FrameSpec | undefined {",
214
+ " // Own entries only: `constructor` and `toString` are not Frames.",
215
+ " const own = (id: string): FrameSpec | undefined =>",
216
+ " Object.prototype.hasOwnProperty.call(FRAME_CATALOGUE, id)",
217
+ " ? FRAME_CATALOGUE[id]",
218
+ " : undefined;",
219
+ " const at = frameId.lastIndexOf(':');",
220
+ " return (",
221
+ " own(frameId) ??",
222
+ " (at > 0 && frameId.slice(at + 1).includes('.')",
223
+ " ? own(frameId.slice(0, at))",
224
+ " : undefined)",
225
+ " );",
226
+ "}",
227
+ "",
228
+ "export function listFrames(): FrameSpec[] {",
229
+ " return Object.values(FRAME_CATALOGUE);",
230
+ "}",
231
+ "",
232
+ ]
233
+ )
234
+ return "\n".join(lines)
235
+
236
+
237
+ def main():
238
+ parser = argparse.ArgumentParser(
239
+ description="Generate Python and TypeScript code from YAML Frame specifications"
240
+ )
241
+ parser.add_argument(
242
+ "--specs-dir",
243
+ type=Path,
244
+ required=True,
245
+ help="Directory containing Frame YAML files",
246
+ )
247
+ parser.add_argument(
248
+ "--python-output", type=Path, required=True, help="Output path for Python file"
249
+ )
250
+ parser.add_argument(
251
+ "--typescript-output",
252
+ type=Path,
253
+ required=True,
254
+ help="Output path for TypeScript file",
255
+ )
256
+ args = parser.parse_args()
257
+
258
+ if not args.specs_dir.exists():
259
+ print(f"Error: Specs directory not found: {args.specs_dir}", file=sys.stderr)
260
+ sys.exit(1)
261
+ specs = load_frame_specs(args.specs_dir)
262
+ if not specs:
263
+ print(
264
+ f"Warning: No Frame specifications found in {args.specs_dir}",
265
+ file=sys.stderr,
266
+ )
267
+ return
268
+ args.python_output.parent.mkdir(parents=True, exist_ok=True)
269
+ args.python_output.write_text(generate_python_code(specs))
270
+ print(f"Generated Python code: {args.python_output}")
271
+ args.typescript_output.parent.mkdir(parents=True, exist_ok=True)
272
+ args.typescript_output.write_text(generate_typescript_code(specs))
273
+ print(f"Generated TypeScript code: {args.typescript_output}")
274
+ print(f"\n✓ Successfully generated code from {len(specs)} Frame specs")
275
+
276
+
277
+ if __name__ == "__main__":
278
+ main()
@@ -119,6 +119,7 @@ def generate_python_code(specs: list[dict[str, Any]]) -> str:
119
119
  f' persistence="{spec.get("persistence", "none")}",',
120
120
  f' scope="{spec.get("scope", "agent")}",',
121
121
  f' backend="{spec.get("backend", "in-memory")}",',
122
+ f" enabled={bool(spec.get('enabled', False))},",
122
123
  f" icon={icon},",
123
124
  f" emoji={emoji},",
124
125
  ")",
@@ -268,6 +269,7 @@ def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
268
269
  f" persistence: '{spec.get('persistence', 'none')}',",
269
270
  f" scope: '{spec.get('scope', 'agent')}',",
270
271
  f" backend: '{spec.get('backend', 'in-memory')}',",
272
+ f" enabled: {'true' if spec.get('enabled', False) else 'false'},",
271
273
  f" icon: '{spec.get('icon', 'database')}',",
272
274
  f" emoji: '{spec.get('emoji', '🧠')}',",
273
275
  "};",