earn-hunter

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Earn Hunter

Earn Hunter

Automated monitor for OKX Flash Earn, Fixed Earn, and Flexible Earn (Simple Earn) opportunities.
{baseDir}
= the directory containing this SKILL.md file. All relative paths (references/, templates/, config/) are resolved from here.
OKX Flash Earn、Fixed Earn和Flexible Earn(Simple Earn)理财机会的自动化监控工具。
{baseDir}
= 包含本SKILL.md文件的目录。所有相对路径(references/、templates/、config/)均以此目录为基准解析。

Preflight

前置检查

  1. Verify
    okx
    CLI installed:
    which okx
    . If missing, install via
    npm install -g @okx_ai/okx-trade-cli
    . On OpenClaw, also verify the in-session
    cron
    tool is available in the agent tool list (used for scheduling — not the
    openclaw
    CLI).
  2. Check optional dependent skills:
    bash
    okx skill list --json
    Optional skills (not required for scanning/notifications):
    • okx-cex-earn
      — needed for purchase guide (subscription execution)
    • okx-cex-auth
      — needed for authentication recovery
    If either is missing, attempt to install but do not block if installation fails:
    bash
    okx skill add okx-cex-earn
    okx skill add okx-cex-auth
    • Install succeeds → continue
    • Install fails (network error, marketplace unavailable, etc.) → warn and continue: "⚠
      {skill_name}
      安装失败,扫描和通知功能不受影响。申购引导和认证恢复需要该 skill,后续可手动安装。"
    • Preflight continues regardless of skill installation result
  3. Auth mode detection — run both, first match wins:
    • okx config show --json
      → has non-empty
      api_key
      field → API Key mode. Add
      --profile live
      to all commands.
    • No API key +
      okx auth status --json
      "status":"logged_in"
      OAuth mode. No
      --profile
      flag needed.
    • Neither → stop. Load
      okx-cex-auth
      skill and follow login steps.
  4. Init config and state:
    • If
      ~/.okx/earn-hunter/
      directory does not exist →
      mkdir -p ~/.okx/earn-hunter
    • If
      ~/.okx/earn-hunter/config.json
      does not exist → copy
      {baseDir}/config/default.json
      to it
    • If
      ~/.okx/earn-hunter/state.json
      does not exist → write
      {"flash":{},"fixed":{},"flexible":{},"consecutive_failures":0,"last_error":""}
    • If
      ~/.okx/earn-hunter/platform.json
      does not exist → run Platform Detection
    • Always (re)install the scan script:
      cp {baseDir}/scripts/scan.sh ~/.okx/earn-hunter/scan.sh && chmod +x ~/.okx/earn-hunter/scan.sh
      . This is the script cron and interactive scans both call.
    • Write
      ~/.okx/earn-hunter/env.snapshot
      with resolved tool paths (cron cannot rely on the user's login PATH):
      bash
      cat > ~/.okx/earn-hunter/env.snapshot << SNAP
      # auto-generated by earn-hunter activation — $(date -Iseconds)
      OKX_BIN=$(command -v okx)
      NODE_BIN=$(command -v node)
      JQ_BIN=$(command -v jq)
      ACTIVATION_PATH=$PATH
      SNAP
      The scan script sources this file to resolve tool paths even under cron's minimal
      PATH=/usr/bin:/bin
      .
    Config/state/platform JSON read-write done by the agent is only for activation/config management. The recurring scan itself is performed entirely by
    scripts/scan.sh
    (shell + jq) —
    jq
    is required for scanning. Verify with
    which jq
    ; if missing, install (
    brew install jq
    /
    apt-get install jq
    ).
  1. 验证
    okx
    CLI是否已安装:执行
    which okx
    。若未安装,通过
    npm install -g @okx_ai/okx-trade-cli
    进行安装。在OpenClaw平台上,还需确认会话内的
    cron
    工具已在Agent工具列表中(用于任务调度——并非
    openclaw
    CLI)。
  2. 检查可选依赖技能:
    bash
    okx skill list --json
    可选技能(扫描/通知功能不需要这些技能):
    • okx-cex-earn
      — 申购引导(执行申购操作)所需
    • okx-cex-auth
      — 认证恢复所需
    若任一技能缺失,尝试安装但安装失败时不阻塞流程
    bash
    okx skill add okx-cex-earn
    okx skill add okx-cex-auth
    • 安装成功 → 继续执行
    • 安装失败(网络错误、市场不可用等)→ 发出警告并继续: "⚠
      {skill_name}
      安装失败,扫描和通知功能不受影响。申购引导和认证恢复需要该skill,后续可手动安装。"
    • 无论技能安装结果如何,前置检查流程继续执行
  3. 认证模式检测 — 依次执行以下两种检测,匹配到第一个即停止:
    • okx config show --json
      → 存在非空
      api_key
      字段 → API Key模式。所有命令需添加
      --profile live
      参数。
    • 无API密钥 +
      okx auth status --json
      → 返回
      "status":"logged_in"
      OAuth模式。无需添加
      --profile
      参数。
    • 两者均不匹配 → 停止流程。加载
      okx-cex-auth
      技能并按照登录步骤操作。
  4. 初始化配置与状态:
    • ~/.okx/earn-hunter/
      目录不存在 → 执行
      mkdir -p ~/.okx/earn-hunter
      创建
    • ~/.okx/earn-hunter/config.json
      不存在 → 将
      {baseDir}/config/default.json
      复制到该路径
    • ~/.okx/earn-hunter/state.json
      不存在 → 写入内容
      {"flash":{},"fixed":{},"flexible":{},"consecutive_failures":0,"last_error":""}
    • ~/.okx/earn-hunter/platform.json
      不存在 → 执行平台检测
    • 始终重新安装扫描脚本:
      cp {baseDir}/scripts/scan.sh ~/.okx/earn-hunter/scan.sh && chmod +x ~/.okx/earn-hunter/scan.sh
      。定时扫描和交互式扫描均会调用此脚本。
    • 将解析后的工具路径写入
      ~/.okx/earn-hunter/env.snapshot
      (cron无法依赖用户登录会话的PATH):
      bash
      cat > ~/.okx/earn-hunter/env.snapshot << SNAP
      # auto-generated by earn-hunter activation — $(date -Iseconds)
      OKX_BIN=$(command -v okx)
      NODE_BIN=$(command -v node)
      JQ_BIN=$(command -v jq)
      ACTIVATION_PATH=$PATH
      SNAP
      扫描脚本会加载此文件,即使在cron的最小
      PATH=/usr/bin:/bin
      环境下也能解析工具路径。
    Agent仅在激活/配置管理阶段读写配置/状态/平台JSON文件。定期扫描操作完全由
    scripts/scan.sh
    (shell + jq)执行
    — 扫描需要依赖
    jq
    工具。通过
    which jq
    验证是否安装;若未安装,执行安装命令(
    brew install jq
    /
    apt-get install jq
    )。

Platform & Channel Detection

平台与渠道检测

Three independent dimensions: platform (where the agent runs), scheduler (what triggers scans), notification channel (where alerts go).
包含三个独立维度:平台(Agent运行环境)、调度器(触发扫描的方式)、通知渠道(接收告警的途径)。

Platform Detection (active probe + user confirmation)

平台检测(主动探测+用户确认)

First run (no
platform.json
exists):
  1. Probe environment clues:
    • OPENCLAW_HOME
      env var exists? → hint: OpenClaw
    • Agent tool list contains
      cron
      /
      delivery
      tools? → hint: OpenClaw
    • HERMES_HOME
      env var exists or
      which hermes
      succeeds? → hint: Hermes Agent
    • Running inside Claude Code session? → hint: Claude Code
    • None of the above matched → hint: Generic
  2. Present detection result and ask user to confirm:
    • "检测到你正在使用 {detected_platform},是否正确?"
    • User confirms → proceed
    • User says no → ask: "你使用的是哪个平台?1) OpenClaw 2) Claude Code 3) Hermes Agent 4) 其他"
  3. Initialize platform config:
    • OpenClaw / Claude Code → copy
      {baseDir}/config/<confirmed_platform>.default.json
      to
      ~/.okx/earn-hunter/platform.json
    • Hermes Agent → copy
      {baseDir}/config/claude-code.default.json
      as base, set
      .platform
      to
      "hermes"
      ,
      .scheduler.type
      to
      "cron"
    • Generic → copy
      {baseDir}/config/claude-code.default.json
      as base, set
      .platform
      to
      "generic"
      ,
      .scheduler.type
      to
      "manual"
  4. Result written to
    ~/.okx/earn-hunter/platform.json
    , subsequent runs skip detection.
Subsequent runs (platform.json exists): Read
~/.okx/earn-hunter/platform.json
and extract the
.platform
field (returns
"openclaw"
,
"claude-code"
,
"hermes"
, or
"generic"
).
No scheduler available on detected platform (only applies to platforms that should have one but don't) → error: "当前客户端不支持定时任务,请升级到最新版本。" Generic platform → no automatic scheduler. Inform: "当前平台不支持自动调度,你可以手动说'执行 earn-hunter 扫描'来触发。"
首次运行(无
platform.json
文件):
  1. 探测环境线索:
    • 存在
      OPENCLAW_HOME
      环境变量?→ 提示:OpenClaw
    • Agent工具列表包含
      cron
      /
      delivery
      工具?→ 提示:OpenClaw
    • 存在
      HERMES_HOME
      环境变量或
      which hermes
      执行成功?→ 提示:Hermes Agent
    • 在Claude Code会话中运行?→ 提示:Claude Code
    • 以上均不匹配 → 提示:通用平台
  2. 展示检测结果并请求用户确认
    • "检测到你正在使用 {detected_platform},是否正确?"
    • 用户确认 → 继续执行
    • 用户否认 → 询问:"你使用的是哪个平台?1) OpenClaw 2) Claude Code 3) Hermes Agent 4) 其他"
  3. 初始化平台配置:
    • OpenClaw / Claude Code → 将
      {baseDir}/config/<confirmed_platform>.default.json
      复制到
      ~/.okx/earn-hunter/platform.json
    • Hermes Agent → 以
      {baseDir}/config/claude-code.default.json
      为基础,设置
      .platform
      "hermes"
      .scheduler.type
      "cron"
    • 通用平台 → 以
      {baseDir}/config/claude-code.default.json
      为基础,设置
      .platform
      "generic"
      .scheduler.type
      "manual"
  4. 将结果写入
    ~/.okx/earn-hunter/platform.json
    ,后续运行跳过检测步骤。
后续运行(已存在platform.json): 读取
~/.okx/earn-hunter/platform.json
并提取
.platform
字段(返回值为
"openclaw"
"claude-code"
"hermes"
"generic"
)。
检测到的平台不支持调度器(仅适用于原本应支持但实际不支持的平台)→ 错误提示:"当前客户端不支持定时任务,请升级到最新版本。" 通用平台 → 不支持自动调度。提示:"当前平台不支持自动调度,你可以手动说'执行 earn-hunter 扫描'来触发。"

Configuration Files

配置文件

FileScopeContent
config.json
SharedScan scope (flash/fixed/flexible), currencies, APY thresholds, terms, language, verboseLog
platform.json
Platform-specificScheduler type/interval, notification channel, TG/Lark credentials
state.json
SharedDedup state
Core config (
config.json
) is identical across platforms. Platform config (
platform.json
) differs — the
scheduler.type
field determines how scans are triggered:
OpenClaw (
openclaw.default.json
):
  • scheduler.type =
    "openclaw-cron"
    — scheduled via the in-session
    cron
    agent tool
    (no OS crontab, no CLI commands). The job runs as an isolated, light-context agent turn and delivers its output back to the conversation channel via cron
    announce
    delivery. notify.channel defaults to
    "session"
    so the scan prints to stdout for
    announce
    to push (avoids double-send).
Claude Code / Hermes / Generic (
claude-code.default.json
):
  • scheduler.type =
    "cron"
    — scheduled via OS crontab →
    scripts/scan.sh
    (zero LLM token cost), notification via TG / Lark curl from the script itself.
文件作用范围内容
config.json
通用配置扫描范围(flash/fixed/flexible)、监控币种、APY阈值、期限、语言、verboseLog
platform.json
平台专属配置调度器类型/间隔、通知渠道、TG/Lark凭证
state.json
通用配置去重状态
核心配置(
config.json
)在所有平台上保持一致。平台配置(
platform.json
)存在差异 —
scheduler.type
字段决定扫描触发方式:
OpenClaw(
openclaw.default.json
):
  • scheduler.type =
    "openclaw-cron"
    — 通过会话内的**
    cron
    Agent工具进行调度(无需系统crontab或CLI命令)。任务以独立轻量上下文的Agent轮次运行,并通过cron的
    announce
    **交付机制将输出返回至对话渠道。notify.channel默认设置为
    "session"
    ,因此扫描结果会打印到标准输出,由
    announce
    推送(避免重复发送)。
Claude Code / Hermes / 通用平台(
claude-code.default.json
):
  • scheduler.type =
    "cron"
    — 通过**系统crontab →
    scripts/scan.sh
    **进行调度(零LLM令牌成本),通知通过脚本直接调用TG / Lark的curl接口发送。

Notification Channels (independent of platform)

通知渠道(与平台无关)

Detect in priority order (PRD requirement: TG first):
  1. Telegram
    $TELEGRAM_BOT_TOKEN
    and
    $TELEGRAM_CHAT_ID
    both set → TG ready
  2. Lark
    platform.notify.lark_webhook
    non-empty → Lark ready
  3. Session — fallback, only works in interactive mode
TG and Lark are standalone push channels — they work regardless of whether the agent client is open. On OS-crontab platforms, scheduled scans send notifications via direct curl. On OpenClaw, the scheduled scan runs in an isolated cron agent turn and delivers via cron
announce
to the conversation channel (channel =
"session"
); TG/Lark curl is not used unless the user explicitly switches the channel.

按优先级检测(产品需求:优先Telegram):
  1. Telegram
    $TELEGRAM_BOT_TOKEN
    $TELEGRAM_CHAT_ID
    均已设置 → TG可用
  2. Lark
    platform.notify.lark_webhook
    非空 → Lark可用
  3. 会话 — 兜底选项,仅在交互模式下生效
TG和Lark是独立推送渠道 — 无论Agent客户端是否打开均可正常工作。在系统crontab平台上,定时扫描通过直接调用curl发送通知;在OpenClaw平台上,定时扫描在独立的cron Agent轮次中运行,并通过cron的**
announce
**交付机制推送至对话渠道(渠道为
"session"
);除非用户明确切换渠道,否则不会使用TG/Lark的curl接口。

Skill Routing

技能路由

User intentRoute
"有闪赚通知我" / "monitor earn" / "帮我监控赚币" / "活期年化高了通知我"Activation Flow
"改 APY 阈值" / "只看 USDT" / "change config" / "活期加上 BTC"Config Management
"申购 USDT 定期 7D" / "subscribe" / "我要买"Purchase Guide
"执行 earn-hunter 扫描" (cron OR interactive)Scan Cycle — run
scripts/scan.sh
and relay its output
"停止监控" / "暂停" / "stop"Pause/Resume
"卸载 earn-hunter" / "uninstall"Uninstall
"测试 earn-hunter" / "smoke test" / "测试定时任务"Test Mode

用户意图路由方向
"有闪赚通知我" / "monitor earn" / "帮我监控赚币" / "活期年化高了通知我"激活流程
"改APY阈值" / "只看USDT" / "change config" / "活期加上BTC"配置管理
"申购USDT定期7D" / "subscribe" / "我要买"申购引导
"执行earn-hunter扫描"(定时或交互式)扫描周期 — 运行
scripts/scan.sh
并转发输出结果
"停止监控" / "暂停" / "stop"暂停/恢复
"卸载earn-hunter" / "uninstall"卸载
"测试earn-hunter" / "smoke test" / "测试定时任务"测试模式

Activation Flow

激活流程

First-time setup. Only confirm platform — everything else uses smart defaults.
首次设置流程。仅需确认平台信息 — 其余均使用智能默认值。

Step 1 — Platform Detection & Confirmation

步骤1 — 平台检测与确认

See Platform Detection. Probe environment → ask user to confirm → write
platform.json
.
详见平台检测。探测环境 → 请求用户确认 → 写入
platform.json

Step 2 — Detect Notification Channel & Confirm

步骤2 — 检测通知渠道并确认

Must actively check available channels before proceeding. Do NOT silently fall back to session.
On OS-crontab platforms, scheduled notifications go out via direct curl; on OpenClaw they go out via cron
announce
to the conversation. Detection order (check each, report status for all):
  1. Check
    $TELEGRAM_BOT_TOKEN
    and
    $TELEGRAM_CHAT_ID
    env vars:
    • Both set → TG ready
    • Token set but chat_id missing → warn: "Telegram 配置不完整(缺少 TELEGRAM_CHAT_ID),跳过 TG" → continue to next channel
    • Neither set → TG not available
  2. Check
    platform.notify.lark_webhook
    or Lark MCP tools:
    • Webhook set and valid (starts with
      https://
      and contains
      /hook/
      ) → Lark ready
    • Webhook set but format invalid (does not start with
      https://
      or missing
      /hook/
      ) → warn: "Lark webhook 格式无效,跳过 Lark" → continue to next channel
    • Not configured and no Lark MCP → Lark not available
Always ask the user to confirm notification channel — never silently default to session. For a monitoring tool, notification is critical; defaulting to session means alerts are lost when the user is not in the conversation.
If one or more external channels detected:
"检测到以下推送渠道可用:
  • {list of detected channels, e.g. Telegram / Lark}
你希望通知发到哪里?
  1. {detected channel 1}
  2. {detected channel 2, if any}
  3. 仅在当前会话显示(离线收不到)"
If no external channel detected:
"新机会才能推送到你手上。你希望通知发到哪里?
  1. Telegram — 需要提供 Bot Token 和 Chat ID(通过环境变量)
  2. Lark/飞书 — 需要提供 Webhook URL
  3. 仅在当前会话显示(⚠ 离线收不到通知)
推荐配置 Telegram 或 Lark,这样即使不在对话中也能收到提醒。"
  • If user picks Telegram → guide setting
    TELEGRAM_BOT_TOKEN
    and
    TELEGRAM_CHAT_ID
    env vars
  • If user picks Lark → ask for webhook URL, validate format (starts with
    https://
    , contains
    /hook/
    ), write to
    platform.notify.lark_webhook
  • If user picks session → write
    "session"
    and warn: "⚠ 离线状态下不会收到通知,建议后续配置外部渠道。"
Write confirmed channel to
platform.json
notify.channel
.
在继续流程前必须主动检查可用渠道。请勿默认使用会话渠道。
在系统crontab平台上,定时通知通过直接调用curl发送;在OpenClaw平台上,通知通过cron的
announce
机制推送至对话。按以下顺序检测(检查每个渠道并报告状态):
  1. 检查
    $TELEGRAM_BOT_TOKEN
    $TELEGRAM_CHAT_ID
    环境变量:
    • 两者均已设置 → TG可用
    • 仅Token已设置但缺少chat_id → 警告:"Telegram配置不完整(缺少TELEGRAM_CHAT_ID),跳过TG" → 继续检测下一个渠道
    • 两者均未设置 → TG不可用
  2. 检查
    platform.notify.lark_webhook
    或Lark MCP工具:
    • Webhook已设置且格式有效(以
      https://
      开头且包含
      /hook/
      )→ Lark可用
    • Webhook已设置但格式无效(不以
      https://
      开头或缺少
      /hook/
      )→ 警告:"Lark webhook格式无效,跳过Lark" → 继续检测下一个渠道
    • 未配置且无Lark MCP → Lark不可用
**始终请求用户确认通知渠道 — 请勿默认使用会话渠道。**对于监控工具而言,通知功能至关重要;默认使用会话渠道意味着用户不在对话中时会错过告警。
若检测到一个或多个外部渠道:
"检测到以下推送渠道可用:
  • {已检测渠道列表,例如Telegram / Lark}
你希望通知发到哪里?
  1. {已检测渠道1}
  2. {已检测渠道2(若存在)}
  3. 仅在当前会话显示(离线收不到)"
若未检测到外部渠道:
"新机会才能推送到你手上。你希望通知发到哪里?
  1. Telegram — 需要提供Bot Token和Chat ID(通过环境变量)
  2. Lark/飞书 — 需要提供Webhook URL
  3. 仅在当前会话显示(⚠ 离线收不到通知)
推荐配置Telegram或Lark,这样即使不在对话中也能收到提醒。"
  • 用户选择Telegram → 引导设置
    TELEGRAM_BOT_TOKEN
    TELEGRAM_CHAT_ID
    环境变量
  • 用户选择Lark → 请求提供webhook URL,验证格式(以
    https://
    开头且包含
    /hook/
    ),写入
    platform.notify.lark_webhook
  • 用户选择会话 → 写入
    "session"
    并警告:"⚠ 离线状态下不会收到通知,建议后续配置外部渠道。"
将确认后的渠道写入
platform.json
notify.channel
字段。

Step 3 — Confirm Scan Config (3-step with defaults)

步骤3 — 确认扫描配置(三步式,含默认值)

Present default config and ask user to confirm or customize. Each step offers a default — user can press enter to accept.
Step 1/3 — 扫描范围: "扫描范围(可多选): [1] Flash Earn(闪赚) [2] Fixed Earn(定期赚币) [3] Flexible Earn(活期赚币) 默认:全选"
  • Default: all three enabled
  • If user picks specific items → disable the others
  • If user only picks [1] → set
    config.fixed.enabled = false
    ,
    config.flexible.enabled = false
    ; skip Step 2/3 and 3/3
  • If user only picks [3] → set
    config.flash.enabled = false
    ,
    config.fixed.enabled = false
    ; go to flexible-specific config (Step 2/3 asks flexible currencies, Step 3/3 asks flexible APY threshold)
Step 2/3 — 监控币种: For Fixed Earn: "定期监控币种:全部(默认,按回车)或输入指定币种(如 USDT, SOL)"
  • Default:
    "all"
    (all currencies)
  • If user specifies → set
    config.currencies
    to array (e.g.
    ["USDT", "SOL"]
    )
For Flexible Earn: "活期监控币种:USDT, USDC(默认,按回车)或输入指定币种"
  • Default:
    ["USDT", "USDC"]
  • If user specifies → set
    config.flexible.currencies
    to array
  • Note: flexible requires per-currency API calls, so recommend keeping the list small
Step 3/3 — APY 阈值: For Fixed Earn: "定期最低 APY 阈值:不限(默认,按回车)或输入百分比(如 8)"
  • Default:
    0
    (no limit)
  • If user specifies → set
    config.fixed.globalMinApy
    to
    value / 100
    (e.g. 8 →
    0.08
    )
For Flexible Earn: "活期最低 APY 阈值:8%(默认,按回车)或输入百分比"
  • Default:
    0.08
    (8%)
  • If user specifies → set
    config.flexible.globalMinApy
    to
    value / 100
Auto-detect language from conversation and write to
config.json
notify.language
.
Write config to
~/.okx/earn-hunter/config.json
.
Display summary using
{baseDir}/templates/activation.md
template (in user's language).
展示默认配置并请求用户确认或自定义。每一步均提供默认值 — 用户可按回车接受。
步骤1/3 — 扫描范围: "扫描范围(可多选): [1] Flash Earn(闪赚) [2] Fixed Earn(定期赚币) [3] Flexible Earn(活期赚币) 默认:全选"
  • 默认值:全部启用
  • 用户选择特定选项 → 禁用未选中的选项
  • 用户仅选择[1] → 设置
    config.fixed.enabled = false
    config.flexible.enabled = false
    ;跳过步骤2/3和3/3
  • 用户仅选择[3] → 设置
    config.flash.enabled = false
    config.fixed.enabled = false
    ;进入活期专属配置(步骤2/3询问活期监控币种,步骤3/3询问活期APY阈值)
步骤2/3 — 监控币种: 针对Fixed Earn:"定期监控币种:全部(默认,按回车)或输入指定币种(如USDT, SOL)"
  • 默认值:
    "all"
    (所有币种)
  • 用户指定币种 → 将
    config.currencies
    设置为数组(例如
    ["USDT", "SOL"]
针对Flexible Earn:"活期监控币种:USDT, USDC(默认,按回车)或输入指定币种"
  • 默认值:
    ["USDT", "USDC"]
  • 用户指定币种 → 将
    config.flexible.currencies
    设置为数组
  • 注意:活期监控需要按币种调用API,因此建议保持监控币种列表精简
步骤3/3 — APY阈值: 针对Fixed Earn:"定期最低APY阈值:不限(默认,按回车)或输入百分比(如8)"
  • 默认值:
    0
    (无限制)
  • 用户指定阈值 → 将
    config.fixed.globalMinApy
    设置为
    value / 100
    (例如8 →
    0.08
针对Flexible Earn:"活期最低APY阈值:8%(默认,按回车)或输入百分比"
  • 默认值:
    0.08
    (8%)
  • 用户指定阈值 → 将
    config.flexible.globalMinApy
    设置为
    value / 100
自动从对话中检测语言并写入
config.json
notify.language
字段。
将配置写入
~/.okx/earn-hunter/config.json
使用
{baseDir}/templates/activation.md
模板(用户语言版本)展示配置摘要。

Step 4 — Smoke Test & Delivery Confirmation

步骤4 — 冒烟测试与交付确认

Smoke test always sends a notification, regardless of
verboseLog
setting.
  1. Run one scan cycle immediately with
    verboseLog
    forced on so output is always produced, even when there are no opportunities. Temporarily flip
    verboseLog
    , run the script, then restore it — and use
    EH_TEST_NAMESPACE=1
    so smoke-test dedup keys go under the
    test:
    prefix and don't pollute production state:
    bash
    jq '.verboseLog=true' ~/.okx/earn-hunter/config.json > ~/.okx/earn-hunter/config.tmp \
      && mv ~/.okx/earn-hunter/config.tmp ~/.okx/earn-hunter/config.json
    EH_TEST_NAMESPACE=1 OKX_PROFILE=live ~/.okx/earn-hunter/scan.sh
    # then restore verboseLog to the user's original value (e.g. false)
  2. If new opportunities found → the script sends the normal notification (rendered from templates)
  3. If no opportunities found → with
    verboseLog
    forced on, the script sends the brief status. Optionally append the activation confirmation message: "Earn Hunter 已激活,当前暂无新机会,将在下一轮自动扫描。" (Use
    {baseDir}/templates/activation.md
    as base, append the no-opportunity note)
  4. TG or Lark channel → ask: "已向 {channel} 发送测试消息,请确认是否收到?"
  5. User confirms → proceed to Step 5
  6. Not received → troubleshoot (see
    notify-channels.md
    )
  7. 5 min no response → ping once
  8. Session channel → skip confirmation
Note: The smoke test ignores
verboseLog
setting — it always produces output to verify the full pipeline works end-to-end.
无论
verboseLog
设置如何,冒烟测试始终会发送通知。
  1. 立即运行一次完整扫描周期,强制开启
    verboseLog
    ,确保即使没有新机会也会生成输出。临时修改
    verboseLog
    设置,运行脚本后恢复原设置 — 并使用
    EH_TEST_NAMESPACE=1
    ,使冒烟测试的去重键以
    test:
    为前缀,避免污染生产状态:
    bash
    jq '.verboseLog=true' ~/.okx/earn-hunter/config.json > ~/.okx/earn-hunter/config.tmp \
      && mv ~/.okx/earn-hunter/config.tmp ~/.okx/earn-hunter/config.json
    EH_TEST_NAMESPACE=1 OKX_PROFILE=live ~/.okx/earn-hunter/scan.sh
    # 之后将verboseLog恢复为用户原设置(例如false)
  2. 若发现新机会 → 脚本发送常规通知(从模板渲染)
  3. 若未发现新机会 → 由于强制开启
    verboseLog
    ,脚本会发送简短状态信息。可选择性追加激活确认消息: "Earn Hunter已激活,当前暂无新机会,将在下一轮自动扫描。" (以
    {baseDir}/templates/activation.md
    为基础,追加无机会提示)
  4. TG或Lark渠道 → 询问: "已向{channel}发送测试消息,请确认是否收到?"
  5. 用户确认 → 继续执行步骤5
  6. 未收到消息 → 排查问题(详见
    notify-channels.md
  7. 5分钟无响应 → 再次提醒
  8. 会话渠道 → 跳过确认步骤
注意: 冒烟测试会忽略
verboseLog
设置 — 始终生成输出以验证端到端流程是否正常工作。

Step 4b — Cron Environment Smoke Test (OS-crontab platforms only)

步骤4b — Cron环境冒烟测试(仅适用于系统crontab平台)

Critical: The user's interactive shell has a full PATH, but cron does not. Run a second smoke test simulating cron's minimal environment to verify
env.snapshot
works:
bash
env -i HOME="$HOME" PATH=/usr/bin:/bin \
  EH_TEST_NAMESPACE=1 OKX_PROFILE=live \
  bash ~/.okx/earn-hunter/scan.sh
  • If exit 0 → cron will work. Proceed to Step 5.
  • If exit 127 / "FATAL: 'okx' not found" →
    env.snapshot
    is incomplete or missing. Re-run Preflight step to regenerate it. Do NOT proceed to Step 5 — the cron job will fail silently.
  • Show the user: "已验证 cron 最小环境下可正常执行。如果此步失败,说明 env.snapshot 中的工具路径有误。"
关键: 用户的交互shell包含完整PATH,但cron环境不包含。运行第二次冒烟测试模拟cron的最小环境,验证
env.snapshot
是否有效:
bash
env -i HOME="$HOME" PATH=/usr/bin:/bin \
  EH_TEST_NAMESPACE=1 OKX_PROFILE=live \
  bash ~/.okx/earn-hunter/scan.sh
  • 若退出码为0 → cron可正常工作。继续执行步骤5。
  • 若退出码为127 / "FATAL: 'okx' not found" →
    env.snapshot
    不完整或缺失。重新运行前置检查步骤生成该文件。请勿继续执行步骤5 — cron任务会静默失败。
  • 告知用户:"已验证cron最小环境下可正常执行。如果此步失败,说明env.snapshot中的工具路径有误。"

Step 5 — Set Up Scheduler

步骤5 — 设置调度器

The scheduling mechanism depends on
platform.json
.scheduler.type
. Branch on the platform.
调度机制取决于
platform.json
.scheduler.type
字段。根据平台分支处理。

OpenClaw (
scheduler.type = "openclaw-cron"
)

OpenClaw(
scheduler.type = "openclaw-cron"

On OpenClaw, scheduling is done inside the conversation by calling the in-session
cron
agent tool
— never an OS command or
openclaw cron
CLI (the CLI path has permission issues in this context). Encourage the user to set it up right here in the chat: the cron job you create inherits the current session's channel, so its scan output is delivered straight back to this conversation.
Call the
cron
tool with
action: "add"
and a
job
shaped like this (read
.scheduler.interval
from
platform.json
for the frequency):
  • name
    :
    "earn-hunter-hourly"
  • schedule
    :
    { "kind": "every", "everyMs": 3600000 }
    — derive
    everyMs
    from
    scheduler.interval
    (
    "1h"
    → 3600000,
    "30m"
    → 1800000,
    "2h"
    → 7200000)
  • sessionTarget
    :
    "isolated"
    — run in an isolated session, not the main one
  • payload
    :
    { "kind": "agentTurn", "message": "执行 earn-hunter 扫描", "lightContext": true }
    • lightContext: true
      runs the turn with a lightweight bootstrap context (skips workspace bootstrap files) → lower token cost per tick.
    • Token budget: OpenClaw cron jobs have no per-job tool-whitelist field (the old
      --tools exec,read,write
      flag no longer exists). The scan stays cheap because it runs the
      okx
      CLI through
      exec
      and does not depend on the 160+ okx MCP tools — so as long as the isolated cron agent isn't configured to load the okx MCP server, only the regular tools (exec/read/write) are in play. Which tools load is governed by the agent's config, not by this job.
  • delivery
    :
    { "mode": "announce" }
    — pushes the turn's output back to the conversation channel that created the job.
When it fires, the isolated agent runs the prompt
"执行 earn-hunter 扫描"
Scan Cycle (which runs
scripts/scan.sh
with channel
session
/stdout) → relays the result →
announce
delivers it here. Any new opportunity is therefore sent automatically.
Do NOT emit any shell/
openclaw cron
CLI command in the conversation — drive scheduling only through the
cron
tool.
在OpenClaw平台上,调度通过对话内调用会话中的
cron
Agent工具
完成 — 切勿使用系统命令或
openclaw cron
CLI(此场景下CLI路径存在权限问题)。建议用户直接在聊天中设置:创建的cron任务会继承当前会话的渠道,因此扫描输出会直接返回至本对话。
调用
cron
工具,设置
action: "add"
,并传入如下格式的
job
(从
platform.json
.scheduler.interval
读取调度频率):
  • name
    :
    "earn-hunter-hourly"
  • schedule
    :
    { "kind": "every", "everyMs": 3600000 }
    — 根据
    scheduler.interval
    转换为
    everyMs
    "1h"
    →3600000,
    "30m"
    →1800000,
    "2h"
    →7200000)
  • sessionTarget
    :
    "isolated"
    — 在独立会话中运行,而非主会话
  • payload
    :
    { "kind": "agentTurn", "message": "执行 earn-hunter 扫描", "lightContext": true }
    • lightContext: true
      以轻量引导上下文运行轮次(跳过工作区引导文件)→ 每次轮次的令牌成本更低。
    • 令牌预算: OpenClaw cron任务无单任务工具白名单字段(旧的
      --tools exec,read,write
      标志已不再存在)。扫描成本较低,因为它通过
      exec
      调用
      okx
      CLI,且不依赖160+个okx MCP工具 — 因此只要独立cron Agent未配置加载okx MCP服务器,就仅会使用常规工具(exec/read/write)。加载哪些工具由Agent配置决定,而非此任务。
  • delivery
    :
    { "mode": "announce" }
    — 将轮次输出推送至创建任务的对话渠道。
任务触发时,独立Agent会执行指令
"执行 earn-hunter 扫描"
扫描周期(运行
scripts/scan.sh
,渠道为
session
/标准输出)→ 转发结果 →
announce
机制将结果推送至本对话。因此新机会会自动发送。
请勿在对话中输出任何shell/
openclaw cron
CLI命令 — 仅通过
cron
工具进行调度。

OS-crontab platforms (
scheduler.type = "cron"
or
"launchagent"
— Claude Code / Hermes / Generic)

系统crontab平台(
scheduler.type = "cron"
"launchagent"
— Claude Code / Hermes / 通用平台)

These use OS scheduler →
scripts/scan.sh
. The script does everything (CLI calls, filter, dedup, render, curl notifications) with zero LLM cost.
Install the script — copy the skill's
scripts/scan.sh
into the state dir so the scheduler has a stable path:
bash
mkdir -p ~/.okx/earn-hunter
cp {baseDir}/scripts/scan.sh ~/.okx/earn-hunter/scan.sh
chmod +x ~/.okx/earn-hunter/scan.sh
Set up scheduler — try crontab first, fallback to LaunchAgent on macOS if cron daemon is not running:
bash
undefined
这些平台使用系统调度器 →
scripts/scan.sh
。脚本会完成所有操作(CLI调用、过滤、去重、渲染、curl发送通知),且零LLM成本
安装脚本 — 将技能的
scripts/scan.sh
复制到状态目录,确保调度器有稳定的路径:
bash
mkdir -p ~/.okx/earn-hunter
cp {baseDir}/scripts/scan.sh ~/.okx/earn-hunter/scan.sh
chmod +x ~/.okx/earn-hunter/scan.sh
设置调度器 — 优先尝试crontab,若macOS的cron守护进程未运行则回退到LaunchAgent:
bash
undefined

Resolve tool directories from the current shell

从当前shell解析工具目录

NODE_DIR=$(dirname "$(command -v node)") OKX_DIR=$(dirname "$(command -v okx)") JQ_DIR=$(dirname "$(command -v jq)") CRON_PATH=$(printf '%s\n' "$NODE_DIR" "$OKX_DIR" "$JQ_DIR" /usr/bin /bin | awk '!seen[$0]++' | paste -sd: -)

**Step A: Try crontab + verify cron daemon (macOS)**

```bash
(crontab -l 2>/dev/null; echo "0 * * * * PATH=$CRON_PATH OKX_PROFILE=live ~/.okx/earn-hunter/scan.sh >> ~/.okx/earn-hunter/cron.log 2>&1") | crontab -
On macOS (
uname -s
==
Darwin
), immediately check if the cron daemon is running:
bash
if [[ "$(uname -s)" == "Darwin" ]] && ! launchctl list com.vix.cron >/dev/null 2>&1; then
  # cron daemon not running — fallback to LaunchAgent
fi
  • If cron daemon is running → done,
    scheduler.type = "cron"
    .
  • If cron daemon is not running → remove the crontab entry and proceed to Step B.
  • On Linux → skip the check (cron is always available),
    scheduler.type = "cron"
    .
Step B: macOS LaunchAgent fallback (
scheduler.type = "launchagent"
)
Generate
~/Library/LaunchAgents/com.okx.earn-hunter.plist
with the resolved paths:
bash
SCAN_SCRIPT="$HOME/.okx/earn-hunter/scan.sh"
LOG_FILE="$HOME/.okx/earn-hunter/cron.log"
INTERVAL=3600  # derive from scheduler.interval: "1h"→3600, "30m"→1800, "10m"→600

cat > ~/Library/LaunchAgents/com.okx.earn-hunter.plist << PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.okx.earn-hunter</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>${SCAN_SCRIPT}</string>
    </array>
    <key>StartInterval</key>
    <integer>${INTERVAL}</integer>
    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>${CRON_PATH}</string>
        <key>OKX_PROFILE</key>
        <string>live</string>
        <key>HOME</key>
        <string>${HOME}</string>
    </dict>
    <key>StandardOutPath</key>
    <string>${LOG_FILE}</string>
    <key>StandardErrorPath</key>
    <string>${LOG_FILE}</string>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>
PLIST

launchctl load ~/Library/LaunchAgents/com.okx.earn-hunter.plist
Write
scheduler.type = "launchagent"
to
platform.json
. Inform user:
"macOS cron 服务未运行,已自动切换为 LaunchAgent 调度(无需 sudo,重启自动恢复)。"
Notes:
  • OAuth mode → omit
    OKX_PROFILE
    from the plist
    EnvironmentVariables
    (or set to empty).
  • LaunchAgent plist paths must be absolute (no
    ~
    ). The activation flow expands
    $HOME
    at generation time.
  • RunAtLoad: true
    means the first scan runs immediately after loading.
  • The script reads
    config.json
    /
    platform.json
    , writes
    state.json
    /
    notify.log
    , and sends notifications via curl to TG Bot API or Lark Webhook itself. No agent involvement needed at tick time.
  • The script exits 0 and produces no output when there are no new opportunities and
    verboseLog=false
    — this is the intended silent behavior.
IMPORTANT: On OS-scheduler platforms, do NOT use agent-platform
/loop
/ Routines
(Claude Code
/loop
, cloud Routines). These spawn LLM sessions per tick and cannot reliably push external notifications. (OpenClaw is the exception above — it uses its in-session
cron
tool with
announce
delivery by design.)

NODE_DIR=$(dirname "$(command -v node)") OKX_DIR=$(dirname "$(command -v okx)") JQ_DIR=$(dirname "$(command -v jq)") CRON_PATH=$(printf '%s\n' "$NODE_DIR" "$OKX_DIR" "$JQ_DIR" /usr/bin /bin | awk '!seen[$0]++' | paste -sd: -)

**步骤A:尝试crontab并验证cron守护进程(macOS)**

```bash
(crontab -l 2>/dev/null; echo "0 * * * * PATH=$CRON_PATH OKX_PROFILE=live ~/.okx/earn-hunter/scan.sh >> ~/.okx/earn-hunter/cron.log 2>&1") | crontab -
在macOS(
uname -s
==
Darwin
)上,立即检查cron守护进程是否运行:
bash
if [[ "$(uname -s)" == "Darwin" ]] && ! launchctl list com.vix.cron >/dev/null 2>&1; then
  # cron守护进程未运行 — 回退到LaunchAgent
fi
  • cron守护进程运行 → 完成设置,
    scheduler.type = "cron"
  • cron守护进程运行 → 删除crontab条目并继续执行步骤B。
  • Linux系统 → 跳过检查(cron始终可用),
    scheduler.type = "cron"
步骤B:macOS LaunchAgent回退方案
scheduler.type = "launchagent"
使用解析后的路径生成
~/Library/LaunchAgents/com.okx.earn-hunter.plist
bash
SCAN_SCRIPT="$HOME/.okx/earn-hunter/scan.sh"
LOG_FILE="$HOME/.okx/earn-hunter/cron.log"
INTERVAL=3600  # 根据scheduler.interval转换:"1h"→3600,"30m"→1800,"10m"→600

cat > ~/Library/LaunchAgents/com.okx.earn-hunter.plist << PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.okx.earn-hunter</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>${SCAN_SCRIPT}</string>
    </array>
    <key>StartInterval</key>
    <integer>${INTERVAL}</integer>
    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>${CRON_PATH}</string>
        <key>OKX_PROFILE</key>
        <string>live</string>
        <key>HOME</key>
        <string>${HOME}</string>
    </dict>
    <key>StandardOutPath</key>
    <string>${LOG_FILE}</string>
    <key>StandardErrorPath</key>
    <string>${LOG_FILE}</string>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>
PLIST

launchctl load ~/Library/LaunchAgents/com.okx.earn-hunter.plist
scheduler.type = "launchagent"
写入
platform.json
。告知用户:
"macOS cron服务未运行,已自动切换为LaunchAgent调度(无需sudo,重启自动恢复)。"
注意事项:
  • OAuth模式 → 在plist的
    EnvironmentVariables
    中省略
    OKX_PROFILE
    (或设置为空)。
  • LaunchAgent plist路径必须为绝对路径(不能使用
    ~
    )。激活流程会在生成时展开
    $HOME
  • RunAtLoad: true
    表示加载后立即运行首次扫描。
  • 脚本会读取
    config.json
    /
    platform.json
    ,写入
    state.json
    /
    notify.log
    ,并通过curl直接调用TG Bot API或Lark Webhook发送通知。触发时无需Agent参与。
  • 当无新机会且
    verboseLog=false
    时,脚本会以0状态码退出且无输出 — 这是预期的静默行为。
重要提示: 在系统调度器平台上,请勿使用Agent平台的
/loop
/ Routines功能(Claude Code的
/loop
、云Routines)。这些功能会在每次触发时生成LLM会话,且无法可靠推送外部通知。(OpenClaw是例外 — 它通过会话内的
cron
工具结合
announce
交付机制实现调度。)

Scan Cycle

扫描周期

The entire Scan Cycle is implemented by
scripts/scan.sh
(pure shell + jq, zero LLM cost).
Whether triggered by OS crontab, by an OpenClaw isolated cron agent turn, or by a user in an interactive session ("执行 earn-hunter 扫描"), the cycle is the same: run the script and relay its output. Do NOT re-implement the scan steps in natural language — the script is the single source of truth.
OpenClaw isolated cron turn: the job's prompt routes here. Run
scripts/scan.sh
(with
platform.json
notify.channel = "session"
, the script prints any notification to stdout); relay that stdout as the turn's response. The cron job's
announce
delivery then pushes it to the conversation channel. Do not curl TG/Lark from this turn — delivery is handled by
announce
.
整个扫描周期由
scripts/scan.sh
(纯shell + jq,零LLM成本)实现。无论是由系统crontab、OpenClaw独立cron Agent轮次触发,还是由用户在交互会话中触发("执行 earn-hunter 扫描"),扫描周期完全相同
:运行脚本并转发输出结果。请勿用自然语言重新实现扫描步骤 — 脚本是唯一的事实来源。
OpenClaw独立cron轮次: 任务指令会路由至此。运行
scripts/scan.sh
platform.json
notify.channel = "session"
,脚本会将任何通知打印到标准输出);将标准输出作为轮次响应转发。cron任务的
announce
交付机制会将结果推送至对话渠道。请勿在此轮次中调用TG/Lark的curl接口 — 交付由
announce
机制处理。

How the agent runs a scan (interactive trigger)

Agent如何运行扫描(交互式触发)

bash
undefined
bash
undefined

Profile: pass OKX_PROFILE only in API Key mode (omit for OAuth mode).

配置文件:仅在API Key模式下传递OKX_PROFILE(OAuth模式下省略)。

OKX_PROFILE=live ~/.okx/earn-hunter/scan.sh

Then **relay the script's stdout verbatim** to the user:
- If the script prints a notification (Flash / Fixed / mixed / verbose status), show it.
- If the script prints **nothing** (silent exit 0, the no-new-opportunity + `verboseLog=false` case), tell the user "本轮扫描完成,无新机会" — do NOT fabricate a "scan complete" message into a channel; the silence is intentional.
OKX_PROFILE=live ~/.okx/earn-hunter/scan.sh

然后**将脚本的标准输出原样转发给用户**:
- 若脚本打印通知(Flash / Fixed / 混合 / 详细状态),则展示该内容。
- 若脚本**无输出**(静默退出0,即无新机会且`verboseLog=false`的情况),告知用户"本轮扫描完成,无新机会" — 请勿在渠道中编造"扫描完成"消息;静默是预期行为。

What the script does (see
references/scan-logic.md
for the spec it implements)

脚本执行逻辑(详见
references/scan-logic.md
中的规范)

  1. Reads
    ~/.okx/earn-hunter/config.json
  2. Runs scan commands (based on
    flash.enabled
    /
    fixed.enabled
    /
    flexible.enabled
    ):
    • Flash:
      okx [--profile live] earn flash-earn projects --status 0,100 --json
    • Fixed:
      okx [--profile live] earn savings fixed-products --json
      (auto-fallback to
      rate-history
      +
      fixedOffers
      on CLI <1.3.3)
    • Flexible: for each currency in
      flexible.currencies
      ,
      okx [--profile live] earn savings rate-history --ccy <ccy> --limit 1 --json
  3. Filters (two-layer APY threshold, terms filter, currency filter; flexible uses threshold-crossing model)
  4. Dedups against
    ~/.okx/earn-hunter/state.json
    (
    state.flash["<id>:<status>"]
    ,
    state.fixed["<ccy>:<term>:<rate>"]
    ,
    state.flexible["<ccy>"]
    )
  5. If new opportunities → renders the matching template (flash / fixed / flexible / mixed) and sends via the detected channel (TG → Lark → session), logging to
    notify.log
  6. Updates
    state.json
    (write new keys; flash ID-level diff cleanup; fixed key-level diff cleanup; flexible threshold-crossing diff cleanup; 7-day TTL; failure counter)
  7. If no new opportunities:
    verboseLog=true
    → brief status;
    verboseLog=false
    silent exit 0, no output, nothing sent
  8. Error handling: consecutive-failure counter (alert at 3, then reset); 401/session-expired → credential alert + stop
Channel routing inside the script: detection order TG (
$TELEGRAM_BOT_TOKEN
+
$TELEGRAM_CHAT_ID
) → Lark (
platform.json
.notify.lark_webhook
) → session (stdout). A
notify.channel
of
telegram
/
lark
/
session
in
platform.json
forces that channel.
Auth / profile: the script never reads or prints credentials. It delegates all auth to the
okx
CLI (which reads
~/.okx/config.toml
). Profile is injected only via the
OKX_PROFILE
env var.
Flexible Earn dedup model: Unlike Flash/Fixed which dedup by specific opportunity, Flexible uses a threshold-crossing model — key is just
<ccy>
. Notifies once when APY crosses above threshold; stays silent while it remains above; resets when APY drops below threshold (diff cleanup removes the key). This avoids frequent notifications from rate fluctuations.

  1. 读取
    ~/.okx/earn-hunter/config.json
  2. 运行扫描命令(基于
    flash.enabled
    /
    fixed.enabled
    /
    flexible.enabled
    ):
    • Flash:
      okx [--profile live] earn flash-earn projects --status 0,100 --json
    • Fixed:
      okx [--profile live] earn savings fixed-products --json
      (CLI版本<1.3.3时自动回退到
      rate-history
      +
      fixedOffers
    • Flexible: 针对
      flexible.currencies
      中的每个币种,执行
      okx [--profile live] earn savings rate-history --ccy <ccy> --limit 1 --json
  3. 过滤(两层APY阈值、期限过滤、币种过滤;活期使用阈值穿越模型)
  4. ~/.okx/earn-hunter/state.json
    进行去重(
    state.flash["<id>:<status>"]
    state.fixed["<ccy>:<term>:<rate>"]
    state.flexible["<ccy>"]
  5. 若发现新机会 → 渲染匹配模板(flash / fixed / flexible / 混合)并通过检测到的渠道发送(TG → Lark → 会话),同时记录到
    notify.log
  6. 更新
    state.json
    (写入新键;清理flash ID级差异;清理fixed键级差异;清理活期阈值穿越差异;7天TTL;失败计数器)
  7. 若无新机会:
    verboseLog=true
    → 发送简短状态;
    verboseLog=false
    静默退出0,无输出,不发送任何内容
  8. 错误处理:连续失败计数器(达到3次时发送告警,之后重置);401/会话过期 → 凭证告警 + 停止扫描
脚本内的渠道路由: 检测顺序为TG(
$TELEGRAM_BOT_TOKEN
+
$TELEGRAM_CHAT_ID
)→ Lark(
platform.json
.notify.lark_webhook
)→ 会话(标准输出)。
platform.json
notify.channel
设置为
telegram
/
lark
/
session
时会强制使用对应渠道。
认证/配置文件: 脚本从不读取或打印凭证。所有认证操作委托给
okx
CLI(读取
~/.okx/config.toml
)。仅通过
OKX_PROFILE
环境变量注入配置文件。
Flexible Earn去重模型: 与Flash/Fixed按特定机会去重不同,活期采用阈值穿越模型 — 键仅为
<ccy>
。当APY超过阈值时发送一次通知;APY保持在阈值以上时静默;APY低于阈值时重置(差异清理会删除对应键)。此模型避免因利率波动导致频繁通知。

Purchase Guide

申购引导

When user wants to subscribe to a Fixed Earn product after receiving a notification.
Read
{baseDir}/references/purchase-guide.md
for the complete flow.
Summary:
  1. Parallel balance check (funding + trading + flexible earn)
  2. Compare fixed APR vs flexible lendingRate
  3. Calculate recommended amount:
    min(idle + movable_simple_earn, lendQuota)
  4. Present recommendation with comparison hint
  5. User confirms → re-check offer availability (soldOut guard) → hand off to
    okx-cex-earn
Edge cases covered in purchase-guide.md:
  • Balance < minLend → show deficit
  • Amount > lendQuota → auto-cap with notice
  • Redeem succeeded but subscribe failed → warn user, funds are in funding account
  • Offer sold out between notification and subscription → inform user
Important: earn-hunter does NOT execute write operations directly. It transfers control to
okx-cex-earn
.

用户收到通知后想要申购Fixed Earn产品时触发。
完整流程详见
{baseDir}/references/purchase-guide.md
流程摘要:
  1. 并行检查余额(资金账户 + 交易账户 + 活期赚币账户)
  2. 对比固定APR与活期lendingRate
  3. 计算推荐申购金额:
    min(idle + movable_simple_earn, lendQuota)
  4. 展示推荐方案并附带对比提示
  5. 用户确认 → 重新检查产品可用性(售罄防护)→ 移交至
    okx-cex-earn
    技能
purchase-guide.md
中涵盖的边缘场景:
  • 余额 < minLend → 展示缺口
  • 金额 > lendQuota → 自动封顶并提示
  • 赎回成功但申购失败 → 警告用户,资金已回到资金账户
  • 通知与申购之间产品售罄 → 告知用户
重要提示: earn-hunter不直接执行写入操作。它会将控制权转移给
okx-cex-earn
技能。

Config Management

配置管理

Read
{baseDir}/references/config-reference.md
for field definitions and natural language examples.
When user wants to change settings:
  1. Parse intent → map to config field (see
    config-reference.md
    for field mapping)
  2. Read the target JSON file (
    config.json
    or
    platform.json
    ) → modify the field → write back
  3. Read the updated file → confirm the change to user
Exception: TG credentials cannot be changed via natural language. Tell user to set environment variables directly.

字段定义和自然语言示例详见
{baseDir}/references/config-reference.md
用户想要修改设置时:
  1. 解析意图 → 映射到配置字段(详见
    config-reference.md
    中的字段映射)
  2. 读取目标JSON文件(
    config.json
    platform.json
    )→ 修改字段 → 写回文件
  3. 读取更新后的文件 → 向用户确认修改结果
例外: TG凭证无法通过自然语言修改。告知用户直接设置环境变量。

Pause/Resume

暂停/恢复

Branch on
platform.json
.scheduler.type
:
OpenClaw (
openclaw-cron
)
— manage via the in-session
cron
tool (no CLI):
  • Pause: call
    cron
    with
    action: "update"
    , targeting the
    earn-hunter-hourly
    job, patch
    { "enabled": false }
    (or
    action: "remove"
    to delete it).
  • Resume:
    action: "update"
    with
    { "enabled": true }
    (or re-create as in Activation Step 5).
  • Use
    action: "list"
    to find the job id.
OS-crontab platforms (
cron
):
  • Pause:
    crontab -l | grep -v 'earn-hunter' | crontab -
  • Resume: Re-add the crontab entry (same as Activation Step 5).
macOS LaunchAgent (
launchagent
):
  • Pause:
    launchctl unload ~/Library/LaunchAgents/com.okx.earn-hunter.plist
  • Resume:
    launchctl load ~/Library/LaunchAgents/com.okx.earn-hunter.plist
Config and state are preserved — resuming picks up where it left off.

根据
platform.json
.scheduler.type
分支处理:
OpenClaw(
openclaw-cron
— 通过会话内的
cron
工具管理(无需CLI):
  • 暂停: 调用
    cron
    工具,设置
    action: "update"
    ,目标为
    earn-hunter-hourly
    任务,更新
    { "enabled": false }
    (或设置
    action: "remove"
    删除任务)。
  • 恢复: 设置
    action: "update"
    并传入
    { "enabled": true }
    (或按照激活步骤5重新创建任务)。
  • 使用
    action: "list"
    查找任务ID。
系统crontab平台(
cron
):
  • 暂停:
    crontab -l | grep -v 'earn-hunter' | crontab -
  • 恢复: 重新添加crontab条目(与激活步骤5相同)。
macOS LaunchAgent(
launchagent
):
  • 暂停:
    launchctl unload ~/Library/LaunchAgents/com.okx.earn-hunter.plist
  • 恢复:
    launchctl load ~/Library/LaunchAgents/com.okx.earn-hunter.plist
配置和状态会被保留 — 恢复后会从暂停处继续执行。

Uninstall

卸载

When user says "卸载" / "uninstall":
  1. Stop the scheduler (same as Pause). For LaunchAgent, also remove the plist:
    launchctl unload ~/Library/LaunchAgents/com.okx.earn-hunter.plist && rm -f ~/Library/LaunchAgents/com.okx.earn-hunter.plist
  2. Ask: "是否保留配置和历史数据?"
    • Yes → only remove scheduler
    • No → also remove
      ~/.okx/earn-hunter/
      directory

用户说出"卸载" / "uninstall"时:
  1. 停止调度器(与暂停操作相同)。对于LaunchAgent,还需删除plist文件:
    launchctl unload ~/Library/LaunchAgents/com.okx.earn-hunter.plist && rm -f ~/Library/LaunchAgents/com.okx.earn-hunter.plist
  2. 询问:"是否保留配置和历史数据?"
    • 是 → 仅删除调度器
    • 否 → 同时删除
      ~/.okx/earn-hunter/
      目录

Test Mode

测试模式

Trigger phrases: "测试 earn-hunter" / "earn-hunter smoke test" / "测试定时任务触发"
Behavior — run the script with the Test Mode hooks (no separate logic needed):
bash
undefined
触发短语:"测试 earn-hunter" / "earn-hunter smoke test" / "测试定时任务触发"
行为 — 使用测试模式钩子运行脚本(无需单独逻辑):
bash
undefined

Force verbose so output is always produced; write dedup keys under test: prefix.

强制开启verbose模式以确保始终生成输出;将去重键写入test:前缀的命名空间。

EH_TEST_NAMESPACE=1 OKX_PROFILE=live ~/.okx/earn-hunter/scan.sh # with config.verboseLog temporarily set to true

1. **Execute a full Scan Cycle** — the script runs the same scan, but `EH_TEST_NAMESPACE=1` isolates state writes
2. **Force-send notification** — temporarily set `config.verboseLog=true` so the script always sends output regardless of whether opportunities are found (restore afterwards)
3. **Dedup writes to test namespace** — `EH_TEST_NAMESPACE=1` prefixes dedup keys with `test:` (e.g. `test:flash:12345:100`); these keys are immune to diff cleanup (only TTL removes them), so test runs do not pollute production state
4. **Output diagnostics** after scan completes:
   - okx auth status (logged in / expired / not configured)
   - Scan command results (flash project count + fixed product count + flexible rate count)
   - Post-filter results (how many passed filters per type)
   - Notification channel status (which channel is configured, send result)
   - Scheduler status (OS-crontab platforms: crontab entry exists? / OpenClaw: `cron` tool `action: "list"` shows the `earn-hunter-hourly` job?)
   - Last 5 lines of `~/.okx/earn-hunter/notify.log`
5. **Completion message:** "测试完成。test: 前缀的 state 不影响正式去重,正式扫描不受影响。"

---
EH_TEST_NAMESPACE=1 OKX_PROFILE=live ~/.okx/earn-hunter/scan.sh # 临时将config.verboseLog设置为true

1. **执行完整扫描周期** — 脚本运行相同的扫描,但`EH_TEST_NAMESPACE=1`会隔离状态写入
2. **强制发送通知** — 临时设置`config.verboseLog=true`,确保无论是否发现机会脚本都会发送输出(之后恢复原设置)
3. **去重键写入测试命名空间** — `EH_TEST_NAMESPACE=1`会为去重键添加`test:`前缀(例如`test:flash:12345:100`);这些键不受差异清理影响(仅TTL会删除它们),因此测试运行不会污染生产状态
4. **扫描完成后输出诊断信息**:
   - okx认证状态(已登录 / 已过期 / 未配置)
   - 扫描命令结果(flash项目数量 + fixed产品数量 + 活期利率数量)
   - 过滤后结果(每种类型通过过滤的数量)
   - 通知渠道状态(已配置的渠道、发送结果)
   - 调度器状态(系统crontab平台:crontab条目是否存在?/ OpenClaw:`cron`工具`action: "list"`是否显示`earn-hunter-hourly`任务?)
   - `~/.okx/earn-hunter/notify.log`的最后5行
5. **完成消息:** "测试完成。test:前缀的state不影响正式去重,正式扫描不受影响。"

---

Error Handling

错误处理

Read
{baseDir}/templates/error-alert.md
for exact alert message templates.
ErrorAction
okx
401 / "Session expired"
Stop scan. Send alert (凭证失效 template). Load
okx-cex-auth
if interactive.
Network error / timeoutRetry once silently. If still fails, skip this cycle.
3 consecutive scan failuresSend alert (连续失败 template). Counter stored in
state.consecutive_failures
, reset after alert.
state.json
corrupted
Reset by writing
{"flash":{},"fixed":{},"consecutive_failures":0,"last_error":""}
. May cause one round of duplicate notifications.
Notification send failsLog to
notify.log
, continue scan. Dedup key NOT added (next cycle retries).
config.json
missing at runtime
Send alert: "earn-hunter 未配置,请运行首次激活流程。"
Dual-client suspected (user mentions both platforms)Warn: "建议仅在一个客户端运行 earn-hunter,避免重复通知。"
verboseLog = true
+ no hits
Send brief status (not silent).

具体告警消息模板详见
{baseDir}/templates/error-alert.md
错误类型处理动作
okx
401 / "Session expired"
停止扫描。发送告警(凭证失效模板)。若处于交互模式则加载
okx-cex-auth
技能。
网络错误 / 超时静默重试一次。若仍失败则跳过本轮扫描。
连续3次扫描失败发送告警(连续失败模板)。计数器存储在
state.consecutive_failures
中,发送告警后重置。
state.json
损坏
通过写入
{"flash":{},"fixed":{},"consecutive_failures":0,"last_error":""}
重置。可能导致一轮重复通知。
通知发送失败记录到
notify.log
,继续扫描。不添加去重键(下一轮会重试)。
运行时缺少
config.json
发送告警:"earn-hunter未配置,请运行首次激活流程。"
疑似双客户端(用户提及多个平台)警告:"建议仅在一个客户端运行earn-hunter,避免重复通知。"
verboseLog = true
且无匹配结果
发送简短状态(不静默)。

i18n

国际化

  • All notifications rendered in user's language (detected at activation, stored in
    config.notify.language
    )
  • Locked terms (never translate): Flash Earn, Fixed Earn, Simple Earn, DCD, APY, APR, OKX, earn-hunter, Telegram, project names, currency codes (USDT, BTC, etc.)
  • Fallback: If LLM rendering fails, send Chinese template + append
    (translation unavailable, sent in zh-CN)

  • 所有通知均以用户语言渲染(激活时检测,存储在
    config.notify.language
  • 固定术语(永不翻译): Flash Earn、Fixed Earn、Simple Earn、DCD、APY、APR、OKX、earn-hunter、Telegram、项目名称、币种代码(USDT、BTC等)
  • 兜底方案: 若LLM渲染失败,发送中文模板并追加
    (translation unavailable, sent in zh-CN)

Global Notes

全局注意事项

  • Security: Never accept credentials in chat. TG token only via env vars. Guide users to
    okx config init
    for OKX auth.
  • Output: Use
    --json
    for all okx commands. Render results as markdown tables.
  • Logging: All notification send results logged to
    ~/.okx/earn-hunter/notify.log
    .
  • Scope: Covers Flash Earn, Simple Earn Fixed, and Simple Earn Flexible (活期). DCD, on-chain, auto-earn are out of scope.
  • Mode: Live trading only.
    config.simulatedTrading
    is always
    false
    .
  • 安全性: 切勿在聊天中接收凭证。TG令牌仅通过环境变量设置。引导用户通过
    okx config init
    进行OKX认证。
  • 输出: 所有okx命令均使用
    --json
    参数。将结果渲染为markdown表格。
  • 日志: 所有通知发送结果记录到
    ~/.okx/earn-hunter/notify.log
  • 范围: 覆盖Flash Earn、Simple Earn定期和Simple Earn活期。DCD、链上产品、自动赚币不在范围内。
  • 模式: 仅支持实盘交易。
    config.simulatedTrading
    始终为
    false

Defensive Design Principles

防御性设计原则

These constraints apply to all changes to earn-hunter:
  1. cron PATH ≠ user shell PATH. Any cron-triggered script must resolve tool paths independently — never assume cron inherits the user's login PATH. The script uses
    env.snapshot
    +
    resolve_bin
    fallback.
  2. Sanity check first. Before running any CLI command, verify the binary exists. Missing tool → immediate exit with clear error, not silent failure.
  3. Never swallow stderr. Use
    2>&1
    (capture into variable) instead of
    2>/dev/null
    for CLI calls. Otherwise error messages vanish and
    last_error
    is empty.
  4. Smoke test must simulate production. Activation smoke test runs twice: once in user shell (verify credentials/network), once in minimal
    env -i PATH=/usr/bin:/bin
    (verify cron can run). Don't pass Step 5 if cron smoke fails.
  5. Alert templates must handle empty fields. If
    last_error
    is empty string, append "check
    ~/.okx/earn-hunter/cron.log
    ; empty error usually means PATH issue" guidance.
  6. Tool paths use snapshot + fallback, not global PATH.
    env.snapshot
    written at activation with exact paths;
    resolve_bin
    tries snapshot →
    command -v
    → hardcoded common paths. Survives
    nvm switch
    / Homebrew upgrades.
这些约束适用于earn-hunter的所有变更:
  1. cron PATH ≠ 用户shell PATH。任何cron触发的脚本必须独立解析工具路径 — 切勿假设cron继承用户登录会话的PATH。脚本使用
    env.snapshot
    +
    resolve_bin
    兜底方案。
  2. 先进行完整性检查。运行任何CLI命令前,验证二进制文件是否存在。工具缺失 → 立即退出并显示清晰错误,而非静默失败。
  3. 切勿忽略stderr。CLI调用使用
    2>&1
    (捕获到变量)而非
    2>/dev/null
    。否则错误消息会丢失,导致
    last_error
    为空。
  4. 冒烟测试必须模拟生产环境。激活冒烟测试运行两次:一次在用户shell中(验证凭证/网络),一次在最小
    env -i PATH=/usr/bin:/bin
    环境中(验证cron可运行)。若cron冒烟测试失败,请勿进入步骤5。
  5. 告警模板必须处理空字段。若
    last_error
    为空字符串,追加"检查
    ~/.okx/earn-hunter/cron.log
    ;空错误通常意味着PATH问题"的指导信息。
  6. 工具路径使用快照+兜底方案,而非全局PATH。激活时写入
    env.snapshot
    包含精确路径;
    resolve_bin
    尝试快照路径 →
    command -v
    → 硬编码常见路径。可在
    nvm switch
    / Homebrew升级后正常工作。