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.
- claude_acc_manager-1.1.0/.agents/skills/next-task/SKILL.md +77 -0
- claude_acc_manager-1.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +51 -0
- claude_acc_manager-1.1.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
- claude_acc_manager-1.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +22 -0
- claude_acc_manager-1.1.0/.github/PULL_REQUEST_TEMPLATE.md +20 -0
- claude_acc_manager-1.1.0/.github/actionlint.yaml +9 -0
- claude_acc_manager-1.1.0/.github/actions/setup-env/action.yml +31 -0
- claude_acc_manager-1.1.0/.github/dependabot.yml +20 -0
- claude_acc_manager-1.1.0/.github/workflows/ci.yml +109 -0
- claude_acc_manager-1.1.0/.github/workflows/docs.yml +82 -0
- claude_acc_manager-1.1.0/.github/workflows/release.yml +176 -0
- claude_acc_manager-1.1.0/.github/workflows/scorecard.yml +49 -0
- claude_acc_manager-1.1.0/.github/workflows/security.yml +120 -0
- claude_acc_manager-1.1.0/.gitignore +31 -0
- claude_acc_manager-1.1.0/.gitleaks.toml +20 -0
- claude_acc_manager-1.1.0/.pre-commit-config.yaml +130 -0
- claude_acc_manager-1.1.0/.python-version +1 -0
- claude_acc_manager-1.1.0/AGENTS.md +85 -0
- claude_acc_manager-1.1.0/CHANGELOG.md +417 -0
- claude_acc_manager-1.1.0/CONTRIBUTING.md +31 -0
- claude_acc_manager-1.1.0/LICENSE +21 -0
- claude_acc_manager-1.1.0/NOTICE +30 -0
- claude_acc_manager-1.1.0/PKG-INFO +69 -0
- claude_acc_manager-1.1.0/README.md +56 -0
- claude_acc_manager-1.1.0/SECURITY.md +17 -0
- claude_acc_manager-1.1.0/docs/adr/0000-template.md +46 -0
- claude_acc_manager-1.1.0/docs/adr/0001-split-durable-decisions-from-ephemeral-build-evidence.md +56 -0
- claude_acc_manager-1.1.0/docs/adr/0002-python-uv-stdlib-urllib-argparse-textual-only-dep.md +50 -0
- claude_acc_manager-1.1.0/docs/adr/0003-credentials-at-rest-under-xdg-with-private-modes.md +44 -0
- claude_acc_manager-1.1.0/docs/adr/0004-login-at-source-via-claude-config-dir.md +41 -0
- claude_acc_manager-1.1.0/docs/adr/0005-oauth-subscription-accounts-only.md +37 -0
- claude_acc_manager-1.1.0/docs/adr/0006-auto-scope-one-shot-and-foreground-loop.md +39 -0
- claude_acc_manager-1.1.0/docs/adr/0007-honest-user-agent-per-endpoint-headers.md +52 -0
- claude_acc_manager-1.1.0/docs/adr/0008-flat-429-backoff-and-last-good-trust.md +46 -0
- claude_acc_manager-1.1.0/docs/adr/0009-token-ownership-active-slot-never-refreshed.md +44 -0
- claude_acc_manager-1.1.0/docs/adr/0010-package-by-component-import-linter-contracts.md +47 -0
- claude_acc_manager-1.1.0/docs/adr/0011-strict-tdd-per-commit-gate-coverage-and-mutation.md +56 -0
- claude_acc_manager-1.1.0/docs/adr/0012-schema-tolerant-usage-model.md +44 -0
- claude_acc_manager-1.1.0/docs/adr/0013-transport-only-cli-main-composition-root.md +39 -0
- claude_acc_manager-1.1.0/docs/adr/0014-credential-write-boundary.md +76 -0
- claude_acc_manager-1.1.0/docs/adr/0015-claude-version-contract-gate.md +123 -0
- claude_acc_manager-1.1.0/docs/architecture.md +270 -0
- claude_acc_manager-1.1.0/docs/getting-started/installation.md +40 -0
- claude_acc_manager-1.1.0/docs/getting-started/quickstart.md +61 -0
- claude_acc_manager-1.1.0/docs/guides/auto-switching.md +70 -0
- claude_acc_manager-1.1.0/docs/guides/diagnostics.md +47 -0
- claude_acc_manager-1.1.0/docs/guides/scripting.md +55 -0
- claude_acc_manager-1.1.0/docs/guides/switching.md +62 -0
- claude_acc_manager-1.1.0/docs/index.md +27 -0
- claude_acc_manager-1.1.0/docs/reference/cli.md +82 -0
- claude_acc_manager-1.1.0/docs/reference/environment-variables.md +27 -0
- claude_acc_manager-1.1.0/docs/reference/json-output.md +87 -0
- claude_acc_manager-1.1.0/docs/reference/settings.md +29 -0
- claude_acc_manager-1.1.0/maintainer/backlog.md +70 -0
- claude_acc_manager-1.1.0/maintainer/research/release-pipeline.md +200 -0
- claude_acc_manager-1.1.0/maintainer/slices/SL-019-pypi-release-pipeline.md +137 -0
- claude_acc_manager-1.1.0/maintainer/slices/_TEMPLATE.md +94 -0
- claude_acc_manager-1.1.0/mkdocs.yml +87 -0
- claude_acc_manager-1.1.0/pyproject.toml +355 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/__init__.py +22 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/__main__.py +153 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/doctor_report.py +92 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/ports.py +454 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/switch_message.py +37 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/add_account.py +85 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/collect_accounts_view.py +119 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/collect_doctor_report.py +199 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/list_accounts.py +45 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/quarantine_dead_lineage.py +45 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/remove_account.py +36 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/set_account_enabled.py +35 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/status_account.py +48 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/application/use_cases/switch_account.py +378 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/credential_fields.py +115 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/entities.py +53 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/oauth_identity.py +74 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/services/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/services/identity_match.py +25 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/services/switch_selection.py +202 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/domain/value_objects.py +60 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/account_credential_store.py +134 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/account_dir_files.py +90 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/active_slot.py +149 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/claude_contract_probe.py +38 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/claude_locks.py +201 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/claude_login_launcher.py +144 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/diagnostics_probe.py +142 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/file_account_store.py +409 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/ops_lock.py +50 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/path_resolver.py +163 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/system_clock.py +17 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/accounts/infrastructure/unclaimed_store.py +67 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/auto/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/auto/application/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/auto/application/auto_engine.py +549 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/auto/application/ports.py +59 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/auto/application/use_cases/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/auto/application/use_cases/freshen_target.py +93 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/auto/domain/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/auto/domain/auto_event.py +112 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/auto/domain/auto_state.py +36 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/auto/domain/services/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/auto/domain/services/auto_rank.py +507 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/auto/domain/services/loop_delay.py +70 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/auto/infrastructure/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/auto/infrastructure/file_auto_state.py +79 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/cli/__init__.py +15 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/cli/auto_output.py +185 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/cli/commands.py +399 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/cli/context.py +81 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/cli/dispatch.py +114 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/cli/human.py +156 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/cli/json_output.py +311 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/cli/parser.py +171 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/settings/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/settings/application/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/settings/application/ports.py +40 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/settings/application/use_cases/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/settings/application/use_cases/list_settings.py +16 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/settings/application/use_cases/load_settings.py +16 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/settings/application/use_cases/set_setting.py +28 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/settings/application/use_cases/unset_setting.py +16 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/settings/domain/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/settings/domain/settings_spec.py +254 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/settings/infrastructure/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/settings/infrastructure/file_settings.py +133 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/shared/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/shared/claude_contract.py +175 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/shared/container.py +54 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/shared/file_lock.py +48 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/shared/fsio.py +104 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/shared/palette.py +15 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/tui/__init__.py +29 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/tui/account_list.py +365 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/tui/app.py +627 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/tui/autoview.py +245 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/tui/cam.tcss +243 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/tui/dashboard.py +304 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/tui/formatting.py +380 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/tui/modals.py +88 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/tui/palette.py +114 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/tui/settings_screen.py +130 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/tui/theme.py +134 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/tui/throbber.py +144 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/tui/visuals.py +326 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/tui/widgets.py +196 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/application/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/application/ports.py +265 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/application/use_cases/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/application/use_cases/fetch_account_usage.py +264 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/oauth_credential.py +91 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/resolved_identity.py +74 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/services/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/services/cache_trust.py +79 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/services/headroom.py +65 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/services/pace.py +85 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/services/poll_policy.py +410 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/usage_cache_entry.py +51 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/usage_report.py +23 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/domain/usage_snapshot.py +139 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/infrastructure/__init__.py +1 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/infrastructure/anthropic_oauth.py +251 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/infrastructure/claude_contract_probe.py +40 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/infrastructure/file_usage_cache.py +257 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/infrastructure/http_transport.py +51 -0
- claude_acc_manager-1.1.0/src/claude_acc_manager/usage/infrastructure/system_clock.py +17 -0
- claude_acc_manager-1.1.0/test/integration/test_anthropic_oauth_live.py +49 -0
- claude_acc_manager-1.1.0/test/integration/test_fetch_account_usage_live.py +112 -0
- claude_acc_manager-1.1.0/test/support/__init__.py +0 -0
- claude_acc_manager-1.1.0/test/support/controllable_clock.py +23 -0
- claude_acc_manager-1.1.0/test/support/engine_harness.py +160 -0
- claude_acc_manager-1.1.0/test/support/failable_account_dir.py +57 -0
- claude_acc_manager-1.1.0/test/support/failable_account_store.py +38 -0
- claude_acc_manager-1.1.0/test/support/failable_active_slot.py +72 -0
- claude_acc_manager-1.1.0/test/support/fake_account_dir.py +76 -0
- claude_acc_manager-1.1.0/test/support/fake_active_identity.py +20 -0
- claude_acc_manager-1.1.0/test/support/fake_active_slot.py +57 -0
- claude_acc_manager-1.1.0/test/support/fake_claude_contract.py +23 -0
- claude_acc_manager-1.1.0/test/support/fake_claude_locks.py +41 -0
- claude_acc_manager-1.1.0/test/support/fake_clock.py +15 -0
- claude_acc_manager-1.1.0/test/support/fake_credential_store.py +30 -0
- claude_acc_manager-1.1.0/test/support/fake_diagnostics.py +68 -0
- claude_acc_manager-1.1.0/test/support/fake_freshen.py +22 -0
- claude_acc_manager-1.1.0/test/support/fake_http_transport.py +43 -0
- claude_acc_manager-1.1.0/test/support/fake_lineage_quarantine.py +18 -0
- claude_acc_manager-1.1.0/test/support/fake_login_launcher.py +19 -0
- claude_acc_manager-1.1.0/test/support/fake_ops_lock.py +37 -0
- claude_acc_manager-1.1.0/test/support/fake_switch_executor.py +21 -0
- claude_acc_manager-1.1.0/test/support/fake_token_refresher.py +26 -0
- claude_acc_manager-1.1.0/test/support/fake_tui_module.py +23 -0
- claude_acc_manager-1.1.0/test/support/fake_unclaimed_store.py +35 -0
- claude_acc_manager-1.1.0/test/support/fake_usage_api.py +41 -0
- claude_acc_manager-1.1.0/test/support/fake_usage_fetch.py +33 -0
- claude_acc_manager-1.1.0/test/support/in_memory_account_store.py +86 -0
- claude_acc_manager-1.1.0/test/support/in_memory_auto_state.py +30 -0
- claude_acc_manager-1.1.0/test/support/in_memory_settings.py +55 -0
- claude_acc_manager-1.1.0/test/support/in_memory_usage_cache.py +23 -0
- claude_acc_manager-1.1.0/test/support/interrupting_fetch_usage.py +17 -0
- claude_acc_manager-1.1.0/test/support/interrupting_list_accounts.py +17 -0
- claude_acc_manager-1.1.0/test/support/recording_stream.py +25 -0
- claude_acc_manager-1.1.0/test/support/rich_asserts.py +76 -0
- claude_acc_manager-1.1.0/test/support/tui_app.py +166 -0
- claude_acc_manager-1.1.0/test/support/use_cases.py +191 -0
- claude_acc_manager-1.1.0/test/unit/accounts/application/test_switch_message.py +34 -0
- claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_add_account.py +274 -0
- claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_collect_accounts_view.py +180 -0
- claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_collect_doctor_report.py +239 -0
- claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_list_accounts.py +68 -0
- claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_quarantine_dead_lineage.py +97 -0
- claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_remove_account.py +117 -0
- claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_set_account_enabled.py +71 -0
- claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_status_account.py +116 -0
- claude_acc_manager-1.1.0/test/unit/accounts/application/use_cases/test_switch_account.py +1054 -0
- claude_acc_manager-1.1.0/test/unit/accounts/domain/services/test_identity_match.py +35 -0
- claude_acc_manager-1.1.0/test/unit/accounts/domain/services/test_switch_selection.py +320 -0
- claude_acc_manager-1.1.0/test/unit/accounts/domain/test_credential_fields.py +212 -0
- claude_acc_manager-1.1.0/test/unit/accounts/domain/test_entities.py +95 -0
- claude_acc_manager-1.1.0/test/unit/accounts/domain/test_oauth_identity.py +158 -0
- claude_acc_manager-1.1.0/test/unit/accounts/domain/test_value_objects.py +79 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/fixtures/config_torn.txt +1 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/fixtures/config_valid.json +11 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/fixtures/credentials_not_object.json +1 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/fixtures/credentials_sibling_keys.json +11 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/fixtures/credentials_torn.txt +1 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/fixtures/credentials_valid.json +9 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_account_credential_store.py +294 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_account_dir_files.py +308 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_active_slot.py +557 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_claude_contract_probe.py +89 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_claude_locks.py +496 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_claude_login_launcher.py +411 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_diagnostics_probe.py +348 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_file_account_store.py +891 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_ops_lock.py +97 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_path_resolver.py +271 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_system_clock.py +46 -0
- claude_acc_manager-1.1.0/test/unit/accounts/infrastructure/test_unclaimed_store.py +74 -0
- claude_acc_manager-1.1.0/test/unit/auto/__init__.py +0 -0
- claude_acc_manager-1.1.0/test/unit/auto/application/__init__.py +0 -0
- claude_acc_manager-1.1.0/test/unit/auto/application/test_auto_engine.py +965 -0
- claude_acc_manager-1.1.0/test/unit/auto/application/test_auto_loop.py +300 -0
- claude_acc_manager-1.1.0/test/unit/auto/application/use_cases/__init__.py +0 -0
- claude_acc_manager-1.1.0/test/unit/auto/application/use_cases/test_freshen_target.py +245 -0
- claude_acc_manager-1.1.0/test/unit/auto/domain/__init__.py +0 -0
- claude_acc_manager-1.1.0/test/unit/auto/domain/services/__init__.py +0 -0
- claude_acc_manager-1.1.0/test/unit/auto/domain/services/test_auto_rank.py +1433 -0
- claude_acc_manager-1.1.0/test/unit/auto/domain/services/test_loop_delay.py +119 -0
- claude_acc_manager-1.1.0/test/unit/auto/infrastructure/__init__.py +0 -0
- claude_acc_manager-1.1.0/test/unit/auto/infrastructure/test_file_auto_state.py +145 -0
- claude_acc_manager-1.1.0/test/unit/conftest.py +29 -0
- claude_acc_manager-1.1.0/test/unit/settings/application/test_settings_use_cases.py +93 -0
- claude_acc_manager-1.1.0/test/unit/settings/domain/test_settings_spec.py +187 -0
- claude_acc_manager-1.1.0/test/unit/settings/infrastructure/test_file_settings.py +185 -0
- claude_acc_manager-1.1.0/test/unit/shared/test_claude_contract.py +317 -0
- claude_acc_manager-1.1.0/test/unit/shared/test_container.py +87 -0
- claude_acc_manager-1.1.0/test/unit/shared/test_file_lock.py +122 -0
- claude_acc_manager-1.1.0/test/unit/shared/test_fsio.py +386 -0
- claude_acc_manager-1.1.0/test/unit/support/test_controllable_clock.py +31 -0
- claude_acc_manager-1.1.0/test/unit/support/test_fake_claude_contract.py +37 -0
- claude_acc_manager-1.1.0/test/unit/support/test_in_memory_account_store.py +100 -0
- claude_acc_manager-1.1.0/test/unit/support/test_in_memory_usage_cache.py +42 -0
- claude_acc_manager-1.1.0/test/unit/test_auto_output.py +161 -0
- claude_acc_manager-1.1.0/test/unit/test_cli.py +3812 -0
- claude_acc_manager-1.1.0/test/unit/test_human.py +239 -0
- claude_acc_manager-1.1.0/test/unit/test_main.py +306 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_app.py +505 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_auto.py +416 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_breakpoints.py +70 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_dashboard.py +414 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_entry.py +69 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_formatting.py +134 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_help.py +101 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_modals.py +95 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_palette.py +165 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_settings_screen.py +299 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_switch.py +434 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_terminal_title.py +186 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_theme.py +57 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_throbber.py +172 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_visuals.py +455 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_watch.py +260 -0
- claude_acc_manager-1.1.0/test/unit/tui/test_widgets.py +821 -0
- claude_acc_manager-1.1.0/test/unit/usage/application/use_cases/test_fetch_account_usage.py +1155 -0
- claude_acc_manager-1.1.0/test/unit/usage/domain/fixtures/usage_response_full.json +35 -0
- claude_acc_manager-1.1.0/test/unit/usage/domain/fixtures/usage_response_minimal.json +10 -0
- claude_acc_manager-1.1.0/test/unit/usage/domain/services/test_cache_trust.py +125 -0
- claude_acc_manager-1.1.0/test/unit/usage/domain/services/test_headroom.py +117 -0
- claude_acc_manager-1.1.0/test/unit/usage/domain/services/test_pace.py +125 -0
- claude_acc_manager-1.1.0/test/unit/usage/domain/services/test_poll_policy.py +711 -0
- claude_acc_manager-1.1.0/test/unit/usage/domain/test_oauth_credential.py +91 -0
- claude_acc_manager-1.1.0/test/unit/usage/domain/test_resolved_identity.py +80 -0
- claude_acc_manager-1.1.0/test/unit/usage/domain/test_usage_cache_entry.py +20 -0
- claude_acc_manager-1.1.0/test/unit/usage/domain/test_usage_snapshot.py +148 -0
- claude_acc_manager-1.1.0/test/unit/usage/infrastructure/test_anthropic_oauth.py +522 -0
- claude_acc_manager-1.1.0/test/unit/usage/infrastructure/test_claude_contract_probe.py +67 -0
- claude_acc_manager-1.1.0/test/unit/usage/infrastructure/test_file_usage_cache.py +421 -0
- claude_acc_manager-1.1.0/test/unit/usage/infrastructure/test_http_transport.py +178 -0
- claude_acc_manager-1.1.0/test/unit/usage/infrastructure/test_system_clock.py +26 -0
- 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
|