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.
Files changed (378) hide show
  1. {pushframe-5.0.0 → pushframe-5.0.2}/.github/workflows/release.yml +9 -3
  2. pushframe-5.0.2/PKG-INFO +629 -0
  3. pushframe-5.0.2/SECURITY.md +100 -0
  4. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/__init__.py +1 -1
  5. {pushframe-5.0.0 → pushframe-5.0.2}/pyproject.toml +8 -1
  6. {pushframe-5.0.0 → pushframe-5.0.2}/scripts/publish-apt-repo.sh +11 -1
  7. pushframe-5.0.2/scripts/test-install-journey-container.sh +65 -0
  8. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_version.py +3 -2
  9. {pushframe-5.0.0 → pushframe-5.0.2}/uv.lock +53 -36
  10. pushframe-5.0.0/PKG-INFO +0 -53
  11. pushframe-5.0.0/scripts/test-install-journey-container.sh +0 -62
  12. {pushframe-5.0.0 → pushframe-5.0.2}/.env.sample +0 -0
  13. {pushframe-5.0.0 → pushframe-5.0.2}/.freebuff/project-id +0 -0
  14. {pushframe-5.0.0 → pushframe-5.0.2}/.github/workflows/tests.yml +0 -0
  15. {pushframe-5.0.0 → pushframe-5.0.2}/.gitignore +0 -0
  16. {pushframe-5.0.0 → pushframe-5.0.2}/.gsd/dispatch-isolation-sentinel.json +0 -0
  17. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/MILESTONES.md +0 -0
  18. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/PROJECT.md +0 -0
  19. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/REQUIREMENTS.md +0 -0
  20. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/RETROSPECTIVE.md +0 -0
  21. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/ROADMAP.md +0 -0
  22. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/STATE.md +0 -0
  23. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/WINDOWS.md +0 -0
  24. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/codebase/ARCHITECTURE.md +0 -0
  25. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/codebase/CONCERNS.md +0 -0
  26. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/codebase/CONVENTIONS.md +0 -0
  27. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/codebase/INTEGRATIONS.md +0 -0
  28. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/codebase/STACK.md +0 -0
  29. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/codebase/STRUCTURE.md +0 -0
  30. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/codebase/TESTING.md +0 -0
  31. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/config.json +0 -0
  32. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/debug/knowledge-base.md +0 -0
  33. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/debug/login-475-null-creds.md +0 -0
  34. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/debug/resolved/cli-status-verbose-logging.md +0 -0
  35. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/debug/resolved/inspect-asset-null-fields.md +0 -0
  36. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/debug/resolved/select-asset-401-unauthorized.md +0 -0
  37. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-REQUIREMENTS.md +0 -0
  38. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-ROADMAP.md +0 -0
  39. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-PLAN.md +0 -0
  40. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-SUMMARY.md +0 -0
  41. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-PLAN.md +0 -0
  42. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-SUMMARY.md +0 -0
  43. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-CONTEXT.md +0 -0
  44. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-DISCUSSION-LOG.md +0 -0
  45. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-PATTERNS.md +0 -0
  46. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-RESEARCH.md +0 -0
  47. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-REVIEW.md +0 -0
  48. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-VERIFICATION.md +0 -0
  49. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-PLAN.md +0 -0
  50. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-SUMMARY.md +0 -0
  51. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-PLAN.md +0 -0
  52. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-SUMMARY.md +0 -0
  53. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-CONTEXT.md +0 -0
  54. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-DISCUSSION-LOG.md +0 -0
  55. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-LIVE-EVIDENCE.md +0 -0
  56. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-PATTERNS.md +0 -0
  57. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-RESEARCH.md +0 -0
  58. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-REVIEW.md +0 -0
  59. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-VERIFICATION.md +0 -0
  60. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-PLAN.md +0 -0
  61. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-SUMMARY.md +0 -0
  62. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-CONTEXT.md +0 -0
  63. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-DISCUSSION-LOG.md +0 -0
  64. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-PATTERNS.md +0 -0
  65. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-RESEARCH.md +0 -0
  66. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-REVIEW.md +0 -0
  67. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-VERIFICATION.md +0 -0
  68. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-ROADMAP.md +0 -0
  69. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/.gitkeep +0 -0
  70. {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
  71. {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
  72. {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
  73. {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
  74. {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
  75. {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
  76. {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
  77. {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
  78. {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
  79. {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
  80. {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
  81. {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
  82. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-REQUIREMENTS.md +0 -0
  83. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-ROADMAP.md +0 -0
  84. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-PLAN.md +0 -0
  85. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-SUMMARY.md +0 -0
  86. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-PLAN.md +0 -0
  87. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-SUMMARY.md +0 -0
  88. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-CONTEXT.md +0 -0
  89. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-DISCUSSION-LOG.md +0 -0
  90. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-PATTERNS.md +0 -0
  91. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-REVIEW.md +0 -0
  92. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-SECURITY.md +0 -0
  93. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-UAT.md +0 -0
  94. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-VERIFICATION.md +0 -0
  95. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-PLAN.md +0 -0
  96. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-SUMMARY.md +0 -0
  97. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-PLAN.md +0 -0
  98. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-SUMMARY.md +0 -0
  99. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-CONTEXT.md +0 -0
  100. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-DISCUSSION-LOG.md +0 -0
  101. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-PATTERNS.md +0 -0
  102. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-RESEARCH.md +0 -0
  103. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-REVIEW.md +0 -0
  104. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-SECURITY.md +0 -0
  105. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-VERIFICATION.md +0 -0
  106. {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
  107. {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
  108. {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
  109. {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
  110. {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
  111. {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
  112. {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
  113. {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
  114. {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
  115. {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
  116. {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
  117. {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
  118. {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
  119. {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
  120. {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
  121. {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
  122. {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
  123. {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
  124. {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
  125. {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
  126. {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
  127. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-CONTEXT.md +0 -0
  128. {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
  129. {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
  130. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-PATTERNS.md +0 -0
  131. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-RESEARCH.md +0 -0
  132. {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
  133. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW.md +0 -0
  134. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-VERIFICATION.md +0 -0
  135. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/.gitkeep +0 -0
  136. {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
  137. {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
  138. {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
  139. {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
  140. {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
  141. {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
  142. {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
  143. {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
  144. {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
  145. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/.gitkeep +0 -0
  146. {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
  147. {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
  148. {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
  149. {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
  150. {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
  151. {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
  152. {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
  153. {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
  154. {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
  155. {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
  156. {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
  157. {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
  158. {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
  159. {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
  160. {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
  161. {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
  162. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/COVERAGE.md +0 -0
  163. {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
  164. {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
  165. {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
  166. {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
  167. {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
  168. {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
  169. {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
  170. {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
  171. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v2.0-quick/README.md +0 -0
  172. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-REQUIREMENTS.md +0 -0
  173. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-ROADMAP.md +0 -0
  174. {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
  175. {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
  176. {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
  177. {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
  178. {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
  179. {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
  180. {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
  181. {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
  182. {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
  183. {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
  184. {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
  185. {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
  186. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-CONTEXT.md +0 -0
  187. {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
  188. {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
  189. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-PATTERNS.md +0 -0
  190. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-REVIEW.md +0 -0
  191. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-VERIFICATION.md +0 -0
  192. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-REQUIREMENTS.md +0 -0
  193. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-ROADMAP.md +0 -0
  194. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-PLAN.md +0 -0
  195. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-SUMMARY.md +0 -0
  196. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-PLAN.md +0 -0
  197. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-SUMMARY.md +0 -0
  198. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-PLAN.md +0 -0
  199. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-SUMMARY.md +0 -0
  200. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-CONTEXT.md +0 -0
  201. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DECISION-RECORD.md +0 -0
  202. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DISCUSSION-LOG.md +0 -0
  203. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-LIVE-FINDINGS.md +0 -0
  204. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/COVERAGE.md +0 -0
  205. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-PLAN.md +0 -0
  206. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-SUMMARY.md +0 -0
  207. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-PLAN.md +0 -0
  208. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-SUMMARY.md +0 -0
  209. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-CONTEXT.md +0 -0
  210. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-DISCUSS-CHECKPOINT.json +0 -0
  211. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-VERIFICATION.md +0 -0
  212. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/17-google-link-album-selection/COVERAGE.md +0 -0
  213. {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
  214. {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
  215. {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
  216. {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
  217. {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
  218. {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
  219. {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
  220. {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
  221. {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
  222. {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
  223. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-PLAN.md +0 -0
  224. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-SUMMARY.md +0 -0
  225. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-PLAN.md +0 -0
  226. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-SUMMARY.md +0 -0
  227. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-CONTEXT.md +0 -0
  228. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-RESEARCH.md +0 -0
  229. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/milestones/v4.0-phases/19-debt-closeout/19-VERIFICATION.md +0 -0
  230. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/20-pushframe-rename-migration-repo-switch/20-SUMMARY.md +0 -0
  231. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/21-debian-package-apt-repo/21-01-PLAN.md +0 -0
  232. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/21-debian-package-apt-repo/21-01-SUMMARY.md +0 -0
  233. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/21-debian-package-apt-repo/21-02-PLAN.md +0 -0
  234. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/21-debian-package-apt-repo/21-02-SUMMARY.md +0 -0
  235. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/21-debian-package-apt-repo/21-CONTEXT.md +0 -0
  236. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/21-debian-package-apt-repo/21-RESEARCH.md +0 -0
  237. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/21-debian-package-apt-repo/21-VERIFICATION.md +0 -0
  238. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/22-01-PLAN.md +0 -0
  239. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/22-01-SUMMARY.md +0 -0
  240. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/22-02-PLAN.md +0 -0
  241. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/22-02-SUMMARY.md +0 -0
  242. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/22-CONTEXT.md +0 -0
  243. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/22-RESEARCH.md +0 -0
  244. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/22-VERIFICATION.md +0 -0
  245. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/phases/22-pypi-release-engineering/OPERATOR-STEPS.md +0 -0
  246. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/ALBUM-ACCESS-V4-ADDENDUM.md +0 -0
  247. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/ALBUM-ACCESS.md +0 -0
  248. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/ARCHITECTURE.md +0 -0
  249. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/BROWSER-AUTOMATION.md +0 -0
  250. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/FEATURES.md +0 -0
  251. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/PITFALLS.md +0 -0
  252. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/STACK.md +0 -0
  253. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/research/SUMMARY.md +0 -0
  254. {pushframe-5.0.0 → pushframe-5.0.2}/.planning/state.json +0 -0
  255. {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
  256. {pushframe-5.0.0 → pushframe-5.0.2}/.python-version +0 -0
  257. {pushframe-5.0.0 → pushframe-5.0.2}/CLAUDE.md +0 -0
  258. {pushframe-5.0.0 → pushframe-5.0.2}/LICENSE +0 -0
  259. {pushframe-5.0.0 → pushframe-5.0.2}/README.md +0 -0
  260. {pushframe-5.0.0 → pushframe-5.0.2}/VERIFICATION-REPORT.md +0 -0
  261. {pushframe-5.0.0 → pushframe-5.0.2}/docs/CLI.md +0 -0
  262. {pushframe-5.0.0 → pushframe-5.0.2}/docs/agents/domain.md +0 -0
  263. {pushframe-5.0.0 → pushframe-5.0.2}/docs/agents/issue-tracker.md +0 -0
  264. {pushframe-5.0.0 → pushframe-5.0.2}/docs/agents/triage-labels.md +0 -0
  265. {pushframe-5.0.0 → pushframe-5.0.2}/docs/superpowers/specs/2026-07-09-write-rate-limiter-design.md +0 -0
  266. {pushframe-5.0.0 → pushframe-5.0.2}/main.py +0 -0
  267. {pushframe-5.0.0 → pushframe-5.0.2}/packaging/control-template +0 -0
  268. {pushframe-5.0.0 → pushframe-5.0.2}/packaging/lintian-overrides +0 -0
  269. {pushframe-5.0.0 → pushframe-5.0.2}/packaging/postinst +0 -0
  270. {pushframe-5.0.0 → pushframe-5.0.2}/packaging/pushframe.copyright +0 -0
  271. {pushframe-5.0.0 → pushframe-5.0.2}/probes/.gitignore +0 -0
  272. {pushframe-5.0.0 → pushframe-5.0.2}/probes/README.md +0 -0
  273. {pushframe-5.0.0 → pushframe-5.0.2}/probes/browser_bootstrap.py +0 -0
  274. {pushframe-5.0.0 → pushframe-5.0.2}/probes/common.py +0 -0
  275. {pushframe-5.0.0 → pushframe-5.0.2}/probes/cookie_vault.py +0 -0
  276. {pushframe-5.0.0 → pushframe-5.0.2}/probes/fidelity_check.py +0 -0
  277. {pushframe-5.0.0 → pushframe-5.0.2}/probes/rpc_capture.py +0 -0
  278. {pushframe-5.0.0 → pushframe-5.0.2}/probes/shared_link_probe.py +0 -0
  279. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/__init__.py +0 -0
  280. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/accountApi.py +0 -0
  281. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/activityApi.py +0 -0
  282. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/assetApi.py +0 -0
  283. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/baseApi.py +0 -0
  284. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/frameApi.py +0 -0
  285. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/notificationApi.py +0 -0
  286. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/peopleApi.py +0 -0
  287. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/api/playlistApi.py +0 -0
  288. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/aura.py +0 -0
  289. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/aws/__init__.py +0 -0
  290. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/aws/awsclient.py +0 -0
  291. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/aws/s3client.py +0 -0
  292. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/aws/sqsclient.py +0 -0
  293. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/cache.py +0 -0
  294. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/cli.py +0 -0
  295. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/client.py +0 -0
  296. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/exif.py +0 -0
  297. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/export.py +0 -0
  298. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/__init__.py +0 -0
  299. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/bootstrap.py +0 -0
  300. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/cache.py +0 -0
  301. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/client.py +0 -0
  302. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/enumerate.py +0 -0
  303. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/manifest.py +0 -0
  304. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/parsers.py +0 -0
  305. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/redaction.py +0 -0
  306. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/google/vault.py +0 -0
  307. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/gsync.py +0 -0
  308. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/migration.py +0 -0
  309. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/models/__init__.py +0 -0
  310. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/models/activity.py +0 -0
  311. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/models/asset.py +0 -0
  312. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/models/frame.py +0 -0
  313. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/models/meta.py +0 -0
  314. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/models/person.py +0 -0
  315. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/models/user.py +0 -0
  316. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/ratelimit.py +0 -0
  317. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/reconcile.py +0 -0
  318. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/sync.py +0 -0
  319. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/utils/dt.py +0 -0
  320. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/utils/io.py +0 -0
  321. {pushframe-5.0.0 → pushframe-5.0.2}/pushframe/utils/settings.py +0 -0
  322. {pushframe-5.0.0 → pushframe-5.0.2}/scripts/build-deb.sh +0 -0
  323. {pushframe-5.0.0 → pushframe-5.0.2}/scripts/make-apt-key.sh +0 -0
  324. {pushframe-5.0.0 → pushframe-5.0.2}/scripts/test-apt-journey-container.sh +0 -0
  325. {pushframe-5.0.0 → pushframe-5.0.2}/scripts/test-deb-container.sh +0 -0
  326. {pushframe-5.0.0 → pushframe-5.0.2}/tests/conftest.py +0 -0
  327. {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/asset_unprocessed.json +0 -0
  328. {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/assets_page1.json +0 -0
  329. {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/assets_page2.json +0 -0
  330. {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/assets_placeholders.json +0 -0
  331. {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/error_envelope.json +0 -0
  332. {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/frame_detail.json +0 -0
  333. {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/frames.json +0 -0
  334. {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/google_rpc_snackc_page.json +0 -0
  335. {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/google_share_page_sample.html +0 -0
  336. {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/login.json +0 -0
  337. {pushframe-5.0.0 → pushframe-5.0.2}/tests/fixtures/probe_af_initdata_sample.html +0 -0
  338. {pushframe-5.0.0 → pushframe-5.0.2}/tests/offline.py +0 -0
  339. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_asset_partial.py +0 -0
  340. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_asset_unprocessed.py +0 -0
  341. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_aura_logger.py +0 -0
  342. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_batch_update_contract.py +0 -0
  343. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_apply.py +0 -0
  344. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_google_album.py +0 -0
  345. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_google_link.py +0 -0
  346. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_inspect.py +0 -0
  347. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_push_budget_geo.py +0 -0
  348. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_reconcile.py +0 -0
  349. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_status.py +0 -0
  350. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_status_google.py +0 -0
  351. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_cli_sync.py +0 -0
  352. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_client_rate_limit.py +0 -0
  353. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_execute_plan.py +0 -0
  354. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_execute_plan_budget_geo.py +0 -0
  355. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_fixtures_validity.py +0 -0
  356. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_google_cache.py +0 -0
  357. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_google_client.py +0 -0
  358. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_google_enumerate.py +0 -0
  359. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_google_manifest.py +0 -0
  360. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_google_parsers.py +0 -0
  361. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_google_vault.py +0 -0
  362. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_gsync_engine.py +0 -0
  363. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_gsync_execute.py +0 -0
  364. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_imports.py +0 -0
  365. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_migration.py +0 -0
  366. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_offline_read_path.py +0 -0
  367. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_prep_upload_formats.py +0 -0
  368. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_probe_browser.py +0 -0
  369. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_probe_shared_link.py +0 -0
  370. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_ratelimit.py +0 -0
  371. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_read_path.py +0 -0
  372. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_reconcile.py +0 -0
  373. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_retry_401.py +0 -0
  374. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_settings_budget.py +0 -0
  375. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_sync_engine.py +0 -0
  376. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_write_endpoints_failloud.py +0 -0
  377. {pushframe-5.0.0 → pushframe-5.0.2}/tests/test_write_formats_live.py +0 -0
  378. {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
- FP=$(grep -m1 -oE '[A-F0-9]{40}' "$KEYDIR/signing-key.asc")
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
@@ -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
+ [![tests](https://github.com/coredmp95/pushframe/actions/workflows/tests.yml/badge.svg)](https://github.com/coredmp95/pushframe/actions/workflows/tests.yml)
63
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
64
+ [![Python 3.14](https://img.shields.io/badge/python-3.14-blue.svg)](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.