Example Plugins

This page provides two complete example plugins to help you quickly understand the development patterns for iRightMenu Pro plugins.

hello-world — Minimal Example

A minimal plugin demonstrating the basic plugin structure, menu registration, and system notifications.

Use case: Getting started, understanding the basic plugin structure.

Directory Structure

com.example.hello-world/
├── plugin.json          # Plugin manifest
├── main.lua             # Main script
└── locales/             # Internationalization
    ├── en.json
    └── zh-Hans.json

plugin.json

{
  "id": "com.example.hello-world",
  "name": "{{plugin.name}}",
  "version": "1.0.0",
  "author": "Your Name",
  "description": "{{plugin.description}}",
  "locale": "en",
  "main": "main.lua",
  "menus": [
    {
      "id": "greet",
      "title": "{{menu.greet}}",
      "description": "Show a greeting notification",
      "enabled": true,
      "showOnFiles": true,
      "showOnFolders": true
    },
    {
      "id": "count",
      "title": "{{menu.count}}",
      "description": "Count selected items",
      "enabled": true,
      "showOnFiles": true,
      "showOnFolders": true,
      "minFiles": 1
    }
  ]
}

main.lua

-- Create the plugin class
local HelloWorld = Plugin:extend()

-- Initialization: register menu handlers
function HelloWorld:init()
    app.log.info("[HelloWorld] Plugin initialized")

    -- Register handlers matching menus[].id in plugin.json
    self:registerHandler("greet", self.showGreeting)
    self:registerHandler("count", self.countItems)
end

-- Handler: show a greeting notification
function HelloWorld:showGreeting(context)
    app.notification.show(
        "Hello!",
        "Welcome to iRightMenu Pro plugins!"
    )
end

-- Handler: count selected items
function HelloWorld:countItems(context)
    local files = context.selectedFiles
    local count = #files

    local message
    if count == 1 then
        message = "1 item selected"
    else
        message = count .. " items selected"
    end

    app.notification.show("Count", message)
end

-- Return the plugin class (the engine instantiates it automatically)
return HelloWorld

Localization Files

locales/zh-Hans.json

{
  "plugin.name": "Hello World",
  "plugin.description": "A simple example plugin",
  "menu.greet": "Say Hello",
  "menu.count": "Count Files"
}

file-processor — Advanced Example

A more complete plugin demonstrating plugin settings, progress bars, file operations, multiple menu items, error handling, and internationalization.

Use case: Learning advanced features, as a starting point for production-grade plugins.

Directory Structure

com.example.file-processor/
├── plugin.json
├── main.lua
└── locales/
    ├── en.json
    └── zh-Hans.json

plugin.json

{
  "id": "com.example.file-processor",
  "name": "{{plugin.name}}",
  "version": "1.0.0",
  "author": "Your Name",
  "description": "{{plugin.description}}",
  "locale": "en",
  "main": "main.lua",
  "settings": {
    "title": "{{settings.title}}",
    "fields": [
      {
        "key": "output_format",
        "type": "select",
        "label": "{{settings.output_format}}",
        "default": "txt",
        "options": ["txt", "json", "csv"]
      },
      {
        "key": "include_hidden",
        "type": "checkbox",
        "label": "{{settings.include_hidden}}",
        "default": false
      },
      {
        "key": "output_dir",
        "type": "folder",
        "label": "{{settings.output_dir}}",
        "default": ""
      }
    ]
  },
  "menus": [
    {
      "id": "list_files",
      "title": "{{menu.list_files}}",
      "description": "Generate a file list from selected items",
      "enabled": true,
      "showOnFiles": true,
      "showOnFolders": true,
      "minFiles": 1
    },
    {
      "id": "calculate_hash",
      "title": "{{menu.calculate_hash}}",
      "description": "Calculate MD5 hash of selected files",
      "enabled": true,
      "showOnFiles": true,
      "showOnFolders": false,
      "minFiles": 1
    }
  ]
}

Settings field types: text (text input), checkbox (toggle), select (dropdown), folder (folder picker). Plugins read user settings via app.settings.get(key, default).

main.lua

local FileProcessor = Plugin:extend()

function FileProcessor:init()
    self:registerHandler("list_files", self.listFiles)
    self:registerHandler("calculate_hash", self.calculateHash)
end

-- ========================================
-- Generate file list
-- ========================================

function FileProcessor:listFiles(context)
    local files = context.selectedFiles
    if #files == 0 then
        app.dialog.alert("Error", "No files selected")
        return
    end

    -- Read plugin settings
    local format = app.settings.get("output_format", "txt")
    local includeHidden = app.settings.get("include_hidden", false)
    local outputDir = app.settings.get("output_dir", "")

    if outputDir == "" then
        outputDir = context.currentDirectory
    end

    local outputFile = app.path.join(outputDir, "file-list." .. format)

    -- Show progress bar
    app.progress.show(app.i18n.t("progress.scanning"), {
        message = app.i18n.t("progress.preparing"),
        indeterminate = true
    })

    -- Collect file information
    local fileList = {}
    for _, path in ipairs(files) do
        local basename = app.path.basename(path)
        if includeHidden or not basename:match("^%.") then
            table.insert(fileList, {
                name = basename,
                path = path,
                size = app.file.size(path) or 0,
                isDir = app.path.isDirectory(path)
            })
        end
    end

    app.progress.hide()

    -- Format output based on settings
    local content
    if format == "json" then
        content = app.json.stringify(fileList, true)
    elseif format == "csv" then
        content = self:formatAsCSV(fileList)
    else
        content = self:formatAsTxt(fileList)
    end

    -- Write to file
    local success = app.file.write(outputFile, content)
    if success then
        app.notification.show(
            app.i18n.t("notify.success"),
            app.i18n.t("notify.file_created", outputFile)
        )
        app.finder.reveal(outputFile)
    else
        app.dialog.alert(
            app.i18n.t("notify.error"),
            app.i18n.t("notify.write_failed")
        )
    end
