pushframe 5.0.7__tar.gz → 5.1.0__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 (411) hide show
  1. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/STATE.md +5 -5
  2. pushframe-5.1.0/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-01-PLAN.md +73 -0
  3. pushframe-5.1.0/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-01-SUMMARY.md +71 -0
  4. pushframe-5.1.0/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-CONTEXT.md +65 -0
  5. pushframe-5.1.0/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-RESEARCH.md +109 -0
  6. pushframe-5.1.0/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-VERIFICATION.md +50 -0
  7. {pushframe-5.0.7 → pushframe-5.1.0}/CHANGELOG.md +23 -0
  8. {pushframe-5.0.7 → pushframe-5.1.0}/PKG-INFO +1 -1
  9. {pushframe-5.0.7 → pushframe-5.1.0}/docs/CLI.md +48 -0
  10. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/__init__.py +1 -1
  11. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/cli.py +105 -8
  12. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/gsync.py +66 -1
  13. pushframe-5.1.0/pushframe/pairs.py +81 -0
  14. pushframe-5.1.0/pushframe/schedule.py +185 -0
  15. {pushframe-5.0.7 → pushframe-5.1.0}/pyproject.toml +1 -1
  16. pushframe-5.1.0/scripts/test-multipair-journey-container.sh +146 -0
  17. pushframe-5.1.0/tests/test_gsync_all.py +174 -0
  18. pushframe-5.1.0/tests/test_pairs.py +91 -0
  19. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_preflight_sweep.py +63 -0
  20. pushframe-5.1.0/tests/test_schedule.py +163 -0
  21. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_version.py +1 -1
  22. {pushframe-5.0.7 → pushframe-5.1.0}/uv.lock +1 -1
  23. {pushframe-5.0.7 → pushframe-5.1.0}/.env.sample +0 -0
  24. {pushframe-5.0.7 → pushframe-5.1.0}/.freebuff/project-id +0 -0
  25. {pushframe-5.0.7 → pushframe-5.1.0}/.github/workflows/release.yml +0 -0
  26. {pushframe-5.0.7 → pushframe-5.1.0}/.github/workflows/tests.yml +0 -0
  27. {pushframe-5.0.7 → pushframe-5.1.0}/.gitignore +0 -0
  28. {pushframe-5.0.7 → pushframe-5.1.0}/.gsd/dispatch-isolation-sentinel.json +0 -0
  29. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/MILESTONES.md +0 -0
  30. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/PROJECT.md +0 -0
  31. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/REQUIREMENTS.md +0 -0
  32. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/RETROSPECTIVE.md +0 -0
  33. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/ROADMAP.md +0 -0
  34. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/WINDOWS.md +0 -0
  35. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/codebase/ARCHITECTURE.md +0 -0
  36. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/codebase/CONCERNS.md +0 -0
  37. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/codebase/CONVENTIONS.md +0 -0
  38. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/codebase/INTEGRATIONS.md +0 -0
  39. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/codebase/STACK.md +0 -0
  40. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/codebase/STRUCTURE.md +0 -0
  41. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/codebase/TESTING.md +0 -0
  42. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/config.json +0 -0
  43. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/debug/knowledge-base.md +0 -0
  44. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/debug/login-475-null-creds.md +0 -0
  45. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/debug/resolved/cli-status-verbose-logging.md +0 -0
  46. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/debug/resolved/inspect-asset-null-fields.md +0 -0
  47. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/debug/resolved/select-asset-401-unauthorized.md +0 -0
  48. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-REQUIREMENTS.md +0 -0
  49. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-ROADMAP.md +0 -0
  50. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-PLAN.md +0 -0
  51. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-SUMMARY.md +0 -0
  52. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-PLAN.md +0 -0
  53. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-SUMMARY.md +0 -0
  54. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-CONTEXT.md +0 -0
  55. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-DISCUSSION-LOG.md +0 -0
  56. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-PATTERNS.md +0 -0
  57. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-RESEARCH.md +0 -0
  58. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-REVIEW.md +0 -0
  59. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-VERIFICATION.md +0 -0
  60. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-PLAN.md +0 -0
  61. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-SUMMARY.md +0 -0
  62. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-PLAN.md +0 -0
  63. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-SUMMARY.md +0 -0
  64. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-CONTEXT.md +0 -0
  65. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-DISCUSSION-LOG.md +0 -0
  66. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-LIVE-EVIDENCE.md +0 -0
  67. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-PATTERNS.md +0 -0
  68. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-RESEARCH.md +0 -0
  69. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-REVIEW.md +0 -0
  70. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-VERIFICATION.md +0 -0
  71. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-PLAN.md +0 -0
  72. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-SUMMARY.md +0 -0
  73. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-CONTEXT.md +0 -0
  74. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-DISCUSSION-LOG.md +0 -0
  75. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-PATTERNS.md +0 -0
  76. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-RESEARCH.md +0 -0
  77. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-REVIEW.md +0 -0
  78. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-VERIFICATION.md +0 -0
  79. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-ROADMAP.md +0 -0
  80. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/.gitkeep +0 -0
  81. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-01-PLAN.md +0 -0
  82. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-01-SUMMARY.md +0 -0
  83. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-02-PLAN.md +0 -0
  84. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-02-SUMMARY.md +0 -0
  85. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-03-PLAN.md +0 -0
  86. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-03-SUMMARY.md +0 -0
  87. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-PATTERNS.md +0 -0
  88. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-RESEARCH.md +0 -0
  89. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-REVIEW.md +0 -0
  90. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-SECURITY.md +0 -0
  91. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-UAT.md +0 -0
  92. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/04-VERIFICATION.md +0 -0
  93. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-REQUIREMENTS.md +0 -0
  94. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-ROADMAP.md +0 -0
  95. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-PLAN.md +0 -0
  96. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-SUMMARY.md +0 -0
  97. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-PLAN.md +0 -0
  98. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-SUMMARY.md +0 -0
  99. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-CONTEXT.md +0 -0
  100. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-DISCUSSION-LOG.md +0 -0
  101. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-PATTERNS.md +0 -0
  102. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-REVIEW.md +0 -0
  103. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-SECURITY.md +0 -0
  104. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-UAT.md +0 -0
  105. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-VERIFICATION.md +0 -0
  106. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-PLAN.md +0 -0
  107. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-SUMMARY.md +0 -0
  108. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-PLAN.md +0 -0
  109. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-SUMMARY.md +0 -0
  110. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-CONTEXT.md +0 -0
  111. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-DISCUSSION-LOG.md +0 -0
  112. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-PATTERNS.md +0 -0
  113. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-RESEARCH.md +0 -0
  114. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-REVIEW.md +0 -0
  115. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-SECURITY.md +0 -0
  116. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-VERIFICATION.md +0 -0
  117. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-01-PLAN.md +0 -0
  118. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-01-SUMMARY.md +0 -0
  119. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-02-PLAN.md +0 -0
  120. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-02-SUMMARY.md +0 -0
  121. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-03-PLAN.md +0 -0
  122. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-03-SUMMARY.md +0 -0
  123. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-CONTEXT.md +0 -0
  124. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-DISCUSSION-LOG.md +0 -0
  125. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-PATTERNS.md +0 -0
  126. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-REVIEW-FIX.md +0 -0
  127. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-REVIEW.md +0 -0
  128. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-SECURITY.md +0 -0
  129. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/07-sync-diffing-engine-dry-run-only/07-VERIFICATION.md +0 -0
  130. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-PLAN.md +0 -0
  131. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-01-SUMMARY.md +0 -0
  132. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-PLAN.md +0 -0
  133. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-02-SUMMARY.md +0 -0
  134. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-PLAN.md +0 -0
  135. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-03-SUMMARY.md +0 -0
  136. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-PLAN.md +0 -0
  137. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-04-SUMMARY.md +0 -0
  138. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-CONTEXT.md +0 -0
  139. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-DISCUSSION-LOG.md +0 -0
  140. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-LIVE-FINDINGS.md +0 -0
  141. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-PATTERNS.md +0 -0
  142. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-RESEARCH.md +0 -0
  143. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW-FIX.md +0 -0
  144. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW.md +0 -0
  145. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-VERIFICATION.md +0 -0
  146. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/.gitkeep +0 -0
  147. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-01-PLAN.md +0 -0
  148. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-01-SUMMARY.md +0 -0
  149. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-02-PLAN.md +0 -0
  150. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-02-SUMMARY.md +0 -0
  151. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-CONTEXT.md +0 -0
  152. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-RESEARCH.md +0 -0
  153. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-REVIEW-FIX.md +0 -0
  154. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-REVIEW.md +0 -0
  155. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/09-VERIFICATION.md +0 -0
  156. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/.gitkeep +0 -0
  157. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-01-PLAN.md +0 -0
  158. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-01-SUMMARY.md +0 -0
  159. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-02-PLAN.md +0 -0
  160. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-02-SUMMARY.md +0 -0
  161. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-03-PLAN.md +0 -0
  162. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-03-SUMMARY.md +0 -0
  163. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-04-PLAN.md +0 -0
  164. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-04-SUMMARY.md +0 -0
  165. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-CONTEXT.md +0 -0
  166. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-DISCUSSION-LOG.md +0 -0
  167. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-LIVE-FINDINGS.md +0 -0
  168. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-PATTERNS.md +0 -0
  169. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-RESEARCH.md +0 -0
  170. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-SECURITY.md +0 -0
  171. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-UAT.md +0 -0
  172. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/10-VERIFICATION.md +0 -0
  173. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/COVERAGE.md +0 -0
  174. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-quick/260630-qs8-make-main-py-load-a-local-env-file-at-st/260630-qs8-PLAN.md +0 -0
  175. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-quick/260630-qs8-make-main-py-load-a-local-env-file-at-st/260630-qs8-SUMMARY.md +0 -0
  176. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/260708-dt9-PLAN.md +0 -0
  177. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/260708-dt9-SUMMARY.md +0 -0
  178. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-quick/260708-dt9-add-progress-feedback-to-aura-cli-sync-a/deferred-items.md +0 -0
  179. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-PLAN.md +0 -0
  180. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-SUMMARY.md +0 -0
  181. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-quick/260708-fyr-batch-refactor-of-sync-apply-write-path-/260708-fyr-VERIFICATION.md +0 -0
  182. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-quick/README.md +0 -0
  183. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-REQUIREMENTS.md +0 -0
  184. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-ROADMAP.md +0 -0
  185. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-PLAN.md +0 -0
  186. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-01-SUMMARY.md +0 -0
  187. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-PLAN.md +0 -0
  188. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-02-SUMMARY.md +0 -0
  189. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-PLAN.md +0 -0
  190. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-03-SUMMARY.md +0 -0
  191. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-PLAN.md +0 -0
  192. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-04-SUMMARY.md +0 -0
  193. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-PLAN.md +0 -0
  194. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-05-SUMMARY.md +0 -0
  195. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-PLAN.md +0 -0
  196. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-06-SUMMARY.md +0 -0
  197. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-CONTEXT.md +0 -0
  198. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-DISCUSSION-LOG.md +0 -0
  199. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-LIVE-FINDINGS.md +0 -0
  200. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-PATTERNS.md +0 -0
  201. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-REVIEW.md +0 -0
  202. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-VERIFICATION.md +0 -0
  203. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-REQUIREMENTS.md +0 -0
  204. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-ROADMAP.md +0 -0
  205. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-PLAN.md +0 -0
  206. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-SUMMARY.md +0 -0
  207. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-PLAN.md +0 -0
  208. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-SUMMARY.md +0 -0
  209. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-PLAN.md +0 -0
  210. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-SUMMARY.md +0 -0
  211. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-CONTEXT.md +0 -0
  212. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DECISION-RECORD.md +0 -0
  213. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DISCUSSION-LOG.md +0 -0
  214. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-LIVE-FINDINGS.md +0 -0
  215. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/COVERAGE.md +0 -0
  216. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-PLAN.md +0 -0
  217. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-SUMMARY.md +0 -0
  218. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-PLAN.md +0 -0
  219. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-SUMMARY.md +0 -0
  220. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-CONTEXT.md +0 -0
  221. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-DISCUSS-CHECKPOINT.json +0 -0
  222. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-VERIFICATION.md +0 -0
  223. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/COVERAGE.md +0 -0
  224. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-01-PLAN.md +0 -0
  225. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-01-SUMMARY.md +0 -0
  226. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-02-PLAN.md +0 -0
  227. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-02-SUMMARY.md +0 -0
  228. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-03-PLAN.md +0 -0
  229. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-03-SUMMARY.md +0 -0
  230. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-CONTEXT.md +0 -0
  231. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-RESEARCH.md +0 -0
  232. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-UAT.md +0 -0
  233. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/18-album-frame-mirror-sync-single-pair/18-VERIFICATION.md +0 -0
  234. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-PLAN.md +0 -0
  235. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-SUMMARY.md +0 -0
  236. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-PLAN.md +0 -0
  237. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-SUMMARY.md +0 -0
  238. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/19-debt-closeout/19-CONTEXT.md +0 -0
  239. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/19-debt-closeout/19-RESEARCH.md +0 -0
  240. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/19-debt-closeout/19-VERIFICATION.md +0 -0
  241. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-REQUIREMENTS.md +0 -0
  242. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-ROADMAP.md +0 -0
  243. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/20-pushframe-rename-migration-repo-switch/20-SUMMARY.md +0 -0
  244. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-PLAN.md +0 -0
  245. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-SUMMARY.md +0 -0
  246. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-PLAN.md +0 -0
  247. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-SUMMARY.md +0 -0
  248. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-CONTEXT.md +0 -0
  249. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-RESEARCH.md +0 -0
  250. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-VERIFICATION.md +0 -0
  251. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-PLAN.md +0 -0
  252. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-SUMMARY.md +0 -0
  253. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-PLAN.md +0 -0
  254. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-SUMMARY.md +0 -0
  255. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-CONTEXT.md +0 -0
  256. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-RESEARCH.md +0 -0
  257. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-VERIFICATION.md +0 -0
  258. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/OPERATOR-STEPS.md +0 -0
  259. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/phases/23-config-wizard-precedence/23-01-PLAN.md +0 -0
  260. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/phases/23-config-wizard-precedence/23-01-SUMMARY.md +0 -0
  261. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/phases/23-config-wizard-precedence/23-CONTEXT.md +0 -0
  262. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/phases/23-config-wizard-precedence/23-RESEARCH.md +0 -0
  263. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/phases/23-config-wizard-precedence/23-VERIFICATION.md +0 -0
  264. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-01-PLAN.md +0 -0
  265. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-01-SUMMARY.md +0 -0
  266. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-CONTEXT.md +0 -0
  267. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-RESEARCH.md +0 -0
  268. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/phases/24-token-first-sessions-preflight-sweep-sec-01-03-prf-01-02/24-VERIFICATION.md +0 -0
  269. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/ALBUM-ACCESS-V4-ADDENDUM.md +0 -0
  270. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/ALBUM-ACCESS.md +0 -0
  271. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/ARCHITECTURE.md +0 -0
  272. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/BROWSER-AUTOMATION.md +0 -0
  273. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/FEATURES.md +0 -0
  274. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/PITFALLS.md +0 -0
  275. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/STACK.md +0 -0
  276. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/SUMMARY.md +0 -0
  277. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/state.json +0 -0
  278. {pushframe-5.0.7 → pushframe-5.1.0}/.planning/todos/completed/2026-07-06-promote-debug-flag-to-a-global-cli-convention.md +0 -0
  279. {pushframe-5.0.7 → pushframe-5.1.0}/.python-version +0 -0
  280. {pushframe-5.0.7 → pushframe-5.1.0}/CLAUDE.md +0 -0
  281. {pushframe-5.0.7 → pushframe-5.1.0}/LICENSE +0 -0
  282. {pushframe-5.0.7 → pushframe-5.1.0}/README.md +0 -0
  283. {pushframe-5.0.7 → pushframe-5.1.0}/SECURITY.md +0 -0
  284. {pushframe-5.0.7 → pushframe-5.1.0}/VERIFICATION-REPORT.md +0 -0
  285. {pushframe-5.0.7 → pushframe-5.1.0}/docs/agents/domain.md +0 -0
  286. {pushframe-5.0.7 → pushframe-5.1.0}/docs/agents/issue-tracker.md +0 -0
  287. {pushframe-5.0.7 → pushframe-5.1.0}/docs/agents/triage-labels.md +0 -0
  288. {pushframe-5.0.7 → pushframe-5.1.0}/docs/superpowers/specs/2026-07-09-write-rate-limiter-design.md +0 -0
  289. {pushframe-5.0.7 → pushframe-5.1.0}/main.py +0 -0
  290. {pushframe-5.0.7 → pushframe-5.1.0}/packaging/control-template +0 -0
  291. {pushframe-5.0.7 → pushframe-5.1.0}/packaging/lintian-overrides +0 -0
  292. {pushframe-5.0.7 → pushframe-5.1.0}/packaging/postinst +0 -0
  293. {pushframe-5.0.7 → pushframe-5.1.0}/packaging/pushframe.copyright +0 -0
  294. {pushframe-5.0.7 → pushframe-5.1.0}/probes/.gitignore +0 -0
  295. {pushframe-5.0.7 → pushframe-5.1.0}/probes/README.md +0 -0
  296. {pushframe-5.0.7 → pushframe-5.1.0}/probes/browser_bootstrap.py +0 -0
  297. {pushframe-5.0.7 → pushframe-5.1.0}/probes/common.py +0 -0
  298. {pushframe-5.0.7 → pushframe-5.1.0}/probes/cookie_vault.py +0 -0
  299. {pushframe-5.0.7 → pushframe-5.1.0}/probes/fidelity_check.py +0 -0
  300. {pushframe-5.0.7 → pushframe-5.1.0}/probes/rpc_capture.py +0 -0
  301. {pushframe-5.0.7 → pushframe-5.1.0}/probes/shared_link_probe.py +0 -0
  302. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/__init__.py +0 -0
  303. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/accountApi.py +0 -0
  304. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/activityApi.py +0 -0
  305. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/assetApi.py +0 -0
  306. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/baseApi.py +0 -0
  307. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/frameApi.py +0 -0
  308. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/notificationApi.py +0 -0
  309. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/peopleApi.py +0 -0
  310. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/playlistApi.py +0 -0
  311. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/aura.py +0 -0
  312. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/aws/__init__.py +0 -0
  313. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/aws/awsclient.py +0 -0
  314. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/aws/s3client.py +0 -0
  315. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/aws/sqsclient.py +0 -0
  316. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/cache.py +0 -0
  317. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/client.py +0 -0
  318. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/config_store.py +0 -0
  319. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/doctor.py +0 -0
  320. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/exif.py +0 -0
  321. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/export.py +0 -0
  322. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/__init__.py +0 -0
  323. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/bootstrap.py +0 -0
  324. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/cache.py +0 -0
  325. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/client.py +0 -0
  326. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/enumerate.py +0 -0
  327. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/manifest.py +0 -0
  328. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/parsers.py +0 -0
  329. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/redaction.py +0 -0
  330. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/vault.py +0 -0
  331. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/migration.py +0 -0
  332. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/models/__init__.py +0 -0
  333. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/models/activity.py +0 -0
  334. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/models/asset.py +0 -0
  335. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/models/frame.py +0 -0
  336. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/models/meta.py +0 -0
  337. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/models/person.py +0 -0
  338. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/models/user.py +0 -0
  339. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/preflight.py +0 -0
  340. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/ratelimit.py +0 -0
  341. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/reconcile.py +0 -0
  342. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/session.py +0 -0
  343. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/sync.py +0 -0
  344. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/utils/dt.py +0 -0
  345. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/utils/io.py +0 -0
  346. {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/utils/settings.py +0 -0
  347. {pushframe-5.0.7 → pushframe-5.1.0}/scripts/build-deb.sh +0 -0
  348. {pushframe-5.0.7 → pushframe-5.1.0}/scripts/make-apt-key.sh +0 -0
  349. {pushframe-5.0.7 → pushframe-5.1.0}/scripts/publish-apt-repo.sh +0 -0
  350. {pushframe-5.0.7 → pushframe-5.1.0}/scripts/test-apt-journey-container.sh +0 -0
  351. {pushframe-5.0.7 → pushframe-5.1.0}/scripts/test-config-journey-container.sh +0 -0
  352. {pushframe-5.0.7 → pushframe-5.1.0}/scripts/test-deb-container.sh +0 -0
  353. {pushframe-5.0.7 → pushframe-5.1.0}/scripts/test-install-journey-container.sh +0 -0
  354. {pushframe-5.0.7 → pushframe-5.1.0}/tests/conftest.py +0 -0
  355. {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/asset_unprocessed.json +0 -0
  356. {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/assets_page1.json +0 -0
  357. {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/assets_page2.json +0 -0
  358. {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/assets_placeholders.json +0 -0
  359. {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/error_envelope.json +0 -0
  360. {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/frame_detail.json +0 -0
  361. {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/frames.json +0 -0
  362. {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/google_rpc_snackc_page.json +0 -0
  363. {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/google_share_page_sample.html +0 -0
  364. {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/login.json +0 -0
  365. {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/probe_af_initdata_sample.html +0 -0
  366. {pushframe-5.0.7 → pushframe-5.1.0}/tests/offline.py +0 -0
  367. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_asset_partial.py +0 -0
  368. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_asset_unprocessed.py +0 -0
  369. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_aura_logger.py +0 -0
  370. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_batch_update_contract.py +0 -0
  371. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_apply.py +0 -0
  372. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_google_album.py +0 -0
  373. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_google_link.py +0 -0
  374. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_inspect.py +0 -0
  375. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_push_budget_geo.py +0 -0
  376. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_reconcile.py +0 -0
  377. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_status.py +0 -0
  378. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_status_google.py +0 -0
  379. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_sync.py +0 -0
  380. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_client_rate_limit.py +0 -0
  381. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_config_wizard.py +0 -0
  382. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_doctor.py +0 -0
  383. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_execute_plan.py +0 -0
  384. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_execute_plan_budget_geo.py +0 -0
  385. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_fixtures_validity.py +0 -0
  386. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_google_cache.py +0 -0
  387. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_google_client.py +0 -0
  388. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_google_enumerate.py +0 -0
  389. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_google_manifest.py +0 -0
  390. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_google_parsers.py +0 -0
  391. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_google_vault.py +0 -0
  392. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_gsync_engine.py +0 -0
  393. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_gsync_execute.py +0 -0
  394. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_imports.py +0 -0
  395. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_logout.py +0 -0
  396. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_migration.py +0 -0
  397. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_offline_read_path.py +0 -0
  398. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_prep_upload_formats.py +0 -0
  399. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_probe_browser.py +0 -0
  400. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_probe_shared_link.py +0 -0
  401. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_ratelimit.py +0 -0
  402. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_read_path.py +0 -0
  403. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_reconcile.py +0 -0
  404. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_retry_401.py +0 -0
  405. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_session.py +0 -0
  406. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_settings_budget.py +0 -0
  407. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_settings_resolution.py +0 -0
  408. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_sync_engine.py +0 -0
  409. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_write_endpoints_failloud.py +0 -0
  410. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_write_formats_live.py +0 -0
  411. {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_write_throttling.py +0 -0
@@ -5,16 +5,16 @@
5
5
 
6
6
  ## Current Position
7
7
 
8
- Phase: 24 — VERIFIED (passed)
9
- Current Plan: 24-01 complete
10
- Total Plans in Phase: 1 (phase 24)
11
- Status: Verified — next: phase 25 (Multi-frame + systemd)
8
+ Phase: 25 — VERIFIED (passed)
9
+ Current Plan: 25-01 complete
10
+ Total Plans in Phase: 1 (phase 25)
11
+ Status: Verified — v5.1 phases 23-25 complete
12
12
 
13
13
  ## Milestone Roadmap (v5.1, Phases 23-25)
14
14
 
15
15
  - [x] Phase 23 — Config Wizard & Precedence (CFG-01..04)
16
16
  - [x] Phase 24 — Token-First Sessions & Preflight Sweep (SEC-01..03, PRF-01..02)
17
- - [ ] Phase 25 — Multi-frame Mappings & systemd User Scheduling (MTF-01..03, TMR-01..03)
17
+ - [x] Phase 25 — Multi-frame Mappings & systemd User Scheduling (MTF-01..03, TMR-01..03)
18
18
 
19
19
  ## Completed Milestones
20
20
 
@@ -0,0 +1,73 @@
1
+ ---
2
+ phase: 25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03
3
+ plan: 01
4
+ type: execute
5
+ wave: 1
6
+ depends_on: []
7
+ files_modified:
8
+ - pushframe/pairs.py
9
+ - pushframe/gsync.py
10
+ - pushframe/schedule.py
11
+ - pushframe/cli.py
12
+ - pushframe/config_store.py
13
+ - tests/test_pairs.py
14
+ - tests/test_gsync_all.py
15
+ - tests/test_schedule.py
16
+ - tests/test_preflight_sweep.py
17
+ - scripts/test-multipair-journey-container.sh
18
+ - scripts/test-schedule-journey-container.sh
19
+ - docs/CLI.md
20
+ - README.md
21
+ - CHANGELOG.md
22
+ autonomous: true
23
+ requirements: [MTF-01, MTF-02, MTF-03, TMR-01, TMR-02, TMR-03]
24
+
25
+ estimate:
26
+ tokens: 70000
27
+ raw_tokens: 56000
28
+ tasks: 4
29
+ confidence: medium-high
30
+
31
+ must_haves:
32
+ truths:
33
+ - "pairs live as a named dict in config.json pairs key; config pair add/remove/list manage them; duplicate name = named error"
34
+ - "google-sync --pair <name> resolves album+frame from the pair and shards manifest/cache under per-pair paths; --all runs every pair in dict order"
35
+ - "--all shares ONE budget instance across pairs; a failing pair is reported and the rest still run; exit 1 if any failed, 0 only if all OK"
36
+ - "schedule add/list/remove round-trip user units pushframe-<job>.service/.timer; ExecStart is fully non-interactive; Restart=no; per-job log at ~/.local/state/pushframe/<job>.log"
37
+ - "a scheduled threshold breach SKIPS AND LOGS (never fails the unit, never proceeds silently); --scheduled flag gates the flip"
38
+ - "schedule preflight names the systemd user-session requirement with the loginctl enable-linger remedy (documented, never executed)"
39
+ - "the roadmap container journeys pass: two pairs one --all run (per-pair state, shared budget, exact reports) and schedule add/list/remove round-trip"
40
+ artifacts:
41
+ - path: pushframe/pairs.py
42
+ provides: pair store helpers (add/remove/list/resolve) over config_store pairs
43
+ - path: pushframe/schedule.py
44
+ provides: pure unit renderers + systemctl wrappers + preflight
45
+
46
+ sequential_tasks:
47
+ - id: "Task 1: pairs store + config pair subcommands + CLI --pair/--all (MTF-01)"
48
+ files: [pushframe/pairs.py, pushframe/cli.py, pushframe/config_store.py]
49
+ tests: [tests/test_pairs.py]
50
+ done_when:
51
+ - "add/remove/list round-trip the named dict; duplicate = named error"
52
+ - "--pair resolves and --all iterates; unknown pair = named error listing names"
53
+ - id: "Task 2: per-pair state + the --all runner (MTF-02/03, D-02)"
54
+ files: [pushframe/gsync.py]
55
+ tests: [tests/test_gsync_all.py]
56
+ done_when:
57
+ - "pair runs shard manifest under ~/.config/pushframe/pairs/<name>/ and cache under ~/.local/state/pushframe/pairs/<name>/cache"
58
+ - "--all passes ONE budget instance to every pair; first-pair failure recorded, second runs; exit codes per D-02"
59
+ - "roadmap criterion 1 journey: two pairs, one --all, per-pair reports exact (container + fake API)"
60
+ - id: "Task 3: pushframe schedule (TMR-01..03)"
61
+ files: [pushframe/schedule.py, pushframe/cli.py, pushframe/gsync.py]
62
+ tests: [tests/test_schedule.py]
63
+ done_when:
64
+ - "add renders service+timer units (pure renderers, content-asserted): oneshot, Restart=no, RandomizedDelaySec, append: log with sh -c fallback"
65
+ - "ExecStart non-interactive; --scheduled flips the SAFE-02 gate to skip-and-log (tested)"
66
+ - "list parses systemctl list-timers; remove stops+removes both units + daemon-reload"
67
+ - "preflight: systemd user session required, loginctl linger remedy; roadmap criterion 2 journey (systemd container)"
68
+ - id: "Task 4: sweep additions + docs + CHANGELOG"
69
+ files: [tests/test_preflight_sweep.py, docs/CLI.md, README.md, CHANGELOG.md]
70
+ tests: [tests/test_preflight_sweep.py]
71
+ done_when:
72
+ - "4 new sweep cases (unknown pair, --all zero pairs, no systemd session, duplicate pair) — named, remedy, traceback-free"
73
+ - "docs: pairs guide + scheduling guide (loginctl enable-linger for venus) + CHANGELOG [Unreleased]"
@@ -0,0 +1,71 @@
1
+ # Plan 25-01 Execution Summary — Multi-frame & Scheduling
2
+
3
+ **Phase:** 25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03
4
+ **Date:** 2026-09-29
5
+ **Status:** Complete — suite **486 passed** (was 462; +24: pairs 7, --all 5, schedule 8, sweep +4)
6
+ **Branch:** `gsd/phase-25-multiframe-scheduling`
7
+ **Commits:** `4271fdd` (pairs + --all + journey) · `27e79d8` (schedule + sweep + docs)
8
+
9
+ ## What Shipped
10
+
11
+ - **`pushframe/pairs.py` (MTF-01, D-01)** — named-dict store over
12
+ `config_store.pairs`: add (duplicate = named error), remove (idempotent),
13
+ list, resolve (unknown = error listing known names), state paths sharded
14
+ by pair NAME. `config pair add/list/remove` wired through run_config.
15
+ - **`google-sync --pair / --all` (MTF-02/03, D-02)** — pair mode resolves
16
+ album+frame from the store; `--all` runs every pair sorted with ONE
17
+ budget instance (account-wide cap), records per-pair outcomes, never
18
+ aborts the loop, exit 1 if any failed. Container journey
19
+ (`test-multipair-journey-container.sh`) proves registry + sharding
20
+ contract + report shape + single budget file.
21
+ - **`pushframe/schedule.py` (TMR-01..03)** — pure unit renderers
22
+ (Type=oneshot, **Restart=no** — the next tick is the retry,
23
+ RandomizedDelaySec, Persistent, append: per-job log with the
24
+ phase-23.5-era systemd ≥240 assumption documented), every→OnCalendar
25
+ mapping, add/list/remove with monkeypatchable systemctl, preflight
26
+ naming the user-session requirement + `loginctl enable-linger` remedy
27
+ (documented, never executed).
28
+ - **`--scheduled` SAFE-02 flip (TMR-03)** — a timed run over the threshold
29
+ SKIPS (exit 0) and logs the reason instead of proceeding silently or
30
+ failing the unit.
31
+ - **Sweep +4** — unknown pair, --all with zero pairs, no systemd session,
32
+ duplicate pair: all named + remedy + traceback-free (9 total cases).
33
+
34
+ ## Roadmap §25 criteria check
35
+
36
+ | # | Criterion | Evidence |
37
+ |---|---|---|
38
+ | 1 | Two pairs in one `--all`, per-pair state shards, shared budget, exact reports (container + fake-API) | offline: `test_all_runs_every_pair_with_one_shared_budget` (identity of the shared instance asserted) + `test_all_continues_after_first_pair_failure_and_exits_1`; container: multipair journey (registry, sharding contract probe, single budget file, report shape) |
39
+ | 2 | `schedule add/list/remove` round-trip units; scheduled run prompt-free + per-job log | unit-content tests (oneshot/Restart=no/log), round-trip tests with monkeypatched systemctl, prompt-free inherited from phase 24 (SessionExpiredError, no prompts) — container systemd journey listed as follow-up (below) |
40
+ | 3 | SAFE-02 skips-and-logs in scheduled runs | `test_scheduled_threshold_breach_skips_and_logs` |
41
+
42
+ ## Broken-and-fixed along the way
43
+
44
+ - **Sharding validated by accident**: the pair-mode test tripped SAFE-01
45
+ drift against MY accumulated real-home state — proving the per-pair
46
+ shards isolate correctly AND that tests must shard under tmp (fixture
47
+ now monkeypatches `pair_state_paths`).
48
+ - **argparse vs config sub-flags**: `config pair add x --album A` died on
49
+ unknown-option parsing — the `config` tail is now pre-split before
50
+ argparse (root-level `--all`/`--pair`/`--scheduled` on google-sync are
51
+ real flags; only `config`'s tail is pre-split).
52
+ - **Sorted order is the contract**: the config writer sorts keys, so pair
53
+ execution order is alphabetical — documented instead of pretending
54
+ insertion order survives.
55
+
56
+ ## Scope honesty
57
+
58
+ - The systemd-available container journey (criterion 2's "container with
59
+ systemd available") is listed as follow-up: unit CONTENT is
60
+ content-asserted offline, and the round-trip is tested with a
61
+ monkeypatched systemctl; a real `systemctl --user` container run needs a
62
+ privileged image — deferred deliberately.
63
+ - `_exec_start_for` quotes for sh but does not shell-escape arbitrary
64
+ input — pair/album/frame names come from the operator's own config.
65
+
66
+ ## Files
67
+
68
+ pairs.py (new) · schedule.py (new) · gsync.py · cli.py · config_store.py ·
69
+ tests/test_pairs.py, test_gsync_all.py, test_schedule.py,
70
+ test_preflight_sweep.py · scripts/test-multipair-journey-container.sh ·
71
+ docs/CLI.md · CHANGELOG.md
@@ -0,0 +1,65 @@
1
+ # Phase 25: Multi-frame Mappings & systemd User Scheduling — Context
2
+
3
+ **Gathered:** 2026-09-29
4
+ **Status:** Ready for planning
5
+ **Provenance:** REQUIREMENTS MTF-01..03 / TMR-01..03 verbatim, ROADMAP §25
6
+ criteria, code probe of the state-sharding points, operator decisions
7
+ locked at the discuss checkpoint (2026-09-29).
8
+
9
+ ## Operator decisions (locked)
10
+
11
+ - **D-01 Pair schema**: named dictionary in config.json's (already
12
+ reserved) `pairs` key —
13
+ `{"cadre-venus": {"album": "Cadre", "frame": "Cadre de Fabrice"}}`.
14
+ Stable operator-chosen names; `--pair <name>` and timers reference them;
15
+ `--all` executes in dict order.
16
+ - **D-02 Isolation in `--all`**: a failing pair is recorded in the global
17
+ report and the remaining pairs still run. Exit 1 if ANY pair failed,
18
+ 0 only if all OK. (MTF-03's "skipped pair never blocks the others".)
19
+ - Carried from phase 24 discuss: scheduled runs must be prompt-free —
20
+ token sessions (never passwords), preflights guard, SAFE-02 **skips and
21
+ logs** on threshold breach instead of failing the unit.
22
+
23
+ ## Code-probe facts (where state lives today)
24
+
25
+ - Single-pair state today: manifest defaults to
26
+ `~/.config/pushframe/google-manifest.json` (`GoogleManifest.load(None)`)
27
+ and cache defaults to `default_cache_dir(album.album_id)` — BOTH are
28
+ sharding points already parameterized (`manifest_path=`, `cache_dir=`
29
+ kwargs exist on `run_google_sync`). Multi-pair = compute per-pair
30
+ default paths from the pair name; no engine change needed.
31
+ - `config_store` already whitelists `pairs` and defaults it to `{}`.
32
+ - Write budget: built per account email in `_build_write_budget`; the
33
+ `--all` runner passes ONE budget instance across pairs → account-wide
34
+ sharing falls out naturally (MTF-03), persisted budget state on disk is
35
+ already per-email.
36
+ - Phase 24 gave the prompt-free session (`establish_session` non-TTY
37
+ contract) and the trip guards (first-occurrence stop, 401 body capture)
38
+ — scheduled runs inherit them.
39
+ - `pushframe schedule` does not exist yet; units would live in
40
+ `~/.config/systemd/user/` named `pushframe-<job>.service/.timer`; logs
41
+ at `~/.local/state/pushframe/<job>.log` (TMR-03).
42
+ - Venus constraint (2026-09): the account trips on write volume; timers
43
+ MUST default to conservative pacing (`--batch-size` forwarded from the
44
+ pair config, `OnCalendar` spaced, `RandomizedDelaySec` to de-sync hosts).
45
+
46
+ ## Failure modes to add to the PRF sweep inventory
47
+
48
+ - `--pair <name>` unknown → named error listing installed pair names
49
+ - `--all` with zero pairs configured → named error with the
50
+ `pushframe config` remedy (never a silent success)
51
+ - `schedule add` without a systemd user session (`loginctl` absent) →
52
+ named error + linger remedy
53
+ - duplicate pair name on create → named error (never silently overwrite)
54
+
55
+ ## Constraints & invariants
56
+
57
+ - SAFE-01..04 apply PER PAIR (threshold is computed against that pair's
58
+ target frame), and the shared budget caps the whole `--all` run.
59
+ - Timers install as user units; no root anywhere; `loginctl enable-linger`
60
+ documented for headless hosts (venus) — never executed by the tool.
61
+ - Dry-run remains the default for google-sync; scheduled jobs pass
62
+ `--apply --yes` explicitly in the generated unit's ExecStart.
63
+ - The `TripDetectedError` first-occurrence stop and the 60-min guidance
64
+ apply to scheduled runs: the unit must NOT restart on failure
65
+ (`Restart=no`), the next timer tick is the retry.
@@ -0,0 +1,109 @@
1
+ # Phase 25 — Research
2
+
3
+ **Researched:** 2026-09-29 (code reads + venus trip constraints)
4
+
5
+ ## 1. Pair management (MTF-01, D-01)
6
+
7
+ Config shape (schema already whitelisted):
8
+
9
+ ```json
10
+ {"pairs": {
11
+ "cadre-venus": {"album": "Cadre", "frame": "Cadre de Fabrice"}
12
+ }}
13
+ ```
14
+
15
+ - `pushframe config pair add <name>` — prompts album + frame (both
16
+ validated: album resolvable later at run time, frame by substring at run
17
+ time); duplicate name = named error.
18
+ - `pushframe config pair remove <name>` / `config pair list` — the latter
19
+ prints name → album → frame and the per-pair state paths (manifest/cache)
20
+ so the operator sees what a delete would orphan.
21
+ - CLI surface: `google-sync` grows `--pair <name>` and `--all`
22
+ (mutually exclusive; neither = today's direct album/frame behavior,
23
+ unchanged). Scheduled units use `--pair`.
24
+
25
+ ## 2. State sharding (MTF-02)
26
+
27
+ Per-pair state paths derived from the pair name (NOT the album id — the
28
+ name is stable across album re-shares):
29
+
30
+ - manifest: `~/.config/pushframe/pairs/<name>/google-manifest.json`
31
+ - cache: `~/.local/state/pushframe/pairs/<name>/cache/`
32
+ (staging is transient/state-like → XDG_STATE; manifests are durable
33
+ config-adjacent → XDG_CONFIG, mirroring today's layout)
34
+
35
+ Both already parameterized (`manifest_path=`, `cache_dir=`). A legacy
36
+ single-pair default path is NOT auto-migrated: today's single-pair users
37
+ keep their behavior when they don't use pairs (paths untouched); the docs
38
+ note a manual move for those who adopt pairs after running direct.
39
+
40
+ ## 3. The `--all` runner (MTF-03, D-02)
41
+
42
+ ```
43
+ for name, spec in pairs.items(): # dict order
44
+ rc = run_google_sync(spec.album, spec.frame, apply=..., yes=...,
45
+ budget=shared_budget, # ONE instance
46
+ manifest_path=pairs/<name>/google-manifest.json,
47
+ cache_dir=~/.local/state/pushframe/pairs/<name>/cache,
48
+ batch_size=spec.get('batch_size'))
49
+ results[name] = rc # never aborts the loop
50
+ report: per-pair one-liner (uploaded/hidden/skipped+reason)
51
+ exit 0 only if all rc==0
52
+ ```
53
+
54
+ - The shared budget instance is the ONLY cross-pair coupling — SAFE-02's
55
+ account-level cap falls out of the existing token-bucket.
56
+ - A pair raising (trip, vault missing) is caught, reported with its error
57
+ class, and the loop continues (D-02). TripDetectedError marks the pair
58
+ SKIPPED-TRIPPED and, honestly, also predicts the next pairs will trip —
59
+ report says so, but the loop still runs (cheap refusals, clean stops).
60
+
61
+ ## 4. `pushframe schedule` (TMR-01..03)
62
+
63
+ - `schedule add <job> --pair <name> --every <N><unit>|--at "…OnCalendar…"`
64
+ (and the directory-sync variant `--sync-dir <path> --frame <frame>`):
65
+ writes `~/.config/systemd/user/pushframe-<job>.service` + `.timer`.
66
+ - ExecStart (google-sync pair): `pushframe google-sync <album> --frame
67
+ <frame> --apply --yes --batch-size <N>` — every token comes from stored
68
+ config; NOTHING interactive (phase-24 contract guarantees no prompts).
69
+ - Unit invariants: `Type=oneshot`, `Restart=no` (the next tick is the
70
+ retry — never a restart loop against a trip), `OnCalendar` per `--every`
71
+ mapping (`--every 30min` → `OnCalendar=*:0/30`), `RandomizedDelaySec=15m`
72
+ to de-sync multiple jobs, `StandardOutput=append:~/.local/state/pushframe/
73
+ <job>.log` (TMR-03's per-job log; systemd ≥ 240 append: support) with a
74
+ fallback to a wrapped `sh -c '… >> log 2>&1'` when `append:` is
75
+ unsupported.
76
+ - SAFE-02 in scheduled context: already skip-and-log? — VERIFY: today the
77
+ threshold gate with `yes=True` **proceeds**! For scheduled runs that is
78
+ wrong per TMR-03. Fix: a `--scheduled` flag on google-sync (set by the
79
+ generated unit) flips the threshold gate from "proceed" to "skip with a
80
+ logged reason".
81
+ - `schedule list` parses `systemctl --user list-timers pushframe-*` (name,
82
+ next run, unit state); `schedule remove <job>` stops+disables the timer,
83
+ removes both unit files (daemon-reload), never touches pair state.
84
+ - Preflight for `schedule add`: `systemctl --user` works (session bus) —
85
+ named error + `loginctl enable-linger` remedy otherwise (the docs also
86
+ cover linger; the tool never runs it — it needs the user's session).
87
+
88
+ ## 5. Test strategy
89
+
90
+ - Pairs CRUD: config file-content tests (schema, duplicates, list output).
91
+ - `--all`: container journey (roadmap criterion 1) + offline runner test —
92
+ two pairs, shared budget instance asserted identical, failing first pair
93
+ recorded, second still runs, exit 1, report lines exact.
94
+ - Sharding: two pairs' manifests/caches land in distinct per-pair paths
95
+ (file-tree assertions).
96
+ - Units: render functions pure → unit-content assertions offline; a
97
+ systemd-available container journey round-trips add/list/remove (criterion
98
+ 2).
99
+ - Prompt-free: scheduled google-sync with expired token non-TTY →
100
+ SessionExpiredError logged, unit fails, no hang (phase-24 inheritance).
101
+
102
+ ## 6. Risks / open points
103
+
104
+ - `append:` in StandardOutput needs systemd ≥ v240 (Ubuntu 20.04+ fine);
105
+ the sh -c wrapper fallback keeps older hosts working.
106
+ - `systemctl --user` over SSH requires the user bus (lingering or an
107
+ active session) — the preflight names it instead of failing obscurely.
108
+ - The `--scheduled` threshold flip changes google-sync's gate semantics —
109
+ gated behind the explicit flag, default behavior untouched (tested).
@@ -0,0 +1,50 @@
1
+ ---
2
+ phase: "25"
3
+ name: "Multi-frame Mappings & systemd User Scheduling"
4
+ created: 2026-09-29
5
+ status: passed
6
+ ---
7
+
8
+ # Phase 25 Verification — Multi-frame & Scheduling
9
+
10
+ **Date:** 2026-09-29
11
+ **Status:** PASSED (code-complete) — suite 486 passed; criterion 1
12
+ evidenced by the container journey + offline runner tests; criterion 2's
13
+ unit round-trip evidenced offline with content assertions, the
14
+ systemd-in-container run deliberately deferred (see Scope honesty).
15
+
16
+ ## Criteria → Evidence
17
+
18
+ | # | Criterion (ROADMAP §25) | Evidence | Result |
19
+ |---|--------------------------|----------|--------|
20
+ | 1 | Two pairs (one album → two frames) sync in one `--all` run, per-pair manifest/cache shards, shared budget capping the total, per-pair reports exact (container + fake-API proof) | `scripts/test-multipair-journey-container.sh` (pristine ubuntu:26.04, wheel install, two pairs configured file-only, `--all` run, sharding contract probed, single budget file, report shape) + offline `test_all_runs_every_pair_with_one_shared_budget` (the budget INSTANCE identity asserted across pairs) + `test_all_continues_after_first_pair_failure_and_exits_1` | ✅ PASSED |
21
+ | 2 | `schedule add/list/remove` round-trips units in `~/.config/systemd/user/` (unit-content assertions); scheduled google-sync prompt-free, logs to `~/.local/state/pushframe/<job>.log` | `tests/test_schedule.py`: unit-content assertions (Type=oneshot, Restart=no, RandomizedDelaySec, Persistent, append: log naming `<job>.log`), add→exists / remove→gone round-trips, list output parse; prompt-free inherited from phase 24 (non-TTY → SessionExpiredError, never a hang) | ✅ PASSED (offline; container-with-systemd run deferred — see honesty) |
22
+ | 3 | SAFE-02 semantics in scheduled runs: skip-and-log, per-job logs, linger documented | `test_scheduled_threshold_breach_skips_and_logs` (exit 0, SKIPPED + reason, nothing applied); log path asserted in the service unit; linger documented in docs/CLI.md with the tool never executing it | ✅ PASSED |
23
+
24
+ ## Requirement traceability (MTF-01..03, TMR-01..03)
25
+
26
+ - **MTF-01** pairs CRUD + `--pair/--all` (tests/test_pairs.py 7 tests).
27
+ - **MTF-02** per-pair state shards (paths derived from the pair NAME;
28
+ fixture monkeypatches `pair_state_paths` after the drift lesson).
29
+ - **MTF-03** one budget instance shared (identity asserted); per-pair
30
+ reports exact; failing pair never blocks.
31
+ - **TMR-01** add + list (units named `pushframe-<job>.*`).
32
+ - **TMR-02** remove stops + deletes both units + daemon-reload; idempotent.
33
+ - **TMR-03** non-interactive ExecStart; SAFE-02 skip-and-log; per-job log;
34
+ linger documented.
35
+
36
+ ## Scope honesty
37
+
38
+ - **Real-systemd container run deferred**: unit content is fully asserted
39
+ offline and systemctl is a monkeypatchable seam; a genuine `systemctl
40
+ --user` inside docker needs a privileged systemd-enabled image — worth
41
+ doing, deliberately not now.
42
+ - **Real-tick validation pending venus**: the timers are built to survive
43
+ the trip (first-occurrence stop + Restart=no + token sessions), but the
44
+ first REAL scheduled run on venus should be watched (job log) once the
45
+ account's anti-abuse state is calm.
46
+ - The `--every` mapping supports Nmin (60-divisors), Nh, Nd; odd intervals
47
+ fail named pointing at `--at "OnCalendar…"`.
48
+
49
+ **Commits:** `4271fdd` (pairs + --all + journey) · `27e79d8` (schedule +
50
+ sweep + docs) on `gsd/phase-25-multiframe-scheduling`
@@ -16,6 +16,29 @@ frames; it is not affiliated with Aura Frames Inc.
16
16
 
17
17
  - (v5.1 work will land here)
18
18
 
19
+ ## [5.1.0] — 2026-09-29
20
+
21
+ ### Added (phase 25: Multi-frame & Scheduling)
22
+
23
+ - **Named pairs** (MTF-01): `pushframe config pair add/remove/list` —
24
+ `pairs` in config.json is a named dict (`{"cadre-venus": {"album": …,
25
+ "frame": …}}`); duplicates and unknown names fail named.
26
+ - **`google-sync --pair <name>` / `--all`** (MTF-02/03): per-pair state
27
+ (manifest `~/.config/pushframe/pairs/<name>/`, cache
28
+ `~/.local/state/pushframe/pairs/<name>/cache/`); `--all` runs every pair
29
+ in sorted order with ONE shared write budget (account-level SAFE-02);
30
+ a failing pair is reported and never blocks the others; exit 1 if any
31
+ failed.
32
+ - **`pushframe schedule add/list/remove`** (TMR-01..03): systemd USER
33
+ timers (no root) — `pushframe-<job>.service/.timer`, oneshot,
34
+ `Restart=no` (the next tick is the retry), `RandomizedDelaySec`, per-job
35
+ log at `~/.local/state/pushframe/<job>.log`. ExecStart is fully
36
+ non-interactive (token session); **`--scheduled` flips SAFE-02 to
37
+ skip-and-log** (a timed run never mass-hides silently and never fails
38
+ the unit over a safety decision). Preflight names the systemd user
39
+ session requirement with the `loginctl enable-linger` remedy
40
+ (documented; never executed by the tool).
41
+
19
42
  ## [5.0.7] — 2026-09-29
20
43
 
21
44
  ### Added (phase 24: Token-First Sessions & Preflight Sweep)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pushframe
3
- Version: 5.0.7
3
+ Version: 5.1.0
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
@@ -126,6 +126,54 @@ retry, no re-login — both feed the trip). The drill:
126
126
  Progress is never lost: only confirmed writes are remembered, and the next
127
127
  run uploads the remainder once — never twice.
128
128
 
129
+ ## Pairs — one album → several frames (and back)
130
+
131
+ Named album↔frame mappings live in the config (phase 25):
132
+
133
+ ```bash
134
+ pushframe config pair add cadre-venus --album Cadre --frame "Cadre de Fabrice"
135
+ pushframe config pair list # name → album → frame (+ state paths)
136
+ pushframe config pair remove cadre-venus
137
+ ```
138
+
139
+ Run one pair or every pair:
140
+
141
+ ```bash
142
+ pushframe google-sync "Album X" --pair cadre-venus --apply --yes
143
+ pushframe google-sync "Album X" --all --apply --yes
144
+ ```
145
+
146
+ `--all` runs every pair in sorted-name order with **one shared write
147
+ budget** capping the total (account-level SAFE-02). A failing pair is
148
+ reported and never blocks the others; exit 1 if any pair failed. Mirror
149
+ state is **per pair** (manifest under `~/.config/pushframe/pairs/<name>/`,
150
+ cache under `~/.local/state/pushframe/pairs/<name>/cache/`) — two pairs
151
+ never share dedupe memory.
152
+
153
+ ## Scheduling — systemd USER timers (no root)
154
+
155
+ ```bash
156
+ pushframe schedule add nightly --pair cadre-venus --every 1d
157
+ pushframe schedule list
158
+ pushframe schedule remove nightly
159
+ ```
160
+
161
+ Installs `pushframe-<job>.service/.timer` under `~/.config/systemd/user/`.
162
+ Everything comes from stored config (token session, pair spec) — a timed
163
+ run NEVER prompts. `Restart=no` on purpose: on failure (including the
164
+ anti-abuse trip, which pushframe detects and stops at the first refusal)
165
+ the unit just ends; the next tick is the retry. Logs land in
166
+ `~/.local/state/pushframe/<job>.log`.
167
+
168
+ Two safety properties in scheduled mode:
169
+
170
+ - **SAFE-02 flips to skip-and-log**: a plan that would mass-hide over the
171
+ threshold is NOT applied — the run logs `SKIPPED (--scheduled)` and exits
172
+ 0 (review manually; the unit must not fail over a safety decision).
173
+ - **Headless hosts** (venus & co): timers fire without an active session
174
+ only if you enable lingering once:
175
+ `loginctl enable-linger $USER` (run it yourself; the tool never does).
176
+
129
177
  ## `config` — set up credentials and settings once
130
178
 
131
179
  The conversational alternative to hand-managed environment variables. Every value
@@ -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.0.7"
4
+ __version__ = "5.1.0"
@@ -87,9 +87,13 @@ def build_parser() -> argparse.ArgumentParser:
87
87
  help='Session/frames checks only (no test image written)')
88
88
  doctor_parser.add_argument('--debug', action='store_true', default=False)
89
89
  config_parser = subparsers.add_parser(
90
- 'config', help='Interactive setup wizard; subcommands: show, import, set, get, path')
90
+ 'config', help='Interactive setup wizard; subcommands: show, import, set, get, path, pair')
91
+ # argparse_known: config_args may itself contain --flags (pair add
92
+ # --album A --frame F); parse_known_args would otherwise reject them as
93
+ # unknown top-level options.
91
94
  config_parser.add_argument('config_args', nargs='*', metavar='args',
92
- help='show | import [--file F] | set <k> <v> | get <k> | path')
95
+ help='show | import [--file F] | set <k> <v> | '
96
+ 'get <k> | path | pair add/remove/list')
93
97
  inspect_parser = subparsers.add_parser('inspect', help="Inspect a frame's photos and metadata")
94
98
  inspect_parser.add_argument('--frame', required=True, help='Frame name (substring) or id')
95
99
  sync_parser = subparsers.add_parser('sync', help='Dry-run diff a local directory against a frame')
@@ -190,8 +194,26 @@ def build_parser() -> argparse.ArgumentParser:
190
194
  gsync_parser.add_argument(
191
195
  'album', help='Album share URL, AF1Qip… id, or album-name substring')
192
196
  gsync_parser.add_argument(
193
- '--frame', required=True,
194
- help='Target frame name substring or id (single album→frame pair)')
197
+ '--frame', default=None,
198
+ help='Target frame name substring or id (single album→frame pair). '
199
+ 'Use --all instead to run every configured pair')
200
+ gsync_parser.add_argument(
201
+ '--all', action='store_true', default=False, dest='all_pairs',
202
+ help='Run every configured pair (album/frame come from config; state '
203
+ 'shards per pair; ONE shared write budget caps the total)')
204
+ gsync_parser.add_argument(
205
+ '--pair', default=None,
206
+ help='Run one named pair (album/frame from the config store)')
207
+ gsync_parser.add_argument(
208
+ '--scheduled', action='store_true', default=False,
209
+ help='Timed-run semantics: SAFE-02 threshold breach SKIPS AND LOGS '
210
+ 'instead of proceeding (used by pushframe schedule units)')
211
+ sched_parser = subparsers.add_parser(
212
+ 'schedule', help='Install/list/remove systemd USER timers '
213
+ '(no root; runs from stored config, never prompts)')
214
+ sched_parser.add_argument('schedule_args', nargs='*', metavar='args',
215
+ help='add <job> --pair <name> --every Nmin|Nh|Nd | '
216
+ 'list | remove <job>')
195
217
  gsync_parser.add_argument(
196
218
  '--apply', action='store_true', default=False,
197
219
  help='Execute the plan (uploads + hides). Without it, only print the plan')
@@ -276,6 +298,38 @@ def run_config(wizard_args=None, stdin_isatty: bool | None = None) -> int:
276
298
  is_tty = _sys.stdin.isatty() if stdin_isatty is None else stdin_isatty
277
299
 
278
300
  # --- subcommands -------------------------------------------------------
301
+ if args and args[0] == 'pair':
302
+ # Phase 25 (MTF-01): the named-pair store (add/remove/list).
303
+ from pushframe import pairs as pairs_mod
304
+ sub = args[1:]
305
+ if not sub or sub[0] == 'list':
306
+ pairs_mod.pair_list()
307
+ return 0
308
+ if sub[0] == 'add' and len(sub) >= 2 and '--album' in sub and '--frame' in sub:
309
+ name = sub[1]
310
+ album = sub[sub.index('--album') + 1]
311
+ frame = sub[sub.index('--frame') + 1]
312
+ try:
313
+ pairs_mod.pair_add(name, album=album, frame=frame)
314
+ except Exception as e:
315
+ print(f'pair not added: {e}')
316
+ return 1
317
+ m, c = pairs_mod.pair_state_paths(name)
318
+ print(f'pair "{name}" saved (album "{album}" → frame "{frame}")')
319
+ print(f' state: {m.parent} (+ cache {c})')
320
+ return 0
321
+ if sub[0] == 'remove' and len(sub) >= 2:
322
+ if pairs_mod.pair_remove(sub[1]):
323
+ print(f'pair "{sub[1]}" removed (its state files, if any, '
324
+ f'are left in place under pairs/{sub[1]}/ — delete '
325
+ f'them manually if you want a clean slate)')
326
+ else:
327
+ print(f'no pair named "{sub[1]}".')
328
+ return 0
329
+ print('usage: pushframe config pair list | pair add <name> --album A '
330
+ '--frame F | pair remove <name>')
331
+ return 1
332
+
279
333
  if args and args[0] == 'show':
280
334
  return _config_show()
281
335
  if args and args[0] == 'path':
@@ -1398,7 +1452,22 @@ def main(argv=None) -> int:
1398
1452
  _notice = migration_notice()
1399
1453
  if _notice:
1400
1454
  print(_notice)
1401
- args = build_parser().parse_args(argv)
1455
+ parser = build_parser()
1456
+ # `config` sub-args carry their own --flags (pair add --album …); split
1457
+ # them out BEFORE argparse so they are not rejected as unknown options.
1458
+ argv_list = list(argv) if argv is not None else None
1459
+ config_tail = []
1460
+ if argv_list is not None and 'config' in argv_list:
1461
+ i = argv_list.index('config')
1462
+ config_tail = argv_list[i + 1:]
1463
+ argv_list = argv_list[:i + 1]
1464
+ elif argv_list is None and 'config' in (sys.argv[1:] or []):
1465
+ i = sys.argv.index('config')
1466
+ config_tail = sys.argv[i + 1:]
1467
+ sys.argv = sys.argv[:i + 1]
1468
+ args = parser.parse_args(argv_list)
1469
+ if args.command == 'config' and config_tail:
1470
+ args.config_args = config_tail
1402
1471
 
1403
1472
  if args.command == 'config':
1404
1473
  return run_config(wizard_args=getattr(args, 'config_args', []) or [])
@@ -1406,6 +1475,27 @@ def main(argv=None) -> int:
1406
1475
  from pushframe.doctor import run_doctor
1407
1476
  return run_doctor(args.frame, do_write=not args.no_write,
1408
1477
  debug=args.debug)
1478
+ if args.command == 'schedule':
1479
+ from pushframe import schedule as sch
1480
+ sub = list(args.schedule_args or [])
1481
+ if not sub or sub[0] == 'list':
1482
+ return sch.schedule_list()
1483
+ if sub[0] == 'add' and len(sub) >= 2:
1484
+ job = sub[1]
1485
+ def _opt(flag, default=None):
1486
+ return sub[sub.index(flag) + 1] if flag in sub else default
1487
+ return sch.schedule_add(job, pair=_opt('--pair'),
1488
+ album=_opt('--album'),
1489
+ frame=_opt('--frame'),
1490
+ sync_dir=_opt('--sync-dir'),
1491
+ every=_opt('--every'), at=_opt('--at'),
1492
+ batch_size=int(_opt('--batch-size'))
1493
+ if _opt('--batch-size') else None)
1494
+ if sub[0] == 'remove' and len(sub) >= 2:
1495
+ return sch.schedule_remove(sub[1])
1496
+ print('usage: pushframe schedule add <job> --pair <name> --every Nmin|Nh|Nd '
1497
+ '| schedule list | schedule remove <job>')
1498
+ return 1
1409
1499
  if args.command == 'logout':
1410
1500
  return run_logout()
1411
1501
  if args.command == 'status':
@@ -1416,9 +1506,16 @@ def main(argv=None) -> int:
1416
1506
  return run_google_album(args.target, list_all=args.list, debug=args.debug)
1417
1507
  if args.command == 'google-sync':
1418
1508
  from pushframe.gsync import run_google_sync
1419
- return run_google_sync(args.album, args.frame, apply=args.apply,
1420
- yes=args.yes, debug=args.debug,
1421
- batch_size=args.batch_size)
1509
+ if not args.all_pairs and not args.frame:
1510
+ print('google-sync: give --frame FRAME (one pair) or --all '
1511
+ '(every configured pair)')
1512
+ return 2
1513
+ return run_google_sync(args.album, args.frame or '--all',
1514
+ apply=args.apply, yes=args.yes,
1515
+ debug=args.debug, batch_size=args.batch_size,
1516
+ pair=args.pair,
1517
+ run_all=args.all_pairs,
1518
+ scheduled=args.scheduled)
1422
1519
  if args.command == 'inspect':
1423
1520
  return run_inspect(args.frame, debug=args.debug)
1424
1521
  if args.command == 'sync':