framer-dalton 0.0.27 → 0.0.28
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 +87 -32
- package/dist/start-relay-server.js +161 -57
- package/docs/skills/framer-project.md +107 -71
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -4,15 +4,15 @@ import path7 from 'path';
|
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import crypto, { randomUUID } from 'crypto';
|
|
6
6
|
import http from 'http';
|
|
7
|
-
import { spawn, execFile } from 'child_process';
|
|
7
|
+
import { spawn, execFile, execFileSync } from 'child_process';
|
|
8
8
|
import { z } from 'zod';
|
|
9
|
-
import
|
|
9
|
+
import os2 from 'os';
|
|
10
10
|
import { ErrorCode, FramerAPIError } from 'framer-api';
|
|
11
11
|
import net from 'net';
|
|
12
12
|
import { fileURLToPath } from 'url';
|
|
13
13
|
import { createTRPCClient, httpLink } from '@trpc/client';
|
|
14
14
|
|
|
15
|
-
/* @framer/ai CLI v0.0.
|
|
15
|
+
/* @framer/ai CLI v0.0.28 */
|
|
16
16
|
var __defProp = Object.defineProperty;
|
|
17
17
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
18
18
|
|
|
@@ -100,9 +100,9 @@ function getConfigDir() {
|
|
|
100
100
|
return path7.join(process.env.XDG_CONFIG_HOME, "framer");
|
|
101
101
|
}
|
|
102
102
|
if (process.platform === "win32") {
|
|
103
|
-
return path7.join(process.env.APPDATA ||
|
|
103
|
+
return path7.join(process.env.APPDATA || os2.homedir(), "framer");
|
|
104
104
|
}
|
|
105
|
-
return path7.join(
|
|
105
|
+
return path7.join(os2.homedir(), ".config", "framer");
|
|
106
106
|
}
|
|
107
107
|
__name(getConfigDir, "getConfigDir");
|
|
108
108
|
function ensureConfigDir() {
|
|
@@ -375,15 +375,59 @@ __name(markTelemetryNoticeShown, "markTelemetryNoticeShown");
|
|
|
375
375
|
// src/version.ts
|
|
376
376
|
var VERSION = (
|
|
377
377
|
// typeof is used to ensure this can be used just via tsx or node etc. without build
|
|
378
|
-
"0.0.
|
|
378
|
+
"0.0.28"
|
|
379
379
|
);
|
|
380
380
|
var trackingEndpoint = "https://events.framer.com/track";
|
|
381
381
|
var inProgressTrackings = /* @__PURE__ */ new Set();
|
|
382
|
-
|
|
382
|
+
var cachedSharedFields;
|
|
383
|
+
function getOsName(platform) {
|
|
384
|
+
switch (platform) {
|
|
385
|
+
case "darwin":
|
|
386
|
+
return "macos";
|
|
387
|
+
case "win32":
|
|
388
|
+
return "windows";
|
|
389
|
+
case "linux":
|
|
390
|
+
return "linux";
|
|
391
|
+
default:
|
|
392
|
+
return "unknown";
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
__name(getOsName, "getOsName");
|
|
396
|
+
function getMacOsVersion() {
|
|
397
|
+
try {
|
|
398
|
+
const version = execFileSync("/usr/bin/sw_vers", ["-productVersion"], {
|
|
399
|
+
encoding: "utf8",
|
|
400
|
+
timeout: 1e3
|
|
401
|
+
}).trim();
|
|
402
|
+
return version || "unknown";
|
|
403
|
+
} catch {
|
|
404
|
+
return "unknown";
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
__name(getMacOsVersion, "getMacOsVersion");
|
|
408
|
+
function getOsVersion(platform) {
|
|
409
|
+
if (platform === "darwin") return getMacOsVersion();
|
|
410
|
+
const version = os2.release().trim();
|
|
411
|
+
return version || "unknown";
|
|
412
|
+
}
|
|
413
|
+
__name(getOsVersion, "getOsVersion");
|
|
414
|
+
function getOsMetadata() {
|
|
415
|
+
const platform = os2.platform();
|
|
416
|
+
const arch = os2.arch();
|
|
383
417
|
return {
|
|
418
|
+
arch: arch || "unknown",
|
|
419
|
+
osName: getOsName(platform),
|
|
420
|
+
osVersion: getOsVersion(platform)
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
__name(getOsMetadata, "getOsMetadata");
|
|
424
|
+
function sharedFields() {
|
|
425
|
+
cachedSharedFields ??= {
|
|
384
426
|
machineId: getMachineId(),
|
|
385
|
-
cliVersion: VERSION
|
|
427
|
+
cliVersion: VERSION,
|
|
428
|
+
...getOsMetadata()
|
|
386
429
|
};
|
|
430
|
+
return cachedSharedFields;
|
|
387
431
|
}
|
|
388
432
|
__name(sharedFields, "sharedFields");
|
|
389
433
|
function wrapEvent(event) {
|
|
@@ -396,16 +440,19 @@ function wrapEvent(event) {
|
|
|
396
440
|
};
|
|
397
441
|
}
|
|
398
442
|
__name(wrapEvent, "wrapEvent");
|
|
399
|
-
function postEvent(
|
|
443
|
+
function postEvent(createEvent) {
|
|
400
444
|
if (!isTelemetryEnabled()) return false;
|
|
401
445
|
if (process.env.NODE_ENV === "test" && true) return false;
|
|
446
|
+
const event = createEvent();
|
|
447
|
+
const body = JSON.stringify([wrapEvent(event)]);
|
|
448
|
+
debug("tracking", `sending ${event.event} to ${trackingEndpoint}: ${body}`);
|
|
402
449
|
const promise = fetch(trackingEndpoint, {
|
|
403
450
|
method: "POST",
|
|
404
451
|
headers: {
|
|
405
452
|
"Content-Type": "application/json",
|
|
406
453
|
"User-Agent": `framer-dalton/${VERSION}`
|
|
407
454
|
},
|
|
408
|
-
body
|
|
455
|
+
body,
|
|
409
456
|
signal: AbortSignal.timeout(
|
|
410
457
|
5e3
|
|
411
458
|
/* 5 seconds */
|
|
@@ -434,35 +481,43 @@ function waitForTrackingToFinish() {
|
|
|
434
481
|
}
|
|
435
482
|
__name(waitForTrackingToFinish, "waitForTrackingToFinish");
|
|
436
483
|
function trackAuthStart(payload) {
|
|
437
|
-
postEvent(
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
484
|
+
postEvent(
|
|
485
|
+
() => ({
|
|
486
|
+
event: "local_agents_auth_start",
|
|
487
|
+
...sharedFields(),
|
|
488
|
+
...payload
|
|
489
|
+
})
|
|
490
|
+
);
|
|
442
491
|
}
|
|
443
492
|
__name(trackAuthStart, "trackAuthStart");
|
|
444
493
|
function trackAuthSuccess(payload) {
|
|
445
|
-
postEvent(
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
494
|
+
postEvent(
|
|
495
|
+
() => ({
|
|
496
|
+
event: "local_agents_auth_success",
|
|
497
|
+
...sharedFields(),
|
|
498
|
+
...payload
|
|
499
|
+
})
|
|
500
|
+
);
|
|
450
501
|
}
|
|
451
502
|
__name(trackAuthSuccess, "trackAuthSuccess");
|
|
452
503
|
function trackSkillsInstall(payload) {
|
|
453
|
-
postEvent(
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
504
|
+
postEvent(
|
|
505
|
+
() => ({
|
|
506
|
+
event: "local_agents_skills_install",
|
|
507
|
+
...sharedFields(),
|
|
508
|
+
...payload
|
|
509
|
+
})
|
|
510
|
+
);
|
|
458
511
|
}
|
|
459
512
|
__name(trackSkillsInstall, "trackSkillsInstall");
|
|
460
513
|
function trackError(payload) {
|
|
461
|
-
postEvent(
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
514
|
+
postEvent(
|
|
515
|
+
() => ({
|
|
516
|
+
event: "local_agents_error",
|
|
517
|
+
...sharedFields(),
|
|
518
|
+
...payload
|
|
519
|
+
})
|
|
520
|
+
);
|
|
466
521
|
}
|
|
467
522
|
__name(trackError, "trackError");
|
|
468
523
|
|
|
@@ -16430,7 +16485,7 @@ async function waitForClaudeCodeSkillDiscovery() {
|
|
|
16430
16485
|
);
|
|
16431
16486
|
}
|
|
16432
16487
|
__name(waitForClaudeCodeSkillDiscovery, "waitForClaudeCodeSkillDiscovery");
|
|
16433
|
-
var FRAMER_TEMPORARY_DIR = path7.join(
|
|
16488
|
+
var FRAMER_TEMPORARY_DIR = path7.join(os2.tmpdir(), "framer");
|
|
16434
16489
|
function ensureTemporaryDir() {
|
|
16435
16490
|
fs7.mkdirSync(FRAMER_TEMPORARY_DIR, { recursive: true });
|
|
16436
16491
|
}
|
|
@@ -16525,7 +16580,7 @@ function cleanupLegacyCanvasEditingSkills(skillRoots = getDefaultSkillRoots()) {
|
|
|
16525
16580
|
}
|
|
16526
16581
|
__name(cleanupLegacyCanvasEditingSkills, "cleanupLegacyCanvasEditingSkills");
|
|
16527
16582
|
function getDefaultSkillRoots() {
|
|
16528
|
-
const home =
|
|
16583
|
+
const home = os2.homedir();
|
|
16529
16584
|
return [
|
|
16530
16585
|
path7.join(home, ".agents", "skills"),
|
|
16531
16586
|
path7.join(home, ".claude", "skills")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs4 from 'fs';
|
|
2
|
-
import
|
|
2
|
+
import os5 from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import 'child_process';
|
|
4
|
+
import { execFileSync } from 'child_process';
|
|
5
5
|
import 'net';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { createTRPCClient, httpLink } from '@trpc/client';
|
|
@@ -14,7 +14,7 @@ import { z } from 'zod';
|
|
|
14
14
|
import { createRequire } from 'module';
|
|
15
15
|
import * as vm from 'vm';
|
|
16
16
|
|
|
17
|
-
/* @framer/ai relay server v0.0.
|
|
17
|
+
/* @framer/ai relay server v0.0.28 */
|
|
18
18
|
var __defProp = Object.defineProperty;
|
|
19
19
|
var __knownSymbol = (name2, symbol) => (symbol = Symbol[name2]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name2);
|
|
20
20
|
var __typeError = (msg) => {
|
|
@@ -72,22 +72,28 @@ Please upgrade: https://nodejs.org/`
|
|
|
72
72
|
);
|
|
73
73
|
process.exit(1);
|
|
74
74
|
}
|
|
75
|
+
var DEFAULT_MAX_LOG_FILE_BYTES = 1024 * 1024;
|
|
76
|
+
var DEFAULT_RETAINED_LOG_FILE_BYTES = 512 * 1024;
|
|
77
|
+
var LOG_TRIM_CHECK_INTERVAL = 10;
|
|
78
|
+
var maxLogFileBytes = DEFAULT_MAX_LOG_FILE_BYTES;
|
|
79
|
+
var retainedLogFileBytes = DEFAULT_RETAINED_LOG_FILE_BYTES;
|
|
75
80
|
function getLogPath() {
|
|
76
81
|
if (process.env.XDG_STATE_HOME) {
|
|
77
82
|
return path.join(process.env.XDG_STATE_HOME, "framer", "relay.log");
|
|
78
83
|
}
|
|
79
84
|
if (process.platform === "win32") {
|
|
80
85
|
return path.join(
|
|
81
|
-
process.env.APPDATA ||
|
|
86
|
+
process.env.APPDATA || os5.homedir(),
|
|
82
87
|
"framer",
|
|
83
88
|
"relay.log"
|
|
84
89
|
);
|
|
85
90
|
}
|
|
86
|
-
return path.join(
|
|
91
|
+
return path.join(os5.homedir(), ".local", "state", "framer", "relay.log");
|
|
87
92
|
}
|
|
88
93
|
__name(getLogPath, "getLogPath");
|
|
89
94
|
var logPath = getLogPath();
|
|
90
95
|
var initialized = false;
|
|
96
|
+
var logEntriesSinceTrimCheck = LOG_TRIM_CHECK_INTERVAL - 1;
|
|
91
97
|
function ensureLogDir() {
|
|
92
98
|
if (initialized) return;
|
|
93
99
|
const dir = path.dirname(logPath);
|
|
@@ -95,11 +101,44 @@ function ensureLogDir() {
|
|
|
95
101
|
initialized = true;
|
|
96
102
|
}
|
|
97
103
|
__name(ensureLogDir, "ensureLogDir");
|
|
104
|
+
function trimLogFileIfNeeded() {
|
|
105
|
+
logEntriesSinceTrimCheck += 1;
|
|
106
|
+
if (logEntriesSinceTrimCheck < LOG_TRIM_CHECK_INTERVAL) return;
|
|
107
|
+
logEntriesSinceTrimCheck = 0;
|
|
108
|
+
let size;
|
|
109
|
+
try {
|
|
110
|
+
size = fs4.statSync(logPath).size;
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
if (size <= maxLogFileBytes) return;
|
|
118
|
+
const retainedBytes = Math.min(retainedLogFileBytes, size);
|
|
119
|
+
const buffer = Buffer.alloc(retainedBytes);
|
|
120
|
+
const file = fs4.openSync(logPath, "r");
|
|
121
|
+
let bytesRead = 0;
|
|
122
|
+
try {
|
|
123
|
+
bytesRead = fs4.readSync(
|
|
124
|
+
file,
|
|
125
|
+
buffer,
|
|
126
|
+
0,
|
|
127
|
+
retainedBytes,
|
|
128
|
+
size - retainedBytes
|
|
129
|
+
);
|
|
130
|
+
} finally {
|
|
131
|
+
fs4.closeSync(file);
|
|
132
|
+
}
|
|
133
|
+
fs4.writeFileSync(logPath, buffer.subarray(0, bytesRead));
|
|
134
|
+
}
|
|
135
|
+
__name(trimLogFileIfNeeded, "trimLogFileIfNeeded");
|
|
98
136
|
function log(message) {
|
|
99
137
|
ensureLogDir();
|
|
100
138
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
101
139
|
fs4.appendFileSync(logPath, `${timestamp} ${message}
|
|
102
140
|
`);
|
|
141
|
+
trimLogFileIfNeeded();
|
|
103
142
|
}
|
|
104
143
|
__name(log, "log");
|
|
105
144
|
function getConfigDir() {
|
|
@@ -107,9 +146,9 @@ function getConfigDir() {
|
|
|
107
146
|
return path.join(process.env.XDG_CONFIG_HOME, "framer");
|
|
108
147
|
}
|
|
109
148
|
if (process.platform === "win32") {
|
|
110
|
-
return path.join(process.env.APPDATA ||
|
|
149
|
+
return path.join(process.env.APPDATA || os5.homedir(), "framer");
|
|
111
150
|
}
|
|
112
|
-
return path.join(
|
|
151
|
+
return path.join(os5.homedir(), ".config", "framer");
|
|
113
152
|
}
|
|
114
153
|
__name(getConfigDir, "getConfigDir");
|
|
115
154
|
function ensureConfigDir() {
|
|
@@ -160,7 +199,7 @@ __name(debug, "debug");
|
|
|
160
199
|
// src/version.ts
|
|
161
200
|
var VERSION = (
|
|
162
201
|
// typeof is used to ensure this can be used just via tsx or node etc. without build
|
|
163
|
-
"0.0.
|
|
202
|
+
"0.0.28"
|
|
164
203
|
);
|
|
165
204
|
|
|
166
205
|
// src/relay-client.ts
|
|
@@ -266,7 +305,7 @@ var ScopedFS = class {
|
|
|
266
305
|
}
|
|
267
306
|
allowedDirs;
|
|
268
307
|
constructor(allowedDirs) {
|
|
269
|
-
const defaultDirs = [process.cwd(), "/tmp",
|
|
308
|
+
const defaultDirs = [process.cwd(), "/tmp", os5.tmpdir()];
|
|
270
309
|
const dirs = allowedDirs ?? defaultDirs;
|
|
271
310
|
this.allowedDirs = [...new Set(dirs.map((d) => path.resolve(d)))];
|
|
272
311
|
}
|
|
@@ -533,7 +572,7 @@ async function execute(session, code, options = {}) {
|
|
|
533
572
|
output.push(args.map((arg) => formatValue(arg)).join(" "));
|
|
534
573
|
}, "info")
|
|
535
574
|
};
|
|
536
|
-
const scopedFs = cwd ? new ScopedFS([cwd, "/tmp",
|
|
575
|
+
const scopedFs = cwd ? new ScopedFS([cwd, "/tmp", os5.tmpdir()]) : new ScopedFS();
|
|
537
576
|
const sandboxedRequire = createSandboxedRequire(scopedFs);
|
|
538
577
|
const vmContextObj = {
|
|
539
578
|
// Framer API
|
|
@@ -795,11 +834,55 @@ function isTelemetryEnabled() {
|
|
|
795
834
|
__name(isTelemetryEnabled, "isTelemetryEnabled");
|
|
796
835
|
var trackingEndpoint = "https://events.framer.com/track";
|
|
797
836
|
var inProgressTrackings = /* @__PURE__ */ new Set();
|
|
798
|
-
|
|
837
|
+
var cachedSharedFields;
|
|
838
|
+
function getOsName(platform) {
|
|
839
|
+
switch (platform) {
|
|
840
|
+
case "darwin":
|
|
841
|
+
return "macos";
|
|
842
|
+
case "win32":
|
|
843
|
+
return "windows";
|
|
844
|
+
case "linux":
|
|
845
|
+
return "linux";
|
|
846
|
+
default:
|
|
847
|
+
return "unknown";
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
__name(getOsName, "getOsName");
|
|
851
|
+
function getMacOsVersion() {
|
|
852
|
+
try {
|
|
853
|
+
const version = execFileSync("/usr/bin/sw_vers", ["-productVersion"], {
|
|
854
|
+
encoding: "utf8",
|
|
855
|
+
timeout: 1e3
|
|
856
|
+
}).trim();
|
|
857
|
+
return version || "unknown";
|
|
858
|
+
} catch {
|
|
859
|
+
return "unknown";
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
__name(getMacOsVersion, "getMacOsVersion");
|
|
863
|
+
function getOsVersion(platform) {
|
|
864
|
+
if (platform === "darwin") return getMacOsVersion();
|
|
865
|
+
const version = os5.release().trim();
|
|
866
|
+
return version || "unknown";
|
|
867
|
+
}
|
|
868
|
+
__name(getOsVersion, "getOsVersion");
|
|
869
|
+
function getOsMetadata() {
|
|
870
|
+
const platform = os5.platform();
|
|
871
|
+
const arch = os5.arch();
|
|
799
872
|
return {
|
|
873
|
+
arch: arch || "unknown",
|
|
874
|
+
osName: getOsName(platform),
|
|
875
|
+
osVersion: getOsVersion(platform)
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
__name(getOsMetadata, "getOsMetadata");
|
|
879
|
+
function sharedFields() {
|
|
880
|
+
cachedSharedFields ??= {
|
|
800
881
|
machineId: getMachineId(),
|
|
801
|
-
cliVersion: VERSION
|
|
882
|
+
cliVersion: VERSION,
|
|
883
|
+
...getOsMetadata()
|
|
802
884
|
};
|
|
885
|
+
return cachedSharedFields;
|
|
803
886
|
}
|
|
804
887
|
__name(sharedFields, "sharedFields");
|
|
805
888
|
function wrapEvent(event) {
|
|
@@ -812,16 +895,19 @@ function wrapEvent(event) {
|
|
|
812
895
|
};
|
|
813
896
|
}
|
|
814
897
|
__name(wrapEvent, "wrapEvent");
|
|
815
|
-
function postEvent(
|
|
898
|
+
function postEvent(createEvent) {
|
|
816
899
|
if (!isTelemetryEnabled()) return false;
|
|
817
900
|
if (process.env.NODE_ENV === "test" && true) return false;
|
|
901
|
+
const event = createEvent();
|
|
902
|
+
const body = JSON.stringify([wrapEvent(event)]);
|
|
903
|
+
debug("tracking", `sending ${event.event} to ${trackingEndpoint}: ${body}`);
|
|
818
904
|
const promise = fetch(trackingEndpoint, {
|
|
819
905
|
method: "POST",
|
|
820
906
|
headers: {
|
|
821
907
|
"Content-Type": "application/json",
|
|
822
908
|
"User-Agent": `framer-dalton/${VERSION}`
|
|
823
909
|
},
|
|
824
|
-
body
|
|
910
|
+
body,
|
|
825
911
|
signal: AbortSignal.timeout(
|
|
826
912
|
5e3
|
|
827
913
|
/* 5 seconds */
|
|
@@ -850,75 +936,93 @@ function waitForTrackingToFinish() {
|
|
|
850
936
|
}
|
|
851
937
|
__name(waitForTrackingToFinish, "waitForTrackingToFinish");
|
|
852
938
|
function trackRelayStart() {
|
|
853
|
-
postEvent(
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
939
|
+
postEvent(
|
|
940
|
+
() => ({
|
|
941
|
+
event: "local_agents_relay_start",
|
|
942
|
+
...sharedFields()
|
|
943
|
+
})
|
|
944
|
+
);
|
|
857
945
|
}
|
|
858
946
|
__name(trackRelayStart, "trackRelayStart");
|
|
859
947
|
var relayShutdownTracked = false;
|
|
860
948
|
function trackRelayShutdown() {
|
|
861
949
|
if (relayShutdownTracked) return;
|
|
862
|
-
relayShutdownTracked = postEvent(
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
950
|
+
relayShutdownTracked = postEvent(
|
|
951
|
+
() => ({
|
|
952
|
+
event: "local_agents_relay_shutdown",
|
|
953
|
+
...sharedFields()
|
|
954
|
+
})
|
|
955
|
+
);
|
|
866
956
|
}
|
|
867
957
|
__name(trackRelayShutdown, "trackRelayShutdown");
|
|
868
958
|
function trackSessionCreate(payload) {
|
|
869
|
-
postEvent(
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
959
|
+
postEvent(
|
|
960
|
+
() => ({
|
|
961
|
+
event: "local_agents_session_create",
|
|
962
|
+
...sharedFields(),
|
|
963
|
+
...payload
|
|
964
|
+
})
|
|
965
|
+
);
|
|
874
966
|
}
|
|
875
967
|
__name(trackSessionCreate, "trackSessionCreate");
|
|
876
968
|
function trackSessionDestroy(payload) {
|
|
877
|
-
postEvent(
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
969
|
+
postEvent(
|
|
970
|
+
() => ({
|
|
971
|
+
event: "local_agents_session_destroy",
|
|
972
|
+
...sharedFields(),
|
|
973
|
+
...payload
|
|
974
|
+
})
|
|
975
|
+
);
|
|
882
976
|
}
|
|
883
977
|
__name(trackSessionDestroy, "trackSessionDestroy");
|
|
884
978
|
function trackExec(payload) {
|
|
885
|
-
postEvent(
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
979
|
+
postEvent(
|
|
980
|
+
() => ({
|
|
981
|
+
event: "local_agents_exec",
|
|
982
|
+
...sharedFields(),
|
|
983
|
+
...payload
|
|
984
|
+
})
|
|
985
|
+
);
|
|
890
986
|
}
|
|
891
987
|
__name(trackExec, "trackExec");
|
|
892
988
|
function trackConnectionAcquire(payload) {
|
|
893
|
-
postEvent(
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
989
|
+
postEvent(
|
|
990
|
+
() => ({
|
|
991
|
+
event: "local_agents_connection_acquire",
|
|
992
|
+
...sharedFields(),
|
|
993
|
+
...payload
|
|
994
|
+
})
|
|
995
|
+
);
|
|
898
996
|
}
|
|
899
997
|
__name(trackConnectionAcquire, "trackConnectionAcquire");
|
|
900
998
|
function trackConnectionReconnect(payload) {
|
|
901
|
-
postEvent(
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
999
|
+
postEvent(
|
|
1000
|
+
() => ({
|
|
1001
|
+
event: "local_agents_connection_reconnect",
|
|
1002
|
+
...sharedFields(),
|
|
1003
|
+
...payload
|
|
1004
|
+
})
|
|
1005
|
+
);
|
|
906
1006
|
}
|
|
907
1007
|
__name(trackConnectionReconnect, "trackConnectionReconnect");
|
|
908
1008
|
function trackConnectionIdleDisconnect(payload) {
|
|
909
|
-
postEvent(
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1009
|
+
postEvent(
|
|
1010
|
+
() => ({
|
|
1011
|
+
event: "local_agents_connection_idle_disconnect",
|
|
1012
|
+
...sharedFields(),
|
|
1013
|
+
...payload
|
|
1014
|
+
})
|
|
1015
|
+
);
|
|
914
1016
|
}
|
|
915
1017
|
__name(trackConnectionIdleDisconnect, "trackConnectionIdleDisconnect");
|
|
916
1018
|
function trackError(payload) {
|
|
917
|
-
postEvent(
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1019
|
+
postEvent(
|
|
1020
|
+
() => ({
|
|
1021
|
+
event: "local_agents_error",
|
|
1022
|
+
...sharedFields(),
|
|
1023
|
+
...payload
|
|
1024
|
+
})
|
|
1025
|
+
);
|
|
922
1026
|
}
|
|
923
1027
|
__name(trackError, "trackError");
|
|
924
1028
|
|
|
@@ -46,6 +46,8 @@ Choose the highest-priority interface available for the task:
|
|
|
46
46
|
- Use `framer.agent.readComponentControls`, `framer.agent.readIconSetControls`, `framer.agent.readIcons`, `framer.agent.readLayoutTemplateControls`, and `framer.agent.readShaderControls` for reading the controls of components, icon sets, icons, layout templates, and shaders.
|
|
47
47
|
- Use `framer.agent.applyChanges` for page and layout edits. Do not use low-level node APIs like `createNode`, `setAttributes`, or `setRect` for design/layout work.
|
|
48
48
|
- Use `framer.agent.publish` for publishing. Do not use `publish`, `getDeployments`, or `deploy` for normal agent publishing flows.
|
|
49
|
+
- Prefer `framer.agent.applyChanges` and project tree read methods for CMS work where possible. Fall back to the collection APIs only for functionality otherwise not supported. Note that if you add collections or fields via collection APIs, some things may not work as expected when then using those collections or fields on the canvas via `framer.agent.applyChanges`.
|
|
50
|
+
- Create styles, design tokens, components, and variables via `framer.agent.applyChanges`. Using plugin API methods can cause issues when trying to use newly created values later in `framer.agent.applyChanges` calls.
|
|
49
51
|
|
|
50
52
|
Use generic plugin API methods only for capabilities that do not have a subcommand or agent-specific counterpart, such as code file management, localization, and redirects.
|
|
51
53
|
|
|
@@ -173,109 +175,143 @@ npx framer-dalton docs ScreenshotOptions # Show type + recursively expa
|
|
|
173
175
|
|
|
174
176
|
### Working with Collections (CMS)
|
|
175
177
|
|
|
176
|
-
Collections are
|
|
178
|
+
Collections and items are nodes: read them with the agent read methods, create and edit them with `framer.agent.applyChanges`. A collection's fields are its `variables`; an item's cells are `$control__<fieldId>` attributes.
|
|
177
179
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
#### Reading Collections
|
|
180
|
+
#### List collections and fields
|
|
181
181
|
|
|
182
182
|
```js
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// Get fields (columns)
|
|
191
|
-
const fields = await collection.getFields();
|
|
192
|
-
// Note that a plain console.log will miss getter-backed properties
|
|
193
|
-
console.log(fields.map((f) => ({ id: f.id, name: f.name, type: f.type })));
|
|
194
|
-
// [{ id: "BnNuS2i3o", name: "Title", type: "string" }, ...]
|
|
195
|
-
|
|
196
|
-
// Get items (rows)
|
|
197
|
-
const items = await collection.getItems();
|
|
198
|
-
console.log(items);
|
|
199
|
-
// [{ id: "XTM8FSHGs", slug: "post-1", draft: false, fieldData: {...} }, ...]
|
|
183
|
+
const collections = await framer.agent.getNodesOfTypes({ types: ["CollectionNode"] });
|
|
184
|
+
console.log(collections.map((c) => ({
|
|
185
|
+
id: c.id,
|
|
186
|
+
name: c.name,
|
|
187
|
+
itemCount: c.$itemCount,
|
|
188
|
+
fields: c.variables.map((v) => ({ id: v.id, name: v.name, type: v.type })),
|
|
189
|
+
})));
|
|
200
190
|
```
|
|
201
191
|
|
|
202
|
-
####
|
|
192
|
+
#### Read items
|
|
193
|
+
|
|
194
|
+
If you know the collection id, serialize the collection with `depth: 1` to read its direct item children.
|
|
203
195
|
|
|
204
196
|
```js
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
const
|
|
197
|
+
const collectionId = "collection-id";
|
|
198
|
+
const collection = await framer.agent.serialize({ id: collectionId, depth: 1 });
|
|
199
|
+
const items = collection.children ?? [];
|
|
200
|
+
console.log({
|
|
201
|
+
itemCount: collection.$itemCount,
|
|
202
|
+
items: items.map((item) => ({ id: item.id, ...item.attributes })),
|
|
203
|
+
});
|
|
204
|
+
```
|
|
208
205
|
|
|
209
|
-
|
|
210
|
-
r.json(),
|
|
211
|
-
);
|
|
206
|
+
For huge collections, paginate the serialized children and process one page per call. Store the page in `state` only when you need to continue in a later exec call. Stop when the logged `nextCursor` is missing.
|
|
212
207
|
|
|
213
|
-
|
|
214
|
-
id: post.id,
|
|
215
|
-
slug: post.slug,
|
|
216
|
-
fieldData: { title: post.title, content: post.body },
|
|
217
|
-
}));
|
|
208
|
+
First page:
|
|
218
209
|
|
|
219
|
-
|
|
210
|
+
```js
|
|
211
|
+
const collectionId = "collection-id";
|
|
212
|
+
const collection = await framer.agent.serialize({ id: collectionId, depth: 1 });
|
|
213
|
+
state.page = await framer.agent.paginate({ items: collection.children ?? [] });
|
|
214
|
+
console.log({
|
|
215
|
+
totalResults: state.page.totalResults,
|
|
216
|
+
nextCursor: state.page.nextCursor,
|
|
217
|
+
items: state.page.results.map((item) => ({ id: item.id, ...item.attributes })),
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
220
|
|
|
221
|
-
|
|
222
|
-
const newIds = new Set(items.map((i) => i.id));
|
|
223
|
-
const toRemove = [...existingIds].filter((id) => !newIds.has(id));
|
|
224
|
-
if (toRemove.length) await collection.removeItems(toRemove);
|
|
221
|
+
Next page, if nextCursor is set:
|
|
225
222
|
|
|
226
|
-
|
|
223
|
+
```js
|
|
224
|
+
state.page = await framer.agent.paginate({
|
|
225
|
+
keyName: state.page.keyName,
|
|
226
|
+
cursor: state.page.nextCursor,
|
|
227
|
+
});
|
|
228
|
+
console.log({
|
|
229
|
+
totalResults: state.page.totalResults,
|
|
230
|
+
nextCursor: state.page.nextCursor,
|
|
231
|
+
items: state.page.results.map((item) => ({ id: item.id, ...item.attributes })),
|
|
232
|
+
});
|
|
227
233
|
```
|
|
228
234
|
|
|
229
|
-
|
|
235
|
+
To search across all collections, read item nodes directly and filter by `$parentId`:
|
|
230
236
|
|
|
231
|
-
|
|
237
|
+
```js
|
|
238
|
+
const collectionId = "collection-id";
|
|
239
|
+
const items = await framer.agent.getNodesOfTypes({ types: ["CollectionItemNode"] });
|
|
240
|
+
const collectionItems = items
|
|
241
|
+
.filter((item) => item.$parentId === collectionId)
|
|
242
|
+
.map((item) => ({ id: item.id, ...item.attributes }));
|
|
243
|
+
console.log(collectionItems);
|
|
244
|
+
```
|
|
232
245
|
|
|
233
|
-
####
|
|
246
|
+
#### Create and edit items
|
|
234
247
|
|
|
235
|
-
|
|
236
|
-
// Add or update items (if id matches existing item, it updates)
|
|
237
|
-
await collection.addItems([
|
|
238
|
-
{
|
|
239
|
-
id: "new-item-1",
|
|
240
|
-
slug: "hello-world",
|
|
241
|
-
fieldData: { titleFieldId: "Hello World" },
|
|
242
|
-
},
|
|
243
|
-
]);
|
|
248
|
+
`+CollectionItemNode` adds a row; `SET … $control__<fieldId>` sets cells (a `SET` on an existing item id updates it; `DEL <itemId>` removes it).
|
|
244
249
|
|
|
245
|
-
|
|
246
|
-
|
|
250
|
+
```js
|
|
251
|
+
const { readFileSync } = require("fs");
|
|
252
|
+
|
|
253
|
+
const collectionId = "collection-id";
|
|
254
|
+
const columnToFieldId = { title: "title-field-id", body: "body-field-id" };
|
|
255
|
+
const rows = JSON.parse(readFileSync("/abs/path/to/import.json", "utf8"));
|
|
256
|
+
|
|
257
|
+
const commands = rows.flatMap((row, i) => {
|
|
258
|
+
const itemId = `item-${i}`;
|
|
259
|
+
const sets = Object.entries(columnToFieldId)
|
|
260
|
+
.filter(([col]) => row[col] != null)
|
|
261
|
+
.map(([col, fieldId]) => `$control__${fieldId}="${String(row[col]).replace(/"/g, '\\"')}"`);
|
|
262
|
+
return [`+CollectionItemNode ${itemId} parent="${collectionId}";`, `SET ${itemId} ${sets.join(" ")};`];
|
|
263
|
+
});
|
|
247
264
|
|
|
248
|
-
|
|
249
|
-
const ids = items.map((i) => i.id).reverse();
|
|
250
|
-
await collection.setItemOrder(ids);
|
|
265
|
+
await framer.agent.applyChanges(commands.join(" "));
|
|
251
266
|
```
|
|
252
267
|
|
|
253
268
|
#### Writing enum fields
|
|
254
269
|
|
|
255
|
-
|
|
270
|
+
With agent methods, enum fields are read and written by case name. Look up the field on the collection's `variables`, verify the case exists, then set the field's `$control__...` key:
|
|
256
271
|
|
|
257
272
|
```js
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
273
|
+
const collection = (await framer.agent.getNodesOfTypes({ types: ["CollectionNode"] }))
|
|
274
|
+
.find((c) => c.name === "Posts");
|
|
275
|
+
const statusField = collection.variables.find((v) => v.name === "Status");
|
|
276
|
+
const status = statusField.cases.find((name) => name === "New");
|
|
277
|
+
|
|
278
|
+
await framer.agent.applyChanges(
|
|
279
|
+
`+CollectionItemNode newPost parent="${collection.id}";
|
|
280
|
+
SET newPost $control__slug="hello-world" ${statusField.key}="${status}";`,
|
|
281
|
+
);
|
|
262
282
|
```
|
|
263
283
|
|
|
264
|
-
####
|
|
284
|
+
#### Sync external data
|
|
285
|
+
|
|
286
|
+
Upsert by a stable key (e.g. slug): `SET` existing rows, add new ones, `DEL` rows no longer in the source.
|
|
265
287
|
|
|
266
288
|
```js
|
|
267
|
-
const
|
|
268
|
-
const
|
|
289
|
+
const collectionId = "collection-id";
|
|
290
|
+
const fieldBySource = { title: "title-field-id", body: "body-field-id" };
|
|
291
|
+
const source = await fetch("https://api.example.com/posts").then((r) => r.json());
|
|
292
|
+
|
|
293
|
+
const existing = (await framer.agent.getNodesOfTypes({ types: ["CollectionItemNode"] }))
|
|
294
|
+
.filter((item) => item.$parentId === collectionId);
|
|
295
|
+
const idBySlug = new Map(existing.map((item) => [item.attributes.$control__slug, item.id]));
|
|
296
|
+
|
|
297
|
+
const seen = new Set();
|
|
298
|
+
const commands = source.flatMap((row, i) => {
|
|
299
|
+
const itemId = idBySlug.get(row.slug) ?? `new-${i}`;
|
|
300
|
+
seen.add(itemId);
|
|
301
|
+
const sets = Object.entries(fieldBySource)
|
|
302
|
+
.filter(([k]) => row[k] != null)
|
|
303
|
+
.map(([k, fieldId]) => `$control__${fieldId}="${String(row[k]).replace(/"/g, '\\"')}"`);
|
|
304
|
+
const add = idBySlug.has(row.slug) ? [] : [`+CollectionItemNode ${itemId} parent="${collectionId}";`];
|
|
305
|
+
return [...add, `SET ${itemId} ${sets.join(" ")};`];
|
|
306
|
+
});
|
|
307
|
+
existing.filter((item) => !seen.has(item.id)).forEach((item) => commands.push(`DEL ${item.id};`));
|
|
269
308
|
|
|
270
|
-
|
|
271
|
-
|
|
309
|
+
await framer.agent.applyChanges(commands.join(" "));
|
|
310
|
+
```
|
|
272
311
|
|
|
273
|
-
|
|
274
|
-
await item.navigateTo();
|
|
312
|
+
#### Field Types
|
|
275
313
|
|
|
276
|
-
|
|
277
|
-
await item.remove();
|
|
278
|
-
```
|
|
314
|
+
`boolean`, `color`, `number`, `string`, `formattedText` (HTML), `image`, `file`, `link`, `date`, `enum`, `collectionReference`, `multiCollectionReference`, `array` (galleries)
|
|
279
315
|
|
|
280
316
|
### Working with Images
|
|
281
317
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "framer-dalton",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.28",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": "./dist/cli.js",
|
|
6
6
|
"main": "./dist/cli.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@biomejs/biome": "^2.4.13",
|
|
32
|
-
"@framerjs/framer-events": "0.0.
|
|
32
|
+
"@framerjs/framer-events": "0.0.185",
|
|
33
33
|
"@types/node": "24.12.4",
|
|
34
34
|
"tsup": "^8.0.2",
|
|
35
35
|
"tsx": "^4.22.3",
|