Repository: espanso/espanso (language: rust)

Issue:
Port .deb installation on v2
Currently, there is no `.deb` archive available for the v2. Port the old logic to the new version


Discussion / hints:

Fix patch:
diff --git a/.github/scripts/ubuntu/build_deb.sh b/.github/scripts/ubuntu/build_deb.sh
new file mode 100755
index 00000000..d2352ad4
--- /dev/null
+++ b/.github/scripts/ubuntu/build_deb.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+set -e
+
+echo "Installing cargo-deb"
+cargo install cargo-deb --version 1.34.0
+
+cd espanso
+
+echo "Building X11 deb package"
+cargo deb -p espanso
+
+echo "Building Wayland deb package"
+cargo deb -p espanso --variant wayland -- --features wayland
+
+cd ..
+cp espanso/target/debian/espanso_*.deb espanso-debian-x11-amd64.deb
+sha256sum espanso-debian-x11-amd64.deb > espanso-debian-x11-amd64-sha256.txt
+cp espanso/target/debian/espanso-wayland*.deb espanso-debian-wayland-amd64.deb
+sha256sum espanso-debian-wayland-amd64.deb > espanso-debian-wayland-amd64-sha256.txt
+ls -la
+
+echo "Copying to mounted volume"
+cp espanso-debian-* /shared
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 974040b8..195608cc 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -117,6 +117,34 @@ jobs:
         if: ${{ github.ref == 'refs/heads/master' }}
         run: |
           gh release upload ${{ needs.extract-version.outputs.espanso_version }} Espanso-X11.AppImage Espanso-X11.AppImage.sha256.txt
+  
+  linux-deb:
+    needs: ["extract-version", "create-release"]
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: Print target version
+        run: |
+          echo Using version ${{ needs.extract-version.outputs.espanso_version }}
+      - name: Build docker image
+        run: |
+          sudo docker build -t espanso-ubuntu . -f .github/scripts/ubuntu/Dockerfile
+      - name: Build Deb packages
+        run: |
+          sudo docker run --rm -v "$(pwd):/shared" espanso-ubuntu espanso/.github/scripts/ubuntu/build_deb.sh
+      - uses: actions/upload-artifact@v2
+        name: "Upload artifacts"
+        with:
+          name: Ubuntu-Debian Artifacts
+          path: |
+            espanso-debian-x11-amd64.deb
+            espanso-debian-wayland-amd64.deb
+      - name: Upload artifacts to Github Releases (if master)
+        if: ${{ github.ref == 'refs/heads/master' }}
+        run: |
+          gh release upload ${{ needs.extract-version.outputs.espanso_version }} espanso-debian-x11-amd64.deb espanso-debian-wayland-amd64.deb espanso-debian-x11-amd64-sha256.txt espanso-debian-wayland-amd64-sha256.txt 
+
 
   macos-intel:
     needs: ["extract-version", "create-release"]
@@ -203,4 +231,26 @@ jobs:
       - name: Upload artifacts to Github Releases (if master)
         if: ${{ github.ref == 'refs/heads/master' }}
         run: |
