# Earn Hunter

> Automatically monitors OKX Flash Earn, Fixed Earn and Flexible Earn opportunities, sends push notifications, and guides subscription. 自动监控 OKX 闪赚、定期和活期赚币机会，推送通知并引导申购。Use when user says: 有闪赚通知我, 监控赚币, monitor earn, notify me about earn, 定时检查理财, 执行 earn-hunter 扫描, earn-hunter scan, 活期年化高了通知我, 监控活期.

- **Type:** Skill
- **Install:** `agentstack add skill-okx-agent-skills-earn-hunter`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [okx](https://agentstack.voostack.com/s/okx)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [okx](https://github.com/okx)
- **Source:** https://github.com/okx/agent-skills/tree/github-main/skills/earn-hunter

## Install

```sh
agentstack add skill-okx-agent-skills-earn-hunter
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# 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.

## 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
4. 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.
5. 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](#platform-detection-active-probe--user-confirmation)
   - 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 .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 扫描'来触发。"

### Configuration Files

| File | Scope | Content |
|---|---|---|
| `config.json` | Shared | Scan scope (flash/fixed/flexible), currencies, APY thresholds, terms, language, verboseLog |
| `platform.json` | Platform-specific | Scheduler type/interval, notification channel, TG/Lark credentials |
| `state.json` | Shared | Dedup 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.

### 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.

---

## Skill Routing

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

---

## Activation Flow

First-time setup. Only confirm platform — everything else uses smart defaults.

### Step 1 — Platform Detection & Confirmation

See [Platform Detection](#platform-detection-active-probe--user-confirmation). Probe environment → ask user to confirm → write `platform.json`.

### 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 `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`.

### 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"` (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).

### Step 4 — Smoke Test & Delivery Confirmation

**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.

### 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 `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 中的工具路径有误。"

### Step 5 — Set Up Scheduler

The scheduling mechanism depends on `platform.json` `.scheduler.type`. Branch on the platform.

#### 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](#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.

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

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
# 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 (`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/ear

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [okx](https://github.com/okx)
- **Source:** [okx/agent-skills](https://github.com/okx/agent-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-okx-agent-skills-earn-hunter
- Seller: https://agentstack.voostack.com/s/okx
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
