Claude が考えている間、CLI のプロンプト下のヒント行で AA の猫が走る(端末だけで描く)

Claude Code の mod(フックモジュール)を集めたリポジトリです。このリポジトリ自体が marketplace になっているので、1 行でインストールできます。
Claude Code のプロンプトで次を実行します。
/plugin install token-weather --marketplace haruki-cl/claude-code-mods
Add marketplace? に y、スコープは user を選ぶと、そのセッションからすぐ動き、以降のセッションでも読み込まれます。run-cat も同じ形で入ります。
/plugin install run-cat --marketplace haruki-cl/claude-code-mods
| mod | 何をするか |
|---|---|
token-weather | コンテキストの使用量を天気で、使用量の枠(5 時間・週)、このセッションが読んだ CLAUDE.md と auto-memory を右のペインに出す。/weather で開閉。各カードの見出しを押すと詳細が開く |
run-cat | Claude が考えている間、プロンプト下のヒント行で AA の猫が走る |
インストールせずにフォルダから読み込むには --plugin-dir を使います。
claude --plugin-dir ./token-weather --plugin-dir ./run-cat
検査とテストは次のとおりです。
claude plugin validate ./token-weather
claude plugin test ./token-weather
.claude-plugin/types/ はエンジンが書き出す型定義で、git には含めません。
hooks/register.ts 83 lines1import { atom, read, update } from 'claude-code'
2import type { Register, Timer } from 'claude-code'
3
4// 何コマ目か。位置と手の形はここから決まる
5const frame = atom({ plugin: 'run-cat', key: 'frame' } as const, 0)
6
7const TICK_MS = 100
8
9// 右へダッシュする猫。土煙と手の形が違う 2 コマを交互に出す
10const DASH_A = 'ε=ε=(=^・ω・^)ノ'
11const DASH_B = '=ε=ε(=^・ω・^)ヘ'
12
13// ターンが終わったらその場で寝る
14const SLEEP = '(=^-ω-^=)zzZ'
15
16const RUN_WIDTH = DASH_A.length
17
18// フッター行の頭の字下げと、ヒントと猫のあいだに入る " · "
19const HINT_INDENT = 2
20const SEPARATOR = 3
21// 行の右端に Claude Code が何か出したときのための余白
22const RIGHT_MARGIN = 20
23
24// 普通の空白は Claude Code に詰められるので、見えない点字の空白で猫を右へずらす
25const BLANK = '⠀'
26
27// x 列目から描いて、room 列で切る。x が負なら左端からはみ出た分を落とす
28function place(art: string, x: number, room: number): string {
29 const shifted = x < 0 ? art.slice(-x) : BLANK.repeat(x) + art
30 return shifted.slice(0, room)
31}
32
33export const register: Register = on => {
34 let timer: Timer | undefined
35
36 const stop = () => {
37 timer?.cancel()
38 timer = undefined
39 }
40
41 on('turn.start', ($, e, next) => {
42 if (!timer) {
43 timer = $.clock.every(TICK_MS, () => {
44 void update($, frame, n => n + 1)
45 })
46 }
47
48 return next(e)
49 })
50
51 on('turn.complete', ($, e, next) => {
52 if (!e.agentId) {
53 stop()
54 }
55
56 return next(e)
57 })
58
59 // プロンプトの下のヒント行(⏵⏵ auto mode on … の行)の末尾に猫を足す
60 on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
61 // 走らせるのは端末の CLI だけ。デスクトップから同じセッションを見ても描かない
62 if (e.surface !== 'terminal') {
63 return next(e)
64 }
65
66 const columns = e.viewport?.columns ?? 80
67 const room = columns - HINT_INDENT - e.props.hint.length - SEPARATOR - RIGHT_MARGIN
68 if (room < RUN_WIDTH) {
69 return next(e)
70 }
71
72 const n = await read($, frame)
73 // 走路の左端の外から出てきて、右端の外へ抜けたらまた左から
74 const x = (n % (room + RUN_WIDTH)) - RUN_WIDTH
75 // 寝るときは走路の中に収める
76 const tail = e.props.isWorking
77 ? place(n % 2 === 0 ? DASH_A : DASH_B, x, room)
78 : place(SLEEP, Math.max(0, Math.min(x, room - SLEEP.length)), room)
79
80 return next({ ...e, props: { ...e.props, tail } })
81 })
82}
83types/index.d.ts 9 lines1// 猫が走り始めてから何コマ進んだか
2export type Frame = number
3
4declare module 'claude-code' {
5 interface PluginState {
6 'run-cat': { frame: Frame }
7 }
8}
9