trackerkeeper 0.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.
Files changed (201) hide show
  1. trackerkeeper-0.1.0/.claude/settings.local.json +18 -0
  2. trackerkeeper-0.1.0/.gitattributes +5 -0
  3. trackerkeeper-0.1.0/.github/release-checklist-template.md +20 -0
  4. trackerkeeper-0.1.0/.github/workflows/aur.yml +48 -0
  5. trackerkeeper-0.1.0/.github/workflows/ci.yml +85 -0
  6. trackerkeeper-0.1.0/.github/workflows/macos.yml +224 -0
  7. trackerkeeper-0.1.0/.github/workflows/pypi-publish.yml +63 -0
  8. trackerkeeper-0.1.0/.github/workflows/release-checklist.yml +33 -0
  9. trackerkeeper-0.1.0/.github/workflows/release.yml +366 -0
  10. trackerkeeper-0.1.0/.github/workflows/winget.yml +45 -0
  11. trackerkeeper-0.1.0/.gitignore +10 -0
  12. trackerkeeper-0.1.0/AGENTS.md +125 -0
  13. trackerkeeper-0.1.0/CLAUDE.md +5 -0
  14. trackerkeeper-0.1.0/LICENSE +338 -0
  15. trackerkeeper-0.1.0/PKG-INFO +144 -0
  16. trackerkeeper-0.1.0/README.md +100 -0
  17. trackerkeeper-0.1.0/docs/ACCESSIBILITY.md +61 -0
  18. trackerkeeper-0.1.0/docs/BACKPORT.md +144 -0
  19. trackerkeeper-0.1.0/docs/BAKING.md +448 -0
  20. trackerkeeper-0.1.0/docs/CHANGELOG.md +55 -0
  21. trackerkeeper-0.1.0/docs/DELIVERY.md +110 -0
  22. trackerkeeper-0.1.0/docs/DESIGN.md +95 -0
  23. trackerkeeper-0.1.0/docs/MACOS.md +223 -0
  24. trackerkeeper-0.1.0/docs/PHILOSOPHY.md +96 -0
  25. trackerkeeper-0.1.0/docs/RELEASING.md +76 -0
  26. trackerkeeper-0.1.0/docs/TEST_BRIDGE.md +143 -0
  27. trackerkeeper-0.1.0/docs/TODO.md +102 -0
  28. trackerkeeper-0.1.0/docs/TRANSLATING.md +114 -0
  29. trackerkeeper-0.1.0/docs/WIND-DOWN.md +43 -0
  30. trackerkeeper-0.1.0/dough-sync.toml +25 -0
  31. trackerkeeper-0.1.0/packaging/appimage/build_appimage.sh +132 -0
  32. trackerkeeper-0.1.0/packaging/appimage/smoke_test_appimage.sh +39 -0
  33. trackerkeeper-0.1.0/packaging/aur/PKGBUILD +62 -0
  34. trackerkeeper-0.1.0/packaging/deb/build_deb.sh +151 -0
  35. trackerkeeper-0.1.0/packaging/deb/smoke_test_deb.sh +55 -0
  36. trackerkeeper-0.1.0/packaging/io.github.wolfgangwarehaus.trackerkeeper.desktop +20 -0
  37. trackerkeeper-0.1.0/packaging/io.github.wolfgangwarehaus.trackerkeeper.metainfo.xml +56 -0
  38. trackerkeeper-0.1.0/packaging/macos/entitlements.plist +21 -0
  39. trackerkeeper-0.1.0/packaging/macos/mas/entitlements.child.plist +13 -0
  40. trackerkeeper-0.1.0/packaging/macos/mas/entitlements.mas.plist +40 -0
  41. trackerkeeper-0.1.0/packaging/macos/mas/scan_symbols.sh +61 -0
  42. trackerkeeper-0.1.0/packaging/macos/mas/sign_mas.sh +65 -0
  43. trackerkeeper-0.1.0/packaging/macos/trackerkeeper.rb +24 -0
  44. trackerkeeper-0.1.0/packaging/msix/AppxManifest.xml +94 -0
  45. trackerkeeper-0.1.0/packaging/msix/STORE-SUBMISSION.md +72 -0
  46. trackerkeeper-0.1.0/packaging/msix/make-assets.sh +60 -0
  47. trackerkeeper-0.1.0/packaging/pyinstaller/launch.py +10 -0
  48. trackerkeeper-0.1.0/packaging/pyinstaller/trackerkeeper.spec +117 -0
  49. trackerkeeper-0.1.0/packaging/templates/appimage/build_appimage.sh.j2 +132 -0
  50. trackerkeeper-0.1.0/packaging/templates/appimage/smoke_test_appimage.sh.j2 +39 -0
  51. trackerkeeper-0.1.0/packaging/templates/aur/PKGBUILD.j2 +62 -0
  52. trackerkeeper-0.1.0/packaging/templates/deb/build_deb.sh.j2 +156 -0
  53. trackerkeeper-0.1.0/packaging/templates/deb/smoke_test_deb.sh.j2 +55 -0
  54. trackerkeeper-0.1.0/packaging/templates/macos/entitlements.plist.j2 +21 -0
  55. trackerkeeper-0.1.0/packaging/templates/macos/mas/entitlements.child.plist.j2 +13 -0
  56. trackerkeeper-0.1.0/packaging/templates/macos/mas/entitlements.mas.plist.j2 +40 -0
  57. trackerkeeper-0.1.0/packaging/templates/macos/mas/scan_symbols.sh.j2 +61 -0
  58. trackerkeeper-0.1.0/packaging/templates/macos/mas/sign_mas.sh.j2 +65 -0
  59. trackerkeeper-0.1.0/packaging/templates/macos/{{app_slug}}.rb.j2 +28 -0
  60. trackerkeeper-0.1.0/packaging/templates/msix/AppxManifest.xml.j2 +94 -0
  61. trackerkeeper-0.1.0/packaging/templates/msix/STORE-SUBMISSION.md.j2 +72 -0
  62. trackerkeeper-0.1.0/packaging/templates/msix/make-assets.sh.j2 +60 -0
  63. trackerkeeper-0.1.0/packaging/templates/pyinstaller/launch.py.j2 +11 -0
  64. trackerkeeper-0.1.0/packaging/templates/pyinstaller/{{app_slug}}.spec.j2 +117 -0
  65. trackerkeeper-0.1.0/packaging/templates/windows/version_info.txt.j2 +35 -0
  66. trackerkeeper-0.1.0/packaging/templates/windows/{{app_slug}}.iss.j2 +59 -0
  67. trackerkeeper-0.1.0/packaging/templates/{{app_id_base}}.desktop.j2 +20 -0
  68. trackerkeeper-0.1.0/packaging/templates/{{app_id_base}}.metainfo.xml.j2 +80 -0
  69. trackerkeeper-0.1.0/packaging/windows/trackerkeeper.iss +57 -0
  70. trackerkeeper-0.1.0/packaging/windows/version_info.txt +35 -0
  71. trackerkeeper-0.1.0/pyproject.toml +237 -0
  72. trackerkeeper-0.1.0/setup.cfg +4 -0
  73. trackerkeeper-0.1.0/tests/baselines/settings.png +0 -0
  74. trackerkeeper-0.1.0/tests/baselines/window.png +0 -0
  75. trackerkeeper-0.1.0/tests/conftest.py +61 -0
  76. trackerkeeper-0.1.0/tests/test_a11y.py +126 -0
  77. trackerkeeper-0.1.0/tests/test_app_wiring.py +121 -0
  78. trackerkeeper-0.1.0/tests/test_bake.py +303 -0
  79. trackerkeeper-0.1.0/tests/test_blur_kwin.py +84 -0
  80. trackerkeeper-0.1.0/tests/test_boot.py +35 -0
  81. trackerkeeper-0.1.0/tests/test_breadboard.py +367 -0
  82. trackerkeeper-0.1.0/tests/test_bridge.py +270 -0
  83. trackerkeeper-0.1.0/tests/test_bus.py +54 -0
  84. trackerkeeper-0.1.0/tests/test_catalog.py +76 -0
  85. trackerkeeper-0.1.0/tests/test_credentials.py +275 -0
  86. trackerkeeper-0.1.0/tests/test_dashboard.py +199 -0
  87. trackerkeeper-0.1.0/tests/test_deliver.py +258 -0
  88. trackerkeeper-0.1.0/tests/test_diagnostics.py +102 -0
  89. trackerkeeper-0.1.0/tests/test_drag_repaint.py +272 -0
  90. trackerkeeper-0.1.0/tests/test_i18n.py +60 -0
  91. trackerkeeper-0.1.0/tests/test_i18n_fmt.py +128 -0
  92. trackerkeeper-0.1.0/tests/test_i18n_plurals.py +125 -0
  93. trackerkeeper-0.1.0/tests/test_identity.py +89 -0
  94. trackerkeeper-0.1.0/tests/test_imports.py +103 -0
  95. trackerkeeper-0.1.0/tests/test_log.py +87 -0
  96. trackerkeeper-0.1.0/tests/test_metadata.py +194 -0
  97. trackerkeeper-0.1.0/tests/test_noborder.py +278 -0
  98. trackerkeeper-0.1.0/tests/test_power_runtime.py +34 -0
  99. trackerkeeper-0.1.0/tests/test_public_api.py +85 -0
  100. trackerkeeper-0.1.0/tests/test_relative_time.py +42 -0
  101. trackerkeeper-0.1.0/tests/test_rig.py +124 -0
  102. trackerkeeper-0.1.0/tests/test_rtl_smoke.py +75 -0
  103. trackerkeeper-0.1.0/tests/test_settings_migration.py +86 -0
  104. trackerkeeper-0.1.0/tests/test_single_instance_forwarding.py +192 -0
  105. trackerkeeper-0.1.0/tests/test_sources.py +230 -0
  106. trackerkeeper-0.1.0/tests/test_tray.py +90 -0
  107. trackerkeeper-0.1.0/tests/test_ui_helpers_widgets.py +31 -0
  108. trackerkeeper-0.1.0/tests/test_updates.py +226 -0
  109. trackerkeeper-0.1.0/tests/test_window_seams.py +133 -0
  110. trackerkeeper-0.1.0/trackerkeeper/__init__.py +55 -0
  111. trackerkeeper-0.1.0/trackerkeeper/__main__.py +3 -0
  112. trackerkeeper-0.1.0/trackerkeeper/_version.py +24 -0
  113. trackerkeeper-0.1.0/trackerkeeper/app.py +445 -0
  114. trackerkeeper-0.1.0/trackerkeeper/assets/__init__.py +2 -0
  115. trackerkeeper-0.1.0/trackerkeeper/assets/trackerkeeper.svg +12 -0
  116. trackerkeeper-0.1.0/trackerkeeper/async_io.py +299 -0
  117. trackerkeeper-0.1.0/trackerkeeper/autostart/__init__.py +52 -0
  118. trackerkeeper-0.1.0/trackerkeeper/autostart/_linux.py +129 -0
  119. trackerkeeper-0.1.0/trackerkeeper/autostart/_macos.py +163 -0
  120. trackerkeeper-0.1.0/trackerkeeper/autostart/_msix.py +181 -0
  121. trackerkeeper-0.1.0/trackerkeeper/autostart/_unsupported.py +22 -0
  122. trackerkeeper-0.1.0/trackerkeeper/autostart/_windows.py +96 -0
  123. trackerkeeper-0.1.0/trackerkeeper/bake.py +217 -0
  124. trackerkeeper-0.1.0/trackerkeeper/blur/__init__.py +212 -0
  125. trackerkeeper-0.1.0/trackerkeeper/blur/_dwm.py +348 -0
  126. trackerkeeper-0.1.0/trackerkeeper/blur/_faux_frost.py +143 -0
  127. trackerkeeper-0.1.0/trackerkeeper/blur/_kwin.py +478 -0
  128. trackerkeeper-0.1.0/trackerkeeper/blur/_macos.py +356 -0
  129. trackerkeeper-0.1.0/trackerkeeper/blur/_unsupported.py +39 -0
  130. trackerkeeper-0.1.0/trackerkeeper/boot_timing.py +75 -0
  131. trackerkeeper-0.1.0/trackerkeeper/breadboard.py +1554 -0
  132. trackerkeeper-0.1.0/trackerkeeper/bus.py +110 -0
  133. trackerkeeper-0.1.0/trackerkeeper/catalog.py +176 -0
  134. trackerkeeper-0.1.0/trackerkeeper/color_tokens.py +697 -0
  135. trackerkeeper-0.1.0/trackerkeeper/credentials.py +471 -0
  136. trackerkeeper-0.1.0/trackerkeeper/custom_tooltip.py +254 -0
  137. trackerkeeper-0.1.0/trackerkeeper/dashboard.py +704 -0
  138. trackerkeeper-0.1.0/trackerkeeper/deliver.py +442 -0
  139. trackerkeeper-0.1.0/trackerkeeper/design_tokens.py +415 -0
  140. trackerkeeper-0.1.0/trackerkeeper/diagnostics.py +168 -0
  141. trackerkeeper-0.1.0/trackerkeeper/drag_repaint/__init__.py +68 -0
  142. trackerkeeper-0.1.0/trackerkeeper/drag_repaint/_kwin.py +192 -0
  143. trackerkeeper-0.1.0/trackerkeeper/drag_repaint/_unsupported.py +41 -0
  144. trackerkeeper-0.1.0/trackerkeeper/drag_repaint/effect/dragrepaint/contents/code/main.js +86 -0
  145. trackerkeeper-0.1.0/trackerkeeper/drag_repaint/effect/dragrepaint/metadata.json +17 -0
  146. trackerkeeper-0.1.0/trackerkeeper/frosted_dialog.py +377 -0
  147. trackerkeeper-0.1.0/trackerkeeper/i18n/__init__.py +101 -0
  148. trackerkeeper-0.1.0/trackerkeeper/i18n/fmt.py +137 -0
  149. trackerkeeper-0.1.0/trackerkeeper/icon_button.py +137 -0
  150. trackerkeeper-0.1.0/trackerkeeper/icons.py +532 -0
  151. trackerkeeper-0.1.0/trackerkeeper/identity.py +135 -0
  152. trackerkeeper-0.1.0/trackerkeeper/item_dialog.py +185 -0
  153. trackerkeeper-0.1.0/trackerkeeper/kde_titlebar.py +196 -0
  154. trackerkeeper-0.1.0/trackerkeeper/keyboard_focus.py +353 -0
  155. trackerkeeper-0.1.0/trackerkeeper/log.py +112 -0
  156. trackerkeeper-0.1.0/trackerkeeper/macos_menubar.py +282 -0
  157. trackerkeeper-0.1.0/trackerkeeper/macos_window.py +172 -0
  158. trackerkeeper-0.1.0/trackerkeeper/metadata.py +121 -0
  159. trackerkeeper-0.1.0/trackerkeeper/noborder/__init__.py +61 -0
  160. trackerkeeper-0.1.0/trackerkeeper/noborder/_kwin.py +247 -0
  161. trackerkeeper-0.1.0/trackerkeeper/noborder/_unsupported.py +42 -0
  162. trackerkeeper-0.1.0/trackerkeeper/notifications/__init__.py +84 -0
  163. trackerkeeper-0.1.0/trackerkeeper/notifications/_linux.py +68 -0
  164. trackerkeeper-0.1.0/trackerkeeper/notifications/_macos.py +159 -0
  165. trackerkeeper-0.1.0/trackerkeeper/notifications/_unsupported.py +22 -0
  166. trackerkeeper-0.1.0/trackerkeeper/notifications/_windows.py +115 -0
  167. trackerkeeper-0.1.0/trackerkeeper/platform_compat.py +149 -0
  168. trackerkeeper-0.1.0/trackerkeeper/power/__init__.py +84 -0
  169. trackerkeeper-0.1.0/trackerkeeper/power/_linux.py +79 -0
  170. trackerkeeper-0.1.0/trackerkeeper/power/_unsupported.py +18 -0
  171. trackerkeeper-0.1.0/trackerkeeper/power/_windows.py +52 -0
  172. trackerkeeper-0.1.0/trackerkeeper/rig.py +338 -0
  173. trackerkeeper-0.1.0/trackerkeeper/scaffold.py +310 -0
  174. trackerkeeper-0.1.0/trackerkeeper/selector.py +529 -0
  175. trackerkeeper-0.1.0/trackerkeeper/settings.py +226 -0
  176. trackerkeeper-0.1.0/trackerkeeper/settings_dialog.py +307 -0
  177. trackerkeeper-0.1.0/trackerkeeper/settings_migration.py +101 -0
  178. trackerkeeper-0.1.0/trackerkeeper/single_instance.py +330 -0
  179. trackerkeeper-0.1.0/trackerkeeper/smooth_scroll.py +155 -0
  180. trackerkeeper-0.1.0/trackerkeeper/sources.py +318 -0
  181. trackerkeeper-0.1.0/trackerkeeper/system_accent.py +363 -0
  182. trackerkeeper-0.1.0/trackerkeeper/terminal.py +423 -0
  183. trackerkeeper-0.1.0/trackerkeeper/test_bridge.py +514 -0
  184. trackerkeeper-0.1.0/trackerkeeper/theme.py +725 -0
  185. trackerkeeper-0.1.0/trackerkeeper/top_bar.py +154 -0
  186. trackerkeeper-0.1.0/trackerkeeper/tray.py +184 -0
  187. trackerkeeper-0.1.0/trackerkeeper/ui_helpers.py +2294 -0
  188. trackerkeeper-0.1.0/trackerkeeper/update_chip.py +119 -0
  189. trackerkeeper-0.1.0/trackerkeeper/updates.py +216 -0
  190. trackerkeeper-0.1.0/trackerkeeper/win_frameless.py +364 -0
  191. trackerkeeper-0.1.0/trackerkeeper/window.py +609 -0
  192. trackerkeeper-0.1.0/trackerkeeper/windows_shortcut.py +355 -0
  193. trackerkeeper-0.1.0/trackerkeeper-breadboard.toml +255 -0
  194. trackerkeeper-0.1.0/trackerkeeper.egg-info/PKG-INFO +144 -0
  195. trackerkeeper-0.1.0/trackerkeeper.egg-info/SOURCES.txt +199 -0
  196. trackerkeeper-0.1.0/trackerkeeper.egg-info/dependency_links.txt +1 -0
  197. trackerkeeper-0.1.0/trackerkeeper.egg-info/entry_points.txt +8 -0
  198. trackerkeeper-0.1.0/trackerkeeper.egg-info/requires.txt +33 -0
  199. trackerkeeper-0.1.0/trackerkeeper.egg-info/scm_file_list.json +194 -0
  200. trackerkeeper-0.1.0/trackerkeeper.egg-info/scm_version.json +8 -0
  201. trackerkeeper-0.1.0/trackerkeeper.egg-info/top_level.txt +1 -0
