diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index ddf2cc7..521f5dc 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -10,6 +10,7 @@ "core:event:default", "core:event:allow-listen", "core:event:allow-emit", - "allow-audit-logs" + "allow-audit-logs", + "allow-mcp-folder-picker" ] } diff --git a/src-tauri/permissions/mcp-folder-picker.toml b/src-tauri/permissions/mcp-folder-picker.toml new file mode 100644 index 0000000..4c748ed --- /dev/null +++ b/src-tauri/permissions/mcp-folder-picker.toml @@ -0,0 +1,4 @@ +[[permission]] +identifier = "allow-mcp-folder-picker" +description = "Open the native folder picker for MCP filesystem allow-list paths." +commands.allow = ["pick_mcp_filesystem_folder"] diff --git a/src-tauri/src/modules/bot/commands.rs b/src-tauri/src/modules/bot/commands.rs index bc54f44..6b15d95 100644 --- a/src-tauri/src/modules/bot/commands.rs +++ b/src-tauri/src/modules/bot/commands.rs @@ -46,14 +46,24 @@ pub async fn disconnect_bot(state: tauri::State<'_, AppState>) -> Result Result, String> { - let folder = app - .dialog() + let (tx, rx) = tokio::sync::oneshot::channel(); + app.dialog() .file() .set_title("Folder for MCP filesystem tools") - .blocking_pick_folder(); + .pick_folder(move |folder| { + let _ = tx.send(folder); + }); + let folder = rx + .await + .map_err(|e| format!("folder picker closed unexpectedly: {e}"))?; Ok(folder.map(|p| p.to_string())) } diff --git a/src/modules/mcp/components/McpServerCard.tsx b/src/modules/mcp/components/McpServerCard.tsx index 98bf031..c7132a7 100644 --- a/src/modules/mcp/components/McpServerCard.tsx +++ b/src/modules/mcp/components/McpServerCard.tsx @@ -34,6 +34,14 @@ type Props = { onReloadServers?: () => Promise; }; +/** Tauri `invoke` rejects with a plain string for command `Err(...)` returns, + * not an `Error` — surface that string instead of a generic fallback. */ +function pickFolderErrorMessage(e: unknown): string { + if (e instanceof Error) return e.message; + if (typeof e === "string" && e.trim()) return e; + return "Could not open folder picker"; +} + /** Detect filesystem MCP package in live args textarea (one token per line). */ function argsTextLooksLikeFilesystem(argsText: string): boolean { return argsText @@ -329,9 +337,7 @@ function InlineEditForm({ const picked = await invoke("pick_mcp_filesystem_folder"); if (picked) addPath(picked); } catch (invokeErr) { - setPickFolderError( - invokeErr instanceof Error ? invokeErr.message : "Could not open folder picker", - ); + setPickFolderError(pickFolderErrorMessage(invokeErr)); } } catch { // Web / non-Tauri: dynamic import of `@tauri-apps/api/core` fails — expected, stay silent @@ -356,9 +362,7 @@ function InlineEditForm({ const picked = await invoke("pick_mcp_filesystem_folder"); if (picked) addTePath(picked); } catch (invokeErr) { - setTePickError( - invokeErr instanceof Error ? invokeErr.message : "Could not open folder picker", - ); + setTePickError(pickFolderErrorMessage(invokeErr)); } } catch { // Web / non-Tauri @@ -386,9 +390,7 @@ function InlineEditForm({ const picked = await invoke("pick_mcp_filesystem_folder"); if (picked) setTePrivatePathInput(picked); } catch (invokeErr) { - setTePrivatePickError( - invokeErr instanceof Error ? invokeErr.message : "Could not open folder picker", - ); + setTePrivatePickError(pickFolderErrorMessage(invokeErr)); } } catch { setTePrivatePickError("Folder picker needs the desktop app (Tauri)."); diff --git a/tools/skills/weather/SKILL.md b/tools/skills/weather/SKILL.md index de2910f..356681e 100644 --- a/tools/skills/weather/SKILL.md +++ b/tools/skills/weather/SKILL.md @@ -25,12 +25,12 @@ Chat, not a datasheet: short intro on how it _feels_, then compact day lines (da ## wttr.in (curl / fetch) -| Goal | Example | -| ---- | ------- | -| Short (default), 1–2 days | `curl -s "wttr.in/London?2&m"` | -| Full week-style table | `curl -s "wttr.in/London?T&m"` | -| One line now | `curl -s "wttr.in/London?format=3"` | -| Custom one-liner | `curl -s "wttr.in/London?format=%l:+%c+%t+%h+%w"` | +| Goal | Example | +| ------------------------- | ------------------------------------------------- | +| Short (default), 1–2 days | `curl -s "wttr.in/London?2&m"` | +| Full week-style table | `curl -s "wttr.in/London?T&m"` | +| One line now | `curl -s "wttr.in/London?format=3"` | +| Custom one-liner | `curl -s "wttr.in/London?format=%l:+%c+%t+%h+%w"` | Tips: `+` for spaces; `?m` / `?u` units; `?1` today only; `?0` now only; airports `wttr.in/JFK`; PNG `wttr.in/Berlin.png`. @@ -41,7 +41,7 @@ Tips: `+` for spaces; `?m` / `?u` units; `?1` today only; `?0` now only; airport **Flow (max 2 geocode + 1 forecast):** 1. `https://geocoding-api.open-meteo.com/v1/search?name=PLACE&count=10` (spaces as `+`) -2. If `results` missing/empty: **one** retry — shorter `name` + `countryCode` (e.g. `name=Breitenau&countryCode=AT` for _Breitenau am Hochlantsch_). Pick the row whose `admin3`/`admin2`/`admin1` matches the user phrase. +2. If `results` missing/empty: **one** retry — shorter `name` + `countryCode` Pick the row whose `admin3`/`admin2`/`admin1` matches the user phrase. 3. `https://api.open-meteo.com/v1/forecast?latitude=LAT&longitude=LON&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max,weathercode&forecast_days=7&timezone=auto` — extend `forecast_days` to 7–16 if they asked for more days. Current only: `curl -s "https://api.open-meteo.com/v1/forecast?latitude=51.5&longitude=-0.12¤t_weather=true"` diff --git a/tools/skills/weather/mandatory.md b/tools/skills/weather/mandatory.md index fbdd082..da6a266 100644 --- a/tools/skills/weather/mandatory.md +++ b/tools/skills/weather/mandatory.md @@ -1 +1 @@ -**MANDATORY for skill:weather:** **One fetch first:** prefer `https://wttr.in/PLACE?2&m` (spaces → `+`, e.g. `Breitenau+am+Hochlantsch`) for tomorrow/today-style questions — smaller/faster than the full table. Use `https://wttr.in/PLACE?T&m` only if the user wants a longer range or `?2` is insufficient. If the body is a normal forecast, **answer and stop** — do not call Open-Meteo in the same run. **Open-Meteo** only if wttr fails or raw JSON is needed: (1) `https://geocoding-api.open-meteo.com/v1/search?name=…&count=10`; (2) if `results` is missing or empty, **one** retry with shorter `name` + `countryCode` (e.g. `https://geocoding-api.open-meteo.com/v1/search?name=Breitenau&countryCode=AT&count=10` for “Breitenau am Hochlantsch”) and pick the row whose `admin3`/`admin2`/`admin1` matches the user phrase; (3) `https://api.open-meteo.com/v1/forecast?latitude=…&longitude=…&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max,weathercode&forecast_days=7&timezone=auto`. Never claim the place was not found after a single geocode on compound EU names. Reply per **How to answer**: plain language; no coordinates or WMO codes unless the user asked for technical detail. +**MANDATORY for skill:weather:** **One fetch first:** prefer `https://wttr.in/PLACE?2&m` for tomorrow/today-style questions — smaller/faster than the full table. Use `https://wttr.in/PLACE?T&m` only if the user wants a longer range or `?2` is insufficient. If the body is a normal forecast, **answer and stop** — do not call Open-Meteo in the same run. **Open-Meteo** only if wttr fails or raw JSON is needed: (1) `https://geocoding-api.open-meteo.com/v1/search?name=…&count=10`; (2) if `results` is missing or empty, **one** retry with shorter `name` + `countryCode` (e.g. `https://geocoding-api.open-meteo.com/v1/search?name=Breitenau&countryCode=AT&count=10` for “Breitenau am Hochlantsch”) and pick the row whose `admin3`/`admin2`/`admin1` matches the user phrase; (3) `https://api.open-meteo.com/v1/forecast?latitude=…&longitude=…&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max,weathercode&forecast_days=7&timezone=auto`. Never claim the place was not found after a single geocode on compound EU names. Reply per **How to answer**: plain language; no coordinates or WMO codes unless the user asked for technical detail.