framer-dalton 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +503 -98
- package/dist/start-relay-server.js +18 -5
- package/docs/skills/framer-canvas-editing-project.md +1 -0
- package/docs/skills/framer.md +19 -9
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path3 from 'path';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
+
import crypto from 'crypto';
|
|
5
|
+
import http from 'http';
|
|
6
|
+
import { spawn, execFile } from 'child_process';
|
|
4
7
|
import fs2 from 'fs';
|
|
5
8
|
import os from 'os';
|
|
6
|
-
import {
|
|
9
|
+
import { z } from 'zod';
|
|
7
10
|
import { fileURLToPath } from 'url';
|
|
8
11
|
import { createTRPCClient, httpLink } from '@trpc/client';
|
|
9
12
|
|
|
10
|
-
/* @framer/ai CLI v0.0.
|
|
13
|
+
/* @framer/ai CLI v0.0.9 */
|
|
11
14
|
var __defProp = Object.defineProperty;
|
|
12
15
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
16
|
+
function openUrl(url) {
|
|
17
|
+
const platform = process.platform;
|
|
18
|
+
let cmd;
|
|
19
|
+
let args;
|
|
20
|
+
if (platform === "darwin") {
|
|
21
|
+
cmd = "open";
|
|
22
|
+
args = [url];
|
|
23
|
+
} else if (platform === "win32") {
|
|
24
|
+
cmd = "cmd";
|
|
25
|
+
args = ["/c", "start", "", url];
|
|
26
|
+
} else {
|
|
27
|
+
cmd = "xdg-open";
|
|
28
|
+
args = [url];
|
|
29
|
+
}
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
const child = execFile(cmd, args);
|
|
32
|
+
child.on("error", () => resolve(false));
|
|
33
|
+
child.on("exit", (code) => resolve(code === 0));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
__name(openUrl, "openUrl");
|
|
13
37
|
function getConfigDir() {
|
|
14
38
|
if (process.env.XDG_CONFIG_HOME) {
|
|
15
39
|
return path3.join(process.env.XDG_CONFIG_HOME, "framer");
|
|
@@ -20,32 +44,92 @@ function getConfigDir() {
|
|
|
20
44
|
return path3.join(os.homedir(), ".config", "framer");
|
|
21
45
|
}
|
|
22
46
|
__name(getConfigDir, "getConfigDir");
|
|
23
|
-
function
|
|
47
|
+
function getProjectsConfigPath() {
|
|
48
|
+
return path3.join(getConfigDir(), "projects.json");
|
|
49
|
+
}
|
|
50
|
+
__name(getProjectsConfigPath, "getProjectsConfigPath");
|
|
51
|
+
function getLegacyCredentialsPath() {
|
|
24
52
|
return path3.join(getConfigDir(), "credentials.json");
|
|
25
53
|
}
|
|
26
|
-
__name(
|
|
27
|
-
|
|
28
|
-
|
|
54
|
+
__name(getLegacyCredentialsPath, "getLegacyCredentialsPath");
|
|
55
|
+
var ProjectsConfigSchema = z.object({
|
|
56
|
+
version: z.literal(2),
|
|
57
|
+
projects: z.record(
|
|
58
|
+
z.string(),
|
|
59
|
+
z.object({
|
|
60
|
+
apiKey: z.string(),
|
|
61
|
+
name: z.string().optional(),
|
|
62
|
+
lastUsedAt: z.string().optional()
|
|
63
|
+
})
|
|
64
|
+
)
|
|
65
|
+
});
|
|
66
|
+
var LegacyCredentialsSchema = z.record(z.string(), z.string());
|
|
67
|
+
function readJsonFile(filePath) {
|
|
68
|
+
if (!fs2.existsSync(filePath)) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
29
71
|
try {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
} catch {
|
|
72
|
+
return JSON.parse(fs2.readFileSync(filePath, "utf-8"));
|
|
73
|
+
} catch (_error) {
|
|
74
|
+
return null;
|
|
34
75
|
}
|
|
35
|
-
return {};
|
|
36
76
|
}
|
|
37
|
-
__name(
|
|
38
|
-
function
|
|
39
|
-
const credPath = getCredentialsPath();
|
|
77
|
+
__name(readJsonFile, "readJsonFile");
|
|
78
|
+
function ensureConfigDir() {
|
|
40
79
|
const configDir = getConfigDir();
|
|
41
80
|
if (!fs2.existsSync(configDir)) {
|
|
42
81
|
fs2.mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
43
82
|
}
|
|
44
|
-
fs2.writeFileSync(credPath, JSON.stringify(creds, null, " "), {
|
|
45
|
-
mode: 384
|
|
46
|
-
});
|
|
47
83
|
}
|
|
48
|
-
__name(
|
|
84
|
+
__name(ensureConfigDir, "ensureConfigDir");
|
|
85
|
+
function writeProjectsConfig(config) {
|
|
86
|
+
ensureConfigDir();
|
|
87
|
+
fs2.writeFileSync(
|
|
88
|
+
getProjectsConfigPath(),
|
|
89
|
+
JSON.stringify(config, null, " "),
|
|
90
|
+
{
|
|
91
|
+
mode: 384
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
__name(writeProjectsConfig, "writeProjectsConfig");
|
|
96
|
+
function readLegacyCredentials() {
|
|
97
|
+
const legacyPath = getLegacyCredentialsPath();
|
|
98
|
+
const parsed = readJsonFile(legacyPath);
|
|
99
|
+
const result = LegacyCredentialsSchema.safeParse(parsed);
|
|
100
|
+
if (!result.success) {
|
|
101
|
+
return {};
|
|
102
|
+
}
|
|
103
|
+
return result.data;
|
|
104
|
+
}
|
|
105
|
+
__name(readLegacyCredentials, "readLegacyCredentials");
|
|
106
|
+
function migrateLegacyCredentials() {
|
|
107
|
+
const legacyCreds = readLegacyCredentials();
|
|
108
|
+
const projects = {};
|
|
109
|
+
for (const [projectId, apiKey] of Object.entries(legacyCreds)) {
|
|
110
|
+
projects[projectId] = { apiKey };
|
|
111
|
+
}
|
|
112
|
+
const config = { version: 2, projects };
|
|
113
|
+
writeProjectsConfig(config);
|
|
114
|
+
fs2.rmSync(getLegacyCredentialsPath(), { force: true });
|
|
115
|
+
return config;
|
|
116
|
+
}
|
|
117
|
+
__name(migrateLegacyCredentials, "migrateLegacyCredentials");
|
|
118
|
+
function readProjectsConfig() {
|
|
119
|
+
const projectsPath = getProjectsConfigPath();
|
|
120
|
+
const parsed = readJsonFile(projectsPath);
|
|
121
|
+
if (parsed !== null) {
|
|
122
|
+
const result = ProjectsConfigSchema.safeParse(parsed);
|
|
123
|
+
if (result.success) {
|
|
124
|
+
return result.data;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (fs2.existsSync(getLegacyCredentialsPath())) {
|
|
128
|
+
return migrateLegacyCredentials();
|
|
129
|
+
}
|
|
130
|
+
return { version: 2, projects: {} };
|
|
131
|
+
}
|
|
132
|
+
__name(readProjectsConfig, "readProjectsConfig");
|
|
49
133
|
function extractProjectId(input) {
|
|
50
134
|
if (/^[a-zA-Z0-9]+$/.test(input)) {
|
|
51
135
|
return input;
|
|
@@ -54,20 +138,279 @@ function extractProjectId(input) {
|
|
|
54
138
|
if (match) {
|
|
55
139
|
return match[1];
|
|
56
140
|
}
|
|
141
|
+
const slugMatch = input.match(/^(?:.+--)?([a-zA-Z0-9]+)(?:-[a-zA-Z0-9]+)?$/);
|
|
142
|
+
if (slugMatch) {
|
|
143
|
+
return slugMatch[1];
|
|
144
|
+
}
|
|
57
145
|
return input;
|
|
58
146
|
}
|
|
59
147
|
__name(extractProjectId, "extractProjectId");
|
|
148
|
+
function listProjects() {
|
|
149
|
+
const config = readProjectsConfig();
|
|
150
|
+
return Object.entries(config.projects).map(([projectId, project2]) => ({
|
|
151
|
+
projectId,
|
|
152
|
+
...project2
|
|
153
|
+
})).sort((a, b) => {
|
|
154
|
+
const left = a.lastUsedAt ?? "";
|
|
155
|
+
const right = b.lastUsedAt ?? "";
|
|
156
|
+
return right.localeCompare(left);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
__name(listProjects, "listProjects");
|
|
60
160
|
function getApiKey(projectId) {
|
|
61
|
-
const
|
|
62
|
-
return
|
|
161
|
+
const config = readProjectsConfig();
|
|
162
|
+
return config.projects[projectId]?.apiKey ?? null;
|
|
63
163
|
}
|
|
64
164
|
__name(getApiKey, "getApiKey");
|
|
65
|
-
function
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
165
|
+
function saveProject(project2) {
|
|
166
|
+
const config = readProjectsConfig();
|
|
167
|
+
config.projects[project2.projectId] = {
|
|
168
|
+
apiKey: project2.apiKey,
|
|
169
|
+
name: project2.name,
|
|
170
|
+
lastUsedAt: project2.lastUsedAt
|
|
171
|
+
};
|
|
172
|
+
writeProjectsConfig(config);
|
|
173
|
+
}
|
|
174
|
+
__name(saveProject, "saveProject");
|
|
175
|
+
function clearApiKey(projectId) {
|
|
176
|
+
const config = readProjectsConfig();
|
|
177
|
+
if (!(projectId in config.projects)) return false;
|
|
178
|
+
delete config.projects[projectId];
|
|
179
|
+
writeProjectsConfig(config);
|
|
180
|
+
return true;
|
|
69
181
|
}
|
|
70
|
-
__name(
|
|
182
|
+
__name(clearApiKey, "clearApiKey");
|
|
183
|
+
|
|
184
|
+
// src/utils.ts
|
|
185
|
+
function formatError(error) {
|
|
186
|
+
if (error instanceof Error) {
|
|
187
|
+
return error.message;
|
|
188
|
+
}
|
|
189
|
+
return String(error);
|
|
190
|
+
}
|
|
191
|
+
__name(formatError, "formatError");
|
|
192
|
+
function printJson(value) {
|
|
193
|
+
console.log(JSON.stringify(value, null, 2));
|
|
194
|
+
}
|
|
195
|
+
__name(printJson, "printJson");
|
|
196
|
+
function print(message) {
|
|
197
|
+
console.log(message);
|
|
198
|
+
}
|
|
199
|
+
__name(print, "print");
|
|
200
|
+
function printError(message) {
|
|
201
|
+
console.error(message);
|
|
202
|
+
}
|
|
203
|
+
__name(printError, "printError");
|
|
204
|
+
|
|
205
|
+
// src/auth-callback.ts
|
|
206
|
+
var TIMEOUT_MS = 3e5;
|
|
207
|
+
var themes = {
|
|
208
|
+
dark: {
|
|
209
|
+
pageBackground: "rgb(17, 17, 17)",
|
|
210
|
+
modalBackground: "rgb(17, 17, 17)",
|
|
211
|
+
modalBorder: "rgba(255, 255, 255, 0.07)",
|
|
212
|
+
titleColor: "#fff",
|
|
213
|
+
textColor: "rgb(102, 102, 102)",
|
|
214
|
+
separatorColor: "rgba(255, 255, 255, 0.07)",
|
|
215
|
+
buttonBackground: "#333",
|
|
216
|
+
buttonBackgroundHover: "#3a3a3a",
|
|
217
|
+
buttonText: "#fff",
|
|
218
|
+
shadow: "0px 10px 30px 0px rgba(0, 0, 0, 0.15)"
|
|
219
|
+
},
|
|
220
|
+
light: {
|
|
221
|
+
pageBackground: "#eee",
|
|
222
|
+
modalBackground: "#fff",
|
|
223
|
+
modalBorder: "rgba(0, 0, 0, 0.07)",
|
|
224
|
+
titleColor: "#333",
|
|
225
|
+
textColor: "rgb(102, 102, 102)",
|
|
226
|
+
separatorColor: "rgba(0, 0, 0, 0.07)",
|
|
227
|
+
buttonBackground: "rgba(0, 0, 0, 0.05)",
|
|
228
|
+
buttonBackgroundHover: "rgba(0, 0, 0, 0.08)",
|
|
229
|
+
buttonText: "#333",
|
|
230
|
+
shadow: "0px 10px 30px 0px rgba(0, 0, 0, 0.15)"
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
function htmlPage(opts) {
|
|
234
|
+
const t = themes[opts.theme];
|
|
235
|
+
return `<!DOCTYPE html>
|
|
236
|
+
<html lang="en">
|
|
237
|
+
<head>
|
|
238
|
+
<meta charset="utf-8">
|
|
239
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
240
|
+
<title>${opts.title}</title>
|
|
241
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
242
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
243
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@500;600&display=swap">
|
|
244
|
+
<style>
|
|
245
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
246
|
+
body{font-family:"Inter",system-ui,-apple-system,sans-serif;font-feature-settings:"cv01" 1,"cv05" 1,"cv09" 1,"cv11" 1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:flex;justify-content:center;align-items:center;min-height:100vh;background:${t.pageBackground}}
|
|
247
|
+
.modal{width:260px;padding:0 10px;border-radius:18px;background:${t.modalBackground};border:1px solid ${t.modalBorder};box-shadow:${t.shadow}}
|
|
248
|
+
.header{display:flex;align-items:center;height:50px;border-bottom:1px solid ${t.separatorColor};color:${t.titleColor};font-size:12px;font-weight:600;line-height:1}
|
|
249
|
+
.content{padding:10px 0}
|
|
250
|
+
.text{color:${t.textColor};font-size:12px;font-weight:500;line-height:1.5;text-wrap:balance}
|
|
251
|
+
.footer{padding:10px 0;border-top:1px solid ${t.separatorColor}}
|
|
252
|
+
.footer button{display:block;width:100%;height:30px;border:none;border-radius:8px;background:${t.buttonBackground};color:${t.buttonText};font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}
|
|
253
|
+
.footer button:hover{background:${t.buttonBackgroundHover}}
|
|
254
|
+
</style>
|
|
255
|
+
</head>
|
|
256
|
+
<body>
|
|
257
|
+
<div class="modal">
|
|
258
|
+
<div class="header">${opts.heading}</div>
|
|
259
|
+
<div class="content"><span class="text">${opts.message}</span></div>
|
|
260
|
+
</div>
|
|
261
|
+
</body>
|
|
262
|
+
</html>`;
|
|
263
|
+
}
|
|
264
|
+
__name(htmlPage, "htmlPage");
|
|
265
|
+
function successHtml(theme) {
|
|
266
|
+
return htmlPage({
|
|
267
|
+
title: "Framer \u2014 Agent Approved",
|
|
268
|
+
heading: "Agent Approved",
|
|
269
|
+
message: "Your local agent now has edit access to the project. You can close this tab and continue with the local agent.",
|
|
270
|
+
theme
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
__name(successHtml, "successHtml");
|
|
274
|
+
function deniedHtml(theme) {
|
|
275
|
+
return htmlPage({
|
|
276
|
+
title: "Framer \u2014 Authorization Denied",
|
|
277
|
+
heading: "Authorization Denied",
|
|
278
|
+
message: "Agent access was denied. You can close this tab.",
|
|
279
|
+
theme
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
__name(deniedHtml, "deniedHtml");
|
|
283
|
+
function errorHtml(theme) {
|
|
284
|
+
return htmlPage({
|
|
285
|
+
title: "Framer \u2014 Authorization Failed",
|
|
286
|
+
heading: "Authorization Failed",
|
|
287
|
+
message: "Missing or invalid parameters. Please try again.",
|
|
288
|
+
theme
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
__name(errorHtml, "errorHtml");
|
|
292
|
+
function parseTheme(value) {
|
|
293
|
+
return value === "light" ? "light" : "dark";
|
|
294
|
+
}
|
|
295
|
+
__name(parseTheme, "parseTheme");
|
|
296
|
+
async function acquireKeyFromBrowser(projectId) {
|
|
297
|
+
const state = crypto.randomBytes(16).toString("hex");
|
|
298
|
+
return new Promise((resolve, reject) => {
|
|
299
|
+
let pendingResult = null;
|
|
300
|
+
const server = http.createServer((req, res) => {
|
|
301
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
302
|
+
if (url.pathname === "/done") {
|
|
303
|
+
const theme2 = parseTheme(url.searchParams.get("theme"));
|
|
304
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
305
|
+
res.end(successHtml(theme2));
|
|
306
|
+
if (pendingResult) {
|
|
307
|
+
const { apiKey: apiKey2 } = pendingResult;
|
|
308
|
+
pendingResult = null;
|
|
309
|
+
cleanup();
|
|
310
|
+
print("\u2713 Agent authorized");
|
|
311
|
+
resolve(apiKey2);
|
|
312
|
+
}
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (url.pathname === "/error") {
|
|
316
|
+
const theme2 = parseTheme(url.searchParams.get("theme"));
|
|
317
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
318
|
+
res.end(errorHtml(theme2));
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (url.pathname !== "/callback") {
|
|
322
|
+
res.writeHead(404);
|
|
323
|
+
res.end();
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const theme = parseTheme(url.searchParams.get("theme"));
|
|
327
|
+
const error = url.searchParams.get("error");
|
|
328
|
+
if (error === "denied") {
|
|
329
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
330
|
+
res.end(deniedHtml(theme));
|
|
331
|
+
cleanup();
|
|
332
|
+
reject(new Error("Authorization denied by user."));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const apiKey = url.searchParams.get("apiKey");
|
|
336
|
+
const returnedState = url.searchParams.get("state");
|
|
337
|
+
if (!apiKey || returnedState !== state) {
|
|
338
|
+
res.writeHead(302, { Location: `/error?theme=${theme}` });
|
|
339
|
+
res.end();
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
pendingResult = { apiKey, theme };
|
|
343
|
+
res.writeHead(302, { Location: `/done?theme=${theme}` });
|
|
344
|
+
res.end();
|
|
345
|
+
});
|
|
346
|
+
const POLL_INTERVAL_MS = 1e3;
|
|
347
|
+
const pollTimer = setInterval(() => {
|
|
348
|
+
const apiKey = getApiKey(projectId);
|
|
349
|
+
if (apiKey) {
|
|
350
|
+
cleanup();
|
|
351
|
+
print("\u2713 API key detected from config");
|
|
352
|
+
resolve(apiKey);
|
|
353
|
+
}
|
|
354
|
+
}, POLL_INTERVAL_MS);
|
|
355
|
+
const HINT_MS = 55e3;
|
|
356
|
+
const hintTimer = setTimeout(() => {
|
|
357
|
+
const settingsUrl = `https://framer.com/projects/${projectId}?view=settings%3Aproject`;
|
|
358
|
+
print("");
|
|
359
|
+
printError("Taking a while? You can generate an API key manually:");
|
|
360
|
+
printError(` 1. Open Site Settings \u2192 General: ${settingsUrl}`);
|
|
361
|
+
printError(" 2. Scroll to API Keys and create a new key");
|
|
362
|
+
printError(
|
|
363
|
+
` 3. In another terminal, run: framer project auth ${projectId} <your-api-key>`
|
|
364
|
+
);
|
|
365
|
+
print("");
|
|
366
|
+
print("Waiting for authorization in browser...");
|
|
367
|
+
}, HINT_MS);
|
|
368
|
+
const timer = setTimeout(() => {
|
|
369
|
+
cleanup();
|
|
370
|
+
reject(
|
|
371
|
+
new Error(
|
|
372
|
+
"Browser authorization timed out. Use `framer project auth <projectUrlOrId> <apiKey>` instead."
|
|
373
|
+
)
|
|
374
|
+
);
|
|
375
|
+
}, TIMEOUT_MS);
|
|
376
|
+
function cleanup() {
|
|
377
|
+
clearTimeout(timer);
|
|
378
|
+
clearTimeout(hintTimer);
|
|
379
|
+
clearInterval(pollTimer);
|
|
380
|
+
server.close();
|
|
381
|
+
}
|
|
382
|
+
__name(cleanup, "cleanup");
|
|
383
|
+
server.listen(0, "127.0.0.1", async () => {
|
|
384
|
+
const addr = server.address();
|
|
385
|
+
if (!addr || typeof addr === "string") {
|
|
386
|
+
cleanup();
|
|
387
|
+
reject(new Error("Failed to start callback server."));
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const callbackUrl = `http://127.0.0.1:${addr.port}/callback`;
|
|
391
|
+
const deeplink = new URL("https://framer.com/projects/server-api/auth");
|
|
392
|
+
deeplink.searchParams.set("callback", callbackUrl);
|
|
393
|
+
deeplink.searchParams.set("state", state);
|
|
394
|
+
deeplink.searchParams.set("projectId", projectId);
|
|
395
|
+
const deeplinkStr = deeplink.toString();
|
|
396
|
+
const opened = await openUrl(deeplinkStr);
|
|
397
|
+
if (opened) {
|
|
398
|
+
print("Waiting for authorization in browser...");
|
|
399
|
+
} else {
|
|
400
|
+
printError("Could not open browser. Open this URL manually:");
|
|
401
|
+
printError(deeplinkStr);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
__name(acquireKeyFromBrowser, "acquireKeyFromBrowser");
|
|
407
|
+
|
|
408
|
+
// src/connection-errors.ts
|
|
409
|
+
var AUTH_ERROR_PATTERNS = ["does not have access", "UNAUTHORIZED"];
|
|
410
|
+
function isAuthError(errorMessage) {
|
|
411
|
+
return AUTH_ERROR_PATTERNS.some((p) => errorMessage.includes(p));
|
|
412
|
+
}
|
|
413
|
+
__name(isAuthError, "isAuthError");
|
|
71
414
|
|
|
72
415
|
// src/types-data.ts
|
|
73
416
|
var types = {
|
|
@@ -2239,6 +2582,20 @@ var types = {
|
|
|
2239
2582
|
}
|
|
2240
2583
|
]
|
|
2241
2584
|
},
|
|
2585
|
+
designpagecloneoptions: {
|
|
2586
|
+
name: "DesignPageCloneOptions",
|
|
2587
|
+
description: "",
|
|
2588
|
+
kind: "interface",
|
|
2589
|
+
references: [],
|
|
2590
|
+
members: [
|
|
2591
|
+
{
|
|
2592
|
+
name: "name",
|
|
2593
|
+
type: "string",
|
|
2594
|
+
description: "",
|
|
2595
|
+
optional: true
|
|
2596
|
+
}
|
|
2597
|
+
]
|
|
2598
|
+
},
|
|
2242
2599
|
diagnosticbase: {
|
|
2243
2600
|
name: "DiagnosticBase",
|
|
2244
2601
|
description: "",
|
|
@@ -4414,7 +4771,7 @@ var types = {
|
|
|
4414
4771
|
name: "LocationControl",
|
|
4415
4772
|
description: "",
|
|
4416
4773
|
kind: "interface",
|
|
4417
|
-
references: ["ControlBase", "Location"],
|
|
4774
|
+
references: ["ControlBase", "Location", "UnsupportedVariable"],
|
|
4418
4775
|
members: [
|
|
4419
4776
|
{
|
|
4420
4777
|
name: "type",
|
|
@@ -4424,7 +4781,7 @@ var types = {
|
|
|
4424
4781
|
},
|
|
4425
4782
|
{
|
|
4426
4783
|
name: "value",
|
|
4427
|
-
type: "Location | undefined",
|
|
4784
|
+
type: "Location | UnsupportedVariable | undefined",
|
|
4428
4785
|
description: "",
|
|
4429
4786
|
optional: true
|
|
4430
4787
|
}
|
|
@@ -5468,6 +5825,18 @@ var types = {
|
|
|
5468
5825
|
description: "",
|
|
5469
5826
|
optional: false
|
|
5470
5827
|
},
|
|
5828
|
+
{
|
|
5829
|
+
name: "cloneWebPage",
|
|
5830
|
+
type: "(nodeId: NodeId, options?: WebPageCloneOptions) => Promise<SomeNodeData | null>",
|
|
5831
|
+
description: "",
|
|
5832
|
+
optional: false
|
|
5833
|
+
},
|
|
5834
|
+
{
|
|
5835
|
+
name: "cloneDesignPage",
|
|
5836
|
+
type: "(nodeId: NodeId, options?: DesignPageCloneOptions) => Promise<SomeNodeData | null>",
|
|
5837
|
+
description: "",
|
|
5838
|
+
optional: false
|
|
5839
|
+
},
|
|
5471
5840
|
{
|
|
5472
5841
|
name: "getNode",
|
|
5473
5842
|
type: "(nodeId: NodeId) => Promise<SomeNodeData | null>",
|
|
@@ -8219,6 +8588,20 @@ var types = {
|
|
|
8219
8588
|
}
|
|
8220
8589
|
]
|
|
8221
8590
|
},
|
|
8591
|
+
webpagecloneoptions: {
|
|
8592
|
+
name: "WebPageCloneOptions",
|
|
8593
|
+
description: "",
|
|
8594
|
+
kind: "interface",
|
|
8595
|
+
references: [],
|
|
8596
|
+
members: [
|
|
8597
|
+
{
|
|
8598
|
+
name: "path",
|
|
8599
|
+
type: "string",
|
|
8600
|
+
description: "",
|
|
8601
|
+
optional: true
|
|
8602
|
+
}
|
|
8603
|
+
]
|
|
8604
|
+
},
|
|
8222
8605
|
widthconstraint: {
|
|
8223
8606
|
name: "WidthConstraint",
|
|
8224
8607
|
description: "",
|
|
@@ -10344,7 +10727,7 @@ var methodsByCategory = {
|
|
|
10344
10727
|
{
|
|
10345
10728
|
name: "clone",
|
|
10346
10729
|
category: "ComponentInstanceNode",
|
|
10347
|
-
signature:
|
|
10730
|
+
signature: "clone(): Promise<typeof this | null>",
|
|
10348
10731
|
description: 'Clone this node, creating a duplicate in the canvas tree.\n\n@returns The cloned node, or `null` if the clone failed.\n@throws If the node is an `UnknownNode`.\n\nUse `"Node.clone"` to check if this method is allowed.',
|
|
10349
10732
|
references: []
|
|
10350
10733
|
},
|
|
@@ -10623,7 +11006,7 @@ var methodsByCategory = {
|
|
|
10623
11006
|
{
|
|
10624
11007
|
name: "clone",
|
|
10625
11008
|
category: "ComponentNode",
|
|
10626
|
-
signature:
|
|
11009
|
+
signature: "clone(): Promise<typeof this | null>",
|
|
10627
11010
|
description: 'Clone this node, creating a duplicate in the canvas tree.\n\n@returns The cloned node, or `null` if the clone failed.\n@throws If the node is an `UnknownNode`.\n\nUse `"Node.clone"` to check if this method is allowed.',
|
|
10628
11011
|
references: []
|
|
10629
11012
|
},
|
|
@@ -10862,9 +11245,9 @@ var methodsByCategory = {
|
|
|
10862
11245
|
{
|
|
10863
11246
|
name: "clone",
|
|
10864
11247
|
category: "DesignPageNode",
|
|
10865
|
-
signature:
|
|
10866
|
-
description:
|
|
10867
|
-
references: []
|
|
11248
|
+
signature: "clone(options?: DesignPageCloneOptions): Promise<this>",
|
|
11249
|
+
description: "Clone the DesignPageNode into a new one with the same content\nIf the given name already exists, the cloned page will be created with a unique name.",
|
|
11250
|
+
references: ["DesignPageCloneOptions"]
|
|
10868
11251
|
},
|
|
10869
11252
|
{
|
|
10870
11253
|
name: "getChildren",
|
|
@@ -11376,7 +11759,7 @@ var methodsByCategory = {
|
|
|
11376
11759
|
{
|
|
11377
11760
|
name: "clone",
|
|
11378
11761
|
category: "FrameNode",
|
|
11379
|
-
signature:
|
|
11762
|
+
signature: "clone(): Promise<typeof this | null>",
|
|
11380
11763
|
description: 'Clone this node, creating a duplicate in the canvas tree.\n\n@returns The cloned node, or `null` if the clone failed.\n@throws If the node is an `UnknownNode`.\n\nUse `"Node.clone"` to check if this method is allowed.',
|
|
11381
11764
|
references: []
|
|
11382
11765
|
},
|
|
@@ -12965,7 +13348,7 @@ var methodsByCategory = {
|
|
|
12965
13348
|
{
|
|
12966
13349
|
name: "clone",
|
|
12967
13350
|
category: "SVGNode",
|
|
12968
|
-
signature:
|
|
13351
|
+
signature: "clone(): Promise<typeof this | null>",
|
|
12969
13352
|
description: 'Clone this node, creating a duplicate in the canvas tree.\n\n@returns The cloned node, or `null` if the clone failed.\n@throws If the node is an `UnknownNode`.\n\nUse `"Node.clone"` to check if this method is allowed.',
|
|
12970
13353
|
references: []
|
|
12971
13354
|
},
|
|
@@ -13184,7 +13567,7 @@ var methodsByCategory = {
|
|
|
13184
13567
|
{
|
|
13185
13568
|
name: "clone",
|
|
13186
13569
|
category: "TextNode",
|
|
13187
|
-
signature:
|
|
13570
|
+
signature: "clone(): Promise<typeof this | null>",
|
|
13188
13571
|
description: 'Clone this node, creating a duplicate in the canvas tree.\n\n@returns The cloned node, or `null` if the clone failed.\n@throws If the node is an `UnknownNode`.\n\nUse `"Node.clone"` to check if this method is allowed.',
|
|
13189
13572
|
references: []
|
|
13190
13573
|
},
|
|
@@ -13732,7 +14115,7 @@ var methodsByCategory = {
|
|
|
13732
14115
|
{
|
|
13733
14116
|
name: "clone",
|
|
13734
14117
|
category: "UnknownNode",
|
|
13735
|
-
signature:
|
|
14118
|
+
signature: "clone(): Promise<typeof this | null>",
|
|
13736
14119
|
description: 'Clone this node, creating a duplicate in the canvas tree.\n\n@returns The cloned node, or `null` if the clone failed.\n@throws If the node is an `UnknownNode`.\n\nUse `"Node.clone"` to check if this method is allowed.',
|
|
13737
14120
|
references: []
|
|
13738
14121
|
},
|
|
@@ -13931,7 +14314,7 @@ var methodsByCategory = {
|
|
|
13931
14314
|
{
|
|
13932
14315
|
name: "clone",
|
|
13933
14316
|
category: "VectorSetItemNode",
|
|
13934
|
-
signature:
|
|
14317
|
+
signature: "clone(): Promise<typeof this | null>",
|
|
13935
14318
|
description: 'Clone this node, creating a duplicate in the canvas tree.\n\n@returns The cloned node, or `null` if the clone failed.\n@throws If the node is an `UnknownNode`.\n\nUse `"Node.clone"` to check if this method is allowed.',
|
|
13936
14319
|
references: []
|
|
13937
14320
|
},
|
|
@@ -14101,7 +14484,7 @@ var methodsByCategory = {
|
|
|
14101
14484
|
{
|
|
14102
14485
|
name: "clone",
|
|
14103
14486
|
category: "VectorSetNode",
|
|
14104
|
-
signature:
|
|
14487
|
+
signature: "clone(): Promise<typeof this | null>",
|
|
14105
14488
|
description: 'Clone this node, creating a duplicate in the canvas tree.\n\n@returns The cloned node, or `null` if the clone failed.\n@throws If the node is an `UnknownNode`.\n\nUse `"Node.clone"` to check if this method is allowed.',
|
|
14106
14489
|
references: []
|
|
14107
14490
|
},
|
|
@@ -14222,9 +14605,9 @@ var methodsByCategory = {
|
|
|
14222
14605
|
{
|
|
14223
14606
|
name: "clone",
|
|
14224
14607
|
category: "WebPageNode",
|
|
14225
|
-
signature:
|
|
14226
|
-
description:
|
|
14227
|
-
references: []
|
|
14608
|
+
signature: "clone(options?: WebPageCloneOptions): Promise<this>",
|
|
14609
|
+
description: "Clone the WebPageNode into a new one with the same content and settings, as a draft\nIf the given path already exists, the cloned page will be created with a unique path.",
|
|
14610
|
+
references: ["WebPageCloneOptions"]
|
|
14228
14611
|
},
|
|
14229
14612
|
{
|
|
14230
14613
|
name: "collectionId",
|
|
@@ -14527,7 +14910,7 @@ ${typeDef}`);
|
|
|
14527
14910
|
__name(renderDocs, "renderDocs");
|
|
14528
14911
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
14529
14912
|
var __dirname$1 = path3.dirname(__filename$1);
|
|
14530
|
-
var VERSION = "0.0.
|
|
14913
|
+
var VERSION = "0.0.9" ;
|
|
14531
14914
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
|
|
14532
14915
|
var client = createTRPCClient({
|
|
14533
14916
|
links: [
|
|
@@ -14712,27 +15095,6 @@ function installSkills(options = { type: "base" }) {
|
|
|
14712
15095
|
}
|
|
14713
15096
|
__name(installSkills, "installSkills");
|
|
14714
15097
|
|
|
14715
|
-
// src/utils.ts
|
|
14716
|
-
function formatError(error) {
|
|
14717
|
-
if (error instanceof Error) {
|
|
14718
|
-
return error.message;
|
|
14719
|
-
}
|
|
14720
|
-
return String(error);
|
|
14721
|
-
}
|
|
14722
|
-
__name(formatError, "formatError");
|
|
14723
|
-
function printJson(value) {
|
|
14724
|
-
console.log(JSON.stringify(value, null, 2));
|
|
14725
|
-
}
|
|
14726
|
-
__name(printJson, "printJson");
|
|
14727
|
-
function print(message) {
|
|
14728
|
-
console.log(message);
|
|
14729
|
-
}
|
|
14730
|
-
__name(print, "print");
|
|
14731
|
-
function printError(message) {
|
|
14732
|
-
console.error(message);
|
|
14733
|
-
}
|
|
14734
|
-
__name(printError, "printError");
|
|
14735
|
-
|
|
14736
15098
|
// src/cli.ts
|
|
14737
15099
|
var program = new Command();
|
|
14738
15100
|
program.name("framer").version(VERSION).description("Framer Server API CLI");
|
|
@@ -14811,40 +15173,38 @@ async function refreshSkillsFromSession(sessionId, projectId) {
|
|
|
14811
15173
|
}
|
|
14812
15174
|
}
|
|
14813
15175
|
__name(refreshSkillsFromSession, "refreshSkillsFromSession");
|
|
14814
|
-
function resolveSessionCredentials(
|
|
15176
|
+
async function resolveSessionCredentials(projectUrlOrId) {
|
|
14815
15177
|
try {
|
|
14816
|
-
const projectId = extractProjectId(
|
|
14817
|
-
|
|
14818
|
-
|
|
14819
|
-
|
|
14820
|
-
return { projectId, apiKey: cachedApiKey };
|
|
14821
|
-
}
|
|
14822
|
-
printError("No API key provided and none cached for this project.");
|
|
14823
|
-
printError("");
|
|
14824
|
-
printError("Usage: framer session new <projectUrl> <apiKey>");
|
|
14825
|
-
process.exit(1);
|
|
15178
|
+
const projectId = extractProjectId(projectUrlOrId);
|
|
15179
|
+
const cachedApiKey = getApiKey(projectId);
|
|
15180
|
+
if (cachedApiKey) {
|
|
15181
|
+
return { projectId, apiKey: cachedApiKey };
|
|
14826
15182
|
}
|
|
14827
|
-
|
|
14828
|
-
|
|
15183
|
+
const newApiKey = await acquireKeyFromBrowser(projectId);
|
|
15184
|
+
saveProject({ projectId, apiKey: newApiKey });
|
|
15185
|
+
return { projectId, apiKey: newApiKey };
|
|
14829
15186
|
} catch (err) {
|
|
14830
|
-
printError(`Failed to
|
|
15187
|
+
printError(`Failed to resolve credentials: ${formatError(err)}`);
|
|
14831
15188
|
process.exit(1);
|
|
14832
15189
|
}
|
|
14833
15190
|
}
|
|
14834
15191
|
__name(resolveSessionCredentials, "resolveSessionCredentials");
|
|
14835
|
-
async function
|
|
14836
|
-
|
|
14837
|
-
|
|
14838
|
-
|
|
14839
|
-
|
|
14840
|
-
|
|
14841
|
-
|
|
14842
|
-
|
|
14843
|
-
|
|
14844
|
-
|
|
15192
|
+
async function getProjectName(sessionId) {
|
|
15193
|
+
const result = await client.exec.mutate({
|
|
15194
|
+
sessionId,
|
|
15195
|
+
code: "return (await framer.getProjectInfo()).name;",
|
|
15196
|
+
cwd: process.cwd()
|
|
15197
|
+
});
|
|
15198
|
+
if (result.error) {
|
|
15199
|
+
throw new Error(result.error);
|
|
15200
|
+
}
|
|
15201
|
+
const projectName = result.output.at(-1);
|
|
15202
|
+
if (typeof projectName !== "string") {
|
|
15203
|
+
throw new Error("Did not receive project name output.");
|
|
14845
15204
|
}
|
|
15205
|
+
return projectName;
|
|
14846
15206
|
}
|
|
14847
|
-
__name(
|
|
15207
|
+
__name(getProjectName, "getProjectName");
|
|
14848
15208
|
async function ensureRelayForCli() {
|
|
14849
15209
|
try {
|
|
14850
15210
|
await ensureRelayServerRunning({ logger: { log: print } });
|
|
@@ -14867,7 +15227,7 @@ program.option("-s, --session <id>", "Session ID (required for code execution)")
|
|
|
14867
15227
|
if (!sessionId) {
|
|
14868
15228
|
printError("Error: -s/--session is required.");
|
|
14869
15229
|
printError(
|
|
14870
|
-
"Run `framer session new <
|
|
15230
|
+
"Run `framer session new <projectUrlOrId>` first to get a session ID."
|
|
14871
15231
|
);
|
|
14872
15232
|
process.exit(1);
|
|
14873
15233
|
}
|
|
@@ -14891,15 +15251,40 @@ program.option("-s, --session <id>", "Session ID (required for code execution)")
|
|
|
14891
15251
|
}
|
|
14892
15252
|
});
|
|
14893
15253
|
var session = program.command("session").description("Manage sessions");
|
|
14894
|
-
session.command("new <
|
|
14895
|
-
const
|
|
14896
|
-
|
|
14897
|
-
|
|
14898
|
-
);
|
|
14899
|
-
|
|
14900
|
-
|
|
14901
|
-
|
|
14902
|
-
|
|
15254
|
+
session.command("new <projectUrlOrId>").description("Create a new session and print the session ID").action(async (projectUrlOrId) => {
|
|
15255
|
+
const [credentials] = await Promise.all([
|
|
15256
|
+
resolveSessionCredentials(projectUrlOrId),
|
|
15257
|
+
ensureRelayForCli()
|
|
15258
|
+
]);
|
|
15259
|
+
try {
|
|
15260
|
+
const result = await client.createSession.mutate({
|
|
15261
|
+
projectId: credentials.projectId,
|
|
15262
|
+
apiKey: credentials.apiKey
|
|
15263
|
+
});
|
|
15264
|
+
const sessionId = result.id;
|
|
15265
|
+
const [projectName] = await Promise.all([
|
|
15266
|
+
getProjectName(sessionId),
|
|
15267
|
+
refreshSkillsFromSession(sessionId, credentials.projectId)
|
|
15268
|
+
]);
|
|
15269
|
+
saveProject({
|
|
15270
|
+
projectId: credentials.projectId,
|
|
15271
|
+
apiKey: credentials.apiKey,
|
|
15272
|
+
name: projectName,
|
|
15273
|
+
lastUsedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15274
|
+
});
|
|
15275
|
+
print(sessionId);
|
|
15276
|
+
} catch (err) {
|
|
15277
|
+
const message = formatError(err);
|
|
15278
|
+
printError(`Failed to create session: ${message}`);
|
|
15279
|
+
if (isAuthError(message)) {
|
|
15280
|
+
clearApiKey(credentials.projectId);
|
|
15281
|
+
printError("The stored API key was invalid and has been removed.");
|
|
15282
|
+
printError(
|
|
15283
|
+
`Please try creating a session again. This will start a browser authentication flow`
|
|
15284
|
+
);
|
|
15285
|
+
}
|
|
15286
|
+
process.exit(1);
|
|
15287
|
+
}
|
|
14903
15288
|
});
|
|
14904
15289
|
session.command("list").description("List all active sessions").action(async () => {
|
|
14905
15290
|
await ensureRelayForCli();
|
|
@@ -14915,6 +15300,26 @@ session.command("list").description("List all active sessions").action(async ()
|
|
|
14915
15300
|
process.exit(1);
|
|
14916
15301
|
}
|
|
14917
15302
|
});
|
|
15303
|
+
var project = program.command("project").description("Manage projects");
|
|
15304
|
+
project.command("list").description("List recently used projects").action(() => {
|
|
15305
|
+
printJson(
|
|
15306
|
+
listProjects().map(({ projectId, name, lastUsedAt }) => ({
|
|
15307
|
+
projectId,
|
|
15308
|
+
name,
|
|
15309
|
+
lastUsedAt
|
|
15310
|
+
}))
|
|
15311
|
+
);
|
|
15312
|
+
});
|
|
15313
|
+
project.command("auth <projectUrlOrId> [apiKey]").description("Authorize and save a project").action(async (projectUrlOrId, apiKey) => {
|
|
15314
|
+
const projectId = extractProjectId(projectUrlOrId);
|
|
15315
|
+
if (apiKey) {
|
|
15316
|
+
saveProject({ projectId, apiKey });
|
|
15317
|
+
} else {
|
|
15318
|
+
const credentials = await resolveSessionCredentials(projectUrlOrId);
|
|
15319
|
+
saveProject(credentials);
|
|
15320
|
+
}
|
|
15321
|
+
print(`Project ${projectId} saved`);
|
|
15322
|
+
});
|
|
14918
15323
|
session.command("destroy <sessionId>").description("Destroy a session").action(async (sessionId) => {
|
|
14919
15324
|
await ensureRelayForCli();
|
|
14920
15325
|
try {
|
|
@@ -13,7 +13,7 @@ import { createRequire } from 'module';
|
|
|
13
13
|
import * as vm from 'vm';
|
|
14
14
|
import { connect } from 'framer-api';
|
|
15
15
|
|
|
16
|
-
/* @framer/ai relay server v0.0.
|
|
16
|
+
/* @framer/ai relay server v0.0.9 */
|
|
17
17
|
var __defProp = Object.defineProperty;
|
|
18
18
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
19
19
|
function getLogPath() {
|
|
@@ -50,7 +50,7 @@ function log(message) {
|
|
|
50
50
|
__name(log, "log");
|
|
51
51
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
52
52
|
path.dirname(__filename$1);
|
|
53
|
-
var VERSION = "0.0.
|
|
53
|
+
var VERSION = "0.0.9" ;
|
|
54
54
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
|
|
55
55
|
createTRPCClient({
|
|
56
56
|
links: [
|
|
@@ -85,6 +85,11 @@ function isConnectionError(errorMessage) {
|
|
|
85
85
|
);
|
|
86
86
|
}
|
|
87
87
|
__name(isConnectionError, "isConnectionError");
|
|
88
|
+
var AUTH_ERROR_PATTERNS = ["does not have access", "UNAUTHORIZED"];
|
|
89
|
+
function isAuthError(errorMessage) {
|
|
90
|
+
return AUTH_ERROR_PATTERNS.some((p) => errorMessage.includes(p));
|
|
91
|
+
}
|
|
92
|
+
__name(isAuthError, "isAuthError");
|
|
88
93
|
var ScopedFS = class {
|
|
89
94
|
static {
|
|
90
95
|
__name(this, "ScopedFS");
|
|
@@ -605,9 +610,17 @@ var appRouter = t.router({
|
|
|
605
610
|
return sessionManager.list();
|
|
606
611
|
}),
|
|
607
612
|
createSession: t.procedure.input(z.object({ projectId: z.string(), apiKey: z.string() })).mutation(async ({ input }) => {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
613
|
+
try {
|
|
614
|
+
const id = await sessionManager.create(input.projectId, input.apiKey);
|
|
615
|
+
log(`session.new id=${id} project=${input.projectId}`);
|
|
616
|
+
return { id };
|
|
617
|
+
} catch (err) {
|
|
618
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
619
|
+
throw new TRPCError({
|
|
620
|
+
code: isAuthError(message) ? "UNAUTHORIZED" : "INTERNAL_SERVER_ERROR",
|
|
621
|
+
message
|
|
622
|
+
});
|
|
623
|
+
}
|
|
611
624
|
}),
|
|
612
625
|
destroySession: t.procedure.input(z.object({ sessionId: z.string() })).mutation(async ({ input }) => {
|
|
613
626
|
await sessionManager.destroy(input.sessionId);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: {{SKILL_NAME}}
|
|
3
3
|
description: "Project-scoped Framer canvas editing skill for project {{PROJECT_ID}}. 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:*)"]
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
## Project Scope
|
package/docs/skills/framer.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: framer
|
|
3
|
-
description:
|
|
3
|
+
description: >
|
|
4
|
+
Use when the user wants to design, edit, or publish a website or web page — creating layouts, editing sections, updating text or images, managing CMS collections and content, syncing external data, creating or modifying code components, managing color and text styles, handling localization, or publishing deployments. Trigger when the user mentions Framer, references their website or web pages, asks to edit designs, update site content, or work with any Framer project — even if they don't explicitly say 'Framer'.
|
|
5
|
+
**Mandatory precondition**: run `npx framer-dalton@latest setup` and let it complete **BEFORE** loading this skill.
|
|
6
|
+
allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)"]
|
|
4
7
|
---
|
|
5
8
|
|
|
6
9
|
If you didn't run this command before loading the skill, run it now:
|
|
7
|
-
|
|
8
10
|
```bash
|
|
9
11
|
npx framer-dalton@latest setup
|
|
10
12
|
```
|
|
@@ -32,11 +34,18 @@ Every task follows these steps:
|
|
|
32
34
|
|
|
33
35
|
#### 1. Connect (once per session)
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
If the user refers to a project by name or description rather than giving a URL, first inspect the recent projects:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx framer-dalton project list
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
Create a session:
|
|
36
46
|
|
|
37
47
|
```bash
|
|
38
|
-
npx framer-dalton session new "<
|
|
39
|
-
npx framer-dalton session new "<projectUrl>" "<apiKey>" # First time: needs API key (Project Settings > General > API Keys)
|
|
48
|
+
npx framer-dalton session new "<url or id>"
|
|
40
49
|
```
|
|
41
50
|
|
|
42
51
|
#### 2. Look up the API (before EVERY code execution)
|
|
@@ -103,7 +112,7 @@ Each session maintains a persistent connection to a Framer project. Use sessions
|
|
|
103
112
|
Get a new session ID:
|
|
104
113
|
|
|
105
114
|
```bash
|
|
106
|
-
npx framer-dalton session new "https://framer.com/projects/Website--abc123"
|
|
115
|
+
npx framer-dalton session new "https://framer.com/projects/Website--abc123"
|
|
107
116
|
# outputs: 1
|
|
108
117
|
```
|
|
109
118
|
|
|
@@ -192,7 +201,10 @@ npx framer-dalton docs ScreenshotOptions # Show type + recursively expa
|
|
|
192
201
|
|
|
193
202
|
### Working with Collections (CMS)
|
|
194
203
|
|
|
195
|
-
Collections are Framer's CMS. Each collection has fields (columns) and items (rows).
|
|
204
|
+
Collections are Framer's CMS. Each collection has fields (columns) and items (rows). There are two types:
|
|
205
|
+
|
|
206
|
+
- **Unmanaged** (`framer.createCollection()`): Users can freely edit these in the Framer UI. Always use this by default.
|
|
207
|
+
- **Managed** (`framer.createManagedCollection()`): Can ONLY be edited via the API. They appear read-only in the Framer UI. Only use managed collections when explicitly asked or when creating a script for data synchronisation.
|
|
196
208
|
|
|
197
209
|
#### Reading Collections
|
|
198
210
|
|
|
@@ -257,8 +269,6 @@ await item.remove();
|
|
|
257
269
|
|
|
258
270
|
#### Managed Collections
|
|
259
271
|
|
|
260
|
-
Managed collections are fully controlled by code - users can't edit them directly. Use for syncing external data sources.
|
|
261
|
-
|
|
262
272
|
```js
|
|
263
273
|
// Create a managed collection
|
|
264
274
|
const managed = await framer.createManagedCollection({ name: "My Sync" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "framer-dalton",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"framer-dalton": "./dist/cli.js"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@trpc/client": "^11.9.0",
|
|
26
26
|
"@trpc/server": "^11.9.0",
|
|
27
27
|
"commander": "^12.1.0",
|
|
28
|
-
"framer-api": "https://pkg.pr.new/framer/FramerStudio/framer-api@
|
|
28
|
+
"framer-api": "https://pkg.pr.new/framer/FramerStudio/framer-api@0711174.tgz",
|
|
29
29
|
"zod": "^4.3.6"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|