Improve first-run and channel setup for non-technical users: detect newer Gemini key formats, pin Node 22.22.3, add Control Panel Telegram approve flow, and keep channels/models when config is rewritten. Persist uclaw wizard state via uclaw-meta.json so restarts skip language/persona prompts. Co-authored-by: Cursor <cursoragent@cursor.com>
81 lines
3.0 KiB
Markdown
81 lines
3.0 KiB
Markdown
---
|
||
name: sg-weather
|
||
description: "Singapore weather and air quality - 2-hour forecast by area, 24-hour outlook, PSI. Official NEA data, no API key."
|
||
metadata: { "openclaw": { "emoji": "🌦️" } }
|
||
---
|
||
|
||
# Singapore Weather
|
||
|
||
Live weather and air quality from NEA via data.gov.sg. No API key, no sign-up.
|
||
|
||
Worth knowing why this exists as its own skill: Singapore rain is intensely
|
||
local. It can pour in Bukit Timah while Changi stays dry, so a national forecast
|
||
is close to useless. The 2-hour forecast is published per area — use it.
|
||
|
||
## What it does
|
||
|
||
- **Next 2 hours, by area** — 47 areas, refreshed every half hour
|
||
- **Next 24 hours** — general outlook plus morning/afternoon/night
|
||
- **PSI** — air quality by region, which matters during haze season
|
||
|
||
## How to run it
|
||
|
||
Use the Bash tool. These are public endpoints.
|
||
|
||
```bash
|
||
# Right now, for one area
|
||
curl -s "https://api-open.data.gov.sg/v2/real-time/api/two-hr-forecast" \
|
||
| python3 -c "
|
||
import json,sys
|
||
area='Ang Mo Kio' # change this
|
||
d=json.load(sys.stdin)['data']
|
||
item=d['items'][0]
|
||
hit=[f['forecast'] for f in item['forecasts'] if f['area']==area]
|
||
print(f'{area}: {hit[0] if hit else \"area not found\"}')
|
||
print('valid until', item['validPeriod']['end'])
|
||
"
|
||
|
||
# Every area at once — useful for 'is it raining anywhere near me'
|
||
curl -s "https://api-open.data.gov.sg/v2/real-time/api/two-hr-forecast" \
|
||
| python3 -c "
|
||
import json,sys
|
||
for f in json.load(sys.stdin)['data']['items'][0]['forecasts']:
|
||
print(f\"{f['area']:22} {f['forecast']}\")
|
||
"
|
||
|
||
# 24-hour outlook
|
||
curl -s "https://api-open.data.gov.sg/v2/real-time/api/twenty-four-hr-forecast" \
|
||
| python3 -c "
|
||
import json,sys
|
||
r=json.load(sys.stdin)['data']['records'][0]
|
||
print('General:', r['general']['forecast']['text'])
|
||
print('Temp:', r['general']['temperature']['low'], '-', r['general']['temperature']['high'], 'C')
|
||
for p in r['periods']:
|
||
print(p['timePeriod']['text'], '->', p['regions']['central']['text'])
|
||
"
|
||
|
||
# Air quality (PSI) by region
|
||
curl -s "https://api-open.data.gov.sg/v2/real-time/api/psi" \
|
||
| python3 -c "
|
||
import json,sys
|
||
psi=json.load(sys.stdin)['data']['items'][0]['readings']['psi_twenty_four_hourly']
|
||
for region,value in psi.items():
|
||
band=('Good' if value<=50 else 'Moderate' if value<=100 else
|
||
'Unhealthy' if value<=200 else 'Very unhealthy' if value<=300 else 'Hazardous')
|
||
print(f'{region:8} {value:4} {band}')
|
||
"
|
||
```
|
||
|
||
## Working notes
|
||
|
||
- Ask which area they are in, or infer it from context. Do not report a national
|
||
average — the whole point is that it differs across the island.
|
||
- The 2-hour forecast is a short text like `Thundery Showers`. Quote it; do not
|
||
embellish it into a narrative.
|
||
- PSI bands: ≤50 Good · 51–100 Moderate · 101–200 Unhealthy · 201–300 Very
|
||
unhealthy · >300 Hazardous. During haze, people are deciding whether to run
|
||
outdoors or keep a child home — give them the number and the band, not a mood.
|
||
- Times come back in SGT. Report them in SGT.
|
||
- If the endpoint is down, say so. Do not fall back to guessing from memory —
|
||
yesterday's weather stated confidently is worse than "I could not reach NEA".
|