framer-dalton 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -10
- package/dist/cli.js +260 -74
- package/dist/start-relay-server.js +2 -3
- package/docs/skills/framer-canvas-editing-project.md +65 -0
- package/docs/{property-controls.md → skills/framer-code-components.md} +991 -0
- package/docs/{server-api.md → skills/framer.md} +98 -182
- package/package.json +1 -4
- package/docs/all-skills.md +0 -6
- package/docs/code-components.md +0 -115
- package/docs/component-examples.md +0 -869
package/README.md
CHANGED
|
@@ -1,34 +1,78 @@
|
|
|
1
1
|
# Framer Agent CLI
|
|
2
2
|
|
|
3
|
-
CLI and
|
|
3
|
+
CLI and agent skills for interacting with Framer projects via the Framer Server API.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
+
Install or refresh the globally available skills:
|
|
8
|
+
|
|
7
9
|
```bash
|
|
8
|
-
npx
|
|
10
|
+
npx framer-dalton@latest setup
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
That installs skills into `~/.agents/skills` and `~/.claude/skills`.
|
|
14
|
+
|
|
15
|
+
## How It Works
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
The CLI and the installed skills are meant to work together. There are three skills:
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
- `framer` - the base skill that explains how to use the CLI and the API in general.
|
|
20
|
+
- `framer-code-components` - explains specific prompts for how to write code components and provides examples.
|
|
21
|
+
- `framer-canvas-editing-project-<project id>` - a dynamically-created skill that explains how to canvas edit and includes project context.
|
|
22
|
+
|
|
23
|
+
1. Running `npx framer-dalton@latest setup` will install the base `framer` and `framer-code-component` skills.
|
|
24
|
+
2. The frontmatter of `framer-code-components` tells agents to always load `framer` first.
|
|
25
|
+
3. The frontmatter of `framer` tells agents to run `npx framer-dalton@latest setup` BEFORE loading the skill. This command will auto-update the cli and update the skill files.
|
|
26
|
+
4. Creating a new session for a project will create a `framer-canvas-editing-project-<project id>` file. This file contains the latest agent system prompt from `framer.getAgentSystemPrompt` and the latest project context from `framer.getAgentContext`. Agents are told not to load this skill until after creating a session.
|
|
16
27
|
|
|
17
28
|
## Local Development
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
### Running a development build against production headless api server
|
|
31
|
+
|
|
32
|
+
1. Run `make install-dev` in this repo.
|
|
33
|
+
|
|
34
|
+
2. Run your agent in this directory and specifically load the `framer-dev` skill:
|
|
20
35
|
|
|
21
36
|
```bash
|
|
22
|
-
|
|
37
|
+
# Claude:
|
|
38
|
+
> /framer-dev
|
|
39
|
+
|
|
40
|
+
# Codex:
|
|
41
|
+
> $framer-dev
|
|
23
42
|
```
|
|
24
43
|
|
|
25
|
-
|
|
44
|
+
### Running with the local headless plugin server
|
|
45
|
+
|
|
46
|
+
Use this when you change something in the Server API implementation.
|
|
47
|
+
|
|
48
|
+
1. Start FramerStudio with `make dev`.
|
|
49
|
+
2. Start FramerHeadlessAPI against the local tunnel with `make dev-tunnel`.
|
|
50
|
+
3. Run `make install-dev` in this repo.
|
|
51
|
+
4. Run your agent in this directory and specifically load the `framer-dev-local` skill:
|
|
26
52
|
|
|
53
|
+
```bash
|
|
54
|
+
# Claude:
|
|
55
|
+
> /framer-dev-local
|
|
56
|
+
|
|
57
|
+
# Codex:
|
|
58
|
+
> $framer-dev-local
|
|
27
59
|
```
|
|
28
|
-
|
|
60
|
+
|
|
61
|
+
### Using a local `framer-api` package
|
|
62
|
+
|
|
63
|
+
Use this when you change the server API interface itself, such as adding or changing API methods.
|
|
64
|
+
|
|
65
|
+
1. Publish or otherwise make your updated `framer-api` package available.
|
|
66
|
+
2. Install it locally with `npm install ...`.
|
|
67
|
+
3. Regenerate the type data:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
make generate-types
|
|
29
71
|
```
|
|
30
72
|
|
|
31
|
-
|
|
73
|
+
### Removing local builds and returning to production
|
|
74
|
+
|
|
75
|
+
Later, to return to the procution skills:
|
|
32
76
|
|
|
33
77
|
```bash
|
|
34
78
|
make uninstall-dev
|
package/dist/cli.js
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import path3 from 'path';
|
|
2
3
|
import { Command } from 'commander';
|
|
3
|
-
import
|
|
4
|
+
import fs2 from 'fs';
|
|
4
5
|
import os from 'os';
|
|
5
|
-
import path, { dirname, join } from 'path';
|
|
6
6
|
import { spawn } from 'child_process';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { createTRPCClient, httpLink } from '@trpc/client';
|
|
9
9
|
|
|
10
|
-
/* @framer/ai CLI v0.0.
|
|
10
|
+
/* @framer/ai CLI v0.0.7 */
|
|
11
11
|
var __defProp = Object.defineProperty;
|
|
12
12
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
13
13
|
function getConfigDir() {
|
|
14
14
|
if (process.env.XDG_CONFIG_HOME) {
|
|
15
|
-
return
|
|
15
|
+
return path3.join(process.env.XDG_CONFIG_HOME, "framer");
|
|
16
16
|
}
|
|
17
17
|
if (process.platform === "win32") {
|
|
18
|
-
return
|
|
18
|
+
return path3.join(process.env.APPDATA || os.homedir(), "framer");
|
|
19
19
|
}
|
|
20
|
-
return
|
|
20
|
+
return path3.join(os.homedir(), ".config", "framer");
|
|
21
21
|
}
|
|
22
22
|
__name(getConfigDir, "getConfigDir");
|
|
23
23
|
function getCredentialsPath() {
|
|
24
|
-
return
|
|
24
|
+
return path3.join(getConfigDir(), "credentials.json");
|
|
25
25
|
}
|
|
26
26
|
__name(getCredentialsPath, "getCredentialsPath");
|
|
27
27
|
function readCredentials() {
|
|
28
28
|
const credPath = getCredentialsPath();
|
|
29
29
|
try {
|
|
30
|
-
if (
|
|
31
|
-
return JSON.parse(
|
|
30
|
+
if (fs2.existsSync(credPath)) {
|
|
31
|
+
return JSON.parse(fs2.readFileSync(credPath, "utf-8"));
|
|
32
32
|
}
|
|
33
33
|
} catch {
|
|
34
34
|
}
|
|
@@ -38,10 +38,10 @@ __name(readCredentials, "readCredentials");
|
|
|
38
38
|
function writeCredentials(creds) {
|
|
39
39
|
const credPath = getCredentialsPath();
|
|
40
40
|
const configDir = getConfigDir();
|
|
41
|
-
if (!
|
|
42
|
-
|
|
41
|
+
if (!fs2.existsSync(configDir)) {
|
|
42
|
+
fs2.mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
fs2.writeFileSync(credPath, JSON.stringify(creds, null, " "), {
|
|
45
45
|
mode: 384
|
|
46
46
|
});
|
|
47
47
|
}
|
|
@@ -14460,6 +14460,7 @@ __name(expandReferences, "expandReferences");
|
|
|
14460
14460
|
function renderDocs(queries) {
|
|
14461
14461
|
const lines = [];
|
|
14462
14462
|
const errors = [];
|
|
14463
|
+
const framerClass = getClass("framer");
|
|
14463
14464
|
if (queries.length === 0) {
|
|
14464
14465
|
queries = ["framer"];
|
|
14465
14466
|
}
|
|
@@ -14482,6 +14483,20 @@ function renderDocs(queries) {
|
|
|
14482
14483
|
${typeDef}`);
|
|
14483
14484
|
}
|
|
14484
14485
|
} else {
|
|
14486
|
+
if (framerClass) {
|
|
14487
|
+
const method = framerClass.methods.find((m) => m.name === query);
|
|
14488
|
+
if (method) {
|
|
14489
|
+
if (method.description)
|
|
14490
|
+
lines.push(formatDocComment(method.description));
|
|
14491
|
+
lines.push(`${method.category}.${method.signature}`);
|
|
14492
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14493
|
+
for (const typeDef of expandReferences(method.references, seen)) {
|
|
14494
|
+
lines.push(`
|
|
14495
|
+
${typeDef}`);
|
|
14496
|
+
}
|
|
14497
|
+
continue;
|
|
14498
|
+
}
|
|
14499
|
+
}
|
|
14485
14500
|
const classData = getClass(query);
|
|
14486
14501
|
if (classData) {
|
|
14487
14502
|
lines.push(
|
|
@@ -14511,8 +14526,8 @@ ${typeDef}`);
|
|
|
14511
14526
|
}
|
|
14512
14527
|
__name(renderDocs, "renderDocs");
|
|
14513
14528
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
14514
|
-
var __dirname$1 =
|
|
14515
|
-
var VERSION = "0.0.
|
|
14529
|
+
var __dirname$1 = path3.dirname(__filename$1);
|
|
14530
|
+
var VERSION = "0.0.7" ;
|
|
14516
14531
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
|
|
14517
14532
|
var client = createTRPCClient({
|
|
14518
14533
|
links: [
|
|
@@ -14548,7 +14563,7 @@ function compareVersions(v1, v2) {
|
|
|
14548
14563
|
return 0;
|
|
14549
14564
|
}
|
|
14550
14565
|
__name(compareVersions, "compareVersions");
|
|
14551
|
-
async function
|
|
14566
|
+
async function ensureRelayServerRunning(options = {}) {
|
|
14552
14567
|
const { logger, restartOnVersionMismatch = true } = options;
|
|
14553
14568
|
const serverVersion = await getRelayServerVersion();
|
|
14554
14569
|
if (serverVersion === VERSION) {
|
|
@@ -14574,7 +14589,7 @@ async function ensureRelayServer(options = {}) {
|
|
|
14574
14589
|
logger?.log("Relay server not running, starting it...");
|
|
14575
14590
|
}
|
|
14576
14591
|
const isRunningFromSource = __filename$1.endsWith(".ts");
|
|
14577
|
-
const scriptPath = isRunningFromSource ?
|
|
14592
|
+
const scriptPath = isRunningFromSource ? path3.resolve(__dirname$1, "./start-relay-server.ts") : path3.resolve(__dirname$1, "./start-relay-server.js");
|
|
14578
14593
|
const serverProcess = spawn(
|
|
14579
14594
|
isRunningFromSource ? "tsx" : process.execPath,
|
|
14580
14595
|
[scriptPath],
|
|
@@ -14595,22 +14610,107 @@ async function ensureRelayServer(options = {}) {
|
|
|
14595
14610
|
}
|
|
14596
14611
|
throw new Error("Failed to start relay server after 5 seconds");
|
|
14597
14612
|
}
|
|
14598
|
-
__name(
|
|
14599
|
-
var
|
|
14600
|
-
var
|
|
14601
|
-
|
|
14602
|
-
|
|
14613
|
+
__name(ensureRelayServerRunning, "ensureRelayServerRunning");
|
|
14614
|
+
var META_SKILL_NAME = "framer";
|
|
14615
|
+
var CODE_COMPONENTS_SKILL_NAME = "framer-code-components";
|
|
14616
|
+
var __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
|
|
14617
|
+
var skillsDocsDir = path3.join(__dirname2, "..", "docs", "skills");
|
|
14618
|
+
function readSkillDoc(name) {
|
|
14619
|
+
return fs2.readFileSync(path3.join(skillsDocsDir, name), "utf-8").trimEnd();
|
|
14620
|
+
}
|
|
14621
|
+
__name(readSkillDoc, "readSkillDoc");
|
|
14622
|
+
function buildMetaSkill() {
|
|
14623
|
+
return `${readSkillDoc("framer.md")}
|
|
14624
|
+
`;
|
|
14625
|
+
}
|
|
14626
|
+
__name(buildMetaSkill, "buildMetaSkill");
|
|
14627
|
+
function buildCodeComponentsSkill() {
|
|
14628
|
+
return `${readSkillDoc("framer-code-components.md")}
|
|
14629
|
+
`;
|
|
14630
|
+
}
|
|
14631
|
+
__name(buildCodeComponentsSkill, "buildCodeComponentsSkill");
|
|
14632
|
+
function renderTemplate(template, values) {
|
|
14633
|
+
let rendered = template;
|
|
14634
|
+
for (const [key, value] of Object.entries(values)) {
|
|
14635
|
+
rendered = rendered.split(`{{${key}}}`).join(value);
|
|
14636
|
+
}
|
|
14637
|
+
return rendered;
|
|
14638
|
+
}
|
|
14639
|
+
__name(renderTemplate, "renderTemplate");
|
|
14640
|
+
function toProjectSkillName(projectId) {
|
|
14641
|
+
const safeProjectId = projectId.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
14642
|
+
return `framer-canvas-editing-project-${safeProjectId}`;
|
|
14643
|
+
}
|
|
14644
|
+
__name(toProjectSkillName, "toProjectSkillName");
|
|
14645
|
+
function buildProjectCanvasSkill(projectId, agentContext, canvasPrompt) {
|
|
14646
|
+
const skillName = toProjectSkillName(projectId);
|
|
14647
|
+
const template = readSkillDoc("framer-canvas-editing-project.md");
|
|
14648
|
+
const content = `${renderTemplate(template, {
|
|
14649
|
+
SKILL_NAME: skillName,
|
|
14650
|
+
PROJECT_ID: projectId,
|
|
14651
|
+
GENERATED_AT: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14652
|
+
CANVAS_PROMPT: canvasPrompt.trimEnd(),
|
|
14653
|
+
AGENT_CONTEXT: agentContext.trimEnd()
|
|
14654
|
+
})}
|
|
14655
|
+
`;
|
|
14656
|
+
return { skillName, content };
|
|
14603
14657
|
}
|
|
14604
|
-
__name(
|
|
14605
|
-
|
|
14606
|
-
|
|
14607
|
-
|
|
14608
|
-
|
|
14609
|
-
|
|
14610
|
-
|
|
14611
|
-
|
|
14658
|
+
__name(buildProjectCanvasSkill, "buildProjectCanvasSkill");
|
|
14659
|
+
function writeSkill(root, skillName, content) {
|
|
14660
|
+
fs2.mkdirSync(root, { recursive: true });
|
|
14661
|
+
const rootStat = fs2.statSync(root);
|
|
14662
|
+
if (!rootStat.isDirectory()) {
|
|
14663
|
+
throw new Error(`Skill root is not a directory: ${root}`);
|
|
14664
|
+
}
|
|
14665
|
+
const skillDir = path3.join(root, skillName);
|
|
14666
|
+
const filePath = path3.join(skillDir, "SKILL.md");
|
|
14667
|
+
if (fs2.existsSync(skillDir)) {
|
|
14668
|
+
const current = fs2.lstatSync(skillDir);
|
|
14669
|
+
if (current.isSymbolicLink() || !current.isDirectory()) {
|
|
14670
|
+
fs2.rmSync(skillDir, { recursive: true, force: true });
|
|
14671
|
+
}
|
|
14672
|
+
}
|
|
14673
|
+
fs2.mkdirSync(skillDir, { recursive: true });
|
|
14674
|
+
fs2.writeFileSync(filePath, content, "utf-8");
|
|
14675
|
+
return filePath;
|
|
14676
|
+
}
|
|
14677
|
+
__name(writeSkill, "writeSkill");
|
|
14678
|
+
function getDefaultSkillRoots() {
|
|
14679
|
+
const home = os.homedir();
|
|
14680
|
+
return [
|
|
14681
|
+
path3.join(home, ".agents", "skills"),
|
|
14682
|
+
path3.join(home, ".claude", "skills")
|
|
14683
|
+
];
|
|
14684
|
+
}
|
|
14685
|
+
__name(getDefaultSkillRoots, "getDefaultSkillRoots");
|
|
14686
|
+
function installSkills(options = { type: "base" }) {
|
|
14687
|
+
const skillRoots = options.skillRoots ?? getDefaultSkillRoots();
|
|
14688
|
+
const skills = [
|
|
14689
|
+
{ name: META_SKILL_NAME, content: buildMetaSkill() },
|
|
14690
|
+
{
|
|
14691
|
+
name: CODE_COMPONENTS_SKILL_NAME,
|
|
14692
|
+
content: buildCodeComponentsSkill()
|
|
14693
|
+
}
|
|
14694
|
+
];
|
|
14695
|
+
if (options.type === "project") {
|
|
14696
|
+
const projectSkill = buildProjectCanvasSkill(
|
|
14697
|
+
options.projectId,
|
|
14698
|
+
options.agentContext,
|
|
14699
|
+
options.canvasPrompt
|
|
14700
|
+
);
|
|
14701
|
+
skills.push({
|
|
14702
|
+
name: projectSkill.skillName,
|
|
14703
|
+
content: projectSkill.content
|
|
14704
|
+
});
|
|
14705
|
+
}
|
|
14706
|
+
return skills.map((skill) => ({
|
|
14707
|
+
skillName: skill.name,
|
|
14708
|
+
paths: skillRoots.map(
|
|
14709
|
+
(root) => writeSkill(root, skill.name, skill.content)
|
|
14710
|
+
)
|
|
14711
|
+
}));
|
|
14612
14712
|
}
|
|
14613
|
-
__name(
|
|
14713
|
+
__name(installSkills, "installSkills");
|
|
14614
14714
|
|
|
14615
14715
|
// src/utils.ts
|
|
14616
14716
|
function formatError(error) {
|
|
@@ -14644,6 +14744,116 @@ async function readStdin() {
|
|
|
14644
14744
|
return Buffer.concat(chunks).toString("utf-8");
|
|
14645
14745
|
}
|
|
14646
14746
|
__name(readStdin, "readStdin");
|
|
14747
|
+
function printSetupSummary(results) {
|
|
14748
|
+
const skillCount = results.length;
|
|
14749
|
+
const installLocations = /* @__PURE__ */ new Set();
|
|
14750
|
+
for (const result of results) {
|
|
14751
|
+
for (const filePath of result.paths) {
|
|
14752
|
+
const skillDir = path3.dirname(filePath);
|
|
14753
|
+
const root = path3.dirname(skillDir);
|
|
14754
|
+
installLocations.add(root);
|
|
14755
|
+
}
|
|
14756
|
+
}
|
|
14757
|
+
print(
|
|
14758
|
+
`Installed ${skillCount} skills to ${Array.from(installLocations).sort().join(", ")}`
|
|
14759
|
+
);
|
|
14760
|
+
}
|
|
14761
|
+
__name(printSetupSummary, "printSetupSummary");
|
|
14762
|
+
async function getAgentSystemPrompt(sessionId) {
|
|
14763
|
+
const result = await client.exec.mutate({
|
|
14764
|
+
sessionId,
|
|
14765
|
+
code: "return await framer.getAgentSystemPrompt();",
|
|
14766
|
+
cwd: process.cwd()
|
|
14767
|
+
});
|
|
14768
|
+
if (result.error) {
|
|
14769
|
+
throw new Error(result.error);
|
|
14770
|
+
}
|
|
14771
|
+
const prompt = result.output.at(-1);
|
|
14772
|
+
if (typeof prompt !== "string") {
|
|
14773
|
+
throw new Error("Did not receive agent prompt output.");
|
|
14774
|
+
}
|
|
14775
|
+
return prompt;
|
|
14776
|
+
}
|
|
14777
|
+
__name(getAgentSystemPrompt, "getAgentSystemPrompt");
|
|
14778
|
+
async function getAgentContext(sessionId) {
|
|
14779
|
+
const result = await client.exec.mutate({
|
|
14780
|
+
sessionId,
|
|
14781
|
+
code: "return await framer.getAgentContext({ pagePath: '/' });",
|
|
14782
|
+
cwd: process.cwd()
|
|
14783
|
+
});
|
|
14784
|
+
if (result.error) {
|
|
14785
|
+
throw new Error(result.error);
|
|
14786
|
+
}
|
|
14787
|
+
const context = result.output.at(-1);
|
|
14788
|
+
if (typeof context !== "string") {
|
|
14789
|
+
throw new Error("Did not receive agent context output.");
|
|
14790
|
+
}
|
|
14791
|
+
return context;
|
|
14792
|
+
}
|
|
14793
|
+
__name(getAgentContext, "getAgentContext");
|
|
14794
|
+
async function refreshSkillsFromSession(sessionId, projectId) {
|
|
14795
|
+
try {
|
|
14796
|
+
const [canvasPrompt, agentContext] = await Promise.all([
|
|
14797
|
+
getAgentSystemPrompt(sessionId),
|
|
14798
|
+
getAgentContext(sessionId)
|
|
14799
|
+
]);
|
|
14800
|
+
installSkills({
|
|
14801
|
+
type: "project",
|
|
14802
|
+
canvasPrompt,
|
|
14803
|
+
projectId,
|
|
14804
|
+
agentContext
|
|
14805
|
+
});
|
|
14806
|
+
} catch (err) {
|
|
14807
|
+
printError(
|
|
14808
|
+
`Failed to refresh skills for session ${sessionId}: ${formatError(err)}`
|
|
14809
|
+
);
|
|
14810
|
+
process.exit(1);
|
|
14811
|
+
}
|
|
14812
|
+
}
|
|
14813
|
+
__name(refreshSkillsFromSession, "refreshSkillsFromSession");
|
|
14814
|
+
function resolveSessionCredentials(projectUrl, apiKeyArg) {
|
|
14815
|
+
try {
|
|
14816
|
+
const projectId = extractProjectId(projectUrl);
|
|
14817
|
+
if (!apiKeyArg) {
|
|
14818
|
+
const cachedApiKey = getApiKey(projectId);
|
|
14819
|
+
if (cachedApiKey) {
|
|
14820
|
+
return { projectId, apiKey: cachedApiKey };
|
|
14821
|
+
}
|
|
14822
|
+
printError("No API key provided and none cached for this project.");
|
|
14823
|
+
printError("");
|
|
14824
|
+
printError("Usage: framer session new <projectUrl> <apiKey>");
|
|
14825
|
+
process.exit(1);
|
|
14826
|
+
}
|
|
14827
|
+
saveApiKey(projectId, apiKeyArg);
|
|
14828
|
+
return { projectId, apiKey: apiKeyArg };
|
|
14829
|
+
} catch (err) {
|
|
14830
|
+
printError(`Failed to create session: ${formatError(err)}`);
|
|
14831
|
+
process.exit(1);
|
|
14832
|
+
}
|
|
14833
|
+
}
|
|
14834
|
+
__name(resolveSessionCredentials, "resolveSessionCredentials");
|
|
14835
|
+
async function createSession(projectId, apiKey) {
|
|
14836
|
+
try {
|
|
14837
|
+
const result = await client.createSession.mutate({
|
|
14838
|
+
projectId,
|
|
14839
|
+
apiKey
|
|
14840
|
+
});
|
|
14841
|
+
return result.id;
|
|
14842
|
+
} catch (err) {
|
|
14843
|
+
printError(`Failed to create session: ${formatError(err)}`);
|
|
14844
|
+
process.exit(1);
|
|
14845
|
+
}
|
|
14846
|
+
}
|
|
14847
|
+
__name(createSession, "createSession");
|
|
14848
|
+
async function ensureRelayForCli() {
|
|
14849
|
+
try {
|
|
14850
|
+
await ensureRelayServerRunning({ logger: { log: print } });
|
|
14851
|
+
} catch (err) {
|
|
14852
|
+
printError(`Failed to check relay status: ${formatError(err)}`);
|
|
14853
|
+
process.exit(1);
|
|
14854
|
+
}
|
|
14855
|
+
}
|
|
14856
|
+
__name(ensureRelayForCli, "ensureRelayForCli");
|
|
14647
14857
|
program.option("-s, --session <id>", "Session ID (required for code execution)").option("-e, --eval <code>", "Code to execute (or pipe via stdin)").action(async (options) => {
|
|
14648
14858
|
const { session: sessionId, eval: evalCode } = options;
|
|
14649
14859
|
let code = evalCode;
|
|
@@ -14661,8 +14871,8 @@ program.option("-s, --session <id>", "Session ID (required for code execution)")
|
|
|
14661
14871
|
);
|
|
14662
14872
|
process.exit(1);
|
|
14663
14873
|
}
|
|
14874
|
+
await ensureRelayForCli();
|
|
14664
14875
|
try {
|
|
14665
|
-
await ensureRelayServer({ logger: { log: print } });
|
|
14666
14876
|
const result = await client.exec.mutate({
|
|
14667
14877
|
sessionId: String(sessionId),
|
|
14668
14878
|
code,
|
|
@@ -14682,36 +14892,18 @@ program.option("-s, --session <id>", "Session ID (required for code execution)")
|
|
|
14682
14892
|
});
|
|
14683
14893
|
var session = program.command("session").description("Manage sessions");
|
|
14684
14894
|
session.command("new <projectUrl> [apiKey]").description("Create a new session and print the session ID").action(async (projectUrlArg, apiKeyArg) => {
|
|
14685
|
-
|
|
14686
|
-
|
|
14687
|
-
|
|
14688
|
-
|
|
14689
|
-
|
|
14690
|
-
|
|
14691
|
-
|
|
14692
|
-
|
|
14693
|
-
printError("No API key provided and none cached for this project.");
|
|
14694
|
-
printError("");
|
|
14695
|
-
printError("Usage: framer session new <projectUrl> <apiKey>");
|
|
14696
|
-
process.exit(1);
|
|
14697
|
-
}
|
|
14698
|
-
} else {
|
|
14699
|
-
saveApiKey(projectId, apiKey);
|
|
14700
|
-
}
|
|
14701
|
-
await ensureRelayServer({ logger: { log: print } });
|
|
14702
|
-
const result = await client.createSession.mutate({
|
|
14703
|
-
projectId,
|
|
14704
|
-
apiKey
|
|
14705
|
-
});
|
|
14706
|
-
print(result.id);
|
|
14707
|
-
} catch (err) {
|
|
14708
|
-
printError(`Failed to create session: ${formatError(err)}`);
|
|
14709
|
-
process.exit(1);
|
|
14710
|
-
}
|
|
14895
|
+
const { projectId, apiKey } = resolveSessionCredentials(
|
|
14896
|
+
projectUrlArg,
|
|
14897
|
+
apiKeyArg
|
|
14898
|
+
);
|
|
14899
|
+
await ensureRelayForCli();
|
|
14900
|
+
const sessionId = await createSession(projectId, apiKey);
|
|
14901
|
+
await refreshSkillsFromSession(sessionId, projectId);
|
|
14902
|
+
print(sessionId);
|
|
14711
14903
|
});
|
|
14712
14904
|
session.command("list").description("List all active sessions").action(async () => {
|
|
14905
|
+
await ensureRelayForCli();
|
|
14713
14906
|
try {
|
|
14714
|
-
await ensureRelayServer({ logger: { log: print } });
|
|
14715
14907
|
const sessions = await client.listSessions.query();
|
|
14716
14908
|
if (sessions.length === 0) {
|
|
14717
14909
|
print("No active sessions");
|
|
@@ -14724,8 +14916,8 @@ session.command("list").description("List all active sessions").action(async ()
|
|
|
14724
14916
|
}
|
|
14725
14917
|
});
|
|
14726
14918
|
session.command("destroy <sessionId>").description("Destroy a session").action(async (sessionId) => {
|
|
14919
|
+
await ensureRelayForCli();
|
|
14727
14920
|
try {
|
|
14728
|
-
await ensureRelayServer({ logger: { log: print } });
|
|
14729
14921
|
await client.destroySession.mutate({ sessionId });
|
|
14730
14922
|
print(`Session ${sessionId} destroyed`);
|
|
14731
14923
|
} catch (err) {
|
|
@@ -14749,24 +14941,18 @@ relay.command("restart").description("Restart the relay server").action(async ()
|
|
|
14749
14941
|
} catch {
|
|
14750
14942
|
}
|
|
14751
14943
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
14752
|
-
await
|
|
14944
|
+
await ensureRelayForCli();
|
|
14753
14945
|
});
|
|
14754
|
-
program.command("
|
|
14755
|
-
|
|
14756
|
-
|
|
14757
|
-
|
|
14758
|
-
|
|
14759
|
-
|
|
14760
|
-
}
|
|
14761
|
-
|
|
14762
|
-
const available = ["(no argument)", ...skillTopics];
|
|
14763
|
-
printError(`Unknown skill topic: ${topic}`);
|
|
14764
|
-
printError(`Available topics: ${available.join(", ")}`);
|
|
14946
|
+
program.command("setup").description(
|
|
14947
|
+
"Install Framer skills into ~/.agents/skills and ~/.claude/skills"
|
|
14948
|
+
).action(() => {
|
|
14949
|
+
try {
|
|
14950
|
+
const results = installSkills();
|
|
14951
|
+
printSetupSummary(results);
|
|
14952
|
+
} catch (err) {
|
|
14953
|
+
printError(`Setup failed: ${formatError(err)}`);
|
|
14765
14954
|
process.exit(1);
|
|
14766
14955
|
}
|
|
14767
|
-
print(readDoc(topic));
|
|
14768
|
-
print("");
|
|
14769
|
-
print(readDoc("all-skills"));
|
|
14770
14956
|
});
|
|
14771
14957
|
program.command("docs [queries...]").description(
|
|
14772
14958
|
"Look up API documentation. Use: docs, docs ClassName, docs Class.method, or docs TypeName"
|
|
@@ -13,7 +13,7 @@ import { createRequire } from 'module';
|
|
|
13
13
|
import * as vm from 'vm';
|
|
14
14
|
import { connect } from 'framer-api';
|
|
15
15
|
|
|
16
|
-
/* @framer/ai relay server v0.0.
|
|
16
|
+
/* @framer/ai relay server v0.0.7 */
|
|
17
17
|
var __defProp = Object.defineProperty;
|
|
18
18
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
19
19
|
function getLogPath() {
|
|
@@ -50,7 +50,7 @@ function log(message) {
|
|
|
50
50
|
__name(log, "log");
|
|
51
51
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
52
52
|
path.dirname(__filename$1);
|
|
53
|
-
var VERSION = "0.0.
|
|
53
|
+
var VERSION = "0.0.7" ;
|
|
54
54
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19988;
|
|
55
55
|
createTRPCClient({
|
|
56
56
|
links: [
|
|
@@ -688,7 +688,6 @@ process.on("unhandledRejection", (reason) => {
|
|
|
688
688
|
});
|
|
689
689
|
async function main() {
|
|
690
690
|
const server = await startRelayServer(RELAY_PORT);
|
|
691
|
-
log(`started v${VERSION} on port ${RELAY_PORT}`);
|
|
692
691
|
console.log(`Framer relay server v${VERSION} running on port ${RELAY_PORT}`);
|
|
693
692
|
process.on("SIGINT", () => {
|
|
694
693
|
log("shutdown SIGINT");
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: {{SKILL_NAME}}
|
|
3
|
+
description: "Project-scoped Framer canvas editing skill for project {{PROJECT_ID}}. Very important: never load this skill without having already read the `framer` skill and without having already run `session new`, which will dynamically update this skill."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Project Scope
|
|
7
|
+
|
|
8
|
+
- Project ID: {{PROJECT_ID}}
|
|
9
|
+
- Generated At: {{GENERATED_AT}}
|
|
10
|
+
|
|
11
|
+
## Rules
|
|
12
|
+
|
|
13
|
+
- For design/layout work, do not use low-level node APIs (`createNode`, `setAttributes`, `setRect`, etc.). Use the canvas editing flow (`readProjectForAgent` + `applyAgentChanges`) with the embedded prompt and project context in this skill.
|
|
14
|
+
- Canvas editing agent methods are employee-only.
|
|
15
|
+
- Canvas editing methods are not globals. Call `framer.readProjectForAgent` and `framer.applyAgentChanges` as `framer.*`.
|
|
16
|
+
- During normal task execution, do not call `framer.getAgentSystemPrompt()` or `framer.getAgentContext()`. This skill already includes `getAgentContext({ pagePath: "/" })`.
|
|
17
|
+
- `framer.readProjectForAgent(queries, { pagePath })` reads project state. Start with page structure only, then request additional targeted queries only if needed. Query types are documented in the embedded prompt below. Never guess names for examples, icon sets, or fonts; look them up first.
|
|
18
|
+
- `framer.applyAgentChanges(dsl, { pagePath })` applies canvas DSL changes. After applying changes, re-read project state to inspect results and iterate with `readProjectForAgent` + `applyAgentChanges` until accurate.
|
|
19
|
+
- Request examples only when needed, and only after inspecting page structure first. Keep example queries targeted and minimal.
|
|
20
|
+
- Use screenshots only when visual verification is necessary and cannot be confirmed from state reads. Do not use them for initial inspection or between every small change.
|
|
21
|
+
- When setting text content, use raw Unicode characters directly (for example `->`, not `\u2192`).
|
|
22
|
+
- Regenerate the embedded prompt and context by running `session new` again for the same project.
|
|
23
|
+
- In rich text, block-level properties (for example line height or alignment) belong on block or parent targets, not `TextRun` targets.
|
|
24
|
+
- `IconNode` `$control__icon` is immutable after creation. To change it, delete and recreate the node with the desired icon value.
|
|
25
|
+
|
|
26
|
+
## Workflow Loop
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# 1) Read page structure first
|
|
30
|
+
framer -s <sessionId> <<'EOF'
|
|
31
|
+
const { results } = await framer.readProjectForAgent(
|
|
32
|
+
[{ type: "page", path: "/" }],
|
|
33
|
+
{ pagePath: "/" }
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
console.log(results);
|
|
37
|
+
EOF
|
|
38
|
+
|
|
39
|
+
# 2) Request additional targeted queries only if needed
|
|
40
|
+
|
|
41
|
+
# 3) Apply changes in a later call, once `dsl` has been prepared
|
|
42
|
+
framer -s <sessionId> <<'EOF'
|
|
43
|
+
const dsl = `
|
|
44
|
+
...your canvas DSL...
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
await framer.applyAgentChanges(dsl, { pagePath: "/" });
|
|
48
|
+
EOF
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Live Agent System Prompt
|
|
52
|
+
|
|
53
|
+
This is the static canvas-editing prompt returned by `framer.getAgentSystemPrompt()`. It is the sole documentation for:
|
|
54
|
+
|
|
55
|
+
- command syntax for `applyAgentChanges`
|
|
56
|
+
- query types and parameters for `readProjectForAgent`
|
|
57
|
+
- design rules and examples used by the canvas editing flow
|
|
58
|
+
|
|
59
|
+
{{CANVAS_PROMPT}}
|
|
60
|
+
|
|
61
|
+
## Live Agent Context (/)
|
|
62
|
+
|
|
63
|
+
This is the dynamic project context returned by `framer.getAgentContext({ pagePath: "/" })`. It contains project-specific data for the current page, including available fonts, components and their controls, design tokens, style presets, and icon sets.
|
|
64
|
+
|
|
65
|
+
{{AGENT_CONTEXT}}
|