framer-dalton 0.0.6 → 0.0.8
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
CHANGED
|
@@ -3,11 +3,12 @@ import path3 from 'path';
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import fs2 from 'fs';
|
|
5
5
|
import os from 'os';
|
|
6
|
+
import { z } from 'zod';
|
|
6
7
|
import { spawn } from 'child_process';
|
|
7
8
|
import { fileURLToPath } from 'url';
|
|
8
9
|
import { createTRPCClient, httpLink } from '@trpc/client';
|
|
9
10
|
|
|
10
|
-
/* @framer/ai CLI v0.0.
|
|
11
|
+
/* @framer/ai CLI v0.0.8 */
|
|
11
12
|
var __defProp = Object.defineProperty;
|
|
12
13
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
13
14
|
function getConfigDir() {
|
|
@@ -20,32 +21,92 @@ function getConfigDir() {
|
|
|
20
21
|
return path3.join(os.homedir(), ".config", "framer");
|
|
21
22
|
}
|
|
22
23
|
__name(getConfigDir, "getConfigDir");
|
|
23
|
-
function
|
|
24
|
+
function getProjectsConfigPath() {
|
|
25
|
+
return path3.join(getConfigDir(), "projects.json");
|
|
26
|
+
}
|
|
27
|
+
__name(getProjectsConfigPath, "getProjectsConfigPath");
|
|
28
|
+
function getLegacyCredentialsPath() {
|
|
24
29
|
return path3.join(getConfigDir(), "credentials.json");
|
|
25
30
|
}
|
|
26
|
-
__name(
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
__name(getLegacyCredentialsPath, "getLegacyCredentialsPath");
|
|
32
|
+
var ProjectsConfigSchema = z.object({
|
|
33
|
+
version: z.literal(2),
|
|
34
|
+
projects: z.record(
|
|
35
|
+
z.string(),
|
|
36
|
+
z.object({
|
|
37
|
+
apiKey: z.string(),
|
|
38
|
+
name: z.string().optional(),
|
|
39
|
+
lastUsedAt: z.string().optional()
|
|
40
|
+
})
|
|
41
|
+
)
|
|
42
|
+
});
|
|
43
|
+
var LegacyCredentialsSchema = z.record(z.string(), z.string());
|
|
44
|
+
function readJsonFile(filePath) {
|
|
45
|
+
if (!fs2.existsSync(filePath)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
29
48
|
try {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
} catch {
|
|
49
|
+
return JSON.parse(fs2.readFileSync(filePath, "utf-8"));
|
|
50
|
+
} catch (_error) {
|
|
51
|
+
return null;
|
|
34
52
|
}
|
|
35
|
-
return {};
|
|
36
53
|
}
|
|
37
|
-
__name(
|
|
38
|
-
function
|
|
39
|
-
const credPath = getCredentialsPath();
|
|
54
|
+
__name(readJsonFile, "readJsonFile");
|
|
55
|
+
function ensureConfigDir() {
|
|
40
56
|
const configDir = getConfigDir();
|
|
41
57
|
if (!fs2.existsSync(configDir)) {
|
|
42
58
|
fs2.mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
43
59
|
}
|
|
44
|
-
fs2.writeFileSync(credPath, JSON.stringify(creds, null, " "), {
|
|
45
|
-
mode: 384
|
|
46
|
-
});
|
|
47
60
|
}
|
|
48
|
-
__name(
|
|
61
|
+
__name(ensureConfigDir, "ensureConfigDir");
|
|
62
|
+
function writeProjectsConfig(config) {
|
|
63
|
+
ensureConfigDir();
|
|
64
|
+
fs2.writeFileSync(
|
|
65
|
+
getProjectsConfigPath(),
|
|
66
|
+
JSON.stringify(config, null, " "),
|
|
67
|
+
{
|
|
68
|
+
mode: 384
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
__name(writeProjectsConfig, "writeProjectsConfig");
|
|
73
|
+
function readLegacyCredentials() {
|
|
74
|
+
const legacyPath = getLegacyCredentialsPath();
|
|
75
|
+
const parsed = readJsonFile(legacyPath);
|
|
76
|
+
const result = LegacyCredentialsSchema.safeParse(parsed);
|
|
77
|
+
if (!result.success) {
|
|
78
|
+
return {};
|
|
79
|
+
}
|
|
80
|
+
return result.data;
|
|
81
|
+
}
|
|
82
|
+
__name(readLegacyCredentials, "readLegacyCredentials");
|
|
83
|
+
function migrateLegacyCredentials() {
|
|
84
|
+
const legacyCreds = readLegacyCredentials();
|
|
85
|
+
const projects = {};
|
|
86
|
+
for (const [projectId, apiKey] of Object.entries(legacyCreds)) {
|
|
87
|
+
projects[projectId] = { apiKey };
|
|
88
|
+
}
|
|
89
|
+
const config = { version: 2, projects };
|
|
90
|
+
writeProjectsConfig(config);
|
|
91
|
+
fs2.rmSync(getLegacyCredentialsPath(), { force: true });
|
|
92
|
+
return config;
|
|
93
|
+
}
|
|
94
|
+
__name(migrateLegacyCredentials, "migrateLegacyCredentials");
|
|
95
|
+
function readProjectsConfig() {
|
|
96
|
+
const projectsPath = getProjectsConfigPath();
|
|
97
|
+
const parsed = readJsonFile(projectsPath);
|
|
98
|
+
if (parsed !== null) {
|
|
99
|
+
const result = ProjectsConfigSchema.safeParse(parsed);
|
|
100
|
+
if (result.success) {
|
|
101
|
+
return result.data;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (fs2.existsSync(getLegacyCredentialsPath())) {
|
|
105
|
+
return migrateLegacyCredentials();
|
|
106
|
+
}
|
|
107
|
+
return { version: 2, projects: {} };
|
|
108
|
+
}
|
|
109
|
+
__name(readProjectsConfig, "readProjectsConfig");
|
|
49
110
|
function extractProjectId(input) {
|
|
50
111
|
if (/^[a-zA-Z0-9]+$/.test(input)) {
|
|
51
112
|
return input;
|
|
@@ -54,20 +115,40 @@ function extractProjectId(input) {
|
|
|
54
115
|
if (match) {
|
|
55
116
|
return match[1];
|
|
56
117
|
}
|
|
118
|
+
const slugMatch = input.match(/^(?:.+--)?([a-zA-Z0-9]+)(?:-[a-zA-Z0-9]+)?$/);
|
|
119
|
+
if (slugMatch) {
|
|
120
|
+
return slugMatch[1];
|
|
121
|
+
}
|
|
57
122
|
return input;
|
|
58
123
|
}
|
|
59
124
|
__name(extractProjectId, "extractProjectId");
|
|
125
|
+
function listProjects() {
|
|
126
|
+
const config = readProjectsConfig();
|
|
127
|
+
return Object.entries(config.projects).map(([projectId, project2]) => ({
|
|
128
|
+
projectId,
|
|
129
|
+
...project2
|
|
130
|
+
})).sort((a, b) => {
|
|
131
|
+
const left = a.lastUsedAt ?? "";
|
|
132
|
+
const right = b.lastUsedAt ?? "";
|
|
133
|
+
return right.localeCompare(left);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
__name(listProjects, "listProjects");
|
|
60
137
|
function getApiKey(projectId) {
|
|
61
|
-
const
|
|
62
|
-
return
|
|
138
|
+
const config = readProjectsConfig();
|
|
139
|
+
return config.projects[projectId]?.apiKey ?? null;
|
|
63
140
|
}
|
|
64
141
|
__name(getApiKey, "getApiKey");
|
|
65
|
-
function
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
142
|
+
function saveProject(project2) {
|
|
143
|
+
const config = readProjectsConfig();
|
|
144
|
+
config.projects[project2.projectId] = {
|
|
145
|
+
apiKey: project2.apiKey,
|
|
146
|
+
name: project2.name,
|
|
147
|
+
lastUsedAt: project2.lastUsedAt
|
|
148
|
+
};
|
|
149
|
+
writeProjectsConfig(config);
|
|
69
150
|
}
|
|
70
|
-
__name(
|
|
151
|
+
__name(saveProject, "saveProject");
|
|
71
152
|
|
|
72
153
|
// src/types-data.ts
|
|
73
154
|
var types = {
|
|
@@ -14527,7 +14608,7 @@ ${typeDef}`);
|
|
|
14527
14608
|
__name(renderDocs, "renderDocs");
|
|
14528
14609
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
14529
14610
|
var __dirname$1 = path3.dirname(__filename$1);
|
|
14530
|
-
var VERSION = "0.0.
|
|
14611
|
+
var VERSION = "0.0.8" ;
|
|
14531
14612
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
|
|
14532
14613
|
var client = createTRPCClient({
|
|
14533
14614
|
links: [
|
|
@@ -14811,40 +14892,42 @@ async function refreshSkillsFromSession(sessionId, projectId) {
|
|
|
14811
14892
|
}
|
|
14812
14893
|
}
|
|
14813
14894
|
__name(refreshSkillsFromSession, "refreshSkillsFromSession");
|
|
14814
|
-
function resolveSessionCredentials(
|
|
14895
|
+
function resolveSessionCredentials(projectUrlOrId, apiKey) {
|
|
14815
14896
|
try {
|
|
14816
|
-
const projectId = extractProjectId(
|
|
14817
|
-
if (
|
|
14818
|
-
|
|
14819
|
-
if (cachedApiKey) {
|
|
14820
|
-
return { projectId, apiKey: cachedApiKey };
|
|
14821
|
-
}
|
|
14822
|
-
printError("No API key provided and none cached for this project.");
|
|
14823
|
-
printError("");
|
|
14824
|
-
printError("Usage: framer session new <projectUrl> <apiKey>");
|
|
14825
|
-
process.exit(1);
|
|
14897
|
+
const projectId = extractProjectId(projectUrlOrId);
|
|
14898
|
+
if (apiKey) {
|
|
14899
|
+
return { projectId, apiKey };
|
|
14826
14900
|
}
|
|
14827
|
-
|
|
14828
|
-
|
|
14901
|
+
const cachedApiKey = getApiKey(projectId);
|
|
14902
|
+
if (cachedApiKey) {
|
|
14903
|
+
return { projectId, apiKey: cachedApiKey };
|
|
14904
|
+
}
|
|
14905
|
+
printError("No API key provided and none cached for this project.");
|
|
14906
|
+
printError("");
|
|
14907
|
+
printError("Usage: framer session new <projectUrl> <apiKey>");
|
|
14908
|
+
process.exit(1);
|
|
14829
14909
|
} catch (err) {
|
|
14830
14910
|
printError(`Failed to create session: ${formatError(err)}`);
|
|
14831
14911
|
process.exit(1);
|
|
14832
14912
|
}
|
|
14833
14913
|
}
|
|
14834
14914
|
__name(resolveSessionCredentials, "resolveSessionCredentials");
|
|
14835
|
-
async function
|
|
14836
|
-
|
|
14837
|
-
|
|
14838
|
-
|
|
14839
|
-
|
|
14840
|
-
|
|
14841
|
-
|
|
14842
|
-
|
|
14843
|
-
|
|
14844
|
-
|
|
14915
|
+
async function getProjectName(sessionId) {
|
|
14916
|
+
const result = await client.exec.mutate({
|
|
14917
|
+
sessionId,
|
|
14918
|
+
code: "return (await framer.getProjectInfo()).name;",
|
|
14919
|
+
cwd: process.cwd()
|
|
14920
|
+
});
|
|
14921
|
+
if (result.error) {
|
|
14922
|
+
throw new Error(result.error);
|
|
14923
|
+
}
|
|
14924
|
+
const projectName = result.output.at(-1);
|
|
14925
|
+
if (typeof projectName !== "string") {
|
|
14926
|
+
throw new Error("Did not receive project name output.");
|
|
14845
14927
|
}
|
|
14928
|
+
return projectName;
|
|
14846
14929
|
}
|
|
14847
|
-
__name(
|
|
14930
|
+
__name(getProjectName, "getProjectName");
|
|
14848
14931
|
async function ensureRelayForCli() {
|
|
14849
14932
|
try {
|
|
14850
14933
|
await ensureRelayServerRunning({ logger: { log: print } });
|
|
@@ -14891,15 +14974,30 @@ program.option("-s, --session <id>", "Session ID (required for code execution)")
|
|
|
14891
14974
|
}
|
|
14892
14975
|
});
|
|
14893
14976
|
var session = program.command("session").description("Manage sessions");
|
|
14894
|
-
session.command("new <
|
|
14895
|
-
const
|
|
14896
|
-
projectUrlArg,
|
|
14897
|
-
apiKeyArg
|
|
14898
|
-
);
|
|
14977
|
+
session.command("new <projectUrlOrId> [apiKey]").description("Create a new session and print the session ID").action(async (projectUrlOrId, apiKey) => {
|
|
14978
|
+
const credentials = resolveSessionCredentials(projectUrlOrId, apiKey);
|
|
14899
14979
|
await ensureRelayForCli();
|
|
14900
|
-
|
|
14901
|
-
|
|
14902
|
-
|
|
14980
|
+
try {
|
|
14981
|
+
const result = await client.createSession.mutate({
|
|
14982
|
+
projectId: credentials.projectId,
|
|
14983
|
+
apiKey: credentials.apiKey
|
|
14984
|
+
});
|
|
14985
|
+
const sessionId = result.id;
|
|
14986
|
+
const [projectName] = await Promise.all([
|
|
14987
|
+
getProjectName(sessionId),
|
|
14988
|
+
refreshSkillsFromSession(sessionId, credentials.projectId)
|
|
14989
|
+
]);
|
|
14990
|
+
saveProject({
|
|
14991
|
+
projectId: credentials.projectId,
|
|
14992
|
+
apiKey: credentials.apiKey,
|
|
14993
|
+
name: projectName,
|
|
14994
|
+
lastUsedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
14995
|
+
});
|
|
14996
|
+
print(sessionId);
|
|
14997
|
+
} catch (err) {
|
|
14998
|
+
printError(`Failed to create session: ${formatError(err)}`);
|
|
14999
|
+
process.exit(1);
|
|
15000
|
+
}
|
|
14903
15001
|
});
|
|
14904
15002
|
session.command("list").description("List all active sessions").action(async () => {
|
|
14905
15003
|
await ensureRelayForCli();
|
|
@@ -14915,6 +15013,16 @@ session.command("list").description("List all active sessions").action(async ()
|
|
|
14915
15013
|
process.exit(1);
|
|
14916
15014
|
}
|
|
14917
15015
|
});
|
|
15016
|
+
var project = program.command("project").description("Manage projects");
|
|
15017
|
+
project.command("list").description("List recently used projects").action(() => {
|
|
15018
|
+
printJson(
|
|
15019
|
+
listProjects().map(({ projectId, name, lastUsedAt }) => ({
|
|
15020
|
+
projectId,
|
|
15021
|
+
name,
|
|
15022
|
+
lastUsedAt
|
|
15023
|
+
}))
|
|
15024
|
+
);
|
|
15025
|
+
});
|
|
14918
15026
|
session.command("destroy <sessionId>").description("Destroy a session").action(async (sessionId) => {
|
|
14919
15027
|
await ensureRelayForCli();
|
|
14920
15028
|
try {
|
|
@@ -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.8 */
|
|
17
17
|
var __defProp = Object.defineProperty;
|
|
18
18
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
19
19
|
function getLogPath() {
|
|
@@ -50,7 +50,7 @@ function log(message) {
|
|
|
50
50
|
__name(log, "log");
|
|
51
51
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
52
52
|
path.dirname(__filename$1);
|
|
53
|
-
var VERSION = "0.0.
|
|
53
|
+
var VERSION = "0.0.8" ;
|
|
54
54
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
|
|
55
55
|
createTRPCClient({
|
|
56
56
|
links: [
|
|
@@ -1,6 +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
5
|
---
|
|
5
6
|
|
|
6
7
|
## Project Scope
|
|
@@ -20,12 +21,8 @@ description: "Project-scoped Framer canvas editing skill for project {{PROJECT_I
|
|
|
20
21
|
- Use screenshots only when visual verification is necessary and cannot be confirmed from state reads. Do not use them for initial inspection or between every small change.
|
|
21
22
|
- When setting text content, use raw Unicode characters directly (for example `->`, not `\u2192`).
|
|
22
23
|
- Regenerate the embedded prompt and context by running `session new` again for the same project.
|
|
23
|
-
- Node IDs assigned during commands are temporary and can only be referenced within the same `framer.applyAgentChanges` call. For later calls, re-read the project to get real IDs.
|
|
24
|
-
- For rich text updates, default to `SET <RichTextNode-id> text="..."` for normal text replacement. Use `SET <TextRun-id> text="..."` only for intentional targeted run-level edits in multi-run content.
|
|
25
|
-
- Child `TextRun` IDs are stable within one `applyAgentChanges` batch, but may change across batches or turns after re-hydration. Re-read before dependent follow-up commands in a new batch.
|
|
26
24
|
- In rich text, block-level properties (for example line height or alignment) belong on block or parent targets, not `TextRun` targets.
|
|
27
25
|
- `IconNode` `$control__icon` is immutable after creation. To change it, delete and recreate the node with the desired icon value.
|
|
28
|
-
- `var(--token-<id>)` requires stable token IDs. Temporary IDs are only reliable within the same `applyAgentChanges` batch. If a token was created in a previous batch, re-read state to get its current real ID before referencing it, or create the token and dependents in one batch.
|
|
29
26
|
|
|
30
27
|
## Workflow Loop
|
|
31
28
|
|
package/docs/skills/framer.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: framer
|
|
3
|
-
description:
|
|
3
|
+
description: >
|
|
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
|
+
**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:*)"]
|
|
4
7
|
---
|
|
5
8
|
|
|
6
9
|
If you didn't run this command before loading the skill, run it now:
|
|
7
|
-
|
|
8
10
|
```bash
|
|
9
11
|
npx framer-dalton@latest setup
|
|
10
12
|
```
|
|
@@ -32,11 +34,19 @@ Every task follows these steps:
|
|
|
32
34
|
|
|
33
35
|
#### 1. Connect (once per session)
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
If the user refers to a project by name or description rather than giving a URL, first inspect the recent projects:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx framer-dalton project list
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Use that list to infer the likely project from the names and recency. If the right project is already known, use its project ID with `session new`. If there are multiple possible matches, clarify with the user. Only ask the user for a Project URL if there is no clear match. Avoid talking about the technical contents of returned data like IDs.
|
|
44
|
+
|
|
45
|
+
Create a session:
|
|
36
46
|
|
|
37
47
|
```bash
|
|
38
|
-
npx framer-dalton session new "<
|
|
39
|
-
npx framer-dalton session new "<
|
|
48
|
+
npx framer-dalton session new "<url or id>" # Uses cached API key
|
|
49
|
+
npx framer-dalton session new "<url or id>" "<apiKey>" # First time: needs API key (Project Settings > General > API Keys)
|
|
40
50
|
```
|
|
41
51
|
|
|
42
52
|
#### 2. Look up the API (before EVERY code execution)
|
|
@@ -192,7 +202,10 @@ npx framer-dalton docs ScreenshotOptions # Show type + recursively expa
|
|
|
192
202
|
|
|
193
203
|
### Working with Collections (CMS)
|
|
194
204
|
|
|
195
|
-
Collections are Framer's CMS. Each collection has fields (columns) and items (rows).
|
|
205
|
+
Collections are Framer's CMS. Each collection has fields (columns) and items (rows). There are two types:
|
|
206
|
+
|
|
207
|
+
- **Unmanaged** (`framer.createCollection()`): Users can freely edit these in the Framer UI. Always use this by default.
|
|
208
|
+
- **Managed** (`framer.createManagedCollection()`): Can ONLY be edited via the API. They appear read-only in the Framer UI. Only use managed collections when explicitly asked or when creating a script for data synchronisation.
|
|
196
209
|
|
|
197
210
|
#### Reading Collections
|
|
198
211
|
|
|
@@ -257,8 +270,6 @@ await item.remove();
|
|
|
257
270
|
|
|
258
271
|
#### Managed Collections
|
|
259
272
|
|
|
260
|
-
Managed collections are fully controlled by code - users can't edit them directly. Use for syncing external data sources.
|
|
261
|
-
|
|
262
273
|
```js
|
|
263
274
|
// Create a managed collection
|
|
264
275
|
const managed = await framer.createManagedCollection({ name: "My Sync" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "framer-dalton",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"framer-dalton": "./dist/cli.js"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@trpc/client": "^11.9.0",
|
|
26
26
|
"@trpc/server": "^11.9.0",
|
|
27
27
|
"commander": "^12.1.0",
|
|
28
|
-
"framer-api": "https://pkg.pr.new/framer/FramerStudio/framer-api@
|
|
28
|
+
"framer-api": "https://pkg.pr.new/framer/FramerStudio/framer-api@0711174.tgz",
|
|
29
29
|
"zod": "^4.3.6"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|