earn-hunter
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseEarn Hunter
Earn Hunter
Automated monitor for OKX Flash Earn, Fixed Earn, and Flexible Earn (Simple Earn) opportunities.
{baseDir}OKX Flash Earn、Fixed Earn和Flexible Earn(Simple Earn)理财机会的自动化监控工具。
{baseDir}Preflight
前置检查
-
VerifyCLI installed:
okx. If missing, install viawhich okx. On OpenClaw, also verify the in-sessionnpm install -g @okx_ai/okx-trade-clitool is available in the agent tool list (used for scheduling — not thecronCLI).openclaw -
Check optional dependent skills:bash
okx skill list --jsonOptional skills (not required for scanning/notifications):- — needed for purchase guide (subscription execution)
okx-cex-earn - — needed for authentication recovery
okx-cex-auth
If either is missing, attempt to install but do not block if installation fails:bashokx 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,后续可手动安装。"
{skill_name} - Preflight continues regardless of skill installation result
-
Auth mode detection — run both, first match wins:
- → has non-empty
okx config show --jsonfield → API Key mode. Addapi_keyto all commands.--profile live - No API key + →
okx auth status --json→ OAuth mode. No"status":"logged_in"flag needed.--profile - Neither → stop. Load skill and follow login steps.
okx-cex-auth
-
Init config and state:
- If directory does not exist →
~/.okx/earn-hunter/mkdir -p ~/.okx/earn-hunter - If does not exist → copy
~/.okx/earn-hunter/config.jsonto it{baseDir}/config/default.json - If does not exist → write
~/.okx/earn-hunter/state.json{"flash":{},"fixed":{},"flexible":{},"consecutive_failures":0,"last_error":""} - If does not exist → run Platform Detection
~/.okx/earn-hunter/platform.json - Always (re)install the scan script: . This is the script cron and interactive scans both call.
cp {baseDir}/scripts/scan.sh ~/.okx/earn-hunter/scan.sh && chmod +x ~/.okx/earn-hunter/scan.sh - Write with resolved tool paths (cron cannot rely on the user's login PATH):
~/.okx/earn-hunter/env.snapshotThe scan script sources this file to resolve tool paths even under cron's minimalbashcat > ~/.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.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(shell + jq) —scripts/scan.shis required for scanning. Verify withjq; if missing, install (which jq/brew install jq).apt-get install jq - If
-
验证CLI是否已安装:执行
okx。若未安装,通过which okx进行安装。在OpenClaw平台上,还需确认会话内的npm install -g @okx_ai/okx-trade-cli工具已在Agent工具列表中(用于任务调度——并非cronCLI)。openclaw -
检查可选依赖技能:bash
okx skill list --json可选技能(扫描/通知功能不需要这些技能):- — 申购引导(执行申购操作)所需
okx-cex-earn - — 认证恢复所需
okx-cex-auth
若任一技能缺失,尝试安装但安装失败时不阻塞流程:bashokx skill add okx-cex-earn okx skill add okx-cex-auth- 安装成功 → 继续执行
- 安装失败(网络错误、市场不可用等)→ 发出警告并继续:
"⚠ 安装失败,扫描和通知功能不受影响。申购引导和认证恢复需要该skill,后续可手动安装。"
{skill_name} - 无论技能安装结果如何,前置检查流程继续执行
-
认证模式检测 — 依次执行以下两种检测,匹配到第一个即停止:
- → 存在非空
okx config show --json字段 → API Key模式。所有命令需添加api_key参数。--profile live - 无API密钥 + → 返回
okx auth status --json→ OAuth模式。无需添加"status":"logged_in"参数。--profile - 两者均不匹配 → 停止流程。加载技能并按照登录步骤操作。
okx-cex-auth
-
初始化配置与状态:
- 若目录不存在 → 执行
~/.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 - 将解析后的工具路径写入(cron无法依赖用户登录会话的PATH):
~/.okx/earn-hunter/env.snapshot扫描脚本会加载此文件,即使在cron的最小bashcat > ~/.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环境下也能解析工具路径。PATH=/usr/bin:/bin
Agent仅在激活/配置管理阶段读写配置/状态/平台JSON文件。定期扫描操作完全由(shell + jq)执行 — 扫描需要依赖scripts/scan.sh工具。通过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 exists):
platform.json- Probe environment clues:
- env var exists? → hint: OpenClaw
OPENCLAW_HOME - Agent tool list contains /
crontools? → hint: OpenClawdelivery - env var exists or
HERMES_HOMEsucceeds? → hint: Hermes Agentwhich hermes - Running inside Claude Code session? → hint: Claude Code
- None of the above matched → hint: Generic
- 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) 其他"
- Initialize platform config:
- OpenClaw / Claude Code → copy to
{baseDir}/config/<confirmed_platform>.default.json~/.okx/earn-hunter/platform.json - Hermes Agent → copy as base, set
{baseDir}/config/claude-code.default.jsonto.platform,"hermes"to.scheduler.type"cron" - Generic → copy as base, set
{baseDir}/config/claude-code.default.jsonto.platform,"generic"to.scheduler.type"manual"
- OpenClaw / Claude Code → copy
- Result written to , subsequent runs skip detection.
~/.okx/earn-hunter/platform.json
Subsequent runs (platform.json exists):
Read and extract the field (returns , , , or ).
~/.okx/earn-hunter/platform.json.platform"openclaw""claude-code""hermes""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- 探测环境线索:
- 存在环境变量?→ 提示:OpenClaw
OPENCLAW_HOME - Agent工具列表包含/
cron工具?→ 提示:OpenClawdelivery - 存在环境变量或
HERMES_HOME执行成功?→ 提示:Hermes Agentwhich hermes - 在Claude Code会话中运行?→ 提示:Claude Code
- 以上均不匹配 → 提示:通用平台
- 存在
- 展示检测结果并请求用户确认:
- "检测到你正在使用 {detected_platform},是否正确?"
- 用户确认 → 继续执行
- 用户否认 → 询问:"你使用的是哪个平台?1) OpenClaw 2) Claude Code 3) Hermes Agent 4) 其他"
- 初始化平台配置:
- 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"
- OpenClaw / Claude Code → 将
- 将结果写入,后续运行跳过检测步骤。
~/.okx/earn-hunter/platform.json
后续运行(已存在platform.json):
读取并提取字段(返回值为、、或)。
~/.okx/earn-hunter/platform.json.platform"openclaw""claude-code""hermes""generic"检测到的平台不支持调度器(仅适用于原本应支持但实际不支持的平台)→ 错误提示:"当前客户端不支持定时任务,请升级到最新版本。"
通用平台 → 不支持自动调度。提示:"当前平台不支持自动调度,你可以手动说'执行 earn-hunter 扫描'来触发。"
Configuration Files
配置文件
| File | Scope | Content |
|---|---|---|
| Shared | Scan scope (flash/fixed/flexible), currencies, APY thresholds, terms, language, verboseLog |
| Platform-specific | Scheduler type/interval, notification channel, TG/Lark credentials |
| Shared | Dedup state |
Core config () is identical across platforms. Platform config () differs — the field determines how scans are triggered:
config.jsonplatform.jsonscheduler.typeOpenClaw ():
openclaw.default.json- scheduler.type = — scheduled via the in-session
"openclaw-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 croncrondelivery. notify.channel defaults toannounceso the scan prints to stdout for"session"to push (avoids double-send).announce
Claude Code / Hermes / Generic ():
claude-code.default.json- scheduler.type = — scheduled via OS crontab →
"cron"(zero LLM token cost), notification via TG / Lark curl from the script itself.scripts/scan.sh
| 文件 | 作用范围 | 内容 |
|---|---|---|
| 通用配置 | 扫描范围(flash/fixed/flexible)、监控币种、APY阈值、期限、语言、verboseLog |
| 平台专属配置 | 调度器类型/间隔、通知渠道、TG/Lark凭证 |
| 通用配置 | 去重状态 |
核心配置()在所有平台上保持一致。平台配置()存在差异 — 字段决定扫描触发方式:
config.jsonplatform.jsonscheduler.typeOpenClaw():
openclaw.default.json- scheduler.type = — 通过会话内的**
"openclaw-cron"Agent工具进行调度(无需系统crontab或CLI命令)。任务以独立轻量上下文的Agent轮次运行,并通过cron的cron**交付机制将输出返回至对话渠道。notify.channel默认设置为announce,因此扫描结果会打印到标准输出,由"session"推送(避免重复发送)。announce
Claude Code / Hermes / 通用平台():
claude-code.default.json- scheduler.type = — 通过**系统crontab →
"cron"**进行调度(零LLM令牌成本),通知通过脚本直接调用TG / Lark的curl接口发送。scripts/scan.sh
Notification Channels (independent of platform)
通知渠道(与平台无关)
Detect in priority order (PRD requirement: TG first):
- Telegram — and
$TELEGRAM_BOT_TOKENboth set → TG ready$TELEGRAM_CHAT_ID - Lark — non-empty → Lark ready
platform.notify.lark_webhook - 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 to the conversation channel (channel = ); TG/Lark curl is not used unless the user explicitly switches the channel.
announce"session"按优先级检测(产品需求:优先Telegram):
- Telegram — 和
$TELEGRAM_BOT_TOKEN均已设置 → TG可用$TELEGRAM_CHAT_ID - Lark — 非空 → Lark可用
platform.notify.lark_webhook - 会话 — 兜底选项,仅在交互模式下生效
TG和Lark是独立推送渠道 — 无论Agent客户端是否打开均可正常工作。在系统crontab平台上,定时扫描通过直接调用curl发送通知;在OpenClaw平台上,定时扫描在独立的cron Agent轮次中运行,并通过cron的****交付机制推送至对话渠道(渠道为);除非用户明确切换渠道,否则不会使用TG/Lark的curl接口。
announce"session"Skill Routing
技能路由
| User intent | Route |
|---|---|
| "有闪赚通知我" / "monitor earn" / "帮我监控赚币" / "活期年化高了通知我" | → Activation Flow |
| "改 APY 阈值" / "只看 USDT" / "change config" / "活期加上 BTC" | → Config Management |
| "申购 USDT 定期 7D" / "subscribe" / "我要买" | → Purchase Guide |
| "执行 earn-hunter 扫描" (cron OR interactive) | → Scan Cycle — run |
| "停止监控" / "暂停" / "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扫描"(定时或交互式) | → 扫描周期 — 运行 |
| "停止监控" / "暂停" / "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.jsonStep 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 to the conversation. Detection order (check each, report status for all):
announce- Check and
$TELEGRAM_BOT_TOKENenv vars:$TELEGRAM_CHAT_ID- 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
- Check or Lark MCP tools:
platform.notify.lark_webhook- Webhook set and valid (starts with and contains
https://) → Lark ready/hook/ - Webhook set but format invalid (does not start with or missing
https://) → warn: "Lark webhook 格式无效,跳过 Lark" → continue to next channel/hook/ - Not configured and no Lark MCP → Lark not available
- Webhook set and valid (starts with
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}
你希望通知发到哪里?
- {detected channel 1}
- {detected channel 2, if any}
- 仅在当前会话显示(离线收不到)"
If no external channel detected:
"新机会才能推送到你手上。你希望通知发到哪里?
- Telegram — 需要提供 Bot Token 和 Chat ID(通过环境变量)
- Lark/飞书 — 需要提供 Webhook URL
- 仅在当前会话显示(⚠ 离线收不到通知)
推荐配置 Telegram 或 Lark,这样即使不在对话中也能收到提醒。"
- If user picks Telegram → guide setting and
TELEGRAM_BOT_TOKENenv varsTELEGRAM_CHAT_ID - If user picks Lark → ask for webhook URL, validate format (starts with , contains
https://), write to/hook/platform.notify.lark_webhook - If user picks session → write and warn: "⚠ 离线状态下不会收到通知,建议后续配置外部渠道。"
"session"
Write confirmed channel to .
platform.jsonnotify.channel在继续流程前必须主动检查可用渠道。请勿默认使用会话渠道。
在系统crontab平台上,定时通知通过直接调用curl发送;在OpenClaw平台上,通知通过cron的机制推送至对话。按以下顺序检测(检查每个渠道并报告状态):
announce- 检查和
$TELEGRAM_BOT_TOKEN环境变量:$TELEGRAM_CHAT_ID- 两者均已设置 → TG可用
- 仅Token已设置但缺少chat_id → 警告:"Telegram配置不完整(缺少TELEGRAM_CHAT_ID),跳过TG" → 继续检测下一个渠道
- 两者均未设置 → TG不可用
- 检查或Lark MCP工具:
platform.notify.lark_webhook- Webhook已设置且格式有效(以开头且包含
https://)→ Lark可用/hook/ - Webhook已设置但格式无效(不以开头或缺少
https://)→ 警告:"Lark webhook格式无效,跳过Lark" → 继续检测下一个渠道/hook/ - 未配置且无Lark MCP → Lark不可用
- Webhook已设置且格式有效(以
**始终请求用户确认通知渠道 — 请勿默认使用会话渠道。**对于监控工具而言,通知功能至关重要;默认使用会话渠道意味着用户不在对话中时会错过告警。
若检测到一个或多个外部渠道:
"检测到以下推送渠道可用:
- {已检测渠道列表,例如Telegram / Lark}
你希望通知发到哪里?
- {已检测渠道1}
- {已检测渠道2(若存在)}
- 仅在当前会话显示(离线收不到)"
若未检测到外部渠道:
"新机会才能推送到你手上。你希望通知发到哪里?
- Telegram — 需要提供Bot Token和Chat ID(通过环境变量)
- Lark/飞书 — 需要提供Webhook URL
- 仅在当前会话显示(⚠ 离线收不到通知)
推荐配置Telegram或Lark,这样即使不在对话中也能收到提醒。"
- 用户选择Telegram → 引导设置和
TELEGRAM_BOT_TOKEN环境变量TELEGRAM_CHAT_ID - 用户选择Lark → 请求提供webhook URL,验证格式(以开头且包含
https://),写入/hook/platform.notify.lark_webhook - 用户选择会话 → 写入并警告:"⚠ 离线状态下不会收到通知,建议后续配置外部渠道。"
"session"
将确认后的渠道写入的字段。
platform.jsonnotify.channelStep 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; skip Step 2/3 and 3/3config.flexible.enabled = false - If user only picks [3] → set ,
config.flash.enabled = false; go to flexible-specific config (Step 2/3 asks flexible currencies, Step 3/3 asks flexible APY threshold)config.fixed.enabled = false
Step 2/3 — 监控币种:
For Fixed Earn: "定期监控币种:全部(默认,按回车)或输入指定币种(如 USDT, SOL)"
- Default: (all currencies)
"all" - If user specifies → set to array (e.g.
config.currencies)["USDT", "SOL"]
For Flexible Earn: "活期监控币种:USDT, USDC(默认,按回车)或输入指定币种"
- Default:
["USDT", "USDC"] - If user specifies → set to array
config.flexible.currencies - Note: flexible requires per-currency API calls, so recommend keeping the list small
Step 3/3 — APY 阈值:
For Fixed Earn: "定期最低 APY 阈值:不限(默认,按回车)或输入百分比(如 8)"
- Default: (no limit)
0 - If user specifies → set to
config.fixed.globalMinApy(e.g. 8 →value / 100)0.08
For Flexible Earn: "活期最低 APY 阈值:8%(默认,按回车)或输入百分比"
- Default: (8%)
0.08 - If user specifies → set to
config.flexible.globalMinApyvalue / 100
Auto-detect language from conversation and write to .
config.jsonnotify.languageWrite config to .
~/.okx/earn-hunter/config.jsonDisplay summary using template (in user's language).
{baseDir}/templates/activation.md展示默认配置并请求用户确认或自定义。每一步均提供默认值 — 用户可按回车接受。
步骤1/3 — 扫描范围:
"扫描范围(可多选):
[1] Flash Earn(闪赚)
[2] Fixed Earn(定期赚币)
[3] Flexible Earn(活期赚币)
默认:全选"
- 默认值:全部启用
- 用户选择特定选项 → 禁用未选中的选项
- 用户仅选择[1] → 设置、
config.fixed.enabled = false;跳过步骤2/3和3/3config.flexible.enabled = false - 用户仅选择[3] → 设置、
config.flash.enabled = false;进入活期专属配置(步骤2/3询问活期监控币种,步骤3/3询问活期APY阈值)config.fixed.enabled = false
步骤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(例如8 →value / 100)0.08
针对Flexible Earn:"活期最低APY阈值:8%(默认,按回车)或输入百分比"
- 默认值:(8%)
0.08 - 用户指定阈值 → 将设置为
config.flexible.globalMinApyvalue / 100
自动从对话中检测语言并写入的字段。
config.jsonnotify.language将配置写入。
~/.okx/earn-hunter/config.json使用模板(用户语言版本)展示配置摘要。
{baseDir}/templates/activation.mdStep 4 — Smoke Test & Delivery Confirmation
步骤4 — 冒烟测试与交付确认
Smoke test always sends a notification, regardless of setting.
verboseLog- Run one scan cycle immediately with forced on so output is always produced, even when there are no opportunities. Temporarily flip
verboseLog, run the script, then restore it — and useverboseLogso smoke-test dedup keys go under theEH_TEST_NAMESPACE=1prefix and don't pollute production state:test:bashjq '.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) - If new opportunities found → the script sends the normal notification (rendered from templates)
- If no opportunities found → with forced on, the script sends the brief status. Optionally append the activation confirmation message: "Earn Hunter 已激活,当前暂无新机会,将在下一轮自动扫描。" (Use
verboseLogas base, append the no-opportunity note){baseDir}/templates/activation.md - TG or Lark channel → ask: "已向 {channel} 发送测试消息,请确认是否收到?"
- User confirms → proceed to Step 5
- Not received → troubleshoot (see )
notify-channels.md - 5 min no response → ping once
- Session channel → skip confirmation
Note: The smoke test ignores setting — it always produces output to verify the full pipeline works end-to-end.
verboseLog无论设置如何,冒烟测试始终会发送通知。
verboseLog- 立即运行一次完整扫描周期,强制开启,确保即使没有新机会也会生成输出。临时修改
verboseLog设置,运行脚本后恢复原设置 — 并使用verboseLog,使冒烟测试的去重键以EH_TEST_NAMESPACE=1为前缀,避免污染生产状态:test:bashjq '.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) - 若发现新机会 → 脚本发送常规通知(从模板渲染)
- 若未发现新机会 → 由于强制开启,脚本会发送简短状态信息。可选择性追加激活确认消息: "Earn Hunter已激活,当前暂无新机会,将在下一轮自动扫描。" (以
verboseLog为基础,追加无机会提示){baseDir}/templates/activation.md - TG或Lark渠道 → 询问: "已向{channel}发送测试消息,请确认是否收到?"
- 用户确认 → 继续执行步骤5
- 未收到消息 → 排查问题(详见)
notify-channels.md - 5分钟无响应 → 再次提醒
- 会话渠道 → 跳过确认步骤
注意: 冒烟测试会忽略设置 — 始终生成输出以验证端到端流程是否正常工作。
verboseLogStep 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 works:
env.snapshotbash
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" → is incomplete or missing. Re-run Preflight step to regenerate it. Do NOT proceed to Step 5 — the cron job will fail silently.
env.snapshot - Show the user: "已验证 cron 最小环境下可正常执行。如果此步失败,说明 env.snapshot 中的工具路径有误。"
关键: 用户的交互shell包含完整PATH,但cron环境不包含。运行第二次冒烟测试模拟cron的最小环境,验证是否有效:
env.snapshotbash
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" → 不完整或缺失。重新运行前置检查步骤生成该文件。请勿继续执行步骤5 — cron任务会静默失败。
env.snapshot - 告知用户:"已验证cron最小环境下可正常执行。如果此步失败,说明env.snapshot中的工具路径有误。"
Step 5 — Set Up Scheduler
步骤5 — 设置调度器
The scheduling mechanism depends on . Branch on the platform.
platform.json.scheduler.type调度机制取决于的字段。根据平台分支处理。
platform.json.scheduler.typeOpenClaw (scheduler.type = "openclaw-cron"
)
scheduler.type = "openclaw-cron"OpenClaw(scheduler.type = "openclaw-cron"
)
scheduler.type = "openclaw-cron"On OpenClaw, scheduling is done inside the conversation by calling the in-session agent tool — never an OS command or 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.
cronopenclaw cronCall the tool with and a shaped like this (read from for the frequency):
cronaction: "add"job.scheduler.intervalplatform.json- :
name"earn-hunter-hourly" - :
schedule— derive{ "kind": "every", "everyMs": 3600000 }fromeveryMs(scheduler.interval→ 3600000,"1h"→ 1800000,"30m"→ 7200000)"2h" - :
sessionTarget— run in an isolated session, not the main one"isolated" - :
payload{ "kind": "agentTurn", "message": "执行 earn-hunter 扫描", "lightContext": true }- runs the turn with a lightweight bootstrap context (skips workspace bootstrap files) → lower token cost per tick.
lightContext: true - Token budget: OpenClaw cron jobs have no per-job tool-whitelist field (the old flag no longer exists). The scan stays cheap because it runs the
--tools exec,read,writeCLI throughokxand 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.exec
- :
delivery— pushes the turn's output back to the conversation channel that created the job.{ "mode": "announce" }
When it fires, the isolated agent runs the prompt → Scan Cycle (which runs with channel /stdout) → relays the result → delivers it here. Any new opportunity is therefore sent automatically.
"执行 earn-hunter 扫描"scripts/scan.shsessionannounceDo NOT emit any shell/ CLI command in the conversation — drive scheduling only through the tool.
openclaw croncron在OpenClaw平台上,调度通过对话内调用会话中的 Agent工具完成 — 切勿使用系统命令或 CLI(此场景下CLI路径存在权限问题)。建议用户直接在聊天中设置:创建的cron任务会继承当前会话的渠道,因此扫描输出会直接返回至本对话。
cronopenclaw cron调用工具,设置,并传入如下格式的(从的读取调度频率):
cronaction: "add"jobplatform.json.scheduler.interval- :
name"earn-hunter-hourly" - :
schedule— 根据{ "kind": "every", "everyMs": 3600000 }转换为scheduler.interval(everyMs→3600000,"1h"→1800000,"30m"→7200000)"2h" - :
sessionTarget— 在独立会话中运行,而非主会话"isolated" - :
payload{ "kind": "agentTurn", "message": "执行 earn-hunter 扫描", "lightContext": true }- 以轻量引导上下文运行轮次(跳过工作区引导文件)→ 每次轮次的令牌成本更低。
lightContext: true - 令牌预算: OpenClaw cron任务无单任务工具白名单字段(旧的标志已不再存在)。扫描成本较低,因为它通过
--tools exec,read,write调用execCLI,且不依赖160+个okx MCP工具 — 因此只要独立cron Agent未配置加载okx MCP服务器,就仅会使用常规工具(exec/read/write)。加载哪些工具由Agent配置决定,而非此任务。okx
- :
delivery— 将轮次输出推送至创建任务的对话渠道。{ "mode": "announce" }
任务触发时,独立Agent会执行指令 → 扫描周期(运行,渠道为/标准输出)→ 转发结果 → 机制将结果推送至本对话。因此新机会会自动发送。
"执行 earn-hunter 扫描"scripts/scan.shsessionannounce请勿在对话中输出任何shell/ CLI命令 — 仅通过工具进行调度。
openclaw croncronOS-crontab platforms (scheduler.type = "cron"
or "launchagent"
— Claude Code / Hermes / Generic)
scheduler.type = "cron""launchagent"系统crontab平台(scheduler.type = "cron"
或"launchagent"
— Claude Code / Hermes / 通用平台)
scheduler.type = "cron""launchagent"These use OS scheduler → . The script does everything (CLI calls, filter, dedup, render, curl notifications) with zero LLM cost.
scripts/scan.shInstall the script — copy the skill's into the state dir so the scheduler has a stable path:
scripts/scan.shbash
mkdir -p ~/.okx/earn-hunter
cp {baseDir}/scripts/scan.sh ~/.okx/earn-hunter/scan.sh
chmod +x ~/.okx/earn-hunter/scan.shSet up scheduler — try crontab first, fallback to LaunchAgent on macOS if cron daemon is not running:
bash
undefined这些平台使用系统调度器 → 。脚本会完成所有操作(CLI调用、过滤、去重、渲染、curl发送通知),且零LLM成本。
scripts/scan.sh安装脚本 — 将技能的复制到状态目录,确保调度器有稳定的路径:
scripts/scan.shbash
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
undefinedResolve 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 ( == ), immediately check if the cron daemon is running:
uname -sDarwinbash
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 with the resolved paths:
~/Library/LaunchAgents/com.okx.earn-hunter.plistbash
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.plistWrite to . Inform user:
scheduler.type = "launchagent"platform.json"macOS cron 服务未运行,已自动切换为 LaunchAgent 调度(无需 sudo,重启自动恢复)。"
Notes:
- OAuth mode → omit from the plist
OKX_PROFILE(or set to empty).EnvironmentVariables - LaunchAgent plist paths must be absolute (no ). The activation flow expands
~at generation time.$HOME - means the first scan runs immediately after loading.
RunAtLoad: true - The script reads /
config.json, writesplatform.json/state.json, and sends notifications via curl to TG Bot API or Lark Webhook itself. No agent involvement needed at tick time.notify.log - The script exits 0 and produces no output when there are no new opportunities and — this is the intended silent behavior.
verboseLog=false
IMPORTANT: On OS-scheduler platforms, do NOT use agent-platform / Routines (Claude Code , cloud Routines). These spawn LLM sessions per tick and cannot reliably push external notifications. (OpenClaw is the exception above — it uses its in-session tool with delivery by design.)
/loop/loopcronannounceNODE_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( == )上,立即检查cron守护进程是否运行:
uname -sDarwinbash
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.plistbash
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,并通过curl直接调用TG Bot API或Lark Webhook发送通知。触发时无需Agent参与。notify.log - 当无新机会且时,脚本会以0状态码退出且无输出 — 这是预期的静默行为。
verboseLog=false
重要提示: 在系统调度器平台上,请勿使用Agent平台的 / Routines功能(Claude Code的、云Routines)。这些功能会在每次触发时生成LLM会话,且无法可靠推送外部通知。(OpenClaw是例外 — 它通过会话内的工具结合交付机制实现调度。)
/loop/loopcronannounceScan Cycle
扫描周期
The entire Scan Cycle is implemented by (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.
scripts/scan.shOpenClaw isolated cron turn: the job's prompt routes here. Run (with , the script prints any notification to stdout); relay that stdout as the turn's response. The cron job's delivery then pushes it to the conversation channel. Do not curl TG/Lark from this turn — delivery is handled by .
scripts/scan.shplatform.jsonnotify.channel = "session"announceannounce整个扫描周期由(纯shell + jq,零LLM成本)实现。无论是由系统crontab、OpenClaw独立cron Agent轮次触发,还是由用户在交互会话中触发("执行 earn-hunter 扫描"),扫描周期完全相同:运行脚本并转发输出结果。请勿用自然语言重新实现扫描步骤 — 脚本是唯一的事实来源。
scripts/scan.shOpenClaw独立cron轮次: 任务指令会路由至此。运行(中,脚本会将任何通知打印到标准输出);将标准输出作为轮次响应转发。cron任务的交付机制会将结果推送至对话渠道。请勿在此轮次中调用TG/Lark的curl接口 — 交付由机制处理。
scripts/scan.shplatform.jsonnotify.channel = "session"announceannounceHow the agent runs a scan (interactive trigger)
Agent如何运行扫描(交互式触发)
bash
undefinedbash
undefinedProfile: 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脚本执行逻辑(详见references/scan-logic.md
中的规范)
references/scan-logic.md- Reads
~/.okx/earn-hunter/config.json - Runs scan commands (based on /
flash.enabled/fixed.enabled):flexible.enabled- Flash:
okx [--profile live] earn flash-earn projects --status 0,100 --json - Fixed: (auto-fallback to
okx [--profile live] earn savings fixed-products --json+rate-historyon CLI <1.3.3)fixedOffers - Flexible: for each currency in ,
flexible.currenciesokx [--profile live] earn savings rate-history --ccy <ccy> --limit 1 --json
- Flash:
- Filters (two-layer APY threshold, terms filter, currency filter; flexible uses threshold-crossing model)
- Dedups against (
~/.okx/earn-hunter/state.json,state.flash["<id>:<status>"],state.fixed["<ccy>:<term>:<rate>"])state.flexible["<ccy>"] - If new opportunities → renders the matching template (flash / fixed / flexible / mixed) and sends via the detected channel (TG → Lark → session), logging to
notify.log - Updates (write new keys; flash ID-level diff cleanup; fixed key-level diff cleanup; flexible threshold-crossing diff cleanup; 7-day TTL; failure counter)
state.json - If no new opportunities: → brief status;
verboseLog=true→ silent exit 0, no output, nothing sentverboseLog=false - Error handling: consecutive-failure counter (alert at 3, then reset); 401/session-expired → credential alert + stop
Channel routing inside the script: detection order TG (+) → Lark ( ) → session (stdout). A of // in forces that channel.
$TELEGRAM_BOT_TOKEN$TELEGRAM_CHAT_IDplatform.json.notify.lark_webhooknotify.channeltelegramlarksessionplatform.jsonAuth / profile: the script never reads or prints credentials. It delegates all auth to the CLI (which reads ). Profile is injected only via the env var.
okx~/.okx/config.tomlOKX_PROFILEFlexible Earn dedup model: Unlike Flash/Fixed which dedup by specific opportunity, Flexible uses a threshold-crossing model — key is just . 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.
<ccy>- 读取
~/.okx/earn-hunter/config.json - 运行扫描命令(基于/
flash.enabled/fixed.enabled):flexible.enabled- Flash:
okx [--profile live] earn flash-earn projects --status 0,100 --json - Fixed: (CLI版本<1.3.3时自动回退到
okx [--profile live] earn savings fixed-products --json+rate-history)fixedOffers - Flexible: 针对中的每个币种,执行
flexible.currenciesokx [--profile live] earn savings rate-history --ccy <ccy> --limit 1 --json
- Flash:
- 过滤(两层APY阈值、期限过滤、币种过滤;活期使用阈值穿越模型)
- 与进行去重(
~/.okx/earn-hunter/state.json、state.flash["<id>:<status>"]、state.fixed["<ccy>:<term>:<rate>"])state.flexible["<ccy>"] - 若发现新机会 → 渲染匹配模板(flash / fixed / flexible / 混合)并通过检测到的渠道发送(TG → Lark → 会话),同时记录到
notify.log - 更新(写入新键;清理flash ID级差异;清理fixed键级差异;清理活期阈值穿越差异;7天TTL;失败计数器)
state.json - 若无新机会:→ 发送简短状态;
verboseLog=true→ 静默退出0,无输出,不发送任何内容verboseLog=false - 错误处理:连续失败计数器(达到3次时发送告警,之后重置);401/会话过期 → 凭证告警 + 停止扫描
脚本内的渠道路由: 检测顺序为TG(+)→ Lark(的)→ 会话(标准输出)。中设置为//时会强制使用对应渠道。
$TELEGRAM_BOT_TOKEN$TELEGRAM_CHAT_IDplatform.json.notify.lark_webhookplatform.jsonnotify.channeltelegramlarksession认证/配置文件: 脚本从不读取或打印凭证。所有认证操作委托给 CLI(读取)。仅通过环境变量注入配置文件。
okx~/.okx/config.tomlOKX_PROFILEFlexible Earn去重模型: 与Flash/Fixed按特定机会去重不同,活期采用阈值穿越模型 — 键仅为。当APY超过阈值时发送一次通知;APY保持在阈值以上时静默;APY低于阈值时重置(差异清理会删除对应键)。此模型避免因利率波动导致频繁通知。
<ccy>Purchase Guide
申购引导
When user wants to subscribe to a Fixed Earn product after receiving a notification.
Read for the complete flow.
{baseDir}/references/purchase-guide.mdSummary:
- Parallel balance check (funding + trading + flexible earn)
- Compare fixed APR vs flexible lendingRate
- Calculate recommended amount:
min(idle + movable_simple_earn, lendQuota) - Present recommendation with comparison hint
- 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流程摘要:
- 并行检查余额(资金账户 + 交易账户 + 活期赚币账户)
- 对比固定APR与活期lendingRate
- 计算推荐申购金额:
min(idle + movable_simple_earn, lendQuota) - 展示推荐方案并附带对比提示
- 用户确认 → 重新检查产品可用性(售罄防护)→ 移交至技能
okx-cex-earn
purchase-guide.md- 余额 < minLend → 展示缺口
- 金额 > lendQuota → 自动封顶并提示
- 赎回成功但申购失败 → 警告用户,资金已回到资金账户
- 通知与申购之间产品售罄 → 告知用户
重要提示: earn-hunter不直接执行写入操作。它会将控制权转移给技能。
okx-cex-earnConfig Management
配置管理
Read for field definitions and natural language examples.
{baseDir}/references/config-reference.mdWhen user wants to change settings:
- Parse intent → map to config field (see for field mapping)
config-reference.md - Read the target JSON file (or
config.json) → modify the field → write backplatform.json - 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用户想要修改设置时:
- 解析意图 → 映射到配置字段(详见中的字段映射)
config-reference.md - 读取目标JSON文件(或
config.json)→ 修改字段 → 写回文件platform.json - 读取更新后的文件 → 向用户确认修改结果
例外: TG凭证无法通过自然语言修改。告知用户直接设置环境变量。
Pause/Resume
暂停/恢复
Branch on :
platform.json.scheduler.typeOpenClaw () — manage via the in-session tool (no CLI):
openclaw-croncron- Pause: call with
cron, targeting theaction: "update"job, patchearn-hunter-hourly(or{ "enabled": false }to delete it).action: "remove" - Resume: with
action: "update"(or re-create as in Activation Step 5).{ "enabled": true } - Use to find the job id.
action: "list"
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.typeOpenClaw() — 通过会话内的工具管理(无需CLI):
openclaw-croncron- 暂停: 调用工具,设置
cron,目标为action: "update"任务,更新earn-hunter-hourly(或设置{ "enabled": false }删除任务)。action: "remove" - 恢复: 设置并传入
action: "update"(或按照激活步骤5重新创建任务)。{ "enabled": true } - 使用查找任务ID。
action: "list"
系统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":
- 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 - Ask: "是否保留配置和历史数据?"
- Yes → only remove scheduler
- No → also remove directory
~/.okx/earn-hunter/
用户说出"卸载" / "uninstall"时:
- 停止调度器(与暂停操作相同)。对于LaunchAgent,还需删除plist文件:
launchctl unload ~/Library/LaunchAgents/com.okx.earn-hunter.plist && rm -f ~/Library/LaunchAgents/com.okx.earn-hunter.plist - 询问:"是否保留配置和历史数据?"
- 是 → 仅删除调度器
- 否 → 同时删除目录
~/.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
undefinedForce 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 for exact alert message templates.
{baseDir}/templates/error-alert.md| Error | Action |
|---|---|
| Stop scan. Send alert (凭证失效 template). Load |
| Network error / timeout | Retry once silently. If still fails, skip this cycle. |
| 3 consecutive scan failures | Send alert (连续失败 template). Counter stored in |
| Reset by writing |
| Notification send fails | Log to |
| Send alert: "earn-hunter 未配置,请运行首次激活流程。" |
| Dual-client suspected (user mentions both platforms) | Warn: "建议仅在一个客户端运行 earn-hunter,避免重复通知。" |
| Send brief status (not silent). |
具体告警消息模板详见。
{baseDir}/templates/error-alert.md| 错误类型 | 处理动作 |
|---|---|
| 停止扫描。发送告警(凭证失效模板)。若处于交互模式则加载 |
| 网络错误 / 超时 | 静默重试一次。若仍失败则跳过本轮扫描。 |
| 连续3次扫描失败 | 发送告警(连续失败模板)。计数器存储在 |
| 通过写入 |
| 通知发送失败 | 记录到 |
运行时缺少 | 发送告警:"earn-hunter未配置,请运行首次激活流程。" |
| 疑似双客户端(用户提及多个平台) | 警告:"建议仅在一个客户端运行earn-hunter,避免重复通知。" |
| 发送简短状态(不静默)。 |
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 for OKX auth.
okx config init - Output: Use for all okx commands. Render results as markdown tables.
--json - 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. is always
config.simulatedTrading.false
- 安全性: 切勿在聊天中接收凭证。TG令牌仅通过环境变量设置。引导用户通过进行OKX认证。
okx config init - 输出: 所有okx命令均使用参数。将结果渲染为markdown表格。
--json - 日志: 所有通知发送结果记录到。
~/.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:
- 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.snapshotfallback.resolve_bin - Sanity check first. Before running any CLI command, verify the binary exists. Missing tool → immediate exit with clear error, not silent failure.
- Never swallow stderr. Use (capture into variable) instead of
2>&1for CLI calls. Otherwise error messages vanish and2>/dev/nullis empty.last_error - Smoke test must simulate production. Activation smoke test runs twice: once in user shell (verify credentials/network), once in minimal (verify cron can run). Don't pass Step 5 if cron smoke fails.
env -i PATH=/usr/bin:/bin - Alert templates must handle empty fields. If is empty string, append "check
last_error; empty error usually means PATH issue" guidance.~/.okx/earn-hunter/cron.log - Tool paths use snapshot + fallback, not global PATH. written at activation with exact paths;
env.snapshottries snapshot →resolve_bin→ hardcoded common paths. Survivescommand -v/ Homebrew upgrades.nvm switch
这些约束适用于earn-hunter的所有变更:
- cron PATH ≠ 用户shell PATH。任何cron触发的脚本必须独立解析工具路径 — 切勿假设cron继承用户登录会话的PATH。脚本使用+
env.snapshot兜底方案。resolve_bin - 先进行完整性检查。运行任何CLI命令前,验证二进制文件是否存在。工具缺失 → 立即退出并显示清晰错误,而非静默失败。
- 切勿忽略stderr。CLI调用使用(捕获到变量)而非
2>&1。否则错误消息会丢失,导致2>/dev/null为空。last_error - 冒烟测试必须模拟生产环境。激活冒烟测试运行两次:一次在用户shell中(验证凭证/网络),一次在最小环境中(验证cron可运行)。若cron冒烟测试失败,请勿进入步骤5。
env -i PATH=/usr/bin:/bin - 告警模板必须处理空字段。若为空字符串,追加"检查
last_error;空错误通常意味着PATH问题"的指导信息。~/.okx/earn-hunter/cron.log - 工具路径使用快照+兜底方案,而非全局PATH。激活时写入包含精确路径;
env.snapshot尝试快照路径 →resolve_bin→ 硬编码常见路径。可在command -v/ Homebrew升级后正常工作。nvm switch