capability-compiler 0.1.0__tar.gz → 0.1.2__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 (137) hide show
  1. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/PKG-INFO +1 -1
  2. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/_version.py +1 -1
  3. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/adapters/fake_adapter.py +56 -1
  4. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/cli/_common.py +24 -0
  5. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/cli/execute_cmd.py +15 -0
  6. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/cli/learn_cmd.py +6 -1
  7. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/server/executor_factory.py +6 -2
  8. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/synthesis/engine.py +27 -2
  9. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_mcp_server.py +57 -0
  10. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_synthesis.py +28 -0
  11. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/.gitignore +0 -0
  12. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/LICENSE +0 -0
  13. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/README.md +0 -0
  14. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/adapters/browser.md +0 -0
  15. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/architecture/README.md +0 -0
  16. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/architecture/mcp.md +0 -0
  17. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/architecture/registry.md +0 -0
  18. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/architecture.md +0 -0
  19. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/benchmarks.md +0 -0
  20. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/ci.md +0 -0
  21. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/cli/commands.md +0 -0
  22. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/concepts/exploration.md +0 -0
  23. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/concepts/local-first.md +0 -0
  24. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/master-plan.md +0 -0
  25. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/models.md +0 -0
  26. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/research/differentiation.md +0 -0
  27. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/research/landscape.md +0 -0
  28. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/research/sources/benchmarks-skill-learning.md +0 -0
  29. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/research/sources/desktop-automation.md +0 -0
  30. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/research/sources/gui-agents-vision.md +0 -0
  31. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/research/sources/mcp-ecosystem.md +0 -0
  32. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/docs/research/sources/packaging-security.md +0 -0
  33. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/pyproject.toml +0 -0
  34. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/__init__.py +0 -0
  35. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/adapters/__init__.py +0 -0
  36. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/adapters/base.py +0 -0
  37. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/adapters/browser/__init__.py +0 -0
  38. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/adapters/browser/adapter.py +0 -0
  39. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/adapters/browser/aria.py +0 -0
  40. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/adapters/browser/elements.py +0 -0
  41. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/adapters/desktop/__init__.py +0 -0
  42. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/adapters/desktop/base.py +0 -0
  43. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/benchmark/__init__.py +0 -0
  44. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/benchmark/data/capability_bench.json +0 -0
  45. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/benchmark/data/seed.json +0 -0
  46. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/benchmark/report.py +0 -0
  47. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/benchmark/spec.py +0 -0
  48. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/benchmark/suite.py +0 -0
  49. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/cli/__init__.py +0 -0
  50. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/cli/_register.py +0 -0
  51. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/cli/benchmark_cmd.py +0 -0
  52. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/cli/config_cmds.py +0 -0
  53. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/cli/main.py +0 -0
  54. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/cli/registry_cmds.py +0 -0
  55. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/cli/serve_cmd.py +0 -0
  56. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/compiler.py +0 -0
  57. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/config.py +0 -0
  58. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/errors.py +0 -0
  59. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/exploration/__init__.py +0 -0
  60. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/exploration/effects.py +0 -0
  61. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/exploration/engine.py +0 -0
  62. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/exploration/semantics.py +0 -0
  63. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/logging.py +0 -0
  64. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/models/__init__.py +0 -0
  65. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/models/action.py +0 -0
  66. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/models/capability.py +0 -0
  67. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/models/observation.py +0 -0
  68. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/models/state.py +0 -0
  69. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/models/transition.py +0 -0
  70. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/models/verification.py +0 -0
  71. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/models/versioning.py +0 -0
  72. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/perception/__init__.py +0 -0
  73. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/perception/pipeline.py +0 -0
  74. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/perception/semantic.py +0 -0
  75. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/providers/__init__.py +0 -0
  76. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/providers/anthropic.py +0 -0
  77. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/providers/base.py +0 -0
  78. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/providers/http.py +0 -0
  79. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/providers/mock.py +0 -0
  80. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/providers/ollama.py +0 -0
  81. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/providers/openai_compat.py +0 -0
  82. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/recording/__init__.py +0 -0
  83. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/recording/recorder.py +0 -0
  84. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/recording/replay.py +0 -0
  85. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/refinement/__init__.py +0 -0
  86. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/refinement/engine.py +0 -0
  87. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/refinement/repair.py +0 -0
  88. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/registry/__init__.py +0 -0
  89. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/registry/permissions.py +0 -0
  90. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/registry/registry.py +0 -0
  91. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/runtime/__init__.py +0 -0
  92. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/runtime/assertions.py +0 -0
  93. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/runtime/executor.py +0 -0
  94. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/server/__init__.py +0 -0
  95. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/server/auth.py +0 -0
  96. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/server/server.py +0 -0
  97. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/server/tool_defs.py +0 -0
  98. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/storage/__init__.py +0 -0
  99. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/storage/base.py +0 -0
  100. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/storage/filesystem.py +0 -0
  101. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/storage/sqlite.py +0 -0
  102. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/synthesis/__init__.py +0 -0
  103. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/synthesis/templating.py +0 -0
  104. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/types.py +0 -0
  105. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/verification/__init__.py +0 -0
  106. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/verification/base.py +0 -0
  107. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/verification/file_visual.py +0 -0
  108. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/verification/runner.py +0 -0
  109. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/src/capability_compiler/verification/verifiers.py +0 -0
  110. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/conftest.py +0 -0
  111. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/fixtures/site/index.html +0 -0
  112. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_action_semantics.py +0 -0
  113. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_aria_parser.py +0 -0
  114. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_benchmark.py +0 -0
  115. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_browser_adapter.py +0 -0
  116. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_browser_unit.py +0 -0
  117. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_cli.py +0 -0
  118. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_compile_e2e.py +0 -0
  119. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_compiler.py +0 -0
  120. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_config.py +0 -0
  121. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_e2e_record_replay.py +0 -0
  122. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_errors.py +0 -0
  123. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_executor.py +0 -0
  124. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_exploration.py +0 -0
  125. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_exploration_e2e.py +0 -0
  126. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_logging.py +0 -0
  127. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_models.py +0 -0
  128. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_perception.py +0 -0
  129. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_providers_http.py +0 -0
  130. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_recording.py +0 -0
  131. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_refinement.py +0 -0
  132. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_refinement_e2e.py +0 -0
  133. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_registry.py +0 -0
  134. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_state_consistency_eval.py +0 -0
  135. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_storage.py +0 -0
  136. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_types.py +0 -0
  137. {capability_compiler-0.1.0 → capability_compiler-0.1.2}/tests/test_verifiers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: capability-compiler
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Turn any software into an API for AI: compile interaction trajectories into verified, reusable capabilities exposed via Python, CLI, and MCP.
5
5
  Project-URL: Homepage, https://github.com/capability-compiler/capability-compiler