-          gh release upload ${{ needs.extract-version.outputs.espanso_version }} Espanso-Mac-M1.zip Espanso-Mac-M1.zip.sha256.txt
\ No newline at end of file
+          gh release upload ${{ needs.extract-version.outputs.espanso_version }} Espanso-Mac-M1.zip Espanso-Mac-M1.zip.sha256.txt
+
+  macos-publish-homebrew:
+    needs: ["extract-version", "create-release", "macos-m1", "macos-intel"]
+    runs-on: macos-11
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: Print target version
+        run: |
+          echo Using version ${{ needs.extract-version.outputs.espanso_version }}
+
+      - name: "Setup SSH deploy key"
+        uses: webfactory/ssh-agent@fc49353b67b2b7c1e0e6a600572d01a69f2672dd
+        with:
+          ssh-private-key: ${{ secrets.HOMEBREW_CASK_SSH_PRIVATE_KEY }}
+      
+      - name: Create and Publish Homebrew Cask
+        if: ${{ github.ref == 'refs/heads/master' }}
+        run: |
+          VERSION="${{ needs.extract-version.outputs.espanso_version }}" ./scripts/publish_homebrew_version.sh
+
+          echo "Cask formula has been published here: "
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 193d7ecf..a9e3bdf7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -572,7 +572,7 @@ dependencies = [
 
 [[package]]
 name = "espanso"
-version = "2.1.2-alpha"
+version = "2.1.3-alpha"
 dependencies = [
  "anyhow",
  "caps",
diff --git a/espanso-config/src/config/resolve.rs b/espanso-config/src/config/resolve.rs
index 548c4337..518c1ff8 100644
--- a/espanso-config/src/config/resolve.rs
+++ b/espanso-config/src/config/resolve.rs
@@ -271,7 +271,7 @@ impl Config for ResolvedConfig {
     match self.parsed.search_trigger.as_deref() {
       Some("OFF") | Some("off") => None,
       Some(x) => Some(x.to_string()),
-      None => Some("jkj".to_string()),
+      None => None,
     }
   }
 
diff --git a/espanso-config/src/matches/group/loader/yaml/mod.rs b/espanso-config/src/matches/group/loader/yaml/mod.rs
index 1559fda4..ebe1dea2 100644
--- a/espanso-config/src/matches/group/loader/yaml/mod.rs
+++ b/espanso-config/src/matches/group/loader/yaml/mod.rs
@@ -300,6 +300,7 @@ pub fn try_convert_into_match(
       effect,
       label: yaml_match.label,
       id: next_id(),
+      search_terms: yaml_match.search_terms.unwrap_or_default(),
     },
     warnings,
   ))
diff --git a/espanso-config/src/matches/group/loader/yaml/parse.rs b/espanso-config/src/matches/group/loader/yaml/parse.rs
index 50c14636..bcdfe18c 100644
--- a/espanso-config/src/matches/group/loader/yaml/parse.rs
+++ b/espanso-config/src/matches/group/loader/yaml/parse.rs
@@ -114,6 +114,9 @@ pub struct YAMLMatch {
 
   #[serde(default)]
   pub html: Option<String>,
+
+  #[serde(default)]
+  pub search_terms: Option<Vec<String>>,
 }
 
 #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
diff --git a/espanso-config/src/matches/mod.rs b/espanso-config/src/matches/mod.rs
index 33fd8e43..474b40b0 100644
--- a/espanso-config/src/matches/mod.rs
+++ b/espanso-config/src/matches/mod.rs
@@ -35,6 +35,7 @@ pub struct Match {
 
   // Metadata
   pub label: Option<String>,
+  pub search_terms: Vec<String>,
 }
 
 impl Default for Match {
@@ -44,6 +45,7 @@ impl Default for Match {
       effect: MatchEffect::None,
       label: None,
       id: 0,
+      search_terms: vec![],
     }
   }
 }
@@ -66,6 +68,15 @@ impl Match {
   pub fn cause_description(&self) -> Option<&str> {
     self.cause.description()
   }
+
+  pub fn search_terms(&self) -> Vec<&str> {
+    self
+      .search_terms
+      .iter()
+      .map(|term| term.as_str())
+      .chain(self.cause.search_terms())
+      .collect()
+  }
 }
 
 // Causes
@@ -100,6 +111,14 @@ impl MatchCause {
     // TODO: insert rendering for hotkey/shortcut
     // TODO: insert rendering for regex? I'm worried it might be too long
   }
