Files
owlry/CLAUDE.md

16 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Build & Development Commands

just build          # Debug build (all workspace members)
just build-ui       # UI binary only
just build-daemon   # Core daemon only
just release        # Release build (LTO, stripped)
just release-daemon # Release build for daemon only
just check          # cargo check + clippy
just test           # Run tests
just fmt            # Format code
just run [ARGS]     # Run UI with optional args (e.g., just run --mode app)
just run-daemon     # Run core daemon
just install-local  # Install core + daemon + runtimes + systemd units

# Dev build with verbose logging
cargo run -p owlry --features dev-logging

# Build core without embedded Lua (smaller binary, uses external owlry-lua)
cargo build -p owlry --release --no-default-features

Usage Examples

Basic Invocation

The UI client connects to the owlry-core daemon via Unix socket IPC. Start the daemon first:

# Start daemon (systemd recommended)
systemctl --user enable --now owlry-core.service

# Or run directly
owlry-core

# Then launch UI
owlry                      # Launch with all providers
owlry -m app               # Applications only
owlry -m cmd               # PATH commands only
owlry --profile dev        # Use a named config profile
owlry -m calc              # Calculator plugin only (if installed)

dmenu Mode

dmenu mode runs locally without the daemon. Use -m dmenu with piped input for interactive selection. The selected item is printed to stdout (not executed), so pipe the output to execute it:

# Screenshot menu (execute selected command)
printf '%s\n' \
  "grimblast --notify copy screen" \
  "grimblast --notify copy area" \
  "grimblast --notify edit screen" \
  | owlry -m dmenu -p "Screenshot" \
  | sh

# Git branch checkout
git branch | owlry -m dmenu -p "checkout" | xargs git checkout

# Kill a process
ps -eo comm | sort -u | owlry -m dmenu -p "kill" | xargs pkill

# Select and open a project
find ~/projects -maxdepth 1 -type d | owlry -m dmenu | xargs code

CLI Flags

Flag Description
-m, --mode MODE Start in single-provider mode (app, cmd, dmenu, calc, etc.)
--profile NAME Use a named profile from config (defines which modes to enable)
-p, --prompt TEXT Custom prompt text for the search input (dmenu mode)

Available Modes

Mode Description
app Desktop applications
cmd PATH commands
dmenu Pipe-based selection (requires stdin, runs locally)
calc Calculator (plugin)
clip Clipboard history (plugin)
emoji Emoji picker (plugin)
ssh SSH hosts (plugin)
sys System actions (plugin)
bm Bookmarks (plugin)
file File search (plugin)
web Web search (plugin)
uuctl systemd user units (plugin)

Search Prefixes

Type these in the search box to filter by provider:

Prefix Provider Example
:app Applications :app firefox
:cmd PATH commands :cmd git
:sys System actions :sys shutdown
:ssh SSH hosts :ssh server
:clip Clipboard :clip password
:bm Bookmarks :bm github
:emoji Emoji :emoji heart
:calc Calculator :calc sqrt(16)
:web Web search :web rust docs
:file Files :file config
:uuctl systemd :uuctl docker
:tag:X Filter by tag :tag:development

Trigger Prefixes

Trigger Provider Example
= Calculator = 5+3
? Web search ? rust programming
/ File search / .bashrc

Keyboard Shortcuts

Key Action
Enter Launch selected item
Escape Close launcher / exit submenu
Up / Down Navigate results
Tab Cycle filter tabs
Shift+Tab Cycle tabs (reverse)
Ctrl+1..9 Toggle tab by position

Plugin CLI