6
6
  Project-URL: Documentation, https://github.com/capability-compiler/capability-compiler/tree/main/docs
@@ -5,4 +5,4 @@ without importing the whole package (pyproject reads it at build time via
5
5
  hatchling's version hook — see ``[tool.hatch.version]``).
6
6
  """
7
7
 
8
- __version__ = "0.1.0"
8
+ __version__ = "0.1.2"
@@ -96,6 +96,46 @@ class FakeAdapter:
96
96
 
97
97
  # -- test/Bench helpers -----------------------------------------------------
98
98
 
99
+ def seed_demo_page(self, *, url: str = "demo://page", title: str = "Demo Page") -> None:
100
+ """Plant the deterministic three-element demo page.
101
+
102
+ A bare fake adapter starts empty — exploration has nothing to
103
+ compile and execution fails its preconditions (the elements a
104
+ learned capability refers to don't exist). This page (a Docs link,
105
+ a Search box, a Submit button) is the canonical offline demo
106
+ environment: ``cc learn``/``cc execute`` and the MCP server's
107
+ default executor all seed it so the full
108
+ learn → compile → persist → execute loop works with zero setup.
109
+ """
110
+ self.set_state(
111
+ url=url,
112
+ title=title,
113
+ elements=[
114
+ InteractiveElement(
115
+ element_id="nav_docs",
116
+ ref="nav-docs",
117
+ role="link",
118
+ name="Docs",
119
+ action_hints=["click"],
120
+ ),
121
+ InteractiveElement(
122
+ element_id="search_box",
123
+ ref="search-box",
124
+ role="searchbox",
125
+ name="Search",
126
+ action_hints=["type", "click"],
127
+ ),
128
+ InteractiveElement(
129
+ element_id="submit_btn",
130
+ ref="submit-btn",
131
+ role="button",
132
+ name="Submit",
133
+ action_hints=["click"],
134
+ ),
135
+ ],
136
+ text_digest="demo page with docs link, search box, and submit button",
137
+ )
138
+
99
139
  def set_state(
100
140
  self,
101
141
  *,
@@ -227,6 +267,21 @@ class FakeAdapter:
227
267
  # -- registration ---------------------------------------------------------------
228
268
 
229
269
 
270
+ def seed_demo_page(adapter: Any, url: str) -> bool:
271
+ """Seed *adapter* with the demo page when (and only when) it is a FakeAdapter.
272
+
273
+ Real adapters (browser/desktop) are never touched — their state belongs
274
+ to the live environment. Returns ``True`` when the demo page was planted.
275
+
276
+ The query string is stripped so ``https://example.com/docs?q=x`` and
277
+ ``https://example.com/docs`` land on the same deterministic page.
278
+ """
279
+ if not isinstance(adapter, FakeAdapter):
280
+ return False
281
+ adapter.seed_demo_page(url=url.rsplit("?", 1)[0])
282
+ return True
283
+
284
+
230
285
  def _factory(settings: Any | None = None) -> FakeAdapter:
231
286
  return FakeAdapter(settings)
232
287
 
@@ -234,7 +289,7 @@ def _factory(settings: Any | None = None) -> FakeAdapter:
234
289
  register_adapter("fake", _factory, description=FakeAdapter.description, replace=True)
235
290
 
236
291
 
237
- __all__ = ["FakeAdapter"]
292
+ __all__ = ["FakeAdapter", "seed_demo_page"]
238
293
 
239
294
 
240
295
  def id_for(prefix: str = "fake") -> str:
@@ -33,6 +33,7 @@ __all__ = [
33
33
  "parse_key_path",
34
34
  "parse_key_value",
35
35
  "run_async",
36
+ "seed_fake_demo",
36
37
  ]
37
38
 
38
39
 
@@ -133,3 +134,26 @@ def parse_key_path(key_path: str) -> list[str]:
133
134
  msg = f"key.path must be 'section.key' (min 2 segments), got {key_path!r}"
134
135
  raise ValueError(msg)
135
136
  return parts
137
+
138
+
139
+ # -- fake-adapter demo seeding ----------------------------------------------------
140
+
141
+
142
+ def seed_fake_demo(adapter_obj: Any, url: str) -> bool:
143
+ """Seed a FakeAdapter with the small demo page so the pipeline has work.
144
+
145
+ The fake adapter starts empty — with no interactive elements exploration
146
+ runs dry (nothing to compile) and execution fails its preconditions (the
147
+ elements a learned capability refers to don't exist). When (and only
148
+ when) the attached adapter is the in-memory FakeAdapter, plant a
149
+ deterministic three-element demo page (Docs link, Search box, Submit
150
+ button) so the full learn → compile → persist → execute loop works
151
+ offline out of the box. Real adapters (browser/desktop) are never
152
+ touched.
153
+
154
+ Returns:
155
+ ``True`` when the demo page was planted, ``False`` otherwise.
156
+ """
157
+ from capability_compiler.adapters.fake_adapter import seed_demo_page
158
+
159
+ return seed_demo_page(adapter_obj, url)
@@ -27,6 +27,7 @@ from capability_compiler.cli._common import (
27
27
  load_registry,
28
28
  load_settings_cached,
29
29
  run_async,
30
+ seed_fake_demo,
30
31
  )
31
32
  from capability_compiler.errors import CapabilityCompilerError, PermissionDeniedError
32
33
 
@@ -117,6 +118,9 @@ def execute(
117
118
  compiler = _build_compiler(settings, adapter)
118
119
  try:
119
120
  run_async(compiler.connect())
121
+ # Give the offline fake adapter the same demo page the capability was
122
+ # learned from, so its preconditions (element presence) can hold.
123
+ seed_fake_demo(getattr(compiler, "adapter", None), _start_url(capability))
120
124
  result = run_async(compiler.execute(capability, inputs, confirmed=confirmed))
121
125
  except CapabilityCompilerError as exc:
122
126
  _fail(exc, json_mode=json_mode)
@@ -171,6 +175,17 @@ def _build_compiler(settings: Any, adapter: str) -> Any:
171
175
  return Compiler(settings, adapter_kind=adapter)
172
176
 
173
177
 
178
+ def _start_url(capability: Any) -> str:
179
+ """First navigation URL in the capability's procedure (fallback: app://x)."""
180
+ from capability_compiler.models.action import NavigateAction
181
+
182
+ for step in capability.procedure.steps:
183
+ action = getattr(step, "action", None)
184
+ if isinstance(action, NavigateAction):
185
+ return action.url
186
+ return "app://x"
187
+
188
+
174
189
  def _render_execution(result: Any) -> None:
175
190
  typer.echo(f"# {result.capability} @ {result.version} — {result.status.value}")
176
191
  typer.echo(f" duration_ms: {result.duration_ms:.1f}")
@@ -22,6 +22,7 @@ from capability_compiler.cli._common import (
22
22
  _wants_json,
23
23
  load_settings_cached,
24
24
  run_async,
25
+ seed_fake_demo,
25
26
  )
26
27
  from capability_compiler.errors import CapabilityCompilerError
27
28
  from capability_compiler.models.action import NavigateAction
@@ -61,7 +62,11 @@ def learn(
61
62
  run_async(compiler.connect())
62
63
  try:
63
64
  target_url = start_url or url
64
- if _can_navigate(compiler):
65
+ if seed_fake_demo(getattr(compiler, "adapter", None), target_url):
66
+ # The demo page already set the URL; navigating again would
67
+ # be a no-op on the fake adapter.
68
+ pass
69
+ elif _can_navigate(compiler):
65
70
  run_async(
66
71
  compiler.adapter.execute(NavigateAction(url=target_url))
67
72
  if compiler.adapter
@@ -49,12 +49,16 @@ def build_default_factory() -> ExecutorFactory:
49
49
 
50
50
  The default keeps the MCP server runnable end-to-end with zero setup so
51
51
  `cc doctor`-style smoke tests succeed even before a real adapter is
52
- registered. Production deployments should override this with the real
53
- adapter (browser/desktop) at the CLI wiring layer.
52
+ registered. The adapter is seeded with the same demo page ``cc learn``
53
+ uses offline, so capabilities learned from that page hold their
54
+ preconditions when invoked over MCP. Production deployments should
55
+ override this with the real adapter (browser/desktop) at the CLI wiring
56
+ layer.
54
57
  """
55
58
 
56
59
  def factory(settings: CompilerSettings) -> CapabilityExecutor:
57
60
  adapter = FakeAdapter(settings)
61
+ adapter.seed_demo_page()
58
62
  log.debug(
59
63
  "executor_factory.default_built",
60
64
  extra={"extra_fields": {"adapter": adapter.kind}},
@@ -31,6 +31,7 @@ from __future__ import annotations
31
31
  import re
32
32
  from typing import TYPE_CHECKING, Any
33
33
 
34
+ from capability_compiler.errors import CapabilityCompilerError
34
35
  from capability_compiler.logging import StructuredLogAdapter, get_logger
35
36
  from capability_compiler.models import (
36
37
  AssertionKind,
@@ -145,6 +146,24 @@ class CapabilitySynthesizer:
145
146
 
146
147
  effects = self._derive_effects(hypotheses)
147
148
 
149
+ if not procedure_steps:
150
+ # Nothing demonstrable happened: the trajectory had no successful
151
+ # actions to compile (empty environment, exploration cut short,
152
+ # or every step failed). A capability with an empty procedure is
153
+ # invalid (it could never execute), so fail loudly with guidance
154
+ # instead of surfacing a raw pydantic validation error.
155
+ raise CapabilityCompilerError(
156
+ "no successful actions in trajectory: cannot compile a capability "
157
+ "from an empty procedure — record at least one successful action "
158
+ "(check that the target environment exposes interactive elements)",
159
+ code="synthesis.empty_procedure",
160
+ context={
161
+ "trajectory_id": trajectory.trajectory_id,
162
+ "total_steps": len(trajectory.steps),
163
+ "skipped_steps": report.skipped_steps,
164
+ },
165
+ )
166
+
148
167
  capability = Capability(
149
168
  name=name,
150
169
  description=description,
@@ -267,7 +286,13 @@ class CapabilitySynthesizer:
267
286
  name=pname,
268
287
  type="string",
269
288
  description=f"Value for '{element_name}'",
270
- required=True,
289
+ # The recorded literal is the default ("literals become
290
+ # defaults, not guesses"): callers may omit the input and
291
+ # the executor/template binder falls back to it. Keeping
292
+ # required=True here would contradict the default and make
293
+ # MCP tool calls with omitted arguments fail SDK-side
294
+ # validation before the executor could apply the default.
295
+ required=False,
271
296
  default=action.text,
272
297
  example=action.text,
273
298
  )
@@ -279,7 +304,7 @@ class CapabilitySynthesizer:
279
304
  name=pname,
280
305
  type="string",
281
306
  description=f"Selected value for '{element_name}'",
282
- required=True,
307
+ required=False, # recorded literal is the default (see above)
283
308
  default=action.value,
284
309
  example=action.value,
285
310
  )
@@ -29,6 +29,7 @@ from capability_compiler.models import (
29
29
  NavigateAction,
30
30
  PermissionScope,
31
31
  ProcedureStep,
32
+ TypeAction,
32
33
  )
33
34
  from capability_compiler.registry import CapabilityRegistry
34
35
  from capability_compiler.server import (
@@ -233,6 +234,62 @@ class TestExecuteTool:
233
234
  assert "navigate" in captured["adapter"].calls
234
235
  assert "click" in captured["adapter"].calls
235
236
 
237
+ async def test_default_factory_runs_demo_capability_with_omitted_inputs(
238
+ self, tmp_path: Path
239
+ ) -> None:
240
+ """Regression: demo-learned capabilities run over MCP with no args.
241
+
242
+ Two bugs used to break this path: synthesis emitted
243
+ ``required=True`` alongside the recorded default (empty ``arguments``
244
+ failed the SDK-side validation before the executor could apply the
245
+ default), and the default executor factory built a bare FakeAdapter
246
+ (preconditions like "'Search' is present" failed because the demo
247
+ page ``cc learn`` compiles from was never seeded server-side).
248
+ """
249
+ registry = CapabilityRegistry.from_settings(_settings(tmp_path))
250
+ await registry.save(
251
+ _make_capability(
252
+ name="demo_search",
253
+ inputs=[
254
+ CapabilityParameter(
255
+ name="search",
256
+ type="string",
257
+ description="Value for 'Search'",
258
+ required=False,
259
+ default="hello",
260
+ )
261
+ ],
262
+ procedure=CapabilityProcedure(
263
+ steps=[
264
+ ProcedureStep(
265
+ step_id="s1",
266
+ action=TypeAction(
267
+ target=ActionTarget(
268
+ ref="search-box", role="searchbox", name="Search"
269
+ ),
270
+ text="{{search}}",
271
+ ),
272
+ ),
273
+ ProcedureStep(
274
+ step_id="s2",
275
+ action=ClickAction(
276
+ target=ActionTarget(
277
+ ref="submit-btn", role="button", name="Submit"
278
+ )
279
+ ),
280
+ ),
281
+ ]
282
+ ),
283
+ )
284
+ )
285
+ # Default factory: bare FakeAdapter + demo page, as `cc serve` uses it.
286
+ server = CapabilityMCPServer(registry, _settings(tmp_path))
287
+ result = await server.execute_tool("demo_search", {})
288
+ assert result["isError"] is False, result["content"][0]["text"]
289
+ body = json.loads(result["content"][0]["text"])
290
+ assert body["status"] == "success"
291
+ assert all(step["success"] for step in body["steps"])
292
+
236
293
  async def test_ping_returns_health_payload(self, tmp_path: Path) -> None:
237
294
  registry = CapabilityRegistry.from_settings(_settings(tmp_path))
238
295
  await registry.save(_make_capability(name="alpha"))
@@ -146,6 +146,34 @@ class TestSynthesis:
146
146
  assert params["format"].default == "png"
147
147
  assert params["file_name"].default == "report"
148
148
 
149
+ async def test_recorded_parameters_are_optional(self):
150
+ """Recorded literals become defaults, not required inputs.
151
+
152
+ Regression: synthesis used to emit ``required=True`` alongside the
153
+ recorded default. The executor applies defaults when a caller omits
154
+ the input, so ``required`` contradicted ``default`` — and the MCP
155
+ tool built from the capability rejected empty-args calls at the SDK
156
+ validation layer before the executor could fill the default in.
157
+ """
158
+ app = ExportFakeApp()
159
+ trajectory, initial, final = await record_export_workflow(app)
160
+ capability = await CapabilitySynthesizer(use_model=False).synthesize(
161
+ trajectory, initial_observation=initial, final_observation=final
162
+ )
163
+
164
+ params = {p.name: p for p in capability.inputs}
165
+ assert params["format"].required is False
166
+ assert params["file_name"].required is False
167
+
168
+ # The MCP wire schema mirrors the contract: nothing required, the
169
+ # recorded literal surfaced as the JSON-Schema default.
170
+ from capability_compiler.server.tool_defs import build_input_schema
171
+
172
+ schema = build_input_schema(capability)
173
+ assert schema.get("required", []) == []
174
+ assert schema["properties"]["format"]["default"] == "png"
175
+ assert schema["properties"]["file_name"]["default"] == "report"
176
+
149
177
  async def test_preconditions_derived(self):
150
178
  app = ExportFakeApp()
151
179
  trajectory, initial, final = await record_export_workflow(app)