gflow-cli 0.18.0__tar.gz → 0.19.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 (414) hide show
  1. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.env.template +9 -0
  2. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/CHANGELOG.md +24 -1
  3. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/PKG-INFO +3 -1
  4. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/CONFIGURATION.md +10 -0
  5. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/INDEX.md +1 -1
  6. gflow_cli-0.19.0/docs/LIVE_VERIFICATION_v0.19.0.md +60 -0
  7. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/SECURITY.md +20 -0
  8. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/USAGE.md +1 -0
  9. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/pyproject.toml +10 -1
  10. gflow_cli-0.19.0/scripts/dev/spike_patchright.py +357 -0
  11. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/__init__.py +1 -1
  12. gflow_cli-0.19.0/src/gflow_cli/api/_engine.py +113 -0
  13. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/_retry.py +5 -3
  14. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/client.py +22 -3
  15. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/recaptcha.py +11 -3
  16. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/transports/ui_automation.py +25 -5
  17. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/cli.py +21 -1
  18. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/config.py +25 -0
  19. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/errors.py +24 -0
  20. gflow_cli-0.19.0/tests/api/test_engine.py +95 -0
  21. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_retry.py +27 -0
  22. gflow_cli-0.19.0/tests/cli/test_settings_validation.py +35 -0
  23. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_config.py +27 -1
  24. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_errors.py +26 -0
  25. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/uv.lock +25 -2
  26. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.claude/README.md +0 -0
  27. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.claude/commands/gflow/active.md +0 -0
  28. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.claude/commands/gflow/branch-review.md +0 -0
  29. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.claude/commands/gflow/changelog.md +0 -0
  30. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.claude/commands/gflow/check.md +0 -0
  31. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.claude/commands/gflow/doc-review.md +0 -0
  32. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.claude/commands/gflow/known-issues.md +0 -0
  33. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.claude/commands/gflow/next.md +0 -0
  34. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.claude/commands/gflow/plan.md +0 -0
  35. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.claude/commands/gflow/pr-council-review.md +0 -0
  36. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.claude/commands/gflow/predict.md +0 -0
  37. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.claude/commands/gflow/release.md +0 -0
  38. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.claude/commands/gflow/scenario.md +0 -0
  39. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.claude/commands/gflow/status.md +0 -0
  40. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.gitattributes +0 -0
  41. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.github/CODEOWNERS +0 -0
  42. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  43. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.github/copilot-instructions.md +0 -0
  44. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.github/dependabot.yml +0 -0
  45. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.github/workflows/ci.yml +0 -0
  46. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.github/workflows/external-pr-triage.yml +0 -0
  47. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.github/workflows/governance-advisory.yml +0 -0
  48. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.github/workflows/governance-benchmark.yml +0 -0
  49. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.github/workflows/release.yml +0 -0
  50. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.gitignore +0 -0
  51. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.gitleaks.toml +0 -0
  52. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.pre-commit-config.yaml +0 -0
  53. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/.secrets.baseline +0 -0
  54. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/AGENTS.md +0 -0
  55. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/CLAUDE.md +0 -0
  56. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/CONFIGURATION.md +0 -0
  57. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/CONTRIBUTING.md +0 -0
  58. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/DISCLAIMER.md +0 -0
  59. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/GEMINI.md +0 -0
  60. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/KNOWN_ISSUES.md +0 -0
  61. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/LICENSE +0 -0
  62. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/PLAN.md +0 -0
  63. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/README.md +0 -0
  64. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/RELEASE.md +0 -0
  65. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/ROADMAP.md +0 -0
  66. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/conftest.py +0 -0
  67. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docker-compose.yml +0 -0
  68. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/AGENT_GUIDE.md +0 -0
  69. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/ARCHITECTURE.md +0 -0
  70. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/AUTHENTICATION.md +0 -0
  71. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/CHARACTER.md +0 -0
  72. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/CHARACTER_RECON.md +0 -0
  73. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/DATA_LAYER.md +0 -0
  74. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/DEBUGGING.md +0 -0
  75. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/DEMOS.md +0 -0
  76. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/DEVELOPMENT.md +0 -0
  77. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/E2E_TESTING.md +0 -0
  78. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/EXTERNAL_STORAGE.md +0 -0
  79. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/GITHUB.md +0 -0
  80. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/GOVERNANCE_BENCHMARK.md +0 -0
  81. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/IMAGE_UPSCALE_RECON.md +0 -0
  82. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/LIVE_VERIFICATION_data_layer.md +0 -0
  83. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/LIVE_VERIFICATION_image_batch.md +0 -0
  84. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/LIVE_VERIFICATION_v0.10.0.md +0 -0
  85. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/LIVE_VERIFICATION_v0.11.0.md +0 -0
  86. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/LIVE_VERIFICATION_v0.12.0.md +0 -0
  87. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/LIVE_VERIFICATION_v0.13.0.md +0 -0
  88. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/LIVE_VERIFICATION_v0.16.0.md +0 -0
  89. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/LIVE_VERIFICATION_v0.17.0.md +0 -0
  90. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/LIVE_VERIFICATION_v0.18.0.md +0 -0
  91. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/LIVE_VERIFICATION_v0.7.0.md +0 -0
  92. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/LIVE_VERIFICATION_v0.8.1.md +0 -0
  93. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/LIVE_VERIFICATION_v0.9.0.md +0 -0
  94. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/LIVE_VERIFICATION_v0.9.1.md +0 -0
  95. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/LIVE_VERIFICATION_video_download.md +0 -0
  96. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/MOVIE.md +0 -0
  97. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/PROJECT_STATUS.md +0 -0
  98. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/USER_GUIDE.md +0 -0
  99. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/assets/demo-split-pf.gif +0 -0
  100. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/assets/example-run.gif +0 -0
  101. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/schemas/movie-handoff.schema.json +0 -0
  102. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/docs/superpowers/plans/2026-06-12-issue-174-library-ui-attach/PLAN.md +0 -0
  103. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/examples/README.md +0 -0
  104. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/examples/batch_from_config.py +0 -0
  105. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/examples/multi_prompt_t2i.py +0 -0
  106. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/examples/sample_config.json +0 -0
  107. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/examples/sample_prompts.txt +0 -0
  108. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/examples/single_image_t2i.py +0 -0
  109. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/examples/workflow_chain.py +0 -0
  110. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/llms.txt +0 -0
  111. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/README.md +0 -0
  112. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/captured/01_upload_image.json +0 -0
  113. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/captured/02_batchAsyncGenerateVideoText.json +0 -0
  114. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/captured/03_batchCheckAsyncVideoGenerationStatus.json +0 -0
  115. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/captured/04_archive_workflow.json +0 -0
  116. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/captured/05_createProject.json +0 -0
  117. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/captured/06_batchGenerateImages.json +0 -0
  118. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/captured/07_batchGenerateImages_seeded.json +0 -0
  119. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/captured/08_batchAsyncGenerateVideoStartAndEndImage.json +0 -0
  120. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/captured/09_batchAsyncGenerateVideoReferenceImages.json +0 -0
  121. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/captured/10_batchCheckAsyncVideoGenerationStatus_successful.json +0 -0
  122. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/captured/11_batchCheckAsyncVideoGenerationStatus_failed.json +0 -0
  123. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/captured/12_create_scene.json +0 -0
  124. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/captured/13_sceneWorkflows_update.json +0 -0
  125. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/captured/14_get_scene_workflows.json +0 -0
  126. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/samples/captured/15_commit_flowWorkflow.json +0 -0
  127. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/ci/check_doc_links.py +0 -0
  128. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/ci/check_materiality.py +0 -0
  129. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/ci/check_repo_hygiene.py +0 -0
  130. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/debug_editor.py +0 -0
  131. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/debug_gen_settings.py +0 -0
  132. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/debug_settings.py +0 -0
  133. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/_recording_client.py +0 -0
  134. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/_spike_common.py +0 -0
  135. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/active_plan.py +0 -0
  136. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/capture_i2v_frame_slots_dom.py +0 -0
  137. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/capture_i2v_intercept_submit.py +0 -0
  138. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/capture_i2v_model_select_repro.py +0 -0
  139. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/capture_i2v_post_bind_state.py +0 -0
  140. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/capture_image_add_media_dom.py +0 -0
  141. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/capture_locale_invariants.py +0 -0
  142. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/cdp_drive_and_probe.py +0 -0
  143. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/character_create_spike.py +0 -0
  144. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/character_create_spike_v2.py +0 -0
  145. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/dump_character_selectors.js +0 -0
  146. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/make_project.py +0 -0
  147. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/materiality_backtest.py +0 -0
  148. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/monitor_pr_38.py +0 -0
  149. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/patch_character.py +0 -0
  150. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/record_flow_capture.py +0 -0
  151. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/skillopt/README.md +0 -0
  152. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/skillopt/harness.py +0 -0
  153. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/skillopt/tasks.json +0 -0
  154. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/spike_char_editor_dom.py +0 -0
  155. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/spike_char_gen_capture.py +0 -0
  156. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/spike_image_upscale_capture.py +0 -0
  157. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/spike_image_upscale_drive.py +0 -0
  158. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/spike_image_upscale_recaptcha_action.py +0 -0
  159. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/spike_image_upscale_rest_probe.py +0 -0
  160. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/spike_issue170_picker_locale_recon.py +0 -0
  161. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/spike_issue174_library_ui_recon.py +0 -0
  162. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/spike_movie_attach_payload.py +0 -0
  163. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/spike_movie_entity_recon.py +0 -0
  164. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/spike_movie_gen_capture.py +0 -0
  165. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/spike_movie_picker_select.py +0 -0
  166. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/spike_movie_voice_list.py +0 -0
  167. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/dev/spike_patch_entity.py +0 -0
  168. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/diag/README.md +0 -0
  169. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/diag/capture_flow_traffic.py +0 -0
  170. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/diag/memory_profile.py +0 -0
  171. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/diag/recaptcha_mint.py +0 -0
  172. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/record_demo.ps1 +0 -0
  173. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/smoke_image.py +0 -0
  174. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/smoke_real_chrome_image.py +0 -0
  175. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/smoke_video_editor.py +0 -0
  176. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/smoke_worker_style.py +0 -0
  177. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/scripts/verify_chrome_auth_viability.py +0 -0
  178. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/skills/README.md +0 -0
  179. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/skills/gflow-cli/SKILL.md +0 -0
  180. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/skills/plan/SKILL.md +0 -0
  181. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/skills/pr-council-review/SKILL.md +0 -0
  182. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/skills/predict/SKILL.md +0 -0
  183. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/skills/scenario/SKILL.md +0 -0
  184. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/skills/status/SKILL.md +0 -0
  185. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/sonar-project.properties +0 -0
  186. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/__main__.py +0 -0
  187. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/_cli_helpers.py +0 -0
  188. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/__init__.py +0 -0
  189. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/_sapisidhash.py +0 -0
  190. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/character.py +0 -0
  191. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/dto.py +0 -0
  192. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/image.py +0 -0
  193. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/image_upscale.py +0 -0
  194. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/routes.py +0 -0
  195. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/scene.py +0 -0
  196. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/transports/__init__.py +0 -0
  197. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/transports/_common.py +0 -0
  198. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/transports/_fingerprint.py +0 -0
  199. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/transports/base.py +0 -0
  200. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/transports/experimental/__init__.py +0 -0
  201. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/transports/experimental/bearer.py +0 -0
  202. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/transports/experimental/evaluate_fetch.py +0 -0
  203. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/transports/experimental/sapisidhash.py +0 -0
  204. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/transports/ui_automation_video.py +0 -0
  205. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/api/video.py +0 -0
  206. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/auth/__init__.py +0 -0
  207. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/auth/base.py +0 -0
  208. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/auth/cookies.py +0 -0
  209. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/auth/factory.py +0 -0
  210. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/auth/internal_chromium.py +0 -0
  211. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/auth/real_chrome.py +0 -0
  212. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/auth/strategies.py +0 -0
  213. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/auth/verification.py +0 -0
  214. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/browser_manager.py +0 -0
  215. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/chain.py +0 -0
  216. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/chain_manifest.py +0 -0
  217. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/cli_character.py +0 -0
  218. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/cli_data.py +0 -0
  219. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/cli_image.py +0 -0
  220. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/cli_models.py +0 -0
  221. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/cli_movie.py +0 -0
  222. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/cli_run.py +0 -0
  223. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/cli_scene.py +0 -0
  224. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/cli_video.py +0 -0
  225. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/composition.py +0 -0
  226. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/data/__init__.py +0 -0
  227. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/data/chain_repo.py +0 -0
  228. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/data/migrations/0001_initial.sql +0 -0
  229. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/data/migrations/0002_add_cloud_storage.sql +0 -0
  230. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/data/migrations/0003_add_scene_tables.sql +0 -0
  231. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/data/migrations/0004_add_scene_output_path.sql +0 -0
  232. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/data/migrations/0005_add_chain_links.sql +0 -0
  233. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/data/migrations/0006_add_operations_metadata.sql +0 -0
  234. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/data/migrations/__init__.py +0 -0
  235. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/data/models.py +0 -0
  236. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/data/queries.py +0 -0
  237. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/data/recorder.py +0 -0
  238. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/data/redaction.py +0 -0
  239. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/data/repository.py +0 -0
  240. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/data/store.py +0 -0
  241. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/exceptions.py +0 -0
  242. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/image_batch.py +0 -0
  243. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/json_output.py +0 -0
  244. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/manifest.py +0 -0
  245. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/media.py +0 -0
  246. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/movie_manifest.py +0 -0
  247. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/observability.py +0 -0
  248. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/paths.py +0 -0
  249. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/profile_store.py +0 -0
  250. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/services/__init__.py +0 -0
  251. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/services/character_create.py +0 -0
  252. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/src/gflow_cli/storage.py +0 -0
  253. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tasks/lessons.md +0 -0
  254. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/test_assets/sample_batch.json +0 -0
  255. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/test_assets/sample_batch.tsv +0 -0
  256. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/test_assets/sample_batch_invalid.tsv +0 -0
  257. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/__init__.py +0 -0
  258. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/__init__.py +0 -0
  259. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/fixtures/character_gen_response.json +0 -0
  260. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/fixtures/patch_entity_response.json +0 -0
  261. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_aisandbox_auth_error.py +0 -0
  262. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_aisandbox_auth_headers.py +0 -0
  263. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_bearer_redaction.py +0 -0
  264. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_character.py +0 -0
  265. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_client.py +0 -0
  266. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_client_character.py +0 -0
  267. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_client_delete_characters.py +0 -0
  268. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_client_generate_character.py +0 -0
  269. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_client_image.py +0 -0
  270. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_client_launch_kwargs.py +0 -0
  271. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_client_patch_entity.py +0 -0
  272. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_client_scene.py +0 -0
  273. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_client_upscale.py +0 -0
  274. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_concurrency.py +0 -0
  275. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_dto.py +0 -0
  276. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_image.py +0 -0
  277. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_image_dto.py +0 -0
  278. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_image_upscale.py +0 -0
  279. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_post_json_aisandbox_auth.py +0 -0
  280. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_recaptcha.py +0 -0
  281. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_routes.py +0 -0
  282. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_routes_character.py +0 -0
  283. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_routes_scene.py +0 -0
  284. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_sapisidhash_helper.py +0 -0
  285. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_scene_models.py +0 -0
  286. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_video.py +0 -0
  287. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/test_video_request.py +0 -0
  288. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/transports/__init__.py +0 -0
  289. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/transports/test_base.py +0 -0
  290. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/transports/test_bearer.py +0 -0
  291. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/transports/test_common.py +0 -0
  292. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/transports/test_evaluate_fetch.py +0 -0
  293. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/transports/test_factory.py +0 -0
  294. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/transports/test_fingerprint.py +0 -0
  295. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/transports/test_sapisidhash.py +0 -0
  296. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/transports/test_transport_timeout.py +0 -0
  297. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/transports/test_ui_automation.py +0 -0
  298. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/transports/test_ui_automation_batch.py +0 -0
  299. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/transports/test_ui_automation_image_mode.py +0 -0
  300. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/transports/test_ui_automation_video.py +0 -0
  301. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/api/transports/test_ui_character_editor.py +0 -0
  302. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/auth/strategies/test_factory.py +0 -0
  303. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/auth/strategies/test_strategies.py +0 -0
  304. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/auth/test_verification.py +0 -0
  305. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/__init__.py +0 -0
  306. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_cli_auth_list.py +0 -0
  307. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_cli_character.py +0 -0
  308. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_cli_character_create.py +0 -0
  309. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_cli_data.py +0 -0
  310. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_cli_image.py +0 -0
  311. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_cli_image_seed_removed.py +0 -0
  312. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_cli_image_selector_drift.py +0 -0
  313. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_cli_image_upscale.py +0 -0
  314. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_cli_models.py +0 -0
  315. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_cli_movie.py +0 -0
  316. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_cli_run.py +0 -0
  317. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_cli_scene.py +0 -0
  318. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_cli_video.py +0 -0
  319. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_cli_video_chain.py +0 -0
  320. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_error_handling.py +0 -0
  321. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_helpers.py +0 -0
  322. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_movie_manifest.py +0 -0
  323. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/cli/test_t2i_multi_prompt.py +0 -0
  324. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/composition/test_character.py +0 -0
  325. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/composition/test_compose_prompt.py +0 -0
  326. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/composition/test_handoff.py +0 -0
  327. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/conftest.py +0 -0
  328. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/data/__init__.py +0 -0
  329. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/data/test_chain_repo.py +0 -0
  330. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/data/test_find_incomplete_character.py +0 -0
  331. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/data/test_models.py +0 -0
  332. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/data/test_packaging.py +0 -0
  333. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/data/test_recorder.py +0 -0
  334. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/data/test_recorder_character.py +0 -0
  335. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/data/test_redaction.py +0 -0
  336. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/data/test_repository.py +0 -0
  337. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/data/test_scene_persistence.py +0 -0
  338. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/data/test_settings_and_errors.py +0 -0
  339. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/data/test_store_migrations.py +0 -0
  340. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/dev/test_record_flow_capture.py +0 -0
  341. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/dev/test_recording_client.py +0 -0
  342. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/__init__.py +0 -0
  343. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/conftest.py +0 -0
  344. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/test_aisandbox_auth_live.py +0 -0
  345. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/test_auth_verification_e2e.py +0 -0
  346. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/test_chain_e2e.py +0 -0
  347. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/test_character_create_e2e.py +0 -0
  348. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/test_data_layer_e2e.py +0 -0
  349. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/test_i2v_flags_e2e.py +0 -0
  350. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/test_image_batch_e2e.py +0 -0
  351. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/test_image_i2i_ref_cap_e2e.py +0 -0
  352. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/test_json_output_e2e.py +0 -0
  353. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/test_locale_selectors_e2e.py +0 -0
  354. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/test_scene_compose_live.py +0 -0
  355. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/test_transports_e2e.py +0 -0
  356. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/test_video_r2v_ref_cap_e2e.py +0 -0
  357. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/e2e/test_video_t2v_e2e.py +0 -0
  358. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/__init__.py +0 -0
  359. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/auth.feature +0 -0
  360. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/auth_login.feature +0 -0
  361. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/character_create.feature +0 -0
  362. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/character_read.feature +0 -0
  363. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/conftest.py +0 -0
  364. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/image.feature +0 -0
  365. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/image_upscale.feature +0 -0
  366. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/locale_picker_include.feature +0 -0
  367. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/test_auth_login_steps.py +0 -0
  368. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/test_auth_steps.py +0 -0
  369. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/test_character_create_steps.py +0 -0
  370. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/test_character_read_steps.py +0 -0
  371. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/test_image_steps.py +0 -0
  372. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/test_image_upscale_steps.py +0 -0
  373. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/test_locale_picker_include_steps.py +0 -0
  374. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/test_step_collision_guard.py +0 -0
  375. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/test_video_chain_steps.py +0 -0
  376. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/features/video_chain.feature +0 -0
  377. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/fixtures/__init__.py +0 -0
  378. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/fixtures/seeded_catalog.py +0 -0
  379. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/image_batch/__init__.py +0 -0
  380. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/image_batch/test_image_manifest.py +0 -0
  381. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/image_batch/test_observability_events.py +0 -0
  382. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/integration/__init__.py +0 -0
  383. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/integration/conftest.py +0 -0
  384. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/integration/constants.py +0 -0
  385. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/integration/test_storage_gcs.py +0 -0
  386. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/integration/test_storage_s3.py +0 -0
  387. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/scripts/test_capture_locale_invariants.py +0 -0
  388. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/scripts/test_check_materiality.py +0 -0
  389. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/scripts/test_check_repo_hygiene.py +0 -0
  390. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/scripts/test_materiality_backtest.py +0 -0
  391. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/services/__init__.py +0 -0
  392. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/services/test_character_create_redaction.py +0 -0
  393. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/services/test_character_create_saga.py +0 -0
  394. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/services/test_character_gen_no_direct_post.py +0 -0
  395. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/smoke/__init__.py +0 -0
  396. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/smoke/test_profile_account_smoke.py +0 -0
  397. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/smoke/test_real_flow.py +0 -0
  398. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_auth.py +0 -0
  399. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_browser_manager.py +0 -0
  400. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_chain.py +0 -0
  401. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_chain_manifest.py +0 -0
  402. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_cli_data.py +0 -0
  403. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_conftest_isolation.py +0 -0
  404. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_data_queries.py +0 -0
  405. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_documentation_gate.py +0 -0
  406. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_errors_403.py +0 -0
  407. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_json_output.py +0 -0
  408. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_manifest.py +0 -0
  409. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_marker_registry.py +0 -0
  410. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_media.py +0 -0
  411. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_observability.py +0 -0
  412. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_paths.py +0 -0
  413. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_profile_store.py +0 -0
  414. {gflow_cli-0.18.0 → gflow_cli-0.19.0}/tests/test_smoke.py +0 -0
