Adding AI Error Analysis to a macOS Menu Bar App with Gemini API

Published: (April 20, 2026 at 08:38 AM EDT)
2 min read
Source: Dev.to

Source: Dev.to

The Problem

When a monitored script fails, most tools just show you a red icon.
In 2026 we expect the AI to come to you and provide actionable insight automatically.

Architecture

  • The script is executed via std::process::Command.
  • The entire flow is automatic; no user interaction is required.

Implementation

pub async fn analyze_error(prompt: &str, api_key: &str) -> Result {
    let client = reqwest::Client::new();
    let response = client
        .post("https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent")
        .header("X-goog-api-key", api_key)
        .json(&serde_json::json!({
            "contents": [{
                "parts": [{ "text": prompt }]
            }],
            "generationConfig": {
                "maxOutputTokens": 256,
                "temperature": 0.3
            }
        }))
        .send()
        .await?;

    let data: serde_json::Value = response.json().await?;
    let text = data["candidates"][0]["content"]["parts"][0]["text"]
        .as_str()
        .unwrap_or("Analysis unavailable")
        .to_string();

    Ok(text)
}

Key Decisions

  • Model: gemini-2.5-flash for speed and cost efficiency.

Triggering the Analysis

match output {
    Ok(out) if out.status.success() => MonitorResult::Success(
        String::from_utf8_lossy(&out.stdout).to_string()
    ),
    Ok(out) => {
        let error_text = format!(
            "stdout: {}\nstderr: {}",
            String::from_utf8_lossy(&out.stdout),
            String::from_utf8_lossy(&out.stderr)
        );

        // Only analyze if AI is enabled and key is available
        let insight = if monitor.analyze_errors {
            if let Some(key) = api_key {
                analyze_error(&error_text, key).await.ok()
            } else {
                None
            }
        } else {
            None
        };

        MonitorResult::Error { error_text, insight }
    }
    Err(e) => MonitorResult::Error {
        error_text: e.to_string(),
        insight: None,
    },
}

The AI Insight UI


  
  AI INSIGHT

{result.insight}

Storing the API Key Securely

pub fn save_gemini_key(key: &str) -> keyring::Result {
    // implementation omitted for brevity
    unimplemented!()
}

pub fn load_gemini_key() -> keyring::Result {
    // implementation omitted for brevity
    unimplemented!()
}

References

  • HiyokoBar:
  • 日本語版:
  • Product Hunt launch:
0 views
Back to Blog

Related posts

Read more »