fable-engine 1.3.6__tar.gz → 1.3.8__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 (165) hide show
  1. {fable_engine-1.3.6/fable_engine.egg-info → fable_engine-1.3.8}/PKG-INFO +44 -27
  2. {fable_engine-1.3.6 → fable_engine-1.3.8}/README.md +43 -26
  3. {fable_engine-1.3.6 → fable_engine-1.3.8}/docs/stop-ai-agents-writing-too-early.md +42 -17
  4. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/scrapers/base.py +1 -1
  5. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/server.py +1 -1
  6. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/session.py +1 -1
  7. {fable_engine-1.3.6 → fable_engine-1.3.8/fable_engine.egg-info}/PKG-INFO +44 -27
  8. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine.egg-info/SOURCES.txt +1 -0
  9. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_mode/__init__.py +1 -1
  10. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_mode/launcher.py +56 -2
  11. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/sandbox_executor.py +4 -1
  12. {fable_engine-1.3.6 → fable_engine-1.3.8}/pyproject.toml +1 -1
  13. {fable_engine-1.3.6 → fable_engine-1.3.8}/setup.py +1 -1
  14. fable_engine-1.3.8/tests/test_shell_call.py +58 -0
  15. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_skill_distribution.py +27 -2
  16. {fable_engine-1.3.6 → fable_engine-1.3.8}/LICENSE +0 -0
  17. {fable_engine-1.3.6 → fable_engine-1.3.8}/MANIFEST.in +0 -0
  18. {fable_engine-1.3.6 → fable_engine-1.3.8}/docs/ai-evidence-adjudicator.md +0 -0
  19. {fable_engine-1.3.6 → fable_engine-1.3.8}/docs/fable-v1-v2-migration.md +0 -0
  20. {fable_engine-1.3.6 → fable_engine-1.3.8}/docs/fable-v2-architecture.md +0 -0
  21. {fable_engine-1.3.6 → fable_engine-1.3.8}/docs/system3-architecture.md +0 -0
  22. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_compressor.py +0 -0
  23. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/__init__.py +0 -0
  24. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/actions/__init__.py +0 -0
  25. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/actions/cas.py +0 -0
  26. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/actions/deliberation.py +0 -0
  27. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/actions/fleet.py +0 -0
  28. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/actions/lifecycle.py +0 -0
  29. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/actions/scrapers.py +0 -0
  30. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/actions/system3.py +0 -0
  31. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/adjudicator.py +0 -0
  32. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/browser.py +0 -0
  33. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/cas.py +0 -0
  34. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/fable_session.json +0 -0
  35. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/guards.py +0 -0
  36. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/schema.py +0 -0
  37. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/scrapers/__init__.py +0 -0
  38. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/scrapers/arxiv.py +0 -0
  39. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/scrapers/github.py +0 -0
  40. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/scrapers/reddit.py +0 -0
  41. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/scrapers/web.py +0 -0
  42. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/scrapers/x.py +0 -0
  43. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/scrapers/youtube.py +0 -0
  44. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/test_server.py +0 -0
  45. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine/updater.py +0 -0
  46. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine.egg-info/dependency_links.txt +0 -0
  47. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine.egg-info/entry_points.txt +0 -0
  48. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_engine.egg-info/top_level.txt +0 -0
  49. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_mode/__main__.py +0 -0
  50. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_mode/adapters.py +0 -0
  51. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_mode/installer.py +0 -0
  52. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_mode/manifest.py +0 -0
  53. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_mode/resources.json +0 -0
  54. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_mode/safety.py +0 -0
  55. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_mode/skill_bundle.py +0 -0
  56. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_mode_entry.py +0 -0
  57. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/__init__.py +0 -0
  58. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/adapters.py +0 -0
  59. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/__init__.py +0 -0
  60. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/ast_tools.py +0 -0
  61. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/compute.py +0 -0
  62. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/design_engine.py +0 -0
  63. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/diagnostics.py +0 -0
  64. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/fleet_dispatcher.py +0 -0
  65. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/mock_auditor.py +0 -0
  66. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/mutation.py +0 -0
  67. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/property_oracle.py +0 -0
  68. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/receipt_attestor.py +0 -0
  69. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/red_team_swarm.py +0 -0
  70. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/test_harness.py +0 -0
  71. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/vector_engine.py +0 -0
  72. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/visual.py +0 -0
  73. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/coder_fleet/workspace.py +0 -0
  74. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/cortical/__init__.py +0 -0
  75. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/cortical/plasticity_engine.py +0 -0
  76. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/execution_broker.py +0 -0
  77. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/proof_engine.py +0 -0
  78. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/protocol.py +0 -0
  79. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/runtime.py +0 -0
  80. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/system3/__init__.py +0 -0
  81. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/system3/causal.py +0 -0
  82. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/system3/dialectical.py +0 -0
  83. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/system3/evolution.py +0 -0
  84. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/system3/executive.py +0 -0
  85. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/system3/free_energy.py +0 -0
  86. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/system3/hyperbolic.py +0 -0
  87. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/system3/induction.py +0 -0
  88. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/system3/kripke.py +0 -0
  89. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/system3/oracle.py +0 -0
  90. {fable_engine-1.3.6 → fable_engine-1.3.8}/fable_v2/verifiers.py +0 -0
  91. {fable_engine-1.3.6 → fable_engine-1.3.8}/rules/AGENTS.md +0 -0
  92. {fable_engine-1.3.6 → fable_engine-1.3.8}/rules/GEMINI.md +0 -0
  93. {fable_engine-1.3.6 → fable_engine-1.3.8}/rules/fable-mode.md +0 -0
  94. {fable_engine-1.3.6 → fable_engine-1.3.8}/setup.cfg +0 -0
  95. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/SKILL.md +0 -0
  96. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/cortex/concurrency.md +0 -0
  97. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/cortex/design_3d.md +0 -0
  98. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/cortex/frontend_design.md +0 -0
  99. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/cortex/process.md +0 -0
  100. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/cortex/python.md +0 -0
  101. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/cortex/research.md +0 -0
  102. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/cortex/rust.md +0 -0
  103. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/cortex/security.md +0 -0
  104. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/cortex/synaptic_matrix.json +0 -0
  105. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/cortex/system.md +0 -0
  106. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/cortex/target.md +0 -0
  107. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/examples/autonomous-agentic-migration.md +0 -0
  108. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/examples/breakthrough-algorithm-synthesis.md +0 -0
  109. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/examples/deepthink-analysis-proof.md +0 -0
  110. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/examples/distributed-system-design.md +0 -0
  111. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/examples/swe-bench-pro-debugging.md +0 -0
  112. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/examples/weak_model_ollama_setup.md +0 -0
  113. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/aaa-threejs-game-engine.md +0 -0
  114. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/adversarial-code-review-swarm.md +0 -0
  115. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/agentic-execution.md +0 -0
  116. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/anti-slop-frontend-architecture.md +0 -0
  117. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/architectural-blueprinting.md +0 -0
  118. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/cinematic-design-engine.md +0 -0
  119. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/cognitive-protocol.md +0 -0
  120. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/deepthink-mode.md +0 -0
  121. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/design-system.md +0 -0
  122. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/design-tokens-and-typographies.md +0 -0
  123. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/goal-rubric-and-pipeline-automation.md +0 -0
  124. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/hebbian-cortical-plasticity.md +0 -0
  125. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/innovation-engine.md +0 -0
  126. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/interleaved-verification.md +0 -0
  127. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/model-velocity-calibration.md +0 -0
  128. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/prompt-scaffolds.md +0 -0
  129. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/proof-architecture.md +0 -0
  130. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/svg-craft-and-vector-design.md +0 -0
  131. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/system2-session-engine.md +0 -0
  132. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/system3-meta-cognition.md +0 -0
  133. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/visual-imagination-engine.md +0 -0
  134. {fable_engine-1.3.6 → fable_engine-1.3.8}/skills/fable-mode/references/weak-model-frontier-uplift.md +0 -0
  135. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/__init__.py +0 -0
  136. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_adjudicator.py +0 -0
  137. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_anti_loop_circuit_breaker.py +0 -0
  138. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_auto_updater.py +0 -0
  139. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_coder_fleet.py +0 -0
  140. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_delegation_compiler.py +0 -0
  141. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_descriptor_boundaries.py +0 -0
  142. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_design_engine.py +0 -0
  143. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_epistemic_evidence_validator.py +0 -0
  144. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_execution_broker.py +0 -0
  145. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_fable_v2.py +0 -0
  146. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_fleet_transitions.py +0 -0
  147. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_fsm_redteam_evolution.py +0 -0
  148. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_goal_rubric_and_pipeline.py +0 -0
  149. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_hebbian_plasticity.py +0 -0
  150. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_packaging_runtime.py +0 -0
  151. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_proof_engine.py +0 -0
  152. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_red_team_seal_path.py +0 -0
  153. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_red_team_swarm.py +0 -0
  154. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_redteam_remediation.py +0 -0
  155. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_registration_transaction.py +0 -0
  156. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_requested_regressions.py +0 -0
  157. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_scrapers.py +0 -0
  158. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_server_actions.py +0 -0
  159. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_server_frontier_actions.py +0 -0
  160. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_server_protocol.py +0 -0
  161. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_stealth_browser.py +0 -0
  162. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_system3.py +0 -0
  163. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_system3_deep_integration.py +0 -0
  164. {fable_engine-1.3.6 → fable_engine-1.3.8}/tests/test_system3_frontier.py +0 -0
  165. {fable_engine-1.3.6 → fable_engine-1.3.8}/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.6
