pushframe 5.1.2__tar.gz → 5.1.3__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.
- {pushframe-5.1.2 → pushframe-5.1.3}/CHANGELOG.md +27 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/PKG-INFO +1 -1
- {pushframe-5.1.2 → pushframe-5.1.3}/docs/CLI.md +63 -12
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/__init__.py +1 -1
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/cli.py +13 -3
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/google/bootstrap.py +15 -4
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/session.py +25 -2
- {pushframe-5.1.2 → pushframe-5.1.3}/pyproject.toml +1 -1
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_cli_apply.py +22 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_cli_google_link.py +31 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_cli_reconcile.py +80 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_config_wizard.py +77 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_session.py +50 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_version.py +1 -1
- {pushframe-5.1.2 → pushframe-5.1.3}/uv.lock +1 -1
- {pushframe-5.1.2 → pushframe-5.1.3}/.env.sample +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.freebuff/project-id +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.github/workflows/release.yml +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.github/workflows/tests.yml +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.gitignore +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.gsd/dispatch-isolation-sentinel.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/MILESTONES.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/PROJECT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/REQUIREMENTS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/RETROSPECTIVE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/ROADMAP.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/STATE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/WINDOWS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/codebase/ARCHITECTURE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/codebase/CONCERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/codebase/CONVENTIONS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/codebase/INTEGRATIONS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/codebase/STACK.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/codebase/STRUCTURE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/codebase/TESTING.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/config.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/debug/knowledge-base.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/debug/login-475-null-creds.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/debug/resolved/cli-status-verbose-logging.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/debug/resolved/inspect-asset-null-fields.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/debug/resolved/select-asset-401-unauthorized.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-REQUIREMENTS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-ROADMAP.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-LIVE-EVIDENCE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.1-ROADMAP.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/.gitkeep +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-03-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-03-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-SECURITY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-UAT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-REQUIREMENTS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-ROADMAP.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-SECURITY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-UAT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-SECURITY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-03-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-03-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-REVIEW-FIX.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-SECURITY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-LIVE-FINDINGS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW-FIX.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/.gitkeep +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-REVIEW-FIX.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/.gitkeep +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-03-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-03-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-04-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-04-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-LIVE-FINDINGS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-SECURITY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-UAT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/COVERAGE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-quick/260630-qs8-make-main-py-load-a-local-env-file-at-st/260630-qs8-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-quick/260630-qs8-make-main-py-load-a-local-env-file-at-st/260630-qs8-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/260708-dt9-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/260708-dt9-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/deferred-items.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v2.0-quick/README.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-REQUIREMENTS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-ROADMAP.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-LIVE-FINDINGS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-REQUIREMENTS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-ROADMAP.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DECISION-RECORD.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-LIVE-FINDINGS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/COVERAGE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-DISCUSS-CHECKPOINT.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/17-google-link-album-selection/COVERAGE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-03-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-03-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-UAT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/19-debt-closeout/19-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/19-debt-closeout/19-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v4.0-phases/19-debt-closeout/19-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-REQUIREMENTS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-ROADMAP.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/20-pushframe-rename-migration-repo-switch/20-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/OPERATOR-STEPS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/phases/23-config-wizard-precedence/23-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/phases/23-config-wizard-precedence/23-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/phases/23-config-wizard-precedence/23-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/phases/23-config-wizard-precedence/23-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/phases/23-config-wizard-precedence/23-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/research/ALBUM-ACCESS-V4-ADDENDUM.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/research/ALBUM-ACCESS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/research/ARCHITECTURE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/research/BROWSER-AUTOMATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/research/FEATURES.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/research/PITFALLS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/research/STACK.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/research/SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/state.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.planning/todos/completed/2026-07-06-promote-debug-flag-to-a-global-cli-convention.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/.python-version +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/CLAUDE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/LICENSE +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/README.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/SECURITY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/VERIFICATION-REPORT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/docs/agents/domain.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/docs/agents/issue-tracker.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/docs/agents/triage-labels.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/docs/superpowers/specs/2026-07-09-write-rate-limiter-design.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/main.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/packaging/control-template +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/packaging/lintian-overrides +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/packaging/postinst +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/packaging/pushframe.copyright +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/probes/.gitignore +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/probes/README.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/probes/browser_bootstrap.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/probes/common.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/probes/cookie_vault.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/probes/fidelity_check.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/probes/rpc_capture.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/probes/shared_link_probe.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/api/__init__.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/api/accountApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/api/activityApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/api/assetApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/api/baseApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/api/frameApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/api/notificationApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/api/peopleApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/api/playlistApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/aura.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/aws/__init__.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/aws/awsclient.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/aws/s3client.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/aws/sqsclient.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/cache.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/client.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/config_store.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/doctor.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/exif.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/export.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/google/__init__.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/google/cache.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/google/client.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/google/enumerate.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/google/manifest.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/google/parsers.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/google/redaction.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/google/vault.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/gsync.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/migration.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/models/__init__.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/models/activity.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/models/asset.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/models/frame.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/models/meta.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/models/person.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/models/user.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/pairs.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/preflight.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/ratelimit.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/reconcile.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/schedule.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/sync.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/utils/dt.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/utils/io.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/pushframe/utils/settings.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/scripts/build-deb.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/scripts/make-apt-key.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/scripts/publish-apt-repo.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/scripts/test-apt-journey-container.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/scripts/test-config-journey-container.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/scripts/test-deb-container.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/scripts/test-install-journey-container.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/scripts/test-multipair-journey-container.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/conftest.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/fixtures/asset_unprocessed.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/fixtures/assets_page1.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/fixtures/assets_page2.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/fixtures/assets_placeholders.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/fixtures/error_envelope.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/fixtures/frame_detail.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/fixtures/frames.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/fixtures/google_rpc_snackc_page.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/fixtures/google_share_page_sample.html +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/fixtures/login.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/fixtures/probe_af_initdata_sample.html +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/offline.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_asset_partial.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_asset_unprocessed.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_aura_logger.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_batch_update_contract.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_cli_google_album.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_cli_inspect.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_cli_push_budget_geo.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_cli_status.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_cli_status_google.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_cli_sync.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_client_rate_limit.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_doctor.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_execute_plan.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_execute_plan_budget_geo.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_fixtures_validity.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_google_cache.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_google_client.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_google_enumerate.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_google_manifest.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_google_parsers.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_google_vault.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_gsync_all.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_gsync_engine.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_gsync_execute.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_imports.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_logout.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_migration.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_network_guard.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_offline_read_path.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_pairs.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_preflight_sweep.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_prep_upload_formats.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_probe_browser.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_probe_shared_link.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_ratelimit.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_read_path.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_reconcile.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_retry_401.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_schedule.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_settings_budget.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_settings_resolution.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_sync_engine.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_write_endpoints_failloud.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_write_formats_live.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.3}/tests/test_write_throttling.py +0 -0
|
@@ -14,6 +14,33 @@ frames; it is not affiliated with Aura Frames Inc.
|
|
|
14
14
|
|
|
15
15
|
## [Unreleased]
|
|
16
16
|
|
|
17
|
+
## [5.1.3] — 2026-09-30
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **`pushframe config` — Enter now keeps the stored email, as the banner
|
|
22
|
+
always promised** (venus, 5.1.2): an empty answer on a configured
|
|
23
|
+
install aborted with "no email given — aborting" instead of keeping
|
|
24
|
+
it. With a stored token, Enter-Enter now finishes the wizard with ZERO
|
|
25
|
+
API calls (the anti-abuse rule: no gratuitous login); typing a new
|
|
26
|
+
email still re-logins, and a fresh install still aborts named on an
|
|
27
|
+
empty answer.
|
|
28
|
+
- **Prompt-contract audit — every interactive prompt checked against its
|
|
29
|
+
banner**, one behavior test per prompt. Two more promised-vs-real gaps
|
|
30
|
+
fixed alongside the wizard's: the session-path login prompt (expired
|
|
31
|
+
token refresh) showed NO banner and aborted the login on a bare Enter —
|
|
32
|
+
it now prints the same "current email — Enter keeps it" banner and
|
|
33
|
+
keeps it; and the two hard-delete confirmation gates only said "To
|
|
34
|
+
confirm, type the number…" — now "Verbatim to confirm —", because the
|
|
35
|
+
y/N reflex answer being silently rejected WAS the contract, just never
|
|
36
|
+
the wording. google-link's manual Enter prompt reworded to make the
|
|
37
|
+
AFTER-login gate explicit ("log into Google …, THEN press Enter"); no
|
|
38
|
+
behavior change there.
|
|
39
|
+
- **The wizard's default-frame question loops on a bad answer**: an
|
|
40
|
+
out-of-range or non-numeric entry used to be silently treated as a
|
|
41
|
+
skip — it now re-asks with `invalid choice, pick 1-N or Enter to skip`
|
|
42
|
+
until a valid number is picked or an explicit Enter skips.
|
|
43
|
+
|
|
17
44
|
## [5.1.2] — 2026-09-30
|
|
18
45
|
|
|
19
46
|
### Changed (tests)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pushframe
|
|
3
|
-
Version: 5.1.
|
|
3
|
+
Version: 5.1.3
|
|
4
4
|
Summary: Unofficial community CLI for Aura Frames digital photo frames (sync a local folder or a Google Photos album; not affiliated with Aura Frames Inc.)
|
|
5
5
|
Project-URL: Homepage, https://github.com/coredmp95/pushframe
|
|
6
6
|
Project-URL: Repository, https://github.com/coredmp95/pushframe
|
|
@@ -108,14 +108,34 @@ tokens; safe to repeat; never run it in CI.
|
|
|
108
108
|
|
|
109
109
|
### When the anti-abuse layer refuses your writes
|
|
110
110
|
|
|
111
|
-
Pushd's anti-abuse layer can refuse writes while reads
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
Pushd's anti-abuse layer can refuse writes while reads keep working
|
|
112
|
+
(proven venus, 2026-09). It has two shapes — learn both, the disguised
|
|
113
|
+
one is the trap:
|
|
114
|
+
|
|
115
|
+
| Surface | Shape | Server body |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `login.json` | **HTTP 475** — explicit lockout | "Aura API is rate-limiting or has locked out this account" |
|
|
118
|
+
| everything else — **token included** | **HTTP 401** — the trip in disguise | `{"error": true, "message": "Request Unauthenticated", "logout": true}` |
|
|
119
|
+
|
|
120
|
+
The 401 arrives on a FRESH token, seconds after a successful login: it
|
|
121
|
+
means "we refuse this client's writes (trip)", not "your token is bad".
|
|
122
|
+
The trip is **account-wide** — not scoped to the session, the endpoint
|
|
123
|
+
that provoked it, or the login that just succeeded — and it can spread
|
|
124
|
+
to more surfaces when provoked (venus: writes-only 401 first; after
|
|
125
|
+
probe activity, `login.json` itself went 475).
|
|
126
|
+
|
|
127
|
+
**Never re-login, never retry.** Every call that touches a tripped
|
|
128
|
+
surface re-arms the clock, and a fresh login bypasses nothing — the
|
|
129
|
+
trip is not session-scoped. Venus's counter-evidence: 8 doctor probes
|
|
130
|
+
spread over 7 hours, all 475, each one buying another wait — the probes
|
|
131
|
+
were the reason nothing cleared. pushframe already stops on the FIRST
|
|
132
|
+
trip-shaped refusal (`TripDetectedError` — no retry, no re-login); the
|
|
133
|
+
discipline is yours to keep outside the tool.
|
|
134
|
+
|
|
135
|
+
The drill:
|
|
116
136
|
|
|
117
137
|
1. `pushframe doctor` before any sync (GO = the write surface is open NOW)
|
|
118
|
-
2. Trip detected → wait **60+ min** from the stop, then `pushframe doctor` again
|
|
138
|
+
2. Trip detected → wait **60+ min** from the stop, then `pushframe doctor` again — **once**. A second tripped doctor is the signal to stop doctoring (every probe re-arms the clock) and escalate to the 24-hour protocol below.
|
|
119
139
|
3. If doctor passes but a sync still trips immediately, lower the per-batch
|
|
120
140
|
volume — the trip keys on batch size (a 1-item write may pass where a
|
|
121
141
|
50-item chunk is refused):
|
|
@@ -123,6 +143,26 @@ retry, no re-login — both feed the trip). The drill:
|
|
|
123
143
|
pushframe google-sync Cadre --frame "Cadre de Fabrice" --apply --yes --batch-size 10
|
|
124
144
|
```
|
|
125
145
|
|
|
146
|
+
#### The 24-hour probe protocol (escalated recovery)
|
|
147
|
+
|
|
148
|
+
When 60+ minutes does not clear the trip (venus, 2026-09-30: still
|
|
149
|
+
tripped after a ~5-hour silence), stop probing and switch to days:
|
|
150
|
+
|
|
151
|
+
1. **Total silence.** No pushframe command of any kind against the
|
|
152
|
+
account — and nothing automated either: a timer or a loop probing
|
|
153
|
+
hourly keeps the account tripped indefinitely.
|
|
154
|
+
2. **After 24h+ of silence, ONE probe — a read, never a doctor.**
|
|
155
|
+
`pushframe status` resumes the stored token session (no login call,
|
|
156
|
+
no write). Doctor is the WRONG first probe here: its write attempt
|
|
157
|
+
re-arms the clock even while it tells you the truth.
|
|
158
|
+
3. **Probe green** (reads answer) → ONE `pushframe doctor` as the single
|
|
159
|
+
write probe. GO → run the sync with a lowered batch size (step 3
|
|
160
|
+
above). Probe trips → back to step 1; next probe in another 24h.
|
|
161
|
+
|
|
162
|
+
Never two probes in one 24h bucket. The wait has no measured shape —
|
|
163
|
+
what is measured is only that hours do not cut it, and every premature
|
|
164
|
+
call restarts it.
|
|
165
|
+
|
|
126
166
|
**Client identity matters.** Pushd's anti-abuse layer also fingerprints
|
|
127
167
|
the client itself: a years-stale `Aura/4.7.790` user agent and the
|
|
128
168
|
all-zeros `0000000000000000` device identifier shared by every pushframe
|
|
@@ -196,12 +236,17 @@ the file is `~/.config/pushframe/config.json`, mode `0600`, written atomically.
|
|
|
196
236
|
|
|
197
237
|
### The wizard (`pushframe config`)
|
|
198
238
|
|
|
199
|
-
|
|
200
|
-
login against the real Aura API before writing
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
239
|
+
On a fresh install it asks for your email, then your password (**hidden
|
|
240
|
+
input**), then **verifies the login against the real Aura API before writing
|
|
241
|
+
anything** — a failed login writes nothing. On an install that already has an
|
|
242
|
+
email stored, **pressing Enter keeps the stored email** — the banner says so and
|
|
243
|
+
means it: with a stored session token, a second Enter reuses the token and the
|
|
244
|
+
wizard finishes WITHOUT any API call (the anti-abuse rule: no gratuitous
|
|
245
|
+
login); only typing a NEW email moves to the password question. On success it
|
|
246
|
+
stores the email plus the session `auth_token` (not the password; a stored
|
|
247
|
+
token is silently refreshed at re-login), then offers the optional questions
|
|
248
|
+
(country for the geo guard, budget tuning) with current effective values as
|
|
249
|
+
defaults.
|
|
205
250
|
|
|
206
251
|
Refuses to run when stdin is not a terminal — scheduled jobs have nothing to
|
|
207
252
|
interact with; feed them the environment or the config file instead.
|
|
@@ -769,6 +814,12 @@ affects uploads, hides and deletes alike.
|
|
|
769
814
|
**There is no automatic retry yet.** If an apply reports 401 failures, simply run it again.
|
|
770
815
|
The operation is safe to repeat: uploads dedupe by md5, and hides are idempotent.
|
|
771
816
|
|
|
817
|
+
This covers the *transient* 401s — the no-special-body failures. If the
|
|
818
|
+
body says `"Request Unauthenticated"`, that is the anti-abuse trip, not
|
|
819
|
+
a transient failure: do NOT run it again, do NOT re-login — see [When
|
|
820
|
+
the anti-abuse layer refuses your
|
|
821
|
+
writes](#when-the-anti-abuse-layer-refuses-your-writes).
|
|
822
|
+
|
|
772
823
|
### A frame's asset count disagrees with its listing
|
|
773
824
|
|
|
774
825
|
`inspect` can report e.g. `Assets: 172` while listing `154`. The frame's own counter and the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
# pushframe — unofficial community CLI for Aura Frames (not affiliated).
|
|
2
2
|
# Version single source for the CLI surface (REL-02); pyproject keeps the
|
|
3
3
|
# packaging metadata version and tests/test_version.py enforces lockstep.
|
|
4
|
-
__version__ = "5.1.
|
|
4
|
+
__version__ = "5.1.3"
|
|
@@ -385,6 +385,11 @@ def run_config(wizard_args=None, stdin_isatty: bool | None = None) -> int:
|
|
|
385
385
|
else:
|
|
386
386
|
print('configuring pushframe — credentials are login-tested now, stored after.')
|
|
387
387
|
email = input('Aura email: ').strip()
|
|
388
|
+
if not email and existing.get('email'):
|
|
389
|
+
# The banner promised "Enter keeps it" — keep that promise. With a
|
|
390
|
+
# stored token this path finishes with ZERO API calls (anti-abuse:
|
|
391
|
+
# a gratuitous login is exactly what the trip punishes).
|
|
392
|
+
email = existing['email']
|
|
388
393
|
if not email:
|
|
389
394
|
print('no email given — aborting, nothing written.')
|
|
390
395
|
return 1
|
|
@@ -415,7 +420,12 @@ def run_config(wizard_args=None, stdin_isatty: bool | None = None) -> int:
|
|
|
415
420
|
for i, f in enumerate(frames, 1):
|
|
416
421
|
print(f' {i}. {f["name"]}')
|
|
417
422
|
choice = input('default frame number (Enter to skip): ').strip()
|
|
418
|
-
|
|
423
|
+
# Prompt-contract audit fix: an out-of-range or non-numeric answer
|
|
424
|
+
# used to be silently treated as a skip — re-ask until a valid
|
|
425
|
+
# number or an explicit Enter.
|
|
426
|
+
while choice and not (choice.isdigit() and 1 <= int(choice) <= len(frames)):
|
|
427
|
+
choice = input(f'invalid choice, pick 1-{len(frames)} or Enter to skip: ').strip()
|
|
428
|
+
if choice:
|
|
419
429
|
data['default_frame'] = frames[int(choice) - 1]['name']
|
|
420
430
|
debug = input('enable debug logging by default? [y/N] ').strip().lower()
|
|
421
431
|
if debug in ('y', 'yes'):
|
|
@@ -1073,7 +1083,7 @@ def run_reconcile(frame_arg: str, *, remove: bool = False, yes: bool = False, me
|
|
|
1073
1083
|
count = len(result.stuck)
|
|
1074
1084
|
print(f'IRREVERSIBLE: {count} row(s) will be permanently destroyed '
|
|
1075
1085
|
f'account-wide via hard-delete. This cannot be undone.')
|
|
1076
|
-
answer = input(f'
|
|
1086
|
+
answer = input(f'Verbatim to confirm — type the number of rows to hard-delete ({count}): ')
|
|
1077
1087
|
if answer.strip() != str(count):
|
|
1078
1088
|
print('Aborted.')
|
|
1079
1089
|
return 0
|
|
@@ -1321,7 +1331,7 @@ def run_sync(dir_arg: str, frame_arg: str, apply: bool = False, yes: bool = Fals
|
|
|
1321
1331
|
count = len(plan.to_delete)
|
|
1322
1332
|
print(f'IRREVERSIBLE: {count} photo(s) will be permanently destroyed '
|
|
1323
1333
|
f'account-wide, not just removed from this frame. This cannot be undone.')
|
|
1324
|
-
answer = input(f'
|
|
1334
|
+
answer = input(f'Verbatim to confirm — type the number of photos to hard-delete ({count}): ')
|
|
1325
1335
|
if answer.strip() != str(count):
|
|
1326
1336
|
print('Aborted.')
|
|
1327
1337
|
return 0
|
|
@@ -84,12 +84,25 @@ def _require_profile() -> Path:
|
|
|
84
84
|
return path
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
def _enter_after_login(context, *, input_fn=input):
|
|
88
|
+
"""Manual-mode harvest close: the Enter comes AFTER the login in the
|
|
89
|
+
opened window and only closes the harvest — the cookie check after it
|
|
90
|
+
(not the Enter itself) decides success. Seam for offline tests."""
|
|
91
|
+
input_fn("…log into Google in the opened window, THEN press Enter "
|
|
92
|
+
"here to finish the harvest> ")
|
|
93
|
+
cookies = context.cookies()
|
|
94
|
+
context.close()
|
|
95
|
+
return cookies
|
|
96
|
+
|
|
97
|
+
|
|
87
98
|
def run_bootstrap(*, auto: bool = False) -> dict:
|
|
88
99
|
"""Open the dedicated-profile browser, harvest cookies, save the vault.
|
|
89
100
|
|
|
90
101
|
auto=True polls the context for completed Google auth (SAPISID-family
|
|
91
102
|
cookies) instead of waiting for an Enter in the terminal — for runs
|
|
92
|
-
launched from a non-interactive shell.
|
|
103
|
+
launched from a non-interactive shell. In manual mode the Enter comes
|
|
104
|
+
AFTER the login and only closes the harvest — the cookie check below
|
|
105
|
+
still decides success. Returns an identity summary dict:
|
|
93
106
|
{vault_path, cookie_count, auth_markers} — never cookie values.
|
|
94
107
|
"""
|
|
95
108
|
profile_dir = _require_profile()
|
|
@@ -181,9 +194,7 @@ def run_bootstrap(*, auto: bool = False) -> dict:
|
|
|
181
194
|
cookies = context.cookies()
|
|
182
195
|
context.close()
|
|
183
196
|
else:
|
|
184
|
-
|
|
185
|
-
cookies = context.cookies()
|
|
186
|
-
context.close()
|
|
197
|
+
cookies = _enter_after_login(context, input_fn=input)
|
|
187
198
|
|
|
188
199
|
if not any(c["name"] in ("SID", "SAPISID", "__Secure-1PSID") for c in cookies):
|
|
189
200
|
raise BootstrapError(
|
|
@@ -71,7 +71,18 @@ def _prompt_login(aura, stdin_isatty: bool):
|
|
|
71
71
|
'stored session expired and stdin is not a terminal — re-run '
|
|
72
72
|
'`pushframe config` once interactively (or set PUSHFRAME_EMAIL/'
|
|
73
73
|
'PUSHFRAME_PASSWORD for this run)')
|
|
74
|
+
stored_email = config_store.load().get('email')
|
|
75
|
+
if stored_email:
|
|
76
|
+
print(f"configuring pushframe (current email: {stored_email} — Enter keeps it)")
|
|
74
77
|
email = input('Aura email: ').strip()
|
|
78
|
+
if not email and stored_email:
|
|
79
|
+
# Same contract as the wizard's prompt (fixed in 5.1.2): the banner
|
|
80
|
+
# promised "Enter keeps it" — an empty answer keeps the stored email
|
|
81
|
+
# instead of feeding an empty email to the login.
|
|
82
|
+
email = stored_email
|
|
83
|
+
if not email:
|
|
84
|
+
print('no email given — aborting, nothing stored.')
|
|
85
|
+
return None
|
|
75
86
|
password = getpass.getpass('Aura password (input hidden): ')
|
|
76
87
|
result = _wizard_login(email, password)
|
|
77
88
|
_store_token(result['email'], result['auth_token'], result.get('user_id'))
|
|
@@ -79,6 +90,18 @@ def _prompt_login(aura, stdin_isatty: bool):
|
|
|
79
90
|
return aura
|
|
80
91
|
|
|
81
92
|
|
|
93
|
+
def _prompt_login_or_fail(aura, stdin_isatty):
|
|
94
|
+
"""_prompt_login may abort (empty email on a never-configured host);
|
|
95
|
+
establish_session must surface that as the named no-credentials error
|
|
96
|
+
instead of returning None where an Aura was expected."""
|
|
97
|
+
result = _prompt_login(aura, stdin_isatty)
|
|
98
|
+
if result is None:
|
|
99
|
+
raise NoCredentialsError(
|
|
100
|
+
'login aborted (no email given) — no session stored; '
|
|
101
|
+
're-run the command to retry')
|
|
102
|
+
return result
|
|
103
|
+
|
|
104
|
+
|
|
82
105
|
def establish_session(aura=None, *, stdin_isatty: bool | None = None,
|
|
83
106
|
do_prompt: bool = True):
|
|
84
107
|
"""Return an authenticated Aura. See module docstring for precedence.
|
|
@@ -120,11 +143,11 @@ def establish_session(aura=None, *, stdin_isatty: bool | None = None,
|
|
|
120
143
|
f'terminal — re-run `pushframe config` once '
|
|
121
144
|
f'interactively') from e
|
|
122
145
|
print('stored session expired — one re-login to refresh it:')
|
|
123
|
-
return
|
|
146
|
+
return _prompt_login_or_fail(aura, stdin_isatty)
|
|
124
147
|
|
|
125
148
|
# 3. interactive first-time setup (same ONE prompt)
|
|
126
149
|
if do_prompt and stdin_isatty:
|
|
127
|
-
return
|
|
150
|
+
return _prompt_login_or_fail(aura, stdin_isatty)
|
|
128
151
|
|
|
129
152
|
# 4. nothing available — named, with the remedy
|
|
130
153
|
raise NoCredentialsError(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pushframe"
|
|
3
|
-
version = "5.1.
|
|
3
|
+
version = "5.1.3"
|
|
4
4
|
description = "Unofficial community CLI for Aura Frames digital photo frames (sync a local folder or a Google Photos album; not affiliated with Aura Frames Inc.)"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
@@ -501,6 +501,28 @@ def test_hard_delete_with_yes_skips_the_gate(tmp_path, monkeypatch, capsys):
|
|
|
501
501
|
assert len(calls) == 1
|
|
502
502
|
|
|
503
503
|
|
|
504
|
+
def test_hard_delete_prompt_states_the_verbatim_rule(tmp_path, monkeypatch, capsys):
|
|
505
|
+
"""Prompt-contract audit: the gate's prompt now SAYS the verbatim rule —
|
|
506
|
+
a reflex 'y' was always rejected; the wording finally admits it."""
|
|
507
|
+
monkeypatch.setattr(cli.sys.stdin, 'isatty', lambda: True)
|
|
508
|
+
prompts = []
|
|
509
|
+
|
|
510
|
+
def fake_input(prompt=''):
|
|
511
|
+
prompts.append(prompt)
|
|
512
|
+
return 'y' # the reflex answer
|
|
513
|
+
|
|
514
|
+
monkeypatch.setattr('builtins.input', fake_input)
|
|
515
|
+
|
|
516
|
+
rc, calls = _run_apply(tmp_path, monkeypatch,
|
|
517
|
+
_assets_response_with_settings(_removal_candidate()),
|
|
518
|
+
yes=False, removal_mode='hard_delete')
|
|
519
|
+
|
|
520
|
+
assert rc == 0
|
|
521
|
+
assert 'Verbatim to confirm' in prompts[-1]
|
|
522
|
+
assert 'Aborted.' in capsys.readouterr().out
|
|
523
|
+
assert calls == []
|
|
524
|
+
|
|
525
|
+
|
|
504
526
|
def test_reshow_is_reported_in_plan_and_summary(tmp_path, monkeypatch, capsys):
|
|
505
527
|
"""A photo present locally but hidden on the frame is a re-show, and gets
|
|
506
528
|
its own line rather than being folded into unchanged (D-08)."""
|
|
@@ -97,6 +97,37 @@ def test_google_link_bootstrap_error_exits_nonzero(monkeypatch, capsys):
|
|
|
97
97
|
assert "no auth cookies detected" in capsys.readouterr().out
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
def test_manual_enter_prompt_states_the_login_before_enter_contract(monkeypatch, capsys):
|
|
101
|
+
"""Prompt-contract audit: the manual-mode Enter prompt reads as
|
|
102
|
+
"log in, THEN press Enter" — the Enter closes the harvest, the login
|
|
103
|
+
is a Google-window action BEFORE it, and the cookie check after the
|
|
104
|
+
Enter — not the Enter itself — decides success."""
|
|
105
|
+
from pushframe.google import bootstrap as bs
|
|
106
|
+
|
|
107
|
+
class _FakeContext:
|
|
108
|
+
def cookies(self):
|
|
109
|
+
return [{"name": "SID", "value": "x", "domain": ".google.com", "path": "/"},
|
|
110
|
+
{"name": "SAPISID", "value": "y", "domain": ".google.com", "path": "/"}]
|
|
111
|
+
|
|
112
|
+
def close(self):
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
entered = []
|
|
116
|
+
|
|
117
|
+
def fake_input(prompt=""):
|
|
118
|
+
entered.append(prompt)
|
|
119
|
+
return ""
|
|
120
|
+
|
|
121
|
+
cookies = bs._enter_after_login(_FakeContext(), input_fn=fake_input)
|
|
122
|
+
|
|
123
|
+
assert len(entered) == 1
|
|
124
|
+
prompt = entered[0]
|
|
125
|
+
low = prompt.lower()
|
|
126
|
+
assert "log into google" in low and "then press enter" in low
|
|
127
|
+
# the harvest closed with the typed Enter; cookies came back from it
|
|
128
|
+
assert {c["name"] for c in cookies} == {"SID", "SAPISID"}
|
|
129
|
+
|
|
130
|
+
|
|
100
131
|
def test_google_link_never_prints_cookie_values(monkeypatch, capsys):
|
|
101
132
|
"""Even the fake bootstrap's summary dict is value-free; if a cookie
|
|
102
133
|
VALUE ever leaked into the flow, this grep would catch it."""
|
|
@@ -310,3 +310,83 @@ def test_run_reconcile_remove_yes_end_to_end_removes_stuck_rows(monkeypatch, cap
|
|
|
310
310
|
assert rc == 0
|
|
311
311
|
out = capsys.readouterr().out
|
|
312
312
|
assert 'Removed: 3 succeeded, 0 failed' in out
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def test_run_reconcile_hard_delete_interactive_gate_rejects_everything_but_the_count(monkeypatch, capsys):
|
|
316
|
+
"""Prompt-contract audit: the hard-delete gate demands the VERBATIM row
|
|
317
|
+
count — the prompt says so, the reflex 'y' is rejected, any other text
|
|
318
|
+
too, and NO write path is touched on any wrong answer."""
|
|
319
|
+
monkeypatch.setenv('AURA_EMAIL', 'you@example.invalid')
|
|
320
|
+
monkeypatch.setenv('AURA_PASSWORD', 'super-secret-pw')
|
|
321
|
+
monkeypatch.setattr('sys.stdin.isatty', lambda: True)
|
|
322
|
+
|
|
323
|
+
aura = offline_aura(overrides={ASSETS_PATH: _placeholder_assets_response()})
|
|
324
|
+
|
|
325
|
+
for wrong in ('y', '12', ''): # 3 stuck rows in the fixture; none of these is '3'
|
|
326
|
+
prompts = []
|
|
327
|
+
|
|
328
|
+
def fake_input(prompt='', _p=prompts):
|
|
329
|
+
_p.append(prompt)
|
|
330
|
+
return wrong
|
|
331
|
+
|
|
332
|
+
monkeypatch.setattr('builtins.input', fake_input)
|
|
333
|
+
rc = run_reconcile('Fake', aura=aura, remove=True, yes=False,
|
|
334
|
+
mechanism='hard-delete')
|
|
335
|
+
assert rc == 0
|
|
336
|
+
assert 'Verbatim to confirm' in prompts[-1]
|
|
337
|
+
assert '(3)' in prompts[-1]
|
|
338
|
+
assert 'Aborted.' in capsys.readouterr().out
|
|
339
|
+
assert _write_paths_hit(aura) == []
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def test_run_reconcile_hard_delete_proceeds_on_the_exact_count(monkeypatch, capsys):
|
|
343
|
+
monkeypatch.setenv('AURA_EMAIL', 'you@example.invalid')
|
|
344
|
+
monkeypatch.setenv('AURA_PASSWORD', 'super-secret-pw')
|
|
345
|
+
monkeypatch.setattr('sys.stdin.isatty', lambda: True)
|
|
346
|
+
monkeypatch.setattr(cli_module, '_build_write_budget', lambda email, ignore_budget: None)
|
|
347
|
+
monkeypatch.setattr('builtins.input', lambda *_: '3') # exactly the fixture's stuck count
|
|
348
|
+
# The offline router has no canned DELETE /v5/assets/{id} route — prove
|
|
349
|
+
# the GATE lets the exact count through by observing the primitive's
|
|
350
|
+
# calls instead (the endpoint's own behavior is covered in test_reconcile).
|
|
351
|
+
import pushframe.reconcile as reconcile_mod
|
|
352
|
+
sent = []
|
|
353
|
+
|
|
354
|
+
def _fake_hard_delete(aura_, frame_id, chunk):
|
|
355
|
+
sent.extend(asset.id for asset in chunk)
|
|
356
|
+
return [object()] * len(chunk)
|
|
357
|
+
|
|
358
|
+
monkeypatch.setitem(reconcile_mod._RECONCILE_PRIMITIVE, 'hard-delete',
|
|
359
|
+
_fake_hard_delete)
|
|
360
|
+
|
|
361
|
+
aura = offline_aura(overrides={ASSETS_PATH: _placeholder_assets_response()})
|
|
362
|
+
|
|
363
|
+
rc = run_reconcile('Fake', aura=aura, remove=True, yes=False,
|
|
364
|
+
mechanism='hard-delete')
|
|
365
|
+
|
|
366
|
+
assert rc == 0
|
|
367
|
+
assert len(sent) == 3
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def test_run_reconcile_remove_interactive_y_n_gate(monkeypatch, capsys):
|
|
371
|
+
"""Prompt-contract audit: the default (remove) mechanism's interactive
|
|
372
|
+
gate is a real y/N — the prompt says so, a non-y answer aborts before
|
|
373
|
+
any write."""
|
|
374
|
+
monkeypatch.setenv('AURA_EMAIL', 'you@example.invalid')
|
|
375
|
+
monkeypatch.setenv('AURA_PASSWORD', 'super-secret-pw')
|
|
376
|
+
monkeypatch.setattr('sys.stdin.isatty', lambda: True)
|
|
377
|
+
prompts = []
|
|
378
|
+
|
|
379
|
+
def fake_input(prompt=''):
|
|
380
|
+
prompts.append(prompt)
|
|
381
|
+
return 'n'
|
|
382
|
+
|
|
383
|
+
monkeypatch.setattr('builtins.input', fake_input)
|
|
384
|
+
|
|
385
|
+
aura = offline_aura(overrides={ASSETS_PATH: _placeholder_assets_response()})
|
|
386
|
+
|
|
387
|
+
rc = run_reconcile('Fake', aura=aura, remove=True, yes=False, mechanism='remove')
|
|
388
|
+
|
|
389
|
+
assert rc == 0
|
|
390
|
+
assert 'Proceed? [y/N]' in prompts[-1]
|
|
391
|
+
assert 'Aborted.' in capsys.readouterr().out
|
|
392
|
+
assert _write_paths_hit(aura) == []
|
|
@@ -105,6 +105,83 @@ def test_wizard_provisioning_keeps_sibling_settings(cfg_path, monkeypatch, fake_
|
|
|
105
105
|
assert st["DEVICE_IDENTIFIER"] != "0000000000000000"
|
|
106
106
|
|
|
107
107
|
|
|
108
|
+
def test_wizard_enter_keeps_existing_email(cfg_path, monkeypatch, capsys):
|
|
109
|
+
"""CLI contract (venus, 5.1.2): the banner promises "Enter keeps it"
|
|
110
|
+
and Enter must keep it — an empty answer on a configured install keeps
|
|
111
|
+
the stored email and offers the stored token (Enter again finishes the
|
|
112
|
+
wizard with ZERO API calls). A fresh install (no stored email) still
|
|
113
|
+
aborts named on an empty answer."""
|
|
114
|
+
from pushframe.cli import run_config
|
|
115
|
+
config_store.update(email='coredmp95@gmail.com', auth_token='tok-keep') # noqa: S105
|
|
116
|
+
|
|
117
|
+
_feed(monkeypatch, ['', '']) # Enter email → Enter keep-token → done
|
|
118
|
+
assert run_config(wizard_args=[], stdin_isatty=True) == 0
|
|
119
|
+
data = json.loads(cfg_path.read_text())
|
|
120
|
+
assert data['email'] == 'coredmp95@gmail.com'
|
|
121
|
+
assert data['auth_token'] == 'tok-keep' # noqa: S105
|
|
122
|
+
assert 'config saved' in capsys.readouterr().out
|
|
123
|
+
|
|
124
|
+
# fresh install: no stored email — empty Enter aborts, writes no session
|
|
125
|
+
cfg_path.unlink()
|
|
126
|
+
_feed(monkeypatch, [''])
|
|
127
|
+
assert run_config(wizard_args=[], stdin_isatty=True) == 1
|
|
128
|
+
data = json.loads(cfg_path.read_text())
|
|
129
|
+
assert 'email' not in data and 'auth_token' not in data # noqa: S105
|
|
130
|
+
assert 'no email given' in capsys.readouterr().out
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_wizard_frame_number_enter_skips_prompt_contract(cfg_path, monkeypatch, fake_login_ok, capsys):
|
|
134
|
+
"""Prompt-contract audit: 'default frame number (Enter to skip)' — a
|
|
135
|
+
bare Enter must skip and store NO default_frame; a valid index selects
|
|
136
|
+
the frame."""
|
|
137
|
+
from pushframe.cli import run_config
|
|
138
|
+
|
|
139
|
+
# Enter skips: no default_frame lands in the file
|
|
140
|
+
_feed(monkeypatch, ['me@example.invalid', 's3cret', '', '']) # noqa: S105
|
|
141
|
+
assert run_config(wizard_args=[], stdin_isatty=True) == 0
|
|
142
|
+
data = json.loads(cfg_path.read_text())
|
|
143
|
+
assert 'default_frame' not in data
|
|
144
|
+
|
|
145
|
+
# a valid index selects the frame
|
|
146
|
+
cfg_path.unlink()
|
|
147
|
+
_feed(monkeypatch, ['me@example.invalid', 's3cret', '1', '']) # noqa: S105
|
|
148
|
+
assert run_config(wizard_args=[], stdin_isatty=True) == 0
|
|
149
|
+
assert json.loads(cfg_path.read_text())['default_frame'] == 'Cadre de Fabrice'
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_wizard_frame_number_out_of_range_re_asked_until_valid_or_skip(cfg_path, monkeypatch, fake_login_ok, capsys):
|
|
153
|
+
"""The loop (wart fix): an out-of-range or non-numeric answer is
|
|
154
|
+
RE-ASKED with the named remedy — 'invalid choice, pick 1-N or Enter to
|
|
155
|
+
skip' — until a valid number is picked or an explicit Enter skips."""
|
|
156
|
+
from pushframe.cli import run_config
|
|
157
|
+
|
|
158
|
+
# invalid '9' → re-ask → '1' selected
|
|
159
|
+
prompts = []
|
|
160
|
+
answers = iter(['me@example.invalid', '9', '1', ''])
|
|
161
|
+
|
|
162
|
+
def fake_input(prompt=''):
|
|
163
|
+
prompts.append(prompt)
|
|
164
|
+
return next(answers)
|
|
165
|
+
|
|
166
|
+
monkeypatch.setattr('builtins.input', fake_input)
|
|
167
|
+
monkeypatch.setattr('getpass.getpass', lambda *a: 's3cret') # noqa: S105
|
|
168
|
+
assert run_config(wizard_args=[], stdin_isatty=True) == 0
|
|
169
|
+
data = json.loads(cfg_path.read_text())
|
|
170
|
+
assert data['default_frame'] == 'Cadre de Fabrice'
|
|
171
|
+
assert prompts.count('invalid choice, pick 1-1 or Enter to skip: ') == 1
|
|
172
|
+
assert any(p.endswith('default frame number (Enter to skip): ')
|
|
173
|
+
for p in prompts)
|
|
174
|
+
|
|
175
|
+
# invalid '0' → re-ask → explicit Enter skips (session still saved)
|
|
176
|
+
cfg_path.unlink()
|
|
177
|
+
prompts.clear()
|
|
178
|
+
answers = iter(['me@example.invalid', '0', '', ''])
|
|
179
|
+
assert run_config(wizard_args=[], stdin_isatty=True) == 0
|
|
180
|
+
data = json.loads(cfg_path.read_text())
|
|
181
|
+
assert 'default_frame' not in data and data['auth_token'] # noqa: S105
|
|
182
|
+
assert prompts.count('invalid choice, pick 1-1 or Enter to skip: ') == 1
|
|
183
|
+
|
|
184
|
+
|
|
108
185
|
def test_show_warns_on_all_zeros_device_identity(cfg_path, capsys, monkeypatch):
|
|
109
186
|
"""Identity hygiene (5.1.1): config show names the shared all-zeros
|
|
110
187
|
device id with its remedy; a provisioned or explicit id silences it."""
|
|
@@ -171,3 +171,53 @@ def test_resume_401_propagates_into_the_tty_path():
|
|
|
171
171
|
assert session_mod._is_auth_failure(RuntimeError(
|
|
172
172
|
"Client error '401 Unauthorized' for url '...'"))
|
|
173
173
|
assert not session_mod._is_auth_failure(ValueError("corrupt config"))
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_prompt_login_bare_enter_keeps_stored_email(cfg_path, monkeypatch, capsys):
|
|
177
|
+
"""Prompt-contract audit: the session-path login prompt shows the same
|
|
178
|
+
"current email — Enter keeps it" banner as the wizard, and a bare Enter
|
|
179
|
+
KEEPS it — exactly one login call, none with an empty email."""
|
|
180
|
+
_clear_creds(monkeypatch)
|
|
181
|
+
_store_session(cfg_path, email="vaulted@example.invalid")
|
|
182
|
+
|
|
183
|
+
from pushframe import session as session_mod
|
|
184
|
+
logins = []
|
|
185
|
+
|
|
186
|
+
def _fake_login(e, p):
|
|
187
|
+
logins.append((e, p))
|
|
188
|
+
return {"email": e, "auth_token": "fresh-tok", # noqa: S105
|
|
189
|
+
"user_id": "u-1", "frames": []}
|
|
190
|
+
|
|
191
|
+
monkeypatch.setattr(session_mod, "_wizard_login", _fake_login)
|
|
192
|
+
monkeypatch.setattr("builtins.input", lambda *a: "") # bare Enter
|
|
193
|
+
import getpass as gp
|
|
194
|
+
monkeypatch.setattr(gp, "getpass", lambda *a: "typed-pw") # noqa: S105
|
|
195
|
+
|
|
196
|
+
aura = session_mod._prompt_login(_RecordingAura(), True)
|
|
197
|
+
|
|
198
|
+
out = capsys.readouterr().out
|
|
199
|
+
assert "current email: vaulted@example.invalid" in out
|
|
200
|
+
assert "Enter keeps it" in out
|
|
201
|
+
assert logins == [("vaulted@example.invalid", "typed-pw")] # noqa: S105
|
|
202
|
+
assert aura is not None
|
|
203
|
+
assert json.loads(cfg_path.read_text())["auth_token"] == "fresh-tok" # noqa: S105
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def test_prompt_login_empty_email_on_fresh_install_aborts_named(cfg_path, monkeypatch, capsys):
|
|
207
|
+
"""The other branch of the same contract: with NO stored email, a bare
|
|
208
|
+
Enter aborts named BEFORE the password question and before any login —
|
|
209
|
+
establish_session surfaces it as the named no-credentials error."""
|
|
210
|
+
_clear_creds(monkeypatch)
|
|
211
|
+
|
|
212
|
+
from pushframe import session as session_mod
|
|
213
|
+
from pushframe.session import NoCredentialsError
|
|
214
|
+
|
|
215
|
+
monkeypatch.setattr(
|
|
216
|
+
session_mod, "_wizard_login",
|
|
217
|
+
lambda *a, **k: pytest.fail("no login call with an empty email"))
|
|
218
|
+
monkeypatch.setattr("builtins.input", lambda *a: "")
|
|
219
|
+
|
|
220
|
+
with pytest.raises(NoCredentialsError) as exc:
|
|
221
|
+
session_mod._prompt_login_or_fail(_RecordingAura(), True)
|
|
222
|
+
assert "no email given" in capsys.readouterr().out
|
|
223
|
+
assert "login aborted" in str(exc.value)
|
|
@@ -19,7 +19,7 @@ PYPROJECT = Path(__file__).resolve().parent.parent / "pyproject.toml"
|
|
|
19
19
|
def test_version_is_first_distribution_release():
|
|
20
20
|
"""The current distribution release (moved off 0.1.0 at 5.0.0; page
|
|
21
21
|
metadata fix shipped in 5.0.4)."""
|
|
22
|
-
assert __version__ == "5.1.
|
|
22
|
+
assert __version__ == "5.1.3"
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def test_version_matches_pyproject_single_source():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|