opencode-skills-collection 3.1.14 → 3.1.16

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.
Files changed (164) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +36 -1
  2. package/bundled-skills/007/scripts/full_audit.py +34 -22
  3. package/bundled-skills/007/scripts/quick_scan.py +24 -15
  4. package/bundled-skills/007/scripts/scanners/dependency_scanner.py +31 -19
  5. package/bundled-skills/007/scripts/scanners/injection_scanner.py +24 -14
  6. package/bundled-skills/007/scripts/scanners/secrets_scanner.py +24 -14
  7. package/bundled-skills/007/scripts/score_calculator.py +25 -13
  8. package/bundled-skills/2slides-ppt-generator/scripts/download_slides_pages_voices.py +15 -1
  9. package/bundled-skills/aegisops-ai/SKILL.md +1 -1
  10. package/bundled-skills/agent-self-scheduling/SKILL.md +88 -0
  11. package/bundled-skills/alpha-vantage/SKILL.md +3 -1
  12. package/bundled-skills/anti-sleep/SKILL.md +90 -0
  13. package/bundled-skills/api-security-best-practices/SKILL.md +1 -1
  14. package/bundled-skills/app-store-optimization/SKILL.md +7 -0
  15. package/bundled-skills/audio-transcriber/scripts/transcribe.py +17 -4
  16. package/bundled-skills/azure-mgmt-botservice-py/SKILL.md +2 -1
  17. package/bundled-skills/azure-resource-manager-mysql-dotnet/SKILL.md +2 -1
  18. package/bundled-skills/azure-resource-manager-postgresql-dotnet/SKILL.md +2 -1
  19. package/bundled-skills/azure-resource-manager-sql-dotnet/SKILL.md +2 -1
  20. package/bundled-skills/biopython/SKILL.md +2 -1
  21. package/bundled-skills/brain-to-docs/SKILL.md +54 -0
  22. package/bundled-skills/browser-harness/SKILL.md +220 -0
  23. package/bundled-skills/browser-harness/references/install.md +132 -0
  24. package/bundled-skills/bumblebee/scripts/render_report.py +16 -3
  25. package/bundled-skills/bun-development/SKILL.md +1 -1
  26. package/bundled-skills/cc-skill-security-review/SKILL.md +2 -2
  27. package/bundled-skills/claude-monitor/scripts/monitor.py +15 -2
  28. package/bundled-skills/cmux/SKILL.md +250 -0
  29. package/bundled-skills/code-polish/SKILL.md +192 -0
  30. package/bundled-skills/code-review-checklist/SKILL.md +1 -1
  31. package/bundled-skills/codex-subagent/SKILL.md +117 -0
  32. package/bundled-skills/competitor-analysis/scripts/capture_screenshots.mjs +24 -3
  33. package/bundled-skills/competitor-analysis/scripts/compile_report.mjs +13 -2
  34. package/bundled-skills/competitor-analysis/scripts/extract_vs_names.mjs +23 -2
  35. package/bundled-skills/competitor-analysis/scripts/gate_candidates.mjs +21 -0
  36. package/bundled-skills/competitor-analysis/scripts/list_urls.mjs +23 -2
  37. package/bundled-skills/competitor-analysis/scripts/merge_partials.mjs +24 -3
  38. package/bundled-skills/container-security-hardening/SKILL.md +1 -1
  39. package/bundled-skills/content-creator/scripts/brand_voice_analyzer.py +15 -1
  40. package/bundled-skills/content-creator/scripts/seo_optimizer.py +15 -1
  41. package/bundled-skills/context-kit/SKILL.md +135 -0
  42. package/bundled-skills/cyber-audit/SKILL.md +138 -0
  43. package/bundled-skills/deepapi/SKILL.md +649 -0
  44. package/bundled-skills/delegating-to-agents/SKILL.md +64 -0
  45. package/bundled-skills/developer-signup-flow/SKILL.md +1 -1
  46. package/bundled-skills/diary/scripts/fetch_diaries.py +14 -1
  47. package/bundled-skills/diary/scripts/prepare_context.py +15 -2
  48. package/bundled-skills/diary/scripts/sync_to_notion.py +14 -1
  49. package/bundled-skills/dispatch/SKILL.md +19 -7
  50. package/bundled-skills/distribute-skill-to-all-agents/SKILL.md +85 -0
  51. package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
  52. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
  53. package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
  54. package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
  55. package/bundled-skills/docs/users/bundles.md +1 -1
  56. package/bundled-skills/docs/users/claude-code-skills.md +1 -1
  57. package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
  58. package/bundled-skills/docs/users/getting-started.md +1 -1
  59. package/bundled-skills/docs/users/kiro-integration.md +1 -1
  60. package/bundled-skills/docs/users/usage.md +4 -4
  61. package/bundled-skills/docs/users/visual-guide.md +4 -4
  62. package/bundled-skills/docx-official/ooxml/scripts/pack.py +30 -3
  63. package/bundled-skills/docx-official/ooxml/scripts/unpack.py +16 -3
  64. package/bundled-skills/drizzle-migration-conflict/scripts/check_drizzle_migrations.py +27 -17
  65. package/bundled-skills/ecl-harness-engineer/agents/creator-config.md +17 -3
  66. package/bundled-skills/ecl-harness-engineer/references/environment-detection-guide.md +9 -5
  67. package/bundled-skills/effective-agent-skills/SKILL.md +333 -0
  68. package/bundled-skills/electron-development/SKILL.md +6 -2
  69. package/bundled-skills/environment-setup-guide/SKILL.md +2 -5
  70. package/bundled-skills/expo-ui/scripts/list-components.js +5 -1
  71. package/bundled-skills/fable-safe-prompt/SKILL.md +79 -0
  72. package/bundled-skills/folder-specific-claude-and-agents-md/SKILL.md +96 -0
  73. package/bundled-skills/frontend-slides/scripts/extract-pptx.py +18 -4
  74. package/bundled-skills/gcp-cloud-run/SKILL.md +2 -1
  75. package/bundled-skills/gemini-api-integration/SKILL.md +3 -1
  76. package/bundled-skills/gemini-live-api-dev/SKILL.md +2 -1
  77. package/bundled-skills/gemini-omni-flash-api/scripts/video/generate_video.py +49 -14
  78. package/bundled-skills/goal-loop/SKILL.md +175 -0
  79. package/bundled-skills/hugging-face-evaluation/SKILL.md +1 -1
  80. package/bundled-skills/hugging-face-jobs/SKILL.md +1 -1
  81. package/bundled-skills/hugging-face-jobs/scripts/finepdfs-stats.py +14 -1
  82. package/bundled-skills/hugging-face-model-trainer/scripts/convert_to_gguf.py +10 -1
  83. package/bundled-skills/hugo-to-markdown/scripts/inventory_hugo_rules.py +14 -1
  84. package/bundled-skills/image-generator/SKILL.md +3 -1
  85. package/bundled-skills/instagram/scripts/export.py +18 -7
  86. package/bundled-skills/interview-style-doc-building/SKILL.md +86 -0
  87. package/bundled-skills/junta-leiloeiros/scripts/export.py +19 -10
  88. package/bundled-skills/landing-page-generator/scripts/landing_page_scaffolder.py +15 -1
  89. package/bundled-skills/linear-claude-skill/SKILL.md +4 -2
  90. package/bundled-skills/linkedin-post-writer/SKILL.md +159 -0
  91. package/bundled-skills/linkedin-post-writer/references/hook-formulas.md +482 -0
  92. package/bundled-skills/llm-council/SKILL.md +4 -2
  93. package/bundled-skills/loki-mode/examples/todo-app-generated/backend/package-lock.json +5 -6
  94. package/bundled-skills/loki-mode/examples/todo-app-generated/backend/package.json +2 -3
  95. package/bundled-skills/markdown-rendering/SKILL.md +72 -0
  96. package/bundled-skills/matematico-tao/scripts/complexity_analyzer.py +14 -1
  97. package/bundled-skills/matematico-tao/scripts/dependency_graph.py +15 -2
  98. package/bundled-skills/mobile-design/scripts/mobile_audit.py +20 -6
  99. package/bundled-skills/monte-carlo-validation-notebook/scripts/resolve_dbt_schema.py +15 -2
  100. package/bundled-skills/multi-agent-architect/SKILL.md +1 -1
  101. package/bundled-skills/odoo-rpc-api/SKILL.md +2 -1
  102. package/bundled-skills/pci-compliance/SKILL.md +3 -1
  103. package/bundled-skills/pdf-official/scripts/create_validation_image.py +17 -3
  104. package/bundled-skills/pdf-official/scripts/extract_form_field_info.py +1 -1
  105. package/bundled-skills/personal-tool-builder/SKILL.md +1 -1
  106. package/bundled-skills/pi-custom-model/SKILL.md +68 -0
  107. package/bundled-skills/pi-web-search/SKILL.md +74 -0
  108. package/bundled-skills/playwright-skill/run.js +19 -1
  109. package/bundled-skills/pptx-official/ooxml/scripts/pack.py +30 -3
  110. package/bundled-skills/pptx-official/ooxml/scripts/unpack.py +16 -3
  111. package/bundled-skills/pptx-official/scripts/inventory.py +17 -4
  112. package/bundled-skills/pptx-official/scripts/rearrange.py +15 -2
  113. package/bundled-skills/pptx-official/scripts/replace.py +16 -3
  114. package/bundled-skills/product-manager-toolkit/scripts/customer_interview_analyzer.py +15 -1
  115. package/bundled-skills/production-code-audit/SKILL.md +1 -1
  116. package/bundled-skills/push-skill-to-github/SKILL.md +61 -0
  117. package/bundled-skills/rclone-cli/references/commands/rclone_completion_powershell.md +3 -2
  118. package/bundled-skills/read-all-adrs/SKILL.md +35 -0
  119. package/bundled-skills/remote-gpu-trainer/scripts/verify_local.py +14 -1
  120. package/bundled-skills/research-prompt/SKILL.md +63 -0
  121. package/bundled-skills/run-deep-swe/SKILL.md +120 -0
  122. package/bundled-skills/senior-architect/scripts/architecture_diagram_generator.py +15 -2
  123. package/bundled-skills/senior-architect/scripts/dependency_analyzer.py +15 -2
  124. package/bundled-skills/senior-architect/scripts/project_architect.py +15 -2
  125. package/bundled-skills/senior-frontend/scripts/bundle_analyzer.py +14 -1
  126. package/bundled-skills/senior-frontend/scripts/frontend_scaffolder.py +14 -1
  127. package/bundled-skills/senior-fullstack/scripts/code_quality_analyzer.py +15 -2
  128. package/bundled-skills/senior-fullstack/scripts/fullstack_scaffolder.py +15 -2
  129. package/bundled-skills/senior-fullstack/scripts/project_scaffolder.py +15 -2
  130. package/bundled-skills/setup-help/SKILL.md +50 -0
  131. package/bundled-skills/shodan-reconnaissance/SKILL.md +2 -1
  132. package/bundled-skills/short/SKILL.md +28 -0
  133. package/bundled-skills/skill-creator/scripts/init_skill.py +15 -2
  134. package/bundled-skills/skill-creator/scripts/package_skill.py +29 -12
  135. package/bundled-skills/skill-creator/scripts/quick_validate.py +14 -2
  136. package/bundled-skills/skill-creator-ms/SKILL.md +1 -1
  137. package/bundled-skills/skill-installer/scripts/install_skill.py +42 -8
  138. package/bundled-skills/skill-installer/scripts/package_skill.py +29 -17
  139. package/bundled-skills/skill-installer/scripts/validate_skill.py +37 -22
  140. package/bundled-skills/stripe-integration/SKILL.md +7 -3
  141. package/bundled-skills/swiftui-expert-skill/scripts/instruments_parser/xctrace.py +7 -1
  142. package/bundled-skills/swiftui-expert-skill/scripts/instruments_parser/xml_utils.py +7 -1
  143. package/bundled-skills/taisly-social-media-posting/SKILL.md +79 -0
  144. package/bundled-skills/technical-tutorials/SKILL.md +1 -1
  145. package/bundled-skills/telegram/scripts/setup_project.py +17 -2
  146. package/bundled-skills/time-ledger/SKILL.md +118 -0
  147. package/bundled-skills/trading-ledger/SKILL.md +121 -0
  148. package/bundled-skills/videodb/scripts/ws_listener.py +17 -4
  149. package/bundled-skills/voice-ai-development/SKILL.md +2 -1
  150. package/bundled-skills/vps-server-management/SKILL.md +72 -0
  151. package/bundled-skills/weaviate/scripts/weaviate_conn.py +20 -18
  152. package/bundled-skills/wgm/SKILL.md +137 -0
  153. package/bundled-skills/whatsapp-cloud-api/scripts/setup_project.py +16 -1
  154. package/bundled-skills/workorai/SKILL.md +137 -0
  155. package/bundled-skills/wp-site-health-auditor/SKILL.md +341 -0
  156. package/bundled-skills/wp-site-health-auditor/references/catalog.md +135 -0
  157. package/bundled-skills/writing-skills/render-graphs.js +13 -2
  158. package/bundled-skills/x-twitter-scraper/SKILL.md +3 -1
  159. package/bundled-skills/youtube-notetaker/scripts/serve.py +41 -31
  160. package/bundled-skills/youtube-notetaker/scripts/vtt_to_transcript.py +16 -2
  161. package/bundled-skills/youtube-notetaker/scripts/write_library_item.py +15 -1
  162. package/bundled-skills/youtube-transcript/SKILL.md +120 -0
  163. package/package.json +5 -1
  164. package/skills_index.json +786 -6
