fable-engine 1.3.2__tar.gz → 1.3.3__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 (159) hide show
  1. {fable_engine-1.3.2/fable_engine.egg-info → fable_engine-1.3.3}/PKG-INFO +27 -6
  2. {fable_engine-1.3.2 → fable_engine-1.3.3}/README.md +26 -5
  3. {fable_engine-1.3.2 → fable_engine-1.3.3}/docs/stop-ai-agents-writing-too-early.md +4 -4
  4. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/scrapers/base.py +1 -1
  5. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/server.py +1 -1
  6. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/session.py +1 -1
  7. {fable_engine-1.3.2 → fable_engine-1.3.3/fable_engine.egg-info}/PKG-INFO +27 -6
  8. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine.egg-info/SOURCES.txt +2 -0
  9. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_mode/__init__.py +1 -1
  10. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_mode/installer.py +12 -1
  11. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_mode/launcher.py +29 -1
  12. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_mode/manifest.py +5 -0
  13. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_mode/resources.json +6 -1
  14. fable_engine-1.3.3/fable_mode/skill_bundle.py +163 -0
  15. {fable_engine-1.3.2 → fable_engine-1.3.3}/pyproject.toml +1 -1
  16. fable_engine-1.3.3/setup.py +70 -0
  17. fable_engine-1.3.3/tests/test_skill_distribution.py +230 -0
  18. fable_engine-1.3.2/setup.py +0 -36
  19. {fable_engine-1.3.2 → fable_engine-1.3.3}/LICENSE +0 -0
  20. {fable_engine-1.3.2 → fable_engine-1.3.3}/MANIFEST.in +0 -0
  21. {fable_engine-1.3.2 → fable_engine-1.3.3}/docs/fable-v1-v2-migration.md +0 -0
  22. {fable_engine-1.3.2 → fable_engine-1.3.3}/docs/fable-v2-architecture.md +0 -0
  23. {fable_engine-1.3.2 → fable_engine-1.3.3}/docs/system3-architecture.md +0 -0
  24. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_compressor.py +0 -0
  25. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/__init__.py +0 -0
  26. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/actions/__init__.py +0 -0
  27. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/actions/cas.py +0 -0
  28. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/actions/deliberation.py +0 -0
  29. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/actions/fleet.py +0 -0
  30. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/actions/lifecycle.py +0 -0
  31. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/actions/scrapers.py +0 -0
  32. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/actions/system3.py +0 -0
  33. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/browser.py +0 -0
  34. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/cas.py +0 -0
  35. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/fable_session.json +0 -0
  36. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/guards.py +0 -0
  37. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/schema.py +0 -0
  38. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/scrapers/__init__.py +0 -0
  39. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/scrapers/arxiv.py +0 -0
  40. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/scrapers/github.py +0 -0
  41. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/scrapers/reddit.py +0 -0
  42. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/scrapers/web.py +0 -0
  43. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/scrapers/x.py +0 -0
  44. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/scrapers/youtube.py +0 -0
  45. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/test_server.py +0 -0
  46. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine/updater.py +0 -0
  47. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine.egg-info/dependency_links.txt +0 -0
  48. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine.egg-info/entry_points.txt +0 -0
  49. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_engine.egg-info/top_level.txt +0 -0
  50. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_mode/__main__.py +0 -0
  51. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_mode/adapters.py +0 -0
  52. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_mode/safety.py +0 -0
  53. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_mode_entry.py +0 -0
  54. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/__init__.py +0 -0
  55. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/adapters.py +0 -0
  56. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/coder_fleet/__init__.py +0 -0
  57. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/coder_fleet/ast_tools.py +0 -0
  58. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/coder_fleet/compute.py +0 -0
  59. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/coder_fleet/design_engine.py +0 -0
  60. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/coder_fleet/diagnostics.py +0 -0
  61. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/coder_fleet/fleet_dispatcher.py +0 -0
  62. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/coder_fleet/mock_auditor.py +0 -0
  63. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/coder_fleet/mutation.py +0 -0
  64. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/coder_fleet/property_oracle.py +0 -0
  65. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/coder_fleet/receipt_attestor.py +0 -0
  66. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/coder_fleet/red_team_swarm.py +0 -0
  67. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/coder_fleet/test_harness.py +0 -0
  68. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/coder_fleet/vector_engine.py +0 -0
  69. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/coder_fleet/visual.py +0 -0
  70. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/coder_fleet/workspace.py +0 -0
  71. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/cortical/__init__.py +0 -0
  72. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/cortical/plasticity_engine.py +0 -0
  73. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/execution_broker.py +0 -0
  74. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/proof_engine.py +0 -0
  75. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/protocol.py +0 -0
  76. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/runtime.py +0 -0
  77. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/system3/__init__.py +0 -0
  78. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/system3/causal.py +0 -0
  79. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/system3/dialectical.py +0 -0
  80. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/system3/evolution.py +0 -0
  81. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/system3/executive.py +0 -0
  82. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/system3/free_energy.py +0 -0
  83. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/system3/hyperbolic.py +0 -0
  84. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/system3/induction.py +0 -0
  85. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/system3/kripke.py +0 -0
  86. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/system3/oracle.py +0 -0
  87. {fable_engine-1.3.2 → fable_engine-1.3.3}/fable_v2/verifiers.py +0 -0
  88. {fable_engine-1.3.2 → fable_engine-1.3.3}/rules/AGENTS.md +0 -0
  89. {fable_engine-1.3.2 → fable_engine-1.3.3}/rules/GEMINI.md +0 -0
  90. {fable_engine-1.3.2 → fable_engine-1.3.3}/rules/fable-mode.md +0 -0
  91. {fable_engine-1.3.2 → fable_engine-1.3.3}/setup.cfg +0 -0
  92. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/SKILL.md +0 -0
  93. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/cortex/concurrency.md +0 -0
  94. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/cortex/design_3d.md +0 -0
  95. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/cortex/frontend_design.md +0 -0
  96. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/cortex/process.md +0 -0
  97. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/cortex/python.md +0 -0
  98. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/cortex/research.md +0 -0
  99. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/cortex/rust.md +0 -0
  100. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/cortex/security.md +0 -0
  101. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/cortex/synaptic_matrix.json +0 -0
  102. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/cortex/system.md +0 -0
  103. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/cortex/target.md +0 -0
  104. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/examples/autonomous-agentic-migration.md +0 -0
  105. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/examples/breakthrough-algorithm-synthesis.md +0 -0
  106. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/examples/deepthink-analysis-proof.md +0 -0
  107. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/examples/distributed-system-design.md +0 -0
  108. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/examples/swe-bench-pro-debugging.md +0 -0
  109. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/examples/weak_model_ollama_setup.md +0 -0
  110. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/aaa-threejs-game-engine.md +0 -0
  111. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/adversarial-code-review-swarm.md +0 -0
  112. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/agentic-execution.md +0 -0
  113. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/anti-slop-frontend-architecture.md +0 -0
  114. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/architectural-blueprinting.md +0 -0
  115. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/cinematic-design-engine.md +0 -0
  116. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/cognitive-protocol.md +0 -0
  117. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/deepthink-mode.md +0 -0
  118. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/design-tokens-and-typographies.md +0 -0
  119. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/goal-rubric-and-pipeline-automation.md +0 -0
  120. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/hebbian-cortical-plasticity.md +0 -0
  121. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/innovation-engine.md +0 -0
  122. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/interleaved-verification.md +0 -0
  123. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/model-velocity-calibration.md +0 -0
  124. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/prompt-scaffolds.md +0 -0
  125. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/proof-architecture.md +0 -0
  126. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/svg-craft-and-vector-design.md +0 -0
  127. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/system2-session-engine.md +0 -0
  128. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/system3-meta-cognition.md +0 -0
  129. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/visual-imagination-engine.md +0 -0
  130. {fable_engine-1.3.2 → fable_engine-1.3.3}/skills/fable-mode/references/weak-model-frontier-uplift.md +0 -0
  131. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/__init__.py +0 -0
  132. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_anti_loop_circuit_breaker.py +0 -0
  133. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_auto_updater.py +0 -0
  134. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_coder_fleet.py +0 -0
  135. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_delegation_compiler.py +0 -0
  136. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_descriptor_boundaries.py +0 -0
  137. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_design_engine.py +0 -0
  138. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_epistemic_evidence_validator.py +0 -0
  139. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_execution_broker.py +0 -0
  140. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_fable_v2.py +0 -0
  141. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_fleet_transitions.py +0 -0
  142. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_fsm_redteam_evolution.py +0 -0
  143. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_goal_rubric_and_pipeline.py +0 -0
  144. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_hebbian_plasticity.py +0 -0
  145. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_packaging_runtime.py +0 -0
  146. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_proof_engine.py +0 -0
  147. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_red_team_swarm.py +0 -0
  148. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_redteam_remediation.py +0 -0
  149. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_registration_transaction.py +0 -0
  150. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_requested_regressions.py +0 -0
  151. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_scrapers.py +0 -0
  152. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_server_actions.py +0 -0
  153. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_server_frontier_actions.py +0 -0
  154. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_server_protocol.py +0 -0
  155. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_stealth_browser.py +0 -0
  156. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_system3.py +0 -0
  157. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_system3_deep_integration.py +0 -0
  158. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_system3_frontier.py +0 -0
  159. {fable_engine-1.3.2 → fable_engine-1.3.3}/tests/test_vector_engine.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: fable-engine
