MacOS Launch Agent CLI - easily create, list, update, and delete launch agent PLIST files for MacOS login items.
Find a file
2026-01-15 07:47:57 -08:00
.gitignore Add uv package structure and pytest test framework 2026-01-11 19:18:17 -08:00
.pre-commit-config.yaml Add pre-commit hooks with ty type checking 2026-01-11 19:51:34 -08:00
.python-version Add uv package structure and pytest test framework 2026-01-11 19:18:17 -08:00
CLAUDE.md Add CLAUDE.md for Claude Code guidance 2026-01-12 16:53:43 -08:00
conftest.py Add uv package structure and pytest test framework 2026-01-11 19:18:17 -08:00
mcquack.py Enhance list command with visual separators 2026-01-13 11:03:33 -08:00
pyproject.toml Release for v1.0.0 2026-01-15 07:47:57 -08:00
README.md Refactor commands to accept plist names and enhance list with rich tables 2026-01-13 10:50:27 -08:00
test_mcquack.py Refactor commands to accept plist names and enhance list with rich tables 2026-01-13 10:50:27 -08:00
uv.lock Release for v1.0.0 2026-01-15 07:47:57 -08:00

mcquack

A simple macOS LaunchAgent manager for executable commands and scripts.

Named after Launchpad McQuack, the fearless (if accident-prone) pilot from DuckTales.

Requirements

  • macOS (uses launchctl and ~/Library/LaunchAgents)
  • uv

Installation

No installation required! Run directly with:

uvx git+https://forge.tail8d86e.ts.net/eblume/mcquack

Or clone and run the script directly (it's already executable).

Usage

# List all mcquack-managed LaunchAgents (shows formatted table)
mcquack list

# Create and load an executable as a LaunchAgent
mcquack create /path/to/your/script

# Or use a command from $PATH
mcquack create mycommand

# Create with additional arguments (note the -- separator)
mcquack create /path/to/your/script -- --arg1 value1 --arg2

# Manage LaunchAgents by name (after creation)
# You can use: just the stem ("myscript"), the label ("mcquack.eblume.myscript"),
# the filename ("mcquack.eblume.myscript.plist"), or the full path

# Kickstart (immediately run) the LaunchAgent
mcquack launch myscript

# Show the current arguments configured in the plist
mcquack show myscript

# Edit the plist in $EDITOR (falls back to vi)
mcquack edit myscript

# Unload (stop) the LaunchAgent
mcquack unload myscript

# Delete the LaunchAgent plist file
mcquack delete myscript

Command Resolution: Paths vs Commands

mcquack intelligently handles two types of arguments:

Paths (contains /, ~, or .):

  • Relative paths: ./script.sh, ../bin/tool
  • Absolute paths: /usr/local/bin/script
  • Home paths: ~/bin/myscript
  • All paths are expanded and resolved to absolute paths
  • Symlinks are resolved to their targets

Commands (no /):

  • Searched on $PATH: python, node, mycommand
  • Stored as-is (not expanded to absolute paths)
  • Must exist on $PATH or creation fails
# Path examples (expanded to absolute paths in plist)
mcquack create ./myscript.sh
mcquack create ~/bin/backup.sh
mcquack create ../tools/monitor

# Command examples (kept as-is in plist)
mcquack create python -- -m http.server 8000
mcquack create node -- server.js

Passing Arguments to Your Command

When passing arguments to your command with create, you must use -- to separate mcquack's options from arguments intended for your command:

# Correct: passes --config and --debug to your command
mcquack create my_script.sh -- --config /path/to/config --debug

# Wrong: --help is interpreted as mcquack's --help flag, shows help instead
mcquack create my_script.sh --help

# Correct: passes --help as an argument to your command
mcquack create my_script.sh -- --help

The -- separator ensures that flags like --help, --verbose, etc. are passed to your command rather than being interpreted by mcquack itself.

To modify arguments after creation, use mcquack edit to open the plist in your editor.

How it works

mcquack creates plist files in ~/Library/LaunchAgents/ with the naming convention:

mcquack.eblume.<commandname>.plist

The generated plist configures the command to:

  • Run at load (or on schedule if --interval or --calendar specified)
  • Keep alive (restart if it exits, unless scheduled)
  • Log stdout/stderr to ~/Library/Logs/mcquack.<commandname>.{out,err}.log

Path Resolution Details

When you provide a path (containing /, ~, or .), mcquack:

  1. Expands ~ to your home directory
  2. Resolves relative paths to absolute paths
  3. Resolves .. and . components
  4. Resolves symlinks to their targets
  5. Validates the file exists and is executable
  6. Stores the absolute path in the plist

When you provide a command name (no /), mcquack:

  1. Searches for the command on your $PATH
  2. Validates the command is executable
  3. Stores the command name as-is in the plist (not the full path)
  4. Lets launchd resolve the command at runtime using its environment

Development

mcquack uses uv for dependency management. Thanks to the uv shebang at the top of mcquack.py, you can always run the script directly without any setup:

./mcquack.py list

This works for users and developers alike—no virtual environment activation or python command needed. uv handles everything automatically.

Running Tests

uv run pytest

Virtual Environment (Optional)

If you prefer working in a virtual environment (e.g., for IDE support or running python mcquack.py directly), you can create and activate one:

uv venv
source .venv/bin/activate

After activation, python mcquack.py will work as expected. But again, ./mcquack.py always works without this step.

License

MIT