framer-dalton 0.0.10 → 0.0.11
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 +63 -37
- 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.11 */
|
|
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: {} };
|
|
@@ -14909,8 +14909,8 @@ ${typeDef}`);
|
|
|
14909
14909
|
}
|
|
14910
14910
|
__name(renderDocs, "renderDocs");
|
|
14911
14911
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
14912
|
-
var __dirname$1 =
|
|
14913
|
-
var VERSION = "0.0.
|
|
14912
|
+
var __dirname$1 = path4.dirname(__filename$1);
|
|
14913
|
+
var VERSION = "0.0.11" ;
|
|
14914
14914
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
|
|
14915
14915
|
var client = createTRPCClient({
|
|
14916
14916
|
links: [
|
|
@@ -14972,7 +14972,7 @@ async function ensureRelayServerRunning(options = {}) {
|
|
|
14972
14972
|
logger?.log("Relay server not running, starting it...");
|
|
14973
14973
|
}
|
|
14974
14974
|
const isRunningFromSource = __filename$1.endsWith(".ts");
|
|
14975
|
-
const scriptPath = isRunningFromSource ?
|
|
14975
|
+
const scriptPath = isRunningFromSource ? path4.resolve(__dirname$1, "./start-relay-server.ts") : path4.resolve(__dirname$1, "./start-relay-server.js");
|
|
14976
14976
|
const serverProcess = spawn(
|
|
14977
14977
|
isRunningFromSource ? "tsx" : process.execPath,
|
|
14978
14978
|
[scriptPath],
|
|
@@ -14994,16 +14994,37 @@ async function ensureRelayServerRunning(options = {}) {
|
|
|
14994
14994
|
throw new Error("Failed to start relay server after 5 seconds");
|
|
14995
14995
|
}
|
|
14996
14996
|
__name(ensureRelayServerRunning, "ensureRelayServerRunning");
|
|
14997
|
+
var FRAMER_TEMPORARY_DIR = path4.join(os.tmpdir(), "framer");
|
|
14998
|
+
function ensureTemporaryDir() {
|
|
14999
|
+
fs3.mkdirSync(FRAMER_TEMPORARY_DIR, { recursive: true });
|
|
15000
|
+
}
|
|
15001
|
+
__name(ensureTemporaryDir, "ensureTemporaryDir");
|
|
15002
|
+
function isTemporaryFile(filePath) {
|
|
15003
|
+
const absolutePath = path4.resolve(filePath);
|
|
15004
|
+
const isInTemporaryDir = absolutePath.startsWith(
|
|
15005
|
+
FRAMER_TEMPORARY_DIR + path4.sep
|
|
15006
|
+
);
|
|
15007
|
+
const isFile = fs3.statSync(absolutePath, { throwIfNoEntry: false })?.isFile() ?? false;
|
|
15008
|
+
return isInTemporaryDir && isFile;
|
|
15009
|
+
}
|
|
15010
|
+
__name(isTemporaryFile, "isTemporaryFile");
|
|
15011
|
+
function removeTemporaryFile(filePath) {
|
|
15012
|
+
fs3.unlinkSync(filePath);
|
|
15013
|
+
}
|
|
15014
|
+
__name(removeTemporaryFile, "removeTemporaryFile");
|
|
15015
|
+
|
|
15016
|
+
// src/skills.ts
|
|
14997
15017
|
var META_SKILL_NAME = "framer";
|
|
14998
15018
|
var CODE_COMPONENTS_SKILL_NAME = "framer-code-components";
|
|
14999
|
-
var __dirname2 =
|
|
15000
|
-
var skillsDocsDir =
|
|
15019
|
+
var __dirname2 = path4.dirname(fileURLToPath(import.meta.url));
|
|
15020
|
+
var skillsDocsDir = path4.join(__dirname2, "..", "docs", "skills");
|
|
15001
15021
|
function readSkillDoc(name) {
|
|
15002
|
-
return
|
|
15022
|
+
return fs3.readFileSync(path4.join(skillsDocsDir, name), "utf-8").trimEnd();
|
|
15003
15023
|
}
|
|
15004
15024
|
__name(readSkillDoc, "readSkillDoc");
|
|
15005
15025
|
function buildMetaSkill() {
|
|
15006
|
-
|
|
15026
|
+
const template = readSkillDoc("framer.md");
|
|
15027
|
+
return `${renderTemplate(template, { FRAMER_TEMPORARY_DIR })}
|
|
15007
15028
|
`;
|
|
15008
15029
|
}
|
|
15009
15030
|
__name(buildMetaSkill, "buildMetaSkill");
|
|
@@ -15033,36 +15054,37 @@ function buildProjectCanvasSkill(projectId, agentContext, canvasPrompt) {
|
|
|
15033
15054
|
PROJECT_ID: projectId,
|
|
15034
15055
|
GENERATED_AT: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15035
15056
|
CANVAS_PROMPT: canvasPrompt.trimEnd(),
|
|
15036
|
-
AGENT_CONTEXT: agentContext.trimEnd()
|
|
15057
|
+
AGENT_CONTEXT: agentContext.trimEnd(),
|
|
15058
|
+
FRAMER_TEMPORARY_DIR
|
|
15037
15059
|
})}
|
|
15038
15060
|
`;
|
|
15039
15061
|
return { skillName, content };
|
|
15040
15062
|
}
|
|
15041
15063
|
__name(buildProjectCanvasSkill, "buildProjectCanvasSkill");
|
|
15042
15064
|
function writeSkill(root, skillName, content) {
|
|
15043
|
-
|
|
15044
|
-
const rootStat =
|
|
15065
|
+
fs3.mkdirSync(root, { recursive: true });
|
|
15066
|
+
const rootStat = fs3.statSync(root);
|
|
15045
15067
|
if (!rootStat.isDirectory()) {
|
|
15046
15068
|
throw new Error(`Skill root is not a directory: ${root}`);
|
|
15047
15069
|
}
|
|
15048
|
-
const skillDir =
|
|
15049
|
-
const filePath =
|
|
15050
|
-
if (
|
|
15051
|
-
const current =
|
|
15070
|
+
const skillDir = path4.join(root, skillName);
|
|
15071
|
+
const filePath = path4.join(skillDir, "SKILL.md");
|
|
15072
|
+
if (fs3.existsSync(skillDir)) {
|
|
15073
|
+
const current = fs3.lstatSync(skillDir);
|
|
15052
15074
|
if (current.isSymbolicLink() || !current.isDirectory()) {
|
|
15053
|
-
|
|
15075
|
+
fs3.rmSync(skillDir, { recursive: true, force: true });
|
|
15054
15076
|
}
|
|
15055
15077
|
}
|
|
15056
|
-
|
|
15057
|
-
|
|
15078
|
+
fs3.mkdirSync(skillDir, { recursive: true });
|
|
15079
|
+
fs3.writeFileSync(filePath, content, "utf-8");
|
|
15058
15080
|
return filePath;
|
|
15059
15081
|
}
|
|
15060
15082
|
__name(writeSkill, "writeSkill");
|
|
15061
15083
|
function getDefaultSkillRoots() {
|
|
15062
15084
|
const home = os.homedir();
|
|
15063
15085
|
return [
|
|
15064
|
-
|
|
15065
|
-
|
|
15086
|
+
path4.join(home, ".agents", "skills"),
|
|
15087
|
+
path4.join(home, ".claude", "skills")
|
|
15066
15088
|
];
|
|
15067
15089
|
}
|
|
15068
15090
|
__name(getDefaultSkillRoots, "getDefaultSkillRoots");
|
|
@@ -15111,8 +15133,8 @@ function printSetupSummary(results) {
|
|
|
15111
15133
|
const installLocations = /* @__PURE__ */ new Set();
|
|
15112
15134
|
for (const result of results) {
|
|
15113
15135
|
for (const filePath of result.paths) {
|
|
15114
|
-
const skillDir =
|
|
15115
|
-
const root =
|
|
15136
|
+
const skillDir = path4.dirname(filePath);
|
|
15137
|
+
const root = path4.dirname(skillDir);
|
|
15116
15138
|
installLocations.add(root);
|
|
15117
15139
|
}
|
|
15118
15140
|
}
|
|
@@ -15216,14 +15238,18 @@ async function ensureRelayForCli() {
|
|
|
15216
15238
|
__name(ensureRelayForCli, "ensureRelayForCli");
|
|
15217
15239
|
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
15240
|
const { session: sessionId, eval: evalCode, file: filePath } = options;
|
|
15241
|
+
ensureTemporaryDir();
|
|
15219
15242
|
let code = evalCode;
|
|
15220
15243
|
if (!code && filePath) {
|
|
15221
15244
|
try {
|
|
15222
|
-
code =
|
|
15245
|
+
code = fs3.readFileSync(filePath, "utf-8");
|
|
15223
15246
|
} catch (err) {
|
|
15224
15247
|
printError(`Failed to read file: ${formatError(err)}`);
|
|
15225
15248
|
process.exit(1);
|
|
15226
15249
|
}
|
|
15250
|
+
if (isTemporaryFile(filePath)) {
|
|
15251
|
+
removeTemporaryFile(filePath);
|
|
15252
|
+
}
|
|
15227
15253
|
}
|
|
15228
15254
|
if (!code && !process.stdin.isTTY) {
|
|
15229
15255
|
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.11 */
|
|
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.11" ;
|
|
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
|