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.
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/.claude/commands/release.md +1 -1
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/.env.template +22 -14
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/.gitignore +3 -0
- gflow_cli-0.4.0a2/CHANGELOG.md +315 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/CLAUDE.md +19 -14
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/CONTRIBUTING.md +10 -8
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/DISCLAIMER.md +2 -2
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/KNOWN_ISSUES.md +24 -6
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/PKG-INFO +38 -29
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/PLAN.md +93 -35
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/README.md +36 -28
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/ARCHITECTURE.md +49 -19
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/AUTHENTICATION.md +12 -12
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/CONFIGURATION.md +35 -33
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/INDEX.md +12 -2
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/SECURITY.md +13 -11
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/USAGE.md +58 -24
- gflow_cli-0.4.0a2/docs/USER_GUIDE.md +625 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/superpowers/plans/2026-05-09-image-mvp-orchestration.md +1 -1
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/superpowers/plans/2026-05-09-image-mvp.md +18 -18
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/superpowers/plans/2026-05-09-video-mvp-orchestration.md +2 -2
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/docs/superpowers/plans/2026-05-09-video-mvp.md +112 -112
- gflow_cli-0.4.0a2/docs/superpowers/plans/2026-05-10-phase-4-hardening-orchestration.md +358 -0
- gflow_cli-0.4.0a2/docs/superpowers/plans/2026-05-10-phase-4-hardening.md +2736 -0
- gflow_cli-0.4.0a2/docs/superpowers/specs/2026-05-10-phase-4-hardening-design.md +484 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/pyproject.toml +7 -5
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/README.md +1 -1
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/scripts/smoke_e2e.py +5 -5
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/scripts/smoke_image.py +5 -5
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/skills/gflow-cli/SKILL.md +3 -3
- {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/__init__.py +1 -1
- gflow_cli-0.4.0a2/src/gflow_cli/__main__.py +6 -0
- gflow_cli-0.4.0a2/src/gflow_cli/_cli_helpers.py +185 -0
- {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/__init__.py +3 -3
- gflow_cli-0.4.0a2/src/gflow_cli/api/_retry.py +138 -0
- gflow_cli-0.4.0a2/src/gflow_cli/api/client.py +897 -0
- {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/recaptcha.py +2 -2
- {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/auth.py +11 -7
- {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/cli.py +29 -9
- {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/cli_image.py +25 -49
- {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/cli_video.py +77 -59
- {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/config.py +40 -6
- gflow_cli-0.4.0a2/src/gflow_cli/errors.py +216 -0
- {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/manifest.py +1 -1
- gflow_cli-0.4.0a2/src/gflow_cli/observability.py +160 -0
- {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/paths.py +4 -4
- {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/profile_store.py +7 -7
- gflow_cli-0.4.0a2/tasks/lessons.md +195 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_client.py +1 -1
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_client_generate_video.py +44 -28
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_client_image.py +304 -85
- gflow_cli-0.4.0a2/tests/api/test_concurrency.py +199 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_dto.py +1 -1
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_image.py +2 -2
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_image_dto.py +1 -1
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_recaptcha.py +1 -1
- gflow_cli-0.4.0a2/tests/api/test_retry.py +243 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_routes.py +1 -1
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/test_video.py +1 -1
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/cli/test_cli_image.py +40 -40
- gflow_cli-0.4.0a2/tests/cli/test_error_handling.py +323 -0
- gflow_cli-0.4.0a2/tests/cli/test_helpers.py +158 -0
- gflow_cli-0.4.0a2/tests/conftest.py +38 -0
- gflow_cli-0.4.0a2/tests/features/__init__.py +0 -0
- gflow_cli-0.4.0a2/tests/features/auth.feature +28 -0
- gflow_cli-0.4.0a2/tests/features/conftest.py +89 -0
- gflow_cli-0.4.0a2/tests/features/image.feature +25 -0
- gflow_cli-0.4.0a2/tests/features/test_auth_steps.py +186 -0
- gflow_cli-0.4.0a2/tests/features/test_image_steps.py +191 -0
- gflow_cli-0.4.0a2/tests/features/test_step_collision_guard.py +16 -0
- gflow_cli-0.4.0a2/tests/features/test_video_steps.py +253 -0
- gflow_cli-0.4.0a2/tests/features/video.feature +27 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/test_auth.py +6 -6
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/test_cli_video.py +83 -13
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/test_config.py +70 -13
- gflow_cli-0.4.0a2/tests/test_errors.py +276 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/test_manifest.py +2 -2
- gflow_cli-0.4.0a2/tests/test_observability.py +165 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/test_paths.py +1 -1
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/test_profile_store.py +7 -7
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/test_smoke.py +7 -7
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/uv.lock +161 -2
- gflow_cli-0.3.0a1/CHANGELOG.md +0 -127
- gflow_cli-0.3.0a1/src/flow_cli/__main__.py +0 -6
- gflow_cli-0.3.0a1/src/flow_cli/api/client.py +0 -567
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/.claude/README.md +0 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/.gitattributes +0 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/.github/workflows/ci.yml +0 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/.github/workflows/release.yml +0 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/LICENSE +0 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/captured/01_upload_image.json +0 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/captured/02_batchAsyncGenerateVideoText.json +0 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/captured/03_batchCheckAsyncVideoGenerationStatus.json +0 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/captured/04_archive_workflow.json +0 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/captured/05_createProject.json +0 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/captured/06_batchGenerateImages.json +0 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/samples/captured/07_batchGenerateImages_seeded.json +0 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/skills/README.md +0 -0
- {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/dto.py +0 -0
- {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/image.py +0 -0
- {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/routes.py +0 -0
- {gflow_cli-0.3.0a1/src/flow_cli → gflow_cli-0.4.0a2/src/gflow_cli}/api/video.py +0 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/__init__.py +0 -0
- {gflow_cli-0.3.0a1 → gflow_cli-0.4.0a2}/tests/api/__init__.py +0 -0
- {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=
|
|
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`
|
|
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
|
-
#
|
|
17
|
+
# GFLOW_CLI_HOME=
|
|
15
18
|
|
|
16
|
-
# Default profile name.
|
|
17
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
45
|
+
# GFLOW_CLI_PROVIDER=flow
|
|
41
46
|
|
|
42
|
-
# Gemini API key — only required when
|
|
47
|
+
# Gemini API key — only required when GFLOW_CLI_PROVIDER=official.
|
|
43
48
|
# Get one at https://aistudio.google.com/apikey
|
|
44
|
-
#
|
|
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
|
-
|
|
56
|
+
# GFLOW_CLI_TIMEOUT_SECONDS=600
|
|
52
57
|
|
|
53
58
|
# Logging level — DEBUG | INFO | WARNING | ERROR
|
|
54
|
-
|
|
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
|
-
|
|
63
|
+
# GFLOW_CLI_LOG_FORMAT=auto
|
|
59
64
|
|
|
60
|
-
#
|
|
61
|
-
|
|
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
|
-
|
|
75
|
+
# GFLOW_CLI_HEADLESS=true
|
|
@@ -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
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
**Phase 4 — Hardening 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/
|
|
30
|
-
> config.py,
|
|
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`
|
|
49
|
-
- **Domain layer is pure** — `src/
|
|
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/
|
|
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, **
|
|
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=
|
|
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/
|
|
82
|
-
| Add an API route | [PLAN.md § 4 Phase status](PLAN.md#4-phase-status) + existing client in `src/
|
|
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/
|
|
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=
|
|
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!
|
|
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/
|
|
51
|
-
- **`src/
|
|
52
|
-
- **`src/
|
|
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=
|
|
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/
|
|
63
|
-
uv run pytest -q --cov=
|
|
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/
|
|
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`
|
|
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-
|
|
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
|
|
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
|
|
52
|
+
gflow video batch ./batch-a.tsv --profile work
|
|
53
53
|
|
|
54
54
|
# Terminal 2 — different profile, same time, OK
|
|
55
|
-
gflow
|
|
55
|
+
gflow video batch ./batch-b.tsv --profile personal
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
**Roadmap:** v0.4
|
|
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 `$
|
|
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 `
|
|
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
|
|