framer-dalton 0.0.24 → 0.0.26

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.
@@ -31,11 +31,7 @@ What you can do with the Framer CLI:
31
31
 
32
32
  The Framer CLI requires network access (to download packages and connect to Framer servers) and file-system access outside the project directory (to install skills into `~/.agents/skills/` and `~/.claude/skills/`). In sandboxed environments, you must execute `npx framer-dalton` commands with elevated permissions from the get-go. Do not try to execute them with the default sandboxed permissions; they will hang or fail.
33
33
 
34
- ### Required Workflow
35
-
36
- Every task follows these steps:
37
-
38
- #### 1. Connect (once per session)
34
+ ### Session Management
39
35
 
40
36
  If the user refers to a project by name or description rather than giving a URL, first inspect the recent projects:
41
37
 
@@ -45,12 +41,16 @@ npx framer-dalton project list
45
41
 
46
42
  Use that list to infer the likely project from the names and recency. If the right project is already known, use its project ID with `session new`. If there are multiple possible matches, clarify with the user. Only ask the user for a Project URL if there is no clear match. Avoid talking about the technical contents of returned data like IDs.
47
43
 
44
+ Each session maintains a persistent connection to a Framer project. Use sessions to keep state separate between different tasks, persist data across multiple execute calls, and reuse the `framer` API instance without reconnecting.
45
+
48
46
  Create a session against an existing project:
49
47
 
50
48
  ```bash
51
49
  npx framer-dalton session new "<url or id>"
52
50
  ```
53
51
 
52
+ This prints the session ID. You must always use that session ID with `-s <id>` for all subsequent commands. Using the same session preserves your `state` between calls.
53
+
54
54
  To create a brand new empty project and connect to it:
55
55
 
56
56
  ```bash
@@ -67,91 +67,6 @@ npx framer-dalton session new "<returned project id>"
67
67
 
68
68
  Note that during beta, you cannot connect to non-beta projects. If creating a session errors with a message about this, you need to move your project to beta.
69
69
 
70
- #### 2. Look up the API (before EVERY code execution)
71
-
72
- **You MUST run `npx framer-dalton docs` before writing any code.** Do not guess method names or signatures.
73
-
74
- ```bash
75
- npx framer-dalton docs Collection # What methods exist?
76
- npx framer-dalton docs Collection.getItems # What are the parameters and return type?
77
- ```
78
-
79
- #### 3. Execute code
80
-
81
- Only after checking docs, write your code to a unique file under `{{FRAMER_TEMPORARY_DIR}}/` and execute with `-f`. Name each file `<sessionId>-<short-summary>.js` where `<short-summary>` is a brief kebab-case description (e.g., `1-read-collections.js`, `1-add-team-member.js`).
82
-
83
- ```bash
84
- npx framer-dalton exec -s 1 -f {{FRAMER_TEMPORARY_DIR}}/1-read-collections.js
85
- ```
86
-
87
- #### 4. Store results in `state`
88
-
89
- Always save results you'll need again. Don't repeat API calls.
90
-
91
- **Do not skip step 2.** The examples below are patterns only - always verify current signatures with `npx framer-dalton docs`.
92
-
93
- ## Core Usage Principles
94
-
95
- - Be concise. Don't narrate implementation details like field IDs, escaping, or internal steps. Just do the work and report what was accomplished in user-facing terms.
96
- - Use `framer.*` for plugin API calls. Top-level methods are not globals.
97
- - Before making changes that add/update/delete content that the user has not clearly and explicitly requested in this conversation, inform the user of what you plan to do and why, and ask them to confirm.
98
- - You do not need to ask for confirmation when carrying out a specific add/update/delete change that the user has already clearly requested (this counts as explicit consent).
99
- - You do not need to explain or ask for confirmation for non-destructive calls like reading project state.
100
- - If the exact action was not agreed upon, or you are inferring a broader change or choosing between multiple reasonable options using your own judgment, ask for confirmation.
101
- - Always ask for confirmation before destructive actions that the user did not explicitly request - especially deletes, cleanup, resets, or broad removals inferred by the agent.
102
-
103
- ## Context Variables
104
-
105
- - `framer` - Connected Framer Server API instance
106
- - `state` - Object persisted between calls within your session
107
- - `console` - For output (`console.log`, `console.error`)
108
- - `require` - Sandboxed Node.js modules: fs, path, url, crypto, buffer, util, os
109
- - Standard globals: `fetch`, `Buffer`, `URL`, `crypto`, `setTimeout`
110
-
111
- **Note:** `fs` operations are sandboxed to cwd, /tmp, and os.tmpdir().
112
-
113
- ## Use `state` to Avoid Repeated Calls
114
-
115
- **Always store results in `state` when you'll need them again.** API calls are slow - don't repeat them.
116
-
117
- ```js
118
- // {{FRAMER_TEMPORARY_DIR}}/1-get-collections.js
119
- state.collections = await framer.getCollections();
120
- ```
121
-
122
- ```bash
123
- npx framer-dalton exec -s 1 -f {{FRAMER_TEMPORARY_DIR}}/1-get-collections.js
124
- ```
125
-
126
- ```js
127
- // {{FRAMER_TEMPORARY_DIR}}/1-get-team-items.js — reuse from state
128
- state.teamItems = await state.collections.find(c => c.name === 'Team').getItems();
129
- console.log(state.teamItems.length);
130
- ```
131
-
132
- ```bash
133
- npx framer-dalton exec -s 1 -f {{FRAMER_TEMPORARY_DIR}}/1-get-team-items.js
134
- ```
135
-
136
- Store anything you'll reference again.
137
-
138
- ## Session Management
139
-
140
- Each session maintains a persistent connection to a Framer project. Use sessions to:
141
-
142
- - Keep state separate between different tasks
143
- - Persist data across multiple execute calls
144
- - Reuse the `framer` API instance without reconnecting
145
-
146
- Get a new session ID:
147
-
148
- ```bash
149
- npx framer-dalton session new "https://framer.com/projects/Website--abc123"
150
- # outputs: 1
151
- ```
152
-
153
- **Always use your own session** - pass `-s <id>` to all commands. Using the same session preserves your `state` between calls.
154
-
155
70
  List active sessions:
156
71
 
157
72
  ```bash
