ray-so

star 39

Generate beautiful code snippet images using ray.so and Playwriter. Builds a URL with code, theme, and settings encoded in the hash fragment, navigates to it in Chrome, and exports the image via Cmd+C clipboard copy. Use this skill when the user asks to create a code image, code screenshot, or code snippet image.

remorses By remorses schedule Updated 6/6/2026

name: ray-so repo: raycast/ray-so description: > Generate beautiful code snippet images using ray.so and Playwriter. Builds a URL with code, theme, and settings encoded in the hash fragment, navigates to it in Chrome, and exports the image via Cmd+C clipboard copy. Use this skill when the user asks to create a code image, code screenshot, or code snippet image.

ray.so Code Image Generation

Generate code snippet images by constructing a ray.so URL, navigating in Chrome via Playwriter, pressing Cmd+C to copy the rendered image to clipboard, and saving the clipboard to a file.

Always use the vercel theme unless the user specifies a different one.

URL structure

All settings live in the hash fragment (#key=value&key2=value2), not query params. Code is Base64url-encoded using Node.js Buffer.from(code).toString('base64url').

https://ray.so/#code=BASE64URL_CODE&theme=vercel&darkMode=true&background=true&padding=64&language=typescript&title=example.ts

Parameters

Parameter Default Values
code (sample) Buffer.from(code).toString('base64url')
theme candy vercel, supabase, tailwind, openai, mintlify, prisma, clerk, elevenlabs, resend, triggerdev, nuxt, browserbase, cloudflare, gemini, stripe, bitmap, noir, ice, sand, forest, mono, breeze, candy, crimson, falcon, meadow, midnight, raindrop, sunset, rabbit, firecrawl, aws, auth0
darkMode true true, false
background true true, false
padding 64 16, 32, 64, 128
language auto-detect javascript, typescript, tsx, jsx, python, go, rust, swift, shell, css, html, json, sql, ruby, java, kotlin, cpp, csharp, dart, elixir, graphql, haskell, lua, markdown, php, scala, toml, yaml, zig, diff, dockerfile, plaintext, and more
title "" Filename shown in the title bar
subtitle "" Subtitle text
width auto Pixel width (number)
lineNumbers undefined true, false
highlightedLines "" Comma-separated line numbers

Workflow: generate and save a code image

Single Playwriter call. Use the script below exactly as-is, only changing the code, language, title, theme, and output path variables. Do not restructure, reorder, or simplify the script; the clipboard interceptor must be injected before Cmd+C is pressed, and the poll loop must run after.

playwriter -s <sessionId> --timeout 30000 -e "$(cat <<'PYEOF'
const code = `export function hello() {
  return "world"
}`
const encoded = Buffer.from(code).toString('base64url')
const params = new URLSearchParams()
params.set('code', encoded)
params.set('theme', 'vercel')
params.set('darkMode', 'true')
params.set('background', 'true')
params.set('padding', '64')
params.set('language', 'typescript')
params.set('title', 'hello.ts')

// Navigate (always fresh page)
state.page = await context.newPage()
await state.page.goto('https://ray.so/#' + params.toString(), { waitUntil: 'domcontentloaded' })
await state.page.waitForSelector('#frame', { timeout: 10000 })

// Intercept clipboard write to capture the PNG blob
await state.page.evaluate(() => {
  window.__clipboardBlob = null
  const origWrite = navigator.clipboard.write.bind(navigator.clipboard)
  navigator.clipboard.write = async (items) => {
    for (const item of items) {
      if (item.types.includes('image/png')) {
        window.__clipboardBlob = await item.getType('image/png')
      }
    }
    return origWrite(items)
  }
})

// Press Cmd+C and poll until the blob is captured (no fixed sleep)
await state.page.keyboard.press('Meta+c')
let attempts = 0
while (attempts < 50) {
  const hasBlob = await state.page.evaluate(() => !!window.__clipboardBlob)
  if (hasBlob) break
  await state.page.waitForTimeout(100)
  attempts++
}
if (attempts >= 50) throw new Error('Clipboard blob not captured after 5s')

// Read blob as base64 and save via Node.js fs
const base64 = await state.page.evaluate(async () => {
  const blob = window.__clipboardBlob
  const buf = await blob.arrayBuffer()
  const bytes = new Uint8Array(buf)
  let binary = ''
  for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i])
  return btoa(binary)
})

const fs = require('node:fs')
const buffer = Buffer.from(base64, 'base64')
fs.writeFileSync('/absolute/path/to/output.png', buffer)
console.log('Saved', buffer.length, 'bytes')

// Close the page
await state.page.close()
PYEOF
)"

The clipboard poll checks every 100ms until the PNG blob is captured, with a 5s timeout. After saving, verify the output image with the read-media tool.

Agent rules

  • Always use vercel theme unless the user asks for something else
  • Always use darkMode=true, background=true, padding=64 by default
  • Set language explicitly when known; do not rely on auto-detect
  • Set title to the filename when available (e.g. greet.ts)
  • Use absolute paths for the output PNG
  • Do not use js-base64 npm package in Playwriter; use Buffer.from(code).toString('base64url') instead (Playwriter sandbox only allows Node.js built-ins)
  • Do not inspect the page between navigate and Cmd+C; the URL params are deterministic
  • After saving, verify the image with read-media tool to confirm it rendered correctly
Install via CLI
npx skills add https://github.com/remorses/opencode-config --skill ray-so
Repository Details
star Stars 39
call_split Forks 2
navigation Branch main
article Path SKILL.md
More from Creator