@leejungkiin/awkit 1.3.8 → 1.4.2

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 (135) hide show
  1. package/bin/awk.js +630 -52
  2. package/bin/claude-generators.js +122 -0
  3. package/core/AGENTS.md +54 -0
  4. package/core/CLAUDE.md +155 -0
  5. package/core/GEMINI.md +44 -9
  6. package/core/GEMINI.md.bak +126 -199
  7. package/package.json +1 -1
  8. package/skills/ai-sprite-maker/SKILL.md +81 -0
  9. package/skills/ai-sprite-maker/scripts/animate_sprite.py +102 -0
  10. package/skills/ai-sprite-maker/scripts/process_sprites.py +140 -0
  11. package/skills/awf-session-restore/SKILL.md +12 -2
  12. package/skills/brainstorm-agent/SKILL.md +11 -8
  13. package/skills/code-review/SKILL.md +21 -33
  14. package/skills/gitnexus/gitnexus-cli/SKILL.md +82 -0
  15. package/skills/gitnexus/gitnexus-debugging/SKILL.md +89 -0
  16. package/skills/gitnexus/gitnexus-exploring/SKILL.md +78 -0
  17. package/skills/gitnexus/gitnexus-guide/SKILL.md +64 -0
  18. package/skills/gitnexus/gitnexus-impact-analysis/SKILL.md +97 -0
  19. package/skills/gitnexus/gitnexus-refactoring/SKILL.md +121 -0
  20. package/skills/lucylab-tts/SKILL.md +64 -0
  21. package/skills/lucylab-tts/resources/voices_library.json +908 -0
  22. package/skills/lucylab-tts/scripts/.env +1 -0
  23. package/skills/lucylab-tts/scripts/lucylab_tts.py +506 -0
  24. package/skills/nm-memory-sync/SKILL.md +14 -1
  25. package/skills/orchestrator/SKILL.md +5 -38
  26. package/skills/ship-to-code/SKILL.md +115 -0
  27. package/skills/short-maker/SKILL.md +150 -0
  28. package/skills/short-maker/_backup/storyboard.html +106 -0
  29. package/skills/short-maker/_backup/video_mixer.py +296 -0
  30. package/skills/short-maker/outputs/fitbite-promo/background.jpg +0 -0
  31. package/skills/short-maker/outputs/fitbite-promo/final/promo-final.mp4 +0 -0
  32. package/skills/short-maker/outputs/fitbite-promo/script.md +19 -0
  33. package/skills/short-maker/outputs/fitbite-promo/segments/scene-01.mp4 +0 -0
  34. package/skills/short-maker/outputs/fitbite-promo/segments/scene-02.mp4 +0 -0
  35. package/skills/short-maker/outputs/fitbite-promo/segments/scene-03.mp4 +0 -0
  36. package/skills/short-maker/outputs/fitbite-promo/segments/scene-04.mp4 +0 -0
  37. package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-01.png +0 -0
  38. package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-02.png +0 -0
  39. package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-03.png +0 -0
  40. package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-04.png +0 -0
  41. package/skills/short-maker/outputs/fitbite-promo/storyboard.html +133 -0
  42. package/skills/short-maker/outputs/fitbite-promo/storyboard.json +38 -0
  43. package/skills/short-maker/outputs/fitbite-promo/temp/merged_chroma.mp4 +0 -0
  44. package/skills/short-maker/outputs/fitbite-promo/temp/merged_crossfaded.mp4 +0 -0
  45. package/skills/short-maker/outputs/fitbite-promo/temp/ready_00.mp4 +0 -0
  46. package/skills/short-maker/outputs/fitbite-promo/temp/ready_01.mp4 +0 -0
  47. package/skills/short-maker/outputs/fitbite-promo/temp/ready_02.mp4 +0 -0
  48. package/skills/short-maker/outputs/fitbite-promo/temp/ready_03.mp4 +0 -0
  49. package/skills/short-maker/outputs/fitbite-promo/tts/manifest.json +31 -0
  50. package/skills/short-maker/outputs/fitbite-promo/tts/scene-01.wav +0 -0
  51. package/skills/short-maker/outputs/fitbite-promo/tts/scene-02.wav +0 -0
  52. package/skills/short-maker/outputs/fitbite-promo/tts/scene-03.wav +0 -0
  53. package/skills/short-maker/outputs/fitbite-promo/tts/scene-04.wav +0 -0
  54. package/skills/short-maker/outputs/fitbite-promo/tts_script.txt +11 -0
  55. package/skills/short-maker/scripts/google-flow-cli/.project-identity +41 -0
  56. package/skills/short-maker/scripts/google-flow-cli/.trae/rules/project_rules.md +52 -0
  57. package/skills/short-maker/scripts/google-flow-cli/CODEBASE.md +67 -0
  58. package/skills/short-maker/scripts/google-flow-cli/GoogleFlowCli.code-workspace +29 -0
  59. package/skills/short-maker/scripts/google-flow-cli/README.md +168 -0
  60. package/skills/short-maker/scripts/google-flow-cli/docs/specs/PROJECT.md +12 -0
  61. package/skills/short-maker/scripts/google-flow-cli/docs/specs/REQUIREMENTS.md +22 -0
  62. package/skills/short-maker/scripts/google-flow-cli/docs/specs/ROADMAP.md +16 -0
  63. package/skills/short-maker/scripts/google-flow-cli/docs/specs/TECH-SPEC.md +13 -0
  64. package/skills/short-maker/scripts/google-flow-cli/gflow/__init__.py +3 -0
  65. package/skills/short-maker/scripts/google-flow-cli/gflow/api/__init__.py +19 -0
  66. package/skills/short-maker/scripts/google-flow-cli/gflow/api/client.py +1921 -0
  67. package/skills/short-maker/scripts/google-flow-cli/gflow/api/models.py +64 -0
  68. package/skills/short-maker/scripts/google-flow-cli/gflow/api/rpc_ids.py +98 -0
  69. package/skills/short-maker/scripts/google-flow-cli/gflow/auth/__init__.py +15 -0
  70. package/skills/short-maker/scripts/google-flow-cli/gflow/auth/browser_auth.py +692 -0
  71. package/skills/short-maker/scripts/google-flow-cli/gflow/auth/humanizer.py +417 -0
  72. package/skills/short-maker/scripts/google-flow-cli/gflow/auth/proxy_ext.py +120 -0
  73. package/skills/short-maker/scripts/google-flow-cli/gflow/auth/recaptcha.py +482 -0
  74. package/skills/short-maker/scripts/google-flow-cli/gflow/batchexecute/__init__.py +5 -0
  75. package/skills/short-maker/scripts/google-flow-cli/gflow/batchexecute/client.py +414 -0
  76. package/skills/short-maker/scripts/google-flow-cli/gflow/cli/__init__.py +1 -0
  77. package/skills/short-maker/scripts/google-flow-cli/gflow/cli/main.py +1075 -0
  78. package/skills/short-maker/scripts/google-flow-cli/pyproject.toml +36 -0
  79. package/skills/short-maker/scripts/google-flow-cli/script.txt +22 -0
  80. package/skills/short-maker/scripts/google-flow-cli/tests/__init__.py +0 -0
  81. package/skills/short-maker/scripts/google-flow-cli/tests/test_batchexecute.py +113 -0
  82. package/skills/short-maker/scripts/google-flow-cli/tests/test_client.py +190 -0
  83. package/skills/short-maker/templates/aida_script.md +40 -0
  84. package/skills/short-maker/templates/mimic_analyzer.md +29 -0
  85. package/skills/single-flow-task-execution/SKILL.md +412 -0
  86. package/skills/single-flow-task-execution/code-quality-reviewer-prompt.md +20 -0
  87. package/skills/single-flow-task-execution/implementer-prompt.md +78 -0
  88. package/skills/single-flow-task-execution/spec-reviewer-prompt.md +61 -0
  89. package/skills/skill-creator/SKILL.md +44 -0
  90. package/skills/spm-build-analysis/SKILL.md +92 -0
  91. package/skills/spm-build-analysis/references/build-optimization-sources.md +155 -0
  92. package/skills/spm-build-analysis/references/recommendation-format.md +85 -0
  93. package/skills/spm-build-analysis/references/spm-analysis-checks.md +105 -0
  94. package/skills/spm-build-analysis/scripts/check_spm_pins.py +118 -0
  95. package/skills/symphony-enforcer/SKILL.md +83 -97
  96. package/skills/symphony-orchestrator/SKILL.md +1 -1
  97. package/skills/trello-sync/SKILL.md +52 -45
  98. package/skills/verification-gate/SKILL.md +13 -2
  99. package/skills/xcode-build-benchmark/SKILL.md +88 -0
  100. package/skills/xcode-build-benchmark/references/benchmark-artifacts.md +94 -0
  101. package/skills/xcode-build-benchmark/references/benchmarking-workflow.md +67 -0
  102. package/skills/xcode-build-benchmark/schemas/build-benchmark.schema.json +230 -0
  103. package/skills/xcode-build-benchmark/scripts/benchmark_builds.py +308 -0
  104. package/skills/xcode-build-fixer/SKILL.md +218 -0
  105. package/skills/xcode-build-fixer/references/build-settings-best-practices.md +216 -0
  106. package/skills/xcode-build-fixer/references/fix-patterns.md +290 -0
  107. package/skills/xcode-build-fixer/references/recommendation-format.md +85 -0
  108. package/skills/xcode-build-fixer/scripts/benchmark_builds.py +308 -0
  109. package/skills/xcode-build-orchestrator/SKILL.md +156 -0
  110. package/skills/xcode-build-orchestrator/references/benchmark-artifacts.md +94 -0
  111. package/skills/xcode-build-orchestrator/references/build-settings-best-practices.md +216 -0
  112. package/skills/xcode-build-orchestrator/references/orchestration-report-template.md +143 -0
  113. package/skills/xcode-build-orchestrator/references/recommendation-format.md +85 -0
  114. package/skills/xcode-build-orchestrator/scripts/benchmark_builds.py +308 -0
  115. package/skills/xcode-build-orchestrator/scripts/diagnose_compilation.py +273 -0
  116. package/skills/xcode-build-orchestrator/scripts/generate_optimization_report.py +533 -0
  117. package/skills/xcode-compilation-analyzer/SKILL.md +89 -0
  118. package/skills/xcode-compilation-analyzer/references/build-optimization-sources.md +155 -0
  119. package/skills/xcode-compilation-analyzer/references/code-compilation-checks.md +106 -0
  120. package/skills/xcode-compilation-analyzer/references/recommendation-format.md +85 -0
  121. package/skills/xcode-compilation-analyzer/scripts/diagnose_compilation.py +273 -0
  122. package/skills/xcode-project-analyzer/SKILL.md +76 -0
  123. package/skills/xcode-project-analyzer/references/build-optimization-sources.md +155 -0
  124. package/skills/xcode-project-analyzer/references/build-settings-best-practices.md +216 -0
  125. package/skills/xcode-project-analyzer/references/project-audit-checks.md +101 -0
  126. package/skills/xcode-project-analyzer/references/recommendation-format.md +85 -0
  127. package/templates/CODEBASE.md +26 -42
  128. package/templates/configs/trello-config.json +2 -2
  129. package/templates/workflow_dual_mode_template.md +5 -5
  130. package/workflows/_uncategorized/conductor-codex.md +125 -0
  131. package/workflows/_uncategorized/conductor.md +97 -0
  132. package/workflows/_uncategorized/ship-to-code.md +85 -0
  133. package/workflows/_uncategorized/trello-sync.md +52 -0
  134. package/workflows/context/codebase-sync.md +10 -87
  135. package/workflows/quality/visual-debug.md +66 -12
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "gflow"
7
+ version = "0.1.0"
8
+ description = "A command-line interface to Google Flow (AI image & video generation)"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.10"
12
+ dependencies = [
13
+ "requests>=2.31",
14
+ "click>=8.1",
15
+ "rich>=13.0",
16
+ "pydantic>=2.0",
17
+ "selenium>=4.15",
18
+ "webdriver-manager>=4.0",
19
+ "websocket-client>=1.6",
20
+ ]
21
+
22
+ [project.optional-dependencies]
23
+ dev = [
24
+ "pytest>=7.0",
25
+ "pytest-cov",
26
+ "ruff",
27
+ ]
28
+
29
+ [project.scripts]
30
+ gflow = "gflow.cli.main:cli"
31
+
32
+ [tool.setuptools.packages.find]
33
+ include = ["gflow*"]
34
+
35
+ [tool.ruff]
36
+ line-length = 120
@@ -0,0 +1,22 @@
1
+
2
+ TITLE: Nếu cả đời này không rực rỡ thì sao?
3
+
4
+ --- SCRIPT (VOICEOVER) ---
5
+ HOOK: Gần đây, mình hay thấy một câu hỏi trên mạng: Nếu cả đời này mình không rực rỡ thì sao nhỉ?
6
+ PROBLEM: Chúng mình ở tuổi hai mươi, ba mươi, hay bị áp lực phải thành công, phải lấp lánh như người ta. Nhiều lúc nhìn lại, tự dưng thấy chạnh lòng một chút vì mình cứ mãi nhạt nhòa, thế này thì chán quá.
7
+ SOLUTION: Nhưng bạn xem nào, nhà Phật có câu 'Bình thường tâm thị đạo'. Tâm trí bình lặng mới là đáng quý nhất. Không rực rỡ như mặt trời thì mình làm một chiếc lá xanh tươi, sống trọn vẹn ở hiện tại, thế cũng đẹp mà, thật đấy.
8
+ CALL TO ACTION: Thả lỏng vai ra một chút nhé. Cứ bình thường thôi, miễn là tâm mình an. Hôm nay bạn có niềm vui nhỏ nào không, kể mình nghe với nhé!
9
+
10
+ --- VISUAL PROMPTS ---
11
+ SCENE 1: Một nữ người Việt Nam, độ tuổi thanh niên (20-30 tuổi), phong cách trang phục áo tràng tu sĩ màu nâu sòng, đầu cạo trọc, phong cách giản dị và thanh khiết với nụ cười nhẹ nhàng., bối cảnh là một phòng đọc sách truyền thống với kệ sách cao chứa nhiều kinh sách, một bức thư pháp treo tường. phía trước là bàn gỗ có bộ ấm trà, nhang đang tỏa khói và các quyển sổ ghi chép. ánh sáng vàng ấm áp từ đèn bàn tạo không gian thanh tịnh và cổ điển., nói giọng miền bắc (hà nội), tông giọng nhẹ nhàng, thấu cảm: 'Gần đây, mình hay thấy một câu hỏi trên mạng: Nếu cả đời này mình không rực rỡ thì sao nhỉ?'. Sư cô hơi nghiêng đầu, ánh mắt nhìn thẳng vào ống kính đầy thấu cảm, khẽ mỉm cười, khói nhang bay nhẹ phía trước. (Giới hạn: 8 giây)
12
+
13
+ SCENE 2: Một nữ người Việt Nam, độ tuổi thanh niên (20-30 tuổi), phong cách trang phục áo tràng tu sĩ màu nâu sòng, đầu cạo trọc, phong cách giản dị và thanh khiết với nụ cười nhẹ nhàng., bối cảnh là một phòng đọc sách truyền thống với kệ sách cao chứa nhiều kinh sách, một bức thư pháp treo tường. phía trước là bàn gỗ có bộ ấm trà, nhang đang tỏa khói và các quyển sổ ghi chép. ánh sáng vàng ấm áp từ đèn bàn tạo không gian thanh tịnh và cổ điển., nói giọng miền bắc (hà nội), tông giọng nhẹ nhàng, thấu cảm: 'Chúng mình ở tuổi hai mươi, ba mươi, hay bị áp lực phải thành công, phải lấp lánh như người ta.'. Sư cô khẽ cúi xuống rót một chén trà nhỏ, động tác khoan thai, rồi ngước lên nhìn người xem. (Giới hạn: 8 giây)
14
+
15
+ SCENE 3: Một nữ người Việt Nam, độ tuổi thanh niên (20-30 tuổi), phong cách trang phục áo tràng tu sĩ màu nâu sòng, đầu cạo trọc, phong cách giản dị và thanh khiết với nụ cười nhẹ nhàng., bối cảnh là một phòng đọc sách truyền thống với kệ sách cao chứa nhiều kinh sách, một bức thư pháp treo tường. phía trước là bàn gỗ có bộ ấm trà, nhang đang tỏa khói và các quyển sổ ghi chép. ánh sáng vàng ấm áp từ đèn bàn tạo không gian thanh tịnh và cổ điển., nói giọng miền bắc (hà nội), tông giọng nhẹ nhàng, thấu cảm: 'Nhiều lúc nhìn lại, tự dưng thấy chạnh lòng một chút vì mình cứ mãi nhạt nhòa, thế này thì chán quá.'. Đôi mắt sư cô chùng xuống một chút, thể hiện sự đồng cảm sâu sắc, hai tay úp nhẹ lên cuốn sổ ghi chép. (Giới hạn: 8 giây)
16
+
17
+ SCENE 4: Một nữ người Việt Nam, độ tuổi thanh niên (20-30 tuổi), phong cách trang phục áo tràng tu sĩ màu nâu sòng, đầu cạo trọc, phong cách giản dị và thanh khiết với nụ cười nhẹ nhàng., bối cảnh là một phòng đọc sách truyền thống với kệ sách cao chứa nhiều kinh sách, một bức thư pháp treo tường. phía trước là bàn gỗ có bộ ấm trà, nhang đang tỏa khói và các quyển sổ ghi chép. ánh sáng vàng ấm áp từ đèn bàn tạo không gian thanh tịnh và cổ điển., nói giọng miền bắc (hà nội), tông giọng nhẹ nhàng, thấu cảm: 'Nhưng bạn xem nào, nhà Phật có câu Bình thường tâm thị đạo. Tâm trí bình lặng mới là đáng quý nhất.'. Sư cô mỉm cười rạng rỡ hơn, ánh mắt ánh lên sự bình an, đưa một tay lên làm cử chỉ xoa dịu. (Giới hạn: 8 giây)
18
+
19
+ SCENE 5: Một nữ người Việt Nam, độ tuổi thanh niên (20-30 tuổi), phong cách trang phục áo tràng tu sĩ màu nâu sòng, đầu cạo trọc, phong cách giản dị và thanh khiết với nụ cười nhẹ nhàng., bối cảnh là một phòng đọc sách truyền thống với kệ sách cao chứa nhiều kinh sách, một bức thư pháp treo tường. phía trước là bàn gỗ có bộ ấm trà, nhang đang tỏa khói và các quyển sổ ghi chép. ánh sáng vàng ấm áp từ đèn bàn tạo không gian thanh tịnh và cổ điển., nói giọng miền bắc (hà nội), tông giọng nhẹ nhàng, thấu cảm: 'Không rực rỡ như mặt trời thì mình làm một chiếc lá xanh tươi, sống trọn vẹn ở hiện tại, thế cũng đẹp mà, thật đấy.'. Sư cô nâng chén trà lên ngang ngực, hơi nghiêng người về phía trước tạo sự gần gũi. (Giới hạn: 8 giây)
20
+
21
+ SCENE 6: Một nữ người Việt Nam, độ tuổi thanh niên (20-30 tuổi), phong cách trang phục áo tràng tu sĩ màu nâu sòng, đầu cạo trọc, phong cách giản dị và thanh khiết với nụ cười nhẹ nhàng., bối cảnh là một phòng đọc sách truyền thống với kệ sách cao chứa nhiều kinh sách, một bức thư pháp treo tường. phía trước là bàn gỗ có bộ ấm trà, nhang đang tỏa khói và các quyển sổ ghi chép. ánh sáng vàng ấm áp từ đèn bàn tạo không gian thanh tịnh và cổ điển., nói giọng miền bắc (hà nội), tông giọng nhẹ nhàng, thấu cảm: 'Thả lỏng vai ra một chút nhé. Cứ bình thường thôi, miễn là tâm mình an. Hôm nay bạn có niềm vui nhỏ nào không, kể mình nghe với nhé!'. Sư cô chắp tay hình búp sen, nở nụ cười tươi tắn chào tạm biệt, ánh sáng vàng hắt lên khuôn mặt thanh tú. (Giới hạn: 8 giây)
22
+
@@ -0,0 +1,113 @@
1
+ """Tests for the BatchExecute protocol client."""
2
+
3
+ import json
4
+ import pytest
5
+
6
+ from gflow.batchexecute.client import (
7
+ BatchExecuteClient,
8
+ RPC,
9
+ _unwrap_json,
10
+ _extract_sapisid,
11
+ _generate_sapisidhash,
12
+ ReqIDGenerator,
13
+ )
14
+
15
+
16
+ class TestReqIDGenerator:
17
+ def test_generates_incrementing_ids(self):
18
+ gen = ReqIDGenerator()
19
+ id1 = int(gen.next())
20
+ id2 = int(gen.next())
21
+ assert id2 > id1
22
+ assert id2 - id1 == 100_000
23
+
24
+ def test_base_is_in_expected_range(self):
25
+ gen = ReqIDGenerator()
26
+ val = int(gen.next())
27
+ assert 1_500_000_000 <= val <= 1_700_000_000
28
+
29
+
30
+ class TestExtractSAPISID:
31
+ def test_extracts_sapisid(self):
32
+ cookies = "SID=abc; HSID=def; SAPISID=my_sapisid_value; SSID=ghi"
33
+ assert _extract_sapisid(cookies) == "my_sapisid_value"
34
+
35
+ def test_returns_none_when_missing(self):
36
+ cookies = "SID=abc; HSID=def"
37
+ assert _extract_sapisid(cookies) is None
38
+
39
+ def test_empty_cookies(self):
40
+ assert _extract_sapisid("") is None
41
+
42
+
43
+ class TestGenerateSAPISIDHASH:
44
+ def test_format(self):
45
+ result = _generate_sapisidhash("test_sapisid", "https://flow.google")
46
+ assert result.startswith("SAPISIDHASH ")
47
+ parts = result.split(" ")[1].split("_")
48
+ assert len(parts) == 2
49
+ # First part is timestamp, second is hex hash
50
+ assert parts[0].isdigit()
51
+ assert all(c in "0123456789abcdef" for c in parts[1])
52
+
53
+
54
+ class TestUnwrapJSON:
55
+ def test_plain_string(self):
56
+ assert _unwrap_json("hello") == "hello"
57
+
58
+ def test_json_array(self):
59
+ result = _unwrap_json('[1, 2, 3]')
60
+ assert result == [1, 2, 3]
61
+
62
+ def test_json_object(self):
63
+ result = _unwrap_json('{"key": "value"}')
64
+ assert result == {"key": "value"}
65
+
66
+ def test_double_encoded(self):
67
+ inner = json.dumps([1, 2, 3])
68
+ outer = json.dumps(inner)
69
+ result = _unwrap_json(outer)
70
+ assert result == [1, 2, 3]
71
+
72
+ def test_non_json(self):
73
+ result = _unwrap_json("not json at all")
74
+ assert result == "not json at all"
75
+
76
+
77
+ class TestBuildRPCData:
78
+ def test_basic_rpc(self):
79
+ rpc = RPC(id="test123", args=["hello", 42])
80
+ data = BatchExecuteClient._build_rpc_data(rpc)
81
+ assert data[0] == "test123"
82
+ assert json.loads(data[1]) == ["hello", 42]
83
+ assert data[2] is None
84
+ assert data[3] == "generic"
85
+
86
+
87
+ class TestDecodeResponse:
88
+ def _make_client(self):
89
+ return BatchExecuteClient(
90
+ host="test.example.com",
91
+ app="TestApp",
92
+ auth_token="token",
93
+ cookies="",
94
+ )
95
+
96
+ def test_chunked_response(self):
97
+ # Simulate a chunked batchexecute response
98
+ chunk_data = json.dumps([
99
+ ["wrb.fr", "rpc123", '["result_data"]', None, None, None, "generic"]
100
+ ])
101
+ prefix = ")]}\'"
102
+ raw = "{}\n\n{}\n{}".format(prefix, len(chunk_data), chunk_data)
103
+
104
+ client = self._make_client()
105
+ responses = client._decode_response(raw)
106
+
107
+ assert len(responses) == 1
108
+ assert responses[0].id == "rpc123"
109
+
110
+ def test_empty_response_raises(self):
111
+ client = self._make_client()
112
+ with pytest.raises(Exception):
113
+ client._decode_response(")]}'")
@@ -0,0 +1,190 @@
1
+ """Tests for the Flow API client."""
2
+
3
+ from unittest.mock import patch, MagicMock
4
+
5
+ import pytest
6
+
7
+ from gflow.api.client import (
8
+ FlowClient, FlowAPIError,
9
+ IMAGE_ASPECT_MAP, VIDEO_ASPECT_MAP,
10
+ IMAGE_MODEL, VIDEO_MODEL, TOOL_NAME,
11
+ )
12
+ from gflow.api.models import AssetType, GenerateImageRequest, GenerateVideoRequest
13
+
14
+
15
+ class TestConstants:
16
+ """Test internal constants match what we discovered."""
17
+
18
+ def test_image_model(self):
19
+ assert IMAGE_MODEL == "NARWHAL"
20
+
21
+ def test_video_model(self):
22
+ assert VIDEO_MODEL == "veo_3_1_t2v_fast_ultra"
23
+
24
+ def test_tool_name(self):
25
+ assert TOOL_NAME == "PINHOLE"
26
+
27
+
28
+ class TestAspectRatioMapping:
29
+ """Test the CLI-friendly aspect ratio mapping."""
30
+
31
+ def test_image_square(self):
32
+ assert IMAGE_ASPECT_MAP["square"] == "IMAGE_ASPECT_RATIO_SQUARE"
33
+ assert IMAGE_ASPECT_MAP["1:1"] == "IMAGE_ASPECT_RATIO_SQUARE"
34
+
35
+ def test_image_portrait(self):
36
+ assert IMAGE_ASPECT_MAP["portrait"] == "IMAGE_ASPECT_RATIO_PORTRAIT"
37
+
38
+ def test_image_landscape(self):
39
+ assert IMAGE_ASPECT_MAP["landscape"] == "IMAGE_ASPECT_RATIO_LANDSCAPE"
40
+
41
+ def test_video_landscape(self):
42
+ assert VIDEO_ASPECT_MAP["landscape"] == "VIDEO_ASPECT_RATIO_LANDSCAPE"
43
+
44
+ def test_video_portrait(self):
45
+ assert VIDEO_ASPECT_MAP["portrait"] == "VIDEO_ASPECT_RATIO_PORTRAIT"
46
+
47
+
48
+ class TestParseImageResponse:
49
+ """Test image response parsing."""
50
+
51
+ def setup_method(self):
52
+ self.client = FlowClient.__new__(FlowClient)
53
+ self.client.debug = False
54
+
55
+ def test_parses_responses_format(self):
56
+ data = {
57
+ "responses": [{
58
+ "generatedImages": [
59
+ {
60
+ "encodedImage": "abc123base64",
61
+ "mediaGenerationId": "media-001",
62
+ "prompt": "a cat",
63
+ "modelNameType": "NARWHAL",
64
+ },
65
+ ]
66
+ }]
67
+ }
68
+ assets = self.client._parse_image_response(data, "a cat")
69
+ assert len(assets) == 1
70
+ assert assets[0].id == "media-001"
71
+ assert assets[0].asset_type == AssetType.IMAGE
72
+ assert assets[0].raw["encodedImage"] == "abc123base64"
73
+
74
+ def test_parses_flat_format(self):
75
+ data = {
76
+ "generatedImages": [
77
+ {"mediaGenerationId": "flat-001", "encodedImage": "xyz"},
78
+ ]
79
+ }
80
+ assets = self.client._parse_image_response(data, "test")
81
+ assert len(assets) == 1
82
+ assert assets[0].id == "flat-001"
83
+
84
+ def test_error_response(self):
85
+ data = {"error": {"message": "bad prompt"}}
86
+ with pytest.raises(FlowAPIError, match="Image generation failed"):
87
+ self.client._parse_image_response(data, "test")
88
+
89
+
90
+ class TestParseVideoResponse:
91
+ """Test video response parsing."""
92
+
93
+ def setup_method(self):
94
+ self.client = FlowClient.__new__(FlowClient)
95
+ self.client.debug = False
96
+
97
+ def test_parses_operations(self):
98
+ data = {
99
+ "operations": [
100
+ {"name": "operations/video-abc123", "done": False},
101
+ ]
102
+ }
103
+ assets = self.client._parse_video_response(data, "a sunset")
104
+ assert len(assets) == 1
105
+ assert assets[0].id == "operations/video-abc123"
106
+ assert assets[0].asset_type == AssetType.VIDEO
107
+
108
+ def test_error_response(self):
109
+ data = {"error": {"message": "video failed"}}
110
+ with pytest.raises(FlowAPIError, match="Video generation failed"):
111
+ self.client._parse_video_response(data, "test")
112
+
113
+
114
+ class TestClientInit:
115
+ """Test client initialization."""
116
+
117
+ def test_creates_with_cookies(self):
118
+ client = FlowClient(cookies="SID=abc; HSID=def")
119
+ assert client.cookies == "SID=abc; HSID=def"
120
+ assert client._access_token == ""
121
+ assert client._project_id == ""
122
+
123
+ def test_session_id_format(self):
124
+ client = FlowClient(cookies="test")
125
+ assert client._session_id.startswith(";")
126
+ assert len(client._session_id) > 5
127
+
128
+
129
+ class TestImagePayload:
130
+ """Test that generate_image builds correct payload."""
131
+
132
+ @patch.object(FlowClient, '_get_recaptcha_token', return_value="fake-recaptcha-token")
133
+ @patch.object(FlowClient, '_sandbox_request')
134
+ @patch.object(FlowClient, '_ensure_project', return_value="proj-123")
135
+ @patch.object(FlowClient, '_ensure_token')
136
+ def test_payload_structure(self, mock_token, mock_proj, mock_request, mock_captcha):
137
+ mock_resp = MagicMock()
138
+ mock_resp.json.return_value = {"responses": [{"generatedImages": []}]}
139
+ mock_request.return_value = mock_resp
140
+
141
+ client = FlowClient(cookies="test")
142
+ req = GenerateImageRequest(prompt="a cat in space", num_images=2, seed=42)
143
+ client.generate_image(req)
144
+
145
+ call_args = mock_request.call_args
146
+ assert call_args[0][0] == "POST"
147
+ assert "proj-123/flowMedia:batchGenerateImages" in call_args[0][1]
148
+
149
+ payload = call_args[1]["json_payload"]
150
+ assert payload["clientContext"]["projectId"] == "proj-123"
151
+ assert payload["clientContext"]["tool"] == "PINHOLE"
152
+ assert payload["clientContext"]["recaptchaContext"]["token"] == "fake-recaptcha-token"
153
+ assert payload["useNewMedia"] is True
154
+ assert len(payload["requests"]) == 2
155
+ assert payload["requests"][0]["imageModelName"] == "NARWHAL"
156
+ assert payload["requests"][0]["structuredPrompt"]["parts"][0]["text"] == "a cat in space"
157
+ assert payload["requests"][0]["seed"] == 42
158
+ assert payload["requests"][0]["imageAspectRatio"] == "IMAGE_ASPECT_RATIO_LANDSCAPE"
159
+ assert payload["requests"][0]["imageInputs"] == []
160
+
161
+
162
+ class TestVideoPayload:
163
+ """Test that generate_video builds correct payload."""
164
+
165
+ @patch.object(FlowClient, '_get_recaptcha_token', return_value="fake-recaptcha-token")
166
+ @patch.object(FlowClient, '_sandbox_request')
167
+ @patch.object(FlowClient, '_ensure_project', return_value="proj-456")
168
+ @patch.object(FlowClient, '_ensure_token')
169
+ def test_payload_structure(self, mock_token, mock_proj, mock_request, mock_captcha):
170
+ mock_resp = MagicMock()
171
+ mock_resp.json.return_value = {"operations": [{"name": "op/1"}]}
172
+ mock_request.return_value = mock_resp
173
+
174
+ client = FlowClient(cookies="test")
175
+ req = GenerateVideoRequest(prompt="a sunset", seed=100)
176
+ client.generate_video(req)
177
+
178
+ call_args = mock_request.call_args
179
+ assert call_args[0][0] == "POST"
180
+ assert "video:batchAsyncGenerateVideoText" in call_args[0][1]
181
+
182
+ payload = call_args[1]["json_payload"]
183
+ assert payload["clientContext"]["projectId"] == "proj-456"
184
+ assert payload["clientContext"]["tool"] == "PINHOLE"
185
+ assert payload["clientContext"]["recaptchaContext"]["token"] == "fake-recaptcha-token"
186
+ assert payload["useV2ModelConfig"] is True
187
+ assert len(payload["requests"]) == 1
188
+ assert payload["requests"][0]["videoModelKey"] == "veo_3_1_t2v_fast_ultra"
189
+ assert payload["requests"][0]["textInput"]["structuredPrompt"]["parts"][0]["text"] == "a sunset"
190
+ assert payload["requests"][0]["aspectRatio"] == "VIDEO_ASPECT_RATIO_LANDSCAPE"
@@ -0,0 +1,40 @@
1
+ <system_prompt>
2
+ Bạn là một Đạo diễn Video Quảng Cáo (App Promo Director) kiêm Chuyên gia Copywriter.
3
+ Nhiệm vụ của bạn là lấy [Tên App + Tính năng/USP] do người dùng cung cấp và biến thành một **Kịch bản Video Ngắn (Short Video)** chuẩn AIDA với thời lượng khoảng 30s.
4
+
5
+ ### Quy tắc AIDA (Bắt buộc)
6
+ 1. **[A] Attention (Hook)**: 3 giây đầu tiên phải đánh thẳng vào nỗi đau hoặc khơi gợi sự tò mò.
7
+ 2. **[I] Interest**: 5-10 giây tiếp theo, phóng đại nỗi đau hoặc giới thiệu giải pháp (tên ứng dụng).
8
+ 3. **[D] Desire**: 10s tiếp theo, show cụ thể lợi ích lớn nhất (UI/UX của App).
9
+ 4. **[A] Action**: 5s cuối, kêu gọi tải app (Call to Action).
10
+
11
+ ### Định dạng Output (JSON)
12
+ Bạn PHẢI trả về ĐÚNG định dạng JSON sau để hệ thống tự động sinh Storyboard. Không giải thích thêm.
13
+
14
+ **Hiệu ứng chuyển cảnh (transition)** được hỗ trợ: `fade`, `slideleft`, `slideright`, `circlecrop`, `dissolve`, `wipeleft`, `wiperight`, `none`.
15
+ Mặc định dùng `fade`. Chọn hiệu ứng phù hợp với nhịp điệu kịch bản (VD: fade cho thay đổi cảm xúc, slideleft cho chuyển bước nhanh, dissolve cho nhẹ nhàng/mộng ảo).
16
+
17
+ ```json
18
+ {
19
+ "title": "<Tên chiến dịch video>",
20
+ "target_format": "16:9",
21
+ "duration": 30,
22
+ "character_prompt": "<Mô tả CỰC KỲ CHI TIẾT nhân vật chính bằng tiếng Anh. VD: A 25 year old Asian female fitness coach named Sarah, oval face, brown high ponytail hair, wearing a teal sports bra and black yoga leggings>",
23
+ "character_seed": 123456,
24
+ "scenes": [
25
+ {
26
+ "id": 1,
27
+ "type": "video",
28
+ "duration": 5,
29
+ "script": "<Câu thoại tiếng Việt cho TTS>",
30
+ "prompt": "<Prompt tiếng ANH siêu chi tiết cho hành động/bối cảnh cảnh này. character_prompt sẽ tự động được ghép vào đầu khi render. VD: standing confidently in a modern kitchen, arms crossed, cinematic wide shot, ultra clear, dramatic lighting...>",
31
+ "transition": "fade"
32
+ }
33
+ ]
34
+ }
35
+ ```
36
+ Lưu ý:
37
+ - Prompt phải tối ưu cho AI Image/Video generator (Imagen 4 / Veo 3.1).
38
+ - `character_prompt` mô tả nhân vật cố định xuyên suốt — KHÔNG lặp lại trong `prompt` của từng scene.
39
+ - `character_seed` dùng để khóa cứng Seed khi render, đảm bảo nhân vật nhất quán.
40
+ </system_prompt>
@@ -0,0 +1,29 @@
1
+ <system_prompt>
2
+ Bạn là Chuyên gia "Giải phẫu Video Viral" (Viral Video Analyst).
3
+ Nhiệm vụ của bạn là nhận Transcript của một video ngắn thành công trên YouTube/TikTok, phân tích cấu trúc nhịp điệu (Pacing & Hook) của nó, sau đó **"Cover" lại kịch bản đó** để dùng cho App được chỉ định.
4
+
5
+ ### Hướng dẫn "Mimic" (Bắt chước)
6
+ - Bóc tách cấu trúc Hook của video gốc: Họ dùng câu hỏi? Họ liệt kê 3 điều? Họ kể chuyện?
7
+ - Giữ nguyên CẤU TRÚC tâm lý đó để giữ chân người xem.
8
+ - Thay thế hoàn toàn NỘI DUNG bằng sản phẩm/app của user.
9
+ - Tốc độ câu chữ (Pacing) phải chuyển biến tương đương video mẫu.
10
+
11
+ ### Định dạng Output (JSON)
12
+ Bạn PHẢI trả về ĐÚNG định dạng JSON sau để hệ thống tự động sinh Storyboard. Không giải thích thêm.
13
+ ```json
14
+ {
15
+ "title": "<Tên chiến dịch video - Mimic Version>",
16
+ "target_format": "16:9",
17
+ "duration": 30,
18
+ "scenes": [
19
+ {
20
+ "id": 1,
21
+ "type": "video",
22
+ "duration": 5,
23
+ "script": "<Câu thoại tiếng Việt Cover cho TTS>",
24
+ "prompt": "<Prompt tiếng ANH siêu chi tiết miêu tả cảnh quay...>"
25
+ }
26
+ ]
27
+ }
28
+ ```
29
+ </system_prompt>