3
+ Version: 1.3.8
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
@@ -133,56 +133,73 @@ red-team swarm for code, and a sealed record of what was verified.
133
133
 
134
134
  <br/>
135
135
 
136
- ### Install
136
+ ### One package, two agent environments
137
+
138
+ Fable ships as one PyPI package. The same package contains the runtime, stdio
139
+ MCP server, and complete Agent Skill. Setup is explicit so installing an MCP
140
+ server never silently activates workspace instructions.
141
+
142
+ Run setup from the project the agent will work in:
137
143
 
138
144
  ```bash
139
- pip install fable-engine
145
+ uvx --from fable-engine==1.3.8 fable-mode setup --yes
140
146
  ```
141
147
 
142
- Or install the MCP server in your editor:
148
+ This resolves the pinned package in an isolated uv environment and copies the
149
+ bundled skill to `.agents/skills/fable-mode`. Use `--dry-run` to preview or
150
+ `--target <dir>` for another skill directory. For a persistent install, use:
143
151
 
144
- [![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.6%22%2C%22fable-engine%22%5D%7D)
145
- [![Add to Cursor](https://img.shields.io/badge/Cursor-Add_MCP_server-black)](cursor://anysphere.cursor-deeplink/mcp/install?name=fable-engine&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJmYWJsZS1lbmdpbmU9PTEuMy4zIiwiZmFibGUtZW5naW5lIl19)
152
+ ```bash
153
+ python -m pip install fable-engine
154
+ fable-mode setup --yes
155
+ ```
146
156
 
147
- 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.
157
+ Then choose only the invocation that matches the agent environment.
148
158
 
149
- Point your agent at the MCP server manually:
159
+ #### Native MCP client
160
+
161
+ Run `fable-engine` as the stdio server. For example:
150
162
 
151
163
  ```jsonc
152
- // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.6 fable-engine
153
- // Cursor: ~/.cursor/mcp.json
164
+ // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.8 fable-engine
165
+ // Cursor or another JSON-configured client:
154
166
  {
155
167
  "mcpServers": {
156
168
  "fable-engine": {
157
169
  "command": "uvx",
158
- "args": ["--from", "fable-engine==1.3.6", "fable-engine"]
170
+ "args": ["--from", "fable-engine==1.3.8", "fable-engine"]
159
171
  }
160
172
  }
161
173
  }
162
174
  ```
163
175
 
164
- Python 3.10+, zero runtime dependencies. Published on [PyPI as `fable-engine`](https://pypi.org/project/fable-engine/).
176
+ [![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.8%22%2C%22fable-engine%22%5D%7D)
165
177
 
166
- <br/>
178
+ #### Shell sandbox with internet, no MCP host
167
179
 
168
- ### Optional: the Agent Skill
169
-
170
- Installing `fable-engine` gives your agent the MCP tools. It does not install
171
- or activate the Fable Mode Agent Skill - the prompt-side workflow in
172
- [`skills/fable-mode`](./skills/fable-mode/SKILL.md). Nothing in the package
173
- activates those instructions on its own; the skill is always a separate,
174
- explicit opt-in.
175
-
176
- The complete skill tree ships inside the wheel. To install it into your
177
- project's skills directory (the cross-client `.agents/skills/` convention):
180
+ The same package exposes a direct JSON transport. Pipe one `fable_session`
181
+ argument object to `fable-mode call`:
178
182
 
179
183
  ```bash
180
- uvx --from fable-engine==1.3.6 fable-mode install-skill --yes
184
+ printf '%s\n' '{"action":"create_session","session_name":"demo","objective":"Verify this change","time_budget_minutes":2}' \
185
+ | uvx --from fable-engine==1.3.8 fable-mode call
181
186
  ```
182
187
 
183
- This copies the skill to `.agents/skills/fable-mode`. Preview first with
184
- `--dry-run`, choose another location with `--target <dir>`, and reload your
185
- agent afterwards so it picks up the skill.
188
+ The command uses JSON Lines: one `fable_session` argument object per input line
189
+ and one JSON result per output line. Keep that process open for a full workflow so
190
+ the authority timer and session stay in the same trusted runtime. A one-line pipe
191
+ is useful for a single inspection call. Each uvx command can resolve an
192
+ isolated environment; `pip install` is better when the sandbox keeps a Python
193
+ environment between calls. Session data persists outside that environment in
194
+ Fable's data directory (`FABLE_DATA_DIR` can override it).
195
+
196
+ Python 3.10+, zero runtime dependencies. Published on [PyPI as `fable-engine`](https://pypi.org/project/fable-engine/).
197
+
198
+ #### Skill activation remains explicit
199
+
200
+ `setup` is the unified path. The older `install-skill` command remains as a
201
+ compatible alias for skill-only installation. Neither `fable-engine` nor
202
+ `pip install fable-engine` writes instructions into a workspace on its own.
186
203
 
187
204
  <br/>
188
205
 
@@ -90,56 +90,73 @@ red-team swarm for code, and a sealed record of what was verified.
90
90
 
91
91
  <br/>
92
92
 
93
- ### Install
93
+ ### One package, two agent environments
94
+
95
+ Fable ships as one PyPI package. The same package contains the runtime, stdio
96
+ MCP server, and complete Agent Skill. Setup is explicit so installing an MCP
97
+ server never silently activates workspace instructions.
98
+
99
+ Run setup from the project the agent will work in:
94
100
 
95
101
  ```bash
96
- pip install fable-engine
102
+ uvx --from fable-engine==1.3.8 fable-mode setup --yes
97
103
  ```
98
104
 
99
- Or install the MCP server in your editor:
105
+ This resolves the pinned package in an isolated uv environment and copies the
106
+ bundled skill to `.agents/skills/fable-mode`. Use `--dry-run` to preview or
107
+ `--target <dir>` for another skill directory. For a persistent install, use:
100
108
 
101
- [![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.6%22%2C%22fable-engine%22%5D%7D)
102
- [![Add to Cursor](https://img.shields.io/badge/Cursor-Add_MCP_server-black)](cursor://anysphere.cursor-deeplink/mcp/install?name=fable-engine&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJmYWJsZS1lbmdpbmU9PTEuMy4zIiwiZmFibGUtZW5naW5lIl19)
109
+ ```bash
110
+ python -m pip install fable-engine
111
+ fable-mode setup --yes
112
+ ```
103
113
 
104
- 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.
114
+ Then choose only the invocation that matches the agent environment.
105
115
 
106
- Point your agent at the MCP server manually:
116
+ #### Native MCP client
117
+
118
+ Run `fable-engine` as the stdio server. For example:
107
119
 
108
120
  ```jsonc
109
- // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.6 fable-engine
110
- // Cursor: ~/.cursor/mcp.json
121
+ // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.8 fable-engine
122
+ // Cursor or another JSON-configured client:
111
123
  {
112
124
  "mcpServers": {
113
125
  "fable-engine": {
114
126
  "command": "uvx",
115
- "args": ["--from", "fable-engine==1.3.6", "fable-engine"]
127
+ "args": ["--from", "fable-engine==1.3.8", "fable-engine"]
116
128
  }
117
129
  }
118
130
  }
119
131
  ```
120
132
 
121
- Python 3.10+, zero runtime dependencies. Published on [PyPI as `fable-engine`](https://pypi.org/project/fable-engine/).
133
+ [![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.8%22%2C%22fable-engine%22%5D%7D)
122
134
 
123
- <br/>
135
+ #### Shell sandbox with internet, no MCP host
124
136
 
125
- ### Optional: the Agent Skill
126
-
127
- Installing `fable-engine` gives your agent the MCP tools. It does not install
128
- or activate the Fable Mode Agent Skill - the prompt-side workflow in
129
- [`skills/fable-mode`](./skills/fable-mode/SKILL.md). Nothing in the package
130
- activates those instructions on its own; the skill is always a separate,
131
- explicit opt-in.
132
-
133
- The complete skill tree ships inside the wheel. To install it into your
134
- project's skills directory (the cross-client `.agents/skills/` convention):
137
+ The same package exposes a direct JSON transport. Pipe one `fable_session`
138
+ argument object to `fable-mode call`:
135
139
 
136
140
  ```bash
137
- uvx --from fable-engine==1.3.6 fable-mode install-skill --yes
141
+ printf '%s\n' '{"action":"create_session","session_name":"demo","objective":"Verify this change","time_budget_minutes":2}' \
142
+ | uvx --from fable-engine==1.3.8 fable-mode call
138
143
  ```
139
144
 
140
- This copies the skill to `.agents/skills/fable-mode`. Preview first with
141
- `--dry-run`, choose another location with `--target <dir>`, and reload your
142
- agent afterwards so it picks up the skill.
145
+ The command uses JSON Lines: one `fable_session` argument object per input line
146
+ and one JSON result per output line. Keep that process open for a full workflow so
147
+ the authority timer and session stay in the same trusted runtime. A one-line pipe
148
+ is useful for a single inspection call. Each uvx command can resolve an
149
+ isolated environment; `pip install` is better when the sandbox keeps a Python
150
+ environment between calls. Session data persists outside that environment in
151
+ Fable's data directory (`FABLE_DATA_DIR` can override it).
152
+
153
+ Python 3.10+, zero runtime dependencies. Published on [PyPI as `fable-engine`](https://pypi.org/project/fable-engine/).
154
+
155
+ #### Skill activation remains explicit
156
+
157
+ `setup` is the unified path. The older `install-skill` command remains as a
158
+ compatible alias for skill-only installation. Neither `fable-engine` nor
159
+ `pip install fable-engine` writes instructions into a workspace on its own.
143
160
 
144
161
  <br/>
145
162
 
@@ -2,27 +2,45 @@
2
2
 
3
3
  AI coding agents often move from a plausible plan to editing files before they have gathered enough evidence. Fable Mode adds a mechanical session gate: the agent can inspect and reason, but the Fable Engine will not report write permission until the configured authority timer and proof prerequisites pass.
4
4
 
5
- Fable Mode is two separate pieces:
5
+ Fable Mode ships as one package with three parts:
6
6
 
7
- - **Fable Engine** is the MCP server that stores session state and enforces its gates.
8
- - **The Fable Mode Agent Skill** is the optional workflow that tells a compatible agent how to use those gates.
7
+ - **Fable Engine** is the stdio MCP server that stores session state and enforces its gates.
8
+ - **The Fable Mode Agent Skill** is the bundled workflow for compatible agents.
9
+ - **The shell transport** calls the same `fable_session` API when an agent has a shell and internet but no MCP host.
9
10
 
10
- Installing one does not silently activate the other.
11
+ The package is unified; activation stays explicit. Installing or launching the
12
+ server does not silently write Agent Skill instructions into a project.
11
13
 
12
- ## Install the engine
14
+ ## Set up from one package
13
15
 
14
- You need Python 3.10+ and [uv](https://docs.astral.sh/uv/getting-started/installation/).
16
+ You need Python 3.10+ and either [uv](https://docs.astral.sh/uv/getting-started/installation/) or pip.
15
17
 
16
- Run the pinned release without changing your global Python environment:
18
+ From the project the agent will work in:
17
19
 
18
20
  ```bash
19
- uvx --from fable-engine==1.3.6 fable-engine
21
+ uvx --from fable-engine==1.3.8 fable-mode setup --dry-run
22
+ uvx --from fable-engine==1.3.8 fable-mode setup --yes
20
23
  ```
21
24
 
25
+ `setup` copies the complete bundled skill to `.agents/skills/fable-mode`. Use
26
+ `--target <dir>` for another skill directory. The installer refuses to
27
+ overwrite local edits unless you pass `--force`.
28
+
29
+ For a persistent environment, install the same package once:
30
+
31
+ ```bash
32
+ python -m pip install fable-engine
33
+ fable-mode setup --yes
34
+ ```
35
+
36
+ ## Route A: an agent with native MCP access
37
+
38
+ Run `fable-engine` as its stdio MCP server.
39
+
22
40
  ### Claude Code
23
41
 
24
42
  ```bash
25
- claude mcp add fable-engine -- uvx --from fable-engine==1.3.6 fable-engine
43
+ claude mcp add fable-engine -- uvx --from fable-engine==1.3.8 fable-engine
26
44
  ```
27
45
 
28
46
  ### Cursor or another JSON-configured MCP client
@@ -32,26 +50,33 @@ claude mcp add fable-engine -- uvx --from fable-engine==1.3.6 fable-engine
32
50
  "mcpServers": {
33
51
  "fable-engine": {
34
52
  "command": "uvx",
35
- "args": ["--from", "fable-engine==1.3.6", "fable-engine"]
53
+ "args": ["--from", "fable-engine==1.3.8", "fable-engine"]
36
54
  }
37
55
  }
38
56
  }
39
57
  ```
40
58
 
41
- Restart or reload the client, then inspect its MCP tools. You should see `fable_session` plus the browser tools exposed by the server.
59
+ Restart or reload the client. Its tool list should include `fable_session`.
42
60
 
43
- ## Install the optional Agent Skill
61
+ ## Route B: an internet-enabled shell sandbox without MCP access
44
62
 
45
- From the root of the project where you want the skill:
63
+ Pipe one `fable_session` argument object to `fable-mode call`:
46
64
 
47
65
  ```bash
48
- uvx --from fable-engine==1.3.6 fable-mode install-skill --dry-run
49
- uvx --from fable-engine==1.3.6 fable-mode install-skill --yes
66
+ printf '%s\n' '{"action":"create_session","session_name":"parser-refactor","objective":"Refactor the parser without changing its public behavior","time_budget_minutes":2}' \
67
+ | uvx --from fable-engine==1.3.8 fable-mode call
50
68
  ```
51
69
 
52
- The default destination is `.agents/skills/fable-mode`. Use `--target <dir>` for a different skill directory. The installer refuses to overwrite local edits unless you pass `--force`.
70
+ The command uses JSON Lines and passes each object to the same Fable handler used
71
+ by MCP. It prints one machine-readable result per input line. Keep one process
72
+ open for the full Think -> Prove -> Attack -> Write workflow; restarting the
73
+ process intentionally starts a fresh authority clock. Invalid, empty, non-object,
74
+ or over-1-MiB requests fail with a nonzero exit code and write the error to stderr.
53
75
 
54
- Reload the agent after installation.
76
+ For later calls, send the same argument objects shown below. When repeated uvx
77
+ calls are undesirable, use the persistent pip installation and replace the
78
+ command after the pipe with `fable-mode call`. Session files live outside the
79
+ uvx environment; set `FABLE_DATA_DIR` to choose their location.
55
80
 
56
81
  ## Run a first session
57
82
 
@@ -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.6"
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.8"
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.6"
240
+ "version": "1.3.8"
241
241
  }
242
242
  }
243
243
  })
@@ -1567,7 +1567,7 @@ class FableSession:
1567
1567
  def to_dict(self) -> Dict[str, Any]:
1568
1568
  """Serializes session to dictionary."""
1569
1569
  return {
1570
- "version": "1.3.6",
1570
+ "version": "1.3.8",
1571
1571
  "session_name": self.session_name,
1572
1572
  "session_id": self.session_id,
1573
1573
  "objective": self.objective,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: fable-engine
3
- Version: 1.3.6
3
+ Version: 1.3.8
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
@@ -133,56 +133,73 @@ red-team swarm for code, and a sealed record of what was verified.
133
133
 
134
134
  <br/>
135
135
 
136
- ### Install
136
+ ### One package, two agent environments
137
+
138
+ Fable ships as one PyPI package. The same package contains the runtime, stdio
139
+ MCP server, and complete Agent Skill. Setup is explicit so installing an MCP
140
+ server never silently activates workspace instructions.
141
+
142
+ Run setup from the project the agent will work in:
137
143
 
138
144
  ```bash
139
- pip install fable-engine
145
+ uvx --from fable-engine==1.3.8 fable-mode setup --yes
140
146
  ```
141
147
 
142
- Or install the MCP server in your editor:
148
+ This resolves the pinned package in an isolated uv environment and copies the
149
+ bundled skill to `.agents/skills/fable-mode`. Use `--dry-run` to preview or
150
+ `--target <dir>` for another skill directory. For a persistent install, use:
143
151
 
144
- [![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.6%22%2C%22fable-engine%22%5D%7D)
145
- [![Add to Cursor](https://img.shields.io/badge/Cursor-Add_MCP_server-black)](cursor://anysphere.cursor-deeplink/mcp/install?name=fable-engine&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJmYWJsZS1lbmdpbmU9PTEuMy4zIiwiZmFibGUtZW5naW5lIl19)
152
+ ```bash
153
+ python -m pip install fable-engine
154
+ fable-mode setup --yes
155
+ ```
146
156
 
147
- 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.
157
+ Then choose only the invocation that matches the agent environment.
148
158
 
149
- Point your agent at the MCP server manually:
159
+ #### Native MCP client
160
+
161
+ Run `fable-engine` as the stdio server. For example:
150
162
 
151
163
  ```jsonc
152
- // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.6 fable-engine
153
- // Cursor: ~/.cursor/mcp.json
164
+ // Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.8 fable-engine
165
+ // Cursor or another JSON-configured client:
154
166
  {
155
167
  "mcpServers": {
156
168
  "fable-engine": {
157
169
  "command": "uvx",
158
- "args": ["--from", "fable-engine==1.3.6", "fable-engine"]
170
+ "args": ["--from", "fable-engine==1.3.8", "fable-engine"]
159
171
  }
160
172
  }
161
173
  }
162
174
  ```
163
175
 
164
- Python 3.10+, zero runtime dependencies. Published on [PyPI as `fable-engine`](https://pypi.org/project/fable-engine/).
176
+ [![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.8%22%2C%22fable-engine%22%5D%7D)
165
177
 
166
- <br/>
178
+ #### Shell sandbox with internet, no MCP host
167
179
 
168
- ### Optional: the Agent Skill
169
-
170
- Installing `fable-engine` gives your agent the MCP tools. It does not install
171
- or activate the Fable Mode Agent Skill - the prompt-side workflow in
172
- [`skills/fable-mode`](./skills/fable-mode/SKILL.md). Nothing in the package
173
- activates those instructions on its own; the skill is always a separate,
174
- explicit opt-in.
175
-
176
- The complete skill tree ships inside the wheel. To install it into your
177
- project's skills directory (the cross-client `.agents/skills/` convention):
180
+ The same package exposes a direct JSON transport. Pipe one `fable_session`
181
+ argument object to `fable-mode call`:
178
182
 
179
183
  ```bash
180
- uvx --from fable-engine==1.3.6 fable-mode install-skill --yes
184
+ printf '%s\n' '{"action":"create_session","session_name":"demo","objective":"Verify this change","time_budget_minutes":2}' \
185
+ | uvx --from fable-engine==1.3.8 fable-mode call
181
186
  ```
182
187
 
183
- This copies the skill to `.agents/skills/fable-mode`. Preview first with
184
- `--dry-run`, choose another location with `--target <dir>`, and reload your
185
- agent afterwards so it picks up the skill.
188
+ The command uses JSON Lines: one `fable_session` argument object per input line
189
+ and one JSON result per output line. Keep that process open for a full workflow so
190
+ the authority timer and session stay in the same trusted runtime. A one-line pipe
191
+ is useful for a single inspection call. Each uvx command can resolve an
192
+ isolated environment; `pip install` is better when the sandbox keeps a Python
193
+ environment between calls. Session data persists outside that environment in
194
+ Fable's data directory (`FABLE_DATA_DIR` can override it).
195
+
196
+ Python 3.10+, zero runtime dependencies. Published on [PyPI as `fable-engine`](https://pypi.org/project/fable-engine/).
197
+
198
+ #### Skill activation remains explicit
199
+
200
+ `setup` is the unified path. The older `install-skill` command remains as a
201
+ compatible alias for skill-only installation. Neither `fable-engine` nor
202
+ `pip install fable-engine` writes instructions into a workspace on its own.
186
203
 
187
204
  <br/>
188
205
 
@@ -154,6 +154,7 @@ tests/test_scrapers.py
154
154
  tests/test_server_actions.py
155
155
  tests/test_server_frontier_actions.py
156
156
  tests/test_server_protocol.py
157
+ tests/test_shell_call.py
157
158
  tests/test_skill_distribution.py
158
159
  tests/test_stealth_browser.py
159
160
  tests/test_system3.py
@@ -1,3 +1,3 @@
1
1
  """Fable Mode portable runtime and transactional installer."""
2
2
 
3
- __version__ = "1.3.6"
3
+ __version__ = "1.3.8"
@@ -249,10 +249,59 @@ def _is_frozen() -> bool:
249
249
  return bool(getattr(sys, "frozen", False) or getattr(sys, "_MEIPASS", None))
250
250
 
251
251
 
252
+ MAX_SHELL_REQUEST_BYTES = 1 * 1024 * 1024
253
+
254
+
255
+ def _shell_call() -> int:
256
+ """Call fable_session over bounded JSON Lines without an MCP host."""
257
+ from fable_engine.server import handle_fable_session
258
+
259
+ # JSON Lines is a UTF-8 protocol. Session text can contain characters the
260
+ # platform default encoding (cp1252 on Windows pipes) cannot encode, so
261
+ # pin both output streams to UTF-8 instead of inheriting the locale.
262
+ for std in (sys.stdout, sys.stderr):
263
+ reconfigure = getattr(std, "reconfigure", None)
264
+ if callable(reconfigure):
265
+ reconfigure(encoding="utf-8", errors="replace")
266
+
267
+ stream = getattr(sys.stdin, "buffer", sys.stdin)
268
+ handled = 0
269
+ while True:
270
+ raw = stream.readline(MAX_SHELL_REQUEST_BYTES + 1)
271
+ if not raw:
272
+ break
273
+ if isinstance(raw, str):
274
+ raw = raw.encode("utf-8")
275
+ if len(raw) > MAX_SHELL_REQUEST_BYTES or (len(raw) == MAX_SHELL_REQUEST_BYTES + 1 and not raw.endswith(b"\n")):
276
+ print("call: request exceeds 1 MiB", file=sys.stderr)
277
+ return 2
278
+ if not raw.strip():
279
+ continue
280
+ try:
281
+ request = json.loads(raw.decode("utf-8"))
282
+ if not isinstance(request, dict):
283
+ raise ValueError("request must be a JSON object")
284
+ if not isinstance(request.get("action"), str) or not request["action"]:
285
+ raise ValueError("request must include a non-empty string action")
286
+ result = handle_fable_session(request)
287
+ except (UnicodeDecodeError, json.JSONDecodeError, ValueError, TypeError, OSError) as exc:
288
+ print(f"call: {exc}", file=sys.stderr)
289
+ return 2
290
+ except Exception as exc:
291
+ print(f"call: {exc}", file=sys.stderr)
292
+ return 1
293
+ print(json.dumps({"ok": True, "result": result}, ensure_ascii=False), flush=True)
294
+ handled += 1
295
+ if not handled:
296
+ print("call: expected at least one JSON object on stdin", file=sys.stderr)
297
+ return 2
298
+ return 0
299
+
300
+
252
301
  def _parser() -> argparse.ArgumentParser:
253
302
  p = argparse.ArgumentParser(prog="fable-mode")
254
303
  p.add_argument("--version", action="version", version=__version__)
255
- p.add_argument("mode", nargs="?", choices=("install", "serve", "verify", "version", "uninstall", "install-skill"), default="install")
304
+ p.add_argument("mode", nargs="?", choices=("install", "serve", "verify", "version", "uninstall", "install-skill", "setup", "call"), default="install")
256
305
  p.add_argument("--yes", action="store_true", help="confirm unattended installation")
257
306
  p.add_argument("--register-hosts", action="store_true")
258
307
  p.add_argument("--aliases", action="store_true", help="also probe legacy cc/antigravity aliases")
@@ -305,7 +354,9 @@ def main(argv: list[str] | None = None) -> int:
305
354
  except (OSError, RuntimeError, ValueError) as exc:
306
355
  print(f"serve: {exc}", file=sys.stderr)
307
356
  return 1
308
- if args.mode == "install-skill":
357
+ if args.mode == "call":
358
+ return _shell_call()
359
+ if args.mode in {"install-skill", "setup"}:
309
360
  target = (Path(args.target).expanduser() if args.target
310
361
  else Path.cwd() / ".agents" / "skills" / SKILL_NAME)
311
362
  if not args.dry_run and not args.yes:
@@ -329,6 +380,9 @@ def main(argv: list[str] | None = None) -> int:
329
380
  print(f"{verb} the Fable Mode Agent Skill ({result.planned} files: {changes}) to {result.target}")
330
381
  if not result.dry_run:
331
382
  print("Restart or reload your agent to pick up the skill.")
383
+ if args.mode == "setup":
384
+ print("Native MCP: run `fable-engine` as a stdio server.")
385
+ print("Shell sandbox: pipe one fable_session argument object to `fable-mode call`.")
332
386
  return 0
333
387
  installer = Installer(Path(args.install_dir) if args.install_dir else None)
334
388
  if args.mode == "verify":
@@ -100,7 +100,10 @@ class SandboxedTarget:
100
100
  if not ready:
101
101
  raise TimeoutError(f"sandbox worker did not respond within {timeout:.1f}s")
102
102
  line = self._proc.stdout.readline()
103
- except (ImportError, AttributeError):
103
+ except (ImportError, AttributeError, OSError):
104
+ # ImportError/AttributeError: no select module or fileno.
105
+ # OSError: Windows select() only accepts sockets and raises on
106
+ # pipe handles (WinError 10038) - use the thread fallback.
104
107
  result: Dict[str, Any] = {}
105
108
 
106
109
  def _reader() -> None:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fable-engine"
7
- version = "1.3.6"
7
+ version = "1.3.8"
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" }]
@@ -40,7 +40,7 @@ class build_py(_build_py):
40
40
 
41
41
  setup(
42
42
  name="fable-engine",
43
- version="1.3.6",
43
+ version="1.3.8",
44
44
  description="Independent deterministic System 2 cognitive engine and mechanical time-lock MCP server",
45
45
  long_description=README,
46
46
  long_description_content_type="text/markdown",
@@ -0,0 +1,58 @@
1
+ """Direct shell transport for agents without native MCP access."""
2
+ import io
3
+ import json
4
+ import sys
5
+ import unittest
6
+ from unittest import mock
7
+
8
+ from fable_mode.launcher import MAX_SHELL_REQUEST_BYTES, _shell_call
9
+
10
+
11
+ class ShellCallTests(unittest.TestCase):
12
+ def _call(self, payload: bytes):
13
+ stdin = io.TextIOWrapper(io.BytesIO(payload), encoding="utf-8")
14
+ stdout, stderr = io.StringIO(), io.StringIO()
15
+ with mock.patch.object(sys, "stdin", stdin), mock.patch.object(sys, "stdout", stdout), mock.patch.object(sys, "stderr", stderr):
16
+ status = _shell_call()
17
+ return status, stdout.getvalue(), stderr.getvalue()
18
+
19
+ def test_valid_json_lines_share_one_runtime_and_emit_one_result_each(self):
20
+ requests = [{"action": "list_sessions"}, {"action": "list_sessions"}]
21
+ payload = "".join(json.dumps(item) + "\n" for item in requests).encode()
22
+ with mock.patch("fable_engine.server.handle_fable_session", side_effect=["first", "second"]) as handle:
23
+ status, stdout, stderr = self._call(payload)
24
+ self.assertEqual(status, 0)
25
+ self.assertEqual(stderr, "")
26
+ self.assertEqual([json.loads(line) for line in stdout.splitlines()],
27
+ [{"ok": True, "result": "first"}, {"ok": True, "result": "second"}])
28
+ self.assertEqual([call.args[0] for call in handle.call_args_list], requests)
29
+
30
+ def test_empty_invalid_and_non_object_requests_fail(self):
31
+ for payload in (b"", b"not json", b"[]", b'{}', b'{"action": ""}'):
32
+ with self.subTest(payload=payload):
33
+ status, stdout, stderr = self._call(payload)
34
+ self.assertEqual(status, 2)
35
+ self.assertEqual(stdout, "")
36
+ self.assertTrue(stderr.startswith("call: "))
37
+
38
+ def test_oversized_request_is_rejected_before_json_parse(self):
39
+ status, stdout, stderr = self._call(b"{" + b"x" * MAX_SHELL_REQUEST_BYTES)
40
+ self.assertEqual(status, 2)
41
+ self.assertEqual(stdout, "")
42
+ self.assertIn("exceeds 1 MiB", stderr)
43
+
44
+ def test_output_stays_utf8_when_pipe_encoding_is_not(self):
45
+ # Windows pipes default to cp1252: session text with emoji must still
46
+ # reach the caller as decodable UTF-8 JSON instead of crashing.
47
+ buffer = io.BytesIO()
48
+ narrow_stdout = io.TextIOWrapper(buffer, encoding="cp1252")
49
+ stdin = io.TextIOWrapper(io.BytesIO(b'{"action": "list_sessions"}\n'), encoding="utf-8")
50
+ with mock.patch.object(sys, "stdin", stdin), mock.patch.object(sys, "stdout", narrow_stdout), \
51
+ mock.patch("fable_engine.server.handle_fable_session",
52
+ return_value="### \U0001f5c2\ufe0f Available Fable Sessions"):
53
+ status = _shell_call()
54
+ narrow_stdout.flush()
55
+ self.assertEqual(status, 0)
56
+ payload = json.loads(buffer.getvalue().decode("utf-8"))
57
+ self.assertTrue(payload["ok"])
58
+ self.assertIn("\U0001f5c2\ufe0f", payload["result"])