framer-dalton 0.0.22 → 0.0.24

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
@@ -7,10 +7,11 @@ import http from 'http';
7
7
  import { spawn, execFile } from 'child_process';
8
8
  import { z } from 'zod';
9
9
  import os from 'os';
10
+ import { ErrorCode, FramerAPIError } from 'framer-api';
10
11
  import { fileURLToPath } from 'url';
11
12
  import { createTRPCClient, httpLink } from '@trpc/client';
12
13
 
13
- /* @framer/ai CLI v0.0.22 */
14
+ /* @framer/ai CLI v0.0.24 */
14
15
  var __defProp = Object.defineProperty;
15
16
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
16
17
 
@@ -328,7 +329,7 @@ __name(markTelemetryNoticeShown, "markTelemetryNoticeShown");
328
329
  // src/version.ts
329
330
  var VERSION = (
330
331
  // typeof is used to ensure this can be used just via tsx or node etc. without build
331
- "0.0.22"
332
+ "0.0.24"
332
333
  );
333
334
  var trackingEndpoint = "https://events.framer.com/track";
334
335
  var inProgressTrackings = /* @__PURE__ */ new Set();
@@ -418,8 +419,27 @@ function trackError(payload) {
418
419
  });
419
420
  }
420
421
  __name(trackError, "trackError");
421
-
422
- // src/utils.ts
422
+ function rehydrateFramerAPIError(error) {
423
+ if (!(error instanceof Error)) return null;
424
+ const framerApi = error.data?.framerApi;
425
+ if (!framerApi || typeof framerApi.code !== "string") return null;
426
+ const code = ErrorCode[framerApi.code] ?? ErrorCode.INTERNAL;
427
+ const options = framerApi.ref ? { ref: framerApi.ref } : void 0;
428
+ return new FramerAPIError(error.message, code, options);
429
+ }
430
+ __name(rehydrateFramerAPIError, "rehydrateFramerAPIError");
431
+ function errorWithRef(message, ref) {
432
+ const err = new Error(message);
433
+ if (ref) err.ref = ref;
434
+ return err;
435
+ }
436
+ __name(errorWithRef, "errorWithRef");
437
+ function getErrorRef(error) {
438
+ if (!(error instanceof Error)) return void 0;
439
+ const ref = error.ref;
440
+ return typeof ref === "string" && ref.length > 0 ? ref : void 0;
441
+ }
442
+ __name(getErrorRef, "getErrorRef");
423
443
  function formatError(error) {
424
444
  if (error instanceof Error) {
425
445
  return error.message;
@@ -427,6 +447,14 @@ function formatError(error) {
427
447
  return String(error);
428
448
  }
429
449
  __name(formatError, "formatError");
450
+ function formatErrorForUser(error) {
451
+ if (!(error instanceof Error)) return String(error);
452
+ const rehydrated = rehydrateFramerAPIError(error);
453
+ if (rehydrated) return rehydrated.toString();
454
+ const ref = getErrorRef(error);
455
+ return ref ? `${error.message} [ref: ${ref}]` : error.message;
456
+ }
457
+ __name(formatErrorForUser, "formatErrorForUser");
430
458
  function printJson(value) {
431
459
  console.log(JSON.stringify(value, null, 2));
432
460
  }
@@ -518,7 +546,7 @@ function successHtml(theme, message) {
518
546
  return htmlPage({
519
547
  title: "Framer \u2014 Agent Approved",
520
548
  heading: "Agent Approved",
521
- message: message ?? "Your local agent now has edit access to the project. You can close this tab and continue with the local agent.",
549
+ message: message ?? "Your external agent now has edit access to the project. You can close this tab and continue with the external agent.",
522
550
  theme
523
551
  });
524
552
  }
@@ -767,7 +795,7 @@ async function acquireAuthWithNewProject() {
767
795
  expectProjectId: true,
768
796
  successMessage: "Project created and agent authorized",
769
797
  authType: "new_project",
770
- browserSuccessMessage: "A new project has been created and your local agent is authorized to edit it. You can close this tab."
798
+ browserSuccessMessage: "A new project has been created and your external agent is authorized to edit it. You can close this tab."
771
799
  });
772
800
  }
773
801
  __name(acquireAuthWithNewProject, "acquireAuthWithNewProject");
@@ -784,7 +812,7 @@ async function acquireAuthWithRemixProject(sourceProjectUrlOrId) {
784
812
  expectProjectId: true,
785
813
  successMessage: "Project remixed and agent authorized",
786
814
  authType: "remix",
787
- browserSuccessMessage: "The project has been remixed and your local agent is authorized to edit it. You can close this tab.",
815
+ browserSuccessMessage: "The project has been remixed and your external agent is authorized to edit it. You can close this tab.",
788
816
  projectOrigin
789
817
  });
790
818
  }
@@ -797,6 +825,48 @@ function isAuthError(errorMessage) {
797
825
  }
798
826
  __name(isAuthError, "isAuthError");
799
827
 
