Skip to content

Latest commit

 

History

History
260 lines (193 loc) · 6.17 KB

File metadata and controls

260 lines (193 loc) · 6.17 KB

mklaunchd Plugin Authoring Guide

mklaunchd supports plugins via the same convention git uses — any executable named mklaunchd-<command> found on PATH is treated as a first-class subcommand. This document covers everything you need to write, test, and distribute an mklaunchd plugin.


How plugins work

When mklaunchd receives a subcommand it does not recognise as a built-in, it scans PATH for an executable named mklaunchd-<command>. If found, it is invoked directly with all remaining arguments passed through unchanged.

mklaunchd monitor io.uradical.myservice --interval 30
# mklaunchd finds mklaunchd-monitor on PATH
# executes: mklaunchd-monitor io.uradical.myservice --interval 30

Plugins are completely independent binaries. They can be written in any language — Swift, Go, Python, shell — as long as the result is an executable on PATH.


Environment contract

mklaunchd passes the following environment variables to every plugin. These are stable across minor versions. Breaking changes to this contract constitute a major version bump.

Variable Description Example
MKLAUNCHD_UID Current user's uid 501
MKLAUNCHD_DOMAIN Resolved launchd domain gui/501
MKLAUNCHD_VERSION mklaunchd version 0.1.0
MKLAUNCHD_PREFIX Plugin prefix string mklaunchd-

All existing environment variables from the parent process are also inherited. PATH is unchanged.


Required conventions

Naming

Your binary must be named mklaunchd-<command> where <command> is a single lowercase word or hyphenated phrase with no spaces:

mklaunchd-monitor      correct
mklaunchd-log-rotate   correct
mklaunchd-MyPlugin     incorrect (uppercase)
mklaunchd-my plugin    incorrect (space)

Exit codes

Code Meaning
0 Success
1 General failure
64 Usage error (bad arguments)
65 Data error (bad input)
78 Configuration error

--help

Implement --help and print usage to stdout.

--version

Implement --version and print your plugin's version to stdout.


Optional conventions

--description

If your plugin responds to --description with a single line of text and exits 0, mklaunchd will display that line in mklaunchd help alongside built-in commands. Keep descriptions under 60 characters.

--json

Where your plugin produces structured output, support --json to emit it as JSON. This makes your plugin composable with jq and consistent with mklaunchd's built-in commands.

NO_COLOR

Respect the NO_COLOR environment variable. If it is set, suppress all ANSI colour codes in your output.


Writing a plugin in Swift

// Package.swift
let package = Package(
    name: "mklaunchd-monitor",
    platforms: [.macOS(.v13)],
    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0")
    ],
    targets: [
        .executableTarget(
            name: "mklaunchd-monitor",
            dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")]
        )
    ]
)
// main.swift
import ArgumentParser
import Foundation

@main
struct Monitor: ParsableCommand {

    static let configuration = CommandConfiguration(
        commandName: "mklaunchd-monitor",
        abstract: "Watch a launchd job and alert on state changes"
    )

    @Argument(help: "Job label to monitor")
    var label: String

    @Flag(name: .long, help: "Print description and exit")
    var description: Bool = false

    mutating func run() throws {
        if description {
            print("Watch a launchd job and alert on state changes")
            return
        }
        let domain = ProcessInfo.processInfo.environment["MKLAUNCHD_DOMAIN"] ?? "gui/501"
        print("Monitoring \(label) in \(domain)")
    }
}
swift build -c release
cp .build/release/mklaunchd-monitor /usr/local/bin/
mklaunchd monitor <label>

Writing a plugin in Go

package main

import (
    "fmt"
    "os"
)

func main() {
    if len(os.Args) > 1 && os.Args[1] == "--description" {
        fmt.Println("Watch a launchd job and alert on state changes")
        os.Exit(0)
    }
    domain := os.Getenv("MKLAUNCHD_DOMAIN")
    if domain == "" { domain = "gui/501" }
    fmt.Printf("Monitoring in domain %s\n", domain)
}

Writing a plugin in shell

#!/bin/zsh
# mklaunchd-notify

case "${1:-}" in
  --description) echo "Send a notification when a job changes state"; exit 0 ;;
  --help)        echo "Usage: mklaunchd notify <label>";              exit 0 ;;
  --version)     echo "mklaunchd-notify 0.1.0";                       exit 0 ;;
esac

LABEL="${1:-}"
if [[ -z "$LABEL" ]]; then echo "Error: label required" >&2; exit 64; fi

osascript -e "display notification \"Job $LABEL changed state\" with title \"mklaunchd\""

Distribution via Homebrew

class MklaunchdMonitor < Formula
  desc "Watch a launchd job and alert on state changes"
  homepage "https://github.com/yourname/mklaunchd-monitor"
  url "https://github.com/yourname/mklaunchd-monitor/archive/refs/tags/v0.1.0.tar.gz"
  sha256 "..."
  depends_on :macos
  depends_on xcode: ["14.0", :build]

  def install
    system "swift", "build", "-c", "release", "--disable-sandbox"
    bin.install ".build/release/mklaunchd-monitor"
  end

  test do
    assert_match "Watch", shell_output("#{bin}/mklaunchd-monitor --description")
  end
end
brew tap yourname/mklaunchd-monitor
brew install mklaunchd-monitor
mklaunchd monitor <label>

Testing your plugin

mklaunchd-myplugin --description
mklaunchd-myplugin --help
mklaunchd-myplugin --version
mklaunchd help | grep myplugin
mklaunchd myplugin --help

First-party plugins

Plugin Command Description
mklaunchd-monitor mklaunchd monitor Watch jobs and alert on state changes

Community plugins are listed in the mklaunchd wiki.


Getting help

Open an issue at https://github.com/uradical/mklaunchd if you have questions about the plugin API or want to list your plugin in the wiki.