owlry plugin list                  # List installed
owlry plugin list --available      # Show registry
owlry plugin search "query"        # Search registry
owlry plugin install <name>        # Install from registry
owlry plugin install ./path        # Install from local path
owlry plugin remove <name>         # Uninstall
owlry plugin enable/disable <name> # Toggle
owlry plugin create <name>         # Create Lua plugin template
owlry plugin create <name> -r rune # Create Rune plugin template
owlry plugin validate ./path       # Validate plugin structure
owlry plugin run <id> <cmd> [args] # Run plugin CLI command
owlry plugin commands <id>         # List plugin commands
owlry plugin runtimes              # Show available runtimes

Release Workflow

Always use just for releases - do NOT manually edit Cargo.toml for version bumps:

# Bump a single crate
just bump-crate owlry-core 0.5.1

# Bump all crates to same version
just bump-all 0.5.1

# Bump core UI only
just bump 0.5.1

# Create and push release tag
git push && just tag

# AUR package management
just aur-update           # Update core UI PKGBUILD
just aur-update-pkg NAME  # Update specific package (owlry-core, owlry-lua, etc.)
just aur-update-all       # Update all AUR packages
just aur-publish          # Publish core UI to AUR
just aur-publish-all      # Publish all AUR packages

# Version inspection
just show-versions        # List all crate versions
just aur-status           # Show AUR package versions and git status

AUR Packaging

The aur/ directory contains PKGBUILDs for core packages:

Category Packages
Core UI owlry
Core Daemon owlry-core
Runtimes owlry-lua, owlry-rune
Meta-bundles owlry-meta-essentials, owlry-meta-widgets, owlry-meta-tools, owlry-meta-full

Plugin AUR packages are in the separate owlry-plugins repo at somegit.dev/Owlibou/owlry-plugins.

Architecture

Client/Daemon Split

Owlry uses a client/daemon architecture:

  • owlry (client): GTK4 UI that connects to the daemon via Unix socket IPC. Handles rendering, user input, and launching applications. In dmenu mode, runs a local ProviderManager directly (no daemon needed).
  • owlry-core (daemon): Headless background service that loads plugins, manages providers, handles fuzzy matching, frecency scoring, and serves queries over IPC. Runs as a systemd user service.

Workspace Structure

owlry/
├── Cargo.toml                      # Workspace root
├── systemd/                        # systemd user service/socket files
│   ├── owlry-core.service
│   └── owlry-core.socket
├── crates/
│   ├── owlry/                      # UI client binary (GTK4 + Layer Shell)
│   │   └── src/
│   │       ├── main.rs             # Entry point
│   │       ├── app.rs              # GTK Application setup, CSS loading
│   │       ├── cli.rs              # Clap CLI argument parsing
│   │       ├── client.rs           # CoreClient - IPC client to daemon
│   │       ├── backend.rs          # SearchBackend - abstraction over IPC/local
│   │       ├── theme.rs            # Theme loading
│   │       ├── plugin_commands.rs  # Plugin CLI subcommand handlers
│   │       ├── providers/          # dmenu provider (local-only)
│   │       └── ui/                 # GTK widgets (MainWindow, ResultRow, submenu)
│   ├── owlry-core/                 # Daemon library + binary
│   │   └── src/
│   │       ├── main.rs             # Daemon entry point
│   │       ├── lib.rs              # Public API (re-exports modules)
│   │       ├── server.rs           # Unix socket IPC server
│   │       ├── ipc.rs              # Request/Response message types
│   │       ├── filter.rs           # ProviderFilter - mode/prefix filtering
│   │       ├── paths.rs            # XDG path utilities, socket path
│   │       ├── notify.rs           # Desktop notifications
│   │       ├── config/             # Config loading (config.toml)
│   │       ├── data/               # FrecencyStore
│   │       ├── providers/          # Application, Command, native/lua provider hosts
│   │       └── plugins/            # Plugin loading, manifests, registry, runtimes
│   ├── owlry-plugin-api/           # ABI-stable plugin interface
│   ├── owlry-lua/                  # Lua script runtime (cdylib)
│   └── owlry-rune/                 # Rune script runtime (cdylib)

IPC Protocol

