- Rename script argument to command throughout codebase - Add resolve_command() function for smart path/command handling - Paths (containing /, ~, or .) are expanded to absolute paths and validated - Commands (no /) are searched on $PATH and kept as-is in plist - Add comprehensive test suite with 13 new tests for path resolution - Verify all resolved paths stay within expected boundaries - Update README with command resolution details and examples Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
164 lines
4.5 KiB
Markdown
164 lines
4.5 KiB
Markdown
# 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](https://github.com/astral-sh/uv)
|
|
|
|
## Installation
|
|
|
|
No installation required! Run directly with:
|
|
|
|
```bash
|
|
uvx git+https://forge.tail8d86e.ts.net/eblume/mcquack
|
|
```
|
|
|
|
Or clone and run the script directly (it's already executable).
|
|
|
|
## Usage
|
|
|
|
```bash
|
|
# List all mcquack-managed LaunchAgents
|
|
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
|
|
|
|
# Kickstart (immediately run) the LaunchAgent
|
|
mcquack launch /path/to/your/script
|
|
|
|
# Show the current arguments configured in the plist
|
|
mcquack show /path/to/your/script
|
|
|
|
# Edit the plist in $EDITOR (falls back to vi)
|
|
mcquack edit /path/to/your/script
|
|
|
|
# Unload (stop) the LaunchAgent
|
|
mcquack unload /path/to/your/script
|
|
|
|
# Delete the LaunchAgent plist file
|
|
mcquack delete /path/to/your/script
|
|
```
|
|
|
|
### 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
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
# 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](https://github.com/astral-sh/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:
|
|
|
|
```bash
|
|
./mcquack.py list
|
|
```
|
|
|
|
This works for users and developers alike—no virtual environment activation or `python`
|
|
command needed. uv handles everything automatically.
|
|
|
|
### Running Tests
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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
|