SLOPSHOPPER

run-cat

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

newspinnertimer
v0.1.0MITupdated 2026-10-09haruki-cl/claude-code-mods/run-cat
A shopper browsing a rack in a slop shop
README

claude-code-mods

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 一覧

mod何をするか
token-weatherコンテキストの使用量を天気で、使用量の枠(5 時間・週)、このセッションが読んだ CLAUDE.md と auto-memory を右のペインに出す。/weather で開閉。各カードの見出しを押すと詳細が開く
run-catClaude が考えている間、プロンプト下のヒント行で 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 には含めません。

Source 2 files
hooks/register.ts 83 lines
1import { 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}
83
types/index.d.ts 9 lines
1// 猫が走り始めてから何コマ進んだか
2export type Frame = number
3
4declare module 'claude-code' {
5  interface PluginState {
6    'run-cat': { frame: Frame }
7  }
8}
9