@@ -106,6 +106,15 @@
106
106
  # visible window). The session is still reused from the persistent profile.
107
107
  # GFLOW_CLI_HEADLESS=true
108
108
 
109
+ # Browser automation engine — playwright (default) | patchright
110
+ # patchright is an OPT-IN, drop-in patched Playwright (Chromium) that avoids the
111
+ # Runtime.enable CDP leak for stronger reCAPTCHA-Enterprise evasion on the HEADED
112
+ # path. It is NOT a headless unlock. Requires a separate install:
113
+ # pip install patchright (or: pip install 'gflow-cli[patchright]')
114
+ # When using system Chrome (channel=chrome, the gflow default) you do NOT need
115
+ # `patchright install chromium`. Unset to revert to the default playwright engine.
116
+ # GFLOW_CLI_BROWSER_ENGINE=playwright
117
+
109
118
  # BCP-47 locale tag passed to Playwright's launch `locale=` parameter
110
119
  # (controls Accept-Language only — Chrome's UI language is forced to
111
120
  # en-US via the `--lang=en-US` launch arg regardless of this setting, so
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.19.0] — 2026-06-12
11
+
12
+ ### Added
13
+
14
+ - **Opt-in Patchright browser engine** via `GFLOW_CLI_BROWSER_ENGINE=patchright`
15
+ (default `playwright`, unchanged). Patchright is a drop-in patched Playwright
16
+ (Chromium) that runs page evaluations in an isolated execution context to
17
+ avoid the `Runtime.enable` CDP leak, for stronger reCAPTCHA-Enterprise evasion
18
+ on the **headed** path. It is **not** a headless unlock and must be installed
19
+ separately: `pip install 'gflow-cli[patchright]'`. The default engine is
20
+ byte-identical to before. `gflow auth status` now reports the active engine.
21
+ - `BrowserEngineUnavailableError` (exit code 24): selecting `patchright` without
22
+ the package installed now fails with a clear `pip install patchright`
23
+ remediation hint instead of a raw `ImportError` hashed to a generic exit 1.
24
+
25
+ ### Fixed
26
+
27
+ - An invalid `GFLOW_CLI_*` enum value (e.g. a typo'd `GFLOW_CLI_BROWSER_ENGINE`,
28
+ `GFLOW_CLI_PROVIDER`, or `GFLOW_CLI_LOG_LEVEL`) now fails with a clean
29
+ configuration error and **exit code 11** naming the offending variable, instead
30
+ of leaking a raw pydantic `ValidationError` traceback and exiting 1.
31
+
10
32
  ## [0.18.0] — 2026-06-12
