framer-dalton 0.0.7 → 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
|
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": {
|