+
+  pub fn search_terms(&self) -> Vec<&str> {
+    if let MatchCause::Trigger(trigger_cause) = &self {
+      trigger_cause.triggers.iter().map(|s| s.as_str()).collect()
+    } else {
+      vec![]
+    }
+  }
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
diff --git a/espanso-engine/src/process/middleware/context_menu.rs b/espanso-engine/src/process/middleware/context_menu.rs
index 9ee53fd9..55a756f6 100644
--- a/espanso-engine/src/process/middleware/context_menu.rs
+++ b/espanso-engine/src/process/middleware/context_menu.rs
@@ -99,7 +99,7 @@ impl Middleware for ContextMenuMiddleware {
             0,
             MenuItem::Simple(SimpleMenuItem {
               id: CONTEXT_ITEM_SECURE_INPUT_EXPLAIN,
-              label: "Why is espanso not working?".to_string(),
+              label: "Why is Espanso not working?".to_string(),
             }),
           );
           items.insert(
diff --git a/espanso-inject/src/mac/mod.rs b/espanso-inject/src/mac/mod.rs
index 2bb12d70..fc8a2ee5 100644
--- a/espanso-inject/src/mac/mod.rs
+++ b/espanso-inject/src/mac/mod.rs
@@ -32,7 +32,7 @@ use crate::{keys, InjectionOptions, Injector};
 #[allow(improper_ctypes)]
 #[link(name = "espansoinject", kind = "static")]
 extern "C" {
-  pub fn inject_string(string: *const c_char);
+  pub fn inject_string(string: *const c_char, delay: i32);
   pub fn inject_separate_vkeys(vkey_array: *const i32, vkey_count: i32, delay: i32);
   pub fn inject_vkeys_combination(vkey_array: *const i32, vkey_count: i32, delay: i32);
 }
@@ -60,10 +60,10 @@ impl MacInjector {
 }
 
 impl Injector for MacInjector {
-  fn send_string(&self, string: &str, _: InjectionOptions) -> Result<()> {
+  fn send_string(&self, string: &str, options: InjectionOptions) -> Result<()> {
     let c_string = CString::new(string)?;
     unsafe {
-      inject_string(c_string.as_ptr());
+      inject_string(c_string.as_ptr(), options.delay);
     }
     Ok(())
   }
diff --git a/espanso-inject/src/mac/native.h b/espanso-inject/src/mac/native.h
index 6d52913a..abab7281 100644
--- a/espanso-inject/src/mac/native.h
+++ b/espanso-inject/src/mac/native.h
@@ -23,7 +23,7 @@
 #include <stdint.h>
 
 // Inject a complete string using the KEYEVENTF_UNICODE flag
-extern "C" void inject_string(char * string);
+extern "C" void inject_string(char * string, int32_t delay);
 
 // Send a sequence of vkey presses and releases
 extern "C" void inject_separate_vkeys(int32_t *vkey_array, int32_t vkey_count, int32_t delay);
diff --git a/espanso-inject/src/mac/native.mm b/espanso-inject/src/mac/native.mm
index cecbb569..8828c28e 100644
--- a/espanso-inject/src/mac/native.mm
+++ b/espanso-inject/src/mac/native.mm
@@ -27,8 +27,10 @@
 // so that we can later skip them in the detect module.
 CGPoint ESPANSO_POINT_MARKER = CGPointMake(-27469, 0);
 
-void inject_string(char *string)
+void inject_string(char *string, int32_t delay)
 {
+  long udelay = delay * 1000;
+
   char * stringCopy = strdup(string);
   dispatch_async(dispatch_get_main_queue(), ^(void) {
     // Convert the c string to a UniChar array as required by the CGEventKeyboardSetUnicodeString method
@@ -49,7 +51,7 @@ void inject_string(char *string)
       CGEventPost(kCGHIDEventTap, e2);
       CFRelease(e2);
 
-      usleep(2000);
+      usleep(udelay);
     }
 
     // Because of a bug ( or undocumented limit ) of the CGEventKeyboardSetUnicodeString method
@@ -69,7 +71,7 @@ void inject_string(char *string)
       CGEventPost(kCGHIDEventTap, e);
       CFRelease(e);
 
-      usleep(2000);
+      usleep(udelay);
 
       // Some applications require an explicit release of the space key
       // For more information: https://github.com/federico-terzi/espanso/issues/159
@@ -78,7 +80,7 @@ void inject_string(char *string)
       CGEventPost(kCGHIDEventTap, e2);
       CFRelease(e2);
 
-      usleep(2000);
+      usleep(udelay);
 
       i += chunk_size;
     }
diff --git a/espanso-modulo/src/search/algorithm.rs b/espanso-modulo/src/search/algorithm.rs
index 80c829aa..9faf0bef 100644
--- a/espanso-modulo/src/search/algorithm.rs
+++ b/espanso-modulo/src/search/algorithm.rs
@@ -44,7 +44,9 @@ fn exact_match(query: &str, items: &[SearchItem]) -> Vec<usize> {
     .iter()
     .enumerate()
     .filter(|(_, item)| {
-      item.label.contains(query) || item.trigger.as_deref().map_or(false, |t| t.contains(query))
+      item.label.contains(query)
+        || item.trigger.as_deref().map_or(false, |t| t.contains(query))
+        || item.search_terms.iter().any(|term| term.contains(query))
     })
     .map(|(i, _)| i)
     .collect()
@@ -61,6 +63,10 @@ fn case_insensitive_exact_match(query: &str, items: &[SearchItem]) -> Vec<usize>
           .trigger
           .as_deref()
           .map_or(false, |t| t.to_lowercase().contains(query))
+        || item
+          .search_terms
+          .iter()
+          .any(|term| term.to_lowercase().contains(&lowercase_query))
     })
     .map(|(i, _)| i)
     .collect()
@@ -79,6 +85,10 @@ fn case_insensitive_keyword(query: &str, items: &[SearchItem]) -> Vec<usize> {
             .trigger
             .as_deref()
             .map_or(false, |t| t.to_lowercase().contains(keyword))
+          && !item
+            .search_terms
+            .iter()
+            .any(|term| term.to_lowercase().contains(keyword))
         {
           return false;
         }
diff --git a/espanso-modulo/src/search/config.rs b/espanso-modulo/src/search/config.rs
index 69fc5421..38635fc5 100644
--- a/espanso-modulo/src/search/config.rs
+++ b/espanso-modulo/src/search/config.rs
@@ -58,6 +58,7 @@ pub struct SearchItem {
   pub id: String,
   pub label: String,
   pub trigger: Option<String>,
+  pub search_terms: Vec<String>,
 
   #[serde(default)]
   pub is_builtin: bool,
diff --git a/espanso-modulo/src/search/generator.rs b/espanso-modulo/src/search/generator.rs
index 98a9c887..497d69c5 100644
--- a/espanso-modulo/src/search/generator.rs
+++ b/espanso-modulo/src/search/generator.rs
@@ -28,6 +28,7 @@ pub fn generate(config: SearchConfig) -> types::Search {
       id: item.id,
       label: item.label,
       trigger: item.trigger,
+      search_terms: item.search_terms,
       is_builtin: item.is_builtin,
     })
     .collect();
diff --git a/espanso-modulo/src/sys/search/mod.rs b/espanso-modulo/src/sys/search/mod.rs
index de448369..6ed1efe6 100644
--- a/espanso-modulo/src/sys/search/mod.rs
+++ b/espanso-modulo/src/sys/search/mod.rs
@@ -26,6 +26,7 @@ pub mod types {
     pub id: String,
     pub label: String,
     pub trigger: Option<String>,
+    pub search_terms: Vec<String>,
     pub is_builtin: bool,
   }
 
diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml
index 65def50f..5a5a7450 100644
--- a/espanso/Cargo.toml
+++ b/espanso/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "espanso"
-version = "2.1.2-alpha"
+version = "2.1.3-alpha"
 authors = ["Federico Terzi <federicoterzi96@gmail.com>"]
 license = "GPL-3.0"
 description = "Cross-platform Text Expander written in Rust"
@@ -72,4 +72,16 @@ espanso-mac-utils = { path = "../espanso-mac-utils" }
 [target.'cfg(target_os="linux")'.dependencies]
 caps = "0.5.2"
 const_format = "0.2.14"
-regex = "1.4.3"
\ No newline at end of file
+regex = "1.4.3"
+
+[package.metadata.deb]
+maintainer = "Federico Terzi <federicoterzi96@gmail.com>"
+depends = "$auto, systemd, libxtst6, xclip, libnotify-bin, libxkbcommon0, libwxgtk3.0-gtk3-0v5"
+section = "utility"
+license-file = ["../LICENSE", "1"]
+
+[package.metadata.deb.variants.wayland]
+depends = "$auto, systemd, libnotify-bin, libxkbcommon0, libwxgtk3.0-gtk3-0v5"
+# TODO: once this issue [1] is fixed, we should create a variant for 
+# wayland to automatically run the setcap script.
+# [1]: https://github.com/mmstick/cargo-deb/issues/151
diff --git a/espanso/src/cli/service/mod.rs b/espanso/src/cli/service/mod.rs
index 6cf18fa1..b9dc7fc0 100644
--- a/espanso/src/cli/service/mod.rs
+++ b/espanso/src/cli/service/mod.rs
@@ -103,6 +103,8 @@ fn service_main(args: CliModuleArgs) -> i32 {
     stop_main(&paths);
     std::thread::sleep(std::time::Duration::from_millis(300));
     return start_main(&paths, &paths_overrides, sub_args);
+  } else {
+    eprintln!("Invalid usage, please run `espanso service --help` for more information.");
   }
 
   SERVICE_SUCCESS
diff --git a/espanso/src/cli/worker/engine/dispatch/executor/secure_input.rs b/espanso/src/cli/worker/engine/dispatch/executor/secure_input.rs
index 40ae47cf..4a1f911e 100644
--- a/espanso/src/cli/worker/engine/dispatch/executor/secure_input.rs
+++ b/espanso/src/cli/worker/engine/dispatch/executor/secure_input.rs
@@ -33,9 +33,7 @@ impl SecureInputManagerAdapter {
 
 impl SecureInputManager for SecureInputManagerAdapter {
   fn display_secure_input_troubleshoot(&self) -> anyhow::Result<()> {
-    // TODO: replace with actual URL
-    // TODO: in the future, this might be a self-contained WebView window
-    opener::open_browser("https://espanso.org/docs")?;
+    opener::open_browser("https://espanso.org/docs/next/troubleshooting/secure-input/")?;
     Ok(())
   }
 
diff --git a/espanso/src/cli/worker/engine/process/middleware/match_select.rs b/espanso/src/cli/worker/engine/process/middleware/match_select.rs
index 0f0b74ea..a4850c71 100644
--- a/espanso/src/cli/worker/engine/process/middleware/match_select.rs
+++ b/espanso/src/cli/worker/engine/process/middleware/match_select.rs
@@ -32,6 +32,7 @@ pub struct MatchSummary<'a> {
   pub id: i32,
   pub label: &'a str,
   pub tag: Option<&'a str>,
+  pub additional_search_terms: Vec<&'a str>,
   pub is_builtin: bool,
 }
 
@@ -65,6 +66,11 @@ impl<'a> MatchSelector for MatchSelectorAdapter<'a> {
           id: m.id.to_string(),
           label: clipped_label,
           tag: m.tag.map(String::from),
+          additional_search_terms: m
+            .additional_search_terms
+            .into_iter()
+            .map(String::from)
+            .collect(),
           is_builtin: m.is_builtin,
         }
       })
diff --git a/espanso/src/cli/worker/engine/process/middleware/render/extension/choice.rs b/espanso/src/cli/worker/engine/process/middleware/render/extension/choice.rs
index d2f245d2..8fe4ddff 100644
--- a/espanso/src/cli/worker/engine/process/middleware/render/extension/choice.rs
+++ b/espanso/src/cli/worker/engine/process/middleware/render/extension/choice.rs
@@ -49,6 +49,7 @@ fn convert_items(choices: &[espanso_render::extension::choice::Choice]) -> Vec<S
       id: choice.id.to_string(),
       label: choice.label.to_string(),
       tag: None,
+      additional_search_terms: vec![],
       is_builtin: false,
     })
     .collect()
diff --git a/espanso/src/cli/worker/match_cache.rs b/espanso/src/cli/worker/match_cache.rs
index 6c2423f8..e143436a 100644
--- a/espanso/src/cli/worker/match_cache.rs
+++ b/espanso/src/cli/worker/match_cache.rs
@@ -129,12 +129,14 @@ impl<'a> super::engine::process::middleware::match_select::MatchProvider<'a>
           id: m.id,
           label: m.description(),
           tag: m.cause_description(),
+          additional_search_terms: m.search_terms(),
           is_builtin: false,
         },
         MatchVariant::Builtin(m) => MatchSummary {
           id: m.id,
           label: m.label,
           tag: m.triggers.first().map(String::as_ref),
+          additional_search_terms: vec![],
           is_builtin: true,
         },
       })
