diff --git a/README.md b/README.md
index b5699bb..1922dce 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,69 @@
# coinhunter-cli
-CoinHunter CLI is the executable tooling layer for CoinHunter.
+
+ The executable CLI layer for CoinHunter.
+ Runtime-safe trading operations, precheck orchestration, review tooling, and market probes.
+
-• Code lives in this repository.
-• User runtime data lives in ~/.coinhunter/ by default.
-• Hermes skills can call this CLI instead of embedding large script collections.
-• Runtime locations can be overridden with COINHUNTER_HOME, HERMES_HOME, COINHUNTER_ENV_FILE, and HERMES_BIN.
+
+
+
+
+
-## Install
+## Why this repo exists
+
+CoinHunter is evolving from a loose bundle of automation scripts into a proper installable command-line tool.
+
+This repository is the tooling layer:
+
+- Code and executable behavior live here.
+- User runtime state lives in `~/.coinhunter/` by default.
+- Hermes skills can call this CLI instead of embedding large script collections.
+- Runtime paths can be overridden with `COINHUNTER_HOME`, `HERMES_HOME`, `COINHUNTER_ENV_FILE`, and `HERMES_BIN`.
+
+In short:
+
+- `coinhunter-cli` = tool
+- CoinHunter skill = strategy / workflow / prompting layer
+- `~/.coinhunter` = user data, logs, state, reviews
+
+## Current architecture
+
+```text
+coinhunter-cli/
+├── src/coinhunter/
+│ ├── cli.py # top-level command router
+│ ├── runtime.py # runtime paths + env loading
+│ ├── doctor.py # diagnostics
+│ ├── paths.py # runtime path inspection
+│ ├── commands/ # thin CLI adapters
+│ ├── services/ # orchestration / application services
+│ └── *.py # compatibility modules + legacy logic under extraction
+└── README.md
+```
+
+The repo is actively being refactored toward a cleaner split:
+
+- `commands/` → argument / CLI adapters
+- `services/` → orchestration and application workflows
+- `runtime/` → paths, env, files, locks, config
+- future `domain/` → trading and precheck core logic
+
+## Implemented command/service splits
+
+The first extraction pass is already live:
+
+- `smart-executor` → `commands.smart_executor` + `services.smart_executor_service`
+- `precheck` → `commands.precheck` + `services.precheck_service`
+- `precheck` internals now also have dedicated service modules for:
+ - `services.precheck_state`
+ - `services.precheck_snapshot`
+ - `services.precheck_analysis`
+
+This keeps behavior stable while giving the codebase a cleaner landing zone for deeper refactors.
+
+## Installation
Editable install:
@@ -15,38 +71,152 @@ Editable install:
pip install -e .
```
-## Core commands
+Run directly after install:
```bash
+coinhunter --help
coinhunter --version
-coinhunter doctor
-coinhunter paths
+```
+
+## Quickstart
+
+Initialize user state:
+
+```bash
coinhunter init
+```
+
+Inspect runtime wiring:
+
+```bash
+coinhunter paths
+coinhunter doctor
+```
+
+Validate exchange credentials:
+
+```bash
coinhunter check-api
-coinhunter smart-executor balances
+```
+
+Run precheck / gate plumbing:
+
+```bash
coinhunter precheck
+coinhunter precheck --mark-run-requested "external-gate queued cron run"
+coinhunter precheck --ack "analysis finished"
+```
+
+Inspect balances or execute trading actions:
+
+```bash
+coinhunter smart-executor balances
+coinhunter smart-executor status
+coinhunter smart-executor hold
+coinhunter smart-executor buy ENJUSDT 50
+coinhunter smart-executor sell-all ENJUSDT
+```
+
+Generate review data:
+
+```bash
coinhunter review-context 12
+coinhunter review-engine 12
+```
+
+Probe external market data:
+
+```bash
coinhunter market-probe bybit-ticker BTCUSDT
+coinhunter market-probe bybit-klines BTCUSDT 60 20
```
## Runtime model
Default layout:
-• ~/.coinhunter/ stores config, positions, logs, reviews, and state.
-• ~/.hermes/.env stores exchange credentials unless COINHUNTER_ENV_FILE overrides it.
-• hermes is discovered from PATH first, then ~/.local/bin/hermes, unless HERMES_BIN overrides it.
+```text
+~/.coinhunter/
+├── accounts.json
+├── config.json
+├── executions.json
+├── notes.json
+├── positions.json
+├── watchlist.json
+├── logs/
+├── reviews/
+└── state/
+```
-## Next refactor direction
+Credential loading:
-This repository now has a dedicated runtime layer and CLI diagnostics. The next major cleanup is to split command adapters from trading services so the internal architecture becomes:
+- Binance credentials are read from `~/.hermes/.env` by default.
+- `COINHUNTER_ENV_FILE` can point to a different env file.
+- `hermes` is resolved from `PATH` first, then `~/.local/bin/hermes`, unless `HERMES_BIN` overrides it.
-• commands/
-• services/
-• runtime/
-• domain logic
+## Useful commands
-The first split is already in place for:
+### Diagnostics
-• smart-executor -> commands.smart_executor + services.smart_executor_service
-• precheck -> commands.precheck + services.precheck_service
+```bash
+coinhunter doctor
+coinhunter paths
+coinhunter check-api
+```
+
+### Trading and execution
+
+```bash
+coinhunter smart-executor balances
+coinhunter smart-executor status
+coinhunter smart-executor hold
+coinhunter smart-executor rebalance FROMUSDT TOUSDT
+```
+
+### Precheck and orchestration
+
+```bash
+coinhunter precheck
+coinhunter external-gate
+coinhunter rotate-external-gate-log
+```
+
+### Review and market research
+
+```bash
+coinhunter review-context 12
+coinhunter review-engine 12
+coinhunter market-probe bybit-ticker BTCUSDT
+```
+
+## Development notes
+
+This project is intentionally moving in small, safe refactor steps:
+
+1. Separate runtime concerns from hardcoded paths.
+2. Move command dispatch into thin adapters.
+3. Introduce orchestration services.
+4. Extract reusable domain logic from large compatibility modules.
+5. Keep cron / Hermes integration stable during migration.
+
+That means some compatibility modules still exist, but the direction is deliberate.
+
+## Near-term roadmap
+
+- Extract more logic from `smart_executor.py` into dedicated execution / portfolio services.
+- Continue shrinking `precheck.py` by moving snapshot and analysis internals into reusable modules.
+- Add `domain/` models for positions, signals, and trigger analysis.
+- Add tests for runtime paths, precheck service behavior, and CLI stability.
+- Evolve toward a more polished installable CLI workflow.
+
+## Philosophy
+
+CoinHunter should become:
+
+- more professional
+- more maintainable
+- safer to operate
+- easier for humans and agents to call
+- less dependent on prompt-only correctness
+
+This repo is where that evolution happens.
diff --git a/src/coinhunter/precheck.py b/src/coinhunter/precheck.py
index 3f68a06..787ceee 100755
--- a/src/coinhunter/precheck.py
+++ b/src/coinhunter/precheck.py
@@ -904,23 +904,15 @@ def update_state_after_observation(state: dict, snapshot: dict, analysis: dict):
def mark_run_requested(note: str = ""):
- state = load_state()
- state["run_requested_at"] = utc_iso()
- state["run_request_note"] = note
- save_state(state)
- print(json.dumps({"ok": True, "run_requested_at": state["run_requested_at"], "note": note}, ensure_ascii=False))
+ from .services.precheck_state import mark_run_requested as service_mark_run_requested
+
+ return service_mark_run_requested(note)
def ack_analysis(note: str = ""):
- state = load_state()
- state["last_deep_analysis_at"] = utc_iso()
- state["pending_trigger"] = False
- state["pending_reasons"] = []
- state["last_ack_note"] = note
- state.pop("run_requested_at", None)
- state.pop("run_request_note", None)
- save_state(state)
- print(json.dumps({"ok": True, "acked_at": state["last_deep_analysis_at"], "note": note}, ensure_ascii=False))
+ from .services.precheck_state import ack_analysis as service_ack_analysis
+
+ return service_ack_analysis(note)
def main():
diff --git a/src/coinhunter/services/precheck_analysis.py b/src/coinhunter/services/precheck_analysis.py
new file mode 100644
index 0000000..57e3254
--- /dev/null
+++ b/src/coinhunter/services/precheck_analysis.py
@@ -0,0 +1,25 @@
+"""Analysis helpers for precheck."""
+
+from __future__ import annotations
+
+from .. import precheck as precheck_module
+
+
+def analyze_trigger(snapshot: dict, state: dict) -> dict:
+ return precheck_module.analyze_trigger(snapshot, state)
+
+
+def build_failure_payload(exc: Exception) -> dict:
+ return {
+ "generated_at": precheck_module.utc_iso(),
+ "status": "deep_analysis_required",
+ "should_analyze": True,
+ "pending_trigger": True,
+ "cooldown_active": False,
+ "reasons": ["precheck-error"],
+ "hard_reasons": ["precheck-error"],
+ "soft_reasons": [],
+ "soft_score": 0,
+ "details": [str(exc)],
+ "compact_summary": f"预检查失败,转入深度分析兜底: {exc}",
+ }
diff --git a/src/coinhunter/services/precheck_service.py b/src/coinhunter/services/precheck_service.py
index b5a2c36..b68f509 100644
--- a/src/coinhunter/services/precheck_service.py
+++ b/src/coinhunter/services/precheck_service.py
@@ -5,39 +5,26 @@ from __future__ import annotations
import json
import sys
-from .. import precheck as precheck_module
+from . import precheck_analysis, precheck_snapshot, precheck_state
def run(argv: list[str] | None = None) -> int:
argv = list(sys.argv[1:] if argv is None else argv)
if argv and argv[0] == "--ack":
- precheck_module.ack_analysis(" ".join(argv[1:]).strip())
+ precheck_state.ack_analysis(" ".join(argv[1:]).strip())
return 0
if argv and argv[0] == "--mark-run-requested":
- precheck_module.mark_run_requested(" ".join(argv[1:]).strip())
+ precheck_state.mark_run_requested(" ".join(argv[1:]).strip())
return 0
try:
- state = precheck_module.sanitize_state_for_stale_triggers(precheck_module.load_state())
- snapshot = precheck_module.build_snapshot()
- analysis = precheck_module.analyze_trigger(snapshot, state)
- precheck_module.save_state(precheck_module.update_state_after_observation(state, snapshot, analysis))
+ state = precheck_state.sanitize_state_for_stale_triggers(precheck_state.load_state())
+ snapshot = precheck_snapshot.build_snapshot()
+ analysis = precheck_analysis.analyze_trigger(snapshot, state)
+ precheck_state.save_state(precheck_state.update_state_after_observation(state, snapshot, analysis))
print(json.dumps(analysis, ensure_ascii=False, indent=2))
return 0
except Exception as exc:
- failure = {
- "generated_at": precheck_module.utc_iso(),
- "status": "deep_analysis_required",
- "should_analyze": True,
- "pending_trigger": True,
- "cooldown_active": False,
- "reasons": ["precheck-error"],
- "hard_reasons": ["precheck-error"],
- "soft_reasons": [],
- "soft_score": 0,
- "details": [str(exc)],
- "compact_summary": f"预检查失败,转入深度分析兜底: {exc}",
- }
- print(json.dumps(failure, ensure_ascii=False, indent=2))
+ print(json.dumps(precheck_analysis.build_failure_payload(exc), ensure_ascii=False, indent=2))
return 0
diff --git a/src/coinhunter/services/precheck_snapshot.py b/src/coinhunter/services/precheck_snapshot.py
new file mode 100644
index 0000000..81ac936
--- /dev/null
+++ b/src/coinhunter/services/precheck_snapshot.py
@@ -0,0 +1,9 @@
+"""Snapshot construction helpers for precheck."""
+
+from __future__ import annotations
+
+from .. import precheck as precheck_module
+
+
+def build_snapshot() -> dict:
+ return precheck_module.build_snapshot()
diff --git a/src/coinhunter/services/precheck_state.py b/src/coinhunter/services/precheck_state.py
new file mode 100644
index 0000000..d05639b
--- /dev/null
+++ b/src/coinhunter/services/precheck_state.py
@@ -0,0 +1,47 @@
+"""State helpers for precheck orchestration."""
+
+from __future__ import annotations
+
+import json
+
+from .. import precheck as precheck_module
+
+
+def load_state() -> dict:
+ return precheck_module.load_state()
+
+
+def save_state(state: dict) -> None:
+ precheck_module.save_state(state)
+
+
+def sanitize_state_for_stale_triggers(state: dict) -> dict:
+ return precheck_module.sanitize_state_for_stale_triggers(state)
+
+
+def update_state_after_observation(state: dict, snapshot: dict, analysis: dict) -> dict:
+ return precheck_module.update_state_after_observation(state, snapshot, analysis)
+
+
+def mark_run_requested(note: str = "") -> dict:
+ state = load_state()
+ state["run_requested_at"] = precheck_module.utc_iso()
+ state["run_request_note"] = note
+ save_state(state)
+ payload = {"ok": True, "run_requested_at": state["run_requested_at"], "note": note}
+ print(json.dumps(payload, ensure_ascii=False))
+ return payload
+
+
+def ack_analysis(note: str = "") -> dict:
+ state = load_state()
+ state["last_deep_analysis_at"] = precheck_module.utc_iso()
+ state["pending_trigger"] = False
+ state["pending_reasons"] = []
+ state["last_ack_note"] = note
+ state.pop("run_requested_at", None)
+ state.pop("run_request_note", None)
+ save_state(state)
+ payload = {"ok": True, "acked_at": state["last_deep_analysis_at"], "note": note}
+ print(json.dumps(payload, ensure_ascii=False))
+ return payload