forkfeed-mcp 1.0.11 → 1.0.13
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 +6 -4
- package/dist/index.js +99 -1
- package/package.json +1 -1
package/dist/guide-content.js
CHANGED
|
@@ -8,20 +8,22 @@ export const GUIDE_CONTENT = `
|
|
|
8
8
|
|
|
9
9
|
Turn GitHub commits into swipeable card content. One fork per repo, one feed per commit, 8 cards per feed (35-62 variants total).
|
|
10
10
|
|
|
11
|
+
**Important: process exactly ONE commit at a time, never multiple commits in a single run.**
|
|
12
|
+
|
|
11
13
|
## Quick start
|
|
12
14
|
|
|
13
|
-
1. Ask which
|
|
15
|
+
1. Ask which single commit to process
|
|
14
16
|
2. Fetch commit data via git or gh CLI
|
|
15
17
|
3. Generate 8-card manifest JSON
|
|
16
18
|
4. Call forkfeed_push with the manifest
|
|
17
19
|
|
|
18
20
|
---
|
|
19
21
|
|
|
20
|
-
## Phase 1: Resolve repo and
|
|
22
|
+
## Phase 1: Resolve repo and commit
|
|
21
23
|
|
|
22
24
|
Use the **current working directory** as the repo. Do not ask which repo.
|
|
23
25
|
|
|
24
|
-
If the user didn't specify
|
|
26
|
+
Process exactly one commit per run. If the user didn't specify, use the latest commit. If they ask for multiple commits, process only the first and tell them to run again for the next.
|
|
25
27
|
|
|
26
28
|
Use **IT Scenes** images. Call **forkfeed_images** to get the full catalog (200 scene images + 30 backgrounds). Match images to content by tags and semantic similarity. Do not ask about image style.
|
|
27
29
|
|
|
@@ -201,7 +203,7 @@ Always call **forkfeed_status** before generating content. It lists published fe
|
|
|
201
203
|
|
|
202
204
|
- **New commit**: Generate only the new feed and cards. The fork's feedIds must include ALL existing feed IDs plus the new one. Missing old IDs causes them to be deleted.
|
|
203
205
|
- **Existing commit**: Warn the user that this commit already has a feed. Only regenerate if they confirm. The old cards will be replaced.
|
|
204
|
-
- **
|
|
206
|
+
- **One commit per run**: Never process multiple commits at once. If the user wants more, they run the command again.
|
|
205
207
|
|
|
206
208
|
---
|
|
207
209
|
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,86 @@ import { GUIDE_CONTENT } from './guide-content.js';
|
|
|
9
9
|
import { IMAGE_CATALOG } from './image-catalog.js';
|
|
10
10
|
const APP_SERVER_URL = (process.env.APP_SERVER_URL || 'https://api.forkfeed.link').replace(/\/+$/, '');
|
|
11
11
|
const TOKEN = process.env.FORKFEED_TOKEN || '';
|
|
12
|
+
// ── Manifest schemas ──────────────────────────────────────────────────
|
|
13
|
+
const forkSchema = z.object({
|
|
14
|
+
_id: z.string().min(1, 'Fork _id is required'),
|
|
15
|
+
title: z.string().min(1, 'Fork title is required'),
|
|
16
|
+
description: z.string(),
|
|
17
|
+
imageSrc: z.string(),
|
|
18
|
+
feedIds: z.array(z.string().min(1)).min(1, 'Fork must reference at least one feed'),
|
|
19
|
+
actionLabel: z.string().optional(),
|
|
20
|
+
actionUrl: z.string().optional(),
|
|
21
|
+
}).passthrough();
|
|
22
|
+
const feedSchema = z.object({
|
|
23
|
+
_id: z.string().min(1, 'Feed _id is required'),
|
|
24
|
+
title: z.string().min(1, 'Feed title is required').max(60, 'Feed title max 60 chars'),
|
|
25
|
+
description: z.string().optional(),
|
|
26
|
+
imageSrc: z.string(),
|
|
27
|
+
mode: z.string(),
|
|
28
|
+
scrollDirection: z.string(),
|
|
29
|
+
engagement: z.boolean().optional(),
|
|
30
|
+
}).passthrough();
|
|
31
|
+
const sizingEnum = z.enum(['automatic', 'wide', 'portrait', 'square', 'small_portrait']);
|
|
32
|
+
const socialSourceEnum = z.enum(['x', 'linkedin', 'instagram', 'facebook', 'threads', 'bluesky']);
|
|
33
|
+
const buttonActionEnum = z.enum(['url', 'fork', 'feed', 'user']);
|
|
34
|
+
const quizOptionSchema = z.object({
|
|
35
|
+
label: z.string().min(1),
|
|
36
|
+
correct: z.boolean(),
|
|
37
|
+
}).passthrough();
|
|
38
|
+
const contentBlockSchema = z.discriminatedUnion('type', [
|
|
39
|
+
z.object({ type: z.literal('CONTENT_IMAGE'), imageSrc: z.string().min(1), sizing: sizingEnum }).passthrough(),
|
|
40
|
+
z.object({ type: z.literal('CONTENT_TITLE'), title: z.string().min(1) }).passthrough(),
|
|
41
|
+
z.object({ type: z.literal('CONTENT_TEXT'), text: z.string().min(1) }).passthrough(),
|
|
42
|
+
z.object({ type: z.literal('CONTENT_CODE'), code: z.string().min(1), language: z.string().optional() }).passthrough(),
|
|
43
|
+
z.object({ type: z.literal('CONTENT_SOCIAL'), name: z.string().min(1), avatarSrc: z.string(), source: socialSourceEnum }).passthrough(),
|
|
44
|
+
z.object({ type: z.literal('CONTENT_SUBTEXT'), text: z.string().min(1) }).passthrough(),
|
|
45
|
+
z.object({ type: z.literal('CONTENT_QUIZ'), question: z.string().min(1), options: z.array(quizOptionSchema).min(2), explanation: z.string().optional() }).passthrough(),
|
|
46
|
+
z.object({ type: z.literal('CONTENT_BUTTON'), label: z.string().min(1), action: buttonActionEnum, target: z.string().min(1) }).passthrough(),
|
|
47
|
+
]);
|
|
48
|
+
const variantSchema = z.discriminatedUnion('type', [
|
|
49
|
+
z.object({ type: z.literal('FULL_IMAGE'), imageSrc: z.string().min(1, 'FULL_IMAGE requires imageSrc'), title: z.string().optional(), subtitle: z.string().optional() }).passthrough(),
|
|
50
|
+
z.object({ type: z.literal('FULL_VIDEO'), videoSrc: z.string().min(1), title: z.string().optional() }).passthrough(),
|
|
51
|
+
z.object({ type: z.literal('CONTENT'), blocks: z.array(contentBlockSchema).min(1, 'CONTENT variant needs at least one block'), backgroundSrc: z.string().optional() }).passthrough(),
|
|
52
|
+
]);
|
|
53
|
+
const cardSchema = z.object({
|
|
54
|
+
_id: z.string().min(1, 'Card _id is required'),
|
|
55
|
+
feedId: z.string().min(1, 'Card feedId is required'),
|
|
56
|
+
order: z.number().int().min(0),
|
|
57
|
+
variants: z.array(variantSchema).min(2, 'Card needs at least 2 variants (cover + detail)'),
|
|
58
|
+
}).passthrough();
|
|
59
|
+
const manifestSchema = z.object({
|
|
60
|
+
forks: z.array(forkSchema).min(1, 'Manifest must have at least one fork'),
|
|
61
|
+
feeds: z.array(feedSchema).min(1, 'Manifest must have at least one feed'),
|
|
62
|
+
cards: z.array(cardSchema).min(1, 'Manifest must have at least one card'),
|
|
63
|
+
});
|
|
64
|
+
/** Cross-reference checks that Zod can't express. Returns error string or null. */
|
|
65
|
+
function crossValidate(manifest) {
|
|
66
|
+
const feedIds = new Set(manifest.feeds.map((f) => f._id));
|
|
67
|
+
const errors = [];
|
|
68
|
+
for (const fork of manifest.forks) {
|
|
69
|
+
for (const fid of fork.feedIds) {
|
|
70
|
+
if (!feedIds.has(fid))
|
|
71
|
+
errors.push(`Fork "${fork._id}" references feed "${fid}" which is not in feeds array`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const card of manifest.cards) {
|
|
75
|
+
if (!feedIds.has(card.feedId))
|
|
76
|
+
errors.push(`Card "${card._id}" references feed "${card.feedId}" which is not in feeds array`);
|
|
77
|
+
for (let vi = 0; vi < card.variants.length; vi++) {
|
|
78
|
+
const v = card.variants[vi];
|
|
79
|
+
if (v.type !== 'CONTENT')
|
|
80
|
+
continue;
|
|
81
|
+
for (const block of v.blocks) {
|
|
82
|
+
if (block.type === 'CONTENT_QUIZ') {
|
|
83
|
+
const hasCorrect = block.options.some((o) => o.correct === true);
|
|
84
|
+
if (!hasCorrect)
|
|
85
|
+
errors.push(`Card "${card._id}" variants[${vi}]: CONTENT_QUIZ must have at least one correct option`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return errors.length > 0 ? errors.join('\n') : null;
|
|
91
|
+
}
|
|
12
92
|
// ── Helpers ────────────────────────────────────────────────────────────
|
|
13
93
|
function authHeaders() {
|
|
14
94
|
return {
|
|
@@ -67,6 +147,24 @@ server.tool('forkfeed_push', 'Push a generated manifest (forks, feeds, cards) to
|
|
|
67
147
|
isError: true,
|
|
68
148
|
};
|
|
69
149
|
}
|
|
150
|
+
// Validate manifest structure before pushing
|
|
151
|
+
const parsed = manifestSchema.safeParse(manifest);
|
|
152
|
+
if (!parsed.success) {
|
|
153
|
+
const issues = parsed.error.issues
|
|
154
|
+
.map((i) => ` ${i.path.join('.')}: ${i.message}`)
|
|
155
|
+
.join('\n');
|
|
156
|
+
return {
|
|
157
|
+
content: [{ type: 'text', text: `Manifest validation failed:\n${issues}\n\nFix the issues above and try again.` }],
|
|
158
|
+
isError: true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const crossErr = crossValidate(parsed.data);
|
|
162
|
+
if (crossErr) {
|
|
163
|
+
return {
|
|
164
|
+
content: [{ type: 'text', text: `Manifest cross-reference errors:\n${crossErr}\n\nFix the issues above and try again.` }],
|
|
165
|
+
isError: true,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
70
168
|
// Save manifest locally before uploading (best-effort, doesn't block push)
|
|
71
169
|
const forkId = manifest.forks?.[0]?._id;
|
|
72
170
|
const filename = `${forkId || `manifest-${Date.now()}`}.json`;
|
|
@@ -233,7 +331,7 @@ server.prompt('forkfeed', 'Turn GitHub commits into swipeable forkfeed content.
|
|
|
233
331
|
2. Call **forkfeed_images** to get the IT Scenes image catalog (200 scenes + 30 backgrounds).
|
|
234
332
|
3. Call **forkfeed_status** to check for existing content. Note which feeds (commits) are already published.
|
|
235
333
|
4. Detect the current repo from the working directory. Use git to get commit data.
|
|
236
|
-
5. Ask which
|
|
334
|
+
5. Ask which ONE commit to process (default: latest). Only one commit at a time, never more. Do NOT ask about image style (always use IT Scenes). Show a table of recent commits with a column indicating whether each already has a published feed (match the 7-char SHA from forkfeed_status feed IDs against the commit SHAs). If the user selects a commit that already has a feed, warn and ask for confirmation before regenerating.
|
|
237
335
|
6. Fetch the diff, analyze it, and generate the 8-card manifest following the guide exactly. Include ALL existing feed IDs in the fork's feedIds array (see "Incremental updates" in the guide).
|
|
238
336
|
7. Match images from the catalog to card content by tags and semantic similarity.
|
|
239
337
|
8. Validate the manifest against the checklist in the guide.
|