@@ -19,7 +19,7 @@ arbitrary and kept only so the same artifact HTML works unmodified):
19
19
  GET /api/video-deepdives/_media/<f> a slide image
20
20
  PATCH /api/video-deepdives/<id> merge {fields:{...}} into frontmatter, rewrite
21
21
  """
22
- import argparse, json, os, sys, re, mimetypes, posixpath
22
+ import argparse, json, os, sys, re, posixpath
23
23
  from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
24
24
  from pathlib import Path
25
25
  from tempfile import TemporaryDirectory
@@ -35,6 +35,13 @@ SAFE_SLUG_RE = re.compile(r"^[A-Za-z0-9_-]+$")
35
35
  SAFE_MEDIA_RE = re.compile(r"^[A-Za-z0-9_.-]+$")
36
36
  SAFE_PATH_PART_RE = re.compile(r"^[A-Za-z0-9_.-]+$")
37
37
  SAFE_CTYPE_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*(?:; charset=[A-Za-z0-9._-]+)?$")
38
+ MEDIA_CONTENT_TYPES = {
39
+ ".gif": "image/gif",
40
+ ".jpeg": "image/jpeg",
41
+ ".jpg": "image/jpeg",
42
+ ".png": "image/png",
43
+ ".webp": "image/webp",
44
+ }
38
45
  LOCAL_ORIGINS = {
39
46
  "http://127.0.0.1:8000": "http://127.0.0.1:8000",
40
47
  "http://localhost:8000": "http://localhost:8000",
@@ -55,46 +62,38 @@ def dump_file(meta, body):
55
62
  return out + body
56
63
 
57
64
 
58
- def library_path(lib, *parts):
59
- root = Path(lib).resolve()
60
- candidate = root
61
- for part in parts:
62
- value = str(part)
63
- if not SAFE_PATH_PART_RE.fullmatch(value) or value in {".", ".."}:
64
- return None
65
- candidate = candidate / value
66
- candidate = candidate.resolve()
65
+ def listed_file(directory, filename, *, allow_directory_symlink=False):
66
+ if not SAFE_PATH_PART_RE.fullmatch(filename or "") or filename in {".", ".."}:
67
+ return None
67
68
  try:
68
- candidate.relative_to(root)
69
- except ValueError:
69
+ directory_path = Path(directory)
70
+ if directory_path.is_symlink() and not allow_directory_symlink:
71
+ return None
72
+ root = directory_path.resolve(strict=True)
73
+ for path in root.iterdir():
74
+ if path.name != filename:
75
+ continue
76
+ if path.is_symlink() or not path.is_file():
77
+ return None
78
+ try:
79
+ path.resolve(strict=True).relative_to(root)
80
+ except (OSError, ValueError):
81
+ return None
82
+ return path
83
+ except OSError:
70
84
  return None
71
- return candidate
72
85
 
73
86
 
74
87
  def media_path(lib, filename):
75
88
  if not SAFE_MEDIA_RE.fullmatch(filename or ""):
76
89
  return None
77
- root = Path(lib).resolve()
78
- candidate = root / "_media" / filename
79
- if candidate.is_symlink():
80
- return None
81
- path = library_path(lib, "_media", filename)
82
- if not path or not path.is_file():
83
- return None
84
- return path
90
+ return listed_file(Path(lib) / "_media", filename)
85
91
 
86
92
 
87
93
  def item_path(lib, slug):
88
94
  if not SAFE_SLUG_RE.fullmatch(slug or ""):
89
95
  return None
90
- root = Path(lib).resolve()
91
- candidate = root / (slug + ".md")
92
- if candidate.is_symlink():
93
- return None
94
- path = library_path(lib, slug + ".md")
95
- if not path or not path.is_file():
96
- return None
97
- return path
96
+ return listed_file(lib, slug + ".md", allow_directory_symlink=True)
98
97
 
99
98
 
100
99
  def safe_content_type(ctype):
@@ -105,6 +104,10 @@ def safe_local_origin(origin):
105
104
  return LOCAL_ORIGINS.get(origin or "")
106
105
 
107
106
 
107
+ def media_content_type(filename):
108
+ return MEDIA_CONTENT_TYPES.get(Path(filename).suffix.lower(), "application/octet-stream")
109
+
110
+
108
111
  def load_item(lib, slug):
109
112
  path = item_path(lib, slug)
110
113
  if not path or not path.is_file():
@@ -181,7 +184,7 @@ class Handler(BaseHTTPRequestHandler):
181
184
  fp = media_path(self.lib, fn)
182
185
  if not fp or not fp.is_file():
183
186
  return self._send(404, {"error": "no such media"})
184
- ctype = mimetypes.guess_type(str(fp))[0] or "application/octet-stream"
187
+ ctype = media_content_type(fn)
185
188
  return self._send(200, fp.read_bytes(), ctype)
186
189
 
187
190
  if path.startswith(API + "/"):
@@ -226,16 +229,23 @@ def self_test():
226
229
  (root / "video_1.md").write_text("---\ntitle: Demo\n---\nBody", encoding="utf-8")
227
230
  (root / "_media").mkdir()
228
231
  (root / "_media" / "video_1-slide-01.jpg").write_bytes(b"x")
232
+ media_target = root / "media-target"
233
+ media_target.mkdir()
234
+ (media_target / "outside.jpg").write_bytes(b"outside")
229
235
  (root / "secret.md").write_text("secret", encoding="utf-8")
230
236
  (root / "linked.md").symlink_to(root / "secret.md")
231
237
  (root / "_media" / "linked.jpg").symlink_to(root / "secret.md")
238
+ (root / "_media_link").symlink_to(media_target)
232
239
  assert load_item(str(root), "video_1")
233
240
  assert load_item(str(root), "linked") is None
234
241
  assert media_path(str(root), "linked.jpg") is None
242
+ assert listed_file(root / "_media_link", "outside.jpg") is None
235
243
  assert load_item(str(root), "../secret") is None
236
- assert library_path(str(root), "_media", "../video_1.md") is None
244
+ assert listed_file(root / "_media", "../video_1.md") is None
237
245
  assert safe_content_type("text/html; charset=utf-8") == "text/html; charset=utf-8"
238
246
  assert safe_content_type("text/html\r\nX-Bad: 1") == "application/octet-stream"
247
+ assert media_content_type("video_1-slide-01.jpg") == "image/jpeg"
248
+ assert media_content_type("video_1-slide-01.svg") == "application/octet-stream"
239
249
  assert safe_local_origin("http://localhost:8000") == LOCAL_ORIGINS["http://localhost:8000"]
240
250
  assert safe_local_origin("http://localhost:3000") is None
241
251
  assert safe_local_origin("http://localhost:8000\r\nX-Bad: 1") is None
@@ -8,6 +8,20 @@ previous cue, so we keep only newly-added words per cue and emit one line per cu
8
8
  time. Strips inline <00:00:00.000> word-timing tags and HTML tags.
9
9
  """
