Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
**/.*/
.DS_Store
node_modules/
13 changes: 7 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Repo notes for AI coding agents (Codex, Claude, others).
Microsoft Clippit/Clippy ACS extracted into:

- 43 animated WebPs (one per ACS animation), in `assets/animations/webp/`
- A Codex-compatible pet package in `assets/pet/` (matches the `/hatch` skill spec: 1536x1872 transparent WebP atlas with 192x208 cells, 8 cols x 9 rows, plus `pet.json`)
- A Codex-compatible v2 pet package in `assets/pet/` (1536x2288 transparent WebP atlas with 192x208 cells, 8 cols x 11 rows, plus `pet.json`)

The Codex pet is published in releases as `clippy-pet.zip`. To install:

Expand All @@ -29,7 +29,8 @@ third-party/ source files (NOT licensed for redistribution)
decompress.wasm ACS image decompressor from computernewb/MSAgent-Chat
assets/
animations/ derived: per-animation webp + sheets + strips + manifest.json
pet/ derived: 8x9 atlas + pet.json + contact-sheet + review.json
pet/ derived: 8x11 v2 atlas + pet.json + contact-sheet + review.json
v2-look-directions.png source: 16 clockwise directions in two 8-cell rows
scripts/ all Bun TypeScript, use sharp for image work
extract-acs-images.ts ACS -> .out/acs-images/*.png + images.json
split-clippy-animations.ts orchestrator: runs extract, then writes assets/animations/*
Expand All @@ -48,7 +49,7 @@ bun scripts/build-clippy-pet.ts # also installs into ~/.codex/pets/clip
bun scripts/update-readme-gallery.ts
```

Build is reproducible: re-running produces byte-identical outputs (modulo paths in `assets/pet/review.json`). Multiple ACS animations share the same underlying frame data, so some webp files are byte-identical by design (e.g. CONGRATULATE/GETWIZARDY).
Build is reproducible: re-running produces byte-identical outputs. The 9 standard rows come from the ACS extraction; the two v2 look-direction rows are appended from the checked-in `assets/pet/v2-look-directions.png` source. Multiple ACS animations share the same underlying frame data, so some webp files are byte-identical by design (e.g. CONGRATULATE/GETWIZARDY).

## Conventions

Expand All @@ -62,9 +63,9 @@ Build is reproducible: re-running produces byte-identical outputs (modulo paths

## /hatch skill compatibility

`assets/pet/spritesheet.webp` and `assets/pet/pet.json` together form a Codex pet that satisfies the `hatch-pet` skill's acceptance criteria: 1536x1872 transparent atlas, 192x208 cells, the 9 standard rows (`idle`, `running-right`, `running-left`, `waving`, `jumping`, `failed`, `waiting`, `running`, `review`).
`assets/pet/spritesheet.webp` and `assets/pet/pet.json` together form a Codex v2 pet that satisfies the `hatch-pet` skill's acceptance criteria: 1536x2288 transparent atlas, 192x208 cells, the 9 standard rows (`idle`, `running-right`, `running-left`, `waving`, `jumping`, `failed`, `waiting`, `running`, `review`), the v2 neutral/front frame in idle row column 6, followed by two rows containing 16 clockwise looking directions.

This pet is built by extraction + sampling, not by `$imagegen`. That bypasses the normal `/hatch` flow but produces a compatible artifact; do not run `/hatch` over this repo.
The standard animation rows are built by extraction + sampling rather than `$imagegen`. The checked-in v2 look-direction source strip was produced and visually validated through `/hatch`; normal regeneration does not invoke image generation.

## Build a custom pet from these animations

Expand Down Expand Up @@ -92,4 +93,4 @@ Rules for editing:
- The script samples frames evenly through each source animation via `sampleIndices`, so a long source animation becomes a smaller number of evenly-spaced poses.
- After editing, re-run `bun scripts/build-clippy-pet.ts`. It writes `assets/pet/spritesheet.webp` + `pet.json` and installs them at `~/.codex/pets/clippy/`. To install under a different name, change the `id` in the generated `pet.json` (or edit it in the script before re-running) and move the folder to `~/.codex/pets/<your-name>/`.

The pipeline is: read the animations manifest -> for each row, pick frames from one source animation -> trim/scale/center each frame into a cell -> composite into the atlas -> write PNG, lossless WebP, and a labeled contact sheet for QA. No `$imagegen` involved.
The pipeline is: read the animations manifest -> for each standard row, pick frames from one source animation -> trim/scale/center each frame into a cell -> append the validated v2 direction strip -> composite the atlas -> write PNG, lossless WebP, and a labeled contact sheet for QA. No image generation occurs during the build.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Microsoft Agent Clippit/Clippy extracted into animated WebP and a Codex pet package.

The pet uses the Codex v2 sprite format, including 16 clockwise looking directions
in 22.5-degree steps after the 9 standard animation rows. The builder also places
the required neutral/front frame in idle row column 6.

> Licensing note: these generated assets are derived from Microsoft Clippit artwork and
> animation data. Do not assume this repo grants redistribution rights for Microsoft assets.

Expand All @@ -25,6 +29,12 @@ To install a prebuilt pet without running anything, download `clippy-pet.zip` fr

Pick a different mix of the 43 animations below for any of the 9 `/hatch` rows by editing `ROW_SPECS` in `scripts/build-clippy-pet.ts`, then re-run it. See [AGENTS.md](AGENTS.md#build-a-custom-pet-from-these-animations) for the row spec format, frame-sampling rules, and the cell geometry constants.

The standard rows remain reproducible from the ACS source. The two v2 looking-direction
rows are appended from [`assets/pet/v2-look-directions.png`](assets/pet/v2-look-directions.png).
The ACS contains eight principal LOOK animations; the checked-in strip adds the eight
intermediate poses required by Codex v2 and was produced and visually validated through
the `/hatch` workflow.

## Animations

| # | Name | WebP | Sheet | Strip | Frames | Duration |
Expand Down
Binary file modified assets/pet/contact-sheet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion assets/pet/pet.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"id": "clippy",
"displayName": "Clippy",
"description": "A classic paperclip assistant rebuilt from Microsoft Agent animation frames.",
"spritesheetPath": "spritesheet.webp"
"spritesheetPath": "spritesheet.webp",
"spriteVersionNumber": 2
}
102 changes: 101 additions & 1 deletion assets/pet/review.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"atlas": "assets/pet/spritesheet.png",
"spritesheet_webp": "assets/pet/spritesheet.webp",
"contact_sheet": "assets/pet/contact-sheet.png",
"pet_dir": "C:\\Users\\Dimava\\.codex\\pets\\clippy",
"pet_dir": "${CODEX_HOME:-$HOME/.codex}/pets/clippy",
"rows": [
{
"state": "idle",
Expand Down Expand Up @@ -49,6 +49,14 @@
"source_frame": 43,
"image_index": 0,
"cell": ".out/pet/frames/idle/05.png"
},
{
"column": 6,
"role": "v2-neutral",
"source_animation": "IDLE1_1",
"source_frame": 1,
"image_index": 0,
"cell": ".out/pet/frames/idle/06.png"
}
]
},
Expand Down Expand Up @@ -456,6 +464,98 @@
"cell": ".out/pet/frames/review/05.png"
}
]
},
{
"state": "look-direction",
"row": 9,
"frames": [
{
"column": 0,
"direction_degrees": 0,
"source": "assets/pet/v2-look-directions.png"
},
{
"column": 1,
"direction_degrees": 22.5,
"source": "assets/pet/v2-look-directions.png"
},
{
"column": 2,
"direction_degrees": 45,
"source": "assets/pet/v2-look-directions.png"
},
{
"column": 3,
"direction_degrees": 67.5,
"source": "assets/pet/v2-look-directions.png"
},
{
"column": 4,
"direction_degrees": 90,
"source": "assets/pet/v2-look-directions.png"
},
{
"column": 5,
"direction_degrees": 112.5,
"source": "assets/pet/v2-look-directions.png"
},
{
"column": 6,
"direction_degrees": 135,
"source": "assets/pet/v2-look-directions.png"
},
{
"column": 7,
"direction_degrees": 157.5,
"source": "assets/pet/v2-look-directions.png"
}
]
},
{
"state": "look-direction",
"row": 10,
"frames": [
{
"column": 0,
"direction_degrees": 180,
"source": "assets/pet/v2-look-directions.png"
},
{
"column": 1,
"direction_degrees": 202.5,
"source": "assets/pet/v2-look-directions.png"
},
{
"column": 2,
"direction_degrees": 225,
"source": "assets/pet/v2-look-directions.png"
},
{
"column": 3,
"direction_degrees": 247.5,
"source": "assets/pet/v2-look-directions.png"
},
{
"column": 4,
"direction_degrees": 270,
"source": "assets/pet/v2-look-directions.png"
},
{
"column": 5,
"direction_degrees": 292.5,
"source": "assets/pet/v2-look-directions.png"
},
{
"column": 6,
"direction_degrees": 315,
"source": "assets/pet/v2-look-directions.png"
},
{
"column": 7,
"direction_degrees": 337.5,
"source": "assets/pet/v2-look-directions.png"
}
]
}
]
}
Binary file modified assets/pet/spritesheet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/pet/spritesheet.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/pet/v2-look-directions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
93 changes: 89 additions & 4 deletions scripts/build-clippy-pet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const root = `${import.meta.dir}/..`;
const animationsManifest = `${root}/assets/animations/manifest.json`;
const acsImageDir = `${root}/.out/acs-images`;
const assetsPet = `${root}/assets/pet`;
const v2LookDirections = `${assetsPet}/v2-look-directions.png`;
const reviewDir = `${root}/.out/pet`;
const petDir = path.resolve(
(process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex")),
Expand All @@ -17,9 +18,12 @@ const petDir = path.resolve(
const CELL_W = 192;
const CELL_H = 208;
const COLUMNS = 8;
const ROWS = 9;
const STANDARD_ROWS = 9;
const LOOK_DIRECTION_ROWS = 2;
const ROWS = STANDARD_ROWS + LOOK_DIRECTION_ROWS;
const ATLAS_W = CELL_W * COLUMNS;
const ATLAS_H = CELL_H * ROWS;
const LOOK_DIRECTIONS = Array.from({ length: 16 }, (_, index) => index * 22.5);

type RowSpec = [state: string, row: number, frameCount: number, animationName: string];
const ROW_SPECS: RowSpec[] = [
Expand All @@ -43,6 +47,9 @@ type Animation = {
if (!fs.existsSync(animationsManifest)) {
throw new Error("Run scripts/split-clippy-animations.ts before building the pet.");
}
if (!fs.existsSync(v2LookDirections)) {
throw new Error(`Missing Codex v2 look-direction source: ${v2LookDirections}`);
}

const manifest = JSON.parse(fs.readFileSync(animationsManifest, "utf-8")) as { animations: Animation[] };
const animationsByName = new Map(manifest.animations.map((a) => [a.name, a]));
Expand Down Expand Up @@ -104,6 +111,8 @@ fs.mkdirSync(petDir, { recursive: true });

const atlasComposites: sharp.OverlayOptions[] = [];
const rowsOut: Array<{ state: string; row: number; frames: object[] }> = [];
let extendedNeutralCell: Buffer | undefined;
let extendedNeutralSource: { sourceFrame: number; imageIndex: number } | undefined;

for (const [state, row, frameCount, animationName] of ROW_SPECS) {
const animation = animationsByName.get(animationName);
Expand All @@ -114,13 +123,20 @@ for (const [state, row, frameCount, animationName] of ROW_SPECS) {
const rowFrames = [];
for (let column = 0; column < selected.length; column++) {
const sourceFrame = selected[column];
const imageIndex = animation.frames[sourceFrame].image_index;
if (sourceFrame === undefined) throw new Error(`missing sampled frame ${column} for ${animationName}`);
const source = animation.frames[sourceFrame];
if (!source) throw new Error(`missing source frame ${sourceFrame} for ${animationName}`);
const imageIndex = source.image_index;
const cell = imageIndex >= 0
? await fitToCell(`${acsImageDir}/${String(imageIndex).padStart(3, "0")}.png`)
: await emptyCell();
const cellPath = `${stateDir}/${String(column).padStart(2, "0")}.png`;
await Bun.write(cellPath, cell);
atlasComposites.push({ input: cell, top: row * CELL_H, left: column * CELL_W });
if (state === "idle" && column === 0) {
extendedNeutralCell = cell;
extendedNeutralSource = { sourceFrame, imageIndex };
}
rowFrames.push({
column,
source_animation: animationName,
Expand All @@ -132,6 +148,70 @@ for (const [state, row, frameCount, animationName] of ROW_SPECS) {
rowsOut.push({ state, row, frames: rowFrames });
}

if (!extendedNeutralCell || !extendedNeutralSource) {
throw new Error("Could not derive the Codex v2 neutral frame from idle column 0");
}
const neutralColumn = 6;
const neutralCellPath = `${framesRoot}/idle/${String(neutralColumn).padStart(2, "0")}.png`;
await Bun.write(neutralCellPath, extendedNeutralCell);
atlasComposites.push({ input: extendedNeutralCell, top: 0, left: neutralColumn * CELL_W });
rowsOut[0]?.frames.push({
column: neutralColumn,
role: "v2-neutral",
source_animation: "IDLE1_1",
source_frame: extendedNeutralSource.sourceFrame + 1,
image_index: extendedNeutralSource.imageIndex,
cell: path.relative(root, neutralCellPath).replaceAll("\\", "/"),
});

const lookMetadata = await sharp(v2LookDirections).metadata();
if (
lookMetadata.width !== ATLAS_W
|| lookMetadata.height !== LOOK_DIRECTION_ROWS * CELL_H
|| lookMetadata.channels !== 4
|| !lookMetadata.hasAlpha
) {
throw new Error(
`Expected ${v2LookDirections} to be a ${ATLAS_W}x${LOOK_DIRECTION_ROWS * CELL_H} RGBA image`,
);
}

for (let index = 0; index < LOOK_DIRECTIONS.length; index++) {
const direction = LOOK_DIRECTIONS[index];
if (direction === undefined) throw new Error(`missing look direction ${index}`);
const row = STANDARD_ROWS + Math.floor(index / COLUMNS);
const column = index % COLUMNS;
const cell = await sharp(v2LookDirections)
.extract({ left: column * CELL_W, top: (row - STANDARD_ROWS) * CELL_H, width: CELL_W, height: CELL_H })
.ensureAlpha()
.raw()
.toBuffer({ resolveWithObject: true });
let hasVisiblePixel = false;
for (let offset = 3; offset < cell.data.length; offset += 4) {
if (cell.data[offset] !== 0) {
hasVisiblePixel = true;
break;
}
}
if (!hasVisiblePixel) {
throw new Error(`Look-direction cell ${index} (${direction} degrees) is empty`);
}
}

atlasComposites.push({ input: v2LookDirections, top: STANDARD_ROWS * CELL_H, left: 0 });
for (let lookRow = 0; lookRow < LOOK_DIRECTION_ROWS; lookRow++) {
const row = STANDARD_ROWS + lookRow;
rowsOut.push({
state: "look-direction",
row,
frames: Array.from({ length: COLUMNS }, (_, column) => ({
column,
direction_degrees: LOOK_DIRECTIONS[lookRow * COLUMNS + column],
source: path.relative(root, v2LookDirections).replaceAll("\\", "/"),
})),
});
}

const atlas = await sharp({
create: { width: ATLAS_W, height: ATLAS_H, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } },
})
Expand All @@ -145,7 +225,11 @@ await Bun.write(spritesheetPng, atlas);
await sharp(atlas).webp({ lossless: true, quality: 100, effort: 6 }).toFile(spritesheetWebp);

const labelsSvg = `<svg width="${ATLAS_W}" height="${ATLAS_H}" xmlns="http://www.w3.org/2000/svg">${
ROW_SPECS.map(([state, row]) => `<text x="4" y="${row * CELL_H + 16}" font-family="monospace" font-size="13" fill="black">${state}</text>`).join("")
[
...ROW_SPECS.map(([state, row]) => [state, row] as const),
["look 000-157.5", 9] as const,
["look 180-337.5", 10] as const,
].map(([state, row]) => `<text x="4" y="${row * CELL_H + 16}" font-family="monospace" font-size="13" fill="black">${state}</text>`).join("")
}${
Array.from({ length: COLUMNS + 1 }, (_, c) => `<line x1="${c * CELL_W}" y1="0" x2="${c * CELL_W}" y2="${ATLAS_H}" stroke="rgb(160,160,160)" />`).join("")
}${
Expand All @@ -167,6 +251,7 @@ const petJson = {
displayName: "Clippy",
description: "A classic paperclip assistant rebuilt from Microsoft Agent animation frames.",
spritesheetPath: "spritesheet.webp",
spriteVersionNumber: 2,
};
await Bun.write(`${assetsPet}/pet.json`, JSON.stringify(petJson, null, 2) + "\n");
await Bun.write(`${petDir}/pet.json`, JSON.stringify(petJson, null, 2) + "\n");
Expand All @@ -176,7 +261,7 @@ const review = {
atlas: path.relative(root, spritesheetPng).replaceAll("\\", "/"),
spritesheet_webp: path.relative(root, spritesheetWebp).replaceAll("\\", "/"),
contact_sheet: path.relative(root, `${assetsPet}/contact-sheet.png`).replaceAll("\\", "/"),
pet_dir: petDir,
pet_dir: "${CODEX_HOME:-$HOME/.codex}/pets/clippy",
rows: rowsOut,
};
await Bun.write(`${assetsPet}/review.json`, JSON.stringify(review, null, 2) + "\n");
Expand Down
10 changes: 10 additions & 0 deletions scripts/update-readme-gallery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const content = `# Codex Clippy

Microsoft Agent Clippit/Clippy extracted into animated WebP and a Codex pet package.

The pet uses the Codex v2 sprite format, including 16 clockwise looking directions
in 22.5-degree steps after the 9 standard animation rows. The builder also places
the required neutral/front frame in idle row column 6.

> Licensing note: these generated assets are derived from Microsoft Clippit artwork and
> animation data. Do not assume this repo grants redistribution rights for Microsoft assets.

Expand All @@ -51,6 +55,12 @@ To install a prebuilt pet without running anything, download \`clippy-pet.zip\`

Pick a different mix of the 43 animations below for any of the 9 \`/hatch\` rows by editing \`ROW_SPECS\` in \`scripts/build-clippy-pet.ts\`, then re-run it. See [AGENTS.md](AGENTS.md#build-a-custom-pet-from-these-animations) for the row spec format, frame-sampling rules, and the cell geometry constants.

The standard rows remain reproducible from the ACS source. The two v2 looking-direction
rows are appended from [\`assets/pet/v2-look-directions.png\`](assets/pet/v2-look-directions.png).
The ACS contains eight principal LOOK animations; the checked-in strip adds the eight
intermediate poses required by Codex v2 and was produced and visually validated through
the \`/hatch\` workflow.

## Animations

| # | Name | WebP | Sheet | Strip | Frames | Duration |
Expand Down