Earn Hunter
Automated monitor for OKX Flash Earn, Fixed Earn, and Flexible Earn (Simple Earn) opportunities.
= the directory containing this SKILL.md file. All relative paths (references/, templates/, config/) are resolved from here.
Preflight
-
Verify
CLI installed:
. If missing, install via
npm install -g @okx_ai/okx-trade-cli
.
On OpenClaw, also verify the in-session
tool is available in the agent tool list (used for scheduling — not the
CLI).
-
Check optional dependent skills:
Optional skills (not required for scanning/notifications):
- — needed for purchase guide (subscription execution)
- — 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,后续可手动安装。"
- Preflight continues regardless of skill installation result
-
Auth mode detection — run both, first match wins:
- → has non-empty field → API Key mode. Add to all commands.
- No API key + → → OAuth mode. No flag needed.
- Neither → stop. Load skill and follow login steps.
-
Init config and state:
- If 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 .
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) —
is required for scanning. Verify with
; if missing, install (
/
).
Platform & Channel Detection
Three independent dimensions: platform (where the agent runs), scheduler (what triggers scans), notification channel (where alerts go).
Platform Detection (active probe + user confirmation)
- Probe environment clues:
- env var exists? → hint: OpenClaw
- Agent tool list contains / tools? → hint: OpenClaw
- env var exists or succeeds? → hint: Hermes Agent
- 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
{baseDir}/config/<confirmed_platform>.default.json
to ~/.okx/earn-hunter/platform.json
- Hermes Agent → copy
{baseDir}/config/claude-code.default.json
as base, set to , to
- Generic → copy
{baseDir}/config/claude-code.default.json
as base, set to , to
- 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
field (returns
,
,
, or
).
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 扫描'来触发。"
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:
- scheduler.type = — scheduled via the in-session 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 delivery. notify.channel defaults to so the scan prints to stdout for to push (avoids double-send).
Claude Code / Hermes / Generic ():
- scheduler.type = — scheduled via OS crontab → (zero LLM token cost), notification via TG / Lark curl from the script itself.
Notification Channels (independent of platform)
Detect in priority order (PRD requirement: TG first):
- Telegram — and both set → TG ready
- Lark —
platform.notify.lark_webhook
non-empty → Lark ready
- 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.
Skill Routing
Activation Flow
First-time setup. Only confirm platform — everything else uses smart defaults.
Step 1 — Platform Detection & Confirmation
See
Platform Detection. Probe environment → ask user to confirm → write
.
Step 2 — Detect Notification Channel & Confirm
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):
- Check and 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
- Check
platform.notify.lark_webhook
or Lark MCP tools:
- Webhook set and valid (starts with and contains ) → Lark ready
- Webhook set but format invalid (does not start with or missing ) → 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}
你希望通知发到哪里?
- {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 env vars
- If user picks Lark → ask for webhook URL, validate format (starts with , contains ), write to
platform.notify.lark_webhook
- If user picks session → write and warn: "⚠ 离线状态下不会收到通知,建议后续配置外部渠道。"
Write confirmed channel to
.
Step 3 — Confirm Scan Config (3-step with defaults)
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 currencies)
- If user specifies → set to array (e.g. )
For Flexible Earn: "活期监控币种:USDT, USDC(默认,按回车)或输入指定币种"
- Default:
- 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: (no limit)
- If user specifies → set
config.fixed.globalMinApy
to (e.g. 8 → )
For Flexible Earn: "活期最低 APY 阈值:8%(默认,按回车)或输入百分比"
- Default: (8%)
- If user specifies → set
config.flexible.globalMinApy
to
Auto-detect language from conversation and write to
.
Write config to
~/.okx/earn-hunter/config.json
.
Display summary using
{baseDir}/templates/activation.md
template (in user's language).
Step 4 — Smoke Test & Delivery Confirmation
Smoke test always sends a notification, regardless of setting.
- Run one scan cycle immediately with forced on so output is always produced, even when there are no opportunities. Temporarily flip , run the script, then restore it — and use so smoke-test dedup keys go under the 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)
- 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
{baseDir}/templates/activation.md
as base, append the no-opportunity note)
- TG or Lark channel → ask: "已向 {channel} 发送测试消息,请确认是否收到?"
- User confirms → proceed to Step 5
- Not received → troubleshoot (see )
- 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.
Step 4b — Cron Environment Smoke Test (OS-crontab platforms only)
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:
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" → 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 中的工具路径有误。"
Step 5 — Set Up Scheduler
The scheduling mechanism depends on
. Branch on the platform.
OpenClaw (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.
Call the
tool with
and a
shaped like this (read
from
for the frequency):
- :
- :
{ "kind": "every", "everyMs": 3600000 }
— derive from ( → 3600000, → 1800000, → 7200000)
- : — run in an isolated session, not the main one
- :
{ "kind": "agentTurn", "message": "执行 earn-hunter 扫描", "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 flag no longer exists). The scan stays cheap because it runs the CLI through 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.
- : — pushes the turn's output back to the conversation channel that created the job.
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.
Do NOT emit any shell/
CLI command in the conversation — drive scheduling only through the
tool.
OS-crontab platforms ( or — Claude Code / Hermes / Generic)
These use
OS scheduler → . The script does everything (CLI calls, filter, dedup, render, curl notifications) with
zero LLM cost.
Install the script — copy the skill's
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
# Resolve tool directories from the current 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:
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, .
- If cron daemon is not running → remove the crontab entry and proceed to Step B.
- On Linux → skip the check (cron is always available), .
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
. Inform user:
"macOS cron 服务未运行,已自动切换为 LaunchAgent 调度(无需 sudo,重启自动恢复)。"
Notes:
- OAuth mode → omit from the plist (or set to empty).
- LaunchAgent plist paths must be absolute (no ). The activation flow expands at generation time.
- means the first scan runs immediately after loading.
- The script reads / , writes / , 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 — this is the intended silent behavior.
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.)
Scan 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.
OpenClaw isolated cron turn: the job's prompt routes here. Run
(with
notify.channel = "session"
, 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
.
How the agent runs a scan (interactive trigger)
bash
# Profile: pass OKX_PROFILE only in API Key mode (omit for OAuth mode).
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 + case), tell the user "本轮扫描完成,无新机会" — do NOT fabricate a "scan complete" message into a channel; the silence is intentional.
What the script does (see for the spec it implements)
- Reads
~/.okx/earn-hunter/config.json
- Runs scan commands (based on / / ):
- Flash:
okx [--profile live] earn flash-earn projects --status 0,100 --json
- Fixed:
okx [--profile live] earn savings fixed-products --json
(auto-fallback to + on CLI <1.3.3)
- Flexible: for each currency in ,
okx [--profile live] earn savings rate-history --ccy <ccy> --limit 1 --json
- 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>"]
, )
- If new opportunities → renders the matching template (flash / fixed / flexible / mixed) and sends via the detected channel (TG → Lark → session), logging to
- Updates (write new keys; flash ID-level diff cleanup; fixed key-level diff cleanup; flexible threshold-crossing diff cleanup; 7-day TTL; failure counter)
- If no new opportunities: → brief status; → silent exit 0, no output, nothing sent
- 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.
Auth / 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.
Flexible 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.
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:
- 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
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
.
Config Management
Read
{baseDir}/references/config-reference.md
for field definitions and natural language examples.
When user wants to change settings:
- Parse intent → map to config field (see for field mapping)
- Read the target JSON file ( or ) → modify the field → write back
- 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.
Pause/Resume
OpenClaw () — manage via the in-session
tool (no CLI):
- Pause: call with , targeting the job, patch (or to delete it).
- Resume: with (or re-create as in Activation Step 5).
- Use to find the job id.
- Pause:
crontab -l | grep -v 'earn-hunter' | crontab -
- Resume: Re-add the crontab entry (same as Activation Step 5).
- 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.
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
Test Mode
Trigger phrases: "测试 earn-hunter" / "earn-hunter smoke test" / "测试定时任务触发"
Behavior — run the script with the Test Mode hooks (no separate logic needed):
bash
# Force verbose so output is always produced; write dedup keys under test: prefix.
EH_TEST_NAMESPACE=1 OKX_PROFILE=live ~/.okx/earn-hunter/scan.sh # with config.verboseLog temporarily set to true
- Execute a full Scan Cycle — the script runs the same scan, but isolates state writes
- Force-send notification — temporarily set so the script always sends output regardless of whether opportunities are found (restore afterwards)
- Dedup writes to test namespace — prefixes dedup keys with (e.g. ); these keys are immune to diff cleanup (only TTL removes them), so test runs do not pollute production state
- 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: tool shows the job?)
- Last 5 lines of
~/.okx/earn-hunter/notify.log
- Completion message: "测试完成。test: 前缀的 state 不影响正式去重,正式扫描不受影响。"
Error Handling
Read
{baseDir}/templates/error-alert.md
for exact alert message templates.
| Error | Action |
|---|
| 401 / "Session expired" | Stop scan. Send alert (凭证失效 template). Load if interactive. |
| Network error / timeout | Retry once silently. If still fails, skip this cycle. |
| 3 consecutive scan failures | Send alert (连续失败 template). Counter stored in state.consecutive_failures
, reset after alert. |
| corrupted | Reset by writing {"flash":{},"fixed":{},"consecutive_failures":0,"last_error":""}
. May cause one round of duplicate notifications. |
| Notification send fails | Log to , continue scan. Dedup key NOT added (next cycle retries). |
| missing at runtime | Send alert: "earn-hunter 未配置,请运行首次激活流程。" |
| Dual-client suspected (user mentions both platforms) | Warn: "建议仅在一个客户端运行 earn-hunter,避免重复通知。" |
| + no hits | Send brief status (not silent). |
i18n
- All notifications rendered in user's language (detected at activation, stored in )
- 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)
Global Notes
- Security: Never accept credentials in chat. TG token only via env vars. Guide users to for OKX auth.
- Output: Use 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. is always .
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 + fallback.
- 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 for CLI calls. Otherwise error messages vanish and is empty.
- 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.
- Alert templates must handle empty fields. If is empty string, append "check
~/.okx/earn-hunter/cron.log
; empty error usually means PATH issue" guidance.
- Tool paths use snapshot + fallback, not global PATH. written at activation with exact paths; tries snapshot → → hardcoded common paths. Survives / Homebrew upgrades.