@@ -160,296 +75,4 @@ npx framer-dalton session list
160
75
 
161
76
  ## Project-scoped skill
162
77
 
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.
164
-
165
- ## Canvas Editing: Alternative Approach (“Prompt the Framer agent”)
166
-
167
- Use this approach only if the user asks for it. Do not mention this approach to the user otherwise.
168
-
169
- This approach is called “Prompt the Framer agent”. With this approach, use `framer.startAgentConversation()` to start a stateful design subagent. Keep the `conversationId` it returns in `state`, and call `framer.continueAgentConversation()` with it to continue the same design task.
170
-
171
- Do not call `framer.getAgentSystemPrompt()` or `framer.getAgentContext()` or `framer.applyAgentChanges()` with this approach.
172
-
173
- Example:
174
-
175
- ```js
176
- state.agent ??= {};
177
- const first = await framer.startAgentConversation(
178
- "Build me a landing page based on the attached screenshot",
179
- {
180
- pagePath: "/",
181
- imageUrls: ["https://example.com/image.png"],
182
- // selectionNodeIds: [...]
183
- },
184
- );
185
- state.agent.conversationId = first.conversationId;
186
- console.log(first.responseMessages);
187
-
188
- const second = await framer.continueAgentConversation("Now make it pink", {
189
- conversationId: state.agent.conversationId,
190
- selectionNodeIds: ["someNodeId"],
191
- // imageUrls: [...],
192
- // changing pagePath or model is not supported
193
- });
194
- console.log(second.responseMessages);
195
- ```
196
-
197
- Prompting may take a while to complete, so set the command timeout to 10 minutes.
198
-
199
- ## Execute Code
200
-
201
- Prefer writing code to a unique file under `{{FRAMER_TEMPORARY_DIR}}/` and executing it with `-f`:
202
-
203
- ```bash
204
- npx framer-dalton exec -s <sessionId> -f {{FRAMER_TEMPORARY_DIR}}/<sessionId>-<short-summary>.js
205
- ```
206
-
207
- For short snippets, `exec` also accepts `-e <code>` or code piped on stdin.
208
-
209
- ## Shell Quoting
210
-
211
- In Windows PowerShell, if an argument contains nested quotes, use a single-quoted here-string and pass the variable. Do not backslash-escape quotes.
212
-
213
- ```powershell
214
- $value = @'
215
- [{"key":"value","filter":["text","$rect"]}]
216
- '@
217
- npx framer-dalton <command> --option $value
218
- ```
219
-
220
- ## API Documentation
221
-
222
- ```bash
223
- npx framer-dalton docs # List all available methods
224
- npx framer-dalton docs framer.getCollections # Show top level method
225
- npx framer-dalton docs Collection # Show class with all method signatures
226
- npx framer-dalton docs Collection.addItems # Show method + recursively expand all referenced types
227
- npx framer-dalton docs ScreenshotOptions # Show type + recursively expand all referenced types
228
- ```
229
-
230
- `docs` with no arguments lists available methods. Looking up a class shows its full definition without expanding referenced types. Looking up a specific method or type automatically expands all referenced types recursively.
231
-
232
- ## API Examples
233
-
234
- **STOP: These are patterns only. Before using any method below, run `npx framer-dalton docs <ClassName>` to verify the current signature.**
235
-
236
- ### Working with Collections (CMS)
237
-
238
- 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.
239
-
240
- **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.
241
-
242
- #### Reading Collections
243
-
244
- ```js
245
- // Get all collections
246
- const collections = await framer.getCollections();
247
- console.log(collections.map((c) => ({ name: c.name, id: c.id })));
248
-
249
- // Get a specific collection by ID
250
- const collection = await framer.getCollection("collection-id");
251
-
252
- // Get fields (columns) - returns array of { id, type, name }
253
- const fields = await collection.getFields();
254
- console.log(fields);
255
- // [{ id: "BnNuS2i3o", type: "string", name: "Title" }, ...]
256
-
257
- // Get items (rows)
258
- const items = await collection.getItems();
259
- console.log(items);
260
- // [{ id: "XTM8FSHGs", slug: "post-1", draft: false, fieldData: {...} }, ...]
261
- ```
262
-
263
- #### Field Types
264
-
265
- `boolean`, `color`, `number`, `string`, `formattedText` (HTML), `image`, `file`, `link`, `date`, `enum`, `collectionReference`, `multiCollectionReference`, `array` (galleries)
266
-
267
- #### Updating Collection Items
268
-
269
- ```js
270
- // Add or update items (if id matches existing item, it updates)
271
- await collection.addItems([
272
- {
273
- id: "new-item-1",
274
- slug: "hello-world",
275
- fieldData: { titleFieldId: "Hello World" },
276
- },
277
- ]);
278
-
279
- // Remove items
280
- await collection.removeItems(["item-id-1", "item-id-2"]);
281
-
282
- // Reorder items
283
- const ids = items.map((i) => i.id).reverse();
284
- await collection.setItemOrder(ids);
285
- ```
286
-
287
- #### Working with CollectionItem
288
-
289
- ```js
290
- const items = await collection.getItems();
291
- const item = items[0];
292
-
293
- // Update item attributes
294
- await item.setAttributes({ slug: "new-slug" });
295
-
296
- // Navigate to item in Framer UI
297
- await item.navigateTo();
298
-
299
- // Remove item
300
- await item.remove();
301
- ```
302
-
303
- ### Working with Images
304
-
305
- 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.
306
-
307
- There are three sources you'll typically pull URLs from:
308
-
309
- **Stock photography.** Use `framer.queryImagesForAgent` to source candidates and stash the URL you want:
310
-
311
- ```js
312
- const { results } = await framer.queryImagesForAgent({
313
- source: "unsplash",
314
- query: "snow-capped mountains",
315
- count: 4,
316
- orientation: "landscape",
317
- });
318
- state.heroUrl = results[0].url;
319
- ```
320
-
321
- **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:
322
-
323
- ```js
324
- state.heroUrl = (await framer.uploadImage({
325
- image: "https://example.com/hero.png",
326
- altText: "Mountain range at sunset",
327
- })).url;
328
- ```
329
-
330
- **An image already on the canvas.** Read the node and reuse its existing image URL:
331
-
332
- ```js
333
- const node = await framer.getNodeForAgent({ id: "<image-node-id>" });
334
- state.heroUrl = node.attributes.fill;
335
- ```
336
-
337
- For inline SVGs, use the plugin method directly:
338
-
339
- ```js
340
- await framer.addSVG({
341
- svg: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"><circle cx="10" cy="10" r="8"/></svg>',
342
- name: "circle.svg",
343
- });
344
- ```
345
-
346
- ### Code Components
347
-
348
- Code components are custom React components that run inside Framer. Use them when you need behavior that Framer's visual tools don't support:
349
-
350
- - **Custom interactivity** — drag-to-reorder, gesture-driven animations, games, form validation
351
- - **External data** — fetching from APIs, rendering dynamic content, real-time updates
352
- - **Complex logic** — state machines, calculations, conditional rendering beyond simple variants
353
- - **Third-party libraries** — maps, charts, video players, rich text editors
354
- - **Canvas/WebGL** — custom drawing, 3D rendering, generative art
355
-
356
- If the design can be built with Framer's built-in components, layout tools, and interactions — don't use a code component. Code components are harder to maintain and can't be visually edited.
357
-
358
- #### Workflow
359
-
360
- 1. **Create** a code file: `framer.createCodeFile("MyComponent.tsx", code)`
361
- 2. **Edit** an existing code file: `codeFile.setFileContent(newCode)`
362
- 3. **Type check**: `codeFile.typecheck()` or `framer.typecheckCode("File.tsx", code)`
363
- 4. **Add to canvas**: `framer.addComponentInstance({ url: codeFile.exports[0].insertURL })`
364
-
365
- Use `npx framer-dalton docs CodeFile` and `npx framer-dalton docs Framer.createCodeFile` to look up the full API.
366
-
367
- #### Authoring guidance
368
-
369
- Before writing component code, load the `framer-code-components` skill.
370
-
371
- ### Storing Data
372
-
373
- Store metadata on nodes or globally in the project.
374
-
375
- ```js
376
- // Store global project data
377
- await framer.setPluginData("myKey", "myValue");
378
- const value = await framer.getPluginData("myKey");
379
-
380
- // Store data on a node
381
- await node.setPluginData("processed", "true");
382
- const nodeData = await node.getPluginData("processed");
383
-
384
- // List all keys
385
- const keys = await framer.getPluginDataKeys();
386
- const nodeKeys = await node.getPluginDataKeys();
387
-
388
- // Delete data (set to null)
389
- await framer.setPluginData("myKey", null);
390
- ```
391
-
392
- ### Localization
393
-
394
- ```js
395
- // Get all locales
396
- const locales = await framer.getLocales();
397
- const defaultLocale = await framer.getDefaultLocale();
398
-
399
- // Get localization groups (pages, CMS items with translations)
400
- const groups = await framer.getLocalizationGroups();
401
-
402
- // Update translations
403
- const french = locales.find((l) => l.code === "fr");
404
- await framer.setLocalizationData({
405
- valuesBySource: {
406
- [sourceId]: {
407
- [french.id]: { action: "set", value: "Bonjour" },
408
- },
409
- },
410
- });
411
- ```
412
-
413
- ### Common Patterns
414
-
415
- #### Iterate over all nodes in project
416
-
417
- ```js
418
- const root = await framer.getCanvasRoot();
419
- for await (const node of root.walk()) {
420
- console.log(node.name);
421
- }
422
- ```
423
-
424
- #### Sync external data to collection
425
-
426
- ```js
427
- const collection = await framer.getCollection("collection-id");
428
- const existing = await collection.getItems();
429
- const existingIds = new Set(existing.map((i) => i.id));
430
-
431
- const externalData = await fetch("https://api.example.com/posts").then((r) =>
432
- r.json(),
433
- );
434
-
435
- const items = externalData.map((post) => ({
436
- id: post.id,
437
- slug: post.slug,
438
- fieldData: { title: post.title, content: post.body },
439
- }));
440
-
441
- await collection.addItems(items);
442
-
443
- // Remove items no longer in external source
444
- const newIds = new Set(items.map((i) => i.id));
445
- const toRemove = [...existingIds].filter((id) => !newIds.has(id));
446
- if (toRemove.length) await collection.removeItems(toRemove);
447
-
448
- await collection.setPluginData("lastSync", new Date().toISOString());
449
- ```
450
-
451
- ### Known Limitations
452
-
453
- - **Pages**: Cannot change the path of a page
454
- - **Code overrides**: Cannot assign overrides to nodes
455
- - **Analytics**: No APIs exist for accessing analytics data
78
+ **Always load the project-scoped skill `framer-project-<projectId>` immediately after `session new`, regardless of the task.** That skill is generated by `session new` and contains the documentation for everything you need to know about working with a Framer project.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "framer-dalton",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "type": "module",
5
5
  "bin": "./dist/cli.js",
