Skill: jackleeio/skill-qr-card (version 0.1.2)

--- name: skill-qr-card description: Generate styled QR images/cards for ClawHub skills so users can scan and install instantly. Supports install-command payloads, ClawHub links, and GitHub links, with share-ready outputs (SVG/PNG). Use when users ask for “scan to install” visuals, QR sharing assets, or branded QR variants for skill distribution. --- # Skill QR Card Create a styled QR card (SVG or PNG) that users can scan to install a skill instantly. ## Workflow 1. Collect inputs - skill slug (required) - display title (optional) - target payload mode: install command / ClawHub URL / README URL 2. Generate QR card - Use `scripts/generate_qr_card.js`. 3. Return output files - SVG (always) - PNG (optional if ImageMagick `convert` is available) 4. Share result - Send generated image path directly. ## Commands - Basic: - `node scripts/generate_qr_card.js --slug skill-feed` - Custom title + mode: - `node scripts/generate_qr_card.js --slug skill-feed --title "SkillFeed" --mode install` - Custom output: - `node scripts/generate_qr_card.js --slug skill-feed --out ./images/skillfeed-card.svg` ## Modes - `install` (default): QR payload = `clawhub install <slug>` - `clawhub`: QR payload = `https://clawhub.ai/<slug>` - `github`: QR payload = `https://github.com/<owner>/<repo>` (requires `--github`) ## Output - `MEDIA:./images/<file>.svg` line is printed for quick sharing. - Optional PNG is generated when system has `convert` installed. ## References - Styling and payload conventions: `references/design-guidelines.md`

path: README.md
content: # Skill QR Card

Generate a polished, shareable QR card image for any ClawHub skill.

## What it does

Skill QR Card creates visual QR assets that people can scan to:

- install a skill directly (`clawhub install <slug>`)
- open the skill on ClawHub
- open a GitHub repository

It is designed for "scan-to-install" sharing in chats, docs, and social posts.

## Features

- Styled QR card output (SVG)
- Optional PNG export (when ImageMagick `convert` is available)
- Multiple payload modes:
  - `install` (default)
  - `clawhub`
  - `github`
- Safe slug validation

## Quick start

```bash
cd skills/skill-qr-card

# Default: QR payload = clawhub install skill-feed
node scripts/generate_qr_card.js --slug skill-feed

# Use ClawHub URL payload
node scripts/generate_qr_card.js --slug skill-feed --mode clawhub

# Use GitHub URL payload
node scripts/generate_qr_card.js --slug skill-feed --mode github --github jackleeio/skill-feed
```

## Script options

```bash
node scripts/generate_qr_card.js \
  --slug <skill-slug> \
  [--title "Display Title"] \
  [--mode install|clawhub|github] \
  [--github <owner/repo>] \
  [--out ./images/custom.svg] \
  [--no-png]
```

## Output

- Main output: SVG card in `./images/`
- Optional output: PNG card (if `convert` exists)
- Script prints `MEDIA:./...` lines for direct chat attachment flows

## Project structure

- `SKILL.md` – skill workflow and usage
- `scripts/generate_qr_card.js` – QR card generator
- `references/design-guidelines.md` – visual and payload conventions

## Install from ClawHub

```bash
clawhub install skill-qr-card
```


