framer-dalton 0.0.23 → 0.0.25
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 +5 -5
- package/dist/cli.js +267 -57
- package/dist/start-relay-server.js +45 -12
- package/docs/skills/framer-code-components.md +1 -1
- package/docs/skills/framer-project.md +426 -0
- package/docs/skills/framer.md +6 -370
- package/package.json +5 -5
- package/docs/skills/framer-canvas-editing-project.md +0 -49
|
@@ -2,18 +2,19 @@ import fs4 from 'fs';
|
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import 'child_process';
|
|
5
|
+
import 'net';
|
|
5
6
|
import { fileURLToPath } from 'url';
|
|
6
7
|
import { createTRPCClient, httpLink } from '@trpc/client';
|
|
7
8
|
import crypto, { randomUUID, timingSafeEqual } from 'crypto';
|
|
8
9
|
import http from 'http';
|
|
9
10
|
import { createHTTPHandler } from '@trpc/server/adapters/standalone';
|
|
10
11
|
import { initTRPC, TRPCError } from '@trpc/server';
|
|
12
|
+
import { connect, FramerAPIError } from 'framer-api';
|
|
11
13
|
import { z } from 'zod';
|
|
12
|
-
import { connect } from 'framer-api';
|
|
13
14
|
import { createRequire } from 'module';
|
|
14
15
|
import * as vm from 'vm';
|
|
15
16
|
|
|
16
|
-
/* @framer/ai relay server v0.0.
|
|
17
|
+
/* @framer/ai relay server v0.0.25 */
|
|
17
18
|
var __defProp = Object.defineProperty;
|
|
18
19
|
var __knownSymbol = (name2, symbol) => (symbol = Symbol[name2]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name2);
|
|
19
20
|
var __typeError = (msg) => {
|
|
@@ -159,7 +160,7 @@ __name(debug, "debug");
|
|
|
159
160
|
// src/version.ts
|
|
160
161
|
var VERSION = (
|
|
161
162
|
// typeof is used to ensure this can be used just via tsx or node etc. without build
|
|
162
|
-
"0.0.
|
|
163
|
+
"0.0.25"
|
|
163
164
|
);
|
|
164
165
|
|
|
165
166
|
// src/relay-client.ts
|
|
@@ -391,6 +392,19 @@ var ScopedFS = class {
|
|
|
391
392
|
}
|
|
392
393
|
constants = fs4.constants;
|
|
393
394
|
};
|
|
395
|
+
function getErrorRef(error) {
|
|
396
|
+
if (!(error instanceof Error)) return void 0;
|
|
397
|
+
const ref = error.ref;
|
|
398
|
+
return typeof ref === "string" && ref.length > 0 ? ref : void 0;
|
|
399
|
+
}
|
|
400
|
+
__name(getErrorRef, "getErrorRef");
|
|
401
|
+
function formatError(error) {
|
|
402
|
+
if (error instanceof Error) {
|
|
403
|
+
return error.message;
|
|
404
|
+
}
|
|
405
|
+
return String(error);
|
|
406
|
+
}
|
|
407
|
+
__name(formatError, "formatError");
|
|
394
408
|
|
|
395
409
|
// src/execute.ts
|
|
396
410
|
var EXECUTION_TIMEOUT = 10 * 60 * 1e3;
|
|
@@ -530,9 +544,11 @@ async function execute(session, code, options = {}) {
|
|
|
530
544
|
if (isConnectionError(errorMessage)) {
|
|
531
545
|
session.connection.markDisconnected();
|
|
532
546
|
}
|
|
547
|
+
const errorRef = getErrorRef(err);
|
|
533
548
|
return {
|
|
534
549
|
output,
|
|
535
|
-
error: errorMessage
|
|
550
|
+
error: errorMessage,
|
|
551
|
+
...errorRef ? { errorRef } : {}
|
|
536
552
|
};
|
|
537
553
|
} finally {
|
|
538
554
|
if (timeoutId) clearTimeout(timeoutId);
|
|
@@ -546,10 +562,13 @@ async function executeWithReconnect(session, code, options, execId) {
|
|
|
546
562
|
);
|
|
547
563
|
try {
|
|
548
564
|
await session.connection.reconnect();
|
|
549
|
-
} catch {
|
|
565
|
+
} catch (err) {
|
|
566
|
+
const inner = err instanceof Error ? err.message : String(err);
|
|
567
|
+
const errorRef = getErrorRef(err);
|
|
550
568
|
return {
|
|
551
569
|
output: [],
|
|
552
|
-
error:
|
|
570
|
+
error: `Failed to get connection for session: ${inner}`,
|
|
571
|
+
...errorRef ? { errorRef } : {}
|
|
553
572
|
};
|
|
554
573
|
}
|
|
555
574
|
}
|
|
@@ -562,13 +581,16 @@ async function executeWithReconnect(session, code, options, execId) {
|
|
|
562
581
|
);
|
|
563
582
|
try {
|
|
564
583
|
await session.connection.reconnect();
|
|
565
|
-
} catch {
|
|
584
|
+
} catch (err) {
|
|
585
|
+
const inner = err instanceof Error ? err.message : String(err);
|
|
586
|
+
const errorRef = getErrorRef(err);
|
|
566
587
|
log(
|
|
567
|
-
`reconnect.failed exec=${execId} session=${session.id} req=${session.connection.framer.requestId} error="
|
|
588
|
+
`reconnect.failed exec=${execId} session=${session.id} req=${session.connection.framer.requestId} error="${inner}"`
|
|
568
589
|
);
|
|
569
590
|
return {
|
|
570
591
|
output: [],
|
|
571
|
-
error:
|
|
592
|
+
error: `Connection lost and failed to reconnect: ${inner}`,
|
|
593
|
+
...errorRef ? { errorRef } : {}
|
|
572
594
|
};
|
|
573
595
|
}
|
|
574
596
|
log(
|
|
@@ -1156,7 +1178,17 @@ var SessionManager = class {
|
|
|
1156
1178
|
var sessionManager = new SessionManager();
|
|
1157
1179
|
|
|
1158
1180
|
// src/router.ts
|
|
1159
|
-
var t = initTRPC.create(
|
|
1181
|
+
var t = initTRPC.create({
|
|
1182
|
+
errorFormatter({ shape, error }) {
|
|
1183
|
+
const cause = error.cause;
|
|
1184
|
+
if (cause instanceof FramerAPIError) {
|
|
1185
|
+
const framerApi = { code: cause.code };
|
|
1186
|
+
if (cause.ref !== void 0) framerApi.ref = cause.ref;
|
|
1187
|
+
return { ...shape, data: { ...shape.data, framerApi } };
|
|
1188
|
+
}
|
|
1189
|
+
return shape;
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1160
1192
|
var nextExecId = 0;
|
|
1161
1193
|
var appRouter = t.router({
|
|
1162
1194
|
version: t.procedure.query(() => {
|
|
@@ -1192,7 +1224,7 @@ var appRouter = t.router({
|
|
|
1192
1224
|
});
|
|
1193
1225
|
return { id: session.id };
|
|
1194
1226
|
} catch (err) {
|
|
1195
|
-
const message =
|
|
1227
|
+
const message = formatError(err);
|
|
1196
1228
|
trackError({
|
|
1197
1229
|
errorType: "session_create_error",
|
|
1198
1230
|
projectId: input.projectId,
|
|
@@ -1201,7 +1233,8 @@ var appRouter = t.router({
|
|
|
1201
1233
|
});
|
|
1202
1234
|
throw new TRPCError({
|
|
1203
1235
|
code: isAuthError(message) ? "UNAUTHORIZED" : "INTERNAL_SERVER_ERROR",
|
|
1204
|
-
message
|
|
1236
|
+
message,
|
|
1237
|
+
cause: err
|
|
1205
1238
|
});
|
|
1206
1239
|
}
|
|
1207
1240
|
}),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: framer-code-components
|
|
3
|
-
description: "Framer code component implementation guidance, platform constraints, layout annotations, property controls, and authoring best practices. Use only after loading the framer skill first in the same task; never load this skill directly as the entry point."
|
|
3
|
+
description: "Framer code component implementation guidance, platform constraints, layout annotations, property controls, and authoring best practices. Use only after loading the framer skill and the generated framer-project-<projectId> skill first in the same task; never load this skill directly as the entry point."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Framer Code Components
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: {{SKILL_NAME}}
|
|
3
|
+
description: "Project-scoped Framer skill for project {{PROJECT_ID}}. Covers project reads, edits, change review, publishing, image sourcing, component operations, and canvas editing. 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
|
+
## Required Workflow
|
|
13
|
+
|
|
14
|
+
Every connected-project task follows these steps:
|
|
15
|
+
|
|
16
|
+
### 1. Look up the API (before EVERY new use of an API method)
|
|
17
|
+
|
|
18
|
+
**You MUST run `npx framer-dalton docs` before writing any code.** Do not guess method names or signatures.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx framer-dalton docs Collection # What methods exist?
|
|
22
|
+
npx framer-dalton docs Collection.getItems # What are the parameters and return type?
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 2. Execute code
|
|
26
|
+
|
|
27
|
+
Only after checking docs, use your write tool to write your code to a unique file under `{{FRAMER_TEMPORARY_DIR}}/`, then execute it with `-f`. Do not create code files with shell heredocs or `cat`. 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`).
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx framer-dalton exec -s 1 -f {{FRAMER_TEMPORARY_DIR}}/1-read-collections.js
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 3. Store results in `state`
|
|
34
|
+
|
|
35
|
+
Always save results you'll need again. Don't repeat API calls.
|
|
36
|
+
|
|
37
|
+
## Method Selection
|
|
38
|
+
|
|
39
|
+
Use agent-specific methods whenever a plugin API method and an agent method overlap:
|
|
40
|
+
|
|
41
|
+
- Use `readProjectForAgent`, `getNodeForAgent`, `getNodesForAgent`, `getNodesOfTypesForAgent`, `getScopeNodeForAgent`, `getGroundNodeForAgent`, `getParentNodeForAgent`, `getAncestorsForAgent`, `serializeForAgent`, `serializeNodesForAgent`, and `paginateForAgent` for project tree reads.
|
|
42
|
+
- Use `applyAgentChanges` for page and layout edits. Do not use low-level node APIs like `createNode`, `setAttributes`, or `setRect` for design/layout work.
|
|
43
|
+
- Use `publishForAgent` for publishing. Do not use `publish`, `getDeployments`, or `deploy` for normal agent publishing flows.
|
|
44
|
+
|
|
45
|
+
Use the plugin API for capabilities that do not have an agent-specific counterpart, such as code file management, localization, and redirects.
|
|
46
|
+
|
|
47
|
+
During normal task execution, do not call `framer.getAgentSystemPrompt()` or `framer.getAgentContext()`. This skill already includes their output.
|
|
48
|
+
|
|
49
|
+
## Core Usage Principles
|
|
50
|
+
|
|
51
|
+
- 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.
|
|
52
|
+
- Use `framer.*` for plugin API calls. Top-level methods are not globals.
|
|
53
|
+
- 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.
|
|
54
|
+
- 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).
|
|
55
|
+
- You do not need to explain or ask for confirmation for non-destructive calls like reading project state.
|
|
56
|
+
- 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.
|
|
57
|
+
- 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.
|
|
58
|
+
|
|
59
|
+
## Compaction
|
|
60
|
+
|
|
61
|
+
**Critical**: if an agent conversation is compacted, you MUST read the entirety of this skill back into context. This is non-negotiable and this instruction itself must not be lost during compaction.
|
|
62
|
+
|
|
63
|
+
## Context Variables
|
|
64
|
+
|
|
65
|
+
- `framer` - Connected Framer Server API instance
|
|
66
|
+
- `state` - Object persisted between calls within your session
|
|
67
|
+
- `console` - For output (`console.log`, `console.error`)
|
|
68
|
+
- `require` - Sandboxed Node.js modules: fs, path, url, crypto, buffer, util, os
|
|
69
|
+
- Standard globals: `fetch`, `Buffer`, `URL`, `crypto`, `setTimeout`
|
|
70
|
+
|
|
71
|
+
**Note:** `fs` operations are sandboxed to cwd, /tmp, and os.tmpdir().
|
|
72
|
+
|
|
73
|
+
## Use `state` to Avoid Repeated Calls
|
|
74
|
+
|
|
75
|
+
**Always store results in `state` when you'll need them again.** API calls are slow - don't repeat them.
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
// {{FRAMER_TEMPORARY_DIR}}/1-get-collections.js
|
|
79
|
+
state.collections = await framer.getCollections();
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx framer-dalton exec -s 1 -f {{FRAMER_TEMPORARY_DIR}}/1-get-collections.js
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
// {{FRAMER_TEMPORARY_DIR}}/1-get-team-items.js — reuse from state
|
|
88
|
+
state.teamItems = await state.collections.find(c => c.name === 'Team').getItems();
|
|
89
|
+
console.log(state.teamItems.length);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx framer-dalton exec -s 1 -f {{FRAMER_TEMPORARY_DIR}}/1-get-team-items.js
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Store anything you'll reference again.
|
|
97
|
+
|
|
98
|
+
## Canvas Editing: Alternative Approach (“Prompt the Framer agent”)
|
|
99
|
+
|
|
100
|
+
Use this approach only if the user asks for it. Do not mention this approach to the user otherwise.
|
|
101
|
+
|
|
102
|
+
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.
|
|
103
|
+
|
|
104
|
+
Do not call `framer.getAgentSystemPrompt()` or `framer.getAgentContext()` or `framer.applyAgentChanges()` with this approach.
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
state.agent ??= {};
|
|
110
|
+
const first = await framer.startAgentConversation(
|
|
111
|
+
"Build me a landing page based on the attached screenshot",
|
|
112
|
+
{
|
|
113
|
+
pagePath: "/",
|
|
114
|
+
imageUrls: ["https://example.com/image.png"],
|
|
115
|
+
// selectionNodeIds: [...]
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
state.agent.conversationId = first.conversationId;
|
|
119
|
+
console.log(first.responseMessages);
|
|
120
|
+
|
|
121
|
+
const second = await framer.continueAgentConversation("Now make it pink", {
|
|
122
|
+
conversationId: state.agent.conversationId,
|
|
123
|
+
selectionNodeIds: ["someNodeId"],
|
|
124
|
+
// imageUrls: [...],
|
|
125
|
+
// changing pagePath or model is not supported
|
|
126
|
+
});
|
|
127
|
+
console.log(second.responseMessages);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Prompting may take a while to complete, so set the command timeout to 10 minutes.
|
|
131
|
+
|
|
132
|
+
## Execute Code
|
|
133
|
+
|
|
134
|
+
Prefer using your write tool to write code to a unique file under `{{FRAMER_TEMPORARY_DIR}}/` and executing it with `-f`. Do not create code files with shell heredocs or `cat`:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npx framer-dalton exec -s <sessionId> -f {{FRAMER_TEMPORARY_DIR}}/<sessionId>-<short-summary>.js
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
For short snippets, `exec` also accepts `-e <code>` or code piped on stdin.
|
|
141
|
+
|
|
142
|
+
## Shell Quoting
|
|
143
|
+
|
|
144
|
+
In Windows PowerShell, if an argument contains nested quotes, use a single-quoted here-string and pass the variable. Do not backslash-escape quotes.
|
|
145
|
+
|
|
146
|
+
```powershell
|
|
147
|
+
$value = @'
|
|
148
|
+
[{"key":"value","filter":["text","$rect"]}]
|
|
149
|
+
'@
|
|
150
|
+
npx framer-dalton <command> --option $value
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## API Documentation
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npx framer-dalton docs # List all available methods
|
|
157
|
+
npx framer-dalton docs framer.getCollections # Show top level method
|
|
158
|
+
npx framer-dalton docs Collection # Show class with all method signatures
|
|
159
|
+
npx framer-dalton docs Collection.addItems # Show method + recursively expand all referenced types
|
|
160
|
+
npx framer-dalton docs ScreenshotOptions # Show type + recursively expand all referenced types
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`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.
|
|
164
|
+
|
|
165
|
+
## API Examples
|
|
166
|
+
|
|
167
|
+
**STOP: These are patterns only. Before using any method below, run `npx framer-dalton docs <ClassName>` to verify the current signature.**
|
|
168
|
+
|
|
169
|
+
### Working with Collections (CMS)
|
|
170
|
+
|
|
171
|
+
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.
|
|
172
|
+
|
|
173
|
+
**Exception — CMS Collection Lists.** The in-canvas construct that *renders* a collection as a repeating list of cards (the "Collection List" / repeater) is an agent/page concern. Build and edit those through `readProjectForAgent` and `applyAgentChanges`, not the plugin API. Use the plugin Collection API to read available collections and field schema first, then drive the list through the project-scoped skill.
|
|
174
|
+
|
|
175
|
+
#### Reading Collections
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
// Get all collections
|
|
179
|
+
const collections = await framer.getCollections();
|
|
180
|
+
console.log(collections.map((c) => ({ name: c.name, id: c.id })));
|
|
181
|
+
|
|
182
|
+
// Get a specific collection by ID
|
|
183
|
+
const collection = await framer.getCollection("collection-id");
|
|
184
|
+
|
|
185
|
+
// Get fields (columns) - returns array of { id, type, name }
|
|
186
|
+
const fields = await collection.getFields();
|
|
187
|
+
console.log(fields);
|
|
188
|
+
// [{ id: "BnNuS2i3o", type: "string", name: "Title" }, ...]
|
|
189
|
+
|
|
190
|
+
// Get items (rows)
|
|
191
|
+
const items = await collection.getItems();
|
|
192
|
+
console.log(items);
|
|
193
|
+
// [{ id: "XTM8FSHGs", slug: "post-1", draft: false, fieldData: {...} }, ...]
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Sync external data to collection
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
const collection = await framer.getCollection("collection-id");
|
|
200
|
+
const existing = await collection.getItems();
|
|
201
|
+
const existingIds = new Set(existing.map((i) => i.id));
|
|
202
|
+
|
|
203
|
+
const externalData = await fetch("https://api.example.com/posts").then((r) =>
|
|
204
|
+
r.json(),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const items = externalData.map((post) => ({
|
|
208
|
+
id: post.id,
|
|
209
|
+
slug: post.slug,
|
|
210
|
+
fieldData: { title: post.title, content: post.body },
|
|
211
|
+
}));
|
|
212
|
+
|
|
213
|
+
await collection.addItems(items);
|
|
214
|
+
|
|
215
|
+
// Remove items no longer in external source
|
|
216
|
+
const newIds = new Set(items.map((i) => i.id));
|
|
217
|
+
const toRemove = [...existingIds].filter((id) => !newIds.has(id));
|
|
218
|
+
if (toRemove.length) await collection.removeItems(toRemove);
|
|
219
|
+
|
|
220
|
+
await collection.setPluginData("lastSync", new Date().toISOString());
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### Field Types
|
|
224
|
+
|
|
225
|
+
`boolean`, `color`, `number`, `string`, `formattedText` (HTML), `image`, `file`, `link`, `date`, `enum`, `collectionReference`, `multiCollectionReference`, `array` (galleries)
|
|
226
|
+
|
|
227
|
+
#### Updating Collection Items
|
|
228
|
+
|
|
229
|
+
```js
|
|
230
|
+
// Add or update items (if id matches existing item, it updates)
|
|
231
|
+
await collection.addItems([
|
|
232
|
+
{
|
|
233
|
+
id: "new-item-1",
|
|
234
|
+
slug: "hello-world",
|
|
235
|
+
fieldData: { titleFieldId: "Hello World" },
|
|
236
|
+
},
|
|
237
|
+
]);
|
|
238
|
+
|
|
239
|
+
// Remove items
|
|
240
|
+
await collection.removeItems(["item-id-1", "item-id-2"]);
|
|
241
|
+
|
|
242
|
+
// Reorder items
|
|
243
|
+
const ids = items.map((i) => i.id).reverse();
|
|
244
|
+
await collection.setItemOrder(ids);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### Working with CollectionItem
|
|
248
|
+
|
|
249
|
+
```js
|
|
250
|
+
const items = await collection.getItems();
|
|
251
|
+
const item = items[0];
|
|
252
|
+
|
|
253
|
+
// Update item attributes
|
|
254
|
+
await item.setAttributes({ slug: "new-slug" });
|
|
255
|
+
|
|
256
|
+
// Navigate to item in Framer UI
|
|
257
|
+
await item.navigateTo();
|
|
258
|
+
|
|
259
|
+
// Remove item
|
|
260
|
+
await item.remove();
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Working with Images
|
|
264
|
+
|
|
265
|
+
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 project-scoped skill.
|
|
266
|
+
|
|
267
|
+
There are three sources you'll typically pull URLs from:
|
|
268
|
+
|
|
269
|
+
**Stock photography.** Use `framer.queryImagesForAgent` to source candidates and stash the URL you want:
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
const { results } = await framer.queryImagesForAgent({
|
|
273
|
+
source: "unsplash",
|
|
274
|
+
query: "snow-capped mountains",
|
|
275
|
+
count: 4,
|
|
276
|
+
orientation: "landscape",
|
|
277
|
+
});
|
|
278
|
+
state.heroUrl = results[0].url;
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**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:
|
|
282
|
+
|
|
283
|
+
```js
|
|
284
|
+
state.heroUrl = (await framer.uploadImage({
|
|
285
|
+
image: "https://example.com/hero.png",
|
|
286
|
+
altText: "Mountain range at sunset",
|
|
287
|
+
})).url;
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**An image already on the canvas.** Read the node and reuse its existing image URL:
|
|
291
|
+
|
|
292
|
+
```js
|
|
293
|
+
const node = await framer.getNodeForAgent({ id: "<image-node-id>" });
|
|
294
|
+
state.heroUrl = node.attributes.fill;
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
For inline SVGs, use the plugin method directly:
|
|
298
|
+
|
|
299
|
+
```js
|
|
300
|
+
await framer.addSVG({
|
|
301
|
+
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"><circle cx="10" cy="10" r="8"/></svg>',
|
|
302
|
+
name: "circle.svg",
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Code Components
|
|
307
|
+
|
|
308
|
+
Code components are custom React components that run inside Framer. Use them when you need behavior that Framer's canvas doesn't support:
|
|
309
|
+
|
|
310
|
+
- **Custom interactivity** — drag-to-reorder, gesture-driven animations, games, form validation
|
|
311
|
+
- **External data** — fetching from APIs, rendering dynamic content, real-time updates
|
|
312
|
+
- **Complex logic** — state machines, calculations, conditional rendering beyond simple variants
|
|
313
|
+
- **Third-party libraries** — maps, charts, video players, rich text editors
|
|
314
|
+
- **Canvas/WebGL** — custom drawing, 3D rendering, generative art
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
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.
|
|
318
|
+
|
|
319
|
+
Before writing code components, load the `framer-code-components` skill.
|
|
320
|
+
|
|
321
|
+
#### Creation
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
state.codeFile = await framer.createCodeFile("Badge.tsx", code);
|
|
325
|
+
state.diagnostics = await state.codeFile.typecheck();
|
|
326
|
+
console.log(state.diagnostics);
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
```js
|
|
330
|
+
state.component = state.codeFile.exports.find(
|
|
331
|
+
(exportItem) => exportItem.type === "component" && exportItem.isDefaultExport,
|
|
332
|
+
);
|
|
333
|
+
await framer.applyAgentChanges(
|
|
334
|
+
`+ComponentInstanceNode badge parent="wrapper" component="${state.component.componentId}"; SET badge width=120 height=48;`,
|
|
335
|
+
{ pagePath: "/" },
|
|
336
|
+
);
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### Editing
|
|
340
|
+
|
|
341
|
+
```js
|
|
342
|
+
const codeFile = await framer.getCodeFile("Badge.tsx");
|
|
343
|
+
state.updatedCodeFile = await codeFile.setFileContent(code);
|
|
344
|
+
state.diagnostics = await state.updatedCodeFile.typecheck();
|
|
345
|
+
console.log(state.diagnostics);
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
For large files, write `codeFile.content` to disk:
|
|
349
|
+
|
|
350
|
+
```js
|
|
351
|
+
const fs = require("fs");
|
|
352
|
+
|
|
353
|
+
state.codeFile = await framer.getCodeFile("Badge.tsx");
|
|
354
|
+
state.localCodePath = "{{FRAMER_TEMPORARY_DIR}}/Badge.tsx";
|
|
355
|
+
fs.writeFileSync(state.localCodePath, state.codeFile.content);
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Update the file with your patch tools, then load it back into Framer:
|
|
359
|
+
|
|
360
|
+
```js
|
|
361
|
+
const fs = require("fs");
|
|
362
|
+
const code = fs.readFileSync(state.localCodePath, "utf8");
|
|
363
|
+
state.updatedCodeFile = await state.codeFile.setFileContent(code);
|
|
364
|
+
state.diagnostics = await state.updatedCodeFile.typecheck();
|
|
365
|
+
console.log(state.diagnostics);
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Storing Data
|
|
369
|
+
|
|
370
|
+
Store metadata on nodes or globally in the project.
|
|
371
|
+
|
|
372
|
+
```js
|
|
373
|
+
// Store global project data
|
|
374
|
+
await framer.setPluginData("myKey", "myValue");
|
|
375
|
+
const value = await framer.getPluginData("myKey");
|
|
376
|
+
|
|
377
|
+
// Store data on a node
|
|
378
|
+
await node.setPluginData("processed", "true");
|
|
379
|
+
const nodeData = await node.getPluginData("processed");
|
|
380
|
+
|
|
381
|
+
// List all keys
|
|
382
|
+
const keys = await framer.getPluginDataKeys();
|
|
383
|
+
const nodeKeys = await node.getPluginDataKeys();
|
|
384
|
+
|
|
385
|
+
// Delete data (set to null)
|
|
386
|
+
await framer.setPluginData("myKey", null);
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Localization
|
|
390
|
+
|
|
391
|
+
```js
|
|
392
|
+
// Get all locales
|
|
393
|
+
const locales = await framer.getLocales();
|
|
394
|
+
const defaultLocale = await framer.getDefaultLocale();
|
|
395
|
+
|
|
396
|
+
// Get localization groups (pages, CMS items with translations)
|
|
397
|
+
const groups = await framer.getLocalizationGroups();
|
|
398
|
+
|
|
399
|
+
// Update translations
|
|
400
|
+
const french = locales.find((l) => l.code === "fr");
|
|
401
|
+
await framer.setLocalizationData({
|
|
402
|
+
valuesBySource: {
|
|
403
|
+
[sourceId]: {
|
|
404
|
+
[french.id]: { action: "set", value: "Bonjour" },
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Known Limitations
|
|
411
|
+
|
|
412
|
+
- **Pages**: Cannot change the path of a page
|
|
413
|
+
- **Code overrides**: Cannot assign overrides to nodes
|
|
414
|
+
- **Analytics**: No APIs exist for accessing analytics data
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
Below is documentation for agent-specific methods and guidelines on how to work on projects.
|
|
419
|
+
|
|
420
|
+
{{AGENT_PROMPT}}
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
Below is the current project context:
|
|
425
|
+
|
|
426
|
+
{{AGENT_CONTEXT}}
|