framer-dalton 0.0.4 → 0.0.5

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/README.md CHANGED
@@ -14,12 +14,22 @@ Then, simply open your preferred agent, ask to interact with your Framer project
14
14
 
15
15
  See [skills/framer/SKILL.md](skills/framer/SKILL.md) for full CLI usage and API documentation.
16
16
 
17
- ## Development
17
+ ## Local Development
18
+
19
+ Build and symlink to PATH + install skill locally:
20
+
21
+ ```bash
22
+ make install-dev
23
+ ```
24
+
25
+ If you want to hit a local FramerHeadlessAPI instance, when interacting with your agent, tell it something like this:
26
+
27
+ ```
28
+ Use the framer skill, but use `FRAMER_HEADLESS_SERVER_URL=ws://localhost:8080/channel/headless-plugin framer` (never with @latest) for all commands instead of `npx framer-dalton`
29
+ ```
30
+
31
+ Remove local install:
18
32
 
19
33
  ```bash
20
- make install-dev # Build and symlink to PATH + install skill locally
21
- make build # Build (installs deps automatically)
22
- make check # Type check + lint
23
- make format # Auto-format
24
- make clean # Clean build artifacts
34
+ make uninstall-dev
25
35
  ```
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import { spawn } from 'child_process';
7
7
  import { fileURLToPath } from 'url';
8
8
  import { createTRPCClient, httpLink } from '@trpc/client';
9
9
 
10
- /* @framer/ai CLI v0.0.4 */
10
+ /* @framer/ai CLI v0.0.5 */
11
11
  var __defProp = Object.defineProperty;
12
12
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
13
13
  function getConfigDir() {
@@ -1412,7 +1412,7 @@ var types = {
1412
1412
  name: "Control",
1413
1413
  description: "",
1414
1414
  kind: "alias",
1415
- alias: "EnumControl | BooleanControl | BorderControl | ShadowControl | DateControl | NumberControl | TransitionControl | StringControl | ColorControl | FormattedTextControl | LinkControl | LinkRelControl | FontControl | PageScopeControl | ScrollSectionControl | CustomCursorControl | CursorControl | FileControl | GapControl | PaddingControl | BorderRadiusControl | CollectionReferenceControl | MultiCollectionReferenceControl | VectorSetItemControl | TrackingIdControl | ImageControl | FusedNumberControl | ObjectControl | ArrayControl | EventHandlerControl | SlotControl",
1415
+ alias: "EnumControl | BooleanControl | BorderControl | ShadowControl | DateControl | NumberControl | TransitionControl | StringControl | ColorControl | FormattedTextControl | LinkControl | LinkRelControl | FontControl | PageScopeControl | ScrollSectionControl | CustomCursorControl | CursorControl | FileControl | GapControl | PaddingControl | BorderRadiusControl | CollectionReferenceControl | MultiCollectionReferenceControl | VectorSetItemControl | TrackingIdControl | ImageControl | FusedNumberControl | ObjectControl | ArrayControl | EventHandlerControl | SlotControl | LocationControl",
1416
1416
  references: [
1417
1417
  "EnumControl",
1418
1418
  "BooleanControl",
@@ -1444,7 +1444,8 @@ var types = {
1444
1444
  "ObjectControl",
1445
1445
  "ArrayControl",
1446
1446
  "EventHandlerControl",
1447
- "SlotControl"
1447
+ "SlotControl",
1448
+ "LocationControl"
1448
1449
  ]
1449
1450
  },
1450
1451
  controlattributes: {
@@ -1461,6 +1462,26 @@ var types = {
1461
1462
  references: ["WithKey"],
1462
1463
  extends: ["WithKey", "Partial", "Partial"]
1463
1464
  },
1465
+ coordinate: {
1466
+ name: "Coordinate",
1467
+ description: "",
1468
+ kind: "interface",
1469
+ references: [],
1470
+ members: [
1471
+ {
1472
+ name: "latitude",
1473
+ type: "number",
1474
+ description: "",
1475
+ optional: false
1476
+ },
1477
+ {
1478
+ name: "longitude",
1479
+ type: "number",
1480
+ description: "",
1481
+ optional: false
1482
+ }
1483
+ ]
1484
+ },
1464
1485
  createarrayfield: {
1465
1486
  name: "CreateArrayField",
1466
1487
  description: "",
@@ -1803,6 +1824,50 @@ var types = {
1803
1824
  references: ["WithLinkVariableType", "CreateVariableBase"],
1804
1825
  extends: ["WithLinkVariableType", "CreateVariableBase"]
1805
1826
  },
1827
+ createlocaleinput: {
1828
+ name: "CreateLocaleInput",
1829
+ description: "Input for creating a new locale.\n@alpha",
1830
+ kind: "interface",
1831
+ references: ["LocaleId"],
1832
+ members: [
1833
+ {
1834
+ name: "language",
1835
+ type: "string",
1836
+ description: 'The language code (e.g., "en", "fr", "zh-Hans"). Use `getLocaleLanguages()` to get the list of valid codes.',
1837
+ optional: false
1838
+ },
1839
+ {
1840
+ name: "region",
1841
+ type: "string",
1842
+ description: 'The optional region code (e.g., "US", "CA"). Use `getLocaleRegions(language)` to get the list of valid codes.',
1843
+ optional: true
1844
+ },
1845
+ {
1846
+ name: "fallbackLocaleId",
1847
+ type: "LocaleId",
1848
+ description: "ID of the fallback locale. Must reference an existing locale.",
1849
+ optional: true
1850
+ },
1851
+ {
1852
+ name: "slug",
1853
+ type: "string",
1854
+ description: 'URL slug for the locale (e.g., "en"). If not provided, one is derived from the code.',
1855
+ optional: true
1856
+ },
1857
+ {
1858
+ name: "name",
1859
+ type: "string",
1860
+ description: 'Display Name for the locale (e.g., "English (US)"). If not provided, one is derived from the code.',
1861
+ optional: true
1862
+ },
1863
+ {
1864
+ name: "draft",
1865
+ type: "boolean",
1866
+ description: "Flag to mark the locale as a draft. Defaults to `false`.",
1867
+ optional: true
1868
+ }
1869
+ ]
1870
+ },
1806
1871
  createmulticollectionreferencefield: {
1807
1872
  name: "CreateMultiCollectionReferenceField",
1808
1873
  description: "",
@@ -4319,6 +4384,53 @@ var types = {
4319
4384
  alias: '{\n action: "set";\n value: string;\n needsReview?: boolean;\n} | {\n action: "clear";\n} | {\n action: "ignore";\n needsReview?: boolean;\n}',
4320
4385
  references: []
4321
4386
  },
4387
+ location: {
4388
+ name: "Location",
4389
+ description: "",
4390
+ kind: "interface",
4391
+ references: ["Coordinate"],
4392
+ members: [
4393
+ {
4394
+ name: "coordinate",
4395
+ type: "Coordinate",
4396
+ description: "",
4397
+ optional: false
4398
+ },
4399
+ {
4400
+ name: "title",
4401
+ type: "string",
4402
+ description: 'Place name, e.g. "Eiffel Tower" or "Framer".',
4403
+ optional: true
4404
+ },
4405
+ {
4406
+ name: "address",
4407
+ type: "string",
4408
+ description: 'Formatted address string, e.g. "Rozengracht 207, 1016 LZ Amsterdam, Netherlands".',
4409
+ optional: true
4410
+ }
4411
+ ]
4412
+ },
4413
+ locationcontrol: {
4414
+ name: "LocationControl",
4415
+ description: "",
4416
+ kind: "interface",
4417
+ references: ["ControlBase", "Location"],
4418
+ members: [
4419
+ {
4420
+ name: "type",
4421
+ type: '"location"',
4422
+ description: "",
4423
+ optional: false
4424
+ },
4425
+ {
4426
+ name: "value",
4427
+ type: "Location | undefined",
4428
+ description: "",
4429
+ optional: true
4430
+ }
4431
+ ],
4432
+ extends: ["ControlBase"]
4433
+ },
4322
4434
  managedarrayfieldinput: {
4323
4435
  name: "ManagedArrayFieldInput",
4324
4436
  description: "",
@@ -5254,6 +5366,24 @@ var types = {
5254
5366
  description: "",
5255
5367
  optional: false
5256
5368
  },
5369
+ {
5370
+ name: "createLocale",
5371
+ type: "(input: CreateLocaleInput) => Promise<Locale>",
5372
+ description: "@alpha",
5373
+ optional: false
5374
+ },
5375
+ {
5376
+ name: "getLocaleLanguages",
5377
+ type: "() => Promise<{\n code: string;\n name: string;\n }[]>",
5378
+ description: "@alpha",
5379
+ optional: false
5380
+ },
5381
+ {
5382
+ name: "getLocaleRegions",
5383
+ type: "(language: string) => Promise<{\n code: string;\n name: string;\n isCommon: boolean;\n }[]>",
5384
+ description: "@alpha",
5385
+ optional: false
5386
+ },
5257
5387
  {
5258
5388
  name: "unstable_ensureMinimumDependencyVersion",
5259
5389
  type: 'FramerPluginAPI["unstable_ensureMinimumDependencyVersion"]',
@@ -6058,6 +6188,30 @@ var types = {
6058
6188
  description: "@alpha",
6059
6189
  optional: false
6060
6190
  },
6191
+ {
6192
+ name: "getAgentSystemPrompt",
6193
+ type: "() => Promise<string>",
6194
+ description: "@alpha",
6195
+ optional: false
6196
+ },
6197
+ {
6198
+ name: "getAgentContext",
6199
+ type: "(options?: {\n pagePath?: string;\n }) => Promise<string>",
6200
+ description: "@alpha",
6201
+ optional: false
6202
+ },
6203
+ {
6204
+ name: "readProjectForAgent",
6205
+ type: "(queries: Record<string, unknown>[], options?: {\n pagePath?: string;\n }) => Promise<{\n results: unknown[];\n }>",
6206
+ description: "@alpha",
6207
+ optional: false
6208
+ },
6209
+ {
6210
+ name: "applyAgentChanges",
6211
+ type: "(dsl: string, options?: {\n pagePath?: string;\n }) => Promise<void>",
6212
+ description: "@alpha",
6213
+ optional: false
6214
+ },
6061
6215
  {
6062
6216
  name: "[getAiServiceInfoMessageType]",
6063
6217
  type: "() => Promise<AiServiceInfo>",
@@ -11648,6 +11802,34 @@ var methodsByCategory = {
11648
11802
  }
11649
11803
  ],
11650
11804
  framer: [
11805
+ {
11806
+ name: "[$framerApiOnly.applyAgentChanges]",
11807
+ category: "framer",
11808
+ signature: "[$framerApiOnly.applyAgentChanges](dsl: string, options?: { pagePath?: string; }): Promise<void>",
11809
+ description: 'Applies commands to the canvas to create, update, remove, move, or duplicate nodes.\n\nThe command syntax is documented in the string returned by {@link getAgentSystemPrompt}.\nEach call is scoped to a single page.\n\n@param dsl - A string of commands separated by `;`. See {@link getAgentSystemPrompt} for syntax.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.',
11810
+ references: []
11811
+ },
11812
+ {
11813
+ name: "[$framerApiOnly.getAgentContext]",
11814
+ category: "framer",
11815
+ signature: "[$framerApiOnly.getAgentContext](options?: { pagePath?: string; }): Promise<string>",
11816
+ description: 'Returns the dynamic project context as a string.\n\nThe context includes project-specific data:\n- **Available fonts** \u2014 font families loaded in the project.\n- **Components** \u2014 component names and their controls.\n- **Design tokens** \u2014 color tokens defined in the project.\n- **Style presets** \u2014 text style presets defined in the project.\n- **Icon sets** \u2014 available icon sets and their definitions.\n\nThis data changes per project and page. Pair with the static prompt\nfrom {@link getAgentSystemPrompt} for complete agent context.\n\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns A string containing the project context.',
11817
+ references: []
11818
+ },
11819
+ {
11820
+ name: "[$framerApiOnly.getAgentSystemPrompt]",
11821
+ category: "framer",
11822
+ signature: "[$framerApiOnly.getAgentSystemPrompt](): Promise<string>",
11823
+ description: "Returns the static agent system prompt as a string.\n\nThe prompt includes:\n- **Command reference** \u2014 syntax for adding, updating, removing, moving, and duplicating nodes.\n- **Design rules** \u2014 spacing, layout, typography, and responsive design guidance.\n- **Examples** \u2014 common UI patterns expressed as commands.\n- **`readProjectForAgent` query reference** \u2014 available query types and their parameters.\n\nThis is the sole documentation for the command syntax used by {@link applyAgentChanges}\nand the query types used by {@link readProjectForAgent}.\n\nThe prompt is static and does not depend on any specific project.\nCall {@link getAgentContext} to get the project-specific context.\n\n@returns A string containing the agent system prompt.",
11824
+ references: []
11825
+ },
11826
+ {
11827
+ name: "[$framerApiOnly.readProjectForAgent]",
11828
+ category: "framer",
11829
+ signature: "[$framerApiOnly.readProjectForAgent](queries: Record<string, unknown>[], options?: { pagePath?: string; }): Promise<{ results: unknown[]; }>",
11830
+ description: 'Reads project state by executing an array of queries against the project.\n\nReturns one result per query. Available query types and their parameters\nare documented in the string returned by {@link getAgentSystemPrompt}.\n\n@param queries - Array of query objects. See {@link getAgentSystemPrompt} for available types.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns An object with a `results` array, one entry per query.',
11831
+ references: []
11832
+ },
11651
11833
  {
11652
11834
  name: "[$framerInternal.initialState]",
11653
11835
  category: "framer",
@@ -11728,6 +11910,13 @@ var methodsByCategory = {
11728
11910
  description: "Add a new text node to the canvas.",
11729
11911
  references: ["AddTextOptions"]
11730
11912
  },
11913
+ {
11914
+ name: "applyAgentChanges",
11915
+ category: "framer",
11916
+ signature: "applyAgentChanges(dsl: string, options?: { pagePath?: string; }): Promise<void>",
11917
+ description: 'Applies commands to the canvas to create, update, remove, move, or duplicate nodes.\n\nThe command syntax is documented in the string returned by {@link getAgentSystemPrompt}.\nEach call is scoped to a single page.\n\n@param dsl - A string of commands separated by `;`. See {@link getAgentSystemPrompt} for syntax.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.',
11918
+ references: []
11919
+ },
11731
11920
  {
11732
11921
  name: "cloneNode",
11733
11922
  category: "framer",
@@ -11777,6 +11966,13 @@ var methodsByCategory = {
11777
11966
  description: "Create a new node on the canvas.",
11778
11967
  references: ["EditableFrameNodeAttributes", "FrameNode"]
11779
11968
  },
11969
+ {
11970
+ name: "createLocale",
11971
+ category: "framer",
11972
+ signature: "createLocale(input: CreateLocaleInput): Promise<Locale>",
11973
+ description: "Create a new locale in the project.\n\n@alpha\n@param input - The locale configuration, use `getLocaleLanguages` and `getLocaleRegions` to get valid language and region codes.\n@returns The created locale.",
11974
+ references: ["CreateLocaleInput", "Locale"]
11975
+ },
11780
11976
  {
11781
11977
  name: "createManagedCollection",
11782
11978
  category: "framer",
@@ -11826,6 +12022,20 @@ var methodsByCategory = {
11826
12022
  description: "",
11827
12023
  references: []
11828
12024
  },
12025
+ {
12026
+ name: "getAgentContext",
12027
+ category: "framer",
12028
+ signature: "getAgentContext(options?: { pagePath?: string; }): Promise<string>",
12029
+ description: 'Returns the dynamic project context as a string.\n\nThe context includes project-specific data:\n- **Available fonts** \u2014 font families loaded in the project.\n- **Components** \u2014 component names and their controls.\n- **Design tokens** \u2014 color tokens defined in the project.\n- **Style presets** \u2014 text style presets defined in the project.\n- **Icon sets** \u2014 available icon sets and their definitions.\n\nThis data changes per project and page. Pair with the static prompt\nfrom {@link getAgentSystemPrompt} for complete agent context.\n\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns A string containing the project context.',
12030
+ references: []
12031
+ },
12032
+ {
12033
+ name: "getAgentSystemPrompt",
12034
+ category: "framer",
12035
+ signature: "getAgentSystemPrompt(): Promise<string>",
12036
+ description: "Returns the static agent system prompt as a string.\n\nThe prompt includes:\n- **Command reference** \u2014 syntax for adding, updating, removing, moving, and duplicating nodes.\n- **Design rules** \u2014 spacing, layout, typography, and responsive design guidance.\n- **Examples** \u2014 common UI patterns expressed as commands.\n- **`readProjectForAgent` query reference** \u2014 available query types and their parameters.\n\nThis is the sole documentation for the command syntax used by {@link applyAgentChanges}\nand the query types used by {@link readProjectForAgent}.\n\nThe prompt is static and does not depend on any specific project.\nCall {@link getAgentContext} to get the project-specific context.\n\n@returns A string containing the agent system prompt.",
12037
+ references: []
12038
+ },
11829
12039
  {
11830
12040
  name: "getCanvasRoot",
11831
12041
  category: "framer",
@@ -11945,6 +12155,20 @@ var methodsByCategory = {
11945
12155
  description: "Get the image of the current selection or `null` if there is no image.\n\nIn `editImage` mode, this returns the image the user already has set,\nwhich your plugin can then modify.",
11946
12156
  references: ["ImageAsset"]
11947
12157
  },
12158
+ {
12159
+ name: "getLocaleLanguages",
12160
+ category: "framer",
12161
+ signature: "getLocaleLanguages(): Promise<{ code: string; name: string; }[]>",
12162
+ description: "Get all available locale languages.\n\n@alpha\n@returns A list of language codes and their display names, sorted by name.",
12163
+ references: []
12164
+ },
12165
+ {
12166
+ name: "getLocaleRegions",
12167
+ category: "framer",
12168
+ signature: "getLocaleRegions(languageCode: string): Promise<{ code: string; name: string; isCommon: boolean; }[]>",
12169
+ description: "Get all available locale regions for a given language.\n\n@alpha\n@param languageCode - The language code to get regions for. Use `getLocaleLanguages` to get valid language codes.\n@returns A list of region codes, their display names, and whether they are commonly paired with the given language.",
12170
+ references: []
12171
+ },
11948
12172
  {
11949
12173
  name: "getLocales",
11950
12174
  category: "framer",
@@ -12071,6 +12295,13 @@ var methodsByCategory = {
12071
12295
  description: "",
12072
12296
  references: ["PublishResult"]
12073
12297
  },
12298
+ {
12299
+ name: "readProjectForAgent",
12300
+ category: "framer",
12301
+ signature: "readProjectForAgent(queries: Record<string, unknown>[], options?: { pagePath?: string; }): Promise<{ results: unknown[]; }>",
12302
+ description: 'Reads project state by executing an array of queries against the project.\n\nReturns one result per query. Available query types and their parameters\nare documented in the string returned by {@link getAgentSystemPrompt}.\n\n@param queries - Array of query objects. See {@link getAgentSystemPrompt} for available types.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns An object with a `results` array, one entry per query.',
12303
+ references: []
12304
+ },
12074
12305
  {
12075
12306
  name: "rejectAllPending",
12076
12307
  category: "framer",
@@ -14281,7 +14512,7 @@ ${typeDef}`);
14281
14512
  __name(renderDocs, "renderDocs");
