eddiezane/lunchy
A friendly wrapper for launchctl
Stale — last commit 5y ago
worst of 4 axeslast commit was 5y ago; no tests detected…
no tests detected; no CI workflows detected…
Documented and popular — useful reference codebase to read through.
last commit was 5y ago; no CI workflows detected
- ✓25+ active contributors
- ✓Distributed ownership (top contributor 38% of recent commits)
- ✓MIT licensed
Show 3 more →Show less
- ⚠Stale — last commit 5y ago
- ⚠No CI workflows detected
- ⚠No test directory detected
What would change the summary?
- →Use as dependency Mixed → Healthy if: 1 commit in the last 365 days; add a test suite
- →Fork & modify Mixed → Healthy if: add a test suite
- →Deploy as-is Mixed → Healthy if: 1 commit in the last 180 days
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Great to learn from" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/eddiezane/lunchy)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/eddiezane/lunchy on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: eddiezane/lunchy
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/eddiezane/lunchy shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
WAIT — Stale — last commit 5y ago
- 25+ active contributors
- Distributed ownership (top contributor 38% of recent commits)
- MIT licensed
- ⚠ Stale — last commit 5y ago
- ⚠ No CI workflows detected
- ⚠ No test directory detected
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live eddiezane/lunchy
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/eddiezane/lunchy.
What it runs against: a local clone of eddiezane/lunchy — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in eddiezane/lunchy | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch main exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 1939 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of eddiezane/lunchy. If you don't
# have one yet, run these first:
#
# git clone https://github.com/eddiezane/lunchy.git
# cd lunchy
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of eddiezane/lunchy and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "eddiezane/lunchy(\\.git)?\\b" \\
&& ok "origin remote is eddiezane/lunchy" \\
|| miss "origin remote is not eddiezane/lunchy (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
&& ok "default branch main exists" \\
|| miss "default branch main no longer exists"
# 4. Critical files exist
test -f "bin/lunchy" \\
&& ok "bin/lunchy" \\
|| miss "missing critical file: bin/lunchy"
test -f "lib/lunchy.rb" \\
&& ok "lib/lunchy.rb" \\
|| miss "missing critical file: lib/lunchy.rb"
test -f "lunchy.gemspec" \\
&& ok "lunchy.gemspec" \\
|| miss "missing critical file: lunchy.gemspec"
test -f "README.md" \\
&& ok "README.md" \\
|| miss "missing critical file: README.md"
test -f "Gemfile" \\
&& ok "Gemfile" \\
|| miss "missing critical file: Gemfile"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 1939 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~1909d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/eddiezane/lunchy"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Lunchy is a Ruby CLI wrapper around macOS's launchctl that dramatically simplifies launching, stopping, and managing launch agents and daemons. Instead of typing exact plist filenames with launchctl's verbose syntax, developers use simple pattern-matching commands like lunchy start redis or lunchy ls to manage background services on their Mac. Single-file core architecture: lib/lunchy.rb contains the main Lunchy class wrapping launchctl operations, with bin/lunchy as the CLI entry point. Shell completion helpers in extras/ for bash and zsh. No complex layering—wrapper pattern directly over system launchctl calls.
👥Who it's for
macOS developers who regularly manage background services (databases, caches, queue workers) and want faster, less error-prone commands than native launchctl. DevOps engineers and developers working on local development environments benefit from the pattern-matching shorthand.
🌱Maturity & risk
This is a stable, production-ready utility with a long history (original author Mike Perham, now maintained by Eddie Zaneski). It has multiple contributors and is distributed via both RubyGems and Homebrew, indicating solid adoption. However, without visible CI/CD configuration files in the provided structure, automated testing coverage is unclear.
Single maintainer (eddiezane) poses continuity risk for a tool in active use. The codebase is small (8,470 lines Ruby + 580 shell) with minimal dependencies listed, reducing supply-chain risk. Lack of visible test files (none mentioned in top 60 files) and no CI pipeline evident suggests potential regression risks when modifying core launchctl interaction code.
Active areas of work
No specific recent activity data provided in the file structure. The repo appears stable rather than actively developed; focus is likely on maintenance and bug fixes rather than feature expansion.
🚀Get running
git clone https://github.com/eddiezane/lunchy.git
cd lunchy
bundle install
bundle exec bin/lunchy ls
Daily commands:
bundle exec bin/lunchy [command] [pattern]
# Examples:
bundle exec bin/lunchy ls
bundle exec bin/lunchy start redis
bundle exec bin/lunchy restart mysql
🗺️Map of the codebase
bin/lunchy— Entry point CLI script that users execute; every contributor must understand the command dispatch flowlib/lunchy.rb— Core library containing all command implementations (start, stop, ls, etc.) and launchctl wrapper logic—the heart of the projectlunchy.gemspec— Gem specification defining dependencies, executables, and metadata; required for proper installation and distributionREADME.md— User-facing documentation that explains the problem being solved and command syntax—critical for understanding project scopeGemfile— Dependency management for development; shows what runtime gems are requiredHistory.md— Changelog tracking feature additions and breaking changes; helps contributors understand project evolution and compatibility concerns
🧩Components & responsibilities
- bin/lunchy (Ruby, shell execution) — Parse command-line arguments and dispatch to library functions; handle help and invalid commands
- Failure mode: Crashes on invalid arguments; no robust error handling visible in entry point
- lib/lunchy.rb (Ruby File I/O, Ruby system calls, launchctl CLI) — Implement core logic: scan plist directories, match patterns, call launchctl, format output
- Failure mode: Pattern matches zero or multiple files → returns error list instead of acting; launchctl failures silently propagate
- extras/lunchy-completion.* (Bash/Zsh syntax) — Provide shell auto-completion for commands and agent names
- Failure mode: Completion fails if plist directory is inaccessible or nonexistent
🔀Data flow
User terminal→bin/lunchy— Command-line arguments (e.g., 'start redis')bin/lunchy→lib/lunchy.rb— Function call with pattern argumentlib/lunchy.rb→Filesystem (~Library/LaunchAgents)— Read plist filenames to find matching agentslib/lunchy.rb→launchctl— System call to load/unload/start/stop plist fileslib/lunchy.rb→bin/lunchy— Return status or error messagebin/lunchy→User terminal— Print result or error to stdout/stderr
🛠️How to make changes
Add a new lunchy command (e.g., lunchy status)
- Open lib/lunchy.rb and add a new public method following the pattern of existing commands (start, stop, etc.) (
lib/lunchy.rb) - Implement the method to parse plist patterns, find matching agents, and call appropriate launchctl operations (
lib/lunchy.rb) - Update bin/lunchy to dispatch the new command via the case/when statement (
bin/lunchy) - Update README.md to document the new command in the feature list and examples (
README.md)
Add shell completion support for a new shell
- Create extras/lunchy-completion.SHELLNAME following the pattern of bash and zsh completion scripts (
extras/lunchy-completion.bash) - Define functions to complete lunchy commands and available agent patterns from plist filenames (
extras/lunchy-completion.SHELLNAME) - Document installation instructions in README.md (
README.md)
Release a new gem version
- Update version number in lunchy.gemspec (
lunchy.gemspec) - Add changelog entry to History.md documenting features, fixes, or breaking changes (
History.md) - Run Rakefile tasks to build and publish the gem (
Rakefile)
🔧Why these technologies
- Ruby — Cross-platform scripting language with strong string/file manipulation for CLI tools; natural for wrapping system binaries
- launchctl (via system calls) — Native macOS service manager; no direct Ruby gem available, so direct subprocess invocation is the only approach
- RubyGems — Standard Ruby package format enabling easy distribution via
gem install lunchy
⚖️Trade-offs already made
-
Direct launchctl subprocess invocation instead of native Objective-C
- Why: Keeps codebase 100% Ruby; simpler maintenance and cross-version compatibility
- Consequence: Slightly slower (subprocess overhead) but acceptable for user-driven CLI operations
-
Pattern matching on plist filenames instead of querying launchctl registry
- Why: Simpler user UX—users type short substrings; filesystem is predictable
- Consequence: Does not detect agents installed outside standard LaunchAgents directories; edge case for non-standard setups
-
Single monolithic lib/lunchy.rb instead of class-based architecture
- Why: Minimal scope (8 commands) makes flat module organization clear and maintainable
- Consequence: Harder to unit test; tight coupling of concerns; would need refactoring if commands grow significantly
🚫Non-goals (don't propose these)
- Does not support LaunchDaemons (system agents) — only LaunchAgents (user agents)
- Does not work on Linux or Windows — macOS-only tool
- Does not parse or validate plist XML — only matches on filename patterns
- Does not handle sudo or elevated privileges — assumes user-level agents
📊Code metrics
- Avg cyclomatic complexity: ~2.8 — Mostly straightforward file I/O and system calls; minimal branching; single-layer architecture with no recursive logic or state machines
- Largest file:
lib/lunchy.rb(200 lines) - Estimated quality issues: ~4 — No explicit error handling for launchctl failures, hardcoded paths, lack of input validation, and monolithic structure without modular tests
⚠️Anti-patterns to avoid
- Silent command execution failures (Medium) —
lib/lunchy.rb: launchctl commands are invoked via backticks with no exit code checking; failures are not explicitly reported to user - Ambiguity without clear feedback (Low) —
lib/lunchy.rb: When a pattern matches multiple plist files, the function returns without taking action—user gets warning but no list of options is displayed - Hardcoded paths (Low) —
lib/lunchy.rb: LaunchAgents path is hardcoded as ~/Library/LaunchAgents; does not respect env vars or alternative agent locations - No input validation (Medium) —
bin/lunchy: Command-line arguments are passed directly to library functions without sanitization; potential for shell injection if library uses system()
🔥Performance hotspots
lib/lunchy.rb directory scan (ls command)(I/O) — Scanning ~/Library/LaunchAgents via Dir.glob on every invocation; unoptimized but acceptable for typical agent counts (10–100 files)launchctl subprocess invocation(Process overhead) — Each command (start, stop, etc.) spawns a new launchctl process; multiple consecutive commands require multiple spawns
🪤Traps & gotchas
macOS-specific paths hardcoded: ~/Library/LaunchAgents and ~/Library/LaunchDaemons—lunchy will not work on Linux. Requires Ruby and Bundler installed locally. Pattern matching is substring-based and will warn if multiple plists match the same pattern, but doesn't error—user must disambiguate. Requires launchctl to be available in PATH (native to macOS). No explicit error handling visible for permission-denied scenarios when stopping/starting privileged agents.
🏗️Architecture
💡Concepts to learn
- macOS launchctl and plist format — Lunchy's entire purpose is abstracting away launchctl's complexity; understanding what launchctl does (load/unload agents) and plist structure is essential to understanding the wrapper's value
- Substring pattern matching over filesystem — Core feature of lunchy: matching user patterns like 'redis' against full plist names; understanding greedy vs. exact matching and collision detection is key to extending the tool
- System subprocess execution in Ruby — Lunchy wraps external system calls to launchctl; understanding Ruby's Kernel#system, backticks, or Open3 patterns is necessary to modify how commands are executed
- Shell completion (bash/zsh) — Lunchy includes bash and zsh completion scripts; understanding completion protocol helps extend CLI usability and add new command tab-completion
- Ruby Gem packaging and distribution — Lunchy is packaged via gemspec for RubyGems and Homebrew; understanding gem versioning, dependencies, and executable stubs is necessary for release and installation workflows
🔗Related repos
bamarni/symfony-console-completion— Similar CLI wrapper philosophy for Symfony; demonstrates how to abstract system tool complexity into developer-friendly commandsgithub/cli— Large-scale example of wrapping system complexity (GitHub's API) into intuitive CLI patterns with pattern matching and smart defaultsHomebrew/homebrew-core— Lunchy is distributed via Homebrew; understanding the formula ecosystem helps with installation and dependency resolutionrails/rails— Large Ruby codebase providing context on Ruby CLI patterns, RSpec testing conventions, and Bundler-based dependency management used here
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive test suite for lib/lunchy.rb
The repo has no visible test directory despite being a Ruby gem with a Rakefile. lib/lunchy.rb contains the core logic for wrapping launchctl (ls, start, stop, restart, status, install, uninstall, show, edit operations). Adding unit tests would catch regressions, document expected behavior, and make contributions safer.
- [ ] Create test/ directory with test_helper.rb
- [ ] Add unit tests in test/test_lunchy.rb covering each command (ls, start, stop, restart, status, install, uninstall, show, edit) with mocked launchctl calls
- [ ] Add rake test task to Rakefile for running tests
- [ ] Document testing instructions in README.md
Add GitHub Actions CI workflow for Ruby gem testing
No CI configuration exists (.github/workflows directory missing). For a Ruby gem distributed via RubyGems, automated testing on multiple Ruby versions (2.x, 3.x) and macOS environments would prevent platform-specific breakage since lunchy directly wraps macOS launchctl.
- [ ] Create .github/workflows/test.yml with matrix testing for Ruby 2.7, 3.0, 3.1, 3.2
- [ ] Configure workflow to run on macOS-latest (required for launchctl availability)
- [ ] Add gem build and bundler steps, then run rake test
- [ ] Update README.md with CI badge once working
Add shell completion installation and documentation to README.md
The repo includes completion scripts (extras/lunchy-completion.bash and extras/lunchy-completion.zsh) but lacks any documentation on how users should install or use them. This is a high-friction gap for users who discover the project.
- [ ] Add 'Installation' section to README.md with instructions for bash users to copy extras/lunchy-completion.bash to ~/.bash_completion.d/
- [ ] Add zsh completion instructions (copy to ~/.zsh/completions/_lunchy or equivalent for common zsh configs)
- [ ] Document how to reload/source the completions in the current shell
- [ ] Consider adding a postinstall hook in lunchy.gemspec to optionally auto-install completions during gem installation
🌿Good first issues
- Add automated test suite: Currently no test files visible in file structure (no spec/, test/, or *_test.rb files). Write RSpec tests for core Lunchy class methods (start, stop, restart) with mocked launchctl system calls.
- Document edge cases in README: Add troubleshooting section covering common failures (permission denied for system agents, plist not found, duplicate pattern matches) with solutions.
- Enhance pattern matching: Current substring matching is fragile for similarly-named agents. Consider adding a
--exactflag for exact plist name matching and improve the 'multiple matches' warning message with better disambiguation hints.
⭐Top contributors
Click to expand
Top contributors
- @mperham — 37 commits
- @eddiezane — 23 commits
- @bunnymatic — 9 commits
- @osaris — 3 commits
- @fhemberger — 3 commits
📝Recent commits
Click to expand
Recent commits
dfe1b06— Merge pull request #58 from eddiezane/add-credits-for-eddie-and-jon-to-readme (eddiezane)123acd2— Merge pull request #74 from vladimyr/homebrew (eddiezane)90c5dab— Update README.md (bunnymatic)4b60499— Add Homebrew install instructions (vladimyr)f3d9cce— Merge pull request #73 from hodog/master (eddiezane)9b3055d— Rewrote some sentences in README (ho-ian)c78e554— Version bump v0.10.4 (eddiezane)da9a3c4— Merge pull request #67 from corroded/fix_zsh_script (eddiezane)f5fdeba— Added -U flag to autoload (termux-io)baffc64— Version bump v0.10.3 (eddiezane)
🔒Security observations
Lunchy is a wrapper tool around macOS launchctl with moderate security concerns. The primary risks stem from potential command injection vulnerabilities when handling user-supplied patterns, unsafe file path handling in the install/edit commands, and unvalidated execution of external commands. Since this tool operates at the system level with launchctl and can load arbitrary services, command injection or path traversal vulnerabilities could have significant impact. The codebase appears to have no external dependencies (Ruby-based tool) and no hardcoded credentials visible. Immediate focus should be on input validation, path canonicalization, and safe command execution practices.
- High · Command Injection via Pattern Matching —
lib/lunchy.rb - pattern matching and command execution logic. The tool accepts user-supplied patterns that are used to match against plist filenames and are passed to launchctl commands. If pattern matching is implemented using shell expansion or string concatenation without proper escaping, an attacker could inject shell commands through specially crafted patterns (e.g., 'pattern; rm -rf /'). Fix: Implement strict input validation for patterns. Use parameterized/escaped command execution methods. Avoid shell interpolation. Use Ruby's Kernel.system with array arguments instead of backticks or %x{} with string interpolation. - High · Arbitrary File Execution via Install Command —
lib/lunchy.rb - install command implementation. The 'install [file]' command appears to accept file paths that are then passed to launchctl. Without proper validation, an attacker could install malicious plist files or reference files outside intended directories, potentially loading arbitrary code as a service. Fix: Validate that the provided file path is within expected directories (e.g., ~/Library/LaunchAgents/). Validate file ownership and permissions. Verify plist file integrity before installation. Restrict to absolute paths or whitelist specific directories. - Medium · Path Traversal in File Operations —
lib/lunchy.rb - edit and show command implementations. Commands like 'edit [pattern]' and 'show [pattern]' may open or read files based on pattern matching. Without proper path validation, an attacker could potentially access sensitive files outside the intended LaunchAgents directory using traversal sequences like '../'. Fix: Implement strict path validation. Resolve all paths to absolute paths and verify they remain within the intended directory. Use File.expand_path and validate against a whitelist directory. - Medium · Unverified External Command Execution —
lib/lunchy.rb - external command invocations (especially edit functionality). The tool wraps launchctl and may execute additional external commands (editors for 'edit' command, etc.). If editor selection or other external commands are not properly controlled, this could lead to arbitrary code execution. Fix: Use explicit, safe methods to invoke external programs. Validate and whitelist allowed editors. Use ENV variables cautiously. Never pass unsanitized user input directly to system commands.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.