3
- Version: 1.3.2
3
+ Version: 1.3.3
4
4
  Summary: Independent deterministic System 2 cognitive engine and mechanical time-lock MCP server
5
5
  Author: REX-codebase
6
6
  License: MIT License
@@ -63,7 +63,7 @@ Dynamic: requires-python
63
63
   
64
64
  <img src="https://img.shields.io/badge/dependencies-0-black" alt="Zero Dependencies"/>
65
65
  &nbsp;
66
- <img src="https://img.shields.io/badge/tests-473%20passing-black" alt="473 tests passing"/>
66
+ <img src="https://img.shields.io/badge/tests-490%20passing-black" alt="490 tests passing"/>
67
67
 
68
68
  <br/><br/>
69
69
 
@@ -116,21 +116,21 @@ pip install fable-engine
116
116
 
117
117
  Or install the MCP server in your editor:
118
118
 
119
- [![Install MCP server in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_server-007ACC?logo=visualstudiocode&logoColor=white)](vscode:mcp/install?%7B%22name%22%3A%22fable-engine%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22fable-engine%3D%3D1.3.2%22%2C%22fable-engine%22%5D%7D)
120
- [![Add to Cursor](https://img.shields.io/badge/Cursor-Add_MCP_server-black)](cursor://anysphere.cursor-deeplink/mcp/install?name=fable-engine&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJmYWJsZS1lbmdpbmU9PTEuMy4xIiwiZmFibGUtZW5naW5lIl19)
119
+ [![Install MCP server in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_server-007ACC?logo=visualstudiocode&logoColor=white)](vscode:mcp/install?%7B%22name%22%3A%22fable-engine%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22fable-engine%3D%3D1.3.3%22%2C%22fable-engine%22%5D%7D)
120
+ [![Add to Cursor](https://img.shields.io/badge/Cursor-Add_MCP_server-black)](cursor://anysphere.cursor-deeplink/mcp/install?name=fable-engine&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJmYWJsZS1lbmdpbmU9PTEuMy4zIiwiZmFibGUtZW5naW5lIl19)
121
121
 
122
122
  These links configure Fable Engine for AI agents in VS Code Chat or Cursor. They do not install a standalone editor extension. Both use `uvx`, which downloads and runs the pinned PyPI release in an isolated environment.
123
123
 
124
124
  Point your agent at the MCP server manually:
125
125
 
126
126
  ```jsonc
127
- // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.2 fable-engine
127
+ // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.3 fable-engine
128
128
  // Cursor: ~/.cursor/mcp.json
129
129
  {
130
130
  "mcpServers": {
131
131
  "fable-engine": {
132
132
  "command": "uvx",
133
- "args": ["--from", "fable-engine==1.3.2", "fable-engine"]
133
+ "args": ["--from", "fable-engine==1.3.3", "fable-engine"]
134
134
  }
135
135
  }
136
136
  }
@@ -140,6 +140,27 @@ Python 3.10+, zero runtime dependencies. Published on [PyPI as `fable-engine`](h
140
140
 
141
141
  <br/>
142
142
 
143
+ ### Optional: the Agent Skill
144
+
145
+ Installing `fable-engine` gives your agent the MCP tools. It does not install
146
+ or activate the Fable Mode Agent Skill - the prompt-side workflow in
147
+ [`skills/fable-mode`](./skills/fable-mode/SKILL.md). Nothing in the package
148
+ activates those instructions on its own; the skill is always a separate,
149
+ explicit opt-in.
150
+
151
+ The complete skill tree ships inside the wheel. To install it into your
152
+ project's skills directory (the cross-client `.agents/skills/` convention):
153
+
154
+ ```bash
155
+ uvx --from fable-engine==1.3.3 fable-mode install-skill --yes
156
+ ```
157
+
158
+ This copies the skill to `.agents/skills/fable-mode`. Preview first with
159
+ `--dry-run`, choose another location with `--target <dir>`, and reload your
160
+ agent afterwards so it picks up the skill.
161
+
162
+ <br/>
163
+
143
164
  ### How it works
144
165
 
145
166
  1. **Think** — Time-locked deliberation. The agent cannot write until the timer ends.
@@ -20,7 +20,7 @@
20
20
  &nbsp;
21
21
  <img src="https://img.shields.io/badge/dependencies-0-black" alt="Zero Dependencies"/>
22
22
  &nbsp;
23
- <img src="https://img.shields.io/badge/tests-473%20passing-black" alt="473 tests passing"/>
23
+ <img src="https://img.shields.io/badge/tests-490%20passing-black" alt="490 tests passing"/>
24
24
 
25
25
  <br/><br/>
26
26
 
@@ -73,21 +73,21 @@ pip install fable-engine
73
73
 
74
74
  Or install the MCP server in your editor:
75
75
 
76
- [![Install MCP server in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_server-007ACC?logo=visualstudiocode&logoColor=white)](vscode:mcp/install?%7B%22name%22%3A%22fable-engine%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22fable-engine%3D%3D1.3.2%22%2C%22fable-engine%22%5D%7D)
77
- [![Add to Cursor](https://img.shields.io/badge/Cursor-Add_MCP_server-black)](cursor://anysphere.cursor-deeplink/mcp/install?name=fable-engine&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJmYWJsZS1lbmdpbmU9PTEuMy4xIiwiZmFibGUtZW5naW5lIl19)
76
+ [![Install MCP server in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_server-007ACC?logo=visualstudiocode&logoColor=white)](vscode:mcp/install?%7B%22name%22%3A%22fable-engine%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22fable-engine%3D%3D1.3.3%22%2C%22fable-engine%22%5D%7D)
77
+ [![Add to Cursor](https://img.shields.io/badge/Cursor-Add_MCP_server-black)](cursor://anysphere.cursor-deeplink/mcp/install?name=fable-engine&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJmYWJsZS1lbmdpbmU9PTEuMy4zIiwiZmFibGUtZW5naW5lIl19)
78
78
 
79
79
  These links configure Fable Engine for AI agents in VS Code Chat or Cursor. They do not install a standalone editor extension. Both use `uvx`, which downloads and runs the pinned PyPI release in an isolated environment.
80
80
 
81
81
  Point your agent at the MCP server manually:
82
82
 
83
83
  ```jsonc
84
- // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.2 fable-engine
84
+ // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.3 fable-engine
85
85
  // Cursor: ~/.cursor/mcp.json
86
86
  {
87
87
  "mcpServers": {
88
88
  "fable-engine": {
89
89
  "command": "uvx",
90
- "args": ["--from", "fable-engine==1.3.2", "fable-engine"]
90
+ "args": ["--from", "fable-engine==1.3.3", "fable-engine"]
91
91
  }
92
92
  }
93
93
  }
@@ -97,6 +97,27 @@ Python 3.10+, zero runtime dependencies. Published on [PyPI as `fable-engine`](h
97
97
 
98
98
  <br/>
99
99
 
100
+ ### Optional: the Agent Skill
101
+
102
+ Installing `fable-engine` gives your agent the MCP tools. It does not install
103
+ or activate the Fable Mode Agent Skill - the prompt-side workflow in
104
+ [`skills/fable-mode`](./skills/fable-mode/SKILL.md). Nothing in the package
105
+ activates those instructions on its own; the skill is always a separate,
106
+ explicit opt-in.
107
+
108
+ The complete skill tree ships inside the wheel. To install it into your
109
+ project's skills directory (the cross-client `.agents/skills/` convention):
110
+
111
+ ```bash
112
+ uvx --from fable-engine==1.3.3 fable-mode install-skill --yes
113
+ ```
114
+
115
+ This copies the skill to `.agents/skills/fable-mode`. Preview first with
116
+ `--dry-run`, choose another location with `--target <dir>`, and reload your
117
+ agent afterwards so it picks up the skill.
118
+
119
+ <br/>
120
+
100
121
  ### How it works
101
122
 
102
123
  1. **Think** — Time-locked deliberation. The agent cannot write until the timer ends.
@@ -18,13 +18,13 @@ Fable Mode separates research from authority. A session begins with execution lo
18
18
  Install [uv](https://docs.astral.sh/uv/getting-started/installation/) so your MCP client can run the package without changing your global Python environment.
19
19
 
20
20
  ```bash
21
- uvx --from fable-engine==1.3.2 fable-engine
21
+ uvx --from fable-engine==1.3.3 fable-engine
22
22
  ```
23
23
 
24
24
  For Claude Code:
25
25
 
26
26
  ```bash
27
- claude mcp add fable-engine -- uvx --from fable-engine==1.3.2 fable-engine
27
+ claude mcp add fable-engine -- uvx --from fable-engine==1.3.3 fable-engine
28
28
  ```
29
29
 
30
30
  For an MCP client that uses JSON configuration:
@@ -34,7 +34,7 @@ For an MCP client that uses JSON configuration:
34
34
  "mcpServers": {
35
35
  "fable-engine": {
36
36
  "command": "uvx",
37
- "args": ["--from", "fable-engine==1.3.2", "fable-engine"]
37
+ "args": ["--from", "fable-engine==1.3.3", "fable-engine"]
38
38
  }
39
39
  }
40
40
  }
@@ -68,7 +68,7 @@ It is not a guarantee of correct code. It makes the path to write access explici
68
68
  The package has no runtime dependencies. You can confirm the pinned release and inspect its MCP initialization response in an isolated environment before adding it to an editor or agent:
69
69
 
70
70
  ```bash
71
- uvx --from fable-engine==1.3.2 fable-engine
71
+ uvx --from fable-engine==1.3.3 fable-engine
72
72
  ```
73
73
 
74
74
  The server communicates over standard input and output. MCP clients start and manage that process for you after configuration.
@@ -25,7 +25,7 @@ from typing import Any, Dict, List, Optional, Tuple
25
25
 
26
26
  logger = logging.getLogger("fable-engine.scrapers.base")
27
27
 
28
- USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 FableResearch/1.3.2"
28
+ USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 FableResearch/1.3.3"
29
29
 
30
30
  MAX_RETRY_DELAY_SECONDS = 10.0
31
31
 
@@ -237,7 +237,7 @@ def main():
237
237
  },
238
238
  "serverInfo": {
239
239
  "name": "fable-engine",
240
- "version": "1.3.2"
240
+ "version": "1.3.3"
241
241
  }
242
242
  }
243
243
  })
@@ -1530,7 +1530,7 @@ class FableSession:
1530
1530
  def to_dict(self) -> Dict[str, Any]:
1531
1531
  """Serializes session to dictionary."""
1532
1532
  return {
1533
- "version": "1.3.2",
1533
+ "version": "1.3.3",
1534
1534
  "session_name": self.session_name,
1535
1535
  "session_id": self.session_id,
1536
1536
  "objective": self.objective,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: fable-engine
3
- Version: 1.3.2
3
+ Version: 1.3.3
4
4
  Summary: Independent deterministic System 2 cognitive engine and mechanical time-lock MCP server
5
5
  Author: REX-codebase
6
6
  License: MIT License
@@ -63,7 +63,7 @@ Dynamic: requires-python
63
63
  &nbsp;
64
64
  <img src="https://img.shields.io/badge/dependencies-0-black" alt="Zero Dependencies"/>
65
65
  &nbsp;
66
- <img src="https://img.shields.io/badge/tests-473%20passing-black" alt="473 tests passing"/>
66
+ <img src="https://img.shields.io/badge/tests-490%20passing-black" alt="490 tests passing"/>
67
67
 
68
68
  <br/><br/>
69
69
 
@@ -116,21 +116,21 @@ pip install fable-engine
116
116
 
117
117
  Or install the MCP server in your editor:
118
118
 
119
- [![Install MCP server in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_server-007ACC?logo=visualstudiocode&logoColor=white)](vscode:mcp/install?%7B%22name%22%3A%22fable-engine%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22fable-engine%3D%3D1.3.2%22%2C%22fable-engine%22%5D%7D)
120
- [![Add to Cursor](https://img.shields.io/badge/Cursor-Add_MCP_server-black)](cursor://anysphere.cursor-deeplink/mcp/install?name=fable-engine&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJmYWJsZS1lbmdpbmU9PTEuMy4xIiwiZmFibGUtZW5naW5lIl19)
119
+ [![Install MCP server in VS Code](https://img.shields.io/badge/VS_Code-Install_MCP_server-007ACC?logo=visualstudiocode&logoColor=white)](vscode:mcp/install?%7B%22name%22%3A%22fable-engine%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22fable-engine%3D%3D1.3.3%22%2C%22fable-engine%22%5D%7D)
120
+ [![Add to Cursor](https://img.shields.io/badge/Cursor-Add_MCP_server-black)](cursor://anysphere.cursor-deeplink/mcp/install?name=fable-engine&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJmYWJsZS1lbmdpbmU9PTEuMy4zIiwiZmFibGUtZW5naW5lIl19)
121
121
 
122
122
  These links configure Fable Engine for AI agents in VS Code Chat or Cursor. They do not install a standalone editor extension. Both use `uvx`, which downloads and runs the pinned PyPI release in an isolated environment.
123
123
 
124
124
  Point your agent at the MCP server manually:
125
125
 
126
126
  ```jsonc
127
- // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.2 fable-engine
127
+ // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.3 fable-engine
128
128
  // Cursor: ~/.cursor/mcp.json
129
129
  {
130
130
  "mcpServers": {
131
131
  "fable-engine": {
132
132
  "command": "uvx",
133
- "args": ["--from", "fable-engine==1.3.2", "fable-engine"]
133
+ "args": ["--from", "fable-engine==1.3.3", "fable-engine"]
134
134
  }
135
135
  }
136
136
  }
@@ -140,6 +140,27 @@ Python 3.10+, zero runtime dependencies. Published on [PyPI as `fable-engine`](h
140
140
 
141
141
  <br/>
142
142
 
143
+ ### Optional: the Agent Skill
144
+
145
+ Installing `fable-engine` gives your agent the MCP tools. It does not install
146
+ or activate the Fable Mode Agent Skill - the prompt-side workflow in
147
+ [`skills/fable-mode`](./skills/fable-mode/SKILL.md). Nothing in the package
148
+ activates those instructions on its own; the skill is always a separate,
149
+ explicit opt-in.
150
+
151
+ The complete skill tree ships inside the wheel. To install it into your
152
+ project's skills directory (the cross-client `.agents/skills/` convention):
153
+
154
+ ```bash
155
+ uvx --from fable-engine==1.3.3 fable-mode install-skill --yes
156
+ ```
157
+
158
+ This copies the skill to `.agents/skills/fable-mode`. Preview first with
159
+ `--dry-run`, choose another location with `--target <dir>`, and reload your
160
+ agent afterwards so it picks up the skill.
161
+
162
+ <br/>
163
+
143
164
  ### How it works
144
165
 
145
166
  1. **Think** — Time-locked deliberation. The agent cannot write until the timer ends.
@@ -47,6 +47,7 @@ fable_mode/launcher.py
47
47
  fable_mode/manifest.py
48
48
  fable_mode/resources.json
49
49
  fable_mode/safety.py
50
+ fable_mode/skill_bundle.py
50
51
  fable_v2/__init__.py
51
52
  fable_v2/adapters.py
52
53
  fable_v2/execution_broker.py
@@ -147,6 +148,7 @@ tests/test_scrapers.py
147
148
  tests/test_server_actions.py
148
149
  tests/test_server_frontier_actions.py
149
150
  tests/test_server_protocol.py
151
+ tests/test_skill_distribution.py
150
152
  tests/test_stealth_browser.py
151
153
  tests/test_system3.py
152
154
  tests/test_system3_deep_integration.py
@@ -1,3 +1,3 @@
1
1
  """Fable Mode portable runtime and transactional installer."""
2
2
 
3
- __version__ = "1.3.2"
3
+ __version__ = "1.3.3"
@@ -125,7 +125,18 @@ def _source_path(root: Path, rel: str) -> Path:
125
125
  if checkout.is_file():
126
126
  return checkout
127
127
  return _license_path()
128
- return root / rel
128
+ candidate = root / rel
129
+ if candidate.exists() or not rel.startswith("skills/"):
130
+ return candidate
131
+ # Wheels carry the skill tree as fable_mode package data, not beside
132
+ # site-packages, so portable installs from a wheel resolve it there.
133
+ try:
134
+ packaged = Path(resources.files("fable_mode").joinpath(rel))
135
+ if packaged.exists():
136
+ return packaged
137
+ except (OSError, TypeError, ValueError):
138
+ pass
139
+ return candidate
129
140
 
130
141
 
131
142
  @dataclass
@@ -17,6 +17,7 @@ from pathlib import Path
17
17
  from .adapters import (RegistrationError, cleanup_recorded_registrations,
18
18
  detect_hosts, register_hosts, validate_registration_record)
19
19
  from .installer import Installer, InstallError, verify_installation
20
+ from .skill_bundle import SKILL_NAME, install_skill
20
21
  from .manifest import ALLOWED_FILES, validate_manifest
21
22
  from . import __version__
22
23
 
@@ -251,7 +252,7 @@ def _is_frozen() -> bool:
251
252
  def _parser() -> argparse.ArgumentParser:
252
253
  p = argparse.ArgumentParser(prog="fable-mode")
253
254
  p.add_argument("--version", action="version", version=__version__)
254
- p.add_argument("mode", nargs="?", choices=("install", "serve", "verify", "version", "uninstall"), default="install")
255
+ p.add_argument("mode", nargs="?", choices=("install", "serve", "verify", "version", "uninstall", "install-skill"), default="install")
255
256
  p.add_argument("--yes", action="store_true", help="confirm unattended installation")
256
257
  p.add_argument("--register-hosts", action="store_true")
257
258
  p.add_argument("--aliases", action="store_true", help="also probe legacy cc/antigravity aliases")
@@ -259,6 +260,8 @@ def _parser() -> argparse.ArgumentParser:
259
260
  p.add_argument("--state-dir")
260
261
  p.add_argument("--install-dir")
261
262
  p.add_argument("--workspace")
263
+ p.add_argument("--target", help="skill destination directory (default: .agents/skills/fable-mode)")
264
+ p.add_argument("--force", action="store_true", help="overwrite foreign or locally edited skill files")
262
265
  return p
263
266
 
264
267
 
@@ -302,6 +305,31 @@ def main(argv: list[str] | None = None) -> int:
302
305
  except (OSError, RuntimeError, ValueError) as exc:
303
306
  print(f"serve: {exc}", file=sys.stderr)
304
307
  return 1
308
+ if args.mode == "install-skill":
309
+ target = (Path(args.target).expanduser() if args.target
310
+ else Path.cwd() / ".agents" / "skills" / SKILL_NAME)
311
+ if not args.dry_run and not args.yes:
312
+ if not sys.stdin.isatty():
313
+ print("install-skill requires --yes in unattended mode", file=sys.stderr)
314
+ return 2
315
+ try:
316
+ confirmed = input(f"Install the Fable Mode Agent Skill to {target}? [y/N] ").strip().lower() in {"y", "yes"}
317
+ except (EOFError, KeyboardInterrupt):
318
+ confirmed = False
319
+ if not confirmed:
320
+ print("Skill installation cancelled.")
321
+ return 0
322
+ try:
323
+ result = install_skill(target, force=args.force, dry_run=args.dry_run)
324
+ except InstallError as exc:
325
+ print(f"install-skill: {exc}", file=sys.stderr)
326
+ return 1
327
+ verb = "Would install" if result.dry_run else "Installed"
328
+ changes = f"{len(result.installed)} new, {len(result.updated)} updated, {len(result.unchanged)} unchanged"
329
+ print(f"{verb} the Fable Mode Agent Skill ({result.planned} files: {changes}) to {result.target}")
330
+ if not result.dry_run:
331
+ print("Restart or reload your agent to pick up the skill.")
332
+ return 0
305
333
  installer = Installer(Path(args.install_dir) if args.install_dir else None)
306
334
  if args.mode == "verify":
307
335
  return verify(installer)
@@ -116,6 +116,11 @@ ALLOWED_FILES: tuple[str, ...] = (
116
116
  "skills/fable-mode/cortex/frontend_design.md",
117
117
  "skills/fable-mode/cortex/research.md",
118
118
  "skills/fable-mode/cortex/concurrency.md",
119
+ "skills/fable-mode/cortex/process.md",
120
+ "skills/fable-mode/cortex/security.md",
121
+ "skills/fable-mode/cortex/system.md",
122
+ "skills/fable-mode/cortex/target.md",
123
+ "skills/fable-mode/cortex/synaptic_matrix.json",
119
124
  "LICENSE",
120
125
  )
121
126
 
@@ -109,6 +109,11 @@
109
109
  "skills/fable-mode/cortex/frontend_design.md",
110
110
  "skills/fable-mode/cortex/research.md",
111
111
  "skills/fable-mode/cortex/concurrency.md",
112
+ "skills/fable-mode/cortex/process.md",
113
+ "skills/fable-mode/cortex/security.md",
114
+ "skills/fable-mode/cortex/system.md",
115
+ "skills/fable-mode/cortex/target.md",
116
+ "skills/fable-mode/cortex/synaptic_matrix.json",
112
117
  "LICENSE"
113
118
  ]
114
- }
119
+ }
@@ -0,0 +1,163 @@
1
+ """Explicit, opt-in installation of the bundled Fable Mode Agent Skill.
2
+
3
+ The MCP server and the Agent Skill are separate artifacts: connecting the
4
+ server never installs or activates skill instructions. The canonical
5
+ ``skills/fable-mode`` tree ships inside the wheel as package data, and this
6
+ module copies it into a caller-chosen skills directory - by default the
7
+ cross-client ``.agents/skills/fable-mode`` convention - only when explicitly
8
+ asked through ``fable-mode install-skill``.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import hashlib
13
+ import json
14
+ import os
15
+ import shutil
16
+ import stat
17
+ from dataclasses import dataclass, field
18
+ from importlib import resources
19
+ from pathlib import Path
20
+
21
+ from . import __version__
22
+ from .installer import InstallError, _reject_path
23
+ from .manifest import source_root
24
+
25
+ SKILL_NAME = "fable-mode"
26
+ SKILL_MARKER = ".fable-skill-install.json"
27
+ SKILL_REL = Path("skills") / SKILL_NAME
28
+
29
+
30
+ def bundled_skill_root() -> Path:
31
+ """Locate the canonical skill tree in a source checkout or an installed wheel."""
32
+ checkout = source_root() / SKILL_REL
33
+ try:
34
+ if checkout.is_dir() and not checkout.is_symlink():
35
+ return checkout
36
+ except OSError:
37
+ pass
38
+ try:
39
+ candidate = Path(resources.files("fable_mode").joinpath(SKILL_REL.as_posix()))
40
+ if candidate.is_dir() and not candidate.is_symlink():
41
+ return candidate
42
+ except (FileNotFoundError, OSError, TypeError, ValueError):
43
+ pass
44
+ raise InstallError("the bundled Fable Mode Agent Skill is unavailable in this installation")
45
+
46
+
47
+ def _hash(path: Path) -> str:
48
+ digest = hashlib.sha256()
49
+ with path.open("rb") as handle:
50
+ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
51
+ digest.update(chunk)
52
+ return digest.hexdigest()
53
+
54
+
55
+ def _skill_files(root: Path) -> dict[str, Path]:
56
+ files: dict[str, Path] = {}
57
+ for path in sorted(root.rglob("*")):
58
+ if path.is_dir():
59
+ continue
60
+ rel = path.relative_to(root).as_posix()
61
+ parts = Path(rel)
62
+ if parts.is_absolute() or ".." in parts.parts:
63
+ raise InstallError(f"unsafe bundled skill path: {rel}")
64
+ if path.is_symlink() or not path.is_file():
65
+ raise InstallError(f"unsafe bundled skill entry: {rel}")
66
+ files[rel] = path
67
+ if "SKILL.md" not in files:
68
+ raise InstallError("the bundled skill tree is incomplete: SKILL.md is missing")
69
+ return files
70
+
71
+
72
+ def _read_marker(target: Path) -> dict | None:
73
+ marker = target / SKILL_MARKER
74
+ try:
75
+ if not marker.is_file() or marker.is_symlink():
76
+ return None
77
+ data = json.loads(marker.read_text(encoding="utf-8"))
78
+ except (OSError, ValueError):
79
+ return None
80
+ if not isinstance(data, dict) or data.get("format") != 1 or data.get("product") != SKILL_NAME:
81
+ return None
82
+ if not isinstance(data.get("files"), dict):
83
+ return None
84
+ return data
85
+
86
+
87
+ @dataclass
88
+ class SkillInstallResult:
89
+ target: Path
90
+ dry_run: bool
91
+ installed: list[str] = field(default_factory=list)
92
+ updated: list[str] = field(default_factory=list)
93
+ unchanged: list[str] = field(default_factory=list)
94
+
95
+ @property
96
+ def planned(self) -> int:
97
+ return len(self.installed) + len(self.updated) + len(self.unchanged)
98
+
99
+
100
+ def install_skill(target: Path, *, force: bool = False, dry_run: bool = False) -> SkillInstallResult:
101
+ """Copy the bundled skill tree into ``target`` with ownership tracking.
102
+
103
+ Idempotent: byte-identical files are left untouched. Files this installer
104
+ previously wrote are upgraded in place; anything else (a foreign directory
105
+ or local edits) requires ``force`` so a skill install can never silently
106
+ overwrite user content.
107
+ """
108
+ source = bundled_skill_root()
109
+ files = _skill_files(source)
110
+ target = Path(target).expanduser().absolute()
111
+ _reject_path(target, allow_missing=True)
112
+ if target.exists() and (target.is_symlink() or not target.is_dir()):
113
+ raise InstallError("skill target is not a real directory")
114
+ marker = _read_marker(target) if target.is_dir() else None
115
+ if target.is_dir() and marker is None and any(target.iterdir()) and not force:
116
+ raise InstallError(
117
+ f"{target} exists and was not installed by fable-mode; pass --force to adopt it")
118
+ recorded = (marker or {}).get("files", {})
119
+ result = SkillInstallResult(target=target, dry_run=dry_run)
120
+ conflicts: list[str] = []
121
+ for rel, src in files.items():
122
+ dst = target / Path(rel)
123
+ if not dst.exists():
124
+ result.installed.append(rel)
125
+ continue
126
+ if dst.is_symlink() or not dst.is_file():
127
+ raise InstallError(f"unsafe existing path in skill target: {rel}")
128
+ new_hash, current_hash = _hash(src), _hash(dst)
129
+ if current_hash == new_hash:
130
+ result.unchanged.append(rel)
131
+ elif (marker is not None and recorded.get(rel) == current_hash) or force:
132
+ result.updated.append(rel)
133
+ else:
134
+ conflicts.append(rel)
135
+ if conflicts:
136
+ listing = ", ".join(conflicts[:5]) + (", ..." if len(conflicts) > 5 else "")
137
+ raise InstallError(f"local modifications would be overwritten ({listing}); pass --force to overwrite")
138
+ if dry_run:
139
+ return result
140
+ target.mkdir(parents=True, exist_ok=True)
141
+ written: dict[str, str] = {}
142
+ for rel in result.installed + result.updated:
143
+ src = files[rel]
144
+ dst = target / Path(rel)
145
+ dst.parent.mkdir(parents=True, exist_ok=True)
146
+ if os.name != "nt":
147
+ cursor = dst.parent
148
+ while cursor != target and cursor != cursor.parent:
149
+ os.chmod(cursor, 0o700)
150
+ cursor = cursor.parent
151
+ shutil.copyfile(src, dst, follow_symlinks=False)
152
+ os.chmod(dst, 0o600)
153
+ written[rel] = _hash(dst)
154
+ for rel in result.unchanged:
155
+ written[rel] = _hash(target / Path(rel))
156
+ if os.name != "nt":
157
+ os.chmod(target, 0o700)
158
+ marker_path = target / SKILL_MARKER
159
+ marker_path.write_text(json.dumps(
160
+ {"format": 1, "product": SKILL_NAME, "version": __version__, "files": written},
161
+ indent=2, sort_keys=True) + "\n", encoding="utf-8")
162
+ os.chmod(marker_path, 0o600)
163
+ return result
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fable-engine"
7
- version = "1.3.2"
7
+ version = "1.3.3"
8
8
  description = "Independent deterministic System 2 cognitive engine and mechanical time-lock MCP server"
9
9
  readme = "README.md"
10
10
  authors = [{ name = "REX-codebase" }]
@@ -0,0 +1,70 @@
1
+ import shutil
2
+ from pathlib import Path
3
+
4
+ from setuptools import setup, find_packages
5
+ from setuptools.command.build_py import build_py as _build_py
6
+
7
+ README = (Path(__file__).parent / "README.md").read_text(encoding="utf-8")
8
+
9
+ _SKILL_SOURCE = Path(__file__).resolve().parent / "skills" / "fable-mode"
10
+
11
+
12
+ class build_py(_build_py):
13
+ """Bundle the canonical Agent Skill tree into the wheel as package data.
14
+
15
+ ``pip`` and ``uvx`` install the wheel, so the explicit
16
+ ``fable-mode install-skill`` flow can only work for those users when the
17
+ skill tree travels inside the ``fable_mode`` package, not just in the
18
+ sdist. The checkout tree stays the single source of truth; it is copied
19
+ into the build here.
20
+ """
21
+
22
+ def run(self):
23
+ super().run()
24
+ if getattr(self, "editable_mode", False):
25
+ # Editable installs resolve the skill tree from the source
26
+ # checkout, so no staged copy is needed.
27
+ return
28
+ copied = 0
29
+ destination_root = Path(self.build_lib) / "fable_mode" / "skills" / "fable-mode"
30
+ for source in sorted(_SKILL_SOURCE.rglob("*")):
31
+ if not source.is_file():
32
+ continue
33
+ destination = destination_root / source.relative_to(_SKILL_SOURCE)
34
+ destination.parent.mkdir(parents=True, exist_ok=True)
35
+ shutil.copyfile(source, destination)
36
+ copied += 1
37
+ if copied == 0:
38
+ raise RuntimeError("canonical skill tree is missing from the source tree")
39
+
40
+
41
+ setup(
42
+ name="fable-engine",
43
+ version="1.3.3",
44
+ description="Independent deterministic System 2 cognitive engine and mechanical time-lock MCP server",
45
+ long_description=README,
46
+ long_description_content_type="text/markdown",
47
+ license="MIT",
48
+ packages=find_packages(),
49
+ py_modules=["fable_mode_entry", "fable_compressor"],
50
+ package_data={"fable_mode": ["resources.json", "LICENSE"], "fable_engine": ["fable_session.json"]},
51
+ python_requires=">=3.10",
52
+ install_requires=[],
53
+ cmdclass={"build_py": build_py},
54
+ entry_points={
55
+ "console_scripts": [
56
+ # Portable package-aware installer/runtime.
57
+ "fable-mode=fable_mode.launcher:main",
58
+ # Legacy V1 MCP entry point.
59
+ "fable-engine=fable_engine.server:main",
60
+ "fable-v1=fable_engine.server:main",
61
+ # V2 process execution boundary.
62
+ "fable-v2-broker=fable_v2.execution_broker:main",
63
+ ],
64
+ },
65
+ classifiers=[
66
+ "Programming Language :: Python :: 3",
67
+ "License :: OSI Approved :: MIT License",
68
+ "Operating System :: OS Independent",
69
+ ],
70
+ )