brainferno-mcp-bridge 0.2.2 → 0.3.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.3.0 — 2026-09-05
4
+
5
+ - After Effects `ae_import_as_comp`: import a layered Photoshop (.psd) or Illustrator (.ai)
6
+ file as a composition — each source layer becomes its own AE layer, keeping blend modes,
7
+ positions, and (for PSD) editable layer styles. `cropped` chooses Composition - Cropped
8
+ Layers. Verified live: a PSD came in as three layers with its drop shadow editable in AE.
9
+ (Illustrator maps top-level layers to AE layers, so put objects on separate layers to keep
10
+ them separate; the file must be RGB and saved PDF-compatible.)
11
+ - After Effects Essential Graphics: `ae_add_to_essential_graphics` exposes a layer property
12
+ (transform channel, a text layer's source text, or any property via propertyPath) as a
13
+ control, and `ae_export_mogrt` exports the composition as a Motion Graphics template
14
+ (.mogrt). Verified live against AE 26.3.
15
+ - Photoshop gradient overlay: `ps_set_layer_style` now takes `style: "gradientOverlay"` with
16
+ `colors` (two or more hex stops), `gradientStyle` (linear/radial/angle/reflected/diamond),
17
+ `angle`, `scale`, `reverse`, `alignWithLayer`, and `dither`.
18
+ - After Effects `ae_set_expression` now rolls the property back to its previous expression
19
+ when the new one fails to compile (it used to leave the broken source assigned).
20
+ - Layer styles for Photoshop and After Effects. Photoshop: `ps_set_layer_style` (add or edit
21
+ a drop shadow, inner shadow, glow, bevel & emboss, satin, color overlay, or stroke — other
22
+ styles on the layer are kept), `ps_get_layer_styles`, `ps_remove_layer_style`. After
23
+ Effects: `ae_add_layer_style`, `ae_set_layer_style_param`, `ae_get_layer_styles`,
24
+ `ae_remove_layer_style` — friendly names map to AE's match names (satin → `chromeFX`,
25
+ colorOverlay → `solidFill`, stroke → `frameFX`).
26
+ - New Premiere Pro tool `pp_insert_mogrt`: drop a Motion Graphics template (.mogrt) on the
27
+ timeline at a time, like dragging it from the Essential Graphics panel. Without
28
+ `videoTrackIndex` it lands on the first track above every video clip playing at that time,
29
+ so the graphic sits over the picture.
30
+
3
31
  ## v0.2.2 — 2026-08-30
4
32
 
5
33
  - The server reported itself as **0.1.0** to every MCP client and every panel, whatever
package/README.md CHANGED
@@ -25,8 +25,9 @@ the way. Every action is one undo step in the app, so you can always step back.
25
25
  - **Runs on your machine.** Nothing leaves your computer unless you turn on *shared* mode
26
26
  for other computers on your network (token-protected).
27
27
 