path: images/2026-03-09-08-53-skill-feed-qr-card.svg
content: <?xml version="1.0" encoding="UTF-8"?>
<svg width="960" height="600" viewBox="0 0 960 600" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
      <stop offset="0%" stop-color="#0f172a"/>
      <stop offset="100%" stop-color="#1e293b"/>
    </linearGradient>
    <linearGradient id="accent" x1="0" y1="0" x2="1" y2="0">
      <stop offset="0%" stop-color="#22d3ee"/>
      <stop offset="100%" stop-color="#818cf8"/>
    </linearGradient>
  </defs>

  <rect x="0" y="0" width="960" height="600" fill="url(#bg)"/>
  <rect x="60" y="60" width="840" height="480" rx="28" fill="#0b1220" stroke="#334155"/>
  <rect x="60" y="60" width="840" height="10" rx="28" fill="url(#accent)"/>

  <text x="110" y="140" fill="#e2e8f0" font-family="Inter, system-ui, sans-serif" font-size="46" font-weight="700">SkillFeed</text>
  <text x="110" y="180" fill="#94a3b8" font-family="Inter, system-ui, sans-serif" font-size="24">Scan to install this skill</text>

  <rect x="520" y="120" width="320" height="320" rx="16" fill="#ffffff"/>
  <g transform="translate(540,140)"><path fill="#ffffff" d="M0 0h25v25H0z"/><path stroke="#111827" d="M0 0.5h7m1 0h1m1 0h3m1 0h3m1 0h7M0 1.5h1m5 0h1m2 0h1m1 0h2m1 0h1m1 0h1m1 0h1m5 0h1M0 2.5h1m1 0h3m1 0h1m2 0h1m1 0h1m2 0h1m1 0h1m1 0h1m1 0h3m1 0h1M0 3.5h1m1 0h3m1 0h1m1 0h2m2 0h4m2 0h1m1 0h3m1 0h1M0 4.5h1m1 0h3m1 0h1m1 0h2m1 0h2m1 0h3m1 0h1m1 0h3m1 0h1M0 5.5h1m5 0h1m1 0h1m3 0h3m3 0h1m5 0h1M0 6.5h7m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h7M8 7.5h1m2 0h1m2 0h3M0 8.5h1m3 0h1m1 0h3m1 0h3m4 0h5m2 0h1M0 9.5h2m1 0h1m1 0h1m8 0h2m4 0h1M0 10.5h1m4 0h4m3 0h1m1 0h3m1 0h2m2 0h1M0 11.5h1m3 0h1m4 0h1m1 0h2m2 0h1m1 0h2m1 0h1m1 0h2M0 12.5h4m2 0h2m1 0h2m1 0h1m2 0h1m1 0h3m1 0h3M0 13.5h2m1 0h3m1 0h2m1 0h1m1 0h1m1 0h4m2 0h2M2 14.5h3m1 0h6m2 0h4M3 15.5h2m2 0h3m1 0h2m2 0h1m2 0h1m3 0h1m1 0h1M0 16.5h3m1 0h1m1 0h2m4 0h1m1 0h7m1 0h1m1 0h1M8 17.5h1m1 0h2m2 0h1m1 0h1m3 0h1m1 0h1M0 18.5h7m1 0h1m1 0h3m1 0h1m1 0h1m1 0h1m1 0h3M0 19.5h1m5 0h1m2 0h1m1 0h2m1 0h1m1 0h1m3 0h1m1 0h3M0 20.5h1m1 0h3m1 0h1m1 0h1m1 0h1m1 0h1m3 0h7M0 21.5h1m1 0h3m1 0h1m3 0h2m1 0h3m1 0h3m1 0h1m1 0h2M0 22.5h1m1 0h3m1 0h1m3 0h7m1 0h1m3 0h2M0 23.5h1m5 0h1m3 0h3m2 0h1m4 0h1m1 0h2M0 24.5h7m1 0h3m1 0h1m2 0h1m2 0h3m1 0h3"/></g>

  <text x="110" y="260" fill="#cbd5e1" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="22">Slug: skill-feed</text>
  <text x="110" y="305" fill="#cbd5e1" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="22">Mode: install</text>

  <rect x="110" y="360" width="360" height="56" rx="12" fill="#111827" stroke="#334155"/>
  <text x="130" y="395" fill="#22d3ee" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="20">clawhub install skill-feed</text>

  <text x="110" y="500" fill="#64748b" font-family="Inter, system-ui, sans-serif" font-size="18">Generated by qr-skill-card</text>
</svg>

path: references/design-guidelines.md
content: # Design Guidelines

## Visual style

- Dark card background with soft gradient header
- Clear title and short subtitle
- High-contrast QR area (white background)
- Footer with payload preview

## Payload defaults

- Prefer `clawhub install <slug>` for fastest user success
- Use direct URL mode when users scan on desktop and open browser first

## File naming

- Use timestamp + slug: `yyyy-mm-dd-hh-mm-<slug>-qr-card.svg`

## Safety

- Validate slug to lowercase/url-safe pattern
- Avoid embedding secrets/tokens in payload


path: scripts/generate_qr_card.js
content: #!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const QRCode = require('qrcode');

function arg(name, fallback = null) {
  const i = process.argv.indexOf(`--${name}`);
  if (i === -1) return fallback;
  return process.argv[i + 1] ?? fallback;
}

