gflow-cli 0.6.0a3__tar.gz → 0.6.0a4__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.
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/CHANGELOG.md +27 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/PKG-INFO +1 -1
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/pyproject.toml +1 -1
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/__init__.py +1 -1
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/cli_image.py +4 -3
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/cli_run.py +17 -104
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/image_batch.py +104 -21
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/paths.py +15 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/cli/test_cli_run.py +15 -7
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/cli/test_t2i_multi_prompt.py +5 -5
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/.claude/README.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/.claude/commands/release.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/.env.template +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/.gitattributes +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/.github/workflows/ci.yml +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/.github/workflows/release.yml +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/.gitignore +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/.planning/todos/pending/2026-05-11-add-project-logo-and-docs-site-promotion-plan.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/CLAUDE.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/CONFIGURATION.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/CONTRIBUTING.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/DISCLAIMER.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/KNOWN_ISSUES.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/LICENSE +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/PLAN.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/README.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/RELEASE.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/ARCHITECTURE.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/AUTHENTICATION.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/CONFIGURATION.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/INDEX.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/SECURITY.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/USAGE.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/USER_GUIDE.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/assets/example-run.gif +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-09-image-mvp-orchestration.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-09-image-mvp.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-09-video-mvp-orchestration.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-09-video-mvp.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-10-phase-4-hardening-orchestration.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-10-phase-4-hardening.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-14-shell-multi-prompt/2026-05-14-shell-multi-prompt-orchestration.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-14-shell-multi-prompt/COUNCIL_FINAL_ARCH.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-14-shell-multi-prompt/COUNCIL_FINAL_SEC_UX.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-14-shell-multi-prompt/COUNCIL_REVIEW_CODE.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-14-shell-multi-prompt/COUNCIL_REVIEW_GEMINI.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-14-shell-multi-prompt/COUNCIL_REVIEW_SECURITY.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-14-shell-multi-prompt/IMPLEMENTATION_REVIEW_PYTHON.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-14-shell-multi-prompt/IMPLEMENTATION_REVIEW_SECURITY.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-14-shell-multi-prompt/PLAN.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-14-shell-multi-prompt/PLAN_REVIEW_CODE.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-14-shell-multi-prompt/PLAN_REVIEW_FOLLOWUP.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-14-shell-multi-prompt/PLAN_REVIEW_PLANNER.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-14-shell-multi-prompt/PLAN_REVIEW_SECURITY.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-14-shell-multi-prompt/PLAN_REVIEW_SECURITY_FOLLOWUP.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-15-auth-login-real-chrome/COUNCIL_FINAL_SEC_UX_VERIFIED.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-15-auth-login-real-chrome/COUNCIL_REVIEW_PLAN_SECURITY.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-15-auth-login-real-chrome/COUNCIL_REVIEW_SPEC_SECURITY.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-15-auth-login-real-chrome/PLAN.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-15-auth-login-real-chrome/orchestration.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/specs/2026-05-10-phase-4-hardening-design.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/specs/2026-05-14-shell-multi-prompt-design.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/specs/2026-05-15-auth-login-real-chrome-design.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/verifications/2026-05-11-phase-4-stage-g.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/examples/README.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/examples/batch_from_config.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/examples/multi_prompt_t2i.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/examples/sample_config.json +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/examples/sample_prompts.txt +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/examples/single_image_t2i.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/samples/README.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/samples/captured/01_upload_image.json +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/samples/captured/02_batchAsyncGenerateVideoText.json +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/samples/captured/03_batchCheckAsyncVideoGenerationStatus.json +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/samples/captured/04_archive_workflow.json +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/samples/captured/05_createProject.json +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/samples/captured/06_batchGenerateImages.json +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/samples/captured/07_batchGenerateImages_seeded.json +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/scripts/diag_capture_flow_traffic.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/scripts/diag_recaptcha_mint.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/scripts/record_demo.ps1 +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/scripts/smoke_e2e.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/scripts/smoke_image.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/scripts/smoke_real_chrome_image.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/scripts/smoke_worker_style.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/scripts/verify_chrome_auth_viability.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/skills/README.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/skills/gflow-cli/SKILL.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/__main__.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/_cli_helpers.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/__init__.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/_retry.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/client.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/dto.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/image.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/recaptcha.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/routes.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/transports/__init__.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/transports/_common.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/transports/_fingerprint.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/transports/base.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/transports/experimental/__init__.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/transports/experimental/bearer.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/transports/experimental/evaluate_fetch.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/transports/experimental/sapisidhash.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/transports/ui_automation.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/api/video.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/auth/__init__.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/auth/base.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/auth/factory.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/auth/internal_chromium.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/auth/real_chrome.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/auth/strategies.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/browser_manager.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/cli.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/cli_video.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/config.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/errors.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/manifest.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/observability.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/src/gflow_cli/profile_store.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tasks/lessons.md +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/__init__.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/__init__.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/test_client.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/test_client_generate_video.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/test_client_image.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/test_concurrency.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/test_dto.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/test_image.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/test_image_dto.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/test_recaptcha.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/test_retry.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/test_routes.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/test_video.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/transports/__init__.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/transports/test_base.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/transports/test_bearer.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/transports/test_common.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/transports/test_evaluate_fetch.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/transports/test_factory.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/transports/test_fingerprint.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/transports/test_sapisidhash.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/api/transports/test_ui_automation.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/auth/strategies/test_factory.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/auth/strategies/test_strategies.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/cli/__init__.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/cli/test_cli_image.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/cli/test_error_handling.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/cli/test_helpers.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/conftest.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/e2e/__init__.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/e2e/test_transports_e2e.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/features/__init__.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/features/auth.feature +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/features/auth_login.feature +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/features/conftest.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/features/image.feature +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/features/test_auth_login_steps.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/features/test_auth_steps.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/features/test_image_steps.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/features/test_step_collision_guard.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/features/test_video_steps.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/features/video.feature +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/smoke/__init__.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/smoke/test_real_flow.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/test_auth.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/test_browser_manager.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/test_cli_video.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/test_config.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/test_errors.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/test_manifest.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/test_observability.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/test_paths.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/test_profile_store.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/tests/test_smoke.py +0 -0
- {gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/uv.lock +0 -0
|
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0a4] — 2026-05-17
|
|
11
|
+
|
|
12
|
+
> **Unified output resolution + batch orchestration refactor.** This release
|
|
13
|
+
> aligns the CLI output structure across all commands and refactors the batch
|
|
14
|
+
> runner to be more generic, preparing the codebase for Phase 6.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`resolve_batch_output_dir` helper** in `paths.py` — centralizes the
|
|
19
|
+
date-partitioned output directory logic used by all generation commands.
|
|
20
|
+
- **`parse_batch_item_dict` helper** in `image_batch.py` — deduplicates JSON
|
|
21
|
+
prompt validation between `gflow run` and other batch sources.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **`gflow run` output directory** — now defaults to date-partitioned
|
|
26
|
+
`$GFLOW_CLI_OUTPUT_DIR/images/<YYYY-MM-DD>/` instead of the legacy
|
|
27
|
+
`out/<UTC-timestamp>/`, matching the `gflow image` convention.
|
|
28
|
+
- **Refactored `run_image_batch`** into a generic `run_sequential_batch`
|
|
29
|
+
orchestrator — now accepts a swappable worker callback, allowing for uniform
|
|
30
|
+
video and image batch handling in the future.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- Removed ~80 lines of duplicate validation logic from `cli_run.py`.
|
|
35
|
+
- Corrected test imports and expectations for unified output resolution.
|
|
36
|
+
|
|
10
37
|
## [0.6.0a3] — 2026-05-17
|
|
11
38
|
|
|
12
39
|
> **Deterministic timeouts + agent-friendly exit codes.** This release hardens
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gflow-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.0a4
|
|
4
4
|
Summary: Unofficial CLI for Google Flow — drive Veo image-to-video generations from the terminal.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ffroliva/gflow-cli
|
|
6
6
|
Project-URL: Issues, https://github.com/ffroliva/gflow-cli/issues
|
|
@@ -60,13 +60,12 @@ from gflow_cli.image_batch import (
|
|
|
60
60
|
prompt_items_from_texts,
|
|
61
61
|
read_prompt_file,
|
|
62
62
|
render_image_batch_summary,
|
|
63
|
-
resolve_t2i_batch_output_dir,
|
|
64
63
|
run_image_batch,
|
|
65
64
|
)
|
|
66
65
|
from gflow_cli.image_batch import (
|
|
67
66
|
MIN_COUNT as _MIN_COUNT,
|
|
68
67
|
)
|
|
69
|
-
from gflow_cli.paths import image_output_path
|
|
68
|
+
from gflow_cli.paths import image_output_path, resolve_batch_output_dir
|
|
70
69
|
|
|
71
70
|
# Case-insensitive 8-4-4-4-12 hex with hyphens — Flow's media UUIDs.
|
|
72
71
|
# When a `--ref` value matches this regex it's treated as an already-uploaded
|
|
@@ -417,7 +416,9 @@ def t2i(
|
|
|
417
416
|
profile_name = _resolve_profile(profile)
|
|
418
417
|
provider_dir = _make_provider_dir(profile_name)
|
|
419
418
|
settings = get_settings()
|
|
420
|
-
output_dir =
|
|
419
|
+
output_dir = resolve_batch_output_dir(
|
|
420
|
+
cli_override=out, output_root=settings.output_dir, kind="images"
|
|
421
|
+
)
|
|
421
422
|
console.print(
|
|
422
423
|
f"\n[bold]gflow image t2i[/bold] · profile=[bold]{profile_name}[/bold] "
|
|
423
424
|
f"· {len(batch_prompts)} prompt(s) · up to {len(batch_prompts) * count} image(s)"
|
|
@@ -15,7 +15,6 @@ from __future__ import annotations
|
|
|
15
15
|
import asyncio
|
|
16
16
|
import json
|
|
17
17
|
import sys
|
|
18
|
-
import time
|
|
19
18
|
from dataclasses import dataclass
|
|
20
19
|
from pathlib import Path
|
|
21
20
|
from typing import Any, cast
|
|
@@ -29,45 +28,16 @@ from gflow_cli.api.transports import EXPERIMENTAL_TRANSPORTS
|
|
|
29
28
|
from gflow_cli.config import get_settings
|
|
30
29
|
from gflow_cli.errors import ConfigurationError
|
|
31
30
|
from gflow_cli.image_batch import (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
from gflow_cli.image_batch import (
|
|
35
|
-
ALLOWED_MODELS as _ALLOWED_MODELS,
|
|
36
|
-
)
|
|
37
|
-
from gflow_cli.image_batch import (
|
|
38
|
-
DEFAULT_ASPECT_RATIO as _DEFAULT_ASPECT_RATIO,
|
|
39
|
-
)
|
|
40
|
-
from gflow_cli.image_batch import (
|
|
41
|
-
DEFAULT_COUNT as _DEFAULT_COUNT,
|
|
42
|
-
)
|
|
43
|
-
from gflow_cli.image_batch import (
|
|
44
|
-
DEFAULT_MODEL as _DEFAULT_MODEL,
|
|
45
|
-
)
|
|
46
|
-
from gflow_cli.image_batch import (
|
|
47
|
-
MAX_COUNT as _MAX_COUNT,
|
|
48
|
-
)
|
|
49
|
-
from gflow_cli.image_batch import (
|
|
50
|
-
MAX_PROMPTS as _MAX_PROMPTS,
|
|
51
|
-
)
|
|
52
|
-
from gflow_cli.image_batch import (
|
|
53
|
-
MAX_TEXT_LEN as _MAX_TEXT_LEN,
|
|
54
|
-
)
|
|
55
|
-
from gflow_cli.image_batch import (
|
|
56
|
-
MIN_COUNT as _MIN_COUNT,
|
|
57
|
-
)
|
|
58
|
-
from gflow_cli.image_batch import (
|
|
59
|
-
MIN_PROMPTS as _MIN_PROMPTS,
|
|
60
|
-
)
|
|
61
|
-
from gflow_cli.image_batch import (
|
|
62
|
-
MIN_TEXT_LEN as _MIN_TEXT_LEN,
|
|
63
|
-
)
|
|
64
|
-
from gflow_cli.image_batch import (
|
|
31
|
+
MAX_PROMPTS,
|
|
32
|
+
MIN_PROMPTS,
|
|
65
33
|
BatchOutcome,
|
|
66
34
|
BatchPromptItem,
|
|
35
|
+
parse_batch_item_dict,
|
|
67
36
|
render_image_batch_summary,
|
|
68
37
|
resolve_exit_code,
|
|
69
38
|
run_image_batch,
|
|
70
39
|
)
|
|
40
|
+
from gflow_cli.paths import resolve_batch_output_dir
|
|
71
41
|
|
|
72
42
|
console = Console()
|
|
73
43
|
|
|
@@ -75,9 +45,6 @@ console = Console()
|
|
|
75
45
|
_ALLOWED_TOP_LEVEL_KEYS: frozenset[str] = frozenset(
|
|
76
46
|
{"profile", "transport", "output_dir", "prompts"}
|
|
77
47
|
)
|
|
78
|
-
_ALLOWED_PROMPT_KEYS: frozenset[str] = frozenset(
|
|
79
|
-
{"text", "aspect_ratio", "model", "count", "output_filename"}
|
|
80
|
-
)
|
|
81
48
|
# ---------------------------------------------------------------------------
|
|
82
49
|
# Dataclasses — validated config + per-prompt outcome.
|
|
83
50
|
# ---------------------------------------------------------------------------
|
|
@@ -129,14 +96,17 @@ class BatchConfig:
|
|
|
129
96
|
if not isinstance(prompts_raw_obj, list):
|
|
130
97
|
raise ConfigurationError("'prompts' must be a JSON array.")
|
|
131
98
|
prompts_raw = cast("list[Any]", prompts_raw_obj)
|
|
132
|
-
|
|
99
|
+
|
|
100
|
+
if not (MIN_PROMPTS <= len(prompts_raw) <= MAX_PROMPTS):
|
|
133
101
|
raise ConfigurationError(
|
|
134
|
-
f"'prompts' must have between {
|
|
135
|
-
f"{
|
|
102
|
+
f"'prompts' must have between {MIN_PROMPTS} and "
|
|
103
|
+
f"{MAX_PROMPTS} entries (got {len(prompts_raw)})."
|
|
136
104
|
)
|
|
137
105
|
prompts: list[BatchPromptItem] = []
|
|
138
106
|
for idx, p in enumerate(prompts_raw):
|
|
139
|
-
|
|
107
|
+
if not isinstance(p, dict):
|
|
108
|
+
raise ConfigurationError(f"prompts[{idx}] must be a JSON object.")
|
|
109
|
+
prompts.append(parse_batch_item_dict(cast("dict[str, Any]", p), idx))
|
|
140
110
|
|
|
141
111
|
profile = data.get("profile")
|
|
142
112
|
if profile is not None and (not isinstance(profile, str) or not profile):
|
|
@@ -155,73 +125,12 @@ class BatchConfig:
|
|
|
155
125
|
output_dir=output_dir,
|
|
156
126
|
)
|
|
157
127
|
|
|
158
|
-
@staticmethod
|
|
159
|
-
def _parse_prompt(p: object, idx: int) -> BatchPromptItem:
|
|
160
|
-
if not isinstance(p, dict):
|
|
161
|
-
raise ConfigurationError(f"prompts[{idx}] must be a JSON object.")
|
|
162
|
-
item = cast("dict[str, Any]", p)
|
|
163
|
-
unknown = set(item) - _ALLOWED_PROMPT_KEYS
|
|
164
|
-
if unknown:
|
|
165
|
-
raise ConfigurationError(
|
|
166
|
-
f"prompts[{idx}] has unknown key(s) {sorted(unknown)!r}. "
|
|
167
|
-
f"Valid: {sorted(_ALLOWED_PROMPT_KEYS)!r}."
|
|
168
|
-
)
|
|
169
|
-
text_raw = item.get("text")
|
|
170
|
-
if not isinstance(text_raw, str):
|
|
171
|
-
raise ConfigurationError(f"prompts[{idx}].text must be a string.")
|
|
172
|
-
if not (_MIN_TEXT_LEN <= len(text_raw) <= _MAX_TEXT_LEN):
|
|
173
|
-
raise ConfigurationError(
|
|
174
|
-
f"prompts[{idx}].text length must be between {_MIN_TEXT_LEN} "
|
|
175
|
-
f"and {_MAX_TEXT_LEN} (got {len(text_raw)})."
|
|
176
|
-
)
|
|
177
|
-
aspect_ratio = item.get("aspect_ratio", _DEFAULT_ASPECT_RATIO)
|
|
178
|
-
if aspect_ratio not in _ALLOWED_ASPECT_RATIOS:
|
|
179
|
-
raise ConfigurationError(
|
|
180
|
-
f"prompts[{idx}].aspect_ratio {aspect_ratio!r} is invalid. "
|
|
181
|
-
f"Valid: {list(_ALLOWED_ASPECT_RATIOS)!r}."
|
|
182
|
-
)
|
|
183
|
-
model = item.get("model", _DEFAULT_MODEL)
|
|
184
|
-
if model not in _ALLOWED_MODELS:
|
|
185
|
-
raise ConfigurationError(
|
|
186
|
-
f"prompts[{idx}].model {model!r} is invalid. Valid: {list(_ALLOWED_MODELS)!r}."
|
|
187
|
-
)
|
|
188
|
-
count = item.get("count", _DEFAULT_COUNT)
|
|
189
|
-
if not isinstance(count, int) or isinstance(count, bool):
|
|
190
|
-
raise ConfigurationError(f"prompts[{idx}].count must be an integer.")
|
|
191
|
-
if not (_MIN_COUNT <= count <= _MAX_COUNT):
|
|
192
|
-
raise ConfigurationError(
|
|
193
|
-
f"prompts[{idx}].count must be between {_MIN_COUNT} and {_MAX_COUNT} (got {count})."
|
|
194
|
-
)
|
|
195
|
-
output_filename = item.get("output_filename")
|
|
196
|
-
if output_filename is not None and (
|
|
197
|
-
not isinstance(output_filename, str) or not output_filename
|
|
198
|
-
):
|
|
199
|
-
raise ConfigurationError(f"prompts[{idx}].output_filename must be a non-empty string.")
|
|
200
|
-
return BatchPromptItem(
|
|
201
|
-
text=text_raw,
|
|
202
|
-
aspect_ratio=aspect_ratio,
|
|
203
|
-
model=model,
|
|
204
|
-
count=count,
|
|
205
|
-
output_filename=output_filename,
|
|
206
|
-
index=idx,
|
|
207
|
-
)
|
|
208
|
-
|
|
209
128
|
|
|
210
129
|
# ---------------------------------------------------------------------------
|
|
211
|
-
# Helpers —
|
|
130
|
+
# Helpers — experimental-transport gating.
|
|
212
131
|
# ---------------------------------------------------------------------------
|
|
213
132
|
|
|
214
133
|
|
|
215
|
-
def _resolve_output_dir(*, cli_override: Path | None, config_value: str | None) -> Path:
|
|
216
|
-
"""CLI flag > config value > default (``out/<UTC-timestamp>/``)."""
|
|
217
|
-
if cli_override is not None:
|
|
218
|
-
return cli_override
|
|
219
|
-
if config_value is not None:
|
|
220
|
-
return Path(config_value)
|
|
221
|
-
stamp = time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
|
|
222
|
-
return Path("out") / stamp
|
|
223
|
-
|
|
224
|
-
|
|
225
134
|
def _check_transport_gated(transport: str | None) -> None:
|
|
226
135
|
"""Per spec D.1 rule 5: experimental transports require env var.
|
|
227
136
|
|
|
@@ -325,7 +234,11 @@ def run(
|
|
|
325
234
|
profile_name = _resolve_profile(profile or cfg.profile)
|
|
326
235
|
provider_dir = _make_provider_dir(profile_name)
|
|
327
236
|
settings = get_settings()
|
|
328
|
-
output_dir =
|
|
237
|
+
output_dir = resolve_batch_output_dir(
|
|
238
|
+
cli_override=output_dir_override,
|
|
239
|
+
config_value=cfg.output_dir,
|
|
240
|
+
output_root=settings.output_dir,
|
|
241
|
+
)
|
|
329
242
|
|
|
330
243
|
console.print(
|
|
331
244
|
f"\n[bold]gflow run[/bold] · profile=[bold]{profile_name}[/bold] "
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
|
-
from collections.abc import Callable
|
|
6
|
+
from collections.abc import Callable, Coroutine
|
|
7
7
|
from dataclasses import dataclass, field
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import TYPE_CHECKING, Any
|
|
@@ -18,6 +18,7 @@ from gflow_cli._cli_helpers import (
|
|
|
18
18
|
from gflow_cli.api.client import FlowApiClient
|
|
19
19
|
from gflow_cli.api.image import Aspect, GenerateImageRequest, Model
|
|
20
20
|
from gflow_cli.errors import EXIT_CODE_MAP, ConfigurationError, GFlowError
|
|
21
|
+
from gflow_cli.paths import resolve_batch_output_dir
|
|
21
22
|
|
|
22
23
|
if TYPE_CHECKING:
|
|
23
24
|
from gflow_cli.api.dto import GeneratedImage
|
|
@@ -157,6 +158,63 @@ def read_prompt_file(path: Path) -> tuple[ParsedPromptLine, ...]:
|
|
|
157
158
|
return parse_prompt_lines(text, source_label=label)
|
|
158
159
|
|
|
159
160
|
|
|
161
|
+
_ALLOWED_PROMPT_KEYS: frozenset[str] = frozenset(
|
|
162
|
+
{"text", "aspect_ratio", "model", "count", "output_filename"}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def parse_batch_item_dict(p: dict[str, Any], idx: int) -> BatchPromptItem:
|
|
167
|
+
"""Parse and validate a dictionary (e.g. from JSON) into a BatchPromptItem.
|
|
168
|
+
|
|
169
|
+
Used by `gflow run` to centralize validation logic.
|
|
170
|
+
"""
|
|
171
|
+
unknown = set(p) - _ALLOWED_PROMPT_KEYS
|
|
172
|
+
if unknown:
|
|
173
|
+
raise ConfigurationError(
|
|
174
|
+
f"prompts[{idx}] has unknown key(s) {sorted(unknown)!r}. "
|
|
175
|
+
f"Valid: {sorted(_ALLOWED_PROMPT_KEYS)!r}."
|
|
176
|
+
)
|
|
177
|
+
text_raw = p.get("text")
|
|
178
|
+
if not isinstance(text_raw, str):
|
|
179
|
+
raise ConfigurationError(f"prompts[{idx}].text must be a string.")
|
|
180
|
+
if not (MIN_TEXT_LEN <= len(text_raw) <= MAX_TEXT_LEN):
|
|
181
|
+
raise ConfigurationError(
|
|
182
|
+
f"prompts[{idx}].text length must be between {MIN_TEXT_LEN} "
|
|
183
|
+
f"and {MAX_TEXT_LEN} (got {len(text_raw)})."
|
|
184
|
+
)
|
|
185
|
+
aspect_ratio = p.get("aspect_ratio", DEFAULT_ASPECT_RATIO)
|
|
186
|
+
if aspect_ratio not in ALLOWED_ASPECT_RATIOS:
|
|
187
|
+
raise ConfigurationError(
|
|
188
|
+
f"prompts[{idx}].aspect_ratio {aspect_ratio!r} is invalid. "
|
|
189
|
+
f"Valid: {list(ALLOWED_ASPECT_RATIOS)!r}."
|
|
190
|
+
)
|
|
191
|
+
model = p.get("model", DEFAULT_MODEL)
|
|
192
|
+
if model not in ALLOWED_MODELS:
|
|
193
|
+
raise ConfigurationError(
|
|
194
|
+
f"prompts[{idx}].model {model!r} is invalid. Valid: {list(ALLOWED_MODELS)!r}."
|
|
195
|
+
)
|
|
196
|
+
count = p.get("count", DEFAULT_COUNT)
|
|
197
|
+
if not isinstance(count, int) or isinstance(count, bool):
|
|
198
|
+
raise ConfigurationError(f"prompts[{idx}].count must be an integer.")
|
|
199
|
+
if not (MIN_COUNT <= count <= MAX_COUNT):
|
|
200
|
+
raise ConfigurationError(
|
|
201
|
+
f"prompts[{idx}].count must be between {MIN_COUNT} and {MAX_COUNT} (got {count})."
|
|
202
|
+
)
|
|
203
|
+
output_filename = p.get("output_filename")
|
|
204
|
+
if output_filename is not None and (
|
|
205
|
+
not isinstance(output_filename, str) or not output_filename
|
|
206
|
+
):
|
|
207
|
+
raise ConfigurationError(f"prompts[{idx}].output_filename must be a non-empty string.")
|
|
208
|
+
return BatchPromptItem(
|
|
209
|
+
text=text_raw,
|
|
210
|
+
aspect_ratio=aspect_ratio,
|
|
211
|
+
model=model,
|
|
212
|
+
count=count,
|
|
213
|
+
output_filename=output_filename,
|
|
214
|
+
index=idx,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
160
218
|
def _validate_item_values(
|
|
161
219
|
*,
|
|
162
220
|
aspect_ratio: str,
|
|
@@ -238,15 +296,6 @@ def prompt_items_from_texts(
|
|
|
238
296
|
return tuple(items)
|
|
239
297
|
|
|
240
298
|
|
|
241
|
-
def resolve_t2i_batch_output_dir(*, out: Path | None, output_root: Path) -> Path:
|
|
242
|
-
if out is not None:
|
|
243
|
-
return out
|
|
244
|
-
from datetime import date
|
|
245
|
-
|
|
246
|
-
today = date.today().isoformat()
|
|
247
|
-
return output_root / "images" / today
|
|
248
|
-
|
|
249
|
-
|
|
250
299
|
async def run_one_image_prompt(
|
|
251
300
|
*,
|
|
252
301
|
client: Any,
|
|
@@ -298,26 +347,60 @@ async def run_image_batch(
|
|
|
298
347
|
client_factory: Callable[..., Any] | None = None,
|
|
299
348
|
) -> list[BatchOutcome]:
|
|
300
349
|
"""Run prompts sequentially through one FlowApiClient session."""
|
|
301
|
-
|
|
350
|
+
|
|
351
|
+
async def image_worker(
|
|
352
|
+
client: Any, project_id: str, idx: int, item: BatchPromptItem
|
|
353
|
+
) -> BatchOutcome:
|
|
354
|
+
return await run_one_image_prompt(
|
|
355
|
+
client=client,
|
|
356
|
+
project_id=project_id,
|
|
357
|
+
idx=idx,
|
|
358
|
+
item=item,
|
|
359
|
+
output_dir=output_dir,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
return await run_sequential_batch(
|
|
363
|
+
profile_dir=profile_dir,
|
|
364
|
+
headless=headless,
|
|
365
|
+
transport=transport,
|
|
366
|
+
items=prompts,
|
|
367
|
+
continue_on_error=continue_on_error,
|
|
368
|
+
project_title=project_title,
|
|
369
|
+
worker=image_worker,
|
|
370
|
+
client_factory=client_factory,
|
|
371
|
+
output_dir=output_dir,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
async def run_sequential_batch(
|
|
376
|
+
*,
|
|
377
|
+
profile_dir: Path,
|
|
378
|
+
headless: bool,
|
|
379
|
+
transport: str | None,
|
|
380
|
+
items: tuple[Any, ...],
|
|
381
|
+
continue_on_error: bool,
|
|
382
|
+
project_title: str,
|
|
383
|
+
worker: Callable[[Any, str, int, Any], Coroutine[Any, Any, BatchOutcome]],
|
|
384
|
+
output_dir: Path | None = None,
|
|
385
|
+
client_factory: Callable[..., Any] | None = None,
|
|
386
|
+
) -> list[BatchOutcome]:
|
|
387
|
+
"""Generic sequential orchestrator for Flow API batches."""
|
|
388
|
+
if output_dir:
|
|
389
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
390
|
+
|
|
302
391
|
outcomes: list[BatchOutcome] = []
|
|
303
392
|
factory = client_factory or FlowApiClient
|
|
304
393
|
async with factory(profile_dir=profile_dir, headless=headless, transport=transport) as client:
|
|
305
394
|
project = await client.create_project(title=project_title)
|
|
306
|
-
for idx, item in enumerate(
|
|
307
|
-
outcome = await
|
|
308
|
-
client=client,
|
|
309
|
-
project_id=project.project_id,
|
|
310
|
-
idx=idx,
|
|
311
|
-
item=item,
|
|
312
|
-
output_dir=output_dir,
|
|
313
|
-
)
|
|
395
|
+
for idx, item in enumerate(items):
|
|
396
|
+
outcome = await worker(client, project.project_id, idx, item)
|
|
314
397
|
outcomes.append(outcome)
|
|
315
398
|
if outcome.status == "fail" and not continue_on_error:
|
|
316
|
-
for skip_idx in range(idx + 1, len(
|
|
399
|
+
for skip_idx in range(idx + 1, len(items)):
|
|
317
400
|
outcomes.append(
|
|
318
401
|
BatchOutcome(
|
|
319
402
|
index=skip_idx,
|
|
320
|
-
prompt=
|
|
403
|
+
prompt=items[skip_idx],
|
|
321
404
|
status="skipped",
|
|
322
405
|
)
|
|
323
406
|
)
|
|
@@ -59,6 +59,21 @@ def _validate_job_id(job_id: str) -> str:
|
|
|
59
59
|
return job_id
|
|
60
60
|
|
|
61
61
|
|
|
62
|
+
def resolve_batch_output_dir(
|
|
63
|
+
*,
|
|
64
|
+
cli_override: Path | None,
|
|
65
|
+
config_value: str | None = None,
|
|
66
|
+
output_root: Path,
|
|
67
|
+
kind: str = "images",
|
|
68
|
+
) -> Path:
|
|
69
|
+
"""CLI flag > config value > default (``<output_root>/<kind>/<YYYY-MM-DD>/``)."""
|
|
70
|
+
if cli_override is not None:
|
|
71
|
+
return cli_override
|
|
72
|
+
if config_value is not None:
|
|
73
|
+
return Path(config_value)
|
|
74
|
+
return output_root / kind / date.today().isoformat()
|
|
75
|
+
|
|
76
|
+
|
|
62
77
|
def video_output_path(
|
|
63
78
|
output_dir: Path,
|
|
64
79
|
*,
|
|
@@ -21,8 +21,8 @@ from gflow_cli.cli import main as cli_main
|
|
|
21
21
|
from gflow_cli.cli_run import (
|
|
22
22
|
BatchConfig,
|
|
23
23
|
_check_transport_gated,
|
|
24
|
-
_resolve_output_dir,
|
|
25
24
|
)
|
|
25
|
+
from gflow_cli.paths import resolve_batch_output_dir
|
|
26
26
|
from gflow_cli.errors import ConfigurationError, WafRejectionError
|
|
27
27
|
|
|
28
28
|
# ---------------------------------------------------------------------------
|
|
@@ -179,18 +179,26 @@ class TestBatchConfigValidation:
|
|
|
179
179
|
|
|
180
180
|
class TestResolveOutputDir:
|
|
181
181
|
def test_cli_override_wins(self) -> None:
|
|
182
|
-
result =
|
|
182
|
+
result = resolve_batch_output_dir(
|
|
183
|
+
cli_override=Path("/cli"), config_value="config", output_root=Path("/root")
|
|
184
|
+
)
|
|
183
185
|
assert result == Path("/cli")
|
|
184
186
|
|
|
185
187
|
def test_config_value_used_when_no_cli(self) -> None:
|
|
186
|
-
result =
|
|
188
|
+
result = resolve_batch_output_dir(
|
|
189
|
+
cli_override=None, config_value="cfg", output_root=Path("/root")
|
|
190
|
+
)
|
|
187
191
|
assert result == Path("cfg")
|
|
188
192
|
|
|
189
193
|
def test_default_when_neither_set(self) -> None:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
+
from datetime import date
|
|
195
|
+
|
|
196
|
+
today = date.today().isoformat()
|
|
197
|
+
result = resolve_batch_output_dir(
|
|
198
|
+
cli_override=None, config_value=None, output_root=Path("/root")
|
|
199
|
+
)
|
|
200
|
+
# Unified format: <root>/images/<YYYY-MM-DD>
|
|
201
|
+
assert result == Path("/root/images") / today
|
|
194
202
|
|
|
195
203
|
|
|
196
204
|
class TestCheckTransportGated:
|
|
@@ -184,7 +184,7 @@ def test_t2i_rejects_51_positional_prompts_before_profile_and_output_dir() -> No
|
|
|
184
184
|
|
|
185
185
|
with (
|
|
186
186
|
patch("gflow_cli.cli_image._resolve_profile") as resolve_profile,
|
|
187
|
-
patch("gflow_cli.cli_image.
|
|
187
|
+
patch("gflow_cli.cli_image.resolve_batch_output_dir") as resolve_output,
|
|
188
188
|
):
|
|
189
189
|
result = CliRunner().invoke(
|
|
190
190
|
main,
|
|
@@ -203,7 +203,7 @@ def test_t2i_rejects_long_positional_prompt_before_profile_and_output_dir() -> N
|
|
|
203
203
|
|
|
204
204
|
with (
|
|
205
205
|
patch("gflow_cli.cli_image._resolve_profile") as resolve_profile,
|
|
206
|
-
patch("gflow_cli.cli_image.
|
|
206
|
+
patch("gflow_cli.cli_image.resolve_batch_output_dir") as resolve_output,
|
|
207
207
|
):
|
|
208
208
|
result = CliRunner().invoke(
|
|
209
209
|
main,
|
|
@@ -235,7 +235,7 @@ def test_t2i_rejects_invalid_prompt_files_before_profile_and_output_dir(
|
|
|
235
235
|
path.write_bytes(content)
|
|
236
236
|
with (
|
|
237
237
|
patch("gflow_cli.cli_image._resolve_profile") as resolve_profile,
|
|
238
|
-
patch("gflow_cli.cli_image.
|
|
238
|
+
patch("gflow_cli.cli_image.resolve_batch_output_dir") as resolve_output,
|
|
239
239
|
):
|
|
240
240
|
result = CliRunner().invoke(
|
|
241
241
|
main,
|
|
@@ -255,7 +255,7 @@ def test_t2i_rejects_missing_prompt_file_before_profile_and_output_dir(tmp_path:
|
|
|
255
255
|
missing = tmp_path / "missing.txt"
|
|
256
256
|
with (
|
|
257
257
|
patch("gflow_cli.cli_image._resolve_profile") as resolve_profile,
|
|
258
|
-
patch("gflow_cli.cli_image.
|
|
258
|
+
patch("gflow_cli.cli_image.resolve_batch_output_dir") as resolve_output,
|
|
259
259
|
):
|
|
260
260
|
result = CliRunner().invoke(
|
|
261
261
|
main,
|
|
@@ -279,7 +279,7 @@ def test_t2i_rejects_prompt_file_directory_before_profile_and_output_dir(
|
|
|
279
279
|
directory.mkdir()
|
|
280
280
|
with (
|
|
281
281
|
patch("gflow_cli.cli_image._resolve_profile") as resolve_profile,
|
|
282
|
-
patch("gflow_cli.cli_image.
|
|
282
|
+
patch("gflow_cli.cli_image.resolve_batch_output_dir") as resolve_output,
|
|
283
283
|
):
|
|
284
284
|
result = CliRunner().invoke(
|
|
285
285
|
main,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-09-image-mvp-orchestration.md
RENAMED
|
File without changes
|
|
File without changes
|
{gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-09-video-mvp-orchestration.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-10-phase-4-hardening.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gflow_cli-0.6.0a3 → gflow_cli-0.6.0a4}/docs/superpowers/plans/2026-05-14-shell-multi-prompt/PLAN.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|