6
6
  "main": "./dist/cli.js",
@@ -16,6 +16,7 @@
16
16
  "typecheck": "tsc --noEmit",
17
17
  "clean": "rm -rf dist",
18
18
  "check": "biome check .",
19
+ "check:fix": "biome check --write .",
19
20
  "format": "biome format --write .",
20
21
  "generate-types": "tsx scripts/generate-types.ts"
21
22
  },
@@ -23,17 +24,17 @@
23
24
  "@trpc/client": "^11.17.0",
24
25
  "@trpc/server": "^11.17.0",
25
26
  "commander": "^12.1.0",
26
- "framer-api": "0.1.10",
27
- "zod": "^4.3.6"
27
+ "framer-api": "^0.1.14-beta",
28
+ "zod": "^4.4.3"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@biomejs/biome": "^2.4.13",
31
- "@framerjs/framer-events": "0.0.175",
32
- "@types/node": "24.10.9",
32
+ "@framerjs/framer-events": "0.0.183",
33
+ "@types/node": "24.12.4",
33
34
  "tsup": "^8.0.2",
34
- "tsx": "^4.19.0",
35
+ "tsx": "^4.22.3",
35
36
  "typescript": "^5.9.2",
36
- "vitest": "^4.0.18"
37
+ "vitest": "^4.1.7"
37
38
  },
