SLOPSHOPPER

focus-timer

Example guest of cc-clawd: /focus 25 starts a 25-minute focus block; the pet types on its laptop and counts down in the band, then celebrates. Works without…

newcommandtoaststatustimer
★ 1v1.0.0MITupdated 2026-10-05yushiran/cc-clawd/examples/focus-timer
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · focus-timer
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM › /focus ⎿ focus-timer: Focus for 25 minutes. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts ⚠ focus-timer: focus 25:00
README

cc-clawd

Pixel pets for Claude Code. Clawd, the Claude Code crab, lives at the left of the band above your prompt, animates at 60 Hz, and knows what Claude is doing: it types on a laptop while Claude works, holds up a ticket when a permission prompt is waiting for you, sips coffee when nothing is happening, and falls asleep late at night. Other mods can tell it what to do, give it rows to show beside it, or give it a different body.

Clawd's twelve states

<sub>One row per state, top to bottom: idle, calm, happy, ecstatic, cheer, worried, sad, sleep, alert, needs, think, dizzy. Rendered with tools/preview.mjs.</sub>

Clawd is Anthropic's Claude Code mascot. This is fan art and an unofficial project, not affiliated with or endorsed by Anthropic.

中文说明

Install

claude plugin marketplace add yushiran/cc-clawd
claude plugin install clawd@cc-clawd

Then run /reload-plugins (or restart Claude Code). Requires a Claude Code build with function-hook mods (2.1.289 or later).

What it does

WhenThe pet
A permission prompt waits for youholds up a ticket with a blinking ? (highest priority)
Claude is workingtypes on a laptop whose screen shows what the tool does: >ls_ for Bash, [==] for Read, +-~ for Edit
Nothing is happeningsips coffee, looks around, stretches, blinks
Quiet for 20 minutes, or 3 minutes after midnightsleeps under the moon
A mod or program sends a signalwhatever the signal says: a monitor of your numbers, confetti, rain, a bell
You click it (desktop app, or renderer: client)a heart, a jump, a sparkle; its eyes follow the pointer

/clawd shows what it is doing and why, and the frame rate. /clawd demo plays every state. /clawd pets lists the pets, and /clawd pet slime switches to one. /clawd bench measures each terminal renderer for four seconds.

Settings are under /config → clawd:

  • pet: clawd, slime, or a pet of your own.
  • renderer: auto picks one per surface; see below.
  • fps: 30, 60 or 120 frames per second.

How it draws

SurfaceRendererWhat you get
Terminal (most)rasterCharacter pixels: quadrant blocks give each cell 2×2 pixels, repainted in place by $.ui.blit
kitty, GhosttyimageReal pixels through the terminal's graphics protocol. Detected automatically, and the result is remembered per terminal
Desktop appclientA surface module that animates on the app's own frame clock and reacts to the pointer
VS Code, mobiletextThe current frame as coloured text, redrawn when the pet's state changes

A frame is computed every 1000/fps ms and sent only when it changed. Computing one takes about 10 µs, under 0.1% of a 60 Hz frame. While Claude works, about 35 of the 60 frames each second actually change. docs/ENGINE.md has the measurements and the best practices behind them.

For mod authors: $.clawd

cc-clawd adds $.clawd to every plugin's $, with no dependency to declare. Call it inside try/catch, and draw your own UI when it throws (the pet is not installed):

try {
  const { columns } = await $.clawd.layout()      // lay your row out to this width
  await $.clawd.slot({ source: 'build', rows: [[['build ', { dim: true }], ['passing', { color: 'green' }]]], action: { label: 'log', command: 'build log' } })
  await $.clawd.signal({ source: 'build', scene: 'confetti', say: 'all green', priority: 80, ttlMs: 20000 })
  await $.clawd.emote({ emote: 'heart' })
} catch {
  // no pet: draw the band yourself
}
MethodWhat it does
signal({ source, scene, mood?, bars?, tool?, say?, priority?, ttlMs? })Puts the pet in a scene. The highest priority wins, ties go to the newest, and scene: null withdraws the signal
emote({ emote, say? })A one-shot reaction over whatever it is doing: heart, sparkle, surprise, question, jump, nod, shake
slot({ source, rows, order?, action? })Your rows in the band, to the right of the pet, with an optional button that runs a slash command
pack(def)Registers a pet (a data pack) for the session
layout() / state() / pets()Width for your rows, what the pet is doing, which pets exist

The full reference is docs/API.md, the types are in plugins/clawd/types/index.d.ts, and examples/focus-timer is a complete guest mod in about 50 lines.

For any program: signal files

Scripts, cron jobs and training runs on a cluster can signal the pet without being a mod. Write a JSON file to ~/.local/share/clawd/signals/:

d=~/.local/share/clawd/signals; mkdir -p $d
printf '{"scene":"confetti","say":"training finished","priority":85,"ttlMs":30000}' > $d/.train.tmp && mv $d/.train.tmp $d/train.json

