feat: add check_update and update tools with background auto-check

On the first tool call of each session, a background task fires once
and fetches the latest release tag from the GitHub API (5 s timeout).
The result is cached in a static OnceLock for the lifetime of the
process — no repeated network calls.

check_update: returns cached result instantly, or fetches on demand
  if the background task hasn't completed yet.

update: downloads the latest tarball for the current platform, extracts
  the binary, and atomically replaces the running executable via a
  temp-file rename. Requires MCP reconnect to activate new version.

Platform support: macos-aarch64, macos-x86_64, linux-x86_64.
Unsupported platforms return a build-from-source hint.

Tool count: 87 → 89. README and MCP_TOOLS.md updated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Devid HW
2026-04-22 07:34:12 +07:00
co-authored by Claude Sonnet 4.6
parent 6f773d6f09
commit bb6240657e
6 changed files with 220 additions and 5 deletions
+22 -3
View File
@@ -2,6 +2,8 @@ use anyhow::Result;
use chrono::Datelike;
use serde_json::{json, Value};
use std::path::Path;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::models::Config;
mod system;
@@ -13,6 +15,13 @@ mod setfiles;
mod reports;
mod utility;
/// Cached result of the background update check.
/// - Not yet initialized: check still in flight (or not spawned yet)
/// - Some(version): a newer version is available
/// - None: already on latest, or GitHub unreachable
pub(crate) static LATEST_VERSION: OnceLock<Option<String>> = OnceLock::new();
static BACKGROUND_CHECK_SPAWNED: AtomicBool = AtomicBool::new(false);
#[derive(Debug)]
pub struct ToolHandler {
pub config: Config,
@@ -24,12 +33,22 @@ impl ToolHandler {
}
pub async fn handle(&self, name: &str, args: &Value) -> Result<Value> {
// Spawn a one-shot background update check on the very first tool call of the session.
if !BACKGROUND_CHECK_SPAWNED.swap(true, Ordering::Relaxed) {
tokio::spawn(async {
let result = system::fetch_latest_version().await;
let _ = LATEST_VERSION.set(result);
});
}
match name {
// System handlers
"verify_setup" => system::handle_verify_setup(&self.config).await,
"list_symbols" => system::handle_list_symbols(&self.config).await,
"healthcheck" => system::handle_healthcheck(&self.config, args).await,
"verify_setup" => system::handle_verify_setup(&self.config).await,
"list_symbols" => system::handle_list_symbols(&self.config).await,
"healthcheck" => system::handle_healthcheck(&self.config, args).await,
"get_active_account" => system::handle_get_active_account(&self.config).await,
"check_update" => system::handle_check_update(&self.config).await,
"update" => system::handle_update(&self.config).await,
// Expert/Indicator/Script handlers
"list_experts" => experts::handle_list_experts(&self.config, args).await,