Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
eb1af41
chore(deps): npm audit fix
Jrocchetti Sep 16, 2026
5def3bc
feat(shapes): bezier paths, shape operators, and matchName lookups
Jrocchetti Sep 16, 2026
4f6e141
feat(tools): expose 10 generators that were written but never registered
Jrocchetti Sep 16, 2026
5a5a57f
feat(render): add render queue tools
Jrocchetti Sep 16, 2026
609858c
feat(masks): add mask creation, animation and property tools
Jrocchetti Sep 16, 2026
fce4678
feat(3d): add renderer, transform, material, geometry, camera and lig…
Jrocchetti Sep 16, 2026
63ea03c
fix(keyframes): stop destroying keyframes on bad input, and fix spati…
Jrocchetti Sep 16, 2026
21e95b2
fix(generators): object literals parsed as blocks broke four tools
Jrocchetti Sep 16, 2026
70a1baf
fix(effects): reorder_effects stale reference, copy_effects syntax error
Jrocchetti Sep 16, 2026
94306d7
fix(layers): polystar star/polygon types were reversed
Jrocchetti Sep 16, 2026
02f0d0c
fix(templates): create_logo_reveal fails clearly when no logo is given
Jrocchetti Sep 16, 2026
e1a4508
docs(skill): correct the ae-mcp skill against the real tools
Jrocchetti Sep 16, 2026
3f34d85
feat(templates): implement the particle logo reveal
Jrocchetti Sep 16, 2026
69b3214
fix(render_frame): wait for saveFrameToPng, and stop trusting stale f…
Jrocchetti Sep 16, 2026
848f596
fix(aliases): point three property shortcuts at properties that exist
Jrocchetti Sep 16, 2026
acb44e1
docs(skill): document particles and the repaired shortcuts
Jrocchetti Sep 16, 2026
6bcb9e1
feat(layers): collapseTransformation, and unclip the particle reveal
Jrocchetti Sep 16, 2026
6c90974
docs(skill): effect bounds, plugin name collisions, particle text exa…
Jrocchetti Sep 16, 2026
a6ae752
docs(skill): don't promise named, single-step undo
Jrocchetti Sep 16, 2026
e74eeac
fix(expressions): repair physics templates; add speed control and wig…
Jrocchetti Sep 16, 2026
c4f61ea
docs(skill): document the repaired physics templates and new ones
Jrocchetti Sep 16, 2026
211d60a
Merge remote-tracking branch 'origin/main' into pr/shape-mask-render-…
ishu86 Oct 8, 2026
ad74243
test: add portable test discovery and CI
ishu86 Oct 8, 2026
caabbaa
fix: harden new tools and preserve expression compatibility
ishu86 Oct 8, 2026
b756802
chore(deps): update MCP SDK to patched release
ishu86 Oct 8, 2026
f22b88a
fix: validate new tool inputs and clarify render limitations
ishu86 Oct 8, 2026
dfc63e4
Merge remote-tracking branch 'origin/main' into pr/shape-mask-render-…
ishu86 Oct 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
442 changes: 178 additions & 264 deletions SKILL/SKILL.md

Large diffs are not rendered by default.

409 changes: 248 additions & 161 deletions SKILL/examples.md

Large diffs are not rendered by default.