diff --git a/espanso/src/gui/mod.rs b/espanso/src/gui/mod.rs
index 86ae5db2..2c1e998f 100644
--- a/espanso/src/gui/mod.rs
+++ b/espanso/src/gui/mod.rs
@@ -32,6 +32,7 @@ pub struct SearchItem {
   pub id: String,
   pub label: String,
   pub tag: Option<String>,
+  pub additional_search_terms: Vec<String>,
   pub is_builtin: bool,
 }
 
diff --git a/espanso/src/gui/modulo/search.rs b/espanso/src/gui/modulo/search.rs
index 163ef943..a55d525e 100644
--- a/espanso/src/gui/modulo/search.rs
+++ b/espanso/src/gui/modulo/search.rs
@@ -73,6 +73,7 @@ struct ModuloSearchItemConfig<'a> {
   id: &'a str,
   label: &'a str,
   trigger: Option<&'a str>,
+  search_terms: Vec<&'a str>,
   is_builtin: bool,
 }
 
@@ -84,6 +85,15 @@ fn convert_items(items: &[SearchItem]) -> Vec<ModuloSearchItemConfig> {
       id: &item.id,
       label: &item.label,
       trigger: item.tag.as_deref(),
+      search_terms: if item.additional_search_terms.is_empty() {
+        vec![]
+      } else {
+        item
+          .additional_search_terms
+          .iter()
+          .map(|term| term.as_str())
+          .collect()
+      },
       is_builtin: item.is_builtin,
     })
     .collect()
