brandly-cli 0.3.2__tar.gz → 0.3.4__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 (130) hide show
  1. brandly_cli-0.3.4/.github/workflows/ci.yml +71 -0
  2. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/.github/workflows/release.yml +10 -0
  3. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/.gitignore +2 -0
  4. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/.pre-commit-config.yaml +1 -1
  5. brandly_cli-0.3.4/LICENSE +21 -0
  6. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/Makefile +20 -7
  7. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/PKG-INFO +2 -1
  8. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/pyproject.toml +1 -1
  9. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/__about__.py +1 -1
  10. brandly_cli-0.3.4/src/brandly_cli/agent_tools.py +312 -0
  11. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/agnes_client.py +244 -2
  12. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/audio_client.py +23 -2
  13. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/autodirector.py +1 -1
  14. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/beat_sync.py +1 -1
  15. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/cli.py +278 -42
  16. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/constants.py +63 -16
  17. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/dubbing.py +86 -11
  18. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/export_platforms.py +1 -1
  19. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/minimax_client.py +134 -52
  20. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/sharing.py +1 -1
  21. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/stitch.py +1 -1
  22. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/thumbnails.py +3 -3
  23. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/utils.py +1 -1
  24. brandly_cli-0.3.4/tests/test_agent_tools.py +267 -0
  25. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_cli.py +19 -0
  26. brandly_cli-0.3.4/tests/test_minimax.py +261 -0
  27. brandly_cli-0.3.2/$null +0 -2
  28. brandly_cli-0.3.2/.github/workflows/ci.yml +0 -37
  29. brandly_cli-0.3.2/byteplus-ark-cli-1.0.24.tgz +0 -0
  30. brandly_cli-0.3.2/generate_svd.py +0 -101
  31. brandly_cli-0.3.2/package/LICENSE +0 -201
  32. brandly_cli-0.3.2/package/README.md +0 -47
  33. brandly_cli-0.3.2/package/manifest.json +0 -72
  34. brandly_cli-0.3.2/package/package.json +0 -48
  35. brandly_cli-0.3.2/package/scripts/postinstall.js +0 -545
  36. brandly_cli-0.3.2/package/scripts/run.js +0 -44
  37. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/.env.example +0 -0
  38. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/README.md +0 -0
  39. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/assets/banner.png +0 -0
  40. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/dev-notes/AUDIT.md +0 -0
  41. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/dev-notes/DEVELOPMENT.md +0 -0
  42. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/dev-notes/GOAL.md +0 -0
  43. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/dev-notes/IMPLEMENTATION.md +0 -0
  44. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/dev-notes/PROGRESS.md +0 -0
  45. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/dev-notes/SECURITY.md +0 -0
  46. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/dev-notes/SPEC.md +0 -0
  47. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/scripts/install-hooks.sh +0 -0
  48. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/README.md +0 -0
  49. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-animal-sheet/SKILL.md +0 -0
  50. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-animal-sheet/references/habitats.md +0 -0
  51. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-animal-sheet/references/prompt-variants.md +0 -0
  52. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-animal-sheet/references/species-guide.md +0 -0
  53. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-character-sheet/SKILL.md +0 -0
  54. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-character-sheet/references/common-types.md +0 -0
  55. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-character-sheet/references/consistency-checklist.md +0 -0
  56. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-character-sheet/references/prompt-variants.md +0 -0
  57. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-consistency/SKILL.md +0 -0
  58. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-consistency/references/anchoring-system.md +0 -0
  59. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-consistency/references/drift-checklist.md +0 -0
  60. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-consistency/references/lock-strength.md +0 -0
  61. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-location-sheet/SKILL.md +0 -0
  62. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-location-sheet/references/lighting-setups.md +0 -0
  63. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-location-sheet/references/location-types.md +0 -0
  64. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-location-sheet/references/prompt-variants.md +0 -0
  65. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-mecha-sheet/SKILL.md +0 -0
  66. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-mecha-sheet/references/mecha-types.md +0 -0
  67. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-mecha-sheet/references/prompt-variants.md +0 -0
  68. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-mecha-sheet/references/technical-reference.md +0 -0
  69. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-object-sheet/SKILL.md +0 -0
  70. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-object-sheet/references/photography-setups.md +0 -0
  71. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-object-sheet/references/product-categories.md +0 -0
  72. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-object-sheet/references/prompt-variants.md +0 -0
  73. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-plant-sheet/SKILL.md +0 -0
  74. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-plant-sheet/references/plant-species.md +0 -0
  75. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-plant-sheet/references/prompt-variants.md +0 -0
  76. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-plant-sheet/references/seasonal-reference.md +0 -0
  77. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-production-bible/SKILL.md +0 -0
  78. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-production-bible/references/asset-map.md +0 -0
  79. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-production-bible/references/bible-template.md +0 -0
  80. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-production-bible/references/example-bible.md +0 -0
  81. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-storyboard/SKILL.md +0 -0
  82. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-storyboard/references/examples.md +0 -0
  83. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-storyboard/references/pacing-guide.md +0 -0
  84. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-storyboard/references/shot-formats.md +0 -0
  85. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-vehicle-sheet/SKILL.md +0 -0
  86. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-vehicle-sheet/references/prompt-variants.md +0 -0
  87. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-vehicle-sheet/references/shot-angles.md +0 -0
  88. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-vehicle-sheet/references/vehicle-types.md +0 -0
  89. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-video-generation/SKILL.md +0 -0
  90. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-video-generation/references/camera-control.md +0 -0
  91. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-video-generation/references/character-consistency.md +0 -0
  92. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-video-generation/references/lighting-presets.md +0 -0
  93. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-video-generation/references/troubleshooting.md +0 -0
  94. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/skills/brandly-video-generation/references/video-styles.md +0 -0
  95. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/__init__.py +0 -0
  96. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/__main__.py +0 -0
  97. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/analyzer.py +0 -0
  98. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/ark_client.py +0 -0
  99. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/captions.py +0 -0
  100. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/cost_tracker.py +0 -0
  101. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/director.py +0 -0
  102. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/edit.py +0 -0
  103. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/memory.py +0 -0
  104. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/project_manager.py +0 -0
  105. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/style_presets.py +0 -0
  106. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/sync.py +0 -0
  107. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/templates.py +0 -0
  108. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/trends.py +0 -0
  109. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/types.py +0 -0
  110. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/video_prompts.py +0 -0
  111. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/src/brandly_cli/webhook.py +0 -0
  112. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/__init__.py +0 -0
  113. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/_check.py +0 -0
  114. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_agnes_client.py +0 -0
  115. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_analyzer.py +0 -0
  116. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_ark_client.py +0 -0
  117. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_autodirector.py +0 -0
  118. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_beat_sync.py +0 -0
  119. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_brandly_cli.py +0 -0
  120. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_dubbing.py +0 -0
  121. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_edit.py +0 -0
  122. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_export_platforms.py +0 -0
  123. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_reference.py +0 -0
  124. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_sharing.py +0 -0
  125. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_stitch.py +0 -0
  126. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_templates.py +0 -0
  127. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_thumbnails.py +0 -0
  128. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_trends.py +0 -0
  129. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_video_prompts.py +0 -0
  130. {brandly_cli-0.3.2 → brandly_cli-0.3.4}/tests/test_webhook.py +0 -0
