framer-dalton 0.0.12 → 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.12 */
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) {
@@ -134,11 +134,10 @@ function extractProjectId(input) {
134
134
  if (/^[a-zA-Z0-9]+$/.test(input)) {
135
135
  return input;
136
136
  }
137
- const match = input.match(/\/projects\/[^/]+--([a-zA-Z0-9]+)/);
138
- if (match) {
139
- return match[1];
140
- }
141
- const slugMatch = input.match(/^(?:.+--)?([a-zA-Z0-9]+)(?:-[a-zA-Z0-9]+)?$/);
137
+ const candidate = input.match(/\/projects\/([^/?#]+)/)?.[1] ?? input;
138
+ const slugMatch = candidate.match(
139
+ /^(?:.+--)?([a-zA-Z0-9]+)(?:-[a-zA-Z0-9]+)?$/
140
+ );
142
141
  if (slugMatch) {
143
142
  return slugMatch[1];
144
143
  }
@@ -181,6 +180,19 @@ function clearApiKey(projectId) {
181
180
  }
182
181
  __name(clearApiKey, "clearApiKey");
183
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
+
184
196
  // src/utils.ts
185
197
  function formatError(error) {
186
198
  if (error instanceof Error) {
@@ -313,20 +325,32 @@ __name(parseCode, "parseCode");
313
325
  async function acquireKeyFromBrowser(projectId) {
314
326
  const state = crypto.randomBytes(16).toString("hex");
315
327
  return new Promise((resolve, reject) => {
316
- let pendingResult = null;
328
+ let settled = false;
329
+ let pendingApiKey = null;
330
+ function settle(fn) {
331
+ if (settled) return;
332
+ settled = true;
333
+ cleanup();
334
+ fn();
335
+ }
336
+ __name(settle, "settle");
317
337
  const server = http.createServer((req, res) => {
318
338
  const url = new URL(req.url ?? "/", `http://127.0.0.1`);
339
+ debug("auth", `request: ${url.pathname}`);
319
340
  if (url.pathname === "/done") {
320
341
  const theme2 = parseTheme(url.searchParams.get("theme"));
321
342
  res.writeHead(200, { "Content-Type": "text/html" });
322
- res.end(successHtml(theme2));
323
- if (pendingResult) {
324
- const { apiKey: apiKey2 } = pendingResult;
325
- pendingResult = null;
326
- cleanup();
327
- print("\u2713 Agent authorized");
328
- resolve(apiKey2);
329
- }
343
+ res.end(successHtml(theme2), () => {
344
+ if (pendingApiKey) {
345
+ const apiKey2 = pendingApiKey;
346
+ pendingApiKey = null;
347
+ settle(() => {
348
+ debug("auth", "received API key via /done, resolving");
349
+ print("\u2713 Agent authorized");
350
+ resolve(apiKey2);
351
+ });
352
+ }
353
+ });
330
354
  return;
331
355
  }
332
356
  if (url.pathname === "/error") {
@@ -346,9 +370,9 @@ async function acquireKeyFromBrowser(projectId) {
346
370
  const code = parseCode(url.searchParams.get("code")) ?? legacyCode;
347
371
  if (code !== null) {
348
372
  res.writeHead(200, { "Content-Type": "text/html" });
349
- res.end(codeHtml(code, theme));
350
- cleanup();
351
- reject(new Error(codeMessages[code]));
373
+ res.end(codeHtml(code, theme), () => {
374
+ settle(() => reject(new Error(codeMessages[code])));
375
+ });
352
376
  return;
353
377
  }
354
378
  const apiKey = url.searchParams.get("apiKey");
@@ -358,7 +382,8 @@ async function acquireKeyFromBrowser(projectId) {
358
382
  res.end();
359
383
  return;
360
384
  }
361
- pendingResult = { apiKey, theme };
385
+ debug("auth", "received valid callback, redirecting to /done");
386
+ pendingApiKey = apiKey;
362
387
  res.writeHead(302, { Location: `/done?theme=${theme}` });
363
388
  res.end();
364
389
  });
@@ -366,9 +391,11 @@ async function acquireKeyFromBrowser(projectId) {
366
391
  const pollTimer = setInterval(() => {
367
392
  const apiKey = getApiKey(projectId);
368
393
  if (apiKey) {
369
- cleanup();
370
- print("\u2713 API key detected from config");
371
- resolve(apiKey);
394
+ settle(() => {
395
+ debug("auth", "API key detected from config file poll");
396
+ print("\u2713 API key detected from config");
397
+ resolve(apiKey);
398
+ });
372
399
  }
373
400
  }, POLL_INTERVAL_MS);
374
401
  const HINT_MS = 55e3;
@@ -385,40 +412,52 @@ async function acquireKeyFromBrowser(projectId) {
385
412
  print("Waiting for authorization in browser...");
386
413
  }, HINT_MS);
387
414
  const timer = setTimeout(() => {
388
- cleanup();
389
- reject(
390
- new Error(
391
- "Browser authorization timed out. Use `framer project auth <projectUrlOrId> <apiKey>` instead."
415
+ settle(
416
+ () => reject(
417
+ new Error(
418
+ "Browser authorization timed out. Use `framer project auth <projectUrlOrId> <apiKey>` instead."
419
+ )
392
420
  )
393
421
  );
394
422
  }, TIMEOUT_MS);
423
+ let cleaned = false;
395
424
  function cleanup() {
425
+ if (cleaned) return;
426
+ cleaned = true;
427
+ debug("auth", "cleanup: clearing timers and closing server");
396
428
  clearTimeout(timer);
397
429
  clearTimeout(hintTimer);
398
430
  clearInterval(pollTimer);
399
431
  server.close();
432
+ server.closeAllConnections();
400
433
  }
401
434
  __name(cleanup, "cleanup");
402
- server.listen(0, "127.0.0.1", async () => {
435
+ server.keepAliveTimeout = 0;
436
+ server.unref();
437
+ debug("auth", "starting callback server...");
438
+ server.listen(0, "127.0.0.1", () => {
403
439
  const addr = server.address();
404
440
  if (!addr || typeof addr === "string") {
405
- cleanup();
406
- reject(new Error("Failed to start callback server."));
441
+ settle(() => reject(new Error("Failed to start callback server.")));
407
442
  return;
408
443
  }
444
+ debug("auth", `callback server listening on port ${addr.port}`);
409
445
  const callbackUrl = `http://127.0.0.1:${addr.port}/callback`;
410
446
  const deeplink = new URL("https://framer.com/projects/server-api/auth");
411
447
  deeplink.searchParams.set("callback", callbackUrl);
412
448
  deeplink.searchParams.set("state", state);
413
449
  deeplink.searchParams.set("projectId", projectId);
414
450
  const deeplinkStr = deeplink.toString();
415
- const opened = await openUrl(deeplinkStr);
416
- if (opened) {
417
- print("Waiting for authorization in browser...");
418
- } else {
419
- printError("Could not open browser. Open this URL manually:");
420
- printError(deeplinkStr);
421
- }
451
+ debug("auth", "opening browser for deeplink...");
452
+ openUrl(deeplinkStr).then((opened) => {
453
+ if (opened) {
454
+ debug("auth", "browser opened successfully");
455
+ print("Waiting for authorization in browser...");
456
+ } else {
457
+ printError("Could not open browser. Open this URL manually:");
458
+ printError(deeplinkStr);
459
+ }
460
+ });
422
461
  });
423
462
  });
424
463
  }
@@ -6612,6 +6651,18 @@ var types = {
6612
6651
  description: "",
6613
6652
  optional: false
6614
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
+ },
6615
6666
  {
6616
6667
  name: "[getHTMLForNodeMessageType]",
6617
6668
  type: "(nodeId: NodeId) => Promise<string | null>",
@@ -14927,9 +14978,16 @@ ${typeDef}`);
14927
14978
  return { lines, errors };
14928
14979
  }
14929
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
14930
14989
  var __filename$1 = fileURLToPath(import.meta.url);
14931
14990
  var __dirname$1 = path4.dirname(__filename$1);
14932
- var VERSION = "0.0.12" ;
14933
14991
  var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
14934
14992
  var client = createTRPCClient({
14935
14993
  links: [
@@ -14967,11 +15025,15 @@ function compareVersions(v1, v2) {
14967
15025
  __name(compareVersions, "compareVersions");
14968
15026
  async function ensureRelayServerRunning(options = {}) {
14969
15027
  const { logger, restartOnVersionMismatch = true } = options;
15028
+ debug("relay", `checking server version (client=${VERSION})...`);
14970
15029
  const serverVersion = await getRelayServerVersion();
15030
+ debug("relay", `server version=${serverVersion ?? "not running"}`);
14971
15031
  if (serverVersion === VERSION) {
15032
+ debug("relay", "version matches, no action needed");
14972
15033
  return;
14973
15034
  }
14974
15035
  if (serverVersion !== null && compareVersions(serverVersion, VERSION) > 0) {
15036
+ debug("relay", "server version is newer, keeping it");
14975
15037
  return;
14976
15038
  }
14977
15039
  if (serverVersion !== null) {
@@ -14979,11 +15041,13 @@ async function ensureRelayServerRunning(options = {}) {
14979
15041
  logger?.log(
14980
15042
  `Relay server version mismatch (server: ${serverVersion}, client: ${VERSION}), restarting...`
14981
15043
  );
15044
+ debug("relay", "shutting down old server...");
14982
15045
  try {
14983
15046
  await client.shutdown.mutate();
14984
15047
  await sleep(500);
14985
15048
  } catch {
14986
15049
  }
15050
+ debug("relay", "old server shut down");
14987
15051
  } else {
14988
15052
  return;
14989
15053
  }
@@ -14992,6 +15056,7 @@ async function ensureRelayServerRunning(options = {}) {
14992
15056
  }
14993
15057
  const isRunningFromSource = __filename$1.endsWith(".ts");
14994
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}`);
14995
15060
  const serverProcess = spawn(
14996
15061
  isRunningFromSource ? "tsx" : process.execPath,
14997
15062
  [scriptPath],
@@ -15002,9 +15067,13 @@ async function ensureRelayServerRunning(options = {}) {
15002
15067
  }
15003
15068
  );
15004
15069
  serverProcess.unref();
15005
- for (let i = 0; i < 10; i++) {
15070
+ for (let attempt = 0; attempt < 10; attempt++) {
15006
15071
  await sleep(500);
15007
15072
  const newVersion = await getRelayServerVersion();
15073
+ debug(
15074
+ "relay",
15075
+ `readiness poll ${attempt + 1}/10: version=${newVersion ?? "not ready"}`
15076
+ );
15008
15077
  if (newVersion) {
15009
15078
  logger?.log("Relay server started successfully");
15010
15079
  return;
@@ -15138,7 +15207,12 @@ __name(installSkills, "installSkills");
15138
15207
 
15139
15208
  // src/cli.ts
15140
15209
  var program = new Command();
15141
- 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
+ });
15142
15216
  async function readStdin() {
15143
15217
  const chunks = [];
15144
15218
  for await (const chunk of process.stdin) {
@@ -15163,11 +15237,13 @@ function printSetupSummary(results) {
15163
15237
  }
15164
15238
  __name(printSetupSummary, "printSetupSummary");
15165
15239
  async function getAgentSystemPrompt(sessionId) {
15240
+ debug("exec", "getAgentSystemPrompt: calling relay...");
15166
15241
  const result = await client.exec.mutate({
15167
15242
  sessionId,
15168
15243
  code: "return await framer.getAgentSystemPrompt();",
15169
15244
  cwd: process.cwd()
15170
15245
  });
15246
+ debug("exec", "getAgentSystemPrompt: relay responded");
15171
15247
  if (result.error) {
15172
15248
  throw new Error(result.error);
15173
15249
  }
@@ -15179,11 +15255,13 @@ async function getAgentSystemPrompt(sessionId) {
15179
15255
  }
15180
15256
  __name(getAgentSystemPrompt, "getAgentSystemPrompt");
15181
15257
  async function getAgentContext(sessionId) {
15258
+ debug("exec", "getAgentContext: calling relay...");
15182
15259
  const result = await client.exec.mutate({
15183
15260
  sessionId,
15184
15261
  code: "return await framer.getAgentContext({ pagePath: '/' });",
15185
15262
  cwd: process.cwd()
15186
15263
  });
15264
+ debug("exec", "getAgentContext: relay responded");
15187
15265
  if (result.error) {
15188
15266
  throw new Error(result.error);
15189
15267
  }
@@ -15196,16 +15274,19 @@ async function getAgentContext(sessionId) {
15196
15274
  __name(getAgentContext, "getAgentContext");
15197
15275
  async function refreshSkillsFromSession(sessionId, projectId) {
15198
15276
  try {
15277
+ debug("skills", "fetching agent system prompt + context...");
15199
15278
  const [canvasPrompt, agentContext] = await Promise.all([
15200
15279
  getAgentSystemPrompt(sessionId),
15201
15280
  getAgentContext(sessionId)
15202
15281
  ]);
15282
+ debug("skills", "installing skills...");
15203
15283
  installSkills({
15204
15284
  type: "project",
15205
15285
  canvasPrompt,
15206
15286
  projectId,
15207
15287
  agentContext
15208
15288
  });
15289
+ debug("skills", "skills installed");
15209
15290
  } catch (err) {
15210
15291
  printError(
15211
15292
  `Failed to refresh skills for session ${sessionId}: ${formatError(err)}`
@@ -15217,11 +15298,15 @@ __name(refreshSkillsFromSession, "refreshSkillsFromSession");
15217
15298
  async function resolveSessionCredentials(projectUrlOrId) {
15218
15299
  try {
15219
15300
  const projectId = extractProjectId(projectUrlOrId);
15301
+ debug("credentials", `projectId=${projectId}`);
15220
15302
  const cachedApiKey = getApiKey(projectId);
15221
15303
  if (cachedApiKey) {
15304
+ debug("credentials", "using cached API key");
15222
15305
  return { projectId, apiKey: cachedApiKey };
15223
15306
  }
15307
+ debug("credentials", "no cached key, starting browser auth flow...");
15224
15308
  const newApiKey = await acquireKeyFromBrowser(projectId);
15309
+ debug("credentials", "browser auth complete, saving project");
15225
15310
  saveProject({ projectId, apiKey: newApiKey });
15226
15311
  return { projectId, apiKey: newApiKey };
15227
15312
  } catch (err) {
@@ -15231,11 +15316,13 @@ async function resolveSessionCredentials(projectUrlOrId) {
15231
15316
  }
15232
15317
  __name(resolveSessionCredentials, "resolveSessionCredentials");
15233
15318
  async function getProjectName(sessionId) {
15319
+ debug("exec", "getProjectName: calling relay...");
15234
15320
  const result = await client.exec.mutate({
15235
15321
  sessionId,
15236
15322
  code: "return (await framer.getProjectInfo()).name;",
15237
15323
  cwd: process.cwd()
15238
15324
  });
15325
+ debug("exec", "getProjectName: relay responded");
15239
15326
  if (result.error) {
15240
15327
  throw new Error(result.error);
15241
15328
  }
@@ -15248,7 +15335,9 @@ async function getProjectName(sessionId) {
15248
15335
  __name(getProjectName, "getProjectName");
15249
15336
  async function ensureRelayForCli() {
15250
15337
  try {
15338
+ debug("relay", "ensuring relay server is running...");
15251
15339
  await ensureRelayServerRunning({ logger: { log: print } });
15340
+ debug("relay", "relay server ready");
15252
15341
  } catch (err) {
15253
15342
  printError(`Failed to check relay status: ${formatError(err)}`);
15254
15343
  process.exit(1);
@@ -15305,27 +15394,39 @@ program.option("-s, --session <id>", "Session ID (required for code execution)")
15305
15394
  });
15306
15395
  var session = program.command("session").description("Manage sessions");
15307
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...");
15308
15399
  const [credentials] = await Promise.all([
15309
15400
  resolveSessionCredentials(projectUrlOrId),
15310
15401
  ensureRelayForCli()
15311
15402
  ]);
15403
+ debug(
15404
+ "session.new",
15405
+ `credentials resolved for project=${credentials.projectId}`
15406
+ );
15312
15407
  try {
15408
+ debug("session.new", "calling createSession on relay...");
15313
15409
  const result = await client.createSession.mutate({
15314
15410
  projectId: credentials.projectId,
15315
15411
  apiKey: credentials.apiKey
15316
15412
  });
15317
15413
  const sessionId = result.id;
15414
+ debug("session.new", `session created id=${sessionId}`);
15415
+ debug("session.new", "fetching project name + refreshing skills...");
15318
15416
  const [projectName] = await Promise.all([
15319
15417
  getProjectName(sessionId),
15320
15418
  refreshSkillsFromSession(sessionId, credentials.projectId)
15321
15419
  ]);
15420
+ debug("session.new", `project name="${projectName}", skills refreshed`);
15322
15421
  saveProject({
15323
15422
  projectId: credentials.projectId,
15324
15423
  apiKey: credentials.apiKey,
15325
15424
  name: projectName,
15326
15425
  lastUsedAt: (/* @__PURE__ */ new Date()).toISOString()
15327
15426
  });
15427
+ debug("session.new", "project saved, printing session id");
15328
15428
  print(sessionId);
15429
+ debug("session.new", "done \u2014 process should exit now");
15329
15430
  } catch (err) {
15330
15431
  const message = formatError(err);
15331
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.12 */
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.12" ;
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]),
@@ -796,6 +805,7 @@ var appRouter = t.router({
796
805
  error: "Failed to get connection for session"
797
806
  };
798
807
  }
808
+ const start = performance.now();
799
809
  const result = await executeWithReconnect(
800
810
  session,
801
811
  framer,
@@ -804,8 +814,11 @@ var appRouter = t.router({
804
814
  () => sessionManager.reconnect(session),
805
815
  execId
806
816
  );
817
+ const elapsed = (performance.now() - start).toFixed(0);
807
818
  if (result.error) {
808
- log(`exec.error ${tag} error="${result.error}"`);
819
+ log(`exec.error ${tag} ${elapsed}ms error="${result.error}"`);
820
+ } else {
821
+ log(`exec.done ${tag} ${elapsed}ms`);
809
822
  }
810
823
  return result;
811
824
  } catch (_) {
@@ -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.12",
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": {