diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 812bccf32b1928ca3c2778eaffbfc29dedf884f7..7bed2ffd3d4c532ff5c44d42530d02fbc7300ca5 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -14,6 +14,17 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
 
+[[package]]
+name = "ahash"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+dependencies = [
+ "getrandom 0.2.6",
+ "once_cell",
+ "version_check",
+]
+
 [[package]]
 name = "aho-corasick"
 version = "0.7.18"
@@ -59,6 +70,115 @@ version = "0.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dabe5a181f83789739c194cbe5a897dde195078fac08568d09221fd6137a7ba8"
 
+[[package]]
+name = "async-channel"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319"
+dependencies = [
+ "concurrent-queue",
+ "event-listener",
+ "futures-core",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "once_cell",
+ "slab",
+]
+
+[[package]]
+name = "async-fs"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b3ca4f8ff117c37c278a2f7415ce9be55560b846b5bc4412aaa5d29c1c3dae2"
+dependencies = [
+ "async-lock",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-io"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b"
+dependencies = [
+ "concurrent-queue",
+ "futures-lite",
+ "libc",
+ "log",
+ "once_cell",
+ "parking",
+ "polling",
+ "slab",
+ "socket2",
+ "waker-fn",
+ "winapi",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6"
+dependencies = [
+ "event-listener",
+]
+
+[[package]]
+name = "async-net"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5373304df79b9b4395068fb080369ec7178608827306ce4d081cba51cac551df"
+dependencies = [
+ "async-io",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-process"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6"
+dependencies = [
+ "async-io",
+ "blocking",
+ "cfg-if",
+ "event-listener",
+ "futures-lite",
+ "libc",
+ "once_cell",
+ "signal-hook",
+ "winapi",
+]
+
+[[package]]
+name = "async-task"
+version = "4.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9"
+
+[[package]]
+name = "async-trait"
+version = "0.1.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "atk"
 version = "0.15.1"
@@ -83,6 +203,12 @@ dependencies = [
  "system-deps 6.0.2",
 ]
 
+[[package]]
+name = "atomic-waker"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
+
 [[package]]
 name = "attohttpc"
 version = "0.18.0"
@@ -162,6 +288,20 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "blocking"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc"
+dependencies = [
+ "async-channel",
+ "async-task",
+ "atomic-waker",
+ "fastrand",
+ "futures-lite",
+ "once_cell",
+]
+
 [[package]]
 name = "brotli"
 version = "3.3.3"
@@ -220,6 +360,12 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
 
+[[package]]
+name = "cache-padded"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
+
 [[package]]
 name = "cairo-rs"
 version = "0.15.10"
@@ -336,6 +482,7 @@ dependencies = [
  "anyhow",
  "auto-launch",
  "chrono",
+ "delay_timer",
  "dirs",
  "dunce",
  "log",
@@ -398,6 +545,25 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "concat-idents"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b6f90860248d75014b7b103db8fee4f291c07bfb41306cdf77a0a5ab7a10d2f"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
+dependencies = [
+ "cache-padded",
+]
+
 [[package]]
 name = "convert_case"
 version = "0.4.0"
@@ -463,6 +629,17 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "cron_clock"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a8699d8ed16e3db689f8ae04d8dc3c6666a4ba7e724e5a157884b7cc385d16b"
+dependencies = [
+ "chrono",
+ "nom",
+ "once_cell",
+]
+
 [[package]]
 name = "crossbeam-channel"
 version = "0.5.4"
@@ -606,6 +783,16 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "dashmap"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
+dependencies = [
+ "cfg-if",
+ "num_cpus",
+]
+
 [[package]]
 name = "deflate"
 version = "0.7.20"
@@ -625,6 +812,31 @@ dependencies = [
  "adler32",
 ]
 
+[[package]]
+name = "delay_timer"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68db8078186fbb91582bc7ba4f7b90e52cc4618a585748dd6a9fb6f4a25ea3c4"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "autocfg",
+ "concat-idents",
+ "cron_clock",
+ "dashmap",
+ "event-listener",
+ "futures",
+ "log",
+ "lru",
+ "once_cell",
+ "rs-snowflake",
+ "rustc_version 0.2.3",
+ "smol",
+ "thiserror",
+ "tokio",
+ "tracing",
+]
+
 [[package]]
 name = "derivative"
 version = "2.2.0"
@@ -757,6 +969,12 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "event-listener"
+version = "2.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
+
 [[package]]
 name = "fastrand"
 version = "1.7.0"
@@ -1289,6 +1507,9 @@ name = "hashbrown"
 version = "0.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+dependencies = [
+ "ahash",
+]
 
 [[package]]
 name = "headers"
@@ -1728,6 +1949,15 @@ dependencies = [
  "tracing-subscriber",
 ]
 
+[[package]]
+name = "lru"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ea2d928b485416e8908cff2d97d621db22b27f7b3b6729e438bcf42c671ba91"
+dependencies = [
+ "hashbrown",
+]
+
 [[package]]
 name = "mac"
 version = "0.1.1"
@@ -1803,6 +2033,12 @@ dependencies = [
  "unicase",
 ]
 
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
 [[package]]
 name = "minisign-verify"
 version = "0.2.0"
@@ -1961,6 +2197,16 @@ version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
 
+[[package]]
+name = "nom"
+version = "7.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
 [[package]]
 name = "ntapi"
 version = "0.3.7"
@@ -2404,6 +2650,19 @@ dependencies = [
  "miniz_oxide 0.5.1",
 ]
 
+[[package]]
+name = "polling"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "log",
+ "wepoll-ffi",
+ "winapi",
+]
+
 [[package]]
 name = "port_scanner"
 version = "0.1.5"
@@ -2706,6 +2965,21 @@ dependencies = [
  "windows 0.33.0",
 ]
 
+[[package]]
+name = "rs-snowflake"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148be9baabb59959aeaf4b27597bce9f071511a6a74a4a20a2c5681722a21e"
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+dependencies = [
+ "semver 0.9.0",
+]
+
 [[package]]
 name = "rustc_version"
 version = "0.3.3"
@@ -2816,13 +3090,22 @@ dependencies = [
  "thin-slice",
 ]
 
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+dependencies = [
+ "semver-parser 0.7.0",
+]
+
 [[package]]
 name = "semver"
 version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
 dependencies = [
- "semver-parser",
+ "semver-parser 0.10.2",
 ]
 
 [[package]]
@@ -2831,6 +3114,12 @@ version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4"
 
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+
 [[package]]
 name = "semver-parser"
 version = "0.10.2"
@@ -3025,6 +3314,16 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "signal-hook"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
 [[package]]
 name = "signal-hook-registry"
 version = "1.4.0"
@@ -3052,6 +3351,24 @@ version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
 
+[[package]]
+name = "smol"
+version = "1.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85cf3b5351f3e783c1d79ab5fc604eeed8b8ae9abd36b166e8b87a089efd85e4"
+dependencies = [
+ "async-channel",
+ "async-executor",
+ "async-fs",
+ "async-io",
+ "async-lock",
+ "async-net",
+ "async-process",
+ "blocking",
+ "futures-lite",
+ "once_cell",
+]
+
 [[package]]
 name = "socket2"
 version = "0.4.4"
@@ -4090,6 +4407,15 @@ dependencies = [
  "windows-bindgen",
 ]
 
+[[package]]
+name = "wepoll-ffi"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
+dependencies = [
+ "cc",
+]
+
 [[package]]
 name = "which"
 version = "4.2.5"
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 6467b00491b91b95b4a0913056d9135fe189dabe..0afe654b70c742ded82d60e2994968db34a40db8 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -21,6 +21,7 @@ nanoid = "0.4.0"
 chrono = "0.4.19"
 serde_json = "1.0"
 serde_yaml = "0.8"
+delay_timer = "0.11.1"
 serde = { version = "1.0", features = ["derive"] }
 tauri = { version = "1.0.0-rc.6", features = ["process-all", "shell-all", "system-tray", "updater", "window-all"] }
 window-shadows = { git = "https://github.com/tauri-apps/window-shadows" }
@@ -39,8 +40,8 @@ port_scanner = "0.1.5"
 winreg = { version = "0.10", features = ["transactions"] }
 
 [features]
-default = [ "custom-protocol" ]
-custom-protocol = [ "tauri/custom-protocol" ]
+default = ["custom-protocol"]
+custom-protocol = ["tauri/custom-protocol"]
 verge-dev = []
 debug-yml = []
 
diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs
index bafc1188cc4cf58fbf5024b0f0ef332c3c6fee9f..d2d146fd3e757443e04303e674883f517dcd50e7 100644
--- a/src-tauri/src/cmds.rs
+++ b/src-tauri/src/cmds.rs
@@ -1,25 +1,26 @@
 use crate::{
-  core::{ClashInfo, PrfItem, PrfOption, Profiles, VergeConfig},
-  states::{ClashState, ProfilesState, VergeState},
+  core::{ClashInfo, Core, PrfItem, PrfOption, Profiles, VergeConfig},
   utils::{dirs, sysopt::SysProxyConfig},
 };
-use crate::{ret_err, wrap_err};
+use crate::{log_if_err, ret_err, wrap_err};
 use anyhow::Result;
 use serde_yaml::Mapping;
 use std::process::Command;
 use tauri::{api, Manager, State};
 
+type CmdResult<T = ()> = Result<T, String>;
+
 /// get all profiles from `profiles.yaml`
 #[tauri::command]
-pub fn get_profiles<'a>(profiles_state: State<'_, ProfilesState>) -> Result<Profiles, String> {
-  let profiles = profiles_state.0.lock().unwrap();
+pub fn get_profiles(core: State<'_, Core>) -> CmdResult<Profiles> {
+  let profiles = core.profiles.lock().unwrap();
   Ok(profiles.clone())
 }
 
 /// synchronize data irregularly
 #[tauri::command]
-pub fn sync_profiles(profiles_state: State<'_, ProfilesState>) -> Result<(), String> {
-  let mut profiles = profiles_state.0.lock().unwrap();
+pub fn sync_profiles(core: State<'_, Core>) -> CmdResult {
+  let mut profiles = core.profiles.lock().unwrap();
   wrap_err!(profiles.sync_file())
 }
 
@@ -29,11 +30,11 @@ pub fn sync_profiles(profiles_state: State<'_, ProfilesState>) -> Result<(), Str
 pub async fn import_profile(
   url: String,
   option: Option<PrfOption>,
-  profiles_state: State<'_, ProfilesState>,
-) -> Result<(), String> {
+  core: State<'_, Core>,
+) -> CmdResult {
   let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?;
 
-  let mut profiles = profiles_state.0.lock().unwrap();
+  let mut profiles = core.profiles.lock().unwrap();
   wrap_err!(profiles.append_item(item))
 }
 
@@ -44,11 +45,11 @@ pub async fn import_profile(
 pub async fn create_profile(
   item: PrfItem, // partial
   file_data: Option<String>,
-  profiles_state: State<'_, ProfilesState>,
-) -> Result<(), String> {
+  core: State<'_, Core>,
+) -> CmdResult {
   let item = wrap_err!(PrfItem::from(item, file_data).await)?;
-  let mut profiles = profiles_state.0.lock().unwrap();
 
+  let mut profiles = core.profiles.lock().unwrap();
   wrap_err!(profiles.append_item(item))
 }
 
@@ -57,12 +58,11 @@ pub async fn create_profile(
 pub async fn update_profile(
   index: String,
   option: Option<PrfOption>,
-  clash_state: State<'_, ClashState>,
-  profiles_state: State<'_, ProfilesState>,
-) -> Result<(), String> {
+  core: State<'_, Core>,
+) -> CmdResult {
   let (url, opt) = {
     // must release the lock here
-    let profiles = profiles_state.0.lock().unwrap();
+    let profiles = core.profiles.lock().unwrap();
     let item = wrap_err!(profiles.get_item(&index))?;
 
     // check the profile type
@@ -82,13 +82,12 @@ pub async fn update_profile(
   let fetch_opt = PrfOption::merge(opt, option);
   let item = wrap_err!(PrfItem::from_url(&url, None, None, fetch_opt).await)?;
 
-  let mut profiles = profiles_state.0.lock().unwrap();
+  let mut profiles = core.profiles.lock().unwrap();
   wrap_err!(profiles.update_item(index.clone(), item))?;
 
   // reactivate the profile
   if Some(index) == profiles.get_current() {
-    let clash = clash_state.0.lock().unwrap();
-    wrap_err!(clash.activate_enhanced(&profiles, false, false))?;
+    log_if_err!(core.activate_enhanced(false, false));
   }
 
   Ok(())
@@ -96,79 +95,55 @@ pub async fn update_profile(
 
 /// change the current profile
 #[tauri::command]
-pub fn select_profile(
-  index: String,
-  clash_state: State<'_, ClashState>,
-  profiles_state: State<'_, ProfilesState>,
-) -> Result<(), String> {
-  let mut profiles = profiles_state.0.lock().unwrap();
-  wrap_err!(profiles.put_current(index))?;
+pub fn select_profile(index: String, core: State<'_, Core>) -> CmdResult {
+  {
+    let mut profiles = core.profiles.lock().unwrap();
+    wrap_err!(profiles.put_current(index))?;
+  }
+
+  log_if_err!(core.activate_enhanced(false, false));
 
-  let clash = clash_state.0.lock().unwrap();
-  wrap_err!(clash.activate_enhanced(&profiles, false, false))
+  Ok(())
 }
 
 /// change the profile chain
 #[tauri::command]
-pub fn change_profile_chain(
-  chain: Option<Vec<String>>,
-  app_handle: tauri::AppHandle,
-  clash_state: State<'_, ClashState>,
-  profiles_state: State<'_, ProfilesState>,
-) -> Result<(), String> {
-  let mut clash = clash_state.0.lock().unwrap();
-  let mut profiles = profiles_state.0.lock().unwrap();
+pub fn change_profile_chain(chain: Option<Vec<String>>, core: State<'_, Core>) -> CmdResult {
+  {
+    let mut profiles = core.profiles.lock().unwrap();
+    profiles.put_chain(chain);
+  }
 
-  profiles.put_chain(chain);
-  clash.set_window(app_handle.get_window("main"));
+  log_if_err!(core.activate_enhanced(false, false));
 
-  wrap_err!(clash.activate_enhanced(&profiles, false, false))
+  Ok(())
 }
 
 /// change the profile valid fields
 #[tauri::command]
-pub fn change_profile_valid(
-  valid: Option<Vec<String>>,
-  app_handle: tauri::AppHandle,
-  clash_state: State<'_, ClashState>,
-  profiles_state: State<'_, ProfilesState>,
-) -> Result<(), String> {
-  let mut clash = clash_state.0.lock().unwrap();
-  let mut profiles = profiles_state.0.lock().unwrap();
-
+pub fn change_profile_valid(valid: Option<Vec<String>>, core: State<Core>) -> CmdResult {
+  let mut profiles = core.profiles.lock().unwrap();
   profiles.put_valid(valid);
-  clash.set_window(app_handle.get_window("main"));
 
-  wrap_err!(clash.activate_enhanced(&profiles, false, false))
+  log_if_err!(core.activate_enhanced(false, false));
+
+  Ok(())
 }
 
 /// manually exec enhanced profile
 #[tauri::command]
-pub fn enhance_profiles(
-  app_handle: tauri::AppHandle,
-  clash_state: State<'_, ClashState>,
-  profiles_state: State<'_, ProfilesState>,
-) -> Result<(), String> {
-  let mut clash = clash_state.0.lock().unwrap();
-  let profiles = profiles_state.0.lock().unwrap();
-
-  clash.set_window(app_handle.get_window("main"));
-
-  wrap_err!(clash.activate_enhanced(&profiles, false, false))
+pub fn enhance_profiles(core: State<'_, Core>) -> CmdResult {
+  log_if_err!(core.activate_enhanced(false, false));
+  Ok(())
 }
 
 /// delete profile item
 #[tauri::command]
-pub fn delete_profile(
-  index: String,
-  clash_state: State<'_, ClashState>,
-  profiles_state: State<'_, ProfilesState>,
-) -> Result<(), String> {
-  let mut profiles = profiles_state.0.lock().unwrap();
+pub fn delete_profile(index: String, core: State<'_, Core>) -> CmdResult {
+  let mut profiles = core.profiles.lock().unwrap();
 
   if wrap_err!(profiles.delete_item(index))? {
-    let clash = clash_state.0.lock().unwrap();
-    wrap_err!(clash.activate_enhanced(&profiles, false, false))?;
+    log_if_err!(core.activate_enhanced(false, false));
   }
 
   Ok(())
@@ -176,19 +151,16 @@ pub fn delete_profile(
 
 /// patch the profile config
 #[tauri::command]
-pub fn patch_profile(
-  index: String,
-  profile: PrfItem,
-  profiles_state: State<'_, ProfilesState>,
-) -> Result<(), String> {
-  let mut profiles = profiles_state.0.lock().unwrap();
+pub fn patch_profile(index: String, profile: PrfItem, core: State<'_, Core>) -> CmdResult {
+  let mut profiles = core.profiles.lock().unwrap();
+
   wrap_err!(profiles.patch_item(index, profile))
 }
 
 /// run vscode command to edit the profile
 #[tauri::command]
-pub fn view_profile(index: String, profiles_state: State<'_, ProfilesState>) -> Result<(), String> {
-  let profiles = profiles_state.0.lock().unwrap();
+pub fn view_profile(index: String, core: State<'_, Core>) -> CmdResult {
+  let mut profiles = core.profiles.lock().unwrap();
   let item = wrap_err!(profiles.get_item(&index))?;
 
   let file = item.file.clone();
@@ -231,11 +203,9 @@ pub fn view_profile(index: String, profiles_state: State<'_, ProfilesState>) ->
 
 /// read the profile item file data
 #[tauri::command]
-pub fn read_profile_file(
-  index: String,
-  profiles_state: State<'_, ProfilesState>,
-) -> Result<String, String> {
-  let profiles = profiles_state.0.lock().unwrap();
+pub fn read_profile_file(index: String, core: State<'_, Core>) -> CmdResult<String> {
+  let mut profiles = core.profiles.lock().unwrap();
+
   let item = wrap_err!(profiles.get_item(&index))?;
   let data = wrap_err!(item.read_file())?;
 
@@ -247,34 +217,36 @@ pub fn read_profile_file(
 pub fn save_profile_file(
   index: String,
   file_data: Option<String>,
-  profiles_state: State<'_, ProfilesState>,
-) -> Result<(), String> {
+  core: State<'_, Core>,
+) -> CmdResult {
   if file_data.is_none() {
     return Ok(());
   }
 
-  let profiles = profiles_state.0.lock().unwrap();
+  let mut profiles = core.profiles.lock().unwrap();
   let item = wrap_err!(profiles.get_item(&index))?;
   wrap_err!(item.save_file(file_data.unwrap()))
 }
 
 /// restart the sidecar
 #[tauri::command]
-pub fn restart_sidecar(
-  clash_state: State<'_, ClashState>,
-  profiles_state: State<'_, ProfilesState>,
-) -> Result<(), String> {
-  let mut clash = clash_state.0.lock().unwrap();
-  let mut profiles = profiles_state.0.lock().unwrap();
+pub fn restart_sidecar(core: State<'_, Core>) -> CmdResult {
+  let mut service = core.service.lock().unwrap();
+
+  wrap_err!(service.restart())?;
 
-  wrap_err!(clash.restart_sidecar(&mut profiles))
+  // 更新配置
+
+  log_if_err!(core.activate_enhanced(false, false));
+
+  Ok(())
 }
 
 /// 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> {
-  let clash = clash_state.0.lock().unwrap();
+pub fn get_clash_info(core: State<'_, Core>) -> CmdResult<ClashInfo> {
+  let clash = core.clash.lock().unwrap();
   Ok(clash.info.clone())
 }
 
@@ -282,16 +254,8 @@ pub fn get_clash_info(clash_state: State<'_, ClashState>) -> Result<ClashInfo, S
 /// after putting the change to the clash core
 /// then we should save the latest config
 #[tauri::command]
-pub fn patch_clash_config(
-  payload: Mapping,
-  clash_state: State<'_, ClashState>,
-  verge_state: State<'_, VergeState>,
-  profiles_state: State<'_, ProfilesState>,
-) -> Result<(), String> {
-  let mut clash = clash_state.0.lock().unwrap();
-  let mut verge = verge_state.0.lock().unwrap();
-  let mut profiles = profiles_state.0.lock().unwrap();
-  wrap_err!(clash.patch_config(payload, &mut verge, &mut profiles))
+pub fn patch_clash_config(payload: Mapping, core: State<'_, Core>) -> CmdResult {
+  wrap_err!(core.patch_clash(payload))
 }
 
 /// get the system proxy
@@ -303,15 +267,15 @@ pub fn get_sys_proxy() -> Result<SysProxyConfig, String> {
 /// 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> {
-  let verge = verge_state.0.lock().unwrap();
+pub fn get_cur_proxy(core: State<'_, Core>) -> CmdResult<Option<SysProxyConfig>> {
+  let verge = core.verge.lock().unwrap();
   Ok(verge.cur_sysproxy.clone())
 }
 
 /// get the verge config
 #[tauri::command]
-pub fn get_verge_config(verge_state: State<'_, VergeState>) -> Result<VergeConfig, String> {
-  let verge = verge_state.0.lock().unwrap();
+pub fn get_verge_config(core: State<'_, Core>) -> CmdResult<VergeConfig> {
+  let verge = core.verge.lock().unwrap();
   let mut config = verge.config.clone();
 
   if config.system_proxy_bypass.is_none() && verge.cur_sysproxy.is_some() {
@@ -327,14 +291,12 @@ pub fn get_verge_config(verge_state: State<'_, VergeState>) -> Result<VergeConfi
 pub fn patch_verge_config(
   payload: VergeConfig,
   app_handle: tauri::AppHandle,
-  clash_state: State<'_, ClashState>,
-  verge_state: State<'_, VergeState>,
-  profiles_state: State<'_, ProfilesState>,
+  core: State<'_, Core>,
 ) -> Result<(), String> {
   let tun_mode = payload.enable_tun_mode.clone();
   let system_proxy = payload.enable_system_proxy.clone();
 
-  let mut verge = verge_state.0.lock().unwrap();
+  let mut verge = core.verge.lock().unwrap();
   wrap_err!(verge.patch_config(payload))?;
 
   // change tun mode
@@ -348,10 +310,9 @@ pub fn patch_verge_config(
       }
     }
 
-    let clash = clash_state.0.lock().unwrap();
-    let profiles = profiles_state.0.lock().unwrap();
+    let profiles = core.profiles.lock().unwrap();
 
-    wrap_err!(clash.activate_enhanced(&profiles, false, false))?;
+    log_if_err!(core.activate_enhanced(false, false));
   }
 
   // change system tray
diff --git a/src-tauri/src/core/clash copy.rs b/src-tauri/src/core/clash copy.rs
new file mode 100644
index 0000000000000000000000000000000000000000..463ab1497e6c12f6ea2e3291fa5069c568d243be
--- /dev/null
+++ b/src-tauri/src/core/clash copy.rs	
@@ -0,0 +1,520 @@
+use super::{PrfEnhancedResult, Profiles, Verge, VergeConfig};
+use crate::log_if_err;
+use crate::utils::{config, dirs, help};
+use anyhow::{bail, Result};
+use reqwest::header::HeaderMap;
+use serde::{Deserialize, Serialize};
+use serde_yaml::{Mapping, Value};
+use std::{collections::HashMap, time::Duration};
+use tauri::api::process::{Command, CommandChild, CommandEvent};
+use tauri::Window;
+use tokio::time::sleep;
+
+#[derive(Default, Debug, Clone, Deserialize, Serialize)]
+pub struct ClashInfo {
+  /// clash sidecar status
+  pub status: String,
+
+  /// clash core port
+  pub port: Option<String>,
+
+  /// same as `external-controller`
+  pub server: Option<String>,
+
+  /// clash secret
+  pub secret: Option<String>,
+}
+
+pub struct Clash {
+  /// maintain the clash config
+  pub config: Mapping,
+
+  /// some info
+  pub info: ClashInfo,
+
+  /// clash sidecar
+  pub sidecar: Option<CommandChild>,
+
+  /// save the main window
+  pub window: Option<Window>,
+}
+
+impl Clash {
+  pub fn new() -> Clash {
+    let config = Clash::read_config();
+    let info = Clash::get_info(&config);
+
+    Clash {
+      config,
+      info,
+      sidecar: None,
+      window: None,
+    }
+  }
+
+  /// 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 {
+    let key_port_1 = Value::from("port");
+    let key_port_2 = Value::from("mixed-port");
+    let key_server = Value::from("external-controller");
+    let key_secret = Value::from("secret");
+
+    let port = match clash_config.get(&key_port_1) {
+      Some(value) => match value {
+        Value::String(val_str) => Some(val_str.clone()),
+        Value::Number(val_num) => Some(val_num.to_string()),
+        _ => None,
+      },
+      _ => None,
+    };
+    let port = match port {
+      Some(_) => port,
+      None => match clash_config.get(&key_port_2) {
+        Some(value) => match value {
+          Value::String(val_str) => Some(val_str.clone()),
+          Value::Number(val_num) => Some(val_num.to_string()),
+          _ => None,
+        },
+        _ => None,
+      },
+    };
+
+    let server = match clash_config.get(&key_server) {
+      Some(value) => match value {
+        Value::String(val_str) => {
+          // `external-controller` could be
+          // "127.0.0.1:9090" or ":9090"
+          // Todo: maybe it could support single port
+          let server = val_str.clone();
+          let server = match server.starts_with(":") {
+            true => format!("127.0.0.1{server}"),
+            false => server,
+          };
+
+          Some(server)
+        }
+        _ => None,
+      },
+      _ => None,
+    };
+    let secret = match clash_config.get(&key_secret) {
+      Some(value) => match value {
+        Value::String(val_str) => Some(val_str.clone()),
+        Value::Bool(val_bool) => Some(val_bool.to_string()),
+        Value::Number(val_num) => Some(val_num.to_string()),
+        _ => None,
+      },
+      _ => None,
+    };
+
+    ClashInfo {
+      status: "init".into(),
+      port,
+      server,
+      secret,
+    }
+  }
+
+  /// save the main window
+  pub fn set_window(&mut self, win: Option<Window>) {
+    self.window = win;
+  }
+
+  /// run clash sidecar
+  pub fn run_sidecar(&mut self, profiles: &Profiles, delay: bool) -> Result<()> {
+    let app_dir = dirs::app_home_dir();
+    let app_dir = app_dir.as_os_str().to_str().unwrap();
+
+    let cmd = Command::new_sidecar("clash")?;
+    let (mut rx, cmd_child) = cmd.args(["-d", app_dir]).spawn()?;
+
+    self.sidecar = Some(cmd_child);
+
+    // clash log
+    tauri::async_runtime::spawn(async move {
+      while let Some(event) = rx.recv().await {
+        match event {
+          CommandEvent::Stdout(line) => log::info!("[clash]: {}", line),
+          CommandEvent::Stderr(err) => log::error!("[clash]: {}", err),
+          _ => {}
+        }
+      }
+    });
+
+    // activate profile
+    log_if_err!(self.activate(&profiles));
+    log_if_err!(self.activate_enhanced(&profiles, delay, true));
+
+    Ok(())
+  }
+
+  /// drop clash sidecar
+  pub fn drop_sidecar(&mut self) -> Result<()> {
+    if let Some(sidecar) = self.sidecar.take() {
+      sidecar.kill()?;
+    }
+    Ok(())
+  }
+
+  /// restart clash sidecar
+  /// should reactivate profile after restart
+  pub fn restart_sidecar(&mut self, profiles: &mut Profiles) -> Result<()> {
+    self.update_config();
+    self.drop_sidecar()?;
+    self.run_sidecar(profiles, false)
+  }
+
+  /// update the clash info
+  pub fn update_config(&mut self) {
+    self.config = Clash::read_config();
+    self.info = Clash::get_info(&self.config);
+  }
+
+  /// patch update the clash config
+  pub fn patch_config(
+    &mut self,
+    patch: Mapping,
+    verge: &mut Verge,
+    profiles: &mut Profiles,
+  ) -> Result<()> {
+    let mix_port_key = Value::from("mixed-port");
+    let mut port = None;
+
+    for (key, value) in patch.into_iter() {
+      let value = value.clone();
+
+      // check whether the mix_port is changed
+      if key == mix_port_key {
+        if value.is_number() {
+          port = value.as_i64().as_ref().map(|n| n.to_string());
+        } else {
+          port = value.as_str().as_ref().map(|s| s.to_string());
+        }
+      }
+
+      self.config.insert(key.clone(), value);
+    }
+
+    self.save_config()?;
+
+    if let Some(port) = port {
+      self.restart_sidecar(profiles)?;
+      verge.init_sysproxy(Some(port));
+    }
+
+    Ok(())
+  }
+
+  /// revise the `tun` and `dns` config
+  fn _tun_mode(mut config: Mapping, enable: bool) -> Mapping {
+    macro_rules! revise {
+      ($map: expr, $key: expr, $val: expr) => {
+        let ret_key = Value::String($key.into());
+        $map.insert(ret_key, Value::from($val));
+      };
+    }
+
+    // if key not exists then append value
+    macro_rules! append {
+      ($map: expr, $key: expr, $val: expr) => {
+        let ret_key = Value::String($key.into());
+        if !$map.contains_key(&ret_key) {
+          $map.insert(ret_key, Value::from($val));
+        }
+      };
+    }
+
+    // tun config
+    let tun_val = config.get(&Value::from("tun"));
+    let mut new_tun = Mapping::new();
+
+    if tun_val.is_some() && tun_val.as_ref().unwrap().is_mapping() {
+      new_tun = tun_val.as_ref().unwrap().as_mapping().unwrap().clone();
+    }
+
+    revise!(new_tun, "enable", enable);
+
+    if enable {
+      append!(new_tun, "stack", "gvisor");
+      append!(new_tun, "dns-hijack", vec!["198.18.0.2:53"]);
+      append!(new_tun, "auto-route", true);
+      append!(new_tun, "auto-detect-interface", true);
+    }
+
+    revise!(config, "tun", new_tun);
+
+    // dns config
+    let dns_val = config.get(&Value::from("dns"));
+    let mut new_dns = Mapping::new();
+
+    if dns_val.is_some() && dns_val.as_ref().unwrap().is_mapping() {
+      new_dns = dns_val.as_ref().unwrap().as_mapping().unwrap().clone();
+    }
+
+    // 借鉴cfw的默认配置
+    revise!(new_dns, "enable", enable);
+
+    if enable {
+      append!(new_dns, "enhanced-mode", "fake-ip");
+      append!(
+        new_dns,
+        "nameserver",
+        vec!["114.114.114.114", "223.5.5.5", "8.8.8.8"]
+      );
+      append!(new_dns, "fallback", vec![] as Vec<&str>);
+
+      #[cfg(target_os = "windows")]
+      append!(
+        new_dns,
+        "fake-ip-filter",
+        vec![
+          "dns.msftncsi.com",
+          "www.msftncsi.com",
+          "www.msftconnecttest.com"
+        ]
+      );
+    }
+
+    revise!(config, "dns", new_dns);
+    config
+  }
+
+  /// activate the profile
+  /// generate a new profile to the temp_dir
+  /// then put the path to the clash core
+  fn _activate(info: ClashInfo, config: Mapping, window: Option<Window>) -> Result<()> {
+    let verge_config = VergeConfig::new();
+    let tun_enable = verge_config.enable_tun_mode.unwrap_or(false);
+
+    let config = Clash::_tun_mode(config, tun_enable);
+
+    let temp_path = dirs::profiles_temp_path();
+    config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?;
+
+    tauri::async_runtime::spawn(async move {
+      if info.server.is_none() {
+        return;
+      }
+
+      let server = info.server.unwrap();
+      let server = format!("http://{server}/configs");
+
+      let mut headers = HeaderMap::new();
+      headers.insert("Content-Type", "application/json".parse().unwrap());
+
+      if let Some(secret) = info.secret.as_ref() {
+        let secret = format!("Bearer {}", secret.clone()).parse().unwrap();
+        headers.insert("Authorization", secret);
+      }
+
+      let mut data = HashMap::new();
+      data.insert("path", temp_path.as_os_str().to_str().unwrap());
+
+      // retry 5 times
+      for _ in 0..5 {
+        match reqwest::ClientBuilder::new().no_proxy().build() {
+          Ok(client) => {
+            let builder = client.put(&server).headers(headers.clone()).json(&data);
+
+            match builder.send().await {
+              Ok(resp) => {
+                if resp.status() != 204 {
+                  log::error!("failed to activate clash for status \"{}\"", resp.status());
+                }
+
+                // emit the window to update something
+                if let Some(window) = window {
+                  window.emit("verge://refresh-clash-config", "yes").unwrap();
+                }
+
+                // do not retry
+                break;
+              }
+              Err(err) => log::error!("failed to activate for `{err}`"),
+            }
+          }
+          Err(err) => log::error!("failed to activate for `{err}`"),
+        }
+        sleep(Duration::from_millis(500)).await;
+      }
+    });
+
+    Ok(())
+  }
+
+  /// enhanced profiles mode
+  /// - (sync) refresh config if enhance chain is null
+  /// - (async) enhanced config
+  pub fn activate_enhanced(&self, profiles: &Profiles, delay: bool, skip: bool) -> Result<()> {
+    if self.window.is_none() {
+      bail!("failed to get the main window");
+    }
+
+    let event_name = help::get_uid("e");
+    let event_name = format!("enhanced-cb-{event_name}");
+
+    // generate the payload
+    let payload = profiles.gen_enhanced(event_name.clone())?;
+
+    let info = self.info.clone();
+
+    // do not run enhanced
+    if payload.chain.len() == 0 {
+      if skip {
+        return Ok(());
+      }
+
+      let mut config = self.config.clone();
+      let filter_data = Clash::strict_filter(payload.current);
+
+      for (key, value) in filter_data.into_iter() {
+        config.insert(key, value);
+      }
+
+      return Clash::_activate(info, config, self.window.clone());
+    }
+
+    let window = self.window.clone().unwrap();
+    let window_move = self.window.clone();
+
+    window.once(&event_name, move |event| {
+      if let Some(result) = event.payload() {
+        let result: PrfEnhancedResult = serde_json::from_str(result).unwrap();
+
+        if let Some(data) = result.data {
+          let mut config = Clash::read_config();
+          let filter_data = Clash::loose_filter(data); // loose filter
+
+          for (key, value) in filter_data.into_iter() {
+            config.insert(key, value);
+          }
+
+          log_if_err!(Clash::_activate(info, config, window_move));
+          log::info!("profile enhanced status {}", result.status);
+        }
+
+        result.error.map(|err| log::error!("{err}"));
+      }
+    });
+
+    tauri::async_runtime::spawn(async move {
+      // wait the window setup during resolve app
+      if delay {
+        sleep(Duration::from_secs(2)).await;
+      }
+      window.emit("script-handler", payload).unwrap();
+    });
+
+    Ok(())
+  }
+
+  /// activate the profile
+  /// auto activate enhanced profile
+  pub fn activate(&self, profiles: &Profiles) -> Result<()> {
+    let data = profiles.gen_activate()?;
+    let data = Clash::strict_filter(data);
+
+    let info = self.info.clone();
+    let mut config = self.config.clone();
+
+    for (key, value) in data.into_iter() {
+      config.insert(key, value);
+    }
+
+    Clash::_activate(info, config, self.window.clone())
+  }
+
+  /// only 5 default fields available (clash config fields)
+  /// convert to lowercase
+  fn strict_filter(config: Mapping) -> Mapping {
+    // Only the following fields are allowed:
+    // proxies/proxy-providers/proxy-groups/rule-providers/rules
+    let valid_keys = vec![
+      "proxies",
+      "proxy-providers",
+      "proxy-groups",
+      "rules",
+      "rule-providers",
+    ];
+
+    let mut new_config = Mapping::new();
+
+    for (key, value) in config.into_iter() {
+      key.as_str().map(|key_str| {
+        // change to lowercase
+        let mut key_str = String::from(key_str);
+        key_str.make_ascii_lowercase();
+
+        // filter
+        if valid_keys.contains(&&*key_str) {
+          new_config.insert(Value::String(key_str), value);
+        }
+      });
+    }
+
+    new_config
+  }
+
+  /// more clash config fields available
+  /// convert to lowercase
+  fn loose_filter(config: Mapping) -> Mapping {
+    // all of these can not be revised by script or merge
+    // http/https/socks port should be under control
+    let not_allow = vec![
+      "port",
+      "socks-port",
+      "mixed-port",
+      "allow-lan",
+      "mode",
+      "external-controller",
+      "secret",
+      "log-level",
+    ];
+
+    let mut new_config = Mapping::new();
+
+    for (key, value) in config.into_iter() {
+      key.as_str().map(|key_str| {
+        // change to lowercase
+        let mut key_str = String::from(key_str);
+        key_str.make_ascii_lowercase();
+
+        // filter
+        if !not_allow.contains(&&*key_str) {
+          new_config.insert(Value::String(key_str), value);
+        }
+      });
+    }
+
+    new_config
+  }
+}
+
+impl Default for Clash {
+  fn default() -> Self {
+    Clash::new()
+  }
+}
+
+impl Drop for Clash {
+  fn drop(&mut self) {
+    if let Err(err) = self.drop_sidecar() {
+      log::error!("{err}");
+    }
+  }
+}
diff --git a/src-tauri/src/core/clash.rs b/src-tauri/src/core/clash.rs
index 463ab1497e6c12f6ea2e3291fa5069c568d243be..2e5d402c9420e66b3488a844f3a54d8d17f05f63 100644
--- a/src-tauri/src/core/clash.rs
+++ b/src-tauri/src/core/clash.rs
@@ -1,14 +1,7 @@
-use super::{PrfEnhancedResult, Profiles, Verge, VergeConfig};
-use crate::log_if_err;
-use crate::utils::{config, dirs, help};
-use anyhow::{bail, Result};
-use reqwest::header::HeaderMap;
+use crate::utils::{config, dirs};
+use anyhow::Result;
 use serde::{Deserialize, Serialize};
 use serde_yaml::{Mapping, Value};
-use std::{collections::HashMap, time::Duration};
-use tauri::api::process::{Command, CommandChild, CommandEvent};
-use tauri::Window;
-use tokio::time::sleep;
 
 #[derive(Default, Debug, Clone, Deserialize, Serialize)]
 pub struct ClashInfo {
@@ -25,56 +18,16 @@ pub struct ClashInfo {
   pub secret: Option<String>,
 }
 
-pub struct Clash {
-  /// maintain the clash config
-  pub config: Mapping,
-
-  /// some info
-  pub info: ClashInfo,
-
-  /// clash sidecar
-  pub sidecar: Option<CommandChild>,
-
-  /// save the main window
-  pub window: Option<Window>,
-}
-
-impl Clash {
-  pub fn new() -> Clash {
-    let config = Clash::read_config();
-    let info = Clash::get_info(&config);
-
-    Clash {
-      config,
-      info,
-      sidecar: None,
-      window: None,
-    }
-  }
-
-  /// 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"),
-    )
-  }
-
+impl ClashInfo {
   /// parse the clash's config.yaml
   /// get some information
-  fn get_info(clash_config: &Mapping) -> ClashInfo {
+  pub fn from(config: &Mapping) -> ClashInfo {
     let key_port_1 = Value::from("port");
     let key_port_2 = Value::from("mixed-port");
     let key_server = Value::from("external-controller");
     let key_secret = Value::from("secret");
 
-    let port = match clash_config.get(&key_port_1) {
+    let port = match config.get(&key_port_1) {
       Some(value) => match value {
         Value::String(val_str) => Some(val_str.clone()),
         Value::Number(val_num) => Some(val_num.to_string()),
@@ -84,7 +37,7 @@ impl Clash {
     };
     let port = match port {
       Some(_) => port,
-      None => match clash_config.get(&key_port_2) {
+      None => match config.get(&key_port_2) {
         Some(value) => match value {
           Value::String(val_str) => Some(val_str.clone()),
           Value::Number(val_num) => Some(val_num.to_string()),
@@ -94,7 +47,7 @@ impl Clash {
       },
     };
 
-    let server = match clash_config.get(&key_server) {
+    let server = match config.get(&key_server) {
       Some(value) => match value {
         Value::String(val_str) => {
           // `external-controller` could be
@@ -112,7 +65,8 @@ impl Clash {
       },
       _ => None,
     };
-    let secret = match clash_config.get(&key_secret) {
+
+    let secret = match config.get(&key_secret) {
       Some(value) => match value {
         Value::String(val_str) => Some(val_str.clone()),
         Value::Bool(val_bool) => Some(val_bool.to_string()),
@@ -129,99 +83,78 @@ impl Clash {
       secret,
     }
   }
+}
 
-  /// save the main window
-  pub fn set_window(&mut self, win: Option<Window>) {
-    self.window = win;
-  }
-
-  /// run clash sidecar
-  pub fn run_sidecar(&mut self, profiles: &Profiles, delay: bool) -> Result<()> {
-    let app_dir = dirs::app_home_dir();
-    let app_dir = app_dir.as_os_str().to_str().unwrap();
-
-    let cmd = Command::new_sidecar("clash")?;
-    let (mut rx, cmd_child) = cmd.args(["-d", app_dir]).spawn()?;
-
-    self.sidecar = Some(cmd_child);
+pub struct Clash {
+  /// maintain the clash config
+  pub config: Mapping,
 
-    // clash log
-    tauri::async_runtime::spawn(async move {
-      while let Some(event) = rx.recv().await {
-        match event {
-          CommandEvent::Stdout(line) => log::info!("[clash]: {}", line),
-          CommandEvent::Stderr(err) => log::error!("[clash]: {}", err),
-          _ => {}
-        }
-      }
-    });
+  /// some info
+  pub info: ClashInfo,
+}
 
-    // activate profile
-    log_if_err!(self.activate(&profiles));
-    log_if_err!(self.activate_enhanced(&profiles, delay, true));
+impl Clash {
+  pub fn new() -> Clash {
+    let config = Clash::read_config();
+    let info = ClashInfo::from(&config);
 
-    Ok(())
+    Clash { config, info }
   }
 
-  /// drop clash sidecar
-  pub fn drop_sidecar(&mut self) -> Result<()> {
-    if let Some(sidecar) = self.sidecar.take() {
-      sidecar.kill()?;
-    }
-    Ok(())
+  /// get clash config
+  pub fn read_config() -> Mapping {
+    config::read_yaml::<Mapping>(dirs::clash_path())
   }
 
-  /// restart clash sidecar
-  /// should reactivate profile after restart
-  pub fn restart_sidecar(&mut self, profiles: &mut Profiles) -> Result<()> {
-    self.update_config();
-    self.drop_sidecar()?;
-    self.run_sidecar(profiles, false)
+  /// save the clash config
+  pub fn save_config(&self) -> Result<()> {
+    config::save_yaml(
+      dirs::clash_path(),
+      &self.config,
+      Some("# Default Config For Clash Core\n\n"),
+    )
   }
 
+  /// todo: delete
   /// update the clash info
   pub fn update_config(&mut self) {
     self.config = Clash::read_config();
-    self.info = Clash::get_info(&self.config);
+    self.info = ClashInfo::from(&self.config);
   }
 
   /// patch update the clash config
-  pub fn patch_config(
-    &mut self,
-    patch: Mapping,
-    verge: &mut Verge,
-    profiles: &mut Profiles,
-  ) -> Result<()> {
-    let mix_port_key = Value::from("mixed-port");
-    let mut port = None;
+  /// if the port is changed then return true
+  pub fn patch_config(&mut self, patch: Mapping) -> Result<bool> {
+    let port_key = Value::from("mixed-port");
+    let server_key = Value::from("external-controller");
+    let secret_key = Value::from("secret");
+
+    let mut change_port = false;
+    let mut change_info = false;
 
     for (key, value) in patch.into_iter() {
-      let value = value.clone();
-
-      // check whether the mix_port is changed
-      if key == mix_port_key {
-        if value.is_number() {
-          port = value.as_i64().as_ref().map(|n| n.to_string());
-        } else {
-          port = value.as_str().as_ref().map(|s| s.to_string());
-        }
+      if key == port_key {
+        change_port = true;
       }
 
-      self.config.insert(key.clone(), value);
-    }
+      if key == port_key || key == server_key || key == secret_key {
+        change_info = true;
+      }
 
-    self.save_config()?;
+      self.config.insert(key, value);
+    }
 
-    if let Some(port) = port {
-      self.restart_sidecar(profiles)?;
-      verge.init_sysproxy(Some(port));
+    if change_info {
+      self.info = ClashInfo::from(&self.config);
     }
 
-    Ok(())
+    self.save_config()?;
+
+    Ok(change_port)
   }
 
   /// revise the `tun` and `dns` config
-  fn _tun_mode(mut config: Mapping, enable: bool) -> Mapping {
+  pub fn _tun_mode(mut config: Mapping, enable: bool) -> Mapping {
     macro_rules! revise {
       ($map: expr, $key: expr, $val: expr) => {
         let ret_key = Value::String($key.into());
@@ -294,154 +227,9 @@ impl Clash {
     config
   }
 
-  /// activate the profile
-  /// generate a new profile to the temp_dir
-  /// then put the path to the clash core
-  fn _activate(info: ClashInfo, config: Mapping, window: Option<Window>) -> Result<()> {
-    let verge_config = VergeConfig::new();
-    let tun_enable = verge_config.enable_tun_mode.unwrap_or(false);
-
-    let config = Clash::_tun_mode(config, tun_enable);
-
-    let temp_path = dirs::profiles_temp_path();
-    config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?;
-
-    tauri::async_runtime::spawn(async move {
-      if info.server.is_none() {
-        return;
-      }
-
-      let server = info.server.unwrap();
-      let server = format!("http://{server}/configs");
-
-      let mut headers = HeaderMap::new();
-      headers.insert("Content-Type", "application/json".parse().unwrap());
-
-      if let Some(secret) = info.secret.as_ref() {
-        let secret = format!("Bearer {}", secret.clone()).parse().unwrap();
-        headers.insert("Authorization", secret);
-      }
-
-      let mut data = HashMap::new();
-      data.insert("path", temp_path.as_os_str().to_str().unwrap());
-
-      // retry 5 times
-      for _ in 0..5 {
-        match reqwest::ClientBuilder::new().no_proxy().build() {
-          Ok(client) => {
-            let builder = client.put(&server).headers(headers.clone()).json(&data);
-
-            match builder.send().await {
-              Ok(resp) => {
-                if resp.status() != 204 {
-                  log::error!("failed to activate clash for status \"{}\"", resp.status());
-                }
-
-                // emit the window to update something
-                if let Some(window) = window {
-                  window.emit("verge://refresh-clash-config", "yes").unwrap();
-                }
-
-                // do not retry
-                break;
-              }
-              Err(err) => log::error!("failed to activate for `{err}`"),
-            }
-          }
-          Err(err) => log::error!("failed to activate for `{err}`"),
-        }
-        sleep(Duration::from_millis(500)).await;
-      }
-    });
-
-    Ok(())
-  }
-
-  /// enhanced profiles mode
-  /// - (sync) refresh config if enhance chain is null
-  /// - (async) enhanced config
-  pub fn activate_enhanced(&self, profiles: &Profiles, delay: bool, skip: bool) -> Result<()> {
-    if self.window.is_none() {
-      bail!("failed to get the main window");
-    }
-
-    let event_name = help::get_uid("e");
-    let event_name = format!("enhanced-cb-{event_name}");
-
-    // generate the payload
-    let payload = profiles.gen_enhanced(event_name.clone())?;
-
-    let info = self.info.clone();
-
-    // do not run enhanced
-    if payload.chain.len() == 0 {
-      if skip {
-        return Ok(());
-      }
-
-      let mut config = self.config.clone();
-      let filter_data = Clash::strict_filter(payload.current);
-
-      for (key, value) in filter_data.into_iter() {
-        config.insert(key, value);
-      }
-
-      return Clash::_activate(info, config, self.window.clone());
-    }
-
-    let window = self.window.clone().unwrap();
-    let window_move = self.window.clone();
-
-    window.once(&event_name, move |event| {
-      if let Some(result) = event.payload() {
-        let result: PrfEnhancedResult = serde_json::from_str(result).unwrap();
-
-        if let Some(data) = result.data {
-          let mut config = Clash::read_config();
-          let filter_data = Clash::loose_filter(data); // loose filter
-
-          for (key, value) in filter_data.into_iter() {
-            config.insert(key, value);
-          }
-
-          log_if_err!(Clash::_activate(info, config, window_move));
-          log::info!("profile enhanced status {}", result.status);
-        }
-
-        result.error.map(|err| log::error!("{err}"));
-      }
-    });
-
-    tauri::async_runtime::spawn(async move {
-      // wait the window setup during resolve app
-      if delay {
-        sleep(Duration::from_secs(2)).await;
-      }
-      window.emit("script-handler", payload).unwrap();
-    });
-
-    Ok(())
-  }
-
-  /// activate the profile
-  /// auto activate enhanced profile
-  pub fn activate(&self, profiles: &Profiles) -> Result<()> {
-    let data = profiles.gen_activate()?;
-    let data = Clash::strict_filter(data);
-
-    let info = self.info.clone();
-    let mut config = self.config.clone();
-
-    for (key, value) in data.into_iter() {
-      config.insert(key, value);
-    }
-
-    Clash::_activate(info, config, self.window.clone())
-  }
-
   /// only 5 default fields available (clash config fields)
   /// convert to lowercase
-  fn strict_filter(config: Mapping) -> Mapping {
+  pub fn strict_filter(config: Mapping) -> Mapping {
     // Only the following fields are allowed:
     // proxies/proxy-providers/proxy-groups/rule-providers/rules
     let valid_keys = vec![
@@ -472,7 +260,7 @@ impl Clash {
 
   /// more clash config fields available
   /// convert to lowercase
-  fn loose_filter(config: Mapping) -> Mapping {
+  pub fn loose_filter(config: Mapping) -> Mapping {
     // all of these can not be revised by script or merge
     // http/https/socks port should be under control
     let not_allow = vec![
@@ -510,11 +298,3 @@ impl Default for Clash {
     Clash::new()
   }
 }
-
-impl Drop for Clash {
-  fn drop(&mut self) {
-    if let Err(err) = self.drop_sidecar() {
-      log::error!("{err}");
-    }
-  }
-}
diff --git a/src-tauri/src/core/enhance.rs b/src-tauri/src/core/enhance.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a1c79e8f83a98dac2c569a237bcd773fa442a8d7
--- /dev/null
+++ b/src-tauri/src/core/enhance.rs
@@ -0,0 +1,66 @@
+use super::prfitem::PrfItem;
+use crate::utils::{config, dirs};
+use serde::{Deserialize, Serialize};
+use serde_yaml::Mapping;
+use std::fs;
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize)]
+pub struct PrfEnhanced {
+  pub current: Mapping,
+
+  pub chain: Vec<PrfData>,
+
+  pub valid: Vec<String>,
+
+  pub callback: String,
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize)]
+pub struct PrfEnhancedResult {
+  pub data: Option<Mapping>,
+
+  pub status: String,
+
+  pub error: Option<String>,
+}
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize)]
+pub struct PrfData {
+  item: PrfItem,
+
+  #[serde(skip_serializing_if = "Option::is_none")]
+  merge: Option<Mapping>,
+
+  #[serde(skip_serializing_if = "Option::is_none")]
+  script: Option<String>,
+}
+
+impl PrfData {
+  pub fn from_item(item: &PrfItem) -> Option<PrfData> {
+    match item.itype.as_ref() {
+      Some(itype) => {
+        let file = item.file.clone()?;
+        let path = dirs::app_profiles_dir().join(file);
+
+        if !path.exists() {
+          return None;
+        }
+
+        match itype.as_str() {
+          "script" => Some(PrfData {
+            item: item.clone(),
+            script: Some(fs::read_to_string(path).unwrap_or("".into())),
+            merge: None,
+          }),
+          "merge" => Some(PrfData {
+            item: item.clone(),
+            merge: Some(config::read_yaml::<Mapping>(path)),
+            script: None,
+          }),
+          _ => None,
+        }
+      }
+      None => None,
+    }
+  }
+}
diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs
index 1cf8f1e3e8446e85115bc9cae03febabd06d04b1..caed8603d5d7586d6ff41f48d66b69d89fcac256 100644
--- a/src-tauri/src/core/mod.rs
+++ b/src-tauri/src/core/mod.rs
@@ -1,7 +1,227 @@
+use self::notice::Notice;
+use self::service::Service;
+use crate::core::enhance::PrfEnhancedResult;
+use crate::log_if_err;
+use crate::utils::{config, dirs, help};
+use anyhow::{bail, Result};
+use serde_yaml::Mapping;
+use std::sync::{Arc, Mutex};
+use std::time::Duration;
+use tauri::Window;
+use tokio::time::sleep;
+
 mod clash;
+mod enhance;
+mod notice;
+mod prfitem;
 mod profiles;
+mod service;
+mod timer;
 mod verge;
 
 pub use self::clash::*;
+pub use self::prfitem::*;
 pub use self::profiles::*;
 pub use self::verge::*;
+
+#[derive(Clone)]
+pub struct Core {
+  pub clash: Arc<Mutex<Clash>>,
+
+  pub verge: Arc<Mutex<Verge>>,
+
+  pub profiles: Arc<Mutex<Profiles>>,
+
+  pub service: Arc<Mutex<Service>>,
+
+  pub window: Arc<Mutex<Option<Window>>>,
+}
+
+impl Core {
+  pub fn new() -> Core {
+    let clash = Clash::new();
+    let verge = Verge::new();
+    let profiles = Profiles::new();
+    let service = Service::new();
+
+    Core {
+      clash: Arc::new(Mutex::new(clash)),
+      verge: Arc::new(Mutex::new(verge)),
+      profiles: Arc::new(Mutex::new(profiles)),
+      service: Arc::new(Mutex::new(service)),
+      window: Arc::new(Mutex::new(None)),
+    }
+  }
+
+  pub fn init(&self) {
+    log_if_err!(self.restart_clash());
+
+    let clash = self.clash.lock().unwrap();
+    let mut verge = self.verge.lock().unwrap();
+    verge.init_sysproxy(clash.info.port.clone());
+
+    log_if_err!(verge.init_launch());
+
+    // system tray
+    // verge.config.enable_system_proxy.map(|enable| {
+    //   log_if_err!(app
+    //     .tray_handle()
+    //     .get_item("system_proxy")
+    //     .set_selected(enable));
+    // });
+  }
+
+  /// save the window instance
+  pub fn set_win(&self, win: Option<Window>) {
+    let mut window = self.window.lock().unwrap();
+    *window = win;
+  }
+
+  /// restart the clash sidecar
+  pub fn restart_clash(&self) -> Result<()> {
+    {
+      let mut service = self.service.lock().unwrap();
+      service.restart()?;
+    }
+
+    self.activate()?;
+    self.activate_enhanced(false, true)
+  }
+
+  /// handle the clash config changed
+  pub fn patch_clash(&self, patch: Mapping) -> Result<()> {
+    let (changed, port) = {
+      let mut clash = self.clash.lock().unwrap();
+      (clash.patch_config(patch)?, clash.info.port.clone())
+    };
+
+    // todo: port check
+
+    if changed {
+      let mut service = self.service.lock().unwrap();
+      service.restart()?;
+
+      self.activate()?;
+      self.activate_enhanced(false, true)?;
+
+      let mut verge = self.verge.lock().unwrap();
+      verge.init_sysproxy(port);
+    }
+
+    Ok(())
+  }
+
+  /// activate the profile
+  /// auto activate enhanced profile
+  pub fn activate(&self) -> Result<()> {
+    let data = {
+      let profiles = self.profiles.lock().unwrap();
+      let data = profiles.gen_activate()?;
+      Clash::strict_filter(data)
+    };
+
+    let (mut config, info) = {
+      let clash = self.clash.lock().unwrap();
+      let config = clash.config.clone();
+      let info = clash.info.clone();
+      (config, info)
+    };
+
+    for (key, value) in data.into_iter() {
+      config.insert(key, value);
+    }
+
+    let config = {
+      let verge = self.verge.lock().unwrap();
+      let tun_mode = verge.config.enable_tun_mode.unwrap_or(false);
+      Clash::_tun_mode(config, tun_mode)
+    };
+
+    let notice = {
+      let window = self.window.lock().unwrap();
+      Notice::from(window.clone())
+    };
+
+    let service = self.service.lock().unwrap();
+    service.set_config(info, config, notice)
+  }
+
+  /// enhanced profiles mode
+  pub fn activate_enhanced(&self, delay: bool, skip: bool) -> Result<()> {
+    let window = self.window.lock().unwrap();
+    if window.is_none() {
+      bail!("failed to get the main window");
+    }
+
+    let event_name = help::get_uid("e");
+    let event_name = format!("enhanced-cb-{event_name}");
+
+    // generate the payload
+    let payload = {
+      let profiles = self.profiles.lock().unwrap();
+      profiles.gen_enhanced(event_name.clone())?
+    };
+
+    // do not run enhanced
+    if payload.chain.len() == 0 {
+      if skip {
+        return Ok(());
+      }
+
+      return self.activate();
+    }
+
+    let tun_mode = {
+      let verge = self.verge.lock().unwrap();
+      verge.config.enable_tun_mode.unwrap_or(false)
+    };
+
+    let info = {
+      let clash = self.clash.lock().unwrap();
+      clash.info.clone()
+    };
+
+    let notice = Notice::from(window.clone());
+    let service = self.service.clone();
+
+    let window = window.clone().unwrap();
+    window.once(&event_name, move |event| {
+      let result = event.payload();
+
+      if result.is_none() {
+        log::warn!("event payload result is none");
+        return;
+      }
+
+      let result = result.unwrap();
+      let result: PrfEnhancedResult = serde_json::from_str(result).unwrap();
+
+      if let Some(data) = result.data {
+        let mut config = Clash::read_config();
+        let filter_data = Clash::loose_filter(data); // loose filter
+
+        for (key, value) in filter_data.into_iter() {
+          config.insert(key, value);
+        }
+
+        let config = Clash::_tun_mode(config, tun_mode);
+
+        let service = service.lock().unwrap();
+        log_if_err!(service.set_config(info, config, notice));
+
+        log::info!("profile enhanced status {}", result.status);
+      }
+
+      result.error.map(|err| log::error!("{err}"));
+    });
+
+    // wait the window setup during resolve app
+    // if delay {
+    //   sleep(Duration::from_secs(2)).await;
+    // }
+
+    window.emit("script-handler", payload).unwrap();
+
+    Ok(())
+  }
+}
diff --git a/src-tauri/src/core/notice.rs b/src-tauri/src/core/notice.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8e942da13fd38f123deec967cd2c3dc25afc7abc
--- /dev/null
+++ b/src-tauri/src/core/notice.rs
@@ -0,0 +1,35 @@
+use crate::log_if_err;
+use tauri::Window;
+
+#[derive(Debug, Default, Clone)]
+pub struct Notice {
+  win: Option<Window>,
+}
+
+impl Notice {
+  pub fn from(win: Option<Window>) -> Notice {
+    Notice { win }
+  }
+
+  pub fn set_win(&mut self, win: Option<Window>) {
+    self.win = win;
+  }
+
+  pub fn refresh_clash(&self) {
+    if let Some(window) = self.win.as_ref() {
+      log_if_err!(window.emit("verge://refresh-clash-config", "yes"));
+    }
+  }
+
+  pub fn refresh_verge(&self) {
+    if let Some(window) = self.win.as_ref() {
+      log_if_err!(window.emit("verge://refresh-verge-config", "yes"));
+    }
+  }
+
+  pub fn refresh_profiles(&self) {
+    if let Some(window) = self.win.as_ref() {
+      log_if_err!(window.emit("verge://refresh-profiles-config", "yes"));
+    }
+  }
+}
diff --git a/src-tauri/src/core/prfitem.rs b/src-tauri/src/core/prfitem.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3d999062c2e219e105685fecf9dab0257d4be985
--- /dev/null
+++ b/src-tauri/src/core/prfitem.rs
@@ -0,0 +1,320 @@
+use crate::utils::{dirs, help, tmpl};
+use anyhow::{bail, Context, Result};
+use serde::{Deserialize, Serialize};
+use std::fs;
+
+#[derive(Debug, Clone, Deserialize, Serialize)]
+pub struct PrfItem {
+  pub uid: Option<String>,
+
+  /// profile item type
+  /// enum value: remote | local | script | merge
+  #[serde(rename = "type")]
+  pub itype: Option<String>,
+
+  /// 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>,
+
+  /// 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<PrfSelected>>,
+
+  /// subscription user info
+  #[serde(skip_serializing_if = "Option::is_none")]
+  pub extra: Option<PrfExtra>,
+
+  /// updated time
+  pub updated: Option<usize>,
+
+  /// some options of the item
+  #[serde(skip_serializing_if = "Option::is_none")]
+  pub option: Option<PrfOption>,
+
+  /// the file data
+  #[serde(skip)]
+  pub file_data: Option<String>,
+}
+
+#[derive(Default, Debug, Clone, Deserialize, Serialize)]
+pub struct PrfSelected {
+  pub name: Option<String>,
+  pub now: Option<String>,
+}
+
+#[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)]
+pub struct PrfExtra {
+  pub upload: usize,
+  pub download: usize,
+  pub total: usize,
+  pub expire: usize,
+}
+
+#[derive(Default, Debug, Clone, Deserialize, Serialize)]
+pub struct PrfOption {
+  /// for `remote` profile's http request
+  /// see issue #13
+  #[serde(skip_serializing_if = "Option::is_none")]
+  pub user_agent: Option<String>,
+
+  /// for `remote` profile
+  #[serde(skip_serializing_if = "Option::is_none")]
+  pub with_proxy: Option<bool>,
+
+  #[serde(skip_serializing_if = "Option::is_none")]
+  pub update_interval: Option<u64>,
+}
+
+impl PrfOption {
+  pub fn merge(one: Option<Self>, other: Option<Self>) -> Option<Self> {
+    if one.is_none() {
+      return other;
+    }
+
+    if one.is_some() && other.is_some() {
+      let mut one = one.unwrap();
+      let other = other.unwrap();
+
+      if let Some(val) = other.user_agent {
+        one.user_agent = Some(val);
+      }
+
+      if let Some(val) = other.with_proxy {
+        one.with_proxy = Some(val);
+      }
+
+      if let Some(val) = other.update_interval {
+        one.update_interval = Some(val);
+      }
+
+      return Some(one);
+    }
+
+    return one;
+  }
+
+  pub fn diff_update_interval(one: Option<&Self>, other: Option<&Self>) -> bool {
+    if one.is_some() && other.is_some() {
+      let one = one.unwrap();
+      let other = other.unwrap();
+
+      return one.update_interval == other.update_interval;
+    }
+
+    return false;
+  }
+}
+
+impl Default for PrfItem {
+  fn default() -> Self {
+    PrfItem {
+      uid: None,
+      itype: None,
+      name: None,
+      desc: None,
+      file: None,
+      url: None,
+      selected: None,
+      extra: None,
+      updated: None,
+      option: None,
+      file_data: None,
+    }
+  }
+}
+
+impl PrfItem {
+  /// From partial item
+  /// must contain `itype`
+  pub async fn from(item: PrfItem, file_data: Option<String>) -> Result<PrfItem> {
+    if item.itype.is_none() {
+      bail!("type should not be null");
+    }
+
+    match item.itype.unwrap().as_str() {
+      "remote" => {
+        if item.url.is_none() {
+          bail!("url should not be null");
+        }
+        let url = item.url.as_ref().unwrap().as_str();
+        let name = item.name;
+        let desc = item.desc;
+        PrfItem::from_url(url, name, desc, item.option).await
+      }
+      "local" => {
+        let name = item.name.unwrap_or("Local File".into());
+        let desc = item.desc.unwrap_or("".into());
+        PrfItem::from_local(name, desc, file_data)
+      }
+      "merge" => {
+        let name = item.name.unwrap_or("Merge".into());
+        let desc = item.desc.unwrap_or("".into());
+        PrfItem::from_merge(name, desc)
+      }
+      "script" => {
+        let name = item.name.unwrap_or("Script".into());
+        let desc = item.desc.unwrap_or("".into());
+        PrfItem::from_script(name, desc)
+      }
+      typ @ _ => bail!("invalid type \"{typ}\""),
+    }
+  }
+
+  /// ## Local type
+  /// create a new item from name/desc
+  pub fn from_local(name: String, desc: String, file_data: Option<String>) -> Result<PrfItem> {
+    let uid = help::get_uid("l");
+    let file = format!("{uid}.yaml");
+
+    Ok(PrfItem {
+      uid: Some(uid),
+      itype: Some("local".into()),
+      name: Some(name),
+      desc: Some(desc),
+      file: Some(file),
+      url: None,
+      selected: None,
+      extra: None,
+      option: None,
+      updated: Some(help::get_now()),
+      file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
+    })
+  }
+
+  /// ## Remote type
+  /// create a new item from url
+  pub async fn from_url(
+    url: &str,
+    name: Option<String>,
+    desc: Option<String>,
+    option: Option<PrfOption>,
+  ) -> Result<PrfItem> {
+    let with_proxy = match option.as_ref() {
+      Some(opt) => opt.with_proxy.unwrap_or(false),
+      None => false,
+    };
+    let user_agent = match option.as_ref() {
+      Some(opt) => opt.user_agent.clone(),
+      None => None,
+    };
+
+    let mut builder = reqwest::ClientBuilder::new();
+
+    if !with_proxy {
+      builder = builder.no_proxy();
+    }
+
+    builder = builder.user_agent(user_agent.unwrap_or("clash-verge/v0.1.0".into()));
+
+    let resp = builder.build()?.get(url).send().await?;
+    let header = resp.headers();
+
+    // parse the Subscription Userinfo
+    let extra = match header.get("Subscription-Userinfo") {
+      Some(value) => {
+        let sub_info = value.to_str().unwrap_or("");
+
+        Some(PrfExtra {
+          upload: help::parse_str(sub_info, "upload=").unwrap_or(0),
+          download: help::parse_str(sub_info, "download=").unwrap_or(0),
+          total: help::parse_str(sub_info, "total=").unwrap_or(0),
+          expire: help::parse_str(sub_info, "expire=").unwrap_or(0),
+        })
+      }
+      None => None,
+    };
+
+    let uid = help::get_uid("r");
+    let file = format!("{uid}.yaml");
+    let name = name.unwrap_or(uid.clone());
+    let data = resp.text_with_charset("utf-8").await?;
+
+    Ok(PrfItem {
+      uid: Some(uid),
+      itype: Some("remote".into()),
+      name: Some(name),
+      desc,
+      file: Some(file),
+      url: Some(url.into()),
+      selected: None,
+      extra,
+      option,
+      updated: Some(help::get_now()),
+      file_data: Some(data),
+    })
+  }
+
+  /// ## Merge type (enhance)
+  /// create the enhanced item by using `merge` rule
+  pub fn from_merge(name: String, desc: String) -> Result<PrfItem> {
+    let uid = help::get_uid("m");
+    let file = format!("{uid}.yaml");
+
+    Ok(PrfItem {
+      uid: Some(uid),
+      itype: Some("merge".into()),
+      name: Some(name),
+      desc: Some(desc),
+      file: Some(file),
+      url: None,
+      selected: None,
+      extra: None,
+      option: None,
+      updated: Some(help::get_now()),
+      file_data: Some(tmpl::ITEM_MERGE.into()),
+    })
+  }
+
+  /// ## Script type (enhance)
+  /// create the enhanced item by using javascript(browserjs)
+  pub fn from_script(name: String, desc: String) -> Result<PrfItem> {
+    let uid = help::get_uid("s");
+    let file = format!("{uid}.js"); // js ext
+
+    Ok(PrfItem {
+      uid: Some(uid),
+      itype: Some("script".into()),
+      name: Some(name),
+      desc: Some(desc),
+      file: Some(file),
+      url: None,
+      selected: None,
+      extra: None,
+      option: None,
+      updated: Some(help::get_now()),
+      file_data: Some(tmpl::ITEM_SCRIPT.into()),
+    })
+  }
+
+  /// get the file data
+  pub fn read_file(&self) -> Result<String> {
+    if self.file.is_none() {
+      bail!("could not find the file");
+    }
+
+    let file = self.file.clone().unwrap();
+    let path = dirs::app_profiles_dir().join(file);
+    fs::read_to_string(path).context("failed to read the file")
+  }
+
+  /// save the file data
+  pub fn save_file(&self, data: String) -> Result<()> {
+    if self.file.is_none() {
+      bail!("could not find the file");
+    }
+
+    let file = self.file.clone().unwrap();
+    let path = dirs::app_profiles_dir().join(file);
+    fs::write(path, data.as_bytes()).context("failed to save the file")
+  }
+}
diff --git a/src-tauri/src/core/profiles.rs b/src-tauri/src/core/profiles.rs
index 19555261ca50c86442f7a1322aa4b4e8fe82e978..af37f9cddb01a2c3b49bccc158fd8d0eb2ee0265 100644
--- a/src-tauri/src/core/profiles.rs
+++ b/src-tauri/src/core/profiles.rs
@@ -1,307 +1,11 @@
-use crate::utils::{config, dirs, help, tmpl};
+use super::enhance::{PrfData, PrfEnhanced};
+use super::prfitem::PrfItem;
+use crate::utils::{config, dirs, help};
 use anyhow::{bail, Context, Result};
 use serde::{Deserialize, Serialize};
 use serde_yaml::Mapping;
 use std::{fs, io::Write};
 
-#[derive(Debug, Clone, Deserialize, Serialize)]
-pub struct PrfItem {
-  pub uid: Option<String>,
-
-  /// profile item type
-  /// enum value: remote | local | script | merge
-  #[serde(rename = "type")]
-  pub itype: Option<String>,
-
-  /// 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>,
-
-  /// 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<PrfSelected>>,
-
-  /// subscription user info
-  #[serde(skip_serializing_if = "Option::is_none")]
-  pub extra: Option<PrfExtra>,
-
-  /// updated time
-  pub updated: Option<usize>,
-
-  /// some options of the item
-  #[serde(skip_serializing_if = "Option::is_none")]
-  pub option: Option<PrfOption>,
-
-  /// the file data
-  #[serde(skip)]
-  pub file_data: Option<String>,
-}
-
-#[derive(Default, Debug, Clone, Deserialize, Serialize)]
-pub struct PrfSelected {
-  pub name: Option<String>,
-  pub now: Option<String>,
-}
-
-#[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)]
-pub struct PrfExtra {
-  pub upload: usize,
-  pub download: usize,
-  pub total: usize,
-  pub expire: usize,
-}
-
-#[derive(Default, Debug, Clone, Deserialize, Serialize)]
-pub struct PrfOption {
-  /// for `remote` profile's http request
-  /// see issue #13
-  #[serde(skip_serializing_if = "Option::is_none")]
-  pub user_agent: Option<String>,
-
-  /// for `remote` profile
-  #[serde(skip_serializing_if = "Option::is_none")]
-  pub with_proxy: Option<bool>,
-}
-
-impl PrfOption {
-  pub fn merge(one: Option<Self>, other: Option<Self>) -> Option<Self> {
-    if one.is_some() && other.is_some() {
-      let mut one = one.unwrap();
-      let other = other.unwrap();
-
-      if let Some(val) = other.user_agent {
-        one.user_agent = Some(val);
-      }
-
-      if let Some(val) = other.with_proxy {
-        one.with_proxy = Some(val);
-      }
-
-      return Some(one);
-    }
-
-    if one.is_none() {
-      return other;
-    }
-
-    return one;
-  }
-}
-
-impl Default for PrfItem {
-  fn default() -> Self {
-    PrfItem {
-      uid: None,
-      itype: None,
-      name: None,
-      desc: None,
-      file: None,
-      url: None,
-      selected: None,
-      extra: None,
-      updated: None,
-      option: None,
-      file_data: None,
-    }
-  }
-}
-
-impl PrfItem {
-  /// From partial item
-  /// must contain `itype`
-  pub async fn from(item: PrfItem, file_data: Option<String>) -> Result<PrfItem> {
-    if item.itype.is_none() {
-      bail!("type should not be null");
-    }
-
-    match item.itype.unwrap().as_str() {
-      "remote" => {
-        if item.url.is_none() {
-          bail!("url should not be null");
-        }
-        let url = item.url.as_ref().unwrap().as_str();
-        let name = item.name;
-        let desc = item.desc;
-        PrfItem::from_url(url, name, desc, item.option).await
-      }
-      "local" => {
-        let name = item.name.unwrap_or("Local File".into());
-        let desc = item.desc.unwrap_or("".into());
-        PrfItem::from_local(name, desc, file_data)
-      }
-      "merge" => {
-        let name = item.name.unwrap_or("Merge".into());
-        let desc = item.desc.unwrap_or("".into());
-        PrfItem::from_merge(name, desc)
-      }
-      "script" => {
-        let name = item.name.unwrap_or("Script".into());
-        let desc = item.desc.unwrap_or("".into());
-        PrfItem::from_script(name, desc)
-      }
-      typ @ _ => bail!("invalid type \"{typ}\""),
-    }
-  }
-
-  /// ## Local type
-  /// create a new item from name/desc
-  pub fn from_local(name: String, desc: String, file_data: Option<String>) -> Result<PrfItem> {
-    let uid = help::get_uid("l");
-    let file = format!("{uid}.yaml");
-
-    Ok(PrfItem {
-      uid: Some(uid),
-      itype: Some("local".into()),
-      name: Some(name),
-      desc: Some(desc),
-      file: Some(file),
-      url: None,
-      selected: None,
-      extra: None,
-      option: None,
-      updated: Some(help::get_now()),
-      file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
-    })
-  }
-
-  /// ## Remote type
-  /// create a new item from url
-  pub async fn from_url(
-    url: &str,
-    name: Option<String>,
-    desc: Option<String>,
-    option: Option<PrfOption>,
-  ) -> Result<PrfItem> {
-    let with_proxy = match option.as_ref() {
-      Some(opt) => opt.with_proxy.unwrap_or(false),
-      None => false,
-    };
-    let user_agent = match option.as_ref() {
-      Some(opt) => opt.user_agent.clone(),
-      None => None,
-    };
-
-    let mut builder = reqwest::ClientBuilder::new();
-
-    if !with_proxy {
-      builder = builder.no_proxy();
-    }
-
-    builder = builder.user_agent(user_agent.unwrap_or("clash-verge/v0.1.0".into()));
-
-    let resp = builder.build()?.get(url).send().await?;
-    let header = resp.headers();
-
-    // parse the Subscription Userinfo
-    let extra = match header.get("Subscription-Userinfo") {
-      Some(value) => {
-        let sub_info = value.to_str().unwrap_or("");
-
-        Some(PrfExtra {
-          upload: help::parse_str(sub_info, "upload=").unwrap_or(0),
-          download: help::parse_str(sub_info, "download=").unwrap_or(0),
-          total: help::parse_str(sub_info, "total=").unwrap_or(0),
-          expire: help::parse_str(sub_info, "expire=").unwrap_or(0),
-        })
-      }
-      None => None,
-    };
-
-    let uid = help::get_uid("r");
-    let file = format!("{uid}.yaml");
-    let name = name.unwrap_or(uid.clone());
-    let data = resp.text_with_charset("utf-8").await?;
-
-    Ok(PrfItem {
-      uid: Some(uid),
-      itype: Some("remote".into()),
-      name: Some(name),
-      desc,
-      file: Some(file),
-      url: Some(url.into()),
-      selected: None,
-      extra,
-      option,
-      updated: Some(help::get_now()),
-      file_data: Some(data),
-    })
-  }
-
-  /// ## Merge type (enhance)
-  /// create the enhanced item by using `merge` rule
-  pub fn from_merge(name: String, desc: String) -> Result<PrfItem> {
-    let uid = help::get_uid("m");
-    let file = format!("{uid}.yaml");
-
-    Ok(PrfItem {
-      uid: Some(uid),
-      itype: Some("merge".into()),
-      name: Some(name),
-      desc: Some(desc),
-      file: Some(file),
-      url: None,
-      selected: None,
-      extra: None,
-      option: None,
-      updated: Some(help::get_now()),
-      file_data: Some(tmpl::ITEM_MERGE.into()),
-    })
-  }
-
-  /// ## Script type (enhance)
-  /// create the enhanced item by using javascript(browserjs)
-  pub fn from_script(name: String, desc: String) -> Result<PrfItem> {
-    let uid = help::get_uid("s");
-    let file = format!("{uid}.js"); // js ext
-
-    Ok(PrfItem {
-      uid: Some(uid),
-      itype: Some("script".into()),
-      name: Some(name),
-      desc: Some(desc),
-      file: Some(file),
-      url: None,
-      selected: None,
-      extra: None,
-      option: None,
-      updated: Some(help::get_now()),
-      file_data: Some(tmpl::ITEM_SCRIPT.into()),
-    })
-  }
-
-  /// get the file data
-  pub fn read_file(&self) -> Result<String> {
-    if self.file.is_none() {
-      bail!("could not find the file");
-    }
-
-    let file = self.file.clone().unwrap();
-    let path = dirs::app_profiles_dir().join(file);
-    fs::read_to_string(path).context("failed to read the file")
-  }
-
-  /// save the file data
-  pub fn save_file(&self, data: String) -> Result<()> {
-    if self.file.is_none() {
-      bail!("could not find the file");
-    }
-
-    let file = self.file.clone().unwrap();
-    let path = dirs::app_profiles_dir().join(file);
-    fs::write(path, data.as_bytes()).context("failed to save the file")
-  }
-}
-
 ///
 /// ## Profiles Config
 ///
@@ -331,6 +35,10 @@ macro_rules! patch {
 }
 
 impl Profiles {
+  pub fn new() -> Self {
+    Profiles::read_file()
+  }
+
   /// read the config from the file
   pub fn read_file() -> Self {
     let mut profiles = config::read_yaml::<Self>(dirs::profiles_path());
@@ -616,64 +324,3 @@ impl Profiles {
     })
   }
 }
-
-#[derive(Default, Debug, Clone, Serialize, Deserialize)]
-pub struct PrfEnhanced {
-  pub current: Mapping,
-
-  pub chain: Vec<PrfData>,
-
-  pub valid: Vec<String>,
-
-  pub callback: String,
-}
-
-#[derive(Default, Debug, Clone, Serialize, Deserialize)]
-pub struct PrfEnhancedResult {
-  pub data: Option<Mapping>,
-
-  pub status: String,
-
-  pub error: Option<String>,
-}
-
-#[derive(Default, Debug, Clone, Serialize, Deserialize)]
-pub struct PrfData {
-  item: PrfItem,
-
-  #[serde(skip_serializing_if = "Option::is_none")]
-  merge: Option<Mapping>,
-
-  #[serde(skip_serializing_if = "Option::is_none")]
-  script: Option<String>,
-}
-
-impl PrfData {
-  pub fn from_item(item: &PrfItem) -> Option<PrfData> {
-    match item.itype.as_ref() {
-      Some(itype) => {
-        let file = item.file.clone()?;
-        let path = dirs::app_profiles_dir().join(file);
-
-        if !path.exists() {
-          return None;
-        }
-
-        match itype.as_str() {
-          "script" => Some(PrfData {
-            item: item.clone(),
-            script: Some(fs::read_to_string(path).unwrap_or("".into())),
-            merge: None,
-          }),
-          "merge" => Some(PrfData {
-            item: item.clone(),
-            merge: Some(config::read_yaml::<Mapping>(path)),
-            script: None,
-          }),
-          _ => None,
-        }
-      }
-      None => None,
-    }
-  }
-}
diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b1eb42eb526e4b9255697c771ac3ed9fd5b26063
--- /dev/null
+++ b/src-tauri/src/core/service.rs
@@ -0,0 +1,121 @@
+use super::{notice::Notice, ClashInfo};
+use crate::log_if_err;
+use crate::utils::{config, dirs};
+use anyhow::{bail, Result};
+use reqwest::header::HeaderMap;
+use serde_yaml::Mapping;
+use std::{collections::HashMap, time::Duration};
+use tauri::api::process::{Command, CommandChild, CommandEvent};
+use tokio::time::sleep;
+
+#[derive(Debug)]
+pub struct Service {
+  sidecar: Option<CommandChild>,
+}
+
+impl Service {
+  pub fn new() -> Service {
+    Service { sidecar: None }
+  }
+
+  pub fn start(&mut self) -> Result<()> {
+    if self.sidecar.is_some() {
+      bail!("could not run clash sidecar twice");
+    }
+
+    let app_dir = dirs::app_home_dir();
+    let app_dir = app_dir.as_os_str().to_str().unwrap();
+
+    let cmd = Command::new_sidecar("clash")?;
+    let (mut rx, cmd_child) = cmd.args(["-d", app_dir]).spawn()?;
+
+    self.sidecar = Some(cmd_child);
+
+    // clash log
+    tauri::async_runtime::spawn(async move {
+      while let Some(event) = rx.recv().await {
+        match event {
+          CommandEvent::Stdout(line) => log::info!("[clash]: {}", line),
+          CommandEvent::Stderr(err) => log::error!("[clash]: {}", err),
+          _ => {}
+        }
+      }
+    });
+
+    Ok(())
+  }
+
+  pub fn stop(&mut self) -> Result<()> {
+    if let Some(sidecar) = self.sidecar.take() {
+      sidecar.kill()?;
+    }
+    Ok(())
+  }
+
+  pub fn restart(&mut self) -> Result<()> {
+    self.stop()?;
+    self.start()
+  }
+
+  pub fn set_config(&self, info: ClashInfo, config: Mapping, notice: Notice) -> Result<()> {
+    if self.sidecar.is_none() {
+      bail!("did not start sidecar");
+    }
+
+    let temp_path = dirs::profiles_temp_path();
+    config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?;
+
+    if info.server.is_none() {
+      bail!("failed to parse the server");
+    }
+
+    let server = info.server.unwrap();
+    let server = format!("http://{server}/configs");
+
+    let mut headers = HeaderMap::new();
+    headers.insert("Content-Type", "application/json".parse().unwrap());
+
+    if let Some(secret) = info.secret.as_ref() {
+      let secret = format!("Bearer {}", secret.clone()).parse().unwrap();
+      headers.insert("Authorization", secret);
+    }
+
+    tauri::async_runtime::spawn(async move {
+      let mut data = HashMap::new();
+      data.insert("path", temp_path.as_os_str().to_str().unwrap());
+
+      // retry 5 times
+      for _ in 0..5 {
+        match reqwest::ClientBuilder::new().no_proxy().build() {
+          Ok(client) => {
+            let builder = client.put(&server).headers(headers.clone()).json(&data);
+
+            match builder.send().await {
+              Ok(resp) => {
+                if resp.status() != 204 {
+                  log::error!("failed to activate clash with status \"{}\"", resp.status());
+                }
+
+                notice.refresh_clash();
+
+                // do not retry
+                break;
+              }
+              Err(err) => log::error!("failed to activate for `{err}`"),
+            }
+          }
+          Err(err) => log::error!("failed to activate for `{err}`"),
+        }
+        sleep(Duration::from_millis(500)).await;
+      }
+    });
+
+    Ok(())
+  }
+}
+
+impl Drop for Service {
+  fn drop(&mut self) {
+    log_if_err!(self.stop());
+  }
+}
diff --git a/src-tauri/src/core/timer.rs b/src-tauri/src/core/timer.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2e94a6f2409bfc733a9676ee4edb4e16243fdc58
--- /dev/null
+++ b/src-tauri/src/core/timer.rs
@@ -0,0 +1,20 @@
+use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, Task, TaskBuilder};
+use std::collections::HashMap;
+
+pub struct Timer {
+  delay_timer: DelayTimer,
+
+  timer_map: HashMap<String, u64>,
+
+  timer_count: u64,
+}
+
+impl Timer {
+  pub fn new() -> Self {
+    Timer {
+      delay_timer: DelayTimerBuilder::default().build(),
+      timer_map: HashMap::new(),
+      timer_count: 1,
+    }
+  }
+}
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 77e99fa02b0c58a8e0f61bdb63e6ca797cd146a2..52790e75b275c2ab2c7556df57d195525456e269 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -5,7 +5,6 @@
 
 mod cmds;
 mod core;
-mod states;
 mod utils;
 
 use crate::{
@@ -39,9 +38,7 @@ fn main() -> std::io::Result<()> {
 
   #[allow(unused_mut)]
   let mut builder = tauri::Builder::default()
-    .manage(states::VergeState::default())
-    .manage(states::ClashState::default())
-    .manage(states::ProfilesState::default())
+    .manage(core::Core::new())
     .setup(|app| Ok(resolve::resolve_setup(app)))
     .system_tray(SystemTray::new().with_menu(tray_menu))
     .on_system_tray_event(move |app_handle, event| match event {
@@ -53,8 +50,8 @@ fn main() -> std::io::Result<()> {
           window.set_focus().unwrap();
         }
         "system_proxy" => {
-          let verge_state = app_handle.state::<states::VergeState>();
-          let mut verge = verge_state.0.lock().unwrap();
+          let core = app_handle.state::<core::Core>();
+          let mut verge = core.verge.lock().unwrap();
 
           let old_value = verge.config.enable_system_proxy.clone().unwrap_or(false);
           let new_value = !old_value;
@@ -68,8 +65,8 @@ fn main() -> std::io::Result<()> {
           }
         }
         "tun_mode" => {
-          let verge_state = app_handle.state::<states::VergeState>();
-          let mut verge = verge_state.0.lock().unwrap();
+          let core = app_handle.state::<core::Core>();
+          let mut verge = core.verge.lock().unwrap();
 
           let old_value = verge.config.enable_tun_mode.clone().unwrap_or(false);
           let new_value = !old_value;
@@ -83,12 +80,8 @@ fn main() -> std::io::Result<()> {
           }
         }
         "restart_clash" => {
-          let clash_state = app_handle.state::<states::ClashState>();
-          let profiles_state = app_handle.state::<states::ProfilesState>();
-          let mut clash = clash_state.0.lock().unwrap();
-          let mut profiles = profiles_state.0.lock().unwrap();
-
-          crate::log_if_err!(clash.restart_sidecar(&mut profiles));
+          let core = app_handle.state::<core::Core>();
+          crate::log_if_err!(core.restart_clash());
         }
         "quit" => {
           resolve::resolve_reset(app_handle);
diff --git a/src-tauri/src/states.rs b/src-tauri/src/states.rs
index 1e101926b679922fbe59255571d29cb61e7b42fb..c22364fa61a7393c2447fd4181642fa41caec031 100644
--- a/src-tauri/src/states.rs
+++ b/src-tauri/src/states.rs
@@ -1,11 +1,11 @@
-use crate::core::{Clash, Profiles, Verge};
-use std::sync::{Arc, Mutex};
+// use crate::core::{Clash, Profiles, Verge};
+// use std::sync::{Arc, Mutex};
 
-#[derive(Default)]
-pub struct ProfilesState(pub Arc<Mutex<Profiles>>);
+// #[derive(Default)]
+// pub struct ProfilesState(pub Arc<Mutex<Profiles>>);
 
-#[derive(Default)]
-pub struct ClashState(pub Arc<Mutex<Clash>>);
+// #[derive(Default)]
+// pub struct ClashState(pub Arc<Mutex<Clash>>);
 
-#[derive(Default)]
-pub struct VergeState(pub Arc<Mutex<Verge>>);
+// #[derive(Default)]
+// pub struct VergeState(pub Arc<Mutex<Verge>>);
diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs
index 8d6934253e2de551d8a861cd1f8257ccefc27900..5678357fe7b2ed388eb894a88e5c35cedd6e24f2 100644
--- a/src-tauri/src/utils/resolve.rs
+++ b/src-tauri/src/utils/resolve.rs
@@ -1,5 +1,5 @@
 use crate::log_if_err;
-use crate::{core::Profiles, states, utils::init, utils::server};
+use crate::{core::Core, utils::init, utils::server};
 use tauri::{App, AppHandle, Manager};
 
 /// handle something when start app
@@ -11,36 +11,21 @@ pub fn resolve_setup(app: &App) {
   init::init_app(app.package_info());
 
   // init states
-  let clash_state = app.state::<states::ClashState>();
-  let verge_state = app.state::<states::VergeState>();
-  let profiles_state = app.state::<states::ProfilesState>();
+  let core = app.state::<Core>();
 
-  let mut clash = clash_state.0.lock().unwrap();
-  let mut verge = verge_state.0.lock().unwrap();
-  let mut profiles = profiles_state.0.lock().unwrap();
+  core.set_win(app.get_window("main"));
+  core.init();
 
-  *profiles = Profiles::read_file();
+  // clash.set_window(app.get_window("main"));
+  // log_if_err!(clash.run_sidecar(&profiles, true));
 
-  clash.set_window(app.get_window("main"));
-  log_if_err!(clash.run_sidecar(&profiles, true));
-
-  verge.init_sysproxy(clash.info.port.clone());
-  log_if_err!(verge.init_launch());
-
-  verge.config.enable_system_proxy.map(|enable| {
-    log_if_err!(app
-      .tray_handle()
-      .get_item("system_proxy")
-      .set_selected(enable));
-  });
-
-  resolve_window(app, verge.config.enable_silent_start.clone());
+  resolve_window(app, None);
 }
 
 /// reset system proxy
 pub fn resolve_reset(app_handle: &AppHandle) {
-  let verge_state = app_handle.state::<states::VergeState>();
-  let mut verge = verge_state.0.lock().unwrap();
+  let core = app_handle.state::<Core>();
+  let mut verge = core.verge.lock().unwrap();
 
   verge.reset_sysproxy();
 }