mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
* feat(web): add pet companion with Codex hatch-pet integration
Introduces a customizable floating pet companion (overlay + entry-view rail
+ composer menu + dedicated Settings → Pets section) that supports built-in
pets, user customization (glyph/image/spritesheet), and one-click adoption
of pets packaged by the upstream Codex `hatch-pet` skill via a new
`/api/codex-pets` daemon endpoint. Vendors the unmodified `hatch-pet`
skill under `skills/hatch-pet/` and adds i18n strings across all locales.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(scripts): sync community Codex pets from public catalogs
Adds `pnpm sync:community-pets` which fetches all pets from
codex-pet-share.pages.dev (paginated Supabase Functions API) and
j20.nz/hatchery (single-shot JSON), then writes each one as
`<id>/pet.json` + `<id>/spritesheet.webp` under
`\${CODEX_HOME:-\$HOME/.codex}/pets/`. The existing daemon
`codex-pets` registry already scans that folder, so synced pets
appear under Settings → Pets → Recently hatched and adopt with one
click — no manual upload. Supports --source/--out/--force/--limit
flags and validates magic bytes so HTML error pages never end up
masquerading as `.webp` files.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(daemon): tighten codex-pets validation and document vendoring
- sanitizeId now rejects ids that still contain `..` after collapsing,
closing a defensive gap on the path-traversal guard for the
`/api/codex-pets/:id/spritesheet` route.
- listCodexPets emits the sanitised folder name as the public id so the
download route resolves directly against the on-disk folder, even when
`manifest.id` differs (manual drops, sanitiser-touched manifests).
- Drop `@ts-nocheck` from `codex-pets.ts`; module is now strict-typed
with explicit interfaces, an unknown-narrowed JSON.parse path, and a
`pickString` helper guarding manifest fields one by one.
- Restrict the spritesheet response CORS header to sandboxed-iframe
callers (Origin: null) instead of unconditional `*`, matching the
existing raw-file route pattern. Same-origin web traffic does not
need the header (web proxies `/api/*` through the daemon).
- Add `skills/hatch-pet/README.md` explaining the vendoring trade-off,
provenance, and re-sync procedure.
- Add `docs/codex-pets.md` covering where pets live, how to populate the
registry without Codex installed, and the manifest contract.
Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)
* fix(i18n): add pet.* keys to Hungarian locale
Hungarian locale was added on main after this branch diverged, so the new
pet.* dictionary keys never landed there and tsc -b reports hu's Dict as
incomplete once main is merged in.
Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code)
* feat(web): atlas-driven pet animations + bundled community pets
Builds on the existing pet companion (#296) with a richer animation
loop, a curated set of community pets that ship with the repo, and a
one-click sync into ~/.codex/pets/.
- Atlas-mode rendering: PetSpriteFace can now play the full Codex 8x9
sprite atlas and swap rows from a JS-driven frame index. PetOverlay
classifies pointer interactions (idle / hover / drag-direction /
long-idle waiting) and maps them to the matching atlas row, so the
pet waves on hover, runs on drag, and falls into a waiting pose
after 6s of stillness. Single-strip pets keep their existing CSS
steps() animation, with the steps timing fixed to jump-none so frame
cells line up on cell boundaries.
- Atlas adoption: PetSettings exposes both "Use full atlas (animated)"
and "Freeze to this row" — full mode keeps every row for the
interaction state machine, single-row mode crops one strip via the
existing canvas helper. New prepareCodexAtlas downscales the atlas
to a localStorage-friendly PNG while preserving the grid layout.
- Settings tabs: pet sources are now split into Built-in / Custom /
Community tabs so each origin gets its own dedicated surface.
- Bundled pets: scripts/bake-community-pets.ts seeds a curated set
(clippit, dario, nyako-shigure, slavik, trump, tux, yelling-dario,
yorha-sit-2b) into assets/community-pets/. The daemon scans this
alongside the user's ~/.codex/pets/ root, with user pets winning
when ids collide. CodexPetSummary gains a `bundled` flag so the UI
can tag those cards with a "Bundled" pill.
- One-click community sync: daemon-side port of sync-community-pets
exposed via POST /api/codex-pets/sync. Returns the same
wrote/skipped/failed/total summary the CLI prints. Web Pet settings
surface this as a "Download community pets" button under the
Community tab.
- Avatar dropdown + hide rail: EntryView's avatar button is now a
small menu (mirrors the project-view AvatarMenu) with toggles for
hiding/showing the pet rail and opening Settings. PetRail gets a
matching × button for the same hide flow.
- Locales: 7 new pet.* keys for tabs, sync, hide/show, atlas full
mode, and the Bundled pill — translated into all 13 supported
locales.
Typechecks pass across all workspace packages; daemon + web vitest
suites stay green.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(web): bundled-pets built-in tab, ambient atlas animations, and community sync button
The Built-in tab now sources its catalog from the bundled spritesheets
at `assets/community-pets/` instead of the eight emoji placeholders that
felt boring next to the Codex hatch-pet atlases.
- Daemon: `listCodexPets` flags `bundled: true` by curated-set membership
in `assets/community-pets/`, not by which folder the sprite happened to
be read from. Previously a fully-synced user inbox preempted every
bundled id and left the tab empty.
- Settings → Pets → Built-in renders the same sprite-card grid as
Community, filtered by `bundled: true`, and reuses the existing
`adoptCodexPet` flow. Community tab filters to non-bundled so the
curated set never appears twice.
- Community tab gains the long-promised "Download community pets"
trigger that calls `/api/codex-pets/sync` and shows an inline status
line for the run summary. Strings already existed in every locale; we
just plumbed the button.
- `PetOverlay` gets ambient atlas-row choreography — while idle, the
overlay occasionally swaps `idle` for a random non-idle row (wave /
hop / look) so the pet doesn't feel frozen. User gestures cancel the
beat and take over instantly. `pickAmbientRow` lives next to
`pickAtlasRow` so both row pickers share the fallback discipline.
- One-shot `migrateCustomPetAtlas` heals configs adopted before the
overlay learned row switching by re-downloading the full spritesheet
so hover / drag / ambient variety light up on next launch.
- `BUILT_IN_PETS` is now an empty array (the type stays for backwards
compat); legacy configs whose `petId` still points at an emoji id
(`mochi`, `pixel`, …) fall back to the user's custom slot in
`resolveActivePet` so the overlay never renders blank.
- i18n: refresh `pet.tabBuiltInHint` (drop "emoji companions") and add
`pet.builtInEmpty` across all locales.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
92 lines
3.6 KiB
Markdown
92 lines
3.6 KiB
Markdown
# Codex pets
|
|
|
|
The pet companion in the web app can adopt pets packaged by the upstream
|
|
Codex `hatch-pet` skill. This doc explains where those pets live, how
|
|
Open Design discovers them, and what to do if you do not have Codex
|
|
installed.
|
|
|
|
## Where pets live
|
|
|
|
The daemon scans this directory on every list request:
|
|
|
|
```
|
|
${CODEX_HOME:-$HOME/.codex}/pets/<pet-id>/
|
|
pet.json # { id, displayName, description, spritesheetPath }
|
|
spritesheet.webp # 1536x1872 8x9 atlas (.png / .gif also accepted)
|
|
```
|
|
|
|
`CODEX_HOME` is honoured if set; otherwise the daemon falls back to
|
|
`~/.codex/pets/`. Both paths follow the upstream Codex conventions.
|
|
|
|
The scan is implemented in `apps/daemon/src/codex-pets.ts` and surfaced
|
|
through `GET /api/codex-pets` (list) and
|
|
`GET /api/codex-pets/:id/spritesheet` (raw bytes). The web pet settings
|
|
panel calls these endpoints from
|
|
`apps/web/src/components/pet/PetSettings.tsx` under the
|
|
"Recently hatched" section.
|
|
|
|
## I do not have Codex installed
|
|
|
|
You do not need Codex to use Open Design. The pet companion ships with
|
|
built-in pets that work out of the box. The "Recently hatched" section
|
|
will simply stay empty until something appears under
|
|
`${CODEX_HOME:-$HOME/.codex}/pets/`.
|
|
|
|
You have three ways to populate it without running Codex:
|
|
|
|
1. **Sync the public catalogs.** Run
|
|
`node --experimental-strip-types scripts/sync-community-pets.ts`
|
|
(see the script header for flags). It downloads pets from the
|
|
community catalogs into the canonical Codex layout, then they show
|
|
up under "Recently hatched" on the next refresh.
|
|
2. **Drop a pet folder in by hand.** Create
|
|
`~/.codex/pets/<your-pet>/` with a `pet.json` and a
|
|
`spritesheet.webp` (8x9 atlas). The daemon does not require Codex to
|
|
be installed — it only needs the directory.
|
|
3. **Run the vendored skill in any chat agent.** The `hatch-pet` skill
|
|
is vendored under `skills/hatch-pet/`. Any agent that can execute
|
|
skills (Codex, or any other) can run it end-to-end and write into the
|
|
same directory.
|
|
|
|
If `~/.codex/pets/` does not exist, the daemon does **not** auto-create
|
|
it — empty list is returned and the UI shows "no recently hatched pets
|
|
yet". Creating the directory is intentionally an explicit user step so
|
|
the daemon never writes outside `OD_DATA_DIR` / project-owned paths
|
|
without a user opting in.
|
|
|
|
## Manifest shape
|
|
|
|
The `pet.json` manifest is read defensively — every field is treated as
|
|
optional and validated as a string before use. The shape we honour:
|
|
|
|
```json
|
|
{
|
|
"id": "shiba-pomegranate",
|
|
"displayName": "Shiba Pom",
|
|
"description": "Friendly pixel-art shiba.",
|
|
"spritesheetPath": "spritesheet.webp"
|
|
}
|
|
```
|
|
|
|
Notes:
|
|
|
|
- The folder name is the on-disk identity. The list endpoint reports
|
|
the sanitised folder name as the public `id` so that
|
|
`/api/codex-pets/:id/spritesheet` can resolve it directly even when
|
|
`manifest.id` differs from the folder name (e.g. the manifest declares
|
|
spaces or punctuation that get sanitised away).
|
|
- `spritesheetPath` is resolved relative to the pet folder and is
|
|
rejected if it would escape the folder. If unset, we fall back to
|
|
`spritesheet.webp`, then `.png`, then `.gif`.
|
|
- Any field that is not a non-empty string is ignored and the UI falls
|
|
back to a sensible default (folder name → display name, empty
|
|
description, etc.).
|
|
|
|
## Related code
|
|
|
|
- Daemon registry + manifest validation: `apps/daemon/src/codex-pets.ts`
|
|
- HTTP routes (list + spritesheet): `apps/daemon/src/server.ts`
|
|
- Web list / adopt UI: `apps/web/src/components/pet/PetSettings.tsx`
|
|
- Shared response types: `packages/contracts/src/api/registry.ts`
|
|
- Vendored skill source: `skills/hatch-pet/`
|
|
- Community catalog sync script: `scripts/sync-community-pets.ts`
|