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.
@@ -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,11 @@
1
+ __pycache__/
2
+ *.pyc
3
+ dist/
4
+ *.egg-info/
5
+ .ruff_cache/
6
+ .pytest_cache/
7
+ coverage/
8
+ .DS_Store
9
+ .claude/worktrees/
10
+ .scratchpad/
11
+ .venv/
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "2.1.0"
3
+ }
@@ -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.
@@ -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