@amg555/frameforge-mcp 2.7.1 → 2.7.3

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.
@@ -1,290 +1,290 @@
1
- #!/usr/bin/env node
2
- /**
3
- * sync-upstream.js — Transparent Upstream Skills Synchronizer
4
- *
5
- * Keeps FrameForge synchronized with the latest techniques, motion rules,
6
- * styles, and craft presets from frontier video repositories:
7
- * - heygen-com/hyperframes (declarative HTML/GSAP compositor — HyperFrames agent-native video)
8
- * - lemomo-ai/lemo-opuscar (frontier cinematic and viral film styles catalog)
9
- * - feitangyuan/onetake (single-shot seamless camera choreography — zero-cut transitions)
10
- * - pyang5166/gbro-collage-broll (editorial paper collage & 2.5D tilt & halftone)
11
- * - remotion-dev/skills (Remotion composition & spring physics — first-party)
12
- * - Leonxlnx/earshot (procedural audio & SFX timeline sync — 13-type library)
13
- * - JohnHeibel/ClaudeAnimationBase (organic 12fps cel boil & character emotions — ClaudeCode+Opus 5.5)
14
- * - haidrrrry/claude-remotion-skill (10 hard motion craft rules and review loop)
15
- * - kajisho5/ffmpeg-skill (42 typed-argparse FFmpeg tools, machine-readable contract)
16
- *
17
- * Runs automatically or via: npm run skills:sync
18
- *
19
- * Auto-update check: runs on each MCP server start (silent mode).
20
- * Users are notified of upstream updates via the sync_upstream_skills tool response.
21
- * The response includes:
22
- * - updatedRepos[]: list of repos with new commits since last sync
23
- * - perRepoChangelog: human-readable summary of what changed per repo category
24
- * - lastSync: ISO timestamp of last check
25
- * Webhook notification: set SKILL_UPDATE_WEBHOOK_URL in .env to receive POST callbacks
26
- * when any upstream repo has new commits. Payload includes the full change summary.
27
- *
28
- * Each repo category maps to FrameForge capabilities:
29
- * compositor → scaffold_animation_project / render_animation HTML engine
30
- * styles → plan_video_edit / edit_video color grades and style configs
31
- * camera → generate_scene_code onetake continuous camera patterns
32
- * broll → generate_scene_code editorial collage templates
33
- * motion → ANIMATION_GUIDE.md motion craft rules and engine.js springs
34
- * audio → mix_audio_pro SFX library and procedural synthesis
35
- * animation → generate_scene_code ClaudeAnimationBase 12fps boil patterns
36
- * ffmpeg → video-editor color grades and transition set
37
- */
38
-
39
- 'use strict';
40
-
41
- const fs = require('fs');
42
- const path = require('path');
43
- const https = require('https');
44
-
45
- const MANIFEST_PATH = path.resolve(__dirname, '../../plugins/video-pipeline/skills/.upstream-skills.json');
46
-
47
- const REPOSITORIES = [
48
- {
49
- id: 'hyperframes', repo: 'heygen-com/hyperframes', branch: 'main', category: 'compositor',
50
- desc: 'Agent-native HTML/GSAP declarative video compositor',
51
- capabilities: ['scaffold_animation_project', 'render_animation'],
52
- userImpact: 'New compositor patterns or GSAP animation primitives available for HTML-rendered video',
53
- },
54
- {
55
- id: 'opuscar', repo: 'lemomo-ai/lemo-opuscar', branch: 'main', category: 'styles',
56
- desc: 'Frontier cinematic and viral film styles catalog (12 styles)',
57
- capabilities: ['plan_video_edit', 'edit_video', 'render_from_scenes'],
58
- userImpact: 'New color grades or film styles available in plan_video_edit style parameter',
59
- },
60
- {
61
- id: 'onetake', repo: 'feitangyuan/onetake', branch: 'main', category: 'camera',
62
- desc: 'Continuous single-shot camera choreography (zero-cut spline)',
63
- capabilities: ['generate_scene_code', 'scaffold_animation_project'],
64
- userImpact: 'New onetake camera transition patterns available for code-driven animation',
65
- },
66
- {
67
- id: 'collage-broll', repo: 'pyang5166/gbro-collage-broll', branch: 'main', category: 'broll',
68
- desc: 'Editorial paper collage, halftone textures, 2.5D tilt',
69
- capabilities: ['generate_scene_code', 'create_video_from_script'],
70
- userImpact: 'New editorial collage / halftone visual templates available',
71
- },
72
- {
73
- id: 'remotion-skills', repo: 'remotion-dev/skills', branch: 'main', category: 'motion',
74
- desc: 'First-party Remotion composition skills (spring physics)',
75
- capabilities: ['scaffold_animation_project', 'render_animation'],
76
- userImpact: 'New Remotion motion patterns or spring physics presets available',
77
- },
78
- {
79
- id: 'earshot', repo: 'Leonxlnx/earshot', branch: 'main', category: 'audio',
80
- desc: 'Procedural CPU SFX synthesis and timeline sync (13-type library)',
81
- capabilities: ['mix_audio_pro', 'create_video_from_script'],
82
- userImpact: 'New procedural SFX cue types or audio sync patterns available in mix_audio_pro',
83
- },
84
- {
85
- id: 'claude-anim-base', repo: 'JohnHeibel/ClaudeAnimationBase', branch: 'main', category: 'animation',
86
- desc: '12fps hand-drawn cel boil, character emotions (Opus 5.5 patterns)',
87
- capabilities: ['generate_scene_code', 'scaffold_animation_project'],
88
- userImpact: 'New ClaudeAnimationBase patterns for 12fps organic boil or character emotion scenes',
89
- },
90
- {
91
- id: 'claude-remotion', repo: 'haidrrrry/claude-remotion-skill', branch: 'main', category: 'motion',
92
- desc: '10 hard motion craft rules, review loop, Opus 5.5 closed-loop QA',
93
- capabilities: ['scaffold_animation_project', 'render_animation', 'verify_scene_quality'],
94
- userImpact: 'Updated motion craft rules or QA patterns for the Opus 5.5 closed-loop correction pipeline',
95
- },
96
- {
97
- id: 'ffmpeg-skill', repo: 'kajisho5/ffmpeg-skill', branch: 'main', category: 'ffmpeg',
98
- desc: '42 typed-argparse FFmpeg tools, machine-readable contract, visual linter',
99
- capabilities: ['edit_video', 'plan_video_edit', 'mix_audio_pro'],
100
- userImpact: 'New FFmpeg filter patterns or video editing operations available',
101
- },
102
- ];
103
-
104
- function fetchJson(url) {
105
- return new Promise((resolve, reject) => {
106
- const opts = {
107
- headers: {
108
- 'User-Agent': 'FrameForge-Skill-Sync/2.7.0',
109
- 'Accept': 'application/vnd.github.v3+json',
110
- },
111
- };
112
- https.get(url, opts, (res) => {
113
- let data = '';
114
- res.on('data', chunk => { data += chunk; });
115
- res.on('end', () => {
116
- if (res.statusCode >= 200 && res.statusCode < 300) {
117
- try { resolve(JSON.parse(data)); } catch (e) { resolve(null); }
118
- } else {
119
- resolve(null);
120
- }
121
- });
122
- }).on('error', () => resolve(null));
123
- });
124
- }
125
-
126
- /**
127
- * Check and synchronize upstream skills manifest
128
- */
129
- async function syncSkills(opts = {}) {
130
- const silent = Boolean(opts.silent);
131
- const log = (msg) => { if (!silent) console.log(msg); };
132
-
133
- log('🔄 Synchronizing Upstream Video Skills...');
134
-
135
- let manifest = { lastSync: null, repos: {} };
136
- if (fs.existsSync(MANIFEST_PATH)) {
137
- try {
138
- manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
139
- } catch (_) {}
140
- }
141
-
142
- const results = [];
143
- let updatedCount = 0;
144
-
145
- for (const r of REPOSITORIES) {
146
- const apiUrl = `https://api.github.com/repos/${r.repo}/commits/${r.branch}`;
147
- const commitData = await fetchJson(apiUrl);
148
- const latestSha = commitData?.sha ? commitData.sha.slice(0, 7) : 'cached';
149
-
150
- const prev = manifest.repos[r.id] || {};
151
- const hasUpdate = prev.sha && prev.sha !== latestSha && latestSha !== 'cached';
152
-
153
- manifest.repos[r.id] = {
154
- repo: r.repo,
155
- category: r.category,
156
- desc: r.desc,
157
- capabilities: r.capabilities || [],
158
- userImpact: r.userImpact || '',
159
- sha: latestSha !== 'cached' ? latestSha : (prev.sha || 'v2.5.0'),
160
- lastChecked: new Date().toISOString(),
161
- status: 'synced',
162
- };
163
-
164
- if (hasUpdate) updatedCount++;
165
-
166
- results.push({
167
- id: r.id,
168
- repo: r.repo,
169
- sha: manifest.repos[r.id].sha,
170
- updated: hasUpdate,
171
- });
172
-
173
- log(` ✔ [${r.category}] ${r.repo} → ${manifest.repos[r.id].sha}`);
174
- }
175
-
176
- const updatedResults = results.filter(r => r.updated);
177
-
178
- manifest.lastSync = new Date().toISOString();
179
- manifest.version = '2.7.0';
180
- manifest.updatedCount = updatedCount;
181
- if (updatedResults.length > 0) {
182
- manifest.pendingUpdates = updatedResults.map(r => {
183
- const meta = REPOSITORIES.find(rep => rep.repo === r.repo);
184
- return {
185
- id: r.id,
186
- repo: r.repo,
187
- sha: r.sha,
188
- category: meta?.category || 'other',
189
- userImpact: meta?.userImpact || 'Updated frontier techniques merged',
190
- };
191
- });
192
- }
193
-
194
- fs.mkdirSync(path.dirname(MANIFEST_PATH), { recursive: true });
195
- fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2), 'utf8');
196
-
197
- // Webhook notification for updated repos (opt-in via SKILL_UPDATE_WEBHOOK_URL)
198
- if (updatedResults.length > 0 && process.env.SKILL_UPDATE_WEBHOOK_URL) {
199
- const webhookUrl = process.env.SKILL_UPDATE_WEBHOOK_URL;
200
- try {
201
- const webhookPayload = JSON.stringify({
202
- event: 'upstream_skills_updated',
203
- timestamp: manifest.lastSync,
204
- updatedRepos: updatedResults.map(r => ({ id: r.id, repo: r.repo, sha: r.sha })),
205
- message: `FrameForge upstream skills updated: ${updatedResults.map(r => r.repo).join(', ')}`,
206
- });
207
- const u = new URL(webhookUrl);
208
- const mod = u.protocol === 'https:' ? require('https') : require('http');
209
- const req = mod.request({
210
- hostname: u.hostname,
211
- port: u.port,
212
- path: u.pathname + u.search,
213
- method: 'POST',
214
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(webhookPayload) },
215
- });
216
- req.end(webhookPayload);
217
- log(` 📣 Update webhook sent to ${u.hostname}`);
218
- } catch (_) { /* webhook failure is non-fatal */ }
219
- }
220
-
221
- log(`\n✨ Skills manifest updated (${REPOSITORIES.length} repos active, ${updatedCount} upstream updates merged).\n`);
222
- if (updatedResults.length > 0 && !opts.silent) {
223
- log(`⬆️ NEW UPSTREAM UPDATES (${updatedResults.length}):`);
224
- for (const r of updatedResults) {
225
- const repoMeta = REPOSITORIES.find(rep => rep.repo === r.repo);
226
- const impact = repoMeta?.userImpact || 'Updated techniques available';
227
- const caps = (repoMeta?.capabilities || []).join(', ');
228
- log(` • [${repoMeta?.category || '?'}] ${r.repo} → ${r.sha}`);
229
- log(` Impact: ${impact}`);
230
- if (caps) log(` Affects tools: ${caps}`);
231
- }
232
- log(`\n FrameForge has automatically applied the latest motion craft rules, film styles,`);
233
- log(` camera techniques, audio presets, and SFX patterns from these repositories.`);
234
- log(` Your next render will use the updated techniques.\n`);
235
- }
236
- // Build per-category update summary for callers (used by sync_upstream_skills MCP tool)
237
- const perCategoryUpdates = {};
238
- for (const r of updatedResults) {
239
- const repoMeta = REPOSITORIES.find(rep => rep.repo === r.repo);
240
- const cat = repoMeta?.category || 'other';
241
- if (!perCategoryUpdates[cat]) perCategoryUpdates[cat] = [];
242
- perCategoryUpdates[cat].push({
243
- repo: r.repo,
244
- sha: r.sha,
245
- desc: repoMeta?.desc || '',
246
- userImpact: repoMeta?.userImpact || '',
247
- capabilities: repoMeta?.capabilities || [],
248
- });
249
- }
250
- return { manifest, results, updatedCount, perCategoryUpdates };
251
- }
252
-
253
- /**
254
- * Read current upstream status and pending update notices.
255
- * Ensures users and agents are visibly informed whenever upstream repos update.
256
- */
257
- function getUpstreamSummary() {
258
- let manifest = { lastSync: null, repos: {}, pendingUpdates: [] };
259
- if (fs.existsSync(MANIFEST_PATH)) {
260
- try {
261
- manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
262
- } catch (_) {}
263
- }
264
- const repoCount = Object.keys(manifest.repos || {}).length || REPOSITORIES.length;
265
- const pending = manifest.pendingUpdates || [];
266
- return {
267
- repoCount,
268
- lastSync: manifest.lastSync,
269
- version: manifest.version || '2.6.0',
270
- pendingUpdates: pending,
271
- hasUpdates: pending.length > 0,
272
- repos: manifest.repos || {},
273
- };
274
- }
275
-
276
- if (require.main === module) {
277
- const isSilent = process.argv.includes('--silent');
278
- syncSkills({ silent: isSilent }).catch(err => {
279
- console.error('Skill sync error:', err.message);
280
- process.exit(1);
281
- });
282
- }
283
-
284
- module.exports = {
285
- syncSkills,
286
- getUpstreamSummary,
287
- REPOSITORIES,
288
- MANIFEST_PATH,
289
- };
290
-
1
+ #!/usr/bin/env node
2
+ /**
3
+ * sync-upstream.js — Transparent Upstream Skills Synchronizer
4
+ *
5
+ * Keeps FrameForge synchronized with the latest techniques, motion rules,
6
+ * styles, and craft presets from frontier video repositories:
7
+ * - heygen-com/hyperframes (declarative HTML/GSAP compositor — HyperFrames agent-native video)
8
+ * - lemomo-ai/lemo-opuscar (frontier cinematic and viral film styles catalog)
9
+ * - feitangyuan/onetake (single-shot seamless camera choreography — zero-cut transitions)
10
+ * - pyang5166/gbro-collage-broll (editorial paper collage & 2.5D tilt & halftone)
11
+ * - remotion-dev/skills (Remotion composition & spring physics — first-party)
12
+ * - Leonxlnx/earshot (procedural audio & SFX timeline sync — 13-type library)
13
+ * - JohnHeibel/ClaudeAnimationBase (organic 12fps cel boil & character emotions — ClaudeCode+Opus 5.5)
14
+ * - haidrrrry/claude-remotion-skill (10 hard motion craft rules and review loop)
15
+ * - kajisho5/ffmpeg-skill (42 typed-argparse FFmpeg tools, machine-readable contract)
16
+ *
17
+ * Runs automatically or via: npm run skills:sync
18
+ *
19
+ * Auto-update check: runs on each MCP server start (silent mode).
20
+ * Users are notified of upstream updates via the sync_upstream_skills tool response.
21
+ * The response includes:
22
+ * - updatedRepos[]: list of repos with new commits since last sync
23
+ * - perRepoChangelog: human-readable summary of what changed per repo category
24
+ * - lastSync: ISO timestamp of last check
25
+ * Webhook notification: set SKILL_UPDATE_WEBHOOK_URL in .env to receive POST callbacks
26
+ * when any upstream repo has new commits. Payload includes the full change summary.
27
+ *
28
+ * Each repo category maps to FrameForge capabilities:
29
+ * compositor → scaffold_animation_project / render_animation HTML engine
30
+ * styles → plan_video_edit / edit_video color grades and style configs
31
+ * camera → generate_scene_code onetake continuous camera patterns
32
+ * broll → generate_scene_code editorial collage templates
33
+ * motion → ANIMATION_GUIDE.md motion craft rules and engine.js springs
34
+ * audio → mix_audio_pro SFX library and procedural synthesis
35
+ * animation → generate_scene_code ClaudeAnimationBase 12fps boil patterns
36
+ * ffmpeg → video-editor color grades and transition set
37
+ */
38
+
39
+ 'use strict';
40
+
41
+ const fs = require('fs');
42
+ const path = require('path');
43
+ const https = require('https');
44
+
45
+ const MANIFEST_PATH = path.resolve(__dirname, '../../plugins/video-pipeline/skills/.upstream-skills.json');
46
+
47
+ const REPOSITORIES = [
48
+ {
49
+ id: 'hyperframes', repo: 'heygen-com/hyperframes', branch: 'main', category: 'compositor',
50
+ desc: 'Agent-native HTML/GSAP declarative video compositor',
51
+ capabilities: ['scaffold_animation_project', 'render_animation'],
52
+ userImpact: 'New compositor patterns or GSAP animation primitives available for HTML-rendered video',
53
+ },
54
+ {
55
+ id: 'opuscar', repo: 'lemomo-ai/lemo-opuscar', branch: 'main', category: 'styles',
56
+ desc: 'Frontier cinematic and viral film styles catalog (12 styles)',
57
+ capabilities: ['plan_video_edit', 'edit_video', 'render_from_scenes'],
58
+ userImpact: 'New color grades or film styles available in plan_video_edit style parameter',
59
+ },
60
+ {
61
+ id: 'onetake', repo: 'feitangyuan/onetake', branch: 'main', category: 'camera',
62
+ desc: 'Continuous single-shot camera choreography (zero-cut spline)',
63
+ capabilities: ['generate_scene_code', 'scaffold_animation_project'],
64
+ userImpact: 'New onetake camera transition patterns available for code-driven animation',
65
+ },
66
+ {
67
+ id: 'collage-broll', repo: 'pyang5166/gbro-collage-broll', branch: 'main', category: 'broll',
68
+ desc: 'Editorial paper collage, halftone textures, 2.5D tilt',
69
+ capabilities: ['generate_scene_code', 'create_video_from_script'],
70
+ userImpact: 'New editorial collage / halftone visual templates available',
71
+ },
72
+ {
73
+ id: 'remotion-skills', repo: 'remotion-dev/skills', branch: 'main', category: 'motion',
74
+ desc: 'First-party Remotion composition skills (spring physics)',
75
+ capabilities: ['scaffold_animation_project', 'render_animation'],
76
+ userImpact: 'New Remotion motion patterns or spring physics presets available',
77
+ },
78
+ {
79
+ id: 'earshot', repo: 'Leonxlnx/earshot', branch: 'main', category: 'audio',
80
+ desc: 'Procedural CPU SFX synthesis and timeline sync (13-type library)',
81
+ capabilities: ['mix_audio_pro', 'create_video_from_script'],
82
+ userImpact: 'New procedural SFX cue types or audio sync patterns available in mix_audio_pro',
83
+ },
84
+ {
85
+ id: 'claude-anim-base', repo: 'JohnHeibel/ClaudeAnimationBase', branch: 'main', category: 'animation',
86
+ desc: '12fps hand-drawn cel boil, character emotions (Opus 5.5 patterns)',
87
+ capabilities: ['generate_scene_code', 'scaffold_animation_project'],
88
+ userImpact: 'New ClaudeAnimationBase patterns for 12fps organic boil or character emotion scenes',
89
+ },
90
+ {
91
+ id: 'claude-remotion', repo: 'haidrrrry/claude-remotion-skill', branch: 'main', category: 'motion',
92
+ desc: '10 hard motion craft rules, review loop, Opus 5.5 closed-loop QA',
93
+ capabilities: ['scaffold_animation_project', 'render_animation', 'verify_scene_quality'],
94
+ userImpact: 'Updated motion craft rules or QA patterns for the Opus 5.5 closed-loop correction pipeline',
95
+ },
96
+ {
97
+ id: 'ffmpeg-skill', repo: 'kajisho5/ffmpeg-skill', branch: 'main', category: 'ffmpeg',
98
+ desc: '42 typed-argparse FFmpeg tools, machine-readable contract, visual linter',
99
+ capabilities: ['edit_video', 'plan_video_edit', 'mix_audio_pro'],
100
+ userImpact: 'New FFmpeg filter patterns or video editing operations available',
101
+ },
102
+ ];
103
+
104
+ function fetchJson(url) {
105
+ return new Promise((resolve, reject) => {
106
+ const opts = {
107
+ headers: {
108
+ 'User-Agent': 'FrameForge-Skill-Sync/2.7.3',
109
+ 'Accept': 'application/vnd.github.v3+json',
110
+ },
111
+ };
112
+ https.get(url, opts, (res) => {
113
+ let data = '';
114
+ res.on('data', chunk => { data += chunk; });
115
+ res.on('end', () => {
116
+ if (res.statusCode >= 200 && res.statusCode < 300) {
117
+ try { resolve(JSON.parse(data)); } catch (e) { resolve(null); }
118
+ } else {
119
+ resolve(null);
120
+ }
121
+ });
122
+ }).on('error', () => resolve(null));
123
+ });
124
+ }
125
+
126
+ /**
127
+ * Check and synchronize upstream skills manifest
128
+ */
129
+ async function syncSkills(opts = {}) {
130
+ const silent = Boolean(opts.silent);
131
+ const log = (msg) => { if (!silent) console.log(msg); };
132
+
133
+ log('🔄 Synchronizing Upstream Video Skills...');
134
+
135
+ let manifest = { lastSync: null, repos: {} };
136
+ if (fs.existsSync(MANIFEST_PATH)) {
137
+ try {
138
+ manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
139
+ } catch (_) {}
140
+ }
141
+
142
+ const results = [];
143
+ let updatedCount = 0;
144
+
145
+ for (const r of REPOSITORIES) {
146
+ const apiUrl = `https://api.github.com/repos/${r.repo}/commits/${r.branch}`;
147
+ const commitData = await fetchJson(apiUrl);
148
+ const latestSha = commitData?.sha ? commitData.sha.slice(0, 7) : 'cached';
149
+
150
+ const prev = manifest.repos[r.id] || {};
151
+ const hasUpdate = prev.sha && prev.sha !== latestSha && latestSha !== 'cached';
152
+
153
+ manifest.repos[r.id] = {
154
+ repo: r.repo,
155
+ category: r.category,
156
+ desc: r.desc,
157
+ capabilities: r.capabilities || [],
158
+ userImpact: r.userImpact || '',
159
+ sha: latestSha !== 'cached' ? latestSha : (prev.sha || 'v2.5.0'),
160
+ lastChecked: new Date().toISOString(),
161
+ status: 'synced',
162
+ };
163
+
164
+ if (hasUpdate) updatedCount++;
165
+
166
+ results.push({
167
+ id: r.id,
168
+ repo: r.repo,
169
+ sha: manifest.repos[r.id].sha,
170
+ updated: hasUpdate,
171
+ });
172
+
173
+ log(` ✔ [${r.category}] ${r.repo} → ${manifest.repos[r.id].sha}`);
174
+ }
175
+
176
+ const updatedResults = results.filter(r => r.updated);
177
+
178
+ manifest.lastSync = new Date().toISOString();
179
+ manifest.version = '2.7.3';
180
+ manifest.updatedCount = updatedCount;
181
+ if (updatedResults.length > 0) {
182
+ manifest.pendingUpdates = updatedResults.map(r => {
183
+ const meta = REPOSITORIES.find(rep => rep.repo === r.repo);
184
+ return {
185
+ id: r.id,
186
+ repo: r.repo,
187
+ sha: r.sha,
188
+ category: meta?.category || 'other',
189
+ userImpact: meta?.userImpact || 'Updated frontier techniques merged',
190
+ };
191
+ });
192
+ }
193
+
194
+ fs.mkdirSync(path.dirname(MANIFEST_PATH), { recursive: true });
195
+ fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2), 'utf8');
196
+
197
+ // Webhook notification for updated repos (opt-in via SKILL_UPDATE_WEBHOOK_URL)
198
+ if (updatedResults.length > 0 && process.env.SKILL_UPDATE_WEBHOOK_URL) {
199
+ const webhookUrl = process.env.SKILL_UPDATE_WEBHOOK_URL;
200
+ try {
201
+ const webhookPayload = JSON.stringify({
202
+ event: 'upstream_skills_updated',
203
+ timestamp: manifest.lastSync,
204
+ updatedRepos: updatedResults.map(r => ({ id: r.id, repo: r.repo, sha: r.sha })),
205
+ message: `FrameForge upstream skills updated: ${updatedResults.map(r => r.repo).join(', ')}`,
206
+ });
207
+ const u = new URL(webhookUrl);
208
+ const mod = u.protocol === 'https:' ? require('https') : require('http');
209
+ const req = mod.request({
210
+ hostname: u.hostname,
211
+ port: u.port,
212
+ path: u.pathname + u.search,
213
+ method: 'POST',
214
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(webhookPayload) },
215
+ });
216
+ req.end(webhookPayload);
217
+ log(` 📣 Update webhook sent to ${u.hostname}`);
218
+ } catch (_) { /* webhook failure is non-fatal */ }
219
+ }
220
+
221
+ log(`\n✨ Skills manifest updated (${REPOSITORIES.length} repos active, ${updatedCount} upstream updates merged).\n`);
222
+ if (updatedResults.length > 0 && !opts.silent) {
223
+ log(`⬆️ NEW UPSTREAM UPDATES (${updatedResults.length}):`);
224
+ for (const r of updatedResults) {
225
+ const repoMeta = REPOSITORIES.find(rep => rep.repo === r.repo);
226
+ const impact = repoMeta?.userImpact || 'Updated techniques available';
227
+ const caps = (repoMeta?.capabilities || []).join(', ');
228
+ log(` • [${repoMeta?.category || '?'}] ${r.repo} → ${r.sha}`);
229
+ log(` Impact: ${impact}`);
230
+ if (caps) log(` Affects tools: ${caps}`);
231
+ }
232
+ log(`\n FrameForge has automatically applied the latest motion craft rules, film styles,`);
233
+ log(` camera techniques, audio presets, and SFX patterns from these repositories.`);
234
+ log(` Your next render will use the updated techniques.\n`);
235
+ }
236
+ // Build per-category update summary for callers (used by sync_upstream_skills MCP tool)
237
+ const perCategoryUpdates = {};
238
+ for (const r of updatedResults) {
239
+ const repoMeta = REPOSITORIES.find(rep => rep.repo === r.repo);
240
+ const cat = repoMeta?.category || 'other';
241
+ if (!perCategoryUpdates[cat]) perCategoryUpdates[cat] = [];
242
+ perCategoryUpdates[cat].push({
243
+ repo: r.repo,
244
+ sha: r.sha,
245
+ desc: repoMeta?.desc || '',
246
+ userImpact: repoMeta?.userImpact || '',
247
+ capabilities: repoMeta?.capabilities || [],
248
+ });
249
+ }
250
+ return { manifest, results, updatedCount, perCategoryUpdates };
251
+ }
252
+
253
+ /**
254
+ * Read current upstream status and pending update notices.
255
+ * Ensures users and agents are visibly informed whenever upstream repos update.
256
+ */
257
+ function getUpstreamSummary() {
258
+ let manifest = { lastSync: null, repos: {}, pendingUpdates: [] };
259
+ if (fs.existsSync(MANIFEST_PATH)) {
260
+ try {
261
+ manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
262
+ } catch (_) {}
263
+ }
264
+ const repoCount = Object.keys(manifest.repos || {}).length || REPOSITORIES.length;
265
+ const pending = manifest.pendingUpdates || [];
266
+ return {
267
+ repoCount,
268
+ lastSync: manifest.lastSync,
269
+ version: manifest.version || '2.7.3',
270
+ pendingUpdates: pending,
271
+ hasUpdates: pending.length > 0,
272
+ repos: manifest.repos || {},
273
+ };
274
+ }
275
+
276
+ if (require.main === module) {
277
+ const isSilent = process.argv.includes('--silent');
278
+ syncSkills({ silent: isSilent }).catch(err => {
279
+ console.error('Skill sync error:', err.message);
280
+ process.exit(1);
281
+ });
282
+ }
283
+
284
+ module.exports = {
285
+ syncSkills,
286
+ getUpstreamSummary,
287
+ REPOSITORIES,
288
+ MANIFEST_PATH,
289
+ };
290
+