Set up macOS launchd service for auto-starting Python applications
Generates complete launchd service infrastructure for auto-starting Python apps on macOS. Use when you need background services, web apps, or daemons that auto-start on login and restart on crash.
/plugin marketplace add pborenstein/plinth/plugin install pborenstein-plinth@pborenstein/plinthThis skill is limited to using the following tools:
README.mdtemplates/dev.sh.templatetemplates/install.sh.templatetemplates/service.plist.templatetemplates/uninstall.sh.templatetemplates/view-logs.sh.templateGenerate complete launchd service infrastructure for Python applications on macOS.
launchd/
├── install.sh # Automated service installer
├── uninstall.sh # Service uninstaller
├── {project}.plist.template # Service configuration
dev.sh # Development mode script
view-logs.sh # Log viewing helper
Requirements:
pyproject.toml with [project.scripts] defining a CLI commandCheck pyproject.toml:
[project.scripts]
yourapp = "yourapp.cli:main"
IMPORTANT - Replacing Existing Setup:
If the project already has launchd/ directory or existing scripts:
Ask the user for these parameters (use AskUserQuestion if needed):
dev.{username} (e.g., "dev.philip")Use these exact values provided by the user - do not infer from existing files.
Only if the user hasn't provided values, you may suggest defaults from pyproject.toml:
# Suggest project name
grep "^name" pyproject.toml
# Suggest CLI command from [project.scripts]
grep -A 5 "\[project.scripts\]" pyproject.toml
But always use what the user explicitly provides.
Create launchd/ directory:
mkdir -p launchd
For each template in skills/macos-launchd-service/templates/, perform substitutions:
Substitution variables:
User-provided parameters (use values from step 1):
{{DOMAIN}} - Reverse domain notation (e.g., "dev.pborenstein", "com.pborenstein"){{PROJECT_NAME}} - Project name (e.g., "temoa"){{MODULE_NAME}} - Python module name for import check{{PORT}} - Port number{{CLI_COMMAND}} - Full CLI command as plist array elements{{DEV_COMMAND}} - Development mode command{{PROCESS_NAME}} - Process name for pkill/pgrepAuto-detected variables (these are filled in by install.sh at runtime):
{{HOME}} - User's home directory{{PROJECT_DIR}} - Absolute path to project directory{{VENV_PYTHON}} - Path to venv Python interpreter{{VENV_BIN}} - Path to venv bin directoryCLI_COMMAND special handling:
Must be converted to plist array format:
Input: "temoa server --host 0.0.0.0 --port 4001"
Output:
<string>{{VENV_BIN}}/temoa</string>
<string>server</string>
<string>--host</string>
<string>0.0.0.0</string>
<string>--port</string>
<string>4001</string>
install.sh (from install.sh.template):
uninstall.sh (from uninstall.sh.template):
{project}.plist.template (from service.plist.template):
dev.sh (from dev.sh.template):
view-logs.sh (from view-logs.sh.template):
~/Library/Logs/{project}.logchmod +x launchd/install.sh
chmod +x launchd/uninstall.sh
chmod +x dev.sh
chmod +x view-logs.sh
After generation, tell the user:
✓ Generated launchd service structure
Next steps:
1. Review generated files in launchd/
2. Run: ./launchd/install.sh
3. Access your service at: http://localhost:{PORT}
4. View logs: ./view-logs.sh
5. Development mode: ./dev.sh
Service will auto-start on login and auto-restart on crash.
Manage service:
Stop: launchctl unload ~/Library/LaunchAgents/{DOMAIN}.{project}.plist
Start: launchctl load ~/Library/LaunchAgents/{DOMAIN}.{project}.plist
Status: launchctl list | grep {project}
Input parameters:
Generated CLI_COMMAND for plist:
<array>
<string>{{VENV_BIN}}/temoa</string>
<string>server</string>
<string>--host</string>
<string>0.0.0.0</string>
<string>--port</string>
<string>4001</string>
<string>--log-level</string>
<string>info</string>
</array>
Reading templates:
Templates are in skills/macos-launchd-service/templates/:
install.sh.templateuninstall.sh.templateservice.plist.templatedev.sh.templateview-logs.sh.templateWriting generated files:
launchd/install.shlaunchd/uninstall.shlaunchd/{PROJECT_NAME}.plist.templatedev.shview-logs.shString substitution:
Simple replace all instances of each {{VARIABLE}} with its value.
CLI_COMMAND conversion:
Split on spaces, wrap each token in <string>TOKEN</string> with proper indentation.
After generation, verify:
{{VARIABLES}} in filesPort conflicts: Use lsof -i :{PORT} to check if port is available
Module not found: User needs to run uv sync first
Permission errors: Ensure ~/Library/LaunchAgents exists