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.6 */
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 getCredentialsPath() {
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(getCredentialsPath, "getCredentialsPath");
27
- function readCredentials() {
28
- const credPath = getCredentialsPath();
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
- if (fs2.existsSync(credPath)) {
31
- return JSON.parse(fs2.readFileSync(credPath, "utf-8"));
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(readCredentials, "readCredentials");
38
- function writeCredentials(creds) {
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(writeCredentials, "writeCredentials");
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 creds = readCredentials();
62
- return creds[projectId] || null;
138
+ const config = readProjectsConfig();
139
+ return config.projects[projectId]?.apiKey ?? null;
63
140
  }
64
141
  __name(getApiKey, "getApiKey");
65
- function saveApiKey(projectId, apiKey) {
66
- const creds = readCredentials();
67
- creds[projectId] = apiKey;
68
- writeCredentials(creds);
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(saveApiKey, "saveApiKey");
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.6" ;
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(projectUrl, apiKeyArg) {
14895
+ function resolveSessionCredentials(projectUrlOrId, apiKey) {
14815
14896
  try {
14816
- const projectId = extractProjectId(projectUrl);
14817
- if (!apiKeyArg) {
14818
- const cachedApiKey = getApiKey(projectId);
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
- saveApiKey(projectId, apiKeyArg);
14828
- return { projectId, apiKey: apiKeyArg };
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 createSession(projectId, apiKey) {
14836
- try {
14837
- const result = await client.createSession.mutate({
14838
- projectId,
14839
- apiKey
14840
- });
14841
- return result.id;
14842
- } catch (err) {
14843
- printError(`Failed to create session: ${formatError(err)}`);
14844
- process.exit(1);
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(createSession, "createSession");
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 <projectUrl> [apiKey]").description("Create a new session and print the session ID").action(async (projectUrlArg, apiKeyArg) => {
14895
- const { projectId, apiKey } = resolveSessionCredentials(
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
- const sessionId = await createSession(projectId, apiKey);
14901
- await refreshSkillsFromSession(sessionId, projectId);
14902
- print(sessionId);
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.6 */
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.6" ;
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
 
@@ -1,10 +1,12 @@
1
1
  ---
2
2
  name: framer
3
- description: "Single entry point for Framer tasks. Mandatory precondition: run `npx framer-dalton@latest setup` and let it complete BEFORE loading this skill."
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
- Ask for the Project URL, then create a session:
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 "<projectUrl>" # Uses cached API key
39
- npx framer-dalton session new "<projectUrl>" "<apiKey>" # First time: needs API key (Project Settings > General > API Keys)
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.6",
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@5804b52.tgz",
28
+ "framer-api": "https://pkg.pr.new/framer/FramerStudio/framer-api@0711174.tgz",
29
29
  "zod": "^4.3.6"
30
30
  },
31
31
  "devDependencies": {