828
+ // src/closest-match.ts
829
+ function levenshteinDistance(source, target) {
830
+ const sourceLength = source.length;
831
+ const targetLength = target.length;
832
+ const matrix = Array.from(
833
+ { length: sourceLength + 1 },
834
+ () => Array.from({ length: targetLength + 1 }).fill(0)
835
+ );
836
+ for (let row = 0; row <= sourceLength; row++) matrix[row][0] = row;
837
+ for (let col = 0; col <= targetLength; col++) matrix[0][col] = col;
838
+ for (let row = 1; row <= sourceLength; row++) {
839
+ for (let col = 1; col <= targetLength; col++) {
840
+ const cost = source[row - 1] === target[col - 1] ? 0 : 1;
841
+ matrix[row][col] = Math.min(
842
+ matrix[row - 1][col] + 1,
843
+ matrix[row][col - 1] + 1,
844
+ matrix[row - 1][col - 1] + cost
845
+ );
846
+ }
847
+ }
848
+ return matrix[sourceLength][targetLength];
849
+ }
850
+ __name(levenshteinDistance, "levenshteinDistance");
851
+ function findClosestMatch(query, candidates) {
852
+ const queryLower = query.toLowerCase();
853
+ let bestMatch;
854
+ let bestDistance = Number.POSITIVE_INFINITY;
855
+ for (const candidate of candidates) {
856
+ const distance = levenshteinDistance(queryLower, candidate.toLowerCase());
857
+ if (distance < bestDistance) {
858
+ bestDistance = distance;
859
+ bestMatch = candidate;
860
+ }
861
+ }
862
+ const maxDistance = Math.max(2, Math.floor(query.length / 3));
863
+ if (bestDistance <= maxDistance) {
864
+ return bestMatch;
865
+ }
866
+ return void 0;
867
+ }
868
+ __name(findClosestMatch, "findClosestMatch");
869
+
800
870
  // src/types-data.ts
801
871
  var types = {
802
872
  addcomponentinstanceoptions: {
@@ -975,7 +1045,7 @@ var types = {
975
1045
  {
976
1046
  name: "imageUrls",
977
1047
  type: "readonly string[]",
978
- description: "Attach images to this agent turn by URL.\n\nURLs can be external or existing Framer asset URLs. They will be ingested into the\nproject's assets if needed, then sent to the model as image parts.\n\nTip: if you have a local file (or bytes), upload it first (e.g. `framer.uploadImage(...)`) and\npass the returned asset URL here.",
1048
+ description: "Attach images to this agent turn by URL.\n\nURLs can be external or existing Framer asset URLs. They will be ingested into the\nproject's assets if needed, then made available to the agent as trusted attachment URLs.\n\nTip: if you have a local file (or bytes), upload it first (e.g. `framer.uploadImage(...)`) and\npass the returned asset URL here.",
979
1049
  optional: true
980
1050
  }
981
1051
  ]
@@ -6293,6 +6363,12 @@ var types = {
6293
6363
  description: "@alpha",
6294
6364
  optional: false
6295
6365
  },
6366
+ {
6367
+ name: "unstable_getDependencyVersion",
6368
+ type: 'FramerPluginAPIAlpha["unstable_getDependencyVersion"]',
6369
+ description: "@alpha",
6370
+ optional: false
6371
+ },
6296
6372
  {
6297
6373
  name: "unstable_ensureMinimumDependencyVersion",
6298
6374
  type: 'FramerPluginAPI["unstable_ensureMinimumDependencyVersion"]',
@@ -7205,6 +7281,24 @@ var types = {
7205
7281
  description: "@alpha",
7206
7282
  optional: false
7207
7283
  },
7284
+ {
7285
+ name: "serializeForAgent",
7286
+ type: "(input: {\n id: string;\n depth?: number;\n attributeFilter?: readonly string[];\n ancestorPath?: boolean;\n }, options?: {\n pagePath?: string;\n }) => Promise<unknown>",
7287
+ description: "@alpha",
7288
+ optional: false
7289
+ },
7290
+ {
7291
+ name: "serializeNodesForAgent",
7292
+ type: "(input: {\n ids: readonly string[];\n depth?: number;\n attributeFilter?: readonly string[];\n ancestorPath?: boolean;\n }, options?: {\n pagePath?: string;\n }) => Promise<unknown>",
7293
+ description: "@alpha",
7294
+ optional: false
7295
+ },
7296
+ {
7297
+ name: "paginateForAgent",
7298
+ type: "(input: {\n items: readonly unknown[];\n keyName?: never;\n cursor?: never;\n } | {\n keyName: string;\n cursor: number;\n items?: never;\n }, options?: {\n pagePath?: string;\n }) => Promise<unknown>",
7299
+ description: "@alpha",
7300
+ optional: false
7301
+ },
7208
7302
  {
7209
7303
  name: "startAgentConversation",
7210
7304
  type: "(prompt: string, options?: StartAgentConversationOptions) => Promise<StartAgentConversationResult>",
@@ -7680,6 +7774,12 @@ var types = {
7680
7774
  type: "number",
7681
7775
  description: "",
7682
7776
  optional: true
7777
+ },
7778
+ {
7779
+ name: "captureTrainingData",
7780
+ type: "boolean",
7781
+ description: "When true, capture per-step (system, tools, messages) \u2192 response training rows for SFT.",
7782
+ optional: true
7683
7783
  }
7684
7784
  ]
7685
7785
  },
@@ -7700,6 +7800,18 @@ var types = {
7700
7800
  type: "string",
7701
7801
  description: "",
7702
7802
  optional: false
7803
+ },
7804
+ {
7805
+ name: "trainingDataFilename",
7806
+ type: "string",
7807
+ description: "Training-data filename, present when `captureTrainingData` was set.",
7808
+ optional: true
7809
+ },
7810
+ {
7811
+ name: "trainingDataJsonl",
7812
+ type: "string",
7813
+ description: "JSONL string with one training row per inner-agent step, present when `captureTrainingData` was set.",
7814
+ optional: true
7703
7815
  }
7704
7816
  ]
7705
7817
  },
