From f2d27233cbe880b415a0d713d4b9bca6044c79d3 Mon Sep 17 00:00:00 2001 From: Tyler Tracy Date: Tue, 30 Dec 2025 19:11:26 -0800 Subject: [PATCH 1/5] Integrate omni-menu into owl as a binary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added omni-menu source code from more_rust branch to src/omni_menu/ - Updated Cargo.toml with gtk4, fuzzy-matcher, dirs, regex dependencies - Added omni-menu as second binary target alongside owl - Updated menu setup to link binary instead of cloning external repo - Updated i3 config keybindings to use new command names - Removed external repo install script Features included: - Main menu with keyboard shortcuts - Search menu (Google, ChatGPT, Notes) - Projects menu (local/remote dev projects) - Scripts menu (owl scripts) - Launch tool menu - Switch bench menu (yard integration) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- CLAUDE.md | 178 ++++++++ Cargo.toml | 12 + setups/i3/i3-config | 4 +- setups/kitty/kitty.conf | 1 + setups/menu/install.sh | 5 - setups/menu/setup.json | 8 +- setups/sway/scripts/desks/framework-sway.sh | 2 +- src/omni_menu/launch_tool_menu.rs | 126 ++++++ src/omni_menu/main.rs | 34 ++ src/omni_menu/main_menu.rs | 129 ++++++ src/omni_menu/projects_menu.rs | 433 ++++++++++++++++++++ src/omni_menu/scripts_menu.rs | 124 ++++++ src/omni_menu/search_menu.rs | 143 +++++++ src/omni_menu/switch_bench_menu.rs | 126 ++++++ src/omni_menu/utils.rs | 28 ++ 15 files changed, 1343 insertions(+), 10 deletions(-) create mode 100644 CLAUDE.md delete mode 100755 setups/menu/install.sh create mode 100644 src/omni_menu/launch_tool_menu.rs create mode 100644 src/omni_menu/main.rs create mode 100644 src/omni_menu/main_menu.rs create mode 100644 src/omni_menu/projects_menu.rs create mode 100644 src/omni_menu/scripts_menu.rs create mode 100644 src/omni_menu/search_menu.rs create mode 100644 src/omni_menu/switch_bench_menu.rs create mode 100644 src/omni_menu/utils.rs diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4641a99 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,178 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +Owl is a modular dotfiles and environment management CLI written in Rust. It manages configurations across different machines through: +- **Setups**: Modular units that handle software configuration (in `setups/`) +- **Nests**: Machine-specific environments that bundle setups together (in `nests/`) +- **Common**: Shared configurations and scripts (in `common/`) + +## Development Commands + +### Building and Running +```bash +cargo build # Build the project +cargo run -- # Run with arguments +cargo build --release # Production build + +# Example: Test nest linking +cargo run -- nest link +cargo run -- setup git info +``` + +### Testing Changes +```bash +# Install the built binary +cargo build && cargo run -- nest link + +# The binary is linked to ~/.local/bin/owl via nest link +# After linking, you can use `owl` directly +``` + +### Validation +```bash +owl setups-validate # Validate all setup.json files +``` + +## Architecture + +### Core Data Model + +The codebase follows a **validate-first pattern**: + +1. **SetupFile** (`setup.json`): Raw JSON with all optional fields + - Parsed directly from disk using serde + - All fields are `Option` to handle missing values + +2. **Setup**: Validated, resolved in-memory representation + - Created by validating a `SetupFile` + - All paths are resolved (no more `local:` or `common:` tokens) + - Used for all operations (link, install, systemd) + +3. **Validated types**: Specific structs for each component + - `ValidatedSetupLink`: Resolved source/target paths with root flag + - `ValidatedRunScript`: RC scripts with resolved paths + - `ValidatedSetupMenuScriptItem`: Menu scripts with paths and names + - `ValidatedSetupService`: Services with resolved unit files and types + +### Path Resolution + +The system uses path tokens that are resolved during validation: + +- `local:` → Relative to the setup directory (e.g., `setups//...`) +- `common:` → Relative to `common/` in the repo root +- `~` → Expands to user home directory +- Absolute paths pass through as-is + +Example: +```json +{ + "source": "local:config/sway.conf", // → setups/sway/config/sway.conf + "source": "common:config/.vimrc", // → common/config/.vimrc + "source": "~/custom/.zshrc" // → /home/user/custom/.zshrc +} +``` + +### Setup System + +**setup.json schema** (all fields optional): +```json +{ + "name": "git", + "dependencies": ["base-shell"], + "install": "local:install.sh", + "links": [ + { + "source": "local:gitconfig", + "target": "~/.gitconfig", + "root": false + } + ], + "rc_scripts": ["common:git-aliases.sh", "local:rc.sh"], + "menu_scripts": ["local:menu-helper.sh"], + "services": [ + { + "path": "local:my-service.service", + "service_type": "User" + } + ] +} +``` + +**Key concepts:** +- **dependencies**: Other setups to install first (recursive) +- **links**: Files/directories to symlink to target locations +- **rc_scripts**: Shell scripts sourced at startup (linked to `~/.config/owl/rc/`) +- **menu_scripts**: Scripts for dmenu/rofi (linked to `~/.config/owl/menu-scripts/`) +- **services**: Systemd units to link and enable (User or System scope) + +### Nest System + +Nests are root setups in `nests/` that define complete machine environments. They use the same `setup.json` format but typically: +- Declare many dependencies +- Include machine-specific links and rc_scripts +- Use `local:` paths relative to the nest directory + +The active nest is tracked in `~/.config/owl/config.json` as `nest_path`. + +### CLI Structure + +Entry point: `src/main.rs` + +Main command groups: +- **setup**: Operations on individual setups + - `owl setup link|install|systemd|info|edit|all [--shallow]` +- **nest**: Operations on the active nest (shorthand for root setup) + - `owl nest link|install|systemd|info|edit|switch|all [--shallow]` +- **system**: Configuration and maintenance + - `owl config`: Show current config + - `owl sync`: Sync repository (fetch, fast-forward, optional push) + - `owl update [--recursive]`: Update owl binary + - `owl setups-validate`: Validate all setups + +The `--shallow` flag prevents recursive dependency processing. + +## Code Conventions + +### Rust Patterns +- Keep all `setup.json` fields optional - validation happens separately +- Use free functions over large impls for CLI orchestration +- Fail fast at validation time with clear, user-friendly errors +- Print structured progress with emojis and colors during operations +- Remove dead code; prefer `quiet: bool` parameters over duplicate functions + +### Shell Scripts +- Use `set -euo pipefail` unless interactive +- Quote variable expansions: `"${VAR}"` +- Make install scripts idempotent (check before installing) +- Scripts receive resolved absolute paths (no `local:` or `common:` tokens) + +### Path Resolution +- Use the single unified resolver for all path token expansion +- All validated types store resolved `PathBuf`s +- Never operate on raw string paths from JSON + +## Common Setup Patterns + +### Adding a new setup +1. Create `setups//setup.json` +2. Add configuration files to the setup directory +3. Use `local:` for files in the setup, `common:` for shared files +4. Define dependencies on other setups if needed +5. Run `owl setups-validate` to check correctness + +### Adding to a nest +Edit `nests//setup.json` and add the setup name to `dependencies`. + +### Service management +Services are linked during `link` and enabled/started during `systemd`: +- User services → `~/.config/systemd/user/` +- System services → `/etc/systemd/system/` (requires sudo) + +### Debugging +- Use `owl setup info` to see what would be linked +- Check `~/.config/owl/config.json` for active configuration +- RC scripts are in `~/.config/owl/rc/` as `rc--` +- Menu scripts are in `~/.config/owl/menu-scripts/` diff --git a/Cargo.toml b/Cargo.toml index 973598a..6905aff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,14 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "owl" +path = "src/main.rs" + +[[bin]] +name = "omni-menu" +path = "src/omni_menu/main.rs" + [dependencies] clap = { version = "4.3.21", features = ["derive"] } colored = "2.0.4" @@ -13,3 +21,7 @@ serde_json = "1.0.105" shellexpand = "3.1.0" thiserror = "1.0" once_cell = "1.19" +gtk = { version = "0.10.2", package = "gtk4" } +fuzzy-matcher = "0.3" +dirs = "5.0" +regex = "1.5" diff --git a/setups/i3/i3-config b/setups/i3/i3-config index 4ed22b1..1b6a35a 100644 --- a/setups/i3/i3-config +++ b/setups/i3/i3-config @@ -19,8 +19,8 @@ for_window [title="ClipGPT"] floating enable for_window [class="Spotify"] move to workspace 9 # Rofi -bindsym $mod+i exec "~/.local/bin/omni-menu run" -bindsym $mod+o exec "~/.local/bin/omni-menu window" +bindsym $mod+i exec "~/.local/bin/omni-menu launch_tool" +bindsym $mod+o exec "rofi -show window" bindsym $mod+p exec "~/.local/bin/omni-menu" bindsym $mod+n exec $TERMINAL --role floating -e "tt notes tui" diff --git a/setups/kitty/kitty.conf b/setups/kitty/kitty.conf index ed93965..70d830a 100644 --- a/setups/kitty/kitty.conf +++ b/setups/kitty/kitty.conf @@ -34,3 +34,4 @@ color15 #FFFFFF # Optional padding window_padding_width 6.0 +map shift+enter send_text all \e\r diff --git a/setups/menu/install.sh b/setups/menu/install.sh deleted file mode 100755 index 8ee4061..0000000 --- a/setups/menu/install.sh +++ /dev/null @@ -1,5 +0,0 @@ -git clone git@github.com:tylerthecoder/omni-menu.git ~/dev/omni-menu - -cd ~/dev/omni-menu - -make install \ No newline at end of file diff --git a/setups/menu/setup.json b/setups/menu/setup.json index 9639c11..66384b5 100644 --- a/setups/menu/setup.json +++ b/setups/menu/setup.json @@ -1,5 +1,9 @@ { - "install": "local:install.sh", "name": "menu", - "links": [] + "links": [ + { + "source": "target/debug/omni-menu", + "target": "~/.local/bin/omni-menu" + } + ] } \ No newline at end of file diff --git a/setups/sway/scripts/desks/framework-sway.sh b/setups/sway/scripts/desks/framework-sway.sh index 4cf21c0..c8d09dd 100755 --- a/setups/sway/scripts/desks/framework-sway.sh +++ b/setups/sway/scripts/desks/framework-sway.sh @@ -9,7 +9,7 @@ for out in $(swaymsg -t get_outputs | jq -r '.[].name'); do fi done -swaymsg output eDP-1 enable mode 2880x1920 position 0 0 +swaymsg output eDP-1 enable mode 2880x1920 position 0 0 scale 1.4 # Key repeat and caps as super in Wayland swaymsg input type:keyboard repeat_delay 200 diff --git a/src/omni_menu/launch_tool_menu.rs b/src/omni_menu/launch_tool_menu.rs new file mode 100644 index 0000000..a53a94e --- /dev/null +++ b/src/omni_menu/launch_tool_menu.rs @@ -0,0 +1,126 @@ +pub mod launch_tool_menu { + use crate::utils::{filter_list, populate_list}; + use gtk::prelude::*; + use gtk::{ + glib, Application, ApplicationWindow, Box as GtkBox, Entry, Label, ListBox, Orientation, + ScrolledWindow, + }; + use std::process::Command; + + const APP_ID: &str = "org.gtk_rs.LaunchToolMenu"; + + fn build_ui(app: &Application) { + let window = ApplicationWindow::builder() + .application(app) + .title("Launch Tool") + .default_width(500) + .default_height(600) + .decorated(true) + .resizable(false) + .modal(true) + .build(); + + let vbox = GtkBox::new(Orientation::Vertical, 5); + vbox.set_margin_top(10); + vbox.set_margin_bottom(10); + vbox.set_margin_start(10); + vbox.set_margin_end(10); + + // Search entry + let search_entry = Entry::new(); + search_entry.set_placeholder_text(Some("Type to filter tools...")); + vbox.append(&search_entry); + + // Scrolled window for list items + let scrolled_window = ScrolledWindow::builder() + .hscrollbar_policy(gtk::PolicyType::Never) + .vscrollbar_policy(gtk::PolicyType::Automatic) + .vexpand(true) + .build(); + + let list_box = ListBox::new(); + list_box.set_selection_mode(gtk::SelectionMode::Single); + list_box.set_activate_on_single_click(true); + scrolled_window.set_child(Some(&list_box)); + + vbox.append(&scrolled_window); + window.set_child(Some(&vbox)); + + // Get tools and populate + let tools = get_yard_tools(); + populate_list(&list_box, &tools); + + // Handle search + let list_box_weak = list_box.downgrade(); + let tools_clone = tools.clone(); + search_entry.connect_changed(move |entry| { + if let Some(list_box) = list_box_weak.upgrade() { + let query = entry.text().to_string().to_lowercase(); + filter_list(&list_box, &tools_clone, &query); + } + }); + + // Handle activation + let window_weak = window.downgrade(); + list_box.connect_row_activated(move |_, row| { + if let Some(label) = row.child().and_then(|w| w.downcast::