diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 36f83e856d2fc1c6d51b4ceedfec91d3a34fb3e3..111805a56e630d8e5b14b63f077b197fd0882997 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -454,6 +454,7 @@ dependencies = [
 name = "clash-verge"
 version = "0.1.0"
 dependencies = [
+ "anyhow",
  "auto-launch",
  "chrono",
  "dirs",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 97c828a2bf1f4bd837346e41110dd8fb63cff488..861b30ef140d26bdbd09cc394d2d764a12aff83a 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -13,6 +13,7 @@ build = "build.rs"
 tauri-build = { version = "1.0.0-rc.3", features = [] }
 
 [dependencies]
+anyhow = "1.0"
 dirs = "4.0.0"
 dunce = "1.0.2"
 chrono = "0.4.19"
diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs
index 2a7c87c8b3fd3ea1bf941a6d0eed71289c48a3a4..8eb16094d520e3c230ff1f32dd6e62a486780461 100644
--- a/src-tauri/src/cmds.rs
+++ b/src-tauri/src/cmds.rs
@@ -1,29 +1,46 @@
 use crate::{
   core::{ClashInfo, ProfileItem, Profiles, VergeConfig},
   states::{ClashState, ProfilesState, VergeState},
-  utils::{dirs::app_home_dir, fetch::fetch_profile, sysopt::SysProxyConfig},
+  utils::{dirs, fetch::fetch_profile, sysopt::SysProxyConfig},
 };
+use anyhow::Result;
 use serde_yaml::Mapping;
 use std::{path::PathBuf, process::Command};
 use tauri::{api, State};
 
+/// wrap the anyhow error
+/// transform the error to String
+macro_rules! wrap_err {
+  ($stat: expr) => {
+    match $stat {
+      Ok(a) => Ok(a),
+      Err(err) => {
+        log::error!("{}", err.to_string());
+        Err(format!("{}", err.to_string()))
+      }
+    }
+  };
+}
+
+/// return the string literal error
+macro_rules! ret_err {
+  ($str: literal) => {
+    return Err($str.into())
+  };
+}
+
 /// get all profiles from `profiles.yaml`
-/// do not acquire the lock of ProfileLock
 #[tauri::command]
 pub fn get_profiles(profiles_state: State<'_, ProfilesState>) -> Result<Profiles, String> {
-  match profiles_state.0.lock() {
-    Ok(profiles) => Ok(profiles.clone()),
-    Err(_) => Err("failed to get profiles lock".into()),
-  }
+  let profiles = profiles_state.0.lock().unwrap();
+  Ok(profiles.clone())
 }
 
 /// synchronize data irregularly
 #[tauri::command]
 pub fn sync_profiles(profiles_state: State<'_, ProfilesState>) -> Result<(), String> {
-  match profiles_state.0.lock() {
-    Ok(mut profiles) => profiles.sync_file(),
-    Err(_) => Err("failed to get profiles lock".into()),
-  }
+  let mut profiles = profiles_state.0.lock().unwrap();
+  wrap_err!(profiles.sync_file())
 }
 
 /// import the profile from url
@@ -36,7 +53,7 @@ pub async fn import_profile(
 ) -> Result<(), String> {
   let result = fetch_profile(&url, with_proxy).await?;
   let mut profiles = profiles_state.0.lock().unwrap();
-  profiles.import_from_url(url, result)
+  wrap_err!(profiles.import_from_url(url, result))
 }
 
 /// new a profile
@@ -49,7 +66,7 @@ pub async fn new_profile(
   profiles_state: State<'_, ProfilesState>,
 ) -> Result<(), String> {
   let mut profiles = profiles_state.0.lock().unwrap();
-  profiles.append_item(name, desc)?;
+  wrap_err!(profiles.append_item(name, desc))?;
   Ok(())
 }
 
@@ -66,34 +83,34 @@ pub async fn update_profile(
     Ok(mut profile) => {
       let items = profile.items.take().unwrap_or(vec![]);
       if index >= items.len() {
-        return Err("the index out of bound".into());
+        ret_err!("the index out of bound");
       }
       let url = match &items[index].url {
         Some(u) => u.clone(),
-        None => return Err("failed to update profile for `invalid url`".into()),
+        None => ret_err!("failed to update profile for `invalid url`"),
       };
       profile.items = Some(items);
       url
     }
-    Err(_) => return Err("failed to get profiles lock".into()),
+    Err(_) => ret_err!("failed to get profiles lock"),
   };
 
   let result = fetch_profile(&url, with_proxy).await?;
 
   match profiles_state.0.lock() {
     Ok(mut profiles) => {
-      profiles.update_item(index, result)?;
+      wrap_err!(profiles.update_item(index, result))?;
 
       // reactivate the profile
       let current = profiles.current.clone().unwrap_or(0);
       if current == index {
         let clash = clash_state.0.lock().unwrap();
-        profiles.activate(&clash)
+        wrap_err!(profiles.activate(&clash))
       } else {
         Ok(())
       }
     }
-    Err(_) => Err("failed to get profiles lock".into()),
+    Err(_) => ret_err!("failed to get profiles lock"),
   }
 }
 
@@ -105,14 +122,10 @@ pub fn select_profile(
   profiles_state: State<'_, ProfilesState>,
 ) -> Result<(), String> {
   let mut profiles = profiles_state.0.lock().unwrap();
+  wrap_err!(profiles.put_current(index))?;
 
-  match profiles.put_current(index) {
-    Ok(()) => {
-      let clash = clash_state.0.lock().unwrap();
-      profiles.activate(&clash)
-    }
-    Err(err) => Err(err),
-  }
+  let clash = clash_state.0.lock().unwrap();
+  wrap_err!(profiles.activate(&clash))
 }
 
 /// delete profile item
@@ -123,16 +136,13 @@ pub fn delete_profile(
   profiles_state: State<'_, ProfilesState>,
 ) -> Result<(), String> {
   let mut profiles = profiles_state.0.lock().unwrap();
-  match profiles.delete_item(index) {
-    Ok(change) => match change {
-      true => {
-        let clash = clash_state.0.lock().unwrap();
-        profiles.activate(&clash)
-      }
-      false => Ok(()),
-    },
-    Err(err) => Err(err),
+
+  if wrap_err!(profiles.delete_item(index))? {
+    let clash = clash_state.0.lock().unwrap();
+    wrap_err!(profiles.activate(&clash))?;
   }
+
+  Ok(())
 }
 
 /// patch the profile config
@@ -142,10 +152,8 @@ pub fn patch_profile(
   profile: ProfileItem,
   profiles_state: State<'_, ProfilesState>,
 ) -> Result<(), String> {
-  match profiles_state.0.lock() {
-    Ok(mut profiles) => profiles.patch_item(index, profile),
-    Err(_) => Err("can not get profiles lock".into()),
-  }
+  let mut profiles = profiles_state.0.lock().unwrap();
+  wrap_err!(profiles.patch_item(index, profile))
 }
 
 /// run vscode command to edit the profile
@@ -156,19 +164,34 @@ pub fn view_profile(index: usize, profiles_state: State<'_, ProfilesState>) -> R
 
   if index >= items.len() {
     profiles.items = Some(items);
-    return Err("the index out of bound".into());
+    ret_err!("the index out of bound");
   }
 
   let file = items[index].file.clone().unwrap_or("".into());
   profiles.items = Some(items);
 
-  let path = app_home_dir().join("profiles").join(file);
+  let path = dirs::app_profiles_dir().join(file);
   if !path.exists() {
-    return Err("the file not found".into());
+    ret_err!("the file not found");
   }
 
   // use vscode first
   if let Ok(code) = which::which("code") {
+    #[cfg(target_os = "windows")]
+    {
+      use std::os::windows::process::CommandExt;
+
+      return match Command::new(code)
+        .creation_flags(0x08000000)
+        .arg(path)
+        .spawn()
+      {
+        Ok(_) => Ok(()),
+        Err(_) => Err("failed to open file by VScode".into()),
+      };
+    }
+
+    #[cfg(not(target_os = "windows"))]
     return match Command::new(code).arg(path).spawn() {
       Ok(_) => Ok(()),
       Err(_) => Err("failed to open file by VScode".into()),
@@ -187,23 +210,15 @@ pub fn restart_sidecar(
   let mut clash = clash_state.0.lock().unwrap();
   let mut profiles = profiles_state.0.lock().unwrap();
 
-  match clash.restart_sidecar(&mut profiles) {
-    Ok(_) => Ok(()),
-    Err(err) => {
-      log::error!("{}", err);
-      Err(err)
-    }
-  }
+  wrap_err!(clash.restart_sidecar(&mut profiles))
 }
 
 /// get the clash core info from the state
 /// the caller can also get the infomation by clash's api
 #[tauri::command]
 pub fn get_clash_info(clash_state: State<'_, ClashState>) -> Result<ClashInfo, String> {
-  match clash_state.0.lock() {
-    Ok(clash) => Ok(clash.info.clone()),
-    Err(_) => Err("failed to get clash lock".into()),
-  }
+  let clash = clash_state.0.lock().unwrap();
+  Ok(clash.info.clone())
 }
 
 /// update the clash core config
@@ -219,26 +234,21 @@ pub fn patch_clash_config(
   let mut clash = clash_state.0.lock().unwrap();
   let mut verge = verge_state.0.lock().unwrap();
   let mut profiles = profiles_state.0.lock().unwrap();
-  clash.patch_config(payload, &mut verge, &mut profiles)
+  wrap_err!(clash.patch_config(payload, &mut verge, &mut profiles))
 }
 
 /// get the system proxy
 #[tauri::command]
 pub fn get_sys_proxy() -> Result<SysProxyConfig, String> {
-  match SysProxyConfig::get_sys() {
-    Ok(value) => Ok(value),
-    Err(err) => Err(err.to_string()),
-  }
+  wrap_err!(SysProxyConfig::get_sys())
 }
 
 /// get the current proxy config
 /// which may not the same as system proxy
 #[tauri::command]
 pub fn get_cur_proxy(verge_state: State<'_, VergeState>) -> Result<Option<SysProxyConfig>, String> {
-  match verge_state.0.lock() {
-    Ok(verge) => Ok(verge.cur_sysproxy.clone()),
-    Err(_) => Err("failed to get verge lock".into()),
-  }
+  let verge = verge_state.0.lock().unwrap();
+  Ok(verge.cur_sysproxy.clone())
 }
 
 /// get the verge config
@@ -266,16 +276,16 @@ pub fn patch_verge_config(
   let tun_mode = payload.enable_tun_mode.clone();
 
   let mut verge = verge_state.0.lock().unwrap();
-  verge.patch_config(payload)?;
+  wrap_err!(verge.patch_config(payload))?;
 
   // change tun mode
   if tun_mode.is_some() {
     let mut clash = clash_state.0.lock().unwrap();
     let profiles = profiles_state.0.lock().unwrap();
 
-    clash.tun_mode(tun_mode.unwrap())?;
+    wrap_err!(clash.tun_mode(tun_mode.unwrap()))?;
     clash.update_config();
-    profiles.activate(&clash)?;
+    wrap_err!(profiles.activate(&clash))?;
   }
 
   Ok(())
@@ -290,14 +300,14 @@ pub fn kill_sidecars() {
 /// open app config dir
 #[tauri::command]
 pub fn open_app_dir() -> Result<(), String> {
-  let app_dir = app_home_dir();
+  let app_dir = dirs::app_home_dir();
   open_path_cmd(app_dir, "failed to open app dir")
 }
 
 /// open logs dir
 #[tauri::command]
 pub fn open_logs_dir() -> Result<(), String> {
-  let log_dir = app_home_dir().join("logs");
+  let log_dir = dirs::app_logs_dir();
   open_path_cmd(log_dir, "failed to open logs dir")
 }
 
diff --git a/src-tauri/src/core/clash.rs b/src-tauri/src/core/clash.rs
index 80ea316cbc18e2b6e6724f13b3d53311a00aec8c..d6a1d8c61687e27c9344c60cfb33120afa92bb45 100644
--- a/src-tauri/src/core/clash.rs
+++ b/src-tauri/src/core/clash.rs
@@ -1,5 +1,6 @@
 use super::{Profiles, Verge};
 use crate::utils::{config, dirs};
+use anyhow::{bail, Result};
 use serde::{Deserialize, Serialize};
 use serde_yaml::{Mapping, Value};
 use tauri::api::process::{Command, CommandChild, CommandEvent};
@@ -31,12 +32,9 @@ pub struct Clash {
   pub sidecar: Option<CommandChild>,
 }
 
-static CLASH_CONFIG: &str = "config.yaml";
-
-// todo: be able to change config field
 impl Clash {
   pub fn new() -> Clash {
-    let config = Clash::get_config();
+    let config = Clash::read_config();
     let info = Clash::get_info(&config);
 
     Clash {
@@ -46,6 +44,20 @@ impl Clash {
     }
   }
 
+  /// get clash config
+  fn read_config() -> Mapping {
+    config::read_yaml::<Mapping>(dirs::clash_path())
+  }
+
+  /// save the clash config
+  fn save_config(&self) -> Result<()> {
+    config::save_yaml(
+      dirs::clash_path(),
+      &self.config,
+      Some("# Default Config For Clash Core\n\n"),
+    )
+  }
+
   /// parse the clash's config.yaml
   /// get some information
   fn get_info(clash_config: &Mapping) -> ClashInfo {
@@ -100,7 +112,7 @@ impl Clash {
   }
 
   /// run clash sidecar
-  pub fn run_sidecar(&mut self) -> Result<(), String> {
+  pub fn run_sidecar(&mut self) -> Result<()> {
     let app_dir = dirs::app_home_dir();
     let app_dir = app_dir.as_os_str().to_str().unwrap();
 
@@ -121,25 +133,23 @@ impl Clash {
           });
           Ok(())
         }
-        Err(err) => Err(err.to_string()),
+        Err(err) => bail!(err.to_string()),
       },
-      Err(err) => Err(err.to_string()),
+      Err(err) => bail!(err.to_string()),
     }
   }
 
   /// drop clash sidecar
-  pub fn drop_sidecar(&mut self) -> Result<(), String> {
+  pub fn drop_sidecar(&mut self) -> Result<()> {
     if let Some(sidecar) = self.sidecar.take() {
-      if let Err(err) = sidecar.kill() {
-        return Err(format!("failed to drop clash for \"{}\"", err));
-      }
+      sidecar.kill()?;
     }
     Ok(())
   }
 
   /// restart clash sidecar
   /// should reactivate profile after restart
-  pub fn restart_sidecar(&mut self, profiles: &mut Profiles) -> Result<(), String> {
+  pub fn restart_sidecar(&mut self, profiles: &mut Profiles) -> Result<()> {
     self.update_config();
     self.drop_sidecar()?;
     self.run_sidecar()?;
@@ -148,31 +158,17 @@ impl Clash {
 
   /// update the clash info
   pub fn update_config(&mut self) {
-    self.config = Clash::get_config();
+    self.config = Clash::read_config();
     self.info = Clash::get_info(&self.config);
   }
 
-  /// get clash config
-  fn get_config() -> Mapping {
-    config::read_yaml::<Mapping>(dirs::app_home_dir().join(CLASH_CONFIG))
-  }
-
-  /// save the clash config
-  fn save_config(&self) -> Result<(), String> {
-    config::save_yaml(
-      dirs::app_home_dir().join(CLASH_CONFIG),
-      &self.config,
-      Some("# Default Config For Clash Core\n\n"),
-    )
-  }
-
   /// patch update the clash config
   pub fn patch_config(
     &mut self,
     patch: Mapping,
     verge: &mut Verge,
     profiles: &mut Profiles,
-  ) -> Result<(), String> {
+  ) -> Result<()> {
     for (key, value) in patch.iter() {
       let value = value.clone();
       let key_str = key.as_str().clone().unwrap_or("");
@@ -206,7 +202,7 @@ impl Clash {
 
   /// enable tun mode
   /// only revise the config and restart the
-  pub fn tun_mode(&mut self, enable: bool) -> Result<(), String> {
+  pub fn tun_mode(&mut self, enable: bool) -> Result<()> {
     let tun_key = Value::String("tun".into());
     let tun_val = self.config.get(&tun_key);
 
@@ -256,7 +252,7 @@ impl Default for Clash {
 impl Drop for Clash {
   fn drop(&mut self) {
     if let Err(err) = self.drop_sidecar() {
-      log::error!("{}", err);
+      log::error!("{err}");
     }
   }
 }
diff --git a/src-tauri/src/core/profiles.rs b/src-tauri/src/core/profiles.rs
index 551deeb98af30f3f8468e93d4bd410b85813e5ce..4fdae29810840a7d26bc4de6158f27f52cf45ee4 100644
--- a/src-tauri/src/core/profiles.rs
+++ b/src-tauri/src/core/profiles.rs
@@ -1,10 +1,10 @@
 use super::{Clash, ClashInfo};
 use crate::utils::{config, dirs, tmpl};
+use anyhow::{bail, Result};
 use reqwest::header::HeaderMap;
 use serde::{Deserialize, Serialize};
 use serde_yaml::{Mapping, Value};
 use std::collections::HashMap;
-use std::env::temp_dir;
 use std::fs::{remove_file, File};
 use std::io::Write;
 use std::path::PathBuf;
@@ -24,18 +24,30 @@ pub struct Profiles {
 pub struct ProfileItem {
   /// profile name
   pub name: Option<String>,
+
   /// profile description
+  #[serde(skip_serializing_if = "Option::is_none")]
   pub desc: Option<String>,
+
   /// profile file
   pub file: Option<String>,
+
   /// current mode
+  #[serde(skip_serializing_if = "Option::is_none")]
   pub mode: Option<String>,
+
   /// source url
+  #[serde(skip_serializing_if = "Option::is_none")]
   pub url: Option<String>,
+
   /// selected infomation
+  #[serde(skip_serializing_if = "Option::is_none")]
   pub selected: Option<Vec<ProfileSelected>>,
+
   /// user info
+  #[serde(skip_serializing_if = "Option::is_none")]
   pub extra: Option<ProfileExtra>,
+
   /// updated time
   pub updated: Option<usize>,
 }
@@ -63,29 +75,26 @@ pub struct ProfileResponse {
   pub extra: Option<ProfileExtra>,
 }
 
-static PROFILE_YAML: &str = "profiles.yaml";
-static PROFILE_TEMP: &str = "clash-verge-runtime.yaml";
-
 impl Profiles {
   /// read the config from the file
   pub fn read_file() -> Self {
-    config::read_yaml::<Profiles>(dirs::app_home_dir().join(PROFILE_YAML))
+    config::read_yaml::<Profiles>(dirs::profiles_path())
   }
 
   /// save the config to the file
-  pub fn save_file(&self) -> Result<(), String> {
+  pub fn save_file(&self) -> Result<()> {
     config::save_yaml(
-      dirs::app_home_dir().join(PROFILE_YAML),
+      dirs::profiles_path(),
       self,
       Some("# Profiles Config for Clash Verge\n\n"),
     )
   }
 
   /// sync the config between file and memory
-  pub fn sync_file(&mut self) -> Result<(), String> {
-    let data = config::read_yaml::<Self>(dirs::app_home_dir().join(PROFILE_YAML));
+  pub fn sync_file(&mut self) -> Result<()> {
+    let data = config::read_yaml::<Self>(dirs::profiles_path());
     if data.current.is_none() {
-      Err("failed to read profiles.yaml".into())
+      bail!("failed to read profiles.yaml")
     } else {
       self.current = data.current;
       self.items = data.items;
@@ -95,9 +104,9 @@ impl Profiles {
 
   /// import the new profile from the url
   /// and update the config file
-  pub fn import_from_url(&mut self, url: String, result: ProfileResponse) -> Result<(), String> {
+  pub fn import_from_url(&mut self, url: String, result: ProfileResponse) -> Result<()> {
     // save the profile file
-    let path = dirs::app_home_dir().join("profiles").join(&result.file);
+    let path = dirs::app_profiles_dir().join(&result.file);
     let file_data = result.data.as_bytes();
     File::create(path).unwrap().write(file_data).unwrap();
 
@@ -130,11 +139,11 @@ impl Profiles {
   }
 
   /// set the current and save to file
-  pub fn put_current(&mut self, index: usize) -> Result<(), String> {
+  pub fn put_current(&mut self, index: usize) -> Result<()> {
     let items = self.items.take().unwrap_or(vec![]);
 
     if index >= items.len() {
-      return Err("the index out of bound".into());
+      bail!("the index out of bound");
     }
 
     self.items = Some(items);
@@ -144,7 +153,7 @@ impl Profiles {
 
   /// append new item
   /// return the new item's index
-  pub fn append_item(&mut self, name: String, desc: String) -> Result<(usize, PathBuf), String> {
+  pub fn append_item(&mut self, name: String, desc: String) -> Result<(usize, PathBuf)> {
     let mut items = self.items.take().unwrap_or(vec![]);
 
     // create a new profile file
@@ -153,7 +162,7 @@ impl Profiles {
       .unwrap()
       .as_secs();
     let file = format!("{}.yaml", now);
-    let path = dirs::app_home_dir().join("profiles").join(&file);
+    let path = dirs::app_profiles_dir().join(&file);
 
     match File::create(&path).unwrap().write(tmpl::ITEM_CONFIG) {
       Ok(_) => {
@@ -172,14 +181,14 @@ impl Profiles {
         self.items = Some(items);
         Ok((index, path))
       }
-      Err(_) => Err("failed to create file".into()),
+      Err(_) => bail!("failed to create file"),
     }
   }
 
   /// update the target profile
   /// and save to config file
   /// only support the url item
-  pub fn update_item(&mut self, index: usize, result: ProfileResponse) -> Result<(), String> {
+  pub fn update_item(&mut self, index: usize, result: ProfileResponse) -> Result<()> {
     let mut items = self.items.take().unwrap_or(vec![]);
 
     let now = SystemTime::now()
@@ -189,7 +198,7 @@ impl Profiles {
 
     // update file
     let file_path = &items[index].file.as_ref().unwrap();
-    let file_path = dirs::app_home_dir().join("profiles").join(file_path);
+    let file_path = dirs::app_profiles_dir().join(file_path);
     let file_data = result.data.as_bytes();
     File::create(file_path).unwrap().write(file_data).unwrap();
 
@@ -202,10 +211,10 @@ impl Profiles {
   }
 
   /// patch item
-  pub fn patch_item(&mut self, index: usize, profile: ProfileItem) -> Result<(), String> {
+  pub fn patch_item(&mut self, index: usize, profile: ProfileItem) -> Result<()> {
     let mut items = self.items.take().unwrap_or(vec![]);
     if index >= items.len() {
-      return Err("index out of bound".into());
+      bail!("index out of range");
     }
 
     if profile.name.is_some() {
@@ -232,19 +241,19 @@ impl Profiles {
   }
 
   /// delete the item
-  pub fn delete_item(&mut self, index: usize) -> Result<bool, String> {
+  pub fn delete_item(&mut self, index: usize) -> Result<bool> {
     let mut current = self.current.clone().unwrap_or(0);
     let mut items = self.items.clone().unwrap_or(vec![]);
 
     if index >= items.len() {
-      return Err("index out of bound".into());
+      bail!("index out of range");
     }
 
     let mut rm_item = items.remove(index);
 
     // delete the file
     if let Some(file) = rm_item.file.take() {
-      let file_path = dirs::app_home_dir().join("profiles").join(file);
+      let file_path = dirs::app_profiles_dir().join(file);
 
       if file_path.exists() {
         if let Err(err) = remove_file(file_path) {
@@ -272,33 +281,34 @@ impl Profiles {
   }
 
   /// activate current profile
-  pub fn activate(&self, clash: &Clash) -> Result<(), String> {
+  pub fn activate(&self, clash: &Clash) -> Result<()> {
     let current = self.current.unwrap_or(0);
     match self.items.clone() {
       Some(items) => {
         if current >= items.len() {
-          return Err("the index out of bound".into());
+          bail!("the index out of bound");
         }
 
         let profile = items[current].clone();
         let clash_config = clash.config.clone();
         let clash_info = clash.info.clone();
+
         tauri::async_runtime::spawn(async move {
           let mut count = 5; // retry times
-          let mut err = String::from("");
+          let mut err = None;
           while count > 0 {
             match activate_profile(&profile, &clash_config, &clash_info).await {
               Ok(_) => return,
-              Err(e) => err = e,
+              Err(e) => err = Some(e),
             }
             count -= 1;
           }
-          log::error!("failed to activate for `{}`", err);
+          log::error!("failed to activate for `{}`", err.unwrap());
         });
 
         Ok(())
       }
-      None => Err("empty profiles".into()),
+      None => bail!("empty profiles"),
     }
   }
 }
@@ -308,23 +318,23 @@ pub async fn activate_profile(
   profile_item: &ProfileItem,
   clash_config: &Mapping,
   clash_info: &ClashInfo,
-) -> Result<(), String> {
+) -> Result<()> {
   // temp profile's path
-  let temp_path = temp_dir().join(PROFILE_TEMP);
+  let temp_path = dirs::profiles_temp_path();
 
   // generate temp profile
   {
     let file_name = match profile_item.file.clone() {
       Some(file_name) => file_name,
-      None => return Err("profile item should have `file` field".into()),
+      None => bail!("profile item should have `file` field"),
     };
 
-    let file_path = dirs::app_home_dir().join("profiles").join(file_name);
+    let file_path = dirs::app_profiles_dir().join(file_name);
     if !file_path.exists() {
-      return Err(format!(
+      bail!(
         "profile `{}` not exists",
         file_path.as_os_str().to_str().unwrap()
-      ));
+      );
     }
 
     // begin to generate the new profile config
@@ -372,12 +382,13 @@ pub async fn activate_profile(
   let mut data = HashMap::new();
   data.insert("path", temp_path.as_os_str().to_str().unwrap());
 
-  let client = match reqwest::ClientBuilder::new().no_proxy().build() {
-    Ok(c) => c,
-    Err(_) => return Err("failed to create http::put".into()),
-  };
-  match client.put(server).headers(headers).json(&data).send().await {
-    Ok(_) => Ok(()),
-    Err(err) => Err(format!("request failed `{}`", err.to_string())),
-  }
+  let client = reqwest::ClientBuilder::new().no_proxy().build()?;
+
+  client
+    .put(server)
+    .headers(headers)
+    .json(&data)
+    .send()
+    .await?;
+  Ok(())
 }
diff --git a/src-tauri/src/core/verge.rs b/src-tauri/src/core/verge.rs
index 349480ab1327360d6a68ad3a0dbe32f43b763de4..67f85c42a2946fecdd2610adfc8de7c50481991c 100644
--- a/src-tauri/src/core/verge.rs
+++ b/src-tauri/src/core/verge.rs
@@ -2,6 +2,7 @@ use crate::{
   core::Clash,
   utils::{config, dirs, sysopt::SysProxyConfig},
 };
+use anyhow::{bail, Result};
 use auto_launch::{AutoLaunch, AutoLaunchBuilder};
 use serde::{Deserialize, Serialize};
 use std::sync::Arc;
@@ -39,17 +40,15 @@ pub struct VergeConfig {
   pub proxy_guard_duration: Option<u64>,
 }
 
-static VERGE_CONFIG: &str = "verge.yaml";
-
 impl VergeConfig {
   pub fn new() -> Self {
-    config::read_yaml::<VergeConfig>(dirs::app_home_dir().join(VERGE_CONFIG))
+    config::read_yaml::<VergeConfig>(dirs::verge_path())
   }
 
   /// Save Verge App Config
-  pub fn save_file(&self) -> Result<(), String> {
+  pub fn save_file(&self) -> Result<()> {
     config::save_yaml(
-      dirs::app_home_dir().join(VERGE_CONFIG),
+      dirs::verge_path(),
       self,
       Some("# The Config for Clash Verge App\n\n"),
     )
@@ -140,30 +139,28 @@ impl Verge {
   }
 
   /// sync the startup when run the app
-  pub fn sync_launch(&self) -> Result<(), String> {
+  pub fn sync_launch(&self) -> Result<()> {
     let enable = self.config.enable_auto_launch.clone().unwrap_or(false);
     if !enable {
       return Ok(());
     }
 
     if self.auto_launch.is_none() {
-      return Err("should init the auto launch first".into());
+      bail!("should init the auto launch first");
     }
 
     let auto_launch = self.auto_launch.clone().unwrap();
 
     let is_enabled = auto_launch.is_enabled().unwrap_or(false);
     if !is_enabled {
-      if let Err(_) = auto_launch.enable() {
-        return Err("failed to enable auto-launch".into());
-      }
+      auto_launch.enable()?;
     }
 
     Ok(())
   }
 
   /// update the startup
-  fn update_launch(&mut self, enable: bool) -> Result<(), String> {
+  fn update_launch(&mut self, enable: bool) -> Result<()> {
     let conf_enable = self.config.enable_auto_launch.clone().unwrap_or(false);
 
     if enable == conf_enable {
@@ -172,24 +169,18 @@ impl Verge {
 
     let auto_launch = self.auto_launch.clone().unwrap();
 
-    let result = match enable {
-      true => auto_launch.enable(),
-      false => auto_launch.disable(),
+    match enable {
+      true => auto_launch.enable()?,
+      false => auto_launch.disable()?,
     };
 
-    match result {
-      Ok(_) => Ok(()),
-      Err(err) => {
-        log::error!("{err}");
-        Err("failed to set system startup info".into())
-      }
-    }
+    Ok(())
   }
 
   /// patch verge config
   /// There should be only one update at a time here
   /// so call the save_file at the end is savely
-  pub fn patch_config(&mut self, patch: VergeConfig) -> Result<(), String> {
+  pub fn patch_config(&mut self, patch: VergeConfig) -> Result<()> {
     // only change it
     if patch.theme_mode.is_some() {
       self.config.theme_mode = patch.theme_mode;
@@ -218,7 +209,7 @@ impl Verge {
           self.cur_sysproxy = Some(sysproxy);
 
           log::error!("failed to set system proxy");
-          return Err("failed to set system proxy".into());
+          bail!("failed to set system proxy");
         }
         self.cur_sysproxy = Some(sysproxy);
       }
@@ -237,7 +228,7 @@ impl Verge {
             self.cur_sysproxy = Some(sysproxy);
 
             log::error!("failed to set system proxy");
-            return Err("failed to set system proxy".into());
+            bail!("failed to set system proxy");
           }
         }
 
diff --git a/src-tauri/src/utils/config.rs b/src-tauri/src/utils/config.rs
index 04a33cc78010a707d3738e4a4f1f8673d56b400b..7b7a357855c1e95856c71ec496ddcda79e60eff8 100644
--- a/src-tauri/src/utils/config.rs
+++ b/src-tauri/src/utils/config.rs
@@ -1,3 +1,4 @@
+use anyhow::{Context, Result};
 use serde::{de::DeserializeOwned, Serialize};
 use std::{fs, path::PathBuf};
 
@@ -7,26 +8,16 @@ pub fn read_yaml<T: DeserializeOwned + Default>(path: PathBuf) -> T {
   serde_yaml::from_str::<T>(&yaml_str).unwrap_or(T::default())
 }
 
-/// - save the data to the file
-/// - can set `prefix` string to add some comments
-pub fn save_yaml<T: Serialize>(
-  path: PathBuf,
-  data: &T,
-  prefix: Option<&str>,
-) -> Result<(), String> {
-  match serde_yaml::to_string(data) {
-    Ok(data_str) => {
-      let yaml_str = match prefix {
-        Some(prefix) => format!("{}{}", prefix, data_str),
-        None => data_str,
-      };
+/// save the data to the file
+/// can set `prefix` string to add some comments
+pub fn save_yaml<T: Serialize>(path: PathBuf, data: &T, prefix: Option<&str>) -> Result<()> {
+  let data_str = serde_yaml::to_string(data)?;
 
-      let path_str = path.as_os_str().to_string_lossy().to_string();
-      match fs::write(path, yaml_str.as_bytes()) {
-        Ok(_) => Ok(()),
-        Err(_) => Err(format!("can not save file `{}`", path_str)),
-      }
-    }
-    Err(_) => Err("can not convert the data to yaml".into()),
-  }
+  let yaml_str = match prefix {
+    Some(prefix) => format!("{prefix}{data_str}"),
+    None => data_str,
+  };
+
+  let path_str = path.as_os_str().to_string_lossy().to_string();
+  fs::write(path, yaml_str.as_bytes()).context(format!("failed to save file \"{path_str}\""))
 }
diff --git a/src-tauri/src/utils/dirs.rs b/src-tauri/src/utils/dirs.rs
index 8b159b41f93b925716b38b0849ce7f1af4a29170..985939c1eb53196abc8202f19df2b61c484e571c 100644
--- a/src-tauri/src/utils/dirs.rs
+++ b/src-tauri/src/utils/dirs.rs
@@ -1,3 +1,4 @@
+use std::env::temp_dir;
 use std::path::{Path, PathBuf};
 use tauri::{
   api::path::{home_dir, resource_dir},
@@ -18,3 +19,34 @@ pub fn app_resources_dir(package_info: &PackageInfo) -> PathBuf {
     .unwrap()
     .join("resources")
 }
+
+/// profiles dir
+pub fn app_profiles_dir() -> PathBuf {
+  app_home_dir().join("profiles")
+}
+
+/// logs dir
+pub fn app_logs_dir() -> PathBuf {
+  app_home_dir().join("logs")
+}
+
+static CLASH_CONFIG: &str = "config.yaml";
+static VERGE_CONFIG: &str = "verge.yaml";
+static PROFILE_YAML: &str = "profiles.yaml";
+static PROFILE_TEMP: &str = "clash-verge-runtime.yaml";
+
+pub fn clash_path() -> PathBuf {
+  app_home_dir().join(CLASH_CONFIG)
+}
+
+pub fn verge_path() -> PathBuf {
+  app_home_dir().join(VERGE_CONFIG)
+}
+
+pub fn profiles_path() -> PathBuf {
+  app_home_dir().join(PROFILE_YAML)
+}
+
+pub fn profiles_temp_path() -> PathBuf {
+  temp_dir().join(PROFILE_TEMP)
+}
diff --git a/src-tauri/src/utils/init.rs b/src-tauri/src/utils/init.rs
index ce5ed6c95b8e3c3779e839f19214d09671fd3a3d..0fc16fd766e024ccb5d52e3a7796c310a5c90b94 100644
--- a/src-tauri/src/utils/init.rs
+++ b/src-tauri/src/utils/init.rs
@@ -1,5 +1,3 @@
-extern crate serde_yaml;
-
 use crate::utils::{dirs, tmpl};
 use chrono::Local;
 use log::LevelFilter;
@@ -7,7 +5,7 @@ use log4rs::append::console::ConsoleAppender;
 use log4rs::append::file::FileAppender;
 use log4rs::config::{Appender, Config, Root};
 use log4rs::encode::pattern::PatternEncoder;
-use std::fs::{self, File};
+use std::fs;
 use std::io::Write;
 use std::path::PathBuf;
 use tauri::PackageInfo;
@@ -48,13 +46,13 @@ fn init_config(app_dir: &PathBuf) -> std::io::Result<()> {
   let profile_path = app_dir.join("profiles.yaml");
 
   if !clash_path.exists() {
-    File::create(clash_path)?.write(tmpl::CLASH_CONFIG)?;
+    fs::File::create(clash_path)?.write(tmpl::CLASH_CONFIG)?;
   }
   if !verge_path.exists() {
-    File::create(verge_path)?.write(tmpl::VERGE_CONFIG)?;
+    fs::File::create(verge_path)?.write(tmpl::VERGE_CONFIG)?;
   }
   if !profile_path.exists() {
-    File::create(profile_path)?.write(tmpl::PROFILES_CONFIG)?;
+    fs::File::create(profile_path)?.write(tmpl::PROFILES_CONFIG)?;
   }
   Ok(())
 }
@@ -63,8 +61,8 @@ fn init_config(app_dir: &PathBuf) -> std::io::Result<()> {
 pub fn init_app(package_info: &PackageInfo) {
   // create app dir
   let app_dir = dirs::app_home_dir();
-  let log_dir = app_dir.join("logs");
-  let profiles_dir = app_dir.join("profiles");
+  let log_dir = dirs::app_logs_dir();
+  let profiles_dir = dirs::app_profiles_dir();
 
   let res_dir = dirs::app_resources_dir(package_info);