pushframe 5.1.2__tar.gz → 5.1.4__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.4/.planning/debug/status-crash-401-trip.md +61 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/CHANGELOG.md +59 -1
- {pushframe-5.1.2 → pushframe-5.1.4}/PKG-INFO +1 -1
- {pushframe-5.1.2 → pushframe-5.1.4}/docs/CLI.md +72 -16
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/__init__.py +1 -1
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/cli.py +52 -6
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/bootstrap.py +15 -4
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/gsync.py +6 -1
- pushframe-5.1.4/pushframe/session.py +306 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pyproject.toml +1 -1
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_apply.py +22 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_google_link.py +31 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_reconcile.py +80 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_status.py +128 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_config_wizard.py +77 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_session.py +50 -0
- pushframe-5.1.4/tests/test_token_refresh_extension.py +157 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_version.py +1 -1
- {pushframe-5.1.2 → pushframe-5.1.4}/uv.lock +1 -1
- pushframe-5.1.2/pushframe/session.py +0 -144
- {pushframe-5.1.2 → pushframe-5.1.4}/.env.sample +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.freebuff/project-id +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.github/workflows/release.yml +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.github/workflows/tests.yml +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.gitignore +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.gsd/dispatch-isolation-sentinel.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/MILESTONES.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/PROJECT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/REQUIREMENTS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/RETROSPECTIVE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/ROADMAP.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/STATE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/WINDOWS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/codebase/ARCHITECTURE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/codebase/CONCERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/codebase/CONVENTIONS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/codebase/INTEGRATIONS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/codebase/STACK.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/codebase/STRUCTURE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/codebase/TESTING.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/config.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/debug/knowledge-base.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/debug/login-475-null-creds.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/debug/resolved/cli-status-verbose-logging.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/debug/resolved/inspect-asset-null-fields.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/debug/resolved/select-asset-401-unauthorized.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-REQUIREMENTS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-ROADMAP.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-LIVE-EVIDENCE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-ROADMAP.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/.gitkeep +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-SECURITY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-UAT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-REQUIREMENTS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-ROADMAP.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-SECURITY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-UAT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-SECURITY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.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.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.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.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-SECURITY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-LIVE-FINDINGS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW-FIX.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/.gitkeep +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.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.4}/.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.4}/.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.4}/.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.4}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.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.4}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/.gitkeep +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.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.4}/.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.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-SECURITY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-UAT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/COVERAGE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.planning/milestones/v2.0-quick/README.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-REQUIREMENTS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-ROADMAP.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-LIVE-FINDINGS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-PATTERNS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-REVIEW.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-REQUIREMENTS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-ROADMAP.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DECISION-RECORD.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DISCUSSION-LOG.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-LIVE-FINDINGS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/COVERAGE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-DISCUSS-CHECKPOINT.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/COVERAGE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-UAT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/19-debt-closeout/19-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/19-debt-closeout/19-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/19-debt-closeout/19-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-REQUIREMENTS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-ROADMAP.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/20-pushframe-rename-migration-repo-switch/20-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/OPERATOR-STEPS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/23-config-wizard-precedence/23-01-PLAN.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/23-config-wizard-precedence/23-01-SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/23-config-wizard-precedence/23-CONTEXT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/23-config-wizard-precedence/23-RESEARCH.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/23-config-wizard-precedence/23-VERIFICATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.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.4}/.planning/research/ALBUM-ACCESS-V4-ADDENDUM.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/research/ALBUM-ACCESS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/research/ARCHITECTURE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/research/BROWSER-AUTOMATION.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/research/FEATURES.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/research/PITFALLS.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/research/STACK.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/research/SUMMARY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/state.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.planning/todos/completed/2026-07-06-promote-debug-flag-to-a-global-cli-convention.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/.python-version +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/CLAUDE.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/LICENSE +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/README.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/SECURITY.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/VERIFICATION-REPORT.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/docs/agents/domain.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/docs/agents/issue-tracker.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/docs/agents/triage-labels.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/docs/superpowers/specs/2026-07-09-write-rate-limiter-design.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/main.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/packaging/control-template +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/packaging/lintian-overrides +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/packaging/postinst +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/packaging/pushframe.copyright +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/probes/.gitignore +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/probes/README.md +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/probes/browser_bootstrap.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/probes/common.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/probes/cookie_vault.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/probes/fidelity_check.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/probes/rpc_capture.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/probes/shared_link_probe.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/__init__.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/accountApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/activityApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/assetApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/baseApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/frameApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/notificationApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/peopleApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/playlistApi.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/aura.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/aws/__init__.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/aws/awsclient.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/aws/s3client.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/aws/sqsclient.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/cache.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/client.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/config_store.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/doctor.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/exif.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/export.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/__init__.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/cache.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/client.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/enumerate.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/manifest.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/parsers.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/redaction.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/vault.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/migration.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/models/__init__.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/models/activity.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/models/asset.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/models/frame.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/models/meta.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/models/person.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/models/user.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/pairs.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/preflight.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/ratelimit.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/reconcile.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/schedule.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/sync.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/utils/dt.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/utils/io.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/utils/settings.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/scripts/build-deb.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/scripts/make-apt-key.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/scripts/publish-apt-repo.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/scripts/test-apt-journey-container.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/scripts/test-config-journey-container.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/scripts/test-deb-container.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/scripts/test-install-journey-container.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/scripts/test-multipair-journey-container.sh +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/conftest.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/asset_unprocessed.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/assets_page1.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/assets_page2.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/assets_placeholders.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/error_envelope.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/frame_detail.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/frames.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/google_rpc_snackc_page.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/google_share_page_sample.html +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/login.json +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/probe_af_initdata_sample.html +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/offline.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_asset_partial.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_asset_unprocessed.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_aura_logger.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_batch_update_contract.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_google_album.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_inspect.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_push_budget_geo.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_status_google.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_sync.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_client_rate_limit.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_doctor.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_execute_plan.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_execute_plan_budget_geo.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_fixtures_validity.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_google_cache.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_google_client.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_google_enumerate.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_google_manifest.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_google_parsers.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_google_vault.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_gsync_all.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_gsync_engine.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_gsync_execute.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_imports.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_logout.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_migration.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_network_guard.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_offline_read_path.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_pairs.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_preflight_sweep.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_prep_upload_formats.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_probe_browser.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_probe_shared_link.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_ratelimit.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_read_path.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_reconcile.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_retry_401.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_schedule.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_settings_budget.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_settings_resolution.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_sync_engine.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_write_endpoints_failloud.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_write_formats_live.py +0 -0
- {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_write_throttling.py +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
slug: status-crash-401-trip
|
|
3
|
+
status: resolved
|
|
4
|
+
created: 2026-09-30T16:20:00+02:00
|
|
5
|
+
updated: 2026-09-30T16:45:00+02:00
|
|
6
|
+
trigger: pushframe status crashes with a raw httpx traceback when the account's anti-abuse trip is armed (401 logout:true on frames.json)
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Debug: status crashes with raw traceback on tripped-account 401
|
|
10
|
+
|
|
11
|
+
## Symptoms
|
|
12
|
+
|
|
13
|
+
- expected: `pushframe status` resumes the stored token session and either lists frames or fails NAMED with a remedy (PRF-02 contract: never a traceback).
|
|
14
|
+
- actual: uncaught `httpx.HTTPStatusError` — a 3-exception chain ending in a raw traceback.
|
|
15
|
+
- error: `401 Unauthorized for https://api.pushd.com/v5/frames.json — server body: {"error": true, "message": "Request Unauthenticated", "logout": true}`
|
|
16
|
+
- timeline: visible since the venus trip (2026-09-30); the client DOES log the body via loguru (`_raise_for_status_with_body:270`) but nothing classifies or catches it on this path.
|
|
17
|
+
- reproduction: `pushframe status` on an account whose trip is armed (venus, live 2026-09-30 16:15).
|
|
18
|
+
|
|
19
|
+
## Scope decisions (user)
|
|
20
|
+
|
|
21
|
+
- Fix the status UX crash ONLY — the trip itself is not a pushframe bug (the 24h protocol is documented).
|
|
22
|
+
- Contract: classify the 401 body — trip-shaped (`logout:true` / "Request Unauthenticated") → anti-abuse verdict + wait remedy, exit non-zero; token-shaped → `pushframe config` remedy.
|
|
23
|
+
|
|
24
|
+
## Current Focus
|
|
25
|
+
|
|
26
|
+
hypothesis: run_status has no handler around the frames read; the 401 trip classification lives only in the WRITE path (execute_plan) and doctor, so a READ-surface 401 escapes as a raw exception.
|
|
27
|
+
test: read run_status' try/except and the write-path classifier; confirm no read-path catch.
|
|
28
|
+
expecting: no except clause covering HTTPStatusError from get_frames in run_status.
|
|
29
|
+
next_action: gather initial evidence (run_status, classifier, PRF-02 sweep inventory).
|
|
30
|
+
|
|
31
|
+
## Evidence
|
|
32
|
+
|
|
33
|
+
- 2026-09-30T16:20:00+02:00: user transcript (venus 16:15) — establish_session SUCCEEDED (resume is offline: headers only), the 401 came from frame_api.get_frames; loguru warning from client.py:270 printed the body, then the exception propagated to the REPL.
|
|
34
|
+
- 2026-09-30T16:35:00+02:00: DECISIVE user experiment — with PUSHFRAME_EMAIL/PASSWORD env set (fresh login each run), `pushframe status` SUCCEEDS: login OK, frames listed (Cadre de Fabrice). Same account, same minutes: token path 401, env path 200.
|
|
35
|
+
- 2026-09-30T16:35:00+02:00: the 14:30 login-475 has LIFTED by ~16:35 (~2h) — the user's env login succeeded with no 475. Lockouts on the login surface clear in hours, not 24h.
|
|
36
|
+
|
|
37
|
+
## Eliminated
|
|
38
|
+
|
|
39
|
+
- hypothesis: the anti-abuse trip spread to READS (refuted 16:35) — a fresh login reads frames.json fine; the 401-on-reads with the trip-shaped body was actually a DEAD STORED TOKEN. The 14:30 wave-4 interpretation ("reads refused too") is void: that probe resumed a stale token and never reached a write.
|
|
40
|
+
- hypothesis: account permanently 475-locked (refuted 16:35) — login surface answers again after ~2h.
|
|
41
|
+
|
|
42
|
+
## Current Focus (updated 16:35)
|
|
43
|
+
|
|
44
|
+
hypothesis: CONFIRMED for the crash — run_status has no handler around get_frames(); the 401 trip classification lives only in the write path (sync._is_write_trip_body) and doctor. On the READ surface, the logout:true body means STALE TOKEN first (env-login reads green); trip-on-reads only if it recurs on a fresh token.
|
|
45
|
+
test: wrap the frames read; classify trip-shaped body (remedy: refresh token via config; caveat: if it recurs on a fresh login, it is the trip — wait); RateLimitError → wait message; generic → named + config remedy.
|
|
46
|
+
expecting: status exits 1 named, never tracebacks, on all three shapes.
|
|
47
|
+
next_action: implement + tests, then suite.
|
|
48
|
+
|
|
49
|
+
## Resolution
|
|
50
|
+
|
|
51
|
+
root_cause: run_status had no handler around the frames read — the 401 trip classification lived only in the write path (sync._is_write_trip_body) and doctor, so a read-surface 401 escaped as a raw 3-exception-chain traceback (PRF-02 violation on the one read command every operator runs first). Root cause of the USER'S alarm was different: the stored token was dead (venus resumptions), while the env path's fresh login read frames fine — proving the trip never reached reads and the 14:30 login-475 had lifted in ~2h.
|
|
52
|
+
fix: run_status wraps get_frames() — trip-shaped 401 body → named message with the refresh-token remedy AND the fresh-token-24h caveat; other 401 → token-rejected remedy; RateLimitError (475/429) → named WAIT (never `pushframe config`); generic → named; all exit 1, zero tracebacks. Tests: 3 new in test_cli_status.py (trip body, token body, 475) — suite 507 passed.
|
|
53
|
+
verification: tests/test_cli_status.py 10/10; full offline suite 507 passed; user's live repro shape covered by the trip-body test.
|
|
54
|
+
files_changed: pushframe/cli.py, tests/test_cli_status.py, CHANGELOG.md, .planning/debug/status-crash-401-trip.md
|
|
55
|
+
addendum (16:50): followed by the one-shot TTY refresh — run_status now offers ONE re-login on a refused stored token and continues in the same run; a refused FRESH login prints the 24h verdict and is never retried; non-TTY fails named. tests/test_cli_status.py 12/12, suite 509 passed. Updated contract test: fresh env creds + trip body = trip-on-reads verdict.
|
|
56
|
+
|
|
57
|
+
## Lessons
|
|
58
|
+
|
|
59
|
+
- The wave-4 interpretation "reads refused too" is VOID: that probe resumed a stale token and never reached a write. Trip-on-reads remains UNPROVEN; the 24h protocol now has a concrete re-entry criterion (same body on a FRESH login).
|
|
60
|
+
- The env path logs in fresh EVERY run — during tripped/token-dead periods it is the honest comparison path, but it also means each env `status` costs a login call (do not loop it).
|
|
61
|
+
- Silence clock resets: last pushd contact moved to 16:15–16:3x (status 401 + the user's env login). Next single probe: tomorrow ≥ 16:30, `pushframe status` on the refreshed token.
|
|
@@ -12,7 +12,65 @@ every channel.
|
|
|
12
12
|
`pushframe` is an unofficial community CLI for Aura Frames digital photo
|
|
13
13
|
frames; it is not affiliated with Aura Frames Inc.
|
|
14
14
|
|
|
15
|
-
## [
|
|
15
|
+
## [5.1.4] - 2026-09-30
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **One-shot TTY token refresh on every frames-reading verb** — `status`,
|
|
20
|
+
`inspect`, `sync`, `push` and `google-sync` (venus, 2026-09-30): when
|
|
21
|
+
the stored token is refused (401) and stdin is a terminal, the command
|
|
22
|
+
offers ONE re-login right there — the new token is persisted and the
|
|
23
|
+
same run continues (venus proved a fresh login reads green while the
|
|
24
|
+
stored token was dead). All five verbs share one gate
|
|
25
|
+
(`session.frames_read_with_refresh`), so the behavior, the failure
|
|
26
|
+
shapes and the guardrails are byte-identical everywhere. Guardrails:
|
|
27
|
+
exactly one attempt per process — a refused FRESH login is never
|
|
28
|
+
retried and prints the 24-hour-silence verdict (that means the
|
|
29
|
+
anti-abuse trip reached the read surface); non-TTY/scheduled runs
|
|
30
|
+
never prompt, they fail named with the `pushframe config` remedy; the
|
|
31
|
+
env-credential path (fresh credentials every run) never prompts.
|
|
32
|
+
One test per verb proves the contract: 401 once → exactly one
|
|
33
|
+
wizard-seam login → re-read succeeds → token persisted.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- **`pushframe status` no longer tracebacks on a refused frames read**
|
|
38
|
+
(venus, debug session status-crash-401-trip): a 401 with the trip's
|
|
39
|
+
`logout:true` body prints a named diagnosis — the remedy is
|
|
40
|
+
`pushframe config` (refresh the stored token; a fresh login read frames
|
|
41
|
+
fine minutes later, so on a READ that body means a dead token first),
|
|
42
|
+
with the 24h-silence protocol reserved for the case where the same
|
|
43
|
+
body recurs on a genuinely fresh login. A 475/429 on the read prints a
|
|
44
|
+
named WAIT (never a re-login suggestion). Every shape exits 1 named —
|
|
45
|
+
the PRF-02 no-traceback contract now holds on the token path's first
|
|
46
|
+
authenticated call.
|
|
47
|
+
|
|
48
|
+
## [5.1.3] — 2026-09-30
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- **`pushframe config` — Enter now keeps the stored email, as the banner
|
|
53
|
+
always promised** (venus, 5.1.2): an empty answer on a configured
|
|
54
|
+
install aborted with "no email given — aborting" instead of keeping
|
|
55
|
+
it. With a stored token, Enter-Enter now finishes the wizard with ZERO
|
|
56
|
+
API calls (the anti-abuse rule: no gratuitous login); typing a new
|
|
57
|
+
email still re-logins, and a fresh install still aborts named on an
|
|
58
|
+
empty answer.
|
|
59
|
+
- **Prompt-contract audit — every interactive prompt checked against its
|
|
60
|
+
banner**, one behavior test per prompt. Two more promised-vs-real gaps
|
|
61
|
+
fixed alongside the wizard's: the session-path login prompt (expired
|
|
62
|
+
token refresh) showed NO banner and aborted the login on a bare Enter —
|
|
63
|
+
it now prints the same "current email — Enter keeps it" banner and
|
|
64
|
+
keeps it; and the two hard-delete confirmation gates only said "To
|
|
65
|
+
confirm, type the number…" — now "Verbatim to confirm —", because the
|
|
66
|
+
y/N reflex answer being silently rejected WAS the contract, just never
|
|
67
|
+
the wording. google-link's manual Enter prompt reworded to make the
|
|
68
|
+
AFTER-login gate explicit ("log into Google …, THEN press Enter"); no
|
|
69
|
+
behavior change there.
|
|
70
|
+
- **The wizard's default-frame question loops on a bad answer**: an
|
|
71
|
+
out-of-range or non-numeric entry used to be silently treated as a
|
|
72
|
+
skip — it now re-asks with `invalid choice, pick 1-N or Enter to skip`
|
|
73
|
+
until a valid number is picked or an explicit Enter skips.
|
|
16
74
|
|
|
17
75
|
## [5.1.2] — 2026-09-30
|
|
18
76
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pushframe
|
|
3
|
-
Version: 5.1.
|
|
3
|
+
Version: 5.1.4
|
|
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.
|
|
@@ -237,10 +282,15 @@ it is the value a command would actually use).
|
|
|
237
282
|
|
|
238
283
|
```
|
|
239
284
|
usage: pushframe status [-h]
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
285
|
+
```The first command to run. With env credentials it logs in fresh; with a
|
|
286
|
+
stored token session it resumes it (no login call) and lists your frames.
|
|
287
|
+
Nothing is ever written except a session token: if the stored token is
|
|
288
|
+
refused (HTTP 401), an interactive run offers ONE re-login right there and
|
|
289
|
+
continues — a refused fresh login is never retried (that shape is the
|
|
290
|
+
anti-abuse trip on reads: 24-hour silence); a non-interactive run fails
|
|
291
|
+
named with the `pushframe config` remedy instead of prompting. `inspect`,
|
|
292
|
+
`sync`, `push` and `google-sync` read frames through the same gate, so
|
|
293
|
+
this contract holds on every frames-reading verb.
|
|
244
294
|
|
|
245
295
|
```bash
|
|
246
296
|
uv run pushframe status
|
|
@@ -769,6 +819,12 @@ affects uploads, hides and deletes alike.
|
|
|
769
819
|
**There is no automatic retry yet.** If an apply reports 401 failures, simply run it again.
|
|
770
820
|
The operation is safe to repeat: uploads dedupe by md5, and hides are idempotent.
|
|
771
821
|
|
|
822
|
+
This covers the *transient* 401s — the no-special-body failures. If the
|
|
823
|
+
body says `"Request Unauthenticated"`, that is the anti-abuse trip, not
|
|
824
|
+
a transient failure: do NOT run it again, do NOT re-login — see [When
|
|
825
|
+
the anti-abuse layer refuses your
|
|
826
|
+
writes](#when-the-anti-abuse-layer-refuses-your-writes).
|
|
827
|
+
|
|
772
828
|
### A frame's asset count disagrees with its listing
|
|
773
829
|
|
|
774
830
|
`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.4"
|
|
@@ -12,6 +12,8 @@ from tqdm import tqdm
|
|
|
12
12
|
from pushframe.aura import Aura
|
|
13
13
|
from pushframe.aws.s3client import S3Client
|
|
14
14
|
from pushframe.aws.sqsclient import SQSClient
|
|
15
|
+
import httpx
|
|
16
|
+
|
|
15
17
|
from pushframe.client import RateLimitError
|
|
16
18
|
from pushframe.google.bootstrap import BootstrapError, PROFILE_ENV_VAR, default_bootstrap
|
|
17
19
|
from pushframe.google.client import GoogleSession
|
|
@@ -385,6 +387,11 @@ def run_config(wizard_args=None, stdin_isatty: bool | None = None) -> int:
|
|
|
385
387
|
else:
|
|
386
388
|
print('configuring pushframe — credentials are login-tested now, stored after.')
|
|
387
389
|
email = input('Aura email: ').strip()
|
|
390
|
+
if not email and existing.get('email'):
|
|
391
|
+
# The banner promised "Enter keeps it" — keep that promise. With a
|
|
392
|
+
# stored token this path finishes with ZERO API calls (anti-abuse:
|
|
393
|
+
# a gratuitous login is exactly what the trip punishes).
|
|
394
|
+
email = existing['email']
|
|
388
395
|
if not email:
|
|
389
396
|
print('no email given — aborting, nothing written.')
|
|
390
397
|
return 1
|
|
@@ -415,7 +422,12 @@ def run_config(wizard_args=None, stdin_isatty: bool | None = None) -> int:
|
|
|
415
422
|
for i, f in enumerate(frames, 1):
|
|
416
423
|
print(f' {i}. {f["name"]}')
|
|
417
424
|
choice = input('default frame number (Enter to skip): ').strip()
|
|
418
|
-
|
|
425
|
+
# Prompt-contract audit fix: an out-of-range or non-numeric answer
|
|
426
|
+
# used to be silently treated as a skip — re-ask until a valid
|
|
427
|
+
# number or an explicit Enter.
|
|
428
|
+
while choice and not (choice.isdigit() and 1 <= int(choice) <= len(frames)):
|
|
429
|
+
choice = input(f'invalid choice, pick 1-{len(frames)} or Enter to skip: ').strip()
|
|
430
|
+
if choice:
|
|
419
431
|
data['default_frame'] = frames[int(choice) - 1]['name']
|
|
420
432
|
debug = input('enable debug logging by default? [y/N] ').strip().lower()
|
|
421
433
|
if debug in ('y', 'yes'):
|
|
@@ -599,6 +611,7 @@ def run_status(aura=None, debug: bool = False, google_session=None) -> int:
|
|
|
599
611
|
# and before login/get_frames (the HTTP calls that trigger them).
|
|
600
612
|
_configure_cli_logging(debug)
|
|
601
613
|
|
|
614
|
+
stored = None # the token-refresh seam only exists on the stored path
|
|
602
615
|
if session[0] == 'env':
|
|
603
616
|
try:
|
|
604
617
|
aura.login()
|
|
@@ -615,7 +628,18 @@ def run_status(aura=None, debug: bool = False, google_session=None) -> int:
|
|
|
615
628
|
user_id=stored.get('user_id'))
|
|
616
629
|
who = stored['email']
|
|
617
630
|
|
|
618
|
-
frames
|
|
631
|
+
# The frames read is the FIRST authenticated call of the token path —
|
|
632
|
+
# its failure modes are operator-relevant and must never traceback
|
|
633
|
+
# (PRF-02). Key lesson of 2026-09-30 (debug/status-crash-401-trip): on
|
|
634
|
+
# a READ, the trip-shaped logout:true body means a STALE STORED TOKEN
|
|
635
|
+
# first — a fresh login read frames fine minutes later — so the remedy
|
|
636
|
+
# is ONE TTY re-login right here (venus: the env path proved a fresh
|
|
637
|
+
# login reads green); the 24h wait is only the answer if a FRESH token
|
|
638
|
+
# is refused too. Never more than ONE refresh attempt per run, never a
|
|
639
|
+
# prompt off-TTY (scheduled runs fail named instead).
|
|
640
|
+
frames = _read_frames_with_refresh(aura, who, stored)
|
|
641
|
+
if frames is None:
|
|
642
|
+
return 1 # named failure already printed by the helper
|
|
619
643
|
print(f'Logged in as {who}')
|
|
620
644
|
print(f'{len(frames)} frames:')
|
|
621
645
|
for frame in frames:
|
|
@@ -629,6 +653,14 @@ def run_status(aura=None, debug: bool = False, google_session=None) -> int:
|
|
|
629
653
|
return 0
|
|
630
654
|
|
|
631
655
|
|
|
656
|
+
def _read_frames_with_refresh(aura, who, stored):
|
|
657
|
+
"""Thin wrapper over the shared gate (session.frames_read_with_refresh)
|
|
658
|
+
— status, inspect, sync/push and google-sync all read frames through
|
|
659
|
+
the same named-failure + one-shot-refresh contract."""
|
|
660
|
+
from pushframe.session import frames_read_with_refresh
|
|
661
|
+
return frames_read_with_refresh(aura, who, stored)
|
|
662
|
+
|
|
663
|
+
|
|
632
664
|
def _google_status_section(google_session=None) -> list[str]:
|
|
633
665
|
"""Build the `Google:` section lines for `status` (LGS-03/D-02).
|
|
634
666
|
|
|
@@ -931,8 +963,15 @@ def run_inspect(frame_arg: str, aura=None, debug: bool = False) -> int:
|
|
|
931
963
|
print(f'Login failed: {e}')
|
|
932
964
|
return 1
|
|
933
965
|
|
|
966
|
+
from pushframe.session import auto_refresh_stored
|
|
967
|
+
stored = auto_refresh_stored()
|
|
968
|
+
frames = _read_frames_with_refresh(
|
|
969
|
+
aura, who=(stored or {}).get('email')
|
|
970
|
+
or os.getenv('PUSHFRAME_EMAIL') or os.getenv('AURA_EMAIL'),
|
|
971
|
+
stored=stored)
|
|
972
|
+
if frames is None:
|
|
973
|
+
return 1
|
|
934
974
|
try:
|
|
935
|
-
frames = aura.frame_api.get_frames()
|
|
936
975
|
resolved = resolve_frame(frame_arg, frames)
|
|
937
976
|
|
|
938
977
|
if resolved.status == 'ambiguous':
|
|
@@ -1073,7 +1112,7 @@ def run_reconcile(frame_arg: str, *, remove: bool = False, yes: bool = False, me
|
|
|
1073
1112
|
count = len(result.stuck)
|
|
1074
1113
|
print(f'IRREVERSIBLE: {count} row(s) will be permanently destroyed '
|
|
1075
1114
|
f'account-wide via hard-delete. This cannot be undone.')
|
|
1076
|
-
answer = input(f'
|
|
1115
|
+
answer = input(f'Verbatim to confirm — type the number of rows to hard-delete ({count}): ')
|
|
1077
1116
|
if answer.strip() != str(count):
|
|
1078
1117
|
print('Aborted.')
|
|
1079
1118
|
return 0
|
|
@@ -1215,8 +1254,15 @@ def run_sync(dir_arg: str, frame_arg: str, apply: bool = False, yes: bool = Fals
|
|
|
1215
1254
|
print(f'Login failed: {e}')
|
|
1216
1255
|
return 1
|
|
1217
1256
|
|
|
1257
|
+
from pushframe.session import auto_refresh_stored
|
|
1258
|
+
stored = auto_refresh_stored()
|
|
1259
|
+
frames = _read_frames_with_refresh(
|
|
1260
|
+
aura, who=(stored or {}).get('email')
|
|
1261
|
+
or os.getenv('PUSHFRAME_EMAIL') or os.getenv('AURA_EMAIL'),
|
|
1262
|
+
stored=stored)
|
|
1263
|
+
if frames is None:
|
|
1264
|
+
return 1
|
|
1218
1265
|
try:
|
|
1219
|
-
frames = aura.frame_api.get_frames()
|
|
1220
1266
|
resolved = resolve_frame(frame_arg, frames)
|
|
1221
1267
|
|
|
1222
1268
|
if resolved.status == 'ambiguous':
|
|
@@ -1321,7 +1367,7 @@ def run_sync(dir_arg: str, frame_arg: str, apply: bool = False, yes: bool = Fals
|
|
|
1321
1367
|
count = len(plan.to_delete)
|
|
1322
1368
|
print(f'IRREVERSIBLE: {count} photo(s) will be permanently destroyed '
|
|
1323
1369
|
f'account-wide, not just removed from this frame. This cannot be undone.')
|
|
1324
|
-
answer = input(f'
|
|
1370
|
+
answer = input(f'Verbatim to confirm — type the number of photos to hard-delete ({count}): ')
|
|
1325
1371
|
if answer.strip() != str(count):
|
|
1326
1372
|
print('Aborted.')
|
|
1327
1373
|
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(
|
|
@@ -382,7 +382,12 @@ def run_google_sync(album_target: str, frame_arg: str, *, apply: bool = False,
|
|
|
382
382
|
print(f'Login failed: {e}')
|
|
383
383
|
return 1
|
|
384
384
|
|
|
385
|
-
|
|
385
|
+
from pushframe.session import auto_refresh_stored, frames_read_with_refresh
|
|
386
|
+
_stored = auto_refresh_stored()
|
|
387
|
+
frames = frames_read_with_refresh(
|
|
388
|
+
aura, who=(_stored or {}).get('email'), stored=_stored)
|
|
389
|
+
if frames is None:
|
|
390
|
+
return 2
|
|
386
391
|
frame_res = resolve_frame(frame_arg, frames)
|
|
387
392
|
if frame_res.status == 'ambiguous':
|
|
388
393
|
print(f"'{frame_arg}' matches more than one frame:")
|