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.
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/STATE.md +5 -5
- 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
- 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
- pushframe-5.1.0/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-CONTEXT.md +65 -0
- pushframe-5.1.0/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-RESEARCH.md +109 -0
- pushframe-5.1.0/.planning/phases/25-multi-frame-mappings-systemd-user-scheduling-mtf-01-03-tmr-01-03/25-VERIFICATION.md +50 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/CHANGELOG.md +23 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/PKG-INFO +1 -1
- {pushframe-5.0.7 → pushframe-5.1.0}/docs/CLI.md +48 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/__init__.py +1 -1
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/cli.py +105 -8
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/gsync.py +66 -1
- pushframe-5.1.0/pushframe/pairs.py +81 -0
- pushframe-5.1.0/pushframe/schedule.py +185 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pyproject.toml +1 -1
- pushframe-5.1.0/scripts/test-multipair-journey-container.sh +146 -0
- pushframe-5.1.0/tests/test_gsync_all.py +174 -0
- pushframe-5.1.0/tests/test_pairs.py +91 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_preflight_sweep.py +63 -0
- pushframe-5.1.0/tests/test_schedule.py +163 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_version.py +1 -1
- {pushframe-5.0.7 → pushframe-5.1.0}/uv.lock +1 -1
- {pushframe-5.0.7 → pushframe-5.1.0}/.env.sample +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.freebuff/project-id +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.github/workflows/release.yml +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.github/workflows/tests.yml +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.gitignore +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.gsd/dispatch-isolation-sentinel.json +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/MILESTONES.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/PROJECT.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/REQUIREMENTS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/RETROSPECTIVE.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/ROADMAP.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/WINDOWS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/codebase/ARCHITECTURE.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/codebase/CONCERNS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/codebase/CONVENTIONS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/codebase/INTEGRATIONS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/codebase/STACK.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/codebase/STRUCTURE.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/codebase/TESTING.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/config.json +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/debug/knowledge-base.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/debug/login-475-null-creds.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/debug/resolved/cli-status-verbose-logging.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/debug/resolved/inspect-asset-null-fields.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/debug/resolved/select-asset-401-unauthorized.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-REQUIREMENTS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-ROADMAP.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-01-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-02-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-CONTEXT.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-PATTERNS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-RESEARCH.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-REVIEW.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/01-toolchain-revival/01-VERIFICATION.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-01-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-02-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-CONTEXT.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-LIVE-EVIDENCE.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-PATTERNS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-RESEARCH.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-REVIEW.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/02-live-read-path-verification/02-VERIFICATION.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-01-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-CONTEXT.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-PATTERNS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-RESEARCH.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-REVIEW.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.0-phases/03-run-docs-verification-report/03-VERIFICATION.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-ROADMAP.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v1.1-phases/04-client-transport-seam-for-offline-testability/.gitkeep +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-REQUIREMENTS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-ROADMAP.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-01-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-02-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-CONTEXT.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-PATTERNS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-REVIEW.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-SECURITY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-UAT.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/05-cli-skeleton-status/05-VERIFICATION.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-01-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-02-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-CONTEXT.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-PATTERNS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-RESEARCH.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-REVIEW.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-SECURITY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/06-inspect-frame-resolution/06-VERIFICATION.md +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-CONTEXT.md +0 -0
- {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
- {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
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-PATTERNS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-RESEARCH.md +0 -0
- {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
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-REVIEW.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/08-destructive-execution-upload-delete-verification/08-VERIFICATION.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/09-proactive-write-rate-limiter-geo-guard/.gitkeep +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/.gitkeep +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-phases/10-hide-instead-of-delete-sync-mode/COVERAGE.md +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v2.0-quick/README.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-REQUIREMENTS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-ROADMAP.md +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-CONTEXT.md +0 -0
- {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
- {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
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-PATTERNS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-REVIEW.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v3.0-phases/11-write-path-reliability-format-support/11-VERIFICATION.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-REQUIREMENTS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-ROADMAP.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-01-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-02-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-03-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-CONTEXT.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DECISION-RECORD.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-DISCUSSION-LOG.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/16-LIVE-FINDINGS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/16-local-mechanism-spike-decision/COVERAGE.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-01-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-02-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-CONTEXT.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-DISCUSS-CHECKPOINT.json +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/17-VERIFICATION.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/17-google-link-album-selection/COVERAGE.md +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/19-debt-closeout/19-01-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/19-debt-closeout/19-02-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/19-debt-closeout/19-CONTEXT.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/19-debt-closeout/19-RESEARCH.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v4.0-phases/19-debt-closeout/19-VERIFICATION.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-REQUIREMENTS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-ROADMAP.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/20-pushframe-rename-migration-repo-switch/20-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-01-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-02-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-CONTEXT.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-RESEARCH.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/21-debian-package-apt-repo/21-VERIFICATION.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-01-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-02-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-CONTEXT.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-RESEARCH.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/22-VERIFICATION.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/milestones/v5.0-phases/22-pypi-release-engineering/OPERATOR-STEPS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/phases/23-config-wizard-precedence/23-01-PLAN.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/phases/23-config-wizard-precedence/23-01-SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/phases/23-config-wizard-precedence/23-CONTEXT.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/phases/23-config-wizard-precedence/23-RESEARCH.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/phases/23-config-wizard-precedence/23-VERIFICATION.md +0 -0
- {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
- {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
- {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
- {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
- {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
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/ALBUM-ACCESS-V4-ADDENDUM.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/ALBUM-ACCESS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/ARCHITECTURE.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/BROWSER-AUTOMATION.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/FEATURES.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/PITFALLS.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/STACK.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/research/SUMMARY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/.planning/state.json +0 -0
- {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
- {pushframe-5.0.7 → pushframe-5.1.0}/.python-version +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/CLAUDE.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/LICENSE +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/README.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/SECURITY.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/VERIFICATION-REPORT.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/docs/agents/domain.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/docs/agents/issue-tracker.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/docs/agents/triage-labels.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/docs/superpowers/specs/2026-07-09-write-rate-limiter-design.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/main.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/packaging/control-template +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/packaging/lintian-overrides +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/packaging/postinst +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/packaging/pushframe.copyright +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/probes/.gitignore +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/probes/README.md +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/probes/browser_bootstrap.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/probes/common.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/probes/cookie_vault.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/probes/fidelity_check.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/probes/rpc_capture.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/probes/shared_link_probe.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/__init__.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/accountApi.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/activityApi.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/assetApi.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/baseApi.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/frameApi.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/notificationApi.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/peopleApi.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/api/playlistApi.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/aura.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/aws/__init__.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/aws/awsclient.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/aws/s3client.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/aws/sqsclient.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/cache.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/client.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/config_store.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/doctor.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/exif.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/export.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/__init__.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/bootstrap.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/cache.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/client.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/enumerate.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/manifest.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/parsers.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/redaction.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/google/vault.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/migration.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/models/__init__.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/models/activity.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/models/asset.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/models/frame.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/models/meta.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/models/person.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/models/user.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/preflight.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/ratelimit.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/reconcile.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/session.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/sync.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/utils/dt.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/utils/io.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/pushframe/utils/settings.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/scripts/build-deb.sh +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/scripts/make-apt-key.sh +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/scripts/publish-apt-repo.sh +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/scripts/test-apt-journey-container.sh +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/scripts/test-config-journey-container.sh +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/scripts/test-deb-container.sh +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/scripts/test-install-journey-container.sh +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/conftest.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/asset_unprocessed.json +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/assets_page1.json +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/assets_page2.json +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/assets_placeholders.json +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/error_envelope.json +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/frame_detail.json +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/frames.json +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/google_rpc_snackc_page.json +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/google_share_page_sample.html +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/login.json +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/fixtures/probe_af_initdata_sample.html +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/offline.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_asset_partial.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_asset_unprocessed.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_aura_logger.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_batch_update_contract.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_apply.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_google_album.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_google_link.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_inspect.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_push_budget_geo.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_reconcile.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_status.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_status_google.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_cli_sync.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_client_rate_limit.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_config_wizard.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_doctor.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_execute_plan.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_execute_plan_budget_geo.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_fixtures_validity.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_google_cache.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_google_client.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_google_enumerate.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_google_manifest.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_google_parsers.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_google_vault.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_gsync_engine.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_gsync_execute.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_imports.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_logout.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_migration.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_offline_read_path.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_prep_upload_formats.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_probe_browser.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_probe_shared_link.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_ratelimit.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_read_path.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_reconcile.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_retry_401.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_session.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_settings_budget.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_settings_resolution.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_sync_engine.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_write_endpoints_failloud.py +0 -0
- {pushframe-5.0.7 → pushframe-5.1.0}/tests/test_write_formats_live.py +0 -0
- {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:
|
|
9
|
-
Current Plan:
|
|
10
|
-
Total Plans in Phase: 1 (phase
|
|
11
|
-
Status: Verified —
|
|
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
|
-
- [
|
|
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
|
|
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
|
|
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> |
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
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':
|