claude-acc-manager 1.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 (306) hide show
  1. claude_acc_manager-1.1.0/.agents/skills/next-task/SKILL.md +77 -0
  2. claude_acc_manager-1.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +51 -0
  3. claude_acc_manager-1.1.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
  4. claude_acc_manager-1.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +22 -0
  5. claude_acc_manager-1.1.0/.github/PULL_REQUEST_TEMPLATE.md +20 -0
  6. claude_acc_manager-1.1.0/.github/actionlint.yaml +9 -0
  7. claude_acc_manager-1.1.0/.github/actions/setup-env/action.yml +31 -0
  8. claude_acc_manager-1.1.0/.github/dependabot.yml +20 -0
  9. claude_acc_manager-1.1.0/.github/workflows/ci.yml +109 -0
  10. claude_acc_manager-1.1.0/.github/workflows/docs.yml +82 -0
  11. claude_acc_manager-1.1.0/.github/workflows/release.yml +176 -0
  12. claude_acc_manager-1.1.0/.github/workflows/scorecard.yml +49 -0
  13. claude_acc_manager-1.1.0/.github/workflows/security.yml +120 -0
  14. claude_acc_manager-1.1.0/.gitignore +31 -0
  15. claude_acc_manager-1.1.0/.gitleaks.toml +20 -0
  16. claude_acc_manager-1.1.0/.pre-commit-config.yaml +130 -0
  17. claude_acc_manager-1.1.0/.python-version +1 -0
  18. claude_acc_manager-1.1.0/AGENTS.md +85 -0
  19. claude_acc_manager-1.1.0/CHANGELOG.md +417 -0
  20. claude_acc_manager-1.1.0/CONTRIBUTING.md +31 -0
  21. claude_acc_manager-1.1.0/LICENSE +21 -0
  22. claude_acc_manager-1.1.0/NOTICE +30 -0
  23. claude_acc_manager-1.1.0/PKG-INFO +69 -0
  24. claude_acc_manager-1.1.0/README.md +56 -0
  25. claude_acc_manager-1.1.0/SECURITY.md +17 -0
  26. claude_acc_manager-1.1.0/docs/adr/0000-template.md +46 -0
  27. claude_acc_manager-1.1.0/docs/adr/0001-split-durable-decisions-from-ephemeral-build-evidence.md +56 -0
  28. claude_acc_manager-1.1.0/docs/adr/0002-python-uv-stdlib-urllib-argparse-textual-only-dep.md +50 -0
  29. claude_acc_manager-1.1.0/docs/adr/0003-credentials-at-rest-under-xdg-with-private-modes.md +44 -0
  30. claude_acc_manager-1.1.0/docs/adr/0004-login-at-source-via-claude-config-dir.md +41 -0
  31. claude_acc_manager-1.1.0/docs/adr/0005-oauth-subscription-accounts-only.md +37 -0
  32. claude_acc_manager-1.1.0/docs/adr/0006-auto-scope-one-shot-and-foreground-loop.md +39 -0
  33. claude_acc_manager-1.1.0/docs/adr/0007-honest-user-agent-per-endpoint-headers.md +52 -0
  34. claude_acc_manager-1.1.0/docs/adr/0008-flat-429-backoff-and-last-good-trust.md +46 -0
  35. claude_acc_manager-1.1.0/docs/adr/0009-token-ownership-active-slot-never-refreshed.md +44 -0
  36. claude_acc_manager-1.1.0/docs/adr/0010-package-by-component-import-linter-contracts.md +47 -0
  37. claude_acc_manager-1.1.0/docs/adr/0011-strict-tdd-per-commit-gate-coverage-and-mutation.md +56 -0
  38. claude_acc_manager-1.1.0/docs/adr/0012-schema-tolerant-usage-model.md +44 -0
  39. claude_acc_manager-1.1.0/docs/adr/0013-transport-only-cli-main-composition-root.md +39 -0
  40. claude_acc_manager-1.1.0/docs/adr/0014-credential-write-boundary.md +76 -0
  41. claude_acc_manager-1.1.0/docs/adr/0015-claude-version-contract-gate.md +123 -0
  42. claude_acc_manager-1.1.0/docs/architecture.md +270 -0
  43. claude_acc_manager-1.1.0/docs/getting-started/installation.md +40 -0
  44. claude_acc_manager-1.1.0/docs/getting-started/quickstart.md +61 -0
  45. claude_acc_manager-1.1.0/docs/guides/auto-switching.md +70 -0
  46. claude_acc_manager-1.1.0/docs/guides/diagnostics.md +47 -0
  47. claude_acc_manager-1.1.0/docs/guides/scripting.md +55 -0
  48. claude_acc_manager-1.1.0/docs/guides/switching.md +62 -0
  49. claude_acc_manager-1.1.0/docs/index.md +27 -0
  50. claude_acc_manager-1.1.0/docs/reference/cli.md +82 -0
  51. claude_acc_manager-1.1.0/docs/reference/environment-variables.md +27 -0
  52. claude_acc_manager-1.1.0/docs/reference/json-output.md +87 -0
  53. claude_acc_manager-1.1.0/docs/reference/settings.md +29 -0
  54. claude_acc_manager-1.1.0/maintainer/backlog.md +70 -0
  55. claude_acc_manager-1.1.0/maintainer/research/release-pipeline.md +200 -0
  56. claude_acc_manager-1.1.0/maintainer/slices/SL-019-pypi-release-pipeline.md +137 -0
  57. claude_acc_manager-1.1.0/maintainer/slices/_TEMPLATE.md +94 -0
  58. claude_acc_manager-1.1.0/mkdocs.yml +87 -0
  59. claude_acc_manager-1.1.0/pyproject.toml +355 -0
  60. claude_acc_manager-1.1.0/src/claude_acc_manager/__init__.py +22 -0
  61. claude_acc_manager-1.1.0/src/claude_acc_manager/__main__.py +153 -0
  62. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/__init__.py +1 -0
  63. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/__init__.py +1 -0
  64. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/doctor_report.py +92 -0
  65. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/ports.py +454 -0
  66. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/switch_message.py +37 -0
  67. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/__init__.py +1 -0
  68. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/add_account.py +85 -0
  69. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/collect_accounts_view.py +119 -0
  70. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/collect_doctor_report.py +199 -0
  71. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/list_accounts.py +45 -0
  72. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/quarantine_dead_lineage.py +45 -0
  73. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/remove_account.py +36 -0
  74. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/set_account_enabled.py +35 -0
  75. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/status_account.py +48 -0
  76. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/switch_account.py +378 -0
  77. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/__init__.py +1 -0
  78. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/credential_fields.py +115 -0
  79. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/entities.py +53 -0
  80. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/oauth_identity.py +74 -0
  81. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/services/__init__.py +1 -0
  82. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/services/identity_match.py +25 -0
  83. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/services/switch_selection.py +202 -0
  84. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/value_objects.py +60 -0
  85. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/__init__.py +1 -0
  86. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/account_credential_store.py +134 -0
  87. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/account_dir_files.py +90 -0
  88. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/active_slot.py +149 -0
  89. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/claude_contract_probe.py +38 -0
  90. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/claude_locks.py +201 -0
  91. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/claude_login_launcher.py +144 -0
  92. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/diagnostics_probe.py +142 -0
  93. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/file_account_store.py +409 -0
  94. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/ops_lock.py +50 -0
  95. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/path_resolver.py +163 -0
  96. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/system_clock.py +17 -0
  97. claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/unclaimed_store.py +67 -0
  98. claude_acc_manager-1.1.0/src/claude_acc_manager/auto/__init__.py +1 -0
  99. claude_acc_manager-1.1.0/src/claude_acc_manager/auto/application/__init__.py +1 -0
  100. claude_acc_manager-1.1.0/src/claude_acc_manager/auto/application/auto_engine.py +549 -0
  101. claude_acc_manager-1.1.0/src/claude_acc_manager/auto/application/ports.py +59 -0
  102. claude_acc_manager-1.1.0/src/claude_acc_manager/auto/application/use_cases/__init__.py +1 -0
  103. claude_acc_manager-1.1.0/src/claude_acc_manager/auto/application/use_cases/freshen_target.py +93 -0
  104. claude_acc_manager-1.1.0/src/claude_acc_manager/auto/domain/__init__.py +1 -0
  105. claude_acc_manager-1.1.0/src/claude_acc_manager/auto/domain/auto_event.py +112 -0
  106. claude_acc_manager-1.1.0/src/claude_acc_manager/auto/domain/auto_state.py +36 -0
  107. claude_acc_manager-1.1.0/src/claude_acc_manager/auto/domain/services/__init__.py +1 -0
  108. claude_acc_manager-1.1.0/src/claude_acc_manager/auto/domain/services/auto_rank.py +507 -0
  109. claude_acc_manager-1.1.0/src/claude_acc_manager/auto/domain/services/loop_delay.py +70 -0
  110. claude_acc_manager-1.1.0/src/claude_acc_manager/auto/infrastructure/__init__.py +1 -0
  111. claude_acc_manager-1.1.0/src/claude_acc_manager/auto/infrastructure/file_auto_state.py +79 -0
  112. claude_acc_manager-1.1.0/src/claude_acc_manager/cli/__init__.py +15 -0
  113. claude_acc_manager-1.1.0/src/claude_acc_manager/cli/auto_output.py +185 -0
  114. claude_acc_manager-1.1.0/src/claude_acc_manager/cli/commands.py +399 -0
  115. claude_acc_manager-1.1.0/src/claude_acc_manager/cli/context.py +81 -0
  116. claude_acc_manager-1.1.0/src/claude_acc_manager/cli/dispatch.py +114 -0
  117. claude_acc_manager-1.1.0/src/claude_acc_manager/cli/human.py +156 -0
  118. claude_acc_manager-1.1.0/src/claude_acc_manager/cli/json_output.py +311 -0
  119. claude_acc_manager-1.1.0/src/claude_acc_manager/cli/parser.py +171 -0
  120. claude_acc_manager-1.1.0/src/claude_acc_manager/settings/__init__.py +1 -0
  121. claude_acc_manager-1.1.0/src/claude_acc_manager/settings/application/__init__.py +1 -0
  122. claude_acc_manager-1.1.0/src/claude_acc_manager/settings/application/ports.py +40 -0
  123. claude_acc_manager-1.1.0/src/claude_acc_manager/settings/application/use_cases/__init__.py +1 -0
  124. claude_acc_manager-1.1.0/src/claude_acc_manager/settings/application/use_cases/list_settings.py +16 -0
  125. claude_acc_manager-1.1.0/src/claude_acc_manager/settings/application/use_cases/load_settings.py +16 -0
  126. claude_acc_manager-1.1.0/src/claude_acc_manager/settings/application/use_cases/set_setting.py +28 -0
  127. claude_acc_manager-1.1.0/src/claude_acc_manager/settings/application/use_cases/unset_setting.py +16 -0
  128. claude_acc_manager-1.1.0/src/claude_acc_manager/settings/domain/__init__.py +1 -0
  129. claude_acc_manager-1.1.0/src/claude_acc_manager/settings/domain/settings_spec.py +254 -0
  130. claude_acc_manager-1.1.0/src/claude_acc_manager/settings/infrastructure/__init__.py +1 -0
  131. claude_acc_manager-1.1.0/src/claude_acc_manager/settings/infrastructure/file_settings.py +133 -0
  132. claude_acc_manager-1.1.0/src/claude_acc_manager/shared/__init__.py +1 -0
  133. claude_acc_manager-1.1.0/src/claude_acc_manager/shared/claude_contract.py +175 -0
  134. claude_acc_manager-1.1.0/src/claude_acc_manager/shared/container.py +54 -0
  135. claude_acc_manager-1.1.0/src/claude_acc_manager/shared/file_lock.py +48 -0
  136. claude_acc_manager-1.1.0/src/claude_acc_manager/shared/fsio.py +104 -0
  137. claude_acc_manager-1.1.0/src/claude_acc_manager/shared/palette.py +15 -0
  138. claude_acc_manager-1.1.0/src/claude_acc_manager/tui/__init__.py +29 -0
  139. claude_acc_manager-1.1.0/src/claude_acc_manager/tui/account_list.py +365 -0
  140. claude_acc_manager-1.1.0/src/claude_acc_manager/tui/app.py +627 -0
  141. claude_acc_manager-1.1.0/src/claude_acc_manager/tui/autoview.py +245 -0
  142. claude_acc_manager-1.1.0/src/claude_acc_manager/tui/cam.tcss +243 -0
  143. claude_acc_manager-1.1.0/src/claude_acc_manager/tui/dashboard.py +304 -0
  144. claude_acc_manager-1.1.0/src/claude_acc_manager/tui/formatting.py +380 -0
  145. claude_acc_manager-1.1.0/src/claude_acc_manager/tui/modals.py +88 -0
  146. claude_acc_manager-1.1.0/src/claude_acc_manager/tui/palette.py +114 -0
  147. claude_acc_manager-1.1.0/src/claude_acc_manager/tui/settings_screen.py +130 -0
  148. claude_acc_manager-1.1.0/src/claude_acc_manager/tui/theme.py +134 -0
  149. claude_acc_manager-1.1.0/src/claude_acc_manager/tui/throbber.py +144 -0
  150. claude_acc_manager-1.1.0/src/claude_acc_manager/tui/visuals.py +326 -0
  151. claude_acc_manager-1.1.0/src/claude_acc_manager/tui/widgets.py +196 -0
  152. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/__init__.py +1 -0
  153. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/application/__init__.py +1 -0
  154. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/application/ports.py +265 -0
  155. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/application/use_cases/__init__.py +1 -0
  156. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/application/use_cases/fetch_account_usage.py +264 -0
  157. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/__init__.py +1 -0
  158. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/oauth_credential.py +91 -0
  159. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/resolved_identity.py +74 -0
  160. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/services/__init__.py +1 -0
  161. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/services/cache_trust.py +79 -0
  162. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/services/headroom.py +65 -0
  163. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/services/pace.py +85 -0
  164. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/services/poll_policy.py +410 -0
  165. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/usage_cache_entry.py +51 -0
  166. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/usage_report.py +23 -0
  167. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/usage_snapshot.py +139 -0
  168. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/infrastructure/__init__.py +1 -0
  169. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/infrastructure/anthropic_oauth.py +251 -0
  170. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/infrastructure/claude_contract_probe.py +40 -0
  171. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/infrastructure/file_usage_cache.py +257 -0
  172. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/infrastructure/http_transport.py +51 -0
  173. claude_acc_manager-1.1.0/src/claude_acc_manager/usage/infrastructure/system_clock.py +17 -0
  174. claude_acc_manager-1.1.0/test/integration/test_anthropic_oauth_live.py +49 -0
  175. claude_acc_manager-1.1.0/test/integration/test_fetch_account_usage_live.py +112 -0
  176. claude_acc_manager-1.1.0/test/support/__init__.py +0 -0
  177. claude_acc_manager-1.1.0/test/support/controllable_clock.py +23 -0
  178. claude_acc_manager-1.1.0/test/support/engine_harness.py +160 -0
  179. claude_acc_manager-1.1.0/test/support/failable_account_dir.py +57 -0
  180. claude_acc_manager-1.1.0/test/support/failable_account_store.py +38 -0
  181. claude_acc_manager-1.1.0/test/support/failable_active_slot.py +72 -0
  182. claude_acc_manager-1.1.0/test/support/fake_account_dir.py +76 -0
  183. claude_acc_manager-1.1.0/test/support/fake_active_identity.py +20 -0
  184. claude_acc_manager-1.1.0/test/support/fake_active_slot.py +57 -0
  185. claude_acc_manager-1.1.0/test/support/fake_claude_contract.py +23 -0
  186. claude_acc_manager-1.1.0/test/support/fake_claude_locks.py +41 -0
  187. claude_acc_manager-1.1.0/test/support/fake_clock.py +15 -0
  188. claude_acc_manager-1.1.0/test/support/fake_credential_store.py +30 -0
  189. claude_acc_manager-1.1.0/test/support/fake_diagnostics.py +68 -0
  190. claude_acc_manager-1.1.0/test/support/fake_freshen.py +22 -0
  191. claude_acc_manager-1.1.0/test/support/fake_http_transport.py +43 -0
  192. claude_acc_manager-1.1.0/test/support/fake_lineage_quarantine.py +18 -0
  193. claude_acc_manager-1.1.0/test/support/fake_login_launcher.py +19 -0
  194. claude_acc_manager-1.1.0/test/support/fake_ops_lock.py +37 -0
  195. claude_acc_manager-1.1.0/test/support/fake_switch_executor.py +21 -0
  196. claude_acc_manager-1.1.0/test/support/fake_token_refresher.py +26 -0
  197. claude_acc_manager-1.1.0/test/support/fake_tui_module.py +23 -0
  198. claude_acc_manager-1.1.0/test/support/fake_unclaimed_store.py +35 -0
  199. claude_acc_manager-1.1.0/test/support/fake_usage_api.py +41 -0
  200. claude_acc_manager-1.1.0/test/support/fake_usage_fetch.py +33 -0
  201. claude_acc_manager-1.1.0/test/support/in_memory_account_store.py +86 -0
  202. claude_acc_manager-1.1.0/test/support/in_memory_auto_state.py +30 -0
  203. claude_acc_manager-1.1.0/test/support/in_memory_settings.py +55 -0
  204. claude_acc_manager-1.1.0/test/support/in_memory_usage_cache.py +23 -0
  205. claude_acc_manager-1.1.0/test/support/interrupting_fetch_usage.py +17 -0
  206. claude_acc_manager-1.1.0/test/support/interrupting_list_accounts.py +17 -0
  207. claude_acc_manager-1.1.0/test/support/recording_stream.py +25 -0
  208. claude_acc_manager-1.1.0/test/support/rich_asserts.py +76 -0
  209. claude_acc_manager-1.1.0/test/support/tui_app.py +166 -0
  210. claude_acc_manager-1.1.0/test/support/use_cases.py +191 -0
  211. claude_acc_manager-1.1.0/test/unit/accounts/application/test_switch_message.py +34 -0
  212. claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_add_account.py +274 -0
  213. claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_collect_accounts_view.py +180 -0
  214. claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_collect_doctor_report.py +239 -0
  215. claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_list_accounts.py +68 -0
  216. claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_quarantine_dead_lineage.py +97 -0
  217. claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_remove_account.py +117 -0
  218. claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_set_account_enabled.py +71 -0
  219. claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_status_account.py +116 -0
  220. claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_switch_account.py +1054 -0
  221. claude_acc_manager-1.1.0/test/unit/accounts/domain/services/test_identity_match.py +35 -0
  222. claude_acc_manager-1.1.0/test/unit/accounts/domain/services/test_switch_selection.py +320 -0
  223. claude_acc_manager-1.1.0/test/unit/accounts/domain/test_credential_fields.py +212 -0
  224. claude_acc_manager-1.1.0/test/unit/accounts/domain/test_entities.py +95 -0
  225. claude_acc_manager-1.1.0/test/unit/accounts/domain/test_oauth_identity.py +158 -0
  226. claude_acc_manager-1.1.0/test/unit/accounts/domain/test_value_objects.py +79 -0
  227. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/fixtures/config_torn.txt +1 -0
  228. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/fixtures/config_valid.json +11 -0
  229. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/fixtures/credentials_not_object.json +1 -0
  230. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/fixtures/credentials_sibling_keys.json +11 -0
  231. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/fixtures/credentials_torn.txt +1 -0
  232. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/fixtures/credentials_valid.json +9 -0
  233. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_account_credential_store.py +294 -0
  234. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_account_dir_files.py +308 -0
  235. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_active_slot.py +557 -0
  236. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_claude_contract_probe.py +89 -0
  237. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_claude_locks.py +496 -0
  238. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_claude_login_launcher.py +411 -0
  239. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_diagnostics_probe.py +348 -0
  240. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_file_account_store.py +891 -0
  241. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_ops_lock.py +97 -0
  242. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_path_resolver.py +271 -0
  243. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_system_clock.py +46 -0
  244. claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_unclaimed_store.py +74 -0
  245. claude_acc_manager-1.1.0/test/unit/auto/__init__.py +0 -0
  246. claude_acc_manager-1.1.0/test/unit/auto/application/__init__.py +0 -0
  247. claude_acc_manager-1.1.0/test/unit/auto/application/test_auto_engine.py +965 -0
  248. claude_acc_manager-1.1.0/test/unit/auto/application/test_auto_loop.py +300 -0
  249. claude_acc_manager-1.1.0/test/unit/auto/application/use_cases/__init__.py +0 -0
  250. claude_acc_manager-1.1.0/test/unit/auto/application/use_cases/test_freshen_target.py +245 -0
  251. claude_acc_manager-1.1.0/test/unit/auto/domain/__init__.py +0 -0
  252. claude_acc_manager-1.1.0/test/unit/auto/domain/services/__init__.py +0 -0
  253. claude_acc_manager-1.1.0/test/unit/auto/domain/services/test_auto_rank.py +1433 -0
  254. claude_acc_manager-1.1.0/test/unit/auto/domain/services/test_loop_delay.py +119 -0
  255. claude_acc_manager-1.1.0/test/unit/auto/infrastructure/__init__.py +0 -0
  256. claude_acc_manager-1.1.0/test/unit/auto/infrastructure/test_file_auto_state.py +145 -0
  257. claude_acc_manager-1.1.0/test/unit/conftest.py +29 -0
  258. claude_acc_manager-1.1.0/test/unit/settings/application/test_settings_use_cases.py +93 -0
  259. claude_acc_manager-1.1.0/test/unit/settings/domain/test_settings_spec.py +187 -0
  260. claude_acc_manager-1.1.0/test/unit/settings/infrastructure/test_file_settings.py +185 -0
  261. claude_acc_manager-1.1.0/test/unit/shared/test_claude_contract.py +317 -0
  262. claude_acc_manager-1.1.0/test/unit/shared/test_container.py +87 -0
  263. claude_acc_manager-1.1.0/test/unit/shared/test_file_lock.py +122 -0
  264. claude_acc_manager-1.1.0/test/unit/shared/test_fsio.py +386 -0
  265. claude_acc_manager-1.1.0/test/unit/support/test_controllable_clock.py +31 -0
  266. claude_acc_manager-1.1.0/test/unit/support/test_fake_claude_contract.py +37 -0
  267. claude_acc_manager-1.1.0/test/unit/support/test_in_memory_account_store.py +100 -0
  268. claude_acc_manager-1.1.0/test/unit/support/test_in_memory_usage_cache.py +42 -0
  269. claude_acc_manager-1.1.0/test/unit/test_auto_output.py +161 -0
  270. claude_acc_manager-1.1.0/test/unit/test_cli.py +3812 -0
  271. claude_acc_manager-1.1.0/test/unit/test_human.py +239 -0
  272. claude_acc_manager-1.1.0/test/unit/test_main.py +306 -0
  273. claude_acc_manager-1.1.0/test/unit/tui/test_app.py +505 -0
  274. claude_acc_manager-1.1.0/test/unit/tui/test_auto.py +416 -0
  275. claude_acc_manager-1.1.0/test/unit/tui/test_breakpoints.py +70 -0
  276. claude_acc_manager-1.1.0/test/unit/tui/test_dashboard.py +414 -0
  277. claude_acc_manager-1.1.0/test/unit/tui/test_entry.py +69 -0
  278. claude_acc_manager-1.1.0/test/unit/tui/test_formatting.py +134 -0
  279. claude_acc_manager-1.1.0/test/unit/tui/test_help.py +101 -0
  280. claude_acc_manager-1.1.0/test/unit/tui/test_modals.py +95 -0
  281. claude_acc_manager-1.1.0/test/unit/tui/test_palette.py +165 -0
  282. claude_acc_manager-1.1.0/test/unit/tui/test_settings_screen.py +299 -0
  283. claude_acc_manager-1.1.0/test/unit/tui/test_switch.py +434 -0
  284. claude_acc_manager-1.1.0/test/unit/tui/test_terminal_title.py +186 -0
  285. claude_acc_manager-1.1.0/test/unit/tui/test_theme.py +57 -0
  286. claude_acc_manager-1.1.0/test/unit/tui/test_throbber.py +172 -0
  287. claude_acc_manager-1.1.0/test/unit/tui/test_visuals.py +455 -0
  288. claude_acc_manager-1.1.0/test/unit/tui/test_watch.py +260 -0
  289. claude_acc_manager-1.1.0/test/unit/tui/test_widgets.py +821 -0
  290. claude_acc_manager-1.1.0/test/unit/usage/application/use_cases/test_fetch_account_usage.py +1155 -0
  291. claude_acc_manager-1.1.0/test/unit/usage/domain/fixtures/usage_response_full.json +35 -0
  292. claude_acc_manager-1.1.0/test/unit/usage/domain/fixtures/usage_response_minimal.json +10 -0
  293. claude_acc_manager-1.1.0/test/unit/usage/domain/services/test_cache_trust.py +125 -0
  294. claude_acc_manager-1.1.0/test/unit/usage/domain/services/test_headroom.py +117 -0
  295. claude_acc_manager-1.1.0/test/unit/usage/domain/services/test_pace.py +125 -0
  296. claude_acc_manager-1.1.0/test/unit/usage/domain/services/test_poll_policy.py +711 -0
  297. claude_acc_manager-1.1.0/test/unit/usage/domain/test_oauth_credential.py +91 -0
  298. claude_acc_manager-1.1.0/test/unit/usage/domain/test_resolved_identity.py +80 -0
  299. claude_acc_manager-1.1.0/test/unit/usage/domain/test_usage_cache_entry.py +20 -0
  300. claude_acc_manager-1.1.0/test/unit/usage/domain/test_usage_snapshot.py +148 -0
  301. claude_acc_manager-1.1.0/test/unit/usage/infrastructure/test_anthropic_oauth.py +522 -0
  302. claude_acc_manager-1.1.0/test/unit/usage/infrastructure/test_claude_contract_probe.py +67 -0
  303. claude_acc_manager-1.1.0/test/unit/usage/infrastructure/test_file_usage_cache.py +421 -0
  304. claude_acc_manager-1.1.0/test/unit/usage/infrastructure/test_http_transport.py +178 -0
  305. claude_acc_manager-1.1.0/test/unit/usage/infrastructure/test_system_clock.py +26 -0
  306. claude_acc_manager-1.1.0/uv.lock +1865 -0
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: next-task
3
+ description: Orient on the repo docs and execute the next milestone task — strict TDD, per-commit gate
4
+ allowed-tools:
5
+ - read
6
+ - grep
7
+ - glob
8
+ permissions:
9
+ allow:
10
+ - Exec(git status)
11
+ - Exec(git log)
12
+ - Exec(git diff)
13
+ triggers:
14
+ - user
15
+ ---
16
+
17
+ # Execution session
18
+
19
+ You are starting a clean session with no memory of earlier ones. The repo's docs and git history are your memory. Follow these steps in order.
20
+
21
+ ## 1. Orient — grep first, read only what you need
22
+
23
+ - Run `git log --oneline -15` and `git status --short`. Uncommitted or untracked work means an earlier session stopped partway through — understand it before starting anything new.
24
+ - Open `maintainer/backlog.md`. Its "Finding things" table lists the greps. Find the next milestone: `grep -n "open ← next" maintainer/backlog.md`.
25
+ - If that milestone has a PRD, it is `maintainer/slices/SL-NNN-*.md` — find its first unchecked task with `grep -n "\[ \]" maintainer/slices/SL-NNN-*.md`. If it has none, you write it first (§3).
26
+ - For a decision, grep the ADR id (`grep -rn "ADR-0009" docs/`) and read only that ADR. For porting evidence, `grep -rn "GAP-" maintainer/research/`.
27
+ - Follow `AGENTS.md`. Do the searches yourself; spawn agents only if I ask.
28
+
29
+ ## 2. Scope
30
+
31
+ - Work the current milestone's next task; a milestone lands as a series of green commits. If a step is too big, split it in the PRD first, then do the first piece.
32
+ - Finish what was started. No new milestone while an earlier one is `open` or `partial`.
33
+ - Pre-visibility order (temporary): M15 and M16 land before the repo goes public; the visibility flip then unblocks M14's closeout (public-run validation + ruleset) and M17's Pages deploy; M18 ships last.
34
+
35
+ ## 3. If the milestone has no PRD, write it first
36
+
37
+ Copy `maintainer/slices/_TEMPLATE.md` to `maintainer/slices/SL-NNN-<slug>.md`. A PRD exists so the executing session never guesses and never assumes. It is not ready without:
38
+
39
+ - **Who else implements this** — a row per reference repo (`claude-swap`, `ai-usagebar`, plus claude-code itself where we interoperate with its behavior), including those lacking the feature. A capability present in none is an invention and must be justified.
40
+ - **Implementation inventory** — per repo that has it, the actual files to open grouped by concern (domain, ports, use case, adapter, CLI entry, persistence, edge cases). Paths, not summaries. Where two repos disagree, the disagreement is the decision the milestone must make — name it.
41
+ - **Gap analysis** — our code against the references, file against file: absent, unreachable, partial, or complete.
42
+
43
+ Verify every `research_repos/` path with `test -e` in the session that writes the PRD. Symbol greps find candidates; open the files before counting them. Cite `path` (add a `~N` line hint only if checked today).
44
+
45
+ ## 4. Build — strict TDD, evidence over assumption
46
+
47
+ - **Before writing code, analyze the source and docs of the reference repos** named in the PRD and take the best of them (`research_repos/`, read-only — never install, import, link, vendor, or commit it).
48
+ - Test-driven development strictly: RED → GREEN → REFACTOR. Each unit is one conventional commit (`type(scope): imperative` — scope = component or concern).
49
+ - Act on measurement and reading, never on assumption. **No workarounds, no band-aids, no over-engineering, no dead code.** Minimum code that solves the problem; refactor carefully when needed.
50
+ - A defect you introduce is yours to fix before you stop, whether or not it was planned — with a regression test.
51
+ - You may `curl` and web-search to double-check facts when the references and official docs don't settle one.
52
+ - Tests are hermetic (injected paths/clocks/transports), Triple-A, named fakes in `test/support/`, tree mirrors `src/`.
53
+
54
+ ## 5. Close the step
55
+
56
+ - The gate is the commit: `uv run pre-commit run --all-files` (actionlint, pyright strict, deptry, bandit, vulture, xenon, import-linter, pytest ≥95% branch, mutmut — see `docs/adr/0011-strict-tdd-per-commit-gate-coverage-and-mutation.md`). No `--no-verify`, ever.
57
+ - Gate and milestone-exit measurements go in the **commit body**, never in a doc.
58
+ - Update the PRD checkboxes and the backlog row by **editing** — one line per milestone, no dated blocks.
59
+ - When a milestone ships: record the exit-gate numbers in the closing commit body, mark the row `shipped`, delete its slice PRD — durable residue goes into an ADR or a code comment first.
60
+ - Stop, and report what shipped, what you measured, and what the next task is.
61
+
62
+ ## Release (M18)
63
+
64
+ - Versions, `CHANGELOG.md`, `v*` tags, and PyPI uploads are **CI artifacts** — semantic-release derives them from conventional commits. Never bump `version` in `pyproject.toml`, hand-edit `CHANGELOG.md`, create or push a tag, or publish a dist inside a session.
65
+ - Commit messages are the release input — keep them conventional (`feat`/`fix`/`docs`/`chore`/`refactor`/`test`/`perf`, `!` or `BREAKING CHANGE` for majors) so the version bump parses correctly.
66
+ - A session fixes code; the release workflow cuts it. If a release itself misbehaves, that's the defect to fix (in the workflow or tool config), never a manual release.
67
+ - CI is supply-chain surface: actions pin by SHA, and every scanner M14 installs (`zizmor`, `gitleaks`, `pip-audit`, CodeQL, Scorecard) must stay green — a new finding is a defect to fix, never suppress or exclude-rule around it.
68
+
69
+ ## Reference repositories — `research_repos/`, read-only and untracked
70
+
71
+ | Repo | Read it for |
72
+ | --- | --- |
73
+ | `claude-swap` (v0.27.0b1, Python) | switch transaction + rollback, claude-code mkdir locks, autoswitch loop, poll policy, usage store/backoff, identity oracle |
74
+ | `ai-usagebar` (v1.14.0, Rust) | `CLAUDE_CONFIG_DIR` account model, `account switch` (dry-run, outgoing capture), flat 429 backoff, usage response parsing |
75
+ | `toad` (v0.6.20, Python) | textual-8.x idioms (`getters`, `@work`/`@on`, `data_bind`, `AUTO_FOCUS`), UX affordances (help panel, throbber, terminal title, breakpoints, key-accelerated menus, schema-driven settings screen), `Visual`/`Strip` renderers, rich CLI output |
76
+
77
+ Structural conventions live in `docs/adr/0010-package-by-component-import-linter-contracts.md` (package-by-component + import-linter) and `AGENTS.md` — no external exemplar.
@@ -0,0 +1,51 @@
1
+ name: Bug report
2
+ description: Something in cam isn't working right
3
+ labels: [bug]
4
+ body:
5
+ - type: textarea
6
+ id: what-happened
7
+ attributes:
8
+ label: What happened?
9
+ description: What did you do, what did you see, and what did you expect instead?
10
+ placeholder: Ran `cam switch`, got ... — expected ...
11
+ validations:
12
+ required: true
13
+ - type: input
14
+ id: version
15
+ attributes:
16
+ label: cam version and install method
17
+ description: Output of `cam --version`, and whether you installed with pipx, uv, or from source.
18
+ placeholder: cam 1.2.3, installed with pipx
19
+ validations:
20
+ required: true
21
+ - type: input
22
+ id: claude-version
23
+ attributes:
24
+ label: claude version
25
+ description: Output of `claude --version` — mutating operations only run inside a verified version band, so this matters.
26
+ placeholder: "claude 2.1.150"
27
+ validations:
28
+ required: true
29
+ - type: input
30
+ id: distro
31
+ attributes:
32
+ label: Linux distribution
33
+ placeholder: "Ubuntu 24.04, Fedora 42, …"
34
+ validations:
35
+ required: false
36
+ - type: textarea
37
+ id: doctor
38
+ attributes:
39
+ label: cam doctor output
40
+ description: Paste the output of `cam doctor` — it reports the interop setup (paths, locks, env, contract band). Review before pasting and remove anything you don't want public.
41
+ render: shell
42
+ validations:
43
+ required: false
44
+ - type: textarea
45
+ id: logs
46
+ attributes:
47
+ label: Command output
48
+ description: Paste the failing command's output — `--json` output is fine too. Never paste access/refresh tokens, API keys, or credential-file contents — cam never prints them, and neither should you.
49
+ render: shell
50
+ validations:
51
+ required: false
@@ -0,0 +1,8 @@
1
+ blank_issues_enabled: true
2
+ contact_links:
3
+ - name: Architecture and design decisions
4
+ url: https://github.com/dmenezesgabriel/claude-acc-manager/blob/main/docs/architecture.md
5
+ about: How cam interoperates with Claude Code — locks, credential files, the usage endpoint, and the verified version band.
6
+ - name: Security concerns
7
+ url: https://github.com/dmenezesgabriel/claude-acc-manager/blob/main/SECURITY.md
8
+ about: Credential-store bugs are reported privately, never as public issues.
@@ -0,0 +1,22 @@
1
+ name: Feature request
2
+ description: Suggest an improvement or new capability
3
+ labels: [enhancement]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: What problem would this solve?
9
+ description: Describe the situation where you miss this feature — the workflow matters more than the mechanism.
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: solution
14
+ attributes:
15
+ label: What would you like to happen?
16
+ description: A rough sketch is fine — commands, flags, behavior.
17
+ validations:
18
+ required: false
19
+ - type: markdown
20
+ attributes:
21
+ value: |
22
+ Worth knowing before you propose: the project is intentionally narrow — Linux only, OAuth subscription accounts only, no API-key or setup-token accounts, no daemon/systemd unit. The explicit non-goals are listed in `docs/architecture.md` §1.
@@ -0,0 +1,20 @@
1
+ <!--
2
+ Thanks for the contribution. The checklist below is what review would
3
+ otherwise ask for — see CONTRIBUTING.md for the reasoning behind each.
4
+ -->
5
+
6
+ ## What this changes
7
+
8
+
9
+
10
+ ## Checklist
11
+
12
+ - [ ] `uv run pre-commit run --all-files` is green — the same gate the commit hooks run (ruff, pyright strict, deptry, bandit, vulture, xenon, import-linter, pytest ≥95% branch, mutmut)
13
+ - [ ] Commit messages are conventional — `type(scope): imperative`
14
+ - [ ] Bug fixes include a test that fails on `main` and passes here — say so below
15
+ - [ ] Tests are hermetic — no real `$HOME`/`$XDG`, no network; `-m integration` is opt-in only
16
+ - [ ] Docs updated where they mention what changed (`README.md`, `docs/`)
17
+
18
+ ## Testing
19
+
20
+ <!-- What you ran, and on which distribution. -->
@@ -0,0 +1,9 @@
1
+ # actionlint 1.7.12 predates GitHub's `$/` self-repository syntax — the
2
+ # dedicated form for referencing actions/workflows in the same repo at the
3
+ # triggering commit, which zizmor's self-repository audit prefers over
4
+ # `./`. Until upstream supports it (rhysd/actionlint), the false positive
5
+ # is ignored here via the same mechanism ruff uses for cache-mode.
6
+ paths:
7
+ ".github/workflows/*.yml":
8
+ ignore:
9
+ - 'specifying action "\$/[^"]*" in invalid format because ref is missing'
@@ -0,0 +1,31 @@
1
+ name: setup-env
2
+
3
+ # The env block every env-consuming job used to paste: pin the uv binary
4
+ # (SHA-pinned action + version/checksum-pinned tool, cached) and
5
+ # `uv sync --locked`. `checkout` deliberately stays a job step — a
6
+ # composite-internal checkout would make this action own the
7
+ # `persist-credentials` decision, which belongs to each job.
8
+ description: Install the pinned uv toolchain and sync the locked project env
9
+
10
+ inputs:
11
+ python-version:
12
+ description: >-
13
+ Interpreter setup-uv provisions. Empty falls back to
14
+ `.python-version` (setup-uv treats "" as unset); `test` passes the
15
+ matrix value.
16
+ required: false
17
+ default: ""
18
+
19
+ runs:
20
+ using: composite
21
+ steps:
22
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
23
+ with:
24
+ version: "0.11.8"
25
+ checksum: "56dd1b66701ecb62fe896abb919444e4b83c5e8645cca953e6ddd496ff8a0feb"
26
+ enable-cache: true
27
+ python-version: ${{ inputs.python-version }}
28
+
29
+ - name: Install dependencies
30
+ run: uv sync --locked
31
+ shell: bash
@@ -0,0 +1,20 @@
1
+ version: 2
2
+
3
+ # github-actions keeps every `# vX.Y.Z`-commented SHA pin fresh — that is what
4
+ # makes SHA pinning sustainable. uv keeps pyproject + uv.lock current.
5
+ # cooldown defers any release younger than 7 days — freshly-published
6
+ # versions are the window most supply-chain attacks are caught inside.
7
+ updates:
8
+ - package-ecosystem: github-actions
9
+ directory: /
10
+ schedule:
11
+ interval: weekly
12
+ cooldown:
13
+ default-days: 7
14
+
15
+ - package-ecosystem: uv
16
+ directory: /
17
+ schedule:
18
+ interval: weekly
19
+ cooldown:
20
+ default-days: 7
@@ -0,0 +1,109 @@
1
+ name: CI
2
+
3
+ # The required merge gate. `gate` runs `pre-commit run --all-files`
4
+ # verbatim so CI can never pass what a commit cannot; `test` re-runs the
5
+ # suite across the advertised requires-python range. The duplication is
6
+ # intentional — gate proves the whole-repo gate at the pinned version,
7
+ # the matrix proves the version surface. The scanner battery lives in
8
+ # security.yml, which also owns the weekly drift cron.
9
+ #
10
+ # Job `name:` values are the required-check API — the default-branch
11
+ # ruleset binds these names. Renaming one requires updating the ruleset
12
+ # in the same commit.
13
+ on:
14
+ push:
15
+ branches: [main]
16
+ pull_request:
17
+ workflow_dispatch:
18
+
19
+ # Default-deny: a job inherits {} unless it re-declares scopes. Every job
20
+ # needs at least `contents: read` — a zero-scope GITHUB_TOKEN cannot even
21
+ # clone the repo (checkout 404s on private, and reads stay token-scoped).
22
+ permissions: {}
23
+
24
+ # Sha-keyed so consecutive main pushes never cancel each other — a ref-keyed
25
+ # group would kill the in-flight main run and lose its coverage signal and
26
+ # mutmut cache banking. PR pushes share the pull_request.number key, so a
27
+ # fresh push still cancels a stale PR run.
28
+ concurrency:
29
+ group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number || github.sha }}
30
+ cancel-in-progress: true
31
+
32
+ env:
33
+ # Every uv invocation must verify the lockfile, never silently relock.
34
+ UV_LOCKED: "1"
35
+
36
+ jobs:
37
+ gate:
38
+ name: gate
39
+ runs-on: ubuntu-24.04
40
+ timeout-minutes: 120
41
+ permissions:
42
+ contents: read
43
+ steps:
44
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
45
+ with:
46
+ persist-credentials: false
47
+
48
+ - uses: $/.github/actions/setup-env
49
+
50
+ # mutants/ is mutmut's workdir: it doubles as the incremental cache,
51
+ # and a cold ~9k-mutant pass already exceeded a 60-min timeout once.
52
+ # Split restore/save so even a killed run banks progress — plain
53
+ # actions/cache only saves on success and the cache would never warm.
54
+ # The key partitions on the TEST tree: mutmut re-runs mutants when
55
+ # their source function changes but trusts verdicts across test edits,
56
+ # so a restore matching a different test/ would replay stale
57
+ # survived/killed results verbatim (observed: 0 mutants re-tested).
58
+ # The hash is `git rev-parse HEAD:test`, computed ONCE here:
59
+ # hashFiles() evaluates lazily per step, and pytest/mutmut drop
60
+ # __pycache__/ under test/ mid-job — a hashFiles save key therefore
61
+ # never matched the restore prefix (observed: 5 saves, 0 hits).
62
+ - name: Compute test-tree hash for the mutants cache key
63
+ id: testhash
64
+ run: echo "hash=$(git rev-parse HEAD:test)" >> "$GITHUB_OUTPUT"
65
+
66
+ - uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
67
+ with:
68
+ path: mutants/
69
+ key: mutants-t${{ steps.testhash.outputs.hash }}-${{ github.run_id }}
70
+ # Deliberately no loose fallback prefix: pre-fix entries were keyed
71
+ # on a pyc-mutated tree that maps to no real commit — restoring one
72
+ # would replay exactly the stale verdicts the partition exists to
73
+ # prevent. The cache rewarms from this commit forward.
74
+ restore-keys: mutants-t${{ steps.testhash.outputs.hash }}-
75
+
76
+ - name: Full local gate (lint, types, contracts, tests ≥95% branch, mutants)
77
+ run: uv run pre-commit run --all-files
78
+
79
+ - if: always()
80
+ uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
81
+ with:
82
+ path: mutants/
83
+ key: mutants-t${{ steps.testhash.outputs.hash }}-${{ github.run_id }}
84
+
85
+ test:
86
+ name: test (${{ matrix.python-version }})
87
+ runs-on: ubuntu-24.04
88
+ timeout-minutes: 20
89
+ permissions:
90
+ contents: read
91
+ strategy:
92
+ fail-fast: false
93
+ matrix:
94
+ python-version: ["3.11", "3.12", "3.13"]
95
+ env:
96
+ # `.python-version` pins 3.12 — the env override is what makes the
97
+ # matrix real; both `uv sync` and `uv run` honor it.
98
+ UV_PYTHON: ${{ matrix.python-version }}
99
+ steps:
100
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
101
+ with:
102
+ persist-credentials: false
103
+
104
+ - uses: $/.github/actions/setup-env
105
+ with:
106
+ python-version: ${{ matrix.python-version }}
107
+
108
+ - name: Run tests
109
+ run: uv run pytest
@@ -0,0 +1,82 @@
1
+ name: Docs
2
+
3
+ # The user docs site (M17). `docs` is the merge-facing check — it runs the
4
+ # same `mkdocs build --strict` the local pre-commit hook runs — and `deploy`
5
+ # publishes the built site to GitHub Pages on main pushes once the repo is
6
+ # public. The split is permission scoping, not ceremony: only `deploy` holds
7
+ # `pages: write` + `id-token: write`; `docs` is contents:read end to end.
8
+ # No paths: filter — a required check that path-skips on untouched PRs would
9
+ # deadlock them.
10
+ #
11
+ # Job `name:` values are the required-check API — the default-branch
12
+ # ruleset binds these names. Renaming one requires updating the ruleset
13
+ # in the same commit.
14
+ on:
15
+ push:
16
+ branches: [main]
17
+ pull_request:
18
+ workflow_dispatch:
19
+
20
+ # Default-deny: a job inherits {} unless it re-declares scopes.
21
+ permissions: {}
22
+
23
+ # Sha-keyed like the sibling workflows — consecutive main pushes must both
24
+ # deploy; a ref-keyed group would drop a deploy between pushes.
25
+ concurrency:
26
+ group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number || github.sha }}
27
+ cancel-in-progress: true
28
+
29
+ env:
30
+ # Every uv invocation must verify the lockfile, never silently relock.
31
+ UV_LOCKED: "1"
32
+ # Silences the vendor banner mkdocs-material prints to stderr on every
33
+ # invocation — it is upstream advocacy, not a build diagnostic (the
34
+ # package's own opt-out).
35
+ NO_MKDOCS_2_WARNING: "1"
36
+
37
+ jobs:
38
+ docs:
39
+ name: docs
40
+ runs-on: ubuntu-24.04
41
+ timeout-minutes: 10
42
+ permissions:
43
+ contents: read
44
+ steps:
45
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
46
+ with:
47
+ persist-credentials: false
48
+
49
+ - uses: $/.github/actions/setup-env
50
+
51
+ # The gate's verbatim check: strict mode escalates the validation
52
+ # block (omitted files, broken links) to build errors.
53
+ - name: Build site (strict)
54
+ run: uv run --group docs mkdocs build --strict
55
+
56
+ # configure-pages is a near-noop once Pages source = "GitHub Actions"
57
+ # is set in repo settings — the maintainer flips that at go-public.
58
+ - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
59
+ if: github.event_name == 'push' && github.event.repository.visibility == 'public'
60
+
61
+ - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
62
+ if: github.event_name == 'push' && github.event.repository.visibility == 'public'
63
+ with:
64
+ path: site/
65
+
66
+ deploy:
67
+ name: deploy
68
+ needs: docs
69
+ runs-on: ubuntu-24.04
70
+ timeout-minutes: 10
71
+ # Inert while private — Pages needs a public repo; the guard mirrors
72
+ # codeql/scorecard so the job activates the moment visibility flips.
73
+ if: github.event_name == 'push' && github.event.repository.visibility == 'public'
74
+ permissions:
75
+ pages: write # deploy to Pages
76
+ id-token: write # OIDC proof for deploy-pages
77
+ environment:
78
+ name: github-pages
79
+ url: ${{ steps.deployment.outputs.page_url }}
80
+ steps:
81
+ - id: deployment
82
+ uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
@@ -0,0 +1,176 @@
1
+ name: Release
2
+
3
+ # The PyPI release pipeline (M18, attrs model): CI never pushes to main —
4
+ # the main-branch ruleset requires the nine checks on every commit, and a
5
+ # fresh release commit can never have run them yet. Instead the maintainer
6
+ # cuts the release locally (python-semantic-release does the version math
7
+ # from conventional commits — the input format the commit-msg hook already
8
+ # enforces) and the release commit rides a normal PR, so it passes the same
9
+ # gate as everything else. Pushing the v{version} tag — not ruleset-
10
+ # restricted; the ruleset targets ~DEFAULT_BRANCH only — then triggers this
11
+ # workflow, which builds the dists from the tag and publishes them: GitHub
12
+ # Release with dist assets, TestPyPI (ungated canary), PyPI (reviewer-gated
13
+ # environment) — all via OIDC trusted publishing, no stored credentials.
14
+ #
15
+ # Maintainer flow per release (the release commit is chore(release), which
16
+ # is not a bumping type, so the tag names the version it carries):
17
+ # git switch -c release/vX.Y.Z && git pull
18
+ # uvx --from python-semantic-release==10.7.0 semantic-release version \
19
+ # --no-tag --no-vcs-release
20
+ # gh pr create ... && gh pr merge --rebase # nine checks run on it
21
+ # git switch main && git pull
22
+ # git tag "v$(uvx --from python-semantic-release==10.7.0 \
23
+ # semantic-release version --print)" && git push origin "v$(...)"
24
+ #
25
+ # Fork guard on every job (attrs' defense in depth): a fork's tag push must
26
+ # never reach this repo's release machinery.
27
+ on:
28
+ push:
29
+ tags: ["v*"]
30
+
31
+ # Default-deny: a job inherits {} unless it re-declares scopes.
32
+ permissions: {}
33
+
34
+ # Publishes serialize per tag, never cancel: a mid-flight publish killed by
35
+ # a cancel would strand the release halfway.
36
+ concurrency:
37
+ group: ${{ github.workflow }}-${{ github.ref }}
38
+ cancel-in-progress: false
39
+
40
+ env:
41
+ # Every uv invocation must verify the lockfile, never silently relock.
42
+ UV_LOCKED: "1"
43
+
44
+ jobs:
45
+ build:
46
+ name: build
47
+ runs-on: ubuntu-24.04
48
+ timeout-minutes: 15
49
+ if: github.repository == 'dmenezesgabriel/claude-acc-manager'
50
+ permissions:
51
+ contents: read
52
+ steps:
53
+ # Tag ref by default on a tag push — build exactly what the tag names.
54
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
55
+ with:
56
+ persist-credentials: false
57
+
58
+ - uses: $/.github/actions/setup-env
59
+
60
+ # ai-usagebar's verify-version lesson, cheapened: the maintainer's
61
+ # local PSR run derives the surfaces, so assert they agree before
62
+ # anything publishes.
63
+ - name: Version surfaces must agree (tag == pyproject == uv.lock)
64
+ env:
65
+ RELEASE_TAG: ${{ github.ref_name }}
66
+ run: |
67
+ set -euo pipefail
68
+ version="${RELEASE_TAG#v}"
69
+ grep -Fqx "version = \"$version\"" pyproject.toml \
70
+ || { echo "::error::pyproject.toml version is not $version"; exit 1; }
71
+ grep -A1 -F 'name = "claude-acc-manager"' uv.lock | grep -Fqx "version = \"$version\"" \
72
+ || { echo "::error::uv.lock version is not $version"; exit 1; }
73
+
74
+ - name: Build dists
75
+ run: uv build
76
+
77
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
78
+ with:
79
+ name: dist
80
+ path: dist
81
+ if-no-files-found: error
82
+
83
+ # Separate job so the GH Release exists even if PyPI fails. The release
84
+ # notes are the tag's own CHANGELOG section (ai-usagebar's extraction) —
85
+ # PSR's publish-action is not used here: `semantic-release publish` refuses
86
+ # to run on a detached-HEAD tag checkout ("cannot match any release
87
+ # groups"), which is exactly what a tag-triggered job checks out.
88
+ release:
89
+ name: release (github.com)
90
+ needs: build
91
+ if: github.repository == 'dmenezesgabriel/claude-acc-manager'
92
+ runs-on: ubuntu-24.04
93
+ timeout-minutes: 10
94
+ permissions:
95
+ contents: write # create the GH Release, upload dist assets
96
+ steps:
97
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
98
+ with:
99
+ persist-credentials: false
100
+
101
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
102
+ with:
103
+ name: dist
104
+ path: dist
105
+
106
+ - name: Create or update the GitHub Release
107
+ env:
108
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
109
+ RELEASE_TAG: ${{ github.ref_name }}
110
+ run: |
111
+ set -euo pipefail
112
+ version="${RELEASE_TAG#v}"
113
+ # PSR's changelog headers are "## vX.Y.Z (date)" — prefix-match the
114
+ # tag's section, stop at the next "## v" header (dots stay literal).
115
+ awk -v ver="$version" '
116
+ BEGIN { hdr = "## v" ver }
117
+ index($0, hdr) == 1 { flag = 1; next }
118
+ flag && index($0, "## v") == 1 { exit }
119
+ flag { print }
120
+ ' CHANGELOG.md > release-notes.md
121
+ if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
122
+ gh release upload "$RELEASE_TAG" dist/* --repo "$GITHUB_REPOSITORY" --clobber
123
+ gh release edit "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --notes-file release-notes.md
124
+ else
125
+ gh release create "$RELEASE_TAG" dist/* --repo "$GITHUB_REPOSITORY" \
126
+ --title "$RELEASE_TAG" --notes-file release-notes.md --latest
127
+ fi
128
+
129
+ # Ungated canary: every release lands on TestPyPI first so the artifact is
130
+ # installable before the human approves the real publish. Independent of
131
+ # `deploy` — a TestPyPI failure never blocks PyPI; the reviewer is the gate.
132
+ deploy-test:
133
+ name: deploy-test (test.pypi.org)
134
+ needs: build
135
+ if: github.repository == 'dmenezesgabriel/claude-acc-manager'
136
+ runs-on: ubuntu-24.04
137
+ timeout-minutes: 10
138
+ permissions:
139
+ contents: read
140
+ id-token: write # OIDC proof for trusted publishing
141
+ environment: pypi-test
142
+ steps:
143
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
144
+ with:
145
+ name: dist
146
+ path: dist
147
+
148
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
149
+ with:
150
+ repository-url: https://test.pypi.org/legacy/
151
+ packages-dir: dist
152
+ print-hash: true
153
+
154
+ # The only reason this is a separate job: id-token: write lives here alone,
155
+ # and the pypi environment's required reviewer makes every real publish a
156
+ # human decision (PyPA guide recommendation).
157
+ deploy:
158
+ name: deploy (pypi.org)
159
+ needs: build
160
+ if: github.repository == 'dmenezesgabriel/claude-acc-manager'
161
+ runs-on: ubuntu-24.04
162
+ timeout-minutes: 10
163
+ permissions:
164
+ contents: read
165
+ id-token: write # OIDC proof for trusted publishing
166
+ environment: pypi
167
+ steps:
168
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
169
+ with:
170
+ name: dist
171
+ path: dist
172
+
173
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
174
+ with:
175
+ packages-dir: dist
176
+ print-hash: true
@@ -0,0 +1,49 @@
1
+ name: Scorecard
2
+
3
+ # Separate file from ci.yml: different cadence (default-branch pushes +
4
+ # weekly drift check, never PRs — pull_request is experimental upstream)
5
+ # and a permission profile nothing in ci.yml shares.
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ schedule:
10
+ - cron: "30 6 * * 1"
11
+ workflow_dispatch:
12
+
13
+ permissions: {}
14
+
15
+ # Sha-keyed so consecutive main pushes never cancel each other — a ref-keyed
16
+ # group would kill an in-flight run mid-publish. (This file has no
17
+ # pull_request trigger; the same expression is kept across workflows so the
18
+ # rule is learned once.)
19
+ concurrency:
20
+ group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.number || github.sha }}
21
+ cancel-in-progress: true
22
+
23
+ jobs:
24
+ scorecard:
25
+ name: scorecard
26
+ runs-on: ubuntu-24.04
27
+ timeout-minutes: 15
28
+ # Results publishing (id-token OIDC) + SARIF upload need the repo public;
29
+ # the guard keeps the job inert rather than failing if visibility flips
30
+ # back to private.
31
+ if: github.event.repository.visibility == 'public'
32
+ permissions:
33
+ contents: read # checkout
34
+ security-events: write # upload results.sarif to code scanning
35
+ id-token: write # OIDC for publish_results (scorecard.dev API)
36
+ steps:
37
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
38
+ with:
39
+ persist-credentials: false
40
+
41
+ - uses: ossf/scorecard-action@55891bbd73f2425e97637d96e306fc9d491d0b21 # v2.4.4
42
+ with:
43
+ results_file: results.sarif
44
+ results_format: sarif
45
+ publish_results: true
46
+
47
+ - uses: github/codeql-action/upload-sarif@88585263c0627ee42c0e1c5143a112c8d6f4aa18 # v4.38.2
48
+ with:
49
+ sarif_file: results.sarif