238 changes: 157 additions & 81 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"author": "",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"@modelcontextprotocol/sdk": "^1.32.1",
"winston": "^3.11.0",
"zod": "^3.22.4"
},
Expand Down
10 changes: 6 additions & 4 deletions src/ae-integration/generators/assetGenerators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,13 @@ export function generateImportFolder(params: {
script += 'var rootFolder = app.project.items.addFolder(folder.name);\n';
script += 'importFilesFromFolder(folder, rootFolder);\n';

script += '{\n';
script += 'var result = ({\n';
script += ' folderId: rootFolder.id,\n';
script += ' folderName: rootFolder.name,\n';
script += ' importedCount: importedItems.length,\n';
script += ' items: importedItems\n';
script += '};\n';
script += '});\n';
script += 'result;\n';

return wrapInUndoGroup(script, 'Import Folder');
}
Expand Down Expand Up @@ -271,11 +272,12 @@ export function generateCollectFiles(params: {
script += 'var projectFile = new File(outputFolder.fsName + "/" + (app.project.file ? app.project.file.name : "collected_project.aep"));\n';
script += 'app.project.save(projectFile);\n';

script += '{\n';
script += 'var result = ({\n';
script += ' outputPath: outputFolder.fsName,\n';
script += ' collectedCount: collectedFiles.length,\n';
script += ' files: collectedFiles\n';
script += '};\n';
script += '});\n';
script += 'result;\n';

return wrapInUndoGroup(script, 'Collect Files');
}
Expand Down
26 changes: 24 additions & 2 deletions src/ae-integration/generators/compositionGenerators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,15 +293,37 @@ export function generateRenderFrame(params: {
script += 'if (!comp.saveFrameToPng) {\n';
script += ' throw new Error("saveFrameToPng is not available in this After Effects version");\n';
script += '}\n';
// Remove any previous render first. Otherwise an old file satisfies the
// existence check below even when nothing new was written.
script += 'if (outFile.exists && !outFile.remove()) {\n';
script += ' throw new Error("Cannot remove the previous frame render: " + outFile.fsName);\n';
script += '}\n';
script += 'comp.saveFrameToPng(t, outFile);\n';
// saveFrameToPng returns BEFORE the file is on disk (measured on AE 26.5: it
// appears ~50 ms later). Checking immediately reported every render as a
// failure. Wait for the file to appear, then for its size to stop changing,
// as a best-effort check for an incomplete write.
script += 'var waitedMs = 0;\n';
script += 'while (!outFile.exists && waitedMs < 15000) { $.sleep(50); waitedMs += 50; }\n';
script += 'if (!outFile.exists) {\n';
script += ' throw new Error("Frame render did not produce a file: " + outFile.fsName);\n';
script += ' throw new Error("Frame render did not produce a file within 15s: " + outFile.fsName);\n';
script += '}\n';
script += 'var lastSize = -1, stableChecks = 0;\n';
script += 'while (stableChecks < 3 && waitedMs < 15000) {\n';
script += ' $.sleep(50); waitedMs += 50;\n';
script += ' if (outFile.length > 0 && outFile.length === lastSize) { stableChecks++; } else { stableChecks = 0; }\n';
script += ' lastSize = outFile.length;\n';
script += '}\n';
script += 'if (stableChecks < 3 || !outFile.exists || outFile.length <= 0) {\n';
script += ' throw new Error("Frame render did not finish writing within 15s: " + outFile.fsName);\n';
script += '}\n';

script += generateResultObject({
success: 'true',
time: 't',
path: 'outFile.fsName'
path: 'outFile.fsName',
bytes: 'outFile.length',
waitedMs: 'waitedMs'
});

return script;
Expand Down
20 changes: 13 additions & 7 deletions src/ae-integration/generators/effectsGenerators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,16 +450,21 @@ export function generateReorderEffects(params: {
script += generateCompAccess(params.compId, params.compName);
script += generateLayerAccess('comp', params.layerIndex, params.layerName);

script += 'var effects = layer.property("Effects");\n';
script += 'var effect = effects(' + params.effectIndex + ');\n';
script += 'if (!effect) {\n';
script += ' throw new Error("Effect not found at index: ' + params.effectIndex + '");\n';
script += 'var effects = layer.property("ADBE Effect Parade");\n';
script += 'if (' + params.effectIndex + ' < 1 || ' + params.effectIndex + ' > effects.numProperties) {\n';
script += ' throw new Error("Effect index ' + params.effectIndex + ' out of range; layer has " + effects.numProperties + " effect(s)");\n';
script += '}\n';
script += 'if (' + params.newIndex + ' < 1 || ' + params.newIndex + ' > effects.numProperties) {\n';
script += ' throw new Error("New index ' + params.newIndex + ' out of range; layer has " + effects.numProperties + " effect(s)");\n';
script += '}\n';
script += 'var effect = effects.property(' + params.effectIndex + ');\n';

// moveTo() invalidates this reference, so read the name first.
script += 'var movedEffectName = effect.name;\n';
script += 'effect.moveTo(' + params.newIndex + ');\n';

script += generateResultObject({
effectName: 'effect.name',
effectName: 'movedEffectName',
newIndex: String(params.newIndex)
});

Expand Down Expand Up @@ -523,10 +528,11 @@ export function generateCopyEffects(params: {
script += ' }\n';
script += '}\n';

script += '{\n';
script += 'var result = ({\n';
script += ' copiedEffects: copiedEffects,\n';
script += ' count: copiedEffects.length\n';
script += '};\n';
script += '});\n';
script += 'result;\n';

return wrapInUndoGroup(script, 'Copy Effects');
}
Expand Down
Loading
Loading