forkfeed-mcp 1.3.3 → 1.3.5
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/dist/guide-content.js +23 -29
- package/dist/index.js +38 -38
- package/package.json +1 -1
package/dist/guide-content.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
export const GUIDE_CONTENT = `
|
|
7
7
|
# Forkfeed Content Guide - "The Fuck I Pushed"
|
|
8
8
|
|
|
9
|
-
Turn GitHub commits into swipeable card content. One fork per repo, one feed per commit,
|
|
9
|
+
Turn GitHub commits into swipeable card content. One fork per repo, one feed per commit, 7 cards per feed.
|
|
10
10
|
|
|
11
11
|
**Process exactly ONE commit at a time, never multiple.**
|
|
12
12
|
|
|
@@ -54,16 +54,16 @@ Only 5 fields + cards. Everything else is auto-populated.
|
|
|
54
54
|
| \`{ "text": "Paragraph..." }\` | CONTENT_TEXT | 50-200 words, use \\\\n\\\\n for breaks |
|
|
55
55
|
| \`{ "code": "const x = 1", "lang": "typescript" }\` | CONTENT_CODE | Real code from diff, 5-20 lines, strip +/- prefixes |
|
|
56
56
|
| \`{ "subtext": "Pro tip..." }\` | CONTENT_SUBTEXT | Aside or protip |
|
|
57
|
-
| \`{ "name": "Chad", "subtitle": "10x Engineer", "avatar": "img98", "source": "linkedin" }\` | CONTENT_SOCIAL |
|
|
58
|
-
| \`{ "question": "What does X do?", "options": [{"label": "A", "correct": false}, {"label": "B", "correct": true}], "explanation": "Because..." }\` | CONTENT_QUIZ | Card
|
|
59
|
-
| \`{ "label": "Google it", "action": "url", "target": "https://google.com/search?q=topic" }\` | CONTENT_BUTTON | Card
|
|
57
|
+
| \`{ "name": "Chad", "subtitle": "10x Engineer", "avatar": "img98", "source": "linkedin" }\` | CONTENT_SOCIAL | Card 2 only. avatar can be image ID or URL. source: x, linkedin, threads, etc. |
|
|
58
|
+
| \`{ "question": "What does X do?", "options": [{"label": "A", "correct": false}, {"label": "B", "correct": true}], "explanation": "Because..." }\` | CONTENT_QUIZ | Card 6 only. 4 options recommended, min 2, one correct, always include explanation |
|
|
59
|
+
| \`{ "label": "Google it", "action": "url", "target": "https://google.com/search?q=topic" }\` | CONTENT_BUTTON | Card 4 only, MUST be last block in every detail variant |
|
|
60
60
|
|
|
61
61
|
### What the builder does automatically
|
|
62
62
|
- Detects repo owner/name from git remote
|
|
63
63
|
- Generates fork/feed IDs from convention (tfip-{owner}-{repo}-{sha})
|
|
64
64
|
- Constructs GitHub action URL
|
|
65
65
|
- Fetches existing feed IDs from server (for incremental updates)
|
|
66
|
-
- Assigns
|
|
66
|
+
- Assigns 7 unique card backgrounds from preference table + commit tags
|
|
67
67
|
- Generates UUID v4 for each card ID
|
|
68
68
|
- Resolves short image IDs (img47, bg10) to full CDN URLs
|
|
69
69
|
- Adds FULL_IMAGE cover variant with fixed title/subtitle per card type
|
|
@@ -77,27 +77,21 @@ Only 5 fields + cards. Everything else is auto-populated.
|
|
|
77
77
|
|
|
78
78
|
---
|
|
79
79
|
|
|
80
|
-
## The
|
|
80
|
+
## The 7 cards
|
|
81
81
|
|
|
82
|
-
Each card = array of detail variants (cover is auto-generated). Provide 1+ variants per card.
|
|
82
|
+
Each card = array of detail variants (cover is auto-generated). Provide 1+ variants per card. Quality over quantity: pick only the most impactful, funny, or educational content.
|
|
83
83
|
|
|
84
84
|
| # | Section | Detail variants | Block pattern | Tone |
|
|
85
85
|
|---|---------|----------------|---------------|------|
|
|
86
|
-
| 0 | Explain like I'm 5 | 3
|
|
87
|
-
| 1 | The roast | 3
|
|
88
|
-
| 2 |
|
|
89
|
-
| 3 |
|
|
90
|
-
| 4 |
|
|
91
|
-
| 5 |
|
|
92
|
-
| 6 |
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
### Card 2 personas (Decoded)
|
|
96
|
-
- "What you meant" (source: "x" or "threads")
|
|
97
|
-
- "Corporate speak" (source: "linkedin")
|
|
98
|
-
- Optional: "Shakespearean" (source: "x"), "Military briefing" (source: "x")
|
|
99
|
-
|
|
100
|
-
### Card 3 personas (LinkedIn)
|
|
86
|
+
| 0 | Explain like I'm 5 | 2-3 | img -> title -> text | Playful, zero jargon |
|
|
87
|
+
| 1 | The roast | 2-3 | img -> title -> text, optional subtext | Maximum cheekiness, swearing ok |
|
|
88
|
+
| 2 | The LinkedIn post | 2-3 | social -> title -> text | Peak LinkedIn parody |
|
|
89
|
+
| 3 | Statistics | 2-3 | img -> title -> code | Deadpan data humor |
|
|
90
|
+
| 4 | Learning moment | 2-3 | img -> title -> text -> optional code -> button(last) | Teach with personality |
|
|
91
|
+
| 5 | Alternatives | 2-3 | img -> title -> text -> optional code | Constructive snark |
|
|
92
|
+
| 6 | Quiz | 4-6 | title -> quiz (no img blocks) | Quiz show energy |
|
|
93
|
+
|
|
94
|
+
### Card 2 personas (LinkedIn)
|
|
101
95
|
- Chad Gitpush: "10x Engineer | Building in Public | DMs Open"
|
|
102
96
|
- Alexandra Middleware: "VP of Forward Thinking at TechCorp"
|
|
103
97
|
- Dave 'Shipping' Johnson: "Just a humble engineer doing humble things"
|
|
@@ -106,14 +100,14 @@ Each card = array of detail variants (cover is auto-generated). Provide 1+ varia
|
|
|
106
100
|
|
|
107
101
|
## Key rules
|
|
108
102
|
|
|
109
|
-
- Exactly
|
|
103
|
+
- Exactly 7 cards in the cards array (one per section above)
|
|
110
104
|
- Use short image IDs from forkfeed_commits output (img47, not full URLs)
|
|
111
|
-
- 12
|
|
112
|
-
- Cards 0-
|
|
113
|
-
- Card
|
|
114
|
-
- Card
|
|
115
|
-
- Card
|
|
116
|
-
-
|
|
105
|
+
- 8-12 unique scene images (img*) across the feed, no duplicate within same card
|
|
106
|
+
- Cards 0-4: code must be REAL from the diff (never fabricated)
|
|
107
|
+
- Card 5: synthesized code IS allowed
|
|
108
|
+
- Card 6: no img blocks, quiz blocks only (with title per variant)
|
|
109
|
+
- Card 4: every detail variant MUST end with a button block
|
|
110
|
+
- Card 2: first block should be social (not img), no engagement metrics subtext (reactions, comments, reposts)
|
|
117
111
|
- CONTENT_QUIZ: 4 options recommended (min 2), exactly one correct, always include explanation
|
|
118
112
|
- No em dashes, smart quotes, or non-ASCII characters
|
|
119
113
|
- Feed title max 60 chars
|
package/dist/index.js
CHANGED
|
@@ -218,12 +218,11 @@ function parseFilePathsFromStats(stats) {
|
|
|
218
218
|
const BG_PREFS = [
|
|
219
219
|
['bg10', 'bg27'], // 0: ELI5
|
|
220
220
|
['bg11', 'bg1'], // 1: Roast
|
|
221
|
-
['
|
|
222
|
-
['
|
|
223
|
-
['
|
|
224
|
-
['
|
|
225
|
-
['
|
|
226
|
-
['bg18', 'bg5'], // 7: Quiz
|
|
221
|
+
['bg25', 'bg24'], // 2: LinkedIn
|
|
222
|
+
['bg9', 'bg3'], // 3: Stats
|
|
223
|
+
['bg12', 'bg13', 'bg14', 'bg15', 'bg17'], // 4: Learning (tech-match)
|
|
224
|
+
['bg25', 'bg30'], // 5: Alternatives
|
|
225
|
+
['bg18', 'bg5'], // 6: Quiz
|
|
227
226
|
];
|
|
228
227
|
// Map language tags to preferred Learning card (index 5) backgrounds
|
|
229
228
|
const LANG_BG = {
|
|
@@ -235,8 +234,8 @@ const LANG_BG = {
|
|
|
235
234
|
};
|
|
236
235
|
function assignBackgrounds(tags) {
|
|
237
236
|
const used = new Set();
|
|
238
|
-
// For card
|
|
239
|
-
const learningPrefs = [...BG_PREFS[
|
|
237
|
+
// For card 4 (Learning), reorder prefs based on detected language tags
|
|
238
|
+
const learningPrefs = [...BG_PREFS[4]];
|
|
240
239
|
for (const tag of tags) {
|
|
241
240
|
const preferred = LANG_BG[tag];
|
|
242
241
|
if (preferred && learningPrefs.includes(preferred)) {
|
|
@@ -247,7 +246,7 @@ function assignBackgrounds(tags) {
|
|
|
247
246
|
}
|
|
248
247
|
}
|
|
249
248
|
return BG_PREFS.map((prefs, i) => {
|
|
250
|
-
const candidates = i ===
|
|
249
|
+
const candidates = i === 4 ? learningPrefs : prefs;
|
|
251
250
|
const pick = candidates.find((bg) => !used.has(bg)) || candidates[0];
|
|
252
251
|
used.add(pick);
|
|
253
252
|
return pick;
|
|
@@ -286,7 +285,6 @@ async function fetchStatusData() {
|
|
|
286
285
|
const COVERS = [
|
|
287
286
|
{ title: "Explain Like I'm 5", subtitle: "Hopefully now you'll understand what you pushed" },
|
|
288
287
|
{ title: "The Roast", subtitle: "Your code had it coming" },
|
|
289
|
-
{ title: "Commit Message, Decoded", subtitle: "What you wrote vs what you meant vs what actually happened" },
|
|
290
288
|
{ title: "The LinkedIn Post", subtitle: "Mass cringe, freshly generated" },
|
|
291
289
|
{ title: "Statistics", subtitle: "The numbers don't lie, but they do judge" },
|
|
292
290
|
{ title: "Learning Moment", subtitle: "Something useful buried in your chaos" },
|
|
@@ -358,10 +356,10 @@ const buildInputSchema = z.object({
|
|
|
358
356
|
variants: z.array(z.object({
|
|
359
357
|
blocks: z.array(z.record(z.string(), z.unknown())).min(1),
|
|
360
358
|
})).min(1),
|
|
361
|
-
})).length(
|
|
359
|
+
})).length(7),
|
|
362
360
|
cwd: z.string().optional().describe('Working directory (defaults to process.cwd())'),
|
|
363
|
-
bgOverride: z.array(z.string()).length(
|
|
364
|
-
coverOverride: z.array(z.string()).length(
|
|
361
|
+
bgOverride: z.array(z.string()).length(7).optional().describe('Override auto-assigned background IDs'),
|
|
362
|
+
coverOverride: z.array(z.string()).length(7).optional().describe('Override cover image IDs (defaults to backgrounds)'),
|
|
365
363
|
});
|
|
366
364
|
// ── Manifest builder ─────────────────────────────────────────────────
|
|
367
365
|
async function buildManifest(input) {
|
|
@@ -407,7 +405,7 @@ async function buildManifest(input) {
|
|
|
407
405
|
title: input.forkTitle,
|
|
408
406
|
description: input.forkDescription,
|
|
409
407
|
imageSrc: coverUrls[0],
|
|
410
|
-
feedIds: [...existingFeedIds
|
|
408
|
+
feedIds: [feedId, ...existingFeedIds],
|
|
411
409
|
...(actionUrl ? { actionLabel: 'View on GitHub', actionUrl } : {}),
|
|
412
410
|
};
|
|
413
411
|
const feed = {
|
|
@@ -482,31 +480,32 @@ async function pushManifestToServer(manifest) {
|
|
|
482
480
|
const url = `https://forkfeed.link/fork/${forkId}`;
|
|
483
481
|
try {
|
|
484
482
|
const qr = await QRCode.toString(url, { type: 'utf8', errorCorrectionLevel: 'L' });
|
|
485
|
-
qrBlock = ['', 'Scan to open in forkfeed:', '', qr
|
|
483
|
+
qrBlock = ['', 'Scan to open in forkfeed:', '', qr].join('\n');
|
|
486
484
|
}
|
|
487
485
|
catch {
|
|
488
|
-
qrBlock =
|
|
486
|
+
qrBlock = '';
|
|
489
487
|
}
|
|
490
488
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
489
|
+
const blocks = [{
|
|
490
|
+
type: 'text',
|
|
491
|
+
text: [
|
|
492
|
+
'Content pushed successfully!',
|
|
493
|
+
'',
|
|
494
|
+
`Uploaded: ${data.uploaded?.feeds || 0} feeds, ${data.uploaded?.cards || 0} cards`,
|
|
495
|
+
'',
|
|
496
|
+
'Forks:',
|
|
497
|
+
forkSummary || ' (none)',
|
|
498
|
+
'',
|
|
499
|
+
saveError || `Manifest saved to ${filepath}`,
|
|
500
|
+
'',
|
|
501
|
+
'Your content is now live in the forkfeed app. It starts as private.',
|
|
502
|
+
'To make it public, change visibility in the app (requires admin approval).',
|
|
503
|
+
].join('\n'),
|
|
504
|
+
}];
|
|
505
|
+
if (qrBlock) {
|
|
506
|
+
blocks.push({ type: 'text', text: qrBlock });
|
|
507
|
+
}
|
|
508
|
+
return { content: blocks };
|
|
510
509
|
}
|
|
511
510
|
catch (err) {
|
|
512
511
|
return {
|
|
@@ -647,7 +646,7 @@ server.tool('forkfeed_push', 'Push a generated manifest (forks, feeds, cards) to
|
|
|
647
646
|
});
|
|
648
647
|
// ── Tool: forkfeed_build ──────────────────────────────────────────────
|
|
649
648
|
server.tool('forkfeed_build', 'Build a forkfeed manifest from simplified content and push it. Auto-detects repo info, assigns backgrounds, fetches existing feeds. Use this instead of forkfeed_push.', {
|
|
650
|
-
content: buildInputSchema.describe('Simplified content: sha, titles, descriptions, and
|
|
649
|
+
content: buildInputSchema.describe('Simplified content: sha, titles, descriptions, and 7 cards with blocks'),
|
|
651
650
|
push: z.boolean().optional().describe('Push immediately after building (default: true)'),
|
|
652
651
|
}, async ({ content, push }) => {
|
|
653
652
|
const shouldPush = push !== false;
|
|
@@ -743,7 +742,7 @@ server.tool('forkfeed_status', 'Check your current forkfeed content: which forks
|
|
|
743
742
|
}
|
|
744
743
|
});
|
|
745
744
|
// ── Prompt: /forkfeed ──────────────────────────────────────────────────
|
|
746
|
-
server.prompt('forkfeed', 'Turn GitHub commits into swipeable forkfeed content. Analyzes your repo, generates
|
|
745
|
+
server.prompt('forkfeed', 'Turn GitHub commits into swipeable forkfeed content. Analyzes your repo, generates 7-card feeds, and pushes them live.', async () => ({
|
|
747
746
|
messages: [
|
|
748
747
|
{
|
|
749
748
|
role: 'user',
|
|
@@ -754,8 +753,9 @@ server.prompt('forkfeed', 'Turn GitHub commits into swipeable forkfeed content.
|
|
|
754
753
|
1. Call **forkfeed_guide** and **forkfeed_commits()** in parallel (no arguments for commits = list mode).
|
|
755
754
|
2. Show the commits table from forkfeed_commits. It already indicates which commits have published feeds. Ask which ONE commit to process. One commit at a time, never more. Do NOT ask about image style.
|
|
756
755
|
3. Call **forkfeed_commits** with the selected commit SHA. This returns the diff, file stats, and suggested scene images. Do NOT run git commands yourself.
|
|
757
|
-
4. Generate the simplified content JSON: sha, feedTitle, feedDescription, forkTitle, forkDescription, and
|
|
756
|
+
4. Generate the simplified content JSON: sha, feedTitle, feedDescription, forkTitle, forkDescription, and 7 cards with blocks. Use short image IDs (img47) for inline images. Do NOT provide owner, repo, backgrounds, existingFeedIds, UUIDs, covers, or type wrappers. The builder auto-detects and generates all of that.
|
|
758
757
|
5. Call **forkfeed_build** with the simplified content (push defaults to true).
|
|
758
|
+
6. Display the QR code block from the push result exactly as returned, so the user can scan it.
|
|
759
759
|
|
|
760
760
|
Start now.`,
|
|
761
761
|
},
|