28
- > Status: v0.2 — every tool verified live on Windows and macOS, the two platforms Adobe
29
- > ships Creative Cloud for (Adobe 2026 apps). See [Status](#status).
28
+ > Status: every tool verified live on Windows and macOS, the two platforms Adobe ships
29
+ > Creative Cloud for (Adobe 2026 apps). The npm badge above is the current version.
30
+ > See [Status](#status).
30
31
 
31
32
  The companion panel inside each app — here in Adobe After Effects and Adobe Photoshop —
32
33
  shows the connection, a live log, and a kill switch:
@@ -77,22 +78,28 @@ Ideas people build with it:
77
78
  Counts are the tools registered per app. Each family has a live-run write-up in
78
79
  `docs/spikes/` with the quirks found on real installs.
79
80
 
80
- ### Photoshop — 18 tools
81
+ ### Photoshop — 21 tools
81
82
  Documents (list, create, open, save, export PNG/JPEG, preview image), layers (create,
82
- text layers with font/size/color, properties, move, duplicate, delete), place an image as a
83
- smart object, fill, filters (Gaussian/motion/unsharp…), resize, crop.
84
-
85
- ### After Effects — 24 tools
86
- Project info and file, compositions and footage (list, create, import), layers of every
87
- kind (footage, solid, text, null, adjustment), keyframes with easing, expressions, effects
88
- and their parameters, text content/font/color/justification, markers, single-frame preview,
89
- render queue, and headless rendering through `aerender` (the UI stays free).
90
-
91
- ### Premiere Pro — 28 tools
83
+ text layers with font/size/color, properties, move, duplicate, delete), layer styles
84
+ (drop shadow, glows, bevel, satin, color and gradient overlay, stroke… — add, adjust, read,
85
+ remove), place an image as a smart object, fill, filters (Gaussian/motion/unsharp…),
86
+ resize, crop.
87
+
88
+ ### After Effects — 31 tools
89
+ Project info and file, compositions and footage (list, create, import), import a layered
90
+ PSD or AI file as a composition (one layer each, with PSD layer styles editable), layers of
91
+ every kind (footage, solid, text, null, adjustment), keyframes with easing, expressions,
92
+ effects and their parameters, layer styles (drop shadow, glows, bevel, stroke… — add, adjust,
93
+ read, remove), text content/font/color/justification, markers, Essential Graphics (expose a
94
+ control, export a Motion Graphics template), single-frame preview, render queue, and
95
+ headless rendering through `aerender` (the UI stays free).
96
+
97
+ ### Premiere Pro — 29 tools
92
98
  Project, sequences and items, a full sequence read (every track and clip with times),
93
- import, create a sequence from media, insert/overwrite, ripple remove, move, trim, rename,
94
- transitions, effects and keyframes (Motion, Opacity, any applied effect), markers, frame
95
- preview, export preset search, and export (in Premiere or handed to Media Encoder).
99
+ import, create a sequence from media, insert/overwrite, Motion Graphics templates (.mogrt)
100
+ dropped over the picture, ripple remove, move, trim, rename, transitions, effects and
101
+ keyframes (Motion, Opacity, any applied effect), markers, frame preview, export preset
102
+ search, and export (in Premiere or handed to Media Encoder).
96
103
 
97
104
  ### Illustrator — 7 tools + Adobe's own 46
98
105
  Create documents, draw shapes (rect, ellipse, line, polygon, star) and text, save `.ai`,
@@ -412,8 +419,9 @@ docs/ build plan, protocol, live-run notes (spikes/), Audition AP
412
419
 
413
420
  - **Working, verified live on Windows and macOS (Adobe 2026 apps)**: all six application
414
421
  lanes and their v1 tool sets, the ffmpeg lane, the job registry, the four pipelines, remote
415
- mode, and the installer. Write-ups with the quirks found: `docs/spikes/05`–`13` (`13` is the
416
- macOS run: aerender path, Media Encoder's config file and renderer).
422
+ mode, and the installer. Write-ups with the quirks found: `docs/spikes/05`–`14` (`13` is the
423
+ macOS run: aerender path, Media Encoder's config file and renderer; `14` is the release
424
+ process and what npm actually does after a publish).
417
425
  - **Not yet**: double-click panel installs (`.ccx`/`.zxp`) so the UXP Developer
418
426
  Tool is not needed; a single signed installer; TLS for shared mode; Audition multitrack
419
427
  writes; Media Encoder queue control.
@@ -9,6 +9,14 @@ export declare function getLayerScript(compId: number, layerIndex: number): stri
9
9
  export declare function openProjectScript(path: string): string;
10
10
  export declare function saveProjectScript(path: string | undefined): string;
11
11
  export declare function importFootageScript(path: string): string;
12
+ /**
13
+ * Import a layered PSD or AI as a composition (one AE layer per source layer).
14
+ * ImportAsType.COMP keeps each layer at document size; COMP_CROPPED_LAYERS crops
15
+ * to content. The importer preserves blend modes and positions, and — for PSD —
16
+ * layer styles as After Effects imports them. There is no ImportOptions flag for
17
+ * the dialog's "editable vs merged layer styles"; AE uses its own default.
18
+ */
19
+ export declare function importAsCompScript(path: string, cropped: boolean): string;
12
20
  export interface CreateCompParams {
13
21
  name: string;
14
22
  width: number;
@@ -62,6 +70,43 @@ export declare function removeKeyframesScript(compId: number, layerIndex: number
62
70
  export declare function setExpressionScript(compId: number, layerIndex: number, property: string, propertyPath: string[] | undefined, expression: string | null): string;
63
71
  export declare function applyEffectScript(compId: number, layerIndex: number, matchName: string): string;
64
72
  export declare function setEffectParamScript(compId: number, layerIndex: number, effect: number | string, param: string, value: number | number[] | string): string;
73
+ /**
74
+ * Layer styles are Photoshop's effects (drop shadow, stroke, …) living under
75
+ * "ADBE Layer Styles". Each maps to an internal key; the group is addressed by
76
+ * its match name "<key>/enabled" and its parameters by "<key>/<param>".
77
+ */
78
+ export declare const LAYER_STYLE_KEYS: {
79
+ readonly dropShadow: "dropShadow";
80
+ readonly innerShadow: "innerShadow";
81
+ readonly outerGlow: "outerGlow";
82
+ readonly innerGlow: "innerGlow";
83
+ readonly bevelEmboss: "bevelEmboss";
84
+ readonly satin: "chromeFX";
85
+ readonly colorOverlay: "solidFill";
86
+ readonly gradientOverlay: "gradientFill";
87
+ readonly stroke: "frameFX";
88
+ };
89
+ export type LayerStyleName = keyof typeof LAYER_STYLE_KEYS;
90
+ export declare function addLayerStyleScript(compId: number, layerIndex: number, style: LayerStyleName): string;
91
+ export declare function getLayerStylesScript(compId: number, layerIndex: number): string;
92
+ export declare function setLayerStyleParamScript(compId: number, layerIndex: number, style: LayerStyleName, param: string, value: number | number[] | string): string;
93
+ export declare function removeLayerStyleScript(compId: number, layerIndex: number, style: LayerStyleName): string;
94
+ /**
95
+ * Expose a layer property in the comp's Essential Graphics panel so the comp can
96
+ * be exported as a Motion Graphics template. Verified live on AE 26.3:
97
+ * canAddToMotionGraphicsTemplate / addToMotionGraphicsTemplate take the comp the
98
+ * property lives in; the control is named after the property (AE 26.3 has no
99
+ * script to rename it). Source Text lives at ADBE Text Properties > Text Document.
100
+ */
101
+ export declare function addToEssentialGraphicsScript(compId: number, layerIndex: number, property: string, propertyPath: string[] | undefined): string;
102
+ /**
103
+ * Export the comp as a .mogrt. The host's exportAsMotionGraphicsTemplate takes a
104
+ * *folder* and names the file from motionGraphicsTemplateName, so we split the
105
+ * requested path, set the name, and write <folder>/<name>.mogrt. Verified live on
106
+ * AE 26.3 (the pre-24.3 "always returns false" bug is gone; we still check the
107
+ * file exists). The project must be saved — a dirty project makes AE prompt.
108
+ */
109
+ export declare function exportMogrtScript(compId: number, folder: string, name: string, overwrite: boolean): string;
65
110
  export interface SetTextParams {
66
111
  compId: number;
67
112
  layerIndex: number;
@@ -78,6 +78,30 @@ const HELPERS = `
78
78
  function __compInfo(c) {
79
79
  return { id: c.id, name: c.name, width: c.width, height: c.height, duration: c.duration, frameRate: c.frameRate, numLayers: c.numLayers, pixelAspect: c.pixelAspect };
80
80
  }
81
+ function __styles(l) {
82
+ var g = l.property("ADBE Layer Styles");
83
+ if (!g) { throw new Error("Layer " + l.index + " has no layer styles group (cameras and lights cannot take styles)."); }
84
+ return g;
85
+ }
86
+ // A style's group is fetched by its match name "<key>/enabled" (the short key
87
+ // resolves for dropShadow only). canSetEnabled is false until the style is
88
+ // actually added, so it doubles as "is this style present".
89
+ function __styleGroup(g, key) {
90
+ var f = null;
91
+ try { f = g.property(key + "/enabled"); } catch (e) {}
92
+ if (f && !f.canSetEnabled) { f = null; }
93
+ return f;
94
+ }
95
+ function __styleParams(s) {
96
+ var out = [];
97
+ for (var i = 1; i <= s.numProperties; i++) {
98
+ var q = s.property(i);
99
+ var v = null;
100
+ try { v = q.value; } catch (e) {}
101
+ out.push({ index: i, name: q.name, matchName: q.matchName, value: v });
102
+ }
103
+ return out;
104
+ }
81
105
  `;
82
106
  const wrap = (body) => `(function () {${HELPERS}${body}
83
107
  })()`;
@@ -152,6 +176,32 @@ export function importFootageScript(path) {
152
176
  return { id: item.id, name: item.name, width: item.width, height: item.height, duration: item.duration, hasVideo: item.hasVideo, hasAudio: item.hasAudio };
153
177
  });`);
154
178
  }
179
+ /**
180
+ * Import a layered PSD or AI as a composition (one AE layer per source layer).
181
+ * ImportAsType.COMP keeps each layer at document size; COMP_CROPPED_LAYERS crops
182
+ * to content. The importer preserves blend modes and positions, and — for PSD —
183
+ * layer styles as After Effects imports them. There is no ImportOptions flag for
184
+ * the dialog's "editable vs merged layer styles"; AE uses its own default.
185
+ */
186
+ export function importAsCompScript(path, cropped) {
187
+ const want = cropped ? "ImportAsType.COMP_CROPPED_LAYERS" : "ImportAsType.COMP";
188
+ return wrap(`
189
+ var f = new File(${lit(path)});
190
+ if (!f.exists) { throw new Error("No file at " + f.fsName); }
191
+ return __undo("Import as composition", function () {
192
+ var io = new ImportOptions(f);
193
+ if (io.canImportAs(${want})) { io.importAs = ${want}; }
194
+ else if (io.canImportAs(ImportAsType.COMP)) { io.importAs = ImportAsType.COMP; }
195
+ else { throw new Error("After Effects can't import this as a composition. Layered .psd works; an .ai must be RGB (not CMYK) and saved with PDF compatibility."); }
196
+ var item = app.project.importFile(io);
197
+ if (!(item instanceof CompItem)) { throw new Error("Imported '" + item.name + "', but not as a composition — the file may have only one layer."); }
198
+ var info = __compInfo(item);
199
+ var layers = [];
200
+ for (var i = 1; i <= item.numLayers; i++) { layers.push(__layerInfo(item.layer(i))); }
201
+ info.layers = layers;
202
+ return info;
203
+ });`);
204
+ }
155
205
  export function createCompScript(p) {
156
206
  return wrap(`
157
207
  return __undo("Create comp", function () {
@@ -271,8 +321,15 @@ export function setExpressionScript(compId, layerIndex, property, propertyPath,
271
321
  return __undo("Set expression", function () {
272
322
  var e = ${opt(expression)};
273
323
  if (e === null) { prop.expression = ""; return { property: prop.name, expression: null }; }
324
+ // Keep the old expression so a failed compile leaves the property untouched
325
+ // (AE otherwise keeps the broken source assigned, just disabled).
326
+ var prev = prop.expression;
274
327
  prop.expression = e;
275
- if (prop.expressionError) { throw new Error("Expression error: " + prop.expressionError); }
328
+ if (prop.expressionError) {
329
+ var err = prop.expressionError;
330
+ prop.expression = prev;
331
+ throw new Error("Expression error: " + err);
332
+ }
276
333
  return { property: prop.name, expression: prop.expression, enabled: prop.expressionEnabled };
277
334
  });`);
278
335
  }
@@ -299,6 +356,180 @@ export function setEffectParamScript(compId, layerIndex, effect, param, value) {
299
356
  if (!pp) { throw new Error("Parameter not found: " + ${lit(param)}); }
300
357
  return __undo("Set effect parameter", function () { pp.setValue(${v}); return { effect: e.name, param: pp.name, value: pp.value }; });`);
301
358
  }
359
+ /**
360
+ * Layer styles are Photoshop's effects (drop shadow, stroke, …) living under
361
+ * "ADBE Layer Styles". Each maps to an internal key; the group is addressed by
362
+ * its match name "<key>/enabled" and its parameters by "<key>/<param>".
363
+ */
364
+ export const LAYER_STYLE_KEYS = {
365
+ dropShadow: "dropShadow",
366
+ innerShadow: "innerShadow",
367
+ outerGlow: "outerGlow",
368
+ innerGlow: "innerGlow",
369
+ bevelEmboss: "bevelEmboss",
370
+ satin: "chromeFX",
371
+ colorOverlay: "solidFill",
372
+ gradientOverlay: "gradientFill",
373
+ stroke: "frameFX",
374
+ };
375
+ /**
376
+ * Layer styles cannot be added with addProperty (After Effects refuses it). The
377
+ * working way is the Layer > Layer Styles menu command, which needs the comp
378
+ * open in the viewer and the target layer selected. The menu ids are the stable
379
+ * 9000–9008 block (verified live); findMenuCommandId returned a bogus id once
380
+ * after a reconnect, so the fixed id runs first and the label is only a retry.
381
+ */
382
+ const LAYER_STYLE_MENU = {
383
+ dropShadow: { label: "Drop Shadow", id: 9000 },
384
+ innerShadow: { label: "Inner Shadow", id: 9001 },
385
+ outerGlow: { label: "Outer Glow", id: 9002 },
386
+ innerGlow: { label: "Inner Glow", id: 9003 },
387
+ bevelEmboss: { label: "Bevel and Emboss", id: 9004 },
388
+ satin: { label: "Satin", id: 9005 },
389
+ colorOverlay: { label: "Color Overlay", id: 9006 },
390
+ gradientOverlay: { label: "Gradient Overlay", id: 9007 },
391
+ stroke: { label: "Stroke", id: 9008 },
392
+ };
393
+ export function addLayerStyleScript(compId, layerIndex, style) {
394
+ const key = LAYER_STYLE_KEYS[style];
395
+ const menu = LAYER_STYLE_MENU[style];
396
+ return wrap(`
397
+ var c = __comp(${num(compId)}); var l = __layer(c, ${num(layerIndex)});
398
+ var g = __styles(l);
399
+ var s = __styleGroup(g, ${lit(key)});
400
+ if (s) {
401
+ return __undo("Enable layer style", function () {
402
+ try { s.enabled = true; } catch (e) {}
403
+ return { style: ${lit(style)}, matchName: s.matchName, params: __styleParams(s), alreadyPresent: true };
404
+ });
405
+ }
406
+ // addProperty does not work for layer styles — the Layer Styles menu command
407
+ // is the only way, and it needs the comp foremost and only this layer selected.
408
+ return __undo("Add layer style", function () {
409
+ c.openInViewer();
410
+ for (var j = 1; j <= c.numLayers; j++) { c.layer(j).selected = false; }
411
+ l.selected = true;
412
+ app.executeCommand(${num(menu.id)});
413
+ var added = __styleGroup(g, ${lit(key)});
414
+ if (!added) {
415
+ // Very rare: the fixed id was renumbered. Retry via the localized label.
416
+ var mid = 0;
417
+ try { mid = app.findMenuCommandId(${lit(menu.label)}); } catch (e) {}
418
+ if (mid) { app.executeCommand(mid); added = __styleGroup(g, ${lit(key)}); }
419
+ }
420
+ if (!added) { throw new Error("Could not add the ${style} layer style (menu command did not take; is the layer a camera or light?)."); }
421
+ try { added.enabled = true; } catch (e) {}
422
+ return { style: ${lit(style)}, matchName: added.matchName, params: __styleParams(added) };
423
+ });`);
424
+ }
425
+ export function getLayerStylesScript(compId, layerIndex) {
426
+ return wrap(`
427
+ var c = __comp(${num(compId)}); var l = __layer(c, ${num(layerIndex)});
428
+ var g = __styles(l);
429
+ var out = { enabled: g.enabled, styles: [] };
430
+ for (var i = 1; i <= g.numProperties; i++) {
431
+ var s = g.property(i);
432
+ if (s.matchName === "ADBE Blend Options Group") { continue; }
433
+ if (!s.canSetEnabled) { continue; } // hidden template group, not actually on the layer
434
+ out.styles.push({ name: s.name, matchName: s.matchName, enabled: s.enabled, params: __styleParams(s) });
435
+ }
436
+ return out;`);
437
+ }
438
+ export function setLayerStyleParamScript(compId, layerIndex, style, param, value) {
439
+ const key = LAYER_STYLE_KEYS[style];
440
+ // Hex colors become AE's [r, g, b, a] 0–1 arrays; other strings pass through.
441
+ const hexMatch = typeof value === "string" && /^#[0-9a-fA-F]{6}$/.test(value);
442
+ const v = hexMatch ? `${rgb(value).slice(0, -1)}, 1]` : typeof value === "string" ? lit(value) : Array.isArray(value) ? `[${value.map(num).join(", ")}]` : num(value);
443
+ return wrap(`
444
+ var c = __comp(${num(compId)}); var l = __layer(c, ${num(layerIndex)});
445
+ var g = __styles(l);
446
+ var s = __styleGroup(g, ${lit(key)});
447
+ if (!s) { throw new Error("The ${style} style is not on this layer. Call ae_add_layer_style first."); }
448
+ var q = null;
449
+ var want = ${lit(param)};
450
+ var wl = want.toLowerCase();
451
+ for (var i = 1; i <= s.numProperties; i++) {
452
+ var cand = s.property(i);
453
+ if (cand.matchName === want || String(cand.name).toLowerCase() === wl) { q = cand; break; }
454
+ }
455
+ if (!q) {
456
+ var names = [];
457
+ for (var j = 1; j <= s.numProperties; j++) { names.push(s.property(j).name); }
458
+ throw new Error("No parameter " + want + " on " + s.name + ". Parameters: " + names.join(", "));
459
+ }
460
+ return __undo("Set layer style parameter", function () { q.setValue(${v}); return { style: ${lit(style)}, param: q.name, matchName: q.matchName, value: q.value }; });`);
461
+ }
462
+ export function removeLayerStyleScript(compId, layerIndex, style) {
463
+ const key = LAYER_STYLE_KEYS[style];
464
+ // After Effects keeps a permanent slot per style and rejects .remove() on it,
465
+ // so "remove" turns the style off (enabled = false) — the visible effect goes
466
+ // away, matching what the eyeball toggle does in the UI.
467
+ return wrap(`
468
+ var c = __comp(${num(compId)}); var l = __layer(c, ${num(layerIndex)});
469
+ var g = __styles(l);
470
+ var s = __styleGroup(g, ${lit(key)});
471
+ if (!s) { throw new Error("The ${style} style is not on this layer."); }
472
+ return __undo("Turn off layer style", function () { s.enabled = false; return { style: ${lit(style)}, enabled: false }; });`);
473
+ }
474
+ // ---- Essential Graphics / Motion Graphics templates -------------------------
475
+ /**
476
+ * Expose a layer property in the comp's Essential Graphics panel so the comp can
477
+ * be exported as a Motion Graphics template. Verified live on AE 26.3:
478
+ * canAddToMotionGraphicsTemplate / addToMotionGraphicsTemplate take the comp the
479
+ * property lives in; the control is named after the property (AE 26.3 has no
480
+ * script to rename it). Source Text lives at ADBE Text Properties > Text Document.
481
+ */
482
+ export function addToEssentialGraphicsScript(compId, layerIndex, property, propertyPath) {
483
+ const path = propertyPath ? `[${propertyPath.map(lit).join(", ")}]` : "null";
484
+ return wrap(`
485
+ var c = __comp(${num(compId)}); var l = __layer(c, ${num(layerIndex)});
486
+ var prop = __prop(l, ${lit(property)}, ${path});
487
+ if (!prop.canAddToMotionGraphicsTemplate(c)) {
488
+ throw new Error("'" + prop.name + "' cannot be added to Essential Graphics — either its type is not supported or a control with that name is already there.");
489
+ }
490
+ return __undo("Add to Essential Graphics", function () {
491
+ var ok = prop.addToMotionGraphicsTemplate(c);
492
+ if (!ok) { throw new Error("After Effects refused to add '" + prop.name + "' to the Essential Graphics panel."); }
493
+ var count = c.motionGraphicsTemplateControllerCount;
494
+ var names = [];
495
+ for (var i = 1; i <= count; i++) { names.push(c.getMotionGraphicsTemplateControllerName(i)); }
496
+ return { added: prop.name, controllerCount: count, controllers: names };
497
+ });`);
498
+ }
499
+ /**
500
+ * Export the comp as a .mogrt. The host's exportAsMotionGraphicsTemplate takes a
501
+ * *folder* and names the file from motionGraphicsTemplateName, so we split the
502
+ * requested path, set the name, and write <folder>/<name>.mogrt. Verified live on
503
+ * AE 26.3 (the pre-24.3 "always returns false" bug is gone; we still check the
504
+ * file exists). The project must be saved — a dirty project makes AE prompt.
505
+ */
506
+ export function exportMogrtScript(compId, folder, name, overwrite) {
507
+ return wrap(`
508
+ var c = __comp(${num(compId)});
509
+ if (!app.project.file) { throw new Error("Save the After Effects project first (ae_save_project) — exporting a Motion Graphics template needs a saved project."); }
510
+ var fo = new Folder(${lit(folder)});
511
+ if (!fo.exists) { fo.create(); }
512
+ c.motionGraphicsTemplateName = ${lit(name)};
513
+ // Read everything off the comp now: exportAsMotionGraphicsTemplate invalidates
514
+ // the comp reference, so touching c afterwards throws "Object is invalid".
515
+ var compName = c.name;
516
+ var controllers = c.motionGraphicsTemplateControllerCount;
517
+ // A missing font (or any project warning) pops a modal, and any modal blocks ALL
518
+ // scripting until a human dismisses it. Suppress dialogs around the save+export
519
+ // so it stays headless.
520
+ var ok = false;
521
+ app.beginSuppressDialogs();
522
+ try {
523
+ app.project.save();
524
+ ok = c.exportAsMotionGraphicsTemplate(${overwrite ? "true" : "false"}, ${lit(folder)});
525
+ } finally {
526
+ app.endSuppressDialogs(false);
527
+ }
528
+ var outPath = ${lit(folder)} + "/" + ${lit(name)} + ".mogrt";
529
+ var f = new File(outPath);
530
+ if (!f.exists) { throw new Error("After Effects did not write the .mogrt (export returned " + ok + "). Does the file already exist without overwrite, or is the folder writable? " + outPath); }
531
+ return { compName: compName, name: ${lit(name)}, outputPath: outPath, controllerCount: controllers };`);
532
+ }
302
533
  export function setTextScript(p) {
303
534
  return wrap(`
304
535
  var c = __comp(${num(p.compId)}); var l = __layer(c, ${num(p.layerIndex)});
@@ -516,6 +747,17 @@ export function registerAfterEffectsTools(server, bridge, options = {}) {
516
747
  description: "Import an image, video, audio, or PSD file into the project. Returns the footage item id for ae_add_layer.",
517
748
  inputSchema: { path: z.string().min(1).describe("Absolute path to the media file.") },
518
749
  }, async ({ path }) => run(importFootageScript(path)));
750
+ server.registerTool("ae_import_as_comp", {
751
+ title: "After Effects: import a PSD or AI file as a composition",
752
+ description: "Import a layered Photoshop (.psd) or Illustrator (.ai) file as a composition — every source layer becomes " +
753
+ "its own After Effects layer, keeping blend modes, positions, and (for PSD) layer styles as After Effects " +
754
+ "imports them. Illustrator files must be RGB (not CMYK) and saved with PDF compatibility. Returns the new " +
755
+ "composition with its layers.",
756
+ inputSchema: {
757
+ path: z.string().min(1).describe("Absolute path to a .psd or .ai file."),
758
+ cropped: z.boolean().optional().describe("Crop each layer to its content (Composition - Cropped Layers). Defaults to false = keep document-size layers."),
759
+ },
760
+ }, async ({ path, cropped }) => run(importAsCompScript(path, cropped ?? false)));
519
761
  // ---- comps & layers -----------------------------------------------------
520
762
  server.registerTool("ae_create_comp", {
521
763
  title: "After Effects: create a composition",
@@ -604,9 +846,52 @@ export function registerAfterEffectsTools(server, bridge, options = {}) {
604
846
  }, async ({ compId: id, layerIndex: li, property, propertyPath: pp }) => run(removeKeyframesScript(id, li, property, pp)));
605
847
  server.registerTool("ae_set_expression", {
606
848
  title: "After Effects: set an expression",
607
- description: "Set (or clear with null) the expression on a layer property. Fails with the error text if the expression does not compile.",
849
+ description: "Set (or clear with null) the expression on a layer property. Use `property` for a transform channel, or " +
850
+ "`propertyPath` (match/display names) to reach an effect parameter, mask, or text source. If the expression " +
851
+ "does not compile it fails with the error text and the property keeps its previous expression.",
608
852
  inputSchema: { compId, layerIndex, property: propertyName, propertyPath, expression: z.string().nullable().describe("Expression source, or null to remove.") },
609
853
  }, async ({ compId: id, layerIndex: li, property, propertyPath: pp, expression }) => run(setExpressionScript(id, li, property, pp, expression)));
854
+ // ---- essential graphics / motion graphics templates ---------------------
855
+ server.registerTool("ae_add_to_essential_graphics", {
856
+ title: "After Effects: add a control to Essential Graphics",
857
+ description: "Expose a layer property in the comp's Essential Graphics panel so the comp can be exported as a Motion " +
858
+ "Graphics template (.mogrt). Give `property` for a transform channel or `sourceText` for a text layer's " +
859
+ "content, or `propertyPath` (match/display names) for anything else, e.g. an effect's Slider or Color. " +
860
+ "The control takes the property's name (rename it in the panel; After Effects has no script for that).",
861
+ inputSchema: {
862
+ compId,
863
+ layerIndex,
864
+ property: z
865
+ .enum(["position", "scale", "rotation", "opacity", "anchorPoint", "sourceText"])
866
+ .optional()
867
+ .describe("Transform channel, or sourceText for a text layer's content. For anything else, use propertyPath."),
868
+ propertyPath,
869
+ },
870
+ }, async ({ compId: id, layerIndex: li, property, propertyPath: pp }) => {
871
+ const path = property === "sourceText" && !pp ? ["ADBE Text Properties", "ADBE Text Document"] : pp;
872
+ const prop = property && property !== "sourceText" ? property : "position";
873
+ return run(addToEssentialGraphicsScript(id, li, prop, path));
874
+ });
875
+ server.registerTool("ae_export_mogrt", {
876
+ title: "After Effects: export a Motion Graphics template",
877
+ description: "Export a composition as a Motion Graphics template (.mogrt) — the controls added with " +
878
+ "ae_add_to_essential_graphics become its editable fields. Saves the project first (it must have been saved " +
879
+ "once). Returns the written path.",
880
+ inputSchema: {
881
+ compId,
882
+ outputPath: z.string().min(1).describe("Absolute path ending in .mogrt. The file name (minus .mogrt) becomes the template name."),
883
+ overwrite: z.boolean().optional().describe("Overwrite an existing file at that path. Defaults to true."),
884
+ },
885
+ annotations: { destructiveHint: true },
886
+ }, async ({ compId: id, outputPath, overwrite }) => {
887
+ const norm = outputPath.replace(/\\/g, "/");
888
+ const slash = norm.lastIndexOf("/");
889
+ const folder = slash >= 0 ? norm.slice(0, slash) : ".";
890
+ const name = (slash >= 0 ? norm.slice(slash + 1) : norm).replace(/\.mogrt$/i, "");
891
+ if (!name)
892
+ return run(exportMogrtScript(id, folder, "Untitled", overwrite ?? true)); // unreachable-ish; keeps a name
893
+ return run(exportMogrtScript(id, folder, name, overwrite ?? true));
894
+ });
610
895
  // ---- effects, text, markers ---------------------------------------------
611
896
  server.registerTool("ae_apply_effect", {
612
897
  title: "After Effects: apply an effect",
@@ -625,6 +910,41 @@ export function registerAfterEffectsTools(server, bridge, options = {}) {
625
910
  value: z.union([z.number(), z.array(z.number()), z.string()]),
626
911
  },
627
912
  }, async ({ compId: id, layerIndex: li, effect, param, value }) => run(setEffectParamScript(id, li, effect, param, value)));
913
+ // ---- layer styles -------------------------------------------------------
914
+ const styleName = z
915
+ .enum(["dropShadow", "innerShadow", "outerGlow", "innerGlow", "bevelEmboss", "satin", "colorOverlay", "gradientOverlay", "stroke"])
916
+ .describe("Layer style name.");
917
+ server.registerTool("ae_add_layer_style", {
918
+ title: "After Effects: add a layer style",
919
+ description: "Add a Photoshop-style layer style (drop shadow, inner shadow, glows, bevel & emboss, satin, color/gradient " +
920
+ "overlay, stroke) to a layer. Returns the style's parameters (names and current values) for ae_set_layer_style_param.",
921
+ inputSchema: { compId, layerIndex, style: styleName },
922
+ }, async ({ compId: id, layerIndex: li, style }) => run(addLayerStyleScript(id, li, style)));
923
+ server.registerTool("ae_get_layer_styles", {
924
+ title: "After Effects: read a layer's styles",
925
+ description: "List the layer styles on a layer with each style's parameters and current values.",
926
+ inputSchema: { compId, layerIndex },
927
+ annotations: { readOnlyHint: true },
928
+ }, async ({ compId: id, layerIndex: li }) => run(getLayerStylesScript(id, li), { timeoutClass: "fast" }));
929
+ server.registerTool("ae_set_layer_style_param", {
930
+ title: "After Effects: set a layer style parameter",
931
+ description: "Set a parameter on a layer style already added with ae_add_layer_style — by display name (e.g. 'Distance', " +
932
+ "'Opacity', 'Color') or match name (e.g. 'dropShadow/blur'). Colors take a hex string or [r,g,b] 0–1 array.",
933
+ inputSchema: {
934
+ compId,
935
+ layerIndex,
936
+ style: styleName,
937
+ param: z.string().min(1).describe("Parameter display name or match name."),
938
+ value: z.union([z.number(), z.array(z.number()), z.string()]).describe("Number, [r,g,b] 0–1 array, or hex color like #ff8800."),
939
+ },
940
+ }, async ({ compId: id, layerIndex: li, style, param, value }) => run(setLayerStyleParamScript(id, li, style, param, value)));
941
+ server.registerTool("ae_remove_layer_style", {
942
+ title: "After Effects: turn off a layer style",
943
+ description: "Turn a layer style off (its visible effect goes away). After Effects keeps a permanent slot per style, " +
944
+ "so this disables the style rather than deleting the slot; ae_add_layer_style turns it back on.",
945
+ inputSchema: { compId, layerIndex, style: styleName },
946
+ annotations: { destructiveHint: true },
947
+ }, async ({ compId: id, layerIndex: li, style }) => run(removeLayerStyleScript(id, li, style)));
628
948
  server.registerTool("ae_set_text", {
629
949
  title: "After Effects: set text layer content and style",
630
950
  description: "Change a text layer's text, font size, font (PostScript name), color, or justification.",