fable-engine 1.3.2__tar.gz → 1.3.4__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.
- {fable_engine-1.3.2/fable_engine.egg-info → fable_engine-1.3.4}/PKG-INFO +27 -6
- {fable_engine-1.3.2 → fable_engine-1.3.4}/README.md +26 -5
- {fable_engine-1.3.2 → fable_engine-1.3.4}/docs/stop-ai-agents-writing-too-early.md +4 -4
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/scrapers/base.py +1 -1
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/server.py +1 -1
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/session.py +1 -1
- {fable_engine-1.3.2 → fable_engine-1.3.4/fable_engine.egg-info}/PKG-INFO +27 -6
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine.egg-info/SOURCES.txt +2 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_mode/__init__.py +1 -1
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_mode/installer.py +12 -1
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_mode/launcher.py +29 -1
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_mode/manifest.py +6 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_mode/resources.json +7 -1
- fable_engine-1.3.4/fable_mode/skill_bundle.py +163 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/pyproject.toml +1 -1
- fable_engine-1.3.4/setup.py +70 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_packaging_runtime.py +14 -0
- fable_engine-1.3.4/tests/test_skill_distribution.py +230 -0
- fable_engine-1.3.2/setup.py +0 -36
- {fable_engine-1.3.2 → fable_engine-1.3.4}/LICENSE +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/MANIFEST.in +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/docs/fable-v1-v2-migration.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/docs/fable-v2-architecture.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/docs/system3-architecture.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_compressor.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/__init__.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/actions/__init__.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/actions/cas.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/actions/deliberation.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/actions/fleet.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/actions/lifecycle.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/actions/scrapers.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/actions/system3.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/browser.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/cas.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/fable_session.json +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/guards.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/schema.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/scrapers/__init__.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/scrapers/arxiv.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/scrapers/github.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/scrapers/reddit.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/scrapers/web.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/scrapers/x.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/scrapers/youtube.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/test_server.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine/updater.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine.egg-info/dependency_links.txt +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine.egg-info/entry_points.txt +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_engine.egg-info/top_level.txt +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_mode/__main__.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_mode/adapters.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_mode/safety.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_mode_entry.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/__init__.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/adapters.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/coder_fleet/__init__.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/coder_fleet/ast_tools.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/coder_fleet/compute.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/coder_fleet/design_engine.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/coder_fleet/diagnostics.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/coder_fleet/fleet_dispatcher.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/coder_fleet/mock_auditor.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/coder_fleet/mutation.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/coder_fleet/property_oracle.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/coder_fleet/receipt_attestor.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/coder_fleet/red_team_swarm.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/coder_fleet/test_harness.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/coder_fleet/vector_engine.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/coder_fleet/visual.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/coder_fleet/workspace.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/cortical/__init__.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/cortical/plasticity_engine.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/execution_broker.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/proof_engine.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/protocol.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/runtime.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/system3/__init__.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/system3/causal.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/system3/dialectical.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/system3/evolution.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/system3/executive.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/system3/free_energy.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/system3/hyperbolic.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/system3/induction.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/system3/kripke.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/system3/oracle.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/fable_v2/verifiers.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/rules/AGENTS.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/rules/GEMINI.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/rules/fable-mode.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/setup.cfg +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/SKILL.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/cortex/concurrency.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/cortex/design_3d.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/cortex/frontend_design.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/cortex/process.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/cortex/python.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/cortex/research.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/cortex/rust.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/cortex/security.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/cortex/synaptic_matrix.json +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/cortex/system.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/cortex/target.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/examples/autonomous-agentic-migration.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/examples/breakthrough-algorithm-synthesis.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/examples/deepthink-analysis-proof.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/examples/distributed-system-design.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/examples/swe-bench-pro-debugging.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/examples/weak_model_ollama_setup.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/aaa-threejs-game-engine.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/adversarial-code-review-swarm.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/agentic-execution.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/anti-slop-frontend-architecture.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/architectural-blueprinting.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/cinematic-design-engine.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/cognitive-protocol.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/deepthink-mode.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/design-tokens-and-typographies.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/goal-rubric-and-pipeline-automation.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/hebbian-cortical-plasticity.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/innovation-engine.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/interleaved-verification.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/model-velocity-calibration.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/prompt-scaffolds.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/proof-architecture.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/svg-craft-and-vector-design.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/system2-session-engine.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/system3-meta-cognition.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/visual-imagination-engine.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/skills/fable-mode/references/weak-model-frontier-uplift.md +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/__init__.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_anti_loop_circuit_breaker.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_auto_updater.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_coder_fleet.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_delegation_compiler.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_descriptor_boundaries.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_design_engine.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_epistemic_evidence_validator.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_execution_broker.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_fable_v2.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_fleet_transitions.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_fsm_redteam_evolution.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_goal_rubric_and_pipeline.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_hebbian_plasticity.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_proof_engine.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_red_team_swarm.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_redteam_remediation.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_registration_transaction.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_requested_regressions.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_scrapers.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_server_actions.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_server_frontier_actions.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_server_protocol.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_stealth_browser.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_system3.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_system3_deep_integration.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/tests/test_system3_frontier.py +0 -0
- {fable_engine-1.3.2 → fable_engine-1.3.4}/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.
|
|
3
|
+
Version: 1.3.4
|
|
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
|
|
|
66
|
-
<img src="https://img.shields.io/badge/tests-
|
|
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
|
-
[](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.
|
|
120
|
-
[](cursor://anysphere.cursor-deeplink/mcp/install?name=fable-engine&config=
|
|
119
|
+
[](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.4%22%2C%22fable-engine%22%5D%7D)
|
|
120
|
+
[](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.
|
|
127
|
+
// Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.4 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.
|
|
133
|
+
"args": ["--from", "fable-engine==1.3.4", "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.4 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
|
|
|
21
21
|
<img src="https://img.shields.io/badge/dependencies-0-black" alt="Zero Dependencies"/>
|
|
22
22
|
|
|
23
|
-
<img src="https://img.shields.io/badge/tests-
|
|
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
|
-
[](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.
|
|
77
|
-
[](cursor://anysphere.cursor-deeplink/mcp/install?name=fable-engine&config=
|
|
76
|
+
[](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.4%22%2C%22fable-engine%22%5D%7D)
|
|
77
|
+
[](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.
|
|
84
|
+
// Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.4 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.
|
|
90
|
+
"args": ["--from", "fable-engine==1.3.4", "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.4 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.
|
|
21
|
+
uvx --from fable-engine==1.3.4 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.
|
|
27
|
+
claude mcp add fable-engine -- uvx --from fable-engine==1.3.4 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.
|
|
37
|
+
"args": ["--from", "fable-engine==1.3.4", "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.
|
|
71
|
+
uvx --from fable-engine==1.3.4 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.
|
|
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.4"
|
|
29
29
|
|
|
30
30
|
MAX_RETRY_DELAY_SECONDS = 10.0
|
|
31
31
|
|
|
@@ -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.
|
|
1533
|
+
"version": "1.3.4",
|
|
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.
|
|
3
|
+
Version: 1.3.4
|
|
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
|
|
|
66
|
-
<img src="https://img.shields.io/badge/tests-
|
|
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
|
-
[](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.
|
|
120
|
-
[](cursor://anysphere.cursor-deeplink/mcp/install?name=fable-engine&config=
|
|
119
|
+
[](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.4%22%2C%22fable-engine%22%5D%7D)
|
|
120
|
+
[](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.
|
|
127
|
+
// Claude Code: claude mcp add fable-engine -- uvx --from fable-engine==1.3.4 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.
|
|
133
|
+
"args": ["--from", "fable-engine==1.3.4", "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.4 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
|
|
@@ -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
|
-
|
|
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)
|
|
@@ -15,6 +15,7 @@ ALLOWED_FILES: tuple[str, ...] = (
|
|
|
15
15
|
"fable_mode/launcher.py",
|
|
16
16
|
"fable_mode/manifest.py",
|
|
17
17
|
"fable_mode/safety.py",
|
|
18
|
+
"fable_mode/skill_bundle.py",
|
|
18
19
|
"fable_mode/resources.json",
|
|
19
20
|
"fable_engine/__init__.py",
|
|
20
21
|
"fable_engine/browser.py",
|
|
@@ -116,6 +117,11 @@ ALLOWED_FILES: tuple[str, ...] = (
|
|
|
116
117
|
"skills/fable-mode/cortex/frontend_design.md",
|
|
117
118
|
"skills/fable-mode/cortex/research.md",
|
|
118
119
|
"skills/fable-mode/cortex/concurrency.md",
|
|
120
|
+
"skills/fable-mode/cortex/process.md",
|
|
121
|
+
"skills/fable-mode/cortex/security.md",
|
|
122
|
+
"skills/fable-mode/cortex/system.md",
|
|
123
|
+
"skills/fable-mode/cortex/target.md",
|
|
124
|
+
"skills/fable-mode/cortex/synaptic_matrix.json",
|
|
119
125
|
"LICENSE",
|
|
120
126
|
)
|
|
121
127
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"fable_mode/launcher.py",
|
|
9
9
|
"fable_mode/manifest.py",
|
|
10
10
|
"fable_mode/safety.py",
|
|
11
|
+
"fable_mode/skill_bundle.py",
|
|
11
12
|
"fable_mode/resources.json",
|
|
12
13
|
"fable_engine/__init__.py",
|
|
13
14
|
"fable_engine/browser.py",
|
|
@@ -109,6 +110,11 @@
|
|
|
109
110
|
"skills/fable-mode/cortex/frontend_design.md",
|
|
110
111
|
"skills/fable-mode/cortex/research.md",
|
|
111
112
|
"skills/fable-mode/cortex/concurrency.md",
|
|
113
|
+
"skills/fable-mode/cortex/process.md",
|
|
114
|
+
"skills/fable-mode/cortex/security.md",
|
|
115
|
+
"skills/fable-mode/cortex/system.md",
|
|
116
|
+
"skills/fable-mode/cortex/target.md",
|
|
117
|
+
"skills/fable-mode/cortex/synaptic_matrix.json",
|
|
112
118
|
"LICENSE"
|
|
113
119
|
]
|
|
114
|
-
}
|
|
120
|
+
}
|
|
@@ -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.
|
|
7
|
+
version = "1.3.4"
|
|
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.4",
|
|
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
|
+
)
|