npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pencil-sync

v0.1.3

Published

Bidirectional sync between .pen design files and frontend code via Claude CLI

Readme

pencil-sync

Node.js >= 20 Tests: 312 passing License: Apache 2.0

Bidirectional sync between Pencil.dev designs and frontend code, powered by Claude Code.

Edit a component in Pencil — code updates automatically. Change code — the design follows. pencil-sync watches .pen files and your source tree, detects conflicts, and uses the Claude CLI to propagate changes in either direction — with budget controls, conflict resolution, and sync loop prevention.

Prerequisites

  • Node.js >= 20
  • Claude CLI installed and authenticated

Install

npm install
npm run build
npm link        # makes pencil-sync available globally on PATH

Quick Start

# Generate a config file
pencil-sync init

# Edit pencil-sync.config.json to point to your .pen file and code directory

# Run a one-time sync
pencil-sync sync

# Start watching for changes
pencil-sync watch

Syncing an Existing Project

To sync a project that lives in a separate repo, create a pencil-sync.config.json in that project and point --config at it:

# 1. Create a config in your project
cd /path/to/my-project
cat > pencil-sync.config.json << 'EOF'
{
  "version": 1,
  "mappings": [
    {
      "id": "my-app",
      "penFile": "./design.pen",
      "codeDir": "./src",
      "codeGlobs": ["components/**/*.tsx", "app/**/*.tsx", "**/*.css"],
      "direction": "both"
    }
  ],
  "settings": {
    "model": "claude-sonnet-4-6",
    "maxBudgetUsd": 0.5
  }
}
EOF

# 2. One-time sync
pencil-sync sync --config /path/to/my-project/pencil-sync.config.json

# 3. Or watch for live changes
pencil-sync watch --config /path/to/my-project/pencil-sync.config.json

# 4. Check status
pencil-sync status --config /path/to/my-project/pencil-sync.config.json

All paths in the config (penFile, codeDir, stateFile) are resolved relative to the config file's directory, so you can run pencil-sync from anywhere.

Example: Next.js + Tailwind project

{
  "version": 1,
  "mappings": [
    {
      "id": "my-app",
      "penFile": "./design.pen",
      "codeDir": "./frontend",
      "codeGlobs": ["components/**/*.tsx", "app/**/*.tsx", "app/**/*.css"],
      "framework": "nextjs",
      "styling": "tailwind",
      "direction": "both"
    }
  ],
  "settings": {
    "model": "claude-sonnet-4-6",
    "maxBudgetUsd": 0.5,
    "conflictStrategy": "prompt"
  }
}
# Sync only design-to-code
pencil-sync sync -c ./pencil-sync.config.json -d pen-to-code

# Watch a specific mapping (useful with multiple mappings)
pencil-sync watch -c ./pencil-sync.config.json -m my-app

Commands

| Command | Description | |---------|-------------| | pencil-sync init | Create a starter config file in the current directory | | pencil-sync sync | Run a one-time sync for all (or a specific) mapping | | pencil-sync watch | Start auto-sync file watcher | | pencil-sync status | Show sync state for all mappings |

Options

-c, --config <path>   Path to config file
-v, --verbose         Enable debug logging

Sync options

pencil-sync sync -d pen-to-code     # Force design-to-code direction
pencil-sync sync -d code-to-pen     # Force code-to-design direction
pencil-sync sync -m my-app          # Sync a specific mapping only
pencil-sync sync -n                 # Dry run: preview what would change without writing files

Configuration

Config file: pencil-sync.config.json (also supports .pencil-sync.json and JSONC with comments)

{
  "version": 1,
  "mappings": [
    {
      "id": "my-app",
      "penFile": "./design.pen",
      "codeDir": "./src",
      "codeGlobs": ["components/**/*.tsx", "app/**/*.tsx", "*.css"],
      "direction": "both"
    }
  ],
  "settings": {
    "debounceMs": 2000,
    "model": "claude-sonnet-4-6",
    "maxBudgetUsd": 0.5,
    "conflictStrategy": "prompt",
    "stateFile": ".pencil-sync-state.json",
    "logLevel": "info"
  }
}

Mapping fields

