SLOPSHOPPER

kawaii-th

เพื่อนน่ารักเหนือช่องพิมพ์ที่เปลี่ยนสีหน้าตาม Claude: คิดอยู่ ทำงาน เจอ error ทำเสร็จ พร้อม kaomoji 50 แบบ

newbandguardcommandprompttimer
★ 3v1.0.0MITupdated 2026-10-24tksoft11/claude-code-mods-th/mods/mod-44-kawaii
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · kawaii-th
› 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 › /kawaii ⎿ kawaii-th: (=^▽^=)✨ แมวเหมียว · เสร็จแล้ว! ⎿ kawaii-th: ⎿ kawaii-th: 🌸 Kawaii TH (Mod 44) ⎿ kawaii-th: ======================================== ⎿ kawaii-th: /kawaii ดูมาสคอตและสถานะ ⎿ kawaii-th: /kawaii pick <ตัว> เปลี่ยนตัว: cat=แมวเหมียว bear=หมีน้อย bunny=กระต่าย penguin=เพนกวิน ghost=ผีน้อย blob (=^▽^=)✨ เสร็จแล้ว! ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
(=^▽^=)✨ เสร็จแล้ว!
README

Mod 44: Kawaii TH

มาสคอตน่ารักเหนือช่องพิมพ์ที่เปลี่ยนสีหน้าตามสถานะจริงของ Claude: กำลังคิด / ใช้เครื่องมืออะไรอยู่ / เจอ error / เสร็จแล้ว / รอคุณตอบ / ว่าง เลือกได้ 6 ตัว (แมว หมี กระต่าย เพนกวิน ผี ก้อนวุ้น) และคลัง kaomoji 50 แบบ 5 หมวดที่ส่งลงช่องพิมพ์ได้

คำสั่ง

คำสั่งทำอะไร
/kawaiiดูมาสคอตและสถานะ
/kawaii pick <ตัว>เปลี่ยนตัว: cat bear bunny penguin ghost blob
/kawaii previewทุกอารมณ์ของตัวปัจจุบัน
/kawaii gallery [หมวด]คลัง kaomoji 50 แบบ
/kawaii use <หมวด> <เลข>ส่ง kaomoji ลงช่องพิมพ์
`/kawaii on\off`แสดง/ซ่อนมาสคอต

ติดตั้ง

/plugin install kawaii-th --marketplace tksoft11/claude-code-mods-th

ต้องตั้ง CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 ก่อนเปิด claude (ดู README หลัก)

โค้ดสอนอะไร

เปิด hooks/register.tsx แล้วอ่านตามบทที่เขียนไว้ในคอมเมนต์:

  1. ตัวละคร 2 เฟรมต่ออารมณ์
  2. คลัง kaomoji
  3. เปลี่ยนอารมณ์และขยับเฟรมเฉพาะตอนทำงาน
  4. ฟัง turn.start / tool.call / turn.complete / Notification
  5. วาดมาสคอตใน AbovePrompt

หมายเหตุ

spinner น่ารักอยู่ใน Mod 35 แล้ว ตัวนี้เน้นมาสคอตตามสถานะ ถ้าเปิดหลาย Mod ที่วาดเหนือช่องพิมพ์พร้อมกัน (เช่น Mod 01) อาจเห็นแค่ตัวเดียว