10
10
  import sys, re, html
11
+ from pathlib import Path
12
+
13
+
14
+ def safe_user_path(path_value, base_dir="."):
15
+ """Resolve a CLI path under the current workspace."""
16
+ if base_dir != ".":
17
+ raise ValueError("Custom base directories are not supported for CLI paths")
18
+ base_path = Path.cwd().resolve()
19
+ resolved_path = Path(path_value).expanduser().resolve()
20
+ try:
21
+ resolved_path.relative_to(base_path)
22
+ except ValueError as exc:
23
+ raise ValueError(f"Path escapes allowed directory: {path_value}") from exc
24
+ return resolved_path
11
25
 
12
26
  TS=re.compile(r'(\d{2}):(\d{2}):(\d{2})\.\d{3}\s*-->\s*(\d{2}):(\d{2}):(\d{2})')
13
27
  INLINE=re.compile(r'<[^>]+>')
@@ -21,7 +35,7 @@ def clean(text):
21
35
 
22
36
  def main():
23
37
  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()
38
+ raw=safe_user_path(sys.argv[1]).open(encoding='utf-8',errors='replace').read().splitlines()
25
39
  cues=[] # (start_label, text)
26
40
  i=0; cur=None
27
41
  while i<len(raw):
@@ -52,7 +66,7 @@ def main():
52
66
  if new:
53
67
  out.append(f"{label} {' '.join(new)}")
54
68
  seen_words=(seen_words+new)[-40:] # bounded window
55
- with open(sys.argv[2],'w',encoding='utf-8') as f:
69
+ with safe_user_path(sys.argv[2]).open('w',encoding='utf-8') as f:
56
70
  f.write('\n'.join(out)+'\n')
57
71
  print(f"wrote {len(out)} transcript lines -> {sys.argv[2]}")
58
72
 
@@ -19,6 +19,20 @@ Writes $VIDEO_LIBRARY_DIR/<YTID>.md (default ~/video-deepdives/<YTID>.md)
19
19
  with YAML frontmatter + transcript body. No em dashes or arrows in titles/notes.
20
20
  """
21
21
  import argparse, json, os, sys, datetime
22
+ from pathlib import Path
23
+
24
+
25
+ def safe_user_path(path_value, base_dir="."):
26
+ """Resolve a CLI path under the current workspace."""
27
+ if base_dir != ".":
28
+ raise ValueError("Custom base directories are not supported for CLI paths")
29
+ base_path = Path.cwd().resolve()
30
+ resolved_path = Path(path_value).expanduser().resolve()
31
+ try:
32
+ resolved_path.relative_to(base_path)
33
+ except ValueError as exc:
34
+ raise ValueError(f"Path escapes allowed directory: {path_value}") from exc
35
+ return resolved_path
22
36
  try:
23
37
  import yaml
24
38
  except ImportError:
@@ -58,7 +72,7 @@ def main():
58
72
  body=open(a.transcript,encoding="utf-8").read().strip()
59
73
  os.makedirs(LIB,exist_ok=True)
60
74
  path=os.path.join(LIB,f"{a.id}.md")
61
- with open(path,"w",encoding="utf-8") as f:
75
+ with safe_user_path(path).open("w",encoding="utf-8") as f:
62
76
  f.write("---\n")
63
77
  yaml.safe_dump(fm,f,sort_keys=False,allow_unicode=True,width=100)
64
78
  f.write("---\n## Transcript\n")
@@ -0,0 +1,120 @@
1
+ ---
2
+ name: youtube-transcript
3
+ description: "Fetch YouTube transcripts through DeepAPI or local fallback tooling and save clean text output."
4
+ category: research
5
+ risk: safe
6
+ source: community
7
+ source_repo: davidondrej/skills
8
+ source_type: community
9
+ date_added: "2026-07-07"
10
+ author: davidondrej
11
+ tags: [youtube, transcripts, research]
12
+ tools: [claude, codex]
13
+ license: "MIT"
14
+ license_source: "https://github.com/davidondrej/skills/blob/main/LICENSE"
15
+ ---
16
+
17
+ # YouTube Transcript (via DeepAPI, yt-dlp fallback)
18
+
19
+ ## When to Use
20
+
21
+ - Use when the user asks for a YouTube transcript, captions, subtitles, or spoken-content extraction.
22
+ - Use when DeepAPI or a local fallback can fetch the transcript safely.
23
+
24
+ Fetch a YouTube video's transcript and save a clean raw `.txt` file. Primary path is DeepAPI `POST /v1/scrape/youtube/transcript`. It runs server-side, so it avoids the local-IP bot flagging that plagues yt-dlp.
25
+
26
+ ## Save location
27
+ - If the user is in a real project/working dir → save there.
28
+ - Otherwise (no dir given, or cwd makes no sense) → save to `~/Downloads`.
29
+ - **Always name the file `Channel_Title` with spaces replaced by `_`** (e.g. `David_Ondrej_title_of_video.txt`). If metadata is unavailable, fall back to the video ID.
30
+
31
+ ## Primary path — DeepAPI
32
+
33
+ `DEEPAPI_API_KEY` must already be present in the environment. Do not read shell
34
+ startup files or print secrets:
35
+
36
+ ```bash
37
+ test -n "$DEEPAPI_API_KEY" || { echo "DEEPAPI_API_KEY is not set"; exit 1; }
38
+ BASE=${DEEPAPI_API_BASE_URL:-https://deepapi.co}
39
+ ```
40
+
41
+ Run the scrape (keep the Idempotency-Key; retries must reuse the SAME one):
42
+
43
+ ```bash
44
+ IDK=$(uuidgen)
45
+ curl -s --max-time 120 "$BASE/v1/scrape/youtube/transcript" \
46
+ -H "Authorization: Bearer $DEEPAPI_API_KEY" \
47
+ -H "Content-Type: application/json" \
48
+ -H "Idempotency-Key: $IDK" \
49
+ -d '{"url": "VIDEO_URL", "maxCostUsd": "0.05", "waitForFinishSecs": 60}' \
50
+ > /tmp/yt_transcript.json
51
+ ```
52
+
53
+ - Non-English videos: add `"language": "de"` (etc.) to the body.
54
+ - `status: running` → wait `next.afterSecs`, then `curl "$BASE$(jq -r '.next.path' /tmp/yt_transcript.json)" -H "Authorization: Bearer $KEY"` until `succeeded` or `failed`.
55
+
56
+ Extract the text and save it:
57
+
58
+ ```bash
59
+ jq -r '.status' /tmp/yt_transcript.json # succeeded | running | failed
60
+ jq -r '.output[0].text' /tmp/yt_transcript.json > "$OUT/$NAME.txt"
61
+ jq -r '.debitMicrousd' /tmp/yt_transcript.json # cost (50000 = $0.05)
62
+ ```
63
+
64
+ `.output[0].segments` also has timed segments (`startSecs`, `durationSecs`, `text`) if the user wants timestamps. Empty `output` = video has no captions; report it, don't retry.
65
+
66
+ For the `Channel_Title` filename, get metadata with a quick `yt-dlp --print "%(channel)s|%(title)s" --skip-download "URL"`; if that fails, use the video ID.
67
+
68
+ ## When to fall back to yt-dlp
69
+
70
+ - `DEEPAPI_API_KEY` missing from the environment.
71
+ - HTTP 402 `insufficient_credits` (tell the user to top up at deepapi.co/credits first; fall back only if they're unavailable).
72
+ - DeepAPI request `failed` twice.
73
+
74
+ Tell the user whenever you fall back — a fallback means the product missed a real use case.
75
+
76
+ ## Fallback path — yt-dlp (local)
77
+
78
+ ```bash
79
+ OUT="$(pwd)" # or ~/Downloads if cwd makes no sense
80
+ META=$(yt-dlp --print "%(channel)s|%(title)s" --skip-download "URL")
81
+ NAME=$(echo "$META" | tr '| ' '__' | tr -cd '[:alnum:]_.-') # "Channel_Title", spaces -> _, strip unsafe chars
82
+ yt-dlp --skip-download --write-subs --write-auto-subs \
83
+ --sub-langs "en.*" --sub-format json3 \
84
+ -o "$OUT/$NAME.%(ext)s" "URL"
85
+ ```
86
+
87
+ - Fall back `channel` → `uploader` → `uploader_id` if `channel` is null.
88
+ - `--skip-download` = captions only. `--write-subs` + `--write-auto-subs` = manual first, auto as fallback.
89
+ - **Always use `json3`, never VTT/SRT** — auto VTT repeats every line twice (rolling captions).
90
+
91
+ Flatten json3 → raw text:
92
+
93
+ ```bash
94
+ python3 - "$OUT" <<'PY'
95
+ import json, html, re, glob, sys, pathlib
96
+ f = glob.glob(sys.argv[1] + "/*.json3")
97
+ if not f: sys.exit("no json3 file")
98
+ data = json.load(open(f[0], encoding="utf-8"))
99
+ parts = ["".join(s.get("utf8","") for s in e.get("segs") or []) for e in data.get("events", [])]
100
+ txt = re.sub(r"\s+", " ", html.unescape(" ".join(p.strip() for p in parts if p.strip()))).strip()
101
+ out = pathlib.Path(f[0]).with_suffix(".txt")
102
+ out.write_text(txt, encoding="utf-8"); print(out)
103
+ PY
104
+ ```
105
+
106
+ ### yt-dlp failure handling
107
+ - Non-English / unknown language: run `yt-dlp --list-subs "URL"` first, then set `--sub-langs`.
108
+ - Newer yt-dlp may need `deno` on PATH for YouTube extraction.
109
+ - On first failure: run `yt-dlp -U` once, retry once, then stop.
110
+ - **429 / "Sign in to confirm you're not a bot"** = IP flagged. STOP — do NOT retry in a loop (makes it worse).
111
+ - Never fall back to downloading audio for Whisper unless the user explicitly asks.
112
+
113
+ ## Output
114
+
115
+ Report the saved path; print the text if short. If DeepAPI was used, also report the cost in dollars.
116
+
117
+ ## Limitations
118
+
119
+ - Adapted from `davidondrej/skills`; verify local paths, tools, credentials, and agent features before acting.
120
+ - For commands, remote access, scheduling, browser automation, or file-changing workflows, get explicit user approval and confirm the target environment first.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-skills-collection",
3
- "version": "3.1.14",
3
+ "version": "3.1.16",
4
4
  "description": "OpenCode CLI plugin that automatically downloads and keeps skills up to date.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -33,6 +33,10 @@
33
33
  ],
34
34
  "author": "Davide Ladisa <info@davideladisa.it>",
35
35
  "license": "MIT",
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "provenance": true
39
+ },
36
40
  "dependencies": {
37
41
  "@opencode-ai/plugin": "^1.15.5",
38
42
  "strip-json-comments": "^5.0.3"