@framer/agent 0.0.35 → 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.35 */
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
 
@@ -85,20 +85,19 @@ function resolveAgentThreadContext(env = process.env) {
85
85
  return {};
86
86
  }
87
87
  __name(resolveAgentThreadContext, "resolveAgentThreadContext");
88
- function openUrl(url) {
88
+ function getOpenUrlCommand(url) {
89
89
  const platform = process.platform;
90
- let cmd;
91
- let args;
92
90
  if (platform === "darwin") {
93
- cmd = "open";
94
- args = [url];
95
- } else if (platform === "win32") {
96
- cmd = "cmd";
97
- args = ["/c", "start", "", `"${url}"`];
98
- } else {
99
- cmd = "xdg-open";
100
- args = [url];
91
+ return { cmd: "open", args: [url] };
92
+ }
93
+ if (platform === "win32") {
94
+ return { cmd: "rundll32", args: ["url.dll,FileProtocolHandler", url] };
101
95
  }
96
+ return { cmd: "xdg-open", args: [url] };
97
+ }
98
+ __name(getOpenUrlCommand, "getOpenUrlCommand");
99
+ function openUrl(url) {
100
+ const { cmd, args } = getOpenUrlCommand(url);
102
101
  return new Promise((resolve) => {
103
102
  const child = execFile(cmd, args);
104
103
  child.on("error", () => resolve(false));
@@ -108,12 +107,12 @@ function openUrl(url) {
108
107
  __name(openUrl, "openUrl");
109
108
  function getConfigDir() {
110
109
  if (process.env.XDG_CONFIG_HOME) {
111
- return path7.join(process.env.XDG_CONFIG_HOME, "framer");
110
+ return path6.join(process.env.XDG_CONFIG_HOME, "framer");
112
111
  }
113
112
  if (process.platform === "win32") {
114
- return path7.join(process.env.APPDATA || os2.homedir(), "framer");
113
+ return path6.join(process.env.APPDATA || os2.homedir(), "framer");
115
114
  }
116
- return path7.join(os2.homedir(), ".config", "framer");
115
+ return path6.join(os2.homedir(), ".config", "framer");
117
116
  }
118
117
  __name(getConfigDir, "getConfigDir");
119
118
  function ensureConfigDir() {
@@ -124,11 +123,11 @@ __name(ensureConfigDir, "ensureConfigDir");
124
123
 
125
124
  // src/config/projects.ts
126
125
  function getProjectsConfigPath() {
127
- return path7.join(getConfigDir(), "projects.json");
126
+ return path6.join(getConfigDir(), "projects.json");
128
127
  }
129
128
  __name(getProjectsConfigPath, "getProjectsConfigPath");
130
129
  function getLegacyCredentialsPath() {
131
- return path7.join(getConfigDir(), "credentials.json");
130
+ return path6.join(getConfigDir(), "credentials.json");
132
131
  }
133
132
  __name(getLegacyCredentialsPath, "getLegacyCredentialsPath");
134
133
  var ProjectsConfigSchema = z.object({
@@ -314,7 +313,7 @@ var SettingsFileSchema = z.object({
314
313
  telemetryNoticeShown: z.boolean().optional()
315
314
  });
316
315
  function getSettingsPath() {
317
- return path7.join(getConfigDir(), "settings.json");
316
+ return path6.join(getConfigDir(), "settings.json");
318
317
  }
319
318
  __name(getSettingsPath, "getSettingsPath");
320
319
  var settings;
@@ -398,7 +397,7 @@ __name(markTelemetryNoticeShown, "markTelemetryNoticeShown");
398
397
  // src/version.ts
399
398
  var VERSION = (
400
399
  // typeof is used to ensure this can be used just via tsx or node etc. without build
401
- "0.0.35"
400
+ "0.0.37"
402
401
  );
403
402
  var trackingEndpoint = "https://events.framer.com/track";
404
403
  var inProgressTrackings = /* @__PURE__ */ new Set();
@@ -1175,7 +1174,7 @@ var types = {
1175
1174
  members: [
1176
1175
  {
1177
1176
  name: "type",
1178
- 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"',
1179
1178
  description: "",
1180
1179
  optional: false
1181
1180
  },
@@ -7628,6 +7627,12 @@ var types = {
7628
7627
  description: "@alpha",
7629
7628
  optional: false
7630
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
+ },
7631
7636
  {
7632
7637
  name: "ping",
7633
7638
  type: "() => Promise<void>",
@@ -7682,6 +7687,24 @@ var types = {
7682
7687
  description: "@alpha",
7683
7688
  optional: false
7684
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
+ },
7685
7708
  {
7686
7709
  name: "getScopeNodeForAgent",
7687
7710
  type: "(input: {\n id: string;\n }, options?: {\n pagePath?: string;\n }) => Promise<unknown>",
@@ -14249,6 +14272,20 @@ var methodsByCategory = {
14249
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.",
14250
14273
  references: []
14251
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
+ },
14252
14289
  {
14253
14290
  name: "getGroundNode",
14254
14291
  category: "FramerAgentAPI",
@@ -14284,6 +14321,13 @@ var methodsByCategory = {
14284
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.',
14285
14322
  references: []
14286
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
+ },
14287
14331
  {
14288
14332
  name: "getScopeNode",
14289
14333
  category: "FramerAgentAPI",
@@ -14396,6 +14440,13 @@ var methodsByCategory = {
14396
14440
  description: "Renames a branch.\n\n@param branchId - Branch id to rename.\n@param title - New branch title.",
14397
14441
  references: []
14398
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
+ },
14399
14450
  {
14400
14451
  name: "reviewChanges",
14401
14452
  category: "FramerAgentAPI",
@@ -16619,6 +16670,391 @@ ${typeDef}`);
16619
16670
  }
16620
16671
  __name(renderDocs, "renderDocs");
16621
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
+
16622
17058
  // src/headless-server-url.ts
16623
17059
  var PROD_HEADLESS_SERVER_URL = "wss://api.framer.com/channel/headless-plugin";
16624
17060
  var DEV_HEADLESS_SERVER_URL = "wss://api.development.framer.com/channel/headless-plugin";
@@ -16670,7 +17106,7 @@ function inferHeadlessServerUrl(projectUrlOrId) {
16670
17106
  }
16671
17107
  __name(inferHeadlessServerUrl, "inferHeadlessServerUrl");
16672
17108
  function getRelayTokenPath() {
16673
- return path7.join(getConfigDir(), "relay-token");
17109
+ return path6.join(getConfigDir(), "relay-token");
16674
17110
  }
16675
17111
  __name(getRelayTokenPath, "getRelayTokenPath");
16676
17112
  function readRelayToken() {
@@ -16685,7 +17121,7 @@ __name(readRelayToken, "readRelayToken");
16685
17121
 
16686
17122
  // src/relay-client.ts
16687
17123
  var __filename$1 = fileURLToPath(import.meta.url);
16688
- var __dirname$1 = path7.dirname(__filename$1);
17124
+ var __dirname3 = path6.dirname(__filename$1);
16689
17125
  var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
16690
17126
  var WAIT_TIMEOUT_SECONDS = 5;
16691
17127
  var client = createTRPCClient({
@@ -16810,7 +17246,7 @@ async function ensureRelayServerRunning(options = {}) {
16810
17246
  logger?.log("Relay server not running, starting it...");
16811
17247
  }
16812
17248
  const isRunningFromSource = __filename$1.endsWith(".ts");
16813
- 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");
16814
17250
  debug("relay", `spawning server process: ${scriptPath}`);
16815
17251
  const serverProcess = spawn(
16816
17252
  isRunningFromSource ? "tsx" : process.execPath,
@@ -16847,171 +17283,6 @@ async function waitForClaudeCodeSkillDiscovery() {
16847
17283
  );
16848
17284
  }
16849
17285
  __name(waitForClaudeCodeSkillDiscovery, "waitForClaudeCodeSkillDiscovery");
16850
- var FRAMER_TEMPORARY_DIR = path7.join(os2.tmpdir(), "framer");
16851
- function ensureTemporaryDir() {
16852
- fs8.mkdirSync(FRAMER_TEMPORARY_DIR, { recursive: true });
16853
- }
16854
- __name(ensureTemporaryDir, "ensureTemporaryDir");
16855
- function cleanupStaleSessionCodeFiles(activeSessionIds) {
16856
- if (!fs8.existsSync(FRAMER_TEMPORARY_DIR)) return;
16857
- const activeSessionIdsSet = new Set(activeSessionIds);
16858
- for (const entry of fs8.readdirSync(FRAMER_TEMPORARY_DIR)) {
16859
- const [sessionId] = entry.split("-");
16860
- if (activeSessionIdsSet.has(sessionId)) continue;
16861
- const filePath = path7.join(FRAMER_TEMPORARY_DIR, entry);
16862
- fs8.rmSync(filePath, { recursive: true, force: true, maxRetries: 2 });
16863
- }
16864
- }
16865
- __name(cleanupStaleSessionCodeFiles, "cleanupStaleSessionCodeFiles");
16866
-
16867
- // src/skills.ts
16868
- var META_SKILL_NAME = "framer";
16869
- var CODE_COMPONENTS_SKILL_NAME = "framer-code-components";
16870
- var __dirname2 = path7.dirname(fileURLToPath(import.meta.url));
16871
- var skillsDocsDir = path7.join(__dirname2, "..", "docs", "skills");
16872
- function readSkillDoc(name2) {
16873
- return fs8.readFileSync(path7.join(skillsDocsDir, name2), "utf-8").trimEnd();
16874
- }
16875
- __name(readSkillDoc, "readSkillDoc");
16876
- function buildMetaSkill() {
16877
- const command = getFramerAgentCommand();
16878
- const template = readSkillDoc("framer.md");
16879
- return `${renderTemplate(template, {
16880
- FRAMER_TEMPORARY_DIR,
16881
- FRAMER_AGENT_COMMAND: command
16882
- })}
16883
- `;
16884
- }
16885
- __name(buildMetaSkill, "buildMetaSkill");
16886
- function buildCodeComponentsSkill() {
16887
- return `${readSkillDoc("framer-code-components.md")}
16888
- `;
16889
- }
16890
- __name(buildCodeComponentsSkill, "buildCodeComponentsSkill");
16891
- function renderTemplate(template, values) {
16892
- let rendered = template;
16893
- for (const [key, value] of Object.entries(values)) {
16894
- rendered = rendered.split(`{{${key}}}`).join(value);
16895
- }
16896
- return rendered;
16897
- }
16898
- __name(renderTemplate, "renderTemplate");
16899
- var PROJECT_SKILL_PREFIX = "framer-project-";
16900
- var LEGACY_CANVAS_EDITING_SKILL_PREFIX = "framer-canvas-editing-project-";
16901
- var PACKAGE_NAME = "@framer/agent";
16902
- var DEFAULT_FRAMER_AGENT_COMMAND = `npx ${PACKAGE_NAME}`;
16903
- var RELEASE_AGE_OVERRIDE_FRAMER_AGENT_COMMAND = `npx --min-release-age=0 ${PACKAGE_NAME}`;
16904
- function getFramerAgentCommand() {
16905
- return isNpxMinReleaseAgeOverrideEnabled() ? RELEASE_AGE_OVERRIDE_FRAMER_AGENT_COMMAND : DEFAULT_FRAMER_AGENT_COMMAND;
16906
- }
16907
- __name(getFramerAgentCommand, "getFramerAgentCommand");
16908
- function toSafeProjectId(projectId) {
16909
- return projectId.replace(/[^a-zA-Z0-9_-]/g, "-");
16910
- }
16911
- __name(toSafeProjectId, "toSafeProjectId");
16912
- function buildProjectSkill(projectId, agentContext, agentPrompt) {
16913
- const command = getFramerAgentCommand();
16914
- const skillName = `${PROJECT_SKILL_PREFIX}${toSafeProjectId(projectId)}`;
16915
- const template = readSkillDoc("framer-project.md");
16916
- const content = `${renderTemplate(template, {
16917
- SKILL_NAME: skillName,
16918
- PROJECT_ID: projectId,
16919
- GENERATED_AT: (/* @__PURE__ */ new Date()).toISOString(),
16920
- AGENT_PROMPT: agentPrompt.trimEnd().replaceAll(DEFAULT_FRAMER_AGENT_COMMAND, command),
16921
- AGENT_CONTEXT: agentContext.trimEnd().replaceAll(DEFAULT_FRAMER_AGENT_COMMAND, command),
16922
- FRAMER_TEMPORARY_DIR,
16923
- FRAMER_AGENT_COMMAND: command
16924
- })}
16925
- `;
16926
- return { skillName, content };
16927
- }
16928
- __name(buildProjectSkill, "buildProjectSkill");
16929
- function writeSkill(root, skillName, content) {
16930
- fs8.mkdirSync(root, { recursive: true });
16931
- const rootStat = fs8.statSync(root);
16932
- if (!rootStat.isDirectory()) {
16933
- throw new Error(`Skill root is not a directory: ${root}`);
16934
- }
16935
- const skillDir = path7.join(root, skillName);
16936
- const filePath = path7.join(skillDir, "SKILL.md");
16937
- const current = fs8.lstatSync(skillDir, { throwIfNoEntry: false });
16938
- if (current && (current.isSymbolicLink() || !current.isDirectory())) {
16939
- fs8.rmSync(skillDir, { recursive: true, force: true });
16940
- }
16941
- fs8.mkdirSync(skillDir, { recursive: true });
16942
- fs8.writeFileSync(filePath, content, "utf-8");
16943
- return filePath;
16944
- }
16945
- __name(writeSkill, "writeSkill");
16946
- function cleanupLegacyCanvasEditingSkills(skillRoots = getDefaultSkillRoots()) {
16947
- for (const root of skillRoots) {
16948
- if (!fs8.existsSync(root)) continue;
16949
- for (const entry of fs8.readdirSync(root)) {
16950
- if (!entry.startsWith(LEGACY_CANVAS_EDITING_SKILL_PREFIX)) continue;
16951
- const skillDir = path7.join(root, entry);
16952
- fs8.rmSync(skillDir, { recursive: true, force: true, maxRetries: 2 });
16953
- }
16954
- }
16955
- }
16956
- __name(cleanupLegacyCanvasEditingSkills, "cleanupLegacyCanvasEditingSkills");
16957
- function getDefaultSkillRoots() {
16958
- const home = os2.homedir();
16959
- return [
16960
- path7.join(home, ".agents", "skills"),
16961
- path7.join(home, ".claude", "skills")
16962
- ];
16963
- }
16964
- __name(getDefaultSkillRoots, "getDefaultSkillRoots");
16965
- function cleanupStaleSkills(activeProjectIds, skillRoots = getDefaultSkillRoots()) {
16966
- const sanitizedActiveProjectIds = new Set(
16967
- [...activeProjectIds].map(toSafeProjectId)
16968
- );
16969
- for (const root of skillRoots) {
16970
- if (!fs8.existsSync(root)) continue;
16971
- for (const entry of fs8.readdirSync(root)) {
16972
- if (!entry.startsWith(PROJECT_SKILL_PREFIX)) continue;
16973
- const sanitizedProjectId = entry.slice(PROJECT_SKILL_PREFIX.length);
16974
- const isActive = sanitizedActiveProjectIds.has(sanitizedProjectId);
16975
- if (isActive) continue;
16976
- const skillDir = path7.join(root, entry);
16977
- fs8.rmSync(skillDir, { recursive: true, force: true, maxRetries: 2 });
16978
- }
16979
- }
16980
- }
16981
- __name(cleanupStaleSkills, "cleanupStaleSkills");
16982
- function installSkills(options = { type: "base" }) {
16983
- const skillRoots = options.skillRoots ?? getDefaultSkillRoots();
16984
- const skills = [
16985
- { name: META_SKILL_NAME, content: buildMetaSkill() },
16986
- {
16987
- name: CODE_COMPONENTS_SKILL_NAME,
16988
- content: buildCodeComponentsSkill()
16989
- }
16990
- ];
16991
- if (options.type === "project") {
16992
- const projectSkill = buildProjectSkill(
16993
- options.projectId,
16994
- options.agentContext,
16995
- options.agentPrompt
16996
- );
16997
- skills.push({
16998
- name: projectSkill.skillName,
16999
- content: projectSkill.content
17000
- });
17001
- }
17002
- const results = skills.map((skill) => ({
17003
- skillName: skill.name,
17004
- paths: skillRoots.map(
17005
- (root) => writeSkill(root, skill.name, skill.content)
17006
- )
17007
- }));
17008
- trackSkillsInstall({
17009
- skillType: options.type,
17010
- skillCount: results.length
17011
- });
17012
- return results;
17013
- }
17014
- __name(installSkills, "installSkills");
17015
17286
 
17016
17287
  // src/cli.ts
17017
17288
  var PROGRAM_NAME = "@framer/agent";
@@ -17039,8 +17310,8 @@ function printSetupSummary(results) {
17039
17310
  const installLocations = /* @__PURE__ */ new Set();
17040
17311
  for (const result of results) {
17041
17312
  for (const filePath of result.paths) {
17042
- const skillDir = path7.dirname(filePath);
17043
- const root = path7.dirname(skillDir);
17313
+ const skillDir = path6.dirname(filePath);
17314
+ const root = path6.dirname(skillDir);
17044
17315
  installLocations.add(root);
17045
17316
  }
17046
17317
  }
@@ -17068,6 +17339,13 @@ async function installBaseSkills() {
17068
17339
  `Failed to clean up legacy canvas editing skills: ${formatErrorForUser(error)}`
17069
17340
  );
17070
17341
  }
17342
+ try {
17343
+ cleanupProjectSkills();
17344
+ } catch (error) {
17345
+ printWarning(
17346
+ `Failed to clean up generated project skills: ${formatErrorForUser(error)}`
17347
+ );
17348
+ }
17071
17349
  const results = installSkills();
17072
17350
  await waitForClaudeCodeSkillDiscovery();
17073
17351
  return results;
@@ -17105,29 +17383,30 @@ async function getAgentContext(sessionId) {
17105
17383
  return context;
17106
17384
  }
17107
17385
  __name(getAgentContext, "getAgentContext");
17108
- async function refreshSkillsFromSession(sessionId, projectId) {
17386
+ async function refreshGeneratedProjectBundleFromSession(sessionId, projectId) {
17109
17387
  try {
17110
17388
  debug("skills", "fetching agent system prompt + context...");
17111
17389
  const [agentPrompt, agentContext] = await Promise.all([
17112
17390
  getAgentSystemPrompt(sessionId),
17113
17391
  getAgentContext(sessionId)
17114
17392
  ]);
17115
- debug("skills", "installing skills...");
17116
- installSkills({
17117
- type: "project",
17393
+ debug("skills", "writing generated project bundle...");
17394
+ writeGeneratedProjectBundle({
17118
17395
  agentPrompt,
17119
17396
  projectId,
17397
+ sessionId,
17120
17398
  agentContext
17121
17399
  });
17122
- debug("skills", "skills installed");
17400
+ debug("skills", "generated project bundle written");
17401
+ await waitForClaudeCodeSkillDiscovery();
17123
17402
  } catch (err) {
17124
17403
  throw errorWithRef(
17125
- `Failed to refresh skills for session ${sessionId}: ${formatError(err)}`,
17404
+ `Failed to refresh generated project bundle for session ${sessionId}: ${formatError(err)}`,
17126
17405
  getErrorRef(err)
17127
17406
  );
17128
17407
  }
17129
17408
  }
17130
- __name(refreshSkillsFromSession, "refreshSkillsFromSession");
17409
+ __name(refreshGeneratedProjectBundleFromSession, "refreshGeneratedProjectBundleFromSession");
17131
17410
  async function resolveSessionCredentials(projectUrlOrId) {
17132
17411
  const projectId = extractProjectId(projectUrlOrId);
17133
17412
  try {
@@ -17316,13 +17595,18 @@ session.command("new <projectUrlOrId>").description("Create a new session and pr
17316
17595
  });
17317
17596
  sessionId = result.id;
17318
17597
  debug("session.new", `session created id=${sessionId}`);
17319
- debug("session.new", "fetching project name + refreshing skills...");
17598
+ debug(
17599
+ "session.new",
17600
+ "fetching project name + refreshing generated bundle..."
17601
+ );
17320
17602
  const [projectName] = await Promise.all([
17321
17603
  getProjectName(sessionId),
17322
- refreshSkillsFromSession(sessionId, projectId)
17604
+ refreshGeneratedProjectBundleFromSession(sessionId, projectId)
17323
17605
  ]);
17324
- debug("session.new", `project name="${projectName}", skills refreshed`);
17325
- await waitForClaudeCodeSkillDiscovery();
17606
+ debug(
17607
+ "session.new",
17608
+ `project name="${projectName}", generated files written`
17609
+ );
17326
17610
  saveProject({
17327
17611
  projectId,
17328
17612
  apiKey: credentials.apiKey,
@@ -17336,10 +17620,10 @@ session.command("new <projectUrlOrId>").description("Create a new session and pr
17336
17620
  (activeSession) => activeSession.projectId
17337
17621
  );
17338
17622
  try {
17339
- cleanupStaleSkills(activeProjectIds);
17623
+ cleanupStaleGeneratedProjectBundles(activeProjectIds);
17340
17624
  } catch (error) {
17341
17625
  printWarning(
17342
- `Failed to clean up stale skills: ${formatErrorForUser(error)}`
17626
+ `Failed to clean up stale generated project bundles: ${formatErrorForUser(error)}`
17343
17627
  );
17344
17628
  }
17345
17629
  const activeSessionIds = activeSessions.map(