Communication uses newline-delimited JSON over a Unix domain socket at $XDG_RUNTIME_DIR/owlry/owlry.sock.

Request types (owlry_core::ipc::Request):

Type Purpose
Query Search with text and optional mode filters
Launch Record a launch event for frecency
Providers List available providers
Refresh Refresh a specific provider
Toggle Toggle visibility (client-side concern, daemon acks)
Submenu Query submenu actions for a plugin item
PluginAction Execute a plugin action command

Response types (owlry_core::ipc::Response):

Type Purpose
Results Search results with Vec<ResultItem>
Providers Provider list with Vec<ProviderDesc>
SubmenuItems Submenu actions for a plugin
Ack Success acknowledgement
Error Error with message

Core Data Flow

[owlry UI]                              [owlry-core daemon]

main.rs → CliArgs → OwlryApp            main.rs → Server::bind()
         ↓                                         ↓
    SearchBackend                        UnixListener accept loop
         ↓                                         ↓
  ┌──────┴──────┐                        handle_request()
  ↓             ↓                              ↓
Daemon       Local (dmenu)         ┌───────────┴───────────┐
  ↓                                ↓                       ↓
CoreClient ──── IPC ────→   ProviderManager         ProviderFilter
                                   ↓                       ↓
                         [Provider impls]         parse_query()
                                   ↓
                            LaunchItem[]
                                   ↓
                         FrecencyStore (boost)
                                   ↓
                         Response::Results ──── IPC ────→ UI rendering

Provider System

Core providers (in owlry-core):

  • Application: Desktop applications from XDG directories
  • Command: Shell commands from PATH

dmenu provider (in owlry client, local only):

  • Dmenu: Pipe-based input (dmenu compatibility)

All other providers are native plugins in the separate owlry-plugins repo (somegit.dev/Owlibou/owlry-plugins).

User plugins (script-based, in ~/.config/owlry/plugins/):

  • Lua plugins: Loaded by owlry-lua runtime from /usr/lib/owlry/runtimes/liblua.so
  • Rune plugins: Loaded by owlry-rune runtime from /usr/lib/owlry/runtimes/librune.so
  • User plugins are hot-reloaded automatically when files change (no daemon restart needed)
  • Custom prefixes (e.g., :hs) are resolved dynamically for user plugins

ProviderManager (in owlry-core) orchestrates providers and handles:

  • Fuzzy matching via SkimMatcherV2
  • Frecency score boosting
  • Native plugin loading from /usr/lib/owlry/plugins/
  • Script runtime loading from /usr/lib/owlry/runtimes/ for user plugins
  • Filesystem watching for automatic user plugin hot-reload

Submenu System: Plugins can return items with SUBMENU:plugin_id:data commands. When selected, the plugin is queried with ?SUBMENU:data to get action items (e.g., systemd service actions).

Plugin API

Native plugins use the ABI-stable interface in owlry-plugin-api:

#[repr(C)]
pub struct PluginVTable {
    pub info: extern "C" fn() -> PluginInfo,
    pub providers: extern "C" fn() -> RVec<ProviderInfo>,
    pub provider_init: extern "C" fn(id: RStr) -> ProviderHandle,
    pub provider_refresh: extern "C" fn(ProviderHandle) -> RVec<PluginItem>,
    pub provider_query: extern "C" fn(ProviderHandle, RStr) -> RVec<PluginItem>,
    pub provider_drop: extern "C" fn(ProviderHandle),
}

// Each plugin exports:
#[no_mangle]
pub extern "C" fn owlry_plugin_vtable() -> &'static PluginVTable

Plugins are compiled as .so (cdylib) and loaded by the daemon at startup.

