feat: publish xianyu hunter hermes skill package
This commit is contained in:
@@ -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`: 1–3 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 20–30s+ 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 BOSS’s 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.
|
||||
+133
@@ -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.
|
||||
Reference in New Issue
Block a user