framer-dalton 0.0.13 → 0.0.15
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 +145 -28
- package/dist/start-relay-server.js +12 -3
- package/docs/skills/framer-canvas-editing-project.md +16 -24
- package/docs/skills/framer.md +12 -7
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import { z } from 'zod';
|
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import { createTRPCClient, httpLink } from '@trpc/client';
|
|
12
12
|
|
|
13
|
-
/* @framer/ai CLI v0.0.
|
|
13
|
+
/* @framer/ai CLI v0.0.15 */
|
|
14
14
|
var __defProp = Object.defineProperty;
|
|
15
15
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
16
16
|
function openUrl(url) {
|
|
@@ -180,6 +180,19 @@ function clearApiKey(projectId) {
|
|
|
180
180
|
}
|
|
181
181
|
__name(clearApiKey, "clearApiKey");
|
|
182
182
|
|
|
183
|
+
// src/debug.ts
|
|
184
|
+
var enabled = false;
|
|
185
|
+
function setDebugEnabled(value) {
|
|
186
|
+
enabled = value;
|
|
187
|
+
}
|
|
188
|
+
__name(setDebugEnabled, "setDebugEnabled");
|
|
189
|
+
function debug(tag, message) {
|
|
190
|
+
if (!enabled) return;
|
|
191
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
192
|
+
console.error(`[debug ${timestamp}] ${tag}: ${message}`);
|
|
193
|
+
}
|
|
194
|
+
__name(debug, "debug");
|
|
195
|
+
|
|
183
196
|
// src/utils.ts
|
|
184
197
|
function formatError(error) {
|
|
185
198
|
if (error instanceof Error) {
|
|
@@ -323,6 +336,7 @@ async function acquireKeyFromBrowser(projectId) {
|
|
|
323
336
|
__name(settle, "settle");
|
|
324
337
|
const server = http.createServer((req, res) => {
|
|
325
338
|
const url = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
339
|
+
debug("auth", `request: ${url.pathname}`);
|
|
326
340
|
if (url.pathname === "/done") {
|
|
327
341
|
const theme2 = parseTheme(url.searchParams.get("theme"));
|
|
328
342
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
@@ -331,6 +345,7 @@ async function acquireKeyFromBrowser(projectId) {
|
|
|
331
345
|
const apiKey2 = pendingApiKey;
|
|
332
346
|
pendingApiKey = null;
|
|
333
347
|
settle(() => {
|
|
348
|
+
debug("auth", "received API key via /done, resolving");
|
|
334
349
|
print("\u2713 Agent authorized");
|
|
335
350
|
resolve(apiKey2);
|
|
336
351
|
});
|
|
@@ -367,6 +382,7 @@ async function acquireKeyFromBrowser(projectId) {
|
|
|
367
382
|
res.end();
|
|
368
383
|
return;
|
|
369
384
|
}
|
|
385
|
+
debug("auth", "received valid callback, redirecting to /done");
|
|
370
386
|
pendingApiKey = apiKey;
|
|
371
387
|
res.writeHead(302, { Location: `/done?theme=${theme}` });
|
|
372
388
|
res.end();
|
|
@@ -376,6 +392,7 @@ async function acquireKeyFromBrowser(projectId) {
|
|
|
376
392
|
const apiKey = getApiKey(projectId);
|
|
377
393
|
if (apiKey) {
|
|
378
394
|
settle(() => {
|
|
395
|
+
debug("auth", "API key detected from config file poll");
|
|
379
396
|
print("\u2713 API key detected from config");
|
|
380
397
|
resolve(apiKey);
|
|
381
398
|
});
|
|
@@ -407,6 +424,7 @@ async function acquireKeyFromBrowser(projectId) {
|
|
|
407
424
|
function cleanup() {
|
|
408
425
|
if (cleaned) return;
|
|
409
426
|
cleaned = true;
|
|
427
|
+
debug("auth", "cleanup: clearing timers and closing server");
|
|
410
428
|
clearTimeout(timer);
|
|
411
429
|
clearTimeout(hintTimer);
|
|
412
430
|
clearInterval(pollTimer);
|
|
@@ -416,20 +434,24 @@ async function acquireKeyFromBrowser(projectId) {
|
|
|
416
434
|
__name(cleanup, "cleanup");
|
|
417
435
|
server.keepAliveTimeout = 0;
|
|
418
436
|
server.unref();
|
|
437
|
+
debug("auth", "starting callback server...");
|
|
419
438
|
server.listen(0, "127.0.0.1", () => {
|
|
420
439
|
const addr = server.address();
|
|
421
440
|
if (!addr || typeof addr === "string") {
|
|
422
441
|
settle(() => reject(new Error("Failed to start callback server.")));
|
|
423
442
|
return;
|
|
424
443
|
}
|
|
444
|
+
debug("auth", `callback server listening on port ${addr.port}`);
|
|
425
445
|
const callbackUrl = `http://127.0.0.1:${addr.port}/callback`;
|
|
426
446
|
const deeplink = new URL("https://framer.com/projects/server-api/auth");
|
|
427
447
|
deeplink.searchParams.set("callback", callbackUrl);
|
|
428
448
|
deeplink.searchParams.set("state", state);
|
|
429
449
|
deeplink.searchParams.set("projectId", projectId);
|
|
430
450
|
const deeplinkStr = deeplink.toString();
|
|
451
|
+
debug("auth", "opening browser for deeplink...");
|
|
431
452
|
openUrl(deeplinkStr).then((opened) => {
|
|
432
453
|
if (opened) {
|
|
454
|
+
debug("auth", "browser opened successfully");
|
|
433
455
|
print("Waiting for authorization in browser...");
|
|
434
456
|
} else {
|
|
435
457
|
printError("Could not open browser. Open this URL manually:");
|
|
@@ -6629,6 +6651,18 @@ var types = {
|
|
|
6629
6651
|
description: "",
|
|
6630
6652
|
optional: false
|
|
6631
6653
|
},
|
|
6654
|
+
{
|
|
6655
|
+
name: "[getCurrentUserMessageType]",
|
|
6656
|
+
type: "() => Promise<User>",
|
|
6657
|
+
description: "",
|
|
6658
|
+
optional: false
|
|
6659
|
+
},
|
|
6660
|
+
{
|
|
6661
|
+
name: "[getProjectInfoMessageType]",
|
|
6662
|
+
type: "() => Promise<ProjectInfo>",
|
|
6663
|
+
description: "",
|
|
6664
|
+
optional: false
|
|
6665
|
+
},
|
|
6632
6666
|
{
|
|
6633
6667
|
name: "[getHTMLForNodeMessageType]",
|
|
6634
6668
|
type: "(nodeId: NodeId) => Promise<string | null>",
|
|
@@ -14944,9 +14978,16 @@ ${typeDef}`);
|
|
|
14944
14978
|
return { lines, errors };
|
|
14945
14979
|
}
|
|
14946
14980
|
__name(renderDocs, "renderDocs");
|
|
14981
|
+
|
|
14982
|
+
// src/version.ts
|
|
14983
|
+
var VERSION = (
|
|
14984
|
+
// typeof is used to ensure this can be used just via tsx or node etc. without build
|
|
14985
|
+
"0.0.15"
|
|
14986
|
+
);
|
|
14987
|
+
|
|
14988
|
+
// src/relay-client.ts
|
|
14947
14989
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
14948
14990
|
var __dirname$1 = path4.dirname(__filename$1);
|
|
14949
|
-
var VERSION = "0.0.13" ;
|
|
14950
14991
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
|
|
14951
14992
|
var client = createTRPCClient({
|
|
14952
14993
|
links: [
|
|
@@ -14984,11 +15025,15 @@ function compareVersions(v1, v2) {
|
|
|
14984
15025
|
__name(compareVersions, "compareVersions");
|
|
14985
15026
|
async function ensureRelayServerRunning(options = {}) {
|
|
14986
15027
|
const { logger, restartOnVersionMismatch = true } = options;
|
|
15028
|
+
debug("relay", `checking server version (client=${VERSION})...`);
|
|
14987
15029
|
const serverVersion = await getRelayServerVersion();
|
|
15030
|
+
debug("relay", `server version=${serverVersion ?? "not running"}`);
|
|
14988
15031
|
if (serverVersion === VERSION) {
|
|
15032
|
+
debug("relay", "version matches, no action needed");
|
|
14989
15033
|
return;
|
|
14990
15034
|
}
|
|
14991
15035
|
if (serverVersion !== null && compareVersions(serverVersion, VERSION) > 0) {
|
|
15036
|
+
debug("relay", "server version is newer, keeping it");
|
|
14992
15037
|
return;
|
|
14993
15038
|
}
|
|
14994
15039
|
if (serverVersion !== null) {
|
|
@@ -14996,11 +15041,13 @@ async function ensureRelayServerRunning(options = {}) {
|
|
|
14996
15041
|
logger?.log(
|
|
14997
15042
|
`Relay server version mismatch (server: ${serverVersion}, client: ${VERSION}), restarting...`
|
|
14998
15043
|
);
|
|
15044
|
+
debug("relay", "shutting down old server...");
|
|
14999
15045
|
try {
|
|
15000
15046
|
await client.shutdown.mutate();
|
|
15001
15047
|
await sleep(500);
|
|
15002
15048
|
} catch {
|
|
15003
15049
|
}
|
|
15050
|
+
debug("relay", "old server shut down");
|
|
15004
15051
|
} else {
|
|
15005
15052
|
return;
|
|
15006
15053
|
}
|
|
@@ -15009,6 +15056,7 @@ async function ensureRelayServerRunning(options = {}) {
|
|
|
15009
15056
|
}
|
|
15010
15057
|
const isRunningFromSource = __filename$1.endsWith(".ts");
|
|
15011
15058
|
const scriptPath = isRunningFromSource ? path4.resolve(__dirname$1, "./start-relay-server.ts") : path4.resolve(__dirname$1, "./start-relay-server.js");
|
|
15059
|
+
debug("relay", `spawning server process: ${scriptPath}`);
|
|
15012
15060
|
const serverProcess = spawn(
|
|
15013
15061
|
isRunningFromSource ? "tsx" : process.execPath,
|
|
15014
15062
|
[scriptPath],
|
|
@@ -15019,9 +15067,13 @@ async function ensureRelayServerRunning(options = {}) {
|
|
|
15019
15067
|
}
|
|
15020
15068
|
);
|
|
15021
15069
|
serverProcess.unref();
|
|
15022
|
-
for (let
|
|
15070
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
15023
15071
|
await sleep(500);
|
|
15024
15072
|
const newVersion = await getRelayServerVersion();
|
|
15073
|
+
debug(
|
|
15074
|
+
"relay",
|
|
15075
|
+
`readiness poll ${attempt + 1}/10: version=${newVersion ?? "not ready"}`
|
|
15076
|
+
);
|
|
15025
15077
|
if (newVersion) {
|
|
15026
15078
|
logger?.log("Relay server started successfully");
|
|
15027
15079
|
return;
|
|
@@ -15155,7 +15207,12 @@ __name(installSkills, "installSkills");
|
|
|
15155
15207
|
|
|
15156
15208
|
// src/cli.ts
|
|
15157
15209
|
var program = new Command();
|
|
15158
|
-
program.name("framer").version(VERSION).description("Framer Server API CLI")
|
|
15210
|
+
program.name("framer").version(VERSION).description("Framer Server API CLI").option("--debug", "Enable debug logging").hook("preAction", (thisCommand) => {
|
|
15211
|
+
const opts = thisCommand.opts();
|
|
15212
|
+
if (opts.debug) {
|
|
15213
|
+
setDebugEnabled(true);
|
|
15214
|
+
}
|
|
15215
|
+
});
|
|
15159
15216
|
async function readStdin() {
|
|
15160
15217
|
const chunks = [];
|
|
15161
15218
|
for await (const chunk of process.stdin) {
|
|
@@ -15180,11 +15237,13 @@ function printSetupSummary(results) {
|
|
|
15180
15237
|
}
|
|
15181
15238
|
__name(printSetupSummary, "printSetupSummary");
|
|
15182
15239
|
async function getAgentSystemPrompt(sessionId) {
|
|
15240
|
+
debug("exec", "getAgentSystemPrompt: calling relay...");
|
|
15183
15241
|
const result = await client.exec.mutate({
|
|
15184
15242
|
sessionId,
|
|
15185
15243
|
code: "return await framer.getAgentSystemPrompt();",
|
|
15186
15244
|
cwd: process.cwd()
|
|
15187
15245
|
});
|
|
15246
|
+
debug("exec", "getAgentSystemPrompt: relay responded");
|
|
15188
15247
|
if (result.error) {
|
|
15189
15248
|
throw new Error(result.error);
|
|
15190
15249
|
}
|
|
@@ -15196,11 +15255,13 @@ async function getAgentSystemPrompt(sessionId) {
|
|
|
15196
15255
|
}
|
|
15197
15256
|
__name(getAgentSystemPrompt, "getAgentSystemPrompt");
|
|
15198
15257
|
async function getAgentContext(sessionId) {
|
|
15258
|
+
debug("exec", "getAgentContext: calling relay...");
|
|
15199
15259
|
const result = await client.exec.mutate({
|
|
15200
15260
|
sessionId,
|
|
15201
15261
|
code: "return await framer.getAgentContext({ pagePath: '/' });",
|
|
15202
15262
|
cwd: process.cwd()
|
|
15203
15263
|
});
|
|
15264
|
+
debug("exec", "getAgentContext: relay responded");
|
|
15204
15265
|
if (result.error) {
|
|
15205
15266
|
throw new Error(result.error);
|
|
15206
15267
|
}
|
|
@@ -15213,16 +15274,19 @@ async function getAgentContext(sessionId) {
|
|
|
15213
15274
|
__name(getAgentContext, "getAgentContext");
|
|
15214
15275
|
async function refreshSkillsFromSession(sessionId, projectId) {
|
|
15215
15276
|
try {
|
|
15277
|
+
debug("skills", "fetching agent system prompt + context...");
|
|
15216
15278
|
const [canvasPrompt, agentContext] = await Promise.all([
|
|
15217
15279
|
getAgentSystemPrompt(sessionId),
|
|
15218
15280
|
getAgentContext(sessionId)
|
|
15219
15281
|
]);
|
|
15282
|
+
debug("skills", "installing skills...");
|
|
15220
15283
|
installSkills({
|
|
15221
15284
|
type: "project",
|
|
15222
15285
|
canvasPrompt,
|
|
15223
15286
|
projectId,
|
|
15224
15287
|
agentContext
|
|
15225
15288
|
});
|
|
15289
|
+
debug("skills", "skills installed");
|
|
15226
15290
|
} catch (err) {
|
|
15227
15291
|
printError(
|
|
15228
15292
|
`Failed to refresh skills for session ${sessionId}: ${formatError(err)}`
|
|
@@ -15234,11 +15298,15 @@ __name(refreshSkillsFromSession, "refreshSkillsFromSession");
|
|
|
15234
15298
|
async function resolveSessionCredentials(projectUrlOrId) {
|
|
15235
15299
|
try {
|
|
15236
15300
|
const projectId = extractProjectId(projectUrlOrId);
|
|
15301
|
+
debug("credentials", `projectId=${projectId}`);
|
|
15237
15302
|
const cachedApiKey = getApiKey(projectId);
|
|
15238
15303
|
if (cachedApiKey) {
|
|
15304
|
+
debug("credentials", "using cached API key");
|
|
15239
15305
|
return { projectId, apiKey: cachedApiKey };
|
|
15240
15306
|
}
|
|
15307
|
+
debug("credentials", "no cached key, starting browser auth flow...");
|
|
15241
15308
|
const newApiKey = await acquireKeyFromBrowser(projectId);
|
|
15309
|
+
debug("credentials", "browser auth complete, saving project");
|
|
15242
15310
|
saveProject({ projectId, apiKey: newApiKey });
|
|
15243
15311
|
return { projectId, apiKey: newApiKey };
|
|
15244
15312
|
} catch (err) {
|
|
@@ -15248,11 +15316,13 @@ async function resolveSessionCredentials(projectUrlOrId) {
|
|
|
15248
15316
|
}
|
|
15249
15317
|
__name(resolveSessionCredentials, "resolveSessionCredentials");
|
|
15250
15318
|
async function getProjectName(sessionId) {
|
|
15319
|
+
debug("exec", "getProjectName: calling relay...");
|
|
15251
15320
|
const result = await client.exec.mutate({
|
|
15252
15321
|
sessionId,
|
|
15253
15322
|
code: "return (await framer.getProjectInfo()).name;",
|
|
15254
15323
|
cwd: process.cwd()
|
|
15255
15324
|
});
|
|
15325
|
+
debug("exec", "getProjectName: relay responded");
|
|
15256
15326
|
if (result.error) {
|
|
15257
15327
|
throw new Error(result.error);
|
|
15258
15328
|
}
|
|
@@ -15265,14 +15335,40 @@ async function getProjectName(sessionId) {
|
|
|
15265
15335
|
__name(getProjectName, "getProjectName");
|
|
15266
15336
|
async function ensureRelayForCli() {
|
|
15267
15337
|
try {
|
|
15338
|
+
debug("relay", "ensuring relay server is running...");
|
|
15268
15339
|
await ensureRelayServerRunning({ logger: { log: print } });
|
|
15340
|
+
debug("relay", "relay server ready");
|
|
15269
15341
|
} catch (err) {
|
|
15270
15342
|
printError(`Failed to check relay status: ${formatError(err)}`);
|
|
15271
15343
|
process.exit(1);
|
|
15272
15344
|
}
|
|
15273
15345
|
}
|
|
15274
15346
|
__name(ensureRelayForCli, "ensureRelayForCli");
|
|
15275
|
-
|
|
15347
|
+
async function execAndPrint(sessionId, code) {
|
|
15348
|
+
await ensureRelayForCli();
|
|
15349
|
+
try {
|
|
15350
|
+
const result = await client.exec.mutate({
|
|
15351
|
+
sessionId: String(sessionId),
|
|
15352
|
+
code,
|
|
15353
|
+
cwd: process.cwd()
|
|
15354
|
+
});
|
|
15355
|
+
for (const line of result.output) {
|
|
15356
|
+
print(line);
|
|
15357
|
+
}
|
|
15358
|
+
if (result.error) {
|
|
15359
|
+
printError(`Error: ${result.error}`);
|
|
15360
|
+
process.exit(1);
|
|
15361
|
+
}
|
|
15362
|
+
} catch (err) {
|
|
15363
|
+
printError(`Execution failed: ${formatError(err)}`);
|
|
15364
|
+
process.exit(1);
|
|
15365
|
+
}
|
|
15366
|
+
}
|
|
15367
|
+
__name(execAndPrint, "execAndPrint");
|
|
15368
|
+
program.command("exec").description("Execute code in a session").requiredOption(
|
|
15369
|
+
"-s, --session <id>",
|
|
15370
|
+
"Session ID (create one with `session new <projectUrlOrId>`)"
|
|
15371
|
+
).option("-e, --eval <code>", "Code to execute (or pipe via stdin)").option("-f, --file <path>", "File containing code to execute").action(async (options, command) => {
|
|
15276
15372
|
const { session: sessionId, eval: evalCode, file: filePath } = options;
|
|
15277
15373
|
ensureTemporaryDir();
|
|
15278
15374
|
let code = evalCode;
|
|
@@ -15291,58 +15387,79 @@ program.option("-s, --session <id>", "Session ID (required for code execution)")
|
|
|
15291
15387
|
code = await readStdin();
|
|
15292
15388
|
}
|
|
15293
15389
|
if (!code) {
|
|
15294
|
-
|
|
15390
|
+
command.help();
|
|
15295
15391
|
return;
|
|
15296
15392
|
}
|
|
15297
|
-
|
|
15298
|
-
|
|
15299
|
-
|
|
15300
|
-
|
|
15301
|
-
|
|
15302
|
-
|
|
15303
|
-
}
|
|
15304
|
-
|
|
15393
|
+
await execAndPrint(sessionId, code);
|
|
15394
|
+
});
|
|
15395
|
+
program.command("apply-changes").description("Apply canvas DSL changes to a page").requiredOption(
|
|
15396
|
+
"-s, --session <id>",
|
|
15397
|
+
"Session ID (create one with `session new <projectUrlOrId>`)"
|
|
15398
|
+
).requiredOption("-e, --eval <dsl>", "DSL string to apply").option("-p, --page <path>", "Target page path", "/").action(async (options) => {
|
|
15399
|
+
const { session: sessionId, eval: dsl, page: pagePath } = options;
|
|
15400
|
+
const code = `
|
|
15401
|
+
const result = await framer.applyAgentChanges(${JSON.stringify(dsl)}, { pagePath: ${JSON.stringify(pagePath)} });
|
|
15402
|
+
if (result) console.log(JSON.stringify(result, null, 2));
|
|
15403
|
+
`;
|
|
15404
|
+
await execAndPrint(sessionId, code);
|
|
15405
|
+
});
|
|
15406
|
+
program.command("read-project").description("Read project state for a page").requiredOption(
|
|
15407
|
+
"-s, --session <id>",
|
|
15408
|
+
"Session ID (create one with `session new <projectUrlOrId>`)"
|
|
15409
|
+
).requiredOption("-q, --queries <json>", "JSON array of query objects").requiredOption("-p, --page <path>", "Target page path").action(async (options) => {
|
|
15410
|
+
const {
|
|
15411
|
+
session: sessionId,
|
|
15412
|
+
queries: queriesJson,
|
|
15413
|
+
page: pagePath
|
|
15414
|
+
} = options;
|
|
15415
|
+
let queries;
|
|
15305
15416
|
try {
|
|
15306
|
-
|
|
15307
|
-
|
|
15308
|
-
|
|
15309
|
-
cwd: process.cwd()
|
|
15310
|
-
});
|
|
15311
|
-
for (const line of result.output) {
|
|
15312
|
-
print(line);
|
|
15313
|
-
}
|
|
15314
|
-
if (result.error) {
|
|
15315
|
-
printError(`Error: ${result.error}`);
|
|
15316
|
-
process.exit(1);
|
|
15317
|
-
}
|
|
15318
|
-
} catch (err) {
|
|
15319
|
-
printError(`Execution failed: ${formatError(err)}`);
|
|
15417
|
+
queries = JSON.parse(queriesJson);
|
|
15418
|
+
} catch {
|
|
15419
|
+
printError("Invalid JSON for --queries");
|
|
15320
15420
|
process.exit(1);
|
|
15321
15421
|
}
|
|
15422
|
+
const code = `
|
|
15423
|
+
const result = await framer.readProjectForAgent(${JSON.stringify(queries)}, { pagePath: ${JSON.stringify(pagePath)} });
|
|
15424
|
+
if (result) console.log(JSON.stringify(result, null, 2));
|
|
15425
|
+
`;
|
|
15426
|
+
await execAndPrint(sessionId, code);
|
|
15322
15427
|
});
|
|
15323
15428
|
var session = program.command("session").description("Manage sessions");
|
|
15324
15429
|
session.command("new <projectUrlOrId>").description("Create a new session and print the session ID").action(async (projectUrlOrId) => {
|
|
15430
|
+
debug("session.new", `starting for ${projectUrlOrId}`);
|
|
15431
|
+
debug("session.new", "resolving credentials + ensuring relay...");
|
|
15325
15432
|
const [credentials] = await Promise.all([
|
|
15326
15433
|
resolveSessionCredentials(projectUrlOrId),
|
|
15327
15434
|
ensureRelayForCli()
|
|
15328
15435
|
]);
|
|
15436
|
+
debug(
|
|
15437
|
+
"session.new",
|
|
15438
|
+
`credentials resolved for project=${credentials.projectId}`
|
|
15439
|
+
);
|
|
15329
15440
|
try {
|
|
15441
|
+
debug("session.new", "calling createSession on relay...");
|
|
15330
15442
|
const result = await client.createSession.mutate({
|
|
15331
15443
|
projectId: credentials.projectId,
|
|
15332
15444
|
apiKey: credentials.apiKey
|
|
15333
15445
|
});
|
|
15334
15446
|
const sessionId = result.id;
|
|
15447
|
+
debug("session.new", `session created id=${sessionId}`);
|
|
15448
|
+
debug("session.new", "fetching project name + refreshing skills...");
|
|
15335
15449
|
const [projectName] = await Promise.all([
|
|
15336
15450
|
getProjectName(sessionId),
|
|
15337
15451
|
refreshSkillsFromSession(sessionId, credentials.projectId)
|
|
15338
15452
|
]);
|
|
15453
|
+
debug("session.new", `project name="${projectName}", skills refreshed`);
|
|
15339
15454
|
saveProject({
|
|
15340
15455
|
projectId: credentials.projectId,
|
|
15341
15456
|
apiKey: credentials.apiKey,
|
|
15342
15457
|
name: projectName,
|
|
15343
15458
|
lastUsedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15344
15459
|
});
|
|
15460
|
+
debug("session.new", "project saved, printing session id");
|
|
15345
15461
|
print(sessionId);
|
|
15462
|
+
debug("session.new", "done \u2014 process should exit now");
|
|
15346
15463
|
} catch (err) {
|
|
15347
15464
|
const message = formatError(err);
|
|
15348
15465
|
printError(`Failed to create session: ${message}`);
|
|
@@ -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.15 */
|
|
17
17
|
var __defProp = Object.defineProperty;
|
|
18
18
|
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
19
19
|
var __typeError = (msg) => {
|
|
@@ -89,9 +89,16 @@ function log(message) {
|
|
|
89
89
|
`);
|
|
90
90
|
}
|
|
91
91
|
__name(log, "log");
|
|
92
|
+
|
|
93
|
+
// src/version.ts
|
|
94
|
+
var VERSION = (
|
|
95
|
+
// typeof is used to ensure this can be used just via tsx or node etc. without build
|
|
96
|
+
"0.0.15"
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// src/relay-client.ts
|
|
92
100
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
93
101
|
path.dirname(__filename$1);
|
|
94
|
-
var VERSION = "0.0.13" ;
|
|
95
102
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
|
|
96
103
|
createTRPCClient({
|
|
97
104
|
links: [
|
|
@@ -527,7 +534,9 @@ var ConnectionPool = class {
|
|
|
527
534
|
}
|
|
528
535
|
return entry.connection;
|
|
529
536
|
}
|
|
530
|
-
const connection = await connect(projectId, apiKey
|
|
537
|
+
const connection = await connect(projectId, apiKey, {
|
|
538
|
+
clientId: `dalton/${VERSION}`
|
|
539
|
+
});
|
|
531
540
|
this.pool.set(projectId, {
|
|
532
541
|
connection,
|
|
533
542
|
sessions: /* @__PURE__ */ new Set([session]),
|
|
@@ -1,7 +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:*)", "Write({{FRAMER_TEMPORARY_DIR}}/*)"]
|
|
4
|
+
allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)", "Read({{FRAMER_TEMPORARY_DIR}}/*)", "Write({{FRAMER_TEMPORARY_DIR}}/*)"]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## Project Scope
|
|
@@ -11,12 +11,11 @@ allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)",
|
|
|
11
11
|
|
|
12
12
|
## Rules
|
|
13
13
|
|
|
14
|
-
- For design/layout work, do not use low-level node APIs (`createNode`, `setAttributes`, `setRect`, etc.). Use the canvas editing flow (`
|
|
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
15
|
- Canvas editing agent methods are employee-only.
|
|
16
|
-
- Canvas editing methods are not globals. Call `framer.readProjectForAgent` and `framer.applyAgentChanges` as `framer.*`.
|
|
17
16
|
- During normal task execution, do not call `framer.getAgentSystemPrompt()` or `framer.getAgentContext()`. This skill already includes `getAgentContext({ pagePath: "/" })`.
|
|
18
|
-
- `framer
|
|
19
|
-
- `framer
|
|
17
|
+
- `npx framer-dalton read-project -s <sessionId> -q <queries> -p <pagePath>` reads project state. Start with page structure only, then request additional targeted queries only if needed. Query types are documented in the embedded prompt below. Never guess names for examples, icon sets, or fonts; look them up first.
|
|
18
|
+
- `npx framer-dalton apply-changes -s <sessionId> -p <pagePath> -e <dsl>` applies canvas DSL changes. After applying changes, re-read project state to inspect results and iterate with `read-project` + `apply-changes` until accurate.
|
|
20
19
|
- Request examples only when needed, and only after inspecting page structure first. Keep example queries targeted and minimal.
|
|
21
20
|
- Use screenshots only when visual verification is necessary and cannot be confirmed from state reads. Do not use them for initial inspection or between every small change.
|
|
22
21
|
- When setting text content, use raw Unicode characters directly (for example `->`, not `\u2192`).
|
|
@@ -27,34 +26,27 @@ allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)",
|
|
|
27
26
|
## Workflow Loop
|
|
28
27
|
|
|
29
28
|
```bash
|
|
30
|
-
# 1) Read page structure first
|
|
31
|
-
|
|
32
|
-
# const { results } = await framer.readProjectForAgent(
|
|
33
|
-
# [{ type: "page", path: "/" }],
|
|
34
|
-
# { pagePath: "/" }
|
|
35
|
-
# );
|
|
36
|
-
# console.log(results);
|
|
37
|
-
|
|
38
|
-
framer -s <sessionId> -f {{FRAMER_TEMPORARY_DIR}}/<sessionId>-read-page.js
|
|
29
|
+
# 1) Read page structure first
|
|
30
|
+
npx framer-dalton read-project -s <sessionId> -q '[{"type":"page","path":"/"}]' -p "/"
|
|
39
31
|
|
|
40
32
|
# 2) Request additional targeted queries only if needed
|
|
41
33
|
|
|
42
|
-
# 3) Apply changes
|
|
43
|
-
|
|
44
|
-
# const dsl = `
|
|
45
|
-
# ...your canvas DSL...
|
|
46
|
-
# `;
|
|
47
|
-
# await framer.applyAgentChanges(dsl, { pagePath: "/" });
|
|
48
|
-
|
|
49
|
-
framer -s <sessionId> -f {{FRAMER_TEMPORARY_DIR}}/<sessionId>-apply-changes.js
|
|
34
|
+
# 3) Apply changes — pass DSL directly via apply-changes
|
|
35
|
+
npx framer-dalton apply-changes -s <sessionId> -p "/" -e "$dsl"
|
|
50
36
|
```
|
|
51
37
|
|
|
38
|
+
## Critical for UX: Streaming
|
|
39
|
+
|
|
40
|
+
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.
|
|
41
|
+
|
|
42
|
+
This ensures progress is streamed to the user in small, visible increments.
|
|
43
|
+
|
|
52
44
|
## Live Agent System Prompt
|
|
53
45
|
|
|
54
46
|
This is the static canvas-editing prompt returned by `framer.getAgentSystemPrompt()`. It is the sole documentation for:
|
|
55
47
|
|
|
56
|
-
- command syntax for `
|
|
57
|
-
- query types and parameters for `
|
|
48
|
+
- command syntax for `apply-changes`
|
|
49
|
+
- query types and parameters for `read-project`
|
|
58
50
|
- design rules and examples used by the canvas editing flow
|
|
59
51
|
|
|
60
52
|
{{CANVAS_PROMPT}}
|
package/docs/skills/framer.md
CHANGED
|
@@ -3,7 +3,7 @@ name: framer
|
|
|
3
3
|
description: >
|
|
4
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
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:*)", "Write({{FRAMER_TEMPORARY_DIR}}/*)"]
|
|
6
|
+
allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)", "Read({{FRAMER_TEMPORARY_DIR}}/*)", "Write({{FRAMER_TEMPORARY_DIR}}/*)"]
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
If you didn't run this command before loading the skill, run it now:
|
|
@@ -28,6 +28,10 @@ What you can do with the Framer CLI:
|
|
|
28
28
|
|
|
29
29
|
## CLI Usage
|
|
30
30
|
|
|
31
|
+
### Permissions
|
|
32
|
+
|
|
33
|
+
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.
|
|
34
|
+
|
|
31
35
|
### Required Workflow
|
|
32
36
|
|
|
33
37
|
Every task follows these steps:
|
|
@@ -62,7 +66,7 @@ npx framer-dalton docs Collection.getItems # What are the parameters and return
|
|
|
62
66
|
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`). Files are automatically deleted after execution.
|
|
63
67
|
|
|
64
68
|
```bash
|
|
65
|
-
npx framer-dalton -s 1 -f {{FRAMER_TEMPORARY_DIR}}/1-read-collections.js
|
|
69
|
+
npx framer-dalton exec -s 1 -f {{FRAMER_TEMPORARY_DIR}}/1-read-collections.js
|
|
66
70
|
```
|
|
67
71
|
|
|
68
72
|
#### 4. Store results in `state`
|
|
@@ -96,7 +100,7 @@ state.collections = await framer.getCollections();
|
|
|
96
100
|
```
|
|
97
101
|
|
|
98
102
|
```bash
|
|
99
|
-
npx framer-dalton -s 1 -f {{FRAMER_TEMPORARY_DIR}}/1-get-collections.js
|
|
103
|
+
npx framer-dalton exec -s 1 -f {{FRAMER_TEMPORARY_DIR}}/1-get-collections.js
|
|
100
104
|
```
|
|
101
105
|
|
|
102
106
|
```js
|
|
@@ -106,7 +110,7 @@ console.log(state.teamItems.length);
|
|
|
106
110
|
```
|
|
107
111
|
|
|
108
112
|
```bash
|
|
109
|
-
npx framer-dalton -s 1 -f {{FRAMER_TEMPORARY_DIR}}/1-get-team-items.js
|
|
113
|
+
npx framer-dalton exec -s 1 -f {{FRAMER_TEMPORARY_DIR}}/1-get-team-items.js
|
|
110
114
|
```
|
|
111
115
|
|
|
112
116
|
Store anything you'll reference again.
|
|
@@ -136,7 +140,7 @@ npx framer-dalton session list
|
|
|
136
140
|
|
|
137
141
|
## Canvas Editing
|
|
138
142
|
|
|
139
|
-
For design tasks, do not try to build or restyle pages with low-level node APIs. Instead, load the dynamically created project-scoped canvas skill and
|
|
143
|
+
For design tasks, do not try to build or restyle pages with low-level node APIs. Instead, load the dynamically created project-scoped canvas skill and use the `read-project` and `apply-changes` subcommands.
|
|
140
144
|
|
|
141
145
|
After session creation, load the dynamically created project-scoped skill `framer-canvas-editing-project-<projectId>` for canvas editing on the connected project. It contains the canvas editing guidance, the live agent system prompt, and the live project context for `pagePath: "/"`.
|
|
142
146
|
|
|
@@ -145,7 +149,7 @@ After session creation, load the dynamically created project-scoped skill `frame
|
|
|
145
149
|
Write your code to a unique file under `{{FRAMER_TEMPORARY_DIR}}/` and execute with `-f`:
|
|
146
150
|
|
|
147
151
|
```bash
|
|
148
|
-
npx framer-dalton -s <sessionId> -f {{FRAMER_TEMPORARY_DIR}}/<sessionId>-<short-summary>.js
|
|
152
|
+
npx framer-dalton exec -s <sessionId> -f {{FRAMER_TEMPORARY_DIR}}/<sessionId>-<short-summary>.js
|
|
149
153
|
```
|
|
150
154
|
|
|
151
155
|
## API Documentation
|
|
@@ -641,6 +645,7 @@ const contributors = await framer.getChangeContributors();
|
|
|
641
645
|
|
|
642
646
|
### Known Limitations
|
|
643
647
|
|
|
644
|
-
- **Pages**:
|
|
648
|
+
- **Pages**: Cannot change the path of a page
|
|
649
|
+
- **Collection Index/Detail Pages**: Cannot be created as new pages; the user must create them through the UI. Once they exist, they can be modified normally through canvas editing.
|
|
645
650
|
- **Code overrides**: Cannot assign overrides to nodes
|
|
646
651
|
- **Analytics**: No APIs exist for accessing analytics data
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "framer-dalton",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
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": "
|
|
28
|
+
"framer-api": "^0.1.4",
|
|
29
29
|
"zod": "^4.3.6"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|