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,124 @@
|
|
|
1
|
+
"""Date utilities for last30days skill."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timedelta, timezone
|
|
4
|
+
from typing import Optional, Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_date_range(days: int = 30) -> Tuple[str, str]:
|
|
8
|
+
"""Get the date range for the last N days.
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
Tuple of (from_date, to_date) as YYYY-MM-DD strings
|
|
12
|
+
"""
|
|
13
|
+
today = datetime.now(timezone.utc).date()
|
|
14
|
+
from_date = today - timedelta(days=days)
|
|
15
|
+
return from_date.isoformat(), today.isoformat()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def parse_date(date_str: Optional[str]) -> Optional[datetime]:
|
|
19
|
+
"""Parse a date string in various formats.
|
|
20
|
+
|
|
21
|
+
Supports: YYYY-MM-DD, ISO 8601, Unix timestamp
|
|
22
|
+
"""
|
|
23
|
+
if not date_str:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
# Try Unix timestamp (from Reddit)
|
|
27
|
+
try:
|
|
28
|
+
ts = float(date_str)
|
|
29
|
+
return datetime.fromtimestamp(ts, tz=timezone.utc)
|
|
30
|
+
except (ValueError, TypeError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
# Try ISO formats
|
|
34
|
+
formats = [
|
|
35
|
+
"%Y-%m-%d",
|
|
36
|
+
"%Y-%m-%dT%H:%M:%S",
|
|
37
|
+
"%Y-%m-%dT%H:%M:%SZ",
|
|
38
|
+
"%Y-%m-%dT%H:%M:%S%z",
|
|
39
|
+
"%Y-%m-%dT%H:%M:%S.%f%z",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
for fmt in formats:
|
|
43
|
+
try:
|
|
44
|
+
return datetime.strptime(date_str, fmt).replace(tzinfo=timezone.utc)
|
|
45
|
+
except ValueError:
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def timestamp_to_date(ts: Optional[float]) -> Optional[str]:
|
|
52
|
+
"""Convert Unix timestamp to YYYY-MM-DD string."""
|
|
53
|
+
if ts is None:
|
|
54
|
+
return None
|
|
55
|
+
try:
|
|
56
|
+
dt = datetime.fromtimestamp(ts, tz=timezone.utc)
|
|
57
|
+
return dt.date().isoformat()
|
|
58
|
+
except (ValueError, TypeError, OSError):
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_date_confidence(date_str: Optional[str], from_date: str, to_date: str) -> str:
|
|
63
|
+
"""Determine confidence level for a date.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
date_str: The date to check (YYYY-MM-DD or None)
|
|
67
|
+
from_date: Start of valid range (YYYY-MM-DD)
|
|
68
|
+
to_date: End of valid range (YYYY-MM-DD)
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
'high', 'med', or 'low'
|
|
72
|
+
"""
|
|
73
|
+
if not date_str:
|
|
74
|
+
return 'low'
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
dt = datetime.strptime(date_str, "%Y-%m-%d").date()
|
|
78
|
+
start = datetime.strptime(from_date, "%Y-%m-%d").date()
|
|
79
|
+
end = datetime.strptime(to_date, "%Y-%m-%d").date()
|
|
80
|
+
|
|
81
|
+
if start <= dt <= end:
|
|
82
|
+
return 'high'
|
|
83
|
+
elif dt < start:
|
|
84
|
+
# Older than range
|
|
85
|
+
return 'low'
|
|
86
|
+
else:
|
|
87
|
+
# Future date (suspicious)
|
|
88
|
+
return 'low'
|
|
89
|
+
except ValueError:
|
|
90
|
+
return 'low'
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def days_ago(date_str: Optional[str]) -> Optional[int]:
|
|
94
|
+
"""Calculate how many days ago a date is.
|
|
95
|
+
|
|
96
|
+
Returns None if date is invalid or missing.
|
|
97
|
+
"""
|
|
98
|
+
if not date_str:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
dt = datetime.strptime(date_str, "%Y-%m-%d").date()
|
|
103
|
+
today = datetime.now(timezone.utc).date()
|
|
104
|
+
delta = today - dt
|
|
105
|
+
return delta.days
|
|
106
|
+
except ValueError:
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def recency_score(date_str: Optional[str], max_days: int = 30) -> int:
|
|
111
|
+
"""Calculate recency score (0-100).
|
|
112
|
+
|
|
113
|
+
0 days ago = 100, max_days ago = 0, clamped.
|
|
114
|
+
"""
|
|
115
|
+
age = days_ago(date_str)
|
|
116
|
+
if age is None:
|
|
117
|
+
return 0 # Unknown date gets worst score
|
|
118
|
+
|
|
119
|
+
if age < 0:
|
|
120
|
+
return 100 # Future date (treat as today)
|
|
121
|
+
if age >= max_days:
|
|
122
|
+
return 0
|
|
123
|
+
|
|
124
|
+
return int(100 * (1 - age / max_days))
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Near-duplicate detection for last30days skill."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import List, Set, Tuple, Union
|
|
5
|
+
|
|
6
|
+
from . import schema
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def normalize_text(text: str) -> str:
|
|
10
|
+
"""Normalize text for comparison.
|
|
11
|
+
|
|
12
|
+
- Lowercase
|
|
13
|
+
- Remove punctuation
|
|
14
|
+
- Collapse whitespace
|
|
15
|
+
"""
|
|
16
|
+
text = text.lower()
|
|
17
|
+
text = re.sub(r'[^\w\s]', ' ', text)
|
|
18
|
+
text = re.sub(r'\s+', ' ', text)
|
|
19
|
+
return text.strip()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_ngrams(text: str, n: int = 3) -> Set[str]:
|
|
23
|
+
"""Get character n-grams from text."""
|
|
24
|
+
text = normalize_text(text)
|
|
25
|
+
if len(text) < n:
|
|
26
|
+
return {text}
|
|
27
|
+
return {text[i:i+n] for i in range(len(text) - n + 1)}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def jaccard_similarity(set1: Set[str], set2: Set[str]) -> float:
|
|
31
|
+
"""Compute Jaccard similarity between two sets."""
|
|
32
|
+
if not set1 or not set2:
|
|
33
|
+
return 0.0
|
|
34
|
+
intersection = len(set1 & set2)
|
|
35
|
+
union = len(set1 | set2)
|
|
36
|
+
return intersection / union if union > 0 else 0.0
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_item_text(item: Union[schema.RedditItem, schema.XItem]) -> str:
|
|
40
|
+
"""Get comparable text from an item."""
|
|
41
|
+
if isinstance(item, schema.RedditItem):
|
|
42
|
+
return item.title
|
|
43
|
+
else:
|
|
44
|
+
return item.text
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def find_duplicates(
|
|
48
|
+
items: List[Union[schema.RedditItem, schema.XItem]],
|
|
49
|
+
threshold: float = 0.7,
|
|
50
|
+
) -> List[Tuple[int, int]]:
|
|
51
|
+
"""Find near-duplicate pairs in items.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
items: List of items to check
|
|
55
|
+
threshold: Similarity threshold (0-1)
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
List of (i, j) index pairs where i < j and items are similar
|
|
59
|
+
"""
|
|
60
|
+
duplicates = []
|
|
61
|
+
|
|
62
|
+
# Pre-compute n-grams
|
|
63
|
+
ngrams = [get_ngrams(get_item_text(item)) for item in items]
|
|
64
|
+
|
|
65
|
+
for i in range(len(items)):
|
|
66
|
+
for j in range(i + 1, len(items)):
|
|
67
|
+
similarity = jaccard_similarity(ngrams[i], ngrams[j])
|
|
68
|
+
if similarity >= threshold:
|
|
69
|
+
duplicates.append((i, j))
|
|
70
|
+
|
|
71
|
+
return duplicates
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def dedupe_items(
|
|
75
|
+
items: List[Union[schema.RedditItem, schema.XItem]],
|
|
76
|
+
threshold: float = 0.7,
|
|
77
|
+
) -> List[Union[schema.RedditItem, schema.XItem]]:
|
|
78
|
+
"""Remove near-duplicates, keeping highest-scored item.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
items: List of items (should be pre-sorted by score descending)
|
|
82
|
+
threshold: Similarity threshold
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Deduplicated items
|
|
86
|
+
"""
|
|
87
|
+
if len(items) <= 1:
|
|
88
|
+
return items
|
|
89
|
+
|
|
90
|
+
# Find duplicate pairs
|
|
91
|
+
dup_pairs = find_duplicates(items, threshold)
|
|
92
|
+
|
|
93
|
+
# Mark indices to remove (always remove the lower-scored one)
|
|
94
|
+
# Since items are pre-sorted by score, the second index is always lower
|
|
95
|
+
to_remove = set()
|
|
96
|
+
for i, j in dup_pairs:
|
|
97
|
+
# Keep the higher-scored one (lower index in sorted list)
|
|
98
|
+
if items[i].score >= items[j].score:
|
|
99
|
+
to_remove.add(j)
|
|
100
|
+
else:
|
|
101
|
+
to_remove.add(i)
|
|
102
|
+
|
|
103
|
+
# Return items not marked for removal
|
|
104
|
+
return [item for idx, item in enumerate(items) if idx not in to_remove]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def dedupe_reddit(
|
|
108
|
+
items: List[schema.RedditItem],
|
|
109
|
+
threshold: float = 0.7,
|
|
110
|
+
) -> List[schema.RedditItem]:
|
|
111
|
+
"""Dedupe Reddit items."""
|
|
112
|
+
return dedupe_items(items, threshold)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def dedupe_x(
|
|
116
|
+
items: List[schema.XItem],
|
|
117
|
+
threshold: float = 0.7,
|
|
118
|
+
) -> List[schema.XItem]:
|
|
119
|
+
"""Dedupe X items."""
|
|
120
|
+
return dedupe_items(items, threshold)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Environment and API key management for last30days skill."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional, Dict, Any
|
|
6
|
+
|
|
7
|
+
CONFIG_DIR = Path.home() / ".config" / "last30days"
|
|
8
|
+
CONFIG_FILE = CONFIG_DIR / ".env"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_env_file(path: Path) -> Dict[str, str]:
|
|
12
|
+
"""Load environment variables from a file."""
|
|
13
|
+
env = {}
|
|
14
|
+
if not path.exists():
|
|
15
|
+
return env
|
|
16
|
+
|
|
17
|
+
with open(path, 'r') as f:
|
|
18
|
+
for line in f:
|
|
19
|
+
line = line.strip()
|
|
20
|
+
if not line or line.startswith('#'):
|
|
21
|
+
continue
|
|
22
|
+
if '=' in line:
|
|
23
|
+
key, _, value = line.partition('=')
|
|
24
|
+
key = key.strip()
|
|
25
|
+
value = value.strip()
|
|
26
|
+
# Remove quotes if present
|
|
27
|
+
if value and value[0] in ('"', "'") and value[-1] == value[0]:
|
|
28
|
+
value = value[1:-1]
|
|
29
|
+
if key and value:
|
|
30
|
+
env[key] = value
|
|
31
|
+
return env
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_config() -> Dict[str, Any]:
|
|
35
|
+
"""Load configuration from ~/.config/last30days/.env and environment."""
|
|
36
|
+
# Load from config file first
|
|
37
|
+
file_env = load_env_file(CONFIG_FILE)
|
|
38
|
+
|
|
39
|
+
# Environment variables override file
|
|
40
|
+
config = {
|
|
41
|
+
'OPENAI_API_KEY': os.environ.get('OPENAI_API_KEY') or file_env.get('OPENAI_API_KEY'),
|
|
42
|
+
'XAI_API_KEY': os.environ.get('XAI_API_KEY') or file_env.get('XAI_API_KEY'),
|
|
43
|
+
'OPENAI_MODEL_POLICY': os.environ.get('OPENAI_MODEL_POLICY') or file_env.get('OPENAI_MODEL_POLICY', 'auto'),
|
|
44
|
+
'OPENAI_MODEL_PIN': os.environ.get('OPENAI_MODEL_PIN') or file_env.get('OPENAI_MODEL_PIN'),
|
|
45
|
+
'XAI_MODEL_POLICY': os.environ.get('XAI_MODEL_POLICY') or file_env.get('XAI_MODEL_POLICY', 'latest'),
|
|
46
|
+
'XAI_MODEL_PIN': os.environ.get('XAI_MODEL_PIN') or file_env.get('XAI_MODEL_PIN'),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return config
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def config_exists() -> bool:
|
|
53
|
+
"""Check if configuration file exists."""
|
|
54
|
+
return CONFIG_FILE.exists()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_available_sources(config: Dict[str, Any]) -> str:
|
|
58
|
+
"""Determine which sources are available based on API keys.
|
|
59
|
+
|
|
60
|
+
Returns: 'both', 'reddit', 'x', or 'web' (fallback when no keys)
|
|
61
|
+
"""
|
|
62
|
+
has_openai = bool(config.get('OPENAI_API_KEY'))
|
|
63
|
+
has_xai = bool(config.get('XAI_API_KEY'))
|
|
64
|
+
|
|
65
|
+
if has_openai and has_xai:
|
|
66
|
+
return 'both'
|
|
67
|
+
elif has_openai:
|
|
68
|
+
return 'reddit'
|
|
69
|
+
elif has_xai:
|
|
70
|
+
return 'x'
|
|
71
|
+
else:
|
|
72
|
+
return 'web' # Fallback: WebSearch only (no API keys needed)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_missing_keys(config: Dict[str, Any]) -> str:
|
|
76
|
+
"""Determine which API keys are missing.
|
|
77
|
+
|
|
78
|
+
Returns: 'both', 'reddit', 'x', or 'none'
|
|
79
|
+
"""
|
|
80
|
+
has_openai = bool(config.get('OPENAI_API_KEY'))
|
|
81
|
+
has_xai = bool(config.get('XAI_API_KEY'))
|
|
82
|
+
|
|
83
|
+
if has_openai and has_xai:
|
|
84
|
+
return 'none'
|
|
85
|
+
elif has_openai:
|
|
86
|
+
return 'x' # Missing xAI key
|
|
87
|
+
elif has_xai:
|
|
88
|
+
return 'reddit' # Missing OpenAI key
|
|
89
|
+
else:
|
|
90
|
+
return 'both' # Missing both keys
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def validate_sources(requested: str, available: str, include_web: bool = False) -> tuple[str, Optional[str]]:
|
|
94
|
+
"""Validate requested sources against available keys.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
requested: 'auto', 'reddit', 'x', 'both', or 'web'
|
|
98
|
+
available: Result from get_available_sources()
|
|
99
|
+
include_web: If True, add WebSearch to available sources
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Tuple of (effective_sources, error_message)
|
|
103
|
+
"""
|
|
104
|
+
# WebSearch-only mode (no API keys)
|
|
105
|
+
if available == 'web':
|
|
106
|
+
if requested == 'auto':
|
|
107
|
+
return 'web', None
|
|
108
|
+
elif requested == 'web':
|
|
109
|
+
return 'web', None
|
|
110
|
+
else:
|
|
111
|
+
return 'web', f"No API keys configured. Using WebSearch fallback. Add keys to ~/.config/last30days/.env for Reddit/X."
|
|
112
|
+
|
|
113
|
+
if requested == 'auto':
|
|
114
|
+
# Add web to sources if include_web is set
|
|
115
|
+
if include_web:
|
|
116
|
+
if available == 'both':
|
|
117
|
+
return 'all', None # reddit + x + web
|
|
118
|
+
elif available == 'reddit':
|
|
119
|
+
return 'reddit-web', None
|
|
120
|
+
elif available == 'x':
|
|
121
|
+
return 'x-web', None
|
|
122
|
+
return available, None
|
|
123
|
+
|
|
124
|
+
if requested == 'web':
|
|
125
|
+
return 'web', None
|
|
126
|
+
|
|
127
|
+
if requested == 'both':
|
|
128
|
+
if available not in ('both',):
|
|
129
|
+
missing = 'xAI' if available == 'reddit' else 'OpenAI'
|
|
130
|
+
return 'none', f"Requested both sources but {missing} key is missing. Use --sources=auto to use available keys."
|
|
131
|
+
if include_web:
|
|
132
|
+
return 'all', None
|
|
133
|
+
return 'both', None
|
|
134
|
+
|
|
135
|
+
if requested == 'reddit':
|
|
136
|
+
if available == 'x':
|
|
137
|
+
return 'none', "Requested Reddit but only xAI key is available."
|
|
138
|
+
if include_web:
|
|
139
|
+
return 'reddit-web', None
|
|
140
|
+
return 'reddit', None
|
|
141
|
+
|
|
142
|
+
if requested == 'x':
|
|
143
|
+
if available == 'reddit':
|
|
144
|
+
return 'none', "Requested X but only OpenAI key is available."
|
|
145
|
+
if include_web:
|
|
146
|
+
return 'x-web', None
|
|
147
|
+
return 'x', None
|
|
148
|
+
|
|
149
|
+
return requested, None
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""HTTP utilities for last30days skill (stdlib only)."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
import urllib.error
|
|
8
|
+
import urllib.request
|
|
9
|
+
from typing import Any, Dict, Optional
|
|
10
|
+
from urllib.parse import urlencode
|
|
11
|
+
|
|
12
|
+
DEFAULT_TIMEOUT = 30
|
|
13
|
+
DEBUG = os.environ.get("LAST30DAYS_DEBUG", "").lower() in ("1", "true", "yes")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def log(msg: str):
|
|
17
|
+
"""Log debug message to stderr."""
|
|
18
|
+
if DEBUG:
|
|
19
|
+
sys.stderr.write(f"[DEBUG] {msg}\n")
|
|
20
|
+
sys.stderr.flush()
|
|
21
|
+
MAX_RETRIES = 3
|
|
22
|
+
RETRY_DELAY = 1.0
|
|
23
|
+
USER_AGENT = "last30days-skill/1.0 (Claude Code Skill)"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class HTTPError(Exception):
|
|
27
|
+
"""HTTP request error with status code."""
|
|
28
|
+
def __init__(self, message: str, status_code: Optional[int] = None, body: Optional[str] = None):
|
|
29
|
+
super().__init__(message)
|
|
30
|
+
self.status_code = status_code
|
|
31
|
+
self.body = body
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def request(
|
|
35
|
+
method: str,
|
|
36
|
+
url: str,
|
|
37
|
+
headers: Optional[Dict[str, str]] = None,
|
|
38
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
39
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
40
|
+
retries: int = MAX_RETRIES,
|
|
41
|
+
) -> Dict[str, Any]:
|
|
42
|
+
"""Make an HTTP request and return JSON response.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
method: HTTP method (GET, POST, etc.)
|
|
46
|
+
url: Request URL
|
|
47
|
+
headers: Optional headers dict
|
|
48
|
+
json_data: Optional JSON body (for POST)
|
|
49
|
+
timeout: Request timeout in seconds
|
|
50
|
+
retries: Number of retries on failure
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Parsed JSON response
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
HTTPError: On request failure
|
|
57
|
+
"""
|
|
58
|
+
headers = headers or {}
|
|
59
|
+
headers.setdefault("User-Agent", USER_AGENT)
|
|
60
|
+
|
|
61
|
+
data = None
|
|
62
|
+
if json_data is not None:
|
|
63
|
+
data = json.dumps(json_data).encode('utf-8')
|
|
64
|
+
headers.setdefault("Content-Type", "application/json")
|
|
65
|
+
|
|
66
|
+
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
67
|
+
|
|
68
|
+
log(f"{method} {url}")
|
|
69
|
+
if json_data:
|
|
70
|
+
log(f"Payload keys: {list(json_data.keys())}")
|
|
71
|
+
|
|
72
|
+
last_error = None
|
|
73
|
+
for attempt in range(retries):
|
|
74
|
+
try:
|
|
75
|
+
with urllib.request.urlopen(req, timeout=timeout) as response:
|
|
76
|
+
body = response.read().decode('utf-8')
|
|
77
|
+
log(f"Response: {response.status} ({len(body)} bytes)")
|
|
78
|
+
return json.loads(body) if body else {}
|
|
79
|
+
except urllib.error.HTTPError as e:
|
|
80
|
+
body = None
|
|
81
|
+
try:
|
|
82
|
+
body = e.read().decode('utf-8')
|
|
83
|
+
except:
|
|
84
|
+
pass
|
|
85
|
+
log(f"HTTP Error {e.code}: {e.reason}")
|
|
86
|
+
if body:
|
|
87
|
+
log(f"Error body: {body[:500]}")
|
|
88
|
+
last_error = HTTPError(f"HTTP {e.code}: {e.reason}", e.code, body)
|
|
89
|
+
|
|
90
|
+
# Don't retry client errors (4xx) except rate limits
|
|
91
|
+
if 400 <= e.code < 500 and e.code != 429:
|
|
92
|
+
raise last_error
|
|
93
|
+
|
|
94
|
+
if attempt < retries - 1:
|
|
95
|
+
time.sleep(RETRY_DELAY * (attempt + 1))
|
|
96
|
+
except urllib.error.URLError as e:
|
|
97
|
+
log(f"URL Error: {e.reason}")
|
|
98
|
+
last_error = HTTPError(f"URL Error: {e.reason}")
|
|
99
|
+
if attempt < retries - 1:
|
|
100
|
+
time.sleep(RETRY_DELAY * (attempt + 1))
|
|
101
|
+
except json.JSONDecodeError as e:
|
|
102
|
+
log(f"JSON decode error: {e}")
|
|
103
|
+
last_error = HTTPError(f"Invalid JSON response: {e}")
|
|
104
|
+
raise last_error
|
|
105
|
+
except (OSError, TimeoutError, ConnectionResetError) as e:
|
|
106
|
+
# Handle socket-level errors (connection reset, timeout, etc.)
|
|
107
|
+
log(f"Connection error: {type(e).__name__}: {e}")
|
|
108
|
+
last_error = HTTPError(f"Connection error: {type(e).__name__}: {e}")
|
|
109
|
+
if attempt < retries - 1:
|
|
110
|
+
time.sleep(RETRY_DELAY * (attempt + 1))
|
|
111
|
+
|
|
112
|
+
if last_error:
|
|
113
|
+
raise last_error
|
|
114
|
+
raise HTTPError("Request failed with no error details")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def get(url: str, headers: Optional[Dict[str, str]] = None, **kwargs) -> Dict[str, Any]:
|
|
118
|
+
"""Make a GET request."""
|
|
119
|
+
return request("GET", url, headers=headers, **kwargs)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def post(url: str, json_data: Dict[str, Any], headers: Optional[Dict[str, str]] = None, **kwargs) -> Dict[str, Any]:
|
|
123
|
+
"""Make a POST request with JSON body."""
|
|
124
|
+
return request("POST", url, headers=headers, json_data=json_data, **kwargs)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def get_reddit_json(path: str) -> Dict[str, Any]:
|
|
128
|
+
"""Fetch Reddit thread JSON.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
path: Reddit path (e.g., /r/subreddit/comments/id/title)
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Parsed JSON response
|
|
135
|
+
"""
|
|
136
|
+
# Ensure path starts with /
|
|
137
|
+
if not path.startswith('/'):
|
|
138
|
+
path = '/' + path
|
|
139
|
+
|
|
140
|
+
# Remove trailing slash and add .json
|
|
141
|
+
path = path.rstrip('/')
|
|
142
|
+
if not path.endswith('.json'):
|
|
143
|
+
path = path + '.json'
|
|
144
|
+
|
|
145
|
+
url = f"https://www.reddit.com{path}?raw_json=1"
|
|
146
|
+
|
|
147
|
+
headers = {
|
|
148
|
+
"User-Agent": USER_AGENT,
|
|
149
|
+
"Accept": "application/json",
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return get(url, headers=headers)
|