framer-dalton 0.0.13 → 0.0.14

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
@@ -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 */
13
+ /* @framer/ai CLI v0.0.14 */
14
14
  var __defProp = Object.defineProperty;
15
15
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
16
16
  function openUrl(url) {
@@ -180,6 +180,19 @@ function clearApiKey(projectId) {
180
180
  }
181
181
  __name(clearApiKey, "clearApiKey");
182
182
 
183
+ // src/debug.ts
184
+ var enabled = false;
185
+ function setDebugEnabled(value) {
186
+ enabled = value;
187
+ }
188
+ __name(setDebugEnabled, "setDebugEnabled");
189
+ function debug(tag, message) {
190
+ if (!enabled) return;
191
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
192
+ console.error(`[debug ${timestamp}] ${tag}: ${message}`);
193
+ }
194
+ __name(debug, "debug");
195
+
183
196
  // src/utils.ts
184
197
  function formatError(error) {
185
198
  if (error instanceof Error) {
@@ -323,6 +336,7 @@ async function acquireKeyFromBrowser(projectId) {
323
336
  __name(settle, "settle");
324
337
  const server = http.createServer((req, res) => {
325
338
  const url = new URL(req.url ?? "/", `http://127.0.0.1`);
339
+ debug("auth", `request: ${url.pathname}`);
326
340
  if (url.pathname === "/done") {
327
341
  const theme2 = parseTheme(url.searchParams.get("theme"));
328
342
  res.writeHead(200, { "Content-Type": "text/html" });
@@ -331,6 +345,7 @@ async function acquireKeyFromBrowser(projectId) {
331
345
  const apiKey2 = pendingApiKey;
332
346
  pendingApiKey = null;
333
347
  settle(() => {
348
+ debug("auth", "received API key via /done, resolving");
334
349
  print("\u2713 Agent authorized");
335
350
  resolve(apiKey2);
336
351
  });
@@ -367,6 +382,7 @@ async function acquireKeyFromBrowser(projectId) {
367
382
  res.end();
368
383
  return;
369
384
  }
385
+ debug("auth", "received valid callback, redirecting to /done");
370
386
  pendingApiKey = apiKey;
371
387
  res.writeHead(302, { Location: `/done?theme=${theme}` });
372
388
  res.end();
@@ -376,6 +392,7 @@ async function acquireKeyFromBrowser(projectId) {
376
392
  const apiKey = getApiKey(projectId);
377
393
  if (apiKey) {
378
394
  settle(() => {
395
+ debug("auth", "API key detected from config file poll");
379
396
  print("\u2713 API key detected from config");
380
397
  resolve(apiKey);
381
398
  });
@@ -407,6 +424,7 @@ async function acquireKeyFromBrowser(projectId) {
407
424
  function cleanup() {
408
425
  if (cleaned) return;
409
426
  cleaned = true;
427
+ debug("auth", "cleanup: clearing timers and closing server");
410
428
  clearTimeout(timer);
411
429
  clearTimeout(hintTimer);
412
430
  clearInterval(pollTimer);
@@ -416,20 +434,24 @@ async function acquireKeyFromBrowser(projectId) {
416
434
  __name(cleanup, "cleanup");
417
435
  server.keepAliveTimeout = 0;
418
436
  server.unref();
437
+ debug("auth", "starting callback server...");
419
438
  server.listen(0, "127.0.0.1", () => {
420
439
  const addr = server.address();
421
440
  if (!addr || typeof addr === "string") {
422
441
  settle(() => reject(new Error("Failed to start callback server.")));
423
442
  return;
424
443
  }
444
+ debug("auth", `callback server listening on port ${addr.port}`);
425
445
  const callbackUrl = `http://127.0.0.1:${addr.port}/callback`;
426
446
  const deeplink = new URL("https://framer.com/projects/server-api/auth");
427
447
  deeplink.searchParams.set("callback", callbackUrl);
428
448
  deeplink.searchParams.set("state", state);
429
449
  deeplink.searchParams.set("projectId", projectId);
430
450
  const deeplinkStr = deeplink.toString();
451
+ debug("auth", "opening browser for deeplink...");
431
452
  openUrl(deeplinkStr).then((opened) => {
432
453
  if (opened) {
454
+ debug("auth", "browser opened successfully");
433
455
  print("Waiting for authorization in browser...");
434
456
  } else {
435
457
  printError("Could not open browser. Open this URL manually:");
@@ -6629,6 +6651,18 @@ var types = {
6629
6651
  description: "",
6630
6652
  optional: false
6631
6653
  },
6654
+ {
6655
+ name: "[getCurrentUserMessageType]",
6656
+ type: "() => Promise<User>",
6657
+ description: "",
6658
+ optional: false
6659
+ },
6660
+ {
6661
+ name: "[getProjectInfoMessageType]",
6662
+ type: "() => Promise<ProjectInfo>",
6663
+ description: "",
6664
+ optional: false
6665
+ },
6632
6666
  {
6633
6667
  name: "[getHTMLForNodeMessageType]",
6634
6668
  type: "(nodeId: NodeId) => Promise<string | null>",
@@ -14944,9 +14978,16 @@ ${typeDef}`);
14944
14978
  return { lines, errors };
14945
14979
  }
14946
14980
  __name(renderDocs, "renderDocs");
14981
+
14982
+ // src/version.ts
14983
+ var VERSION = (
14984
+ // typeof is used to ensure this can be used just via tsx or node etc. without build
14985
+ "0.0.14"
14986
+ );
14987
+
14988
+ // src/relay-client.ts
14947
14989
  var __filename$1 = fileURLToPath(import.meta.url);
14948
14990
  var __dirname$1 = path4.dirname(__filename$1);
14949
- var VERSION = "0.0.13" ;
14950
14991
  var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
14951
14992
  var client = createTRPCClient({
14952
14993
  links: [
@@ -14984,11 +15025,15 @@ function compareVersions(v1, v2) {
14984
15025
  __name(compareVersions, "compareVersions");
14985
15026
  async function ensureRelayServerRunning(options = {}) {
14986
15027
  const { logger, restartOnVersionMismatch = true } = options;
15028
+ debug("relay", `checking server version (client=${VERSION})...`);
14987
15029
  const serverVersion = await getRelayServerVersion();
15030
+ debug("relay", `server version=${serverVersion ?? "not running"}`);
14988
15031
  if (serverVersion === VERSION) {
15032
+ debug("relay", "version matches, no action needed");
14989
15033
  return;
14990
15034
  }
14991
15035
  if (serverVersion !== null && compareVersions(serverVersion, VERSION) > 0) {
15036
+ debug("relay", "server version is newer, keeping it");
14992
15037
  return;
14993
15038
  }
14994
15039
  if (serverVersion !== null) {
@@ -14996,11 +15041,13 @@ async function ensureRelayServerRunning(options = {}) {
14996
15041
  logger?.log(
14997
15042
  `Relay server version mismatch (server: ${serverVersion}, client: ${VERSION}), restarting...`
14998
15043
  );
15044
+ debug("relay", "shutting down old server...");
14999
15045
  try {
15000
15046
  await client.shutdown.mutate();
15001
15047
  await sleep(500);
15002
15048
  } catch {
15003
15049
  }
15050
+ debug("relay", "old server shut down");
15004
15051
  } else {
15005
15052
  return;
15006
15053
  }
@@ -15009,6 +15056,7 @@ async function ensureRelayServerRunning(options = {}) {
15009
15056
  }
15010
15057
  const isRunningFromSource = __filename$1.endsWith(".ts");
15011
15058
  const scriptPath = isRunningFromSource ? path4.resolve(__dirname$1, "./start-relay-server.ts") : path4.resolve(__dirname$1, "./start-relay-server.js");
15059
+ debug("relay", `spawning server process: ${scriptPath}`);
15012
15060
  const serverProcess = spawn(
15013
15061
  isRunningFromSource ? "tsx" : process.execPath,
15014
15062
  [scriptPath],
@@ -15019,9 +15067,13 @@ async function ensureRelayServerRunning(options = {}) {
15019
15067
  }
15020
15068
  );
15021
15069
  serverProcess.unref();
15022
- for (let i = 0; i < 10; i++) {
15070
+ for (let attempt = 0; attempt < 10; attempt++) {
15023
15071
  await sleep(500);
15024
15072
  const newVersion = await getRelayServerVersion();
15073
+ debug(
15074
+ "relay",
15075
+ `readiness poll ${attempt + 1}/10: version=${newVersion ?? "not ready"}`
15076
+ );
15025
15077
  if (newVersion) {
15026
15078
  logger?.log("Relay server started successfully");
15027
15079
  return;
@@ -15155,7 +15207,12 @@ __name(installSkills, "installSkills");
15155
15207
 
15156
15208
  // src/cli.ts
15157
15209
  var program = new Command();
15158
- program.name("framer").version(VERSION).description("Framer Server API CLI");
15210
+ program.name("framer").version(VERSION).description("Framer Server API CLI").option("--debug", "Enable debug logging").hook("preAction", (thisCommand) => {
15211
+ const opts = thisCommand.opts();
15212
+ if (opts.debug) {
15213
+ setDebugEnabled(true);
15214
+ }
15215
+ });
15159
15216
  async function readStdin() {
15160
15217
  const chunks = [];
15161
15218
  for await (const chunk of process.stdin) {
@@ -15180,11 +15237,13 @@ function printSetupSummary(results) {
15180
15237
  }
15181
15238
  __name(printSetupSummary, "printSetupSummary");
15182
15239
  async function getAgentSystemPrompt(sessionId) {
15240
+ debug("exec", "getAgentSystemPrompt: calling relay...");
15183
15241
  const result = await client.exec.mutate({
15184
15242
  sessionId,
15185
15243
  code: "return await framer.getAgentSystemPrompt();",
15186
15244
  cwd: process.cwd()
15187
15245
  });
15246
+ debug("exec", "getAgentSystemPrompt: relay responded");
15188
15247
  if (result.error) {
15189
15248
  throw new Error(result.error);
15190
15249
  }
@@ -15196,11 +15255,13 @@ async function getAgentSystemPrompt(sessionId) {
15196
15255
  }
15197
15256
  __name(getAgentSystemPrompt, "getAgentSystemPrompt");
15198
15257
  async function getAgentContext(sessionId) {
15258
+ debug("exec", "getAgentContext: calling relay...");
15199
15259
  const result = await client.exec.mutate({
15200
15260
  sessionId,
15201
15261
  code: "return await framer.getAgentContext({ pagePath: '/' });",
15202
15262
  cwd: process.cwd()
15203
15263
  });
15264
+ debug("exec", "getAgentContext: relay responded");
15204
15265
  if (result.error) {
15205
15266
  throw new Error(result.error);
15206
15267
  }
@@ -15213,16 +15274,19 @@ async function getAgentContext(sessionId) {
15213
15274
  __name(getAgentContext, "getAgentContext");
15214
15275
  async function refreshSkillsFromSession(sessionId, projectId) {
15215
15276
  try {
15277
+ debug("skills", "fetching agent system prompt + context...");
15216
15278
  const [canvasPrompt, agentContext] = await Promise.all([
15217
15279
  getAgentSystemPrompt(sessionId),
15218
15280
  getAgentContext(sessionId)
15219
15281
  ]);
15282
+ debug("skills", "installing skills...");
15220
15283
  installSkills({
15221
15284
  type: "project",
15222
15285
  canvasPrompt,
15223
15286
  projectId,
15224
15287
  agentContext
15225
15288
  });
15289
+ debug("skills", "skills installed");
15226
15290
  } catch (err) {
15227
15291
  printError(
15228
15292
  `Failed to refresh skills for session ${sessionId}: ${formatError(err)}`
@@ -15234,11 +15298,15 @@ __name(refreshSkillsFromSession, "refreshSkillsFromSession");
15234
15298
  async function resolveSessionCredentials(projectUrlOrId) {
15235
15299
  try {
15236
15300
  const projectId = extractProjectId(projectUrlOrId);
15301
+ debug("credentials", `projectId=${projectId}`);
15237
15302
  const cachedApiKey = getApiKey(projectId);
15238
15303
  if (cachedApiKey) {
15304
+ debug("credentials", "using cached API key");
15239
15305
  return { projectId, apiKey: cachedApiKey };
15240
15306
  }
15307
+ debug("credentials", "no cached key, starting browser auth flow...");
15241
15308
  const newApiKey = await acquireKeyFromBrowser(projectId);
15309
+ debug("credentials", "browser auth complete, saving project");
15242
15310
  saveProject({ projectId, apiKey: newApiKey });
15243
15311
  return { projectId, apiKey: newApiKey };
15244
15312
  } catch (err) {
@@ -15248,11 +15316,13 @@ async function resolveSessionCredentials(projectUrlOrId) {
15248
15316
  }
15249
15317
  __name(resolveSessionCredentials, "resolveSessionCredentials");
15250
15318
  async function getProjectName(sessionId) {
15319
+ debug("exec", "getProjectName: calling relay...");
15251
15320
  const result = await client.exec.mutate({
15252
15321
  sessionId,
15253
15322
  code: "return (await framer.getProjectInfo()).name;",
15254
15323
  cwd: process.cwd()
15255
15324
  });
15325
+ debug("exec", "getProjectName: relay responded");
15256
15326
  if (result.error) {
15257
15327
  throw new Error(result.error);
15258
15328
  }
@@ -15265,7 +15335,9 @@ async function getProjectName(sessionId) {
15265
15335
  __name(getProjectName, "getProjectName");
15266
15336
  async function ensureRelayForCli() {
15267
15337
  try {
15338
+ debug("relay", "ensuring relay server is running...");
15268
15339
  await ensureRelayServerRunning({ logger: { log: print } });
15340
+ debug("relay", "relay server ready");
15269
15341
  } catch (err) {
15270
15342
  printError(`Failed to check relay status: ${formatError(err)}`);
15271
15343
  process.exit(1);
@@ -15322,27 +15394,39 @@ program.option("-s, --session <id>", "Session ID (required for code execution)")
15322
15394
  });
15323
15395
  var session = program.command("session").description("Manage sessions");
15324
15396
  session.command("new <projectUrlOrId>").description("Create a new session and print the session ID").action(async (projectUrlOrId) => {
15397
+ debug("session.new", `starting for ${projectUrlOrId}`);
15398
+ debug("session.new", "resolving credentials + ensuring relay...");
15325
15399
  const [credentials] = await Promise.all([
15326
15400
  resolveSessionCredentials(projectUrlOrId),
15327
15401
  ensureRelayForCli()
15328
15402
  ]);
15403
+ debug(
15404
+ "session.new",
15405
+ `credentials resolved for project=${credentials.projectId}`
15406
+ );
15329
15407
  try {
15408
+ debug("session.new", "calling createSession on relay...");
15330
15409
  const result = await client.createSession.mutate({
15331
15410
  projectId: credentials.projectId,
15332
15411
  apiKey: credentials.apiKey
15333
15412
  });
15334
15413
  const sessionId = result.id;
15414
+ debug("session.new", `session created id=${sessionId}`);
15415
+ debug("session.new", "fetching project name + refreshing skills...");
15335
15416
  const [projectName] = await Promise.all([
15336
15417
  getProjectName(sessionId),
15337
15418
  refreshSkillsFromSession(sessionId, credentials.projectId)
15338
15419
  ]);
15420
+ debug("session.new", `project name="${projectName}", skills refreshed`);
15339
15421
  saveProject({
15340
15422
  projectId: credentials.projectId,
15341
15423
  apiKey: credentials.apiKey,
15342
15424
  name: projectName,
15343
15425
  lastUsedAt: (/* @__PURE__ */ new Date()).toISOString()
15344
15426
  });
15427
+ debug("session.new", "project saved, printing session id");
15345
15428
  print(sessionId);
15429
+ debug("session.new", "done \u2014 process should exit now");
15346
15430
  } catch (err) {
15347
15431
  const message = formatError(err);
15348
15432
  printError(`Failed to create session: ${message}`);
@@ -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.13 */
16
+ /* @framer/ai relay server v0.0.14 */
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) => {
@@ -89,9 +89,16 @@ function log(message) {
89
89
  `);
90
90
  }
91
91
  __name(log, "log");
92
+
93
+ // src/version.ts
94
+ var VERSION = (
95
+ // typeof is used to ensure this can be used just via tsx or node etc. without build
96
+ "0.0.14"
97
+ );
98
+
99
+ // src/relay-client.ts
92
100
  var __filename$1 = fileURLToPath(import.meta.url);
93
101
  path.dirname(__filename$1);
94
- var VERSION = "0.0.13" ;
95
102
  var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
96
103
  createTRPCClient({
97
104
  links: [
@@ -527,7 +534,9 @@ var ConnectionPool = class {
527
534
  }
528
535
  return entry.connection;
529
536
  }
530
- const connection = await connect(projectId, apiKey);
537
+ const connection = await connect(projectId, apiKey, {
538
+ clientId: `dalton/${VERSION}`
539
+ });
531
540
  this.pool.set(projectId, {
532
541
  connection,
533
542
  sessions: /* @__PURE__ */ new Set([session]),
@@ -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({{FRAMER_TEMPORARY_DIR}}/*)"]
4
+ allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)", "Read({{FRAMER_TEMPORARY_DIR}}/*)", "Write({{FRAMER_TEMPORARY_DIR}}/*)"]
5
5
  ---
6
6
 
7
7
  ## Project Scope
@@ -49,6 +49,12 @@ framer -s <sessionId> -f {{FRAMER_TEMPORARY_DIR}}/<sessionId>-read-page.js
49
49
  framer -s <sessionId> -f {{FRAMER_TEMPORARY_DIR}}/<sessionId>-apply-changes.js
50
50
  ```
51
51
 
52
+ ## Critical for UX: Streaming
53
+
54
+ When you generate a DSL file to apply, keep it short. Each file must only cover ONE logical section (header, footer, hero, card, etc.). Once you generate ONE section, apply it and continue with the next section.
55
+
56
+ This ensures progress is streamed to the user in small, visible increments.
57
+
52
58
  ## Live Agent System Prompt
53
59
 
54
60
  This is the static canvas-editing prompt returned by `framer.getAgentSystemPrompt()`. It is the sole documentation for:
@@ -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({{FRAMER_TEMPORARY_DIR}}/*)"]
6
+ allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)", "Read({{FRAMER_TEMPORARY_DIR}}/*)", "Write({{FRAMER_TEMPORARY_DIR}}/*)"]
7
7
  ---
8
8
 
9
9
  If you didn't run this command before loading the skill, run it now:
@@ -28,6 +28,10 @@ What you can do with the Framer CLI:
28
28
 
29
29
  ## CLI Usage
30
30
 
31
+ ### Permissions
32
+
33
+ The Framer CLI requires network access (to download packages and connect to Framer servers) and file-system access outside the project directory (to install skills into `~/.agents/skills/` and `~/.claude/skills/`). In sandboxed environments, you must execute `npx framer-dalton` commands with elevated permissions from the get-go. Do not try to execute them with the default sandboxed permissions; they will hang or fail.
34
+
31
35
  ### Required Workflow
32
36
 
33
37
  Every task follows these steps:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "framer-dalton",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
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@0711174.tgz",
28
+ "framer-api": "^0.1.4",
29
29
  "zod": "^4.3.6"
30
30
  },
31
31
  "devDependencies": {