feat: publish xianyu hunter hermes skill package

This commit is contained in:
2026-05-06 19:26:45 +08:00
commit 6384156097
59 changed files with 9627 additions and 0 deletions
+435
View File
@@ -0,0 +1,435 @@
---
name: xianyu-hunter-monitor
description: Use when BOSS asks to monitor, search, filter, analyze, or follow up on 闲鱼/goofish products through the Disrush/xianyu-hunter Playwright-based assistant. Covers setup, login, API config, safe monitoring mode, task creation, result inspection, and operational limits.
version: 1.0.0
author: Hermes Agent
license: MIT
metadata:
hermes:
tags: [xianyu, goofish, monitoring, shopping, playwright, automation]
related_skills: [github-repo-management, goofish-monitor-deploy, systematic-debugging]
---
# Xianyu Hunter Monitor
## Overview
`Disrush/xianyu-hunter` is a Node.js + Playwright web automation project for 闲鱼/Goofish. The local BOSS fork now runs in **Hermes analysis mode**: the web app only searches product pages, extracts product cards/details, and performs lightweight local prefiltering. It does **not** require configuring an AI API inside the project, and it does **not** contact sellers or run the upstream auto-chat stage.
For BOSS, use this skill in **Hermes-controlled collection + analysis mode**: Hermes should operate the CLI directly (login/check/create/run/report/schedule) and treat the Web UI as a backup inspection channel only. Run modest searches, collect candidate listings into `data/tasks.json`, then analyze/summarize candidates from Hermes (or generate a Markdown report with `tools/hermes_report.mjs`).
Session-specific implementation notes for the Hermes-main-control refactor are stored in `references/hermes-main-control-refactor-2026-05.md`.
LAN dashboard login button and stale port/PID restart notes are stored in `references/web-login-lan-and-kill-stale-server-2026-05.md`.
Local working clone established during analysis:
```bash
/Users/chick/.Hermes/workspace/research/xianyu-hunter
```
The analyzed revision was `de268ba` (`feat: 消息监听从 DOM 轮询改造为 WebSocket 实时推送 + DOM 补漏`).
## When to Use
Use this skill when the user asks to:
- 监测闲鱼商品、扫货、蹲商品、查低价、找二手货。
- Analyze 闲鱼 listings against criteria such as price, condition, location, personal seller, warranty, battery health, etc.
- Run or configure `xianyu-hunter` / `闲鱼扫货助手` / `goofish.com` automation.
- Inspect previous xianyu-hunter task results under its `data/tasks.json`.
- Decide whether to contact sellers based on filtered candidates.
Do **not** use this skill for:
- Generic price research that does not involve 闲鱼/goofish.
- High-frequency scraping or bulk automated messaging.
- Any action that commits BOSS to a purchase, payment, or binding offer.
## Important Safety / Consent Rules
1. **Default to analysis-only.** Do not start auto-chat unless BOSS explicitly asks to contact sellers.
2. **No purchase commitments.** Never authorize payment, deposits, order creation, or final purchase via automation.
3. **Respect account risk.** The project uses a real browser and randomized delays, but 闲鱼 may still show captcha, rate-limit, or penalize automation. Keep pages/tasks modest.
4. **Manual captcha/login.** Login and verification must be completed by the user in the visible Chromium window.
5. **Protect secrets.** API key is stored locally at `data/config.json`; never print it. If inspecting config, redact as `[REDACTED]`.
6. **Do not hide automation risk.** Tell BOSS when a run may trigger platform verification or account risk.
## Project Anatomy
Key files:
- `server.mjs` — Express + WebSocket dashboard, listens on `PORT` or `3000`.
- `lib/browser.mjs` — Playwright persistent Chromium profile at `browser-data`, `headless: false`.
- `lib/login.mjs` — opens `https://www.goofish.com/` and checks login state.
- `lib/search.mjs` — searches `https://www.goofish.com/search?q=...`, applies filters, scrapes cards.
- `lib/filter.mjs` — opens detail pages and performs LLM fine filtering.
- `lib/chat.mjs` — optional seller chat engine, message extraction, WebSocket monitoring.
- `lib/ai.mjs` — OpenAI-compatible `/chat/completions` calls and prompts.
- `lib/config.mjs` — provider/model/API key config persisted to `data/config.json`.
- `data/tasks.json` — task state, candidate products, logs, chat sessions.
Runtime data:
- `browser-data/` stores Chromium login/session state.
- `data/config.json` stores AI provider config.
- `data/tasks.json` stores task progress/results.
## First-Time Setup
From the local clone:
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
npm install
npx playwright install chromium
```
Start the dashboard:
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
PORT=3000 npm start
```
Open locally or from LAN:
```text
http://localhost:3000
http://192.168.2.69:3000
```
Then:
1. Start the dashboard; no AI/API settings are needed in the web UI.
2. Use the Web UI button **打开闲鱼登录窗口** to trigger the Mac desktop Chromium login window, or run `node tools/xianyu_ops.mjs login` from the repo.
3. Complete QR/captcha/login through macOS remote desktop/screen sharing in the spawned Chromium window. The Web UI does not embed the Goofish login page.
4. If captcha/slider appears, BOSS must complete it manually.
## Recommended Monitoring-Only Task Parameters
Use restrained defaults:
- `maxPages`: 13 initially.
- `personalSeller`: true when searching high-scam/high-noise categories.
- `region`: set if local pickup matters.
- `priceMin`/`priceMax`: use broad but reasonable bounds.
- Requirements should include risk criteria, e.g. “只看国行、自用、无拆修、成色好;排除维修/配件/求购/商家批量”。
Preferred prompts for monitoring mode:
```text
coarsePrompt: 你是闲鱼商品监测助手。只做标题相关性初筛,目标是帮用户发现值得进一步分析的候选商品。只有标题与需求明确矛盾、明显是配件/维修/求购/租赁/广告时才排除;拿不准必须保留。返回纯JSON数组。
finePrompt: 你是闲鱼商品风险分析助手。根据商品详情判断是否值得用户人工查看。只做监测分析,不要鼓励自动联系卖家。优先识别:型号/规格不符、价格异常、疑似商家批量、维修/配件/求购、描述矛盾、成色/保修/拆修风险。拿不准可以通过,但理由必须提示待人工确认。返回JSON{"pass": true/false, "reason": "简短理由,包含关键风险或亮点"}
```
## Hermes CLI Control Flow
Preferred operation is through the unified CLI wrapper; the Web dashboard is only a backup view.
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
# Check/start/stop background local service
node tools/xianyu_ops.mjs status --port 3000
node tools/xianyu_ops.mjs start --port 3000
node tools/xianyu_ops.mjs stop
# Open persistent Chromium and let BOSS/Hermes check login state
node tools/xianyu_ops.mjs login
# If the dashboard lacks a login button, add a POST route that calls checkLogin()
# and a header button labelled “打开闲鱼登录窗口”; then restart the exact PID
# listening on port 3000 (the CLI stop command may lose its .runtime state).
# Create task only
node tools/xianyu_ops.mjs create \
--name 'Mac mini M4 监测' \
--queries 'Mac mini M4,macmini m4' \
--price-max 3300 \
--region 成都 \
--pages 2 \
--requirements '自用,国行,成色好,无拆修,预算3300以内;排除维修/配件/求购/商家批量'
# Full run: ensure service, create task, wait for completion, generate report
node tools/xianyu_ops.mjs run \
--name 'Mac mini M4 监测' \
--queries 'Mac mini M4,macmini m4' \
--price-max 3300 \
--region 成都 \
--pages 2 \
--requirements '自用,国行,成色好,无拆修,预算3300以内;排除维修/配件/求购/商家批量' \
--limit 30
```
The CLI starts the local service if needed, never requires project AI configuration, and never contacts sellers.
## Creating a Task via API
API/helper usage remains available for compatibility, but prefer `tools/xianyu_ops.mjs` above. The local fork starts collection immediately and does not require AI configuration.
### Helper script
A helper script was added to the local clone:
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
node tools/create_monitor_task.mjs \
--name 'Mac mini M4 监测' \
--queries 'Mac mini M4,macmini m4' \
--price-max 3300 \
--region 成都 \
--pages 2 \
--requirements '自用,国行,成色好,无拆修,预算3300以内;排除维修/配件/求购/商家批量;只做筛选分析,不自动联系卖家'
```
Environment override:
```bash
XIANHU_BASE_URL=http://127.0.0.1:3000 node tools/create_monitor_task.mjs ...
```
### Raw API
```bash
curl -sS http://127.0.0.1:3000/api/tasks \
-H 'Content-Type: application/json' \
-d '{
"name":"Mac mini M4 监测",
"queries":["Mac mini M4","macmini m4"],
"priceMin":null,
"priceMax":3300,
"region":"成都",
"personalSeller":true,
"maxPages":2,
"customRequirements":"自用,国行,成色好,无拆修,预算3300以内;排除维修/配件/求购/商家批量;只做筛选分析,不自动联系卖家",
"coarsePrompt":"你是闲鱼商品监测助手。只做标题相关性初筛,目标是帮用户发现值得进一步分析的候选商品。只有标题与需求明确矛盾、明显是配件/维修/求购/租赁/广告时才排除;拿不准必须保留。返回纯JSON数组。",
"finePrompt":"你是闲鱼商品风险分析助手。根据商品详情判断是否值得用户人工查看。只做监测分析,不要鼓励自动联系卖家。优先识别:型号/规格不符、价格异常、疑似商家批量、维修/配件/求购、描述矛盾、成色/保修/拆修风险。拿不准可以通过,但理由必须提示待人工确认。返回JSON{\"pass\": true/false, \"reason\": \"简短理由,包含关键风险或亮点\"}",
"analysisMode":"hermes"
}'
```
## Hermes Report Generation
After a task finishes, generate a local Markdown report from persisted candidates. Prefer the wrapper:
```bash
node tools/xianyu_ops.mjs report --task <task_id> --limit 30
```
Or call the report script directly:
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
node tools/hermes_report.mjs --task <task_id> --limit 30
# or omit --task to use the latest task
node tools/hermes_report.mjs --limit 30
```
The report is written to:
```text
reports/<task_id>-hermes-report.md
```
Use the generated report as a first-pass local risk summary, then add Hermes judgment in the chat response when BOSS asks for recommendations.
## Inspecting Results
Use API while server is running:
```bash
curl -sS http://127.0.0.1:3000/api/tasks | python3 -m json.tool
curl -sS http://127.0.0.1:3000/api/tasks/<task_id> | python3 -m json.tool
```
Or inspect persisted tasks directly:
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
python3 - <<'PY'
import json
from pathlib import Path
p=Path('data/tasks.json')
if not p.exists():
print('no tasks.json')
raise SystemExit
for t in json.loads(p.read_text()):
print('\n#', t.get('name'), t.get('id'), 'stage=', t.get('stage'))
products=t.get('products',{})
print('raw/coarse/fine:', len(products.get('raw',[])), len(products.get('coarseFiltered',[])), len(products.get('fineFiltered',[])))
for item in products.get('fineFiltered',[])[:20]:
print('-', item.get('title'), item.get('price') or item.get('detailPrice'), item.get('sellerLocation'), item.get('href'))
if item.get('description'):
print(' desc:', item['description'][:120].replace('\n',' '))
PY
```
When presenting results to BOSS, include:
- Title and price.
- Location/seller info.
- Link.
- Why it passed/fail if logs contain the reason.
- Risk flags: price too low, “仅展示/配件/维修/求购”, no description, high 想要人数, seller looks commercial, missing proof/warranty.
- Suggested next manual question if contacting seller is needed.
## Hermes Web Analysis + Notifications
When BOSS asks to make xianyu-hunter more productized, prefer Web-visible features rather than backend-only endpoints. The current local fork can expose:
- `POST /api/tasks/:id/analyze` to score fine-filtered candidates, persist `hermesAnalysis`, generate `reports/<task_id>-analysis.md`, and optionally notify.
- `GET/PUT /api/notify-config` backed by `data/notify-config.json` for Hermes webhook, Bark, PushPlus, and generic webhook channels.
- A header **通知设置** modal, task-level **执行分析** button, and **Hermes 分析** tab with recommendation/caution/reject cards.
Implementation and verification details are in `references/web-analysis-notifications-2026-05.md`.
## Scheduling with Hermes
For scheduled monitoring, create a Hermes cron job whose prompt is self-contained and uses the CLI wrapper. A reusable prompt template is stored at:
```text
/Users/chick/.Hermes/workspace/research/xianyu-hunter/tools/cron_prompt_template.md
```
Recommended cron job settings:
- `workdir`: `/Users/chick/.Hermes/workspace/research/xianyu-hunter`
- `enabled_toolsets`: `["terminal", "file"]`
- `deliver`: origin/current chat
- prompt: include task name, keywords, page count, region/price, requirements, and explicitly state no seller contact.
The cron prompt should run:
```bash
node tools/xianyu_ops.mjs run --name '<任务名>' --queries '<关键词1,关键词2>' --pages 1 --requirements '<需求>' --limit 30
```
If login/captcha is required, the cron run should report that manual intervention is needed rather than trying to bypass verification.
## Web Login / Cookie Persistence
The local BOSS fork should expose account login controls in the Web UI, not only through the CLI. The preferred product shape is a visible **账号登录 / Cookie** entry in the header that opens a login status panel.
Expected implementation pattern:
- `server.mjs` exposes login APIs:
- `GET /api/login/status` — opens/uses the persistent Playwright profile to probe a real Goofish search page and reports `loggedIn`, `loginWall`, `cookieCount`, `cardCount`, current URL/title, and storage path.
- `POST /api/login/open` — opens the persistent Chromium profile at Goofish for manual扫码/验证码 login.
- `POST /api/login/wait` — waits/rechecks login completion using the same login detection as tasks.
- `POST /api/login/close-browser` — closes the Playwright browser context without deleting cookies.
- `public/index.html` header should have `账号登录 / Cookie`, opening a modal that explains the login flow and shows status.
- `public/app.js` should render login status and logs instead of relying on alert-only UX.
- Cookies are persisted automatically in the Playwright persistent profile at `browser-data/`; do not export or print raw cookies.
- Cookie JSON upload should be treated as a fast local persistence step: after `ctx.addCookies()`, return status from `ctx.cookies()` only. Do **not** navigate to Goofish inside the import endpoint; run the slower real network/login probe only when the user clicks “检测登录状态”.
Important detection pitfall: `nick--` and avatar elements can still exist on a login-wall page. Treat login as valid only when the search probe does **not** show `登录后可以更懂你` / `立即登录` / `请登录` and either product cards load or enough relevant cookies are present. Do not accept homepage-only login checks as sufficient.
Session-specific implementation notes are captured in `references/web-login-cookie-panel-2026-05.md`.
Cookie JSON import timeout and verification notes are captured in `references/cookie-json-import-timeout-2026-05.md`.
Web dashboard QR-login and account status panel implementation notes are captured in `references/web-qr-login-account-panel-2026-05.md`.
BOSS later simplified the preferred login UX: the visible Web login modal should expose only **获取/刷新登录二维码** and **刷新账号状态**; hide legacy buttons such as manual browser open, Cookie JSON upload, wait-detect, and close-session from the normal flow. Details are captured in `references/web-login-simplified-buttons-2026-05.md`.
Login UX, persistent-profile lock recovery (`ProcessSingleton` / `SingletonLock`), and partial-result recovery from `coarseFiltered` are captured in `references/login-ux-locks-and-partial-results-2026-05.md`.
Web task editing, manual save-only tasks, scheduling, `maxItems`, AI-optimize fallback, API smoke tests, and stale port restart pitfalls are captured in `references/web-task-editing-scheduling-ai-optimize-2026-05.md`.
Web cache cleanup implementation notes are captured in `references/web-cache-cleanup-2026-05.md`.
Web dashboard homepage/productized empty-state implementation notes are captured in `references/web-home-dashboard-polish-2026-05.md`.
Home dashboard clickable overview/workflow cards and deep-link behavior are captured in `references/web-home-clickable-dashboard-cards-2026-05.md`.
Return navigation after deep-linking from the home dashboard is captured in `references/web-home-return-navigation-2026-05.md`.
Important BOSS UX correction: do not present a direct `login.taobao.com` screenshot as the "Xianyu QR" even though Goofish uses Alibaba/Taobao unified login under the hood. Open/login from `goofish.com` first, label any redirect honestly, and prefer the visible browser login flow when BOSS wants to scan manually.
Real QR-login screenshot flow for Feishu/mobile convenience is captured in `references/qr-login-screenshot-2026-05.md`. When BOSS says Cookie JSON is inconvenient, prefer opening the real Taobao/Goofish login page in the persistent profile, screenshotting the QR, keeping the browser alive briefly, and replying with `MEDIA:/tmp/xianyu-login-qr.png`.
## Troubleshooting
### Project asks for AI API configuration
The local BOSS fork should not ask for API configuration. If this appears, verify you are running `/Users/chick/.Hermes/workspace/research/xianyu-hunter` and that `server.mjs` no longer gates `/api/tasks` behind `isConfigured()`. Also ensure the browser is loading `public/app.js?v=9` and `public/index.html` from the local fork.
### Browser asks for login or captcha
This is expected. BOSS must complete it in the visible Chromium window. Do not attempt to bypass captcha.
### CLI login says OK but search page still shows login wall
`node tools/xianyu_ops.mjs login` can produce a false-positive `loggedIn: true` if the homepage check is weak, while the search page body still contains `登录后可以更懂你` / `立即登录` and result cards never load. Verify with a real search page before blaming selectors:
```bash
node --input-type=module <<'JS'
import { chromium } from 'playwright';
const ctx = await chromium.launchPersistentContext('./browser-data', {headless:false});
const page = await ctx.newPage();
await page.goto('https://www.goofish.com/search?q='+encodeURIComponent('e3 1245 v3'), {waitUntil:'domcontentloaded', timeout:30000});
await page.waitForTimeout(8000);
console.log((await page.locator('body').innerText()).slice(0,1000));
await ctx.close();
JS
```
If the body shows `登录后可以更懂你,推荐你喜欢的商品!立即登录`, ask BOSS to manually log in in the visible Chromium window, then rerun the task. Do not treat zero results as a valid market result in this state.
### Search returns zero or selector errors
Goofish DOM changes often. Re-check selectors in:
- `lib/search.mjs` card selector: `a[class*="feeds-item-wrap"]`
- title selector: `[class*="row1-wrap-title"]`
- price selector: `[class*="price-wrap"]`
- detail selector in `lib/filter.mjs`
Open the browser page and inspect manually if needed.
### Task enters chat stage unexpectedly
The local BOSS fork should no longer enter `chatting`; `lib/task.mjs` stops after detail collection and clears `chatSessions`. If a task enters chat, you are probably running upstream code or an old cached working tree. Stop the task and re-check `lib/task.mjs` for `ChatManager` imports.
### Port conflict
Use another port:
```bash
PORT=3010 npm start
```
Set helper base URL:
```bash
XIANHU_BASE_URL=http://127.0.0.1:3010 node tools/create_monitor_task.mjs ...
```
## Recommended Future Hardening
If BOSS wants this as a durable production monitor, implement these patches in the repo:
1. Add `chatEnabled: false` task config and skip Stage 5 unless enabled.
2. Add a read-only CLI/API endpoint that returns candidate listings + AI reasons without starting chats.
3. Add scheduled runs with deduplication and notifications back to Hermes/Feishu.
4. Store task templates separately from results.
5. Add rate limits and cooldowns per keyword/account.
6. Add export formats: Markdown report, CSV, JSON.
## Verification Checklist
- [ ] `node -v` is >= 18.
- [ ] `npm install` completed in the repo clone.
- [ ] `npx playwright install chromium` completed.
- [ ] `node tools/xianyu_ops.mjs start --port 3000` launches the local service.
- [ ] `node tools/xianyu_ops.mjs status --port 3000` reports service state.
- [ ] Browser is logged into goofish.com.
- [ ] Task uses modest page count and monitoring-focused requirements.
- [ ] No AI/API settings banner appears in the dashboard.
- [ ] Task stops after detail collection; no seller contact/chat stage runs.
- [ ] `node tools/hermes_report.mjs --limit 30` can generate a report from `data/tasks.json`.
- [ ] Results are summarized with links, prices, risk flags, and suggested manual next steps.
@@ -0,0 +1,68 @@
# Cookie JSON Import Panel and Timeout Pitfall — 2026-05
Context: while adding Web UI account controls for the local BOSS `xianyu-hunter` fork, `POST /api/login/import-cookies` initially timed out even for valid Cookie JSON.
## Durable lesson
Do **not** make the Cookie JSON import endpoint perform a real Goofish page navigation immediately after `ctx.addCookies()`. In practice this can hang for 2030s+ because the persistent Playwright context may already have a stale/blocked tab, Goofish network verification may stall, or the login probe page may hit a login wall/captcha.
Instead, import should be a fast local persistence operation:
1. Parse payload as either `[...]`, `{ "cookies": [...] }`, or `{ "data": [...] }`.
2. Normalize cookie fields for Playwright (`name`, `value`, `domain`, `path`, `secure`, `httpOnly`, `sameSite`, optional `expires`).
3. `await ctx.addCookies(cookies)`.
4. Return local status from `await ctx.cookies()` only: imported count, relevant Goofish/Taobao/Alibaba cookie count, timestamp, and storage path.
5. Tell the UI/user to click **检测登录状态** for the slower network probe.
## Known-good response shape
For valid import, return quickly with JSON similar to:
```json
{
"ok": true,
"imported": 1,
"loggedIn": true,
"cookieCount": 10,
"url": "cookie-import://local",
"title": "Cookie 已导入,点击“检测登录状态”可联网验证",
"storage": "browser-data/"
}
```
`loggedIn` here is only a heuristic from cookie count, not a real network login verification.
## Verification commands
Run from `/Users/chick/.Hermes/workspace/research/xianyu-hunter`:
```bash
node --check lib/login.mjs && node --check server.mjs && node --check public/app.js
curl -sS -i -m 10 -X POST http://127.0.0.1:3000/api/login/import-cookies \
-H 'Content-Type: application/json' \
-d '{}' | sed -n '1,60p'
curl -sS -i -m 10 -X POST http://127.0.0.1:3000/api/login/import-cookies \
-H 'Content-Type: application/json' \
-d '{"cookies":[{"name":"test_cookie_for_import_probe","value":"1","domain":".goofish.com","path":"/"}]}' | sed -n '1,120p'
curl -sS -m 10 http://127.0.0.1:3000/ | grep -E '上传 Cookie JSON|账号登录 / Cookie'
```
Expected:
- Invalid payload returns HTTP 400 with the Chinese format error.
- Valid payload returns HTTP 200 within 10 seconds.
- UI contains `账号登录 / Cookie` and `上传 Cookie JSON`.
## Restart pitfall
If changes appear not to take effect, kill the exact listener PID on port 3000. The wrapper/background shell may leave a stale `node server.mjs` and Playwright Chromium process alive, so a normal restart can accidentally leave the old code serving requests.
```bash
for p in $(lsof -tiTCP:3000 -sTCP:LISTEN 2>/dev/null); do kill "$p" 2>/dev/null || true; done
sleep 1
for p in $(lsof -tiTCP:3000 -sTCP:LISTEN 2>/dev/null); do kill -9 "$p" 2>/dev/null || true; done
PORT=3000 node server.mjs
```
@@ -0,0 +1,101 @@
# Hermes 主控重构记录(2026-05
## 背景
BOSS 明确希望闲鱼采集项目由 Hermes 直接操作:登录检查、创建任务、分析结果、定时执行;Web UI 仅作为备用渠道。
## 当前本地项目定位
工作目录:
```bash
/Users/chick/.Hermes/workspace/research/xianyu-hunter
```
当前模式:
- Hermes 是主控入口。
- Web UI 仅备用查看。
- 项目不需要配置 AI API / API Key / 模型。
- 项目不会自动联系卖家、不会聊天、不会询价。
- 项目只负责:搜索采集、详情采集、本地保守预筛、持久化结果。
- 深度判断由 Hermes 根据 `data/tasks.json` 或报告完成。
## 新增/主要入口
统一 CLI
```bash
node tools/xianyu_ops.mjs <command>
```
命令:
```bash
node tools/xianyu_ops.mjs status --port 3000
node tools/xianyu_ops.mjs start --port 3000
node tools/xianyu_ops.mjs stop
node tools/xianyu_ops.mjs login
node tools/xianyu_ops.mjs create --name '<任务名>' --queries '<关键词1,关键词2>' --requirements '<需求>'
node tools/xianyu_ops.mjs run --name '<任务名>' --queries '<关键词1,关键词2>' --requirements '<需求>' --limit 30
node tools/xianyu_ops.mjs report --task <task_id> --limit 30
```
`run` 会:检查/启动服务 → 创建任务 → 等待完成 → 生成报告。
## 报告与定时
报告脚本:
```bash
node tools/hermes_report.mjs --task <task_id> --limit 30
```
报告输出:
```text
reports/<task_id>-hermes-report.md
```
定时任务提示模板:
```text
tools/cron_prompt_template.md
```
定时任务要显式声明:不联系卖家、不下单、不承诺交易;如遇登录/验证码,报告需要人工处理,不绕过验证。
## 已验证
已做语法检查:
```bash
node --check server.mjs
node --check lib/task.mjs
node --check lib/filter.mjs
node --check public/app.js
node --check tools/xianyu_ops.mjs
node --check tools/hermes_report.mjs
```
已验证服务可启动:
```bash
node tools/xianyu_ops.mjs start --port 3000
node tools/xianyu_ops.mjs status --port 3000
```
注意:真实闲鱼端到端采集仍取决于账号登录状态、验证码和 Goofish DOM 选择器是否仍有效。
## 未来会话触发语
用户说以下任意内容时,应加载 `xianyu-hunter-monitor`
- “帮我监测闲鱼 …”
- “蹲一下闲鱼 …”
- “分析最新闲鱼采集结果”
- “检查闲鱼登录状态”
- “启动闲鱼助手”
- “每 X 小时监测 …”
优先用 `tools/xianyu_ops.mjs`,不要让 BOSS 手动打开 Web 除非需要查看或排错。
@@ -0,0 +1,115 @@
# Xianyu-hunter Hermes send_message notifications (2026-05)
Session learning: BOSS wanted xianyu-hunter notification settings changed from a Hermes webhook URL to a Web-visible platform selector that sends through Hermes `send_message` directly.
## Implementation pattern
- Backend file: `/Users/chick/.Hermes/workspace/research/xianyu-hunter/lib/notifier.mjs`.
- Keep `GET/PUT /api/notify-config`, but add `targetOptions` to the returned config for UI dropdowns.
- Use channel type `hermes-send-message` instead of `hermes-webhook`.
- Migrate existing `hermes-webhook` channels to `hermes-send-message` and map bare `feishu` to the current Feishu DM when appropriate.
- Server-side send implementation can spawn Hermes Agent's Python venv and call the tool handler:
```js
const script = `import json\nfrom tools.send_message_tool import send_message_tool\nprint(send_message_tool({"action":"send","target":${JSON.stringify(target)},"message":${JSON.stringify(message)}}))\n`;
const child = spawn(
process.env.HERMES_PYTHON || '/Users/chick/.hermes/hermes-agent/venv/bin/python3',
['-c', script],
{ cwd: process.env.HERMES_AGENT_DIR || '/Users/chick/.hermes/hermes-agent' }
);
```
## UI pattern
- Frontend files: `public/app.js`, `public/index.html`.
- In notification modal, label Hermes channel as **Hermes 平台消息**.
- Provide a dropdown populated from `notifyConfig.targetOptions` and a separate optional custom target input.
- Target examples:
- `feishu:oc_3e963a150d98f254d8f38881bcfa1812`
- `weixin:o9cq809DWSYDlvbA9pj1akncjrhY@im.wechat`
- `weixin:o9cq808qCd1W-cIDo0iWjteE5lHY@im.wechat`
- `qqbot` if configured
- Bump static cache query strings (`app.js?v=...`, `style.css?v=...`) after UI edits.
## Verification
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
node --check server.mjs
node --check lib/notifier.mjs
node --check public/app.js
node tools/xianyu_ops.mjs stop || true
node tools/xianyu_ops.mjs start --port 3000
curl -sS http://127.0.0.1:3000/api/notify-config | python3 -m json.tool | head -90
```
Smoke-test notifier without running a real task:
```bash
node --input-type=module <<'JS'
import { sendNotifications } from './lib/notifier.mjs';
const task = { id: 'smoke-test', name: '通知配置 smoke test' };
const summary = {
minScore: 75, total: 1, recommendedCount: 1, cautionCount: 0, rejectedCount: 0,
reportPath: 'smoke-test',
topItems: [{ title: 'xianyu-hunter 通知测试', score: 99, verdict: 'recommend', price: '测试', location: '本机', reason: '验证服务端可直接调用 Hermes send_message', href: '' }]
};
const config = { enabled: true, notifyOnlyGood: false, minScore: 75, channels: [{ id:'smoke-feishu', type:'hermes-send-message', name:'飞书 smoke', enabled:true, target:'feishu:oc_3e963a150d98f254d8f38881bcfa1812' }] };
const r = await sendNotifications(task, summary, { force: true, config });
console.log(JSON.stringify(r, null, 2));
if (!r.ok) process.exit(1);
JS
```
## Test notification channel
When BOSS asks to add/test notification channels, add a Web-visible test path rather than relying on real task analysis:
- Backend route in `server.mjs`:
- `POST /api/notify-test`
- Accepts `{ config, title }` where `config` has the same shape as `/api/notify-config`.
- Builds a fake task `{ id: 'notify-test', name: '通知渠道测试' }` plus a one-item `summary`.
- Calls `sendNotifications(task, summary, { force: true, config: { ...config, enabled: true, notifyOnlyGood: false } })` so the test bypasses score/good-only gating.
- Return HTTP 200 if `result.ok`, otherwise 500 with per-channel errors.
- Frontend in `public/app.js`:
- Add `testNotifyConfigFromForm()` beside `saveNotifyConfigFromForm()`.
- It calls `collectNotifyConfig()`, requires at least one enabled channel, posts to `/api/notify-test`, and writes ✅/❌ per-channel results into `#notify-log`.
- It should not require saving first; this lets BOSS test temporary target edits directly from the modal.
- UI in `public/index.html`:
- Add a modal footer button: `发送测试通知` wired to `testNotifyConfigFromForm()`.
- Bump static cache query strings (`app.js?v=...`, `style.css?v=...`).
Verification command:
```bash
curl -sS -X POST http://127.0.0.1:3000/api/notify-test \
-H 'Content-Type: application/json' \
-d '{"config":{"enabled":true,"notifyOnlyGood":false,"minScore":75,"channels":[{"id":"api-test-feishu","type":"hermes-send-message","name":"API测试飞书","enabled":true,"target":"feishu:oc_3e963a150d98f254d8f38881bcfa1812"}]},"title":"xianyu-hunter API 测试通知"}' \
| python3 -m json.tool
```
A known-good result has `ok: true` and `results[0].ok: true`; in the 2026-05 run this sent a real Feishu test message successfully.
Do **not** spawn Homebrew Python such as `/Users/chick/homebrew/opt/python@3.11/bin/python3.11` for Hermes `send_message`. It may lack Hermes dependencies (`yaml`, gateway modules) and fail with:
```text
Failed to load gateway config: No module named 'yaml'
```
Use Hermes' own venv interpreter:
```text
/Users/chick/.hermes/hermes-agent/venv/bin/python3
```
You can verify it with:
```bash
/Users/chick/.hermes/hermes-agent/venv/bin/python3 - <<'PY'
import yaml
import gateway.config
from tools.send_message_tool import send_message_tool
print(send_message_tool({'action':'list'}))
PY
```
@@ -0,0 +1,78 @@
# Goofish Login UX, Browser Lock, and Partial-Result Recovery — 2026-05
## What happened
During a Goofish/Xianyu search task, a temporary helper opened a direct Taobao unified-login URL and sent its screenshot as a "real login QR". BOSS correctly objected that it looked like a Taobao login page and not the expected Xianyu entry.
Goofish web login is backed by Alibaba/Taobao account infrastructure, so `login.taobao.com` can be normal after following the Goofish login flow. However, for BOSS UX, do **not** present a direct Taobao URL/screenshot as if it were the Xianyu login entry.
## Preferred login workflow
1. Start/keep the dashboard service.
2. Open `https://www.goofish.com/` in the persistent Playwright profile (`browser-data/`).
3. Let BOSS interact with the visible browser window and click the Goofish page's own login/modal/QR controls.
4. If a screenshot is needed, label it accurately: "Goofish page login/modal; may redirect to Taobao unified login".
5. After BOSS says login is done, close any temporary helper Chromium using the same profile before running task automation.
6. Verify with `GET /api/login/status` against a real search page, not homepage only.
## Pitfall: ProcessSingleton / SingletonLock
If a temp QR/login helper is still using `browser-data/`, later dashboard/API/task runs may fail with:
```text
browserType.launchPersistentContext: Failed to create a ProcessSingleton for your profile directory
... browser-data/SingletonLock: File exists (17)
```
Safe recovery used in this session:
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
pkill -f 'xianyu_goofish_login_entry_tmp.mjs|xianyu_qr_login_tmp.mjs' || true
pkill -f 'Google Chrome for Testing.*xianyu-hunter/browser-data' || true
sleep 2
for p in $(lsof -tiTCP:3000 -sTCP:LISTEN 2>/dev/null); do kill "$p" 2>/dev/null || true; done
sleep 1
PORT=3000 node server.mjs
```
Then verify:
```bash
curl -sS -m 60 http://127.0.0.1:3000/api/login/status | python3 -m json.tool
```
A good result includes `loggedIn: true`, `loginWall: false`, nonzero `cookieCount`, and product cards on a search URL.
## Partial-result recovery
If a run is interrupted during `fine_filter`, `tools/hermes_report.mjs` may report `候选 0` because it only sees `fineFiltered`. Do not conclude there were no products. Inspect `data/tasks.json` and mine `products.coarseFiltered` or `products.raw` directly.
Example used for E3-1245v3:
```bash
python3 - <<'PY'
import json,re
from pathlib import Path
p=Path('data/tasks.json')
data=json.loads(p.read_text())
t=[x for x in data if x.get('id')=='<task_id>'][0]
items=t.get('products',{}).get('coarseFiltered',[]) or t.get('products',{}).get('raw',[])
def price_num(v):
m=re.search(r'(\d+(?:\.\d+)?)', str(v or '').replace(',',''))
return float(m.group(1)) if m else None
for it in items:
title=(it.get('title') or '').lower().replace('-',' ')
text=' '.join(str(it.get(k,'')) for k in ['title','description','detailDescription']).lower()
if '1245' in title and 'v3' in title and not any(w in text for w in ['回收','求购','主板','套装','整机','散热','维修','不包含 cpu']):
print(it.get('price') or it.get('detailPrice'), it.get('location') or it.get('sellerLocation'), it.get('title'), it.get('href') or it.get('url'))
PY
```
## Presentation guidance
When reporting results to BOSS:
- State the live status first (login ok, task id, counts raw/coarse/fine, whether run completed or was interrupted).
- If a report says 0 candidates but raw/coarse has data, explicitly explain that this is a stage/report limitation.
- Rank candidates by closeness to budget and risk, and call out exclusions (e.g. "不包含 CPU", "不能进系统", "回收", "主板套装").
@@ -0,0 +1,62 @@
# Real QR Login Capture for Xianyu/Goofish
Session learning: BOSS found Cookie JSON import inconvenient and asked for the real login QR code directly in chat. The web app's `POST /api/login/open` opens a visible Chromium window on the Mac, but that is not enough for Feishu/mobile use; generating a screenshot and returning it as `MEDIA:/tmp/xianyu-login-qr.png` is more convenient.
## Working pattern
From `/Users/chick/.Hermes/workspace/research/xianyu-hunter`:
1. Close the existing Playwright browser context through the app if it may hold the profile lock:
```bash
curl -sS -m 10 -X POST http://127.0.0.1:3000/api/login/close-browser \
-H 'Content-Type: application/json' -d '{}'
```
2. Run a Playwright helper from inside the repo, not `/tmp`, so `import { chromium } from 'playwright'` resolves against the project dependencies.
3. Launch a persistent context using the real project profile:
```js
const userDataDir = '/Users/chick/.Hermes/workspace/research/xianyu-hunter/browser-data';
const ctx = await chromium.launchPersistentContext(userDataDir, {
headless: false,
viewport: { width: 1100, height: 900 },
locale: 'zh-CN',
args: ['--disable-blink-features=AutomationControlled', '--no-first-run'],
});
```
4. Navigate to Taobao/Goofish login:
```js
const loginUrl = 'https://login.taobao.com/member/login.jhtml?redirectURL=' + encodeURIComponent('https://www.goofish.com/');
await page.goto(loginUrl, { waitUntil: 'domcontentloaded', timeout: 45000 });
```
5. If possible, click QR-login affordances such as `扫码登录`, `二维码登录`, `.icon-qrcode`, or `[class*=qrcode]`, wait a few seconds, then screenshot:
```js
await page.screenshot({ path: '/tmp/xianyu-login-qr.png', fullPage: false });
```
6. Keep the process alive for about 5 minutes so the QR remains valid while BOSS scans:
```js
await page.waitForTimeout(5 * 60 * 1000);
```
7. Reply with Feishu media syntax:
```text
MEDIA:/tmp/xianyu-login-qr.png
```
After BOSS scans, call `/api/login/status` or run the normal task/login verification.
## Pitfalls
- Running the helper from `/tmp` failed with `ERR_MODULE_NOT_FOUND: Cannot find package 'playwright'`; create/run the helper inside the xianyu-hunter repo or set module resolution explicitly.
- A persistent Chromium profile can be locked by the dashboard's existing browser context. Use `/api/login/close-browser` first or kill stale Goofish Playwright processes if navigation hangs.
- `vision_analyze` can fail due provider/API key issues; do not depend on it to validate the QR screenshot. If the screenshot exists and is recent, send it; BOSS can scan/confirm.
- Do not print or export raw cookies. QR login is a user-driven login flow and is preferable to asking BOSS to handle Cookie JSON when convenience is the goal.
@@ -0,0 +1,99 @@
# Web Hermes Analysis + Notifications (2026-05)
Session learning from extending BOSS's local `xianyu-hunter` fork with Web-visible analysis and notification controls.
## Product shape BOSS expects
When adding analysis/notification capability, make it visible in the Web dashboard, not only as backend APIs:
- Header button: **通知设置**.
- Task controls: **执行分析** beside edit/duplicate/start/rerun/delete.
- New task tab: **Hermes 分析** with recommendation/caution/reject summary and item cards.
- Product list should show analysis verdict badges and concise reasons inline.
- Analysis should persist into `data/tasks.json` and generate a Markdown report under `reports/`.
## Backend API pattern
Add a lightweight local analysis layer and notifier:
- `lib/analyzer.mjs`
- `buildAnalysis(task, { limit })` returns `{ items, summary }`.
- `renderAnalysisMarkdown(task, summary)` writes `reports/<task_id>-analysis.md`.
- Include item-level `hermesAnalysis: { score, verdict, reason, flags, positives, analyzedAt }`.
- `lib/notifier.mjs`
- `GET /api/notify-config` and `PUT /api/notify-config` backed by `data/notify-config.json`.
- `sendNotifications(task, summary, { force })` supports:
- `hermes-webhook`: POST `{ message, target, source, taskId }`.
- `bark`: POST `{ title, body, group }` to `https://api.day.app/<key>` or configured endpoint.
- `pushplus`: POST `{ token, title, content, template:'txt' }`.
- `webhook`: generic JSON `{ title, message, taskId }`.
- `server.mjs`
- `POST /api/tasks/:id/analyze` should call `taskManager.analyzeTask`, write report, persist summary, and optionally notify.
## TaskManager persistence details
- Add `getTaskFull(id)` for routes that need full in-memory task data.
- Add `setTaskAnalysis(id, analysis)` that merges item `hermesAnalysis` into `products.fineFiltered`, stores `task.hermesAnalysis`, persists to disk, and emits `task:updated`.
- Include `hermesAnalysis` in `_serialize`, `_serializeFull`, and `_loadFromDisk`.
- Clear `task.hermesAnalysis = null` when resetting products/rerunning a task.
- New/duplicated tasks should initialize `hermesAnalysis: null`.
## Frontend implementation details
- Bump asset query versions in `index.html` after changing CSS/JS to avoid stale browser cache (`style.css?v=11`, `app.js?v=11`, etc.).
- `public/app.js` helpers:
- `analyzeTaskNow(id)` calls `POST /api/tasks/:id/analyze` with `{ notify: true }`, then refreshes task detail.
- `renderAnalysis()` renders empty state, stat cards, report path, and scored item cards.
- When rendering raw product rows, map analysis from `products.fineFiltered` by `id`; raw items often do not contain the merged `hermesAnalysis` directly.
- Preserve notification channel `id` when saving from the form; otherwise repeated saves create new channel IDs.
## Verification checklist
Run syntax checks:
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
node --check server.mjs
node --check lib/task.mjs
node --check lib/analyzer.mjs
node --check lib/notifier.mjs
node --check public/app.js
```
Restart exact stale listener if `xianyu_ops.mjs stop` says `no-recorded-server` but port is still occupied:
```bash
pid=$(lsof -nP -iTCP:3000 -sTCP:LISTEN -t || true)
[ -z "$pid" ] || kill "$pid"
node tools/xianyu_ops.mjs start --port 3000
```
Smoke test APIs:
```bash
curl -fsS http://127.0.0.1:3000/api/notify-config | python3 -m json.tool
curl -fsS http://127.0.0.1:3000/api/tasks -o /tmp/xh_tasks.json
TASK=$(python3 - <<'PY'
import json
j=json.load(open('/tmp/xh_tasks.json'))
print(j[0]['id'] if j else '')
PY
)
curl -fsS -X POST "http://127.0.0.1:3000/api/tasks/$TASK/analyze" \
-H 'Content-Type: application/json' \
-d '{"notify":false,"limit":10}' | python3 -m json.tool
```
Important pitfall: piping a large `/api/tasks` response directly into `python3 - <<'PY'` consumes stdin for the script, not JSON. Save curl output to a temp file first.
## Model-specific scoring pitfall: E3-1245v3
For CPU searches like `E3-1245v3`, scoring must distinguish true target models from related models used as SEO keywords.
Rules added in `lib/analyzer.mjs`:
- If the task query indicates `E3-1245v3`, require an exact-ish match: `e3\s*-?\s*1245\s*[_-]?\s*v3`.
- Penalize titles whose main model is another E3 v3, e.g. `E3-1275v3 ... 性能超 E3-1245v3`, because the title may only use 1245v3 as a comparison/keyword.
- Flag failure terms beyond obvious `坏/不亮/暗病`, including `不能进系统`.
This reduced false recommendations in the E3-1245v3 task from related CPUs and broken parts.
@@ -0,0 +1,62 @@
# Web Cache Cleanup Entry — 2026-05
BOSS requested a Web UI entry to clear xianyu-hunter cached collection results while optionally preserving Hermes-approved quality results.
Implemented in local clone:
```text
/Users/chick/.Hermes/workspace/research/xianyu-hunter
```
Files changed:
- `lib/task.mjs`
- Added `TaskManager.clearTaskCache(id, { mode, minScore })`.
- Refuses running tasks by returning `null`.
- `mode: 'all'`: clears `products.raw`, `products.coarseFiltered`, `products.fineFiltered`, `hermesAnalysis`, chat arrays; resets stage to `pending`.
- `mode: 'hermes-recommend'`: keeps only `fineFiltered` items with `item.hermesAnalysis.verdict === 'recommend'`.
- `mode: 'hermes-pass'`: keeps `recommend` or `caution` items whose score is >= `minScore`.
- For keep modes, retained items are written to all three product buckets so the task still visibly has cached quality results.
- Adds a log line and broadcasts `task:updated`.
- `server.mjs`
- Added `POST /api/tasks/:id/clear-cache` with JSON body:
```json
{ "mode": "all|hermes-recommend|hermes-pass", "minScore": 75 }
```
- `public/index.html`
- Header button: `清除缓存`.
- Modal `#clear-cache-modal-overlay` with scope (`active`/`all`), mode, min score, preview, and log.
- Cache-busted assets to `style.css?v=15` and `app.js?v=15`.
- `public/app.js`
- Added `showClearCacheModal`, `hideClearCacheModal`, `renderClearCachePreview`, `clearCacheFromModal`, `getClearTargets`, `countQualityItems`.
- Frontend can clean current selected task or all non-running tasks.
- Shows preview of raw/coarse/fine counts and how many quality items will be retained.
- `public/style.css`
- Added `.clear-cache-modal`, `.cache-warning`, `.cache-preview*`, `.cache-counts` styles.
Verification performed:
```bash
node --check public/app.js
node --check server.mjs
node --check lib/task.mjs
```
Restart caveat:
- `node tools/xianyu_ops.mjs start --port 3000` reported running but did not replace stale PID 9407, so new route initially 404'd.
- Correct recovery was to kill the stale listener (`kill 9407`) and start tracked server with `PORT=3000 node server.mjs`.
- Active verified process after implementation: PID 11594 from Hermes background session `proc_41df821080bd`.
Smoke test:
1. Fetch `/` and verify `app.js?v=15`, `clear-cache-modal-overlay`.
2. Create save-only `__cache_smoke_test__` task via `POST /api/tasks`.
3. Call `POST /api/tasks/<id>/clear-cache` with `{ "mode": "all" }`.
4. Verify response `ok: true` and all counts zero.
5. Delete smoke task.
@@ -0,0 +1,50 @@
# Web Home Dashboard Clickable Cards (2026-05)
Session context: BOSS liked the xianyu-hunter Web dashboard visual polish, then requested that the home cards be actionable instead of decorative.
Implementation pattern used in `/Users/chick/.Hermes/workspace/research/xianyu-hunter`:
- Keep the home dashboard as the landing/empty state, but make overview cards real `<button>` elements.
- Add a generic routing helper in `public/app.js`:
- `findLatestTask(predicate)` sorts tasks by `updatedAt || createdAt` descending.
- `openHomeCard(target)` maps dashboard metrics to the most relevant task:
- `tasks` → latest task
- `raw` → latest task with `products.rawCount > 0`
- `fine` → latest task with `products.fineCount > 0`
- `recommend` → latest task with `hermesAnalysis.recommendedCount > 0`, then open `analysis` tab
- `running` → latest running task
- `scheduled` → latest task with `config.schedule.enabled`
- If no matching task exists, open `showCreateModal()` instead of silently doing nothing.
- Extend `selectTask(id, tab = 'products')` so cards can deep-link into either the product list or Hermes analysis tab after `refreshTask()` and `renderTaskDetail()`.
- Convert workflow steps into clickable buttons:
- 登录 → `showLoginModal()`
- 新建任务 → `showCreateModal()`
- 采集/预筛 → `openHomeCard('fine')`
- Hermes 分析 → `openHomeCard('recommend')`
- Style clickable cards with visible affordances:
- `cursor: pointer`, hover translate, border/glow changes, focus-visible outline.
- Include a small action line such as `查看分析结果 →` so the behavior is legible.
Verification checklist:
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
node --check public/app.js
git diff --check -- public/index.html public/style.css public/app.js
node tools/xianyu_ops.mjs status --port 3000
node --input-type=module <<'JS'
const base = 'http://127.0.0.1:3000';
for (const path of ['/', '/style.css?v=<CACHE_VERSION>', '/app.js?v=<CACHE_VERSION>']) {
const res = await fetch(base + path);
const text = await res.text();
console.log(path, res.status, text.length);
if (!res.ok) process.exitCode = 1;
}
JS
```
Pitfalls:
- If cards are changed from `div` to `button`, preserve visual reset (`font: inherit`, `text-align: left`, `color: var(--text)`, `width: 100%`) or they will inherit browser button styling.
- Bump both `style.css?v=N` and `app.js?v=N` in `public/index.html` after Web UI changes, otherwise BOSS may see stale assets.
- Do not make a dashboard card a dead click target; when there is no matching data, open the task creation modal as a useful fallback.
@@ -0,0 +1,49 @@
# Web home dashboard polish and verification (2026-05)
Use this note when continuing productization of BOSS's local `xianyu-hunter` Web dashboard.
## Context
BOSS disliked an empty/weak landing page and prefers productized, visible Web UI improvements over backend-only work. The local fork's homepage was changed from a plain empty state into a dashboard in `public/index.html`, with runtime data populated by `renderHomeDashboard()` in `public/app.js`.
## Implemented shape
- `public/index.html`
- Main content empty state is now `.home-dashboard`.
- Includes hero card, overview grid `#home-overview`, recent task list `#home-task-list`, and workflow panel.
- Cache-busting versions bumped to `style.css?v=17` and `app.js?v=17`.
- `public/app.js`
- `renderHomeDashboard()` computes totals: task count, running count, raw cached products, fine candidates, Hermes recommendations, scheduled task count.
- Renders recent tasks sorted by `updatedAt || createdAt`.
- Called after WebSocket connect/task create/update/finish and when no active task is selected.
- `public/style.css`
- Added `/* Home dashboard */` section: hero card, overview cards, recent task cards, workflow cards, responsive breakpoints, light/dark theme support.
## Verification commands
Run from `/Users/chick/.Hermes/workspace/research/xianyu-hunter`:
```bash
node --check public/app.js
node --check server.mjs
git diff --check -- public/index.html public/style.css public/app.js
node tools/xianyu_ops.mjs status --port 3000
node --input-type=module <<'JS'
const base = 'http://127.0.0.1:3000';
for (const path of ['/', '/style.css?v=17', '/app.js?v=17', '/api/tasks']) {
const res = await fetch(base + path);
const text = await res.text();
console.log(path, res.status, text.length, text.slice(0, 80).replace(/\n/g, ' '));
if (!res.ok) process.exitCode = 1;
}
JS
```
Expected: all syntax/diff checks pass, service is running on port 3000, and all HTTP probes return 200.
## Pitfalls
- After executing tool calls, always convert results into a visible response for BOSS; an empty assistant message after tools is treated as a failure.
- Use `git diff --check -- <paths>` directly; do not combine `--cached --no-index` incorrectly.
- When adding UI assets, bump query-string cache versions in `public/index.html` so BOSS sees changes immediately.
- Keep dashboard changes directly visible at `http://127.0.0.1:3000/` or LAN address; BOSS values immediately inspectable Web UI.
@@ -0,0 +1,43 @@
# Web Home Return Navigation — 2026-05
Context: after making the xianyu-hunter Web home dashboard cards clickable, BOSS pointed out that users could jump into a task detail page but had no obvious way back to the home dashboard.
Reusable fix pattern:
1. Add a `showHome()` function in `public/app.js` that:
- sets `activeTaskId = null`;
- calls `renderTaskList()` to clear sidebar active state;
- hides `#task-detail`;
- shows `#empty-state` / home dashboard;
- resets home scroll position if available;
- calls `renderHomeDashboard()` so stats/recent tasks refresh.
2. Make the top brand/title clickable and call `showHome()`.
3. Add a visible header button labelled `首页` for discoverability.
4. Add a task-detail pipeline control `← 首页` next to task actions so the return path remains visible after jumping into a task.
5. Add CSS for `.brand-button`, `.btn-home-main`, and `.btn-home` so the controls look intentional and keyboard focus is visible.
6. Bump static asset cache versions in `public/index.html` after changing `public/app.js` or `public/style.css`.
Verification used:
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
node --check public/app.js
git diff --check -- public/index.html public/style.css public/app.js
node tools/xianyu_ops.mjs status --port 3000
node --input-type=module <<'JS'
const base = 'http://127.0.0.1:3000';
const checks = [
['/', 'showHome'],
['/style.css?v=<version>', 'brand-button'],
['/app.js?v=<version>', 'function showHome()'],
];
for (const [path, needle] of checks) {
const res = await fetch(base + path);
const text = await res.text();
console.log(path, res.status, text.length, text.includes(needle));
if (!res.ok || !text.includes(needle)) process.exitCode = 1;
}
JS
```
UX lesson: when dashboard cards deep-link into content, always provide an equally obvious return path. Do not rely only on browser back, especially in a single-page Web UI with hidden/shown panels.
@@ -0,0 +1,80 @@
# Web Login + Cookie Persistence Panel (2026-05)
Context: BOSS wanted the xianyu-hunter Web UI to support login like the original project, saving cookies for reuse. A plain “open login window” button was not enough; the Web UI needed a visible login/cookie panel with status detection.
## Implemented shape
Files in `/Users/chick/.Hermes/workspace/research/xianyu-hunter`:
- `lib/browser.mjs`
- Added `getOpenPagesInfo()` to report whether the persistent Playwright browser context is open and list page title/URL.
- `lib/login.mjs`
- Added `openLoginPage(emitLog)`.
- Added `getLoginStatus(existingPage = null)`.
- `checkLogin()` now uses stronger login status detection and logs cookie count + `browser-data/` persistence.
- `server.mjs`
- Imports `checkLogin`, `getLoginStatus`, `openLoginPage`, `closeBrowser`, `getOpenPagesInfo`.
- Added APIs:
- `GET /api/login/status`
- `POST /api/login/open`
- `POST /api/login/wait`
- `POST /api/login/close-browser`
- `public/index.html`
- Header button: `账号登录 / Cookie`.
- Login modal: status card, actions, help text, log panel.
- `public/app.js`
- Added `showLoginModal`, `hideLoginModal`, `renderLoginStatus`, `appendLoginLog`, `checkLoginStatus`, `openLoginWindow`, `waitLoginStatus`, `closeLoginBrowser`.
- `public/style.css`
- Added login modal styles (`.login-modal`, `.login-status-card`, `.login-actions`, `.login-help`, `.login-log`).
## Login detection rule
Do not trust homepage-only checks. In this session, CLI `login` said OK while the real search page still showed:
- `登录后可以更懂你,推荐你喜欢的商品!`
- `立即登录`
Use a real search probe such as:
```text
https://www.goofish.com/search?q=e3%201245%20v3
```
Treat status as logged in only if:
- login wall text is absent, and
- either product cards load or enough Goofish/Taobao/Alibaba cookies exist.
`nick--` and avatar elements alone are false-positive prone on login-wall pages.
## Verification commands
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
node --check server.mjs
node --check lib/browser.mjs
node --check lib/login.mjs
node --check public/app.js
# Restart if needed
lsof -tiTCP:3000 -sTCP:LISTEN | xargs -r kill
PORT=3000 node server.mjs
# Verify UI markers and API
curl -sS http://127.0.0.1:3000/ | grep -E '账号登录 / Cookie|闲鱼账号登录 / Cookie'
curl -sS http://127.0.0.1:3000/api/login/status | python3 -m json.tool
lsof -nP -iTCP:3000 -sTCP:LISTEN
```
Expected LAN URL for BOSSs Mac in this environment:
```text
http://192.168.2.69:3000
```
## Operational notes
- Cookies are stored in `browser-data/` via Playwright persistent context. Do not print raw cookies.
- `POST /api/login/open` opens a visible Chromium window on the Mac desktop. BOSS must use remote control/screen sharing to scan QR or complete captcha there.
- `POST /api/login/close-browser` should not delete cookies; it only closes the context.
- If a stale server process keeps old routes, kill the listener on port 3000 before starting the new server.
@@ -0,0 +1,84 @@
# Web login button + LAN exposure notes (2026-05)
Session learning from BOSS testing xianyu-hunter login from a LAN browser.
## Symptoms
- Dashboard was reachable from LAN at `http://192.168.2.69:3000`, because `lsof -nP -iTCP:3000 -sTCP:LISTEN` showed `TCP *:3000 (LISTEN)`.
- BOSS could not find a login entry in the Web UI because `public/index.html` only had `+ 新建任务`.
- `node tools/xianyu_ops.mjs stop` can report `no-recorded-server` / lose `.runtime` state while an old `node server.mjs` is still listening on port 3000. In that case new code is not active until the old PID is killed.
## Fix pattern
1. Add server imports:
```js
import { checkLogin } from './lib/login.mjs';
import { closeBrowser } from './lib/browser.mjs';
```
2. Add API routes before `server.listen`:
```js
app.post('/api/login/open', async (req, res) => {
try {
const logs = [];
const ok = await checkLogin(msg => logs.push(msg));
res.json({ ok, loggedIn: ok, logs });
} catch (err) {
res.status(500).json({ ok: false, error: err.message });
}
});
app.post('/api/login/close-browser', async (req, res) => {
await closeBrowser();
res.json({ ok: true });
});
```
3. Add a Web UI header button:
```html
<button class="btn" onclick="openLoginWindow()">打开闲鱼登录窗口</button>
```
4. Add `openLoginWindow()` in `public/app.js` near `apiPost()`:
```js
async function openLoginWindow() {
alert('即将在 Mac 桌面打开一个 Chromium 闲鱼窗口;请通过远程桌面/屏幕共享在那个窗口里完成扫码或验证码登录。');
try {
const result = await apiPost('/api/login/open', {});
const logs = (result.logs || []).join('\n');
if (result.loggedIn) {
alert(`登录状态正常。\n\n${logs}`);
} else {
alert(`还未检测到登录完成,请在弹出的 Chromium 窗口里登录后再试。\n\n${logs}`);
}
} catch (err) {
alert(`打开登录窗口失败:${err.message}`);
}
}
```
## Verification
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
node --check server.mjs
node --check public/app.js
lsof -nP -iTCP:3000 -sTCP:LISTEN
curl -sS http://127.0.0.1:3000/ | grep -o '打开闲鱼登录窗口' | head -1
curl -sS -X POST http://127.0.0.1:3000/api/login/close-browser
```
If `curl -X POST /api/login/close-browser` returns an HTML `Cannot POST` page, the stale server is still running. Kill the actual listening PID from `lsof` and restart:
```bash
kill -TERM <pid>
sleep 2
kill -KILL <pid> 2>/dev/null || true
PORT=3000 node server.mjs
```
Run the final server as a tracked background process in Hermes rather than using shell-level `nohup` wrappers.
@@ -0,0 +1,44 @@
# Web QR Login Simplification — 2026-05
Session learning from BOSS refining the xianyu-hunter Web login UX.
## User preference
BOSS found the login panel too busy after adding QR login and account status. For this project, the preferred Web login UX is deliberately minimal:
- Keep only **获取/刷新登录二维码** and **刷新账号状态** in the visible login modal.
- Do not expose legacy/backup actions as primary UI buttons:
- 打开闲鱼登录窗口
- 等待并检测
- 上传 Cookie JSON
- 关闭登录浏览器/关闭登录会话
- Keep backup APIs/functions available if useful for troubleshooting, but hide them from the normal Web UI unless BOSS explicitly asks for advanced controls.
- Simplify help text to the QR-first flow: get QR → scan → refresh status → cookies persist in `browser-data/`.
## Implementation shape used
In `/Users/chick/.Hermes/workspace/research/xianyu-hunter/public/index.html`, the login actions block should be:
```html
<div class="login-actions">
<button class="btn btn-primary" onclick="fetchLoginQr()">获取/刷新登录二维码</button>
<button class="btn" onclick="checkLoginStatus()">刷新账号状态</button>
</div>
```
The help text should avoid mentioning JSON import or manual browser opening in the normal flow.
`public/app.js` may still contain `openLoginWindow()`, `waitLoginStatus()`, `importCookieFile()`, and `closeLoginBrowser()` as hidden troubleshooting utilities, but no visible element should call them in the standard modal.
## Verification commands
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
node --check public/app.js
node --check server.mjs
node --check lib/login.mjs
PORT=3000 node server.mjs
curl -sS http://127.0.0.1:3000/ | grep -E '获取/刷新登录二维码|刷新账号状态|上传 Cookie JSON|打开闲鱼登录窗口|等待并检测|关闭登录会话'
```
Expected UI marker result: only the two kept buttons and QR/status help text should appear; no legacy button labels should appear.
@@ -0,0 +1,67 @@
# Web 后台二维码登录与账号状态面板(2026-05)
## Trigger
BOSS 反馈 Cookie JSON/外部截图仍不够方便,希望登录流程直接植入 xianyu-hunter Web 后台:点击登录即可获取真实登录二维码,并在后台看到当前账号基础状态。
## Implemented pattern
Files in local clone `/Users/chick/.Hermes/workspace/research/xianyu-hunter`:
- `lib/login.mjs`
- Add `getLoginQr(emitLog)`.
- It opens `https://www.goofish.com/` through the persistent Playwright profile, clicks the visible login entry when present, tries QR-login selectors, and returns a `data:image/png;base64,...` screenshot as `qrImage` when not logged in.
- It reuses `getLoginStatus(page)` so already-logged-in accounts return status and no QR image.
- `getLoginStatus()` now also returns account basics: `accountName`, `nickCandidates`, `avatarUrl`.
- `server.mjs`
- Import `getLoginQr` and expose `POST /api/login/qr`.
- `public/index.html`
- In the login modal, add account row (`login-account-row`) and QR panel (`login-qr-panel`).
- Primary action is now **获取登录二维码**; **打开闲鱼登录窗口** remains a fallback for captcha/expired QR/manual verification.
- `public/app.js`
- Add `fetchLoginQr()`, `renderLoginQr()`, `clearLoginQr()`.
- Extend `renderLoginStatus()` to show nickname/avatar/cookie count/account state.
- `public/style.css`
- Add `.login-qr-panel`, `.login-qr-img`, `.login-account-row`, `.login-avatar` styles.
## Important workflow corrections learned
1. Do **not** manually fabricate a Taobao login URL and send it to BOSS as “闲鱼二维码”. Goofish may use Taobao/Alibaba unified login under the hood, but the user experience must start from the Goofish Web login entry to avoid confusion.
2. Prefer an in-dashboard QR panel over sending QR screenshots through chat. BOSS explicitly wants the backend page to own login, QR display, and account status.
3. Keep visible Chromium login as fallback only. QR/captcha/verification may still require the desktop browser.
4. Avoid concurrent Playwright profiles. Temporary QR scripts and the main service can conflict on `browser-data/SingletonLock`; close/kill temporary QR browser processes before running tasks or status probes.
5. If `POST /api/login/qr` is called when already logged in, return status and no QR image; frontend should clear/hide the QR panel and show account info instead.
## Verification recipe
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
node --check lib/login.mjs
node --check server.mjs
node --check public/app.js
# restart exact listener if needed
for p in $(lsof -tiTCP:3000 -sTCP:LISTEN 2>/dev/null); do kill "$p" 2>/dev/null || true; done
PORT=3000 node server.mjs
```
API checks:
```bash
curl -sS -m 60 http://127.0.0.1:3000/api/login/status | python3 -m json.tool
curl -sS -m 90 -X POST http://127.0.0.1:3000/api/login/qr \
-H 'Content-Type: application/json' -d '{}' > /tmp/xianyu_qr_api.json
python3 - <<'PY'
import json
j=json.load(open('/tmp/xianyu_qr_api.json'))
print(j.get('ok'), j.get('loggedIn'), j.get('cookieCount'), j.get('accountName'), bool(j.get('qrImage')))
PY
curl -sS -m 10 http://127.0.0.1:3000/ | grep -E '获取登录二维码|扫码登录|login-qr-panel'
```
Expected logged-in example from the session:
- `loggedIn: true`
- `cookieCount: 27`
- `accountName: 札幌打滚的风筝鱼`
- `qrImage: false` because QR is unnecessary while logged in.
@@ -0,0 +1,133 @@
# Web Task Editing, Scheduling, and AI-Optimize UI (2026-05)
Session learning from extending BOSS's local `xianyu-hunter` fork at:
```text
/Users/chick/.Hermes/workspace/research/xianyu-hunter
```
## Feature shape implemented
When BOSS asks for Web后台 task management instead of Hermes-only CLI control, keep the product shape focused on monitoring/analysis-only:
- Web task modal supports both create and edit.
- Existing tasks can be edited only when not running.
- Task form supports:
- search keywords (`queries`)
- price min/max
- region
- personal seller toggle
- max pages
- `maxItems` / 单次最大商品寻找量
- custom requirements
- coarse/fine prompts
- schedule enabled + interval minutes
- AI optimize/current-form generation
- Save modes:
- `autoStart:false` = 手动新增任务但不立即采集
- save + run = create and start immediately
- edit + reset run = save then rerun with old products cleared
- Keep seller chat disabled. Scheduled/background runs must still stop at collection + local prefilter/detail collection and never auto-contact sellers.
## Backend/API pattern
Good API additions:
```text
PUT /api/tasks/:id edit task config (reject while running)
POST /api/tasks/optimize generate optimized config from current form
POST /api/tasks accepts autoStart:false
POST /api/tasks/:id/start accepts { reset:true }
```
Task config should normalize legacy and new schedule shapes:
```js
const existingSchedule = config.schedule || {};
const scheduleEnabled = config.scheduleEnabled ?? existingSchedule.enabled ?? false;
const scheduleIntervalMinutes = config.scheduleIntervalMinutes ?? existingSchedule.intervalMinutes ?? 0;
```
`maxItems` should be enforced after each query's dedupe and before fine detail fetch:
```js
const remaining = Math.max(0, (task.config.maxItems || 60) - task.products.raw.length);
const accepted = deduped.slice(0, remaining);
task.products.raw.push(...accepted);
if (task.products.raw.length >= (task.config.maxItems || 60)) break;
await fineFilter(task.products.coarseFiltered.slice(0, task.config.maxItems || 60), ...)
```
Scheduler pattern:
- `TaskManager` starts an interval timer in constructor after `_loadFromDisk()`.
- Tick every ~30s.
- For enabled schedule with due `nextRunAt`, non-running task: push a log and call `startTask(id, { reset: true, scheduled: true })`.
- On scheduled run, set `lastRunAt` and normalize next run.
## AI optimize fallback
`POST /api/tasks/optimize` can try `chatCompletion()`, but must tolerate missing AI config. If it fails, return `{ ok:true, source:'local-fallback', task }` so the UI still works.
Local fallback should generate conservative monitoring defaults:
- `maxPages`: 2
- `maxItems`: 40
- `personalSeller`: true
- requirements explicitly include: no seller contact, exclude parts/repair/wanted/rental/recycling/commercial bulk/title spam.
## Frontend/UI notes
- Reuse the create modal for editing by tracking `editingTaskId`.
- Add buttons in pipeline controls: 编辑, 复制, 继续/启动, 重新采集, 删除.
- `重新采集` should confirm because it clears current round products.
- `fillTaskForm(taskOrConfig)` avoids duplicate create/edit/duplicate mapping bugs.
- Add cache-busting version bump for `style.css` and `app.js`; also serve static files with `Cache-Control: no-store` to avoid stale dashboard assets.
## Verification recipe
Before final response:
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
node --check server.mjs && node --check lib/task.mjs && node --check public/app.js
```
Restart the exact server on port 3000. If `node tools/xianyu_ops.mjs stop` says `no-recorded-server` but port is in use, kill both the listener and parent npm process:
```bash
lsof -nP -iTCP:3000 -sTCP:LISTEN
ps -p <listener_pid> -o pid,ppid,command
kill -TERM <parent_pid> <listener_pid>
```
Then start in background and verify API:
```bash
PORT=3000 npm start
curl -sS http://127.0.0.1:3000/api/tasks | python3 -m json.tool
```
Smoke-test the new APIs with a temporary task and delete it after:
```bash
python3 - <<'PY'
import json, urllib.request
base='http://127.0.0.1:3000'
def req(method,path,data=None):
body=json.dumps(data).encode() if data is not None else None
r=urllib.request.Request(base+path,data=body,method=method,headers={'Content-Type':'application/json'})
with urllib.request.urlopen(r,timeout=30) as resp:
return json.loads(resp.read().decode())
opt=req('POST','/api/tasks/optimize',{'product':'e3-1245v3 cpu','budget':'150','currentRequirements':'只要CPU本体,排除主板套装'})
created=req('POST','/api/tasks',{**opt['task'],'name':'__api_verify_temp__','autoStart':False,'scheduleEnabled':True,'scheduleIntervalMinutes':120,'maxItems':25})
updated=req('PUT',f"/api/tasks/{created['id']}",{'name':'__api_verify_temp_updated__','queries':['e3 1245 v3 verify'],'maxItems':30,'scheduleEnabled':False,'scheduleIntervalMinutes':0})
req('DELETE',f"/api/tasks/{created['id']}")
print('ok', opt.get('source'), created['id'], updated['config']['maxItems'])
PY
```
## Pitfall
Do not leave a final user response empty after tools. If a tool result shows a port conflict or failed restart, process it immediately, continue with the fix, and give BOSS the final verified state.
@@ -0,0 +1,66 @@
# Web Theme Toggle + Visual Polish — 2026-05
Session learning from BOSS's xianyu-hunter dashboard refinement request: when asked to make the UI more polished, at minimum provide explicit dark/light theme switching and make the result visible in the Web UI immediately.
## Implemented pattern
Files in `/Users/chick/.Hermes/workspace/research/xianyu-hunter`:
- `public/index.html`
- Add a header button before existing settings/login/create actions:
- `id="theme-toggle"`
- `onclick="toggleTheme()"`
- child spans `theme-icon` and `theme-label`
- Bump static asset query versions, e.g. `style.css?v=14`, `app.js?v=14`, to avoid stale browser cache.
- `public/app.js`
- Add `THEME_STORAGE_KEY = 'xianyu-hunter-theme'`.
- Add `getPreferredTheme()`:
- prefer saved `localStorage` value if `light`/`dark`.
- otherwise follow `window.matchMedia('(prefers-color-scheme: light)')`.
- Add `applyTheme(theme)`:
- sets `document.documentElement.dataset.theme`.
- updates `theme-icon` and `theme-label` when DOM elements exist.
- Add `toggleTheme()`:
- toggles `light`/`dark`, persists to `localStorage`, reapplies.
- Call `applyTheme(getPreferredTheme())` early so theme applies before app data rendering.
- `public/style.css`
- Use `:root` as default dark theme and `:root[data-theme="light"]` for light variables.
- Expand variables beyond the older basic palette: `--surface`, `--surface-strong`, `--border-strong`, `--muted`, `--accent3`, `--shadow`, `--shadow-soft`, `--glow`.
- Prefer real light-theme variables rather than simple inversion.
- Polish high-visibility surfaces: header, sidebar, content shell, task cards, buttons, pipeline, tabs, stat cards, product table wrapper, modal, log blocks.
- Use cache-safe CSS constructs with existing browser target: gradients, `backdrop-filter`, CSS variables, and `color-mix()`.
## Verification commands
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
node --check public/app.js
node --check server.mjs
python3 - <<'PY'
from pathlib import Path
html=Path('public/index.html').read_text()
css=Path('public/style.css').read_text()
js=Path('public/app.js').read_text()
for needle in ['style.css?v=', 'app.js?v=', 'theme-toggle']:
print(needle, needle in html)
print('light theme vars', ':root[data-theme="light"]' in css)
print('toggleTheme', 'function toggleTheme()' in js)
PY
node tools/xianyu_ops.mjs stop || true
node tools/xianyu_ops.mjs start --port 3000
node tools/xianyu_ops.mjs status --port 3000
python3 - <<'PY'
import urllib.request
html=urllib.request.urlopen('http://127.0.0.1:3000/', timeout=10).read().decode('utf-8')
print('theme button', 'theme-toggle' in html)
print('css v', 'style.css?v=' in html)
print('app v', 'app.js?v=' in html)
PY
```
## Pitfalls
- Do not stop after backend/CSS edits only; BOSS expects visible product UI changes and live verification.
- If CSS/JS filenames use query-string cache busting, bump both versions in `index.html`; otherwise the browser may keep the previous UI.
- `applyTheme()` may run before the header DOM exists, so DOM updates must guard missing `theme-icon`/`theme-label` elements.
- Preserve existing login/notification/task controls while adding the theme toggle; do not regress the simplified login UX.