@@ -0,0 +1,18 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(ls -la /home/august/Projects/ && ls -la /home/august/Projects/dough)",
5
+ "Bash(xargs -n1 basename)",
6
+ "Bash(timeout 300 bash -lc 'ruff check . >/tmp/ruff.txt 2>&1; echo \"ruff exit=$?\"; python -m trackerkeeper.bake --check >/tmp/bake.txt 2>&1; echo \"bake exit=$?\"')",
7
+ "Bash(timeout 400 pytest -q)",
8
+ "Bash(timeout 120 pytest -q tests/test_imports.py)",
9
+ "Bash(python -c \"import sys; print\\(sys.prefix\\)\")",
10
+ "Read(//usr/bin/**)",
11
+ "Bash(.venv/bin/python -c \"import trackerkeeper; print\\('ok', trackerkeeper.__file__\\)\")",
12
+ "Bash(timeout 400 .venv/bin/python -m pytest -q)",
13
+ "Bash(timeout 500 xvfb-run -a .venv/bin/python -m pytest -q)",
14
+ "Bash(timeout 500 .venv/bin/python -m pytest -q -p no:cacheprovider)",
15
+ "Bash(QT_QPA_PLATFORM=offscreen timeout 500 .venv/bin/python -m pytest -q)"
16
+ ]
17
+ }
18
+ }
@@ -0,0 +1,5 @@
1
+ # Build anywhere: every text file checks out LF on every OS. Windows git
2
+ # defaults (core.autocrlf=true on the GitHub runners) would otherwise convert
3
+ # the working tree to CRLF — breaking the byte-exact `dough bake --check`
4
+ # drift gate and any rendered shell script.
5
+ * text=auto eol=lf
@@ -0,0 +1,20 @@
1
+ # Release ${TAG} — propagation checklist
2
+
3
+ Auto-opened by `release-checklist.yml` on the `${TAG}` tag. Tick each channel as
4
+ it propagates. See `docs/RELEASING.md`.
5
+
6
+ ## Automated — verify
7
+ - [ ] **GitHub release** drafted by `release.yml` — `.deb`, AppImage + `.zsync`, sdist/wheel, `SHA256SUMS`, Sigstore attestations
8
+ - [ ] Cross-distro **smoke tests** green (deb + AppImage self-containment)
9
+ - [ ] **Review the draft** and click **Publish** ← the one human gate
10
+ - [ ] **PyPI** — `pypi-publish.yml` uploaded `${V}`: https://pypi.org/project/trackerkeeper-base/${V}/
11
+ - [ ] Provenance verifies: `gh attestation verify <asset> --repo wolfgangwarehaus/trackerkeeper`
12
+
13
+ ## Dormant / deferred — light up when configured (docs/TODO.md)
14
+ - [ ] **AUR** — `aur.yml` pushed `trackerkeeper ${V}` (needs `AUR_SSH_PRIVATE_KEY` + AUR registration)
15
+ - [ ] **winget / MSIX / Windows Inno** — deferred (no Windows channel yet)
16
+ - [ ] **macOS cask** — deferred (needs an Apple Developer account)
17
+
18
+ ## Housekeeping
19
+ - [ ] `docs/CHANGELOG.md`: the `[${V}]` section is dated + accurate
20
+ - [ ] `[Unreleased]` reset to empty for the next cycle
@@ -0,0 +1,48 @@
1
+ name: aur
2
+
3
+ # Publish the PKGBUILD to the AUR when a release is PUBLISHED: stamp pkgver from
4
+ # the tag, refresh the checksum via updpkgsums, regenerate .SRCINFO, and push to
5
+ # aur@aur.archlinux.org.
6
+ #
7
+ # DORMANT until BOTH: (a) the AUR reopens new-package registration (frozen after
8
+ # the 2026 malware wave) AND (b) you add the AUR_SSH_PRIVATE_KEY secret (a
9
+ # dedicated AUR deploy keypair; public half on your AUR account). Until then the
10
+ # steps skip with a one-line pointer. See docs/RELEASING.md.
11
+
12
+ on:
13
+ release:
14
+ types: [released]
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ aur:
21
+ runs-on: ubuntu-latest
22
+ env:
23
+ HAVE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY != '' }}
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ if: env.HAVE_KEY == 'true'
27
+ - name: Stamp PKGBUILD pkgver from the tag
28
+ if: env.HAVE_KEY == 'true'
29
+ run: |
30
+ V="${{ github.event.release.tag_name }}"; V="${V#v}"
31
+ sed -i -E "s/^pkgver=.*/pkgver=${V}/; s/^pkgrel=.*/pkgrel=1/" packaging/aur/PKGBUILD
32
+ - name: Publish to the AUR
33
+ if: env.HAVE_KEY == 'true'
34
+ uses: KSXGitHub/github-actions-deploy-aur@084b0d9b15415bf9cdb65d44dad1efe37a354050 # v4.2.0
35
+ with:
36
+ pkgname: trackerkeeper
37
+ pkgbuild: packaging/aur/PKGBUILD
38
+ commit_username: wolfgangwarehaus
39
+ commit_email: augustvontrips@gmail.com
40
+ ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
41
+ commit_message: trackerkeeper ${{ github.event.release.tag_name }}
42
+ updpkgsums: true
43
+ ssh_keyscan_types: rsa,ecdsa,ed25519
44
+ - name: Dormant — AUR_SSH_PRIVATE_KEY not set
45
+ if: env.HAVE_KEY != 'true'
46
+ run: |
47
+ echo "AUR_SSH_PRIVATE_KEY is not set → AUR auto-deploy is DORMANT (no-op)."
48
+ echo "(AUR new-package registration is also frozen.) See docs/RELEASING.md."
@@ -0,0 +1,85 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint-and-smoke:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ with:
14
+ # The editable install runs setuptools-scm, which derives the version
15
+ # from the tag — it needs the full history + tags, not a shallow clone.
16
+ fetch-depth: 0
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.12"
20
+ # PySide6's offscreen platform plugin still needs the EGL/GL/xkb libs.
21
+ - name: System libs for headless Qt
22
+ run: sudo apt-get update && sudo apt-get install -y libegl1 libgl1 libxkbcommon0
23
+ # Install the project itself, not a hand-picked dep list — the boot smoke
24
+ # imports the real app, so it needs whatever [project].dependencies says
25
+ # (a fork that adds deps stays green without touching this file).
26
+ - name: Install
27
+ run: pip install ruff -e .
28
+ - name: Lint
29
+ run: ruff check trackerkeeper/
30
+ # The rig owns the smoke (one source — the same verb a maker or an AI
31
+ # runs locally): offscreen boot + settings dialog. See trackerkeeper/rig.py.
32
+ - name: Boot smoke (offscreen)
33
+ run: python -m trackerkeeper.rig boot
34
+
35
+ tests:
36
+ # Every platform trackerkeeper claims to deliver to gets the suite run ON it — the
37
+ # rigging-and-testing direction: a Windows/macOS code bump should fail HERE,
38
+ # not at release time. Linux stays the canonical leg (it also gates bake).
39
+ strategy:
40
+ fail-fast: false
41
+ matrix:
42
+ os: [ubuntu-latest, windows-latest, macos-latest]
43
+ runs-on: ${{ matrix.os }}
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+ with:
47
+ # The editable install runs setuptools-scm, which derives the version
48
+ # from the tag — it needs the full history + tags, not a shallow clone.
49
+ fetch-depth: 0
50
+ - uses: actions/setup-python@v5
51
+ with:
52
+ python-version: "3.12"
53
+ # PySide6's offscreen platform plugin still needs the EGL/GL/xkb libs.
54
+ - name: System libs for headless Qt
55
+ if: runner.os == 'Linux'
56
+ run: sudo apt-get update && sudo apt-get install -y libegl1 libgl1 libxkbcommon0
57
+ - name: Install (core + dev extras)
58
+ run: pip install -e .[dev]
59
+ - name: Packaging drift gate (trackerkeeper bake --check)
60
+ # Linux-only: the gate validates exec bits on rendered .sh, which have
61
+ # no meaning on the Windows filesystem.
62
+ if: runner.os == 'Linux'
63
+ run: python -m trackerkeeper.bake --check
64
+ - name: Pytest (offscreen)
65
+ env:
66
+ QT_QPA_PLATFORM: offscreen
67
+ run: pytest
68
+
69
+ # Gate-test the PyPI build itself (the publish workflow runs the same steps on a
70
+ # tag): catch a broken sdist/wheel or bad metadata BEFORE a release, not during.
71
+ build:
72
+ runs-on: ubuntu-latest
73
+ steps:
74
+ - uses: actions/checkout@v4
75
+ with:
76
+ # setuptools-scm derives the version from the tag — needs full history.
77
+ fetch-depth: 0
78
+ - uses: actions/setup-python@v5
79
+ with:
80
+ python-version: "3.12"
81
+ - name: Build + check sdist & wheel
82
+ run: |
83
+ python -m pip install --upgrade pip build twine
84
+ python -m build
85
+ python -m twine check dist/*
@@ -0,0 +1,224 @@
1
+ name: macos
2
+
3
+ # Build, sign, notarize, and ship the macOS .dmg + bump the Homebrew cask when a
4
+ # release is PUBLISHED. DORMANT until the Apple secrets exist (the whole job
5
+ # skips) — macOS is a present-but-dormant goal (docs/BAKING.md §5).
6
+ #
7
+ # One UNIVERSAL2 .dmg (arm64 + x86_64 in a single bundle) — trackerkeeper has no native
8
+ # deps, so a single fat build reaches both chips (see the freeze step for the
9
+ # per-arch-matrix fallback).
10
+ #
11
+ # Activate (one-time): a $99/yr Apple Developer membership, then add the secrets
12
+ # MACOS_CERTIFICATE (base64 of a Developer ID Application .p12), MACOS_CERTIFICATE_PWD,
13
+ # MACOS_KEYCHAIN_PWD, APPLE_TEAM_ID, and HOMEBREW_TAP_TOKEN (a PAT for the separate
14
+ # homebrew-<tap> repo). For NOTARIZATION add EITHER the App Store Connect API key
15
+ # (PREFERRED — scoped + revocable, no 2FA coupling): APPLE_API_KEY_ID,
16
+ # APPLE_API_ISSUER, APPLE_API_KEY_B64 (base64 of the AuthKey_XXXX.p8); OR the
17
+ # Apple-ID fallback: APPLE_ID + APPLE_APP_SPECIFIC_PASSWORD. See docs/RELEASING.md.
18
+
19
+ on:
20
+ release:
21
+ types: [released]
22
+ workflow_dispatch:
23
+ inputs:
24
+ tag:
25
+ description: "Release tag to build (e.g. v0.1.0)"
26
+ required: true
27
+
28
+ permissions:
29
+ contents: write # gh release upload
30
+
31
+ jobs:
32
+ macos:
33
+ # macos-14 = the oldest FREE Apple-silicon image, and setup-python installs a
34
+ # universal2 CPython framework on it — so the freeze can emit both slices for
35
+ # a single universal2 .app/.dmg. The bundle's honest macOS floor is pinned to
36
+ # this runner's OS (see the freeze step). FALLBACK: a fork that later adds a
37
+ # native dependency WITHOUT universal2 wheels must switch to a per-arch
38
+ # (arm64 + x86_64) matrix — PyInstaller --target-arch universal2 aborts on the
39
+ # thin binaries (jellytoast ships exactly that matrix for its libmpv build).
40
+ runs-on: macos-14
41
+ env:
42
+ HAVE_APPLE: ${{ secrets.APPLE_TEAM_ID != '' }}
43
+ # secrets.* is NOT a valid context inside `if:` — referencing it there makes
44
+ # GitHub reject the whole workflow file at parse time (a 0s "workflow file
45
+ # issue" failure on every push). Gate via env, same as HAVE_APPLE.
46
+ HAVE_TAP: ${{ secrets.HOMEBREW_TAP_TOKEN != '' }}
47
+ permissions:
48
+ contents: write # gh release upload (+ attest reads the artifact)
49
+ id-token: write # Sigstore OIDC for build provenance
50
+ attestations: write # write the provenance attestation
51
+ steps:
52
+ - name: Dormant — Apple secrets not set
53
+ if: env.HAVE_APPLE != 'true'
54
+ run: |
55
+ echo "APPLE_TEAM_ID is not set → the macOS channel is DORMANT (no-op)."
56
+ echo "Activate with an Apple Developer account + the secrets. See docs/RELEASING.md."
57
+
58
+ - uses: actions/checkout@v4
59
+ if: env.HAVE_APPLE == 'true'
60
+ with:
61
+ ref: ${{ github.event.release.tag_name || inputs.tag }}
62
+ fetch-depth: 0
63
+ - uses: actions/setup-python@v5
64
+ if: env.HAVE_APPLE == 'true'
65
+ with:
66
+ # setup-python's macOS builds are universal2 framework builds — needed
67
+ # so PyInstaller can freeze a universal2 (arm64 + x86_64) bundle.
68
+ python-version: "3.12"
69
+ - name: Resolve version
70
+ id: ver
71
+ if: env.HAVE_APPLE == 'true'
72
+ run: |
73
+ V="${{ github.event.release.tag_name || inputs.tag }}"
74
+ echo "version=${V#v}" >> "$GITHUB_OUTPUT"
75
+
76
+ - name: Build the .app (render manifests + freeze)
77
+ if: env.HAVE_APPLE == 'true'
78
+ env:
79
+ VERSION: ${{ steps.ver.outputs.version }}
80
+ run: |
81
+ python -m pip install --upgrade pip
82
+ pip install ".[bake]" pyinstaller
83
+ brew install librsvg create-dmg
84
+ python -m trackerkeeper.bake --packaging packaging --release-version "$VERSION" --release-date "$(date -u +%F)"
85
+ # Render the .icns from the brand SVG (an .iconset of the required sizes).
86
+ ICONSET=packaging/macos/trackerkeeper.iconset
87
+ mkdir -p "$ICONSET"
88
+ # Canonical iconutil sizes only (NOT 64 — iconutil rejects a non-standard
89
+ # icon_64x64). Each base size + its @2x.
90
+ for s in 16 32 128 256 512; do
91
+ rsvg-convert -w "$s" -h "$s" trackerkeeper/assets/trackerkeeper.svg -o "$ICONSET/icon_${s}x${s}.png"
92
+ rsvg-convert -w $((s*2)) -h $((s*2)) trackerkeeper/assets/trackerkeeper.svg -o "$ICONSET/icon_${s}x${s}@2x.png"
93
+ done
94
+ iconutil -c icns "$ICONSET" -o packaging/macos/trackerkeeper.icns
95
+ # UNIVERSAL2: the spec's EXE carries target_arch="universal2" (rendered
96
+ # by `trackerkeeper bake`), and the universal2 CPython above lets the freeze emit
97
+ # both slices; trackerkeeper has no native deps, so every wheel ships universal2
98
+ # and the freeze never hits a thin binary.
99
+ #
100
+ # HONEST FLOOR: the bundle's REAL macOS minimum is the runner OS — this is
101
+ # the only macOS we ever build + smoke-test on, so we can't truthfully
102
+ # claim to support anything older. Match the C-extension deployment target
103
+ # to that floor BEFORE the freeze (so any source-built wheel agrees), then
104
+ # stamp it into the frozen Info.plist AFTER the freeze. An unpinned build
105
+ # would inherit the spec's low default and dock-bounce-and-die (dyld/Qt
106
+ # bail before first paint) on a too-old macOS; the offscreen smoke-test
107
+ # below runs ON this floor, so a too-high floor can't ship silently either.
108
+ FLOOR="$(sw_vers -productVersion | cut -d. -f1).0"
109
+ export MACOSX_DEPLOYMENT_TARGET="$FLOOR"
110
+ pyinstaller packaging/pyinstaller/trackerkeeper.spec --noconfirm
111
+ /usr/libexec/PlistBuddy -c "Set :LSMinimumSystemVersion $FLOOR" \
112
+ "dist/trackerkeeper.app/Contents/Info.plist"
113
+ echo "LSMinimumSystemVersion now: $(/usr/libexec/PlistBuddy -c \
114
+ 'Print :LSMinimumSystemVersion' dist/trackerkeeper.app/Contents/Info.plist)"
115
+
116
+ - name: Smoke-test the .app (boots headless, offscreen)
117
+ if: env.HAVE_APPLE == 'true'
118
+ # Boot the frozen binary with no display: an import/loader failure (a
119
+ # missing bundled lib, a broken universal2 slice) dies fast with a real
120
+ # exit code; surviving ~12s means Qt + the bundle initialized. Runs ON the
121
+ # pinned floor OS, so a false/too-high floor can't ship silently.
122
+ env:
123
+ QT_QPA_PLATFORM: offscreen
124
+ run: |
125
+ BIN="dist/trackerkeeper.app/Contents/MacOS/trackerkeeper"
126
+ test -x "$BIN"
127
+ "$BIN" &
128
+ PID=$!
129
+ sleep 12
130
+ if kill -0 "$PID" 2>/dev/null; then
131
+ echo "still alive after 12s — Qt + bundle initialized OK"
132
+ kill "$PID" 2>/dev/null || true
133
+ else
134
+ wait "$PID"; rc=$?
135
+ echo "app exited early (rc=$rc) — likely an import/loader failure"
136
+ exit 1
137
+ fi
138
+
139
+ - name: Import the signing certificate
140
+ if: env.HAVE_APPLE == 'true'
141
+ env:
142
+ MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
143
+ MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
144
+ MACOS_KEYCHAIN_PWD: ${{ secrets.MACOS_KEYCHAIN_PWD }}
145
+ run: |
146
+ echo "$MACOS_CERTIFICATE" | base64 --decode > cert.p12
147
+ security create-keychain -p "$MACOS_KEYCHAIN_PWD" build.keychain
148
+ security default-keychain -s build.keychain
149
+ security unlock-keychain -p "$MACOS_KEYCHAIN_PWD" build.keychain
150
+ security import cert.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
151
+ security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_KEYCHAIN_PWD" build.keychain
152
+ # codesign searches the keychain SEARCH LIST, not just the default — add ours.
153
+ security list-keychains -d user -s build.keychain $(security list-keychains -d user | tr -d '"')
154
+ rm -f cert.p12
155
+
156
+ - name: Sign, package, notarize, staple
157
+ if: env.HAVE_APPLE == 'true'
158
+ env:
159
+ # Notarization credentials — API key PREFERRED, Apple ID as fallback.
160
+ APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
161
+ APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
162
+ APPLE_API_KEY_B64: ${{ secrets.APPLE_API_KEY_B64 }}
163
+ APPLE_ID: ${{ secrets.APPLE_ID }}
164
+ APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
165
+ APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
166
+ VERSION: ${{ steps.ver.outputs.version }}
167
+ run: |
168
+ IDENTITY="Developer ID Application"
169
+ APP="dist/trackerkeeper.app"
170
+ ENTITLEMENTS="packaging/macos/entitlements.plist"
171
+ # Sign EVERY nested Mach-O bottom-up — dylibs, .so, AND the Qt/Python
172
+ # .framework binaries (which are neither *.dylib nor *.so, so a name
173
+ # filter misses them and notarization rejects the unsigned code) — then
174
+ # the .app, all with the hardened runtime + entitlements (so PySide6's
175
+ # dylibs still load under disabled library-validation).
176
+ find "$APP" -type f -print0 | while IFS= read -r -d '' f; do
177
+ if file "$f" | grep -q "Mach-O"; then
178
+ codesign --force --timestamp --options runtime \
179
+ --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$f"
180
+ fi
181
+ done
182
+ codesign --force --timestamp --options runtime \
183
+ --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$APP"
184
+ codesign --verify --strict --verbose=2 "$APP"
185
+ DMG="dist/trackerkeeper-${VERSION}-macos.dmg"
186
+ create-dmg --volname "trackerkeeper ${VERSION}" --app-drop-link 480 120 \
187
+ --icon "trackerkeeper.app" 160 120 --window-size 640 280 "$DMG" "$APP"
188
+ codesign --force --timestamp --sign "$IDENTITY" "$DMG"
189
+ # Notarize the .dmg. PREFER the App Store Connect API key (scoped,
190
+ # individually revocable, not coupled to an Apple ID's 2FA) when its
191
+ # secrets exist; otherwise use the Apple ID + app-specific password.
192
+ if [ -n "${APPLE_API_KEY_ID:-}" ]; then
193
+ KEY="$(mktemp /tmp/AuthKey_XXXXXX.p8)"
194
+ echo "$APPLE_API_KEY_B64" | base64 --decode > "$KEY"
195
+ xcrun notarytool submit "$DMG" \
196
+ --key "$KEY" --key-id "$APPLE_API_KEY_ID" --issuer "$APPLE_API_ISSUER" --wait
197
+ rm -f "$KEY"
198
+ else
199
+ xcrun notarytool submit "$DMG" --apple-id "$APPLE_ID" \
200
+ --password "$APPLE_APP_SPECIFIC_PASSWORD" --team-id "$APPLE_TEAM_ID" --wait
201
+ fi
202
+ xcrun stapler staple "$DMG"
203
+
204
+ - name: Upload the .dmg to the release
205
+ if: env.HAVE_APPLE == 'true'
206
+ env:
207
+ GH_TOKEN: ${{ github.token }}
208
+ TAG: ${{ github.event.release.tag_name || inputs.tag }}
209
+ run: gh release upload "$TAG" --repo "${{ github.repository }}" --clobber dist/*-macos.dmg
210
+
211
+ - name: Attest .dmg build provenance
212
+ if: env.HAVE_APPLE == 'true'
213
+ uses: actions/attest-build-provenance@v4
214
+ with:
215
+ subject-path: dist/*-macos.dmg
216
+
217
+ - name: Bump the Homebrew cask
218
+ if: ${{ env.HAVE_APPLE == 'true' && env.HAVE_TAP == 'true' }}
219
+ uses: macauley/action-homebrew-bump-cask@v1
220
+ with:
221
+ token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
222
+ tap: wolfgangwarehaus/homebrew-tap
223
+ cask: trackerkeeper
224
+ tag: ${{ github.event.release.tag_name || inputs.tag }}
@@ -0,0 +1,63 @@
1
+ name: Publish to PyPI
2
+
3
+ # Publishes the sdist + wheel to PyPI via **Trusted Publishing (OIDC)** — there is
4
+ # NO API token to store or rotate; PyPI verifies this workflow's identity directly.
5
+ #
6
+ # Triggers:
7
+ # - release: published → auto-publishes when a GitHub release goes public.
8
+ # - workflow_dispatch → manual, for a given tag (backfill / re-run).
9
+ #
10
+ # One-time PyPI setup — create a pending publisher (the project doesn't exist on
11
+ # PyPI yet, so use the "pending publisher" form):
12
+ # https://pypi.org/manage/account/publishing/ → Add a new pending publisher:
13
+ # PyPI Project Name: trackerkeeper-base
14
+ # Owner: wolfgangwarehaus
15
+ # Repository name: trackerkeeper
16
+ # Workflow name: pypi-publish.yml
17
+ # Environment name: pypi
18
+ # These must match the values below exactly or PyPI rejects the OIDC token.
19
+
20
+ on:
21
+ release:
22
+ types: [published]
23
+ workflow_dispatch:
24
+ inputs:
25
+ tag:
26
+ description: "Tag to build + publish (e.g. v0.1.0)"
27
+ required: true
28
+
29
+ permissions:
30
+ contents: read
31
+
32
+ jobs:
33
+ pypi:
34
+ runs-on: ubuntu-latest
35
+ environment:
36
+ name: pypi
37
+ url: https://pypi.org/project/trackerkeeper-base/
38
+ permissions:
39
+ contents: read # gh release download
40
+ id-token: write # mint the short-lived OIDC token Trusted Publishing needs
41
+ steps:
42
+ - uses: actions/setup-python@v5
43
+ with:
44
+ python-version: "3.12"
45
+ # Publish the SAME sdist + wheel bytes that release.yml built, attached to
46
+ # the release, and attested — don't rebuild (a second build wouldn't match
47
+ # the release page / SHA256SUMS / provenance). BAKING.md §6.1: Phase-2
48
+ # channels DOWNLOAD the published asset.
49
+ - name: Download the sdist + wheel from the GitHub release
50
+ env:
51
+ GH_TOKEN: ${{ github.token }}
52
+ TAG: ${{ github.event.release.tag_name || inputs.tag }}
53
+ run: |
54
+ mkdir -p dist
55
+ gh release download "$TAG" --repo "${{ github.repository }}" \
56
+ --pattern '*.whl' --pattern '*.tar.gz' --dir dist
57
+ python -m pip install --upgrade pip twine
58
+ python -m twine check dist/*
59
+ - name: Publish to PyPI (Trusted Publishing / OIDC)
60
+ uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 (2026-07 tip)
61
+ with:
62
+ # Idempotent: a re-run won't fail on an already-uploaded version.
63
+ skip-existing: true
@@ -0,0 +1,33 @@
1
+ name: release-checklist
2
+
3
+ # On a v* tag push, open a per-release propagation checklist issue from
4
+ # .github/release-checklist-template.md — a durable board for tracking each
5
+ # channel as it propagates. See docs/RELEASING.md.
6
+
7
+ on:
8
+ push:
9
+ tags: ["v*"]
10
+
11
+ permissions:
12
+ issues: write
13
+ contents: read
14
+
15
+ jobs:
16
+ checklist:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - name: Open the propagation checklist issue
21
+ env:
22
+ GH_TOKEN: ${{ github.token }}
23
+ REPO: ${{ github.repository }}
24
+ TAG: ${{ github.ref_name }}
25
+ run: |
26
+ export V="${TAG#v}"
27
+ # Only substitute V + TAG — leave every other $-token in the template alone.
28
+ envsubst '${V} ${TAG}' < .github/release-checklist-template.md > /tmp/body.md
29
+ TITLE="Release ${TAG} — propagation checklist"
30
+ # Fall back without the 'release' label if it doesn't exist yet, so the
31
+ # issue is always created.
32
+ gh issue create --repo "$REPO" --title "$TITLE" --body-file /tmp/body.md --label release \
33
+ || gh issue create --repo "$REPO" --title "$TITLE" --body-file /tmp/body.md