# dsh-whale-care ![version](https://img.shields.io/badge/version-0.5.7-blue) ![license](https://img.shields.io/badge/license-MIT-green) ![dsh](https://img.shields.io/badge/dsh-0.1.2_to_0.2.x-6f42c1) ![no api key](https://img.shields.io/badge/API%20key-not%20required-brightgreen) ![node](https://img.shields.io/badge/node-22_%2F_24-339933) > Current version **0.5.7** (early, still growing). A **care engine** for [DeepSeek Harness (dsh)](https://github.com/deepseek-ai/deepseek-harness): it teaches the assistant how to actually care about one person instead of dumping data on them. Weather is the first and cheapest care source — everyone steps outside, and cold, rain and air quality hit you directly. So this version uses it to get the "caring" loop right; habits (sleep, meals, water, sitting too long) can plug into the same judgment layer later. **The plugin is MIT and free; the weather data needs no API key and no account.** To be precise about "free": the plugin and both weather APIs cost nothing, but **every agent turn still burns model tokens**; wttr.in is a community service (can rate-limit), and Open-Meteo's free tier is non-commercial use only. **中文**: [README.md](README.md) ## Quick start ```bash # 1. install dsh plugin --profile web add github:fanyingnie2163663/dsh-whale-care # 2. restart dsh, then tell the agent who you are "Remember: I'm in Hefei, call me A-Ning, I feel the cold, I bike to work, I have a sensitive nose." # 3. then just ask "Umbrella today?" "What should I wear tomorrow?" "Which day this week is coldest?" ``` No API key, no signup; preferences stay in `~/.dsh/whale-weather/profile.json` on your machine. **How it differs from a plain weather plugin**: | Plain weather plugin | This one | | --- | --- | | `rain 78%` | "Rain today (78%) — take an umbrella." | | `tomorrow 12–20°C` | "Tomorrow 12–20°C — a jacket by day, a sweater in the morning." | | Same answer for everyone | Remembers you feel the cold, bike to work, have a sensitive nose | | Silent until asked | Sends one message each morning (switchable, follows your routine) | ## Where it runs: plain dsh, QQ is optional **This is a standard dsh plugin and does not depend on qq-bridge.** Restart dsh and **any dsh session** (Web, TUI, headless) can call all five tools. qq-bridge is only an optional host that puts the agent on QQ. | Setup | What you get | Do reminders/alerts fire on time? | | --- | --- | --- | | dsh only (Web / TUI / headless) | all five tools | **No**: they are checked when you talk to the agent and it calls `weather_reminder(due)` / `weather_alert` | | dsh + qq-bridge (QQ) | all five tools (allowlist needed) | **Yes**: the bridge checks reminders every 30s and alerts at 07:00 / 18:00 | qq-bridge adds exactly two things, neither of which the plugin depends on: 1. **Runtime tool allowlist**: the `qq-chat-v2` preset rejects non-MCP tools by default, so the five names must be added to `SAFE_EXACT` (see “QQ bot users”); 2. **Scheduled wake-ups**: dsh has no timer of its own; a reminder/alert needs a host to wake the agent at the right moment. Without it, reminders are only checked the next time the agent is awake. ## Why A weather app says "78% chance of rain"; it does not say "you always forget your umbrella — put one in your bag". A voice assistant says "it will be colder tomorrow"; it does not remember that you feel the cold, bike everywhere, or have a sensitive nose. This plugin tries to say the second half: **work out what actually matters, then turn the conclusion into a short, warm bit of care.** - **It knows who you are**: nickname, cold/heat sensitivity, how you get around, umbrella, health notes — all explicitly set by the user; - **It knows what time it is**: "take care on the way" in the morning, "get some rest" at night; no sunscreen reminders after dark; - **It only says what matters**: ranked by impact, at most three items, never a rules dump; - **It stays local**: preferences live in `~/.dsh/whale-weather/profile.json`, nothing is uploaded. ## Five tools | Tool | Purpose | | --- | --- | | `weather` | Detailed report: current conditions, air quality, 3-day forecast, generic advice | | `weather_care` | **Caring version**: personalized by profile and time of day, ready to send as-is | | `weather_profile` | Read/write preferences: city, nickname, cold/heat sensitivity, how you get around, umbrella, health notes | | `weather_reminder` | One reminder engine: timed text reminders (medication, water) or weather-conditioned ones (rain/temp drop/wind/swing/AQI; speaks only when the condition matches) | | `weather_alert` | Extreme-weather alerts: rainstorm / gale / heat / cold wave / thunder / dust / fog / air pollution, levels yellow/orange/red, deduped per day | ## What the caring version looks like > A-Ning, good evening. Hefei is 14°C now (feels like 13°C), haze, humidity 69%. > Tomorrow 9–14°C. Wear a light jacket or trench coat by day; a padded coat and scarf in the morning and evening. > Tomorrow's high is only 14°C, 6°C lower than today — you feel the cold, so don't skip the jacket and trousers. > Air is poor (AQI 63) — your nose is sensitive, keep the mask on tomorrow. > Tomorrow haze. > Get some rest, don't stay up too late. How preferences change the wording and thresholds: | Preference | Effect | | --- | --- | | `nickname` | Opens with the name the user wants to be called | | `coldSensitive` | Warns at a 3°C drop (5°C default), keeps-warm at ≤8°C | | `heatSensitive` | Heat warning at 28°C (32°C default) | | `commute` = `bike` | Rain → "roads are slippery, slow down"; wind → "don't fight the headwind" | | `commute` = `walk` | Rain → "avoid the puddles" | | `hasUmbrella` = false | Rain → "you always forget your umbrella, put one in your bag" | | `notes` with 鼻/咽/哮喘/过敏 | Poor air → "your nose is sensitive, wear the mask" | | Time of day | Different greeting and closing; evening adds tomorrow's preview | Preferences live in `$DSH_HOME/whale-weather/profile.json` (default `~/.dsh/whale-weather/profile.json`, mode 600). ## Detailed report features - **Current**: temperature, feels-like, condition (localized to Chinese), humidity, wind, visibility - **Air quality**: AQI + PM2.5 + level (excellent / good / moderate / poor / very poor) - **3-day forecast**: high/low, condition, **chance of rain**, wind - **Advice** (only when relevant, max 4 lines): - rain probability ≥40% (or today's condition has rain) → take an umbrella - wind ≥30 km/h → scarf + windproof jacket - tomorrow's high ≥5°C lower than today → dress warmer (≥5°C higher → lighter clothes) - day/night swing ≥10°C → jacket for morning/evening - low ≤5°C → keep warm - UV index ≥6 → sun protection - AQI ≤20 → great for a walk; 20–40 → fine to go out; 40–60 → sensitive groups stay in; >60 → mask, less outdoor time - **Chinese condition mapping**: wttr's `lang_zh` does not actually translate; this plugin ships a 40+ entry WWO → Chinese map, falling back to the original text - Core logic is decoupled from dsh: import `dsh-whale-care/weather` from any agent framework ## Install notes The install command is in **Quick start** above. Restart dsh afterwards; the agent's tool list will then contain **`weather`**. **Supported DSH versions**: `0.1.2-rc.1` through `0.2.x`. `package.json` declares the peer range per generation (`>=0.1.2-rc.1 <0.2.0 || … || >=0.2.1-0 <0.3.0`) plus `engines.dsh`; `0.3` is untested, and the peer gate refuses the install instead of letting it crash at runtime. After installing, ask the agent to run `weather_profile(action="get")` once: if another plugin on this machine already stored preferences/impressions (e.g. qq-bridge's member notes — see "Optional: daily care and host impressions"), the output will include an impression line, and the agent can use it instead of asking the same questions again. ## Usage | Param | Type | Description | | --- | --- | --- | | `city` | string (required) | City name in Chinese, pinyin or English (Hefei, 合肥, Tokyo) | | `days` | integer (optional) | Forecast days 1–3, default 3 | ## Sample output ``` Hefei now 14°C (feels like 13°C), haze, humidity 69%, wind 5km/h, visibility 10km Air quality poor (AQI 63, PM2.5 57μg/m³) Today (10-30) 12~23°C sunny, rain 6%, wind 9km/h Tomorrow (10-31) 14~24°C haze, rain 16%, wind 19km/h Day after (11-01) 12~20°C drizzle, rain 78%, wind 28km/h Advice: · Rain chance up to 78% — take an umbrella · Today's day/night swing is 11°C — bring a jacket · Air is poor (AQI 63) — wear a mask, less time outdoors ``` (The actual output is Chinese; see README.md.) ## Optional: QQ bot users (qq-bridge) `qq-bridge`'s `qq-chat-v2` preset enforces an execution-time tool allowlist (`SAFE_EXACT` in `plugins/qq-agent-presets/qq-tool-restrict.mjs`). Non-MCP tools are rejected by default. Add one line: ```js const SAFE_EXACT = new Set([ 'ask_user_question', 'todo_write', 'weather', // ← dsh-whale-care 'weather_care', // ← dsh-whale-care 'weather_profile', // ← dsh-whale-care 'weather_reminder', // ← dsh-whale-care 'weather_alert', // ← dsh-whale-care ]) ``` Then restart dsh; otherwise the model gets `工具 "weather" 不在 QQ 桥接白名单内`. ## Optional: daily care and host impressions **Daily care**: a host (e.g. qq-bridge) can send one `weather_care` message every morning. Whether it runs, and when, is controlled by two profile fields: `dailyCare` (`false` to turn it off) and `careTime` (`HH:MM`; unset = follow the user's routine). Without a host scheduler this never fires — plain dsh only checks when the model wakes up. **Host impressions**: the plugin **does not depend on any host** — preferences live only in `~/.dsh/whale-weather/profile.json`, and everything works without any other file. If another plugin on the same machine knows the user's habits (e.g. qq-bridge's impressions of chat members), it can periodically write a **read-only snapshot** that this plugin uses to fill blanks: > That file is defined as an **impression snapshot written by an external plugin** — not this plugin's own data. This plugin only reads it, never writes back, and never requires it. > After installing, have the agent run `weather_profile(action="get")` once: if impressions exist, use them; otherwise just ask. ```json // $DSH_HOME/whale-weather/user-context.json (0600 recommended) { "version": 1, "updatedAt": 1793399075989, "source": "qq-bridge", "user": { "qq": "741934896", "displayName": "DDDeSt1ny", "traits": ["平时骑车上班", "有鼻炎"], "interactionCount": 3, "lastSeenAt": 1793303867850 } } ``` - Values in `profile.json` always win; the snapshot only fills **missing** fields: commute, cold/heat sensitivity, health notes - Older than 7 days → ignored; missing file / broken JSON / wrong version → ignored - Keyword matching only (bike / walk / transit / drive, cold/heat, rhinitis / allergy …); no guessing, and free text is never pasted into the forecast message - Writing the snapshot is the host's job; this plugin only reads ## Data sources & privacy | Purpose | Service | Data sent | | --- | --- | --- | | Weather | [wttr.in](https://wttr.in) | city name | | Air quality | [open-meteo.com](https://open-meteo.com) | lat/lon returned by wttr | No accounts, keys or chat content are sent. The plugin collects and reports nothing. Both services are free, registration-less public endpoints (wttr.in is community-run and may rate-limit; Open-Meteo's free tier is non-commercial only) — please don't hammer them. ## Development ```bash node scripts/demo.mjs Hefei node scripts/demo.mjs Tokyo 2 ``` ``` lib/weather.js pure logic: wttr + open-meteo + Chinese map + advice rules lib/index.js dsh adapter: registers the `weather` tool via defineTool cordis.patch.yml dsh bundle patch scripts/demo.mjs standalone demo ``` ## License MIT. Weather data remains the property of wttr.in / open-meteo.