@@ -0,0 +1,71 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize, reopened]
6
+ push:
7
+ branches: [main]
8
+
9
+ # Cancel superseded runs on the same ref (for PRs); keep main runs to
10
+ # avoid clobbering a long build when a new commit lands mid-run.
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
14
+
15
+ jobs:
16
+ quality:
17
+ runs-on: ubuntu-latest
18
+ strategy:
19
+ fail-fast: false
20
+ matrix:
21
+ python-version: ["3.10", "3.11", "3.12"]
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - name: Install FFmpeg
26
+ run: sudo apt-get update && sudo apt-get install -y ffmpeg
27
+
28
+ - uses: actions/setup-python@v5
29
+ with:
30
+ python-version: ${{ matrix.python-version }}
31
+
32
+ - name: Install dependencies
33
+ run: |
34
+ python -m pip install --upgrade pip
35
+ pip install -e ".[dev]" build twine
36
+
37
+ - name: Lint
38
+ run: ruff check src/ tests/
39
+
40
+ - name: Type check
41
+ run: mypy src/
42
+
43
+ - name: Syntax check
44
+ run: python -m py_compile src/brandly_cli/*.py
45
+
46
+ - name: Run tests
47
+ run: |
48
+ python -m pytest tests/ -v --tb=short \
49
+ --cov=brandly_cli --cov-report=xml --cov-report=term-missing
50
+
51
+ - name: Build package
52
+ run: python -m build
53
+
54
+ - name: Check build
55
+ run: python -m twine check dist/*
56
+
57
+ - name: Upload dist
58
+ if: ${{ matrix.python-version == '3.12' }}
59
+ uses: actions/upload-artifact@v4
60
+ with:
61
+ name: dist
62
+ path: dist/
63
+ if-no-files-found: error
64
+
65
+ - name: Upload coverage
66
+ if: ${{ matrix.python-version == '3.12' }}
67
+ uses: actions/upload-artifact@v4
68
+ with:
69
+ name: coverage
70
+ path: coverage.xml
71
+ if-no-files-found: warn
@@ -29,5 +29,15 @@ jobs:
29
29
  - name: Build package
30
30
  run: python -m build
31
31
 
32
+ - name: Check build
33
+ run: python -m twine check dist/*
34
+
35
+ - name: Upload dist
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: dist-${{ github.event.release.tag_name }}
39
+ path: dist/
40
+ if-no-files-found: error
41
+
32
42
  - name: Release to PyPI
33
43
  uses: pypa/gh-action-pypi-publish@release/v1
@@ -48,3 +48,5 @@ skills_output/
48
48
 
49
49
  # Pre-commit
50
50
  .pre-commit-cache/
51
+ .agnes/
52
+ debates/
@@ -3,7 +3,7 @@ repos:
3
3
  hooks:
4
4
  - id: ruff-lint
5
5
  name: ruff-lint
6
- entry: python3 -m ruff check src/
6
+ entry: python3 -m ruff check src/ tests/
7
7
  language: system
8
8
  types: [python]
9
9
  pass_filenames: false
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dream Pixels Forge
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,14 +1,14 @@
1
1
  # Brandly CLI — Makefile
2
2
 
3
3
  PYTHON := python3
4
- PNPM := pnpm
5
4
  PIP := $(PYTHON) -m pip
6
5
  PYTEST := $(PYTHON) -m pytest
7
6
  RUFF := $(PYTHON) -m ruff
7
+ MYPY := $(PYTHON) -m mypy
8
8
  ROOT ?= .
9
9
 
10
- .PHONY: help install dev test lint type-check build clean \
11
- pre-commit ci e2e upgrade sync
10
+ .PHONY: help install dev test cov lint lint-fix check-syntax type-check \
11
+ build clean pre-commit ci e2e upgrade sync
12
12
 
13
13
  ## Show this help
14
14
  help:
@@ -19,6 +19,10 @@ help:
19
19
  install: ## install
20
20
  $(PIP) install -e ".[dev]"
21
21
 
22
+ ## Install full dev environment (package + build tooling, mirrors CI)
23
+ dev: ## dev
24
+ $(PIP) install -e ".[dev]" build twine
25
+
22
26
  ## Install pre-commit hooks
23
27
  pre-commit: ## pre-commit
24
28
  $(PIP) install pre-commit
@@ -34,18 +38,27 @@ cov: ## cov
34
38
 
35
39
  ## Run linter
36
40
  lint: ## lint
37
- $(RUFF) check src/
41
+ $(RUFF) check src/ tests/
38
42
 
39
43
  ## Fix lint issues automatically
40
44
  lint-fix: ## lint-fix
41
- $(RUFF) check src/ --fix
45
+ $(RUFF) check src/ tests/ --fix
46
+
47
+ ## Type check
48
+ type-check: ## type-check
49
+ $(MYPY) src/
42
50
 
43
51
  ## Syntax check all modules
44
52
  check-syntax: ## check-syntax
45
53
  $(PYTHON) -m py_compile src/brandly_cli/*.py
46
54
 
47
- ## Run all quality gates (test + lint + syntax)
48
- ci: check-syntax lint test ## ci
55
+ ## Build sdist + wheel (run 'make dev' first to install build/twine)
56
+ build: ## build
57
+ $(PYTHON) -m build
58
+ $(PYTHON) -m twine check dist/*
59
+
60
+ ## Run all quality gates (lint + type-check + test + build)
61
+ ci: lint type-check test build ## ci
49
62
  @echo "All quality gates passed."
50
63
 
51
64
  ## End-to-end CLI smoke test
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: brandly-cli
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: AI product video orchestrator — add image, video, and sound generation to any AI tool
5
5
  Project-URL: Homepage, https://github.com/Dream-Pixels-Forge/brandly-cli
6
6
  Project-URL: Repository, https://github.com/Dream-Pixels-Forge/brandly-cli
7
7
  Project-URL: Issues, https://github.com/Dream-Pixels-Forge/brandly-cli/issues
8
8
  Author-email: Dream Pixels Forge <contact@dreampixelsforge.com>
9
9
  License-Expression: MIT
10
+ License-File: LICENSE
10
11
  Keywords: agent,ai,cli,generation,image,orchestrator,video
11
12
  Classifier: Development Status :: 4 - Beta
12
13
  Classifier: Intended Audience :: Developers
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "brandly-cli"
7
- version = "0.3.2"
7
+ version = "0.3.4"
8
8
  description = "AI product video orchestrator — add image, video, and sound generation to any AI tool"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """Package metadata."""
2
2
 
3
- __version__ = "0.3.2"
3
+ __version__ = "0.3.4"
@@ -0,0 +1,312 @@
1
+ """Built-in tool handlers for the Agnes AI agent loop.
2
+
3
+ Each tool is a Python callable that takes only JSON-serializable arguments
4
+ and returns a JSON-serializable result. The tool *descriptor* (name + JSON
5
+ schema) is exposed to the Agnes model via the OpenAI-compatible
6
+ ``tools`` field, and this module provides the handler that actually
7
+ executes it on the client side.
8
+
9
+ Tools are intentionally small and read-only: they mirror the state the
10
+ brandly CLI already exposes (project data, Agnes jobs, model catalog)
11
+ so an agent can answer questions like "which projects have failed
12
+ videos?" without any new user input.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ from collections.abc import Awaitable, Callable
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ from rich.console import Console
23
+
24
+ console = Console()
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # Tool handler type
28
+ #
29
+ # A handler is a function (sync or async) that takes keyword arguments and
30
+ # returns a JSON-serializable value. ``agent_tool_loop`` will await the
31
+ # result if it's a coroutine, and stringify non-strings with ``json.dumps``.
32
+ # ---------------------------------------------------------------------------
33
+
34
+ ToolHandler = Callable[..., Any] | Callable[..., Awaitable[Any]]
35
+
36
+
37
+ # ---------------------------------------------------------------------------
38
+ # Tool: list_projects
39
+ # ---------------------------------------------------------------------------
40
+
41
+ def _list_projects(
42
+ status: str | None = None,
43
+ limit: int = 20,
44
+ root: str | None = None,
45
+ ) -> dict[str, Any]:
46
+ """List brandly projects on disk, optionally filtered by status."""
47
+ import asyncio
48
+
49
+ try:
50
+ from brandly_cli.project_manager import ProjectManager
51
+
52
+ pm = ProjectManager(Path(root or "."))
53
+ projects = asyncio.run(pm.list_with_status())
54
+ if status:
55
+ projects = [p for p in projects if p.get("status") == status]
56
+ projects = projects[:limit]
57
+ return {
58
+ "count": len(projects),
59
+ "projects": [
60
+ {
61
+ "id": p.get("id", ""),
62
+ "name": p.get("name", ""),
63
+ "status": p.get("status", ""),
64
+ "updated_at": p.get("updated_at", ""),
65
+ }
66
+ for p in projects
67
+ ],
68
+ }
69
+ except Exception as e: # pragma: no cover - safety net
70
+ return {"error": f"list_projects: {e}"}
71
+
72
+
73
+ # ---------------------------------------------------------------------------
74
+ # Tool: get_project
75
+ # ---------------------------------------------------------------------------
76
+
77
+ def _get_project(project_id: str, root: str | None = None) -> dict[str, Any]:
78
+ """Fetch a single project by ID from the project manager."""
79
+ import asyncio
80
+
81
+ try:
82
+ from brandly_cli.project_manager import ProjectManager
83
+
84
+ pm = ProjectManager(Path(root or "."))
85
+ data = asyncio.run(pm.read(project_id))
86
+ if not data:
87
+ return {"error": f"project {project_id} not found"}
88
+ model_dump = getattr(data, "model_dump", None)
89
+ return model_dump() if callable(model_dump) else {"id": project_id}
90
+ except Exception as e:
91
+ return {"error": f"get_project: {e}"}
92
+
93
+
94
+ # ---------------------------------------------------------------------------
95
+ # Tool: list_jobs (Agnes video jobs)
96
+ # ---------------------------------------------------------------------------
97
+
98
+ def _list_jobs(
99
+ status: str | None = None,
100
+ limit: int = 10,
101
+ ) -> dict[str, Any]:
102
+ """List recent Agnes video-generation jobs (read-only)."""
103
+ import asyncio
104
+
105
+ try:
106
+ from brandly_cli.agnes_client import list_jobs
107
+
108
+ return {"jobs": asyncio.run(list_jobs(status=status, limit=limit))}
109
+ except Exception as e:
110
+ return {"error": f"list_jobs: {e}"}
111
+
112
+
113
+ # ---------------------------------------------------------------------------
114
+ # Tool: generate_image
115
+ # ---------------------------------------------------------------------------
116
+
117
+ def _generate_image(
118
+ prompt: str,
119
+ model: str = "agnes-image-2.5-flash",
120
+ size: str = "2K",
121
+ ratio: str = "16:9",
122
+ ) -> dict[str, Any]:
123
+ """Generate a single Agnes image. Synchronous wrapper over async."""
124
+ import asyncio
125
+
126
+ try:
127
+ from brandly_cli.agnes_client import generate_image
128
+
129
+ return asyncio.run(
130
+ generate_image(prompt, model=model, size=size, ratio=ratio)
131
+ )
132
+ except Exception as e:
133
+ return {"error": f"generate_image: {e}"}
134
+
135
+
136
+ # ---------------------------------------------------------------------------
137
+ # Tool: list_models
138
+ # ---------------------------------------------------------------------------
139
+
140
+ def _list_models(category: str | None = None) -> dict[str, Any]:
141
+ """List available model IDs across image / video / audio / text."""
142
+ try:
143
+ from brandly_cli.constants import get_all_models
144
+
145
+ all_models = get_all_models()
146
+ if category and category in all_models:
147
+ return {"category": category, "models": all_models[category]}
148
+ return all_models
149
+ except Exception as e:
150
+ return {"error": f"list_models: {e}"}
151
+
152
+
153
+ # ---------------------------------------------------------------------------
154
+ # Tool registry
155
+ # ---------------------------------------------------------------------------
156
+
157
+ BUILTIN_TOOLS: dict[str, dict[str, Any]] = {
158
+ "list_projects": {
159
+ "handler": _list_projects,
160
+ "schema": {
161
+ "type": "object",
162
+ "properties": {
163
+ "status": {
164
+ "type": "string",
165
+ "description": (
166
+ "Optional status filter (e.g. pending, running, failed)."
167
+ ) ,
168
+ },
169
+ "limit": {
170
+ "type": "integer",
171
+ "default": 20,
172
+ "description": "Max projects to return.",
173
+ },
174
+ "root": {
175
+ "type": "string",
176
+ "description": "Project root directory (defaults to cwd).",
177
+ },
178
+ },
179
+ },
180
+ "description": "List brandly projects on disk, optionally filtered by status.",
181
+ },
182
+ "get_project": {
183
+ "handler": _get_project,
184
+ "schema": {
185
+ "type": "object",
186
+ "properties": {
187
+ "project_id": {
188
+ "type": "string",
189
+ "description": "UUID or slug of the project to fetch.",
190
+ },
191
+ "root": {
192
+ "type": "string",
193
+ "description": "Project root directory.",
194
+ },
195
+ },
196
+ "required": ["project_id"],
197
+ },
198
+ "description": "Fetch a single project by ID, including phases and cost tracking.",
199
+ },
200
+ "list_jobs": {
201
+ "handler": _list_jobs,
202
+ "schema": {
203
+ "type": "object",
204
+ "properties": {
205
+ "status": {
206
+ "type": "string",
207
+ "description": "Optional status filter.",
208
+ },
209
+ "limit": {
210
+ "type": "integer",
211
+ "default": 10,
212
+ },
213
+ },
214
+ },
215
+ "description": "List recent Agnes video-generation jobs (read-only).",
216
+ },
217
+ "generate_image": {
218
+ "handler": _generate_image,
219
+ "schema": {
220
+ "type": "object",
221
+ "properties": {
222
+ "prompt": {
223
+ "type": "string",
224
+ "description": "Text description of the image to generate.",
225
+ },
226
+ "model": {
227
+ "type": "string",
228
+ "default": "agnes-image-2.5-flash",
229
+ "description": "Agnes image model ID.",
230
+ },
231
+ "size": {
232
+ "type": "string",
233
+ "default": "2K",
234
+ "description": "Image size tier (1K/2K/3K/4K).",
235
+ },
236
+ "ratio": {
237
+ "type": "string",
238
+ "default": "16:9",
239
+ "description": "Aspect ratio (16:9, 9:16, 1:1, 4:3, 4:5).",
240
+ },
241
+ },
242
+ "required": ["prompt"],
243
+ },
244
+ "description": "Generate a single Agnes image from a text prompt.",
245
+ },
246
+ "list_models": {
247
+ "handler": _list_models,
248
+ "schema": {
249
+ "type": "object",
250
+ "properties": {
251
+ "category": {
252
+ "type": "string",
253
+ "enum": ["image", "video", "audio"],
254
+ "description": "Optional category filter.",
255
+ },
256
+ },
257
+ },
258
+ "description": "List available model IDs and metadata across image/video/audio.",
259
+ },
260
+ }
261
+
262
+
263
+ def get_builtin_tools() -> list[tuple[str, ToolHandler, dict[str, Any], str]]:
264
+ """Return the list of (name, handler, json_schema, description) tuples
265
+ expected by ``agent_tool_loop`` in ``agnes_client``.
266
+
267
+ The OpenAI-compatible ``tools`` payload requires:
268
+ {"type": "function", "function": {"name": ..., "description": ..., "parameters": {...}}}
269
+ so each entry is pre-shaped to drop in as-is.
270
+ """
271
+ out: list[tuple[str, ToolHandler, dict[str, Any], str]] = []
272
+ for name, spec in BUILTIN_TOOLS.items():
273
+ out.append((name, spec["handler"], spec["schema"], spec["description"]))
274
+ return out
275
+
276
+
277
+ def describe_tools() -> dict[str, Any]:
278
+ """Human-readable summary of the built-in tool set, for CLI help."""
279
+ return {
280
+ name: {
281
+ "description": spec["description"],
282
+ "parameters": spec["schema"],
283
+ }
284
+ for name, spec in BUILTIN_TOOLS.items()
285
+ }
286
+
287
+
288
+ def invoke_tool(name: str, **kwargs: Any) -> Any:
289
+ """Invoke a named built-in tool with keyword args.
290
+
291
+ Returns the raw handler result (dict, list, str, …). Callers in
292
+ ``agent_tool_loop`` will JSON-stringify non-string results.
293
+ """
294
+ if name not in BUILTIN_TOOLS:
295
+ raise KeyError(f"Unknown tool: {name}. Available: {list(BUILTIN_TOOLS)}")
296
+ handler = BUILTIN_TOOLS[name]["handler"]
297
+ return handler(**kwargs)
298
+
299
+
300
+ # ---------------------------------------------------------------------------
301
+ # JSON helpers
302
+ # ---------------------------------------------------------------------------
303
+
304
+
305
+ def to_json(value: Any) -> str:
306
+ """Stringify a tool result for feeding back to the LLM."""
307
+ if isinstance(value, str):
308
+ return value
309
+ try:
310
+ return json.dumps(value, default=str, ensure_ascii=False)
311
+ except (TypeError, ValueError):
312
+ return str(value)