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 +228 -33
- package/dist/start-relay-server.js +44 -12
- package/docs/skills/framer-canvas-editing-project.md +1 -1
- package/docs/skills/framer.md +14 -2
- package/package.json +5 -5
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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: ${
|
|
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: ${
|
|
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(
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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.
|
|
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.
|
|
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:
|
|
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="
|
|
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:
|
|
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 =
|
|
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`,
|
|
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
|
|
package/docs/skills/framer.md
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
24
|
-
"@trpc/server": "^11.
|
|
23
|
+
"@trpc/client": "^11.17.0",
|
|
24
|
+
"@trpc/server": "^11.17.0",
|
|
25
25
|
"commander": "^12.1.0",
|
|
26
|
-
"framer-api": "
|
|
26
|
+
"framer-api": "0.1.10",
|
|
27
27
|
"zod": "^4.3.6"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@biomejs/biome": "^2.
|
|
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",
|