@@ -13092,6 +13204,13 @@ var methodsByCategory = {
13092
13204
  description: 'Converts an external component into a local project component.\n\n@param input - `{ id, replaceAll? }`: the id of the external instance, and whether to replace all instances.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns `success` (with the local component name), `needs_confirmation` (retry with `replaceAll`), or `blocked` with a reason.',
13093
13205
  references: []
13094
13206
  },
13207
+ {
13208
+ name: "[$framerApiOnly.paginateForAgent]",
13209
+ category: "framer",
13210
+ signature: "[$framerApiOnly.paginateForAgent](input: { items: readonly unknown[]; keyName?: never; cursor?: never; } | { keyName: string; cursor: number; items?: never; }, options?: { pagePath?: string; }): Promise<unknown>",
13211
+ description: 'Paginate a large array of values across multiple calls. The cursor is\nopaque and only valid within the same headless session and page.\n\n- First page: pass `{ items }`.\n- Continuation: pass `{ keyName, cursor }` using values returned by a previous call.\n\n@param input - `{ items }` for a fresh array, or `{ keyName, cursor }` for continuation.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns The current page, including `keyName`, `cursor`, `results`, `totalResults`, and (if more pages remain) `nextCursor`.',
13212
+ references: []
13213
+ },
13095
13214
  {
13096
13215
  name: "[$framerApiOnly.publishForAgent]",
13097
13216
  category: "framer",
@@ -13120,6 +13239,20 @@ var methodsByCategory = {
13120
13239
  description: "Reviews changes made by prior {@link applyAgentChanges} calls in this session.\n\nConsumes accumulated diagnostics from the session's agent context.\n\n@param options.pagePath - Target page path (e.g. `\"/about\"`). Defaults to the active page.\n@returns The session's accumulated changes, errors, warnings, and deferred trait reports.",
13121
13240
  references: []
13122
13241
  },
13242
+ {
13243
+ name: "[$framerApiOnly.serializeForAgent]",
13244
+ category: "framer",
13245
+ signature: "[$framerApiOnly.serializeForAgent](input: { id: string; depth?: number; attributeFilter?: readonly string[]; ancestorPath?: boolean; }, options?: { pagePath?: string; }): Promise<unknown>",
13246
+ description: 'Serialize a single node on the page.\n\n@param input - `{ id }` plus optional serialization options:\n - `depth` \u2014 limit how many descendant levels to include.\n - `attributeFilter` \u2014 only return the listed attributes per node.\n - `ancestorPath` \u2014 also return the path of ancestors up to the page root.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns The serialized node, or `null` if no node with that id exists on the page.',
13247
+ references: []
13248
+ },
13249
+ {
13250
+ name: "[$framerApiOnly.serializeNodesForAgent]",
13251
+ category: "framer",
13252
+ signature: "[$framerApiOnly.serializeNodesForAgent](input: { ids: readonly string[]; depth?: number; attributeFilter?: readonly string[]; ancestorPath?: boolean; }, options?: { pagePath?: string; }): Promise<unknown>",
13253
+ description: 'Serialize multiple nodes on the page. Ids that don\'t resolve to a node are skipped.\n\n@param input - `{ ids }` plus optional serialization options:\n - `depth` \u2014 limit how many descendant levels to include.\n - `attributeFilter` \u2014 only return the listed attributes per node.\n - `ancestorPath` \u2014 also return the path of ancestors up to the page root.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns The serialized nodes that were found, in input order.',
13254
+ references: []
13255
+ },
13123
13256
  {
13124
13257
  name: "[$framerInternal.initialState]",
13125
13258
  category: "framer",
@@ -13651,6 +13784,13 @@ var methodsByCategory = {
13651
13784
  description: 'Get the current mode.\n\nA plugin can launch in a special mode where only a subset of the API is\nallowed. The mode is set when the plugin launches and never changes while\nthe plugin is active.\n\n@example\n```ts\nif (framer.mode === "image" || framer.mode === "editImage") {\n // Do image mode specific logic\n return\n}\n```\n@category settings',
13652
13785
  references: ["Mode"]
13653
13786
  },
13787
+ {
13788
+ name: "paginateForAgent",
13789
+ category: "framer",
13790
+ signature: "paginateForAgent(input: { items: readonly unknown[]; keyName?: never; cursor?: never; } | { keyName: string; cursor: number; items?: never; }, options?: { pagePath?: string; }): Promise<unknown>",
13791
+ description: 'Paginate a large array of values across multiple calls. The cursor is\nopaque and only valid within the same headless session and page.\n\n- First page: pass `{ items }`.\n- Continuation: pass `{ keyName, cursor }` using values returned by a previous call.\n\n@param input - `{ items }` for a fresh array, or `{ keyName, cursor }` for continuation.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns The current page, including `keyName`, `cursor`, `results`, `totalResults`, and (if more pages remain) `nextCursor`.',
13792
+ references: []
13793
+ },
13654
13794
  {
13655
13795
  name: "publish",
13656
13796
  category: "framer",
@@ -13731,6 +13871,20 @@ var methodsByCategory = {
13731
13871
  description: "",
13732
13872
  references: ["ScreenshotOptions", "ScreenshotResult"]
13733
13873
  },
13874
+ {
13875
+ name: "serializeForAgent",
13876
+ category: "framer",
13877
+ signature: "serializeForAgent(input: { id: string; depth?: number; attributeFilter?: readonly string[]; ancestorPath?: boolean; }, options?: { pagePath?: string; }): Promise<unknown>",
13878
+ description: 'Serialize a single node on the page.\n\n@param input - `{ id }` plus optional serialization options:\n - `depth` \u2014 limit how many descendant levels to include.\n - `attributeFilter` \u2014 only return the listed attributes per node.\n - `ancestorPath` \u2014 also return the path of ancestors up to the page root.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns The serialized node, or `null` if no node with that id exists on the page.',
13879
+ references: []
13880
+ },
13881
+ {
13882
+ name: "serializeNodesForAgent",
13883
+ category: "framer",
13884
+ signature: "serializeNodesForAgent(input: { ids: readonly string[]; depth?: number; attributeFilter?: readonly string[]; ancestorPath?: boolean; }, options?: { pagePath?: string; }): Promise<unknown>",
13885
+ description: 'Serialize multiple nodes on the page. Ids that don\'t resolve to a node are skipped.\n\n@param input - `{ ids }` plus optional serialization options:\n - `depth` \u2014 limit how many descendant levels to include.\n - `attributeFilter` \u2014 only return the listed attributes per node.\n - `ancestorPath` \u2014 also return the path of ancestors up to the page root.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns The serialized nodes that were found, in input order.',
13886
+ references: []
13887
+ },
13734
13888
  {
13735
13889
  name: "sessionId",
13736
13890
  category: "framer",
@@ -15830,6 +15984,26 @@ function getType(name2) {
15830
15984
  __name(getType, "getType");
15831
15985
 
15832
15986
  // src/docs.ts
15987
+ function getAllKnownNames() {
15988
+ const bareNames = [
15989
+ ...Object.values(classes).map((classInfo) => classInfo.name),
15990
+ ...Object.values(types).map((typeInfo) => typeInfo.name)
15991
+ ];
15992
+ const framerMethods = methodsByCategory.framer ?? [];
15993
+ for (const method of framerMethods) {
15994
+ bareNames.push(method.name);
15995
+ }
15996
+ const methodNames = [];
15997
+ for (const [category, methods] of Object.entries(methodsByCategory)) {
15998
+ const categoryInfo = classes[category];
15999
+ const displayCategory = categoryInfo?.name ?? category;
16000
+ for (const method of methods) {
16001
+ methodNames.push(`${displayCategory}.${method.name}`);
16002
+ }
16003
+ }
16004
+ return { bareNames, methodNames };
16005
+ }
16006
+ __name(getAllKnownNames, "getAllKnownNames");
15833
16007
  function formatDocComment(text, indent = "") {
15834
16008
  const lines = text.split("\n");
15835
16009
  if (lines.length === 1) {
@@ -15919,8 +16093,11 @@ function renderDocs(queries) {
15919
16093
  if (query.includes(".")) {
15920
16094
  const method = getMethod(query);
15921
16095
  if (!method) {
16096
+ const { methodNames } = getAllKnownNames();
16097
+ const suggestion = findClosestMatch(query, methodNames);
16098
+ const hint = suggestion ? ` Did you mean '${suggestion}'?` : "";
15922
16099
  errors.push(
15923
- `Method '${query}' not found. Use 'framer docs' to list all.`
16100
+ `Method '${query}' not found.${hint} Use 'framer docs' to list all.`
15924
16101
  );
15925
16102
  return { lines, errors };
15926
16103
  }
@@ -15965,7 +16142,12 @@ ${typeDef}`);
15965
16142
  ${typeDef}`);
15966
16143
  }
15967
16144
  } else {
15968
- errors.push(`'${query}' not found. Use 'framer docs' to list all.`);
16145
+ const { bareNames } = getAllKnownNames();
16146
+ const suggestion = findClosestMatch(query, bareNames);
16147
+ const hint = suggestion ? ` Did you mean '${suggestion}'?` : "";
16148
+ errors.push(
16149
+ `'${query}' not found.${hint} Use 'framer docs' to list all.`
16150
+ );
15969
16151
  return { lines, errors };
15970
16152
  }
15971
16153
  }
@@ -16143,6 +16325,16 @@ async function ensureRelayServerRunning(options = {}) {
16143
16325
  throw new Error("Failed to start relay server after 5 seconds");
16144
16326
  }
16145
16327
  __name(ensureRelayServerRunning, "ensureRelayServerRunning");
16328
+
16329
+ // src/skill-discovery-wait.ts
16330
+ var CLAUDE_CODE_SKILL_DISCOVERY_WAIT_MS = 4e3;
16331
+ async function waitForClaudeCodeSkillDiscovery() {
16332
+ if (process.env.CLAUDECODE === void 0) return;
16333
+ await new Promise(
16334
+ (resolve) => setTimeout(resolve, CLAUDE_CODE_SKILL_DISCOVERY_WAIT_MS)
16335
+ );
16336
+ }
16337
+ __name(waitForClaudeCodeSkillDiscovery, "waitForClaudeCodeSkillDiscovery");
16146
16338
  var FRAMER_TEMPORARY_DIR = path7.join(os.tmpdir(), "framer");
16147
16339
  function ensureTemporaryDir() {
16148
16340
  fs7.mkdirSync(FRAMER_TEMPORARY_DIR, { recursive: true });
@@ -16294,6 +16486,10 @@ program.name(PROGRAM_NAME).version(VERSION).description("Framer Server API CLI")
16294
16486
  setDebugEnabled(true);
16295
16487
  }
16296
16488
  });
16489
+ function throwRelayError(result) {
16490
+ throw errorWithRef(result.error ?? "Relay error", result.errorRef);
16491
+ }
16492
+ __name(throwRelayError, "throwRelayError");
16297
16493
  async function readStdin() {
16298
16494
  const chunks = [];
16299
16495
  for await (const chunk of process.stdin) {
@@ -16336,9 +16532,7 @@ async function getAgentSystemPrompt(sessionId) {
16336
16532
  cwd: process.cwd()
16337
16533
  });
16338
16534
  debug("exec", "getAgentSystemPrompt: relay responded");
16339
- if (result.error) {
16340
- throw new Error(result.error);
16341
- }
16535
+ if (result.error) throwRelayError(result);
16342
16536
  const prompt = result.output.at(-1);
16343
16537
  if (typeof prompt !== "string") {
16344
16538
  throw new Error("Did not receive agent prompt output.");
@@ -16354,9 +16548,7 @@ async function getAgentContext(sessionId) {
16354
16548
  cwd: process.cwd()
16355
16549
  });
16356
16550
  debug("exec", "getAgentContext: relay responded");
16357
- if (result.error) {
16358
- throw new Error(result.error);
16359
- }
16551
+ if (result.error) throwRelayError(result);
16360
16552
  const context = result.output.at(-1);
16361
16553
  if (typeof context !== "string") {
16362
16554
  throw new Error("Did not receive agent context output.");
@@ -16380,8 +16572,9 @@ async function refreshSkillsFromSession(sessionId, projectId) {
16380
16572
  });
16381
16573
  debug("skills", "skills installed");
16382
16574
  } catch (err) {
16383
- throw new Error(
16384
- `Failed to refresh skills for session ${sessionId}: ${formatError(err)}`
16575
+ throw errorWithRef(
16576
+ `Failed to refresh skills for session ${sessionId}: ${formatError(err)}`,
16577
+ getErrorRef(err)
16385
16578
  );
16386
16579
  }
16387
16580
  }
@@ -16407,7 +16600,7 @@ async function resolveSessionCredentials(projectUrlOrId) {
16407
16600
  projectId,
16408
16601
  errorMessage: message
16409
16602
  });
16410
- printError(`Failed to resolve credentials: ${message}`);
16603
+ printError(`Failed to resolve credentials: ${formatErrorForUser(err)}`);
16411
16604
  await waitForTrackingToFinish();
16412
16605
  process.exit(1);
16413
16606
  }
@@ -16421,9 +16614,7 @@ async function getProjectName(sessionId) {
16421
16614
  cwd: process.cwd()
16422
16615
  });
16423
16616
  debug("exec", "getProjectName: relay responded");
16424
- if (result.error) {
16425
- throw new Error(result.error);
16426
- }
16617
+ if (result.error) throwRelayError(result);
16427
16618
  const projectName = result.output.at(-1);
16428
16619
  if (typeof projectName !== "string") {
16429
16620
  throw new Error("Did not receive project name output.");
@@ -16445,7 +16636,7 @@ async function ensureRelayForCli(context) {
16445
16636
  userId: context?.userId,
16446
16637
  errorMessage: message
16447
16638
  });
16448
- printError(`Failed to check relay status: ${message}`);
16639
+ printError(`Failed to check relay status: ${formatErrorForUser(err)}`);
16449
16640
  await waitForTrackingToFinish();
16450
16641
  process.exit(1);
16451
16642
  }
@@ -16463,12 +16654,14 @@ async function execAndPrint(sessionId, code) {
16463
16654
  print(line);
16464
16655
  }
16465
16656
  if (result.error) {
16466
- printError(`Error: ${result.error}`);
16657
+ printError(
16658
+ `Error: ${formatErrorForUser(errorWithRef(result.error, result.errorRef))}`
16659
+ );
16467
16660
  await waitForTrackingToFinish();
16468
16661
  process.exit(1);
16469
16662
  }
