ผีไทยมาบอก error: เมื่อคำสั่งที่ Claude รันพัง จะมีผีประจำ bug มาอธิบายอาการ สาเหตุ วิธีแก้ เป็นภาษาไทย/อีสาน และให้ Claude แปล error เป็นภาษาคนได้

ผีไทยมาบอก error: เมื่อคำสั่งที่ Claude รันพัง Mod จะจับคู่ข้อความ error จริงกับ "ผีประจำบั๊ก" 13 ตน (กระสือ = แรมหมด, ผีพราย = พอร์ตถูกยึด, ผีกองกอย = หาไฟล์ไม่เจอ, แม่นาค = null/undefined, เปรต = stack overflow ...) แล้วเขียนอาการ สาเหตุ วิธีแก้ลงในแชตเป็นภาษาไทยปนอีสาน ถ้ารันคำสั่งเดิมผ่านในภายหลังนับเป็นการปราบผีได้บุญ และให้ Claude แปล error เป็นภาษาคนได้
| คำสั่ง | ทำอะไร | |
|---|---|---|
/ghost | ทำเนียบผี + ค่าบุญ | |
/ghost <ชื่อผี> | รายละเอียด เช่น /ghost krasue | |
/ghost last | error ล่าสุดที่ผีมาสิง | |
/ghost explain | ให้ Claude แปล error ล่าสุดเป็นภาษาคน | |
| `/ghost on\ | off` | เปิด/ปิด |
/ghost reset confirm | ล้างสถิติ |
/plugin install ghost-debug-th --marketplace tksoft11/claude-code-mods-th
ต้องตั้ง CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 ก่อนเปิด claude (ดู README หลัก)
เปิด hooks/register.tsx แล้วอ่านตามบทที่เขียนไว้ในคอมเมนต์:
ต่อยอดจากไฟล์ mod 49 ต้นฉบับ (เนื้อหาผีและภาษาอีสานคงไว้) แต่เปลี่ยนจากข้อมูลสมมติเป็นการจับ error จริง รวมไอเดีย error-decoder ไว้ด้วย บรรทัดผีแสดงในแชตของ terminal (โหมด -p จะอยู่ใน debug log)
hooks/register.tsx 365 lines1/**
2 * ============================================================================
3 * MOD 49: Ghost Debug TH - ผีไทยมาบอก Error (ฉบับสอนทำ)
4 * ============================================================================
5 * แรงบันดาลใจจาก: ความเชื่อเรื่องผีไทย/อีสาน + ความหลอนของบั๊ก
6 * "บ่แม่นผีหลอก แต่บั๊กหลอก!"
7 *
8 * หน้าที่ของ Mod นี้:
9 * 1. ดูผลลัพธ์คำสั่ง Bash ที่ Claude รัน ถ้าพัง จะจับคู่ข้อความ error กับ "ผีประจำบั๊ก" 13 ตน
10 * แล้วเขียนบรรทัดเตือนลงแชต (คุณเห็น แต่ไม่ส่งให้โมเดล) พร้อมสาเหตุและวิธีแก้
11 * 2. ปราบผีได้: ถ้าคำสั่งเดิมรันผ่านในภายหลัง นับเป็น "ปราบสำเร็จ" ได้บุญ
12 * 3. /ghost explain - ให้ Claude (Haiku) แปล error ล่าสุดเป็นภาษาคน สั้นๆ
13 * 4. ทำเนียบผี + ค่าบุญบารมี
14 */
15
16import type { EngineInterface, Register } from 'claude-code'
17
18interface Ghost {
19 name: string
20 bug: string
21 emoji: string
22 color: string
23 patterns: RegExp
24 symptom: string
25 cause: string
26 fix: string
27 chant: string
28 isan: string
29 debug: string
30}
31
32// ==========================================================================
33// บทที่ 1: ทำเนียบผี - แต่ละตนผูกกับรูปแบบข้อความ error จริง
34// ==========================================================================
35const GHOSTS: Record<string, Ghost> = {
36 krasue: {
37 name: 'กระสือ',
38 bug: 'Memory Leak / หน่วยความจำหมด',
39 emoji: '🫀',
40 color: '#ff4757',
41 patterns: /heap out of memory|JavaScript heap|MemoryError|OOMKilled|Cannot allocate memory|out of memory|std::bad_alloc/i,
42 symptom: 'แรมไหลบ่หยุด คือกระสือกินไก่ตอนกลางคืน โปรแกรมบวมขึ้นเรื่อยๆ จน OOM!',
43 cause: 'ลืม unsubscribe, ลืม clearInterval, cache บ่มี TTL, useEffect บ่ cleanup คือเฮ็ดนาแล้วบ่ถอนหญ้า',
44 fix: 'ใส่ cleanup function, ใช้ WeakRef, ใส่ LRU cache, ถ่าย heap snapshot เทียบกัน เหมือนหมอผีตรวจรอยเท้า',
45 chant: 'โอม มะโม มา มะมา ปิดหู ปิดตา ปิดปาก memory!',
46 isan: 'กระสือมันหิวหลาย กินแรมบ่จักอิ่ม!',
47 debug: 'node --inspect + Chrome heap snapshot | npx memlab',
48 },
49 krahang: {
50 name: 'กระหัง',
51 bug: 'Build / Deploy ล้ม',
52 emoji: '🧹',
53 color: '#3742fa',
54 patterns: /deploy(?:ment)? failed|build failed|failed to compile|Build error occurred|error during build|Command "[^"]*build[^"]*" exited/i,
55 symptom: 'ในเครื่องรันได้ แต่ build/deploy แล้วตาย คือกระหังเอากระด้งบินหนีตอนกลางคืน!',
56 cause: 'env บ่ตรงกัน, ลืม build arg, Dockerfile cache เก่า คือเฮือนคนละหลัง คาถาคนละบท',
57 fix: 'ล็อกเวอร์ชัน Node/Python, build แบบ reproducible, docker --no-cache, ปล่อย canary ก่อน',
58 chant: 'โอม กระหังเอ้ย กลับมาเด้อ กลับมา deploy โอมเพี้ยง!',
59 isan: 'มันบินไปแล้วอ้าย บินไปไสแล้วบ่ฮู้!',
60 debug: 'ดู log ของ CI/แพลตฟอร์ม | เทียบ node -v และ env ทั้งสองฝั่ง',
61 },
62 phi_pop: {
63 name: 'ผีปอบ',
64 bug: 'ค้าง / Timeout / ลูปไม่จบ',
65 emoji: '🍽️',
66 color: '#8e8e8e',
67 patterns: /timed? ?out|timeout of \d+ms exceeded|ETIMEDOUT|exceeded the timeout|took too long|Killed\s*$/im,
68 symptom: 'CPU ขึ้น 100% รอแล้วรออีก ผีปอบเข้าสิงลูปบ่มีทางออก วนไปจนตาย!',
69 cause: 'ลูปบ่มีเงื่อนไขออก, recursion ลืม base case, ลืม await, รอ network ที่บ่ตอบ',
70 fix: 'ใส่ทางออกให้ลูป ใส่ timeout ที่สมเหตุสมผล ใส่ yield ให้ Event Loop หายใจ',
71 chant: 'นะโม ปราบปอบ ถอนปอบ ออก ออก ออก! Break! Break! Break!',
72 isan: 'ปอบเข้าหม้อแล้ว กินบ่เซา!',
73 debug: 'top / Task Manager ดู CPU | node --cpu-prof | py-spy dump',
74 },
75 nang_tani: {
76 name: 'นางตานี',
77 bug: 'API / Network หลอน',
78 emoji: '🍌',
79 color: '#2ed573',
80 patterns: /ECONNRESET|ECONNREFUSED|socket hang up|fetch failed|getaddrinfo|ENOTFOUND|Connection refused|Could not resolve host|\b(?:HTTP|status)\s*(?:code\s*)?(?:429|5\d\d)\b|Too Many Requests|Bad Gateway|Service Unavailable/i,
81 symptom: 'เรียก API เทือเดียวกัน แต่ผลบ่คือกัน บางเทือได้ บางเทือบ่ได้ วันดีคืนดีกะหลอก!',
82 cause: 'ต้นทางบ่เสถียร, ปลายทางยังบ่เปิด, DNS ผิด, โดนจำกัด rate',
83 fix: 'เช็คว่าเซิร์ฟเวอร์เปิดอยู่ไหม, ใส่ retry + backoff, เคารพ 429 (Retry-After), ใส่ circuit breaker',
84 chant: 'แม่ตานีเอ้ย ขอ API ดีๆ ให้ลูกหลานแหน่เด้อ สาธุ!',
85 isan: 'API มื้อนี้คือผีนางตานี งามแต่ใจบ่ดี!',
86 debug: 'curl -v <url> | nslookup <host> | ดู status page ของผู้ให้บริการ',
87 },
88 pret: {
89 name: 'เปรต',
90 bug: 'Stack Overflow',
91 emoji: '📏',
92 color: '#ff6b81',
93 patterns: /Maximum call stack size exceeded|RecursionError|StackOverflowError|stack overflow|too much recursion/i,
94 symptom: 'Stack สูงคือเปรต สูงลิบลิ่ว แต่ปากนิดเดียว recursion บ่มีที่จบ กรรมหนักหลาย!',
95 cause: 'เรียกตัวเองบ่หยุด, JSON/object วนเป็นวง, setter เรียกตัวเอง',
96 fix: 'ใส่เงื่อนไขหยุด, เปลี่ยน recursion เป็น loop, จำกัดความลึก',
97 chant: 'อโหสิกรรม เปรตเอ้ย ลงมาเด้อ ลงมา iterative!',
98 isan: 'สูงปานเปรต แต่กินบ่ได้จักเทือ!',
99 debug: 'ดู stack trace ว่าฟังก์ชันไหนซ้ำ | console.trace()',
100 },
101 phi_am: {
102 name: 'ผีอำ',
103 bug: 'Deadlock',
104 emoji: '🛌',
105 color: '#5352ed',
106 patterns: /deadlock|all goroutines are asleep|Lock wait timeout exceeded/i,
107 symptom: 'โปรแกรมค้าง ขยับบ่ได้ คือถูกผีอำนั่งทับหน้าอก! ล็อกสองตัวแย่งกัน!',
108 cause: 'ล็อกไขว้กัน A รอ B, B รอ A คือเกวียนสองเล่มเจอกันทางแคบ บ่มีไผยอมถอย',
109 fix: 'กำหนดลำดับการล็อกให้ตรงกัน, ใส่ timeout, ลดขอบเขตของ transaction',
110 chant: 'อย่าอำเด้อ ปล่อยเด้อ ตื่น! ตื่น! ตื่น!',
111 isan: 'อำคักอำแหน่ ขยับบ่อได้!',
112 debug: 'jstack | SELECT * FROM pg_locks | SHOW ENGINE INNODB STATUS',
113 },
114 phi_tai_hong: {
115 name: 'ผีตายโหง',
116 bug: 'Crash / Segfault / Panic',
117 emoji: '💀',
118 color: '#ffa502',
119 patterns: /Segmentation fault|SIGSEGV|core dumped|panicked at|Abort trap|SIGABRT|fatal error:|Fatal Python error|EXC_BAD_ACCESS/i,
120 symptom: 'ดับปุ๊บปั๊บ บ่ทันได้ร่ำลา! ตายกลางโค้ด บ่ได้สั่งเสีย!',
121 cause: 'native module พัง, Rust panic, C/C++ ลืมเช็ค null, version ของ binary บ่ตรงกับระบบ',
122 fix: 'ติดตั้ง native module ใหม่ (npm rebuild), อ่าน backtrace, ใส่ RUST_BACKTRACE=1, ใช้ gdb/lldb',
123 chant: 'ไปสู่ที่ชอบที่ชอบเด้อ อย่ามา crash แถวนี้อีก สาธุ!',
124 isan: 'ตายโหงคักๆ บ่มีไผตั้งโตทัน!',
125 debug: 'RUST_BACKTRACE=1 | gdb ./app core | npm rebuild',
126 },
127 phi_phong: {
128 name: 'ผีโพง',
129 bug: 'ทรัพยากรหมด (ดิสก์ / ไฟล์ที่เปิด)',
130 emoji: '🔥',
131 color: '#ff7f50',
132 patterns: /ENOSPC|No space left on device|EMFILE|too many open files|ENFILE|System limit for number of file watchers/i,
133 symptom: 'เครื่องร้อนคือไฟโพง! ดิสก์เต็มหรือเปิดไฟล์ค้างไว้จนระบบบ่ไหว',
134 cause: 'log/cache บวม, node_modules หลายชุด, watcher เปิดไฟล์เยอะเกิน',
135 fix: 'ลบ cache/log ที่บ่ใช้, เพิ่ม ulimit / fs.inotify.max_user_watches, ปิด watcher ที่บ่จำเป็น',
136 chant: 'ดับไฟ ดับไฟ ผีโพงเอ้ย ดับซะ!',
137 isan: 'ฮ้อนคัก ฮ้อนแหน่ คือผีโพงลง!',
138 debug: 'df -h | du -sh * | ulimit -n',
139 },
140 nang_takian: {
141 name: 'นางตะเคียน',
142 bug: 'Race Condition / ไฟล์ถูกล็อก',
143 emoji: '🌳',
144 color: '#7bed9f',
145 patterns: /race condition|DATA RACE|ThreadSanitizer|EBUSY|resource busy or locked|database is locked|another git process|index\.lock|being used by another process/i,
146 symptom: 'บางทีได้บางทีบ่ได้ รันเครื่องเฮาได้ รันเครื่องเพื่อนตาย คือนางตะเคียนเลือกคนสิง!',
147 cause: 'สองงานแย่งไฟล์/ตัวแปรเดียวกัน, ลืม lock, โปรแกรมอื่นเปิดไฟล์ค้าง',
148 fix: 'ใส่ lock / transaction, รันทีละงาน, ปิดโปรแกรมที่จับไฟล์ไว้, ลบ .git/index.lock ถ้าไม่มี git ตัวอื่นรันอยู่',
149 chant: 'แม่ตะเคียนเอ้ย ให้โค้ดมันเรียงกันแหน่ อย่าแย่งกัน!',
150 isan: 'แย่งกันคือผีแย่งผ้า!',
151 debug: 'go test -race | ดูว่า process ไหนจับไฟล์ (Resource Monitor / lsof)',
152 },
153 phi_phrai: {
154 name: 'ผีพราย',
155 bug: 'Port ถูกยึด / Zombie Process',
156 emoji: '👶',
157 color: '#70a1ff',
158 patterns: /EADDRINUSE|address already in use|Only one usage of each socket address|port \d+ is (?:already )?in use|defunct/i,
159 symptom: 'โปรเซสเก่าตายแล้วแต่บ่ไปเกิด ยังยึดพอร์ตไว้อยู่!',
160 cause: 'dev server ตัวเก่ายังรันค้าง, ปิด terminal แต่ process ลูกยังอยู่',
161 fix: 'หา process ที่ใช้พอร์ตแล้วปิด (Mod 18: /pain port) หรือเปลี่ยนพอร์ต',
162 chant: 'ไปเกิดเด้อลูก ไปสู่สุคติเด้อ อย่ามาลอยอยู่นี่!',
163 isan: 'ตายแล้วบ่ไปไส ลอยอยู่นี่หละ!',
164 debug: 'netstat -ano | findstr :3000 | lsof -i :3000',
165 },
166 kongkoi: {
167 name: 'ผีกองกอย',
168 bug: 'หาไฟล์/โมดูล/คำสั่งไม่เจอ',
169 emoji: '🦶',
170 color: '#a29bfe',
171 patterns: /ENOENT|No such file or directory|Cannot find module|Module not found|ModuleNotFoundError|No module named|command not found|is not recognized as an internal or external command|cannot find the path/i,
172 symptom: 'ผีกองกอยขาเดียวกระโดดหลงป่า หาทางบ่เจอ! path ผิด หรือของยังบ่ได้ติดตั้ง',
173 cause: 'ยังบ่ npm install / pip install, พิมพ์ path ผิด, รันจากโฟลเดอร์ผิด, ลืม activate venv',
174 fix: 'ติดตั้ง dependency, เช็ค cwd และตัวสะกด path, เช็คว่าคำสั่งอยู่ใน PATH',
175 chant: 'กองกอยเอ้ย กระโดดกลับทางเก่าเด้อ ทางนี้แหละ!',
176 isan: 'หลงป่าแล้วอ้าย บ่ฮู้ทางออก!',
177 debug: 'pwd | ls | npm ls <pkg> | which <cmd> / where <cmd>',
178 },
179 fao_sap: {
180 name: 'ผีเฝ้าทรัพย์',
181 bug: 'ไม่มีสิทธิ์ (Permission / Auth)',
182 emoji: '🗝️',
183 color: '#ffd32a',
184 patterns: /EACCES|EPERM|Permission denied|Access is denied|Unauthorized|\b401\b|\b403\b|Forbidden|authentication failed|invalid api key/i,
185 symptom: 'ผีเฝ้าทรัพย์ขวางประตู บ่ให้เข้า! ไม่มีสิทธิ์อ่าน/เขียน หรือ token ใช้บ่ได้',
186 cause: 'สิทธิ์ไฟล์บ่พอ, token หมดอายุหรือผิด, ไฟล์ถูกเปิดค้างโดยโปรแกรมอื่น (Windows)',
187 fix: 'เช็คเจ้าของไฟล์/สิทธิ์, login ใหม่หรือเปลี่ยน token, อย่าแก้ด้วย sudo/chmod 777 แบบเหวี่ยงแห',
188 chant: 'ขออนุญาตเจ้าที่เจ้าทาง ลูกช้างมาดีเด้อ!',
189 isan: 'บ่ได้รับอนุญาต อย่าซิเข้าเด้อ!',
190 debug: 'ls -l / icacls <file> | gh auth status | เช็ค env ของ token',
191 },
192 mae_nak: {
193 name: 'แม่นาค',
194 bug: 'null / undefined',
195 emoji: '🫳',
196 color: '#e84393',
197 patterns: /Cannot read propert(?:y|ies) of (?:undefined|null)|undefined is not (?:a function|an object)|is not a function|NoneType' object|null pointer|NullPointerException|nil pointer dereference|AttributeError/i,
198 symptom: 'แม่นาคยื่นมือยาวไปเก็บมะนาวที่บ่มี! เรียกใช้ของที่ยังเป็น null/undefined',
199 cause: 'ข้อมูลยังโหลดบ่เสร็จ, ชื่อ property ผิด, API ตอบรูปแบบบ่คือที่คิด',
200 fix: 'เช็คค่าก่อนใช้ (?. หรือ if), log ค่าจริงออกมาดู, กำหนด type ให้ชัด',
201 chant: 'พี่มากขา ของบ่มี อย่าเอื้อมไปเอาเด้อ!',
202 isan: 'เอื้อมไปเอาของบ่มี มือยาวปานใดกะบ่ได้!',
203 debug: 'console.log(JSON.stringify(x)) ก่อนบรรทัดที่พัง | เปิด strictNullChecks',
204 },
205}
206
207interface Stats {
208 seen: Record<string, number>
209 exorcised: number
210}
211
212let stats: Stats = { seen: {}, exorcised: 0 }
213let enabled = true
214// คำสั่งที่ยังมีผีสิงอยู่ (ถ้ารันผ่านภายหลัง = ปราบสำเร็จ)
215let haunted = new Map<string, string>()
216let lastError: { command: string; text: string; ghost?: string } | undefined
217
218function detect(text: string): string | undefined {
219 return Object.keys(GHOSTS).find(id => GHOSTS[id]?.patterns.test(text))
220}
221
222function karmaTitle(): string {
223 const k = stats.exorcised
224 if (k >= 50) return '🙏 หมอธรรมระดับเซียน'
225 if (k >= 20) return '📿 หมอผีประจำหมู่บ้าน'
226 if (k >= 5) return '🕯️ ลูกศิษย์หมอธรรม'
227 return '🌱 มือใหม่หัดไล่ผี'
228}
229
230function log($: EngineInterface, text: string): void {
231 try {
232 $.ui.log(text)
233 } catch {}
234}
235
236async function save($: EngineInterface): Promise<void> {
237 await $.store.set('stats', stats)
238}
239
240// ==========================================================================
241// บทที่ 2: ผีปรากฏตัว - บรรทัดเดียวพอ ไม่รก รายละเอียดดูด้วย /ghost <ชื่อ>
242// ==========================================================================
243function apparition(id: string, g: Ghost): string {
244 return `${g.emoji} ${g.name}มาแล้ว! (${g.bug}) ${g.isan}\n อาการ: ${g.symptom}\n แก้: ${g.fix}\n ดูเพิ่ม: /ghost ${id} · ให้ Claude แปล error: /ghost explain`
245}
246
247function detail(id: string, g: Ghost): string {
248 return [
249 `${g.emoji} ${g.name} — ${g.bug}`,
250 ` พบแล้ว ${stats.seen[id] ?? 0} ครั้ง`,
251 '',
252 `😱 อาการ: ${g.symptom}`,
253 `🔍 สาเหตุ: ${g.cause}`,
254 `🛠️ วิธีแก้: ${g.fix}`,
255 `🧪 คำสั่งช่วย: ${g.debug}`,
256 `📿 คาถา: ${g.chant}`,
257 `🗣️ อีสาน: ${g.isan}`,
258 ].join('\n')
259}
260
261const HELP = `👻 Ghost Debug TH (Mod 49)
262========================================
263/ghost ทำเนียบผี + ค่าบุญ
264/ghost <ชื่อผี> รายละเอียด เช่น /ghost krasue
265/ghost last error ล่าสุดที่ผีมาสิง
266/ghost explain ให้ Claude แปล error ล่าสุดเป็นภาษาคน
267/ghost on|off เปิด/ปิดผี (ตอนนี้: STATE)
268/ghost reset confirm ล้างสถิติ`
269
270export const register: Register = on => {
271 on('session.start', async ($, e, next) => {
272 stats = { seen: {}, exorcised: 0, ...((await $.store.get('stats')) as Stats | undefined) }
273 enabled = ((await $.store.get('enabled')) as boolean | undefined) ?? true
274 haunted = new Map()
275 await $.command.register({ name: 'ghost', description: 'ผีไทยมาบอก error: อาการ สาเหตุ วิธีแก้ และแปล error เป็นภาษาคน (/ghost help)', argumentHint: '[ชื่อผี]|last|explain|on|off' })
276 return next(e)
277 })
278
279 // ==========================================================================
280 // บทที่ 3: ดูผลลัพธ์ Bash - พังเมื่อไหร่ ผีมาเมื่อนั้น / ผ่านเมื่อไหร่ ปราบได้
281 // ==========================================================================
282 on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
283 const r = await next(e)
284 if (!enabled || r.deny !== undefined) return r
285 const key = e.command.trim()
286 if (!r.isError) {
287 const id = haunted.get(key)
288 if (id) {
289 haunted.delete(key)
290 stats = { ...stats, exorcised: stats.exorcised + 1 }
291 await save($)
292 log($, `🙏 ปราบ${GHOSTS[id]?.name ?? 'ผี'}สำเร็จ! ได้บุญ +1 (รวม ${stats.exorcised}) ${karmaTitle()}`)
293 }
294 return r
295 }
296 const text = r.text ?? ''
297 const id = detect(text)
298 lastError = { command: key, text: text.slice(-4000), ghost: id }
299 const g = id ? GHOSTS[id] : undefined
300 if (id && g) {
301 stats = { ...stats, seen: { ...stats.seen, [id]: (stats.seen[id] ?? 0) + 1 } }
302 haunted.set(key, id)
303 await save($)
304 log($, apparition(id, g))
305 $.ui.toast(`${g.emoji} ${g.name}: ${g.bug}`)
306 }
307 return r
308 })
309
310 on('command.run', { command: 'ghost' }, async ($, e) => {
311 const [sub = '', ...rest] = e.args.trim().split(/\s+/).filter(Boolean)
312 switch (sub) {
313 case '': {
314 const rows = Object.entries(GHOSTS).map(([id, g]) => `${g.emoji} ${g.name.padEnd(12)} ${g.bug.padEnd(30)} พบ ${stats.seen[id] ?? 0} (${id})`)
315 const total = Object.values(stats.seen).reduce((s, n) => s + n, 0)
316 return { text: [`👻 ทำเนียบผีประจำบั๊ก — พบผี ${total} ครั้ง ปราบได้ ${stats.exorcised} ครั้ง ${karmaTitle()}`, '', ...rows, '', 'รายละเอียด: /ghost <id>'].join('\n') }
317 }
318 case 'help':
319 return { text: HELP.replace('STATE', enabled ? 'เปิด' : 'ปิด') }
320 case 'on':
321 case 'off':
322 enabled = sub === 'on'
323 await $.store.set('enabled', enabled)
324 return { text: enabled ? '👻 ผีกลับมาเฝ้าแล้ว' : '🙏 ผีไปพักแล้ว (/ghost on เพื่อเรียกกลับ)' }
325 case 'last': {
326 if (!lastError) return { text: 'ยังไม่มี error ใน session นี้ 🙏' }
327 const g = lastError.ghost ? GHOSTS[lastError.ghost] : undefined
328 return { text: `$ ${lastError.command}\n${lastError.text.slice(-1500)}\n\n${g ? `${g.emoji} ผีที่สิง: ${g.name} (${g.bug})` : '❓ ผีบ่ฮู้จัก — ลอง /ghost explain'}` }
329 }
330 case 'explain': {
331 // ==================================================================
332 // บทที่ 4: แปล error เป็นภาษาคนด้วย Haiku (ใช้ token นิดหน่อย จึงเรียกเมื่อสั่งเท่านั้น)
333 // ==================================================================
334 if (!lastError) return { text: 'ยังไม่มี error ให้แปล' }
335 const system = 'คุณเป็นผู้ช่วยนักพัฒนา อธิบาย error เป็นภาษาไทยที่เข้าใจง่าย ตอบไม่เกิน 5 บรรทัด: 1) error นี้หมายความว่าอะไร 2) สาเหตุที่น่าจะเป็น 3) สิ่งที่ควรลองทำก่อน ห้ามเดาเกินข้อมูลที่ให้มา'
336 const prompt = `คำสั่ง: ${lastError.command}\n\nผลลัพธ์:\n${lastError.text.slice(-3000)}`
337 const g = lastError.ghost ? GHOSTS[lastError.ghost] : undefined
338 for (const model of ['haiku', await $.session.model()]) {
339 const r = await $.model.complete({ model, system, prompt, maxTokens: 400 }).catch(() => undefined)
340 if (r?.isAnswered) return { text: `${g ? `${g.emoji} ${g.name}กระซิบว่า…` : '🔍 ถอดรหัส error'}\n\n${r.text.trim()}` }
341 }
342 return { text: '❌ ถาม Claude ไม่สำเร็จ ลองใหม่อีกครั้ง' }
343 }
344 case 'reset':
345 if (rest[0] !== 'confirm') return { text: 'ล้างสถิติทั้งหมด? พิมพ์ /ghost reset confirm' }
346 stats = { seen: {}, exorcised: 0 }
347 await save($)
348 return { text: '🧹 ล้างสถิติแล้ว' }
349 default: {
350 const g = GHOSTS[sub]
351 return { text: g ? detail(sub, g) : `บ่ฮู้จักผี "${sub}"\n${HELP.replace('STATE', enabled ? 'เปิด' : 'ปิด')}` }
352 }
353 }
354 })
355}
356
357/**
358 * ========================================================================
359 * การบ้าน / ต่อยอด
360 * ========================================================================
361 * 1. เพิ่มผีของคุณเอง เช่น ผีกะ = Dependency Hell (เวอร์ชันชนกัน)
362 * 2. ให้ผีตามดู error ในผลลัพธ์ของ tool อื่นด้วย (เช่น MCP)
363 * 3. เปิดแผงวาดผีตัวใหญ่แบบ ASCII ตอนเจอผีระดับหลอนจัด (crash, OOM)
364 */
365