From 4d8232f4fe8125e33a9e967c4331d100d337b58a Mon Sep 17 00:00:00 2001 From: JaeLeex Date: Thu, 2 Jul 2026 16:03:24 -0400 Subject: [PATCH 1/2] chore: archive quoting_engine, SEDA oracle, legacy deploy templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move quoting_engine (incl. seda_oracle), Hermes/OpenClaw Railway deploy templates, and QE tests to archive/. Hedge uses strategies/cfi_funding.py (HL fundingHistory only) — no SEDA dependency. Co-authored-by: Cursor --- README.md | 10 +++-- archive/README.md | 22 +++++++++++ .../deploy}/hermes-railway/Dockerfile | 0 .../deploy}/hermes-railway/package.json | 0 .../deploy}/hermes-railway/railway.toml | 4 +- .../deploy}/hermes-railway/src/bootstrap.mjs | 0 .../deploy}/hermes-railway/src/gateway.js | 0 .../deploy}/hermes-railway/src/onboard.js | 0 .../deploy}/hermes-railway/src/server.js | 0 .../deploy}/hermes-railway/src/status.js | 0 .../hermes-railway/workspace/AGENTS.md | 0 .../hermes-railway/workspace/BOOTSTRAP.md | 0 .../deploy}/hermes-railway/workspace/SOUL.md | 0 .../deploy}/hermes-railway/workspace/TOOLS.md | 0 .../deploy}/openclaw-railway/.env.example | 0 .../deploy}/openclaw-railway/Dockerfile | 0 .../deploy}/openclaw-railway/package.json | 0 .../deploy}/openclaw-railway/railway.toml | 4 +- .../openclaw-railway/src/bootstrap.mjs | 0 .../deploy}/openclaw-railway/src/gateway.js | 0 .../deploy}/openclaw-railway/src/onboard.js | 0 .../deploy}/openclaw-railway/src/server.js | 0 .../deploy}/openclaw-railway/src/status.js | 0 .../openclaw-railway/workspace/AGENTS.md | 0 .../openclaw-railway/workspace/BOOTSTRAP.md | 0 .../openclaw-railway/workspace/SOUL.md | 0 .../openclaw-railway/workspace/TOOLS.md | 0 .../quoting_engine}/__init__.py | 0 .../quoting_engine}/config.py | 0 .../configs/events/default_calendar.yaml | 0 .../quoting_engine}/configs/funding_rate.yaml | 0 .../quoting_engine}/configs/us3m.yaml | 0 .../quoting_engine}/configs/vxxn.yaml | 0 .../quoting_engine}/engine.py | 0 .../quoting_engine}/event_schedule.py | 0 .../quoting_engine}/fair_value.py | 0 .../quoting_engine}/feeds/__init__.py | 0 .../quoting_engine}/feeds/base.py | 0 .../quoting_engine}/feeds/funding_rate.py | 0 .../quoting_engine}/feeds/microprice.py | 0 .../quoting_engine}/feeds/oracle_monitor.py | 0 .../quoting_engine}/feeds/seda_oracle.py | 0 .../quoting_engine}/inventory.py | 0 .../quoting_engine}/ladder.py | 0 .../quoting_engine}/metrics.py | 0 .../quoting_engine}/spread.py | 0 .../quoting_engine}/toxicity.py | 0 .../quoting_engine}/vol_estimator.py | 0 cli/api/status_reader.py | 2 +- cli/commands/hedge.py | 16 ++++---- cli/hedge_display.py | 2 +- docs/hl_feature_audit.md | 2 +- pyproject.toml | 4 +- scripts/validate_agent_cli.py | 1 - strategies/cfi_funding.py | 38 +++++++++++++++++++ tests/_archive/README.md | 9 ++--- .../{ => _archive}/quoting_engine/conftest.py | 4 ++ .../quoting_engine/test_qe_config.py | 0 .../quoting_engine/test_qe_disagreement.py | 0 .../quoting_engine/test_qe_engine.py | 0 .../test_qe_event_schedule_calendar.py | 0 .../quoting_engine/test_qe_fair_value.py | 0 .../quoting_engine/test_qe_feeds_base.py | 0 .../test_qe_funding_boundary.py | 0 .../test_qe_funding_rate_feed.py | 0 .../quoting_engine/test_qe_fv_band.py | 0 .../quoting_engine/test_qe_inventory.py | 0 .../quoting_engine/test_qe_inventory_caps.py | 0 .../quoting_engine/test_qe_ladder.py | 0 .../quoting_engine/test_qe_liq_advanced.py | 0 .../quoting_engine/test_qe_metrics.py | 0 .../quoting_engine/test_qe_microprice.py | 0 .../quoting_engine/test_qe_oracle_monitor.py | 0 .../quoting_engine/test_qe_regime.py | 0 .../quoting_engine/test_qe_spread.py | 0 .../quoting_engine/test_qe_toxicity.py | 0 .../quoting_engine/test_qe_toxicity_tiered.py | 0 .../quoting_engine/test_qe_vol_estimator.py | 0 78 files changed, 91 insertions(+), 27 deletions(-) create mode 100644 archive/README.md rename {deploy => archive/deploy}/hermes-railway/Dockerfile (100%) rename {deploy => archive/deploy}/hermes-railway/package.json (100%) rename {deploy => archive/deploy}/hermes-railway/railway.toml (78%) rename {deploy => archive/deploy}/hermes-railway/src/bootstrap.mjs (100%) rename {deploy => archive/deploy}/hermes-railway/src/gateway.js (100%) rename {deploy => archive/deploy}/hermes-railway/src/onboard.js (100%) rename {deploy => archive/deploy}/hermes-railway/src/server.js (100%) rename {deploy => archive/deploy}/hermes-railway/src/status.js (100%) rename {deploy => archive/deploy}/hermes-railway/workspace/AGENTS.md (100%) rename {deploy => archive/deploy}/hermes-railway/workspace/BOOTSTRAP.md (100%) rename {deploy => archive/deploy}/hermes-railway/workspace/SOUL.md (100%) rename {deploy => archive/deploy}/hermes-railway/workspace/TOOLS.md (100%) rename {deploy => archive/deploy}/openclaw-railway/.env.example (100%) rename {deploy => archive/deploy}/openclaw-railway/Dockerfile (100%) rename {deploy => archive/deploy}/openclaw-railway/package.json (100%) rename {deploy => archive/deploy}/openclaw-railway/railway.toml (79%) rename {deploy => archive/deploy}/openclaw-railway/src/bootstrap.mjs (100%) rename {deploy => archive/deploy}/openclaw-railway/src/gateway.js (100%) rename {deploy => archive/deploy}/openclaw-railway/src/onboard.js (100%) rename {deploy => archive/deploy}/openclaw-railway/src/server.js (100%) rename {deploy => archive/deploy}/openclaw-railway/src/status.js (100%) rename {deploy => archive/deploy}/openclaw-railway/workspace/AGENTS.md (100%) rename {deploy => archive/deploy}/openclaw-railway/workspace/BOOTSTRAP.md (100%) rename {deploy => archive/deploy}/openclaw-railway/workspace/SOUL.md (100%) rename {deploy => archive/deploy}/openclaw-railway/workspace/TOOLS.md (100%) rename {quoting_engine => archive/quoting_engine}/__init__.py (100%) rename {quoting_engine => archive/quoting_engine}/config.py (100%) rename {quoting_engine => archive/quoting_engine}/configs/events/default_calendar.yaml (100%) rename {quoting_engine => archive/quoting_engine}/configs/funding_rate.yaml (100%) rename {quoting_engine => archive/quoting_engine}/configs/us3m.yaml (100%) rename {quoting_engine => archive/quoting_engine}/configs/vxxn.yaml (100%) rename {quoting_engine => archive/quoting_engine}/engine.py (100%) rename {quoting_engine => archive/quoting_engine}/event_schedule.py (100%) rename {quoting_engine => archive/quoting_engine}/fair_value.py (100%) rename {quoting_engine => archive/quoting_engine}/feeds/__init__.py (100%) rename {quoting_engine => archive/quoting_engine}/feeds/base.py (100%) rename {quoting_engine => archive/quoting_engine}/feeds/funding_rate.py (100%) rename {quoting_engine => archive/quoting_engine}/feeds/microprice.py (100%) rename {quoting_engine => archive/quoting_engine}/feeds/oracle_monitor.py (100%) rename {quoting_engine => archive/quoting_engine}/feeds/seda_oracle.py (100%) rename {quoting_engine => archive/quoting_engine}/inventory.py (100%) rename {quoting_engine => archive/quoting_engine}/ladder.py (100%) rename {quoting_engine => archive/quoting_engine}/metrics.py (100%) rename {quoting_engine => archive/quoting_engine}/spread.py (100%) rename {quoting_engine => archive/quoting_engine}/toxicity.py (100%) rename {quoting_engine => archive/quoting_engine}/vol_estimator.py (100%) create mode 100644 strategies/cfi_funding.py rename tests/{ => _archive}/quoting_engine/conftest.py (83%) rename tests/{ => _archive}/quoting_engine/test_qe_config.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_disagreement.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_engine.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_event_schedule_calendar.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_fair_value.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_feeds_base.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_funding_boundary.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_funding_rate_feed.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_fv_band.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_inventory.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_inventory_caps.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_ladder.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_liq_advanced.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_metrics.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_microprice.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_oracle_monitor.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_regime.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_spread.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_toxicity.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_toxicity_tiered.py (100%) rename tests/{ => _archive}/quoting_engine/test_qe_vol_estimator.py (100%) diff --git a/README.md b/README.md index 4851768..4cb91f9 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,9 @@ --- -Ship CFI v2 funding-rate hedging on [Hyperliquid](https://hyperliquid.xyz) perps and Paragon HIP-3 BTCSWP swap markets (`para:BTCSWP` mainnet, `osrs:BTCSWP` / `yex:BTCSWP` testnet). Primary surface: `hl hedge propose|execute|status|auto|backtest`. Also exposes Guard, Radar, Pulse, APEX, and REFLECT as optional operator tools. Works as a standalone CLI, a [Claude Code](https://docs.anthropic.com/en/docs/claude-code) skill, an [OpenClaw](https://agentskills.io) or [Hermes](https://github.com/NousResearch/hermes-agent) agent toolset, or a standalone MCP server. +Ship CFI v2 funding-rate hedging on [Hyperliquid](https://hyperliquid.xyz) perps and Paragon HIP-3 BTCSWP swap markets (`para:BTCSWP` mainnet, `osrs:BTCSWP` / `yex:BTCSWP` testnet). Primary surface: `hl hedge propose|execute|status|auto|backtest`. K2 inputs come from HL funding history only (no SEDA). Also exposes Guard, Radar, Pulse, APEX, and REFLECT as optional operator tools. Works as a standalone CLI, a [Claude Code](https://docs.anthropic.com/en/docs/claude-code) skill, an [OpenClaw](https://agentskills.io) or [Hermes](https://github.com/NousResearch/hermes-agent) agent toolset, or a standalone MCP server. + +Legacy market-making (`quoting_engine`), SEDA oracle, and Hermes/OpenClaw Railway deploy templates were archived 2026-07-02 under `archive/` — see `archive/README.md`. --- @@ -524,7 +526,7 @@ mcp_servers: # cwd: /path/to/agent-cli # if not launched from the repo root ``` -This is exactly what `deploy/hermes-railway` auto-generates on boot (alongside `platform_toolsets`), so a deployed Hermes agent is wired out of the box — no OpenClaw-specific assumptions anywhere in the MCP server. +This is exactly what the legacy `archive/deploy/hermes-railway` template auto-generates on boot (alongside `platform_toolsets`), so a deployed Hermes agent is wired out of the box — no OpenClaw-specific assumptions anywhere in the MCP server. ### HTTP API & SSE @@ -565,8 +567,8 @@ self-host templates. They are not part of the new hosted MCP subscription architecture because they provision user-facing conversational/autonomous agents. Do not expose them as the paid Nunchi product path. -For legacy experiments, use `deploy/openclaw-railway/railway.toml` or -`deploy/hermes-railway/railway.toml` as the config-as-code path and keep the +For legacy experiments, use `archive/deploy/openclaw-railway/railway.toml` or +`archive/deploy/hermes-railway/railway.toml` as the config-as-code path and keep the Railway build root at the repo root. --- diff --git a/archive/README.md b/archive/README.md new file mode 100644 index 0000000..7cd75ff --- /dev/null +++ b/archive/README.md @@ -0,0 +1,22 @@ +# Archived legacy components + +**Date:** 2026-07-02 +**Reason:** Product focus narrowed to CFI v2 funding-rate hedge only (`hl hedge`, `cfi_hedge` strategy). Market-making stack, SEDA oracle, and legacy agent deploy templates are no longer maintained in the active tree. + +## What moved here + +| Path | Description | +|------|-------------| +| `archive/quoting_engine/` | Full market-making stack (fair value, spreads, ladders, toxicity, funding feeds). Includes `feeds/seda_oracle.py` (deprecated). | +| `archive/deploy/hermes-railway/` | Hermes agent gateway Railway template | +| `archive/deploy/openclaw-railway/` | OpenClaw agent gateway Railway template | + +## Active replacement + +Hedge K2 inputs now come from **Hyperliquid funding only** via `strategies/cfi_funding.py` — no SEDA oracle. + +## Running archived tests manually + +```bash +PYTHONPATH=archive:. pytest tests/_archive/quoting_engine/ -v +``` diff --git a/deploy/hermes-railway/Dockerfile b/archive/deploy/hermes-railway/Dockerfile similarity index 100% rename from deploy/hermes-railway/Dockerfile rename to archive/deploy/hermes-railway/Dockerfile diff --git a/deploy/hermes-railway/package.json b/archive/deploy/hermes-railway/package.json similarity index 100% rename from deploy/hermes-railway/package.json rename to archive/deploy/hermes-railway/package.json diff --git a/deploy/hermes-railway/railway.toml b/archive/deploy/hermes-railway/railway.toml similarity index 78% rename from deploy/hermes-railway/railway.toml rename to archive/deploy/hermes-railway/railway.toml index a1db35e..16afa37 100644 --- a/deploy/hermes-railway/railway.toml +++ b/archive/deploy/hermes-railway/railway.toml @@ -2,8 +2,8 @@ builder = "dockerfile" # Build context is the repo root so the Dockerfile can COPY the agent-cli source. # Set this service's Root Directory to the repo root (/) in Railway, not -# deploy/hermes-railway, otherwise `COPY . .` cannot reach the CLI package. -dockerfilePath = "deploy/hermes-railway/Dockerfile" +# archive/deploy/hermes-railway, otherwise `COPY . .` cannot reach the CLI package. +dockerfilePath = "archive/deploy/hermes-railway/Dockerfile" [deploy] healthcheckPath = "/health" diff --git a/deploy/hermes-railway/src/bootstrap.mjs b/archive/deploy/hermes-railway/src/bootstrap.mjs similarity index 100% rename from deploy/hermes-railway/src/bootstrap.mjs rename to archive/deploy/hermes-railway/src/bootstrap.mjs diff --git a/deploy/hermes-railway/src/gateway.js b/archive/deploy/hermes-railway/src/gateway.js similarity index 100% rename from deploy/hermes-railway/src/gateway.js rename to archive/deploy/hermes-railway/src/gateway.js diff --git a/deploy/hermes-railway/src/onboard.js b/archive/deploy/hermes-railway/src/onboard.js similarity index 100% rename from deploy/hermes-railway/src/onboard.js rename to archive/deploy/hermes-railway/src/onboard.js diff --git a/deploy/hermes-railway/src/server.js b/archive/deploy/hermes-railway/src/server.js similarity index 100% rename from deploy/hermes-railway/src/server.js rename to archive/deploy/hermes-railway/src/server.js diff --git a/deploy/hermes-railway/src/status.js b/archive/deploy/hermes-railway/src/status.js similarity index 100% rename from deploy/hermes-railway/src/status.js rename to archive/deploy/hermes-railway/src/status.js diff --git a/deploy/hermes-railway/workspace/AGENTS.md b/archive/deploy/hermes-railway/workspace/AGENTS.md similarity index 100% rename from deploy/hermes-railway/workspace/AGENTS.md rename to archive/deploy/hermes-railway/workspace/AGENTS.md diff --git a/deploy/hermes-railway/workspace/BOOTSTRAP.md b/archive/deploy/hermes-railway/workspace/BOOTSTRAP.md similarity index 100% rename from deploy/hermes-railway/workspace/BOOTSTRAP.md rename to archive/deploy/hermes-railway/workspace/BOOTSTRAP.md diff --git a/deploy/hermes-railway/workspace/SOUL.md b/archive/deploy/hermes-railway/workspace/SOUL.md similarity index 100% rename from deploy/hermes-railway/workspace/SOUL.md rename to archive/deploy/hermes-railway/workspace/SOUL.md diff --git a/deploy/hermes-railway/workspace/TOOLS.md b/archive/deploy/hermes-railway/workspace/TOOLS.md similarity index 100% rename from deploy/hermes-railway/workspace/TOOLS.md rename to archive/deploy/hermes-railway/workspace/TOOLS.md diff --git a/deploy/openclaw-railway/.env.example b/archive/deploy/openclaw-railway/.env.example similarity index 100% rename from deploy/openclaw-railway/.env.example rename to archive/deploy/openclaw-railway/.env.example diff --git a/deploy/openclaw-railway/Dockerfile b/archive/deploy/openclaw-railway/Dockerfile similarity index 100% rename from deploy/openclaw-railway/Dockerfile rename to archive/deploy/openclaw-railway/Dockerfile diff --git a/deploy/openclaw-railway/package.json b/archive/deploy/openclaw-railway/package.json similarity index 100% rename from deploy/openclaw-railway/package.json rename to archive/deploy/openclaw-railway/package.json diff --git a/deploy/openclaw-railway/railway.toml b/archive/deploy/openclaw-railway/railway.toml similarity index 79% rename from deploy/openclaw-railway/railway.toml rename to archive/deploy/openclaw-railway/railway.toml index bd0e575..50d7479 100644 --- a/deploy/openclaw-railway/railway.toml +++ b/archive/deploy/openclaw-railway/railway.toml @@ -2,8 +2,8 @@ builder = "dockerfile" # Build context is the repo root so the Dockerfile can COPY the agent-cli source. # Set this service's Root Directory to the repo root (/) in Railway, not -# deploy/openclaw-railway, otherwise `COPY . .` cannot reach the CLI package. -dockerfilePath = "deploy/openclaw-railway/Dockerfile" +# archive/deploy/openclaw-railway, otherwise `COPY . .` cannot reach the CLI package. +dockerfilePath = "archive/deploy/openclaw-railway/Dockerfile" [deploy] healthcheckPath = "/health" diff --git a/deploy/openclaw-railway/src/bootstrap.mjs b/archive/deploy/openclaw-railway/src/bootstrap.mjs similarity index 100% rename from deploy/openclaw-railway/src/bootstrap.mjs rename to archive/deploy/openclaw-railway/src/bootstrap.mjs diff --git a/deploy/openclaw-railway/src/gateway.js b/archive/deploy/openclaw-railway/src/gateway.js similarity index 100% rename from deploy/openclaw-railway/src/gateway.js rename to archive/deploy/openclaw-railway/src/gateway.js diff --git a/deploy/openclaw-railway/src/onboard.js b/archive/deploy/openclaw-railway/src/onboard.js similarity index 100% rename from deploy/openclaw-railway/src/onboard.js rename to archive/deploy/openclaw-railway/src/onboard.js diff --git a/deploy/openclaw-railway/src/server.js b/archive/deploy/openclaw-railway/src/server.js similarity index 100% rename from deploy/openclaw-railway/src/server.js rename to archive/deploy/openclaw-railway/src/server.js diff --git a/deploy/openclaw-railway/src/status.js b/archive/deploy/openclaw-railway/src/status.js similarity index 100% rename from deploy/openclaw-railway/src/status.js rename to archive/deploy/openclaw-railway/src/status.js diff --git a/deploy/openclaw-railway/workspace/AGENTS.md b/archive/deploy/openclaw-railway/workspace/AGENTS.md similarity index 100% rename from deploy/openclaw-railway/workspace/AGENTS.md rename to archive/deploy/openclaw-railway/workspace/AGENTS.md diff --git a/deploy/openclaw-railway/workspace/BOOTSTRAP.md b/archive/deploy/openclaw-railway/workspace/BOOTSTRAP.md similarity index 100% rename from deploy/openclaw-railway/workspace/BOOTSTRAP.md rename to archive/deploy/openclaw-railway/workspace/BOOTSTRAP.md diff --git a/deploy/openclaw-railway/workspace/SOUL.md b/archive/deploy/openclaw-railway/workspace/SOUL.md similarity index 100% rename from deploy/openclaw-railway/workspace/SOUL.md rename to archive/deploy/openclaw-railway/workspace/SOUL.md diff --git a/deploy/openclaw-railway/workspace/TOOLS.md b/archive/deploy/openclaw-railway/workspace/TOOLS.md similarity index 100% rename from deploy/openclaw-railway/workspace/TOOLS.md rename to archive/deploy/openclaw-railway/workspace/TOOLS.md diff --git a/quoting_engine/__init__.py b/archive/quoting_engine/__init__.py similarity index 100% rename from quoting_engine/__init__.py rename to archive/quoting_engine/__init__.py diff --git a/quoting_engine/config.py b/archive/quoting_engine/config.py similarity index 100% rename from quoting_engine/config.py rename to archive/quoting_engine/config.py diff --git a/quoting_engine/configs/events/default_calendar.yaml b/archive/quoting_engine/configs/events/default_calendar.yaml similarity index 100% rename from quoting_engine/configs/events/default_calendar.yaml rename to archive/quoting_engine/configs/events/default_calendar.yaml diff --git a/quoting_engine/configs/funding_rate.yaml b/archive/quoting_engine/configs/funding_rate.yaml similarity index 100% rename from quoting_engine/configs/funding_rate.yaml rename to archive/quoting_engine/configs/funding_rate.yaml diff --git a/quoting_engine/configs/us3m.yaml b/archive/quoting_engine/configs/us3m.yaml similarity index 100% rename from quoting_engine/configs/us3m.yaml rename to archive/quoting_engine/configs/us3m.yaml diff --git a/quoting_engine/configs/vxxn.yaml b/archive/quoting_engine/configs/vxxn.yaml similarity index 100% rename from quoting_engine/configs/vxxn.yaml rename to archive/quoting_engine/configs/vxxn.yaml diff --git a/quoting_engine/engine.py b/archive/quoting_engine/engine.py similarity index 100% rename from quoting_engine/engine.py rename to archive/quoting_engine/engine.py diff --git a/quoting_engine/event_schedule.py b/archive/quoting_engine/event_schedule.py similarity index 100% rename from quoting_engine/event_schedule.py rename to archive/quoting_engine/event_schedule.py diff --git a/quoting_engine/fair_value.py b/archive/quoting_engine/fair_value.py similarity index 100% rename from quoting_engine/fair_value.py rename to archive/quoting_engine/fair_value.py diff --git a/quoting_engine/feeds/__init__.py b/archive/quoting_engine/feeds/__init__.py similarity index 100% rename from quoting_engine/feeds/__init__.py rename to archive/quoting_engine/feeds/__init__.py diff --git a/quoting_engine/feeds/base.py b/archive/quoting_engine/feeds/base.py similarity index 100% rename from quoting_engine/feeds/base.py rename to archive/quoting_engine/feeds/base.py diff --git a/quoting_engine/feeds/funding_rate.py b/archive/quoting_engine/feeds/funding_rate.py similarity index 100% rename from quoting_engine/feeds/funding_rate.py rename to archive/quoting_engine/feeds/funding_rate.py diff --git a/quoting_engine/feeds/microprice.py b/archive/quoting_engine/feeds/microprice.py similarity index 100% rename from quoting_engine/feeds/microprice.py rename to archive/quoting_engine/feeds/microprice.py diff --git a/quoting_engine/feeds/oracle_monitor.py b/archive/quoting_engine/feeds/oracle_monitor.py similarity index 100% rename from quoting_engine/feeds/oracle_monitor.py rename to archive/quoting_engine/feeds/oracle_monitor.py diff --git a/quoting_engine/feeds/seda_oracle.py b/archive/quoting_engine/feeds/seda_oracle.py similarity index 100% rename from quoting_engine/feeds/seda_oracle.py rename to archive/quoting_engine/feeds/seda_oracle.py diff --git a/quoting_engine/inventory.py b/archive/quoting_engine/inventory.py similarity index 100% rename from quoting_engine/inventory.py rename to archive/quoting_engine/inventory.py diff --git a/quoting_engine/ladder.py b/archive/quoting_engine/ladder.py similarity index 100% rename from quoting_engine/ladder.py rename to archive/quoting_engine/ladder.py diff --git a/quoting_engine/metrics.py b/archive/quoting_engine/metrics.py similarity index 100% rename from quoting_engine/metrics.py rename to archive/quoting_engine/metrics.py diff --git a/quoting_engine/spread.py b/archive/quoting_engine/spread.py similarity index 100% rename from quoting_engine/spread.py rename to archive/quoting_engine/spread.py diff --git a/quoting_engine/toxicity.py b/archive/quoting_engine/toxicity.py similarity index 100% rename from quoting_engine/toxicity.py rename to archive/quoting_engine/toxicity.py diff --git a/quoting_engine/vol_estimator.py b/archive/quoting_engine/vol_estimator.py similarity index 100% rename from quoting_engine/vol_estimator.py rename to archive/quoting_engine/vol_estimator.py diff --git a/cli/api/status_reader.py b/cli/api/status_reader.py index 1cda7ce..fa017dc 100644 --- a/cli/api/status_reader.py +++ b/cli/api/status_reader.py @@ -2,7 +2,7 @@ Shared utility used by: - scripts/entrypoint.py (imported directly) -- deploy/openclaw-railway/src/server.js (via `python3 -m cli.api.status_reader`) +- archive/deploy/openclaw-railway/src/server.js (via `python3 -m cli.api.status_reader`) """ from __future__ import annotations diff --git a/cli/commands/hedge.py b/cli/commands/hedge.py index ffcfa63..9f83dd7 100644 --- a/cli/commands/hedge.py +++ b/cli/commands/hedge.py @@ -8,8 +8,8 @@ hl hedge backtest --coin BTC [--days N] wrap hedge_calculator.py hl hedge auto [--coins ...] [--dry-run] agent-controlled auto-open loop -All math + oracle access is delegated to `strategies/cfi_hedge.py` and -`quoting_engine/feeds/seda_oracle.py`. Signing + submission are delegated to +All math + funding inputs are delegated to `strategies/cfi_hedge.py` and +`strategies/cfi_funding.py` (HL funding rates only). Signing + submission are delegated to the Hyperliquid Python SDK via `DirectHLProxy.place_order()` — same code path as `hl trade`. No custom signing here. """ @@ -116,8 +116,8 @@ def _build_proposal( Returns (proposal, snapshot) or raises typer.Exit if no position open. """ from strategies.cfi_hedge import build_cfi_hedge_proposal, get_cfi_profile - from quoting_engine.feeds.seda_oracle import ( - fetch_btcswp_snapshot, + from strategies.cfi_funding import ( + fetch_cfi_funding_snapshot, fetch_hl_current_funding_hr, ) @@ -145,7 +145,7 @@ def _build_proposal( raise typer.Exit(1) position = _position_to_summary(raw_pos, coin_override=coin) - snapshot = fetch_btcswp_snapshot(profile) + snapshot = fetch_cfi_funding_snapshot(profile) current_funding = fetch_hl_current_funding_hr(coin) if current_funding is None: # Fall back to oracle's r_ema if HL didn't answer. @@ -303,8 +303,8 @@ def status_cmd( get_cfi_profile, ) from cli.hedge_display import hedge_status_block - from quoting_engine.feeds.seda_oracle import ( - fetch_btcswp_snapshot, + from strategies.cfi_funding import ( + fetch_cfi_funding_snapshot, fetch_hl_current_funding_hr, ) @@ -327,7 +327,7 @@ def _refresh(): live.append({"job": h, "snapshot": None, "drift_apy": 0.0, "savings_usd": h.get("cumulative_savings_usd", 0.0)}) continue try: - snap = fetch_btcswp_snapshot(profile) + snap = fetch_cfi_funding_snapshot(profile) except Exception: snap = None current_hr = fetch_hl_current_funding_hr(h.get("coin", "BTC")) diff --git a/cli/hedge_display.py b/cli/hedge_display.py index 8db8e33..5e9e267 100644 --- a/cli/hedge_display.py +++ b/cli/hedge_display.py @@ -101,7 +101,7 @@ def hedge_proposal_block(proposal, snapshot, *, mainnet: bool = False) -> str: else "↓" if proposal.wire_drift_per_hour_usd < -0.5 else "↔" ) - src = "SEDA live" if snapshot.source == "seda" else f"{YELLOW}replay (HL fundingHistory){RESET}" + src = f"{GREEN}HL fundingHistory{RESET}" network = "mainnet" if mainnet else "testnet" lines = [ diff --git a/docs/hl_feature_audit.md b/docs/hl_feature_audit.md index 44f3bab..f0f1692 100644 --- a/docs/hl_feature_audit.md +++ b/docs/hl_feature_audit.md @@ -172,7 +172,7 @@ These read `snapshot.funding_rate` in their trading logic but don't import any H **Tightly coupled (quoting engine + HyperliquidFundingRate, 4 strategies):** `engine_mm`, `funding_arb`, `regime_mm`, `liquidation_mm` -These directly import `HyperliquidFundingRate` from the quoting engine and require `_engine_base.py` (path hack to `~/Tee-work-/quoting_engine`). Porting requires refactoring the quoting engine's funding rate interface. +These directly import `HyperliquidFundingRate` from the quoting engine (now under `archive/quoting_engine/`) and require `_engine_base.py` (path hack to `~/Tee-work-/quoting_engine`). Porting requires refactoring the quoting engine's funding rate interface. ### Highest-Priority Abstractions for Multi-Exchange Support diff --git a/pyproject.toml b/pyproject.toml index f567620..97a45fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ Repository = "https://github.com/Nunchi-trade/agent-cli" [tool.pytest.ini_options] pythonpath = ["."] -norecursedirs = ["_archive", ".*", "build", "dist"] +norecursedirs = ["_archive", "archive", ".*", "build", "dist"] [tool.mypy] python_version = "3.10" @@ -56,4 +56,4 @@ disallow_untyped_defs = false ignore_missing_imports = true [tool.setuptools.packages.find] -include = ["cli*", "strategies*", "sdk*", "common*", "parent*", "modules*", "skills*", "quoting_engine*"] +include = ["cli*", "strategies*", "sdk*", "common*", "parent*", "modules*", "skills*"] diff --git a/scripts/validate_agent_cli.py b/scripts/validate_agent_cli.py index b08ca45..72821bc 100644 --- a/scripts/validate_agent_cli.py +++ b/scripts/validate_agent_cli.py @@ -130,7 +130,6 @@ def base_cases(python: str, quick_pytest: bool) -> list[TestCase]: "strategies", "modules", "skills", - "quoting_engine", "scripts", ], python, diff --git a/strategies/cfi_funding.py b/strategies/cfi_funding.py new file mode 100644 index 0000000..ffd5f48 --- /dev/null +++ b/strategies/cfi_funding.py @@ -0,0 +1,38 @@ +"""CFI v2 funding inputs from Hyperliquid only.""" +from __future__ import annotations +import logging, os, time +from dataclasses import dataclass +from typing import Literal, Optional +import requests +from strategies.cfi_hedge import BTCSWP_PROFILE, CFIAssetProfile, FundingRateSample, compute_k2_from_history +log = logging.getLogger(__name__) +HL_INFO_URL_DEFAULT = "https://api.hyperliquid.xyz/info" +def _hl_info_url() -> str: return os.environ.get("HL_INFO_URL", HL_INFO_URL_DEFAULT) +@dataclass(frozen=True) +class CfiFundingSnapshot: + source: Literal["hl"]; timestamp_iso: str; timestamp_ms: int; oracle_px: Optional[float] + k_fixed_hr: float; r_ema_hr: float; cfi: Optional[float] +def _fetch_funding_history(coin, lookback_hours, *, timeout_s=10.0): + end_time = int(time.time()*1000); start_time = end_time - lookback_hours*3600000 + resp = requests.post(_hl_info_url(), json={"type":"fundingHistory","coin":coin,"startTime":start_time,"endTime":end_time}, timeout=timeout_s) + resp.raise_for_status(); return resp.json() or [] +def fetch_hl_current_funding_hr(coin, *, timeout_s=10.0): + try: + resp = requests.post(_hl_info_url(), json={"type":"metaAndAssetCtxs"}, timeout=timeout_s); resp.raise_for_status(); data = resp.json() + for meta, ctx in zip(data[0]["universe"], data[1]): + if meta.get("name")==coin: return float(ctx.get("funding",0)) + return None + except Exception as e: + log.warning("cfi_funding: fetch_hl_current_funding_hr(%s) failed: %s", coin, e); return None +def replay_k2_from_hl(profile=BTCSWP_PROFILE, *, lookback_hours=168, timeout_s=10.0): + hist = _fetch_funding_history(profile.hl_coin, lookback_hours, timeout_s=timeout_s) + samples = [FundingRateSample(funding_rate=float(e["fundingRate"]), time=int(e["time"])) for e in hist] + k = compute_k2_from_history(samples, profile) + if samples: + r_ema = samples[0].funding_rate + for s in samples: r_ema = (1-profile.k2_beta)*r_ema + profile.k2_beta*s.funding_rate + else: r_ema = profile.fixed_leg_initial + now_ms = int(time.time()*1000) + return CfiFundingSnapshot(source="hl", timestamp_iso=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(now_ms/1000)), timestamp_ms=now_ms, oracle_px=None, k_fixed_hr=k, r_ema_hr=r_ema, cfi=None) +def fetch_cfi_funding_snapshot(profile=BTCSWP_PROFILE, *, timeout_s=10.0): return replay_k2_from_hl(profile, timeout_s=timeout_s) +BTCSWPOracleSnapshot = CfiFundingSnapshot; fetch_btcswp_snapshot = fetch_cfi_funding_snapshot diff --git a/tests/_archive/README.md b/tests/_archive/README.md index 91a9979..a2eea21 100644 --- a/tests/_archive/README.md +++ b/tests/_archive/README.md @@ -1,6 +1,5 @@ -# Archived strategy tests +# Archived strategy and quoting-engine tests -Tests for legacy strategies archived on **2026-07-02**. Not collected by default pytest runs -(`norecursedirs` excludes `_archive`). - -Run manually if needed: `pytest tests/_archive/ -v` +```bash +PYTHONPATH=archive:. pytest tests/_archive/quoting_engine/ -v +``` diff --git a/tests/quoting_engine/conftest.py b/tests/_archive/quoting_engine/conftest.py similarity index 83% rename from tests/quoting_engine/conftest.py rename to tests/_archive/quoting_engine/conftest.py index 4339da0..c0deb60 100644 --- a/tests/quoting_engine/conftest.py +++ b/tests/_archive/quoting_engine/conftest.py @@ -1,5 +1,9 @@ """Shared fixtures for quoting engine tests.""" +import sys +from pathlib import Path import pytest +_archive_root = Path(__file__).resolve().parents[2] / "archive" +if str(_archive_root) not in sys.path: sys.path.insert(0, str(_archive_root)) from quoting_engine.config import ( MarketConfig, FairValueWeights, SpreadParams, LadderParams, SkewParams, diff --git a/tests/quoting_engine/test_qe_config.py b/tests/_archive/quoting_engine/test_qe_config.py similarity index 100% rename from tests/quoting_engine/test_qe_config.py rename to tests/_archive/quoting_engine/test_qe_config.py diff --git a/tests/quoting_engine/test_qe_disagreement.py b/tests/_archive/quoting_engine/test_qe_disagreement.py similarity index 100% rename from tests/quoting_engine/test_qe_disagreement.py rename to tests/_archive/quoting_engine/test_qe_disagreement.py diff --git a/tests/quoting_engine/test_qe_engine.py b/tests/_archive/quoting_engine/test_qe_engine.py similarity index 100% rename from tests/quoting_engine/test_qe_engine.py rename to tests/_archive/quoting_engine/test_qe_engine.py diff --git a/tests/quoting_engine/test_qe_event_schedule_calendar.py b/tests/_archive/quoting_engine/test_qe_event_schedule_calendar.py similarity index 100% rename from tests/quoting_engine/test_qe_event_schedule_calendar.py rename to tests/_archive/quoting_engine/test_qe_event_schedule_calendar.py diff --git a/tests/quoting_engine/test_qe_fair_value.py b/tests/_archive/quoting_engine/test_qe_fair_value.py similarity index 100% rename from tests/quoting_engine/test_qe_fair_value.py rename to tests/_archive/quoting_engine/test_qe_fair_value.py diff --git a/tests/quoting_engine/test_qe_feeds_base.py b/tests/_archive/quoting_engine/test_qe_feeds_base.py similarity index 100% rename from tests/quoting_engine/test_qe_feeds_base.py rename to tests/_archive/quoting_engine/test_qe_feeds_base.py diff --git a/tests/quoting_engine/test_qe_funding_boundary.py b/tests/_archive/quoting_engine/test_qe_funding_boundary.py similarity index 100% rename from tests/quoting_engine/test_qe_funding_boundary.py rename to tests/_archive/quoting_engine/test_qe_funding_boundary.py diff --git a/tests/quoting_engine/test_qe_funding_rate_feed.py b/tests/_archive/quoting_engine/test_qe_funding_rate_feed.py similarity index 100% rename from tests/quoting_engine/test_qe_funding_rate_feed.py rename to tests/_archive/quoting_engine/test_qe_funding_rate_feed.py diff --git a/tests/quoting_engine/test_qe_fv_band.py b/tests/_archive/quoting_engine/test_qe_fv_band.py similarity index 100% rename from tests/quoting_engine/test_qe_fv_band.py rename to tests/_archive/quoting_engine/test_qe_fv_band.py diff --git a/tests/quoting_engine/test_qe_inventory.py b/tests/_archive/quoting_engine/test_qe_inventory.py similarity index 100% rename from tests/quoting_engine/test_qe_inventory.py rename to tests/_archive/quoting_engine/test_qe_inventory.py diff --git a/tests/quoting_engine/test_qe_inventory_caps.py b/tests/_archive/quoting_engine/test_qe_inventory_caps.py similarity index 100% rename from tests/quoting_engine/test_qe_inventory_caps.py rename to tests/_archive/quoting_engine/test_qe_inventory_caps.py diff --git a/tests/quoting_engine/test_qe_ladder.py b/tests/_archive/quoting_engine/test_qe_ladder.py similarity index 100% rename from tests/quoting_engine/test_qe_ladder.py rename to tests/_archive/quoting_engine/test_qe_ladder.py diff --git a/tests/quoting_engine/test_qe_liq_advanced.py b/tests/_archive/quoting_engine/test_qe_liq_advanced.py similarity index 100% rename from tests/quoting_engine/test_qe_liq_advanced.py rename to tests/_archive/quoting_engine/test_qe_liq_advanced.py diff --git a/tests/quoting_engine/test_qe_metrics.py b/tests/_archive/quoting_engine/test_qe_metrics.py similarity index 100% rename from tests/quoting_engine/test_qe_metrics.py rename to tests/_archive/quoting_engine/test_qe_metrics.py diff --git a/tests/quoting_engine/test_qe_microprice.py b/tests/_archive/quoting_engine/test_qe_microprice.py similarity index 100% rename from tests/quoting_engine/test_qe_microprice.py rename to tests/_archive/quoting_engine/test_qe_microprice.py diff --git a/tests/quoting_engine/test_qe_oracle_monitor.py b/tests/_archive/quoting_engine/test_qe_oracle_monitor.py similarity index 100% rename from tests/quoting_engine/test_qe_oracle_monitor.py rename to tests/_archive/quoting_engine/test_qe_oracle_monitor.py diff --git a/tests/quoting_engine/test_qe_regime.py b/tests/_archive/quoting_engine/test_qe_regime.py similarity index 100% rename from tests/quoting_engine/test_qe_regime.py rename to tests/_archive/quoting_engine/test_qe_regime.py diff --git a/tests/quoting_engine/test_qe_spread.py b/tests/_archive/quoting_engine/test_qe_spread.py similarity index 100% rename from tests/quoting_engine/test_qe_spread.py rename to tests/_archive/quoting_engine/test_qe_spread.py diff --git a/tests/quoting_engine/test_qe_toxicity.py b/tests/_archive/quoting_engine/test_qe_toxicity.py similarity index 100% rename from tests/quoting_engine/test_qe_toxicity.py rename to tests/_archive/quoting_engine/test_qe_toxicity.py diff --git a/tests/quoting_engine/test_qe_toxicity_tiered.py b/tests/_archive/quoting_engine/test_qe_toxicity_tiered.py similarity index 100% rename from tests/quoting_engine/test_qe_toxicity_tiered.py rename to tests/_archive/quoting_engine/test_qe_toxicity_tiered.py diff --git a/tests/quoting_engine/test_qe_vol_estimator.py b/tests/_archive/quoting_engine/test_qe_vol_estimator.py similarity index 100% rename from tests/quoting_engine/test_qe_vol_estimator.py rename to tests/_archive/quoting_engine/test_qe_vol_estimator.py From 328a54a19927fe0e085d288d98a09cf8d999af66 Mon Sep 17 00:00:00 2001 From: JaeLeex Date: Wed, 8 Jul 2026 08:00:11 -0400 Subject: [PATCH 2/2] chore: remove all Hermes/OpenClaw deploy references Delete archived hermes-railway and openclaw-railway deploy templates entirely and strip remaining references from docs, telemetry, skills, and README. Product surface is CLI + MCP only. Co-authored-by: Cursor --- README.md | 31 +- archive/README.md | 4 +- archive/deploy/hermes-railway/Dockerfile | 92 ------ archive/deploy/hermes-railway/package.json | 14 - archive/deploy/hermes-railway/railway.toml | 23 -- .../deploy/hermes-railway/src/bootstrap.mjs | 139 -------- archive/deploy/hermes-railway/src/gateway.js | 114 ------- archive/deploy/hermes-railway/src/onboard.js | 133 -------- archive/deploy/hermes-railway/src/server.js | 273 ---------------- archive/deploy/hermes-railway/src/status.js | 63 ---- .../deploy/hermes-railway/workspace/AGENTS.md | 47 --- .../hermes-railway/workspace/BOOTSTRAP.md | 39 --- .../deploy/hermes-railway/workspace/SOUL.md | 17 - .../deploy/hermes-railway/workspace/TOOLS.md | 61 ---- archive/deploy/openclaw-railway/.env.example | 18 -- archive/deploy/openclaw-railway/Dockerfile | 87 ----- archive/deploy/openclaw-railway/package.json | 13 - archive/deploy/openclaw-railway/railway.toml | 23 -- .../deploy/openclaw-railway/src/bootstrap.mjs | 286 ----------------- .../deploy/openclaw-railway/src/gateway.js | 100 ------ .../deploy/openclaw-railway/src/onboard.js | 154 --------- archive/deploy/openclaw-railway/src/server.js | 297 ------------------ archive/deploy/openclaw-railway/src/status.js | 62 ---- .../openclaw-railway/workspace/AGENTS.md | 47 --- .../openclaw-railway/workspace/BOOTSTRAP.md | 39 --- .../deploy/openclaw-railway/workspace/SOUL.md | 17 - .../openclaw-railway/workspace/TOOLS.md | 59 ---- cli/api/status_reader.py | 1 - cli/hedge_display.py | 2 +- cli/skill.md | 14 +- cli/telemetry.py | 4 - docs/RUNBOOK.md | 3 +- docs/api-reference.md | 12 +- tests/test_telemetry.py | 4 - 34 files changed, 22 insertions(+), 2270 deletions(-) delete mode 100644 archive/deploy/hermes-railway/Dockerfile delete mode 100644 archive/deploy/hermes-railway/package.json delete mode 100644 archive/deploy/hermes-railway/railway.toml delete mode 100644 archive/deploy/hermes-railway/src/bootstrap.mjs delete mode 100644 archive/deploy/hermes-railway/src/gateway.js delete mode 100644 archive/deploy/hermes-railway/src/onboard.js delete mode 100644 archive/deploy/hermes-railway/src/server.js delete mode 100644 archive/deploy/hermes-railway/src/status.js delete mode 100644 archive/deploy/hermes-railway/workspace/AGENTS.md delete mode 100644 archive/deploy/hermes-railway/workspace/BOOTSTRAP.md delete mode 100644 archive/deploy/hermes-railway/workspace/SOUL.md delete mode 100644 archive/deploy/hermes-railway/workspace/TOOLS.md delete mode 100644 archive/deploy/openclaw-railway/.env.example delete mode 100644 archive/deploy/openclaw-railway/Dockerfile delete mode 100644 archive/deploy/openclaw-railway/package.json delete mode 100644 archive/deploy/openclaw-railway/railway.toml delete mode 100644 archive/deploy/openclaw-railway/src/bootstrap.mjs delete mode 100644 archive/deploy/openclaw-railway/src/gateway.js delete mode 100644 archive/deploy/openclaw-railway/src/onboard.js delete mode 100644 archive/deploy/openclaw-railway/src/server.js delete mode 100644 archive/deploy/openclaw-railway/src/status.js delete mode 100644 archive/deploy/openclaw-railway/workspace/AGENTS.md delete mode 100644 archive/deploy/openclaw-railway/workspace/BOOTSTRAP.md delete mode 100644 archive/deploy/openclaw-railway/workspace/SOUL.md delete mode 100644 archive/deploy/openclaw-railway/workspace/TOOLS.md diff --git a/README.md b/README.md index 4cb91f9..2252cbb 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ --- -Ship CFI v2 funding-rate hedging on [Hyperliquid](https://hyperliquid.xyz) perps and Paragon HIP-3 BTCSWP swap markets (`para:BTCSWP` mainnet, `osrs:BTCSWP` / `yex:BTCSWP` testnet). Primary surface: `hl hedge propose|execute|status|auto|backtest`. K2 inputs come from HL funding history only (no SEDA). Also exposes Guard, Radar, Pulse, APEX, and REFLECT as optional operator tools. Works as a standalone CLI, a [Claude Code](https://docs.anthropic.com/en/docs/claude-code) skill, an [OpenClaw](https://agentskills.io) or [Hermes](https://github.com/NousResearch/hermes-agent) agent toolset, or a standalone MCP server. +Ship CFI v2 funding-rate hedging on [Hyperliquid](https://hyperliquid.xyz) perps and Paragon HIP-3 BTCSWP swap markets (`para:BTCSWP` mainnet, `osrs:BTCSWP` / `yex:BTCSWP` testnet). Primary surface: `hl hedge propose|execute|status|auto|backtest`. K2 inputs come from HL funding history only (no SEDA). Also exposes Guard, Radar, Pulse, APEX, and REFLECT as optional operator tools. Works as a standalone CLI, a [Claude Code](https://docs.anthropic.com/en/docs/claude-code) skill, or a standalone MCP server. -Legacy market-making (`quoting_engine`), SEDA oracle, and Hermes/OpenClaw Railway deploy templates were archived 2026-07-02 under `archive/` — see `archive/README.md`. +Legacy market-making (`quoting_engine`) and SEDA oracle were archived 2026-07-02 under `archive/` — see `archive/README.md`. --- @@ -195,12 +195,6 @@ https://raw.githubusercontent.com/Nunchi-trade/agent-cli/main/skills/guard/SKILL https://raw.githubusercontent.com/Nunchi-trade/agent-cli/main/skills/reflect/SKILL.md ``` -### Install a skill (OpenClaw / ClawHub) - -```bash -clawhub install nunchi-trade/yex-trader -``` - ### Install a skill (Claude Code) ```bash @@ -515,10 +509,10 @@ Fast tools (strategies, builder, wallet, setup, memory, journal, judge) call Pyt ### Use from any agent harness -`hl mcp serve` is **harness-neutral** — [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [OpenClaw](https://agentskills.io), and [Hermes](https://github.com/NousResearch/hermes-agent) consume the exact same tools. To point a **Hermes** agent (or any MCP host) at this repo, register the server in its config: +`hl mcp serve` is **harness-neutral** — [Claude Code](https://docs.anthropic.com/en/docs/claude-code), Cursor, Codex, and any MCP-compatible client consume the exact same tools. Register the server in your MCP host config: ```yaml -# Hermes ~/.hermes/config.yaml (or any MCP harness) +# Any MCP harness (e.g. ~/.cursor/mcp.json, Claude Code settings) mcp_servers: nunchi_trading: command: python3 @@ -526,8 +520,6 @@ mcp_servers: # cwd: /path/to/agent-cli # if not launched from the repo root ``` -This is exactly what the legacy `archive/deploy/hermes-railway` template auto-generates on boot (alongside `platform_toolsets`), so a deployed Hermes agent is wired out of the box — no OpenClaw-specific assumptions anywhere in the MCP server. - ### HTTP API & SSE Every deployed agent also exposes an HTTP REST API and SSE real-time feed for dashboards, monitoring, and external integrations. A separate leaderboard microservice tracks agent PnL rankings. @@ -539,7 +531,7 @@ Every deployed agent also exposes an HTTP REST API and SSE real-time feed for da ## Deploy on Railway The subscription product uses Railway as a **shared MCP tools runtime**, not as -a per-user Hermes/OpenClaw/autonomous-agent host. The top-level Railway template +a per-user autonomous-agent host. The top-level Railway template defaults to `RUN_MODE=mcp`; Nunchi operates this runner pool behind `mcp-gateway`, while users run their own Cursor, Claude, Codex, or local `agent-cli` clients. @@ -560,17 +552,6 @@ defaults to `RUN_MODE=mcp`; Nunchi operates this runner pool behind - **mcp** (default) — SSE MCP server for the shared Railway tools runtime. - **apex** / **strategy** — direct autonomous loops for local/self-hosted operators, not the Nunchi subscription product path. -### Legacy Agent Templates - -The OpenClaw and Hermes Railway templates are retained as legacy/reference -self-host templates. They are not part of the new hosted MCP subscription -architecture because they provision user-facing conversational/autonomous -agents. Do not expose them as the paid Nunchi product path. - -For legacy experiments, use `archive/deploy/openclaw-railway/railway.toml` or -`archive/deploy/hermes-railway/railway.toml` as the config-as-code path and keep the -Railway build root at the repo root. - --- ## Architecture @@ -671,7 +652,7 @@ pytest tests/ -v # 1300+ tests ## Attribution -Inspired by openclaw, senpi, and claude code. +Inspired by senpi and claude code. --- ## Links diff --git a/archive/README.md b/archive/README.md index 7cd75ff..6cfada1 100644 --- a/archive/README.md +++ b/archive/README.md @@ -1,15 +1,13 @@ # Archived legacy components **Date:** 2026-07-02 -**Reason:** Product focus narrowed to CFI v2 funding-rate hedge only (`hl hedge`, `cfi_hedge` strategy). Market-making stack, SEDA oracle, and legacy agent deploy templates are no longer maintained in the active tree. +**Reason:** Product focus narrowed to CFI v2 funding-rate hedge only (`hl hedge`, `cfi_hedge` strategy). Market-making stack and SEDA oracle are no longer maintained in the active tree. ## What moved here | Path | Description | |------|-------------| | `archive/quoting_engine/` | Full market-making stack (fair value, spreads, ladders, toxicity, funding feeds). Includes `feeds/seda_oracle.py` (deprecated). | -| `archive/deploy/hermes-railway/` | Hermes agent gateway Railway template | -| `archive/deploy/openclaw-railway/` | OpenClaw agent gateway Railway template | ## Active replacement diff --git a/archive/deploy/hermes-railway/Dockerfile b/archive/deploy/hermes-railway/Dockerfile deleted file mode 100644 index c00f246..0000000 --- a/archive/deploy/hermes-railway/Dockerfile +++ /dev/null @@ -1,92 +0,0 @@ -# Hermes Agent + agent-cli — Railway one-click image -# -# Pinned upstream: github.com/NousResearch/hermes-agent @ ${HERMES_GIT_REF} -# Pinned interface: hermes dashboard --host 0.0.0.0 --port --insecure --no-open -# -# Layout mirrors deploy/openclaw-railway: an Express front door at $PORT proxies -# to the internal Hermes dashboard, while preserving Nunchi-specific /api/* -# endpoints driven by agent-cli's status_reader. - -FROM node:22-bookworm AS hermes-build - -RUN apt-get update \ - && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - git ca-certificates curl python3 python3-venv python3-pip make g++ \ - && rm -rf /var/lib/apt/lists/* - -# uv (matches upstream Dockerfile) for fast, reproducible Python installs -ADD https://astral.sh/uv/install.sh /tmp/uv-install.sh -RUN sh /tmp/uv-install.sh && mv /root/.local/bin/uv /root/.local/bin/uvx /usr/local/bin/ - -WORKDIR /hermes -ARG HERMES_GIT_REF=v2026.4.30 -RUN git clone --depth 1 --branch "${HERMES_GIT_REF}" https://github.com/NousResearch/hermes-agent.git . - -# Build the web dashboard SPA + TUI assets. -ENV npm_config_install_links=false -RUN npm install --prefer-offline --no-audit \ - && (cd web && npm install --prefer-offline --no-audit && npm run build) \ - && (cd ui-tui && npm install --prefer-offline --no-audit && npm run build) \ - && npm cache clean --force - - -# Runtime image -FROM node:22-bookworm -ENV NODE_ENV=production -ENV PYTHONUNBUFFERED=1 - -RUN apt-get update \ - && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - ca-certificates curl build-essential gcc g++ make procps file git \ - python3 python3-pip python3-venv pkg-config sudo ripgrep tini \ - && rm -rf /var/lib/apt/lists/* \ - && ln -sf /usr/bin/python3 /usr/local/bin/python - -# uv (used for both Hermes and agent-cli installs) -ADD https://astral.sh/uv/install.sh /tmp/uv-install.sh -RUN sh /tmp/uv-install.sh && mv /root/.local/bin/uv /root/.local/bin/uvx /usr/local/bin/ - -# Copy Hermes source + pre-built UI assets -COPY --from=hermes-build /hermes /hermes - -# Install Hermes into a venv, including MCP + messaging extras (Telegram/Discord/Slack/etc). -WORKDIR /hermes -RUN uv venv \ - && uv pip install --no-cache-dir -e ".[mcp,messaging,web,cli]" - -# Make hermes CLI globally available. -RUN printf '%s\n' '#!/usr/bin/env bash' \ - 'source /hermes/.venv/bin/activate' \ - 'exec hermes "$@"' \ - > /usr/local/bin/hermes && chmod +x /usr/local/bin/hermes - -# Install agent-cli (the Nunchi trading CLI) into the same venv so the -# `nunchi_trading` MCP server (`python3 -m cli.main mcp serve`) and the -# /api/* status_reader endpoints can both find it. -# NOTE: build context MUST be the repo root (see deploy/hermes-railway/railway.toml -# dockerfilePath + Railway service Root Directory = repo root). All COPY paths below -# are therefore relative to the repo root, not to deploy/hermes-railway/. -WORKDIR /agent-cli -COPY . . -# `uv venv` (line ~54) creates the venv WITHOUT pip, so /hermes/.venv/bin/pip -# doesn't exist. Install into that same venv with `uv pip` (same mechanism used -# to install Hermes above), targeting it via VIRTUAL_ENV. -RUN VIRTUAL_ENV=/hermes/.venv uv pip install --no-cache-dir -e ".[mcp]" - -# Express wrapper -WORKDIR /app -COPY deploy/hermes-railway/package.json ./ -RUN npm install --production - -COPY deploy/hermes-railway/src ./src - -# Workspace defaults — copied to volume on first boot -COPY deploy/hermes-railway/workspace /opt/workspace-defaults - -RUN mkdir -p /data -ENV PORT=8080 -EXPOSE 8080 - -# tini reaps MCP stdio subprocesses (matches upstream Hermes Dockerfile) -ENTRYPOINT ["/usr/bin/tini", "-g", "--"] -CMD ["node", "src/server.js"] diff --git a/archive/deploy/hermes-railway/package.json b/archive/deploy/hermes-railway/package.json deleted file mode 100644 index 4bdf4e6..0000000 --- a/archive/deploy/hermes-railway/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "nunchi-hermes-railway", - "version": "0.1.0", - "description": "One-click Hermes Agent (Nous Research) trading deployment for Hyperliquid — powered by Nunchi agent-cli", - "private": true, - "scripts": { - "start": "node src/server.js" - }, - "dependencies": { - "express": "^4.21.0", - "http-proxy": "^1.18.1", - "js-yaml": "^4.1.0" - } -} diff --git a/archive/deploy/hermes-railway/railway.toml b/archive/deploy/hermes-railway/railway.toml deleted file mode 100644 index 16afa37..0000000 --- a/archive/deploy/hermes-railway/railway.toml +++ /dev/null @@ -1,23 +0,0 @@ -[build] -builder = "dockerfile" -# Build context is the repo root so the Dockerfile can COPY the agent-cli source. -# Set this service's Root Directory to the repo root (/) in Railway, not -# archive/deploy/hermes-railway, otherwise `COPY . .` cannot reach the CLI package. -dockerfilePath = "archive/deploy/hermes-railway/Dockerfile" - -[deploy] -healthcheckPath = "/health" -healthcheckTimeout = 300 -restartPolicyType = "on_failure" - -[[deploy.volumes]] -mountPath = "/data" - -[variables] -PORT = "8080" -HL_TESTNET = "true" -INTERNAL_GATEWAY_HOST = "127.0.0.1" -INTERNAL_GATEWAY_PORT = "9119" -HERMES_HOME = "/data/.hermes" -HERMES_DASHBOARD_HOST = "127.0.0.1" -HERMES_DASHBOARD_PORT = "9119" diff --git a/archive/deploy/hermes-railway/src/bootstrap.mjs b/archive/deploy/hermes-railway/src/bootstrap.mjs deleted file mode 100644 index 7c60579..0000000 --- a/archive/deploy/hermes-railway/src/bootstrap.mjs +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Bootstrap — auto-configure Hermes Agent with the Nunchi trading MCP server. - * - * Hermes splits config across two files in HERMES_HOME: - * - .env — provider API keys, TELEGRAM_BOT_TOKEN, etc. - * - config.yaml — model.provider, mcp_servers, platform_toolsets - * - * We seed both from Railway env vars on startup. User edits to either file - * are preserved across restarts (we only write keys we own). - */ -import { existsSync, mkdirSync, copyFileSync, writeFileSync, readFileSync, readdirSync } from "fs"; -import { join } from "path"; -import { execSync } from "child_process"; -import yaml from "js-yaml"; - -const HERMES_HOME = process.env.HERMES_HOME || "/data/.hermes"; -const WORKSPACE_DIR = join(HERMES_HOME, "workspace"); -const WORKSPACE_DEFAULTS = "/opt/workspace-defaults"; -const CONFIG_PATH = join(HERMES_HOME, "config.yaml"); -const ENV_PATH = join(HERMES_HOME, ".env"); - -// AI_PROVIDER → (Hermes provider name, env var Hermes reads the key from) -const PROVIDER_MAP = { - anthropic: { provider: "anthropic", envKey: "ANTHROPIC_API_KEY" }, - openai: { provider: "openrouter", envKey: "OPENAI_API_KEY" }, - openrouter: { provider: "openrouter", envKey: "OPENROUTER_API_KEY" }, - gemini: { provider: "gemini", envKey: "GEMINI_API_KEY" }, - google: { provider: "gemini", envKey: "GOOGLE_API_KEY" }, - nous: { provider: "nous-api", envKey: "NOUS_API_KEY" }, - zai: { provider: "zai", envKey: "GLM_API_KEY" }, - kimi: { provider: "kimi-coding", envKey: "KIMI_API_KEY" }, - huggingface: { provider: "huggingface", envKey: "HF_TOKEN" }, -}; - -export async function bootstrap() { - console.log("[bootstrap] Starting auto-configuration..."); - - for (const dir of [ - HERMES_HOME, - WORKSPACE_DIR, - join(HERMES_HOME, "skills"), - join(HERMES_HOME, "memories"), - join(HERMES_HOME, "logs"), - join(HERMES_HOME, "sessions"), - ]) { - mkdirSync(dir, { recursive: true }); - } - - if (existsSync(WORKSPACE_DEFAULTS)) { - for (const file of readdirSync(WORKSPACE_DEFAULTS)) { - const dest = join(WORKSPACE_DIR, file); - if (!existsSync(dest)) { - copyFileSync(join(WORKSPACE_DEFAULTS, file), dest); - console.log(`[bootstrap] Synced ${file} to workspace`); - } - } - } - - writeEnvFile(); - writeConfigYaml(); - - // Best-effort Hyperliquid builder fee approval (idempotent) - if (process.env.HL_PRIVATE_KEY) { - try { - const mainnet = (process.env.HL_TESTNET || "true").toLowerCase() === "false"; - const args = mainnet ? ["builder", "approve", "--mainnet"] : ["builder", "approve"]; - execSync(`python3 -m cli.main ${args.join(" ")}`, { - timeout: 30000, - cwd: "/agent-cli", - stdio: "pipe", - }); - console.log("[bootstrap] Builder fee approval sent"); - } catch { - // best-effort - } - } - - console.log("[bootstrap] Configuration complete"); -} - -function writeEnvFile() { - const aiProvider = (process.env.AI_PROVIDER || "anthropic").toLowerCase(); - const aiKey = process.env.AI_API_KEY || ""; - const providerInfo = PROVIDER_MAP[aiProvider] || PROVIDER_MAP.anthropic; - - const lines = []; - if (aiKey) lines.push(`${providerInfo.envKey}=${aiKey}`); - if (process.env.TELEGRAM_BOT_TOKEN) lines.push(`TELEGRAM_BOT_TOKEN=${process.env.TELEGRAM_BOT_TOKEN}`); - if (process.env.DISCORD_BOT_TOKEN) lines.push(`DISCORD_BOT_TOKEN=${process.env.DISCORD_BOT_TOKEN}`); - if (process.env.SLACK_BOT_TOKEN) lines.push(`SLACK_BOT_TOKEN=${process.env.SLACK_BOT_TOKEN}`); - - if (lines.length === 0) { - console.log("[bootstrap] No credentials to write to .env"); - return; - } - - writeFileSync(ENV_PATH, lines.join("\n") + "\n", { mode: 0o600 }); - console.log(`[bootstrap] Wrote ${lines.length} entries to .env`); -} - -function writeConfigYaml() { - const aiProvider = (process.env.AI_PROVIDER || "anthropic").toLowerCase(); - const providerInfo = PROVIDER_MAP[aiProvider] || PROVIDER_MAP.anthropic; - - const existing = existsSync(CONFIG_PATH) - ? (yaml.load(readFileSync(CONFIG_PATH, "utf-8")) || {}) - : {}; - - const config = { - ...existing, - model: { - ...(existing.model || {}), - provider: providerInfo.provider, - ...(process.env.HERMES_MODEL ? { default: process.env.HERMES_MODEL } : {}), - }, - mcp_servers: { - ...(existing.mcp_servers || {}), - nunchi_trading: { - command: "python3", - args: ["-m", "cli.main", "mcp", "serve"], - cwd: "/agent-cli", - env: { - HL_PRIVATE_KEY: process.env.HL_PRIVATE_KEY || "", - HL_TESTNET: process.env.HL_TESTNET || "true", - }, - }, - }, - }; - - if (process.env.TELEGRAM_BOT_TOKEN) { - config.platform_toolsets = { - ...(existing.platform_toolsets || {}), - telegram: ["hermes-telegram"], - }; - } - - writeFileSync(CONFIG_PATH, yaml.dump(config, { lineWidth: 120 })); - console.log("[bootstrap] Wrote config.yaml"); -} diff --git a/archive/deploy/hermes-railway/src/gateway.js b/archive/deploy/hermes-railway/src/gateway.js deleted file mode 100644 index 95a467b..0000000 --- a/archive/deploy/hermes-railway/src/gateway.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Gateway lifecycle — spawn, monitor, and restart the Hermes dashboard server. - * - * `hermes dashboard` runs a FastAPI HTTP server (default :9119). With - * --insecure --no-open it's safe to bind to a non-localhost interface inside - * the container; the Express front door at $PORT is what's actually exposed - * to the public network. - */ -const { spawn } = require("child_process"); -const http = require("http"); - -const GATEWAY_HOST = process.env.INTERNAL_GATEWAY_HOST || "127.0.0.1"; -const GATEWAY_PORT = parseInt(process.env.INTERNAL_GATEWAY_PORT || "9119", 10); -const HERMES_BIN = process.env.HERMES_BIN || "/usr/local/bin/hermes"; -const HERMES_HOME = process.env.HERMES_HOME || "/data/.hermes"; - -let gatewayProcess = null; - -function startGateway() { - if (gatewayProcess && !gatewayProcess.killed) { - console.log("[gateway] Already running (pid=%d)", gatewayProcess.pid); - return; - } - - console.log("[gateway] Starting hermes dashboard..."); - - const args = [ - "dashboard", - "--host", GATEWAY_HOST, - "--port", String(GATEWAY_PORT), - "--no-open", - ]; - // Hermes refuses non-localhost binds without --insecure (it exposes API - // keys). Inside a container that's the expected deployment shape — the - // Express wrapper is the public surface, not the dashboard directly. - if (GATEWAY_HOST !== "127.0.0.1" && GATEWAY_HOST !== "localhost") { - args.push("--insecure"); - } - - gatewayProcess = spawn(HERMES_BIN, args, { - env: { - ...process.env, - HERMES_HOME, - NODE_ENV: "production", - }, - stdio: ["ignore", "pipe", "pipe"], - }); - - gatewayProcess.stdout.on("data", (data) => { - const line = data.toString().trim(); - if (line) console.log(`[gateway] ${line}`); - }); - - gatewayProcess.stderr.on("data", (data) => { - const line = data.toString().trim(); - if (line) console.error(`[gateway] ${redactTokens(line)}`); - }); - - gatewayProcess.on("exit", (code, signal) => { - console.log(`[gateway] Exited (code=${code}, signal=${signal})`); - gatewayProcess = null; - }); - - console.log("[gateway] Spawned (pid=%d)", gatewayProcess.pid); -} - -async function waitForGatewayReady(timeoutMs = 60000) { - const start = Date.now(); - while (Date.now() - start < timeoutMs) { - try { - await httpGet(`http://${GATEWAY_HOST}:${GATEWAY_PORT}/`); - return true; - } catch { - await sleep(500); - } - } - throw new Error(`Hermes dashboard did not become ready within ${timeoutMs}ms`); -} - -function getGatewayProcess() { - return gatewayProcess; -} - -function restartGateway() { - if (gatewayProcess && !gatewayProcess.killed) { - gatewayProcess.kill("SIGTERM"); - setTimeout(() => { - if (gatewayProcess && !gatewayProcess.killed) { - gatewayProcess.kill("SIGKILL"); - } - }, 5000); - } - setTimeout(() => startGateway(), 1500); -} - -function httpGet(url) { - return new Promise((resolve, reject) => { - http.get(url, { timeout: 3000 }, (res) => { - let body = ""; - res.on("data", (chunk) => (body += chunk)); - res.on("end", () => resolve(body)); - }).on("error", reject); - }); -} - -function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -function redactTokens(str) { - return str.replace(/(?:sk-[a-zA-Z0-9-]{10,}|[a-f0-9]{64})/g, "[REDACTED]"); -} - -module.exports = { startGateway, waitForGatewayReady, getGatewayProcess, restartGateway }; diff --git a/archive/deploy/hermes-railway/src/onboard.js b/archive/deploy/hermes-railway/src/onboard.js deleted file mode 100644 index ef4dd5e..0000000 --- a/archive/deploy/hermes-railway/src/onboard.js +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Auto-onboard — resolve Telegram chat ID and send the ready message. - * - * Hermes loads config.yaml + .env on dashboard startup, so there's no - * separate onboard CLI step (unlike OpenClaw). This module exists only to - * (a) detect the operator's Telegram chat ID by username and (b) post the - * ready message once per fresh credential set. - */ -const { existsSync, readFileSync, writeFileSync } = require("fs"); -const { join } = require("path"); -const https = require("https"); - -const HERMES_HOME = process.env.HERMES_HOME || "/data/.hermes"; -const FINGERPRINT_PATH = join(HERMES_HOME, ".env-fingerprint"); - -async function autoOnboard() { - const aiProvider = process.env.AI_PROVIDER; - const aiKey = process.env.AI_API_KEY; - - if (!aiProvider || !aiKey) { - console.log("[onboard] No AI_PROVIDER or AI_API_KEY set, skipping auto-onboard"); - return; - } - - const fingerprint = computeFingerprint(); - if (existsSync(FINGERPRINT_PATH)) { - const stored = readFileSync(FINGERPRINT_PATH, "utf-8").trim(); - if (stored === fingerprint) { - console.log("[onboard] Environment unchanged, skipping onboard"); - return; - } - } - - console.log("[onboard] Running auto-onboard..."); - - let telegramChatId = null; - if (process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_USERNAME) { - try { - telegramChatId = await resolveTelegramChatId( - process.env.TELEGRAM_BOT_TOKEN, - process.env.TELEGRAM_USERNAME, - ); - if (telegramChatId) { - console.log(`[onboard] Resolved Telegram chat ID: ${telegramChatId}`); - const userMd = `# User\n\nTelegram chat ID: ${telegramChatId}\nUsername: ${process.env.TELEGRAM_USERNAME}\n`; - writeFileSync(join(HERMES_HOME, "workspace", "USER.md"), userMd); - } - } catch (err) { - console.warn("[onboard] Could not resolve Telegram chat ID:", err.message); - } - } - - if (process.env.TELEGRAM_BOT_TOKEN) { - try { - await sendTelegramMessage( - process.env.TELEGRAM_BOT_TOKEN, - "Nunchi Hermes agent is ready. Say 'hl apex run' to start autonomous trading, or 'hl radar once' to scan for opportunities.", - telegramChatId, - ); - console.log("[onboard] Sent ready message to Telegram"); - } catch (err) { - console.warn("[onboard] Could not send Telegram message:", err.message); - } - } - - writeFileSync(FINGERPRINT_PATH, fingerprint); - console.log("[onboard] Onboarding complete"); -} - -function computeFingerprint() { - const crypto = require("crypto"); - const parts = [ - process.env.AI_PROVIDER || "", - (process.env.AI_API_KEY || "").slice(-8), - process.env.TELEGRAM_BOT_TOKEN ? "tg" : "", - process.env.HL_TESTNET || "true", - ]; - return crypto.createHash("sha256").update(parts.join("|")).digest("hex").slice(0, 16); -} - -async function resolveTelegramChatId(botToken, username) { - const cleanUsername = username.replace("@", "").toLowerCase(); - const data = await fetchJson(`https://api.telegram.org/bot${botToken}/getUpdates?limit=50`); - if (!data.ok || !data.result) return null; - - for (const update of data.result) { - const msg = update.message || update.my_chat_member; - if (!msg || !msg.from) continue; - if ((msg.from.username || "").toLowerCase() === cleanUsername) { - return msg.chat.id; - } - } - return null; -} - -async function sendTelegramMessage(botToken, text, chatId = null) { - if (!chatId) { - const data = await fetchJson(`https://api.telegram.org/bot${botToken}/getUpdates?limit=1`); - if (!data.ok || !data.result || data.result.length === 0) return; - chatId = data.result[0].message?.chat?.id; - } - if (!chatId) return; - - await fetchJson(`https://api.telegram.org/bot${botToken}/sendMessage`, { - method: "POST", - body: JSON.stringify({ chat_id: chatId, text }), - }); -} - -function fetchJson(url, opts = {}) { - return new Promise((resolve, reject) => { - const req = https.request(url, { - method: opts.method || "GET", - headers: opts.body ? { "Content-Type": "application/json" } : {}, - timeout: 10000, - }, (res) => { - let body = ""; - res.on("data", (chunk) => (body += chunk)); - res.on("end", () => { - try { - resolve(JSON.parse(body)); - } catch { - resolve({ ok: false }); - } - }); - }); - req.on("error", reject); - if (opts.body) req.write(opts.body); - req.end(); - }); -} - -module.exports = { autoOnboard }; diff --git a/archive/deploy/hermes-railway/src/server.js b/archive/deploy/hermes-railway/src/server.js deleted file mode 100644 index 634f3b9..0000000 --- a/archive/deploy/hermes-railway/src/server.js +++ /dev/null @@ -1,273 +0,0 @@ -/** - * Railway entrypoint — health check + reverse proxy to Hermes dashboard. - * - * Flow: - * 1. Run bootstrap (write .env + config.yaml, sync workspace defaults) - * 2. Auto-onboard if Telegram credentials present - * 3. Start `hermes dashboard` as a child process on the internal port - * 4. Serve health checks + Nunchi /api/* + proxy everything else to dashboard - */ -const express = require("express"); -const crypto = require("crypto"); -const fs = require("fs"); -const path = require("path"); -const { execSync } = require("child_process"); -const httpProxy = require("http-proxy"); -const { bootstrap } = require("./bootstrap.mjs"); -const { startGateway, waitForGatewayReady, getGatewayProcess } = require("./gateway"); -const { autoOnboard } = require("./onboard"); -const { readStatus, readStrategies } = require("./status"); - -const app = express(); -const PORT = parseInt(process.env.PORT || "8080", 10); -const GATEWAY_HOST = process.env.INTERNAL_GATEWAY_HOST || "127.0.0.1"; -const GATEWAY_PORT = parseInt(process.env.INTERNAL_GATEWAY_PORT || "9119", 10); -const START_TIME = Date.now(); -const AGENT_CLI_DIR = "/agent-cli"; -const DATA_DIR = process.env.DATA_DIR || "/data"; -const CONTROL_API_TOKEN = process.env.CONTROL_API_TOKEN || ""; - -const proxy = httpProxy.createProxyServer({ - target: `http://${GATEWAY_HOST}:${GATEWAY_PORT}`, - ws: true, - changeOrigin: true, -}); - -proxy.on("error", (err, req, res) => { - if (res.writeHead) { - res.writeHead(502, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ error: "gateway_unavailable", message: err.message })); - } -}); - -app.get("/health", (req, res) => { - const gw = getGatewayProcess(); - res.json({ - status: "ok", - uptime_s: Math.floor((Date.now() - START_TIME) / 1000), - gateway_alive: gw ? !gw.killed : false, - gateway_pid: gw ? gw.pid : null, - }); -}); - -const CORS_ORIGIN = process.env.CORS_ORIGIN || "*"; -app.use("/api", (req, res, next) => { - res.header("Access-Control-Allow-Origin", CORS_ORIGIN); - res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-API-Token"); - if (req.method === "OPTIONS") return res.sendStatus(204); - next(); -}); - -function tokensEqual(a, b) { - const left = Buffer.from(a || ""); - const right = Buffer.from(b || ""); - return left.length === right.length && crypto.timingSafeEqual(left, right); -} - -function requireControlAuth(req, res, next) { - if (!CONTROL_API_TOKEN) { - return res.status(503).json({ - error: "control_auth_required", - message: "Set CONTROL_API_TOKEN to enable mutating control endpoints.", - }); - } - - const auth = req.get("authorization") || ""; - const bearer = auth.toLowerCase().startsWith("bearer ") ? auth.slice(7).trim() : ""; - const provided = bearer || req.get("x-api-token") || ""; - if (!tokensEqual(provided, CONTROL_API_TOKEN)) { - return res.status(401).json({ error: "unauthorized" }); - } - return next(); -} - -app.get("/status", async (req, res) => { - try { - const output = execSync("python3 -m cli.main apex status", { - timeout: 10000, - encoding: "utf-8", - cwd: AGENT_CLI_DIR, - }); - res.type("text/plain").send(output); - } catch (e) { - res.type("text/plain").send(e.stdout || e.stderr || e.message); - } -}); - -app.get("/api/status", (req, res) => { - res.json(readStatus()); -}); - -app.get("/api/strategies", (req, res) => { - res.json(readStrategies()); -}); - -app.get("/api/feed", (req, res) => { - res.writeHead(200, { - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache", - "X-Accel-Buffering": "no", - Connection: "keep-alive", - }); - - let lastTick = -1; - const interval = setInterval(() => { - try { - const status = readStatus(); - const tick = status.tick_count || 0; - if (tick !== lastTick) { - lastTick = tick; - res.write(`data: ${JSON.stringify(status)}\n\n`); - } - } catch { - /* ignore */ - } - }, 2000); - - req.on("close", () => clearInterval(interval)); -}); - -app.post("/api/skill/install", express.json(), (req, res) => { - try { - const output = execSync("python3 -m cli.api.status_reader strategies", { - timeout: 10000, - encoding: "utf-8", - cwd: AGENT_CLI_DIR, - }); - const data = JSON.parse(output.trim()); - const count = Object.keys(data.strategies || {}).length; - res.json({ installed: true, strategies: count, tools: 13 }); - } catch (e) { - res.status(500).json({ installed: false, error: e.message }); - } -}); - -app.post("/api/pause", requireControlAuth, (req, res) => { - const gw = getGatewayProcess(); - if (gw && !gw.killed) { - try { - process.kill(gw.pid, "SIGSTOP"); - res.json({ status: "paused" }); - } catch (e) { - res.status(500).json({ error: e.message }); - } - } else { - res.status(409).json({ error: "No running agent to pause" }); - } -}); - -app.post("/api/resume", requireControlAuth, (req, res) => { - const gw = getGatewayProcess(); - if (gw && !gw.killed) { - try { - process.kill(gw.pid, "SIGCONT"); - res.json({ status: "resumed" }); - } catch (e) { - res.status(500).json({ error: e.message }); - } - } else { - res.status(409).json({ error: "No paused agent to resume" }); - } -}); - -app.post("/api/configure", requireControlAuth, express.json(), (req, res) => { - const configPath = path.join(DATA_DIR, "apex", "config-override.json"); - try { - fs.mkdirSync(path.dirname(configPath), { recursive: true }); - fs.writeFileSync(configPath, JSON.stringify(req.body, null, 2)); - res.json({ status: "ok", applied_at: "next_tick" }); - } catch (e) { - res.status(500).json({ error: e.message }); - } -}); - -app.get("/api/trades", (req, res) => { - const limit = parseInt(req.query.limit || "50", 10); - try { - const output = execSync( - `python3 -m cli.api.status_reader trades --data-dir ${DATA_DIR} --limit ${limit}`, - { timeout: 10000, encoding: "utf-8", cwd: AGENT_CLI_DIR, stdio: ["pipe", "pipe", "pipe"] } - ); - res.json(JSON.parse(output.trim())); - } catch (err) { - res.status(500).json({ error: err.message }); - } -}); - -app.get("/api/reflect", (req, res) => { - try { - const output = execSync( - `python3 -m cli.api.status_reader reflect --data-dir ${DATA_DIR}`, - { timeout: 10000, encoding: "utf-8", cwd: AGENT_CLI_DIR, stdio: ["pipe", "pipe", "pipe"] } - ); - res.json(JSON.parse(output.trim())); - } catch (err) { - res.status(500).json({ error: err.message }); - } -}); - -app.get("/api/scanner", (req, res) => { - try { - const output = execSync( - `python3 -m cli.api.status_reader radar --data-dir ${DATA_DIR}`, - { timeout: 10000, encoding: "utf-8", cwd: AGENT_CLI_DIR, stdio: ["pipe", "pipe", "pipe"] } - ); - res.json(JSON.parse(output.trim())); - } catch (err) { - res.status(500).json({ error: err.message }); - } -}); - -app.get("/api/journal", (req, res) => { - const limit = parseInt(req.query.limit || "50", 10); - try { - const output = execSync( - `python3 -m cli.api.status_reader journal --data-dir ${DATA_DIR} --limit ${limit}`, - { timeout: 10000, encoding: "utf-8", cwd: AGENT_CLI_DIR, stdio: ["pipe", "pipe", "pipe"] } - ); - res.json(JSON.parse(output.trim())); - } catch (err) { - res.status(500).json({ error: err.message }); - } -}); - -// Everything else → Hermes dashboard -app.use((req, res) => { - proxy.web(req, res); -}); - -const server = app.listen(PORT, async () => { - console.log(`[server] Listening on :${PORT}`); - - try { - await bootstrap(); - await autoOnboard(); - startGateway(); - await waitForGatewayReady(); - console.log("[server] Hermes dashboard is ready"); - } catch (err) { - console.error("[server] Startup error:", err.message); - // Keep server up so /health stays green and the user can inspect logs - } -}); - -server.on("upgrade", (req, socket, head) => { - proxy.ws(req, socket, head); -}); - -function shutdown(signal) { - console.log(`[server] ${signal} received, shutting down`); - const gw = getGatewayProcess(); - if (gw && !gw.killed) { - gw.kill("SIGTERM"); - setTimeout(() => { - if (!gw.killed) gw.kill("SIGKILL"); - }, 10000); - } - server.close(() => process.exit(0)); - setTimeout(() => process.exit(1), 15000); -} - -process.on("SIGTERM", () => shutdown("SIGTERM")); -process.on("SIGINT", () => shutdown("SIGINT")); diff --git a/archive/deploy/hermes-railway/src/status.js b/archive/deploy/hermes-railway/src/status.js deleted file mode 100644 index 7d5781a..0000000 --- a/archive/deploy/hermes-railway/src/status.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Status reader — shells out to cli.api.status_reader for agent state. - * Identical contract to the openclaw-railway status module: the underlying - * agent runtime doesn't matter, only agent-cli's view of trading state. - */ -const { execSync } = require("child_process"); -const fs = require("fs"); -const path = require("path"); - -const AGENT_CLI_DIR = "/agent-cli"; -const DATA_DIR = process.env.DATA_DIR || "/data"; - -function readStatus() { - try { - const output = execSync( - `python3 -m cli.api.status_reader status --data-dir ${DATA_DIR}`, - { timeout: 10000, encoding: "utf-8", cwd: AGENT_CLI_DIR, stdio: ["pipe", "pipe", "pipe"] } - ); - return JSON.parse(output.trim()); - } catch (err) { - const apexState = path.join(DATA_DIR, "apex", "state.json"); - if (fs.existsSync(apexState)) { - try { - const state = JSON.parse(fs.readFileSync(apexState, "utf-8")); - const active = (state.slots || []).filter((s) => s.status === "active"); - return { - status: "running", - engine: "apex", - tick_count: state.tick_count || 0, - daily_pnl: state.daily_pnl || 0, - total_pnl: state.total_pnl || 0, - active_slots: active, - positions: active.map((s) => ({ - slot: s.slot_id, - market: s.instrument || "", - side: s.side || "", - size: s.entry_size || 0, - entry: s.entry_price || 0, - roe: s.roe_pct || 0, - phase: s.dsl_phase || 0, - })), - }; - } catch { - // fall through - } - } - return { status: "stopped", error: err.message }; - } -} - -function readStrategies() { - try { - const output = execSync( - `python3 -m cli.api.status_reader strategies`, - { timeout: 10000, encoding: "utf-8", cwd: AGENT_CLI_DIR, stdio: ["pipe", "pipe", "pipe"] } - ); - return JSON.parse(output.trim()); - } catch (err) { - return { error: err.message }; - } -} - -module.exports = { readStatus, readStrategies }; diff --git a/archive/deploy/hermes-railway/workspace/AGENTS.md b/archive/deploy/hermes-railway/workspace/AGENTS.md deleted file mode 100644 index b8da34e..0000000 --- a/archive/deploy/hermes-railway/workspace/AGENTS.md +++ /dev/null @@ -1,47 +0,0 @@ -# Agent Operating Guidelines - -You are a Nunchi autonomous trading agent on Hyperliquid running inside Hermes Agent. You manage positions, scan for opportunities, and protect capital using the `hl` CLI and the `nunchi_trading` MCP server. - -## Core Rules - -1. **Capital preservation first.** Never risk more than the configured daily loss limit. Always use DSL trailing stops on every position. -2. **Data-driven decisions only.** Never invent market data. If you don't have data, run `hl radar once` or `hl movers once` to get it. -3. **Report all actions.** When you enter or exit a position, tell the user via Telegram with: instrument, direction, size, price, and reason. -4. **Verify before trading.** Before any trade, run `hl account` to check balance and `hl status` to see existing positions. -5. **Run REFLECT after sessions.** After any trading session (or when asked), run `hl reflect run` to analyze performance and learn from mistakes. - -## Trading Workflow - -1. **Scan**: `hl radar once` — find the best setups across all HL perps -2. **Validate**: Check radar score (>170 = actionable), confirm direction aligns with BTC macro -3. **Enter**: `hl trade ` or let APEX handle it: `hl apex run` -4. **Monitor**: `hl status --watch` — track positions and PnL -5. **Exit**: DSL handles exits automatically, or manual: `hl trade ` -6. **Review**: `hl reflect run --since ` — analyze what worked and what didn't - -## APEX Autonomous Mode - -When the user says "start trading" or "run APEX": -```bash -hl apex run --preset default -``` - -APEX manages 2-3 concurrent positions automatically: -- Scans for opportunities every 15 ticks -- Detects emerging movers every tick -- Applies DSL trailing stops to every position -- Exits on conviction collapse, stagnation, or hard stops -- Auto-adjusts parameters based on REFLECT performance reviews - -## Safety - -- Never expose private keys, API keys, or tokens in messages -- Never run `git push`, `rm -rf`, or destructive shell commands -- If a trade fails, report the error and suggest next steps — don't retry blindly -- If daily loss limit is triggered, stop all trading and notify the user - -## Memory - -- Hermes persists session memory under `$HERMES_HOME/memories/` automatically — read recent entries on startup for context -- After each trading session, summarize what happened (entries, exits, PnL, lessons) so the next run starts with context -- Track winning/losing patterns across sessions to improve future decisions diff --git a/archive/deploy/hermes-railway/workspace/BOOTSTRAP.md b/archive/deploy/hermes-railway/workspace/BOOTSTRAP.md deleted file mode 100644 index 524b44e..0000000 --- a/archive/deploy/hermes-railway/workspace/BOOTSTRAP.md +++ /dev/null @@ -1,39 +0,0 @@ -# Bootstrap - -Run these checks on every startup. Execute silently — only report errors or the final status. - -## Startup Sequence - -1. **Check environment:** -```bash -hl setup check -``` -If this fails, report the missing configuration to the user and stop. - -2. **Approve builder fee (idempotent):** -```bash -hl builder approve -``` - -3. **Check account balance:** -```bash -hl account -``` -If balance is 0, tell the user: "Account has no balance. Run `hl setup claim-usdyp` for testnet funds, or deposit USDC for mainnet." - -4. **Check existing positions:** -```bash -hl status -``` - -5. **Check APEX state (if exists):** -```bash -hl apex status -``` - -6. **Report ready:** -Send to user: "Agent ready. Balance: $X. Active positions: N. Say 'start trading' to begin APEX, or ask me to scan for opportunities." - -## On Failure - -If any check fails, report the specific error and suggest a fix. Do not start trading with a broken environment. diff --git a/archive/deploy/hermes-railway/workspace/SOUL.md b/archive/deploy/hermes-railway/workspace/SOUL.md deleted file mode 100644 index d79db94..0000000 --- a/archive/deploy/hermes-railway/workspace/SOUL.md +++ /dev/null @@ -1,17 +0,0 @@ -# Soul - -You are a direct, data-driven trading partner. You don't sugarcoat losses or hype wins. You present facts, show the numbers, and let the user decide. - -## Values - -- **Accuracy over speed.** Wrong data is worse than no data. Always verify before acting. -- **Protect capital.** A trader who survives can always trade tomorrow. Never gamble. -- **Admit uncertainty.** If you don't know, say so. Markets are unpredictable — act accordingly. -- **Keep it brief.** Report results concisely. No fluff. Numbers speak louder than narratives. - -## Communication Style - -- Lead with the result: "Entered ETH-PERP long @ $2,450, size 0.5" — not a paragraph of analysis -- Use tables for multi-item data (positions, radar results, REFLECT metrics) -- Flag warnings prominently: "DAILY LOSS LIMIT at 80% — consider stopping" -- Celebrate wins briefly, analyze losses thoroughly diff --git a/archive/deploy/hermes-railway/workspace/TOOLS.md b/archive/deploy/hermes-railway/workspace/TOOLS.md deleted file mode 100644 index f065eb5..0000000 --- a/archive/deploy/hermes-railway/workspace/TOOLS.md +++ /dev/null @@ -1,61 +0,0 @@ -# Tools - -## MCP Server: nunchi_trading - -The primary tool provider, registered in `config.yaml` under `mcp_servers.nunchi_trading`. Exposes 24 trading tools via Model Context Protocol, including: - -- `account` — Show HL account state (balance, margin, positions) -- `status` — Current positions, PnL, and risk state -- `trade` — Place a single order (instrument, side, size) -- `run_strategy` — Start autonomous strategy trading -- `strategies` — List all 14 available strategies -- `radar_run` — Run opportunity radar across all HL perps -- `apex_status` — Show APEX orchestrator state -- `apex_run` — Start APEX autonomous multi-slot trading -- `reflect_run` — Run REFLECT performance review -- `setup_check` — Validate environment configuration -- `builder_status` — Check builder fee approval status -- `wallet_list` — List available wallets -- `wallet_auto` — Create wallet automatically -- `funding_rates` — Read current funding rates -- `funding_hedge_propose` — Build a CFI v2 funding hedge proposal -- `funding_hedge_backtest` — Run the reference funding hedge backtest -- `funding_hedge_execute` — Execute or dry-run a CFI v2 hedge; requires `confirmed=true` - -## CLI: hl - -All MCP tools are also available as CLI commands. Use the CLI for operations not exposed via MCP: - -```bash -hl apex run [--preset default|conservative|aggressive] [--mainnet] -hl radar once [--mock] -hl movers once [--mock] -hl dsl run -i ETH-PERP [--preset tight] -hl reflect run [--since DATE] -hl house join [--url URL] -``` - -## Hermes Built-in Toolsets - -The trading MCP runs alongside Hermes's bundled toolsets. Useful ones: -- `terminal` — run shell commands (e.g. `hl ...`) -- `file` — read/write files in the workspace -- `web` — search and extract live market context -- `cronjob` — schedule recurring tasks -- `skills` — list and view installed skills (e.g. trading playbooks) - -Toggle via `platform_toolsets` in `config.yaml`. The Telegram channel ships with the `hermes-telegram` preset by default. - -## Shell - -Available: `python3`, `node`, `git`, `rg` (ripgrep), `curl` -Not available: `jq` (use `python3 -c "import json; ..."` instead) - -## Cron / Scheduling - -APEX has built-in scheduling: -- Daily PnL reset at UTC midnight -- REFLECT performance review every 4 hours -- Auto-parameter adjustment based on REFLECT findings - -For custom schedules, use Hermes's `cronjob` toolset (`cronjob create ...`) or the gateway's cron system. diff --git a/archive/deploy/openclaw-railway/.env.example b/archive/deploy/openclaw-railway/.env.example deleted file mode 100644 index b5e16dc..0000000 --- a/archive/deploy/openclaw-railway/.env.example +++ /dev/null @@ -1,18 +0,0 @@ -# Required -HL_PRIVATE_KEY=0x... # Hyperliquid private key -AI_PROVIDER=anthropic # anthropic, openai, gemini, openrouter, blockrun -AI_API_KEY=sk-ant-... # API key for chosen provider (not needed for blockrun) -TELEGRAM_BOT_TOKEN=123456789:AA... # From @BotFather -TELEGRAM_USERNAME=your_username # Your Telegram @username - -# Optional -HL_TESTNET=true # true (default) or false for mainnet -SETUP_PASSWORD= # Password for control UI (recommended) - -# ClawRouter / x402 (when AI_PROVIDER=blockrun) -# No API key needed — pay per LLM call with USDC via x402 micropayments. -# Install ClawRouter: curl -fsSL https://blockrun.ai/ClawRouter-update | bash -# Fund wallet with USDC on Base or Solana. -# BLOCKRUN_WALLET_KEY=0x... # EVM private key for x402 payment signing -# BLOCKRUN_PROXY_PORT=8402 # ClawRouter local proxy port (default: 8402) -# BLOCKRUN_PAYMENT_CHAIN=base # "base" (EVM) or "solana" diff --git a/archive/deploy/openclaw-railway/Dockerfile b/archive/deploy/openclaw-railway/Dockerfile deleted file mode 100644 index 996c23e..0000000 --- a/archive/deploy/openclaw-railway/Dockerfile +++ /dev/null @@ -1,87 +0,0 @@ -# Build OpenClaw from source -FROM node:22-bookworm AS openclaw-build - -RUN apt-get update \ - && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - git ca-certificates curl python3 make g++ \ - && rm -rf /var/lib/apt/lists/* - -RUN curl -fsSL https://bun.sh/install | bash -ENV PATH="/root/.bun/bin:${PATH}" -RUN corepack enable - -WORKDIR /openclaw -ARG OPENCLAW_GIT_REF=v2026.2.22 -RUN git clone --depth 1 --branch "${OPENCLAW_GIT_REF}" https://github.com/openclaw/openclaw.git . - -# Patch workspace protocol references -RUN set -eux; \ - find ./extensions -name 'package.json' -type f | while read -r f; do \ - sed -i -E 's/"openclaw"[[:space:]]*:[[:space:]]*">=[^"]+"/"openclaw": "*"/g' "$f"; \ - sed -i -E 's/"openclaw"[[:space:]]*:[[:space:]]*"workspace:[^"]+"/"openclaw": "*"/g' "$f"; \ - done - -RUN pnpm install --no-frozen-lockfile -RUN pnpm build -ENV OPENCLAW_PREFER_PNPM=1 -RUN pnpm ui:install && pnpm ui:build - - -# Runtime image -FROM node:22-bookworm -ENV NODE_ENV=production - -RUN apt-get update \ - && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - ca-certificates curl build-essential gcc g++ make procps file git \ - python3 python3-pip python3-venv pkg-config sudo ripgrep \ - && rm -rf /var/lib/apt/lists/* \ - && ln -sf /usr/bin/python3 /usr/local/bin/python - -# Node tools -RUN corepack enable -RUN npm install -g mcporter@0.7.3 mcp-remote@0.1.38 - -# OpenClaw from build stage -COPY --from=openclaw-build /openclaw /openclaw -RUN printf '%s\n' '#!/usr/bin/env bash' 'exec node /openclaw/dist/entry.js "$@"' \ - > /usr/local/bin/openclaw && chmod +x /usr/local/bin/openclaw - -# rg wrapper: strip grep-style flags that agents pass -RUN mv /usr/bin/rg /usr/bin/rg-real \ - && printf '%s\n' \ - '#!/usr/bin/env bash' \ - 'args=()' \ - 'for a in "$@"; do' \ - ' case "$a" in -R|-r) ;; *) args+=("$a") ;; esac' \ - 'done' \ - 'exec /usr/bin/rg-real "${args[@]}"' \ - > /usr/local/bin/rg && chmod +x /usr/local/bin/rg - -# Install agent-cli (our trading CLI) -# NOTE: build context MUST be the repo root (see deploy/openclaw-railway/railway.toml -# dockerfilePath + Railway service Root Directory = repo root). All COPY paths below -# are therefore relative to the repo root, not to deploy/openclaw-railway/. -WORKDIR /agent-cli -COPY . . -RUN pip install --no-cache-dir --break-system-packages -e ".[mcp]" - -# Wrapper app -WORKDIR /app -COPY deploy/openclaw-railway/package.json ./ -RUN npm install --production - -COPY deploy/openclaw-railway/src ./src - -# Workspace defaults (copied to volume at runtime) -COPY deploy/openclaw-railway/workspace /opt/workspace-defaults - -# Vendor mcporter skill -RUN set -eux; \ - mkdir -p /opt/openclaw-skills; \ - cp -r /openclaw/skills/mcporter /opt/openclaw-skills/ 2>/dev/null || true - -RUN mkdir -p /data -ENV PORT=8080 -EXPOSE 8080 -CMD ["node", "src/server.js"] diff --git a/archive/deploy/openclaw-railway/package.json b/archive/deploy/openclaw-railway/package.json deleted file mode 100644 index ae71171..0000000 --- a/archive/deploy/openclaw-railway/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "nunchi-openclaw-railway", - "version": "0.1.0", - "description": "One-click OpenClaw trading agent for Hyperliquid — powered by Nunchi agent-cli", - "private": true, - "scripts": { - "start": "node src/server.js" - }, - "dependencies": { - "express": "^4.21.0", - "http-proxy": "^1.18.1" - } -} diff --git a/archive/deploy/openclaw-railway/railway.toml b/archive/deploy/openclaw-railway/railway.toml deleted file mode 100644 index 50d7479..0000000 --- a/archive/deploy/openclaw-railway/railway.toml +++ /dev/null @@ -1,23 +0,0 @@ -[build] -builder = "dockerfile" -# Build context is the repo root so the Dockerfile can COPY the agent-cli source. -# Set this service's Root Directory to the repo root (/) in Railway, not -# archive/deploy/openclaw-railway, otherwise `COPY . .` cannot reach the CLI package. -dockerfilePath = "archive/deploy/openclaw-railway/Dockerfile" - -[deploy] -healthcheckPath = "/health" -healthcheckTimeout = 300 -restartPolicyType = "on_failure" - -[[deploy.volumes]] -mountPath = "/data" - -[variables] -PORT = "8080" -HL_TESTNET = "true" -INTERNAL_GATEWAY_HOST = "127.0.0.1" -INTERNAL_GATEWAY_PORT = "18789" -OPENCLAW_ENTRY = "/openclaw/dist/entry.js" -OPENCLAW_STATE_DIR = "/data/.openclaw" -OPENCLAW_WORKSPACE_DIR = "/data/workspace" diff --git a/archive/deploy/openclaw-railway/src/bootstrap.mjs b/archive/deploy/openclaw-railway/src/bootstrap.mjs deleted file mode 100644 index 95275e5..0000000 --- a/archive/deploy/openclaw-railway/src/bootstrap.mjs +++ /dev/null @@ -1,286 +0,0 @@ -/** - * Bootstrap — auto-configure OpenClaw (v2026.2.22) with the Nunchi trading MCP server. - * - * Creates persistent directories, syncs workspace files, writes a v2026.2.22-VALID - * openclaw.json, and registers our stdio MCP server with mcporter (the MCP runtime - * OpenClaw v2026.2.22 ships with). - * - * Schema source: openclaw v2026.2.22 (pinned tag), src/config/zod-schema*.ts. - * The root config schema is `.strict()` (rejects unknown keys), composed in - * src/config/zod-schema.ts -> OpenClawSchema. Every key emitted below maps to a - * real schema field; see the inline citations. - */ -import { existsSync, mkdirSync, copyFileSync, writeFileSync, readdirSync } from "fs"; -import { join } from "path"; -import { execSync } from "child_process"; - -const STATE_DIR = process.env.OPENCLAW_STATE_DIR || "/data/.openclaw"; -const WORKSPACE_DIR = process.env.OPENCLAW_WORKSPACE_DIR || "/data/workspace"; -const WORKSPACE_DEFAULTS = "/opt/workspace-defaults"; -const CONFIG_PATH = join(STATE_DIR, "openclaw.json"); - -// mcporter reads exactly one config file when MCPORTER_CONFIG is set (no home/project -// merge), so we pin an absolute path under the persistent state dir. This makes the -// `nunchi_trading` server reachable from `mcporter` regardless of the agent's cwd. -// Source: mcporter@0.7.3 dist/config.js resolveConfigPath() — explicit --config, then -// process.env.MCPORTER_CONFIG, then /config/mcporter.json. docs/config.md -// "Config Resolution Order" #2: "If MCPORTER_CONFIG is set, only that file is used." -const MCPORTER_CONFIG_PATH = join(STATE_DIR, "mcporter.json"); - -// AI_PROVIDER (our deploy env) -> { envVar, modelRef } for OpenClaw v2026.2.22. -// -// provider key home: OpenClaw resolves provider API keys from provider-native ENV vars -// at runtime (src/agents/live-auth-keys.ts PROVIDER_API_KEY_CONFIG: anthropic -> -// ANTHROPIC_API_KEY, google -> GEMINI_API_KEY (+GOOGLE_API_KEY fallback), openai -> -// OPENAI_API_KEY; derived `${BASE}_API_KEY` for the rest; cf. src/config/io.ts which -// allowlists OPENAI_API_KEY/ANTHROPIC_API_KEY/GEMINI_API_KEY/OPENROUTER_API_KEY). There -// is NO top-level `apiKey`/`provider` key in OpenClawSchema, so the credential is passed -// via env (gateway.js forwards `...process.env` to the gateway child), NOT written to -// openclaw.json. -// -// model home: agents.defaults.model.primary (src/config/zod-schema.agent-defaults.ts -// AgentDefaultsSchema.model.primary: z.string()). Values are OpenClaw's own catalog -// defaults so the active provider matches the supplied key: -// - anthropic alias "sonnet" -> "anthropic/claude-sonnet-4-6" (defaults.ts DEFAULT_MODEL_ALIASES) -// - openai "gpt" -> "openai/gpt-5.2" -// - google/gemini -> "google/gemini-3-pro-preview" -// - openrouter -> "openrouter/auto" (src/commands/onboard-auth.credentials.ts OPENROUTER_DEFAULT_MODEL_REF) -// If model.primary is omitted, OpenClaw falls back to DEFAULT_MODEL="claude-opus-4-6" -// (src/agents/defaults.ts) which is anthropic-only — wrong for non-anthropic keys — -// hence we always set it. -const PROVIDER_MAP = { - anthropic: { envVar: "ANTHROPIC_API_KEY", modelRef: "anthropic/claude-sonnet-4-6" }, - openai: { envVar: "OPENAI_API_KEY", modelRef: "openai/gpt-5.2" }, - gemini: { envVar: "GEMINI_API_KEY", modelRef: "google/gemini-3-pro-preview" }, - google: { envVar: "GEMINI_API_KEY", modelRef: "google/gemini-3-pro-preview" }, - openrouter: { envVar: "OPENROUTER_API_KEY", modelRef: "openrouter/auto" }, -}; - -export async function bootstrap() { - console.log("[bootstrap] Starting auto-configuration..."); - - // 1. Create persistent directories - for (const dir of [ - STATE_DIR, - WORKSPACE_DIR, - join(STATE_DIR, "config"), - join(WORKSPACE_DIR, "memory"), - join(WORKSPACE_DIR, "skills"), - ]) { - mkdirSync(dir, { recursive: true }); - } - - // 2. Sync workspace files from defaults (don't overwrite existing) - if (existsSync(WORKSPACE_DEFAULTS)) { - for (const file of readdirSync(WORKSPACE_DEFAULTS)) { - const dest = join(WORKSPACE_DIR, file); - if (!existsSync(dest)) { - copyFileSync(join(WORKSPACE_DEFAULTS, file), dest); - console.log(`[bootstrap] Synced ${file} to workspace`); - } - } - } - - // 3. Make the AI provider key visible to OpenClaw under the env var its runtime - // resolver expects. Our deploy passes the generic AI_API_KEY; OpenClaw looks for - // the provider-native var (ANTHROPIC_API_KEY, etc.). Set it on this process so it - // propagates to the gateway child (gateway.js spawns with `...process.env`). - // Source: src/agents/live-auth-keys.ts collectProviderApiKeys(). - applyProviderKeyEnv(); - - // 4. Register the nunchi_trading stdio MCP server with mcporter, and pin - // MCPORTER_CONFIG so the agent's `mcporter` calls resolve it. - writeMcporterConfig(); - - // 5. Generate a v2026.2.22-valid openclaw.json - const config = buildConfig(); - writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2)); - console.log("[bootstrap] Generated openclaw.json"); - - // 6. Auto-approve builder fee (best-effort) - if (process.env.HL_PRIVATE_KEY) { - try { - const mainnet = (process.env.HL_TESTNET || "true").toLowerCase() === "false"; - const args = mainnet ? ["builder", "approve", "--mainnet"] : ["builder", "approve"]; - execSync(`python3 -m cli.main ${args.join(" ")}`, { - timeout: 30000, - cwd: "/agent-cli", - stdio: "pipe", - }); - console.log("[bootstrap] Builder fee approval sent"); - } catch { - // best-effort - } - } - - console.log("[bootstrap] Configuration complete"); -} - -/** Resolve the deploy's AI_PROVIDER -> provider entry (defaults to anthropic). */ -function resolveProvider() { - const aiProvider = (process.env.AI_PROVIDER || "anthropic").toLowerCase(); - const providerInfo = PROVIDER_MAP[aiProvider]; - if (!providerInfo) { - // blockrun/x402/ClawRouter is NOT a provider in OpenClaw v2026.2.22 (no source - // support: grep of the pinned tree for "blockrun"/"x402"/"ClawRouter" is empty). - // Do not fabricate config for it; fall back to anthropic so the gateway still boots. - console.warn( - `[bootstrap] AI_PROVIDER="${aiProvider}" is not supported by OpenClaw v2026.2.22; ` + - "falling back to anthropic. (blockrun/x402 has no native OpenClaw provider.)", - ); - return { aiProvider: "anthropic", providerInfo: PROVIDER_MAP.anthropic }; - } - return { aiProvider, providerInfo }; -} - -/** Export the provider-native API-key env var (from generic AI_API_KEY) for the gateway. */ -function applyProviderKeyEnv() { - const { providerInfo } = resolveProvider(); - const aiKey = process.env.AI_API_KEY || ""; - if (!aiKey) { - console.warn("[bootstrap] AI_API_KEY not set; OpenClaw will have no provider credential"); - return; - } - // Only set if the provider-native var isn't already provided explicitly. - if (!process.env[providerInfo.envVar]?.trim()) { - process.env[providerInfo.envVar] = aiKey; - console.log(`[bootstrap] Exported ${providerInfo.envVar} for OpenClaw provider auth`); - } -} - -/** - * Write mcporter's config registering the nunchi_trading stdio server, and pin - * MCPORTER_CONFIG to it. mcporter config shape (mcporter@0.7.3 dist/config-schema.js - * RawConfigSchema): { mcpServers: Record }> }. `mcpServers` is required even if empty. - */ -function writeMcporterConfig() { - const isMainnet = (process.env.HL_TESTNET || "true").toLowerCase() === "false"; - const mcporterConfig = { - mcpServers: { - nunchi_trading: { - // stdio server: our trading CLI's MCP entrypoint. Array args avoid shell quoting. - command: "python3", - args: ["-m", "cli.main", "mcp", "serve"], - env: { - // Static env for the spawned server. mcporter supports ${VAR} interpolation, - // but we resolve at write-time from the deploy env for determinism. - HL_PRIVATE_KEY: process.env.HL_PRIVATE_KEY || "", - HL_TESTNET: isMainnet ? "false" : "true", - // The MCP server runs from the trading CLI package dir. - PYTHONPATH: "/agent-cli", - }, - }, - }, - }; - writeFileSync(MCPORTER_CONFIG_PATH, JSON.stringify(mcporterConfig, null, 2)); - // Pin for every downstream `mcporter` invocation (forwarded to the gateway child via - // `...process.env` in gateway.js). docs/config.md: MCPORTER_CONFIG => single-file mode. - process.env.MCPORTER_CONFIG = MCPORTER_CONFIG_PATH; - console.log(`[bootstrap] Registered nunchi_trading MCP server (${MCPORTER_CONFIG_PATH})`); -} - -/** - * Build a v2026.2.22-valid openclaw.json. - * - * Root keys used (all present in OpenClawSchema, src/config/zod-schema.ts): - * - gateway.controlUi.{allowInsecureAuth,dangerouslyDisableDeviceAuth} (zod-schema.ts:423-424) - * - agents.defaults.{maxConcurrent,subagents.maxConcurrent,workspace,model.primary} - * (zod-schema.agent-defaults.ts: AgentDefaultsSchema) - * - channels.telegram.{botToken,dmPolicy,allowFrom} (zod-schema.providers-core.ts) - * - * Removed dead keys (rejected by the strict schema) and where they went: - * deviceAuth -> gateway.controlUi.dangerouslyDisableDeviceAuth - * insecureAuth -> gateway.controlUi.allowInsecureAuth - * agentConcurrency -> agents.defaults.maxConcurrent - * subagentConcurrency -> agents.defaults.subagents.maxConcurrent - * provider, apiKey -> dropped (provider-native ENV var + agents.defaults.model.primary) - * mcpServers -> dropped (mcporter.json + MCPORTER_CONFIG, see writeMcporterConfig) - * workspaceDir -> agents.defaults.workspace - * stateDir -> dropped (OPENCLAW_STATE_DIR env, set by gateway.js) - */ -function buildConfig() { - const { providerInfo } = resolveProvider(); - - // Gateway port (deploy passes --port too via gateway.js; keep config consistent). - const gatewayPort = Number.parseInt(process.env.INTERNAL_GATEWAY_PORT || "18789", 10); - - const config = { - gateway: { - // mode=local is REQUIRED to start the gateway: src/cli/gateway-cli/run.ts:212 - // blocks start unless gateway.mode==="local" (or --allow-unconfigured). We set it - // here so the gateway boots from bootstrap config alone, independent of the - // (best-effort, may-fail) `openclaw onboard` step in onboard.js. - mode: "local", - // Loopback bind — the wrapper (server.js) reverse-proxies to it; not exposed directly. - bind: "loopback", - port: gatewayPort, - // Headless deployment: skip the Control UI device-identity + secure-context auth. - // dangerouslyDisableDeviceAuth is the flag that actually disables device-identity - // checks (src/security/audit.ts:405); allowInsecureAuth permits non-HTTPS control - // contexts. Both are under gateway.controlUi (OpenClawSchema gateway.controlUi). - controlUi: { - allowInsecureAuth: true, - dangerouslyDisableDeviceAuth: true, - }, - }, - - agents: { - defaults: { - // Concurrency (was agentConcurrency / subagentConcurrency). - // src/config/agent-limits.ts reads agents.defaults.maxConcurrent and - // agents.defaults.subagents.maxConcurrent. - maxConcurrent: 10, - subagents: { - maxConcurrent: 12, - }, - // Workspace (was workspaceDir). AgentDefaultsSchema.workspace: z.string(). - workspace: WORKSPACE_DIR, - // Provider/model selection. AgentDefaultsSchema.model.primary: z.string(). - model: { - primary: providerInfo.modelRef, - }, - }, - }, - }; - // NOTE on the mcporter skill: we intentionally write NO top-level `skills` config. - // The mcporter skill is BUNDLED with OpenClaw (/openclaw/skills/mcporter/SKILL.md) and - // bundled skills are auto-included by default — src/agents/skills/config.ts - // shouldIncludeSkill() only excludes a skill if skills.entries..enabled===false or - // a non-empty skills.allowBundled allowlist omits it. With `skills` unset, the mcporter - // skill loads automatically as long as its `mcporter` binary exists (it does: Dockerfile - // `npm install -g mcporter@0.7.3`), per the skill's `requires.bins:["mcporter"]` - // eligibility check. Adding `skills.allowBundled:["mcporter"]` would instead DISABLE every - // other bundled skill, so we leave it unset. - - // Telegram integration. Channel config lives under channels.telegram - // (TelegramConfigSchema, zod-schema.providers-core.ts): - // - botToken: z.string() (line 138) - // - allowFrom: (string|number)[] (NOT "allowedUsers") (line 142) - // - dmPolicy: "pairing"|"allowlist"|"open"|"disabled" (DmPolicySchema, default "pairing") - // - // IMPORTANT: Telegram authorization matches on NUMERIC sender/chat IDs, not @usernames - // (confirmed by OpenClaw's own doctor: "Telegram allowFrom contains non-numeric entries - // ...requires numeric sender IDs"). TELEGRAM_USERNAME is a @handle, which is NOT a valid - // allowFrom entry. So: - // - If TELEGRAM_USERNAME is numeric (a chat ID), allowlist it directly. - // - Otherwise leave dmPolicy at its schema default ("pairing") and omit allowFrom; the - // user authorizes via the Telegram pairing flow. (onboard.js separately resolves the - // @handle -> numeric chat ID via getUpdates and records it in USER.md.) - if (process.env.TELEGRAM_BOT_TOKEN) { - const telegram = { - botToken: process.env.TELEGRAM_BOT_TOKEN, - }; - const rawUser = (process.env.TELEGRAM_USERNAME || "").replace("@", "").trim(); - if (rawUser && /^\d+$/.test(rawUser)) { - // Numeric chat ID -> safe to allowlist. - telegram.dmPolicy = "allowlist"; - telegram.allowFrom = [Number(rawUser)]; - } - // else: dmPolicy defaults to "pairing" (omit allowFrom) — do not inject a non-numeric - // @username that would authorize nobody. - config.channels = { telegram }; - } - - return config; -} diff --git a/archive/deploy/openclaw-railway/src/gateway.js b/archive/deploy/openclaw-railway/src/gateway.js deleted file mode 100644 index 50cf266..0000000 --- a/archive/deploy/openclaw-railway/src/gateway.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Gateway lifecycle management — spawn, monitor, and restart the OpenClaw gateway. - */ -const { spawn } = require("child_process"); -const http = require("http"); - -const GATEWAY_HOST = process.env.INTERNAL_GATEWAY_HOST || "127.0.0.1"; -const GATEWAY_PORT = parseInt(process.env.INTERNAL_GATEWAY_PORT || "18789", 10); -const OPENCLAW_ENTRY = process.env.OPENCLAW_ENTRY || "/openclaw/dist/entry.js"; -const STATE_DIR = process.env.OPENCLAW_STATE_DIR || "/data/.openclaw"; - -let gatewayProcess = null; - -function startGateway() { - if (gatewayProcess && !gatewayProcess.killed) { - console.log("[gateway] Already running (pid=%d)", gatewayProcess.pid); - return; - } - - console.log("[gateway] Starting OpenClaw gateway..."); - - gatewayProcess = spawn("node", [OPENCLAW_ENTRY, "gateway", "--port", String(GATEWAY_PORT)], { - env: { - ...process.env, - OPENCLAW_STATE_DIR: STATE_DIR, - OPENCLAW_WORKSPACE_DIR: process.env.OPENCLAW_WORKSPACE_DIR || "/data/workspace", - NODE_ENV: "production", - }, - stdio: ["ignore", "pipe", "pipe"], - }); - - gatewayProcess.stdout.on("data", (data) => { - const line = data.toString().trim(); - if (line) console.log(`[gateway] ${line}`); - }); - - gatewayProcess.stderr.on("data", (data) => { - const line = data.toString().trim(); - // Redact tokens in logs - if (line) console.error(`[gateway] ${redactTokens(line)}`); - }); - - gatewayProcess.on("exit", (code, signal) => { - console.log(`[gateway] Exited (code=${code}, signal=${signal})`); - gatewayProcess = null; - }); - - console.log("[gateway] Spawned (pid=%d)", gatewayProcess.pid); -} - -async function waitForGatewayReady(timeoutMs = 30000) { - const start = Date.now(); - while (Date.now() - start < timeoutMs) { - try { - await httpGet(`http://${GATEWAY_HOST}:${GATEWAY_PORT}/health`); - return true; - } catch { - await sleep(500); - } - } - throw new Error(`Gateway did not become ready within ${timeoutMs}ms`); -} - -function getGatewayProcess() { - return gatewayProcess; -} - -function restartGateway() { - if (gatewayProcess && !gatewayProcess.killed) { - gatewayProcess.kill("SIGTERM"); - setTimeout(() => { - if (gatewayProcess && !gatewayProcess.killed) { - gatewayProcess.kill("SIGKILL"); - } - }, 5000); - } - setTimeout(() => startGateway(), 1500); -} - -// Helpers - -function httpGet(url) { - return new Promise((resolve, reject) => { - http.get(url, { timeout: 3000 }, (res) => { - let body = ""; - res.on("data", (chunk) => (body += chunk)); - res.on("end", () => resolve(body)); - }).on("error", reject); - }); -} - -function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -function redactTokens(str) { - return str.replace(/(?:sk-[a-zA-Z0-9-]{10,}|[a-f0-9]{64})/g, "[REDACTED]"); -} - -module.exports = { startGateway, waitForGatewayReady, getGatewayProcess, restartGateway }; diff --git a/archive/deploy/openclaw-railway/src/onboard.js b/archive/deploy/openclaw-railway/src/onboard.js deleted file mode 100644 index 552bfa7..0000000 --- a/archive/deploy/openclaw-railway/src/onboard.js +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Auto-onboard — configure OpenClaw with AI provider and Telegram on first deploy. - */ -const { execSync } = require("child_process"); -const { existsSync, readFileSync, writeFileSync } = require("fs"); -const { join } = require("path"); -const https = require("https"); - -const STATE_DIR = process.env.OPENCLAW_STATE_DIR || "/data/.openclaw"; -const FINGERPRINT_PATH = join(STATE_DIR, ".env-fingerprint"); - -async function autoOnboard() { - const aiProvider = process.env.AI_PROVIDER; - const aiKey = process.env.AI_API_KEY; - - if (!aiProvider || !aiKey) { - console.log("[onboard] No AI_PROVIDER or AI_API_KEY set, skipping auto-onboard"); - return; - } - - // Check if already configured with same env - const fingerprint = computeFingerprint(); - if (existsSync(FINGERPRINT_PATH)) { - const stored = readFileSync(FINGERPRINT_PATH, "utf-8").trim(); - if (stored === fingerprint) { - console.log("[onboard] Environment unchanged, skipping onboard"); - return; - } - } - - console.log("[onboard] Running auto-onboard..."); - - let telegramChatId = null; - - try { - // Run OpenClaw onboard - execSync("openclaw onboard --non-interactive --accept-risk", { - timeout: 60000, - stdio: "pipe", - env: { - ...process.env, - OPENCLAW_STATE_DIR: STATE_DIR, - }, - }); - console.log("[onboard] OpenClaw onboard complete"); - } catch (err) { - console.warn("[onboard] OpenClaw onboard failed (may already be configured):", err.message); - } - - // Resolve Telegram chat ID if username provided - if (process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_USERNAME) { - try { - telegramChatId = await resolveTelegramChatId( - process.env.TELEGRAM_BOT_TOKEN, - process.env.TELEGRAM_USERNAME, - ); - if (telegramChatId) { - console.log(`[onboard] Resolved Telegram chat ID: ${telegramChatId}`); - // Write USER.md with chat ID - const userMd = `# User\n\nTelegram chat ID: ${telegramChatId}\nUsername: ${process.env.TELEGRAM_USERNAME}\n`; - writeFileSync( - join(process.env.OPENCLAW_WORKSPACE_DIR || "/data/workspace", "USER.md"), - userMd, - ); - } - } catch (err) { - console.warn("[onboard] Could not resolve Telegram chat ID:", err.message); - } - } - - // Send ready message to Telegram - if (process.env.TELEGRAM_BOT_TOKEN) { - try { - await sendTelegramMessage( - process.env.TELEGRAM_BOT_TOKEN, - "Nunchi trading agent is ready. Say 'hl apex run' to start autonomous trading, or 'hl radar once' to scan for opportunities.", - telegramChatId, - ); - console.log("[onboard] Sent ready message to Telegram"); - } catch (err) { - console.warn("[onboard] Could not send Telegram message:", err.message); - } - } - - // Store fingerprint - writeFileSync(FINGERPRINT_PATH, fingerprint); - console.log("[onboard] Onboarding complete"); -} - -function computeFingerprint() { - const crypto = require("crypto"); - const parts = [ - process.env.AI_PROVIDER || "", - (process.env.AI_API_KEY || "").slice(-8), - process.env.TELEGRAM_BOT_TOKEN ? "tg" : "", - process.env.HL_TESTNET || "true", - ]; - return crypto.createHash("sha256").update(parts.join("|")).digest("hex").slice(0, 16); -} - -async function resolveTelegramChatId(botToken, username) { - const cleanUsername = username.replace("@", "").toLowerCase(); - const data = await fetchJson(`https://api.telegram.org/bot${botToken}/getUpdates?limit=50`); - if (!data.ok || !data.result) return null; - - for (const update of data.result) { - const msg = update.message || update.my_chat_member; - if (!msg || !msg.from) continue; - if ((msg.from.username || "").toLowerCase() === cleanUsername) { - return msg.chat.id; - } - } - return null; -} - -async function sendTelegramMessage(botToken, text, chatId = null) { - if (!chatId) { - // Try to find a chat to send to when no username-specific chat was resolved. - const data = await fetchJson(`https://api.telegram.org/bot${botToken}/getUpdates?limit=1`); - if (!data.ok || !data.result || data.result.length === 0) return; - chatId = data.result[0].message?.chat?.id; - } - if (!chatId) return; - - await fetchJson(`https://api.telegram.org/bot${botToken}/sendMessage`, { - method: "POST", - body: JSON.stringify({ chat_id: chatId, text }), - }); -} - -function fetchJson(url, opts = {}) { - return new Promise((resolve, reject) => { - const req = https.request(url, { - method: opts.method || "GET", - headers: opts.body ? { "Content-Type": "application/json" } : {}, - timeout: 10000, - }, (res) => { - let body = ""; - res.on("data", (chunk) => (body += chunk)); - res.on("end", () => { - try { - resolve(JSON.parse(body)); - } catch { - resolve({ ok: false }); - } - }); - }); - req.on("error", reject); - if (opts.body) req.write(opts.body); - req.end(); - }); -} - -module.exports = { autoOnboard }; diff --git a/archive/deploy/openclaw-railway/src/server.js b/archive/deploy/openclaw-railway/src/server.js deleted file mode 100644 index bb13b21..0000000 --- a/archive/deploy/openclaw-railway/src/server.js +++ /dev/null @@ -1,297 +0,0 @@ -/** - * Railway entrypoint — health check + reverse proxy to OpenClaw gateway. - * - * Flow: - * 1. Run bootstrap (auto-configure OpenClaw + MCP + Telegram) - * 2. Start OpenClaw gateway as child process - * 3. Serve health checks + proxy all other traffic to gateway - */ -const express = require("express"); -const crypto = require("crypto"); -const fs = require("fs"); -const path = require("path"); -const { execSync } = require("child_process"); -const httpProxy = require("http-proxy"); -const { bootstrap } = require("./bootstrap.mjs"); -const { startGateway, waitForGatewayReady, getGatewayProcess } = require("./gateway"); -const { autoOnboard } = require("./onboard"); -const { readStatus, readStrategies } = require("./status"); - -const app = express(); -const PORT = parseInt(process.env.PORT || "8080", 10); -const GATEWAY_HOST = process.env.INTERNAL_GATEWAY_HOST || "127.0.0.1"; -const GATEWAY_PORT = parseInt(process.env.INTERNAL_GATEWAY_PORT || "18789", 10); -const START_TIME = Date.now(); -const AGENT_CLI_DIR = "/agent-cli"; -const DATA_DIR = process.env.DATA_DIR || "/data"; -const CONTROL_API_TOKEN = process.env.CONTROL_API_TOKEN || ""; - -// Proxy to OpenClaw gateway -const proxy = httpProxy.createProxyServer({ - target: `http://${GATEWAY_HOST}:${GATEWAY_PORT}`, - ws: true, - changeOrigin: true, -}); - -proxy.on("error", (err, req, res) => { - if (res.writeHead) { - res.writeHead(502, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ error: "gateway_unavailable", message: err.message })); - } -}); - -// Health check -app.get("/health", (req, res) => { - const gw = getGatewayProcess(); - res.json({ - status: "ok", - uptime_s: Math.floor((Date.now() - START_TIME) / 1000), - gateway_alive: gw ? !gw.killed : false, - gateway_pid: gw ? gw.pid : null, - }); -}); - -// CORS middleware for /api/* routes -const CORS_ORIGIN = process.env.CORS_ORIGIN || "*"; -app.use("/api", (req, res, next) => { - res.header("Access-Control-Allow-Origin", CORS_ORIGIN); - res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-API-Token"); - if (req.method === "OPTIONS") return res.sendStatus(204); - next(); -}); - -function tokensEqual(a, b) { - const left = Buffer.from(a || ""); - const right = Buffer.from(b || ""); - return left.length === right.length && crypto.timingSafeEqual(left, right); -} - -function requireControlAuth(req, res, next) { - if (!CONTROL_API_TOKEN) { - return res.status(503).json({ - error: "control_auth_required", - message: "Set CONTROL_API_TOKEN to enable mutating control endpoints.", - }); - } - - const auth = req.get("authorization") || ""; - const bearer = auth.toLowerCase().startsWith("bearer ") ? auth.slice(7).trim() : ""; - const provided = bearer || req.get("x-api-token") || ""; - if (!tokensEqual(provided, CONTROL_API_TOKEN)) { - return res.status(401).json({ error: "unauthorized" }); - } - return next(); -} - -// Trading status (human-readable, calls hl CLI directly) -app.get("/status", async (req, res) => { - const { execSync } = require("child_process"); - try { - const output = execSync("python3 -m cli.main apex status", { - timeout: 10000, - encoding: "utf-8", - cwd: "/agent-cli", - }); - res.type("text/plain").send(output); - } catch (e) { - res.type("text/plain").send(e.stdout || e.stderr || e.message); - } -}); - -// API: Agent status (JSON, for UI) -app.get("/api/status", (req, res) => { - res.json(readStatus()); -}); - -// API: Strategy catalog -app.get("/api/strategies", (req, res) => { - res.json(readStrategies()); -}); - -// API: SSE feed — polls status every 2s -app.get("/api/feed", (req, res) => { - res.writeHead(200, { - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache", - "X-Accel-Buffering": "no", - Connection: "keep-alive", - }); - - let lastTick = -1; - const interval = setInterval(() => { - try { - const status = readStatus(); - const tick = status.tick_count || 0; - if (tick !== lastTick) { - lastTick = tick; - res.write(`data: ${JSON.stringify(status)}\n\n`); - } - } catch { - // ignore read errors - } - }, 2000); - - req.on("close", () => clearInterval(interval)); -}); - -// API: Install/update Nunchi trading skill -app.post("/api/skill/install", express.json(), (req, res) => { - const { execSync } = require("child_process"); - try { - // Verify agent-cli is available by checking strategies - const output = execSync("python3 -m cli.api.status_reader strategies", { - timeout: 10000, - encoding: "utf-8", - cwd: "/agent-cli", - }); - const data = JSON.parse(output.trim()); - const count = Object.keys(data.strategies || {}).length; - res.json({ installed: true, strategies: count, tools: 13 }); - } catch (e) { - res.status(500).json({ installed: false, error: e.message }); - } -}); - -// API: Pause agent -app.post("/api/pause", requireControlAuth, (req, res) => { - const gw = getGatewayProcess(); - if (gw && !gw.killed) { - try { - process.kill(gw.pid, "SIGSTOP"); - res.json({ status: "paused" }); - } catch (e) { - res.status(500).json({ error: e.message }); - } - } else { - res.status(409).json({ error: "No running agent to pause" }); - } -}); - -// API: Resume agent -app.post("/api/resume", requireControlAuth, (req, res) => { - const gw = getGatewayProcess(); - if (gw && !gw.killed) { - try { - process.kill(gw.pid, "SIGCONT"); - res.json({ status: "resumed" }); - } catch (e) { - res.status(500).json({ error: e.message }); - } - } else { - res.status(409).json({ error: "No paused agent to resume" }); - } -}); - -// API: Configure agent (write config override) -app.post("/api/configure", requireControlAuth, express.json(), (req, res) => { - const configPath = path.join(DATA_DIR, "apex", "config-override.json"); - try { - fs.mkdirSync(path.dirname(configPath), { recursive: true }); - fs.writeFileSync(configPath, JSON.stringify(req.body, null, 2)); - res.json({ status: "ok", applied_at: "next_tick" }); - } catch (e) { - res.status(500).json({ error: e.message }); - } -}); - -// API: Trade history -app.get("/api/trades", (req, res) => { - const limit = parseInt(req.query.limit || "50", 10); - try { - const output = execSync( - `python3 -m cli.api.status_reader trades --data-dir ${DATA_DIR} --limit ${limit}`, - { timeout: 10000, encoding: "utf-8", cwd: AGENT_CLI_DIR, stdio: ["pipe", "pipe", "pipe"] } - ); - res.json(JSON.parse(output.trim())); - } catch (err) { - res.status(500).json({ error: err.message }); - } -}); - -// API: REFLECT reports -app.get("/api/reflect", (req, res) => { - try { - const output = execSync( - `python3 -m cli.api.status_reader reflect --data-dir ${DATA_DIR}`, - { timeout: 10000, encoding: "utf-8", cwd: AGENT_CLI_DIR, stdio: ["pipe", "pipe", "pipe"] } - ); - res.json(JSON.parse(output.trim())); - } catch (err) { - res.status(500).json({ error: err.message }); - } -}); - -// API: RADAR (scanner) history -app.get("/api/scanner", (req, res) => { - try { - const output = execSync( - `python3 -m cli.api.status_reader radar --data-dir ${DATA_DIR}`, - { timeout: 10000, encoding: "utf-8", cwd: AGENT_CLI_DIR, stdio: ["pipe", "pipe", "pipe"] } - ); - res.json(JSON.parse(output.trim())); - } catch (err) { - res.status(500).json({ error: err.message }); - } -}); - -// API: Journal entries -app.get("/api/journal", (req, res) => { - const limit = parseInt(req.query.limit || "50", 10); - try { - const output = execSync( - `python3 -m cli.api.status_reader journal --data-dir ${DATA_DIR} --limit ${limit}`, - { timeout: 10000, encoding: "utf-8", cwd: AGENT_CLI_DIR, stdio: ["pipe", "pipe", "pipe"] } - ); - res.json(JSON.parse(output.trim())); - } catch (err) { - res.status(500).json({ error: err.message }); - } -}); - -// Everything else proxies to OpenClaw gateway -app.use((req, res) => { - proxy.web(req, res); -}); - -// WebSocket upgrade -const server = app.listen(PORT, async () => { - console.log(`[server] Listening on :${PORT}`); - - try { - // Step 1: Bootstrap (create dirs, sync workspace, generate configs) - await bootstrap(); - - // Step 2: Auto-onboard if credentials present - await autoOnboard(); - - // Step 3: Start OpenClaw gateway - startGateway(); - await waitForGatewayReady(); - console.log("[server] OpenClaw gateway is ready"); - } catch (err) { - console.error("[server] Startup error:", err.message); - // Keep server running for health checks even if gateway fails - } -}); - -server.on("upgrade", (req, socket, head) => { - proxy.ws(req, socket, head); -}); - -// Graceful shutdown -function shutdown(signal) { - console.log(`[server] ${signal} received, shutting down`); - const gw = getGatewayProcess(); - if (gw && !gw.killed) { - gw.kill("SIGTERM"); - setTimeout(() => { - if (!gw.killed) gw.kill("SIGKILL"); - }, 10000); - } - server.close(() => process.exit(0)); - setTimeout(() => process.exit(1), 15000); -} - -process.on("SIGTERM", () => shutdown("SIGTERM")); -process.on("SIGINT", () => shutdown("SIGINT")); diff --git a/archive/deploy/openclaw-railway/src/status.js b/archive/deploy/openclaw-railway/src/status.js deleted file mode 100644 index 1a920e9..0000000 --- a/archive/deploy/openclaw-railway/src/status.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Status reader — shells out to cli.api.status_reader for agent state. - */ -const { execSync } = require("child_process"); -const fs = require("fs"); -const path = require("path"); - -const AGENT_CLI_DIR = "/agent-cli"; -const DATA_DIR = process.env.DATA_DIR || "/data"; - -function readStatus() { - try { - const output = execSync( - `python3 -m cli.api.status_reader status --data-dir ${DATA_DIR}`, - { timeout: 10000, encoding: "utf-8", cwd: AGENT_CLI_DIR, stdio: ["pipe", "pipe", "pipe"] } - ); - return JSON.parse(output.trim()); - } catch (err) { - // Fallback: try reading apex state.json directly - const apexState = path.join(DATA_DIR, "apex", "state.json"); - if (fs.existsSync(apexState)) { - try { - const state = JSON.parse(fs.readFileSync(apexState, "utf-8")); - const active = (state.slots || []).filter((s) => s.status === "active"); - return { - status: "running", - engine: "apex", - tick_count: state.tick_count || 0, - daily_pnl: state.daily_pnl || 0, - total_pnl: state.total_pnl || 0, - active_slots: active, - positions: active.map((s) => ({ - slot: s.slot_id, - market: s.instrument || "", - side: s.side || "", - size: s.entry_size || 0, - entry: s.entry_price || 0, - roe: s.roe_pct || 0, - phase: s.dsl_phase || 0, - })), - }; - } catch { - // fall through - } - } - return { status: "stopped", error: err.message }; - } -} - -function readStrategies() { - try { - const output = execSync( - `python3 -m cli.api.status_reader strategies`, - { timeout: 10000, encoding: "utf-8", cwd: AGENT_CLI_DIR, stdio: ["pipe", "pipe", "pipe"] } - ); - return JSON.parse(output.trim()); - } catch (err) { - return { error: err.message }; - } -} - -module.exports = { readStatus, readStrategies }; diff --git a/archive/deploy/openclaw-railway/workspace/AGENTS.md b/archive/deploy/openclaw-railway/workspace/AGENTS.md deleted file mode 100644 index cfff725..0000000 --- a/archive/deploy/openclaw-railway/workspace/AGENTS.md +++ /dev/null @@ -1,47 +0,0 @@ -# Agent Operating Guidelines - -You are a Nunchi autonomous trading agent on Hyperliquid. You manage positions, scan for opportunities, and protect capital using the `hl` CLI and MCP tools. - -## Core Rules - -1. **Capital preservation first.** Never risk more than the configured daily loss limit. Always use DSL trailing stops on every position. -2. **Data-driven decisions only.** Never invent market data. If you don't have data, run `hl radar once` or `hl movers once` to get it. -3. **Report all actions.** When you enter or exit a position, tell the user via Telegram with: instrument, direction, size, price, and reason. -4. **Verify before trading.** Before any trade, run `hl account` to check balance and `hl status` to see existing positions. -5. **Run REFLECT after sessions.** After any trading session (or when asked), run `hl reflect run` to analyze performance and learn from mistakes. - -## Trading Workflow - -1. **Scan**: `hl radar once` — find the best setups across all HL perps -2. **Validate**: Check radar score (>170 = actionable), confirm direction aligns with BTC macro -3. **Enter**: `hl trade ` or let APEX handle it: `hl apex run` -4. **Monitor**: `hl status --watch` — track positions and PnL -5. **Exit**: DSL handles exits automatically, or manual: `hl trade ` -6. **Review**: `hl reflect run --since ` — analyze what worked and what didn't - -## APEX Autonomous Mode - -When the user says "start trading" or "run APEX": -```bash -hl apex run --preset default -``` - -APEX manages 2-3 concurrent positions automatically: -- Scans for opportunities every 15 ticks -- Detects emerging movers every tick -- Applies DSL trailing stops to every position -- Exits on conviction collapse, stagnation, or hard stops -- Auto-adjusts parameters based on REFLECT performance reviews - -## Safety - -- Never expose private keys, API keys, or tokens in messages -- Never run `git push`, `rm -rf`, or destructive shell commands -- If a trade fails, report the error and suggest next steps — don't retry blindly -- If daily loss limit is triggered, stop all trading and notify the user - -## Memory - -- Read `memory/session.md` on startup for context from previous sessions -- After each trading session, write a brief summary to `memory/session.md` -- Track winning/losing patterns to improve future decisions diff --git a/archive/deploy/openclaw-railway/workspace/BOOTSTRAP.md b/archive/deploy/openclaw-railway/workspace/BOOTSTRAP.md deleted file mode 100644 index 524b44e..0000000 --- a/archive/deploy/openclaw-railway/workspace/BOOTSTRAP.md +++ /dev/null @@ -1,39 +0,0 @@ -# Bootstrap - -Run these checks on every startup. Execute silently — only report errors or the final status. - -## Startup Sequence - -1. **Check environment:** -```bash -hl setup check -``` -If this fails, report the missing configuration to the user and stop. - -2. **Approve builder fee (idempotent):** -```bash -hl builder approve -``` - -3. **Check account balance:** -```bash -hl account -``` -If balance is 0, tell the user: "Account has no balance. Run `hl setup claim-usdyp` for testnet funds, or deposit USDC for mainnet." - -4. **Check existing positions:** -```bash -hl status -``` - -5. **Check APEX state (if exists):** -```bash -hl apex status -``` - -6. **Report ready:** -Send to user: "Agent ready. Balance: $X. Active positions: N. Say 'start trading' to begin APEX, or ask me to scan for opportunities." - -## On Failure - -If any check fails, report the specific error and suggest a fix. Do not start trading with a broken environment. diff --git a/archive/deploy/openclaw-railway/workspace/SOUL.md b/archive/deploy/openclaw-railway/workspace/SOUL.md deleted file mode 100644 index d79db94..0000000 --- a/archive/deploy/openclaw-railway/workspace/SOUL.md +++ /dev/null @@ -1,17 +0,0 @@ -# Soul - -You are a direct, data-driven trading partner. You don't sugarcoat losses or hype wins. You present facts, show the numbers, and let the user decide. - -## Values - -- **Accuracy over speed.** Wrong data is worse than no data. Always verify before acting. -- **Protect capital.** A trader who survives can always trade tomorrow. Never gamble. -- **Admit uncertainty.** If you don't know, say so. Markets are unpredictable — act accordingly. -- **Keep it brief.** Report results concisely. No fluff. Numbers speak louder than narratives. - -## Communication Style - -- Lead with the result: "Entered ETH-PERP long @ $2,450, size 0.5" — not a paragraph of analysis -- Use tables for multi-item data (positions, radar results, REFLECT metrics) -- Flag warnings prominently: "DAILY LOSS LIMIT at 80% — consider stopping" -- Celebrate wins briefly, analyze losses thoroughly diff --git a/archive/deploy/openclaw-railway/workspace/TOOLS.md b/archive/deploy/openclaw-railway/workspace/TOOLS.md deleted file mode 100644 index 49964cb..0000000 --- a/archive/deploy/openclaw-railway/workspace/TOOLS.md +++ /dev/null @@ -1,59 +0,0 @@ -# Tools - -## MCP Server: nunchi_trading - -The primary tool provider. Reach it through the **mcporter** skill — it is registered -as an mcporter stdio server (not a native gateway tool). Usage: - -```bash -mcporter list nunchi_trading --schema # list the server's tools + input schemas -mcporter call nunchi_trading.account # call a tool (no args) -mcporter call nunchi_trading.trade instrument=ETH-PERP side=buy size=0.1 -``` - -Exposes 24 trading tools via Model Context Protocol, including: - -- `account` — Show HL account state (balance, margin, positions) -- `status` — Current positions, PnL, and risk state -- `trade` — Place a single order (instrument, side, size) -- `run_strategy` — Start autonomous strategy trading -- `strategies` — List all 14 available strategies -- `radar_run` — Run opportunity radar across all HL perps -- `apex_status` — Show APEX orchestrator state -- `apex_run` — Start APEX autonomous multi-slot trading -- `reflect_run` — Run REFLECT performance review -- `setup_check` — Validate environment configuration -- `builder_status` — Check builder fee approval status -- `wallet_list` — List available wallets -- `wallet_auto` — Create wallet automatically -- `funding_rates` — Read current funding rates -- `funding_hedge_propose` — Build a CFI v2 funding hedge proposal -- `funding_hedge_backtest` — Run the reference funding hedge backtest -- `funding_hedge_execute` — Execute or dry-run a CFI v2 hedge; requires `confirmed=true` - -## CLI: hl - -All MCP tools are also available as CLI commands. Use the CLI for operations not exposed via MCP: - -```bash -hl apex run [--preset default|conservative|aggressive] [--mainnet] -hl radar once [--mock] -hl movers once [--mock] -hl dsl run -i ETH-PERP [--preset tight] -hl reflect run [--since DATE] -hl house join [--url URL] -``` - -## Shell - -Available: `python3`, `node`, `git`, `rg` (ripgrep), `curl` -Not available: `jq` (use `python3 -c "import json; ..."` instead) - -## Cron / Scheduling - -APEX has built-in scheduling: -- Daily PnL reset at UTC midnight -- REFLECT performance review every 4 hours -- Auto-parameter adjustment based on REFLECT findings - -For custom schedules, use the gateway's cron system. diff --git a/cli/api/status_reader.py b/cli/api/status_reader.py index fa017dc..93503bd 100644 --- a/cli/api/status_reader.py +++ b/cli/api/status_reader.py @@ -2,7 +2,6 @@ Shared utility used by: - scripts/entrypoint.py (imported directly) -- archive/deploy/openclaw-railway/src/server.js (via `python3 -m cli.api.status_reader`) """ from __future__ import annotations diff --git a/cli/hedge_display.py b/cli/hedge_display.py index 5e9e267..7a4966d 100644 --- a/cli/hedge_display.py +++ b/cli/hedge_display.py @@ -153,7 +153,7 @@ def hedge_proposal_block(proposal, snapshot, *, mainnet: bool = False) -> str: lines.extend([ "", f"{DIM}Execution: signs against {hedge.market} (HL HIP-3, asset index resolved " - f"by the HL Python SDK). 10 bps Nunchi builder fee. No hermes-api dep.{RESET}", + f"by the HL Python SDK). 10 bps Nunchi builder fee.{RESET}", ]) return "\n".join(lines) diff --git a/cli/skill.md b/cli/skill.md index 4311127..8048415 100644 --- a/cli/skill.md +++ b/cli/skill.md @@ -6,13 +6,13 @@ argument-hint: "hedge [coin] | run cfi_hedge" allowed-tools: - Bash metadata: - openclaw: - requires: - env: - - HL_PRIVATE_KEY - bins: - - python3 - primaryEnv: HL_PRIVATE_KEY + author: Nunchi Trade + requires: + env: + - HL_PRIVATE_KEY + bins: + - python3 + primaryEnv: HL_PRIVATE_KEY --- # YEX Trader — CFI funding-rate hedge diff --git a/cli/telemetry.py b/cli/telemetry.py index 47c0dc8..778e71f 100644 --- a/cli/telemetry.py +++ b/cli/telemetry.py @@ -42,10 +42,6 @@ def _get_version() -> str: def _detect_deploy_mode() -> str: if os.environ.get("RAILWAY_SERVICE_NAME"): return "railway" - if os.environ.get("OPENCLAW_STATE_DIR"): - return "openclaw" - if os.environ.get("HERMES_HOME"): - return "hermes" return "local" diff --git a/docs/RUNBOOK.md b/docs/RUNBOOK.md index 8d63b7f..5790eda 100644 --- a/docs/RUNBOOK.md +++ b/docs/RUNBOOK.md @@ -2,7 +2,7 @@ The default Railway deployment is the shared MCP/tools runtime used behind `mcp-gateway`. It should run `RUN_MODE=mcp` and expose tools only; it must not -run a per-user Hermes/OpenClaw/autonomous-agent loop for the subscription +run a per-user autonomous-agent loop for the subscription product. The APEX sections below apply only when an operator explicitly opts into self-hosted autonomous modes. @@ -134,7 +134,6 @@ railway logs | jq '.level, .message' - [ ] `RUN_MODE=mcp` for Nunchi-hosted shared tools runtime - [ ] `HL_TESTNET=true` unless the runner is explicitly approved for mainnet -- [ ] Do not deploy Hermes/OpenClaw as a Nunchi subscription product surface - [ ] Configure generic metering upload when reporting usage: `NUNCHI_METERING_URL`, `NUNCHI_METERING_TOKEN` - [ ] `API_AUTH_TOKEN` set for control endpoint security - [ ] Persistent volume mounted at `/data` diff --git a/docs/api-reference.md b/docs/api-reference.md index 9ae7dd3..b99c268 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -6,7 +6,7 @@ This guide covers every method for pulling data from a running Nunchi agent. Thr |------|----------|----------| | HTTP REST API | HTTP/JSON | Dashboards, monitoring, external integrations | | SSE Feed | Server-Sent Events | Live streaming to frontends | -| MCP Server | Model Context Protocol | AI agent orchestration (Claude, OpenClaw) | +| MCP Server | Model Context Protocol | AI agent orchestration (Claude Code, Cursor, Codex) | > **Security:** All endpoints are unauthenticated. If your agent is publicly accessible, anyone with the URL can read its state. Plan your network security accordingly. @@ -27,14 +27,14 @@ python scripts/entrypoint.py The HTTP server binds to `0.0.0.0:$PORT` (default 8080). -**Railway / OpenClaw (Node.js entrypoint):** +**Railway (shared MCP tools runtime):** ```bash -# Express server with reverse proxy to OpenClaw gateway -node src/server.js +# Python entrypoint — health server on $PORT + MCP SSE server +python scripts/entrypoint.py ``` -The Express server binds to `0.0.0.0:$PORT` (default 8080) and exposes the same API surface. +The HTTP server binds to `0.0.0.0:$PORT` (default 8080) and exposes the REST API surface. ### Base URL @@ -583,7 +583,7 @@ setInterval(() => fetchLeaderboard().then(renderTable), 30000); ## MCP Server -The MCP server exposes 21 tools for AI agent orchestration via the [Model Context Protocol](https://modelcontextprotocol.io). This is the access path for Claude Code, OpenClaw, or any MCP-compatible client. +The MCP server exposes 21 tools for AI agent orchestration via the [Model Context Protocol](https://modelcontextprotocol.io). This is the access path for Claude Code, Cursor, Codex, or any MCP-compatible client. ### Starting the Server diff --git a/tests/test_telemetry.py b/tests/test_telemetry.py index feff42a..406d8a7 100644 --- a/tests/test_telemetry.py +++ b/tests/test_telemetry.py @@ -104,10 +104,6 @@ def test_railway(self): with patch.dict(os.environ, {"RAILWAY_SERVICE_NAME": "agent-cli"}): assert _detect_deploy_mode() == "railway" - def test_openclaw(self): - with patch.dict(os.environ, {"OPENCLAW_STATE_DIR": "/data/.openclaw"}): - assert _detect_deploy_mode() == "openclaw" - class TestFactory: def test_create_telemetry_returns_client(self):