Agent skill
macos-launchd-service
Set up macOS launchd service for auto-starting Python applications
Install this agent skill to your Project
npx add-skill https://github.com/pborenstein/plinth/tree/main/skills/macos-launchd-service
SKILL.md
macOS launchd Service Setup
Generate complete launchd service infrastructure for Python applications on macOS.
What This Skill Creates
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
When to Use This Skill
- Web services: FastAPI, Flask apps that should auto-start
- Background services: Daemons, periodic tasks
- Development servers: Local services you want always running
Before Running
Requirements:
- macOS 10.10+ with launchd
- Python project with uv and .venv
pyproject.tomlwith[project.scripts]defining a CLI command
Check pyproject.toml:
[project.scripts]
yourapp = "yourapp.cli:main"
IMPORTANT - Replacing Existing Setup:
If the project already has launchd/ directory or existing scripts:
- ALWAYS use the parameters provided by the user
- DO NOT read values from existing files
- The user wants to replace with NEW values, not keep old ones
- Overwrite existing files with the new parameter values
Step-by-Step Process
1. Gather Information from User
Ask the user for these parameters (use AskUserQuestion if needed):
- Domain (e.g., "dev.pborenstein", "com.pborenstein") - Reverse domain notation for service label
- Default suggestion:
dev.{username}(e.g., "dev.philip") - Best practice: Use owned domain (e.g., "dev.pborenstein" if you own pborenstein.dev)
- Default suggestion:
- Project name (e.g., "temoa", "apantli") - lowercase, no spaces
- Module name (e.g., "temoa", "apantli") - Python import name
- Port number (e.g., 4001, 4000) - unique port for this service
- CLI command (e.g., "temoa server", "python3 -m apantli.server")
- Dev command (e.g., "temoa server --reload", "python3 -m apantli.server --reload")
- Process name (e.g., "temoa server", "apantli.server") - for detecting running processes
Use these exact values provided by the user - do not infer from existing files.
2. Suggest Defaults from pyproject.toml (Optional)
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.
3. Generate launchd Directory
Create launchd/ directory:
mkdir -p launchd
4. Generate Files from Templates
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/pgrep
Auto-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 directory
CLI_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>
5. Generated Files
install.sh (from install.sh.template):
- Auto-detects environment (username, paths, venv)
- Validates venv and module are installed
- Generates service plist from template
- Uses
launchctl bootstrapto register and start service - Shows access information and points to
./dev.shfor management
uninstall.sh (from uninstall.sh.template):
- Checks if service exists
- Shows what will be removed
- Asks for confirmation
- Uses
launchctl bootoutto stop service - Removes plist file
- Confirms uninstall complete
{project}.plist.template (from service.plist.template):
- Service configuration with substitution placeholders
- Used by install.sh to generate final plist
- Configured with RunAtLoad, KeepAlive for production
dev.sh (from dev.sh.template):
- Subcommands: bare = dev mode,
start,stop,status - Dev mode: stops service, runs with auto-reload, uses caffeinate
- On exit: service stays stopped, no prompt -- prints
./dev.sh startreminder - Uses modern
launchctl bootstrap/bootout(not deprecatedload/unload)
view-logs.sh (from view-logs.sh.template):
- Modes: app logs, errors, or all
- Uses tail -f for live viewing
- Logs location:
~/Library/Logs/{project}.log
6. Make Scripts Executable
chmod +x launchd/install.sh
chmod +x launchd/uninstall.sh
chmod +x dev.sh
chmod +x view-logs.sh
7. Provide Next Steps
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:
./dev.sh stop Stop the service
./dev.sh start Start the service
./dev.sh status Show service status
./dev.sh Dev mode (stops service, runs with reload)
Example: temoa
Input parameters:
- Domain: dev.pborenstein
- Project name: temoa
- Module name: temoa
- Port: 4001
- CLI command: temoa server --host 0.0.0.0 --port 4001 --log-level info
- Dev command: temoa server --reload
- Process name: temoa server
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>
Implementation Notes
Reading templates:
Templates are in skills/macos-launchd-service/templates/:
install.sh.templateuninstall.sh.templateservice.plist.templatedev.sh.templateview-logs.sh.template
Writing generated files:
launchd/install.shlaunchd/uninstall.shlaunchd/{PROJECT_NAME}.plist.templatedev.shview-logs.sh
String 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.
Validation
After generation, verify:
- All 5 files created (install.sh, uninstall.sh, plist.template, dev.sh, view-logs.sh)
- Scripts are executable
- No leftover
{{VARIABLES}}in files - CLI_COMMAND properly formatted as plist array
Common Issues
Port 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
See Also
- references/usage.md - Detailed usage guide
- Example implementations: temoa, apantli in nahuatl-projects
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
python-project-init
Initialize complete Python project with comprehensive documentation, development environment, and tooling. Use when creating a new Python project from scratch.
fastapi-sweetener
Add FastAPI server capabilities to an existing Python project with uvicorn, OpenAPI docs, and configuration management
releaserator
Generate semantic versioned releases with changelog and GitHub releases
git-workflow-hooks
Install git hooks to prevent common workflow mistakes
edit-article
Edit and improve articles by restructuring sections, improving clarity, and tightening prose. Use when user wants to edit, revise, or improve an article draft.
handoff
Compact the current conversation into a handoff document for another agent to pick up.
Didn't find tool you were looking for?