framer-dalton 0.0.9 → 0.0.10
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 +13 -5
- package/dist/start-relay-server.js +171 -32
- package/docs/skills/framer-canvas-editing-project.md +16 -16
- package/docs/skills/framer.md +22 -55
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs2 from 'fs';
|
|
2
3
|
import path3 from 'path';
|
|
3
4
|
import { Command } from 'commander';
|
|
4
5
|
import crypto from 'crypto';
|
|
5
6
|
import http from 'http';
|
|
6
7
|
import { spawn, execFile } from 'child_process';
|
|
7
|
-
import fs2 from 'fs';
|
|
8
8
|
import os from 'os';
|
|
9
9
|
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.10 */
|
|
14
14
|
var __defProp = Object.defineProperty;
|
|
15
15
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
16
16
|
function openUrl(url) {
|
|
@@ -14910,7 +14910,7 @@ ${typeDef}`);
|
|
|
14910
14910
|
__name(renderDocs, "renderDocs");
|
|
14911
14911
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
14912
14912
|
var __dirname$1 = path3.dirname(__filename$1);
|
|
14913
|
-
var VERSION = "0.0.
|
|
14913
|
+
var VERSION = "0.0.10" ;
|
|
14914
14914
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
|
|
14915
14915
|
var client = createTRPCClient({
|
|
14916
14916
|
links: [
|
|
@@ -15214,9 +15214,17 @@ async function ensureRelayForCli() {
|
|
|
15214
15214
|
}
|
|
15215
15215
|
}
|
|
15216
15216
|
__name(ensureRelayForCli, "ensureRelayForCli");
|
|
15217
|
-
program.option("-s, --session <id>", "Session ID (required for code execution)").option("-e, --eval <code>", "Code to execute (or pipe via stdin)").action(async (options) => {
|
|
15218
|
-
const { session: sessionId, eval: evalCode } = options;
|
|
15217
|
+
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
|
+
const { session: sessionId, eval: evalCode, file: filePath } = options;
|
|
15219
15219
|
let code = evalCode;
|
|
15220
|
+
if (!code && filePath) {
|
|
15221
|
+
try {
|
|
15222
|
+
code = fs2.readFileSync(filePath, "utf-8");
|
|
15223
|
+
} catch (err) {
|
|
15224
|
+
printError(`Failed to read file: ${formatError(err)}`);
|
|
15225
|
+
process.exit(1);
|
|
15226
|
+
}
|
|
15227
|
+
}
|
|
15220
15228
|
if (!code && !process.stdin.isTTY) {
|
|
15221
15229
|
code = await readStdin();
|
|
15222
15230
|
}
|
|
@@ -13,9 +13,50 @@ 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.10 */
|
|
17
17
|
var __defProp = Object.defineProperty;
|
|
18
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
19
|
+
var __typeError = (msg) => {
|
|
20
|
+
throw TypeError(msg);
|
|
21
|
+
};
|
|
18
22
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
23
|
+
var __using = (stack, value, async) => {
|
|
24
|
+
if (value != null) {
|
|
25
|
+
if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
|
|
26
|
+
var dispose, inner;
|
|
27
|
+
if (dispose === void 0) {
|
|
28
|
+
dispose = value[__knownSymbol("dispose")];
|
|
29
|
+
}
|
|
30
|
+
if (typeof dispose !== "function") __typeError("Object not disposable");
|
|
31
|
+
if (inner) dispose = function() {
|
|
32
|
+
try {
|
|
33
|
+
inner.call(this);
|
|
34
|
+
} catch (e) {
|
|
35
|
+
return Promise.reject(e);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
stack.push([async, dispose, value]);
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
};
|
|
42
|
+
var __callDispose = (stack, error, hasError) => {
|
|
43
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
44
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
45
|
+
};
|
|
46
|
+
var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
|
|
47
|
+
var next = (it) => {
|
|
48
|
+
while (it = stack.pop()) {
|
|
49
|
+
try {
|
|
50
|
+
var result = it[1] && it[1].call(it[2]);
|
|
51
|
+
if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
52
|
+
} catch (e) {
|
|
53
|
+
fail(e);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (hasError) throw error;
|
|
57
|
+
};
|
|
58
|
+
return next();
|
|
59
|
+
};
|
|
19
60
|
function getLogPath() {
|
|
20
61
|
if (process.env.XDG_STATE_HOME) {
|
|
21
62
|
return path.join(process.env.XDG_STATE_HOME, "framer", "relay.log");
|
|
@@ -50,7 +91,7 @@ function log(message) {
|
|
|
50
91
|
__name(log, "log");
|
|
51
92
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
52
93
|
path.dirname(__filename$1);
|
|
53
|
-
var VERSION = "0.0.
|
|
94
|
+
var VERSION = "0.0.10" ;
|
|
54
95
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
|
|
55
96
|
createTRPCClient({
|
|
56
97
|
links: [
|
|
@@ -486,7 +527,8 @@ var ConnectionPool = class {
|
|
|
486
527
|
const connection = await connect(projectId, apiKey);
|
|
487
528
|
this.pool.set(projectId, {
|
|
488
529
|
connection,
|
|
489
|
-
sessions: /* @__PURE__ */ new Set([session])
|
|
530
|
+
sessions: /* @__PURE__ */ new Set([session]),
|
|
531
|
+
connected: true
|
|
490
532
|
});
|
|
491
533
|
return connection;
|
|
492
534
|
}
|
|
@@ -515,11 +557,26 @@ var ConnectionPool = class {
|
|
|
515
557
|
if (!entry) return null;
|
|
516
558
|
try {
|
|
517
559
|
await entry.connection.reconnect();
|
|
560
|
+
entry.connected = true;
|
|
518
561
|
return entry.connection;
|
|
519
562
|
} catch {
|
|
520
563
|
return null;
|
|
521
564
|
}
|
|
522
565
|
}
|
|
566
|
+
/**
|
|
567
|
+
* Disconnect a project's connection without removing sessions.
|
|
568
|
+
* The next exec will trigger a reconnect via executeWithReconnect.
|
|
569
|
+
*/
|
|
570
|
+
async disconnect(projectId) {
|
|
571
|
+
const entry = this.pool.get(projectId);
|
|
572
|
+
if (!entry || !entry.connected) return;
|
|
573
|
+
entry.connected = false;
|
|
574
|
+
await entry.connection.disconnect();
|
|
575
|
+
}
|
|
576
|
+
isConnected(projectId) {
|
|
577
|
+
const entry = this.pool.get(projectId);
|
|
578
|
+
return entry?.connected ?? false;
|
|
579
|
+
}
|
|
523
580
|
/**
|
|
524
581
|
* Release a session from a connection.
|
|
525
582
|
* If no sessions remain, the connection is disconnected and removed.
|
|
@@ -548,11 +605,14 @@ var ConnectionPool = class {
|
|
|
548
605
|
var connectionPool = new ConnectionPool();
|
|
549
606
|
|
|
550
607
|
// src/session-manager.ts
|
|
608
|
+
var SESSION_IDLE_TIMEOUT_MS = 60 * 1e3;
|
|
609
|
+
var SESSION_IDLE_CHECK_INTERVAL_MS = 30 * 1e3;
|
|
551
610
|
var SessionManager = class {
|
|
552
611
|
static {
|
|
553
612
|
__name(this, "SessionManager");
|
|
554
613
|
}
|
|
555
614
|
sessions = /* @__PURE__ */ new Map();
|
|
615
|
+
idleCheck = null;
|
|
556
616
|
async create(projectId, apiKey) {
|
|
557
617
|
let id = 1;
|
|
558
618
|
while (this.sessions.has(String(id))) {
|
|
@@ -562,9 +622,13 @@ var SessionManager = class {
|
|
|
562
622
|
id: String(id),
|
|
563
623
|
projectId,
|
|
564
624
|
apiKey,
|
|
565
|
-
state: {}
|
|
625
|
+
state: {},
|
|
626
|
+
lastActivityAt: 0,
|
|
627
|
+
inflight: 0
|
|
566
628
|
};
|
|
629
|
+
this.startIdleCheck();
|
|
567
630
|
await connectionPool.acquire(projectId, apiKey, session);
|
|
631
|
+
session.lastActivityAt = Date.now();
|
|
568
632
|
this.sessions.set(String(id), session);
|
|
569
633
|
return String(id);
|
|
570
634
|
}
|
|
@@ -584,6 +648,21 @@ var SessionManager = class {
|
|
|
584
648
|
async reconnect(session) {
|
|
585
649
|
return connectionPool.reconnect(session.projectId);
|
|
586
650
|
}
|
|
651
|
+
/** Marks session as actively executing. Dispose to release. */
|
|
652
|
+
exec(id) {
|
|
653
|
+
const session = this.sessions.get(id);
|
|
654
|
+
if (session) {
|
|
655
|
+
session.inflight++;
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
[Symbol.dispose]: () => {
|
|
659
|
+
if (session) {
|
|
660
|
+
session.inflight--;
|
|
661
|
+
session.lastActivityAt = Date.now();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
}
|
|
587
666
|
async destroy(id) {
|
|
588
667
|
const session = this.sessions.get(id);
|
|
589
668
|
if (!session) {
|
|
@@ -591,12 +670,50 @@ var SessionManager = class {
|
|
|
591
670
|
}
|
|
592
671
|
await connectionPool.release(session.projectId, session);
|
|
593
672
|
this.sessions.delete(id);
|
|
673
|
+
if (this.sessions.size === 0) {
|
|
674
|
+
this.stopIdleCheck();
|
|
675
|
+
}
|
|
594
676
|
}
|
|
595
677
|
async destroyAll() {
|
|
596
678
|
for (const id of this.sessions.keys()) {
|
|
597
679
|
await this.destroy(id);
|
|
598
680
|
}
|
|
599
681
|
}
|
|
682
|
+
startIdleCheck() {
|
|
683
|
+
if (this.idleCheck) return;
|
|
684
|
+
this.idleCheck = setInterval(() => {
|
|
685
|
+
this.reapIdleSessions().catch((err) => {
|
|
686
|
+
log(`reap error: ${err instanceof Error ? err.message : err}`);
|
|
687
|
+
});
|
|
688
|
+
}, SESSION_IDLE_CHECK_INTERVAL_MS);
|
|
689
|
+
this.idleCheck.unref();
|
|
690
|
+
}
|
|
691
|
+
stopIdleCheck() {
|
|
692
|
+
if (this.idleCheck) {
|
|
693
|
+
clearInterval(this.idleCheck);
|
|
694
|
+
this.idleCheck = null;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
async reapIdleSessions() {
|
|
698
|
+
const now = Date.now();
|
|
699
|
+
const disconnects = [];
|
|
700
|
+
for (const session of this.sessions.values()) {
|
|
701
|
+
if (session.inflight > 0) continue;
|
|
702
|
+
if (now - session.lastActivityAt < SESSION_IDLE_TIMEOUT_MS) continue;
|
|
703
|
+
if (!connectionPool.isConnected(session.projectId)) continue;
|
|
704
|
+
const { projectId } = session;
|
|
705
|
+
log(`idle disconnect project=${projectId}`);
|
|
706
|
+
disconnects.push(connectionPool.disconnect(projectId));
|
|
707
|
+
}
|
|
708
|
+
const results = await Promise.allSettled(disconnects);
|
|
709
|
+
for (const result of results) {
|
|
710
|
+
if (result.status === "rejected") {
|
|
711
|
+
log(
|
|
712
|
+
`disconnect error: ${result.reason instanceof Error ? result.reason.message : result.reason}`
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
600
717
|
};
|
|
601
718
|
var sessionManager = new SessionManager();
|
|
602
719
|
|
|
@@ -633,35 +750,43 @@ var appRouter = t.router({
|
|
|
633
750
|
cwd: z.string().optional()
|
|
634
751
|
})
|
|
635
752
|
).mutation(async ({ input }) => {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
753
|
+
var _stack = [];
|
|
754
|
+
try {
|
|
755
|
+
const { sessionId, code, cwd } = input;
|
|
756
|
+
const session = sessionManager.get(sessionId);
|
|
757
|
+
if (!session) {
|
|
758
|
+
throw new TRPCError({
|
|
759
|
+
code: "NOT_FOUND",
|
|
760
|
+
message: `Session ${sessionId} not found`
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
const _guard = __using(_stack, sessionManager.exec(sessionId));
|
|
764
|
+
log(
|
|
765
|
+
`exec session=${sessionId} code=${JSON.stringify(code).slice(0, 100)}`
|
|
766
|
+
);
|
|
767
|
+
const framer = sessionManager.getFramer(session);
|
|
768
|
+
if (!framer) {
|
|
769
|
+
return {
|
|
770
|
+
output: [],
|
|
771
|
+
error: "Failed to get connection for session"
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
const result = await executeWithReconnect(
|
|
775
|
+
session,
|
|
776
|
+
framer,
|
|
777
|
+
code,
|
|
778
|
+
{ cwd },
|
|
779
|
+
() => sessionManager.reconnect(session)
|
|
780
|
+
);
|
|
781
|
+
if (result.error) {
|
|
782
|
+
log(`exec.error session=${sessionId} error="${result.error}"`);
|
|
783
|
+
}
|
|
784
|
+
return result;
|
|
785
|
+
} catch (_) {
|
|
786
|
+
var _error = _, _hasError = true;
|
|
787
|
+
} finally {
|
|
788
|
+
__callDispose(_stack, _error, _hasError);
|
|
663
789
|
}
|
|
664
|
-
return result;
|
|
665
790
|
}),
|
|
666
791
|
shutdown: t.procedure.mutation(() => {
|
|
667
792
|
log("shutdown requested");
|
|
@@ -672,11 +797,25 @@ var appRouter = t.router({
|
|
|
672
797
|
});
|
|
673
798
|
|
|
674
799
|
// src/relay-server.ts
|
|
800
|
+
var IDLE_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
|
|
801
|
+
var IDLE_CHECK_INTERVAL_MS = 60 * 1e3;
|
|
675
802
|
var trpcHandler = createHTTPHandler({ router: appRouter });
|
|
676
803
|
async function startRelayServer(port = RELAY_PORT) {
|
|
804
|
+
let lastActivityAt = Date.now();
|
|
677
805
|
const server = http.createServer((req, res) => {
|
|
806
|
+
lastActivityAt = Date.now();
|
|
678
807
|
trpcHandler(req, res);
|
|
679
808
|
});
|
|
809
|
+
const idleCheck = setInterval(() => {
|
|
810
|
+
const idleMs = Date.now() - lastActivityAt;
|
|
811
|
+
if (idleMs >= IDLE_TIMEOUT_MS) {
|
|
812
|
+
log(`idle for ${Math.round(idleMs / 1e3)}s, shutting down`);
|
|
813
|
+
clearInterval(idleCheck);
|
|
814
|
+
server.close();
|
|
815
|
+
process.exit(0);
|
|
816
|
+
}
|
|
817
|
+
}, IDLE_CHECK_INTERVAL_MS);
|
|
818
|
+
idleCheck.unref();
|
|
680
819
|
return new Promise((resolve, reject) => {
|
|
681
820
|
server.on("error", reject);
|
|
682
821
|
server.listen(port, "127.0.0.1", () => {
|
|
@@ -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:*)"]
|
|
4
|
+
allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)", "Write(/tmp/framer-*)"]
|
|
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
|
|
31
|
-
framer
|
|
32
|
-
const { results } = await framer.readProjectForAgent(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
);
|
|
30
|
+
# 1) Read page structure first — write code to /tmp/framer-<sessionId>.js, then execute with -f
|
|
31
|
+
# /tmp/framer-<sessionId>.js:
|
|
32
|
+
# const { results } = await framer.readProjectForAgent(
|
|
33
|
+
# [{ type: "page", path: "/" }],
|
|
34
|
+
# { pagePath: "/" }
|
|
35
|
+
# );
|
|
36
|
+
# console.log(results);
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
EOF
|
|
38
|
+
framer -s <sessionId> -f /tmp/framer-<sessionId>.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
|
-
framer
|
|
44
|
-
const dsl = `
|
|
45
|
-
...your canvas DSL...
|
|
46
|
-
`;
|
|
43
|
+
# /tmp/framer-<sessionId>.js:
|
|
44
|
+
# const dsl = `
|
|
45
|
+
# ...your canvas DSL...
|
|
46
|
+
# `;
|
|
47
|
+
# await framer.applyAgentChanges(dsl, { pagePath: "/" });
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
EOF
|
|
49
|
+
framer -s <sessionId> -f /tmp/framer-<sessionId>.js
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
## Live Agent System Prompt
|
|
@@ -61,6 +61,6 @@ This is the static canvas-editing prompt returned by `framer.getAgentSystemPromp
|
|
|
61
61
|
|
|
62
62
|
## Live Agent Context (/)
|
|
63
63
|
|
|
64
|
-
This is the dynamic project context returned by `framer.getAgentContext({ pagePath: "/" })`. It contains project-specific data for the current page, including available fonts, components
|
|
64
|
+
This is the dynamic project context returned by `framer.getAgentContext({ pagePath: "/" })`. It contains project-specific data for the current page, including available fonts, available components, design tokens, style presets, and icon sets.
|
|
65
65
|
|
|
66
66
|
{{AGENT_CONTEXT}}
|
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:*)"]
|
|
6
|
+
allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)", "Write(/tmp/framer-*)"]
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
If you didn't run this command before loading the skill, run it now:
|
|
@@ -59,12 +59,14 @@ 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:
|
|
62
|
+
Only after checking docs, write your code to a `framer-<sessionId>.js` file in the OS temp directory and execute with `-f`. Use the session ID in the filename to avoid collisions between parallel sessions:
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
|
-
npx framer-dalton -s 1 -
|
|
65
|
+
npx framer-dalton -s 1 -f /tmp/framer-1.js
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
On Windows, use the equivalent temp directory (e.g. `%TEMP%\framer-1.js`).
|
|
69
|
+
|
|
68
70
|
#### 4. Store results in `state`
|
|
69
71
|
|
|
70
72
|
Always save results you'll need again. Don't repeat API calls.
|
|
@@ -90,13 +92,23 @@ Always save results you'll need again. Don't repeat API calls.
|
|
|
90
92
|
|
|
91
93
|
**Always store results in `state` when you'll need them again.** API calls are slow - don't repeat them.
|
|
92
94
|
|
|
95
|
+
```js
|
|
96
|
+
// /tmp/framer-1.js
|
|
97
|
+
state.collections = await framer.getCollections();
|
|
98
|
+
```
|
|
99
|
+
|
|
93
100
|
```bash
|
|
94
|
-
|
|
95
|
-
|
|
101
|
+
npx framer-dalton -s 1 -f /tmp/framer-1.js
|
|
102
|
+
```
|
|
96
103
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
```js
|
|
105
|
+
// /tmp/framer-1.js — reuse from state
|
|
106
|
+
state.teamItems = await state.collections.find(c => c.name === 'Team').getItems();
|
|
107
|
+
console.log(state.teamItems.length);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npx framer-dalton -s 1 -f /tmp/framer-1.js
|
|
100
112
|
```
|
|
101
113
|
|
|
102
114
|
Store anything you'll reference again.
|
|
@@ -132,55 +144,10 @@ After session creation, load the dynamically created project-scoped skill `frame
|
|
|
132
144
|
|
|
133
145
|
## Execute Code
|
|
134
146
|
|
|
135
|
-
|
|
136
|
-
npx framer-dalton -s <sessionId> -e "<code>"
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
**Escaping:** For code containing `$` (e.g. `$control__` properties), HTML, or nested quotes, use a heredoc (see below). `-e "..."` works for everything else, including multiline.
|
|
140
|
-
|
|
141
|
-
**Examples:**
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
# Fetch collections and store in state (always store results you'll reuse)
|
|
145
|
-
npx framer-dalton -s 1 -e "state.collections = await framer.getCollections(); console.log(state.collections.map(c => c.name))"
|
|
146
|
-
|
|
147
|
-
# Use stored data in subsequent calls
|
|
148
|
-
npx framer-dalton -s 1 -e "state.team = state.collections.find(c => c.name === 'Team')"
|
|
149
|
-
npx framer-dalton -s 1 -e "state.teamItems = await state.team.getItems(); console.log(state.teamItems.length)"
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
**Multiline code with heredoc (recommended for complex strings):**
|
|
153
|
-
|
|
154
|
-
For code containing HTML, quotes, or special characters, use a heredoc to avoid escaping issues:
|
|
147
|
+
Write your code to `framer-<sessionId>.js` in the OS temp directory and execute with `-f`:
|
|
155
148
|
|
|
156
149
|
```bash
|
|
157
|
-
npx framer-dalton -s
|
|
158
|
-
const translations = {
|
|
159
|
-
"node-id": "<h2>Ship's Treasures</h2>",
|
|
160
|
-
"other-id": "<p>Text with "quotes" and <tags></p>"
|
|
161
|
-
};
|
|
162
|
-
await framer.setLocalizationData({ valuesBySource: translations });
|
|
163
|
-
EOF
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
The `<<'EOF'` syntax (with quotes around EOF) prevents shell interpolation.
|
|
167
|
-
|
|
168
|
-
**Alternative: pipe from file:**
|
|
169
|
-
|
|
170
|
-
```bash
|
|
171
|
-
cat script.js | npx framer-dalton -s 1
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
**Multiline inline code:**
|
|
175
|
-
|
|
176
|
-
```bash
|
|
177
|
-
npx framer-dalton -s 1 -e "
|
|
178
|
-
const collections = await framer.getCollections();
|
|
179
|
-
for (const c of collections) {
|
|
180
|
-
const items = await c.getItems();
|
|
181
|
-
console.log(c.name, items.length);
|
|
182
|
-
}
|
|
183
|
-
"
|
|
150
|
+
npx framer-dalton -s <sessionId> -f /tmp/framer-<sessionId>.js
|
|
184
151
|
```
|
|
185
152
|
|
|
186
153
|
## API Documentation
|