@pinepaper.studio/mcp-server 1.6.10 → 1.6.11
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/README.md +37 -1
- package/dist/design/design-systems.d.ts +12 -3
- package/dist/design/design-systems.d.ts.map +1 -1
- package/dist/design/design-systems.js +12 -11
- package/dist/design/design-systems.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/tools/definitions.d.ts +1 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +51 -9
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/engine-methods.d.ts.map +1 -1
- package/dist/tools/engine-methods.js +4 -0
- package/dist/tools/engine-methods.js.map +1 -1
- package/dist/tools/engine-surface.d.ts +1 -1
- package/dist/tools/engine-surface.d.ts.map +1 -1
- package/dist/tools/engine-surface.js +8 -4
- package/dist/tools/engine-surface.js.map +1 -1
- package/dist/tools/handlers.js +9 -0
- package/dist/tools/handlers.js.map +1 -1
- package/dist/types/code-generator.d.ts +15 -0
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +139 -28
- package/dist/types/code-generator.js.map +1 -1
- package/dist/vendor/design/design-style.d.ts +3 -1
- package/dist/vendor/design/design-style.d.ts.map +1 -1
- package/dist/vendor/design/design-style.js +37 -2
- package/dist/vendor/design/design-style.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -181,12 +181,27 @@ app.historyManager.saveState();
|
|
|
181
181
|
*/
|
|
182
182
|
function generateModifyItemCode(itemId, properties) {
|
|
183
183
|
const { contentType, contentFormat, countdownTarget, countdownEndText, ...restProperties } = properties;
|
|
184
|
+
// modifyItem(id, changes), NOT select() + modify().
|
|
185
|
+
//
|
|
186
|
+
// modify() operates on the SELECTION, so this selected an id and then edited
|
|
187
|
+
// whatever was selected — on an id that does not resolve, select changes
|
|
188
|
+
// nothing and modify edits the PREVIOUS selection instead. modifyItem takes
|
|
189
|
+
// the reference and returns false when it cannot resolve it, which is the
|
|
190
|
+
// answer this emitter needs and never asked for.
|
|
191
|
+
//
|
|
192
|
+
// The warning the engine logs on that path is console.warn, which production
|
|
193
|
+
// strips, so the failure was invisible twice over.
|
|
184
194
|
let code = `
|
|
185
195
|
// Modify item ${itemId}
|
|
186
|
-
|
|
187
|
-
// Pass non-dynamic-content properties to app.modify
|
|
196
|
+
let _modified = true;`;
|
|
188
197
|
if (Object.keys(restProperties).length > 0) {
|
|
189
|
-
code +=
|
|
198
|
+
code += `
|
|
199
|
+
_modified = app.modifyItem('${itemId}', ${JSON.stringify(restProperties, null, 2)}) !== false;
|
|
200
|
+
if (!_modified) { return { success: false, error: 'no item ${itemId} — nothing was modified' }; }`;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
code += `
|
|
204
|
+
if (!app.getItemById('${itemId}')) { return { success: false, error: 'no item ${itemId}' }; }`;
|
|
190
205
|
}
|
|
191
206
|
// Handle dynamic content type changes
|
|
192
207
|
if (contentType !== undefined) {
|
|
@@ -212,10 +227,10 @@ if (entry && entry.item && app.setDynamicContent) app.setDynamicContent(entry.it
|
|
|
212
227
|
}
|
|
213
228
|
code += `
|
|
214
229
|
app.historyManager.saveState();
|
|
215
|
-
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
return code.trim()
|
|
230
|
+
return { success: true, itemId: '${itemId}' };`;
|
|
231
|
+
// Wrapped, because the body now RETURNS early on a miss rather than falling
|
|
232
|
+
// through to an unconditional success.
|
|
233
|
+
return `(function() {\n${code.trim()}\n})();`;
|
|
219
234
|
}
|
|
220
235
|
/**
|
|
221
236
|
* Template for deleting items
|
|
@@ -675,9 +690,24 @@ function generateSetCanvasSizeCode(width, height, preset) {
|
|
|
675
690
|
: `{ width: ${width}, height: ${height} }`;
|
|
676
691
|
return `
|
|
677
692
|
// Set canvas size
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
693
|
+
(function() {
|
|
694
|
+
// READ THE VERDICT. This called setCanvasSize and then declared success with
|
|
695
|
+
// the dimensions it had ASKED for — so an unknown preset, or a width of -5,
|
|
696
|
+
// came back as a successful resize while the canvas was left exactly as it
|
|
697
|
+
// was. The engine now refuses by name; older builds return undefined, which
|
|
698
|
+
// is not a refusal and must not be read as one.
|
|
699
|
+
const r = app.setCanvasSize(${sizeArg});
|
|
700
|
+
if (r && r.ok === false) {
|
|
701
|
+
return { success: false, error: r.reason || 'the canvas size was refused',
|
|
702
|
+
...(r.known ? { knownPresets: r.known } : {}) };
|
|
703
|
+
}
|
|
704
|
+
app.historyManager.saveState();
|
|
705
|
+
// Report what the canvas ACTUALLY took where the engine says so, rather than
|
|
706
|
+
// echoing the request back.
|
|
707
|
+
return { success: true, width: (r && r.width) || ${width}, height: (r && r.height) || ${height},
|
|
708
|
+
...(r && r.preset ? { preset: r.preset } : {}),
|
|
709
|
+
...(r && r.unbounded !== undefined ? { unbounded: r.unbounded } : {}) };
|
|
710
|
+
})();
|
|
681
711
|
`.trim();
|
|
682
712
|
}
|
|
683
713
|
/**
|
|
@@ -1220,19 +1250,33 @@ export class PinePaperCodeGenerator {
|
|
|
1220
1250
|
if (url) {
|
|
1221
1251
|
return `
|
|
1222
1252
|
// Import SVG from URL${source === 'figma' ? ' (Figma-normalised)' : ''}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1253
|
+
// WRAPPED, because these were bare top-level statements with an await. That
|
|
1254
|
+
// parses under the governor, which builds an AsyncFunction, and is a SYNTAX
|
|
1255
|
+
// ERROR on the eval fallback — so importing an SVG by URL never ran at all on
|
|
1256
|
+
// a studio without runGenerated. A paren-led async IIFE parses on both and
|
|
1257
|
+
// still gives the governor a trailing expression to capture.
|
|
1258
|
+
(async function() {
|
|
1259
|
+
let response;
|
|
1260
|
+
try {
|
|
1261
|
+
response = await fetch('${url}');
|
|
1262
|
+
} catch (netErr) {
|
|
1263
|
+
return { success: false, error: 'could not reach ${url} — ' + ((netErr && netErr.message) || 'network request failed') };
|
|
1264
|
+
}
|
|
1265
|
+
if (!response.ok) {
|
|
1266
|
+
return { success: false, error: 'the server refused ${url} — HTTP ' + response.status + ' ' + (response.statusText || ''), status: response.status };
|
|
1267
|
+
}
|
|
1268
|
+
const svgText = await response.text();
|
|
1269
|
+
const _r = ${importExpr('svgText')};
|
|
1270
|
+
const imported = _r.item;
|
|
1271
|
+
if (!imported) {
|
|
1272
|
+
return { success: false, error: 'the response from ${url} did not parse as SVG. Check the URL serves the file itself rather than a page around it.' };
|
|
1273
|
+
}
|
|
1228
1274
|
imported.position = new paper.Point(${position.x}, ${position.y});
|
|
1229
1275
|
imported.scale(${scale});
|
|
1230
1276
|
const itemId = app.registerItem(imported, 'svg-import', { source: 'mcp' });
|
|
1231
1277
|
app.historyManager.saveState();
|
|
1232
|
-
|
|
1233
|
-
}
|
|
1234
|
-
throw new Error('Failed to import SVG from URL');
|
|
1235
|
-
}
|
|
1278
|
+
return { success: true, itemId, changes: _r.changes, position: { x: ${position.x}, y: ${position.y} } };
|
|
1279
|
+
})();
|
|
1236
1280
|
`.trim();
|
|
1237
1281
|
}
|
|
1238
1282
|
if (svgString) {
|
|
@@ -2465,8 +2509,12 @@ return { itemId };
|
|
|
2465
2509
|
const modifyProps = JSON.stringify(op.properties || {});
|
|
2466
2510
|
return `
|
|
2467
2511
|
const targetId = ${itemRef};
|
|
2468
|
-
|
|
2469
|
-
|
|
2512
|
+
// modifyItem(id, changes), not select() + modify(). modify() edits the
|
|
2513
|
+
// SELECTION, so an id that does not resolve left the PREVIOUS selection to be
|
|
2514
|
+
// edited instead — and this then reported modified:true either way.
|
|
2515
|
+
if (app.modifyItem(targetId, ${modifyProps}) === false) {
|
|
2516
|
+
return { success: false, itemId: targetId, error: 'no item ' + targetId + ' — nothing was modified' };
|
|
2517
|
+
}
|
|
2470
2518
|
return { itemId: targetId, modified: true };
|
|
2471
2519
|
`;
|
|
2472
2520
|
case 'animate':
|
|
@@ -2541,9 +2589,16 @@ return { success: true, generator: '${genName}'${op.generatorRegion ? ', region:
|
|
|
2541
2589
|
const h = op.height || 1080;
|
|
2542
2590
|
const sizeArg = op.preset ? `'${op.preset}'` : `{ width: ${w}, height: ${h} }`;
|
|
2543
2591
|
return `
|
|
2544
|
-
|
|
2592
|
+
// Read the verdict, as the standalone tool now does: an unknown preset or an
|
|
2593
|
+
// out-of-range dimension is refused by name, and reporting the size we ASKED
|
|
2594
|
+
// for left the batch claiming a resize that never happened.
|
|
2595
|
+
const _cs = app.setCanvasSize(${sizeArg});
|
|
2596
|
+
if (_cs && _cs.ok === false) {
|
|
2597
|
+
return { success: false, error: _cs.reason || 'the canvas size was refused',
|
|
2598
|
+
...(_cs.known ? { knownPresets: _cs.known } : {}) };
|
|
2599
|
+
}
|
|
2545
2600
|
await new Promise(r => setTimeout(r, 50));
|
|
2546
|
-
return { success: true, width: ${w}, height: ${h} };
|
|
2601
|
+
return { success: true, width: (_cs && _cs.width) || ${w}, height: (_cs && _cs.height) || ${h} };
|
|
2547
2602
|
`;
|
|
2548
2603
|
}
|
|
2549
2604
|
case 'keyframe_animate': {
|
|
@@ -4071,7 +4126,7 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
4071
4126
|
// records the baked offset at item.data._bakedRotation and sums the two itself
|
|
4072
4127
|
// (PinePaper.js does exactly this); read the same sum rather than the half of
|
|
4073
4128
|
// it that happens to be zero.
|
|
4074
|
-
const _rot = (item.data?._bakedRotation || 0) + (item.rotation || 0);
|
|
4129
|
+
const _rot = typeof app.authoredRotation === 'function' ? app.authoredRotation(item) : ((item.data?._bakedRotation || 0) + (item.rotation || 0));
|
|
4075
4130
|
if (_rot) itemData.properties.rotation = _rot;
|
|
4076
4131
|
if (item.data?.content) itemData.properties.content = item.data.content;
|
|
4077
4132
|
if (item.visible === false) itemData.properties.visible = false;
|
|
@@ -4433,6 +4488,33 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
4433
4488
|
}
|
|
4434
4489
|
})();
|
|
4435
4490
|
`.trim();
|
|
4491
|
+
}
|
|
4492
|
+
/**
|
|
4493
|
+
* What THIS item can animate, from the engine's own table.
|
|
4494
|
+
*
|
|
4495
|
+
* The table is derived from the applier rather than written beside it, so it
|
|
4496
|
+
* cannot drift from what actually gets applied — and it is filtered by item
|
|
4497
|
+
* type, because the trim/dash four only exist on a path and a rectangle has
|
|
4498
|
+
* no fontSize.
|
|
4499
|
+
*
|
|
4500
|
+
* `interpolates: false` is passed through VERBATIM and never flattened away.
|
|
4501
|
+
* `content` and `blendMode` are DISCRETE — they hold their value until the
|
|
4502
|
+
* next keyframe rather than blending toward it — and treating content as
|
|
4503
|
+
* tweenable is what made every word in a cycler appear half a beat early.
|
|
4504
|
+
* A list that says only "animatable" rebuilds that bug in the next agent.
|
|
4505
|
+
*/
|
|
4506
|
+
generateListItemAnimatableProperties(itemId) {
|
|
4507
|
+
return `
|
|
4508
|
+
// Animatable properties of ${JSON.stringify(itemId)}
|
|
4509
|
+
(function() {
|
|
4510
|
+
if (typeof app.listAnimatableProperties !== 'function') {
|
|
4511
|
+
return { success: false, error: 'app.listAnimatableProperties unavailable — update the studio. The keyframe tool lists the properties it accepts in its own description meanwhile.' };
|
|
4512
|
+
}
|
|
4513
|
+
const item = app.getItemById(${JSON.stringify(itemId)});
|
|
4514
|
+
if (!item) { return { success: false, error: 'no item ${itemId}' }; }
|
|
4515
|
+
const r = app.listAnimatableProperties(item);
|
|
4516
|
+
return { success: true, ...r };
|
|
4517
|
+
})();`.trim();
|
|
4436
4518
|
}
|
|
4437
4519
|
generateGetAnimatableProperties() {
|
|
4438
4520
|
return `
|
|
@@ -4829,7 +4911,21 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} // The RE
|
|
|
4829
4911
|
// Toggle loop mode
|
|
4830
4912
|
(function() {
|
|
4831
4913
|
${guard}
|
|
4832
|
-
|
|
4914
|
+
// chainLoop is a PROPERTY, set by createChain({loop}) and read by the chain
|
|
4915
|
+
// advance. setLoop has never existed, so this reported a toggle it had not
|
|
4916
|
+
// performed — and a chain built without loop went on not looping.
|
|
4917
|
+
if (typeof app.sceneManager.setLoop === 'function') {
|
|
4918
|
+
app.sceneManager.setLoop(${input.enabled ?? true});
|
|
4919
|
+
} else if ('chainLoop' in app.sceneManager) {
|
|
4920
|
+
app.sceneManager.chainLoop = ${input.enabled ?? true};
|
|
4921
|
+
} else {
|
|
4922
|
+
return { success: false, action: 'toggle_loop',
|
|
4923
|
+
error: 'this build has no chain-loop control to set. Pass loop:true to create_chain instead, which is where the setting is read from.' };
|
|
4924
|
+
}
|
|
4925
|
+
if (!app.sceneManager.sceneChain || !app.sceneManager.sceneChain.length) {
|
|
4926
|
+
return { success: true, action: 'toggle_loop', enabled: ${input.enabled ?? true},
|
|
4927
|
+
note: 'no chain exists yet — this setting applies to the next one created, and create_chain takes loop directly.' };
|
|
4928
|
+
}
|
|
4833
4929
|
return { success: true, action: 'toggle_loop', enabled: ${input.enabled ?? true} };
|
|
4834
4930
|
})();
|
|
4835
4931
|
`.trim();
|
|
@@ -5707,7 +5803,7 @@ ${resolve}
|
|
|
5707
5803
|
bounds: item.bounds ? { x: item.bounds.x, y: item.bounds.y, width: item.bounds.width, height: item.bounds.height } : null,
|
|
5708
5804
|
visible: item.visible,
|
|
5709
5805
|
opacity: item.opacity,
|
|
5710
|
-
rotation: (item.data?._bakedRotation || 0) + (item.rotation || 0),
|
|
5806
|
+
rotation: typeof app.authoredRotation === 'function' ? app.authoredRotation(item) : ((item.data?._bakedRotation || 0) + (item.rotation || 0)),
|
|
5711
5807
|
selected: item.selected,
|
|
5712
5808
|
};
|
|
5713
5809
|
})();`.trim();
|
|
@@ -6481,7 +6577,7 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
6481
6577
|
const entry = app.itemRegistry.get(${itemIdStr});
|
|
6482
6578
|
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
6483
6579
|
const b = entry.item.bounds;
|
|
6484
|
-
return { success: true, action: 'get_dimensions', itemId: ${itemIdStr}, x: b.x, y: b.y, width: b.width, height: b.height, rotation: (entry.item.data?._bakedRotation || 0) + (entry.item.rotation || 0) };
|
|
6580
|
+
return { success: true, action: 'get_dimensions', itemId: ${itemIdStr}, x: b.x, y: b.y, width: b.width, height: b.height, rotation: typeof app.authoredRotation === 'function' ? app.authoredRotation(entry.item) : ((entry.item.data?._bakedRotation || 0) + (entry.item.rotation || 0)) };
|
|
6485
6581
|
})();`.trim();
|
|
6486
6582
|
}
|
|
6487
6583
|
case 'set_snap': {
|
|
@@ -6990,10 +7086,25 @@ ${guard}
|
|
|
6990
7086
|
});
|
|
6991
7087
|
return `
|
|
6992
7088
|
// Display text style: ${input.styleKey} — stacked-layer title, id adopted from the text
|
|
6993
|
-
(function() {
|
|
7089
|
+
(async function() {
|
|
6994
7090
|
if (typeof app.applyTextStyle !== 'function') { return { success: false, error: 'app.applyTextStyle unavailable — update FxTool to a text-styles build' }; }
|
|
7091
|
+
// LOAD THE FACES FIRST. The decorative stylesheet is fetched on pointerenter
|
|
7092
|
+
// over the style grid — a gesture no agent ever makes — so every decorative
|
|
7093
|
+
// family was missing in production and a style asking for one silently drew
|
|
7094
|
+
// the fallback and reported success. ensureFontsLoaded is the fix; guarded,
|
|
7095
|
+
// because a studio without it behaves as it always did.
|
|
7096
|
+
if (typeof app.ensureFontsLoaded === 'function') {
|
|
7097
|
+
try { await app.ensureFontsLoaded('decorative'); } catch (_) { /* the pending flag below still reports it */ }
|
|
7098
|
+
}
|
|
6995
7099
|
const r = app.applyTextStyle(${S(input.itemId)}, ${S(input.styleKey)}, ${opts});
|
|
6996
7100
|
if (!r || !r.ok) { return { success: false, error: (r && r.error) || 'style failed' }; }
|
|
7101
|
+
// fontPending: the face is not there YET, so this drew in the fallback. Said
|
|
7102
|
+
// out loud rather than left to be discovered in the export.
|
|
7103
|
+
if (r.fontPending) {
|
|
7104
|
+
return { success: true, action: 'apply_style', id: r.id, style: r.style, layers: r.layers,
|
|
7105
|
+
palette: r.palette, bounds: r.bounds, fontPending: true,
|
|
7106
|
+
warning: 'the face for this style had not loaded, so it drew in a FALLBACK font. Re-apply the style, or call pinepaper_font check with the family and your text to see what is really being used.' };
|
|
7107
|
+
}
|
|
6997
7108
|
// The styled group ADOPTS the text item's registry id — r.id is the SAME id
|
|
6998
7109
|
// the caller passed, so their handle keeps working. Say so in the result.
|
|
6999
7110
|
return { success: true, action: 'apply_style', id: r.id, style: r.style, layers: r.layers, palette: r.palette, bounds: r.bounds };
|