fix(portable): Gemini keys, Telegram pairing UI, and config durability

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>
This commit is contained in:
2026-08-18 18:45:03 +08:00
parent 0be8c3e3fe
commit dd8e0f55c3
35 changed files with 2673 additions and 34 deletions

View File

@@ -0,0 +1,80 @@
---
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 · 51100 Moderate · 101200 Unhealthy · 201300 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".