@ouro.bot/cli 0.1.0-alpha.233 → 0.1.0-alpha.235
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
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
{
|
|
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
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.235",
|
|
6
|
+
"changes": [
|
|
7
|
+
"fix: MCP tool double-prefix \u2014 tools already prefixed by server name no longer get a redundant second prefix in the unified registry.",
|
|
8
|
+
"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.",
|
|
9
|
+
"feat: expanded ISO/FIPS divergence table \u2014 21 new entries for correct travel advisory resolution across all major divergent country codes.",
|
|
10
|
+
"fix: BitwardenCredentialStore retry logic \u2014 exponential backoff with configurable retries for transient bw CLI failures, plus bw-not-installed error.",
|
|
11
|
+
"chore: travel MCP packages (Duffel, Expedia) status confirmed GitHub-only, not published to npm."
|
|
12
|
+
]
|
|
13
|
+
},
|
|
4
14
|
{
|
|
5
15
|
"version": "0.1.0-alpha.233",
|
|
6
16
|
"changes": [
|
|
@@ -33,6 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports._senseTurnRetryDelays = exports.SENSE_TURN_RETRY_DELAYS_MS = exports.SENSE_TURN_MAX_RETRIES = void 0;
|
|
37
|
+
exports._setSenseTurnRetryDelays = _setSenseTurnRetryDelays;
|
|
36
38
|
exports.createMcpServer = createMcpServer;
|
|
37
39
|
exports.getToolSchemas = getToolSchemas;
|
|
38
40
|
const socket_client_1 = require("../daemon/socket-client");
|
|
@@ -40,6 +42,47 @@ const agentService = __importStar(require("../daemon/agent-service"));
|
|
|
40
42
|
const runtime_1 = require("../../nerves/runtime");
|
|
41
43
|
const session_id_resolver_1 = require("../daemon/session-id-resolver");
|
|
42
44
|
const pending_1 = require("../../mind/pending");
|
|
45
|
+
exports.SENSE_TURN_MAX_RETRIES = 3;
|
|
46
|
+
exports.SENSE_TURN_RETRY_DELAYS_MS = [1000, 2000, 4000];
|
|
47
|
+
// Allow test override
|
|
48
|
+
exports._senseTurnRetryDelays = exports.SENSE_TURN_RETRY_DELAYS_MS;
|
|
49
|
+
function _setSenseTurnRetryDelays(delays) { exports._senseTurnRetryDelays = delays; }
|
|
50
|
+
/**
|
|
51
|
+
* Send a senseTurn command to the daemon with retry logic.
|
|
52
|
+
* Retries on transient failures: empty response (daemon mid-restart),
|
|
53
|
+
* ECONNREFUSED (daemon not yet listening), ENOENT (socket not yet created).
|
|
54
|
+
*/
|
|
55
|
+
async function sendSenseTurnWithRetry(socketPath, command) {
|
|
56
|
+
let lastError = null;
|
|
57
|
+
/* v8 ignore start -- retry loop: functionally tested via mcp-send-message retry tests @preserve */
|
|
58
|
+
for (let attempt = 0; attempt <= exports.SENSE_TURN_MAX_RETRIES; attempt++) {
|
|
59
|
+
try {
|
|
60
|
+
const response = await (0, socket_client_1.sendDaemonCommand)(socketPath, command);
|
|
61
|
+
return response;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
65
|
+
const msg = lastError.message;
|
|
66
|
+
const isTransient = msg.includes("ECONNREFUSED")
|
|
67
|
+
|| msg.includes("ENOENT")
|
|
68
|
+
|| msg.includes("empty response")
|
|
69
|
+
|| msg.includes("Empty response");
|
|
70
|
+
if (!isTransient || attempt >= exports.SENSE_TURN_MAX_RETRIES) {
|
|
71
|
+
throw lastError;
|
|
72
|
+
}
|
|
73
|
+
const delay = exports._senseTurnRetryDelays[attempt] ?? 4000;
|
|
74
|
+
(0, runtime_1.emitNervesEvent)({
|
|
75
|
+
component: "daemon",
|
|
76
|
+
event: "daemon.mcp_sense_turn_retry",
|
|
77
|
+
message: `senseTurn attempt ${attempt + 1} failed, retrying in ${delay}ms`,
|
|
78
|
+
meta: { attempt: attempt + 1, error: msg, delay },
|
|
79
|
+
});
|
|
80
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
throw lastError ?? new Error("senseTurn failed after retries");
|
|
84
|
+
/* v8 ignore stop */
|
|
85
|
+
}
|
|
43
86
|
/**
|
|
44
87
|
* Maps MCP tool names to daemon command kinds.
|
|
45
88
|
*/
|
|
@@ -248,7 +291,7 @@ function createMcpServer(options) {
|
|
|
248
291
|
const message = toolArgs.message ?? "";
|
|
249
292
|
/* v8 ignore stop */
|
|
250
293
|
try {
|
|
251
|
-
const response = await (
|
|
294
|
+
const response = await sendSenseTurnWithRetry(socketPath, {
|
|
252
295
|
kind: "agent.senseTurn",
|
|
253
296
|
agent,
|
|
254
297
|
friendId,
|
|
@@ -322,7 +365,7 @@ function createMcpServer(options) {
|
|
|
322
365
|
const context = toolArgs.context;
|
|
323
366
|
const delegateMessage = context ? `[delegate] ${task}\n\ncontext: ${context}` : `[delegate] ${task}`;
|
|
324
367
|
try {
|
|
325
|
-
const response = await (
|
|
368
|
+
const response = await sendSenseTurnWithRetry(socketPath, {
|
|
326
369
|
kind: "agent.senseTurn",
|
|
327
370
|
agent,
|
|
328
371
|
friendId,
|
|
@@ -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)
|