@opendirectory.dev/skills 0.1.0

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 (212) hide show
  1. package/.claude/skills/claude-md-generator/.env.example +7 -0
  2. package/.claude/skills/claude-md-generator/README.md +78 -0
  3. package/.claude/skills/claude-md-generator/SKILL.md +248 -0
  4. package/.claude/skills/claude-md-generator/evals/evals.json +35 -0
  5. package/.claude/skills/claude-md-generator/references/section-guide.md +175 -0
  6. package/dist/e2e.test.d.ts +1 -0
  7. package/dist/e2e.test.js +62 -0
  8. package/dist/fs-adapters.d.ts +4 -0
  9. package/dist/fs-adapters.js +101 -0
  10. package/dist/fs-adapters.test.d.ts +1 -0
  11. package/dist/fs-adapters.test.js +108 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +211 -0
  14. package/dist/transformers.d.ts +6 -0
  15. package/dist/transformers.js +2 -0
  16. package/package.json +25 -0
  17. package/registry.json +226 -0
  18. package/skills/blog-cover-image-cli/.github/workflows/publish.yml +19 -0
  19. package/skills/blog-cover-image-cli/LICENSE +15 -0
  20. package/skills/blog-cover-image-cli/README.md +126 -0
  21. package/skills/blog-cover-image-cli/SKILL.md +7 -0
  22. package/skills/blog-cover-image-cli/agent-skill/blog-cover-generator/README.md +30 -0
  23. package/skills/blog-cover-image-cli/agent-skill/blog-cover-generator/SKILL.md +72 -0
  24. package/skills/blog-cover-image-cli/bin/cli.js +226 -0
  25. package/skills/blog-cover-image-cli/examples/100x_UX_Research_AI_Agent.png +0 -0
  26. package/skills/blog-cover-image-cli/examples/Firecrawl-supabase-bolt.png +0 -0
  27. package/skills/blog-cover-image-cli/examples/Git-City_Case_study_Cover_Image.jpg +0 -0
  28. package/skills/blog-cover-image-cli/examples/THE DISTRIBUTION LAYER (2).png +0 -0
  29. package/skills/blog-cover-image-cli/examples/canva-perplexity-duolingo-cover-image.png +0 -0
  30. package/skills/blog-cover-image-cli/examples/gamma-mistral-veed.png +0 -0
  31. package/skills/blog-cover-image-cli/examples/server-survival-case-study-cover-image(1).png +0 -0
  32. package/skills/blog-cover-image-cli/examples/viral-meme-automation.png +0 -0
  33. package/skills/blog-cover-image-cli/index.js +2 -0
  34. package/skills/blog-cover-image-cli/package-lock.json +2238 -0
  35. package/skills/blog-cover-image-cli/package.json +37 -0
  36. package/skills/blog-cover-image-cli/src/geminiGenerator.js +126 -0
  37. package/skills/blog-cover-image-cli/src/imageValidator.js +54 -0
  38. package/skills/blog-cover-image-cli/src/logoFetcher.js +86 -0
  39. package/skills/claude-md-generator/.env.example +7 -0
  40. package/skills/claude-md-generator/README.md +78 -0
  41. package/skills/claude-md-generator/SKILL.md +254 -0
  42. package/skills/claude-md-generator/evals/evals.json +35 -0
  43. package/skills/claude-md-generator/references/section-guide.md +175 -0
  44. package/skills/cook-the-blog/README.md +86 -0
  45. package/skills/cook-the-blog/SKILL.md +130 -0
  46. package/skills/dependency-update-bot/.env.example +13 -0
  47. package/skills/dependency-update-bot/README.md +101 -0
  48. package/skills/dependency-update-bot/SKILL.md +376 -0
  49. package/skills/dependency-update-bot/evals/evals.json +45 -0
  50. package/skills/dependency-update-bot/references/changelog-patterns.md +201 -0
  51. package/skills/docs-from-code/.env.example +13 -0
  52. package/skills/docs-from-code/README.md +97 -0
  53. package/skills/docs-from-code/SKILL.md +160 -0
  54. package/skills/docs-from-code/evals/evals.json +29 -0
  55. package/skills/docs-from-code/references/extraction-guide.md +174 -0
  56. package/skills/docs-from-code/references/output-template.md +135 -0
  57. package/skills/docs-from-code/scripts/extract_py.py +238 -0
  58. package/skills/docs-from-code/scripts/extract_ts.ts +284 -0
  59. package/skills/docs-from-code/scripts/package.json +18 -0
  60. package/skills/explain-this-pr/README.md +74 -0
  61. package/skills/explain-this-pr/SKILL.md +130 -0
  62. package/skills/explain-this-pr/evals/evals.json +35 -0
  63. package/skills/google-trends-api-skills/README.md +78 -0
  64. package/skills/google-trends-api-skills/SKILL.md +7 -0
  65. package/skills/google-trends-api-skills/google-trends-api/SKILL.md +163 -0
  66. package/skills/google-trends-api-skills/google-trends-api/references/api-responses.md +188 -0
  67. package/skills/google-trends-api-skills/google-trends-api/scripts/discover_keywords.py +344 -0
  68. package/skills/google-trends-api-skills/seo-keyword-research/SKILL.md +205 -0
  69. package/skills/google-trends-api-skills/seo-keyword-research/references/keyword-placement-guide.md +89 -0
  70. package/skills/google-trends-api-skills/seo-keyword-research/references/tech-blog-examples.md +207 -0
  71. package/skills/google-trends-api-skills/seo-keyword-research/scripts/blog_seo_research.py +373 -0
  72. package/skills/hackernews-intel/.env.example +33 -0
  73. package/skills/hackernews-intel/README.md +161 -0
  74. package/skills/hackernews-intel/SKILL.md +156 -0
  75. package/skills/hackernews-intel/evals/evals.json +35 -0
  76. package/skills/hackernews-intel/package.json +15 -0
  77. package/skills/hackernews-intel/scripts/monitor-hn.js +258 -0
  78. package/skills/kill-the-standup/.env.example +22 -0
  79. package/skills/kill-the-standup/README.md +84 -0
  80. package/skills/kill-the-standup/SKILL.md +169 -0
  81. package/skills/kill-the-standup/evals/evals.json +35 -0
  82. package/skills/kill-the-standup/references/standup-format.md +102 -0
  83. package/skills/linkedin-post-generator/.env.example +14 -0
  84. package/skills/linkedin-post-generator/README.md +107 -0
  85. package/skills/linkedin-post-generator/SKILL.md +228 -0
  86. package/skills/linkedin-post-generator/evals/evals.json +35 -0
  87. package/skills/linkedin-post-generator/references/linkedin-format.md +216 -0
  88. package/skills/linkedin-post-generator/references/output-template.md +154 -0
  89. package/skills/llms-txt-generator/.env.example +18 -0
  90. package/skills/llms-txt-generator/README.md +142 -0
  91. package/skills/llms-txt-generator/SKILL.md +176 -0
  92. package/skills/llms-txt-generator/evals/evals.json +35 -0
  93. package/skills/llms-txt-generator/references/llms-txt-spec.md +88 -0
  94. package/skills/llms-txt-generator/references/output-template.md +76 -0
  95. package/skills/llms-txt-generator/test-output/genzcareer.in/llms.txt +31 -0
  96. package/skills/luma-attendees-scraper/README.md +170 -0
  97. package/skills/luma-attendees-scraper/SKILL.md +7 -0
  98. package/skills/luma-attendees-scraper/luma_attendees_export.js +223 -0
  99. package/skills/meeting-brief-generator/.env.example +21 -0
  100. package/skills/meeting-brief-generator/README.md +90 -0
  101. package/skills/meeting-brief-generator/SKILL.md +275 -0
  102. package/skills/meeting-brief-generator/evals/evals.json +35 -0
  103. package/skills/meeting-brief-generator/references/brief-format.md +114 -0
  104. package/skills/meeting-brief-generator/references/output-template.md +150 -0
  105. package/skills/meta-ads-skill/README.md +100 -0
  106. package/skills/meta-ads-skill/SKILL.md +7 -0
  107. package/skills/meta-ads-skill/meta-ads-skill/SKILL.md +41 -0
  108. package/skills/meta-ads-skill/meta-ads-skill/references/report_templates.md +47 -0
  109. package/skills/meta-ads-skill/meta-ads-skill/references/workflows.md +51 -0
  110. package/skills/meta-ads-skill/meta-ads-skill/scripts/auth_check.py +22 -0
  111. package/skills/meta-ads-skill/meta-ads-skill/scripts/formatters.py +46 -0
  112. package/skills/newsletter-digest/.env.example +20 -0
  113. package/skills/newsletter-digest/README.md +147 -0
  114. package/skills/newsletter-digest/SKILL.md +221 -0
  115. package/skills/newsletter-digest/evals/evals.json +35 -0
  116. package/skills/newsletter-digest/feeds.json +7 -0
  117. package/skills/newsletter-digest/package.json +15 -0
  118. package/skills/newsletter-digest/references/digest-format.md +123 -0
  119. package/skills/newsletter-digest/references/output-template.md +136 -0
  120. package/skills/newsletter-digest/scripts/fetch-feeds.js +141 -0
  121. package/skills/newsletter-digest/scripts/ghost-publish.js +147 -0
  122. package/skills/noise2blog/.env.example +16 -0
  123. package/skills/noise2blog/README.md +107 -0
  124. package/skills/noise2blog/SKILL.md +229 -0
  125. package/skills/noise2blog/evals/evals.json +35 -0
  126. package/skills/noise2blog/references/blog-format.md +188 -0
  127. package/skills/noise2blog/references/output-template.md +184 -0
  128. package/skills/outreach-sequence-builder/.env.example +12 -0
  129. package/skills/outreach-sequence-builder/README.md +108 -0
  130. package/skills/outreach-sequence-builder/SKILL.md +248 -0
  131. package/skills/outreach-sequence-builder/evals/evals.json +36 -0
  132. package/skills/outreach-sequence-builder/references/output-template.md +171 -0
  133. package/skills/outreach-sequence-builder/references/sequence-format.md +167 -0
  134. package/skills/outreach-sequence-builder/references/signal-playbook.md +117 -0
  135. package/skills/position-me/README.md +71 -0
  136. package/skills/position-me/SKILL.md +7 -0
  137. package/skills/position-me/position-me/SKILL.md +50 -0
  138. package/skills/position-me/position-me/references/EVALUATION_SOP.md +40 -0
  139. package/skills/position-me/position-me/references/REPORT_TEMPLATE.md +58 -0
  140. package/skills/position-me/position-me/scripts/extract_links.py +49 -0
  141. package/skills/pr-description-writer/README.md +81 -0
  142. package/skills/pr-description-writer/SKILL.md +141 -0
  143. package/skills/pr-description-writer/evals/evals.json +35 -0
  144. package/skills/pr-description-writer/references/pr-format-guide.md +145 -0
  145. package/skills/producthunt-launch-kit/.env.example +7 -0
  146. package/skills/producthunt-launch-kit/README.md +95 -0
  147. package/skills/producthunt-launch-kit/SKILL.md +380 -0
  148. package/skills/producthunt-launch-kit/evals/evals.json +35 -0
  149. package/skills/producthunt-launch-kit/references/copy-rules.md +124 -0
  150. package/skills/reddit-icp-monitor/.env.example +16 -0
  151. package/skills/reddit-icp-monitor/README.md +117 -0
  152. package/skills/reddit-icp-monitor/SKILL.md +271 -0
  153. package/skills/reddit-icp-monitor/evals/evals.json +40 -0
  154. package/skills/reddit-icp-monitor/references/icp-format.md +131 -0
  155. package/skills/reddit-icp-monitor/references/reply-rules.md +110 -0
  156. package/skills/reddit-post-engine/.env.example +13 -0
  157. package/skills/reddit-post-engine/README.md +103 -0
  158. package/skills/reddit-post-engine/SKILL.md +303 -0
  159. package/skills/reddit-post-engine/evals/evals.json +35 -0
  160. package/skills/reddit-post-engine/references/subreddit-playbook.md +156 -0
  161. package/skills/schema-markup-generator/.env.example +19 -0
  162. package/skills/schema-markup-generator/README.md +114 -0
  163. package/skills/schema-markup-generator/SKILL.md +192 -0
  164. package/skills/schema-markup-generator/evals/evals.json +35 -0
  165. package/skills/schema-markup-generator/references/json-ld-spec.md +263 -0
  166. package/skills/schema-markup-generator/references/output-template.md +556 -0
  167. package/skills/show-hn-writer/.env.example +14 -0
  168. package/skills/show-hn-writer/README.md +88 -0
  169. package/skills/show-hn-writer/SKILL.md +303 -0
  170. package/skills/show-hn-writer/evals/evals.json +35 -0
  171. package/skills/show-hn-writer/references/hn-rules.md +74 -0
  172. package/skills/show-hn-writer/references/title-formulas.md +93 -0
  173. package/skills/stargazer/README.md +79 -0
  174. package/skills/stargazer/SKILL.md +7 -0
  175. package/skills/stargazer/stargazer-skill/SKILL.md +58 -0
  176. package/skills/stargazer/stargazer-skill/assets/.env.example +18 -0
  177. package/skills/stargazer/stargazer-skill/scripts/convert_to_csv.py +63 -0
  178. package/skills/stargazer/stargazer-skill/scripts/count_emails.py +52 -0
  179. package/skills/stargazer/stargazer-skill/scripts/stargazer_deep_extractor.py +450 -0
  180. package/skills/tweet-thread-from-blog/.env.example +14 -0
  181. package/skills/tweet-thread-from-blog/README.md +109 -0
  182. package/skills/tweet-thread-from-blog/SKILL.md +177 -0
  183. package/skills/tweet-thread-from-blog/evals/evals.json +35 -0
  184. package/skills/tweet-thread-from-blog/references/output-template.md +193 -0
  185. package/skills/tweet-thread-from-blog/references/thread-format.md +107 -0
  186. package/skills/twitter-GTM-find-skill/README.md +43 -0
  187. package/skills/twitter-GTM-find-skill/SKILL.md +7 -0
  188. package/skills/twitter-GTM-find-skill/twitter-GTM-find/SKILL.md +37 -0
  189. package/skills/twitter-GTM-find-skill/twitter-GTM-find/references/icp-checklist.md +35 -0
  190. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/package.json +23 -0
  191. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/run_pipeline.sh +8 -0
  192. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/debug.ts +23 -0
  193. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/extractor.ts +79 -0
  194. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/icp-filter.ts +87 -0
  195. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/index.ts +94 -0
  196. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/scraper.ts +41 -0
  197. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/tsconfig.json +13 -0
  198. package/skills/yc-intent-radar-skill/README.md +39 -0
  199. package/skills/yc-intent-radar-skill/SKILL.md +7 -0
  200. package/skills/yc-intent-radar-skill/yc-jobs-scraper/SKILL.md +59 -0
  201. package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/auth.js +29 -0
  202. package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/db.js +62 -0
  203. package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/export_radar_candidates.js +40 -0
  204. package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/package-lock.json +1525 -0
  205. package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/package.json +12 -0
  206. package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/scraper.js +217 -0
  207. package/src/e2e.test.ts +35 -0
  208. package/src/fs-adapters.test.ts +91 -0
  209. package/src/fs-adapters.ts +65 -0
  210. package/src/index.ts +182 -0
  211. package/src/transformers.ts +6 -0
  212. package/tsconfig.json +8 -0