The pet reads the folder every second. Deleting the file withdraws the signal.

Make your own pet

A pet is a pack: plain JSON that holds a palette, character-art sprites and, for each state, a loop of frames plus props from the shared effects. Put it in ~/.config/clawd/pets/<id>.json and run /clawd pet <id>.

{
  "format": "cc-clawd/pack@1", "id": "blob", "cols": 17, "rows": 3, "mode": "quad",
  "palette": { "#": "#7bd88f" },
  "at": [1, 1],
  "sprites": { "a": ["..####..", ".##..##.", "########"], "b": ["........", "..####..", "########"] },
  "states": { "idle": { "frames": [["a", 900], ["b", 200]], "fx": [["coffee", 18, 0]] } }
}

States the pack lacks fall back toward idle. The built-in slime (examples/pets/my-slime.json) is a full example:

The slime, a data pack

Preview a pack in a plain terminal, render a contact sheet, or check it:

node tools/preview.mjs examples/pets/my-slime.json
node tools/preview.mjs examples/pets/my-slime.json --png sheet.png
node tools/preview.mjs --check my-pet.json

docs/PACKS.md is the format: modes, sprites, frames, the effects and their sizes, fallbacks, and how to make a built-in.

Develop

claude plugin validate plugins/clawd
claude plugin test plugins/clawd          # the engine, the brain, and the host end to end on every surface
node tools/preview.mjs                    # every state of Clawd in this terminal

The layout:

PathWhat it is
plugins/clawd/engine/pixel.jsCanvas and encoders (Raster cells, text spans, RGBA)
plugins/clawd/engine/fx.jsProps and effects
plugins/clawd/engine/pack.jsPack compiler and stage
plugins/clawd/engine/brain.jsWhat the pet does: priorities, signals, emotes, sleep
plugins/clawd/hooks/register.jsThe host: Claude Code events, $.clawd, the band, renderers
plugins/clawd/hooks/client.jsThe surface-side renderer
plugins/clawd/pets/Built-in pets

The engine is pure JavaScript with no dependencies, so the same files run in a hooks module, a client module and Node.

License

CC BY-NC-SA 4.0 for the code. Clawd's design belongs to Anthropic.

Source 1 files
hooks/register.js 50 lines
1// focus-timer: the smallest useful guest of cc-clawd. It shows the three things a guest does:
2//   $.clawd.signal  put the pet in a scene (laptop while you focus, confetti when you are done)
3//   $.clawd.slot    hand the pet a row for the band (the countdown, with a button to stop)
4//   a fallback      every call sits in try/catch; without the pet, the countdown goes to the status line
5// No dependency on cc-clawd is declared: $.clawd is on every plugin's `$` once cc-clawd is installed.
6
7let endsAt = 0          // when the block ends (ms), 0 when none runs
8let minutes = 25
9
10const mmss = (ms) => { const s = Math.max(0, Math.round(ms / 1000)); return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0') }
11
12async function show($) {
13  const left = endsAt - (await $.clock.now())
14  try {
15    if (!endsAt) {
16      await $.clawd.slot({ source: 'focus', rows: null })
17      return
18    }
19    if (left <= 0) {
20      endsAt = 0
21      await $.clawd.slot({ source: 'focus', rows: null })
22      await $.clawd.signal({ source: 'focus', scene: 'confetti', say: minutes + ' minutes of focus done', priority: 85, ttlMs: 20000 })
23      return
24    }
25    await $.clawd.slot({ source: 'focus', order: 5, rows: [[['focus ', { dim: true }], [mmss(left), { bold: true }], [' left', { dim: true }]]], action: { label: 'stop', command: 'focus stop' } })
26    await $.clawd.signal({ source: 'focus', scene: 'laptop', tool: 'Edit', priority: 40, ttlMs: 5000 })
27  } catch (err) {
28    // no pet installed: the status line instead
29    await $.ui.status(endsAt && left > 0 ? 'focus ' + mmss(left) : undefined)
30    if (endsAt && left <= 0) { endsAt = 0; $.ui.toast(minutes + ' minutes of focus done') }
31  }
32}
33
34export function register(on) {
35  on('session.start', async ($, e, next) => {
36    $.clock.every(1000, () => { show($) })
37    try { await $.command.register({ name: 'focus', description: 'Focus block: /focus [minutes] starts one (default 25), /focus stop ends it' }) } catch (err) { /* the name is taken */ }
38    return next(e)
39  })
40
41  on('command.run', { command: 'focus' }, async ($, e) => {
42    const arg = String(e.args || '').trim()
43    if (arg === 'stop') { endsAt = 0; await show($); return { text: 'Focus block stopped.' } }
44    minutes = Math.max(1, Math.min(180, Number(arg) || 25))
45    endsAt = (await $.clock.now()) + minutes * 60e3
46    await show($)
47    return { text: 'Focus for ' + minutes + ' minutes.' }
48  })
49}
50