Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 6 additions & 32 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -1,38 +1,12 @@
# Gitleaks configuration for ledjay-skills
# https://github.com/gitleaks/gitleaks

title = "ledjay-skills secrets detection"
title = "ledjay-skills gitleaks config"

[extend]
# Use default rules plus custom ones
useDefault = true

# Allowlist - files that might have false positives
[[allowlists]]
description = "Allow example files"
paths = [
'''_template/.*''',
'''examples/.*''',
'''\.design-system/.*''',
]

# Custom rules for skill-specific patterns
[[rules]]
id = "llm-api-key"
description = "LLM API Key"
regex = '''(?i)(openai|anthropic|claude|gemini|groq)[-_]?(api[_-]?key|key)\s*[=:]\s*['\"][a-zA-Z0-9_-]{20,}['\"]'''
tags = ["api", "llm", "key"]

[[rules]]
id = "mcp-server-token"
description = "MCP Server Token"
regex = '''(?i)mcp[_-]?(token|secret|key)\s*[=:]\s*['\"][a-zA-Z0-9_-]{16,}['\"]'''
tags = ["mcp", "token"]

# Entropy-based detection for random strings
[[rules]]
id = "high-entropy-string"
description = "High entropy string (potential secret)"
regex = '''['"]([a-zA-Z0-9_-]{32,})['"]'''
entropy = 4.5
tags = ["entropy"]
# Allowlist for false positives
[allowlist]
description = "Ignore bundled .cjs files and alphabets"
paths = ['''pencil-mcp/scripts/.*\.cjs$''']
regexes = ['''[A-Za-z0-9]{50,}''']
277 changes: 126 additions & 151 deletions pencil-mcp/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: pencil-mcp
description: Control Pencil.dev design tool via MCP to create, inspect, and export UI designs programmatically. Covers batch_design DSL, design tokens, components, and code sync. Use when working with .pen files, Pencil MCP, design systems, or mockups.
version: 2.1.0
version: 2.3.0
compatible-agents:
- letta-code
- codex
Expand Down Expand Up @@ -31,210 +31,185 @@ Control **Pencil** (pencil.dev) via its MCP server. Create designs, manage token
- Creating designs programmatically via MCP
- Building design systems (tokens, components, variants)
- Exporting assets (PNG, JPEG, PDF)
- Debugging Pencil MCP issues (gotchas, token costs)
- Debugging Pencil MCP issues

**DON'T use for:**
- General design tasks (use Pencil desktop app directly)
- Non-Pencil design tools (Figma, Sketch, etc.)

## ⚠️ Critical Warning
## Rule Categories

> **NEVER write `.pen` JSON files directly.**
>
> The `.pen` format is an internal schema. Writing JSON manually will open in Pencil but show an **empty canvas**.
>
> **Always use MCP tools** (`batch_design`, `set_variables`, etc.) to create content.
| Priority | Category | Impact | Prefix |
|----------|----------|--------|--------|
| 1 | **Critical** | Design breaks | `critical-` |
| 2 | **Workflow** | Performance | `workflow-` |
| 3 | **Gotchas** | Unexpected behavior | `gotcha-` |
| 4 | **Optimization** | Token costs | `opt-` |

## Prerequisites
## Quick Reference

- Pencil desktop app running (`/Applications/Pencil.app`)
- Dependencies: `cd <skill-path>/scripts && npm install && npm run build`
### 1. Critical Rules (MUST follow)

## Core Concepts
- `critical-no-direct-json` — NEVER write `.pen` JSON directly, use MCP tools
- `critical-hex-colors` — Use hex only (`#RRGGBB`), OKLCH renders invisible
- `critical-pencil-running` — Pencil desktop app MUST be running before MCP calls
- `critical-save-new-files` — New files require manual save (Cmd+S) before MCP works

| Concept | Description |
|---------|-------------|
| **MCP Client** | Two modes: CLI (one-off calls) vs Library (complex workflows) |
| **batch_design DSL** | Custom DSL for insert/update/delete operations (max 25 per call) |
| **Design Tokens** | 2-level architecture: primitives (hex) → semantics (refs) |
| **Shell Pattern** | Pencil doesn't have Figma variants. Shell Pattern = workaround for component variants |
| **Theme Pattern** | For orthogonal visual axes (Color × Style × State) |
| **Schema Tax** | `get_editor_state` returns ~9,500 tokens of static schema every call |
### 2. Workflow Rules

## Quick Start
- `workflow-choose-mode` — CLI (1 call) / Batch (2-5 calls) / Library (6+ calls)
- `workflow-always-screenshot` — Call `get_screenshot` after every `batch_design`
- `workflow-user-action` — Use `user_action` step when manual intervention needed
- `workflow-capture-ids` — Capture IDs for resuming across user actions

### CLI Mode (one-off calls)
### 3. Gotcha Rules

> ⚠️ **Important for CLI mode**: Each `pencil.cjs` invocation opens a **new MCP connection**. This means:
> - `filePath` context is LOST between calls
> - Bindings from `batch_design` are NOT available in subsequent calls
> - You MUST capture and reuse node IDs between invocations
> - WebSocket reconnection messages (`attempt 1/3`) are **normal**
- `gotcha-max-25-ops` — Max 25 operations per `batch_design`
- `gotcha-max-10-tokens` — Max 5-10 tokens per `set_variables`
- `gotcha-no-font-variables` — Use literal strings for `fontFamily`, not variables
- `gotcha-relative-paths` — Use relative paths for `filePath` (absolute = timeout)
- `gotcha-ephemeral-bindings` — Bindings die between CLI calls, use returned IDs

```bash
# Recommended timeout: 30-60s (not 15-20s)
# Always use the compiled bundle (10x faster: 0.15s vs 1.5s)
### 4. Optimization Rules

# 1. Open document (use RELATIVE path to avoid timeout)
node <skill-path>/scripts/pencil.cjs call open_document '{
"filePath": "design.pen"
}'
- `opt-cache-nodes` — Cache nodes with `batch_get` depth 0, reuse for lookups
- `opt-use-compiled` — Use `pencil.cjs` (10x faster than `tsx`)
- `opt-batch-mode` — Batch mode = 5x+ faster than CLI for multiple operations

## Mode Selection

# 2. Create tokens (filePath optional if file just opened)
```
How many MCP operations?
├─ 1 → CLI mode: node pencil.cjs call <tool> '<json>'
├─ 2-5 → Batch mode: node pencil-batch.cjs '{"steps":[...]}'
└─ 6+ → Library mode: import { PencilClient } from './pencil.ts'
```

## CLI Mode (single operation)

```bash
# Each call = new connection (bindings lost!)
node <skill-path>/scripts/pencil.cjs call set_variables '{
"variables": {
"primary-500": {"type": "color", "value": "#3D7A4F"}
}
"variables": {"primary": {"type": "color", "value": "#3D7A4F"}}
}'
# → Returns: {"success": true}
```

# 3. Create design (CAPTURE the returned IDs!)
node <skill-path>/scripts/pencil.cjs call batch_design '{
"operations": "card=I(document,{type:\"frame\",name:\"Card\",width:300,layout:\"vertical\",gap:8,padding:[16,16],fill:\"#FFFFFF\",cornerRadius:12})"
}'
# → Returns: {"insertedIds": ["ZwNEy", ...], "bindings": {"card": "ZwNEy"}}
# ⚠️ Bindings are NO LONGER AVAILABLE in next call!
# Use the insertedIds in subsequent operations.
## Batch Mode (multiple operations, one connection)

# 4. Add children to the card (use the ID from step 3)
node <skill-path>/scripts/pencil.cjs call batch_design '{
"operations": "I(\"ZwNEy\",{type:\"text\",content:\"Hello\",fontSize:16})"
}'
# Note: filePath may be needed here since context was lost
> 🏆 **Recommended for 2-5 operations** — 5x+ faster than CLI.

# 5. Get screenshot (MUST specify filePath in CLI mode)
node <skill-path>/scripts/pencil.cjs call get_screenshot '{
"nodeId": "ZwNEy",
```bash
node <skill-path>/scripts/pencil-batch.cjs '{
"filePath": "design.pen",
"outputPath": "./screenshot.png"
"steps": [
{ "tool": "open_document", "args": { "filePathOrTemplate": "design.pen" } },
{ "tool": "set_variables", "args": { "variables": {...} } },
{ "tool": "batch_design", "args": { "operations": "..." }, "capture": "card" },
{ "tool": "user_action", "message": "Please save the file (Cmd+S)" },
{ "tool": "get_screenshot", "args": { "nodeId": "${card}", "outputPath": "out.png" } }
]
}'
```

**Rebuild after editing pencil.ts:**
```bash
cd <skill-path>/scripts && npm run build
**Features:**
- Single MCP connection for all steps
- Auto-injects `filePath` where needed
- Variable substitution: `${name}` → captured node ID
- `user_action` step: pauses for manual intervention
- Returns `capturedIds` + `remainingSteps` for resuming

### user_action Step

When manual intervention is needed (e.g., saving a new file):

```json
{ "tool": "user_action", "message": "Please save the file (Cmd+S), then tell me to continue" }
```

**Returns:**
```json
{
"resume": {
"userActionRequired": "Please save...",
"capturedIds": { "card": "ABC123" },
"remainingSteps": [...],
"resumeHint": "JSON to copy-paste for continuing"
}
}
```

### Library Mode (complex workflows)
**As LLM:** Ask user to perform action, then continue with:
```json
{
"filePath": "...",
"capturedIds": { "card": "ABC123" },
"steps": [...] // from resume.remainingSteps
}
```

> 🏆 **Recommended for multi-step operations** — single connection, no context loss.
## Library Mode (complex workflows)

For 6+ operations, write a TypeScript script:

```typescript
import { PencilClient } from './pencil.ts'
import { batch, screenshot, getNodes, setTokens } from './helpers.ts'
import { PencilClient } from './pencil.js'
import { batch, screenshot, getNodes, setTokens } from './helpers.js'

const pencil = new PencilClient()
await pencil.connect()

// Create tokens
await setTokens(pencil, {
'primary-500': { type: 'color', value: '#3D7A4F' },
})
const nodes = await getNodes(pencil) // Cache once
await setTokens(pencil, { 'primary': { type: 'color', value: '#3D7A4F' } })

// Create design
const { insertedIds } = await batch(pencil, `
card=I(document,{type:"frame",name:"Card",width:300,layout:"vertical",gap:8,padding:[16,16],fill:"#FFFFFF",cornerRadius:12})
I(card,{type:"text",content:"Hello",fontSize:16})
card=I(document,{type:"frame",name:"Card"})
I(card,{type:"text",content:"Hello"})
`)

// Visual verification
await screenshot(pencil, insertedIds[0], './card.png')

await pencil.disconnect()
```

## CLI vs Library Mode
## New File Limitation

| Aspect | CLI Mode | Library Mode |
|--------|----------|--------------|
| **Connection** | New per call | Persistent |
| **filePath context** | LOST between calls | Preserved |
| **Bindings** | Ephemeral | Available |
| **Speed** | Slower (reconnection) | 5-10x faster |
| **Use case** | One-off operations | Multi-step workflows |

## Gotchas Checklist

Before any Pencil MCP call, check:

- [ ] **Pencil desktop app is running**
- [ ] **Using hex colors** (OKLCH renders invisible)
- [ ] **Relative paths** for `filePath` (absolute = timeout)
- [ ] **Max 25 ops** per `batch_design`
- [ ] **Max 5-10 tokens** per `set_variables`
- [ ] **Timeout set to 30-60s** (not 15-20s)
- [ ] **No variables in `fontFamily`** (use literal strings)
- [ ] **Always call `get_screenshot`** after design changes