14282
14513
  var __filename$1 = fileURLToPath(import.meta.url);
14283
14514
  var __dirname$1 = path.dirname(__filename$1);
14284
- var VERSION = "0.0.4" ;
14515
+ var VERSION = "0.0.5" ;
14285
14516
  var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
14286
14517
  var client = createTRPCClient({
14287
14518
  links: [
@@ -14413,8 +14644,8 @@ async function readStdin() {
14413
14644
  return Buffer.concat(chunks).toString("utf-8");
14414
14645
  }
14415
14646
  __name(readStdin, "readStdin");
14416
- program.option("-s, --session <id>", "Session ID (required for code execution)").option("-e, --eval <code>", "Code to execute (or pipe via stdin)").option("--timeout <ms>", "Execution timeout in milliseconds", "30000").action(async (options) => {
14417
- const { session: sessionId, eval: evalCode, timeout } = options;
14647
+ program.option("-s, --session <id>", "Session ID (required for code execution)").option("-e, --eval <code>", "Code to execute (or pipe via stdin)").action(async (options) => {
14648
+ const { session: sessionId, eval: evalCode } = options;
14418
14649
  let code = evalCode;
14419
14650
  if (!code && !process.stdin.isTTY) {
14420
14651
  code = await readStdin();
@@ -14435,7 +14666,6 @@ program.option("-s, --session <id>", "Session ID (required for code execution)")
14435
14666
  const result = await client.exec.mutate({
14436
14667
  sessionId: String(sessionId),
14437
14668
  code,
14438
- timeout: parseInt(timeout, 10),
14439
14669
  cwd: process.cwd()
14440
14670
  });
14441
14671
  for (const line of result.output) {
@@ -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.4 */
16
+ /* @framer/ai relay server v0.0.5 */
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.4" ;
53
+ var VERSION = "0.0.5" ;
54
54
  var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
55
55
  createTRPCClient({
56
56
  links: [
@@ -266,8 +266,8 @@ var ScopedFS = class {
266
266
  constants = fs2.constants;
267
267
  };
268
268
 
269
- // src/executor.ts
270
- var DEFAULT_TIMEOUT = 3e4;
269
+ // src/execute.ts
270
+ var EXECUTION_TIMEOUT = 10 * 60 * 1e3;
271
271
  var baseRequire = createRequire(import.meta.url);
272
272
  var ALLOWED_MODULES = /* @__PURE__ */ new Set([
273
273
  "path",
@@ -329,7 +329,7 @@ async function sandboxedImport(scopedFs, specifier) {
329
329
  }
330
330
  __name(sandboxedImport, "sandboxedImport");
331
331
  async function execute(session, framer, code, options = {}) {
332
- const { timeout = DEFAULT_TIMEOUT, cwd } = options;
332
+ const { cwd } = options;
333
333
  const output = [];
334
334
  const customConsole = {
335
335
  log: /* @__PURE__ */ __name((...args) => {
@@ -376,22 +376,24 @@ async function execute(session, framer, code, options = {}) {
376
376
  };
377
377
  const vmContext = vm.createContext(vmContextObj);
378
378
  const wrappedCode = `(async () => { ${code} })()`;
379
+ let timeoutId;
379
380
  try {
380
381
  const script = new vm.Script(wrappedCode, {
381
382
  filename: "framer-exec.js"
382
383
  });
383
384
  const resultPromise = script.runInContext(vmContext, {
384
385
  timeout: 5e3
385
- // Short timeout for sync part
386
386
  });
387
387
  const result = await Promise.race([
388
388
  resultPromise,
389
- new Promise(
390
- (_, reject) => setTimeout(
391
- () => reject(new Error(`Execution timed out after ${timeout}ms`)),
392
- timeout
393
- )
394
- )
389
+ new Promise((_, reject) => {
390
+ timeoutId = setTimeout(
391
+ () => reject(
392
+ new Error(`Execution timed out after ${EXECUTION_TIMEOUT}ms`)
393
+ ),
394
+ EXECUTION_TIMEOUT
395
+ );
396
+ })
395
397
  ]);
396
398
  if (result !== void 0) {
397
399
  output.push(formatValue(result));
@@ -403,9 +405,42 @@ async function execute(session, framer, code, options = {}) {
403
405
  output,
404
406
  error: errorMessage
405
407
  };
408
+ } finally {
409
+ if (timeoutId) clearTimeout(timeoutId);
406
410
  }
407
411
  }
408
412
  __name(execute, "execute");
413
+ async function executeWithReconnect(session, framer, code, options, reconnect) {
414
+ const result = await tryExecute(session, framer, code, options);
415
+ if (!result.error || !isConnectionError(result.error)) {
416
+ return result;
417
+ }
418
+ log(
419
+ `reconnect session=${session.id} project=${session.projectId} reason="${result.error}"`
420
+ );
421
+ const newFramer = await reconnect();
422
+ if (!newFramer) {
423
+ log(
424
+ `reconnect.failed session=${session.id} error="no connection returned"`
425
+ );
426
+ return {
427
+ output: [],
428
+ error: "Connection lost and failed to reconnect"
429
+ };
430
+ }
431
+ log(`reconnect.success session=${session.id}`);
432
+ return tryExecute(session, newFramer, code, options);
433
+ }
434
+ __name(executeWithReconnect, "executeWithReconnect");
435
+ async function tryExecute(session, framer, code, options) {
436
+ try {
437
+ return await execute(session, framer, code, options);
438
+ } catch (err) {
439
+ const error = err instanceof Error ? err.message : String(err);
440
+ return { output: [], error };
441
+ }
442
+ }
443
+ __name(tryExecute, "tryExecute");
409
444
  function formatValue(value) {
410
445
  if (value === null) return "null";
411
446
  if (value === void 0) return "undefined";
@@ -431,6 +466,7 @@ var ConnectionPool = class {
431
466
  __name(this, "ConnectionPool");
432
467
  }
433
468
  pool = /* @__PURE__ */ new Map();
469
+ reconnectPromises = /* @__PURE__ */ new Map();
434
470
  /**
435
471
  * Acquire a connection for a session.
436
472
  * If a connection already exists for the project, the session is added to it.
@@ -458,15 +494,26 @@ var ConnectionPool = class {
458
494
  }
459
495
  /**
460
496
  * Reconnect a project's connection (call after catching a connection error).
461
- * Uses the same connection object but swaps the underlying WebSocket.
497
+ * Concurrent callers for the same project share a single reconnect attempt.
462
498
  */
463
499
  async reconnect(projectId) {
500
+ const existingPromise = this.reconnectPromises.get(projectId);
501
+ if (existingPromise) return existingPromise;
502
+ const promise = this.doReconnect(projectId).finally(() => {
503
+ this.reconnectPromises.delete(projectId);
504
+ });
505
+ this.reconnectPromises.set(projectId, promise);
506
+ return promise;
507
+ }
508
+ async doReconnect(projectId) {
464
509
  const entry = this.pool.get(projectId);
465
- if (!entry) {
510
+ if (!entry) return null;
511
+ try {
512
+ await entry.connection.reconnect();
513
+ return entry.connection;
514
+ } catch {
466
515
  return null;
467
516
  }
468
- await entry.connection.reconnect();
469
- return entry.connection;
470
517
  }
471
518
  /**
472
519
  * Release a session from a connection.
@@ -570,11 +617,10 @@ var appRouter = t.router({
570
617
  z.object({
571
618
  sessionId: z.string(),
572
619
  code: z.string(),
573
- timeout: z.number().default(3e4),
574
620
  cwd: z.string().optional()
575
621
  })
576
622
  ).mutation(async ({ input }) => {
577
- const { sessionId, code, timeout, cwd } = input;
623
+ const { sessionId, code, cwd } = input;
578
624
  const session = sessionManager.get(sessionId);
579
625
  if (!session) {
580
626
  throw new TRPCError({
@@ -585,43 +631,20 @@ var appRouter = t.router({
585
631
  log(
586
632
  `exec session=${sessionId} code=${JSON.stringify(code).slice(0, 100)}`
587
633
  );
588
- let framer = sessionManager.getFramer(session);
634
+ const framer = sessionManager.getFramer(session);
589
635
  if (!framer) {
590
636
  return {
591
637
  output: [],
592
638
  error: "Failed to get connection for session"
593
639
  };
594
640
  }
595
- let result;
596
- try {
597
- result = await execute(session, framer, code, { timeout, cwd });
598
- } catch (execErr) {
599
- const errMsg = execErr instanceof Error ? execErr.message : String(execErr);
600
- result = { output: [], error: errMsg };
601
- }
602
- if (result.error && isConnectionError(result.error)) {
603
- log(
604
- `reconnect session=${sessionId} project=${session.projectId} reason="${result.error}"`
605
- );
606
- framer = await sessionManager.reconnect(session);
607
- if (framer) {
608
- try {
609
- result = await execute(session, framer, code, {
610
- timeout,
611
- cwd
612
- });
613
- log(`reconnect.success session=${sessionId}`);
614
- } catch (retryErr) {
615
- const errMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
616
- log(`reconnect.failed session=${sessionId} error="${errMsg}"`);
617
- result = { output: [], error: errMsg };
618
- }
619
- } else {
620
- log(
621
- `reconnect.failed session=${sessionId} error="no connection returned"`
622
- );
623
- }
624
- }
641
+ const result = await executeWithReconnect(
642
+ session,
643
+ framer,
644
+ code,
645
+ { cwd },
646
+ () => sessionManager.reconnect(session)
647
+ );
625
648
  if (result.error) {
626
649
  log(`exec.error session=${sessionId} error="${result.error}"`);
627
650
  }
@@ -73,8 +73,6 @@ npx framer-dalton session list
73
73
  npx framer-dalton -s <sessionId> -e "<code>"
74
74
  ```
75
75
 
76
- Default timeout is 30 seconds. Increase with `--timeout <ms>`.
77
-
78
76
  **Escaping:** For code with HTML, quotes, or special characters, use a heredoc (see below). For simple strings, use `$'...'` syntax.
79
77
 
80
78
  **Examples:**
@@ -606,7 +604,10 @@ Server API exclusive methods for capturing visual output from nodes.
606
604
  const result = await framer.screenshot(node.id);
607
605
  console.log(result.mimeType); // "image/png"
608
606
  const os = require("os");
609
- await require("fs").promises.writeFile(os.tmpdir() + "/screenshot.png", result.data);
607
+ await require("fs").promises.writeFile(
608
+ os.tmpdir() + "/screenshot.png",
609
+ result.data,
610
+ );
610
611
 
611
612
  // With options
612
613
  const jpg = await framer.screenshot(node.id, {
@@ -667,12 +668,75 @@ const contributors = await framer.getChangeContributors();
667
668
 
668
669
  ---
669
670
 
671
+ ## Canvas Editing
672
+
673
+ **Use this for all design tasks** — creating pages, building sections, recreating designs from screenshots, or any task that involves layout or visual styling on the canvas.
674
+
675
+ **Hard rule:** For design/layout work, do **not** use low-level node APIs (`createNode`, `setAttributes`, `setRect`, etc.). Always use the canvas editing flow (`getAgentSystemPrompt` + `getAgentContext` + `readProjectForAgent` + `applyAgentChanges`). If you start writing low-level node code for design work, stop and switch to canvas editing methods.
676
+
677
+ **Access note:** Canvas editing `agent` methods are employee-only. If these methods fail or are unavailable, the account likely does not have employee access.
678
+
679
+ ### Methods
680
+
681
+ **`framer.getAgentSystemPrompt()`** — Returns the command reference, design rules, layout patterns, and examples. This is the sole documentation for the command syntax and query types. It is static (same for every project) — fetch once per session and persist to a file.
682
+
683
+ **`framer.getAgentContext({ pagePath })`** — Returns the project's available fonts, components, color tokens, text style presets, and icon sets for the target page scope. Fetch before generating commands and use the same `pagePath` across context/read/apply calls.
684
+
685
+ **`framer.readProjectForAgent(queries, { pagePath })`** — Reads project state. Query types are documented in the system prompt. Use liberally — query the page tree, icon sets, components, and examples before generating commands. Multiple queries can be batched in a single call. Never guess at names for examples, icon sets, or fonts — always look them up first.
686
+
687
+ **`framer.applyAgentChanges(dsl, { pagePath })`** — Applies commands to the canvas. Node IDs you assign are temporary — re-read the page tree via `readProjectForAgent` if you need actual IDs after applying.
688
+
689
+ ### Workflow
690
+
691
+ ```js
692
+ // 1. Save the system prompt to a file (once per session).
693
+ const fs = require("fs");
694
+ const prompt = await framer.getAgentSystemPrompt();
695
+ fs.writeFileSync("/tmp/framer-canvas-reference.txt", prompt);
696
+ console.log("Saved to /tmp/framer-canvas-reference.txt —", prompt.length, "chars");
697
+
698
+ // 2. Get project context
699
+ const projectCtx = await framer.getAgentContext({ pagePath: "/" });
700
+ console.log(projectCtx);
701
+
702
+ // 3. Read project state (query types are in the prompt)
703
+ const { results } = await framer.readProjectForAgent(
704
+ [{ type: "page", path: "/" }],
705
+ { pagePath: "/" }
706
+ );
707
+
708
+ // 4. Apply changes
709
+ await framer.applyAgentChanges(dsl, { pagePath: "/" });
710
+ ```
711
+
712
+ **You must read the entire system prompt file before writing any commands.** It contains command syntax, design rules, layout patterns, examples, and query documentation. Skipping sections will cause incorrect output.
713
+
714
+ After applying changes, take screenshots to inspect the result. Check padding, spacing, typography, icon choice, and alignment. Iterate with additional `readProjectForAgent` + `applyAgentChanges` passes until visually accurate.
715
+
716
+ When setting text content, use raw Unicode characters directly (e.g. `→` not `\u2192`).
717
+
718
+ Always fetch fresh context per session — the format may change between versions.
719
+
720
+ ### What canvas editing can do
721
+
722
+ Beyond basic page layout, the command language supports:
723
+
724
+ - **Colour tokens** — Create color tokens (`ColorStyleTokenNode`) with light/dark mode variants. Reference them across the project for consistent theming.
725
+ - **Text style presets** — Create reusable typography presets (`TextStylePresetNode`) and apply them to text nodes. Build full typographic systems (headings, body, captions, etc.).
726
+ - **Smart components** — Author reusable components (`ComponentNode`) with multiple visual variants, scoped variables, and property controls. Instantiate them anywhere with `ComponentInstanceNode`.
727
+ - **Interactive effects** — Add hover effects (scale, opacity, color), tap effects, scroll-triggered appear animations, transitions between component variants, and event handlers.
728
+ - **Icon sets** — Insert icons from named vector sets. Query available sets and icons via `readProjectForAgent`.
729
+ - **Responsive breakpoints** — Create page breakpoints (Desktop, Tablet, Mobile) using the replica/variant system, with per-breakpoint overrides.
730
+ - **Complex layouts** — Stack and grid layouts with auto-fill, fractional units, space-between distribution, and nested grids for asymmetric layouts.
731
+
732
+ ---
733
+
670
734
  ## Capabilities
671
735
 
672
736
  What you can do with the Framer CLI:
673
737
 
738
+ - **Canvas Editing** For design tasks — creating or editing pages, sections, layouts, recreating designs from screenshots, etc.
674
739
  - **CMS**: Create, read, update, delete collections and items. Sync external databases.
675
- - **Canvas**: Create and modify frames, text, images, SVGs. Build layouts programmatically.
676
740
  - **Styles**: Manage color and text styles. Sync design systems.
677
741
  - **Code Components**: Create, edit, type-check, and add custom React components to the canvas.
678
742
  - **Assets**: Upload and manage images and files.
@@ -680,12 +744,12 @@ What you can do with the Framer CLI:
680
744
  - **Data**: Store metadata on nodes and projects for plugin state.
681
745
  - **Screenshots**: Capture node screenshots as PNG/JPEG. Export nodes as SVG.
682
746
  - **Publishing**: Publish projects, manage deployments, track changes.
747
+ - **Low-level Node APIs**: Create and modify individual nodes. Only use these for targeted, surgical edits to specific nodes — not for building pages or layouts.
683
748
 
684
749
  ---
685
750
 
686
751
  ## Known Limitations
687
752
 
688
753
  - **Pages**: No list, delete, update, move, or settings APIs (create only)
689
- - **Canvas/design work**: Not viable via API. Can create/read nodes but limited modification support (transforms, constraints, layout)
690
754
  - **Code overrides**: Cannot assign overrides to nodes
691
-
755
+ - **Analytics**: No APIs exist for accessing analytics data
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "framer-dalton",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
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": "^0.1.2-alpha.0",
28
+ "framer-api": "https://pkg.pr.new/framer/FramerStudio/framer-api@5804b52.tgz",
29
29
  "zod": "^4.3.6"
30
30
  },
31
31
  "devDependencies": {