@ouro.bot/cli 0.1.0-alpha.234 → 0.1.0-alpha.236
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.json
CHANGED
|
@@ -2,9 +2,20 @@
|
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
4
|
{
|
|
5
|
-
"version": "0.1.0-alpha.
|
|
5
|
+
"version": "0.1.0-alpha.236",
|
|
6
6
|
"changes": [
|
|
7
|
-
"fix(
|
|
7
|
+
"fix(engine): provider-aware retry classification — retries now use both generic isTransientError AND provider classifyError",
|
|
8
|
+
"feat(engine): retry instrumentation — engine.provider_retry / engine.provider_retry_skip events with full diagnostics"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"version": "0.1.0-alpha.235",
|
|
13
|
+
"changes": [
|
|
14
|
+
"fix: MCP tool double-prefix \u2014 tools already prefixed by server name no longer get a redundant second prefix in the unified registry.",
|
|
15
|
+
"feat: Open-Meteo zero-auth weather \u2014 replaced OpenWeatherMap with Open-Meteo forecast + geocoding API. Weather now works without any API key or credential provisioning.",
|
|
16
|
+
"feat: expanded ISO/FIPS divergence table \u2014 21 new entries for correct travel advisory resolution across all major divergent country codes.",
|
|
17
|
+
"fix: BitwardenCredentialStore retry logic \u2014 exponential backoff with configurable retries for transient bw CLI failures, plus bw-not-installed error.",
|
|
18
|
+
"chore: travel MCP packages (Duffel, Expedia) status confirmed GitHub-only, not published to npm."
|
|
8
19
|
]
|
|
9
20
|
},
|
|
10
21
|
{
|
package/dist/heart/core.js
CHANGED
|
@@ -959,20 +959,48 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
959
959
|
callbacks.onError(new Error("context trimmed, retrying..."), "transient");
|
|
960
960
|
continue;
|
|
961
961
|
}
|
|
962
|
-
// Transient errors: retry with exponential backoff
|
|
963
|
-
|
|
962
|
+
// Transient errors: retry with exponential backoff.
|
|
963
|
+
// Two-layer detection: generic isTransientError (HTTP codes, network patterns)
|
|
964
|
+
// PLUS provider-specific classifyError (catches SDK-wrapped errors that
|
|
965
|
+
// generic detection might miss).
|
|
966
|
+
const errorForClassification = e instanceof Error ? e : /* v8 ignore next -- defensive @preserve */ new Error(String(e));
|
|
967
|
+
let providerClassification;
|
|
968
|
+
try {
|
|
969
|
+
providerClassification = providerRuntime.classifyError(errorForClassification);
|
|
970
|
+
}
|
|
971
|
+
catch {
|
|
972
|
+
/* v8 ignore next -- defensive: classifyError should not throw @preserve */
|
|
973
|
+
providerClassification = "unknown";
|
|
974
|
+
}
|
|
975
|
+
const retryableByClassification = providerClassification === "rate-limit"
|
|
976
|
+
|| providerClassification === "server-error"
|
|
977
|
+
|| providerClassification === "network-error";
|
|
978
|
+
const retryableByGeneric = isTransientError(e);
|
|
979
|
+
const shouldRetry = (retryableByGeneric || retryableByClassification) && retryCount < MAX_RETRIES;
|
|
980
|
+
(0, runtime_1.emitNervesEvent)({
|
|
981
|
+
level: shouldRetry ? "info" : "warn",
|
|
982
|
+
event: shouldRetry ? "engine.provider_retry" : "engine.provider_retry_skip",
|
|
983
|
+
component: "engine",
|
|
984
|
+
message: shouldRetry
|
|
985
|
+
? `provider error is retryable (attempt ${retryCount + 1}/${MAX_RETRIES})`
|
|
986
|
+
: `provider error is not retryable or retries exhausted`,
|
|
987
|
+
meta: {
|
|
988
|
+
provider: providerRuntime.id,
|
|
989
|
+
model: providerRuntime.model,
|
|
990
|
+
retryCount,
|
|
991
|
+
maxRetries: MAX_RETRIES,
|
|
992
|
+
retryableByGeneric,
|
|
993
|
+
retryableByClassification,
|
|
994
|
+
providerClassification,
|
|
995
|
+
errorMessage: errorForClassification.message.slice(0, 200),
|
|
996
|
+
httpStatus: e.status ?? null,
|
|
997
|
+
},
|
|
998
|
+
});
|
|
999
|
+
if (shouldRetry) {
|
|
964
1000
|
retryCount++;
|
|
965
1001
|
const delay = RETRY_BASE_MS * Math.pow(2, retryCount - 1);
|
|
966
|
-
let classification;
|
|
967
|
-
try {
|
|
968
|
-
classification = providerRuntime.classifyError(e instanceof Error ? e : /* v8 ignore next -- defensive: errors are always Error instances @preserve */ new Error(String(e)));
|
|
969
|
-
}
|
|
970
|
-
catch {
|
|
971
|
-
/* v8 ignore next -- defensive: classifyError should not throw @preserve */
|
|
972
|
-
classification = classifyTransientError(e);
|
|
973
|
-
}
|
|
974
1002
|
/* v8 ignore next -- defensive: all classifications have labels @preserve */
|
|
975
|
-
const cause = TRANSIENT_RETRY_LABELS[
|
|
1003
|
+
const cause = TRANSIENT_RETRY_LABELS[providerClassification] ?? providerClassification;
|
|
976
1004
|
callbacks.onError(new Error(`${cause}, retrying in ${delay / 1000}s (${retryCount}/${MAX_RETRIES})...`), "transient");
|
|
977
1005
|
// Wait with abort support
|
|
978
1006
|
const aborted = await new Promise((resolve) => {
|
|
@@ -995,14 +1023,8 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
995
1023
|
providerRuntime.resetTurnState(messages);
|
|
996
1024
|
continue;
|
|
997
1025
|
}
|
|
998
|
-
terminalError =
|
|
999
|
-
|
|
1000
|
-
terminalErrorClassification = providerRuntime.classifyError(terminalError);
|
|
1001
|
-
}
|
|
1002
|
-
catch {
|
|
1003
|
-
/* v8 ignore next -- defensive: classifyError should not throw @preserve */
|
|
1004
|
-
terminalErrorClassification = "unknown";
|
|
1005
|
-
}
|
|
1026
|
+
terminalError = errorForClassification;
|
|
1027
|
+
terminalErrorClassification = providerClassification;
|
|
1006
1028
|
/* v8 ignore start — auth-failure guidance: tested via provider error classification tests @preserve */
|
|
1007
1029
|
if (terminalErrorClassification === "auth-failure") {
|
|
1008
1030
|
const agentName = (0, identity_2.getAgentName)();
|
|
@@ -22,6 +22,10 @@ function execBw(args, sessionToken) {
|
|
|
22
22
|
return new Promise((resolve, reject) => {
|
|
23
23
|
(0, node_child_process_1.execFile)("bw", args, { timeout: 30_000, env }, (err, stdout) => {
|
|
24
24
|
if (err) {
|
|
25
|
+
if (isBwNotInstalled(err)) {
|
|
26
|
+
reject(new Error("bw CLI not found. Install from https://bitwarden.com/help/cli/"));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
25
29
|
reject(new Error(`bw CLI error: ${err.message}`));
|
|
26
30
|
return;
|
|
27
31
|
}
|
|
@@ -29,6 +33,27 @@ function execBw(args, sessionToken) {
|
|
|
29
33
|
});
|
|
30
34
|
});
|
|
31
35
|
}
|
|
36
|
+
/** Check if the error indicates the bw CLI binary is not installed. */
|
|
37
|
+
function isBwNotInstalled(err) {
|
|
38
|
+
const msg = err.message.toLowerCase();
|
|
39
|
+
const code = err.code;
|
|
40
|
+
return code === "ENOENT" || msg.includes("enoent") || msg.includes("not found") || msg.includes("command not found");
|
|
41
|
+
}
|
|
42
|
+
/** Check if the error is transient (network/timeout) and worth retrying. */
|
|
43
|
+
function isTransientError(err) {
|
|
44
|
+
const msg = err.message.toLowerCase();
|
|
45
|
+
return (msg.includes("econnrefused") ||
|
|
46
|
+
msg.includes("etimedout") ||
|
|
47
|
+
msg.includes("enotfound") ||
|
|
48
|
+
msg.includes("socket hang up") ||
|
|
49
|
+
msg.includes("503") ||
|
|
50
|
+
msg.includes("server unavailable"));
|
|
51
|
+
}
|
|
52
|
+
const MAX_RETRIES = 3;
|
|
53
|
+
const BASE_BACKOFF_MS = 1000;
|
|
54
|
+
function delay(ms) {
|
|
55
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
56
|
+
}
|
|
32
57
|
// ---------------------------------------------------------------------------
|
|
33
58
|
// BitwardenCredentialStore
|
|
34
59
|
// ---------------------------------------------------------------------------
|
|
@@ -48,15 +73,50 @@ class BitwardenCredentialStore {
|
|
|
48
73
|
/**
|
|
49
74
|
* Ensure the bw CLI is authenticated and unlocked.
|
|
50
75
|
* Handles three states: logged out → login, locked → unlock, already unlocked → no-op.
|
|
76
|
+
* Retries transient failures (network/timeout) up to MAX_RETRIES with exponential backoff.
|
|
51
77
|
*/
|
|
52
78
|
async login() {
|
|
79
|
+
let lastError;
|
|
80
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
81
|
+
try {
|
|
82
|
+
await this.loginAttempt();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
/* v8 ignore next -- defensive: loginAttempt always throws Error instances @preserve */
|
|
87
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
88
|
+
// Don't retry non-transient errors (auth failures, bw not installed)
|
|
89
|
+
if (!isTransientError(lastError)) {
|
|
90
|
+
throw lastError;
|
|
91
|
+
}
|
|
92
|
+
// Don't retry after final attempt
|
|
93
|
+
if (attempt === MAX_RETRIES - 1)
|
|
94
|
+
break;
|
|
95
|
+
const backoffMs = BASE_BACKOFF_MS * Math.pow(2, attempt);
|
|
96
|
+
(0, runtime_1.emitNervesEvent)({
|
|
97
|
+
event: "repertoire.bw_login_retry",
|
|
98
|
+
component: "repertoire",
|
|
99
|
+
message: `bw login attempt ${attempt + 1} failed, retrying in ${backoffMs}ms`,
|
|
100
|
+
meta: { attempt: attempt + 1, backoffMs, reason: lastError.message },
|
|
101
|
+
});
|
|
102
|
+
await delay(backoffMs);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
throw lastError;
|
|
106
|
+
}
|
|
107
|
+
/** Single login attempt — called by login() retry loop. */
|
|
108
|
+
async loginAttempt() {
|
|
53
109
|
// Check current status
|
|
54
110
|
let status = {};
|
|
55
111
|
try {
|
|
56
112
|
const raw = await execBw(["status"]);
|
|
57
113
|
status = JSON.parse(raw);
|
|
58
114
|
}
|
|
59
|
-
catch {
|
|
115
|
+
catch (err) {
|
|
116
|
+
// If bw CLI is not installed or a transient error, propagate it for retry
|
|
117
|
+
if (err instanceof Error && (isBwNotInstalled(err) || isTransientError(err))) {
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
60
120
|
// CLI not configured or broken — proceed with full setup
|
|
61
121
|
}
|
|
62
122
|
// Configure server URL if needed (only works when logged out)
|
|
@@ -18,7 +18,9 @@ function mcpToolsAsDefinitions(mcpManager) {
|
|
|
18
18
|
tool: {
|
|
19
19
|
type: "function",
|
|
20
20
|
function: {
|
|
21
|
-
name: `${entry.server}_
|
|
21
|
+
name: tool.name.startsWith(`${entry.server}_`) || tool.name === entry.server
|
|
22
|
+
? tool.name
|
|
23
|
+
: `${entry.server}_${tool.name}`,
|
|
22
24
|
description: tool.description || `MCP tool: ${tool.name} (server: ${entry.server})`,
|
|
23
25
|
parameters: tool.inputSchema ?? { type: "object", properties: {} },
|
|
24
26
|
},
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
* Travel API client module.
|
|
4
4
|
*
|
|
5
5
|
* Provides HTTP clients for:
|
|
6
|
-
* -
|
|
6
|
+
* - Open-Meteo weather forecast (no auth, WMO weather codes)
|
|
7
7
|
* - US State Dept Travel Advisories (no auth)
|
|
8
8
|
* - Nominatim Geocoding (no auth, requires User-Agent)
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.TRAVEL_ADVISORY_RSS_URL = void 0;
|
|
11
|
+
exports.TRAVEL_ADVISORY_RSS_URL = exports.WMO_WEATHER_CODES = void 0;
|
|
12
|
+
exports.wmoWeatherDescription = wmoWeatherDescription;
|
|
12
13
|
exports.getWeather = getWeather;
|
|
13
14
|
exports.getWeatherByCity = getWeatherByCity;
|
|
14
15
|
exports.isoToCountryName = isoToCountryName;
|
|
@@ -20,47 +21,105 @@ exports.geocode = geocode;
|
|
|
20
21
|
exports.reverseGeocode = reverseGeocode;
|
|
21
22
|
exports.searchPOI = searchPOI;
|
|
22
23
|
const runtime_1 = require("../nerves/runtime");
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
// --- Open-Meteo Weather (zero auth) ---
|
|
25
|
+
const OPEN_METEO_FORECAST = "https://api.open-meteo.com/v1/forecast";
|
|
26
|
+
const OPEN_METEO_GEOCODING = "https://geocoding-api.open-meteo.com/v1/search";
|
|
27
|
+
const OPEN_METEO_HEADERS = {
|
|
28
|
+
"User-Agent": "Ouroboros/1.0 (https://github.com/ouroborosbot/ouroboros)",
|
|
29
|
+
Accept: "application/json",
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* WMO weather interpretation codes (WW) to human-readable descriptions.
|
|
33
|
+
* Spec: https://www.nodc.noaa.gov/archive/arc0021/0002199/1.1/data/0-data/HTML/WMO-CODE/WMO4677.HTM
|
|
34
|
+
*/
|
|
35
|
+
exports.WMO_WEATHER_CODES = {
|
|
36
|
+
0: "clear sky",
|
|
37
|
+
1: "mainly clear",
|
|
38
|
+
2: "partly cloudy",
|
|
39
|
+
3: "overcast",
|
|
40
|
+
45: "fog",
|
|
41
|
+
48: "depositing rime fog",
|
|
42
|
+
51: "light drizzle",
|
|
43
|
+
53: "moderate drizzle",
|
|
44
|
+
55: "dense drizzle",
|
|
45
|
+
56: "light freezing drizzle",
|
|
46
|
+
57: "dense freezing drizzle",
|
|
47
|
+
61: "slight rain",
|
|
48
|
+
63: "moderate rain",
|
|
49
|
+
65: "heavy rain",
|
|
50
|
+
66: "light freezing rain",
|
|
51
|
+
67: "heavy freezing rain",
|
|
52
|
+
71: "slight snowfall",
|
|
53
|
+
73: "moderate snowfall",
|
|
54
|
+
75: "heavy snowfall",
|
|
55
|
+
77: "snow grains",
|
|
56
|
+
80: "slight rain showers",
|
|
57
|
+
81: "moderate rain showers",
|
|
58
|
+
82: "violent rain showers",
|
|
59
|
+
85: "slight snow showers",
|
|
60
|
+
86: "heavy snow showers",
|
|
61
|
+
95: "thunderstorm",
|
|
62
|
+
96: "thunderstorm with slight hail",
|
|
63
|
+
99: "thunderstorm with heavy hail",
|
|
64
|
+
};
|
|
65
|
+
/** Convert a WMO weather code to a human-readable description. */
|
|
66
|
+
function wmoWeatherDescription(code) {
|
|
67
|
+
return exports.WMO_WEATHER_CODES[code] ?? "unknown";
|
|
30
68
|
}
|
|
31
69
|
async function getWeather(lat, lon) {
|
|
32
70
|
return withNervesEvents("getWeather", { lat, lon }, async () => {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
71
|
+
const url = `${OPEN_METEO_FORECAST}?latitude=${lat}&longitude=${lon}` +
|
|
72
|
+
`¤t=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,weather_code` +
|
|
73
|
+
`&daily=temperature_2m_max,temperature_2m_min,weather_code` +
|
|
74
|
+
`&timezone=auto`;
|
|
75
|
+
const res = await fetch(url, { headers: OPEN_METEO_HEADERS });
|
|
36
76
|
if (!res.ok) {
|
|
37
|
-
throw new Error(`
|
|
77
|
+
throw new Error(`Open-Meteo API error: ${res.status} ${res.statusText}`);
|
|
38
78
|
}
|
|
39
79
|
const data = await res.json();
|
|
40
|
-
return
|
|
80
|
+
return parseOpenMeteoResponse(data, lat, lon);
|
|
41
81
|
});
|
|
42
82
|
}
|
|
43
83
|
async function getWeatherByCity(city) {
|
|
44
84
|
return withNervesEvents("getWeatherByCity", { city }, async () => {
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const
|
|
85
|
+
// Step 1: geocode the city name via Open-Meteo geocoding
|
|
86
|
+
const geoUrl = `${OPEN_METEO_GEOCODING}?name=${encodeURIComponent(city)}&count=1&language=en`;
|
|
87
|
+
const geoRes = await fetch(geoUrl, { headers: OPEN_METEO_HEADERS });
|
|
88
|
+
if (!geoRes.ok) {
|
|
89
|
+
throw new Error(`Open-Meteo geocoding error: ${geoRes.status} ${geoRes.statusText}`);
|
|
90
|
+
}
|
|
91
|
+
const geoData = await geoRes.json();
|
|
92
|
+
const results = geoData.results;
|
|
93
|
+
if (!results || results.length === 0) {
|
|
94
|
+
throw new Error(`No location found for city "${city}"`);
|
|
95
|
+
}
|
|
96
|
+
const loc = results[0];
|
|
97
|
+
// Step 2: fetch forecast for the resolved coordinates
|
|
98
|
+
const url = `${OPEN_METEO_FORECAST}?latitude=${loc.latitude}&longitude=${loc.longitude}` +
|
|
99
|
+
`¤t=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,weather_code` +
|
|
100
|
+
`&daily=temperature_2m_max,temperature_2m_min,weather_code` +
|
|
101
|
+
`&timezone=auto`;
|
|
102
|
+
const res = await fetch(url, { headers: OPEN_METEO_HEADERS });
|
|
48
103
|
if (!res.ok) {
|
|
49
|
-
throw new Error(`
|
|
104
|
+
throw new Error(`Open-Meteo API error: ${res.status} ${res.statusText}`);
|
|
50
105
|
}
|
|
51
106
|
const data = await res.json();
|
|
52
|
-
return
|
|
107
|
+
return parseOpenMeteoResponse(data, loc.latitude, loc.longitude, loc.name, loc.country_code);
|
|
53
108
|
});
|
|
54
109
|
}
|
|
55
|
-
function
|
|
110
|
+
function parseOpenMeteoResponse(data, lat, lon, cityName, countryCode) {
|
|
111
|
+
const current = data.current ?? {};
|
|
112
|
+
const daily = data.daily ?? {};
|
|
56
113
|
return {
|
|
57
|
-
temperature:
|
|
58
|
-
feelsLike:
|
|
59
|
-
description:
|
|
60
|
-
humidity:
|
|
61
|
-
windSpeed:
|
|
62
|
-
city:
|
|
63
|
-
country:
|
|
114
|
+
temperature: current.temperature_2m ?? 0,
|
|
115
|
+
feelsLike: current.apparent_temperature ?? 0,
|
|
116
|
+
description: wmoWeatherDescription(current.weather_code ?? -1),
|
|
117
|
+
humidity: current.relative_humidity_2m ?? 0,
|
|
118
|
+
windSpeed: current.wind_speed_10m ?? 0,
|
|
119
|
+
city: cityName ?? `${lat},${lon}`,
|
|
120
|
+
country: countryCode ?? "",
|
|
121
|
+
dailyHigh: daily.temperature_2m_max?.[0],
|
|
122
|
+
dailyLow: daily.temperature_2m_min?.[0],
|
|
64
123
|
};
|
|
65
124
|
}
|
|
66
125
|
// --- US State Dept Travel Advisories ---
|
|
@@ -79,31 +138,52 @@ exports.TRAVEL_ADVISORY_RSS_URL = "https://travel.state.gov/_res/rss/TAsTWs.xml"
|
|
|
79
138
|
* This table handles the ~30 codes where they diverge.
|
|
80
139
|
*/
|
|
81
140
|
const ISO_FIPS_DIVERGENCE = {
|
|
141
|
+
AG: "Antigua and Barbuda", // FIPS: AC
|
|
82
142
|
AT: "Austria", // FIPS: AU
|
|
83
143
|
AU: "Australia", // FIPS: AS
|
|
84
144
|
BD: "Bangladesh", // FIPS: BG
|
|
145
|
+
BH: "Bahrain", // FIPS: BA
|
|
85
146
|
BN: "Brunei", // FIPS: BX
|
|
86
147
|
BO: "Bolivia", // FIPS: BL
|
|
148
|
+
BS: "Bahamas", // FIPS: BF
|
|
149
|
+
BW: "Botswana", // FIPS: BC
|
|
150
|
+
CD: "Congo (Kinshasa)", // FIPS: CG
|
|
87
151
|
CH: "Switzerland", // FIPS: SZ
|
|
152
|
+
CI: "Cote d'Ivoire", // FIPS: IV
|
|
153
|
+
CL: "Chile", // FIPS: CI
|
|
154
|
+
CN: "China", // FIPS: CH
|
|
88
155
|
CZ: "Czechia", // FIPS: EZ
|
|
89
156
|
DE: "Germany", // FIPS: GM
|
|
157
|
+
DK: "Denmark", // FIPS: DA
|
|
158
|
+
DO: "Dominican Republic", // FIPS: DR
|
|
159
|
+
DZ: "Algeria", // FIPS: AG
|
|
90
160
|
ES: "Spain", // FIPS: SP
|
|
91
161
|
FI: "Finland", // FIPS: FI → same, but title might say "Finland"
|
|
92
162
|
GQ: "Equatorial Guinea", // FIPS: EK
|
|
93
163
|
HR: "Croatia", // FIPS: HR → same
|
|
94
164
|
IE: "Ireland", // FIPS: EI
|
|
165
|
+
IL: "Israel", // FIPS: IS
|
|
166
|
+
JP: "Japan", // FIPS: JA
|
|
95
167
|
KP: "North Korea", // FIPS: KN
|
|
96
168
|
KR: "South Korea", // FIPS: KS
|
|
97
169
|
LT: "Lithuania", // FIPS: LH
|
|
170
|
+
MA: "Morocco", // FIPS: MO
|
|
98
171
|
MM: "Burma", // FIPS: BM (State Dept uses "Burma (Myanmar)")
|
|
172
|
+
NG: "Nigeria", // FIPS: NI
|
|
99
173
|
NO: "Norway", // FIPS: NO → same
|
|
100
174
|
PH: "Philippines", // FIPS: RP
|
|
175
|
+
PT: "Portugal", // FIPS: PO
|
|
101
176
|
RO: "Romania", // FIPS: RO → same
|
|
102
177
|
SE: "Sweden", // FIPS: SW
|
|
178
|
+
SG: "Singapore", // FIPS: SN
|
|
103
179
|
TL: "Timor-Leste", // FIPS: TT
|
|
180
|
+
TR: "Turkey", // FIPS: TU
|
|
104
181
|
TW: "Taiwan", // FIPS: TW → same
|
|
182
|
+
UA: "Ukraine", // FIPS: UP
|
|
105
183
|
VA: "Holy See", // FIPS: VT
|
|
184
|
+
VN: "Vietnam", // FIPS: VM
|
|
106
185
|
YE: "Yemen", // FIPS: YM
|
|
186
|
+
ZA: "South Africa", // FIPS: SF
|
|
107
187
|
};
|
|
108
188
|
/**
|
|
109
189
|
* Look up the expected country name for an ISO alpha-2 code,
|
package/package.json
CHANGED
|
@@ -117,7 +117,7 @@ Track and reference these travel preferences:
|
|
|
117
117
|
## Tool Reference
|
|
118
118
|
|
|
119
119
|
### Native Tools (always available)
|
|
120
|
-
- `weather_lookup` - Current weather by city or coordinates
|
|
120
|
+
- `weather_lookup` - Current weather and daily forecast by city or coordinates (Open-Meteo, zero config)
|
|
121
121
|
- `travel_advisory` - US State Dept advisory by country code
|
|
122
122
|
- `geocode_search` - Location/POI search with coordinates
|
|
123
123
|
- `credential_get` - Check credential metadata for a domain (never returns passwords)
|