Source 1 files
hooks/register.tsx 286 lines
1/**
2 * ============================================================================
3 * MOD 44: Kawaii TH - มาสคอตน่ารักที่เปลี่ยนหน้าตามอารมณ์ของ Claude (ฉบับสอนทำ)
4 * ============================================================================
5 * แรงบันดาลใจจาก: Tamagotchi และ kaomoji ญี่ปุ่น (◕‿◕)
6 *
7 * หน้าที่ของ Mod นี้:
8 * 1. มาสคอตเหนือช่องพิมพ์ เปลี่ยนสีหน้าตามสถานะจริงของ Claude:
9 *    กำลังคิด / ใช้เครื่องมือ / เจอ error / เสร็จแล้ว / รอคุณตอบ / ว่าง
10 * 2. เลือกตัวละครได้ 6 ตัว (แมว หมี กระต่าย เพนกวิน ผี ก้อนวุ้น)
11 * 3. คลัง kaomoji 50 แบบ แบ่งหมวด ส่งเข้าช่องพิมพ์ได้
12 *
13 * หมายเหตุ: spinner น่ารักอยู่ใน Mod 35 แล้ว ตัวนี้เน้นมาสคอตตามสถานะ
14 */
15
16import type { EngineInterface, Register, Timer } from 'claude-code'
17
18type Mood = 'idle' | 'thinking' | 'tool' | 'error' | 'done' | 'waiting'
19
20// ==========================================================================
21// บทที่ 1: ตัวละคร - แต่ละตัวมีหน้า 2 เฟรมต่ออารมณ์ (สลับเฟรม = กระพริบ/ขยับ)
22// ==========================================================================
23interface Mascot {
24  name: string
25  color: string
26  faces: Record<Mood, [string, string]>
27}
28
29const MASCOTS = {
30  cat: {
31    name: 'แมวเหมียว',
32    color: '#ff8fab',
33    faces: {
34      idle: ['ฅ^•ﻌ•^ฅ', 'ฅ^-ﻌ-^ฅ'],
35      thinking: ['(=ↀωↀ=)?', '(=ↀωↀ=)…'],
36      tool: ['ฅ(=•ω•=)ฅ🔧', 'ฅ(=•ω•=)ฅ🔨'],
37      error: ['(=TωT=)', '(=;ω;=)'],
38      done: ['(=^▽^=)✨', '(=^ω^=)🎉'],
39      waiting: ['(=・ω・=)ノ?', '(=・ω・=)ノ!'],
40    },
41  },
42  bear: {
43    name: 'หมีน้อย',
44    color: '#c08552',
45    faces: {
46      idle: ['ʕ•ᴥ•ʔ', 'ʕ-ᴥ-ʔ'],
47      thinking: ['ʕ•ᴥ•ʔ💭', 'ʕ·ᴥ·ʔ💭'],
48      tool: ['ʕ•̀ᴥ•́ʔ✧🔧', 'ʕ•̀ᴥ•́ʔ✧⚙️'],
49      error: ['ʕ´•ᴥ•`ʔ', 'ʕ;ᴥ;ʔ'],
50      done: ['ʕ•ᴥ•ʔ🍯', 'ʕ^ᴥ^ʔ🍯'],
51      waiting: ['ʕ•ᴥ•ʔノ', 'ʕ•ᴥ•ʔノ゛'],
52    },
53  },
54  bunny: {
55    name: 'กระต่าย',
56    color: '#b39ddb',
57    faces: {
58      idle: ['(\\_/)\n( •.•)', '(\\_/)\n( -.-)'],
59      thinking: ['(\\_/)\n( •.•)?', '(\\_/)\n( °.°)?'],
60      tool: ['(\\_/)\n( •.•)>🥕', '(\\_/)\n( •.•)>🔧'],
61      error: ['(\\_/)\n( T.T)', '(\\_/)\n( ;.;)'],
62      done: ['(\\_/)\n( ^.^)✨', '(\\_/)\n(≧.≦)🎉'],
63      waiting: ['(\\_/)\n( •.•)ノ', '(\\_/)\n( •.•)/'],
64    },
65  },
66  penguin: {
67    name: 'เพนกวิน',
68    color: '#4fc3f7',
69    faces: {
70      idle: ['<(\'v\')>', '<(-v-)>'],
71      thinking: ['<(\'v\')>?', '<(°v°)>?'],
72      tool: ['<(\'v\')>🔧', '<(\'v\')>💻'],
73      error: ['<(;v;)>', '<(TvT)>'],
74      done: ['\\(^v^)/', '<(^v^)>🐟'],
75      waiting: ['<(\'v\')>ノ', '<(\'v\')>/'],
76    },
77  },
78  ghost: {
79    name: 'ผีน้อย',
80    color: '#b0bec5',
81    faces: {
82      idle: ['👻 (ᵔᴥᵔ)', '👻 (ᵕᴥᵕ)'],
83      thinking: ['👻 (・_・ヾ', '👻 (・_・;)'],
84      tool: ['👻 ᕦ(ò_óˇ)ᕤ', '👻 ᕙ(ò_óˇ)ᕗ'],
85      error: ['👻 (×_×)', '👻 (x_x)'],
86      done: ['👻 ヽ(・∀・)ノ', '👻 \\(•◡•)/'],
87      waiting: ['👻 (・ω・)ノ', '👻 (・ω・)/'],
88    },
89  },
90  blob: {
91    name: 'ก้อนวุ้น',
92    color: '#81c784',
93    faces: {
94      idle: ['(。•ᴗ•。)', '(。-ᴗ-。)'],
95      thinking: ['(。•ˇ‸ˇ•。)', '(。•́︿•̀。)?'],
96      tool: ['(ง •̀_•́)ง', '(ว •̀_•́)ว'],
97      error: ['(。•́︿•̀。)', '(。╯︵╰。)'],
98      done: ['(ノ◕ヮ◕)ノ*:・゚✧', '(ノ´ヮ`)ノ*: ・゚'],
99      waiting: ['(。•ᴗ•。)ノ', '(。•ᴗ•。)/'],
100    },
101  },
102} satisfies Record<string, Mascot>
103
104function findMascot(key: string): Mascot | undefined {
105  return (MASCOTS as Record<string, Mascot>)[key]
106}
107
108const MOOD_TEXT: Record<Mood, string> = {
109  idle: 'ว่างอยู่ คุยกันได้นะ',
110  thinking: 'กำลังคิด…',
111  tool: 'กำลังทำงาน',
112  error: 'อุ๊ย มีอะไรผิดพลาด',
113  done: 'เสร็จแล้ว!',
114  waiting: 'รอคุณอยู่นะ',
115}
116
117// ==========================================================================
118// บทที่ 2: คลัง kaomoji 50 แบบ
119// ==========================================================================
120const KAOMOJI: Record<string, string[]> = {
121  ดีใจ: ['(◕‿◕)', '(^▽^)', '(≧▽≦)', 'ヽ(・∀・)ノ', '(ノ◕ヮ◕)ノ*:・゚✧', '٩(◕‿◕。)۶', '(*^‿^*)', '\\(^o^)/', '(✿◠‿◠)', '(っ˘ω˘ς)'],
122  รัก: ['(♡˙︶˙♡)', '(´。• ᵕ •。`) ♡', '(◍•ᴗ•◍)❤', '♡(ᐢ ᴥ ᐢし)', '(っ´▽`)っ♡', '(˘³˘)♥', '(*˘︶˘*).。.:*♡', '(ɔˆ ³(ˆ⌣ˆc)', '♡^▽^♡', '(灬º‿º灬)♡'],
123  เศร้า: ['(╥﹏╥)', '(。•́︿•̀。)', '(ಥ﹏ಥ)', '(T_T)', '(´;ω;`)', '(っ˘̩╭╮˘̩)っ', '(。╯︵╰。)', '(ノ_<。)', '(-_-;)', '(;へ:)'],
124  สู้ๆ: ['(ง •̀_•́)ง', 'ᕦ(ò_óˇ)ᕤ', '(๑•̀ㅂ•́)و✧', '(ง°ل͜°)ง', '٩(ˊᗜˋ*)و', 'o(`ω´ )o', '(•̀ᴗ•́)و ̑̑', '(ᗒᗣᗕ)՞', '୧(๑•̀ᗝ•́)૭', '(メ` ロ ´)'],
125  สัตว์: ['ฅ^•ﻌ•^ฅ', 'ʕ•ᴥ•ʔ', '(=^・ω・^=)', 'U・ᴥ・U', "<('v')>", '(・θ・)', '(•ㅅ•)', '∪・ω・∪', '(◕ᴥ◕)', '/ᐠ。ꞈ。ᐟ\\'],
126}
127
128let mood: Mood = 'idle'
129let mascot = 'cat'
130let enabled = true
131let toolName = ''
132let frame = 0
133let animator: Timer | undefined
134let settle: Timer | undefined
135
136// ==========================================================================
137// บทที่ 3: เปลี่ยนอารมณ์ - ขยับเฟรมเฉพาะตอนกำลังทำงาน ว่างแล้วหยุด timer
138// ==========================================================================
139function setMood($: EngineInterface, next: Mood): void {
140  mood = next
141  settle?.cancel()
142  settle = undefined
143  const busy = next === 'thinking' || next === 'tool'
144  if (busy && !animator) animator = $.clock.every(700, () => {
145    frame++
146    $.ui.invalidate('ui.render')
147  })
148  if (!busy) {
149    animator?.cancel()
150    animator = undefined
151  }
152  // done/error ค้างไว้สักพัก แล้วกลับเป็นว่าง
153  if (next === 'done' || next === 'error') settle = $.clock.after(8_000, () => setMood($, 'idle'))
154  $.ui.invalidate('ui.render')
155}
156
157async function save($: EngineInterface): Promise<void> {
158  await $.store.set('kawaii', { mascot, enabled })
159}
160
161const HELP = `🌸 Kawaii TH (Mod 44)
162========================================
163/kawaii                 ดูมาสคอตและสถานะ
164/kawaii pick <ตัว>      เปลี่ยนตัว: ${Object.entries(MASCOTS).map(([k, m]) => `${k}=${m.name}`).join(' ')}
165/kawaii preview         ดูทุกอารมณ์ของตัวปัจจุบัน
166/kawaii gallery [หมวด]  คลัง kaomoji 50 แบบ (${Object.keys(KAOMOJI).join(' ')})
167/kawaii use <หมวด> <เลข>  ส่ง kaomoji ลงช่องพิมพ์
168/kawaii on|off          แสดง/ซ่อนมาสคอต`
169
170export const register: Register = on => {
171  on('session.start', async ($, e, next) => {
172    const saved = (await $.store.get('kawaii')) as { mascot?: string; enabled?: boolean } | undefined
173    mascot = saved?.mascot && findMascot(saved.mascot) ? saved.mascot : 'cat'
174    enabled = saved?.enabled ?? true
175    mood = 'idle'
176    await $.command.register({ name: 'kawaii', description: 'มาสคอตน่ารักที่เปลี่ยนหน้าตามสถานะ Claude + คลัง kaomoji (/kawaii help)', argumentHint: 'pick|preview|gallery|use|on|off' })
177    return next(e)
178  })
179
180  // ==========================================================================
181  // บทที่ 4: ฟังสถานะจริงของ Claude (เฉพาะงานหลัก ไม่นับ subagent)
182  // ==========================================================================
183  on('turn.start', async ($, e, next) => {
184    if (!e.agentId) setMood($, 'thinking')
185    return next(e)
186  })
187
188  on('tool.call', async ($, e, next) => {
189    toolName = e.tool
190    if (mood !== 'error') setMood($, 'tool')
191    const r = await next(e)
192    if (r.isError) setMood($, 'error')
193    else if (mood === 'tool') setMood($, 'thinking')
194    return r
195  })
196
197  on('turn.complete', async ($, e, next) => {
198    const r = await next(e)
199    if (!e.agentId) setMood($, e.reason === 'error' ? 'error' : e.reason === 'aborted' ? 'idle' : 'done')
200    return r
201  })
202
203  // Claude Code แจ้งว่ารออนุญาต/รอคำตอบ
204  on('classic.Notification', async ($, e, next) => {
205    setMood($, 'waiting')
206    return next(e)
207  })
208
209  on('prompt.submit', async ($, e, next) => {
210    if (mood === 'waiting') setMood($, 'thinking')
211    return next(e)
212  })
213
214  // ==========================================================================
215  // บทที่ 5: วาดมาสคอตเหนือช่องพิมพ์
216  // ==========================================================================
217  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
218    if (!enabled || e.props.hasSurvey) return next(e)
219    const { Box, Text } = $.ui.resolve(e)
220    const m = findMascot(mascot) ?? MASCOTS.cat
221    const face = m.faces[mood][frame % 2 === 0 ? 0 : 1].split('\n')
222    const label = mood === 'tool' && toolName ? `${MOOD_TEXT.tool}: ${toolName}` : MOOD_TEXT[mood]
223    return (
224      <Box flexDirection="row" gap={1}>
225        <Box flexDirection="column">
226          {face.map(l => (
227            <Text color={m.color}>{l}</Text>
228          ))}
229        </Box>
230        <Text color={mood === 'error' ? '#e57373' : undefined} dimColor={mood === 'idle'}>
231          {label}
232        </Text>
233      </Box>
234    )
235  })
236
237  on('command.run', { command: 'kawaii' }, async ($, e) => {
238    const [sub = '', ...rest] = e.args.trim().split(/\s+/).filter(Boolean)
239    const m = findMascot(mascot) ?? MASCOTS.cat
240    switch (sub) {
241      case '':
242        return { text: `${m.faces[mood][0]}  ${m.name} · ${MOOD_TEXT[mood]}${enabled ? '' : ' (ซ่อนอยู่)'}\n\n${HELP}` }
243      case 'pick': {
244        const key = rest[0] ?? ''
245        const picked = findMascot(key)
246        if (!picked) return { text: `ไม่มีตัว "${key}"\n${HELP}` }
247        mascot = key
248        enabled = true
249        await save($)
250        $.ui.invalidate('ui.render')
251        return { text: `${picked.faces.done[0]}  สวัสดี! ฉันคือ${picked.name}` }
252      }
253      case 'preview':
254        return { text: [`🌸 ${m.name}`, ...(Object.keys(MOOD_TEXT) as Mood[]).map(k => `${MOOD_TEXT[k].padEnd(22)} ${m.faces[k][0].replace('\n', ' ')}`)].join('\n') }
255      case 'gallery': {
256        const cats = rest[0] ? [rest[0]] : Object.keys(KAOMOJI)
257        const out = cats.filter(c => KAOMOJI[c]).map(c => `【${c}】\n${(KAOMOJI[c] ?? []).map((k, i) => `${String(i + 1).padStart(2)}. ${k}`).join('\n')}`)
258        return { text: out.length ? `🌸 kaomoji ${cats.length === 1 ? '' : '50 แบบ'}\n\n${out.join('\n\n')}\n\nส่งลงช่องพิมพ์: /kawaii use ดีใจ 3` : `ไม่มีหมวด "${rest[0]}" (${Object.keys(KAOMOJI).join(' ')})` }
259      }
260      case 'use': {
261        const k = KAOMOJI[rest[0] ?? '']?.[Number(rest[1]) - 1]
262        if (!k) return { text: 'วิธีใช้: /kawaii use ดีใจ 3  (ดูเลขจาก /kawaii gallery)' }
263        await $.prompt.fill({ text: k })
264        return { text: `ใส่ ${k} ลงช่องพิมพ์แล้ว` }
265      }
266      case 'on':
267      case 'off':
268        enabled = sub === 'on'
269        await save($)
270        $.ui.invalidate('ui.render')
271        return { text: enabled ? `${m.faces.idle[0]} กลับมาแล้ว!` : 'ซ่อนมาสคอตแล้ว (/kawaii on เพื่อเรียกกลับ)' }
272      default:
273        return { text: HELP }
274    }
275  })
276}
277
278/**
279 * ========================================================================
280 * การบ้าน / ต่อยอด
281 * ========================================================================
282 * 1. ให้มาสคอต "หิว" ถ้าไม่ได้คุยกันนาน และ "อิ่ม" ทุกครั้งที่งานสำเร็จ แบบ Tamagotchi
283 * 2. ใช้หน้าตาต่างกันตามเครื่องมือ: Read = อ่านหนังสือ, Bash = พิมพ์คีย์บอร์ด
284 * 3. ออกแบบตัวละครของคุณเองแล้วเพิ่มลงใน MASCOTS
285 */
286