| Field | Required | Description | |-------|----------|-------------| | id | Yes | Unique identifier for this mapping | | penFile | Yes | Path to the .pen design file (relative to config) | | codeDir | Yes | Path to the code directory (relative to config) | | codeGlobs | Yes | Glob patterns for code files to track | | direction | Yes | "both", "pen-to-code", or "code-to-pen" | | penScreens | No | Specific screens to sync (defaults to all) | | framework | No | Auto-detected: nextjs, react, vue, svelte, astro | | styling | No | Auto-detected: tailwind, styled-components, css-modules, css | | styleFiles | No | CSS/config files with design tokens (e.g., ["app/globals.css", "tailwind.config.js"]). Enables the color fast path and provides context to Claude for other changes. |

Settings

| Setting | Default | Description | |---------|---------|-------------| | debounceMs | 2000 | Debounce delay for file change events | | model | claude-sonnet-4-6 | Claude model to use | | maxBudgetUsd | 0.5 | Maximum spend per session (enforced) | | conflictStrategy | prompt | How to handle conflicts: prompt, pen-wins, code-wins, auto-merge | | stateFile | .pencil-sync-state.json | Path to sync state file | | logLevel | info | Log level: debug, info, warn, error |

How It Works

File Change (chokidar)
  -> debounced trigger
    -> SyncEngine.syncMapping()
      -> LockManager.acquire()
      -> ConflictDetector (hash comparison)
      -> syncPenToCode() or syncCodeToPen()
        -> Snapshot .pen nodes, diff against previous state
        -> Fill changes:  direct CSS variable replacement (fast path)
        -> Other changes: build prompt → spawn Claude CLI → diff file hashes
      -> StateStore.updateMappingState()
      -> LockManager.release() (with grace period)

Color sync: direct replacement (fast path)

Fill/color changes are applied directly by pencil-sync as a find-and-replace in your CSS file. This is faster and more reliable than Claude CLI for colors because it's a mechanical hex→RGB conversion — no reasoning needed. Claude CLI is still used for text, typography, and layout changes that require understanding the component structure.

