Skip to content

Repository files navigation

Spuddy

Spuddy

spud.cherry.surf · Download for macOS

A hand-crocheted potato who lives in the corner of your screen — and hands you a little card of encouragement, one day at a time.

He's a real crocheted doll, 3D-scanned and brought to life on your desktop. He follows your cursor, mutters to himself, remembers what you tell him, and knits you a golden card when you least expect it.


macOS Electron three.js Vite Claude OpenAI Cloudflare Workers


Spuddy sitting in the corner of the desktop, holding a card that reads 'You don't have to be perfect to be wonderful', muttering 'the lamp hums. i don't mind it.'

He doesn't take up a window. He doesn't ping you. He just sits in the bottom-right corner of your day, glancing over when you move your mouse, holding out something kind.


🥔 A card a day

Today's card popup: 'This little potato believes in you.' — Spud · day 24

Tap the little white card in his hands and he hands you today's line — something warm, something dry, something you needed to hear.

  • Tap the card to draw. Like it? Keep it ♥ and it goes into your book.
  • Draws are an unlimited little gacha — "the good ones don't run out. tap the card again, go on."
  • Every card renders on the physical card in his hands — not a speech bubble. The card is the whole point.
  • Works fully offline: a hand-written pool ships in the app, so he's never speechless.
The Card Book, Cards tab: seven kept cards in a grid, the golden ones dashed in gold and marked with a ✦, favorites marked with a filled heart, and filter chips for all / golden / favorites

Everything you keep lands in the Card Book — filter it down to just the goldens, or just the ones you loved.


✨ Golden Stitch

A rare Golden Stitch card, hand-woven by the AI and signed by Spud: 'You are not behind. You are exactly here, and here is where the good stuff starts.'

Every so often he goes quiet, takes his time, and knits a golden card by hand. Most of them are a famous line he's been saving up, signed by whoever first said it. But once he knows you well enough, now and then he weaves one live instead — out of the things you've actually talked about — and signs that one himself. Rare on purpose. Kept forever.

The card in the popup is the same one he's holding out to you in the corner.


💬 He listens — and he remembers

Spuddy answering 'today was a lot' with 'That sounds exhausting, especially with so much already on your plate. No need to make sense of it tonight — what part felt heaviest?' — the chat box below reads 'tell Spud what's on your mind…'

Hover over him and a little chat box slides out. Tell him what's on your mind — "today was a lot" — and he answers in his own voice.

  • He replies in character — powered by a live LLM, grounded in whoever's currently on duty.
  • Each buddy has a genuinely different voice: Spud above takes your side and leaves one easy handle to pick back up; the gardener would meet the same day with soil and rain, the taco with snacks and all-caps.
  • He remembers — and everything he keeps is yours to unpick, anytime.
The Memory tab: a quilt of patches, each with a little doodled potato wearing the mood — sunny ones dashed in gold, rainy ones dashed in blue with a raindrop — over facts like 'their sister Nell had a baby girl named Wren' and 'quietly worries their mum is lonely in that house alone'

What he learns about you gets distilled into the Memory quilt — one patch per thing, the sunny ones stitched in gold, the rainy ones in blue. It's the whole of what he knows, in one place, and every patch has an × on it.


🧵 Meet the crew

The Buddies collection: Spud, Taco, Sprinkles, Bloom, Mochi, Prof — 6 of 6 friends

Six little souls, each a different kind of comfort. Spud comes with the app; the other five you don't buy — you earn them, through five different kinds of care: keep · favorite · confide · show up · go gold. Once a friend, always a friend; tap Set active to put anyone on duty.


🌱 He lives his own life

Spuddy isn't a screensaver — he has needs and moods, and they drift over time (a small personality engine ticks in the background).

  • Bored? He entertains himself — chases his tail, hums, tips his card back to re-read it, practices his wave.
  • Sleepy? He nods off, jerks upright, and a poke startles him awake.
  • Missing you? He taps the glass to get your attention. Respond and he cheers; ignore him and he just wilts for a moment and quietly backs off — he'll never nag.
  • Been away a while? He gives you a little welcome-back.

He mutters through it all in soft dashed thought-bubbles ("the lamp hums. i don't mind it.") and only speaks in a solid bubble when he's actually talking to you.

And the small stuff: 90 minutes glued to your chair and he'll ask you to stretch. Past 11pm and he'll nudge you toward bed.


🎬 Every motion is hand-keyed

Nothing here is a canned clip. Every movement is authored on an eased-curve rig where the origin is always the contact point, squash preserves volume (sx·sy ≈ 1), springs overshoot on a cubic-bezier(.34, 1.56, .64, 1), and each one is paired with a soft synth blip. The long, self-directed ones — humming to himself, dozing off, reading his own card — are the ones I sweated the most.

Spuddy humming
Humming
sways on a 1.32s beat, the card keeping time like a metronome; ♪ drift off, eyes half-lidded and content
Spuddy dozing off
Dozing off
deep 5.8s breaths, lids at 82%, nods down and jerks upright — the nod-and-catch — with little Z's
Spuddy reading his card
Reading his card
tips the card back and reads it to himself, eyes tracking the line, head tilting ±4° along
Spuddy knocking on the glass
Knock knock
leans in 11°, taps the glass twice, big hopeful eyes — this is how he says he misses you

…and a dozen more under the hood — the big stretch, chasing his tail, juggling the card, waving, cheering into confetti, holding the card out to you, wilting when he's ignored, sneezing it askew, plus tap-squish, eye-roll, shy peek, hop, pirouette, look-around, and a horizontal fling that sets him spinning and settles on an underdamped spring.


🪡 What makes it special

This isn't a sprite. Under the hood:

  • It's a real crocheted doll. Every buddy started as a physical hand-crocheted amigurumi, photographed, AI-3D-scanned (Rodin), then retopologized in Blender + gltf-transform down to 1–3 MB PBR .glb files — real yarn fibers, real stitches, lit in real time with image-based lighting and Khronos PBR Neutral tone mapping in three.js.
  • The encouragement lives on the card in his hands — a live canvas texture painted onto the doll's own held card, not an overlay. The card is the soul of the potato.
  • His eyes and head follow your cursor across the whole screen (eyes lead, head trails); left alone, he blinks and glances around on his own.
  • Grab him and fling him anywhere — a horizontal throw sets him spinning, and he settles back with an underdamped spring.
  • He's part-rigged — independent claws, eyes, and card, driven by an eased-keyframe motion system, so he can wave, present, roll his eyes, and flip his card.
  • He stays out of your way — a transparent, always-on-top, click-through window docked in the corner. Clicks pass straight through to whatever's behind him.

🚀 Run it from source

cd app
yarn install
yarn start

A little potato appears in the bottom-right corner, and a matching one lands in your menu bar (Show / Hide, Language, Quit). Prefer the packaged app? Grab the signed DMG above.


🔑 Bring your own brain (optional)

Chat and Golden Stitch talk to an LLM through a tiny Cloudflare Worker gateway (server/) — the API key lives server-side as a Worker secret and never enters the app or the repo. The build ships pointing at a hosted gateway; to run your own, deploy the Worker (or run it locally) with your own key:

cd server
cp .dev.vars.example .dev.vars   # add OPENAI_API_KEY=... (or Anthropic / Gemini / DeepSeek)
npm run dev                      # http://localhost:8787

Then point the app at it:

// ~/.config/spuddy/config.json
{ "serverUrl": "http://localhost:8787" }

The gateway starts chat at GPT and each day's card pools at Claude; either walks on down the same chain (Claude · GPT · Gemini · DeepSeek, skipping any backend you haven't given a key) when one's having an off day, and both primaries flip at runtime from KV with no redeploy. It meters a per-device daily budget and pre-generates each day's card pools on a cron — so one shared key can safely carry a lot of little potatoes. With no gateway reachable, he falls back to built-in lines — everything still works, he just can't answer you in the moment. Full deploy + secrets in server/README.md.


🧶 Under the hood

app/                   TypeScript throughout (yarn typecheck / yarn build)
├─ electron/src/       main process (bundled to electron/*.cjs by esbuild) —
│                      transparent always-on-top window · tray · global cursor
│                      polling · AI gateway calls (keys stay server-side)
└─ src/
   ├─ main.ts          bootstrap — builds scene + brain, wires the modules
   ├─ app/             features — gacha draws · chat + memory · speech bubbles ·
   │                   Book/Buddies panels · taps & drags · unlocks
   ├─ scene/           three.js — scene · part rig hinges · PBR + IBL lighting ·
   │                   eased-keyframe motions · the live card texture
   ├─ ui/              popup markup — overlay · cards · book · buddies · effects
   ├─ brain.ts         the personality engine — needs, moods, autonomous behavior
   ├─ content.ts       every line, persona, and unlock rule
   └─ types.ts · store.ts · sfx.ts · remote.ts
server/                optional Cloudflare Worker gateway (key vault · budget · cron),
                       also TypeScript (src/worker.ts · generate.ts · providers.ts)

The 3D pipeline (real doll → scan → Blender + gltf-transform → Draco/WEBP .glb) and per-character card-plane calibration are documented in app/README.md.


📜 License

Spuddy is split-licensed, on purpose:

  • The code is open. The source under app/ and server/ is MIT — fork it, learn from it, build your own thing with it. See LICENSE.
  • My work on Spuddy isn't. The parts I actually made — the 3D modeling & rigging, the hand-keyed animations, the renders, the written lines, and the Spuddy app & name — are all-rights-reserved. Please don't lift them. See ASSETS-LICENSE.md.
  • The dolls aren't mine to give. Each buddy is based on a hand-crocheted doll originally made by someone else — I don't claim those underlying designs, and neither should you.

In short: take the engine and learn from it — just don't lift my models, animations, or writing. 🥔





“Rest is productive. Signed, a potato.”

Made with yarn, three.js, and a lot of encouragement.

About

A hand-crocheted potato desktop pet for macOS — he follows your cursor, mutters to himself, remembers what you tell him, and hands you a little card of encouragement, one day at a time. Built with Electron + three.js.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages