How to Implement a Custom Action in the OpenLogi Action Catalog: A Complete Guide

To implement a custom action in OpenLogi, add a new variant to the Action enum in openlogi-core, update the UI metadata in the impl Action block, optionally define an icon in ActionRingIcon, and implement the OS-level execution logic in openlogi-inject.

OpenLogi is a Rust-based automation platform developed by AprilNEA that enables users to trigger workflows through an Actions Ring interface. The project organizes its architecture into multiple crates, with the core action definitions residing in openlogi-core and runtime execution handled by openlogi-inject. When you implement a custom action in the OpenLogi action catalog, you are extending the central Action enum while maintaining serialization stability and cross-platform compatibility.

Step-by-Step Implementation Guide

1. Define the Action Enum Variant in openlogi-core

All action types are defined in crates/openlogi-core/src/binding/action.rs. The Action enum uses explicit payload types to maintain a stable TOML serialization schema on disk.

Locate the Action enum definition and add your new variant with an appropriate payload type. For actions requiring string input (such as URLs or text), use String or a dedicated struct:

// crates/openlogi-core/src/binding/action.rs
pub enum Action {
    // ... existing variants ...
    /// Open a user-specified URL in the default browser.
    OpenUrl(String),
    // ... other variants ...
}

The variant name is serialized verbatim, ensuring configuration files remain compatible across versions. This "stability contract" is documented in the file comments.

2. Add UI Metadata and Translation Keys

The impl Action block (approximately line 300 in the same file) defines how the action appears in the user interface. You must provide three metadata methods for your new variant:

// crates/openlogi-core/src/binding/action.rs
impl Action {
    pub fn label(&self) -> &'static str {
        match self {
            // ... other arms ...
            Self::OpenUrl(_) => "Open URL",
        }
    }
    
    pub fn translation_key(&self) -> &'static str {
        match self {
            // ... other arms ...
            Self::OpenUrl(_) => "actions.open_url",
        }
    }
    
    pub fn category(&self) -> Category {
        match self {
            // ... other arms ...
            Self::OpenUrl(_) => Category::System,
        }
    }
}

Note: Do not add payload-carrying variants to the for_each_unit_action! macro. This macro only lists payload-free actions that appear directly in the UI picker without additional configuration.

3. Register an Icon for the Actions Ring

To display a custom glyph in the Actions Ring, extend the ActionRingIcon enum in crates/openlogi-core/src/binding/action_ring/icon.rs:

// crates/openlogi-core/src/binding/action_ring/icon.rs
pub enum ActionRingIcon {
    // ... existing icons ...
    OpenUrl,
}

impl ActionRingIcon {
    pub const fn asset_path(self) -> &'static str {
        match self {
            // ... existing mappings ...
            Self::OpenUrl => "action-icons/globe.svg",
        }
    }
}

Ensure you also update ActionRingIcon::ALL so the editor can display your new icon in the configuration interface. Place the corresponding SVG asset in the specified path.

4. Implement OS-Level Execution Logic

The actual system-level behavior is implemented in the openlogi-inject crate. Open crates/openlogi-inject/src/lib.rs and add a match arm in the execute function to handle your new action:

// crates/openlogi-inject/src/lib.rs
match action {
    // ... other actions ...
    Action::OpenUrl(url) => {
        #[cfg(target_os = "macos")]
        std::process::Command::new("open")
            .arg(url)
            .spawn()
            .ok();
        #[cfg(target_os = "linux")]
        std::process::Command::new("xdg-open")
            .arg(url)
            .spawn()
            .ok();
        #[cfg(target_os = "windows")]
        std::process::Command::new("cmd")
            .args(&["/C", "start", "", url])
            .spawn()
            .ok();
    }
    // ... other actions ...
}

This pattern ensures cross-platform compatibility by using native commands: open for macOS, xdg-open for Linux, and cmd /C start for Windows.

5. Expose the Action in the UI Picker (Optional)

By default, payload-carrying actions do not appear in Action::catalog(), which powers the UI picker. If you want users to select this action from the pop-over interface:

  • Add a template row to the for_each_unit_action! macro with the not_pickable tag removed
  • Supply a default payload or dialog prompt in the UI code to collect the required data (e.g., a text input for the URL)

Otherwise, the action remains available only through manual configuration files or scripting interfaces.

6. Update Tests and Validate Changes

Maintain code quality by updating the test suite:

  1. Serialization tests: Add a unit test in crates/openlogi-core/src/binding/action.rs verifying the new variant round-trips correctly through serialization.

  2. Catalog parity: If your action appears in the UI picker, update crates/openlogi-desktop/tests/catalog_parity.rs to ensure every pickable action has a corresponding translation key.

Run the workspace validation commands:

cargo fmt --check
cargo clippy -p openlogi-core -- -D warnings
cargo test -p openlogi-core

These checks enforce the project's linting standards and verify that your changes do not break existing functionality.

Key Source Files Reference

File Role
crates/openlogi-core/src/binding/action.rs Defines the Action enum and core metadata methods (label, translation_key, category).
crates/openlogi-core/src/binding/action_ring/icon.rs Maps actions to visual icons via the ActionRingIcon enum and asset_path method.
crates/openlogi-inject/src/lib.rs Executes actions at the OS level; contains the execute function with platform-specific logic.
crates/openlogi-desktop/tests/catalog_parity.rs Ensures UI pickable actions match localization files.

Summary

  • Define your action by adding a variant to the Action enum in openlogi-core with a stable payload type.
  • Metadata methods control how the action appears in the UI, including labels and translation keys.
  • Icons are registered separately in ActionRingIcon and mapped to SVG assets for the Actions Ring.
  • Execution logic belongs in openlogi-inject using conditional compilation for cross-platform support.
  • Visibility in the UI picker requires opt-in via the X-macro table if the action carries no payload.
  • Testing includes serialization round-trips and catalog parity checks to maintain system integrity.

Frequently Asked Questions

What is the difference between payload-carrying and unit actions in OpenLogi?

Unit actions are payload-free variants listed in the for_each_unit_action! macro that appear directly in the UI picker. Payload-carrying actions (like OpenUrl(String) or RunShellCommand(String)) require additional data and do not appear in the picker unless explicitly templated. This distinction preserves type safety while allowing flexible extensibility.

Where does the actual execution of an action happen in OpenLogi?

The runtime execution occurs in crates/openlogi-inject/src/lib.rs within the execute function. This crate bridges the abstract Action enum with OS-specific system calls, handling platform differences through conditional compilation attributes like #[cfg(target_os = "macos")].

How do I ensure my custom action appears in the OpenLogi UI picker?

Payload-free actions automatically appear in the picker via the for_each_unit_action! macro. For payload-carrying actions, you must either provide a template with default values in the macro (removing the not_pickable tag) or implement a UI dialog that collects the required data before instantiating the action.

What testing is required when adding a new action to OpenLogi?

You must verify serialization stability with a round-trip unit test in the core crate and, if applicable, update the catalog parity test in the desktop crate to validate localization coverage. Always run cargo clippy and cargo test on the affected packages before submitting changes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →