framer-dalton 0.0.1 → 0.0.2
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 +708 -2
- package/dist/start-relay-server.js +2 -2
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import path from 'path';
|
|
|
6
6
|
import { spawn } from 'child_process';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
|
|
9
|
-
/* @framer/ai CLI v0.0.
|
|
9
|
+
/* @framer/ai CLI v0.0.2 */
|
|
10
10
|
var __defProp = Object.defineProperty;
|
|
11
11
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
12
12
|
function getConfigDir() {
|
|
@@ -73,7 +73,7 @@ function getConfigPath() {
|
|
|
73
73
|
__name(getConfigPath, "getConfigPath");
|
|
74
74
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
75
75
|
var __dirname$1 = path.dirname(__filename$1);
|
|
76
|
-
var VERSION = "0.0.
|
|
76
|
+
var VERSION = "0.0.2" ;
|
|
77
77
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19987;
|
|
78
78
|
async function getRelayServerVersion(port = RELAY_PORT) {
|
|
79
79
|
try {
|
|
@@ -160,6 +160,709 @@ async function ensureRelayServer(options = {}) {
|
|
|
160
160
|
}
|
|
161
161
|
__name(ensureRelayServer, "ensureRelayServer");
|
|
162
162
|
|
|
163
|
+
// src/skill-docs.ts
|
|
164
|
+
var skillDocs = `## CLI Usage
|
|
165
|
+
|
|
166
|
+
Use \`npx framer-dalton@latest\` for the first command in a session to ensure you're using the latest version. Subsequent commands can omit \`@latest\`:
|
|
167
|
+
|
|
168
|
+
\`\`\`bash
|
|
169
|
+
npx framer-dalton@latest session new "<projectUrl>" "<apiKey>" # First command: use @latest
|
|
170
|
+
npx framer-dalton docs Collection # Subsequent: @latest optional
|
|
171
|
+
\`\`\`
|
|
172
|
+
|
|
173
|
+
### Required Workflow
|
|
174
|
+
|
|
175
|
+
Every task follows these steps:
|
|
176
|
+
|
|
177
|
+
#### 1. Connect (once per session)
|
|
178
|
+
|
|
179
|
+
Ask for the Project URL, then create a session:
|
|
180
|
+
|
|
181
|
+
\`\`\`bash
|
|
182
|
+
npx framer-dalton@latest session new "<projectUrl>" # Uses cached API key
|
|
183
|
+
npx framer-dalton@latest session new "<projectUrl>" "<apiKey>" # First time: needs API key
|
|
184
|
+
\`\`\`
|
|
185
|
+
|
|
186
|
+
#### 2. Look up the API (before EVERY code execution)
|
|
187
|
+
|
|
188
|
+
**You MUST run \`npx framer-dalton docs\` before writing any code.** Do not guess method names or signatures.
|
|
189
|
+
|
|
190
|
+
\`\`\`bash
|
|
191
|
+
npx framer-dalton docs Collection # What methods exist?
|
|
192
|
+
npx framer-dalton docs Collection.getItems # What are the parameters and return type?
|
|
193
|
+
\`\`\`
|
|
194
|
+
|
|
195
|
+
#### 3. Execute code
|
|
196
|
+
|
|
197
|
+
Only after checking docs:
|
|
198
|
+
|
|
199
|
+
\`\`\`bash
|
|
200
|
+
npx framer-dalton -s 1 -e "state.items = await collection.getItems(); console.log(state.items.length)"
|
|
201
|
+
\`\`\`
|
|
202
|
+
|
|
203
|
+
#### 4. Store results in \`state\`
|
|
204
|
+
|
|
205
|
+
Always save results you'll need again. Don't repeat API calls.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
**Do not skip step 2.** The examples below are patterns only - always verify current signatures with \`npx framer-dalton docs\`.
|
|
210
|
+
|
|
211
|
+
### Communication style
|
|
212
|
+
|
|
213
|
+
Be concise. Don't narrate implementation details like field IDs, escaping, or internal steps. Just do the work and report what was accomplished in user-facing terms.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### Session Management
|
|
218
|
+
|
|
219
|
+
Each session maintains a persistent connection to a Framer project. Use sessions to:
|
|
220
|
+
|
|
221
|
+
- Keep state separate between different tasks
|
|
222
|
+
- Persist data across multiple execute calls
|
|
223
|
+
- Reuse the \`framer\` API instance without reconnecting
|
|
224
|
+
|
|
225
|
+
Get a new session ID:
|
|
226
|
+
|
|
227
|
+
\`\`\`bash
|
|
228
|
+
npx framer-dalton@latest session new "https://framer.com/projects/Website--abc123" "framer_api_xxx"
|
|
229
|
+
# outputs: 1
|
|
230
|
+
\`\`\`
|
|
231
|
+
|
|
232
|
+
**Always use your own session** - pass \`-s <id>\` to all commands. Using the same session preserves your \`state\` between calls.
|
|
233
|
+
|
|
234
|
+
List active sessions:
|
|
235
|
+
|
|
236
|
+
\`\`\`bash
|
|
237
|
+
npx framer-dalton session list
|
|
238
|
+
\`\`\`
|
|
239
|
+
|
|
240
|
+
### Execute Code
|
|
241
|
+
|
|
242
|
+
\`\`\`bash
|
|
243
|
+
npx framer-dalton -s <sessionId> -e "<code>"
|
|
244
|
+
\`\`\`
|
|
245
|
+
|
|
246
|
+
Default timeout is 30 seconds. Increase with \`--timeout <ms>\`.
|
|
247
|
+
|
|
248
|
+
**Escaping:** For code with HTML, quotes, or special characters, use a heredoc (see below). For simple strings, use \`$'...'\` syntax.
|
|
249
|
+
|
|
250
|
+
**Examples:**
|
|
251
|
+
|
|
252
|
+
\`\`\`bash
|
|
253
|
+
# Fetch collections and store in state (always store results you'll reuse)
|
|
254
|
+
npx framer-dalton -s 1 -e "state.collections = await framer.getCollections(); console.log(state.collections.map(c => c.name))"
|
|
255
|
+
|
|
256
|
+
# Use stored data in subsequent calls
|
|
257
|
+
npx framer-dalton -s 1 -e "state.team = state.collections.find(c => c.name === 'Team')"
|
|
258
|
+
npx framer-dalton -s 1 -e "state.teamItems = await state.team.getItems(); console.log(state.teamItems.length)"
|
|
259
|
+
\`\`\`
|
|
260
|
+
|
|
261
|
+
**Multiline code with heredoc (recommended for complex strings):**
|
|
262
|
+
|
|
263
|
+
For code containing HTML, quotes, or special characters, use a heredoc to avoid escaping issues:
|
|
264
|
+
|
|
265
|
+
\`\`\`bash
|
|
266
|
+
npx framer-dalton -s 1 <<'EOF'
|
|
267
|
+
const translations = {
|
|
268
|
+
"node-id": "<h2>Ship's Treasures</h2>",
|
|
269
|
+
"other-id": "<p>Text with "quotes" and <tags></p>"
|
|
270
|
+
};
|
|
271
|
+
await framer.setLocalizationData({ valuesBySource: translations });
|
|
272
|
+
EOF
|
|
273
|
+
\`\`\`
|
|
274
|
+
|
|
275
|
+
The \`<<'EOF'\` syntax (with quotes around EOF) prevents shell interpolation.
|
|
276
|
+
|
|
277
|
+
**Alternative: pipe from file:**
|
|
278
|
+
|
|
279
|
+
\`\`\`bash
|
|
280
|
+
cat script.js | npx framer-dalton -s 1
|
|
281
|
+
\`\`\`
|
|
282
|
+
|
|
283
|
+
**Simple inline code:**
|
|
284
|
+
|
|
285
|
+
\`\`\`bash
|
|
286
|
+
npx framer-dalton -s 1 -e $'
|
|
287
|
+
const collections = await framer.getCollections();
|
|
288
|
+
for (const c of collections) {
|
|
289
|
+
const items = await c.getItems();
|
|
290
|
+
console.log(c.name, items.length);
|
|
291
|
+
}
|
|
292
|
+
'
|
|
293
|
+
\`\`\`
|
|
294
|
+
|
|
295
|
+
### API Documentation
|
|
296
|
+
|
|
297
|
+
\`\`\`bash
|
|
298
|
+
npx framer-dalton docs # List all classes and methods
|
|
299
|
+
npx framer-dalton docs Collection # Show all methods for Collection
|
|
300
|
+
npx framer-dalton docs Collection.addItems # Show method with parameter types inlined
|
|
301
|
+
npx framer-dalton docs framer.screenshot # Show method signature and types
|
|
302
|
+
npx framer-dalton docs ScreenshotOptions # Look up a specific type definition
|
|
303
|
+
\`\`\`
|
|
304
|
+
|
|
305
|
+
When looking up a method, all referenced types are automatically expanded inline.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Context Variables
|
|
310
|
+
|
|
311
|
+
- \`framer\` - Connected Framer Server API instance
|
|
312
|
+
- \`state\` - Object persisted between calls within your session
|
|
313
|
+
- \`console\` - For output (\`console.log\`, \`console.error\`)
|
|
314
|
+
- \`require\` - Sandboxed Node.js modules: fs, path, url, crypto, buffer, util, os
|
|
315
|
+
- Standard globals: \`fetch\`, \`Buffer\`, \`URL\`, \`crypto\`, \`setTimeout\`
|
|
316
|
+
|
|
317
|
+
**Note:** \`fs\` operations are sandboxed to cwd, /tmp, and os.tmpdir().
|
|
318
|
+
|
|
319
|
+
### Use \`state\` to Avoid Repeated Calls
|
|
320
|
+
|
|
321
|
+
**Always store results in \`state\` when you'll need them again.** API calls are slow - don't repeat them.
|
|
322
|
+
|
|
323
|
+
\`\`\`bash
|
|
324
|
+
# First call: fetch and store
|
|
325
|
+
npx framer-dalton -s 1 -e "state.collections = await framer.getCollections()"
|
|
326
|
+
|
|
327
|
+
# Later calls: reuse from state
|
|
328
|
+
npx framer-dalton -s 1 -e "const team = state.collections.find(c => c.name === 'Team')"
|
|
329
|
+
npx framer-dalton -s 1 -e "state.teamItems = await state.collections.find(c => c.name === 'Team').getItems()"
|
|
330
|
+
\`\`\`
|
|
331
|
+
|
|
332
|
+
Store anything you'll reference again: collections, items, nodes, fields.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## API Examples
|
|
337
|
+
|
|
338
|
+
**STOP: These are patterns only. Before using any method below, run \`npx framer-dalton docs <ClassName>\` to verify the current signature.**
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Working with Collections (CMS)
|
|
343
|
+
|
|
344
|
+
Collections are Framer's CMS. Each collection has fields (columns) and items (rows).
|
|
345
|
+
|
|
346
|
+
### Reading Collections
|
|
347
|
+
|
|
348
|
+
\`\`\`js
|
|
349
|
+
// Get all collections
|
|
350
|
+
const collections = await framer.getCollections();
|
|
351
|
+
console.log(collections.map((c) => ({ name: c.name, id: c.id })));
|
|
352
|
+
|
|
353
|
+
// Get a specific collection by ID
|
|
354
|
+
const collection = await framer.getCollection("collection-id");
|
|
355
|
+
|
|
356
|
+
// Get fields (columns) - returns array of { id, type, name }
|
|
357
|
+
const fields = await collection.getFields();
|
|
358
|
+
console.log(fields);
|
|
359
|
+
// [{ id: "BnNuS2i3o", type: "string", name: "Title" }, ...]
|
|
360
|
+
|
|
361
|
+
// Get items (rows)
|
|
362
|
+
const items = await collection.getItems();
|
|
363
|
+
console.log(items);
|
|
364
|
+
// [{ id: "XTM8FSHGs", slug: "post-1", draft: false, fieldData: {...} }, ...]
|
|
365
|
+
\`\`\`
|
|
366
|
+
|
|
367
|
+
### Field Types
|
|
368
|
+
|
|
369
|
+
\`boolean\`, \`color\`, \`number\`, \`string\`, \`formattedText\` (HTML), \`image\`, \`file\`, \`link\`, \`date\`, \`enum\`, \`collectionReference\`, \`multiCollectionReference\`, \`array\` (galleries)
|
|
370
|
+
|
|
371
|
+
### Updating Collection Items
|
|
372
|
+
|
|
373
|
+
\`\`\`js
|
|
374
|
+
// Add or update items (if id matches existing item, it updates)
|
|
375
|
+
await collection.addItems([
|
|
376
|
+
{
|
|
377
|
+
id: "new-item-1",
|
|
378
|
+
slug: "hello-world",
|
|
379
|
+
fieldData: { titleFieldId: "Hello World" },
|
|
380
|
+
},
|
|
381
|
+
]);
|
|
382
|
+
|
|
383
|
+
// Remove items
|
|
384
|
+
await collection.removeItems(["item-id-1", "item-id-2"]);
|
|
385
|
+
|
|
386
|
+
// Reorder items
|
|
387
|
+
const ids = items.map((i) => i.id).reverse();
|
|
388
|
+
await collection.setItemOrder(ids);
|
|
389
|
+
\`\`\`
|
|
390
|
+
|
|
391
|
+
### Working with CollectionItem
|
|
392
|
+
|
|
393
|
+
\`\`\`js
|
|
394
|
+
const items = await collection.getItems();
|
|
395
|
+
const item = items[0];
|
|
396
|
+
|
|
397
|
+
// Update item attributes
|
|
398
|
+
await item.setAttributes({ slug: "new-slug" });
|
|
399
|
+
|
|
400
|
+
// Navigate to item in Framer UI
|
|
401
|
+
await item.navigateTo();
|
|
402
|
+
|
|
403
|
+
// Remove item
|
|
404
|
+
await item.remove();
|
|
405
|
+
\`\`\`
|
|
406
|
+
|
|
407
|
+
### Managed Collections
|
|
408
|
+
|
|
409
|
+
Managed collections are fully controlled by code - users can't edit them directly. Use for syncing external data sources.
|
|
410
|
+
|
|
411
|
+
\`\`\`js
|
|
412
|
+
// Create a managed collection
|
|
413
|
+
const managed = await framer.createManagedCollection({ name: "My Sync" });
|
|
414
|
+
|
|
415
|
+
// Set fields
|
|
416
|
+
await managed.setFields([
|
|
417
|
+
{ id: "title", name: "Title", type: "string" },
|
|
418
|
+
{ id: "content", name: "Content", type: "formattedText" },
|
|
419
|
+
{ id: "published", name: "Published", type: "date" },
|
|
420
|
+
]);
|
|
421
|
+
|
|
422
|
+
// Add items
|
|
423
|
+
await managed.addItems([
|
|
424
|
+
{
|
|
425
|
+
id: "1",
|
|
426
|
+
slug: "first-post",
|
|
427
|
+
fieldData: { title: "Hello", content: "<p>World</p>" },
|
|
428
|
+
},
|
|
429
|
+
]);
|
|
430
|
+
|
|
431
|
+
// Store sync metadata
|
|
432
|
+
await managed.setPluginData("lastSync", new Date().toISOString());
|
|
433
|
+
const lastSync = await managed.getPluginData("lastSync");
|
|
434
|
+
\`\`\`
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## Working with Nodes
|
|
439
|
+
|
|
440
|
+
Nodes are layers on the canvas: frames, text, images, components, etc.
|
|
441
|
+
|
|
442
|
+
### Getting Nodes
|
|
443
|
+
|
|
444
|
+
\`\`\`js
|
|
445
|
+
// Get current selection
|
|
446
|
+
const selection = await framer.getSelection();
|
|
447
|
+
|
|
448
|
+
// Get canvas root
|
|
449
|
+
const root = await framer.getCanvasRoot();
|
|
450
|
+
|
|
451
|
+
// Get node by ID
|
|
452
|
+
const node = await framer.getNode("node-id");
|
|
453
|
+
|
|
454
|
+
// Get children of a node
|
|
455
|
+
const children = await framer.getChildren(node.id);
|
|
456
|
+
|
|
457
|
+
// Get parent
|
|
458
|
+
const parent = await framer.getParent(node.id);
|
|
459
|
+
|
|
460
|
+
// Find nodes by type
|
|
461
|
+
const frameNodes = await framer.getNodesWithType("FrameNode");
|
|
462
|
+
const textNodes = await framer.getNodesWithType("TextNode");
|
|
463
|
+
|
|
464
|
+
// Find nodes with specific attribute set
|
|
465
|
+
const nodesWithBg = await framer.getNodesWithAttributeSet("backgroundColor");
|
|
466
|
+
const nodesWithImage = await framer.getNodesWithAttributeSet("backgroundImage");
|
|
467
|
+
\`\`\`
|
|
468
|
+
|
|
469
|
+
### Creating Nodes
|
|
470
|
+
|
|
471
|
+
\`\`\`js
|
|
472
|
+
// Create a frame
|
|
473
|
+
const frame = await framer.createFrameNode({
|
|
474
|
+
name: "My Frame",
|
|
475
|
+
width: 200,
|
|
476
|
+
height: 100,
|
|
477
|
+
backgroundColor: "rgba(255, 0, 0, 1)",
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Create text (creates TextNode, then use setText)
|
|
481
|
+
const textNode = await framer.createTextNode({ name: "My Text" });
|
|
482
|
+
await textNode.setText("Hello World");
|
|
483
|
+
|
|
484
|
+
// Add to specific parent
|
|
485
|
+
const child = await framer.createFrameNode({ name: "Child" }, parentNode.id);
|
|
486
|
+
\`\`\`
|
|
487
|
+
|
|
488
|
+
### Modifying Nodes
|
|
489
|
+
|
|
490
|
+
\`\`\`js
|
|
491
|
+
// Update attributes
|
|
492
|
+
await node.setAttributes({
|
|
493
|
+
name: "New Name",
|
|
494
|
+
width: 300,
|
|
495
|
+
height: 200,
|
|
496
|
+
backgroundColor: "rgba(0, 0, 255, 0.5)",
|
|
497
|
+
visible: true,
|
|
498
|
+
locked: false,
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Move to different parent
|
|
502
|
+
await framer.setParent(node.id, newParentId);
|
|
503
|
+
|
|
504
|
+
// Clone a node
|
|
505
|
+
const clone = await node.clone();
|
|
506
|
+
|
|
507
|
+
// Remove nodes
|
|
508
|
+
await framer.removeNodes([node.id]);
|
|
509
|
+
\`\`\`
|
|
510
|
+
|
|
511
|
+
### Working with Text
|
|
512
|
+
|
|
513
|
+
\`\`\`js
|
|
514
|
+
// Get text content
|
|
515
|
+
const text = await framer.getText(); // from selection
|
|
516
|
+
const nodeText = await textNode.getText();
|
|
517
|
+
|
|
518
|
+
// Set text
|
|
519
|
+
await framer.setText("New text"); // on selection
|
|
520
|
+
await textNode.setText("Hello World");
|
|
521
|
+
\`\`\`
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## Working with Images
|
|
526
|
+
|
|
527
|
+
\`\`\`js
|
|
528
|
+
// Add image to canvas
|
|
529
|
+
await framer.addImage({
|
|
530
|
+
image: "https://example.com/image.png",
|
|
531
|
+
name: "My Image",
|
|
532
|
+
altText: "Description",
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
// Set image on selected node
|
|
536
|
+
await framer.setImage({
|
|
537
|
+
image: "https://example.com/image.png",
|
|
538
|
+
altText: "Description",
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// Upload image without adding to canvas (for later use)
|
|
542
|
+
const imageAsset = await framer.uploadImage({
|
|
543
|
+
image: "https://example.com/image.png",
|
|
544
|
+
name: "My Image",
|
|
545
|
+
altText: "Alt text",
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
// Use uploaded image when creating a frame
|
|
549
|
+
await framer.createFrameNode({ backgroundImage: imageAsset });
|
|
550
|
+
|
|
551
|
+
// Get image from selection
|
|
552
|
+
const image = await framer.getImage();
|
|
553
|
+
console.log(image?.url);
|
|
554
|
+
|
|
555
|
+
// Add SVG (must be < 10kb)
|
|
556
|
+
await framer.addSVG({
|
|
557
|
+
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"><circle cx="10" cy="10" r="8"/></svg>',
|
|
558
|
+
name: "circle.svg",
|
|
559
|
+
});
|
|
560
|
+
\`\`\`
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## Working with Styles
|
|
565
|
+
|
|
566
|
+
### Color Styles
|
|
567
|
+
|
|
568
|
+
\`\`\`js
|
|
569
|
+
// List all color styles
|
|
570
|
+
const colorStyles = await framer.getColorStyles();
|
|
571
|
+
|
|
572
|
+
// Get specific style
|
|
573
|
+
const style = await framer.getColorStyle("style-id");
|
|
574
|
+
|
|
575
|
+
// Create color style (supports light/dark themes)
|
|
576
|
+
const newStyle = await framer.createColorStyle({
|
|
577
|
+
name: "Brand/Primary", // Use / for folders
|
|
578
|
+
light: "rgba(242, 59, 57, 1)",
|
|
579
|
+
dark: "rgba(180, 40, 40, 1)",
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
// Update style
|
|
583
|
+
await style.setAttributes({ light: "rgba(0, 100, 200, 1)" });
|
|
584
|
+
|
|
585
|
+
// Remove style
|
|
586
|
+
await style.remove();
|
|
587
|
+
\`\`\`
|
|
588
|
+
|
|
589
|
+
### Text Styles
|
|
590
|
+
|
|
591
|
+
\`\`\`js
|
|
592
|
+
// List all text styles
|
|
593
|
+
const textStyles = await framer.getTextStyles();
|
|
594
|
+
|
|
595
|
+
// Create text style
|
|
596
|
+
const heading = await framer.createTextStyle({
|
|
597
|
+
name: "Typography/Heading 1",
|
|
598
|
+
tag: "h1",
|
|
599
|
+
fontSize: "48px",
|
|
600
|
+
lineHeight: "1.2em",
|
|
601
|
+
fontWeight: 700,
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
// With breakpoints for responsive typography
|
|
605
|
+
const responsive = await framer.createTextStyle({
|
|
606
|
+
fontSize: "24px",
|
|
607
|
+
minWidth: 1280,
|
|
608
|
+
breakpoints: [
|
|
609
|
+
{ minWidth: 1024, fontSize: "20px" },
|
|
610
|
+
{ minWidth: 768, fontSize: "18px" },
|
|
611
|
+
{ minWidth: 320, fontSize: "16px" },
|
|
612
|
+
],
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// Update text style
|
|
616
|
+
await heading.setAttributes({ fontSize: "52px" });
|
|
617
|
+
\`\`\`
|
|
618
|
+
|
|
619
|
+
### Fonts
|
|
620
|
+
|
|
621
|
+
\`\`\`js
|
|
622
|
+
// Get available fonts
|
|
623
|
+
const fonts = await framer.getFonts();
|
|
624
|
+
|
|
625
|
+
// Get specific font
|
|
626
|
+
const font = await framer.getFont("Inter", { weight: 400 });
|
|
627
|
+
const boldFont = await framer.getFont("Inter", { weight: 700 });
|
|
628
|
+
|
|
629
|
+
// Use font in text style
|
|
630
|
+
await framer.createTextStyle({
|
|
631
|
+
name: "Body",
|
|
632
|
+
font,
|
|
633
|
+
boldFont,
|
|
634
|
+
});
|
|
635
|
+
\`\`\`
|
|
636
|
+
|
|
637
|
+
---
|
|
638
|
+
|
|
639
|
+
## Working with Code Files
|
|
640
|
+
|
|
641
|
+
\`\`\`js
|
|
642
|
+
// List code files
|
|
643
|
+
const codeFiles = await framer.getCodeFiles();
|
|
644
|
+
|
|
645
|
+
// Get specific code file
|
|
646
|
+
const file = await framer.getCodeFile("file-id");
|
|
647
|
+
|
|
648
|
+
// Create code file
|
|
649
|
+
const newFile = await framer.createCodeFile({
|
|
650
|
+
name: "MyComponent.tsx",
|
|
651
|
+
content: \`export function MyComponent() { return <div>Hello</div> }\`,
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
// Type check code
|
|
655
|
+
const errors = await framer.typecheckCode(\`const x: string = 123\`);
|
|
656
|
+
\`\`\`
|
|
657
|
+
|
|
658
|
+
---
|
|
659
|
+
|
|
660
|
+
## Component Instances
|
|
661
|
+
|
|
662
|
+
\`\`\`js
|
|
663
|
+
// Insert a component by URL
|
|
664
|
+
await framer.addComponentInstance({
|
|
665
|
+
url: "https://framer.com/m/Button-5TDo.js",
|
|
666
|
+
attributes: {
|
|
667
|
+
width: "200px",
|
|
668
|
+
height: "50px",
|
|
669
|
+
controls: { label: "Click me", variant: "primary" },
|
|
670
|
+
},
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// Insert as detached layers (editable)
|
|
674
|
+
await framer.addDetachedComponentLayers({
|
|
675
|
+
url: "https://framer.com/m/Card-abc.js",
|
|
676
|
+
attributes: { title: "My Card" },
|
|
677
|
+
});
|
|
678
|
+
\`\`\`
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
|
|
682
|
+
## Storing Data
|
|
683
|
+
|
|
684
|
+
Store metadata on nodes or globally in the project.
|
|
685
|
+
|
|
686
|
+
\`\`\`js
|
|
687
|
+
// Store global project data
|
|
688
|
+
await framer.setPluginData("myKey", "myValue");
|
|
689
|
+
const value = await framer.getPluginData("myKey");
|
|
690
|
+
|
|
691
|
+
// Store data on a node
|
|
692
|
+
await node.setPluginData("processed", "true");
|
|
693
|
+
const nodeData = await node.getPluginData("processed");
|
|
694
|
+
|
|
695
|
+
// List all keys
|
|
696
|
+
const keys = await framer.getPluginDataKeys();
|
|
697
|
+
const nodeKeys = await node.getPluginDataKeys();
|
|
698
|
+
|
|
699
|
+
// Delete data (set to null)
|
|
700
|
+
await framer.setPluginData("myKey", null);
|
|
701
|
+
\`\`\`
|
|
702
|
+
|
|
703
|
+
---
|
|
704
|
+
|
|
705
|
+
## Localization
|
|
706
|
+
|
|
707
|
+
\`\`\`js
|
|
708
|
+
// Get all locales
|
|
709
|
+
const locales = await framer.getLocales();
|
|
710
|
+
const defaultLocale = await framer.getDefaultLocale();
|
|
711
|
+
|
|
712
|
+
// Get localization groups (pages, CMS items with translations)
|
|
713
|
+
const groups = await framer.getLocalizationGroups();
|
|
714
|
+
|
|
715
|
+
// Update translations
|
|
716
|
+
const french = locales.find((l) => l.code === "fr");
|
|
717
|
+
await framer.setLocalizationData({
|
|
718
|
+
valuesBySource: {
|
|
719
|
+
[sourceId]: {
|
|
720
|
+
[french.id]: { action: "set", value: "Bonjour" },
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
\`\`\`
|
|
725
|
+
|
|
726
|
+
---
|
|
727
|
+
|
|
728
|
+
## Common Patterns
|
|
729
|
+
|
|
730
|
+
### Iterate over all nodes in project
|
|
731
|
+
|
|
732
|
+
\`\`\`js
|
|
733
|
+
const root = await framer.getCanvasRoot();
|
|
734
|
+
for await (const node of root.walk()) {
|
|
735
|
+
console.log(node.name);
|
|
736
|
+
}
|
|
737
|
+
\`\`\`
|
|
738
|
+
|
|
739
|
+
### Sync external data to collection
|
|
740
|
+
|
|
741
|
+
\`\`\`js
|
|
742
|
+
const collection = await framer.getManagedCollection();
|
|
743
|
+
const existingIds = new Set(await collection.getItemIds());
|
|
744
|
+
|
|
745
|
+
const externalData = await fetch("https://api.example.com/posts").then((r) =>
|
|
746
|
+
r.json(),
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
const items = externalData.map((post) => ({
|
|
750
|
+
id: post.id,
|
|
751
|
+
slug: post.slug,
|
|
752
|
+
fieldData: { title: post.title, content: post.body },
|
|
753
|
+
}));
|
|
754
|
+
|
|
755
|
+
await collection.addItems(items);
|
|
756
|
+
|
|
757
|
+
// Remove items no longer in external source
|
|
758
|
+
const newIds = new Set(items.map((i) => i.id));
|
|
759
|
+
const toRemove = [...existingIds].filter((id) => !newIds.has(id));
|
|
760
|
+
if (toRemove.length) await collection.removeItems(toRemove);
|
|
761
|
+
|
|
762
|
+
await collection.setPluginData("lastSync", new Date().toISOString());
|
|
763
|
+
\`\`\`
|
|
764
|
+
|
|
765
|
+
### Batch update all images' alt text
|
|
766
|
+
|
|
767
|
+
\`\`\`js
|
|
768
|
+
const nodes = await framer.getNodesWithAttributeSet("backgroundImage");
|
|
769
|
+
for (const node of nodes) {
|
|
770
|
+
if (!node.backgroundImage) continue;
|
|
771
|
+
await node.setAttributes({
|
|
772
|
+
backgroundImage: node.backgroundImage.cloneWithAttributes({
|
|
773
|
+
altText: "Updated description",
|
|
774
|
+
}),
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
\`\`\`
|
|
778
|
+
|
|
779
|
+
---
|
|
780
|
+
|
|
781
|
+
## Screenshots and SVG Export
|
|
782
|
+
|
|
783
|
+
Server API exclusive methods for capturing visual output from nodes.
|
|
784
|
+
|
|
785
|
+
\`\`\`js
|
|
786
|
+
// Take a screenshot of a node (returns Buffer + mimeType)
|
|
787
|
+
const result = await framer.screenshot(node.id);
|
|
788
|
+
console.log(result.mimeType); // "image/png"
|
|
789
|
+
await require("fs").promises.writeFile("/tmp/screenshot.png", result.data);
|
|
790
|
+
|
|
791
|
+
// With options
|
|
792
|
+
const jpg = await framer.screenshot(node.id, {
|
|
793
|
+
format: "jpeg", // "png" (default) or "jpeg"
|
|
794
|
+
quality: 90, // JPEG quality 0-100 (default 100)
|
|
795
|
+
scale: 2, // Pixel density: 0.5, 1, 1.5, 2, 3, or 4 (default 1)
|
|
796
|
+
clip: { x: 0, y: 0, width: 100, height: 100 }, // Clip region in CSS pixels
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
// Export as SVG string
|
|
800
|
+
const svgString = await framer.exportSVG(node.id);
|
|
801
|
+
await require("fs").promises.writeFile("/tmp/export.svg", svgString);
|
|
802
|
+
\`\`\`
|
|
803
|
+
|
|
804
|
+
---
|
|
805
|
+
|
|
806
|
+
## Publishing and Deployments
|
|
807
|
+
|
|
808
|
+
Server API exclusive methods for publishing and managing deployments.
|
|
809
|
+
|
|
810
|
+
\`\`\`js
|
|
811
|
+
// Publish the project (creates a new deployment)
|
|
812
|
+
const result = await framer.publish();
|
|
813
|
+
console.log(result.deployment.id); // Deployment ID
|
|
814
|
+
console.log(result.hostnames); // Array of hostnames
|
|
815
|
+
|
|
816
|
+
// Get all deployments
|
|
817
|
+
const deployments = await framer.getDeployments();
|
|
818
|
+
for (const d of deployments) {
|
|
819
|
+
console.log(d.id, d.createdAt);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Deploy a specific deployment to domains
|
|
823
|
+
const hostnames = await framer.deploy(deploymentId, ["example.com"]);
|
|
824
|
+
|
|
825
|
+
// Get current publish info (production and staging URLs)
|
|
826
|
+
const info = await framer.getPublishInfo();
|
|
827
|
+
console.log(info.production?.url); // Production URL
|
|
828
|
+
console.log(info.staging?.url); // Staging URL
|
|
829
|
+
\`\`\`
|
|
830
|
+
|
|
831
|
+
---
|
|
832
|
+
|
|
833
|
+
## Change Tracking
|
|
834
|
+
|
|
835
|
+
Server API exclusive methods for tracking project changes.
|
|
836
|
+
|
|
837
|
+
\`\`\`js
|
|
838
|
+
// Get changed paths since last publish
|
|
839
|
+
const changes = await framer.getChangedPaths();
|
|
840
|
+
console.log(changes.added); // New paths
|
|
841
|
+
console.log(changes.modified); // Modified paths
|
|
842
|
+
console.log(changes.removed); // Removed paths
|
|
843
|
+
|
|
844
|
+
// Get contributors to changes (returns user IDs)
|
|
845
|
+
const contributors = await framer.getChangeContributors();
|
|
846
|
+
\`\`\`
|
|
847
|
+
|
|
848
|
+
---
|
|
849
|
+
|
|
850
|
+
## Capabilities
|
|
851
|
+
|
|
852
|
+
What you can do with the Framer CLI:
|
|
853
|
+
|
|
854
|
+
- **CMS**: Create, read, update, delete collections and items. Sync external databases.
|
|
855
|
+
- **Canvas**: Create and modify frames, text, images, SVGs. Build layouts programmatically.
|
|
856
|
+
- **Styles**: Manage color and text styles. Sync design systems.
|
|
857
|
+
- **Components**: Insert component instances with configured properties.
|
|
858
|
+
- **Code**: Create and manage code component files.
|
|
859
|
+
- **Assets**: Upload and manage images and files.
|
|
860
|
+
- **Localization**: Manage translations programmatically.
|
|
861
|
+
- **Data**: Store metadata on nodes and projects for plugin state.
|
|
862
|
+
- **Screenshots**: Capture node screenshots as PNG/JPEG. Export nodes as SVG.
|
|
863
|
+
- **Publishing**: Publish projects, manage deployments, track changes.
|
|
864
|
+
`;
|
|
865
|
+
|
|
163
866
|
// src/types-data.ts
|
|
164
867
|
var methods = [
|
|
165
868
|
{
|
|
@@ -6299,6 +7002,9 @@ function getReferencedTypes(text, seen = /* @__PURE__ */ new Set()) {
|
|
|
6299
7002
|
return result;
|
|
6300
7003
|
}
|
|
6301
7004
|
__name(getReferencedTypes, "getReferencedTypes");
|
|
7005
|
+
program.command("skill").description("Output complete skill documentation for Claude").action(() => {
|
|
7006
|
+
print(skillDocs);
|
|
7007
|
+
});
|
|
6302
7008
|
program.command("docs [queries...]").description(
|
|
6303
7009
|
"Look up API documentation. Use: docs, docs ClassName, docs Class.method, or docs TypeName"
|
|
6304
7010
|
).action((queries) => {
|
|
@@ -9,7 +9,7 @@ import { createRequire } from 'module';
|
|
|
9
9
|
import * as vm from 'vm';
|
|
10
10
|
import { connect } from 'framer-api';
|
|
11
11
|
|
|
12
|
-
/* @framer/ai relay server v0.0.
|
|
12
|
+
/* @framer/ai relay server v0.0.2 */
|
|
13
13
|
var __defProp = Object.defineProperty;
|
|
14
14
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
15
15
|
function getLogPath() {
|
|
@@ -46,7 +46,7 @@ function log(message) {
|
|
|
46
46
|
__name(log, "log");
|
|
47
47
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
48
48
|
path.dirname(__filename$1);
|
|
49
|
-
var VERSION = "0.0.
|
|
49
|
+
var VERSION = "0.0.2" ;
|
|
50
50
|
var RELAY_PORT = Number(process.env.FRAMER_CLI_PORT) || 19987;
|
|
51
51
|
|
|
52
52
|
// src/connection-errors.ts
|