Plugin locations (when deployed):

  • /usr/lib/owlry/plugins/*.so - Native plugins
  • /usr/lib/owlry/runtimes/*.so - Script runtimes (liblua.so, librune.so)
  • ~/.config/owlry/plugins/ - User plugins (Lua/Rune)

Filter & Prefix System

ProviderFilter (owlry-core/src/filter.rs) handles:

  • CLI mode selection (--mode app)
  • Profile-based mode selection (--profile dev)
  • Provider toggling (Ctrl+1/2/3)
  • Prefix parsing (:app, :cmd, :sys, etc.)
  • Dynamic prefix fallback for user plugins (any :word prefix maps to Plugin(word))

Query parsing extracts prefix and forwards clean query to providers.

SearchBackend

SearchBackend (owlry/src/backend.rs) abstracts over two modes:

  • Daemon: Wraps CoreClient, sends queries over IPC to owlry-core
  • Local: Wraps ProviderManager directly (used for dmenu mode only)

UI Layer

  • MainWindow (src/ui/main_window.rs): GTK4 window with Layer Shell overlay
  • ResultRow (src/ui/result_row.rs): Individual result rendering
  • submenu (src/ui/submenu.rs): Universal submenu parsing utilities (plugins provide actions)

Configuration

Config (owlry-core/src/config/mod.rs) loads from ~/.config/owlry/config.toml:

  • Auto-detects terminal ($TERMINAL -> xdg-terminal-exec -> common terminals)
  • Optional use_uwsm = true for systemd session integration (launches apps via uwsm app --)
  • Profiles: Define named mode sets under [profiles.<name>] with modes = ["app", "cmd", ...]

Theming

CSS loading priority (owlry/src/app.rs):

  1. Base structural CSS (resources/base.css)
  2. Theme CSS (built-in "owl" or custom ~/.config/owlry/themes/{name}.css)
  3. User overrides (~/.config/owlry/style.css)
  4. Config variable injection

Systemd Integration

Service files in systemd/:

  • owlry-core.service: Runs daemon as Type=simple, restarts on failure
  • owlry-core.socket: Socket activation at %t/owlry/owlry.sock

Start with: systemctl --user enable --now owlry-core.service

Plugins

Plugins live in a separate repository: somegit.dev/Owlibou/owlry-plugins

13 native plugin crates, all compiled as cdylib (.so):

Category Plugins Behavior
Static bookmarks, clipboard, emoji, scripts, ssh, system, systemd Loaded at startup, refresh() populates items
Dynamic calculator, websearch, filesearch Queried per-keystroke via query()
Widget weather, media, pomodoro Displayed at top of results

Key Patterns

  • Rc<RefCell> used throughout for GTK signal handlers needing mutable state
  • Feature flag dev-logging: Wraps debug!() calls in #[cfg(feature = "dev-logging")]
  • Feature flag lua: Enables built-in Lua runtime (off by default); enable to embed Lua in core binary
  • Script runtimes: External .so runtimes loaded from /usr/lib/owlry/runtimes/ — Lua and Rune user plugins loaded from ~/.config/owlry/plugins/
  • Hot-reload: Filesystem watcher (notify crate) monitors user plugins dir and reloads runtimes on file changes
  • dmenu mode: Runs locally without daemon. Use -m dmenu with piped stdin
  • Frecency: Time-decayed frequency scoring stored in ~/.local/share/owlry/frecency.json
  • ABI stability: Plugin interface uses abi_stable crate for safe Rust dynamic linking
  • Plugin API v3: Adds position (Normal/Widget) and priority fields to ProviderInfo
  • ProviderType simplification: Core uses only Application, Command, Dmenu, Plugin(String) - all plugin-specific types removed from core

Dependencies (Rust 1.90+, GTK 4.12+)

External tool dependencies (for plugins):

  • Clipboard plugin: cliphist, wl-clipboard
  • File search plugin: fd or mlocate
  • Emoji plugin: wl-clipboard, noto-fonts-emoji
  • Systemd plugin: systemd (user services)
  • Bookmarks plugin: Firefox support uses rusqlite with bundled SQLite (no system dependency)