bps-kit 1.0.1 → 1.0.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.
- package/package.json +1 -1
- package/templates/.agents/agents/backend-specialist.md +263 -0
- package/templates/.agents/agents/code-archaeologist.md +106 -0
- package/templates/.agents/agents/database-architect.md +226 -0
- package/templates/.agents/agents/debugger.md +225 -0
- package/templates/.agents/agents/devops-engineer.md +242 -0
- package/templates/.agents/agents/documentation-writer.md +104 -0
- package/templates/.agents/agents/explorer-agent.md +73 -0
- package/templates/.agents/agents/frontend-specialist.md +593 -0
- package/templates/.agents/agents/game-developer.md +162 -0
- package/templates/.agents/agents/mobile-developer.md +377 -0
- package/templates/.agents/agents/orchestrator.md +416 -0
- package/templates/.agents/agents/penetration-tester.md +188 -0
- package/templates/.agents/agents/performance-optimizer.md +187 -0
- package/templates/.agents/agents/product-manager.md +112 -0
- package/templates/.agents/agents/product-owner.md +95 -0
- package/templates/.agents/agents/project-planner.md +406 -0
- package/templates/.agents/agents/qa-automation-engineer.md +103 -0
- package/templates/.agents/agents/security-auditor.md +170 -0
- package/templates/.agents/agents/seo-specialist.md +111 -0
- package/templates/.agents/agents/test-engineer.md +158 -0
- package/templates/.agents/rules/GEMINI.md +219 -0
- package/templates/.agents/scripts/auto_preview.py +148 -0
- package/templates/.agents/scripts/checklist.py +217 -0
- package/templates/.agents/scripts/session_manager.py +120 -0
- package/templates/.agents/scripts/verify_all.py +327 -0
- package/templates/.agents/workflows/brainstorm.md +113 -0
- package/templates/.agents/workflows/create.md +59 -0
- package/templates/.agents/workflows/debug.md +103 -0
- package/templates/.agents/workflows/deploy.md +176 -0
- package/templates/.agents/workflows/enhance.md +63 -0
- package/templates/.agents/workflows/orchestrate.md +237 -0
- package/templates/.agents/workflows/plan.md +89 -0
- package/templates/.agents/workflows/preview.md +81 -0
- package/templates/.agents/workflows/setup-brain.md +39 -0
- package/templates/.agents/workflows/status.md +86 -0
- package/templates/.agents/workflows/test.md +144 -0
- package/templates/.agents/workflows/ui-ux-pro-max.md +296 -0
- package/templates/skills_normal/api-patterns/scripts/api_validator.py +211 -0
- package/templates/skills_normal/database-design/scripts/schema_validator.py +172 -0
- package/templates/skills_normal/frontend-design/scripts/accessibility_checker.py +183 -0
- package/templates/skills_normal/frontend-design/scripts/ux_audit.py +722 -0
- package/templates/skills_normal/git-pushing/scripts/smart_commit.sh +19 -0
- package/templates/skills_normal/lint-and-validate/scripts/lint_runner.py +184 -0
- package/templates/skills_normal/lint-and-validate/scripts/type_coverage.py +173 -0
- package/templates/skills_normal/performance-profiling/scripts/lighthouse_audit.py +76 -0
- package/templates/skills_normal/senior-fullstack/scripts/code_quality_analyzer.py +114 -0
- package/templates/skills_normal/senior-fullstack/scripts/fullstack_scaffolder.py +114 -0
- package/templates/skills_normal/senior-fullstack/scripts/project_scaffolder.py +114 -0
- package/templates/skills_normal/seo-fundamentals/scripts/seo_checker.py +219 -0
- package/templates/skills_normal/testing-patterns/scripts/test_runner.py +219 -0
- package/templates/skills_normal/vulnerability-scanner/scripts/security_scan.py +458 -0
- package/templates/vault/007/scripts/config.py +472 -0
- package/templates/vault/007/scripts/full_audit.py +1306 -0
- package/templates/vault/007/scripts/quick_scan.py +481 -0
- package/templates/vault/007/scripts/requirements.txt +26 -0
- package/templates/vault/007/scripts/scanners/__init__.py +0 -0
- package/templates/vault/007/scripts/scanners/dependency_scanner.py +1305 -0
- package/templates/vault/007/scripts/scanners/injection_scanner.py +1104 -0
- package/templates/vault/007/scripts/scanners/secrets_scanner.py +1008 -0
- package/templates/vault/007/scripts/score_calculator.py +693 -0
- package/templates/vault/agent-orchestrator/scripts/match_skills.py +329 -0
- package/templates/vault/agent-orchestrator/scripts/orchestrate.py +304 -0
- package/templates/vault/agent-orchestrator/scripts/requirements.txt +1 -0
- package/templates/vault/agent-orchestrator/scripts/scan_registry.py +508 -0
- package/templates/vault/ai-studio-image/scripts/config.py +613 -0
- package/templates/vault/ai-studio-image/scripts/generate.py +630 -0
- package/templates/vault/ai-studio-image/scripts/prompt_engine.py +424 -0
- package/templates/vault/ai-studio-image/scripts/requirements.txt +4 -0
- package/templates/vault/ai-studio-image/scripts/templates.py +349 -0
- package/templates/vault/android_ui_verification/scripts/verify_ui.sh +32 -0
- package/templates/vault/apify-audience-analysis/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-brand-reputation-monitoring/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-competitor-intelligence/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-content-analytics/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-ecommerce/reference/scripts/package.json +3 -0
- package/templates/vault/apify-ecommerce/reference/scripts/run_actor.js +369 -0
- package/templates/vault/apify-influencer-discovery/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-lead-generation/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-market-research/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-trend-analysis/reference/scripts/run_actor.js +363 -0
- package/templates/vault/apify-ultimate-scraper/reference/scripts/run_actor.js +363 -0
- package/templates/vault/audio-transcriber/scripts/install-requirements.sh +190 -0
- package/templates/vault/audio-transcriber/scripts/transcribe.py +486 -0
- package/templates/vault/claude-monitor/scripts/api_bench.py +240 -0
- package/templates/vault/claude-monitor/scripts/config.py +69 -0
- package/templates/vault/claude-monitor/scripts/health_check.py +362 -0
- package/templates/vault/claude-monitor/scripts/monitor.py +296 -0
- package/templates/vault/content-creator/scripts/brand_voice_analyzer.py +185 -0
- package/templates/vault/content-creator/scripts/seo_optimizer.py +419 -0
- package/templates/vault/context-agent/scripts/active_context.py +227 -0
- package/templates/vault/context-agent/scripts/compressor.py +149 -0
- package/templates/vault/context-agent/scripts/config.py +69 -0
- package/templates/vault/context-agent/scripts/context_loader.py +155 -0
- package/templates/vault/context-agent/scripts/context_manager.py +302 -0
- package/templates/vault/context-agent/scripts/models.py +103 -0
- package/templates/vault/context-agent/scripts/project_registry.py +132 -0
- package/templates/vault/context-agent/scripts/requirements.txt +6 -0
- package/templates/vault/context-agent/scripts/search.py +115 -0
- package/templates/vault/context-agent/scripts/session_parser.py +206 -0
- package/templates/vault/context-agent/scripts/session_summary.py +319 -0
- package/templates/vault/context-guardian/scripts/context_snapshot.py +229 -0
- package/templates/vault/docx/ooxml/scripts/pack.py +159 -0
- package/templates/vault/docx/ooxml/scripts/unpack.py +29 -0
- package/templates/vault/docx/ooxml/scripts/validate.py +69 -0
- package/templates/vault/docx/ooxml/scripts/validation/__init__.py +15 -0
- package/templates/vault/docx/ooxml/scripts/validation/base.py +951 -0
- package/templates/vault/docx/ooxml/scripts/validation/docx.py +274 -0
- package/templates/vault/docx/ooxml/scripts/validation/pptx.py +315 -0
- package/templates/vault/docx/ooxml/scripts/validation/redlining.py +279 -0
- package/templates/vault/docx/scripts/__init__.py +1 -0
- package/templates/vault/docx/scripts/document.py +1276 -0
- package/templates/vault/docx/scripts/templates/comments.xml +3 -0
- package/templates/vault/docx/scripts/templates/commentsExtended.xml +3 -0
- package/templates/vault/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/templates/vault/docx/scripts/templates/commentsIds.xml +3 -0
- package/templates/vault/docx/scripts/templates/people.xml +3 -0
- package/templates/vault/docx/scripts/utilities.py +374 -0
- package/templates/vault/docx-official/ooxml/scripts/pack.py +159 -0
- package/templates/vault/docx-official/ooxml/scripts/unpack.py +29 -0
- package/templates/vault/docx-official/ooxml/scripts/validate.py +69 -0
- package/templates/vault/docx-official/ooxml/scripts/validation/__init__.py +15 -0
- package/templates/vault/docx-official/ooxml/scripts/validation/base.py +951 -0
- package/templates/vault/docx-official/ooxml/scripts/validation/docx.py +274 -0
- package/templates/vault/docx-official/ooxml/scripts/validation/pptx.py +315 -0
- package/templates/vault/docx-official/ooxml/scripts/validation/redlining.py +279 -0
- package/templates/vault/docx-official/scripts/__init__.py +1 -0
- package/templates/vault/docx-official/scripts/document.py +1276 -0
- package/templates/vault/docx-official/scripts/templates/comments.xml +3 -0
- package/templates/vault/docx-official/scripts/templates/commentsExtended.xml +3 -0
- package/templates/vault/docx-official/scripts/templates/commentsExtensible.xml +3 -0
- package/templates/vault/docx-official/scripts/templates/commentsIds.xml +3 -0
- package/templates/vault/docx-official/scripts/templates/people.xml +3 -0
- package/templates/vault/docx-official/scripts/utilities.py +374 -0
- package/templates/vault/geo-fundamentals/scripts/geo_checker.py +289 -0
- package/templates/vault/helm-chart-scaffolding/scripts/validate-chart.sh +244 -0
- package/templates/vault/i18n-localization/scripts/i18n_checker.py +241 -0
- package/templates/vault/instagram/scripts/account_setup.py +233 -0
- package/templates/vault/instagram/scripts/analyze.py +221 -0
- package/templates/vault/instagram/scripts/api_client.py +444 -0
- package/templates/vault/instagram/scripts/auth.py +411 -0
- package/templates/vault/instagram/scripts/comments.py +160 -0
- package/templates/vault/instagram/scripts/config.py +111 -0
- package/templates/vault/instagram/scripts/db.py +467 -0
- package/templates/vault/instagram/scripts/export.py +138 -0
- package/templates/vault/instagram/scripts/governance.py +233 -0
- package/templates/vault/instagram/scripts/hashtags.py +114 -0
- package/templates/vault/instagram/scripts/insights.py +170 -0
- package/templates/vault/instagram/scripts/media.py +65 -0
- package/templates/vault/instagram/scripts/messages.py +103 -0
- package/templates/vault/instagram/scripts/profile.py +58 -0
- package/templates/vault/instagram/scripts/publish.py +449 -0
- package/templates/vault/instagram/scripts/requirements.txt +5 -0
- package/templates/vault/instagram/scripts/run_all.py +189 -0
- package/templates/vault/instagram/scripts/schedule.py +189 -0
- package/templates/vault/instagram/scripts/serve_api.py +234 -0
- package/templates/vault/instagram/scripts/templates.py +155 -0
- package/templates/vault/junta-leiloeiros/scripts/db.py +216 -0
- package/templates/vault/junta-leiloeiros/scripts/export.py +137 -0
- package/templates/vault/junta-leiloeiros/scripts/requirements.txt +15 -0
- package/templates/vault/junta-leiloeiros/scripts/run_all.py +190 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/__init__.py +4 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/base_scraper.py +209 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/generic_scraper.py +110 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucap.py +110 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/juceac.py +72 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/juceal.py +72 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/juceb.py +68 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucec.py +63 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucema.py +211 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucemg.py +218 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucep.py +70 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucepa.py +74 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucepar.py +80 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucepe.py +78 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucepi.py +69 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucer.py +256 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucerja.py +170 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucern.py +71 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucesc.py +89 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucesp.py +233 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucetins.py +134 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucis_df.py +63 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/jucisrs.py +299 -0
- package/templates/vault/junta-leiloeiros/scripts/scraper/states.py +99 -0
- package/templates/vault/junta-leiloeiros/scripts/serve_api.py +164 -0
- package/templates/vault/junta-leiloeiros/scripts/web_scraper_fallback.py +233 -0
- package/templates/vault/last30days/scripts/last30days.py +521 -0
- package/templates/vault/last30days/scripts/lib/__init__.py +1 -0
- package/templates/vault/last30days/scripts/lib/cache.py +152 -0
- package/templates/vault/last30days/scripts/lib/dates.py +124 -0
- package/templates/vault/last30days/scripts/lib/dedupe.py +120 -0
- package/templates/vault/last30days/scripts/lib/env.py +149 -0
- package/templates/vault/last30days/scripts/lib/http.py +152 -0
- package/templates/vault/last30days/scripts/lib/models.py +175 -0
- package/templates/vault/last30days/scripts/lib/normalize.py +160 -0
- package/templates/vault/last30days/scripts/lib/openai_reddit.py +230 -0
- package/templates/vault/last30days/scripts/lib/reddit_enrich.py +232 -0
- package/templates/vault/last30days/scripts/lib/render.py +383 -0
- package/templates/vault/last30days/scripts/lib/schema.py +336 -0
- package/templates/vault/last30days/scripts/lib/score.py +311 -0
- package/templates/vault/last30days/scripts/lib/ui.py +324 -0
- package/templates/vault/last30days/scripts/lib/websearch.py +401 -0
- package/templates/vault/last30days/scripts/lib/xai_x.py +217 -0
- package/templates/vault/leiloeiro-avaliacao/scripts/governance.py +106 -0
- package/templates/vault/leiloeiro-avaliacao/scripts/requirements.txt +1 -0
- package/templates/vault/leiloeiro-edital/scripts/governance.py +106 -0
- package/templates/vault/leiloeiro-edital/scripts/requirements.txt +1 -0
- package/templates/vault/leiloeiro-ia/scripts/governance.py +106 -0
- package/templates/vault/leiloeiro-ia/scripts/requirements.txt +1 -0
- package/templates/vault/leiloeiro-juridico/scripts/governance.py +106 -0
- package/templates/vault/leiloeiro-juridico/scripts/requirements.txt +1 -0
- package/templates/vault/leiloeiro-mercado/scripts/governance.py +106 -0
- package/templates/vault/leiloeiro-mercado/scripts/requirements.txt +1 -0
- package/templates/vault/leiloeiro-risco/scripts/governance.py +106 -0
- package/templates/vault/leiloeiro-risco/scripts/requirements.txt +1 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/database.ts +24 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/db.ts +35 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/index.ts +2 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/migrations.ts +31 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/schema.sql +8 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/index.ts +44 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/routes/todos.ts +155 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/types/index.ts +35 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/App.css +384 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/App.tsx +81 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/api/todos.ts +57 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/ConfirmDialog.tsx +26 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/EmptyState.tsx +8 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/TodoForm.tsx +43 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/TodoItem.tsx +36 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/TodoList.tsx +27 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/hooks/useTodos.ts +81 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/index.css +48 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/main.tsx +10 -0
- package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/vite-env.d.ts +1 -0
- package/templates/vault/loki-mode/scripts/export-to-vibe-kanban.sh +178 -0
- package/templates/vault/loki-mode/scripts/loki-wrapper.sh +281 -0
- package/templates/vault/loki-mode/scripts/take-screenshots.js +55 -0
- package/templates/vault/matematico-tao/scripts/complexity_analyzer.py +544 -0
- package/templates/vault/matematico-tao/scripts/dependency_graph.py +538 -0
- package/templates/vault/mcp-builder/scripts/connections.py +151 -0
- package/templates/vault/mcp-builder/scripts/evaluation.py +373 -0
- package/templates/vault/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/templates/vault/mcp-builder/scripts/requirements.txt +2 -0
- package/templates/vault/mobile-design/scripts/mobile_audit.py +670 -0
- package/templates/vault/notebooklm/scripts/__init__.py +81 -0
- package/templates/vault/notebooklm/scripts/ask_question.py +256 -0
- package/templates/vault/notebooklm/scripts/auth_manager.py +358 -0
- package/templates/vault/notebooklm/scripts/browser_session.py +255 -0
- package/templates/vault/notebooklm/scripts/browser_utils.py +107 -0
- package/templates/vault/notebooklm/scripts/cleanup_manager.py +302 -0
- package/templates/vault/notebooklm/scripts/config.py +44 -0
- package/templates/vault/notebooklm/scripts/notebook_manager.py +410 -0
- package/templates/vault/notebooklm/scripts/run.py +102 -0
- package/templates/vault/notebooklm/scripts/setup_environment.py +204 -0
- package/templates/vault/pdf/scripts/check_bounding_boxes.py +70 -0
- package/templates/vault/pdf/scripts/check_bounding_boxes_test.py +226 -0
- package/templates/vault/pdf/scripts/check_fillable_fields.py +12 -0
- package/templates/vault/pdf/scripts/convert_pdf_to_images.py +35 -0
- package/templates/vault/pdf/scripts/create_validation_image.py +41 -0
- package/templates/vault/pdf/scripts/extract_form_field_info.py +152 -0
- package/templates/vault/pdf/scripts/fill_fillable_fields.py +114 -0
- package/templates/vault/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/templates/vault/pdf-official/scripts/check_bounding_boxes.py +70 -0
- package/templates/vault/pdf-official/scripts/check_bounding_boxes_test.py +226 -0
- package/templates/vault/pdf-official/scripts/check_fillable_fields.py +12 -0
- package/templates/vault/pdf-official/scripts/convert_pdf_to_images.py +35 -0
- package/templates/vault/pdf-official/scripts/create_validation_image.py +41 -0
- package/templates/vault/pdf-official/scripts/extract_form_field_info.py +152 -0
- package/templates/vault/pdf-official/scripts/fill_fillable_fields.py +114 -0
- package/templates/vault/pdf-official/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/templates/vault/planning-with-files/scripts/check-complete.sh +44 -0
- package/templates/vault/planning-with-files/scripts/init-session.sh +120 -0
- package/templates/vault/pptx/ooxml/scripts/pack.py +159 -0
- package/templates/vault/pptx/ooxml/scripts/unpack.py +29 -0
- package/templates/vault/pptx/ooxml/scripts/validate.py +69 -0
- package/templates/vault/pptx/ooxml/scripts/validation/__init__.py +15 -0
- package/templates/vault/pptx/ooxml/scripts/validation/base.py +951 -0
- package/templates/vault/pptx/ooxml/scripts/validation/docx.py +274 -0
- package/templates/vault/pptx/ooxml/scripts/validation/pptx.py +315 -0
- package/templates/vault/pptx/ooxml/scripts/validation/redlining.py +279 -0
- package/templates/vault/pptx/scripts/html2pptx.js +979 -0
- package/templates/vault/pptx/scripts/inventory.py +1020 -0
- package/templates/vault/pptx/scripts/rearrange.py +231 -0
- package/templates/vault/pptx/scripts/replace.py +385 -0
- package/templates/vault/pptx/scripts/thumbnail.py +450 -0
- package/templates/vault/pptx-official/ooxml/scripts/pack.py +159 -0
- package/templates/vault/pptx-official/ooxml/scripts/unpack.py +29 -0
- package/templates/vault/pptx-official/ooxml/scripts/validate.py +69 -0
- package/templates/vault/pptx-official/ooxml/scripts/validation/__init__.py +15 -0
- package/templates/vault/pptx-official/ooxml/scripts/validation/base.py +951 -0
- package/templates/vault/pptx-official/ooxml/scripts/validation/docx.py +274 -0
- package/templates/vault/pptx-official/ooxml/scripts/validation/pptx.py +315 -0
- package/templates/vault/pptx-official/ooxml/scripts/validation/redlining.py +279 -0
- package/templates/vault/pptx-official/scripts/html2pptx.js +979 -0
- package/templates/vault/pptx-official/scripts/inventory.py +1020 -0
- package/templates/vault/pptx-official/scripts/rearrange.py +231 -0
- package/templates/vault/pptx-official/scripts/replace.py +385 -0
- package/templates/vault/pptx-official/scripts/thumbnail.py +450 -0
- package/templates/vault/product-manager-toolkit/scripts/customer_interview_analyzer.py +441 -0
- package/templates/vault/product-manager-toolkit/scripts/rice_prioritizer.py +296 -0
- package/templates/vault/prompt-engineering-patterns/scripts/optimize-prompt.py +279 -0
- package/templates/vault/scripts/.skill_cache.json +7538 -0
- package/templates/vault/scripts/skill_search.py +228 -0
- package/templates/vault/senior-architect/scripts/architecture_diagram_generator.py +114 -0
- package/templates/vault/senior-architect/scripts/dependency_analyzer.py +114 -0
- package/templates/vault/senior-architect/scripts/project_architect.py +114 -0
- package/templates/vault/shopify-development/scripts/requirements.txt +19 -0
- package/templates/vault/shopify-development/scripts/shopify_graphql.py +428 -0
- package/templates/vault/shopify-development/scripts/shopify_init.py +441 -0
- package/templates/vault/shopify-development/scripts/tests/test_shopify_init.py +379 -0
- package/templates/vault/skill-creator/scripts/init_skill.py +303 -0
- package/templates/vault/skill-creator/scripts/package_skill.py +110 -0
- package/templates/vault/skill-creator/scripts/quick_validate.py +95 -0
- package/templates/vault/skill-installer/scripts/detect_skills.py +318 -0
- package/templates/vault/skill-installer/scripts/install_skill.py +1708 -0
- package/templates/vault/skill-installer/scripts/package_skill.py +417 -0
- package/templates/vault/skill-installer/scripts/requirements.txt +1 -0
- package/templates/vault/skill-installer/scripts/validate_skill.py +430 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/__init__.py +13 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/code_quality.py +247 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/cross_skill.py +134 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/dependencies.py +121 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/documentation.py +189 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/governance_audit.py +153 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/performance.py +164 -0
- package/templates/vault/skill-sentinel/scripts/analyzers/security.py +189 -0
- package/templates/vault/skill-sentinel/scripts/config.py +158 -0
- package/templates/vault/skill-sentinel/scripts/cost_optimizer.py +146 -0
- package/templates/vault/skill-sentinel/scripts/db.py +354 -0
- package/templates/vault/skill-sentinel/scripts/governance.py +58 -0
- package/templates/vault/skill-sentinel/scripts/recommender.py +228 -0
- package/templates/vault/skill-sentinel/scripts/report_generator.py +224 -0
- package/templates/vault/skill-sentinel/scripts/requirements.txt +1 -0
- package/templates/vault/skill-sentinel/scripts/run_audit.py +290 -0
- package/templates/vault/skill-sentinel/scripts/scanner.py +271 -0
- package/templates/vault/stability-ai/scripts/config.py +266 -0
- package/templates/vault/stability-ai/scripts/generate.py +687 -0
- package/templates/vault/stability-ai/scripts/requirements.txt +4 -0
- package/templates/vault/stability-ai/scripts/styles.py +174 -0
- package/templates/vault/telegram/assets/boilerplate/nodejs/src/bot-client.ts +86 -0
- package/templates/vault/telegram/assets/boilerplate/nodejs/src/handlers.ts +79 -0
- package/templates/vault/telegram/assets/boilerplate/nodejs/src/index.ts +32 -0
- package/templates/vault/telegram/scripts/send_message.py +143 -0
- package/templates/vault/telegram/scripts/setup_project.py +103 -0
- package/templates/vault/telegram/scripts/test_bot.py +144 -0
- package/templates/vault/typescript-expert/scripts/ts_diagnostic.py +203 -0
- package/templates/vault/ui-ux-pro-max/scripts/__pycache__/core.cpython-314.pyc +0 -0
- package/templates/vault/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-314.pyc +0 -0
- package/templates/vault/ui-ux-pro-max/scripts/core.py +257 -0
- package/templates/vault/ui-ux-pro-max/scripts/design_system.py +487 -0
- package/templates/vault/ui-ux-pro-max/scripts/search.py +76 -0
- package/templates/vault/videodb/scripts/ws_listener.py +204 -0
- package/templates/vault/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
- package/templates/vault/web-artifacts-builder/scripts/init-artifact.sh +322 -0
- package/templates/vault/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/templates/vault/webapp-testing/scripts/with_server.py +106 -0
- package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/index.ts +125 -0
- package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/template-manager.ts +67 -0
- package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/types.ts +216 -0
- package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/webhook-handler.ts +173 -0
- package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/whatsapp-client.ts +193 -0
- package/templates/vault/whatsapp-cloud-api/scripts/send_test_message.py +137 -0
- package/templates/vault/whatsapp-cloud-api/scripts/setup_project.py +118 -0
- package/templates/vault/whatsapp-cloud-api/scripts/validate_config.py +190 -0
- package/templates/vault/youtube-summarizer/scripts/extract-transcript.py +65 -0
- package/templates/vault/youtube-summarizer/scripts/install-dependencies.sh +28 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Autenticação OAuth 2.0 para Instagram Graph API.
|
|
3
|
+
|
|
4
|
+
Uso:
|
|
5
|
+
python scripts/auth.py --setup # Configuração inicial completa
|
|
6
|
+
python scripts/auth.py --refresh # Renovar token
|
|
7
|
+
python scripts/auth.py --status # Ver status do token
|
|
8
|
+
python scripts/auth.py --revoke # Revogar token
|
|
9
|
+
|
|
10
|
+
O fluxo:
|
|
11
|
+
1. Usuário fornece App ID e App Secret (do Facebook Developer Console)
|
|
12
|
+
2. Script abre browser para autorização OAuth
|
|
13
|
+
3. Servidor local (localhost:8765) captura o redirect com o code
|
|
14
|
+
4. Troca code por token curto (1hr) → token longo (60 dias)
|
|
15
|
+
5. Descobre Instagram User ID via Facebook Pages
|
|
16
|
+
6. Salva tudo no banco SQLite (accounts table)
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import argparse
|
|
21
|
+
import asyncio
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
import webbrowser
|
|
26
|
+
from datetime import datetime, timedelta, timezone
|
|
27
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Optional
|
|
30
|
+
from urllib.parse import parse_qs, urlparse
|
|
31
|
+
|
|
32
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
33
|
+
|
|
34
|
+
import httpx
|
|
35
|
+
|
|
36
|
+
from config import (
|
|
37
|
+
GRAPH_API_BASE,
|
|
38
|
+
OAUTH_AUTHORIZE_URL,
|
|
39
|
+
OAUTH_REDIRECT_PORT,
|
|
40
|
+
OAUTH_REDIRECT_URI,
|
|
41
|
+
OAUTH_SCOPES,
|
|
42
|
+
OAUTH_TOKEN_URL,
|
|
43
|
+
)
|
|
44
|
+
from db import Database
|
|
45
|
+
|
|
46
|
+
db = Database()
|
|
47
|
+
db.init()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ── OAuth Callback Server ────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
class OAuthCallbackHandler(BaseHTTPRequestHandler):
|
|
53
|
+
"""Servidor HTTP mínimo para capturar o callback OAuth."""
|
|
54
|
+
authorization_code: Optional[str] = None
|
|
55
|
+
|
|
56
|
+
def do_GET(self):
|
|
57
|
+
parsed = urlparse(self.path)
|
|
58
|
+
params = parse_qs(parsed.query)
|
|
59
|
+
|
|
60
|
+
if "code" in params:
|
|
61
|
+
OAuthCallbackHandler.authorization_code = params["code"][0]
|
|
62
|
+
self.send_response(200)
|
|
63
|
+
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
64
|
+
self.end_headers()
|
|
65
|
+
self.wfile.write(
|
|
66
|
+
b"<html><body><h2>Autorizado com sucesso!</h2>"
|
|
67
|
+
b"<p>Pode fechar esta janela e voltar ao terminal.</p></body></html>"
|
|
68
|
+
)
|
|
69
|
+
elif "error" in params:
|
|
70
|
+
error = params.get("error_description", params.get("error", ["desconhecido"]))[0]
|
|
71
|
+
self.send_response(400)
|
|
72
|
+
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
73
|
+
self.end_headers()
|
|
74
|
+
self.wfile.write(f"<html><body><h2>Erro: {error}</h2></body></html>".encode())
|
|
75
|
+
else:
|
|
76
|
+
self.send_response(404)
|
|
77
|
+
self.end_headers()
|
|
78
|
+
|
|
79
|
+
def log_message(self, format, *args):
|
|
80
|
+
pass # silencia logs do servidor
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def wait_for_oauth_code() -> Optional[str]:
|
|
84
|
+
"""Inicia servidor local e espera pelo código de autorização."""
|
|
85
|
+
server = HTTPServer(("localhost", OAUTH_REDIRECT_PORT), OAuthCallbackHandler)
|
|
86
|
+
server.timeout = 120 # 2 minutos
|
|
87
|
+
print(f"Aguardando autorização em http://localhost:{OAUTH_REDIRECT_PORT}/callback ...")
|
|
88
|
+
print("(Timeout: 2 minutos)\n")
|
|
89
|
+
|
|
90
|
+
while OAuthCallbackHandler.authorization_code is None:
|
|
91
|
+
server.handle_request()
|
|
92
|
+
if OAuthCallbackHandler.authorization_code is not None:
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
server.server_close()
|
|
96
|
+
return OAuthCallbackHandler.authorization_code
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ── Token Exchange ────────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
async def exchange_code_for_short_token(
|
|
102
|
+
code: str, app_id: str, app_secret: str,
|
|
103
|
+
) -> dict:
|
|
104
|
+
"""Troca authorization code por short-lived token (~1hr)."""
|
|
105
|
+
async with httpx.AsyncClient(timeout=30) as client:
|
|
106
|
+
resp = await client.get(
|
|
107
|
+
OAUTH_TOKEN_URL,
|
|
108
|
+
params={
|
|
109
|
+
"client_id": app_id,
|
|
110
|
+
"redirect_uri": OAUTH_REDIRECT_URI,
|
|
111
|
+
"client_secret": app_secret,
|
|
112
|
+
"code": code,
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
resp.raise_for_status()
|
|
116
|
+
return resp.json()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
async def exchange_for_long_lived_token(
|
|
120
|
+
short_token: str, app_id: str, app_secret: str,
|
|
121
|
+
) -> dict:
|
|
122
|
+
"""Troca short-lived token por long-lived token (60 dias)."""
|
|
123
|
+
async with httpx.AsyncClient(timeout=30) as client:
|
|
124
|
+
resp = await client.get(
|
|
125
|
+
OAUTH_TOKEN_URL,
|
|
126
|
+
params={
|
|
127
|
+
"grant_type": "fb_exchange_token",
|
|
128
|
+
"client_id": app_id,
|
|
129
|
+
"client_secret": app_secret,
|
|
130
|
+
"fb_exchange_token": short_token,
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
resp.raise_for_status()
|
|
134
|
+
return resp.json()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
async def refresh_long_lived_token(access_token: str, app_id: str, app_secret: str) -> dict:
|
|
138
|
+
"""Renova um long-lived token (deve ter mais de 24hr e menos de 60 dias)."""
|
|
139
|
+
async with httpx.AsyncClient(timeout=30) as client:
|
|
140
|
+
resp = await client.get(
|
|
141
|
+
OAUTH_TOKEN_URL,
|
|
142
|
+
params={
|
|
143
|
+
"grant_type": "fb_exchange_token",
|
|
144
|
+
"client_id": app_id,
|
|
145
|
+
"client_secret": app_secret,
|
|
146
|
+
"fb_exchange_token": access_token,
|
|
147
|
+
},
|
|
148
|
+
)
|
|
149
|
+
resp.raise_for_status()
|
|
150
|
+
return resp.json()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ── Instagram User Discovery ─────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
async def discover_instagram_account(access_token: str) -> dict:
|
|
156
|
+
"""
|
|
157
|
+
Descobre o Instagram Business/Creator account via Facebook Pages.
|
|
158
|
+
Retorna: {ig_user_id, username, account_type, facebook_page_id}
|
|
159
|
+
"""
|
|
160
|
+
async with httpx.AsyncClient(timeout=30) as client:
|
|
161
|
+
# 1. Listar Facebook Pages
|
|
162
|
+
resp = await client.get(
|
|
163
|
+
f"{GRAPH_API_BASE}/me/accounts",
|
|
164
|
+
params={"access_token": access_token, "fields": "id,name,access_token"},
|
|
165
|
+
)
|
|
166
|
+
resp.raise_for_status()
|
|
167
|
+
pages = resp.json().get("data", [])
|
|
168
|
+
|
|
169
|
+
if not pages:
|
|
170
|
+
raise ValueError(
|
|
171
|
+
"Nenhuma Facebook Page encontrada. "
|
|
172
|
+
"Crie uma Facebook Page e vincule à sua conta Instagram."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# 2. Para cada page, verificar se tem Instagram Business Account
|
|
176
|
+
for page in pages:
|
|
177
|
+
page_id = page["id"]
|
|
178
|
+
resp = await client.get(
|
|
179
|
+
f"{GRAPH_API_BASE}/{page_id}",
|
|
180
|
+
params={
|
|
181
|
+
"access_token": access_token,
|
|
182
|
+
"fields": "instagram_business_account",
|
|
183
|
+
},
|
|
184
|
+
)
|
|
185
|
+
resp.raise_for_status()
|
|
186
|
+
ig_account = resp.json().get("instagram_business_account")
|
|
187
|
+
|
|
188
|
+
if ig_account:
|
|
189
|
+
ig_user_id = ig_account["id"]
|
|
190
|
+
# 3. Buscar detalhes da conta Instagram
|
|
191
|
+
resp = await client.get(
|
|
192
|
+
f"{GRAPH_API_BASE}/{ig_user_id}",
|
|
193
|
+
params={
|
|
194
|
+
"access_token": access_token,
|
|
195
|
+
"fields": "id,username,account_type,name,profile_picture_url,"
|
|
196
|
+
"followers_count,follows_count,media_count",
|
|
197
|
+
},
|
|
198
|
+
)
|
|
199
|
+
resp.raise_for_status()
|
|
200
|
+
ig_info = resp.json()
|
|
201
|
+
return {
|
|
202
|
+
"ig_user_id": ig_user_id,
|
|
203
|
+
"username": ig_info.get("username"),
|
|
204
|
+
"account_type": ig_info.get("account_type", "BUSINESS"),
|
|
205
|
+
"facebook_page_id": page_id,
|
|
206
|
+
"profile": ig_info,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
raise ValueError(
|
|
210
|
+
"Nenhuma conta Instagram Business/Creator vinculada às Facebook Pages encontradas. "
|
|
211
|
+
"Vincule sua conta Instagram a uma Facebook Page nas configurações do Instagram."
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# ── Auto-Refresh ──────────────────────────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
async def auto_refresh_if_needed(account_id: Optional[int] = None) -> Optional[str]:
|
|
218
|
+
"""
|
|
219
|
+
Verifica se o token está próximo de expirar (< 7 dias) e renova.
|
|
220
|
+
Retorna o token atual (renovado ou não).
|
|
221
|
+
"""
|
|
222
|
+
account = db.get_active_account() if account_id is None else db.get_account_by_id(account_id)
|
|
223
|
+
if not account:
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
token = account["access_token"]
|
|
227
|
+
expires_at = account.get("token_expires_at")
|
|
228
|
+
|
|
229
|
+
if not expires_at:
|
|
230
|
+
return token
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
expiry = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
|
|
234
|
+
except (ValueError, AttributeError):
|
|
235
|
+
return token
|
|
236
|
+
|
|
237
|
+
now = datetime.now(timezone.utc)
|
|
238
|
+
days_left = (expiry - now).days
|
|
239
|
+
|
|
240
|
+
if days_left <= 7:
|
|
241
|
+
print(f"Token expira em {days_left} dias. Renovando...")
|
|
242
|
+
try:
|
|
243
|
+
result = await refresh_long_lived_token(
|
|
244
|
+
token, account["app_id"], account["app_secret"]
|
|
245
|
+
)
|
|
246
|
+
new_token = result["access_token"]
|
|
247
|
+
new_expires = (now + timedelta(seconds=result.get("expires_in", 5184000))).isoformat()
|
|
248
|
+
db.update_token(account["id"], new_token, new_expires)
|
|
249
|
+
print(f"Token renovado. Nova expiração: {new_expires[:10]}")
|
|
250
|
+
return new_token
|
|
251
|
+
except Exception as e:
|
|
252
|
+
print(f"AVISO: Falha ao renovar token: {e}")
|
|
253
|
+
return token
|
|
254
|
+
|
|
255
|
+
return token
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# ── Setup Flow ────────────────────────────────────────────────────────────────
|
|
259
|
+
|
|
260
|
+
async def setup() -> None:
|
|
261
|
+
"""Fluxo completo de setup OAuth."""
|
|
262
|
+
print("=" * 60)
|
|
263
|
+
print("CONFIGURAÇÃO OAUTH - INSTAGRAM GRAPH API")
|
|
264
|
+
print("=" * 60)
|
|
265
|
+
print()
|
|
266
|
+
print("Você precisa de um Meta App com o produto Instagram Graph API.")
|
|
267
|
+
print("Crie em: https://developers.facebook.com/apps/")
|
|
268
|
+
print()
|
|
269
|
+
|
|
270
|
+
# App credentials
|
|
271
|
+
app_id = os.environ.get("INSTAGRAM_APP_ID") or input("App ID: ").strip()
|
|
272
|
+
app_secret = os.environ.get("INSTAGRAM_APP_SECRET") or input("App Secret: ").strip()
|
|
273
|
+
|
|
274
|
+
if not app_id or not app_secret:
|
|
275
|
+
print("ERRO: App ID e App Secret são obrigatórios.")
|
|
276
|
+
sys.exit(1)
|
|
277
|
+
|
|
278
|
+
# Construir URL de autorização
|
|
279
|
+
scopes = ",".join(OAUTH_SCOPES)
|
|
280
|
+
auth_url = (
|
|
281
|
+
f"{OAUTH_AUTHORIZE_URL}?"
|
|
282
|
+
f"client_id={app_id}&"
|
|
283
|
+
f"redirect_uri={OAUTH_REDIRECT_URI}&"
|
|
284
|
+
f"scope={scopes}&"
|
|
285
|
+
f"response_type=code"
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
print(f"\nAbrindo browser para autorização...")
|
|
289
|
+
# Mask client_id in auth URL to avoid logging credentials
|
|
290
|
+
masked_url = auth_url.replace(app_id, app_id[:4] + "...masked") if app_id else auth_url
|
|
291
|
+
print(f"URL: {masked_url}\n")
|
|
292
|
+
webbrowser.open(auth_url)
|
|
293
|
+
|
|
294
|
+
# Esperar callback
|
|
295
|
+
OAuthCallbackHandler.authorization_code = None
|
|
296
|
+
code = wait_for_oauth_code()
|
|
297
|
+
|
|
298
|
+
if not code:
|
|
299
|
+
print("ERRO: Timeout ou falha na autorização.")
|
|
300
|
+
sys.exit(1)
|
|
301
|
+
|
|
302
|
+
print("Código de autorização recebido. Trocando por token...")
|
|
303
|
+
|
|
304
|
+
# Trocar por short-lived token
|
|
305
|
+
short_result = await exchange_code_for_short_token(code, app_id, app_secret)
|
|
306
|
+
short_token = short_result["access_token"]
|
|
307
|
+
print("Token curto obtido.")
|
|
308
|
+
|
|
309
|
+
# Trocar por long-lived token
|
|
310
|
+
long_result = await exchange_for_long_lived_token(short_token, app_id, app_secret)
|
|
311
|
+
long_token = long_result["access_token"]
|
|
312
|
+
expires_in = long_result.get("expires_in", 5184000) # 60 dias default
|
|
313
|
+
expires_at = (datetime.now(timezone.utc) + timedelta(seconds=expires_in)).isoformat()
|
|
314
|
+
print(f"Token longo obtido. Expira em: {expires_at[:10]}")
|
|
315
|
+
|
|
316
|
+
# Descobrir conta Instagram
|
|
317
|
+
print("Buscando conta Instagram vinculada...")
|
|
318
|
+
ig_info = await discover_instagram_account(long_token)
|
|
319
|
+
|
|
320
|
+
# Salvar no banco
|
|
321
|
+
account_id = db.upsert_account({
|
|
322
|
+
"ig_user_id": ig_info["ig_user_id"],
|
|
323
|
+
"username": ig_info["username"],
|
|
324
|
+
"account_type": ig_info["account_type"],
|
|
325
|
+
"access_token": long_token,
|
|
326
|
+
"token_expires_at": expires_at,
|
|
327
|
+
"facebook_page_id": ig_info["facebook_page_id"],
|
|
328
|
+
"app_id": app_id,
|
|
329
|
+
"app_secret": app_secret,
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
print()
|
|
333
|
+
print("=" * 60)
|
|
334
|
+
print("CONFIGURAÇÃO CONCLUÍDA")
|
|
335
|
+
print("=" * 60)
|
|
336
|
+
profile = ig_info.get("profile", {})
|
|
337
|
+
print(f" Conta: @{ig_info['username']}")
|
|
338
|
+
print(f" Tipo: {ig_info['account_type']}")
|
|
339
|
+
print(f" Seguidores: {profile.get('followers_count', '?')}")
|
|
340
|
+
print(f" Posts: {profile.get('media_count', '?')}")
|
|
341
|
+
print(f" Token expira: {expires_at[:10]}")
|
|
342
|
+
print(f" Account ID (interno): {account_id}")
|
|
343
|
+
print()
|
|
344
|
+
print("Pronto! Use 'python scripts/status.py' para verificar.")
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
async def show_status() -> None:
|
|
348
|
+
"""Mostra status da autenticação."""
|
|
349
|
+
account = db.get_active_account()
|
|
350
|
+
if not account:
|
|
351
|
+
print(json.dumps({"status": "not_configured", "message": "Nenhuma conta configurada. Execute: python scripts/auth.py --setup"}, indent=2))
|
|
352
|
+
return
|
|
353
|
+
|
|
354
|
+
expires_at = account.get("token_expires_at", "")
|
|
355
|
+
now = datetime.now(timezone.utc)
|
|
356
|
+
try:
|
|
357
|
+
expiry = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
|
|
358
|
+
days_left = (expiry - now).days
|
|
359
|
+
token_status = "valid" if days_left > 0 else "expired"
|
|
360
|
+
except (ValueError, AttributeError):
|
|
361
|
+
days_left = -1
|
|
362
|
+
token_status = "unknown"
|
|
363
|
+
|
|
364
|
+
result = {
|
|
365
|
+
"status": token_status,
|
|
366
|
+
"username": account["username"],
|
|
367
|
+
"account_type": account["account_type"],
|
|
368
|
+
"ig_user_id": account["ig_user_id"],
|
|
369
|
+
"token_expires_at": expires_at,
|
|
370
|
+
"days_remaining": days_left,
|
|
371
|
+
"auto_refresh": days_left <= 7 if days_left >= 0 else False,
|
|
372
|
+
}
|
|
373
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
async def do_refresh() -> None:
|
|
377
|
+
"""Força renovação do token."""
|
|
378
|
+
token = await auto_refresh_if_needed()
|
|
379
|
+
if token:
|
|
380
|
+
print("Token renovado com sucesso.")
|
|
381
|
+
await show_status()
|
|
382
|
+
else:
|
|
383
|
+
print("Nenhuma conta configurada para renovar.")
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def main():
|
|
387
|
+
parser = argparse.ArgumentParser(description="Autenticação OAuth Instagram")
|
|
388
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
389
|
+
group.add_argument("--setup", action="store_true", help="Configuração inicial completa")
|
|
390
|
+
group.add_argument("--refresh", action="store_true", help="Renovar token")
|
|
391
|
+
group.add_argument("--status", action="store_true", help="Ver status do token")
|
|
392
|
+
group.add_argument("--revoke", action="store_true", help="Revogar token (desativar conta)")
|
|
393
|
+
args = parser.parse_args()
|
|
394
|
+
|
|
395
|
+
if args.setup:
|
|
396
|
+
asyncio.run(setup())
|
|
397
|
+
elif args.refresh:
|
|
398
|
+
asyncio.run(do_refresh())
|
|
399
|
+
elif args.status:
|
|
400
|
+
asyncio.run(show_status())
|
|
401
|
+
elif args.revoke:
|
|
402
|
+
account = db.get_active_account()
|
|
403
|
+
if account:
|
|
404
|
+
db._connect().execute("UPDATE accounts SET is_active = 0 WHERE id = ?", [account["id"]])
|
|
405
|
+
print(f"Conta @{account['username']} desativada.")
|
|
406
|
+
else:
|
|
407
|
+
print("Nenhuma conta ativa para revogar.")
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
if __name__ == "__main__":
|
|
411
|
+
main()
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Gestão de comentários do Instagram.
|
|
3
|
+
|
|
4
|
+
Uso:
|
|
5
|
+
python scripts/comments.py --list --media-id 12345
|
|
6
|
+
python scripts/comments.py --reply --comment-id 67890 --text "Obrigado!"
|
|
7
|
+
python scripts/comments.py --delete --comment-id 67890
|
|
8
|
+
python scripts/comments.py --mentions
|
|
9
|
+
python scripts/comments.py --unreplied
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import asyncio
|
|
15
|
+
import json
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
20
|
+
|
|
21
|
+
from api_client import InstagramAPI
|
|
22
|
+
from auth import auto_refresh_if_needed
|
|
23
|
+
from db import Database
|
|
24
|
+
from governance import GovernanceManager
|
|
25
|
+
|
|
26
|
+
db = Database()
|
|
27
|
+
db.init()
|
|
28
|
+
gov = GovernanceManager(db)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def list_comments(media_id: str, limit: int = 50) -> None:
|
|
32
|
+
"""Lista comentários de um post."""
|
|
33
|
+
await auto_refresh_if_needed()
|
|
34
|
+
api = InstagramAPI()
|
|
35
|
+
result = await api.get_comments(media_id, limit=limit)
|
|
36
|
+
await api.close()
|
|
37
|
+
|
|
38
|
+
comments = result.get("data", [])
|
|
39
|
+
|
|
40
|
+
# Salvar no banco
|
|
41
|
+
account = db.get_active_account()
|
|
42
|
+
if account:
|
|
43
|
+
for c in comments:
|
|
44
|
+
db.upsert_comments([{
|
|
45
|
+
"account_id": account["id"],
|
|
46
|
+
"ig_comment_id": c["id"],
|
|
47
|
+
"ig_media_id": media_id,
|
|
48
|
+
"username": c.get("username", ""),
|
|
49
|
+
"text": c.get("text", ""),
|
|
50
|
+
"timestamp": c.get("timestamp", ""),
|
|
51
|
+
}])
|
|
52
|
+
|
|
53
|
+
print(json.dumps({"total": len(comments), "comments": comments}, indent=2, ensure_ascii=False))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def reply_to_comment(comment_id: str, text: str) -> None:
|
|
57
|
+
"""Responde a um comentário."""
|
|
58
|
+
await auto_refresh_if_needed()
|
|
59
|
+
|
|
60
|
+
if gov.requires_confirmation("reply_comment"):
|
|
61
|
+
result = gov.create_confirmation_request(
|
|
62
|
+
"reply_comment",
|
|
63
|
+
{"comment_id": comment_id, "reply_text": text},
|
|
64
|
+
)
|
|
65
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
api = InstagramAPI()
|
|
69
|
+
result = await api.reply_to_comment(comment_id, text)
|
|
70
|
+
await api.close()
|
|
71
|
+
|
|
72
|
+
gov.log_action(
|
|
73
|
+
"reply_comment",
|
|
74
|
+
params={"comment_id": comment_id, "text": text},
|
|
75
|
+
result=result,
|
|
76
|
+
account_id=db.get_active_account()["id"] if db.get_active_account() else None,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
print(json.dumps({"status": "replied", "result": result}, indent=2, ensure_ascii=False))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def delete_comment(comment_id: str) -> None:
|
|
83
|
+
"""Deleta um comentário."""
|
|
84
|
+
await auto_refresh_if_needed()
|
|
85
|
+
|
|
86
|
+
if gov.requires_confirmation("delete_comment"):
|
|
87
|
+
result = gov.create_confirmation_request(
|
|
88
|
+
"delete_comment",
|
|
89
|
+
{"comment_id": comment_id},
|
|
90
|
+
)
|
|
91
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
api = InstagramAPI()
|
|
95
|
+
result = await api.delete_comment(comment_id)
|
|
96
|
+
await api.close()
|
|
97
|
+
|
|
98
|
+
gov.log_action(
|
|
99
|
+
"delete_comment",
|
|
100
|
+
params={"comment_id": comment_id},
|
|
101
|
+
result=result,
|
|
102
|
+
account_id=db.get_active_account()["id"] if db.get_active_account() else None,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
print(json.dumps({"status": "deleted", "comment_id": comment_id}, indent=2, ensure_ascii=False))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
async def show_mentions(limit: int = 25) -> None:
|
|
109
|
+
"""Mostra menções recentes."""
|
|
110
|
+
await auto_refresh_if_needed()
|
|
111
|
+
api = InstagramAPI()
|
|
112
|
+
result = await api.get_mentions(limit=limit)
|
|
113
|
+
await api.close()
|
|
114
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def show_unreplied() -> None:
|
|
118
|
+
"""Mostra comentários não respondidos."""
|
|
119
|
+
account = db.get_active_account()
|
|
120
|
+
if not account:
|
|
121
|
+
print(json.dumps({"error": "Nenhuma conta configurada"}, indent=2))
|
|
122
|
+
return
|
|
123
|
+
comments = db.get_comments(account_id=account["id"], unreplied_only=True)
|
|
124
|
+
print(json.dumps({"total": len(comments), "unreplied": comments}, indent=2, ensure_ascii=False))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def main():
|
|
128
|
+
parser = argparse.ArgumentParser(description="Comentários do Instagram")
|
|
129
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
130
|
+
group.add_argument("--list", action="store_true", help="Listar comentários")
|
|
131
|
+
group.add_argument("--reply", action="store_true", help="Responder comentário")
|
|
132
|
+
group.add_argument("--delete", action="store_true", help="Deletar comentário")
|
|
133
|
+
group.add_argument("--mentions", action="store_true", help="Ver menções")
|
|
134
|
+
group.add_argument("--unreplied", action="store_true", help="Comentários não respondidos")
|
|
135
|
+
parser.add_argument("--media-id", help="ID da mídia")
|
|
136
|
+
parser.add_argument("--comment-id", help="ID do comentário")
|
|
137
|
+
parser.add_argument("--text", help="Texto da resposta")
|
|
138
|
+
parser.add_argument("--limit", type=int, default=50, help="Limite de resultados")
|
|
139
|
+
args = parser.parse_args()
|
|
140
|
+
|
|
141
|
+
if args.list:
|
|
142
|
+
if not args.media_id:
|
|
143
|
+
parser.error("--media-id é obrigatório com --list")
|
|
144
|
+
asyncio.run(list_comments(args.media_id, args.limit))
|
|
145
|
+
elif args.reply:
|
|
146
|
+
if not args.comment_id or not args.text:
|
|
147
|
+
parser.error("--comment-id e --text são obrigatórios com --reply")
|
|
148
|
+
asyncio.run(reply_to_comment(args.comment_id, args.text))
|
|
149
|
+
elif args.delete:
|
|
150
|
+
if not args.comment_id:
|
|
151
|
+
parser.error("--comment-id é obrigatório com --delete")
|
|
152
|
+
asyncio.run(delete_comment(args.comment_id))
|
|
153
|
+
elif args.mentions:
|
|
154
|
+
asyncio.run(show_mentions(args.limit))
|
|
155
|
+
elif args.unreplied:
|
|
156
|
+
asyncio.run(show_unreplied())
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
main()
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuração central da skill Instagram.
|
|
3
|
+
|
|
4
|
+
Todos os paths, constantes da API e specs de mídia ficam aqui.
|
|
5
|
+
Importado por todos os outros scripts.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict
|
|
11
|
+
|
|
12
|
+
# ── Paths ─────────────────────────────────────────────────────────────────────
|
|
13
|
+
ROOT_DIR = Path(__file__).parent.parent
|
|
14
|
+
SCRIPTS_DIR = ROOT_DIR / "scripts"
|
|
15
|
+
DATA_DIR = ROOT_DIR / "data"
|
|
16
|
+
EXPORTS_DIR = DATA_DIR / "exports"
|
|
17
|
+
STATIC_DIR = ROOT_DIR / "static"
|
|
18
|
+
DB_PATH = DATA_DIR / "instagram.db"
|
|
19
|
+
|
|
20
|
+
# Garante que diretórios existem
|
|
21
|
+
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
EXPORTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
|
|
24
|
+
# ── Instagram Graph API ───────────────────────────────────────────────────────
|
|
25
|
+
API_VERSION = "v21.0"
|
|
26
|
+
GRAPH_API_BASE = f"https://graph.facebook.com/{API_VERSION}"
|
|
27
|
+
GRAPH_IG_BASE = f"https://graph.instagram.com/{API_VERSION}"
|
|
28
|
+
|
|
29
|
+
# OAuth
|
|
30
|
+
OAUTH_REDIRECT_PORT = 8765
|
|
31
|
+
OAUTH_REDIRECT_URI = f"http://localhost:{OAUTH_REDIRECT_PORT}/callback"
|
|
32
|
+
OAUTH_AUTHORIZE_URL = f"https://www.facebook.com/{API_VERSION}/dialog/oauth"
|
|
33
|
+
OAUTH_TOKEN_URL = f"{GRAPH_API_BASE}/oauth/access_token"
|
|
34
|
+
|
|
35
|
+
# Scopes necessários
|
|
36
|
+
OAUTH_SCOPES = [
|
|
37
|
+
"instagram_basic",
|
|
38
|
+
"instagram_content_publish",
|
|
39
|
+
"instagram_manage_comments",
|
|
40
|
+
"instagram_manage_insights",
|
|
41
|
+
"instagram_manage_messages",
|
|
42
|
+
"pages_show_list",
|
|
43
|
+
"pages_read_engagement",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
# ── Rate Limits ───────────────────────────────────────────────────────────────
|
|
47
|
+
RATE_LIMIT_REQUESTS_PER_HOUR = 200
|
|
48
|
+
RATE_LIMIT_PUBLISHES_PER_DAY = 25
|
|
49
|
+
RATE_LIMIT_HASHTAGS_PER_WEEK = 30
|
|
50
|
+
RATE_LIMIT_DMS_PER_HOUR = 200
|
|
51
|
+
RATE_LIMIT_WARNING_THRESHOLD = 0.9 # alerta em 90% do limite
|
|
52
|
+
|
|
53
|
+
# ── Media Specs ───────────────────────────────────────────────────────────────
|
|
54
|
+
MEDIA_SPECS: Dict[str, Dict[str, Any]] = {
|
|
55
|
+
"PHOTO": {
|
|
56
|
+
"formats": ["JPEG"],
|
|
57
|
+
"min_width": 320,
|
|
58
|
+
"max_width": 1440,
|
|
59
|
+
"aspect_ratio_min": 4 / 5, # 0.8
|
|
60
|
+
"aspect_ratio_max": 1.91,
|
|
61
|
+
"max_size_mb": 8,
|
|
62
|
+
},
|
|
63
|
+
"VIDEO": {
|
|
64
|
+
"formats": ["MP4", "MOV"],
|
|
65
|
+
"codec": "H.264",
|
|
66
|
+
"audio": "AAC",
|
|
67
|
+
"min_duration_sec": 3,
|
|
68
|
+
"max_duration_sec": 60,
|
|
69
|
+
"max_size_mb": 100,
|
|
70
|
+
},
|
|
71
|
+
"REEL": {
|
|
72
|
+
"formats": ["MP4", "MOV"],
|
|
73
|
+
"codec": "H.264",
|
|
74
|
+
"audio": "AAC",
|
|
75
|
+
"recommended_width": 1080,
|
|
76
|
+
"recommended_height": 1920,
|
|
77
|
+
"aspect_ratio": "9:16",
|
|
78
|
+
"min_duration_sec": 3,
|
|
79
|
+
"max_duration_sec": 90,
|
|
80
|
+
"max_size_mb": 1024,
|
|
81
|
+
},
|
|
82
|
+
"STORY": {
|
|
83
|
+
"recommended_width": 1080,
|
|
84
|
+
"recommended_height": 1920,
|
|
85
|
+
"aspect_ratio": "9:16",
|
|
86
|
+
},
|
|
87
|
+
"CAROUSEL": {
|
|
88
|
+
"min_items": 2,
|
|
89
|
+
"max_items": 10,
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# ── Imgur (upload de mídia local) ─────────────────────────────────────────────
|
|
94
|
+
IMGUR_UPLOAD_URL = "https://api.imgur.com/3/image"
|
|
95
|
+
IMGUR_CLIENT_ID = "546c25a59c58ad7" # anonymous uploads (público)
|
|
96
|
+
|
|
97
|
+
# ── Retry / Backoff ──────────────────────────────────────────────────────────
|
|
98
|
+
MAX_RETRIES = 3
|
|
99
|
+
RETRY_BACKOFF_BASE = 2 # segundos: 2^1, 2^2, 2^3
|
|
100
|
+
REQUEST_TIMEOUT = 30.0
|
|
101
|
+
|
|
102
|
+
# ── Governance ────────────────────────────────────────────────────────────────
|
|
103
|
+
# Categorias de ação para confirmação
|
|
104
|
+
ACTION_CATEGORIES = {
|
|
105
|
+
"READ": [], # sem confirmação
|
|
106
|
+
"ENGAGE": ["reply_comment", "hide_comment", "unhide_comment"],
|
|
107
|
+
"PUBLISH": ["publish_photo", "publish_video", "publish_reel",
|
|
108
|
+
"publish_story", "publish_carousel", "schedule_post"],
|
|
109
|
+
"DELETE": ["delete_comment"],
|
|
110
|
+
"MESSAGE": ["send_dm"],
|
|
111
|
+
}
|