11
33
 
12
34
  ### Added
@@ -1527,7 +1549,8 @@ shell-script template that branches on these codes.
1527
1549
 
1528
1550
  First skeleton. Not functional end-to-end yet.
1529
1551
 
1530
- [Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.18.0...HEAD
1552
+ [Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.19.0...HEAD
1553
+ [0.19.0]: https://github.com/ffroliva/gflow-cli/compare/v0.18.0...v0.19.0
1531
1554
  [0.18.0]: https://github.com/ffroliva/gflow-cli/compare/v0.17.0...v0.18.0
1532
1555
  [0.17.0]: https://github.com/ffroliva/gflow-cli/compare/v0.16.0...v0.17.0
1533
1556
  [0.16.0]: https://github.com/ffroliva/gflow-cli/compare/v0.15.1...v0.16.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gflow-cli
3
- Version: 0.18.0
3
+ Version: 0.19.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
@@ -58,6 +58,8 @@ Requires-Dist: ruff>=0.15.16; extra == 'dev'
58
58
  Provides-Extra: gcs
59
59
  Requires-Dist: gcsfs>=2024.2.0; extra == 'gcs'
60
60
  Requires-Dist: universal-pathlib>=0.2.5; extra == 'gcs'
61
+ Provides-Extra: patchright
62
+ Requires-Dist: patchright==1.60.1; extra == 'patchright'
61
63
  Provides-Extra: s3
62
64
  Requires-Dist: s3fs>=2024.2.0; extra == 's3'
63
65
  Requires-Dist: universal-pathlib>=0.2.5; extra == 's3'
@@ -190,6 +190,16 @@ GFLOW_CLI_HISTORY_PROMPTS=redacted gflow image t2i "confidential brief"
190
190
  **Default:** `true`
191
191
  **When to flip to `false`:** if reCAPTCHA Enterprise refuses to mint tokens (Google's bot-detection sometimes refuses headless Chromium but accepts a visible window). Set to `false` and re-run; the browser will appear during generation but the session is still reused from the persistent profile.
192
192
 
193
+ ### `GFLOW_CLI_BROWSER_ENGINE`
194
+
195
+ **What:** Selects the browser-automation engine backing the Playwright API.
196
+ **Values:** `playwright` | `patchright`
197
+ **Default:** `playwright`
198
+ **What `patchright` is:** an opt-in, drop-in patched Playwright (Chromium) that runs page evaluations in an isolated execution context to avoid the `Runtime.enable` CDP leak, for stronger reCAPTCHA-Enterprise evasion on the **headed** path. It is **not** a headless unlock — Google still detects headless Chromium regardless of engine.
199
+ **Install:** `pip install 'gflow-cli[patchright]'` (or `pip install patchright`). Selecting `patchright` without it installed fails fast with exit code 24 and a pip remediation hint. When using system Chrome (the gflow default, `channel=chrome`) you do **not** need `patchright install chromium`.
200
+ **Reverting:** unset the variable (or set `playwright`) — the default path is byte-identical to a build without this feature, with no profile migration.
201
+ **Security note:** the `patchright` extra ships a *patched Chromium driver* that handles your live Google session cookies; it is exact-pinned and treated as a security-review-required dependency. See [SECURITY.md § Dependencies](SECURITY.md).
202
+
193
203
  ### `GFLOW_CLI_LOCALE`
194
204
 
195
205
  **What:** BCP-47 locale tag passed to Playwright's `launch_persistent_context(locale=...)` — controls the `Accept-Language` HTTP header only.
@@ -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.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)
114
+ **"What was actually live-verified for the latest release?"** → latest: [LIVE_VERIFICATION_v0.19.0](LIVE_VERIFICATION_v0.19.0.md) (opt-in patchright engine: credit-free `image t2i` end-to-end under `GFLOW_CLI_BROWSER_ENGINE=patchright`, 5-layer ledger + missing-dep/invalid-enum/default-path checks; 403→200 premise documented as pending a hot profile). v0.14.0 / v0.15.0 / v0.15.1 shipped without a dedicated doc (CI + the v0.13.0 baseline). Prior: [v0.18.0](LIVE_VERIFICATION_v0.18.0.md) · [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,60 @@
1
+ # Live Verification — v0.19.0
2
+
3
+ Feature under test: **opt-in Patchright browser engine** (`GFLOW_CLI_BROWSER_ENGINE=patchright`).
4
+ Verified 2026-06-12 against live Google Flow on a real authenticated profile. Image
5
+ generation is credit-free, so the end-to-end run cost **$0**.
6
+
7
+ ## Layer 1 — file count
8
+
9
+ One image produced by `gflow image t2i` through the integrated patchright path:
10
+
11
+ ```
12
+ scripts/dev/_spike_out/patch_verify/images/2026-06-12/6cb21d9e-e4f9-40ba-897c-eb25c6333892_1.jpg
13
+ ```
14
+
15
+ (written to a throwaway `GFLOW_CLI_OUTPUT_DIR`, since cleaned up).
16
+
17
+ ## Layer 2 — magic bytes
18
+
19
+ `b[:3] == FF D8 FF` → **valid JPEG**. Size 723,911 bytes.
20
+
21
+ ## Layer 3 — dimensions / shape
22
+
23
+ Pillow: `format=JPEG dims=(768, 1376)` → correct **9:16** portrait (the default aspect).
24
+
25
+ ## Layer 4 — structlog invariants
26
+
27
+ From the live run (`correlation_id=40877345-0af4-472f-966a-94f88112f0d9`):
28
+
29
+ - `browser.engine_selected engine=patchright` — the **settings resolver** selected
30
+ patchright (the real wired path, not a test monkeypatch).
31
+ - `ui_automation.batch_request_body` — prompt submitted (`bytes=5054`).
32
+ - `ui_automation.batch_response_seen status=200` and
33
+ `ui_automation.batch_response_captured status=200` — `batchGenerateImages`
34
+ returned 200 and the response body parsed under patchright.
35
+
36
+ ## Layer 5 — user-confirmable artifact
37
+
38
+ A studio-lit image of "a small green cactus in a terracotta pot" — visually
39
+ inspectable, matches the prompt.
40
+
41
+ ## Supporting checks (same release)
42
+
43
+ | Check | Evidence | Result |
44
+ |---|---|---|
45
+ | Default path byte-identical | client-level `navigator.webdriver` read through real `FlowApiClient`: `playwright → None` (masked), `patchright → False` (both non-bot) | ✅ default still stealthed |
46
+ | Missing-dependency UX | `GFLOW_CLI_BROWSER_ENGINE=patchright` with patchright uninstalled → `image t2i` | ✅ **exit 24**, `BrowserEngineUnavailableError` + `pip install patchright` hint, no traceback, no browser launched |
47
+ | Invalid enum setting | `GFLOW_CLI_BROWSER_ENGINE=patchwright` (and `GFLOW_CLI_PROVIDER=bogus`) | ✅ **exit 11**, clean "Configuration error" naming the variable, no raw pydantic traceback |
48
+ | reCAPTCHA mint under patchright | `TokenMinter.mint` with `isolated_context=False` | ✅ returns a real token (the predicted isolated-world break, fixed) |
49
+ | Retry across engine boundary | patchright's distinct `TimeoutError` through the real retry policy | ✅ retried (unit test) |
50
+
51
+ ## Premise still pending (documented, not a blocker)
52
+
53
+ The *value* premise — does patchright reduce real 403s vs Playwright — could **not**
54
+ be measured this cycle: the documented hot profile (`denon82`) had cooled, so plain
55
+ Playwright also returned 200 (no 403 to flip). A faithful 403 cannot be emulated
56
+ locally (a route-injected 403 is engine-blind; inflating real WAF heat risks the
57
+ account). The feature ships as a **proven non-breaking, opt-in escape-hatch** with
58
+ the default unchanged. Re-fire trigger: the instant any profile actually 403s, run
59
+ `scripts/dev/spike_patchright.py --profile <hot> --engines playwright,patchright`
60
+ to capture the `WAF_403 → OK_200` differential.
@@ -201,6 +201,26 @@ Audited with `pip-audit` in CI on every push. Major dependency surface:
201
201
 
202
202
  No transitive dep with known CVEs at the time of v0.1.0 scaffold.
203
203
 
204
+ ### Optional `patchright` engine — elevated supply-chain trust
205
+
206
+ The `gflow-cli[patchright]` extra (opt-in via `GFLOW_CLI_BROWSER_ENGINE=patchright`)
207
+ installs **Patchright**, a single-maintainer package that ships a **patched
208
+ Chromium driver**. Because that driver *is* the browser process that loads your
209
+ real Google session — it has direct access to session cookies, the OAuth Bearer
210
+ token, and SAPISID — it carries a higher blast radius than an ordinary Python
211
+ dependency: a compromised release could exfiltrate the full Google session.
212
+
213
+ Controls:
214
+
215
+ - **Optional-only** — never in the base `dependencies`; the default install pulls
216
+ neither the package nor its driver.
217
+ - **Exact-pinned** (`patchright==1.60.1`) so a release cannot silently move the
218
+ browser binary underneath you.
219
+ - **Security-review-required on bump** — a Patchright version change is treated
220
+ like a browser-binary change, NOT a routine dependabot auto-merge.
221
+ - Default engine is `playwright` (Microsoft, security-reviewed); patchright is an
222
+ experiment you opt into per the trade-off above.
223
+
204
224
  ## Reporting
205
225
 
206
226
  | Issue type | How |
@@ -1031,6 +1031,7 @@ shell scripts can branch on the failure mode without parsing stderr.
1031
1031
  | `21` | `ChainPartialError` | A video chain stopped mid-way; earlier links completed | Resume from the last completed link shown in the error |
1032
1032
  | `22` | `UpscaleUnavailableError` | 4K upscale is gated to Flow **Ultra** accounts (HTTP 403) | Use `--scale 2k`, or upgrade the Flow plan |
1033
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 |
1034
+ | `24` | `BrowserEngineUnavailableError` | `GFLOW_CLI_BROWSER_ENGINE=patchright` but the engine is not installed | `pip install 'gflow-cli[patchright]'`, or unset `GFLOW_CLI_BROWSER_ENGINE` |
1034
1035
  | `130`| SIGINT | User-interrupted (Ctrl-C) | — |
1035
1036
 
1036
1037
  **Exit code 16 — data store / migration error.** Fires when:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "gflow-cli"
3
- version = "0.18.0"
3
+ version = "0.19.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" }
@@ -58,6 +58,15 @@ s3 = [
58
58
  chain = [
59
59
  "av>=12",
60
60
  ]
61
+ # Patchright engine extra — opt-in, drop-in patched Playwright (Chromium) that
62
+ # avoids the Runtime.enable CDP leak on the headed path. Selected via
63
+ # GFLOW_CLI_BROWSER_ENGINE=patchright. Ships a PATCHED Chromium driver that
64
+ # handles live Google session cookies/Bearer/SAPISID, so it is EXACT-pinned and
65
+ # treated as a security-review-required bump (NOT a dependabot auto-merge) —
66
+ # see SECURITY.md. Not a headless unlock.
67
+ patchright = [
68
+ "patchright==1.60.1",
69
+ ]
61
70
 
62
71
  [project.scripts]
63
72
  gflow = "gflow_cli.cli:main"
@@ -0,0 +1,357 @@
1
+ #!/usr/bin/env python3
2
+ r"""Patchright engine spike — the Phase-0 kill-gate for the opt-in browser engine.
3
+
4
+ Plan: docs/superpowers/plans/2026-06-12-patchright-engine/{PLAN.md,SCENARIO.md}
5
+ Predict verdict: CAUTION 6/10 — this spike must prove the premise before any
6
+ production file is touched.
7
+
8
+ Touches ZERO production files. It swaps the browser engine at RUNTIME (monkeypatch
9
+ of the module-level ``async_playwright`` that ``client.py`` / ``ui_automation.py``
10
+ import) and reuses the real ``FlowApiClient.generate_image`` path so the 403→200
11
+ comparison is faithful — not a hand-rolled request that could 403 for the wrong
12
+ reason. Image generation is credit-free (memory: flow-credits-videos-only), so the
13
+ decisive hot-profile leg costs $0.
14
+
15
+ Two probe phases per engine (``playwright`` baseline vs ``patchright``):
16
+
17
+ A. PRIMITIVE PROBE (owns its own page; full control)
18
+ - launch the real profile with the EXACT production launch kwargs
19
+ (replicates client._persistent_context_kwargs + the navigator.webdriver mask)
20
+ - record channel (scenario 7), navigator.webdriver (scenario 3 baseline)
21
+ - discover_site_key (scenario 8 / Leg 2)
22
+ - MINT A/B (scenario 1 / Leg 1, the make-or-break): under patchright, call the
23
+ reCAPTCHA execute JS with the default (isolated) context AND with
24
+ isolated_context=False — record which returns a token. This directly
25
+ validates/invalidates the "isolated_context default breaks grecaptcha"
26
+ hypothesis from predict.
27
+ - listener smoke (Legs 3/4 lite): page.on("response")/page.on("request") fire.
28
+
29
+ B. FAITHFUL GENERATION (scenario 2 / Leg 6 — DECISIVE, + real Leg 3)
30
+ - swap engine on client.py + ui_automation.py
31
+ - patch recaptcha.TokenMinter.mint to inject isolated_context=False (patchright)
32
+ - attach a context-page response listener capturing the batchGenerateImages
33
+ HTTP status + whether response.json() parses
34
+ - run the real generate_image and classify the outcome (200 / 403-WAF /
35
+ mint-broken / other)
36
+
37
+ Run it on a HOT profile (one currently 403ing under playwright) for the decisive
38
+ comparison. Pass-bar = patchright measurably flips 403→200 AND mint works AND
39
+ listeners parse. Any red leg → STOP (write SPIKE-RESULTS.md, recommend
40
+ cadence-shaping). See PLAN.md Phase 0.
41
+
42
+ Usage (headed, supervised; PYTHONUTF8=1 on Windows):
43
+
44
+ .venv\Scripts\python.exe scripts\dev\spike_patchright.py ^
45
+ --profile denon82 --engines playwright,patchright ^
46
+ --prompt "a red ceramic mug on a wooden table, studio light"
47
+
48
+ # primitives only (no generation), e.g. on a clean profile:
49
+ .venv\Scripts\python.exe scripts\dev\spike_patchright.py ^
50
+ --profile ffroliva --skip-generation
51
+
52
+ Outputs (gitignored): scripts/dev/_spike_out/spike_patchright_<ts>.json
53
+ """
54
+
55
+ from __future__ import annotations
56
+
57
+ import argparse
58
+ import asyncio
59
+ import json
60
+ import sys
61
+ import time
62
+ import traceback
63
+ from pathlib import Path
64
+ from typing import Any
65
+
66
+ _ROOT = Path(__file__).resolve().parents[2]
67
+ _SRC = _ROOT / "src"
68
+ if _SRC.exists() and str(_SRC) not in sys.path:
69
+ sys.path.insert(0, str(_SRC))
70
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
71
+
72
+ from _spike_common import default_out_path, resolve_profile_dir, step # noqa: E402
73
+
74
+ # Production surfaces under test (worktree src/ via the bootstrap above).
75
+ import gflow_cli.api.client as client_mod # noqa: E402
76
+ import gflow_cli.api.transports.ui_automation as ui_mod # noqa: E402
77
+ from gflow_cli.api import recaptcha as recaptcha_mod # noqa: E402
78
+ from gflow_cli.api import routes # noqa: E402
79
+ from gflow_cli.api.client import FlowApiClient # noqa: E402
80
+ from gflow_cli.api.image import Aspect, GenerateImageRequest, Model # noqa: E402
81
+ from gflow_cli.api.recaptcha import RecaptchaError, discover_site_key # noqa: E402
82
+ from gflow_cli.browser_manager import channel_for_profile # noqa: E402
83
+
84
+ _EXECUTE_JS = recaptcha_mod._EXECUTE_JS # noqa: SLF001 — spike reuses the production mint JS
85
+
86
+ # Original references so each engine run restores a clean module state.
87
+ _PW_ASYNC = client_mod.async_playwright
88
+ _ORIG_MINT = recaptcha_mod.TokenMinter.mint
89
+
90
+
91
+ def _engine_async_playwright(engine: str) -> Any:
92
+ """Return the async_playwright factory for *engine* (imports patchright lazily)."""
93
+ if engine == "playwright":
94
+ return _PW_ASYNC
95
+ if engine == "patchright":
96
+ from patchright.async_api import async_playwright as patch_async
97
+
98
+ return patch_async
99
+ msg = f"unknown engine {engine!r}"
100
+ raise ValueError(msg)
101
+
102
+
103
+ def _launch_kwargs(profile_dir: Path) -> dict[str, Any]:
104
+ """Replicate client._persistent_context_kwargs verbatim (headed, identical for
105
+ both engines so ENGINE is the only variable in the comparison)."""
106
+ return {
107
+ "user_data_dir": str(profile_dir),
108
+ "headless": False,
109
+ "viewport": {"width": 1280, "height": 720},
110
+ "locale": "en-US",
111
+ "extra_http_headers": {"Accept-Language": "en-US,en;q=0.9"},
112
+ "channel": channel_for_profile(profile_dir),
113
+ "ignore_default_args": ["--enable-automation", "--no-sandbox", "--password-store=basic"],
114
+ "args": ["--disable-blink-features=AutomationControlled", "--disable-dev-shm-usage"],
115
+ }
116
+
117
+
118
+ async def _eval_token(page: Any, site_key: str, action: str, *, isolated: bool | None) -> Any:
119
+ """Run the production reCAPTCHA execute JS. ``isolated=None`` → engine default
120
+ (no kwarg, valid for both engines); ``isolated=False`` → patchright main-world."""
121
+ if isolated is None:
122
+ return await page.evaluate(_EXECUTE_JS, [site_key, action])
123
+ return await page.evaluate(_EXECUTE_JS, [site_key, action], isolated_context=isolated)
124
+
125
+
126
+ async def probe_primitives(engine: str, profile_dir: Path, action: str) -> dict[str, Any]:
127
+ """Phase A — primitives on a self-owned page. No FlowApiClient, full control."""
128
+ out: dict[str, Any] = {"engine": engine, "channel": channel_for_profile(profile_dir)}
129
+ apw = _engine_async_playwright(engine)
130
+ req_count = {"request": 0, "response": 0}
131
+ try:
132
+ async with apw() as pw:
133
+ try:
134
+ ctx = await pw.chromium.launch_persistent_context(**_launch_kwargs(profile_dir))
135
+ except Exception as exc: # noqa: BLE001
136
+ out["launch_error"] = f"{type(exc).__name__}: {exc}"
137
+ out["hint"] = (
138
+ "patchright may need `patchright install chromium` if channel='chrome' "
139
+ "is not honoured — scenario 7."
140
+ )
141
+ return out
142
+ out["launch_ok"] = True
143
+ await ctx.add_init_script(
144
+ "Object.defineProperty(navigator,'webdriver',{get:()=>undefined})",
145
+ )
146
+ page = ctx.pages[0] if ctx.pages else await ctx.new_page()
147
+ page.on("request", lambda *_: req_count.__setitem__("request", req_count["request"] + 1))
148
+ page.on(
149
+ "response", lambda *_: req_count.__setitem__("response", req_count["response"] + 1)
150
+ )
151
+ await page.goto(routes.EDITOR_BOOTSTRAP_URL, wait_until="domcontentloaded", timeout=60_000)
152
+ await page.wait_for_timeout(4000)
153
+
154
+ out["navigator_webdriver"] = await page.evaluate("() => navigator.webdriver")
155
+ try:
156
+ site_key = await discover_site_key(page)
157
+ out["site_key_prefix"] = site_key[:12]
158
+ out["discover_site_key"] = "ok"
159
+ except RecaptchaError as exc:
160
+ out["discover_site_key"] = f"FAIL: {exc}"
161
+ site_key = ""
162
+
163
+ # MINT A/B — the make-or-break (Leg 1 / scenario 1).
164
+ if site_key:
165
+ # Engine default (playwright: main world; patchright: isolated world).
166
+ try:
167
+ tok = await _eval_token(page, site_key, action, isolated=None)
168
+ out["mint_default"] = "ok" if (isinstance(tok, str) and tok) else "empty"
169
+ except Exception as exc: # noqa: BLE001
170
+ out["mint_default"] = f"FAIL: {type(exc).__name__}: {str(exc)[:80]}"
171
+ # Patchright-only: force main world.
172
+ if engine == "patchright":
173
+ try:
174
+ tok2 = await _eval_token(page, site_key, action, isolated=False)
175
+ out["mint_isolated_false"] = (
176
+ "ok" if (isinstance(tok2, str) and tok2) else "empty"
177
+ )
178
+ except Exception as exc: # noqa: BLE001
179
+ out["mint_isolated_false"] = f"FAIL: {type(exc).__name__}: {str(exc)[:80]}"
180
+
181
+ out["listener_counts"] = dict(req_count)
182
+ await ctx.close()
183
+ except Exception as exc: # noqa: BLE001
184
+ out["error"] = f"{type(exc).__name__}: {exc}"
185
+ out["traceback"] = traceback.format_exc(limit=4)
186
+ return out
187
+
188
+
189
+ def _install_engine(engine: str) -> None:
190
+ """Runtime engine swap on the production modules + mint fix for patchright."""
191
+ apw = _engine_async_playwright(engine)
192
+ client_mod.async_playwright = apw
193
+ ui_mod.async_playwright = apw
194
+ if engine == "patchright":
195
+
196
+ async def _patched_mint(self: Any, action: str) -> str:
197
+ site_key = await self.site_key()
198
+ token = await self._page.evaluate( # noqa: SLF001
199
+ _EXECUTE_JS, [site_key, action], isolated_context=False
200
+ )
201
+ if not isinstance(token, str) or not token:
202
+ msg = f"empty token (patched main-world mint) for action={action!r}"
203
+ raise RecaptchaError(msg)
204
+ return token
205
+
206
+ recaptcha_mod.TokenMinter.mint = _patched_mint # type: ignore[method-assign]
207
+ else:
208
+ recaptcha_mod.TokenMinter.mint = _ORIG_MINT # type: ignore[method-assign]
209
+
210
+
211
+ def _restore_engine() -> None:
212
+ client_mod.async_playwright = _PW_ASYNC
213
+ ui_mod.async_playwright = _PW_ASYNC
214
+ recaptcha_mod.TokenMinter.mint = _ORIG_MINT # type: ignore[method-assign]
215
+
216
+
217
+ async def probe_generation(
218
+ engine: str, profile_dir: Path, *, prompt: str, aspect: str, model: str, action: str
219
+ ) -> dict[str, Any]:
220
+ """Phase B — faithful generate_image under *engine*; classify 200 vs 403 (Leg 6)."""
221
+ out: dict[str, Any] = {"engine": engine}
222
+ batch_hits: list[dict[str, Any]] = []
223
+ _install_engine(engine)
224
+ try:
225
+ req = GenerateImageRequest(
226
+ prompt=prompt, aspect=Aspect.from_cli(aspect), model=Model.from_cli(model)
227
+ )
228
+ async with FlowApiClient(profile_dir=profile_dir, headless=False) as client:
229
+ # Attach a response listener to every pooled page (scenario 4 — assert
230
+ # the batchGenerateImages body parses, not just that a callback fires).
231
+ def _on_response(resp: Any) -> None:
232
+ if "batchGenerateImages" in resp.url:
233
+ asyncio.create_task(_record(resp))
234
+
235
+ async def _record(resp: Any) -> None:
236
+ entry: dict[str, Any] = {"status": resp.status}
237
+ try:
238
+ body = await resp.json()
239
+ entry["json_ok"] = True
240
+ entry["has_media"] = bool(
241
+ isinstance(body, dict) and (body.get("media") or body.get("results"))
242
+ )
243
+ except Exception as exc: # noqa: BLE001
244
+ entry["json_ok"] = False
245
+ entry["json_err"] = type(exc).__name__
246
+ batch_hits.append(entry)
247
+
248
+ for p in getattr(client, "_pages", []):
249
+ p.on("response", _on_response)
250
+
251
+ t0 = time.time()
252
+ try:
253
+ img = await client.generate_image(req=req, recaptcha_action=action)
254
+ out["result"] = "OK_200"
255
+ out["media_url_present"] = bool(getattr(img, "url", None) or getattr(img, "name", None))
256
+ except RecaptchaError as exc:
257
+ out["result"] = "MINT_BROKEN"
258
+ out["detail"] = str(exc)[:160]
259
+ except client_mod.WafRejectionError as exc:
260
+ out["result"] = "WAF_403"
261
+ out["detail"] = str(exc)[:160]
262
+ except Exception as exc: # noqa: BLE001
263
+ out["result"] = f"OTHER:{type(exc).__name__}"
264
+ out["detail"] = str(exc)[:160]
265
+ out["elapsed_s"] = round(time.time() - t0, 1)
266
+ await asyncio.sleep(0.5) # let any in-flight _record tasks settle
267
+ out["batch_responses"] = batch_hits
268
+ except Exception as exc: # noqa: BLE001
269
+ out["error"] = f"{type(exc).__name__}: {exc}"
270
+ out["traceback"] = traceback.format_exc(limit=4)
271
+ finally:
272
+ _restore_engine()
273
+ return out
274
+
275
+
276
+ async def _run(args: argparse.Namespace, profile_dir: Path, out_path: Path) -> int:
277
+ engines = [e.strip() for e in args.engines.split(",") if e.strip()]
278
+ results: dict[str, Any] = {
279
+ "spike": "patchright-engine-phase0",
280
+ "capturedAt": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
281
+ "profile": args.profile,
282
+ "engines": engines,
283
+ "action": args.action,
284
+ "primitives": [],
285
+ "generation": [],
286
+ }
287
+ for engine in engines:
288
+ step("A", f"[{engine}] primitive probe", prefix="patch")
289
+ results["primitives"].append(await probe_primitives(engine, profile_dir, args.action))
290
+ if not args.skip_generation:
291
+ step("B", f"[{engine}] faithful generation (credit-free image)", prefix="patch")
292
+ results["generation"].append(
293
+ await probe_generation(
294
+ engine,
295
+ profile_dir,
296
+ prompt=args.prompt,
297
+ aspect=args.aspect,
298
+ model=args.model,
299
+ action=args.action,
300
+ )
301
+ )
302
+
303
+ out_path.parent.mkdir(parents=True, exist_ok=True)
304
+ out_path.write_text(json.dumps(results, indent=2, ensure_ascii=False), encoding="utf-8")
305
+
306
+ # Console summary — the at-a-glance gate readout.
307
+ print("\n================ PATCHRIGHT SPIKE SUMMARY ================", flush=True)
308
+ for p in results["primitives"]:
309
+ eng = p["engine"]
310
+ print(
311
+ f"[A {eng:10}] launch={p.get('launch_ok', p.get('launch_error', '?'))} "
312
+ f"channel={p.get('channel')} webdriver={p.get('navigator_webdriver')} "
313
+ f"discover={p.get('discover_site_key')} mint_default={p.get('mint_default')} "
314
+ f"mint_iso_false={p.get('mint_isolated_false', 'n/a')} "
315
+ f"listeners={p.get('listener_counts')}",
316
+ flush=True,
317
+ )
318
+ for g in results["generation"]:
319
+ eng = g["engine"]
320
+ print(
321
+ f"[B {eng:10}] result={g.get('result', g.get('error'))} "
322
+ f"elapsed={g.get('elapsed_s')}s batch={g.get('batch_responses')}",
323
+ flush=True,
324
+ )
325
+ print(f"\n[patch] full results -> {out_path}", flush=True)
326
+ print(
327
+ "[patch] GATE: pass = patchright mint works (mint_iso_false=ok) AND "
328
+ "(on a hot profile) patchright result=OK_200 while playwright result=WAF_403.",
329
+ flush=True,
330
+ )
331
+ return 0
332
+
333
+
334
+ def main(argv: list[str] | None = None) -> int:
335
+ p = argparse.ArgumentParser(description="Patchright engine Phase-0 spike (credit-free).")
336
+ p.add_argument("--profile", default="ffroliva", help="gflow profile name (use a HOT one for the decisive leg)")
337
+ p.add_argument("--engines", default="playwright,patchright", help="comma list")
338
+ p.add_argument("--prompt", default="a red ceramic mug on a wooden table, soft studio light")
339
+ p.add_argument("--aspect", default="9:16")
340
+ p.add_argument("--model", default="nano2")
341
+ p.add_argument("--action", default="imageGeneration", help="reCAPTCHA action (memory: action mismatch → silent 403)")
342
+ p.add_argument("--skip-generation", action="store_true", help="primitives only (no credit-free gen)")
343
+ p.add_argument("--out", default=None)
344
+ args = p.parse_args(argv)
345
+
346
+ profile_dir = resolve_profile_dir(args.profile)
347
+ out_path = Path(args.out) if args.out else default_out_path("spike_patchright", ".json")
348
+ step("--", f"profile={args.profile} engines={args.engines} gen={not args.skip_generation}", prefix="patch")
349
+ try:
350
+ return asyncio.run(_run(args, profile_dir, out_path))
351
+ except KeyboardInterrupt:
352
+ print("[patch] aborted", file=sys.stderr)
353
+ return 130
354
+
355
+
356
+ if __name__ == "__main__":
357
+ raise SystemExit(main())
@@ -1,3 +1,3 @@
1
1
  """gflow-cli — unofficial CLI for Google Flow."""
2
2
 
3
- __version__ = "0.18.0"
3
+ __version__ = "0.19.0"