pushframe 5.1.2__tar.gz → 5.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (415) hide show
  1. pushframe-5.1.4/.planning/debug/status-crash-401-trip.md +61 -0
  2. {pushframe-5.1.2 → pushframe-5.1.4}/CHANGELOG.md +59 -1
  3. {pushframe-5.1.2 → pushframe-5.1.4}/PKG-INFO +1 -1
  4. {pushframe-5.1.2 → pushframe-5.1.4}/docs/CLI.md +72 -16
  5. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/__init__.py +1 -1
  6. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/cli.py +52 -6
  7. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/bootstrap.py +15 -4
  8. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/gsync.py +6 -1
  9. pushframe-5.1.4/pushframe/session.py +306 -0
  10. {pushframe-5.1.2 → pushframe-5.1.4}/pyproject.toml +1 -1
  11. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_apply.py +22 -0
  12. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_google_link.py +31 -0
  13. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_reconcile.py +80 -0
  14. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_status.py +128 -0
  15. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_config_wizard.py +77 -0
  16. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_session.py +50 -0
  17. pushframe-5.1.4/tests/test_token_refresh_extension.py +157 -0
  18. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_version.py +1 -1
  19. {pushframe-5.1.2 → pushframe-5.1.4}/uv.lock +1 -1
  20. pushframe-5.1.2/pushframe/session.py +0 -144
  21. {pushframe-5.1.2 → pushframe-5.1.4}/.env.sample +0 -0
  22. {pushframe-5.1.2 → pushframe-5.1.4}/.freebuff/project-id +0 -0
  23. {pushframe-5.1.2 → pushframe-5.1.4}/.github/workflows/release.yml +0 -0
  24. {pushframe-5.1.2 → pushframe-5.1.4}/.github/workflows/tests.yml +0 -0
  25. {pushframe-5.1.2 → pushframe-5.1.4}/.gitignore +0 -0
  26. {pushframe-5.1.2 → pushframe-5.1.4}/.gsd/dispatch-isolation-sentinel.json +0 -0
  27. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/MILESTONES.md +0 -0
  28. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/PROJECT.md +0 -0
  29. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/REQUIREMENTS.md +0 -0
  30. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/RETROSPECTIVE.md +0 -0
  31. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/ROADMAP.md +0 -0
  32. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/STATE.md +0 -0
  33. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/WINDOWS.md +0 -0
  34. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/codebase/ARCHITECTURE.md +0 -0
  35. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/codebase/CONCERNS.md +0 -0
  36. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/codebase/CONVENTIONS.md +0 -0
  37. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/codebase/INTEGRATIONS.md +0 -0
  38. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/codebase/STACK.md +0 -0
  39. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/codebase/STRUCTURE.md +0 -0
  40. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/codebase/TESTING.md +0 -0
  41. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/config.json +0 -0
  42. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/debug/knowledge-base.md +0 -0
  43. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/debug/login-475-null-creds.md +0 -0
  44. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/debug/resolved/cli-status-verbose-logging.md +0 -0
  45. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/debug/resolved/inspect-asset-null-fields.md +0 -0
  46. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/debug/resolved/select-asset-401-unauthorized.md +0 -0
  47. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-REQUIREMENTS.md +0 -0
  48. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-ROADMAP.md +0 -0
  49. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-PLAN.md +0 -0
  50. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-SUMMARY.md +0 -0
  51. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-PLAN.md +0 -0
  52. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-SUMMARY.md +0 -0
  53. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-CONTEXT.md +0 -0
  54. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-DISCUSSION-LOG.md +0 -0
  55. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-PATTERNS.md +0 -0
  56. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-RESEARCH.md +0 -0
  57. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-REVIEW.md +0 -0
  58. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-VERIFICATION.md +0 -0
  59. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-PLAN.md +0 -0
  60. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-SUMMARY.md +0 -0
  61. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-PLAN.md +0 -0
  62. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-SUMMARY.md +0 -0
  63. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-CONTEXT.md +0 -0
  64. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-DISCUSSION-LOG.md +0 -0
  65. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-LIVE-EVIDENCE.md +0 -0
  66. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-PATTERNS.md +0 -0
  67. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-RESEARCH.md +0 -0
  68. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-REVIEW.md +0 -0
  69. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-VERIFICATION.md +0 -0
  70. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-PLAN.md +0 -0
  71. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-SUMMARY.md +0 -0
  72. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-CONTEXT.md +0 -0
  73. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-DISCUSSION-LOG.md +0 -0
  74. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-PATTERNS.md +0 -0
  75. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-RESEARCH.md +0 -0
  76. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-REVIEW.md +0 -0
  77. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-VERIFICATION.md +0 -0
  78. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-ROADMAP.md +0 -0
  79. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/.gitkeep +0 -0
  80. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-01-PLAN.md +0 -0
  81. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-01-SUMMARY.md +0 -0
  82. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-02-PLAN.md +0 -0
  83. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-02-SUMMARY.md +0 -0
  84. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-03-PLAN.md +0 -0
  85. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-03-SUMMARY.md +0 -0
  86. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-PATTERNS.md +0 -0
  87. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-RESEARCH.md +0 -0
  88. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-REVIEW.md +0 -0
  89. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-SECURITY.md +0 -0
  90. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-UAT.md +0 -0
  91. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-VERIFICATION.md +0 -0
  92. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-REQUIREMENTS.md +0 -0
  93. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-ROADMAP.md +0 -0
  94. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-PLAN.md +0 -0
  95. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-SUMMARY.md +0 -0
  96. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-PLAN.md +0 -0
  97. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-SUMMARY.md +0 -0
  98. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-CONTEXT.md +0 -0
  99. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-DISCUSSION-LOG.md +0 -0
  100. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-PATTERNS.md +0 -0
  101. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-REVIEW.md +0 -0
  102. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-SECURITY.md +0 -0
  103. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-UAT.md +0 -0
  104. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-VERIFICATION.md +0 -0
  105. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-PLAN.md +0 -0
  106. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-SUMMARY.md +0 -0
  107. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-PLAN.md +0 -0
  108. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-SUMMARY.md +0 -0
  109. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-CONTEXT.md +0 -0
  110. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-DISCUSSION-LOG.md +0 -0
  111. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-PATTERNS.md +0 -0
  112. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-RESEARCH.md +0 -0
  113. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-REVIEW.md +0 -0
  114. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-SECURITY.md +0 -0
  115. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-VERIFICATION.md +0 -0
  116. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-01-PLAN.md +0 -0
  117. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-01-SUMMARY.md +0 -0
  118. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-02-PLAN.md +0 -0
  119. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-02-SUMMARY.md +0 -0
  120. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-03-PLAN.md +0 -0
  121. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-03-SUMMARY.md +0 -0
  122. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-CONTEXT.md +0 -0
  123. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-DISCUSSION-LOG.md +0 -0
  124. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-PATTERNS.md +0 -0
  125. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-REVIEW-FIX.md +0 -0
  126. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-REVIEW.md +0 -0
  127. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-SECURITY.md +0 -0
  128. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-VERIFICATION.md +0 -0
  129. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-PLAN.md +0 -0
  130. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-SUMMARY.md +0 -0
  131. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-PLAN.md +0 -0
  132. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-SUMMARY.md +0 -0
  133. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-PLAN.md +0 -0
  134. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-SUMMARY.md +0 -0
  135. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-PLAN.md +0 -0
  136. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-SUMMARY.md +0 -0
  137. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-CONTEXT.md +0 -0
  138. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-DISCUSSION-LOG.md +0 -0
  139. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-LIVE-FINDINGS.md +0 -0
  140. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-PATTERNS.md +0 -0
  141. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-RESEARCH.md +0 -0
  142. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW-FIX.md +0 -0
  143. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW.md +0 -0
  144. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-VERIFICATION.md +0 -0
  145. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/.gitkeep +0 -0
  146. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-01-PLAN.md +0 -0
  147. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-01-SUMMARY.md +0 -0
  148. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-02-PLAN.md +0 -0
  149. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-02-SUMMARY.md +0 -0
  150. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-CONTEXT.md +0 -0
  151. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-RESEARCH.md +0 -0
  152. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-REVIEW-FIX.md +0 -0
  153. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-REVIEW.md +0 -0
  154. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-VERIFICATION.md +0 -0
  155. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/.gitkeep +0 -0
  156. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-01-PLAN.md +0 -0
  157. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-01-SUMMARY.md +0 -0
  158. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-02-PLAN.md +0 -0
  159. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-02-SUMMARY.md +0 -0
  160. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-03-PLAN.md +0 -0
  161. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-03-SUMMARY.md +0 -0
  162. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-04-PLAN.md +0 -0
  163. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-04-SUMMARY.md +0 -0
  164. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-CONTEXT.md +0 -0
  165. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-DISCUSSION-LOG.md +0 -0
  166. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-LIVE-FINDINGS.md +0 -0
  167. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-PATTERNS.md +0 -0
  168. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-RESEARCH.md +0 -0
  169. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-SECURITY.md +0 -0
  170. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-UAT.md +0 -0
  171. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-VERIFICATION.md +0 -0
  172. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/COVERAGE.md +0 -0
  173. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-quick/260630-qs8-make-main-py-load-a-local-env-file-at-st/260630-qs8-PLAN.md +0 -0
  174. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-quick/260630-qs8-make-main-py-load-a-local-env-file-at-st/260630-qs8-SUMMARY.md +0 -0
  175. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/260708-dt9-PLAN.md +0 -0
  176. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/260708-dt9-SUMMARY.md +0 -0
  177. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/deferred-items.md +0 -0
  178. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-PLAN.md +0 -0
  179. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-SUMMARY.md +0 -0
  180. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-VERIFICATION.md +0 -0
  181. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v2.0-quick/README.md +0 -0
  182. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-REQUIREMENTS.md +0 -0
  183. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-ROADMAP.md +0 -0
  184. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-PLAN.md +0 -0
  185. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-SUMMARY.md +0 -0
  186. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-PLAN.md +0 -0
  187. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-SUMMARY.md +0 -0
  188. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-PLAN.md +0 -0
  189. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-SUMMARY.md +0 -0
  190. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-PLAN.md +0 -0
  191. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-SUMMARY.md +0 -0
  192. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-PLAN.md +0 -0
  193. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-SUMMARY.md +0 -0
  194. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-PLAN.md +0 -0
  195. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-SUMMARY.md +0 -0
  196. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-CONTEXT.md +0 -0
  197. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-DISCUSSION-LOG.md +0 -0
  198. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-LIVE-FINDINGS.md +0 -0
  199. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-PATTERNS.md +0 -0
  200. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-REVIEW.md +0 -0
  201. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-VERIFICATION.md +0 -0
  202. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-REQUIREMENTS.md +0 -0
  203. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-ROADMAP.md +0 -0
  204. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-PLAN.md +0 -0
  205. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-SUMMARY.md +0 -0
  206. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-PLAN.md +0 -0
  207. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-SUMMARY.md +0 -0
  208. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-PLAN.md +0 -0
  209. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-SUMMARY.md +0 -0
  210. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-CONTEXT.md +0 -0
  211. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DECISION-RECORD.md +0 -0
  212. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DISCUSSION-LOG.md +0 -0
  213. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-LIVE-FINDINGS.md +0 -0
  214. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/COVERAGE.md +0 -0
  215. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-PLAN.md +0 -0
  216. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-SUMMARY.md +0 -0
  217. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-PLAN.md +0 -0
  218. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-SUMMARY.md +0 -0
  219. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-CONTEXT.md +0 -0
  220. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-DISCUSS-CHECKPOINT.json +0 -0
  221. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-VERIFICATION.md +0 -0
  222. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/17-google-link-album-selection/COVERAGE.md +0 -0
  223. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-01-PLAN.md +0 -0
  224. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-01-SUMMARY.md +0 -0
  225. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-02-PLAN.md +0 -0
  226. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-02-SUMMARY.md +0 -0
  227. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-03-PLAN.md +0 -0
  228. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-03-SUMMARY.md +0 -0
  229. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-CONTEXT.md +0 -0
  230. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-RESEARCH.md +0 -0
  231. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-UAT.md +0 -0
  232. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-VERIFICATION.md +0 -0
  233. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-PLAN.md +0 -0
  234. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-SUMMARY.md +0 -0
  235. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-PLAN.md +0 -0
  236. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-SUMMARY.md +0 -0
  237. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/19-debt-closeout/19-CONTEXT.md +0 -0
  238. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/19-debt-closeout/19-RESEARCH.md +0 -0
  239. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v4.0-phases/19-debt-closeout/19-VERIFICATION.md +0 -0
  240. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-REQUIREMENTS.md +0 -0
  241. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-ROADMAP.md +0 -0
  242. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/20-pushframe-rename-migration-repo-switch/20-SUMMARY.md +0 -0
  243. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-PLAN.md +0 -0
  244. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-SUMMARY.md +0 -0
  245. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-PLAN.md +0 -0
  246. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-SUMMARY.md +0 -0
  247. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-CONTEXT.md +0 -0
  248. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-RESEARCH.md +0 -0
  249. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-VERIFICATION.md +0 -0
  250. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-PLAN.md +0 -0
  251. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-SUMMARY.md +0 -0
  252. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-PLAN.md +0 -0
  253. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-SUMMARY.md +0 -0
  254. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-CONTEXT.md +0 -0
  255. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-RESEARCH.md +0 -0
  256. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-VERIFICATION.md +0 -0
  257. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/OPERATOR-STEPS.md +0 -0
  258. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/23-config-wizard-precedence/23-01-PLAN.md +0 -0
  259. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/23-config-wizard-precedence/23-01-SUMMARY.md +0 -0
  260. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/23-config-wizard-precedence/23-CONTEXT.md +0 -0
  261. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/23-config-wizard-precedence/23-RESEARCH.md +0 -0
  262. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/23-config-wizard-precedence/23-VERIFICATION.md +0 -0
  263. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-01-PLAN.md +0 -0
  264. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-01-SUMMARY.md +0 -0
  265. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-CONTEXT.md +0 -0
  266. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-RESEARCH.md +0 -0
  267. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-VERIFICATION.md +0 -0
  268. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-01-PLAN.md +0 -0
  269. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-01-SUMMARY.md +0 -0
  270. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-CONTEXT.md +0 -0
  271. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-RESEARCH.md +0 -0
  272. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-VERIFICATION.md +0 -0
  273. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/research/ALBUM-ACCESS-V4-ADDENDUM.md +0 -0
  274. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/research/ALBUM-ACCESS.md +0 -0
  275. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/research/ARCHITECTURE.md +0 -0
  276. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/research/BROWSER-AUTOMATION.md +0 -0
  277. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/research/FEATURES.md +0 -0
  278. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/research/PITFALLS.md +0 -0
  279. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/research/STACK.md +0 -0
  280. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/research/SUMMARY.md +0 -0
  281. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/state.json +0 -0
  282. {pushframe-5.1.2 → pushframe-5.1.4}/.planning/todos/completed/2026-07-06-promote-debug-flag-to-a-global-cli-convention.md +0 -0
  283. {pushframe-5.1.2 → pushframe-5.1.4}/.python-version +0 -0
  284. {pushframe-5.1.2 → pushframe-5.1.4}/CLAUDE.md +0 -0
  285. {pushframe-5.1.2 → pushframe-5.1.4}/LICENSE +0 -0
  286. {pushframe-5.1.2 → pushframe-5.1.4}/README.md +0 -0
  287. {pushframe-5.1.2 → pushframe-5.1.4}/SECURITY.md +0 -0
  288. {pushframe-5.1.2 → pushframe-5.1.4}/VERIFICATION-REPORT.md +0 -0
  289. {pushframe-5.1.2 → pushframe-5.1.4}/docs/agents/domain.md +0 -0
  290. {pushframe-5.1.2 → pushframe-5.1.4}/docs/agents/issue-tracker.md +0 -0
  291. {pushframe-5.1.2 → pushframe-5.1.4}/docs/agents/triage-labels.md +0 -0
  292. {pushframe-5.1.2 → pushframe-5.1.4}/docs/superpowers/specs/2026-07-09-write-rate-limiter-design.md +0 -0
  293. {pushframe-5.1.2 → pushframe-5.1.4}/main.py +0 -0
  294. {pushframe-5.1.2 → pushframe-5.1.4}/packaging/control-template +0 -0
  295. {pushframe-5.1.2 → pushframe-5.1.4}/packaging/lintian-overrides +0 -0
  296. {pushframe-5.1.2 → pushframe-5.1.4}/packaging/postinst +0 -0
  297. {pushframe-5.1.2 → pushframe-5.1.4}/packaging/pushframe.copyright +0 -0
  298. {pushframe-5.1.2 → pushframe-5.1.4}/probes/.gitignore +0 -0
  299. {pushframe-5.1.2 → pushframe-5.1.4}/probes/README.md +0 -0
  300. {pushframe-5.1.2 → pushframe-5.1.4}/probes/browser_bootstrap.py +0 -0
  301. {pushframe-5.1.2 → pushframe-5.1.4}/probes/common.py +0 -0
  302. {pushframe-5.1.2 → pushframe-5.1.4}/probes/cookie_vault.py +0 -0
  303. {pushframe-5.1.2 → pushframe-5.1.4}/probes/fidelity_check.py +0 -0
  304. {pushframe-5.1.2 → pushframe-5.1.4}/probes/rpc_capture.py +0 -0
  305. {pushframe-5.1.2 → pushframe-5.1.4}/probes/shared_link_probe.py +0 -0
  306. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/__init__.py +0 -0
  307. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/accountApi.py +0 -0
  308. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/activityApi.py +0 -0
  309. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/assetApi.py +0 -0
  310. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/baseApi.py +0 -0
  311. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/frameApi.py +0 -0
  312. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/notificationApi.py +0 -0
  313. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/peopleApi.py +0 -0
  314. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/api/playlistApi.py +0 -0
  315. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/aura.py +0 -0
  316. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/aws/__init__.py +0 -0
  317. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/aws/awsclient.py +0 -0
  318. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/aws/s3client.py +0 -0
  319. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/aws/sqsclient.py +0 -0
  320. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/cache.py +0 -0
  321. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/client.py +0 -0
  322. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/config_store.py +0 -0
  323. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/doctor.py +0 -0
  324. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/exif.py +0 -0
  325. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/export.py +0 -0
  326. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/__init__.py +0 -0
  327. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/cache.py +0 -0
  328. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/client.py +0 -0
  329. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/enumerate.py +0 -0
  330. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/manifest.py +0 -0
  331. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/parsers.py +0 -0
  332. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/redaction.py +0 -0
  333. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/google/vault.py +0 -0
  334. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/migration.py +0 -0
  335. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/models/__init__.py +0 -0
  336. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/models/activity.py +0 -0
  337. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/models/asset.py +0 -0
  338. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/models/frame.py +0 -0
  339. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/models/meta.py +0 -0
  340. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/models/person.py +0 -0
  341. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/models/user.py +0 -0
  342. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/pairs.py +0 -0
  343. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/preflight.py +0 -0
  344. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/ratelimit.py +0 -0
  345. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/reconcile.py +0 -0
  346. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/schedule.py +0 -0
  347. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/sync.py +0 -0
  348. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/utils/dt.py +0 -0
  349. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/utils/io.py +0 -0
  350. {pushframe-5.1.2 → pushframe-5.1.4}/pushframe/utils/settings.py +0 -0
  351. {pushframe-5.1.2 → pushframe-5.1.4}/scripts/build-deb.sh +0 -0
  352. {pushframe-5.1.2 → pushframe-5.1.4}/scripts/make-apt-key.sh +0 -0
  353. {pushframe-5.1.2 → pushframe-5.1.4}/scripts/publish-apt-repo.sh +0 -0
  354. {pushframe-5.1.2 → pushframe-5.1.4}/scripts/test-apt-journey-container.sh +0 -0
  355. {pushframe-5.1.2 → pushframe-5.1.4}/scripts/test-config-journey-container.sh +0 -0
  356. {pushframe-5.1.2 → pushframe-5.1.4}/scripts/test-deb-container.sh +0 -0
  357. {pushframe-5.1.2 → pushframe-5.1.4}/scripts/test-install-journey-container.sh +0 -0
  358. {pushframe-5.1.2 → pushframe-5.1.4}/scripts/test-multipair-journey-container.sh +0 -0
  359. {pushframe-5.1.2 → pushframe-5.1.4}/tests/conftest.py +0 -0
  360. {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/asset_unprocessed.json +0 -0
  361. {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/assets_page1.json +0 -0
  362. {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/assets_page2.json +0 -0
  363. {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/assets_placeholders.json +0 -0
  364. {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/error_envelope.json +0 -0
  365. {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/frame_detail.json +0 -0
  366. {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/frames.json +0 -0
  367. {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/google_rpc_snackc_page.json +0 -0
  368. {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/google_share_page_sample.html +0 -0
  369. {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/login.json +0 -0
  370. {pushframe-5.1.2 → pushframe-5.1.4}/tests/fixtures/probe_af_initdata_sample.html +0 -0
  371. {pushframe-5.1.2 → pushframe-5.1.4}/tests/offline.py +0 -0
  372. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_asset_partial.py +0 -0
  373. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_asset_unprocessed.py +0 -0
  374. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_aura_logger.py +0 -0
  375. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_batch_update_contract.py +0 -0
  376. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_google_album.py +0 -0
  377. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_inspect.py +0 -0
  378. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_push_budget_geo.py +0 -0
  379. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_status_google.py +0 -0
  380. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_cli_sync.py +0 -0
  381. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_client_rate_limit.py +0 -0
  382. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_doctor.py +0 -0
  383. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_execute_plan.py +0 -0
  384. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_execute_plan_budget_geo.py +0 -0
  385. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_fixtures_validity.py +0 -0
  386. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_google_cache.py +0 -0
  387. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_google_client.py +0 -0
  388. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_google_enumerate.py +0 -0
  389. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_google_manifest.py +0 -0
  390. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_google_parsers.py +0 -0
  391. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_google_vault.py +0 -0
  392. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_gsync_all.py +0 -0
  393. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_gsync_engine.py +0 -0
  394. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_gsync_execute.py +0 -0
  395. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_imports.py +0 -0
  396. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_logout.py +0 -0
  397. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_migration.py +0 -0
  398. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_network_guard.py +0 -0
  399. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_offline_read_path.py +0 -0
  400. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_pairs.py +0 -0
  401. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_preflight_sweep.py +0 -0
  402. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_prep_upload_formats.py +0 -0
  403. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_probe_browser.py +0 -0
  404. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_probe_shared_link.py +0 -0
  405. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_ratelimit.py +0 -0
  406. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_read_path.py +0 -0
  407. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_reconcile.py +0 -0
  408. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_retry_401.py +0 -0
  409. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_schedule.py +0 -0
  410. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_settings_budget.py +0 -0
  411. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_settings_resolution.py +0 -0
  412. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_sync_engine.py +0 -0
  413. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_write_endpoints_failloud.py +0 -0
  414. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_write_formats_live.py +0 -0
  415. {pushframe-5.1.2 → pushframe-5.1.4}/tests/test_write_throttling.py +0 -0
@@ -0,0 +1,61 @@
1
+ ---
2
+ slug: status-crash-401-trip
3
+ status: resolved
4
+ created: 2026-09-30T16:20:00+02:00
5
+ updated: 2026-09-30T16:45:00+02:00
6
+ trigger: pushframe status crashes with a raw httpx traceback when the account's anti-abuse trip is armed (401 logout:true on frames.json)
7
+ ---
8
+
9
+ # Debug: status crashes with raw traceback on tripped-account 401
10
+
11
+ ## Symptoms
12
+
13
+ - expected: `pushframe status` resumes the stored token session and either lists frames or fails NAMED with a remedy (PRF-02 contract: never a traceback).
14
+ - actual: uncaught `httpx.HTTPStatusError` — a 3-exception chain ending in a raw traceback.
15
+ - error: `401 Unauthorized for https://api.pushd.com/v5/frames.json — server body: {"error": true, "message": "Request Unauthenticated", "logout": true}`
16
+ - timeline: visible since the venus trip (2026-09-30); the client DOES log the body via loguru (`_raise_for_status_with_body:270`) but nothing classifies or catches it on this path.
17
+ - reproduction: `pushframe status` on an account whose trip is armed (venus, live 2026-09-30 16:15).
18
+
19
+ ## Scope decisions (user)
20
+
21
+ - Fix the status UX crash ONLY — the trip itself is not a pushframe bug (the 24h protocol is documented).
22
+ - Contract: classify the 401 body — trip-shaped (`logout:true` / "Request Unauthenticated") → anti-abuse verdict + wait remedy, exit non-zero; token-shaped → `pushframe config` remedy.
23
+
24
+ ## Current Focus
25
+
26
+ hypothesis: run_status has no handler around the frames read; the 401 trip classification lives only in the WRITE path (execute_plan) and doctor, so a READ-surface 401 escapes as a raw exception.
27
+ test: read run_status' try/except and the write-path classifier; confirm no read-path catch.
28
+ expecting: no except clause covering HTTPStatusError from get_frames in run_status.
29
+ next_action: gather initial evidence (run_status, classifier, PRF-02 sweep inventory).
30
+
31
+ ## Evidence
32
+
33
+ - 2026-09-30T16:20:00+02:00: user transcript (venus 16:15) — establish_session SUCCEEDED (resume is offline: headers only), the 401 came from frame_api.get_frames; loguru warning from client.py:270 printed the body, then the exception propagated to the REPL.
34
+ - 2026-09-30T16:35:00+02:00: DECISIVE user experiment — with PUSHFRAME_EMAIL/PASSWORD env set (fresh login each run), `pushframe status` SUCCEEDS: login OK, frames listed (Cadre de Fabrice). Same account, same minutes: token path 401, env path 200.
35
+ - 2026-09-30T16:35:00+02:00: the 14:30 login-475 has LIFTED by ~16:35 (~2h) — the user's env login succeeded with no 475. Lockouts on the login surface clear in hours, not 24h.
36
+
37
+ ## Eliminated
38
+
39
+ - hypothesis: the anti-abuse trip spread to READS (refuted 16:35) — a fresh login reads frames.json fine; the 401-on-reads with the trip-shaped body was actually a DEAD STORED TOKEN. The 14:30 wave-4 interpretation ("reads refused too") is void: that probe resumed a stale token and never reached a write.
40
+ - hypothesis: account permanently 475-locked (refuted 16:35) — login surface answers again after ~2h.
41
+
42
+ ## Current Focus (updated 16:35)
43
+
44
+ hypothesis: CONFIRMED for the crash — run_status has no handler around get_frames(); the 401 trip classification lives only in the write path (sync._is_write_trip_body) and doctor. On the READ surface, the logout:true body means STALE TOKEN first (env-login reads green); trip-on-reads only if it recurs on a fresh token.
45
+ test: wrap the frames read; classify trip-shaped body (remedy: refresh token via config; caveat: if it recurs on a fresh login, it is the trip — wait); RateLimitError → wait message; generic → named + config remedy.
46
+ expecting: status exits 1 named, never tracebacks, on all three shapes.
47
+ next_action: implement + tests, then suite.
48
+
49
+ ## Resolution
50
+
51
+ root_cause: run_status had no handler around the frames read — the 401 trip classification lived only in the write path (sync._is_write_trip_body) and doctor, so a read-surface 401 escaped as a raw 3-exception-chain traceback (PRF-02 violation on the one read command every operator runs first). Root cause of the USER'S alarm was different: the stored token was dead (venus resumptions), while the env path's fresh login read frames fine — proving the trip never reached reads and the 14:30 login-475 had lifted in ~2h.
52
+ fix: run_status wraps get_frames() — trip-shaped 401 body → named message with the refresh-token remedy AND the fresh-token-24h caveat; other 401 → token-rejected remedy; RateLimitError (475/429) → named WAIT (never `pushframe config`); generic → named; all exit 1, zero tracebacks. Tests: 3 new in test_cli_status.py (trip body, token body, 475) — suite 507 passed.
53
+ verification: tests/test_cli_status.py 10/10; full offline suite 507 passed; user's live repro shape covered by the trip-body test.
54
+ files_changed: pushframe/cli.py, tests/test_cli_status.py, CHANGELOG.md, .planning/debug/status-crash-401-trip.md
55
+ addendum (16:50): followed by the one-shot TTY refresh — run_status now offers ONE re-login on a refused stored token and continues in the same run; a refused FRESH login prints the 24h verdict and is never retried; non-TTY fails named. tests/test_cli_status.py 12/12, suite 509 passed. Updated contract test: fresh env creds + trip body = trip-on-reads verdict.
56
+
57
+ ## Lessons
58
+
59
+ - The wave-4 interpretation "reads refused too" is VOID: that probe resumed a stale token and never reached a write. Trip-on-reads remains UNPROVEN; the 24h protocol now has a concrete re-entry criterion (same body on a FRESH login).
60
+ - The env path logs in fresh EVERY run — during tripped/token-dead periods it is the honest comparison path, but it also means each env `status` costs a login call (do not loop it).
61
+ - Silence clock resets: last pushd contact moved to 16:15–16:3x (status 401 + the user's env login). Next single probe: tomorrow ≥ 16:30, `pushframe status` on the refreshed token.
@@ -12,7 +12,65 @@ every channel.
12
12
  `pushframe` is an unofficial community CLI for Aura Frames digital photo
13
13
  frames; it is not affiliated with Aura Frames Inc.
14
14
 
15
- ## [Unreleased]
15
+ ## [5.1.4] - 2026-09-30
16
+
17
+ ### Added
18
+
19
+ - **One-shot TTY token refresh on every frames-reading verb** — `status`,
20
+ `inspect`, `sync`, `push` and `google-sync` (venus, 2026-09-30): when
21
+ the stored token is refused (401) and stdin is a terminal, the command
22
+ offers ONE re-login right there — the new token is persisted and the
23
+ same run continues (venus proved a fresh login reads green while the
24
+ stored token was dead). All five verbs share one gate
25
+ (`session.frames_read_with_refresh`), so the behavior, the failure
26
+ shapes and the guardrails are byte-identical everywhere. Guardrails:
27
+ exactly one attempt per process — a refused FRESH login is never
28
+ retried and prints the 24-hour-silence verdict (that means the
29
+ anti-abuse trip reached the read surface); non-TTY/scheduled runs
30
+ never prompt, they fail named with the `pushframe config` remedy; the
31
+ env-credential path (fresh credentials every run) never prompts.
32
+ One test per verb proves the contract: 401 once → exactly one
33
+ wizard-seam login → re-read succeeds → token persisted.
34
+
35
+ ### Fixed
36
+
37
+ - **`pushframe status` no longer tracebacks on a refused frames read**
38
+ (venus, debug session status-crash-401-trip): a 401 with the trip's
39
+ `logout:true` body prints a named diagnosis — the remedy is
40
+ `pushframe config` (refresh the stored token; a fresh login read frames
41
+ fine minutes later, so on a READ that body means a dead token first),
42
+ with the 24h-silence protocol reserved for the case where the same
43
+ body recurs on a genuinely fresh login. A 475/429 on the read prints a
44
+ named WAIT (never a re-login suggestion). Every shape exits 1 named —
45
+ the PRF-02 no-traceback contract now holds on the token path's first
46
+ authenticated call.
47
+
48
+ ## [5.1.3] — 2026-09-30
49
+
50
+ ### Fixed
51
+
52
+ - **`pushframe config` — Enter now keeps the stored email, as the banner
53
+ always promised** (venus, 5.1.2): an empty answer on a configured
54
+ install aborted with "no email given — aborting" instead of keeping
55
+ it. With a stored token, Enter-Enter now finishes the wizard with ZERO
56
+ API calls (the anti-abuse rule: no gratuitous login); typing a new
57
+ email still re-logins, and a fresh install still aborts named on an
58
+ empty answer.
59
+ - **Prompt-contract audit — every interactive prompt checked against its
60
+ banner**, one behavior test per prompt. Two more promised-vs-real gaps
61
+ fixed alongside the wizard's: the session-path login prompt (expired
62
+ token refresh) showed NO banner and aborted the login on a bare Enter —
63
+ it now prints the same "current email — Enter keeps it" banner and
64
+ keeps it; and the two hard-delete confirmation gates only said "To
65
+ confirm, type the number…" — now "Verbatim to confirm —", because the
66
+ y/N reflex answer being silently rejected WAS the contract, just never
67
+ the wording. google-link's manual Enter prompt reworded to make the
68
+ AFTER-login gate explicit ("log into Google …, THEN press Enter"); no
69
+ behavior change there.
70
+ - **The wizard's default-frame question loops on a bad answer**: an
71
+ out-of-range or non-numeric entry used to be silently treated as a
72
+ skip — it now re-asks with `invalid choice, pick 1-N or Enter to skip`
73
+ until a valid number is picked or an explicit Enter skips.
16
74
 
17
75
  ## [5.1.2] — 2026-09-30
18
76
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pushframe
3
- Version: 5.1.2
3
+ Version: 5.1.4
4
4
  Summary: Unofficial community CLI for Aura Frames digital photo frames (sync a local folder or a Google Photos album; not affiliated with Aura Frames Inc.)
5
5
  Project-URL: Homepage, https://github.com/coredmp95/pushframe
6
6
  Project-URL: Repository, https://github.com/coredmp95/pushframe
@@ -108,14 +108,34 @@ tokens; safe to repeat; never run it in CI.
108
108
 
109
109
  ### When the anti-abuse layer refuses your writes
110
110
 
111
- Pushd's anti-abuse layer can refuse writes while reads and login keep
112
- working (proven venus, 2026-09). The refusal body says
113
- `"Request Unauthenticated"` — **that is the trip's disguise, not a token
114
- problem**. pushframe recognizes it and stops on the FIRST refusal (no
115
- retry, no re-login — both feed the trip). The drill:
111
+ Pushd's anti-abuse layer can refuse writes while reads keep working
112
+ (proven venus, 2026-09). It has two shapes — learn both, the disguised
113
+ one is the trap:
114
+
115
+ | Surface | Shape | Server body |
116
+ |---|---|---|
117
+ | `login.json` | **HTTP 475** — explicit lockout | "Aura API is rate-limiting or has locked out this account" |
118
+ | everything else — **token included** | **HTTP 401** — the trip in disguise | `{"error": true, "message": "Request Unauthenticated", "logout": true}` |
119
+
120
+ The 401 arrives on a FRESH token, seconds after a successful login: it
121
+ means "we refuse this client's writes (trip)", not "your token is bad".
122
+ The trip is **account-wide** — not scoped to the session, the endpoint
123
+ that provoked it, or the login that just succeeded — and it can spread
124
+ to more surfaces when provoked (venus: writes-only 401 first; after
125
+ probe activity, `login.json` itself went 475).
126
+
127
+ **Never re-login, never retry.** Every call that touches a tripped
128
+ surface re-arms the clock, and a fresh login bypasses nothing — the
129
+ trip is not session-scoped. Venus's counter-evidence: 8 doctor probes
130
+ spread over 7 hours, all 475, each one buying another wait — the probes
131
+ were the reason nothing cleared. pushframe already stops on the FIRST
132
+ trip-shaped refusal (`TripDetectedError` — no retry, no re-login); the
133
+ discipline is yours to keep outside the tool.
134
+
135
+ The drill:
116
136
 
117
137
  1. `pushframe doctor` before any sync (GO = the write surface is open NOW)
118
- 2. Trip detected → wait **60+ min** from the stop, then `pushframe doctor` again
138
+ 2. Trip detected → wait **60+ min** from the stop, then `pushframe doctor` again — **once**. A second tripped doctor is the signal to stop doctoring (every probe re-arms the clock) and escalate to the 24-hour protocol below.
119
139
  3. If doctor passes but a sync still trips immediately, lower the per-batch
120
140
  volume — the trip keys on batch size (a 1-item write may pass where a
121
141
  50-item chunk is refused):
@@ -123,6 +143,26 @@ retry, no re-login — both feed the trip). The drill:
123
143
  pushframe google-sync Cadre --frame "Cadre de Fabrice" --apply --yes --batch-size 10
124
144
  ```
125
145
 
146
+ #### The 24-hour probe protocol (escalated recovery)
147
+
148
+ When 60+ minutes does not clear the trip (venus, 2026-09-30: still
149
+ tripped after a ~5-hour silence), stop probing and switch to days:
150
+
151
+ 1. **Total silence.** No pushframe command of any kind against the
152
+ account — and nothing automated either: a timer or a loop probing
153
+ hourly keeps the account tripped indefinitely.
154
+ 2. **After 24h+ of silence, ONE probe — a read, never a doctor.**
155
+ `pushframe status` resumes the stored token session (no login call,
156
+ no write). Doctor is the WRONG first probe here: its write attempt
157
+ re-arms the clock even while it tells you the truth.
158
+ 3. **Probe green** (reads answer) → ONE `pushframe doctor` as the single
159
+ write probe. GO → run the sync with a lowered batch size (step 3
160
+ above). Probe trips → back to step 1; next probe in another 24h.
161
+
162
+ Never two probes in one 24h bucket. The wait has no measured shape —
163
+ what is measured is only that hours do not cut it, and every premature
164
+ call restarts it.
165
+
126
166
  **Client identity matters.** Pushd's anti-abuse layer also fingerprints
127
167
  the client itself: a years-stale `Aura/4.7.790` user agent and the
128
168
  all-zeros `0000000000000000` device identifier shared by every pushframe
@@ -196,12 +236,17 @@ the file is `~/.config/pushframe/config.json`, mode `0600`, written atomically.
196
236
 
197
237
  ### The wizard (`pushframe config`)
198
238
 
199
- Asks for your email, then your password (**hidden input**), then **verifies the
200
- login against the real Aura API before writing anything** — a failed login writes
201
- nothing. On success it stores the email plus the session `auth_token` (not the
202
- password; a stored token is silently refreshed at re-login), then offers the
203
- optional questions (country for the geo guard, budget tuning) with current
204
- effective values as defaults.
239
+ On a fresh install it asks for your email, then your password (**hidden
240
+ input**), then **verifies the login against the real Aura API before writing
241
+ anything** — a failed login writes nothing. On an install that already has an
242
+ email stored, **pressing Enter keeps the stored email** — the banner says so and
243
+ means it: with a stored session token, a second Enter reuses the token and the
244
+ wizard finishes WITHOUT any API call (the anti-abuse rule: no gratuitous
245
+ login); only typing a NEW email moves to the password question. On success it
246
+ stores the email plus the session `auth_token` (not the password; a stored
247
+ token is silently refreshed at re-login), then offers the optional questions
248
+ (country for the geo guard, budget tuning) with current effective values as
249
+ defaults.
205
250
 
206
251
  Refuses to run when stdin is not a terminal — scheduled jobs have nothing to
207
252
  interact with; feed them the environment or the config file instead.
@@ -237,10 +282,15 @@ it is the value a command would actually use).
237
282
 
238
283
  ```
239
284
  usage: pushframe status [-h]
240
- ```
241
-
242
- The first command to run. It checks your credentials are present, logs in, and lists your
243
- frames. Nothing is ever written.
285
+ ```The first command to run. With env credentials it logs in fresh; with a
286
+ stored token session it resumes it (no login call) and lists your frames.
287
+ Nothing is ever written except a session token: if the stored token is
288
+ refused (HTTP 401), an interactive run offers ONE re-login right there and
289
+ continues — a refused fresh login is never retried (that shape is the
290
+ anti-abuse trip on reads: 24-hour silence); a non-interactive run fails
291
+ named with the `pushframe config` remedy instead of prompting. `inspect`,
292
+ `sync`, `push` and `google-sync` read frames through the same gate, so
293
+ this contract holds on every frames-reading verb.
244
294
 
245
295
  ```bash
246
296
  uv run pushframe status
@@ -769,6 +819,12 @@ affects uploads, hides and deletes alike.
769
819
  **There is no automatic retry yet.** If an apply reports 401 failures, simply run it again.
770
820
  The operation is safe to repeat: uploads dedupe by md5, and hides are idempotent.
771
821
 
822
+ This covers the *transient* 401s — the no-special-body failures. If the
823
+ body says `"Request Unauthenticated"`, that is the anti-abuse trip, not
824
+ a transient failure: do NOT run it again, do NOT re-login — see [When
825
+ the anti-abuse layer refuses your
826
+ writes](#when-the-anti-abuse-layer-refuses-your-writes).
827
+
772
828
  ### A frame's asset count disagrees with its listing
773
829
 
774
830
  `inspect` can report e.g. `Assets: 172` while listing `154`. The frame's own counter and the
@@ -1,4 +1,4 @@
1
1
  # pushframe — unofficial community CLI for Aura Frames (not affiliated).
2
2
  # Version single source for the CLI surface (REL-02); pyproject keeps the
3
3
  # packaging metadata version and tests/test_version.py enforces lockstep.
4
- __version__ = "5.1.2"
4
+ __version__ = "5.1.4"
@@ -12,6 +12,8 @@ from tqdm import tqdm
12
12
  from pushframe.aura import Aura
13
13
  from pushframe.aws.s3client import S3Client
14
14
  from pushframe.aws.sqsclient import SQSClient
15
+ import httpx
16
+
15
17
  from pushframe.client import RateLimitError
16
18
  from pushframe.google.bootstrap import BootstrapError, PROFILE_ENV_VAR, default_bootstrap
17
19
  from pushframe.google.client import GoogleSession
@@ -385,6 +387,11 @@ def run_config(wizard_args=None, stdin_isatty: bool | None = None) -> int:
385
387
  else:
386
388
  print('configuring pushframe — credentials are login-tested now, stored after.')
387
389
  email = input('Aura email: ').strip()
390
+ if not email and existing.get('email'):
391
+ # The banner promised "Enter keeps it" — keep that promise. With a
392
+ # stored token this path finishes with ZERO API calls (anti-abuse:
393
+ # a gratuitous login is exactly what the trip punishes).
394
+ email = existing['email']
388
395
  if not email:
389
396
  print('no email given — aborting, nothing written.')
390
397
  return 1
@@ -415,7 +422,12 @@ def run_config(wizard_args=None, stdin_isatty: bool | None = None) -> int:
415
422
  for i, f in enumerate(frames, 1):
416
423
  print(f' {i}. {f["name"]}')
417
424
  choice = input('default frame number (Enter to skip): ').strip()
418
- if choice.isdigit() and 1 <= int(choice) <= len(frames):
425
+ # Prompt-contract audit fix: an out-of-range or non-numeric answer
426
+ # used to be silently treated as a skip — re-ask until a valid
427
+ # number or an explicit Enter.
428
+ while choice and not (choice.isdigit() and 1 <= int(choice) <= len(frames)):
429
+ choice = input(f'invalid choice, pick 1-{len(frames)} or Enter to skip: ').strip()
430
+ if choice:
419
431
  data['default_frame'] = frames[int(choice) - 1]['name']
420
432
  debug = input('enable debug logging by default? [y/N] ').strip().lower()
421
433
  if debug in ('y', 'yes'):
@@ -599,6 +611,7 @@ def run_status(aura=None, debug: bool = False, google_session=None) -> int:
599
611
  # and before login/get_frames (the HTTP calls that trigger them).
600
612
  _configure_cli_logging(debug)
601
613
 
614
+ stored = None # the token-refresh seam only exists on the stored path
602
615
  if session[0] == 'env':
603
616
  try:
604
617
  aura.login()
@@ -615,7 +628,18 @@ def run_status(aura=None, debug: bool = False, google_session=None) -> int:
615
628
  user_id=stored.get('user_id'))
616
629
  who = stored['email']
617
630
 
618
- frames = aura.frame_api.get_frames()
631
+ # The frames read is the FIRST authenticated call of the token path —
632
+ # its failure modes are operator-relevant and must never traceback
633
+ # (PRF-02). Key lesson of 2026-09-30 (debug/status-crash-401-trip): on
634
+ # a READ, the trip-shaped logout:true body means a STALE STORED TOKEN
635
+ # first — a fresh login read frames fine minutes later — so the remedy
636
+ # is ONE TTY re-login right here (venus: the env path proved a fresh
637
+ # login reads green); the 24h wait is only the answer if a FRESH token
638
+ # is refused too. Never more than ONE refresh attempt per run, never a
639
+ # prompt off-TTY (scheduled runs fail named instead).
640
+ frames = _read_frames_with_refresh(aura, who, stored)
641
+ if frames is None:
642
+ return 1 # named failure already printed by the helper
619
643
  print(f'Logged in as {who}')
620
644
  print(f'{len(frames)} frames:')
621
645
  for frame in frames:
@@ -629,6 +653,14 @@ def run_status(aura=None, debug: bool = False, google_session=None) -> int:
629
653
  return 0
630
654
 
631
655
 
656
+ def _read_frames_with_refresh(aura, who, stored):
657
+ """Thin wrapper over the shared gate (session.frames_read_with_refresh)
658
+ — status, inspect, sync/push and google-sync all read frames through
659
+ the same named-failure + one-shot-refresh contract."""
660
+ from pushframe.session import frames_read_with_refresh
661
+ return frames_read_with_refresh(aura, who, stored)
662
+
663
+
632
664
  def _google_status_section(google_session=None) -> list[str]:
633
665
  """Build the `Google:` section lines for `status` (LGS-03/D-02).
634
666
 
@@ -931,8 +963,15 @@ def run_inspect(frame_arg: str, aura=None, debug: bool = False) -> int:
931
963
  print(f'Login failed: {e}')
932
964
  return 1
933
965
 
966
+ from pushframe.session import auto_refresh_stored
967
+ stored = auto_refresh_stored()
968
+ frames = _read_frames_with_refresh(
969
+ aura, who=(stored or {}).get('email')
970
+ or os.getenv('PUSHFRAME_EMAIL') or os.getenv('AURA_EMAIL'),
971
+ stored=stored)
972
+ if frames is None:
973
+ return 1
934
974
  try:
935
- frames = aura.frame_api.get_frames()
936
975
  resolved = resolve_frame(frame_arg, frames)
937
976
 
938
977
  if resolved.status == 'ambiguous':
@@ -1073,7 +1112,7 @@ def run_reconcile(frame_arg: str, *, remove: bool = False, yes: bool = False, me
1073
1112
  count = len(result.stuck)
1074
1113
  print(f'IRREVERSIBLE: {count} row(s) will be permanently destroyed '
1075
1114
  f'account-wide via hard-delete. This cannot be undone.')
1076
- answer = input(f'To confirm, type the number of rows to hard-delete ({count}): ')
1115
+ answer = input(f'Verbatim to confirm — type the number of rows to hard-delete ({count}): ')
1077
1116
  if answer.strip() != str(count):
1078
1117
  print('Aborted.')
1079
1118
  return 0
@@ -1215,8 +1254,15 @@ def run_sync(dir_arg: str, frame_arg: str, apply: bool = False, yes: bool = Fals
1215
1254
  print(f'Login failed: {e}')
1216
1255
  return 1
1217
1256
 
1257
+ from pushframe.session import auto_refresh_stored
1258
+ stored = auto_refresh_stored()
1259
+ frames = _read_frames_with_refresh(
1260
+ aura, who=(stored or {}).get('email')
1261
+ or os.getenv('PUSHFRAME_EMAIL') or os.getenv('AURA_EMAIL'),
1262
+ stored=stored)
1263
+ if frames is None:
1264
+ return 1
1218
1265
  try:
1219
- frames = aura.frame_api.get_frames()
1220
1266
  resolved = resolve_frame(frame_arg, frames)
1221
1267
 
1222
1268
  if resolved.status == 'ambiguous':
@@ -1321,7 +1367,7 @@ def run_sync(dir_arg: str, frame_arg: str, apply: bool = False, yes: bool = Fals
1321
1367
  count = len(plan.to_delete)
1322
1368
  print(f'IRREVERSIBLE: {count} photo(s) will be permanently destroyed '
1323
1369
  f'account-wide, not just removed from this frame. This cannot be undone.')
1324
- answer = input(f'To confirm, type the number of photos to hard-delete ({count}): ')
1370
+ answer = input(f'Verbatim to confirm — type the number of photos to hard-delete ({count}): ')
1325
1371
  if answer.strip() != str(count):
1326
1372
  print('Aborted.')
1327
1373
  return 0
@@ -84,12 +84,25 @@ def _require_profile() -> Path:
84
84
  return path
85
85
 
86
86
 
87
+ def _enter_after_login(context, *, input_fn=input):
88
+ """Manual-mode harvest close: the Enter comes AFTER the login in the
89
+ opened window and only closes the harvest — the cookie check after it
90
+ (not the Enter itself) decides success. Seam for offline tests."""
91
+ input_fn("…log into Google in the opened window, THEN press Enter "
92
+ "here to finish the harvest> ")
93
+ cookies = context.cookies()
94
+ context.close()
95
+ return cookies
96
+
97
+
87
98
  def run_bootstrap(*, auto: bool = False) -> dict:
88
99
  """Open the dedicated-profile browser, harvest cookies, save the vault.
89
100
 
90
101
  auto=True polls the context for completed Google auth (SAPISID-family
91
102
  cookies) instead of waiting for an Enter in the terminal — for runs
92
- launched from a non-interactive shell. Returns an identity summary dict:
103
+ launched from a non-interactive shell. In manual mode the Enter comes
104
+ AFTER the login and only closes the harvest — the cookie check below
105
+ still decides success. Returns an identity summary dict:
93
106
  {vault_path, cookie_count, auth_markers} — never cookie values.
94
107
  """
95
108
  profile_dir = _require_profile()
@@ -181,9 +194,7 @@ def run_bootstrap(*, auto: bool = False) -> dict:
181
194
  cookies = context.cookies()
182
195
  context.close()
183
196
  else:
184
- input("…press Enter here AFTER logging in on the opened window> ")
185
- cookies = context.cookies()
186
- context.close()
197
+ cookies = _enter_after_login(context, input_fn=input)
187
198
 
188
199
  if not any(c["name"] in ("SID", "SAPISID", "__Secure-1PSID") for c in cookies):
189
200
  raise BootstrapError(
@@ -382,7 +382,12 @@ def run_google_sync(album_target: str, frame_arg: str, *, apply: bool = False,
382
382
  print(f'Login failed: {e}')
383
383
  return 1
384
384
 
385
- frames = aura.frame_api.get_frames()
385
+ from pushframe.session import auto_refresh_stored, frames_read_with_refresh
386
+ _stored = auto_refresh_stored()
387
+ frames = frames_read_with_refresh(
388
+ aura, who=(_stored or {}).get('email'), stored=_stored)
389
+ if frames is None:
390
+ return 2
386
391
  frame_res = resolve_frame(frame_arg, frames)
387
392
  if frame_res.status == 'ambiguous':
388
393
  print(f"'{frame_arg}' matches more than one frame:")