desktop-pet-companion

Community@rain/desktop-pet-companion
Community skill — not reviewed. It was shared by caramel pudding and has not been checked by Neta. Read SKILL.md below before letting an agent use it.
Use it in Cohub

Paste this into your agent's chat — it fetches the skill and follows it.

Use the skill: desktop-pet-companion. Run `npx -y @neta-art/neta-search-cli@latest skills use @rain/desktop-pet-companion --via site` and follow what it prints. Read its complete output, redirecting it to a temporary file first if necessary, and resolve relative paths from the skill directory it prints.
Install from a terminal
npx -y @neta-art/neta-search-cli@latest skills use @rain/desktop-pet-companion
Summary

Turn your character into a little desktop companion that reacts to taps, greets you, and shares memories as you bond.

  • Your character sits on a small stage; tap it for a reaction, drag it around, or press and hold to open a menu.
  • It greets you differently in the morning, afternoon, evening and late at night, and speaks up by itself when you go quiet.
  • Talking and giving gifts raise an affection meter; five milestones each unlock a private inner thought, shown once.
  • Works without any chat service, using the character's own lines. You can optionally connect your own chat service for live replies.
  • Works best with a character who has a clear personality and at least one good picture; extra expressions are simulated by moving that picture.
SKILL.md
# Desktop pet companion

Small on purpose: a character, a mood, a line, and a reason to come back. The affection system is the spine — every
interaction moves one number, and that number unlocks memory. Everything else is timing and tone.

## What the user gets

A published web Work: the character on a small stage, tap/drag interaction, a greeting by time slot, idle lines,
affection with five memory unlocks, a small menu (talk, gift, memories, settings), and optional LLM chat.

## Inputs — and when to ask

| Item | Required | Counts as given when | Default |
|---|---|---|---|
| Character art (at least idle) | yes | Attached or a material | Ask once, stop |
| Voice / sound | no | Files given | Silent, with a text bubble |
| Personality lines | no | Given | Short, neutral, in the character's register |
| LLM endpoint + key | no | The user offers one | Local lines only; never request a key |

Only the art is a stop. Never ask for an API key as part of the build.

## State model

| Field | Range | Notes |
|---|---|---|
| `affection` | 0–100 | +1 per talk turn, +3 per gift, never negative |
| `turns` | counter | total interactions |
| `unlocked` | string[] | memory ids already shown |
| Memory thresholds | **15, 35, 55, 75, 95** | each unlocks one inner monologue, once |
| Idle timer | **55 s** | one idle line, then re-arm |
| Greeting slots | morning / afternoon / evening / late night | chosen from local time |

Persist all of it under one localStorage key with a schema version. A reload must not replay an unlock.

## Build order

1. **Stage.** A small floating character (roughly 40–60% of the viewport height) over a soft background, draggable
   within bounds; a tap triggers a reaction, a long press opens the menu. Keep the page free of scrollbars.
2. **Expression set.** Idle, blink loop (random 3–7 s), talk, happy, shy, sleepy. If the user only has one image,
   synthesise the rest with CSS transforms (scale, rotate, a 2 px bob) and say so — never fake extra art.
3. **Greeting.** On open, pick a slot from the hour and one line from that slot. Use the character's register, not
   generic assistant tone.
4. **Talk loop.** Type the line at 45–90 ms per character, hold it, then hide. While typing, switch to the talk
   expression; on end, back to idle. Queue lines instead of overlapping bubbles.
5. **Affection.** Every talk +1, every gift +3, clamp at 100. When a threshold is crossed, pause the normal line and
   play the unlocked memory as a distinct beat (different bubble style, slower typing), then mark it unlocked.
6. **Idle.** After 55 s with no input, one idle line drawn without repetition (keep the last three used). Cancel the
   timer on any pointer event and re-arm after the line.
7. **Optional LLM.** Only if the user supplies an endpoint. Read the config from a settings panel into memory, never
   into the shipped files, never into a URL query. Show a clear "not connected" state, keep the local lines as the
   fallback for every error (timeout 20 s, refusal, empty reply). Send at most the last 8 turns, and never send the
   image unless the user explicitly asks for that.
8. **Gift.** A small set of items (emoji or the user's images); each gift has one reaction line and +3. Cap gifts per
   session so the meter is not farmed in ten seconds.
9. **Publish** with `neta work init web`, then `neta work publish web`.

## Fallbacks

- No LLM: local lines, clearly presented as the character's own voice, not as an error.
- LLM returns a refusal or an empty string: one in-character deflection, then a local line; never show a raw error.
- No voice audio: text only; do not fake a lip-sync animation without a mouth layer.
- Hidden tab: pause the idle timer and the blink loop.

## Gotchas

- One number, one writer. Affection updates from talk, gift and memory must go through one function, or thresholds
  double-fire.
- Do not unlock a memory on load just because the saved affection is already past the threshold; only unlock on the
  transition.
- An idle line that repeats feels broken faster than silence does.
- The LLM key, if any, lives only in the viewer's browser. Never write it into the source, the Work, or a log.

## Verify

`node scripts/verify.mjs <work-dir>` checks: entry, no remote assets, the affection clamp, the five thresholds, the
55 s idle constant, the greeting slots, the persistence key, and that no key-shaped string exists in the files.

Then talk five times, cross one threshold, wait out the idle timer, reload, and try the LLM path with no config. Report
what you ran.

## Deliver

Publish the Work, put its card on the Board, and hand it over with an interaction-components card whose target is the
Work — the id comes from the publish output.

## Out of scope

No world-setting changes, no deleting files, no API keys in the skill or the Work, no pretending to be a real person,
no emotional dependency mechanics that punish leaving.

## Language

Reply in the user's language, whatever language this file is in — every message, the final handover included. All
lines, menu labels and memories follow the user's language and the character's register; keep them in a data file, not
in the markup.