end

-- ========================================
-- Calculate MD5 hash
-- ========================================

function FileProcessor:calculateHash(context)
    local files = context.selectedFiles

    -- Filter out directories, keep only files
    local regularFiles = {}
    for _, path in ipairs(files) do
        if app.path.isFile(path) then
            table.insert(regularFiles, path)
        end
    end

    if #regularFiles == 0 then
        app.dialog.alert("Error", "No regular files selected")
        return
    end

    -- Hash calculation with progress
    app.progress.show(app.i18n.t("progress.hashing"), {
        message = "",
        indeterminate = false
    })

    local results = {}
    for i, path in ipairs(regularFiles) do
        local percent = (i / #regularFiles) * 100
        local filename = app.path.basename(path)
        app.progress.update(percent, filename)

        local hash = app.crypto.md5File(path)
        if hash then
            table.insert(results, { name = filename, hash = hash })
        end
    end

    app.progress.hide()

    -- Display results
    if #results > 0 then
        local message = ""
        for _, r in ipairs(results) do
            message = message .. r.name .. "\n" .. r.hash .. "\n\n"
        end
        app.dialog.alert(app.i18n.t("dialog.hash_results"), message)
    end
end

-- ========================================
-- Utility functions
-- ========================================

function FileProcessor:formatAsTxt(fileList)
    local lines = {}
    for _, info in ipairs(fileList) do
        local sizeStr = self:formatSize(info.size)
        local typeStr = info.isDir and "[DIR]" or "[FILE]"
        table.insert(lines, string.format("%s %s %s", typeStr, sizeStr, info.name))
    end
    return table.concat(lines, "\n")
end

function FileProcessor:formatAsCSV(fileList)
    local lines = {"name,path,size,type"}
    for _, info in ipairs(fileList) do
        local typeStr = info.isDir and "directory" or "file"
        table.insert(lines, string.format('"%s","%s",%d,%s',
            info.name, info.path, info.size, typeStr))
    end
    return table.concat(lines, "\n")
end

function FileProcessor:formatSize(bytes)
    if bytes < 1024 then
        return string.format("%d B", bytes)
    elseif bytes < 1024 * 1024 then
        return string.format("%.1f KB", bytes / 1024)
    elseif bytes < 1024 * 1024 * 1024 then
        return string.format("%.1f MB", bytes / (1024 * 1024))
    else
        return string.format("%.2f GB", bytes / (1024 * 1024 * 1024))
    end
end

return FileProcessor

Localization Files

locales/zh-Hans.json

{
  "plugin.name": "File Processor",
  "plugin.description": "Process files and generate reports",
  "settings.title": "File Processor Settings",
  "settings.output_format": "Output Format",
  "settings.include_hidden": "Include Hidden Files",
  "settings.output_dir": "Output Directory",
  "menu.list_files": "Generate File List",
  "menu.calculate_hash": "Calculate MD5 Hash",
  "progress.scanning": "Scanning files",
  "progress.preparing": "Preparing...",
  "progress.hashing": "Calculating hashes",
  "notify.success": "Success",
  "notify.error": "Error",
  "notify.file_created": "File created: {0}",
  "notify.write_failed": "Failed to write file",
  "dialog.hash_results": "MD5 Hash Results"
}

Quick Test

Copy an example plugin to the plugins directory to test it:

cp -r hello-world ~/Library/Group\ Containers/group.net.ymlab.iRightMenu.pro/Plugins/com.example.hello-world

Then reload plugins in iRightMenu Pro, or restart Finder.

Using Examples as Templates

  1. Copy an example directory and rename it to your plugin ID (e.g., com.yourname.myplugin)
  2. Update id, name, version, and other fields in plugin.json
  3. Write your plugin logic in main.lua
  4. Update the localization files under locales/

Learn More

Developer Documentation
User Guide
Getting Started Script Menus FAQ
Script Development
Development Guide
Plugin Development
Quick Start Development Guide Example Plugins
API Reference
Overview API Query Plugin Info Logging Finder Context Plugin Settings Internationalization
UI & Interaction
Dialog Progress Notification Chooser WebView Status Bar Dock
Files & Paths
File Operations Path Utilities Finder Actions Trash Extended Attributes Metadata File Watcher
Data Formats
JSON Plist CSV XML PDF Image
Text & Encoding
String Regex Date & Time Color Crypto
System
Shell Commands Process Application System Info AppleScript Shortcuts
System Info
Network Power/Battery Screen/Appearance Audio Bluetooth Location
Network
HTTP WebSocket URL
Input & Clipboard
Keyboard Mouse Hotkey Clipboard Window
Storage
SQLite Keychain UserDefaults
Media
OCR QR Code
Utilities
Archive UTI Share Timer Wake Lock Thread