Mirror of
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: gh action, bluesky post resolution

+24 -28
+1 -3
.github/workflows/generate-post.yml
··· 17 17 steps: 18 18 - name: Checkout repository 19 19 uses: actions/checkout@v4 20 - with: 21 - ref: ${{ github.head_ref }} 22 20 23 21 - name: Setup Node.js 24 22 uses: actions/setup-node@v4 ··· 31 29 32 30 - name: Generate post 33 31 env: 34 - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 + MODELS_TOKEN: ${{ secrets.MODELS_TOKEN }} 35 33 run: pnpm run generate 36 34 37 35 - name: Commit and push new post
+4 -2
package.json
··· 9 9 "build": "astro check && astro build", 10 10 "preview": "astro preview", 11 11 "astro": "astro", 12 - "generate": "tsx --env-file=.env scripts/generate-digest.ts", 13 - "backfill": "tsx --env-file=.env scripts/backfill.ts" 12 + "generate": "tsx scripts/generate-digest.ts", 13 + "local:generate": "tsx --env-file=.env scripts/generate-digest.ts", 14 + "backfill": "tsx scripts/backfill.ts", 15 + "local:backfill": "tsx --env-file=.env scripts/backfill.ts" 14 16 }, 15 17 "dependencies": { 16 18 "@astrojs/check": "^0.9.6",
+1 -1
scripts/backfill.ts
··· 58 58 ): Promise<Event[]> { 59 59 const owner = "npmx-dev"; 60 60 const repo = "npmx.dev"; 61 - const token = getRequiredEnv("GITHUB_TOKEN"); 61 + const token = getRequiredEnv("MODELS_TOKEN"); 62 62 const events: Event[] = []; 63 63 64 64 const startIso = start.toISOString().split(".")[0] + "Z";
+18 -22
src/lib/events.ts
··· 46 46 export async function fetchGitHubEvents(since: Date): Promise<Event[]> { 47 47 const owner = "npmx-dev"; 48 48 const repo = "npmx.dev"; 49 - const token = getRequiredEnv("GITHUB_TOKEN"); 49 + const token = getRequiredEnv("MODELS_TOKEN"); 50 50 const events: Event[] = []; 51 51 52 - const halfDayInMs = 12 * 60 * 60 * 1000; 53 - const until = new Date(since.getTime() + halfDayInMs); 54 - const startIso = since.toISOString().split(".")[0]; 55 - const endIso = until.toISOString().split(".")[0]; 56 - const dateRange = `${startIso}Z..${endIso}Z`; 52 + const startIso = since.toISOString().split(".")[0] + "Z"; 53 + const now = new Date(); 54 + const endIso = now.toISOString().split(".")[0] + "Z"; 57 55 58 56 const query = encodeURIComponent( 59 - `repo:${owner}/${repo} is:closed created:${dateRange}`, 57 + `repo:${owner}/${repo} is:closed closed:${startIso}..${endIso}`, 60 58 ); 61 59 62 60 const headers = { ··· 82 80 title: `${isPR ? "Merged PR" : "Closed Issue"} #${item.number}: ${item.title}`, 83 81 description: item.body || "No description provided", 84 82 url: item.html_url, 85 - timestamp: item.created_at, 83 + timestamp: item.closed_at || item.created_at, 86 84 }); 87 85 }); 88 86 LOG.success(`GitHub: Found ${events.length} finalized items.`); ··· 106 104 const { did } = await resolve.json(); 107 105 108 106 const feedRes = await fetch( 109 - `https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=${did}&limit=30&filter=posts_with_replies`, 107 + `https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=${did}&limit=50&filter=posts_with_replies`, 110 108 ); 111 109 112 110 if (feedRes.ok) { ··· 114 112 115 113 const candidates = feed.reduce((acc: Event[], item: any) => { 116 114 const actionTimestamp = item.reason?.indexedAt || item.post.indexedAt; 117 - const isRecent = new Date(actionTimestamp) >= since; 115 + const itemDate = new Date(actionTimestamp); 118 116 119 - if (isRecent) { 120 - const author = item.post.author.handle; 117 + if (itemDate >= since) { 118 + const authorHandle = item.post.author.handle; 121 119 const isRepost = !!item.reason; 122 120 const postText = item.post.record.text; 121 + const postId = item.post.uri.split("/").pop(); 123 122 124 123 acc.push({ 125 124 source: "bluesky", 126 - title: `${isRepost ? `[Repost from @${author}] ` : ""}${postText.substring(0, 80)}`, 125 + title: `${isRepost ? `[Repost from @${authorHandle}] ` : ""}${postText.substring(0, 80)}`, 127 126 description: postText, 128 - url: `https://bsky.app/profile/${handle}/post/${item.post.uri.split("/").pop()}`, 127 + url: `https://bsky.app/profile/${authorHandle}/post/${postId}`, 129 128 timestamp: actionTimestamp, 130 129 }); 131 130 } ··· 153 152 } 154 153 155 154 export async function generateSmartDigest(events: Event[]): Promise<Topic[]> { 156 - const token = getRequiredEnv("GITHUB_TOKEN"); 155 + const token = getRequiredEnv("MODELS_TOKEN"); 157 156 if (!token || events.length === 0) return []; 158 157 159 158 LOG.ai( 160 159 `Clustering ${events.length} signals into topics (Prioritizing Bluesky)...`, 161 160 ); 162 161 163 - // Directive to the AI: Use Bluesky as community anchors 164 162 const prompt = `You are a technical analyst for npmx. Group the following events into 5-6 logical "Topics". 165 163 166 164 STRATEGY: ··· 185 183 body: JSON.stringify({ 186 184 messages: [{ role: "user", content: prompt }], 187 185 model: "gpt-4o-mini", 188 - temperature: 0.3, // Lower temp for more stable clustering 186 + temperature: 0.3, 189 187 response_format: { type: "json_object" }, 190 188 }), 191 189 }, ··· 196 194 const content = data.choices[0].message.content; 197 195 const parsed = JSON.parse(content); 198 196 199 - // Re-ranking Logic: Slightly boost topics that contain Bluesky content 200 197 const rawTopics = parsed.topics.map((t: any) => { 201 198 const validated = TopicSchema.parse(t); 202 199 const hasBluesky = events.some( 203 200 (e) => 204 201 e.source === "bluesky" && 205 - t.summary.includes(e.title.substring(0, 20)), 202 + (t.summary.includes(e.title.substring(0, 15)) || 203 + t.title.includes(e.source)), 206 204 ); 207 205 208 206 return { 209 207 ...validated, 210 208 title: sanitizeBrand(validated.title), 211 209 summary: sanitizeBrand(validated.summary), 212 - // Subtle priority boost for community-facing topics 213 210 relevanceScore: hasBluesky 214 211 ? Math.min(10, validated.relevanceScore + 1) 215 212 : validated.relevanceScore, 216 213 }; 217 214 }); 218 215 219 - // Sort by the new boosted score 220 216 const topics = rawTopics.sort( 221 217 (a: Topic, b: Topic) => b.relevanceScore - a.relevanceScore, 222 218 ); ··· 233 229 } 234 230 235 231 export async function generateCatchyTitle(topic: Topic): Promise<string> { 236 - const token = getRequiredEnv("GITHUB_TOKEN"); 232 + const token = getRequiredEnv("MODELS_TOKEN"); 237 233 if (!token) return "New Update"; 238 234 239 235 const prompt = `You are a tech journalist for npmx. Create a very short (max 5-7 words), catchy headline for this topic.