diff --git a/espanso/src/main.rs b/espanso/src/main.rs
index 97d42d93..d89dfd5b 100644
--- a/espanso/src/main.rs
+++ b/espanso/src/main.rs
@@ -360,7 +360,7 @@ For example, specifying 'email' is equivalent to 'match/email.yml'."#))
         .subcommand(restart_subcommand.clone())
         .subcommand(stop_subcommand.clone())
         .subcommand(status_subcommand.clone())
-        .about("Register and manage 'espanso' as a system service."),
+        .about("A collection of commands to manage the Espanso service (for example, enabling auto-start on system boot)."),
     )
     .subcommand(start_subcommand)
     .subcommand(restart_subcommand)
diff --git a/espanso/src/patch/patches/linux/thunderbird_x11.rs b/espanso/src/patch/patches/linux/thunderbird_x11.rs
index eeb366ab..8474bcb0 100644
--- a/espanso/src/patch/patches/linux/thunderbird_x11.rs
+++ b/espanso/src/patch/patches/linux/thunderbird_x11.rs
@@ -19,8 +19,6 @@
 
 use std::sync::Arc;
 
-use espanso_config::config::Backend;
-
 use crate::patch::patches::{PatchedConfig, Patches};
 use crate::patch::PatchDefinition;
 
@@ -34,8 +32,6 @@ pub fn patch() -> PatchDefinition {
         base,
         name,
         Patches {
-          paste_shortcut: Some(Some("CTRL+SHIFT+V".to_string())),
-          backend: Some(Backend::Clipboard),
           key_delay: Some(Some(15)),
           inject_delay: Some(Some(15)),
           ..Default::default()
diff --git a/scripts/publish_homebrew_version.sh b/scripts/publish_homebrew_version.sh
new file mode 100755
index 00000000..7c378aea
--- /dev/null
+++ b/scripts/publish_homebrew_version.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+set -e
+
+if [[ -z "$VERSION" ]]; then
+  echo "Missing target VERSION environment variable, please specify it"
+  exit 1
+fi
+
+# Removing the v suffix, if present
+VERSION=${VERSION#"v"}
+
+rm -Rf target/homebrew
+mkdir -p target/homebrew/artifacts
+
+echo "Targeting version $VERSION"
+echo "Downloading macOS artifacts"
+
+gh release download v$VERSION --pattern "Espanso-Mac*" --dir target/homebrew/artifacts
+
+echo "Reading artifacts hashes"
+INTEL_SHA=$(cat target/homebrew/artifacts/Espanso-Mac-Intel.zip.sha256.txt | awk -F ' ' '{print $1}')
+M1_SHA=$(cat target/homebrew/artifacts/Espanso-Mac-M1.zip.sha256.txt | awk -F ' ' '{print $1}')
+
+echo "Cloning tap repository"
+
+pushd target/homebrew
+git clone git@github.com:espanso/homebrew-espanso.git
+
+pushd homebrew-espanso
+echo "Rendering formula template"
+
+cat ../../../scripts/resources/macos/formula_template.rb | sed "s/{{{VERSION}}}/$VERSION/g" | \
+  sed "s/{{{INTEL_SHA}}}/$INTEL_SHA/g" | sed "s/{{{M1_SHA}}}/$M1_SHA/g" > ./Casks/espanso.rb
+
+echo "Committing version update"
+git add Casks/espanso.rb
+git commit -m "Version bump: $VERSION"
+
+echo "Pushing changes"
+git push
+
+echo "Done!"
\ No newline at end of file
diff --git a/scripts/resources/macos/formula_template.rb b/scripts/resources/macos/formula_template.rb
new file mode 100644
index 00000000..7a3e299e
--- /dev/null
+++ b/scripts/resources/macos/formula_template.rb
@@ -0,0 +1,19 @@
+cask "espanso" do
+  version "{{{VERSION}}}"
+
+  if Hardware::CPU.intel?
+    url "https://github.com/federico-terzi/espanso/releases/download/v#{version}/Espanso-Mac-Intel.zip"
+    sha256 "{{{INTEL_SHA}}}"
+  else
+    url "https://github.com/federico-terzi/espanso/releases/download/v#{version}/Espanso-Mac-M1.zip"
+    sha256 "{{{M1_SHA}}}"
+  end
+
+  name "Espanso"
+  desc "A Privacy-first, Cross-platform Text Expander"
+  homepage "https://espanso.org/"
+
+  app "Espanso.app"
+
+  zap trash: "~/Library/Caches/espanso"
+end
\ No newline at end of file
diff --git a/snapcraft.yaml b/snapcraft.yaml
index c69662a5..82b7b3ec 100644
--- a/snapcraft.yaml
+++ b/snapcraft.yaml
@@ -1,5 +1,5 @@
 name: espanso
-version: 2.1.2-alpha
+version: 2.1.3-alpha
 summary: A Cross-platform Text Expander written in Rust
 description: |
   espanso is a Cross-platform, Text Expander written in Rust.


Test patch: