@framer/agent 0.0.36 → 0.0.37

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
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import fs8 from 'fs';
3
- import path7 from 'path';
3
+ import path6 from 'path';
4
4
  import { Command } from 'commander';
5
5
  import crypto, { randomUUID } from 'crypto';
6
6
  import http from 'http';
@@ -9,11 +9,11 @@ import { z } from 'zod';
9
9
  import os2 from 'os';
10
10
  import isWsl from 'is-wsl';
11
11
  import { ErrorCode, FramerAPIError } from 'framer-api';
12
- import net from 'net';
13
12
  import { fileURLToPath } from 'url';
13
+ import net from 'net';
14
14
  import { createTRPCClient, httpLink } from '@trpc/client';
15
15
 
16
- /* @framer/ai CLI v0.0.36 */
16
+ /* @framer/ai CLI v0.0.37 */
17
17
  var __defProp = Object.defineProperty;
18
18
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
19
19
 
@@ -107,12 +107,12 @@ function openUrl(url) {
107
107
  __name(openUrl, "openUrl");
108
108
  function getConfigDir() {
109
109
  if (process.env.XDG_CONFIG_HOME) {
110
- return path7.join(process.env.XDG_CONFIG_HOME, "framer");
110
+ return path6.join(process.env.XDG_CONFIG_HOME, "framer");
111
111
  }
112
112
  if (process.platform === "win32") {
113
- return path7.join(process.env.APPDATA || os2.homedir(), "framer");
113
+ return path6.join(process.env.APPDATA || os2.homedir(), "framer");
114
114
  }
115
- return path7.join(os2.homedir(), ".config", "framer");
115
+ return path6.join(os2.homedir(), ".config", "framer");
116
116
  }
117
117
  __name(getConfigDir, "getConfigDir");
118
118
  function ensureConfigDir() {
@@ -123,11 +123,11 @@ __name(ensureConfigDir, "ensureConfigDir");
123
123
 
124
124
  // src/config/projects.ts
125
125
  function getProjectsConfigPath() {
126
- return path7.join(getConfigDir(), "projects.json");
126
+ return path6.join(getConfigDir(), "projects.json");
127
127
  }
128
128
  __name(getProjectsConfigPath, "getProjectsConfigPath");
129
129
  function getLegacyCredentialsPath() {
130
- return path7.join(getConfigDir(), "credentials.json");
130
+ return path6.join(getConfigDir(), "credentials.json");
131
131
  }
132
132
  __name(getLegacyCredentialsPath, "getLegacyCredentialsPath");
133
133
  var ProjectsConfigSchema = z.object({
@@ -313,7 +313,7 @@ var SettingsFileSchema = z.object({
313
313
  telemetryNoticeShown: z.boolean().optional()
314
314
  });
315
315
  function getSettingsPath() {
316
- return path7.join(getConfigDir(), "settings.json");
316
+ return path6.join(getConfigDir(), "settings.json");
317
317
  }
318
318
  __name(getSettingsPath, "getSettingsPath");
319
319
  var settings;
@@ -397,7 +397,7 @@ __name(markTelemetryNoticeShown, "markTelemetryNoticeShown");
397
397
  // src/version.ts
398
398
  var VERSION = (
399
399
  // typeof is used to ensure this can be used just via tsx or node etc. without build
400
- "0.0.36"
400
+ "0.0.37"
401
401
  );
402
402
  var trackingEndpoint = "https://events.framer.com/track";
403
403
  var inProgressTrackings = /* @__PURE__ */ new Set();
@@ -1174,7 +1174,7 @@ var types = {
1174
1174
  members: [
1175
1175
  {
1176
1176
  name: "type",
1177
- type: '"page" | "collection" | "designPage" | "siteSettings" | "component" | "layoutTemplate" | "vectorSet" | "codeFile"',
1177
+ type: '"page" | "collection" | "designPage" | "siteSettings" | "component" | "layoutTemplate" | "vectorSet" | "codeFile" | "colorStyle" | "textStyle" | "linkStyle" | "blockquoteStyle" | "inlineCodeStyle" | "localization" | "localizationGlossary"',
1178
1178
  description: "",
1179
1179
  optional: false
1180
1180
  },
@@ -7627,6 +7627,12 @@ var types = {
7627
7627
  description: "@alpha",
7628
7628
  optional: false
7629
7629
  },
7630
+ {
7631
+ name: "replaceTextForAgent",
7632
+ type: "(input: {\n id: string;\n searchText: string;\n replaceText: string;\n }, options?: {\n pagePath?: string;\n }) => Promise<boolean>",
7633
+ description: "@alpha",
7634
+ optional: false
7635
+ },
7630
7636
  {
7631
7637
  name: "ping",
7632
7638
  type: "() => Promise<void>",
@@ -7681,6 +7687,24 @@ var types = {
7681
7687
  description: "@alpha",
7682
7688
  optional: false
7683
7689
  },
7690
+ {
7691
+ name: "getDescendantsOfTypesForAgent",
7692
+ type: "(input: {\n id: string;\n types: readonly string[];\n }, options?: {\n pagePath?: string;\n }) => Promise<unknown>",
7693
+ description: "@alpha",
7694
+ optional: false
7695
+ },
7696
+ {
7697
+ name: "getRectForAgent",
7698
+ type: "(input: {\n id: string;\n }, options?: {\n pagePath?: string;\n }) => Promise<Rect$1 | null>",
7699
+ description: "@alpha",
7700
+ optional: false
7701
+ },
7702
+ {
7703
+ name: "getDescendantReferencesOfTypesForAgent",
7704
+ type: "(input: {\n id: string;\n types: readonly string[];\n }, options?: {\n pagePath?: string;\n }) => Promise<unknown>",
7705
+ description: "@alpha",
7706
+ optional: false
7707
+ },
7684
7708
  {
7685
7709
  name: "getScopeNodeForAgent",
7686
7710
  type: "(input: {\n id: string;\n }, options?: {\n pagePath?: string;\n }) => Promise<unknown>",
@@ -14248,6 +14272,20 @@ var methodsByCategory = {
14248
14272
  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. Pair with the static prompt\nfrom {@link getSystemPrompt} for complete agent context.\n\n@returns A string containing the project context.",
14249
14273
  references: []
14250
14274
  },
14275
+ {
14276
+ name: "getDescendantReferencesOfTypes",
14277
+ category: "FramerAgentAPI",
14278
+ signature: "getDescendantReferencesOfTypes(input: { id: string; types: readonly string[]; }, options?: { pagePath?: string; }): Promise<unknown>",
14279
+ description: 'Get style and token nodes referenced by a node\'s descendants.\n\n@param input - `{ id, types }`: the ancestor node id and referenced node kinds to match.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns The referenced nodes without their children.',
14280
+ references: []
14281
+ },
14282
+ {
14283
+ name: "getDescendantsOfTypes",
14284
+ category: "FramerAgentAPI",
14285
+ signature: "getDescendantsOfTypes(input: { id: string; types: readonly string[]; }, options?: { pagePath?: string; }): Promise<unknown>",
14286
+ description: 'Get descendants of a node with one or more kinds (e.g. `"FrameNode"`, `"RichTextNode"`, `"TextRun"`).\n\n@param input - `{ id, types }`: the ancestor node id and node kinds to match.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns The matching descendants without their children.',
14287
+ references: []
14288
+ },
14251
14289
  {
14252
14290
  name: "getGroundNode",
14253
14291
  category: "FramerAgentAPI",
@@ -14283,6 +14321,13 @@ var methodsByCategory = {
14283
14321
  description: 'Get the direct parent of a node.\n\n@param input - `{ id }`: the id of the child node.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns The parent node, or `null` if the node has no parent or doesn\'t exist.',
14284
14322
  references: []
14285
14323
  },
14324
+ {
14325
+ name: "getRect",
14326
+ category: "FramerAgentAPI",
14327
+ signature: "getRect(input: { id: string; }, options?: { pagePath?: string; }): Promise<Rect$1 | null>",
14328
+ description: 'Get the measured rect for a node.\n\n@param input - `{ id }`: the id of the node to measure.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns The rounded measured rect, or `null` if no node with that id exists on the page.',
14329
+ references: ["Rect$1"]
14330
+ },
14286
14331
  {
14287
14332
  name: "getScopeNode",
14288
14333
  category: "FramerAgentAPI",
@@ -14395,6 +14440,13 @@ var methodsByCategory = {
14395
14440
  description: "Renames a branch.\n\n@param branchId - Branch id to rename.\n@param title - New branch title.",
14396
14441
  references: []
14397
14442
  },
14443
+ {
14444
+ name: "replaceText",
14445
+ category: "FramerAgentAPI",
14446
+ signature: "replaceText(input: { id: string; searchText: string; replaceText: string; }, options?: { pagePath?: string; }): Promise<boolean>",
14447
+ description: 'Replaces text within a text-like node.\n\n@param input - `{ id, searchText, replaceText }`: the target node id, text to find, and replacement text.\n@param options.pagePath - Target page path (e.g. `"/about"`). Defaults to the active page.\n@returns `true` when text was replaced, or `false` when no matching text was found.',
14448
+ references: []
14449
+ },
14398
14450
  {
14399
14451
  name: "reviewChanges",
14400
14452
  category: "FramerAgentAPI",
@@ -16618,6 +16670,391 @@ ${typeDef}`);
16618
16670
  }
16619
16671
  __name(renderDocs, "renderDocs");
16620
16672
 
16673
+ // src/prompt-context.ts
16674
+ function toKebabId(title) {
16675
+ const id = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
16676
+ return id || "section";
16677
+ }
16678
+ __name(toKebabId, "toKebabId");
16679
+ function uniquifyId(id, seen) {
16680
+ const count = seen.get(id) ?? 0;
16681
+ seen.set(id, count + 1);
16682
+ return count === 0 ? id : `${id}-${count + 1}`;
16683
+ }
16684
+ __name(uniquifyId, "uniquifyId");
16685
+ function splitPromptSections(markdown) {
16686
+ const normalized = markdown.replaceAll("\r\n", "\n");
16687
+ if (normalized.trim() === "") {
16688
+ throw new Error("Cannot split an empty agent prompt.");
16689
+ }
16690
+ const sections = [];
16691
+ let current;
16692
+ for (const line of normalized.split("\n")) {
16693
+ const heading = line.match(/^#\s+(.+?)\s*#*\s*$/u);
16694
+ if (heading) {
16695
+ current = { title: heading[1].trim(), lines: [] };
16696
+ sections.push(current);
16697
+ continue;
16698
+ }
16699
+ if (!current) {
16700
+ current = { title: "Preamble", lines: [] };
16701
+ sections.push(current);
16702
+ }
16703
+ current.lines.push(line);
16704
+ }
16705
+ const seen = /* @__PURE__ */ new Map();
16706
+ return sections.map((section) => ({
16707
+ id: uniquifyId(toKebabId(section.title), seen),
16708
+ title: section.title,
16709
+ content: section.lines.join("\n").trim()
16710
+ })).filter(
16711
+ (section) => section.title !== "Preamble" || section.content !== ""
16712
+ );
16713
+ }
16714
+ __name(splitPromptSections, "splitPromptSections");
16715
+ function renderTemplate(template, values) {
16716
+ let rendered = template;
16717
+ for (const [key, value] of Object.entries(values)) {
16718
+ rendered = rendered.split(`{{${key}}}`).join(value);
16719
+ }
16720
+ return rendered;
16721
+ }
16722
+ __name(renderTemplate, "renderTemplate");
16723
+ function writeRenderedFile(filePath, content) {
16724
+ fs8.mkdirSync(path6.dirname(filePath), { recursive: true });
16725
+ fs8.writeFileSync(filePath, `${content.trimEnd()}
16726
+ `, "utf-8");
16727
+ }
16728
+ __name(writeRenderedFile, "writeRenderedFile");
16729
+ function isTextFile(filePath) {
16730
+ const extension = path6.extname(filePath).toLowerCase();
16731
+ return [
16732
+ ".css",
16733
+ ".html",
16734
+ ".js",
16735
+ ".json",
16736
+ ".md",
16737
+ ".mjs",
16738
+ ".ts",
16739
+ ".tsx",
16740
+ ".txt",
16741
+ ".yaml",
16742
+ ".yml"
16743
+ ].includes(extension);
16744
+ }
16745
+ __name(isTextFile, "isTextFile");
16746
+ function renderFileTree(sourceDir, destinationDir, options) {
16747
+ for (const entry of fs8.readdirSync(sourceDir, { withFileTypes: true })) {
16748
+ const sourcePath = path6.join(sourceDir, entry.name);
16749
+ const destinationPath = path6.join(destinationDir, entry.name);
16750
+ if (entry.isDirectory()) {
16751
+ fs8.mkdirSync(destinationPath, { recursive: true });
16752
+ renderFileTree(sourcePath, destinationPath, options);
16753
+ continue;
16754
+ }
16755
+ if (entry.isSymbolicLink()) {
16756
+ throw new Error(options.getSymlinkErrorMessage(sourcePath));
16757
+ }
16758
+ const renderedDestinationPath = options.getRenderedDestinationPath({
16759
+ sourcePath,
16760
+ destinationPath
16761
+ });
16762
+ if (renderedDestinationPath) {
16763
+ const renderedContent = renderTemplate(
16764
+ fs8.readFileSync(sourcePath, "utf-8"),
16765
+ options.templateValues
16766
+ );
16767
+ writeRenderedFile(
16768
+ renderedDestinationPath,
16769
+ options.transformRenderedContent?.(renderedContent) ?? renderedContent
16770
+ );
16771
+ continue;
16772
+ }
16773
+ fs8.copyFileSync(sourcePath, destinationPath);
16774
+ }
16775
+ }
16776
+ __name(renderFileTree, "renderFileTree");
16777
+ var FRAMER_TEMPORARY_DIR = path6.join(os2.tmpdir(), "framer");
16778
+ function ensureTemporaryDir() {
16779
+ fs8.mkdirSync(FRAMER_TEMPORARY_DIR, { recursive: true });
16780
+ }
16781
+ __name(ensureTemporaryDir, "ensureTemporaryDir");
16782
+ function cleanupStaleSessionCodeFiles(activeSessionIds) {
16783
+ if (!fs8.existsSync(FRAMER_TEMPORARY_DIR)) return;
16784
+ const activeSessionIdsSet = new Set(activeSessionIds);
16785
+ for (const entry of fs8.readdirSync(FRAMER_TEMPORARY_DIR)) {
16786
+ const [sessionId] = entry.split("-");
16787
+ if (activeSessionIdsSet.has(sessionId)) continue;
16788
+ const filePath = path6.join(FRAMER_TEMPORARY_DIR, entry);
16789
+ fs8.rmSync(filePath, { recursive: true, force: true, maxRetries: 2 });
16790
+ }
16791
+ }
16792
+ __name(cleanupStaleSessionCodeFiles, "cleanupStaleSessionCodeFiles");
16793
+
16794
+ // src/skills.ts
16795
+ var META_SKILL_NAME = "framer";
16796
+ var CODE_COMPONENTS_SKILL_NAME = "framer-code-components";
16797
+ var __dirname$1 = path6.dirname(fileURLToPath(import.meta.url));
16798
+ var sourceSkillsDir = path6.join(__dirname$1, "..", "skills");
16799
+ var GENERATED_PROJECTS_DIR_NAME = "projects";
16800
+ var GENERATED_PROJECT_TEMPLATE_ID = "__template__";
16801
+ var PROJECT_SKILL_PREFIX = "framer-project-";
16802
+ var LEGACY_CANVAS_EDITING_SKILL_PREFIX = "framer-canvas-editing-project-";
16803
+ var PACKAGE_NAME = "@framer/agent";
16804
+ var DEFAULT_FRAMER_AGENT_COMMAND = `npx ${PACKAGE_NAME}`;
16805
+ var RELEASE_AGE_OVERRIDE_FRAMER_AGENT_COMMAND = `npx --min-release-age=0 ${PACKAGE_NAME}`;
16806
+ function getFramerAgentCommand() {
16807
+ return isNpxMinReleaseAgeOverrideEnabled() ? RELEASE_AGE_OVERRIDE_FRAMER_AGENT_COMMAND : DEFAULT_FRAMER_AGENT_COMMAND;
16808
+ }
16809
+ __name(getFramerAgentCommand, "getFramerAgentCommand");
16810
+ function replaceDefaultFramerAgentCommand(content) {
16811
+ return content.replaceAll(
16812
+ DEFAULT_FRAMER_AGENT_COMMAND,
16813
+ getFramerAgentCommand()
16814
+ );
16815
+ }
16816
+ __name(replaceDefaultFramerAgentCommand, "replaceDefaultFramerAgentCommand");
16817
+ function getSkillTemplateValues(skillDir) {
16818
+ return {
16819
+ FRAMER_TEMPORARY_DIR,
16820
+ FRAMER_AGENT_COMMAND: getFramerAgentCommand(),
16821
+ FRAMER_SKILL_DIR: skillDir
16822
+ };
16823
+ }
16824
+ __name(getSkillTemplateValues, "getSkillTemplateValues");
16825
+ function toSafeProjectId(projectId) {
16826
+ return projectId.replace(/[^a-zA-Z0-9_-]/g, "-");
16827
+ }
16828
+ __name(toSafeProjectId, "toSafeProjectId");
16829
+ function removeSkillDirContents(skillDir, options) {
16830
+ for (const entry of fs8.readdirSync(skillDir, { withFileTypes: true })) {
16831
+ const entryPath = path6.join(skillDir, entry.name);
16832
+ if (options.preserveGeneratedProjectBundles && entry.name === GENERATED_PROJECTS_DIR_NAME && entry.isDirectory()) {
16833
+ fs8.rmSync(path6.join(entryPath, GENERATED_PROJECT_TEMPLATE_ID), {
16834
+ recursive: true,
16835
+ force: true,
16836
+ maxRetries: 2
16837
+ });
16838
+ continue;
16839
+ }
16840
+ fs8.rmSync(entryPath, { recursive: true, force: true, maxRetries: 2 });
16841
+ }
16842
+ }
16843
+ __name(removeSkillDirContents, "removeSkillDirContents");
16844
+ function prepareSkillDir(root, skillName) {
16845
+ fs8.mkdirSync(root, { recursive: true });
16846
+ const rootStat = fs8.statSync(root);
16847
+ if (!rootStat.isDirectory()) {
16848
+ throw new Error(`Skill root is not a directory: ${root}`);
16849
+ }
16850
+ const skillDir = path6.join(root, skillName);
16851
+ const current = fs8.lstatSync(skillDir, { throwIfNoEntry: false });
16852
+ if (current && (current.isSymbolicLink() || !current.isDirectory())) {
16853
+ fs8.rmSync(skillDir, { recursive: true, force: true });
16854
+ }
16855
+ if (current?.isDirectory()) {
16856
+ removeSkillDirContents(skillDir, {
16857
+ preserveGeneratedProjectBundles: skillName === META_SKILL_NAME
16858
+ });
16859
+ }
16860
+ fs8.mkdirSync(skillDir, { recursive: true });
16861
+ return skillDir;
16862
+ }
16863
+ __name(prepareSkillDir, "prepareSkillDir");
16864
+ function installSourceSkill(root, skillName) {
16865
+ const sourceDir = path6.join(sourceSkillsDir, skillName);
16866
+ const skillDir = prepareSkillDir(root, skillName);
16867
+ renderFileTree(sourceDir, skillDir, {
16868
+ templateValues: getSkillTemplateValues(skillDir),
16869
+ getRenderedDestinationPath: /* @__PURE__ */ __name(({ sourcePath, destinationPath }) => isTextFile(sourcePath) ? destinationPath : void 0, "getRenderedDestinationPath"),
16870
+ getSymlinkErrorMessage: /* @__PURE__ */ __name((sourcePath) => `Source skill contains unsupported symlink: ${sourcePath}`, "getSymlinkErrorMessage")
16871
+ });
16872
+ return path6.join(skillDir, "SKILL.md");
16873
+ }
16874
+ __name(installSourceSkill, "installSourceSkill");
16875
+ function cleanupLegacyCanvasEditingSkills(skillRoots = getDefaultSkillRoots()) {
16876
+ for (const root of skillRoots) {
16877
+ if (!fs8.existsSync(root)) continue;
16878
+ for (const entry of fs8.readdirSync(root)) {
16879
+ if (!entry.startsWith(LEGACY_CANVAS_EDITING_SKILL_PREFIX)) continue;
16880
+ const skillDir = path6.join(root, entry);
16881
+ fs8.rmSync(skillDir, { recursive: true, force: true, maxRetries: 2 });
16882
+ }
16883
+ }
16884
+ }
16885
+ __name(cleanupLegacyCanvasEditingSkills, "cleanupLegacyCanvasEditingSkills");
16886
+ function getDefaultSkillRoots() {
16887
+ const home = os2.homedir();
16888
+ return [
16889
+ path6.join(home, ".agents", "skills"),
16890
+ path6.join(home, ".claude", "skills")
16891
+ ];
16892
+ }
16893
+ __name(getDefaultSkillRoots, "getDefaultSkillRoots");
16894
+ function cleanupProjectSkills(skillRoots = getDefaultSkillRoots()) {
16895
+ for (const root of skillRoots) {
16896
+ if (!fs8.existsSync(root)) continue;
16897
+ for (const entry of fs8.readdirSync(root)) {
16898
+ if (!entry.startsWith(PROJECT_SKILL_PREFIX)) continue;
16899
+ const skillDir = path6.join(root, entry);
16900
+ fs8.rmSync(skillDir, { recursive: true, force: true, maxRetries: 2 });
16901
+ }
16902
+ }
16903
+ }
16904
+ __name(cleanupProjectSkills, "cleanupProjectSkills");
16905
+ function installSkills(options = { type: "base" }) {
16906
+ const skillRoots = options.skillRoots ?? getDefaultSkillRoots();
16907
+ const skillNames = [META_SKILL_NAME, CODE_COMPONENTS_SKILL_NAME];
16908
+ const results = skillNames.map((skillName) => ({
16909
+ skillName,
16910
+ paths: skillRoots.map((root) => installSourceSkill(root, skillName))
16911
+ }));
16912
+ trackSkillsInstall({
16913
+ skillType: options.type,
16914
+ skillCount: results.length
16915
+ });
16916
+ return results;
16917
+ }
16918
+ __name(installSkills, "installSkills");
16919
+
16920
+ // src/generated-project-bundle.ts
16921
+ var PROJECT_BUNDLE_SCHEMA_VERSION = 1;
16922
+ var PROJECT_TEMPLATE_ID = "__template__";
16923
+ var TEMPLATE_FILE_MARKER = ".template";
16924
+ var __dirname2 = path6.dirname(fileURLToPath(import.meta.url));
16925
+ function sha256(content) {
16926
+ return crypto.createHash("sha256").update(content).digest("hex");
16927
+ }
16928
+ __name(sha256, "sha256");
16929
+ function renderPromptFile(section) {
16930
+ return [`# ${section.title}`, "", section.content].join("\n");
16931
+ }
16932
+ __name(renderPromptFile, "renderPromptFile");
16933
+ function getRenderedTemplatePath(filePath) {
16934
+ const extension = path6.extname(filePath);
16935
+ const suffix = `${TEMPLATE_FILE_MARKER}${extension}`;
16936
+ if (!filePath.endsWith(suffix)) return void 0;
16937
+ return filePath.slice(0, -suffix.length) + extension;
16938
+ }
16939
+ __name(getRenderedTemplatePath, "getRenderedTemplatePath");
16940
+ function makeMetadata(options, safeProjectId) {
16941
+ return {
16942
+ projectId: options.projectId,
16943
+ safeProjectId,
16944
+ sessionId: options.sessionId,
16945
+ generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
16946
+ cliVersion: VERSION,
16947
+ promptHash: sha256(options.agentPrompt),
16948
+ contextHash: sha256(options.agentContext),
16949
+ contextSchemaVersion: PROJECT_BUNDLE_SCHEMA_VERSION
16950
+ };
16951
+ }
16952
+ __name(makeMetadata, "makeMetadata");
16953
+ function getInstalledFramerSkillDirs(skillRoots) {
16954
+ return skillRoots.map((root) => path6.join(root, "framer")).filter((skillDir) => fs8.existsSync(path6.join(skillDir, "SKILL.md")));
16955
+ }
16956
+ __name(getInstalledFramerSkillDirs, "getInstalledFramerSkillDirs");
16957
+ function getProjectTemplateDir(skillDir) {
16958
+ const installedTemplateDir = path6.join(
16959
+ skillDir,
16960
+ "projects",
16961
+ PROJECT_TEMPLATE_ID
16962
+ );
16963
+ if (fs8.existsSync(installedTemplateDir)) return installedTemplateDir;
16964
+ return path6.join(
16965
+ __dirname2,
16966
+ "..",
16967
+ "skills",
16968
+ "framer",
16969
+ "projects",
16970
+ PROJECT_TEMPLATE_ID
16971
+ );
16972
+ }
16973
+ __name(getProjectTemplateDir, "getProjectTemplateDir");
16974
+ function cleanupStaleGeneratedProjectBundles(activeProjectIds, skillRoots = getDefaultSkillRoots()) {
16975
+ const activeSafeProjectIds = new Set(activeProjectIds.map(toSafeProjectId));
16976
+ for (const skillDir of getInstalledFramerSkillDirs(skillRoots)) {
16977
+ const projectsDir = path6.join(skillDir, "projects");
16978
+ if (!fs8.existsSync(projectsDir)) continue;
16979
+ for (const entry of fs8.readdirSync(projectsDir, { withFileTypes: true })) {
16980
+ if (!entry.isDirectory()) continue;
16981
+ if (entry.name === PROJECT_TEMPLATE_ID) continue;
16982
+ if (activeSafeProjectIds.has(entry.name)) continue;
16983
+ fs8.rmSync(path6.join(projectsDir, entry.name), {
16984
+ recursive: true,
16985
+ force: true,
16986
+ maxRetries: 2
16987
+ });
16988
+ }
16989
+ }
16990
+ }
16991
+ __name(cleanupStaleGeneratedProjectBundles, "cleanupStaleGeneratedProjectBundles");
16992
+ function makeTemplateValues(metadata, sections, agentContext) {
16993
+ return {
16994
+ PROJECT_ID: metadata.projectId,
16995
+ SAFE_PROJECT_ID: metadata.safeProjectId,
16996
+ SESSION_ID: metadata.sessionId,
16997
+ GENERATED_AT: metadata.generatedAt,
16998
+ PROMPT_SECTION_LIST: sections.map((section) => `- \`prompt/${section.id}.md\` - ${section.title}`).join("\n"),
16999
+ PROJECT_CONTEXT: agentContext,
17000
+ READ_PROJECT_DOCS_COMMAND: replaceDefaultFramerAgentCommand(
17001
+ "npx @framer/agent@latest docs framer.agent.readProject"
17002
+ ),
17003
+ READ_PROJECT_COMMAND: replaceDefaultFramerAgentCommand(
17004
+ `npx @framer/agent@latest read-project -s ${metadata.sessionId} -p "/" -q '<queries-json>'`
17005
+ )
17006
+ };
17007
+ }
17008
+ __name(makeTemplateValues, "makeTemplateValues");
17009
+ function writeGeneratedProjectBundle(options) {
17010
+ const skillRoots = options.skillRoots ?? getDefaultSkillRoots();
17011
+ const framerSkillDirs = getInstalledFramerSkillDirs(skillRoots);
17012
+ if (framerSkillDirs.length === 0) {
17013
+ throw new Error(
17014
+ "Could not find an installed framer skill. Run `@framer/agent setup` before `session new`."
17015
+ );
17016
+ }
17017
+ const safeProjectId = toSafeProjectId(options.projectId);
17018
+ if (safeProjectId === PROJECT_TEMPLATE_ID) {
17019
+ throw new Error(`Project ID resolves to reserved template directory.`);
17020
+ }
17021
+ const renderedPrompt = replaceDefaultFramerAgentCommand(options.agentPrompt);
17022
+ const renderedContext = replaceDefaultFramerAgentCommand(
17023
+ options.agentContext
17024
+ );
17025
+ const sections = splitPromptSections(renderedPrompt);
17026
+ const metadata = makeMetadata(
17027
+ { ...options, agentPrompt: renderedPrompt, agentContext: renderedContext },
17028
+ safeProjectId
17029
+ );
17030
+ const paths = [];
17031
+ for (const skillDir of framerSkillDirs) {
17032
+ const projectDir = path6.join(skillDir, "projects", safeProjectId);
17033
+ const templateDir = getProjectTemplateDir(skillDir);
17034
+ fs8.rmSync(projectDir, { recursive: true, force: true, maxRetries: 2 });
17035
+ fs8.mkdirSync(projectDir, { recursive: true });
17036
+ renderFileTree(templateDir, projectDir, {
17037
+ templateValues: makeTemplateValues(metadata, sections, renderedContext),
17038
+ getRenderedDestinationPath: /* @__PURE__ */ __name(({ sourcePath, destinationPath }) => getRenderedTemplatePath(destinationPath) ?? (isTextFile(sourcePath) ? destinationPath : void 0), "getRenderedDestinationPath"),
17039
+ getSymlinkErrorMessage: /* @__PURE__ */ __name((sourcePath) => `Project template contains unsupported symlink: ${sourcePath}`, "getSymlinkErrorMessage"),
17040
+ transformRenderedContent: replaceDefaultFramerAgentCommand
17041
+ });
17042
+ for (const section of sections) {
17043
+ writeRenderedFile(
17044
+ path6.join(projectDir, "prompt", `${section.id}.md`),
17045
+ renderPromptFile(section)
17046
+ );
17047
+ }
17048
+ writeRenderedFile(
17049
+ path6.join(projectDir, "metadata.json"),
17050
+ JSON.stringify(metadata, null, " ")
17051
+ );
17052
+ paths.push(projectDir);
17053
+ }
17054
+ return { safeProjectId, paths };
17055
+ }
17056
+ __name(writeGeneratedProjectBundle, "writeGeneratedProjectBundle");
17057
+
16621
17058
  // src/headless-server-url.ts
16622
17059
  var PROD_HEADLESS_SERVER_URL = "wss://api.framer.com/channel/headless-plugin";
16623
17060
  var DEV_HEADLESS_SERVER_URL = "wss://api.development.framer.com/channel/headless-plugin";
@@ -16669,7 +17106,7 @@ function inferHeadlessServerUrl(projectUrlOrId) {
16669
17106
  }
16670
17107
  __name(inferHeadlessServerUrl, "inferHeadlessServerUrl");
16671
17108
  function getRelayTokenPath() {
16672
- return path7.join(getConfigDir(), "relay-token");
17109
+ return path6.join(getConfigDir(), "relay-token");
16673
17110
  }
16674
17111
  __name(getRelayTokenPath, "getRelayTokenPath");
16675
17112
  function readRelayToken() {
@@ -16684,7 +17121,7 @@ __name(readRelayToken, "readRelayToken");
16684
17121
 
16685
17122
  // src/relay-client.ts
16686
17123
  var __filename$1 = fileURLToPath(import.meta.url);
16687
- var __dirname$1 = path7.dirname(__filename$1);
17124
+ var __dirname3 = path6.dirname(__filename$1);
16688
17125
  var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
16689
17126
  var WAIT_TIMEOUT_SECONDS = 5;
16690
17127
  var client = createTRPCClient({
@@ -16809,7 +17246,7 @@ async function ensureRelayServerRunning(options = {}) {
16809
17246
  logger?.log("Relay server not running, starting it...");
16810
17247
  }
16811
17248
  const isRunningFromSource = __filename$1.endsWith(".ts");
16812
- const scriptPath = isRunningFromSource ? path7.resolve(__dirname$1, "./start-relay-server.ts") : path7.resolve(__dirname$1, "./start-relay-server.js");
17249
+ const scriptPath = isRunningFromSource ? path6.resolve(__dirname3, "./start-relay-server.ts") : path6.resolve(__dirname3, "./start-relay-server.js");
16813
17250
  debug("relay", `spawning server process: ${scriptPath}`);
16814
17251
  const serverProcess = spawn(
16815
17252
  isRunningFromSource ? "tsx" : process.execPath,
@@ -16846,171 +17283,6 @@ async function waitForClaudeCodeSkillDiscovery() {
16846
17283
  );
16847
17284
  }
16848
17285
  __name(waitForClaudeCodeSkillDiscovery, "waitForClaudeCodeSkillDiscovery");
16849
- var FRAMER_TEMPORARY_DIR = path7.join(os2.tmpdir(), "framer");
16850
- function ensureTemporaryDir() {
16851
- fs8.mkdirSync(FRAMER_TEMPORARY_DIR, { recursive: true });
16852
- }
16853
- __name(ensureTemporaryDir, "ensureTemporaryDir");
16854
- function cleanupStaleSessionCodeFiles(activeSessionIds) {
16855
- if (!fs8.existsSync(FRAMER_TEMPORARY_DIR)) return;
16856
- const activeSessionIdsSet = new Set(activeSessionIds);
16857
- for (const entry of fs8.readdirSync(FRAMER_TEMPORARY_DIR)) {
16858
- const [sessionId] = entry.split("-");
16859
- if (activeSessionIdsSet.has(sessionId)) continue;
16860
- const filePath = path7.join(FRAMER_TEMPORARY_DIR, entry);
16861
- fs8.rmSync(filePath, { recursive: true, force: true, maxRetries: 2 });
16862
- }
16863
- }
16864
- __name(cleanupStaleSessionCodeFiles, "cleanupStaleSessionCodeFiles");
16865
-
16866
- // src/skills.ts
16867
- var META_SKILL_NAME = "framer";
16868
- var CODE_COMPONENTS_SKILL_NAME = "framer-code-components";
16869
- var __dirname2 = path7.dirname(fileURLToPath(import.meta.url));
16870
- var skillsDocsDir = path7.join(__dirname2, "..", "docs", "skills");
16871
- function readSkillDoc(name2) {
16872
- return fs8.readFileSync(path7.join(skillsDocsDir, name2), "utf-8").trimEnd();
16873
- }
16874
- __name(readSkillDoc, "readSkillDoc");
16875
- function buildMetaSkill() {
16876
- const command = getFramerAgentCommand();
16877
- const template = readSkillDoc("framer.md");
16878
- return `${renderTemplate(template, {
16879
- FRAMER_TEMPORARY_DIR,
16880
- FRAMER_AGENT_COMMAND: command
16881
- })}
16882
- `;
16883
- }
16884
- __name(buildMetaSkill, "buildMetaSkill");
16885
- function buildCodeComponentsSkill() {
16886
- return `${readSkillDoc("framer-code-components.md")}
16887
- `;
16888
- }
16889
- __name(buildCodeComponentsSkill, "buildCodeComponentsSkill");
16890
- function renderTemplate(template, values) {
16891
- let rendered = template;
16892
- for (const [key, value] of Object.entries(values)) {
16893
- rendered = rendered.split(`{{${key}}}`).join(value);
16894
- }
16895
- return rendered;
16896
- }
16897
- __name(renderTemplate, "renderTemplate");
16898
- var PROJECT_SKILL_PREFIX = "framer-project-";
16899
- var LEGACY_CANVAS_EDITING_SKILL_PREFIX = "framer-canvas-editing-project-";
16900
- var PACKAGE_NAME = "@framer/agent";
16901
- var DEFAULT_FRAMER_AGENT_COMMAND = `npx ${PACKAGE_NAME}`;
16902
- var RELEASE_AGE_OVERRIDE_FRAMER_AGENT_COMMAND = `npx --min-release-age=0 ${PACKAGE_NAME}`;
16903
- function getFramerAgentCommand() {
16904
- return isNpxMinReleaseAgeOverrideEnabled() ? RELEASE_AGE_OVERRIDE_FRAMER_AGENT_COMMAND : DEFAULT_FRAMER_AGENT_COMMAND;
16905
- }
16906
- __name(getFramerAgentCommand, "getFramerAgentCommand");
16907
- function toSafeProjectId(projectId) {
16908
- return projectId.replace(/[^a-zA-Z0-9_-]/g, "-");
16909
- }
16910
- __name(toSafeProjectId, "toSafeProjectId");
16911
- function buildProjectSkill(projectId, agentContext, agentPrompt) {
16912
- const command = getFramerAgentCommand();
16913
- const skillName = `${PROJECT_SKILL_PREFIX}${toSafeProjectId(projectId)}`;
16914
- const template = readSkillDoc("framer-project.md");
16915
- const content = `${renderTemplate(template, {
16916
- SKILL_NAME: skillName,
16917
- PROJECT_ID: projectId,
16918
- GENERATED_AT: (/* @__PURE__ */ new Date()).toISOString(),
16919
- AGENT_PROMPT: agentPrompt.trimEnd().replaceAll(DEFAULT_FRAMER_AGENT_COMMAND, command),
16920
- AGENT_CONTEXT: agentContext.trimEnd().replaceAll(DEFAULT_FRAMER_AGENT_COMMAND, command),
16921
- FRAMER_TEMPORARY_DIR,
16922
- FRAMER_AGENT_COMMAND: command
16923
- })}
16924
- `;
16925
- return { skillName, content };
16926
- }
16927
- __name(buildProjectSkill, "buildProjectSkill");
16928
- function writeSkill(root, skillName, content) {
16929
- fs8.mkdirSync(root, { recursive: true });
16930
- const rootStat = fs8.statSync(root);
16931
- if (!rootStat.isDirectory()) {
16932
- throw new Error(`Skill root is not a directory: ${root}`);
16933
- }
16934
- const skillDir = path7.join(root, skillName);
16935
- const filePath = path7.join(skillDir, "SKILL.md");
16936
- const current = fs8.lstatSync(skillDir, { throwIfNoEntry: false });
16937
- if (current && (current.isSymbolicLink() || !current.isDirectory())) {
16938
- fs8.rmSync(skillDir, { recursive: true, force: true });
16939
- }
16940
- fs8.mkdirSync(skillDir, { recursive: true });
16941
- fs8.writeFileSync(filePath, content, "utf-8");
16942
- return filePath;
16943
- }
16944
- __name(writeSkill, "writeSkill");
16945
- function cleanupLegacyCanvasEditingSkills(skillRoots = getDefaultSkillRoots()) {
16946
- for (const root of skillRoots) {
16947
- if (!fs8.existsSync(root)) continue;
16948
- for (const entry of fs8.readdirSync(root)) {
16949
- if (!entry.startsWith(LEGACY_CANVAS_EDITING_SKILL_PREFIX)) continue;
16950
- const skillDir = path7.join(root, entry);
16951
- fs8.rmSync(skillDir, { recursive: true, force: true, maxRetries: 2 });
16952
- }
16953
- }
16954
- }
16955
- __name(cleanupLegacyCanvasEditingSkills, "cleanupLegacyCanvasEditingSkills");
16956
- function getDefaultSkillRoots() {
16957
- const home = os2.homedir();
16958
- return [
16959
- path7.join(home, ".agents", "skills"),
16960
- path7.join(home, ".claude", "skills")
16961
- ];
16962
- }
16963
- __name(getDefaultSkillRoots, "getDefaultSkillRoots");
16964
- function cleanupStaleSkills(activeProjectIds, skillRoots = getDefaultSkillRoots()) {
16965
- const sanitizedActiveProjectIds = new Set(
16966
- [...activeProjectIds].map(toSafeProjectId)
16967
- );
16968
- for (const root of skillRoots) {
16969
- if (!fs8.existsSync(root)) continue;
16970
- for (const entry of fs8.readdirSync(root)) {
16971
- if (!entry.startsWith(PROJECT_SKILL_PREFIX)) continue;
16972
- const sanitizedProjectId = entry.slice(PROJECT_SKILL_PREFIX.length);
16973
- const isActive = sanitizedActiveProjectIds.has(sanitizedProjectId);
16974
- if (isActive) continue;
16975
- const skillDir = path7.join(root, entry);
16976
- fs8.rmSync(skillDir, { recursive: true, force: true, maxRetries: 2 });
16977
- }
16978
- }
16979
- }
16980
- __name(cleanupStaleSkills, "cleanupStaleSkills");
16981
- function installSkills(options = { type: "base" }) {
16982
- const skillRoots = options.skillRoots ?? getDefaultSkillRoots();
16983
- const skills = [
16984
- { name: META_SKILL_NAME, content: buildMetaSkill() },
16985
- {
16986
- name: CODE_COMPONENTS_SKILL_NAME,
16987
- content: buildCodeComponentsSkill()
16988
- }
16989
- ];
16990
- if (options.type === "project") {
16991
- const projectSkill = buildProjectSkill(
16992
- options.projectId,
16993
- options.agentContext,
16994
- options.agentPrompt
16995
- );
16996
- skills.push({
16997
- name: projectSkill.skillName,
16998
- content: projectSkill.content
16999
- });
17000
- }
17001
- const results = skills.map((skill) => ({
17002
- skillName: skill.name,
17003
- paths: skillRoots.map(
17004
- (root) => writeSkill(root, skill.name, skill.content)
17005
- )
17006
- }));
17007
- trackSkillsInstall({
17008
- skillType: options.type,
17009
- skillCount: results.length
17010
- });
17011
- return results;
17012
- }
17013
- __name(installSkills, "installSkills");
17014
17286
 
17015
17287
  // src/cli.ts
17016
17288
  var PROGRAM_NAME = "@framer/agent";
@@ -17038,8 +17310,8 @@ function printSetupSummary(results) {
17038
17310
  const installLocations = /* @__PURE__ */ new Set();
17039
17311
  for (const result of results) {
17040
17312
  for (const filePath of result.paths) {
17041
- const skillDir = path7.dirname(filePath);
17042
- const root = path7.dirname(skillDir);
17313
+ const skillDir = path6.dirname(filePath);
17314
+ const root = path6.dirname(skillDir);
17043
17315
  installLocations.add(root);
17044
17316
  }
17045
17317
  }
@@ -17067,6 +17339,13 @@ async function installBaseSkills() {
17067
17339
  `Failed to clean up legacy canvas editing skills: ${formatErrorForUser(error)}`
17068
17340
  );
17069
17341
  }
17342
+ try {
17343
+ cleanupProjectSkills();
17344
+ } catch (error) {
17345
+ printWarning(
17346
+ `Failed to clean up generated project skills: ${formatErrorForUser(error)}`
17347
+ );
17348
+ }
17070
17349
  const results = installSkills();
17071
17350
  await waitForClaudeCodeSkillDiscovery();
17072
17351
  return results;
@@ -17104,29 +17383,30 @@ async function getAgentContext(sessionId) {
17104
17383
  return context;
17105
17384
  }
17106
17385
  __name(getAgentContext, "getAgentContext");
17107
- async function refreshSkillsFromSession(sessionId, projectId) {
17386
+ async function refreshGeneratedProjectBundleFromSession(sessionId, projectId) {
17108
17387
  try {
17109
17388
  debug("skills", "fetching agent system prompt + context...");
17110
17389
  const [agentPrompt, agentContext] = await Promise.all([
17111
17390
  getAgentSystemPrompt(sessionId),
17112
17391
  getAgentContext(sessionId)
17113
17392
  ]);
17114
- debug("skills", "installing skills...");
17115
- installSkills({
17116
- type: "project",
17393
+ debug("skills", "writing generated project bundle...");
17394
+ writeGeneratedProjectBundle({
17117
17395
  agentPrompt,
17118
17396
  projectId,
17397
+ sessionId,
17119
17398
  agentContext
17120
17399
  });
17121
- debug("skills", "skills installed");
17400
+ debug("skills", "generated project bundle written");
17401
+ await waitForClaudeCodeSkillDiscovery();
17122
17402
  } catch (err) {
17123
17403
  throw errorWithRef(
17124
- `Failed to refresh skills for session ${sessionId}: ${formatError(err)}`,
17404
+ `Failed to refresh generated project bundle for session ${sessionId}: ${formatError(err)}`,
17125
17405
  getErrorRef(err)
17126
17406
  );
17127
17407
  }
17128
17408
  }
17129
- __name(refreshSkillsFromSession, "refreshSkillsFromSession");
17409
+ __name(refreshGeneratedProjectBundleFromSession, "refreshGeneratedProjectBundleFromSession");
17130
17410
  async function resolveSessionCredentials(projectUrlOrId) {
17131
17411
  const projectId = extractProjectId(projectUrlOrId);
17132
17412
  try {
@@ -17315,13 +17595,18 @@ session.command("new <projectUrlOrId>").description("Create a new session and pr
17315
17595
  });
17316
17596
  sessionId = result.id;
17317
17597
  debug("session.new", `session created id=${sessionId}`);
17318
- debug("session.new", "fetching project name + refreshing skills...");
17598
+ debug(
17599
+ "session.new",
17600
+ "fetching project name + refreshing generated bundle..."
17601
+ );
17319
17602
  const [projectName] = await Promise.all([
17320
17603
  getProjectName(sessionId),
17321
- refreshSkillsFromSession(sessionId, projectId)
17604
+ refreshGeneratedProjectBundleFromSession(sessionId, projectId)
17322
17605
  ]);
17323
- debug("session.new", `project name="${projectName}", skills refreshed`);
17324
- await waitForClaudeCodeSkillDiscovery();
17606
+ debug(
17607
+ "session.new",
17608
+ `project name="${projectName}", generated files written`
17609
+ );
17325
17610
  saveProject({
17326
17611
  projectId,
17327
17612
  apiKey: credentials.apiKey,
@@ -17335,10 +17620,10 @@ session.command("new <projectUrlOrId>").description("Create a new session and pr
17335
17620
  (activeSession) => activeSession.projectId
17336
17621
  );
17337
17622
  try {
17338
- cleanupStaleSkills(activeProjectIds);
17623
+ cleanupStaleGeneratedProjectBundles(activeProjectIds);
17339
17624
  } catch (error) {
17340
17625
  printWarning(
17341
- `Failed to clean up stale skills: ${formatErrorForUser(error)}`
17626
+ `Failed to clean up stale generated project bundles: ${formatErrorForUser(error)}`
17342
17627
  );
17343
17628
  }
17344
17629
  const activeSessionIds = activeSessions.map(