diff --git a/options/custom/default.nix b/options/custom/default.nix
index 3d177bd..9b2928a 100644
--- a/options/custom/default.nix
+++ b/options/custom/default.nix
@@ -43,6 +43,7 @@ in {
     };
 
     lockscreen = mkOption {default = "hyprlock";};
+    menu = mkOption {default = "rofi";};
     wallpaper = mkOption {default = false;};
 
     browser = {
diff --git a/options/custom/desktops/niri/binds.nix b/options/custom/desktops/niri/binds.nix
index b805332..db4a866 100644
--- a/options/custom/desktops/niri/binds.nix
+++ b/options/custom/desktops/niri/binds.nix
@@ -10,6 +10,7 @@ with lib; let
 
   audio = config.home-manager.users.${config.custom.username}.home.file.".local/bin/audio".source;
   bash = "${pkgs.bash}/bin/bash";
+  bitwarden = "${pkgs.bitwarden-desktop}/bin/bitwarden";
   cat = "${pkgs.coreutils}/bin/cat";
   codium = "${config.home-manager.users.${config.custom.username}.programs.vscode.package}/bin/codium";
   ghostty = "${hm.programs.ghostty.package}/bin/ghostty";
@@ -29,13 +30,10 @@ with lib; let
   playerctl = "${pkgs.playerctl}/bin/playerctl";
   power = config.home-manager.users.${config.custom.username}.home.file.".local/bin/power".source;
   remote = config.home-manager.users.${config.custom.username}.home.file.".local/bin/remote".source;
-  rm = "${pkgs.coreutils}/bin/rm";
   steam = "${config.programs.steam.package}/bin/steam";
   swayosd-client = "${pkgs.swayosd}/bin/swayosd-client";
   virt-manager = "${config.programs.virt-manager.package}/bin/virt-manager";
-  walker = "${config.home-manager.users.${config.custom.username}.programs.walker.package}/bin/walker";
   waydroid = "${pkgs.waydroid}/bin/waydroid";
-  _1password = "${config.programs._1password-gui.package}/bin/1password";
   youtube-music = "${pkgs.youtube-music}/bin/youtube-music";
 in {
   options.custom.desktops.niri.binds = {
@@ -133,9 +131,9 @@ in {
             (key "M" "Mod" (spawn youtube-music))
             (key "Minus" "Mod" (spawn [swayosd-client "--output-volume" "lower"]))
             (key "O" "Mod" (spawn [loupe "/tmp/wallpaper.png"]))
-            (key "P" "Ctrl+Alt" (spawn [pkill "1password"]))
-            (key "P" "Mod" (spawn _1password))
-            (key "P" "Mod+Shift" (spawn [_1password "--quick-access"]))
+            (key "P" "Ctrl+Alt" (spawn [pkill "bitwarden"]))
+            (key "P" "Mod" (spawn [bash "-c" config.custom.menus.vault.show]))
+            (key "P" "Mod+Shift" (spawn bitwarden))
             (key "Q" "Mod" close-window)
             (key "R" "Mod" focus-window-or-workspace-down)
             (key "R" "Mod+Shift" move-window-down-or-to-workspace-down)
@@ -154,9 +152,9 @@ in {
             (key "T" "Mod" (spawn ghostty))
             (key "Tab" "Mod" switch-focus-between-floating-and-tiling)
             (key "Up" "Mod" (spawn [swayosd-client "--brightness" "raise"]))
-            (key "V" "Mod" (spawn [walker "--modules" "clipboard"]))
+            (key "V" "Mod" (spawn [bash "-c" config.custom.menus.clipboard.show]))
             (key "V" "Mod+Ctrl" (spawn vm))
-            (key "V" "Mod+Shift" (spawn [bash "-c" "${rm} ~/.cache/walker/clipboard.gob && ${notify-send} menu 'Clipboard cleared' --urgency low"]))
+            (key "V" "Mod+Shift" (spawn [bash "-c" config.custom.menus.clipboard.clear]))
             (key "W" "Mod" focus-window-or-workspace-up)
             (key "W" "Mod+Shift" move-window-up-or-to-workspace-up)
             (key "WheelScrollDown" "Mod" focus-window-or-workspace-down)
@@ -181,9 +179,9 @@ in {
             # TODO: Uncomment when fixed
             #// (key "Shift_L" "Mod" focus-workspace-previous)
             # TODO: Use "Super_L" when fixed
-            (key "Space" "Mod" (spawn walker))
+            (key "Space" "Mod" (spawn [bash "-c" config.custom.menus.show]))
             (key "Space" "Mod+Ctrl+Shift" (spawn networkmanager_dmenu))
-            (key "Space" "Mod+Shift" (spawn [walker "--modules" "search"]))
+            (key "Space" "Mod+Shift" (spawn [bash "-c" config.custom.menus.search.show]))
 
             # Media keys
             # https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h
diff --git a/options/custom/desktops/niri/default.nix b/options/custom/desktops/niri/default.nix
index d608200..d3081e2 100644
--- a/options/custom/desktops/niri/default.nix
+++ b/options/custom/desktops/niri/default.nix
@@ -38,9 +38,6 @@ in {
       services = {
         # Enable rootless Xwayland
         xwayland-satellite.enable = cfg.xwayland;
-
-        # Enable X11/Wayland clipboard sync
-        clipsync.enable = true;
       };
     };
 
diff --git a/options/custom/desktops/niri/misc.nix b/options/custom/desktops/niri/misc.nix
index 4b4db98..a846e7f 100644
--- a/options/custom/desktops/niri/misc.nix
+++ b/options/custom/desktops/niri/misc.nix
@@ -9,8 +9,8 @@ with lib; let
   hm = config.home-manager.users.${config.custom.username};
 
   audio = config.home-manager.users.${config.custom.username}.home.file.".local/bin/audio".source;
+  bash = "${pkgs.bash}/bin/bash";
   niri = "${config.programs.niri.package}/bin/niri";
-  rm = "${pkgs.coreutils}/bin/rm";
   sway-audio-idle-inhibit = "${pkgs.sway-audio-idle-inhibit}/bin/sway-audio-idle-inhibit";
   wallpaper = "${config.home-manager.users.${config.custom.username}.home.file.".local/bin/wallpaper".source}";
 in {
@@ -49,12 +49,10 @@ in {
           #!! Not executed in a shell
           # https://github.com/YaLTeR/niri/wiki/Configuration:-Key-Bindings#spawn
           # https://github.com/sodiboo/niri-flake/blob/main/docs.md#programsnirisettingsspawn-at-startup
-          spawn-at-startup = let
-            home = hm.home.homeDirectory;
-          in
+          spawn-at-startup =
             [
               {command = [audio "--init"];} # Enforce audio profile state
-              {command = [rm "${home}/.cache/walker/clipboard.gob"];} # Clear clipboard history
+              {command = [bash "-c" config.custom.menus.clipboard.clear];} # Clear clipboard history
               {command = [sway-audio-idle-inhibit];} # Inhibit while audio is playing
             ]
             ++ optionals config.custom.wallpaper [
diff --git a/options/custom/programs/anyrun.nix b/options/custom/menus/anyrun.nix
similarity index 88%
rename from options/custom/programs/anyrun.nix
rename to options/custom/menus/anyrun.nix
index f4a589c..eea7057 100644
--- a/options/custom/programs/anyrun.nix
+++ b/options/custom/menus/anyrun.nix
@@ -6,13 +6,11 @@
   ...
 }:
 with lib; let
-  cfg = config.custom.programs.anyrun;
+  cfg = config.custom.menus.anyrun;
 in {
-  options.custom.programs.anyrun.enable = mkOption {default = false;};
+  options.custom.menus.anyrun.enable = mkOption {default = false;};
 
   config.home-manager.users.${config.custom.username} = mkIf cfg.enable {
-    imports = [inputs.anyrun.homeManagerModules.default];
-
     # https://github.com/Kirottu/anyrun
     programs.anyrun = {
       enable = true;
diff --git a/options/custom/menus/default.nix b/options/custom/menus/default.nix
new file mode 100644
index 0000000..46b6149
--- /dev/null
+++ b/options/custom/menus/default.nix
@@ -0,0 +1,34 @@
+{
+  config,
+  lib,
+  ...
+}:
+with lib; let
+  cfg = config.custom.menus;
+in {
+  options.custom.menus = {
+    enable = mkOption {default = config.custom.full;};
+    show = mkOption {default = "";};
+
+    clipboard = {
+      clear = mkOption {default = "";};
+      show = mkOption {default = "";};
+    };
+
+    dmenu.show = mkOption {default = "";};
+    emoji.show = mkOption {default = "";};
+    network.show = mkOption {default = "";};
+    search.show = mkOption {default = "";};
+    vault.show = mkOption {default = "";};
+  };
+
+  config = mkIf cfg.enable {
+    custom.menus = {
+      anyrun.enable = config.custom.menu == "anyrun";
+      fuzzel.enable = config.custom.menu == "fuzzel";
+      rofi.enable = config.custom.menu == "rofi";
+      walker.enable = config.custom.menu == "walker";
+      wofi.enable = config.custom.menu == "wofi";
+    };
+  };
+}
diff --git a/options/custom/programs/fuzzel.nix b/options/custom/menus/fuzzel.nix
similarity index 87%
rename from options/custom/programs/fuzzel.nix
rename to options/custom/menus/fuzzel.nix
index 50f4da4..5b9f5d0 100644
--- a/options/custom/programs/fuzzel.nix
+++ b/options/custom/menus/fuzzel.nix
@@ -4,9 +4,9 @@
   ...
 }:
 with lib; let
-  cfg = config.custom.programs.fuzzel;
+  cfg = config.custom.menus.fuzzel;
 in {
-  options.custom.programs.fuzzel.enable = mkOption {default = false;};
+  options.custom.menus.fuzzel.enable = mkOption {default = false;};
 
   config.home-manager.users.${config.custom.username} = mkIf cfg.enable {
     # https://codeberg.org/dnkl/fuzzel
diff --git a/options/custom/menus/rofi/clipboard.sh b/options/custom/menus/rofi/clipboard.sh
new file mode 100644
index 0000000..612ac0a
--- /dev/null
+++ b/options/custom/menus/rofi/clipboard.sh
@@ -0,0 +1,50 @@
+#! /usr/bin/env bash
+
+TMPDIR=/tmp/cliphist
+
+# Clean up tmp dir
+rm -rf "$TMPDIR"
+
+# TODO: Add keybinds
+# https://github.com/lbonn/rofi/blob/wayland/doc/rofi-script.5.markdown#environment
+case "$ROFI_RETV" in
+  # List entries
+  0)
+    mkdir -p "$TMPDIR"
+
+    # Parse over clipboard
+    cliphist list | while read -r line; do
+      # Skip over HTML elements
+      # https://github.com/sentriz/cliphist/commit/95c193604fce7c5ec094ff9bf1c62cc6f5395750
+      if [[ "$line" == *meta\ http-equiv=* ]]; then
+        continue
+      fi
+
+      # Isolate index and entry name
+      id="$(cut -f 1 - <<< "$line")"
+      name="$(cut -f 2 - <<< "$line")"
+
+      # Check for image entries
+      if [[ "$line" =~ ^([0-9]+)[[:space:]]+\[\[\ binary.*(jpg|jpeg|png|bmp) ]]; then
+        # Set image extension and icon path
+        extension="${BASH_REMATCH[2]}"
+        icon="$TMPDIR/$id.$extension"
+
+        # Write decoded image to tmp dir
+        if ! [[ -f "$icon" ]]; then
+          cliphist decode "$id" > "$icon"
+        fi
+
+        # Pass entry to rofi
+        printf '%s\x0icon\x1f%s\x1finfo\x1f%s\n' "$name" "$icon" "$id"
+      else
+        printf '%s\x0info\x1f%s\n' "$name" "$id"
+      fi
+    done
+    ;;
+  # Select entry
+  1)
+    # Decode from env var and copy to clipboard
+    cliphist decode "$ROFI_INFO" | wl-copy
+    ;;
+esac
diff --git a/options/custom/menus/rofi/custom.rasi b/options/custom/menus/rofi/custom.rasi
new file mode 100644
index 0000000..aad9c24
--- /dev/null
+++ b/options/custom/menus/rofi/custom.rasi
@@ -0,0 +1,85 @@
+/***
+https://github.com/lbonn/rofi/blob/wayland/doc/rofi-theme.5.markdown
+https://github.com/newmanls/rofi-themes-collection/blob/master/themes/rounded-common.rasi
+https://github.com/newmanls/rofi-themes-collection/blob/master/themes/rounded-pink-dark.rasi
+***/
+
+* {
+  background-color: transparent;
+  font: 'sans-serif 14';
+  margin: 0;
+  padding: 0;
+  spacing: 0;
+  text-color: #93a1a1;
+}
+
+window {
+  background-color: #002b36;
+  border-color: #073642;
+  border-radius: 30px;
+  location: north;
+  width: 750;
+  y-offset: calc(50% - 25% / 2);
+}
+
+mainbox {
+  padding: 4px;
+}
+
+inputbar {
+  background-color: #073642;
+  border-color: #d33682;
+  border-radius: 30px;
+  padding: 8px 8px 8px 4px;
+  spacing: 4px;
+}
+
+prompt {
+  font: 'monospace 16';
+  padding: 4px 4px 4px 8px;
+  text-color: #93a1a1;
+  vertical-align: 0.5;
+}
+
+entry {
+  font: 'monospace 18';
+  placeholder-color: #586e7580;
+  vertical-align: 0.5;
+}
+
+message {
+  padding: 8px 8px 4px 8px;
+}
+
+textbox {
+  font: 'sans-serif 10';
+}
+
+listview {
+  background-color: transparent;
+  columns: 1;
+  fixed-height: false;
+  lines: 5;
+  margin: 4px 0 0;
+}
+
+element {
+  border-radius: 30px;
+  padding: 12px;
+  spacing: 8px;
+}
+
+element selected normal,
+element selected active {
+  background-color: #586e7540;
+}
+
+element-icon {
+  size: 32px;
+  vertical-align: 0.5;
+}
+
+element-text {
+  text-color: inherit;
+  vertical-align: 0.5;
+}
diff --git a/options/custom/menus/rofi/default.nix b/options/custom/menus/rofi/default.nix
new file mode 100644
index 0000000..779da74
--- /dev/null
+++ b/options/custom/menus/rofi/default.nix
@@ -0,0 +1,131 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.custom.menus.rofi;
+  hm = config.home-manager.users.${config.custom.username};
+
+  cliphist = getExe hm.services.cliphist.package;
+  networkmanager_dmenu = getExe pkgs.networkmanager_dmenu;
+  notify-send = getExe pkgs.libnotify;
+  pkill = getExe' pkgs.procps "pkill";
+  rofi = getExe hm.programs.rofi.finalPackage;
+  rofi-rbw = getExe pkgs.rofi-rbw;
+  rofimoji = getExe pkgs.rofimoji;
+in {
+  options.custom.menus.rofi = {
+    enable = mkOption {default = false;};
+  };
+
+  config = mkIf cfg.enable {
+    custom = mkIf (config.custom.menu == "rofi") {
+      menus = let
+        quit = "${pkill} --exact rofi";
+      in {
+        show = "${quit} || ${rofi} -show combi -show-icons";
+
+        clipboard = {
+          show = "${quit} || ${rofi} -show clipboard -show-icons";
+          clear = "${cliphist} wipe && ${notify-send} '> cliphist' 'Clipboard cleared' --urgency low";
+        };
+
+        dmenu.show = "${quit} || ${rofi} -dmenu";
+        emoji.show = "${quit} || ${rofimoji} --prompt 󰞅";
+        network.show = "${quit} || ${rofi} -dmenu -p ";
+        search.show = "";
+        vault.show = "${quit} || ${rofi-rbw} --prompt ";
+      };
+
+      services = {
+        cliphist.enable = true;
+      };
+    };
+
+    environment.systemPackages = [
+      pkgs.rofimoji # https://github.com/fdw/rofimoji
+    ];
+
+    home-manager.sharedModules = [
+      {
+        #!! Creates package derivation
+        #?? hm.programs.rofi.finalPackage
+        # https://github.com/davatorium/rofi
+        programs.rofi = {
+          enable = true;
+
+          # https://github.com/lbonn/rofi
+          # https://github.com/lbonn/rofi/tree/wayland/doc
+          package = pkgs.rofi-wayland; # Wayland fork
+
+          plugins = with pkgs; [
+            rofi-calc # https://github.com/svenstaro/rofi-calc
+          ];
+
+          #?? rofi-theme-selector
+          theme = "custom";
+
+          # https://github.com/lbonn/rofi/blob/wayland/CONFIG.md
+          # https://github.com/lbonn/rofi/blob/wayland/doc/rofi.1.markdown
+          # https://www.nerdfonts.com/cheat-sheet
+          extraConfig = {
+            combi-hide-mode-prefix = true;
+            combi-modes = ["drun" "run" "calc"];
+            cycle = false;
+            display-calc = "󱖦";
+            display-clipboard = "󰅌";
+            display-combi = "";
+            display-dmenu = "󰗧";
+            display-drun = "";
+            display-keys = "";
+            display-run = "";
+            display-ssh = "";
+            drun-display-format = "{name}"; # Display only names
+            drun-match-fields = "name"; # Disable matching of invisible desktop attributes
+            matching = "prefix"; # Match beginning of words
+
+            # https://github.com/lbonn/rofi/blob/wayland/doc/rofi.1.markdown#available-modes
+            modes = [
+              "calc"
+              "clipboard"
+              "combi"
+              "drun"
+              "keys"
+              "run"
+              "ssh"
+            ];
+          };
+        };
+
+        xdg.configFile = {
+          # https://github.com/lbonn/rofi/blob/wayland/doc/rofi-theme.5.markdown
+          "rofi/custom.rasi".text = ''
+            ${readFile ./custom.rasi}
+
+            window, inputbar {
+              border: ${toString config.custom.border}px;
+            }
+          '';
+
+          # https://github.com/lbonn/rofi/blob/wayland/doc/rofi-script.5.markdown
+          # https://github.com/sentriz/cliphist?tab=readme-ov-file#picker-examples
+          "rofi/scripts/clipboard" = {
+            #// source = getExe' hm.services.cliphist.package "cliphist-rofi-img";
+
+            # HACK: Cannot easily hide index via display-columns without dmenu mode
+            # https://github.com/sentriz/cliphist/issues/130
+            # https://github.com/davatorium/rofi/discussions/1993#discussioncomment-9971764
+            # https://github.com/sentriz/cliphist/pull/124
+            source = getExe (pkgs.writeShellApplication {
+              name = "clipboard.sh";
+              runtimeInputs = with pkgs; [coreutils gnused wl-clipboard];
+              text = readFile ./clipboard.sh;
+            });
+          };
+        };
+      }
+    ];
+  };
+}
diff --git a/options/custom/menus/walker/default.nix b/options/custom/menus/walker/default.nix
new file mode 100644
index 0000000..02ac1c2
--- /dev/null
+++ b/options/custom/menus/walker/default.nix
@@ -0,0 +1,276 @@
+{
+  config,
+  lib,
+  inputs,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.custom.menus.walker;
+  hm = config.home-manager.users.${config.custom.username};
+
+  notify-send = getExe pkgs.libnotify;
+  rm = getExe' pkgs.coreutils "rm";
+  walker = getExe hm.programs.walker.package;
+in {
+  options.custom.menus.walker = {
+    enable = mkOption {default = false;};
+    icons = mkOption {default = ["edit-find" "terminal"];};
+  };
+
+  config = mkIf cfg.enable {
+    custom = {
+      menus = mkIf (config.custom.menu == "walker") {
+        show = walker;
+
+        clipboard = {
+          show = "${walker} --modules clipboard";
+          clear = "${rm} ~/.cache/walker/clipboard.gob && ${notify-send} '> walker' 'Clipboard cleared' --urgency low";
+        };
+
+        dmenu.show = "${walker} --modules dmenu";
+        emoji.show = "${walker} --modules emojis";
+        search.show = "${walker} --modules search";
+        vault.show = "";
+      };
+
+      services = {
+        clipnotify.enable = true;
+      };
+    };
+
+    home-manager.sharedModules = [
+      {
+        # https://github.com/abenz1267/walker
+        # https://github.com/abenz1267/walker?tab=readme-ov-file#building-from-source
+        # https://github.com/abenz1267/walker/blob/master/nix/hm-module.nix
+        programs.walker = {
+          enable = true;
+
+          #!! Service must be restarted for changes to take effect
+          #?? systemctl --user restart walker.service
+          runAsService = true;
+
+          # https://github.com/abenz1267/walker/wiki/Basic-Configuration
+          # https://github.com/abenz1267/walker/blob/master/internal/config/config.default.toml
+          config = {
+            activation_mode.disabled = true; # Key chords
+            close_when_open = true;
+            disable_click_to_close = true;
+            force_keyboard_focus = true;
+            hotreload_theme = true;
+            ignore_mouse = true;
+
+            list = {
+              placeholder = "";
+            };
+
+            search = {
+              placeholder = "";
+              #// resume_last_query = true;
+            };
+
+            # https://github.com/abenz1267/walker/wiki/Modules
+            # https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/tree/master/Papirus/64x64
+            disabled = [
+              "ai"
+              "commands"
+              "custom_commands"
+              "finder"
+              "websearch" # Replaced by custom plugin
+              "windows"
+            ];
+
+            builtins = let
+            in {
+              applications = {
+                actions.enabled = false;
+                hide_without_query = true;
+                placeholder = "";
+                show_generic = false;
+                switcher_only = false;
+              };
+
+              bookmarks = {
+                icon = "user-bookmarks";
+                placeholder = "";
+                prefix = "b ";
+                switcher_only = false;
+              };
+
+              calc = {
+                icon = "accessories-calculator";
+                min_chars = 1;
+                placeholder = "";
+                prefix = "=";
+                show_icon_when_single = true;
+                switcher_only = false;
+              };
+
+              clipboard = {
+                max_entries = 50;
+                placeholder = "";
+                switcher_only = true;
+              };
+
+              dmenu = {
+                keep_sort = true;
+                placeholder = "Input";
+                switcher_only = true;
+              };
+
+              emojis = {
+                placeholder = "";
+                prefix = "`";
+                switcher_only = false;
+              };
+
+              finder = {
+                icon = "filetypes";
+                placeholder = "";
+                prefix = "//";
+                show_icon_when_single = true;
+                switcher_only = false;
+              };
+
+              runner = {
+                icon = "utilities-x-terminal";
+                placeholder = "";
+                prefix = ">";
+                show_icon_when_single = true;
+                switcher_only = false;
+              };
+
+              ssh = {
+                icon = "folder-remote-symbolic";
+                placeholder = "";
+                prefix = "ssh ";
+                show_icon_when_single = true;
+                switcher_only = false;
+              };
+
+              switcher = {
+                icon = "application-default-icon";
+                prefix = "/";
+                show_icon_when_single = true;
+              };
+
+              symbols = {
+                placeholder = "";
+                prefix = "sym ";
+                switcher_only = false;
+              };
+
+              translation = {
+                icon = "translator";
+                placeholder = "";
+                prefix = "tr ";
+                switcher_only = false;
+              };
+
+              websearch = {
+                placeholder = "system-search";
+                switcher_only = false;
+                entries = [{}];
+              };
+            };
+
+            # TODO: Keybinds
+            # https://github.com/abenz1267/walker/wiki/Keybinds
+
+            # https://github.com/abenz1267/walker/wiki/Plugins
+            plugins = [
+              {
+                # Search engines by keyword prefix
+                name = "search";
+                placeholder = "";
+                show_icon_when_single = true;
+                switcher_only = false;
+
+                src = "${pkgs.writeShellApplication {
+                  name = "search";
+                  text = readFile ./search.sh;
+                  runtimeInputs = with pkgs; [coreutils jq xdg-utils];
+                }}/bin/search '%TERM%'";
+              }
+            ];
+          };
+
+          # https://github.com/abenz1267/walker/wiki/Theming
+          theme = {
+            style = ''
+              #box {
+                border: ${toString config.custom.border}px #073642 solid;
+                font: larger ${config.custom.settings.fonts.sans-serif};
+              }
+
+              ${readFile ./style.css}
+            '';
+
+            # https://github.com/abenz1267/walker/blob/master/internal/config/layout.default.toml
+            layout.ui.window = let
+              w = 750;
+              h = 300;
+            in {
+              width = w;
+              height = h;
+
+              box = {
+                h_align = "fill";
+                width = -1;
+                height = -1;
+
+                scroll = {
+                  h_align = "fill";
+                  h_scrollbar_policy = "external";
+                  v_scrollbar_policy = "external";
+
+                  list = {
+                    width = -1;
+                    height = -1;
+                    min_width = -1;
+                    min_height = -1;
+                    max_width = w;
+                    max_height = h;
+
+                    item = {
+                      text = {
+                        sub = {
+                          hide = true; # Subtext
+                        };
+                      };
+                    };
+                  };
+                };
+              };
+            };
+          };
+        };
+      }
+    ];
+
+    # # HACK: Create theme files for module prompt icons
+    # #?? MODULE.theme = "icon-ICON"
+    # # https://github.com/abenz1267/walker/blob/bb584eab3b0cc48ebfbac1a5da019864d74781c4/nix/hm-module.nix#L86
+    # xdg.configFile = listToAttrs (flatten (forEach cfg.icons (
+    #   icon: [
+    #     {
+    #       name = "walker/themes/icon-${icon}.css";
+    #       value = {text = hm.programs.walker.theme.style;};
+    #     }
+    #     {
+    #       name = "walker/themes/icon-${icon}.json";
+    #       value = {
+    #         text = builtins.toJSON (recursiveUpdate hm.programs.walker.theme.layout {
+    #           ui.window.box.search.prompt.icon = icon;
+    #         });
+    #       };
+    #     }
+    #   ]
+    # )));
+
+    # HACK: Allow child processes to live, otherwise applications launched through service are killed on stop
+    # https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html#KillMode=
+    systemd.user.services.walker.Service.KillMode = "process";
+  };
+}
diff --git a/options/custom/programs/walker/search.sh b/options/custom/menus/walker/search.sh
similarity index 100%
rename from options/custom/programs/walker/search.sh
rename to options/custom/menus/walker/search.sh
diff --git a/options/custom/programs/walker/style.css b/options/custom/menus/walker/style.css
similarity index 100%
rename from options/custom/programs/walker/style.css
rename to options/custom/menus/walker/style.css
diff --git a/options/custom/programs/wofi.nix b/options/custom/menus/wofi.nix
similarity index 91%
rename from options/custom/programs/wofi.nix
rename to options/custom/menus/wofi.nix
index 5f574f6..609bf05 100644
--- a/options/custom/programs/wofi.nix
+++ b/options/custom/menus/wofi.nix
@@ -4,9 +4,9 @@
   ...
 }:
 with lib; let
-  cfg = config.custom.programs.wofi;
+  cfg = config.custom.menus.wofi;
 in {
-  options.custom.programs.wofi.enable = mkOption {default = false;};
+  options.custom.menus.wofi.enable = mkOption {default = false;};
 
   config.home-manager.users.${config.custom.username} = mkIf cfg.enable {
     # https://hg.sr.ht/~scoopta/wofi
diff --git a/options/custom/programs/bitwarden-menu.nix b/options/custom/programs/bitwarden-menu.nix
index d48c1c2..4f31101 100644
--- a/options/custom/programs/bitwarden-menu.nix
+++ b/options/custom/programs/bitwarden-menu.nix
@@ -1,31 +1,46 @@
 {
   config,
   lib,
+  pkgs,
   ...
 }:
 with lib; let
-  wofi = "${config.home-manager.users.${config.custom.username}.programs.wofi.package}/bin/wofi";
-
   cfg = config.custom.programs.bitwarden-menu;
+  hm = config.home-manager.users.${config.custom.username};
+
+  walker = getExe hm.programs.walker.package;
 in {
-  options.custom.programs.bitwarden-menu.enable = mkOption {default = false;};
+  options.custom.programs.bitwarden-menu = {
+    enable = mkOption {default = false;};
+  };
 
-  config.home-manager.users.${config.custom.username} = mkIf cfg.enable {
+  config = {
     # https://github.com/firecat53/bitwarden-menu
-    #!! Options not available, files written directly
-    # https://github.com/firecat53/bitwarden-menu/blob/main/docs/configure.md
-    xdg.configFile."bwm/config.ini".text = ''
-      [dmenu]
-      dmenu_command = ${wofi} --dmenu
+    environment.systemPackages = with pkgs; [
+      bitwarden-cli
+      bitwarden-menu
+    ];
 
-      [dmenu_passphrase]
-      obscure = True
+    home-manager.sharedModules = mkIf cfg.enable [
+      {
+        # TODO: Check for official options
+        # https://github.com/firecat53/bitwarden-menu/blob/main/docs/configure.md
+        xdg.configFile."bwm/config.ini".text = generators.toINI {} {
+          dmenu = {
+            dmenu_command = "${walker} --dmenu --forceprint";
+          };
 
-      # FIXME: Login options taking effect
-      [vault]
-      server = https://vault.bitwarden.com
-      twofactor = 0
-      session_timeout_min = 720
-    '';
+          dmenu_passphrase = {
+            obscure = true;
+          };
+
+          vault = {
+            server_1 = "https://vault.${config.custom.domain}";
+            login_1 = "${config.custom.username}@${config.custom.domain}";
+            twofactor_1 = 0;
+          };
+        };
+      }
+    ];
   };
 }
diff --git a/options/custom/programs/networkmanager-dmenu.nix b/options/custom/programs/networkmanager-dmenu.nix
index 831efae..bb3ea98 100644
--- a/options/custom/programs/networkmanager-dmenu.nix
+++ b/options/custom/programs/networkmanager-dmenu.nix
@@ -1,26 +1,36 @@
 {
   config,
   lib,
+  pkgs,
   ...
 }:
 with lib; let
-  menu = config.home-manager.users.${config.custom.username}.home.file.".local/bin/menu".source;
-
   cfg = config.custom.programs.networkmanager-dmenu;
+
+  bash = getExe pkgs.bash;
 in {
   options.custom.programs.networkmanager-dmenu.enable = mkOption {default = false;};
 
-  config.home-manager.users.${config.custom.username} = mkIf cfg.enable {
+  config = mkIf cfg.enable {
     # https://github.com/firecat53/networkmanager-dmenu
-    # https://github.com/firecat53/networkmanager-dmenu/blob/main/config.ini.example
-    #!! Option not available, files written directly
-    xdg.configFile."networkmanager-dmenu/config.ini".text = ''
-      [dmenu]
-      compact = true
-      dmenu_command = ${menu} input
-      active_chars = 
-      wifi_icons = 󰤯󰤟󰤢󰤥󰤨
-      format = {icon}    {name}
-    '';
+    environment.systemPackages = [pkgs.networkmanager_dmenu];
+
+    home-manager.users.${config.custom.username} = {
+      # https://github.com/firecat53/networkmanager-dmenu/blob/main/config.ini.example
+      #!! Option not available, files written directly
+      xdg.configFile."networkmanager-dmenu/config.ini".text = ''
+        [dmenu]
+        compact = true
+        dmenu_command = ${bash} -c '${config.custom.menus.network.show}'
+        list_saved = true
+        active_chars = 
+        highlight = true
+        wifi_icons = 󰤯󰤟󰤢󰤥󰤨
+        format = {icon}    {name}
+
+        [dmenu_passphrase]
+        obscure = true
+      '';
+    };
   };
 }
diff --git a/options/custom/programs/rbw.nix b/options/custom/programs/rbw.nix
index 240e909..b1f1a18 100644
--- a/options/custom/programs/rbw.nix
+++ b/options/custom/programs/rbw.nix
@@ -7,21 +7,41 @@
 with lib; let
   cfg = config.custom.programs.rbw;
 in {
-  options.custom.programs.rbw.enable = mkOption {default = false;};
+  options.custom.programs.rbw = {
+    enable = mkOption {default = false;};
+  };
 
-  config.home-manager.users.${config.custom.username} = mkIf cfg.enable {
-    # https://github.com/doy/rbw
-    #!! Register with API secrets before using
-    #?? rbw register
-    #?? rbw login
-    programs.rbw = {
-      enable = true;
+  config = {
+    # https://github.com/fdw/rofi-rbw
+    environment.systemPackages = [pkgs.rofi-rbw];
 
-      # https://github.com/doy/rbw?tab=readme-ov-file#configuration
-      settings = {
-        email = "myned@bjork.tech";
-        pinentry = pkgs.pinentry-gnome3;
-      };
-    };
+    home-manager.sharedModules = mkIf cfg.enable [
+      {
+        # https://github.com/doy/rbw
+        #!! Register with API secrets before using
+        #?? rbw register
+        #?? rbw login
+        programs.rbw = {
+          enable = true;
+
+          # https://github.com/doy/rbw?tab=readme-ov-file#configuration
+          settings = {
+            base_url = "https://vault.${config.custom.domain}";
+            email = "${config.custom.username}@${config.custom.domain}";
+            pinentry = pkgs.pinentry-gnome3;
+          };
+        };
+
+        # TODO: Enable input emulation when merged (uinput.enable?)
+        # https://github.com/NixOS/nixpkgs/pull/303745
+        # https://github.com/fdw/rofi-rbw?tab=readme-ov-file#configuration
+        xdg.configFile = {
+          "rofi-rbw.rc".text = ''
+            action=copy
+            selector=${config.custom.menu}
+          '';
+        };
+      }
+    ];
   };
 }
diff --git a/options/custom/programs/rofi-rbw.nix b/options/custom/programs/rofi-rbw.nix
deleted file mode 100644
index 61e88d8..0000000
--- a/options/custom/programs/rofi-rbw.nix
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  config,
-  lib,
-  ...
-}:
-with lib; let
-  cfg = config.custom.programs.rofi-rbw;
-in {
-  options.custom.programs.rofi-rbw.enable = mkOption {default = false;};
-
-  config.home-manager.users.${config.custom.username} = mkIf cfg.enable {
-    # https://github.com/fdw/rofi-rbw
-    #!! Options not available, files written directly
-    # https://github.com/fdw/rofi-rbw?tab=readme-ov-file#configuration
-    # TODO: Enable input emulation when merged (uinput.enable?)
-    # https://github.com/NixOS/nixpkgs/pull/303745
-    xdg.configFile."rofi-rbw.rc".text = ''
-      action=copy
-    '';
-  };
-}
diff --git a/options/custom/programs/rofi.nix b/options/custom/programs/rofi.nix
deleted file mode 100644
index 20487c7..0000000
--- a/options/custom/programs/rofi.nix
+++ /dev/null
@@ -1,182 +0,0 @@
-{
-  config,
-  lib,
-  pkgs,
-  ...
-}:
-with lib; let
-  cfg = config.custom.programs.rofi;
-in {
-  options.custom.programs.rofi.enable = mkOption {default = false;};
-
-  config.home-manager.users.${config.custom.username} = mkIf cfg.enable {
-    #!! Creates package derivation
-    #?? config.home-manager.users.${config.custom.username}.programs.rofi.finalPackage
-    # https://github.com/lbonn/rofi
-    programs.rofi = {
-      enable = true;
-      package = pkgs.rofi-wayland; # Wayland fork
-
-      # TODO: Look into rofi plugins
-      plugins = with pkgs; [
-        rofi-rbw # Bitwarden
-        rofimoji # Character picker
-
-        # TODO: Remove when rofi v1.7.6 released
-        # Build against rofi-wayland due to ABI incompatibility with upstream
-        # https://github.com/lbonn/rofi/issues/96
-        # https://github.com/NixOS/nixpkgs/issues/298539
-        (rofi-calc.override {rofi-unwrapped = rofi-wayland-unwrapped;}) # Calculator
-        (rofi-top.override {rofi-unwrapped = rofi-wayland-unwrapped;}) # System monitor
-      ];
-
-      #?? rofi-theme-selector
-      theme = "custom";
-      font = "${config.custom.settings.fonts.monospace} 16";
-
-      # https://github.com/davatorium/rofi/blob/next/CONFIG.md
-      extraConfig = {
-        modi = "drun,run,calc";
-        matching = "prefix"; # Match beginning of words
-        drun-display-format = "{name}"; # Display only names
-        drun-match-fields = "name"; # Disable matching of invisible desktop attributes
-      };
-    };
-
-    # https://github.com/davatorium/rofi/blob/next/doc/rofi-theme.5.markdown
-    # https://github.com/davatorium/rofi/blob/next/themes/paper-float.rasi
-    # TODO: Clean up theme
-    home.file.".config/rofi/custom.rasi".text = ''
-      * {
-          background: #073642ff;
-          alternate:  #002b36ff;
-          text: #eee8d5ff;
-          accent:  #d33682ff;
-
-          spacing: 2;
-          text-color: @text;
-          background-color: #00000000;
-          border-color: @accent;
-          anchor: north;
-          location: center;
-      }
-      window {
-          transparency: "real";
-          background-color: #00000000;
-          border: 0;
-          padding: 0% 0% 1em 0%;
-          x-offset: 0;
-          y-offset: -10%;
-      }
-      mainbox {
-          padding: 0px;
-          border: 0;
-          spacing: 1%;
-      }
-      message {
-          border: 2px;
-          padding: 1em;
-          background-color: @background;
-          text-color: @text;
-      }
-      textbox normal {
-          text-color: @text;
-          padding: 0;
-          border: 0;
-      }
-      listview {
-          fixed-height: 1;
-          border: 2px;
-          padding: 1em;
-          reverse: false;
-
-          columns: 1;
-          background-color: @background;
-      }
-      element {
-          border: 0;
-          padding: 2px;
-          highlight: bold ;
-      }
-      element-text {
-          background-color: inherit;
-          text-color:       inherit;
-      }
-      element normal.normal {
-          text-color: @text;
-          background-color: @background;
-      }
-      element normal.urgent {
-          text-color: @text;
-          background-color: @background;
-      }
-      element normal.active {
-          text-color: @text;
-          background-color: @background;
-      }
-      element selected.normal {
-          text-color: @text;
-          background-color: @accent;
-      }
-      element selected.urgent {
-          text-color: @text;
-          background-color: @accent;
-      }
-      element selected.active {
-          text-color: @text;
-          background-color: @accent;
-      }
-      element alternate.normal {
-          text-color: @text;
-          background-color: @alternate;
-      }
-      element alternate.urgent {
-          text-color: @text;
-          background-color: @alternate;
-      }
-      element alternate.active {
-          text-color: @text;
-          background-color: @alternate;
-      }
-      scrollbar {
-          border: 0;
-          padding: 0;
-      }
-      inputbar {
-          spacing: 0;
-          border: 2px;
-          padding: 0.5em 1em;
-          background-color: @background;
-          index: 0;
-      }
-      inputbar normal {
-          foreground-color: @text;
-          background-color: @background;
-      }
-      mode-switcher {
-          border: 2px;
-          padding: 0.5em 1em;
-          background-color: @background;
-          index: 10;
-      }
-      button selected {
-          text-color: @accent;
-      }
-      inputbar {
-          children:   [ prompt,textbox-prompt-colon,entry,case-indicator ];
-      }
-      textbox-prompt-colon {
-          expand:     false;
-          str:        ":";
-          margin:     0px 0.3em 0em 0em ;
-          text-color: @text;
-      }
-      error-message {
-          border: 2px;
-          padding: 1em;
-          background-color: @background;
-          text-color: @text;
-      }
-    '';
-  };
-}
diff --git a/options/custom/programs/walker/default.nix b/options/custom/programs/walker/default.nix
deleted file mode 100644
index aebed52..0000000
--- a/options/custom/programs/walker/default.nix
+++ /dev/null
@@ -1,250 +0,0 @@
-{
-  config,
-  lib,
-  inputs,
-  pkgs,
-  ...
-}:
-with lib; let
-  cfg = config.custom.programs.walker;
-  hm = config.home-manager.users.${config.custom.username};
-in {
-  options.custom.programs.walker = {
-    enable = mkOption {default = false;};
-    icons = mkOption {default = ["edit-find" "terminal"];};
-  };
-
-  config.home-manager.users.${config.custom.username} = mkIf cfg.enable {
-    imports = [inputs.walker.homeManagerModules.default];
-
-    # https://github.com/abenz1267/walker
-    # https://github.com/abenz1267/walker?tab=readme-ov-file#building-from-source
-    # https://github.com/abenz1267/walker/blob/master/nix/hm-module.nix
-    programs.walker = {
-      enable = true;
-
-      #!! Service must be restarted for changes to take effect
-      #?? systemctl --user restart walker.service
-      runAsService = true;
-
-      # https://github.com/abenz1267/walker/wiki/Basic-Configuration
-      # https://github.com/abenz1267/walker/blob/master/internal/config/config.default.toml
-      config = {
-        activation_mode.disabled = true; # Key chords
-        close_when_open = true;
-        disable_click_to_close = true;
-        force_keyboard_focus = true;
-        hotreload_theme = true;
-        ignore_mouse = true;
-
-        list = {
-          placeholder = "";
-        };
-
-        search = {
-          placeholder = "";
-          #// resume_last_query = true;
-        };
-
-        # https://github.com/abenz1267/walker/wiki/Modules
-        # https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/tree/master/Papirus/64x64
-        disabled = [
-          "ai"
-          "commands"
-          "custom_commands"
-          "finder"
-          "websearch" # Replaced by custom plugin
-          "windows"
-        ];
-
-        builtins = let
-        in {
-          applications = {
-            actions.enabled = false;
-            hide_without_query = true;
-            placeholder = "";
-            show_generic = false;
-            switcher_only = false;
-          };
-
-          bookmarks = {
-            icon = "user-bookmarks";
-            placeholder = "";
-            prefix = "b";
-            switcher_only = false;
-          };
-
-          calc = {
-            icon = "accessories-calculator";
-            min_chars = 1;
-            placeholder = "";
-            prefix = "=";
-            show_icon_when_single = true;
-            switcher_only = false;
-          };
-
-          clipboard = {
-            max_entries = 50;
-            placeholder = "";
-            switcher_only = true;
-          };
-
-          dmenu = {
-            keep_sort = true;
-            placeholder = "Input";
-            switcher_only = true;
-          };
-
-          emojis = {
-            placeholder = "";
-            prefix = "`";
-            switcher_only = false;
-          };
-
-          finder = {
-            icon = "filetypes";
-            placeholder = "";
-            prefix = "//";
-            show_icon_when_single = true;
-            switcher_only = false;
-          };
-
-          runner = {
-            icon = "utilities-x-terminal";
-            placeholder = "";
-            prefix = ">";
-            show_icon_when_single = true;
-            switcher_only = false;
-          };
-
-          ssh = {
-            icon = "folder-remote-symbolic";
-            placeholder = "";
-            prefix = "ssh";
-            show_icon_when_single = true;
-            switcher_only = false;
-          };
-
-          switcher = {
-            icon = "application-default-icon";
-            prefix = "/";
-            show_icon_when_single = true;
-          };
-
-          symbols = {
-            placeholder = "";
-            prefix = "sym";
-            switcher_only = false;
-          };
-
-          translation = {
-            icon = "translator";
-            placeholder = "";
-            prefix = "tr";
-            switcher_only = false;
-          };
-
-          websearch = {
-            placeholder = "system-search";
-            switcher_only = false;
-            entries = [{}];
-          };
-        };
-
-        # TODO: Keybinds
-        # https://github.com/abenz1267/walker/wiki/Keybinds
-
-        # https://github.com/abenz1267/walker/wiki/Plugins
-        plugins = [
-          {
-            # Search engines by keyword prefix
-            name = "search";
-            placeholder = "";
-            show_icon_when_single = true;
-            switcher_only = false;
-
-            src = "${pkgs.writeShellApplication {
-              name = "search";
-              text = readFile ./search.sh;
-              runtimeInputs = with pkgs; [coreutils jq xdg-utils];
-            }}/bin/search '%TERM%'";
-          }
-        ];
-      };
-
-      # https://github.com/abenz1267/walker/wiki/Theming
-      theme = {
-        style = ''
-          #box {
-            border: ${toString config.custom.border}px #073642 solid;
-            font: larger ${config.custom.settings.fonts.sans-serif};
-          }
-
-          ${readFile ./style.css}
-        '';
-
-        # https://github.com/abenz1267/walker/blob/master/internal/config/layout.default.toml
-        layout.ui.window = let
-          w = 750;
-          h = 300;
-        in {
-          width = w;
-          height = h;
-
-          box = {
-            h_align = "fill";
-            width = -1;
-            height = -1;
-
-            scroll = {
-              h_align = "fill";
-              h_scrollbar_policy = "external";
-              v_scrollbar_policy = "external";
-
-              list = {
-                width = -1;
-                height = -1;
-                min_width = -1;
-                min_height = -1;
-                max_width = w;
-                max_height = h;
-
-                item = {
-                  text = {
-                    sub = {
-                      hide = true; # Subtext
-                    };
-                  };
-                };
-              };
-            };
-          };
-        };
-      };
-    };
-
-    # # HACK: Create theme files for module prompt icons
-    # #?? MODULE.theme = "icon-ICON"
-    # # https://github.com/abenz1267/walker/blob/bb584eab3b0cc48ebfbac1a5da019864d74781c4/nix/hm-module.nix#L86
-    # xdg.configFile = listToAttrs (flatten (forEach cfg.icons (
-    #   icon: [
-    #     {
-    #       name = "walker/themes/icon-${icon}.css";
-    #       value = {text = hm.programs.walker.theme.style;};
-    #     }
-    #     {
-    #       name = "walker/themes/icon-${icon}.json";
-    #       value = {
-    #         text = builtins.toJSON (recursiveUpdate hm.programs.walker.theme.layout {
-    #           ui.window.box.search.prompt.icon = icon;
-    #         });
-    #       };
-    #     }
-    #   ]
-    # )));
-
-    # HACK: Allow child processes to live, otherwise applications launched through service are killed on stop
-    # https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html#KillMode=
-    systemd.user.services.walker.Service.KillMode = "process";
-  };
-}
diff --git a/options/custom/scripts/bwm.sh b/options/custom/scripts/bwm.sh
deleted file mode 100644
index 0d3a2c4..0000000
--- a/options/custom/scripts/bwm.sh
+++ /dev/null
@@ -1,50 +0,0 @@
-#! /usr/bin/env bash
-
-# Bitwarden dmenu
-# TODO: Clear clipboard after timer
-
-SESSIONFILE="$XDG_RUNTIME_DIR/bwm"
-
-# Use current session if exists
-if test -f "$SESSIONFILE"; then
-  BW_SESSION="$(cat "$SESSIONFILE")" && export BW_SESSION
-fi
-
-# Unlock vault if needed
-if ! bw unlock --check; then
-  # Prompt for obfuscated password
-  password="$(wofi --dmenu --password --lines 3 <<< 'Vault locked. Enter master password.')"
-
-  # Save session to /tmp
-  BW_SESSION="$(bw unlock "$password" --raw)" && export BW_SESSION
-  touch "$SESSIONFILE"
-  chmod u=rw,g=,o= "$SESSIONFILE"
-  echo "$BW_SESSION" > "$SESSIONFILE"
-fi
-
-# Prompt for search term
-search="$(wofi --dmenu --lines 3 <<< 'Enter item to search.')"
-
-# Gather and parse results
-items="$(bw list items --search "$search")"
-usernames="$(jq -r '.[].login.username' <<< "$items")"
-passwords="$(jq -r '.[].login.password' <<< "$items")"
-
-# Prompt to select username
-username="$(wofi --dmenu <<< "$usernames")"
-
-# Find matching password line number
-count=1
-
-while IFS= read -r username; do
-  if [[ "$username" == "$username" ]]; then
-    break
-  else
-    ((count++))
-  fi
-done <<< "$usernames"
-
-# Copy line to clipboard
-tail --lines "+$count" <<< "$passwords" | head -1 | tee >(xclip -rmlastnl -selection clipboard &> /dev/null) >(wl-copy --trim-newline &> /dev/null)
-
-notify-send '> bwm' 'Copied' --urgency low
diff --git a/options/custom/scripts/default.nix b/options/custom/scripts/default.nix
index f82f729..46f8931 100644
--- a/options/custom/scripts/default.nix
+++ b/options/custom/scripts/default.nix
@@ -51,15 +51,6 @@ in {
               easyeffects
               libnotify
             ])
-            (bash "bwm" [
-              bitwarden-cli
-              coreutils
-              jq
-              libnotify
-              wl-clipboard
-              wofi
-              xclip
-            ])
             (bash "calc" [
               coreutils
               libnotify
@@ -179,6 +170,15 @@ in {
               jq
               libnotify
             ])
+            (bash "vault" [
+              argc
+              bitwarden-cli
+              coreutils
+              jq
+              libnotify
+              walker
+              wl-clipboard
+            ])
             (bash "vpn" [
               gnused
               jq
diff --git a/options/custom/scripts/vault.sh b/options/custom/scripts/vault.sh
new file mode 100644
index 0000000..0ed3600
--- /dev/null
+++ b/options/custom/scripts/vault.sh
@@ -0,0 +1,74 @@
+#! /usr/bin/env bash
+
+# @describe Bitwarden menu client
+#
+# https://github.com/sigoden/argc
+
+# @meta combine-shorts
+
+eval "$(argc --argc-eval "$0" "$@")"
+
+SESSIONFILE="$XDG_RUNTIME_DIR/vault"
+
+# Use current session if exists
+if test -f "$SESSIONFILE"; then
+  BW_SESSION="$(cat "$SESSIONFILE")" && export BW_SESSION
+fi
+
+# Unlock vault if needed
+if ! bw unlock --check; then
+  # Log in if needed
+  if ! bw login --check; then
+    # Prompt for server URL
+    BW_SERVER="$(walker --dmenu --forceprint <<< "Logged out. Enter server URL.")"
+
+    # Use server in bw config
+    bw config server "$BW_SERVER"
+
+    # Prompt for email and password
+    BW_EMAIL="$(walker --dmenu --forceprint <<< "Saved. Enter email address.")"
+    BW_PASSWORD="$(walker --dmenu --forceprint <<< "Saved. Enter master password.")"
+
+    # Log in to vault
+    BW_SESSION="$(bw login --raw "$BW_EMAIL" "$BW_PASSWORD")" && export BW_SESSION
+  else
+    # BUG: Walker crashes in password mode
+    # Prompt for obfuscated password
+    BW_PASSWORD="$(walker --dmenu --forceprint <<< "Vault locked. Enter master password.")"
+
+    # Unlock vault
+    BW_SESSION="$(bw unlock --raw "$BW_PASSWORD")" && export BW_SESSION
+  fi
+
+  # Save session to file
+  touch "$SESSIONFILE"
+  chmod u=rw,g=,o= "$SESSIONFILE"
+  echo "$BW_SESSION" > "$SESSIONFILE"
+fi
+
+# Prompt for search term
+search="$(walker --dmenu --forceprint <<< "Enter item to search.")"
+
+# Gather and parse results
+items="$(bw list items --search "$search")"
+usernames="$(jq -r ".[].login.username" <<< "$items")"
+passwords="$(jq -r ".[].login.password" <<< "$items")"
+
+# Prompt to select username
+username="$(walker --dmenu <<< "$usernames")"
+
+# Find matching password line number
+count=1
+
+while IFS= read -r username; do
+  if [[ "$username" == "$username" ]]; then
+    break
+  else
+    ((count++))
+  fi
+done <<< "$usernames"
+
+# Copy line to clipboard
+tail --lines "+$count" <<< "$passwords" | head -1 | wl-copy --trim-newline &> /dev/null
+
+notify-send "> bwm" "Copied" --urgency low