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,401 @@
|
|
|
1
|
+
"""WebSearch module for last30days skill.
|
|
2
|
+
|
|
3
|
+
NOTE: WebSearch uses Claude's built-in WebSearch tool, which runs INSIDE Claude Code.
|
|
4
|
+
Unlike Reddit/X which use external APIs, WebSearch results are obtained by Claude
|
|
5
|
+
directly and passed to this module for normalization and scoring.
|
|
6
|
+
|
|
7
|
+
The typical flow is:
|
|
8
|
+
1. Claude invokes WebSearch tool with the topic
|
|
9
|
+
2. Claude passes results to parse_websearch_results()
|
|
10
|
+
3. Results are normalized into WebSearchItem objects
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
from datetime import datetime, timedelta
|
|
15
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
16
|
+
from urllib.parse import urlparse
|
|
17
|
+
|
|
18
|
+
from . import schema
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Month name mappings for date parsing
|
|
22
|
+
MONTH_MAP = {
|
|
23
|
+
"jan": 1, "january": 1,
|
|
24
|
+
"feb": 2, "february": 2,
|
|
25
|
+
"mar": 3, "march": 3,
|
|
26
|
+
"apr": 4, "april": 4,
|
|
27
|
+
"may": 5,
|
|
28
|
+
"jun": 6, "june": 6,
|
|
29
|
+
"jul": 7, "july": 7,
|
|
30
|
+
"aug": 8, "august": 8,
|
|
31
|
+
"sep": 9, "sept": 9, "september": 9,
|
|
32
|
+
"oct": 10, "october": 10,
|
|
33
|
+
"nov": 11, "november": 11,
|
|
34
|
+
"dec": 12, "december": 12,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def extract_date_from_url(url: str) -> Optional[str]:
|
|
39
|
+
"""Try to extract a date from URL path.
|
|
40
|
+
|
|
41
|
+
Many sites embed dates in URLs like:
|
|
42
|
+
- /2026/01/24/article-title
|
|
43
|
+
- /2026-01-24/article
|
|
44
|
+
- /blog/20260124/title
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
url: URL to parse
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Date string in YYYY-MM-DD format, or None
|
|
51
|
+
"""
|
|
52
|
+
# Pattern 1: /YYYY/MM/DD/ (most common)
|
|
53
|
+
match = re.search(r'/(\d{4})/(\d{2})/(\d{2})/', url)
|
|
54
|
+
if match:
|
|
55
|
+
year, month, day = match.groups()
|
|
56
|
+
if 2020 <= int(year) <= 2030 and 1 <= int(month) <= 12 and 1 <= int(day) <= 31:
|
|
57
|
+
return f"{year}-{month}-{day}"
|
|
58
|
+
|
|
59
|
+
# Pattern 2: /YYYY-MM-DD/ or /YYYY-MM-DD-
|
|
60
|
+
match = re.search(r'/(\d{4})-(\d{2})-(\d{2})[-/]', url)
|
|
61
|
+
if match:
|
|
62
|
+
year, month, day = match.groups()
|
|
63
|
+
if 2020 <= int(year) <= 2030 and 1 <= int(month) <= 12 and 1 <= int(day) <= 31:
|
|
64
|
+
return f"{year}-{month}-{day}"
|
|
65
|
+
|
|
66
|
+
# Pattern 3: /YYYYMMDD/ (compact)
|
|
67
|
+
match = re.search(r'/(\d{4})(\d{2})(\d{2})/', url)
|
|
68
|
+
if match:
|
|
69
|
+
year, month, day = match.groups()
|
|
70
|
+
if 2020 <= int(year) <= 2030 and 1 <= int(month) <= 12 and 1 <= int(day) <= 31:
|
|
71
|
+
return f"{year}-{month}-{day}"
|
|
72
|
+
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def extract_date_from_snippet(text: str) -> Optional[str]:
|
|
77
|
+
"""Try to extract a date from text snippet or title.
|
|
78
|
+
|
|
79
|
+
Looks for patterns like:
|
|
80
|
+
- January 24, 2026 or Jan 24, 2026
|
|
81
|
+
- 24 January 2026
|
|
82
|
+
- 2026-01-24
|
|
83
|
+
- "3 days ago", "yesterday", "last week"
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
text: Text to parse
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Date string in YYYY-MM-DD format, or None
|
|
90
|
+
"""
|
|
91
|
+
if not text:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
text_lower = text.lower()
|
|
95
|
+
|
|
96
|
+
# Pattern 1: Month DD, YYYY (e.g., "January 24, 2026")
|
|
97
|
+
match = re.search(
|
|
98
|
+
r'\b(jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|'
|
|
99
|
+
r'jul(?:y)?|aug(?:ust)?|sep(?:t(?:ember)?)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)'
|
|
100
|
+
r'\s+(\d{1,2})(?:st|nd|rd|th)?,?\s*(\d{4})\b',
|
|
101
|
+
text_lower
|
|
102
|
+
)
|
|
103
|
+
if match:
|
|
104
|
+
month_str, day, year = match.groups()
|
|
105
|
+
month = MONTH_MAP.get(month_str[:3])
|
|
106
|
+
if month and 2020 <= int(year) <= 2030 and 1 <= int(day) <= 31:
|
|
107
|
+
return f"{year}-{month:02d}-{int(day):02d}"
|
|
108
|
+
|
|
109
|
+
# Pattern 2: DD Month YYYY (e.g., "24 January 2026")
|
|
110
|
+
match = re.search(
|
|
111
|
+
r'\b(\d{1,2})(?:st|nd|rd|th)?\s+'
|
|
112
|
+
r'(jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|'
|
|
113
|
+
r'jul(?:y)?|aug(?:ust)?|sep(?:t(?:ember)?)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)'
|
|
114
|
+
r'\s+(\d{4})\b',
|
|
115
|
+
text_lower
|
|
116
|
+
)
|
|
117
|
+
if match:
|
|
118
|
+
day, month_str, year = match.groups()
|
|
119
|
+
month = MONTH_MAP.get(month_str[:3])
|
|
120
|
+
if month and 2020 <= int(year) <= 2030 and 1 <= int(day) <= 31:
|
|
121
|
+
return f"{year}-{month:02d}-{int(day):02d}"
|
|
122
|
+
|
|
123
|
+
# Pattern 3: YYYY-MM-DD (ISO format)
|
|
124
|
+
match = re.search(r'\b(\d{4})-(\d{2})-(\d{2})\b', text)
|
|
125
|
+
if match:
|
|
126
|
+
year, month, day = match.groups()
|
|
127
|
+
if 2020 <= int(year) <= 2030 and 1 <= int(month) <= 12 and 1 <= int(day) <= 31:
|
|
128
|
+
return f"{year}-{month}-{day}"
|
|
129
|
+
|
|
130
|
+
# Pattern 4: Relative dates ("3 days ago", "yesterday", etc.)
|
|
131
|
+
today = datetime.now()
|
|
132
|
+
|
|
133
|
+
if "yesterday" in text_lower:
|
|
134
|
+
date = today - timedelta(days=1)
|
|
135
|
+
return date.strftime("%Y-%m-%d")
|
|
136
|
+
|
|
137
|
+
if "today" in text_lower:
|
|
138
|
+
return today.strftime("%Y-%m-%d")
|
|
139
|
+
|
|
140
|
+
# "N days ago"
|
|
141
|
+
match = re.search(r'\b(\d+)\s*days?\s*ago\b', text_lower)
|
|
142
|
+
if match:
|
|
143
|
+
days = int(match.group(1))
|
|
144
|
+
if days <= 60: # Reasonable range
|
|
145
|
+
date = today - timedelta(days=days)
|
|
146
|
+
return date.strftime("%Y-%m-%d")
|
|
147
|
+
|
|
148
|
+
# "N hours ago" -> today
|
|
149
|
+
match = re.search(r'\b(\d+)\s*hours?\s*ago\b', text_lower)
|
|
150
|
+
if match:
|
|
151
|
+
return today.strftime("%Y-%m-%d")
|
|
152
|
+
|
|
153
|
+
# "last week" -> ~7 days ago
|
|
154
|
+
if "last week" in text_lower:
|
|
155
|
+
date = today - timedelta(days=7)
|
|
156
|
+
return date.strftime("%Y-%m-%d")
|
|
157
|
+
|
|
158
|
+
# "this week" -> ~3 days ago (middle of week)
|
|
159
|
+
if "this week" in text_lower:
|
|
160
|
+
date = today - timedelta(days=3)
|
|
161
|
+
return date.strftime("%Y-%m-%d")
|
|
162
|
+
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def extract_date_signals(
|
|
167
|
+
url: str,
|
|
168
|
+
snippet: str,
|
|
169
|
+
title: str,
|
|
170
|
+
) -> Tuple[Optional[str], str]:
|
|
171
|
+
"""Extract date from any available signal.
|
|
172
|
+
|
|
173
|
+
Tries URL first (most reliable), then snippet, then title.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
url: Page URL
|
|
177
|
+
snippet: Page snippet/description
|
|
178
|
+
title: Page title
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Tuple of (date_string, confidence)
|
|
182
|
+
- date from URL: 'high' confidence
|
|
183
|
+
- date from snippet/title: 'med' confidence
|
|
184
|
+
- no date found: None, 'low' confidence
|
|
185
|
+
"""
|
|
186
|
+
# Try URL first (most reliable)
|
|
187
|
+
url_date = extract_date_from_url(url)
|
|
188
|
+
if url_date:
|
|
189
|
+
return url_date, "high"
|
|
190
|
+
|
|
191
|
+
# Try snippet
|
|
192
|
+
snippet_date = extract_date_from_snippet(snippet)
|
|
193
|
+
if snippet_date:
|
|
194
|
+
return snippet_date, "med"
|
|
195
|
+
|
|
196
|
+
# Try title
|
|
197
|
+
title_date = extract_date_from_snippet(title)
|
|
198
|
+
if title_date:
|
|
199
|
+
return title_date, "med"
|
|
200
|
+
|
|
201
|
+
return None, "low"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# Domains to exclude (Reddit and X are handled separately)
|
|
205
|
+
EXCLUDED_DOMAINS = {
|
|
206
|
+
"reddit.com",
|
|
207
|
+
"www.reddit.com",
|
|
208
|
+
"old.reddit.com",
|
|
209
|
+
"twitter.com",
|
|
210
|
+
"www.twitter.com",
|
|
211
|
+
"x.com",
|
|
212
|
+
"www.x.com",
|
|
213
|
+
"mobile.twitter.com",
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def extract_domain(url: str) -> str:
|
|
218
|
+
"""Extract the domain from a URL.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
url: Full URL
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Domain string (e.g., "medium.com")
|
|
225
|
+
"""
|
|
226
|
+
try:
|
|
227
|
+
parsed = urlparse(url)
|
|
228
|
+
domain = parsed.netloc.lower()
|
|
229
|
+
# Remove www. prefix for cleaner display
|
|
230
|
+
if domain.startswith("www."):
|
|
231
|
+
domain = domain[4:]
|
|
232
|
+
return domain
|
|
233
|
+
except Exception:
|
|
234
|
+
return ""
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def is_excluded_domain(url: str) -> bool:
|
|
238
|
+
"""Check if URL is from an excluded domain (Reddit/X).
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
url: URL to check
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
True if URL should be excluded
|
|
245
|
+
"""
|
|
246
|
+
try:
|
|
247
|
+
parsed = urlparse(url)
|
|
248
|
+
domain = parsed.netloc.lower()
|
|
249
|
+
return domain in EXCLUDED_DOMAINS
|
|
250
|
+
except Exception:
|
|
251
|
+
return False
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def parse_websearch_results(
|
|
255
|
+
results: List[Dict[str, Any]],
|
|
256
|
+
topic: str,
|
|
257
|
+
from_date: str = "",
|
|
258
|
+
to_date: str = "",
|
|
259
|
+
) -> List[Dict[str, Any]]:
|
|
260
|
+
"""Parse WebSearch results into normalized format.
|
|
261
|
+
|
|
262
|
+
This function expects results from Claude's WebSearch tool.
|
|
263
|
+
Each result should have: title, url, snippet, and optionally date/relevance.
|
|
264
|
+
|
|
265
|
+
Uses "Date Detective" approach:
|
|
266
|
+
1. Extract dates from URLs (high confidence)
|
|
267
|
+
2. Extract dates from snippets/titles (med confidence)
|
|
268
|
+
3. Hard filter: exclude items with verified old dates
|
|
269
|
+
4. Keep items with no date signals (with low confidence penalty)
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
results: List of WebSearch result dicts
|
|
273
|
+
topic: Original search topic (for context)
|
|
274
|
+
from_date: Start date for filtering (YYYY-MM-DD)
|
|
275
|
+
to_date: End date for filtering (YYYY-MM-DD)
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
List of normalized item dicts ready for WebSearchItem creation
|
|
279
|
+
"""
|
|
280
|
+
items = []
|
|
281
|
+
|
|
282
|
+
for i, result in enumerate(results):
|
|
283
|
+
if not isinstance(result, dict):
|
|
284
|
+
continue
|
|
285
|
+
|
|
286
|
+
url = result.get("url", "")
|
|
287
|
+
if not url:
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
# Skip Reddit/X URLs (handled separately)
|
|
291
|
+
if is_excluded_domain(url):
|
|
292
|
+
continue
|
|
293
|
+
|
|
294
|
+
title = str(result.get("title", "")).strip()
|
|
295
|
+
snippet = str(result.get("snippet", result.get("description", ""))).strip()
|
|
296
|
+
|
|
297
|
+
if not title and not snippet:
|
|
298
|
+
continue
|
|
299
|
+
|
|
300
|
+
# Use Date Detective to extract date signals
|
|
301
|
+
date = result.get("date") # Use provided date if available
|
|
302
|
+
date_confidence = "low"
|
|
303
|
+
|
|
304
|
+
if date and re.match(r'^\d{4}-\d{2}-\d{2}$', str(date)):
|
|
305
|
+
# Provided date is valid
|
|
306
|
+
date_confidence = "med"
|
|
307
|
+
else:
|
|
308
|
+
# Try to extract date from URL/snippet/title
|
|
309
|
+
extracted_date, confidence = extract_date_signals(url, snippet, title)
|
|
310
|
+
if extracted_date:
|
|
311
|
+
date = extracted_date
|
|
312
|
+
date_confidence = confidence
|
|
313
|
+
|
|
314
|
+
# Hard filter: if we found a date and it's too old, skip
|
|
315
|
+
if date and from_date and date < from_date:
|
|
316
|
+
continue # DROP - verified old content
|
|
317
|
+
|
|
318
|
+
# Hard filter: if date is in the future, skip (parsing error)
|
|
319
|
+
if date and to_date and date > to_date:
|
|
320
|
+
continue # DROP - future date
|
|
321
|
+
|
|
322
|
+
# Get relevance if provided, default to 0.5
|
|
323
|
+
relevance = result.get("relevance", 0.5)
|
|
324
|
+
try:
|
|
325
|
+
relevance = min(1.0, max(0.0, float(relevance)))
|
|
326
|
+
except (TypeError, ValueError):
|
|
327
|
+
relevance = 0.5
|
|
328
|
+
|
|
329
|
+
item = {
|
|
330
|
+
"id": f"W{i+1}",
|
|
331
|
+
"title": title[:200], # Truncate long titles
|
|
332
|
+
"url": url,
|
|
333
|
+
"source_domain": extract_domain(url),
|
|
334
|
+
"snippet": snippet[:500], # Truncate long snippets
|
|
335
|
+
"date": date,
|
|
336
|
+
"date_confidence": date_confidence,
|
|
337
|
+
"relevance": relevance,
|
|
338
|
+
"why_relevant": str(result.get("why_relevant", "")).strip(),
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
items.append(item)
|
|
342
|
+
|
|
343
|
+
return items
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def normalize_websearch_items(
|
|
347
|
+
items: List[Dict[str, Any]],
|
|
348
|
+
from_date: str,
|
|
349
|
+
to_date: str,
|
|
350
|
+
) -> List[schema.WebSearchItem]:
|
|
351
|
+
"""Convert parsed dicts to WebSearchItem objects.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
items: List of parsed item dicts
|
|
355
|
+
from_date: Start of date range (YYYY-MM-DD)
|
|
356
|
+
to_date: End of date range (YYYY-MM-DD)
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
List of WebSearchItem objects
|
|
360
|
+
"""
|
|
361
|
+
result = []
|
|
362
|
+
|
|
363
|
+
for item in items:
|
|
364
|
+
web_item = schema.WebSearchItem(
|
|
365
|
+
id=item["id"],
|
|
366
|
+
title=item["title"],
|
|
367
|
+
url=item["url"],
|
|
368
|
+
source_domain=item["source_domain"],
|
|
369
|
+
snippet=item["snippet"],
|
|
370
|
+
date=item.get("date"),
|
|
371
|
+
date_confidence=item.get("date_confidence", "low"),
|
|
372
|
+
relevance=item.get("relevance", 0.5),
|
|
373
|
+
why_relevant=item.get("why_relevant", ""),
|
|
374
|
+
)
|
|
375
|
+
result.append(web_item)
|
|
376
|
+
|
|
377
|
+
return result
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def dedupe_websearch(items: List[schema.WebSearchItem]) -> List[schema.WebSearchItem]:
|
|
381
|
+
"""Remove duplicate WebSearch items.
|
|
382
|
+
|
|
383
|
+
Deduplication is based on URL.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
items: List of WebSearchItem objects
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
Deduplicated list
|
|
390
|
+
"""
|
|
391
|
+
seen_urls = set()
|
|
392
|
+
result = []
|
|
393
|
+
|
|
394
|
+
for item in items:
|
|
395
|
+
# Normalize URL for comparison
|
|
396
|
+
url_key = item.url.lower().rstrip("/")
|
|
397
|
+
if url_key not in seen_urls:
|
|
398
|
+
seen_urls.add(url_key)
|
|
399
|
+
result.append(item)
|
|
400
|
+
|
|
401
|
+
return result
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""xAI API client for X (Twitter) discovery."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from . import http
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _log_error(msg: str):
|
|
12
|
+
"""Log error to stderr."""
|
|
13
|
+
sys.stderr.write(f"[X ERROR] {msg}\n")
|
|
14
|
+
sys.stderr.flush()
|
|
15
|
+
|
|
16
|
+
# xAI uses responses endpoint with Agent Tools API
|
|
17
|
+
XAI_RESPONSES_URL = "https://api.x.ai/v1/responses"
|
|
18
|
+
|
|
19
|
+
# Depth configurations: (min, max) posts to request
|
|
20
|
+
DEPTH_CONFIG = {
|
|
21
|
+
"quick": (8, 12),
|
|
22
|
+
"default": (20, 30),
|
|
23
|
+
"deep": (40, 60),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
X_SEARCH_PROMPT = """You have access to real-time X (Twitter) data. Search for posts about: {topic}
|
|
27
|
+
|
|
28
|
+
Focus on posts from {from_date} to {to_date}. Find {min_items}-{max_items} high-quality, relevant posts.
|
|
29
|
+
|
|
30
|
+
IMPORTANT: Return ONLY valid JSON in this exact format, no other text:
|
|
31
|
+
{{
|
|
32
|
+
"items": [
|
|
33
|
+
{{
|
|
34
|
+
"text": "Post text content (truncated if long)",
|
|
35
|
+
"url": "https://x.com/user/status/...",
|
|
36
|
+
"author_handle": "username",
|
|
37
|
+
"date": "YYYY-MM-DD or null if unknown",
|
|
38
|
+
"engagement": {{
|
|
39
|
+
"likes": 100,
|
|
40
|
+
"reposts": 25,
|
|
41
|
+
"replies": 15,
|
|
42
|
+
"quotes": 5
|
|
43
|
+
}},
|
|
44
|
+
"why_relevant": "Brief explanation of relevance",
|
|
45
|
+
"relevance": 0.85
|
|
46
|
+
}}
|
|
47
|
+
]
|
|
48
|
+
}}
|
|
49
|
+
|
|
50
|
+
Rules:
|
|
51
|
+
- relevance is 0.0 to 1.0 (1.0 = highly relevant)
|
|
52
|
+
- date must be YYYY-MM-DD format or null
|
|
53
|
+
- engagement can be null if unknown
|
|
54
|
+
- Include diverse voices/accounts if applicable
|
|
55
|
+
- Prefer posts with substantive content, not just links"""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def search_x(
|
|
59
|
+
api_key: str,
|
|
60
|
+
model: str,
|
|
61
|
+
topic: str,
|
|
62
|
+
from_date: str,
|
|
63
|
+
to_date: str,
|
|
64
|
+
depth: str = "default",
|
|
65
|
+
mock_response: Optional[Dict] = None,
|
|
66
|
+
) -> Dict[str, Any]:
|
|
67
|
+
"""Search X for relevant posts using xAI API with live search.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
api_key: xAI API key
|
|
71
|
+
model: Model to use
|
|
72
|
+
topic: Search topic
|
|
73
|
+
from_date: Start date (YYYY-MM-DD)
|
|
74
|
+
to_date: End date (YYYY-MM-DD)
|
|
75
|
+
depth: Research depth - "quick", "default", or "deep"
|
|
76
|
+
mock_response: Mock response for testing
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Raw API response
|
|
80
|
+
"""
|
|
81
|
+
if mock_response is not None:
|
|
82
|
+
return mock_response
|
|
83
|
+
|
|
84
|
+
min_items, max_items = DEPTH_CONFIG.get(depth, DEPTH_CONFIG["default"])
|
|
85
|
+
|
|
86
|
+
headers = {
|
|
87
|
+
"Authorization": f"Bearer {api_key}",
|
|
88
|
+
"Content-Type": "application/json",
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# Adjust timeout based on depth (generous for API response time)
|
|
92
|
+
timeout = 90 if depth == "quick" else 120 if depth == "default" else 180
|
|
93
|
+
|
|
94
|
+
# Use Agent Tools API with x_search tool
|
|
95
|
+
payload = {
|
|
96
|
+
"model": model,
|
|
97
|
+
"tools": [
|
|
98
|
+
{"type": "x_search"}
|
|
99
|
+
],
|
|
100
|
+
"input": [
|
|
101
|
+
{
|
|
102
|
+
"role": "user",
|
|
103
|
+
"content": X_SEARCH_PROMPT.format(
|
|
104
|
+
topic=topic,
|
|
105
|
+
from_date=from_date,
|
|
106
|
+
to_date=to_date,
|
|
107
|
+
min_items=min_items,
|
|
108
|
+
max_items=max_items,
|
|
109
|
+
),
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return http.post(XAI_RESPONSES_URL, payload, headers=headers, timeout=timeout)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def parse_x_response(response: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
118
|
+
"""Parse xAI response to extract X items.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
response: Raw API response
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
List of item dicts
|
|
125
|
+
"""
|
|
126
|
+
items = []
|
|
127
|
+
|
|
128
|
+
# Check for API errors first
|
|
129
|
+
if "error" in response and response["error"]:
|
|
130
|
+
error = response["error"]
|
|
131
|
+
err_msg = error.get("message", str(error)) if isinstance(error, dict) else str(error)
|
|
132
|
+
_log_error(f"xAI API error: {err_msg}")
|
|
133
|
+
if http.DEBUG:
|
|
134
|
+
_log_error(f"Full error response: {json.dumps(response, indent=2)[:1000]}")
|
|
135
|
+
return items
|
|
136
|
+
|
|
137
|
+
# Try to find the output text
|
|
138
|
+
output_text = ""
|
|
139
|
+
if "output" in response:
|
|
140
|
+
output = response["output"]
|
|
141
|
+
if isinstance(output, str):
|
|
142
|
+
output_text = output
|
|
143
|
+
elif isinstance(output, list):
|
|
144
|
+
for item in output:
|
|
145
|
+
if isinstance(item, dict):
|
|
146
|
+
if item.get("type") == "message":
|
|
147
|
+
content = item.get("content", [])
|
|
148
|
+
for c in content:
|
|
149
|
+
if isinstance(c, dict) and c.get("type") == "output_text":
|
|
150
|
+
output_text = c.get("text", "")
|
|
151
|
+
break
|
|
152
|
+
elif "text" in item:
|
|
153
|
+
output_text = item["text"]
|
|
154
|
+
elif isinstance(item, str):
|
|
155
|
+
output_text = item
|
|
156
|
+
if output_text:
|
|
157
|
+
break
|
|
158
|
+
|
|
159
|
+
# Also check for choices (older format)
|
|
160
|
+
if not output_text and "choices" in response:
|
|
161
|
+
for choice in response["choices"]:
|
|
162
|
+
if "message" in choice:
|
|
163
|
+
output_text = choice["message"].get("content", "")
|
|
164
|
+
break
|
|
165
|
+
|
|
166
|
+
if not output_text:
|
|
167
|
+
return items
|
|
168
|
+
|
|
169
|
+
# Extract JSON from the response
|
|
170
|
+
json_match = re.search(r'\{[\s\S]*"items"[\s\S]*\}', output_text)
|
|
171
|
+
if json_match:
|
|
172
|
+
try:
|
|
173
|
+
data = json.loads(json_match.group())
|
|
174
|
+
items = data.get("items", [])
|
|
175
|
+
except json.JSONDecodeError:
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
# Validate and clean items
|
|
179
|
+
clean_items = []
|
|
180
|
+
for i, item in enumerate(items):
|
|
181
|
+
if not isinstance(item, dict):
|
|
182
|
+
continue
|
|
183
|
+
|
|
184
|
+
url = item.get("url", "")
|
|
185
|
+
if not url:
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
# Parse engagement
|
|
189
|
+
engagement = None
|
|
190
|
+
eng_raw = item.get("engagement")
|
|
191
|
+
if isinstance(eng_raw, dict):
|
|
192
|
+
engagement = {
|
|
193
|
+
"likes": int(eng_raw.get("likes", 0)) if eng_raw.get("likes") else None,
|
|
194
|
+
"reposts": int(eng_raw.get("reposts", 0)) if eng_raw.get("reposts") else None,
|
|
195
|
+
"replies": int(eng_raw.get("replies", 0)) if eng_raw.get("replies") else None,
|
|
196
|
+
"quotes": int(eng_raw.get("quotes", 0)) if eng_raw.get("quotes") else None,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
clean_item = {
|
|
200
|
+
"id": f"X{i+1}",
|
|
201
|
+
"text": str(item.get("text", "")).strip()[:500], # Truncate long text
|
|
202
|
+
"url": url,
|
|
203
|
+
"author_handle": str(item.get("author_handle", "")).strip().lstrip("@"),
|
|
204
|
+
"date": item.get("date"),
|
|
205
|
+
"engagement": engagement,
|
|
206
|
+
"why_relevant": str(item.get("why_relevant", "")).strip(),
|
|
207
|
+
"relevance": min(1.0, max(0.0, float(item.get("relevance", 0.5)))),
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# Validate date format
|
|
211
|
+
if clean_item["date"]:
|
|
212
|
+
if not re.match(r'^\d{4}-\d{2}-\d{2}$', str(clean_item["date"])):
|
|
213
|
+
clean_item["date"] = None
|
|
214
|
+
|
|
215
|
+
clean_items.append(clean_item)
|
|
216
|
+
|
|
217
|
+
return clean_items
|