When a .pen node's fill property changes:

  1. The old and new hex values are converted to space-separated RGB channels (#22484634 72 70)
  2. All CSS variable declarations matching the old RGB value are replaced with the new RGB in every theme block (:root, [data-theme="monokai"], [data-theme="nord"], etc.)
  3. The updated CSS file is written back

Requirements for the fast path:

  • styleFiles must include a .css file in the mapping config
  • CSS variables must use the RGB channel format: --color-token-name: R G B;
  • Multiple theme blocks are supported — all occurrences are updated in one pass

Non-color changes (text content, font size, font weight, etc.) are still delegated to Claude CLI with a focused diff-based prompt.

// Example: enable the fast path by adding styleFiles
{
  "id": "my-app",
  "penFile": "./design.pen",
  "codeDir": "./src",
  "codeGlobs": ["**/*.tsx", "**/*.css"],
  "direction": "both",
  "styleFiles": ["app/globals.css", "tailwind.config.js"]
}

Change detection

Changes are detected by comparing SHA-256 hashes of files before and after Claude runs. This is more reliable than parsing Claude's natural language output.

Budget enforcement

Token usage is parsed from Claude CLI verbose output and accumulated per session. If cumulative spend reaches maxBudgetUsd, further sync operations are blocked.

Sync loop prevention

When a sync writes files (e.g., pen-to-code writes code files), the watcher would normally detect those writes and trigger a reverse sync. This is prevented by:

  1. A grace period (debounceMs + 500ms) that keeps the lock held after sync completes
  2. Direction-aware trigger suppression that ignores reverse-direction echoes

Conflict resolution

When both the .pen file and code files have changed since the last sync:

  • prompt — Interactive: asks the user to choose a resolution
  • pen-wins — Design takes priority, overwrites code
  • code-wins — Code takes priority, overwrites design
  • auto-merge — Claude attempts to merge both sides

MCP Integration

By default, pencil-sync reads .pen files directly from disk and delegates code edits to the Claude CLI using standard file tools (Edit, Write, Read, Glob, Grep). Enabling the Pencil MCP server gives the Claude subprocess direct, structured access to the .pen file — enabling richer code-to-design sync (reading node IDs, updating design properties, taking screenshots) without raw file parsing.

How it works

Without MCP (default)
  Claude subprocess
    └── file tools only (Edit, Write, Read, Glob, Grep)
    └── reads .pen file as raw JSON snapshot

With MCP enabled
  Claude subprocess
    └── file tools + Pencil MCP tools
          mcp__pencil__batch_get       — read nodes by ID or pattern
          mcp__pencil__batch_design    — insert / update / delete nodes
          mcp__pencil__set_variables   — update design variables / themes
          mcp__pencil__get_screenshot  — visual validation
    └── .pen contents accessed via encrypted MCP protocol (not raw file read)

Setup

  1. Install and configure the Pencil MCP server.

  2. Create an MCP config file (e.g. mcp.json):

{
  "mcpServers": {
    "pencil": {
      "command": "npx",
      "args": ["-y", "@pencil/mcp-server"]
    }
  }
}
  1. Add mcpConfigPath to your pencil-sync.config.json:
{
  "settings": {
    "model": "claude-sonnet-4-6",
    "maxBudgetUsd": 0.5,
    "mcpConfigPath": "./mcp.json"
  }
}

MCP usage flow (end-to-end)

  1. Run a one-time code-to-design sync with MCP enabled:
pencil-sync sync -d code-to-pen -c ./pencil-sync.config.json
  1. Verify MCP tool usage in logs (look for mcp__pencil__ tool calls instead of raw .pen JSON writes):
DEBUG=pencil-sync:* pencil-sync sync -d code-to-pen -c ./pencil-sync.config.json
  1. Run watcher mode for ongoing sync:
pencil-sync watch -c ./pencil-sync.config.json
  1. Validate state after a few edits:
pencil-sync status -c ./pencil-sync.config.json

Effect on sync direction

| Direction | Without MCP | With MCP | |-----------|-------------|----------| | pen-to-code | Reads .pen snapshot → color fast path or Claude file edits | Same (snapshot read is local) | | code-to-pen | Claude edits .pen as raw JSON | Claude uses batch_design / set_variables to write structured updates | | conflict auto-merge | Claude reasons over both sides via file tools | Claude can visually verify via get_screenshot |

MCP is most impactful for code-to-pen and auto-merge — the directions that need to write back to the design file.

Security note

.pen files are encrypted. The Pencil MCP server is the only supported way to read or write their contents. If mcpConfigPath is not set, pencil-sync falls back to treating the .pen file as a JSON snapshot (works for design-to-code; code-to-pen writes may be unreliable).


Development

npm run dev          # TypeScript watch mode
npm test             # Run tests
npm run test:watch   # Run tests in watch mode
npm run build        # Build for production

Docker

The container runs as the unprivileged node user (UID 1000). On Linux hosts, ensure the mounted project directory is owned by UID 1000:

# Linux: set ownership before running
sudo chown -R 1000:1000 /path/to/my-project

docker build -t pencil-sync .
docker run -v $(pwd):/project pencil-sync watch --config /project/pencil-sync.config.json

Project Structure

src/
  index.ts              CLI entry point (commander)
  sync-engine.ts        Orchestrates sync: locks, conflicts, direction routing, budget
  pen-to-code.ts        Design -> code sync with filesystem diffing
  code-to-pen.ts        Code -> design sync with hash diffing
  claude-runner.ts       Spawns Claude CLI, parses token usage
  lock-manager.ts        Per-mapping mutex with grace period and loop prevention
  state-store.ts         SHA-256 hashes persisted to JSON, file collection, diffing
  conflict-detector.ts   Detects when both sides changed since last sync
  prompt-builder.ts      Loads markdown templates, fills placeholders
  config.ts              Config loading, framework/styling auto-detection
  watcher.ts             Chokidar file watching with debounced triggers
  logger.ts              Colored timestamped logging
  __tests__/             312 tests across 19 test files (vitest)
prompts/
  pen-to-code.md         Template for design-to-code prompts
  code-to-pen.md         Template for code-to-design prompts
  conflict-resolve.md    Template for conflict resolution prompts

Keywords

pencil.dev, claude code, design to code, code to design, .pen files, bidirectional sync, AI coding, vibe coding, design sync, MCP, Anthropic, frontend tooling

License

Apache 2.0