framer-dalton 0.0.24 → 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 +107 -26
- package/dist/start-relay-server.js +3 -2
- package/docs/skills/framer-code-components.md +1 -1
- package/docs/skills/framer-project.md +426 -0
- package/docs/skills/framer.md +6 -383
- package/package.json +2 -2
- package/docs/skills/framer-canvas-editing-project.md +0 -49
package/README.md
CHANGED
|
@@ -16,14 +16,14 @@ That installs skills into `~/.agents/skills` and `~/.claude/skills`.
|
|
|
16
16
|
|
|
17
17
|
The CLI and the installed skills are meant to work together. There are three skills:
|
|
18
18
|
|
|
19
|
-
- `framer` - the base skill that explains
|
|
19
|
+
- `framer` - the base meta skill that explains setup, project selection, and session creation.
|
|
20
20
|
- `framer-code-components` - explains specific prompts for how to write code components and provides examples.
|
|
21
|
-
- `framer-
|
|
21
|
+
- `framer-project-<project id>` - a dynamically-created skill for interacting with a specific project.
|
|
22
22
|
|
|
23
|
-
1. Running `npx framer-dalton@latest setup` will install the base `framer` and `framer-code-
|
|
24
|
-
2. The frontmatter of `framer-code-components` tells agents to always load `framer` first.
|
|
23
|
+
1. Running `npx framer-dalton@latest setup` will install the base `framer` and `framer-code-components` skills.
|
|
24
|
+
2. The frontmatter of `framer-code-components` tells agents to always load `framer` and the generated project skill first.
|
|
25
25
|
3. The frontmatter of `framer` tells agents to run `npx framer-dalton@latest setup` BEFORE loading the skill. This command will auto-update the cli and update the skill files.
|
|
26
|
-
4. Creating a new session for a project will create a `framer-
|
|
26
|
+
4. Creating a new session for a project will create a `framer-project-<project id>` file. This file contains the latest agent system prompt from `framer.getAgentSystemPrompt` and the latest project context from `framer.getAgentContext`. Agents must load this project-specific skill immediately after creating a session before doing any connected-project work.
|
|
27
27
|
|
|
28
28
|
## Local Development
|
|
29
29
|
|
package/dist/cli.js
CHANGED
|
@@ -8,10 +8,11 @@ import { spawn, execFile } from 'child_process';
|
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
import os from 'os';
|
|
10
10
|
import { ErrorCode, FramerAPIError } from 'framer-api';
|
|
11
|
+
import net from 'net';
|
|
11
12
|
import { fileURLToPath } from 'url';
|
|
12
13
|
import { createTRPCClient, httpLink } from '@trpc/client';
|
|
13
14
|
|
|
14
|
-
/* @framer/ai CLI v0.0.
|
|
15
|
+
/* @framer/ai CLI v0.0.25 */
|
|
15
16
|
var __defProp = Object.defineProperty;
|
|
16
17
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
17
18
|
|
|
@@ -329,7 +330,7 @@ __name(markTelemetryNoticeShown, "markTelemetryNoticeShown");
|
|
|
329
330
|
// src/version.ts
|
|
330
331
|
var VERSION = (
|
|
331
332
|
// typeof is used to ensure this can be used just via tsx or node etc. without build
|
|
332
|
-
"0.0.
|
|
333
|
+
"0.0.25"
|
|
333
334
|
);
|
|
334
335
|
var trackingEndpoint = "https://events.framer.com/track";
|
|
335
336
|
var inProgressTrackings = /* @__PURE__ */ new Set();
|
|
@@ -1803,6 +1804,12 @@ var types = {
|
|
|
1803
1804
|
kind: "interface",
|
|
1804
1805
|
references: ["CodeExportCommon"],
|
|
1805
1806
|
members: [
|
|
1807
|
+
{
|
|
1808
|
+
name: "componentId",
|
|
1809
|
+
type: "string",
|
|
1810
|
+
description: "Component ID for this code file export.",
|
|
1811
|
+
optional: false
|
|
1812
|
+
},
|
|
1806
1813
|
{
|
|
1807
1814
|
name: "insertURL",
|
|
1808
1815
|
type: "string",
|
|
@@ -4478,7 +4485,7 @@ var types = {
|
|
|
4478
4485
|
name: "HostnameType",
|
|
4479
4486
|
description: "",
|
|
4480
4487
|
kind: "alias",
|
|
4481
|
-
alias: '"default" | "custom" | "version"',
|
|
4488
|
+
alias: '"default" | "custom" | "version" | "branch"',
|
|
4482
4489
|
references: []
|
|
4483
4490
|
},
|
|
4484
4491
|
imageassetdata: {
|
|
@@ -6959,7 +6966,7 @@ var types = {
|
|
|
6959
6966
|
},
|
|
6960
6967
|
{
|
|
6961
6968
|
name: "unstable_getCodeFile",
|
|
6962
|
-
type: "(
|
|
6969
|
+
type: "(idOrPath: string) => Promise<CodeFileData | null>",
|
|
6963
6970
|
description: "@deprecated",
|
|
6964
6971
|
optional: false
|
|
6965
6972
|
},
|
|
@@ -7019,7 +7026,7 @@ var types = {
|
|
|
7019
7026
|
},
|
|
7020
7027
|
{
|
|
7021
7028
|
name: "getCodeFile",
|
|
7022
|
-
type: "(
|
|
7029
|
+
type: "(idOrPath: string) => Promise<CodeFileData | null>",
|
|
7023
7030
|
description: "",
|
|
7024
7031
|
optional: false
|
|
7025
7032
|
},
|
|
@@ -7760,9 +7767,15 @@ var types = {
|
|
|
7760
7767
|
{
|
|
7761
7768
|
name: "model",
|
|
7762
7769
|
type: "string",
|
|
7763
|
-
description: "",
|
|
7770
|
+
description: "Supervisor model id, optionally with eval-style reasoning effort suffix, e.g. `anthropic/claude-opus-4.6=low`.",
|
|
7764
7771
|
optional: false
|
|
7765
7772
|
},
|
|
7773
|
+
{
|
|
7774
|
+
name: "userAgentModel",
|
|
7775
|
+
type: "string",
|
|
7776
|
+
description: "Inner Framer Agent model override. Defaults to `model` when omitted. Supports the same `model=effort` syntax.",
|
|
7777
|
+
optional: true
|
|
7778
|
+
},
|
|
7766
7779
|
{
|
|
7767
7780
|
name: "pagePath",
|
|
7768
7781
|
type: "string",
|
|
@@ -13514,8 +13527,8 @@ var methodsByCategory = {
|
|
|
13514
13527
|
{
|
|
13515
13528
|
name: "getCodeFile",
|
|
13516
13529
|
category: "framer",
|
|
13517
|
-
signature: "getCodeFile(
|
|
13518
|
-
description: 'Get a specific code file by its ID.\n\n@param
|
|
13530
|
+
signature: "getCodeFile(idOrPath: string): Promise<CodeFile | null>",
|
|
13531
|
+
description: 'Get a specific code file by its ID or file path.\n\n@param idOrPath - The unique identifier (e.g. `"codeFile/AbcD_23"`) or\nfile path (e.g. `"Button.tsx"` or `"overrides/withAnalytics.tsx"`) of the\ncode file.\n@returns The CodeFile instance or `null` if not found.\n\n@example\n```ts\n// Look up by ID\nconst codeFile = await framer.getCodeFile("codeFile/AbcD_23")\n\n// Look up by file path\nconst codeFile = await framer.getCodeFile("Button.tsx")\n```\n@category code-files',
|
|
13519
13532
|
references: ["CodeFile"]
|
|
13520
13533
|
},
|
|
13521
13534
|
{
|
|
@@ -16225,6 +16238,7 @@ __name(readRelayToken, "readRelayToken");
|
|
|
16225
16238
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
16226
16239
|
var __dirname$1 = path7.dirname(__filename$1);
|
|
16227
16240
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
|
|
16241
|
+
var WAIT_TIMEOUT_SECONDS = 5;
|
|
16228
16242
|
var client = createTRPCClient({
|
|
16229
16243
|
links: [
|
|
16230
16244
|
httpLink({
|
|
@@ -16252,6 +16266,43 @@ function sleep(ms) {
|
|
|
16252
16266
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
16253
16267
|
}
|
|
16254
16268
|
__name(sleep, "sleep");
|
|
16269
|
+
var PORT_PROBE_TIMEOUT_MS = 1e3;
|
|
16270
|
+
function probePort(port) {
|
|
16271
|
+
return new Promise((resolve) => {
|
|
16272
|
+
const socket = net.createConnection({ host: "127.0.0.1", port });
|
|
16273
|
+
const done = /* @__PURE__ */ __name((result) => {
|
|
16274
|
+
socket.removeAllListeners();
|
|
16275
|
+
socket.destroy();
|
|
16276
|
+
resolve(result);
|
|
16277
|
+
}, "done");
|
|
16278
|
+
socket.setTimeout(PORT_PROBE_TIMEOUT_MS);
|
|
16279
|
+
socket.once("connect", () => done("in_use"));
|
|
16280
|
+
socket.once("timeout", () => {
|
|
16281
|
+
debug("relay", `port ${port} probe timed out`);
|
|
16282
|
+
done("unknown");
|
|
16283
|
+
});
|
|
16284
|
+
socket.once("error", (err) => {
|
|
16285
|
+
if (err.code === "ECONNREFUSED") {
|
|
16286
|
+
done("free");
|
|
16287
|
+
} else {
|
|
16288
|
+
debug("relay", `port ${port} probe error: ${err.code ?? err.message}`);
|
|
16289
|
+
done("unknown");
|
|
16290
|
+
}
|
|
16291
|
+
});
|
|
16292
|
+
});
|
|
16293
|
+
}
|
|
16294
|
+
__name(probePort, "probePort");
|
|
16295
|
+
async function waitForPortFree(port, timeoutMs) {
|
|
16296
|
+
const deadline = Date.now() + timeoutMs;
|
|
16297
|
+
while (Date.now() < deadline) {
|
|
16298
|
+
if (await probePort(port) === "free") {
|
|
16299
|
+
return true;
|
|
16300
|
+
}
|
|
16301
|
+
await sleep(100);
|
|
16302
|
+
}
|
|
16303
|
+
return false;
|
|
16304
|
+
}
|
|
16305
|
+
__name(waitForPortFree, "waitForPortFree");
|
|
16255
16306
|
function compareVersions(v1, v2) {
|
|
16256
16307
|
const parts1 = v1.split(".").map(Number);
|
|
16257
16308
|
const parts2 = v2.split(".").map(Number);
|
|
@@ -16287,8 +16338,20 @@ async function ensureRelayServerRunning(options = {}) {
|
|
|
16287
16338
|
debug("relay", "shutting down old server...");
|
|
16288
16339
|
try {
|
|
16289
16340
|
await client.shutdown.mutate();
|
|
16290
|
-
|
|
16291
|
-
|
|
16341
|
+
} catch (err) {
|
|
16342
|
+
debug(
|
|
16343
|
+
"relay",
|
|
16344
|
+
`shutdown RPC failed: ${err instanceof Error ? err.message : String(err)}`
|
|
16345
|
+
);
|
|
16346
|
+
}
|
|
16347
|
+
const freed = await waitForPortFree(
|
|
16348
|
+
RELAY_PORT,
|
|
16349
|
+
WAIT_TIMEOUT_SECONDS * 1e3
|
|
16350
|
+
);
|
|
16351
|
+
if (!freed) {
|
|
16352
|
+
throw new Error(
|
|
16353
|
+
`Old relay server (v${serverVersion}) is still listening on 127.0.0.1:${RELAY_PORT} after ${WAIT_TIMEOUT_SECONDS}s; please kill it manually`
|
|
16354
|
+
);
|
|
16292
16355
|
}
|
|
16293
16356
|
debug("relay", "old server shut down");
|
|
16294
16357
|
} else {
|
|
@@ -16317,7 +16380,7 @@ async function ensureRelayServerRunning(options = {}) {
|
|
|
16317
16380
|
"relay",
|
|
16318
16381
|
`readiness poll ${attempt + 1}/10: version=${newVersion ?? "not ready"}`
|
|
16319
16382
|
);
|
|
16320
|
-
if (newVersion) {
|
|
16383
|
+
if (newVersion === VERSION) {
|
|
16321
16384
|
logger?.log("Relay server started successfully");
|
|
16322
16385
|
return;
|
|
16323
16386
|
}
|
|
@@ -16380,26 +16443,27 @@ function renderTemplate(template, values) {
|
|
|
16380
16443
|
return rendered;
|
|
16381
16444
|
}
|
|
16382
16445
|
__name(renderTemplate, "renderTemplate");
|
|
16383
|
-
var
|
|
16446
|
+
var PROJECT_SKILL_PREFIX = "framer-project-";
|
|
16447
|
+
var LEGACY_CANVAS_EDITING_SKILL_PREFIX = "framer-canvas-editing-project-";
|
|
16384
16448
|
function toSafeProjectId(projectId) {
|
|
16385
16449
|
return projectId.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
16386
16450
|
}
|
|
16387
16451
|
__name(toSafeProjectId, "toSafeProjectId");
|
|
16388
|
-
function
|
|
16389
|
-
const skillName = `${
|
|
16390
|
-
const template = readSkillDoc("framer-
|
|
16452
|
+
function buildProjectSkill(projectId, agentContext, agentPrompt) {
|
|
16453
|
+
const skillName = `${PROJECT_SKILL_PREFIX}${toSafeProjectId(projectId)}`;
|
|
16454
|
+
const template = readSkillDoc("framer-project.md");
|
|
16391
16455
|
const content = `${renderTemplate(template, {
|
|
16392
16456
|
SKILL_NAME: skillName,
|
|
16393
16457
|
PROJECT_ID: projectId,
|
|
16394
16458
|
GENERATED_AT: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16395
|
-
|
|
16459
|
+
AGENT_PROMPT: agentPrompt.trimEnd(),
|
|
16396
16460
|
AGENT_CONTEXT: agentContext.trimEnd(),
|
|
16397
16461
|
FRAMER_TEMPORARY_DIR
|
|
16398
16462
|
})}
|
|
16399
16463
|
`;
|
|
16400
16464
|
return { skillName, content };
|
|
16401
16465
|
}
|
|
16402
|
-
__name(
|
|
16466
|
+
__name(buildProjectSkill, "buildProjectSkill");
|
|
16403
16467
|
function writeSkill(root, skillName, content) {
|
|
16404
16468
|
fs7.mkdirSync(root, { recursive: true });
|
|
16405
16469
|
const rootStat = fs7.statSync(root);
|
|
@@ -16417,6 +16481,17 @@ function writeSkill(root, skillName, content) {
|
|
|
16417
16481
|
return filePath;
|
|
16418
16482
|
}
|
|
16419
16483
|
__name(writeSkill, "writeSkill");
|
|
16484
|
+
function cleanupLegacyCanvasEditingSkills(skillRoots = getDefaultSkillRoots()) {
|
|
16485
|
+
for (const root of skillRoots) {
|
|
16486
|
+
if (!fs7.existsSync(root)) continue;
|
|
16487
|
+
for (const entry of fs7.readdirSync(root)) {
|
|
16488
|
+
if (!entry.startsWith(LEGACY_CANVAS_EDITING_SKILL_PREFIX)) continue;
|
|
16489
|
+
const skillDir = path7.join(root, entry);
|
|
16490
|
+
fs7.rmSync(skillDir, { recursive: true, force: true, maxRetries: 2 });
|
|
16491
|
+
}
|
|
16492
|
+
}
|
|
16493
|
+
}
|
|
16494
|
+
__name(cleanupLegacyCanvasEditingSkills, "cleanupLegacyCanvasEditingSkills");
|
|
16420
16495
|
function getDefaultSkillRoots() {
|
|
16421
16496
|
const home = os.homedir();
|
|
16422
16497
|
return [
|
|
@@ -16425,16 +16500,15 @@ function getDefaultSkillRoots() {
|
|
|
16425
16500
|
];
|
|
16426
16501
|
}
|
|
16427
16502
|
__name(getDefaultSkillRoots, "getDefaultSkillRoots");
|
|
16428
|
-
function cleanupStaleSkills(activeProjectIds, skillRoots) {
|
|
16429
|
-
const roots = skillRoots ?? getDefaultSkillRoots();
|
|
16503
|
+
function cleanupStaleSkills(activeProjectIds, skillRoots = getDefaultSkillRoots()) {
|
|
16430
16504
|
const sanitizedActiveProjectIds = new Set(
|
|
16431
16505
|
[...activeProjectIds].map(toSafeProjectId)
|
|
16432
16506
|
);
|
|
16433
|
-
for (const root of
|
|
16507
|
+
for (const root of skillRoots) {
|
|
16434
16508
|
if (!fs7.existsSync(root)) continue;
|
|
16435
16509
|
for (const entry of fs7.readdirSync(root)) {
|
|
16436
|
-
if (!entry.startsWith(
|
|
16437
|
-
const sanitizedProjectId = entry.slice(
|
|
16510
|
+
if (!entry.startsWith(PROJECT_SKILL_PREFIX)) continue;
|
|
16511
|
+
const sanitizedProjectId = entry.slice(PROJECT_SKILL_PREFIX.length);
|
|
16438
16512
|
const isActive = sanitizedActiveProjectIds.has(sanitizedProjectId);
|
|
16439
16513
|
if (isActive) continue;
|
|
16440
16514
|
const skillDir = path7.join(root, entry);
|
|
@@ -16453,10 +16527,10 @@ function installSkills(options = { type: "base" }) {
|
|
|
16453
16527
|
}
|
|
16454
16528
|
];
|
|
16455
16529
|
if (options.type === "project") {
|
|
16456
|
-
const projectSkill =
|
|
16530
|
+
const projectSkill = buildProjectSkill(
|
|
16457
16531
|
options.projectId,
|
|
16458
16532
|
options.agentContext,
|
|
16459
|
-
options.
|
|
16533
|
+
options.agentPrompt
|
|
16460
16534
|
);
|
|
16461
16535
|
skills.push({
|
|
16462
16536
|
name: projectSkill.skillName,
|
|
@@ -16559,14 +16633,14 @@ __name(getAgentContext, "getAgentContext");
|
|
|
16559
16633
|
async function refreshSkillsFromSession(sessionId, projectId) {
|
|
16560
16634
|
try {
|
|
16561
16635
|
debug("skills", "fetching agent system prompt + context...");
|
|
16562
|
-
const [
|
|
16636
|
+
const [agentPrompt, agentContext] = await Promise.all([
|
|
16563
16637
|
getAgentSystemPrompt(sessionId),
|
|
16564
16638
|
getAgentContext(sessionId)
|
|
16565
16639
|
]);
|
|
16566
16640
|
debug("skills", "installing skills...");
|
|
16567
16641
|
installSkills({
|
|
16568
16642
|
type: "project",
|
|
16569
|
-
|
|
16643
|
+
agentPrompt,
|
|
16570
16644
|
projectId,
|
|
16571
16645
|
agentContext
|
|
16572
16646
|
});
|
|
@@ -16937,6 +17011,13 @@ program.command("setup").description(
|
|
|
16937
17011
|
"Install Framer skills into ~/.agents/skills and ~/.claude/skills"
|
|
16938
17012
|
).action(async () => {
|
|
16939
17013
|
try {
|
|
17014
|
+
try {
|
|
17015
|
+
cleanupLegacyCanvasEditingSkills();
|
|
17016
|
+
} catch (error) {
|
|
17017
|
+
printWarning(
|
|
17018
|
+
`Failed to clean up legacy canvas editing skills: ${formatErrorForUser(error)}`
|
|
17019
|
+
);
|
|
17020
|
+
}
|
|
16940
17021
|
const results = installSkills();
|
|
16941
17022
|
await waitForClaudeCodeSkillDiscovery();
|
|
16942
17023
|
printSetupSummary(results);
|
|
@@ -2,6 +2,7 @@ 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';
|
|
@@ -13,7 +14,7 @@ import { z } from 'zod';
|
|
|
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
|
|
@@ -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}}
|
package/docs/skills/framer.md
CHANGED
|
@@ -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
|
-
###
|
|
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-
|
|
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.
|
|
3
|
+
"version": "0.0.25",
|
|
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.17.0",
|
|
24
24
|
"@trpc/server": "^11.17.0",
|
|
25
25
|
"commander": "^12.1.0",
|
|
26
|
-
"framer-api": "0.1.
|
|
26
|
+
"framer-api": "0.1.11",
|
|
27
27
|
"zod": "^4.3.6"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
@@ -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}}
|