@@ -0,0 +1,123 @@
1
+ # Digest Format Reference
2
+
3
+ Rules for structuring newsletter digests. Read this before generating any content.
4
+
5
+ ---
6
+
7
+ ## Three Digest Formats
8
+
9
+ ### Format 1: Weekly Roundup
10
+
11
+ Use for: General weekly digest covering top stories across all monitored feeds.
12
+
13
+ Target length: 350-500 words.
14
+
15
+ Structure:
16
+ 1. **This Week in [Topic/Industry]** — one sentence capturing the week's theme
17
+ 2. **Top Story** — 100-150 words on the single most significant story. Include one specific data point or quote from the source.
18
+ 3. **Also Worth Reading** — 3-5 stories, each 40-60 words with a source attribution link
19
+ 4. **Quick Takes** — 3-5 one-line bullets for minor stories
20
+ 5. **Until Next Week** — 1-2 sentence closing
21
+
22
+ Example section structure:
23
+
24
+ ```
25
+ This Week in Developer Tools
26
+
27
+ The npm ecosystem had a rough week.
28
+
29
+ Top Story
30
+ [Title as H3]
31
+ [100-150 word summary. Every stat from the source. Source attribution link at end.]
32
+
33
+ Also Worth Reading
34
+ [Title as H3, linked to article]
35
+ [40-60 words. Key insight. [Source: Name](URL)]
36
+
37
+ Quick Takes
38
+ - [One-line summary. Source.](URL)
39
+ - [One-line summary. Source.](URL)
40
+
41
+ Until Next Week
42
+ [Closing sentence. No CTA filler.]
43
+ ```
44
+
45
+ ---
46
+
47
+ ### Format 2: Topic Deep Dive
48
+
49
+ Use for: A focused issue on a single topic (e.g., "AI agents this week", "security incidents this week").
50
+
51
+ Target length: 450-650 words.
52
+
53
+ Structure:
54
+ 1. **The Big Picture** — 80-100 words on why this topic matters this week
55
+ 2. **Key Developments** — 3-4 sections, each 80-100 words, covering one development each
56
+ 3. **What to Watch** — 2-3 bullet points on what happens next
57
+ 4. **Resources** — list of all source articles with URLs
58
+
59
+ ---
60
+
61
+ ### Format 3: Curated Picks
62
+
63
+ Use for: "Editor's choice" digest with opinionated selection of the best reads.
64
+
65
+ Target length: 250-350 words.
66
+
67
+ Structure:
68
+ 1. **This Week's Picks** — one sentence framing the selection
69
+ 2. **5 picks** — each with title (linked), 30-40 word description, and a one-line "Why read it" note
70
+ 3. **One to skip** — optional: one overhyped story and why it did not make the cut
71
+
72
+ ---
73
+
74
+ ## Length Rules
75
+
76
+ | Format | Min | Target | Max |
77
+ |--------|-----|--------|-----|
78
+ | Weekly Roundup | 300 | 400 | 500 |
79
+ | Topic Deep Dive | 400 | 550 | 650 |
80
+ | Curated Picks | 200 | 300 | 380 |
81
+
82
+ If Gemini output is too long, cut the Quick Takes section first, then trim the Also Worth Reading summaries.
83
+
84
+ ---
85
+
86
+ ## Attribution Rules
87
+
88
+ Every non-obvious claim needs a source link. Format:
89
+
90
+ - Inline: `[Source: Publication Name](https://full-url)`
91
+ - Section header links: `### [Story Title](https://full-url)`
92
+ - Bullet links: `- [Headline](https://full-url) — one-line summary`
93
+
94
+ Never write a data point, statistic, or quote without a linked attribution.
95
+
96
+ ---
97
+
98
+ ## Tone Rules
99
+
100
+ - Write for a reader who is busy. They scan. Use short paragraphs.
101
+ - No hype. No "this is huge." Let the facts speak.
102
+ - No first-person plural ("we noticed"). Write as a neutral observer.
103
+ - One idea per paragraph, maximum three sentences per paragraph.
104
+ - Dates: use full dates (April 10, 2026) not relative dates (this Tuesday).
105
+
106
+ ---
107
+
108
+ ## HTML Output Rules
109
+
110
+ When generating HTML for Ghost:
111
+ - Use `<h2>` for section headings (e.g., "Top Story", "Also Worth Reading")
112
+ - Use `<h3>` for individual story titles
113
+ - Use `<p>` for body text
114
+ - Use `<ul>` and `<li>` for Quick Takes and bullet lists
115
+ - Use `<a href="URL">` for all source links
116
+ - Do not use `<div>` with inline styles
117
+ - Do not use markdown syntax in the HTML output
118
+
119
+ When generating Markdown for Substack:
120
+ - Use `##` for section headings
121
+ - Use `###` for story titles
122
+ - Use `[text](url)` for links
123
+ - Use `-` for bullet points
@@ -0,0 +1,136 @@
1
+ # Output Templates
2
+
3
+ Three templates — one per digest format. Replace every [BRACKETED FIELD] with content from the fetched articles. Never leave bracket text in the final output. Skip sections that have no source material rather than inventing content.
4
+
5
+ ---
6
+
7
+ ## Template 1: Weekly Roundup
8
+
9
+ ```html
10
+ <h2>This Week in [INDUSTRY/TOPIC]</h2>
11
+ <p>[ONE SENTENCE capturing the week's theme or dominant story. Specific, not generic.]</p>
12
+
13
+ <h2>Top Story</h2>
14
+ <h3><a href="[ARTICLE_URL]">[ARTICLE_TITLE]</a></h3>
15
+ <p>[100-150 word summary of the most significant story this week. Include one specific data point or quote directly from the article. No invented statistics.] <a href="[ARTICLE_URL]">[SOURCE_NAME]</a></p>
16
+
17
+ <h2>Also Worth Reading</h2>
18
+
19
+ <h3><a href="[ARTICLE_2_URL]">[ARTICLE_2_TITLE]</a></h3>
20
+ <p>[40-60 word summary. Key insight from the article.] <a href="[ARTICLE_2_URL]">[SOURCE_2_NAME]</a></p>
21
+
22
+ <h3><a href="[ARTICLE_3_URL]">[ARTICLE_3_TITLE]</a></h3>
23
+ <p>[40-60 word summary. Key insight from the article.] <a href="[ARTICLE_3_URL]">[SOURCE_3_NAME]</a></p>
24
+
25
+ <h3><a href="[ARTICLE_4_URL]">[ARTICLE_4_TITLE]</a></h3>
26
+ <p>[40-60 word summary. Key insight from the article.] <a href="[ARTICLE_4_URL]">[SOURCE_4_NAME]</a></p>
27
+
28
+ <h2>Quick Takes</h2>
29
+ <ul>
30
+ <li><a href="[ARTICLE_5_URL]">[ARTICLE_5_TITLE]</a> — [One-line summary.]</li>
31
+ <li><a href="[ARTICLE_6_URL]">[ARTICLE_6_TITLE]</a> — [One-line summary.]</li>
32
+ <li><a href="[ARTICLE_7_URL]">[ARTICLE_7_TITLE]</a> — [One-line summary.]</li>
33
+ </ul>
34
+
35
+ <p>[1-2 sentence closing. No CTA filler. No "see you next week".]</p>
36
+ ```
37
+
38
+ Worked example:
39
+
40
+ ```html
41
+ <h2>This Week in Developer Tools</h2>
42
+ <p>A major npm supply chain incident and two new runtime releases dominated developer news.</p>
43
+
44
+ <h2>Top Story</h2>
45
+ <h3><a href="https://blog.npmjs.org/post/supply-chain">npm Detects Malicious Package Injecting Crypto Miner</a></h3>
46
+ <p>The npm security team removed 47 packages after discovering a supply chain attack targeting Node.js build environments. The packages had been downloaded 2.3 million times before detection. The attack injected a crypto miner into postinstall scripts. npm is rolling out mandatory provenance attestation for all new package versions by June 2026. <a href="https://blog.npmjs.org/post/supply-chain">npm Blog</a></p>
47
+
48
+ <h2>Also Worth Reading</h2>
49
+
50
+ <h3><a href="https://bun.sh/blog/bun-1.2">Bun 1.2 Ships with Built-In S3 and SQL Support</a></h3>
51
+ <p>Bun 1.2 adds first-party APIs for S3 storage and SQL databases without any npm packages. The release also brings a 20% startup time improvement. <a href="https://bun.sh/blog/bun-1.2">Bun Blog</a></p>
52
+
53
+ <h2>Quick Takes</h2>
54
+ <ul>
55
+ <li><a href="https://nodejs.org/en/blog/release/v22.14.0">Node.js 22.14.0 Released</a> — Minor patch with V8 12.4 backports and a fix for the http2 memory leak.</li>
56
+ <li><a href="https://deno.com/blog/deno-2.2">Deno 2.2 Adds WebGPU Compute Shaders</a> — First runtime to support GPU compute from server-side JavaScript.</li>
57
+ </ul>
58
+
59
+ <p>Four feeds, 34 articles, one interesting week.</p>
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Template 2: Topic Deep Dive
65
+
66
+ ```html
67
+ <h2>The Big Picture</h2>
68
+ <p>[80-100 words on why this topic is significant this week. Ground in specific events, not general claims. One data point from a source.]</p>
69
+
70
+ <h2>[DEVELOPMENT_1_HEADING]</h2>
71
+ <h3><a href="[ARTICLE_URL]">[ARTICLE_TITLE]</a></h3>
72
+ <p>[80-100 word summary. Specific facts, no invented details.] <a href="[ARTICLE_URL]">[SOURCE_NAME]</a></p>
73
+
74
+ <h2>[DEVELOPMENT_2_HEADING]</h2>
75
+ <h3><a href="[ARTICLE_URL]">[ARTICLE_TITLE]</a></h3>
76
+ <p>[80-100 word summary.] <a href="[ARTICLE_URL]">[SOURCE_NAME]</a></p>
77
+
78
+ <h2>[DEVELOPMENT_3_HEADING]</h2>
79
+ <h3><a href="[ARTICLE_URL]">[ARTICLE_TITLE]</a></h3>
80
+ <p>[80-100 word summary.] <a href="[ARTICLE_URL]">[SOURCE_NAME]</a></p>
81
+
82
+ <h2>What to Watch</h2>
83
+ <ul>
84
+ <li>[Next development to follow, grounded in current events.]</li>
85
+ <li>[Second thing to watch.]</li>
86
+ <li>[Third thing to watch.]</li>
87
+ </ul>
88
+
89
+ <h2>This Week's Sources</h2>
90
+ <ul>
91
+ <li><a href="[URL_1]">[TITLE_1]</a> — [SOURCE_NAME_1]</li>
92
+ <li><a href="[URL_2]">[TITLE_2]</a> — [SOURCE_NAME_2]</li>
93
+ </ul>
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Template 3: Curated Picks
99
+
100
+ ```html
101
+ <h2>This Week's Picks</h2>
102
+ <p>[One sentence framing the selection — what connects these picks or what made them stand out.]</p>
103
+
104
+ <h3>1. <a href="[ARTICLE_1_URL]">[ARTICLE_1_TITLE]</a></h3>
105
+ <p>[30-40 word description. Key takeaway.]</p>
106
+ <p><strong>Why read it:</strong> [One sentence. Specific reason.] <a href="[ARTICLE_1_URL]">[SOURCE_1_NAME]</a></p>
107
+
108
+ <h3>2. <a href="[ARTICLE_2_URL]">[ARTICLE_2_TITLE]</a></h3>
109
+ <p>[30-40 word description. Key takeaway.]</p>
110
+ <p><strong>Why read it:</strong> [One sentence. Specific reason.] <a href="[ARTICLE_2_URL]">[SOURCE_2_NAME]</a></p>
111
+
112
+ <h3>3. <a href="[ARTICLE_3_URL]">[ARTICLE_3_TITLE]</a></h3>
113
+ <p>[30-40 word description.]</p>
114
+ <p><strong>Why read it:</strong> [One sentence.] <a href="[ARTICLE_3_URL]">[SOURCE_3_NAME]</a></p>
115
+
116
+ <h3>4. <a href="[ARTICLE_4_URL]">[ARTICLE_4_TITLE]</a></h3>
117
+ <p>[30-40 word description.]</p>
118
+ <p><strong>Why read it:</strong> [One sentence.] <a href="[ARTICLE_4_URL]">[SOURCE_4_NAME]</a></p>
119
+
120
+ <h3>5. <a href="[ARTICLE_5_URL]">[ARTICLE_5_TITLE]</a></h3>
121
+ <p>[30-40 word description.]</p>
122
+ <p><strong>Why read it:</strong> [One sentence.] <a href="[ARTICLE_5_URL]">[SOURCE_5_NAME]</a></p>
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Fill-in Rules
128
+
129
+ 1. Replace every [BRACKETED FIELD] with content from the fetched articles
130
+ 2. Never leave bracket text in the output
131
+ 3. Every URL must be an absolute URL (starts with `https://`)
132
+ 4. Every non-obvious claim needs a linked source attribution
133
+ 5. Skip a section if there is no source material for it
134
+ 6. Check word count before presenting — see references/digest-format.md for targets
135
+ 7. Do not use markdown in HTML output; do not use HTML tags in Markdown output
136
+ 8. Write the Markdown version after the HTML version by converting heading tags and links
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * fetch-feeds.js
4
+ * Fetches RSS/Atom feeds, filters by date window, deduplicates, and saves to JSON.
5
+ *
6
+ * Usage:
7
+ * node fetch-feeds.js # Default: last 7 days
8
+ * node fetch-feeds.js --days=14 # Extend window to 14 days
9
+ *
10
+ * Output: /tmp/newsletter-digest-articles.json
11
+ */
12
+
13
+ import Parser from 'rss-parser';
14
+ import { readFileSync, writeFileSync } from 'fs';
15
+ import { fileURLToPath } from 'url';
16
+ import { dirname, join } from 'path';
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+
20
+ // Parse CLI args
21
+ const args = process.argv.slice(2);
22
+ const daysArg = args.find(a => a.startsWith('--days='));
23
+ const DAYS = daysArg ? parseInt(daysArg.split('=')[1], 10) : 7;
24
+ const OUTPUT_PATH = '/tmp/newsletter-digest-articles.json';
25
+ const FEEDS_PATH = join(__dirname, '..', 'feeds.json');
26
+
27
+ async function main() {
28
+ // Load feeds config
29
+ let feeds;
30
+ try {
31
+ feeds = JSON.parse(readFileSync(FEEDS_PATH, 'utf8'));
32
+ } catch (err) {
33
+ console.error(`ERROR: Could not read feeds.json at ${FEEDS_PATH}`);
34
+ console.error('Create feeds.json with this format:');
35
+ console.error('[{"url": "https://example.com/feed", "name": "Source Name"}]');
36
+ process.exit(1);
37
+ }
38
+
39
+ if (!Array.isArray(feeds) || feeds.length === 0) {
40
+ console.error('ERROR: feeds.json is empty or not an array.');
41
+ process.exit(1);
42
+ }
43
+
44
+ const cutoffDate = new Date();
45
+ cutoffDate.setDate(cutoffDate.getDate() - DAYS);
46
+
47
+ const parser = new Parser({
48
+ timeout: 10000,
49
+ headers: { 'User-Agent': 'newsletter-digest-skill/1.0' },
50
+ customFields: {
51
+ item: ['summary', 'description', 'content:encoded', 'media:content'],
52
+ },
53
+ });
54
+
55
+ const allArticles = [];
56
+ const seenUrls = new Set();
57
+ const feedsSummary = [];
58
+
59
+ for (const feed of feeds) {
60
+ if (!feed.url || !feed.name) {
61
+ console.warn(`WARN: Skipping malformed feed entry: ${JSON.stringify(feed)}`);
62
+ continue;
63
+ }
64
+
65
+ let parsed;
66
+ try {
67
+ console.log(`Fetching: ${feed.name} (${feed.url})`);
68
+ parsed = await parser.parseURL(feed.url);
69
+ } catch (err) {
70
+ console.warn(`WARN: Failed to fetch ${feed.name}: ${err.message}`);
71
+ feedsSummary.push({ name: feed.name, count: 0, error: err.message });
72
+ continue;
73
+ }
74
+
75
+ let feedCount = 0;
76
+ for (const item of parsed.items) {
77
+ const pubDate = item.pubDate || item.isoDate;
78
+ if (!pubDate) continue;
79
+
80
+ const publishedAt = new Date(pubDate);
81
+ if (isNaN(publishedAt.getTime())) continue;
82
+ if (publishedAt < cutoffDate) continue;
83
+
84
+ const url = item.link || item.guid;
85
+ if (!url || seenUrls.has(url)) continue;
86
+ seenUrls.add(url);
87
+
88
+ // Extract the best available summary
89
+ const content =
90
+ item['content:encoded'] ||
91
+ item.content ||
92
+ item.summary ||
93
+ item.description ||
94
+ '';
95
+
96
+ // Strip HTML tags for plain text excerpt
97
+ const plainText = content.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
98
+ const excerpt = plainText.slice(0, 400) + (plainText.length > 400 ? '...' : '');
99
+
100
+ allArticles.push({
101
+ title: item.title || 'Untitled',
102
+ url,
103
+ source: feed.name,
104
+ publishedAt: publishedAt.toISOString(),
105
+ author: item.author || item.creator || '',
106
+ excerpt,
107
+ });
108
+ feedCount++;
109
+ }
110
+
111
+ feedsSummary.push({ name: feed.name, count: feedCount });
112
+ console.log(` Found ${feedCount} articles in the last ${DAYS} days`);
113
+ }
114
+
115
+ // Sort newest first
116
+ allArticles.sort((a, b) => new Date(b.publishedAt) - new Date(a.publishedAt));
117
+
118
+ const output = {
119
+ fetchedAt: new Date().toISOString(),
120
+ daysWindow: DAYS,
121
+ dateRange: {
122
+ from: cutoffDate.toISOString(),
123
+ to: new Date().toISOString(),
124
+ },
125
+ articles: allArticles,
126
+ feedsSummary,
127
+ };
128
+
129
+ writeFileSync(OUTPUT_PATH, JSON.stringify(output, null, 2));
130
+
131
+ console.log(`\nDone. ${allArticles.length} articles saved to ${OUTPUT_PATH}`);
132
+ feedsSummary.forEach(f => {
133
+ const status = f.error ? `ERROR: ${f.error}` : `${f.count} articles`;
134
+ console.log(` ${f.name}: ${status}`);
135
+ });
136
+ }
137
+
138
+ main().catch(err => {
139
+ console.error('Unexpected error:', err);
140
+ process.exit(1);
141
+ });
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ghost-publish.js
4
+ * Publishes the newsletter digest to Ghost CMS via Admin API.
5
+ *
6
+ * Usage:
7
+ * node ghost-publish.js --title "Weekly Digest: Apr 7-14" --status draft
8
+ * node ghost-publish.js --title "Weekly Digest: Apr 7-14" --status published
9
+ *
10
+ * Reads digest content from: /tmp/newsletter-digest-output.json
11
+ * Required env vars: GHOST_URL, GHOST_ADMIN_KEY
12
+ *
13
+ * GHOST_ADMIN_KEY format: "key_id:secret" (from Ghost Admin > Settings > Integrations)
14
+ */
15
+
16
+ import jwt from 'jsonwebtoken';
17
+ import { readFileSync } from 'fs';
18
+
19
+ const OUTPUT_PATH = '/tmp/newsletter-digest-output.json';
20
+
21
+ function generateToken(adminKey) {
22
+ const [id, secret] = adminKey.split(':');
23
+ if (!id || !secret) {
24
+ throw new Error('GHOST_ADMIN_KEY must be in format "key_id:secret"');
25
+ }
26
+ // Ghost requires the secret as binary (hex-decoded), signed with HS256
27
+ return jwt.sign({}, Buffer.from(secret, 'hex'), {
28
+ keyid: id,
29
+ algorithm: 'HS256',
30
+ expiresIn: '5m',
31
+ audience: '/admin/',
32
+ });
33
+ }
34
+
35
+ async function main() {
36
+ const args = process.argv.slice(2);
37
+ const titleArg = args.find(a => a.startsWith('--title='));
38
+ const statusArg = args.find(a => a.startsWith('--status='));
39
+
40
+ const title = titleArg ? titleArg.split('=').slice(1).join('=') : null;
41
+ const status = statusArg ? statusArg.split('=')[1] : 'draft';
42
+
43
+ if (!title) {
44
+ console.error('ERROR: --title is required');
45
+ process.exit(1);
46
+ }
47
+
48
+ if (!['draft', 'published'].includes(status)) {
49
+ console.error('ERROR: --status must be "draft" or "published"');
50
+ process.exit(1);
51
+ }
52
+
53
+ const ghostUrl = process.env.GHOST_URL;
54
+ const adminKey = process.env.GHOST_ADMIN_KEY;
55
+
56
+ if (!ghostUrl) {
57
+ console.error('ERROR: GHOST_URL is not set');
58
+ process.exit(1);
59
+ }
60
+ if (!adminKey) {
61
+ console.error('ERROR: GHOST_ADMIN_KEY is not set');
62
+ process.exit(1);
63
+ }
64
+
65
+ // Read digest content
66
+ let digestOutput;
67
+ try {
68
+ digestOutput = JSON.parse(readFileSync(OUTPUT_PATH, 'utf8'));
69
+ } catch (err) {
70
+ console.error(`ERROR: Could not read digest output at ${OUTPUT_PATH}`);
71
+ console.error('The agent must write the digest to this file before running ghost-publish.js');
72
+ process.exit(1);
73
+ }
74
+
75
+ const html = digestOutput.html;
76
+ if (!html) {
77
+ console.error('ERROR: digest output JSON does not contain an "html" field');
78
+ process.exit(1);
79
+ }
80
+
81
+ // Generate JWT token
82
+ let token;
83
+ try {
84
+ token = generateToken(adminKey);
85
+ } catch (err) {
86
+ console.error(`ERROR: Failed to generate Ghost JWT: ${err.message}`);
87
+ process.exit(1);
88
+ }
89
+
90
+ // Build Ghost API URL (strip trailing slash)
91
+ const baseUrl = ghostUrl.replace(/\/$/, '');
92
+ const apiUrl = `${baseUrl}/ghost/api/admin/posts/?source=html`;
93
+
94
+ const payload = {
95
+ posts: [
96
+ {
97
+ title,
98
+ html,
99
+ status,
100
+ },
101
+ ],
102
+ };
103
+
104
+ // POST to Ghost Admin API
105
+ let response;
106
+ try {
107
+ const res = await fetch(apiUrl, {
108
+ method: 'POST',
109
+ headers: {
110
+ 'Content-Type': 'application/json',
111
+ Authorization: `Ghost ${token}`,
112
+ },
113
+ body: JSON.stringify(payload),
114
+ });
115
+
116
+ response = await res.json();
117
+
118
+ if (!res.ok) {
119
+ console.error('Ghost API error:', JSON.stringify(response, null, 2));
120
+ process.exit(1);
121
+ }
122
+ } catch (err) {
123
+ console.error(`ERROR: Network request to Ghost failed: ${err.message}`);
124
+ process.exit(1);
125
+ }
126
+
127
+ const post = response.posts?.[0];
128
+ if (!post) {
129
+ console.error('ERROR: Ghost did not return a post object');
130
+ console.error(JSON.stringify(response, null, 2));
131
+ process.exit(1);
132
+ }
133
+
134
+ console.log(`\nPost created successfully.`);
135
+ console.log(` Status: ${post.status}`);
136
+ console.log(` Title: ${post.title}`);
137
+ console.log(` Slug: ${post.slug}`);
138
+ console.log(` Admin URL: ${baseUrl}/ghost/#/editor/post/${post.id}`);
139
+ if (post.status === 'published') {
140
+ console.log(` Public URL: ${post.url}`);
141
+ }
142
+ }
143
+
144
+ main().catch(err => {
145
+ console.error('Unexpected error:', err);
146
+ process.exit(1);
147
+ });
@@ -0,0 +1,16 @@
1
+ # noise2blog — Environment Variables
2
+ # =====================================
3
+ # The agent writes the blog post — no separate backend needed.
4
+ # Only Gemini is required. Tavily is optional for research enrichment.
5
+
6
+ # Required: Google Gemini API key
7
+ # Get it: aistudio.google.com → Get API key → Create API key
8
+ # Free tier available (rate-limited). Paid tier for higher volume.
9
+ GEMINI_API_KEY=your_gemini_api_key_here
10
+
11
+ # Optional: Tavily API key (for research enrichment)
12
+ # When set, the agent searches for supporting data to verify claims in your raw content.
13
+ # Without this, the blog post is based entirely on what you provide — which is fine for
14
+ # personal stories, experience-based tutorials, and opinion posts.
15
+ # Get it: app.tavily.com → API Keys
16
+ TAVILY_API_KEY=your_tavily_api_key_here
@@ -0,0 +1,107 @@
1
+ # noise2blog
2
+
3
+ <img width="1280" height="640" alt="noise2blog" src="https://github.com/user-attachments/assets/2359cff9-dfd4-4276-bb3e-4b6091ad6983" />
4
+
5
+
6
+ Paste your rough notes, bullet points, voice transcript, or tweet dump and get a publication-ready blog post.
7
+
8
+ ## What It Does
9
+
10
+ - Accepts any rough input: bullet points, rough notes, voice transcripts, tweet dumps, short drafts, or a URL
11
+ - Detects the right blog post style automatically (Technical Tutorial, Case Study, Thought Leadership, Explainer)
12
+ - Enriches claims with Tavily research when you set an API key (supporting data, verification)
13
+ - Generates a Markdown post with no AI slop, no em dashes, no banned words
14
+ - Formats frontmatter for Ghost, dev.to, Substack, or Hashnode on request
15
+
16
+ ## Requirements
17
+
18
+ | Requirement | Purpose | How to Set Up |
19
+ |------------|---------|--------------|
20
+ | Google Gemini API key | Generates the blog post | aistudio.google.com, Get API key |
21
+ | Tavily API key (optional) | Enriches claims with research | app.tavily.com, API Keys |
22
+
23
+ No LLM backend to run. The agent calls the Gemini API directly.
24
+
25
+ ## Setup
26
+
27
+ ```bash
28
+ cp .env.example .env
29
+ ```
30
+
31
+ Edit `.env` and fill in:
32
+ - `GEMINI_API_KEY` (required)
33
+ - `TAVILY_API_KEY` (optional, enables research enrichment)
34
+
35
+ ## Blog Post Styles
36
+
37
+ | Style | Use When | Signals in Your Input |
38
+ |-------|----------|----------------------|
39
+ | Technical Tutorial | Step-by-step guide, code walkthrough | Numbered steps, commands, "how to" |
40
+ | Case Study | Build log, before/after story, lessons learned | Specific results, journey narrative |
41
+ | Thought Leadership | Opinion piece, counterintuitive argument | Strong claim, debate framing |
42
+ | Explainer | Explaining a concept or tool to newcomers | Definition-first, comparisons |
43
+
44
+ The agent detects the style automatically. Override it with: "Use Thought Leadership style" or "Make this a tutorial".
45
+
46
+ ## How to Use
47
+
48
+ From pasted notes:
49
+
50
+ ```
51
+ "Write a blog post from these notes: [paste your content]"
52
+ "Turn these bullet points into a blog post"
53
+ "Expand this into an article"
54
+ ```
55
+
56
+ From a voice transcript:
57
+
58
+ ```
59
+ "Turn this transcript into a blog post: [paste transcript]"
60
+ "Clean up this voice note and make it publishable"
61
+ ```
62
+
63
+ From a tweet thread:
64
+
65
+ ```
66
+ "Turn this tweet thread into a blog: [paste tweets]"
67
+ "Expand this thread into a full article"
68
+ ```
69
+
70
+ With style override:
71
+
72
+ ```
73
+ "Write a thought leadership post from these notes"
74
+ "Make this a technical tutorial"
75
+ ```
76
+
77
+ With platform formatting:
78
+
79
+ ```
80
+ "Write the post and format it for dev.to"
81
+ "Give me Ghost frontmatter too"
82
+ ```
83
+
84
+ ## Output
85
+
86
+ - Full blog post in Markdown (800-1,800 words)
87
+ - Meta description (1-2 sentences)
88
+ - Alternative title option
89
+ - Platform-specific frontmatter on request
90
+
91
+ ## Project Structure
92
+
93
+ ```
94
+ noise2blog/
95
+ ├── SKILL.md
96
+ ├── README.md
97
+ ├── .env.example
98
+ ├── evals/
99
+ │ └── evals.json
100
+ └── references/
101
+ ├── blog-format.md
102
+ └── output-template.md
103
+ ```
104
+
105
+ ## License
106
+
107
+ MIT