framer-dalton 0.0.21 → 0.0.22
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/cli.js +358 -48
- package/dist/start-relay-server.js +167 -58
- package/docs/skills/framer-canvas-editing-project.md +6 -14
- package/docs/skills/framer.md +28 -289
- package/package.json +2 -2
package/docs/skills/framer.md
CHANGED
|
@@ -15,7 +15,7 @@ npx framer-dalton@latest setup
|
|
|
15
15
|
|
|
16
16
|
What you can do with the Framer CLI:
|
|
17
17
|
|
|
18
|
-
- **Canvas Editing
|
|
18
|
+
- **Canvas Editing**: For design tasks — creating or editing pages, sections, layouts, recreating designs from screenshots, etc.
|
|
19
19
|
- **CMS**: Create, read, update, delete collections and items. Sync external databases.
|
|
20
20
|
- **Styles**: Manage color and text styles. Sync design systems.
|
|
21
21
|
- **Code Components**: Create, edit, type-check, and add custom React components to the canvas.
|
|
@@ -24,7 +24,6 @@ What you can do with the Framer CLI:
|
|
|
24
24
|
- **Data**: Store metadata on nodes and projects for plugin state.
|
|
25
25
|
- **Screenshots**: Capture node screenshots as PNG/JPEG. Export nodes as SVG.
|
|
26
26
|
- **Publishing**: Publish projects, manage deployments, track changes.
|
|
27
|
-
- **Low-level Node APIs**: Create and modify individual nodes. Only use these for targeted, surgical edits to specific nodes - not for building pages or layouts.
|
|
28
27
|
|
|
29
28
|
## CLI Usage
|
|
30
29
|
|
|
@@ -159,9 +158,9 @@ List active sessions:
|
|
|
159
158
|
npx framer-dalton session list
|
|
160
159
|
```
|
|
161
160
|
|
|
162
|
-
##
|
|
161
|
+
## Project-scoped skill
|
|
163
162
|
|
|
164
|
-
|
|
163
|
+
**Always load the project-scoped skill `framer-canvas-editing-project-<projectId>` immediately after `session new`, regardless of the task.** It is the source of truth for the connected project and is required for canvas editing, project-tree reads, change review, publishing, page management, sourcing stock images, and component operations. CMS plugin API, code components, localization, plugin data, and CLI mechanics remain in this framer-dalton skill — use both together.
|
|
165
164
|
|
|
166
165
|
## Canvas Editing: Alternative Approach (“Prompt the Framer agent”)
|
|
167
166
|
|
|
@@ -199,7 +198,7 @@ Prompting may take a while to complete, so set the command timeout to 10 minutes
|
|
|
199
198
|
|
|
200
199
|
## Execute Code
|
|
201
200
|
|
|
202
|
-
|
|
201
|
+
Use your write tool to write your code to a unique file under `{{FRAMER_TEMPORARY_DIR}}/` and execute with `-f`:
|
|
203
202
|
|
|
204
203
|
```bash
|
|
205
204
|
npx framer-dalton exec -s <sessionId> -f {{FRAMER_TEMPORARY_DIR}}/<sessionId>-<short-summary>.js
|
|
@@ -223,10 +222,9 @@ npx framer-dalton docs ScreenshotOptions # Show type + recursively expa
|
|
|
223
222
|
|
|
224
223
|
### Working with Collections (CMS)
|
|
225
224
|
|
|
226
|
-
Collections are Framer's CMS. Each collection has fields (columns) and items (rows).
|
|
225
|
+
Collections are Framer's CMS. Each collection has fields (columns) and items (rows). Use the plugin Collection API (the methods below) for collection schema and item CRUD.
|
|
227
226
|
|
|
228
|
-
-
|
|
229
|
-
- **Managed** (`framer.createManagedCollection()`): Can ONLY be edited via the API. They appear read-only in the Framer UI. Only use managed collections when explicitly asked or when creating a script for data synchronisation.
|
|
227
|
+
**Exception — CMS Collection Lists.** The in-canvas construct that *renders* a collection as a repeating list of cards (the "Collection List" / repeater) is a canvas concern. Build and edit those through canvas editing, not the plugin API. Use the plugin Collection API to read available collections and field schema first, then drive the list through the canvas-editing skill.
|
|
230
228
|
|
|
231
229
|
#### Reading Collections
|
|
232
230
|
|
|
@@ -289,228 +287,49 @@ await item.navigateTo();
|
|
|
289
287
|
await item.remove();
|
|
290
288
|
```
|
|
291
289
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
```js
|
|
295
|
-
// Create a managed collection
|
|
296
|
-
const managed = await framer.createManagedCollection({ name: "My Sync" });
|
|
297
|
-
|
|
298
|
-
// Set fields
|
|
299
|
-
await managed.setFields([
|
|
300
|
-
{ id: "title", name: "Title", type: "string" },
|
|
301
|
-
{ id: "content", name: "Content", type: "formattedText" },
|
|
302
|
-
{ id: "published", name: "Published", type: "date" },
|
|
303
|
-
]);
|
|
304
|
-
|
|
305
|
-
// Add items
|
|
306
|
-
await managed.addItems([
|
|
307
|
-
{
|
|
308
|
-
id: "1",
|
|
309
|
-
slug: "first-post",
|
|
310
|
-
fieldData: { title: "Hello", content: "<p>World</p>" },
|
|
311
|
-
},
|
|
312
|
-
]);
|
|
313
|
-
|
|
314
|
-
// Store sync metadata
|
|
315
|
-
await managed.setPluginData("lastSync", new Date().toISOString());
|
|
316
|
-
const lastSync = await managed.getPluginData("lastSync");
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
### Working with Nodes
|
|
320
|
-
|
|
321
|
-
Nodes are layers on the canvas: frames, text, images, components, etc.
|
|
322
|
-
|
|
323
|
-
#### Getting Nodes
|
|
324
|
-
|
|
325
|
-
```js
|
|
326
|
-
// Get current selection
|
|
327
|
-
const selection = await framer.getSelection();
|
|
328
|
-
|
|
329
|
-
// Get canvas root
|
|
330
|
-
const root = await framer.getCanvasRoot();
|
|
331
|
-
|
|
332
|
-
// Get node by ID
|
|
333
|
-
const node = await framer.getNode("node-id");
|
|
334
|
-
|
|
335
|
-
// Get children of a node
|
|
336
|
-
const children = await framer.getChildren(node.id);
|
|
290
|
+
### Working with Images
|
|
337
291
|
|
|
338
|
-
|
|
339
|
-
const parent = await framer.getParent(node.id);
|
|
292
|
+
Canvas editing accepts image URLs directly when setting an image fill, so the typical task is to obtain a URL and pass it through to the canvas-editing skill.
|
|
340
293
|
|
|
341
|
-
|
|
342
|
-
const frameNodes = await framer.getNodesWithType("FrameNode");
|
|
343
|
-
const textNodes = await framer.getNodesWithType("TextNode");
|
|
294
|
+
There are three sources you'll typically pull URLs from:
|
|
344
295
|
|
|
345
|
-
|
|
346
|
-
const nodesWithBg = await framer.getNodesWithAttributeSet("backgroundColor");
|
|
347
|
-
const nodesWithImage = await framer.getNodesWithAttributeSet("backgroundImage");
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
#### Creating Nodes
|
|
296
|
+
**Stock photography.** Use `framer.queryImagesForAgent` to source candidates and stash the URL you want:
|
|
351
297
|
|
|
352
298
|
```js
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
backgroundColor: "rgba(255, 0, 0, 1)",
|
|
299
|
+
const { results } = await framer.queryImagesForAgent({
|
|
300
|
+
source: "unsplash",
|
|
301
|
+
query: "snow-capped mountains",
|
|
302
|
+
count: 4,
|
|
303
|
+
orientation: "landscape",
|
|
359
304
|
});
|
|
360
|
-
|
|
361
|
-
// Create text (creates TextNode, then use setText)
|
|
362
|
-
const textNode = await framer.createTextNode({ name: "My Text" });
|
|
363
|
-
await textNode.setText("Hello World");
|
|
364
|
-
|
|
365
|
-
// Add to specific parent
|
|
366
|
-
const child = await framer.createFrameNode({ name: "Child" }, parentNode.id);
|
|
305
|
+
state.heroUrl = results[0].url;
|
|
367
306
|
```
|
|
368
307
|
|
|
369
|
-
|
|
308
|
+
**An external URL the user provided.** Pass it through directly. If you'll reuse the same image across many edits, upload it once and stash the resulting asset URL to avoid re-resolving the source on every change:
|
|
370
309
|
|
|
371
310
|
```js
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
height: 200,
|
|
377
|
-
backgroundColor: "rgba(0, 0, 255, 0.5)",
|
|
378
|
-
visible: true,
|
|
379
|
-
locked: false,
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
// Move to different parent
|
|
383
|
-
await framer.setParent(node.id, newParentId);
|
|
384
|
-
|
|
385
|
-
// Clone a node
|
|
386
|
-
const clone = await node.clone();
|
|
387
|
-
|
|
388
|
-
// Remove nodes
|
|
389
|
-
await framer.removeNodes([node.id]);
|
|
311
|
+
state.heroUrl = (await framer.uploadImage({
|
|
312
|
+
image: "https://example.com/hero.png",
|
|
313
|
+
altText: "Mountain range at sunset",
|
|
314
|
+
})).url;
|
|
390
315
|
```
|
|
391
316
|
|
|
392
|
-
|
|
317
|
+
**An image already on the canvas.** Read the node and reuse its existing image URL:
|
|
393
318
|
|
|
394
319
|
```js
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const nodeText = await textNode.getText();
|
|
398
|
-
|
|
399
|
-
// Set text
|
|
400
|
-
await framer.setText("New text"); // on selection
|
|
401
|
-
await textNode.setText("Hello World");
|
|
320
|
+
const node = await framer.getNodeForAgent({ id: "<image-node-id>" });
|
|
321
|
+
state.heroUrl = node.attributes.fill;
|
|
402
322
|
```
|
|
403
323
|
|
|
404
|
-
|
|
324
|
+
For inline SVGs, use the plugin method directly:
|
|
405
325
|
|
|
406
326
|
```js
|
|
407
|
-
// Add image to canvas
|
|
408
|
-
await framer.addImage({
|
|
409
|
-
image: "https://example.com/image.png",
|
|
410
|
-
name: "My Image",
|
|
411
|
-
altText: "Description",
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
// Set image on selected node
|
|
415
|
-
await framer.setImage({
|
|
416
|
-
image: "https://example.com/image.png",
|
|
417
|
-
altText: "Description",
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
// Upload image without adding to canvas (for later use)
|
|
421
|
-
const imageAsset = await framer.uploadImage({
|
|
422
|
-
image: "https://example.com/image.png",
|
|
423
|
-
name: "My Image",
|
|
424
|
-
altText: "Alt text",
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
// Use uploaded image when creating a frame
|
|
428
|
-
await framer.createFrameNode({ backgroundImage: imageAsset });
|
|
429
|
-
|
|
430
|
-
// Get image from selection
|
|
431
|
-
const image = await framer.getImage();
|
|
432
|
-
console.log(image?.url);
|
|
433
|
-
|
|
434
|
-
// Add SVG (must be < 10kb)
|
|
435
327
|
await framer.addSVG({
|
|
436
328
|
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"><circle cx="10" cy="10" r="8"/></svg>',
|
|
437
329
|
name: "circle.svg",
|
|
438
330
|
});
|
|
439
331
|
```
|
|
440
332
|
|
|
441
|
-
### Working with Styles
|
|
442
|
-
|
|
443
|
-
#### Color Styles
|
|
444
|
-
|
|
445
|
-
```js
|
|
446
|
-
// List all color styles
|
|
447
|
-
const colorStyles = await framer.getColorStyles();
|
|
448
|
-
|
|
449
|
-
// Get specific style
|
|
450
|
-
const style = await framer.getColorStyle("style-id");
|
|
451
|
-
|
|
452
|
-
// Create color style (supports light/dark themes)
|
|
453
|
-
const newStyle = await framer.createColorStyle({
|
|
454
|
-
name: "Brand/Primary", // Use / for folders
|
|
455
|
-
light: "rgba(242, 59, 57, 1)",
|
|
456
|
-
dark: "rgba(180, 40, 40, 1)",
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
// Update style
|
|
460
|
-
await style.setAttributes({ light: "rgba(0, 100, 200, 1)" });
|
|
461
|
-
|
|
462
|
-
// Remove style
|
|
463
|
-
await style.remove();
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
#### Text Styles
|
|
467
|
-
|
|
468
|
-
```js
|
|
469
|
-
// List all text styles
|
|
470
|
-
const textStyles = await framer.getTextStyles();
|
|
471
|
-
|
|
472
|
-
// Create text style
|
|
473
|
-
const heading = await framer.createTextStyle({
|
|
474
|
-
name: "Typography/Heading 1",
|
|
475
|
-
tag: "h1",
|
|
476
|
-
fontSize: "48px",
|
|
477
|
-
lineHeight: "1.2em",
|
|
478
|
-
fontWeight: 700,
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
// With breakpoints for responsive typography
|
|
482
|
-
const responsive = await framer.createTextStyle({
|
|
483
|
-
fontSize: "24px",
|
|
484
|
-
minWidth: 1280,
|
|
485
|
-
breakpoints: [
|
|
486
|
-
{ minWidth: 1024, fontSize: "20px" },
|
|
487
|
-
{ minWidth: 768, fontSize: "18px" },
|
|
488
|
-
{ minWidth: 320, fontSize: "16px" },
|
|
489
|
-
],
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
// Update text style
|
|
493
|
-
await heading.setAttributes({ fontSize: "52px" });
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
#### Fonts
|
|
497
|
-
|
|
498
|
-
```js
|
|
499
|
-
// Get available fonts
|
|
500
|
-
const fonts = await framer.getFonts();
|
|
501
|
-
|
|
502
|
-
// Get specific font
|
|
503
|
-
const font = await framer.getFont("Inter", { weight: 400 });
|
|
504
|
-
const boldFont = await framer.getFont("Inter", { weight: 700 });
|
|
505
|
-
|
|
506
|
-
// Use font in text style
|
|
507
|
-
await framer.createTextStyle({
|
|
508
|
-
name: "Body",
|
|
509
|
-
font,
|
|
510
|
-
boldFont,
|
|
511
|
-
});
|
|
512
|
-
```
|
|
513
|
-
|
|
514
333
|
### Code Components
|
|
515
334
|
|
|
516
335
|
Code components are custom React components that run inside Framer. Use them when you need behavior that Framer's visual tools don't support:
|
|
@@ -592,8 +411,9 @@ for await (const node of root.walk()) {
|
|
|
592
411
|
#### Sync external data to collection
|
|
593
412
|
|
|
594
413
|
```js
|
|
595
|
-
const collection = await framer.
|
|
596
|
-
const
|
|
414
|
+
const collection = await framer.getCollection("collection-id");
|
|
415
|
+
const existing = await collection.getItems();
|
|
416
|
+
const existingIds = new Set(existing.map((i) => i.id));
|
|
597
417
|
|
|
598
418
|
const externalData = await fetch("https://api.example.com/posts").then((r) =>
|
|
599
419
|
r.json(),
|
|
@@ -615,87 +435,6 @@ if (toRemove.length) await collection.removeItems(toRemove);
|
|
|
615
435
|
await collection.setPluginData("lastSync", new Date().toISOString());
|
|
616
436
|
```
|
|
617
437
|
|
|
618
|
-
#### Batch update all images' alt text
|
|
619
|
-
|
|
620
|
-
```js
|
|
621
|
-
const nodes = await framer.getNodesWithAttributeSet("backgroundImage");
|
|
622
|
-
for (const node of nodes) {
|
|
623
|
-
if (!node.backgroundImage) continue;
|
|
624
|
-
await node.setAttributes({
|
|
625
|
-
backgroundImage: node.backgroundImage.cloneWithAttributes({
|
|
626
|
-
altText: "Updated description",
|
|
627
|
-
}),
|
|
628
|
-
});
|
|
629
|
-
}
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
### Screenshots and SVG Export
|
|
633
|
-
|
|
634
|
-
Server API exclusive methods for capturing visual output from nodes.
|
|
635
|
-
|
|
636
|
-
```js
|
|
637
|
-
// Take a screenshot of a node (returns Buffer + mimeType)
|
|
638
|
-
const result = await framer.screenshot(node.id);
|
|
639
|
-
console.log(result.mimeType); // "image/png"
|
|
640
|
-
const os = require("os");
|
|
641
|
-
await require("fs").promises.writeFile(
|
|
642
|
-
os.tmpdir() + "/screenshot.png",
|
|
643
|
-
result.data,
|
|
644
|
-
);
|
|
645
|
-
|
|
646
|
-
// With options
|
|
647
|
-
const jpg = await framer.screenshot(node.id, {
|
|
648
|
-
format: "jpeg", // "png" (default) or "jpeg"
|
|
649
|
-
quality: 90, // JPEG quality 0-100 (default 100)
|
|
650
|
-
scale: 2, // Pixel density: 0.5, 1, 1.5, 2, 3, or 4 (default 1)
|
|
651
|
-
clip: { x: 0, y: 0, width: 100, height: 100 }, // Clip region in CSS pixels
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
// Export as SVG string
|
|
655
|
-
const svgString = await framer.exportSVG(node.id);
|
|
656
|
-
await require("fs").promises.writeFile(os.tmpdir() + "/export.svg", svgString);
|
|
657
|
-
```
|
|
658
|
-
|
|
659
|
-
### Publishing and Deployments
|
|
660
|
-
|
|
661
|
-
Server API exclusive methods for publishing and managing deployments.
|
|
662
|
-
|
|
663
|
-
```js
|
|
664
|
-
// Publish the project (creates a new deployment)
|
|
665
|
-
const result = await framer.publish();
|
|
666
|
-
console.log(result.deployment.id); // Deployment ID
|
|
667
|
-
console.log(result.hostnames); // Array of hostnames
|
|
668
|
-
|
|
669
|
-
// Get all deployments
|
|
670
|
-
const deployments = await framer.getDeployments();
|
|
671
|
-
for (const d of deployments) {
|
|
672
|
-
console.log(d.id, d.createdAt);
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
// Deploy a specific deployment to domains
|
|
676
|
-
const hostnames = await framer.deploy(deploymentId, ["example.com"]);
|
|
677
|
-
|
|
678
|
-
// Get current publish info (production and staging URLs)
|
|
679
|
-
const info = await framer.getPublishInfo();
|
|
680
|
-
console.log(info.production?.url); // Production URL
|
|
681
|
-
console.log(info.staging?.url); // Staging URL
|
|
682
|
-
```
|
|
683
|
-
|
|
684
|
-
### Change Tracking
|
|
685
|
-
|
|
686
|
-
Server API exclusive methods for tracking project changes.
|
|
687
|
-
|
|
688
|
-
```js
|
|
689
|
-
// Get changed paths since last publish
|
|
690
|
-
const changes = await framer.getChangedPaths();
|
|
691
|
-
console.log(changes.added); // New paths
|
|
692
|
-
console.log(changes.modified); // Modified paths
|
|
693
|
-
console.log(changes.removed); // Removed paths
|
|
694
|
-
|
|
695
|
-
// Get contributors to changes (returns user IDs)
|
|
696
|
-
const contributors = await framer.getChangeContributors();
|
|
697
|
-
```
|
|
698
|
-
|
|
699
438
|
### Known Limitations
|
|
700
439
|
|
|
701
440
|
- **Pages**: Cannot change the path of a page
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "framer-dalton",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": "./dist/cli.js",
|
|
6
6
|
"main": "./dist/cli.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@trpc/client": "^11.9.0",
|
|
24
24
|
"@trpc/server": "^11.9.0",
|
|
25
25
|
"commander": "^12.1.0",
|
|
26
|
-
"framer-api": "0.1.
|
|
26
|
+
"framer-api": "^0.1.8",
|
|
27
27
|
"zod": "^4.3.6"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|