framer-dalton 0.0.21 → 0.0.22
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 +358 -48
- package/dist/start-relay-server.js +167 -58
- package/docs/skills/framer-canvas-editing-project.md +6 -14
- package/docs/skills/framer.md +28 -289
- package/package.json +2 -2
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs4 from 'fs';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import 'child_process';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { createTRPCClient, httpLink } from '@trpc/client';
|
|
7
|
+
import crypto, { randomUUID, timingSafeEqual } from 'crypto';
|
|
7
8
|
import http from 'http';
|
|
8
9
|
import { createHTTPHandler } from '@trpc/server/adapters/standalone';
|
|
9
10
|
import { initTRPC, TRPCError } from '@trpc/server';
|
|
10
11
|
import { z } from 'zod';
|
|
11
12
|
import { connect } from 'framer-api';
|
|
12
|
-
import crypto, { randomUUID } from 'crypto';
|
|
13
13
|
import { createRequire } from 'module';
|
|
14
14
|
import * as vm from 'vm';
|
|
15
15
|
|
|
16
|
-
/* @framer/ai relay server v0.0.
|
|
16
|
+
/* @framer/ai relay server v0.0.22 */
|
|
17
17
|
var __defProp = Object.defineProperty;
|
|
18
18
|
var __knownSymbol = (name2, symbol) => (symbol = Symbol[name2]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name2);
|
|
19
19
|
var __typeError = (msg) => {
|
|
@@ -90,17 +90,67 @@ var initialized = false;
|
|
|
90
90
|
function ensureLogDir() {
|
|
91
91
|
if (initialized) return;
|
|
92
92
|
const dir = path.dirname(logPath);
|
|
93
|
-
|
|
93
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
94
94
|
initialized = true;
|
|
95
95
|
}
|
|
96
96
|
__name(ensureLogDir, "ensureLogDir");
|
|
97
97
|
function log(message) {
|
|
98
98
|
ensureLogDir();
|
|
99
99
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
100
|
-
|
|
100
|
+
fs4.appendFileSync(logPath, `${timestamp} ${message}
|
|
101
101
|
`);
|
|
102
102
|
}
|
|
103
103
|
__name(log, "log");
|
|
104
|
+
function getConfigDir() {
|
|
105
|
+
if (process.env.XDG_CONFIG_HOME) {
|
|
106
|
+
return path.join(process.env.XDG_CONFIG_HOME, "framer");
|
|
107
|
+
}
|
|
108
|
+
if (process.platform === "win32") {
|
|
109
|
+
return path.join(process.env.APPDATA || os.homedir(), "framer");
|
|
110
|
+
}
|
|
111
|
+
return path.join(os.homedir(), ".config", "framer");
|
|
112
|
+
}
|
|
113
|
+
__name(getConfigDir, "getConfigDir");
|
|
114
|
+
function ensureConfigDir() {
|
|
115
|
+
const configDir = getConfigDir();
|
|
116
|
+
fs4.mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
117
|
+
}
|
|
118
|
+
__name(ensureConfigDir, "ensureConfigDir");
|
|
119
|
+
|
|
120
|
+
// src/config/relay-token.ts
|
|
121
|
+
function getRelayTokenPath() {
|
|
122
|
+
return path.join(getConfigDir(), "relay-token");
|
|
123
|
+
}
|
|
124
|
+
__name(getRelayTokenPath, "getRelayTokenPath");
|
|
125
|
+
function generateRelayToken() {
|
|
126
|
+
return crypto.randomBytes(32).toString("base64url");
|
|
127
|
+
}
|
|
128
|
+
__name(generateRelayToken, "generateRelayToken");
|
|
129
|
+
function readRelayToken() {
|
|
130
|
+
try {
|
|
131
|
+
const token = fs4.readFileSync(getRelayTokenPath(), "utf-8").trim();
|
|
132
|
+
return token.length > 0 ? token : null;
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
__name(readRelayToken, "readRelayToken");
|
|
138
|
+
function writeRelayToken(token) {
|
|
139
|
+
ensureConfigDir();
|
|
140
|
+
fs4.writeFileSync(getRelayTokenPath(), token, { mode: 384 });
|
|
141
|
+
}
|
|
142
|
+
__name(writeRelayToken, "writeRelayToken");
|
|
143
|
+
function clearRelayToken(expectedToken) {
|
|
144
|
+
const filePath = getRelayTokenPath();
|
|
145
|
+
if (expectedToken) {
|
|
146
|
+
const currentToken = readRelayToken();
|
|
147
|
+
if (currentToken !== expectedToken) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
fs4.rmSync(filePath, { force: true });
|
|
152
|
+
}
|
|
153
|
+
__name(clearRelayToken, "clearRelayToken");
|
|
104
154
|
function debug(tag, message) {
|
|
105
155
|
return;
|
|
106
156
|
}
|
|
@@ -109,7 +159,7 @@ __name(debug, "debug");
|
|
|
109
159
|
// src/version.ts
|
|
110
160
|
var VERSION = (
|
|
111
161
|
// typeof is used to ensure this can be used just via tsx or node etc. without build
|
|
112
|
-
"0.0.
|
|
162
|
+
"0.0.22"
|
|
113
163
|
);
|
|
114
164
|
|
|
115
165
|
// src/relay-client.ts
|
|
@@ -119,7 +169,14 @@ var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
|
|
|
119
169
|
createTRPCClient({
|
|
120
170
|
links: [
|
|
121
171
|
httpLink({
|
|
122
|
-
url: `http://127.0.0.1:${RELAY_PORT}
|
|
172
|
+
url: `http://127.0.0.1:${RELAY_PORT}`,
|
|
173
|
+
headers() {
|
|
174
|
+
const token = readRelayToken();
|
|
175
|
+
if (!token) {
|
|
176
|
+
return {};
|
|
177
|
+
}
|
|
178
|
+
return { Authorization: `Bearer ${token}` };
|
|
179
|
+
}
|
|
123
180
|
})
|
|
124
181
|
]
|
|
125
182
|
});
|
|
@@ -186,11 +243,11 @@ var ScopedFS = class {
|
|
|
186
243
|
// Sync methods
|
|
187
244
|
readFileSync = /* @__PURE__ */ __name((filePath, options) => {
|
|
188
245
|
const resolved = this.resolvePath(filePath.toString());
|
|
189
|
-
return
|
|
246
|
+
return fs4.readFileSync(resolved, options);
|
|
190
247
|
}, "readFileSync");
|
|
191
248
|
writeFileSync = /* @__PURE__ */ __name((filePath, data, options) => {
|
|
192
249
|
const resolved = this.resolvePath(filePath.toString());
|
|
193
|
-
|
|
250
|
+
fs4.writeFileSync(
|
|
194
251
|
resolved,
|
|
195
252
|
data,
|
|
196
253
|
options
|
|
@@ -198,7 +255,7 @@ var ScopedFS = class {
|
|
|
198
255
|
}, "writeFileSync");
|
|
199
256
|
appendFileSync = /* @__PURE__ */ __name((filePath, data, options) => {
|
|
200
257
|
const resolved = this.resolvePath(filePath.toString());
|
|
201
|
-
|
|
258
|
+
fs4.appendFileSync(
|
|
202
259
|
resolved,
|
|
203
260
|
data,
|
|
204
261
|
options
|
|
@@ -206,76 +263,76 @@ var ScopedFS = class {
|
|
|
206
263
|
}, "appendFileSync");
|
|
207
264
|
readdirSync = /* @__PURE__ */ __name((dirPath, options) => {
|
|
208
265
|
const resolved = this.resolvePath(dirPath.toString());
|
|
209
|
-
return
|
|
266
|
+
return fs4.readdirSync(resolved, options);
|
|
210
267
|
}, "readdirSync");
|
|
211
268
|
mkdirSync = /* @__PURE__ */ __name((dirPath, options) => {
|
|
212
269
|
const resolved = this.resolvePath(dirPath.toString());
|
|
213
|
-
return
|
|
270
|
+
return fs4.mkdirSync(resolved, options);
|
|
214
271
|
}, "mkdirSync");
|
|
215
272
|
rmdirSync = /* @__PURE__ */ __name((dirPath, options) => {
|
|
216
273
|
const resolved = this.resolvePath(dirPath.toString());
|
|
217
|
-
|
|
274
|
+
fs4.rmdirSync(resolved, options);
|
|
218
275
|
}, "rmdirSync");
|
|
219
276
|
unlinkSync = /* @__PURE__ */ __name((filePath) => {
|
|
220
277
|
const resolved = this.resolvePath(filePath.toString());
|
|
221
|
-
|
|
278
|
+
fs4.unlinkSync(resolved);
|
|
222
279
|
}, "unlinkSync");
|
|
223
280
|
statSync = /* @__PURE__ */ __name((filePath, options) => {
|
|
224
281
|
const resolved = this.resolvePath(filePath.toString());
|
|
225
|
-
return
|
|
282
|
+
return fs4.statSync(resolved, options);
|
|
226
283
|
}, "statSync");
|
|
227
284
|
lstatSync = /* @__PURE__ */ __name((filePath, options) => {
|
|
228
285
|
const resolved = this.resolvePath(filePath.toString());
|
|
229
|
-
return
|
|
286
|
+
return fs4.lstatSync(resolved, options);
|
|
230
287
|
}, "lstatSync");
|
|
231
288
|
existsSync = /* @__PURE__ */ __name((filePath) => {
|
|
232
289
|
try {
|
|
233
290
|
const resolved = this.resolvePath(filePath.toString());
|
|
234
|
-
return
|
|
291
|
+
return fs4.existsSync(resolved);
|
|
235
292
|
} catch {
|
|
236
293
|
return false;
|
|
237
294
|
}
|
|
238
295
|
}, "existsSync");
|
|
239
296
|
accessSync = /* @__PURE__ */ __name((filePath, mode) => {
|
|
240
297
|
const resolved = this.resolvePath(filePath.toString());
|
|
241
|
-
|
|
298
|
+
fs4.accessSync(resolved, mode);
|
|
242
299
|
}, "accessSync");
|
|
243
300
|
copyFileSync = /* @__PURE__ */ __name((src, dest, mode) => {
|
|
244
301
|
const resolvedSrc = this.resolvePath(src.toString());
|
|
245
302
|
const resolvedDest = this.resolvePath(dest.toString());
|
|
246
|
-
|
|
303
|
+
fs4.copyFileSync(resolvedSrc, resolvedDest, mode);
|
|
247
304
|
}, "copyFileSync");
|
|
248
305
|
renameSync = /* @__PURE__ */ __name((oldPath, newPath) => {
|
|
249
306
|
const resolvedOld = this.resolvePath(oldPath.toString());
|
|
250
307
|
const resolvedNew = this.resolvePath(newPath.toString());
|
|
251
|
-
|
|
308
|
+
fs4.renameSync(resolvedOld, resolvedNew);
|
|
252
309
|
}, "renameSync");
|
|
253
310
|
rmSync = /* @__PURE__ */ __name((filePath, options) => {
|
|
254
311
|
const resolved = this.resolvePath(filePath.toString());
|
|
255
|
-
|
|
312
|
+
fs4.rmSync(resolved, options);
|
|
256
313
|
}, "rmSync");
|
|
257
314
|
// Stream methods
|
|
258
315
|
createReadStream = /* @__PURE__ */ __name((filePath, options) => {
|
|
259
316
|
const resolved = this.resolvePath(filePath.toString());
|
|
260
|
-
return
|
|
317
|
+
return fs4.createReadStream(resolved, options);
|
|
261
318
|
}, "createReadStream");
|
|
262
319
|
createWriteStream = /* @__PURE__ */ __name((filePath, options) => {
|
|
263
320
|
const resolved = this.resolvePath(filePath.toString());
|
|
264
|
-
return
|
|
321
|
+
return fs4.createWriteStream(resolved, options);
|
|
265
322
|
}, "createWriteStream");
|
|
266
323
|
// Promise-based API (fs.promises equivalent)
|
|
267
324
|
get promises() {
|
|
268
325
|
return {
|
|
269
326
|
readFile: /* @__PURE__ */ __name(async (filePath, options) => {
|
|
270
327
|
const resolved = this.resolvePath(filePath.toString());
|
|
271
|
-
return
|
|
328
|
+
return fs4.promises.readFile(
|
|
272
329
|
resolved,
|
|
273
330
|
options
|
|
274
331
|
);
|
|
275
332
|
}, "readFile"),
|
|
276
333
|
writeFile: /* @__PURE__ */ __name(async (filePath, data, options) => {
|
|
277
334
|
const resolved = this.resolvePath(filePath.toString());
|
|
278
|
-
return
|
|
335
|
+
return fs4.promises.writeFile(
|
|
279
336
|
resolved,
|
|
280
337
|
data,
|
|
281
338
|
options
|
|
@@ -283,7 +340,7 @@ var ScopedFS = class {
|
|
|
283
340
|
}, "writeFile"),
|
|
284
341
|
appendFile: /* @__PURE__ */ __name(async (filePath, data, options) => {
|
|
285
342
|
const resolved = this.resolvePath(filePath.toString());
|
|
286
|
-
return
|
|
343
|
+
return fs4.promises.appendFile(
|
|
287
344
|
resolved,
|
|
288
345
|
data,
|
|
289
346
|
options
|
|
@@ -291,48 +348,48 @@ var ScopedFS = class {
|
|
|
291
348
|
}, "appendFile"),
|
|
292
349
|
readdir: /* @__PURE__ */ __name(async (dirPath, options) => {
|
|
293
350
|
const resolved = this.resolvePath(dirPath.toString());
|
|
294
|
-
return
|
|
351
|
+
return fs4.promises.readdir(
|
|
295
352
|
resolved,
|
|
296
353
|
options
|
|
297
354
|
);
|
|
298
355
|
}, "readdir"),
|
|
299
356
|
mkdir: /* @__PURE__ */ __name(async (dirPath, options) => {
|
|
300
357
|
const resolved = this.resolvePath(dirPath.toString());
|
|
301
|
-
return
|
|
358
|
+
return fs4.promises.mkdir(resolved, options);
|
|
302
359
|
}, "mkdir"),
|
|
303
360
|
rmdir: /* @__PURE__ */ __name(async (dirPath, options) => {
|
|
304
361
|
const resolved = this.resolvePath(dirPath.toString());
|
|
305
|
-
return
|
|
362
|
+
return fs4.promises.rmdir(resolved, options);
|
|
306
363
|
}, "rmdir"),
|
|
307
364
|
unlink: /* @__PURE__ */ __name(async (filePath) => {
|
|
308
365
|
const resolved = this.resolvePath(filePath.toString());
|
|
309
|
-
return
|
|
366
|
+
return fs4.promises.unlink(resolved);
|
|
310
367
|
}, "unlink"),
|
|
311
368
|
stat: /* @__PURE__ */ __name(async (filePath, options) => {
|
|
312
369
|
const resolved = this.resolvePath(filePath.toString());
|
|
313
|
-
return
|
|
370
|
+
return fs4.promises.stat(resolved, options);
|
|
314
371
|
}, "stat"),
|
|
315
372
|
access: /* @__PURE__ */ __name(async (filePath, mode) => {
|
|
316
373
|
const resolved = this.resolvePath(filePath.toString());
|
|
317
|
-
return
|
|
374
|
+
return fs4.promises.access(resolved, mode);
|
|
318
375
|
}, "access"),
|
|
319
376
|
copyFile: /* @__PURE__ */ __name(async (src, dest, mode) => {
|
|
320
377
|
const resolvedSrc = this.resolvePath(src.toString());
|
|
321
378
|
const resolvedDest = this.resolvePath(dest.toString());
|
|
322
|
-
return
|
|
379
|
+
return fs4.promises.copyFile(resolvedSrc, resolvedDest, mode);
|
|
323
380
|
}, "copyFile"),
|
|
324
381
|
rename: /* @__PURE__ */ __name(async (oldPath, newPath) => {
|
|
325
382
|
const resolvedOld = this.resolvePath(oldPath.toString());
|
|
326
383
|
const resolvedNew = this.resolvePath(newPath.toString());
|
|
327
|
-
return
|
|
384
|
+
return fs4.promises.rename(resolvedOld, resolvedNew);
|
|
328
385
|
}, "rename"),
|
|
329
386
|
rm: /* @__PURE__ */ __name(async (filePath, options) => {
|
|
330
387
|
const resolved = this.resolvePath(filePath.toString());
|
|
331
|
-
return
|
|
388
|
+
return fs4.promises.rm(resolved, options);
|
|
332
389
|
}, "rm")
|
|
333
390
|
};
|
|
334
391
|
}
|
|
335
|
-
constants =
|
|
392
|
+
constants = fs4.constants;
|
|
336
393
|
};
|
|
337
394
|
|
|
338
395
|
// src/execute.ts
|
|
@@ -438,7 +495,7 @@ async function execute(session, code, options = {}) {
|
|
|
438
495
|
URLSearchParams,
|
|
439
496
|
TextEncoder,
|
|
440
497
|
TextDecoder,
|
|
441
|
-
crypto,
|
|
498
|
+
crypto: crypto,
|
|
442
499
|
AbortController,
|
|
443
500
|
AbortSignal,
|
|
444
501
|
structuredClone
|
|
@@ -540,21 +597,6 @@ function formatValue(value) {
|
|
|
540
597
|
}
|
|
541
598
|
}
|
|
542
599
|
__name(formatValue, "formatValue");
|
|
543
|
-
function getConfigDir() {
|
|
544
|
-
if (process.env.XDG_CONFIG_HOME) {
|
|
545
|
-
return path.join(process.env.XDG_CONFIG_HOME, "framer");
|
|
546
|
-
}
|
|
547
|
-
if (process.platform === "win32") {
|
|
548
|
-
return path.join(process.env.APPDATA || os.homedir(), "framer");
|
|
549
|
-
}
|
|
550
|
-
return path.join(os.homedir(), ".config", "framer");
|
|
551
|
-
}
|
|
552
|
-
__name(getConfigDir, "getConfigDir");
|
|
553
|
-
function ensureConfigDir() {
|
|
554
|
-
const configDir = getConfigDir();
|
|
555
|
-
fs2.mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
556
|
-
}
|
|
557
|
-
__name(ensureConfigDir, "ensureConfigDir");
|
|
558
600
|
var DEFAULT_FS_POLL_INTERVAL_MS = 1e3;
|
|
559
601
|
var fsPollIntervalMs = DEFAULT_FS_POLL_INTERVAL_MS;
|
|
560
602
|
var SettingsWatcher = class {
|
|
@@ -566,7 +608,7 @@ var SettingsWatcher = class {
|
|
|
566
608
|
__name(this, "SettingsWatcher");
|
|
567
609
|
}
|
|
568
610
|
start(callback) {
|
|
569
|
-
|
|
611
|
+
fs4.watchFile(
|
|
570
612
|
this.settingsPath,
|
|
571
613
|
{ persistent: false, interval: fsPollIntervalMs },
|
|
572
614
|
(curr, prev) => {
|
|
@@ -583,7 +625,7 @@ var SettingsWatcher = class {
|
|
|
583
625
|
return this;
|
|
584
626
|
}
|
|
585
627
|
stop() {
|
|
586
|
-
|
|
628
|
+
fs4.unwatchFile(this.settingsPath);
|
|
587
629
|
return this;
|
|
588
630
|
}
|
|
589
631
|
};
|
|
@@ -620,7 +662,7 @@ __name(getSettings, "getSettings");
|
|
|
620
662
|
function readSettingsFile() {
|
|
621
663
|
const settingsPath = getSettingsPath();
|
|
622
664
|
try {
|
|
623
|
-
const raw = JSON.parse(
|
|
665
|
+
const raw = JSON.parse(fs4.readFileSync(settingsPath, "utf-8"));
|
|
624
666
|
const result = SettingsFileSchema.parse(raw);
|
|
625
667
|
return {
|
|
626
668
|
machineId: result.machineId ?? DEFAULT_SETTINGS.machineId,
|
|
@@ -639,7 +681,7 @@ function setSettings(newSettings) {
|
|
|
639
681
|
__name(setSettings, "setSettings");
|
|
640
682
|
function writeSettingsFile(settings2) {
|
|
641
683
|
ensureConfigDir();
|
|
642
|
-
|
|
684
|
+
fs4.writeFileSync(getSettingsPath(), JSON.stringify(settings2, null, " "), {
|
|
643
685
|
mode: 384
|
|
644
686
|
});
|
|
645
687
|
}
|
|
@@ -1205,12 +1247,74 @@ var appRouter = t.router({
|
|
|
1205
1247
|
var IDLE_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
|
|
1206
1248
|
var IDLE_CHECK_INTERVAL_MS = 60 * 1e3;
|
|
1207
1249
|
var trpcHandler = createHTTPHandler({ router: appRouter });
|
|
1250
|
+
function getHostHeader(req) {
|
|
1251
|
+
const host = req.headers.host;
|
|
1252
|
+
if (typeof host === "string") {
|
|
1253
|
+
return host;
|
|
1254
|
+
}
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
__name(getHostHeader, "getHostHeader");
|
|
1258
|
+
function isAllowedHost(hostHeader, listeningPort) {
|
|
1259
|
+
if (!hostHeader || listeningPort === null) {
|
|
1260
|
+
return false;
|
|
1261
|
+
}
|
|
1262
|
+
const normalizedHost = hostHeader.trim().toLowerCase();
|
|
1263
|
+
return normalizedHost === `127.0.0.1:${listeningPort}`;
|
|
1264
|
+
}
|
|
1265
|
+
__name(isAllowedHost, "isAllowedHost");
|
|
1266
|
+
function getAuthorizationHeader(req) {
|
|
1267
|
+
const header = req.headers.authorization;
|
|
1268
|
+
if (typeof header === "string") {
|
|
1269
|
+
return header;
|
|
1270
|
+
}
|
|
1271
|
+
return null;
|
|
1272
|
+
}
|
|
1273
|
+
__name(getAuthorizationHeader, "getAuthorizationHeader");
|
|
1274
|
+
function isAuthorized(authorizationHeader, expectedToken) {
|
|
1275
|
+
if (!authorizationHeader) {
|
|
1276
|
+
return false;
|
|
1277
|
+
}
|
|
1278
|
+
const match = /^Bearer\s+(.+)$/i.exec(authorizationHeader.trim());
|
|
1279
|
+
if (!match) {
|
|
1280
|
+
return false;
|
|
1281
|
+
}
|
|
1282
|
+
const providedToken = match[1] ?? "";
|
|
1283
|
+
const expectedBuffer = Buffer.from(expectedToken);
|
|
1284
|
+
const providedBuffer = Buffer.from(providedToken);
|
|
1285
|
+
if (expectedBuffer.length !== providedBuffer.length) {
|
|
1286
|
+
return false;
|
|
1287
|
+
}
|
|
1288
|
+
return timingSafeEqual(expectedBuffer, providedBuffer);
|
|
1289
|
+
}
|
|
1290
|
+
__name(isAuthorized, "isAuthorized");
|
|
1291
|
+
function getListeningPort(server) {
|
|
1292
|
+
const address = server.address();
|
|
1293
|
+
if (!address || typeof address === "string") {
|
|
1294
|
+
return null;
|
|
1295
|
+
}
|
|
1296
|
+
return address.port;
|
|
1297
|
+
}
|
|
1298
|
+
__name(getListeningPort, "getListeningPort");
|
|
1208
1299
|
async function startRelayServer(port = RELAY_PORT) {
|
|
1209
1300
|
let lastActivityAt = Date.now();
|
|
1301
|
+
const relayToken = generateRelayToken();
|
|
1210
1302
|
const server = http.createServer((req, res) => {
|
|
1303
|
+
const listeningPort = getListeningPort(server);
|
|
1304
|
+
if (!isAllowedHost(getHostHeader(req), listeningPort)) {
|
|
1305
|
+
res.writeHead(403).end("Forbidden");
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
if (!isAuthorized(getAuthorizationHeader(req), relayToken)) {
|
|
1309
|
+
res.writeHead(401).end("Unauthorized");
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1211
1312
|
lastActivityAt = Date.now();
|
|
1212
1313
|
trpcHandler(req, res);
|
|
1213
1314
|
});
|
|
1315
|
+
server.on("close", () => {
|
|
1316
|
+
clearRelayToken(relayToken);
|
|
1317
|
+
});
|
|
1214
1318
|
const idleCheck = setInterval(() => {
|
|
1215
1319
|
const idleMs = Date.now() - lastActivityAt;
|
|
1216
1320
|
if (idleMs >= IDLE_TIMEOUT_MS) {
|
|
@@ -1225,9 +1329,14 @@ async function startRelayServer(port = RELAY_PORT) {
|
|
|
1225
1329
|
}, IDLE_CHECK_INTERVAL_MS);
|
|
1226
1330
|
idleCheck.unref();
|
|
1227
1331
|
return new Promise((resolve, reject) => {
|
|
1228
|
-
server.on("error",
|
|
1332
|
+
server.on("error", (error) => {
|
|
1333
|
+
clearRelayToken(relayToken);
|
|
1334
|
+
reject(error);
|
|
1335
|
+
});
|
|
1229
1336
|
server.listen(port, "127.0.0.1", () => {
|
|
1230
|
-
|
|
1337
|
+
writeRelayToken(relayToken);
|
|
1338
|
+
const listeningPort = getListeningPort(server) ?? port;
|
|
1339
|
+
log(`started v${VERSION} on port ${listeningPort}`);
|
|
1231
1340
|
resolve(server);
|
|
1232
1341
|
});
|
|
1233
1342
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: {{SKILL_NAME}}
|
|
3
|
-
description: "Project-scoped Framer
|
|
3
|
+
description: "Project-scoped Framer skill for project {{PROJECT_ID}}. Covers canvas editing, project reads, change review, publishing, image sourcing, and component operations. Very important: never load this skill without having already read the `framer` skill and without having already run `session new`, which will dynamically update this skill."
|
|
4
4
|
allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)", "Read({{FRAMER_TEMPORARY_DIR}}/*)", "Write({{FRAMER_TEMPORARY_DIR}}/*)"]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -13,14 +13,10 @@ allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)",
|
|
|
13
13
|
|
|
14
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
|
- During normal task execution, do not call `framer.getAgentSystemPrompt()` or `framer.getAgentContext()`. This skill already includes `getAgentContext({ pagePath: "/" })`.
|
|
16
|
-
- `
|
|
17
|
-
- `npx framer-dalton
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
- When setting text content, use raw Unicode characters directly (for example `->`, not `\u2192`).
|
|
21
|
-
- Regenerate the embedded prompt and context by running `session new` again for the same project.
|
|
22
|
-
- In rich text, block-level properties (for example line height or alignment) belong on block or parent targets, not `TextRun` targets.
|
|
23
|
-
- `IconNode` `$control__icon` is immutable after creation. To change it, delete and recreate the node with the desired icon value.
|
|
16
|
+
- `read-project` and `apply-changes` are one-liner CLI shortcuts for the high-frequency canvas-editing methods. Use them to read state and apply DSL inline without writing a file first:
|
|
17
|
+
- `npx framer-dalton read-project -s <sessionId> -q <queries> -p <pagePath>` — equivalent to `framer.readProjectForAgent(queries, { pagePath })`. Query types are documented in the embedded prompt below.
|
|
18
|
+
- `npx framer-dalton apply-changes -s <sessionId> -p <pagePath> -e <dsl>` — equivalent to `framer.applyAgentChanges(dsl, { pagePath })`.
|
|
19
|
+
- The embedded prompt below also references other agent-surface methods (`reviewChangesForAgent`, `publishForAgent`, `queryImagesForAgent`, `flattenComponentInstanceForAgent`, `makeExternalComponentLocalForAgent`, and `getNodeForAgent` / `getNodesForAgent` / `getNodesOfTypesForAgent` / `getScopeNodeForAgent` / `getGroundNodeForAgent` / `getParentNodeForAgent` / `getAncestorsForAgent`). These have no dedicated shortcut; invoke via `npx framer-dalton exec -s <sessionId> -e 'console.log(await framer.<method>(<args>))'`.
|
|
24
20
|
|
|
25
21
|
## Workflow Loop
|
|
26
22
|
|
|
@@ -42,11 +38,7 @@ This ensures progress is streamed to the user in small, visible increments.
|
|
|
42
38
|
|
|
43
39
|
## Live Agent System Prompt
|
|
44
40
|
|
|
45
|
-
This is the static
|
|
46
|
-
|
|
47
|
-
- command syntax for `apply-changes`
|
|
48
|
-
- query types and parameters for `read-project`
|
|
49
|
-
- design rules and examples used by the canvas editing flow
|
|
41
|
+
This is the static prompt returned by `framer.getAgentSystemPrompt()`.
|
|
50
42
|
|
|
51
43
|
{{CANVAS_PROMPT}}
|
|
52
44
|
|