pushframe 5.0.0__tar.gz → 5.0.2__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.0.0 → pushframe-5.0.2}/.github/workflows/release.yml +9 -3
- pushframe-5.0.2/PKG-INFO +629 -0
- pushframe-5.0.2/SECURITY.md +100 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/__init__.py +1 -1
- {pushframe-5.0.0 → pushframe-5.0.2}/pyproject.toml +8 -1
- {pushframe-5.0.0 → pushframe-5.0.2}/scripts/publish-apt-repo.sh +11 -1
- pushframe-5.0.2/scripts/test-install-journey-container.sh +65 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_version.py +3 -2
- {pushframe-5.0.0 → pushframe-5.0.2}/uv.lock +53 -36
- pushframe-5.0.0/PKG-INFO +0 -53
- pushframe-5.0.0/scripts/test-install-journey-container.sh +0 -62
- {pushframe-5.0.0 → pushframe-5.0.2}/.env.sample +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.freebuff/project-id +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.github/workflows/tests.yml +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.gitignore +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.gsd/dispatch-isolation-sentinel.json +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/MILESTONES.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/PROJECT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/REQUIREMENTS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/RETROSPECTIVE.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/ROADMAP.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/STATE.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/WINDOWS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/codebase/ARCHITECTURE.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/codebase/CONCERNS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/codebase/CONVENTIONS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/codebase/INTEGRATIONS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/codebase/STACK.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/codebase/STRUCTURE.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/codebase/TESTING.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/config.json +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/debug/knowledge-base.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/debug/login-475-null-creds.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/debug/resolved/cli-status-verbose-logging.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/debug/resolved/inspect-asset-null-fields.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/debug/resolved/select-asset-401-unauthorized.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-REQUIREMENTS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-ROADMAP.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-PATTERNS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-RESEARCH.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-REVIEW.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-LIVE-EVIDENCE.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-PATTERNS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-RESEARCH.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-REVIEW.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-PATTERNS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-RESEARCH.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-REVIEW.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-ROADMAP.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/.gitkeep +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-03-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-03-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-PATTERNS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-RESEARCH.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-REVIEW.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-SECURITY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-UAT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-REQUIREMENTS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-ROADMAP.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-PATTERNS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-REVIEW.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-SECURITY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-UAT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-PATTERNS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-RESEARCH.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-REVIEW.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-SECURITY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-03-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-03-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-PATTERNS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-REVIEW-FIX.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-REVIEW.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-SECURITY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-LIVE-FINDINGS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-PATTERNS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-RESEARCH.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW-FIX.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/.gitkeep +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-RESEARCH.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-REVIEW-FIX.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-REVIEW.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/.gitkeep +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-03-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-03-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-04-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-04-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-LIVE-FINDINGS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-PATTERNS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-RESEARCH.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-SECURITY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-UAT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/COVERAGE.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.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.0.0 → pushframe-5.0.2}/.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.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/260708-dt9-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/260708-dt9-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/deferred-items.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-quick/README.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-REQUIREMENTS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-ROADMAP.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-LIVE-FINDINGS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-PATTERNS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-REVIEW.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-REQUIREMENTS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-ROADMAP.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DECISION-RECORD.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-LIVE-FINDINGS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/COVERAGE.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-DISCUSS-CHECKPOINT.json +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/COVERAGE.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-03-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-03-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-RESEARCH.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-UAT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-RESEARCH.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/20-pushframe-rename-migration-repo-switch/20-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/21-debian-package-apt-repo/21-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/21-debian-package-apt-repo/21-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/21-debian-package-apt-repo/21-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/21-debian-package-apt-repo/21-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/21-debian-package-apt-repo/21-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/21-debian-package-apt-repo/21-RESEARCH.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/21-debian-package-apt-repo/21-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/22-01-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/22-01-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/22-02-PLAN.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/22-02-SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/22-CONTEXT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/22-RESEARCH.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/22-VERIFICATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/OPERATOR-STEPS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/ALBUM-ACCESS-V4-ADDENDUM.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/ALBUM-ACCESS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/ARCHITECTURE.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/BROWSER-AUTOMATION.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/FEATURES.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/PITFALLS.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/STACK.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/SUMMARY.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/state.json +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.planning/todos/completed/2026-07-06-promote-debug-flag-to-a-global-cli-convention.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/.python-version +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/CLAUDE.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/LICENSE +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/README.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/VERIFICATION-REPORT.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/docs/CLI.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/docs/agents/domain.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/docs/agents/issue-tracker.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/docs/agents/triage-labels.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/docs/superpowers/specs/2026-07-09-write-rate-limiter-design.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/main.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/packaging/control-template +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/packaging/lintian-overrides +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/packaging/postinst +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/packaging/pushframe.copyright +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/probes/.gitignore +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/probes/README.md +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/probes/browser_bootstrap.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/probes/common.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/probes/cookie_vault.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/probes/fidelity_check.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/probes/rpc_capture.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/probes/shared_link_probe.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/__init__.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/accountApi.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/activityApi.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/assetApi.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/baseApi.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/frameApi.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/notificationApi.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/peopleApi.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/playlistApi.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/aura.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/aws/__init__.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/aws/awsclient.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/aws/s3client.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/aws/sqsclient.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/cache.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/cli.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/client.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/exif.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/export.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/__init__.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/bootstrap.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/cache.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/client.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/enumerate.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/manifest.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/parsers.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/redaction.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/vault.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/gsync.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/migration.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/models/__init__.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/models/activity.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/models/asset.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/models/frame.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/models/meta.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/models/person.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/models/user.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/ratelimit.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/reconcile.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/sync.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/utils/dt.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/utils/io.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/utils/settings.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/scripts/build-deb.sh +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/scripts/make-apt-key.sh +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/scripts/test-apt-journey-container.sh +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/scripts/test-deb-container.sh +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/conftest.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/asset_unprocessed.json +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/assets_page1.json +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/assets_page2.json +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/assets_placeholders.json +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/error_envelope.json +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/frame_detail.json +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/frames.json +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/google_rpc_snackc_page.json +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/google_share_page_sample.html +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/login.json +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/probe_af_initdata_sample.html +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/offline.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_asset_partial.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_asset_unprocessed.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_aura_logger.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_batch_update_contract.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_apply.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_google_album.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_google_link.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_inspect.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_push_budget_geo.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_reconcile.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_status.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_status_google.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_sync.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_client_rate_limit.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_execute_plan.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_execute_plan_budget_geo.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_fixtures_validity.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_google_cache.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_google_client.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_google_enumerate.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_google_manifest.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_google_parsers.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_google_vault.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_gsync_engine.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_gsync_execute.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_imports.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_migration.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_offline_read_path.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_prep_upload_formats.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_probe_browser.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_probe_shared_link.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_ratelimit.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_read_path.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_reconcile.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_retry_401.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_settings_budget.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_sync_engine.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_write_endpoints_failloud.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_write_formats_live.py +0 -0
- {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_write_throttling.py +0 -0
|
@@ -105,12 +105,18 @@ jobs:
|
|
|
105
105
|
install -d -m 700 "$KEYDIR"
|
|
106
106
|
printf '%s\n' "$PUSHFRAME_APT_SIGNING_KEY" > "$KEYDIR/signing-key.asc"
|
|
107
107
|
chmod 600 "$KEYDIR/signing-key.asc"
|
|
108
|
-
|
|
108
|
+
sudo apt-get update -qq
|
|
109
|
+
sudo apt-get install -y -qq gnupg dpkg-dev >/dev/null
|
|
110
|
+
# derive the fingerprint with gpg (the ASCII armor does not carry
|
|
111
|
+
# it as text — a grep-based extraction fails and killed the first
|
|
112
|
+
# release run)
|
|
113
|
+
gpg --import "$KEYDIR/signing-key.asc" 2>/dev/null
|
|
114
|
+
FP=$(gpg --list-secret-keys --with-colons | awk -F: '/^fpr:/ {print $10; exit}')
|
|
109
115
|
printf '%s\n' "$FP" > "$KEYDIR/fingerprint"
|
|
110
116
|
echo "signing key restored: $FP"
|
|
111
|
-
# publish-apt-repo.sh needs gpg + the standalone python for uv pip
|
|
112
|
-
sudo apt-get update -qq && sudo apt-get install -y -qq gnupg >/dev/null
|
|
113
117
|
- name: Rebuild + republish the APT repository (gh-pages)
|
|
118
|
+
env:
|
|
119
|
+
APT_GIT_AUTHOR: pushframe-release-bot <coredmp95@gmail.com>
|
|
114
120
|
run: |
|
|
115
121
|
uv python install 3.14
|
|
116
122
|
./scripts/publish-apt-repo.sh
|
pushframe-5.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pushframe
|
|
3
|
+
Version: 5.0.2
|
|
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
|
+
Project-URL: Homepage, https://github.com/coredmp95/pushframe
|
|
6
|
+
Project-URL: Repository, https://github.com/coredmp95/pushframe
|
|
7
|
+
Project-URL: Issues, https://github.com/coredmp95/pushframe/issues
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Fabrice DIDIERJEAN (coredmp95)
|
|
11
|
+
Copyright (c) 2023 zmanowar (zach@codehooker.com) — original auraframes client
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
Provenance note: this project began as a fork of zmanowar's `auraframes`
|
|
34
|
+
(https://github.com/zmanowar/auraframes), which ships without an explicit
|
|
35
|
+
license. The original author's work (reverse-engineered API models, auth,
|
|
36
|
+
read/upload flows) is credited in the README and preserved in the git
|
|
37
|
+
history. This fork's own additions are licensed MIT above. If you are the
|
|
38
|
+
original author and want a different arrangement, please open an issue.
|
|
39
|
+
License-File: LICENSE
|
|
40
|
+
Requires-Python: >=3.14
|
|
41
|
+
Requires-Dist: boto3>=1.34
|
|
42
|
+
Requires-Dist: botocore>=1.34
|
|
43
|
+
Requires-Dist: geopy>=2.4
|
|
44
|
+
Requires-Dist: h2>=4
|
|
45
|
+
Requires-Dist: httpx[http2]>=0.27
|
|
46
|
+
Requires-Dist: loguru>=0.7
|
|
47
|
+
Requires-Dist: piexif>=1.1.3
|
|
48
|
+
Requires-Dist: pillow-heif>=1.6
|
|
49
|
+
Requires-Dist: pillow>=10.4
|
|
50
|
+
Requires-Dist: pydantic>=2
|
|
51
|
+
Requires-Dist: python-dotenv>=1.0
|
|
52
|
+
Requires-Dist: requests>=2.31
|
|
53
|
+
Requires-Dist: tqdm>=4.66
|
|
54
|
+
Provides-Extra: dev
|
|
55
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
56
|
+
Provides-Extra: google-browser
|
|
57
|
+
Requires-Dist: playwright>=1.63.0; extra == 'google-browser'
|
|
58
|
+
Description-Content-Type: text/markdown
|
|
59
|
+
|
|
60
|
+
# Aura Frames (PUSHD) Python Client [unofficial]
|
|
61
|
+
|
|
62
|
+
[](https://github.com/coredmp95/pushframe/actions/workflows/tests.yml)
|
|
63
|
+
[](LICENSE)
|
|
64
|
+
[](https://www.python.org/downloads/)
|
|
65
|
+
|
|
66
|
+
Implements most of the AuraFrames APIs in Python.
|
|
67
|
+
|
|
68
|
+
Any advice or issues are welcome.
|
|
69
|
+
|
|
70
|
+
> **Provenance:** this project is a renamed, heavily extended fork of
|
|
71
|
+
> [zmanowar/auraframes](https://github.com/zmanowar/auraframes) — the original
|
|
72
|
+
> 2023 reverse-engineering of the Aura/Pushd API is his work (see
|
|
73
|
+
> [Credits](#credits) and [LICENSE](LICENSE)). Unofficial community tool:
|
|
74
|
+
> **not affiliated with or endorsed by Aura Frames Inc.**
|
|
75
|
+
|
|
76
|
+
> **Read path: VERIFIED end-to-end** against `api.pushd.com/v5` (login → list → fetch →
|
|
77
|
+
> download). See [`VERIFICATION-REPORT.md`](VERIFICATION-REPORT.md) for the per-step status
|
|
78
|
+
> and the live API drift repaired.
|
|
79
|
+
>
|
|
80
|
+
> **Write path: VERIFIED end-to-end** against a live frame — upload, hide, re-show, remove,
|
|
81
|
+
> and irreversible delete have each been exercised through the CLI, including the
|
|
82
|
+
> confirmation gates. See [`docs/CLI.md`](docs/CLI.md) for the commands and the known issues.
|
|
83
|
+
>
|
|
84
|
+
> The **device** upload/download flows described near the end of this README remain
|
|
85
|
+
> **documented from code, not verified** — they describe what the official app does, not a
|
|
86
|
+
> path this client exercises.
|
|
87
|
+
|
|
88
|
+
## Requirements
|
|
89
|
+
|
|
90
|
+
- Python 3.14 (pinned via `.python-version`) and [`uv`](https://docs.astral.sh/uv/) for
|
|
91
|
+
environment, dependency, and interpreter management.
|
|
92
|
+
- A live Aura account (email + password) for any command that actually hits the API.
|
|
93
|
+
|
|
94
|
+
## Install (Ubuntu/Debian)
|
|
95
|
+
|
|
96
|
+
Pre-built `.deb` packages embed their own Python 3.14 runtime under
|
|
97
|
+
`/usr/lib/pushframe/` — **no system Python is used or modified**, and the
|
|
98
|
+
package needs only `ca-certificates` and `libc6`. Removal is clean (the
|
|
99
|
+
package owns every file it ships, including bytecode; nothing is written
|
|
100
|
+
into the system tree at run time).
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# from a release artifact:
|
|
104
|
+
sudo apt install ./pushframe_<version>_amd64.deb
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### uv tool (or pip) — any Linux distro, per-user
|
|
108
|
+
|
|
109
|
+
The PyPI package is pure Python and self-contained — no system Python is
|
|
110
|
+
touched either; `uv` manages an isolated environment for the tool:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
uv tool install pushframe # or: pipx install pushframe
|
|
114
|
+
pushframe --version
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Which path when?** deb/APT = system-wide install on Ubuntu/Debian
|
|
118
|
+
servers (root-owned, autoremove-friendly). `uv tool install` = per-user,
|
|
119
|
+
no sudo, any distro with uv (or pipx) installed. Same CLI, same config
|
|
120
|
+
(`~/.config/pushframe/`), same version story: every channel ships the
|
|
121
|
+
same release, and `pushframe --version` tells you what runs.
|
|
122
|
+
|
|
123
|
+
### APT repository
|
|
124
|
+
|
|
125
|
+
A signed APT repository is served from this project's GitHub Pages. Three
|
|
126
|
+
commands, exactly as verified in a clean container:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# 0. the apt keyring dir (already present on recent systems)
|
|
130
|
+
sudo install -d -m 0755 /etc/apt/keyrings
|
|
131
|
+
|
|
132
|
+
# 1. trust the repository key
|
|
133
|
+
# (dedicated signing key, fingerprint
|
|
134
|
+
# 0EE2DB2DB1360C58C4B2E0BF2EC06828F722F391)
|
|
135
|
+
curl -fsSL https://coredmp95.github.io/pushframe/dists/pushframe.asc \
|
|
136
|
+
| sudo gpg --dearmor -o /etc/apt/keyrings/pushframe.gpg
|
|
137
|
+
|
|
138
|
+
# 2. add the sources entry
|
|
139
|
+
echo "deb [signed-by=/etc/apt/keyrings/pushframe.gpg] https://coredmp95.github.io/pushframe stable main" \
|
|
140
|
+
| sudo tee /etc/apt/sources.list.d/pushframe.list
|
|
141
|
+
|
|
142
|
+
# 3. install
|
|
143
|
+
sudo apt update && sudo apt install pushframe
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`apt update` must stay free of signature warnings — if it is not, compare the
|
|
147
|
+
key fingerprint above with `gpg --show-keys /etc/apt/keyrings/pushframe.gpg`.
|
|
148
|
+
The repository and the `.deb` are produced by `scripts/publish-apt-repo.sh`
|
|
149
|
+
and `scripts/build-deb.sh`; the signature chain (InRelease/Release.gpg) is
|
|
150
|
+
verified end-to-end by `scripts/test-apt-journey-container.sh`.
|
|
151
|
+
|
|
152
|
+
## Setup & Run (uv, from source)
|
|
153
|
+
|
|
154
|
+
This project uses `uv` exclusively (no `pip` / `venv` / `poetry`). From a clean checkout:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# 1. Install runtime dependencies (creates .venv, respects uv.lock + .python-version).
|
|
158
|
+
# uv auto-installs the pinned Python 3.14 interpreter on first sync if needed.
|
|
159
|
+
uv sync
|
|
160
|
+
|
|
161
|
+
# 2. Run the read-path demo (login -> list frames -> fetch assets -> download one image).
|
|
162
|
+
# With PUSHFRAME_EMAIL / PUSHFRAME_PASSWORD unset it prints a helpful message and exits cleanly.
|
|
163
|
+
uv run python main.py
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
To run the asserted live read-path proof, you need the **`dev` extra** (pytest +
|
|
167
|
+
python-dotenv live there and are **not** installed by `uv sync` alone):
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# Install the dev extra, then run the 4 live read-path tests (needs real credentials):
|
|
171
|
+
uv sync --extra dev
|
|
172
|
+
uv run pytest -m live
|
|
173
|
+
# — or, in one shot without a separate sync step:
|
|
174
|
+
uv run --extra dev pytest -m live
|
|
175
|
+
|
|
176
|
+
# The credential-less default suite stays green (live tests deselected):
|
|
177
|
+
uv run pytest -m "not live"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
> Documenting a bare `uv run pytest -m live` **without** `--extra dev` (or a prior
|
|
181
|
+
> `uv sync --extra dev`) will fail on a clean checkout — `pytest` is an opt-in extra.
|
|
182
|
+
|
|
183
|
+
## Environment Variables
|
|
184
|
+
|
|
185
|
+
Credentials are read from environment variables (see `pushframe/utils/settings.py`). Copy
|
|
186
|
+
`.env.sample` to `.env` and fill in your credentials for the live test path (`.env` is
|
|
187
|
+
gitignored — never commit real secrets); shell-exported variables also work and take
|
|
188
|
+
precedence.
|
|
189
|
+
|
|
190
|
+
**Required:**
|
|
191
|
+
- `PUSHFRAME_EMAIL`: The email of the account to authenticate with.
|
|
192
|
+
- `Aura.login` may optionally be called with an email and password instead of setting env vars.
|
|
193
|
+
- `PUSHFRAME_PASSWORD`: The password of the account to authenticate with.
|
|
194
|
+
|
|
195
|
+
**Optional (with defaults):**
|
|
196
|
+
- `PUSHFRAME_LOCALE`: The locale of the device to mimic. (Default: `en-US`)
|
|
197
|
+
- `PUSHFRAME_APP_IDENTIFIER`: The identifier of the aura app. (Default: `com.pushd.client`)
|
|
198
|
+
- This may change between iOS and Android app implementations, untested.
|
|
199
|
+
- `PUSHFRAME_DEVICE_IDENTIFIER`: The unique identifier of the device to mimic. (Default: `0000000000000000`)
|
|
200
|
+
- Ideally this should be set to your unique identifier, though it accepts others.
|
|
201
|
+
|
|
202
|
+
**Optional — write budget & geo guard** (used by `sync --apply`, `push`, and `google-sync --apply`; see
|
|
203
|
+
[*Write Path*](#write-path-upload--status--anti-abuse-budget) below):
|
|
204
|
+
- `PUSHFRAME_COUNTRY`: Expected account country for the geo pre-flight check, e.g. `FR`.
|
|
205
|
+
**Unset disables the check entirely.**
|
|
206
|
+
- `PUSHFRAME_GEO_FAIL_OPEN`: Continue if the country lookup itself fails. (Default: `true`)
|
|
207
|
+
- `PUSHFRAME_WRITE_BUDGET_CAPACITY`: Token-bucket capacity, in requests. (Default: `30`)
|
|
208
|
+
- `PUSHFRAME_WRITE_BUDGET_REFILL_PER_MIN`: Refill rate per minute. (Default: `0.75`)
|
|
209
|
+
- `PUSHFRAME_WRITE_BUDGET_WAIT`: Wait for a refill rather than stopping. (Default: `true`)
|
|
210
|
+
- `PUSHFRAME_WRITE_BUDGET_MAX_WAIT`: Max seconds to wait. (Default: `3600`)
|
|
211
|
+
- `PUSHFRAME_STATE_DIR`: Where the persisted budget lives. (Default: `~/.config/pushframe`)
|
|
212
|
+
|
|
213
|
+
**Optional — Google sync:**
|
|
214
|
+
- `PUSHFRAME_PROBE_CHROME_PROFILE`: Dedicated Chrome profile directory used by `google-link` for
|
|
215
|
+
the one-time cookie harvest (required for that command only).
|
|
216
|
+
- `PUSHFRAME_GOOGLE_SYNC_REMOVAL_THRESHOLD`: Fraction of the frame's photos above which
|
|
217
|
+
`google-sync` demands explicit confirmation before hiding (Default: `0.2`).
|
|
218
|
+
|
|
219
|
+
Boolean variables accept `1`, `true`, `yes`, `on` (case-insensitive); anything else is false.
|
|
220
|
+
|
|
221
|
+
> **Legacy names:** the old `AURA_*` spellings (`AURA_EMAIL`, `AURA_STATE_DIR`,
|
|
222
|
+
> `AURA_WRITE_BUDGET_*`, `AURA_PROBE_CHROME_PROFILE`, …) are still read as
|
|
223
|
+
> fallbacks — one release of grace. The old config directory
|
|
224
|
+
> `~/.config/auraframes/` is migrated automatically to `~/.config/pushframe/`
|
|
225
|
+
> on the first run (the old directory is left untouched).
|
|
226
|
+
|
|
227
|
+
## CLI Usage (`pushframe`)
|
|
228
|
+
|
|
229
|
+
A CLI wraps the library (installed as the `pushframe` entry point by `uv sync`). There are
|
|
230
|
+
eight commands:
|
|
231
|
+
|
|
232
|
+
| Command | What it does | Writes? |
|
|
233
|
+
|---------|--------------|---------|
|
|
234
|
+
| `status` | Check credentials, log in, list your frames, show the Google link state | No |
|
|
235
|
+
| `inspect` | Show one frame's photos and metadata | No |
|
|
236
|
+
| `sync` | Make a frame **match** a local directory | Yes, with `--apply` |
|
|
237
|
+
| `push` | Upload from a supply directory — **never** removes | Yes, with `--apply` |
|
|
238
|
+
| `reconcile` | Report (and optionally remove) stuck placeholder rows on a frame | Only with `--apply` |
|
|
239
|
+
| `google-link` | Link (or re-link) your Google Photos account | Vault write only (outside the repo) |
|
|
240
|
+
| `google-album` | Select a Google Photos album and enumerate it exactly | No (read-only) |
|
|
241
|
+
| `google-sync` | Mirror a Google Photos album onto a frame (dry run by default) | Yes, with `--apply` |
|
|
242
|
+
|
|
243
|
+
**→ Full reference with every flag, real output, and known issues: [`docs/CLI.md`](docs/CLI.md)**
|
|
244
|
+
|
|
245
|
+
### Start here
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# Health check: are credentials set, does login work, which frames exist?
|
|
249
|
+
uv run pushframe status
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
PUSHFRAME_EMAIL: set
|
|
254
|
+
PUSHFRAME_PASSWORD: set
|
|
255
|
+
Logged in as you@example.com
|
|
256
|
+
1 frames:
|
|
257
|
+
- Living Room (id: 00000000-0000-0000-0000-000000000000)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`--frame` takes a **case-insensitive substring of the frame name**, or an exact id. An
|
|
261
|
+
ambiguous substring stops the run and lists the matches rather than guessing:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
uv run pushframe inspect --frame "living"
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### `sync` — match a directory (dry run by default)
|
|
268
|
+
|
|
269
|
+
Nothing changes without `--apply`:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
uv run pushframe sync ./photos/ --frame "Living Room"
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
Sync plan for Living Room (id: 00000000-...) — DRY RUN, nothing will be changed
|
|
277
|
+
To upload: 12
|
|
278
|
+
To hide: 3
|
|
279
|
+
To re-show: 1
|
|
280
|
+
Unchanged: 84
|
|
281
|
+
Already hidden: 2 (no action needed)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Photos are matched by **md5 content hash**, not filename — renaming a file locally does not
|
|
285
|
+
cause a re-upload.
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
# Apply it. One confirmation covers the whole plan and echoes the frame name + id.
|
|
289
|
+
uv run pushframe sync ./photos/ --frame "Living Room" --apply
|
|
290
|
+
|
|
291
|
+
# Non-interactive (CI, scripts) — --yes is required, otherwise it fails closed.
|
|
292
|
+
uv run pushframe sync ./photos/ --frame "Living Room" --apply --yes
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Removed photos are hidden, not deleted
|
|
296
|
+
|
|
297
|
+
A photo that leaves your directory is **hidden** by default: it stops displaying but stays in
|
|
298
|
+
your account, and comes straight back if you restore the file. The frame has no photo-count
|
|
299
|
+
limit, so preservation is the safe default — a mistaken sync should cost visibility, never
|
|
300
|
+
photos.
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
# Default: hide. Reversible.
|
|
304
|
+
uv run pushframe sync ./photos/ --frame "Living Room" --apply --yes
|
|
305
|
+
|
|
306
|
+
# Remove from this frame (the asset survives in your account).
|
|
307
|
+
uv run pushframe sync ./photos/ --frame "Living Room" --apply --yes --delete
|
|
308
|
+
|
|
309
|
+
# Destroy account-wide. IRREVERSIBLE.
|
|
310
|
+
uv run pushframe sync ./photos/ --frame "Living Room" --apply --hard-delete
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
| Flag | Effect | Reversible |
|
|
314
|
+
|------|--------|------------|
|
|
315
|
+
| *(none)* | Hidden — stops displaying, stays on the frame | **Yes** |
|
|
316
|
+
| `--delete` | Removed from this frame | Must re-upload |
|
|
317
|
+
| `--hard-delete` | Destroyed account-wide | **No** |
|
|
318
|
+
|
|
319
|
+
`--delete` and `--hard-delete` are mutually exclusive (argparse rejects both together).
|
|
320
|
+
|
|
321
|
+
`--hard-delete` does **not** accept a y/N answer. It makes you re-type the exact count, so you
|
|
322
|
+
have to read the number first:
|
|
323
|
+
|
|
324
|
+
```
|
|
325
|
+
To hard-delete: 12
|
|
326
|
+
IRREVERSIBLE: 12 photo(s) will be permanently destroyed account-wide, not just removed from
|
|
327
|
+
this frame. This cannot be undone.
|
|
328
|
+
To confirm, type the number of photos to hard-delete (12): y
|
|
329
|
+
Aborted.
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Only `12` proceeds. Note that `--yes` skips this gate like any other, so
|
|
333
|
+
`sync --apply --yes --hard-delete` destroys without prompting — use it deliberately.
|
|
334
|
+
|
|
335
|
+
### Restoring a hidden photo
|
|
336
|
+
|
|
337
|
+
Because hiding is reversible and hidden photos still count as present for deduplication, the
|
|
338
|
+
round trip is just moving the file back:
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
mv ./photos/sunset.jpg /tmp/ && uv run pushframe sync ./photos/ --frame "Living Room" --apply --yes
|
|
342
|
+
# -> To hide: 1
|
|
343
|
+
|
|
344
|
+
mv /tmp/sunset.jpg ./photos/ && uv run pushframe sync ./photos/ --frame "Living Room" --apply --yes
|
|
345
|
+
# -> To re-show: 1 (and "To upload: 0" -- it is un-hidden, not uploaded again)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### `push` — upload only, never removes
|
|
349
|
+
|
|
350
|
+
`push` is structurally additive: its removal list is forced empty, so it cannot hide, remove,
|
|
351
|
+
or re-show anything. Use it when you want to *add* from a supply directory without the frame
|
|
352
|
+
being diffed to match it.
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
# Dry run, then apply. Photos already on the frame are skipped by md5.
|
|
356
|
+
uv run pushframe push ./buffet/ --frame "Living Room"
|
|
357
|
+
uv run pushframe push ./buffet/ --frame "Living Room" --apply --yes
|
|
358
|
+
|
|
359
|
+
# Pacing flags for the anti-abuse write budget (see below):
|
|
360
|
+
# --limit N upload at most N photos this run
|
|
361
|
+
# --batch-size N assets per select_asset/batch_update call (default 50)
|
|
362
|
+
# --chunk-delay S seconds to pause between write chunks (default 5)
|
|
363
|
+
uv run pushframe push ./buffet/ --frame "Living Room" --apply --yes --limit 40
|
|
364
|
+
|
|
365
|
+
# Budget / geo overrides:
|
|
366
|
+
# --max-wait S cap the wait for budget refill (default 3600)
|
|
367
|
+
# --no-wait stop instead of waiting when the budget is dry
|
|
368
|
+
# --country XX expected account country for the geo pre-flight guard
|
|
369
|
+
# --ignore-budget bypass the budget entirely (escape hatch)
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**If you are unsure which to use, use `push`** — it cannot take anything away.
|
|
373
|
+
|
|
374
|
+
### Google Photos albums — `google-link` / `google-album`
|
|
375
|
+
|
|
376
|
+
These commands read your **Google Photos** shared albums (a separate account from the Aura
|
|
377
|
+
API). The mechanism is the browser-automation one proven in phase 16: a dedicated-profile
|
|
378
|
+
browser harvests the session cookies once, and every later operation is plain authenticated
|
|
379
|
+
HTTP over the internal `batchexecute` API — no browser runs again.
|
|
380
|
+
|
|
381
|
+
**One-time setup:** point `PUSHFRAME_PROBE_CHROME_PROFILE` at a **dedicated** Chrome profile
|
|
382
|
+
directory (your daily-driver profile is structurally unreachable), then link:
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
export PUSHFRAME_PROBE_CHROME_PROFILE=~/.config/pushframe/chrome-profile
|
|
386
|
+
uv run pushframe google-link
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
A browser window opens; log into Google inside it. The command auto-detects the completed
|
|
390
|
+
login, saves the session to `~/.config/pushframe/google-cookies.json` with `0600`
|
|
391
|
+
permissions outside the repository, and prints only identity signals — cookie **names**, a
|
|
392
|
+
count, never values. **Re-linking is the same command**: when a session expires (they do,
|
|
393
|
+
that cadence is an accepted operational cost), run `google-link` again.
|
|
394
|
+
|
|
395
|
+
Then select and enumerate an album by name, share link, or id:
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
# Discover the account's shared albums:
|
|
399
|
+
uv run pushframe google-album --list
|
|
400
|
+
|
|
401
|
+
# By name substring — ambiguity prints a numbered list and stops (exit 2):
|
|
402
|
+
uv run pushframe google-album "Corse"
|
|
403
|
+
|
|
404
|
+
# By share link or album id — used exactly as given:
|
|
405
|
+
uv run pushframe google-album "https://photos.google.com/share/AF1Qip...?key=..."
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
The resolved album is walked **completely** (the internal `snAcKc` continuation RPC,
|
|
409
|
+
300 items/page, until exhaustion — the count must match what the Google Photos UI shows)
|
|
410
|
+
and every item's exact byte size is measured with 1-byte `Range` requests, so the summary
|
|
411
|
+
prints the exact disk weight without downloading a single photo:
|
|
412
|
+
|
|
413
|
+
```
|
|
414
|
+
Album: Vacances Corse (id shape: photos.google.com/share/AF1Qip…0001)
|
|
415
|
+
Items: 24 (pages: 1, exhausted: cleanly)
|
|
416
|
+
Disk weight: 90,813,552 bytes = 86.6 MiB (min 512,331, max 8,120,444, avg 3,783,898)
|
|
417
|
+
Per-item (index | id shape | WxH | bytes):
|
|
418
|
+
1 | AF1Qip…base1 | 4898x3265 | 3,412,350
|
|
419
|
+
...
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Privacy posture:** session cookies live only in the `0600` vault outside the repository
|
|
423
|
+
and are readable solely by the `pushframe.google` package (sync/CLI code paths are
|
|
424
|
+
structurally refused); album capability URLs are secrets-like and are always printed
|
|
425
|
+
redacted (`AF1Qip…<last4>`). `status` reports the Google link state — `linked: yes/no`,
|
|
426
|
+
the account email, session usability — and never a cookie value or token.
|
|
427
|
+
|
|
428
|
+
### `google-sync` — mirror a Google album onto a frame
|
|
429
|
+
|
|
430
|
+
One verb ties the Google side to the frame: enumerate the album, download what is missing,
|
|
431
|
+
upload it, and mirror removals as **hides**. Start to finish:
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
# One-time setup (or again whenever the Google session expires):
|
|
435
|
+
export PUSHFRAME_PROBE_CHROME_PROFILE=~/.config/pushframe/chrome-profile
|
|
436
|
+
uv run pushframe google-link
|
|
437
|
+
|
|
438
|
+
# Find the album, then mirror it:
|
|
439
|
+
uv run pushframe google-album --list
|
|
440
|
+
uv run pushframe google-sync "Cadre" --frame "Cadre de Fabrice" # dry-run plan (writes nothing)
|
|
441
|
+
uv run pushframe google-sync "Cadre" --frame "Cadre de Fabrice" --apply # one y/N, then it mirrors
|
|
442
|
+
|
|
443
|
+
# Non-interactive (CI, scripts) — --yes is required for --apply, otherwise it fails closed:
|
|
444
|
+
uv run pushframe google-sync "Cadre" --frame "Cadre de Fabrice" --apply --yes
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
A real first run against the live pair (album « Cadre », 24 photos, one of them already on
|
|
448
|
+
the frame but hidden from an earlier experiment):
|
|
449
|
+
|
|
450
|
+
```
|
|
451
|
+
Plan: 23 to upload, 1 to re-show, 0 unchanged, 0 to hide, 0 already hidden
|
|
452
|
+
Videos skipped: 0 (metadata delta — videos are out of sync scope, never silently dropped)
|
|
453
|
+
Upload candidates: 23 items, 129,680 → 8,847,782 bytes (sum ≈ 87.0 MiB)
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
The re-show line is the md5 dedupe working across accounts: that photo's bytes were already
|
|
457
|
+
on the frame, so the plan re-shows it instead of re-uploading it. After this apply the
|
|
458
|
+
cache is pruned and the manifest persists — so a steady-state run costs one album listing
|
|
459
|
+
and one frame listing, and **downloads and uploads nothing**:
|
|
460
|
+
|
|
461
|
+
```
|
|
462
|
+
Plan: 0 to upload, 0 to re-show, 23 unchanged, 0 to hide, 1 already hidden
|
|
463
|
+
Videos skipped: 0 (metadata delta — videos are out of sync scope, never silently dropped)
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
#### Why the second run is free
|
|
467
|
+
|
|
468
|
+
Photos download once into a staging cache
|
|
469
|
+
(`~/.config/pushframe/google-cache/<album>/`), are uploaded with the same md5 convention
|
|
470
|
+
the frame uses, and the cache is **pruned** after the uploads confirm. What survives is a
|
|
471
|
+
persistent manifest (`~/.config/pushframe/google-manifest.json`, mode `0600`) mapping each
|
|
472
|
+
Google photo id to its md5. The plan is rebuilt from the **album listing + manifest**, never
|
|
473
|
+
from a walk of the pruned cache — that is what makes disk minimisation safe: "already synced"
|
|
474
|
+
and "removed from the album" stay distinguishable.
|
|
475
|
+
|
|
476
|
+
#### Mirror semantics
|
|
477
|
+
|
|
478
|
+
- **Remove a photo from the Google album**, re-run with `--apply`: it is **hidden** on the
|
|
479
|
+
frame (`exclude_asset`) — it stops displaying but stays. Re-add it to the album and the
|
|
480
|
+
next run re-shows it **without re-uploading a byte**. Both halves were proven live (see
|
|
481
|
+
`18-UAT.md` in the planning tree).
|
|
482
|
+
- **Videos are skipped with a counted line** (the frame reports null md5 for videos, so
|
|
483
|
+
content-hash diffing cannot see them) — never silently dropped.
|
|
484
|
+
- An **empty or truncated album listing aborts** with an error instead of producing a plan
|
|
485
|
+
(SAFE-01) — a Google-side glitch can never read as "delete/hide everything".
|
|
486
|
+
- If a plan's removals exceed **20 % of the frame's photos**, an explicit confirmation
|
|
487
|
+
echoes both counts first (SAFE-02; threshold overridable via
|
|
488
|
+
`PUSHFRAME_GOOGLE_SYNC_REMOVAL_THRESHOLD`).
|
|
489
|
+
- **This verb never deletes.** Removal means hide; the gated `--delete`/`--hard-delete`
|
|
490
|
+
tiers stay with `sync` only (SAFE-03).
|
|
491
|
+
- A failed or partial download is reported as failed and retried next run — never uploaded
|
|
492
|
+
as junk bytes (SAFE-04).
|
|
493
|
+
- Google-side downloads run concurrently (bounded pool); every frame write stays sequential
|
|
494
|
+
and paced by the [write budget](#write-path-upload--status--anti-abuse-budget).
|
|
495
|
+
|
|
496
|
+
### Exit codes and logging
|
|
497
|
+
|
|
498
|
+
`0` on success (a dry run and an aborted confirmation both count as success); `1` on missing
|
|
499
|
+
credentials, login failure, an unresolvable `--frame`, any per-item failure, a rate-limit
|
|
500
|
+
abort, a geo mismatch, or an exhausted budget.
|
|
501
|
+
|
|
502
|
+
Add `--debug` **before** the subcommand for verbose request/response logging on stderr
|
|
503
|
+
(`pushframe --debug status`, not `pushframe status --debug`). Every run also writes a full log
|
|
504
|
+
to `logs/file_{timestamp}.log`.
|
|
505
|
+
|
|
506
|
+
### Known issue: writes sometimes 401 on the first try
|
|
507
|
+
|
|
508
|
+
Roughly 4 in 10 live write runs have been seen failing with `401 Unauthorized` and succeeding
|
|
509
|
+
on an immediate re-run, with no change to credentials or network. There is **no automatic
|
|
510
|
+
retry yet** — if an apply reports 401 failures, just run it again. Repeating is safe: uploads
|
|
511
|
+
dedupe by md5 and hides are idempotent. See [`docs/CLI.md`](docs/CLI.md#known-issues) for the
|
|
512
|
+
other known quirks (asset-count mismatch, undeletable placeholder rows).
|
|
513
|
+
|
|
514
|
+
## Write Path (upload) — status & anti-abuse budget
|
|
515
|
+
|
|
516
|
+
> **Live-verified (v2.0).** Upload, hide, re-show, remove and hard-delete have each been run
|
|
517
|
+
> end-to-end against a real frame. The caveats below are about *volume and reliability*, not
|
|
518
|
+
> about whether the path works.
|
|
519
|
+
|
|
520
|
+
Key findings (from live runs + decompiling the official Android app):
|
|
521
|
+
|
|
522
|
+
- **Writes intermittently return a bare `401` and succeed on retry** — roughly 4 in 10
|
|
523
|
+
observed runs, independent of endpoint, credentials, or exit-IP country. The client has no
|
|
524
|
+
automatic retry yet, so a failed apply should simply be re-run. This is distinct from the
|
|
525
|
+
budget trip below: it clears immediately rather than after a cooldown.
|
|
526
|
+
- **A write `401` is therefore not proof of a lockout.** Retry with a fresh login first, then
|
|
527
|
+
check the geo guard, then suspect the budget.
|
|
528
|
+
|
|
529
|
+
- `select_asset` and `batch_update` are **native batch endpoints** — the client now sends a
|
|
530
|
+
whole chunk of assets per call (`WRITE_BATCH_SIZE`, default 50) instead of one call per
|
|
531
|
+
file, collapsing ~3N Pushd write calls to ~2 per chunk.
|
|
532
|
+
- The account has a rolling **write-volume budget** over a time window (≈50 new assets
|
|
533
|
+
observed before a trip). Exceeding it returns a bare `401`/custom `475` (no `Retry-After`),
|
|
534
|
+
which the client detects as a run of consecutive failures and **aborts loudly** rather than
|
|
535
|
+
hammering. The official app never trips this because it drip-feeds uploads via a background
|
|
536
|
+
`JobScheduler` queue over time (gated on charging + WiFi), retrying failures across runs.
|
|
537
|
+
- Consequence for bulk imports: use `push --limit` in **small waves spaced over time** rather
|
|
538
|
+
than one big burst. `sync`/`push` are resumable — the md5 diff means re-running only
|
|
539
|
+
attempts what is still missing. A large first-time import has still not been driven to
|
|
540
|
+
completion in one sitting.
|
|
541
|
+
- A client-side **token-bucket budget and geo pre-flight guard** now run before every write
|
|
542
|
+
(see the `PUSHFRAME_WRITE_BUDGET_*` / `PUSHFRAME_COUNTRY` variables above), so the server-side
|
|
543
|
+
lockout is hard to reach by accident.
|
|
544
|
+
|
|
545
|
+
## iOS/Android Device's Upload Image Flow
|
|
546
|
+
|
|
547
|
+
> **Documented from code, NOT verified in this revive milestone.** The flow below is
|
|
548
|
+
> transcribed from the 2023-era implementation and has not been exercised against the live
|
|
549
|
+
> API during the read-path revive. Treat it as a reference, not a proven path. NOTE: the
|
|
550
|
+
> current CLI write path **batches** steps 4–9 across many assets per call (see *Write Path*
|
|
551
|
+
> above); the per-asset sequence below is the original single-asset reference.
|
|
552
|
+
|
|
553
|
+
[Aura.upload_image](pushframe/aura.py#L101) attempts to implement this flow as closely as possible.
|
|
554
|
+
1. A frame is selected and the frame's data is retrieved from the API (`/frames/<frame_id>.json`).
|
|
555
|
+
2. An image on the device is selected for upload.
|
|
556
|
+
3. An Asset object is created for the image and a GUID (`local_identifier`) is generated.
|
|
557
|
+
4. A POST request is made (`/frames/<frame_id>/select_asset.json`) with the asset's `local_identifier`.
|
|
558
|
+
- This allows the asset to be related to a specific frame once the image has been uploaded.
|
|
559
|
+
5. SQS is polled (may not be necessary)
|
|
560
|
+
- The result does not seem to be used in a meaningful way.
|
|
561
|
+
6. Another `select_asset.json` request is sent with the same information.
|
|
562
|
+
7. A `put_object` request is made to the S3 bucket `images.senseapp.co` with the image, the MD5 and uploaded filename are retrieved.
|
|
563
|
+
8. The asset object is populated with the S3 response.
|
|
564
|
+
9. A PUT request is sent to `/assets/batch_update.json` with the asset information.
|
|
565
|
+
- I believe this is when the location exif data is read from the image file itself and used to populate future asset requests. Manually populating the exif data on the Asset object before sending it does not get returned in future requests.
|
|
566
|
+
- The height and width of the image can be spoofed in the Asset object to produce skewed images, it seems like only the location exif is used.
|
|
567
|
+
10. SQS is polled again
|
|
568
|
+
|
|
569
|
+
```mermaid
|
|
570
|
+
sequenceDiagram
|
|
571
|
+
Device ->> Aura Frames API: Selects a Frame
|
|
572
|
+
Aura Frames API -->> Device: Frame object
|
|
573
|
+
Device ->> Device: An image is selected and <br/>an asset object is created on the device.
|
|
574
|
+
Device ->> Aura Frames API: PUT request to select the asset
|
|
575
|
+
Note left of Aura Frames API: The asset's local_identifier<br/>and frame id are associated.
|
|
576
|
+
Aura Frames API -->> Device: The number of assets that failed to associate
|
|
577
|
+
Device ->> SQS: Polls queue
|
|
578
|
+
Note left of SQS: The result does not seem to be used<br/>in a meaningful way.
|
|
579
|
+
Device ->> Aura Frames API: PUT request to select the asset
|
|
580
|
+
Aura Frames API -->> Device: The number of assets that failed to associate
|
|
581
|
+
Device ->> S3 Bucket: The image is uploaded via put_object.
|
|
582
|
+
S3 Bucket -->> Device: The uploaded filename and MD5 of the image.
|
|
583
|
+
Device ->> Device: Populates the Asset object with the S3 data.
|
|
584
|
+
Device ->> Aura Frames API: The Asset object is uploaded.
|
|
585
|
+
Note left of Aura Frames API: The image's exif data is used for the Asset's location.
|
|
586
|
+
Device ->> SQS: Polls queue
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
## iOS/Android Device's Download/View Image Flow
|
|
590
|
+
|
|
591
|
+
> **VERIFIED end-to-end this milestone** (READ-01..READ-04) — see
|
|
592
|
+
> [`VERIFICATION-REPORT.md`](VERIFICATION-REPORT.md). `main.py` drives exactly this path.
|
|
593
|
+
|
|
594
|
+
1. A frame is selected and the frame's data is retrieved from the API (`/frames/<frame_id>.json`).
|
|
595
|
+
2. A paginated list of assets is retrieved with the `frame_id` (`/frames/{frame_id}/assets.json`).
|
|
596
|
+
3. A URL is built that contains the image proxy URL, the asset's uploaded user id, and the asset's S3 filename.
|
|
597
|
+
- See [export.py](pushframe/export.py)
|
|
598
|
+
4. The image is retrieved from the URL.
|
|
599
|
+
5. TODO: Describe rendering
|
|
600
|
+
|
|
601
|
+
### TODOs
|
|
602
|
+
|
|
603
|
+
> These are open reverse-engineering notes — **documented from code, NOT verified in this
|
|
604
|
+
> revive milestone.**
|
|
605
|
+
|
|
606
|
+
- Map out the actual SQS flow.
|
|
607
|
+
- SQS may be polling constantly and used for push notification / update requests.
|
|
608
|
+
- Determine if it's possible to have 2 active logins for the same account
|
|
609
|
+
- Is it possible to associate an asset to multiple frames? Currently, the device flow uploads the image for each frame, there may be some backend process to dedupe them.
|
|
610
|
+
- Worth checking if the Asset's (S3) filename changes.
|
|
611
|
+
- Reverse the _actual_ frame's rendering process. Presumably it uses the same endpoints.
|
|
612
|
+
- Worth checking with MITM proxy before JTAG/firmware dumping.
|
|
613
|
+
|
|
614
|
+
## Credits
|
|
615
|
+
|
|
616
|
+
This is an unofficial, reverse-engineered client.
|
|
617
|
+
|
|
618
|
+
- **Original author:** [zmanowar](https://github.com/zmanowar) (`zach@codehooker.com`) —
|
|
619
|
+
created the original Aura/Pushd Python client in 2023
|
|
620
|
+
([zmanowar/auraframes](https://github.com/zmanowar/auraframes)), including the
|
|
621
|
+
reverse-engineered API models, the auth/read/upload flows, and the first version of this
|
|
622
|
+
README. All of the reverse-engineering insight this project builds on is his work; his
|
|
623
|
+
commits are preserved in this repository's git history.
|
|
624
|
+
- **Revive & extend (2026):** Fabrice DIDIERJEAN — modernized the ~3-year-old codebase onto
|
|
625
|
+
Python 3.14 + `uv`, verified the read path live, then built and live-proved the whole
|
|
626
|
+
current tool: the `pushframe` CLI (`status`/`inspect`/`sync`/`push`/`reconcile`), the
|
|
627
|
+
batched anti-abuse write path, and the entire local Google Photos → frame mirror
|
|
628
|
+
(cookie-vault linking, album enumeration, pruned-cache sync with a persistent
|
|
629
|
+
manifest, hide-by-default semantics). Project renamed `pushframe` in v5.0.
|