function hasFlag(name) {
  return process.argv.includes(`--${name}`);
}

const slug = arg('slug');
if (!slug || !/^[a-z0-9][a-z0-9-]{1,63}$/.test(slug)) {
  console.error('Usage: node scripts/generate_qr_card.js --slug <skill-slug> [--title <title>] [--mode install|clawhub|github] [--github <owner/repo>] [--out <file.svg>]');
  process.exit(1);
}

const title = arg('title', slug.replace(/-/g, ' ').replace(/\b\w/g, s => s.toUpperCase()));
const mode = arg('mode', 'install');
const github = arg('github');
const ts = new Date().toISOString().replace(/[:T]/g, '-').slice(0, 16);
const out = arg('out', `./images/${ts}-${slug}-qr-card.svg`);

let payload = `clawhub install ${slug}`;
if (mode === 'clawhub') payload = `https://clawhub.ai/${slug}`;
if (mode === 'github') {
  if (!github) {
    console.error('--github <owner/repo> is required for --mode github');
    process.exit(1);
  }
  payload = `https://github.com/${github}`;
}

fs.mkdirSync(path.dirname(out), { recursive: true });

(async () => {
  const qrSvgRaw = await QRCode.toString(payload, {
    type: 'svg',
    errorCorrectionLevel: 'M',
    margin: 0,
    width: 360,
    color: { dark: '#111827', light: '#ffffff' },
  });

  const qrBody = qrSvgRaw
    .replace(/^[\s\S]*?<svg[^>]*>/, '')
    .replace(/<\/svg>\s*$/, '');

  const svg = `<?xml version="1.0" encoding="UTF-8"?>
<svg width="960" height="600" viewBox="0 0 960 600" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
      <stop offset="0%" stop-color="#0f172a"/>
      <stop offset="100%" stop-color="#1e293b"/>
    </linearGradient>
    <linearGradient id="accent" x1="0" y1="0" x2="1" y2="0">
      <stop offset="0%" stop-color="#22d3ee"/>
      <stop offset="100%" stop-color="#818cf8"/>
    </linearGradient>
  </defs>

  <rect x="0" y="0" width="960" height="600" fill="url(#bg)"/>
  <rect x="60" y="60" width="840" height="480" rx="28" fill="#0b1220" stroke="#334155"/>
  <rect x="60" y="60" width="840" height="10" rx="28" fill="url(#accent)"/>

  <text x="110" y="140" fill="#e2e8f0" font-family="Inter, system-ui, sans-serif" font-size="46" font-weight="700">${escapeXml(title)}</text>
  <text x="110" y="180" fill="#94a3b8" font-family="Inter, system-ui, sans-serif" font-size="24">Scan to install this skill</text>

  <rect x="520" y="120" width="320" height="320" rx="16" fill="#ffffff"/>
  <g transform="translate(540,140)">${qrBody}</g>

  <text x="110" y="260" fill="#cbd5e1" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="22">Slug: ${escapeXml(slug)}</text>
  <text x="110" y="305" fill="#cbd5e1" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="22">Mode: ${escapeXml(mode)}</text>

  <rect x="110" y="360" width="360" height="56" rx="12" fill="#111827" stroke="#334155"/>
  <text x="130" y="395" fill="#22d3ee" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="20">${escapeXml(payload)}</text>

  <text x="110" y="500" fill="#64748b" font-family="Inter, system-ui, sans-serif" font-size="18">Generated by skill-qr-card</text>
</svg>`;

  fs.writeFileSync(out, svg, 'utf8');
  console.log(`Wrote: ${out}`);
  console.log(`MEDIA:${out.startsWith('./') ? out : './' + path.relative(process.cwd(), out)}`);

  if (!hasFlag('no-png')) {
    const pngOut = out.replace(/\.svg$/i, '.png');
    try {
      execSync(`convert -background none "${out}" "${pngOut}"`, { stdio: 'ignore' });
      console.log(`Wrote: ${pngOut}`);
      console.log(`MEDIA:${pngOut.startsWith('./') ? pngOut : './' + path.relative(process.cwd(), pngOut)}`);
    } catch {
      // optional
    }
  }
})();

function escapeXml(s) {
  return String(s)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');
}


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: clean | SkillSpector: clean (LOW)