@trieungoctam/vibekit 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +85 -0
- package/agents/debugger.md +158 -0
- package/agents/docs-manager.md +220 -0
- package/agents/planner.md +129 -0
- package/agents/researcher.md +58 -0
- package/agents/reviewer.md +152 -0
- package/agents/tester.md +126 -0
- package/bin/vibekit.js +18 -0
- package/hooks/lib/ck-config-utils.cjs +831 -0
- package/hooks/lib/colors.cjs +95 -0
- package/hooks/lib/config-counter.cjs +103 -0
- package/hooks/lib/context-builder.cjs +616 -0
- package/hooks/lib/git-info-cache.cjs +143 -0
- package/hooks/lib/hook-logger.cjs +92 -0
- package/hooks/lib/privacy-checker.cjs +297 -0
- package/hooks/lib/project-detector.cjs +474 -0
- package/hooks/lib/scout-checker.cjs +263 -0
- package/hooks/lib/transcript-parser.cjs +181 -0
- package/hooks/post-edit-simplify-reminder.cjs +156 -0
- package/hooks/privacy-block.cjs +166 -0
- package/hooks/scout-block.cjs +147 -0
- package/hooks/session-init.cjs +360 -0
- package/package.json +41 -0
- package/rules/development-rules.md +52 -0
- package/rules/documentation-management.md +121 -0
- package/rules/orchestration-protocol.md +43 -0
- package/rules/primary-workflow.md +57 -0
- package/rules/team-coordination-rules.md +90 -0
- package/skills/ai/agent-browser/SKILL.md +294 -0
- package/skills/ai/agent-browser/references/.gitkeep +0 -0
- package/skills/ai/agent-browser/references/agent-browser-vs-chrome-devtools.md +112 -0
- package/skills/ai/agent-browser/references/browserbase-cloud-setup.md +161 -0
- package/skills/ai/ai-artist/SKILL.md +122 -0
- package/skills/ai/ai-artist/data/awesome-prompts.csv +3592 -0
- package/skills/ai/ai-artist/data/lighting.csv +19 -0
- package/skills/ai/ai-artist/data/nano-banana-templates.csv +17 -0
- package/skills/ai/ai-artist/data/platforms.csv +11 -0
- package/skills/ai/ai-artist/data/styles.csv +26 -0
- package/skills/ai/ai-artist/data/techniques.csv +19 -0
- package/skills/ai/ai-artist/data/use-cases.csv +16 -0
- package/skills/ai/ai-artist/references/advanced-techniques.md +184 -0
- package/skills/ai/ai-artist/references/awesome-nano-banana-pro-prompts.md +8575 -0
- package/skills/ai/ai-artist/references/domain-code.md +66 -0
- package/skills/ai/ai-artist/references/domain-data.md +72 -0
- package/skills/ai/ai-artist/references/domain-marketing.md +66 -0
- package/skills/ai/ai-artist/references/domain-patterns.md +33 -0
- package/skills/ai/ai-artist/references/domain-writing.md +68 -0
- package/skills/ai/ai-artist/references/image-prompting.md +141 -0
- package/skills/ai/ai-artist/references/llm-prompting.md +165 -0
- package/skills/ai/ai-artist/references/nano-banana.md +136 -0
- package/skills/ai/ai-artist/references/reasoning-techniques.md +201 -0
- package/skills/ai/ai-artist/references/validation-workflow.md +117 -0
- package/skills/ai/ai-artist/scripts/core.py +197 -0
- package/skills/ai/ai-artist/scripts/extract_prompts.py +102 -0
- package/skills/ai/ai-artist/scripts/generate.py +370 -0
- package/skills/ai/ai-artist/scripts/search.py +147 -0
- package/skills/ai/ai-multimodal/.env.example +204 -0
- package/skills/ai/ai-multimodal/SKILL.md +110 -0
- package/skills/ai/ai-multimodal/references/audio-processing.md +387 -0
- package/skills/ai/ai-multimodal/references/image-generation.md +939 -0
- package/skills/ai/ai-multimodal/references/music-generation.md +311 -0
- package/skills/ai/ai-multimodal/references/video-analysis.md +515 -0
- package/skills/ai/ai-multimodal/references/video-generation.md +457 -0
- package/skills/ai/ai-multimodal/references/vision-understanding.md +492 -0
- package/skills/ai/ai-multimodal/scripts/.coverage +0 -0
- package/skills/ai/ai-multimodal/scripts/check_setup.py +315 -0
- package/skills/ai/ai-multimodal/scripts/document_converter.py +395 -0
- package/skills/ai/ai-multimodal/scripts/gemini_batch_process.py +1185 -0
- package/skills/ai/ai-multimodal/scripts/media_optimizer.py +506 -0
- package/skills/ai/ai-multimodal/scripts/requirements.txt +26 -0
- package/skills/ai/ai-multimodal/scripts/tests/.coverage +0 -0
- package/skills/ai/ai-multimodal/scripts/tests/requirements.txt +20 -0
- package/skills/ai/ai-multimodal/scripts/tests/test_document_converter.py +74 -0
- package/skills/ai/ai-multimodal/scripts/tests/test_gemini_batch_process.py +362 -0
- package/skills/ai/ai-multimodal/scripts/tests/test_media_optimizer.py +373 -0
- package/skills/ai/mcp-management/README.md +219 -0
- package/skills/ai/mcp-management/SKILL.md +210 -0
- package/skills/ai/mcp-management/assets/tools.json +3146 -0
- package/skills/ai/mcp-management/references/configuration.md +114 -0
- package/skills/ai/mcp-management/references/gemini-cli-integration.md +221 -0
- package/skills/ai/mcp-management/references/mcp-protocol.md +116 -0
- package/skills/ai/mcp-management/scripts/.env.example +10 -0
- package/skills/ai/mcp-management/scripts/cli.ts +195 -0
- package/skills/ai/mcp-management/scripts/dist/analyze-tools.js +70 -0
- package/skills/ai/mcp-management/scripts/dist/cli.js +160 -0
- package/skills/ai/mcp-management/scripts/dist/mcp-client.js +183 -0
- package/skills/ai/mcp-management/scripts/mcp-client.ts +230 -0
- package/skills/ai/mcp-management/scripts/package.json +20 -0
- package/skills/ai/mcp-management/scripts/tsconfig.json +15 -0
- package/skills/core/brainstorm/SKILL.md +164 -0
- package/skills/core/brainstorm/scripts/frame-template.html +214 -0
- package/skills/core/brainstorm/scripts/helper.js +88 -0
- package/skills/core/brainstorm/scripts/server.cjs +338 -0
- package/skills/core/brainstorm/scripts/start-server.sh +153 -0
- package/skills/core/brainstorm/scripts/stop-server.sh +55 -0
- package/skills/core/brainstorm/spec-document-reviewer-prompt.md +49 -0
- package/skills/core/brainstorm/visual-companion.md +286 -0
- package/skills/core/code-review/SKILL.md +147 -0
- package/skills/core/code-review/references/code-review-reception.md +113 -0
- package/skills/core/code-review/references/codebase-scan-workflow.md +29 -0
- package/skills/core/code-review/references/edge-case-scouting.md +119 -0
- package/skills/core/code-review/references/parallel-review-workflow.md +69 -0
- package/skills/core/code-review/references/requesting-code-review.md +116 -0
- package/skills/core/code-review/references/task-management-reviews.md +140 -0
- package/skills/core/code-review/references/verification-before-completion.md +139 -0
- package/skills/core/cook/README.md +86 -0
- package/skills/core/cook/SKILL.md +113 -0
- package/skills/core/cook/references/intent-detection.md +101 -0
- package/skills/core/cook/references/review-cycle.md +75 -0
- package/skills/core/cook/references/subagent-patterns.md +75 -0
- package/skills/core/cook/references/workflow-steps.md +172 -0
- package/skills/core/debug/SKILL.md +121 -0
- package/skills/core/debug/references/defense-in-depth.md +124 -0
- package/skills/core/debug/references/frontend-verification.md +103 -0
- package/skills/core/debug/references/investigation-methodology.md +101 -0
- package/skills/core/debug/references/log-and-ci-analysis.md +97 -0
- package/skills/core/debug/references/performance-diagnostics.md +113 -0
- package/skills/core/debug/references/reporting-standards.md +122 -0
- package/skills/core/debug/references/root-cause-tracing.md +122 -0
- package/skills/core/debug/references/systematic-debugging.md +102 -0
- package/skills/core/debug/references/task-management-debugging.md +155 -0
- package/skills/core/debug/references/verification.md +123 -0
- package/skills/core/debug/scripts/find-polluter.sh +63 -0
- package/skills/core/debug/scripts/find-polluter.test.md +102 -0
- package/skills/core/execute/SKILL.md +70 -0
- package/skills/core/fix/SKILL.md +111 -0
- package/skills/core/fix/references/complexity-assessment.md +72 -0
- package/skills/core/fix/references/mode-selection.md +46 -0
- package/skills/core/fix/references/parallel-exploration.md +100 -0
- package/skills/core/fix/references/review-cycle.md +77 -0
- package/skills/core/fix/references/skill-activation-matrix.md +78 -0
- package/skills/core/fix/references/task-orchestration.md +103 -0
- package/skills/core/fix/references/workflow-ci.md +28 -0
- package/skills/core/fix/references/workflow-deep.md +122 -0
- package/skills/core/fix/references/workflow-logs.md +72 -0
- package/skills/core/fix/references/workflow-quick.md +59 -0
- package/skills/core/fix/references/workflow-standard.md +111 -0
- package/skills/core/fix/references/workflow-test.md +75 -0
- package/skills/core/fix/references/workflow-types.md +33 -0
- package/skills/core/fix/references/workflow-ui.md +75 -0
- package/skills/core/plan/SKILL.md +145 -0
- package/skills/core/plan/plan-document-reviewer-prompt.md +49 -0
- package/skills/core/subagent-dev/SKILL.md +277 -0
- package/skills/core/subagent-dev/code-quality-reviewer-prompt.md +26 -0
- package/skills/core/subagent-dev/implementer-prompt.md +113 -0
- package/skills/core/subagent-dev/spec-reviewer-prompt.md +61 -0
- package/skills/core/tdd/SKILL.md +371 -0
- package/skills/core/tdd/testing-anti-patterns.md +299 -0
- package/skills/core/test/SKILL.md +109 -0
- package/skills/core/test/references/report-format.md +58 -0
- package/skills/core/test/references/test-execution-workflow.md +103 -0
- package/skills/core/test/references/ui-testing-workflow.md +65 -0
- package/skills/core/verify/SKILL.md +139 -0
- package/skills/dev/backend-dev/SKILL.md +96 -0
- package/skills/dev/backend-dev/references/backend-api-design.md +495 -0
- package/skills/dev/backend-dev/references/backend-architecture.md +454 -0
- package/skills/dev/backend-dev/references/backend-authentication.md +338 -0
- package/skills/dev/backend-dev/references/backend-code-quality.md +659 -0
- package/skills/dev/backend-dev/references/backend-debugging.md +904 -0
- package/skills/dev/backend-dev/references/backend-devops.md +494 -0
- package/skills/dev/backend-dev/references/backend-mindset.md +387 -0
- package/skills/dev/backend-dev/references/backend-performance.md +397 -0
- package/skills/dev/backend-dev/references/backend-security.md +290 -0
- package/skills/dev/backend-dev/references/backend-technologies.md +256 -0
- package/skills/dev/backend-dev/references/backend-testing.md +429 -0
- package/skills/dev/context-engineering/SKILL.md +108 -0
- package/skills/dev/context-engineering/references/context-compression.md +84 -0
- package/skills/dev/context-engineering/references/context-degradation.md +93 -0
- package/skills/dev/context-engineering/references/context-fundamentals.md +75 -0
- package/skills/dev/context-engineering/references/context-optimization.md +82 -0
- package/skills/dev/context-engineering/references/evaluation.md +89 -0
- package/skills/dev/context-engineering/references/memory-systems.md +88 -0
- package/skills/dev/context-engineering/references/multi-agent-patterns.md +90 -0
- package/skills/dev/context-engineering/references/project-development.md +97 -0
- package/skills/dev/context-engineering/references/runtime-awareness.md +202 -0
- package/skills/dev/context-engineering/references/tool-design.md +86 -0
- package/skills/dev/context-engineering/scripts/compression_evaluator.py +349 -0
- package/skills/dev/context-engineering/scripts/context_analyzer.py +317 -0
- package/skills/dev/context-engineering/scripts/tests/test_edge_cases.py +246 -0
- package/skills/dev/databases/SKILL.md +84 -0
- package/skills/dev/databases/analytics.md +198 -0
- package/skills/dev/databases/db-design.md +188 -0
- package/skills/dev/databases/incremental-etl.md +213 -0
- package/skills/dev/databases/references/mongodb-aggregation.md +447 -0
- package/skills/dev/databases/references/mongodb-atlas.md +465 -0
- package/skills/dev/databases/references/mongodb-crud.md +408 -0
- package/skills/dev/databases/references/mongodb-indexing.md +442 -0
- package/skills/dev/databases/references/postgresql-administration.md +594 -0
- package/skills/dev/databases/references/postgresql-performance.md +527 -0
- package/skills/dev/databases/references/postgresql-psql-cli.md +467 -0
- package/skills/dev/databases/references/postgresql-queries.md +475 -0
- package/skills/dev/databases/scripts/.coverage +0 -0
- package/skills/dev/databases/scripts/db_backup.py +502 -0
- package/skills/dev/databases/scripts/db_migrate.py +426 -0
- package/skills/dev/databases/scripts/db_performance_check.py +457 -0
- package/skills/dev/databases/scripts/requirements.txt +20 -0
- package/skills/dev/databases/scripts/tests/coverage-db.json +1 -0
- package/skills/dev/databases/scripts/tests/requirements.txt +4 -0
- package/skills/dev/databases/scripts/tests/test_db_backup.py +340 -0
- package/skills/dev/databases/scripts/tests/test_db_migrate.py +277 -0
- package/skills/dev/databases/scripts/tests/test_db_performance_check.py +370 -0
- package/skills/dev/databases/stacks/bigquery.md +231 -0
- package/skills/dev/databases/stacks/d1_cloudflare.md +137 -0
- package/skills/dev/databases/stacks/mysql.md +216 -0
- package/skills/dev/databases/stacks/postgres.md +235 -0
- package/skills/dev/databases/stacks/sqlite.md +244 -0
- package/skills/dev/databases/transactional.md +176 -0
- package/skills/dev/devops/.env.example +76 -0
- package/skills/dev/devops/SKILL.md +97 -0
- package/skills/dev/devops/references/browser-rendering.md +305 -0
- package/skills/dev/devops/references/cloudflare-d1-kv.md +123 -0
- package/skills/dev/devops/references/cloudflare-platform.md +271 -0
- package/skills/dev/devops/references/cloudflare-r2-storage.md +280 -0
- package/skills/dev/devops/references/cloudflare-workers-advanced.md +312 -0
- package/skills/dev/devops/references/cloudflare-workers-apis.md +309 -0
- package/skills/dev/devops/references/cloudflare-workers-basics.md +418 -0
- package/skills/dev/devops/references/docker-basics.md +297 -0
- package/skills/dev/devops/references/docker-compose.md +292 -0
- package/skills/dev/devops/references/gcloud-platform.md +297 -0
- package/skills/dev/devops/references/gcloud-services.md +304 -0
- package/skills/dev/devops/references/kubernetes-basics.md +99 -0
- package/skills/dev/devops/references/kubernetes-helm-advanced.md +75 -0
- package/skills/dev/devops/references/kubernetes-helm.md +81 -0
- package/skills/dev/devops/references/kubernetes-kubectl.md +74 -0
- package/skills/dev/devops/references/kubernetes-security-advanced.md +98 -0
- package/skills/dev/devops/references/kubernetes-security.md +95 -0
- package/skills/dev/devops/references/kubernetes-troubleshooting-advanced.md +74 -0
- package/skills/dev/devops/references/kubernetes-troubleshooting.md +49 -0
- package/skills/dev/devops/references/kubernetes-workflows-advanced.md +75 -0
- package/skills/dev/devops/references/kubernetes-workflows.md +78 -0
- package/skills/dev/devops/scripts/cloudflare_deploy.py +269 -0
- package/skills/dev/devops/scripts/docker_optimize.py +332 -0
- package/skills/dev/devops/scripts/requirements.txt +20 -0
- package/skills/dev/devops/scripts/tests/requirements.txt +3 -0
- package/skills/dev/devops/scripts/tests/test_cloudflare_deploy.py +285 -0
- package/skills/dev/devops/scripts/tests/test_docker_optimize.py +436 -0
- package/skills/dev/frontend-design/SKILL.md +78 -0
- package/skills/dev/frontend-design/references/ai-multimodal-overview.md +165 -0
- package/skills/dev/frontend-design/references/analysis-best-practices.md +80 -0
- package/skills/dev/frontend-design/references/analysis-prompts.md +141 -0
- package/skills/dev/frontend-design/references/analysis-techniques.md +118 -0
- package/skills/dev/frontend-design/references/animejs.md +396 -0
- package/skills/dev/frontend-design/references/asset-generation.md +337 -0
- package/skills/dev/frontend-design/references/design-extraction-overview.md +71 -0
- package/skills/dev/frontend-design/references/extraction-best-practices.md +141 -0
- package/skills/dev/frontend-design/references/extraction-output-templates.md +162 -0
- package/skills/dev/frontend-design/references/extraction-prompts.md +127 -0
- package/skills/dev/frontend-design/references/technical-accessibility.md +119 -0
- package/skills/dev/frontend-design/references/technical-best-practices.md +97 -0
- package/skills/dev/frontend-design/references/technical-optimization.md +44 -0
- package/skills/dev/frontend-design/references/technical-overview.md +90 -0
- package/skills/dev/frontend-design/references/technical-workflows.md +150 -0
- package/skills/dev/frontend-design/references/visual-analysis-overview.md +95 -0
- package/skills/dev/frontend-design/references/workflow-3d.md +102 -0
- package/skills/dev/frontend-design/references/workflow-describe.md +87 -0
- package/skills/dev/frontend-design/references/workflow-immersive.md +87 -0
- package/skills/dev/frontend-design/references/workflow-quick.md +57 -0
- package/skills/dev/frontend-design/references/workflow-screenshot.md +63 -0
- package/skills/dev/frontend-design/references/workflow-video.md +74 -0
- package/skills/dev/frontend-dev/SKILL.md +400 -0
- package/skills/dev/frontend-dev/resources/common-patterns.md +331 -0
- package/skills/dev/frontend-dev/resources/complete-examples.md +872 -0
- package/skills/dev/frontend-dev/resources/component-patterns.md +502 -0
- package/skills/dev/frontend-dev/resources/data-fetching.md +767 -0
- package/skills/dev/frontend-dev/resources/file-organization.md +502 -0
- package/skills/dev/frontend-dev/resources/loading-and-error-states.md +501 -0
- package/skills/dev/frontend-dev/resources/performance.md +406 -0
- package/skills/dev/frontend-dev/resources/routing-guide.md +364 -0
- package/skills/dev/frontend-dev/resources/styling-guide.md +428 -0
- package/skills/dev/frontend-dev/resources/typescript-standards.md +418 -0
- package/skills/dev/git/SKILL.md +114 -0
- package/skills/dev/git/references/branch-management.md +88 -0
- package/skills/dev/git/references/commit-standards.md +46 -0
- package/skills/dev/git/references/gh-cli-guide.md +109 -0
- package/skills/dev/git/references/safety-protocols.md +69 -0
- package/skills/dev/git/references/workflow-commit.md +58 -0
- package/skills/dev/git/references/workflow-merge.md +48 -0
- package/skills/dev/git/references/workflow-pr.md +58 -0
- package/skills/dev/git/references/workflow-push.md +52 -0
- package/skills/dev/git-worktree/SKILL.md +218 -0
- package/skills/utils/ask/SKILL.md +58 -0
- package/skills/utils/bootstrap/SKILL.md +101 -0
- package/skills/utils/bootstrap/references/shared-phases.md +59 -0
- package/skills/utils/bootstrap/references/workflow-auto.md +52 -0
- package/skills/utils/bootstrap/references/workflow-fast.md +50 -0
- package/skills/utils/bootstrap/references/workflow-full.md +60 -0
- package/skills/utils/bootstrap/references/workflow-parallel.md +59 -0
- package/skills/utils/ck-help/SKILL.md +102 -0
- package/skills/utils/ck-help/scripts/ck-help.py +1321 -0
- package/skills/utils/ck-help/scripts/commands_data.yaml +3 -0
- package/skills/utils/ck-help/scripts/skills_data.yaml +593 -0
- package/skills/utils/copywriting/SKILL.md +94 -0
- package/skills/utils/copywriting/references/copy-formulas.md +150 -0
- package/skills/utils/copywriting/references/cta-patterns.md +168 -0
- package/skills/utils/copywriting/references/email-copy.md +193 -0
- package/skills/utils/copywriting/references/headline-templates.md +140 -0
- package/skills/utils/copywriting/references/landing-page-copy.md +175 -0
- package/skills/utils/copywriting/references/power-words.md +189 -0
- package/skills/utils/copywriting/references/social-media-copy.md +222 -0
- package/skills/utils/copywriting/references/workflow-cro.md +83 -0
- package/skills/utils/copywriting/references/workflow-enhance.md +32 -0
- package/skills/utils/copywriting/references/workflow-fast.md +29 -0
- package/skills/utils/copywriting/references/workflow-good.md +39 -0
- package/skills/utils/copywriting/references/writing-styles.md +247 -0
- package/skills/utils/copywriting/scripts/extract-writing-styles.py +308 -0
- package/skills/utils/copywriting/templates/copy-brief.md +49 -0
- package/skills/utils/docs/SKILL.md +55 -0
- package/skills/utils/docs/references/init-workflow.md +32 -0
- package/skills/utils/docs/references/summarize-workflow.md +18 -0
- package/skills/utils/docs/references/update-workflow.md +59 -0
- package/skills/utils/journal/SKILL.md +11 -0
- package/skills/utils/kanban/SKILL.md +99 -0
- package/skills/utils/preview/SKILL.md +75 -0
- package/skills/utils/preview/references/generation-modes.md +95 -0
- package/skills/utils/preview/references/view-mode.md +42 -0
- package/skills/utils/repomix/SKILL.md +248 -0
- package/skills/utils/repomix/references/configuration.md +211 -0
- package/skills/utils/repomix/references/usage-patterns.md +232 -0
- package/skills/utils/repomix/scripts/.coverage +0 -0
- package/skills/utils/repomix/scripts/README.md +179 -0
- package/skills/utils/repomix/scripts/repomix_batch.py +455 -0
- package/skills/utils/repomix/scripts/repos.example.json +15 -0
- package/skills/utils/repomix/scripts/requirements.txt +15 -0
- package/skills/utils/repomix/scripts/tests/test_repomix_batch.py +531 -0
- package/skills/utils/research/SKILL.md +171 -0
- package/skills/utils/scout/SKILL.md +89 -0
- package/skills/utils/scout/references/external-scouting.md +140 -0
- package/skills/utils/scout/references/internal-scouting.md +119 -0
- package/skills/utils/scout/references/task-management-scouting.md +125 -0
- package/skills/utils/sequential-thinking/.env.example +8 -0
- package/skills/utils/sequential-thinking/README.md +183 -0
- package/skills/utils/sequential-thinking/SKILL.md +95 -0
- package/skills/utils/sequential-thinking/package.json +31 -0
- package/skills/utils/sequential-thinking/references/advanced-strategies.md +79 -0
- package/skills/utils/sequential-thinking/references/advanced-techniques.md +76 -0
- package/skills/utils/sequential-thinking/references/core-patterns.md +95 -0
- package/skills/utils/sequential-thinking/references/examples-api.md +88 -0
- package/skills/utils/sequential-thinking/references/examples-architecture.md +94 -0
- package/skills/utils/sequential-thinking/references/examples-debug.md +90 -0
- package/skills/utils/sequential-thinking/scripts/format-thought.js +159 -0
- package/skills/utils/sequential-thinking/scripts/process-thought.js +236 -0
- package/skills/utils/sequential-thinking/tests/format-thought.test.js +133 -0
- package/skills/utils/sequential-thinking/tests/process-thought.test.js +215 -0
- package/skills/utils/write-skill/SKILL.md +655 -0
- package/skills/utils/write-skill/anthropic-best-practices.md +1150 -0
- package/skills/utils/write-skill/examples/CLAUDE_MD_TESTING.md +189 -0
- package/skills/utils/write-skill/graphviz-conventions.dot +172 -0
- package/skills/utils/write-skill/persuasion-principles.md +187 -0
- package/skills/utils/write-skill/render-graphs.js +168 -0
- package/skills/utils/write-skill/testing-skills-with-subagents.md +384 -0
- package/src/commands/init.js +238 -0
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Optimize media files for Gemini API processing.
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
- Compress videos/audio for size limits
|
|
7
|
+
- Resize images appropriately
|
|
8
|
+
- Split long videos into chunks
|
|
9
|
+
- Format conversion
|
|
10
|
+
- Quality vs size optimization
|
|
11
|
+
- Validation before upload
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Optional, Dict, Any, List
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from dotenv import load_dotenv
|
|
24
|
+
except ImportError:
|
|
25
|
+
load_dotenv = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def load_env_files():
|
|
29
|
+
"""Load .env files in correct priority order.
|
|
30
|
+
|
|
31
|
+
Priority order (highest to lowest):
|
|
32
|
+
1. process.env (runtime environment variables)
|
|
33
|
+
2. .claude/skills/ai-multimodal/.env (skill-specific config)
|
|
34
|
+
3. .claude/skills/.env (shared skills config)
|
|
35
|
+
4. .claude/.env (Claude global config)
|
|
36
|
+
"""
|
|
37
|
+
if not load_dotenv:
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
# Determine base paths
|
|
41
|
+
script_dir = Path(__file__).parent
|
|
42
|
+
skill_dir = script_dir.parent # .claude/skills/ai-multimodal
|
|
43
|
+
skills_dir = skill_dir.parent # .claude/skills
|
|
44
|
+
claude_dir = skills_dir.parent # .claude
|
|
45
|
+
|
|
46
|
+
# Priority 2: Skill-specific .env
|
|
47
|
+
env_file = skill_dir / '.env'
|
|
48
|
+
if env_file.exists():
|
|
49
|
+
load_dotenv(env_file)
|
|
50
|
+
|
|
51
|
+
# Priority 3: Shared skills .env
|
|
52
|
+
env_file = skills_dir / '.env'
|
|
53
|
+
if env_file.exists():
|
|
54
|
+
load_dotenv(env_file)
|
|
55
|
+
|
|
56
|
+
# Priority 4: Claude global .env
|
|
57
|
+
env_file = claude_dir / '.env'
|
|
58
|
+
if env_file.exists():
|
|
59
|
+
load_dotenv(env_file)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# Load environment variables at module level
|
|
63
|
+
load_env_files()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def check_ffmpeg() -> bool:
|
|
67
|
+
"""Check if ffmpeg is installed."""
|
|
68
|
+
try:
|
|
69
|
+
subprocess.run(['ffmpeg', '-version'],
|
|
70
|
+
stdout=subprocess.DEVNULL,
|
|
71
|
+
stderr=subprocess.DEVNULL,
|
|
72
|
+
check=True)
|
|
73
|
+
return True
|
|
74
|
+
except (subprocess.CalledProcessError, FileNotFoundError, Exception):
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_media_info(file_path: str) -> Dict[str, Any]:
|
|
79
|
+
"""Get media file information using ffprobe."""
|
|
80
|
+
if not check_ffmpeg():
|
|
81
|
+
return {}
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
cmd = [
|
|
85
|
+
'ffprobe',
|
|
86
|
+
'-v', 'quiet',
|
|
87
|
+
'-print_format', 'json',
|
|
88
|
+
'-show_format',
|
|
89
|
+
'-show_streams',
|
|
90
|
+
file_path
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
94
|
+
data = json.loads(result.stdout)
|
|
95
|
+
|
|
96
|
+
info = {
|
|
97
|
+
'size': int(data['format'].get('size', 0)),
|
|
98
|
+
'duration': float(data['format'].get('duration', 0)),
|
|
99
|
+
'bit_rate': int(data['format'].get('bit_rate', 0)),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Get video/audio specific info
|
|
103
|
+
for stream in data.get('streams', []):
|
|
104
|
+
if stream['codec_type'] == 'video':
|
|
105
|
+
info['width'] = stream.get('width', 0)
|
|
106
|
+
info['height'] = stream.get('height', 0)
|
|
107
|
+
info['fps'] = eval(stream.get('r_frame_rate', '0/1'))
|
|
108
|
+
elif stream['codec_type'] == 'audio':
|
|
109
|
+
info['sample_rate'] = int(stream.get('sample_rate', 0))
|
|
110
|
+
info['channels'] = stream.get('channels', 0)
|
|
111
|
+
|
|
112
|
+
return info
|
|
113
|
+
|
|
114
|
+
except (subprocess.CalledProcessError, json.JSONDecodeError, Exception):
|
|
115
|
+
return {}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def optimize_video(
|
|
119
|
+
input_path: str,
|
|
120
|
+
output_path: str,
|
|
121
|
+
target_size_mb: Optional[int] = None,
|
|
122
|
+
max_duration: Optional[int] = None,
|
|
123
|
+
quality: int = 23,
|
|
124
|
+
resolution: Optional[str] = None,
|
|
125
|
+
verbose: bool = False
|
|
126
|
+
) -> bool:
|
|
127
|
+
"""Optimize video file for Gemini API."""
|
|
128
|
+
if not check_ffmpeg():
|
|
129
|
+
print("Error: ffmpeg not installed")
|
|
130
|
+
print("Install: apt-get install ffmpeg (Linux) or brew install ffmpeg (Mac)")
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
info = get_media_info(input_path)
|
|
134
|
+
if not info:
|
|
135
|
+
print(f"Error: Could not read media info from {input_path}")
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
if verbose:
|
|
139
|
+
print(f"Input: {Path(input_path).name}")
|
|
140
|
+
print(f" Size: {info['size'] / (1024*1024):.2f} MB")
|
|
141
|
+
print(f" Duration: {info['duration']:.2f}s")
|
|
142
|
+
if 'width' in info:
|
|
143
|
+
print(f" Resolution: {info['width']}x{info['height']}")
|
|
144
|
+
print(f" Bit rate: {info['bit_rate'] / 1000:.0f} kbps")
|
|
145
|
+
|
|
146
|
+
# Build ffmpeg command
|
|
147
|
+
cmd = ['ffmpeg', '-i', input_path, '-y']
|
|
148
|
+
|
|
149
|
+
# Video codec
|
|
150
|
+
cmd.extend(['-c:v', 'libx264', '-crf', str(quality)])
|
|
151
|
+
|
|
152
|
+
# Resolution
|
|
153
|
+
if resolution:
|
|
154
|
+
cmd.extend(['-vf', f'scale={resolution}'])
|
|
155
|
+
elif 'width' in info and info['width'] > 1920:
|
|
156
|
+
cmd.extend(['-vf', 'scale=1920:-2']) # Max 1080p
|
|
157
|
+
|
|
158
|
+
# Audio codec
|
|
159
|
+
cmd.extend(['-c:a', 'aac', '-b:a', '128k', '-ac', '2'])
|
|
160
|
+
|
|
161
|
+
# Duration limit
|
|
162
|
+
if max_duration and info['duration'] > max_duration:
|
|
163
|
+
cmd.extend(['-t', str(max_duration)])
|
|
164
|
+
|
|
165
|
+
# Target size (rough estimate using bitrate)
|
|
166
|
+
if target_size_mb:
|
|
167
|
+
target_bits = target_size_mb * 8 * 1024 * 1024
|
|
168
|
+
duration = min(info['duration'], max_duration) if max_duration else info['duration']
|
|
169
|
+
target_bitrate = int(target_bits / duration)
|
|
170
|
+
# Reserve some for audio (128kbps)
|
|
171
|
+
video_bitrate = max(target_bitrate - 128000, 500000)
|
|
172
|
+
cmd.extend(['-b:v', str(video_bitrate)])
|
|
173
|
+
|
|
174
|
+
cmd.append(output_path)
|
|
175
|
+
|
|
176
|
+
if verbose:
|
|
177
|
+
print(f"\nOptimizing...")
|
|
178
|
+
print(f" Command: {' '.join(cmd)}")
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
subprocess.run(cmd, check=True, capture_output=not verbose)
|
|
182
|
+
|
|
183
|
+
# Check output
|
|
184
|
+
output_info = get_media_info(output_path)
|
|
185
|
+
if output_info and verbose:
|
|
186
|
+
print(f"\nOutput: {Path(output_path).name}")
|
|
187
|
+
print(f" Size: {output_info['size'] / (1024*1024):.2f} MB")
|
|
188
|
+
print(f" Duration: {output_info['duration']:.2f}s")
|
|
189
|
+
if 'width' in output_info:
|
|
190
|
+
print(f" Resolution: {output_info['width']}x{output_info['height']}")
|
|
191
|
+
compression = (1 - output_info['size'] / info['size']) * 100
|
|
192
|
+
print(f" Compression: {compression:.1f}%")
|
|
193
|
+
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
except subprocess.CalledProcessError as e:
|
|
197
|
+
print(f"Error optimizing video: {e}")
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def optimize_audio(
|
|
202
|
+
input_path: str,
|
|
203
|
+
output_path: str,
|
|
204
|
+
target_size_mb: Optional[int] = None,
|
|
205
|
+
bitrate: str = '64k',
|
|
206
|
+
sample_rate: int = 16000,
|
|
207
|
+
verbose: bool = False
|
|
208
|
+
) -> bool:
|
|
209
|
+
"""Optimize audio file for Gemini API."""
|
|
210
|
+
if not check_ffmpeg():
|
|
211
|
+
print("Error: ffmpeg not installed")
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
info = get_media_info(input_path)
|
|
215
|
+
if not info:
|
|
216
|
+
print(f"Error: Could not read media info from {input_path}")
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
if verbose:
|
|
220
|
+
print(f"Input: {Path(input_path).name}")
|
|
221
|
+
print(f" Size: {info['size'] / (1024*1024):.2f} MB")
|
|
222
|
+
print(f" Duration: {info['duration']:.2f}s")
|
|
223
|
+
|
|
224
|
+
# Build command
|
|
225
|
+
cmd = [
|
|
226
|
+
'ffmpeg', '-i', input_path, '-y',
|
|
227
|
+
'-c:a', 'aac',
|
|
228
|
+
'-b:a', bitrate,
|
|
229
|
+
'-ar', str(sample_rate),
|
|
230
|
+
'-ac', '1', # Mono (Gemini uses mono anyway)
|
|
231
|
+
output_path
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
if verbose:
|
|
235
|
+
print(f"\nOptimizing...")
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
subprocess.run(cmd, check=True, capture_output=not verbose)
|
|
239
|
+
|
|
240
|
+
output_info = get_media_info(output_path)
|
|
241
|
+
if output_info and verbose:
|
|
242
|
+
print(f"\nOutput: {Path(output_path).name}")
|
|
243
|
+
print(f" Size: {output_info['size'] / (1024*1024):.2f} MB")
|
|
244
|
+
compression = (1 - output_info['size'] / info['size']) * 100
|
|
245
|
+
print(f" Compression: {compression:.1f}%")
|
|
246
|
+
|
|
247
|
+
return True
|
|
248
|
+
|
|
249
|
+
except subprocess.CalledProcessError as e:
|
|
250
|
+
print(f"Error optimizing audio: {e}")
|
|
251
|
+
return False
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def optimize_image(
|
|
255
|
+
input_path: str,
|
|
256
|
+
output_path: str,
|
|
257
|
+
max_width: int = 1920,
|
|
258
|
+
quality: int = 85,
|
|
259
|
+
verbose: bool = False
|
|
260
|
+
) -> bool:
|
|
261
|
+
"""Optimize image file for Gemini API."""
|
|
262
|
+
try:
|
|
263
|
+
from PIL import Image
|
|
264
|
+
except ImportError:
|
|
265
|
+
print("Error: Pillow not installed")
|
|
266
|
+
print("Install with: pip install pillow")
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
try:
|
|
270
|
+
img = Image.open(input_path)
|
|
271
|
+
|
|
272
|
+
if verbose:
|
|
273
|
+
print(f"Input: {Path(input_path).name}")
|
|
274
|
+
print(f" Size: {Path(input_path).stat().st_size / 1024:.2f} KB")
|
|
275
|
+
print(f" Resolution: {img.width}x{img.height}")
|
|
276
|
+
|
|
277
|
+
# Resize if needed
|
|
278
|
+
if img.width > max_width:
|
|
279
|
+
ratio = max_width / img.width
|
|
280
|
+
new_height = int(img.height * ratio)
|
|
281
|
+
img = img.resize((max_width, new_height), Image.Resampling.LANCZOS)
|
|
282
|
+
if verbose:
|
|
283
|
+
print(f" Resized to: {img.width}x{img.height}")
|
|
284
|
+
|
|
285
|
+
# Convert RGBA to RGB if saving as JPEG
|
|
286
|
+
if output_path.lower().endswith('.jpg') or output_path.lower().endswith('.jpeg'):
|
|
287
|
+
if img.mode == 'RGBA':
|
|
288
|
+
rgb_img = Image.new('RGB', img.size, (255, 255, 255))
|
|
289
|
+
rgb_img.paste(img, mask=img.split()[3])
|
|
290
|
+
img = rgb_img
|
|
291
|
+
|
|
292
|
+
# Save
|
|
293
|
+
img.save(output_path, quality=quality, optimize=True)
|
|
294
|
+
|
|
295
|
+
if verbose:
|
|
296
|
+
print(f"\nOutput: {Path(output_path).name}")
|
|
297
|
+
print(f" Size: {Path(output_path).stat().st_size / 1024:.2f} KB")
|
|
298
|
+
compression = (1 - Path(output_path).stat().st_size / Path(input_path).stat().st_size) * 100
|
|
299
|
+
print(f" Compression: {compression:.1f}%")
|
|
300
|
+
|
|
301
|
+
return True
|
|
302
|
+
|
|
303
|
+
except Exception as e:
|
|
304
|
+
print(f"Error optimizing image: {e}")
|
|
305
|
+
return False
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def split_video(
|
|
309
|
+
input_path: str,
|
|
310
|
+
output_dir: str,
|
|
311
|
+
chunk_duration: int = 3600,
|
|
312
|
+
verbose: bool = False
|
|
313
|
+
) -> List[str]:
|
|
314
|
+
"""Split long video into chunks."""
|
|
315
|
+
if not check_ffmpeg():
|
|
316
|
+
print("Error: ffmpeg not installed")
|
|
317
|
+
return []
|
|
318
|
+
|
|
319
|
+
info = get_media_info(input_path)
|
|
320
|
+
if not info:
|
|
321
|
+
return []
|
|
322
|
+
|
|
323
|
+
total_duration = info['duration']
|
|
324
|
+
num_chunks = int(total_duration / chunk_duration) + 1
|
|
325
|
+
|
|
326
|
+
if num_chunks == 1:
|
|
327
|
+
if verbose:
|
|
328
|
+
print("Video is short enough, no splitting needed")
|
|
329
|
+
return [input_path]
|
|
330
|
+
|
|
331
|
+
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
|
332
|
+
output_files = []
|
|
333
|
+
|
|
334
|
+
for i in range(num_chunks):
|
|
335
|
+
start_time = i * chunk_duration
|
|
336
|
+
output_file = Path(output_dir) / f"{Path(input_path).stem}_chunk_{i+1}.mp4"
|
|
337
|
+
|
|
338
|
+
cmd = [
|
|
339
|
+
'ffmpeg', '-i', input_path, '-y',
|
|
340
|
+
'-ss', str(start_time),
|
|
341
|
+
'-t', str(chunk_duration),
|
|
342
|
+
'-c', 'copy',
|
|
343
|
+
str(output_file)
|
|
344
|
+
]
|
|
345
|
+
|
|
346
|
+
if verbose:
|
|
347
|
+
print(f"Creating chunk {i+1}/{num_chunks}...")
|
|
348
|
+
|
|
349
|
+
try:
|
|
350
|
+
subprocess.run(cmd, check=True, capture_output=not verbose)
|
|
351
|
+
output_files.append(str(output_file))
|
|
352
|
+
except subprocess.CalledProcessError as e:
|
|
353
|
+
print(f"Error creating chunk {i+1}: {e}")
|
|
354
|
+
|
|
355
|
+
return output_files
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def main():
|
|
359
|
+
parser = argparse.ArgumentParser(
|
|
360
|
+
description='Optimize media files for Gemini API',
|
|
361
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
362
|
+
epilog="""
|
|
363
|
+
Examples:
|
|
364
|
+
# Optimize video to 100MB
|
|
365
|
+
%(prog)s --input video.mp4 --output optimized.mp4 --target-size 100
|
|
366
|
+
|
|
367
|
+
# Optimize audio
|
|
368
|
+
%(prog)s --input audio.mp3 --output optimized.m4a --bitrate 64k
|
|
369
|
+
|
|
370
|
+
# Resize image
|
|
371
|
+
%(prog)s --input image.jpg --output resized.jpg --max-width 1920
|
|
372
|
+
|
|
373
|
+
# Split long video
|
|
374
|
+
%(prog)s --input long-video.mp4 --split --chunk-duration 3600 --output-dir ./chunks
|
|
375
|
+
|
|
376
|
+
# Batch optimize directory
|
|
377
|
+
%(prog)s --input-dir ./videos --output-dir ./optimized --quality 85
|
|
378
|
+
"""
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
parser.add_argument('--input', help='Input file')
|
|
382
|
+
parser.add_argument('--output', help='Output file')
|
|
383
|
+
parser.add_argument('--input-dir', help='Input directory for batch processing')
|
|
384
|
+
parser.add_argument('--output-dir', help='Output directory for batch processing')
|
|
385
|
+
parser.add_argument('--target-size', type=int, help='Target size in MB')
|
|
386
|
+
parser.add_argument('--quality', type=int, default=85,
|
|
387
|
+
help='Quality (video: 0-51 CRF, image: 1-100) (default: 85)')
|
|
388
|
+
parser.add_argument('--max-width', type=int, default=1920,
|
|
389
|
+
help='Max image width (default: 1920)')
|
|
390
|
+
parser.add_argument('--bitrate', default='64k',
|
|
391
|
+
help='Audio bitrate (default: 64k)')
|
|
392
|
+
parser.add_argument('--resolution', help='Video resolution (e.g., 1920x1080)')
|
|
393
|
+
parser.add_argument('--split', action='store_true', help='Split long video into chunks')
|
|
394
|
+
parser.add_argument('--chunk-duration', type=int, default=3600,
|
|
395
|
+
help='Chunk duration in seconds (default: 3600 = 1 hour)')
|
|
396
|
+
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')
|
|
397
|
+
|
|
398
|
+
args = parser.parse_args()
|
|
399
|
+
|
|
400
|
+
# Validate arguments
|
|
401
|
+
if not args.input and not args.input_dir:
|
|
402
|
+
parser.error("Either --input or --input-dir required")
|
|
403
|
+
|
|
404
|
+
# Single file processing
|
|
405
|
+
if args.input:
|
|
406
|
+
input_path = Path(args.input)
|
|
407
|
+
if not input_path.exists():
|
|
408
|
+
print(f"Error: Input file not found: {input_path}")
|
|
409
|
+
sys.exit(1)
|
|
410
|
+
|
|
411
|
+
if args.split:
|
|
412
|
+
output_dir = args.output_dir or './chunks'
|
|
413
|
+
chunks = split_video(str(input_path), output_dir, args.chunk_duration, args.verbose)
|
|
414
|
+
print(f"\nCreated {len(chunks)} chunks in {output_dir}")
|
|
415
|
+
sys.exit(0)
|
|
416
|
+
|
|
417
|
+
if not args.output:
|
|
418
|
+
parser.error("--output required for single file processing")
|
|
419
|
+
|
|
420
|
+
output_path = Path(args.output)
|
|
421
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
422
|
+
|
|
423
|
+
# Determine file type
|
|
424
|
+
ext = input_path.suffix.lower()
|
|
425
|
+
|
|
426
|
+
if ext in ['.mp4', '.mov', '.avi', '.mkv', '.webm', '.flv']:
|
|
427
|
+
success = optimize_video(
|
|
428
|
+
str(input_path),
|
|
429
|
+
str(output_path),
|
|
430
|
+
target_size_mb=args.target_size,
|
|
431
|
+
quality=args.quality,
|
|
432
|
+
resolution=args.resolution,
|
|
433
|
+
verbose=args.verbose
|
|
434
|
+
)
|
|
435
|
+
elif ext in ['.mp3', '.wav', '.m4a', '.flac', '.aac']:
|
|
436
|
+
success = optimize_audio(
|
|
437
|
+
str(input_path),
|
|
438
|
+
str(output_path),
|
|
439
|
+
target_size_mb=args.target_size,
|
|
440
|
+
bitrate=args.bitrate,
|
|
441
|
+
verbose=args.verbose
|
|
442
|
+
)
|
|
443
|
+
elif ext in ['.jpg', '.jpeg', '.png', '.webp']:
|
|
444
|
+
success = optimize_image(
|
|
445
|
+
str(input_path),
|
|
446
|
+
str(output_path),
|
|
447
|
+
max_width=args.max_width,
|
|
448
|
+
quality=args.quality,
|
|
449
|
+
verbose=args.verbose
|
|
450
|
+
)
|
|
451
|
+
else:
|
|
452
|
+
print(f"Error: Unsupported file type: {ext}")
|
|
453
|
+
sys.exit(1)
|
|
454
|
+
|
|
455
|
+
sys.exit(0 if success else 1)
|
|
456
|
+
|
|
457
|
+
# Batch processing
|
|
458
|
+
if args.input_dir:
|
|
459
|
+
if not args.output_dir:
|
|
460
|
+
parser.error("--output-dir required for batch processing")
|
|
461
|
+
|
|
462
|
+
input_dir = Path(args.input_dir)
|
|
463
|
+
output_dir = Path(args.output_dir)
|
|
464
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
465
|
+
|
|
466
|
+
# Find all media files
|
|
467
|
+
patterns = ['*.mp4', '*.mov', '*.avi', '*.mkv', '*.webm',
|
|
468
|
+
'*.mp3', '*.wav', '*.m4a', '*.flac',
|
|
469
|
+
'*.jpg', '*.jpeg', '*.png', '*.webp']
|
|
470
|
+
|
|
471
|
+
files = []
|
|
472
|
+
for pattern in patterns:
|
|
473
|
+
files.extend(input_dir.glob(pattern))
|
|
474
|
+
|
|
475
|
+
if not files:
|
|
476
|
+
print(f"No media files found in {input_dir}")
|
|
477
|
+
sys.exit(1)
|
|
478
|
+
|
|
479
|
+
print(f"Found {len(files)} files to process")
|
|
480
|
+
|
|
481
|
+
success_count = 0
|
|
482
|
+
for input_file in files:
|
|
483
|
+
output_file = output_dir / input_file.name
|
|
484
|
+
|
|
485
|
+
ext = input_file.suffix.lower()
|
|
486
|
+
success = False
|
|
487
|
+
|
|
488
|
+
if ext in ['.mp4', '.mov', '.avi', '.mkv', '.webm', '.flv']:
|
|
489
|
+
success = optimize_video(str(input_file), str(output_file),
|
|
490
|
+
quality=args.quality, verbose=args.verbose)
|
|
491
|
+
elif ext in ['.mp3', '.wav', '.m4a', '.flac', '.aac']:
|
|
492
|
+
success = optimize_audio(str(input_file), str(output_file),
|
|
493
|
+
bitrate=args.bitrate, verbose=args.verbose)
|
|
494
|
+
elif ext in ['.jpg', '.jpeg', '.png', '.webp']:
|
|
495
|
+
success = optimize_image(str(input_file), str(output_file),
|
|
496
|
+
max_width=args.max_width, quality=args.quality,
|
|
497
|
+
verbose=args.verbose)
|
|
498
|
+
|
|
499
|
+
if success:
|
|
500
|
+
success_count += 1
|
|
501
|
+
|
|
502
|
+
print(f"\nProcessed: {success_count}/{len(files)} files")
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
if __name__ == '__main__':
|
|
506
|
+
main()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# AI Multimodal Skill Dependencies
|
|
2
|
+
# Python 3.10+ required
|
|
3
|
+
|
|
4
|
+
# Google Gemini API
|
|
5
|
+
google-genai>=0.1.0
|
|
6
|
+
|
|
7
|
+
# PDF processing
|
|
8
|
+
pypdf>=4.0.0
|
|
9
|
+
|
|
10
|
+
# Document conversion
|
|
11
|
+
python-docx>=1.0.0
|
|
12
|
+
docx2pdf>=0.1.8 # Windows only, optional on Linux/macOS
|
|
13
|
+
|
|
14
|
+
# Markdown processing
|
|
15
|
+
markdown>=3.5.0
|
|
16
|
+
|
|
17
|
+
# Image processing
|
|
18
|
+
Pillow>=10.0.0
|
|
19
|
+
|
|
20
|
+
# Environment variable management
|
|
21
|
+
python-dotenv>=1.0.0
|
|
22
|
+
|
|
23
|
+
# Testing dependencies (dev)
|
|
24
|
+
pytest>=8.0.0
|
|
25
|
+
pytest-cov>=4.1.0
|
|
26
|
+
pytest-mock>=3.12.0
|
|
Binary file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Core dependencies
|
|
2
|
+
google-genai>=0.2.0
|
|
3
|
+
python-dotenv>=1.0.0
|
|
4
|
+
|
|
5
|
+
# Image processing
|
|
6
|
+
pillow>=10.0.0
|
|
7
|
+
|
|
8
|
+
# PDF processing
|
|
9
|
+
pypdf>=3.0.0
|
|
10
|
+
|
|
11
|
+
# Document conversion
|
|
12
|
+
markdown>=3.5
|
|
13
|
+
|
|
14
|
+
# Testing
|
|
15
|
+
pytest>=7.4.0
|
|
16
|
+
pytest-cov>=4.1.0
|
|
17
|
+
pytest-mock>=3.12.0
|
|
18
|
+
|
|
19
|
+
# Optional dependencies for full functionality
|
|
20
|
+
# ffmpeg-python>=0.2.0 # For media optimization (requires ffmpeg installed)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for document_converter.py
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from unittest.mock import Mock, patch, MagicMock, mock_open
|
|
9
|
+
|
|
10
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
11
|
+
|
|
12
|
+
import document_converter as dc
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestAPIKeyFinder:
|
|
16
|
+
"""Test API key finding logic."""
|
|
17
|
+
|
|
18
|
+
@patch.dict('os.environ', {'GEMINI_API_KEY': 'test-key-from-env'})
|
|
19
|
+
def test_find_api_key_from_env(self):
|
|
20
|
+
"""Test finding API key from environment."""
|
|
21
|
+
api_key = dc.find_api_key()
|
|
22
|
+
assert api_key == 'test-key-from-env'
|
|
23
|
+
|
|
24
|
+
@patch.dict('os.environ', {}, clear=True)
|
|
25
|
+
@patch('document_converter.load_dotenv', None)
|
|
26
|
+
def test_find_api_key_no_key(self):
|
|
27
|
+
"""Test when no API key is available."""
|
|
28
|
+
api_key = dc.find_api_key()
|
|
29
|
+
assert api_key is None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestProjectRoot:
|
|
33
|
+
"""Test project root finding."""
|
|
34
|
+
|
|
35
|
+
@patch('pathlib.Path.exists')
|
|
36
|
+
def test_find_project_root_with_git(self, mock_exists):
|
|
37
|
+
"""Test finding project root with .git directory."""
|
|
38
|
+
root = dc.find_project_root()
|
|
39
|
+
assert isinstance(root, Path)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestMimeType:
|
|
43
|
+
"""Test MIME type detection."""
|
|
44
|
+
|
|
45
|
+
def test_pdf_mime_type(self):
|
|
46
|
+
"""Test PDF MIME type."""
|
|
47
|
+
assert dc.get_mime_type('document.pdf') == 'application/pdf'
|
|
48
|
+
|
|
49
|
+
def test_image_mime_types(self):
|
|
50
|
+
"""Test image MIME types."""
|
|
51
|
+
assert dc.get_mime_type('image.jpg') == 'image/jpeg'
|
|
52
|
+
assert dc.get_mime_type('image.png') == 'image/png'
|
|
53
|
+
|
|
54
|
+
def test_unknown_mime_type(self):
|
|
55
|
+
"""Test unknown file extension."""
|
|
56
|
+
assert dc.get_mime_type('file.unknown') == 'application/octet-stream'
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TestIntegration:
|
|
60
|
+
"""Integration tests."""
|
|
61
|
+
|
|
62
|
+
def test_mime_type_integration(self):
|
|
63
|
+
"""Test MIME type detection with various extensions."""
|
|
64
|
+
test_cases = [
|
|
65
|
+
('document.pdf', 'application/pdf'),
|
|
66
|
+
('image.jpg', 'image/jpeg'),
|
|
67
|
+
('unknown.xyz', 'application/octet-stream'),
|
|
68
|
+
]
|
|
69
|
+
for file_path, expected_mime in test_cases:
|
|
70
|
+
assert dc.get_mime_type(file_path) == expected_mime
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == '__main__':
|
|
74
|
+
pytest.main([__file__, '-v', '--cov=document_converter', '--cov-report=term-missing'])
|