@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.
- package/.claude/skills/claude-md-generator/.env.example +7 -0
- package/.claude/skills/claude-md-generator/README.md +78 -0
- package/.claude/skills/claude-md-generator/SKILL.md +248 -0
- package/.claude/skills/claude-md-generator/evals/evals.json +35 -0
- package/.claude/skills/claude-md-generator/references/section-guide.md +175 -0
- package/dist/e2e.test.d.ts +1 -0
- package/dist/e2e.test.js +62 -0
- package/dist/fs-adapters.d.ts +4 -0
- package/dist/fs-adapters.js +101 -0
- package/dist/fs-adapters.test.d.ts +1 -0
- package/dist/fs-adapters.test.js +108 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +211 -0
- package/dist/transformers.d.ts +6 -0
- package/dist/transformers.js +2 -0
- package/package.json +25 -0
- package/registry.json +226 -0
- package/skills/blog-cover-image-cli/.github/workflows/publish.yml +19 -0
- package/skills/blog-cover-image-cli/LICENSE +15 -0
- package/skills/blog-cover-image-cli/README.md +126 -0
- package/skills/blog-cover-image-cli/SKILL.md +7 -0
- package/skills/blog-cover-image-cli/agent-skill/blog-cover-generator/README.md +30 -0
- package/skills/blog-cover-image-cli/agent-skill/blog-cover-generator/SKILL.md +72 -0
- package/skills/blog-cover-image-cli/bin/cli.js +226 -0
- package/skills/blog-cover-image-cli/examples/100x_UX_Research_AI_Agent.png +0 -0
- package/skills/blog-cover-image-cli/examples/Firecrawl-supabase-bolt.png +0 -0
- package/skills/blog-cover-image-cli/examples/Git-City_Case_study_Cover_Image.jpg +0 -0
- package/skills/blog-cover-image-cli/examples/THE DISTRIBUTION LAYER (2).png +0 -0
- package/skills/blog-cover-image-cli/examples/canva-perplexity-duolingo-cover-image.png +0 -0
- package/skills/blog-cover-image-cli/examples/gamma-mistral-veed.png +0 -0
- package/skills/blog-cover-image-cli/examples/server-survival-case-study-cover-image(1).png +0 -0
- package/skills/blog-cover-image-cli/examples/viral-meme-automation.png +0 -0
- package/skills/blog-cover-image-cli/index.js +2 -0
- package/skills/blog-cover-image-cli/package-lock.json +2238 -0
- package/skills/blog-cover-image-cli/package.json +37 -0
- package/skills/blog-cover-image-cli/src/geminiGenerator.js +126 -0
- package/skills/blog-cover-image-cli/src/imageValidator.js +54 -0
- package/skills/blog-cover-image-cli/src/logoFetcher.js +86 -0
- package/skills/claude-md-generator/.env.example +7 -0
- package/skills/claude-md-generator/README.md +78 -0
- package/skills/claude-md-generator/SKILL.md +254 -0
- package/skills/claude-md-generator/evals/evals.json +35 -0
- package/skills/claude-md-generator/references/section-guide.md +175 -0
- package/skills/cook-the-blog/README.md +86 -0
- package/skills/cook-the-blog/SKILL.md +130 -0
- package/skills/dependency-update-bot/.env.example +13 -0
- package/skills/dependency-update-bot/README.md +101 -0
- package/skills/dependency-update-bot/SKILL.md +376 -0
- package/skills/dependency-update-bot/evals/evals.json +45 -0
- package/skills/dependency-update-bot/references/changelog-patterns.md +201 -0
- package/skills/docs-from-code/.env.example +13 -0
- package/skills/docs-from-code/README.md +97 -0
- package/skills/docs-from-code/SKILL.md +160 -0
- package/skills/docs-from-code/evals/evals.json +29 -0
- package/skills/docs-from-code/references/extraction-guide.md +174 -0
- package/skills/docs-from-code/references/output-template.md +135 -0
- package/skills/docs-from-code/scripts/extract_py.py +238 -0
- package/skills/docs-from-code/scripts/extract_ts.ts +284 -0
- package/skills/docs-from-code/scripts/package.json +18 -0
- package/skills/explain-this-pr/README.md +74 -0
- package/skills/explain-this-pr/SKILL.md +130 -0
- package/skills/explain-this-pr/evals/evals.json +35 -0
- package/skills/google-trends-api-skills/README.md +78 -0
- package/skills/google-trends-api-skills/SKILL.md +7 -0
- package/skills/google-trends-api-skills/google-trends-api/SKILL.md +163 -0
- package/skills/google-trends-api-skills/google-trends-api/references/api-responses.md +188 -0
- package/skills/google-trends-api-skills/google-trends-api/scripts/discover_keywords.py +344 -0
- package/skills/google-trends-api-skills/seo-keyword-research/SKILL.md +205 -0
- package/skills/google-trends-api-skills/seo-keyword-research/references/keyword-placement-guide.md +89 -0
- package/skills/google-trends-api-skills/seo-keyword-research/references/tech-blog-examples.md +207 -0
- package/skills/google-trends-api-skills/seo-keyword-research/scripts/blog_seo_research.py +373 -0
- package/skills/hackernews-intel/.env.example +33 -0
- package/skills/hackernews-intel/README.md +161 -0
- package/skills/hackernews-intel/SKILL.md +156 -0
- package/skills/hackernews-intel/evals/evals.json +35 -0
- package/skills/hackernews-intel/package.json +15 -0
- package/skills/hackernews-intel/scripts/monitor-hn.js +258 -0
- package/skills/kill-the-standup/.env.example +22 -0
- package/skills/kill-the-standup/README.md +84 -0
- package/skills/kill-the-standup/SKILL.md +169 -0
- package/skills/kill-the-standup/evals/evals.json +35 -0
- package/skills/kill-the-standup/references/standup-format.md +102 -0
- package/skills/linkedin-post-generator/.env.example +14 -0
- package/skills/linkedin-post-generator/README.md +107 -0
- package/skills/linkedin-post-generator/SKILL.md +228 -0
- package/skills/linkedin-post-generator/evals/evals.json +35 -0
- package/skills/linkedin-post-generator/references/linkedin-format.md +216 -0
- package/skills/linkedin-post-generator/references/output-template.md +154 -0
- package/skills/llms-txt-generator/.env.example +18 -0
- package/skills/llms-txt-generator/README.md +142 -0
- package/skills/llms-txt-generator/SKILL.md +176 -0
- package/skills/llms-txt-generator/evals/evals.json +35 -0
- package/skills/llms-txt-generator/references/llms-txt-spec.md +88 -0
- package/skills/llms-txt-generator/references/output-template.md +76 -0
- package/skills/llms-txt-generator/test-output/genzcareer.in/llms.txt +31 -0
- package/skills/luma-attendees-scraper/README.md +170 -0
- package/skills/luma-attendees-scraper/SKILL.md +7 -0
- package/skills/luma-attendees-scraper/luma_attendees_export.js +223 -0
- package/skills/meeting-brief-generator/.env.example +21 -0
- package/skills/meeting-brief-generator/README.md +90 -0
- package/skills/meeting-brief-generator/SKILL.md +275 -0
- package/skills/meeting-brief-generator/evals/evals.json +35 -0
- package/skills/meeting-brief-generator/references/brief-format.md +114 -0
- package/skills/meeting-brief-generator/references/output-template.md +150 -0
- package/skills/meta-ads-skill/README.md +100 -0
- package/skills/meta-ads-skill/SKILL.md +7 -0
- package/skills/meta-ads-skill/meta-ads-skill/SKILL.md +41 -0
- package/skills/meta-ads-skill/meta-ads-skill/references/report_templates.md +47 -0
- package/skills/meta-ads-skill/meta-ads-skill/references/workflows.md +51 -0
- package/skills/meta-ads-skill/meta-ads-skill/scripts/auth_check.py +22 -0
- package/skills/meta-ads-skill/meta-ads-skill/scripts/formatters.py +46 -0
- package/skills/newsletter-digest/.env.example +20 -0
- package/skills/newsletter-digest/README.md +147 -0
- package/skills/newsletter-digest/SKILL.md +221 -0
- package/skills/newsletter-digest/evals/evals.json +35 -0
- package/skills/newsletter-digest/feeds.json +7 -0
- package/skills/newsletter-digest/package.json +15 -0
- package/skills/newsletter-digest/references/digest-format.md +123 -0
- package/skills/newsletter-digest/references/output-template.md +136 -0
- package/skills/newsletter-digest/scripts/fetch-feeds.js +141 -0
- package/skills/newsletter-digest/scripts/ghost-publish.js +147 -0
- package/skills/noise2blog/.env.example +16 -0
- package/skills/noise2blog/README.md +107 -0
- package/skills/noise2blog/SKILL.md +229 -0
- package/skills/noise2blog/evals/evals.json +35 -0
- package/skills/noise2blog/references/blog-format.md +188 -0
- package/skills/noise2blog/references/output-template.md +184 -0
- package/skills/outreach-sequence-builder/.env.example +12 -0
- package/skills/outreach-sequence-builder/README.md +108 -0
- package/skills/outreach-sequence-builder/SKILL.md +248 -0
- package/skills/outreach-sequence-builder/evals/evals.json +36 -0
- package/skills/outreach-sequence-builder/references/output-template.md +171 -0
- package/skills/outreach-sequence-builder/references/sequence-format.md +167 -0
- package/skills/outreach-sequence-builder/references/signal-playbook.md +117 -0
- package/skills/position-me/README.md +71 -0
- package/skills/position-me/SKILL.md +7 -0
- package/skills/position-me/position-me/SKILL.md +50 -0
- package/skills/position-me/position-me/references/EVALUATION_SOP.md +40 -0
- package/skills/position-me/position-me/references/REPORT_TEMPLATE.md +58 -0
- package/skills/position-me/position-me/scripts/extract_links.py +49 -0
- package/skills/pr-description-writer/README.md +81 -0
- package/skills/pr-description-writer/SKILL.md +141 -0
- package/skills/pr-description-writer/evals/evals.json +35 -0
- package/skills/pr-description-writer/references/pr-format-guide.md +145 -0
- package/skills/producthunt-launch-kit/.env.example +7 -0
- package/skills/producthunt-launch-kit/README.md +95 -0
- package/skills/producthunt-launch-kit/SKILL.md +380 -0
- package/skills/producthunt-launch-kit/evals/evals.json +35 -0
- package/skills/producthunt-launch-kit/references/copy-rules.md +124 -0
- package/skills/reddit-icp-monitor/.env.example +16 -0
- package/skills/reddit-icp-monitor/README.md +117 -0
- package/skills/reddit-icp-monitor/SKILL.md +271 -0
- package/skills/reddit-icp-monitor/evals/evals.json +40 -0
- package/skills/reddit-icp-monitor/references/icp-format.md +131 -0
- package/skills/reddit-icp-monitor/references/reply-rules.md +110 -0
- package/skills/reddit-post-engine/.env.example +13 -0
- package/skills/reddit-post-engine/README.md +103 -0
- package/skills/reddit-post-engine/SKILL.md +303 -0
- package/skills/reddit-post-engine/evals/evals.json +35 -0
- package/skills/reddit-post-engine/references/subreddit-playbook.md +156 -0
- package/skills/schema-markup-generator/.env.example +19 -0
- package/skills/schema-markup-generator/README.md +114 -0
- package/skills/schema-markup-generator/SKILL.md +192 -0
- package/skills/schema-markup-generator/evals/evals.json +35 -0
- package/skills/schema-markup-generator/references/json-ld-spec.md +263 -0
- package/skills/schema-markup-generator/references/output-template.md +556 -0
- package/skills/show-hn-writer/.env.example +14 -0
- package/skills/show-hn-writer/README.md +88 -0
- package/skills/show-hn-writer/SKILL.md +303 -0
- package/skills/show-hn-writer/evals/evals.json +35 -0
- package/skills/show-hn-writer/references/hn-rules.md +74 -0
- package/skills/show-hn-writer/references/title-formulas.md +93 -0
- package/skills/stargazer/README.md +79 -0
- package/skills/stargazer/SKILL.md +7 -0
- package/skills/stargazer/stargazer-skill/SKILL.md +58 -0
- package/skills/stargazer/stargazer-skill/assets/.env.example +18 -0
- package/skills/stargazer/stargazer-skill/scripts/convert_to_csv.py +63 -0
- package/skills/stargazer/stargazer-skill/scripts/count_emails.py +52 -0
- package/skills/stargazer/stargazer-skill/scripts/stargazer_deep_extractor.py +450 -0
- package/skills/tweet-thread-from-blog/.env.example +14 -0
- package/skills/tweet-thread-from-blog/README.md +109 -0
- package/skills/tweet-thread-from-blog/SKILL.md +177 -0
- package/skills/tweet-thread-from-blog/evals/evals.json +35 -0
- package/skills/tweet-thread-from-blog/references/output-template.md +193 -0
- package/skills/tweet-thread-from-blog/references/thread-format.md +107 -0
- package/skills/twitter-GTM-find-skill/README.md +43 -0
- package/skills/twitter-GTM-find-skill/SKILL.md +7 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/SKILL.md +37 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/references/icp-checklist.md +35 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/package.json +23 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/run_pipeline.sh +8 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/debug.ts +23 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/extractor.ts +79 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/icp-filter.ts +87 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/index.ts +94 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/scraper.ts +41 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/tsconfig.json +13 -0
- package/skills/yc-intent-radar-skill/README.md +39 -0
- package/skills/yc-intent-radar-skill/SKILL.md +7 -0
- package/skills/yc-intent-radar-skill/yc-jobs-scraper/SKILL.md +59 -0
- package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/auth.js +29 -0
- package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/db.js +62 -0
- package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/export_radar_candidates.js +40 -0
- package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/package-lock.json +1525 -0
- package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/package.json +12 -0
- package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/scraper.js +217 -0
- package/src/e2e.test.ts +35 -0
- package/src/fs-adapters.test.ts +91 -0
- package/src/fs-adapters.ts +65 -0
- package/src/index.ts +182 -0
- package/src/transformers.ts +6 -0
- 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
|