See `references/gotchas.md` for complete list.
> ⚠️ **Pencil MCP cannot save files programmatically.**
>
> When creating a new file:
> 1. Batch script creates minimal `.pen` on disk
> 2. Pencil opens it but content is "Untitled"
> 3. **User MUST manually save (Cmd+S)**
> 4. Use `user_action` step to pause and ask user
> 5. Continue after user confirms

## Tool Reference

| Tool | Use for |
|------|---------|
| `open_document` | Open/create `.pen` file |
| `batch_design` | Insert/update/delete nodes (max 25 ops) |
| `batch_get` | Read nodes by ID or pattern |
| `get_screenshot` | Visual verification — ALWAYS call after design |
| `set_variables` | Create design tokens |
| `get_variables` | Read existing tokens |
| `export_nodes` | Export PNG/JPEG/PDF |
| Tool | Use for | Gotcha |
|------|---------|--------|
| `open_document` | Open/create file | Use `filePathOrTemplate` |
| `batch_design` | Create/modify nodes | Max 25 ops |
| `batch_get` | Read nodes | Use depth 0 to avoid tax |
| `get_screenshot` | Visual verification | ALWAYS call after design |
| `set_variables` | Create tokens | Max 5-10 per call |
| `export_nodes` | Export PNG/JPEG/PDF | — |

## Reference Files

| File | Content |
|------|---------|
| `references/dsl.md` | Complete batch_design DSL syntax |
| `references/components.md` | Shell Pattern, slots, variants, compositions |
| `references/tokens.md` | 2-level token architecture, theming |
| `references/gotchas.md` | Critical pitfalls and workarounds |
| `references/mcp-optimization.md` | Token cost audit, caching strategies |
| `references/pen-schema.md` | `.pen` JSON schema (read-only!) |

## Examples

### Create a design system

```typescript
// See references/components.md for Shell Pattern
const family = await createComponentFamily(pencil, {
shell: {
name: 'Button',
width: 160,
layout: 'horizontal',
gap: 8,
padding: [12, 24],
cornerRadius: 8,
fill: '#3D7A4F',
children: [
{ type: 'text', name: 'btn-label', content: 'Label', fontSize: 14 },
],
},
variants: [
{ name: 'Primary', fill: '#3D7A4F' },
{ name: 'Danger', fill: '#DC2626' },
],
})
```

### Export assets

```typescript
await exportNodes(pencil, [cardId], {
format: 'PNG',
scale: 2,
outputDir: './exports',
})
```
| `references/components.md` | Shell Pattern, slots, variants |
| `references/tokens.md` | 2-level token architecture |
| `references/gotchas.md` | Complete pitfalls list |
| `references/mcp-optimization.md` | Token cost audit |
| `references/pen-schema.md` | `.pen` schema (READ ONLY!) |

## Troubleshooting

| Issue | Solution |
|-------|----------|
| Empty canvas | You wrote `.pen` JSON directly — use MCP tools |
| "wrong .pen file" | Add `filePath` to the call (context lost in CLI mode) |
| Timeout | Use relative path, increase timeout to 60s |
| Invisible colors | You used OKLCH — use hex only |
| Screenshot fails | Add `filePath` parameter in CLI mode |
| Bindings not found | They're ephemeral in CLI — use returned IDs |
| WebSocket reconnection | Normal behavior in CLI mode — ignore |
| Empty canvas | Wrote `.pen` JSON directly — use MCP tools |
| "wrong .pen file" | Add `filePath` or use batch mode |
| Invisible colors | Used OKLCH — use hex only |
| Timeout | Relative path, timeout 60s |
| Bindings not found | Ephemeral in CLI — use returned IDs |
| New file won't export | User must save (Cmd+S) first |
Loading
Loading