Set up macOS launchd service for auto-starting Python applications
/plugin marketplace add pborenstein/plinth/plugin install pborenstein-plinth@pborenstein/plinthThis skill inherits all available tools. When active, it can use any tool Claude has access to.
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"
You'll need:
dev.{username} (e.g., "dev.philip")Read pyproject.toml to infer defaults:
# Project name
grep "^name" pyproject.toml
# Check for [project.scripts]
grep -A 5 "\[project.scripts\]" pyproject.toml
Create launchd/ directory:
mkdir -p launchd
For each template in skills/macos-launchd-service/templates/, perform substitutions:
Substitution variables:
{{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/pgrep{{USERNAME}}, {{HOME}}, {{PROJECT_DIR}}, {{VENV_PYTHON}}, {{VENV_BIN}} - Auto-detected by install.shCLI_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