mac-upkeep 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mac_upkeep-2.1.0/.github/workflows/release.yml +87 -0
- mac_upkeep-2.1.0/.github/workflows/test.yml +16 -0
- mac_upkeep-2.1.0/.gitignore +11 -0
- mac_upkeep-2.1.0/.release-please-manifest.json +3 -0
- mac_upkeep-2.1.0/CHANGELOG.md +79 -0
- mac_upkeep-2.1.0/CLAUDE.md +128 -0
- mac_upkeep-2.1.0/CONTRIBUTING.md +58 -0
- mac_upkeep-2.1.0/LICENSE +21 -0
- mac_upkeep-2.1.0/PKG-INFO +145 -0
- mac_upkeep-2.1.0/README.md +118 -0
- mac_upkeep-2.1.0/pyproject.toml +52 -0
- mac_upkeep-2.1.0/release-please-config.json +17 -0
- mac_upkeep-2.1.0/src/mac_upkeep/__init__.py +0 -0
- mac_upkeep-2.1.0/src/mac_upkeep/cli.py +439 -0
- mac_upkeep-2.1.0/src/mac_upkeep/config.py +264 -0
- mac_upkeep-2.1.0/src/mac_upkeep/defaults.toml +81 -0
- mac_upkeep-2.1.0/src/mac_upkeep/notify.py +106 -0
- mac_upkeep-2.1.0/src/mac_upkeep/output.py +206 -0
- mac_upkeep-2.1.0/src/mac_upkeep/tasks.py +246 -0
- mac_upkeep-2.1.0/tests/__init__.py +0 -0
- mac_upkeep-2.1.0/tests/test_cli.py +174 -0
- mac_upkeep-2.1.0/tests/test_config.py +401 -0
- mac_upkeep-2.1.0/tests/test_notify.py +208 -0
- mac_upkeep-2.1.0/tests/test_output.py +131 -0
- mac_upkeep-2.1.0/tests/test_tasks.py +367 -0
- mac_upkeep-2.1.0/uv.lock +191 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
permissions:
|
|
6
|
+
contents: write
|
|
7
|
+
pull-requests: write
|
|
8
|
+
jobs:
|
|
9
|
+
release-please:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
outputs:
|
|
12
|
+
release_created: ${{ steps.release.outputs.release_created }}
|
|
13
|
+
tag_name: ${{ steps.release.outputs.tag_name }}
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/create-github-app-token@v2
|
|
16
|
+
id: app-token
|
|
17
|
+
with:
|
|
18
|
+
app-id: ${{ vars.RELEASE_PLEASE_APP_ID }}
|
|
19
|
+
private-key: ${{ secrets.RELEASE_PLEASE_PRIVATE_KEY }}
|
|
20
|
+
- id: release
|
|
21
|
+
uses: googleapis/release-please-action@v4
|
|
22
|
+
with:
|
|
23
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
24
|
+
config-file: release-please-config.json
|
|
25
|
+
|
|
26
|
+
- name: Checkout release PR branch
|
|
27
|
+
if: ${{ steps.release.outputs.pr && !steps.release.outputs.release_created }}
|
|
28
|
+
uses: actions/checkout@v4
|
|
29
|
+
with:
|
|
30
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
31
|
+
ref: release-please--branches--main
|
|
32
|
+
|
|
33
|
+
- name: Setup uv
|
|
34
|
+
if: ${{ steps.release.outputs.pr && !steps.release.outputs.release_created }}
|
|
35
|
+
uses: astral-sh/setup-uv@v5
|
|
36
|
+
|
|
37
|
+
- name: Update uv.lock in release PR
|
|
38
|
+
if: ${{ steps.release.outputs.pr && !steps.release.outputs.release_created }}
|
|
39
|
+
run: |
|
|
40
|
+
uv lock
|
|
41
|
+
if git diff --quiet uv.lock; then
|
|
42
|
+
echo "uv.lock already up to date"
|
|
43
|
+
else
|
|
44
|
+
git config user.name "github-actions[bot]"
|
|
45
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
46
|
+
git add uv.lock
|
|
47
|
+
git commit -m "chore: update uv.lock"
|
|
48
|
+
git push
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
bump-tap:
|
|
52
|
+
needs: release-please
|
|
53
|
+
if: ${{ needs.release-please.outputs.release_created == 'true' }}
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/create-github-app-token@v2
|
|
57
|
+
id: app-token
|
|
58
|
+
with:
|
|
59
|
+
app-id: ${{ vars.RELEASE_PLEASE_APP_ID }}
|
|
60
|
+
private-key: ${{ secrets.RELEASE_PLEASE_PRIVATE_KEY }}
|
|
61
|
+
repositories: homebrew-tap
|
|
62
|
+
- name: Dispatch formula update
|
|
63
|
+
uses: peter-evans/repository-dispatch@v3
|
|
64
|
+
with:
|
|
65
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
66
|
+
repository: calvindotsg/homebrew-tap
|
|
67
|
+
event-type: formula-update
|
|
68
|
+
client-payload: |-
|
|
69
|
+
{
|
|
70
|
+
"formula": "mac-upkeep",
|
|
71
|
+
"version": "${{ needs.release-please.outputs.tag_name }}",
|
|
72
|
+
"url": "https://github.com/calvindotsg/maintenance/archive/refs/tags/${{ needs.release-please.outputs.tag_name }}.tar.gz",
|
|
73
|
+
"type": "python"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pypi-publish:
|
|
77
|
+
needs: release-please
|
|
78
|
+
if: ${{ needs.release-please.outputs.release_created == 'true' }}
|
|
79
|
+
runs-on: ubuntu-latest
|
|
80
|
+
environment: pypi
|
|
81
|
+
permissions:
|
|
82
|
+
id-token: write
|
|
83
|
+
steps:
|
|
84
|
+
- uses: actions/checkout@v4
|
|
85
|
+
- uses: astral-sh/setup-uv@v5
|
|
86
|
+
- run: uv build
|
|
87
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [main]
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: macos-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: astral-sh/setup-uv@v5
|
|
13
|
+
- run: uv lock --check
|
|
14
|
+
- run: uv sync
|
|
15
|
+
- run: uv run ruff check src/ tests/
|
|
16
|
+
- run: uv run pytest
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [2.1.0](https://github.com/calvindotsg/maintenance/compare/v2.0.0...v2.1.0) (2026-04-08)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* custom task support, detect auto-inference, validation ([#16](https://github.com/calvindotsg/maintenance/issues/16)) ([d8af4e6](https://github.com/calvindotsg/maintenance/commit/d8af4e62620ebbf1088e0e55c9faf7cdbe0f0785))
|
|
9
|
+
* PyPI publishing and platform guard ([#18](https://github.com/calvindotsg/maintenance/issues/18)) ([af5e341](https://github.com/calvindotsg/maintenance/commit/af5e341e0798e7cd5e997de5d9dea17897ff369e))
|
|
10
|
+
|
|
11
|
+
## [2.0.0](https://github.com/calvindotsg/maintenance/compare/v1.2.1...v2.0.0) (2026-04-08)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### ⚠ BREAKING CHANGES
|
|
15
|
+
|
|
16
|
+
* Package renamed from maintenance to mac-upkeep.
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* rename package to mac-upkeep ([#15](https://github.com/calvindotsg/maintenance/issues/15)) ([aef9e86](https://github.com/calvindotsg/maintenance/commit/aef9e8604f3c3a95286f06274e7737d81ec1dc5a))
|
|
21
|
+
* TOML-driven task registry + init/show-config commands ([#12](https://github.com/calvindotsg/maintenance/issues/12)) ([23a7f0b](https://github.com/calvindotsg/maintenance/commit/23a7f0b53931eae733a8a7bf9cf141e5c0460797))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Documentation
|
|
25
|
+
|
|
26
|
+
* update README and CLAUDE.md for TOML-driven architecture ([#14](https://github.com/calvindotsg/maintenance/issues/14)) ([2ce0583](https://github.com/calvindotsg/maintenance/commit/2ce058350b5d0665113ec3064d299ce777709a30))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### CI/CD
|
|
30
|
+
|
|
31
|
+
* keep uv.lock in sync with release-please version bumps ([#10](https://github.com/calvindotsg/maintenance/issues/10)) ([d6a81a9](https://github.com/calvindotsg/maintenance/commit/d6a81a9c72cb3a764708d74e505b0f5a86a14697))
|
|
32
|
+
|
|
33
|
+
## [1.2.1](https://github.com/calvindotsg/maintenance/compare/v1.2.0...v1.2.1) (2026-04-07)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Bug Fixes
|
|
37
|
+
|
|
38
|
+
* --force filter, frequency scheduling, task discoverability ([#9](https://github.com/calvindotsg/maintenance/issues/9)) ([26a5ac1](https://github.com/calvindotsg/maintenance/commit/26a5ac123406836266480f246ba4c7f1b8789cd7))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Documentation
|
|
42
|
+
|
|
43
|
+
* **claude:** document v1.2.0 patterns for AI agent effectiveness ([#7](https://github.com/calvindotsg/maintenance/issues/7)) ([a6d5f0d](https://github.com/calvindotsg/maintenance/commit/a6d5f0dca8c51cb2b97e71ea35c95771f9fb8abf))
|
|
44
|
+
|
|
45
|
+
## [1.2.0](https://github.com/calvindotsg/maintenance/compare/v1.1.1...v1.2.0) (2026-04-07)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
### Features
|
|
49
|
+
|
|
50
|
+
* add brew tasks, frequency scheduling, live TUI, and actionable notifications ([#5](https://github.com/calvindotsg/maintenance/issues/5)) ([9ee2381](https://github.com/calvindotsg/maintenance/commit/9ee23811fa60fbb483b4ff695aabc92a8c23899c))
|
|
51
|
+
|
|
52
|
+
## [1.1.1](https://github.com/calvindotsg/maintenance/compare/v1.1.0...v1.1.1) (2026-04-06)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
### Bug Fixes
|
|
56
|
+
|
|
57
|
+
* **tasks:** close stdin and add --force to uv cache prune ([#4](https://github.com/calvindotsg/maintenance/issues/4)) ([4692060](https://github.com/calvindotsg/maintenance/commit/46920604eaccf8e8402d8cb0e70bb3c0e4b22606))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### Documentation
|
|
61
|
+
|
|
62
|
+
* **claude:** add release-please manifest and token scoping constraints ([8cd3775](https://github.com/calvindotsg/maintenance/commit/8cd377570eb332fe00ef1367fc1743af84b2e667))
|
|
63
|
+
|
|
64
|
+
## [1.1.0](https://github.com/calvindotsg/maintenance/compare/v1.0.0...v1.1.0) (2026-04-05)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
### Features
|
|
68
|
+
|
|
69
|
+
* rich output, macOS notifications, and tap automation ([#1](https://github.com/calvindotsg/maintenance/issues/1)) ([c0d9577](https://github.com/calvindotsg/maintenance/commit/c0d9577c13dcf4902af37b3c4a72d448fb9e93a3))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
### Bug Fixes
|
|
73
|
+
|
|
74
|
+
* add chmod 0440 to sudoers setup instructions ([060d848](https://github.com/calvindotsg/maintenance/commit/060d848de637e9db72168336d04ac516d2fb3ec4))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
### Miscellaneous
|
|
78
|
+
|
|
79
|
+
* add release-please manifest and gitignore .scratchpad ([0e8d983](https://github.com/calvindotsg/maintenance/commit/0e8d983826ec2735667ed321e8ee77a5c2175343))
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
## Quick Commands
|
|
4
|
+
|
|
5
|
+
| Command | Purpose |
|
|
6
|
+
|---------|---------|
|
|
7
|
+
| `uv sync` | Install dependencies |
|
|
8
|
+
| `uv run ruff check src/ tests/` | Lint |
|
|
9
|
+
| `uv run ruff format src/ tests/` | Format |
|
|
10
|
+
| `uv run pytest` | Run tests |
|
|
11
|
+
| `uv run pytest --cov` | Run tests with coverage |
|
|
12
|
+
| `uv run mac-upkeep run --dry-run` | Test CLI without side effects |
|
|
13
|
+
| `uv run mac-upkeep init` | Generate starter config (auto-detect tools) |
|
|
14
|
+
| `uv run mac-upkeep show-config --default` | Show all available task options |
|
|
15
|
+
| `uv run mac-upkeep notify-test` | Verify macOS notification permissions |
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
defaults.toml → bundled task definitions (11 tasks), loaded via importlib.resources
|
|
21
|
+
config.py → TaskDef dataclass, load_task_defs(), resolve_variables(), get_brew_prefix(),
|
|
22
|
+
Config.load() (3-layer merge: defaults.toml → user config → env vars)
|
|
23
|
+
tasks.py → _build_cmd(), run_task(), _run(), run_all_tasks() data-driven loop,
|
|
24
|
+
frequency scheduling, ANSI stripping
|
|
25
|
+
cli.py → Typer app: run, tasks, init, show-config, setup, status, logs, notify-test
|
|
26
|
+
output.py → TaskResult dataclass, Rich Live table TUI (interactive), Python logging (non-interactive)
|
|
27
|
+
notify.py → macOS notifications via terminal-notifier (preferred) / osascript (fallback)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Entry point: `mac_upkeep.cli:app` (registered in pyproject.toml `[project.scripts]`).
|
|
31
|
+
|
|
32
|
+
Task execution order defined in `defaults.toml` `[run] order`. Users override in `~/.config/mac-upkeep/config.toml`.
|
|
33
|
+
|
|
34
|
+
## Key Patterns
|
|
35
|
+
|
|
36
|
+
### TOML-driven task registry
|
|
37
|
+
|
|
38
|
+
- **`defaults.toml` is the single source of truth.** Adding a built-in task = 1 TOML entry. No Python code changes needed. The file is bundled via `importlib.resources.files("mac_upkeep")` and loaded at startup.
|
|
39
|
+
- **`TaskDef` fields are minimal.** `defaults.toml` only specifies fields that differ from `TaskDef` dataclass defaults (`frequency="weekly"`, `enabled=True`, `sudo=False`, `shell=""`, `require_file=""`, `timeout=300`). Weekly tasks omit `frequency`.
|
|
40
|
+
- **Config.load() reads user TOML once.** Parsed data is passed to `load_task_defs()` as a dict — avoids double file read. Brewfile and notification settings are extracted from the same parse.
|
|
41
|
+
|
|
42
|
+
### Detection and execution
|
|
43
|
+
|
|
44
|
+
- **`detect` is separate from `command`**: `detect` field specifies which binary to check with `shutil.which()`. This is separate from the command that runs. Reason: for sudo tasks, `_build_cmd()` prepends `["sudo", "-n"]` to the command — if detect used `cmd[0]`, it would check `shutil.which("sudo")` instead of the actual tool. Validated against topgrade's `require("binary")` pattern.
|
|
45
|
+
- **`_build_cmd()` composable**: `sudo` and `shell` are independent flags. Both branches (shell vs non-shell) merge before the sudo check. No early return in the shell branch — sudo wraps the entire invocation.
|
|
46
|
+
- **stdin closed for task subprocesses**: `stdin=subprocess.DEVNULL` prevents interactive tool hangs. Mole detects interactive mode via `[[ -t 0 ]]` (stdin is TTY) or unguarded `read_key` calls. Fisher uses `$last_pid` for job control which fails in non-interactive shells ([fisher#608](https://github.com/jorgebucaran/fisher/issues/608)), hence `shell = "fish --interactive -c"`.
|
|
47
|
+
|
|
48
|
+
### Filter-then-frequency contract
|
|
49
|
+
|
|
50
|
+
`_run()` has two early-return checks in strict order:
|
|
51
|
+
1. **Filter** (`force_tasks is not None and task_key not in force_tasks`) — unconditional
|
|
52
|
+
2. **Frequency** (`not dry_run and force_tasks is None and not _should_run()`) — conditional
|
|
53
|
+
|
|
54
|
+
Do not add conditions to the filter or remove the `force_tasks is None` guard from frequency. `require_file` tasks are checked in `run_all_tasks()` BEFORE calling `_run()`, respecting filter→enabled→file order.
|
|
55
|
+
|
|
56
|
+
### Frequency scheduling
|
|
57
|
+
|
|
58
|
+
- Thresholds are 6 days for weekly and 27 days for monthly (not 7/30 — buffer for launchd schedule drift after sleep/reboot). State tracked in `~/.local/state/mac-upkeep/last-run.json`.
|
|
59
|
+
- **Safety net**: prevents redundant runs from launchd coalescing, manual `mac-upkeep run`, or formula regressions re-enabling RunAtLoad. Do not remove even with `run_at_load false`. Homebrew defaults `run_at_load` to `true` ([service.rb:55](https://github.com/Homebrew/brew/blob/main/Library/Homebrew/service.rb)) — undocumented in Formula Cookbook.
|
|
60
|
+
- Timestamps only update on successful non-dry-run execution. Corrupt/missing state file silently triggers re-run.
|
|
61
|
+
|
|
62
|
+
### Output and notifications
|
|
63
|
+
|
|
64
|
+
- **Interactive detection**: `sys.stdout.isatty()` switches between Rich Live table and Python logging. Same code path, different presentation.
|
|
65
|
+
- **Rich Live TUI state separation**: `_TaskState` holds status/reason/duration; `_generate_table()` renders. Debug output scrolls ABOVE the pinned table via `self._live.console.print()` — never put debug content into `_TaskState`.
|
|
66
|
+
- **Notifications always fire**: `notify()` is called regardless of `output.interactive`. The headless + notification + click-to-act pattern means notifications are the user's feedback channel for scheduled runs.
|
|
67
|
+
- **terminal-notifier preferred**: `shutil.which("terminal-notifier")` tries the richer tool first. Fallback to osascript loses `-group` (dedup), `-activate` (focus terminal), `-open` (click action).
|
|
68
|
+
- **Bundle ID detection chain**: `CMUX_BUNDLE_ID` env var → Ghostty.app plist via `defaults read` → `com.apple.Terminal` fallback.
|
|
69
|
+
- **Rich is a transitive dependency**: `typer>=0.12` requires `rich>=12.3.0`. Using Rich adds zero new runtime dependencies.
|
|
70
|
+
|
|
71
|
+
### sudo + HOME
|
|
72
|
+
|
|
73
|
+
`sudo -n` with full path `$BREW_PREFIX/bin/mo`. Sudoers `env_keep += "HOME"` preserves user's home directory (otherwise `HOME=/var/root` and mole misses user caches). The `sudo` field in `TaskDef` exists instead of embedding `sudo` in the command so that: (a) dry-run can skip sudo, (b) `detect` infers the correct binary, (c) `mac-upkeep setup` can generate sudoers rules.
|
|
74
|
+
|
|
75
|
+
### Brew prefix detection
|
|
76
|
+
|
|
77
|
+
`get_brew_prefix()` lives in `config.py` (moved from tasks.py). Called once during `Config.load()` for `${BREW_PREFIX}` variable resolution. Subprocess call with architecture fallback — portable across Apple Silicon (`/opt/homebrew`) and Intel (`/usr/local`).
|
|
78
|
+
|
|
79
|
+
## Non-Obvious Constraints
|
|
80
|
+
|
|
81
|
+
- `gcloud-cli` is a Homebrew cask, not a formula — can't be a formula dependency. Auto-detected at runtime.
|
|
82
|
+
- `mo_purge` non-interactive mode (stdin closed) auto-selects items not modified in the last 7 days. Interactive mode shows a TUI selector.
|
|
83
|
+
- `mo_optimize` has three unguarded `read_key` calls that block on stdin. With stdin closed, `read` returns non-zero → prompts skipped. Safe operations still run.
|
|
84
|
+
- `uv cache prune` requires `--force` in environments with long-running `uvx` processes (MCP servers). Without it, prune blocks on the cache lock ([astral-sh/uv#16112](https://github.com/astral-sh/uv/issues/16112)).
|
|
85
|
+
- NOPASSWD in sudoers bypasses PAM entirely — no interaction with Touch ID (pam_tid) setup.
|
|
86
|
+
- **launchd PATH requires `std_service_path_env`**: launchd default PATH is `/usr/bin:/bin:/usr/sbin:/sbin`. Without `environment_variables PATH: std_service_path_env` in the formula service block, all Homebrew-installed tools fail `shutil.which()`. Notifications fall back to osascript.
|
|
87
|
+
- **No Python FileHandler for log file**: launchd redirects stderr to the log file. Python `FileHandler` causes duplicate lines. Log rotation handled by macOS newsyslog.d.
|
|
88
|
+
- **newsyslog.d config** printed by `mac-upkeep setup` but NOT auto-installed (requires `sudo tee`).
|
|
89
|
+
- **`terminal-notifier` is optional** — installed via `brew install terminal-notifier`.
|
|
90
|
+
- **Do not open terminal windows from launchd** — fragile (focus stealing, macOS 13+ permission escalation). Use headless + notification + click-to-act.
|
|
91
|
+
- **Testing**: `Config.load()` calls `get_brew_prefix()` which runs `subprocess.run(["brew", "--prefix"])`. Tests that mock `subprocess.run` or `shutil.which` will capture this call too (shared module objects). Mock `mac_upkeep.config.get_brew_prefix` directly in `init` command tests.
|
|
92
|
+
|
|
93
|
+
## Release Process
|
|
94
|
+
|
|
95
|
+
Automated via release-please + homebrew-tap dispatch:
|
|
96
|
+
|
|
97
|
+
1. Commit changes using conventional commits, push to main
|
|
98
|
+
2. release-please creates a release PR (bumps version in `pyproject.toml`, updates CHANGELOG.md)
|
|
99
|
+
3. Release workflow auto-updates `uv.lock` on the PR branch, test.yml validates via `uv lock --check`
|
|
100
|
+
4. Merge the release PR → GitHub release + tag created → `bump-tap` dispatches to homebrew-tap, `pypi-publish` publishes to PyPI
|
|
101
|
+
5. Verify: check homebrew-tap Actions tab for successful formula update
|
|
102
|
+
|
|
103
|
+
## Reusable Patterns
|
|
104
|
+
|
|
105
|
+
This repo serves as a reference for Python CLI projects using Typer + UV.
|
|
106
|
+
|
|
107
|
+
**Copy directly** (adjust versions/paths):
|
|
108
|
+
- `.github/workflows/test.yml` — lint + test CI on macOS
|
|
109
|
+
- `.github/workflows/release.yml` — release-please with GitHub App token + tap dispatch + PyPI Trusted Publishing (OIDC)
|
|
110
|
+
- `release-please-config.json` + `.release-please-manifest.json` — config and version tracking (both required)
|
|
111
|
+
- `pyproject.toml` structure — Hatchling build, Ruff lint+format, pytest config
|
|
112
|
+
- `CONTRIBUTING.md` — dev setup, commit conventions, PR process
|
|
113
|
+
|
|
114
|
+
**Adapt:**
|
|
115
|
+
- TOML-driven task definitions with `importlib.resources` bundling — for any CLI needing an extensible command registry where adding a task shouldn't require code changes
|
|
116
|
+
- `init` with system detection via `shutil.which()` — for any CLI replacing static example config files with generated, system-aware configs
|
|
117
|
+
- 3-layer config merge (bundled `defaults.toml` → user `config.toml` → env vars) with field-level override — for any CLI needing layered configuration
|
|
118
|
+
- `${VAR}` variable resolution in TOML fields — for portable paths across architectures (`${BREW_PREFIX}` resolves differently on Apple Silicon vs Intel)
|
|
119
|
+
- Rich Live table TUI (`isatty()` detection + `_TaskState` + `Live` + `console.print()` above pinned table) for any CLI running interactively AND via scheduler
|
|
120
|
+
- terminal-notifier with osascript fallback for any macOS launchd service needing actionable notifications
|
|
121
|
+
- `repository_dispatch` + GitHub App for cross-repo automation
|
|
122
|
+
- `subprocess.run(stdin=subprocess.DEVNULL)` for any CLI orchestrator wrapping interactive tools
|
|
123
|
+
- Per-task frequency scheduling with XDG state file + threshold buffers for any periodic CLI tool
|
|
124
|
+
- newsyslog.d config generation via setup command for any macOS launchd service needing log rotation
|
|
125
|
+
|
|
126
|
+
**Project-specific (do not copy):**
|
|
127
|
+
- Mole CLI wrapper and sudo/HOME/sudoers configuration
|
|
128
|
+
- Homebrew tap formula + poet resource regeneration
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Development Setup
|
|
4
|
+
|
|
5
|
+
> **Note:** macOS is required for development and testing.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/calvindotsg/mac-upkeep.git
|
|
9
|
+
cd mac-upkeep
|
|
10
|
+
uv sync
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Code Style
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv run ruff check src/ tests/ # Lint
|
|
17
|
+
uv run ruff format src/ tests/ # Format
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Testing
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv run pytest # Run tests
|
|
24
|
+
uv run pytest --cov # With coverage
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Dependencies
|
|
28
|
+
|
|
29
|
+
After modifying `pyproject.toml` dependencies, run `uv lock` to update the lockfile. CI runs `uv lock --check` and will fail on stale lockfiles.
|
|
30
|
+
|
|
31
|
+
## Commit Conventions
|
|
32
|
+
|
|
33
|
+
This project uses [Conventional Commits v1.0.0](https://www.conventionalcommits.org/en/v1.0.0/).
|
|
34
|
+
|
|
35
|
+
| Type | When |
|
|
36
|
+
|------|------|
|
|
37
|
+
| `feat` | New task, CLI subcommand, or user-facing behavior |
|
|
38
|
+
| `fix` | Bug fix (wrong exit code, broken task, etc.) |
|
|
39
|
+
| `docs` | README, CLAUDE.md, CONTRIBUTING.md changes |
|
|
40
|
+
| `chore` | Dependencies, config files |
|
|
41
|
+
| `ci` | GitHub Actions workflows |
|
|
42
|
+
| `test` | Test additions or fixes |
|
|
43
|
+
| `refactor` | Code restructuring without behavior change |
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
feat: add docker system prune task
|
|
49
|
+
fix: handle missing gcloud gracefully
|
|
50
|
+
docs: update README with new task table
|
|
51
|
+
chore: update typer to 0.13
|
|
52
|
+
ci: add macOS runner to test matrix
|
|
53
|
+
test: add test for mo purge failure path
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Release Process
|
|
57
|
+
|
|
58
|
+
Automated via [release-please](https://github.com/googleapis/release-please). Use conventional commits — `feat:` and `fix:` trigger version bumps. See CLAUDE.md for full flow.
|
mac_upkeep-2.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 calvindotsg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mac-upkeep
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: Automated macOS maintenance CLI
|
|
5
|
+
Project-URL: Homepage, https://github.com/calvindotsg/maintenance
|
|
6
|
+
Project-URL: Repository, https://github.com/calvindotsg/maintenance
|
|
7
|
+
Project-URL: Issues, https://github.com/calvindotsg/maintenance/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/calvindotsg/maintenance/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Calvin
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: automation,cli,homebrew,macos,maintenance
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Environment :: MacOS X
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: MacOS
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: System :: Systems Administration
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: typer>=0.12
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# mac-upkeep
|
|
29
|
+
|
|
30
|
+
Automated macOS maintenance CLI. Runs weekly via `brew services` to keep your dev environment clean.
|
|
31
|
+
|
|
32
|
+
> **Requires macOS** — uses Homebrew, launchd, and macOS notifications.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
brew install calvindotsg/tap/mac-upkeep
|
|
38
|
+
brew services start mac-upkeep # Monday 12 PM weekly
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or via pip/uvx:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install mac-upkeep
|
|
45
|
+
uvx mac-upkeep run # one-off without installing
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Tasks
|
|
49
|
+
|
|
50
|
+
Homebrew updates, dev tool cache pruning (gcloud, pnpm, uv), Fish plugin updates, system optimization via [mole](https://github.com/nicehash/mole), and Brewfile enforcement.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
mac-upkeep tasks # See all tasks with frequency and last-run status
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Tasks auto-detect installed tools — missing tools are skipped. Each task runs on a weekly or monthly schedule. Use `--force <task>` to run a specific task on demand.
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
mac-upkeep run # Run tasks (frequency-checked)
|
|
62
|
+
mac-upkeep run --dry-run # Preview without executing
|
|
63
|
+
mac-upkeep run --force brew_update # Run only brew_update
|
|
64
|
+
mac-upkeep run --force all # Run all, ignoring schedule
|
|
65
|
+
mac-upkeep run --debug # Verbose output
|
|
66
|
+
mac-upkeep tasks # List tasks with status
|
|
67
|
+
mac-upkeep init # Generate config (detects your tools)
|
|
68
|
+
mac-upkeep show-config --default # Show all available task options
|
|
69
|
+
mac-upkeep show-config # Show your config overrides
|
|
70
|
+
mac-upkeep setup # Print sudoers rules
|
|
71
|
+
mac-upkeep status # Show brew service status
|
|
72
|
+
mac-upkeep logs # View last 20 log lines
|
|
73
|
+
mac-upkeep logs -f # Follow logs
|
|
74
|
+
mac-upkeep --version # Show version
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Configuration
|
|
78
|
+
|
|
79
|
+
Works out of the box with zero configuration. To customize, generate a starter config:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
mac-upkeep init
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This probes your system, detects installed tools, and writes a commented config to `~/.config/mac-upkeep/config.toml`. Only detected tasks are listed. Built-in defaults apply automatically — uncomment lines to override.
|
|
86
|
+
|
|
87
|
+
To see all available tasks and options:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
mac-upkeep show-config --default
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Override examples
|
|
94
|
+
|
|
95
|
+
```toml
|
|
96
|
+
# ~/.config/mac-upkeep/config.toml
|
|
97
|
+
|
|
98
|
+
# Disable a task
|
|
99
|
+
[tasks.gcloud]
|
|
100
|
+
enabled = false
|
|
101
|
+
|
|
102
|
+
# Change frequency (weekly or monthly)
|
|
103
|
+
[tasks.brew_update]
|
|
104
|
+
frequency = "monthly"
|
|
105
|
+
|
|
106
|
+
# Set Brewfile path explicitly
|
|
107
|
+
[paths]
|
|
108
|
+
brewfile = "~/.config/Brewfile"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Custom tasks
|
|
112
|
+
|
|
113
|
+
Add your own tasks using the same format:
|
|
114
|
+
|
|
115
|
+
```toml
|
|
116
|
+
[tasks.docker_prune]
|
|
117
|
+
description = "Prune Docker system"
|
|
118
|
+
command = "docker system prune -f"
|
|
119
|
+
detect = "docker"
|
|
120
|
+
frequency = "monthly"
|
|
121
|
+
|
|
122
|
+
# Control execution order
|
|
123
|
+
[run]
|
|
124
|
+
order = ["brew_update", "brew_upgrade", "docker_prune", "brew_cleanup", "brew_bundle"]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Environment variables
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
MAC_UPKEEP_GCLOUD=false mac-upkeep run # Disable a task
|
|
131
|
+
MAC_UPKEEP_GCLOUD_FREQUENCY=monthly mac-upkeep run # Override frequency
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Sudoers
|
|
135
|
+
|
|
136
|
+
`mo_clean` and `mo_optimize` require passwordless sudo for the `mo` binary:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
mac-upkeep setup | sudo tee /etc/sudoers.d/mac-upkeep && sudo chmod 0440 /etc/sudoers.d/mac-upkeep
|
|
140
|
+
sudo visudo -c
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# mac-upkeep
|
|
2
|
+
|
|
3
|
+
Automated macOS maintenance CLI. Runs weekly via `brew services` to keep your dev environment clean.
|
|
4
|
+
|
|
5
|
+
> **Requires macOS** — uses Homebrew, launchd, and macOS notifications.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
brew install calvindotsg/tap/mac-upkeep
|
|
11
|
+
brew services start mac-upkeep # Monday 12 PM weekly
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Or via pip/uvx:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install mac-upkeep
|
|
18
|
+
uvx mac-upkeep run # one-off without installing
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Tasks
|
|
22
|
+
|
|
23
|
+
Homebrew updates, dev tool cache pruning (gcloud, pnpm, uv), Fish plugin updates, system optimization via [mole](https://github.com/nicehash/mole), and Brewfile enforcement.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
mac-upkeep tasks # See all tasks with frequency and last-run status
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Tasks auto-detect installed tools — missing tools are skipped. Each task runs on a weekly or monthly schedule. Use `--force <task>` to run a specific task on demand.
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
mac-upkeep run # Run tasks (frequency-checked)
|
|
35
|
+
mac-upkeep run --dry-run # Preview without executing
|
|
36
|
+
mac-upkeep run --force brew_update # Run only brew_update
|
|
37
|
+
mac-upkeep run --force all # Run all, ignoring schedule
|
|
38
|
+
mac-upkeep run --debug # Verbose output
|
|
39
|
+
mac-upkeep tasks # List tasks with status
|
|
40
|
+
mac-upkeep init # Generate config (detects your tools)
|
|
41
|
+
mac-upkeep show-config --default # Show all available task options
|
|
42
|
+
mac-upkeep show-config # Show your config overrides
|
|
43
|
+
mac-upkeep setup # Print sudoers rules
|
|
44
|
+
mac-upkeep status # Show brew service status
|
|
45
|
+
mac-upkeep logs # View last 20 log lines
|
|
46
|
+
mac-upkeep logs -f # Follow logs
|
|
47
|
+
mac-upkeep --version # Show version
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
Works out of the box with zero configuration. To customize, generate a starter config:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
mac-upkeep init
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This probes your system, detects installed tools, and writes a commented config to `~/.config/mac-upkeep/config.toml`. Only detected tasks are listed. Built-in defaults apply automatically — uncomment lines to override.
|
|
59
|
+
|
|
60
|
+
To see all available tasks and options:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
mac-upkeep show-config --default
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Override examples
|
|
67
|
+
|
|
68
|
+
```toml
|
|
69
|
+
# ~/.config/mac-upkeep/config.toml
|
|
70
|
+
|
|
71
|
+
# Disable a task
|
|
72
|
+
[tasks.gcloud]
|
|
73
|
+
enabled = false
|
|
74
|
+
|
|
75
|
+
# Change frequency (weekly or monthly)
|
|
76
|
+
[tasks.brew_update]
|
|
77
|
+
frequency = "monthly"
|
|
78
|
+
|
|
79
|
+
# Set Brewfile path explicitly
|
|
80
|
+
[paths]
|
|
81
|
+
brewfile = "~/.config/Brewfile"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Custom tasks
|
|
85
|
+
|
|
86
|
+
Add your own tasks using the same format:
|
|
87
|
+
|
|
88
|
+
```toml
|
|
89
|
+
[tasks.docker_prune]
|
|
90
|
+
description = "Prune Docker system"
|
|
91
|
+
command = "docker system prune -f"
|
|
92
|
+
detect = "docker"
|
|
93
|
+
frequency = "monthly"
|
|
94
|
+
|
|
95
|
+
# Control execution order
|
|
96
|
+
[run]
|
|
97
|
+
order = ["brew_update", "brew_upgrade", "docker_prune", "brew_cleanup", "brew_bundle"]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Environment variables
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
MAC_UPKEEP_GCLOUD=false mac-upkeep run # Disable a task
|
|
104
|
+
MAC_UPKEEP_GCLOUD_FREQUENCY=monthly mac-upkeep run # Override frequency
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Sudoers
|
|
108
|
+
|
|
109
|
+
`mo_clean` and `mo_optimize` require passwordless sudo for the `mo` binary:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
mac-upkeep setup | sudo tee /etc/sudoers.d/mac-upkeep && sudo chmod 0440 /etc/sudoers.d/mac-upkeep
|
|
113
|
+
sudo visudo -c
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT
|