@opendirectory.dev/skills 0.1.59 → 0.1.61

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.
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env bash
2
+ # export-chart.sh — Screenshot a Chart.js HTML file as a high-quality retina PNG
3
+ #
4
+ # Usage:
5
+ # bash scripts/export-chart.sh <path-to-html> [output.png] [--width N] [--height N]
6
+ #
7
+ # Examples:
8
+ # bash scripts/export-chart.sh ./my-chart/chart.html
9
+ # bash scripts/export-chart.sh ./my-chart/chart.html ./chart.png --width 1200 --height 628
10
+ #
11
+ # What this does:
12
+ # 1. Starts a local HTTP server so CDN scripts and fonts load correctly
13
+ # 2. Launches headless Chromium at the specified viewport with deviceScaleFactor: 2
14
+ # 3. Waits for window.__chartReady === true (set by Chart.js animation.onComplete)
15
+ # 4. Takes a PNG screenshot — output is 2× the specified dimensions (retina quality)
16
+ # 5. Cleans up temp files and opens the result
17
+ #
18
+ # Output PNG = 2× viewport size (e.g. 1080×1080 viewport → 2160×2160 PNG)
19
+ set -euo pipefail
20
+
21
+ # ─── Colors ────────────────────────────────────────────────
22
+ RED='\033[0;31m'
23
+ GREEN='\033[0;32m'
24
+ CYAN='\033[0;36m'
25
+ YELLOW='\033[1;33m'
26
+ BOLD='\033[1m'
27
+ NC='\033[0m'
28
+
29
+ info() { echo -e "${CYAN}ℹ${NC} $*"; }
30
+ ok() { echo -e "${GREEN}✓${NC} $*"; }
31
+ warn() { echo -e "${YELLOW}⚠${NC} $*"; }
32
+ err() { echo -e "${RED}✗${NC} $*" >&2; }
33
+
34
+ # ─── Parse flags ──────────────────────────────────────────
35
+ WIDTH=1080
36
+ HEIGHT=1080
37
+
38
+ POSITIONAL=()
39
+ while [[ $# -gt 0 ]]; do
40
+ case $1 in
41
+ --width) WIDTH="$2"; shift 2 ;;
42
+ --height) HEIGHT="$2"; shift 2 ;;
43
+ *) POSITIONAL+=("$1"); shift ;;
44
+ esac
45
+ done
46
+ set -- "${POSITIONAL[@]}"
47
+
48
+ # ─── Input validation ─────────────────────────────────────
49
+
50
+ if [[ $# -lt 1 ]]; then
51
+ err "Usage: bash scripts/export-chart.sh <path-to-html> [output.png] [--width N] [--height N]"
52
+ err ""
53
+ err "Examples:"
54
+ err " bash scripts/export-chart.sh ./my-chart/chart.html"
55
+ err " bash scripts/export-chart.sh ./my-chart/chart.html ./chart.png --width 1200 --height 628"
56
+ exit 1
57
+ fi
58
+
59
+ INPUT_HTML="$1"
60
+ if [[ ! -f "$INPUT_HTML" ]]; then
61
+ err "File not found: $INPUT_HTML"
62
+ exit 1
63
+ fi
64
+
65
+ INPUT_HTML=$(cd "$(dirname "$INPUT_HTML")" && pwd)/$(basename "$INPUT_HTML")
66
+
67
+ if [[ $# -ge 2 ]]; then
68
+ OUTPUT_PNG="$2"
69
+ else
70
+ OUTPUT_PNG="$(dirname "$INPUT_HTML")/$(basename "$INPUT_HTML" .html).png"
71
+ fi
72
+
73
+ OUTPUT_DIR=$(dirname "$OUTPUT_PNG")
74
+ mkdir -p "$OUTPUT_DIR"
75
+ OUTPUT_PNG="$OUTPUT_DIR/$(basename "$OUTPUT_PNG")"
76
+
77
+ echo ""
78
+ echo -e "${BOLD}╔══════════════════════════════════════╗${NC}"
79
+ echo -e "${BOLD}║ Export Chart to PNG ║${NC}"
80
+ echo -e "${BOLD}╚══════════════════════════════════════╝${NC}"
81
+ echo ""
82
+ info "Viewport: ${WIDTH}×${HEIGHT}px → PNG output: $((WIDTH*2))×$((HEIGHT*2))px @2× retina"
83
+ echo ""
84
+
85
+ # ─── Step 1: Check Node.js ────────────────────────────────
86
+
87
+ info "Checking dependencies..."
88
+
89
+ if ! command -v node &>/dev/null; then
90
+ err "Node.js is required but not installed."
91
+ err ""
92
+ err "Install Node.js:"
93
+ err " macOS: brew install node"
94
+ err " or visit https://nodejs.org and download the installer"
95
+ exit 1
96
+ fi
97
+
98
+ ok "Node.js found ($(node --version))"
99
+
100
+ # ─── Step 2: Create temp dir + copy screenshot script ─────
101
+
102
+ TEMP_DIR=$(mktemp -d)
103
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
104
+ TEMP_SCRIPT="$TEMP_DIR/screenshot-chart.mjs"
105
+ cp "$SCRIPT_DIR/screenshot-chart.mjs" "$TEMP_SCRIPT"
106
+
107
+ SERVE_DIR=$(dirname "$INPUT_HTML")
108
+ HTML_FILENAME=$(basename "$INPUT_HTML")
109
+
110
+ # ─── Step 3: Install Playwright ───────────────────────────
111
+
112
+ info "Setting up Playwright (headless Chromium)..."
113
+ info "This may take a moment on first run..."
114
+ echo ""
115
+
116
+ cd "$TEMP_DIR"
117
+
118
+ cat > "$TEMP_DIR/package.json" << 'PKG'
119
+ { "name": "chart-export", "private": true, "type": "module" }
120
+ PKG
121
+
122
+ npm install playwright 2>/dev/null || {
123
+ err "Failed to install Playwright."
124
+ err "Try running: npm install playwright"
125
+ rm -rf "$TEMP_DIR"
126
+ exit 1
127
+ }
128
+
129
+ npx playwright install chromium 2>/dev/null || {
130
+ err "Failed to install Chromium browser for Playwright."
131
+ err "Try running manually: npx playwright install chromium"
132
+ rm -rf "$TEMP_DIR"
133
+ exit 1
134
+ }
135
+
136
+ ok "Playwright ready"
137
+ echo ""
138
+
139
+ # ─── Step 4: Screenshot ───────────────────────────────────
140
+
141
+ info "Launching headless Chromium and capturing chart..."
142
+ echo ""
143
+
144
+ node "$TEMP_SCRIPT" \
145
+ "$SERVE_DIR" \
146
+ "$HTML_FILENAME" \
147
+ "$OUTPUT_PNG" \
148
+ "$WIDTH" \
149
+ "$HEIGHT" || {
150
+ err "Chart screenshot failed."
151
+ err ""
152
+ err "Common causes:"
153
+ err " - window.__chartReady never set (check animation.onComplete in HTML)"
154
+ err " - Chart.js CDN script failed to load (check internet connection)"
155
+ err " - HTML syntax error (open in browser to verify)"
156
+ rm -rf "$TEMP_DIR"
157
+ exit 1
158
+ }
159
+
160
+ # ─── Step 5: Cleanup + success ────────────────────────────
161
+
162
+ rm -rf "$TEMP_DIR"
163
+
164
+ echo ""
165
+ echo -e "${BOLD}════════════════════════════════════════${NC}"
166
+ ok "Chart exported successfully!"
167
+ echo ""
168
+ echo -e " ${BOLD}File:${NC} $OUTPUT_PNG"
169
+ echo ""
170
+ FILE_SIZE=$(du -h "$OUTPUT_PNG" | cut -f1 | xargs)
171
+ echo " Size: $FILE_SIZE"
172
+ echo " Dimensions: $((WIDTH*2))×$((HEIGHT*2))px (@2× retina)"
173
+ echo ""
174
+ echo " Ready for social, decks, reports, or email."
175
+ echo -e "${BOLD}════════════════════════════════════════${NC}"
176
+ echo ""
177
+
178
+ if command -v open &>/dev/null; then
179
+ open "$OUTPUT_PNG"
180
+ elif command -v xdg-open &>/dev/null; then
181
+ xdg-open "$OUTPUT_PNG"
182
+ fi
@@ -0,0 +1,143 @@
1
+ // screenshot-chart.mjs — Capture an ECharts v6 HTML file as a retina-quality PNG
2
+ //
3
+ // Args: <serve-dir> <html-filename> <output-png> <width> <height>
4
+ //
5
+ // How it works:
6
+ // 1. Starts a local HTTP server to serve the HTML and allow CDN scripts to load
7
+ // 2. Launches Chromium at the specified viewport with deviceScaleFactor: 2
8
+ // → output PNG is 2× viewport dimensions (retina quality)
9
+ // 3. Waits for window.__chartReady === true
10
+ // → set by chart.on('finished', ...) in the HTML (registered BEFORE setOption)
11
+ // → belt-and-suspenders: also set by chart.on('rendered', ...) + 100ms debounce
12
+ // → ECharts bug #14101/#17500: 'finished' silently skips if registered after setOption
13
+ // 4. Takes the screenshot and saves as PNG
14
+ //
15
+ // CRITICAL: The HTML must register events BEFORE chart.setOption():
16
+ // chart.on('finished', () => { window.__chartReady = true; });
17
+ // chart.on('rendered', () => { clearTimeout(t); t = setTimeout(() => { window.__chartReady = true; }, 100); });
18
+ // chart.setOption(option); // setOption LAST
19
+
20
+ import { chromium } from 'playwright';
21
+ import { createServer } from 'http';
22
+ import { readFileSync, writeFileSync } from 'fs';
23
+ import { join, extname } from 'path';
24
+
25
+ const SERVE_DIR = process.argv[2];
26
+ const HTML_FILE = process.argv[3];
27
+ const OUTPUT_PNG = process.argv[4];
28
+ const VP_WIDTH = parseInt(process.argv[5]) || 1080;
29
+ const VP_HEIGHT = parseInt(process.argv[6]) || 1080;
30
+
31
+ // ─── Static file server ───────────────────────────────────
32
+ // Required for CDN scripts and Google Fonts to load via HTTP
33
+ // (file:// protocol blocks cross-origin requests)
34
+
35
+ const MIME_TYPES = {
36
+ '.html': 'text/html',
37
+ '.css': 'text/css',
38
+ '.js': 'application/javascript',
39
+ '.json': 'application/json',
40
+ '.png': 'image/png',
41
+ '.jpg': 'image/jpeg',
42
+ '.svg': 'image/svg+xml',
43
+ '.woff': 'font/woff',
44
+ '.woff2':'font/woff2',
45
+ '.ttf': 'font/ttf',
46
+ };
47
+
48
+ const server = createServer((req, res) => {
49
+ const decoded = decodeURIComponent(req.url);
50
+ const filePath = join(SERVE_DIR, decoded === '/' ? HTML_FILE : decoded);
51
+ try {
52
+ const content = readFileSync(filePath);
53
+ const ext = extname(filePath).toLowerCase();
54
+ res.writeHead(200, { 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream' });
55
+ res.end(content);
56
+ } catch {
57
+ res.writeHead(404);
58
+ res.end('Not found');
59
+ }
60
+ });
61
+
62
+ const port = await new Promise((resolve) => {
63
+ server.listen(0, () => resolve(server.address().port));
64
+ });
65
+
66
+ console.log(` Local server on port ${port}`);
67
+
68
+ // ─── Launch browser at 2× deviceScaleFactor ──────────────
69
+ // deviceScaleFactor: 2 → screenshot is 2× VP dimensions (retina)
70
+ // --font-render-hinting=none → sharper text on Linux CI
71
+
72
+ const browser = await chromium.launch({
73
+ args: [
74
+ '--no-sandbox',
75
+ '--disable-dev-shm-usage',
76
+ '--font-render-hinting=none',
77
+ ]
78
+ });
79
+
80
+ const context = await browser.newContext({
81
+ viewport: { width: VP_WIDTH, height: VP_HEIGHT },
82
+ deviceScaleFactor: 2,
83
+ });
84
+
85
+ const page = await context.newPage();
86
+
87
+ // Capture browser console errors for diagnostics
88
+ const pageErrors = [];
89
+ page.on('console', msg => {
90
+ if (msg.type() === 'error') pageErrors.push(msg.text());
91
+ });
92
+ page.on('pageerror', err => pageErrors.push(err.message));
93
+
94
+ // ─── Navigate and wait for chart ─────────────────────────
95
+ // Use networkidle to ensure CDN scripts (Chart.js, plugins) finish loading
96
+ // before we check window.__chartReady
97
+
98
+ await page.goto(`http://localhost:${port}/`, { waitUntil: 'networkidle' });
99
+
100
+ // Wait for fonts (CDN Google Fonts need a moment)
101
+ await page.evaluate(() => document.fonts.ready);
102
+
103
+ // Wait for Chart.js to finish rendering
104
+ // window.__chartReady is set by animation.onComplete in the HTML
105
+ // Timeout: 15s — generous to allow CDN scripts on slow networks
106
+ console.log(' Waiting for chart to render...');
107
+ try {
108
+ await page.waitForFunction(() => window.__chartReady === true, { timeout: 15000 });
109
+ } catch {
110
+ // Provide a helpful error message
111
+ const bodyHTML = await page.evaluate(() => document.body.innerHTML.substring(0, 500));
112
+ console.error(' ERROR: window.__chartReady was never set after 15s.');
113
+ if (pageErrors.length > 0) {
114
+ console.error(' Browser console errors:');
115
+ pageErrors.forEach(e => console.error(' ', e));
116
+ }
117
+ console.error(' Check that your HTML chart config includes:');
118
+ console.error(' animation: { duration: 0, onComplete: () => { window.__chartReady = true; } }');
119
+ console.error(' If using CDN scripts, verify internet access from the headless browser.');
120
+ console.error(' Page body preview:', bodyHTML);
121
+ await browser.close();
122
+ server.close();
123
+ process.exit(1);
124
+ }
125
+
126
+ console.log(' Chart rendered — taking screenshot');
127
+
128
+ // ─── Screenshot ───────────────────────────────────────────
129
+ // animations: 'disabled' stops CSS transitions — does NOT affect Chart.js canvas
130
+ // The canvas is already fully drawn at this point (waited for __chartReady)
131
+
132
+ await page.screenshot({
133
+ path: OUTPUT_PNG,
134
+ animations: 'disabled',
135
+ clip: { x: 0, y: 0, width: VP_WIDTH, height: VP_HEIGHT },
136
+ });
137
+
138
+ await browser.close();
139
+ server.close();
140
+
141
+ const { statSync } = await import('fs');
142
+ const sizeKB = Math.round(statSync(OUTPUT_PNG).size / 1024);
143
+ console.log(` ✓ PNG saved: ${OUTPUT_PNG} (${sizeKB}KB, ${VP_WIDTH * 2}×${VP_HEIGHT * 2}px)`);
@@ -0,0 +1,87 @@
1
+ # vid-motion-graphics
2
+
3
+ Generates multi-scene motion graphics as MP4 from a content brief. HTML/CSS animations rendered frame-by-frame in headless Chromium via Playwright, assembled with FFmpeg. No React, no AI APIs, no Python.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npx "@opendirectory.dev/skills" install vid-motion-graphics --target claude
9
+ ```
10
+
11
+ ### Video Tutorial
12
+ Watch this quick video to see how it's done:
13
+
14
+ https://github.com/user-attachments/assets/ee98a1b5-ebc4-452f-bbfb-c434f2935067
15
+
16
+ ### Step 1: Download the skill from GitHub
17
+ 1. Click the **Code** button on this repo's GitHub page.
18
+ 2. Select **Download ZIP** to download the repository.
19
+ 3. Extract the ZIP file on your computer.
20
+
21
+ ### Step 2: Install the Skill in Claude
22
+ 1. Open your **Claude desktop app**.
23
+ 2. Go to the sidebar on the left side and click on the **Customize** section.
24
+ 3. Click on the **Skills** tab, then click on the **+** (plus) icon button to create a new skill.
25
+ 4. Choose the option to **Upload a skill**, and drag and drop the `.zip` file (or you can extract it and drop the folder, both work).
26
+
27
+ > **Note:** Make sure you are uploading the folder that contains the `SKILL.md` file!
28
+
29
+ ## What it does
30
+
31
+ 1. Takes a content brief (what each scene should say) and style params
32
+ 2. Generates a multi-scene HTML/CSS animation file
33
+ 3. Captures frames via Playwright + Web Animations API seeking
34
+ 4. Assembles PNG sequence → H.264 MP4 with FFmpeg
35
+ 5. Optionally mixes in background audio
36
+
37
+ ## Output
38
+
39
+ - Format: H.264 MP4, `-pix_fmt yuv420p` (compatible with QuickTime, iOS, Android, Twitter, LinkedIn, Instagram)
40
+ - Default: 1080×1080px @2× retina (2160×2160 actual)
41
+ - Supports: 1:1, 16:9 (1920×1080), 9:16 (1080×1920)
42
+ - FPS: 24, 30, or 60
43
+
44
+ ## Scene types
45
+
46
+ | Type | Best for |
47
+ |---|---|
48
+ | `title-card` | Opening hook, brand intro |
49
+ | `stat-reveal` | Single oversized metric |
50
+ | `bullet-list` | 2–4 supporting points |
51
+ | `split-screen` | Before/after, two values |
52
+ | `quote-card` | Testimonial, pull quote |
53
+ | `cta-card` | Final scene, call to action |
54
+
55
+ ## Style presets
56
+
57
+ | Preset | Feel |
58
+ |---|---|
59
+ | `kinetic-dark` | Dark bg, electric yellow, tight grotesque (default) |
60
+ | `editorial-light` | White bg, serif display, refined |
61
+ | `data-pulse` | Deep navy, mono, terminal/dashboard |
62
+ | `bold-type` | White bg, Bebas Neue, red accent, slam-in |
63
+ | `minimal-clean` | Off-white, Cormorant, gentle rise |
64
+
65
+ ## Requirements
66
+
67
+ - Node.js (for Playwright frame capture)
68
+ - FFmpeg (for MP4 assembly)
69
+ - Internet access for Google Fonts CDN during capture
70
+
71
+ ## Usage
72
+
73
+ ```
74
+ Create a 9-second motion graphic. Brief: 'Q4 revenue hit $4.2M — 85% growth.
75
+ Three drivers: enterprise deals, churn 1.2%, price increase.
76
+ CTA: acme.com/q4'. Style: data-pulse. Aspect: 1:1.
77
+ ```
78
+
79
+ ## Quick export
80
+
81
+ ```bash
82
+ bash scripts/export-video.sh chart/[slug]/video.html --duration 9
83
+ ```
84
+
85
+ ## Differentiator vs SkillsMP
86
+
87
+ All top SkillsMP motion-graphic skills require Remotion (React build step) or AI video APIs (Runway, Kling — cost + rate limits). This skill uses the proven HTML/CSS → Playwright → FFmpeg pipeline from the `graphic-gif` family: zero new dependencies, pixel-perfect output, full CSS control.