gflow-cli 0.3.0a1__tar.gz → 0.4.0a2__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 (105) hide show
  1. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/.claude/commands/release.md +1 -1
  2. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/.env.template +22 -14
  3. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/.gitignore +3 -0
  4. gflow_cli-0.4.0a2/CHANGELOG.md +315 -0
  5. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/CLAUDE.md +19 -14
  6. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/CONTRIBUTING.md +10 -8
  7. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/DISCLAIMER.md +2 -2
  8. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/KNOWN_ISSUES.md +24 -6
  9. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/PKG-INFO +38 -29
  10. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/PLAN.md +93 -35
  11. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/README.md +36 -28
  12. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/ARCHITECTURE.md +49 -19
  13. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/AUTHENTICATION.md +12 -12
  14. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/CONFIGURATION.md +35 -33
  15. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/INDEX.md +12 -2
  16. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/SECURITY.md +13 -11
  17. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/USAGE.md +58 -24
  18. gflow_cli-0.4.0a2/docs/USER_GUIDE.md +625 -0
  19. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/superpowers/plans/2026-05-09-image-mvp-orchestration.md +1 -1
  20. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/superpowers/plans/2026-05-09-image-mvp.md +18 -18
  21. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/superpowers/plans/2026-05-09-video-mvp-orchestration.md +2 -2
  22. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/superpowers/plans/2026-05-09-video-mvp.md +112 -112
  23. gflow_cli-0.4.0a2/docs/superpowers/plans/2026-05-10-phase-4-hardening-orchestration.md +358 -0
  24. gflow_cli-0.4.0a2/docs/superpowers/plans/2026-05-10-phase-4-hardening.md +2736 -0
  25. gflow_cli-0.4.0a2/docs/superpowers/specs/2026-05-10-phase-4-hardening-design.md +484 -0
  26. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/pyproject.toml +7 -5
  27. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/README.md +1 -1
  28. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/scripts/smoke_e2e.py +5 -5
  29. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/scripts/smoke_image.py +5 -5
  30. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/skills/gflow-cli/SKILL.md +3 -3
  31. {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/__init__.py +1 -1
  32. gflow_cli-0.4.0a2/src/gflow_cli/__main__.py +6 -0
  33. gflow_cli-0.4.0a2/src/gflow_cli/_cli_helpers.py +185 -0
  34. {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/__init__.py +3 -3
  35. gflow_cli-0.4.0a2/src/gflow_cli/api/_retry.py +138 -0
  36. gflow_cli-0.4.0a2/src/gflow_cli/api/client.py +897 -0
  37. {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/recaptcha.py +2 -2
  38. {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/auth.py +11 -7
  39. {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/cli.py +29 -9
  40. {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/cli_image.py +25 -49
  41. {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/cli_video.py +77 -59
  42. {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/config.py +40 -6
  43. gflow_cli-0.4.0a2/src/gflow_cli/errors.py +216 -0
  44. {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/manifest.py +1 -1
  45. gflow_cli-0.4.0a2/src/gflow_cli/observability.py +160 -0
  46. {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/paths.py +4 -4
  47. {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/profile_store.py +7 -7
  48. gflow_cli-0.4.0a2/tasks/lessons.md +195 -0
  49. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_client.py +1 -1
  50. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_client_generate_video.py +44 -28
  51. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_client_image.py +304 -85
  52. gflow_cli-0.4.0a2/tests/api/test_concurrency.py +199 -0
  53. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_dto.py +1 -1
  54. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_image.py +2 -2
  55. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_image_dto.py +1 -1
  56. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_recaptcha.py +1 -1
  57. gflow_cli-0.4.0a2/tests/api/test_retry.py +243 -0
  58. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_routes.py +1 -1
  59. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_video.py +1 -1
  60. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/cli/test_cli_image.py +40 -40
  61. gflow_cli-0.4.0a2/tests/cli/test_error_handling.py +323 -0
  62. gflow_cli-0.4.0a2/tests/cli/test_helpers.py +158 -0
  63. gflow_cli-0.4.0a2/tests/conftest.py +38 -0
  64. gflow_cli-0.4.0a2/tests/features/__init__.py +0 -0
  65. gflow_cli-0.4.0a2/tests/features/auth.feature +28 -0
  66. gflow_cli-0.4.0a2/tests/features/conftest.py +89 -0
  67. gflow_cli-0.4.0a2/tests/features/image.feature +25 -0
  68. gflow_cli-0.4.0a2/tests/features/test_auth_steps.py +186 -0
  69. gflow_cli-0.4.0a2/tests/features/test_image_steps.py +191 -0
  70. gflow_cli-0.4.0a2/tests/features/test_step_collision_guard.py +16 -0
  71. gflow_cli-0.4.0a2/tests/features/test_video_steps.py +253 -0
  72. gflow_cli-0.4.0a2/tests/features/video.feature +27 -0
  73. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/test_auth.py +6 -6
  74. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/test_cli_video.py +83 -13
  75. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/test_config.py +70 -13
  76. gflow_cli-0.4.0a2/tests/test_errors.py +276 -0
  77. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/test_manifest.py +2 -2
  78. gflow_cli-0.4.0a2/tests/test_observability.py +165 -0
  79. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/test_paths.py +1 -1
  80. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/test_profile_store.py +7 -7
  81. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/test_smoke.py +7 -7
  82. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/uv.lock +161 -2
  83. gflow_cli-0.3.0a1/CHANGELOG.md +0 -127
  84. gflow_cli-0.3.0a1/src/flow_cli/__main__.py +0 -6
  85. gflow_cli-0.3.0a1/src/flow_cli/api/client.py +0 -567
  86. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/.claude/README.md +0 -0
  87. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/.gitattributes +0 -0
  88. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/.github/workflows/ci.yml +0 -0
  89. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/.github/workflows/release.yml +0 -0
  90. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/LICENSE +0 -0
  91. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/captured/01_upload_image.json +0 -0
  92. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/captured/02_batchAsyncGenerateVideoText.json +0 -0
  93. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/captured/03_batchCheckAsyncVideoGenerationStatus.json +0 -0
  94. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/captured/04_archive_workflow.json +0 -0
  95. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/captured/05_createProject.json +0 -0
  96. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/captured/06_batchGenerateImages.json +0 -0
  97. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/captured/07_batchGenerateImages_seeded.json +0 -0
  98. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/skills/README.md +0 -0
  99. {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/dto.py +0 -0
  100. {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/image.py +0 -0
  101. {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/routes.py +0 -0
  102. {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/video.py +0 -0
  103. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/__init__.py +0 -0
  104. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/__init__.py +0 -0
  105. {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/cli/__init__.py +0 -0
@@ -32,7 +32,7 @@ Ask the user (if not already provided):
32
32
  uv run ruff check src tests
33
33
  uv run ruff format --check src tests
34
34
  uv run pyright src
35
- uv run pytest -q --cov=flow_cli --cov-fail-under=80
35
+ uv run pytest -q --cov=gflow_cli --cov-fail-under=80
36
36
  ```
37
37
 
38
38
  4. **Bump version** in `pyproject.toml`:
@@ -1,6 +1,9 @@
1
1
  # gflow-cli — example environment variables.
2
- # Copy to `.env` (in this repo or in $FLOW_CLI_HOME) and customise.
2
+ # Copy to `.env` in the directory where you invoke `gflow` and uncomment what
3
+ # you want to override. ($GFLOW_CLI_HOME is NOT a .env search path — only the
4
+ # current working directory is loaded.)
3
5
  # All variables are OPTIONAL — defaults work out of the box for most users.
6
+ # Lines below are commented to show the SHIPPED DEFAULT — uncomment to override.
4
7
 
5
8
  # -----------------------------------------------------------------------------
6
9
  # Auth & profiles
@@ -11,10 +14,12 @@
11
14
  # Windows: %LOCALAPPDATA%\gflow-cli (e.g. C:\Users\<you>\AppData\Local\gflow-cli)
12
15
  # macOS: ~/Library/Application Support/gflow-cli
13
16
  # Linux: $XDG_DATA_HOME/gflow-cli (typically ~/.local/share/gflow-cli)
14
- # FLOW_CLI_HOME=
17
+ # GFLOW_CLI_HOME=
15
18
 
16
- # Default profile name. Override per-call with `--profile <name>`.
17
- FLOW_CLI_PROFILE=default
19
+ # Default profile name. When unset, the resolution chain auto-picks (see
20
+ # docs/CONFIGURATION.md § GFLOW_CLI_PROFILE). Override per-call with
21
+ # `--profile <name>`.
22
+ # GFLOW_CLI_PROFILE=default
18
23
 
19
24
  # -----------------------------------------------------------------------------
20
25
  # Output paths
@@ -28,7 +33,7 @@ FLOW_CLI_PROFILE=default
28
33
  # Windows: %USERPROFILE%\Downloads\gflow-cli
29
34
  # macOS: ~/Downloads/gflow-cli
30
35
  # Linux: $XDG_DOWNLOAD_DIR/gflow-cli (falls back to ~/Downloads/gflow-cli)
31
- # FLOW_CLI_OUTPUT_DIR=
36
+ # GFLOW_CLI_OUTPUT_DIR=
32
37
 
33
38
  # -----------------------------------------------------------------------------
34
39
  # Provider selection
@@ -37,31 +42,34 @@ FLOW_CLI_PROFILE=default
37
42
  # Backend to use for generations.
38
43
  # flow - reverse-engineered Flow REST API (default, requires Ultra/Pro)
39
44
  # official - official Veo SDK (planned for v0.5+, requires Gemini API key)
40
- FLOW_CLI_PROVIDER=flow
45
+ # GFLOW_CLI_PROVIDER=flow
41
46
 
42
- # Gemini API key — only required when FLOW_CLI_PROVIDER=official.
47
+ # Gemini API key — only required when GFLOW_CLI_PROVIDER=official.
43
48
  # Get one at https://aistudio.google.com/apikey
44
- # FLOW_CLI_GEMINI_API_KEY=
49
+ # GFLOW_CLI_GEMINI_API_KEY=
45
50
 
46
51
  # -----------------------------------------------------------------------------
47
52
  # Runtime tuning
48
53
  # -----------------------------------------------------------------------------
49
54
 
50
55
  # Per-request HTTP timeout (seconds). Veo videos can take 60-180s each.
51
- FLOW_CLI_TIMEOUT_SECONDS=600
56
+ # GFLOW_CLI_TIMEOUT_SECONDS=600
52
57
 
53
58
  # Logging level — DEBUG | INFO | WARNING | ERROR
54
- FLOW_CLI_LOG_LEVEL=INFO
59
+ # GFLOW_CLI_LOG_LEVEL=INFO
55
60
 
56
61
  # Log format — auto | text | json
57
62
  # auto = text on TTY, json when piped (default)
58
- FLOW_CLI_LOG_FORMAT=auto
63
+ # GFLOW_CLI_LOG_FORMAT=auto
59
64
 
60
- # Concurrency for batch operations (requires multiple profiles, v0.4+).
61
- FLOW_CLI_CONCURRENCY=1
65
+ # Per-worker Playwright Page-pool size for batch runs (1-16). FlowApiClient
66
+ # opens N Pages inside one shared persistent BrowserContext; `gflow video batch`
67
+ # fans out via asyncio.gather. ~30-60 MiB memory per Page on Chromium headless;
68
+ # don't exceed 8 without measuring. Shipped in v0.4.0a2.
69
+ # GFLOW_CLI_CONCURRENCY=1
62
70
 
63
71
  # Run Playwright in headless mode for non-`auth login` commands (true|false).
64
72
  # Default: true. Set to false if reCAPTCHA Enterprise refuses to mint tokens
65
73
  # (Google's bot-detection sometimes refuses headless Chromium but accepts a
66
74
  # visible window). The session is still reused from the persistent profile.
67
- FLOW_CLI_HEADLESS=true
75
+ # GFLOW_CLI_HEADLESS=true
@@ -51,3 +51,6 @@ samples/*.captured.json # sandbox-recorded API exchanges may contain PII
51
51
  # Claude Code worktrees and lock files
52
52
  .claude/worktrees/
53
53
  .claude/scheduled_tasks.lock
54
+
55
+ # Doc-council scratch dir (bundles + raw reviews + consensus matrix)
56
+ .doc-council/
@@ -0,0 +1,315 @@
1
+ # Changelog
2
+
3
+ All notable changes to `gflow-cli` will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.4.0a2] — 2026-05-11
11
+
12
+ > **Documentation polish.** Same release surface as v0.4.0a1; this tag fixes
13
+ > a doc-council pass: four broken Python snippets in the README, a shell
14
+ > exit-code branching example that silently dropped failures, a stale anchor
15
+ > link in `AUTHENTICATION.md`, three USER_GUIDE journeys the target audience
16
+ > needs (credit budgeting, pipeline wiring, error recovery), and a sweep of
17
+ > "planned v0.3 / v0.4" callouts across 9 files that had been overtaken by
18
+ > the Phase 4 release. No code changes. No tests changed.
19
+
20
+ ### Fixed (docs)
21
+
22
+ - **`README.md` Python quick-start snippet rewritten** — the prior block had
23
+ four real bugs (`from gflow_cli.paths import profile_dir` → import error,
24
+ `upload_image(path, project_id)` args reversed, `generate_video(prompt=,
25
+ start_asset=, aspect=)` wrong kwargs, `poll_video_status` method does not
26
+ exist). Snippet now uses the same invocation pattern as
27
+ `gflow_cli.cli_video._run_i2v` and would actually run.
28
+ - **`docs/USAGE.md` exit-code branching example** — `if ! cmd; then case $?`
29
+ always saw `0` because the `if` consumed the exit code; rewritten to
30
+ capture `rc=$?` first. Exit code `2` re-labelled "Bad CLI usage" (auth is
31
+ exit `3`).
32
+ - **`docs/AUTHENTICATION.md` anchor link** to the Phase 4 PLAN heading
33
+ fixed (was `#phase-4--hardening--post-v030a1`, did not exist).
34
+ - **`CHANGELOG.md` footer** — added `[0.4.0a1]:` and `[0.4.0a2]:` compare
35
+ links; reset `[Unreleased]` to compare from v0.4.0a2.
36
+ - **`docs/USER_GUIDE.md` Journey 2** endpoint name `flowMedia:batchGenerateVeoVideo`
37
+ → real route `/v1/video:batchAsyncGenerateVideoText`.
38
+ - **`docs/USER_GUIDE.md` Journey 5.2** invalid placeholder UUID
39
+ (`media-uuid-abc-...`) → canonical hex shape.
40
+ - **`docs/USER_GUIDE.md` Journey 7.1** `echo $?` placement — previously
41
+ captured the exit code of an intermediate command, not the failing batch.
42
+ - **`docs/USER_GUIDE.md` Journey 7.3** softened the "Flow doesn't re-bill"
43
+ claim — billing is a private-API contract we cannot assert.
44
+ - **`KNOWN_ISSUES.md` same-profile examples** swapped `gflow image batch`
45
+ (does not exist) → `gflow video batch`.
46
+
47
+ ### Added (docs)
48
+
49
+ - **`docs/USER_GUIDE.md` Journey on credit budgeting** — rule-of-thumb credit
50
+ cost per `video t2v` / `video i2v` / `image t2i` / `image i2i` call, links
51
+ to Flow's credit-balance UI, batch-cost math example.
52
+ - **`docs/USER_GUIDE.md` Journey on wiring outputs into a pipeline** —
53
+ deterministic output-dir layout, `find` (POSIX) + `Get-ChildItem`
54
+ (PowerShell) recipes, `ffmpeg` consumer example.
55
+ - **`docs/USER_GUIDE.md` Journey on `ContentPolicyError` / `RateLimitError`
56
+ recovery** — what each error means, how long to wait, prompt-rewrite
57
+ pattern, when retry is futile.
58
+ - **`README.md` doc-nav** now links `docs/USER_GUIDE.md` (was missing).
59
+ - **`README.md` Stack table** lists `tenacity` and `structlog` (were
60
+ shipped in v0.4.0a1 but not in the stack overview).
61
+
62
+ ### Changed (docs)
63
+
64
+ - **`CHANGELOG.md` [0.4.0a1] section reordered** — `Added — Phase 4
65
+ hardening` now appears before `Breaking`. The hardening release was
66
+ user-visible value; the env-var rename was a one-line update for most
67
+ users.
68
+ - **Per-class exit codes 3–7** promoted from a bullet to its own "Migration
69
+ notes" subsection in the [0.4.0a1] block.
70
+ - **Version-time-warp sweep across 9 files** — every `(planned v0.3)`,
71
+ `(planned v0.4)`, `v0.3+ will add`, `v0.4 will add`, and `current scaffold
72
+ ignores this` line either describes shipped behaviour or points at v0.5+.
73
+ Files touched: `README.md`, `CHANGELOG.md`, `CLAUDE.md`, `PLAN.md`,
74
+ `KNOWN_ISSUES.md`, `CONFIGURATION.md`, `AUTHENTICATION.md`,
75
+ `ARCHITECTURE.md`, `DISCLAIMER.md`, `SECURITY.md`, `CONTRIBUTING.md`,
76
+ `.env.template`.
77
+ - **`docs/ARCHITECTURE.md` Concurrency section** describes the shipped
78
+ `asyncio.Queue` Page pool, not the target-DDD `Semaphore` model.
79
+ - **`docs/ARCHITECTURE.md` Observability section** describes the shipped
80
+ `error_raised` / `error_unhandled` event names, not the target-DDD
81
+ dot-path names.
82
+ - **`docs/ARCHITECTURE.md` DDD error class names** annotated as target —
83
+ shipped Phase 4 names (`AuthExpiredError`, `RateLimitError`,
84
+ `ContentPolicyError`, `NetworkError`, `WireFormatError`) listed alongside.
85
+ - **`docs/CONFIGURATION.md` `GFLOW_CLI_CONCURRENCY`** describes shipped
86
+ behaviour (per-worker Page pool, `asyncio.gather` fan-out).
87
+
88
+ ## [0.4.0a1] — 2026-05-11
89
+
90
+ > **Phase 4 hardening release.** Concurrency, retry/backoff, typed errors,
91
+ > and structured logs ship — your existing scripts keep running (the
92
+ > `FLOW_CLI_*` env-var shim is in place until v0.5.0). The user-visible
93
+ > contract that changed: shell scripts can now branch on stable per-class
94
+ > exit codes (3–7) for auth / rate-limit / content-policy / network /
95
+ > wire-format failures.
96
+
97
+ ### Added — Phase 4 hardening
98
+
99
+ - **Per-worker Playwright Page pool.** `FlowApiClient.__aenter__` opens
100
+ `Settings.concurrency` Pages inside a single persistent BrowserContext.
101
+ Operations check out a Page via `asyncio.Queue` (FIFO, bounded by
102
+ `maxsize=N`). `GFLOW_CLI_CONCURRENCY=N` (1–16) now actually parallelizes.
103
+ - **`gflow video batch` fans out via `asyncio.gather`** over manifest
104
+ entries — was sequential pre-v0.4.0a1.
105
+ - **`tenacity`-based retry layer** (3 attempts, exponential jittered
106
+ backoff 1s±25% → 2s±25% → 4s±25%) on 5xx / 429 / `playwright.async_api.Error`
107
+ / `TimeoutError`. `Retry-After` honoured, **capped at 60 s**. `reraise=True`
108
+ so the original exception's `__cause__` chain is preserved. reCAPTCHA
109
+ token re-minted **inside the retry loop, every attempt**, on the worker's
110
+ own Page.
111
+ - **RFC 9457 Problem Details exception hierarchy:**
112
+ `GFlowError → FlowApiError → {AuthExpiredError, RateLimitError,
113
+ ContentPolicyError, NetworkError, WireFormatError}`. `except FlowApiError`
114
+ catches the typed subclasses (back-compat). Each carries
115
+ `problem_type` URI, `title`, `status`, `detail`, `instance`
116
+ (`gflow:error:<correlation_id>`), `remediation_hint`, and `route`.
117
+ `to_problem_details()` serializes to the RFC 9457 JSON shape.
118
+ - **Per-class exit codes**: 3 (auth) / 4 (rate-limit) / 5 (content-policy) /
119
+ 6 (network) / 7 (wire-format). Exit 1 = unhandled. Exit 130 = SIGINT.
120
+ - **`WireFormatError` discovery payload** — `route_name`, `http_status`,
121
+ `content_type`, `top_level_keys`, `body_prefix_redacted` so log mining
122
+ can propose new error subclasses for unexpected response shapes.
123
+ - **`structlog` bootstrap** with TTY auto-detection (text on TTY, JSON when
124
+ piped). `show_locals=False` mandatory on the exception renderer so frame
125
+ locals (which may contain auth tokens) NEVER reach the log stream.
126
+ `correlation_id` + `cli_version` bound via `contextvars` at the process
127
+ boundary.
128
+ - **`error_raised` and `error_unhandled` events.** `error_raised` for caught
129
+ `GFlowError`s — carries Problem Details. `error_unhandled` for anything
130
+ else — privacy-safe: hashes message + stack with SHA-256, never logs raw
131
+ payload.
132
+ - **12 `pytest-bdd` scenarios** across `auth.feature`, `video.feature`,
133
+ `image.feature` — all use a mocked `FlowApiClient`. A
134
+ `_forbid_live_playwright` autouse tripwire fails any scenario that
135
+ accidentally tries to start a real browser.
136
+
137
+ ### Migration notes — stable exit codes
138
+
139
+ Shell scripts that previously branched on exit code `1` for any failure
140
+ can now distinguish the failure class. The mapping is locked by an
141
+ ordering-invariant test in `tests/test_errors.py`:
142
+
143
+ | Exit | Error class | Meaning | Retry? |
144
+ |------|-----------------------|--------------------------------------|----------------|
145
+ | 0 | — | Success | — |
146
+ | 1 | (unhandled) | Bug. Filed via `error_unhandled` | No |
147
+ | 2 | (Click) | Bad CLI usage / missing arg | Fix the call |
148
+ | 3 | `AuthExpiredError` | Session cookies invalidated | After re-login |
149
+ | 4 | `RateLimitError` | Flow returned 429 | Yes, with wait |
150
+ | 5 | `ContentPolicyError` | Prompt blocked upstream | After rewrite |
151
+ | 6 | `NetworkError` | DNS / TLS / 5xx after retry | Yes |
152
+ | 7 | `WireFormatError` | Response shape changed (Flow update) | File a bug |
153
+ | 130 | (SIGINT) | User Ctrl-C | — |
154
+
155
+ See [`docs/USAGE.md § Exit codes`](docs/USAGE.md#exit-codes) for a
156
+ shell-script template that branches on these codes.
157
+
158
+ ### Breaking — package + env-var rename
159
+
160
+ - **Python package renamed: `flow_cli` → `gflow_cli`.** All imports must
161
+ change: `from gflow_cli...` (was `from flow_cli...`). The PyPI distribution
162
+ name (`gflow-cli`), the CLI binary (`gflow`), and the user data directory
163
+ (`gflow-cli/` under `platformdirs`) are unchanged.
164
+ - **Env var prefix renamed: `FLOW_CLI_*` → `GFLOW_CLI_*`.** Affected vars:
165
+ `GFLOW_CLI_HOME`, `GFLOW_CLI_OUTPUT_DIR`, `GFLOW_CLI_PROFILE`,
166
+ `GFLOW_CLI_HEADLESS`, `GFLOW_CLI_LOG_LEVEL`, `GFLOW_CLI_LOG_FORMAT`,
167
+ `GFLOW_CLI_PROVIDER`, `GFLOW_CLI_TIMEOUT_SECONDS`, `GFLOW_CLI_CONCURRENCY`,
168
+ `GFLOW_CLI_GEMINI_API_KEY`.
169
+ - **Backwards-compat shim.** Legacy `FLOW_CLI_*` env vars continue to work
170
+ in v0.4.x; on first encounter the process emits a single
171
+ `DeprecationWarning` to stderr summarising the promoted keys. The shim
172
+ will be removed in v0.5.0 — update your `.env` files and shell exports.
173
+
174
+ ### Changed
175
+
176
+ - `FlowApiError` re-parented under `GFlowError`. Legacy positional
177
+ constructor `FlowApiError(status, body, *, route)` preserved (auto-detected
178
+ via `isinstance(args[0], int) and not isinstance(args[0], bool)`).
179
+ - `_resolve_profile` and `_make_provider_dir` deduped — relocated from
180
+ `cli_image.py` + `cli_video.py` to `gflow_cli._cli_helpers`. AST-based
181
+ drift guard in `tests/cli/test_helpers.py` prevents regression.
182
+ - All `logging.*` callsites in `src/` migrated to `structlog`. The
183
+ remaining `print()` in `auth.py` swapped to Rich `console.print()`.
184
+
185
+ ### Internal
186
+
187
+ - New module: `gflow_cli.errors` (RFC 9457 hierarchy + `EXIT_CODE_MAP`).
188
+ - New module: `gflow_cli.observability` (structlog bootstrap + event
189
+ emitters; `show_locals=False` via
190
+ `ExceptionRenderer(ExceptionDictTransformer(show_locals=False))`).
191
+ - New module: `gflow_cli.api._retry` (tenacity `AsyncRetrying` +
192
+ `Retry-After` parser, capped at 60 s).
193
+ - New module: `gflow_cli._cli_helpers` (shared CLI-boundary handlers +
194
+ profile/provider helpers).
195
+
196
+ ## [0.3.0a1] — 2026-05-10
197
+
198
+ ### Added
199
+ - **`gflow image upload PATH`** — upload a single local image (PNG/JPEG) into a
200
+ fresh Flow project and print the asset UUID + dimensions Flow inferred. The
201
+ UUID is reusable as a starting frame for `gflow image i2i --ref` and
202
+ `gflow video i2v`.
203
+ - **`gflow image t2i PROMPT`** — text-to-image generation (1–4 images per call)
204
+ via Google Flow's Imagen / Nano Banana models.
205
+ Flags: `--model {nano2|nano-pro|image4}`, `--aspect {9:16|16:9|1:1|4:3|3:4}`,
206
+ `-n/--count` (1–4), `--seed` (single-image only), `--out DIR`, `--profile`.
207
+ Files land date-partitioned under `$GFLOW_CLI_OUTPUT_DIR/images/<YYYY-MM-DD>/`
208
+ by default; `--out DIR` writes flat as `<DIR>/<media_name>_<n>.png`.
209
+ - **`gflow image i2i PROMPT --ref PATH_OR_UUID`** — image-to-image generation
210
+ with one or more reference images. Each `--ref` is classified at the CLI
211
+ boundary: case-insensitive 8-4-4-4-12 hex UUIDs are passed through verbatim
212
+ (no upload), anything else is canonicalized (symlinks resolved at validation
213
+ time) and uploaded before use. `--ref` is repeatable; UUIDs and paths can mix
214
+ freely on the same call. Same flag set as `t2i` otherwise.
215
+ - **Multi-image fan-out** — `t2i` / `i2i` with `-n {2..4}` mint a single shared
216
+ `batch_id` and issue N parallel POSTs (one per shot, each with its own random
217
+ seed). Same-batch images share the prompt + refs; per-shot variation comes
218
+ from independent seeds.
219
+ - **Three image models** wired behind CLI aliases:
220
+ `nano2` → `NARWHAL` (Nano Banana 2; default, fast/balanced),
221
+ `nano-pro` → `GEM_PIX_2` (Nano Banana Pro; higher quality),
222
+ `image4` → `IMAGEN_3_5` (Imagen 4; photoreal-leaning).
223
+ - **Five aspect ratios** for image generation: `9:16`, `16:9`, `1:1`, `4:3`,
224
+ `3:4` (default `9:16`, matching the Flow web UI).
225
+ - `download_image()` on `FlowApiClient` — direct download of a generated
226
+ image's signed `fifeUrl` to disk. Streams to a temp file and atomically
227
+ renames on success; enforces an SSRF host allowlist (only Google-controlled
228
+ CDNs accepted).
229
+ - `scripts/smoke_image.py` — live single-image E2E smoke script (image
230
+ counterpart of `scripts/smoke_e2e.py` for video). Run after
231
+ `gflow auth login` to exercise the full happy path: project create →
232
+ `batchGenerateImages` → fifeUrl download.
233
+
234
+ ### Changed
235
+ - `FlowApiClient.upload_image` now validates **PNG/JPEG/WebP/GIF magic
236
+ bytes** and rejects files larger than **20 MB** before issuing the upload
237
+ request. Existing callers (`gflow video i2v`, `gflow video batch`) inherit
238
+ the stricter validation; previously-undocumented use of `upload_image` for
239
+ non-image payloads no longer works (was never officially supported).
240
+ - Project renamed `flow-cli` → `gflow-cli` across all docs and source. The
241
+ PyPI package and GitHub repo were already at the new name in v0.2.0a1;
242
+ this commit completes the in-source rename. Local clones may want to
243
+ rename their working directory to match `gh clone https://github.com/ffroliva/gflow-cli`
244
+ behavior.
245
+
246
+ ### Security
247
+ - DEBUG-level body logs now redact reCAPTCHA Enterprise tokens and other
248
+ bearer-style fields before emission, eliminating a token-leak vector when
249
+ users share verbose logs while filing bug reports.
250
+ - `download_image()` enforces an **SSRF host allowlist** on the signed
251
+ `fifeUrl` returned by Flow — only Google-controlled image CDNs
252
+ (`*.googleusercontent.com`, etc.) are followed; any other host raises
253
+ before the GET is issued. Defends against a Flow-side bug or compromise
254
+ redirecting downloads to an attacker-controlled origin.
255
+ - `project_id` allowlist regex `^[A-Za-z0-9-]{1,128}$` on
256
+ `batch_generate_images_url` — closes percent-encoded slash (`%2F`),
257
+ Unicode-lookalike (U+FF0F / U+2215 / U+29F8), and CRLF/NUL injection
258
+ bypasses that the previous denylist guard let through.
259
+
260
+ ### CI
261
+ - Test matrix now includes Python 3.13 alongside 3.11 and 3.12.
262
+
263
+ ## [0.2.0a1] — 2026-05-09
264
+
265
+ ### Added
266
+ - **`gflow video t2v`** — generate a video from a text prompt via Veo 3.1.
267
+ Flags: `--aspect 9:16|16:9|1:1`, `--seed`, `--output`, `--profile`, `--poll-interval`.
268
+ - **`gflow video i2v`** — generate a video from a start image + text prompt (Veo 3.1 I2V).
269
+ - **`gflow video batch`** — run a TSV manifest of video generations against one shared project.
270
+ - `gflow_cli.api` package — low-level REST client (`FlowApiClient`) + value objects
271
+ (`GenerateVideoRequest`, `VideoOperation`, `VideoStatus`) for video generation.
272
+ - `gflow_cli.api.recaptcha` — reCAPTCHA Enterprise token minting via Playwright `page.evaluate`.
273
+ `TokenMinter` caches the discovered site key per session; `mint(action)` is called immediately
274
+ before each generation request.
275
+ - `gflow_cli.manifest` — TSV manifest parser for `gflow video batch`. Supports optional
276
+ `start_image`, `end_image`, `aspect`, `output_path` columns; skips `# `-prefixed comments.
277
+ - `GFLOW_CLI_HEADLESS` env var (`bool`, default `true`). Set to `false` if reCAPTCHA refuses
278
+ to mint tokens in headless mode (Google bot detection fallback).
279
+ - `scripts/smoke_e2e.py` — one-shot live T2V smoke test; run after `gflow auth login` to
280
+ verify the full happy path (create project → generate_video → poll → download).
281
+ - **`CLAUDE.md`** at repo root — project memory hub for AI coding agents
282
+ (Claude Code reads natively; Cursor/Codex/Gemini/Aider can read as reference).
283
+ - **`.claude/`** directory — repo-local Claude Code surface for maintainers.
284
+ - `.claude/README.md` — what goes here, how to extend.
285
+ - `.claude/commands/release.md` — `/release` slash command that automates
286
+ version bump + CHANGELOG migration + tag + push, with quality gates.
287
+ - `gflow_cli.profile_store` — profile inventory + default-profile persistence
288
+ in `$GFLOW_CLI_HOME/config.toml`. Five-step resolution chain (CLI flag > env >
289
+ config > auto-select > raise) with named exceptions
290
+ (`NoProfilesError`, `NoDefaultProfileError`).
291
+ - New auth subcommands: bare `gflow auth`, `gflow auth list`, `gflow auth use <name>`,
292
+ `gflow auth logout [--profile NAME] [-y]`. First login auto-sets default profile.
293
+ - `KNOWN_ISSUES.md` at repo root — open/mitigated/resolved issues with workarounds.
294
+ - `docs/` tree (INDEX, AUTHENTICATION, CONFIGURATION, ARCHITECTURE, USAGE, SECURITY).
295
+ - `.env.template` documenting every supported env var.
296
+ - GitHub Actions CI: ruff, pyright, pytest on Python 3.11 and 3.12.
297
+ - GitHub Actions release workflow: tag-triggered PyPI publish via Trusted Publishing.
298
+ - MIT license, comprehensive README, [`DISCLAIMER.md`](DISCLAIMER.md), [`CONTRIBUTING.md`](CONTRIBUTING.md).
299
+ - [`skills/gflow-cli/SKILL.md`](skills/gflow-cli/SKILL.md) — installable Claude Code Skill.
300
+
301
+ ### Removed
302
+ - `gflow_cli.providers.FlowProvider` and `gflow_cli.models` — superseded by `gflow_cli.api`.
303
+ - Legacy CLI stubs: `gflow upload`, `gflow generate`, `gflow status`, `gflow download`,
304
+ `gflow i2v`. Replaced by the wired `gflow video` subgroup.
305
+
306
+ ## [0.1.0] — _unreleased_
307
+
308
+ First skeleton. Not functional end-to-end yet.
309
+
310
+ [Unreleased]: https://github.com/ffroliva/gflow-cli/compare/v0.4.0a2...HEAD
311
+ [0.4.0a2]: https://github.com/ffroliva/gflow-cli/compare/v0.4.0a1...v0.4.0a2
312
+ [0.4.0a1]: https://github.com/ffroliva/gflow-cli/compare/v0.3.0a1...v0.4.0a1
313
+ [0.3.0a1]: https://github.com/ffroliva/gflow-cli/releases/tag/v0.3.0a1
314
+ [0.2.0a1]: https://github.com/ffroliva/gflow-cli/releases/tag/v0.2.0a1
315
+ [0.1.0]: https://github.com/ffroliva/gflow-cli/releases/tag/v0.1.0
@@ -17,17 +17,22 @@
17
17
 
18
18
  ## Active phase
19
19
 
20
- **Phase 3Image MVP DONE (v0.3.0a1).** `gflow image upload/t2i/i2i` shipped.
21
- Tag at `ccce4d5`. Next phase TBD (Phase 4 Hardening — concurrency pool,
22
- retry/backoff, structlog, BDD).
20
+ **Phase 4Hardening DONE (v0.4.0a2).** Per-worker Page pool, tenacity
21
+ retry/backoff with reCAPTCHA re-mint, RFC 9457 Problem Details error
22
+ taxonomy with per-class exit codes 3–7, structlog observability with
23
+ `error_raised` / `error_unhandled` events, and 12 pytest-bdd scenarios all
24
+ shipped. Documentation polish landed in v0.4.0a2. Next phase TBD — likely
25
+ Phase 5 public alpha on PyPI followed by Phase 6 operations history
26
+ (see `PLAN.md`).
23
27
 
24
28
  ## Architecture (skim)
25
29
 
26
30
  > Note: the layered diagram below describes the **target** architecture
27
31
  > (deferred per [PLAN.md ADR #2](PLAN.md#5-decision-log-adrs-in-miniature)).
28
32
  > The current package layout is the simpler
29
- > `src/flow_cli/{api/, cli.py, cli_image.py, cli_video.py, auth.py,
30
- > config.py, paths.py, profile_store.py}`. See
33
+ > `src/gflow_cli/{api/, cli.py, cli_image.py, cli_video.py, _cli_helpers.py,
34
+ > auth.py, config.py, errors.py, observability.py, paths.py,
35
+ > profile_store.py}`. See
31
36
  > [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the full target shape.
32
37
 
33
38
  ```text
@@ -45,18 +50,18 @@ Dependency rule: **`domain/` depends on nothing**. `application/` depends on `do
45
50
  - **Never commit secrets.** `.gitignore` excludes `auth/`, `profile_*/`, `*.cookies.json`, `storage_state.json`, `secrets.json`, `.env`, `.env.local`, `.env.*.local`. If you see one of these staged, abort and tell the user.
46
51
  - **Never add `Co-Authored-By: Claude` (or any AI co-author) to commit messages.** Author attribution is the human user's only.
47
52
  - **Sessions belong outside the repo.** Default location is `$LOCALAPPDATA/gflow-cli/profile_*` (Windows) or `~/.local/share/gflow-cli/profile_*` (POSIX) via [`platformdirs`](https://github.com/platformdirs/platformdirs). Never store sessions in `/tmp`, `%TEMP%`, or anywhere the OS auto-reaps.
48
- - **No `print()` in `src/`** — use `structlog` (or `logging` until structlog lands in Phase 1).
49
- - **Domain layer is pure** — `src/flow_cli/domain/*` must not import `application/`, `infrastructure/`, or `interfaces/`. No I/O, no frameworks.
53
+ - **No raw `print()` and no `import logging` in `src/`** — use `structlog` for structured events, or Rich `console.print(...)` for user-facing output.
54
+ - **Domain layer is pure** — `src/gflow_cli/domain/*` must not import `application/`, `infrastructure/`, or `interfaces/`. No I/O, no frameworks.
50
55
  - **Frozen dataclasses for value objects.** Aggregates may have controlled mutation methods, but VOs (AspectRatio, Prompt, JobId, ...) are immutable.
51
56
  - **Async all the way down.** Handlers and providers are `async def`. CLI is the only place that calls `asyncio.run(...)`.
52
57
  - **TDD is non-negotiable.** Red → Green → Refactor → Commit. Coverage floor: **80% overall**, **90% on `domain/` and `application/`**. See [CONTRIBUTING.md](CONTRIBUTING.md).
53
58
 
54
59
  ## Coding conventions
55
60
 
56
- - **Python 3.11+**, strict typing (`pyright --strict` on `src/flow_cli/`), `from __future__ import annotations` at the top of every module.
61
+ - **Python 3.11+**, strict typing (`pyright --strict` on `src/gflow_cli/`), `from __future__ import annotations` at the top of every module.
57
62
  - **`@dataclass(frozen=True)`** for value objects and DTOs. `Protocol` for ports.
58
63
  - **`pathlib.Path`** everywhere — never raw strings for filesystem paths.
59
- - **Click + Rich** for the CLI, **httpx + Playwright** for I/O, **pytest + pytest-bdd** for tests.
64
+ - **Click + Rich** for the CLI, **Playwright `page.request`** as the HTTP transport (auto-attaches Google session cookies), **`tenacity`** for retry/backoff, **`structlog`** for structured logs, **`pytest + pytest-bdd`** for tests.
60
65
  - **Conventional Commits** for messages (`feat:`, `fix:`, `docs:`, `test:`, `chore:`, `refactor:`, etc.). See examples in `git log`.
61
66
  - **Short files, single responsibility.** ~200-400 lines typical, 800 max. Split into `package/<topic>.py` if growing.
62
67
 
@@ -68,7 +73,7 @@ Run all four BEFORE asking to commit:
68
73
  uv run ruff check src tests # lint
69
74
  uv run ruff format --check src tests # formatting
70
75
  uv run pyright src # types
71
- uv run pytest -q --cov=flow_cli # tests + coverage
76
+ uv run pytest -q --cov=gflow_cli # tests + coverage
72
77
  ```
73
78
 
74
79
  CI runs the same four on every push (see `.github/workflows/ci.yml`).
@@ -78,10 +83,10 @@ CI runs the same four on every push (see `.github/workflows/ci.yml`).
78
83
  | I need to… | Read this |
79
84
  |---|---|
80
85
  | Understand the architecture | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) |
81
- | Add a CLI command | [docs/USAGE.md](docs/USAGE.md) + existing patterns in `src/flow_cli/cli.py` |
82
- | Add an API route | [PLAN.md § 4 Phase status](PLAN.md#4-phase-status) + existing client in `src/flow_cli/api/client.py` + capture data under `samples/captured/` |
86
+ | Add a CLI command | [docs/USAGE.md](docs/USAGE.md) + existing patterns in `src/gflow_cli/cli.py` |
87
+ | Add an API route | [PLAN.md § 4 Phase status](PLAN.md#4-phase-status) + existing client in `src/gflow_cli/api/client.py` + capture data under `samples/captured/` |
83
88
  | Touch auth | [docs/AUTHENTICATION.md](docs/AUTHENTICATION.md) |
84
- | Add a config knob | [docs/CONFIGURATION.md](docs/CONFIGURATION.md) + `.env.template` + (later) `src/flow_cli/shared/config.py` |
89
+ | Add a config knob | [docs/CONFIGURATION.md](docs/CONFIGURATION.md) + `.env.template` + (later) `src/gflow_cli/shared/config.py` |
85
90
  | Write a test | [CONTRIBUTING.md § TDD](CONTRIBUTING.md#test-driven-development-mandatory) + existing patterns in `tests/` |
86
91
  | Cut a release | [README § Releases](README.md#releases) — bump version in `pyproject.toml`, update CHANGELOG, `git tag vX.Y.Z`, push |
87
92
  | Track a known issue | [KNOWN_ISSUES.md](KNOWN_ISSUES.md) |
@@ -104,7 +109,7 @@ uv run gflow auth login --profile experiments # named profile
104
109
  2. `uv run pytest tests/<area>/test_<thing>.py` — verify it fails for the right reason.
105
110
  3. Implement the minimum production code to pass.
106
111
  4. Refactor.
107
- 5. `uv run pytest -q --cov=flow_cli` — verify nothing else broke and coverage didn't regress.
112
+ 5. `uv run pytest -q --cov=gflow_cli` — verify nothing else broke and coverage didn't regress.
108
113
 
109
114
  ### Update a doc
110
115
 
@@ -1,6 +1,6 @@
1
1
  # Contributing to gflow-cli
2
2
 
3
- Thanks for considering a contribution! Pre-1.0 the repo is private and managed by [@ffroliva](https://github.com/ffroliva), but the workflow described here is what'll be opened up to the community at the v0.2 alpha milestone.
3
+ Thanks for considering a contribution! The repo is public and PRs are welcome. Pre-1.0 means APIs may shift between minor versions; check [PLAN.md](PLAN.md) for the active phase before starting a non-trivial change.
4
4
 
5
5
  ## Development setup
6
6
 
@@ -39,6 +39,8 @@ def test_parse_uuid_from_url(): ...
39
39
  async def test_upload_returns_asset(): ...
40
40
 
41
41
  @pytest.mark.live # Hits the real Flow API. Requires GFLOW_LIVE=1 env var.
42
+ # Note: the live-test gate is GFLOW_LIVE (deliberately short — not GFLOW_CLI_LIVE) so
43
+ # you can opt in with a single export. Runtime config still uses the GFLOW_CLI_* prefix.
42
44
  @pytest.mark.skipif(not os.getenv("GFLOW_LIVE"), reason="live tests opt-in")
43
45
  async def test_full_i2v_roundtrip(): ...
44
46
  ```
@@ -47,20 +49,20 @@ CI runs `unit` + `integration` on every push. `live` tests run only on the maint
47
49
 
48
50
  ### Coverage targets
49
51
 
50
- - **`src/flow_cli/cli.py`, `src/flow_cli/cli_image.py`, `src/flow_cli/cli_video.py`**: 70%+ (CLI plumbing — some Click branches are hard to unit-test)
51
- - **`src/flow_cli/api/`**: 90%+ (the meat — every captured route has a contract test)
52
- - **`src/flow_cli/auth.py`, `config.py`, `paths.py`, `profile_store.py`**: 80%+
52
+ - **`src/gflow_cli/cli.py`, `src/gflow_cli/cli_image.py`, `src/gflow_cli/cli_video.py`**: 70%+ (CLI plumbing — some Click branches are hard to unit-test)
53
+ - **`src/gflow_cli/api/`**: 90%+ (the meat — every captured route has a contract test)
54
+ - **`src/gflow_cli/auth.py`, `config.py`, `paths.py`, `profile_store.py`**: 80%+
53
55
  - **Overall**: 80%+
54
56
 
55
- `uv run pytest --cov=flow_cli --cov-fail-under=80` enforces the floor. Don't merge below it.
57
+ `uv run pytest --cov=gflow_cli --cov-fail-under=80` enforces the floor. Don't merge below it.
56
58
 
57
59
  ## Quality gates (run before commit)
58
60
 
59
61
  ```bash
60
62
  uv run ruff check src tests # lint
61
63
  uv run ruff format src tests # auto-format
62
- uv run pyright src # type-check (strict on src/flow_cli/)
63
- uv run pytest -q --cov=flow_cli # tests + coverage
64
+ uv run pyright src # type-check (strict on src/gflow_cli/)
65
+ uv run pytest -q --cov=gflow_cli # tests + coverage
64
66
  ```
65
67
 
66
68
  CI runs all four on every push. Local pre-commit hook recommended:
@@ -84,7 +86,7 @@ repos:
84
86
  result = await mock_client.new_route(...)
85
87
  assert result.some_field
86
88
  ```
87
- 3. **Implement** in `src/flow_cli/api/client.py` (and add helpers under `src/flow_cli/api/` as needed) until green.
89
+ 3. **Implement** in `src/gflow_cli/api/client.py` (and add helpers under `src/gflow_cli/api/` as needed) until green.
88
90
  4. **Add a `live` test** that runs the real flow end-to-end (skipped in CI by default).
89
91
  5. **Update `CHANGELOG.md`** under `[Unreleased] → Added`.
90
92
  6. **Document** the route in the README's Architecture section if it's a new capability.
@@ -13,7 +13,7 @@ This tool calls Google's private REST API at `aisandbox-pa.googleapis.com`. That
13
13
  - **Subject to access controls.** Google may rate-limit, throttle, restrict, or revoke access to this surface for any account at any time.
14
14
  - **Not covered by any Google SLA.** When this surface goes down, you have no support recourse.
15
15
 
16
- If you need a stable, supported, contractual API, use the [official Google Gen AI SDK](https://github.com/googleapis/python-genai) and the public Veo API on `generativelanguage.googleapis.com`. `gflow-cli` plans to support that path as a `--provider official` option in v0.3+.
16
+ If you need a stable, supported, contractual API, use the [official Google Gen AI SDK](https://github.com/googleapis/python-genai) and the public Veo API on `generativelanguage.googleapis.com`. `gflow-cli` may support that path as a `GFLOW_CLI_PROVIDER=official` option in a future release (planned v0.5+); it is **not** part of v0.4.0a2.
17
17
 
18
18
  ## Account responsibility
19
19
 
@@ -69,4 +69,4 @@ See [LICENSE](LICENSE) for the full legal text. By installing or using `gflow-cl
69
69
 
70
70
  ---
71
71
 
72
- _Last updated: 2026-05-09._
72
+ _Last updated: 2026-05-11 — refreshed for v0.4.0a2._
@@ -35,7 +35,7 @@ Re-running `auth login` reuses the existing profile dir (you typically just clic
35
35
 
36
36
  **Why we don't auto-refresh:** Google's session-refresh flow can include CAPTCHA / device verification that only a human can complete. A community SDK can't reliably automate that step. See [docs/AUTHENTICATION.md § Refresh / expiry](docs/AUTHENTICATION.md#refresh--expiry).
37
37
 
38
- **Roadmap:** v0.4 will add a periodic background "session liveness" check that warns ~24h before expected expiry, and a `gflow auth refresh` command that opens the browser only if needed.
38
+ **Roadmap:** not scheduled. The Phase 4 hardening pass (v0.4.0a2) added typed `AuthExpiredError` + exit code `3` so scripts can branch on auth expiry deterministically. A periodic "session liveness" check + a `gflow auth refresh` command are still candidates for a later phase, but not committed to a version yet.
39
39
 
40
40
  ---
41
41
 
@@ -49,13 +49,13 @@ Chromium refuses to open two persistent contexts on the same `user-data-dir` sim
49
49
 
50
50
  ```bash
51
51
  # Terminal 1
52
- gflow image batch ./batch-a.tsv --profile work
52
+ gflow video batch ./batch-a.tsv --profile work
53
53
 
54
54
  # Terminal 2 — different profile, same time, OK
55
- gflow image batch ./batch-b.tsv --profile personal
55
+ gflow video batch ./batch-b.tsv --profile personal
56
56
  ```
57
57
 
58
- **Roadmap:** v0.4's per-account pool will queue same-profile calls automatically.
58
+ **Roadmap:** Phase 4 (v0.4.0a2) added concurrency *inside* one `gflow video batch` process via `GFLOW_CLI_CONCURRENCY=N` (per-worker Page pool on one shared BrowserContext). Cross-process same-profile serialization is a Chromium constraint we cannot work around without rewriting the auth model — multiple shells against the same profile remains a "use different profiles" workaround.
59
59
 
60
60
  ---
61
61
 
@@ -93,11 +93,29 @@ Other ratios may be silently rejected or coerced server-side. We validate in the
93
93
 
94
94
  ---
95
95
 
96
+ ### `gflow video batch` does not skip already-completed entries
97
+
98
+ - **Status:** Open · **Severity:** Medium · **Affects:** v0.2.0a1+
99
+
100
+ If a `gflow video batch` run dies partway through (auth expiry, network blip, Ctrl-C) and you rerun the same TSV manifest, every row is re-submitted to Flow. Flow's private API does not expose a "have I generated this before?" predicate, and `gflow-cli` does not yet maintain a local manifest-of-outputs to compare against.
101
+
102
+ **Cost implication:** re-running a partially completed manifest **may consume additional Veo / Imagen credits**. We cannot guarantee that Flow de-duplicates server-side — credit accounting on a private API is not contractual.
103
+
104
+ **Workaround:** before rerunning, trim the manifest down to the rows whose `output_path` does not yet exist on disk:
105
+ ```bash
106
+ awk -F'\t' 'NR==1 || (system("test -e " $NF) != 0)' manifest.tsv > manifest.remaining.tsv
107
+ gflow video batch manifest.remaining.tsv
108
+ ```
109
+
110
+ **Roadmap:** under consideration for Phase 6 (operations history with a local SQLite ledger — see `PLAN.md`).
111
+
112
+ ---
113
+
96
114
  ### Output dir is not tidied automatically
97
115
 
98
116
  - **Status:** Open · **Severity:** Low · **By design**
99
117
 
100
- `gflow-cli` never deletes from `$FLOW_CLI_OUTPUT_DIR`. Generated assets accumulate forever unless you clean them up.
118
+ `gflow-cli` never deletes from `$GFLOW_CLI_OUTPUT_DIR`. Generated assets accumulate forever unless you clean them up.
101
119
 
102
120
  **Workaround:** schedule a cron / Task Scheduler job, e.g.:
103
121
  ```bash
@@ -119,7 +137,7 @@ _(none yet)_
119
137
 
120
138
  - **Status:** Resolved · **Severity:** Critical (blocked usage) · **Fixed in:** v0.2.0a1
121
139
 
122
- The v0.1 scaffold left `upload_image`, `start_generation`, `get_job`, `download` raising `NotImplementedError`. v0.2.0a1 wired the video routes (T2V/I2V/batch) on a new `flow_cli.api.client.FlowApiClient` and removed the legacy `providers/` + `models` modules. v0.3.0a1 added the image routes (`gflow image upload/t2i/i2i`) on the same client.
140
+ The v0.1 scaffold left `upload_image`, `start_generation`, `get_job`, `download` raising `NotImplementedError`. v0.2.0a1 wired the video routes (T2V/I2V/batch) on a new `gflow_cli.api.client.FlowApiClient` and removed the legacy `providers/` + `models` modules. v0.3.0a1 added the image routes (`gflow image upload/t2i/i2i`) on the same client.
123
141
 
124
142
  ---
125
143