opencode-skills-collection 3.1.0 → 3.1.1
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/android-ui-journey-testing/SKILL.md +191 -0
- package/bundled-skills/ask-matt/SKILL.md +92 -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/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 +194 -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 +1 -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/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/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 +205 -0
- package/bundled-skills/loop-library/agents/openai.yaml +4 -0
- package/bundled-skills/loop-library/references/catalog.md +270 -0
- 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/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 +173 -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 +1956 -286
- 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,173 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Standalone viewer + API server for a YouTube deep-dive library.
|
|
3
|
+
|
|
4
|
+
Zero framework dependencies (Python stdlib + PyYAML). It serves the interactive
|
|
5
|
+
artifact and a small read/write API over a plain folder of markdown files, so the
|
|
6
|
+
whole thing runs anywhere with no custom backend.
|
|
7
|
+
|
|
8
|
+
python3 serve.py [--dir LIBRARY] [--port 8000] [--artifact path/to/artifact.html]
|
|
9
|
+
|
|
10
|
+
LIBRARY defaults to $VIDEO_LIBRARY_DIR or ~/video-deepdives. Layout:
|
|
11
|
+
LIBRARY/<YTID>.md one markdown file per video (frontmatter + transcript)
|
|
12
|
+
LIBRARY/_media/<YTID>-slide-NN.jpg slide images
|
|
13
|
+
|
|
14
|
+
Routes (the artifact talks to these; the /api/video-deepdives namespace is
|
|
15
|
+
arbitrary and kept only so the same artifact HTML works unmodified):
|
|
16
|
+
GET / the artifact (single-page app)
|
|
17
|
+
GET /api/video-deepdives list every video (flattened frontmatter)
|
|
18
|
+
GET /api/video-deepdives/<id> one video: {meta, body}
|
|
19
|
+
GET /api/video-deepdives/_media/<f> a slide image
|
|
20
|
+
PATCH /api/video-deepdives/<id> merge {fields:{...}} into frontmatter, rewrite
|
|
21
|
+
"""
|
|
22
|
+
import argparse, json, os, sys, re, mimetypes, posixpath
|
|
23
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
import yaml
|
|
27
|
+
except ImportError:
|
|
28
|
+
sys.exit("pip install pyyaml")
|
|
29
|
+
|
|
30
|
+
API = "/api/video-deepdives"
|
|
31
|
+
FM_RE = re.compile(r"^---\n(.*?)\n---\n?(.*)$", re.DOTALL)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def split_frontmatter(text):
|
|
35
|
+
"""Return (meta_dict, body_str) from a markdown file with YAML frontmatter."""
|
|
36
|
+
m = FM_RE.match(text)
|
|
37
|
+
if not m:
|
|
38
|
+
return {}, text
|
|
39
|
+
meta = yaml.safe_load(m.group(1)) or {}
|
|
40
|
+
return meta, m.group(2)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def dump_file(meta, body):
|
|
44
|
+
out = "---\n" + yaml.safe_dump(meta, sort_keys=False, allow_unicode=True, width=100) + "---\n"
|
|
45
|
+
return out + body
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def load_item(lib, slug):
|
|
49
|
+
path = os.path.join(lib, slug + ".md")
|
|
50
|
+
if not os.path.isfile(path):
|
|
51
|
+
return None
|
|
52
|
+
meta, body = split_frontmatter(open(path, encoding="utf-8").read())
|
|
53
|
+
return path, meta, body
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def list_items(lib):
|
|
57
|
+
items = []
|
|
58
|
+
for fn in sorted(os.listdir(lib)):
|
|
59
|
+
if not fn.endswith(".md") or fn.startswith("_"):
|
|
60
|
+
continue
|
|
61
|
+
slug = fn[:-3]
|
|
62
|
+
loaded = load_item(lib, slug)
|
|
63
|
+
if not loaded:
|
|
64
|
+
continue
|
|
65
|
+
_, meta, body = loaded
|
|
66
|
+
it = dict(meta)
|
|
67
|
+
it["slug"] = slug
|
|
68
|
+
it["file"] = fn
|
|
69
|
+
it["preview"] = body.strip()[:160]
|
|
70
|
+
items.append(it)
|
|
71
|
+
return items
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Handler(BaseHTTPRequestHandler):
|
|
75
|
+
lib = None
|
|
76
|
+
artifact = None
|
|
77
|
+
|
|
78
|
+
def log_message(self, *a):
|
|
79
|
+
pass # quiet
|
|
80
|
+
|
|
81
|
+
def _send(self, code, body, ctype="application/json"):
|
|
82
|
+
if isinstance(body, (dict, list)):
|
|
83
|
+
body = json.dumps(body).encode()
|
|
84
|
+
elif isinstance(body, str):
|
|
85
|
+
body = body.encode()
|
|
86
|
+
self.send_response(code)
|
|
87
|
+
self.send_header("Content-Type", ctype)
|
|
88
|
+
self.send_header("Content-Length", str(len(body)))
|
|
89
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
90
|
+
self.send_header("Access-Control-Allow-Methods", "GET, PATCH, OPTIONS")
|
|
91
|
+
self.send_header("Access-Control-Allow-Headers", "Content-Type")
|
|
92
|
+
self.end_headers()
|
|
93
|
+
if self.command != "HEAD":
|
|
94
|
+
self.wfile.write(body)
|
|
95
|
+
|
|
96
|
+
def do_OPTIONS(self):
|
|
97
|
+
self._send(204, b"")
|
|
98
|
+
|
|
99
|
+
def do_GET(self):
|
|
100
|
+
path = self.path.split("?", 1)[0].rstrip("/") or "/"
|
|
101
|
+
if path in ("/", "/index.html"):
|
|
102
|
+
try:
|
|
103
|
+
return self._send(200, open(self.artifact, encoding="utf-8").read(), "text/html; charset=utf-8")
|
|
104
|
+
except OSError:
|
|
105
|
+
return self._send(500, {"error": "artifact not found: " + self.artifact})
|
|
106
|
+
|
|
107
|
+
if path == API:
|
|
108
|
+
items = list_items(self.lib)
|
|
109
|
+
return self._send(200, {"collection": "video-deepdives", "total": len(items), "items": items})
|
|
110
|
+
|
|
111
|
+
if path.startswith(API + "/_media/"):
|
|
112
|
+
fn = posixpath.basename(path) # strip any traversal
|
|
113
|
+
fp = os.path.join(self.lib, "_media", fn)
|
|
114
|
+
if not os.path.isfile(fp):
|
|
115
|
+
return self._send(404, {"error": "no such media"})
|
|
116
|
+
ctype = mimetypes.guess_type(fp)[0] or "application/octet-stream"
|
|
117
|
+
with open(fp, "rb") as f:
|
|
118
|
+
return self._send(200, f.read(), ctype)
|
|
119
|
+
|
|
120
|
+
if path.startswith(API + "/"):
|
|
121
|
+
slug = posixpath.basename(path)
|
|
122
|
+
loaded = load_item(self.lib, slug)
|
|
123
|
+
if not loaded:
|
|
124
|
+
return self._send(404, {"error": "no such item"})
|
|
125
|
+
_, meta, body = loaded
|
|
126
|
+
return self._send(200, {"slug": slug, "type": "video-deepdive", "meta": meta, "body": body.rstrip("\n")})
|
|
127
|
+
|
|
128
|
+
return self._send(404, {"error": "not found"})
|
|
129
|
+
|
|
130
|
+
def do_PATCH(self):
|
|
131
|
+
path = self.path.split("?", 1)[0].rstrip("/")
|
|
132
|
+
if not path.startswith(API + "/"):
|
|
133
|
+
return self._send(404, {"error": "not found"})
|
|
134
|
+
slug = posixpath.basename(path)
|
|
135
|
+
loaded = load_item(self.lib, slug)
|
|
136
|
+
if not loaded:
|
|
137
|
+
return self._send(404, {"error": "no such item"})
|
|
138
|
+
fp, meta, body = loaded
|
|
139
|
+
try:
|
|
140
|
+
n = int(self.headers.get("Content-Length", 0))
|
|
141
|
+
payload = json.loads(self.rfile.read(n) or b"{}")
|
|
142
|
+
except (ValueError, json.JSONDecodeError):
|
|
143
|
+
return self._send(400, {"error": "bad json"})
|
|
144
|
+
fields = payload.get("fields", payload) # accept {fields:{...}} or a bare dict
|
|
145
|
+
if not isinstance(fields, dict):
|
|
146
|
+
return self._send(400, {"error": "fields must be an object"})
|
|
147
|
+
meta.update(fields)
|
|
148
|
+
open(fp, "w", encoding="utf-8").write(dump_file(meta, body))
|
|
149
|
+
return self._send(200, {"ok": True, "slug": slug, "updated": list(fields.keys())})
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def main():
|
|
153
|
+
ap = argparse.ArgumentParser()
|
|
154
|
+
ap.add_argument("--dir", default=os.path.expanduser(os.environ.get("VIDEO_LIBRARY_DIR", "~/video-deepdives")))
|
|
155
|
+
ap.add_argument("--port", type=int, default=int(os.environ.get("VIDEO_LIBRARY_PORT", "8000")))
|
|
156
|
+
ap.add_argument("--host", default="127.0.0.1")
|
|
157
|
+
here = os.path.dirname(os.path.abspath(__file__))
|
|
158
|
+
ap.add_argument("--artifact", default=os.path.join(here, "..", "reference", "artifact.html"))
|
|
159
|
+
a = ap.parse_args()
|
|
160
|
+
|
|
161
|
+
lib = os.path.abspath(os.path.expanduser(a.dir))
|
|
162
|
+
os.makedirs(lib, exist_ok=True)
|
|
163
|
+
Handler.lib = lib
|
|
164
|
+
Handler.artifact = os.path.abspath(a.artifact)
|
|
165
|
+
n = len([f for f in os.listdir(lib) if f.endswith(".md") and not f.startswith("_")])
|
|
166
|
+
print(f"Library: {lib} ({n} videos)")
|
|
167
|
+
print(f"Artifact: {Handler.artifact}")
|
|
168
|
+
print(f"Serving on http://{a.host}:{a.port}/ (Ctrl-C to stop)")
|
|
169
|
+
ThreadingHTTPServer((a.host, a.port), Handler).serve_forever()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if __name__ == "__main__":
|
|
173
|
+
main()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Resolve a YouTube id from a URL/id, print the scratch dir, and report embeddability.
|
|
3
|
+
# Usage: setup.sh "<youtube_url_or_id>"
|
|
4
|
+
#
|
|
5
|
+
# Library location is configurable via the VIDEO_LIBRARY_DIR env var
|
|
6
|
+
# (default: ~/video-deepdives). One markdown file per video lives there.
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
IN="${1:?usage: setup.sh <youtube_url_or_id>}"
|
|
9
|
+
LIB="${VIDEO_LIBRARY_DIR:-$HOME/video-deepdives}"
|
|
10
|
+
|
|
11
|
+
# Extract 11-char id from common URL shapes, or accept a bare id.
|
|
12
|
+
YTID="$(printf '%s' "$IN" | sed -nE 's#.*(youtu\.be/|v=|/embed/|/shorts/)([A-Za-z0-9_-]{11}).*#\2#p')"
|
|
13
|
+
[ -z "$YTID" ] && [ "${#IN}" -eq 11 ] && YTID="$IN"
|
|
14
|
+
[ -z "$YTID" ] && { echo "Could not parse a YouTube id from: $IN" >&2; exit 1; }
|
|
15
|
+
|
|
16
|
+
SCRATCH="/tmp/ytnote-$YTID"
|
|
17
|
+
mkdir -p "$SCRATCH"
|
|
18
|
+
|
|
19
|
+
# Embeddability: oembed returns 200 if embedding allowed, 401 if the owner disabled it.
|
|
20
|
+
CODE="$(curl -s -o /dev/null -w '%{http_code}' \
|
|
21
|
+
"https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=$YTID&format=json" || echo "000")"
|
|
22
|
+
if [ "$CODE" = "200" ]; then EMBED="allowed"; else EMBED="BLOCKED (oembed $CODE) — inline player disabled, artifact falls back to YouTube link"; fi
|
|
23
|
+
|
|
24
|
+
echo "YTID: $YTID"
|
|
25
|
+
echo "SCRATCH: $SCRATCH"
|
|
26
|
+
echo "EMBED: $EMBED"
|
|
27
|
+
echo "LIBRARY: $LIB/$YTID.md"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Verify a video is correctly served by the standalone server + appears in the index.
|
|
3
|
+
# Usage: verify.sh <YTID> [base_url]
|
|
4
|
+
# Start the server first: python3 scripts/serve.py --dir <LIBRARY> --port 8000
|
|
5
|
+
set -uo pipefail
|
|
6
|
+
YTID="${1:?usage: verify.sh <YTID> [base_url]}"
|
|
7
|
+
BASE="${2:-http://127.0.0.1:8000}" # standalone serve.py default port
|
|
8
|
+
COLL="$BASE/api/video-deepdives"
|
|
9
|
+
fail=0
|
|
10
|
+
|
|
11
|
+
code(){ curl -s -o /dev/null -w '%{http_code}' "$1"; }
|
|
12
|
+
|
|
13
|
+
echo "1) collection list:"
|
|
14
|
+
C=$(code "$COLL"); echo " GET $COLL -> $C"; [ "$C" = 200 ] || fail=1
|
|
15
|
+
if curl -s "$COLL" | grep -q "\"$YTID\""; then echo " ✓ $YTID present in index"; else echo " ✗ $YTID NOT in index"; fail=1; fi
|
|
16
|
+
|
|
17
|
+
echo "2) item:"
|
|
18
|
+
C=$(code "$COLL/$YTID"); echo " GET $COLL/$YTID -> $C"; [ "$C" = 200 ] || fail=1
|
|
19
|
+
|
|
20
|
+
echo "3) first slide image:"
|
|
21
|
+
C=$(code "$COLL/_media/$YTID-slide-01.jpg"); echo " GET .../_media/$YTID-slide-01.jpg -> $C"; [ "$C" = 200 ] || fail=1
|
|
22
|
+
|
|
23
|
+
echo "4) artifact shell:"
|
|
24
|
+
C=$(code "$BASE/"); echo " GET / -> $C"; [ "$C" = 200 ] || fail=1
|
|
25
|
+
|
|
26
|
+
if [ "$fail" = 0 ]; then
|
|
27
|
+
echo "ALL GOOD. Open: $BASE/#/$YTID"
|
|
28
|
+
else
|
|
29
|
+
echo "SOME CHECKS FAILED — is serve.py running and pointed at the library that contains $YTID?"
|
|
30
|
+
fi
|
|
31
|
+
exit $fail
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Convert a YouTube .vtt (manual or auto-captions) into clean [HH:MM:SS] transcript lines.
|
|
3
|
+
|
|
4
|
+
Usage: vtt_to_transcript.py <input.vtt> <output.txt>
|
|
5
|
+
|
|
6
|
+
Handles the rolling-duplicate problem in auto-captions: each cue repeats the tail of the
|
|
7
|
+
previous cue, so we keep only newly-added words per cue and emit one line per cue start
|
|
8
|
+
time. Strips inline <00:00:00.000> word-timing tags and HTML tags.
|
|
9
|
+
"""
|
|
10
|
+
import sys, re, html
|
|
11
|
+
|
|
12
|
+
TS=re.compile(r'(\d{2}):(\d{2}):(\d{2})\.\d{3}\s*-->\s*(\d{2}):(\d{2}):(\d{2})')
|
|
13
|
+
INLINE=re.compile(r'<[^>]+>')
|
|
14
|
+
|
|
15
|
+
def hhmmss(h,m,s): return f"[{int(h):02d}:{int(m):02d}:{int(s):02d}]"
|
|
16
|
+
|
|
17
|
+
def clean(text):
|
|
18
|
+
text=INLINE.sub('',text)
|
|
19
|
+
text=html.unescape(text)
|
|
20
|
+
return re.sub(r'\s+',' ',text).strip()
|
|
21
|
+
|
|
22
|
+
def main():
|
|
23
|
+
if len(sys.argv)!=3: sys.exit("usage: vtt_to_transcript.py <in.vtt> <out.txt>")
|
|
24
|
+
raw=open(sys.argv[1],encoding='utf-8',errors='replace').read().splitlines()
|
|
25
|
+
cues=[] # (start_label, text)
|
|
26
|
+
i=0; cur=None
|
|
27
|
+
while i<len(raw):
|
|
28
|
+
m=TS.search(raw[i])
|
|
29
|
+
if m:
|
|
30
|
+
if cur: cues.append(cur)
|
|
31
|
+
cur=[hhmmss(*m.groups()[:3]),[]]
|
|
32
|
+
i+=1
|
|
33
|
+
while i<len(raw) and not TS.search(raw[i]) and raw[i].strip()!='':
|
|
34
|
+
if raw[i].strip() and not raw[i].strip().isdigit():
|
|
35
|
+
cur[1].append(clean(raw[i]))
|
|
36
|
+
i+=1
|
|
37
|
+
else:
|
|
38
|
+
i+=1
|
|
39
|
+
if cur: cues.append(cur)
|
|
40
|
+
|
|
41
|
+
# De-duplicate rolling captions: keep only the suffix not already seen.
|
|
42
|
+
out=[]; seen_words=[]
|
|
43
|
+
for label,parts in cues:
|
|
44
|
+
text=clean(' '.join(parts))
|
|
45
|
+
if not text: continue
|
|
46
|
+
words=text.split()
|
|
47
|
+
# find longest overlap of seen tail with this cue's head
|
|
48
|
+
overlap=0; maxk=min(len(words),len(seen_words))
|
|
49
|
+
for k in range(maxk,0,-1):
|
|
50
|
+
if seen_words[-k:]==words[:k]: overlap=k; break
|
|
51
|
+
new=words[overlap:]
|
|
52
|
+
if new:
|
|
53
|
+
out.append(f"{label} {' '.join(new)}")
|
|
54
|
+
seen_words=(seen_words+new)[-40:] # bounded window
|
|
55
|
+
with open(sys.argv[2],'w',encoding='utf-8') as f:
|
|
56
|
+
f.write('\n'.join(out)+'\n')
|
|
57
|
+
print(f"wrote {len(out)} transcript lines -> {sys.argv[2]}")
|
|
58
|
+
|
|
59
|
+
if __name__=="__main__": main()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Assemble the library markdown file for a video deep-dive.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
write_library_item.py --id <YTID> --title "..." --speaker "..." \
|
|
6
|
+
--tags a,b,c --slides slides.json --transcript transcript.txt [--created YYYY-MM-DD]
|
|
7
|
+
|
|
8
|
+
slides.json: a JSON array of slide objects. Each:
|
|
9
|
+
{
|
|
10
|
+
"idx": 1, # sequence/original frame number (display only; sorted by t)
|
|
11
|
+
"t": 55.7, # seconds (float ok) — used for video seeking
|
|
12
|
+
"mmss": "00:55", # display label
|
|
13
|
+
"title": "Slide title", # short headline
|
|
14
|
+
"note": "1-3 sentences grounded in the transcript at this timestamp.",
|
|
15
|
+
"img": "/api/video-deepdives/_media/<YTID>-slide-01.jpg"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
Writes $VIDEO_LIBRARY_DIR/<YTID>.md (default ~/video-deepdives/<YTID>.md)
|
|
19
|
+
with YAML frontmatter + transcript body. No em dashes or arrows in titles/notes.
|
|
20
|
+
"""
|
|
21
|
+
import argparse, json, os, sys, datetime
|
|
22
|
+
try:
|
|
23
|
+
import yaml
|
|
24
|
+
except ImportError:
|
|
25
|
+
sys.exit("pip install pyyaml")
|
|
26
|
+
|
|
27
|
+
LIB = os.path.expanduser(os.environ.get("VIDEO_LIBRARY_DIR", "~/video-deepdives"))
|
|
28
|
+
|
|
29
|
+
def main():
|
|
30
|
+
ap=argparse.ArgumentParser()
|
|
31
|
+
ap.add_argument("--id",required=True)
|
|
32
|
+
ap.add_argument("--title",required=True)
|
|
33
|
+
ap.add_argument("--speaker",default="")
|
|
34
|
+
ap.add_argument("--tags",default="")
|
|
35
|
+
ap.add_argument("--slides",required=True)
|
|
36
|
+
ap.add_argument("--transcript",required=True)
|
|
37
|
+
ap.add_argument("--created",default=datetime.date.today().isoformat())
|
|
38
|
+
a=ap.parse_args()
|
|
39
|
+
|
|
40
|
+
slides=json.load(open(a.slides))
|
|
41
|
+
slides=sorted(slides,key=lambda s:s["t"])
|
|
42
|
+
for bad in ("—","→"):
|
|
43
|
+
for s in slides:
|
|
44
|
+
if bad in (s.get("title") or "")+(s.get("note") or ""):
|
|
45
|
+
sys.exit(f"Found forbidden char {bad!r} in slide notes/titles; remove it.")
|
|
46
|
+
|
|
47
|
+
fm={
|
|
48
|
+
"id":a.id,
|
|
49
|
+
"title":a.title,
|
|
50
|
+
"youtube_id":a.id,
|
|
51
|
+
"speaker":a.speaker,
|
|
52
|
+
"source_url":f"https://www.youtube.com/watch?v={a.id}",
|
|
53
|
+
"slide_count":len(slides),
|
|
54
|
+
"created":a.created,
|
|
55
|
+
"tags":[t.strip() for t in a.tags.split(",") if t.strip()],
|
|
56
|
+
"slides":slides,
|
|
57
|
+
}
|
|
58
|
+
body=open(a.transcript,encoding="utf-8").read().strip()
|
|
59
|
+
os.makedirs(LIB,exist_ok=True)
|
|
60
|
+
path=os.path.join(LIB,f"{a.id}.md")
|
|
61
|
+
with open(path,"w",encoding="utf-8") as f:
|
|
62
|
+
f.write("---\n")
|
|
63
|
+
yaml.safe_dump(fm,f,sort_keys=False,allow_unicode=True,width=100)
|
|
64
|
+
f.write("---\n## Transcript\n")
|
|
65
|
+
f.write(body+"\n")
|
|
66
|
+
print(f"wrote {path} ({len(slides)} slides, {len(body.splitlines())} transcript lines)")
|
|
67
|
+
print("Verify with: scripts/verify.sh "+a.id)
|
|
68
|
+
|
|
69
|
+
if __name__=="__main__": main()
|
package/package.json
CHANGED