16470
16663
  } catch (err) {
16471
- printError(`Execution failed: ${formatError(err)}`);
16664
+ printError(`Execution failed: ${formatErrorForUser(err)}`);
16472
16665
  await waitForTrackingToFinish();
16473
16666
  process.exit(1);
16474
16667
  }
@@ -16485,7 +16678,7 @@ program.command("exec").description("Execute code in a session").requiredOption(
16485
16678
  try {
16486
16679
  code = fs7.readFileSync(filePath, "utf-8");
16487
16680
  } catch (err) {
16488
- printError(`Failed to read file: ${formatError(err)}`);
16681
+ printError(`Failed to read file: ${formatErrorForUser(err)}`);
16489
16682
  await waitForTrackingToFinish();
16490
16683
  process.exit(1);
16491
16684
  }
@@ -16574,6 +16767,7 @@ session.command("new <projectUrlOrId>").description("Create a new session and pr
16574
16767
  refreshSkillsFromSession(sessionId, projectId)
16575
16768
  ]);
16576
16769
  debug("session.new", `project name="${projectName}", skills refreshed`);
16770
+ await waitForClaudeCodeSkillDiscovery();
16577
16771
  saveProject({
16578
16772
  projectId,
16579
16773
  apiKey: credentials.apiKey,
@@ -16590,7 +16784,7 @@ session.command("new <projectUrlOrId>").description("Create a new session and pr
16590
16784
  cleanupStaleSkills(activeProjectIds);
16591
16785
  } catch (error) {
16592
16786
  printWarning(
16593
- `Failed to clean up stale skills: ${formatError(error)}`
16787
+ `Failed to clean up stale skills: ${formatErrorForUser(error)}`
16594
16788
  );
16595
16789
  }
16596
16790
  const activeSessionIds = activeSessions.map(
@@ -16600,7 +16794,7 @@ session.command("new <projectUrlOrId>").description("Create a new session and pr
16600
16794
  cleanupStaleSessionCodeFiles(activeSessionIds);
16601
16795
  } catch (error) {
16602
16796
  printWarning(
16603
- `Failed to clean up stale session code files: ${formatError(error)}`
16797
+ `Failed to clean up stale session code files: ${formatErrorForUser(error)}`
16604
16798
  );
16605
16799
  }
16606
16800
  debug(
@@ -16619,7 +16813,7 @@ session.command("new <projectUrlOrId>").description("Create a new session and pr
16619
16813
  userId,
16620
16814
  errorMessage: message
16621
16815
  });
16622
- printError(`Failed to create session: ${message}`);
16816
+ printError(`Failed to create session: ${formatErrorForUser(err)}`);
16623
16817
  if (isAuthError(message)) {
16624
16818
  clearApiKey(projectId);
16625
16819
  printError("The stored API key was invalid and has been removed.");
@@ -16653,7 +16847,7 @@ session.command("list").description("List all active sessions").action(async ()
16653
16847
  })
16654
16848
  );
16655
16849
  } catch (err) {
16656
- printError(`Failed to list sessions: ${formatError(err)}`);
16850
+ printError(`Failed to list sessions: ${formatErrorForUser(err)}`);
16657
16851
  await waitForTrackingToFinish();
16658
16852
  process.exit(1);
16659
16853
  }
@@ -16693,7 +16887,7 @@ async function saveAuthResult(verb, authFn) {
16693
16887
  saveProject({ projectId, apiKey: result.apiKey, userId: result.userId });
16694
16888
  print(`Project ${projectId} saved`);
16695
16889
  } catch (error) {
16696
- printError(`Failed to ${verb} project: ${formatError(error)}`);
16890
+ printError(`Failed to ${verb} project: ${formatErrorForUser(error)}`);
16697
16891
  await waitForTrackingToFinish();
16698
16892
  process.exit(1);
16699
16893
  }
@@ -16716,7 +16910,7 @@ session.command("destroy <sessionId>").description("Destroy a session").action(a
16716
16910
  await client.destroySession.mutate({ sessionId });
16717
16911
  print(`Session ${sessionId} destroyed`);
16718
16912
  } catch (err) {
16719
- printError(`Failed to destroy session: ${formatError(err)}`);
16913
+ printError(`Failed to destroy session: ${formatErrorForUser(err)}`);
16720
16914
  await waitForTrackingToFinish();
16721
16915
  process.exit(1);
16722
16916
  }
@@ -16744,10 +16938,11 @@ program.command("setup").description(
16744
16938
  ).action(async () => {
16745
16939
  try {
16746
16940
  const results = installSkills();
16941
+ await waitForClaudeCodeSkillDiscovery();
16747
16942
  printSetupSummary(results);
16748
16943
  printTelemetryNotice();
16749
16944
  } catch (err) {
16750
- printError(`Setup failed: ${formatError(err)}`);
16945
+ printError(`Setup failed: ${formatErrorForUser(err)}`);
16751
16946
  await waitForTrackingToFinish();
16752
16947
  process.exit(1);
16753
16948
  }
@@ -8,12 +8,12 @@ import crypto, { randomUUID, timingSafeEqual } from 'crypto';
8
8
  import http from 'http';
9
9
  import { createHTTPHandler } from '@trpc/server/adapters/standalone';
10
10
  import { initTRPC, TRPCError } from '@trpc/server';
11
+ import { connect, FramerAPIError } from 'framer-api';
11
12
  import { z } from 'zod';
12
- import { connect } from 'framer-api';
13
13
  import { createRequire } from 'module';
14
14
  import * as vm from 'vm';
15
15
 
16
- /* @framer/ai relay server v0.0.22 */
16
+ /* @framer/ai relay server v0.0.24 */
17
17
  var __defProp = Object.defineProperty;
18
18
  var __knownSymbol = (name2, symbol) => (symbol = Symbol[name2]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name2);
19
19
  var __typeError = (msg) => {
@@ -159,7 +159,7 @@ __name(debug, "debug");
159
159
  // src/version.ts
160
160
  var VERSION = (
161
161
  // typeof is used to ensure this can be used just via tsx or node etc. without build
162
- "0.0.22"
162
+ "0.0.24"
163
163
  );
164
164
 
165
165
  // src/relay-client.ts
@@ -391,6 +391,19 @@ var ScopedFS = class {
391
391
  }
392
392
  constants = fs4.constants;
393
393
  };
394
+ function getErrorRef(error) {
395
+ if (!(error instanceof Error)) return void 0;
396
+ const ref = error.ref;
397
+ return typeof ref === "string" && ref.length > 0 ? ref : void 0;
398
+ }
399
+ __name(getErrorRef, "getErrorRef");
400
+ function formatError(error) {
401
+ if (error instanceof Error) {
402
+ return error.message;
403
+ }
404
+ return String(error);
405
+ }
406
+ __name(formatError, "formatError");
394
407
 
395
408
  // src/execute.ts
396
409
  var EXECUTION_TIMEOUT = 10 * 60 * 1e3;
@@ -530,9 +543,11 @@ async function execute(session, code, options = {}) {
530
543
  if (isConnectionError(errorMessage)) {
531
544
  session.connection.markDisconnected();
532
545
  }
546
+ const errorRef = getErrorRef(err);
533
547
  return {
534
548
  output,
535
- error: errorMessage
549
+ error: errorMessage,
550
+ ...errorRef ? { errorRef } : {}
536
551
  };
537
552
  } finally {
538
553
  if (timeoutId) clearTimeout(timeoutId);
@@ -546,10 +561,13 @@ async function executeWithReconnect(session, code, options, execId) {
546
561
  );
547
562
  try {
548
563
  await session.connection.reconnect();
549
- } catch {
564
+ } catch (err) {
565
+ const inner = err instanceof Error ? err.message : String(err);
566
+ const errorRef = getErrorRef(err);
550
567
  return {
551
568
  output: [],
552
- error: "Failed to get connection for session"
569
+ error: `Failed to get connection for session: ${inner}`,
570
+ ...errorRef ? { errorRef } : {}
553
571
  };
554
572
  }
555
573
  }
@@ -562,13 +580,16 @@ async function executeWithReconnect(session, code, options, execId) {
562
580
  );
563
581
  try {
564
582
  await session.connection.reconnect();
565
- } catch {
583
+ } catch (err) {
584
+ const inner = err instanceof Error ? err.message : String(err);
585
+ const errorRef = getErrorRef(err);
566
586
  log(
567
- `reconnect.failed exec=${execId} session=${session.id} req=${session.connection.framer.requestId} error="reconnect failed"`
587
+ `reconnect.failed exec=${execId} session=${session.id} req=${session.connection.framer.requestId} error="${inner}"`
568
588
  );
569
589
  return {
570
590
  output: [],
571
- error: "Connection lost and failed to reconnect"
591
+ error: `Connection lost and failed to reconnect: ${inner}`,
592
+ ...errorRef ? { errorRef } : {}
572
593
  };
573
594
  }
574
595
  log(
@@ -1156,7 +1177,17 @@ var SessionManager = class {
1156
1177
  var sessionManager = new SessionManager();
1157
1178
 
1158
1179
  // src/router.ts
1159
- var t = initTRPC.create();
1180
+ var t = initTRPC.create({
1181
+ errorFormatter({ shape, error }) {
1182
+ const cause = error.cause;
1183
+ if (cause instanceof FramerAPIError) {
1184
+ const framerApi = { code: cause.code };
1185
+ if (cause.ref !== void 0) framerApi.ref = cause.ref;
1186
+ return { ...shape, data: { ...shape.data, framerApi } };
1187
+ }
1188
+ return shape;
1189
+ }
1190
+ });
1160
1191
  var nextExecId = 0;
1161
1192
  var appRouter = t.router({
1162
1193
  version: t.procedure.query(() => {
@@ -1192,7 +1223,7 @@ var appRouter = t.router({
1192
1223
  });
1193
1224
  return { id: session.id };
1194
1225
  } catch (err) {
1195
- const message = err instanceof Error ? err.message : String(err);
1226
+ const message = formatError(err);
1196
1227
  trackError({
1197
1228
  errorType: "session_create_error",
1198
1229
  projectId: input.projectId,
@@ -1201,7 +1232,8 @@ var appRouter = t.router({
1201
1232
  });
1202
1233
  throw new TRPCError({
1203
1234
  code: isAuthError(message) ? "UNAUTHORIZED" : "INTERNAL_SERVER_ERROR",
1204
- message
1235
+ message,
1236
+ cause: err
1205
1237
  });
1206
1238
  }
1207
1239
  }),
@@ -16,7 +16,7 @@ allowed-tools: ["Bash(npx framer-dalton:*)", "Bash(npx framer-dalton@latest:*)",
16
16
  - `read-project` and `apply-changes` are one-liner CLI shortcuts for the high-frequency canvas-editing methods. Use them to read state and apply DSL inline without writing a file first:
17
17
  - `npx framer-dalton read-project -s <sessionId> -q <queries> -p <pagePath>` — equivalent to `framer.readProjectForAgent(queries, { pagePath })`. Query types are documented in the embedded prompt below.
18
18
  - `npx framer-dalton apply-changes -s <sessionId> -p <pagePath> -e <dsl>` — equivalent to `framer.applyAgentChanges(dsl, { pagePath })`.
19
- - The embedded prompt below also references other agent-surface methods (`reviewChangesForAgent`, `publishForAgent`, `queryImagesForAgent`, `flattenComponentInstanceForAgent`, `makeExternalComponentLocalForAgent`, and `getNodeForAgent` / `getNodesForAgent` / `getNodesOfTypesForAgent` / `getScopeNodeForAgent` / `getGroundNodeForAgent` / `getParentNodeForAgent` / `getAncestorsForAgent`). These have no dedicated shortcut; invoke via `npx framer-dalton exec -s <sessionId> -e 'console.log(await framer.<method>(<args>))'`.
19
+ - The embedded prompt below also references other agent-surface methods (`reviewChangesForAgent`, `publishForAgent`, `queryImagesForAgent`, `flattenComponentInstanceForAgent`, `makeExternalComponentLocalForAgent`, `getNodeForAgent` / `getNodesForAgent` / `getNodesOfTypesForAgent` / `getScopeNodeForAgent` / `getGroundNodeForAgent` / `getParentNodeForAgent` / `getAncestorsForAgent`, and `serializeForAgent` / `serializeNodesForAgent` / `paginateForAgent`). These have no dedicated shortcut; invoke via `npx framer-dalton exec -s <sessionId> -e 'console.log(await framer.<method>(<args>))'`.
20
20
 
21
21
  ## Workflow Loop
22
22
 
@@ -198,12 +198,25 @@ Prompting may take a while to complete, so set the command timeout to 10 minutes
198
198
 
199
199
  ## Execute Code
200
200
 
201
- Use your write tool to write your code to a unique file under `{{FRAMER_TEMPORARY_DIR}}/` and execute with `-f`:
201
+ Prefer writing code to a unique file under `{{FRAMER_TEMPORARY_DIR}}/` and executing it with `-f`:
202
202
 
203
203
  ```bash
204
204
  npx framer-dalton exec -s <sessionId> -f {{FRAMER_TEMPORARY_DIR}}/<sessionId>-<short-summary>.js
205
205
  ```
206
206
 
207
+ For short snippets, `exec` also accepts `-e <code>` or code piped on stdin.
208
+
209
+ ## Shell Quoting
210
+
211
+ In Windows PowerShell, if an argument contains nested quotes, use a single-quoted here-string and pass the variable. Do not backslash-escape quotes.
212
+
213
+ ```powershell
214
+ $value = @'
215
+ [{"key":"value","filter":["text","$rect"]}]
216
+ '@
217
+ npx framer-dalton <command> --option $value
218
+ ```
219
+
207
220
  ## API Documentation
208
221
 
209
222
  ```bash
@@ -438,6 +451,5 @@ await collection.setPluginData("lastSync", new Date().toISOString());
438
451
  ### Known Limitations
439
452
 
440
453
  - **Pages**: Cannot change the path of a page
441
- - **Collection Index/Detail Pages**: Cannot be created as new pages; the user must create them through the UI. Once they exist, they can be modified normally through canvas editing.
442
454
  - **Code overrides**: Cannot assign overrides to nodes
443
455
  - **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.22",
3
+ "version": "0.0.24",
4
4
  "type": "module",
5
5
  "bin": "./dist/cli.js",
6
6
  "main": "./dist/cli.js",
@@ -20,14 +20,14 @@
20
20
  "generate-types": "tsx scripts/generate-types.ts"
21
21
  },
22
22
  "dependencies": {
23
- "@trpc/client": "^11.9.0",
24
- "@trpc/server": "^11.9.0",
23
+ "@trpc/client": "^11.17.0",
24
+ "@trpc/server": "^11.17.0",
25
25
  "commander": "^12.1.0",
26
- "framer-api": "^0.1.8",
26
+ "framer-api": "0.1.10",
27
27
  "zod": "^4.3.6"
28
28
  },
29
29
  "devDependencies": {
30
- "@biomejs/biome": "^2.3.13",
30
+ "@biomejs/biome": "^2.4.13",
31
31
  "@framerjs/framer-events": "0.0.175",
32
32
  "@types/node": "24.10.9",
33
33
  "tsup": "^8.0.2",