38
39
  "packageManager": "yarn@4.13.0",
39
40
  "engines": {
@@ -1,49 +0,0 @@
1
- ---
2
- name: {{SKILL_NAME}}
3
- description: "Project-scoped Framer skill for project {{PROJECT_ID}}. Covers canvas editing, project reads, change review, publishing, image sourcing, and component operations. Very important: never load this skill without having already read the `framer` skill and without having already run `session new`, which will dynamically update this skill."
4
- allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)", "Read({{FRAMER_TEMPORARY_DIR}}/*)", "Write({{FRAMER_TEMPORARY_DIR}}/*)"]
5
- ---
6
-
7
- ## Project Scope
8
-
9
- - Project ID: {{PROJECT_ID}}
10
- - Generated At: {{GENERATED_AT}}
11
-
12
- ## Rules
13
-
14
- - For design/layout work, do not use low-level node APIs (`createNode`, `setAttributes`, `setRect`, etc.). Use the canvas editing flow (`read-project` + `apply-changes`) with the embedded prompt and project context in this skill.
15
- - During normal task execution, do not call `framer.getAgentSystemPrompt()` or `framer.getAgentContext()`. This skill already includes `getAgentContext({ pagePath: "/" })`.
16
- - `read-project` and `apply-changes` are one-liner CLI shortcuts for the high-frequency canvas-editing methods. Use them to read state and apply DSL inline without writing a file first:
17
- - `npx framer-dalton read-project -s <sessionId> -q <queries> -p <pagePath>` — equivalent to `framer.readProjectForAgent(queries, { pagePath })`. Query types are documented in the embedded prompt below.
18
- - `npx framer-dalton apply-changes -s <sessionId> -p <pagePath> -e <dsl>` — equivalent to `framer.applyAgentChanges(dsl, { pagePath })`.
19
- - The embedded prompt below also references other agent-surface methods (`reviewChangesForAgent`, `publishForAgent`, `queryImagesForAgent`, `flattenComponentInstanceForAgent`, `makeExternalComponentLocalForAgent`, `getNodeForAgent` / `getNodesForAgent` / `getNodesOfTypesForAgent` / `getScopeNodeForAgent` / `getGroundNodeForAgent` / `getParentNodeForAgent` / `getAncestorsForAgent`, and `serializeForAgent` / `serializeNodesForAgent` / `paginateForAgent`). These have no dedicated shortcut; invoke via `npx framer-dalton exec -s <sessionId> -e 'console.log(await framer.<method>(<args>))'`.
20
-
21
- ## Workflow Loop
22
-
23
- ```bash
24
- # 1) Read page structure first (fill in real query from Live Agent System Prompt)
25
- npx framer-dalton read-project -s <sessionId> -q '<query to get page structure>' -p "/"
26
-
27
- # 2) Request additional targeted queries only if needed
28
-
29
- # 3) Apply changes — pass DSL directly via apply-changes
30
- npx framer-dalton apply-changes -s <sessionId> -p "/" -e "$dsl"
31
- ```
32
-
33
- ## Critical for UX: Streaming
34
-
35
- When you generate DSL to apply, keep it short. Each `apply-changes` call must only cover ONE logical section (header, footer, hero, card, etc.). Once you apply ONE section, continue with the next.
36
-
37
- This ensures progress is streamed to the user in small, visible increments.
38
-
39
- ## Live Agent System Prompt
40
-
41
- This is the static prompt returned by `framer.getAgentSystemPrompt()`.
42
-
43
- {{CANVAS_PROMPT}}
44
-
45
- ## Live Agent Context (/)
46
-
47
- This is the dynamic project context returned by `framer.getAgentContext({ pagePath: "/" })`. It contains project-specific data for the current page, including available fonts, available components, design tokens, style presets, and icon sets.
48
-
49
- {{AGENT_CONTEXT}}