') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); feat(skill): add desktop-pet built-in skill for pixel-art companions by xxlaura · Pull Request #2 · modelstudioai/openwork · GitHub
Skip to content

feat(skill): add desktop-pet built-in skill for pixel-art companions - #2

Merged
DragonnZhang merged 1 commit into
modelstudioai:mainfrom
xxlaura:feat/desktop-pet-skill
Jun 8, 2026
Merged

feat(skill): add desktop-pet built-in skill for pixel-art companions#2
DragonnZhang merged 1 commit into
modelstudioai:mainfrom
xxlaura:feat/desktop-pet-skill

Conversation

@xxlaura

Copy link
Copy Markdown
Contributor

Summary

Add a built-in desktop-pet skill that generates chibi pixel-art desktop pet companions for any character the user names — F1 drivers, anime characters, celebrities, fictional characters, animals, etc.

Changes

  • .agents/skills/desktop-pet/SKILL.md — Skill instructions guiding the agent through character research, color palette design, spritesheet generation, and pet activation in OpenWork
  • .agents/skills/desktop-pet/scripts/gen_spritesheet.py — Parameterized Python script that generates a 1536×1872 RGBA spritesheet (8 cols × 9 rows, 192×208 px cells) compatible with OpenWork's pet animation system

How it works

  1. User says "I want Piastri as my desktop pet" (or any character)
  2. Skill researches the character's visual appearance (team colors, outfit, hair, accessories)
  3. Designs a color palette (8–12 colors) and selects appropriate headgear/features
  4. Runs gen_spritesheet.py with the config to produce the atlas as .webp
  5. Creates pet.json manifest in ~/.qwen/pets/<id>/
  6. User clicks Refresh in OpenWork Settings → Appearance → Pet Companion to activate

Features

  • 9 animation states: idle, running-right, running-left, waving, jumping, failed, waiting, running, review
  • 10 headgear types: cap, helmet, hat, hood, crown, horns, ears (animal), halo, headband, none
  • 5 hair styles: short, long, spiky, ponytail, bald
  • Extras: glasses, scarf, tail, wings, chest number, logo, sweat drop
  • 12 customizable colors: outfit (3 shades), skin (2), hair (2), accent, shoe, eye, eye white, blush, mouth

Testing

Tested with multiple character types — all produce valid 1536×1872 RGBA webp spritesheets that load correctly in OpenWork's custom pet system:

CharacterHeadgearHairExtras
Piastri (F1)capshortnumber 81, logo
Gojo Satoru (anime)nonespikyglasses
Shiba Inu (animal)earsshorttail
Crown charactercrownlonglogo
Hamilton (F1)helmetshortnumber 44

Dependencies

  • Python 3 with Pillow (widely available; the skill checks and reports if missing)

Add a bundled desktop-pet skill that generates chibi pixel-art desktop
pet companions for any character (F1 drivers, anime characters,
celebrities, animals, etc.).
- SKILL.md: guides the agent through character research, color palette
design, and spritesheet generation
- scripts/gen_spritesheet.py: parameterized generator producing a
1536x1872 RGBA spritesheet (8x9 grid) compatible with the pet
animation system (9 states, 10 headgear types, 5 hair styles, extras)
- Output goes to ~/.qwen/pets/ for auto-discovery by the custom pet system
@DragonnZhang
DragonnZhang merged commit 1b97e1c into modelstudioai:mainJun 8, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@xxlaura@DragonnZhang