From 5bc775d28d6fee429795480ceb5ad89c424b2675 Mon Sep 17 00:00:00 2001 From: Zaid Date: Mon, 20 Jul 2026 19:14:15 -0600 Subject: [PATCH 1/2] Switch embedded weather skill to keyless wttr.in endpoint --- .claude/skills/forge.md | 4 ++-- forge-cli/internal/tui/steps/egress_step.go | 7 +++---- forge-skills/local/embedded/weather/SKILL.md | 15 ++++++++++++--- forge-skills/local/scanner_test.go | 2 +- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.claude/skills/forge.md b/.claude/skills/forge.md index ff14fe7d..fbde1568 100644 --- a/.claude/skills/forge.md +++ b/.claude/skills/forge.md @@ -1096,10 +1096,10 @@ metadata: requires: bins: [curl] # binaries that must be in PATH env: - required: [WEATHER_API_KEY] + required: [] 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 --- diff --git a/forge-cli/internal/tui/steps/egress_step.go b/forge-cli/internal/tui/steps/egress_step.go index 592ac168..7a5849c7 100644 --- a/forge-cli/internal/tui/steps/egress_step.go +++ b/forge-cli/internal/tui/steps/egress_step.go @@ -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 diff --git a/forge-skills/local/embedded/weather/SKILL.md b/forge-skills/local/embedded/weather/SKILL.md index d634df24..a9012851 100644 --- a/forge-skills/local/embedded/weather/SKILL.md +++ b/forge-skills/local/embedded/weather/SKILL.md @@ -17,8 +17,7 @@ metadata: one_of: [] optional: [] egress_domains: - - api.openweathermap.org - - api.weatherapi.com + - wttr.in --- ## Tool: weather_current @@ -27,9 +26,19 @@ Get current weather for a location. **Input:** location (string) - City name or coordinates **Output:** Current temperature, conditions, humidity, and wind speed +```bash +curl -s "https://wttr.in/${location}?format=j1" +``` +Relevant fields in the response: `current_condition[0].temp_C`, `current_condition[0].weatherDesc[0].value`, `current_condition[0].humidity`, `current_condition[0].windspeedKmph`. + ## Tool: weather_forecast Get weather forecast for a location. -**Input:** location (string), days (integer: 1-7) +**Input:** location (string), days (integer: 1-3) **Output:** Daily forecast with high/low temperatures and conditions + +```bash +curl -s "https://wttr.in/${location}?format=j1" +``` +The `weather` array in the response contains up to 3 days of forecast (today + 2 days ahead). Each entry has `date`, `maxtempC`, `mintempC`, and `hourly[].weatherDesc[0].value`. wttr.in's free JSON endpoint caps at 3 days — for a longer forecast window, `open-meteo.com` would be needed instead. diff --git a/forge-skills/local/scanner_test.go b/forge-skills/local/scanner_test.go index ce68ba8c..f3bde3d3 100644 --- a/forge-skills/local/scanner_test.go +++ b/forge-skills/local/scanner_test.go @@ -38,7 +38,7 @@ metadata: bins: - curl egress_domains: - - api.openweathermap.org + - wttr.in --- ## Tool: weather_current Get current weather. From 7e2bac803531b4d19d2c73e2b9fe137d32e549b9 Mon Sep 17 00:00:00 2001 From: Zaid Date: Tue, 21 Jul 2026 23:03:40 -0600 Subject: [PATCH 2/2] Add executable scripts for weather_current and weather_forecast tools --- forge-skills/local/embedded/weather/SKILL.md | 1 + .../weather/scripts/weather-current.sh | 29 ++++++++++++++ .../weather/scripts/weather-forecast.sh | 38 +++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100755 forge-skills/local/embedded/weather/scripts/weather-current.sh create mode 100755 forge-skills/local/embedded/weather/scripts/weather-forecast.sh diff --git a/forge-skills/local/embedded/weather/SKILL.md b/forge-skills/local/embedded/weather/SKILL.md index a9012851..8bfd71c1 100644 --- a/forge-skills/local/embedded/weather/SKILL.md +++ b/forge-skills/local/embedded/weather/SKILL.md @@ -12,6 +12,7 @@ metadata: requires: bins: - curl + - jq env: required: [] one_of: [] diff --git a/forge-skills/local/embedded/weather/scripts/weather-current.sh b/forge-skills/local/embedded/weather/scripts/weather-current.sh new file mode 100755 index 00000000..83b8700c --- /dev/null +++ b/forge-skills/local/embedded/weather/scripts/weather-current.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +input="$1" + +location=$(jq -r '.location // empty' <<< "$input" 2>/dev/null) || location="" + +if [[ -z "$location" ]]; then + jq -n '{error: "location is required"}' + exit 0 +fi + +encoded_location=$(jq -rn --arg s "$location" '$s|@uri') + +response=$(curl -s -w "\n%{http_code}" "https://wttr.in/${encoded_location}?format=j1") +http_code=$(tail -n1 <<< "$response") +body=$(sed '$d' <<< "$response") + +if [[ "$http_code" != "200" ]]; then + jq -n --arg code "$http_code" '{error: "wttr.in request failed with status \($code)"}' + exit 0 +fi + +echo "$body" | jq '{ + temperature_c: .current_condition[0].temp_C, + condition: .current_condition[0].weatherDesc[0].value, + humidity: .current_condition[0].humidity, + wind_speed_kmph: .current_condition[0].windspeedKmph +}' 2>/dev/null || jq -n '{error: "failed to parse weather data"}' diff --git a/forge-skills/local/embedded/weather/scripts/weather-forecast.sh b/forge-skills/local/embedded/weather/scripts/weather-forecast.sh new file mode 100755 index 00000000..b87eb91e --- /dev/null +++ b/forge-skills/local/embedded/weather/scripts/weather-forecast.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +input="$1" + +location=$(jq -r '.location // empty' <<< "$input" 2>/dev/null) || location="" +days=$(jq -r '.days // 3' <<< "$input" 2>/dev/null) || days=3 + +if [[ -z "$location" ]]; then + jq -n '{error: "location is required"}' + exit 0 +fi + +if ! [[ "$days" =~ ^[0-9]+$ ]]; then + days=3 +fi +if (( days < 1 )); then days=1; fi +if (( days > 3 )); then days=3; fi + +encoded_location=$(jq -rn --arg s "$location" '$s|@uri') + +response=$(curl -s -w "\n%{http_code}" "https://wttr.in/${encoded_location}?format=j1") +http_code=$(tail -n1 <<< "$response") +body=$(sed '$d' <<< "$response") + +if [[ "$http_code" != "200" ]]; then + jq -n --arg code "$http_code" '{error: "wttr.in request failed with status \($code)"}' + exit 0 +fi + +echo "$body" | jq --argjson days "$days" '{ + forecast: [.weather[0:$days][] | { + date: .date, + max_temp_c: .maxtempC, + min_temp_c: .mintempC, + condition: .hourly[0].weatherDesc[0].value + }] +}' 2>/dev/null || jq -n '{error: "failed to parse forecast data"}'