Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude/skills/forge.md
Original file line number Diff line number Diff line change
Expand Up @@ -1094,12 +1094,12 @@ description: Weather data skill # required, one-line summary
metadata:
forge:
requires:
bins: [curl] # binaries that must be in PATH
bins: [curl, jq] # binaries that must be in PATH
env:
required: [WEATHER_API_KEY]
required: [] # keyless: wttr.in needs no API key
one_of: []
optional: []
egress_domains: [api.openweathermap.org]
egress_domains: [wttr.in]
denied_tools: [http_request] # tools this skill must NOT use
timeout_hint: 60 # suggested execution timeout in seconds
---
Expand Down
7 changes: 3 additions & 4 deletions forge-cli/internal/tui/steps/egress_step.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,9 @@ func inferSource(domain string, ctx *tui.WizardContext) string {

// Skill domains
skillDomains := map[string]string{
"api.github.com": "github skill",
"github.com": "github skill",
"api.openweathermap.org": "weather skill",
"api.weatherapi.com": "weather skill",
"api.github.com": "github skill",
"github.com": "github skill",
"wttr.in": "weather skill",
}
if src, ok := skillDomains[domain]; ok {
return src
Expand Down
53 changes: 46 additions & 7 deletions forge-skills/local/embedded/weather/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,63 @@ metadata:
requires:
bins:
- curl
- jq
env:
required: []
one_of: []
optional: []
egress_domains:
- api.openweathermap.org
- api.weatherapi.com
- wttr.in
---

# Weather Skill

Get current weather and short-range forecasts for any location using
[wttr.in](https://wttr.in). **No API key required** — wttr.in is a free,
keyless service, so this skill works out of the box.

## Quick Start

```bash
./scripts/weather-current.sh '{"location": "Tokyo"}'
./scripts/weather-forecast.sh '{"location": "Tokyo", "days": 3}'
```

The `location` accepts a city name (`Tokyo`, `New York`), an airport code
(`SFO`), or coordinates (`35.68,139.76`).

## Tool: weather_current

Get current weather for a location.

**Input:** location (string) - City name or coordinates
**Output:** Current temperature, conditions, humidity, and wind speed
**Input:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| location | string | yes | City name, airport code, or `lat,lon` coordinates |

**Output:** JSON object with `location`, `temperature_c`, `temperature_f`,
`feels_like_c`, `conditions`, `humidity`, `wind_kmph`, `wind_dir`, and
`observation_time_utc`.

```bash
./scripts/weather-current.sh '{"location": "Tokyo"}'
```

## Tool: weather_forecast

Get weather forecast for a location.
Get a daily weather forecast for a location.

**Input:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| location | string | yes | City name, airport code, or `lat,lon` coordinates |
| days | integer | no | Number of days, 1-3. Default: 3 (wttr.in caps the free forecast at 3 days) |

**Output:** JSON object with `location` and a `forecast` array of
`{date, high_c, low_c, high_f, low_f, conditions, chance_of_rain_pct}`.

**Input:** location (string), days (integer: 1-7)
**Output:** Daily forecast with high/low temperatures and conditions
```bash
./scripts/weather-forecast.sh '{"location": "Tokyo", "days": 3}'
```
57 changes: 57 additions & 0 deletions forge-skills/local/embedded/weather/scripts/weather-current.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# weather-current.sh — Current weather for a location via wttr.in.
# Usage: ./weather-current.sh '{"location": "Tokyo"}'
#
# Requires: curl, jq. No API key or environment variable needed — wttr.in
# is a free, keyless service.
set -euo pipefail

# --- Read input ---
INPUT="${1:-}"
if [ -z "$INPUT" ]; then
echo '{"error": "usage: weather-current.sh {\"location\": \"...\"}"}' >&2
exit 1
fi

# Validate JSON
if ! echo "$INPUT" | jq empty 2>/dev/null; then
echo '{"error": "invalid JSON input"}' >&2
exit 1
fi

# --- Check required fields ---
LOCATION=$(echo "$INPUT" | jq -r '.location // empty')
if [ -z "$LOCATION" ]; then
echo '{"error": "location field is required"}' >&2
exit 1
fi

# URL-encode the location for the wttr.in path segment.
LOCATION_ENC=$(jq -rn --arg s "$LOCATION" '$s|@uri')

# --- Call wttr.in (j1 = JSON format) ---
RESPONSE=$(curl -s -w "\n%{http_code}" \
-H "User-Agent: curl" \
"https://wttr.in/${LOCATION_ENC}?format=j1")

# Split response body and status code
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')

if [ "$HTTP_CODE" -ne 200 ]; then
echo "{\"error\": \"wttr.in returned status $HTTP_CODE for location '$LOCATION'\"}" >&2
exit 1
fi

# --- Shape the current conditions ---
echo "$BODY" | jq '{
location: (.nearest_area[0] | "\(.areaName[0].value), \(.country[0].value)"),
temperature_c: (.current_condition[0].temp_C | tonumber),
temperature_f: (.current_condition[0].temp_F | tonumber),
feels_like_c: (.current_condition[0].FeelsLikeC | tonumber),
conditions: .current_condition[0].weatherDesc[0].value,
humidity: (.current_condition[0].humidity | tonumber),
wind_kmph: (.current_condition[0].windspeedKmph | tonumber),
wind_dir: .current_condition[0].winddir16Point,
observation_time_utc: .current_condition[0].observation_time
}'
66 changes: 66 additions & 0 deletions forge-skills/local/embedded/weather/scripts/weather-forecast.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# weather-forecast.sh — Daily forecast for a location via wttr.in.
# Usage: ./weather-forecast.sh '{"location": "Tokyo", "days": 3}'
#
# Requires: curl, jq. No API key or environment variable needed — wttr.in
# is a free, keyless service. wttr.in caps the free forecast at 3 days.
set -euo pipefail

# --- Read input ---
INPUT="${1:-}"
if [ -z "$INPUT" ]; then
echo '{"error": "usage: weather-forecast.sh {\"location\": \"...\", \"days\": 3}"}' >&2
exit 1
fi

# Validate JSON
if ! echo "$INPUT" | jq empty 2>/dev/null; then
echo '{"error": "invalid JSON input"}' >&2
exit 1
fi

# --- Check required fields ---
LOCATION=$(echo "$INPUT" | jq -r '.location // empty')
if [ -z "$LOCATION" ]; then
echo '{"error": "location field is required"}' >&2
exit 1
fi

# Clamp days to the 1-3 range wttr.in serves for free.
DAYS=$(echo "$INPUT" | jq -r '.days // 3')
case "$DAYS" in
'' | *[!0-9]*) DAYS=3 ;;
esac
if [ "$DAYS" -lt 1 ]; then DAYS=1; fi
if [ "$DAYS" -gt 3 ]; then DAYS=3; fi

# URL-encode the location for the wttr.in path segment.
LOCATION_ENC=$(jq -rn --arg s "$LOCATION" '$s|@uri')

# --- Call wttr.in (j1 = JSON format) ---
RESPONSE=$(curl -s -w "\n%{http_code}" \
-H "User-Agent: curl" \
"https://wttr.in/${LOCATION_ENC}?format=j1")

# Split response body and status code
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')

if [ "$HTTP_CODE" -ne 200 ]; then
echo "{\"error\": \"wttr.in returned status $HTTP_CODE for location '$LOCATION'\"}" >&2
exit 1
fi

# --- Shape the forecast (hourly index 4 ~ midday) ---
echo "$BODY" | jq --argjson days "$DAYS" '{
location: (.nearest_area[0] | "\(.areaName[0].value), \(.country[0].value)"),
forecast: [ .weather[0:$days][] | {
date: .date,
high_c: (.maxtempC | tonumber),
low_c: (.mintempC | tonumber),
high_f: (.maxtempF | tonumber),
low_f: (.mintempF | tonumber),
conditions: .hourly[4].weatherDesc[0].value,
chance_of_rain_pct: (.hourly[4].chanceofrain | tonumber)
} ]
}'
2 changes: 1 addition & 1 deletion forge-skills/local/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ metadata:
bins:
- curl
egress_domains:
- api.openweathermap.org
- wttr.in
---
## Tool: weather_current
Get current weather.
Expand Down
Loading