framer-dalton 0.0.10 → 0.0.12
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 +95 -50
- package/dist/start-relay-server.js +48 -22
- package/docs/skills/framer-canvas-editing-project.md +6 -6
- package/docs/skills/framer.md +9 -11
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import fs3 from 'fs';
|
|
3
|
+
import path4 from 'path';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import crypto from 'crypto';
|
|
6
6
|
import http from 'http';
|
|
@@ -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.12 */
|
|
14
14
|
var __defProp = Object.defineProperty;
|
|
15
15
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
16
16
|
function openUrl(url) {
|
|
@@ -36,20 +36,20 @@ function openUrl(url) {
|
|
|
36
36
|
__name(openUrl, "openUrl");
|
|
37
37
|
function getConfigDir() {
|
|
38
38
|
if (process.env.XDG_CONFIG_HOME) {
|
|
39
|
-
return
|
|
39
|
+
return path4.join(process.env.XDG_CONFIG_HOME, "framer");
|
|
40
40
|
}
|
|
41
41
|
if (process.platform === "win32") {
|
|
42
|
-
return
|
|
42
|
+
return path4.join(process.env.APPDATA || os.homedir(), "framer");
|
|
43
43
|
}
|
|
44
|
-
return
|
|
44
|
+
return path4.join(os.homedir(), ".config", "framer");
|
|
45
45
|
}
|
|
46
46
|
__name(getConfigDir, "getConfigDir");
|
|
47
47
|
function getProjectsConfigPath() {
|
|
48
|
-
return
|
|
48
|
+
return path4.join(getConfigDir(), "projects.json");
|
|
49
49
|
}
|
|
50
50
|
__name(getProjectsConfigPath, "getProjectsConfigPath");
|
|
51
51
|
function getLegacyCredentialsPath() {
|
|
52
|
-
return
|
|
52
|
+
return path4.join(getConfigDir(), "credentials.json");
|
|
53
53
|
}
|
|
54
54
|
__name(getLegacyCredentialsPath, "getLegacyCredentialsPath");
|
|
55
55
|
var ProjectsConfigSchema = z.object({
|
|
@@ -65,11 +65,11 @@ var ProjectsConfigSchema = z.object({
|
|
|
65
65
|
});
|
|
66
66
|
var LegacyCredentialsSchema = z.record(z.string(), z.string());
|
|
67
67
|
function readJsonFile(filePath) {
|
|
68
|
-
if (!
|
|
68
|
+
if (!fs3.existsSync(filePath)) {
|
|
69
69
|
return null;
|
|
70
70
|
}
|
|
71
71
|
try {
|
|
72
|
-
return JSON.parse(
|
|
72
|
+
return JSON.parse(fs3.readFileSync(filePath, "utf-8"));
|
|
73
73
|
} catch (_error) {
|
|
74
74
|
return null;
|
|
75
75
|
}
|
|
@@ -77,14 +77,14 @@ function readJsonFile(filePath) {
|
|
|
77
77
|
__name(readJsonFile, "readJsonFile");
|
|
78
78
|
function ensureConfigDir() {
|
|
79
79
|
const configDir = getConfigDir();
|
|
80
|
-
if (!
|
|
81
|
-
|
|
80
|
+
if (!fs3.existsSync(configDir)) {
|
|
81
|
+
fs3.mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
__name(ensureConfigDir, "ensureConfigDir");
|
|
85
85
|
function writeProjectsConfig(config) {
|
|
86
86
|
ensureConfigDir();
|
|
87
|
-
|
|
87
|
+
fs3.writeFileSync(
|
|
88
88
|
getProjectsConfigPath(),
|
|
89
89
|
JSON.stringify(config, null, " "),
|
|
90
90
|
{
|
|
@@ -111,7 +111,7 @@ function migrateLegacyCredentials() {
|
|
|
111
111
|
}
|
|
112
112
|
const config = { version: 2, projects };
|
|
113
113
|
writeProjectsConfig(config);
|
|
114
|
-
|
|
114
|
+
fs3.rmSync(getLegacyCredentialsPath(), { force: true });
|
|
115
115
|
return config;
|
|
116
116
|
}
|
|
117
117
|
__name(migrateLegacyCredentials, "migrateLegacyCredentials");
|
|
@@ -124,7 +124,7 @@ function readProjectsConfig() {
|
|
|
124
124
|
return result.data;
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
-
if (
|
|
127
|
+
if (fs3.existsSync(getLegacyCredentialsPath())) {
|
|
128
128
|
return migrateLegacyCredentials();
|
|
129
129
|
}
|
|
130
130
|
return { version: 2, projects: {} };
|
|
@@ -204,6 +204,16 @@ __name(printError, "printError");
|
|
|
204
204
|
|
|
205
205
|
// src/auth-callback.ts
|
|
206
206
|
var TIMEOUT_MS = 3e5;
|
|
207
|
+
var codeMessages = {
|
|
208
|
+
"user.denied": "Authorization denied by user.",
|
|
209
|
+
"project.access_denied": "You don\u2019t have access to this project.",
|
|
210
|
+
"project.not_found": "Project not found. Check that the project ID is correct.",
|
|
211
|
+
"project.unauthorized": "Not logged in to Framer.",
|
|
212
|
+
"api_key.access_denied": "You don\u2019t have permission to create API keys for this project.",
|
|
213
|
+
"api_key.unauthorized": "Session expired.",
|
|
214
|
+
connection_error: "Could not connect to Framer.",
|
|
215
|
+
unknown: "Authorization failed."
|
|
216
|
+
};
|
|
207
217
|
var themes = {
|
|
208
218
|
dark: {
|
|
209
219
|
pageBackground: "rgb(17, 17, 17)",
|
|
@@ -271,15 +281,6 @@ function successHtml(theme) {
|
|
|
271
281
|
});
|
|
272
282
|
}
|
|
273
283
|
__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
284
|
function errorHtml(theme) {
|
|
284
285
|
return htmlPage({
|
|
285
286
|
title: "Framer \u2014 Authorization Failed",
|
|
@@ -289,10 +290,26 @@ function errorHtml(theme) {
|
|
|
289
290
|
});
|
|
290
291
|
}
|
|
291
292
|
__name(errorHtml, "errorHtml");
|
|
293
|
+
function codeHtml(code, theme) {
|
|
294
|
+
const heading = code === "user.denied" ? "Authorization Denied" : "Authorization Failed";
|
|
295
|
+
return htmlPage({
|
|
296
|
+
title: `Framer \u2014 ${heading}`,
|
|
297
|
+
heading,
|
|
298
|
+
message: codeMessages[code],
|
|
299
|
+
theme
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
__name(codeHtml, "codeHtml");
|
|
292
303
|
function parseTheme(value) {
|
|
293
304
|
return value === "light" ? "light" : "dark";
|
|
294
305
|
}
|
|
295
306
|
__name(parseTheme, "parseTheme");
|
|
307
|
+
var validCodes = new Set(Object.keys(codeMessages));
|
|
308
|
+
function parseCode(value) {
|
|
309
|
+
if (value !== null && validCodes.has(value)) return value;
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
__name(parseCode, "parseCode");
|
|
296
313
|
async function acquireKeyFromBrowser(projectId) {
|
|
297
314
|
const state = crypto.randomBytes(16).toString("hex");
|
|
298
315
|
return new Promise((resolve, reject) => {
|
|
@@ -324,12 +341,14 @@ async function acquireKeyFromBrowser(projectId) {
|
|
|
324
341
|
return;
|
|
325
342
|
}
|
|
326
343
|
const theme = parseTheme(url.searchParams.get("theme"));
|
|
327
|
-
const
|
|
328
|
-
|
|
344
|
+
const legacyError = url.searchParams.get("error");
|
|
345
|
+
const legacyCode = legacyError === "denied" ? "user.denied" : legacyError === "failed" ? "project.unauthorized" : null;
|
|
346
|
+
const code = parseCode(url.searchParams.get("code")) ?? legacyCode;
|
|
347
|
+
if (code !== null) {
|
|
329
348
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
330
|
-
res.end(
|
|
349
|
+
res.end(codeHtml(code, theme));
|
|
331
350
|
cleanup();
|
|
332
|
-
reject(new Error(
|
|
351
|
+
reject(new Error(codeMessages[code]));
|
|
333
352
|
return;
|
|
334
353
|
}
|
|
335
354
|
const apiKey = url.searchParams.get("apiKey");
|
|
@@ -14909,8 +14928,8 @@ ${typeDef}`);
|
|
|
14909
14928
|
}
|
|
14910
14929
|
__name(renderDocs, "renderDocs");
|
|
14911
14930
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
14912
|
-
var __dirname$1 =
|
|
14913
|
-
var VERSION = "0.0.
|
|
14931
|
+
var __dirname$1 = path4.dirname(__filename$1);
|
|
14932
|
+
var VERSION = "0.0.12" ;
|
|
14914
14933
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
|
|
14915
14934
|
var client = createTRPCClient({
|
|
14916
14935
|
links: [
|
|
@@ -14972,7 +14991,7 @@ async function ensureRelayServerRunning(options = {}) {
|
|
|
14972
14991
|
logger?.log("Relay server not running, starting it...");
|
|
14973
14992
|
}
|
|
14974
14993
|
const isRunningFromSource = __filename$1.endsWith(".ts");
|
|
14975
|
-
const scriptPath = isRunningFromSource ?
|
|
14994
|
+
const scriptPath = isRunningFromSource ? path4.resolve(__dirname$1, "./start-relay-server.ts") : path4.resolve(__dirname$1, "./start-relay-server.js");
|
|
14976
14995
|
const serverProcess = spawn(
|
|
14977
14996
|
isRunningFromSource ? "tsx" : process.execPath,
|
|
14978
14997
|
[scriptPath],
|
|
@@ -14994,16 +15013,37 @@ async function ensureRelayServerRunning(options = {}) {
|
|
|
14994
15013
|
throw new Error("Failed to start relay server after 5 seconds");
|
|
14995
15014
|
}
|
|
14996
15015
|
__name(ensureRelayServerRunning, "ensureRelayServerRunning");
|
|
15016
|
+
var FRAMER_TEMPORARY_DIR = path4.join(os.tmpdir(), "framer");
|
|
15017
|
+
function ensureTemporaryDir() {
|
|
15018
|
+
fs3.mkdirSync(FRAMER_TEMPORARY_DIR, { recursive: true });
|
|
15019
|
+
}
|
|
15020
|
+
__name(ensureTemporaryDir, "ensureTemporaryDir");
|
|
15021
|
+
function isTemporaryFile(filePath) {
|
|
15022
|
+
const absolutePath = path4.resolve(filePath);
|
|
15023
|
+
const isInTemporaryDir = absolutePath.startsWith(
|
|
15024
|
+
FRAMER_TEMPORARY_DIR + path4.sep
|
|
15025
|
+
);
|
|
15026
|
+
const isFile = fs3.statSync(absolutePath, { throwIfNoEntry: false })?.isFile() ?? false;
|
|
15027
|
+
return isInTemporaryDir && isFile;
|
|
15028
|
+
}
|
|
15029
|
+
__name(isTemporaryFile, "isTemporaryFile");
|
|
15030
|
+
function removeTemporaryFile(filePath) {
|
|
15031
|
+
fs3.unlinkSync(filePath);
|
|
15032
|
+
}
|
|
15033
|
+
__name(removeTemporaryFile, "removeTemporaryFile");
|
|
15034
|
+
|
|
15035
|
+
// src/skills.ts
|
|
14997
15036
|
var META_SKILL_NAME = "framer";
|
|
14998
15037
|
var CODE_COMPONENTS_SKILL_NAME = "framer-code-components";
|
|
14999
|
-
var __dirname2 =
|
|
15000
|
-
var skillsDocsDir =
|
|
15038
|
+
var __dirname2 = path4.dirname(fileURLToPath(import.meta.url));
|
|
15039
|
+
var skillsDocsDir = path4.join(__dirname2, "..", "docs", "skills");
|
|
15001
15040
|
function readSkillDoc(name) {
|
|
15002
|
-
return
|
|
15041
|
+
return fs3.readFileSync(path4.join(skillsDocsDir, name), "utf-8").trimEnd();
|
|
15003
15042
|
}
|
|
15004
15043
|
__name(readSkillDoc, "readSkillDoc");
|
|
15005
15044
|
function buildMetaSkill() {
|
|
15006
|
-
|
|
15045
|
+
const template = readSkillDoc("framer.md");
|
|
15046
|
+
return `${renderTemplate(template, { FRAMER_TEMPORARY_DIR })}
|
|
15007
15047
|
`;
|
|
15008
15048
|
}
|
|
15009
15049
|
__name(buildMetaSkill, "buildMetaSkill");
|
|
@@ -15033,36 +15073,37 @@ function buildProjectCanvasSkill(projectId, agentContext, canvasPrompt) {
|
|
|
15033
15073
|
PROJECT_ID: projectId,
|
|
15034
15074
|
GENERATED_AT: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15035
15075
|
CANVAS_PROMPT: canvasPrompt.trimEnd(),
|
|
15036
|
-
AGENT_CONTEXT: agentContext.trimEnd()
|
|
15076
|
+
AGENT_CONTEXT: agentContext.trimEnd(),
|
|
15077
|
+
FRAMER_TEMPORARY_DIR
|
|
15037
15078
|
})}
|
|
15038
15079
|
`;
|
|
15039
15080
|
return { skillName, content };
|
|
15040
15081
|
}
|
|
15041
15082
|
__name(buildProjectCanvasSkill, "buildProjectCanvasSkill");
|
|
15042
15083
|
function writeSkill(root, skillName, content) {
|
|
15043
|
-
|
|
15044
|
-
const rootStat =
|
|
15084
|
+
fs3.mkdirSync(root, { recursive: true });
|
|
15085
|
+
const rootStat = fs3.statSync(root);
|
|
15045
15086
|
if (!rootStat.isDirectory()) {
|
|
15046
15087
|
throw new Error(`Skill root is not a directory: ${root}`);
|
|
15047
15088
|
}
|
|
15048
|
-
const skillDir =
|
|
15049
|
-
const filePath =
|
|
15050
|
-
if (
|
|
15051
|
-
const current =
|
|
15089
|
+
const skillDir = path4.join(root, skillName);
|
|
15090
|
+
const filePath = path4.join(skillDir, "SKILL.md");
|
|
15091
|
+
if (fs3.existsSync(skillDir)) {
|
|
15092
|
+
const current = fs3.lstatSync(skillDir);
|
|
15052
15093
|
if (current.isSymbolicLink() || !current.isDirectory()) {
|
|
15053
|
-
|
|
15094
|
+
fs3.rmSync(skillDir, { recursive: true, force: true });
|
|
15054
15095
|
}
|
|
15055
15096
|
}
|
|
15056
|
-
|
|
15057
|
-
|
|
15097
|
+
fs3.mkdirSync(skillDir, { recursive: true });
|
|
15098
|
+
fs3.writeFileSync(filePath, content, "utf-8");
|
|
15058
15099
|
return filePath;
|
|
15059
15100
|
}
|
|
15060
15101
|
__name(writeSkill, "writeSkill");
|
|
15061
15102
|
function getDefaultSkillRoots() {
|
|
15062
15103
|
const home = os.homedir();
|
|
15063
15104
|
return [
|
|
15064
|
-
|
|
15065
|
-
|
|
15105
|
+
path4.join(home, ".agents", "skills"),
|
|
15106
|
+
path4.join(home, ".claude", "skills")
|
|
15066
15107
|
];
|
|
15067
15108
|
}
|
|
15068
15109
|
__name(getDefaultSkillRoots, "getDefaultSkillRoots");
|
|
@@ -15111,8 +15152,8 @@ function printSetupSummary(results) {
|
|
|
15111
15152
|
const installLocations = /* @__PURE__ */ new Set();
|
|
15112
15153
|
for (const result of results) {
|
|
15113
15154
|
for (const filePath of result.paths) {
|
|
15114
|
-
const skillDir =
|
|
15115
|
-
const root =
|
|
15155
|
+
const skillDir = path4.dirname(filePath);
|
|
15156
|
+
const root = path4.dirname(skillDir);
|
|
15116
15157
|
installLocations.add(root);
|
|
15117
15158
|
}
|
|
15118
15159
|
}
|
|
@@ -15216,14 +15257,18 @@ async function ensureRelayForCli() {
|
|
|
15216
15257
|
__name(ensureRelayForCli, "ensureRelayForCli");
|
|
15217
15258
|
program.option("-s, --session <id>", "Session ID (required for code execution)").option("-e, --eval <code>", "Code to execute (or pipe via stdin)").option("-f, --file <path>", "File containing code to execute").action(async (options) => {
|
|
15218
15259
|
const { session: sessionId, eval: evalCode, file: filePath } = options;
|
|
15260
|
+
ensureTemporaryDir();
|
|
15219
15261
|
let code = evalCode;
|
|
15220
15262
|
if (!code && filePath) {
|
|
15221
15263
|
try {
|
|
15222
|
-
code =
|
|
15264
|
+
code = fs3.readFileSync(filePath, "utf-8");
|
|
15223
15265
|
} catch (err) {
|
|
15224
15266
|
printError(`Failed to read file: ${formatError(err)}`);
|
|
15225
15267
|
process.exit(1);
|
|
15226
15268
|
}
|
|
15269
|
+
if (isTemporaryFile(filePath)) {
|
|
15270
|
+
removeTemporaryFile(filePath);
|
|
15271
|
+
}
|
|
15227
15272
|
}
|
|
15228
15273
|
if (!code && !process.stdin.isTTY) {
|
|
15229
15274
|
code = await readStdin();
|
|
@@ -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.12 */
|
|
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) => {
|
|
@@ -91,7 +91,7 @@ function log(message) {
|
|
|
91
91
|
__name(log, "log");
|
|
92
92
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
93
93
|
path.dirname(__filename$1);
|
|
94
|
-
var VERSION = "0.0.
|
|
94
|
+
var VERSION = "0.0.12" ;
|
|
95
95
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
|
|
96
96
|
createTRPCClient({
|
|
97
97
|
links: [
|
|
@@ -456,25 +456,24 @@ async function execute(session, framer, code, options = {}) {
|
|
|
456
456
|
}
|
|
457
457
|
}
|
|
458
458
|
__name(execute, "execute");
|
|
459
|
-
async function executeWithReconnect(session, framer, code, options, reconnect) {
|
|
459
|
+
async function executeWithReconnect(session, framer, code, options, reconnect, execId) {
|
|
460
460
|
const result = await tryExecute(session, framer, code, options);
|
|
461
461
|
if (!result.error || !isConnectionError(result.error)) {
|
|
462
462
|
return result;
|
|
463
463
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
);
|
|
464
|
+
const reqId = framer.requestId;
|
|
465
|
+
const tag = `exec=${execId} session=${session.id}${reqId ? ` req=${reqId}` : ""}`;
|
|
466
|
+
log(`reconnect ${tag} project=${session.projectId} reason="${result.error}"`);
|
|
467
467
|
const newFramer = await reconnect();
|
|
468
468
|
if (!newFramer) {
|
|
469
|
-
log(
|
|
470
|
-
`reconnect.failed session=${session.id} error="no connection returned"`
|
|
471
|
-
);
|
|
469
|
+
log(`reconnect.failed ${tag} error="no connection returned"`);
|
|
472
470
|
return {
|
|
473
471
|
output: [],
|
|
474
472
|
error: "Connection lost and failed to reconnect"
|
|
475
473
|
};
|
|
476
474
|
}
|
|
477
|
-
|
|
475
|
+
const newReqId = newFramer.requestId;
|
|
476
|
+
log(`reconnect.success ${tag}${newReqId ? ` new_req=${newReqId}` : ""}`);
|
|
478
477
|
return tryExecute(session, newFramer, code, options);
|
|
479
478
|
}
|
|
480
479
|
__name(executeWithReconnect, "executeWithReconnect");
|
|
@@ -522,6 +521,10 @@ var ConnectionPool = class {
|
|
|
522
521
|
const entry = this.pool.get(projectId);
|
|
523
522
|
if (entry) {
|
|
524
523
|
entry.sessions.add(session);
|
|
524
|
+
if (!entry.connected) {
|
|
525
|
+
await entry.connection.reconnect();
|
|
526
|
+
entry.connected = true;
|
|
527
|
+
}
|
|
525
528
|
return entry.connection;
|
|
526
529
|
}
|
|
527
530
|
const connection = await connect(projectId, apiKey);
|
|
@@ -645,6 +648,9 @@ var SessionManager = class {
|
|
|
645
648
|
getFramer(session) {
|
|
646
649
|
return connectionPool.getConnection(session.projectId);
|
|
647
650
|
}
|
|
651
|
+
isConnected(session) {
|
|
652
|
+
return connectionPool.isConnected(session.projectId);
|
|
653
|
+
}
|
|
648
654
|
async reconnect(session) {
|
|
649
655
|
return connectionPool.reconnect(session.projectId);
|
|
650
656
|
}
|
|
@@ -696,13 +702,23 @@ var SessionManager = class {
|
|
|
696
702
|
}
|
|
697
703
|
async reapIdleSessions() {
|
|
698
704
|
const now = Date.now();
|
|
699
|
-
const
|
|
705
|
+
const projectSessions = /* @__PURE__ */ new Map();
|
|
700
706
|
for (const session of this.sessions.values()) {
|
|
701
|
-
|
|
702
|
-
if (
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
707
|
+
const existing = projectSessions.get(session.projectId);
|
|
708
|
+
if (existing) existing.push(session);
|
|
709
|
+
else projectSessions.set(session.projectId, [session]);
|
|
710
|
+
}
|
|
711
|
+
const disconnects = [];
|
|
712
|
+
for (const [projectId, sessions] of projectSessions) {
|
|
713
|
+
if (!connectionPool.isConnected(projectId)) continue;
|
|
714
|
+
const allIdle = sessions.every(
|
|
715
|
+
(s) => s.inflight === 0 && now - s.lastActivityAt >= SESSION_IDLE_TIMEOUT_MS
|
|
716
|
+
);
|
|
717
|
+
if (!allIdle) continue;
|
|
718
|
+
const reqId = connectionPool.getConnection(projectId)?.requestId;
|
|
719
|
+
log(
|
|
720
|
+
`idle disconnect project=${projectId}${reqId ? ` req=${reqId}` : ""}`
|
|
721
|
+
);
|
|
706
722
|
disconnects.push(connectionPool.disconnect(projectId));
|
|
707
723
|
}
|
|
708
724
|
const results = await Promise.allSettled(disconnects);
|
|
@@ -719,6 +735,7 @@ var sessionManager = new SessionManager();
|
|
|
719
735
|
|
|
720
736
|
// src/router.ts
|
|
721
737
|
var t = initTRPC.create();
|
|
738
|
+
var nextExecId = 0;
|
|
722
739
|
var appRouter = t.router({
|
|
723
740
|
version: t.procedure.query(() => {
|
|
724
741
|
return { version: VERSION };
|
|
@@ -761,11 +778,19 @@ var appRouter = t.router({
|
|
|
761
778
|
});
|
|
762
779
|
}
|
|
763
780
|
const _guard = __using(_stack, sessionManager.exec(sessionId));
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
)
|
|
767
|
-
|
|
781
|
+
const execId = nextExecId++;
|
|
782
|
+
let framer = sessionManager.getFramer(session);
|
|
783
|
+
if (framer && !sessionManager.isConnected(session)) {
|
|
784
|
+
log(
|
|
785
|
+
`exec.reconnect exec=${execId} session=${sessionId} reason="idle disconnected"`
|
|
786
|
+
);
|
|
787
|
+
framer = await sessionManager.reconnect(session);
|
|
788
|
+
}
|
|
789
|
+
const reqId = framer?.requestId;
|
|
790
|
+
const tag = `exec=${execId} session=${sessionId}${reqId ? ` req=${reqId}` : ""}`;
|
|
791
|
+
log(`exec ${tag} code=${JSON.stringify(code).slice(0, 100)}`);
|
|
768
792
|
if (!framer) {
|
|
793
|
+
log(`exec.error ${tag} error="no connection"`);
|
|
769
794
|
return {
|
|
770
795
|
output: [],
|
|
771
796
|
error: "Failed to get connection for session"
|
|
@@ -776,10 +801,11 @@ var appRouter = t.router({
|
|
|
776
801
|
framer,
|
|
777
802
|
code,
|
|
778
803
|
{ cwd },
|
|
779
|
-
() => sessionManager.reconnect(session)
|
|
804
|
+
() => sessionManager.reconnect(session),
|
|
805
|
+
execId
|
|
780
806
|
);
|
|
781
807
|
if (result.error) {
|
|
782
|
-
log(`exec.error
|
|
808
|
+
log(`exec.error ${tag} error="${result.error}"`);
|
|
783
809
|
}
|
|
784
810
|
return result;
|
|
785
811
|
} catch (_) {
|
|
@@ -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(
|
|
4
|
+
allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)", "Write({{FRAMER_TEMPORARY_DIR}}/*)"]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## Project Scope
|
|
@@ -27,26 +27,26 @@ allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)",
|
|
|
27
27
|
## Workflow Loop
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
# 1) Read page structure first — write code to
|
|
31
|
-
#
|
|
30
|
+
# 1) Read page structure first — write code to a unique file, then execute with -f
|
|
31
|
+
# {{FRAMER_TEMPORARY_DIR}}/<sessionId>-read-page.js:
|
|
32
32
|
# const { results } = await framer.readProjectForAgent(
|
|
33
33
|
# [{ type: "page", path: "/" }],
|
|
34
34
|
# { pagePath: "/" }
|
|
35
35
|
# );
|
|
36
36
|
# console.log(results);
|
|
37
37
|
|
|
38
|
-
framer -s <sessionId> -f
|
|
38
|
+
framer -s <sessionId> -f {{FRAMER_TEMPORARY_DIR}}/<sessionId>-read-page.js
|
|
39
39
|
|
|
40
40
|
# 2) Request additional targeted queries only if needed
|
|
41
41
|
|
|
42
42
|
# 3) Apply changes in a later call, once `dsl` has been prepared
|
|
43
|
-
#
|
|
43
|
+
# {{FRAMER_TEMPORARY_DIR}}/<sessionId>-apply-changes.js:
|
|
44
44
|
# const dsl = `
|
|
45
45
|
# ...your canvas DSL...
|
|
46
46
|
# `;
|
|
47
47
|
# await framer.applyAgentChanges(dsl, { pagePath: "/" });
|
|
48
48
|
|
|
49
|
-
framer -s <sessionId> -f
|
|
49
|
+
framer -s <sessionId> -f {{FRAMER_TEMPORARY_DIR}}/<sessionId>-apply-changes.js
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
## Live Agent System 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(
|
|
6
|
+
allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)", "Write({{FRAMER_TEMPORARY_DIR}}/*)"]
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
If you didn't run this command before loading the skill, run it now:
|
|
@@ -59,14 +59,12 @@ npx framer-dalton docs Collection.getItems # What are the parameters and return
|
|
|
59
59
|
|
|
60
60
|
#### 3. Execute code
|
|
61
61
|
|
|
62
|
-
Only after checking docs, write your code to a
|
|
62
|
+
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
63
|
|
|
64
64
|
```bash
|
|
65
|
-
npx framer-dalton -s 1 -f /
|
|
65
|
+
npx framer-dalton -s 1 -f {{FRAMER_TEMPORARY_DIR}}/1-read-collections.js
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
On Windows, use the equivalent temp directory (e.g. `%TEMP%\framer-1.js`).
|
|
69
|
-
|
|
70
68
|
#### 4. Store results in `state`
|
|
71
69
|
|
|
72
70
|
Always save results you'll need again. Don't repeat API calls.
|
|
@@ -93,22 +91,22 @@ Always save results you'll need again. Don't repeat API calls.
|
|
|
93
91
|
**Always store results in `state` when you'll need them again.** API calls are slow - don't repeat them.
|
|
94
92
|
|
|
95
93
|
```js
|
|
96
|
-
// /
|
|
94
|
+
// {{FRAMER_TEMPORARY_DIR}}/1-get-collections.js
|
|
97
95
|
state.collections = await framer.getCollections();
|
|
98
96
|
```
|
|
99
97
|
|
|
100
98
|
```bash
|
|
101
|
-
npx framer-dalton -s 1 -f /
|
|
99
|
+
npx framer-dalton -s 1 -f {{FRAMER_TEMPORARY_DIR}}/1-get-collections.js
|
|
102
100
|
```
|
|
103
101
|
|
|
104
102
|
```js
|
|
105
|
-
// /
|
|
103
|
+
// {{FRAMER_TEMPORARY_DIR}}/1-get-team-items.js — reuse from state
|
|
106
104
|
state.teamItems = await state.collections.find(c => c.name === 'Team').getItems();
|
|
107
105
|
console.log(state.teamItems.length);
|
|
108
106
|
```
|
|
109
107
|
|
|
110
108
|
```bash
|
|
111
|
-
npx framer-dalton -s 1 -f /
|
|
109
|
+
npx framer-dalton -s 1 -f {{FRAMER_TEMPORARY_DIR}}/1-get-team-items.js
|
|
112
110
|
```
|
|
113
111
|
|
|
114
112
|
Store anything you'll reference again.
|
|
@@ -144,10 +142,10 @@ After session creation, load the dynamically created project-scoped skill `frame
|
|
|
144
142
|
|
|
145
143
|
## Execute Code
|
|
146
144
|
|
|
147
|
-
Write your code to
|
|
145
|
+
Write your code to a unique file under `{{FRAMER_TEMPORARY_DIR}}/` and execute with `-f`:
|
|
148
146
|
|
|
149
147
|
```bash
|
|
150
|
-
npx framer-dalton -s <sessionId> -f
|
|
148
|
+
npx framer-dalton -s <sessionId> -f {{FRAMER_TEMPORARY_DIR}}/<sessionId>-<short-summary>.js
|
|
151
149
|
```
|
|
152
150
|
|
|
153
151
|
## API Documentation
|