gflow-cli 0.17.0__tar.gz → 0.18.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (409) hide show
  1. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/CHANGELOG.md +20 -1
  2. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/PKG-INFO +1 -1
  3. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/INDEX.md +1 -1
  4. gflow_cli-0.18.0/docs/LIVE_VERIFICATION_v0.18.0.md +72 -0
  5. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/USAGE.md +6 -0
  6. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/pyproject.toml +1 -1
  7. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/__init__.py +1 -1
  8. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/ui_automation.py +15 -5
  9. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/ui_automation_video.py +34 -8
  10. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_image.py +4 -0
  11. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/errors.py +31 -0
  12. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/exceptions.py +1 -0
  13. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_ui_automation_image_mode.py +24 -2
  14. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_ui_automation_video.py +16 -3
  15. gflow_cli-0.18.0/tests/cli/test_cli_image_selector_drift.py +74 -0
  16. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_errors.py +37 -0
  17. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/uv.lock +1 -1
  18. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/README.md +0 -0
  19. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/active.md +0 -0
  20. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/branch-review.md +0 -0
  21. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/changelog.md +0 -0
  22. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/check.md +0 -0
  23. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/doc-review.md +0 -0
  24. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/known-issues.md +0 -0
  25. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/next.md +0 -0
  26. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/plan.md +0 -0
  27. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/pr-council-review.md +0 -0
  28. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/predict.md +0 -0
  29. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/release.md +0 -0
  30. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/scenario.md +0 -0
  31. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.claude/commands/gflow/status.md +0 -0
  32. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.env.template +0 -0
  33. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.gitattributes +0 -0
  34. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/CODEOWNERS +0 -0
  35. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  36. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/copilot-instructions.md +0 -0
  37. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/dependabot.yml +0 -0
  38. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/workflows/ci.yml +0 -0
  39. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/workflows/external-pr-triage.yml +0 -0
  40. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/workflows/governance-advisory.yml +0 -0
  41. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/workflows/governance-benchmark.yml +0 -0
  42. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.github/workflows/release.yml +0 -0
  43. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.gitignore +0 -0
  44. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.gitleaks.toml +0 -0
  45. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.pre-commit-config.yaml +0 -0
  46. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/.secrets.baseline +0 -0
  47. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/AGENTS.md +0 -0
  48. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/CLAUDE.md +0 -0
  49. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/CONFIGURATION.md +0 -0
  50. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/CONTRIBUTING.md +0 -0
  51. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/DISCLAIMER.md +0 -0
  52. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/GEMINI.md +0 -0
  53. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/KNOWN_ISSUES.md +0 -0
  54. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/LICENSE +0 -0
  55. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/PLAN.md +0 -0
  56. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/README.md +0 -0
  57. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/RELEASE.md +0 -0
  58. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/ROADMAP.md +0 -0
  59. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/conftest.py +0 -0
  60. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docker-compose.yml +0 -0
  61. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/AGENT_GUIDE.md +0 -0
  62. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/ARCHITECTURE.md +0 -0
  63. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/AUTHENTICATION.md +0 -0
  64. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/CHARACTER.md +0 -0
  65. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/CHARACTER_RECON.md +0 -0
  66. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/CONFIGURATION.md +0 -0
  67. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/DATA_LAYER.md +0 -0
  68. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/DEBUGGING.md +0 -0
  69. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/DEMOS.md +0 -0
  70. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/DEVELOPMENT.md +0 -0
  71. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/E2E_TESTING.md +0 -0
  72. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/EXTERNAL_STORAGE.md +0 -0
  73. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/GITHUB.md +0 -0
  74. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/GOVERNANCE_BENCHMARK.md +0 -0
  75. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/IMAGE_UPSCALE_RECON.md +0 -0
  76. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_data_layer.md +0 -0
  77. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_image_batch.md +0 -0
  78. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.10.0.md +0 -0
  79. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.11.0.md +0 -0
  80. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.12.0.md +0 -0
  81. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.13.0.md +0 -0
  82. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.16.0.md +0 -0
  83. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.17.0.md +0 -0
  84. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.7.0.md +0 -0
  85. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.8.1.md +0 -0
  86. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.9.0.md +0 -0
  87. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_v0.9.1.md +0 -0
  88. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/LIVE_VERIFICATION_video_download.md +0 -0
  89. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/MOVIE.md +0 -0
  90. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/PROJECT_STATUS.md +0 -0
  91. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/SECURITY.md +0 -0
  92. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/USER_GUIDE.md +0 -0
  93. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/assets/demo-split-pf.gif +0 -0
  94. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/assets/example-run.gif +0 -0
  95. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/schemas/movie-handoff.schema.json +0 -0
  96. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/docs/superpowers/plans/2026-06-12-issue-174-library-ui-attach/PLAN.md +0 -0
  97. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/examples/README.md +0 -0
  98. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/examples/batch_from_config.py +0 -0
  99. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/examples/multi_prompt_t2i.py +0 -0
  100. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/examples/sample_config.json +0 -0
  101. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/examples/sample_prompts.txt +0 -0
  102. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/examples/single_image_t2i.py +0 -0
  103. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/examples/workflow_chain.py +0 -0
  104. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/llms.txt +0 -0
  105. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/README.md +0 -0
  106. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/01_upload_image.json +0 -0
  107. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/02_batchAsyncGenerateVideoText.json +0 -0
  108. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/03_batchCheckAsyncVideoGenerationStatus.json +0 -0
  109. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/04_archive_workflow.json +0 -0
  110. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/05_createProject.json +0 -0
  111. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/06_batchGenerateImages.json +0 -0
  112. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/07_batchGenerateImages_seeded.json +0 -0
  113. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/08_batchAsyncGenerateVideoStartAndEndImage.json +0 -0
  114. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/09_batchAsyncGenerateVideoReferenceImages.json +0 -0
  115. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/10_batchCheckAsyncVideoGenerationStatus_successful.json +0 -0
  116. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/11_batchCheckAsyncVideoGenerationStatus_failed.json +0 -0
  117. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/12_create_scene.json +0 -0
  118. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/13_sceneWorkflows_update.json +0 -0
  119. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/14_get_scene_workflows.json +0 -0
  120. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/samples/captured/15_commit_flowWorkflow.json +0 -0
  121. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/ci/check_doc_links.py +0 -0
  122. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/ci/check_materiality.py +0 -0
  123. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/ci/check_repo_hygiene.py +0 -0
  124. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/debug_editor.py +0 -0
  125. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/debug_gen_settings.py +0 -0
  126. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/debug_settings.py +0 -0
  127. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/_recording_client.py +0 -0
  128. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/_spike_common.py +0 -0
  129. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/active_plan.py +0 -0
  130. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/capture_i2v_frame_slots_dom.py +0 -0
  131. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/capture_i2v_intercept_submit.py +0 -0
  132. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/capture_i2v_model_select_repro.py +0 -0
  133. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/capture_i2v_post_bind_state.py +0 -0
  134. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/capture_image_add_media_dom.py +0 -0
  135. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/capture_locale_invariants.py +0 -0
  136. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/cdp_drive_and_probe.py +0 -0
  137. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/character_create_spike.py +0 -0
  138. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/character_create_spike_v2.py +0 -0
  139. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/dump_character_selectors.js +0 -0
  140. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/make_project.py +0 -0
  141. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/materiality_backtest.py +0 -0
  142. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/monitor_pr_38.py +0 -0
  143. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/patch_character.py +0 -0
  144. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/record_flow_capture.py +0 -0
  145. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/skillopt/README.md +0 -0
  146. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/skillopt/harness.py +0 -0
  147. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/skillopt/tasks.json +0 -0
  148. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_char_editor_dom.py +0 -0
  149. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_char_gen_capture.py +0 -0
  150. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_image_upscale_capture.py +0 -0
  151. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_image_upscale_drive.py +0 -0
  152. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_image_upscale_recaptcha_action.py +0 -0
  153. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_image_upscale_rest_probe.py +0 -0
  154. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_issue170_picker_locale_recon.py +0 -0
  155. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_issue174_library_ui_recon.py +0 -0
  156. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_movie_attach_payload.py +0 -0
  157. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_movie_entity_recon.py +0 -0
  158. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_movie_gen_capture.py +0 -0
  159. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_movie_picker_select.py +0 -0
  160. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_movie_voice_list.py +0 -0
  161. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/dev/spike_patch_entity.py +0 -0
  162. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/diag/README.md +0 -0
  163. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/diag/capture_flow_traffic.py +0 -0
  164. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/diag/memory_profile.py +0 -0
  165. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/diag/recaptcha_mint.py +0 -0
  166. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/record_demo.ps1 +0 -0
  167. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/smoke_image.py +0 -0
  168. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/smoke_real_chrome_image.py +0 -0
  169. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/smoke_video_editor.py +0 -0
  170. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/smoke_worker_style.py +0 -0
  171. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/scripts/verify_chrome_auth_viability.py +0 -0
  172. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/skills/README.md +0 -0
  173. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/skills/gflow-cli/SKILL.md +0 -0
  174. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/skills/plan/SKILL.md +0 -0
  175. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/skills/pr-council-review/SKILL.md +0 -0
  176. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/skills/predict/SKILL.md +0 -0
  177. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/skills/scenario/SKILL.md +0 -0
  178. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/skills/status/SKILL.md +0 -0
  179. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/sonar-project.properties +0 -0
  180. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/__main__.py +0 -0
  181. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/_cli_helpers.py +0 -0
  182. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/__init__.py +0 -0
  183. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/_retry.py +0 -0
  184. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/_sapisidhash.py +0 -0
  185. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/character.py +0 -0
  186. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/client.py +0 -0
  187. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/dto.py +0 -0
  188. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/image.py +0 -0
  189. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/image_upscale.py +0 -0
  190. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/recaptcha.py +0 -0
  191. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/routes.py +0 -0
  192. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/scene.py +0 -0
  193. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/__init__.py +0 -0
  194. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/_common.py +0 -0
  195. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/_fingerprint.py +0 -0
  196. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/base.py +0 -0
  197. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/experimental/__init__.py +0 -0
  198. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/experimental/bearer.py +0 -0
  199. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/experimental/evaluate_fetch.py +0 -0
  200. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/transports/experimental/sapisidhash.py +0 -0
  201. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/api/video.py +0 -0
  202. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/__init__.py +0 -0
  203. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/base.py +0 -0
  204. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/cookies.py +0 -0
  205. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/factory.py +0 -0
  206. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/internal_chromium.py +0 -0
  207. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/real_chrome.py +0 -0
  208. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/strategies.py +0 -0
  209. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/auth/verification.py +0 -0
  210. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/browser_manager.py +0 -0
  211. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/chain.py +0 -0
  212. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/chain_manifest.py +0 -0
  213. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli.py +0 -0
  214. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_character.py +0 -0
  215. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_data.py +0 -0
  216. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_models.py +0 -0
  217. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_movie.py +0 -0
  218. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_run.py +0 -0
  219. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_scene.py +0 -0
  220. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/cli_video.py +0 -0
  221. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/composition.py +0 -0
  222. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/config.py +0 -0
  223. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/__init__.py +0 -0
  224. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/chain_repo.py +0 -0
  225. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/migrations/0001_initial.sql +0 -0
  226. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/migrations/0002_add_cloud_storage.sql +0 -0
  227. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/migrations/0003_add_scene_tables.sql +0 -0
  228. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/migrations/0004_add_scene_output_path.sql +0 -0
  229. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/migrations/0005_add_chain_links.sql +0 -0
  230. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/migrations/0006_add_operations_metadata.sql +0 -0
  231. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/migrations/__init__.py +0 -0
  232. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/models.py +0 -0
  233. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/queries.py +0 -0
  234. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/recorder.py +0 -0
  235. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/redaction.py +0 -0
  236. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/repository.py +0 -0
  237. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/data/store.py +0 -0
  238. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/image_batch.py +0 -0
  239. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/json_output.py +0 -0
  240. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/manifest.py +0 -0
  241. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/media.py +0 -0
  242. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/movie_manifest.py +0 -0
  243. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/observability.py +0 -0
  244. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/paths.py +0 -0
  245. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/profile_store.py +0 -0
  246. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/services/__init__.py +0 -0
  247. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/services/character_create.py +0 -0
  248. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/src/gflow_cli/storage.py +0 -0
  249. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tasks/lessons.md +0 -0
  250. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/test_assets/sample_batch.json +0 -0
  251. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/test_assets/sample_batch.tsv +0 -0
  252. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/test_assets/sample_batch_invalid.tsv +0 -0
  253. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/__init__.py +0 -0
  254. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/__init__.py +0 -0
  255. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/fixtures/character_gen_response.json +0 -0
  256. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/fixtures/patch_entity_response.json +0 -0
  257. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_aisandbox_auth_error.py +0 -0
  258. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_aisandbox_auth_headers.py +0 -0
  259. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_bearer_redaction.py +0 -0
  260. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_character.py +0 -0
  261. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client.py +0 -0
  262. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_character.py +0 -0
  263. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_delete_characters.py +0 -0
  264. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_generate_character.py +0 -0
  265. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_image.py +0 -0
  266. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_launch_kwargs.py +0 -0
  267. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_patch_entity.py +0 -0
  268. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_scene.py +0 -0
  269. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_client_upscale.py +0 -0
  270. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_concurrency.py +0 -0
  271. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_dto.py +0 -0
  272. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_image.py +0 -0
  273. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_image_dto.py +0 -0
  274. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_image_upscale.py +0 -0
  275. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_post_json_aisandbox_auth.py +0 -0
  276. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_recaptcha.py +0 -0
  277. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_retry.py +0 -0
  278. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_routes.py +0 -0
  279. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_routes_character.py +0 -0
  280. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_routes_scene.py +0 -0
  281. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_sapisidhash_helper.py +0 -0
  282. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_scene_models.py +0 -0
  283. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_video.py +0 -0
  284. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/test_video_request.py +0 -0
  285. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/__init__.py +0 -0
  286. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_base.py +0 -0
  287. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_bearer.py +0 -0
  288. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_common.py +0 -0
  289. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_evaluate_fetch.py +0 -0
  290. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_factory.py +0 -0
  291. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_fingerprint.py +0 -0
  292. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_sapisidhash.py +0 -0
  293. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_transport_timeout.py +0 -0
  294. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_ui_automation.py +0 -0
  295. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_ui_automation_batch.py +0 -0
  296. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/api/transports/test_ui_character_editor.py +0 -0
  297. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/auth/strategies/test_factory.py +0 -0
  298. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/auth/strategies/test_strategies.py +0 -0
  299. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/auth/test_verification.py +0 -0
  300. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/__init__.py +0 -0
  301. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_auth_list.py +0 -0
  302. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_character.py +0 -0
  303. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_character_create.py +0 -0
  304. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_data.py +0 -0
  305. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_image.py +0 -0
  306. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_image_seed_removed.py +0 -0
  307. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_image_upscale.py +0 -0
  308. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_models.py +0 -0
  309. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_movie.py +0 -0
  310. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_run.py +0 -0
  311. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_scene.py +0 -0
  312. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_video.py +0 -0
  313. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_cli_video_chain.py +0 -0
  314. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_error_handling.py +0 -0
  315. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_helpers.py +0 -0
  316. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_movie_manifest.py +0 -0
  317. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/cli/test_t2i_multi_prompt.py +0 -0
  318. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/composition/test_character.py +0 -0
  319. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/composition/test_compose_prompt.py +0 -0
  320. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/composition/test_handoff.py +0 -0
  321. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/conftest.py +0 -0
  322. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/__init__.py +0 -0
  323. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_chain_repo.py +0 -0
  324. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_find_incomplete_character.py +0 -0
  325. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_models.py +0 -0
  326. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_packaging.py +0 -0
  327. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_recorder.py +0 -0
  328. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_recorder_character.py +0 -0
  329. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_redaction.py +0 -0
  330. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_repository.py +0 -0
  331. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_scene_persistence.py +0 -0
  332. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_settings_and_errors.py +0 -0
  333. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/data/test_store_migrations.py +0 -0
  334. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/dev/test_record_flow_capture.py +0 -0
  335. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/dev/test_recording_client.py +0 -0
  336. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/__init__.py +0 -0
  337. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/conftest.py +0 -0
  338. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_aisandbox_auth_live.py +0 -0
  339. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_auth_verification_e2e.py +0 -0
  340. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_chain_e2e.py +0 -0
  341. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_character_create_e2e.py +0 -0
  342. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_data_layer_e2e.py +0 -0
  343. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_i2v_flags_e2e.py +0 -0
  344. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_image_batch_e2e.py +0 -0
  345. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_image_i2i_ref_cap_e2e.py +0 -0
  346. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_json_output_e2e.py +0 -0
  347. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_locale_selectors_e2e.py +0 -0
  348. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_scene_compose_live.py +0 -0
  349. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_transports_e2e.py +0 -0
  350. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_video_r2v_ref_cap_e2e.py +0 -0
  351. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/e2e/test_video_t2v_e2e.py +0 -0
  352. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/__init__.py +0 -0
  353. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/auth.feature +0 -0
  354. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/auth_login.feature +0 -0
  355. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/character_create.feature +0 -0
  356. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/character_read.feature +0 -0
  357. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/conftest.py +0 -0
  358. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/image.feature +0 -0
  359. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/image_upscale.feature +0 -0
  360. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/locale_picker_include.feature +0 -0
  361. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_auth_login_steps.py +0 -0
  362. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_auth_steps.py +0 -0
  363. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_character_create_steps.py +0 -0
  364. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_character_read_steps.py +0 -0
  365. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_image_steps.py +0 -0
  366. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_image_upscale_steps.py +0 -0
  367. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_locale_picker_include_steps.py +0 -0
  368. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_step_collision_guard.py +0 -0
  369. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/test_video_chain_steps.py +0 -0
  370. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/features/video_chain.feature +0 -0
  371. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/fixtures/__init__.py +0 -0
  372. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/fixtures/seeded_catalog.py +0 -0
  373. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/image_batch/__init__.py +0 -0
  374. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/image_batch/test_image_manifest.py +0 -0
  375. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/image_batch/test_observability_events.py +0 -0
  376. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/integration/__init__.py +0 -0
  377. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/integration/conftest.py +0 -0
  378. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/integration/constants.py +0 -0
  379. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/integration/test_storage_gcs.py +0 -0
  380. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/integration/test_storage_s3.py +0 -0
  381. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/scripts/test_capture_locale_invariants.py +0 -0
  382. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/scripts/test_check_materiality.py +0 -0
  383. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/scripts/test_check_repo_hygiene.py +0 -0
  384. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/scripts/test_materiality_backtest.py +0 -0
  385. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/services/__init__.py +0 -0
  386. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/services/test_character_create_redaction.py +0 -0
  387. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/services/test_character_create_saga.py +0 -0
  388. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/services/test_character_gen_no_direct_post.py +0 -0
  389. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/smoke/__init__.py +0 -0
  390. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/smoke/test_profile_account_smoke.py +0 -0
  391. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/smoke/test_real_flow.py +0 -0
  392. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_auth.py +0 -0
  393. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_browser_manager.py +0 -0
  394. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_chain.py +0 -0
  395. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_chain_manifest.py +0 -0
  396. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_cli_data.py +0 -0
  397. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_config.py +0 -0
  398. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_conftest_isolation.py +0 -0
  399. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_data_queries.py +0 -0
  400. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_documentation_gate.py +0 -0
  401. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_errors_403.py +0 -0
  402. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_json_output.py +0 -0
  403. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_manifest.py +0 -0
  404. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_marker_registry.py +0 -0
  405. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_media.py +0 -0
  406. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_observability.py +0 -0
  407. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_paths.py +0 -0
  408. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_profile_store.py +0 -0
  409. {gflow_cli-0.17.0 → gflow_cli-0.18.0}/tests/test_smoke.py +0 -0
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.18.0] — 2026-06-12
11
+
12
+ ### Added
13
+
14
+ - `UiSelectorDriftError` (exit code 23): UI-automation selector-probe failures — e.g. the
15
+ mode-switch `crop_*` trigger missing from the Flow editor (issue #183) — now raise a typed
16
+ error carrying the probe name, the debug-screenshot path, and a remediation hint, instead
17
+ of an opaque "Unexpected error" (exit 1) whose message was hashed away in logs. Converted
18
+ probes: mode-switch trigger, Image/Video mode tabs, and video sub-mode tabs, in both the
19
+ image and video transports.
20
+
21
+ ### Fixed
22
+
23
+ - `gflow image` commands (`t2i` / `i2i` / `upscale` / `upload`) now plumb their output
24
+ directory into the API client, so debug screenshots are actually captured on
25
+ UI-automation failures. Previously the transport's screenshot directory was never set on
26
+ the image path and drift errors reported `Screenshot: None` even with `--verbose`.
27
+
10
28
  ## [0.17.0] — 2026-06-12
11
29
 
12
30
  ### Added
@@ -1509,7 +1527,8 @@ shell-script template that branches on these codes.
1509
1527
 
1510
1528
  First skeleton. Not functional end-to-end yet.
1511
1529
 
1512
- [Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.17.0...HEAD
1530
+ [Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.18.0...HEAD
1531
+ [0.18.0]: https://github.com/ffroliva/gflow-cli/compare/v0.17.0...v0.18.0
1513
1532
  [0.17.0]: https://github.com/ffroliva/gflow-cli/compare/v0.16.0...v0.17.0
1514
1533
  [0.16.0]: https://github.com/ffroliva/gflow-cli/compare/v0.15.1...v0.16.0
1515
1534
  [0.15.1]: https://github.com/ffroliva/gflow-cli/compare/v0.15.0...v0.15.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gflow-cli
3
- Version: 0.17.0
3
+ Version: 0.18.0
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
@@ -111,7 +111,7 @@ Slash commands for Claude Code, stored in `.claude/commands/gflow/`. All prefixe
111
111
  **"A gflow command hangs / fails — where do I start?"** → [DEBUGGING § Quick reference](DEBUGGING.md#quick-reference)
112
112
  **"Flow's UI broke a selector — how do I diagnose it?"** → [DEBUGGING § Inspecting Flow's live UI](DEBUGGING.md#inspecting-flows-live-ui)
113
113
  **"What does each `ui_automation.*` log event mean?"** → [DEBUGGING § Listener & HTTP-layer debugging](DEBUGGING.md#listener--http-layer-debugging)
114
- **"What was actually live-verified for the latest release?"** → latest: [LIVE_VERIFICATION_v0.17.0](LIVE_VERIFICATION_v0.17.0.md) (#168 cookie verification live e2e_auth; #177 exit-7 hint recorded not-triggerable, credit-free). v0.14.0 / v0.15.0 / v0.15.1 shipped without a dedicated doc (CI + the v0.13.0 baseline). Prior: [v0.16.0](LIVE_VERIFICATION_v0.16.0.md) · [v0.13.0](LIVE_VERIFICATION_v0.13.0.md) · [v0.12.0](LIVE_VERIFICATION_v0.12.0.md) · [v0.11.0](LIVE_VERIFICATION_v0.11.0.md) · [v0.10.0](LIVE_VERIFICATION_v0.10.0.md) · [v0.9.1](LIVE_VERIFICATION_v0.9.1.md) · [v0.9.0](LIVE_VERIFICATION_v0.9.0.md) · [v0.8.1](LIVE_VERIFICATION_v0.8.1.md) · [v0.7.0](LIVE_VERIFICATION_v0.7.0.md)
114
+ **"What was actually live-verified for the latest release?"** → latest: [LIVE_VERIFICATION_v0.18.0](LIVE_VERIFICATION_v0.18.0.md) (#183 exit-23 selector-drift error: live drift simulation + happy path, credit-free). v0.14.0 / v0.15.0 / v0.15.1 shipped without a dedicated doc (CI + the v0.13.0 baseline). Prior: [v0.17.0](LIVE_VERIFICATION_v0.17.0.md) · [v0.16.0](LIVE_VERIFICATION_v0.16.0.md) · [v0.13.0](LIVE_VERIFICATION_v0.13.0.md) · [v0.12.0](LIVE_VERIFICATION_v0.12.0.md) · [v0.11.0](LIVE_VERIFICATION_v0.11.0.md) · [v0.10.0](LIVE_VERIFICATION_v0.10.0.md) · [v0.9.1](LIVE_VERIFICATION_v0.9.1.md) · [v0.9.0](LIVE_VERIFICATION_v0.9.0.md) · [v0.8.1](LIVE_VERIFICATION_v0.8.1.md) · [v0.7.0](LIVE_VERIFICATION_v0.7.0.md)
115
115
  **"Where is the reverse-engineered wire protocol for a feature?"** → the `*_RECON.md` design docs: [CHARACTER_RECON](CHARACTER_RECON.md) (Flow character entity protocol), [IMAGE_UPSCALE_RECON](IMAGE_UPSCALE_RECON.md) (`/v1/flow/upsampleImage` wire). Naming convention: one `<FEATURE>_RECON.md` per reverse-engineered surface, kept as the durable spec after the feature ships.
116
116
  **"What was live-verified for the data layer (PR #58)?"** → [LIVE_VERIFICATION_data_layer](LIVE_VERIFICATION_data_layer.md) — 1 Imagen + 1 Veo credit on denon82, 6-layer ledger (file + magic + Pillow + DB rows + CLI round-trip + structlog)
117
117
  **"What was live-verified for the video-download feature (#29)?"** → [LIVE_VERIFICATION_video_download](LIVE_VERIFICATION_video_download.md)
@@ -0,0 +1,72 @@
1
+ # Live verification — v0.18.0
2
+
3
+ > Evidence record for the v0.18.0 release. v0.18.0 ships one feature — typed
4
+ > `UiSelectorDriftError` (exit code 23) for UI-automation selector-probe failures
5
+ > (issue #183, PR #184) — and one fix: the `gflow image` command family now wires
6
+ > its output directory into the API client so debug screenshots are actually
7
+ > captured on UI-automation failures.
8
+
9
+ ## Summary
10
+
11
+ - **Verified by:** ffroliva (Claude Code)
12
+ - **Date:** 2026-06-12 (live runs on the PR #184 head `45ae442`, merged unchanged
13
+ into `develop @ 9f1d80b`)
14
+ - **gflow-cli version:** 0.18.0 (pre-tag verification)
15
+ - **Status:** 🟢 Green — both the failure path (drift simulation) and the happy
16
+ path live-verified, **credit-free** (image generation spends no Veo credits)
17
+
18
+ ## 1. Pre-tag gates
19
+
20
+ - `ruff check src tests` — clean; `ruff format --check` — 220 files formatted
21
+ - `pyright src` — 0 errors (7 pre-existing `browser_cookie3` resolution errors in
22
+ the local env only, untouched `auth/cookies.py`; CI green)
23
+ - Scoped pytest (errors + transports + cli surfaces) — 475 passed, 1 skipped
24
+ - PR #184 CI — 7/7 checks green on `45ae442` (test matrix 3.11/3.12/3.13,
25
+ gitleaks, SonarCloud)
26
+
27
+ ## 2. Failure path — live drift simulation (the issue-#183 condition)
28
+
29
+ `MODE_SWITCH_TRIGGER_SELECTORS` was patched (in both transport module namespaces)
30
+ to a guaranteed-miss selector, reproducing exactly the reporter's DOM condition —
31
+ the crop_* mode-switch trigger absent from the Flow editor. The real CLI t2i path
32
+ was then run against live Flow (profile `denon82`, project created, $0 credits —
33
+ the failure fires before any generation).
34
+
35
+ | Ledger layer | Evidence |
36
+ |---|---|
37
+ | Exit code | **23** (`UiSelectorDriftError` via `EXIT_CODE_MAP`) |
38
+ | File count | 1 — `debug_no_mode_trigger.png` written to the `--out` dir |
39
+ | Magic bytes | `\x89PNG\r\n\x1a\n` ✅ (valid PNG) |
40
+ | Dimensions | 1280×720 (Pillow) |
41
+ | structlog invariants | `selector_miss` ×1 (bogus selector) → `selector_probe_failed` → `error_raised` with full RFC 9457 problem payload; **zero** `error_unhandled` / `message_hash` events |
42
+ | User-visible artifact | `Flow UI selector drift: probe=mode_switch_trigger: no matching element found on the Flow editor. Screenshot: <out-dir>/debug_no_mode_trigger.png` + remediation hint (no `--verbose` claim, PII warning present) |
43
+
44
+ This is the user-facing contract the release exists for: the issue-#183 reporter
45
+ saw `Unexpected error` (exit 1) with the real message hashed away; on v0.18.0 the
46
+ same condition produces an actionable, screenshot-carrying exit-23 error.
47
+
48
+ ## 3. Happy path — mode-switch unregressed
49
+
50
+ `gflow --verbose image t2i "<prompt>" --profile denon82 --aspect 9:16 --out <dir>`
51
+ against live Flow:
52
+
53
+ | Ledger layer | Evidence |
54
+ |---|---|
55
+ | Exit code | 0 |
56
+ | File count | 1 (`ec262f4c-….jpg`) |
57
+ | Magic bytes | `\xff\xd8\xff` ✅ (valid JPEG) |
58
+ | Dimensions | 768×1376 (portrait — matches `--aspect 9:16`) |
59
+ | structlog invariants | `image_mode_entered` fired (mode-switch dropdown found and used); no `UiSelectorDriftError` in the run |
60
+ | User gallery | image present in the Flow project gallery (denon82) |
61
+
62
+ ## 4. Not verified this cycle (recorded, not omitted)
63
+
64
+ - **The drift error on a genuinely affected account:** the issue-#183 reporter's
65
+ account is in the (suspected) issue-#174 new-UI A/B cohort; both maintainer
66
+ accounts probed as the old dialog UI on 2026-06-12, so the organic condition is
67
+ not reproducible on our side. The simulation in § 2 exercises the identical
68
+ code path (selector cascade miss → typed raise → CLI boundary → exit 23).
69
+ Reporter confirmation is requested on #183.
70
+ - **`cli_character.py` screenshot wiring:** intentionally out of scope for this
71
+ release (tracked as a follow-up); its drift errors correctly omit the
72
+ `Screenshot:` clause.
@@ -1022,9 +1022,15 @@ shell scripts can branch on the failure mode without parsing stderr.
1022
1022
  | `12` | `AuthLoginTimeoutError` | Browser sign-in was not completed in time | Re-run login or raise `GFLOW_CLI_AUTH_LOGIN_TIMEOUT` |
1023
1023
  | `13` | `SecurityError` | Unsafe local profile or secret handling blocked | Follow the error's safety guidance |
1024
1024
  | `14` | `AuthBrowserRejectedError` | Google rejected the login browser | `gflow auth login --browser chrome` |
1025
+ | `15` | `BrowserSessionClosedError` | The automation browser window was closed mid-operation | Re-run; keep the browser window open until the command finishes |
1025
1026
  | `16` | `DataStoreError` | Local database cannot be opened, a migration failed, or the DB schema is newer than the installed gflow-cli | See below |
1026
1027
  | `17` | `ModelModeIncompatibilityError` | The chosen video model can't do the requested mode (e.g. `--model omni-flash` with an `i2v` start/end frame — issue #125) | Use `--model veo-lite` (or veo-fast / veo-quality / veo-lite-lp) for `i2v` |
1027
1028
  | `18` | `VideoModelSelectionError` | gflow could not select the requested video model in Flow's editor for an `i2v` run (model-picker option not found) | Usually transient — retry; if it persists, Flow's model-picker UI changed (report referencing #125) |
1029
+ | `19` | `SceneConcatError` | Server-side scene render/concat failed (`gflow scene --output`) | Retry; the recorded compose survives, so re-render is safe |
1030
+ | `20` | `FrameExtractionError` | Could not extract the last frame for a video chain link | Check the source video downloaded intact; retry the link |
1031
+ | `21` | `ChainPartialError` | A video chain stopped mid-way; earlier links completed | Resume from the last completed link shown in the error |
1032
+ | `22` | `UpscaleUnavailableError` | 4K upscale is gated to Flow **Ultra** accounts (HTTP 403) | Use `--scale 2k`, or upgrade the Flow plan |
1033
+ | `23` | `UiSelectorDriftError` | A Flow editor control could not be located — Google changed the frontend (issue #183) | Update gflow-cli; file a bug with the probe name + debug screenshot from the error message |
1028
1034
  | `130`| SIGINT | User-interrupted (Ctrl-C) | — |
1029
1035
 
1030
1036
  **Exit code 16 — data store / migration error.** Fires when:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "gflow-cli"
3
- version = "0.17.0"
3
+ version = "0.18.0"
4
4
  description = "Unofficial CLI for Google Flow — drive Veo image-to-video generations from the terminal."
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
@@ -1,3 +1,3 @@
1
1
  """gflow-cli — unofficial CLI for Google Flow."""
2
2
 
3
- __version__ = "0.17.0"
3
+ __version__ = "0.18.0"
@@ -32,6 +32,7 @@ from gflow_cli.api.transports.ui_automation_video import (
32
32
  ENTITY_ATTACH_DRIFT_HINT,
33
33
  MODE_SWITCH_TRIGGER_SELECTORS,
34
34
  VideoGenerationMixin,
35
+ selector_drift_detail,
35
36
  zip_entity_refs,
36
37
  )
37
38
  from gflow_cli.errors import (
@@ -39,6 +40,7 @@ from gflow_cli.errors import (
39
40
  BatchPartialError,
40
41
  ContentPolicyError,
41
42
  GFlowError,
43
+ UiSelectorDriftError,
42
44
  WafRejectionError,
43
45
  WireFormatError,
44
46
  )
@@ -989,9 +991,12 @@ class UiAutomationTransport(VideoGenerationMixin):
989
991
  )
990
992
  if trigger is None:
991
993
  shot = await _capture_debug_screenshot(page, out_dir, "debug_no_mode_trigger.png")
992
- msg = f"mode-switch dropdown trigger not found on the Flow editor. Screenshot: {shot}"
993
- raise RuntimeError(
994
- msg,
994
+ raise UiSelectorDriftError(
995
+ selector_drift_detail(
996
+ "mode_switch_trigger",
997
+ "no matching element found on the Flow editor.",
998
+ shot,
999
+ )
995
1000
  )
996
1001
  await trigger.click()
997
1002
  await page.wait_for_timeout(800)
@@ -1002,8 +1007,13 @@ class UiAutomationTransport(VideoGenerationMixin):
1002
1007
  )
1003
1008
  if image_tab is None:
1004
1009
  shot = await _capture_debug_screenshot(page, out_dir, "debug_no_image_tab.png")
1005
- msg = f"Image tab not found in the mode dropdown. Screenshot: {shot}"
1006
- raise RuntimeError(msg)
1010
+ raise UiSelectorDriftError(
1011
+ selector_drift_detail(
1012
+ "image_mode_tab",
1013
+ "Image tab not found in the mode dropdown.",
1014
+ shot,
1015
+ )
1016
+ )
1007
1017
  await image_tab.click()
1008
1018
  await page.wait_for_timeout(1200)
1009
1019
  await page.keyboard.press("Escape")
@@ -41,6 +41,7 @@ from gflow_cli.errors import (
41
41
  AuthExpiredError,
42
42
  ModelModeIncompatibilityError,
43
43
  TransportTimeoutError,
44
+ UiSelectorDriftError,
44
45
  VideoModelSelectionError,
45
46
  WafRejectionError,
46
47
  WireFormatError,
@@ -396,6 +397,20 @@ async def _capture_debug_screenshot(page: Any, out_dir: Path | None, filename: s
396
397
  return shot_path
397
398
 
398
399
 
400
+ def selector_drift_detail(probe: str, what: str, shot: Path | None) -> str:
401
+ """Build a :class:`UiSelectorDriftError` detail string for a probe miss.
402
+
403
+ Shared by the image and video transports so the message shape stays
404
+ symmetric, and so the ``Screenshot:`` clause is omitted (rather than
405
+ rendering a literal ``None``) when no debug screenshot was captured —
406
+ the caller had no ``out_dir`` to write into.
407
+ """
408
+ detail = f"probe={probe}: {what}"
409
+ if shot is not None:
410
+ detail = f"{detail} Screenshot: {shot}"
411
+ return detail
412
+
413
+
399
414
  def _summarize_request_image_inputs(request: Any) -> dict[str, Any]:
400
415
  """Privacy-safe summary of the image inputs in a generate request body:
401
416
  presence of startImage/endImage + referenceImages count, each as an 8-char
@@ -811,9 +826,12 @@ class VideoGenerationMixin:
811
826
  )
812
827
  if trigger is None:
813
828
  shot = await _capture_debug_screenshot(page, out_dir, "debug_no_mode_trigger.png")
814
- msg = f"mode-switch dropdown trigger not found on the Flow editor. Screenshot: {shot}"
815
- raise RuntimeError(
816
- msg,
829
+ raise UiSelectorDriftError(
830
+ selector_drift_detail(
831
+ "mode_switch_trigger",
832
+ "no matching element found on the Flow editor.",
833
+ shot,
834
+ )
817
835
  )
818
836
  await trigger.click()
819
837
  await page.wait_for_timeout(800)
@@ -824,8 +842,13 @@ class VideoGenerationMixin:
824
842
  )
825
843
  if video_tab is None:
826
844
  shot = await _capture_debug_screenshot(page, out_dir, "debug_no_video_tab.png")
827
- msg = f"Video tab not found in the mode dropdown. Screenshot: {shot}"
828
- raise RuntimeError(msg)
845
+ raise UiSelectorDriftError(
846
+ selector_drift_detail(
847
+ "video_mode_tab",
848
+ "Video tab not found in the mode dropdown.",
849
+ shot,
850
+ )
851
+ )
829
852
  await video_tab.click()
830
853
  await page.wait_for_timeout(1200)
831
854
  log.info("ui_automation_video.video_mode_entered")
@@ -996,9 +1019,12 @@ class VideoGenerationMixin:
996
1019
  )
997
1020
  if tab is None:
998
1021
  shot = await _capture_debug_screenshot(page, out_dir, f"debug_no_submode_{sub}.png")
999
- msg = f"video sub-mode tab {sub!r} not found on the Flow editor. Screenshot: {shot}"
1000
- raise RuntimeError(
1001
- msg,
1022
+ raise UiSelectorDriftError(
1023
+ selector_drift_detail(
1024
+ f"video_submode_{sub}",
1025
+ f"video sub-mode tab {sub!r} not found on the Flow editor.",
1026
+ shot,
1027
+ )
1002
1028
  )
1003
1029
  await tab.click()
1004
1030
  await page.wait_for_timeout(900)
@@ -353,6 +353,7 @@ async def _run_upload(
353
353
  profile_dir=profile_dir,
354
354
  headless=headless,
355
355
  transport=transport,
356
+ out_dir=settings.output_dir,
356
357
  ) as client:
357
358
  console.print(_CREATING_PROJECT_MSG)
358
359
  project = await client.create_project(title="gflow-cli upload")
@@ -544,6 +545,7 @@ async def _run_upscale(
544
545
  profile_dir=profile_dir,
545
546
  headless=headless,
546
547
  transport=transport,
548
+ out_dir=output_root,
547
549
  ) as client:
548
550
  console.print(f"Upscaling [bold]{media_id}[/bold] to {scale_label.upper()}...")
549
551
  target = await client.upsample_image(
@@ -871,6 +873,7 @@ async def _run_t2i(
871
873
  profile_dir=profile_dir,
872
874
  headless=headless,
873
875
  transport=transport,
876
+ out_dir=out if out is not None else output_root,
874
877
  ) as client:
875
878
  # Title is a `gflow-cli ...` prefix per project convention (post-rename a02684f).
876
879
  # cli_video.py's _run_t2v / _run_i2v don't currently set a title — tracked separately.
@@ -1264,6 +1267,7 @@ async def _run_i2i(
1264
1267
  profile_dir=profile_dir,
1265
1268
  headless=headless,
1266
1269
  transport=transport,
1270
+ out_dir=out if out is not None else output_root,
1267
1271
  ) as client:
1268
1272
  # Title is a `gflow-cli ...` prefix per project convention (post-rename a02684f).
1269
1273
  # cli_video.py's _run_t2v / _run_i2v don't currently set a title — tracked separately.
@@ -28,6 +28,7 @@ __all__ = [
28
28
  "SceneConcatError",
29
29
  "SecurityError",
30
30
  "TransportTimeoutError",
31
+ "UiSelectorDriftError",
31
32
  "UpscaleUnavailableError",
32
33
  "VideoModelSelectionError",
33
34
  "WafRejectionError",
@@ -381,6 +382,32 @@ class UpscaleUnavailableError(GFlowError):
381
382
  )
382
383
 
383
384
 
385
+ class UiSelectorDriftError(GFlowError):
386
+ """Raised when a UI-automation selector cascade finds no matching element.
387
+
388
+ Indicates that Flow's frontend has changed in a way that invalidates one
389
+ of the selector probes (mode-switch trigger, mode tab, sub-mode tab, etc.).
390
+ The ``detail`` names the probe label and includes the debug screenshot path
391
+ when one was captured.
392
+
393
+ This is a hard failure — gflow cannot safely proceed without the control —
394
+ but it is *diagnosed*, not opaque: the user gets the probe name and the
395
+ screenshot for inspection. Exit code 23 lets scripted callers branch on
396
+ "the UI changed and needs a selector update" versus generic error (1).
397
+ """
398
+
399
+ problem_type = "https://gflow-cli.dev/errors/ui-selector-drift"
400
+ title = "Flow UI selector drift"
401
+ _default_remediation = (
402
+ "A Flow editor UI element could not be located — Google may have updated "
403
+ "their frontend. Check for a newer gflow-cli release, then file a bug at "
404
+ "https://github.com/ffroliva/gflow-cli/issues referencing the probe name "
405
+ "and attaching the debug screenshot from this message, if one was captured "
406
+ "(review it first — the viewport may show your account name/avatar; do NOT "
407
+ "include tokens or signed URLs)."
408
+ )
409
+
410
+
384
411
  class SecurityError(GFlowError):
385
412
  """Raised when a security boundary is violated (e.g. profile_dir outside HOME)."""
386
413
 
@@ -643,6 +670,10 @@ EXIT_CODE_MAP: dict[type[GFlowError], int] = {
643
670
  # from WafRejectionError's 10 even though both are HTTP 403. Direct GFlowError
644
671
  # subclass, so unconstrained by the ordering invariant.
645
672
  UpscaleUnavailableError: 22,
673
+ # UiSelectorDriftError (issue #183): Flow UI changed, selector probe failed.
674
+ # Direct GFlowError subclass; exit 23 lets scripts distinguish "UI drifted"
675
+ # from generic error (1) without parsing stderr.
676
+ UiSelectorDriftError: 23,
646
677
  # ModelModeIncompatibilityError + VideoModelSelectionError BEFORE
647
678
  # ConfigurationError (their parent) so the isinstance walk lands on 17/18,
648
679
  # not 11. Per [[exit-code-map-ordering-invariant-test-pitfall]].
@@ -24,6 +24,7 @@ from gflow_cli.errors import ProblemDetails as ProblemDetails
24
24
  from gflow_cli.errors import RateLimitError as RateLimitError
25
25
  from gflow_cli.errors import SecurityError as SecurityError
26
26
  from gflow_cli.errors import TransportTimeoutError as TransportTimeoutError
27
+ from gflow_cli.errors import UiSelectorDriftError as UiSelectorDriftError
27
28
  from gflow_cli.errors import UpscaleUnavailableError as UpscaleUnavailableError
28
29
  from gflow_cli.errors import WafRejectionError as WafRejectionError
29
30
  from gflow_cli.errors import WireFormatError as WireFormatError
@@ -9,6 +9,7 @@ otherwise an account whose last-used mode was Video silently routes
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
+ from pathlib import Path
12
13
  from unittest.mock import AsyncMock, MagicMock
13
14
 
14
15
  import pytest
@@ -18,6 +19,7 @@ from gflow_cli.api.transports.ui_automation import UiAutomationTransport
18
19
  from gflow_cli.api.transports.ui_automation_video import (
19
20
  MODE_SWITCH_TRIGGER_SELECTORS,
20
21
  )
22
+ from gflow_cli.errors import UiSelectorDriftError
21
23
 
22
24
 
23
25
  def _cascade_page(visible: set[str]) -> MagicMock:
@@ -53,15 +55,35 @@ class TestSwitchToImageMode:
53
55
  @pytest.mark.asyncio
54
56
  async def test_raises_when_trigger_missing(self) -> None:
55
57
  page = _cascade_page(set())
56
- with pytest.raises(RuntimeError, match="mode-switch"):
58
+ with pytest.raises(UiSelectorDriftError, match="mode_switch_trigger"):
57
59
  await UiAutomationTransport._switch_to_image_mode(page, out_dir=None)
58
60
 
59
61
  @pytest.mark.asyncio
60
62
  async def test_raises_when_image_tab_missing(self) -> None:
61
63
  page = _cascade_page({MODE_SWITCH_TRIGGER_SELECTORS[0]})
62
- with pytest.raises(RuntimeError, match="Image tab"):
64
+ with pytest.raises(UiSelectorDriftError, match="Image tab"):
63
65
  await UiAutomationTransport._switch_to_image_mode(page, out_dir=None)
64
66
 
67
+ @pytest.mark.asyncio
68
+ async def test_trigger_miss_detail_carries_screenshot_path(self, tmp_path: Path) -> None:
69
+ # The screenshot path is the diagnostic payload issue-#183 reporters
70
+ # need — assert it survives into the user-visible detail.
71
+ page = _cascade_page(set())
72
+ page.screenshot = AsyncMock()
73
+ with pytest.raises(UiSelectorDriftError, match="debug_no_mode_trigger.png"):
74
+ await UiAutomationTransport._switch_to_image_mode(page, out_dir=tmp_path)
75
+ page.screenshot.assert_awaited()
76
+
77
+ @pytest.mark.asyncio
78
+ async def test_trigger_miss_detail_omits_screenshot_clause_without_out_dir(self) -> None:
79
+ # No out_dir → no capture; the detail must drop the clause entirely
80
+ # rather than render a literal "Screenshot: None".
81
+ page = _cascade_page(set())
82
+ with pytest.raises(UiSelectorDriftError) as excinfo:
83
+ await UiAutomationTransport._switch_to_image_mode(page, out_dir=None)
84
+ assert "Screenshot" not in str(excinfo.value)
85
+ assert "None" not in str(excinfo.value)
86
+
65
87
 
66
88
  class TestModeSwitchCallSite:
67
89
  """Regression guard: assert ``_switch_to_image_mode`` is actually
@@ -19,7 +19,12 @@ from gflow_cli.api.transports.ui_automation_video import (
19
19
  VideoGenerationMixin,
20
20
  )
21
21
  from gflow_cli.api.video import Aspect, GenerateVideoRequest, Mode, VideoModel, VideoStatus
22
- from gflow_cli.errors import AuthExpiredError, TransportTimeoutError, WireFormatError
22
+ from gflow_cli.errors import (
23
+ AuthExpiredError,
24
+ TransportTimeoutError,
25
+ UiSelectorDriftError,
26
+ WireFormatError,
27
+ )
23
28
 
24
29
 
25
30
  def _make_listener_page() -> tuple[MagicMock, list]:
@@ -227,7 +232,7 @@ class TestSwitchToVideoMode:
227
232
  @pytest.mark.asyncio
228
233
  async def test_raises_when_trigger_missing(self) -> None:
229
234
  page = _cascade_page(set())
230
- with pytest.raises(RuntimeError, match="mode-switch"):
235
+ with pytest.raises(UiSelectorDriftError, match="mode_switch_trigger"):
231
236
  await VideoGenerationMixin._switch_to_video_mode(page, out_dir=None)
232
237
 
233
238
  @pytest.mark.asyncio
@@ -235,9 +240,17 @@ class TestSwitchToVideoMode:
235
240
  from gflow_cli.api.transports import ui_automation_video as mod
236
241
 
237
242
  page = _cascade_page({mod.MODE_SWITCH_TRIGGER_SELECTORS[0]})
238
- with pytest.raises(RuntimeError, match="Video tab"):
243
+ with pytest.raises(UiSelectorDriftError, match="Video tab"):
239
244
  await VideoGenerationMixin._switch_to_video_mode(page, out_dir=None)
240
245
 
246
+ @pytest.mark.asyncio
247
+ async def test_submode_miss_raises_drift_error(self) -> None:
248
+ # The sub-mode probe is the same selector-cascade pattern as the
249
+ # mode-switch trigger and must carry the same typed-error contract.
250
+ page = _cascade_page(set())
251
+ with pytest.raises(UiSelectorDriftError, match="video_submode_references"):
252
+ await VideoGenerationMixin._switch_video_sub_mode(page, "references", out_dir=None)
253
+
241
254
 
242
255
  class TestWaitVideoEditorReady:
243
256
  @pytest.mark.asyncio
@@ -0,0 +1,74 @@
1
+ """CLI tests for the ``UiSelectorDriftError`` surface (issue #183).
2
+
3
+ Proves the two user-visible guarantees the typed error exists for:
4
+
5
+ 1. exit code 23 with the probe name in the output — instead of the old bare
6
+ ``RuntimeError`` whose message was hashed by the unhandled-error handler,
7
+ leaving the user with an opaque "Unexpected error" (exit 1);
8
+ 2. the ``out_dir`` wiring on the image surface — the transport can only
9
+ capture debug screenshots when ``FlowApiClient`` receives ``out_dir``,
10
+ and it was silently ``None`` for every ``gflow image`` command.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from pathlib import Path
16
+ from unittest.mock import AsyncMock, MagicMock, patch
17
+
18
+ import pytest
19
+ from click.testing import CliRunner, Result
20
+
21
+ from gflow_cli.errors import UiSelectorDriftError
22
+
23
+
24
+ @pytest.fixture
25
+ def runner() -> CliRunner:
26
+ return CliRunner()
27
+
28
+
29
+ def _drifting_client() -> MagicMock:
30
+ """Stub FlowApiClient whose generate_image hits selector drift."""
31
+ client = MagicMock()
32
+ client.__aenter__ = AsyncMock(return_value=client)
33
+ client.__aexit__ = AsyncMock(return_value=None)
34
+ client.create_project = AsyncMock(
35
+ return_value=MagicMock(project_id="proj-1", title="gflow-cli t2i")
36
+ )
37
+ client.generate_image = AsyncMock(
38
+ side_effect=UiSelectorDriftError(
39
+ "probe=mode_switch_trigger: no matching element found on the Flow editor."
40
+ )
41
+ )
42
+ return client
43
+
44
+
45
+ def _invoke_t2i(runner: CliRunner, tmp_path: Path, out_dir: Path) -> tuple[Result, MagicMock]:
46
+ client = _drifting_client()
47
+ with (
48
+ patch("gflow_cli.cli_image.FlowApiClient", return_value=client) as client_cls,
49
+ patch("gflow_cli.cli_image._make_provider_dir", return_value=tmp_path / "prof"),
50
+ patch("gflow_cli.cli_image._resolve_profile", return_value="default"),
51
+ ):
52
+ from gflow_cli.cli import main
53
+
54
+ result = runner.invoke(
55
+ main,
56
+ ["image", "t2i", "a cat", "--out", str(out_dir)],
57
+ catch_exceptions=False,
58
+ )
59
+ return result, client_cls
60
+
61
+
62
+ def test_t2i_selector_drift_maps_exit_23_with_probe_name(runner: CliRunner, tmp_path: Path) -> None:
63
+ result, _ = _invoke_t2i(runner, tmp_path, tmp_path / "out")
64
+ assert result.exit_code == 23, result.output
65
+ assert "mode_switch_trigger" in result.output
66
+ # Regression guard for the issue-#183 root cause: the typed error's real
67
+ # message must reach the user, not the hashed "Unexpected error" fallback.
68
+ assert "Unexpected error" not in result.output
69
+
70
+
71
+ def test_t2i_wires_out_dir_into_client(runner: CliRunner, tmp_path: Path) -> None:
72
+ out_dir = tmp_path / "out"
73
+ _, client_cls = _invoke_t2i(runner, tmp_path, out_dir)
74
+ assert client_cls.call_args.kwargs["out_dir"] == out_dir
@@ -22,6 +22,7 @@ from gflow_cli.errors import (
22
22
  ProblemDetails,
23
23
  RateLimitError,
24
24
  TransportTimeoutError,
25
+ UiSelectorDriftError,
25
26
  UpscaleUnavailableError,
26
27
  VideoModelSelectionError,
27
28
  WafRejectionError,
@@ -452,3 +453,39 @@ def test_chain_partial_error_partial_results_defaults_empty() -> None:
452
453
  (but present) ``partial_results`` list — never None."""
453
454
  err = ChainPartialError(detail="first link failed")
454
455
  assert err.partial_results == []
456
+
457
+
458
+ # ---------- UiSelectorDriftError (issue #183) ----------
459
+
460
+
461
+ def test_ui_selector_drift_error_exit_code_23() -> None:
462
+ """UiSelectorDriftError -> exit code 23 (issue #183).
463
+
464
+ Raised when a UI-automation selector cascade finds no matching element,
465
+ indicating that Flow's frontend has changed. Exit 23 lets scripted
466
+ callers distinguish "UI drifted" from generic error (1)."""
467
+ err = UiSelectorDriftError(
468
+ detail="probe=mode_switch_trigger: no matching element found on the Flow editor."
469
+ )
470
+ assert _exit_code_for(err) == 23
471
+ assert EXIT_CODE_MAP[UiSelectorDriftError] == 23
472
+ assert isinstance(err, GFlowError)
473
+ assert err.remediation_hint != ""
474
+
475
+
476
+ def test_ui_selector_drift_error_problem_details() -> None:
477
+ """UiSelectorDriftError carries RFC 9457 Problem Details with a stable type URI."""
478
+ err = UiSelectorDriftError(detail="probe=image_mode_tab: Image tab not found.")
479
+ pd = err.to_problem_details()
480
+ assert pd["type"] == "https://gflow-cli.dev/errors/ui-selector-drift"
481
+ assert pd["title"] == "Flow UI selector drift"
482
+ assert "image_mode_tab" in pd.get("detail", "")
483
+ assert "remediation_hint" in pd
484
+
485
+
486
+ def test_ui_selector_drift_error_not_a_subclass_of_flow_api_error() -> None:
487
+ """UiSelectorDriftError is a direct GFlowError subclass — it is NOT a
488
+ FlowApiError (it is a UI-automation concern, not a wire-protocol error)."""
489
+ err = UiSelectorDriftError(detail="probe=mode_switch_trigger: selector cascade failed.")
490
+ assert isinstance(err, GFlowError)
491
+ assert not isinstance(err, FlowApiError)
@@ -765,7 +765,7 @@ wheels = [
765
765
 
766
766
  [[package]]
767
767
  name = "gflow-cli"
768
- version = "0.17.0"
768
+ version = "0.18.0"
769
769
  source = { editable = "." }
770
770
  dependencies = [
771
771
  { name = "browser-cookie3" },
File without changes
File without changes
File without changes