opencode-skills-collection 3.1.0 → 3.1.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/bundled-skills/.antigravity-install-manifest.json +84 -1
- package/bundled-skills/2slides-ppt-generator/SKILL.md +8 -7
- package/bundled-skills/android-cli/SKILL.md +19 -7
- package/bundled-skills/android-ui-journey-testing/SKILL.md +191 -0
- package/bundled-skills/apple-notes-search/SKILL.md +12 -2
- package/bundled-skills/ask-matt/SKILL.md +92 -0
- package/bundled-skills/atlas-ledger/SKILL.md +8 -0
- package/bundled-skills/bugs-are-annoying/SKILL.md +137 -0
- package/bundled-skills/codebase-design/DEEPENING.md +37 -0
- package/bundled-skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/bundled-skills/codebase-design/SKILL.md +145 -0
- package/bundled-skills/codex-fable5/SKILL.md +10 -2
- package/bundled-skills/competitor-analysis/LICENSE.txt +21 -0
- package/bundled-skills/competitor-analysis/SKILL.md +434 -0
- package/bundled-skills/competitor-analysis/references/battle-card-subagent.md +127 -0
- package/bundled-skills/competitor-analysis/references/battle-card.md +91 -0
- package/bundled-skills/competitor-analysis/references/example-research.md +130 -0
- package/bundled-skills/competitor-analysis/references/report-template.html +127 -0
- package/bundled-skills/competitor-analysis/references/research-patterns.md +217 -0
- package/bundled-skills/competitor-analysis/references/workflow.md +434 -0
- package/bundled-skills/competitor-analysis/scripts/capture_screenshots.mjs +142 -0
- package/bundled-skills/competitor-analysis/scripts/compile_report.mjs +929 -0
- package/bundled-skills/competitor-analysis/scripts/extract_vs_names.mjs +140 -0
- package/bundled-skills/competitor-analysis/scripts/gate_candidates.mjs +224 -0
- package/bundled-skills/competitor-analysis/scripts/list_urls.mjs +90 -0
- package/bundled-skills/competitor-analysis/scripts/md_utils.mjs +50 -0
- package/bundled-skills/competitor-analysis/scripts/merge_partials.mjs +291 -0
- package/bundled-skills/competitor-analysis/scripts/package.json +6 -0
- package/bundled-skills/design-it/3d-ui/SKILL.md +259 -0
- package/bundled-skills/design-it/SKILL.md +170 -0
- package/bundled-skills/design-it/ai-native-ui/SKILL.md +295 -0
- package/bundled-skills/design-it/aurora-ui/SKILL.md +307 -0
- package/bundled-skills/design-it/bento-ui/SKILL.md +314 -0
- package/bundled-skills/design-it/brutalism/SKILL.md +270 -0
- package/bundled-skills/design-it/brutalist-typography/SKILL.md +287 -0
- package/bundled-skills/design-it/card-based-design/SKILL.md +262 -0
- package/bundled-skills/design-it/claymorphism/SKILL.md +287 -0
- package/bundled-skills/design-it/color-blocking/SKILL.md +278 -0
- package/bundled-skills/design-it/command-center-ui/SKILL.md +345 -0
- package/bundled-skills/design-it/cyber-y2k/SKILL.md +312 -0
- package/bundled-skills/design-it/cyberpunk-ui/SKILL.md +262 -0
- package/bundled-skills/design-it/dark-mode/SKILL.md +289 -0
- package/bundled-skills/design-it/dashboard-design/SKILL.md +331 -0
- package/bundled-skills/design-it/data-dense-design/SKILL.md +322 -0
- package/bundled-skills/design-it/duotone-design/SKILL.md +248 -0
- package/bundled-skills/design-it/editorial-design/SKILL.md +328 -0
- package/bundled-skills/design-it/flat-design/SKILL.md +221 -0
- package/bundled-skills/design-it/flat-design-2/SKILL.md +240 -0
- package/bundled-skills/design-it/floating-ui/SKILL.md +299 -0
- package/bundled-skills/design-it/frutiger-aero/SKILL.md +274 -0
- package/bundled-skills/design-it/glassmorphism/SKILL.md +272 -0
- package/bundled-skills/design-it/gradient-design/SKILL.md +309 -0
- package/bundled-skills/design-it/high-contrast/SKILL.md +288 -0
- package/bundled-skills/design-it/holographic-ui/SKILL.md +310 -0
- package/bundled-skills/design-it/isometric-design/SKILL.md +228 -0
- package/bundled-skills/design-it/layered-design/SKILL.md +247 -0
- package/bundled-skills/design-it/material-design/SKILL.md +275 -0
- package/bundled-skills/design-it/maximalism/SKILL.md +297 -0
- package/bundled-skills/design-it/minimalism/SKILL.md +267 -0
- package/bundled-skills/design-it/monochromatic-ui/SKILL.md +296 -0
- package/bundled-skills/design-it/neo-brutalism/SKILL.md +270 -0
- package/bundled-skills/design-it/neumorphism/SKILL.md +248 -0
- package/bundled-skills/design-it/retro-design/SKILL.md +283 -0
- package/bundled-skills/design-it/retro-futurism/SKILL.md +259 -0
- package/bundled-skills/design-it/sci-fi-interface/SKILL.md +309 -0
- package/bundled-skills/design-it/skeuomorphism/SKILL.md +280 -0
- package/bundled-skills/design-it/soft-pastel/SKILL.md +307 -0
- package/bundled-skills/design-it/spatial-computing-ui/SKILL.md +300 -0
- package/bundled-skills/design-it/spatial-design/SKILL.md +268 -0
- package/bundled-skills/design-it/swiss-design/SKILL.md +293 -0
- package/bundled-skills/design-it/synthwave/SKILL.md +257 -0
- package/bundled-skills/design-it/tile-design/SKILL.md +297 -0
- package/bundled-skills/design-it/typography-first/SKILL.md +247 -0
- package/bundled-skills/design-it/vaporwave/SKILL.md +331 -0
- package/bundled-skills/design-it/vibrant-maximalism/SKILL.md +291 -0
- package/bundled-skills/design-it/widget-based-design/SKILL.md +274 -0
- package/bundled-skills/design-it/y2k-design/SKILL.md +268 -0
- package/bundled-skills/diagnosing-bugs/SKILL.md +165 -0
- package/bundled-skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/bundled-skills/docs/contributors/skill-scoring.md +235 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/bundles.md +145 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/specialized-plugin-roadmap.md +11 -4
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/bundled-skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/bundled-skills/domain-modeling/SKILL.md +105 -0
- package/bundled-skills/dos-verify-done-claims/SKILL.md +16 -4
- package/bundled-skills/ecl-harness-engineer/agents/creator-config.md +1 -1
- package/bundled-skills/ecl-harness-engineer/references/environment-config-guide.md +2 -2
- package/bundled-skills/ecl-harness-engineer/references/environment-detection-guide.md +4 -4
- package/bundled-skills/event-staffing-ordering/SKILL.md +4 -0
- package/bundled-skills/grill-me/SKILL.md +36 -0
- package/bundled-skills/grill-with-docs/SKILL.md +36 -0
- package/bundled-skills/grilling/SKILL.md +39 -0
- package/bundled-skills/handoff/SKILL.md +45 -0
- package/bundled-skills/image-generator/.env.example +7 -0
- package/bundled-skills/image-generator/SKILL.md +509 -0
- package/bundled-skills/improve-codebase-architecture/HTML-REPORT.md +123 -0
- package/bundled-skills/improve-codebase-architecture/SKILL.md +97 -0
- package/bundled-skills/learn/SKILL.md +156 -0
- package/bundled-skills/lesson-generator/SKILL.md +90 -0
- package/bundled-skills/llm-council/.env.example +7 -0
- package/bundled-skills/llm-council/SKILL.md +602 -0
- package/bundled-skills/loop-library/SKILL.md +208 -0
- package/bundled-skills/loop-library/agents/openai.yaml +4 -0
- package/bundled-skills/loop-library/references/catalog.md +270 -0
- package/bundled-skills/lovable-cleanup/SKILL.md +9 -7
- package/bundled-skills/macos-screen-recorder/SKILL.md +9 -1
- package/bundled-skills/mailtrap-managing-contacts/SKILL.md +112 -0
- package/bundled-skills/mailtrap-sending-emails/SKILL.md +167 -0
- package/bundled-skills/mailtrap-setting-up-sending-domain/SKILL.md +77 -0
- package/bundled-skills/mailtrap-testing-with-sandbox/SKILL.md +110 -0
- package/bundled-skills/prototype/LOGIC.md +79 -0
- package/bundled-skills/prototype/SKILL.md +62 -0
- package/bundled-skills/prototype/UI.md +112 -0
- package/bundled-skills/screenstudio-alt/SKILL.md +9 -1
- package/bundled-skills/setup-matt-pocock-skills/SKILL.md +158 -0
- package/bundled-skills/setup-matt-pocock-skills/domain.md +51 -0
- package/bundled-skills/setup-matt-pocock-skills/issue-tracker-github.md +34 -0
- package/bundled-skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +35 -0
- package/bundled-skills/setup-matt-pocock-skills/issue-tracker-local.md +19 -0
- package/bundled-skills/setup-matt-pocock-skills/triage-labels.md +15 -0
- package/bundled-skills/survey-generator/LICENSE +21 -0
- package/bundled-skills/survey-generator/SKILL.md +143 -0
- package/bundled-skills/survey-generator/build_artifact.py +208 -0
- package/bundled-skills/survey-generator/examples/agentic-engineering/research_bundle.json +1196 -0
- package/bundled-skills/survey-generator/examples/agentic-engineering/survey.html +706 -0
- package/bundled-skills/survey-generator/style_spec.json +85 -0
- package/bundled-skills/survey-generator/templates/research_bundle_template.json +69 -0
- package/bundled-skills/tdd/SKILL.md +139 -0
- package/bundled-skills/tdd/mocking.md +59 -0
- package/bundled-skills/tdd/refactoring.md +10 -0
- package/bundled-skills/tdd/tests.md +61 -0
- package/bundled-skills/teach/GLOSSARY-FORMAT.md +35 -0
- package/bundled-skills/teach/LEARNING-RECORD-FORMAT.md +46 -0
- package/bundled-skills/teach/MISSION-FORMAT.md +31 -0
- package/bundled-skills/teach/RESOURCES-FORMAT.md +32 -0
- package/bundled-skills/teach/SKILL.md +169 -0
- package/bundled-skills/to-issues/SKILL.md +115 -0
- package/bundled-skills/to-prd/SKILL.md +104 -0
- package/bundled-skills/tools-page-seo-optimizer/SKILL.md +616 -0
- package/bundled-skills/triage/AGENT-BRIEF.md +207 -0
- package/bundled-skills/triage/OUT-OF-SCOPE.md +105 -0
- package/bundled-skills/triage/SKILL.md +143 -0
- package/bundled-skills/vibecode-production-qa-validator/SKILL.md +371 -141
- package/bundled-skills/wiki-builder/SKILL.md +157 -0
- package/bundled-skills/wiki-builder/agents/openai.yaml +5 -0
- package/bundled-skills/wiki-builder/references/wiki-flavors.md +98 -0
- package/bundled-skills/wiki-builder/scripts/init_wiki.sh +105 -0
- package/bundled-skills/wiki-builder/templates/index.md +20 -0
- package/bundled-skills/wiki-builder/templates/maintenance-log.md +7 -0
- package/bundled-skills/wiki-builder/templates/prompts/compile-concept-page.md +12 -0
- package/bundled-skills/wiki-builder/templates/prompts/compile-index.md +11 -0
- package/bundled-skills/wiki-builder/templates/prompts/compile-source-page.md +12 -0
- package/bundled-skills/wiki-builder/templates/prompts/lint-wiki.md +10 -0
- package/bundled-skills/wiki-builder/templates/prompts/query-and-file.md +11 -0
- package/bundled-skills/wiki-builder/templates/sources.md +9 -0
- package/bundled-skills/wiki-builder/templates/wiki.config.md +53 -0
- package/bundled-skills/writing-great-skills/GLOSSARY.md +181 -0
- package/bundled-skills/writing-great-skills/SKILL.md +111 -0
- package/bundled-skills/yao-meta-skill/SKILL.md +86 -0
- package/bundled-skills/yao-meta-skill/agents/interface.yaml +26 -0
- package/bundled-skills/yao-meta-skill/manifest.json +24 -0
- package/bundled-skills/yao-meta-skill/references/artifact-design-doctrine.md +49 -0
- package/bundled-skills/yao-meta-skill/references/authoring-discipline.md +78 -0
- package/bundled-skills/yao-meta-skill/references/autonomous-adaptation.md +65 -0
- package/bundled-skills/yao-meta-skill/references/distribution-registry-method.md +60 -0
- package/bundled-skills/yao-meta-skill/references/eval-playbook.md +69 -0
- package/bundled-skills/yao-meta-skill/references/gate-selection.md +68 -0
- package/bundled-skills/yao-meta-skill/references/governance.md +134 -0
- package/bundled-skills/yao-meta-skill/references/human-review-template.md +54 -0
- package/bundled-skills/yao-meta-skill/references/intent-dialogue.md +138 -0
- package/bundled-skills/yao-meta-skill/references/iteration-philosophy.md +30 -0
- package/bundled-skills/yao-meta-skill/references/non-skill-decision-tree.md +39 -0
- package/bundled-skills/yao-meta-skill/references/operating-modes.md +107 -0
- package/bundled-skills/yao-meta-skill/references/output-eval-method.md +113 -0
- package/bundled-skills/yao-meta-skill/references/output-quality-risk.md +41 -0
- package/bundled-skills/yao-meta-skill/references/output-visual-quality.md +53 -0
- package/bundled-skills/yao-meta-skill/references/packaging-contracts.md +70 -0
- package/bundled-skills/yao-meta-skill/references/pattern-extraction-doctrine.md +76 -0
- package/bundled-skills/yao-meta-skill/references/platform-capability-matrix.md +49 -0
- package/bundled-skills/yao-meta-skill/references/prompt-engineering-doctrine.md +76 -0
- package/bundled-skills/yao-meta-skill/references/qa-ladder.md +57 -0
- package/bundled-skills/yao-meta-skill/references/reference-scan.md +126 -0
- package/bundled-skills/yao-meta-skill/references/regression-cause-taxonomy.md +80 -0
- package/bundled-skills/yao-meta-skill/references/resource-boundaries.md +120 -0
- package/bundled-skills/yao-meta-skill/references/review-studio-method.md +87 -0
- package/bundled-skills/yao-meta-skill/references/review-waiver-method.md +76 -0
- package/bundled-skills/yao-meta-skill/references/runtime-conformance-method.md +21 -0
- package/bundled-skills/yao-meta-skill/references/skill-archetypes.md +86 -0
- package/bundled-skills/yao-meta-skill/references/skill-atlas-method.md +35 -0
- package/bundled-skills/yao-meta-skill/references/skill-engineering-method.md +210 -0
- package/bundled-skills/yao-meta-skill/references/skill-ir-method.md +41 -0
- package/bundled-skills/yao-meta-skill/references/skillops-decision-policy.md +53 -0
- package/bundled-skills/yao-meta-skill/references/systems-thinking-doctrine.md +75 -0
- package/bundled-skills/yao-meta-skill/references/telemetry-drift-method.md +182 -0
- package/bundled-skills/yao-meta-skill/references/trust-security-method.md +79 -0
- package/bundled-skills/yao-meta-skill/references/user-memory-policy.md +35 -0
- package/bundled-skills/youtube-notetaker/SKILL.md +209 -0
- package/bundled-skills/youtube-notetaker/reference/artifact.html +269 -0
- package/bundled-skills/youtube-notetaker/scripts/contact_sheet.py +53 -0
- package/bundled-skills/youtube-notetaker/scripts/detect_slides.sh +19 -0
- package/bundled-skills/youtube-notetaker/scripts/download.sh +24 -0
- package/bundled-skills/youtube-notetaker/scripts/extract_slides.py +43 -0
- package/bundled-skills/youtube-notetaker/scripts/serve.py +222 -0
- package/bundled-skills/youtube-notetaker/scripts/setup.sh +27 -0
- package/bundled-skills/youtube-notetaker/scripts/verify.sh +31 -0
- package/bundled-skills/youtube-notetaker/scripts/vtt_to_transcript.py +59 -0
- package/bundled-skills/youtube-notetaker/scripts/write_library_item.py +69 -0
- package/package.json +1 -1
- package/skills_index.json +2013 -330
- package/bundled-skills/ai-md/SKILL.md +0 -523
- package/bundled-skills/atlas-contract/SKILL.md +0 -650
- package/bundled-skills/busybox-on-windows/SKILL.md +0 -40
- package/bundled-skills/monte-carlo-prevent/SKILL.md +0 -257
- package/bundled-skills/monte-carlo-prevent/references/TROUBLESHOOTING.md +0 -23
- package/bundled-skills/monte-carlo-prevent/references/parameters.md +0 -32
- package/bundled-skills/monte-carlo-prevent/references/workflows.md +0 -478
- package/bundled-skills/monte-carlo-push-ingestion/SKILL.md +0 -372
- package/bundled-skills/monte-carlo-push-ingestion/references/anomaly-detection.md +0 -87
- package/bundled-skills/monte-carlo-push-ingestion/references/custom-lineage.md +0 -203
- package/bundled-skills/monte-carlo-push-ingestion/references/direct-http-api.md +0 -207
- package/bundled-skills/monte-carlo-push-ingestion/references/prerequisites.md +0 -150
- package/bundled-skills/monte-carlo-push-ingestion/references/push-lineage.md +0 -160
- package/bundled-skills/monte-carlo-push-ingestion/references/push-metadata.md +0 -158
- package/bundled-skills/monte-carlo-push-ingestion/references/push-query-logs.md +0 -219
- package/bundled-skills/monte-carlo-push-ingestion/references/validation.md +0 -257
- package/bundled-skills/monte-carlo-push-ingestion/scripts/sample_verify.py +0 -357
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_and_push_lineage.py +0 -70
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_and_push_metadata.py +0 -65
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_and_push_query_logs.py +0 -70
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_lineage.py +0 -214
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_metadata.py +0 -160
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_query_logs.py +0 -164
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/push_lineage.py +0 -198
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/push_metadata.py +0 -193
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/push_query_logs.py +0 -207
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_and_push_metadata.py +0 -71
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_and_push_query_logs.py +0 -64
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_metadata.py +0 -253
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_query_logs.py +0 -149
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/push_metadata.py +0 -190
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/push_query_logs.py +0 -208
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_and_push_lineage.py +0 -83
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_and_push_metadata.py +0 -77
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_and_push_query_logs.py +0 -83
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_lineage.py +0 -240
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_metadata.py +0 -212
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_query_logs.py +0 -204
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/push_lineage.py +0 -192
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/push_metadata.py +0 -178
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/push_query_logs.py +0 -200
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_and_push_lineage.py +0 -119
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_and_push_metadata.py +0 -119
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_and_push_query_logs.py +0 -117
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_lineage.py +0 -265
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_metadata.py +0 -313
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_query_logs.py +0 -284
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/push_lineage.py +0 -309
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/push_metadata.py +0 -245
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/push_query_logs.py +0 -255
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_and_push_lineage.py +0 -78
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_and_push_metadata.py +0 -80
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_and_push_query_logs.py +0 -88
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_lineage.py +0 -235
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_metadata.py +0 -219
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_query_logs.py +0 -239
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/push_lineage.py +0 -178
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/push_metadata.py +0 -178
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/push_query_logs.py +0 -196
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_and_push_lineage.py +0 -154
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_and_push_metadata.py +0 -137
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_and_push_query_logs.py +0 -137
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_lineage.py +0 -349
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_metadata.py +0 -329
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_query_logs.py +0 -254
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/push_lineage.py +0 -307
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/push_metadata.py +0 -228
- package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/push_query_logs.py +0 -248
- package/bundled-skills/monte-carlo-push-ingestion/scripts/test_template_sdk_usage.py +0 -340
- package/bundled-skills/skill-optimizer/SKILL.md +0 -271
- package/bundled-skills/using-superpowers/SKILL.md +0 -98
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Parses "X vs Y" patterns from `browse cloud search` result titles across discovery batch files.
|
|
4
|
+
// Produces a ranked list of candidate competitor names, with an example title each,
|
|
5
|
+
// and attempts to resolve each name to a domain from the result URL pool.
|
|
6
|
+
//
|
|
7
|
+
// Usage: node extract_vs_names.mjs <directory> [--prefix competitor] [--seed "Exa,Tavily,SerpAPI"]
|
|
8
|
+
//
|
|
9
|
+
// Output: newline-delimited JSON to stdout, one object per candidate:
|
|
10
|
+
// { "name": "serper", "hits": 3, "domain": "serper.dev", "example": "Tavily vs Serper..." }
|
|
11
|
+
|
|
12
|
+
import { readdirSync, readFileSync } from 'fs';
|
|
13
|
+
import { join } from 'path';
|
|
14
|
+
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
|
|
17
|
+
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
|
|
18
|
+
console.error(`Usage: node extract_vs_names.mjs <directory> [--prefix <prefix>] [--seed "<csv>"]
|
|
19
|
+
|
|
20
|
+
Reads all <prefix>_discovery_batch_*.json files, parses "X vs Y" patterns from result
|
|
21
|
+
titles, and outputs a ranked list of candidate competitor names as newline-delimited JSON.
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--prefix <prefix> Batch file prefix (default: "competitor")
|
|
25
|
+
--seed "<csv>" Comma-separated list of seed names to exclude from output
|
|
26
|
+
(you already know these; want the OTHER side of the comparison)
|
|
27
|
+
--help, -h Show this help message`);
|
|
28
|
+
process.exit(args.includes('--help') || args.includes('-h') ? 0 : 1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const dir = args[0];
|
|
32
|
+
const prefixIdx = args.indexOf('--prefix');
|
|
33
|
+
const prefix = prefixIdx !== -1 && args[prefixIdx + 1] ? args[prefixIdx + 1] : 'competitor';
|
|
34
|
+
const seedIdx = args.indexOf('--seed');
|
|
35
|
+
const seeds = seedIdx !== -1 && args[seedIdx + 1]
|
|
36
|
+
? args[seedIdx + 1].split(',').map(s => s.trim().toLowerCase()).filter(Boolean)
|
|
37
|
+
: [];
|
|
38
|
+
const seedSet = new Set(seeds);
|
|
39
|
+
|
|
40
|
+
// Escape regex metacharacters in the user-supplied prefix so a value like
|
|
41
|
+
// "comp.+" matches the literal filename, not as a regex pattern.
|
|
42
|
+
const escapedPrefix = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
43
|
+
const pattern = new RegExp(`^${escapedPrefix}_discovery_batch_.*\\.json$`);
|
|
44
|
+
|
|
45
|
+
let files;
|
|
46
|
+
try {
|
|
47
|
+
files = readdirSync(dir).filter(f => pattern.test(f)).sort();
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.error(`Error reading directory ${dir}: ${err.message}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (files.length === 0) {
|
|
54
|
+
console.error(`No ${prefix}_discovery_batch_*.json files found in ${dir}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const allResults = [];
|
|
59
|
+
for (const f of files) {
|
|
60
|
+
try {
|
|
61
|
+
const d = JSON.parse(readFileSync(join(dir, f), 'utf-8'));
|
|
62
|
+
const rs = Array.isArray(d) ? d : d.results || [];
|
|
63
|
+
allResults.push(...rs);
|
|
64
|
+
} catch {}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Build a lookup of hostname -> candidate root domain from all result URLs.
|
|
68
|
+
// Used later to try to resolve "serper" -> "serper.dev".
|
|
69
|
+
// Exclude any host whose root-base equals a seed name — otherwise a short extracted token
|
|
70
|
+
// like "exa" can match the user's own domain (exa.ai).
|
|
71
|
+
const hostMap = new Map();
|
|
72
|
+
for (const r of allResults) {
|
|
73
|
+
if (!r.url) continue;
|
|
74
|
+
try {
|
|
75
|
+
const h = new URL(r.url).hostname.replace(/^www\./, '');
|
|
76
|
+
const root = h.split('.').slice(-2).join('.');
|
|
77
|
+
const rootBase = root.split('.')[0];
|
|
78
|
+
if (seedSet.has(rootBase)) continue;
|
|
79
|
+
if (!hostMap.has(root)) hostMap.set(root, h);
|
|
80
|
+
} catch {}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Extract names from "X vs Y" patterns.
|
|
84
|
+
const counts = new Map();
|
|
85
|
+
for (const r of allResults) {
|
|
86
|
+
const title = (r.title || '').toLowerCase();
|
|
87
|
+
const ms = [...title.matchAll(/\b([a-z][\w.\-]{2,})\s+(?:vs\.?|versus)\s+([a-z][\w.\-]{2,})/g)];
|
|
88
|
+
for (const m of ms) {
|
|
89
|
+
for (const raw of [m[1], m[2]]) {
|
|
90
|
+
const name = raw.replace(/[^a-z0-9.\-]/g, '').trim();
|
|
91
|
+
if (!name || name.length < 3) continue;
|
|
92
|
+
if (seedSet.has(name)) continue;
|
|
93
|
+
// Reject obvious non-product tokens
|
|
94
|
+
if (['the', 'and', 'for', 'with', 'best', 'top', 'better', 'using', 'choosing'].includes(name)) continue;
|
|
95
|
+
if (!counts.has(name)) counts.set(name, { name, hits: 0, example: r.title });
|
|
96
|
+
counts.get(name).hits += 1;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Try to resolve each name to a domain.
|
|
102
|
+
// Strategy:
|
|
103
|
+
// 1. Exact match on rootBase wins outright.
|
|
104
|
+
// 2. Otherwise allow rootBase.startsWith(needle) ONLY when the suffix is a known
|
|
105
|
+
// branding token (e.g. "serp" → "serpapi.com"). Bidirectional startsWith
|
|
106
|
+
// was too loose: "serp" matched serpstack.com, "exa" matched example.com.
|
|
107
|
+
// 3. Among multiple suffix matches, prefer the shortest suffix (most specific —
|
|
108
|
+
// "serp" should match "serpapi" before "serpapilabs"). Deterministic.
|
|
109
|
+
const BRAND_SUFFIXES = ['api','search','app','ai','io','hq','co','dev','tech','cloud','agent','agents','labs','lab'];
|
|
110
|
+
|
|
111
|
+
function resolveDomain(name) {
|
|
112
|
+
const needle = name.replace(/\./g, '');
|
|
113
|
+
let exact = null;
|
|
114
|
+
let bestSuffix = null; // { host, suffixLen }
|
|
115
|
+
for (const [root, host] of hostMap.entries()) {
|
|
116
|
+
const rootBase = root.split('.')[0];
|
|
117
|
+
if (rootBase === needle) { exact = host; break; }
|
|
118
|
+
if (rootBase.length > needle.length && rootBase.startsWith(needle)) {
|
|
119
|
+
const suffix = rootBase.slice(needle.length).replace(/^[\-_]/, '');
|
|
120
|
+
if (BRAND_SUFFIXES.includes(suffix)) {
|
|
121
|
+
if (!bestSuffix || suffix.length < bestSuffix.suffixLen) {
|
|
122
|
+
bestSuffix = { host, suffixLen: suffix.length };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (exact) return exact;
|
|
128
|
+
if (bestSuffix) return bestSuffix.host;
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const ranked = [...counts.values()]
|
|
133
|
+
.map(c => ({ ...c, domain: resolveDomain(c.name) }))
|
|
134
|
+
.sort((a, b) => b.hits - a.hits);
|
|
135
|
+
|
|
136
|
+
for (const c of ranked) {
|
|
137
|
+
console.log(JSON.stringify(c));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.error(`Extracted ${ranked.length} candidate names from ${files.length} batch files`);
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Category-fit gate. For each candidate URL, fetch the homepage hero via `browse cloud fetch`,
|
|
4
|
+
// extract visible text, and decide whether the candidate is in the same category as
|
|
5
|
+
// the user's company based on include/exclude keyword rules.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// cat urls.txt | node gate_candidates.mjs \
|
|
9
|
+
// --include "web search api,neural search,retrieval api,semantic search,search for agents" \
|
|
10
|
+
// --exclude "vector database,observability,analytics,enterprise search appliance,site search widget" \
|
|
11
|
+
// --concurrency 6
|
|
12
|
+
//
|
|
13
|
+
// Output: newline-delimited JSON to stdout with one object per URL:
|
|
14
|
+
// { "url": "https://foo.com", "status": "PASS" | "REJECT" | "UNKNOWN",
|
|
15
|
+
// "matched_includes": [...], "matched_excludes": [...], "title": "...", "hero": "..." }
|
|
16
|
+
|
|
17
|
+
import { execFile } from 'child_process';
|
|
18
|
+
import { promisify } from 'util';
|
|
19
|
+
import { readFileSync } from 'fs';
|
|
20
|
+
|
|
21
|
+
// Async execFile so the worker pool actually parallelizes. spawnSync blocks the entire
|
|
22
|
+
// event loop, which silently turns --concurrency N into N=1 — every URL fetched serially
|
|
23
|
+
// regardless of the flag. With promisified execFile, N workers can wait on N pending
|
|
24
|
+
// `browse cloud fetch` processes concurrently.
|
|
25
|
+
const execFileAsync = promisify(execFile);
|
|
26
|
+
|
|
27
|
+
const args = process.argv.slice(2);
|
|
28
|
+
|
|
29
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
30
|
+
console.error(`Usage: cat urls.txt | node gate_candidates.mjs [options]
|
|
31
|
+
|
|
32
|
+
Reads URLs from stdin (one per line) OR from --input <file>. For each URL, fetches
|
|
33
|
+
the homepage via \`browse cloud fetch --allow-redirects\`, extracts the first N chars of visible
|
|
34
|
+
text (the hero / tagline area), and classifies against include/exclude keyword rules.
|
|
35
|
+
|
|
36
|
+
Options:
|
|
37
|
+
--include "<csv>" Required. Comma-separated keywords; candidate PASSES if any match.
|
|
38
|
+
--exclude "<csv>" Comma-separated keywords; candidate REJECTS if any match.
|
|
39
|
+
--input <file> Read URLs from file instead of stdin.
|
|
40
|
+
--concurrency <n> Max parallel fetches (default: 6).
|
|
41
|
+
--hero-chars <n> Chars of visible text to examine (default: 800).
|
|
42
|
+
--help, -h Show this help message.`);
|
|
43
|
+
process.exit(args.includes('--help') || args.includes('-h') ? 0 : 1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function flag(name) {
|
|
47
|
+
const i = args.indexOf(name);
|
|
48
|
+
return i !== -1 ? args[i + 1] : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const includes = (flag('--include') || '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean);
|
|
52
|
+
const excludes = (flag('--exclude') || '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean);
|
|
53
|
+
// Floor at 1: `--concurrency 0` or a non-numeric value makes parseInt yield 0/NaN, which would
|
|
54
|
+
// spawn zero workers — the script would exit "successfully" having gated nothing, making
|
|
55
|
+
// discovery look empty with no error. Always run at least one worker.
|
|
56
|
+
const concurrency = Math.max(1, parseInt(flag('--concurrency') || '6', 10) || 0);
|
|
57
|
+
const heroChars = parseInt(flag('--hero-chars') || '800', 10);
|
|
58
|
+
const inputFile = flag('--input');
|
|
59
|
+
|
|
60
|
+
function stripHtml(html) {
|
|
61
|
+
const withoutActiveContent = removeElementContent(removeElementContent(html, 'script'), 'style');
|
|
62
|
+
return withoutActiveContent
|
|
63
|
+
.replace(/<[^>]*>/g, ' ')
|
|
64
|
+
.replace(/</g, '<')
|
|
65
|
+
.replace(/>/g, '>')
|
|
66
|
+
.replace(/"/g, '"')
|
|
67
|
+
.replace(/'/g, "'")
|
|
68
|
+
.replace(/ /g, ' ')
|
|
69
|
+
.replace(/&/g, '&')
|
|
70
|
+
.replace(/\s+/g, ' ')
|
|
71
|
+
.trim();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function removeElementContent(html, tagName) {
|
|
75
|
+
let out = '';
|
|
76
|
+
let cursor = 0;
|
|
77
|
+
const lower = html.toLowerCase();
|
|
78
|
+
const openNeedle = `<${tagName}`;
|
|
79
|
+
const closeNeedle = `</${tagName}`;
|
|
80
|
+
while (cursor < html.length) {
|
|
81
|
+
const start = lower.indexOf(openNeedle, cursor);
|
|
82
|
+
if (start === -1) {
|
|
83
|
+
out += html.slice(cursor);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
out += html.slice(cursor, start);
|
|
87
|
+
const close = lower.indexOf(closeNeedle, start + openNeedle.length);
|
|
88
|
+
if (close === -1) {
|
|
89
|
+
cursor = html.length;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
const closeEnd = html.indexOf('>', close + closeNeedle.length);
|
|
93
|
+
cursor = closeEnd === -1 ? html.length : closeEnd + 1;
|
|
94
|
+
out += ' ';
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (args.includes('--self-test')) {
|
|
100
|
+
console.assert(stripHtml('<p>A&lt;B</p>') === 'A<B');
|
|
101
|
+
console.assert(stripHtml('<script>alert(1)</script ignored><p>ok</p>') === 'ok');
|
|
102
|
+
process.exit(0);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (includes.length === 0) {
|
|
106
|
+
console.error('Error: --include is required');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let urls;
|
|
111
|
+
if (inputFile) {
|
|
112
|
+
urls = readFileSync(inputFile, 'utf-8').split('\n').map(l => l.trim()).filter(Boolean);
|
|
113
|
+
} else {
|
|
114
|
+
const stdin = readFileSync(0, 'utf-8');
|
|
115
|
+
urls = stdin.split('\n').map(l => l.trim()).filter(Boolean);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (urls.length === 0) {
|
|
119
|
+
console.error('Error: no URLs provided (pipe via stdin or use --input)');
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Position-aware classification:
|
|
124
|
+
// 1. Exclude term in <title> → REJECT (their primary identity is the excluded category)
|
|
125
|
+
// 2. Include term in <title> → PASS (their primary identity matches)
|
|
126
|
+
// 3. Include in early hero (200ch) → PASS iff no exclude in early hero
|
|
127
|
+
// 4. Otherwise → REJECT (default conservative)
|
|
128
|
+
// Rationale: <title> is the single strongest signal of what a company sells.
|
|
129
|
+
// Mid/late hero mentions (e.g. "we also support web scraping use cases") shouldn't
|
|
130
|
+
// disqualify a real competitor that self-identifies in its title as a cloud browser.
|
|
131
|
+
function classify(title, heroFull, includes, excludes) {
|
|
132
|
+
const titleLower = (title || '').toLowerCase();
|
|
133
|
+
const heroLower = heroFull.toLowerCase();
|
|
134
|
+
const heroEarly = heroLower.slice(0, 200);
|
|
135
|
+
|
|
136
|
+
const incTitle = includes.filter(k => titleLower.includes(k));
|
|
137
|
+
const excTitle = excludes.filter(k => titleLower.includes(k));
|
|
138
|
+
const incEarly = includes.filter(k => heroEarly.includes(k));
|
|
139
|
+
const excEarly = excludes.filter(k => heroEarly.includes(k));
|
|
140
|
+
const incHero = includes.filter(k => heroLower.includes(k));
|
|
141
|
+
const excHero = excludes.filter(k => heroLower.includes(k));
|
|
142
|
+
|
|
143
|
+
let status, reason;
|
|
144
|
+
if (incTitle.length > 0 && excTitle.length > 0) {
|
|
145
|
+
// Hybrid-identity title (e.g. "Browser Automation & Web Scraping API").
|
|
146
|
+
// Break the tie by the early hero — whichever category has more mentions wins.
|
|
147
|
+
if (incEarly.length > excEarly.length) { status = 'PASS'; reason = `title-hybrid→hero200 leans include(${incEarly[0] || incTitle[0]})`; }
|
|
148
|
+
else if (excEarly.length > incEarly.length) { status = 'REJECT'; reason = `title-hybrid→hero200 leans exclude(${excEarly[0] || excTitle[0]})`; }
|
|
149
|
+
else { status = 'PASS'; reason = `title-hybrid→tie, defaulting include(${incTitle[0]})`; }
|
|
150
|
+
}
|
|
151
|
+
else if (excTitle.length > 0) { status = 'REJECT'; reason = `title→exclude(${excTitle[0]})`; }
|
|
152
|
+
else if (incTitle.length > 0) { status = 'PASS'; reason = `title→include(${incTitle[0]})`; }
|
|
153
|
+
else if (incEarly.length > 0 && excEarly.length === 0) { status = 'PASS'; reason = `hero200→include(${incEarly[0]})`; }
|
|
154
|
+
else if (excEarly.length > 0) { status = 'REJECT'; reason = `hero200→exclude(${excEarly[0]})`; }
|
|
155
|
+
else if (incHero.length > 0 && excHero.length === 0) { status = 'PASS'; reason = `hero→include(${incHero[0]})`; }
|
|
156
|
+
// Late-hero conflict: both include AND exclude appear in chars 200–800 (nothing in
|
|
157
|
+
// title or early hero). This is genuine ambiguous signal, not absence — return UNKNOWN
|
|
158
|
+
// so the candidate surfaces in the user-confirmation bucket at Step 4.5 instead of
|
|
159
|
+
// being silently dropped as REJECT.
|
|
160
|
+
else if (incHero.length > 0 && excHero.length > 0) { status = 'UNKNOWN'; reason = `hero→conflict(include:${incHero[0]}, exclude:${excHero[0]})`; }
|
|
161
|
+
else { status = 'REJECT'; reason = 'no category signal'; }
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
status, reason,
|
|
165
|
+
matched_includes: [...new Set([...incTitle, ...incEarly, ...incHero])],
|
|
166
|
+
matched_excludes: [...new Set([...excTitle, ...excEarly, ...excHero])],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function gateOne(url) {
|
|
171
|
+
let stdout;
|
|
172
|
+
try {
|
|
173
|
+
// --format raw returns the JSON envelope with raw HTML in `.content` (the default
|
|
174
|
+
// is markdown, which has no <title> tag for the position-aware classifier to read).
|
|
175
|
+
const r = await execFileAsync('browse', ['cloud', 'fetch', '--allow-redirects', '--format', 'raw', url], {
|
|
176
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
177
|
+
timeout: 20000,
|
|
178
|
+
});
|
|
179
|
+
stdout = r.stdout;
|
|
180
|
+
} catch (err) {
|
|
181
|
+
// Non-zero exit, timeout, or spawn failure all surface here.
|
|
182
|
+
return { url, status: 'UNKNOWN', reason: `browse cloud fetch failed: ${err.message}`, matched_includes: [], matched_excludes: [], title: '', hero: '' };
|
|
183
|
+
}
|
|
184
|
+
let resp;
|
|
185
|
+
try { resp = JSON.parse(stdout); } catch {
|
|
186
|
+
return { url, status: 'UNKNOWN', reason: 'non-JSON response', matched_includes: [], matched_excludes: [], title: '', hero: '' };
|
|
187
|
+
}
|
|
188
|
+
const html = resp.content || '';
|
|
189
|
+
const titleM = html.match(/<title[^>]*>([^<]*)<\/title>/i);
|
|
190
|
+
const title = titleM ? titleM[1].trim() : '';
|
|
191
|
+
const heroFull = stripHtml(html).slice(0, heroChars);
|
|
192
|
+
const c = classify(title, heroFull, includes, excludes);
|
|
193
|
+
return {
|
|
194
|
+
url,
|
|
195
|
+
status: c.status,
|
|
196
|
+
reason: c.reason,
|
|
197
|
+
matched_includes: c.matched_includes,
|
|
198
|
+
matched_excludes: c.matched_excludes,
|
|
199
|
+
title,
|
|
200
|
+
hero: heroFull.slice(0, 240),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Run with bounded concurrency
|
|
205
|
+
const results = [];
|
|
206
|
+
async function runAll() {
|
|
207
|
+
const queue = [...urls];
|
|
208
|
+
const workers = Array(Math.min(concurrency, queue.length)).fill(0).map(async () => {
|
|
209
|
+
while (queue.length > 0) {
|
|
210
|
+
const u = queue.shift();
|
|
211
|
+
const r = await gateOne(u);
|
|
212
|
+
results.push(r);
|
|
213
|
+
console.log(JSON.stringify(r));
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
await Promise.all(workers);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
await runAll();
|
|
220
|
+
|
|
221
|
+
const pass = results.filter(r => r.status === 'PASS').length;
|
|
222
|
+
const reject = results.filter(r => r.status === 'REJECT').length;
|
|
223
|
+
const unknown = results.filter(r => r.status === 'UNKNOWN').length;
|
|
224
|
+
console.error(`\nGate: ${pass} PASS / ${reject} REJECT / ${unknown} UNKNOWN (of ${results.length})`);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Deduplicates discovery URLs from `browse cloud search` JSON output files.
|
|
4
|
+
// Usage: node list_urls.mjs /tmp [--prefix competitor]
|
|
5
|
+
// Reads all {prefix}_discovery_batch_*.json files, deduplicates by domain,
|
|
6
|
+
// outputs one URL per line to stdout, stats to stderr.
|
|
7
|
+
|
|
8
|
+
import { readdirSync, readFileSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
|
|
13
|
+
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
|
|
14
|
+
console.error(`Usage: node list_urls.mjs <directory> [--prefix <prefix>]
|
|
15
|
+
|
|
16
|
+
Reads all <prefix>_discovery_batch_*.json files from <directory>,
|
|
17
|
+
deduplicates URLs by domain, and outputs one URL per line to stdout.
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
--prefix <prefix> Batch file prefix (default: "competitor")
|
|
21
|
+
--help, -h Show this help message
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
node list_urls.mjs /tmp
|
|
25
|
+
node list_urls.mjs /tmp --prefix competitor`);
|
|
26
|
+
process.exit(args.includes('--help') || args.includes('-h') ? 0 : 1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const dir = args[0];
|
|
30
|
+
const prefixIdx = args.indexOf('--prefix');
|
|
31
|
+
const prefix = prefixIdx !== -1 && args[prefixIdx + 1] ? args[prefixIdx + 1] : 'competitor';
|
|
32
|
+
|
|
33
|
+
// Escape regex metacharacters in the user-supplied prefix so a value like
|
|
34
|
+
// "comp.+" matches the literal filename, not as a regex pattern.
|
|
35
|
+
const escapedPrefix = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
36
|
+
const pattern = new RegExp(`^${escapedPrefix}_discovery_batch_.*\\.json$`);
|
|
37
|
+
|
|
38
|
+
let files;
|
|
39
|
+
try {
|
|
40
|
+
files = readdirSync(dir)
|
|
41
|
+
.filter(f => pattern.test(f))
|
|
42
|
+
.sort();
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error(`Error reading directory ${dir}: ${err.message}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (files.length === 0) {
|
|
49
|
+
console.error(`No ${prefix}_discovery_batch_*.json files found in ${dir}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Dedup by hostname, but prefer the site root over a deep link. The first search hit for a
|
|
54
|
+
// domain is often a blog/doc/comparison path; gating + enrichment want the homepage, so when
|
|
55
|
+
// multiple URLs share a host we keep the shallowest path (fewest segments). First-seen host
|
|
56
|
+
// order is preserved (Map.set on an existing key keeps its position).
|
|
57
|
+
const byDomain = new Map(); // hostname -> { url, depth }
|
|
58
|
+
let totalResults = 0;
|
|
59
|
+
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
try {
|
|
62
|
+
const data = JSON.parse(readFileSync(join(dir, file), 'utf-8'));
|
|
63
|
+
const results = Array.isArray(data) ? data : (data.results || []);
|
|
64
|
+
totalResults += results.length;
|
|
65
|
+
|
|
66
|
+
for (const result of results) {
|
|
67
|
+
const url = result.url;
|
|
68
|
+
if (!url) continue;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const u = new URL(url);
|
|
72
|
+
const hostname = u.hostname.replace(/^www\./, '');
|
|
73
|
+
const depth = u.pathname.replace(/\/+$/, '').split('/').filter(Boolean).length;
|
|
74
|
+
const existing = byDomain.get(hostname);
|
|
75
|
+
if (!existing || depth < existing.depth) byDomain.set(hostname, { url, depth });
|
|
76
|
+
} catch {
|
|
77
|
+
// Skip invalid URLs
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error(`Warning: Failed to parse ${file}: ${err.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const urls = [...byDomain.values()].map(v => v.url);
|
|
86
|
+
for (const url of urls) {
|
|
87
|
+
console.log(url);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.error(`\n${files.length} files, ${totalResults} total results, ${urls.length} unique domains`);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Shared markdown parsing helpers for competitor-analysis scripts.
|
|
2
|
+
// Used by compile_report.mjs, merge_partials.mjs, and capture_screenshots.mjs.
|
|
3
|
+
|
|
4
|
+
// Parses YAML-ish frontmatter delimited by `---` lines.
|
|
5
|
+
// Returns an object of fields, or null if no frontmatter delimiter is found.
|
|
6
|
+
export function parseFrontmatter(content) {
|
|
7
|
+
content = content.replace(/\r\n/g, '\n'); // tolerate CRLF — anchors below assume LF
|
|
8
|
+
const m = content.match(/^---\n([\s\S]*?)\n---/);
|
|
9
|
+
if (!m) return null;
|
|
10
|
+
const fields = {};
|
|
11
|
+
for (const line of m[1].split('\n')) {
|
|
12
|
+
const idx = line.indexOf(':');
|
|
13
|
+
if (idx > 0) {
|
|
14
|
+
const k = line.slice(0, idx).trim();
|
|
15
|
+
const v = line.slice(idx + 1).trim().replace(/^["']|["']$/g, '');
|
|
16
|
+
if (k && v) fields[k] = v;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return fields;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Returns the body text after the closing `---` of the frontmatter, trimmed.
|
|
23
|
+
// If no frontmatter is present, returns the full content trimmed — so callers
|
|
24
|
+
// that don't gate on parseFrontmatter still get usable text.
|
|
25
|
+
export function parseBody(content) {
|
|
26
|
+
content = content.replace(/\r\n/g, '\n'); // tolerate CRLF — anchors below assume LF
|
|
27
|
+
const m = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)/);
|
|
28
|
+
return m ? m[1].trim() : content.trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Splits a markdown body into sections keyed by `## Heading` line.
|
|
32
|
+
// Content before the first `## ` is dropped (matches existing behavior).
|
|
33
|
+
export function parseSections(body) {
|
|
34
|
+
const sections = {};
|
|
35
|
+
const lines = body.replace(/\r\n/g, '\n').split('\n');
|
|
36
|
+
let currentKey = null;
|
|
37
|
+
let buffer = [];
|
|
38
|
+
for (const line of lines) {
|
|
39
|
+
const m = line.match(/^## (.+)$/);
|
|
40
|
+
if (m) {
|
|
41
|
+
if (currentKey !== null) sections[currentKey] = buffer.join('\n').trim();
|
|
42
|
+
currentKey = m[1].trim();
|
|
43
|
+
buffer = [];
|
|
44
|
+
} else if (currentKey !== null) {
|
|
45
|
+
buffer.push(line);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (currentKey !== null) sections[currentKey] = buffer.join('\n').trim();
|
|
49
|
+
return sections;
|
|
50
|
+
}
|